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