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