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