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