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