QGIS API Documentation  3.4.15-Madeira (e83d02e274)
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 
23 #include <list>
24 
25 #include "pal/pal.h"
26 #include "pal/feature.h"
27 #include "pal/layer.h"
28 #include "pal/palexception.h"
29 #include "pal/problem.h"
30 #include "pal/labelposition.h"
31 
32 #include <cmath>
33 
34 #include <QApplication>
35 #include <QByteArray>
36 #include <QString>
37 #include <QFontMetrics>
38 #include <QTime>
39 #include <QPainter>
40 
41 #include "diagram/qgsdiagram.h"
42 #include "qgsdiagramrenderer.h"
43 #include "qgsfontutils.h"
44 #include "qgslabelsearchtree.h"
45 #include "qgsexpression.h"
46 #include "qgslabelingengine.h"
47 #include "qgsvectorlayerlabeling.h"
48 
49 #include "qgslogger.h"
50 #include "qgsvectorlayer.h"
51 #include "qgsvectordataprovider.h"
54 #include "qgsgeometry.h"
55 #include "qgsmarkersymbollayer.h"
56 #include "qgspainting.h"
57 #include "qgsproject.h"
58 #include "qgsproperty.h"
59 #include "qgssymbollayerutils.h"
61 #include "qgscurvepolygon.h"
62 #include "qgsgeometrycollection.h"
63 #include <QMessageBox>
64 
65 
66 using namespace pal;
67 
68 // -------------
69 
70 /* ND: Default point label position priority. These are set to match variants of the ideal placement priority described
71  in "Making Maps", Krygier & Wood (2011) (p216),
72  "Elements of Cartography", Robinson et al (1995)
73  and "Designing Better Maps", Brewer (2005) (p76)
74  Note that while they agree on positions 1-4, 5-8 are more contentious so I've selected these placements
75  based on my preferences, and to follow Krygier and Wood's placements more closer. (I'm not going to disagree
76  with Denis Wood on anything cartography related...!)
77 */
78 const QVector< QgsPalLayerSettings::PredefinedPointPosition > QgsPalLayerSettings::DEFAULT_PLACEMENT_ORDER
79 {
88 };
89 //debugging only - don't use these placements by default
90 /* << QgsPalLayerSettings::TopSlightlyLeft
91 << QgsPalLayerSettings::BottomSlightlyLeft;
92 << QgsPalLayerSettings::TopMiddle
93 << QgsPalLayerSettings::BottomMiddle;*/
94 
95 QgsPropertiesDefinition QgsPalLayerSettings::sPropertyDefinitions;
96 
97 void QgsPalLayerSettings::initPropertyDefinitions()
98 {
99  if ( !sPropertyDefinitions.isEmpty() )
100  return;
101 
102  const QString origin = QStringLiteral( "labeling" );
103 
104  sPropertyDefinitions = QgsPropertiesDefinition
105  {
106  { QgsPalLayerSettings::Size, QgsPropertyDefinition( "Size", QObject::tr( "Font size" ), QgsPropertyDefinition::DoublePositive, origin ) },
107  { QgsPalLayerSettings::Bold, QgsPropertyDefinition( "Bold", QObject::tr( "Bold style" ), QgsPropertyDefinition::Boolean, origin ) },
108  { QgsPalLayerSettings::Italic, QgsPropertyDefinition( "Italic", QObject::tr( "Italic style" ), QgsPropertyDefinition::Boolean, origin ) },
109  { QgsPalLayerSettings::Underline, QgsPropertyDefinition( "Underline", QObject::tr( "Draw underline" ), QgsPropertyDefinition::Boolean, origin ) },
110  { QgsPalLayerSettings::Color, QgsPropertyDefinition( "Color", QObject::tr( "Text color" ), QgsPropertyDefinition::ColorNoAlpha, origin ) },
111  { QgsPalLayerSettings::Strikeout, QgsPropertyDefinition( "Strikeout", QObject::tr( "Draw strikeout" ), QgsPropertyDefinition::Boolean, origin ) },
112  {
113  QgsPalLayerSettings::Family, QgsPropertyDefinition( "Family", QgsPropertyDefinition::DataTypeString, QObject::tr( "Font family" ), QObject::tr( "string " ) + QObject::tr( "[<b>family</b>|<b>family[foundry]</b>],<br>"
114  "e.g. Helvetica or Helvetica [Cronyx]" ), origin )
115  },
116  {
117  QgsPalLayerSettings::FontStyle, QgsPropertyDefinition( "FontStyle", QgsPropertyDefinition::DataTypeString, QObject::tr( "Font style" ), QObject::tr( "string " ) + QObject::tr( "[<b>font style name</b>|<b>Ignore</b>],<br>"
118  "e.g. Bold Condensed or Light Italic" ), origin )
119  },
120  { QgsPalLayerSettings::FontSizeUnit, QgsPropertyDefinition( "FontSizeUnit", QObject::tr( "Font size units" ), QgsPropertyDefinition::RenderUnits, origin ) },
121  { QgsPalLayerSettings::FontTransp, QgsPropertyDefinition( "FontTransp", QObject::tr( "Text transparency" ), QgsPropertyDefinition::Opacity, origin ) },
122  { QgsPalLayerSettings::FontOpacity, QgsPropertyDefinition( "FontOpacity", QObject::tr( "Text opacity" ), QgsPropertyDefinition::Opacity, origin ) },
123  { 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 ) },
124  { QgsPalLayerSettings::FontLetterSpacing, QgsPropertyDefinition( "FontLetterSpacing", QObject::tr( "Letter spacing" ), QgsPropertyDefinition::Double, origin ) },
125  { QgsPalLayerSettings::FontWordSpacing, QgsPropertyDefinition( "FontWordSpacing", QObject::tr( "Word spacing" ), QgsPropertyDefinition::Double, origin ) },
126  { QgsPalLayerSettings::FontBlendMode, QgsPropertyDefinition( "FontBlendMode", QObject::tr( "Text blend mode" ), QgsPropertyDefinition::BlendMode, origin ) },
127  { QgsPalLayerSettings::MultiLineWrapChar, QgsPropertyDefinition( "MultiLineWrapChar", QObject::tr( "Wrap character" ), QgsPropertyDefinition::String, origin ) },
128  { QgsPalLayerSettings::AutoWrapLength, QgsPropertyDefinition( "AutoWrapLength", QObject::tr( "Automatic word wrap line length" ), QgsPropertyDefinition::IntegerPositive, origin ) },
129  { QgsPalLayerSettings::MultiLineHeight, QgsPropertyDefinition( "MultiLineHeight", QObject::tr( "Line height" ), QgsPropertyDefinition::DoublePositive, origin ) },
130  { 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 ) },
131  { QgsPalLayerSettings::DirSymbDraw, QgsPropertyDefinition( "DirSymbDraw", QObject::tr( "Draw direction symbol" ), QgsPropertyDefinition::Boolean, origin ) },
132  { QgsPalLayerSettings::DirSymbLeft, QgsPropertyDefinition( "DirSymbLeft", QObject::tr( "Left direction symbol" ), QgsPropertyDefinition::String, origin ) },
133  { QgsPalLayerSettings::DirSymbRight, QgsPropertyDefinition( "DirSymbRight", QObject::tr( "Right direction symbol" ), QgsPropertyDefinition::String, origin ) },
134  { QgsPalLayerSettings::DirSymbPlacement, QgsPropertyDefinition( "DirSymbPlacement", QgsPropertyDefinition::DataTypeString, QObject::tr( "Direction symbol placement" ), QObject::tr( "string " ) + "[<b>LeftRight</b>|<b>Above</b>|<b>Below</b>]", origin ) },
135  { QgsPalLayerSettings::DirSymbReverse, QgsPropertyDefinition( "DirSymbReverse", QObject::tr( "Reverse direction symbol" ), QgsPropertyDefinition::Boolean, origin ) },
136  { QgsPalLayerSettings::NumFormat, QgsPropertyDefinition( "NumFormat", QObject::tr( "Format as number" ), QgsPropertyDefinition::Boolean, origin ) },
137  { QgsPalLayerSettings::NumDecimals, QgsPropertyDefinition( "NumDecimals", QObject::tr( "Number of decimal places" ), QgsPropertyDefinition::IntegerPositive, origin ) },
138  { QgsPalLayerSettings::NumPlusSign, QgsPropertyDefinition( "NumPlusSign", QObject::tr( "Draw + sign" ), QgsPropertyDefinition::Boolean, origin ) },
139  { QgsPalLayerSettings::BufferDraw, QgsPropertyDefinition( "BufferDraw", QObject::tr( "Draw buffer" ), QgsPropertyDefinition::Boolean, origin ) },
140  { QgsPalLayerSettings::BufferSize, QgsPropertyDefinition( "BufferSize", QObject::tr( "Symbol size" ), QgsPropertyDefinition::DoublePositive, origin ) },
141  { QgsPalLayerSettings::BufferUnit, QgsPropertyDefinition( "BufferUnit", QObject::tr( "Buffer units" ), QgsPropertyDefinition::RenderUnits, origin ) },
142  { QgsPalLayerSettings::BufferColor, QgsPropertyDefinition( "BufferColor", QObject::tr( "Buffer color" ), QgsPropertyDefinition::ColorNoAlpha, origin ) },
143  { QgsPalLayerSettings::BufferTransp, QgsPropertyDefinition( "BufferTransp", QObject::tr( "Buffer transparency" ), QgsPropertyDefinition::Opacity, origin ) },
144  { QgsPalLayerSettings::BufferOpacity, QgsPropertyDefinition( "BufferOpacity", QObject::tr( "Buffer opacity" ), QgsPropertyDefinition::Opacity, origin ) },
145  { QgsPalLayerSettings::BufferJoinStyle, QgsPropertyDefinition( "BufferJoinStyle", QObject::tr( "Buffer join style" ), QgsPropertyDefinition::PenJoinStyle, origin ) },
146  { QgsPalLayerSettings::BufferBlendMode, QgsPropertyDefinition( "BufferBlendMode", QObject::tr( "Buffer blend mode" ), QgsPropertyDefinition::BlendMode, origin ) },
147  { QgsPalLayerSettings::ShapeDraw, QgsPropertyDefinition( "ShapeDraw", QObject::tr( "Draw shape" ), QgsPropertyDefinition::Boolean, origin ) },
148  {
149  QgsPalLayerSettings::ShapeKind, QgsPropertyDefinition( "ShapeKind", QgsPropertyDefinition::DataTypeString, QObject::tr( "Shape type" ), QObject::tr( "string " ) + QStringLiteral( "[<b>Rectangle</b>|<b>Square</b>|<br>"
150  "<b>Ellipse</b>|<b>Circle</b>|<b>SVG</b>]" ), origin )
151  },
152  { QgsPalLayerSettings::ShapeSVGFile, QgsPropertyDefinition( "ShapeSVGFile", QObject::tr( "Shape SVG path" ), QgsPropertyDefinition::SvgPath, origin ) },
153  { QgsPalLayerSettings::ShapeSizeType, QgsPropertyDefinition( "ShapeSizeType", QgsPropertyDefinition::DataTypeString, QObject::tr( "Shape size type" ), QObject::tr( "string " ) + "[<b>Buffer</b>|<b>Fixed</b>]", origin ) },
154  { QgsPalLayerSettings::ShapeSizeX, QgsPropertyDefinition( "ShapeSizeX", QObject::tr( "Shape size (X)" ), QgsPropertyDefinition::Double, origin ) },
155  { QgsPalLayerSettings::ShapeSizeY, QgsPropertyDefinition( "ShapeSizeY", QObject::tr( "Shape size (Y)" ), QgsPropertyDefinition::Double, origin ) },
156  { QgsPalLayerSettings::ShapeSizeUnits, QgsPropertyDefinition( "ShapeSizeUnits", QObject::tr( "Shape size units" ), QgsPropertyDefinition::RenderUnits, origin ) },
157  { QgsPalLayerSettings::ShapeRotationType, QgsPropertyDefinition( "ShapeRotationType", QgsPropertyDefinition::DataTypeString, QObject::tr( "Shape rotation type" ), QObject::tr( "string " ) + "[<b>Sync</b>|<b>Offset</b>|<b>Fixed</b>]", origin ) },
158  { QgsPalLayerSettings::ShapeRotation, QgsPropertyDefinition( "ShapeRotation", QObject::tr( "Shape rotation" ), QgsPropertyDefinition::Rotation, origin ) },
159  { QgsPalLayerSettings::ShapeOffset, QgsPropertyDefinition( "ShapeOffset", QObject::tr( "Shape offset" ), QgsPropertyDefinition::Offset, origin ) },
160  { QgsPalLayerSettings::ShapeOffsetUnits, QgsPropertyDefinition( "ShapeOffsetUnits", QObject::tr( "Shape offset units" ), QgsPropertyDefinition::RenderUnits, origin ) },
161  { QgsPalLayerSettings::ShapeRadii, QgsPropertyDefinition( "ShapeRadii", QObject::tr( "Shape radii" ), QgsPropertyDefinition::Size2D, origin ) },
162  { QgsPalLayerSettings::ShapeRadiiUnits, QgsPropertyDefinition( "ShapeRadiiUnits", QObject::tr( "Symbol radii units" ), QgsPropertyDefinition::RenderUnits, origin ) },
163  { QgsPalLayerSettings::ShapeTransparency, QgsPropertyDefinition( "ShapeTransparency", QObject::tr( "Shape transparency" ), QgsPropertyDefinition::Opacity, origin ) },
164  { QgsPalLayerSettings::ShapeOpacity, QgsPropertyDefinition( "ShapeOpacity", QObject::tr( "Shape opacity" ), QgsPropertyDefinition::Opacity, origin ) },
165  { QgsPalLayerSettings::ShapeBlendMode, QgsPropertyDefinition( "ShapeBlendMode", QObject::tr( "Shape blend mode" ), QgsPropertyDefinition::BlendMode, origin ) },
166  { QgsPalLayerSettings::ShapeFillColor, QgsPropertyDefinition( "ShapeFillColor", QObject::tr( "Shape fill color" ), QgsPropertyDefinition::ColorWithAlpha, origin ) },
167  { QgsPalLayerSettings::ShapeStrokeColor, QgsPropertyDefinition( "ShapeBorderColor", QObject::tr( "Shape stroke color" ), QgsPropertyDefinition::ColorWithAlpha, origin ) },
168  { QgsPalLayerSettings::ShapeStrokeWidth, QgsPropertyDefinition( "ShapeBorderWidth", QObject::tr( "Shape stroke width" ), QgsPropertyDefinition::StrokeWidth, origin ) },
169  { QgsPalLayerSettings::ShapeStrokeWidthUnits, QgsPropertyDefinition( "ShapeBorderWidthUnits", QObject::tr( "Shape stroke width units" ), QgsPropertyDefinition::RenderUnits, origin ) },
170  { QgsPalLayerSettings::ShapeJoinStyle, QgsPropertyDefinition( "ShapeJoinStyle", QObject::tr( "Shape join style" ), QgsPropertyDefinition::PenJoinStyle, origin ) },
171  { QgsPalLayerSettings::ShadowDraw, QgsPropertyDefinition( "ShadowDraw", QObject::tr( "Draw shadow" ), QgsPropertyDefinition::Boolean, origin ) },
172  {
173  QgsPalLayerSettings::ShadowUnder, QgsPropertyDefinition( "ShadowUnder", QgsPropertyDefinition::DataTypeString, QObject::tr( "Symbol size" ), QObject::tr( "string " ) + QStringLiteral( "[<b>Lowest</b>|<b>Text</b>|<br>"
174  "<b>Buffer</b>|<b>Background</b>]" ), origin )
175  },
176  { QgsPalLayerSettings::ShadowOffsetAngle, QgsPropertyDefinition( "ShadowOffsetAngle", QObject::tr( "Shadow offset angle" ), QgsPropertyDefinition::Rotation, origin ) },
177  { QgsPalLayerSettings::ShadowOffsetDist, QgsPropertyDefinition( "ShadowOffsetDist", QObject::tr( "Shadow offset distance" ), QgsPropertyDefinition::DoublePositive, origin ) },
178  { QgsPalLayerSettings::ShadowOffsetUnits, QgsPropertyDefinition( "ShadowOffsetUnits", QObject::tr( "Shadow offset units" ), QgsPropertyDefinition::RenderUnits, origin ) },
179  { QgsPalLayerSettings::ShadowRadius, QgsPropertyDefinition( "ShadowRadius", QObject::tr( "Shadow blur radius" ), QgsPropertyDefinition::DoublePositive, origin ) },
180  { QgsPalLayerSettings::ShadowRadiusUnits, QgsPropertyDefinition( "ShadowRadiusUnits", QObject::tr( "Shadow blur units" ), QgsPropertyDefinition::RenderUnits, origin ) },
181  { QgsPalLayerSettings::ShadowTransparency, QgsPropertyDefinition( "ShadowTransparency", QObject::tr( "Shadow transparency" ), QgsPropertyDefinition::Opacity, origin ) },
182  { QgsPalLayerSettings::ShadowOpacity, QgsPropertyDefinition( "ShadowOpacity", QObject::tr( "Shadow opacity" ), QgsPropertyDefinition::Opacity, origin ) },
183  { QgsPalLayerSettings::ShadowScale, QgsPropertyDefinition( "ShadowScale", QObject::tr( "Shadow scale" ), QgsPropertyDefinition::IntegerPositive, origin ) },
184  { QgsPalLayerSettings::ShadowColor, QgsPropertyDefinition( "ShadowColor", QObject::tr( "Shadow color" ), QgsPropertyDefinition::ColorNoAlpha, origin ) },
185  { QgsPalLayerSettings::ShadowBlendMode, QgsPropertyDefinition( "ShadowBlendMode", QObject::tr( "Shadow blend mode" ), QgsPropertyDefinition::BlendMode, origin ) },
186 
187  { QgsPalLayerSettings::CentroidWhole, QgsPropertyDefinition( "CentroidWhole", QgsPropertyDefinition::DataTypeString, QObject::tr( "Centroid of whole shape" ), QObject::tr( "string " ) + "[<b>Visible</b>|<b>Whole</b>]", origin ) },
188  {
189  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>"
190  "<b>3</b>=Left|<b>4</b>=Over|<b>5</b>=Right|<br>"
191  "<b>6</b>=Below Left|<b>7</b>=Below|<b>8</b>=Below Right]" ), origin )
192  },
193  { QgsPalLayerSettings::OffsetXY, QgsPropertyDefinition( "OffsetXY", QObject::tr( "Offset" ), QgsPropertyDefinition::Offset, origin ) },
194  { QgsPalLayerSettings::OffsetUnits, QgsPropertyDefinition( "OffsetUnits", QObject::tr( "Offset units" ), QgsPropertyDefinition::RenderUnits, origin ) },
195  { QgsPalLayerSettings::LabelDistance, QgsPropertyDefinition( "LabelDistance", QObject::tr( "Label distance" ), QgsPropertyDefinition::Double, origin ) },
196  { QgsPalLayerSettings::DistanceUnits, QgsPropertyDefinition( "DistanceUnits", QObject::tr( "Label distance units" ), QgsPropertyDefinition::RenderUnits, origin ) },
197  { QgsPalLayerSettings::OffsetRotation, QgsPropertyDefinition( "OffsetRotation", QObject::tr( "Offset rotation" ), QgsPropertyDefinition::Rotation, origin ) },
198  { 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 ) },
199  { QgsPalLayerSettings::RepeatDistance, QgsPropertyDefinition( "RepeatDistance", QObject::tr( "Repeat distance" ), QgsPropertyDefinition::DoublePositive, origin ) },
200  { QgsPalLayerSettings::RepeatDistanceUnit, QgsPropertyDefinition( "RepeatDistanceUnit", QObject::tr( "Repeat distance unit" ), QgsPropertyDefinition::RenderUnits, origin ) },
201  { QgsPalLayerSettings::Priority, QgsPropertyDefinition( "Priority", QgsPropertyDefinition::DataTypeString, QObject::tr( "Label priority" ), QObject::tr( "double [0.0-10.0]" ), origin ) },
202  { QgsPalLayerSettings::IsObstacle, QgsPropertyDefinition( "IsObstacle", QObject::tr( "Feature is a label obstacle" ), QgsPropertyDefinition::Boolean, origin ) },
203  { QgsPalLayerSettings::ObstacleFactor, QgsPropertyDefinition( "ObstacleFactor", QgsPropertyDefinition::DataTypeNumeric, QObject::tr( "Obstacle factor" ), QObject::tr( "double [0.0-10.0]" ), origin ) },
204  {
205  QgsPalLayerSettings::PredefinedPositionOrder, QgsPropertyDefinition( "PredefinedPositionOrder", QgsPropertyDefinition::DataTypeString, QObject::tr( "Predefined position order" ), QObject::tr( "Comma separated list of placements in order of priority<br>" )
206  + QStringLiteral( "[<b>TL</b>=Top left|<b>TSL</b>=Top, slightly left|<b>T</b>=Top middle|<br>"
207  "<b>TSR</b>=Top, slightly right|<b>TR</b>=Top right|<br>"
208  "<b>L</b>=Left|<b>R</b>=Right|<br>"
209  "<b>BL</b>=Bottom left|<b>BSL</b>=Bottom, slightly left|<b>B</b>=Bottom middle|<br>"
210  "<b>BSR</b>=Bottom, slightly right|<b>BR</b>=Bottom right]" ), origin )
211  },
212  {
213  QgsPalLayerSettings::LinePlacementOptions, QgsPropertyDefinition( "LinePlacementFlags", QgsPropertyDefinition::DataTypeString, QObject::tr( "Line placement options" ), QObject::tr( "Comma separated list of placement options<br>" )
214  + QStringLiteral( "[<b>OL</b>=On line|<b>AL</b>=Above line|<b>BL</b>=Below line|<br>"
215  "<b>LO</b>=Respect line orientation]" ), origin )
216  },
217  { QgsPalLayerSettings::PositionX, QgsPropertyDefinition( "PositionX", QObject::tr( "Position (X)" ), QgsPropertyDefinition::Double, origin ) },
218  { QgsPalLayerSettings::PositionY, QgsPropertyDefinition( "PositionY", QObject::tr( "Position (Y)" ), QgsPropertyDefinition::Double, origin ) },
219  { QgsPalLayerSettings::Hali, QgsPropertyDefinition( "Hali", QgsPropertyDefinition::DataTypeString, QObject::tr( "Horizontal alignment" ), QObject::tr( "string " ) + "[<b>Left</b>|<b>Center</b>|<b>Right</b>]", origin ) },
220  {
221  QgsPalLayerSettings::Vali, QgsPropertyDefinition( "Vali", QgsPropertyDefinition::DataTypeString, QObject::tr( "Vertical alignment" ), QObject::tr( "string " ) + QStringLiteral( "[<b>Bottom</b>|<b>Base</b>|<br>"
222  "<b>Half</b>|<b>Cap</b>|<b>Top</b>]" ), origin )
223  },
224  { QgsPalLayerSettings::Rotation, QgsPropertyDefinition( "Rotation", QObject::tr( "Label rotation (deprecated)" ), QgsPropertyDefinition::Rotation, origin ) },
225  { QgsPalLayerSettings::LabelRotation, QgsPropertyDefinition( "LabelRotation", QObject::tr( "Label rotation" ), QgsPropertyDefinition::Rotation, origin ) },
226  { QgsPalLayerSettings::ScaleVisibility, QgsPropertyDefinition( "ScaleVisibility", QObject::tr( "Scale based visibility" ), QgsPropertyDefinition::Boolean, origin ) },
227  { QgsPalLayerSettings::MinScale, QgsPropertyDefinition( "MinScale", QObject::tr( "Minimum scale (denominator)" ), QgsPropertyDefinition::Double, origin ) },
228  { QgsPalLayerSettings::MaxScale, QgsPropertyDefinition( "MaxScale", QObject::tr( "Maximum scale (denominator)" ), QgsPropertyDefinition::Double, origin ) },
229  { QgsPalLayerSettings::MinimumScale, QgsPropertyDefinition( "MinimumScale", QObject::tr( "Minimum scale (denominator)" ), QgsPropertyDefinition::Double, origin ) },
230  { QgsPalLayerSettings::MaximumScale, QgsPropertyDefinition( "MaximumScale", QObject::tr( "Maximum scale (denominator)" ), QgsPropertyDefinition::Double, origin ) },
231 
232  { QgsPalLayerSettings::FontLimitPixel, QgsPropertyDefinition( "FontLimitPixel", QObject::tr( "Limit font pixel size" ), QgsPropertyDefinition::Boolean, origin ) },
233  { QgsPalLayerSettings::FontMinPixel, QgsPropertyDefinition( "FontMinPixel", QObject::tr( "Minimum pixel size" ), QgsPropertyDefinition::IntegerPositive, origin ) },
234  { QgsPalLayerSettings::FontMaxPixel, QgsPropertyDefinition( "FontMaxPixel", QObject::tr( "Maximum pixel size" ), QgsPropertyDefinition::IntegerPositive, origin ) },
235  { QgsPalLayerSettings::ZIndex, QgsPropertyDefinition( "ZIndex", QObject::tr( "Label z-index" ), QgsPropertyDefinition::Double, origin ) },
236  { QgsPalLayerSettings::Show, QgsPropertyDefinition( "Show", QObject::tr( "Show label" ), QgsPropertyDefinition::Boolean, origin ) },
237  { QgsPalLayerSettings::AlwaysShow, QgsPropertyDefinition( "AlwaysShow", QObject::tr( "Always show label" ), QgsPropertyDefinition::Boolean, origin ) },
238  };
239 }
240 
242 {
243  initPropertyDefinitions();
244 
245  drawLabels = true;
246  isExpression = false;
247  fieldIndex = 0;
248 
249  previewBkgrdColor = Qt::white;
250  useSubstitutions = false;
251 
252  // text formatting
253  multilineAlign = MultiFollowPlacement;
254  addDirectionSymbol = false;
255  leftDirectionSymbol = QStringLiteral( "<" );
256  rightDirectionSymbol = QStringLiteral( ">" );
257  reverseDirectionSymbol = false;
258  placeDirectionSymbol = SymbolLeftRight;
259  formatNumbers = false;
260  decimals = 3;
261  plusSign = false;
262 
263  // placement
264  placement = AroundPoint;
265  placementFlags = AboveLine | MapOrientation;
266  centroidWhole = false;
267  centroidInside = false;
268  predefinedPositionOrder = DEFAULT_PLACEMENT_ORDER;
269  fitInPolygonOnly = false;
270  quadOffset = QuadrantOver;
271  xOffset = 0;
272  yOffset = 0;
273  offsetUnits = QgsUnitTypes::RenderMillimeters;
274  dist = 0;
276  offsetType = FromPoint;
277  angleOffset = 0;
278  preserveRotation = true;
279  maxCurvedCharAngleIn = 25.0;
280  maxCurvedCharAngleOut = -25.0;
281  priority = 5;
282  repeatDistance = 0;
283  repeatDistanceUnit = QgsUnitTypes::RenderMillimeters;
284 
285  // rendering
286  scaleVisibility = false;
287  maximumScale = 0.0;
288  minimumScale = 0.0;
289  fontLimitPixelSize = false;
290  fontMinPixelSize = 0; //trigger to turn it on by default for map unit labels
291  fontMaxPixelSize = 10000;
292  displayAll = false;
293  upsidedownLabels = Upright;
294 
295  labelPerPart = false;
296  mergeLines = false;
297  minFeatureSize = 0.0;
298  limitNumLabels = false;
299  maxNumLabels = 2000;
300  obstacle = true;
301  obstacleFactor = 1.0;
302  obstacleType = PolygonInterior;
303  zIndex = 0.0;
304 }
305 
307  : fieldIndex( 0 )
308  , mDataDefinedProperties( s.mDataDefinedProperties )
309 {
310  *this = s;
311 }
312 
314 {
315  if ( this == &s )
316  return *this;
317 
318  // copy only permanent stuff
319 
321 
322  // text style
323  fieldName = s.fieldName;
328 
329  // text formatting
330  wrapChar = s.wrapChar;
340  decimals = s.decimals;
341  plusSign = s.plusSign;
342 
343  // placement
344  placement = s.placement;
351  xOffset = s.xOffset;
352  yOffset = s.yOffset;
355  dist = s.dist;
357  distUnits = s.distUnits;
363  priority = s.priority;
367 
368  // rendering
377 
383  obstacle = s.obstacle;
386  zIndex = s.zIndex;
387 
388  mFormat = s.mFormat;
389  mDataDefinedProperties = s.mDataDefinedProperties;
390 
391  return *this;
392 }
393 
394 
396 {
397  // pal layer is deleted internally in PAL
398 
399  delete expression;
400 }
401 
402 
404 {
405  initPropertyDefinitions();
406  return sPropertyDefinitions;
407 }
408 
410 {
411  if ( !expression )
412  {
413  expression = new QgsExpression( fieldName );
414  }
415  return expression;
416 }
417 
418 static Qt::PenJoinStyle _decodePenJoinStyle( const QString &str )
419 {
420  if ( str.compare( QLatin1String( "Miter" ), Qt::CaseInsensitive ) == 0 ) return Qt::MiterJoin;
421  if ( str.compare( QLatin1String( "Round" ), Qt::CaseInsensitive ) == 0 ) return Qt::RoundJoin;
422  return Qt::BevelJoin; // "Bevel"
423 }
424 
425 QString updateDataDefinedString( const QString &value )
426 {
427  // TODO: update or remove this when project settings for labeling are migrated to better XML layout
428  QString newValue = value;
429  if ( !value.isEmpty() && !value.contains( QLatin1String( "~~" ) ) )
430  {
431  QStringList values;
432  values << QStringLiteral( "1" ); // all old-style values are active if not empty
433  values << QStringLiteral( "0" );
434  values << QString();
435  values << value; // all old-style values are only field names
436  newValue = values.join( QStringLiteral( "~~" ) );
437  }
438 
439  return newValue;
440 }
441 
442 void QgsPalLayerSettings::readOldDataDefinedProperty( QgsVectorLayer *layer, QgsPalLayerSettings::Property p )
443 {
444  QString newPropertyName = "labeling/dataDefined/" + sPropertyDefinitions.value( p ).name();
445  QVariant newPropertyField = layer->customProperty( newPropertyName, QVariant() );
446 
447  if ( !newPropertyField.isValid() )
448  return;
449 
450  QString ddString = newPropertyField.toString();
451 
452  if ( !ddString.isEmpty() && ddString != QLatin1String( "0~~0~~~~" ) )
453  {
454  // TODO: update this when project settings for labeling are migrated to better XML layout
455  QString newStyleString = updateDataDefinedString( ddString );
456  QStringList ddv = newStyleString.split( QStringLiteral( "~~" ) );
457 
458  bool active = ddv.at( 0 ).toInt();
459  if ( ddv.at( 1 ).toInt() )
460  {
461  mDataDefinedProperties.setProperty( p, QgsProperty::fromExpression( ddv.at( 2 ), active ) );
462  }
463  else
464  {
465  mDataDefinedProperties.setProperty( p, QgsProperty::fromField( ddv.at( 3 ), active ) );
466  }
467  }
468  else
469  {
470  // remove unused properties
471  layer->removeCustomProperty( newPropertyName );
472  }
473 }
474 
475 void QgsPalLayerSettings::readOldDataDefinedPropertyMap( QgsVectorLayer *layer, QDomElement *parentElem )
476 {
477  if ( !layer && !parentElem )
478  {
479  return;
480  }
481 
482  QgsPropertiesDefinition::const_iterator i = sPropertyDefinitions.constBegin();
483  for ( ; i != sPropertyDefinitions.constEnd(); ++i )
484  {
485  if ( layer )
486  {
487  // reading from layer's custom properties
488  readOldDataDefinedProperty( layer, static_cast< Property >( i.key() ) );
489  }
490  else if ( parentElem )
491  {
492  // reading from XML
493  QDomElement e = parentElem->firstChildElement( i.value().name() );
494  if ( !e.isNull() )
495  {
496  bool active = e.attribute( QStringLiteral( "active" ) ).compare( QLatin1String( "true" ), Qt::CaseInsensitive ) == 0;
497  bool isExpression = e.attribute( QStringLiteral( "useExpr" ) ).compare( QLatin1String( "true" ), Qt::CaseInsensitive ) == 0;
498  if ( isExpression )
499  {
500  mDataDefinedProperties.setProperty( i.key(), QgsProperty::fromExpression( e.attribute( QStringLiteral( "expr" ) ), active ) );
501  }
502  else
503  {
504  mDataDefinedProperties.setProperty( i.key(), QgsProperty::fromField( e.attribute( QStringLiteral( "field" ) ), active ) );
505  }
506  }
507  }
508  }
509 }
510 
511 void QgsPalLayerSettings::readFromLayerCustomProperties( QgsVectorLayer *layer )
512 {
513  if ( layer->customProperty( QStringLiteral( "labeling" ) ).toString() != QLatin1String( "pal" ) )
514  {
515  if ( layer->geometryType() == QgsWkbTypes::PointGeometry )
517 
518  // for polygons the "over point" (over centroid) placement is better than the default
519  // "around point" (around centroid) which is more suitable for points
520  if ( layer->geometryType() == QgsWkbTypes::PolygonGeometry )
522 
523  return; // there's no information available
524  }
525 
526  // NOTE: set defaults for newly added properties, for backwards compatibility
527 
528  drawLabels = layer->customProperty( QStringLiteral( "labeling/drawLabels" ), true ).toBool();
529 
530  mFormat.readFromLayer( layer );
531 
532  // text style
533  fieldName = layer->customProperty( QStringLiteral( "labeling/fieldName" ) ).toString();
534  isExpression = layer->customProperty( QStringLiteral( "labeling/isExpression" ) ).toBool();
535  previewBkgrdColor = QColor( layer->customProperty( QStringLiteral( "labeling/previewBkgrdColor" ), QVariant( "#ffffff" ) ).toString() );
536  QDomDocument doc( QStringLiteral( "substitutions" ) );
537  doc.setContent( layer->customProperty( QStringLiteral( "labeling/substitutions" ) ).toString() );
538  QDomElement replacementElem = doc.firstChildElement( QStringLiteral( "substitutions" ) );
539  substitutions.readXml( replacementElem );
540  useSubstitutions = layer->customProperty( QStringLiteral( "labeling/useSubstitutions" ) ).toBool();
541 
542  // text formatting
543  wrapChar = layer->customProperty( QStringLiteral( "labeling/wrapChar" ) ).toString();
544  autoWrapLength = layer->customProperty( QStringLiteral( "labeling/autoWrapLength" ) ).toInt();
545  useMaxLineLengthForAutoWrap = layer->customProperty( QStringLiteral( "labeling/useMaxLineLengthForAutoWrap" ), QStringLiteral( "1" ) ).toBool();
546 
547  multilineAlign = static_cast< MultiLineAlign >( layer->customProperty( QStringLiteral( "labeling/multilineAlign" ), QVariant( MultiFollowPlacement ) ).toUInt() );
548  addDirectionSymbol = layer->customProperty( QStringLiteral( "labeling/addDirectionSymbol" ) ).toBool();
549  leftDirectionSymbol = layer->customProperty( QStringLiteral( "labeling/leftDirectionSymbol" ), QVariant( "<" ) ).toString();
550  rightDirectionSymbol = layer->customProperty( QStringLiteral( "labeling/rightDirectionSymbol" ), QVariant( ">" ) ).toString();
551  reverseDirectionSymbol = layer->customProperty( QStringLiteral( "labeling/reverseDirectionSymbol" ) ).toBool();
552  placeDirectionSymbol = static_cast< DirectionSymbols >( layer->customProperty( QStringLiteral( "labeling/placeDirectionSymbol" ), QVariant( SymbolLeftRight ) ).toUInt() );
553  formatNumbers = layer->customProperty( QStringLiteral( "labeling/formatNumbers" ) ).toBool();
554  decimals = layer->customProperty( QStringLiteral( "labeling/decimals" ) ).toInt();
555  plusSign = layer->customProperty( QStringLiteral( "labeling/plussign" ) ).toBool();
556 
557  // placement
558  placement = static_cast< Placement >( layer->customProperty( QStringLiteral( "labeling/placement" ) ).toInt() );
559  placementFlags = layer->customProperty( QStringLiteral( "labeling/placementFlags" ) ).toUInt();
560  centroidWhole = layer->customProperty( QStringLiteral( "labeling/centroidWhole" ), QVariant( false ) ).toBool();
561  centroidInside = layer->customProperty( QStringLiteral( "labeling/centroidInside" ), QVariant( false ) ).toBool();
562  predefinedPositionOrder = QgsLabelingUtils::decodePredefinedPositionOrder( layer->customProperty( QStringLiteral( "labeling/predefinedPositionOrder" ) ).toString() );
563  if ( predefinedPositionOrder.isEmpty() )
564  predefinedPositionOrder = DEFAULT_PLACEMENT_ORDER;
565  fitInPolygonOnly = layer->customProperty( QStringLiteral( "labeling/fitInPolygonOnly" ), QVariant( false ) ).toBool();
566  dist = layer->customProperty( QStringLiteral( "labeling/dist" ) ).toDouble();
567  distUnits = layer->customProperty( QStringLiteral( "labeling/distInMapUnits" ) ).toBool() ? QgsUnitTypes::RenderMapUnits : QgsUnitTypes::RenderMillimeters;
568  if ( layer->customProperty( QStringLiteral( "labeling/distMapUnitScale" ) ).toString().isEmpty() )
569  {
570  //fallback to older property
571  double oldMin = layer->customProperty( QStringLiteral( "labeling/distMapUnitMinScale" ), 0.0 ).toDouble();
572  distMapUnitScale.minScale = oldMin != 0 ? 1.0 / oldMin : 0;
573  double oldMax = layer->customProperty( QStringLiteral( "labeling/distMapUnitMaxScale" ), 0.0 ).toDouble();
574  distMapUnitScale.maxScale = oldMax != 0 ? 1.0 / oldMax : 0;
575  }
576  else
577  {
578  distMapUnitScale = QgsSymbolLayerUtils::decodeMapUnitScale( layer->customProperty( QStringLiteral( "labeling/distMapUnitScale" ) ).toString() );
579  }
580  offsetType = static_cast< OffsetType >( layer->customProperty( QStringLiteral( "labeling/offsetType" ), QVariant( FromPoint ) ).toUInt() );
581  quadOffset = static_cast< QuadrantPosition >( layer->customProperty( QStringLiteral( "labeling/quadOffset" ), QVariant( QuadrantOver ) ).toUInt() );
582  xOffset = layer->customProperty( QStringLiteral( "labeling/xOffset" ), QVariant( 0.0 ) ).toDouble();
583  yOffset = layer->customProperty( QStringLiteral( "labeling/yOffset" ), QVariant( 0.0 ) ).toDouble();
584  if ( layer->customProperty( QStringLiteral( "labeling/labelOffsetInMapUnits" ), QVariant( true ) ).toBool() )
586  else
588 
589  if ( layer->customProperty( QStringLiteral( "labeling/labelOffsetMapUnitScale" ) ).toString().isEmpty() )
590  {
591  //fallback to older property
592  double oldMin = layer->customProperty( QStringLiteral( "labeling/labelOffsetMapUnitMinScale" ), 0.0 ).toDouble();
593  labelOffsetMapUnitScale.minScale = oldMin != 0 ? 1.0 / oldMin : 0;
594  double oldMax = layer->customProperty( QStringLiteral( "labeling/labelOffsetMapUnitMaxScale" ), 0.0 ).toDouble();
595  labelOffsetMapUnitScale.maxScale = oldMax != 0 ? 1.0 / oldMax : 0;
596  }
597  else
598  {
599  labelOffsetMapUnitScale = QgsSymbolLayerUtils::decodeMapUnitScale( layer->customProperty( QStringLiteral( "labeling/labelOffsetMapUnitScale" ) ).toString() );
600  }
601 
602  QVariant tempAngle = layer->customProperty( QStringLiteral( "labeling/angleOffset" ), QVariant() );
603  if ( tempAngle.isValid() )
604  {
605  double oldAngle = layer->customProperty( QStringLiteral( "labeling/angleOffset" ), QVariant( 0.0 ) ).toDouble();
606  angleOffset = std::fmod( 360 - oldAngle, 360.0 );
607  }
608  else
609  {
610  angleOffset = layer->customProperty( QStringLiteral( "labeling/rotationAngle" ), QVariant( 0.0 ) ).toDouble();
611  }
612 
613  preserveRotation = layer->customProperty( QStringLiteral( "labeling/preserveRotation" ), QVariant( true ) ).toBool();
614  maxCurvedCharAngleIn = layer->customProperty( QStringLiteral( "labeling/maxCurvedCharAngleIn" ), QVariant( 25.0 ) ).toDouble();
615  maxCurvedCharAngleOut = layer->customProperty( QStringLiteral( "labeling/maxCurvedCharAngleOut" ), QVariant( -25.0 ) ).toDouble();
616  priority = layer->customProperty( QStringLiteral( "labeling/priority" ) ).toInt();
617  repeatDistance = layer->customProperty( QStringLiteral( "labeling/repeatDistance" ), 0.0 ).toDouble();
618  switch ( layer->customProperty( QStringLiteral( "labeling/repeatDistanceUnit" ), QVariant( 1 ) ).toUInt() )
619  {
620  case 0:
622  break;
623  case 1:
625  break;
626  case 2:
628  break;
629  case 3:
631  break;
632  }
633  if ( layer->customProperty( QStringLiteral( "labeling/repeatDistanceMapUnitScale" ) ).toString().isEmpty() )
634  {
635  //fallback to older property
636  double oldMin = layer->customProperty( QStringLiteral( "labeling/repeatDistanceMapUnitMinScale" ), 0.0 ).toDouble();
637  repeatDistanceMapUnitScale.minScale = oldMin != 0 ? 1.0 / oldMin : 0;
638  double oldMax = layer->customProperty( QStringLiteral( "labeling/repeatDistanceMapUnitMaxScale" ), 0.0 ).toDouble();
639  repeatDistanceMapUnitScale.maxScale = oldMax != 0 ? 1.0 / oldMax : 0;
640  }
641  else
642  {
643  repeatDistanceMapUnitScale = QgsSymbolLayerUtils::decodeMapUnitScale( layer->customProperty( QStringLiteral( "labeling/repeatDistanceMapUnitScale" ) ).toString() );
644  }
645 
646  // rendering
647  double scalemn = layer->customProperty( QStringLiteral( "labeling/scaleMin" ), QVariant( 0 ) ).toDouble();
648  double scalemx = layer->customProperty( QStringLiteral( "labeling/scaleMax" ), QVariant( 0 ) ).toDouble();
649 
650  // fix for scale visibility limits being keyed off of just its values in the past (<2.0)
651  QVariant scalevis = layer->customProperty( QStringLiteral( "labeling/scaleVisibility" ), QVariant() );
652  if ( scalevis.isValid() )
653  {
654  scaleVisibility = scalevis.toBool();
655  maximumScale = scalemn;
656  minimumScale = scalemx;
657  }
658  else if ( scalemn > 0 || scalemx > 0 )
659  {
660  scaleVisibility = true;
661  maximumScale = scalemn;
662  minimumScale = scalemx;
663  }
664  else
665  {
666  // keep scaleMin and scaleMax at new 1.0 defaults (1 and 10000000, were 0 and 0)
667  scaleVisibility = false;
668  }
669 
670 
671  fontLimitPixelSize = layer->customProperty( QStringLiteral( "labeling/fontLimitPixelSize" ), QVariant( false ) ).toBool();
672  fontMinPixelSize = layer->customProperty( QStringLiteral( "labeling/fontMinPixelSize" ), QVariant( 0 ) ).toInt();
673  fontMaxPixelSize = layer->customProperty( QStringLiteral( "labeling/fontMaxPixelSize" ), QVariant( 10000 ) ).toInt();
674  displayAll = layer->customProperty( QStringLiteral( "labeling/displayAll" ), QVariant( false ) ).toBool();
675  upsidedownLabels = static_cast< UpsideDownLabels >( layer->customProperty( QStringLiteral( "labeling/upsidedownLabels" ), QVariant( Upright ) ).toUInt() );
676 
677  labelPerPart = layer->customProperty( QStringLiteral( "labeling/labelPerPart" ) ).toBool();
678  mergeLines = layer->customProperty( QStringLiteral( "labeling/mergeLines" ) ).toBool();
679  minFeatureSize = layer->customProperty( QStringLiteral( "labeling/minFeatureSize" ) ).toDouble();
680  limitNumLabels = layer->customProperty( QStringLiteral( "labeling/limitNumLabels" ), QVariant( false ) ).toBool();
681  maxNumLabels = layer->customProperty( QStringLiteral( "labeling/maxNumLabels" ), QVariant( 2000 ) ).toInt();
682  obstacle = layer->customProperty( QStringLiteral( "labeling/obstacle" ), QVariant( true ) ).toBool();
683  obstacleFactor = layer->customProperty( QStringLiteral( "labeling/obstacleFactor" ), QVariant( 1.0 ) ).toDouble();
684  obstacleType = static_cast< ObstacleType >( layer->customProperty( QStringLiteral( "labeling/obstacleType" ), QVariant( PolygonInterior ) ).toUInt() );
685  zIndex = layer->customProperty( QStringLiteral( "labeling/zIndex" ), QVariant( 0.0 ) ).toDouble();
686 
687  mDataDefinedProperties.clear();
688  if ( layer->customProperty( QStringLiteral( "labeling/ddProperties" ) ).isValid() )
689  {
690  QDomDocument doc( QStringLiteral( "dd" ) );
691  doc.setContent( layer->customProperty( QStringLiteral( "labeling/ddProperties" ) ).toString() );
692  QDomElement elem = doc.firstChildElement( QStringLiteral( "properties" ) );
693  mDataDefinedProperties.readXml( elem, sPropertyDefinitions );
694  }
695  else
696  {
697  // read QGIS 2.x style data defined properties
698  readOldDataDefinedPropertyMap( layer, nullptr );
699  }
700  // upgrade older data defined settings
701  if ( mDataDefinedProperties.isActive( FontTransp ) )
702  {
703  mDataDefinedProperties.setProperty( FontOpacity, QgsProperty::fromExpression( QStringLiteral( "100 - (%1)" ).arg( mDataDefinedProperties.property( FontTransp ).asExpression() ) ) );
704  mDataDefinedProperties.setProperty( FontTransp, QgsProperty() );
705  }
706  if ( mDataDefinedProperties.isActive( BufferTransp ) )
707  {
708  mDataDefinedProperties.setProperty( BufferOpacity, QgsProperty::fromExpression( QStringLiteral( "100 - (%1)" ).arg( mDataDefinedProperties.property( BufferTransp ).asExpression() ) ) );
709  mDataDefinedProperties.setProperty( BufferTransp, QgsProperty() );
710  }
711  if ( mDataDefinedProperties.isActive( ShapeTransparency ) )
712  {
713  mDataDefinedProperties.setProperty( ShapeOpacity, QgsProperty::fromExpression( QStringLiteral( "100 - (%1)" ).arg( mDataDefinedProperties.property( ShapeTransparency ).asExpression() ) ) );
714  mDataDefinedProperties.setProperty( ShapeTransparency, QgsProperty() );
715  }
716  if ( mDataDefinedProperties.isActive( ShadowTransparency ) )
717  {
718  mDataDefinedProperties.setProperty( ShadowOpacity, QgsProperty::fromExpression( QStringLiteral( "100 - (%1)" ).arg( mDataDefinedProperties.property( ShadowTransparency ).asExpression() ) ) );
719  mDataDefinedProperties.setProperty( ShadowTransparency, QgsProperty() );
720  }
721  if ( mDataDefinedProperties.isActive( Rotation ) )
722  {
723  mDataDefinedProperties.setProperty( LabelRotation, QgsProperty::fromExpression( QStringLiteral( "360 - (%1)" ).arg( mDataDefinedProperties.property( Rotation ).asExpression() ) ) );
724  mDataDefinedProperties.setProperty( Rotation, QgsProperty() );
725  }
726  // older 2.x projects had min/max scale flipped - so change them here.
727  if ( mDataDefinedProperties.isActive( MinScale ) )
728  {
729  mDataDefinedProperties.setProperty( MaximumScale, mDataDefinedProperties.property( MinScale ) );
730  mDataDefinedProperties.setProperty( MinScale, QgsProperty() );
731  }
732  if ( mDataDefinedProperties.isActive( MaxScale ) )
733  {
734  mDataDefinedProperties.setProperty( MinimumScale, mDataDefinedProperties.property( MaxScale ) );
735  mDataDefinedProperties.setProperty( MaxScale, QgsProperty() );
736  }
737 }
738 
739 void QgsPalLayerSettings::readXml( QDomElement &elem, const QgsReadWriteContext &context )
740 {
741  // text style
742  QDomElement textStyleElem = elem.firstChildElement( QStringLiteral( "text-style" ) );
743  fieldName = textStyleElem.attribute( QStringLiteral( "fieldName" ) );
744  isExpression = textStyleElem.attribute( QStringLiteral( "isExpression" ) ).toInt();
745 
746  mFormat.readXml( elem, context );
747  previewBkgrdColor = QColor( textStyleElem.attribute( QStringLiteral( "previewBkgrdColor" ), QStringLiteral( "#ffffff" ) ) );
748  substitutions.readXml( textStyleElem.firstChildElement( QStringLiteral( "substitutions" ) ) );
749  useSubstitutions = textStyleElem.attribute( QStringLiteral( "useSubstitutions" ) ).toInt();
750 
751  // text formatting
752  QDomElement textFormatElem = elem.firstChildElement( QStringLiteral( "text-format" ) );
753  wrapChar = textFormatElem.attribute( QStringLiteral( "wrapChar" ) );
754  autoWrapLength = textFormatElem.attribute( QStringLiteral( "autoWrapLength" ), QStringLiteral( "0" ) ).toInt();
755  useMaxLineLengthForAutoWrap = textFormatElem.attribute( QStringLiteral( "useMaxLineLengthForAutoWrap" ), QStringLiteral( "1" ) ).toInt();
756  multilineAlign = static_cast< MultiLineAlign >( textFormatElem.attribute( QStringLiteral( "multilineAlign" ), QString::number( MultiFollowPlacement ) ).toUInt() );
757  addDirectionSymbol = textFormatElem.attribute( QStringLiteral( "addDirectionSymbol" ) ).toInt();
758  leftDirectionSymbol = textFormatElem.attribute( QStringLiteral( "leftDirectionSymbol" ), QStringLiteral( "<" ) );
759  rightDirectionSymbol = textFormatElem.attribute( QStringLiteral( "rightDirectionSymbol" ), QStringLiteral( ">" ) );
760  reverseDirectionSymbol = textFormatElem.attribute( QStringLiteral( "reverseDirectionSymbol" ) ).toInt();
761  placeDirectionSymbol = static_cast< DirectionSymbols >( textFormatElem.attribute( QStringLiteral( "placeDirectionSymbol" ), QString::number( SymbolLeftRight ) ).toUInt() );
762  formatNumbers = textFormatElem.attribute( QStringLiteral( "formatNumbers" ) ).toInt();
763  decimals = textFormatElem.attribute( QStringLiteral( "decimals" ) ).toInt();
764  plusSign = textFormatElem.attribute( QStringLiteral( "plussign" ) ).toInt();
765 
766  // placement
767  QDomElement placementElem = elem.firstChildElement( QStringLiteral( "placement" ) );
768  placement = static_cast< Placement >( placementElem.attribute( QStringLiteral( "placement" ) ).toInt() );
769  placementFlags = placementElem.attribute( QStringLiteral( "placementFlags" ) ).toUInt();
770  centroidWhole = placementElem.attribute( QStringLiteral( "centroidWhole" ), QStringLiteral( "0" ) ).toInt();
771  centroidInside = placementElem.attribute( QStringLiteral( "centroidInside" ), QStringLiteral( "0" ) ).toInt();
772  predefinedPositionOrder = QgsLabelingUtils::decodePredefinedPositionOrder( placementElem.attribute( QStringLiteral( "predefinedPositionOrder" ) ) );
773  if ( predefinedPositionOrder.isEmpty() )
774  predefinedPositionOrder = DEFAULT_PLACEMENT_ORDER;
775  fitInPolygonOnly = placementElem.attribute( QStringLiteral( "fitInPolygonOnly" ), QStringLiteral( "0" ) ).toInt();
776  dist = placementElem.attribute( QStringLiteral( "dist" ) ).toDouble();
777  if ( !placementElem.hasAttribute( QStringLiteral( "distUnits" ) ) )
778  {
779  if ( placementElem.attribute( QStringLiteral( "distInMapUnits" ) ).toInt() )
781  else
783  }
784  else
785  {
786  distUnits = QgsUnitTypes::decodeRenderUnit( placementElem.attribute( QStringLiteral( "distUnits" ) ) );
787  }
788  if ( !placementElem.hasAttribute( QStringLiteral( "distMapUnitScale" ) ) )
789  {
790  //fallback to older property
791  double oldMin = placementElem.attribute( QStringLiteral( "distMapUnitMinScale" ), QStringLiteral( "0" ) ).toDouble();
792  distMapUnitScale.minScale = oldMin != 0 ? 1.0 / oldMin : 0;
793  double oldMax = placementElem.attribute( QStringLiteral( "distMapUnitMaxScale" ), QStringLiteral( "0" ) ).toDouble();
794  distMapUnitScale.maxScale = oldMax != 0 ? 1.0 / oldMax : 0;
795  }
796  else
797  {
798  distMapUnitScale = QgsSymbolLayerUtils::decodeMapUnitScale( placementElem.attribute( QStringLiteral( "distMapUnitScale" ) ) );
799  }
800  offsetType = static_cast< OffsetType >( placementElem.attribute( QStringLiteral( "offsetType" ), QString::number( FromPoint ) ).toUInt() );
801  quadOffset = static_cast< QuadrantPosition >( placementElem.attribute( QStringLiteral( "quadOffset" ), QString::number( QuadrantOver ) ).toUInt() );
802  xOffset = placementElem.attribute( QStringLiteral( "xOffset" ), QStringLiteral( "0" ) ).toDouble();
803  yOffset = placementElem.attribute( QStringLiteral( "yOffset" ), QStringLiteral( "0" ) ).toDouble();
804  if ( !placementElem.hasAttribute( QStringLiteral( "offsetUnits" ) ) )
805  {
806  offsetUnits = placementElem.attribute( QStringLiteral( "labelOffsetInMapUnits" ), QStringLiteral( "1" ) ).toInt() ? QgsUnitTypes::RenderMapUnits : QgsUnitTypes::RenderMillimeters;
807  }
808  else
809  {
810  offsetUnits = QgsUnitTypes::decodeRenderUnit( placementElem.attribute( QStringLiteral( "offsetUnits" ) ) );
811  }
812  if ( !placementElem.hasAttribute( QStringLiteral( "labelOffsetMapUnitScale" ) ) )
813  {
814  //fallback to older property
815  double oldMin = placementElem.attribute( QStringLiteral( "labelOffsetMapUnitMinScale" ), QStringLiteral( "0" ) ).toDouble();
816  labelOffsetMapUnitScale.minScale = oldMin != 0 ? 1.0 / oldMin : 0;
817  double oldMax = placementElem.attribute( QStringLiteral( "labelOffsetMapUnitMaxScale" ), QStringLiteral( "0" ) ).toDouble();
818  labelOffsetMapUnitScale.maxScale = oldMax != 0 ? 1.0 / oldMax : 0;
819  }
820  else
821  {
822  labelOffsetMapUnitScale = QgsSymbolLayerUtils::decodeMapUnitScale( placementElem.attribute( QStringLiteral( "labelOffsetMapUnitScale" ) ) );
823  }
824 
825  if ( placementElem.hasAttribute( QStringLiteral( "angleOffset" ) ) )
826  {
827  double oldAngle = placementElem.attribute( QStringLiteral( "angleOffset" ), QStringLiteral( "0" ) ).toDouble();
828  angleOffset = std::fmod( 360 - oldAngle, 360.0 );
829  }
830  else
831  {
832  angleOffset = placementElem.attribute( QStringLiteral( "rotationAngle" ), QStringLiteral( "0" ) ).toDouble();
833  }
834 
835  preserveRotation = placementElem.attribute( QStringLiteral( "preserveRotation" ), QStringLiteral( "1" ) ).toInt();
836  maxCurvedCharAngleIn = placementElem.attribute( QStringLiteral( "maxCurvedCharAngleIn" ), QStringLiteral( "25" ) ).toDouble();
837  maxCurvedCharAngleOut = placementElem.attribute( QStringLiteral( "maxCurvedCharAngleOut" ), QStringLiteral( "-25" ) ).toDouble();
838  priority = placementElem.attribute( QStringLiteral( "priority" ) ).toInt();
839  repeatDistance = placementElem.attribute( QStringLiteral( "repeatDistance" ), QStringLiteral( "0" ) ).toDouble();
840  if ( !placementElem.hasAttribute( QStringLiteral( "repeatDistanceUnits" ) ) )
841  {
842  // upgrade old setting
843  switch ( placementElem.attribute( QStringLiteral( "repeatDistanceUnit" ), QString::number( 1 ) ).toUInt() )
844  {
845  case 0:
847  break;
848  case 1:
850  break;
851  case 2:
853  break;
854  case 3:
856  break;
857  }
858  }
859  else
860  {
861  repeatDistanceUnit = QgsUnitTypes::decodeRenderUnit( placementElem.attribute( QStringLiteral( "repeatDistanceUnits" ) ) );
862  }
863  if ( !placementElem.hasAttribute( QStringLiteral( "repeatDistanceMapUnitScale" ) ) )
864  {
865  //fallback to older property
866  double oldMin = placementElem.attribute( QStringLiteral( "repeatDistanceMapUnitMinScale" ), QStringLiteral( "0" ) ).toDouble();
867  repeatDistanceMapUnitScale.minScale = oldMin != 0 ? 1.0 / oldMin : 0;
868  double oldMax = placementElem.attribute( QStringLiteral( "repeatDistanceMapUnitMaxScale" ), QStringLiteral( "0" ) ).toDouble();
869  repeatDistanceMapUnitScale.maxScale = oldMax != 0 ? 1.0 / oldMax : 0;
870  }
871  else
872  {
873  repeatDistanceMapUnitScale = QgsSymbolLayerUtils::decodeMapUnitScale( placementElem.attribute( QStringLiteral( "repeatDistanceMapUnitScale" ) ) );
874  }
875 
876  // rendering
877  QDomElement renderingElem = elem.firstChildElement( QStringLiteral( "rendering" ) );
878 
879  drawLabels = renderingElem.attribute( QStringLiteral( "drawLabels" ), QStringLiteral( "1" ) ).toInt();
880 
881  maximumScale = renderingElem.attribute( QStringLiteral( "scaleMin" ), QStringLiteral( "0" ) ).toDouble();
882  minimumScale = renderingElem.attribute( QStringLiteral( "scaleMax" ), QStringLiteral( "0" ) ).toDouble();
883  scaleVisibility = renderingElem.attribute( QStringLiteral( "scaleVisibility" ) ).toInt();
884 
885  fontLimitPixelSize = renderingElem.attribute( QStringLiteral( "fontLimitPixelSize" ), QStringLiteral( "0" ) ).toInt();
886  fontMinPixelSize = renderingElem.attribute( QStringLiteral( "fontMinPixelSize" ), QStringLiteral( "0" ) ).toInt();
887  fontMaxPixelSize = renderingElem.attribute( QStringLiteral( "fontMaxPixelSize" ), QStringLiteral( "10000" ) ).toInt();
888  displayAll = renderingElem.attribute( QStringLiteral( "displayAll" ), QStringLiteral( "0" ) ).toInt();
889  upsidedownLabels = static_cast< UpsideDownLabels >( renderingElem.attribute( QStringLiteral( "upsidedownLabels" ), QString::number( Upright ) ).toUInt() );
890 
891  labelPerPart = renderingElem.attribute( QStringLiteral( "labelPerPart" ) ).toInt();
892  mergeLines = renderingElem.attribute( QStringLiteral( "mergeLines" ) ).toInt();
893  minFeatureSize = renderingElem.attribute( QStringLiteral( "minFeatureSize" ) ).toDouble();
894  limitNumLabels = renderingElem.attribute( QStringLiteral( "limitNumLabels" ), QStringLiteral( "0" ) ).toInt();
895  maxNumLabels = renderingElem.attribute( QStringLiteral( "maxNumLabels" ), QStringLiteral( "2000" ) ).toInt();
896  obstacle = renderingElem.attribute( QStringLiteral( "obstacle" ), QStringLiteral( "1" ) ).toInt();
897  obstacleFactor = renderingElem.attribute( QStringLiteral( "obstacleFactor" ), QStringLiteral( "1" ) ).toDouble();
898  obstacleType = static_cast< ObstacleType >( renderingElem.attribute( QStringLiteral( "obstacleType" ), QString::number( PolygonInterior ) ).toUInt() );
899  zIndex = renderingElem.attribute( QStringLiteral( "zIndex" ), QStringLiteral( "0.0" ) ).toDouble();
900 
901  QDomElement ddElem = elem.firstChildElement( QStringLiteral( "dd_properties" ) );
902  if ( !ddElem.isNull() )
903  {
904  mDataDefinedProperties.readXml( ddElem, sPropertyDefinitions );
905  }
906  else
907  {
908  // upgrade 2.x style dd project
909  mDataDefinedProperties.clear();
910  QDomElement ddElem = elem.firstChildElement( QStringLiteral( "data-defined" ) );
911  readOldDataDefinedPropertyMap( nullptr, &ddElem );
912  }
913  // upgrade older data defined settings
914  if ( mDataDefinedProperties.isActive( FontTransp ) )
915  {
916  mDataDefinedProperties.setProperty( FontOpacity, QgsProperty::fromExpression( QStringLiteral( "100 - (%1)" ).arg( mDataDefinedProperties.property( FontTransp ).asExpression() ) ) );
917  mDataDefinedProperties.setProperty( FontTransp, QgsProperty() );
918  }
919  if ( mDataDefinedProperties.isActive( BufferTransp ) )
920  {
921  mDataDefinedProperties.setProperty( BufferOpacity, QgsProperty::fromExpression( QStringLiteral( "100 - (%1)" ).arg( mDataDefinedProperties.property( BufferTransp ).asExpression() ) ) );
922  mDataDefinedProperties.setProperty( BufferTransp, QgsProperty() );
923  }
924  if ( mDataDefinedProperties.isActive( ShapeTransparency ) )
925  {
926  mDataDefinedProperties.setProperty( ShapeOpacity, QgsProperty::fromExpression( QStringLiteral( "100 - (%1)" ).arg( mDataDefinedProperties.property( ShapeTransparency ).asExpression() ) ) );
927  mDataDefinedProperties.setProperty( ShapeTransparency, QgsProperty() );
928  }
929  if ( mDataDefinedProperties.isActive( ShadowTransparency ) )
930  {
931  mDataDefinedProperties.setProperty( ShadowOpacity, QgsProperty::fromExpression( QStringLiteral( "100 - (%1)" ).arg( mDataDefinedProperties.property( ShadowTransparency ).asExpression() ) ) );
932  mDataDefinedProperties.setProperty( ShadowTransparency, QgsProperty() );
933  }
934  if ( mDataDefinedProperties.isActive( Rotation ) )
935  {
936  mDataDefinedProperties.setProperty( LabelRotation, QgsProperty::fromExpression( QStringLiteral( "360 - (%1)" ).arg( mDataDefinedProperties.property( Rotation ).asExpression() ) ) );
937  mDataDefinedProperties.setProperty( Rotation, QgsProperty() );
938  }
939  // older 2.x projects had min/max scale flipped - so change them here.
940  if ( mDataDefinedProperties.isActive( MinScale ) )
941  {
942  mDataDefinedProperties.setProperty( MaximumScale, mDataDefinedProperties.property( MinScale ) );
943  mDataDefinedProperties.setProperty( MinScale, QgsProperty() );
944  }
945  if ( mDataDefinedProperties.isActive( MaxScale ) )
946  {
947  mDataDefinedProperties.setProperty( MinimumScale, mDataDefinedProperties.property( MaxScale ) );
948  mDataDefinedProperties.setProperty( MaxScale, QgsProperty() );
949  }
950 }
951 
952 
953 
954 QDomElement QgsPalLayerSettings::writeXml( QDomDocument &doc, const QgsReadWriteContext &context )
955 {
956  QDomElement textStyleElem = mFormat.writeXml( doc, context );
957 
958  // text style
959  textStyleElem.setAttribute( QStringLiteral( "fieldName" ), fieldName );
960  textStyleElem.setAttribute( QStringLiteral( "isExpression" ), isExpression );
961  textStyleElem.setAttribute( QStringLiteral( "previewBkgrdColor" ), previewBkgrdColor.name() );
962  QDomElement replacementElem = doc.createElement( QStringLiteral( "substitutions" ) );
963  substitutions.writeXml( replacementElem, doc );
964  textStyleElem.appendChild( replacementElem );
965  textStyleElem.setAttribute( QStringLiteral( "useSubstitutions" ), useSubstitutions );
966 
967  // text formatting
968  QDomElement textFormatElem = doc.createElement( QStringLiteral( "text-format" ) );
969  textFormatElem.setAttribute( QStringLiteral( "wrapChar" ), wrapChar );
970  textFormatElem.setAttribute( QStringLiteral( "autoWrapLength" ), autoWrapLength );
971  textFormatElem.setAttribute( QStringLiteral( "useMaxLineLengthForAutoWrap" ), useMaxLineLengthForAutoWrap );
972  textFormatElem.setAttribute( QStringLiteral( "multilineAlign" ), static_cast< unsigned int >( multilineAlign ) );
973  textFormatElem.setAttribute( QStringLiteral( "addDirectionSymbol" ), addDirectionSymbol );
974  textFormatElem.setAttribute( QStringLiteral( "leftDirectionSymbol" ), leftDirectionSymbol );
975  textFormatElem.setAttribute( QStringLiteral( "rightDirectionSymbol" ), rightDirectionSymbol );
976  textFormatElem.setAttribute( QStringLiteral( "reverseDirectionSymbol" ), reverseDirectionSymbol );
977  textFormatElem.setAttribute( QStringLiteral( "placeDirectionSymbol" ), static_cast< unsigned int >( placeDirectionSymbol ) );
978  textFormatElem.setAttribute( QStringLiteral( "formatNumbers" ), formatNumbers );
979  textFormatElem.setAttribute( QStringLiteral( "decimals" ), decimals );
980  textFormatElem.setAttribute( QStringLiteral( "plussign" ), plusSign );
981 
982  // placement
983  QDomElement placementElem = doc.createElement( QStringLiteral( "placement" ) );
984  placementElem.setAttribute( QStringLiteral( "placement" ), placement );
985  placementElem.setAttribute( QStringLiteral( "placementFlags" ), static_cast< unsigned int >( placementFlags ) );
986  placementElem.setAttribute( QStringLiteral( "centroidWhole" ), centroidWhole );
987  placementElem.setAttribute( QStringLiteral( "centroidInside" ), centroidInside );
988  placementElem.setAttribute( QStringLiteral( "predefinedPositionOrder" ), QgsLabelingUtils::encodePredefinedPositionOrder( predefinedPositionOrder ) );
989  placementElem.setAttribute( QStringLiteral( "fitInPolygonOnly" ), fitInPolygonOnly );
990  placementElem.setAttribute( QStringLiteral( "dist" ), dist );
991  placementElem.setAttribute( QStringLiteral( "distUnits" ), QgsUnitTypes::encodeUnit( distUnits ) );
992  placementElem.setAttribute( QStringLiteral( "distMapUnitScale" ), QgsSymbolLayerUtils::encodeMapUnitScale( distMapUnitScale ) );
993  placementElem.setAttribute( QStringLiteral( "offsetType" ), static_cast< unsigned int >( offsetType ) );
994  placementElem.setAttribute( QStringLiteral( "quadOffset" ), static_cast< unsigned int >( quadOffset ) );
995  placementElem.setAttribute( QStringLiteral( "xOffset" ), xOffset );
996  placementElem.setAttribute( QStringLiteral( "yOffset" ), yOffset );
997  placementElem.setAttribute( QStringLiteral( "offsetUnits" ), QgsUnitTypes::encodeUnit( offsetUnits ) );
998  placementElem.setAttribute( QStringLiteral( "labelOffsetMapUnitScale" ), QgsSymbolLayerUtils::encodeMapUnitScale( labelOffsetMapUnitScale ) );
999  placementElem.setAttribute( QStringLiteral( "rotationAngle" ), angleOffset );
1000  placementElem.setAttribute( QStringLiteral( "preserveRotation" ), preserveRotation );
1001  placementElem.setAttribute( QStringLiteral( "maxCurvedCharAngleIn" ), maxCurvedCharAngleIn );
1002  placementElem.setAttribute( QStringLiteral( "maxCurvedCharAngleOut" ), maxCurvedCharAngleOut );
1003  placementElem.setAttribute( QStringLiteral( "priority" ), priority );
1004  placementElem.setAttribute( QStringLiteral( "repeatDistance" ), repeatDistance );
1005  placementElem.setAttribute( QStringLiteral( "repeatDistanceUnits" ), QgsUnitTypes::encodeUnit( repeatDistanceUnit ) );
1006  placementElem.setAttribute( QStringLiteral( "repeatDistanceMapUnitScale" ), QgsSymbolLayerUtils::encodeMapUnitScale( repeatDistanceMapUnitScale ) );
1007 
1008  // rendering
1009  QDomElement renderingElem = doc.createElement( QStringLiteral( "rendering" ) );
1010  renderingElem.setAttribute( QStringLiteral( "drawLabels" ), drawLabels );
1011  renderingElem.setAttribute( QStringLiteral( "scaleVisibility" ), scaleVisibility );
1012  renderingElem.setAttribute( QStringLiteral( "scaleMin" ), maximumScale );
1013  renderingElem.setAttribute( QStringLiteral( "scaleMax" ), minimumScale );
1014  renderingElem.setAttribute( QStringLiteral( "fontLimitPixelSize" ), fontLimitPixelSize );
1015  renderingElem.setAttribute( QStringLiteral( "fontMinPixelSize" ), fontMinPixelSize );
1016  renderingElem.setAttribute( QStringLiteral( "fontMaxPixelSize" ), fontMaxPixelSize );
1017  renderingElem.setAttribute( QStringLiteral( "displayAll" ), displayAll );
1018  renderingElem.setAttribute( QStringLiteral( "upsidedownLabels" ), static_cast< unsigned int >( upsidedownLabels ) );
1019 
1020  renderingElem.setAttribute( QStringLiteral( "labelPerPart" ), labelPerPart );
1021  renderingElem.setAttribute( QStringLiteral( "mergeLines" ), mergeLines );
1022  renderingElem.setAttribute( QStringLiteral( "minFeatureSize" ), minFeatureSize );
1023  renderingElem.setAttribute( QStringLiteral( "limitNumLabels" ), limitNumLabels );
1024  renderingElem.setAttribute( QStringLiteral( "maxNumLabels" ), maxNumLabels );
1025  renderingElem.setAttribute( QStringLiteral( "obstacle" ), obstacle );
1026  renderingElem.setAttribute( QStringLiteral( "obstacleFactor" ), obstacleFactor );
1027  renderingElem.setAttribute( QStringLiteral( "obstacleType" ), static_cast< unsigned int >( obstacleType ) );
1028  renderingElem.setAttribute( QStringLiteral( "zIndex" ), zIndex );
1029 
1030  QDomElement ddElem = doc.createElement( QStringLiteral( "dd_properties" ) );
1031  mDataDefinedProperties.writeXml( ddElem, sPropertyDefinitions );
1032 
1033  QDomElement elem = doc.createElement( QStringLiteral( "settings" ) );
1034  elem.appendChild( textStyleElem );
1035  elem.appendChild( textFormatElem );
1036  elem.appendChild( placementElem );
1037  elem.appendChild( renderingElem );
1038  elem.appendChild( ddElem );
1039  return elem;
1040 }
1041 
1042 bool QgsPalLayerSettings::checkMinimumSizeMM( const QgsRenderContext &ct, const QgsGeometry &geom, double minSize ) const
1043 {
1044  return QgsPalLabeling::checkMinimumSizeMM( ct, geom, minSize );
1045 }
1046 
1047 void QgsPalLayerSettings::calculateLabelSize( const QFontMetricsF *fm, QString text, double &labelX, double &labelY, QgsFeature *f, QgsRenderContext *context )
1048 {
1049  if ( !fm || !f )
1050  {
1051  return;
1052  }
1053 
1054  //try to keep < 2.12 API - handle no passed render context
1055  std::unique_ptr< QgsRenderContext > scopedRc;
1056  if ( !context )
1057  {
1058  scopedRc.reset( new QgsRenderContext() );
1059  if ( f )
1060  scopedRc->expressionContext().setFeature( *f );
1061  }
1062  QgsRenderContext *rc = context ? context : scopedRc.get();
1063 
1064  QString wrapchr = wrapChar;
1065  int evalAutoWrapLength = autoWrapLength;
1066  double multilineH = mFormat.lineHeight();
1067 
1068  bool addDirSymb = addDirectionSymbol;
1069  QString leftDirSymb = leftDirectionSymbol;
1070  QString rightDirSymb = rightDirectionSymbol;
1072 
1073  if ( f == mCurFeat ) // called internally, use any stored data defined values
1074  {
1075  if ( dataDefinedValues.contains( QgsPalLayerSettings::MultiLineWrapChar ) )
1076  {
1077  wrapchr = dataDefinedValues.value( QgsPalLayerSettings::MultiLineWrapChar ).toString();
1078  }
1079 
1080  if ( dataDefinedValues.contains( QgsPalLayerSettings::AutoWrapLength ) )
1081  {
1082  evalAutoWrapLength = dataDefinedValues.value( QgsPalLayerSettings::AutoWrapLength, evalAutoWrapLength ).toInt();
1083  }
1084 
1085  if ( dataDefinedValues.contains( QgsPalLayerSettings::MultiLineHeight ) )
1086  {
1087  multilineH = dataDefinedValues.value( QgsPalLayerSettings::MultiLineHeight ).toDouble();
1088  }
1089 
1090  if ( dataDefinedValues.contains( QgsPalLayerSettings::DirSymbDraw ) )
1091  {
1092  addDirSymb = dataDefinedValues.value( QgsPalLayerSettings::DirSymbDraw ).toBool();
1093  }
1094 
1095  if ( addDirSymb )
1096  {
1097 
1098  if ( dataDefinedValues.contains( QgsPalLayerSettings::DirSymbLeft ) )
1099  {
1100  leftDirSymb = dataDefinedValues.value( QgsPalLayerSettings::DirSymbLeft ).toString();
1101  }
1102  if ( dataDefinedValues.contains( QgsPalLayerSettings::DirSymbRight ) )
1103  {
1104  rightDirSymb = dataDefinedValues.value( QgsPalLayerSettings::DirSymbRight ).toString();
1105  }
1106 
1107  if ( dataDefinedValues.contains( QgsPalLayerSettings::DirSymbPlacement ) )
1108  {
1109  placeDirSymb = static_cast< QgsPalLayerSettings::DirectionSymbols >( dataDefinedValues.value( QgsPalLayerSettings::DirSymbPlacement ).toInt() );
1110  }
1111 
1112  }
1113 
1114  }
1115  else // called externally with passed-in feature, evaluate data defined
1116  {
1118  wrapchr = mDataDefinedProperties.value( QgsPalLayerSettings::MultiLineWrapChar, rc->expressionContext(), wrapchr ).toString();
1119 
1120  rc->expressionContext().setOriginalValueVariable( evalAutoWrapLength );
1121  evalAutoWrapLength = mDataDefinedProperties.value( QgsPalLayerSettings::AutoWrapLength, rc->expressionContext(), evalAutoWrapLength ).toInt();
1122 
1123  rc->expressionContext().setOriginalValueVariable( multilineH );
1124  multilineH = mDataDefinedProperties.valueAsDouble( QgsPalLayerSettings::MultiLineHeight, rc->expressionContext(), multilineH );
1125 
1126  rc->expressionContext().setOriginalValueVariable( addDirSymb );
1127  addDirSymb = mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::DirSymbDraw, rc->expressionContext(), addDirSymb );
1128 
1129  if ( addDirSymb ) // don't do extra evaluations if not adding a direction symbol
1130  {
1131  rc->expressionContext().setOriginalValueVariable( leftDirSymb );
1132  leftDirSymb = mDataDefinedProperties.value( QgsPalLayerSettings::DirSymbLeft, rc->expressionContext(), leftDirSymb ).toString();
1133 
1134  rc->expressionContext().setOriginalValueVariable( rightDirSymb );
1135  rightDirSymb = mDataDefinedProperties.value( QgsPalLayerSettings::DirSymbLeft, rc->expressionContext(), rightDirSymb ).toString();
1136 
1137  rc->expressionContext().setOriginalValueVariable( static_cast< int >( placeDirSymb ) );
1138  placeDirSymb = static_cast< QgsPalLayerSettings::DirectionSymbols >( mDataDefinedProperties.valueAsInt( QgsPalLayerSettings::DirSymbPlacement, rc->expressionContext(), placeDirSymb ) );
1139  }
1140 
1141  }
1142 
1143  if ( wrapchr.isEmpty() )
1144  {
1145  wrapchr = QStringLiteral( "\n" ); // default to new line delimiter
1146  }
1147 
1148  //consider the space needed for the direction symbol
1149  if ( addDirSymb && placement == QgsPalLayerSettings::Line
1150  && ( !leftDirSymb.isEmpty() || !rightDirSymb.isEmpty() ) )
1151  {
1152  QString dirSym = leftDirSymb;
1153 
1154  if ( fm->width( rightDirSymb ) > fm->width( dirSym ) )
1155  dirSym = rightDirSymb;
1156 
1157  if ( placeDirSymb == QgsPalLayerSettings::SymbolLeftRight )
1158  {
1159  text.append( dirSym );
1160  }
1161  else
1162  {
1163  text.prepend( dirSym + QStringLiteral( "\n" ) ); // SymbolAbove or SymbolBelow
1164  }
1165  }
1166 
1167  double w = 0.0, h = 0.0;
1168  QStringList multiLineSplit = QgsPalLabeling::splitToLines( text, wrapchr, evalAutoWrapLength, useMaxLineLengthForAutoWrap );
1169  int lines = multiLineSplit.size();
1170 
1171  double labelHeight = fm->ascent() + fm->descent(); // ignore +1 for baseline
1172 
1173  h += fm->height() + static_cast< double >( ( lines - 1 ) * labelHeight * multilineH );
1174 
1175  for ( int i = 0; i < lines; ++i )
1176  {
1177  double width = fm->width( multiLineSplit.at( i ) );
1178  if ( width > w )
1179  {
1180  w = width;
1181  }
1182  }
1183 
1184 #if 0 // XXX strk
1185  QgsPointXY ptSize = xform->toMapCoordinatesF( w, h );
1186  labelX = std::fabs( ptSize.x() - ptZero.x() );
1187  labelY = std::fabs( ptSize.y() - ptZero.y() );
1188 #else
1189  double uPP = xform->mapUnitsPerPixel();
1190  labelX = w * uPP;
1191  labelY = h * uPP;
1192 #endif
1193 }
1194 
1196 {
1197  // either used in QgsPalLabeling (palLayer is set) or in QgsLabelingEngine (labelFeature is set)
1198  Q_ASSERT( labelFeature );
1199 
1200  QVariant exprVal; // value() is repeatedly nulled on data defined evaluation and replaced when successful
1201  mCurFeat = &f;
1202 
1203  // data defined is obstacle? calculate this first, to avoid wasting time working with obstacles we don't require
1204  bool isObstacle = mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::IsObstacle, context.expressionContext(), obstacle ); // default to layer default
1205 
1206  if ( !drawLabels )
1207  {
1208  if ( isObstacle )
1209  {
1210  registerObstacleFeature( f, context, labelFeature, obstacleGeometry );
1211  }
1212  return;
1213  }
1214 
1215 // mCurFields = &layer->fields();
1216 
1217  // store data defined-derived values for later adding to label feature for use during rendering
1218  dataDefinedValues.clear();
1219 
1220  // data defined show label? defaults to show label if not set
1221  context.expressionContext().setOriginalValueVariable( true );
1222  if ( !mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::Show, context.expressionContext(), true ) )
1223  {
1224  return;
1225  }
1226 
1227  // data defined scale visibility?
1228  bool useScaleVisibility = mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::ScaleVisibility, context.expressionContext(), scaleVisibility );
1229 
1230  if ( useScaleVisibility )
1231  {
1232  // data defined min scale?
1234  double maxScale = mDataDefinedProperties.valueAsDouble( QgsPalLayerSettings::MaximumScale, context.expressionContext(), maximumScale );
1235 
1236  // scales closer than 1:1
1237  if ( maxScale < 0 )
1238  {
1239  maxScale = 1 / std::fabs( maxScale );
1240  }
1241 
1242  if ( !qgsDoubleNear( maxScale, 0.0 ) && context.rendererScale() < maxScale )
1243  {
1244  return;
1245  }
1246 
1247  // data defined max scale?
1249  double minScale = mDataDefinedProperties.valueAsDouble( QgsPalLayerSettings::MinimumScale, context.expressionContext(), minimumScale );
1250 
1251  // scales closer than 1:1
1252  if ( minScale < 0 )
1253  {
1254  minScale = 1 / std::fabs( minScale );
1255  }
1256 
1257  if ( !qgsDoubleNear( minScale, 0.0 ) && context.rendererScale() > minScale )
1258  {
1259  return;
1260  }
1261  }
1262 
1263  QFont labelFont = mFormat.font();
1264  // labelFont will be added to label feature for use during label painting
1265 
1266  // data defined font units?
1267  QgsUnitTypes::RenderUnit fontunits = mFormat.sizeUnit();
1268  exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::FontSizeUnit, context.expressionContext() );
1269  if ( exprVal.isValid() )
1270  {
1271  QString units = exprVal.toString();
1272  if ( !units.isEmpty() )
1273  {
1274  bool ok;
1276  if ( ok )
1277  fontunits = res;
1278  }
1279  }
1280 
1281  //data defined label size?
1282  context.expressionContext().setOriginalValueVariable( mFormat.size() );
1283  double fontSize = mDataDefinedProperties.valueAsDouble( QgsPalLayerSettings::Size, context.expressionContext(), mFormat.size() );
1284  if ( fontSize <= 0.0 )
1285  {
1286  return;
1287  }
1288 
1289  int fontPixelSize = QgsTextRenderer::sizeToPixel( fontSize, context, fontunits, mFormat.sizeMapUnitScale() );
1290  // don't try to show font sizes less than 1 pixel (Qt complains)
1291  if ( fontPixelSize < 1 )
1292  {
1293  return;
1294  }
1295  labelFont.setPixelSize( fontPixelSize );
1296 
1297  // NOTE: labelFont now always has pixelSize set, so pointSize or pointSizeF might return -1
1298 
1299  // defined 'minimum/maximum pixel font size'?
1300  if ( fontunits == QgsUnitTypes::RenderMapUnits )
1301  {
1302  if ( mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::FontLimitPixel, context.expressionContext(), fontLimitPixelSize ) )
1303  {
1304  int fontMinPixel = mDataDefinedProperties.valueAsInt( QgsPalLayerSettings::FontMinPixel, context.expressionContext(), fontMinPixelSize );
1305  int fontMaxPixel = mDataDefinedProperties.valueAsInt( QgsPalLayerSettings::FontMaxPixel, context.expressionContext(), fontMaxPixelSize );
1306 
1307  if ( fontMinPixel > labelFont.pixelSize() || labelFont.pixelSize() > fontMaxPixel )
1308  {
1309  return;
1310  }
1311  }
1312  }
1313 
1314  // NOTE: the following parsing functions calculate and store any data defined values for later use in QgsPalLabeling::drawLabeling
1315  // this is done to provide clarity, and because such parsing is not directly related to PAL feature registration calculations
1316 
1317  // calculate rest of font attributes and store any data defined values
1318  // this is done here for later use in making label backgrounds part of collision management (when implemented)
1319  labelFont.setCapitalization( QFont::MixedCase ); // reset this - we don't use QFont's handling as it breaks with curved labels
1320  parseTextStyle( labelFont, fontunits, context );
1321  parseTextFormatting( context );
1322  parseTextBuffer( context );
1323  parseShapeBackground( context );
1324  parseDropShadow( context );
1325 
1326  QString labelText;
1327 
1328  // Check to see if we are a expression string.
1329  if ( isExpression )
1330  {
1332  if ( exp->hasParserError() )
1333  {
1334  QgsDebugMsgLevel( QStringLiteral( "Expression parser error:%1" ).arg( exp->parserErrorString() ), 4 );
1335  return;
1336  }
1337 
1338  QVariant result = exp->evaluate( &context.expressionContext() ); // expression prepared in QgsPalLabeling::prepareLayer()
1339  if ( exp->hasEvalError() )
1340  {
1341  QgsDebugMsgLevel( QStringLiteral( "Expression parser eval error:%1" ).arg( exp->evalErrorString() ), 4 );
1342  return;
1343  }
1344  labelText = result.isNull() ? QString() : result.toString();
1345  }
1346  else
1347  {
1348  const QVariant &v = f.attribute( fieldIndex );
1349  labelText = v.isNull() ? QString() : v.toString();
1350  }
1351 
1352  // apply text replacements
1353  if ( useSubstitutions )
1354  {
1355  labelText = substitutions.process( labelText );
1356  }
1357 
1358  // apply capitalization
1360  // maintain API - capitalization may have been set in textFont
1361  if ( mFormat.font().capitalization() != QFont::MixedCase )
1362  {
1363  capitalization = static_cast< QgsStringUtils::Capitalization >( mFormat.font().capitalization() );
1364  }
1365  // data defined font capitalization?
1366  exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::FontCase, context.expressionContext() );
1367  if ( exprVal.isValid() )
1368  {
1369  QString fcase = exprVal.toString().trimmed();
1370  QgsDebugMsgLevel( QStringLiteral( "exprVal FontCase:%1" ).arg( fcase ), 4 );
1371 
1372  if ( !fcase.isEmpty() )
1373  {
1374  if ( fcase.compare( QLatin1String( "NoChange" ), Qt::CaseInsensitive ) == 0 )
1375  {
1376  capitalization = QgsStringUtils::MixedCase;
1377  }
1378  else if ( fcase.compare( QLatin1String( "Upper" ), Qt::CaseInsensitive ) == 0 )
1379  {
1380  capitalization = QgsStringUtils::AllUppercase;
1381  }
1382  else if ( fcase.compare( QLatin1String( "Lower" ), Qt::CaseInsensitive ) == 0 )
1383  {
1384  capitalization = QgsStringUtils::AllLowercase;
1385  }
1386  else if ( fcase.compare( QLatin1String( "Capitalize" ), Qt::CaseInsensitive ) == 0 )
1387  {
1389  }
1390  }
1391  }
1392  labelText = QgsStringUtils::capitalize( labelText, capitalization );
1393 
1394  // format number if label text is coercible to a number
1395  if ( mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::NumFormat, context.expressionContext(), formatNumbers ) )
1396  {
1397  // data defined decimal places?
1398  int decimalPlaces = mDataDefinedProperties.valueAsInt( QgsPalLayerSettings::NumDecimals, context.expressionContext(), decimals );
1399  if ( decimalPlaces <= 0 ) // needs to be positive
1400  decimalPlaces = decimals;
1401 
1402  // data defined plus sign?
1403  bool signPlus = mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::NumPlusSign, context.expressionContext(), plusSign );
1404 
1405  QVariant textV( labelText );
1406  bool ok;
1407  double d = textV.toDouble( &ok );
1408  if ( ok )
1409  {
1410  QString numberFormat;
1411  if ( d > 0 && signPlus )
1412  {
1413  numberFormat.append( '+' );
1414  }
1415  numberFormat.append( "%1" );
1416  labelText = numberFormat.arg( d, 0, 'f', decimalPlaces );
1417  }
1418  }
1419 
1420  // NOTE: this should come AFTER any option that affects font metrics
1421  std::unique_ptr<QFontMetricsF> labelFontMetrics( new QFontMetricsF( labelFont ) );
1422  double labelX, labelY; // will receive label size
1423  calculateLabelSize( labelFontMetrics.get(), labelText, labelX, labelY, mCurFeat, &context );
1424 
1425 
1426  // maximum angle between curved label characters (hardcoded defaults used in QGIS <2.0)
1427  //
1428  double maxcharanglein = 20.0; // range 20.0-60.0
1429  double maxcharangleout = -20.0; // range 20.0-95.0
1430 
1432  {
1433  maxcharanglein = maxCurvedCharAngleIn;
1434  maxcharangleout = maxCurvedCharAngleOut;
1435 
1436  //data defined maximum angle between curved label characters?
1437  exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::CurvedCharAngleInOut, context.expressionContext() );
1438  if ( exprVal.isValid() )
1439  {
1440  QString ptstr = exprVal.toString().trimmed();
1441  QgsDebugMsgLevel( QStringLiteral( "exprVal CurvedCharAngleInOut:%1" ).arg( ptstr ), 4 );
1442 
1443  if ( !ptstr.isEmpty() )
1444  {
1445  QPointF maxcharanglePt = QgsSymbolLayerUtils::decodePoint( ptstr );
1446  maxcharanglein = qBound( 20.0, static_cast< double >( maxcharanglePt.x() ), 60.0 );
1447  maxcharangleout = qBound( 20.0, static_cast< double >( maxcharanglePt.y() ), 95.0 );
1448  }
1449  }
1450  // make sure maxcharangleout is always negative
1451  maxcharangleout = -( std::fabs( maxcharangleout ) );
1452  }
1453 
1454  // data defined centroid whole or clipped?
1455  bool wholeCentroid = centroidWhole;
1456  exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::CentroidWhole, context.expressionContext() );
1457  if ( exprVal.isValid() )
1458  {
1459  QString str = exprVal.toString().trimmed();
1460  QgsDebugMsgLevel( QStringLiteral( "exprVal CentroidWhole:%1" ).arg( str ), 4 );
1461 
1462  if ( !str.isEmpty() )
1463  {
1464  if ( str.compare( QLatin1String( "Visible" ), Qt::CaseInsensitive ) == 0 )
1465  {
1466  wholeCentroid = false;
1467  }
1468  else if ( str.compare( QLatin1String( "Whole" ), Qt::CaseInsensitive ) == 0 )
1469  {
1470  wholeCentroid = true;
1471  }
1472  }
1473  }
1474 
1475  QgsGeometry geom = f.geometry();
1476  if ( geom.isNull() )
1477  {
1478  return;
1479  }
1480 
1481  // simplify?
1482  const QgsVectorSimplifyMethod &simplifyMethod = context.vectorSimplifyMethod();
1483  std::unique_ptr<QgsGeometry> scopedClonedGeom;
1484  if ( simplifyMethod.simplifyHints() != QgsVectorSimplifyMethod::NoSimplification && simplifyMethod.forceLocalOptimization() )
1485  {
1486  int simplifyHints = simplifyMethod.simplifyHints() | QgsMapToPixelSimplifier::SimplifyEnvelope;
1488  QgsGeometry g = geom;
1489  QgsMapToPixelSimplifier simplifier( simplifyHints, simplifyMethod.tolerance(), simplifyAlgorithm );
1490  geom = simplifier.simplify( geom );
1491  }
1492 
1493  // whether we're going to create a centroid for polygon
1494  bool centroidPoly = ( ( placement == QgsPalLayerSettings::AroundPoint
1496  && geom.type() == QgsWkbTypes::PolygonGeometry );
1497 
1498  // CLIP the geometry if it is bigger than the extent
1499  // don't clip if centroid is requested for whole feature
1500  bool doClip = false;
1501  if ( !centroidPoly || !wholeCentroid )
1502  {
1503  doClip = true;
1504  }
1505 
1506  // if using fitInPolygonOnly option, generate the permissible zone (must happen before geometry is modified - e.g.,
1507  // as a result of using perimeter based labeling and the geometry is converted to a boundary)
1508  QgsGeometry permissibleZone;
1510  {
1511  permissibleZone = geom;
1512  if ( QgsPalLabeling::geometryRequiresPreparation( permissibleZone, context, ct, doClip ? extentGeom : QgsGeometry(), mergeLines ) )
1513  {
1514  permissibleZone = QgsPalLabeling::prepareGeometry( permissibleZone, context, ct, doClip ? extentGeom : QgsGeometry(), mergeLines );
1515  }
1516  }
1517 
1518  // if using perimeter based labeling for polygons, get the polygon's
1519  // linear boundary and use that for the label geometry
1520  if ( ( geom.type() == QgsWkbTypes::PolygonGeometry )
1521  && ( placement == Line || placement == PerimeterCurved ) )
1522  {
1523  geom = QgsGeometry( geom.constGet()->boundary() );
1524  }
1525 
1526  geos::unique_ptr geos_geom_clone;
1527  if ( QgsPalLabeling::geometryRequiresPreparation( geom, context, ct, doClip ? extentGeom : QgsGeometry(), mergeLines ) )
1528  {
1529  geom = QgsPalLabeling::prepareGeometry( geom, context, ct, doClip ? extentGeom : QgsGeometry(), mergeLines );
1530 
1531  if ( geom.isNull() )
1532  return;
1533  }
1534  geos_geom_clone = QgsGeos::asGeos( geom );
1535 
1536  if ( isObstacle )
1537  {
1538  if ( !obstacleGeometry.isNull() && QgsPalLabeling::geometryRequiresPreparation( obstacleGeometry, context, ct, doClip ? extentGeom : QgsGeometry(), mergeLines ) )
1539  {
1540  obstacleGeometry = QgsGeometry( QgsPalLabeling::prepareGeometry( obstacleGeometry, context, ct, doClip ? extentGeom : QgsGeometry(), mergeLines ) );
1541  }
1542  }
1543 
1544  if ( minFeatureSize > 0 && !checkMinimumSizeMM( context, geom, minFeatureSize ) )
1545  return;
1546 
1547  if ( !geos_geom_clone )
1548  return; // invalid geometry
1549 
1550  // likelihood exists label will be registered with PAL and may be drawn
1551  // check if max number of features to label (already registered with PAL) has been reached
1552  // Debug output at end of QgsPalLabeling::drawLabeling(), when deleting temp geometries
1553  if ( limitNumLabels )
1554  {
1555  if ( !maxNumLabels )
1556  {
1557  return;
1558  }
1559  if ( mFeatsRegPal >= maxNumLabels )
1560  {
1561  return;
1562  }
1563 
1564  int divNum = static_cast< int >( ( static_cast< double >( mFeaturesToLabel ) / maxNumLabels ) + 0.5 ); // NOLINT
1565  if ( divNum && ( mFeatsRegPal == static_cast< int >( mFeatsSendingToPal / divNum ) ) )
1566  {
1567  mFeatsSendingToPal += 1;
1568  if ( divNum && mFeatsSendingToPal % divNum )
1569  {
1570  return;
1571  }
1572  }
1573  }
1574 
1575  geos::unique_ptr geosObstacleGeomClone;
1576  if ( isObstacle && !obstacleGeometry.isNull() )
1577  {
1578  geosObstacleGeomClone = QgsGeos::asGeos( obstacleGeometry );
1579  }
1580 
1581 
1582  //data defined position / alignment / rotation?
1583  bool dataDefinedPosition = false;
1584  bool layerDefinedRotation = false;
1585  bool dataDefinedRotation = false;
1586  double xPos = 0.0, yPos = 0.0, angle = 0.0;
1587  bool ddXPos = false, ddYPos = false;
1588  double quadOffsetX = 0.0, quadOffsetY = 0.0;
1589  double offsetX = 0.0, offsetY = 0.0;
1590 
1591  //data defined quadrant offset?
1592  bool ddFixedQuad = false;
1593  QuadrantPosition quadOff = quadOffset;
1594  context.expressionContext().setOriginalValueVariable( static_cast< int >( quadOff ) );
1595  exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::OffsetQuad, context.expressionContext() );
1596  if ( exprVal.isValid() )
1597  {
1598  bool ok;
1599  int quadInt = exprVal.toInt( &ok );
1600  QgsDebugMsgLevel( QStringLiteral( "exprVal OffsetQuad:%1" ).arg( quadInt ), 4 );
1601  if ( ok && 0 <= quadInt && quadInt <= 8 )
1602  {
1603  quadOff = static_cast< QuadrantPosition >( quadInt );
1604  ddFixedQuad = true;
1605  }
1606  }
1607 
1608  // adjust quadrant offset of labels
1609  switch ( quadOff )
1610  {
1611  case QuadrantAboveLeft:
1612  quadOffsetX = -1.0;
1613  quadOffsetY = 1.0;
1614  break;
1615  case QuadrantAbove:
1616  quadOffsetX = 0.0;
1617  quadOffsetY = 1.0;
1618  break;
1619  case QuadrantAboveRight:
1620  quadOffsetX = 1.0;
1621  quadOffsetY = 1.0;
1622  break;
1623  case QuadrantLeft:
1624  quadOffsetX = -1.0;
1625  quadOffsetY = 0.0;
1626  break;
1627  case QuadrantRight:
1628  quadOffsetX = 1.0;
1629  quadOffsetY = 0.0;
1630  break;
1631  case QuadrantBelowLeft:
1632  quadOffsetX = -1.0;
1633  quadOffsetY = -1.0;
1634  break;
1635  case QuadrantBelow:
1636  quadOffsetX = 0.0;
1637  quadOffsetY = -1.0;
1638  break;
1639  case QuadrantBelowRight:
1640  quadOffsetX = 1.0;
1641  quadOffsetY = -1.0;
1642  break;
1643  case QuadrantOver:
1644  default:
1645  break;
1646  }
1647 
1648  //data defined label offset?
1649  double xOff = xOffset;
1650  double yOff = yOffset;
1652  exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::OffsetXY, context.expressionContext() );
1653  if ( exprVal.isValid() )
1654  {
1655  QString ptstr = exprVal.toString().trimmed();
1656  QgsDebugMsgLevel( QStringLiteral( "exprVal OffsetXY:%1" ).arg( ptstr ), 4 );
1657 
1658  if ( !ptstr.isEmpty() )
1659  {
1660  QPointF ddOffPt = QgsSymbolLayerUtils::decodePoint( ptstr );
1661  xOff = ddOffPt.x();
1662  yOff = ddOffPt.y();
1663  }
1664  }
1665 
1666  // data defined label offset units?
1668  exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::OffsetUnits, context.expressionContext() );
1669  if ( exprVal.isValid() )
1670  {
1671  QString units = exprVal.toString().trimmed();
1672  QgsDebugMsgLevel( QStringLiteral( "exprVal OffsetUnits:%1" ).arg( units ), 4 );
1673  if ( !units.isEmpty() )
1674  {
1675  bool ok = false;
1676  QgsUnitTypes::RenderUnit decodedUnits = QgsUnitTypes::decodeRenderUnit( units, &ok );
1677  if ( ok )
1678  {
1679  offUnit = decodedUnits;
1680  }
1681  }
1682  }
1683 
1684  // adjust offset of labels to match chosen unit and map scale
1685  // offsets match those of symbology: -x = left, -y = up
1686  offsetX = context.convertToMapUnits( xOff, offUnit, labelOffsetMapUnitScale );
1687  // must be negative to match symbology offset direction
1688  offsetY = context.convertToMapUnits( -yOff, offUnit, labelOffsetMapUnitScale );
1689 
1690  // layer defined rotation?
1691  // only rotate non-pinned OverPoint placements until other placements are supported in pal::Feature
1693  {
1694  layerDefinedRotation = true;
1695  angle = ( 360 - angleOffset ) * M_PI / 180; // convert to radians counterclockwise
1696  }
1697 
1698  const QgsMapToPixel &m2p = context.mapToPixel();
1699  //data defined rotation?
1701  exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::LabelRotation, context.expressionContext() );
1702  if ( exprVal.isValid() )
1703  {
1704  bool ok;
1705  double rotD = exprVal.toDouble( &ok );
1706  QgsDebugMsgLevel( QStringLiteral( "exprVal Rotation:%1" ).arg( rotD ), 4 );
1707  if ( ok )
1708  {
1709  dataDefinedRotation = true;
1710  // TODO: add setting to disable having data defined rotation follow
1711  // map rotation ?
1712  rotD += m2p.mapRotation();
1713  angle = ( 360 - rotD ) * M_PI / 180.0;
1714  }
1715  }
1716 
1717  exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::PositionX, context.expressionContext() );
1718  if ( exprVal.isValid() )
1719  {
1720  if ( !exprVal.isNull() )
1721  xPos = exprVal.toDouble( &ddXPos );
1722  QgsDebugMsgLevel( QStringLiteral( "exprVal PositionX:%1" ).arg( xPos ), 4 );
1723 
1724  exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::PositionY, context.expressionContext() );
1725  if ( exprVal.isValid() )
1726  {
1727  //data defined position. But field values could be NULL -> positions will be generated by PAL
1728  if ( !exprVal.isNull() )
1729  yPos = exprVal.toDouble( &ddYPos );
1730  QgsDebugMsgLevel( QStringLiteral( "exprVal PositionY:%1" ).arg( yPos ), 4 );
1731 
1732  if ( ddXPos && ddYPos )
1733  {
1734  dataDefinedPosition = true;
1735  // layer rotation set, but don't rotate pinned labels unless data defined
1736  if ( layerDefinedRotation && !dataDefinedRotation )
1737  {
1738  angle = 0.0;
1739  }
1740 
1741  //x/y shift in case of alignment
1742  double xdiff = 0.0;
1743  double ydiff = 0.0;
1744 
1745  //horizontal alignment
1746  exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Hali, context.expressionContext() );
1747  if ( exprVal.isValid() )
1748  {
1749  QString haliString = exprVal.toString();
1750  QgsDebugMsgLevel( QStringLiteral( "exprVal Hali:%1" ).arg( haliString ), 4 );
1751  if ( haliString.compare( QLatin1String( "Center" ), Qt::CaseInsensitive ) == 0 )
1752  {
1753  xdiff -= labelX / 2.0;
1754  }
1755  else if ( haliString.compare( QLatin1String( "Right" ), Qt::CaseInsensitive ) == 0 )
1756  {
1757  xdiff -= labelX;
1758  }
1759  }
1760 
1761  //vertical alignment
1762  exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Vali, context.expressionContext() );
1763  if ( exprVal.isValid() )
1764  {
1765  QString valiString = exprVal.toString();
1766  QgsDebugMsgLevel( QStringLiteral( "exprVal Vali:%1" ).arg( valiString ), 4 );
1767 
1768  if ( valiString.compare( QLatin1String( "Bottom" ), Qt::CaseInsensitive ) != 0 )
1769  {
1770  if ( valiString.compare( QLatin1String( "Top" ), Qt::CaseInsensitive ) == 0 )
1771  {
1772  ydiff -= labelY;
1773  }
1774  else
1775  {
1776  double descentRatio = labelFontMetrics->descent() / labelFontMetrics->height();
1777  if ( valiString.compare( QLatin1String( "Base" ), Qt::CaseInsensitive ) == 0 )
1778  {
1779  ydiff -= labelY * descentRatio;
1780  }
1781  else //'Cap' or 'Half'
1782  {
1783  double capHeightRatio = ( labelFontMetrics->boundingRect( 'H' ).height() + 1 + labelFontMetrics->descent() ) / labelFontMetrics->height();
1784  ydiff -= labelY * capHeightRatio;
1785  if ( valiString.compare( QLatin1String( "Half" ), Qt::CaseInsensitive ) == 0 )
1786  {
1787  ydiff += labelY * ( capHeightRatio - descentRatio ) / 2.0;
1788  }
1789  }
1790  }
1791  }
1792  }
1793 
1794  if ( dataDefinedRotation )
1795  {
1796  //adjust xdiff and ydiff because the hali/vali point needs to be the rotation center
1797  double xd = xdiff * std::cos( angle ) - ydiff * std::sin( angle );
1798  double yd = xdiff * std::sin( angle ) + ydiff * std::cos( angle );
1799  xdiff = xd;
1800  ydiff = yd;
1801  }
1802 
1803  //project xPos and yPos from layer to map CRS, handle rotation
1804  QgsGeometry ddPoint( new QgsPoint( xPos, yPos ) );
1805  if ( QgsPalLabeling::geometryRequiresPreparation( ddPoint, context, ct ) )
1806  {
1807  ddPoint = QgsPalLabeling::prepareGeometry( ddPoint, context, ct );
1808  xPos = static_cast< const QgsPoint * >( ddPoint.constGet() )->x();
1809  yPos = static_cast< const QgsPoint * >( ddPoint.constGet() )->y();
1810  }
1811 
1812  xPos += xdiff;
1813  yPos += ydiff;
1814  }
1815  else
1816  {
1817  // only rotate non-pinned OverPoint placements until other placements are supported in pal::Feature
1818  if ( dataDefinedRotation && placement != QgsPalLayerSettings::OverPoint )
1819  {
1820  angle = 0.0;
1821  }
1822  }
1823  }
1824  }
1825 
1826  // data defined always show?
1827  bool alwaysShow = mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::AlwaysShow, context.expressionContext(), false );
1828 
1829  // set repeat distance
1830  // data defined repeat distance?
1832  double repeatDist = mDataDefinedProperties.valueAsDouble( QgsPalLayerSettings::RepeatDistance, context.expressionContext(), repeatDistance );
1833 
1834  // data defined label-repeat distance units?
1836  exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::RepeatDistanceUnit, context.expressionContext() );
1837  if ( exprVal.isValid() )
1838  {
1839  QString units = exprVal.toString().trimmed();
1840  QgsDebugMsgLevel( QStringLiteral( "exprVal RepeatDistanceUnits:%1" ).arg( units ), 4 );
1841  if ( !units.isEmpty() )
1842  {
1843  bool ok = false;
1844  QgsUnitTypes::RenderUnit decodedUnits = QgsUnitTypes::decodeRenderUnit( units, &ok );
1845  if ( ok )
1846  {
1847  repeatUnits = decodedUnits;
1848  }
1849  }
1850  }
1851 
1852  if ( !qgsDoubleNear( repeatDist, 0.0 ) )
1853  {
1854  if ( repeatUnits != QgsUnitTypes::RenderMapUnits )
1855  {
1856  repeatDist = context.convertToMapUnits( repeatDist, repeatUnits, repeatDistanceMapUnitScale );
1857  }
1858  }
1859 
1860  // feature to the layer
1861  QgsTextLabelFeature *lf = new QgsTextLabelFeature( f.id(), std::move( geos_geom_clone ), QSizeF( labelX, labelY ) );
1862  mFeatsRegPal++;
1863 
1864  *labelFeature = lf;
1865  ( *labelFeature )->setHasFixedPosition( dataDefinedPosition );
1866  ( *labelFeature )->setFixedPosition( QgsPointXY( xPos, yPos ) );
1867  // use layer-level defined rotation, but not if position fixed
1868  ( *labelFeature )->setHasFixedAngle( dataDefinedRotation || ( !dataDefinedPosition && !qgsDoubleNear( angle, 0.0 ) ) );
1869  ( *labelFeature )->setFixedAngle( angle );
1870  ( *labelFeature )->setQuadOffset( QPointF( quadOffsetX, quadOffsetY ) );
1871  ( *labelFeature )->setPositionOffset( QgsPointXY( offsetX, offsetY ) );
1872  ( *labelFeature )->setOffsetType( offsetType );
1873  ( *labelFeature )->setAlwaysShow( alwaysShow );
1874  ( *labelFeature )->setRepeatDistance( repeatDist );
1875  ( *labelFeature )->setLabelText( labelText );
1876  ( *labelFeature )->setPermissibleZone( permissibleZone );
1877  if ( geosObstacleGeomClone )
1878  {
1879  ( *labelFeature )->setObstacleGeometry( std::move( geosObstacleGeomClone ) );
1880 
1881  if ( geom.type() == QgsWkbTypes::PointGeometry )
1882  {
1883  //register symbol size
1884  ( *labelFeature )->setSymbolSize( QSizeF( obstacleGeometry.boundingBox().width(),
1885  obstacleGeometry.boundingBox().height() ) );
1886  }
1887  }
1888 
1889  //set label's visual margin so that top visual margin is the leading, and bottom margin is the font's descent
1890  //this makes labels align to the font's baseline or highest character
1891  double topMargin = std::max( 0.25 * labelFontMetrics->ascent(), 0.0 );
1892  double bottomMargin = 1.0 + labelFontMetrics->descent();
1893  QgsMargins vm( 0.0, topMargin, 0.0, bottomMargin );
1894  vm *= xform->mapUnitsPerPixel();
1895  ( *labelFeature )->setVisualMargin( vm );
1896 
1897  // store the label's calculated font for later use during painting
1898  QgsDebugMsgLevel( QStringLiteral( "PAL font stored definedFont: %1, Style: %2" ).arg( labelFont.toString(), labelFont.styleName() ), 4 );
1899  lf->setDefinedFont( labelFont );
1900 
1901  // TODO: only for placement which needs character info
1902  // account for any data defined font metrics adjustments
1904  labelFontMetrics.get(), xform, maxcharanglein, maxcharangleout );
1905  // for labelFeature the LabelInfo is passed to feat when it is registered
1906 
1907  // TODO: allow layer-wide feature dist in PAL...?
1908 
1909  // data defined label-feature distance?
1911  double distance = mDataDefinedProperties.valueAsDouble( QgsPalLayerSettings::LabelDistance, context.expressionContext(), dist );
1912 
1913  // data defined label-feature distance units?
1915  exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::DistanceUnits, context.expressionContext() );
1916  if ( exprVal.isValid() )
1917  {
1918  QString units = exprVal.toString().trimmed();
1919  QgsDebugMsgLevel( QStringLiteral( "exprVal DistanceUnits:%1" ).arg( units ), 4 );
1920  if ( !units.isEmpty() )
1921  {
1922  bool ok = false;
1923  QgsUnitTypes::RenderUnit decodedUnits = QgsUnitTypes::decodeRenderUnit( units, &ok );
1924  if ( ok )
1925  {
1926  distUnit = decodedUnits;
1927  }
1928  }
1929  }
1930  distance = context.convertToPainterUnits( distance, distUnit, distMapUnitScale );
1931 
1932  // when using certain placement modes, we force a tiny minimum distance. This ensures that
1933  // candidates are created just offset from a border and avoids candidates being incorrectly flagged as colliding with neighbours
1935  {
1936  distance = ( distance < 0 ? -1 : 1 ) * std::max( std::fabs( distance ), 1.0 );
1937  }
1938 
1939  if ( !qgsDoubleNear( distance, 0.0 ) )
1940  {
1941  double d = ptOne.distance( ptZero ) * distance;
1942  ( *labelFeature )->setDistLabel( d );
1943  }
1944 
1945  if ( ddFixedQuad )
1946  {
1947  ( *labelFeature )->setHasFixedQuadrant( true );
1948  }
1949 
1950  pal::LineArrangementFlags featureArrangementFlags = static_cast< pal::LineArrangementFlags >( placementFlags );
1952  const QString dataDefinedLineArrangement = mDataDefinedProperties.valueAsString( QgsPalLayerSettings::LinePlacementOptions, context.expressionContext() );
1953  if ( !dataDefinedLineArrangement.isEmpty() )
1954  {
1955  featureArrangementFlags = QgsLabelingUtils::decodeLinePlacementFlags( dataDefinedLineArrangement );
1956  }
1957  ( *labelFeature )->setArrangementFlags( featureArrangementFlags );
1958 
1959  // data defined z-index?
1961  double z = mDataDefinedProperties.valueAsDouble( QgsPalLayerSettings::ZIndex, context.expressionContext(), zIndex );
1962  ( *labelFeature )->setZIndex( z );
1963 
1964  // data defined priority?
1966  exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Priority, context.expressionContext() );
1967  if ( exprVal.isValid() )
1968  {
1969  bool ok;
1970  double priorityD = exprVal.toDouble( &ok );
1971  if ( ok )
1972  {
1973  priorityD = qBound( 0.0, priorityD, 10.0 );
1974  priorityD = 1 - priorityD / 10.0; // convert 0..10 --> 1..0
1975  ( *labelFeature )->setPriority( priorityD );
1976  }
1977  }
1978 
1979  ( *labelFeature )->setIsObstacle( isObstacle );
1980 
1981  double featObstacleFactor = obstacleFactor;
1983  exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::ObstacleFactor, context.expressionContext() );
1984  if ( exprVal.isValid() )
1985  {
1986  bool ok;
1987  double factorD = exprVal.toDouble( &ok );
1988  if ( ok )
1989  {
1990  factorD = qBound( 0.0, factorD, 10.0 );
1991  factorD = factorD / 5.0 + 0.0001; // convert 0 -> 10 to 0.0001 -> 2.0
1992  featObstacleFactor = factorD;
1993  }
1994  }
1995  ( *labelFeature )->setObstacleFactor( featObstacleFactor );
1996 
1997  QVector< QgsPalLayerSettings::PredefinedPointPosition > positionOrder = predefinedPositionOrder;
1998  if ( positionOrder.isEmpty() )
1999  positionOrder = QgsPalLayerSettings::DEFAULT_PLACEMENT_ORDER;
2000 
2002  QString dataDefinedOrder = mDataDefinedProperties.valueAsString( QgsPalLayerSettings::PredefinedPositionOrder, context.expressionContext() );
2003  if ( !dataDefinedOrder.isEmpty() )
2004  {
2005  positionOrder = QgsLabelingUtils::decodePredefinedPositionOrder( dataDefinedOrder );
2006  }
2007  ( *labelFeature )->setPredefinedPositionOrder( positionOrder );
2008 
2009  // add parameters for data defined labeling to label feature
2010  lf->setDataDefinedValues( dataDefinedValues );
2011 }
2012 
2013 void QgsPalLayerSettings::registerObstacleFeature( QgsFeature &f, QgsRenderContext &context, QgsLabelFeature **obstacleFeature, const QgsGeometry &obstacleGeometry )
2014 {
2015  mCurFeat = &f;
2016 
2017  QgsGeometry geom;
2018  if ( !obstacleGeometry.isNull() )
2019  {
2020  geom = obstacleGeometry;
2021  }
2022  else
2023  {
2024  geom = f.geometry();
2025  }
2026 
2027  if ( geom.isNull() )
2028  {
2029  return;
2030  }
2031 
2032  // simplify?
2033  const QgsVectorSimplifyMethod &simplifyMethod = context.vectorSimplifyMethod();
2034  std::unique_ptr<QgsGeometry> scopedClonedGeom;
2035  if ( simplifyMethod.simplifyHints() != QgsVectorSimplifyMethod::NoSimplification && simplifyMethod.forceLocalOptimization() )
2036  {
2037  int simplifyHints = simplifyMethod.simplifyHints() | QgsMapToPixelSimplifier::SimplifyEnvelope;
2039  QgsMapToPixelSimplifier simplifier( simplifyHints, simplifyMethod.tolerance(), simplifyAlgorithm );
2040  geom = simplifier.simplify( geom );
2041  }
2042 
2043  geos::unique_ptr geos_geom_clone;
2044  std::unique_ptr<QgsGeometry> scopedPreparedGeom;
2045 
2047  {
2048  geom = QgsPalLabeling::prepareGeometry( geom, context, ct, extentGeom, mergeLines );
2049  }
2050  geos_geom_clone = QgsGeos::asGeos( geom );
2051 
2052  if ( !geos_geom_clone )
2053  return; // invalid geometry
2054 
2055  // feature to the layer
2056  *obstacleFeature = new QgsLabelFeature( f.id(), std::move( geos_geom_clone ), QSizeF( 0, 0 ) );
2057  ( *obstacleFeature )->setIsObstacle( true );
2058  mFeatsRegPal++;
2059 }
2060 
2061 bool QgsPalLayerSettings::dataDefinedValEval( DataDefinedValueType valType,
2063  QVariant &exprVal, QgsExpressionContext &context, const QVariant &originalValue )
2064 {
2065  if ( !mDataDefinedProperties.isActive( p ) )
2066  return false;
2067 
2068  context.setOriginalValueVariable( originalValue );
2069  exprVal = mDataDefinedProperties.value( p, context );
2070  if ( exprVal.isValid() )
2071  {
2072  switch ( valType )
2073  {
2074  case DDBool:
2075  {
2076  bool bol = exprVal.toBool();
2077  dataDefinedValues.insert( p, QVariant( bol ) );
2078  return true;
2079  }
2080  case DDInt:
2081  {
2082  bool ok;
2083  int size = exprVal.toInt( &ok );
2084 
2085  if ( ok )
2086  {
2087  dataDefinedValues.insert( p, QVariant( size ) );
2088  return true;
2089  }
2090  return false;
2091  }
2092  case DDIntPos:
2093  {
2094  bool ok;
2095  int size = exprVal.toInt( &ok );
2096 
2097  if ( ok && size > 0 )
2098  {
2099  dataDefinedValues.insert( p, QVariant( size ) );
2100  return true;
2101  }
2102  return false;
2103  }
2104  case DDDouble:
2105  {
2106  bool ok;
2107  double size = exprVal.toDouble( &ok );
2108 
2109  if ( ok )
2110  {
2111  dataDefinedValues.insert( p, QVariant( size ) );
2112  return true;
2113  }
2114  return false;
2115  }
2116  case DDDoublePos:
2117  {
2118  bool ok;
2119  double size = exprVal.toDouble( &ok );
2120 
2121  if ( ok && size > 0.0 )
2122  {
2123  dataDefinedValues.insert( p, QVariant( size ) );
2124  return true;
2125  }
2126  return false;
2127  }
2128  case DDRotation180:
2129  {
2130  bool ok;
2131  double rot = exprVal.toDouble( &ok );
2132  if ( ok )
2133  {
2134  if ( rot < -180.0 && rot >= -360 )
2135  {
2136  rot += 360;
2137  }
2138  if ( rot > 180.0 && rot <= 360 )
2139  {
2140  rot -= 360;
2141  }
2142  if ( rot >= -180 && rot <= 180 )
2143  {
2144  dataDefinedValues.insert( p, QVariant( rot ) );
2145  return true;
2146  }
2147  }
2148  return false;
2149  }
2150  case DDOpacity:
2151  {
2152  bool ok;
2153  int size = exprVal.toDouble( &ok );
2154  if ( ok && size >= 0 && size <= 100 )
2155  {
2156  dataDefinedValues.insert( p, QVariant( size ) );
2157  return true;
2158  }
2159  return false;
2160  }
2161  case DDString:
2162  {
2163  QString str = exprVal.toString(); // don't trim whitespace
2164 
2165  dataDefinedValues.insert( p, QVariant( str ) ); // let it stay empty if it is
2166  return true;
2167  }
2168  case DDUnits:
2169  {
2170  QString unitstr = exprVal.toString().trimmed();
2171 
2172  if ( !unitstr.isEmpty() )
2173  {
2174  dataDefinedValues.insert( p, QVariant( static_cast< int >( QgsUnitTypes::decodeRenderUnit( unitstr ) ) ) );
2175  return true;
2176  }
2177  return false;
2178  }
2179  case DDColor:
2180  {
2181  QString colorstr = exprVal.toString().trimmed();
2182  QColor color = QgsSymbolLayerUtils::decodeColor( colorstr );
2183 
2184  if ( color.isValid() )
2185  {
2186  dataDefinedValues.insert( p, QVariant( color ) );
2187  return true;
2188  }
2189  return false;
2190  }
2191  case DDJoinStyle:
2192  {
2193  QString joinstr = exprVal.toString().trimmed();
2194 
2195  if ( !joinstr.isEmpty() )
2196  {
2197  dataDefinedValues.insert( p, QVariant( static_cast< int >( _decodePenJoinStyle( joinstr ) ) ) );
2198  return true;
2199  }
2200  return false;
2201  }
2202  case DDBlendMode:
2203  {
2204  QString blendstr = exprVal.toString().trimmed();
2205 
2206  if ( !blendstr.isEmpty() )
2207  {
2208  dataDefinedValues.insert( p, QVariant( static_cast< int >( QgsSymbolLayerUtils::decodeBlendMode( blendstr ) ) ) );
2209  return true;
2210  }
2211  return false;
2212  }
2213  case DDPointF:
2214  {
2215  QString ptstr = exprVal.toString().trimmed();
2216 
2217  if ( !ptstr.isEmpty() )
2218  {
2219  dataDefinedValues.insert( p, QVariant( QgsSymbolLayerUtils::decodePoint( ptstr ) ) );
2220  return true;
2221  }
2222  return false;
2223  }
2224  case DDSizeF:
2225  {
2226  QString ptstr = exprVal.toString().trimmed();
2227 
2228  if ( !ptstr.isEmpty() )
2229  {
2230  dataDefinedValues.insert( p, QVariant( QgsSymbolLayerUtils::decodeSize( ptstr ) ) );
2231  return true;
2232  }
2233  return false;
2234  }
2235  }
2236  }
2237  return false;
2238 }
2239 
2240 void QgsPalLayerSettings::parseTextStyle( QFont &labelFont,
2241  QgsUnitTypes::RenderUnit fontunits,
2242  QgsRenderContext &context )
2243 {
2244  // NOTE: labelFont already has pixelSize set, so pointSize or pointSizeF might return -1
2245 
2246  QVariant exprVal; // value() is repeatedly nulled on data defined evaluation and replaced when successful
2247 
2248  // Two ways to generate new data defined font:
2249  // 1) Family + [bold] + [italic] (named style is ignored and font is built off of base family)
2250  // 2) Family + named style (bold or italic is ignored)
2251 
2252  // data defined font family?
2253  QString ddFontFamily;
2254  context.expressionContext().setOriginalValueVariable( labelFont.family() );
2255  exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Family, context.expressionContext() );
2256  if ( exprVal.isValid() )
2257  {
2258  QString family = exprVal.toString().trimmed();
2259  QgsDebugMsgLevel( QStringLiteral( "exprVal Font family:%1" ).arg( family ), 4 );
2260 
2261  if ( labelFont.family() != family )
2262  {
2263  // testing for ddFontFamily in QFontDatabase.families() may be slow to do for every feature
2264  // (i.e. don't use QgsFontUtils::fontFamilyMatchOnSystem( family ) here)
2265  if ( QgsFontUtils::fontFamilyOnSystem( family ) )
2266  {
2267  ddFontFamily = family;
2268  }
2269  }
2270  }
2271 
2272  // data defined named font style?
2273  QString ddFontStyle;
2274  exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::FontStyle, context.expressionContext() );
2275  if ( exprVal.isValid() )
2276  {
2277  QString fontstyle = exprVal.toString().trimmed();
2278  QgsDebugMsgLevel( QStringLiteral( "exprVal Font style:%1" ).arg( fontstyle ), 4 );
2279  ddFontStyle = fontstyle;
2280  }
2281 
2282  // data defined bold font style?
2283  context.expressionContext().setOriginalValueVariable( labelFont.bold() );
2284  bool ddBold = mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::Bold, context.expressionContext(), false );
2285 
2286  // data defined italic font style?
2287  context.expressionContext().setOriginalValueVariable( labelFont.italic() );
2288  bool ddItalic = mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::Italic, context.expressionContext(), false );
2289 
2290  // TODO: update when pref for how to resolve missing family (use matching algorithm or just default font) is implemented
2291  // (currently defaults to what has been read in from layer settings)
2292  QFont newFont;
2293  QFont appFont = QApplication::font();
2294  bool newFontBuilt = false;
2295  if ( ddBold || ddItalic )
2296  {
2297  // new font needs built, since existing style needs removed
2298  newFont = QFont( !ddFontFamily.isEmpty() ? ddFontFamily : labelFont.family() );
2299  newFontBuilt = true;
2300  newFont.setBold( ddBold );
2301  newFont.setItalic( ddItalic );
2302  }
2303  else if ( !ddFontStyle.isEmpty()
2304  && ddFontStyle.compare( QLatin1String( "Ignore" ), Qt::CaseInsensitive ) != 0 )
2305  {
2306  if ( !ddFontFamily.isEmpty() )
2307  {
2308  // both family and style are different, build font from database
2309  QFont styledfont = mFontDB.font( ddFontFamily, ddFontStyle, appFont.pointSize() );
2310  if ( appFont != styledfont )
2311  {
2312  newFont = styledfont;
2313  newFontBuilt = true;
2314  }
2315  }
2316 
2317  // update the font face style
2318  QgsFontUtils::updateFontViaStyle( newFontBuilt ? newFont : labelFont, ddFontStyle );
2319  }
2320  else if ( !ddFontFamily.isEmpty() )
2321  {
2322  if ( ddFontStyle.compare( QLatin1String( "Ignore" ), Qt::CaseInsensitive ) != 0 )
2323  {
2324  // just family is different, build font from database
2325  QFont styledfont = mFontDB.font( ddFontFamily, mFormat.namedStyle(), appFont.pointSize() );
2326  if ( appFont != styledfont )
2327  {
2328  newFont = styledfont;
2329  newFontBuilt = true;
2330  }
2331  }
2332  else
2333  {
2334  newFont = QFont( ddFontFamily );
2335  newFontBuilt = true;
2336  }
2337  }
2338 
2339  if ( newFontBuilt )
2340  {
2341  // copy over existing font settings
2342  //newFont = newFont.resolve( labelFont ); // should work, but let's be sure what's being copied
2343  newFont.setPixelSize( labelFont.pixelSize() );
2344  newFont.setUnderline( labelFont.underline() );
2345  newFont.setStrikeOut( labelFont.strikeOut() );
2346  newFont.setWordSpacing( labelFont.wordSpacing() );
2347  newFont.setLetterSpacing( QFont::AbsoluteSpacing, labelFont.letterSpacing() );
2348 
2349  labelFont = newFont;
2350  }
2351 
2352  // data defined word spacing?
2353  double wordspace = labelFont.wordSpacing();
2354  context.expressionContext().setOriginalValueVariable( wordspace );
2355  wordspace = mDataDefinedProperties.valueAsDouble( QgsPalLayerSettings::FontWordSpacing, context.expressionContext(), wordspace );
2356  labelFont.setWordSpacing( context.convertToPainterUnits( wordspace, fontunits, mFormat.sizeMapUnitScale() ) );
2357 
2358  // data defined letter spacing?
2359  double letterspace = labelFont.letterSpacing();
2360  context.expressionContext().setOriginalValueVariable( letterspace );
2361  letterspace = mDataDefinedProperties.valueAsDouble( QgsPalLayerSettings::FontLetterSpacing, context.expressionContext(), letterspace );
2362  labelFont.setLetterSpacing( QFont::AbsoluteSpacing, context.convertToPainterUnits( letterspace, fontunits, mFormat.sizeMapUnitScale() ) );
2363 
2364  // data defined strikeout font style?
2365  if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Strikeout ) )
2366  {
2367  context.expressionContext().setOriginalValueVariable( labelFont.strikeOut() );
2368  bool strikeout = mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::Strikeout, context.expressionContext(), false );
2369  labelFont.setStrikeOut( strikeout );
2370  }
2371 
2372  // data defined underline font style?
2373  if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Underline ) )
2374  {
2375  context.expressionContext().setOriginalValueVariable( labelFont.underline() );
2376  bool underline = mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::Underline, context.expressionContext(), false );
2377  labelFont.setUnderline( underline );
2378  }
2379 
2380  // pass the rest on to QgsPalLabeling::drawLabeling
2381 
2382  // data defined font color?
2383  dataDefinedValEval( DDColor, QgsPalLayerSettings::Color, exprVal, context.expressionContext(), QgsSymbolLayerUtils::encodeColor( mFormat.color() ) );
2384 
2385  // data defined font opacity?
2386  dataDefinedValEval( DDOpacity, QgsPalLayerSettings::FontOpacity, exprVal, context.expressionContext(), mFormat.opacity() * 100 );
2387 
2388  // data defined font blend mode?
2389  dataDefinedValEval( DDBlendMode, QgsPalLayerSettings::FontBlendMode, exprVal, context.expressionContext() );
2390 
2391 }
2392 
2393 void QgsPalLayerSettings::parseTextBuffer( QgsRenderContext &context )
2394 {
2395  QVariant exprVal; // value() is repeatedly nulled on data defined evaluation and replaced when successful
2396 
2397  QgsTextBufferSettings buffer = mFormat.buffer();
2398 
2399  // data defined draw buffer?
2400  bool drawBuffer = mFormat.buffer().enabled();
2401  if ( dataDefinedValEval( DDBool, QgsPalLayerSettings::BufferDraw, exprVal, context.expressionContext(), buffer.enabled() ) )
2402  {
2403  drawBuffer = exprVal.toBool();
2404  }
2405 
2406  if ( !drawBuffer )
2407  {
2408  return;
2409  }
2410 
2411  // data defined buffer size?
2412  double bufrSize = buffer.size();
2413  if ( dataDefinedValEval( DDDoublePos, QgsPalLayerSettings::BufferSize, exprVal, context.expressionContext(), buffer.size() ) )
2414  {
2415  bufrSize = exprVal.toDouble();
2416  }
2417 
2418  // data defined buffer transparency?
2419  double bufferOpacity = buffer.opacity() * 100;
2420  if ( dataDefinedValEval( DDOpacity, QgsPalLayerSettings::BufferOpacity, exprVal, context.expressionContext(), bufferOpacity ) )
2421  {
2422  bufferOpacity = exprVal.toDouble();
2423  }
2424 
2425  drawBuffer = ( drawBuffer && bufrSize > 0.0 && bufferOpacity > 0 );
2426 
2427  if ( !drawBuffer )
2428  {
2429  dataDefinedValues.insert( QgsPalLayerSettings::BufferDraw, QVariant( false ) ); // trigger value
2430  dataDefinedValues.remove( QgsPalLayerSettings::BufferSize );
2431  dataDefinedValues.remove( QgsPalLayerSettings::BufferOpacity );
2432  return; // don't bother evaluating values that won't be used
2433  }
2434 
2435  // data defined buffer units?
2436  dataDefinedValEval( DDUnits, QgsPalLayerSettings::BufferUnit, exprVal, context.expressionContext() );
2437 
2438  // data defined buffer color?
2439  dataDefinedValEval( DDColor, QgsPalLayerSettings::BufferColor, exprVal, context.expressionContext(), QgsSymbolLayerUtils::encodeColor( buffer.color() ) );
2440 
2441  // data defined buffer pen join style?
2442  dataDefinedValEval( DDJoinStyle, QgsPalLayerSettings::BufferJoinStyle, exprVal, context.expressionContext(), QgsSymbolLayerUtils::encodePenJoinStyle( buffer.joinStyle() ) );
2443 
2444  // data defined buffer blend mode?
2445  dataDefinedValEval( DDBlendMode, QgsPalLayerSettings::BufferBlendMode, exprVal, context.expressionContext() );
2446 }
2447 
2448 void QgsPalLayerSettings::parseTextFormatting( QgsRenderContext &context )
2449 {
2450  QVariant exprVal; // value() is repeatedly nulled on data defined evaluation and replaced when successful
2451 
2452  // data defined multiline wrap character?
2453  QString wrapchr = wrapChar;
2454  if ( dataDefinedValEval( DDString, QgsPalLayerSettings::MultiLineWrapChar, exprVal, context.expressionContext(), wrapChar ) )
2455  {
2456  wrapchr = exprVal.toString();
2457  }
2458 
2459  int evalAutoWrapLength = autoWrapLength;
2460  if ( dataDefinedValEval( DDInt, QgsPalLayerSettings::AutoWrapLength, exprVal, context.expressionContext(), evalAutoWrapLength ) )
2461  {
2462  evalAutoWrapLength = exprVal.toInt();
2463  }
2464 
2465  // data defined multiline height?
2466  dataDefinedValEval( DDDouble, QgsPalLayerSettings::MultiLineHeight, exprVal, context.expressionContext() );
2467 
2468  // data defined multiline text align?
2469  context.expressionContext().setOriginalValueVariable( mFormat.lineHeight() );
2470  exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::MultiLineAlignment, context.expressionContext() );
2471  if ( exprVal.isValid() )
2472  {
2473  QString str = exprVal.toString().trimmed();
2474  QgsDebugMsgLevel( QStringLiteral( "exprVal MultiLineAlignment:%1" ).arg( str ), 4 );
2475 
2476  if ( !str.isEmpty() )
2477  {
2478  // "Left"
2480 
2481  if ( str.compare( QLatin1String( "Center" ), Qt::CaseInsensitive ) == 0 )
2482  {
2484  }
2485  else if ( str.compare( QLatin1String( "Right" ), Qt::CaseInsensitive ) == 0 )
2486  {
2487  aligntype = QgsPalLayerSettings::MultiRight;
2488  }
2489  else if ( str.compare( QLatin1String( "Follow" ), Qt::CaseInsensitive ) == 0 )
2490  {
2492  }
2493  dataDefinedValues.insert( QgsPalLayerSettings::MultiLineAlignment, QVariant( static_cast< int >( aligntype ) ) );
2494  }
2495  }
2496 
2497  // data defined direction symbol?
2498  bool drawDirSymb = addDirectionSymbol;
2499  if ( dataDefinedValEval( DDBool, QgsPalLayerSettings::DirSymbDraw, exprVal, context.expressionContext(), addDirectionSymbol ) )
2500  {
2501  drawDirSymb = exprVal.toBool();
2502  }
2503 
2504  if ( drawDirSymb )
2505  {
2506  // data defined direction left symbol?
2507  dataDefinedValEval( DDString, QgsPalLayerSettings::DirSymbLeft, exprVal, context.expressionContext(), leftDirectionSymbol );
2508 
2509  // data defined direction right symbol?
2510  dataDefinedValEval( DDString, QgsPalLayerSettings::DirSymbRight, exprVal, context.expressionContext(), rightDirectionSymbol );
2511 
2512  // data defined direction symbol placement?
2513  exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::DirSymbPlacement, context.expressionContext() );
2514  if ( exprVal.isValid() )
2515  {
2516  QString str = exprVal.toString().trimmed();
2517  QgsDebugMsgLevel( QStringLiteral( "exprVal DirSymbPlacement:%1" ).arg( str ), 4 );
2518 
2519  if ( !str.isEmpty() )
2520  {
2521  // "LeftRight"
2523 
2524  if ( str.compare( QLatin1String( "Above" ), Qt::CaseInsensitive ) == 0 )
2525  {
2527  }
2528  else if ( str.compare( QLatin1String( "Below" ), Qt::CaseInsensitive ) == 0 )
2529  {
2531  }
2532  dataDefinedValues.insert( QgsPalLayerSettings::DirSymbPlacement, QVariant( static_cast< int >( placetype ) ) );
2533  }
2534  }
2535 
2536  // data defined direction symbol reversed?
2537  dataDefinedValEval( DDBool, QgsPalLayerSettings::DirSymbReverse, exprVal, context.expressionContext(), reverseDirectionSymbol );
2538  }
2539 
2540  // formatting for numbers is inline with generation of base label text and not passed to label painting
2541 }
2542 
2543 void QgsPalLayerSettings::parseShapeBackground( QgsRenderContext &context )
2544 {
2545  QVariant exprVal; // value() is repeatedly nulled on data defined evaluation and replaced when successful
2546 
2547  QgsTextBackgroundSettings background = mFormat.background();
2548 
2549  // data defined draw shape?
2550  bool drawShape = background.enabled();
2551  if ( dataDefinedValEval( DDBool, QgsPalLayerSettings::ShapeDraw, exprVal, context.expressionContext(), drawShape ) )
2552  {
2553  drawShape = exprVal.toBool();
2554  }
2555 
2556  if ( !drawShape )
2557  {
2558  return;
2559  }
2560 
2561  // data defined shape transparency?
2562  double shapeOpacity = background.opacity() * 100;
2563  if ( dataDefinedValEval( DDOpacity, QgsPalLayerSettings::ShapeOpacity, exprVal, context.expressionContext(), shapeOpacity ) )
2564  {
2565  shapeOpacity = 100.0 * exprVal.toDouble();
2566  }
2567 
2568  drawShape = ( drawShape && shapeOpacity > 0 ); // size is not taken into account (could be)
2569 
2570  if ( !drawShape )
2571  {
2572  dataDefinedValues.insert( QgsPalLayerSettings::ShapeDraw, QVariant( false ) ); // trigger value
2573  dataDefinedValues.remove( QgsPalLayerSettings::ShapeOpacity );
2574  return; // don't bother evaluating values that won't be used
2575  }
2576 
2577  // data defined shape kind?
2578  QgsTextBackgroundSettings::ShapeType shapeKind = background.type();
2579  exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::ShapeKind, context.expressionContext() );
2580  if ( exprVal.isValid() )
2581  {
2582  QString skind = exprVal.toString().trimmed();
2583  QgsDebugMsgLevel( QStringLiteral( "exprVal ShapeKind:%1" ).arg( skind ), 4 );
2584 
2585  if ( !skind.isEmpty() )
2586  {
2587  // "Rectangle"
2589 
2590  if ( skind.compare( QLatin1String( "Square" ), Qt::CaseInsensitive ) == 0 )
2591  {
2593  }
2594  else if ( skind.compare( QLatin1String( "Ellipse" ), Qt::CaseInsensitive ) == 0 )
2595  {
2597  }
2598  else if ( skind.compare( QLatin1String( "Circle" ), Qt::CaseInsensitive ) == 0 )
2599  {
2601  }
2602  else if ( skind.compare( QLatin1String( "SVG" ), Qt::CaseInsensitive ) == 0 )
2603  {
2605  }
2606  shapeKind = shpkind;
2607  dataDefinedValues.insert( QgsPalLayerSettings::ShapeKind, QVariant( static_cast< int >( shpkind ) ) );
2608  }
2609  }
2610 
2611  // data defined shape SVG path?
2612  QString svgPath = background.svgFile();
2613  context.expressionContext().setOriginalValueVariable( svgPath );
2614  exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::ShapeSVGFile, context.expressionContext() );
2615  if ( exprVal.isValid() )
2616  {
2617  QString svgfile = exprVal.toString().trimmed();
2618  QgsDebugMsgLevel( QStringLiteral( "exprVal ShapeSVGFile:%1" ).arg( svgfile ), 4 );
2619 
2620  // '' empty paths are allowed
2621  svgPath = QgsSymbolLayerUtils::svgSymbolNameToPath( svgfile, context.pathResolver() );
2622  dataDefinedValues.insert( QgsPalLayerSettings::ShapeSVGFile, QVariant( svgPath ) );
2623  }
2624 
2625  // data defined shape size type?
2626  QgsTextBackgroundSettings::SizeType shpSizeType = background.sizeType();
2627  exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::ShapeSizeType, context.expressionContext() );
2628  if ( exprVal.isValid() )
2629  {
2630  QString stype = exprVal.toString().trimmed();
2631  QgsDebugMsgLevel( QStringLiteral( "exprVal ShapeSizeType:%1" ).arg( stype ), 4 );
2632 
2633  if ( !stype.isEmpty() )
2634  {
2635  // "Buffer"
2637 
2638  if ( stype.compare( QLatin1String( "Fixed" ), Qt::CaseInsensitive ) == 0 )
2639  {
2641  }
2642  shpSizeType = sizType;
2643  dataDefinedValues.insert( QgsPalLayerSettings::ShapeSizeType, QVariant( static_cast< int >( sizType ) ) );
2644  }
2645  }
2646 
2647  // data defined shape size X? (SVGs only use X for sizing)
2648  double ddShpSizeX = background.size().width();
2649  if ( dataDefinedValEval( DDDouble, QgsPalLayerSettings::ShapeSizeX, exprVal, context.expressionContext(), ddShpSizeX ) )
2650  {
2651  ddShpSizeX = exprVal.toDouble();
2652  }
2653 
2654  // data defined shape size Y?
2655  double ddShpSizeY = background.size().height();
2656  if ( dataDefinedValEval( DDDouble, QgsPalLayerSettings::ShapeSizeY, exprVal, context.expressionContext(), ddShpSizeY ) )
2657  {
2658  ddShpSizeY = exprVal.toDouble();
2659  }
2660 
2661  // don't continue under certain circumstances (e.g. size is fixed)
2662  bool skip = false;
2663  if ( shapeKind == QgsTextBackgroundSettings::ShapeSVG
2664  && ( svgPath.isEmpty()
2665  || ( !svgPath.isEmpty()
2666  && shpSizeType == QgsTextBackgroundSettings::SizeFixed
2667  && ddShpSizeX == 0.0 ) ) )
2668  {
2669  skip = true;
2670  }
2671  if ( shapeKind != QgsTextBackgroundSettings::ShapeSVG
2672  && shpSizeType == QgsTextBackgroundSettings::SizeFixed
2673  && ( ddShpSizeX == 0.0 || ddShpSizeY == 0.0 ) )
2674  {
2675  skip = true;
2676  }
2677 
2678  if ( skip )
2679  {
2680  dataDefinedValues.insert( QgsPalLayerSettings::ShapeDraw, QVariant( false ) ); // trigger value
2681  dataDefinedValues.remove( QgsPalLayerSettings::ShapeOpacity );
2682  dataDefinedValues.remove( QgsPalLayerSettings::ShapeKind );
2683  dataDefinedValues.remove( QgsPalLayerSettings::ShapeSVGFile );
2684  dataDefinedValues.remove( QgsPalLayerSettings::ShapeSizeX );
2685  dataDefinedValues.remove( QgsPalLayerSettings::ShapeSizeY );
2686  return; // don't bother evaluating values that won't be used
2687  }
2688 
2689  // data defined shape size units?
2690  dataDefinedValEval( DDUnits, QgsPalLayerSettings::ShapeSizeUnits, exprVal, context.expressionContext() );
2691 
2692  // data defined shape rotation type?
2693  exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::ShapeRotationType, context.expressionContext() );
2694  if ( exprVal.isValid() )
2695  {
2696  QString rotstr = exprVal.toString().trimmed();
2697  QgsDebugMsgLevel( QStringLiteral( "exprVal ShapeRotationType:%1" ).arg( rotstr ), 4 );
2698 
2699  if ( !rotstr.isEmpty() )
2700  {
2701  // "Sync"
2703 
2704  if ( rotstr.compare( QLatin1String( "Offset" ), Qt::CaseInsensitive ) == 0 )
2705  {
2707  }
2708  else if ( rotstr.compare( QLatin1String( "Fixed" ), Qt::CaseInsensitive ) == 0 )
2709  {
2711  }
2712  dataDefinedValues.insert( QgsPalLayerSettings::ShapeRotationType, QVariant( static_cast< int >( rottype ) ) );
2713  }
2714  }
2715 
2716  // data defined shape rotation?
2717  dataDefinedValEval( DDRotation180, QgsPalLayerSettings::ShapeRotation, exprVal, context.expressionContext(), background.rotation() );
2718 
2719  // data defined shape offset?
2720  dataDefinedValEval( DDPointF, QgsPalLayerSettings::ShapeOffset, exprVal, context.expressionContext(), QgsSymbolLayerUtils::encodePoint( background.offset() ) );
2721 
2722  // data defined shape offset units?
2723  dataDefinedValEval( DDUnits, QgsPalLayerSettings::ShapeOffsetUnits, exprVal, context.expressionContext() );
2724 
2725  // data defined shape radii?
2726  dataDefinedValEval( DDSizeF, QgsPalLayerSettings::ShapeRadii, exprVal, context.expressionContext(), QgsSymbolLayerUtils::encodeSize( background.radii() ) );
2727 
2728  // data defined shape radii units?
2729  dataDefinedValEval( DDUnits, QgsPalLayerSettings::ShapeRadiiUnits, exprVal, context.expressionContext() );
2730 
2731  // data defined shape blend mode?
2732  dataDefinedValEval( DDBlendMode, QgsPalLayerSettings::ShapeBlendMode, exprVal, context.expressionContext() );
2733 
2734  // data defined shape fill color?
2735  dataDefinedValEval( DDColor, QgsPalLayerSettings::ShapeFillColor, exprVal, context.expressionContext(), QgsSymbolLayerUtils::encodeColor( background.fillColor() ) );
2736 
2737  // data defined shape stroke color?
2738  dataDefinedValEval( DDColor, QgsPalLayerSettings::ShapeStrokeColor, exprVal, context.expressionContext(), QgsSymbolLayerUtils::encodeColor( background.strokeColor() ) );
2739 
2740  // data defined shape stroke width?
2741  dataDefinedValEval( DDDoublePos, QgsPalLayerSettings::ShapeStrokeWidth, exprVal, context.expressionContext(), background.strokeWidth() );
2742 
2743  // data defined shape stroke width units?
2744  dataDefinedValEval( DDUnits, QgsPalLayerSettings::ShapeStrokeWidthUnits, exprVal, context.expressionContext() );
2745 
2746  // data defined shape join style?
2747  dataDefinedValEval( DDJoinStyle, QgsPalLayerSettings::ShapeJoinStyle, exprVal, context.expressionContext(), QgsSymbolLayerUtils::encodePenJoinStyle( background.joinStyle() ) );
2748 
2749 }
2750 
2751 void QgsPalLayerSettings::parseDropShadow( QgsRenderContext &context )
2752 {
2753  QVariant exprVal; // value() is repeatedly nulled on data defined evaluation and replaced when successful
2754 
2755  QgsTextShadowSettings shadow = mFormat.shadow();
2756 
2757  // data defined draw shadow?
2758  bool drawShadow = shadow.enabled();
2759  if ( dataDefinedValEval( DDBool, QgsPalLayerSettings::ShadowDraw, exprVal, context.expressionContext(), drawShadow ) )
2760  {
2761  drawShadow = exprVal.toBool();
2762  }
2763 
2764  if ( !drawShadow )
2765  {
2766  return;
2767  }
2768 
2769  // data defined shadow transparency?
2770  double shadowOpacity = shadow.opacity() * 100;
2771  if ( dataDefinedValEval( DDOpacity, QgsPalLayerSettings::ShadowOpacity, exprVal, context.expressionContext(), shadowOpacity ) )
2772  {
2773  shadowOpacity = exprVal.toDouble();
2774  }
2775 
2776  // data defined shadow offset distance?
2777  double shadowOffDist = shadow.offsetDistance();
2778  if ( dataDefinedValEval( DDDoublePos, QgsPalLayerSettings::ShadowOffsetDist, exprVal, context.expressionContext(), shadowOffDist ) )
2779  {
2780  shadowOffDist = exprVal.toDouble();
2781  }
2782 
2783  // data defined shadow offset distance?
2784  double shadowRad = shadow.blurRadius();
2785  if ( dataDefinedValEval( DDDoublePos, QgsPalLayerSettings::ShadowRadius, exprVal, context.expressionContext(), shadowRad ) )
2786  {
2787  shadowRad = exprVal.toDouble();
2788  }
2789 
2790  drawShadow = ( drawShadow && shadowOpacity > 0 && !( shadowOffDist == 0.0 && shadowRad == 0.0 ) );
2791 
2792  if ( !drawShadow )
2793  {
2794  dataDefinedValues.insert( QgsPalLayerSettings::ShadowDraw, QVariant( false ) ); // trigger value
2795  dataDefinedValues.remove( QgsPalLayerSettings::ShadowOpacity );
2796  dataDefinedValues.remove( QgsPalLayerSettings::ShadowOffsetDist );
2797  dataDefinedValues.remove( QgsPalLayerSettings::ShadowRadius );
2798  return; // don't bother evaluating values that won't be used
2799  }
2800 
2801  // data defined shadow under type?
2802  exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::ShadowUnder, context.expressionContext() );
2803  if ( exprVal.isValid() )
2804  {
2805  QString str = exprVal.toString().trimmed();
2806  QgsDebugMsgLevel( QStringLiteral( "exprVal ShadowUnder:%1" ).arg( str ), 4 );
2807 
2808  if ( !str.isEmpty() )
2809  {
2810  // "Lowest"
2812 
2813  if ( str.compare( QLatin1String( "Text" ), Qt::CaseInsensitive ) == 0 )
2814  {
2816  }
2817  else if ( str.compare( QLatin1String( "Buffer" ), Qt::CaseInsensitive ) == 0 )
2818  {
2820  }
2821  else if ( str.compare( QLatin1String( "Background" ), Qt::CaseInsensitive ) == 0 )
2822  {
2824  }
2825  dataDefinedValues.insert( QgsPalLayerSettings::ShadowUnder, QVariant( static_cast< int >( shdwtype ) ) );
2826  }
2827  }
2828 
2829  // data defined shadow offset angle?
2830  dataDefinedValEval( DDRotation180, QgsPalLayerSettings::ShadowOffsetAngle, exprVal, context.expressionContext(), shadow.offsetAngle() );
2831 
2832  // data defined shadow offset units?
2833  dataDefinedValEval( DDUnits, QgsPalLayerSettings::ShadowOffsetUnits, exprVal, context.expressionContext() );
2834 
2835  // data defined shadow radius?
2836  dataDefinedValEval( DDDouble, QgsPalLayerSettings::ShadowRadius, exprVal, context.expressionContext(), shadow.blurRadius() );
2837 
2838  // data defined shadow radius units?
2839  dataDefinedValEval( DDUnits, QgsPalLayerSettings::ShadowRadiusUnits, exprVal, context.expressionContext() );
2840 
2841  // data defined shadow scale? ( gui bounds to 0-2000, no upper bound here )
2842  dataDefinedValEval( DDIntPos, QgsPalLayerSettings::ShadowScale, exprVal, context.expressionContext(), shadow.scale() );
2843 
2844  // data defined shadow color?
2845  dataDefinedValEval( DDColor, QgsPalLayerSettings::ShadowColor, exprVal, context.expressionContext(), QgsSymbolLayerUtils::encodeColor( shadow.color() ) );
2846 
2847  // data defined shadow blend mode?
2848  dataDefinedValEval( DDBlendMode, QgsPalLayerSettings::ShadowBlendMode, exprVal, context.expressionContext() );
2849 }
2850 
2851 // -------------
2852 
2853 
2855 {
2856  return layer->labelsEnabled() || layer->diagramsEnabled();
2857 }
2858 
2859 
2860 bool QgsPalLabeling::geometryRequiresPreparation( const QgsGeometry &geometry, QgsRenderContext &context, const QgsCoordinateTransform &ct, const QgsGeometry &clipGeometry, bool mergeLines )
2861 {
2862  if ( geometry.isNull() )
2863  {
2864  return false;
2865  }
2866 
2867  if ( geometry.type() == QgsWkbTypes::LineGeometry && geometry.isMultipart() && mergeLines )
2868  {
2869  return true;
2870  }
2871 
2872  //requires reprojection
2873  if ( ct.isValid() && !ct.isShortCircuited() )
2874  return true;
2875 
2876  //requires rotation
2877  const QgsMapToPixel &m2p = context.mapToPixel();
2878  if ( !qgsDoubleNear( m2p.mapRotation(), 0 ) )
2879  return true;
2880 
2881  //requires clip
2882  if ( !clipGeometry.isNull() && !clipGeometry.boundingBox().contains( geometry.boundingBox() ) )
2883  return true;
2884 
2885  //requires fixing
2886  if ( geometry.type() == QgsWkbTypes::PolygonGeometry && !geometry.isGeosValid() )
2887  return true;
2888 
2889  return false;
2890 }
2891 
2892 QStringList QgsPalLabeling::splitToLines( const QString &text, const QString &wrapCharacter, const int autoWrapLength, const bool useMaxLineLengthWhenAutoWrapping )
2893 {
2894  QStringList multiLineSplit;
2895  if ( !wrapCharacter.isEmpty() && wrapCharacter != QLatin1String( "\n" ) )
2896  {
2897  //wrap on both the wrapchr and new line characters
2898  const QStringList lines = text.split( wrapCharacter );
2899  for ( const QString &line : lines )
2900  {
2901  multiLineSplit.append( line.split( '\n' ) );
2902  }
2903  }
2904  else
2905  {
2906  multiLineSplit = text.split( '\n' );
2907  }
2908 
2909  // apply auto wrapping to each manually created line
2910  if ( autoWrapLength != 0 )
2911  {
2912  QStringList autoWrappedLines;
2913  autoWrappedLines.reserve( multiLineSplit.count() );
2914  for ( const QString &line : qgis::as_const( multiLineSplit ) )
2915  {
2916  autoWrappedLines.append( QgsStringUtils::wordWrap( line, autoWrapLength, useMaxLineLengthWhenAutoWrapping ).split( '\n' ) );
2917  }
2918  multiLineSplit = autoWrappedLines;
2919  }
2920  return multiLineSplit;
2921 }
2922 
2923 QStringList QgsPalLabeling::splitToGraphemes( const QString &text )
2924 {
2925  QStringList graphemes;
2926  QTextBoundaryFinder boundaryFinder( QTextBoundaryFinder::Grapheme, text );
2927  int currentBoundary = -1;
2928  int previousBoundary = 0;
2929  while ( ( currentBoundary = boundaryFinder.toNextBoundary() ) > 0 )
2930  {
2931  graphemes << text.mid( previousBoundary, currentBoundary - previousBoundary );
2932  previousBoundary = currentBoundary;
2933  }
2934  return graphemes;
2935 }
2936 
2938 {
2939  if ( geometry.isNull() )
2940  {
2941  return QgsGeometry();
2942  }
2943 
2944  //don't modify the feature's geometry so that geometry based expressions keep working
2945  QgsGeometry geom = geometry;
2946 
2947  if ( geom.type() == QgsWkbTypes::LineGeometry && geom.isMultipart() && mergeLines )
2948  {
2949  geom = geom.mergeLines();
2950  }
2951 
2952  //reproject the geometry if necessary
2953  if ( ct.isValid() && !ct.isShortCircuited() )
2954  {
2955  try
2956  {
2957  geom.transform( ct );
2958  }
2959  catch ( QgsCsException &cse )
2960  {
2961  Q_UNUSED( cse );
2962  QgsDebugMsgLevel( QStringLiteral( "Ignoring feature due to transformation exception" ), 4 );
2963  return QgsGeometry();
2964  }
2965  // geometry transforms may result in nan points, remove these
2966  geom.filterVertices( []( const QgsPoint & point )->bool
2967  {
2968  return std::isfinite( point.x() ) && std::isfinite( point.y() );
2969  } );
2970  if ( QgsCurvePolygon *cp = qgsgeometry_cast< QgsCurvePolygon * >( geom.get() ) )
2971  cp->removeInvalidRings();
2972  }
2973 
2974  // Rotate the geometry if needed, before clipping
2975  const QgsMapToPixel &m2p = context.mapToPixel();
2976  if ( !qgsDoubleNear( m2p.mapRotation(), 0 ) )
2977  {
2978  QgsPointXY center = context.mapExtent().center();
2979  if ( geom.rotate( m2p.mapRotation(), center ) )
2980  {
2981  QgsDebugMsg( QStringLiteral( "Error rotating geometry" ).arg( geom.asWkt() ) );
2982  return QgsGeometry();
2983  }
2984  }
2985 
2986  // fix invalid polygons
2987  if ( geom.type() == QgsWkbTypes::PolygonGeometry )
2988  {
2989  if ( geom.isMultipart() )
2990  {
2991  // important -- we need to treat ever part in isolation here. We can't test the validity of the whole geometry
2992  // at once, because touching parts would result in an invalid geometry, and buffering this "dissolves" the parts.
2993  // because the actual label engine treats parts as separate entities, we aren't bound by the usual "touching parts are invalid" rule
2994  // see https://github.com/qgis/QGIS/issues/26763
2995  QVector< QgsGeometry> parts;
2996  parts.reserve( qgsgeometry_cast< const QgsGeometryCollection * >( geom.constGet() )->numGeometries() );
2997  for ( auto it = geom.const_parts_begin(); it != geom.const_parts_end(); ++it )
2998  {
2999  QgsGeometry partGeom( ( *it )->clone() );
3000  if ( !partGeom.isGeosValid() )
3001  {
3002  partGeom = partGeom.buffer( 0, 0 );
3003  }
3004  parts.append( partGeom );
3005  }
3006  geom = QgsGeometry::collectGeometry( parts );
3007  }
3008  else if ( !geom.isGeosValid() )
3009  {
3010  QgsGeometry bufferGeom = geom.buffer( 0, 0 );
3011  if ( bufferGeom.isNull() )
3012  {
3013  QgsDebugMsg( QStringLiteral( "Could not repair geometry: %1" ).arg( bufferGeom.lastError() ) );
3014  return QgsGeometry();
3015  }
3016  geom = bufferGeom;
3017  }
3018  }
3019 
3020  if ( !clipGeometry.isNull() &&
3021  ( ( qgsDoubleNear( m2p.mapRotation(), 0 ) && !clipGeometry.boundingBox().contains( geom.boundingBox() ) )
3022  || ( !qgsDoubleNear( m2p.mapRotation(), 0 ) && !clipGeometry.contains( geom ) ) ) )
3023  {
3024  QgsGeometry clipGeom = geom.intersection( clipGeometry ); // creates new geometry
3025  if ( clipGeom.isNull() )
3026  {
3027  return QgsGeometry();
3028  }
3029  geom = clipGeom;
3030  }
3031 
3032  return geom;
3033 }
3034 
3035 bool QgsPalLabeling::checkMinimumSizeMM( const QgsRenderContext &context, const QgsGeometry &geom, double minSize )
3036 {
3037  if ( minSize <= 0 )
3038  {
3039  return true;
3040  }
3041 
3042  if ( geom.isNull() )
3043  {
3044  return false;
3045  }
3046 
3047  QgsWkbTypes::GeometryType featureType = geom.type();
3048  if ( featureType == QgsWkbTypes::PointGeometry ) //minimum size does not apply to point features
3049  {
3050  return true;
3051  }
3052 
3053  double mapUnitsPerMM = context.mapToPixel().mapUnitsPerPixel() * context.scaleFactor();
3054  if ( featureType == QgsWkbTypes::LineGeometry )
3055  {
3056  double length = geom.length();
3057  if ( length >= 0.0 )
3058  {
3059  return ( length >= ( minSize * mapUnitsPerMM ) );
3060  }
3061  }
3062  else if ( featureType == QgsWkbTypes::PolygonGeometry )
3063  {
3064  double area = geom.area();
3065  if ( area >= 0.0 )
3066  {
3067  return ( std::sqrt( area ) >= ( minSize * mapUnitsPerMM ) );
3068  }
3069  }
3070  return true; //should never be reached. Return true in this case to label such geometries anyway.
3071 }
3072 
3073 
3074 void QgsPalLabeling::dataDefinedTextStyle( QgsPalLayerSettings &tmpLyr,
3075  const QMap< QgsPalLayerSettings::Property, QVariant > &ddValues )
3076 {
3077  QgsTextFormat format = tmpLyr.format();
3078  bool changed = false;
3079 
3080  //font color
3081  if ( ddValues.contains( QgsPalLayerSettings::Color ) )
3082  {
3083  QVariant ddColor = ddValues.value( QgsPalLayerSettings::Color );
3084  format.setColor( ddColor.value<QColor>() );
3085  changed = true;
3086  }
3087 
3088  //font transparency
3089  if ( ddValues.contains( QgsPalLayerSettings::FontOpacity ) )
3090  {
3091  format.setOpacity( ddValues.value( QgsPalLayerSettings::FontOpacity ).toDouble() / 100.0 );
3092  changed = true;
3093  }
3094 
3095  //font blend mode
3096  if ( ddValues.contains( QgsPalLayerSettings::FontBlendMode ) )
3097  {
3098  format.setBlendMode( static_cast< QPainter::CompositionMode >( ddValues.value( QgsPalLayerSettings::FontBlendMode ).toInt() ) );
3099  changed = true;
3100  }
3101 
3102  if ( changed )
3103  {
3104  tmpLyr.setFormat( format );
3105  }
3106 }
3107 
3108 void QgsPalLabeling::dataDefinedTextFormatting( QgsPalLayerSettings &tmpLyr,
3109  const QMap< QgsPalLayerSettings::Property, QVariant > &ddValues )
3110 {
3111  if ( ddValues.contains( QgsPalLayerSettings::MultiLineWrapChar ) )
3112  {
3113  tmpLyr.wrapChar = ddValues.value( QgsPalLayerSettings::MultiLineWrapChar ).toString();
3114  }
3115 
3116  if ( ddValues.contains( QgsPalLayerSettings::AutoWrapLength ) )
3117  {
3118  tmpLyr.autoWrapLength = ddValues.value( QgsPalLayerSettings::AutoWrapLength ).toInt();
3119  }
3120 
3121  if ( ddValues.contains( QgsPalLayerSettings::MultiLineHeight ) )
3122  {
3123  QgsTextFormat format = tmpLyr.format();
3124  format.setLineHeight( ddValues.value( QgsPalLayerSettings::MultiLineHeight ).toDouble() );
3125  tmpLyr.setFormat( format );
3126  }
3127 
3128  if ( ddValues.contains( QgsPalLayerSettings::MultiLineAlignment ) )
3129  {
3130  tmpLyr.multilineAlign = static_cast< QgsPalLayerSettings::MultiLineAlign >( ddValues.value( QgsPalLayerSettings::MultiLineAlignment ).toInt() );
3131  }
3132 
3133  if ( ddValues.contains( QgsPalLayerSettings::DirSymbDraw ) )
3134  {
3135  tmpLyr.addDirectionSymbol = ddValues.value( QgsPalLayerSettings::DirSymbDraw ).toBool();
3136  }
3137 
3138  if ( tmpLyr.addDirectionSymbol )
3139  {
3140 
3141  if ( ddValues.contains( QgsPalLayerSettings::DirSymbLeft ) )
3142  {
3143  tmpLyr.leftDirectionSymbol = ddValues.value( QgsPalLayerSettings::DirSymbLeft ).toString();
3144  }
3145  if ( ddValues.contains( QgsPalLayerSettings::DirSymbRight ) )
3146  {
3147  tmpLyr.rightDirectionSymbol = ddValues.value( QgsPalLayerSettings::DirSymbRight ).toString();
3148  }
3149 
3150  if ( ddValues.contains( QgsPalLayerSettings::DirSymbPlacement ) )
3151  {
3152  tmpLyr.placeDirectionSymbol = static_cast< QgsPalLayerSettings::DirectionSymbols >( ddValues.value( QgsPalLayerSettings::DirSymbPlacement ).toInt() );
3153  }
3154 
3155  if ( ddValues.contains( QgsPalLayerSettings::DirSymbReverse ) )
3156  {
3157  tmpLyr.reverseDirectionSymbol = ddValues.value( QgsPalLayerSettings::DirSymbReverse ).toBool();
3158  }
3159 
3160  }
3161 }
3162 
3163 void QgsPalLabeling::dataDefinedTextBuffer( QgsPalLayerSettings &tmpLyr,
3164  const QMap< QgsPalLayerSettings::Property, QVariant > &ddValues )
3165 {
3166  QgsTextBufferSettings buffer = tmpLyr.format().buffer();
3167  bool changed = false;
3168 
3169  //buffer draw
3170  if ( ddValues.contains( QgsPalLayerSettings::BufferDraw ) )
3171  {
3172  buffer.setEnabled( ddValues.value( QgsPalLayerSettings::BufferDraw ).toBool() );
3173  changed = true;
3174  }
3175 
3176  if ( !buffer.enabled() )
3177  {
3178  if ( changed )
3179  {
3180  QgsTextFormat format = tmpLyr.format();
3181  format.setBuffer( buffer );
3182  tmpLyr.setFormat( format );
3183  }
3184 
3185  // tmpLyr.bufferSize > 0.0 && tmpLyr.bufferTransp < 100 figured in during evaluation
3186  return; // don't continue looking for unused values
3187  }
3188 
3189  //buffer size
3190  if ( ddValues.contains( QgsPalLayerSettings::BufferSize ) )
3191  {
3192  buffer.setSize( ddValues.value( QgsPalLayerSettings::BufferSize ).toDouble() );
3193  changed = true;
3194  }
3195 
3196  //buffer opacity
3197  if ( ddValues.contains( QgsPalLayerSettings::BufferOpacity ) )
3198  {
3199  buffer.setOpacity( ddValues.value( QgsPalLayerSettings::BufferOpacity ).toDouble() / 100.0 );
3200  changed = true;
3201  }
3202 
3203  //buffer size units
3204  if ( ddValues.contains( QgsPalLayerSettings::BufferUnit ) )
3205  {
3206  QgsUnitTypes::RenderUnit bufunit = static_cast< QgsUnitTypes::RenderUnit >( ddValues.value( QgsPalLayerSettings::BufferUnit ).toInt() );
3207  buffer.setSizeUnit( bufunit );
3208  changed = true;
3209  }
3210 
3211  //buffer color
3212  if ( ddValues.contains( QgsPalLayerSettings::BufferColor ) )
3213  {
3214  QVariant ddColor = ddValues.value( QgsPalLayerSettings::BufferColor );
3215  buffer.setColor( ddColor.value<QColor>() );
3216  changed = true;
3217  }
3218 
3219  //buffer pen join style
3220  if ( ddValues.contains( QgsPalLayerSettings::BufferJoinStyle ) )
3221  {
3222  buffer.setJoinStyle( static_cast< Qt::PenJoinStyle >( ddValues.value( QgsPalLayerSettings::BufferJoinStyle ).toInt() ) );
3223  changed = true;
3224  }
3225 
3226  //buffer blend mode
3227  if ( ddValues.contains( QgsPalLayerSettings::BufferBlendMode ) )
3228  {
3229  buffer.setBlendMode( static_cast< QPainter::CompositionMode >( ddValues.value( QgsPalLayerSettings::BufferBlendMode ).toInt() ) );
3230  changed = true;
3231  }
3232 
3233  if ( changed )
3234  {
3235  QgsTextFormat format = tmpLyr.format();
3236  format.setBuffer( buffer );
3237  tmpLyr.setFormat( format );
3238  }
3239 }
3240 
3241 void QgsPalLabeling::dataDefinedShapeBackground( QgsPalLayerSettings &tmpLyr,
3242  const QMap< QgsPalLayerSettings::Property, QVariant > &ddValues )
3243 {
3244  QgsTextBackgroundSettings background = tmpLyr.format().background();
3245  bool changed = false;
3246 
3247  //shape draw
3248  if ( ddValues.contains( QgsPalLayerSettings::ShapeDraw ) )
3249  {
3250  background.setEnabled( ddValues.value( QgsPalLayerSettings::ShapeDraw ).toBool() );
3251  changed = true;
3252  }
3253 
3254  if ( !background.enabled() )
3255  {
3256  if ( changed )
3257  {
3258  QgsTextFormat format = tmpLyr.format();
3259  format.setBackground( background );
3260  tmpLyr.setFormat( format );
3261  }
3262  return; // don't continue looking for unused values
3263  }
3264 
3265  if ( ddValues.contains( QgsPalLayerSettings::ShapeKind ) )
3266  {
3267  background.setType( static_cast< QgsTextBackgroundSettings::ShapeType >( ddValues.value( QgsPalLayerSettings::ShapeKind ).toInt() ) );
3268  changed = true;
3269  }
3270 
3271  if ( ddValues.contains( QgsPalLayerSettings::ShapeSVGFile ) )
3272  {
3273  background.setSvgFile( ddValues.value( QgsPalLayerSettings::ShapeSVGFile ).toString() );
3274  changed = true;
3275  }
3276 
3277  if ( ddValues.contains( QgsPalLayerSettings::ShapeSizeType ) )
3278  {
3279  background.setSizeType( static_cast< QgsTextBackgroundSettings::SizeType >( ddValues.value( QgsPalLayerSettings::ShapeSizeType ).toInt() ) );
3280  changed = true;
3281  }
3282 
3283  if ( ddValues.contains( QgsPalLayerSettings::ShapeSizeX ) )
3284  {
3285  QSizeF size = background.size();
3286  size.setWidth( ddValues.value( QgsPalLayerSettings::ShapeSizeX ).toDouble() );
3287  background.setSize( size );
3288  changed = true;
3289  }
3290  if ( ddValues.contains( QgsPalLayerSettings::ShapeSizeY ) )
3291  {
3292  QSizeF size = background.size();
3293  size.setHeight( ddValues.value( QgsPalLayerSettings::ShapeSizeY ).toDouble() );
3294  background.setSize( size );
3295  changed = true;
3296  }
3297 
3298  if ( ddValues.contains( QgsPalLayerSettings::ShapeSizeUnits ) )
3299  {
3300  background.setSizeUnit( static_cast< QgsUnitTypes::RenderUnit >( ddValues.value( QgsPalLayerSettings::ShapeSizeUnits ).toInt() ) );
3301  changed = true;
3302  }
3303 
3304  if ( ddValues.contains( QgsPalLayerSettings::ShapeRotationType ) )
3305  {
3306  background.setRotationType( static_cast< QgsTextBackgroundSettings::RotationType >( ddValues.value( QgsPalLayerSettings::ShapeRotationType ).toInt() ) );
3307  changed = true;
3308  }
3309 
3310  if ( ddValues.contains( QgsPalLayerSettings::ShapeRotation ) )
3311  {
3312  background.setRotation( ddValues.value( QgsPalLayerSettings::ShapeRotation ).toDouble() );
3313  changed = true;
3314  }
3315 
3316  if ( ddValues.contains( QgsPalLayerSettings::ShapeOffset ) )
3317  {
3318  background.setOffset( ddValues.value( QgsPalLayerSettings::ShapeOffset ).toPointF() );
3319  changed = true;
3320  }
3321 
3322  if ( ddValues.contains( QgsPalLayerSettings::ShapeOffsetUnits ) )
3323  {
3324  background.setOffsetUnit( static_cast< QgsUnitTypes::RenderUnit >( ddValues.value( QgsPalLayerSettings::ShapeOffsetUnits ).toInt() ) );
3325  changed = true;
3326  }
3327 
3328  if ( ddValues.contains( QgsPalLayerSettings::ShapeRadii ) )
3329  {
3330  background.setRadii( ddValues.value( QgsPalLayerSettings::ShapeRadii ).toSizeF() );
3331  changed = true;
3332  }
3333 
3334  if ( ddValues.contains( QgsPalLayerSettings::ShapeRadiiUnits ) )
3335  {
3336  background.setRadiiUnit( static_cast< QgsUnitTypes::RenderUnit >( ddValues.value( QgsPalLayerSettings::ShapeRadiiUnits ).toInt() ) );
3337  changed = true;
3338  }
3339 
3340  if ( ddValues.contains( QgsPalLayerSettings::ShapeBlendMode ) )
3341  {
3342  background.setBlendMode( static_cast< QPainter::CompositionMode >( ddValues.value( QgsPalLayerSettings::ShapeBlendMode ).toInt() ) );
3343  changed = true;
3344  }
3345 
3346  if ( ddValues.contains( QgsPalLayerSettings::ShapeFillColor ) )
3347  {
3348  QVariant ddColor = ddValues.value( QgsPalLayerSettings::ShapeFillColor );
3349  background.setFillColor( ddColor.value<QColor>() );
3350  changed = true;
3351  }
3352 
3353  if ( ddValues.contains( QgsPalLayerSettings::ShapeStrokeColor ) )
3354  {
3355  QVariant ddColor = ddValues.value( QgsPalLayerSettings::ShapeStrokeColor );
3356  background.setStrokeColor( ddColor.value<QColor>() );
3357  changed = true;
3358  }
3359 
3360  if ( ddValues.contains( QgsPalLayerSettings::ShapeOpacity ) )
3361  {
3362  background.setOpacity( ddValues.value( QgsPalLayerSettings::ShapeOpacity ).toDouble() / 100.0 );
3363  changed = true;
3364  }
3365 
3366  if ( ddValues.contains( QgsPalLayerSettings::ShapeStrokeWidth ) )
3367  {
3368  background.setStrokeWidth( ddValues.value( QgsPalLayerSettings::ShapeStrokeWidth ).toDouble() );
3369  changed = true;
3370  }
3371 
3372  if ( ddValues.contains( QgsPalLayerSettings::ShapeStrokeWidthUnits ) )
3373  {
3374  background.setStrokeWidthUnit( static_cast< QgsUnitTypes::RenderUnit >( ddValues.value( QgsPalLayerSettings::ShapeStrokeWidthUnits ).toInt() ) );
3375  changed = true;
3376  }
3377 
3378  if ( ddValues.contains( QgsPalLayerSettings::ShapeJoinStyle ) )
3379  {
3380  background.setJoinStyle( static_cast< Qt::PenJoinStyle >( ddValues.value( QgsPalLayerSettings::ShapeJoinStyle ).toInt() ) );
3381  changed = true;
3382  }
3383 
3384  if ( changed )
3385  {
3386  QgsTextFormat format = tmpLyr.format();
3387  format.setBackground( background );
3388  tmpLyr.setFormat( format );
3389  }
3390 }
3391 
3392 void QgsPalLabeling::dataDefinedDropShadow( QgsPalLayerSettings &tmpLyr,
3393  const QMap< QgsPalLayerSettings::Property, QVariant > &ddValues )
3394 {
3395  QgsTextShadowSettings shadow = tmpLyr.format().shadow();
3396  bool changed = false;
3397 
3398  //shadow draw
3399  if ( ddValues.contains( QgsPalLayerSettings::ShadowDraw ) )
3400  {
3401  shadow.setEnabled( ddValues.value( QgsPalLayerSettings::ShadowDraw ).toBool() );
3402  changed = true;
3403  }
3404 
3405  if ( !shadow.enabled() )
3406  {
3407  if ( changed )
3408  {
3409  QgsTextFormat format = tmpLyr.format();
3410  format.setShadow( shadow );
3411  tmpLyr.setFormat( format );
3412  }
3413  return; // don't continue looking for unused values
3414  }
3415 
3416  if ( ddValues.contains( QgsPalLayerSettings::ShadowUnder ) )
3417  {
3418  shadow.setShadowPlacement( static_cast< QgsTextShadowSettings::ShadowPlacement >( ddValues.value( QgsPalLayerSettings::ShadowUnder ).toInt() ) );
3419  changed = true;
3420  }
3421 
3422  if ( ddValues.contains( QgsPalLayerSettings::ShadowOffsetAngle ) )
3423  {
3424  shadow.setOffsetAngle( ddValues.value( QgsPalLayerSettings::ShadowOffsetAngle ).toInt() );
3425  changed = true;
3426  }
3427 
3428  if ( ddValues.contains( QgsPalLayerSettings::ShadowOffsetDist ) )
3429  {
3430  shadow.setOffsetDistance( ddValues.value( QgsPalLayerSettings::ShadowOffsetDist ).toDouble() );
3431  changed = true;
3432  }
3433 
3434  if ( ddValues.contains( QgsPalLayerSettings::ShadowOffsetUnits ) )
3435  {
3436  shadow.setOffsetUnit( static_cast< QgsUnitTypes::RenderUnit >( ddValues.value( QgsPalLayerSettings::ShadowOffsetUnits ).toInt() ) );
3437  changed = true;
3438  }
3439 
3440  if ( ddValues.contains( QgsPalLayerSettings::ShadowRadius ) )
3441  {
3442  shadow.setBlurRadius( ddValues.value( QgsPalLayerSettings::ShadowRadius ).toDouble() );
3443  changed = true;
3444  }
3445 
3446  if ( ddValues.contains( QgsPalLayerSettings::ShadowRadiusUnits ) )
3447  {
3448  shadow.setBlurRadiusUnit( static_cast< QgsUnitTypes::RenderUnit >( ddValues.value( QgsPalLayerSettings::ShadowRadiusUnits ).toInt() ) );
3449  changed = true;
3450  }
3451 
3452  if ( ddValues.contains( QgsPalLayerSettings::ShadowColor ) )
3453  {
3454  QVariant ddColor = ddValues.value( QgsPalLayerSettings::ShadowColor );
3455  shadow.setColor( ddColor.value<QColor>() );
3456  changed = true;
3457  }
3458 
3459  if ( ddValues.contains( QgsPalLayerSettings::ShadowOpacity ) )
3460  {
3461  shadow.setOpacity( ddValues.value( QgsPalLayerSettings::ShadowOpacity ).toDouble() / 100.0 );
3462  changed = true;
3463  }
3464 
3465  if ( ddValues.contains( QgsPalLayerSettings::ShadowScale ) )
3466  {
3467  shadow.setScale( ddValues.value( QgsPalLayerSettings::ShadowScale ).toInt() );
3468  changed = true;
3469  }
3470 
3471 
3472  if ( ddValues.contains( QgsPalLayerSettings::ShadowBlendMode ) )
3473  {
3474  shadow.setBlendMode( static_cast< QPainter::CompositionMode >( ddValues.value( QgsPalLayerSettings::ShadowBlendMode ).toInt() ) );
3475  changed = true;
3476  }
3477 
3478  if ( changed )
3479  {
3480  QgsTextFormat format = tmpLyr.format();
3481  format.setShadow( shadow );
3482  tmpLyr.setFormat( format );
3483  }
3484 }
3485 
3486 
3487 void QgsPalLabeling::drawLabelCandidateRect( pal::LabelPosition *lp, QPainter *painter, const QgsMapToPixel *xform, QList<QgsLabelCandidate> *candidates )
3488 {
3489  QgsPointXY outPt = xform->transform( lp->getX(), lp->getY() );
3490 
3491  painter->save();
3492 
3493 #if 0 // TODO: generalize some of this
3494  double w = lp->getWidth();
3495  double h = lp->getHeight();
3496  double cx = lp->getX() + w / 2.0;
3497  double cy = lp->getY() + h / 2.0;
3498  double scale = 1.0 / xform->mapUnitsPerPixel();
3499  double rotation = xform->mapRotation();
3500  double sw = w * scale;
3501  double sh = h * scale;
3502  QRectF rect( -sw / 2, -sh / 2, sw, sh );
3503 
3504  painter->translate( xform->transform( QPointF( cx, cy ) ).toQPointF() );
3505  if ( rotation )
3506  {
3507  // Only if not horizontal
3508  if ( lp->getFeaturePart()->getLayer()->getArrangement() != P_POINT &&
3509  lp->getFeaturePart()->getLayer()->getArrangement() != P_POINT_OVER &&
3510  lp->getFeaturePart()->getLayer()->getArrangement() != P_HORIZ )
3511  {
3512  painter->rotate( rotation );
3513  }
3514  }
3515  painter->translate( rect.bottomLeft() );
3516  painter->rotate( -lp->getAlpha() * 180 / M_PI );
3517  painter->translate( -rect.bottomLeft() );
3518 #else
3519  QgsPointXY outPt2 = xform->transform( lp->getX() + lp->getWidth(), lp->getY() + lp->getHeight() );
3520  QRectF rect( 0, 0, outPt2.x() - outPt.x(), outPt2.y() - outPt.y() );
3521  painter->translate( QPointF( outPt.x(), outPt.y() ) );
3522  painter->rotate( -lp->getAlpha() * 180 / M_PI );
3523 #endif
3524 
3525  if ( lp->conflictsWithObstacle() )
3526  {
3527  painter->setPen( QColor( 255, 0, 0, 64 ) );
3528  }
3529  else
3530  {
3531  painter->setPen( QColor( 0, 0, 0, 64 ) );
3532  }
3533  painter->drawRect( rect );
3534  painter->restore();
3535 
3536  // save the rect
3537  rect.moveTo( outPt.x(), outPt.y() );
3538  if ( candidates )
3539  candidates->append( QgsLabelCandidate( rect, lp->cost() * 1000 ) );
3540 
3541  // show all parts of the multipart label
3542  if ( lp->getNextPart() )
3543  drawLabelCandidateRect( lp->getNextPart(), painter, xform, candidates );
3544 }
3545 
3547  : mLabelSearchTree( qgis::make_unique< QgsLabelSearchTree >() )
3548 {
3549 }
3550 
3552 
3553 QList<QgsLabelPosition> QgsLabelingResults::labelsAtPosition( const QgsPointXY &p ) const
3554 {
3555  QList<QgsLabelPosition> positions;
3556 
3557  QList<QgsLabelPosition *> positionPointers;
3558  if ( mLabelSearchTree )
3559  {
3560  mLabelSearchTree->label( p, positionPointers );
3561  QList<QgsLabelPosition *>::const_iterator pointerIt = positionPointers.constBegin();
3562  for ( ; pointerIt != positionPointers.constEnd(); ++pointerIt )
3563  {
3564  positions.push_back( QgsLabelPosition( **pointerIt ) );
3565  }
3566  }
3567 
3568  return positions;
3569 }
3570 
3571 QList<QgsLabelPosition> QgsLabelingResults::labelsWithinRect( const QgsRectangle &r ) const
3572 {
3573  QList<QgsLabelPosition> positions;
3574 
3575  QList<QgsLabelPosition *> positionPointers;
3576  if ( mLabelSearchTree )
3577  {
3578  mLabelSearchTree->labelsInRect( r, positionPointers );
3579  QList<QgsLabelPosition *>::const_iterator pointerIt = positionPointers.constBegin();
3580  for ( ; pointerIt != positionPointers.constEnd(); ++pointerIt )
3581  {
3582  positions.push_back( QgsLabelPosition( **pointerIt ) );
3583  }
3584  }
3585 
3586  return positions;
3587 }
3588 
3590 {
3591  mLabelSearchTree->setMapSettings( settings );
3592 }
QPointF toQPointF() const
Converts a point to a QPointF.
Definition: qgspointxy.h:148
void setProperty(int key, const QgsProperty &property)
Adds a property to the collection and takes ownership of it.
Label below point, slightly right of center.
Render units (eg mm/pixels/map units)
Definition: qgsproperty.h:63
Class for parsing and evaluation of expressions (formerly called "search strings").
QgsFeatureId id
Definition: qgsfeature.h:64
static const QgsPropertiesDefinition & propertyDefinitions()
Returns the labeling property definitions.
ShapeType type() const
Returns the type of background shape (e.g., square, ellipse, SVG).
bool hasEvalError() const
Returns true if an error occurred when evaluating last input.
double xOffset
Horizontal offset of label.
The class is used as a container of context for various read/write operations on other objects...
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...
Shape transparency (deprecated)
static QgsGeometry prepareGeometry(const QgsGeometry &geometry, QgsRenderContext &context, const QgsCoordinateTransform &ct, const QgsGeometry &clipGeometry=QgsGeometry(), bool mergeLines=false)
Prepares a geometry for registration with PAL.
void setScale(int scale)
Sets the scaling used for the drop shadow (in percentage of original size).
Shape size is determined by adding a buffer margin around text.
bool isGeosValid(QgsGeometry::ValidityFlags flags=nullptr) const
Checks validity of the geometry using GEOS.
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:40
void setStrokeWidth(double width)
Sets the width of the shape&#39;s stroke (stroke).
Label on bottom-left of point.
double y
Definition: qgspoint.h:42
RotationType
Methods for determining the rotation of the background shape.
double maxCurvedCharAngleOut
Maximum angle between outside curved label characters (valid range -20.0 to -95.0) ...
double maximumScale
The maximum map scale (i.e.
QString leftDirectionSymbol
String to use for left direction arrows.
QPointF offset() const
Returns the offset used for drawing the background shape.
bool hasParserError() const
Returns true if an error occurred when parsing the input expression.
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.
QgsUnitTypes::RenderUnit repeatDistanceUnit
Units for repeating labels for a single feature.
QFont font() const
Returns the font used for rendering text.
bool diagramsEnabled() const
Returns whether the layer contains diagrams which are enabled and should be drawn.
double distance(double x, double y) const
Returns the distance between this point and a specified x, y coordinate.
Definition: qgspointxy.h:190
void setSizeUnit(QgsUnitTypes::RenderUnit unit)
Sets the units used for the shape&#39;s size.
QSizeF size() const
Returns the size of the background shape.
void readXml(const QDomElement &elem, const QgsReadWriteContext &context)
Read settings from a DOM element.
double opacity() const
Returns the buffer opacity.
virtual bool writeXml(QDomElement &collectionElem, const QgsPropertiesDefinition &definitions) const
Writes the current state of the property collection into an XML element.
bool isNull() const
Returns true if the geometry is null (ie, contains no underlying geometry accessible via geometry() )...
OperationResult transform(const QgsCoordinateTransform &ct, QgsCoordinateTransform::TransformDirection direction=QgsCoordinateTransform::ForwardTransform, bool transformZ=false) SIP_THROW(QgsCsException)
Transforms this geometry as described by the coordinate transform ct.
Qt::PenJoinStyle joinStyle() const
Returns the join style used for drawing the background shape.
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.
void registerFeature(QgsFeature &f, QgsRenderContext &context, QgsLabelFeature **labelFeature=nullptr, QgsGeometry obstacleGeometry=QgsGeometry())
Register a feature for labeling.
double angleOffset
Label rotation, in degrees clockwise.
virtual bool readXml(const QDomElement &collectionElem, const QgsPropertiesDefinition &definitions)
Reads property collection state from an XML element.
double getWidth() const
bool formatNumbers
Set to true to format numeric label text as numbers (e.g.
const QgsVectorSimplifyMethod & vectorSimplifyMethod() const
Added in QGIS v2.4.
A class to query the labeling structure at a given point (small wrapper around pal RTree class) ...
void readXml(QDomElement &elem, const QgsReadWriteContext &context)
Read settings from a DOM element.
Draw shadow under buffer.
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...
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.
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
int scale() const
Returns the scaling used for the drop shadow (in percentage of original size).
double tolerance() const
Gets the tolerance of simplification in map units. Represents the maximum distance in map units betwe...
QgsTextShadowSettings & shadow()
Returns a reference to the text drop shadow settings.
double convertToPainterUnits(double size, QgsUnitTypes::RenderUnit unit, const QgsMapUnitScale &scale=QgsMapUnitScale()) const
Converts a size from the specified units to painter units (pixels).
static void drawLabelCandidateRect(pal::LabelPosition *lp, QPainter *painter, const QgsMapToPixel *xform, QList< QgsLabelCandidate > *candidates=nullptr)
double y
Definition: qgspointxy.h:48
Qt::PenJoinStyle joinStyle() const
Returns the buffer join style.
void setStrokeColor(const QColor &color)
Sets the color used for outlining the background shape.
double rendererScale() const
Returns the renderer map scale.
void setSize(double size)
Sets the size of the buffer.
A class to represent a 2D point.
Definition: qgspointxy.h:43
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:278
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.
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.
bool contains(const QgsRectangle &rect) const
Returns true when rectangle contains other rectangle.
Definition: qgsrectangle.h:341
double convertToMapUnits(double size, QgsUnitTypes::RenderUnit unit, const QgsMapUnitScale &scale=QgsMapUnitScale()) const
Converts a size from the specified units to map units.
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.
static QPointF decodePoint(const QString &string)
Decodes a QSizeF from a string.
Candidates are placed in predefined positions around a point. Preference is given to positions with g...
double cost() const
Returns the candidate label position&#39;s geographical cost.
QgsRectangle boundingBox() const
Returns the bounding box of the geometry.
QuadrantPosition quadOffset
Sets the quadrant in which to offset labels from feature.
QgsUnitTypes::RenderUnit offsetUnits
Units for offsets of label.
QgsRectangle mapExtent() const
Returns the original extent of the map being rendered.
double size() const
Returns the size for rendered text.
void setOffsetUnit(QgsUnitTypes::RenderUnit units)
Sets the units used for the shape&#39;s offset.
static QgsProperty fromExpression(const QString &expression, bool isActive=true)
Returns a new ExpressionBasedProperty created from the specified expression.
Shadow transparency (deprecated)
double yOffset
Vertical offset of label.
Class that adds extra information to QgsLabelFeature for text labels.
Curve polygon geometry type.
QgsGeometry intersection(const QgsGeometry &geometry) const
Returns a geometry representing the points shared by this geometry and other.
void setBlurRadius(double blurRadius)
Sets the blur radius for the shadow.
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)
Label on top of point, slightly right of center.
bool addDirectionSymbol
If true, &#39;<&#39; or &#39;>&#39; (or custom strings set via leftDirectionSymbol and rightDirectionSymbol) will be ...
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:106
bool drawLabels
Whether to draw labels for this layer.
void setBackground(const QgsTextBackgroundSettings &backgroundSettings)
Sets the text&#39;s background settings.q.
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...
double scaleFactor() const
Returns the scaling factor for the render to convert painter units to physical sizes.
bool enabled() const
Returns whether the background is enabled.
const QgsTextFormat & format() const
Returns the label text formatting settings, e.g., font settings, buffer settings, etc...
FeaturePart * getFeaturePart()
Returns the feature corresponding to this labelposition.
double opacity() const
Returns the text&#39;s opacity.
QColor color() const
Returns the color that text will be rendered in.
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.
Percentage of another measurement (e.g., canvas size, feature size)
Definition: qgsunittypes.h:115
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...
QSizeF radii() const
Returns the radii used for rounding the corners of shapes.
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.
bool isShortCircuited() const
Returns true if the transform short circuits because the source and destination are equivalent...
int autoWrapLength
If non-zero, indicates that label text should be automatically wrapped to (ideally) the specified num...
QgsGeometry buffer(double distance, int segments) const
Returns a buffer region around this geometry having the given width and with a specified number of se...
bool enabled() const
Returns whether the shadow is enabled.
double blurRadius() const
Returns the blur radius for the shadow.
double mapRotation() const
Returns current map rotation in degrees.
double maxCurvedCharAngleIn
Maximum angle between inside curved label characters (valid range 20.0 to 60.0).
QgsCoordinateTransform ct
QColor color() const
Returns the color of the buffer.
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.
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.
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...
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.
bool contains(const QgsPointXY *p) const
Tests for containment of a point (uses GEOS)
The QgsMapSettings class contains configuration for rendering of the map.
static QString encodeColor(const QColor &color)
static bool staticWillUseLayer(QgsVectorLayer *layer)
called to find out whether the layer is used for labeling
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...
QColor strokeColor() const
Returns the color used for outlining the background shape.
bool conflictsWithObstacle() const
Returns whether the position is marked as conflicting with an obstacle feature.
Place direction symbols on left/right of label.
Perform transforms between map coordinates and device coordinates.
Definition: qgsmaptopixel.h:36
QList< QgsLabelPosition > labelsWithinRect(const QgsRectangle &r) const
Returns infos about labels within a given (map) rectangle.
void setBlendMode(QPainter::CompositionMode mode)
Sets the blending mode used for drawing the background shape.
Shape rotation is a fixed angle.
Property
Data definable properties.
No simplification can be applied.
ObstacleType obstacleType
Controls how features act as obstacles for labels.
const QgsAbstractGeometry * constGet() const
Returns a non-modifiable (const) reference to the underlying abstract geometry primitive.
static pal::LineArrangementFlags decodeLinePlacementFlags(const QString &string)
Decodes a string to set of line placement flags.
QString namedStyle() const
Returns the named style for the font used for rendering text (e.g., "bold").
bool displayAll
If true, all features will be labelled even when overlaps occur.
void setIsObstacle(bool enabled)
Sets whether the feature will act as an obstacle for labels.
bool enabled() const
Returns whether the buffer is enabled.
void setOffsetDistance(double distance)
Sets the distance for offsetting the position of the shadow from the text.
Label on left of point.
double offsetDistance() const
Returns the distance for offsetting the position of the shadow from the text.
static QString capitalize(const QString &string, Capitalization capitalization)
Converts a string by applying capitalization rules to the string.
Offset distance applies from point geometry.
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39
QString lastError() const
Returns an error string referring to the last error encountered either when this geometry was created...
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].
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.
void setBlendMode(QPainter::CompositionMode mode)
Sets the blending mode used for drawing the drop shadow.
QDomElement writeXml(QDomDocument &doc, const QgsReadWriteContext &context)
Write settings into a DOM element.
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.
SizeType sizeType() const
Returns the method used to determine the size of the background shape (e.g., fixed size or buffer aro...
std::unique_ptr< GEOSGeometry, GeosDeleter > unique_ptr
Scoped GEOS pointer.
Definition: qgsgeos.h:79
void setEnabled(bool enabled)
Sets whether the text shadow will be drawn.
void setEnabled(bool enabled)
Sets whether the text buffer will be drawn.
double getY(int i=0) const
Returns the down-left y coordinate.
QgsPointXY center() const
Returns the center point of the rectangle.
Definition: qgsrectangle.h:229
Horizontal alignment for data defined label position (Left, Center, Right)
QString svgFile() const
Returns the absolute path to the background SVG file, if set.
Arranges candidates in a circle around a point (or centroid of a polygon). Applies to point or polygo...
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.
Draw shadow under text.
QgsTextBackgroundSettings & background()
Returns a reference to the text background settings.
QgsTextBufferSettings & buffer()
Returns a reference to the text buffer settings.
double opacity() const
Returns the background shape&#39;s opacity.
Upside-down labels (90 <= angle < 270) are shown upright.
SizeType
Methods for determining the background shape size.
double mapUnitsPerPixel() const
Returns current map units per pixel.
OffsetType
Behavior modifier for label offset and distance, only applies in some label placement modes...
QgsFeature * mCurFeat
QgsAbstractGeometry::const_part_iterator const_parts_begin() const
Returns STL-style const iterator pointing to the first part of the geometry.
LabelPosition * getNextPart() const
static QPainter::CompositionMode decodeBlendMode(const QString &s)
void calculateLabelSize(const QFontMetricsF *fm, QString text, double &labelX, double &labelY, QgsFeature *f=nullptr, QgsRenderContext *context=nullptr)
void removeCustomProperty(const QString &key)
Remove a custom property from layer.
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.
QVariant customProperty(const QString &value, const QVariant &defaultValue=QVariant()) const
Read a custom property from layer.
A store for object properties.
Definition: qgsproperty.h:229
double getX(int i=0) const
Returns the down-left x coordinate.
void setRadii(QSizeF radii)
Sets the radii used for rounding the corners of shapes.
Implementation of GeometrySimplifier using the "MapToPixel" algorithm.
QgsMapUnitScale sizeMapUnitScale() const
Returns the map unit scale object for the size.
Point geometry type, with support for z-dimension and m-values.
Definition: qgspoint.h:37
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.
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...
double x
Definition: qgspointxy.h:47
Definition for a property.
Definition: qgsproperty.h:46
bool forceLocalOptimization() const
Gets where the simplification executes, after fetch the geometries from provider, or when supported...
Capitalization
Capitalization options.
int offsetAngle() const
Returns the angle for offsetting the position of the shadow from the text.
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.
Draw shadow below all text components.
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.
QgsExpressionContext & expressionContext()
Gets the expression context.
Label rotation (deprecated, for old project compatibility only)
static QSizeF decodeSize(const QString &string)
Decodes a QSizeF from a string.
double length() const
Returns the length of geometry using GEOS.
double getAlpha() const
Returns the angle to rotate text (in rad).
bool plusSign
Whether &#39;+&#39; signs should be prepended to positive numeric labels.
void setEnabled(bool enabled)
Sets whether the text background will be drawn.
unsigned int placementFlags
ShapeType
Background shape types.
QString wrapChar
Wrapping character string.
QgsPalLayerSettings & operator=(const QgsPalLayerSettings &s)
copy operator - only copies the permanent members
QgsGeometry simplify(double tolerance) const
Returns a simplified version of this geometry using a specified tolerance value.
GeometryType
The geometry types are used to group QgsWkbTypes::Type in a coarse way.
Definition: qgswkbtypes.h:138
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 process(const QString &input) const
Processes a given input string, applying any valid replacements which should be made using QgsStringR...
QString rightDirectionSymbol
String to use for right direction arrows.
bool preserveRotation
True if label rotation should be preserved during label pin/unpin operations.
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...
QgsExpression * getLabelExpression()
Returns the QgsExpression for this label settings.
Arranges candidates over a point (or centroid of a polygon), or at a preset offset from the point...
This class contains information how to simplify geometries fetched from a vector layer.
Contains information about the context of a rendering operation.
Shape rotation is offset from text rotation.
bool centroidWhole
True if feature centroid should be calculated from the whole feature, or false if only the visible pa...
QgsAbstractGeometry::const_part_iterator const_parts_end() const
Returns STL-style iterator pointing to the imaginary part after the last part of the geometry...
bool scaleVisibility
Set to true to limit label visibility to a range of scales.
QString asWkt(int precision=17) const
Exports the geometry to WKT.
The QgsLabelFeature class describes a feature that should be used within the labeling engine...
QVariant attribute(const QString &name) const
Lookup attribute value from attribute name.
Definition: qgsfeature.cpp:262
static geos::unique_ptr asGeos(const QgsGeometry &geometry, double precision=0)
Returns a geos geometry - caller takes ownership of the object (should be deleted with GEOSGeom_destr...
Definition: qgsgeos.cpp:166
QgsGeometry extentGeom
Property requires a string value.
Definition: qgsproperty.h:91
Mixed case, ie no change.
double getHeight() const
QgsMapUnitScale distMapUnitScale
Map unit scale for label feature distance.
QColor color() const
Returns the color of the drop shadow.
bool fitInPolygonOnly
True if only labels which completely fit within a polygon are allowed.
Container for settings relating to a text shadow.
QgsUnitTypes::RenderUnit sizeUnit() const
Returns the units for the size of rendered text.
void setMapSettings(const QgsMapSettings &settings)
Sets the map settings associated with the labeling run.
Container for settings relating to a text buffer.
void setFillColor(const QColor &color)
Sets the color used for filing the background shape.
static bool updateFontViaStyle(QFont &f, const QString &fontstyle, bool fallback=false)
Updates font with named style and retain all font properties.
OffsetType offsetType
Offset type for layer (only applies in certain placement modes)
double dist
Distance from feature to the label.
bool isMultipart() const
Returns true if WKB of the geometry is of WKBMulti* type.
QString asExpression() const
Returns an expression string representing the state of the property, or an empty string if the proper...
QDomElement writeXml(QDomDocument &doc, const QgsReadWriteContext &context) const
Write settings into a DOM element.
bool useSubstitutions
True if substitutions should be applied.
void setStrokeWidthUnit(QgsUnitTypes::RenderUnit units)
Sets the units used for the shape&#39;s stroke width.
static QStringList splitToGraphemes(const QString &text)
Splits a text string to a list of graphemes, which are the smallest allowable character divisions in ...
SimplifyHints simplifyHints() const
Gets the simplification hints of the vector layer managed.
double size() const
Returns the size of the buffer.
Class for doing transforms between two map coordinate systems.
LabelPosition is a candidate feature label position.
Definition: labelposition.h:55
const QgsMapToPixel & mapToPixel() const
Returns the context&#39;s map to pixel transform, which transforms between map coordinates and device coo...
Shape rotation is synced with text rotation.
void setShadowPlacement(QgsTextShadowSettings::ShadowPlacement placement)
Sets the placement for the drop shadow.
Label on right of point.
void setBlendMode(QPainter::CompositionMode mode)
Sets the blending mode used for drawing the text.
void setBuffer(const QgsTextBufferSettings &bufferSettings)
Sets the text&#39;s buffer settings.
static QgsMapUnitScale decodeMapUnitScale(const QString &str)
bool limitNumLabels
True if the number of labels drawn should be limited.
Draw shadow under background shape.
double rotation() const
Returns the rotation for the background shape, in degrees clockwise.
bool isExpression
True if this label is made from a expression string, e.g., FieldName || &#39;mm&#39;.
Convert all characters to lowercase.
QgsWkbTypes::GeometryType geometryType() const
Returns point, line or polygon.
void setShadow(const QgsTextShadowSettings &shadowSettings)
Sets the text&#39;s drop shadow settings.
QgsGeometry geometry
Definition: qgsfeature.h:67
void readXml(const QDomElement &elem)
Reads the collection state from an XML element.
Custom exception class for Coordinate Reference System related exceptions.
Definition: qgsexception.h:65
static QVector< QgsPalLayerSettings::PredefinedPointPosition > decodePredefinedPositionOrder(const QString &positionString)
Decodes a string to an ordered list of predefined point label positions.
static QString svgSymbolNameToPath(const QString &name, const QgsPathResolver &pathResolver)
Determines an SVG symbol&#39;s path from its name.
Y-coordinate data defined label position.
bool labelsEnabled() const
Returns whether the layer contains labels which are enabled and should be drawn.
Container for all settings relating to text rendering.
Path to an SVG file.
Definition: qgsproperty.h:77
void setBlendMode(QPainter::CompositionMode mode)
Sets the blending mode used for drawing the buffer.
bool centroidInside
True if centroid positioned labels must be placed inside their corresponding feature polygon...
Max scale (deprecated, for old project compatibility only)
double width() const
Returns the width of the rectangle.
Definition: qgsrectangle.h:201
void setSvgFile(const QString &file)
Sets the path to the background SVG file.
double opacity() const
Returns the shadow&#39;s opacity.
double lineHeight() const
Returns the line height for text.
const QgsPathResolver & pathResolver() const
Returns the path resolver for conversion between relative and absolute paths during rendering operati...
Represents a vector layer which manages a vector based data sets.
double minScale
The minimum scale, or 0.0 if unset.
void setOpacity(double opacity)
Sets the buffer opacity.
QgsPointXY transform(const QgsPointXY &p) const
Transform the point from map (world) coordinates to device coordinates.
Square - buffered sizes only.
QString updateDataDefinedString(const QString &value)
QgsWkbTypes::GeometryType type() const
Returns type of the geometry as a QgsWkbTypes::GeometryType.
void setOriginalValueVariable(const QVariant &value)
Sets the original value variable value for the context.
QString parserErrorString() const
Returns parser error.
double minFeatureSize
Minimum feature size (in millimeters) for a feature to be labelled.
double strokeWidth() const
Returns the width of the shape&#39;s stroke (stroke).
Vertical alignment for data defined label position (Bottom, Base, Half, Cap, Top) ...
static QgsGeometry collectGeometry(const QVector< QgsGeometry > &geometries)
Creates a new multipart geometry from a list of QgsGeometry objects.
QColor fillColor() const
Returns the color used for filing the background shape.
QgsGeometry mergeLines() const
Merges any connected lines in a LineString/MultiLineString geometry and converts them to single line ...
QString evalErrorString() const
Returns evaluation error.
void setRotationType(RotationType type)
Sets the method used for rotating the background shape.
int fontMinPixelSize
Minimum pixel size for showing rendered map unit labels (1 - 1000).
void setColor(const QColor &color)
Sets the color for the buffer.
int priority
Label priority.
bool labelPerPart
True if every part of a multi-part feature should be labeled.
QgsUnitTypes::RenderUnit distUnits
Units the distance from feature to the label.
static QString encodePenJoinStyle(Qt::PenJoinStyle style)
bool useMaxLineLengthForAutoWrap
If true, indicates that when auto wrapping label text the autoWrapLength length indicates the maximum...
void writeXml(QDomElement &elem, QDomDocument &doc) const
Writes the collection state to an XML element.
double area() const
Returns the area of the geometry using GEOS.
QgsMapUnitScale labelOffsetMapUnitScale
Map unit scale for label offset.
static QStringList splitToLines(const QString &text, const QString &wrapCharacter, int autoWrapLength=0, bool useMaxLineLengthWhenAutoWrapping=true)
Splits a text string to a list of separate lines, using a specified wrap character (wrapCharacter)...
QgsProperty property(int key) const override
Returns a matching property from the collection, if one exists.
2D size (width/height different)
Definition: qgsproperty.h:70
The QgsMargins class defines the four margins of a rectangle.
Definition: qgsmargins.h:37
void setOpacity(double opacity)
Sets the background shape&#39;s opacity.
RenderUnit
Rendering size units.
Definition: qgsunittypes.h:110
static QColor decodeColor(const QString &str)
Buffer transparency (deprecated)
double height() const
Returns the height of the rectangle.
Definition: qgsrectangle.h:208
int valueAsInt(int key, const QgsExpressionContext &context, int defaultValue=0, bool *ok=nullptr) const
Calculates the current value of the property with the specified key and interprets it as an integer...
QList< QgsLabelPosition > labelsAtPosition(const QgsPointXY &p) const
Returns infos about labels at a given (map) position.
Text transparency (deprecated)
bool isValid() const
Returns true if the coordinate transform is valid, ie both the source and destination CRS have been s...
double minimumScale
The minimum map scale (i.e.
SimplifyAlgorithm simplifyAlgorithm() const
Gets the local simplification algorithm of the vector layer managed.
QString fieldName
Name of field (or an expression) to use for label text.
bool fontLimitPixelSize
True if label sizes should be limited by pixel size.
ObstacleType
Valid obstacle types, which affect how features within the layer will act as obstacles for labels...
DirectionSymbols placeDirectionSymbol
Placement option for direction symbols.
void setOffsetAngle(int angle)
Sets the angle for offsetting the position of the shadow from the text.
void readFromLayer(QgsVectorLayer *layer)
Reads settings from a layer&#39;s custom properties (for QGIS 2.x projects).
Color with no alpha channel.
Definition: qgsproperty.h:65
void setRotation(double rotation)
Sets the rotation for the background shape, in degrees clockwise.
double x
Definition: qgspoint.h:41