QGIS API Documentation  3.24.2-Tisler (13c1a02865)
qgspallabeling.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgspallabeling.cpp
3  Smart labeling for vector layers
4  -------------------
5  begin : June 2009
6  copyright : (C) Martin Dobias
7  email : wonder dot sk at gmail dot com
8 
9  ***************************************************************************
10  * *
11  * This program is free software; you can redistribute it and/or modify *
12  * it under the terms of the GNU General Public License as published by *
13  * the Free Software Foundation; either version 2 of the License, or *
14  * (at your option) any later version. *
15  * *
16  ***************************************************************************/
17 
18 #include "qgspallabeling.h"
19 #include "qgstextlabelfeature.h"
20 #include "qgsunittypes.h"
21 #include "qgsexception.h"
22 #include "qgsapplication.h"
23 #include "qgsstyle.h"
24 
25 #include <list>
26 
27 #include "pal/pal.h"
28 #include "pal/feature.h"
29 #include "pal/layer.h"
30 #include "pal/palexception.h"
31 #include "pal/problem.h"
32 #include "pal/labelposition.h"
33 
34 #include <cmath>
35 
36 #include <QApplication>
37 #include <QByteArray>
38 #include <QString>
39 #include <QFontMetrics>
40 #include <QTime>
41 #include <QPainter>
42 #if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
43 #include <QDesktopWidget>
44 #else
45 #include <QScreen>
46 #include <QWidget>
47 #endif
48 #include <QTextBoundaryFinder>
49 
50 #include "diagram/qgsdiagram.h"
51 #include "qgsdiagramrenderer.h"
52 #include "qgsfontutils.h"
53 #include "qgslabelsearchtree.h"
54 #include "qgsexpression.h"
55 #include "qgslabelingengine.h"
56 #include "qgsvectorlayerlabeling.h"
57 #include "qgstextrendererutils.h"
58 #include "qgstextfragment.h"
59 #include "qgsmultisurface.h"
60 #include "qgslogger.h"
61 #include "qgsvectorlayer.h"
62 #include "qgsvectordataprovider.h"
65 #include "qgsgeometry.h"
66 #include "qgsreferencedgeometry.h"
67 #include "qgsmarkersymbollayer.h"
68 #include "qgspainting.h"
69 #include "qgsproject.h"
70 #include "qgsproperty.h"
71 #include "qgssymbollayerutils.h"
73 #include "qgscurvepolygon.h"
74 #include "qgsmessagelog.h"
75 #include "qgsgeometrycollection.h"
76 #include "callouts/qgscallout.h"
78 #include "qgsvectortilelayer.h"
80 
81 using namespace pal;
82 
83 // -------------
84 
85 /* ND: Default point label position priority. These are set to match variants of the ideal placement priority described
86  in "Making Maps", Krygier & Wood (2011) (p216),
87  "Elements of Cartography", Robinson et al (1995)
88  and "Designing Better Maps", Brewer (2005) (p76)
89  Note that while they agree on positions 1-4, 5-8 are more contentious so I've selected these placements
90  based on my preferences, and to follow Krygier and Wood's placements more closer. (I'm not going to disagree
91  with Denis Wood on anything cartography related...!)
92 */
93 typedef QVector< QgsPalLayerSettings::PredefinedPointPosition > PredefinedPointPositionVector;
95 {
104 } ) )
105 //debugging only - don't use these placements by default
106 /* << QgsPalLayerSettings::TopSlightlyLeft
107 << QgsPalLayerSettings::BottomSlightlyLeft;
108 << QgsPalLayerSettings::TopMiddle
109 << QgsPalLayerSettings::BottomMiddle;*/
110 
111 Q_GLOBAL_STATIC( QgsPropertiesDefinition, sPropertyDefinitions )
112 
113 void QgsPalLayerSettings::initPropertyDefinitions()
114 {
115  if ( !sPropertyDefinitions()->isEmpty() )
116  return;
117 
118  const QString origin = QStringLiteral( "labeling" );
119 
120  *sPropertyDefinitions() = QgsPropertiesDefinition
121  {
122  { QgsPalLayerSettings::Size, QgsPropertyDefinition( "Size", QObject::tr( "Font size" ), QgsPropertyDefinition::DoublePositive, origin ) },
123  { QgsPalLayerSettings::Bold, QgsPropertyDefinition( "Bold", QObject::tr( "Bold style" ), QgsPropertyDefinition::Boolean, origin ) },
124  { QgsPalLayerSettings::Italic, QgsPropertyDefinition( "Italic", QObject::tr( "Italic style" ), QgsPropertyDefinition::Boolean, origin ) },
125  { QgsPalLayerSettings::Underline, QgsPropertyDefinition( "Underline", QObject::tr( "Draw underline" ), QgsPropertyDefinition::Boolean, origin ) },
126  { QgsPalLayerSettings::Color, QgsPropertyDefinition( "Color", QObject::tr( "Text color" ), QgsPropertyDefinition::ColorNoAlpha, origin ) },
127  { QgsPalLayerSettings::Strikeout, QgsPropertyDefinition( "Strikeout", QObject::tr( "Draw strikeout" ), QgsPropertyDefinition::Boolean, origin ) },
128  {
129  QgsPalLayerSettings::Family, QgsPropertyDefinition( "Family", QgsPropertyDefinition::DataTypeString, QObject::tr( "Font family" ), QObject::tr( "string " ) + QObject::tr( "[<b>family</b>|<b>family[foundry]</b>],<br>"
130  "e.g. Helvetica or Helvetica [Cronyx]" ), origin )
131  },
132  {
133  QgsPalLayerSettings::FontStyle, QgsPropertyDefinition( "FontStyle", QgsPropertyDefinition::DataTypeString, QObject::tr( "Font style" ), QObject::tr( "string " ) + QObject::tr( "[<b>font style name</b>|<b>Ignore</b>],<br>"
134  "e.g. Bold Condensed or Light Italic" ), origin )
135  },
136  { QgsPalLayerSettings::FontSizeUnit, QgsPropertyDefinition( "FontSizeUnit", QObject::tr( "Font size units" ), QgsPropertyDefinition::RenderUnits, origin ) },
137  { QgsPalLayerSettings::FontTransp, QgsPropertyDefinition( "FontTransp", QObject::tr( "Text transparency" ), QgsPropertyDefinition::Opacity, origin ) },
138  { QgsPalLayerSettings::FontOpacity, QgsPropertyDefinition( "FontOpacity", QObject::tr( "Text opacity" ), QgsPropertyDefinition::Opacity, origin ) },
139  { QgsPalLayerSettings::FontStretchFactor, QgsPropertyDefinition( "FontStretchFactor", QObject::tr( "Font stretch factor" ), QgsPropertyDefinition::IntegerPositiveGreaterZero, origin ) },
140  { QgsPalLayerSettings::FontCase, QgsPropertyDefinition( "FontCase", QgsPropertyDefinition::DataTypeString, QObject::tr( "Font case" ), QObject::tr( "string " ) + QStringLiteral( "[<b>NoChange</b>|<b>Upper</b>|<br><b>Lower</b>|<b>Title</b>|<b>Capitalize</b>|<b>SmallCaps</b>|<b>AllSmallCaps</b>]" ), origin ) },
141  { QgsPalLayerSettings::FontLetterSpacing, QgsPropertyDefinition( "FontLetterSpacing", QObject::tr( "Letter spacing" ), QgsPropertyDefinition::Double, origin ) },
142  { QgsPalLayerSettings::FontWordSpacing, QgsPropertyDefinition( "FontWordSpacing", QObject::tr( "Word spacing" ), QgsPropertyDefinition::Double, origin ) },
143  { QgsPalLayerSettings::FontBlendMode, QgsPropertyDefinition( "FontBlendMode", QObject::tr( "Text blend mode" ), QgsPropertyDefinition::BlendMode, origin ) },
144  { QgsPalLayerSettings::MultiLineWrapChar, QgsPropertyDefinition( "MultiLineWrapChar", QObject::tr( "Wrap character" ), QgsPropertyDefinition::String, origin ) },
145  { QgsPalLayerSettings::AutoWrapLength, QgsPropertyDefinition( "AutoWrapLength", QObject::tr( "Automatic word wrap line length" ), QgsPropertyDefinition::IntegerPositive, origin ) },
146  { QgsPalLayerSettings::MultiLineHeight, QgsPropertyDefinition( "MultiLineHeight", QObject::tr( "Line height" ), QgsPropertyDefinition::DoublePositive, origin ) },
147  { QgsPalLayerSettings::MultiLineAlignment, QgsPropertyDefinition( "MultiLineAlignment", QgsPropertyDefinition::DataTypeString, QObject::tr( "Line alignment" ), QObject::tr( "string " ) + "[<b>Left</b>|<b>Center</b>|<b>Right</b>|<b>Follow</b>]", origin ) },
148  { QgsPalLayerSettings::TextOrientation, QgsPropertyDefinition( "TextOrientation", QgsPropertyDefinition::DataTypeString, QObject::tr( "Text orientation" ), QObject::tr( "string " ) + "[<b>horizontal</b>|<b>vertical</b>]", origin ) },
149  { QgsPalLayerSettings::DirSymbDraw, QgsPropertyDefinition( "DirSymbDraw", QObject::tr( "Draw direction symbol" ), QgsPropertyDefinition::Boolean, origin ) },
150  { QgsPalLayerSettings::DirSymbLeft, QgsPropertyDefinition( "DirSymbLeft", QObject::tr( "Left direction symbol" ), QgsPropertyDefinition::String, origin ) },
151  { QgsPalLayerSettings::DirSymbRight, QgsPropertyDefinition( "DirSymbRight", QObject::tr( "Right direction symbol" ), QgsPropertyDefinition::String, origin ) },
152  { QgsPalLayerSettings::DirSymbPlacement, QgsPropertyDefinition( "DirSymbPlacement", QgsPropertyDefinition::DataTypeString, QObject::tr( "Direction symbol placement" ), QObject::tr( "string " ) + "[<b>LeftRight</b>|<b>Above</b>|<b>Below</b>]", origin ) },
153  { QgsPalLayerSettings::DirSymbReverse, QgsPropertyDefinition( "DirSymbReverse", QObject::tr( "Reverse direction symbol" ), QgsPropertyDefinition::Boolean, origin ) },
154  { QgsPalLayerSettings::NumFormat, QgsPropertyDefinition( "NumFormat", QObject::tr( "Format as number" ), QgsPropertyDefinition::Boolean, origin ) },
155  { QgsPalLayerSettings::NumDecimals, QgsPropertyDefinition( "NumDecimals", QObject::tr( "Number of decimal places" ), QgsPropertyDefinition::IntegerPositive, origin ) },
156  { QgsPalLayerSettings::NumPlusSign, QgsPropertyDefinition( "NumPlusSign", QObject::tr( "Draw + sign" ), QgsPropertyDefinition::Boolean, origin ) },
157  { QgsPalLayerSettings::BufferDraw, QgsPropertyDefinition( "BufferDraw", QObject::tr( "Draw buffer" ), QgsPropertyDefinition::Boolean, origin ) },
158  { QgsPalLayerSettings::BufferSize, QgsPropertyDefinition( "BufferSize", QObject::tr( "Symbol size" ), QgsPropertyDefinition::DoublePositive, origin ) },
159  { QgsPalLayerSettings::BufferUnit, QgsPropertyDefinition( "BufferUnit", QObject::tr( "Buffer units" ), QgsPropertyDefinition::RenderUnits, origin ) },
160  { QgsPalLayerSettings::BufferColor, QgsPropertyDefinition( "BufferColor", QObject::tr( "Buffer color" ), QgsPropertyDefinition::ColorNoAlpha, origin ) },
161  { QgsPalLayerSettings::BufferTransp, QgsPropertyDefinition( "BufferTransp", QObject::tr( "Buffer transparency" ), QgsPropertyDefinition::Opacity, origin ) },
162  { QgsPalLayerSettings::BufferOpacity, QgsPropertyDefinition( "BufferOpacity", QObject::tr( "Buffer opacity" ), QgsPropertyDefinition::Opacity, origin ) },
163  { QgsPalLayerSettings::BufferJoinStyle, QgsPropertyDefinition( "BufferJoinStyle", QObject::tr( "Buffer join style" ), QgsPropertyDefinition::PenJoinStyle, origin ) },
164  { QgsPalLayerSettings::BufferBlendMode, QgsPropertyDefinition( "BufferBlendMode", QObject::tr( "Buffer blend mode" ), QgsPropertyDefinition::BlendMode, origin ) },
165 
166  { QgsPalLayerSettings::MaskEnabled, QgsPropertyDefinition( "MaskEnabled", QObject::tr( "Enable mask" ), QgsPropertyDefinition::Boolean, origin ) },
167  { QgsPalLayerSettings::MaskBufferSize, QgsPropertyDefinition( "MaskBufferSize", QObject::tr( "Mask buffer size" ), QgsPropertyDefinition::DoublePositive, origin ) },
168  { QgsPalLayerSettings::MaskBufferUnit, QgsPropertyDefinition( "MaskBufferUnit", QObject::tr( "Mask buffer unit" ), QgsPropertyDefinition::RenderUnits, origin ) },
169  { QgsPalLayerSettings::MaskOpacity, QgsPropertyDefinition( "MaskOpacity", QObject::tr( "Mask opacity" ), QgsPropertyDefinition::Opacity, origin ) },
170  { QgsPalLayerSettings::MaskJoinStyle, QgsPropertyDefinition( "MaskJoinStyle", QObject::tr( "Mask join style" ), QgsPropertyDefinition::PenJoinStyle, origin ) },
171 
172  { QgsPalLayerSettings::ShapeDraw, QgsPropertyDefinition( "ShapeDraw", QObject::tr( "Draw shape" ), QgsPropertyDefinition::Boolean, origin ) },
173  {
174  QgsPalLayerSettings::ShapeKind, QgsPropertyDefinition( "ShapeKind", QgsPropertyDefinition::DataTypeString, QObject::tr( "Shape type" ), QObject::tr( "string " ) + QStringLiteral( "[<b>Rectangle</b>|<b>Square</b>|<br>"
175  "<b>Ellipse</b>|<b>Circle</b>|<b>SVG</b>]" ), origin )
176  },
177  { QgsPalLayerSettings::ShapeSVGFile, QgsPropertyDefinition( "ShapeSVGFile", QObject::tr( "Shape SVG path" ), QgsPropertyDefinition::SvgPath, origin ) },
178  { QgsPalLayerSettings::ShapeSizeType, QgsPropertyDefinition( "ShapeSizeType", QgsPropertyDefinition::DataTypeString, QObject::tr( "Shape size type" ), QObject::tr( "string " ) + "[<b>Buffer</b>|<b>Fixed</b>]", origin ) },
179  { QgsPalLayerSettings::ShapeSizeX, QgsPropertyDefinition( "ShapeSizeX", QObject::tr( "Shape size (X)" ), QgsPropertyDefinition::Double, origin ) },
180  { QgsPalLayerSettings::ShapeSizeY, QgsPropertyDefinition( "ShapeSizeY", QObject::tr( "Shape size (Y)" ), QgsPropertyDefinition::Double, origin ) },
181  { QgsPalLayerSettings::ShapeSizeUnits, QgsPropertyDefinition( "ShapeSizeUnits", QObject::tr( "Shape size units" ), QgsPropertyDefinition::RenderUnits, origin ) },
182  { QgsPalLayerSettings::ShapeRotationType, QgsPropertyDefinition( "ShapeRotationType", QgsPropertyDefinition::DataTypeString, QObject::tr( "Shape rotation type" ), QObject::tr( "string " ) + "[<b>Sync</b>|<b>Offset</b>|<b>Fixed</b>]", origin ) },
183  { QgsPalLayerSettings::ShapeRotation, QgsPropertyDefinition( "ShapeRotation", QObject::tr( "Shape rotation" ), QgsPropertyDefinition::Rotation, origin ) },
184  { QgsPalLayerSettings::ShapeOffset, QgsPropertyDefinition( "ShapeOffset", QObject::tr( "Shape offset" ), QgsPropertyDefinition::Offset, origin ) },
185  { QgsPalLayerSettings::ShapeOffsetUnits, QgsPropertyDefinition( "ShapeOffsetUnits", QObject::tr( "Shape offset units" ), QgsPropertyDefinition::RenderUnits, origin ) },
186  { QgsPalLayerSettings::ShapeRadii, QgsPropertyDefinition( "ShapeRadii", QObject::tr( "Shape radii" ), QgsPropertyDefinition::Size2D, origin ) },
187  { QgsPalLayerSettings::ShapeRadiiUnits, QgsPropertyDefinition( "ShapeRadiiUnits", QObject::tr( "Symbol radii units" ), QgsPropertyDefinition::RenderUnits, origin ) },
188  { QgsPalLayerSettings::ShapeTransparency, QgsPropertyDefinition( "ShapeTransparency", QObject::tr( "Shape transparency" ), QgsPropertyDefinition::Opacity, origin ) },
189  { QgsPalLayerSettings::ShapeOpacity, QgsPropertyDefinition( "ShapeOpacity", QObject::tr( "Shape opacity" ), QgsPropertyDefinition::Opacity, origin ) },
190  { QgsPalLayerSettings::ShapeBlendMode, QgsPropertyDefinition( "ShapeBlendMode", QObject::tr( "Shape blend mode" ), QgsPropertyDefinition::BlendMode, origin ) },
191  { QgsPalLayerSettings::ShapeFillColor, QgsPropertyDefinition( "ShapeFillColor", QObject::tr( "Shape fill color" ), QgsPropertyDefinition::ColorWithAlpha, origin ) },
192  { QgsPalLayerSettings::ShapeStrokeColor, QgsPropertyDefinition( "ShapeBorderColor", QObject::tr( "Shape stroke color" ), QgsPropertyDefinition::ColorWithAlpha, origin ) },
193  { QgsPalLayerSettings::ShapeStrokeWidth, QgsPropertyDefinition( "ShapeBorderWidth", QObject::tr( "Shape stroke width" ), QgsPropertyDefinition::StrokeWidth, origin ) },
194  { QgsPalLayerSettings::ShapeStrokeWidthUnits, QgsPropertyDefinition( "ShapeBorderWidthUnits", QObject::tr( "Shape stroke width units" ), QgsPropertyDefinition::RenderUnits, origin ) },
195  { QgsPalLayerSettings::ShapeJoinStyle, QgsPropertyDefinition( "ShapeJoinStyle", QObject::tr( "Shape join style" ), QgsPropertyDefinition::PenJoinStyle, origin ) },
196  { QgsPalLayerSettings::ShadowDraw, QgsPropertyDefinition( "ShadowDraw", QObject::tr( "Draw shadow" ), QgsPropertyDefinition::Boolean, origin ) },
197  {
198  QgsPalLayerSettings::ShadowUnder, QgsPropertyDefinition( "ShadowUnder", QgsPropertyDefinition::DataTypeString, QObject::tr( "Symbol size" ), QObject::tr( "string " ) + QStringLiteral( "[<b>Lowest</b>|<b>Text</b>|<br>"
199  "<b>Buffer</b>|<b>Background</b>]" ), origin )
200  },
201  { QgsPalLayerSettings::ShadowOffsetAngle, QgsPropertyDefinition( "ShadowOffsetAngle", QObject::tr( "Shadow offset angle" ), QgsPropertyDefinition::Rotation, origin ) },
202  { QgsPalLayerSettings::ShadowOffsetDist, QgsPropertyDefinition( "ShadowOffsetDist", QObject::tr( "Shadow offset distance" ), QgsPropertyDefinition::DoublePositive, origin ) },
203  { QgsPalLayerSettings::ShadowOffsetUnits, QgsPropertyDefinition( "ShadowOffsetUnits", QObject::tr( "Shadow offset units" ), QgsPropertyDefinition::RenderUnits, origin ) },
204  { QgsPalLayerSettings::ShadowRadius, QgsPropertyDefinition( "ShadowRadius", QObject::tr( "Shadow blur radius" ), QgsPropertyDefinition::DoublePositive, origin ) },
205  { QgsPalLayerSettings::ShadowRadiusUnits, QgsPropertyDefinition( "ShadowRadiusUnits", QObject::tr( "Shadow blur units" ), QgsPropertyDefinition::RenderUnits, origin ) },
206  { QgsPalLayerSettings::ShadowTransparency, QgsPropertyDefinition( "ShadowTransparency", QObject::tr( "Shadow transparency" ), QgsPropertyDefinition::Opacity, origin ) },
207  { QgsPalLayerSettings::ShadowOpacity, QgsPropertyDefinition( "ShadowOpacity", QObject::tr( "Shadow opacity" ), QgsPropertyDefinition::Opacity, origin ) },
208  { QgsPalLayerSettings::ShadowScale, QgsPropertyDefinition( "ShadowScale", QObject::tr( "Shadow scale" ), QgsPropertyDefinition::IntegerPositive, origin ) },
209  { QgsPalLayerSettings::ShadowColor, QgsPropertyDefinition( "ShadowColor", QObject::tr( "Shadow color" ), QgsPropertyDefinition::ColorNoAlpha, origin ) },
210  { QgsPalLayerSettings::ShadowBlendMode, QgsPropertyDefinition( "ShadowBlendMode", QObject::tr( "Shadow blend mode" ), QgsPropertyDefinition::BlendMode, origin ) },
211 
212  { QgsPalLayerSettings::CentroidWhole, QgsPropertyDefinition( "CentroidWhole", QgsPropertyDefinition::DataTypeString, QObject::tr( "Centroid of whole shape" ), QObject::tr( "string " ) + "[<b>Visible</b>|<b>Whole</b>]", origin ) },
213  {
214  QgsPalLayerSettings::OffsetQuad, QgsPropertyDefinition( "OffsetQuad", QgsPropertyDefinition::DataTypeString, QObject::tr( "Offset quadrant" ), QObject::tr( "int<br>" ) + QStringLiteral( "[<b>0</b>=Above Left|<b>1</b>=Above|<b>2</b>=Above Right|<br>"
215  "<b>3</b>=Left|<b>4</b>=Over|<b>5</b>=Right|<br>"
216  "<b>6</b>=Below Left|<b>7</b>=Below|<b>8</b>=Below Right]" ), origin )
217  },
218  { QgsPalLayerSettings::OffsetXY, QgsPropertyDefinition( "OffsetXY", QObject::tr( "Offset" ), QgsPropertyDefinition::Offset, origin ) },
219  { QgsPalLayerSettings::OffsetUnits, QgsPropertyDefinition( "OffsetUnits", QObject::tr( "Offset units" ), QgsPropertyDefinition::RenderUnits, origin ) },
220  { QgsPalLayerSettings::LabelDistance, QgsPropertyDefinition( "LabelDistance", QObject::tr( "Label distance" ), QgsPropertyDefinition::Double, origin ) },
221  { QgsPalLayerSettings::DistanceUnits, QgsPropertyDefinition( "DistanceUnits", QObject::tr( "Label distance units" ), QgsPropertyDefinition::RenderUnits, origin ) },
222  { QgsPalLayerSettings::OffsetRotation, QgsPropertyDefinition( "OffsetRotation", QObject::tr( "Offset rotation" ), QgsPropertyDefinition::Rotation, origin ) },
223  { QgsPalLayerSettings::CurvedCharAngleInOut, QgsPropertyDefinition( "CurvedCharAngleInOut", QgsPropertyDefinition::DataTypeString, QObject::tr( "Curved character angles" ), QObject::tr( "double coord [<b>in,out</b> as 20.0-60.0,20.0-95.0]" ), origin ) },
224  { QgsPalLayerSettings::RepeatDistance, QgsPropertyDefinition( "RepeatDistance", QObject::tr( "Repeat distance" ), QgsPropertyDefinition::DoublePositive, origin ) },
225  { QgsPalLayerSettings::RepeatDistanceUnit, QgsPropertyDefinition( "RepeatDistanceUnit", QObject::tr( "Repeat distance unit" ), QgsPropertyDefinition::RenderUnits, origin ) },
226  { QgsPalLayerSettings::OverrunDistance, QgsPropertyDefinition( "OverrunDistance", QObject::tr( "Overrun distance" ), QgsPropertyDefinition::DoublePositive, origin ) },
227  { QgsPalLayerSettings::LineAnchorPercent, QgsPropertyDefinition( "LineAnchorPercent", QObject::tr( "Line anchor percentage, as fraction from 0.0 to 1.0" ), QgsPropertyDefinition::Double0To1, origin ) },
228  { QgsPalLayerSettings::LineAnchorClipping, QgsPropertyDefinition( "LineAnchorClipping", QgsPropertyDefinition::DataTypeString, QObject::tr( "Line anchor clipping mode" ), QObject::tr( "string " ) + QStringLiteral( "[<b>visible</b>|<b>entire</b>]" ), origin ) },
229  { QgsPalLayerSettings::Priority, QgsPropertyDefinition( "Priority", QgsPropertyDefinition::DataTypeNumeric, QObject::tr( "Label priority" ), QObject::tr( "double [0.0-10.0]" ), origin ) },
230  { QgsPalLayerSettings::IsObstacle, QgsPropertyDefinition( "IsObstacle", QObject::tr( "Feature is a label obstacle" ), QgsPropertyDefinition::Boolean, origin ) },
231  { QgsPalLayerSettings::ObstacleFactor, QgsPropertyDefinition( "ObstacleFactor", QgsPropertyDefinition::DataTypeNumeric, QObject::tr( "Obstacle factor" ), QObject::tr( "double [0.0-10.0]" ), origin ) },
232  {
233  QgsPalLayerSettings::PredefinedPositionOrder, QgsPropertyDefinition( "PredefinedPositionOrder", QgsPropertyDefinition::DataTypeString, QObject::tr( "Predefined position order" ), QObject::tr( "Comma separated list of placements in order of priority<br>" )
234  + QStringLiteral( "[<b>TL</b>=Top left|<b>TSL</b>=Top, slightly left|<b>T</b>=Top middle|<br>"
235  "<b>TSR</b>=Top, slightly right|<b>TR</b>=Top right|<br>"
236  "<b>L</b>=Left|<b>R</b>=Right|<br>"
237  "<b>BL</b>=Bottom left|<b>BSL</b>=Bottom, slightly left|<b>B</b>=Bottom middle|<br>"
238  "<b>BSR</b>=Bottom, slightly right|<b>BR</b>=Bottom right]" ), origin )
239  },
240  {
241  QgsPalLayerSettings::LinePlacementOptions, QgsPropertyDefinition( "LinePlacementFlags", QgsPropertyDefinition::DataTypeString, QObject::tr( "Line placement options" ), QObject::tr( "Comma separated list of placement options<br>" )
242  + QStringLiteral( "[<b>OL</b>=On line|<b>AL</b>=Above line|<b>BL</b>=Below line|<br>"
243  "<b>LO</b>=Respect line orientation]" ), origin )
244  },
245  { QgsPalLayerSettings::PolygonLabelOutside, QgsPropertyDefinition( "PolygonLabelOutside", QgsPropertyDefinition::DataTypeString, QObject::tr( "Label outside polygons" ), QObject::tr( "string " ) + "[<b>yes</b> (allow placing outside)|<b>no</b> (never place outside)|<b>force</b> (always place outside)]", origin ) },
246  { QgsPalLayerSettings::PositionX, QgsPropertyDefinition( "PositionX", QObject::tr( "Position (X)" ), QgsPropertyDefinition::Double, origin ) },
247  { QgsPalLayerSettings::PositionY, QgsPropertyDefinition( "PositionY", QObject::tr( "Position (Y)" ), QgsPropertyDefinition::Double, origin ) },
248  { QgsPalLayerSettings::PositionPoint, QgsPropertyDefinition( "PositionPoint", QgsPropertyDefinition::DataTypeString, QObject::tr( "Position (point)" ), QObject::tr( "A point geometry" ), origin ) },
249  { QgsPalLayerSettings::Hali, QgsPropertyDefinition( "Hali", QgsPropertyDefinition::DataTypeString, QObject::tr( "Horizontal alignment" ), QObject::tr( "string " ) + "[<b>Left</b>|<b>Center</b>|<b>Right</b>]", origin ) },
250  {
251  QgsPalLayerSettings::Vali, QgsPropertyDefinition( "Vali", QgsPropertyDefinition::DataTypeString, QObject::tr( "Vertical alignment" ), QObject::tr( "string " ) + QStringLiteral( "[<b>Bottom</b>|<b>Base</b>|<br>"
252  "<b>Half</b>|<b>Cap</b>|<b>Top</b>]" ), origin )
253  },
254  { QgsPalLayerSettings::Rotation, QgsPropertyDefinition( "Rotation", QObject::tr( "Label rotation (deprecated)" ), QgsPropertyDefinition::Rotation, origin ) },
255  { QgsPalLayerSettings::LabelRotation, QgsPropertyDefinition( "LabelRotation", QObject::tr( "Label rotation" ), QgsPropertyDefinition::Rotation, origin ) },
256  { QgsPalLayerSettings::ScaleVisibility, QgsPropertyDefinition( "ScaleVisibility", QObject::tr( "Scale based visibility" ), QgsPropertyDefinition::Boolean, origin ) },
257  { QgsPalLayerSettings::MinScale, QgsPropertyDefinition( "MinScale", QObject::tr( "Minimum scale (denominator)" ), QgsPropertyDefinition::Double, origin ) },
258  { QgsPalLayerSettings::MaxScale, QgsPropertyDefinition( "MaxScale", QObject::tr( "Maximum scale (denominator)" ), QgsPropertyDefinition::Double, origin ) },
259  { QgsPalLayerSettings::MinimumScale, QgsPropertyDefinition( "MinimumScale", QObject::tr( "Minimum scale (denominator)" ), QgsPropertyDefinition::Double, origin ) },
260  { QgsPalLayerSettings::MaximumScale, QgsPropertyDefinition( "MaximumScale", QObject::tr( "Maximum scale (denominator)" ), QgsPropertyDefinition::Double, origin ) },
261 
262  { QgsPalLayerSettings::FontLimitPixel, QgsPropertyDefinition( "FontLimitPixel", QObject::tr( "Limit font pixel size" ), QgsPropertyDefinition::Boolean, origin ) },
263  { QgsPalLayerSettings::FontMinPixel, QgsPropertyDefinition( "FontMinPixel", QObject::tr( "Minimum pixel size" ), QgsPropertyDefinition::IntegerPositive, origin ) },
264  { QgsPalLayerSettings::FontMaxPixel, QgsPropertyDefinition( "FontMaxPixel", QObject::tr( "Maximum pixel size" ), QgsPropertyDefinition::IntegerPositive, origin ) },
265  { QgsPalLayerSettings::ZIndex, QgsPropertyDefinition( "ZIndex", QObject::tr( "Label z-index" ), QgsPropertyDefinition::Double, origin ) },
266  { QgsPalLayerSettings::Show, QgsPropertyDefinition( "Show", QObject::tr( "Show label" ), QgsPropertyDefinition::Boolean, origin ) },
267  { QgsPalLayerSettings::AlwaysShow, QgsPropertyDefinition( "AlwaysShow", QObject::tr( "Always show label" ), QgsPropertyDefinition::Boolean, origin ) },
268  { QgsPalLayerSettings::CalloutDraw, QgsPropertyDefinition( "CalloutDraw", QObject::tr( "Draw callout" ), QgsPropertyDefinition::Boolean, origin ) },
269  { QgsPalLayerSettings::LabelAllParts, QgsPropertyDefinition( "LabelAllParts", QObject::tr( "Label all parts" ), QgsPropertyDefinition::Boolean, origin ) },
270  };
271 }
272 
273 Q_NOWARN_DEPRECATED_PUSH // because of deprecated members
275  : predefinedPositionOrder( *DEFAULT_PLACEMENT_ORDER() )
276  , mCallout( QgsCalloutRegistry::defaultCallout() )
277 {
278  initPropertyDefinitions();
279 
281 }
283 
284 Q_NOWARN_DEPRECATED_PUSH // because of deprecated members
286  : fieldIndex( 0 )
287  , mDataDefinedProperties( s.mDataDefinedProperties )
288 {
289  *this = s;
290 }
292 
294 {
295  if ( this == &s )
296  return *this;
297 
298  // copy only permanent stuff
299 
301 
302  // text style
303  fieldName = s.fieldName;
310 
311  // text formatting
312  wrapChar = s.wrapChar;
317  decimals = s.decimals;
318  plusSign = s.plusSign;
319 
320  // placement
321  placement = s.placement;
322  mPolygonPlacementFlags = s.mPolygonPlacementFlags;
328  xOffset = s.xOffset;
329  yOffset = s.yOffset;
332  dist = s.dist;
334  distUnits = s.distUnits;
338  mRotationUnit = s.mRotationUnit;
341  priority = s.priority;
345 
346  // rendering
355 
357  zIndex = s.zIndex;
358 
359  mFormat = s.mFormat;
360  mDataDefinedProperties = s.mDataDefinedProperties;
361 
362  mCallout.reset( s.mCallout ? s.mCallout->clone() : nullptr );
363 
364  mLineSettings = s.mLineSettings;
365  mObstacleSettings = s.mObstacleSettings;
366  mThinningSettings = s.mThinningSettings;
367 
371  layerType = s.layerType;
372 
373  mLegendString = s.mLegendString;
374 
375  mUnplacedVisibility = s.mUnplacedVisibility;
376 
377  return *this;
378 }
379 
380 bool QgsPalLayerSettings::prepare( QgsRenderContext &context, QSet<QString> &attributeNames, const QgsFields &fields, const QgsMapSettings &mapSettings, const QgsCoordinateReferenceSystem &crs )
381 {
382  if ( drawLabels )
383  {
384  if ( fieldName.isEmpty() )
385  {
386  return false;
387  }
388 
389  if ( isExpression )
390  {
391  QgsExpression exp( fieldName );
392  if ( exp.hasEvalError() )
393  {
394  QgsDebugMsgLevel( "Prepare error:" + exp.evalErrorString(), 4 );
395  return false;
396  }
397  }
398  else
399  {
400  // If we aren't an expression, we check to see if we can find the column.
401  if ( fields.lookupField( fieldName ) == -1 )
402  {
403  return false;
404  }
405  }
406  }
407 
408  mCurFields = fields;
409 
410  if ( drawLabels || mObstacleSettings.isObstacle() )
411  {
412  if ( drawLabels )
413  {
414  // add field indices for label's text, from expression or field
415  if ( isExpression )
416  {
417  // prepare expression for use in QgsPalLayerSettings::registerFeature()
419  exp->prepare( &context.expressionContext() );
420  if ( exp->hasEvalError() )
421  {
422  QgsDebugMsgLevel( "Prepare error:" + exp->evalErrorString(), 4 );
423  }
424  const auto referencedColumns = exp->referencedColumns();
425  for ( const QString &name : referencedColumns )
426  {
427  attributeNames.insert( name );
428  }
429  }
430  else
431  {
432  attributeNames.insert( fieldName );
433  }
434  }
435 
436  mDataDefinedProperties.prepare( context.expressionContext() );
437  // add field indices of data defined expression or field
438  attributeNames.unite( dataDefinedProperties().referencedFields( context.expressionContext() ) );
439  }
440 
441  // NOW INITIALIZE QgsPalLayerSettings
442 
443  // TODO: ideally these (non-configuration) members should get out of QgsPalLayerSettings to QgsVectorLayerLabelProvider::prepare
444  // (together with registerFeature() & related methods) and QgsPalLayerSettings just stores config
445 
446  // save the pal layer to our layer context (with some additional info)
447  fieldIndex = fields.lookupField( fieldName );
448 
449  xform = &mapSettings.mapToPixel();
451  if ( context.coordinateTransform().isValid() )
452  // this is context for layer rendering
453  ct = context.coordinateTransform();
454  else
455  {
456  // otherwise fall back to creating our own CT
457  ct = QgsCoordinateTransform( crs, mapSettings.destinationCrs(), mapSettings.transformContext() );
458  }
459  ptZero = xform->toMapCoordinates( 0, 0 );
460  ptOne = xform->toMapCoordinates( 1, 0 );
461 
462  // rect for clipping
463  QgsRectangle r1 = mapSettings.visibleExtent();
464  r1.grow( mapSettings.extentBuffer() );
466 
467  if ( !qgsDoubleNear( mapSettings.rotation(), 0.0 ) )
468  {
469  //PAL features are prerotated, so extent also needs to be unrotated
470  extentGeom.rotate( -mapSettings.rotation(), mapSettings.visibleExtent().center() );
471  }
472 
473  mFeatsSendingToPal = 0;
474 
476  {
477  mGeometryGeneratorExpression = QgsExpression( geometryGenerator );
478  mGeometryGeneratorExpression.prepare( &context.expressionContext() );
479  if ( mGeometryGeneratorExpression.hasParserError() )
480  {
481  QgsMessageLog::logMessage( mGeometryGeneratorExpression.parserErrorString(), QObject::tr( "Labeling" ) );
482  return false;
483  }
484 
485  const auto referencedColumns = mGeometryGeneratorExpression.referencedColumns();
486  for ( const QString &name : referencedColumns )
487  {
488  attributeNames.insert( name );
489  }
490  }
491  attributeNames.unite( mFormat.referencedFields( context ) );
492 
493  if ( mCallout )
494  {
495  const auto referencedColumns = mCallout->referencedFields( context );
496  for ( const QString &name : referencedColumns )
497  {
498  attributeNames.insert( name );
499  }
500  }
501 
502  return true;
503 }
504 
505 QSet<QString> QgsPalLayerSettings::referencedFields( const QgsRenderContext &context ) const
506 {
507  QSet<QString> referenced;
508  if ( drawLabels )
509  {
510  if ( isExpression )
511  {
512  referenced.unite( QgsExpression( fieldName ).referencedColumns() );
513  }
514  else
515  {
516  referenced.insert( fieldName );
517  }
518  }
519 
520  referenced.unite( mFormat.referencedFields( context ) );
521 
522  // calling referencedFields() with ignoreContext=true because in our expression context
523  // we do not have valid QgsFields yet - because of that the field names from expressions
524  // wouldn't get reported
525  referenced.unite( mDataDefinedProperties.referencedFields( context.expressionContext(), true ) );
526 
528  {
529  QgsExpression geomGeneratorExpr( geometryGenerator );
530  referenced.unite( geomGeneratorExpr.referencedColumns() );
531  }
532 
533  if ( mCallout )
534  {
535  referenced.unite( mCallout->referencedFields( context ) );
536  }
537 
538  return referenced;
539 }
540 
542 {
543  if ( mRenderStarted )
544  {
545  qWarning( "Start render called for when a previous render was already underway!!" );
546  return;
547  }
548 
550  {
551  // force horizontal orientation, other orientation modes aren't unsupported for curved placement
553  mDataDefinedProperties.property( QgsPalLayerSettings::TextOrientation ).setActive( false );
554  }
555 
556  if ( mCallout )
557  {
558  mCallout->startRender( context );
559  }
560 
561  mRenderStarted = true;
562 }
563 
565 {
566  if ( !mRenderStarted )
567  {
568  qWarning( "Stop render called for QgsPalLayerSettings without a startRender call!" );
569  return;
570  }
571 
572  if ( mCallout )
573  {
574  mCallout->stopRender( context );
575  }
576 
577  mRenderStarted = false;
578 }
579 
581 {
582  return mFormat.containsAdvancedEffects() || mCallout->containsAdvancedEffects();
583 }
584 
586 {
587  if ( mRenderStarted )
588  {
589  qWarning( "stopRender was not called on QgsPalLayerSettings object!" );
590  }
591 
592  // pal layer is deleted internally in PAL
593 
594  delete expression;
595 }
596 
597 
599 {
600  initPropertyDefinitions();
601  return *sPropertyDefinitions();
602 }
603 
605 {
606  if ( !expression )
607  {
608  expression = new QgsExpression( fieldName );
609  }
610  return expression;
611 }
612 
614 {
615  return mRotationUnit;
616 }
617 
619 {
620  mRotationUnit = angleUnit;
621 }
622 
623 QString updateDataDefinedString( const QString &value )
624 {
625  // TODO: update or remove this when project settings for labeling are migrated to better XML layout
626  QString newValue = value;
627  if ( !value.isEmpty() && !value.contains( QLatin1String( "~~" ) ) )
628  {
629  QStringList values;
630  values << QStringLiteral( "1" ); // all old-style values are active if not empty
631  values << QStringLiteral( "0" );
632  values << QString();
633  values << value; // all old-style values are only field names
634  newValue = values.join( QLatin1String( "~~" ) );
635  }
636 
637  return newValue;
638 }
639 
640 void QgsPalLayerSettings::readOldDataDefinedProperty( QgsVectorLayer *layer, QgsPalLayerSettings::Property p )
641 {
642  QString newPropertyName = "labeling/dataDefined/" + sPropertyDefinitions()->value( p ).name();
643  QVariant newPropertyField = layer->customProperty( newPropertyName, QVariant() );
644 
645  if ( !newPropertyField.isValid() )
646  return;
647 
648  QString ddString = newPropertyField.toString();
649 
650  if ( !ddString.isEmpty() && ddString != QLatin1String( "0~~0~~~~" ) )
651  {
652  // TODO: update this when project settings for labeling are migrated to better XML layout
653  QString newStyleString = updateDataDefinedString( ddString );
654  QStringList ddv = newStyleString.split( QStringLiteral( "~~" ) );
655 
656  bool active = ddv.at( 0 ).toInt();
657  if ( ddv.at( 1 ).toInt() )
658  {
659  mDataDefinedProperties.setProperty( p, QgsProperty::fromExpression( ddv.at( 2 ), active ) );
660  }
661  else
662  {
663  mDataDefinedProperties.setProperty( p, QgsProperty::fromField( ddv.at( 3 ), active ) );
664  }
665  }
666  else
667  {
668  // remove unused properties
669  layer->removeCustomProperty( newPropertyName );
670  }
671 }
672 
673 void QgsPalLayerSettings::readOldDataDefinedPropertyMap( QgsVectorLayer *layer, QDomElement *parentElem )
674 {
675  if ( !layer && !parentElem )
676  {
677  return;
678  }
679 
680  QgsPropertiesDefinition::const_iterator i = sPropertyDefinitions()->constBegin();
681  for ( ; i != sPropertyDefinitions()->constEnd(); ++i )
682  {
683  if ( layer )
684  {
685  // reading from layer's custom properties
686  readOldDataDefinedProperty( layer, static_cast< Property >( i.key() ) );
687  }
688  else if ( parentElem )
689  {
690  // reading from XML
691  QDomElement e = parentElem->firstChildElement( i.value().name() );
692  if ( !e.isNull() )
693  {
694  bool active = e.attribute( QStringLiteral( "active" ) ).compare( QLatin1String( "true" ), Qt::CaseInsensitive ) == 0;
695  bool isExpression = e.attribute( QStringLiteral( "useExpr" ) ).compare( QLatin1String( "true" ), Qt::CaseInsensitive ) == 0;
696  if ( isExpression )
697  {
698  mDataDefinedProperties.setProperty( i.key(), QgsProperty::fromExpression( e.attribute( QStringLiteral( "expr" ) ), active ) );
699  }
700  else
701  {
702  mDataDefinedProperties.setProperty( i.key(), QgsProperty::fromField( e.attribute( QStringLiteral( "field" ) ), active ) );
703  }
704  }
705  }
706  }
707 }
708 
709 void QgsPalLayerSettings::readFromLayerCustomProperties( QgsVectorLayer *layer )
710 {
711  if ( layer->customProperty( QStringLiteral( "labeling" ) ).toString() != QLatin1String( "pal" ) )
712  {
713  if ( layer->geometryType() == QgsWkbTypes::PointGeometry )
715 
716  // for polygons the "over point" (over centroid) placement is better than the default
717  // "around point" (around centroid) which is more suitable for points
718  if ( layer->geometryType() == QgsWkbTypes::PolygonGeometry )
720 
721  return; // there's no information available
722  }
723 
724  // NOTE: set defaults for newly added properties, for backwards compatibility
725 
726  drawLabels = layer->customProperty( QStringLiteral( "labeling/drawLabels" ), true ).toBool();
727 
728  mFormat.readFromLayer( layer );
729 
730  // text style
731  fieldName = layer->customProperty( QStringLiteral( "labeling/fieldName" ) ).toString();
732  isExpression = layer->customProperty( QStringLiteral( "labeling/isExpression" ) ).toBool();
734  previewBkgrdColor = QColor( layer->customProperty( QStringLiteral( "labeling/previewBkgrdColor" ), QVariant( "#ffffff" ) ).toString() );
736  QDomDocument doc( QStringLiteral( "substitutions" ) );
737  doc.setContent( layer->customProperty( QStringLiteral( "labeling/substitutions" ) ).toString() );
738  QDomElement replacementElem = doc.firstChildElement( QStringLiteral( "substitutions" ) );
739  substitutions.readXml( replacementElem );
740  useSubstitutions = layer->customProperty( QStringLiteral( "labeling/useSubstitutions" ) ).toBool();
741 
742  // text formatting
743  wrapChar = layer->customProperty( QStringLiteral( "labeling/wrapChar" ) ).toString();
744  autoWrapLength = layer->customProperty( QStringLiteral( "labeling/autoWrapLength" ) ).toInt();
745  useMaxLineLengthForAutoWrap = layer->customProperty( QStringLiteral( "labeling/useMaxLineLengthForAutoWrap" ), QStringLiteral( "1" ) ).toBool();
746 
747  multilineAlign = static_cast< MultiLineAlign >( layer->customProperty( QStringLiteral( "labeling/multilineAlign" ), QVariant( MultiFollowPlacement ) ).toUInt() );
748  mLineSettings.setAddDirectionSymbol( layer->customProperty( QStringLiteral( "labeling/addDirectionSymbol" ) ).toBool() );
749  mLineSettings.setLeftDirectionSymbol( layer->customProperty( QStringLiteral( "labeling/leftDirectionSymbol" ), QVariant( "<" ) ).toString() );
750  mLineSettings.setRightDirectionSymbol( layer->customProperty( QStringLiteral( "labeling/rightDirectionSymbol" ), QVariant( ">" ) ).toString() );
751  mLineSettings.setReverseDirectionSymbol( layer->customProperty( QStringLiteral( "labeling/reverseDirectionSymbol" ) ).toBool() );
752  mLineSettings.setDirectionSymbolPlacement( static_cast< QgsLabelLineSettings::DirectionSymbolPlacement >( layer->customProperty( QStringLiteral( "labeling/placeDirectionSymbol" ), QVariant( static_cast< int >( QgsLabelLineSettings::DirectionSymbolPlacement::SymbolLeftRight ) ) ).toUInt() ) );
753  formatNumbers = layer->customProperty( QStringLiteral( "labeling/formatNumbers" ) ).toBool();
754  decimals = layer->customProperty( QStringLiteral( "labeling/decimals" ) ).toInt();
755  plusSign = layer->customProperty( QStringLiteral( "labeling/plussign" ) ).toBool();
756 
757  // placement
758  placement = static_cast< Placement >( layer->customProperty( QStringLiteral( "labeling/placement" ) ).toInt() );
759  mLineSettings.setPlacementFlags( static_cast< QgsLabeling::LinePlacementFlags >( layer->customProperty( QStringLiteral( "labeling/placementFlags" ) ).toUInt() ) );
760  centroidWhole = layer->customProperty( QStringLiteral( "labeling/centroidWhole" ), QVariant( false ) ).toBool();
761  centroidInside = layer->customProperty( QStringLiteral( "labeling/centroidInside" ), QVariant( false ) ).toBool();
762  predefinedPositionOrder = QgsLabelingUtils::decodePredefinedPositionOrder( layer->customProperty( QStringLiteral( "labeling/predefinedPositionOrder" ) ).toString() );
763  if ( predefinedPositionOrder.isEmpty() )
764  predefinedPositionOrder = *DEFAULT_PLACEMENT_ORDER();
765  fitInPolygonOnly = layer->customProperty( QStringLiteral( "labeling/fitInPolygonOnly" ), QVariant( false ) ).toBool();
766  dist = layer->customProperty( QStringLiteral( "labeling/dist" ) ).toDouble();
767  distUnits = layer->customProperty( QStringLiteral( "labeling/distInMapUnits" ) ).toBool() ? QgsUnitTypes::RenderMapUnits : QgsUnitTypes::RenderMillimeters;
768  if ( layer->customProperty( QStringLiteral( "labeling/distMapUnitScale" ) ).toString().isEmpty() )
769  {
770  //fallback to older property
771  double oldMin = layer->customProperty( QStringLiteral( "labeling/distMapUnitMinScale" ), 0.0 ).toDouble();
772  distMapUnitScale.minScale = !qgsDoubleNear( oldMin, 0.0 ) ? 1.0 / oldMin : 0;
773  double oldMax = layer->customProperty( QStringLiteral( "labeling/distMapUnitMaxScale" ), 0.0 ).toDouble();
774  distMapUnitScale.maxScale = !qgsDoubleNear( oldMax, 0.0 ) ? 1.0 / oldMax : 0;
775  }
776  else
777  {
778  distMapUnitScale = QgsSymbolLayerUtils::decodeMapUnitScale( layer->customProperty( QStringLiteral( "labeling/distMapUnitScale" ) ).toString() );
779  }
780  offsetType = static_cast< OffsetType >( layer->customProperty( QStringLiteral( "labeling/offsetType" ), QVariant( FromPoint ) ).toUInt() );
781  quadOffset = static_cast< QuadrantPosition >( layer->customProperty( QStringLiteral( "labeling/quadOffset" ), QVariant( QuadrantOver ) ).toUInt() );
782  xOffset = layer->customProperty( QStringLiteral( "labeling/xOffset" ), QVariant( 0.0 ) ).toDouble();
783  yOffset = layer->customProperty( QStringLiteral( "labeling/yOffset" ), QVariant( 0.0 ) ).toDouble();
784  if ( layer->customProperty( QStringLiteral( "labeling/labelOffsetInMapUnits" ), QVariant( true ) ).toBool() )
786  else
788 
789  if ( layer->customProperty( QStringLiteral( "labeling/labelOffsetMapUnitScale" ) ).toString().isEmpty() )
790  {
791  //fallback to older property
792  double oldMin = layer->customProperty( QStringLiteral( "labeling/labelOffsetMapUnitMinScale" ), 0.0 ).toDouble();
793  labelOffsetMapUnitScale.minScale = !qgsDoubleNear( oldMin, 0.0 ) ? 1.0 / oldMin : 0;
794  double oldMax = layer->customProperty( QStringLiteral( "labeling/labelOffsetMapUnitMaxScale" ), 0.0 ).toDouble();
795  labelOffsetMapUnitScale.maxScale = !qgsDoubleNear( oldMax, 0 ) ? 1.0 / oldMax : 0;
796  }
797  else
798  {
799  labelOffsetMapUnitScale = QgsSymbolLayerUtils::decodeMapUnitScale( layer->customProperty( QStringLiteral( "labeling/labelOffsetMapUnitScale" ) ).toString() );
800  }
801 
802  QVariant tempAngle = layer->customProperty( QStringLiteral( "labeling/angleOffset" ), QVariant() );
803  if ( tempAngle.isValid() )
804  {
805  double oldAngle = layer->customProperty( QStringLiteral( "labeling/angleOffset" ), QVariant( 0.0 ) ).toDouble();
806  angleOffset = std::fmod( 360 - oldAngle, 360.0 );
807  }
808  else
809  {
810  angleOffset = layer->customProperty( QStringLiteral( "labeling/rotationAngle" ), QVariant( 0.0 ) ).toDouble();
811  }
812 
813  preserveRotation = layer->customProperty( QStringLiteral( "labeling/preserveRotation" ), QVariant( true ) ).toBool();
814  mRotationUnit = layer->customEnumProperty( QStringLiteral( "labeling/rotationUnit" ), QgsUnitTypes::AngleDegrees );
815  maxCurvedCharAngleIn = layer->customProperty( QStringLiteral( "labeling/maxCurvedCharAngleIn" ), QVariant( 25.0 ) ).toDouble();
816  maxCurvedCharAngleOut = layer->customProperty( QStringLiteral( "labeling/maxCurvedCharAngleOut" ), QVariant( -25.0 ) ).toDouble();
817  priority = layer->customProperty( QStringLiteral( "labeling/priority" ) ).toInt();
818  repeatDistance = layer->customProperty( QStringLiteral( "labeling/repeatDistance" ), 0.0 ).toDouble();
819  switch ( layer->customProperty( QStringLiteral( "labeling/repeatDistanceUnit" ), QVariant( 1 ) ).toUInt() )
820  {
821  case 0:
823  break;
824  case 1:
826  break;
827  case 2:
829  break;
830  case 3:
832  break;
833  }
834  if ( layer->customProperty( QStringLiteral( "labeling/repeatDistanceMapUnitScale" ) ).toString().isEmpty() )
835  {
836  //fallback to older property
837  double oldMin = layer->customProperty( QStringLiteral( "labeling/repeatDistanceMapUnitMinScale" ), 0.0 ).toDouble();
838  repeatDistanceMapUnitScale.minScale = !qgsDoubleNear( oldMin, 0 ) ? 1.0 / oldMin : 0;
839  double oldMax = layer->customProperty( QStringLiteral( "labeling/repeatDistanceMapUnitMaxScale" ), 0.0 ).toDouble();
840  repeatDistanceMapUnitScale.maxScale = !qgsDoubleNear( oldMax, 0 ) ? 1.0 / oldMax : 0;
841  }
842  else
843  {
844  repeatDistanceMapUnitScale = QgsSymbolLayerUtils::decodeMapUnitScale( layer->customProperty( QStringLiteral( "labeling/repeatDistanceMapUnitScale" ) ).toString() );
845  }
846 
847  // rendering
848  double scalemn = layer->customProperty( QStringLiteral( "labeling/scaleMin" ), QVariant( 0 ) ).toDouble();
849  double scalemx = layer->customProperty( QStringLiteral( "labeling/scaleMax" ), QVariant( 0 ) ).toDouble();
850 
851  // fix for scale visibility limits being keyed off of just its values in the past (<2.0)
852  QVariant scalevis = layer->customProperty( QStringLiteral( "labeling/scaleVisibility" ), QVariant() );
853  if ( scalevis.isValid() )
854  {
855  scaleVisibility = scalevis.toBool();
856  maximumScale = scalemn;
857  minimumScale = scalemx;
858  }
859  else if ( scalemn > 0 || scalemx > 0 )
860  {
861  scaleVisibility = true;
862  maximumScale = scalemn;
863  minimumScale = scalemx;
864  }
865  else
866  {
867  // keep scaleMin and scaleMax at new 1.0 defaults (1 and 10000000, were 0 and 0)
868  scaleVisibility = false;
869  }
870 
871 
872  fontLimitPixelSize = layer->customProperty( QStringLiteral( "labeling/fontLimitPixelSize" ), QVariant( false ) ).toBool();
873  fontMinPixelSize = layer->customProperty( QStringLiteral( "labeling/fontMinPixelSize" ), QVariant( 0 ) ).toInt();
874  fontMaxPixelSize = layer->customProperty( QStringLiteral( "labeling/fontMaxPixelSize" ), QVariant( 10000 ) ).toInt();
875  displayAll = layer->customProperty( QStringLiteral( "labeling/displayAll" ), QVariant( false ) ).toBool();
876  upsidedownLabels = static_cast< UpsideDownLabels >( layer->customProperty( QStringLiteral( "labeling/upsidedownLabels" ), QVariant( Upright ) ).toUInt() );
877 
878  labelPerPart = layer->customProperty( QStringLiteral( "labeling/labelPerPart" ) ).toBool();
879  mLineSettings.setMergeLines( layer->customProperty( QStringLiteral( "labeling/mergeLines" ) ).toBool() );
880  mThinningSettings.setMinimumFeatureSize( layer->customProperty( QStringLiteral( "labeling/minFeatureSize" ) ).toDouble() );
881  mThinningSettings.setLimitNumberLabelsEnabled( layer->customProperty( QStringLiteral( "labeling/limitNumLabels" ), QVariant( false ) ).toBool() );
882  mThinningSettings.setMaximumNumberLabels( layer->customProperty( QStringLiteral( "labeling/maxNumLabels" ), QVariant( 2000 ) ).toInt() );
883  mObstacleSettings.setIsObstacle( layer->customProperty( QStringLiteral( "labeling/obstacle" ), QVariant( true ) ).toBool() );
884  mObstacleSettings.setFactor( layer->customProperty( QStringLiteral( "labeling/obstacleFactor" ), QVariant( 1.0 ) ).toDouble() );
885  mObstacleSettings.setType( static_cast< QgsLabelObstacleSettings::ObstacleType >( layer->customProperty( QStringLiteral( "labeling/obstacleType" ), QVariant( PolygonInterior ) ).toUInt() ) );
886  zIndex = layer->customProperty( QStringLiteral( "labeling/zIndex" ), QVariant( 0.0 ) ).toDouble();
887 
888  mDataDefinedProperties.clear();
889  if ( layer->customProperty( QStringLiteral( "labeling/ddProperties" ) ).isValid() )
890  {
891  QDomDocument doc( QStringLiteral( "dd" ) );
892  doc.setContent( layer->customProperty( QStringLiteral( "labeling/ddProperties" ) ).toString() );
893  QDomElement elem = doc.firstChildElement( QStringLiteral( "properties" ) );
894  mDataDefinedProperties.readXml( elem, *sPropertyDefinitions() );
895  }
896  else
897  {
898  // read QGIS 2.x style data defined properties
899  readOldDataDefinedPropertyMap( layer, nullptr );
900  }
901  // upgrade older data defined settings
902  if ( mDataDefinedProperties.isActive( FontTransp ) )
903  {
904  mDataDefinedProperties.setProperty( FontOpacity, QgsProperty::fromExpression( QStringLiteral( "100 - (%1)" ).arg( mDataDefinedProperties.property( FontTransp ).asExpression() ) ) );
905  mDataDefinedProperties.setProperty( FontTransp, QgsProperty() );
906  }
907  if ( mDataDefinedProperties.isActive( BufferTransp ) )
908  {
909  mDataDefinedProperties.setProperty( BufferOpacity, QgsProperty::fromExpression( QStringLiteral( "100 - (%1)" ).arg( mDataDefinedProperties.property( BufferTransp ).asExpression() ) ) );
910  mDataDefinedProperties.setProperty( BufferTransp, QgsProperty() );
911  }
912  if ( mDataDefinedProperties.isActive( ShapeTransparency ) )
913  {
914  mDataDefinedProperties.setProperty( ShapeOpacity, QgsProperty::fromExpression( QStringLiteral( "100 - (%1)" ).arg( mDataDefinedProperties.property( ShapeTransparency ).asExpression() ) ) );
915  mDataDefinedProperties.setProperty( ShapeTransparency, QgsProperty() );
916  }
917  if ( mDataDefinedProperties.isActive( ShadowTransparency ) )
918  {
919  mDataDefinedProperties.setProperty( ShadowOpacity, QgsProperty::fromExpression( QStringLiteral( "100 - (%1)" ).arg( mDataDefinedProperties.property( ShadowTransparency ).asExpression() ) ) );
920  mDataDefinedProperties.setProperty( ShadowTransparency, QgsProperty() );
921  }
922  if ( mDataDefinedProperties.isActive( Rotation ) )
923  {
924  mDataDefinedProperties.setProperty( LabelRotation, QgsProperty::fromExpression( QStringLiteral( "360 - (%1)" ).arg( mDataDefinedProperties.property( Rotation ).asExpression() ) ) );
925  mDataDefinedProperties.setProperty( Rotation, QgsProperty() );
926  }
927  // older 2.x projects had min/max scale flipped - so change them here.
928  if ( mDataDefinedProperties.isActive( MinScale ) )
929  {
930  mDataDefinedProperties.setProperty( MaximumScale, mDataDefinedProperties.property( MinScale ) );
931  mDataDefinedProperties.setProperty( MinScale, QgsProperty() );
932  }
933  if ( mDataDefinedProperties.isActive( MaxScale ) )
934  {
935  mDataDefinedProperties.setProperty( MinimumScale, mDataDefinedProperties.property( MaxScale ) );
936  mDataDefinedProperties.setProperty( MaxScale, QgsProperty() );
937  }
938 }
939 
940 void QgsPalLayerSettings::readXml( const QDomElement &elem, const QgsReadWriteContext &context )
941 {
942  // text style
943  QDomElement textStyleElem = elem.firstChildElement( QStringLiteral( "text-style" ) );
944  fieldName = textStyleElem.attribute( QStringLiteral( "fieldName" ) );
945  isExpression = textStyleElem.attribute( QStringLiteral( "isExpression" ) ).toInt();
946 
947  mFormat.readXml( elem, context );
949  previewBkgrdColor = QColor( textStyleElem.attribute( QStringLiteral( "previewBkgrdColor" ), QStringLiteral( "#ffffff" ) ) );
951  substitutions.readXml( textStyleElem.firstChildElement( QStringLiteral( "substitutions" ) ) );
952  useSubstitutions = textStyleElem.attribute( QStringLiteral( "useSubstitutions" ) ).toInt();
953  mLegendString = textStyleElem.attribute( QStringLiteral( "legendString" ), QObject::tr( "Aa" ) );
954 
955  // text formatting
956  QDomElement textFormatElem = elem.firstChildElement( QStringLiteral( "text-format" ) );
957  wrapChar = textFormatElem.attribute( QStringLiteral( "wrapChar" ) );
958  autoWrapLength = textFormatElem.attribute( QStringLiteral( "autoWrapLength" ), QStringLiteral( "0" ) ).toInt();
959  useMaxLineLengthForAutoWrap = textFormatElem.attribute( QStringLiteral( "useMaxLineLengthForAutoWrap" ), QStringLiteral( "1" ) ).toInt();
960  multilineAlign = static_cast< MultiLineAlign >( textFormatElem.attribute( QStringLiteral( "multilineAlign" ), QString::number( MultiFollowPlacement ) ).toUInt() );
961  mLineSettings.setAddDirectionSymbol( textFormatElem.attribute( QStringLiteral( "addDirectionSymbol" ) ).toInt() );
962  mLineSettings.setLeftDirectionSymbol( textFormatElem.attribute( QStringLiteral( "leftDirectionSymbol" ), QStringLiteral( "<" ) ) );
963  mLineSettings.setRightDirectionSymbol( textFormatElem.attribute( QStringLiteral( "rightDirectionSymbol" ), QStringLiteral( ">" ) ) );
964  mLineSettings.setReverseDirectionSymbol( textFormatElem.attribute( QStringLiteral( "reverseDirectionSymbol" ) ).toInt() );
965  mLineSettings.setDirectionSymbolPlacement( static_cast< QgsLabelLineSettings::DirectionSymbolPlacement >( textFormatElem.attribute( QStringLiteral( "placeDirectionSymbol" ), QString::number( static_cast< int >( QgsLabelLineSettings::DirectionSymbolPlacement::SymbolLeftRight ) ) ).toUInt() ) );
966  formatNumbers = textFormatElem.attribute( QStringLiteral( "formatNumbers" ) ).toInt();
967  decimals = textFormatElem.attribute( QStringLiteral( "decimals" ) ).toInt();
968  plusSign = textFormatElem.attribute( QStringLiteral( "plussign" ) ).toInt();
969 
970  // placement
971  QDomElement placementElem = elem.firstChildElement( QStringLiteral( "placement" ) );
972  placement = static_cast< Placement >( placementElem.attribute( QStringLiteral( "placement" ) ).toInt() );
973  mLineSettings.setPlacementFlags( static_cast< QgsLabeling::LinePlacementFlags >( placementElem.attribute( QStringLiteral( "placementFlags" ) ).toUInt() ) );
974  mPolygonPlacementFlags = static_cast< QgsLabeling::PolygonPlacementFlags >( placementElem.attribute( QStringLiteral( "polygonPlacementFlags" ), QString::number( static_cast< int >( QgsLabeling::PolygonPlacementFlag::AllowPlacementInsideOfPolygon ) ) ).toInt() );
975 
976  centroidWhole = placementElem.attribute( QStringLiteral( "centroidWhole" ), QStringLiteral( "0" ) ).toInt();
977  centroidInside = placementElem.attribute( QStringLiteral( "centroidInside" ), QStringLiteral( "0" ) ).toInt();
978  predefinedPositionOrder = QgsLabelingUtils::decodePredefinedPositionOrder( placementElem.attribute( QStringLiteral( "predefinedPositionOrder" ) ) );
979  if ( predefinedPositionOrder.isEmpty() )
980  predefinedPositionOrder = *DEFAULT_PLACEMENT_ORDER();
981  fitInPolygonOnly = placementElem.attribute( QStringLiteral( "fitInPolygonOnly" ), QStringLiteral( "0" ) ).toInt();
982  dist = placementElem.attribute( QStringLiteral( "dist" ) ).toDouble();
983  if ( !placementElem.hasAttribute( QStringLiteral( "distUnits" ) ) )
984  {
985  if ( placementElem.attribute( QStringLiteral( "distInMapUnits" ) ).toInt() )
987  else
989  }
990  else
991  {
992  distUnits = QgsUnitTypes::decodeRenderUnit( placementElem.attribute( QStringLiteral( "distUnits" ) ) );
993  }
994  if ( !placementElem.hasAttribute( QStringLiteral( "distMapUnitScale" ) ) )
995  {
996  //fallback to older property
997  double oldMin = placementElem.attribute( QStringLiteral( "distMapUnitMinScale" ), QStringLiteral( "0" ) ).toDouble();
998  distMapUnitScale.minScale = !qgsDoubleNear( oldMin, 0 ) ? 1.0 / oldMin : 0;
999  double oldMax = placementElem.attribute( QStringLiteral( "distMapUnitMaxScale" ), QStringLiteral( "0" ) ).toDouble();
1000  distMapUnitScale.maxScale = !qgsDoubleNear( oldMax, 0 ) ? 1.0 / oldMax : 0;
1001  }
1002  else
1003  {
1004  distMapUnitScale = QgsSymbolLayerUtils::decodeMapUnitScale( placementElem.attribute( QStringLiteral( "distMapUnitScale" ) ) );
1005  }
1006  offsetType = static_cast< OffsetType >( placementElem.attribute( QStringLiteral( "offsetType" ), QString::number( FromPoint ) ).toUInt() );
1007  quadOffset = static_cast< QuadrantPosition >( placementElem.attribute( QStringLiteral( "quadOffset" ), QString::number( QuadrantOver ) ).toUInt() );
1008  xOffset = placementElem.attribute( QStringLiteral( "xOffset" ), QStringLiteral( "0" ) ).toDouble();
1009  yOffset = placementElem.attribute( QStringLiteral( "yOffset" ), QStringLiteral( "0" ) ).toDouble();
1010  if ( !placementElem.hasAttribute( QStringLiteral( "offsetUnits" ) ) )
1011  {
1012  offsetUnits = placementElem.attribute( QStringLiteral( "labelOffsetInMapUnits" ), QStringLiteral( "1" ) ).toInt() ? QgsUnitTypes::RenderMapUnits : QgsUnitTypes::RenderMillimeters;
1013  }
1014  else
1015  {
1016  offsetUnits = QgsUnitTypes::decodeRenderUnit( placementElem.attribute( QStringLiteral( "offsetUnits" ) ) );
1017  }
1018  if ( !placementElem.hasAttribute( QStringLiteral( "labelOffsetMapUnitScale" ) ) )
1019  {
1020  //fallback to older property
1021  double oldMin = placementElem.attribute( QStringLiteral( "labelOffsetMapUnitMinScale" ), QStringLiteral( "0" ) ).toDouble();
1022  labelOffsetMapUnitScale.minScale = !qgsDoubleNear( oldMin, 0.0 ) ? 1.0 / oldMin : 0;
1023  double oldMax = placementElem.attribute( QStringLiteral( "labelOffsetMapUnitMaxScale" ), QStringLiteral( "0" ) ).toDouble();
1024  labelOffsetMapUnitScale.maxScale = !qgsDoubleNear( oldMax, 0.0 ) ? 1.0 / oldMax : 0;
1025  }
1026  else
1027  {
1028  labelOffsetMapUnitScale = QgsSymbolLayerUtils::decodeMapUnitScale( placementElem.attribute( QStringLiteral( "labelOffsetMapUnitScale" ) ) );
1029  }
1030 
1031  if ( placementElem.hasAttribute( QStringLiteral( "angleOffset" ) ) )
1032  {
1033  double oldAngle = placementElem.attribute( QStringLiteral( "angleOffset" ), QStringLiteral( "0" ) ).toDouble();
1034  angleOffset = std::fmod( 360 - oldAngle, 360.0 );
1035  }
1036  else
1037  {
1038  angleOffset = placementElem.attribute( QStringLiteral( "rotationAngle" ), QStringLiteral( "0" ) ).toDouble();
1039  }
1040 
1041  preserveRotation = placementElem.attribute( QStringLiteral( "preserveRotation" ), QStringLiteral( "1" ) ).toInt();
1042  mRotationUnit = qgsEnumKeyToValue( placementElem.attribute( QStringLiteral( "rotationUnit" ), qgsEnumValueToKey( QgsUnitTypes::AngleDegrees ) ), QgsUnitTypes::AngleDegrees );
1043  maxCurvedCharAngleIn = placementElem.attribute( QStringLiteral( "maxCurvedCharAngleIn" ), QStringLiteral( "25" ) ).toDouble();
1044  maxCurvedCharAngleOut = placementElem.attribute( QStringLiteral( "maxCurvedCharAngleOut" ), QStringLiteral( "-25" ) ).toDouble();
1045  priority = placementElem.attribute( QStringLiteral( "priority" ) ).toInt();
1046  repeatDistance = placementElem.attribute( QStringLiteral( "repeatDistance" ), QStringLiteral( "0" ) ).toDouble();
1047  if ( !placementElem.hasAttribute( QStringLiteral( "repeatDistanceUnits" ) ) )
1048  {
1049  // upgrade old setting
1050  switch ( placementElem.attribute( QStringLiteral( "repeatDistanceUnit" ), QString::number( 1 ) ).toUInt() )
1051  {
1052  case 0:
1054  break;
1055  case 1:
1057  break;
1058  case 2:
1060  break;
1061  case 3:
1063  break;
1064  }
1065  }
1066  else
1067  {
1068  repeatDistanceUnit = QgsUnitTypes::decodeRenderUnit( placementElem.attribute( QStringLiteral( "repeatDistanceUnits" ) ) );
1069  }
1070  if ( !placementElem.hasAttribute( QStringLiteral( "repeatDistanceMapUnitScale" ) ) )
1071  {
1072  //fallback to older property
1073  double oldMin = placementElem.attribute( QStringLiteral( "repeatDistanceMapUnitMinScale" ), QStringLiteral( "0" ) ).toDouble();
1074  repeatDistanceMapUnitScale.minScale = !qgsDoubleNear( oldMin, 0.0 ) ? 1.0 / oldMin : 0;
1075  double oldMax = placementElem.attribute( QStringLiteral( "repeatDistanceMapUnitMaxScale" ), QStringLiteral( "0" ) ).toDouble();
1076  repeatDistanceMapUnitScale.maxScale = !qgsDoubleNear( oldMax, 0.0 ) ? 1.0 / oldMax : 0;
1077  }
1078  else
1079  {
1080  repeatDistanceMapUnitScale = QgsSymbolLayerUtils::decodeMapUnitScale( placementElem.attribute( QStringLiteral( "repeatDistanceMapUnitScale" ) ) );
1081  }
1082 
1083  mLineSettings.setOverrunDistance( placementElem.attribute( QStringLiteral( "overrunDistance" ), QStringLiteral( "0" ) ).toDouble() );
1084  mLineSettings.setOverrunDistanceUnit( QgsUnitTypes::decodeRenderUnit( placementElem.attribute( QStringLiteral( "overrunDistanceUnit" ) ) ) );
1085  mLineSettings.setOverrunDistanceMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( placementElem.attribute( QStringLiteral( "overrunDistanceMapUnitScale" ) ) ) );
1086  mLineSettings.setLineAnchorPercent( placementElem.attribute( QStringLiteral( "lineAnchorPercent" ), QStringLiteral( "0.5" ) ).toDouble() );
1087  mLineSettings.setAnchorType( static_cast< QgsLabelLineSettings::AnchorType >( placementElem.attribute( QStringLiteral( "lineAnchorType" ), QStringLiteral( "0" ) ).toInt() ) );
1088  mLineSettings.setAnchorClipping( static_cast< QgsLabelLineSettings::AnchorClipping >( placementElem.attribute( QStringLiteral( "lineAnchorClipping" ), QStringLiteral( "0" ) ).toInt() ) );
1089 
1090  geometryGenerator = placementElem.attribute( QStringLiteral( "geometryGenerator" ) );
1091  geometryGeneratorEnabled = placementElem.attribute( QStringLiteral( "geometryGeneratorEnabled" ) ).toInt();
1092  geometryGeneratorType = qgsEnumKeyToValue( placementElem.attribute( QStringLiteral( "geometryGeneratorType" ) ), QgsWkbTypes::PointGeometry );
1093 
1094  layerType = qgsEnumKeyToValue( placementElem.attribute( QStringLiteral( "layerType" ) ), QgsWkbTypes::UnknownGeometry );
1095 
1096  // rendering
1097  QDomElement renderingElem = elem.firstChildElement( QStringLiteral( "rendering" ) );
1098 
1099  drawLabels = renderingElem.attribute( QStringLiteral( "drawLabels" ), QStringLiteral( "1" ) ).toInt();
1100 
1101  maximumScale = renderingElem.attribute( QStringLiteral( "scaleMin" ), QStringLiteral( "0" ) ).toDouble();
1102  minimumScale = renderingElem.attribute( QStringLiteral( "scaleMax" ), QStringLiteral( "0" ) ).toDouble();
1103  scaleVisibility = renderingElem.attribute( QStringLiteral( "scaleVisibility" ) ).toInt();
1104 
1105  fontLimitPixelSize = renderingElem.attribute( QStringLiteral( "fontLimitPixelSize" ), QStringLiteral( "0" ) ).toInt();
1106  fontMinPixelSize = renderingElem.attribute( QStringLiteral( "fontMinPixelSize" ), QStringLiteral( "0" ) ).toInt();
1107  fontMaxPixelSize = renderingElem.attribute( QStringLiteral( "fontMaxPixelSize" ), QStringLiteral( "10000" ) ).toInt();
1108  displayAll = renderingElem.attribute( QStringLiteral( "displayAll" ), QStringLiteral( "0" ) ).toInt();
1109  upsidedownLabels = static_cast< UpsideDownLabels >( renderingElem.attribute( QStringLiteral( "upsidedownLabels" ), QString::number( Upright ) ).toUInt() );
1110 
1111  labelPerPart = renderingElem.attribute( QStringLiteral( "labelPerPart" ) ).toInt();
1112  mLineSettings.setMergeLines( renderingElem.attribute( QStringLiteral( "mergeLines" ) ).toInt() );
1113  mThinningSettings.setMinimumFeatureSize( renderingElem.attribute( QStringLiteral( "minFeatureSize" ) ).toDouble() );
1114  mThinningSettings.setLimitNumberLabelsEnabled( renderingElem.attribute( QStringLiteral( "limitNumLabels" ), QStringLiteral( "0" ) ).toInt() );
1115  mThinningSettings.setMaximumNumberLabels( renderingElem.attribute( QStringLiteral( "maxNumLabels" ), QStringLiteral( "2000" ) ).toInt() );
1116  mObstacleSettings.setIsObstacle( renderingElem.attribute( QStringLiteral( "obstacle" ), QStringLiteral( "1" ) ).toInt() );
1117  mObstacleSettings.setFactor( renderingElem.attribute( QStringLiteral( "obstacleFactor" ), QStringLiteral( "1" ) ).toDouble() );
1118  mObstacleSettings.setType( static_cast< QgsLabelObstacleSettings::ObstacleType >( renderingElem.attribute( QStringLiteral( "obstacleType" ), QString::number( PolygonInterior ) ).toUInt() ) );
1119  zIndex = renderingElem.attribute( QStringLiteral( "zIndex" ), QStringLiteral( "0.0" ) ).toDouble();
1120  mUnplacedVisibility = static_cast< Qgis::UnplacedLabelVisibility >( renderingElem.attribute( QStringLiteral( "unplacedVisibility" ), QString::number( static_cast< int >( Qgis::UnplacedLabelVisibility::FollowEngineSetting ) ) ).toInt() );
1121 
1122  QDomElement ddElem = elem.firstChildElement( QStringLiteral( "dd_properties" ) );
1123  if ( !ddElem.isNull() )
1124  {
1125  mDataDefinedProperties.readXml( ddElem, *sPropertyDefinitions() );
1126  }
1127  else
1128  {
1129  // upgrade 2.x style dd project
1130  mDataDefinedProperties.clear();
1131  QDomElement ddElem = elem.firstChildElement( QStringLiteral( "data-defined" ) );
1132  readOldDataDefinedPropertyMap( nullptr, &ddElem );
1133  }
1134  // upgrade older data defined settings
1135  if ( mDataDefinedProperties.isActive( FontTransp ) )
1136  {
1137  mDataDefinedProperties.setProperty( FontOpacity, QgsProperty::fromExpression( QStringLiteral( "100 - (%1)" ).arg( mDataDefinedProperties.property( FontTransp ).asExpression() ) ) );
1138  mDataDefinedProperties.setProperty( FontTransp, QgsProperty() );
1139  }
1140  if ( mDataDefinedProperties.isActive( BufferTransp ) )
1141  {
1142  mDataDefinedProperties.setProperty( BufferOpacity, QgsProperty::fromExpression( QStringLiteral( "100 - (%1)" ).arg( mDataDefinedProperties.property( BufferTransp ).asExpression() ) ) );
1143  mDataDefinedProperties.setProperty( BufferTransp, QgsProperty() );
1144  }
1145  if ( mDataDefinedProperties.isActive( ShapeTransparency ) )
1146  {
1147  mDataDefinedProperties.setProperty( ShapeOpacity, QgsProperty::fromExpression( QStringLiteral( "100 - (%1)" ).arg( mDataDefinedProperties.property( ShapeTransparency ).asExpression() ) ) );
1148  mDataDefinedProperties.setProperty( ShapeTransparency, QgsProperty() );
1149  }
1150  if ( mDataDefinedProperties.isActive( ShadowTransparency ) )
1151  {
1152  mDataDefinedProperties.setProperty( ShadowOpacity, QgsProperty::fromExpression( QStringLiteral( "100 - (%1)" ).arg( mDataDefinedProperties.property( ShadowTransparency ).asExpression() ) ) );
1153  mDataDefinedProperties.setProperty( ShadowTransparency, QgsProperty() );
1154  }
1155  if ( mDataDefinedProperties.isActive( Rotation ) )
1156  {
1157  mDataDefinedProperties.setProperty( LabelRotation, QgsProperty::fromExpression( QStringLiteral( "360 - (%1)" ).arg( mDataDefinedProperties.property( Rotation ).asExpression() ) ) );
1158  mDataDefinedProperties.setProperty( Rotation, QgsProperty() );
1159  }
1160  // older 2.x projects had min/max scale flipped - so change them here.
1161  if ( mDataDefinedProperties.isActive( MinScale ) )
1162  {
1163  mDataDefinedProperties.setProperty( MaximumScale, mDataDefinedProperties.property( MinScale ) );
1164  mDataDefinedProperties.setProperty( MinScale, QgsProperty() );
1165  }
1166  if ( mDataDefinedProperties.isActive( MaxScale ) )
1167  {
1168  mDataDefinedProperties.setProperty( MinimumScale, mDataDefinedProperties.property( MaxScale ) );
1169  mDataDefinedProperties.setProperty( MaxScale, QgsProperty() );
1170  }
1171 
1172  // TODO - replace with registry when multiple callout styles exist
1173  const QString calloutType = elem.attribute( QStringLiteral( "calloutType" ) );
1174  if ( calloutType.isEmpty() )
1175  mCallout.reset( QgsCalloutRegistry::defaultCallout() );
1176  else
1177  {
1178  mCallout.reset( QgsApplication::calloutRegistry()->createCallout( calloutType, elem.firstChildElement( QStringLiteral( "callout" ) ), context ) );
1179  if ( !mCallout )
1180  mCallout.reset( QgsCalloutRegistry::defaultCallout() );
1181  }
1182 }
1183 
1184 QDomElement QgsPalLayerSettings::writeXml( QDomDocument &doc, const QgsReadWriteContext &context ) const
1185 {
1186  QDomElement textStyleElem = mFormat.writeXml( doc, context );
1187 
1188  // text style
1189  textStyleElem.setAttribute( QStringLiteral( "fieldName" ), fieldName );
1190  textStyleElem.setAttribute( QStringLiteral( "isExpression" ), isExpression );
1191  QDomElement replacementElem = doc.createElement( QStringLiteral( "substitutions" ) );
1192  substitutions.writeXml( replacementElem, doc );
1193  textStyleElem.appendChild( replacementElem );
1194  textStyleElem.setAttribute( QStringLiteral( "useSubstitutions" ), useSubstitutions );
1195  textStyleElem.setAttribute( QStringLiteral( "legendString" ), mLegendString );
1196 
1197  // text formatting
1198  QDomElement textFormatElem = doc.createElement( QStringLiteral( "text-format" ) );
1199  textFormatElem.setAttribute( QStringLiteral( "wrapChar" ), wrapChar );
1200  textFormatElem.setAttribute( QStringLiteral( "autoWrapLength" ), autoWrapLength );
1201  textFormatElem.setAttribute( QStringLiteral( "useMaxLineLengthForAutoWrap" ), useMaxLineLengthForAutoWrap );
1202  textFormatElem.setAttribute( QStringLiteral( "multilineAlign" ), static_cast< unsigned int >( multilineAlign ) );
1203  textFormatElem.setAttribute( QStringLiteral( "addDirectionSymbol" ), mLineSettings.addDirectionSymbol() );
1204  textFormatElem.setAttribute( QStringLiteral( "leftDirectionSymbol" ), mLineSettings.leftDirectionSymbol() );
1205  textFormatElem.setAttribute( QStringLiteral( "rightDirectionSymbol" ), mLineSettings.rightDirectionSymbol() );
1206  textFormatElem.setAttribute( QStringLiteral( "reverseDirectionSymbol" ), mLineSettings.reverseDirectionSymbol() );
1207  textFormatElem.setAttribute( QStringLiteral( "placeDirectionSymbol" ), static_cast< unsigned int >( mLineSettings.directionSymbolPlacement() ) );
1208  textFormatElem.setAttribute( QStringLiteral( "formatNumbers" ), formatNumbers );
1209  textFormatElem.setAttribute( QStringLiteral( "decimals" ), decimals );
1210  textFormatElem.setAttribute( QStringLiteral( "plussign" ), plusSign );
1211 
1212  // placement
1213  QDomElement placementElem = doc.createElement( QStringLiteral( "placement" ) );
1214  placementElem.setAttribute( QStringLiteral( "placement" ), placement );
1215  placementElem.setAttribute( QStringLiteral( "polygonPlacementFlags" ), static_cast< int >( mPolygonPlacementFlags ) );
1216  placementElem.setAttribute( QStringLiteral( "placementFlags" ), static_cast< unsigned int >( mLineSettings.placementFlags() ) );
1217  placementElem.setAttribute( QStringLiteral( "centroidWhole" ), centroidWhole );
1218  placementElem.setAttribute( QStringLiteral( "centroidInside" ), centroidInside );
1219  placementElem.setAttribute( QStringLiteral( "predefinedPositionOrder" ), QgsLabelingUtils::encodePredefinedPositionOrder( predefinedPositionOrder ) );
1220  placementElem.setAttribute( QStringLiteral( "fitInPolygonOnly" ), fitInPolygonOnly );
1221  placementElem.setAttribute( QStringLiteral( "dist" ), dist );
1222  placementElem.setAttribute( QStringLiteral( "distUnits" ), QgsUnitTypes::encodeUnit( distUnits ) );
1223  placementElem.setAttribute( QStringLiteral( "distMapUnitScale" ), QgsSymbolLayerUtils::encodeMapUnitScale( distMapUnitScale ) );
1224  placementElem.setAttribute( QStringLiteral( "offsetType" ), static_cast< unsigned int >( offsetType ) );
1225  placementElem.setAttribute( QStringLiteral( "quadOffset" ), static_cast< unsigned int >( quadOffset ) );
1226  placementElem.setAttribute( QStringLiteral( "xOffset" ), xOffset );
1227  placementElem.setAttribute( QStringLiteral( "yOffset" ), yOffset );
1228  placementElem.setAttribute( QStringLiteral( "offsetUnits" ), QgsUnitTypes::encodeUnit( offsetUnits ) );
1229  placementElem.setAttribute( QStringLiteral( "labelOffsetMapUnitScale" ), QgsSymbolLayerUtils::encodeMapUnitScale( labelOffsetMapUnitScale ) );
1230  placementElem.setAttribute( QStringLiteral( "rotationAngle" ), angleOffset );
1231  placementElem.setAttribute( QStringLiteral( "preserveRotation" ), preserveRotation );
1232  placementElem.setAttribute( QStringLiteral( "rotationUnit" ), qgsEnumValueToKey( mRotationUnit ) );
1233  placementElem.setAttribute( QStringLiteral( "maxCurvedCharAngleIn" ), maxCurvedCharAngleIn );
1234  placementElem.setAttribute( QStringLiteral( "maxCurvedCharAngleOut" ), maxCurvedCharAngleOut );
1235  placementElem.setAttribute( QStringLiteral( "priority" ), priority );
1236  placementElem.setAttribute( QStringLiteral( "repeatDistance" ), repeatDistance );
1237  placementElem.setAttribute( QStringLiteral( "repeatDistanceUnits" ), QgsUnitTypes::encodeUnit( repeatDistanceUnit ) );
1238  placementElem.setAttribute( QStringLiteral( "repeatDistanceMapUnitScale" ), QgsSymbolLayerUtils::encodeMapUnitScale( repeatDistanceMapUnitScale ) );
1239  placementElem.setAttribute( QStringLiteral( "overrunDistance" ), mLineSettings.overrunDistance() );
1240  placementElem.setAttribute( QStringLiteral( "overrunDistanceUnit" ), QgsUnitTypes::encodeUnit( mLineSettings.overrunDistanceUnit() ) );
1241  placementElem.setAttribute( QStringLiteral( "overrunDistanceMapUnitScale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mLineSettings.overrunDistanceMapUnitScale() ) );
1242  placementElem.setAttribute( QStringLiteral( "lineAnchorPercent" ), mLineSettings.lineAnchorPercent() );
1243  placementElem.setAttribute( QStringLiteral( "lineAnchorType" ), static_cast< int >( mLineSettings.anchorType() ) );
1244  placementElem.setAttribute( QStringLiteral( "lineAnchorClipping" ), static_cast< int >( mLineSettings.anchorClipping() ) );
1245 
1246  placementElem.setAttribute( QStringLiteral( "geometryGenerator" ), geometryGenerator );
1247  placementElem.setAttribute( QStringLiteral( "geometryGeneratorEnabled" ), geometryGeneratorEnabled );
1248  const QMetaEnum metaEnum( QMetaEnum::fromType<QgsWkbTypes::GeometryType>() );
1249  placementElem.setAttribute( QStringLiteral( "geometryGeneratorType" ), metaEnum.valueToKey( geometryGeneratorType ) );
1250 
1251  placementElem.setAttribute( QStringLiteral( "layerType" ), metaEnum.valueToKey( layerType ) );
1252 
1253  // rendering
1254  QDomElement renderingElem = doc.createElement( QStringLiteral( "rendering" ) );
1255  renderingElem.setAttribute( QStringLiteral( "drawLabels" ), drawLabels );
1256  renderingElem.setAttribute( QStringLiteral( "scaleVisibility" ), scaleVisibility );
1257  renderingElem.setAttribute( QStringLiteral( "scaleMin" ), maximumScale );
1258  renderingElem.setAttribute( QStringLiteral( "scaleMax" ), minimumScale );
1259  renderingElem.setAttribute( QStringLiteral( "fontLimitPixelSize" ), fontLimitPixelSize );
1260  renderingElem.setAttribute( QStringLiteral( "fontMinPixelSize" ), fontMinPixelSize );
1261  renderingElem.setAttribute( QStringLiteral( "fontMaxPixelSize" ), fontMaxPixelSize );
1262  renderingElem.setAttribute( QStringLiteral( "displayAll" ), displayAll );
1263  renderingElem.setAttribute( QStringLiteral( "upsidedownLabels" ), static_cast< unsigned int >( upsidedownLabels ) );
1264 
1265  renderingElem.setAttribute( QStringLiteral( "labelPerPart" ), labelPerPart );
1266  renderingElem.setAttribute( QStringLiteral( "mergeLines" ), mLineSettings.mergeLines() );
1267  renderingElem.setAttribute( QStringLiteral( "minFeatureSize" ), mThinningSettings.minimumFeatureSize() );
1268  renderingElem.setAttribute( QStringLiteral( "limitNumLabels" ), mThinningSettings.limitNumberOfLabelsEnabled() );
1269  renderingElem.setAttribute( QStringLiteral( "maxNumLabels" ), mThinningSettings.maximumNumberLabels() );
1270  renderingElem.setAttribute( QStringLiteral( "obstacle" ), mObstacleSettings.isObstacle() );
1271  renderingElem.setAttribute( QStringLiteral( "obstacleFactor" ), mObstacleSettings.factor() );
1272  renderingElem.setAttribute( QStringLiteral( "obstacleType" ), static_cast< unsigned int >( mObstacleSettings.type() ) );
1273  renderingElem.setAttribute( QStringLiteral( "zIndex" ), zIndex );
1274  renderingElem.setAttribute( QStringLiteral( "unplacedVisibility" ), static_cast< int >( mUnplacedVisibility ) );
1275 
1276  QDomElement ddElem = doc.createElement( QStringLiteral( "dd_properties" ) );
1277  mDataDefinedProperties.writeXml( ddElem, *sPropertyDefinitions() );
1278 
1279  QDomElement elem = doc.createElement( QStringLiteral( "settings" ) );
1280  elem.appendChild( textStyleElem );
1281  elem.appendChild( textFormatElem );
1282  elem.appendChild( placementElem );
1283  elem.appendChild( renderingElem );
1284  elem.appendChild( ddElem );
1285 
1286  if ( mCallout )
1287  {
1288  elem.setAttribute( QStringLiteral( "calloutType" ), mCallout->type() );
1289  mCallout->saveProperties( doc, elem, context );
1290  }
1291 
1292  return elem;
1293 }
1294 
1296 {
1297  mCallout.reset( callout );
1298 }
1299 
1300 QPixmap QgsPalLayerSettings::labelSettingsPreviewPixmap( const QgsPalLayerSettings &settings, QSize size, const QString &previewText, int padding )
1301 {
1302  // for now, just use format
1303  QgsTextFormat tempFormat = settings.format();
1304  QPixmap pixmap( size );
1305  pixmap.fill( Qt::transparent );
1306  QPainter painter;
1307  painter.begin( &pixmap );
1308 
1309  painter.setRenderHint( QPainter::Antialiasing );
1310 
1311  QRect rect( 0, 0, size.width(), size.height() );
1312 
1313  // shameless eye candy - use a subtle gradient when drawing background
1314  painter.setPen( Qt::NoPen );
1315  QColor background1 = tempFormat.previewBackgroundColor();
1316  if ( ( background1.lightnessF() < 0.7 ) )
1317  {
1318  background1 = background1.darker( 125 );
1319  }
1320  else
1321  {
1322  background1 = background1.lighter( 125 );
1323  }
1324  QColor background2 = tempFormat.previewBackgroundColor();
1325  QLinearGradient linearGrad( QPointF( 0, 0 ), QPointF( 0, rect.height() ) );
1326  linearGrad.setColorAt( 0, background1 );
1327  linearGrad.setColorAt( 1, background2 );
1328  painter.setBrush( QBrush( linearGrad ) );
1329  if ( size.width() > 30 )
1330  {
1331  painter.drawRoundedRect( rect, 6, 6 );
1332  }
1333  else
1334  {
1335  // don't use rounded rect for small previews
1336  painter.drawRect( rect );
1337  }
1338  painter.setBrush( Qt::NoBrush );
1339  painter.setPen( Qt::NoPen );
1340  padding += 1; // move text away from background border
1341 
1342  QgsRenderContext context;
1343  QgsMapToPixel newCoordXForm;
1344  newCoordXForm.setParameters( 1, 0, 0, 0, 0, 0 );
1345  context.setMapToPixel( newCoordXForm );
1347 
1348 #if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
1349  const double logicalDpiX = QgsApplication::desktop()->logicalDpiX();
1350 #else
1351  QWidget *activeWindow = QApplication::activeWindow();
1352  const double logicalDpiX = activeWindow && activeWindow->screen() ? activeWindow->screen()->logicalDotsPerInchX() : 96.0;
1353 #endif
1354  context.setScaleFactor( logicalDpiX / 25.4 );
1355 
1356  context.setUseAdvancedEffects( true );
1357  context.setPainter( &painter );
1358 
1359  // slightly inset text to account for buffer/background
1360  const double fontSize = context.convertToPainterUnits( tempFormat.size(), tempFormat.sizeUnit(), tempFormat.sizeMapUnitScale() );
1361  double xtrans = 0;
1362  if ( tempFormat.buffer().enabled() )
1363  xtrans = tempFormat.buffer().sizeUnit() == QgsUnitTypes::RenderPercentage
1364  ? fontSize * tempFormat.buffer().size() / 100
1365  : context.convertToPainterUnits( tempFormat.buffer().size(), tempFormat.buffer().sizeUnit(), tempFormat.buffer().sizeMapUnitScale() );
1366  if ( tempFormat.background().enabled() && tempFormat.background().sizeType() != QgsTextBackgroundSettings::SizeFixed )
1367  xtrans = std::max( xtrans, context.convertToPainterUnits( tempFormat.background().size().width(), tempFormat.background().sizeUnit(), tempFormat.background().sizeMapUnitScale() ) );
1368 
1369  double ytrans = 0.0;
1370  if ( tempFormat.buffer().enabled() )
1371  ytrans = std::max( ytrans, tempFormat.buffer().sizeUnit() == QgsUnitTypes::RenderPercentage
1372  ? fontSize * tempFormat.buffer().size() / 100
1373  : context.convertToPainterUnits( tempFormat.buffer().size(), tempFormat.buffer().sizeUnit(), tempFormat.buffer().sizeMapUnitScale() ) );
1374  if ( tempFormat.background().enabled() )
1375  ytrans = std::max( ytrans, context.convertToPainterUnits( tempFormat.background().size().height(), tempFormat.background().sizeUnit(), tempFormat.background().sizeMapUnitScale() ) );
1376 
1377  const QStringList text = QStringList() << ( previewText.isEmpty() ? settings.legendString() : previewText );
1378  const double textHeight = QgsTextRenderer::textHeight( context, tempFormat, text, QgsTextRenderer::Rect );
1379  QRectF textRect = rect;
1380  textRect.setLeft( xtrans + padding );
1381  textRect.setWidth( rect.width() - xtrans - 2 * padding );
1382 
1383  if ( textRect.width() > 2000 )
1384  textRect.setWidth( 2000 - 2 * padding );
1385 
1386  const double bottom = textRect.height() / 2 + textHeight / 2;
1387  textRect.setTop( bottom - textHeight );
1388  textRect.setBottom( bottom );
1389 
1390  const double iconWidth = QFontMetricsF( QFont() ).horizontalAdvance( 'X' ) * Qgis::UI_SCALE_FACTOR;
1391 
1392  if ( settings.callout() && settings.callout()->enabled() )
1393  {
1394  // draw callout preview
1395  const double textWidth = QgsTextRenderer::textWidth( context, tempFormat, text );
1396  QgsCallout *callout = settings.callout();
1397  callout->startRender( context );
1398  QgsCallout::QgsCalloutContext calloutContext;
1399  QRectF labelRect( textRect.left() + ( textRect.width() - textWidth ) / 2.0, textRect.top(), textWidth, textRect.height() );
1400  callout->render( context, labelRect, 0, QgsGeometry::fromPointXY( QgsPointXY( labelRect.left() - iconWidth * 1.5, labelRect.bottom() + iconWidth ) ), calloutContext );
1401  callout->stopRender( context );
1402  }
1403 
1404  QgsTextRenderer::drawText( textRect, 0, QgsTextRenderer::AlignCenter, text, context, tempFormat );
1405 
1406  if ( size.width() > 30 )
1407  {
1408  // draw a label icon
1409 
1410  QgsApplication::getThemeIcon( QStringLiteral( "labelingSingle.svg" ) ).paint( &painter, QRect(
1411  rect.width() - iconWidth * 3, rect.height() - iconWidth * 3,
1412  iconWidth * 2, iconWidth * 2 ), Qt::AlignRight | Qt::AlignBottom );
1413  }
1414 
1415  // draw border on top of text
1416  painter.setBrush( Qt::NoBrush );
1417  painter.setPen( QPen( tempFormat.previewBackgroundColor().darker( 150 ), 0 ) );
1418  if ( size.width() > 30 )
1419  {
1420  painter.drawRoundedRect( rect, 6, 6 );
1421  }
1422  else
1423  {
1424  // don't use rounded rect for small previews
1425  painter.drawRect( rect );
1426  }
1427 
1428  painter.end();
1429  return pixmap;
1430 }
1431 
1433 {
1434  return mUnplacedVisibility;
1435 }
1436 
1438 {
1439  mUnplacedVisibility = visibility;
1440 }
1441 
1442 bool QgsPalLayerSettings::checkMinimumSizeMM( const QgsRenderContext &ct, const QgsGeometry &geom, double minSize ) const
1443 {
1444  return QgsPalLabeling::checkMinimumSizeMM( ct, geom, minSize );
1445 }
1446 
1447 void QgsPalLayerSettings::calculateLabelSize( const QFontMetricsF *fm, const QString &text, double &labelX, double &labelY, const QgsFeature *f, QgsRenderContext *context, double *rotatedLabelX, double *rotatedLabelY, QgsTextDocument *document )
1448 {
1449  if ( !fm || !f )
1450  {
1451  return;
1452  }
1453 
1454  QString textCopy( text );
1455 
1456  //try to keep < 2.12 API - handle no passed render context
1457  std::unique_ptr< QgsRenderContext > scopedRc;
1458  if ( !context )
1459  {
1460  scopedRc.reset( new QgsRenderContext() );
1461  if ( f )
1462  scopedRc->expressionContext().setFeature( *f );
1463  }
1464  QgsRenderContext *rc = context ? context : scopedRc.get();
1465 
1466  QString wrapchr = wrapChar;
1467  int evalAutoWrapLength = autoWrapLength;
1468  double multilineH = mFormat.lineHeight();
1469  QgsTextFormat::TextOrientation orientation = mFormat.orientation();
1470 
1471  bool addDirSymb = mLineSettings.addDirectionSymbol();
1472  QString leftDirSymb = mLineSettings.leftDirectionSymbol();
1473  QString rightDirSymb = mLineSettings.rightDirectionSymbol();
1475 
1476  if ( f == mCurFeat ) // called internally, use any stored data defined values
1477  {
1478  if ( dataDefinedValues.contains( QgsPalLayerSettings::MultiLineWrapChar ) )
1479  {
1480  wrapchr = dataDefinedValues.value( QgsPalLayerSettings::MultiLineWrapChar ).toString();
1481  }
1482 
1483  if ( dataDefinedValues.contains( QgsPalLayerSettings::AutoWrapLength ) )
1484  {
1485  evalAutoWrapLength = dataDefinedValues.value( QgsPalLayerSettings::AutoWrapLength, evalAutoWrapLength ).toInt();
1486  }
1487 
1488  if ( dataDefinedValues.contains( QgsPalLayerSettings::MultiLineHeight ) )
1489  {
1490  multilineH = dataDefinedValues.value( QgsPalLayerSettings::MultiLineHeight ).toDouble();
1491  }
1492 
1493  if ( dataDefinedValues.contains( QgsPalLayerSettings::TextOrientation ) )
1494  {
1495  orientation = QgsTextRendererUtils::decodeTextOrientation( dataDefinedValues.value( QgsPalLayerSettings::TextOrientation ).toString() );
1496  }
1497 
1498  if ( dataDefinedValues.contains( QgsPalLayerSettings::DirSymbDraw ) )
1499  {
1500  addDirSymb = dataDefinedValues.value( QgsPalLayerSettings::DirSymbDraw ).toBool();
1501  }
1502 
1503  if ( addDirSymb )
1504  {
1505 
1506  if ( dataDefinedValues.contains( QgsPalLayerSettings::DirSymbLeft ) )
1507  {
1508  leftDirSymb = dataDefinedValues.value( QgsPalLayerSettings::DirSymbLeft ).toString();
1509  }
1510  if ( dataDefinedValues.contains( QgsPalLayerSettings::DirSymbRight ) )
1511  {
1512  rightDirSymb = dataDefinedValues.value( QgsPalLayerSettings::DirSymbRight ).toString();
1513  }
1514 
1515  if ( dataDefinedValues.contains( QgsPalLayerSettings::DirSymbPlacement ) )
1516  {
1517  placeDirSymb = static_cast< QgsLabelLineSettings::DirectionSymbolPlacement >( dataDefinedValues.value( QgsPalLayerSettings::DirSymbPlacement ).toInt() );
1518  }
1519 
1520  }
1521 
1522  }
1523  else // called externally with passed-in feature, evaluate data defined
1524  {
1525  if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::MultiLineWrapChar ) )
1526  {
1528  wrapchr = mDataDefinedProperties.value( QgsPalLayerSettings::MultiLineWrapChar, rc->expressionContext(), wrapchr ).toString();
1529  }
1530 
1531  if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::AutoWrapLength ) )
1532  {
1533  rc->expressionContext().setOriginalValueVariable( evalAutoWrapLength );
1534  evalAutoWrapLength = mDataDefinedProperties.value( QgsPalLayerSettings::AutoWrapLength, rc->expressionContext(), evalAutoWrapLength ).toInt();
1535  }
1536 
1537  if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::MultiLineHeight ) )
1538  {
1539  rc->expressionContext().setOriginalValueVariable( multilineH );
1540  multilineH = mDataDefinedProperties.valueAsDouble( QgsPalLayerSettings::MultiLineHeight, rc->expressionContext(), multilineH );
1541  }
1542 
1543  if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::TextOrientation ) )
1544  {
1545  QString encoded = QgsTextRendererUtils::encodeTextOrientation( orientation );
1548  }
1549 
1550  if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::DirSymbDraw ) )
1551  {
1552  rc->expressionContext().setOriginalValueVariable( addDirSymb );
1553  addDirSymb = mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::DirSymbDraw, rc->expressionContext(), addDirSymb );
1554  }
1555 
1556  if ( addDirSymb ) // don't do extra evaluations if not adding a direction symbol
1557  {
1558  if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::DirSymbLeft ) )
1559  {
1560  rc->expressionContext().setOriginalValueVariable( leftDirSymb );
1561  leftDirSymb = mDataDefinedProperties.value( QgsPalLayerSettings::DirSymbLeft, rc->expressionContext(), leftDirSymb ).toString();
1562  }
1563 
1564  if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::DirSymbRight ) )
1565  {
1566  rc->expressionContext().setOriginalValueVariable( rightDirSymb );
1567  rightDirSymb = mDataDefinedProperties.value( QgsPalLayerSettings::DirSymbRight, rc->expressionContext(), rightDirSymb ).toString();
1568  }
1569 
1570  if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::DirSymbPlacement ) )
1571  {
1572  rc->expressionContext().setOriginalValueVariable( static_cast< int >( placeDirSymb ) );
1573  placeDirSymb = static_cast< QgsLabelLineSettings::DirectionSymbolPlacement >( mDataDefinedProperties.valueAsInt( QgsPalLayerSettings::DirSymbPlacement, rc->expressionContext(), static_cast< int >( placeDirSymb ) ) );
1574  }
1575  }
1576  }
1577 
1578  if ( wrapchr.isEmpty() )
1579  {
1580  wrapchr = QStringLiteral( "\n" ); // default to new line delimiter
1581  }
1582 
1583  //consider the space needed for the direction symbol
1584  if ( addDirSymb && placement == QgsPalLayerSettings::Line
1585  && ( !leftDirSymb.isEmpty() || !rightDirSymb.isEmpty() ) )
1586  {
1587  QString dirSym = leftDirSymb;
1588 
1589  if ( fm->horizontalAdvance( rightDirSymb ) > fm->horizontalAdvance( dirSym ) )
1590  dirSym = rightDirSymb;
1591 
1592  switch ( placeDirSymb )
1593  {
1595  textCopy.append( dirSym );
1596  break;
1597 
1600  textCopy.prepend( dirSym + QStringLiteral( "\n" ) );
1601  break;
1602  }
1603  }
1604 
1605  double w = 0.0, h = 0.0, rw = 0.0, rh = 0.0;
1606  double labelHeight = fm->ascent() + fm->descent(); // ignore +1 for baseline
1607 
1608  QStringList multiLineSplit;
1609 
1610  if ( document )
1611  {
1612  document->splitLines( wrapchr, evalAutoWrapLength, useMaxLineLengthForAutoWrap );
1613  multiLineSplit = document->toPlainText();
1614  }
1615  else
1616  {
1617  multiLineSplit = QgsPalLabeling::splitToLines( textCopy, wrapchr, evalAutoWrapLength, useMaxLineLengthForAutoWrap );
1618  }
1619 
1620  int lines = multiLineSplit.size();
1621 
1622  switch ( orientation )
1623  {
1625  {
1626  h += fm->height() + static_cast< double >( ( lines - 1 ) * labelHeight * multilineH );
1627 
1628  for ( const auto &line : multiLineSplit )
1629  {
1630  w = std::max( w, fm->horizontalAdvance( line ) );
1631  }
1632  break;
1633  }
1634 
1636  {
1637  double letterSpacing = mFormat.scaledFont( *context ).letterSpacing();
1638  double labelWidth = fm->maxWidth();
1639  w = labelWidth + ( lines - 1 ) * labelWidth * multilineH;
1640 
1641  int maxLineLength = 0;
1642  for ( const auto &line : multiLineSplit )
1643  {
1644  maxLineLength = std::max( maxLineLength, static_cast<int>( line.length() ) );
1645  }
1646  h = fm->ascent() * maxLineLength + ( maxLineLength - 1 ) * letterSpacing;
1647  break;
1648  }
1649 
1651  {
1652  double widthHorizontal = 0.0;
1653  for ( const auto &line : multiLineSplit )
1654  {
1655  widthHorizontal = std::max( w, fm->horizontalAdvance( line ) );
1656  }
1657 
1658  double widthVertical = 0.0;
1659  double letterSpacing = mFormat.scaledFont( *context ).letterSpacing();
1660  double labelWidth = fm->maxWidth();
1661  widthVertical = labelWidth + ( lines - 1 ) * labelWidth * multilineH;
1662 
1663  double heightHorizontal = 0.0;
1664  heightHorizontal += fm->height() + static_cast< double >( ( lines - 1 ) * labelHeight * multilineH );
1665 
1666  double heightVertical = 0.0;
1667  int maxLineLength = 0;
1668  for ( const auto &line : multiLineSplit )
1669  {
1670  maxLineLength = std::max( maxLineLength, static_cast<int>( line.length() ) );
1671  }
1672  heightVertical = fm->ascent() * maxLineLength + ( maxLineLength - 1 ) * letterSpacing;
1673 
1674  w = widthHorizontal;
1675  rw = heightVertical;
1676  h = heightHorizontal;
1677  rh = widthVertical;
1678  break;
1679  }
1680  }
1681 
1682 #if 0 // XXX strk
1683  QgsPointXY ptSize = xform->toMapCoordinatesF( w, h );
1684  labelX = std::fabs( ptSize.x() - ptZero.x() );
1685  labelY = std::fabs( ptSize.y() - ptZero.y() );
1686 #else
1687  double uPP = xform->mapUnitsPerPixel();
1688  labelX = w * uPP;
1689  labelY = h * uPP;
1690  if ( rotatedLabelX && rotatedLabelY )
1691  {
1692  *rotatedLabelX = rw * uPP;
1693  *rotatedLabelY = rh * uPP;
1694  }
1695 #endif
1696 }
1697 
1699 {
1700  registerFeatureWithDetails( f, context, QgsGeometry(), nullptr );
1701 }
1702 
1703 std::unique_ptr<QgsLabelFeature> QgsPalLayerSettings::registerFeatureWithDetails( const QgsFeature &f, QgsRenderContext &context, QgsGeometry obstacleGeometry, const QgsSymbol *symbol )
1704 {
1705  QVariant exprVal; // value() is repeatedly nulled on data defined evaluation and replaced when successful
1706  mCurFeat = &f;
1707 
1708  // data defined is obstacle? calculate this first, to avoid wasting time working with obstacles we don't require
1709  bool isObstacle = mObstacleSettings.isObstacle();
1710  if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::IsObstacle ) )
1711  isObstacle = mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::IsObstacle, context.expressionContext(), isObstacle ); // default to layer default
1712 
1713  if ( !drawLabels )
1714  {
1715  if ( isObstacle )
1716  {
1717  return registerObstacleFeature( f, context, obstacleGeometry );
1718  }
1719  else
1720  {
1721  return nullptr;
1722  }
1723  }
1724 
1725  QgsFeature feature = f;
1727  {
1728  const QgsGeometry geometry = mGeometryGeneratorExpression.evaluate( &context.expressionContext() ).value<QgsGeometry>();
1729  if ( mGeometryGeneratorExpression.hasEvalError() )
1730  QgsMessageLog::logMessage( mGeometryGeneratorExpression.evalErrorString(), QObject::tr( "Labeling" ) );
1731 
1732  if ( obstacleGeometry.isNull() )
1733  {
1734  // if an explicit obstacle geometry hasn't been set, we must always use the original feature geometry
1735  // as the obstacle -- because we want to use the geometry which was used to render the symbology
1736  // for the feature as the obstacle for other layers' labels, NOT the generated geometry which is used
1737  // only to place labels for this layer.
1738  obstacleGeometry = f.geometry();
1739  }
1740 
1741  feature.setGeometry( geometry );
1742  }
1743 
1744  // store data defined-derived values for later adding to label feature for use during rendering
1745  dataDefinedValues.clear();
1746 
1747  // data defined show label? defaults to show label if not set
1748  if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Show ) )
1749  {
1750  context.expressionContext().setOriginalValueVariable( true );
1751  if ( !mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::Show, context.expressionContext(), true ) )
1752  {
1753  return nullptr;
1754  }
1755  }
1756 
1757  // data defined scale visibility?
1758  bool useScaleVisibility = scaleVisibility;
1759  if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::ScaleVisibility ) )
1760  useScaleVisibility = mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::ScaleVisibility, context.expressionContext(), scaleVisibility );
1761 
1762  if ( useScaleVisibility )
1763  {
1764  // data defined min scale?
1765  double maxScale = maximumScale;
1766  if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::MaximumScale ) )
1767  {
1769  maxScale = mDataDefinedProperties.valueAsDouble( QgsPalLayerSettings::MaximumScale, context.expressionContext(), maxScale );
1770  }
1771 
1772  // scales closer than 1:1
1773  if ( maxScale < 0 )
1774  {
1775  maxScale = 1 / std::fabs( maxScale );
1776  }
1777 
1778  if ( !qgsDoubleNear( maxScale, 0.0 ) && context.rendererScale() < maxScale )
1779  {
1780  return nullptr;
1781  }
1782 
1783  // data defined min scale?
1784  double minScale = minimumScale;
1785  if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::MinimumScale ) )
1786  {
1788  minScale = mDataDefinedProperties.valueAsDouble( QgsPalLayerSettings::MinimumScale, context.expressionContext(), minScale );
1789  }
1790 
1791  // scales closer than 1:1
1792  if ( minScale < 0 )
1793  {
1794  minScale = 1 / std::fabs( minScale );
1795  }
1796 
1797  if ( !qgsDoubleNear( minScale, 0.0 ) && context.rendererScale() > minScale )
1798  {
1799  return nullptr;
1800  }
1801  }
1802 
1803  QFont labelFont = mFormat.font();
1804  // labelFont will be added to label feature for use during label painting
1805 
1806  // data defined font units?
1807  QgsUnitTypes::RenderUnit fontunits = mFormat.sizeUnit();
1808  exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::FontSizeUnit, context.expressionContext() );
1809  if ( !exprVal.isNull() )
1810  {
1811  QString units = exprVal.toString();
1812  if ( !units.isEmpty() )
1813  {
1814  bool ok;
1816  if ( ok )
1817  fontunits = res;
1818  }
1819  }
1820 
1821  //data defined label size?
1822  double fontSize = mFormat.size();
1823  if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Size ) )
1824  {
1825  context.expressionContext().setOriginalValueVariable( fontSize );
1826  fontSize = mDataDefinedProperties.valueAsDouble( QgsPalLayerSettings::Size, context.expressionContext(), fontSize );
1827  }
1828  if ( fontSize <= 0.0 )
1829  {
1830  return nullptr;
1831  }
1832 
1833  int fontPixelSize = QgsTextRenderer::sizeToPixel( fontSize, context, fontunits, mFormat.sizeMapUnitScale() );
1834  // don't try to show font sizes less than 1 pixel (Qt complains)
1835  if ( fontPixelSize < 1 )
1836  {
1837  return nullptr;
1838  }
1839  labelFont.setPixelSize( fontPixelSize );
1840 
1841  // NOTE: labelFont now always has pixelSize set, so pointSize or pointSizeF might return -1
1842 
1843  // defined 'minimum/maximum pixel font size'?
1844  if ( fontunits == QgsUnitTypes::RenderMapUnits )
1845  {
1846  if ( mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::FontLimitPixel, context.expressionContext(), fontLimitPixelSize ) )
1847  {
1848  int fontMinPixel = mDataDefinedProperties.valueAsInt( QgsPalLayerSettings::FontMinPixel, context.expressionContext(), fontMinPixelSize );
1849  int fontMaxPixel = mDataDefinedProperties.valueAsInt( QgsPalLayerSettings::FontMaxPixel, context.expressionContext(), fontMaxPixelSize );
1850 
1851  if ( fontMinPixel > labelFont.pixelSize() || labelFont.pixelSize() > fontMaxPixel )
1852  {
1853  return nullptr;
1854  }
1855  }
1856  }
1857 
1858  // NOTE: the following parsing functions calculate and store any data defined values for later use in QgsPalLabeling::drawLabeling
1859  // this is done to provide clarity, and because such parsing is not directly related to PAL feature registration calculations
1860 
1861  // calculate rest of font attributes and store any data defined values
1862  // this is done here for later use in making label backgrounds part of collision management (when implemented)
1863  labelFont.setCapitalization( QFont::MixedCase ); // reset this - we don't use QFont's handling as it breaks with curved labels
1864 
1865  parseTextStyle( labelFont, fontunits, context );
1866  if ( mDataDefinedProperties.hasActiveProperties() )
1867  {
1868  parseTextFormatting( context );
1869  parseTextBuffer( context );
1870  parseTextMask( context );
1871  parseShapeBackground( context );
1872  parseDropShadow( context );
1873  }
1874 
1875  QString labelText;
1876 
1877  // Check to see if we are a expression string.
1878  if ( isExpression )
1879  {
1881  if ( exp->hasParserError() )
1882  {
1883  QgsDebugMsgLevel( QStringLiteral( "Expression parser error:%1" ).arg( exp->parserErrorString() ), 4 );
1884  return nullptr;
1885  }
1886 
1887  QVariant result = exp->evaluate( &context.expressionContext() ); // expression prepared in QgsPalLabeling::prepareLayer()
1888  if ( exp->hasEvalError() )
1889  {
1890  QgsDebugMsgLevel( QStringLiteral( "Expression parser eval error:%1" ).arg( exp->evalErrorString() ), 4 );
1891  return nullptr;
1892  }
1893  labelText = result.isNull() ? QString() : result.toString();
1894  }
1895  else
1896  {
1897  const QVariant &v = feature.attribute( fieldIndex );
1898  labelText = v.isNull() ? QString() : v.toString();
1899  }
1900 
1901  // apply text replacements
1902  if ( useSubstitutions )
1903  {
1904  labelText = substitutions.process( labelText );
1905  }
1906 
1907  // apply capitalization
1908  Qgis::Capitalization capitalization = mFormat.capitalization();
1909  // maintain API - capitalization may have been set in textFont
1910  if ( capitalization == Qgis::Capitalization::MixedCase && mFormat.font().capitalization() != QFont::MixedCase )
1911  {
1912  capitalization = static_cast< Qgis::Capitalization >( mFormat.font().capitalization() );
1913  }
1914  // data defined font capitalization?
1915  if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::FontCase ) )
1916  {
1917  exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::FontCase, context.expressionContext() );
1918  if ( !exprVal.isNull() )
1919  {
1920  QString fcase = exprVal.toString().trimmed();
1921  QgsDebugMsgLevel( QStringLiteral( "exprVal FontCase:%1" ).arg( fcase ), 4 );
1922 
1923  if ( !fcase.isEmpty() )
1924  {
1925  if ( fcase.compare( QLatin1String( "NoChange" ), Qt::CaseInsensitive ) == 0 )
1926  {
1927  capitalization = Qgis::Capitalization::MixedCase;
1928  }
1929  else if ( fcase.compare( QLatin1String( "Upper" ), Qt::CaseInsensitive ) == 0 )
1930  {
1931  capitalization = Qgis::Capitalization::AllUppercase;
1932  }
1933  else if ( fcase.compare( QLatin1String( "Lower" ), Qt::CaseInsensitive ) == 0 )
1934  {
1935  capitalization = Qgis::Capitalization::AllLowercase;
1936  }
1937  else if ( fcase.compare( QLatin1String( "Capitalize" ), Qt::CaseInsensitive ) == 0 )
1938  {
1940  }
1941  else if ( fcase.compare( QLatin1String( "Title" ), Qt::CaseInsensitive ) == 0 )
1942  {
1943  capitalization = Qgis::Capitalization::TitleCase;
1944  }
1945 #if defined(HAS_KDE_QT5_SMALL_CAPS_FIX) || QT_VERSION >= QT_VERSION_CHECK(6, 3, 0)
1946  else if ( fcase.compare( QLatin1String( "SmallCaps" ), Qt::CaseInsensitive ) == 0 )
1947  {
1948  capitalization = Qgis::Capitalization::SmallCaps;
1949  }
1950  else if ( fcase.compare( QLatin1String( "AllSmallCaps" ), Qt::CaseInsensitive ) == 0 )
1951  {
1952  capitalization = Qgis::Capitalization::AllSmallCaps;
1953  }
1954 #endif
1955  }
1956  }
1957  }
1958  labelText = QgsStringUtils::capitalize( labelText, capitalization );
1959 
1960  // format number if label text is coercible to a number
1961  bool evalFormatNumbers = formatNumbers;
1962  if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::NumFormat ) )
1963  {
1964  evalFormatNumbers = mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::NumFormat, context.expressionContext(), evalFormatNumbers );
1965  }
1966  if ( evalFormatNumbers )
1967  {
1968  // data defined decimal places?
1969  int decimalPlaces = mDataDefinedProperties.valueAsInt( QgsPalLayerSettings::NumDecimals, context.expressionContext(), decimals );
1970  if ( decimalPlaces <= 0 ) // needs to be positive
1971  decimalPlaces = decimals;
1972 
1973  // data defined plus sign?
1974  bool signPlus = mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::NumPlusSign, context.expressionContext(), plusSign );
1975 
1976  QVariant textV( labelText );
1977  bool ok;
1978  double d = textV.toDouble( &ok );
1979  if ( ok )
1980  {
1981  QString numberFormat;
1982  if ( d > 0 && signPlus )
1983  {
1984  numberFormat.append( '+' );
1985  }
1986  numberFormat.append( "%1" );
1987  labelText = numberFormat.arg( d, 0, 'f', decimalPlaces );
1988  }
1989  }
1990 
1991  // NOTE: this should come AFTER any option that affects font metrics
1992  std::unique_ptr<QFontMetricsF> labelFontMetrics( new QFontMetricsF( labelFont ) );
1993  double labelX, labelY, rotatedLabelX, rotatedLabelY; // will receive label size
1994 
1995  QgsTextDocument doc;
1996  if ( format().allowHtmlFormatting() )
1997  doc = QgsTextDocument::fromHtml( QStringList() << labelText );
1998 
1999  // also applies the line split to doc!
2000  calculateLabelSize( labelFontMetrics.get(), labelText, labelX, labelY, mCurFeat, &context, &rotatedLabelX, &rotatedLabelY, format().allowHtmlFormatting() ? &doc : nullptr );
2001 
2002  // maximum angle between curved label characters (hardcoded defaults used in QGIS <2.0)
2003  //
2004  double maxcharanglein = 20.0; // range 20.0-60.0
2005  double maxcharangleout = -20.0; // range 20.0-95.0
2006 
2008  {
2009  maxcharanglein = maxCurvedCharAngleIn;
2010  maxcharangleout = maxCurvedCharAngleOut;
2011 
2012  //data defined maximum angle between curved label characters?
2013  if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::CurvedCharAngleInOut ) )
2014  {
2015  exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::CurvedCharAngleInOut, context.expressionContext() );
2016  bool ok = false;
2017  const QPointF maxcharanglePt = QgsSymbolLayerUtils::toPoint( exprVal, &ok );
2018  if ( ok )
2019  {
2020  maxcharanglein = std::clamp( static_cast< double >( maxcharanglePt.x() ), 20.0, 60.0 );
2021  maxcharangleout = std::clamp( static_cast< double >( maxcharanglePt.y() ), 20.0, 95.0 );
2022  }
2023  }
2024  // make sure maxcharangleout is always negative
2025  maxcharangleout = -( std::fabs( maxcharangleout ) );
2026  }
2027 
2028  // data defined centroid whole or clipped?
2029  bool wholeCentroid = centroidWhole;
2030  if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::CentroidWhole ) )
2031  {
2032  exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::CentroidWhole, context.expressionContext() );
2033  if ( !exprVal.isNull() )
2034  {
2035  QString str = exprVal.toString().trimmed();
2036  QgsDebugMsgLevel( QStringLiteral( "exprVal CentroidWhole:%1" ).arg( str ), 4 );
2037 
2038  if ( !str.isEmpty() )
2039  {
2040  if ( str.compare( QLatin1String( "Visible" ), Qt::CaseInsensitive ) == 0 )
2041  {
2042  wholeCentroid = false;
2043  }
2044  else if ( str.compare( QLatin1String( "Whole" ), Qt::CaseInsensitive ) == 0 )
2045  {
2046  wholeCentroid = true;
2047  }
2048  }
2049  }
2050  }
2051 
2052  QgsGeometry geom = feature.geometry();
2053  if ( geom.isNull() )
2054  {
2055  return nullptr;
2056  }
2057 
2058  // simplify?
2059  const QgsVectorSimplifyMethod &simplifyMethod = context.vectorSimplifyMethod();
2060  std::unique_ptr<QgsGeometry> scopedClonedGeom;
2061  if ( simplifyMethod.simplifyHints() != QgsVectorSimplifyMethod::NoSimplification && simplifyMethod.forceLocalOptimization() )
2062  {
2063  unsigned int simplifyHints = simplifyMethod.simplifyHints() | QgsMapToPixelSimplifier::SimplifyEnvelope;
2065  QgsMapToPixelSimplifier simplifier( simplifyHints, simplifyMethod.tolerance(), simplifyAlgorithm );
2066  geom = simplifier.simplify( geom );
2067  }
2068 
2069  if ( !context.featureClipGeometry().isEmpty() )
2070  {
2071  const QgsWkbTypes::GeometryType expectedType = geom.type();
2072  geom = geom.intersection( context.featureClipGeometry() );
2073  geom.convertGeometryCollectionToSubclass( expectedType );
2074  }
2075 
2076  // whether we're going to create a centroid for polygon
2077  bool centroidPoly = ( ( placement == QgsPalLayerSettings::AroundPoint
2079  && geom.type() == QgsWkbTypes::PolygonGeometry );
2080 
2081  // CLIP the geometry if it is bigger than the extent
2082  // don't clip if centroid is requested for whole feature
2083  bool doClip = false;
2084  if ( !centroidPoly || !wholeCentroid )
2085  {
2086  doClip = true;
2087  }
2088 
2089 
2090  QgsLabeling::PolygonPlacementFlags polygonPlacement = mPolygonPlacementFlags;
2091  if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::PolygonLabelOutside ) )
2092  {
2093  const QVariant dataDefinedOutside = mDataDefinedProperties.value( QgsPalLayerSettings::PolygonLabelOutside, context.expressionContext() );
2094  if ( !dataDefinedOutside.isNull() )
2095  {
2096  if ( dataDefinedOutside.type() == QVariant::String )
2097  {
2098  const QString value = dataDefinedOutside.toString().trimmed();
2099  if ( value.compare( QLatin1String( "force" ), Qt::CaseInsensitive ) == 0 )
2100  {
2101  // forced outside placement -- remove inside flag, add outside flag
2102  polygonPlacement &= ~static_cast< int >( QgsLabeling::PolygonPlacementFlag::AllowPlacementInsideOfPolygon );
2103  polygonPlacement |= QgsLabeling::PolygonPlacementFlag::AllowPlacementOutsideOfPolygon;
2104  }
2105  else if ( value.compare( QLatin1String( "yes" ), Qt::CaseInsensitive ) == 0 )
2106  {
2107  // permit outside placement
2108  polygonPlacement |= QgsLabeling::PolygonPlacementFlag::AllowPlacementOutsideOfPolygon;
2109  }
2110  else if ( value.compare( QLatin1String( "no" ), Qt::CaseInsensitive ) == 0 )
2111  {
2112  // block outside placement
2113  polygonPlacement &= ~static_cast< int >( QgsLabeling::PolygonPlacementFlag::AllowPlacementOutsideOfPolygon );
2114  }
2115  }
2116  else
2117  {
2118  if ( dataDefinedOutside.toBool() )
2119  {
2120  // permit outside placement
2121  polygonPlacement |= QgsLabeling::PolygonPlacementFlag::AllowPlacementOutsideOfPolygon;
2122  }
2123  else
2124  {
2125  // block outside placement
2126  polygonPlacement &= ~static_cast< int >( QgsLabeling::PolygonPlacementFlag::AllowPlacementOutsideOfPolygon );
2127  }
2128  }
2129  }
2130  }
2131 
2132  QgsLabelLineSettings lineSettings = mLineSettings;
2133  lineSettings.updateDataDefinedProperties( mDataDefinedProperties, context.expressionContext() );
2134 
2135  if ( geom.type() == QgsWkbTypes::LineGeometry )
2136  {
2137  switch ( lineSettings.anchorClipping() )
2138  {
2140  break;
2141 
2143  doClip = false;
2144  break;
2145  }
2146  }
2147 
2148  // if using fitInPolygonOnly option, generate the permissible zone (must happen before geometry is modified - e.g.,
2149  // as a result of using perimeter based labeling and the geometry is converted to a boundary)
2150  // note that we also force this if we are permitting labels to be placed outside of polygons too!
2151  QgsGeometry permissibleZone;
2152  if ( geom.type() == QgsWkbTypes::PolygonGeometry && ( fitInPolygonOnly || polygonPlacement & QgsLabeling::PolygonPlacementFlag::AllowPlacementOutsideOfPolygon ) )
2153  {
2154  permissibleZone = geom;
2155  if ( QgsPalLabeling::geometryRequiresPreparation( permissibleZone, context, ct, doClip ? extentGeom : QgsGeometry(), lineSettings.mergeLines() ) )
2156  {
2157  permissibleZone = QgsPalLabeling::prepareGeometry( permissibleZone, context, ct, doClip ? extentGeom : QgsGeometry(), lineSettings.mergeLines() );
2158  }
2159  }
2160 
2161  // if using perimeter based labeling for polygons, get the polygon's
2162  // linear boundary and use that for the label geometry
2163  if ( ( geom.type() == QgsWkbTypes::PolygonGeometry )
2164  && ( placement == Line || placement == PerimeterCurved ) )
2165  {
2166  geom = QgsGeometry( geom.constGet()->boundary() );
2167  }
2168 
2169  geos::unique_ptr geos_geom_clone;
2171  {
2172  geom = QgsPalLabeling::prepareGeometry( geom, context, ct, doClip ? extentGeom : QgsGeometry(), lineSettings.mergeLines() );
2173 
2174  if ( geom.isEmpty() )
2175  return nullptr;
2176  }
2177  geos_geom_clone = QgsGeos::asGeos( geom );
2178 
2179  if ( isObstacle || ( geom.type() == QgsWkbTypes::PointGeometry && offsetType == FromSymbolBounds ) )
2180  {
2181  if ( !obstacleGeometry.isNull() && QgsPalLabeling::geometryRequiresPreparation( obstacleGeometry, context, ct, doClip ? extentGeom : QgsGeometry(), lineSettings.mergeLines() ) )
2182  {
2183  obstacleGeometry = QgsGeometry( QgsPalLabeling::prepareGeometry( obstacleGeometry, context, ct, doClip ? extentGeom : QgsGeometry(), lineSettings.mergeLines() ) );
2184  }
2185  }
2186 
2187  QgsLabelThinningSettings featureThinningSettings = mThinningSettings;
2188  featureThinningSettings.updateDataDefinedProperties( mDataDefinedProperties, context.expressionContext() );
2189 
2190  double minimumSize = 0.0;
2191  if ( featureThinningSettings.minimumFeatureSize() > 0 )
2192  {
2193  // for minimum feature size on merged lines, we need to delay the filtering after the merging occurred in PAL
2194  if ( geom.type() == QgsWkbTypes::LineGeometry && mLineSettings.mergeLines() )
2195  {
2196  minimumSize = context.convertToMapUnits( featureThinningSettings.minimumFeatureSize(), QgsUnitTypes::RenderMillimeters );
2197  }
2198  else
2199  {
2200  if ( !checkMinimumSizeMM( context, geom, featureThinningSettings.minimumFeatureSize() ) )
2201  return nullptr;
2202  }
2203  }
2204 
2205  if ( !geos_geom_clone )
2206  return nullptr; // invalid geometry
2207 
2208  // likelihood exists label will be registered with PAL and may be drawn
2209  // check if max number of features to label (already registered with PAL) has been reached
2210  // Debug output at end of QgsPalLabeling::drawLabeling(), when deleting temp geometries
2211  if ( featureThinningSettings.limitNumberOfLabelsEnabled() )
2212  {
2213  if ( !featureThinningSettings.maximumNumberLabels() )
2214  {
2215  return nullptr;
2216  }
2217  if ( mFeatsRegPal >= featureThinningSettings.maximumNumberLabels() )
2218  {
2219  return nullptr;
2220  }
2221 
2222  int divNum = static_cast< int >( ( static_cast< double >( mFeaturesToLabel ) / featureThinningSettings.maximumNumberLabels() ) + 0.5 ); // NOLINT
2223  if ( divNum && ( mFeatsRegPal == static_cast< int >( mFeatsSendingToPal / divNum ) ) )
2224  {
2225  mFeatsSendingToPal += 1;
2226  if ( divNum && mFeatsSendingToPal % divNum )
2227  {
2228  return nullptr;
2229  }
2230  }
2231  }
2232 
2233  //data defined position / alignment / rotation?
2234  bool layerDefinedRotation = false;
2235  bool dataDefinedRotation = false;
2236  double xPos = 0.0, yPos = 0.0, angle = 0.0;
2237  double quadOffsetX = 0.0, quadOffsetY = 0.0;
2238  double offsetX = 0.0, offsetY = 0.0;
2239  QgsPointXY anchorPosition;
2240 
2242  {
2243  anchorPosition = geom.centroid().asPoint();
2244  }
2245  //x/y shift in case of alignment
2246  double xdiff = 0.0;
2247  double ydiff = 0.0;
2248 
2249  //data defined quadrant offset?
2250  bool ddFixedQuad = false;
2251  QuadrantPosition quadOff = quadOffset;
2252  if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::OffsetQuad ) )
2253  {
2254  context.expressionContext().setOriginalValueVariable( static_cast< int >( quadOff ) );
2255  exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::OffsetQuad, context.expressionContext() );
2256  if ( !exprVal.isNull() )
2257  {
2258  bool ok;
2259  int quadInt = exprVal.toInt( &ok );
2260  if ( ok && 0 <= quadInt && quadInt <= 8 )
2261  {
2262  quadOff = static_cast< QuadrantPosition >( quadInt );
2263  ddFixedQuad = true;
2264  }
2265  }
2266  }
2267 
2268  // adjust quadrant offset of labels
2269  switch ( quadOff )
2270  {
2271  case QuadrantAboveLeft:
2272  quadOffsetX = -1.0;
2273  quadOffsetY = 1.0;
2274  break;
2275  case QuadrantAbove:
2276  quadOffsetX = 0.0;
2277  quadOffsetY = 1.0;
2278  break;
2279  case QuadrantAboveRight:
2280  quadOffsetX = 1.0;
2281  quadOffsetY = 1.0;
2282  break;
2283  case QuadrantLeft:
2284  quadOffsetX = -1.0;
2285  quadOffsetY = 0.0;
2286  break;
2287  case QuadrantRight:
2288  quadOffsetX = 1.0;
2289  quadOffsetY = 0.0;
2290  break;
2291  case QuadrantBelowLeft:
2292  quadOffsetX = -1.0;
2293  quadOffsetY = -1.0;
2294  break;
2295  case QuadrantBelow:
2296  quadOffsetX = 0.0;
2297  quadOffsetY = -1.0;
2298  break;
2299  case QuadrantBelowRight:
2300  quadOffsetX = 1.0;
2301  quadOffsetY = -1.0;
2302  break;
2303  case QuadrantOver:
2304  break;
2305  }
2306 
2307  //data defined label offset?
2308  double xOff = xOffset;
2309  double yOff = yOffset;
2310  if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::OffsetXY ) )
2311  {
2313  exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::OffsetXY, context.expressionContext() );
2314  bool ok = false;
2315  const QPointF ddOffPt = QgsSymbolLayerUtils::toPoint( exprVal, &ok );
2316  if ( ok )
2317  {
2318  xOff = ddOffPt.x();
2319  yOff = ddOffPt.y();
2320  }
2321  }
2322 
2323  // data defined label offset units?
2325  if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::OffsetUnits ) )
2326  {
2327  exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::OffsetUnits, context.expressionContext() );
2328  if ( !exprVal.isNull() )
2329  {
2330  QString units = exprVal.toString().trimmed();
2331  if ( !units.isEmpty() )
2332  {
2333  bool ok = false;
2334  QgsUnitTypes::RenderUnit decodedUnits = QgsUnitTypes::decodeRenderUnit( units, &ok );
2335  if ( ok )
2336  {
2337  offUnit = decodedUnits;
2338  }
2339  }
2340  }
2341  }
2342 
2343  // adjust offset of labels to match chosen unit and map scale
2344  // offsets match those of symbology: -x = left, -y = up
2345  offsetX = context.convertToMapUnits( xOff, offUnit, labelOffsetMapUnitScale );
2346  // must be negative to match symbology offset direction
2347  offsetY = context.convertToMapUnits( -yOff, offUnit, labelOffsetMapUnitScale );
2348 
2349  // layer defined rotation?
2350  if ( !qgsDoubleNear( angleOffset, 0.0 ) )
2351  {
2352  layerDefinedRotation = true;
2353  angle = ( 360 - angleOffset ) * M_PI / 180; // convert to radians counterclockwise
2354  }
2355 
2356  const QgsMapToPixel &m2p = context.mapToPixel();
2357  //data defined rotation?
2358  if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::LabelRotation ) )
2359  {
2361  exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::LabelRotation, context.expressionContext() );
2362  if ( !exprVal.isNull() )
2363  {
2364  bool ok;
2365  const double rotation = exprVal.toDouble( &ok );
2366  if ( ok )
2367  {
2368  dataDefinedRotation = true;
2369 
2370  double rotationDegrees = rotation * QgsUnitTypes::fromUnitToUnitFactor( mRotationUnit,
2372 
2373  // TODO: add setting to disable having data defined rotation follow
2374  // map rotation ?
2375  rotationDegrees += m2p.mapRotation();
2376  angle = ( 360 - rotationDegrees ) * M_PI / 180.0;
2377  }
2378  }
2379  }
2380 
2381  bool hasDataDefinedPosition = false;
2382  {
2383  bool ddPosition = false;
2384 
2385  if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::PositionX )
2386  && mDataDefinedProperties.isActive( QgsPalLayerSettings::PositionY ) )
2387  {
2388  const QVariant xPosProperty = mDataDefinedProperties.value( QgsPalLayerSettings::PositionX, context.expressionContext() );
2389  const QVariant yPosProperty = mDataDefinedProperties.value( QgsPalLayerSettings::PositionY, context.expressionContext() );
2390  if ( !xPosProperty.isNull()
2391  && !yPosProperty.isNull() )
2392  {
2393  ddPosition = true;
2394 
2395  bool ddXPos = false, ddYPos = false;
2396  xPos = xPosProperty.toDouble( &ddXPos );
2397  yPos = yPosProperty.toDouble( &ddYPos );
2398  if ( ddXPos && ddYPos )
2399  hasDataDefinedPosition = true;
2400  }
2401  }
2402  else if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::PositionPoint ) )
2403  {
2404  const QVariant pointPosProperty = mDataDefinedProperties.value( QgsPalLayerSettings::PositionPoint, context.expressionContext() );
2405  if ( !pointPosProperty.isNull() )
2406  {
2407  ddPosition = true;
2408 
2409  QgsPoint point;
2410  if ( pointPosProperty.canConvert<QgsReferencedGeometry>() )
2411  {
2412  QgsReferencedGeometry referencedGeometryPoint = pointPosProperty.value<QgsReferencedGeometry>();
2413  point = QgsPoint( referencedGeometryPoint.asPoint() );
2414 
2415  if ( !referencedGeometryPoint.isNull()
2416  && ct.sourceCrs() != referencedGeometryPoint.crs() )
2417  QgsMessageLog::logMessage( QObject::tr( "Label position geometry is not in layer coordinates reference system. Layer CRS: '%1', Geometry CRS: '%2'" ).arg( ct.sourceCrs().userFriendlyIdentifier(), referencedGeometryPoint.crs().userFriendlyIdentifier() ), QObject::tr( "Labeling" ), Qgis::Warning );
2418  }
2419  else if ( pointPosProperty.canConvert<QgsGeometry>() )
2420  {
2421  point = QgsPoint( pointPosProperty.value<QgsGeometry>().asPoint() );
2422  }
2423 
2424  if ( !point.isEmpty() )
2425  {
2426  hasDataDefinedPosition = true;
2427 
2428  xPos = point.x();
2429  yPos = point.y();
2430  }
2431  }
2432  }
2433 
2434  if ( ddPosition )
2435  {
2436  //data defined position. But field values could be NULL -> positions will be generated by PAL
2437  if ( hasDataDefinedPosition )
2438  {
2439  // layer rotation set, but don't rotate pinned labels unless data defined
2440  if ( layerDefinedRotation && !dataDefinedRotation )
2441  {
2442  angle = 0.0;
2443  }
2444 
2445  //horizontal alignment
2446  if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Hali ) )
2447  {
2448  exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Hali, context.expressionContext() );
2449  if ( !exprVal.isNull() )
2450  {
2451  QString haliString = exprVal.toString();
2452  if ( haliString.compare( QLatin1String( "Center" ), Qt::CaseInsensitive ) == 0 )
2453  {
2454  xdiff -= labelX / 2.0;
2455  }
2456  else if ( haliString.compare( QLatin1String( "Right" ), Qt::CaseInsensitive ) == 0 )
2457  {
2458  xdiff -= labelX;
2459  }
2460  }
2461  }
2462 
2463  //vertical alignment
2464  if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Vali ) )
2465  {
2466  exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Vali, context.expressionContext() );
2467  if ( !exprVal.isNull() )
2468  {
2469  QString valiString = exprVal.toString();
2470  if ( valiString.compare( QLatin1String( "Bottom" ), Qt::CaseInsensitive ) != 0 )
2471  {
2472  if ( valiString.compare( QLatin1String( "Top" ), Qt::CaseInsensitive ) == 0 )
2473  {
2474  ydiff -= labelY;
2475  }
2476  else
2477  {
2478  double descentRatio = labelFontMetrics->descent() / labelFontMetrics->height();
2479  if ( valiString.compare( QLatin1String( "Base" ), Qt::CaseInsensitive ) == 0 )
2480  {
2481  ydiff -= labelY * descentRatio;
2482  }
2483  else //'Cap' or 'Half'
2484  {
2485  double capHeightRatio = ( labelFontMetrics->boundingRect( 'H' ).height() + 1 + labelFontMetrics->descent() ) / labelFontMetrics->height();
2486  ydiff -= labelY * capHeightRatio;
2487  if ( valiString.compare( QLatin1String( "Half" ), Qt::CaseInsensitive ) == 0 )
2488  {
2489  ydiff += labelY * ( capHeightRatio - descentRatio ) / 2.0;
2490  }
2491  }
2492  }
2493  }
2494  }
2495  }
2496 
2497  if ( dataDefinedRotation )
2498  {
2499  //adjust xdiff and ydiff because the hali/vali point needs to be the rotation center
2500  double xd = xdiff * std::cos( angle ) - ydiff * std::sin( angle );
2501  double yd = xdiff * std::sin( angle ) + ydiff * std::cos( angle );
2502  xdiff = xd;
2503  ydiff = yd;
2504  }
2505 
2506  //project xPos and yPos from layer to map CRS, handle rotation
2507  QgsGeometry ddPoint( new QgsPoint( xPos, yPos ) );
2508  if ( QgsPalLabeling::geometryRequiresPreparation( ddPoint, context, ct ) )
2509  {
2510  ddPoint = QgsPalLabeling::prepareGeometry( ddPoint, context, ct );
2511  if ( const QgsPoint *point = qgsgeometry_cast< const QgsPoint * >( ddPoint.constGet() ) )
2512  {
2513  xPos = point->x();
2514  yPos = point->y();
2515  anchorPosition = QgsPointXY( xPos, yPos );
2516  }
2517  else
2518  {
2519  QgsMessageLog::logMessage( QObject::tr( "Invalid data defined label position (%1, %2)" ).arg( xPos ).arg( yPos ), QObject::tr( "Labeling" ) );
2520  hasDataDefinedPosition = false;
2521  }
2522  }
2523  else
2524  {
2525  anchorPosition = QgsPointXY( xPos, yPos );
2526  }
2527 
2528  xPos += xdiff;
2529  yPos += ydiff;
2530  }
2531  else
2532  {
2533  anchorPosition = QgsPointXY( xPos, yPos );
2534  }
2535  }
2536  }
2537 
2538  // data defined always show?
2539  bool alwaysShow = false;
2540  if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::AlwaysShow ) )
2541  {
2542  alwaysShow = mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::AlwaysShow, context.expressionContext(), false );
2543  }
2544 
2545  // set repeat distance
2546  // data defined repeat distance?
2547  double repeatDist = repeatDistance;
2548  if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::RepeatDistance ) )
2549  {
2550  context.expressionContext().setOriginalValueVariable( repeatDist );
2551  repeatDist = mDataDefinedProperties.valueAsDouble( QgsPalLayerSettings::RepeatDistance, context.expressionContext(), repeatDist );
2552  }
2553 
2554  // data defined label-repeat distance units?
2556  if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::RepeatDistanceUnit ) )
2557  {
2558  exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::RepeatDistanceUnit, context.expressionContext() );
2559  if ( !exprVal.isNull() )
2560  {
2561  QString units = exprVal.toString().trimmed();
2562  if ( !units.isEmpty() )
2563  {
2564  bool ok = false;
2565  QgsUnitTypes::RenderUnit decodedUnits = QgsUnitTypes::decodeRenderUnit( units, &ok );
2566  if ( ok )
2567  {
2568  repeatUnits = decodedUnits;
2569  }
2570  }
2571  }
2572  }
2573 
2574  if ( !qgsDoubleNear( repeatDist, 0.0 ) )
2575  {
2576  if ( repeatUnits != QgsUnitTypes::RenderMapUnits )
2577  {
2578  repeatDist = context.convertToMapUnits( repeatDist, repeatUnits, repeatDistanceMapUnitScale );
2579  }
2580  }
2581 
2582  // overrun distance
2583  double overrunDistanceEval = lineSettings.overrunDistance();
2584  if ( !qgsDoubleNear( overrunDistanceEval, 0.0 ) )
2585  {
2586  overrunDistanceEval = context.convertToMapUnits( overrunDistanceEval, lineSettings.overrunDistanceUnit(), lineSettings.overrunDistanceMapUnitScale() );
2587  }
2588 
2589  // we smooth out the overrun label extensions by 1 mm, to avoid little jaggies right at the start or end of the lines
2590  // causing the overrun extension to extend out in an undesirable direction. This is hard coded, we don't want to overload
2591  // users with options they likely don't need to see...
2592  const double overrunSmoothDist = context.convertToMapUnits( 1, QgsUnitTypes::RenderMillimeters );
2593 
2594  bool labelAll = labelPerPart && !hasDataDefinedPosition;
2595  if ( !hasDataDefinedPosition )
2596  {
2597  if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::LabelAllParts ) )
2598  {
2600  labelAll = mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::LabelAllParts, context.expressionContext(), labelPerPart );
2601  }
2602  }
2603 
2604  // feature to the layer
2605  std::unique_ptr< QgsTextLabelFeature > labelFeature = std::make_unique< QgsTextLabelFeature>( feature.id(), std::move( geos_geom_clone ), QSizeF( labelX, labelY ) );
2606  labelFeature->setAnchorPosition( anchorPosition );
2607  labelFeature->setFeature( feature );
2608  labelFeature->setSymbol( symbol );
2609  labelFeature->setDocument( doc );
2610  if ( !qgsDoubleNear( rotatedLabelX, 0.0 ) && !qgsDoubleNear( rotatedLabelY, 0.0 ) )
2611  labelFeature->setRotatedSize( QSizeF( rotatedLabelX, rotatedLabelY ) );
2612  mFeatsRegPal++;
2613 
2614  labelFeature->setHasFixedPosition( hasDataDefinedPosition );
2615  labelFeature->setFixedPosition( QgsPointXY( xPos, yPos ) );
2616  // use layer-level defined rotation, but not if position fixed
2617  labelFeature->setHasFixedAngle( dataDefinedRotation || ( !hasDataDefinedPosition && !qgsDoubleNear( angle, 0.0 ) ) );
2618  labelFeature->setFixedAngle( angle );
2619  labelFeature->setQuadOffset( QPointF( quadOffsetX, quadOffsetY ) );
2620  labelFeature->setPositionOffset( QgsPointXY( offsetX, offsetY ) );
2621  labelFeature->setOffsetType( offsetType );
2622  labelFeature->setAlwaysShow( alwaysShow );
2623  labelFeature->setRepeatDistance( repeatDist );
2624  labelFeature->setLabelText( labelText );
2625  labelFeature->setPermissibleZone( permissibleZone );
2626  labelFeature->setOverrunDistance( overrunDistanceEval );
2627  labelFeature->setOverrunSmoothDistance( overrunSmoothDist );
2628  labelFeature->setLineAnchorPercent( lineSettings.lineAnchorPercent() );
2629  labelFeature->setLineAnchorType( lineSettings.anchorType() );
2630  labelFeature->setLabelAllParts( labelAll );
2631  labelFeature->setOriginalFeatureCrs( context.coordinateTransform().sourceCrs() );
2632  labelFeature->setMinimumSize( minimumSize );
2633  if ( geom.type() == QgsWkbTypes::PointGeometry && !obstacleGeometry.isNull() )
2634  {
2635  //register symbol size
2636  labelFeature->setSymbolSize( QSizeF( obstacleGeometry.boundingBox().width(),
2637  obstacleGeometry.boundingBox().height() ) );
2638  }
2639 
2640  //set label's visual margin so that top visual margin is the leading, and bottom margin is the font's descent
2641  //this makes labels align to the font's baseline or highest character
2642  double topMargin = std::max( 0.25 * labelFontMetrics->ascent(), 0.0 );
2643  double bottomMargin = 1.0 + labelFontMetrics->descent();
2644  QgsMargins vm( 0.0, topMargin, 0.0, bottomMargin );
2645  vm *= xform->mapUnitsPerPixel();
2646  labelFeature->setVisualMargin( vm );
2647 
2648  // store the label's calculated font for later use during painting
2649  QgsDebugMsgLevel( QStringLiteral( "PAL font stored definedFont: %1, Style: %2" ).arg( labelFont.toString(), labelFont.styleName() ), 4 );
2650  labelFeature->setDefinedFont( labelFont );
2651  labelFeature->setFontMetrics( *labelFontMetrics );
2652 
2653  labelFeature->setMaximumCharacterAngleInside( std::clamp( maxcharanglein, 20.0, 60.0 ) * M_PI / 180 );
2654  labelFeature->setMaximumCharacterAngleOutside( std::clamp( maxcharangleout, -95.0, -20.0 ) * M_PI / 180 );
2655  switch ( placement )
2656  {
2664  // these placements don't require text metrics
2665  break;
2666 
2669  labelFeature->setTextMetrics( QgsTextLabelFeature::calculateTextMetrics( xform, *labelFontMetrics, labelFont.letterSpacing(), labelFont.wordSpacing(), labelText, format().allowHtmlFormatting() ? &doc : nullptr ) );
2670  break;
2671  }
2672 
2673  // for labelFeature the LabelInfo is passed to feat when it is registered
2674 
2675  // TODO: allow layer-wide feature dist in PAL...?
2676 
2677  // data defined label-feature distance?
2678  double distance = dist;
2679  if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::LabelDistance ) )
2680  {
2681  context.expressionContext().setOriginalValueVariable( distance );
2682  distance = mDataDefinedProperties.valueAsDouble( QgsPalLayerSettings::LabelDistance, context.expressionContext(), distance );
2683  }
2684 
2685  // data defined label-feature distance units?
2687  if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::DistanceUnits ) )
2688  {
2689  exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::DistanceUnits, context.expressionContext() );
2690  if ( !exprVal.isNull() )
2691  {
2692  QString units = exprVal.toString().trimmed();
2693  QgsDebugMsgLevel( QStringLiteral( "exprVal DistanceUnits:%1" ).arg( units ), 4 );
2694  if ( !units.isEmpty() )
2695  {
2696  bool ok = false;
2697  QgsUnitTypes::RenderUnit decodedUnits = QgsUnitTypes::decodeRenderUnit( units, &ok );
2698  if ( ok )
2699  {
2700  distUnit = decodedUnits;
2701  }
2702  }
2703  }
2704  }
2705  distance = context.convertToPainterUnits( distance, distUnit, distMapUnitScale );
2706 
2707  // when using certain placement modes, we force a tiny minimum distance. This ensures that
2708  // candidates are created just offset from a border and avoids candidates being incorrectly flagged as colliding with neighbours
2712  {
2713  distance = ( distance < 0 ? -1 : 1 ) * std::max( std::fabs( distance ), 1.0 );
2714  }
2719  placement == QgsPalLayerSettings::Free ) && polygonPlacement & QgsLabeling::PolygonPlacementFlag::AllowPlacementOutsideOfPolygon ) )
2720  {
2721  distance = std::max( distance, 2.0 );
2722  }
2723 
2724  if ( !qgsDoubleNear( distance, 0.0 ) )
2725  {
2726  double d = ptOne.distance( ptZero ) * distance;
2727  labelFeature->setDistLabel( d );
2728  }
2729 
2730  if ( ddFixedQuad )
2731  {
2732  labelFeature->setHasFixedQuadrant( true );
2733  }
2734 
2735  labelFeature->setArrangementFlags( lineSettings.placementFlags() );
2736 
2737  labelFeature->setPolygonPlacementFlags( polygonPlacement );
2738 
2739  // data defined z-index?
2740  double z = zIndex;
2741  if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::ZIndex ) )
2742  {
2744  z = mDataDefinedProperties.valueAsDouble( QgsPalLayerSettings::ZIndex, context.expressionContext(), z );
2745  }
2746  labelFeature->setZIndex( z );
2747 
2748  // data defined priority?
2749  if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Priority ) )
2750  {
2752  exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Priority, context.expressionContext() );
2753  if ( !exprVal.isNull() )
2754  {
2755  bool ok;
2756  double priorityD = exprVal.toDouble( &ok );
2757  if ( ok )
2758  {
2759  priorityD = std::clamp( priorityD, 0.0, 10.0 );
2760  priorityD = 1 - priorityD / 10.0; // convert 0..10 --> 1..0
2761  labelFeature->setPriority( priorityD );
2762  }
2763  }
2764  }
2765 
2766  QgsLabelObstacleSettings os = mObstacleSettings;
2767  os.setIsObstacle( isObstacle );
2768  os.updateDataDefinedProperties( mDataDefinedProperties, context.expressionContext() );
2769  os.setObstacleGeometry( obstacleGeometry );
2770  labelFeature->setObstacleSettings( os );
2771 
2772  QVector< QgsPalLayerSettings::PredefinedPointPosition > positionOrder = predefinedPositionOrder;
2773  if ( positionOrder.isEmpty() )
2774  positionOrder = *DEFAULT_PLACEMENT_ORDER();
2775 
2776  if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::PredefinedPositionOrder ) )
2777  {
2779  QString dataDefinedOrder = mDataDefinedProperties.valueAsString( QgsPalLayerSettings::PredefinedPositionOrder, context.expressionContext() );
2780  if ( !dataDefinedOrder.isEmpty() )
2781  {
2782  positionOrder = QgsLabelingUtils::decodePredefinedPositionOrder( dataDefinedOrder );
2783  }
2784  }
2785  labelFeature->setPredefinedPositionOrder( positionOrder );
2786 
2787  // add parameters for data defined labeling to label feature
2788  labelFeature->setDataDefinedValues( dataDefinedValues );
2789 
2790  return labelFeature;
2791 }
2792 
2793 std::unique_ptr<QgsLabelFeature> QgsPalLayerSettings::registerObstacleFeature( const QgsFeature &f, QgsRenderContext &context, const QgsGeometry &obstacleGeometry )
2794 {
2795  mCurFeat = &f;
2796 
2797  QgsGeometry geom;
2798  if ( !obstacleGeometry.isNull() )
2799  {
2800  geom = obstacleGeometry;
2801  }
2802  else
2803  {
2804  geom = f.geometry();
2805  }
2806 
2807  if ( geom.isNull() )
2808  {
2809  return nullptr;
2810  }
2811 
2812  // don't even try to register linestrings with only one vertex as an obstacle
2813  if ( const QgsLineString *ls = qgsgeometry_cast< const QgsLineString * >( geom.constGet() ) )
2814  {
2815  if ( ls->numPoints() < 2 )
2816  return nullptr;
2817  }
2818 
2819  // simplify?
2820  const QgsVectorSimplifyMethod &simplifyMethod = context.vectorSimplifyMethod();
2821  std::unique_ptr<QgsGeometry> scopedClonedGeom;
2822  if ( simplifyMethod.simplifyHints() != QgsVectorSimplifyMethod::NoSimplification && simplifyMethod.forceLocalOptimization() )
2823  {
2824  int simplifyHints = simplifyMethod.simplifyHints() | QgsMapToPixelSimplifier::SimplifyEnvelope;
2826  QgsMapToPixelSimplifier simplifier( simplifyHints, simplifyMethod.tolerance(), simplifyAlgorithm );
2827  geom = simplifier.simplify( geom );
2828  }
2829 
2830  geos::unique_ptr geos_geom_clone;
2831  std::unique_ptr<QgsGeometry> scopedPreparedGeom;
2832 
2833  if ( QgsPalLabeling::geometryRequiresPreparation( geom, context, ct, extentGeom, mLineSettings.mergeLines() ) )
2834  {
2835  geom = QgsPalLabeling::prepareGeometry( geom, context, ct, extentGeom, mLineSettings.mergeLines() );
2836  }
2837  geos_geom_clone = QgsGeos::asGeos( geom );
2838 
2839  if ( !geos_geom_clone )
2840  return nullptr; // invalid geometry
2841 
2842  // feature to the layer
2843  std::unique_ptr< QgsLabelFeature > obstacleFeature = std::make_unique< QgsLabelFeature >( f.id(), std::move( geos_geom_clone ), QSizeF( 0, 0 ) );
2844  obstacleFeature->setFeature( f );
2845 
2846  QgsLabelObstacleSettings os = mObstacleSettings;
2847  os.setIsObstacle( true );
2848  os.updateDataDefinedProperties( mDataDefinedProperties, context.expressionContext() );
2849  obstacleFeature->setObstacleSettings( os );
2850 
2851  mFeatsRegPal++;
2852  return obstacleFeature;
2853 }
2854 
2855 bool QgsPalLayerSettings::dataDefinedValEval( DataDefinedValueType valType,
2857  QVariant &exprVal, QgsExpressionContext &context, const QVariant &originalValue )
2858 {
2859  if ( !mDataDefinedProperties.isActive( p ) )
2860  return false;
2861 
2862  context.setOriginalValueVariable( originalValue );
2863  exprVal = mDataDefinedProperties.value( p, context );
2864  if ( !exprVal.isNull() )
2865  {
2866  switch ( valType )
2867  {
2868  case DDBool:
2869  {
2870  bool bol = exprVal.toBool();
2871  dataDefinedValues.insert( p, QVariant( bol ) );
2872  return true;
2873  }
2874  case DDInt:
2875  {
2876  bool ok;
2877  int size = exprVal.toInt( &ok );
2878 
2879  if ( ok )
2880  {
2881  dataDefinedValues.insert( p, QVariant( size ) );
2882  return true;
2883  }
2884  return false;
2885  }
2886  case DDIntPos:
2887  {
2888  bool ok;
2889  int size = exprVal.toInt( &ok );
2890 
2891  if ( ok && size > 0 )
2892  {
2893  dataDefinedValues.insert( p, QVariant( size ) );
2894  return true;
2895  }
2896  return false;
2897  }
2898  case DDDouble:
2899  {
2900  bool ok;
2901  double size = exprVal.toDouble( &ok );
2902 
2903  if ( ok )
2904  {
2905  dataDefinedValues.insert( p, QVariant( size ) );
2906  return true;
2907  }
2908  return false;
2909  }
2910  case DDDoublePos:
2911  {
2912  bool ok;
2913  double size = exprVal.toDouble( &ok );
2914 
2915  if ( ok && size > 0.0 )
2916  {
2917  dataDefinedValues.insert( p, QVariant( size ) );
2918  return true;
2919  }
2920  return false;
2921  }
2922  case DDRotation180:
2923  {
2924  bool ok;
2925  double rot = exprVal.toDouble( &ok );
2926  if ( ok )
2927  {
2928  if ( rot < -180.0 && rot >= -360 )
2929  {
2930  rot += 360;
2931  }
2932  if ( rot > 180.0 && rot <= 360 )
2933  {
2934  rot -= 360;
2935  }
2936  if ( rot >= -180 && rot <= 180 )
2937  {
2938  dataDefinedValues.insert( p, QVariant( rot ) );
2939  return true;
2940  }
2941  }
2942  return false;
2943  }
2944  case DDOpacity:
2945  {
2946  bool ok;
2947  int size = exprVal.toInt( &ok );
2948  if ( ok && size >= 0 && size <= 100 )
2949  {
2950  dataDefinedValues.insert( p, QVariant( size ) );
2951  return true;
2952  }
2953  return false;
2954  }
2955  case DDString:
2956  {
2957  QString str = exprVal.toString(); // don't trim whitespace
2958 
2959  dataDefinedValues.insert( p, QVariant( str ) ); // let it stay empty if it is
2960  return true;
2961  }
2962  case DDUnits:
2963  {
2964  QString unitstr = exprVal.toString().trimmed();
2965 
2966  if ( !unitstr.isEmpty() )
2967  {
2968  dataDefinedValues.insert( p, QVariant( static_cast< int >( QgsUnitTypes::decodeRenderUnit( unitstr ) ) ) );
2969  return true;
2970  }
2971  return false;
2972  }
2973  case DDColor:
2974  {
2975  QString colorstr = exprVal.toString().trimmed();
2976  QColor color = QgsSymbolLayerUtils::decodeColor( colorstr );
2977 
2978  if ( color.isValid() )
2979  {
2980  dataDefinedValues.insert( p, QVariant( color ) );
2981  return true;
2982  }
2983  return false;
2984  }
2985  case DDJoinStyle:
2986  {
2987  QString joinstr = exprVal.toString().trimmed();
2988 
2989  if ( !joinstr.isEmpty() )
2990  {
2991  dataDefinedValues.insert( p, QVariant( static_cast< int >( QgsSymbolLayerUtils::decodePenJoinStyle( joinstr ) ) ) );
2992  return true;
2993  }
2994  return false;
2995  }
2996  case DDBlendMode:
2997  {
2998  QString blendstr = exprVal.toString().trimmed();
2999 
3000  if ( !blendstr.isEmpty() )
3001  {
3002  dataDefinedValues.insert( p, QVariant( static_cast< int >( QgsSymbolLayerUtils::decodeBlendMode( blendstr ) ) ) );
3003  return true;
3004  }
3005  return false;
3006  }
3007  case DDPointF:
3008  {
3009  bool ok = false;
3010  const QPointF res = QgsSymbolLayerUtils::toPoint( exprVal, &ok );
3011  if ( ok )
3012  {
3013  dataDefinedValues.insert( p, res );
3014  return true;
3015  }
3016  return false;
3017  }
3018  case DDSizeF:
3019  {
3020  bool ok = false;
3021  const QSizeF res = QgsSymbolLayerUtils::toSize( exprVal, &ok );
3022  if ( ok )
3023  {
3024  dataDefinedValues.insert( p, res );
3025  return true;
3026  }
3027  return false;
3028  }
3029  }
3030  }
3031  return false;
3032 }
3033 
3034 void QgsPalLayerSettings::parseTextStyle( QFont &labelFont,
3035  QgsUnitTypes::RenderUnit fontunits,
3036  QgsRenderContext &context )
3037 {
3038  // NOTE: labelFont already has pixelSize set, so pointSize or pointSizeF might return -1
3039 
3040  QVariant exprVal; // value() is repeatedly nulled on data defined evaluation and replaced when successful
3041 
3042  // Two ways to generate new data defined font:
3043  // 1) Family + [bold] + [italic] (named style is ignored and font is built off of base family)
3044  // 2) Family + named style (bold or italic is ignored)
3045 
3046  // data defined font family?
3047  QString ddFontFamily;
3048  if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Family ) )
3049  {
3050  context.expressionContext().setOriginalValueVariable( labelFont.family() );
3051  exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Family, context.expressionContext() );
3052  if ( !exprVal.isNull() )
3053  {
3054  QString family = exprVal.toString().trimmed();
3055  QgsDebugMsgLevel( QStringLiteral( "exprVal Font family:%1" ).arg( family ), 4 );
3056 
3057  if ( labelFont.family() != family )
3058  {
3059  // testing for ddFontFamily in QFontDatabase.families() may be slow to do for every feature
3060  // (i.e. don't use QgsFontUtils::fontFamilyMatchOnSystem( family ) here)
3061  if ( QgsFontUtils::fontFamilyOnSystem( family ) )
3062  {
3063  ddFontFamily = family;
3064  }
3065  }
3066  }
3067  }
3068 
3069  // data defined named font style?
3070  QString ddFontStyle;
3071  if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::FontStyle ) )
3072  {
3073  exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::FontStyle, context.expressionContext() );
3074  if ( !exprVal.isNull() )
3075  {
3076  QString fontstyle = exprVal.toString().trimmed();
3077  QgsDebugMsgLevel( QStringLiteral( "exprVal Font style:%1" ).arg( fontstyle ), 4 );
3078  ddFontStyle = fontstyle;
3079  }
3080  }
3081 
3082  // data defined bold font style?
3083  bool ddBold = false;
3084  if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Bold ) )
3085  {
3086  context.expressionContext().setOriginalValueVariable( labelFont.bold() );
3087  ddBold = mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::Bold, context.expressionContext(), false );
3088  }
3089 
3090  // data defined italic font style?
3091  bool ddItalic = false;
3092  if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Italic ) )
3093  {
3094  context.expressionContext().setOriginalValueVariable( labelFont.italic() );
3095  ddItalic = mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::Italic, context.expressionContext(), false );
3096  }
3097 
3098  // TODO: update when pref for how to resolve missing family (use matching algorithm or just default font) is implemented
3099  // (currently defaults to what has been read in from layer settings)
3100  QFont newFont;
3101  QFont appFont = QApplication::font();
3102  bool newFontBuilt = false;
3103  if ( ddBold || ddItalic )
3104  {
3105  // new font needs built, since existing style needs removed
3106  newFont = QFont( !ddFontFamily.isEmpty() ? ddFontFamily : labelFont.family() );
3107  newFontBuilt = true;
3108  newFont.setBold( ddBold );
3109  newFont.setItalic( ddItalic );
3110  }
3111  else if ( !ddFontStyle.isEmpty()
3112  && ddFontStyle.compare( QLatin1String( "Ignore" ), Qt::CaseInsensitive ) != 0 )
3113  {
3114  if ( !ddFontFamily.isEmpty() )
3115  {
3116  // both family and style are different, build font from database
3117  if ( !mFontDB )
3118  mFontDB = std::make_unique< QFontDatabase >();
3119 
3120  QFont styledfont = mFontDB->font( ddFontFamily, ddFontStyle, appFont.pointSize() );
3121  if ( appFont != styledfont )
3122  {
3123  newFont = styledfont;
3124  newFontBuilt = true;
3125  }
3126  }
3127 
3128  // update the font face style
3129  QgsFontUtils::updateFontViaStyle( newFontBuilt ? newFont : labelFont, ddFontStyle );
3130  }
3131  else if ( !ddFontFamily.isEmpty() )
3132  {
3133  if ( ddFontStyle.compare( QLatin1String( "Ignore" ), Qt::CaseInsensitive ) != 0 )
3134  {
3135  // just family is different, build font from database
3136  if ( !mFontDB )
3137  mFontDB = std::make_unique< QFontDatabase >();
3138  QFont styledfont = mFontDB->font( ddFontFamily, mFormat.namedStyle(), appFont.pointSize() );
3139  if ( appFont != styledfont )
3140  {
3141  newFont = styledfont;
3142  newFontBuilt = true;
3143  }
3144  }
3145  else
3146  {
3147  newFont = QFont( ddFontFamily );
3148  newFontBuilt = true;
3149  }
3150  }
3151 
3152  if ( newFontBuilt )
3153  {
3154  // copy over existing font settings
3155  //newFont = newFont.resolve( labelFont ); // should work, but let's be sure what's being copied
3156  newFont.setPixelSize( labelFont.pixelSize() );
3157  newFont.setUnderline( labelFont.underline() );
3158  newFont.setStrikeOut( labelFont.strikeOut() );
3159  newFont.setWordSpacing( labelFont.wordSpacing() );
3160  newFont.setLetterSpacing( QFont::AbsoluteSpacing, labelFont.letterSpacing() );
3161 
3162  labelFont = newFont;
3163  }
3164 
3165  // data defined word spacing?
3166  double wordspace = labelFont.wordSpacing();
3167  if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::FontWordSpacing ) )
3168  {
3169  context.expressionContext().setOriginalValueVariable( wordspace );
3170  wordspace = mDataDefinedProperties.valueAsDouble( QgsPalLayerSettings::FontWordSpacing, context.expressionContext(), wordspace );
3171  }
3172  labelFont.setWordSpacing( context.convertToPainterUnits( wordspace, fontunits, mFormat.sizeMapUnitScale() ) );
3173 
3174  // data defined letter spacing?
3175  double letterspace = labelFont.letterSpacing();
3176  if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::FontLetterSpacing ) )
3177  {
3178  context.expressionContext().setOriginalValueVariable( letterspace );
3179  letterspace = mDataDefinedProperties.valueAsDouble( QgsPalLayerSettings::FontLetterSpacing, context.expressionContext(), letterspace );
3180  }
3181  labelFont.setLetterSpacing( QFont::AbsoluteSpacing, context.convertToPainterUnits( letterspace, fontunits, mFormat.sizeMapUnitScale() ) );
3182 
3183  // data defined strikeout font style?
3184  if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Strikeout ) )
3185  {
3186  context.expressionContext().setOriginalValueVariable( labelFont.strikeOut() );
3187  bool strikeout = mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::Strikeout, context.expressionContext(), false );
3188  labelFont.setStrikeOut( strikeout );
3189  }
3190 
3191  // data defined stretch
3192  if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::FontStretchFactor ) )
3193  {
3195  labelFont.setStretch( mDataDefinedProperties.valueAsInt( QgsPalLayerSettings::FontStretchFactor, context.expressionContext(), mFormat.stretchFactor() ) );
3196  }
3197 
3198  // data defined underline font style?
3199  if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Underline ) )
3200  {
3201  context.expressionContext().setOriginalValueVariable( labelFont.underline() );
3202  bool underline = mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::Underline, context.expressionContext(), false );
3203  labelFont.setUnderline( underline );
3204  }
3205 
3206  // pass the rest on to QgsPalLabeling::drawLabeling
3207 
3208  // data defined font color?
3209  dataDefinedValEval( DDColor, QgsPalLayerSettings::Color, exprVal, context.expressionContext(), QgsSymbolLayerUtils::encodeColor( mFormat.color() ) );
3210 
3211  // data defined font opacity?
3212  dataDefinedValEval( DDOpacity, QgsPalLayerSettings::FontOpacity, exprVal, context.expressionContext(), mFormat.opacity() * 100 );
3213 
3214  // data defined font blend mode?
3215  dataDefinedValEval( DDBlendMode, QgsPalLayerSettings::FontBlendMode, exprVal, context.expressionContext() );
3216 
3217 }
3218 
3219 void QgsPalLayerSettings::parseTextBuffer( QgsRenderContext &context )
3220 {
3221  QVariant exprVal; // value() is repeatedly nulled on data defined evaluation and replaced when successful
3222 
3223  QgsTextBufferSettings buffer = mFormat.buffer();
3224 
3225  // data defined draw buffer?
3226  bool drawBuffer = mFormat.buffer().enabled();
3227  if ( dataDefinedValEval( DDBool, QgsPalLayerSettings::BufferDraw, exprVal, context.expressionContext(), buffer.enabled() ) )
3228  {
3229  drawBuffer = exprVal.toBool();
3230  }
3231  else if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::BufferDraw ) && exprVal.isNull() )
3232  {
3233  dataDefinedValues.insert( QgsPalLayerSettings::BufferDraw, QVariant( drawBuffer ) );
3234  }
3235 
3236  if ( !drawBuffer )
3237  {
3238  return;
3239  }
3240 
3241  // data defined buffer size?
3242  double bufrSize = buffer.size();
3243  if ( dataDefinedValEval( DDDoublePos, QgsPalLayerSettings::BufferSize, exprVal, context.expressionContext(), buffer.size() ) )
3244  {
3245  bufrSize = exprVal.toDouble();
3246  }
3247 
3248  // data defined buffer transparency?
3249  double bufferOpacity = buffer.opacity() * 100;
3250  if ( dataDefinedValEval( DDOpacity, QgsPalLayerSettings::BufferOpacity, exprVal, context.expressionContext(), bufferOpacity ) )
3251  {
3252  bufferOpacity = exprVal.toDouble();
3253  }
3254 
3255  drawBuffer = ( drawBuffer && bufrSize > 0.0 && bufferOpacity > 0 );
3256 
3257  if ( !drawBuffer )
3258  {
3259  dataDefinedValues.insert( QgsPalLayerSettings::BufferDraw, QVariant( false ) ); // trigger value
3260  dataDefinedValues.remove( QgsPalLayerSettings::BufferSize );
3261  dataDefinedValues.remove( QgsPalLayerSettings::BufferOpacity );
3262  return; // don't bother evaluating values that won't be used
3263  }
3264 
3265  // data defined buffer units?
3266  dataDefinedValEval( DDUnits, QgsPalLayerSettings::BufferUnit, exprVal, context.expressionContext() );
3267 
3268  // data defined buffer color?
3269  dataDefinedValEval( DDColor, QgsPalLayerSettings::BufferColor, exprVal, context.expressionContext(), QgsSymbolLayerUtils::encodeColor( buffer.color() ) );
3270 
3271  // data defined buffer pen join style?
3272  dataDefinedValEval( DDJoinStyle, QgsPalLayerSettings::BufferJoinStyle, exprVal, context.expressionContext(), QgsSymbolLayerUtils::encodePenJoinStyle( buffer.joinStyle() ) );
3273 
3274  // data defined buffer blend mode?
3275  dataDefinedValEval( DDBlendMode, QgsPalLayerSettings::BufferBlendMode, exprVal, context.expressionContext() );
3276 }
3277 
3278 void QgsPalLayerSettings::parseTextMask( QgsRenderContext &context )
3279 {
3280  QVariant exprVal; // value() is repeatedly nulled on data defined evaluation and replaced when successful
3281 
3282  QgsTextMaskSettings mask = mFormat.mask();
3283 
3284  // data defined enabled mask?
3285  bool maskEnabled = mask.enabled();
3286  if ( dataDefinedValEval( DDBool, QgsPalLayerSettings::MaskEnabled, exprVal, context.expressionContext(), mask.enabled() ) )
3287  {
3288  maskEnabled = exprVal.toBool();
3289  }
3290 
3291  if ( !maskEnabled )
3292  {
3293  return;
3294  }
3295 
3296  // data defined buffer size?
3297  double bufrSize = mask.size();
3298  if ( dataDefinedValEval( DDDoublePos, QgsPalLayerSettings::MaskBufferSize, exprVal, context.expressionContext(), mask.size() ) )
3299  {
3300  bufrSize = exprVal.toDouble();
3301  }
3302 
3303  // data defined opacity?
3304  double opacity = mask.opacity() * 100;
3305  if ( dataDefinedValEval( DDOpacity, QgsPalLayerSettings::MaskOpacity, exprVal, context.expressionContext(), opacity ) )
3306  {
3307  opacity = exprVal.toDouble();
3308  }
3309 
3310  maskEnabled = ( maskEnabled && bufrSize > 0.0 && opacity > 0 );
3311 
3312  if ( !maskEnabled )
3313  {
3314  dataDefinedValues.insert( QgsPalLayerSettings::MaskEnabled, QVariant( false ) ); // trigger value
3315  dataDefinedValues.remove( QgsPalLayerSettings::MaskBufferSize );
3316  dataDefinedValues.remove( QgsPalLayerSettings::MaskOpacity );
3317  return; // don't bother evaluating values that won't be used
3318  }
3319 
3320  // data defined buffer units?
3321  dataDefinedValEval( DDUnits, QgsPalLayerSettings::MaskBufferUnit, exprVal, context.expressionContext() );
3322 
3323  // data defined buffer pen join style?
3324  dataDefinedValEval( DDJoinStyle, QgsPalLayerSettings::MaskJoinStyle, exprVal, context.expressionContext(), QgsSymbolLayerUtils::encodePenJoinStyle( mask.joinStyle() ) );
3325 }
3326 
3327 void QgsPalLayerSettings::parseTextFormatting( QgsRenderContext &context )
3328 {
3329  QVariant exprVal; // value() is repeatedly nulled on data defined evaluation and replaced when successful
3330 
3331  // data defined multiline wrap character?
3332  QString wrapchr = wrapChar;
3333  if ( dataDefinedValEval( DDString, QgsPalLayerSettings::MultiLineWrapChar, exprVal, context.expressionContext(), wrapChar ) )
3334  {
3335  wrapchr = exprVal.toString();
3336  }
3337 
3338  int evalAutoWrapLength = autoWrapLength;
3339  if ( dataDefinedValEval( DDInt, QgsPalLayerSettings::AutoWrapLength, exprVal, context.expressionContext(), evalAutoWrapLength ) )
3340  {
3341  evalAutoWrapLength = exprVal.toInt();
3342  }
3343 
3344  // data defined multiline height?
3345  dataDefinedValEval( DDDouble, QgsPalLayerSettings::MultiLineHeight, exprVal, context.expressionContext() );
3346 
3347  // data defined multiline text align?
3348  if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::MultiLineAlignment ) )
3349  {
3350  context.expressionContext().setOriginalValueVariable( mFormat.lineHeight() );
3351  exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::MultiLineAlignment, context.expressionContext() );
3352  if ( !exprVal.isNull() )
3353  {
3354  QString str = exprVal.toString().trimmed();
3355  QgsDebugMsgLevel( QStringLiteral( "exprVal MultiLineAlignment:%1" ).arg( str ), 4 );
3356 
3357  if ( !str.isEmpty() )
3358  {
3359  // "Left"
3361 
3362  if ( str.compare( QLatin1String( "Center" ), Qt::CaseInsensitive ) == 0 )
3363  {
3365  }
3366  else if ( str.compare( QLatin1String( "Right" ), Qt::CaseInsensitive ) == 0 )
3367  {
3368  aligntype = QgsPalLayerSettings::MultiRight;
3369  }
3370  else if ( str.compare( QLatin1String( "Follow" ), Qt::CaseInsensitive ) == 0 )
3371  {
3373  }
3374  else if ( str.compare( QLatin1String( "Justify" ), Qt::CaseInsensitive ) == 0 )
3375  {
3377  }
3378  dataDefinedValues.insert( QgsPalLayerSettings::MultiLineAlignment, QVariant( static_cast< int >( aligntype ) ) );
3379  }
3380  }
3381  }
3382 
3383  // text orientation
3384  if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::TextOrientation ) )
3385  {
3386  const QString encoded = QgsTextRendererUtils::encodeTextOrientation( mFormat.orientation() );
3387  context.expressionContext().setOriginalValueVariable( encoded );
3388  exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::TextOrientation, context.expressionContext() );
3389  if ( !exprVal.isNull() )
3390  {
3391  QString str = exprVal.toString().trimmed();
3392  if ( !str.isEmpty() )
3393  dataDefinedValues.insert( QgsPalLayerSettings::TextOrientation, str );
3394  }
3395  }
3396 
3397  // data defined direction symbol?
3398  bool drawDirSymb = mLineSettings.addDirectionSymbol();
3399  if ( dataDefinedValEval( DDBool, QgsPalLayerSettings::DirSymbDraw, exprVal, context.expressionContext(), drawDirSymb ) )
3400  {
3401  drawDirSymb = exprVal.toBool();
3402  }
3403 
3404  if ( drawDirSymb )
3405  {
3406  // data defined direction left symbol?
3407  dataDefinedValEval( DDString, QgsPalLayerSettings::DirSymbLeft, exprVal, context.expressionContext(), mLineSettings.leftDirectionSymbol() );
3408 
3409  // data defined direction right symbol?
3410  dataDefinedValEval( DDString, QgsPalLayerSettings::DirSymbRight, exprVal, context.expressionContext(), mLineSettings.rightDirectionSymbol() );
3411 
3412  // data defined direction symbol placement?
3413  exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::DirSymbPlacement, context.expressionContext() );
3414  if ( !exprVal.isNull() )
3415  {
3416  QString str = exprVal.toString().trimmed();
3417  QgsDebugMsgLevel( QStringLiteral( "exprVal DirSymbPlacement:%1" ).arg( str ), 4 );
3418 
3419  if ( !str.isEmpty() )
3420  {
3421  // "LeftRight"
3423 
3424  if ( str.compare( QLatin1String( "Above" ), Qt::CaseInsensitive ) == 0 )
3425  {
3427  }
3428  else if ( str.compare( QLatin1String( "Below" ), Qt::CaseInsensitive ) == 0 )
3429  {
3431  }
3432  dataDefinedValues.insert( QgsPalLayerSettings::DirSymbPlacement, QVariant( static_cast< int >( placetype ) ) );
3433  }
3434  }
3435 
3436  // data defined direction symbol reversed?
3437  dataDefinedValEval( DDBool, QgsPalLayerSettings::DirSymbReverse, exprVal, context.expressionContext(), mLineSettings.reverseDirectionSymbol() );
3438  }
3439 
3440  // formatting for numbers is inline with generation of base label text and not passed to label painting
3441 }
3442 
3443 void QgsPalLayerSettings::parseShapeBackground( QgsRenderContext &context )
3444 {
3445  QVariant exprVal; // value() is repeatedly nulled on data defined evaluation and replaced when successful
3446 
3447  QgsTextBackgroundSettings background = mFormat.background();
3448 
3449  // data defined draw shape?
3450  bool drawShape = background.enabled();
3451  if ( dataDefinedValEval( DDBool, QgsPalLayerSettings::ShapeDraw, exprVal, context.expressionContext(), drawShape ) )
3452  {
3453  drawShape = exprVal.toBool();
3454  }
3455 
3456  if ( !drawShape )
3457  {
3458  return;
3459  }
3460 
3461  // data defined shape transparency?
3462  double shapeOpacity = background.opacity() * 100;
3463  if ( dataDefinedValEval( DDOpacity, QgsPalLayerSettings::ShapeOpacity, exprVal, context.expressionContext(), shapeOpacity ) )
3464  {
3465  shapeOpacity = 100.0 * exprVal.toDouble();
3466  }
3467 
3468  drawShape = ( drawShape && shapeOpacity > 0 ); // size is not taken into account (could be)
3469 
3470  if ( !drawShape )
3471  {
3472  dataDefinedValues.insert( QgsPalLayerSettings::ShapeDraw, QVariant( false ) ); // trigger value
3473  dataDefinedValues.remove( QgsPalLayerSettings::ShapeOpacity );
3474  return; // don't bother evaluating values that won't be used
3475  }
3476 
3477  // data defined shape kind?
3478  QgsTextBackgroundSettings::ShapeType shapeKind = background.type();
3479  if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::ShapeKind ) )
3480  {
3481  exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::ShapeKind, context.expressionContext() );
3482  if ( !exprVal.isNull() )
3483  {
3484  QString skind = exprVal.toString().trimmed();
3485  QgsDebugMsgLevel( QStringLiteral( "exprVal ShapeKind:%1" ).arg( skind ), 4 );
3486 
3487  if ( !skind.isEmpty() )
3488  {
3489  shapeKind = QgsTextRendererUtils::decodeShapeType( skind );
3490  dataDefinedValues.insert( QgsPalLayerSettings::ShapeKind, QVariant( static_cast< int >( shapeKind ) ) );
3491  }
3492  }
3493  }
3494 
3495  // data defined shape SVG path?
3496  QString svgPath = background.svgFile();
3497  if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::ShapeSVGFile ) )
3498  {
3499  context.expressionContext().setOriginalValueVariable( svgPath );
3500  exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::ShapeSVGFile, context.expressionContext() );
3501  if ( !exprVal.isNull() )
3502  {
3503  QString svgfile = exprVal.toString().trimmed();
3504  QgsDebugMsgLevel( QStringLiteral( "exprVal ShapeSVGFile:%1" ).arg( svgfile ), 4 );
3505 
3506  // '' empty paths are allowed
3507  svgPath = QgsSymbolLayerUtils::svgSymbolNameToPath( svgfile, context.pathResolver() );
3508  dataDefinedValues.insert( QgsPalLayerSettings::ShapeSVGFile, QVariant( svgPath ) );
3509  }
3510  }
3511 
3512  // data defined shape size type?
3513  QgsTextBackgroundSettings::SizeType shpSizeType = background.sizeType();
3514  if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::ShapeSizeType ) )
3515  {
3516  exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::ShapeSizeType, context.expressionContext() );
3517  if ( !exprVal.isNull() )
3518  {
3519  QString stype = exprVal.toString().trimmed();
3520  QgsDebugMsgLevel( QStringLiteral( "exprVal ShapeSizeType:%1" ).arg( stype ), 4 );
3521 
3522  if ( !stype.isEmpty() )
3523  {
3524  shpSizeType = QgsTextRendererUtils::decodeBackgroundSizeType( stype );
3525  dataDefinedValues.insert( QgsPalLayerSettings::ShapeSizeType, QVariant( static_cast< int >( shpSizeType ) ) );
3526  }
3527  }
3528  }
3529 
3530  // data defined shape size X? (SVGs only use X for sizing)
3531  double ddShpSizeX = background.size().width();
3532  if ( dataDefinedValEval( DDDouble, QgsPalLayerSettings::ShapeSizeX, exprVal, context.expressionContext(), ddShpSizeX ) )
3533  {
3534  ddShpSizeX = exprVal.toDouble();
3535  }
3536 
3537  // data defined shape size Y?
3538  double ddShpSizeY = background.size().height();
3539  if ( dataDefinedValEval( DDDouble, QgsPalLayerSettings::ShapeSizeY, exprVal, context.expressionContext(), ddShpSizeY ) )
3540  {
3541  ddShpSizeY = exprVal.toDouble();
3542  }
3543 
3544  // don't continue under certain circumstances (e.g. size is fixed)
3545  bool skip = false;
3546  if ( shapeKind == QgsTextBackgroundSettings::ShapeSVG
3547  && ( svgPath.isEmpty()
3548  || ( !svgPath.isEmpty()
3549  && shpSizeType == QgsTextBackgroundSettings::SizeFixed
3550  && ddShpSizeX == 0.0 ) ) )
3551  {
3552  skip = true;
3553  }
3555  && ( !background.markerSymbol()
3556  || ( background.markerSymbol()
3557  && shpSizeType == QgsTextBackgroundSettings::SizeFixed
3558  && ddShpSizeX == 0.0 ) ) )
3559  {
3560  skip = true;
3561  }
3562  if ( shapeKind != QgsTextBackgroundSettings::ShapeSVG
3564  && shpSizeType == QgsTextBackgroundSettings::SizeFixed
3565  && ( ddShpSizeX == 0.0 || ddShpSizeY == 0.0 ) )
3566  {
3567  skip = true;
3568  }
3569 
3570  if ( skip )
3571  {
3572  dataDefinedValues.insert( QgsPalLayerSettings::ShapeDraw, QVariant( false ) ); // trigger value
3573  dataDefinedValues.remove( QgsPalLayerSettings::ShapeOpacity );
3574  dataDefinedValues.remove( QgsPalLayerSettings::ShapeKind );
3575  dataDefinedValues.remove( QgsPalLayerSettings::ShapeSVGFile );
3576  dataDefinedValues.remove( QgsPalLayerSettings::ShapeSizeX );
3577  dataDefinedValues.remove( QgsPalLayerSettings::ShapeSizeY );
3578  return; // don't bother evaluating values that won't be used
3579  }
3580 
3581  // data defined shape size units?
3582  dataDefinedValEval( DDUnits, QgsPalLayerSettings::ShapeSizeUnits, exprVal, context.expressionContext() );
3583 
3584  // data defined shape rotation type?
3585  if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::ShapeRotationType ) )
3586  {
3587  exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::ShapeRotationType, context.expressionContext() );
3588  if ( !exprVal.isNull() )
3589  {
3590  QString rotstr = exprVal.toString().trimmed();
3591  QgsDebugMsgLevel( QStringLiteral( "exprVal ShapeRotationType:%1" ).arg( rotstr ), 4 );
3592 
3593  if ( !rotstr.isEmpty() )
3594  {
3595  // "Sync"
3597  dataDefinedValues.insert( QgsPalLayerSettings::ShapeRotationType, QVariant( static_cast< int >( rottype ) ) );
3598  }
3599  }
3600  }
3601 
3602  // data defined shape rotation?
3603  dataDefinedValEval( DDRotation180, QgsPalLayerSettings::ShapeRotation, exprVal, context.expressionContext(), background.rotation() );
3604 
3605  // data defined shape offset?
3606  dataDefinedValEval( DDPointF, QgsPalLayerSettings::ShapeOffset, exprVal, context.expressionContext(), QgsSymbolLayerUtils::encodePoint( background.offset() ) );
3607 
3608  // data defined shape offset units?
3609  dataDefinedValEval( DDUnits, QgsPalLayerSettings::ShapeOffsetUnits, exprVal, context.expressionContext() );
3610 
3611  // data defined shape radii?
3612  dataDefinedValEval( DDSizeF, QgsPalLayerSettings::ShapeRadii, exprVal, context.expressionContext(), QgsSymbolLayerUtils::encodeSize( background.radii() ) );
3613 
3614  // data defined shape radii units?
3615  dataDefinedValEval( DDUnits, QgsPalLayerSettings::ShapeRadiiUnits, exprVal, context.expressionContext() );
3616 
3617  // data defined shape blend mode?
3618  dataDefinedValEval( DDBlendMode, QgsPalLayerSettings::ShapeBlendMode, exprVal, context.expressionContext() );
3619 
3620  // data defined shape fill color?
3621  dataDefinedValEval( DDColor, QgsPalLayerSettings::ShapeFillColor, exprVal, context.expressionContext(), QgsSymbolLayerUtils::encodeColor( background.fillColor() ) );
3622 
3623  // data defined shape stroke color?
3624  dataDefinedValEval( DDColor, QgsPalLayerSettings::ShapeStrokeColor, exprVal, context.expressionContext(), QgsSymbolLayerUtils::encodeColor( background.strokeColor() ) );
3625 
3626  // data defined shape stroke width?
3627  dataDefinedValEval( DDDoublePos, QgsPalLayerSettings::ShapeStrokeWidth, exprVal, context.expressionContext(), background.strokeWidth() );
3628 
3629  // data defined shape stroke width units?
3630  dataDefinedValEval( DDUnits, QgsPalLayerSettings::ShapeStrokeWidthUnits, exprVal, context.expressionContext() );
3631 
3632  // data defined shape join style?
3633  dataDefinedValEval( DDJoinStyle, QgsPalLayerSettings::ShapeJoinStyle, exprVal, context.expressionContext(), QgsSymbolLayerUtils::encodePenJoinStyle( background.joinStyle() ) );
3634 
3635 }
3636 
3637 void QgsPalLayerSettings::parseDropShadow( QgsRenderContext &context )
3638 {
3639  QVariant exprVal; // value() is repeatedly nulled on data defined evaluation and replaced when successful
3640 
3641  QgsTextShadowSettings shadow = mFormat.shadow();
3642 
3643  // data defined draw shadow?
3644  bool drawShadow = shadow.enabled();
3645  if ( dataDefinedValEval( DDBool, QgsPalLayerSettings::ShadowDraw, exprVal, context.expressionContext(), drawShadow ) )
3646  {
3647  drawShadow = exprVal.toBool();
3648  }
3649 
3650  if ( !drawShadow )
3651  {
3652  return;
3653  }
3654 
3655  // data defined shadow transparency?
3656  double shadowOpacity = shadow.opacity() * 100;
3657  if ( dataDefinedValEval( DDOpacity, QgsPalLayerSettings::ShadowOpacity, exprVal, context.expressionContext(), shadowOpacity ) )
3658  {
3659  shadowOpacity = exprVal.toDouble();
3660  }
3661 
3662  // data defined shadow offset distance?
3663  double shadowOffDist = shadow.offsetDistance();
3664  if ( dataDefinedValEval( DDDoublePos, QgsPalLayerSettings::ShadowOffsetDist, exprVal, context.expressionContext(), shadowOffDist ) )
3665  {
3666  shadowOffDist = exprVal.toDouble();
3667  }
3668 
3669  // data defined shadow offset distance?
3670  double shadowRad = shadow.blurRadius();
3671  if ( dataDefinedValEval( DDDoublePos, QgsPalLayerSettings::ShadowRadius, exprVal, context.expressionContext(), shadowRad ) )
3672  {
3673  shadowRad = exprVal.toDouble();
3674  }
3675 
3676  drawShadow = ( drawShadow && shadowOpacity > 0 && !( shadowOffDist == 0.0 && shadowRad == 0.0 ) );
3677 
3678  if ( !drawShadow )
3679  {
3680  dataDefinedValues.insert( QgsPalLayerSettings::ShadowDraw, QVariant( false ) ); // trigger value
3681  dataDefinedValues.remove( QgsPalLayerSettings::ShadowOpacity );
3682  dataDefinedValues.remove( QgsPalLayerSettings::ShadowOffsetDist );
3683  dataDefinedValues.remove( QgsPalLayerSettings::ShadowRadius );
3684  return; // don't bother evaluating values that won't be used
3685  }
3686 
3687  // data defined shadow under type?
3688  if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::ShadowUnder ) )
3689  {
3690  exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::ShadowUnder, context.expressionContext() );
3691  if ( !exprVal.isNull() )
3692  {
3693  QString str = exprVal.toString().trimmed();
3694  QgsDebugMsgLevel( QStringLiteral( "exprVal ShadowUnder:%1" ).arg( str ), 4 );
3695 
3696  if ( !str.isEmpty() )
3697  {
3699  dataDefinedValues.insert( QgsPalLayerSettings::ShadowUnder, QVariant( static_cast< int >( shdwtype ) ) );
3700  }
3701  }
3702  }
3703 
3704  // data defined shadow offset angle?
3705  dataDefinedValEval( DDRotation180, QgsPalLayerSettings::ShadowOffsetAngle, exprVal, context.expressionContext(), shadow.offsetAngle() );
3706 
3707  // data defined shadow offset units?
3708  dataDefinedValEval( DDUnits, QgsPalLayerSettings::ShadowOffsetUnits, exprVal, context.expressionContext() );
3709 
3710  // data defined shadow radius?
3711  dataDefinedValEval( DDDouble, QgsPalLayerSettings::ShadowRadius, exprVal, context.expressionContext(), shadow.blurRadius() );
3712 
3713  // data defined shadow radius units?
3714  dataDefinedValEval( DDUnits, QgsPalLayerSettings::ShadowRadiusUnits, exprVal, context.expressionContext() );
3715 
3716  // data defined shadow scale? ( gui bounds to 0-2000, no upper bound here )
3717  dataDefinedValEval( DDIntPos, QgsPalLayerSettings::ShadowScale, exprVal, context.expressionContext(), shadow.scale() );
3718 
3719  // data defined shadow color?
3720  dataDefinedValEval( DDColor, QgsPalLayerSettings::ShadowColor, exprVal, context.expressionContext(), QgsSymbolLayerUtils::encodeColor( shadow.color() ) );
3721 
3722  // data defined shadow blend mode?
3723  dataDefinedValEval( DDBlendMode, QgsPalLayerSettings::ShadowBlendMode, exprVal, context.expressionContext() );
3724 }
3725 
3726 // -------------
3727 
3728 
3730 {
3731  switch ( layer->type() )
3732  {
3734  {
3735  const QgsVectorLayer *vl = qobject_cast< const QgsVectorLayer * >( layer );
3736  return vl->labelsEnabled() || vl->diagramsEnabled();
3737  }
3738 
3740  {
3741  const QgsVectorTileLayer *vl = qobject_cast< const QgsVectorTileLayer * >( layer );
3742  if ( !vl->labeling() )
3743  return false;
3744 
3745  if ( const QgsVectorTileBasicLabeling *labeling = dynamic_cast< const QgsVectorTileBasicLabeling *>( vl->labeling() ) )
3746  return !labeling->styles().empty();
3747 
3748  return false;
3749  }
3750 
3757  return false;
3758  }
3759  return false;
3760 }
3761 
3762 
3763 bool QgsPalLabeling::geometryRequiresPreparation( const QgsGeometry &geometry, QgsRenderContext &context, const QgsCoordinateTransform &ct, const QgsGeometry &clipGeometry, bool mergeLines )
3764 {
3765  if ( geometry.isNull() )
3766  {
3767  return false;
3768  }
3769 
3770  if ( geometry.type() == QgsWkbTypes::LineGeometry && geometry.isMultipart() && mergeLines )
3771  {
3772  return true;
3773  }
3774 
3775  //requires reprojection
3776  if ( ct.isValid() && !ct.isShortCircuited() )
3777  return true;
3778 
3779  //requires rotation
3780  const QgsMapToPixel &m2p = context.mapToPixel();
3781  if ( !qgsDoubleNear( m2p.mapRotation(), 0 ) )
3782  return true;
3783 
3784  //requires clip
3785  if ( !clipGeometry.isNull() && !clipGeometry.boundingBox().contains( geometry.boundingBox() ) )
3786  return true;
3787 
3788  //requires fixing
3789  if ( geometry.type() == QgsWkbTypes::PolygonGeometry && !geometry.isGeosValid() )
3790  return true;
3791 
3792  return false;
3793 }
3794 
3795 QStringList QgsPalLabeling::splitToLines( const QString &text, const QString &wrapCharacter, const int autoWrapLength, const bool useMaxLineLengthWhenAutoWrapping )
3796 {
3797  QStringList multiLineSplit;
3798  if ( !wrapCharacter.isEmpty() && wrapCharacter != QLatin1String( "\n" ) )
3799  {
3800  //wrap on both the wrapchr and new line characters
3801  const QStringList lines = text.split( wrapCharacter );
3802  for ( const QString &line : lines )
3803  {
3804  multiLineSplit.append( line.split( '\n' ) );
3805  }
3806  }
3807  else
3808  {
3809  multiLineSplit = text.split( '\n' );
3810  }
3811 
3812  // apply auto wrapping to each manually created line
3813  if ( autoWrapLength != 0 )
3814  {
3815  QStringList autoWrappedLines;
3816  autoWrappedLines.reserve( multiLineSplit.count() );
3817  for ( const QString &line : std::as_const( multiLineSplit ) )
3818  {
3819  autoWrappedLines.append( QgsStringUtils::wordWrap( line, autoWrapLength, useMaxLineLengthWhenAutoWrapping ).split( '\n' ) );
3820  }
3821  multiLineSplit = autoWrappedLines;
3822  }
3823  return multiLineSplit;
3824 }
3825 
3826 QStringList QgsPalLabeling::splitToGraphemes( const QString &text )
3827 {
3828  QStringList graphemes;
3829  QTextBoundaryFinder boundaryFinder( QTextBoundaryFinder::Grapheme, text );
3830  int currentBoundary = -1;
3831  int previousBoundary = 0;
3832  while ( ( currentBoundary = boundaryFinder.toNextBoundary() ) > 0 )
3833  {
3834  graphemes << text.mid( previousBoundary, currentBoundary - previousBoundary );
3835  previousBoundary = currentBoundary;
3836  }
3837  return graphemes;
3838 }
3839 
3840 QgsGeometry QgsPalLabeling::prepareGeometry( const QgsGeometry &geometry, QgsRenderContext &context, const QgsCoordinateTransform &ct, const QgsGeometry &clipGeometry, bool mergeLines )
3841 {
3842  if ( geometry.isNull() )
3843  {
3844  return QgsGeometry();
3845  }
3846 
3847  //don't modify the feature's geometry so that geometry based expressions keep working
3848  QgsGeometry geom = geometry;
3849 
3850  if ( geom.type() == QgsWkbTypes::LineGeometry && geom.isMultipart() && mergeLines )
3851  {
3852  geom = geom.mergeLines();
3853  }
3854 
3855  //reproject the geometry if necessary
3856  if ( ct.isValid() && !ct.isShortCircuited() )
3857  {
3858  try
3859  {
3860  geom.transform( ct );
3861  }
3862  catch ( QgsCsException &cse )
3863  {
3864  Q_UNUSED( cse )
3865  QgsDebugMsgLevel( QStringLiteral( "Ignoring feature due to transformation exception" ), 4 );
3866  return QgsGeometry();
3867  }
3868  // geometry transforms may result in nan points, remove these
3869  geom.filterVertices( []( const QgsPoint & point )->bool
3870  {
3871  return std::isfinite( point.x() ) && std::isfinite( point.y() );
3872  } );
3873  if ( QgsCurvePolygon *cp = qgsgeometry_cast< QgsCurvePolygon * >( geom.get() ) )
3874  {
3875  cp->removeInvalidRings();
3876  }
3877  else if ( QgsMultiSurface *ms = qgsgeometry_cast< QgsMultiSurface * >( geom.get() ) )
3878  {
3879  for ( int i = 0; i < ms->numGeometries(); ++i )
3880  {
3881  if ( QgsCurvePolygon *cp = qgsgeometry_cast< QgsCurvePolygon * >( ms->geometryN( i ) ) )
3882  cp->removeInvalidRings();
3883  }
3884  }
3885  }
3886 
3887  // Rotate the geometry if needed, before clipping
3888  const QgsMapToPixel &m2p = context.mapToPixel();
3889  if ( !qgsDoubleNear( m2p.mapRotation(), 0 ) )
3890  {
3891  QgsPointXY center = context.mapExtent().center();
3892  if ( geom.rotate( m2p.mapRotation(), center ) != Qgis::GeometryOperationResult::Success )
3893  {
3894  QgsDebugMsg( QStringLiteral( "Error rotating geometry" ).arg( geom.asWkt() ) );
3895  return QgsGeometry();
3896  }
3897  }
3898 
3899 #if GEOS_VERSION_MAJOR>3 || ( GEOS_VERSION_MAJOR == 3 && GEOS_VERSION_MINOR>=9 )
3900  // much faster code path for GEOS 3.9+
3901  const bool mustClip = ( !clipGeometry.isNull() &&
3902  ( ( qgsDoubleNear( m2p.mapRotation(), 0 ) && !clipGeometry.boundingBox().contains( geom.boundingBox() ) )
3903  || ( !qgsDoubleNear( m2p.mapRotation(), 0 ) && !clipGeometry.contains( geom ) ) ) );
3904 
3905  bool mustClipExact = false;
3906  if ( mustClip )
3907  {
3908  // nice and fast, but can result in invalid geometries. At least it will potentially strip out a bunch of unwanted vertices upfront!
3909  QgsGeometry clipGeom = geom.clipped( clipGeometry.boundingBox() );
3910  if ( clipGeom.isEmpty() )
3911  return QgsGeometry();
3912 
3913  geom = clipGeom;
3914 
3915  // we've now clipped against the BOUNDING BOX of clipGeometry. But if clipGeometry is an axis parallel rectangle, then there's no
3916  // need to do an exact (potentially costly) intersection clip as well!
3917  mustClipExact = !clipGeometry.isAxisParallelRectangle( 0.001 );
3918  }
3919 
3920  // fix invalid polygons
3921  if ( geom.type() == QgsWkbTypes::PolygonGeometry )
3922  {
3923  if ( geom.isMultipart() )
3924  {
3925  // important -- we need to treat ever part in isolation here. We can't test the validity of the whole geometry
3926  // at once, because touching parts would result in an invalid geometry, and buffering this "dissolves" the parts.
3927  // because the actual label engine treats parts as separate entities, we aren't bound by the usual "touching parts are invalid" rule
3928  // see https://github.com/qgis/QGIS/issues/26763
3929  QVector< QgsGeometry> parts;
3930  parts.reserve( qgsgeometry_cast< const QgsGeometryCollection * >( geom.constGet() )->numGeometries() );
3931  for ( auto it = geom.const_parts_begin(); it != geom.const_parts_end(); ++it )
3932  {
3933  QgsGeometry partGeom( ( *it )->clone() );
3934  if ( !partGeom.isGeosValid() )
3935  {
3936 
3937  partGeom = partGeom.makeValid();
3938  }
3939  parts.append( partGeom );
3940  }
3941  geom = QgsGeometry::collectGeometry( parts );
3942  }
3943  else if ( !geom.isGeosValid() )
3944  {
3945 
3946  QgsGeometry bufferGeom = geom.makeValid();
3947  if ( bufferGeom.isNull() )
3948  {
3949  QgsDebugMsg( QStringLiteral( "Could not repair geometry: %1" ).arg( bufferGeom.lastError() ) );
3950  return QgsGeometry();
3951  }
3952  geom = bufferGeom;
3953  }
3954  }
3955 
3956  if ( mustClipExact )
3957  {
3958  // now do the real intersection against the actual clip geometry
3959  QgsGeometry clipGeom = geom.intersection( clipGeometry );
3960  if ( clipGeom.isEmpty() )
3961  {
3962  return QgsGeometry();
3963  }
3964  geom = clipGeom;
3965  }
3966 #else
3967  // fix invalid polygons
3968  if ( geom.type() == QgsWkbTypes::PolygonGeometry )
3969  {
3970  if ( geom.isMultipart() )
3971  {
3972  // important -- we need to treat ever part in isolation here. We can't test the validity of the whole geometry
3973  // at once, because touching parts would result in an invalid geometry, and buffering this "dissolves" the parts.
3974  // because the actual label engine treats parts as separate entities, we aren't bound by the usual "touching parts are invalid" rule
3975  // see https://github.com/qgis/QGIS/issues/26763
3976  QVector< QgsGeometry> parts;
3977  parts.reserve( qgsgeometry_cast< const QgsGeometryCollection * >( geom.constGet() )->numGeometries() );
3978  for ( auto it = geom.const_parts_begin(); it != geom.const_parts_end(); ++it )
3979  {
3980  QgsGeometry partGeom( ( *it )->clone() );
3981  if ( !partGeom.isGeosValid() )
3982  {
3983  partGeom = partGeom.buffer( 0, 0 );
3984  }
3985  parts.append( partGeom );
3986  }
3987  geom = QgsGeometry::collectGeometry( parts );
3988  }
3989  else if ( !geom.isGeosValid() )
3990  {
3991  QgsGeometry bufferGeom = geom.buffer( 0, 0 );
3992  if ( bufferGeom.isNull() )
3993  {
3994  QgsDebugMsg( QStringLiteral( "Could not repair geometry: %1" ).arg( bufferGeom.lastError() ) );
3995  return QgsGeometry();
3996  }
3997  geom = bufferGeom;
3998  }
3999  }
4000 
4001  if ( !clipGeometry.isNull() &&
4002  ( ( qgsDoubleNear( m2p.mapRotation(), 0 ) && !clipGeometry.boundingBox().contains( geom.boundingBox() ) )
4003  || ( !qgsDoubleNear( m2p.mapRotation(), 0 ) && !clipGeometry.contains( geom ) ) ) )
4004  {
4005  QgsGeometry clipGeom = geom.intersection( clipGeometry ); // creates new geometry
4006  if ( clipGeom.isEmpty() )
4007  {
4008  return QgsGeometry();
4009  }
4010  geom = clipGeom;
4011  }
4012 #endif
4013 
4014  return geom;
4015 }
4016 
4017 bool QgsPalLabeling::checkMinimumSizeMM( const QgsRenderContext &context, const QgsGeometry &geom, double minSize )
4018 {
4019  if ( minSize <= 0 )
4020  {
4021  return true;
4022  }
4023 
4024  if ( geom.isNull() )
4025  {
4026  return false;
4027  }
4028 
4029  QgsWkbTypes::GeometryType featureType = geom.type();
4030  if ( featureType == QgsWkbTypes::PointGeometry ) //minimum size does not apply to point features
4031  {
4032  return true;
4033  }
4034 
4035  double mapUnitsPerMM = context.mapToPixel().mapUnitsPerPixel() * context.scaleFactor();
4036  if ( featureType == QgsWkbTypes::LineGeometry )
4037  {
4038  double length = geom.length();
4039  if ( length >= 0.0 )
4040  {
4041  return ( length >= ( minSize * mapUnitsPerMM ) );
4042  }
4043  }
4044  else if ( featureType == QgsWkbTypes::PolygonGeometry )
4045  {
4046  double area = geom.area();
4047  if ( area >= 0.0 )
4048  {
4049  return ( std::sqrt( area ) >= ( minSize * mapUnitsPerMM ) );
4050  }
4051  }
4052  return true; //should never be reached. Return true in this case to label such geometries anyway.
4053 }
4054 
4055 
4056 void QgsPalLabeling::dataDefinedTextStyle( QgsPalLayerSettings &tmpLyr,
4057  const QMap< QgsPalLayerSettings::Property, QVariant > &ddValues )
4058 {
4059  QgsTextFormat format = tmpLyr.format();
4060  bool changed = false;
4061 
4062  //font color
4063  if ( ddValues.contains( QgsPalLayerSettings::Color ) )
4064  {
4065  QVariant ddColor = ddValues.value( QgsPalLayerSettings::Color );
4066  format.setColor( ddColor.value<QColor>() );
4067  changed = true;
4068  }
4069 
4070  //font transparency
4071  if ( ddValues.contains( QgsPalLayerSettings::FontOpacity ) )
4072  {
4073  format.setOpacity( ddValues.value( QgsPalLayerSettings::FontOpacity ).toDouble() / 100.0 );
4074  changed = true;
4075  }
4076 
4077  //font blend mode
4078  if ( ddValues.contains( QgsPalLayerSettings::FontBlendMode ) )
4079  {
4080  format.setBlendMode( static_cast< QPainter::CompositionMode >( ddValues.value( QgsPalLayerSettings::FontBlendMode ).toInt() ) );
4081  changed = true;
4082  }
4083 
4084  if ( changed )
4085  {
4086  tmpLyr.setFormat( format );
4087  }
4088 }
4089 
4090 void QgsPalLabeling::dataDefinedTextFormatting( QgsPalLayerSettings &tmpLyr,
4091  const QMap< QgsPalLayerSettings::Property, QVariant > &ddValues )
4092 {
4093  if ( ddValues.contains( QgsPalLayerSettings::MultiLineWrapChar ) )
4094  {
4095  tmpLyr.wrapChar = ddValues.value( QgsPalLayerSettings::MultiLineWrapChar ).toString();
4096  }
4097 
4098  if ( ddValues.contains( QgsPalLayerSettings::AutoWrapLength ) )
4099  {
4100  tmpLyr.autoWrapLength = ddValues.value( QgsPalLayerSettings::AutoWrapLength ).toInt();
4101  }
4102 
4103  if ( ddValues.contains( QgsPalLayerSettings::MultiLineHeight ) )
4104  {
4105  QgsTextFormat format = tmpLyr.format();
4106  format.setLineHeight( ddValues.value( QgsPalLayerSettings::MultiLineHeight ).toDouble() );
4107  tmpLyr.setFormat( format );
4108  }
4109 
4110  if ( ddValues.contains( QgsPalLayerSettings::MultiLineAlignment ) )
4111  {
4112  tmpLyr.multilineAlign = static_cast< QgsPalLayerSettings::MultiLineAlign >( ddValues.value( QgsPalLayerSettings::MultiLineAlignment ).toInt() );
4113  }
4114 
4115  if ( ddValues.contains( QgsPalLayerSettings::TextOrientation ) )
4116  {
4117  QgsTextFormat format = tmpLyr.format();
4119  tmpLyr.setFormat( format );
4120  }
4121 
4122  if ( ddValues.contains( QgsPalLayerSettings::DirSymbDraw ) )
4123  {
4124  tmpLyr.lineSettings().setAddDirectionSymbol( ddValues.value( QgsPalLayerSettings::DirSymbDraw ).toBool() );
4125  }
4126 
4127  if ( tmpLyr.lineSettings().addDirectionSymbol() )
4128  {
4129 
4130  if ( ddValues.contains( QgsPalLayerSettings::DirSymbLeft ) )
4131  {
4132  tmpLyr.lineSettings().setLeftDirectionSymbol( ddValues.value( QgsPalLayerSettings::DirSymbLeft ).toString() );
4133  }
4134  if ( ddValues.contains( QgsPalLayerSettings::DirSymbRight ) )
4135  {
4136  tmpLyr.lineSettings().setRightDirectionSymbol( ddValues.value( QgsPalLayerSettings::DirSymbRight ).toString() );
4137  }
4138 
4139  if ( ddValues.contains( QgsPalLayerSettings::DirSymbPlacement ) )
4140  {
4142  }
4143 
4144  if ( ddValues.contains( QgsPalLayerSettings::DirSymbReverse ) )
4145  {
4146  tmpLyr.lineSettings().setReverseDirectionSymbol( ddValues.value( QgsPalLayerSettings::DirSymbReverse ).toBool() );
4147  }
4148 
4149  }
4150 }
4151 
4152 void QgsPalLabeling::dataDefinedTextBuffer( QgsPalLayerSettings &tmpLyr,
4153  const QMap< QgsPalLayerSettings::Property, QVariant > &ddValues )
4154 {
4155  QgsTextBufferSettings buffer = tmpLyr.format().buffer();
4156  bool changed = false;
4157 
4158  //buffer draw
4159  if ( ddValues.contains( QgsPalLayerSettings::BufferDraw ) )
4160  {
4161  buffer.setEnabled( ddValues.value( QgsPalLayerSettings::BufferDraw ).toBool() );
4162  changed = true;
4163  }
4164 
4165  if ( !buffer.enabled() )
4166  {
4167  if ( changed )
4168  {
4169  QgsTextFormat format = tmpLyr.format();
4170  format.setBuffer( buffer );
4171  tmpLyr.setFormat( format );
4172  }
4173 
4174  // tmpLyr.bufferSize > 0.0 && tmpLyr.bufferTransp < 100 figured in during evaluation
4175  return; // don't continue looking for unused values
4176  }
4177 
4178  //buffer size
4179  if ( ddValues.contains( QgsPalLayerSettings::BufferSize ) )
4180  {
4181  buffer.setSize( ddValues.value( QgsPalLayerSettings::BufferSize ).toDouble() );
4182  changed = true;
4183  }
4184 
4185  //buffer opacity
4186  if ( ddValues.contains( QgsPalLayerSettings::BufferOpacity ) )
4187  {
4188  buffer.setOpacity( ddValues.value( QgsPalLayerSettings::BufferOpacity ).toDouble() / 100.0 );
4189  changed = true;
4190  }
4191 
4192  //buffer size units
4193  if ( ddValues.contains( QgsPalLayerSettings::BufferUnit ) )
4194  {
4195  QgsUnitTypes::RenderUnit bufunit = static_cast< QgsUnitTypes::RenderUnit >( ddValues.value( QgsPalLayerSettings::BufferUnit ).toInt() );
4196  buffer.setSizeUnit( bufunit );
4197  changed = true;
4198  }
4199 
4200  //buffer color
4201  if ( ddValues.contains( QgsPalLayerSettings::BufferColor ) )
4202  {
4203  QVariant ddColor = ddValues.value( QgsPalLayerSettings::BufferColor );
4204  buffer.setColor( ddColor.value<QColor>() );
4205  changed = true;
4206  }
4207 
4208  //buffer pen join style
4209  if ( ddValues.contains( QgsPalLayerSettings::BufferJoinStyle ) )
4210  {
4211  buffer.setJoinStyle( static_cast< Qt::PenJoinStyle >( ddValues.value( QgsPalLayerSettings::BufferJoinStyle ).toInt() ) );
4212  changed = true;
4213  }
4214 
4215  //buffer blend mode
4216  if ( ddValues.contains( QgsPalLayerSettings::BufferBlendMode ) )
4217  {
4218  buffer.setBlendMode( static_cast< QPainter::CompositionMode >( ddValues.value( QgsPalLayerSettings::BufferBlendMode ).toInt() ) );
4219  changed = true;
4220  }
4221 
4222  if ( changed )
4223  {
4224  QgsTextFormat format = tmpLyr.format();
4225  format.setBuffer( buffer );
4226  tmpLyr.setFormat( format );
4227  }
4228 }
4229 
4230 void QgsPalLabeling::dataDefinedTextMask( QgsPalLayerSettings &tmpLyr,
4231  const QMap< QgsPalLayerSettings::Property, QVariant > &ddValues )
4232 {
4233  if ( ddValues.isEmpty() )
4234  return;
4235 
4236  QgsTextMaskSettings mask = tmpLyr.format().mask();
4237  bool changed = false;
4238 
4239  // enabled ?
4240  if ( ddValues.contains( QgsPalLayerSettings::MaskEnabled ) )
4241  {
4242  mask.setEnabled( ddValues.value( QgsPalLayerSettings::MaskEnabled ).toBool() );
4243  changed = true;
4244  }
4245 
4246  if ( !mask.enabled() )
4247  {
4248  if ( changed )
4249  {
4250  QgsTextFormat format = tmpLyr.format();
4251  format.setMask( mask );
4252  tmpLyr.setFormat( format );
4253  }
4254 
4255  // tmpLyr.bufferSize > 0.0 && tmpLyr.bufferTransp < 100 figured in during evaluation
4256  return; // don't continue looking for unused values
4257  }
4258 
4259  // buffer size
4260  if ( ddValues.contains( QgsPalLayerSettings::MaskBufferSize ) )
4261  {
4262  mask.setSize( ddValues.value( QgsPalLayerSettings::MaskBufferSize ).toDouble() );
4263  changed = true;
4264  }
4265 
4266  // opacity
4267  if ( ddValues.contains( QgsPalLayerSettings::MaskOpacity ) )
4268  {
4269  mask.setOpacity( ddValues.value( QgsPalLayerSettings::MaskOpacity ).toDouble() / 100.0 );
4270  changed = true;
4271  }
4272 
4273  // buffer size units
4274  if ( ddValues.contains( QgsPalLayerSettings::MaskBufferUnit ) )
4275  {
4276  QgsUnitTypes::RenderUnit bufunit = static_cast< QgsUnitTypes::RenderUnit >( ddValues.value( QgsPalLayerSettings::MaskBufferUnit ).toInt() );
4277  mask.setSizeUnit( bufunit );
4278  changed = true;
4279  }
4280 
4281  // pen join style
4282  if ( ddValues.contains( QgsPalLayerSettings::MaskJoinStyle ) )
4283  {
4284  mask.setJoinStyle( static_cast< Qt::PenJoinStyle >( ddValues.value( QgsPalLayerSettings::MaskJoinStyle ).toInt() ) );
4285  changed = true;
4286  }
4287 
4288  if ( changed )
4289  {
4290  QgsTextFormat format = tmpLyr.format();
4291  format.setMask( mask );
4292  tmpLyr.setFormat( format );
4293  }
4294 }
4295 
4296 void QgsPalLabeling::dataDefinedShapeBackground( QgsPalLayerSettings &tmpLyr,
4297  const QMap< QgsPalLayerSettings::Property, QVariant > &ddValues )
4298 {
4299  QgsTextBackgroundSettings background = tmpLyr.format().background();
4300  bool changed = false;
4301 
4302  //shape draw
4303  if ( ddValues.contains( QgsPalLayerSettings::ShapeDraw ) )
4304  {
4305  background.setEnabled( ddValues.value( QgsPalLayerSettings::ShapeDraw ).toBool() );
4306  changed = true;
4307  }
4308 
4309  if ( !background.enabled() )
4310  {
4311  if ( changed )
4312  {
4313  QgsTextFormat format = tmpLyr.format();
4314  format.setBackground( background );
4315  tmpLyr.setFormat( format );
4316  }
4317  return; // don't continue looking for unused values
4318  }
4319 
4320  if ( ddValues.contains( QgsPalLayerSettings::ShapeKind ) )
4321  {
4322  background.setType( static_cast< QgsTextBackgroundSettings::ShapeType >( ddValues.value( QgsPalLayerSettings::ShapeKind ).toInt() ) );
4323  changed = true;
4324  }
4325 
4326  if ( ddValues.contains( QgsPalLayerSettings::ShapeSVGFile ) )
4327  {
4328  background.setSvgFile( ddValues.value( QgsPalLayerSettings::ShapeSVGFile ).toString() );
4329  changed = true;
4330  }
4331 
4332  if ( ddValues.contains( QgsPalLayerSettings::ShapeSizeType ) )
4333  {
4334  background.setSizeType( static_cast< QgsTextBackgroundSettings::SizeType >( ddValues.value( QgsPalLayerSettings::ShapeSizeType ).toInt() ) );
4335  changed = true;
4336  }
4337 
4338  if ( ddValues.contains( QgsPalLayerSettings::ShapeSizeX ) )
4339  {
4340  QSizeF size = background.size();
4341  size.setWidth( ddValues.value( QgsPalLayerSettings::ShapeSizeX ).toDouble() );
4342  background.setSize( size );
4343  changed = true;
4344  }
4345  if ( ddValues.contains( QgsPalLayerSettings::ShapeSizeY ) )
4346  {
4347  QSizeF size = background.size();
4348  size.setHeight( ddValues.value( QgsPalLayerSettings::ShapeSizeY ).toDouble() );
4349  background.setSize( size );
4350  changed = true;
4351  }
4352 
4353  if ( ddValues.contains( QgsPalLayerSettings::ShapeSizeUnits ) )
4354  {
4355  background.setSizeUnit( static_cast< QgsUnitTypes::RenderUnit >( ddValues.value( QgsPalLayerSettings::ShapeSizeUnits ).toInt() ) );
4356  changed = true;
4357  }
4358 
4359  if ( ddValues.contains( QgsPalLayerSettings::ShapeRotationType ) )
4360  {
4361  background.setRotationType( static_cast< QgsTextBackgroundSettings::RotationType >( ddValues.value( QgsPalLayerSettings::ShapeRotationType ).toInt() ) );
4362  changed = true;
4363  }
4364 
4365  if ( ddValues.contains( QgsPalLayerSettings::ShapeRotation ) )
4366  {
4367  background.setRotation( ddValues.value( QgsPalLayerSettings::ShapeRotation ).toDouble() );
4368  changed = true;
4369  }
4370 
4371  if ( ddValues.contains( QgsPalLayerSettings::ShapeOffset ) )
4372  {
4373  background.setOffset( ddValues.value( QgsPalLayerSettings::ShapeOffset ).toPointF() );
4374  changed = true;
4375  }
4376 
4377  if ( ddValues.contains( QgsPalLayerSettings::ShapeOffsetUnits ) )
4378  {
4379  background.setOffsetUnit( static_cast< QgsUnitTypes::RenderUnit >( ddValues.value( QgsPalLayerSettings::ShapeOffsetUnits ).toInt() ) );
4380  changed = true;
4381  }
4382 
4383  if ( ddValues.contains( QgsPalLayerSettings::ShapeRadii ) )
4384  {
4385  background.setRadii( ddValues.value( QgsPalLayerSettings::ShapeRadii ).toSizeF() );
4386  changed = true;
4387  }
4388 
4389  if ( ddValues.contains( QgsPalLayerSettings::ShapeRadiiUnits ) )
4390  {
4391  background.setRadiiUnit( static_cast< QgsUnitTypes::RenderUnit >( ddValues.value( QgsPalLayerSettings::ShapeRadiiUnits ).toInt() ) );
4392  changed = true;
4393  }
4394 
4395  if ( ddValues.contains( QgsPalLayerSettings::ShapeBlendMode ) )
4396  {
4397  background.setBlendMode( static_cast< QPainter::CompositionMode >( ddValues.value( QgsPalLayerSettings::ShapeBlendMode ).toInt() ) );
4398  changed = true;
4399  }
4400 
4401  if ( ddValues.contains( QgsPalLayerSettings::ShapeFillColor ) )
4402  {
4403  QVariant ddColor = ddValues.value( QgsPalLayerSettings::ShapeFillColor );
4404  background.setFillColor( ddColor.value<QColor>() );
4405  changed = true;
4406  }
4407 
4408  if ( ddValues.contains( QgsPalLayerSettings::ShapeStrokeColor ) )
4409  {
4410  QVariant ddColor = ddValues.value( QgsPalLayerSettings::ShapeStrokeColor );
4411  background.setStrokeColor( ddColor.value<QColor>() );
4412  changed = true;
4413  }
4414 
4415  if ( ddValues.contains( QgsPalLayerSettings::ShapeOpacity ) )
4416  {
4417  background.setOpacity( ddValues.value( QgsPalLayerSettings::ShapeOpacity ).toDouble() / 100.0 );
4418  changed = true;
4419  }
4420 
4421  if ( ddValues.contains( QgsPalLayerSettings::ShapeStrokeWidth ) )
4422  {
4423  background.setStrokeWidth( ddValues.value( QgsPalLayerSettings::ShapeStrokeWidth ).toDouble() );
4424  changed = true;
4425  }
4426 
4427  if ( ddValues.contains( QgsPalLayerSettings::ShapeStrokeWidthUnits ) )
4428  {
4429  background.setStrokeWidthUnit( static_cast< QgsUnitTypes::RenderUnit >( ddValues.value( QgsPalLayerSettings::ShapeStrokeWidthUnits ).toInt() ) );
4430  changed = true;
4431  }
4432 
4433  if ( ddValues.contains( QgsPalLayerSettings::ShapeJoinStyle ) )
4434  {
4435  background.setJoinStyle( static_cast< Qt::PenJoinStyle >( ddValues.value( QgsPalLayerSettings::ShapeJoinStyle ).toInt() ) );
4436  changed = true;
4437  }
4438 
4439  if ( changed )
4440  {
4441  QgsTextFormat format = tmpLyr.format();
4442  format.setBackground( background );
4443  tmpLyr.setFormat( format );
4444  }
4445 }
4446 
4447 void QgsPalLabeling::dataDefinedDropShadow( QgsPalLayerSettings &tmpLyr,
4448  const QMap< QgsPalLayerSettings::Property, QVariant > &ddValues )
4449 {
4450  QgsTextShadowSettings shadow = tmpLyr.format().shadow();
4451  bool changed = false;
4452 
4453  //shadow draw
4454  if ( ddValues.contains( QgsPalLayerSettings::ShadowDraw ) )
4455  {
4456  shadow.setEnabled( ddValues.value( QgsPalLayerSettings::ShadowDraw ).toBool() );
4457  changed = true;
4458  }
4459 
4460  if ( !shadow.enabled() )
4461  {
4462  if ( changed )
4463  {
4464  QgsTextFormat format = tmpLyr.format();
4465  format.setShadow( shadow );
4466  tmpLyr.setFormat( format );
4467  }
4468  return; // don't continue looking for unused values
4469  }
4470 
4471  if ( ddValues.contains( QgsPalLayerSettings::ShadowUnder ) )
4472  {
4473  shadow.setShadowPlacement( static_cast< QgsTextShadowSettings::ShadowPlacement >( ddValues.value( QgsPalLayerSettings::ShadowUnder ).toInt() ) );
4474  changed = true;
4475  }
4476 
4477  if ( ddValues.contains( QgsPalLayerSettings::ShadowOffsetAngle ) )
4478  {
4479  shadow.setOffsetAngle( ddValues.value( QgsPalLayerSettings::ShadowOffsetAngle ).toInt() );
4480  changed = true;
4481  }
4482 
4483  if ( ddValues.contains( QgsPalLayerSettings::ShadowOffsetDist ) )
4484  {
4485  shadow.setOffsetDistance( ddValues.value( QgsPalLayerSettings::ShadowOffsetDist ).toDouble() );
4486  changed = true;
4487  }
4488 
4489  if ( ddValues.contains( QgsPalLayerSettings::ShadowOffsetUnits ) )
4490  {
4491  shadow.setOffsetUnit( static_cast< QgsUnitTypes::RenderUnit >( ddValues.value( QgsPalLayerSettings::ShadowOffsetUnits ).toInt() ) );
4492  changed = true;
4493  }
4494 
4495  if ( ddValues.contains( QgsPalLayerSettings::ShadowRadius ) )
4496  {
4497  shadow.setBlurRadius( ddValues.value( QgsPalLayerSettings::ShadowRadius ).toDouble() );
4498  changed = true;
4499  }
4500 
4501  if ( ddValues.contains( QgsPalLayerSettings::ShadowRadiusUnits ) )
4502  {
4503  shadow.setBlurRadiusUnit( static_cast< QgsUnitTypes::RenderUnit >( ddValues.value( QgsPalLayerSettings::ShadowRadiusUnits ).toInt() ) );
4504  changed = true;
4505  }
4506 
4507  if ( ddValues.contains( QgsPalLayerSettings::ShadowColor ) )
4508  {
4509  QVariant ddColor = ddValues.value( QgsPalLayerSettings::ShadowColor );
4510  shadow.setColor( ddColor.value<QColor>() );
4511  changed = true;
4512  }
4513 
4514  if ( ddValues.contains( QgsPalLayerSettings::ShadowOpacity ) )
4515  {
4516  shadow.setOpacity( ddValues.value( QgsPalLayerSettings::ShadowOpacity ).toDouble() / 100.0 );
4517  changed = true;
4518  }
4519 
4520  if ( ddValues.contains( QgsPalLayerSettings::ShadowScale ) )
4521  {
4522  shadow.setScale( ddValues.value( QgsPalLayerSettings::ShadowScale ).toInt() );
4523  changed = true;
4524  }
4525 
4526 
4527  if ( ddValues.contains( QgsPalLayerSettings::ShadowBlendMode ) )
4528  {
4529  shadow.setBlendMode( static_cast< QPainter::CompositionMode >( ddValues.value( QgsPalLayerSettings::ShadowBlendMode ).toInt() ) );
4530  changed = true;
4531  }
4532 
4533  if ( changed )
4534  {
4535  QgsTextFormat format = tmpLyr.format();
4536  format.setShadow( shadow );
4537  tmpLyr.setFormat( format );
4538  }
4539 }
4540 
4541 void QgsPalLabeling::drawLabelCandidateRect( pal::LabelPosition *lp, QPainter *painter, const QgsMapToPixel *xform, QList<QgsLabelCandidate> *candidates )
4542 {
4543  QgsPointXY outPt = xform->transform( lp->getX(), lp->getY() );
4544 
4545  painter->save();
4546 
4547 #if 0 // TODO: generalize some of this
4548  double w = lp->getWidth();
4549  double h = lp->getHeight();
4550  double cx = lp->getX() + w / 2.0;
4551  double cy = lp->getY() + h / 2.0;
4552  double scale = 1.0 / xform->mapUnitsPerPixel();
4553  double rotation = xform->mapRotation();
4554  double sw = w * scale;
4555  double sh = h * scale;
4556  QRectF rect( -sw / 2, -sh / 2, sw, sh );
4557 
4558  painter->translate( xform->transform( QPointF( cx, cy ) ).toQPointF() );
4559  if ( rotation )
4560  {
4561  // Only if not horizontal
4562  if ( lp->getFeaturePart()->getLayer()->getArrangement() != P_POINT &&
4563  lp->getFeaturePart()->getLayer()->getArrangement() != P_POINT_OVER &&
4564  lp->getFeaturePart()->getLayer()->getArrangement() != P_HORIZ )
4565  {
4566  painter->rotate( rotation );
4567  }
4568  }
4569  painter->translate( rect.bottomLeft() );
4570  painter->rotate( -lp->getAlpha() * 180 / M_PI );
4571  painter->translate( -rect.bottomLeft() );
4572 #else
4573  QgsPointXY outPt2 = xform->transform( lp->getX() + lp->getWidth(), lp->getY() + lp->getHeight() );
4574  QRectF rect( 0, 0, outPt2.x() - outPt.x(), outPt2.y() - outPt.y() );
4575  painter->translate( QPointF( outPt.x(), outPt.y() ) );
4576  painter->rotate( -lp->getAlpha() * 180 / M_PI );
4577 #endif
4578 
4579  if ( lp->conflictsWithObstacle() )
4580  {
4581  painter->setPen( QColor( 255, 0, 0, 64 ) );
4582  }
4583  else
4584  {
4585  painter->setPen( QColor( 0, 0, 0, 64 ) );
4586  }
4587  painter->drawRect( rect );
4588  painter->restore();
4589 
4590  // save the rect
4591  rect.moveTo( outPt.x(), outPt.y() );
4592  if ( candidates )
4593  candidates->append( QgsLabelCandidate( rect, lp->cost() * 1000 ) );
4594 
4595  // show all parts of the multipart label
4596  if ( lp->nextPart() )
4597  drawLabelCandidateRect( lp->nextPart(), painter, xform, candidates );
4598 }
@ Success
Operation succeeded.
@ Warning
Warning message.
Definition: qgis.h:109
Capitalization
String capitalization options.
Definition: qgis.h:1236
@ AllSmallCaps
Force all characters to small caps (since QGIS 3.24)
@ MixedCase
Mixed case, ie no change.
@ AllLowercase
Convert all characters to lowercase.
@ TitleCase
Simple title case conversion - does not fully grammatically parse the text and uses simple rules only...
@ SmallCaps
Mixed case small caps (since QGIS 3.24)
@ ForceFirstLetterToCapital
Convert just the first letter of each word to uppercase, leave the rest untouched.
@ AllUppercase
Convert all characters to uppercase.
UnplacedLabelVisibility
Unplaced label visibility.
Definition: qgis.h:444
@ FollowEngineSetting
Respect the label engine setting.
@ Antialiasing
Use antialiasing while drawing.
static const double UI_SCALE_FACTOR
UI scaling factor.
Definition: qgis.h:1380
virtual QgsAbstractGeometry * boundary() const =0
Returns the closure of the combinatorial boundary of the geometry (ie the topological boundary of the...
int valueAsInt(int key, const QgsExpressionContext &context, int defaultValue=0, bool *ok=nullptr) const
Calculates the current value of the property with the specified key and interprets it as an integer.
bool valueAsBool(int key, const QgsExpressionContext &context, bool defaultValue=false, bool *ok=nullptr) const
Calculates the current value of the property with the specified key and interprets it as an boolean.
double valueAsDouble(int key, const QgsExpressionContext &context, double defaultValue=0.0, bool *ok=nullptr) const
Calculates the current value of the property with the specified key and interprets it as a double.
virtual bool readXml(const QDomElement &collectionElem, const QgsPropertiesDefinition &definitions)
Reads property collection state from an XML element.
QString valueAsString(int key, const QgsExpressionContext &context, const QString &defaultString=QString(), bool *ok=nullptr) const
Calculates the current value of the property with the specified key and interprets it as a string.
virtual bool writeXml(QDomElement &collectionElem, const QgsPropertiesDefinition &definitions) const
Writes the current state of the property collection into an XML element.
static QIcon getThemeIcon(const QString &name, const QColor &fillColor=QColor(), const QColor &strokeColor=QColor())
Helper to get a theme icon.
static QgsCalloutRegistry * calloutRegistry()
Returns the application's callout registry, used for managing callout types.
Registry of available callout classes.
static QgsCallout * defaultCallout()
Create a new instance of a callout with default settings.
Contains additional contextual information about the context in which a callout is being rendered.
Definition: qgscallout.h:246
Abstract base class for callout renderers.
Definition: qgscallout.h:53
virtual void stopRender(QgsRenderContext &context)
Finalises the callout after a set of rendering operations on the specified render context.
Definition: qgscallout.cpp:127
void render(QgsRenderContext &context, const QRectF &rect, const double angle, const QgsGeometry &anchor, QgsCalloutContext &calloutContext)
Renders the callout onto the specified render context.
Definition: qgscallout.cpp:148
virtual void startRender(QgsRenderContext &context)
Prepares the callout for rendering on the specified render context.
Definition: qgscallout.cpp:123
bool enabled() const
Returns true if the the callout is enabled.
Definition: qgscallout.h:319
This class represents a coordinate reference system (CRS).
QString userFriendlyIdentifier(IdentifierType type=MediumString) const
Returns a user friendly identifier for the CRS.
Class for doing transforms between two map coordinate systems.
QgsCoordinateReferenceSystem sourceCrs() const
Returns the source coordinate reference system, which the transform will transform coordinates from.
bool isShortCircuited() const
Returns true if the transform short circuits because the source and destination are equivalent.
bool isValid() const
Returns true if the coordinate transform is valid, ie both the source and destination CRS have been s...
Custom exception class for Coordinate Reference System related exceptions.
Definition: qgsexception.h:66
Curve polygon geometry type.
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
void setOriginalValueVariable(const QVariant &value)
Sets the original value variable value for the context.
Class for parsing and evaluation of expressions (formerly called "search strings").
bool prepare(const QgsExpressionContext *context)
Gets the expression ready for evaluation - find out column indexes.
bool hasParserError() const
Returns true if an error occurred when parsing the input expression.
QString evalErrorString() const
Returns evaluation error.
QString parserErrorString() const
Returns parser error.
QSet< QString > referencedColumns() const
Gets list of columns referenced by the expression.
bool hasEvalError() const
Returns true if an error occurred when evaluating last input.
QVariant evaluate()
Evaluate the feature and return the result.
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
Definition: qgsfeature.h:56
QgsGeometry geometry
Definition: qgsfeature.h:67
QVariant attribute(const QString &name) const
Lookup attribute value by attribute name.
Definition: qgsfeature.cpp:320
void setGeometry(const QgsGeometry &geometry)
Set the feature's geometry.
Definition: qgsfeature.cpp:163
Q_GADGET QgsFeatureId id
Definition: qgsfeature.h:64
Container of fields for a vector layer.
Definition: qgsfields.h:45
int lookupField(const QString &fieldName) const
Looks up field's index from the field name.
Definition: qgsfields.cpp:349
static bool fontFamilyOnSystem(const QString &family)
Check whether font family is on system in a quick manner, which does not compare [foundry].
static bool updateFontViaStyle(QFont &f, const QString &fontstyle, bool fallback=false)
Updates font with named style and retain all font properties.
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:125
QgsGeometry clipped(const QgsRectangle &rectangle)
Clips the geometry using the specified rectangle.
const QgsAbstractGeometry * constGet() const SIP_HOLDGIL
Returns a non-modifiable (const) reference to the underlying abstract geometry primitive.
double length() const
Returns the planar, 2-dimensional length of geometry.
QgsAbstractGeometry::const_part_iterator const_parts_begin() const
Returns STL-style const iterator pointing to the first part of the geometry.
static QgsGeometry collectGeometry(const QVector< QgsGeometry > &geometries)
Creates a new multipart geometry from a list of QgsGeometry objects.
Qgis::GeometryOperationResult transform(const QgsCoordinateTransform &ct, Qgis::TransformDirection direction=Qgis::TransformDirection::Forward, bool transformZ=false) SIP_THROW(QgsCsException)
Transforms this geometry as described by the coordinate transform ct.
bool isAxisParallelRectangle(double maximumDeviation, bool simpleRectanglesOnly=false) const
Returns true if the geometry is a polygon that is almost an axis-parallel rectangle.
Q_GADGET bool isNull
Definition: qgsgeometry.h:127
QgsAbstractGeometry * get()
Returns a modifiable (non-const) reference to the underlying abstract geometry primitive.
static QgsGeometry fromRect(const QgsRectangle &rect) SIP_HOLDGIL
Creates a new geometry from a QgsRectangle.
bool isMultipart() const SIP_HOLDGIL
Returns true if WKB of the geometry is of WKBMulti* type.
QgsGeometry intersection(const QgsGeometry &geometry) const
Returns a geometry representing the points shared by this geometry and other.
bool contains(const QgsPointXY *p) const
Returns true if the geometry contains the point p.
QgsPointXY asPoint() const
Returns the contents of the geometry as a 2-dimensional point.
void filterVertices(const std::function< bool(const QgsPoint &) > &filter)
Filters the vertices from the geometry in place, removing any which do not return true for the filter...
bool isGeosValid(Qgis::GeometryValidityFlags flags=Qgis::GeometryValidityFlags()) const
Checks validity of the geometry using GEOS.
static QgsGeometry fromPointXY(const QgsPointXY &point) SIP_HOLDGIL
Creates a new geometry from a QgsPointXY object.
QgsWkbTypes::GeometryType type
Definition: qgsgeometry.h:128
double area() const
Returns the planar, 2-dimensional area of the geometry.
QgsGeometry centroid() const
Returns the center of mass of a geometry.
QgsGeometry makeValid() const
Attempts to make an invalid geometry valid without losing vertices.
QString lastError() const SIP_HOLDGIL
Returns an error string referring to the last error encountered either when this geometry was created...
QgsGeometry mergeLines() const
Merges any connected lines in a LineString/MultiLineString geometry and converts them to single line ...
QgsGeometry buffer(double distance, int segments) const
Returns a buffer region around this geometry having the given width and with a specified number of se...
bool isEmpty() const
Returns true if the geometry is empty (eg a linestring with no vertices, or a collection with no geom...
QgsAbstractGeometry::const_part_iterator const_parts_end() const
Returns STL-style iterator pointing to the imaginary part after the last part of the geometry.
QgsGeometry simplify(double tolerance) const
Returns a simplified version of this geometry using a specified tolerance value.
QgsRectangle boundingBox() const
Returns the bounding box of the geometry.
Qgis::GeometryOperationResult rotate(double rotation, const QgsPointXY &center)
Rotate this geometry around the Z axis.
bool convertGeometryCollectionToSubclass(QgsWkbTypes::GeometryType geomType)
Converts geometry collection to a the desired geometry type subclass (multi-point,...
QString asWkt(int precision=17) const
Exports the geometry to WKT.
static geos::unique_ptr asGeos(const QgsGeometry &geometry, double precision=0)
Returns a geos geometry - caller takes ownership of the object (should be deleted with GEOSGeom_destr...
Definition: qgsgeos.cpp:181
Represents a label candidate.
Contains settings related to how the label engine places and formats labels for line features (or pol...
AnchorType
Line anchor types.
void setPlacementFlags(QgsLabeling::LinePlacementFlags flags)
Returns the line placement flags, which dictate how line labels can be placed above or below the line...
QgsLabeling::LinePlacementFlags placementFlags() const
Returns the line placement flags, which dictate how line labels can be placed above or below the line...
bool reverseDirectionSymbol() const
Returns true if direction symbols should be reversed.
void setLineAnchorPercent(double percent)
Sets the percent along the line at which labels should be placed.
DirectionSymbolPlacement directionSymbolPlacement() const
Returns the placement for direction symbols.
AnchorClipping
Clipping behavior for line anchor calculation.
@ UseEntireLine
Entire original feature line geometry is used when calculating the line anchor for labels.
@ UseVisiblePartsOfLine
Only visible parts of lines are considered when calculating the line anchor for labels.
void setDirectionSymbolPlacement(DirectionSymbolPlacement placement)
Sets the placement for direction symbols.
AnchorType anchorType() const
Returns the line anchor type, which dictates how the lineAnchorPercent() setting is handled.
QString leftDirectionSymbol() const
Returns the string to use for left direction arrows.
void setLeftDirectionSymbol(const QString &symbol)
Sets the string to use for left direction arrows.
QgsMapUnitScale overrunDistanceMapUnitScale() const
Returns the map unit scale for label overrun distance.
double overrunDistance() const
Returns the distance which labels are allowed to overrun past the start or end of line features.
QgsUnitTypes::RenderUnit overrunDistanceUnit() const
Returns the units for label overrun distance.
void setMergeLines(bool merge)
Sets whether connected line features with identical label text should be merged prior to generating l...
DirectionSymbolPlacement
Placement options for direction symbols.
@ SymbolLeftRight
Place direction symbols on left/right of label.
@ SymbolAbove
Place direction symbols on above label.
@ SymbolBelow
Place direction symbols on below label.
void setRightDirectionSymbol(const QString &symbol)
Sets the string to use for right direction arrows.
QString rightDirectionSymbol() const
Returns the string to use for right direction arrows.
void setAnchorClipping(AnchorClipping clipping)
Sets the line anchor clipping mode, which dictates how line strings are clipped before calculating th...
void setOverrunDistanceMapUnitScale(const QgsMapUnitScale &scale)
Sets the map unit scale for label overrun distance.
bool addDirectionSymbol() const
Returns true if '<' or '>' (or custom strings set via leftDirectionSymbol and rightDirectionSymbol) w...
double lineAnchorPercent() const
Returns the percent along the line at which labels should be placed.
bool mergeLines() const
Returns true if connected line features with identical label text should be merged prior to generatin...
void setAnchorType(AnchorType type)
Sets the line anchor type, which dictates how the lineAnchorPercent() setting is handled.
void setOverrunDistance(double distance)
Sets the distance which labels are allowed to overrun past the start or end of line features.
AnchorClipping anchorClipping() const
Returns the line anchor clipping mode, which dictates how line strings are clipped before calculating...
void setOverrunDistanceUnit(const QgsUnitTypes::RenderUnit &unit)
Sets the unit for label overrun distance.
void setReverseDirectionSymbol(bool reversed)
Sets whether the direction symbols should be reversed.
void setAddDirectionSymbol(bool enabled)
Sets whether '<' or '>' (or custom strings set via leftDirectionSymbol and rightDirectionSymbol) will...
void updateDataDefinedProperties(const QgsPropertyCollection &properties, QgsExpressionContext &context)
Updates the thinning settings to respect any data defined properties set within the specified propert...
Contains settings related to how the label engine treats features as obstacles.
double factor() const
Returns the obstacle factor, where 1.0 = default, < 1.0 more likely to be covered by labels,...
void setType(ObstacleType type)
Controls how features act as obstacles for labels.
ObstacleType type() const
Returns how features act as obstacles for labels.
void setIsObstacle(bool isObstacle)
Sets whether features are obstacles to labels of other layers.
void setFactor(double factor)
Sets the obstacle factor, where 1.0 = default, < 1.0 more likely to be covered by labels,...
ObstacleType
Valid obstacle types, which affect how features within the layer will act as obstacles for labels.
void updateDataDefinedProperties(const QgsPropertyCollection &properties, QgsExpressionContext &context)
Updates the obstacle settings to respect any data defined properties set within the specified propert...
void setObstacleGeometry(const QgsGeometry &obstacleGeom)
Sets the label's obstacle geometry, if different to the feature geometry.
bool isObstacle() const
Returns true if the features are obstacles to labels of other layers.
Contains settings related to how the label engine removes candidate label positions and reduces the n...
void setMaximumNumberLabels(int number)
Sets the maximum number of labels which should be drawn for this layer.
double minimumFeatureSize() const
Returns the minimum feature size (in millimeters) for a feature to be labelled.
int maximumNumberLabels() const
Returns the maximum number of labels which should be drawn for this layer.
void updateDataDefinedProperties(const QgsPropertyCollection &properties, QgsExpressionContext &context)
Updates the thinning settings to respect any data defined properties set within the specified propert...
void setLimitNumberLabelsEnabled(bool enabled)
Sets whether the the number of labels drawn for the layer should be limited.
bool limitNumberOfLabelsEnabled() const
Returns true if the number of labels drawn for the layer should be limited.
void setMinimumFeatureSize(double size)
Sets the minimum feature size (in millimeters) for a feature to be labelled.
static QVector< QgsPalLayerSettings::PredefinedPointPosition > decodePredefinedPositionOrder(const QString &positionString)
Decodes a string to an ordered list of predefined point label positions.
static QString encodePredefinedPositionOrder(const QVector< QgsPalLayerSettings::PredefinedPointPosition > &positions)
Encodes an ordered list of predefined point label positions to a string.
Line string geometry type, with support for z-dimension and m-values.
Definition: qgslinestring.h:44
Base class for all map layer types.
Definition: qgsmaplayer.h:73
Q_INVOKABLE QVariant customProperty(const QString &value, const QVariant &defaultValue=QVariant()) const
Read a custom property from layer.
void removeCustomProperty(const QString &key)
Remove a custom property from layer.
QgsMapLayerType type
Definition: qgsmaplayer.h:80
T customEnumProperty(const QString &key, const T &defaultValue)
Returns the property value for a property based on an enum.
Definition: qgsmaplayer.h:736
The QgsMapSettings class contains configuration for rendering of the map.
double extentBuffer() const
Returns the buffer in map units to use around the visible extent for rendering symbols whose correspo...
const QgsMapToPixel & mapToPixel() const
QgsRectangle visibleExtent() const
Returns the actual extent derived from requested extent that takes output image size into account.
double rotation() const
Returns the rotation of the resulting map image, in degrees clockwise.
QgsCoordinateReferenceSystem destinationCrs() const
Returns the destination coordinate reference system for the map render.
QgsCoordinateTransformContext transformContext() const
Returns the coordinate transform context, which stores various information regarding which datum tran...
Implementation of GeometrySimplifier using the "MapToPixel" algorithm.
SimplifyAlgorithm
Types of simplification algorithms that can be used.
@ SimplifyEnvelope
The geometries can be fully simplified by its BoundingBox.
QgsGeometry simplify(const QgsGeometry &geometry) const override
Returns a simplified version the specified geometry.
Perform transforms between map coordinates and device coordinates.
Definition: qgsmaptopixel.h:39
double mapUnitsPerPixel() const
Returns the current map units per pixel.
QgsPointXY toMapCoordinates(int x, int y) const
Transforms device coordinates to map (world) coordinates.
QgsPointXY transform(const QgsPointXY &p) const
Transforms a point p from map (world) coordinates to device coordinates.
Definition: qgsmaptopixel.h:90
double mapRotation() const
Returns the current map rotation in degrees (clockwise).
void setParameters(double mapUnitsPerPixel, double centerX, double centerY, int widthPixels, int heightPixels, double rotation)
Sets parameters for use in transforming coordinates.
double maxScale
The maximum scale, or 0.0 if unset.
double minScale
The minimum scale, or 0.0 if unset.
The QgsMargins class defines the four margins of a rectangle.
Definition: qgsmargins.h:38
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::MessageLevel::Warning, bool notifyUser=true)
Adds a message to the log instance (and creates it if necessary).
Multi surface geometry collection.
static void drawLabelCandidateRect(pal::LabelPosition *lp, QPainter *painter, const QgsMapToPixel *xform, QList< QgsLabelCandidate > *candidates=nullptr)
static QStringList splitToLines(const QString &text, const QString &wrapCharacter, int autoWrapLength=0, bool useMaxLineLengthWhenAutoWrapping=true)
Splits a text string to a list of separate lines, using a specified wrap character (wrapCharacter).
static QgsGeometry prepareGeometry(const QgsGeometry &geometry, QgsRenderContext &context, const QgsCoordinateTransform &ct, const QgsGeometry &clipGeometry=QgsGeometry(), bool mergeLines=false)
Prepares a geometry for registration with PAL.
static bool geometryRequiresPreparation(const QgsGeometry &geometry, QgsRenderContext &context, const QgsCoordinateTransform &ct, const QgsGeometry &clipGeometry=QgsGeometry(), bool mergeLines=false)
Checks whether a geometry requires preparation before registration with PAL.
static bool staticWillUseLayer(const QgsMapLayer *layer)
Called to find out whether a specified layer is used for labeling.
static QStringList splitToGraphemes(const QString &text)
Splits a text string to a list of graphemes, which are the smallest allowable character divisions in ...
Contains settings for how a map layer will be labeled.
bool fitInPolygonOnly
true if only labels which completely fit within a polygon are allowed.
double yOffset
Vertical offset of label.
QgsMapUnitScale labelOffsetMapUnitScale
Map unit scale for label offset.
int fontMaxPixelSize
Maximum pixel size for showing rendered map unit labels (1 - 10000).
std::unique_ptr< QgsLabelFeature > registerFeatureWithDetails(const QgsFeature &feature, QgsRenderContext &context, QgsGeometry obstacleGeometry=QgsGeometry(), const QgsSymbol *symbol=nullptr)
Registers a feature for labeling.
double maxCurvedCharAngleIn
Maximum angle between inside curved label characters (valid range 20.0 to 60.0).
void setFormat(const QgsTextFormat &format)
Sets the label text formatting settings, e.g., font settings, buffer settings, etc.
double zIndex
Z-Index of label, where labels with a higher z-index are rendered on top of labels with a lower z-ind...
const QgsMapToPixel * xform
void startRender(QgsRenderContext &context)
Prepares the label settings for rendering.
bool displayAll
If true, all features will be labelled even when overlaps occur.
QString wrapChar
Wrapping character string.
QSet< QString > referencedFields(const QgsRenderContext &context) const
Returns all field names referenced by the configuration (e.g.
double xOffset
Horizontal offset of label.
QgsPropertyCollection & dataDefinedProperties()
Returns a reference to the label's property collection, used for data defined overrides.
QgsCoordinateTransform ct
bool drawLabels
Whether to draw labels for this layer.
bool fontLimitPixelSize
true if label sizes should be limited by pixel size.
QgsExpression * getLabelExpression()
Returns the QgsExpression for this label settings.
QString legendString() const
legendString
QuadrantPosition quadOffset
Sets the quadrant in which to offset labels from feature.
double minimumScale
The minimum map scale (i.e.
void calculateLabelSize(const QFontMetricsF *fm, const QString &text, double &labelX, double &labelY, const QgsFeature *f=nullptr, QgsRenderContext *context=nullptr, double *rotatedLabelX=nullptr, double *rotatedLabelY=nullptr, QgsTextDocument *document=nullptr)
Calculates the space required to render the provided text in map units.
void registerFeature(const QgsFeature &f, QgsRenderContext &context)
Registers a feature for labeling.
@ Upright
Upside-down labels (90 <= angle < 270) are shown upright.
QgsPalLayerSettings & operator=(const QgsPalLayerSettings &s)
copy operator - only copies the permanent members
QgsWkbTypes::GeometryType geometryGeneratorType
The type of the result geometry of the geometry generator.
void readXml(const QDomElement &elem, const QgsReadWriteContext &context)
Read settings from a DOM element.
QgsUnitTypes::RenderUnit offsetUnits
Units for offsets of label.
bool scaleVisibility
Set to true to limit label visibility to a range of scales.
QgsCallout * callout() const
Returns the label callout renderer, responsible for drawing label callouts.
double repeatDistance
Distance for repeating labels for a single feature.
QgsUnitTypes::AngleUnit rotationUnit() const
Unit for rotation of labels.
bool geometryGeneratorEnabled
Defines if the geometry generator is enabled or not. If disabled, the standard geometry will be taken...
Placement
Placement modes which determine how label candidates are generated for a feature.
@ PerimeterCurved
Arranges candidates following the curvature of a polygon's boundary. Applies to polygon layers only.
@ Curved
Arranges candidates following the curvature of a line feature. Applies to line layers only.
@ Horizontal
Arranges horizontal candidates scattered throughout a polygon feature. Applies to polygon layers only...
@ Free
Arranges candidates scattered throughout a polygon feature. Candidates are rotated to respect the pol...
@ OverPoint
Arranges candidates over a point (or centroid of a polygon), or at a preset offset from the point....
@ AroundPoint
Arranges candidates in a circle around a point (or centroid of a polygon). Applies to point or polygo...
@ Line
Arranges candidates parallel to a generalised line representing the feature or parallel to a polygon'...
@ OrderedPositionsAroundPoint
Candidates are placed in predefined positions around a point. Preference is given to positions with g...
@ OutsidePolygons
Candidates are placed outside of polygon boundaries. Applies to polygon layers only....
QVector< PredefinedPointPosition > predefinedPositionOrder
Ordered list of predefined label positions for points.
bool centroidInside
true if centroid positioned labels must be placed inside their corresponding feature polygon,...
int priority
Label priority.
QDomElement writeXml(QDomDocument &doc, const QgsReadWriteContext &context) const
Write settings into a DOM element.
bool labelPerPart
true if every part of a multi-part feature should be labeled.
@ MultiJustify
Justified.
QgsUnitTypes::RenderUnit distUnits
Units the distance from feature to the label.
@ BottomSlightlyRight
Label below point, slightly right of center.
@ MiddleLeft
Label on left of point.
@ TopSlightlyRight
Label on top of point, slightly right of center.
@ MiddleRight
Label on right of point.
@ BottomRight
Label on bottom right of point.
@ BottomLeft
Label on bottom-left of point.
@ TopRight
Label on top-right of point.
@ TopLeft
Label on top-left of point.
QgsUnitTypes::RenderUnit repeatDistanceUnit
Units for repeating labels for a single feature.
OffsetType offsetType
Offset type for layer (only applies in certain placement modes)
MultiLineAlign multilineAlign
Horizontal alignment of multi-line labels.
int fontMinPixelSize
Minimum pixel size for showing rendered map unit labels (1 - 1000).
double angleOffset
Label rotation, in degrees clockwise.
double maxCurvedCharAngleOut
Maximum angle between outside curved label characters (valid range -20.0 to -95.0)
Property
Data definable properties.
@ MaskEnabled
Whether the mask is enabled.
@ LabelRotation
Label rotation.
@ PositionY
Y-coordinate data defined label position.
@ OverrunDistance
Distance which labels can extend past either end of linear features.
@ Strikeout
Use strikeout.
@ FontStyle
Font style name.
@ PositionX
X-coordinate data defined label position.
@ CalloutDraw
Show callout.
@ Underline
Use underline.
@ FontLetterSpacing
Letter spacing.
@ MaskJoinStyle
Mask join style.
@ PositionPoint
Point-coordinate data defined label position.
@ Bold
Use bold style.
@ MaxScale
Max scale (deprecated, for old project compatibility only)
@ Hali
Horizontal alignment for data defined label position (Left, Center, Right)
@ FontStretchFactor
Font stretch factor, since QGIS 3.24.
@ FontTransp
Text transparency (deprecated)
@ MaskBufferUnit
Mask buffer size unit.
@ LabelAllParts
Whether all parts of multi-part features should be labeled.
@ ShadowOpacity
Shadow opacity.
@ BufferOpacity
Buffer opacity.
@ LineAnchorPercent
Portion along line at which labels should be anchored (since QGIS 3.16)
@ ShapeOpacity
Shape opacity.
@ FontSizeUnit
Font size units.
@ Rotation
Label rotation (deprecated, for old project compatibility only)
@ Italic
Use italic style.
@ ShapeTransparency
Shape transparency (deprecated)
@ ShadowTransparency
Shadow transparency (deprecated)
@ MinScale
Min scale (deprecated, for old project compatibility only)
@ FontWordSpacing
Word spacing.
@ MaskBufferSize
Mask buffer size.
@ LineAnchorClipping
Clipping mode for line anchor calculation (since QGIS 3.20)
@ MinimumScale
Minimum map scale (ie most "zoomed out")
@ FontBlendMode
Text blend mode.
@ MaximumScale
Maximum map scale (ie most "zoomed in")
@ MaskOpacity
Mask opacity.
@ Family
Font family.
@ BufferTransp
Buffer transparency (deprecated)
@ PolygonLabelOutside
Whether labels outside a polygon feature are permitted, or should be forced (since QGIS 3....
@ FontCase
Label text case.
@ LinePlacementOptions
Line placement flags.
@ Vali
Vertical alignment for data defined label position (Bottom, Base, Half, Cap, Top)
@ FontOpacity
Text opacity.
const QgsLabelLineSettings & lineSettings() const
Returns the label line settings, which contain settings related to how the label engine places and fo...
void setRotationUnit(QgsUnitTypes::AngleUnit angleUnit)
Set unit for rotation of labels.
OffsetType
Behavior modifier for label offset and distance, only applies in some label placement modes.
@ FromPoint
Offset distance applies from point geometry.
@ FromSymbolBounds
Offset distance applies from rendered symbol bounds.
bool isExpression
true if this label is made from a expression string, e.g., FieldName || 'mm'
bool preserveRotation
True if label rotation should be preserved during label pin/unpin operations.
static QPixmap labelSettingsPreviewPixmap(const QgsPalLayerSettings &settings, QSize size, const QString &previewText=QString(), int padding=0)
Returns a pixmap preview for label settings.
bool plusSign
Whether '+' signs should be prepended to positive numeric labels.
bool prepare(QgsRenderContext &context, QSet< QString > &attributeNames, const QgsFields &fields, const QgsMapSettings &mapSettings, const QgsCoordinateReferenceSystem &crs)
Prepare for registration of features.
QString geometryGenerator
The geometry generator expression. Null if disabled.
QgsMapUnitScale distMapUnitScale
Map unit scale for label feature distance.
QgsStringReplacementCollection substitutions
Substitution collection for automatic text substitution with labels.
Q_DECL_DEPRECATED QColor previewBkgrdColor
const QgsFeature * mCurFeat
int decimals
Number of decimal places to show for numeric labels.
double dist
Distance from feature to the label.
QgsMapUnitScale repeatDistanceMapUnitScale
Map unit scale for repeating labels for a single feature.
bool centroidWhole
true if feature centroid should be calculated from the whole feature, or false if only the visible pa...
const QgsTextFormat & format() const
Returns the label text formatting settings, e.g., font settings, buffer settings, etc.
UpsideDownLabels upsidedownLabels
Controls whether upside down labels are displayed and how they are handled.
QString fieldName
Name of field (or an expression) to use for label text.
bool formatNumbers
Set to true to format numeric label text as numbers (e.g.
bool containsAdvancedEffects() const
Returns true if any component of the label settings requires advanced effects such as blend modes,...
QgsWkbTypes::GeometryType layerType
Geometry type of layers associated with these settings.
void setCallout(QgsCallout *callout)
Sets the label callout renderer, responsible for drawing label callouts.
double maximumScale
The maximum map scale (i.e.
int autoWrapLength
If non-zero, indicates that label text should be automatically wrapped to (ideally) the specified num...
Qgis::UnplacedLabelVisibility unplacedVisibility() const
Returns the layer's unplaced label visibility.
bool useMaxLineLengthForAutoWrap
If true, indicates that when auto wrapping label text the autoWrapLength length indicates the maximum...
void setUnplacedVisibility(Qgis::UnplacedLabelVisibility visibility)
Sets the layer's unplaced label visibility.
static const QgsPropertiesDefinition & propertyDefinitions()
Returns the labeling property definitions.
void stopRender(QgsRenderContext &context)
Finalises the label settings after use.
bool useSubstitutions
True if substitutions should be applied.
A class to represent a 2D point.
Definition: qgspointxy.h:59
double y
Definition: qgspointxy.h:63
Q_GADGET double x
Definition: qgspointxy.h:62
double distance(double x, double y) const SIP_HOLDGIL
Returns the distance between this point and a specified x, y coordinate.
Definition: qgspointxy.h:211
Point geometry type, with support for z-dimension and m-values.
Definition: qgspoint.h:49
bool isEmpty() const override SIP_HOLDGIL
Returns true if the geometry is empty.
Definition: qgspoint.cpp:767
Q_GADGET double x
Definition: qgspoint.h:52
double y
Definition: qgspoint.h:53
QgsProperty property(int key) const override
Returns a matching property from the collection, if one exists.
QVariant value(int key, const QgsExpressionContext &context, const QVariant &defaultValue=QVariant()) const override
Returns the calculated value of the property with the specified key from within the collection.
void setProperty(int key, const QgsProperty &property)
Adds a property to the collection and takes ownership of it.
QSet< QString > referencedFields(const QgsExpressionContext &context=QgsExpressionContext(), bool ignoreContext=false) const override
Returns the set of any fields referenced by the active properties from the collection.
void clear() override
Removes all properties from the collection.
bool isActive(int key) const override
Returns true if the collection contains an active property with the specified key.
bool hasActiveProperties() const override
Returns true if the collection has any active properties, or false if all properties within the colle...
bool prepare(const QgsExpressionContext &context=QgsExpressionContext()) const override
Prepares the collection against a specified expression context.
Definition for a property.
Definition: qgsproperty.h:47
@ Double
Double value (including negative values)
Definition: qgsproperty.h:57
@ Double0To1
Double value between 0-1 (inclusive)
Definition: qgsproperty.h:59
@ StrokeWidth
Line stroke width.
Definition: qgsproperty.h:72
@ String
Any string value.
Definition: qgsproperty.h:61
@ BlendMode
Blend mode.
Definition: qgsproperty.h:67
@ Boolean
Boolean value.
Definition: qgsproperty.h:53
@ RenderUnits
Render units (eg mm/pixels/map units)
Definition: qgsproperty.h:63
@ PenJoinStyle
Pen join style.
Definition: qgsproperty.h:66
@ SvgPath
Path to an SVG file.
Definition: qgsproperty.h:77
@ IntegerPositiveGreaterZero
Non-zero positive integer values.
Definition: qgsproperty.h:56
@ IntegerPositive
Positive integer values (including 0)
Definition: qgsproperty.h:55
@ Opacity
Opacity (0-100)
Definition: qgsproperty.h:62
@ ColorNoAlpha
Color with no alpha channel.
Definition: qgsproperty.h:65
@ Rotation
Rotation (value between 0-360 degrees)
Definition: qgsproperty.h:60
@ ColorWithAlpha
Color with alpha channel.
Definition: qgsproperty.h:64
@ DoublePositive
Positive double value (including 0)
Definition: qgsproperty.h:58
@ Size2D
2D size (width/height different)
Definition: qgsproperty.h:70
@ DataTypeString
Property requires a string value.
Definition: qgsproperty.h:92
@ DataTypeNumeric
Property requires a numeric value.
Definition: qgsproperty.h:99
A store for object properties.
Definition: qgsproperty.h:231
QString asExpression() const
Returns an expression string representing the state of the property, or an empty string if the proper...
static QgsProperty fromExpression(const QString &expression, bool isActive=true)
Returns a new ExpressionBasedProperty created from the specified expression.
static QgsProperty fromField(const QString &fieldName, bool isActive=true)
Returns a new FieldBasedProperty created from the specified field name.
void setActive(bool active)
Sets whether the property is currently active.
The class is used as a container of context for various read/write operations on other objects.
A rectangle specified with double values.
Definition: qgsrectangle.h:42
double height() const SIP_HOLDGIL
Returns the height of the rectangle.
Definition: qgsrectangle.h:230
void grow(double delta)
Grows the rectangle in place by the specified amount.
Definition: qgsrectangle.h:296
double width() const SIP_HOLDGIL
Returns the width of the rectangle.
Definition: qgsrectangle.h:223
bool contains(const QgsRectangle &rect) const SIP_HOLDGIL
Returns true when rectangle contains other rectangle.
Definition: qgsrectangle.h:363
QgsPointXY center() const SIP_HOLDGIL
Returns the center point of the rectangle.
Definition: qgsrectangle.h:251
QgsCoordinateReferenceSystem crs() const
Returns the associated coordinate reference system, or an invalid CRS if no reference system is set.
A QgsGeometry with associated coordinate reference system.
Contains information about the context of a rendering operation.
double scaleFactor() const
Returns the scaling factor for the render to convert painter units to physical sizes.
void setScaleFactor(double factor)
Sets the scaling factor for the render to convert painter units to physical sizes.
void setUseAdvancedEffects(bool enabled)
Used to enable or disable advanced effects such as blend modes.
double rendererScale() const
Returns the renderer map scale.
QgsExpressionContext & expressionContext()
Gets the expression context.
const QgsMapToPixel & mapToPixel() const
Returns the context's map to pixel transform, which transforms between map coordinates and device coo...
double convertToPainterUnits(double size, QgsUnitTypes::RenderUnit unit, const QgsMapUnitScale &scale=QgsMapUnitScale(), Qgis::RenderSubcomponentProperty property=Qgis::RenderSubcomponentProperty::Generic) const
Converts a size from the specified units to painter units (pixels).
QgsGeometry featureClipGeometry() const
Returns the geometry to use to clip features at render time.
QgsRectangle mapExtent() const
Returns the original extent of the map being rendered.
void setFlag(Qgis::RenderContextFlag flag, bool on=true)
Enable or disable a particular flag (other flags are not affected)
void setMapToPixel(const QgsMapToPixel &mtp)
Sets the context's map to pixel transform, which transforms between map coordinates and device coordi...
double convertToMapUnits(double size, QgsUnitTypes::RenderUnit unit, const QgsMapUnitScale &scale=QgsMapUnitScale()) const
Converts a size from the specified units to map units.
void setPainter(QPainter *p)
Sets the destination QPainter for the render operation.
QgsCoordinateTransform coordinateTransform() const
Returns the current coordinate transform for the context.
const QgsVectorSimplifyMethod & vectorSimplifyMethod() const
Returns the simplification settings to use when rendering vector layers.
const QgsPathResolver & pathResolver() const
Returns the path resolver for conversion between relative and absolute paths during rendering operati...
void readXml(const QDomElement &elem)
Reads the collection state from an XML element.
QString process(const QString &input) const
Processes a given input string, applying any valid replacements which should be made using QgsStringR...
void writeXml(QDomElement &elem, QDomDocument &doc) const
Writes the collection state to an XML element.
static QString capitalize(const QString &string, Qgis::Capitalization capitalization)
Converts a string by applying capitalization rules to the string.
static QString wordWrap(const QString &string, int length, bool useMaxLineLength=true, const QString &customDelimiter=QString())
Automatically wraps a string by inserting new line characters at appropriate locations in the string.
QgsTextFormat defaultTextFormat(QgsStyle::TextFormatContext context=QgsStyle::TextFormatContext::Labeling) const
Returns the default text format to use for new text based objects in the specified context.
Definition: qgsstyle.cpp:1231
static QgsStyle * defaultStyle()
Returns default application-wide style.
Definition: qgsstyle.cpp:131
@ Labeling
Text format used in labeling.
static Qt::PenJoinStyle decodePenJoinStyle(const QString &str)
static QString encodeMapUnitScale(const QgsMapUnitScale &mapUnitScale)
static QColor decodeColor(const QString &str)
static QPointF toPoint(const QVariant &value, bool *ok=nullptr)
Converts a value to a point.
static QgsMapUnitScale decodeMapUnitScale(const QString &str)
static QSizeF toSize(const QVariant &value, bool *ok=nullptr)
Converts a value to a size.
static QPainter::CompositionMode decodeBlendMode(const QString &s)
static QString encodeSize(QSizeF size)
Encodes a QSizeF to a string.
static QString svgSymbolNameToPath(const QString &name, const QgsPathResolver &pathResolver)
Determines an SVG symbol's path from its name.
static QString encodeColor(const QColor &color)
static QString encodePoint(QPointF point)
Encodes a QPointF to a string.
static QString encodePenJoinStyle(Qt::PenJoinStyle style)
Abstract base class for all rendered symbols.
Definition: qgssymbol.h:38
Container for settings relating to a text background object.
QString svgFile() const
Returns the absolute path to the background SVG file, if set.
QSizeF size() const
Returns the size of the background shape.
QSizeF radii() const
Returns the radii used for rounding the corners of shapes.
void setOpacity(double opacity)
Sets the background shape's opacity.
void setStrokeColor(const QColor &color)
Sets the color used for outlining the background shape.
Qt::PenJoinStyle joinStyle() const
Returns the join style used for drawing the background shape.
SizeType
Methods for determining the background shape size.
bool enabled() const
Returns whether the background is enabled.
void setJoinStyle(Qt::PenJoinStyle style)
Sets the join style used for drawing the background shape.
void setRadiiUnit(QgsUnitTypes::RenderUnit units)
Sets the units used for the shape's radii.
double opacity() const
Returns the background shape's opacity.
double rotation() const
Returns the rotation for the background shape, in degrees clockwise.
QColor fillColor() const
Returns the color used for filing the background shape.
void setRadii(QSizeF radii)
Sets the radii used for rounding the corners of shapes.
SizeType sizeType() const
Returns the method used to determine the size of the background shape (e.g., fixed size or buffer aro...
ShapeType type() const
Returns the type of background shape (e.g., square, ellipse, SVG).
double strokeWidth() const
Returns the width of the shape's stroke (stroke).
void setSizeType(SizeType type)
Sets the method used to determine the size of the background shape (e.g., fixed size or buffer around...
ShapeType
Background shape types.
void setFillColor(const QColor &color)
Sets the color used for filing the background shape.
void setStrokeWidthUnit(QgsUnitTypes::RenderUnit units)
Sets the units used for the shape's stroke width.
QColor strokeColor() const
Returns the color used for outlining the background shape.
void setRotationType(RotationType type)
Sets the method used for rotating the background shape.
void setBlendMode(QPainter::CompositionMode mode)
Sets the blending mode used for drawing the background shape.
QgsMapUnitScale sizeMapUnitScale() const
Returns the map unit scale object for the shape size.
void setType(ShapeType type)
Sets the type of background shape to draw (e.g., square, ellipse, SVG).
QgsUnitTypes::RenderUnit sizeUnit() const
Returns the units used for the shape's size.
RotationType
Methods for determining the rotation of the background shape.
void setEnabled(bool enabled)
Sets whether the text background will be drawn.
QgsMarkerSymbol * markerSymbol() const
Returns the marker symbol to be rendered in the background.
void setRotation(double rotation)
Sets the rotation for the background shape, in degrees clockwise.
void setOffset(QPointF offset)
Sets the offset used for drawing the background shape.
void setSize(QSizeF size)
Sets the size of the background shape.
void setSizeUnit(QgsUnitTypes::RenderUnit unit)
Sets the units used for the shape's size.
void setSvgFile(const QString &file)
Sets the path to the background SVG file.
void setStrokeWidth(double width)
Sets the width of the shape's stroke (stroke).
QPointF offset() const
Returns the offset used for drawing the background shape.
void setOffsetUnit(QgsUnitTypes::RenderUnit units)
Sets the units used for the shape's offset.
Container for settings relating to a text buffer.
void setBlendMode(QPainter::CompositionMode mode)
Sets the blending mode used for drawing the buffer.
Qt::PenJoinStyle joinStyle() const
Returns the buffer join style.
double size() const
Returns the size of the buffer.
void setColor(const QColor &color)
Sets the color for the buffer.
void setOpacity(double opacity)
Sets the buffer opacity.
QgsMapUnitScale sizeMapUnitScale() const
Returns the map unit scale object for the buffer size.
bool enabled() const
Returns whether the buffer is enabled.
double opacity() const
Returns the buffer opacity.
void setEnabled(bool enabled)
Sets whether the text buffer will be drawn.
QgsUnitTypes::RenderUnit sizeUnit() const
Returns the units for the buffer size.
QColor color() const
Returns the color of the buffer.
void setSizeUnit(QgsUnitTypes::RenderUnit unit)
Sets the units used for the buffer size.
void setJoinStyle(Qt::PenJoinStyle style)
Sets the join style used for drawing the buffer.
void setSize(double size)
Sets the size of the buffer.
Represents a document consisting of one or more QgsTextBlock objects.
void splitLines(const QString &wrapCharacter, int autoWrapLength=0, bool useMaxLineLengthWhenAutoWrapping=true)
Splits lines of text in the document to separate lines, using a specified wrap character (wrapCharact...
QStringList toPlainText() const
Returns a list of plain text lines of text representing the document.
static QgsTextDocument fromHtml(const QStringList &lines)
Constructor for QgsTextDocument consisting of a set of HTML formatted lines.
Container for all settings relating to text rendering.
Definition: qgstextformat.h:41
void setColor(const QColor &color)
Sets the color that text will be rendered in.
void setBlendMode(QPainter::CompositionMode mode)
Sets the blending mode used for drawing the text.
QgsMapUnitScale sizeMapUnitScale() const
Returns the map unit scale object for the size.
QSet< QString > referencedFields(const QgsRenderContext &context) const
Returns all field names referenced by the configuration (e.g.
double lineHeight() const
Returns the line height for text.
QgsUnitTypes::RenderUnit sizeUnit() const
Returns the units for the size of rendered text.
int stretchFactor() const
Returns the text's stretch factor.
void setOrientation(TextOrientation orientation)
Sets the orientation for the text.
void setShadow(const QgsTextShadowSettings &shadowSettings)
Sets the text's drop shadow settings.
void setMask(const QgsTextMaskSettings &maskSettings)
Sets the text's masking settings.
QFont scaledFont(const QgsRenderContext &context, double scaleFactor=1.0, bool *isZeroSize=nullptr) const
Returns a font with the size scaled to match the format's size settings (including units and map unit...
void setOpacity(double opacity)
Sets the text's opacity.
void readXml(const QDomElement &elem, const QgsReadWriteContext &context)
Read settings from a DOM element.
TextOrientation orientation() const
Returns the orientation of the text.
Qgis::Capitalization capitalization() const
Returns the text capitalization style.
QgsTextMaskSettings & mask()
Returns a reference to the masking settings.
void setBuffer(const QgsTextBufferSettings &bufferSettings)
Sets the text's buffer settings.
QgsTextBackgroundSettings & background()
Returns a reference to the text background settings.
TextOrientation
Text orientation.
Definition: qgstextformat.h:46
@ HorizontalOrientation
Vertically oriented text.
Definition: qgstextformat.h:47
@ RotationBasedOrientation
Horizontally or vertically oriented text based on rotation (only available for map labeling)
Definition: qgstextformat.h:49
@ VerticalOrientation
Horizontally oriented text.
Definition: qgstextformat.h:48
double opacity() const
Returns the text's opacity.
QString namedStyle() const
Returns the named style for the font used for rendering text (e.g., "bold").
double size() const
Returns the size for rendered text.
QgsTextShadowSettings & shadow()
Returns a reference to the text drop shadow settings.
void setBackground(const QgsTextBackgroundSettings &backgroundSettings)
Sets the text's background settings.q.
QDomElement writeXml(QDomDocument &doc, const QgsReadWriteContext &context) const
Write settings into a DOM element.
QColor color() const
Returns the color that text will be rendered in.
QFont font() const
Returns the font used for rendering text.
void readFromLayer(QgsVectorLayer *layer)
Reads settings from a layer's custom properties (for QGIS 2.x projects).
QColor previewBackgroundColor() const
Returns the background color for text previews.
bool containsAdvancedEffects() const
Returns true if any component of the font format requires advanced effects such as blend modes,...
QgsTextBufferSettings & buffer()
Returns a reference to the text buffer settings.
void setLineHeight(double height)
Sets the line height for text.
static QgsPrecalculatedTextMetrics calculateTextMetrics(const QgsMapToPixel *xform, const QFontMetricsF &fontMetrics, double letterSpacing, double wordSpacing, const QString &text=QString(), QgsTextDocument *document=nullptr)
Calculate text metrics for later retrieval via textMetrics().
Container for settings relating to a selective masking around a text.
void setEnabled(bool)
Returns whether the mask is enabled.
void setSize(double size)
Sets the size of the buffer.
double size() const
Returns the size of the buffer.
void setSizeUnit(QgsUnitTypes::RenderUnit unit)
Sets the units used for the buffer size.
void setJoinStyle(Qt::PenJoinStyle style)
Sets the join style used for drawing the buffer.
double opacity() const
Returns the mask's opacity.
bool enabled() const
Returns whether the mask is enabled.
Qt::PenJoinStyle joinStyle() const
Returns the buffer join style.
void setOpacity(double opacity)
Sets the mask's opacity.
static QgsTextBackgroundSettings::ShapeType decodeShapeType(const QString &string)
Decodes a string representation of a background shape type to a type.
static QString encodeTextOrientation(QgsTextFormat::TextOrientation orientation)
Encodes a text orientation.
static QgsTextFormat::TextOrientation decodeTextOrientation(const QString &name, bool *ok=nullptr)
Attempts to decode a string representation of a text orientation.
static QgsTextShadowSettings::ShadowPlacement decodeShadowPlacementType(const QString &string)
Decodes a string representation of a shadow placement type to a type.
static QgsTextBackgroundSettings::RotationType decodeBackgroundRotationType(const QString &string)
Decodes a string representation of a background rotation type to a type.
static QgsTextBackgroundSettings::SizeType decodeBackgroundSizeType(const QString &string)
Decodes a string representation of a background size type to a type.
static double textWidth(const QgsRenderContext &context, const QgsTextFormat &format, const QStringList &textLines, QFontMetricsF *fontMetrics=nullptr)
Returns the width of a text based on a given format.
static void drawText(const QRectF &rect, double rotation, HAlignment alignment, const QStringList &textLines, QgsRenderContext &context, const QgsTextFormat &format, bool drawAsOutlines=true, VAlignment vAlignment=AlignTop, Qgis::TextRendererFlags flags=Qgis::TextRendererFlags())
Draws text within a rectangle using the specified settings.
@ AlignCenter
Center align.
static int sizeToPixel(double size, const QgsRenderContext &c, QgsUnitTypes::RenderUnit unit, const QgsMapUnitScale &mapUnitScale=QgsMapUnitScale())
Calculates pixel size (considering output size should be in pixel or map units, scale factors and opt...
static double textHeight(const QgsRenderContext &context, const QgsTextFormat &format, const QStringList &textLines, DrawMode mode=Point, QFontMetricsF *fontMetrics=nullptr, Qgis::TextRendererFlags flags=Qgis::TextRendererFlags(), double maxLineWidth=0)
Returns the height of a text based on a given format.
@ Rect
Text within rectangle draw mode.
Container for settings relating to a text shadow.
int offsetAngle() const
Returns the angle for offsetting the position of the shadow from the text.
bool enabled() const
Returns whether the shadow is enabled.
int scale() const
Returns the scaling used for the drop shadow (in percentage of original size).
void setShadowPlacement(QgsTextShadowSettings::ShadowPlacement placement)
Sets the placement for the drop shadow.
double opacity() const
Returns the shadow's opacity.
void setBlendMode(QPainter::CompositionMode mode)
Sets the blending mode used for drawing the drop shadow.
void setColor(const QColor &color)
Sets the color for the drop shadow.
QColor color() const
Returns the color of the drop shadow.
ShadowPlacement
Placement positions for text shadow.
void setScale(int scale)
Sets the scaling used for the drop shadow (in percentage of original size).
double offsetDistance() const
Returns the distance for offsetting the position of the shadow from the text.
void setOffsetDistance(double distance)
Sets the distance for offsetting the position of the shadow from the text.
void setOpacity(double opacity)
Sets the shadow's opacity.
void setOffsetAngle(int angle)
Sets the angle for offsetting the position of the shadow from the text.
void setBlurRadiusUnit(QgsUnitTypes::RenderUnit units)
Sets the units used for the shadow's blur radius.
double blurRadius() const
Returns the blur radius for the shadow.
void setBlurRadius(double blurRadius)
Sets the blur radius for the shadow.
void setOffsetUnit(QgsUnitTypes::RenderUnit units)
Sets the units used for the shadow's offset.
void setEnabled(bool enabled)
Sets whether the text shadow will be drawn.
AngleUnit
Units of angles.
Definition: qgsunittypes.h:132
@ AngleDegrees
Degrees.
Definition: qgsunittypes.h:133
static Q_INVOKABLE QString encodeUnit(QgsUnitTypes::DistanceUnit unit)
Encodes a distance unit to a string.
static Q_INVOKABLE double fromUnitToUnitFactor(QgsUnitTypes::DistanceUnit fromUnit, QgsUnitTypes::DistanceUnit toUnit)
Returns the conversion factor between the specified distance units.
static Q_INVOKABLE QgsUnitTypes::RenderUnit decodeRenderUnit(const QString &string, bool *ok=nullptr)
Decodes a render unit from a string.
RenderUnit
Rendering size units.
Definition: qgsunittypes.h:168
@ RenderPercentage
Percentage of another measurement (e.g., canvas size, feature size)
Definition: qgsunittypes.h:172
@ RenderPoints
Points (e.g., for font sizes)
Definition: qgsunittypes.h:173
@ RenderMillimeters
Millimeters.
Definition: qgsunittypes.h:169
@ RenderMapUnits
Map units.
Definition: qgsunittypes.h:170
Represents a vector layer which manages a vector based data sets.
bool labelsEnabled() const
Returns whether the layer contains labels which are enabled and should be drawn.
Q_INVOKABLE QgsWkbTypes::GeometryType geometryType() const
Returns point, line or polygon.
bool diagramsEnabled() const
Returns whether the layer contains diagrams which are enabled and should be drawn.
This class contains information how to simplify geometries fetched from a vector layer.
double tolerance() const
Gets the tolerance of simplification in map units. Represents the maximum distance in map units betwe...
bool forceLocalOptimization() const
Gets where the simplification executes, after fetch the geometries from provider, or when supported,...
SimplifyHints simplifyHints() const
Gets the simplification hints of the vector layer managed.
SimplifyAlgorithm simplifyAlgorithm() const
Gets the local simplification algorithm of the vector layer managed.
@ NoSimplification
No simplification can be applied.
Basic labeling configuration for vector tile layers.
Implements a map layer that is dedicated to rendering of vector tiles.
QgsVectorTileLabeling * labeling() const
Returns currently assigned labeling.
GeometryType
The geometry types are used to group QgsWkbTypes::Type in a coarse way.
Definition: qgswkbtypes.h:141
LabelPosition is a candidate feature label position.
Definition: labelposition.h:56
double getAlpha() const
Returns the angle to rotate text (in rad).
double getHeight() const
double cost() const
Returns the candidate label position's geographical cost.
bool conflictsWithObstacle() const
Returns whether the position is marked as conflicting with an obstacle feature.
double getWidth() const
LabelPosition * nextPart() const
Returns the next part of this label position (i.e.
FeaturePart * getFeaturePart() const
Returns the feature corresponding to this labelposition.
double getX(int i=0) const
Returns the down-left x coordinate.
double getY(int i=0) const
Returns the down-left y coordinate.
@ PointCloudLayer
Point cloud layer. Added in QGIS 3.18.
@ MeshLayer
Mesh layer. Added in QGIS 3.2.
@ VectorLayer
Vector layer.
@ RasterLayer
Raster layer.
@ GroupLayer
Composite group layer. Added in QGIS 3.24.
@ VectorTileLayer
Vector tile layer. Added in QGIS 3.14.
@ AnnotationLayer
Contains freeform, georeferenced annotations. Added in QGIS 3.16.
@ PluginLayer
Plugin based layer.
double ANALYSIS_EXPORT angle(QgsPoint *p1, QgsPoint *p2, QgsPoint *p3, QgsPoint *p4)
Calculates the angle between two segments (in 2 dimension, z-values are ignored)
Definition: MathUtils.cpp:786
std::unique_ptr< GEOSGeometry, GeosDeleter > unique_ptr
Scoped GEOS pointer.
Definition: qgsgeos.h:79
#define str(x)
Definition: qgis.cpp:37
#define Q_NOWARN_DEPRECATED_POP
Definition: qgis.h:2065
QString qgsEnumValueToKey(const T &value)
Returns the value for the given key of an enum.
Definition: qgis.h:1737
#define Q_NOWARN_DEPRECATED_PUSH
Definition: qgis.h:2064
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:1578
T qgsEnumKeyToValue(const QString &key, const T &defaultValue, bool tryValueAsKey=true)
Returns the value corresponding to the given key of an enum.
Definition: qgis.h:1750
Q_GLOBAL_STATIC(QReadWriteLock, sDefinitionCacheLock)
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
QString updateDataDefinedString(const QString &value)
Q_GLOBAL_STATIC_WITH_ARGS(PredefinedPointPositionVector, DEFAULT_PLACEMENT_ORDER,({ QgsPalLayerSettings::TopRight, QgsPalLayerSettings::TopLeft, QgsPalLayerSettings::BottomRight, QgsPalLayerSettings::BottomLeft, QgsPalLayerSettings::MiddleRight, QgsPalLayerSettings::MiddleLeft, QgsPalLayerSettings::TopSlightlyRight, QgsPalLayerSettings::BottomSlightlyRight })) void QgsPalLayerSettings
QVector< QgsPalLayerSettings::PredefinedPointPosition > PredefinedPointPositionVector
QMap< int, QgsPropertyDefinition > QgsPropertiesDefinition
Definition of available properties.
const QgsCoordinateReferenceSystem & crs