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