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