QGIS API Documentation 4.3.0-Master (c8cdfe19d7b)
Loading...
Searching...
No Matches
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
20#include <cmath>
21#include <memory>
22
23#include "callouts/qgscallout.h"
25#include "qgsapplication.h"
26#include "qgscolorutils.h"
27#include "qgscurvepolygon.h"
28#include "qgsdatadefinedutils.h"
29#include "qgsexception.h"
30#include "qgsexpression.h"
31#include "qgsfontmanager.h"
32#include "qgsfontutils.h"
33#include "qgsgeometry.h"
35#include "qgslabelingengine.h"
36#include "qgslogger.h"
38#include "qgsmeshlayer.h"
39#include "qgsmessagelog.h"
40#include "qgsmultisurface.h"
41#include "qgsproperty.h"
42#include "qgsrasterlayer.h"
44#include "qgsscaleutils.h"
45#include "qgsstyle.h"
46#include "qgssymbollayerutils.h"
47#include "qgstextlabelfeature.h"
48#include "qgstextrenderer.h"
50#include "qgsunittypes.h"
51#include "qgsvariantutils.h"
52#include "qgsvectorlayer.h"
54#include "qgsvectortilelayer.h"
55
56#include <QApplication>
57#include <QByteArray>
58#include <QFontMetrics>
59#include <QMimeData>
60#include <QPainter>
61#include <QScreen>
62#include <QString>
63#include <QTextBoundaryFinder>
64#include <QTime>
65#include <QWidget>
66
67using namespace Qt::StringLiterals;
68
69using namespace pal;
70
71// -------------
72
73/* ND: Default point label position priority. These are set to match variants of the ideal placement priority described
74 in "Making Maps", Krygier & Wood (2011) (p216),
75 "Elements of Cartography", Robinson et al (1995)
76 and "Designing Better Maps", Brewer (2005) (p76)
77 Note that while they agree on positions 1-4, 5-8 are more contentious so I've selected these placements
78 based on my preferences, and to follow Krygier and Wood's placements more closer. (I'm not going to disagree
79 with Denis Wood on anything cartography related...!)
80*/
81typedef QVector< Qgis::LabelPredefinedPointPosition > PredefinedPointPositionVector;
84 DEFAULT_PLACEMENT_ORDER,
85 (
94 )
95)
96//debugging only - don't use these placements by default
97/* << static_cast< int >( QgsPalLayerSettings::Property::TopSlightlyLeft )
98<< static_cast< int >( QgsPalLayerSettings::Property::BottomSlightlyLeft );
99<< static_cast< int >( QgsPalLayerSettings::Property::TopMiddle )
100<< static_cast< int >( QgsPalLayerSettings::Property::BottomMiddle );*/
101
102Q_GLOBAL_STATIC( QgsPropertiesDefinition, sPropertyDefinitions )
103
104void QgsPalLayerSettings::initPropertyDefinitions()
105{
106 if ( !sPropertyDefinitions()->isEmpty() )
107 return;
108
109 const QString origin = u"labeling"_s;
110
111 *sPropertyDefinitions() = QgsPropertiesDefinition {
112 { static_cast< int >( QgsPalLayerSettings::Property::Size ), QgsPropertyDefinition( "Size", QObject::tr( "Font size" ), QgsPropertyDefinition::DoublePositive, origin ) },
113 { static_cast< int >( QgsPalLayerSettings::Property::Bold ), QgsPropertyDefinition( "Bold", QObject::tr( "Bold style" ), QgsPropertyDefinition::Boolean, origin ) },
114 { static_cast< int >( QgsPalLayerSettings::Property::Italic ), QgsPropertyDefinition( "Italic", QObject::tr( "Italic style" ), QgsPropertyDefinition::Boolean, origin ) },
115 { static_cast< int >( QgsPalLayerSettings::Property::Underline ), QgsPropertyDefinition( "Underline", QObject::tr( "Draw underline" ), QgsPropertyDefinition::Boolean, origin ) },
116 { static_cast< int >( QgsPalLayerSettings::Property::Color ), QgsPropertyDefinition( "Color", QObject::tr( "Text color" ), QgsPropertyDefinition::ColorNoAlpha, origin ) },
117 { static_cast< int >( QgsPalLayerSettings::Property::Strikeout ), QgsPropertyDefinition( "Strikeout", QObject::tr( "Draw strikeout" ), QgsPropertyDefinition::Boolean, origin ) },
118 { static_cast< int >( QgsPalLayerSettings::Property::Family ),
119 QgsPropertyDefinition(
120 "Family",
122 QObject::tr( "Font family" ),
123 QObject::tr( "string " )
124 + QObject::tr(
125 "[<b>family</b>|<b>family[foundry]</b>],<br>"
126 "e.g. Helvetica or Helvetica [Cronyx]"
127 ),
128 origin
129 ) },
130 { static_cast< int >( QgsPalLayerSettings::Property::FontStyle ),
131 QgsPropertyDefinition(
132 "FontStyle",
134 QObject::tr( "Font style" ),
135 QObject::tr( "string " )
136 + QObject::tr(
137 "[<b>font style name</b>|<b>Ignore</b>],<br>"
138 "e.g. Bold Condensed or Light Italic"
139 ),
140 origin
141 ) },
142 { static_cast< int >( QgsPalLayerSettings::Property::FontSizeUnit ), QgsPropertyDefinition( "FontSizeUnit", QObject::tr( "Font size units" ), QgsPropertyDefinition::RenderUnits, origin ) },
143 { static_cast< int >( QgsPalLayerSettings::Property::FontTransp ), QgsPropertyDefinition( "FontTransp", QObject::tr( "Text transparency" ), QgsPropertyDefinition::Opacity, origin ) },
144 { static_cast< int >( QgsPalLayerSettings::Property::FontOpacity ), QgsPropertyDefinition( "FontOpacity", QObject::tr( "Text opacity" ), QgsPropertyDefinition::Opacity, origin ) },
146 QgsPropertyDefinition( "FontStretchFactor", QObject::tr( "Font stretch factor" ), QgsPropertyDefinition::IntegerPositiveGreaterZero, origin ) },
147 { static_cast< int >( QgsPalLayerSettings::Property::FontCase ),
148 QgsPropertyDefinition( "FontCase", QgsPropertyDefinition::DataTypeString, QObject::tr( "Font case" ), QObject::tr( "string " ) + u"[<b>NoChange</b>|<b>Upper</b>|<br><b>Lower</b>|<b>Title</b>|<b>Capitalize</b>|<b>SmallCaps</b>|<b>AllSmallCaps</b>]"_s, origin ) },
149 { static_cast< int >( QgsPalLayerSettings::Property::FontLetterSpacing ), QgsPropertyDefinition( "FontLetterSpacing", QObject::tr( "Letter spacing" ), QgsPropertyDefinition::Double, origin ) },
150 { static_cast< int >( QgsPalLayerSettings::Property::FontWordSpacing ), QgsPropertyDefinition( "FontWordSpacing", QObject::tr( "Word spacing" ), QgsPropertyDefinition::Double, origin ) },
151 { static_cast< int >( QgsPalLayerSettings::Property::FontBlendMode ), QgsPropertyDefinition( "FontBlendMode", QObject::tr( "Text blend mode" ), QgsPropertyDefinition::BlendMode, origin ) },
152 { static_cast< int >( QgsPalLayerSettings::Property::MultiLineWrapChar ), QgsPropertyDefinition( "MultiLineWrapChar", QObject::tr( "Wrap character" ), QgsPropertyDefinition::String, origin ) },
153 { static_cast< int >( QgsPalLayerSettings::Property::AutoWrapLength ),
154 QgsPropertyDefinition( "AutoWrapLength", QObject::tr( "Automatic word wrap line length" ), QgsPropertyDefinition::IntegerPositive, origin ) },
155 { static_cast< int >( QgsPalLayerSettings::Property::MultiLineHeight ), QgsPropertyDefinition( "MultiLineHeight", QObject::tr( "Line height" ), QgsPropertyDefinition::DoublePositive, origin ) },
157 QgsPropertyDefinition( "MultiLineAlignment", QgsPropertyDefinition::DataTypeString, QObject::tr( "Line alignment" ), QObject::tr( "string " ) + "[<b>Left</b>|<b>Center</b>|<b>Right</b>|<b>Follow</b>]", origin ) },
159 QgsPropertyDefinition( "TabStopDistance", QgsPropertyDefinition::DataTypeNumeric, QObject::tr( "Tab stop distance(s)" ), QObject::tr( "Numeric distance or array of distances" ), origin ) },
161 QgsPropertyDefinition( "TextOrientation", QgsPropertyDefinition::DataTypeString, QObject::tr( "Text orientation" ), QObject::tr( "string " ) + "[<b>Horizontal</b>|<b>Vertical</b>|<b>RotationBased</b>]", origin ) },
162 { static_cast< int >( QgsPalLayerSettings::Property::DirSymbDraw ), QgsPropertyDefinition( "DirSymbDraw", QObject::tr( "Draw direction symbol" ), QgsPropertyDefinition::Boolean, origin ) },
163 { static_cast< int >( QgsPalLayerSettings::Property::DirSymbLeft ), QgsPropertyDefinition( "DirSymbLeft", QObject::tr( "Left direction symbol" ), QgsPropertyDefinition::String, origin ) },
164 { static_cast< int >( QgsPalLayerSettings::Property::DirSymbRight ), QgsPropertyDefinition( "DirSymbRight", QObject::tr( "Right direction symbol" ), QgsPropertyDefinition::String, origin ) },
166 QgsPropertyDefinition( "DirSymbPlacement", QgsPropertyDefinition::DataTypeString, QObject::tr( "Direction symbol placement" ), QObject::tr( "string " ) + "[<b>LeftRight</b>|<b>Above</b>|<b>Below</b>]", origin ) },
167 { static_cast< int >( QgsPalLayerSettings::Property::DirSymbReverse ), QgsPropertyDefinition( "DirSymbReverse", QObject::tr( "Reverse direction symbol" ), QgsPropertyDefinition::Boolean, origin ) },
168 { static_cast< int >( QgsPalLayerSettings::Property::NumFormat ), QgsPropertyDefinition( "NumFormat", QObject::tr( "Format as number" ), QgsPropertyDefinition::Boolean, origin ) },
169 { static_cast< int >( QgsPalLayerSettings::Property::NumDecimals ),
170 QgsPropertyDefinition( "NumDecimals", QObject::tr( "Number of decimal places" ), QgsPropertyDefinition::IntegerPositive, origin ) },
171 { static_cast< int >( QgsPalLayerSettings::Property::NumPlusSign ), QgsPropertyDefinition( "NumPlusSign", QObject::tr( "Draw + sign" ), QgsPropertyDefinition::Boolean, origin ) },
172 { static_cast< int >( QgsPalLayerSettings::Property::BufferDraw ), QgsPropertyDefinition( "BufferDraw", QObject::tr( "Draw buffer" ), QgsPropertyDefinition::Boolean, origin ) },
173 { static_cast< int >( QgsPalLayerSettings::Property::BufferSize ), QgsPropertyDefinition( "BufferSize", QObject::tr( "Symbol size" ), QgsPropertyDefinition::DoublePositive, origin ) },
174 { static_cast< int >( QgsPalLayerSettings::Property::BufferUnit ), QgsPropertyDefinition( "BufferUnit", QObject::tr( "Buffer units" ), QgsPropertyDefinition::RenderUnits, origin ) },
175 { static_cast< int >( QgsPalLayerSettings::Property::BufferColor ), QgsPropertyDefinition( "BufferColor", QObject::tr( "Buffer color" ), QgsPropertyDefinition::ColorNoAlpha, origin ) },
176 { static_cast< int >( QgsPalLayerSettings::Property::BufferTransp ), QgsPropertyDefinition( "BufferTransp", QObject::tr( "Buffer transparency" ), QgsPropertyDefinition::Opacity, origin ) },
177 { static_cast< int >( QgsPalLayerSettings::Property::BufferOpacity ), QgsPropertyDefinition( "BufferOpacity", QObject::tr( "Buffer opacity" ), QgsPropertyDefinition::Opacity, origin ) },
178 { static_cast< int >( QgsPalLayerSettings::Property::BufferJoinStyle ), QgsPropertyDefinition( "BufferJoinStyle", QObject::tr( "Buffer join style" ), QgsPropertyDefinition::PenJoinStyle, origin ) },
179 { static_cast< int >( QgsPalLayerSettings::Property::BufferBlendMode ), QgsPropertyDefinition( "BufferBlendMode", QObject::tr( "Buffer blend mode" ), QgsPropertyDefinition::BlendMode, origin ) },
180
181 { static_cast< int >( QgsPalLayerSettings::Property::MaskEnabled ), QgsPropertyDefinition( "MaskEnabled", QObject::tr( "Enable mask" ), QgsPropertyDefinition::Boolean, origin ) },
182 { static_cast< int >( QgsPalLayerSettings::Property::MaskBufferSize ), QgsPropertyDefinition( "MaskBufferSize", QObject::tr( "Mask buffer size" ), QgsPropertyDefinition::DoublePositive, origin ) },
183 { static_cast< int >( QgsPalLayerSettings::Property::MaskBufferUnit ), QgsPropertyDefinition( "MaskBufferUnit", QObject::tr( "Mask buffer unit" ), QgsPropertyDefinition::RenderUnits, origin ) },
184 { static_cast< int >( QgsPalLayerSettings::Property::MaskOpacity ), QgsPropertyDefinition( "MaskOpacity", QObject::tr( "Mask opacity" ), QgsPropertyDefinition::Opacity, origin ) },
185 { static_cast< int >( QgsPalLayerSettings::Property::MaskJoinStyle ), QgsPropertyDefinition( "MaskJoinStyle", QObject::tr( "Mask join style" ), QgsPropertyDefinition::PenJoinStyle, origin ) },
186
187 { static_cast< int >( QgsPalLayerSettings::Property::ShapeDraw ), QgsPropertyDefinition( "ShapeDraw", QObject::tr( "Draw shape" ), QgsPropertyDefinition::Boolean, origin ) },
188 { static_cast< int >( QgsPalLayerSettings::Property::ShapeKind ),
189 QgsPropertyDefinition(
190 "ShapeKind",
192 QObject::tr( "Shape type" ),
193 QObject::tr( "string " )
194 + QStringLiteral(
195 "[<b>Rectangle</b>|<b>Square</b>|<br>"
196 "<b>Ellipse</b>|<b>Circle</b>|<b>SVG</b>]"
197 ),
198 origin
199 ) },
200 { static_cast< int >( QgsPalLayerSettings::Property::ShapeSVGFile ), QgsPropertyDefinition( "ShapeSVGFile", QObject::tr( "Shape SVG path" ), QgsPropertyDefinition::SvgPath, origin ) },
201 { static_cast< int >( QgsPalLayerSettings::Property::ShapeSizeType ),
202 QgsPropertyDefinition( "ShapeSizeType", QgsPropertyDefinition::DataTypeString, QObject::tr( "Shape size type" ), QObject::tr( "string " ) + "[<b>Buffer</b>|<b>Fixed</b>]", origin ) },
203 { static_cast< int >( QgsPalLayerSettings::Property::ShapeSizeX ), QgsPropertyDefinition( "ShapeSizeX", QObject::tr( "Shape size (X)" ), QgsPropertyDefinition::Double, origin ) },
204 { static_cast< int >( QgsPalLayerSettings::Property::ShapeSizeY ), QgsPropertyDefinition( "ShapeSizeY", QObject::tr( "Shape size (Y)" ), QgsPropertyDefinition::Double, origin ) },
205 { static_cast< int >( QgsPalLayerSettings::Property::ShapeSizeUnits ), QgsPropertyDefinition( "ShapeSizeUnits", QObject::tr( "Shape size units" ), QgsPropertyDefinition::RenderUnits, origin ) },
207 QgsPropertyDefinition( "ShapeRotationType", QgsPropertyDefinition::DataTypeString, QObject::tr( "Shape rotation type" ), QObject::tr( "string " ) + "[<b>Sync</b>|<b>Offset</b>|<b>Fixed</b>]", origin ) },
208 { static_cast< int >( QgsPalLayerSettings::Property::ShapeRotation ), QgsPropertyDefinition( "ShapeRotation", QObject::tr( "Shape rotation" ), QgsPropertyDefinition::Rotation, origin ) },
209 { static_cast< int >( QgsPalLayerSettings::Property::ShapeOffset ), QgsPropertyDefinition( "ShapeOffset", QObject::tr( "Shape offset" ), QgsPropertyDefinition::Offset, origin ) },
211 QgsPropertyDefinition( "ShapeOffsetUnits", QObject::tr( "Shape offset units" ), QgsPropertyDefinition::RenderUnits, origin ) },
212 { static_cast< int >( QgsPalLayerSettings::Property::ShapeRadii ), QgsPropertyDefinition( "ShapeRadii", QObject::tr( "Shape radii" ), QgsPropertyDefinition::Size2D, origin ) },
213 { static_cast< int >( QgsPalLayerSettings::Property::ShapeRadiiUnits ), QgsPropertyDefinition( "ShapeRadiiUnits", QObject::tr( "Symbol radii units" ), QgsPropertyDefinition::RenderUnits, origin ) },
214 { static_cast< int >( QgsPalLayerSettings::Property::ShapeTransparency ), QgsPropertyDefinition( "ShapeTransparency", QObject::tr( "Shape transparency" ), QgsPropertyDefinition::Opacity, origin ) },
215 { static_cast< int >( QgsPalLayerSettings::Property::ShapeOpacity ), QgsPropertyDefinition( "ShapeOpacity", QObject::tr( "Shape opacity" ), QgsPropertyDefinition::Opacity, origin ) },
216 { static_cast< int >( QgsPalLayerSettings::Property::ShapeBlendMode ), QgsPropertyDefinition( "ShapeBlendMode", QObject::tr( "Shape blend mode" ), QgsPropertyDefinition::BlendMode, origin ) },
217 { static_cast< int >( QgsPalLayerSettings::Property::ShapeFillColor ), QgsPropertyDefinition( "ShapeFillColor", QObject::tr( "Shape fill color" ), QgsPropertyDefinition::ColorWithAlpha, origin ) },
219 QgsPropertyDefinition( "ShapeBorderColor", QObject::tr( "Shape stroke color" ), QgsPropertyDefinition::ColorWithAlpha, origin ) },
221 QgsPropertyDefinition( "ShapeBorderWidth", QObject::tr( "Shape stroke width" ), QgsPropertyDefinition::StrokeWidth, origin ) },
223 QgsPropertyDefinition( "ShapeBorderWidthUnits", QObject::tr( "Shape stroke width units" ), QgsPropertyDefinition::RenderUnits, origin ) },
224 { static_cast< int >( QgsPalLayerSettings::Property::ShapeJoinStyle ), QgsPropertyDefinition( "ShapeJoinStyle", QObject::tr( "Shape join style" ), QgsPropertyDefinition::PenJoinStyle, origin ) },
225 { static_cast< int >( QgsPalLayerSettings::Property::ShadowDraw ), QgsPropertyDefinition( "ShadowDraw", QObject::tr( "Draw shadow" ), QgsPropertyDefinition::Boolean, origin ) },
226 { static_cast< int >( QgsPalLayerSettings::Property::ShadowUnder ),
227 QgsPropertyDefinition(
228 "ShadowUnder",
230 QObject::tr( "Symbol size" ),
231 QObject::tr( "string " )
232 + QStringLiteral(
233 "[<b>Lowest</b>|<b>Text</b>|<br>"
234 "<b>Buffer</b>|<b>Background</b>]"
235 ),
236 origin
237 ) },
239 QgsPropertyDefinition( "ShadowOffsetAngle", QObject::tr( "Shadow offset angle" ), QgsPropertyDefinition::Rotation, origin ) },
241 QgsPropertyDefinition( "ShadowOffsetDist", QObject::tr( "Shadow offset distance" ), QgsPropertyDefinition::DoublePositive, origin ) },
243 QgsPropertyDefinition( "ShadowOffsetUnits", QObject::tr( "Shadow offset units" ), QgsPropertyDefinition::RenderUnits, origin ) },
244 { static_cast< int >( QgsPalLayerSettings::Property::ShadowRadius ), QgsPropertyDefinition( "ShadowRadius", QObject::tr( "Shadow blur radius" ), QgsPropertyDefinition::DoublePositive, origin ) },
246 QgsPropertyDefinition( "ShadowRadiusUnits", QObject::tr( "Shadow blur units" ), QgsPropertyDefinition::RenderUnits, origin ) },
248 QgsPropertyDefinition( "ShadowTransparency", QObject::tr( "Shadow transparency" ), QgsPropertyDefinition::Opacity, origin ) },
249 { static_cast< int >( QgsPalLayerSettings::Property::ShadowOpacity ), QgsPropertyDefinition( "ShadowOpacity", QObject::tr( "Shadow opacity" ), QgsPropertyDefinition::Opacity, origin ) },
250 { static_cast< int >( QgsPalLayerSettings::Property::ShadowScale ), QgsPropertyDefinition( "ShadowScale", QObject::tr( "Shadow scale" ), QgsPropertyDefinition::IntegerPositive, origin ) },
251 { static_cast< int >( QgsPalLayerSettings::Property::ShadowColor ), QgsPropertyDefinition( "ShadowColor", QObject::tr( "Shadow color" ), QgsPropertyDefinition::ColorNoAlpha, origin ) },
252 { static_cast< int >( QgsPalLayerSettings::Property::ShadowBlendMode ), QgsPropertyDefinition( "ShadowBlendMode", QObject::tr( "Shadow blend mode" ), QgsPropertyDefinition::BlendMode, origin ) },
253
254 { static_cast< int >( QgsPalLayerSettings::Property::CentroidWhole ),
255 QgsPropertyDefinition( "CentroidWhole", QgsPropertyDefinition::DataTypeString, QObject::tr( "Centroid of whole shape" ), QObject::tr( "string " ) + "[<b>Visible</b>|<b>Whole</b>]", origin ) },
256 { static_cast< int >( QgsPalLayerSettings::Property::OffsetQuad ),
257 QgsPropertyDefinition(
258 "OffsetQuad",
260 QObject::tr( "Offset quadrant" ),
261 QObject::tr( "int<br>" )
262 + QStringLiteral(
263 "[<b>0</b>=Above Left|<b>1</b>=Above|<b>2</b>=Above Right|<br>"
264 "<b>3</b>=Left|<b>4</b>=Over|<b>5</b>=Right|<br>"
265 "<b>6</b>=Below Left|<b>7</b>=Below|<b>8</b>=Below Right]"
266 ),
267 origin
268 ) },
269 { static_cast< int >( QgsPalLayerSettings::Property::OffsetXY ), QgsPropertyDefinition( "OffsetXY", QObject::tr( "Offset" ), QgsPropertyDefinition::Offset, origin ) },
270 { static_cast< int >( QgsPalLayerSettings::Property::OffsetUnits ), QgsPropertyDefinition( "OffsetUnits", QObject::tr( "Offset units" ), QgsPropertyDefinition::RenderUnits, origin ) },
271 { static_cast< int >( QgsPalLayerSettings::Property::LabelDistance ), QgsPropertyDefinition( "LabelDistance", QObject::tr( "Label distance" ), QgsPropertyDefinition::Double, origin ) },
272 { static_cast< int >( QgsPalLayerSettings::Property::DistanceUnits ), QgsPropertyDefinition( "DistanceUnits", QObject::tr( "Label distance units" ), QgsPropertyDefinition::RenderUnits, origin ) },
273 { static_cast< int >( QgsPalLayerSettings::Property::OffsetRotation ), QgsPropertyDefinition( "OffsetRotation", QObject::tr( "Offset rotation" ), QgsPropertyDefinition::Rotation, origin ) },
275 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 ) },
276 { static_cast< int >( QgsPalLayerSettings::Property::RepeatDistance ), QgsPropertyDefinition( "RepeatDistance", QObject::tr( "Repeat distance" ), QgsPropertyDefinition::DoublePositive, origin ) },
278 QgsPropertyDefinition( "RepeatDistanceUnit", QObject::tr( "Repeat distance unit" ), QgsPropertyDefinition::RenderUnits, origin ) },
279 { static_cast< int >( QgsPalLayerSettings::Property::OverrunDistance ), QgsPropertyDefinition( "OverrunDistance", QObject::tr( "Overrun distance" ), QgsPropertyDefinition::DoublePositive, origin ) },
281 QgsPropertyDefinition( "LineAnchorPercent", QObject::tr( "Line anchor percentage, as fraction from 0.0 to 1.0" ), QgsPropertyDefinition::Double0To1, origin ) },
283 QgsPropertyDefinition( "LineAnchorClipping", QgsPropertyDefinition::DataTypeString, QObject::tr( "Line anchor clipping mode" ), QObject::tr( "string " ) + u"[<b>Visible</b>|<b>Entire</b>]"_s, origin ) },
284 { static_cast< int >( QgsPalLayerSettings::Property::LineAnchorType ),
285 QgsPropertyDefinition( "LineAnchorType", QgsPropertyDefinition::DataTypeString, QObject::tr( "Line anchor type" ), QObject::tr( "string " ) + u"[<b>Hint</b>|<b>Strict</b>]"_s, origin ) },
287 QgsPropertyDefinition( "LineAnchorTextPoint", QgsPropertyDefinition::DataTypeString, QObject::tr( "Line anchor text point" ), QObject::tr( "string " ) + u"[<b>Follow</b>|<b>Start</b>|<b>Center</b>|<b>End</b>]"_s, origin ) },
289 QgsPropertyDefinition( "CurvedLabelMode", QgsPropertyDefinition::DataTypeString, QObject::tr( "Curved label mode" ), QObject::tr( "string " ) + u"[<b>Default</b>|<b>CharactersAtVertices</b>|<b>StretchCharacterSpacingToFit</b>|<b>StretchWordSpacingToFit</b>]"_s, origin ) },
290 { static_cast< int >( QgsPalLayerSettings::Property::Priority ),
291 QgsPropertyDefinition( "Priority", QgsPropertyDefinition::DataTypeNumeric, QObject::tr( "Label priority" ), QObject::tr( "double [0.0-10.0]" ), origin ) },
292 { static_cast< int >( QgsPalLayerSettings::Property::IsObstacle ), QgsPropertyDefinition( "IsObstacle", QObject::tr( "Feature is a label obstacle" ), QgsPropertyDefinition::Boolean, origin ) },
293 { static_cast< int >( QgsPalLayerSettings::Property::ObstacleFactor ),
294 QgsPropertyDefinition( "ObstacleFactor", QgsPropertyDefinition::DataTypeNumeric, QObject::tr( "Obstacle factor" ), QObject::tr( "double [0.0-10.0]" ), origin ) },
296 QgsPropertyDefinition(
297 "PredefinedPositionOrder",
299 QObject::tr( "Predefined position order" ),
300 QObject::tr( "Comma separated list of placements in order of priority<br>" )
301 + QStringLiteral(
302 "[<b>TopLeft</b>=Top left|<b>TopSlightyLeft</b>=Top, slightly left|<b>Top</b>=Top middle|<br>"
303 "<b>TopSlightlyRight</b>=Top, slightly right|<b>TopRight</b>=Top right|<br>"
304 "<b>Left</b>=Left|<b>Right</b>=Right|<br>"
305 "<b>BottomLeft</b>=Bottom left|<b>BottomSlightlyLeft</b>=Bottom, slightly left|<b>Bottom</b>=Bottom middle|<br>"
306 "<b>BottomSlightlyRight</b>=Bottom, slightly right|<b>BottomRight</b>=Bottom right|<b>OverPoint</b>=Over point]"
307 ),
308 origin
309 ) },
311 QgsPropertyDefinition(
312 "LinePlacementFlags",
314 QObject::tr( "Line placement options" ),
315 QObject::tr( "Comma separated list of placement options<br>" )
316 + QStringLiteral(
317 "[<b>OnLine</b>=On line|<b>AboveLine</b>=Above line|<b>BelowLine</b>=Below line|<br>"
318 "<b>LineOrientation</b>=Respect line orientation]"
319 ),
320 origin
321 ) },
323 QgsPropertyDefinition( "PolygonLabelOutside", QgsPropertyDefinition::DataTypeString, QObject::tr( "Label outside polygons" ), QObject::tr( "string " ) + "[<b>Yes</b> (allow placing outside)|<b>No</b> (never place outside)|<b>Force</b> (always place outside)]", origin ) },
324 { static_cast< int >( QgsPalLayerSettings::Property::PositionX ), QgsPropertyDefinition( "PositionX", QObject::tr( "Position (X)" ), QgsPropertyDefinition::Double, origin ) },
325 { static_cast< int >( QgsPalLayerSettings::Property::PositionY ), QgsPropertyDefinition( "PositionY", QObject::tr( "Position (Y)" ), QgsPropertyDefinition::Double, origin ) },
326 { static_cast< int >( QgsPalLayerSettings::Property::PositionPoint ),
327 QgsPropertyDefinition( "PositionPoint", QgsPropertyDefinition::DataTypeString, QObject::tr( "Position (point)" ), QObject::tr( "A point geometry" ), origin ) },
328 { static_cast< int >( QgsPalLayerSettings::Property::Hali ),
329 QgsPropertyDefinition( "Hali", QgsPropertyDefinition::DataTypeString, QObject::tr( "Horizontal alignment" ), QObject::tr( "string " ) + "[<b>Left</b>|<b>Center</b>|<b>Right</b>]", origin ) },
330 { static_cast< int >( QgsPalLayerSettings::Property::Vali ),
331 QgsPropertyDefinition(
332 "Vali",
334 QObject::tr( "Vertical alignment" ),
335 QObject::tr( "string " )
336 + QStringLiteral(
337 "[<b>Bottom</b>|<b>Base</b>|<br>"
338 "<b>Half</b>|<b>Cap</b>|<b>Top</b>]"
339 ),
340 origin
341 ) },
342 { static_cast< int >( QgsPalLayerSettings::Property::Rotation ), QgsPropertyDefinition( "Rotation", QObject::tr( "Label rotation (deprecated)" ), QgsPropertyDefinition::Rotation, origin ) },
343 { static_cast< int >( QgsPalLayerSettings::Property::LabelRotation ), QgsPropertyDefinition( "LabelRotation", QObject::tr( "Label rotation" ), QgsPropertyDefinition::Rotation, origin ) },
344 { static_cast< int >( QgsPalLayerSettings::Property::ScaleVisibility ), QgsPropertyDefinition( "ScaleVisibility", QObject::tr( "Scale based visibility" ), QgsPropertyDefinition::Boolean, origin ) },
345 { static_cast< int >( QgsPalLayerSettings::Property::MinScale ), QgsPropertyDefinition( "MinScale", QObject::tr( "Minimum scale (denominator)" ), QgsPropertyDefinition::Double, origin ) },
346 { static_cast< int >( QgsPalLayerSettings::Property::MaxScale ), QgsPropertyDefinition( "MaxScale", QObject::tr( "Maximum scale (denominator)" ), QgsPropertyDefinition::Double, origin ) },
347 { static_cast< int >( QgsPalLayerSettings::Property::MinimumScale ), QgsPropertyDefinition( "MinimumScale", QObject::tr( "Minimum scale (denominator)" ), QgsPropertyDefinition::Double, origin ) },
348 { static_cast< int >( QgsPalLayerSettings::Property::MaximumScale ), QgsPropertyDefinition( "MaximumScale", QObject::tr( "Maximum scale (denominator)" ), QgsPropertyDefinition::Double, origin ) },
349
350 { static_cast< int >( QgsPalLayerSettings::Property::FontLimitPixel ), QgsPropertyDefinition( "FontLimitPixel", QObject::tr( "Limit font pixel size" ), QgsPropertyDefinition::Boolean, origin ) },
351 { static_cast< int >( QgsPalLayerSettings::Property::FontMinPixel ), QgsPropertyDefinition( "FontMinPixel", QObject::tr( "Minimum pixel size" ), QgsPropertyDefinition::IntegerPositive, origin ) },
352 { static_cast< int >( QgsPalLayerSettings::Property::FontMaxPixel ), QgsPropertyDefinition( "FontMaxPixel", QObject::tr( "Maximum pixel size" ), QgsPropertyDefinition::IntegerPositive, origin ) },
353 { static_cast< int >( QgsPalLayerSettings::Property::ZIndex ), QgsPropertyDefinition( "ZIndex", QObject::tr( "Label z-index" ), QgsPropertyDefinition::Double, origin ) },
354 { static_cast< int >( QgsPalLayerSettings::Property::Show ), QgsPropertyDefinition( "Show", QObject::tr( "Show label" ), QgsPropertyDefinition::Boolean, origin ) },
355 { static_cast< int >( QgsPalLayerSettings::Property::AlwaysShow ), QgsPropertyDefinition( "AlwaysShow", QObject::tr( "Always show label" ), QgsPropertyDefinition::Boolean, origin ) },
356 { static_cast< int >( QgsPalLayerSettings::Property::CalloutDraw ), QgsPropertyDefinition( "CalloutDraw", QObject::tr( "Draw callout" ), QgsPropertyDefinition::Boolean, origin ) },
357 { static_cast< int >( QgsPalLayerSettings::Property::LabelAllParts ),
358 QgsPropertyDefinition( "LabelAllParts", QgsPropertyDefinition::DataTypeString, QObject::tr( "Multipart geometry behavior" ), QObject::tr( "string " ) + "[<b>LargestPartOnly</b>|<b>LabelEveryPart</b>|<b>SplitLabelTextLinesOverParts</b>]", origin ) },
360 QgsPropertyDefinition( "AllowDegradedPlacement", QObject::tr( "Allow inferior fallback placements" ), QgsPropertyDefinition::Boolean, origin ) },
362 QgsPropertyDefinition( "OverlapHandling", QgsPropertyDefinition::DataTypeString, QObject::tr( "Overlap handling" ), QObject::tr( "string " ) + "[<b>Prevent</b>|<b>AllowIfNeeded</b>|<b>AlwaysAllow</b>]", origin ) },
363
365 QgsPropertyDefinition( "WhitespaceCollisionHandling", QgsPropertyDefinition::DataTypeString, QObject::tr( "Whitespace collision handling" ), QObject::tr( "string " ) + u"[<b>TreatWhitespaceAsCollision</b>|<b>IgnoreWhitespaceCollisions</b>]"_s, origin ) },
366
367 { static_cast< int >( QgsPalLayerSettings::Property::MaximumDistance ), QgsPropertyDefinition( "MaximumDistance", QObject::tr( "Maximum distance" ), QgsPropertyDefinition::DoublePositive, origin ) },
368
370 QgsPropertyDefinition( "LabelMarginDistance", QObject::tr( "Minimum distance to other labels" ), QgsPropertyDefinition::DoublePositive, origin ) },
372 QgsPropertyDefinition( "RemoveDuplicates", QObject::tr( "Remove duplicate labels" ), QgsPropertyDefinition::Boolean, origin ) },
374 QgsPropertyDefinition( "RemoveDuplicateDistance", QObject::tr( "Minimum distance to duplicate labels" ), QgsPropertyDefinition::DoublePositive, origin ) },
375
376 };
377}
378
379Q_NOWARN_DEPRECATED_PUSH // because of deprecated members
381 : mCallout( QgsCalloutRegistry::defaultCallout() )
382{
383 mPointSettings.setPredefinedPositionOrder( *DEFAULT_PLACEMENT_ORDER() );
384
385 initPropertyDefinitions();
386
388}
390
391Q_NOWARN_DEPRECATED_PUSH // because of deprecated members
393 : mDataDefinedProperties( s.mDataDefinedProperties )
394{
395 *this = s;
396}
398
400{
401 if ( this == &s )
402 return *this;
403
404 // copy only permanent stuff
405
407
408 // text style
416
417 // text formatting
418 wrapChar = s.wrapChar;
423 decimals = s.decimals;
424 plusSign = s.plusSign;
425
426 // placement
428 mPolygonPlacementFlags = s.mPolygonPlacementFlags;
432 xOffset = s.xOffset;
433 yOffset = s.yOffset;
436 dist = s.dist;
442 mRotationUnit = s.mRotationUnit;
445 priority = s.priority;
449
450 // rendering
458
459 zIndex = s.zIndex;
460
461 mFormat = s.mFormat;
462 mDataDefinedProperties = s.mDataDefinedProperties;
463
464 mCallout.reset( s.mCallout ? s.mCallout->clone() : nullptr );
465
466 mPlacementSettings = s.mPlacementSettings;
467 mLineSettings = s.mLineSettings;
468 mPointSettings = s.mPointSettings;
469 mObstacleSettings = s.mObstacleSettings;
470 mThinningSettings = s.mThinningSettings;
471
476
477 mLegendString = s.mLegendString;
478
479 mUnplacedVisibility = s.mUnplacedVisibility;
480
481 return *this;
482}
483
484bool QgsPalLayerSettings::prepare( QgsRenderContext &context, QSet<QString> &attributeNames, const QgsFields &fields, const QgsMapSettings &mapSettings, const QgsCoordinateReferenceSystem &crs )
485{
486 if ( drawLabels )
487 {
488 if ( fieldName.isEmpty() )
489 {
490 return false;
491 }
492
493 if ( isExpression )
494 {
496 if ( exp.hasEvalError() )
497 {
498 QgsDebugMsgLevel( "Prepare error:" + exp.evalErrorString(), 4 );
499 return false;
500 }
501 }
502 else
503 {
504 // If we aren't an expression, we check to see if we can find the column.
505 if ( fields.lookupField( fieldName ) == -1 )
506 {
507 return false;
508 }
509 }
510 }
511
512 mCurFields = fields;
513
514 if ( drawLabels || mObstacleSettings.isObstacle() )
515 {
516 if ( drawLabels )
517 {
518 // add field indices for label's text, from expression or field
519 if ( isExpression )
520 {
521 // prepare expression for use in static_cast< int >( QgsPalLayerSettings::Property::registerFeature )()
523 exp->prepare( &context.expressionContext() );
524 if ( exp->hasEvalError() )
525 {
526 QgsDebugMsgLevel( "Prepare error:" + exp->evalErrorString(), 4 );
527 }
528 const auto referencedColumns = exp->referencedColumns();
529 for ( const QString &name : referencedColumns )
530 {
531 attributeNames.insert( name );
532 }
533 }
534 else
535 {
536 attributeNames.insert( fieldName );
537 }
538 }
539
540 mDataDefinedProperties.prepare( context.expressionContext() );
541 // add field indices of data defined expression or field
542 attributeNames.unite( dataDefinedProperties().referencedFields( context.expressionContext() ) );
543 }
544
545 // NOW INITIALIZE QgsPalLayerSettings
546
547 // TODO: ideally these (non-configuration) members should get out of QgsPalLayerSettings to QgsVectorLayerLabelProvider::prepare
548 // (together with registerFeature() & related methods) and QgsPalLayerSettings just stores config
549
550 // save the pal layer to our layer context (with some additional info)
551 fieldIndex = fields.lookupField( fieldName );
552
553 xform = &mapSettings.mapToPixel();
555 if ( context.coordinateTransform().isValid() )
556 // this is context for layer rendering
557 ct = context.coordinateTransform();
558 else
559 {
560 // otherwise fall back to creating our own CT
561 ct = QgsCoordinateTransform( crs, mapSettings.destinationCrs(), mapSettings.transformContext() );
562 }
563 ptZero = xform->toMapCoordinates( 0, 0 );
564 ptOne = xform->toMapCoordinates( 1, 0 );
565
566 // rect for clipping
567 QgsRectangle r1 = mapSettings.visibleExtent();
568 QgsDebugMsgLevel( u"Visible extent: %1"_s.arg( r1.toString() ), 2 );
569 QgsDebugMsgLevel( u"mapSetting extentBuffer: %1"_s.arg( mapSettings.extentBuffer() ), 2 );
570 r1.grow( mapSettings.extentBuffer() );
571 QgsDebugMsgLevel( u"Grown visible extent: %1"_s.arg( r1.toString() ), 2 );
573 QgsDebugMsgLevel( u"Extent geom %1"_s.arg( extentGeom.asWkt() ), 2 );
574
575 if ( !qgsDoubleNear( mapSettings.rotation(), 0.0 ) )
576 {
577 //PAL features are prerotated, so extent also needs to be unrotated
578 extentGeom.rotate( -mapSettings.rotation(), mapSettings.visibleExtent().center() );
579 QgsDebugMsgLevel( u"Rotated extent geom %1"_s.arg( extentGeom.asWkt() ), 2 );
580 }
581
583
585 {
586 mGeometryGeneratorExpression = QgsExpression( geometryGenerator );
587 mGeometryGeneratorExpression.prepare( &context.expressionContext() );
588 if ( mGeometryGeneratorExpression.hasParserError() )
589 {
590 QgsMessageLog::logMessage( mGeometryGeneratorExpression.parserErrorString(), QObject::tr( "Labeling" ) );
591 return false;
592 }
593
594 const auto referencedColumns = mGeometryGeneratorExpression.referencedColumns();
595 for ( const QString &name : referencedColumns )
596 {
597 attributeNames.insert( name );
598 }
599 }
600 attributeNames.unite( mFormat.referencedFields( context ) );
601
602 if ( mCallout )
603 {
604 const auto referencedColumns = mCallout->referencedFields( context );
605 for ( const QString &name : referencedColumns )
606 {
607 attributeNames.insert( name );
608 }
609 }
610
611 return true;
612}
613
614QSet<QString> QgsPalLayerSettings::referencedFields( const QgsRenderContext &context ) const
615{
616 QSet<QString> referenced;
617 if ( drawLabels )
618 {
619 if ( isExpression )
620 {
621 referenced.unite( QgsExpression( fieldName ).referencedColumns() );
622 }
623 else
624 {
625 referenced.insert( fieldName );
626 }
627 }
628
629 referenced.unite( mFormat.referencedFields( context ) );
630
631 // calling referencedFields() with ignoreContext=true because in our expression context
632 // we do not have valid QgsFields yet - because of that the field names from expressions
633 // wouldn't get reported
634 referenced.unite( mDataDefinedProperties.referencedFields( context.expressionContext(), true ) );
635
637 {
638 QgsExpression geomGeneratorExpr( geometryGenerator );
639 referenced.unite( geomGeneratorExpr.referencedColumns() );
640 }
641
642 if ( mCallout )
643 {
644 referenced.unite( mCallout->referencedFields( context ) );
645 }
646
647 return referenced;
648}
649
651{
652 if ( mRenderStarted )
653 {
654 qWarning( "Start render called for when a previous render was already underway!!" );
655 return;
656 }
657
658 switch ( placement )
659 {
662 {
663 // force horizontal orientation, other orientation modes aren't unsupported for curved placement
664 mFormat.setOrientation( Qgis::TextOrientation::Horizontal );
665 mDataDefinedProperties.property( QgsPalLayerSettings::Property::TextOrientation ).setActive( false );
666 break;
667 }
668
676 break;
677 }
678
679 if ( mCallout )
680 {
681 mCallout->startRender( context );
682 }
683
684 mRenderStarted = true;
685}
686
688{
689 if ( !mRenderStarted )
690 {
691 qWarning( "Stop render called for QgsPalLayerSettings without a startRender call!" );
692 return;
693 }
694
695 if ( mCallout )
696 {
697 mCallout->stopRender( context );
698 }
699
700 mRenderStarted = false;
701}
702
704{
705 return mFormat.containsAdvancedEffects() || mCallout->containsAdvancedEffects();
706}
707
709{
710 if ( mRenderStarted )
711 {
712 qWarning( "stopRender was not called on QgsPalLayerSettings object!" );
713 }
714}
715
716
718{
719 initPropertyDefinitions();
720 return *sPropertyDefinitions();
721}
722
724{
725 if ( !expression )
726 {
727 expression = std::make_unique<QgsExpression>( fieldName );
728 }
729 return expression.get();
730}
731
733{
734 return mRotationUnit;
735}
736
738{
739 mRotationUnit = angleUnit;
740}
741
742QString updateDataDefinedString( const QString &value )
743{
744 // TODO: update or remove this when project settings for labeling are migrated to better XML layout
745 QString newValue = value;
746 if ( !value.isEmpty() && !value.contains( "~~"_L1 ) )
747 {
748 QStringList values;
749 values << u"1"_s; // all old-style values are active if not empty
750 values << u"0"_s;
751 values << QString();
752 values << value; // all old-style values are only field names
753 newValue = values.join( "~~"_L1 );
754 }
755
756 return newValue;
757}
758
759void QgsPalLayerSettings::readOldDataDefinedProperty( QgsVectorLayer *layer, QgsPalLayerSettings::Property p )
760{
761 QString newPropertyName = "labeling/dataDefined/" + sPropertyDefinitions()->value( static_cast< int >( p ) ).name();
762 QVariant newPropertyField = layer->customProperty( newPropertyName, QVariant() );
763
764 if ( !newPropertyField.isValid() )
765 return;
766
767 QString ddString = newPropertyField.toString();
768
769 if ( !ddString.isEmpty() && ddString != "0~~0~~~~"_L1 )
770 {
771 // TODO: update this when project settings for labeling are migrated to better XML layout
772 QString newStyleString = updateDataDefinedString( ddString );
773 QStringList ddv = newStyleString.split( u"~~"_s );
774
775 bool active = ddv.at( 0 ).toInt();
776 if ( ddv.at( 1 ).toInt() )
777 {
778 mDataDefinedProperties.setProperty( p, QgsProperty::fromExpression( ddv.at( 2 ), active ) );
779 }
780 else
781 {
782 mDataDefinedProperties.setProperty( p, QgsProperty::fromField( ddv.at( 3 ), active ) );
783 }
784 }
785 else
786 {
787 // remove unused properties
788 layer->removeCustomProperty( newPropertyName );
789 }
790}
791
792void QgsPalLayerSettings::readOldDataDefinedPropertyMap( QgsVectorLayer *layer, QDomElement *parentElem )
793{
794 if ( !layer && !parentElem )
795 {
796 return;
797 }
798
799 QgsPropertiesDefinition::const_iterator i = sPropertyDefinitions()->constBegin();
800 for ( ; i != sPropertyDefinitions()->constEnd(); ++i )
801 {
802 if ( layer )
803 {
804 // reading from layer's custom properties
805 readOldDataDefinedProperty( layer, static_cast< Property >( i.key() ) );
806 }
807 else if ( parentElem )
808 {
809 // reading from XML
810 QDomElement e = parentElem->firstChildElement( i.value().name() );
811 if ( !e.isNull() )
812 {
813 bool active = e.attribute( u"active"_s ).compare( "true"_L1, Qt::CaseInsensitive ) == 0;
814 bool isExpression = e.attribute( u"useExpr"_s ).compare( "true"_L1, Qt::CaseInsensitive ) == 0;
815 if ( isExpression )
816 {
817 mDataDefinedProperties.setProperty( i.key(), QgsProperty::fromExpression( e.attribute( u"expr"_s ), active ) );
818 }
819 else
820 {
821 mDataDefinedProperties.setProperty( i.key(), QgsProperty::fromField( e.attribute( u"field"_s ), active ) );
822 }
823 }
824 }
825 }
826}
827
828void QgsPalLayerSettings::readFromLayerCustomProperties( QgsVectorLayer *layer )
829{
830 if ( layer->customProperty( u"labeling"_s ).toString() != "pal"_L1 )
831 {
832 if ( layer->geometryType() == Qgis::GeometryType::Point )
834
835 // for polygons the "over point" (over centroid) placement is better than the default
836 // "around point" (around centroid) which is more suitable for points
839
840 return; // there's no information available
841 }
842
843 // NOTE: set defaults for newly added properties, for backwards compatibility
844
845 drawLabels = layer->customProperty( u"labeling/drawLabels"_s, true ).toBool();
846
847 mFormat.readFromLayer( layer );
848
849 // text style
850 fieldName = layer->customProperty( u"labeling/fieldName"_s ).toString();
851 isExpression = layer->customProperty( u"labeling/isExpression"_s ).toBool();
853 previewBkgrdColor = QColor( layer->customProperty( u"labeling/previewBkgrdColor"_s, QVariant( "#ffffff" ) ).toString() );
855 QDomDocument doc( u"substitutions"_s );
856 doc.setContent( layer->customProperty( u"labeling/substitutions"_s ).toString() );
857 QDomElement replacementElem = doc.firstChildElement( u"substitutions"_s );
858 substitutions.readXml( replacementElem );
859 useSubstitutions = layer->customProperty( u"labeling/useSubstitutions"_s ).toBool();
860
861 // text formatting
862 wrapChar = layer->customProperty( u"labeling/wrapChar"_s ).toString();
863 autoWrapLength = layer->customProperty( u"labeling/autoWrapLength"_s ).toInt();
864 useMaxLineLengthForAutoWrap = layer->customProperty( u"labeling/useMaxLineLengthForAutoWrap"_s, u"1"_s ).toBool();
865
867 layer->customProperty( u"labeling/multilineAlign"_s, QVariant( static_cast< int >( Qgis::LabelMultiLineAlignment::FollowPlacement ) ) ).toUInt()
868 );
869 mLineSettings.setAddDirectionSymbol( layer->customProperty( u"labeling/addDirectionSymbol"_s ).toBool() );
870 mLineSettings.setLeftDirectionSymbol( layer->customProperty( u"labeling/leftDirectionSymbol"_s, QVariant( "<" ) ).toString() );
871 mLineSettings.setRightDirectionSymbol( layer->customProperty( u"labeling/rightDirectionSymbol"_s, QVariant( ">" ) ).toString() );
872 mLineSettings.setReverseDirectionSymbol( layer->customProperty( u"labeling/reverseDirectionSymbol"_s ).toBool() );
873 mLineSettings.setDirectionSymbolPlacement(
875 layer->customProperty( u"labeling/placeDirectionSymbol"_s, QVariant( static_cast< int >( QgsLabelLineSettings::DirectionSymbolPlacement::SymbolLeftRight ) ) ).toUInt()
876 )
877 );
878 formatNumbers = layer->customProperty( u"labeling/formatNumbers"_s ).toBool();
879 decimals = layer->customProperty( u"labeling/decimals"_s ).toInt();
880 plusSign = layer->customProperty( u"labeling/plussign"_s ).toBool();
881
882 // placement
883 placement = static_cast< Qgis::LabelPlacement >( layer->customProperty( u"labeling/placement"_s ).toInt() );
884 mLineSettings.setPlacementFlags( static_cast< Qgis::LabelLinePlacementFlags >( layer->customProperty( u"labeling/placementFlags"_s ).toUInt() ) );
885 centroidWhole = layer->customProperty( u"labeling/centroidWhole"_s, QVariant( false ) ).toBool();
886 centroidInside = layer->customProperty( u"labeling/centroidInside"_s, QVariant( false ) ).toBool();
887
888 QVector<Qgis::LabelPredefinedPointPosition> predefinedPositionOrder = QgsLabelingUtils::decodePredefinedPositionOrder( layer->customProperty( u"labeling/predefinedPositionOrder"_s ).toString() );
889 if ( predefinedPositionOrder.isEmpty() )
890 predefinedPositionOrder = *DEFAULT_PLACEMENT_ORDER();
891 mPointSettings.setPredefinedPositionOrder( predefinedPositionOrder );
892
893 fitInPolygonOnly = layer->customProperty( u"labeling/fitInPolygonOnly"_s, QVariant( false ) ).toBool();
894 dist = layer->customProperty( u"labeling/dist"_s ).toDouble();
895 distUnits = layer->customProperty( u"labeling/distInMapUnits"_s ).toBool() ? Qgis::RenderUnit::MapUnits : Qgis::RenderUnit::Millimeters;
896 if ( layer->customProperty( u"labeling/distMapUnitScale"_s ).toString().isEmpty() )
897 {
898 //fallback to older property
899 double oldMin = layer->customProperty( u"labeling/distMapUnitMinScale"_s, 0.0 ).toDouble();
900 distMapUnitScale.minScale = !qgsDoubleNear( oldMin, 0.0 ) ? 1.0 / oldMin : 0;
901 double oldMax = layer->customProperty( u"labeling/distMapUnitMaxScale"_s, 0.0 ).toDouble();
902 distMapUnitScale.maxScale = !qgsDoubleNear( oldMax, 0.0 ) ? 1.0 / oldMax : 0;
903 }
904 else
905 {
906 distMapUnitScale = QgsSymbolLayerUtils::decodeMapUnitScale( layer->customProperty( u"labeling/distMapUnitScale"_s ).toString() );
907 }
908 offsetType = static_cast< Qgis::LabelOffsetType >( layer->customProperty( u"labeling/offsetType"_s, QVariant( static_cast< int >( Qgis::LabelOffsetType::FromPoint ) ) ).toUInt() );
909 mPointSettings.setQuadrant(
910 static_cast< Qgis::LabelQuadrantPosition >( layer->customProperty( u"labeling/quadOffset"_s, QVariant( static_cast< int >( Qgis::LabelQuadrantPosition::Over ) ) ).toUInt() )
911 );
912 xOffset = layer->customProperty( u"labeling/xOffset"_s, QVariant( 0.0 ) ).toDouble();
913 yOffset = layer->customProperty( u"labeling/yOffset"_s, QVariant( 0.0 ) ).toDouble();
914 if ( layer->customProperty( u"labeling/labelOffsetInMapUnits"_s, QVariant( true ) ).toBool() )
916 else
918
919 if ( layer->customProperty( u"labeling/labelOffsetMapUnitScale"_s ).toString().isEmpty() )
920 {
921 //fallback to older property
922 double oldMin = layer->customProperty( u"labeling/labelOffsetMapUnitMinScale"_s, 0.0 ).toDouble();
923 labelOffsetMapUnitScale.minScale = !qgsDoubleNear( oldMin, 0.0 ) ? 1.0 / oldMin : 0;
924 double oldMax = layer->customProperty( u"labeling/labelOffsetMapUnitMaxScale"_s, 0.0 ).toDouble();
925 labelOffsetMapUnitScale.maxScale = !qgsDoubleNear( oldMax, 0 ) ? 1.0 / oldMax : 0;
926 }
927 else
928 {
929 labelOffsetMapUnitScale = QgsSymbolLayerUtils::decodeMapUnitScale( layer->customProperty( u"labeling/labelOffsetMapUnitScale"_s ).toString() );
930 }
931
932 QVariant tempAngle = layer->customProperty( u"labeling/angleOffset"_s, QVariant() );
933 if ( tempAngle.isValid() )
934 {
935 double oldAngle = layer->customProperty( u"labeling/angleOffset"_s, QVariant( 0.0 ) ).toDouble();
936 angleOffset = std::fmod( 360 - oldAngle, 360.0 );
937 }
938 else
939 {
940 angleOffset = layer->customProperty( u"labeling/rotationAngle"_s, QVariant( 0.0 ) ).toDouble();
941 }
942
943 preserveRotation = layer->customProperty( u"labeling/preserveRotation"_s, QVariant( true ) ).toBool();
944 mRotationUnit = layer->customEnumProperty( u"labeling/rotationUnit"_s, Qgis::AngleUnit::Degrees );
945 maxCurvedCharAngleIn = layer->customProperty( u"labeling/maxCurvedCharAngleIn"_s, QVariant( 25.0 ) ).toDouble();
946 maxCurvedCharAngleOut = layer->customProperty( u"labeling/maxCurvedCharAngleOut"_s, QVariant( -25.0 ) ).toDouble();
947 priority = layer->customProperty( u"labeling/priority"_s ).toInt();
948 repeatDistance = layer->customProperty( u"labeling/repeatDistance"_s, 0.0 ).toDouble();
949 switch ( layer->customProperty( u"labeling/repeatDistanceUnit"_s, QVariant( 1 ) ).toUInt() )
950 {
951 case 0:
953 break;
954 case 1:
956 break;
957 case 2:
959 break;
960 case 3:
962 break;
963 }
964 if ( layer->customProperty( u"labeling/repeatDistanceMapUnitScale"_s ).toString().isEmpty() )
965 {
966 //fallback to older property
967 double oldMin = layer->customProperty( u"labeling/repeatDistanceMapUnitMinScale"_s, 0.0 ).toDouble();
968 repeatDistanceMapUnitScale.minScale = !qgsDoubleNear( oldMin, 0 ) ? 1.0 / oldMin : 0;
969 double oldMax = layer->customProperty( u"labeling/repeatDistanceMapUnitMaxScale"_s, 0.0 ).toDouble();
970 repeatDistanceMapUnitScale.maxScale = !qgsDoubleNear( oldMax, 0 ) ? 1.0 / oldMax : 0;
971 }
972 else
973 {
974 repeatDistanceMapUnitScale = QgsSymbolLayerUtils::decodeMapUnitScale( layer->customProperty( u"labeling/repeatDistanceMapUnitScale"_s ).toString() );
975 }
976
977 // rendering
978 double scalemn = layer->customProperty( u"labeling/scaleMin"_s, QVariant( 0 ) ).toDouble();
979 double scalemx = layer->customProperty( u"labeling/scaleMax"_s, QVariant( 0 ) ).toDouble();
980
981 // fix for scale visibility limits being keyed off of just its values in the past (<2.0)
982 QVariant scalevis = layer->customProperty( u"labeling/scaleVisibility"_s, QVariant() );
983 if ( scalevis.isValid() )
984 {
985 scaleVisibility = scalevis.toBool();
986 maximumScale = scalemn;
987 minimumScale = scalemx;
988 }
989 else if ( scalemn > 0 || scalemx > 0 )
990 {
991 scaleVisibility = true;
992 maximumScale = scalemn;
993 minimumScale = scalemx;
994 }
995 else
996 {
997 // keep scaleMin and scaleMax at new 1.0 defaults (1 and 10000000, were 0 and 0)
998 scaleVisibility = false;
999 }
1000
1001
1002 fontLimitPixelSize = layer->customProperty( u"labeling/fontLimitPixelSize"_s, QVariant( false ) ).toBool();
1003 fontMinPixelSize = layer->customProperty( u"labeling/fontMinPixelSize"_s, QVariant( 0 ) ).toInt();
1004 fontMaxPixelSize = layer->customProperty( u"labeling/fontMaxPixelSize"_s, QVariant( 10000 ) ).toInt();
1005 if ( layer->customProperty( u"labeling/displayAll"_s, QVariant( false ) ).toBool() )
1006 {
1007 mPlacementSettings.setOverlapHandling( Qgis::LabelOverlapHandling::AllowOverlapIfRequired );
1008 mPlacementSettings.setAllowDegradedPlacement( true );
1009 }
1010 else
1011 {
1012 mPlacementSettings.setOverlapHandling( Qgis::LabelOverlapHandling::PreventOverlap );
1013 mPlacementSettings.setAllowDegradedPlacement( false );
1014 }
1016 layer->customProperty( u"labeling/upsidedownLabels"_s, QVariant( static_cast< int >( Qgis::UpsideDownLabelHandling::FlipUpsideDownLabels ) ) ).toUInt()
1017 );
1018
1019 mLineSettings.setMergeLines( layer->customProperty( u"labeling/mergeLines"_s ).toBool() );
1020 mThinningSettings.setMinimumFeatureSize( layer->customProperty( u"labeling/minFeatureSize"_s ).toDouble() );
1021 mThinningSettings.setLimitNumberLabelsEnabled( layer->customProperty( u"labeling/limitNumLabels"_s, QVariant( false ) ).toBool() );
1022 mThinningSettings.setMaximumNumberLabels( layer->customProperty( u"labeling/maxNumLabels"_s, QVariant( 2000 ) ).toInt() );
1023 mObstacleSettings.setIsObstacle( layer->customProperty( u"labeling/obstacle"_s, QVariant( true ) ).toBool() );
1024 mObstacleSettings.setFactor( layer->customProperty( u"labeling/obstacleFactor"_s, QVariant( 1.0 ) ).toDouble() );
1025 mObstacleSettings.setType(
1027 layer->customProperty( u"labeling/obstacleType"_s, QVariant( static_cast< int >( QgsLabelObstacleSettings::ObstacleType::PolygonInterior ) ) ).toUInt()
1028 )
1029 );
1030 zIndex = layer->customProperty( u"labeling/zIndex"_s, QVariant( 0.0 ) ).toDouble();
1031
1032 mDataDefinedProperties.clear();
1033 if ( layer->customProperty( u"labeling/ddProperties"_s ).isValid() )
1034 {
1035 QDomDocument doc( u"dd"_s );
1036 doc.setContent( layer->customProperty( u"labeling/ddProperties"_s ).toString() );
1037 QDomElement elem = doc.firstChildElement( u"properties"_s );
1038 mDataDefinedProperties.readXml( elem, *sPropertyDefinitions() );
1039 }
1040 else
1041 {
1042 // read QGIS 2.x style data defined properties
1043 readOldDataDefinedPropertyMap( layer, nullptr );
1044 }
1045 // upgrade older data defined settings
1046 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::FontTransp ) )
1047 {
1048 mDataDefinedProperties
1049 .setProperty( QgsPalLayerSettings::Property::FontOpacity, QgsProperty::fromExpression( u"100 - (%1)"_s.arg( mDataDefinedProperties.property( QgsPalLayerSettings::Property::FontTransp ).asExpression() ) ) );
1050 mDataDefinedProperties.setProperty( QgsPalLayerSettings::Property::FontTransp, QgsProperty() );
1051 }
1052 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::BufferTransp ) )
1053 {
1054 mDataDefinedProperties
1055 .setProperty( QgsPalLayerSettings::Property::BufferOpacity, QgsProperty::fromExpression( u"100 - (%1)"_s.arg( mDataDefinedProperties.property( QgsPalLayerSettings::Property::BufferTransp ).asExpression() ) ) );
1056 mDataDefinedProperties.setProperty( QgsPalLayerSettings::Property::BufferTransp, QgsProperty() );
1057 }
1058 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::ShapeTransparency ) )
1059 {
1060 mDataDefinedProperties
1061 .setProperty( QgsPalLayerSettings::Property::ShapeOpacity, QgsProperty::fromExpression( u"100 - (%1)"_s.arg( mDataDefinedProperties.property( QgsPalLayerSettings::Property::ShapeTransparency ).asExpression() ) ) );
1062 mDataDefinedProperties.setProperty( QgsPalLayerSettings::Property::ShapeTransparency, QgsProperty() );
1063 }
1064 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::ShadowTransparency ) )
1065 {
1066 mDataDefinedProperties
1067 .setProperty( QgsPalLayerSettings::Property::ShadowOpacity, QgsProperty::fromExpression( u"100 - (%1)"_s.arg( mDataDefinedProperties.property( QgsPalLayerSettings::Property::ShadowTransparency ).asExpression() ) ) );
1068 mDataDefinedProperties.setProperty( QgsPalLayerSettings::Property::ShadowTransparency, QgsProperty() );
1069 }
1070 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::Rotation ) )
1071 {
1072 mDataDefinedProperties
1073 .setProperty( QgsPalLayerSettings::Property::LabelRotation, QgsProperty::fromExpression( u"360 - (%1)"_s.arg( mDataDefinedProperties.property( QgsPalLayerSettings::Property::Rotation ).asExpression() ) ) );
1074 mDataDefinedProperties.setProperty( QgsPalLayerSettings::Property::Rotation, QgsProperty() );
1075 }
1076 // older 2.x projects had min/max scale flipped - so change them here.
1077 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::MinScale ) )
1078 {
1079 mDataDefinedProperties.setProperty( QgsPalLayerSettings::Property::MaximumScale, mDataDefinedProperties.property( QgsPalLayerSettings::Property::MinScale ) );
1080 mDataDefinedProperties.setProperty( QgsPalLayerSettings::Property::MinScale, QgsProperty() );
1081 }
1082 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::MaxScale ) )
1083 {
1084 mDataDefinedProperties.setProperty( QgsPalLayerSettings::Property::MinimumScale, mDataDefinedProperties.property( QgsPalLayerSettings::Property::MaxScale ) );
1085 mDataDefinedProperties.setProperty( QgsPalLayerSettings::Property::MaxScale, QgsProperty() );
1086 }
1087}
1088
1089void QgsPalLayerSettings::readXml( const QDomElement &elem, const QgsReadWriteContext &context )
1090{
1091 // text style
1092 QDomElement textStyleElem = elem.firstChildElement( u"text-style"_s );
1093 fieldName = textStyleElem.attribute( u"fieldName"_s );
1094 isExpression = textStyleElem.attribute( u"isExpression"_s ).toInt();
1095
1096 mFormat.readXml( elem, context );
1098 previewBkgrdColor = QColor( textStyleElem.attribute( u"previewBkgrdColor"_s, u"#ffffff"_s ) );
1100 substitutions.readXml( textStyleElem.firstChildElement( u"substitutions"_s ) );
1101 useSubstitutions = textStyleElem.attribute( u"useSubstitutions"_s ).toInt();
1102 mLegendString = textStyleElem.attribute( u"legendString"_s, QObject::tr( "Aa" ) );
1103
1104 // text formatting
1105 QDomElement textFormatElem = elem.firstChildElement( u"text-format"_s );
1106 wrapChar = textFormatElem.attribute( u"wrapChar"_s );
1107 autoWrapLength = textFormatElem.attribute( u"autoWrapLength"_s, u"0"_s ).toInt();
1108 useMaxLineLengthForAutoWrap = textFormatElem.attribute( u"useMaxLineLengthForAutoWrap"_s, u"1"_s ).toInt();
1110 textFormatElem.attribute( u"multilineAlign"_s, QString::number( static_cast< int >( Qgis::LabelMultiLineAlignment::FollowPlacement ) ) ).toUInt()
1111 );
1112 mLineSettings.setAddDirectionSymbol( textFormatElem.attribute( u"addDirectionSymbol"_s ).toInt() );
1113 mLineSettings.setLeftDirectionSymbol( textFormatElem.attribute( u"leftDirectionSymbol"_s, u"<"_s ) );
1114 mLineSettings.setRightDirectionSymbol( textFormatElem.attribute( u"rightDirectionSymbol"_s, u">"_s ) );
1115 mLineSettings.setReverseDirectionSymbol( textFormatElem.attribute( u"reverseDirectionSymbol"_s ).toInt() );
1116 mLineSettings.setDirectionSymbolPlacement(
1118 textFormatElem.attribute( u"placeDirectionSymbol"_s, QString::number( static_cast< int >( QgsLabelLineSettings::DirectionSymbolPlacement::SymbolLeftRight ) ) ).toUInt()
1119 )
1120 );
1121 formatNumbers = textFormatElem.attribute( u"formatNumbers"_s ).toInt();
1122 decimals = textFormatElem.attribute( u"decimals"_s ).toInt();
1123 plusSign = textFormatElem.attribute( u"plussign"_s ).toInt();
1124
1125 // placement
1126 QDomElement placementElem = elem.firstChildElement( u"placement"_s );
1127 placement = static_cast< Qgis::LabelPlacement >( placementElem.attribute( u"placement"_s ).toInt() );
1128 mLineSettings.setPlacementFlags( static_cast< Qgis::LabelLinePlacementFlags >( placementElem.attribute( u"placementFlags"_s ).toUInt() ) );
1129 mPolygonPlacementFlags = static_cast< Qgis::LabelPolygonPlacementFlags >(
1130 placementElem.attribute( u"polygonPlacementFlags"_s, QString::number( static_cast< int >( Qgis::LabelPolygonPlacementFlag::AllowPlacementInsideOfPolygon ) ) ).toInt()
1131 );
1132
1133 centroidWhole = placementElem.attribute( u"centroidWhole"_s, u"0"_s ).toInt();
1134 centroidInside = placementElem.attribute( u"centroidInside"_s, u"0"_s ).toInt();
1135
1136 QVector<Qgis::LabelPredefinedPointPosition> predefinedPositionOrder = QgsLabelingUtils::decodePredefinedPositionOrder( placementElem.attribute( u"predefinedPositionOrder"_s ) );
1137 if ( predefinedPositionOrder.isEmpty() )
1138 predefinedPositionOrder = *DEFAULT_PLACEMENT_ORDER();
1139 mPointSettings.setPredefinedPositionOrder( predefinedPositionOrder );
1140
1141 fitInPolygonOnly = placementElem.attribute( u"fitInPolygonOnly"_s, u"0"_s ).toInt();
1142 dist = placementElem.attribute( u"dist"_s ).toDouble();
1143 if ( !placementElem.hasAttribute( u"distUnits"_s ) )
1144 {
1145 if ( placementElem.attribute( u"distInMapUnits"_s ).toInt() )
1147 else
1149 }
1150 else
1151 {
1152 distUnits = QgsUnitTypes::decodeRenderUnit( placementElem.attribute( u"distUnits"_s ) );
1153 }
1154 if ( !placementElem.hasAttribute( u"distMapUnitScale"_s ) )
1155 {
1156 //fallback to older property
1157 double oldMin = placementElem.attribute( u"distMapUnitMinScale"_s, u"0"_s ).toDouble();
1158 distMapUnitScale.minScale = !qgsDoubleNear( oldMin, 0 ) ? 1.0 / oldMin : 0;
1159 double oldMax = placementElem.attribute( u"distMapUnitMaxScale"_s, u"0"_s ).toDouble();
1160 distMapUnitScale.maxScale = !qgsDoubleNear( oldMax, 0 ) ? 1.0 / oldMax : 0;
1161 }
1162 else
1163 {
1164 distMapUnitScale = QgsSymbolLayerUtils::decodeMapUnitScale( placementElem.attribute( u"distMapUnitScale"_s ) );
1165 }
1166 offsetType = static_cast< Qgis::LabelOffsetType >( placementElem.attribute( u"offsetType"_s, QString::number( static_cast< int >( Qgis::LabelOffsetType::FromPoint ) ) ).toUInt() );
1167 mPointSettings.setQuadrant(
1168 static_cast< Qgis::LabelQuadrantPosition >( placementElem.attribute( u"quadOffset"_s, QString::number( static_cast< int >( Qgis::LabelQuadrantPosition::Over ) ) ).toUInt() )
1169 );
1170 xOffset = placementElem.attribute( u"xOffset"_s, u"0"_s ).toDouble();
1171 yOffset = placementElem.attribute( u"yOffset"_s, u"0"_s ).toDouble();
1172 if ( !placementElem.hasAttribute( u"offsetUnits"_s ) )
1173 {
1174 offsetUnits = placementElem.attribute( u"labelOffsetInMapUnits"_s, u"1"_s ).toInt() ? Qgis::RenderUnit::MapUnits : Qgis::RenderUnit::Millimeters;
1175 }
1176 else
1177 {
1178 offsetUnits = QgsUnitTypes::decodeRenderUnit( placementElem.attribute( u"offsetUnits"_s ) );
1179 }
1180 if ( !placementElem.hasAttribute( u"labelOffsetMapUnitScale"_s ) )
1181 {
1182 //fallback to older property
1183 double oldMin = placementElem.attribute( u"labelOffsetMapUnitMinScale"_s, u"0"_s ).toDouble();
1184 labelOffsetMapUnitScale.minScale = !qgsDoubleNear( oldMin, 0.0 ) ? 1.0 / oldMin : 0;
1185 double oldMax = placementElem.attribute( u"labelOffsetMapUnitMaxScale"_s, u"0"_s ).toDouble();
1186 labelOffsetMapUnitScale.maxScale = !qgsDoubleNear( oldMax, 0.0 ) ? 1.0 / oldMax : 0;
1187 }
1188 else
1189 {
1190 labelOffsetMapUnitScale = QgsSymbolLayerUtils::decodeMapUnitScale( placementElem.attribute( u"labelOffsetMapUnitScale"_s ) );
1191 }
1192
1193 if ( placementElem.hasAttribute( u"angleOffset"_s ) )
1194 {
1195 double oldAngle = placementElem.attribute( u"angleOffset"_s, u"0"_s ).toDouble();
1196 angleOffset = std::fmod( 360 - oldAngle, 360.0 );
1197 }
1198 else
1199 {
1200 angleOffset = placementElem.attribute( u"rotationAngle"_s, u"0"_s ).toDouble();
1201 }
1202
1203 preserveRotation = placementElem.attribute( u"preserveRotation"_s, u"1"_s ).toInt();
1204 {
1205 QString rotationUnitString = placementElem.attribute( u"rotationUnit"_s, qgsEnumValueToKey( Qgis::AngleUnit::Degrees ) );
1206 if ( rotationUnitString.startsWith( "Angle"_L1 ) )
1207 {
1208 // compatibility with QGIS < 3.30
1209 rotationUnitString = rotationUnitString.mid( 5 );
1210 }
1211
1212 mRotationUnit = qgsEnumKeyToValue( rotationUnitString, Qgis::AngleUnit::Degrees );
1213 }
1214 maxCurvedCharAngleIn = placementElem.attribute( u"maxCurvedCharAngleIn"_s, u"25"_s ).toDouble();
1215 maxCurvedCharAngleOut = placementElem.attribute( u"maxCurvedCharAngleOut"_s, u"-25"_s ).toDouble();
1216 priority = placementElem.attribute( u"priority"_s ).toInt();
1217 repeatDistance = placementElem.attribute( u"repeatDistance"_s, u"0"_s ).toDouble();
1218 if ( !placementElem.hasAttribute( u"repeatDistanceUnits"_s ) )
1219 {
1220 // upgrade old setting
1221 switch ( placementElem.attribute( u"repeatDistanceUnit"_s, QString::number( 1 ) ).toUInt() )
1222 {
1223 case 0:
1225 break;
1226 case 1:
1228 break;
1229 case 2:
1231 break;
1232 case 3:
1234 break;
1235 }
1236 }
1237 else
1238 {
1239 repeatDistanceUnit = QgsUnitTypes::decodeRenderUnit( placementElem.attribute( u"repeatDistanceUnits"_s ) );
1240 }
1241 if ( !placementElem.hasAttribute( u"repeatDistanceMapUnitScale"_s ) )
1242 {
1243 //fallback to older property
1244 double oldMin = placementElem.attribute( u"repeatDistanceMapUnitMinScale"_s, u"0"_s ).toDouble();
1245 repeatDistanceMapUnitScale.minScale = !qgsDoubleNear( oldMin, 0.0 ) ? 1.0 / oldMin : 0;
1246 double oldMax = placementElem.attribute( u"repeatDistanceMapUnitMaxScale"_s, u"0"_s ).toDouble();
1247 repeatDistanceMapUnitScale.maxScale = !qgsDoubleNear( oldMax, 0.0 ) ? 1.0 / oldMax : 0;
1248 }
1249 else
1250 {
1251 repeatDistanceMapUnitScale = QgsSymbolLayerUtils::decodeMapUnitScale( placementElem.attribute( u"repeatDistanceMapUnitScale"_s ) );
1252 }
1253
1254 mLineSettings.setOverrunDistance( placementElem.attribute( u"overrunDistance"_s, u"0"_s ).toDouble() );
1255 mLineSettings.setOverrunDistanceUnit( QgsUnitTypes::decodeRenderUnit( placementElem.attribute( u"overrunDistanceUnit"_s ) ) );
1256 mLineSettings.setOverrunDistanceMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( placementElem.attribute( u"overrunDistanceMapUnitScale"_s ) ) );
1257 mLineSettings.setLineAnchorPercent( placementElem.attribute( u"lineAnchorPercent"_s, u"0.5"_s ).toDouble() );
1258 mLineSettings.setAnchorType( static_cast< QgsLabelLineSettings::AnchorType >( placementElem.attribute( u"lineAnchorType"_s, u"0"_s ).toInt() ) );
1259 mLineSettings.setAnchorClipping( static_cast< QgsLabelLineSettings::AnchorClipping >( placementElem.attribute( u"lineAnchorClipping"_s, u"0"_s ).toInt() ) );
1260 // when reading the anchor text point we default to center mode, to keep same result as for projects created in < 3.26
1261 mLineSettings.setAnchorTextPoint( qgsEnumKeyToValue( placementElem.attribute( u"lineAnchorTextPoint"_s ), Qgis::TextAnchorPoint::CenterOfText ) );
1262 mLineSettings.setCurvedLabelMode( qgsEnumKeyToValue( placementElem.attribute( u"curvedLabelMode"_s ), Qgis::CurvedLabelMode::Default ) );
1263
1264 mPointSettings.setMaximumDistance( placementElem.attribute( u"maximumDistance"_s, u"0"_s ).toDouble() );
1265 mPointSettings.setMaximumDistanceUnit( QgsUnitTypes::decodeRenderUnit( placementElem.attribute( u"maximumDistanceUnit"_s ) ) );
1266 mPointSettings.setMaximumDistanceMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( placementElem.attribute( u"maximumDistanceMapUnitScale"_s ) ) );
1267
1268 geometryGenerator = placementElem.attribute( u"geometryGenerator"_s );
1269 geometryGeneratorEnabled = placementElem.attribute( u"geometryGeneratorEnabled"_s ).toInt();
1270 {
1271 QString geometryTypeKey = placementElem.attribute( u"geometryGeneratorType"_s );
1272 // maintain compatibility with < 3.3.0
1273 if ( geometryTypeKey.endsWith( "Geometry"_L1 ) )
1274 geometryTypeKey.chop( 8 );
1275
1277 }
1278 {
1279 QString layerTypeKey = placementElem.attribute( u"layerType"_s );
1280 // maintain compatibility with < 3.3.0
1281 if ( layerTypeKey.endsWith( "Geometry"_L1 ) )
1282 layerTypeKey.chop( 8 );
1283
1285 }
1286
1287 mPlacementSettings.setAllowDegradedPlacement( placementElem.attribute( u"allowDegraded"_s, u"0"_s ).toInt() );
1288
1289 // rendering
1290 QDomElement renderingElem = elem.firstChildElement( u"rendering"_s );
1291
1292 drawLabels = renderingElem.attribute( u"drawLabels"_s, u"1"_s ).toInt();
1293
1294 maximumScale = renderingElem.attribute( u"scaleMin"_s, u"0"_s ).toDouble();
1295 minimumScale = renderingElem.attribute( u"scaleMax"_s, u"0"_s ).toDouble();
1296 scaleVisibility = renderingElem.attribute( u"scaleVisibility"_s ).toInt();
1297
1298 fontLimitPixelSize = renderingElem.attribute( u"fontLimitPixelSize"_s, u"0"_s ).toInt();
1299 fontMinPixelSize = renderingElem.attribute( u"fontMinPixelSize"_s, u"0"_s ).toInt();
1300 fontMaxPixelSize = renderingElem.attribute( u"fontMaxPixelSize"_s, u"10000"_s ).toInt();
1301
1302 if ( placementElem.hasAttribute( u"overlapHandling"_s ) )
1303 {
1304 mPlacementSettings.setOverlapHandling( qgsEnumKeyToValue( placementElem.attribute( u"overlapHandling"_s ), Qgis::LabelOverlapHandling::PreventOverlap ) );
1305 }
1306 else
1307 {
1308 // legacy setting
1309 if ( renderingElem.attribute( u"displayAll"_s, u"0"_s ).toInt() )
1310 {
1311 mPlacementSettings.setOverlapHandling( Qgis::LabelOverlapHandling::AllowOverlapIfRequired );
1312 mPlacementSettings.setAllowDegradedPlacement( true );
1313 }
1314 else
1315 {
1316 mPlacementSettings.setOverlapHandling( Qgis::LabelOverlapHandling::PreventOverlap );
1317 mPlacementSettings.setAllowDegradedPlacement( false );
1318 }
1319 }
1320
1321 mPlacementSettings.setPrioritization( qgsEnumKeyToValue( placementElem.attribute( u"prioritization"_s ), Qgis::LabelPrioritization::PreferCloser ) );
1322
1324 renderingElem.attribute( u"upsidedownLabels"_s, QString::number( static_cast< int >( Qgis::UpsideDownLabelHandling::FlipUpsideDownLabels ) ) ).toUInt()
1325 );
1326
1327 if ( placementElem.hasAttribute( u"multipartBehavior"_s ) )
1328 {
1329 mPlacementSettings.setMultiPartBehavior( qgsEnumKeyToValue( placementElem.attribute( u"multipartBehavior"_s ), Qgis::MultiPartLabelingBehavior::LabelLargestPartOnly ) );
1330 }
1331 else
1332 {
1333 // legacy setting
1334 if ( renderingElem.attribute( u"labelPerPart"_s, u"0"_s ).toInt() )
1335 {
1336 mPlacementSettings.setMultiPartBehavior( Qgis::MultiPartLabelingBehavior::LabelEveryPartWithEntireLabel );
1337 }
1338 else
1339 {
1340 mPlacementSettings.setMultiPartBehavior( Qgis::MultiPartLabelingBehavior::LabelLargestPartOnly );
1341 }
1342 }
1343
1344 mPlacementSettings.setWhitespaceCollisionHandling( qgsEnumKeyToValue( placementElem.attribute( u"whitespaceCollisions"_s ), Qgis::LabelWhitespaceCollisionHandling::TreatWhitespaceAsCollision ) );
1345
1346 mLineSettings.setMergeLines( renderingElem.attribute( u"mergeLines"_s ).toInt() );
1347 mThinningSettings.setMinimumFeatureSize( renderingElem.attribute( u"minFeatureSize"_s ).toDouble() );
1348 mThinningSettings.setLimitNumberLabelsEnabled( renderingElem.attribute( u"limitNumLabels"_s, u"0"_s ).toInt() );
1349 mThinningSettings.setMaximumNumberLabels( renderingElem.attribute( u"maxNumLabels"_s, u"2000"_s ).toInt() );
1350
1351 mThinningSettings.setLabelMarginDistance( placementElem.attribute( u"labelMarginDistance"_s, u"0"_s ).toDouble() );
1352 mThinningSettings.setLabelMarginDistanceUnit( QgsUnitTypes::decodeRenderUnit( placementElem.attribute( u"labelMarginDistanceUnit"_s ) ) );
1353 mThinningSettings.setLabelMarginDistanceMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( placementElem.attribute( u"labelMarginDistanceMapUnitScale"_s ) ) );
1354
1355 mThinningSettings.setMinimumDistanceToDuplicate( placementElem.attribute( u"minDistanceToDuplicates"_s, QString::number( QgsLabelThinningSettings::DEFAULT_MINIMUM_DISTANCE_TO_DUPLICATE ) ).toDouble() );
1356 mThinningSettings.setMinimumDistanceToDuplicateUnit( QgsUnitTypes::decodeRenderUnit( placementElem.attribute( u"minDistanceToDuplicatesUnit"_s ) ) );
1357 mThinningSettings.setMinimumDistanceToDuplicateMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( placementElem.attribute( u"minDistanceToDuplicateMapUnitScale"_s ) ) );
1358 mThinningSettings.setAllowDuplicateRemoval( placementElem.attribute( u"allowDuplicateRemoval"_s, u"0"_s ).toInt() );
1359
1360 mObstacleSettings.setIsObstacle( renderingElem.attribute( u"obstacle"_s, u"1"_s ).toInt() );
1361 mObstacleSettings.setFactor( renderingElem.attribute( u"obstacleFactor"_s, u"1"_s ).toDouble() );
1362 mObstacleSettings.setType(
1364 renderingElem.attribute( u"obstacleType"_s, QString::number( static_cast< int >( QgsLabelObstacleSettings::ObstacleType::PolygonInterior ) ) ).toUInt()
1365 )
1366 );
1367 zIndex = renderingElem.attribute( u"zIndex"_s, u"0.0"_s ).toDouble();
1368 mUnplacedVisibility = static_cast< Qgis::UnplacedLabelVisibility >(
1369 renderingElem.attribute( u"unplacedVisibility"_s, QString::number( static_cast< int >( Qgis::UnplacedLabelVisibility::FollowEngineSetting ) ) ).toInt()
1370 );
1371
1372 QDomElement ddElem = elem.firstChildElement( u"dd_properties"_s );
1373 if ( !ddElem.isNull() )
1374 {
1375 mDataDefinedProperties.readXml( ddElem, *sPropertyDefinitions() );
1376 }
1377 else
1378 {
1379 // upgrade 2.x style dd project
1380 mDataDefinedProperties.clear();
1381 QDomElement ddElem = elem.firstChildElement( u"data-defined"_s );
1382 readOldDataDefinedPropertyMap( nullptr, &ddElem );
1383 }
1384 // upgrade older data defined settings
1385 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::FontTransp ) )
1386 {
1387 mDataDefinedProperties
1388 .setProperty( QgsPalLayerSettings::Property::FontOpacity, QgsProperty::fromExpression( u"100 - (%1)"_s.arg( mDataDefinedProperties.property( QgsPalLayerSettings::Property::FontTransp ).asExpression() ) ) );
1389 mDataDefinedProperties.setProperty( QgsPalLayerSettings::Property::FontTransp, QgsProperty() );
1390 }
1391 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::BufferTransp ) )
1392 {
1393 mDataDefinedProperties
1394 .setProperty( QgsPalLayerSettings::Property::BufferOpacity, QgsProperty::fromExpression( u"100 - (%1)"_s.arg( mDataDefinedProperties.property( QgsPalLayerSettings::Property::BufferTransp ).asExpression() ) ) );
1395 mDataDefinedProperties.setProperty( QgsPalLayerSettings::Property::BufferTransp, QgsProperty() );
1396 }
1397 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::ShapeTransparency ) )
1398 {
1399 mDataDefinedProperties
1400 .setProperty( QgsPalLayerSettings::Property::ShapeOpacity, QgsProperty::fromExpression( u"100 - (%1)"_s.arg( mDataDefinedProperties.property( QgsPalLayerSettings::Property::ShapeTransparency ).asExpression() ) ) );
1401 mDataDefinedProperties.setProperty( QgsPalLayerSettings::Property::ShapeTransparency, QgsProperty() );
1402 }
1403 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::ShadowTransparency ) )
1404 {
1405 mDataDefinedProperties
1406 .setProperty( QgsPalLayerSettings::Property::ShadowOpacity, QgsProperty::fromExpression( u"100 - (%1)"_s.arg( mDataDefinedProperties.property( QgsPalLayerSettings::Property::ShadowTransparency ).asExpression() ) ) );
1407 mDataDefinedProperties.setProperty( QgsPalLayerSettings::Property::ShadowTransparency, QgsProperty() );
1408 }
1409 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::Rotation ) )
1410 {
1411 mDataDefinedProperties
1412 .setProperty( QgsPalLayerSettings::Property::LabelRotation, QgsProperty::fromExpression( u"360 - (%1)"_s.arg( mDataDefinedProperties.property( QgsPalLayerSettings::Property::Rotation ).asExpression() ) ) );
1413 mDataDefinedProperties.setProperty( QgsPalLayerSettings::Property::Rotation, QgsProperty() );
1414 }
1415 // older 2.x projects had min/max scale flipped - so change them here.
1416 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::MinScale ) )
1417 {
1418 mDataDefinedProperties.setProperty( QgsPalLayerSettings::Property::MaximumScale, mDataDefinedProperties.property( QgsPalLayerSettings::Property::MinScale ) );
1419 mDataDefinedProperties.setProperty( QgsPalLayerSettings::Property::MinScale, QgsProperty() );
1420 }
1421 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::MaxScale ) )
1422 {
1423 mDataDefinedProperties.setProperty( QgsPalLayerSettings::Property::MinimumScale, mDataDefinedProperties.property( QgsPalLayerSettings::Property::MaxScale ) );
1424 mDataDefinedProperties.setProperty( QgsPalLayerSettings::Property::MaxScale, QgsProperty() );
1425 }
1426
1427 // TODO - replace with registry when multiple callout styles exist
1428 const QString calloutType = elem.attribute( u"calloutType"_s );
1429 if ( calloutType.isEmpty() )
1431 else
1432 {
1433 mCallout = QgsApplication::calloutRegistry()->createCallout( calloutType, elem.firstChildElement( u"callout"_s ), context );
1434 if ( !mCallout )
1436 }
1437}
1438
1439QDomElement QgsPalLayerSettings::writeXml( QDomDocument &doc, const QgsReadWriteContext &context ) const
1440{
1441 QDomElement textStyleElem = mFormat.writeXml( doc, context );
1442
1443 // text style
1444 textStyleElem.setAttribute( u"fieldName"_s, fieldName );
1445 textStyleElem.setAttribute( u"isExpression"_s, isExpression );
1446 QDomElement replacementElem = doc.createElement( u"substitutions"_s );
1447 substitutions.writeXml( replacementElem, doc );
1448 textStyleElem.appendChild( replacementElem );
1449 textStyleElem.setAttribute( u"useSubstitutions"_s, useSubstitutions );
1450 textStyleElem.setAttribute( u"legendString"_s, mLegendString );
1451
1452 // text formatting
1453 QDomElement textFormatElem = doc.createElement( u"text-format"_s );
1454 textFormatElem.setAttribute( u"wrapChar"_s, wrapChar );
1455 textFormatElem.setAttribute( u"autoWrapLength"_s, autoWrapLength );
1456 textFormatElem.setAttribute( u"useMaxLineLengthForAutoWrap"_s, useMaxLineLengthForAutoWrap );
1457 textFormatElem.setAttribute( u"multilineAlign"_s, static_cast< unsigned int >( multilineAlign ) );
1458 textFormatElem.setAttribute( u"addDirectionSymbol"_s, mLineSettings.addDirectionSymbol() );
1459 textFormatElem.setAttribute( u"leftDirectionSymbol"_s, mLineSettings.leftDirectionSymbol() );
1460 textFormatElem.setAttribute( u"rightDirectionSymbol"_s, mLineSettings.rightDirectionSymbol() );
1461 textFormatElem.setAttribute( u"reverseDirectionSymbol"_s, mLineSettings.reverseDirectionSymbol() );
1462 textFormatElem.setAttribute( u"placeDirectionSymbol"_s, static_cast< unsigned int >( mLineSettings.directionSymbolPlacement() ) );
1463 textFormatElem.setAttribute( u"formatNumbers"_s, formatNumbers );
1464 textFormatElem.setAttribute( u"decimals"_s, decimals );
1465 textFormatElem.setAttribute( u"plussign"_s, plusSign );
1466
1467 // placement
1468 QDomElement placementElem = doc.createElement( u"placement"_s );
1469 placementElem.setAttribute( u"placement"_s, static_cast< int >( placement ) );
1470 placementElem.setAttribute( u"polygonPlacementFlags"_s, static_cast< int >( mPolygonPlacementFlags ) );
1471 placementElem.setAttribute( u"placementFlags"_s, static_cast< unsigned int >( mLineSettings.placementFlags() ) );
1472 placementElem.setAttribute( u"centroidWhole"_s, centroidWhole );
1473 placementElem.setAttribute( u"centroidInside"_s, centroidInside );
1474 placementElem.setAttribute( u"predefinedPositionOrder"_s, QgsLabelingUtils::encodePredefinedPositionOrder( mPointSettings.predefinedPositionOrder() ) );
1475 placementElem.setAttribute( u"fitInPolygonOnly"_s, fitInPolygonOnly );
1476 placementElem.setAttribute( u"dist"_s, dist );
1477 placementElem.setAttribute( u"distUnits"_s, QgsUnitTypes::encodeUnit( distUnits ) );
1478 placementElem.setAttribute( u"distMapUnitScale"_s, QgsSymbolLayerUtils::encodeMapUnitScale( distMapUnitScale ) );
1479 placementElem.setAttribute( u"offsetType"_s, static_cast< unsigned int >( offsetType ) );
1480 placementElem.setAttribute( u"quadOffset"_s, static_cast< unsigned int >( mPointSettings.quadrant() ) );
1481 placementElem.setAttribute( u"xOffset"_s, xOffset );
1482 placementElem.setAttribute( u"yOffset"_s, yOffset );
1483 placementElem.setAttribute( u"offsetUnits"_s, QgsUnitTypes::encodeUnit( offsetUnits ) );
1484 placementElem.setAttribute( u"labelOffsetMapUnitScale"_s, QgsSymbolLayerUtils::encodeMapUnitScale( labelOffsetMapUnitScale ) );
1485 placementElem.setAttribute( u"rotationAngle"_s, angleOffset );
1486 placementElem.setAttribute( u"preserveRotation"_s, preserveRotation );
1487 {
1488 // append Angle prefix to maintain compatibility with QGIS < 3.30
1489 const QString rotationUnitString = u"Angle"_s + qgsEnumValueToKey( mRotationUnit );
1490 placementElem.setAttribute( u"rotationUnit"_s, rotationUnitString );
1491 }
1492 placementElem.setAttribute( u"maxCurvedCharAngleIn"_s, maxCurvedCharAngleIn );
1493 placementElem.setAttribute( u"maxCurvedCharAngleOut"_s, maxCurvedCharAngleOut );
1494 placementElem.setAttribute( u"priority"_s, priority );
1495 placementElem.setAttribute( u"repeatDistance"_s, repeatDistance );
1496 placementElem.setAttribute( u"repeatDistanceUnits"_s, QgsUnitTypes::encodeUnit( repeatDistanceUnit ) );
1497 placementElem.setAttribute( u"repeatDistanceMapUnitScale"_s, QgsSymbolLayerUtils::encodeMapUnitScale( repeatDistanceMapUnitScale ) );
1498 placementElem.setAttribute( u"overrunDistance"_s, mLineSettings.overrunDistance() );
1499 placementElem.setAttribute( u"overrunDistanceUnit"_s, QgsUnitTypes::encodeUnit( mLineSettings.overrunDistanceUnit() ) );
1500 placementElem.setAttribute( u"overrunDistanceMapUnitScale"_s, QgsSymbolLayerUtils::encodeMapUnitScale( mLineSettings.overrunDistanceMapUnitScale() ) );
1501 placementElem.setAttribute( u"lineAnchorPercent"_s, mLineSettings.lineAnchorPercent() );
1502 placementElem.setAttribute( u"lineAnchorType"_s, static_cast< int >( mLineSettings.anchorType() ) );
1503 placementElem.setAttribute( u"lineAnchorClipping"_s, static_cast< int >( mLineSettings.anchorClipping() ) );
1504 placementElem.setAttribute( u"lineAnchorTextPoint"_s, qgsEnumValueToKey( mLineSettings.anchorTextPoint() ) );
1505 if ( mLineSettings.curvedLabelMode() != Qgis::CurvedLabelMode::Default )
1506 {
1507 placementElem.setAttribute( u"curvedLabelMode"_s, qgsEnumValueToKey( mLineSettings.curvedLabelMode() ) );
1508 }
1509
1510 placementElem.setAttribute( u"maximumDistance"_s, mPointSettings.maximumDistance() );
1511 placementElem.setAttribute( u"maximumDistanceUnit"_s, QgsUnitTypes::encodeUnit( mPointSettings.maximumDistanceUnit() ) );
1512 placementElem.setAttribute( u"maximumDistanceMapUnitScale"_s, QgsSymbolLayerUtils::encodeMapUnitScale( mPointSettings.maximumDistanceMapUnitScale() ) );
1513
1514 placementElem.setAttribute( u"geometryGenerator"_s, geometryGenerator );
1515 placementElem.setAttribute( u"geometryGeneratorEnabled"_s, geometryGeneratorEnabled );
1516 placementElem.setAttribute( u"geometryGeneratorType"_s, qgsEnumValueToKey( geometryGeneratorType ) + u"Geometry"_s );
1517
1518 placementElem.setAttribute( u"layerType"_s, qgsEnumValueToKey( layerType ) + u"Geometry"_s );
1519
1520 placementElem.setAttribute( u"overlapHandling"_s, qgsEnumValueToKey( mPlacementSettings.overlapHandling() ) );
1521 placementElem.setAttribute( u"prioritization"_s, qgsEnumValueToKey( mPlacementSettings.prioritization() ) );
1522 placementElem.setAttribute( u"allowDegraded"_s, mPlacementSettings.allowDegradedPlacement() ? u"1"_s : u"0"_s );
1523
1524 // rendering
1525 QDomElement renderingElem = doc.createElement( u"rendering"_s );
1526 renderingElem.setAttribute( u"drawLabels"_s, drawLabels );
1527 renderingElem.setAttribute( u"scaleVisibility"_s, scaleVisibility );
1528 renderingElem.setAttribute( u"scaleMin"_s, maximumScale );
1529 renderingElem.setAttribute( u"scaleMax"_s, minimumScale );
1530 renderingElem.setAttribute( u"fontLimitPixelSize"_s, fontLimitPixelSize );
1531 renderingElem.setAttribute( u"fontMinPixelSize"_s, fontMinPixelSize );
1532 renderingElem.setAttribute( u"fontMaxPixelSize"_s, fontMaxPixelSize );
1533 renderingElem.setAttribute( u"upsidedownLabels"_s, static_cast< unsigned int >( upsidedownLabels ) );
1534 placementElem.setAttribute( u"multipartBehavior"_s, qgsEnumValueToKey( mPlacementSettings.multiPartBehavior() ) );
1535 if ( mPlacementSettings.whitespaceCollisionHandling() != Qgis::LabelWhitespaceCollisionHandling::TreatWhitespaceAsCollision )
1536 placementElem.setAttribute( u"whitespaceCollisions"_s, qgsEnumValueToKey( mPlacementSettings.whitespaceCollisionHandling() ) );
1537 renderingElem.setAttribute( u"mergeLines"_s, mLineSettings.mergeLines() );
1538 renderingElem.setAttribute( u"minFeatureSize"_s, mThinningSettings.minimumFeatureSize() );
1539 renderingElem.setAttribute( u"limitNumLabels"_s, mThinningSettings.limitNumberOfLabelsEnabled() );
1540 renderingElem.setAttribute( u"maxNumLabels"_s, mThinningSettings.maximumNumberLabels() );
1541
1542 if ( mThinningSettings.labelMarginDistance() > 0 )
1543 {
1544 placementElem.setAttribute( u"labelMarginDistance"_s, mThinningSettings.labelMarginDistance() );
1545 }
1546 if ( mThinningSettings.labelMarginDistanceUnit() != Qgis::RenderUnit::Millimeters )
1547 {
1548 placementElem.setAttribute( u"labelMarginDistanceUnit"_s, QgsUnitTypes::encodeUnit( mThinningSettings.labelMarginDistanceUnit() ) );
1549 }
1550 if ( !mThinningSettings.labelMarginDistanceMapUnitScale().isNull() )
1551 {
1552 placementElem.setAttribute( u"labelMarginDistanceMapUnitScale"_s, QgsSymbolLayerUtils::encodeMapUnitScale( mThinningSettings.labelMarginDistanceMapUnitScale() ) );
1553 }
1554
1555 if ( mThinningSettings.minimumDistanceToDuplicate() != QgsLabelThinningSettings::DEFAULT_MINIMUM_DISTANCE_TO_DUPLICATE )
1556 {
1557 placementElem.setAttribute( u"minDistanceToDuplicates"_s, mThinningSettings.minimumDistanceToDuplicate() );
1558 }
1559 if ( mThinningSettings.minimumDistanceToDuplicateUnit() != Qgis::RenderUnit::Millimeters )
1560 {
1561 placementElem.setAttribute( u"minDistanceToDuplicatesUnit"_s, QgsUnitTypes::encodeUnit( mThinningSettings.minimumDistanceToDuplicateUnit() ) );
1562 }
1563 if ( !mThinningSettings.minimumDistanceToDuplicateMapUnitScale().isNull() )
1564 {
1565 placementElem.setAttribute( u"minDistanceToDuplicateMapUnitScale"_s, QgsSymbolLayerUtils::encodeMapUnitScale( mThinningSettings.minimumDistanceToDuplicateMapUnitScale() ) );
1566 }
1567 if ( mThinningSettings.allowDuplicateRemoval() )
1568 {
1569 placementElem.setAttribute( u"allowDuplicateRemoval"_s, u"1"_s );
1570 }
1571
1572 renderingElem.setAttribute( u"obstacle"_s, mObstacleSettings.isObstacle() );
1573 renderingElem.setAttribute( u"obstacleFactor"_s, mObstacleSettings.factor() );
1574 renderingElem.setAttribute( u"obstacleType"_s, static_cast< unsigned int >( mObstacleSettings.type() ) );
1575 renderingElem.setAttribute( u"zIndex"_s, zIndex );
1576 renderingElem.setAttribute( u"unplacedVisibility"_s, static_cast< int >( mUnplacedVisibility ) );
1577
1578 QDomElement ddElem = doc.createElement( u"dd_properties"_s );
1579 mDataDefinedProperties.writeXml( ddElem, *sPropertyDefinitions() );
1580
1581 QDomElement elem = doc.createElement( u"settings"_s );
1582 elem.appendChild( textStyleElem );
1583 elem.appendChild( textFormatElem );
1584 elem.appendChild( placementElem );
1585 elem.appendChild( renderingElem );
1586 elem.appendChild( ddElem );
1587
1588 if ( mCallout )
1589 {
1590 elem.setAttribute( u"calloutType"_s, mCallout->type() );
1591 mCallout->saveProperties( doc, elem, context );
1592 }
1593
1594 return elem;
1595}
1596
1598{
1599 mCallout.reset( callout );
1600}
1601
1602QPixmap QgsPalLayerSettings::labelSettingsPreviewPixmap( const QgsPalLayerSettings &settings, QSize size, const QString &previewText, int padding, const QgsScreenProperties &screen )
1603{
1604 const double devicePixelRatio = screen.isValid() ? screen.devicePixelRatio() : 1;
1605
1606 // for now, just use format
1607 QgsTextFormat tempFormat = settings.format();
1608 QPixmap pixmap( size * devicePixelRatio );
1609 pixmap.fill( Qt::transparent );
1610 pixmap.setDevicePixelRatio( devicePixelRatio );
1611 QPainter painter;
1612 painter.begin( &pixmap );
1613
1614 painter.setRenderHint( QPainter::Antialiasing );
1615
1616 const QRectF rect( 0, 0, size.width(), size.height() );
1617
1618 // shameless eye candy - use a subtle gradient when drawing background
1619 painter.setPen( Qt::NoPen );
1620 QColor background1 = tempFormat.previewBackgroundColor();
1621 if ( ( background1.lightnessF() < 0.7 ) )
1622 {
1623 background1 = background1.darker( 125 );
1624 }
1625 else
1626 {
1627 background1 = background1.lighter( 125 );
1628 }
1629 QColor background2 = tempFormat.previewBackgroundColor();
1630 QLinearGradient linearGrad( QPointF( 0, 0 ), QPointF( 0, rect.height() ) );
1631 linearGrad.setColorAt( 0, background1 );
1632 linearGrad.setColorAt( 1, background2 );
1633 painter.setBrush( QBrush( linearGrad ) );
1634 if ( size.width() > 30 )
1635 {
1636 painter.drawRoundedRect( rect, 6, 6 );
1637 }
1638 else
1639 {
1640 // don't use rounded rect for small previews
1641 painter.drawRect( rect );
1642 }
1643 painter.setBrush( Qt::NoBrush );
1644 painter.setPen( Qt::NoPen );
1645 padding += 1; // move text away from background border
1646
1647 QgsRenderContext context;
1648 QgsMapToPixel newCoordXForm;
1649 newCoordXForm.setParameters( 1, 0, 0, 0, 0, 0 );
1650 context.setMapToPixel( newCoordXForm );
1652
1653 if ( screen.isValid() )
1654 {
1655 screen.updateRenderContextForScreen( context );
1656 }
1657 else
1658 {
1659 QWidget *activeWindow = QApplication::activeWindow();
1660 if ( QScreen *screen = activeWindow ? activeWindow->screen() : nullptr )
1661 {
1662 context.setScaleFactor( screen->physicalDotsPerInch() / 25.4 );
1663 context.setDevicePixelRatio( screen->devicePixelRatio() );
1664 }
1665 else
1666 {
1667 context.setScaleFactor( 96.0 / 25.4 );
1668 context.setDevicePixelRatio( 1.0 );
1669 }
1670 }
1671
1673 context.setPainter( &painter );
1674
1675 // slightly inset text to account for buffer/background
1676 const double fontSize = context.convertToPainterUnits( tempFormat.size(), tempFormat.sizeUnit(), tempFormat.sizeMapUnitScale() );
1677 double xtrans = 0;
1678 if ( tempFormat.buffer().enabled() )
1679 xtrans = tempFormat.buffer().sizeUnit() == Qgis::RenderUnit::Percentage
1680 ? fontSize * tempFormat.buffer().size() / 100
1681 : context.convertToPainterUnits( tempFormat.buffer().size(), tempFormat.buffer().sizeUnit(), tempFormat.buffer().sizeMapUnitScale() );
1682 if ( tempFormat.background().enabled() && tempFormat.background().sizeType() != QgsTextBackgroundSettings::SizeFixed )
1683 xtrans = std::max( xtrans, context.convertToPainterUnits( tempFormat.background().size().width(), tempFormat.background().sizeUnit(), tempFormat.background().sizeMapUnitScale() ) );
1684
1685 double ytrans = 0.0;
1686 if ( tempFormat.buffer().enabled() )
1687 ytrans = std::max(
1688 ytrans,
1689 tempFormat.buffer().sizeUnit() == Qgis::RenderUnit::Percentage ? fontSize * tempFormat.buffer().size() / 100
1690 : context.convertToPainterUnits( tempFormat.buffer().size(), tempFormat.buffer().sizeUnit(), tempFormat.buffer().sizeMapUnitScale() )
1691 );
1692 if ( tempFormat.background().enabled() )
1693 ytrans = std::max( ytrans, context.convertToPainterUnits( tempFormat.background().size().height(), tempFormat.background().sizeUnit(), tempFormat.background().sizeMapUnitScale() ) );
1694
1695 const QStringList text = QStringList() << ( previewText.isEmpty() ? settings.legendString() : previewText );
1696 const double textHeight = QgsTextRenderer::textHeight( context, tempFormat, text, Qgis::TextLayoutMode::Rectangle );
1697 QRectF textRect = rect;
1698 textRect.setLeft( xtrans + padding );
1699 textRect.setWidth( rect.width() - xtrans - 2 * padding );
1700
1701 if ( textRect.width() > 2000 )
1702 textRect.setWidth( 2000 - 2 * padding );
1703
1704 const double bottom = textRect.height() / 2 + textHeight / 2;
1705 textRect.setTop( bottom - textHeight );
1706 textRect.setBottom( bottom );
1707
1708 const double iconWidth = QFontMetricsF( QFont() ).horizontalAdvance( 'X' ) * Qgis::UI_SCALE_FACTOR;
1709
1710 if ( settings.callout() && settings.callout()->enabled() )
1711 {
1712 // draw callout preview
1713 const double textWidth = QgsTextRenderer::textWidth( context, tempFormat, text );
1714 QgsCallout *callout = settings.callout();
1715 callout->startRender( context );
1716 QgsCallout::QgsCalloutContext calloutContext;
1717 QRectF labelRect( textRect.left() + ( textRect.width() - textWidth ) / 2.0, textRect.top(), textWidth, textRect.height() );
1718 callout->render( context, labelRect, 0, QgsGeometry::fromPointXY( QgsPointXY( labelRect.left() - iconWidth * 1.5, labelRect.bottom() + iconWidth ) ), calloutContext );
1719 callout->stopRender( context );
1720 }
1721
1722 QgsTextRenderer::drawText( textRect, 0, Qgis::TextHorizontalAlignment::Center, text, context, tempFormat );
1723
1724 if ( size.width() > 30 )
1725 {
1726 // draw a label icon
1727
1728 QgsApplication::getThemeIcon( u"labelingSingle.svg"_s ).paint( &painter, QRect( rect.width() - iconWidth * 3, rect.height() - iconWidth * 3, iconWidth * 2, iconWidth * 2 ), Qt::AlignRight | Qt::AlignBottom );
1729 }
1730
1731 // draw border on top of text
1732 painter.setBrush( Qt::NoBrush );
1733 painter.setPen( QPen( tempFormat.previewBackgroundColor().darker( 150 ), 0 ) );
1734 if ( size.width() > 30 )
1735 {
1736 painter.drawRoundedRect( rect, 6, 6 );
1737 }
1738 else
1739 {
1740 // don't use rounded rect for small previews
1741 painter.drawRect( rect );
1742 }
1743
1744 painter.end();
1745 return pixmap;
1746}
1747
1749{
1750 return mUnplacedVisibility;
1751}
1752
1754{
1755 mUnplacedVisibility = visibility;
1756}
1757
1758bool QgsPalLayerSettings::checkMinimumSizeMM( const QgsRenderContext &ct, const QgsGeometry &geom, double minSize ) const
1759{
1760 return QgsPalLabeling::checkMinimumSizeMM( ct, geom, minSize );
1761}
1762
1764 const QFontMetricsF *fm, const QString &text, double &labelX, double &labelY, const QgsFeature *f, QgsRenderContext *context, double *rotatedLabelX, double *rotatedLabelY
1765)
1766{
1767 // NOTE: This whole method is deprecated is only kept for 3.x API stability.
1768 // There is no requirement to update the logic here, just leave it as is till we can completely remove for 4.0
1769 if ( !fm || !f )
1770 {
1771 return;
1772 }
1773
1774 QString textCopy( text );
1775
1776 //try to keep < 2.12 API - handle no passed render context
1777 std::unique_ptr< QgsRenderContext > scopedRc;
1778 if ( !context )
1779 {
1780 scopedRc = std::make_unique<QgsRenderContext>();
1781 if ( f )
1782 scopedRc->expressionContext().setFeature( *f );
1783 }
1784 QgsRenderContext *rc = context ? context : scopedRc.get();
1785
1786 QString wrapchr = wrapChar;
1787 int evalAutoWrapLength = autoWrapLength;
1788 double multilineH = mFormat.lineHeight();
1789 Qgis::TextOrientation orientation = mFormat.orientation();
1790
1791 bool addDirSymb = mLineSettings.addDirectionSymbol();
1792 QString leftDirSymb = mLineSettings.leftDirectionSymbol();
1793 QString rightDirSymb = mLineSettings.rightDirectionSymbol();
1794 QgsLabelLineSettings::DirectionSymbolPlacement placeDirSymb = mLineSettings.directionSymbolPlacement();
1795
1796 if ( f == mCurFeat ) // called internally, use any stored data defined values
1797 {
1798 if ( dataDefinedValues.contains( QgsPalLayerSettings::Property::MultiLineWrapChar ) )
1799 {
1800 wrapchr = dataDefinedValues.value( QgsPalLayerSettings::Property::MultiLineWrapChar ).toString();
1801 }
1802
1803 if ( dataDefinedValues.contains( QgsPalLayerSettings::Property::AutoWrapLength ) )
1804 {
1805 evalAutoWrapLength = dataDefinedValues.value( QgsPalLayerSettings::Property::AutoWrapLength, evalAutoWrapLength ).toInt();
1806 }
1807
1808 if ( dataDefinedValues.contains( QgsPalLayerSettings::Property::MultiLineHeight ) )
1809 {
1810 multilineH = dataDefinedValues.value( QgsPalLayerSettings::Property::MultiLineHeight ).toDouble();
1811 }
1812
1813 if ( dataDefinedValues.contains( QgsPalLayerSettings::Property::TextOrientation ) )
1814 {
1815 orientation = QgsTextRendererUtils::decodeTextOrientation( dataDefinedValues.value( QgsPalLayerSettings::Property::TextOrientation ).toString() );
1816 }
1817
1818 if ( dataDefinedValues.contains( QgsPalLayerSettings::Property::DirSymbDraw ) )
1819 {
1820 addDirSymb = dataDefinedValues.value( QgsPalLayerSettings::Property::DirSymbDraw ).toBool();
1821 }
1822
1823 if ( addDirSymb )
1824 {
1825 if ( dataDefinedValues.contains( QgsPalLayerSettings::Property::DirSymbLeft ) )
1826 {
1827 leftDirSymb = dataDefinedValues.value( QgsPalLayerSettings::Property::DirSymbLeft ).toString();
1828 }
1829 if ( dataDefinedValues.contains( QgsPalLayerSettings::Property::DirSymbRight ) )
1830 {
1831 rightDirSymb = dataDefinedValues.value( QgsPalLayerSettings::Property::DirSymbRight ).toString();
1832 }
1833
1834 if ( dataDefinedValues.contains( QgsPalLayerSettings::Property::DirSymbPlacement ) )
1835 {
1836 placeDirSymb = static_cast< QgsLabelLineSettings::DirectionSymbolPlacement >( dataDefinedValues.value( QgsPalLayerSettings::Property::DirSymbPlacement ).toInt() );
1837 }
1838 }
1839 }
1840 else // called externally with passed-in feature, evaluate data defined
1841 {
1842 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::MultiLineWrapChar ) )
1843 {
1845 wrapchr = mDataDefinedProperties.value( QgsPalLayerSettings::Property::MultiLineWrapChar, rc->expressionContext(), wrapchr ).toString();
1846 }
1847
1848 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::AutoWrapLength ) )
1849 {
1850 rc->expressionContext().setOriginalValueVariable( evalAutoWrapLength );
1851 evalAutoWrapLength = mDataDefinedProperties.value( QgsPalLayerSettings::Property::AutoWrapLength, rc->expressionContext(), evalAutoWrapLength ).toInt();
1852 }
1853
1854 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::MultiLineHeight ) )
1855 {
1856 rc->expressionContext().setOriginalValueVariable( multilineH );
1857 multilineH = mDataDefinedProperties.valueAsDouble( QgsPalLayerSettings::Property::MultiLineHeight, rc->expressionContext(), multilineH );
1858 }
1859
1860 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::TextOrientation ) )
1861 {
1862 QString encoded = QgsTextRendererUtils::encodeTextOrientation( orientation );
1864 orientation = QgsTextRendererUtils::decodeTextOrientation( mDataDefinedProperties.valueAsString( QgsPalLayerSettings::Property::TextOrientation, rc->expressionContext(), encoded ) );
1865 }
1866
1867 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::DirSymbDraw ) )
1868 {
1869 rc->expressionContext().setOriginalValueVariable( addDirSymb );
1870 addDirSymb = mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::Property::DirSymbDraw, rc->expressionContext(), addDirSymb );
1871 }
1872
1873 if ( addDirSymb ) // don't do extra evaluations if not adding a direction symbol
1874 {
1875 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::DirSymbLeft ) )
1876 {
1877 rc->expressionContext().setOriginalValueVariable( leftDirSymb );
1878 leftDirSymb = mDataDefinedProperties.value( QgsPalLayerSettings::Property::DirSymbLeft, rc->expressionContext(), leftDirSymb ).toString();
1879 }
1880
1881 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::DirSymbRight ) )
1882 {
1883 rc->expressionContext().setOriginalValueVariable( rightDirSymb );
1884 rightDirSymb = mDataDefinedProperties.value( QgsPalLayerSettings::Property::DirSymbRight, rc->expressionContext(), rightDirSymb ).toString();
1885 }
1886
1887 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::DirSymbPlacement ) )
1888 {
1889 rc->expressionContext().setOriginalValueVariable( static_cast< int >( placeDirSymb ) );
1890 placeDirSymb = static_cast< QgsLabelLineSettings::DirectionSymbolPlacement >(
1891 mDataDefinedProperties.valueAsInt( QgsPalLayerSettings::Property::DirSymbPlacement, rc->expressionContext(), static_cast< int >( placeDirSymb ) )
1892 );
1893 }
1894 }
1895 }
1896
1897 if ( wrapchr.isEmpty() )
1898 {
1899 wrapchr = u"\n"_s; // default to new line delimiter
1900 }
1901
1902 //consider the space needed for the direction symbol
1903 if ( addDirSymb && placement == Qgis::LabelPlacement::Line && ( !leftDirSymb.isEmpty() || !rightDirSymb.isEmpty() ) )
1904 {
1905 QString dirSym = leftDirSymb;
1906
1907 if ( fm->horizontalAdvance( rightDirSymb ) > fm->horizontalAdvance( dirSym ) )
1908 dirSym = rightDirSymb;
1909
1910 switch ( placeDirSymb )
1911 {
1913 textCopy.append( dirSym );
1914 break;
1915
1918 textCopy.prepend( dirSym + u"\n"_s );
1919 break;
1920 }
1921 }
1922
1923 double w = 0.0, h = 0.0, rw = 0.0, rh = 0.0;
1924 double labelHeight = fm->ascent() + fm->descent(); // ignore +1 for baseline
1925
1926 const QStringList multiLineSplit = QgsPalLabeling::splitToLines( textCopy, wrapchr, evalAutoWrapLength, useMaxLineLengthForAutoWrap );
1927 const int lines = multiLineSplit.size();
1928
1929 const double lineHeightPainterUnits = rc->convertToPainterUnits( mFormat.lineHeight(), mFormat.lineHeightUnit() );
1930
1931 switch ( orientation )
1932 {
1934 {
1935 h += fm->height() + static_cast< double >( ( lines - 1 ) * ( mFormat.lineHeightUnit() == Qgis::RenderUnit::Percentage ? ( labelHeight * multilineH ) : lineHeightPainterUnits ) );
1936
1937 for ( const QString &line : std::as_const( multiLineSplit ) )
1938 {
1939 w = std::max( w, fm->horizontalAdvance( line ) );
1940 }
1941 break;
1942 }
1943
1945 {
1946 double letterSpacing = mFormat.scaledFont( *rc ).letterSpacing();
1947 double labelWidth = fm->maxWidth();
1948 w = labelWidth + ( lines - 1 ) * ( mFormat.lineHeightUnit() == Qgis::RenderUnit::Percentage ? ( labelWidth * multilineH ) : lineHeightPainterUnits );
1949
1950 int maxLineLength = 0;
1951 for ( const QString &line : std::as_const( multiLineSplit ) )
1952 {
1953 maxLineLength = std::max( maxLineLength, static_cast<int>( line.length() ) );
1954 }
1955 h = fm->ascent() * maxLineLength + ( maxLineLength - 1 ) * letterSpacing;
1956 break;
1957 }
1958
1960 {
1961 double widthHorizontal = 0.0;
1962 for ( const QString &line : std::as_const( multiLineSplit ) )
1963 {
1964 widthHorizontal = std::max( widthHorizontal, fm->horizontalAdvance( line ) );
1965 }
1966
1967 double widthVertical = 0.0;
1968 double letterSpacing = mFormat.scaledFont( *rc ).letterSpacing();
1969 double labelWidth = fm->maxWidth();
1970 widthVertical = labelWidth + ( lines - 1 ) * ( mFormat.lineHeightUnit() == Qgis::RenderUnit::Percentage ? ( labelWidth * multilineH ) : lineHeightPainterUnits );
1971
1972 double heightHorizontal = 0.0;
1973 heightHorizontal += fm->height() + static_cast< double >( ( lines - 1 ) * ( mFormat.lineHeightUnit() == Qgis::RenderUnit::Percentage ? ( labelHeight * multilineH ) : lineHeightPainterUnits ) );
1974
1975 double heightVertical = 0.0;
1976 int maxLineLength = 0;
1977 for ( const QString &line : std::as_const( multiLineSplit ) )
1978 {
1979 maxLineLength = std::max( maxLineLength, static_cast<int>( line.length() ) );
1980 }
1981 heightVertical = fm->ascent() * maxLineLength + ( maxLineLength - 1 ) * letterSpacing;
1982
1983 w = widthHorizontal;
1984 rw = heightVertical;
1985 h = heightHorizontal;
1986 rh = widthVertical;
1987 break;
1988 }
1989 }
1990
1991 double uPP = xform->mapUnitsPerPixel();
1992 labelX = w * uPP;
1993 labelY = h * uPP;
1994 if ( rotatedLabelX && rotatedLabelY )
1995 {
1996 *rotatedLabelX = rw * uPP;
1997 *rotatedLabelY = rh * uPP;
1998 }
1999}
2000
2001void QgsPalLayerSettings::calculateLabelMetrics(
2002 const QFontMetricsF &fm, QgsRenderContext &context, const QgsTextFormat &format, QgsTextDocument &document, QgsTextDocumentMetrics &documentMetrics, QSizeF &size, QSizeF &rotatedSize, QRectF &outerBounds
2003) const
2004{
2005 if ( !mCurFeat )
2006 {
2007 return;
2008 }
2009
2010 Qgis::TextOrientation orientation = format.orientation();
2011
2012 bool addDirSymb = mLineSettings.addDirectionSymbol();
2013 QString leftDirSymb = mLineSettings.leftDirectionSymbol();
2014 QString rightDirSymb = mLineSettings.rightDirectionSymbol();
2015 QgsLabelLineSettings::DirectionSymbolPlacement placeDirSymb = mLineSettings.directionSymbolPlacement();
2016 double multilineH = mFormat.lineHeight();
2017
2018 if ( dataDefinedValues.contains( QgsPalLayerSettings::Property::TextOrientation ) )
2019 {
2020 orientation = QgsTextRendererUtils::decodeTextOrientation( dataDefinedValues.value( QgsPalLayerSettings::Property::TextOrientation ).toString() );
2021 }
2022
2023 if ( dataDefinedValues.contains( QgsPalLayerSettings::Property::MultiLineHeight ) )
2024 {
2025 multilineH = dataDefinedValues.value( QgsPalLayerSettings::Property::MultiLineHeight ).toDouble();
2026 }
2027
2028 if ( dataDefinedValues.contains( QgsPalLayerSettings::Property::DirSymbDraw ) )
2029 {
2030 addDirSymb = dataDefinedValues.value( QgsPalLayerSettings::Property::DirSymbDraw ).toBool();
2031 }
2032
2033 if ( addDirSymb )
2034 {
2035 if ( dataDefinedValues.contains( QgsPalLayerSettings::Property::DirSymbLeft ) )
2036 {
2037 leftDirSymb = dataDefinedValues.value( QgsPalLayerSettings::Property::DirSymbLeft ).toString();
2038 }
2039 if ( dataDefinedValues.contains( QgsPalLayerSettings::Property::DirSymbRight ) )
2040 {
2041 rightDirSymb = dataDefinedValues.value( QgsPalLayerSettings::Property::DirSymbRight ).toString();
2042 }
2043
2044 if ( dataDefinedValues.contains( QgsPalLayerSettings::Property::DirSymbPlacement ) )
2045 {
2046 placeDirSymb = static_cast< QgsLabelLineSettings::DirectionSymbolPlacement >( dataDefinedValues.value( QgsPalLayerSettings::Property::DirSymbPlacement ).toInt() );
2047 }
2048 }
2049
2050 const double lineHeightPainterUnits = context.convertToPainterUnits( mFormat.lineHeight(), mFormat.lineHeightUnit() );
2051
2052 //consider the space needed for the direction symbol
2053 QSizeF maximumExtraSpaceAllowance( 0, 0 );
2054 QSizeF minimumSize( 0, 0 );
2055 if ( addDirSymb && placement == Qgis::LabelPlacement::Line && ( !leftDirSymb.isEmpty() || !rightDirSymb.isEmpty() ) )
2056 {
2057 // we don't know which symbol we'll be rendering yet, so just assume the worst and that
2058 // we'll be rendering the larger one
2059 const QString dirSym = fm.horizontalAdvance( rightDirSymb ) > fm.horizontalAdvance( leftDirSymb ) ? rightDirSymb : leftDirSymb;
2060
2061 switch ( placeDirSymb )
2062 {
2064 maximumExtraSpaceAllowance = QSizeF( fm.horizontalAdvance( dirSym ), 0 );
2065 break;
2066
2069 maximumExtraSpaceAllowance = QSizeF( 0, ( mFormat.lineHeightUnit() == Qgis::RenderUnit::Percentage ? ( ( fm.ascent() + fm.descent() ) * multilineH ) : lineHeightPainterUnits ) );
2070 minimumSize = QSizeF( fm.horizontalAdvance( dirSym ), 0 );
2071 break;
2072 }
2073 }
2074
2075 double w = 0.0;
2076 double h = 0.0;
2077 double rw = 0.0;
2078 double rh = 0.0;
2079
2080 documentMetrics = QgsTextDocumentMetrics::calculateMetrics( document, format, context );
2081 const QSizeF documentSize = documentMetrics.documentSize( Qgis::TextLayoutMode::Labeling, orientation != Qgis::TextOrientation::RotationBased ? orientation : Qgis::TextOrientation::Horizontal );
2082 w = std::max( minimumSize.width(), documentSize.width() + maximumExtraSpaceAllowance.width() );
2083 h = std::max( minimumSize.height(), documentSize.height() + maximumExtraSpaceAllowance.height() );
2084
2085 if ( orientation == Qgis::TextOrientation::RotationBased )
2086 {
2087 const QSizeF rotatedSize = documentMetrics.documentSize( Qgis::TextLayoutMode::Labeling, Qgis::TextOrientation::Vertical );
2088 rh = std::max( minimumSize.width(), rotatedSize.width() + maximumExtraSpaceAllowance.width() );
2089 rw = std::max( minimumSize.height(), rotatedSize.height() + maximumExtraSpaceAllowance.height() );
2090 }
2091
2092 const double uPP = xform->mapUnitsPerPixel();
2093 size = QSizeF( w * uPP, h * uPP );
2094 rotatedSize = QSizeF( rw * uPP, rh * uPP );
2095
2096 // TODO -- does this need to account for maximumExtraSpaceAllowance / minimumSize ? Right now the size
2097 // of line direction symbols will be ignored
2098 const QRectF outerBoundsPixels = documentMetrics.outerBounds( Qgis::TextLayoutMode::Labeling, orientation );
2099
2100 outerBounds = QRectF( outerBoundsPixels.left() * uPP, outerBoundsPixels.top() * uPP, outerBoundsPixels.width() * uPP, outerBoundsPixels.height() * uPP );
2101}
2102
2107
2108bool QgsPalLayerSettings::isLabelVisible( QgsRenderContext &context ) const
2109{
2110 // data defined show label? defaults to show label if not set
2111 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::Show ) )
2112 {
2114 if ( !mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::Property::Show, context.expressionContext(), true ) )
2115 {
2116 return false;
2117 }
2118 }
2119
2120 // data defined scale visibility?
2121 bool useScaleVisibility = scaleVisibility;
2122 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::ScaleVisibility ) )
2123 useScaleVisibility = mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::Property::ScaleVisibility, context.expressionContext(), scaleVisibility );
2124
2125 if ( useScaleVisibility )
2126 {
2127 // data defined min scale?
2128 double maxScale = maximumScale;
2129 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::MaximumScale ) )
2130 {
2132 maxScale = mDataDefinedProperties.valueAsDouble( QgsPalLayerSettings::Property::MaximumScale, context.expressionContext(), maxScale );
2133 }
2134
2135 // scales closer than 1:1
2136 if ( maxScale < 0 )
2137 {
2138 maxScale = 1 / std::fabs( maxScale );
2139 }
2140
2141 // maxScale is inclusive ( < --> no label )
2142 if ( !qgsDoubleNear( maxScale, 0.0 ) && QgsScaleUtils::lessThanMaximumScale( context.rendererScale(), maxScale ) )
2143 {
2144 return false;
2145 }
2146
2147 // data defined min scale?
2148 double minScale = minimumScale;
2149 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::MinimumScale ) )
2150 {
2152 minScale = mDataDefinedProperties.valueAsDouble( QgsPalLayerSettings::Property::MinimumScale, context.expressionContext(), minScale );
2153 }
2154
2155 // scales closer than 1:1
2156 if ( minScale < 0 )
2157 {
2158 minScale = 1 / std::fabs( minScale );
2159 }
2160
2161 // minScale is exclusive ( >= --> no label )
2162 if ( !qgsDoubleNear( minScale, 0.0 ) && QgsScaleUtils::equalToOrGreaterThanMinimumScale( context.rendererScale(), minScale ) )
2163 {
2164 return false;
2165 }
2166 }
2167
2168 return true;
2169}
2170
2171QgsTextFormat QgsPalLayerSettings::evaluateTextFormat( QgsRenderContext &context, bool &labelIsHidden )
2172{
2173 labelIsHidden = false;
2174 QgsTextFormat evaluatedFormat = mFormat;
2175
2176 QFont labelFont = evaluatedFormat.font();
2177 // labelFont will be added to label feature for use during label painting
2178
2179 // data defined font units?
2180 Qgis::RenderUnit fontunits = evaluatedFormat.sizeUnit();
2181 const QVariant exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Property::FontSizeUnit, context.expressionContext() );
2182 if ( !QgsVariantUtils::isNull( exprVal ) )
2183 {
2184 QString units = exprVal.toString();
2185 if ( !units.isEmpty() )
2186 {
2187 bool ok;
2189 if ( ok )
2190 fontunits = res;
2191 }
2192 }
2193
2194 //data defined label size?
2195 double fontSize = evaluatedFormat.size();
2196 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::Size ) )
2197 {
2198 context.expressionContext().setOriginalValueVariable( fontSize );
2199 fontSize = mDataDefinedProperties.valueAsDouble( QgsPalLayerSettings::Property::Size, context.expressionContext(), fontSize );
2200 }
2201 if ( fontSize <= 0.0 )
2202 {
2203 labelIsHidden = true;
2204 return evaluatedFormat;
2205 }
2206
2207 int fontPixelSize = QgsTextRenderer::sizeToPixel( fontSize, context, fontunits, evaluatedFormat.sizeMapUnitScale() );
2208 // don't try to show font sizes less than 1 pixel (Qt complains)
2209 if ( fontPixelSize < 1 )
2210 {
2211 labelIsHidden = true;
2212 return evaluatedFormat;
2213 }
2214 labelFont.setPixelSize( fontPixelSize );
2215
2216 // NOTE: labelFont now always has pixelSize set, so pointSize or pointSizeF might return -1
2217
2218 // defined 'minimum/maximum pixel font size'?
2219 if ( fontunits == Qgis::RenderUnit::MapUnits )
2220 {
2221 if ( mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::Property::FontLimitPixel, context.expressionContext(), fontLimitPixelSize ) )
2222 {
2223 int fontMinPixel = mDataDefinedProperties.valueAsInt( QgsPalLayerSettings::Property::FontMinPixel, context.expressionContext(), fontMinPixelSize );
2224 int fontMaxPixel = mDataDefinedProperties.valueAsInt( QgsPalLayerSettings::Property::FontMaxPixel, context.expressionContext(), fontMaxPixelSize );
2225
2226 if ( fontMinPixel > labelFont.pixelSize() || labelFont.pixelSize() > fontMaxPixel )
2227 {
2228 labelIsHidden = true;
2229 return evaluatedFormat;
2230 }
2231 }
2232 }
2233
2234 // NOTE: the following parsing functions calculate and store any data defined values for later use in QgsPalLabeling::drawLabeling
2235 // this is done to provide clarity, and because such parsing is not directly related to PAL feature registration calculations
2236
2237 // calculate rest of font attributes and store any data defined values
2238 // this is done here for later use in making label backgrounds part of collision management (when implemented)
2239
2240 // maintain API - capitalization may have been set in textFont
2241 if ( evaluatedFormat.capitalization() == Qgis::Capitalization::MixedCase && mFormat.font().capitalization() != QFont::MixedCase )
2242 {
2243 evaluatedFormat.setCapitalization( static_cast< Qgis::Capitalization >( mFormat.font().capitalization() ) );
2244 }
2245 labelFont.setCapitalization( QFont::MixedCase ); // reset this - we don't use QFont's handling
2246 // as above REQUIRED for maintaining API
2247 evaluatedFormat.setCapitalization( evaluatedFormat.capitalization() );
2248
2249 // data defined font capitalization?
2250 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::FontCase ) )
2251 {
2252 const QVariant exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Property::FontCase, context.expressionContext() );
2253 if ( !QgsVariantUtils::isNull( exprVal ) )
2254 {
2255 static constexpr auto FONT_CASE_MAP = makeDataDefinedStringDecoder<Qgis::Capitalization>( {
2256 { u"nochange", Qgis::Capitalization::MixedCase },
2260 { u"title", Qgis::Capitalization::TitleCase },
2261 { u"smallcaps", Qgis::Capitalization::SmallCaps },
2262 { u"allsmallcaps", Qgis::Capitalization::AllSmallCaps },
2263 } );
2264
2265 const QString fcase = exprVal.toString();
2266 auto evaluatedCase = FONT_CASE_MAP.lookup( fcase );
2267 if ( evaluatedCase )
2268 {
2269 evaluatedFormat.setCapitalization( *evaluatedCase );
2270 }
2271 }
2272 }
2273
2274 parseTextStyle( labelFont, fontunits, context );
2275 if ( mDataDefinedProperties.hasActiveProperties() )
2276 {
2277 parseTextFormatting( context );
2278 parseTextBuffer( context );
2279 parseTextMask( context );
2280 parseShapeBackground( context );
2281 parseDropShadow( context );
2282 }
2283
2284 if ( dataDefinedValues.contains( QgsPalLayerSettings::Property::TabStopDistance ) )
2285 {
2286 QList<QgsTextFormat::Tab> tabPositions;
2287 if ( dataDefinedValues.value( QgsPalLayerSettings::Property::TabStopDistance ).userType() == QMetaType::Type::QVariantList )
2288 {
2289 const QVariantList parts = dataDefinedValues.value( QgsPalLayerSettings::Property::TabStopDistance ).toList();
2290 for ( const QVariant &part : parts )
2291 {
2292 tabPositions.append( QgsTextFormat::Tab( part.toDouble() ) );
2293 }
2294 evaluatedFormat.setTabPositions( tabPositions );
2295 }
2296 else if ( dataDefinedValues.value( QgsPalLayerSettings::Property::TabStopDistance ).userType() == QMetaType::Type::QStringList )
2297 {
2298 const QStringList parts = dataDefinedValues.value( QgsPalLayerSettings::Property::TabStopDistance ).toStringList();
2299 for ( const QString &part : parts )
2300 {
2301 tabPositions.append( QgsTextFormat::Tab( part.toDouble() ) );
2302 }
2303 evaluatedFormat.setTabPositions( tabPositions );
2304 }
2305 else
2306 {
2307 evaluatedFormat.setTabPositions( tabPositions );
2308 evaluatedFormat.setTabStopDistance( dataDefinedValues.value( QgsPalLayerSettings::Property::TabStopDistance ).toDouble() );
2309 }
2310 }
2311
2312 evaluatedFormat.setFont( labelFont );
2313 // undo scaling by symbology reference scale, as this would have been applied in the previous call to QgsTextRenderer::sizeToPixel and we risk
2314 // double-applying it if we don't re-adjust, since all the text format metric calculations assume an unscaled format font size is present
2315 const double symbologyReferenceScaleFactor = context.symbologyReferenceScale() > 0 ? context.symbologyReferenceScale() / context.rendererScale() : 1;
2316 evaluatedFormat.setSize( labelFont.pixelSize() / symbologyReferenceScaleFactor );
2317 evaluatedFormat.setSizeUnit( Qgis::RenderUnit::Pixels );
2318
2319 return evaluatedFormat;
2320}
2321
2322bool QgsPalLayerSettings::evaluateLabelContent( const QgsFeature &feature, QgsRenderContext &context, bool allowMultipleLines, QString &labelText, QgsTextDocument &document, const QgsTextFormat &format ) const
2323{
2324 QgsTextFormat formatEvaluated = format;
2325
2326 // Check to see if we are using an expression string.
2327 if ( isExpression )
2328 {
2329 QgsExpression *exp = getLabelExpression();
2330 if ( exp->hasParserError() )
2331 {
2332 QgsDebugMsgLevel( u"Expression parser error:%1"_s.arg( exp->parserErrorString() ), 4 );
2333 return false;
2334 }
2335
2336 QVariant result = exp->evaluate( &context.expressionContext() ); // expression prepared in QgsPalLabeling::prepareLayer()
2337 if ( exp->hasEvalError() )
2338 {
2339 QgsDebugMsgLevel( u"Expression parser eval error:%1"_s.arg( exp->evalErrorString() ), 4 );
2340 return false;
2341 }
2342 labelText = QgsVariantUtils::isNull( result ) ? QString() : result.toString();
2343 }
2344 else
2345 {
2346 const QVariant &v = feature.attribute( fieldIndex );
2347 labelText = QgsVariantUtils::isNull( v ) ? QString() : v.toString();
2348 }
2349
2350 // TODO -- this is in the wrong place. We should be substituting the text only, ie after we have parsed
2351 // any HTML tags to a text document.
2352
2353 // apply text replacements
2354 if ( useSubstitutions )
2355 {
2356 labelText = substitutions.process( labelText );
2357 }
2358
2359 // TODO -- this is in the wrong place. We should be formatting numbers AFTER converting HTML to documents
2360
2361 // format number if label text is coercible to a number
2362 bool evalFormatNumbers = formatNumbers;
2363 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::NumFormat ) )
2364 {
2365 evalFormatNumbers = mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::Property::NumFormat, context.expressionContext(), evalFormatNumbers );
2366 }
2367 if ( evalFormatNumbers )
2368 {
2369 // data defined decimal places?
2370 int decimalPlaces = mDataDefinedProperties.valueAsInt( QgsPalLayerSettings::Property::NumDecimals, context.expressionContext(), decimals );
2371 if ( decimalPlaces <= 0 ) // needs to be positive
2372 decimalPlaces = decimals;
2373
2374 // data defined plus sign?
2375 bool signPlus = mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::Property::NumPlusSign, context.expressionContext(), plusSign );
2376
2377 QVariant textV( labelText );
2378 bool ok;
2379 double d = textV.toDouble( &ok );
2380 if ( ok )
2381 {
2382 QString numberFormat;
2383 if ( d > 0 && signPlus )
2384 {
2385 numberFormat.append( '+' );
2386 }
2387 numberFormat.append( "%1" );
2388 labelText = numberFormat.arg( QLocale().toString( d, 'f', decimalPlaces ) );
2389 }
2390 }
2391
2392 // maintain API - capitalization may have been set in textFont
2393 if ( formatEvaluated.capitalization() == Qgis::Capitalization::MixedCase && mFormat.font().capitalization() != QFont::MixedCase )
2394 {
2395 formatEvaluated.setCapitalization( static_cast< Qgis::Capitalization >( mFormat.font().capitalization() ) );
2396 }
2397
2398 QString wrapchr = wrapChar;
2399 QString wrapchrCopy = wrapChar;
2400 int evalAutoWrapLength = autoWrapLength;
2401 if ( allowMultipleLines )
2402 {
2403 if ( dataDefinedValues.contains( QgsPalLayerSettings::Property::MultiLineWrapChar ) )
2404 {
2405 wrapchr = dataDefinedValues.value( QgsPalLayerSettings::Property::MultiLineWrapChar ).toString();
2406 }
2407 if ( dataDefinedValues.contains( QgsPalLayerSettings::Property::AutoWrapLength ) )
2408 {
2409 evalAutoWrapLength = dataDefinedValues.value( QgsPalLayerSettings::Property::AutoWrapLength, evalAutoWrapLength ).toInt();
2410 }
2411
2412 wrapchrCopy = wrapchr;
2413
2414 if ( wrapchr.isEmpty() )
2415 {
2416 wrapchr = u"\n"_s; // default to new line delimiter
2417 wrapchrCopy = wrapchr;
2418 }
2419 else if ( formatEvaluated.capitalization() != Qgis::Capitalization::MixedCase )
2420 {
2421 // Change wrapchr to a non printable character to avoid issues with capitalization.
2422 while ( labelText.contains( wrapchr ) )
2423 {
2424 labelText.replace( wrapchr, QString( QChar::ReplacementCharacter ) );
2425 }
2426
2427 wrapchr = QString( QChar::ReplacementCharacter );
2428 }
2429 }
2430
2431 document = QgsTextDocument::fromTextAndFormat( { labelText }, formatEvaluated );
2432 labelText = document.toPlainText().join( wrapchrCopy );
2433
2434 if ( allowMultipleLines )
2435 {
2436 document.splitLines( wrapchr, evalAutoWrapLength, useMaxLineLengthForAutoWrap );
2437 }
2438
2439 return true;
2440}
2441
2442QgsGeometry QgsPalLayerSettings::evaluateLabelGeometry( const QgsFeature &feature, QgsRenderContext &context, const QgsLabelLineSettings &lineSettings ) const
2443{
2444 QgsGeometry geom = feature.geometry();
2445 if ( geom.isEmpty() )
2446 {
2447 return geom;
2448 }
2449
2450 // simplify?
2451 bool allowSimplify = true;
2452 switch ( placement )
2453 {
2456 {
2457 switch ( lineSettings.curvedLabelMode() )
2458 {
2460 // don't simplify -- this would remove vertices, making characters out of sync with remaining vertices
2461 allowSimplify = false;
2462 break;
2466 break;
2467 }
2468 break;
2469 }
2470
2478 break;
2479 }
2480
2481 const QgsVectorSimplifyMethod &simplifyMethod = context.vectorSimplifyMethod();
2482 std::unique_ptr<QgsGeometry> scopedClonedGeom;
2484 {
2485 unsigned int simplifyHints = simplifyMethod.simplifyHints() | QgsMapToPixelSimplifier::SimplifyEnvelope;
2486 const Qgis::VectorSimplificationAlgorithm simplifyAlgorithm = simplifyMethod.simplifyAlgorithm();
2487 QgsMapToPixelSimplifier simplifier( simplifyHints, simplifyMethod.tolerance(), simplifyAlgorithm );
2488 geom = simplifier.simplify( geom );
2489 }
2490
2491 return geom;
2492}
2493
2494std::vector<std::unique_ptr<QgsLabelFeature> > QgsPalLayerSettings::registerFeatureWithDetails( const QgsFeature &f, QgsRenderContext &context, const QgsLabelFeatureDetails &details )
2495{
2496 QVariant exprVal; // value() is repeatedly nulled on data defined evaluation and replaced when successful
2497 mCurFeat = &f;
2498
2499 // data defined is obstacle? calculate this first, to avoid wasting time working with obstacles we don't require
2500 bool isObstacle = mObstacleSettings.isObstacle();
2501 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::IsObstacle ) )
2502 isObstacle = mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::Property::IsObstacle, context.expressionContext(), isObstacle ); // default to layer default
2503
2504 std::vector<std::unique_ptr<QgsLabelFeature> > res;
2505
2506 QgsGeometry obstacleGeometry = details.obstacleGeometry();
2507
2508 // possibly an obstacle-only feature
2509 if ( !drawLabels )
2510 {
2511 if ( isObstacle )
2512 {
2513 std::unique_ptr< QgsLabelFeature > obstacle = registerObstacleFeature( f, context, obstacleGeometry );
2514 if ( obstacle )
2515 res.emplace_back( std::move( obstacle ) );
2516 return res;
2517 }
2518 else
2519 {
2520 return {};
2521 }
2522 }
2523
2524 if ( !isLabelVisible( context ) )
2525 return {};
2526
2527 QgsFeature feature = f;
2529 {
2530 const QgsGeometry geometry = mGeometryGeneratorExpression.evaluate( &context.expressionContext() ).value<QgsGeometry>();
2531 if ( mGeometryGeneratorExpression.hasEvalError() )
2532 QgsMessageLog::logMessage( mGeometryGeneratorExpression.evalErrorString(), QObject::tr( "Labeling" ) );
2533
2534 if ( obstacleGeometry.isNull() )
2535 {
2536 // if an explicit obstacle geometry hasn't been set, we must always use the original feature geometry
2537 // as the obstacle -- because we want to use the geometry which was used to render the symbology
2538 // for the feature as the obstacle for other layers' labels, NOT the generated geometry which is used
2539 // only to place labels for this layer.
2540 obstacleGeometry = f.geometry();
2541 }
2542
2543 feature.setGeometry( geometry );
2544 }
2545
2546 // store data defined-derived values for later adding to label feature for use during rendering
2547 dataDefinedValues.clear();
2548
2549 // evaluate text format and font properties
2550 bool labelIsHidden = false;
2551 QgsTextFormat evaluatedFormat = evaluateTextFormat( context, labelIsHidden );
2552 if ( labelIsHidden )
2553 return {};
2554
2556 {
2557 // when rendering labels in a selected state, we render them using a buffer in the layer's selection color
2558 dataDefinedValues.insert( QgsPalLayerSettings::Property::BufferDraw, true );
2559 dataDefinedValues.insert( QgsPalLayerSettings::Property::BufferSize, 2 );
2560 dataDefinedValues.insert( QgsPalLayerSettings::Property::BufferColor, context.selectionColor() );
2561 }
2562
2563 QgsLabelPlacementSettings placementSettings = mPlacementSettings;
2564 placementSettings.updateDataDefinedProperties( mDataDefinedProperties, context.expressionContext() );
2565
2566 QString labelText;
2567 QgsTextDocument doc;
2568 bool allowMultipleLines = true;
2569 switch ( placement )
2570 {
2573 {
2574 switch ( placementSettings.multiPartBehavior() )
2575 {
2578 // for these curved placements we only support a single line of text
2579 allowMultipleLines = false;
2580 break;
2581
2583 // this mode permits multiple lines, as we'll be splitting them over the geometry parts
2584 allowMultipleLines = true;
2585 break;
2586 }
2587 break;
2588 }
2589
2597 {
2598 break;
2599 }
2600 }
2601 if ( !evaluateLabelContent( feature, context, allowMultipleLines, labelText, doc, evaluatedFormat ) )
2602 return {};
2603
2604 // geometry
2605 QgsLabelLineSettings lineSettings = mLineSettings;
2606 lineSettings.updateDataDefinedProperties( mDataDefinedProperties, context.expressionContext() );
2607
2608 QgsLabelPointSettings pointSettings = mPointSettings;
2609 pointSettings.updateDataDefinedProperties( mDataDefinedProperties, context.expressionContext() );
2610
2611 QgsGeometry geom = evaluateLabelGeometry( feature, context, lineSettings );
2612 if ( geom.isEmpty() )
2613 return {};
2614
2615
2616 // data defined centroid whole or clipped?
2617 bool wholeCentroid = centroidWhole;
2618 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::CentroidWhole ) )
2619 {
2620 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Property::CentroidWhole, context.expressionContext() );
2621 if ( !QgsVariantUtils::isNull( exprVal ) )
2622 {
2623 QString str = exprVal.toString().trimmed();
2624 QgsDebugMsgLevel( u"exprVal CentroidWhole:%1"_s.arg( str ), 4 );
2625
2626 if ( !str.isEmpty() )
2627 {
2628 if ( str.compare( "Visible"_L1, Qt::CaseInsensitive ) == 0 )
2629 {
2630 wholeCentroid = false;
2631 }
2632 else if ( str.compare( "Whole"_L1, Qt::CaseInsensitive ) == 0 )
2633 {
2634 wholeCentroid = true;
2635 }
2636 }
2637 }
2638 }
2639
2640 // whether we're going to create a centroid for polygon
2642
2643 // CLIP the geometry if it is bigger than the extent
2644 // don't clip if centroid is requested for whole feature
2645 bool doClip = false;
2646 if ( !centroidPoly || !wholeCentroid )
2647 {
2648 doClip = true;
2649 }
2650
2652 {
2653 if ( !obstacleGeometry.isNull() && QgsPalLabeling::geometryRequiresPreparation( obstacleGeometry, context, ct, doClip ? extentGeom : QgsGeometry(), lineSettings.mergeLines() ) )
2654 {
2655 obstacleGeometry = QgsGeometry( QgsPalLabeling::prepareGeometry( obstacleGeometry, context, ct, doClip ? extentGeom : QgsGeometry(), lineSettings.mergeLines() ) );
2656 }
2657 }
2658
2659 switch ( placementSettings.multiPartBehavior() )
2660 {
2663 {
2664 std::unique_ptr< QgsTextLabelFeature > label
2665 = generateLabelFeature( context, feature, 0, geom, obstacleGeometry, doc, labelText, evaluatedFormat, details.symbol(), lineSettings, pointSettings, placementSettings, isObstacle, doClip );
2666 if ( label )
2667 res.emplace_back( std::move( label ) );
2668 break;
2669 }
2670
2672 {
2673 const QVector< QgsGeometry > geometryParts = geom.asGeometryCollection();
2674 const QVector< QgsTextDocument > documentParts = doc.splitBlocksToDocuments();
2675 const std::size_t partCount = std::min( geometryParts.size(), documentParts.size() );
2676 res.reserve( partCount );
2677 for ( std::size_t i = 0; i < partCount; ++i )
2678 {
2679 std::unique_ptr< QgsTextLabelFeature > label
2680 = generateLabelFeature( context, feature, static_cast< int >( i ), geometryParts[i], obstacleGeometry, documentParts[i], labelText, evaluatedFormat, details.symbol(), lineSettings, pointSettings, placementSettings, isObstacle, doClip );
2681 if ( label )
2682 res.emplace_back( std::move( label ) );
2683 }
2684
2685 break;
2686 }
2687 }
2688
2689 return res;
2690}
2691
2692std::unique_ptr< QgsTextLabelFeature> QgsPalLayerSettings::generateLabelFeature(
2693 QgsRenderContext &context,
2694 const QgsFeature &feature,
2695 int subPartId,
2696 QgsGeometry geom,
2697 const QgsGeometry &obstacleGeometry,
2698 QgsTextDocument doc,
2699 const QString &labelText,
2700 const QgsTextFormat &evaluatedFormat,
2701 const QgsSymbol *symbol,
2702 const QgsLabelLineSettings &lineSettings,
2703 const QgsLabelPointSettings &pointSettings,
2704 const QgsLabelPlacementSettings &placementSettings,
2705 bool isObstacle,
2706 bool doClip
2707) const
2708{
2709 QVariant exprVal;
2710 QSizeF labelSize;
2711 QSizeF rotatedSize;
2712
2713 QgsTextDocumentMetrics documentMetrics;
2714 QRectF outerBounds;
2715 QFontMetricsF labelFontMetrics( evaluatedFormat.font() );
2716 calculateLabelMetrics( labelFontMetrics, context, evaluatedFormat, doc, documentMetrics, labelSize, rotatedSize, outerBounds );
2717
2718 // maximum angle between curved label characters (hardcoded defaults used in QGIS <2.0)
2719 //
2720 double maxcharanglein = 20.0; // range 20.0-60.0
2721 double maxcharangleout = -20.0; // range 20.0-95.0
2722
2723 switch ( placement )
2724 {
2727 {
2728 maxcharanglein = maxCurvedCharAngleIn;
2729 maxcharangleout = maxCurvedCharAngleOut;
2730
2731 //data defined maximum angle between curved label characters?
2732 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::CurvedCharAngleInOut ) )
2733 {
2734 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Property::CurvedCharAngleInOut, context.expressionContext() );
2735 bool ok = false;
2736 const QPointF maxcharanglePt = QgsSymbolLayerUtils::toPoint( exprVal, &ok );
2737 if ( ok )
2738 {
2739 maxcharanglein = std::clamp( static_cast< double >( maxcharanglePt.x() ), 20.0, 60.0 );
2740 maxcharangleout = std::clamp( static_cast< double >( maxcharanglePt.y() ), 20.0, 95.0 );
2741 }
2742 }
2743 // make sure maxcharangleout is always negative
2744 maxcharangleout = -( std::fabs( maxcharangleout ) );
2745 break;
2746 }
2747
2755 break;
2756 }
2757
2758 if ( !context.featureClipGeometry().isEmpty() )
2759 {
2760 const Qgis::GeometryType expectedType = geom.type();
2761 geom = geom.intersection( context.featureClipGeometry(), QgsGeometryParameters(), context.feedback() );
2762 geom.convertGeometryCollectionToSubclass( expectedType );
2763 }
2764
2765 Qgis::LabelPolygonPlacementFlags polygonPlacement = mPolygonPlacementFlags;
2766 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::PolygonLabelOutside ) )
2767 {
2768 const QVariant dataDefinedOutside = mDataDefinedProperties.value( QgsPalLayerSettings::Property::PolygonLabelOutside, context.expressionContext() );
2769 if ( !QgsVariantUtils::isNull( dataDefinedOutside ) )
2770 {
2771 if ( dataDefinedOutside.userType() == QMetaType::Type::QString )
2772 {
2773 const QString value = dataDefinedOutside.toString().trimmed();
2774 if ( value.compare( "force"_L1, Qt::CaseInsensitive ) == 0 )
2775 {
2776 // forced outside placement -- remove inside flag, add outside flag
2777 polygonPlacement &= ~static_cast< int >( Qgis::LabelPolygonPlacementFlag::AllowPlacementInsideOfPolygon );
2779 }
2780 else if ( value.compare( "yes"_L1, Qt::CaseInsensitive ) == 0 )
2781 {
2782 // permit outside placement
2784 }
2785 else if ( value.compare( "no"_L1, Qt::CaseInsensitive ) == 0 )
2786 {
2787 // block outside placement
2788 polygonPlacement &= ~static_cast< int >( Qgis::LabelPolygonPlacementFlag::AllowPlacementOutsideOfPolygon );
2789 }
2790 }
2791 else
2792 {
2793 if ( dataDefinedOutside.toBool() )
2794 {
2795 // permit outside placement
2797 }
2798 else
2799 {
2800 // block outside placement
2801 polygonPlacement &= ~static_cast< int >( Qgis::LabelPolygonPlacementFlag::AllowPlacementOutsideOfPolygon );
2802 }
2803 }
2804 }
2805 }
2806
2808 {
2809 switch ( lineSettings.anchorClipping() )
2810 {
2812 break;
2813
2815 doClip = false;
2816 break;
2817 }
2818
2820 {
2821 switch ( lineSettings.curvedLabelMode() )
2822 {
2824 // don't clip geometries when in this mode, or vertices will get out-of-sync with their corresponding
2825 // characters!
2826 doClip = false;
2827 break;
2831 break;
2832 }
2833 }
2834 }
2835
2836 // if using fitInPolygonOnly option, generate the permissible zone (must happen before geometry is modified - e.g.,
2837 // as a result of using perimeter based labeling and the geometry is converted to a boundary)
2838 // note that we also force this if we are permitting labels to be placed outside of polygons too!
2839 QgsGeometry permissibleZone;
2841 {
2842 permissibleZone = geom;
2843 if ( QgsPalLabeling::geometryRequiresPreparation( permissibleZone, context, ct, doClip ? extentGeom : QgsGeometry(), lineSettings.mergeLines() ) )
2844 {
2845 permissibleZone = QgsPalLabeling::prepareGeometry( permissibleZone, context, ct, doClip ? extentGeom : QgsGeometry(), lineSettings.mergeLines() );
2846 }
2847 }
2848
2849 // if using perimeter based labeling for polygons, get the polygon's
2850 // linear boundary and use that for the label geometry
2852 {
2853 geom = QgsGeometry( geom.constGet()->boundary() );
2854 }
2855
2856 geos::unique_ptr geos_geom_clone;
2857 if ( QgsPalLabeling::geometryRequiresPreparation( geom, context, ct, doClip ? extentGeom : QgsGeometry(), lineSettings.mergeLines() ) )
2858 {
2859 geom = QgsPalLabeling::prepareGeometry( geom, context, ct, doClip ? extentGeom : QgsGeometry(), lineSettings.mergeLines() );
2860
2861 if ( geom.isEmpty() )
2862 return {};
2863 }
2865
2866 QgsLabelThinningSettings featureThinningSettings = mThinningSettings;
2867 featureThinningSettings.updateDataDefinedProperties( mDataDefinedProperties, context.expressionContext() );
2868
2869 double minimumSize = 0.0;
2870 if ( featureThinningSettings.minimumFeatureSize() > 0 )
2871 {
2872 // for minimum feature size on merged lines, we need to delay the filtering after the merging occurred in PAL
2873 if ( geom.type() == Qgis::GeometryType::Line && lineSettings.mergeLines() )
2874 {
2875 minimumSize = context.convertToMapUnits( featureThinningSettings.minimumFeatureSize(), Qgis::RenderUnit::Millimeters );
2876 }
2877 else
2878 {
2879 if ( !checkMinimumSizeMM( context, geom, featureThinningSettings.minimumFeatureSize() ) )
2880 return {};
2881 }
2882 }
2883
2884 if ( !geos_geom_clone )
2885 return {}; // invalid geometry
2886
2887 // likelihood exists label will be registered with PAL and may be drawn
2888 // check if max number of features to label (already registered with PAL) has been reached
2889 // Debug output at end of QgsPalLabeling::drawLabeling(), when deleting temp geometries
2890 if ( featureThinningSettings.limitNumberOfLabelsEnabled() )
2891 {
2892 if ( !featureThinningSettings.maximumNumberLabels() )
2893 {
2894 return {};
2895 }
2896 if ( mFeatsRegPal >= featureThinningSettings.maximumNumberLabels() )
2897 {
2898 return {};
2899 }
2900
2901 int divNum = static_cast< int >( ( static_cast< double >( mFeaturesToLabel ) / featureThinningSettings.maximumNumberLabels() ) + 0.5 ); // NOLINT
2902 if ( divNum && ( mFeatsRegPal == static_cast< int >( mFeatsSendingToPal / divNum ) ) )
2903 {
2904 mFeatsSendingToPal += 1;
2905 if ( divNum && mFeatsSendingToPal % divNum )
2906 {
2907 return {};
2908 }
2909 }
2910 }
2911
2912 //data defined position / alignment / rotation?
2913 bool layerDefinedRotation = false;
2914 bool dataDefinedRotation = false;
2915 double xPos = 0.0, yPos = 0.0;
2916 double angleInRadians = 0.0;
2917 double quadOffsetX = 0.0, quadOffsetY = 0.0;
2918 double offsetX = 0.0, offsetY = 0.0;
2919 QgsPointXY anchorPosition;
2920
2922 {
2923 anchorPosition = geom.centroid().asPoint();
2924 }
2925 //x/y shift in case of alignment
2926 double xdiff = 0.0;
2927 double ydiff = 0.0;
2928
2929 //data defined quadrant offset?
2930 bool ddFixedQuad = false;
2931 Qgis::LabelQuadrantPosition quadOff = pointSettings.quadrant();
2932 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::OffsetQuad ) )
2933 {
2934 context.expressionContext().setOriginalValueVariable( static_cast< int >( quadOff ) );
2935 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Property::OffsetQuad, context.expressionContext() );
2936 if ( !QgsVariantUtils::isNull( exprVal ) )
2937 {
2938 bool ok;
2939 int quadInt = exprVal.toInt( &ok );
2940 if ( ok && 0 <= quadInt && quadInt <= 8 )
2941 {
2942 quadOff = static_cast< Qgis::LabelQuadrantPosition >( quadInt );
2943 ddFixedQuad = true;
2944 }
2945 }
2946 }
2947
2948 // adjust quadrant offset of labels
2949 switch ( quadOff )
2950 {
2952 quadOffsetX = -1.0;
2953 quadOffsetY = 1.0;
2954 break;
2956 quadOffsetX = 0.0;
2957 quadOffsetY = 1.0;
2958 break;
2960 quadOffsetX = 1.0;
2961 quadOffsetY = 1.0;
2962 break;
2964 quadOffsetX = -1.0;
2965 quadOffsetY = 0.0;
2966 break;
2968 quadOffsetX = 1.0;
2969 quadOffsetY = 0.0;
2970 break;
2972 quadOffsetX = -1.0;
2973 quadOffsetY = -1.0;
2974 break;
2976 quadOffsetX = 0.0;
2977 quadOffsetY = -1.0;
2978 break;
2980 quadOffsetX = 1.0;
2981 quadOffsetY = -1.0;
2982 break;
2984 break;
2985 }
2986
2987 //data defined label offset?
2988 double xOff = xOffset;
2989 double yOff = yOffset;
2990 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::OffsetXY ) )
2991 {
2993 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Property::OffsetXY, context.expressionContext() );
2994 bool ok = false;
2995 const QPointF ddOffPt = QgsSymbolLayerUtils::toPoint( exprVal, &ok );
2996 if ( ok )
2997 {
2998 xOff = ddOffPt.x();
2999 yOff = ddOffPt.y();
3000 }
3001 }
3002
3003 // data defined label offset units?
3004 Qgis::RenderUnit offUnit = offsetUnits;
3005 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::OffsetUnits ) )
3006 {
3007 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Property::OffsetUnits, context.expressionContext() );
3008 if ( !QgsVariantUtils::isNull( exprVal ) )
3009 {
3010 QString units = exprVal.toString().trimmed();
3011 if ( !units.isEmpty() )
3012 {
3013 bool ok = false;
3014 Qgis::RenderUnit decodedUnits = QgsUnitTypes::decodeRenderUnit( units, &ok );
3015 if ( ok )
3016 {
3017 offUnit = decodedUnits;
3018 }
3019 }
3020 }
3021 }
3022
3023 // adjust offset of labels to match chosen unit and map scale
3024 // offsets match those of symbology: -x = left, -y = up
3025 offsetX = context.convertToMapUnits( xOff, offUnit, labelOffsetMapUnitScale );
3026 // must be negative to match symbology offset direction
3027 offsetY = context.convertToMapUnits( -yOff, offUnit, labelOffsetMapUnitScale );
3028
3029 // layer defined rotation?
3030 if ( !qgsDoubleNear( angleOffset, 0.0 ) )
3031 {
3032 layerDefinedRotation = true;
3033 angleInRadians = ( 360 - angleOffset ) * M_PI / 180; // convert to radians counterclockwise
3034 }
3035
3036 const QgsMapToPixel &m2p = context.mapToPixel();
3037 //data defined rotation?
3038 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::LabelRotation ) )
3039 {
3041 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Property::LabelRotation, context.expressionContext() );
3042 if ( !QgsVariantUtils::isNull( exprVal ) )
3043 {
3044 bool ok;
3045 const double rotation = exprVal.toDouble( &ok );
3046 if ( ok )
3047 {
3048 dataDefinedRotation = true;
3049
3050 double rotationDegrees = rotation * QgsUnitTypes::fromUnitToUnitFactor( mRotationUnit, Qgis::AngleUnit::Degrees );
3051
3052 // TODO: add setting to disable having data defined rotation follow
3053 // map rotation ?
3054 rotationDegrees += m2p.mapRotation();
3055 angleInRadians = ( 360 - rotationDegrees ) * M_PI / 180.0;
3056 }
3057 }
3058 }
3059
3060 bool hasDataDefinedPosition = false;
3061 {
3062 bool ddPosition = false;
3063
3064 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::PositionX ) && mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::PositionY ) )
3065 {
3066 const QVariant xPosProperty = mDataDefinedProperties.value( QgsPalLayerSettings::Property::PositionX, context.expressionContext() );
3067 const QVariant yPosProperty = mDataDefinedProperties.value( QgsPalLayerSettings::Property::PositionY, context.expressionContext() );
3068 if ( !QgsVariantUtils::isNull( xPosProperty ) && !QgsVariantUtils::isNull( yPosProperty ) )
3069 {
3070 ddPosition = true;
3071
3072 bool ddXPos = false, ddYPos = false;
3073 xPos = xPosProperty.toDouble( &ddXPos );
3074 yPos = yPosProperty.toDouble( &ddYPos );
3075 if ( ddXPos && ddYPos )
3076 hasDataDefinedPosition = true;
3077 }
3078 }
3079 else if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::PositionPoint ) )
3080 {
3081 const QVariant pointPosProperty = mDataDefinedProperties.value( QgsPalLayerSettings::Property::PositionPoint, context.expressionContext() );
3082 if ( !QgsVariantUtils::isNull( pointPosProperty ) )
3083 {
3084 ddPosition = true;
3085
3086 QgsPoint point;
3087 if ( pointPosProperty.userType() == qMetaTypeId<QgsReferencedGeometry>() )
3088 {
3089 QgsReferencedGeometry referencedGeometryPoint = pointPosProperty.value<QgsReferencedGeometry>();
3090 point = QgsPoint( referencedGeometryPoint.asPoint() );
3091
3092 if ( !referencedGeometryPoint.isNull() && ct.sourceCrs() != referencedGeometryPoint.crs() )
3094 QObject::tr( "Label position geometry is not in layer coordinates reference system. Layer CRS: '%1', Geometry CRS: '%2'" )
3095 .arg( ct.sourceCrs().userFriendlyIdentifier(), referencedGeometryPoint.crs().userFriendlyIdentifier() ),
3096 QObject::tr( "Labeling" ),
3098 );
3099 }
3100 else if ( pointPosProperty.userType() == qMetaTypeId< QgsGeometry>() )
3101 {
3102 point = QgsPoint( pointPosProperty.value<QgsGeometry>().asPoint() );
3103 }
3104
3105 if ( !point.isEmpty() )
3106 {
3107 hasDataDefinedPosition = true;
3108
3109 xPos = point.x();
3110 yPos = point.y();
3111 }
3112 }
3113 }
3114
3115 if ( ddPosition )
3116 {
3117 //data defined position. But field values could be NULL -> positions will be generated by PAL
3118 if ( hasDataDefinedPosition )
3119 {
3120 // layer rotation set, but don't rotate pinned labels unless data defined
3121 if ( layerDefinedRotation && !dataDefinedRotation )
3122 {
3123 angleInRadians = 0.0;
3124 }
3125
3126 //horizontal alignment
3127 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::Hali ) )
3128 {
3129 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Property::Hali, context.expressionContext() );
3130 if ( !QgsVariantUtils::isNull( exprVal ) )
3131 {
3132 QString haliString = exprVal.toString();
3133 if ( haliString.compare( "Center"_L1, Qt::CaseInsensitive ) == 0 )
3134 {
3135 xdiff -= labelSize.width() / 2.0;
3136 }
3137 else if ( haliString.compare( "Right"_L1, Qt::CaseInsensitive ) == 0 )
3138 {
3139 xdiff -= labelSize.width();
3140 }
3141 }
3142 }
3143
3144 //vertical alignment
3145 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::Vali ) )
3146 {
3147 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Property::Vali, context.expressionContext() );
3148 if ( !QgsVariantUtils::isNull( exprVal ) )
3149 {
3150 QString valiString = exprVal.toString();
3151 if ( valiString.compare( "Bottom"_L1, Qt::CaseInsensitive ) != 0 )
3152 {
3153 if ( valiString.compare( "Top"_L1, Qt::CaseInsensitive ) == 0 )
3154 {
3155 ydiff -= labelSize.height();
3156 }
3157 else
3158 {
3159 double descentRatio = labelFontMetrics.descent() / labelFontMetrics.height();
3160 if ( valiString.compare( "Base"_L1, Qt::CaseInsensitive ) == 0 )
3161 {
3162 ydiff -= labelSize.height() * descentRatio;
3163 }
3164 else //'Cap' or 'Half'
3165 {
3166 double capHeightRatio = ( labelFontMetrics.boundingRect( 'H' ).height() + 1 + labelFontMetrics.descent() ) / labelFontMetrics.height();
3167 ydiff -= labelSize.height() * capHeightRatio;
3168 if ( valiString.compare( "Half"_L1, Qt::CaseInsensitive ) == 0 )
3169 {
3170 ydiff += labelSize.height() * ( capHeightRatio - descentRatio ) / 2.0;
3171 }
3172 }
3173 }
3174 }
3175 }
3176 }
3177
3178 if ( dataDefinedRotation )
3179 {
3180 //adjust xdiff and ydiff because the hali/vali point needs to be the rotation center
3181 double xd = xdiff * std::cos( angleInRadians ) - ydiff * std::sin( angleInRadians );
3182 double yd = xdiff * std::sin( angleInRadians ) + ydiff * std::cos( angleInRadians );
3183 xdiff = xd;
3184 ydiff = yd;
3185 }
3186
3187 //project xPos and yPos from layer to map CRS, handle rotation
3188 QgsGeometry ddPoint( new QgsPoint( xPos, yPos ) );
3189 if ( QgsPalLabeling::geometryRequiresPreparation( ddPoint, context, ct ) )
3190 {
3191 ddPoint = QgsPalLabeling::prepareGeometry( ddPoint, context, ct );
3192 if ( const QgsPoint *point = qgsgeometry_cast< const QgsPoint * >( ddPoint.constGet() ) )
3193 {
3194 xPos = point->x();
3195 yPos = point->y();
3196 anchorPosition = QgsPointXY( xPos, yPos );
3197 }
3198 else
3199 {
3200 QgsMessageLog::logMessage( QObject::tr( "Invalid data defined label position (%1, %2)" ).arg( xPos ).arg( yPos ), QObject::tr( "Labeling" ) );
3201 hasDataDefinedPosition = false;
3202 }
3203 }
3204 else
3205 {
3206 anchorPosition = QgsPointXY( xPos, yPos );
3207 }
3208
3209 xPos += xdiff;
3210 yPos += ydiff;
3211 }
3212 else
3213 {
3214 anchorPosition = QgsPointXY( xPos, yPos );
3215 }
3216 }
3217 }
3218
3219 // data defined always show?
3220 bool alwaysShow = false;
3221 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::AlwaysShow ) )
3222 {
3223 alwaysShow = mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::Property::AlwaysShow, context.expressionContext(), false );
3224 }
3225
3226 // set repeat distance
3227 // data defined repeat distance?
3228 double repeatDist = repeatDistance;
3229 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::RepeatDistance ) )
3230 {
3231 context.expressionContext().setOriginalValueVariable( repeatDist );
3232 repeatDist = mDataDefinedProperties.valueAsDouble( QgsPalLayerSettings::Property::RepeatDistance, context.expressionContext(), repeatDist );
3233 }
3234
3235 // data defined label-repeat distance units?
3237 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::RepeatDistanceUnit ) )
3238 {
3239 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Property::RepeatDistanceUnit, context.expressionContext() );
3240 if ( !QgsVariantUtils::isNull( exprVal ) )
3241 {
3242 QString units = exprVal.toString().trimmed();
3243 if ( !units.isEmpty() )
3244 {
3245 bool ok = false;
3246 Qgis::RenderUnit decodedUnits = QgsUnitTypes::decodeRenderUnit( units, &ok );
3247 if ( ok )
3248 {
3249 repeatUnits = decodedUnits;
3250 }
3251 }
3252 }
3253 }
3254
3255 if ( !qgsDoubleNear( repeatDist, 0.0 ) )
3256 {
3257 if ( repeatUnits != Qgis::RenderUnit::MapUnits )
3258 {
3259 repeatDist = context.convertToMapUnits( repeatDist, repeatUnits, repeatDistanceMapUnitScale );
3260 }
3261 }
3262
3263 // overrun distance
3264 double overrunDistanceEval = lineSettings.overrunDistance();
3265 if ( !qgsDoubleNear( overrunDistanceEval, 0.0 ) )
3266 {
3267 overrunDistanceEval = context.convertToMapUnits( overrunDistanceEval, lineSettings.overrunDistanceUnit(), lineSettings.overrunDistanceMapUnitScale() );
3268 }
3269
3270 // we smooth out the overrun label extensions by 1 mm, to avoid little jaggies right at the start or end of the lines
3271 // causing the overrun extension to extend out in an undesirable direction. This is hard coded, we don't want to overload
3272 // users with options they likely don't need to see...
3273 const double overrunSmoothDist = context.convertToMapUnits( 1, Qgis::RenderUnit::Millimeters );
3274
3275 // maximum distance
3276 double maximumDistanceEval = pointSettings.maximumDistance();
3277 if ( !qgsDoubleNear( maximumDistanceEval, 0.0 ) )
3278 {
3279 maximumDistanceEval = context.convertToMapUnits( maximumDistanceEval, pointSettings.maximumDistanceUnit(), pointSettings.maximumDistanceMapUnitScale() );
3280 }
3281
3282 // feature to the layer
3283 auto labelFeature = std::make_unique< QgsTextLabelFeature>( feature.id(), std::move( geos_geom_clone ), labelSize, subPartId );
3284 labelFeature->setAnchorPosition( anchorPosition );
3285 labelFeature->setFeature( feature );
3286 labelFeature->setSymbol( symbol );
3287 labelFeature->setDocument( doc, documentMetrics );
3288 if ( !qgsDoubleNear( rotatedSize.width(), 0.0 ) && !qgsDoubleNear( rotatedSize.height(), 0.0 ) )
3289 labelFeature->setRotatedSize( rotatedSize );
3290 mFeatsRegPal++;
3291
3292 labelFeature->setHasFixedPosition( hasDataDefinedPosition );
3293 labelFeature->setFixedPosition( QgsPointXY( xPos, yPos ) );
3294 // use layer-level defined rotation, but not if position fixed
3295 labelFeature->setHasFixedAngle( dataDefinedRotation || ( !hasDataDefinedPosition && !qgsDoubleNear( angleInRadians, 0.0 ) ) );
3296 labelFeature->setFixedAngle( angleInRadians );
3297 labelFeature->setQuadOffset( QPointF( quadOffsetX, quadOffsetY ) );
3298 labelFeature->setPositionOffset( QgsPointXY( offsetX, offsetY ) );
3299 labelFeature->setOffsetType( offsetType );
3300 labelFeature->setAlwaysShow( alwaysShow );
3301 labelFeature->setRepeatDistance( repeatDist );
3302 labelFeature->setLabelText( labelText );
3303 labelFeature->setPermissibleZone( permissibleZone );
3304 labelFeature->setOverrunDistance( overrunDistanceEval );
3305 labelFeature->setOverrunSmoothDistance( overrunSmoothDist );
3306 labelFeature->setMaximumDistance( maximumDistanceEval );
3307 labelFeature->setLineAnchorPercent( lineSettings.lineAnchorPercent() );
3308 labelFeature->setLineAnchorType( lineSettings.anchorType() );
3309 labelFeature->setLineAnchorTextPoint( lineSettings.anchorTextPoint() );
3310 labelFeature->setCurvedLabelMode( lineSettings.curvedLabelMode() );
3311 labelFeature->setMultiPartBehavior( placementSettings.multiPartBehavior() );
3312 labelFeature->setOriginalFeatureCrs( context.coordinateTransform().sourceCrs() );
3313 labelFeature->setMinimumSize( minimumSize );
3314 labelFeature->setWhitespaceCollisionHandling( placementSettings.whitespaceCollisionHandling() );
3315 if ( geom.type() == Qgis::GeometryType::Point && !obstacleGeometry.isNull() )
3316 {
3317 //register symbol size
3318 labelFeature->setSymbolSize( QSizeF( obstacleGeometry.boundingBox().width(), obstacleGeometry.boundingBox().height() ) );
3319 }
3320
3321 if ( outerBounds.left() != 0 || outerBounds.top() != 0 || !qgsDoubleNear( outerBounds.width(), labelSize.width() ) || !qgsDoubleNear( outerBounds.height(), labelSize.height() ) )
3322 {
3323 labelFeature->setOuterBounds( outerBounds );
3324 }
3325
3326 QgsLabelFeatureThinningSettings thinning;
3327 if ( featureThinningSettings.labelMarginDistance() > 0 )
3328 {
3329 thinning.setLabelMarginDistance(
3330 context.convertToMapUnits( featureThinningSettings.labelMarginDistance(), featureThinningSettings.labelMarginDistanceUnit(), featureThinningSettings.labelMarginDistanceMapUnitScale() )
3331 );
3332 }
3333 if ( featureThinningSettings.allowDuplicateRemoval() )
3334 {
3335 thinning.setNoRepeatDistance(
3336 context.convertToMapUnits( featureThinningSettings.minimumDistanceToDuplicate(), featureThinningSettings.minimumDistanceToDuplicateUnit(), featureThinningSettings.minimumDistanceToDuplicateMapUnitScale() )
3337 );
3338 }
3339 ( *labelFeature ).setThinningSettings( thinning );
3340
3341 //set label's visual margin so that top visual margin is the leading, and bottom margin is the font's descent
3342 //this makes labels align to the font's baseline or highest character
3343 double topMargin = std::max( 0.25 * labelFontMetrics.ascent(), 0.0 );
3344 double bottomMargin = 1.0 + labelFontMetrics.descent();
3345 QgsMargins vm( 0.0, topMargin, 0.0, bottomMargin );
3346 vm *= xform->mapUnitsPerPixel();
3347 labelFeature->setVisualMargin( vm );
3348
3349 // store the label's calculated font for later use during painting
3350 QgsDebugMsgLevel( u"PAL font stored definedFont: %1, Style: %2"_s.arg( evaluatedFormat.font().toString(), evaluatedFormat.font().styleName() ), 4 );
3351 labelFeature->setDefinedFont( evaluatedFormat.font() );
3352
3353 labelFeature->setMaximumCharacterAngleInside( std::clamp( maxcharanglein, 20.0, 60.0 ) * M_PI / 180 );
3354 labelFeature->setMaximumCharacterAngleOutside( std::clamp( maxcharangleout, -95.0, -20.0 ) * M_PI / 180 );
3355 switch ( placement )
3356 {
3364 // these placements don't require text metrics
3365 break;
3366
3369 labelFeature->setTextMetrics(
3370 QgsTextLabelFeature::
3371 calculateTextMetrics( xform, context, evaluatedFormat, evaluatedFormat.font(), labelFontMetrics, evaluatedFormat.font().letterSpacing(), evaluatedFormat.font().wordSpacing(), doc, documentMetrics )
3372 );
3373 break;
3374 }
3375
3376 // for labelFeature the LabelInfo is passed to feat when it is registered
3377
3378 // TODO: allow layer-wide feature dist in PAL...?
3379
3380 // data defined label-feature distance?
3381 double distance = dist;
3382 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::LabelDistance ) )
3383 {
3384 context.expressionContext().setOriginalValueVariable( distance );
3385 distance = mDataDefinedProperties.valueAsDouble( QgsPalLayerSettings::Property::LabelDistance, context.expressionContext(), distance );
3386 }
3387
3388 // data defined label-feature distance units?
3389 Qgis::RenderUnit distUnit = distUnits;
3390 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::DistanceUnits ) )
3391 {
3392 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Property::DistanceUnits, context.expressionContext() );
3393 if ( !QgsVariantUtils::isNull( exprVal ) )
3394 {
3395 QString units = exprVal.toString().trimmed();
3396 QgsDebugMsgLevel( u"exprVal DistanceUnits:%1"_s.arg( units ), 4 );
3397 if ( !units.isEmpty() )
3398 {
3399 bool ok = false;
3400 Qgis::RenderUnit decodedUnits = QgsUnitTypes::decodeRenderUnit( units, &ok );
3401 if ( ok )
3402 {
3403 distUnit = decodedUnits;
3404 }
3405 }
3406 }
3407 }
3408 distance = context.convertToPainterUnits( distance, distUnit, distMapUnitScale );
3409
3410 // when using certain placement modes, we force a tiny minimum distance. This ensures that
3411 // candidates are created just offset from a border and avoids candidates being incorrectly flagged as colliding with neighbours
3412 switch ( placement )
3413 {
3417 distance = ( distance < 0 ? -1 : 1 ) * std::max( std::fabs( distance ), 1.0 );
3418 break;
3419
3421 break;
3422
3428 {
3429 distance = std::max( distance, 2.0 );
3430 }
3431 break;
3432
3434 distance = std::max( distance, 2.0 );
3435 break;
3436 }
3437
3438 if ( !qgsDoubleNear( distance, 0.0 ) )
3439 {
3440 double d = ptOne.distance( ptZero ) * distance;
3441 labelFeature->setDistLabel( d );
3442 }
3443
3444 if ( ddFixedQuad )
3445 {
3446 labelFeature->setHasFixedQuadrant( true );
3447 }
3448
3449 labelFeature->setArrangementFlags( lineSettings.placementFlags() );
3450
3451 labelFeature->setPolygonPlacementFlags( polygonPlacement );
3452
3453 // data defined z-index?
3454 double z = zIndex;
3455 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::ZIndex ) )
3456 {
3458 z = mDataDefinedProperties.valueAsDouble( QgsPalLayerSettings::Property::ZIndex, context.expressionContext(), z );
3459 }
3460 labelFeature->setZIndex( z );
3461
3462 // data defined priority?
3463 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::Priority ) )
3464 {
3466 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Property::Priority, context.expressionContext() );
3467 if ( !QgsVariantUtils::isNull( exprVal ) )
3468 {
3469 bool ok;
3470 double priorityD = exprVal.toDouble( &ok );
3471 if ( ok )
3472 {
3473 priorityD = std::clamp( priorityD, 0.0, 10.0 );
3474 priorityD = 1 - priorityD / 10.0; // convert 0..10 --> 1..0
3475 labelFeature->setPriority( priorityD );
3476 }
3477 }
3478 }
3479
3480 labelFeature->setAllowDegradedPlacement( placementSettings.allowDegradedPlacement() );
3481 labelFeature->setOverlapHandling( placementSettings.overlapHandling() );
3482 labelFeature->setPrioritization( placementSettings.prioritization() );
3483
3484 QgsLabelObstacleSettings os = mObstacleSettings;
3485 os.setIsObstacle( isObstacle );
3486 os.updateDataDefinedProperties( mDataDefinedProperties, context.expressionContext() );
3487 os.setObstacleGeometry( obstacleGeometry );
3488 labelFeature->setObstacleSettings( os );
3489
3490 QVector< Qgis::LabelPredefinedPointPosition > positionOrder = pointSettings.predefinedPositionOrder();
3491 if ( positionOrder.isEmpty() )
3492 positionOrder = *DEFAULT_PLACEMENT_ORDER();
3493
3494 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::PredefinedPositionOrder ) )
3495 {
3497 QString dataDefinedOrder = mDataDefinedProperties.valueAsString( QgsPalLayerSettings::Property::PredefinedPositionOrder, context.expressionContext() );
3498 if ( !dataDefinedOrder.isEmpty() )
3499 {
3500 positionOrder = QgsLabelingUtils::decodePredefinedPositionOrder( dataDefinedOrder );
3501 }
3502 }
3503 labelFeature->setPredefinedPositionOrder( positionOrder );
3504
3505 // add parameters for data defined labeling to label feature
3506 labelFeature->setDataDefinedValues( dataDefinedValues );
3507
3508 return labelFeature;
3509}
3510
3511std::unique_ptr<QgsLabelFeature> QgsPalLayerSettings::registerObstacleFeature( const QgsFeature &f, QgsRenderContext &context, const QgsGeometry &obstacleGeometry )
3512{
3513 mCurFeat = &f;
3514
3515 QgsGeometry geom;
3516 if ( !obstacleGeometry.isNull() )
3517 {
3518 geom = obstacleGeometry;
3519 }
3520 else
3521 {
3522 geom = f.geometry();
3523 }
3524
3525 if ( geom.isNull() )
3526 {
3527 return nullptr;
3528 }
3529
3530 // don't even try to register linestrings with only one vertex as an obstacle
3531 if ( const QgsLineString *ls = qgsgeometry_cast< const QgsLineString * >( geom.constGet() ) )
3532 {
3533 if ( ls->numPoints() < 2 )
3534 return nullptr;
3535 }
3536
3537 // simplify?
3538 const QgsVectorSimplifyMethod &simplifyMethod = context.vectorSimplifyMethod();
3539 std::unique_ptr<QgsGeometry> scopedClonedGeom;
3541 {
3542 int simplifyHints = simplifyMethod.simplifyHints() | QgsMapToPixelSimplifier::SimplifyEnvelope;
3543 const Qgis::VectorSimplificationAlgorithm simplifyAlgorithm = simplifyMethod.simplifyAlgorithm();
3544 QgsMapToPixelSimplifier simplifier( simplifyHints, simplifyMethod.tolerance(), simplifyAlgorithm );
3545 geom = simplifier.simplify( geom );
3546 }
3547
3548 geos::unique_ptr geos_geom_clone;
3549 std::unique_ptr<QgsGeometry> scopedPreparedGeom;
3550
3551 if ( QgsPalLabeling::geometryRequiresPreparation( geom, context, ct, extentGeom, mLineSettings.mergeLines() ) )
3552 {
3553 geom = QgsPalLabeling::prepareGeometry( geom, context, ct, extentGeom, mLineSettings.mergeLines() );
3554 }
3555 geos_geom_clone = QgsGeos::asGeos( geom );
3556
3557 if ( !geos_geom_clone )
3558 return nullptr; // invalid geometry
3559
3560 // feature to the layer
3561 auto obstacleFeature = std::make_unique< QgsLabelFeature >( f.id(), std::move( geos_geom_clone ), QSizeF( 0, 0 ) );
3562 obstacleFeature->setFeature( f );
3563
3564 QgsLabelObstacleSettings os = mObstacleSettings;
3565 os.setIsObstacle( true );
3566 os.updateDataDefinedProperties( mDataDefinedProperties, context.expressionContext() );
3567 obstacleFeature->setObstacleSettings( os );
3568
3569 mFeatsRegPal++;
3570 return obstacleFeature;
3571}
3572
3573bool QgsPalLayerSettings::dataDefinedValEval( DataDefinedValueType valType, QgsPalLayerSettings::Property p, QVariant &exprVal, QgsExpressionContext &context, const QVariant &originalValue )
3574{
3575 if ( !mDataDefinedProperties.isActive( p ) )
3576 return false;
3577
3578 context.setOriginalValueVariable( originalValue );
3579 exprVal = mDataDefinedProperties.value( p, context );
3580 if ( !QgsVariantUtils::isNull( exprVal ) )
3581 {
3582 switch ( valType )
3583 {
3584 case DDBool:
3585 {
3586 bool bol = exprVal.toBool();
3587 dataDefinedValues.insert( p, QVariant( bol ) );
3588 return true;
3589 }
3590 case DDInt:
3591 {
3592 bool ok;
3593 int size = exprVal.toInt( &ok );
3594
3595 if ( ok )
3596 {
3597 dataDefinedValues.insert( p, QVariant( size ) );
3598 return true;
3599 }
3600 return false;
3601 }
3602 case DDIntPos:
3603 {
3604 bool ok;
3605 int size = exprVal.toInt( &ok );
3606
3607 if ( ok && size > 0 )
3608 {
3609 dataDefinedValues.insert( p, QVariant( size ) );
3610 return true;
3611 }
3612 return false;
3613 }
3614 case DDDouble:
3615 {
3616 bool ok;
3617 double size = exprVal.toDouble( &ok );
3618
3619 if ( ok )
3620 {
3621 dataDefinedValues.insert( p, QVariant( size ) );
3622 return true;
3623 }
3624 return false;
3625 }
3626 case DDDoublePos:
3627 {
3628 bool ok;
3629 double size = exprVal.toDouble( &ok );
3630
3631 if ( ok && size > 0.0 )
3632 {
3633 dataDefinedValues.insert( p, QVariant( size ) );
3634 return true;
3635 }
3636 return false;
3637 }
3638 case DDRotation180:
3639 {
3640 bool ok;
3641 double rot = exprVal.toDouble( &ok );
3642 if ( ok )
3643 {
3644 if ( rot < -180.0 && rot >= -360 )
3645 {
3646 rot += 360;
3647 }
3648 if ( rot > 180.0 && rot <= 360 )
3649 {
3650 rot -= 360;
3651 }
3652 if ( rot >= -180 && rot <= 180 )
3653 {
3654 dataDefinedValues.insert( p, QVariant( rot ) );
3655 return true;
3656 }
3657 }
3658 return false;
3659 }
3660 case DDOpacity:
3661 {
3662 bool ok;
3663 int size = exprVal.toInt( &ok );
3664 if ( ok && size >= 0 && size <= 100 )
3665 {
3666 dataDefinedValues.insert( p, QVariant( size ) );
3667 return true;
3668 }
3669 return false;
3670 }
3671 case DDString:
3672 {
3673 QString str = exprVal.toString(); // don't trim whitespace
3674
3675 dataDefinedValues.insert( p, QVariant( str ) ); // let it stay empty if it is
3676 return true;
3677 }
3678 case DDUnits:
3679 {
3680 QString unitstr = exprVal.toString().trimmed();
3681
3682 if ( !unitstr.isEmpty() )
3683 {
3684 dataDefinedValues.insert( p, QVariant( static_cast< int >( QgsUnitTypes::decodeRenderUnit( unitstr ) ) ) );
3685 return true;
3686 }
3687 return false;
3688 }
3689 case DDColor:
3690 {
3691 QString colorstr = exprVal.toString().trimmed();
3692 QColor color = QgsColorUtils::colorFromString( colorstr );
3693
3694 if ( color.isValid() )
3695 {
3696 dataDefinedValues.insert( p, QVariant( color ) );
3697 return true;
3698 }
3699 return false;
3700 }
3701 case DDJoinStyle:
3702 {
3703 QString joinstr = exprVal.toString().trimmed();
3704
3705 if ( !joinstr.isEmpty() )
3706 {
3707 dataDefinedValues.insert( p, QVariant( static_cast< int >( QgsSymbolLayerUtils::decodePenJoinStyle( joinstr ) ) ) );
3708 return true;
3709 }
3710 return false;
3711 }
3712 case DDBlendMode:
3713 {
3714 QString blendstr = exprVal.toString().trimmed();
3715
3716 if ( !blendstr.isEmpty() )
3717 {
3718 dataDefinedValues.insert( p, QVariant( static_cast< int >( QgsSymbolLayerUtils::decodeBlendMode( blendstr ) ) ) );
3719 return true;
3720 }
3721 return false;
3722 }
3723 case DDPointF:
3724 {
3725 bool ok = false;
3726 const QPointF res = QgsSymbolLayerUtils::toPoint( exprVal, &ok );
3727 if ( ok )
3728 {
3729 dataDefinedValues.insert( p, res );
3730 return true;
3731 }
3732 return false;
3733 }
3734 case DDSizeF:
3735 {
3736 bool ok = false;
3737 const QSizeF res = QgsSymbolLayerUtils::toSize( exprVal, &ok );
3738 if ( ok )
3739 {
3740 dataDefinedValues.insert( p, res );
3741 return true;
3742 }
3743 return false;
3744 }
3745 }
3746 }
3747 return false;
3748}
3749
3750void QgsPalLayerSettings::parseTextStyle( QFont &labelFont, Qgis::RenderUnit fontunits, QgsRenderContext &context )
3751{
3752 // NOTE: labelFont already has pixelSize set, so pointSize or pointSizeF might return -1
3753
3754 QVariant exprVal; // value() is repeatedly nulled on data defined evaluation and replaced when successful
3755
3756 // Two ways to generate new data defined font:
3757 // 1) Family + [bold] + [italic] (named style is ignored and font is built off of base family)
3758 // 2) Family + named style (bold or italic is ignored)
3759
3760 // data defined font family?
3761 QString ddFontFamily;
3762 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::Family ) )
3763 {
3764 context.expressionContext().setOriginalValueVariable( labelFont.family() );
3765 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Property::Family, context.expressionContext() );
3766 if ( !QgsVariantUtils::isNull( exprVal ) )
3767 {
3768 QString family = exprVal.toString().trimmed();
3769 QgsDebugMsgLevel( u"exprVal Font family:%1"_s.arg( family ), 4 );
3770
3772 if ( labelFont.family() != family )
3773 {
3774 // testing for ddFontFamily in QFontDatabase.families() may be slow to do for every feature
3775 // (i.e. don't use QgsFontUtils::fontFamilyMatchOnSystem( family ) here)
3776 if ( QgsFontUtils::fontFamilyOnSystem( family ) )
3777 {
3778 ddFontFamily = family;
3779 }
3780 }
3781 }
3782 }
3783
3784 // data defined named font style?
3785 QString ddFontStyle;
3786 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::FontStyle ) )
3787 {
3788 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Property::FontStyle, context.expressionContext() );
3789 if ( !QgsVariantUtils::isNull( exprVal ) )
3790 {
3791 QString fontstyle = exprVal.toString().trimmed();
3792 QgsDebugMsgLevel( u"exprVal Font style:%1"_s.arg( fontstyle ), 4 );
3793 ddFontStyle = fontstyle;
3794 }
3795 }
3796
3797 // data defined bold font style?
3798 bool ddBold = false;
3799 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::Bold ) )
3800 {
3801 context.expressionContext().setOriginalValueVariable( labelFont.bold() );
3802 ddBold = mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::Property::Bold, context.expressionContext(), false );
3803 }
3804
3805 // data defined italic font style?
3806 bool ddItalic = false;
3807 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::Italic ) )
3808 {
3809 context.expressionContext().setOriginalValueVariable( labelFont.italic() );
3810 ddItalic = mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::Property::Italic, context.expressionContext(), false );
3811 }
3812
3813 // TODO: update when pref for how to resolve missing family (use matching algorithm or just default font) is implemented
3814 // (currently defaults to what has been read in from layer settings)
3815 QFont newFont;
3816 QFont appFont = QApplication::font();
3817 bool newFontBuilt = false;
3818 if ( ddBold || ddItalic )
3819 {
3820 // new font needs built, since existing style needs removed
3821 newFont = QgsFontUtils::createFont( !ddFontFamily.isEmpty() ? ddFontFamily : labelFont.family() );
3822 newFontBuilt = true;
3823 newFont.setBold( ddBold );
3824 newFont.setItalic( ddItalic );
3825 }
3826 else if ( !ddFontStyle.isEmpty() && ddFontStyle.compare( "Ignore"_L1, Qt::CaseInsensitive ) != 0 )
3827 {
3828 if ( !ddFontFamily.isEmpty() )
3829 {
3830 // both family and style are different, build font from database
3831 if ( !mFontDB )
3832 mFontDB = std::make_unique< QFontDatabase >();
3833
3834 QFont styledfont = mFontDB->font( ddFontFamily, ddFontStyle, appFont.pointSize() );
3835 if ( appFont != styledfont )
3836 {
3837 newFont = styledfont;
3838 newFontBuilt = true;
3839 }
3840 }
3841
3842 // update the font face style
3843 QgsFontUtils::updateFontViaStyle( newFontBuilt ? newFont : labelFont, ddFontStyle );
3844 }
3845 else if ( !ddFontFamily.isEmpty() )
3846 {
3847 if ( ddFontStyle.compare( "Ignore"_L1, Qt::CaseInsensitive ) != 0 )
3848 {
3849 // just family is different, build font from database
3850 if ( !mFontDB )
3851 mFontDB = std::make_unique< QFontDatabase >();
3852 QFont styledfont = mFontDB->font( ddFontFamily, mFormat.namedStyle(), appFont.pointSize() );
3853 if ( appFont != styledfont )
3854 {
3855 newFont = styledfont;
3856 newFontBuilt = true;
3857 }
3858 }
3859 else
3860 {
3861 newFont = QgsFontUtils::createFont( ddFontFamily );
3862 newFontBuilt = true;
3863 }
3864 }
3865
3866 if ( newFontBuilt )
3867 {
3868 // copy over existing font settings
3869 //newFont = newFont.resolve( labelFont ); // should work, but let's be sure what's being copied
3870 newFont.setPixelSize( labelFont.pixelSize() );
3871 newFont.setUnderline( labelFont.underline() );
3872 newFont.setStrikeOut( labelFont.strikeOut() );
3873 newFont.setWordSpacing( labelFont.wordSpacing() );
3874 newFont.setLetterSpacing( QFont::AbsoluteSpacing, labelFont.letterSpacing() );
3875
3876 labelFont = newFont;
3877 }
3878
3879 // data defined word spacing?
3880 double wordspace = labelFont.wordSpacing();
3881 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::FontWordSpacing ) )
3882 {
3883 context.expressionContext().setOriginalValueVariable( wordspace );
3884 wordspace = mDataDefinedProperties.valueAsDouble( QgsPalLayerSettings::Property::FontWordSpacing, context.expressionContext(), wordspace );
3885 }
3886 labelFont.setWordSpacing( context.convertToPainterUnits( wordspace, fontunits, mFormat.sizeMapUnitScale() ) );
3887
3888 // data defined letter spacing?
3889 double letterspace = labelFont.letterSpacing();
3890 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::FontLetterSpacing ) )
3891 {
3892 context.expressionContext().setOriginalValueVariable( letterspace );
3893 letterspace = mDataDefinedProperties.valueAsDouble( QgsPalLayerSettings::Property::FontLetterSpacing, context.expressionContext(), letterspace );
3894 }
3895 labelFont.setLetterSpacing( QFont::AbsoluteSpacing, context.convertToPainterUnits( letterspace, fontunits, mFormat.sizeMapUnitScale() ) );
3896
3897 // data defined strikeout font style?
3898 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::Strikeout ) )
3899 {
3900 context.expressionContext().setOriginalValueVariable( labelFont.strikeOut() );
3901 bool strikeout = mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::Property::Strikeout, context.expressionContext(), false );
3902 labelFont.setStrikeOut( strikeout );
3903 }
3904
3905 // data defined stretch
3906 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::FontStretchFactor ) )
3907 {
3908 context.expressionContext().setOriginalValueVariable( mFormat.stretchFactor() );
3909 labelFont.setStretch( mDataDefinedProperties.valueAsInt( QgsPalLayerSettings::Property::FontStretchFactor, context.expressionContext(), mFormat.stretchFactor() ) );
3910 }
3911
3912 // data defined underline font style?
3913 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::Underline ) )
3914 {
3915 context.expressionContext().setOriginalValueVariable( labelFont.underline() );
3916 bool underline = mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::Property::Underline, context.expressionContext(), false );
3917 labelFont.setUnderline( underline );
3918 }
3919
3920 // pass the rest on to QgsPalLabeling::drawLabeling
3921
3922 // data defined font color?
3923 dataDefinedValEval( DDColor, QgsPalLayerSettings::Property::Color, exprVal, context.expressionContext(), QgsColorUtils::colorToString( mFormat.color() ) );
3924
3925 // data defined font opacity?
3926 dataDefinedValEval( DDOpacity, QgsPalLayerSettings::Property::FontOpacity, exprVal, context.expressionContext(), mFormat.opacity() * 100 );
3927
3928 // data defined font blend mode?
3929 dataDefinedValEval( DDBlendMode, QgsPalLayerSettings::Property::FontBlendMode, exprVal, context.expressionContext() );
3930}
3931
3932void QgsPalLayerSettings::parseTextBuffer( QgsRenderContext &context )
3933{
3934 QVariant exprVal; // value() is repeatedly nulled on data defined evaluation and replaced when successful
3935
3936 QgsTextBufferSettings buffer = mFormat.buffer();
3937
3938 // data defined draw buffer?
3939 bool drawBuffer = mFormat.buffer().enabled();
3940 if ( dataDefinedValEval( DDBool, QgsPalLayerSettings::Property::BufferDraw, exprVal, context.expressionContext(), buffer.enabled() ) )
3941 {
3942 drawBuffer = exprVal.toBool();
3943 }
3944 else if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::BufferDraw ) && QgsVariantUtils::isNull( exprVal ) )
3945 {
3946 dataDefinedValues.insert( QgsPalLayerSettings::Property::BufferDraw, QVariant( drawBuffer ) );
3947 }
3948
3949 if ( !drawBuffer )
3950 {
3951 return;
3952 }
3953
3954 // data defined buffer size?
3955 double bufrSize = buffer.size();
3956 if ( dataDefinedValEval( DDDoublePos, QgsPalLayerSettings::Property::BufferSize, exprVal, context.expressionContext(), buffer.size() ) )
3957 {
3958 bufrSize = exprVal.toDouble();
3959 }
3960
3961 // data defined buffer transparency?
3962 double bufferOpacity = buffer.opacity() * 100;
3963 if ( dataDefinedValEval( DDOpacity, QgsPalLayerSettings::Property::BufferOpacity, exprVal, context.expressionContext(), bufferOpacity ) )
3964 {
3965 bufferOpacity = exprVal.toDouble();
3966 }
3967
3968 drawBuffer = ( drawBuffer && bufrSize > 0.0 && bufferOpacity > 0 );
3969
3970 if ( !drawBuffer )
3971 {
3972 dataDefinedValues.insert( QgsPalLayerSettings::Property::BufferDraw, QVariant( false ) ); // trigger value
3973 dataDefinedValues.remove( QgsPalLayerSettings::Property::BufferSize );
3974 dataDefinedValues.remove( QgsPalLayerSettings::Property::BufferOpacity );
3975 return; // don't bother evaluating values that won't be used
3976 }
3977
3978 // data defined buffer units?
3979 dataDefinedValEval( DDUnits, QgsPalLayerSettings::Property::BufferUnit, exprVal, context.expressionContext() );
3980
3981 // data defined buffer color?
3982 dataDefinedValEval( DDColor, QgsPalLayerSettings::Property::BufferColor, exprVal, context.expressionContext(), QgsColorUtils::colorToString( buffer.color() ) );
3983
3984 // data defined buffer pen join style?
3985 dataDefinedValEval( DDJoinStyle, QgsPalLayerSettings::Property::BufferJoinStyle, exprVal, context.expressionContext(), QgsSymbolLayerUtils::encodePenJoinStyle( buffer.joinStyle() ) );
3986
3987 // data defined buffer blend mode?
3988 dataDefinedValEval( DDBlendMode, QgsPalLayerSettings::Property::BufferBlendMode, exprVal, context.expressionContext() );
3989}
3990
3991void QgsPalLayerSettings::parseTextMask( QgsRenderContext &context )
3992{
3993 QVariant exprVal; // value() is repeatedly nulled on data defined evaluation and replaced when successful
3994
3995 QgsTextMaskSettings mask = mFormat.mask();
3996
3997 // data defined enabled mask?
3998 bool maskEnabled = mask.enabled();
3999 if ( dataDefinedValEval( DDBool, QgsPalLayerSettings::Property::MaskEnabled, exprVal, context.expressionContext(), mask.enabled() ) )
4000 {
4001 maskEnabled = exprVal.toBool();
4002 }
4003
4004 if ( !maskEnabled )
4005 {
4006 return;
4007 }
4008
4009 // data defined buffer size?
4010 double bufrSize = mask.size();
4011 if ( dataDefinedValEval( DDDoublePos, QgsPalLayerSettings::Property::MaskBufferSize, exprVal, context.expressionContext(), mask.size() ) )
4012 {
4013 bufrSize = exprVal.toDouble();
4014 }
4015
4016 // data defined opacity?
4017 double opacity = mask.opacity() * 100;
4018 if ( dataDefinedValEval( DDOpacity, QgsPalLayerSettings::Property::MaskOpacity, exprVal, context.expressionContext(), opacity ) )
4019 {
4020 opacity = exprVal.toDouble();
4021 }
4022
4023 maskEnabled = ( maskEnabled && bufrSize > 0.0 && opacity > 0 );
4024
4025 if ( !maskEnabled )
4026 {
4027 dataDefinedValues.insert( QgsPalLayerSettings::Property::MaskEnabled, QVariant( false ) ); // trigger value
4028 dataDefinedValues.remove( QgsPalLayerSettings::Property::MaskBufferSize );
4029 dataDefinedValues.remove( QgsPalLayerSettings::Property::MaskOpacity );
4030 return; // don't bother evaluating values that won't be used
4031 }
4032
4033 // data defined buffer units?
4034 dataDefinedValEval( DDUnits, QgsPalLayerSettings::Property::MaskBufferUnit, exprVal, context.expressionContext() );
4035
4036 // data defined buffer pen join style?
4037 dataDefinedValEval( DDJoinStyle, QgsPalLayerSettings::Property::MaskJoinStyle, exprVal, context.expressionContext(), QgsSymbolLayerUtils::encodePenJoinStyle( mask.joinStyle() ) );
4038}
4039
4040void QgsPalLayerSettings::parseTextFormatting( QgsRenderContext &context )
4041{
4042 QVariant exprVal; // value() is repeatedly nulled on data defined evaluation and replaced when successful
4043
4044 // data defined multiline wrap character?
4045 QString wrapchr = wrapChar;
4046 if ( dataDefinedValEval( DDString, QgsPalLayerSettings::Property::MultiLineWrapChar, exprVal, context.expressionContext(), wrapChar ) )
4047 {
4048 wrapchr = exprVal.toString();
4049 }
4050
4051 int evalAutoWrapLength = autoWrapLength;
4052 if ( dataDefinedValEval( DDInt, QgsPalLayerSettings::Property::AutoWrapLength, exprVal, context.expressionContext(), evalAutoWrapLength ) )
4053 {
4054 evalAutoWrapLength = exprVal.toInt();
4055 }
4056
4057 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::TabStopDistance ) )
4058 {
4059 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Property::TabStopDistance, context.expressionContext() );
4060 if ( !QgsVariantUtils::isNull( exprVal ) )
4061 {
4062 dataDefinedValues.insert( QgsPalLayerSettings::Property::TabStopDistance, exprVal );
4063 }
4064 }
4065
4066 // data defined multiline height?
4067 dataDefinedValEval( DDDouble, QgsPalLayerSettings::Property::MultiLineHeight, exprVal, context.expressionContext() );
4068
4069 // data defined multiline text align?
4070 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::MultiLineAlignment ) )
4071 {
4072 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Property::MultiLineAlignment, context.expressionContext() );
4073 if ( !QgsVariantUtils::isNull( exprVal ) )
4074 {
4075 const QString str = exprVal.toString();
4076 QgsDebugMsgLevel( u"exprVal MultiLineAlignment:%1"_s.arg( str ), 4 );
4077
4078 static constexpr auto MULTI_LINE_ALIGNMENT_MAP = makeDataDefinedStringDecoder<Qgis::LabelMultiLineAlignment>(
4084 );
4085
4086 auto evaluatedAlignment = MULTI_LINE_ALIGNMENT_MAP.lookup( str );
4087 if ( evaluatedAlignment )
4088 {
4089 dataDefinedValues.insert( QgsPalLayerSettings::Property::MultiLineAlignment, QVariant( static_cast< int >( *evaluatedAlignment ) ) );
4090 }
4091 else if ( !str.isEmpty() )
4092 {
4093 // for compatibility with old logic, non-matching strings get treated as "Left"
4095 dataDefinedValues.insert( QgsPalLayerSettings::Property::MultiLineAlignment, QVariant( static_cast< int >( aligntype ) ) );
4096 }
4097 }
4098 }
4099
4100 // text orientation
4101 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::TextOrientation ) )
4102 {
4103 const QString encoded = QgsTextRendererUtils::encodeTextOrientation( mFormat.orientation() );
4104 context.expressionContext().setOriginalValueVariable( encoded );
4105 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Property::TextOrientation, context.expressionContext() );
4106 if ( !QgsVariantUtils::isNull( exprVal ) )
4107 {
4108 QString str = exprVal.toString().trimmed();
4109 if ( !str.isEmpty() )
4110 dataDefinedValues.insert( QgsPalLayerSettings::Property::TextOrientation, str );
4111 }
4112 }
4113
4114 // data defined direction symbol?
4115 bool drawDirSymb = mLineSettings.addDirectionSymbol();
4116 if ( dataDefinedValEval( DDBool, QgsPalLayerSettings::Property::DirSymbDraw, exprVal, context.expressionContext(), drawDirSymb ) )
4117 {
4118 drawDirSymb = exprVal.toBool();
4119 }
4120
4121 if ( drawDirSymb )
4122 {
4123 // data defined direction left symbol?
4124 dataDefinedValEval( DDString, QgsPalLayerSettings::Property::DirSymbLeft, exprVal, context.expressionContext(), mLineSettings.leftDirectionSymbol() );
4125
4126 // data defined direction right symbol?
4127 dataDefinedValEval( DDString, QgsPalLayerSettings::Property::DirSymbRight, exprVal, context.expressionContext(), mLineSettings.rightDirectionSymbol() );
4128
4129 // data defined direction symbol placement?
4130 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Property::DirSymbPlacement, context.expressionContext() );
4131 if ( !QgsVariantUtils::isNull( exprVal ) )
4132 {
4133 const QString str = exprVal.toString();
4134
4139 );
4140
4141 auto evaluatedDirection = DIRECTION_MAP.lookup( str );
4142 if ( evaluatedDirection )
4143 {
4144 dataDefinedValues.insert( QgsPalLayerSettings::Property::DirSymbPlacement, QVariant( static_cast< int >( *evaluatedDirection ) ) );
4145 }
4146 else if ( !str.trimmed().isEmpty() )
4147 {
4148 // for compatibility with old logic non-empty strings fallback to LeftRight mode
4150 dataDefinedValues.insert( QgsPalLayerSettings::Property::DirSymbPlacement, QVariant( static_cast< int >( placetype ) ) );
4151 }
4152 }
4153
4154 // data defined direction symbol reversed?
4155 dataDefinedValEval( DDBool, QgsPalLayerSettings::Property::DirSymbReverse, exprVal, context.expressionContext(), mLineSettings.reverseDirectionSymbol() );
4156 }
4157
4158 // formatting for numbers is inline with generation of base label text and not passed to label painting
4159}
4160
4161void QgsPalLayerSettings::parseShapeBackground( QgsRenderContext &context )
4162{
4163 QVariant exprVal; // value() is repeatedly nulled on data defined evaluation and replaced when successful
4164
4165 QgsTextBackgroundSettings background = mFormat.background();
4166
4167 // data defined draw shape?
4168 bool drawShape = background.enabled();
4169 if ( dataDefinedValEval( DDBool, QgsPalLayerSettings::Property::ShapeDraw, exprVal, context.expressionContext(), drawShape ) )
4170 {
4171 drawShape = exprVal.toBool();
4172 }
4173
4174 if ( !drawShape )
4175 {
4176 return;
4177 }
4178
4179 // data defined shape transparency?
4180 double shapeOpacity = background.opacity() * 100;
4181 if ( dataDefinedValEval( DDOpacity, QgsPalLayerSettings::Property::ShapeOpacity, exprVal, context.expressionContext(), shapeOpacity ) )
4182 {
4183 shapeOpacity = 100.0 * exprVal.toDouble();
4184 }
4185
4186 drawShape = ( drawShape && shapeOpacity > 0 ); // size is not taken into account (could be)
4187
4188 if ( !drawShape )
4189 {
4190 dataDefinedValues.insert( QgsPalLayerSettings::Property::ShapeDraw, QVariant( false ) ); // trigger value
4191 dataDefinedValues.remove( QgsPalLayerSettings::Property::ShapeOpacity );
4192 return; // don't bother evaluating values that won't be used
4193 }
4194
4195 // data defined shape kind?
4196 QgsTextBackgroundSettings::ShapeType shapeKind = background.type();
4197 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::ShapeKind ) )
4198 {
4199 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Property::ShapeKind, context.expressionContext() );
4200 if ( !QgsVariantUtils::isNull( exprVal ) )
4201 {
4202 QString skind = exprVal.toString().trimmed();
4203 QgsDebugMsgLevel( u"exprVal ShapeKind:%1"_s.arg( skind ), 4 );
4204
4205 if ( !skind.isEmpty() )
4206 {
4207 shapeKind = QgsTextRendererUtils::decodeShapeType( skind );
4208 dataDefinedValues.insert( QgsPalLayerSettings::Property::ShapeKind, QVariant( static_cast< int >( shapeKind ) ) );
4209 }
4210 }
4211 }
4212
4213 // data defined shape SVG path?
4214 QString svgPath = background.svgFile();
4215 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::ShapeSVGFile ) )
4216 {
4217 context.expressionContext().setOriginalValueVariable( svgPath );
4218 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Property::ShapeSVGFile, context.expressionContext() );
4219 if ( !QgsVariantUtils::isNull( exprVal ) )
4220 {
4221 QString svgfile = exprVal.toString().trimmed();
4222 QgsDebugMsgLevel( u"exprVal ShapeSVGFile:%1"_s.arg( svgfile ), 4 );
4223
4224 // '' empty paths are allowed
4225 svgPath = QgsSymbolLayerUtils::svgSymbolNameToPath( svgfile, context.pathResolver() );
4226 dataDefinedValues.insert( QgsPalLayerSettings::Property::ShapeSVGFile, QVariant( svgPath ) );
4227 }
4228 }
4229
4230 // data defined shape size type?
4231 QgsTextBackgroundSettings::SizeType shpSizeType = background.sizeType();
4232 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::ShapeSizeType ) )
4233 {
4234 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Property::ShapeSizeType, context.expressionContext() );
4235 if ( !QgsVariantUtils::isNull( exprVal ) )
4236 {
4237 QString stype = exprVal.toString().trimmed();
4238 QgsDebugMsgLevel( u"exprVal ShapeSizeType:%1"_s.arg( stype ), 4 );
4239
4240 if ( !stype.isEmpty() )
4241 {
4243 dataDefinedValues.insert( QgsPalLayerSettings::Property::ShapeSizeType, QVariant( static_cast< int >( shpSizeType ) ) );
4244 }
4245 }
4246 }
4247
4248 // data defined shape size X? (SVGs only use X for sizing)
4249 double ddShpSizeX = background.size().width();
4250 if ( dataDefinedValEval( DDDouble, QgsPalLayerSettings::Property::ShapeSizeX, exprVal, context.expressionContext(), ddShpSizeX ) )
4251 {
4252 ddShpSizeX = exprVal.toDouble();
4253 }
4254
4255 // data defined shape size Y?
4256 double ddShpSizeY = background.size().height();
4257 if ( dataDefinedValEval( DDDouble, QgsPalLayerSettings::Property::ShapeSizeY, exprVal, context.expressionContext(), ddShpSizeY ) )
4258 {
4259 ddShpSizeY = exprVal.toDouble();
4260 }
4261
4262 // don't continue under certain circumstances (e.g. size is fixed)
4263 bool skip = false;
4264 if ( shapeKind == QgsTextBackgroundSettings::ShapeSVG && ( svgPath.isEmpty() || ( !svgPath.isEmpty() && shpSizeType == QgsTextBackgroundSettings::SizeFixed && ddShpSizeX == 0.0 ) ) )
4265 {
4266 skip = true;
4267 }
4269 && ( !background.markerSymbol() || ( background.markerSymbol() && shpSizeType == QgsTextBackgroundSettings::SizeFixed && ddShpSizeX == 0.0 ) ) )
4270 {
4271 skip = true;
4272 }
4273 if ( shapeKind != QgsTextBackgroundSettings::ShapeSVG
4275 && shpSizeType == QgsTextBackgroundSettings::SizeFixed
4276 && ( ddShpSizeX == 0.0 || ddShpSizeY == 0.0 ) )
4277 {
4278 skip = true;
4279 }
4280
4281 if ( skip )
4282 {
4283 dataDefinedValues.insert( QgsPalLayerSettings::Property::ShapeDraw, QVariant( false ) ); // trigger value
4284 dataDefinedValues.remove( QgsPalLayerSettings::Property::ShapeOpacity );
4285 dataDefinedValues.remove( QgsPalLayerSettings::Property::ShapeKind );
4286 dataDefinedValues.remove( QgsPalLayerSettings::Property::ShapeSVGFile );
4287 dataDefinedValues.remove( QgsPalLayerSettings::Property::ShapeSizeX );
4288 dataDefinedValues.remove( QgsPalLayerSettings::Property::ShapeSizeY );
4289 return; // don't bother evaluating values that won't be used
4290 }
4291
4292 // data defined shape size units?
4293 dataDefinedValEval( DDUnits, QgsPalLayerSettings::Property::ShapeSizeUnits, exprVal, context.expressionContext() );
4294
4295 // data defined shape rotation type?
4296 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::ShapeRotationType ) )
4297 {
4298 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Property::ShapeRotationType, context.expressionContext() );
4299 if ( !QgsVariantUtils::isNull( exprVal ) )
4300 {
4301 QString rotstr = exprVal.toString().trimmed();
4302 QgsDebugMsgLevel( u"exprVal ShapeRotationType:%1"_s.arg( rotstr ), 4 );
4303
4304 if ( !rotstr.isEmpty() )
4305 {
4306 // "Sync"
4308 dataDefinedValues.insert( QgsPalLayerSettings::Property::ShapeRotationType, QVariant( static_cast< int >( rottype ) ) );
4309 }
4310 }
4311 }
4312
4313 // data defined shape rotation?
4314 dataDefinedValEval( DDRotation180, QgsPalLayerSettings::Property::ShapeRotation, exprVal, context.expressionContext(), background.rotation() );
4315
4316 // data defined shape offset?
4317 dataDefinedValEval( DDPointF, QgsPalLayerSettings::Property::ShapeOffset, exprVal, context.expressionContext(), QgsSymbolLayerUtils::encodePoint( background.offset() ) );
4318
4319 // data defined shape offset units?
4320 dataDefinedValEval( DDUnits, QgsPalLayerSettings::Property::ShapeOffsetUnits, exprVal, context.expressionContext() );
4321
4322 // data defined shape radii?
4323 dataDefinedValEval( DDSizeF, QgsPalLayerSettings::Property::ShapeRadii, exprVal, context.expressionContext(), QgsSymbolLayerUtils::encodeSize( background.radii() ) );
4324
4325 // data defined shape radii units?
4326 dataDefinedValEval( DDUnits, QgsPalLayerSettings::Property::ShapeRadiiUnits, exprVal, context.expressionContext() );
4327
4328 // data defined shape blend mode?
4329 dataDefinedValEval( DDBlendMode, QgsPalLayerSettings::Property::ShapeBlendMode, exprVal, context.expressionContext() );
4330
4331 // data defined shape fill color?
4332 dataDefinedValEval( DDColor, QgsPalLayerSettings::Property::ShapeFillColor, exprVal, context.expressionContext(), QgsColorUtils::colorToString( background.fillColor() ) );
4333
4334 // data defined shape stroke color?
4335 dataDefinedValEval( DDColor, QgsPalLayerSettings::Property::ShapeStrokeColor, exprVal, context.expressionContext(), QgsColorUtils::colorToString( background.strokeColor() ) );
4336
4337 // data defined shape stroke width?
4338 dataDefinedValEval( DDDoublePos, QgsPalLayerSettings::Property::ShapeStrokeWidth, exprVal, context.expressionContext(), background.strokeWidth() );
4339
4340 // data defined shape stroke width units?
4341 dataDefinedValEval( DDUnits, QgsPalLayerSettings::Property::ShapeStrokeWidthUnits, exprVal, context.expressionContext() );
4342
4343 // data defined shape join style?
4344 dataDefinedValEval( DDJoinStyle, QgsPalLayerSettings::Property::ShapeJoinStyle, exprVal, context.expressionContext(), QgsSymbolLayerUtils::encodePenJoinStyle( background.joinStyle() ) );
4345}
4346
4347void QgsPalLayerSettings::parseDropShadow( QgsRenderContext &context )
4348{
4349 QVariant exprVal; // value() is repeatedly nulled on data defined evaluation and replaced when successful
4350
4351 QgsTextShadowSettings shadow = mFormat.shadow();
4352
4353 // data defined draw shadow?
4354 bool drawShadow = shadow.enabled();
4355 if ( dataDefinedValEval( DDBool, QgsPalLayerSettings::Property::ShadowDraw, exprVal, context.expressionContext(), drawShadow ) )
4356 {
4357 drawShadow = exprVal.toBool();
4358 }
4359
4360 if ( !drawShadow )
4361 {
4362 return;
4363 }
4364
4365 // data defined shadow transparency?
4366 double shadowOpacity = shadow.opacity() * 100;
4367 if ( dataDefinedValEval( DDOpacity, QgsPalLayerSettings::Property::ShadowOpacity, exprVal, context.expressionContext(), shadowOpacity ) )
4368 {
4369 shadowOpacity = exprVal.toDouble();
4370 }
4371
4372 // data defined shadow offset distance?
4373 double shadowOffDist = shadow.offsetDistance();
4374 if ( dataDefinedValEval( DDDoublePos, QgsPalLayerSettings::Property::ShadowOffsetDist, exprVal, context.expressionContext(), shadowOffDist ) )
4375 {
4376 shadowOffDist = exprVal.toDouble();
4377 }
4378
4379 // data defined shadow offset distance?
4380 double shadowRad = shadow.blurRadius();
4381 if ( dataDefinedValEval( DDDoublePos, QgsPalLayerSettings::Property::ShadowRadius, exprVal, context.expressionContext(), shadowRad ) )
4382 {
4383 shadowRad = exprVal.toDouble();
4384 }
4385
4386 drawShadow = ( drawShadow && shadowOpacity > 0 && !( shadowOffDist == 0.0 && shadowRad == 0.0 ) );
4387
4388 if ( !drawShadow )
4389 {
4390 dataDefinedValues.insert( QgsPalLayerSettings::Property::ShadowDraw, QVariant( false ) ); // trigger value
4391 dataDefinedValues.remove( QgsPalLayerSettings::Property::ShadowOpacity );
4392 dataDefinedValues.remove( QgsPalLayerSettings::Property::ShadowOffsetDist );
4393 dataDefinedValues.remove( QgsPalLayerSettings::Property::ShadowRadius );
4394 return; // don't bother evaluating values that won't be used
4395 }
4396
4397 // data defined shadow under type?
4398 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::ShadowUnder ) )
4399 {
4400 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Property::ShadowUnder, context.expressionContext() );
4401 if ( !QgsVariantUtils::isNull( exprVal ) )
4402 {
4403 QString str = exprVal.toString().trimmed();
4404 QgsDebugMsgLevel( u"exprVal ShadowUnder:%1"_s.arg( str ), 4 );
4405
4406 if ( !str.isEmpty() )
4407 {
4409 dataDefinedValues.insert( QgsPalLayerSettings::Property::ShadowUnder, QVariant( static_cast< int >( shdwtype ) ) );
4410 }
4411 }
4412 }
4413
4414 // data defined shadow offset angle?
4415 dataDefinedValEval( DDRotation180, QgsPalLayerSettings::Property::ShadowOffsetAngle, exprVal, context.expressionContext(), shadow.offsetAngle() );
4416
4417 // data defined shadow offset units?
4418 dataDefinedValEval( DDUnits, QgsPalLayerSettings::Property::ShadowOffsetUnits, exprVal, context.expressionContext() );
4419
4420 // data defined shadow radius?
4421 dataDefinedValEval( DDDouble, QgsPalLayerSettings::Property::ShadowRadius, exprVal, context.expressionContext(), shadow.blurRadius() );
4422
4423 // data defined shadow radius units?
4424 dataDefinedValEval( DDUnits, QgsPalLayerSettings::Property::ShadowRadiusUnits, exprVal, context.expressionContext() );
4425
4426 // data defined shadow scale? ( gui bounds to 0-2000, no upper bound here )
4427 dataDefinedValEval( DDIntPos, QgsPalLayerSettings::Property::ShadowScale, exprVal, context.expressionContext(), shadow.scale() );
4428
4429 // data defined shadow color?
4430 dataDefinedValEval( DDColor, QgsPalLayerSettings::Property::ShadowColor, exprVal, context.expressionContext(), QgsColorUtils::colorToString( shadow.color() ) );
4431
4432 // data defined shadow blend mode?
4433 dataDefinedValEval( DDBlendMode, QgsPalLayerSettings::Property::ShadowBlendMode, exprVal, context.expressionContext() );
4434}
4435
4436// -------------
4437
4438
4440{
4441 switch ( layer->type() )
4442 {
4444 {
4445 const QgsVectorLayer *vl = qobject_cast< const QgsVectorLayer * >( layer );
4446 return ( vl->labelsEnabled() && vl->labeling() )
4447 || ( vl->diagramsEnabled() && vl->diagramRenderer() )
4448 || ( vl->renderer() && vl->renderer()->flags().testFlag( Qgis::FeatureRendererFlag::AffectsLabeling ) );
4449 }
4450
4452 {
4453 const QgsVectorTileLayer *vl = qobject_cast< const QgsVectorTileLayer * >( layer );
4454 if ( !vl->labeling() )
4455 return false;
4456
4457 if ( const QgsVectorTileBasicLabeling *labeling = dynamic_cast< const QgsVectorTileBasicLabeling *>( vl->labeling() ) )
4458 return !labeling->styles().empty();
4459
4460 return false;
4461 }
4462
4464 {
4465 const QgsMeshLayer *ml = qobject_cast< const QgsMeshLayer * >( layer );
4466 return ml->labeling() && ml->labelsEnabled();
4467 }
4468
4470 {
4471 const QgsRasterLayer *rl = qobject_cast< const QgsRasterLayer * >( layer );
4472 return rl->labeling() && rl->labelsEnabled();
4473 }
4474
4480 return false;
4481 }
4482 return false;
4483}
4484
4485
4486bool QgsPalLabeling::geometryRequiresPreparation( const QgsGeometry &geometry, QgsRenderContext &context, const QgsCoordinateTransform &ct, const QgsGeometry &clipGeometry, bool mergeLines )
4487{
4488 if ( geometry.isNull() )
4489 {
4490 return false;
4491 }
4492
4493 if ( geometry.type() == Qgis::GeometryType::Line && geometry.isMultipart() && mergeLines )
4494 {
4495 return true;
4496 }
4497
4498 //requires reprojection
4499 if ( ct.isValid() && !ct.isShortCircuited() )
4500 return true;
4501
4502 //requires rotation
4503 const QgsMapToPixel &m2p = context.mapToPixel();
4504 if ( !qgsDoubleNear( m2p.mapRotation(), 0 ) )
4505 return true;
4506
4507 //requires clip
4508 if ( !clipGeometry.isNull() && !clipGeometry.boundingBox().contains( geometry.boundingBox() ) )
4509 return true;
4510
4511 //requires fixing
4512 if ( geometry.type() == Qgis::GeometryType::Polygon && !geometry.isGeosValid() )
4513 return true;
4514
4515 return false;
4516}
4517
4518QStringList QgsPalLabeling::splitToLines( const QString &text, const QString &wrapCharacter, const int autoWrapLength, const bool useMaxLineLengthWhenAutoWrapping )
4519{
4520 QStringList multiLineSplit;
4521 if ( !wrapCharacter.isEmpty() && wrapCharacter != "\n"_L1 )
4522 {
4523 //wrap on both the wrapchr and new line characters
4524 const QStringList lines = text.split( wrapCharacter );
4525 for ( const QString &line : lines )
4526 {
4527 multiLineSplit.append( line.split( '\n' ) );
4528 }
4529 }
4530 else
4531 {
4532 multiLineSplit = text.split( '\n' );
4533 }
4534
4535 // apply auto wrapping to each manually created line
4536 if ( autoWrapLength != 0 )
4537 {
4538 QStringList autoWrappedLines;
4539 autoWrappedLines.reserve( multiLineSplit.count() );
4540 for ( const QString &line : std::as_const( multiLineSplit ) )
4541 {
4542 autoWrappedLines.append( QgsStringUtils::wordWrap( line, autoWrapLength, useMaxLineLengthWhenAutoWrapping ).split( '\n' ) );
4543 }
4544 multiLineSplit = autoWrappedLines;
4545 }
4546 return multiLineSplit;
4547}
4548
4549QStringList QgsPalLabeling::splitToGraphemes( const QString &text )
4550{
4551 QStringList graphemes;
4552 QTextBoundaryFinder boundaryFinder( QTextBoundaryFinder::Grapheme, text );
4553 int currentBoundary = -1;
4554 int previousBoundary = 0;
4555 while ( ( currentBoundary = boundaryFinder.toNextBoundary() ) > 0 )
4556 {
4557 graphemes << text.mid( previousBoundary, currentBoundary - previousBoundary );
4558 previousBoundary = currentBoundary;
4559 }
4560 return graphemes;
4561}
4562
4563QgsGeometry QgsPalLabeling::prepareGeometry( const QgsGeometry &geometry, QgsRenderContext &context, const QgsCoordinateTransform &ct, const QgsGeometry &clipGeometry, bool mergeLines )
4564{
4565 if ( geometry.isNull() )
4566 {
4567 return QgsGeometry();
4568 }
4569
4570 //don't modify the feature's geometry so that geometry based expressions keep working
4571 QgsGeometry geom = geometry;
4572
4573 if ( geom.type() == Qgis::GeometryType::Line && geom.isMultipart() && mergeLines )
4574 {
4575 geom = geom.mergeLines();
4576 }
4577
4578 //reproject the geometry if necessary
4579 if ( ct.isValid() && !ct.isShortCircuited() )
4580 {
4581 try
4582 {
4583 geom.transform( ct );
4584 }
4585 catch ( QgsCsException &cse )
4586 {
4587 Q_UNUSED( cse )
4588 QgsDebugMsgLevel( u"Ignoring feature due to transformation exception"_s, 4 );
4589 return QgsGeometry();
4590 }
4591 // geometry transforms may result in nan points, remove these
4592 geom.filterVertices( []( const QgsPoint &point ) -> bool { return std::isfinite( point.x() ) && std::isfinite( point.y() ); } );
4594 {
4595 cp->removeInvalidRings();
4596 }
4598 {
4599 for ( int i = 0; i < ms->numGeometries(); ++i )
4600 {
4601 if ( QgsCurvePolygon *cp = qgsgeometry_cast< QgsCurvePolygon * >( ms->geometryN( i ) ) )
4602 cp->removeInvalidRings();
4603 }
4604 }
4605 }
4606
4607 // Rotate the geometry if needed
4608 const QgsMapToPixel &m2p = context.mapToPixel();
4609 if ( !qgsDoubleNear( m2p.mapRotation(), 0 ) )
4610 {
4611 QgsPointXY center = context.mapExtent().center();
4612 if ( geom.rotate( m2p.mapRotation(), center ) != Qgis::GeometryOperationResult::Success )
4613 {
4614 QgsDebugError( u"Error rotating geometry"_s.arg( geom.asWkt() ) );
4615 return QgsGeometry();
4616 }
4617 }
4618
4619 const bool mustClip
4620 = ( !clipGeometry.isNull() && ( ( qgsDoubleNear( m2p.mapRotation(), 0 ) && !clipGeometry.boundingBox().contains( geom.boundingBox() ) ) || ( !qgsDoubleNear( m2p.mapRotation(), 0 ) && !clipGeometry.contains( geom ) ) ) );
4621
4622 bool mustClipExact = false;
4623 if ( mustClip )
4624 {
4625 // nice and fast, but can result in invalid geometries. At least it will potentially strip out a bunch of unwanted vertices upfront!
4626 QgsGeometry clipGeom = geom.clipped( clipGeometry.boundingBox(), context.feedback() );
4627 if ( clipGeom.isEmpty() )
4628 return QgsGeometry();
4629
4630 geom = clipGeom;
4631
4632 // we've now clipped against the BOUNDING BOX of clipGeometry. But if clipGeometry is an axis parallel rectangle, then there's no
4633 // need to do an exact (potentially costly) intersection clip as well!
4634 mustClipExact = !clipGeometry.isAxisParallelRectangle( 0.001 );
4635 }
4636
4637 // fix invalid polygons
4638 if ( geom.type() == Qgis::GeometryType::Polygon )
4639 {
4640 if ( geom.isMultipart() )
4641 {
4642 // important -- we need to treat ever part in isolation here. We can't test the validity of the whole geometry
4643 // at once, because touching parts would result in an invalid geometry, and buffering this "dissolves" the parts.
4644 // because the actual label engine treats parts as separate entities, we aren't bound by the usual "touching parts are invalid" rule
4645 // see https://github.com/qgis/QGIS/issues/26763
4646 QVector< QgsGeometry> parts;
4647 parts.reserve( qgsgeometry_cast< const QgsGeometryCollection * >( geom.constGet() )->numGeometries() );
4648 for ( auto it = geom.const_parts_begin(); it != geom.const_parts_end(); ++it )
4649 {
4650 QgsGeometry partGeom( ( *it )->clone() );
4651 if ( !partGeom.isGeosValid() )
4652 {
4653 partGeom = partGeom.makeValid( Qgis::MakeValidMethod::Linework, false, context.feedback() );
4654 }
4655 parts.append( partGeom );
4656 }
4657 geom = QgsGeometry::collectGeometry( parts );
4658 }
4659 else if ( !geom.isGeosValid() )
4660 {
4661 QgsGeometry bufferGeom = geom.makeValid( Qgis::MakeValidMethod::Linework, false, context.feedback() );
4662 if ( bufferGeom.isNull() )
4663 {
4664 QgsDebugError( u"Could not repair geometry: %1"_s.arg( bufferGeom.lastError() ) );
4665 return QgsGeometry();
4666 }
4667 geom = bufferGeom;
4668 }
4669 }
4670
4671 if ( mustClipExact )
4672 {
4673 // now do the real intersection against the actual clip geometry
4674 QgsGeometry clipGeom = geom.intersection( clipGeometry, QgsGeometryParameters(), context.feedback() );
4675 if ( clipGeom.isEmpty() )
4676 {
4677 return QgsGeometry();
4678 }
4679 geom = clipGeom;
4680 }
4681
4682 return geom;
4683}
4684
4685bool QgsPalLabeling::checkMinimumSizeMM( const QgsRenderContext &context, const QgsGeometry &geom, double minSize )
4686{
4687 if ( minSize <= 0 )
4688 {
4689 return true;
4690 }
4691
4692 if ( geom.isNull() )
4693 {
4694 return false;
4695 }
4696
4697 Qgis::GeometryType featureType = geom.type();
4698 if ( featureType == Qgis::GeometryType::Point ) //minimum size does not apply to point features
4699 {
4700 return true;
4701 }
4702
4703 double mapUnitsPerMM = context.mapToPixel().mapUnitsPerPixel() * context.scaleFactor();
4704 if ( featureType == Qgis::GeometryType::Line )
4705 {
4706 double length = geom.length();
4707 if ( length >= 0.0 )
4708 {
4709 return ( length >= ( minSize * mapUnitsPerMM ) );
4710 }
4711 }
4712 else if ( featureType == Qgis::GeometryType::Polygon )
4713 {
4714 double area = geom.area();
4715 if ( area >= 0.0 )
4716 {
4717 return ( std::sqrt( area ) >= ( minSize * mapUnitsPerMM ) );
4718 }
4719 }
4720 return true; //should never be reached. Return true in this case to label such geometries anyway.
4721}
4722
4723
4724void QgsPalLabeling::dataDefinedTextStyle( QgsPalLayerSettings &tmpLyr, const QMap< QgsPalLayerSettings::Property, QVariant > &ddValues )
4725{
4726 QgsTextFormat format = tmpLyr.format();
4727 bool changed = false;
4728
4729 //font color
4730 if ( ddValues.contains( QgsPalLayerSettings::Property::Color ) )
4731 {
4732 QVariant ddColor = ddValues.value( QgsPalLayerSettings::Property::Color );
4733 format.setColor( ddColor.value<QColor>() );
4734 changed = true;
4735 }
4736
4737 //font transparency
4738 if ( ddValues.contains( QgsPalLayerSettings::Property::FontOpacity ) )
4739 {
4740 format.setOpacity( ddValues.value( QgsPalLayerSettings::Property::FontOpacity ).toDouble() / 100.0 );
4741 changed = true;
4742 }
4743
4744 //font blend mode
4745 if ( ddValues.contains( QgsPalLayerSettings::Property::FontBlendMode ) )
4746 {
4747 format.setBlendMode( static_cast< QPainter::CompositionMode >( ddValues.value( QgsPalLayerSettings::Property::FontBlendMode ).toInt() ) );
4748 changed = true;
4749 }
4750
4751 if ( changed )
4752 {
4753 tmpLyr.setFormat( format );
4754 }
4755}
4756
4757void QgsPalLabeling::dataDefinedTextFormatting( QgsPalLayerSettings &tmpLyr, const QMap< QgsPalLayerSettings::Property, QVariant > &ddValues )
4758{
4759 if ( ddValues.contains( QgsPalLayerSettings::Property::MultiLineWrapChar ) )
4760 {
4761 tmpLyr.wrapChar = ddValues.value( QgsPalLayerSettings::Property::MultiLineWrapChar ).toString();
4762 }
4763
4764 if ( ddValues.contains( QgsPalLayerSettings::Property::AutoWrapLength ) )
4765 {
4766 tmpLyr.autoWrapLength = ddValues.value( QgsPalLayerSettings::Property::AutoWrapLength ).toInt();
4767 }
4768
4769 if ( ddValues.contains( QgsPalLayerSettings::Property::MultiLineHeight ) )
4770 {
4771 QgsTextFormat format = tmpLyr.format();
4772 format.setLineHeight( ddValues.value( QgsPalLayerSettings::Property::MultiLineHeight ).toDouble() );
4773 tmpLyr.setFormat( format );
4774 }
4775
4776 if ( ddValues.contains( QgsPalLayerSettings::Property::TabStopDistance ) )
4777 {
4778 QgsTextFormat format = tmpLyr.format();
4779 QList<QgsTextFormat::Tab> tabPositions;
4780 if ( ddValues.value( QgsPalLayerSettings::Property::TabStopDistance ).userType() == QMetaType::Type::QVariantList )
4781 {
4782 const QVariantList parts = ddValues.value( QgsPalLayerSettings::Property::TabStopDistance ).toList();
4783 for ( const QVariant &part : parts )
4784 {
4785 tabPositions.append( QgsTextFormat::Tab( part.toDouble() ) );
4786 }
4787 format.setTabPositions( tabPositions );
4788 }
4789 else if ( ddValues.value( QgsPalLayerSettings::Property::TabStopDistance ).userType() == QMetaType::Type::QStringList )
4790 {
4791 const QStringList parts = ddValues.value( QgsPalLayerSettings::Property::TabStopDistance ).toStringList();
4792 for ( const QString &part : parts )
4793 {
4794 tabPositions.append( QgsTextFormat::Tab( part.toDouble() ) );
4795 }
4796 format.setTabPositions( tabPositions );
4797 }
4798 else
4799 {
4800 format.setTabPositions( tabPositions );
4801 format.setTabStopDistance( ddValues.value( QgsPalLayerSettings::Property::TabStopDistance ).toDouble() );
4802 }
4803 tmpLyr.setFormat( format );
4804 }
4805
4806 if ( ddValues.contains( QgsPalLayerSettings::Property::MultiLineAlignment ) )
4807 {
4808 tmpLyr.multilineAlign = static_cast< Qgis::LabelMultiLineAlignment >( ddValues.value( QgsPalLayerSettings::Property::MultiLineAlignment ).toInt() );
4809 }
4810
4811 if ( ddValues.contains( QgsPalLayerSettings::Property::TextOrientation ) )
4812 {
4813 QgsTextFormat format = tmpLyr.format();
4815 tmpLyr.setFormat( format );
4816 }
4817
4818 if ( ddValues.contains( QgsPalLayerSettings::Property::DirSymbDraw ) )
4819 {
4821 }
4822
4823 if ( tmpLyr.lineSettings().addDirectionSymbol() )
4824 {
4825 if ( ddValues.contains( QgsPalLayerSettings::Property::DirSymbLeft ) )
4826 {
4828 }
4829 if ( ddValues.contains( QgsPalLayerSettings::Property::DirSymbRight ) )
4830 {
4832 }
4833
4834 if ( ddValues.contains( QgsPalLayerSettings::Property::DirSymbPlacement ) )
4835 {
4837 }
4838
4839 if ( ddValues.contains( QgsPalLayerSettings::Property::DirSymbReverse ) )
4840 {
4842 }
4843 }
4844}
4845
4846void QgsPalLabeling::dataDefinedTextBuffer( QgsPalLayerSettings &tmpLyr, const QMap< QgsPalLayerSettings::Property, QVariant > &ddValues )
4847{
4848 QgsTextBufferSettings buffer = tmpLyr.format().buffer();
4849 bool changed = false;
4850
4851 //buffer draw
4852 if ( ddValues.contains( QgsPalLayerSettings::Property::BufferDraw ) )
4853 {
4854 buffer.setEnabled( ddValues.value( QgsPalLayerSettings::Property::BufferDraw ).toBool() );
4855 changed = true;
4856 }
4857
4858 if ( !buffer.enabled() )
4859 {
4860 if ( changed )
4861 {
4862 QgsTextFormat format = tmpLyr.format();
4863 format.setBuffer( buffer );
4864 tmpLyr.setFormat( format );
4865 }
4866
4867 // tmpLyr.bufferSize > 0.0 && tmpLyr.bufferTransp < 100 figured in during evaluation
4868 return; // don't continue looking for unused values
4869 }
4870
4871 //buffer size
4872 if ( ddValues.contains( QgsPalLayerSettings::Property::BufferSize ) )
4873 {
4874 buffer.setSize( ddValues.value( QgsPalLayerSettings::Property::BufferSize ).toDouble() );
4875 changed = true;
4876 }
4877
4878 //buffer opacity
4879 if ( ddValues.contains( QgsPalLayerSettings::Property::BufferOpacity ) )
4880 {
4881 buffer.setOpacity( ddValues.value( QgsPalLayerSettings::Property::BufferOpacity ).toDouble() / 100.0 );
4882 changed = true;
4883 }
4884
4885 //buffer size units
4886 if ( ddValues.contains( QgsPalLayerSettings::Property::BufferUnit ) )
4887 {
4888 Qgis::RenderUnit bufunit = static_cast< Qgis::RenderUnit >( ddValues.value( QgsPalLayerSettings::Property::BufferUnit ).toInt() );
4889 buffer.setSizeUnit( bufunit );
4890 changed = true;
4891 }
4892
4893 //buffer color
4894 if ( ddValues.contains( QgsPalLayerSettings::Property::BufferColor ) )
4895 {
4896 QVariant ddColor = ddValues.value( QgsPalLayerSettings::Property::BufferColor );
4897 buffer.setColor( ddColor.value<QColor>() );
4898 changed = true;
4899 }
4900
4901 //buffer pen join style
4902 if ( ddValues.contains( QgsPalLayerSettings::Property::BufferJoinStyle ) )
4903 {
4904 buffer.setJoinStyle( static_cast< Qt::PenJoinStyle >( ddValues.value( QgsPalLayerSettings::Property::BufferJoinStyle ).toInt() ) );
4905 changed = true;
4906 }
4907
4908 //buffer blend mode
4909 if ( ddValues.contains( QgsPalLayerSettings::Property::BufferBlendMode ) )
4910 {
4911 buffer.setBlendMode( static_cast< QPainter::CompositionMode >( ddValues.value( QgsPalLayerSettings::Property::BufferBlendMode ).toInt() ) );
4912 changed = true;
4913 }
4914
4915 if ( changed )
4916 {
4917 QgsTextFormat format = tmpLyr.format();
4918 format.setBuffer( buffer );
4919 tmpLyr.setFormat( format );
4920 }
4921}
4922
4923void QgsPalLabeling::dataDefinedTextMask( QgsPalLayerSettings &tmpLyr, const QMap< QgsPalLayerSettings::Property, QVariant > &ddValues )
4924{
4925 if ( ddValues.isEmpty() )
4926 return;
4927
4928 QgsTextMaskSettings mask = tmpLyr.format().mask();
4929 bool changed = false;
4930
4931 // enabled ?
4932 if ( ddValues.contains( QgsPalLayerSettings::Property::MaskEnabled ) )
4933 {
4934 mask.setEnabled( ddValues.value( QgsPalLayerSettings::Property::MaskEnabled ).toBool() );
4935 changed = true;
4936 }
4937
4938 if ( !mask.enabled() )
4939 {
4940 if ( changed )
4941 {
4942 QgsTextFormat format = tmpLyr.format();
4943 format.setMask( mask );
4944 tmpLyr.setFormat( format );
4945 }
4946
4947 // tmpLyr.bufferSize > 0.0 && tmpLyr.bufferTransp < 100 figured in during evaluation
4948 return; // don't continue looking for unused values
4949 }
4950
4951 // buffer size
4952 if ( ddValues.contains( QgsPalLayerSettings::Property::MaskBufferSize ) )
4953 {
4954 mask.setSize( ddValues.value( QgsPalLayerSettings::Property::MaskBufferSize ).toDouble() );
4955 changed = true;
4956 }
4957
4958 // opacity
4959 if ( ddValues.contains( QgsPalLayerSettings::Property::MaskOpacity ) )
4960 {
4961 mask.setOpacity( ddValues.value( QgsPalLayerSettings::Property::MaskOpacity ).toDouble() / 100.0 );
4962 changed = true;
4963 }
4964
4965 // buffer size units
4966 if ( ddValues.contains( QgsPalLayerSettings::Property::MaskBufferUnit ) )
4967 {
4968 Qgis::RenderUnit bufunit = static_cast< Qgis::RenderUnit >( ddValues.value( QgsPalLayerSettings::Property::MaskBufferUnit ).toInt() );
4969 mask.setSizeUnit( bufunit );
4970 changed = true;
4971 }
4972
4973 // pen join style
4974 if ( ddValues.contains( QgsPalLayerSettings::Property::MaskJoinStyle ) )
4975 {
4976 mask.setJoinStyle( static_cast< Qt::PenJoinStyle >( ddValues.value( QgsPalLayerSettings::Property::MaskJoinStyle ).toInt() ) );
4977 changed = true;
4978 }
4979
4980 if ( changed )
4981 {
4982 QgsTextFormat format = tmpLyr.format();
4983 format.setMask( mask );
4984 tmpLyr.setFormat( format );
4985 }
4986}
4987
4988void QgsPalLabeling::dataDefinedShapeBackground( QgsPalLayerSettings &tmpLyr, const QMap< QgsPalLayerSettings::Property, QVariant > &ddValues )
4989{
4990 QgsTextBackgroundSettings background = tmpLyr.format().background();
4991 bool changed = false;
4992
4993 //shape draw
4994 if ( ddValues.contains( QgsPalLayerSettings::Property::ShapeDraw ) )
4995 {
4996 background.setEnabled( ddValues.value( QgsPalLayerSettings::Property::ShapeDraw ).toBool() );
4997 changed = true;
4998 }
4999
5000 if ( !background.enabled() )
5001 {
5002 if ( changed )
5003 {
5004 QgsTextFormat format = tmpLyr.format();
5005 format.setBackground( background );
5006 tmpLyr.setFormat( format );
5007 }
5008 return; // don't continue looking for unused values
5009 }
5010
5011 if ( ddValues.contains( QgsPalLayerSettings::Property::ShapeKind ) )
5012 {
5013 background.setType( static_cast< QgsTextBackgroundSettings::ShapeType >( ddValues.value( QgsPalLayerSettings::Property::ShapeKind ).toInt() ) );
5014 changed = true;
5015 }
5016
5017 if ( ddValues.contains( QgsPalLayerSettings::Property::ShapeSVGFile ) )
5018 {
5019 background.setSvgFile( ddValues.value( QgsPalLayerSettings::Property::ShapeSVGFile ).toString() );
5020 changed = true;
5021 }
5022
5023 if ( ddValues.contains( QgsPalLayerSettings::Property::ShapeSizeType ) )
5024 {
5025 background.setSizeType( static_cast< QgsTextBackgroundSettings::SizeType >( ddValues.value( QgsPalLayerSettings::Property::ShapeSizeType ).toInt() ) );
5026 changed = true;
5027 }
5028
5029 if ( ddValues.contains( QgsPalLayerSettings::Property::ShapeSizeX ) )
5030 {
5031 QSizeF size = background.size();
5032 size.setWidth( ddValues.value( QgsPalLayerSettings::Property::ShapeSizeX ).toDouble() );
5033 background.setSize( size );
5034 changed = true;
5035 }
5036 if ( ddValues.contains( QgsPalLayerSettings::Property::ShapeSizeY ) )
5037 {
5038 QSizeF size = background.size();
5039 size.setHeight( ddValues.value( QgsPalLayerSettings::Property::ShapeSizeY ).toDouble() );
5040 background.setSize( size );
5041 changed = true;
5042 }
5043
5044 if ( ddValues.contains( QgsPalLayerSettings::Property::ShapeSizeUnits ) )
5045 {
5046 background.setSizeUnit( static_cast< Qgis::RenderUnit >( ddValues.value( QgsPalLayerSettings::Property::ShapeSizeUnits ).toInt() ) );
5047 changed = true;
5048 }
5049
5050 if ( ddValues.contains( QgsPalLayerSettings::Property::ShapeRotationType ) )
5051 {
5053 changed = true;
5054 }
5055
5056 if ( ddValues.contains( QgsPalLayerSettings::Property::ShapeRotation ) )
5057 {
5058 background.setRotation( ddValues.value( QgsPalLayerSettings::Property::ShapeRotation ).toDouble() );
5059 changed = true;
5060 }
5061
5062 if ( ddValues.contains( QgsPalLayerSettings::Property::ShapeOffset ) )
5063 {
5064 background.setOffset( ddValues.value( QgsPalLayerSettings::Property::ShapeOffset ).toPointF() );
5065 changed = true;
5066 }
5067
5068 if ( ddValues.contains( QgsPalLayerSettings::Property::ShapeOffsetUnits ) )
5069 {
5070 background.setOffsetUnit( static_cast< Qgis::RenderUnit >( ddValues.value( QgsPalLayerSettings::Property::ShapeOffsetUnits ).toInt() ) );
5071 changed = true;
5072 }
5073
5074 if ( ddValues.contains( QgsPalLayerSettings::Property::ShapeRadii ) )
5075 {
5076 background.setRadii( ddValues.value( QgsPalLayerSettings::Property::ShapeRadii ).toSizeF() );
5077 changed = true;
5078 }
5079
5080 if ( ddValues.contains( QgsPalLayerSettings::Property::ShapeRadiiUnits ) )
5081 {
5082 background.setRadiiUnit( static_cast< Qgis::RenderUnit >( ddValues.value( QgsPalLayerSettings::Property::ShapeRadiiUnits ).toInt() ) );
5083 changed = true;
5084 }
5085
5086 if ( ddValues.contains( QgsPalLayerSettings::Property::ShapeBlendMode ) )
5087 {
5088 background.setBlendMode( static_cast< QPainter::CompositionMode >( ddValues.value( QgsPalLayerSettings::Property::ShapeBlendMode ).toInt() ) );
5089 changed = true;
5090 }
5091
5092 if ( ddValues.contains( QgsPalLayerSettings::Property::ShapeFillColor ) )
5093 {
5094 QVariant ddColor = ddValues.value( QgsPalLayerSettings::Property::ShapeFillColor );
5095 background.setFillColor( ddColor.value<QColor>() );
5096 changed = true;
5097 }
5098
5099 if ( ddValues.contains( QgsPalLayerSettings::Property::ShapeStrokeColor ) )
5100 {
5101 QVariant ddColor = ddValues.value( QgsPalLayerSettings::Property::ShapeStrokeColor );
5102 background.setStrokeColor( ddColor.value<QColor>() );
5103 changed = true;
5104 }
5105
5106 if ( ddValues.contains( QgsPalLayerSettings::Property::ShapeOpacity ) )
5107 {
5108 background.setOpacity( ddValues.value( QgsPalLayerSettings::Property::ShapeOpacity ).toDouble() / 100.0 );
5109 changed = true;
5110 }
5111
5112 if ( ddValues.contains( QgsPalLayerSettings::Property::ShapeStrokeWidth ) )
5113 {
5114 background.setStrokeWidth( ddValues.value( QgsPalLayerSettings::Property::ShapeStrokeWidth ).toDouble() );
5115 changed = true;
5116 }
5117
5119 {
5120 background.setStrokeWidthUnit( static_cast< Qgis::RenderUnit >( ddValues.value( QgsPalLayerSettings::Property::ShapeStrokeWidthUnits ).toInt() ) );
5121 changed = true;
5122 }
5123
5124 if ( ddValues.contains( QgsPalLayerSettings::Property::ShapeJoinStyle ) )
5125 {
5126 background.setJoinStyle( static_cast< Qt::PenJoinStyle >( ddValues.value( QgsPalLayerSettings::Property::ShapeJoinStyle ).toInt() ) );
5127 changed = true;
5128 }
5129
5130 if ( changed )
5131 {
5132 QgsTextFormat format = tmpLyr.format();
5133 format.setBackground( background );
5134 tmpLyr.setFormat( format );
5135 }
5136}
5137
5138void QgsPalLabeling::dataDefinedDropShadow( QgsPalLayerSettings &tmpLyr, const QMap< QgsPalLayerSettings::Property, QVariant > &ddValues )
5139{
5140 QgsTextShadowSettings shadow = tmpLyr.format().shadow();
5141 bool changed = false;
5142
5143 //shadow draw
5144 if ( ddValues.contains( QgsPalLayerSettings::Property::ShadowDraw ) )
5145 {
5146 shadow.setEnabled( ddValues.value( QgsPalLayerSettings::Property::ShadowDraw ).toBool() );
5147 changed = true;
5148 }
5149
5150 if ( !shadow.enabled() )
5151 {
5152 if ( changed )
5153 {
5154 QgsTextFormat format = tmpLyr.format();
5155 format.setShadow( shadow );
5156 tmpLyr.setFormat( format );
5157 }
5158 return; // don't continue looking for unused values
5159 }
5160
5161 if ( ddValues.contains( QgsPalLayerSettings::Property::ShadowUnder ) )
5162 {
5164 changed = true;
5165 }
5166
5167 if ( ddValues.contains( QgsPalLayerSettings::Property::ShadowOffsetAngle ) )
5168 {
5169 shadow.setOffsetAngle( ddValues.value( QgsPalLayerSettings::Property::ShadowOffsetAngle ).toInt() );
5170 changed = true;
5171 }
5172
5173 if ( ddValues.contains( QgsPalLayerSettings::Property::ShadowOffsetDist ) )
5174 {
5175 shadow.setOffsetDistance( ddValues.value( QgsPalLayerSettings::Property::ShadowOffsetDist ).toDouble() );
5176 changed = true;
5177 }
5178
5179 if ( ddValues.contains( QgsPalLayerSettings::Property::ShadowOffsetUnits ) )
5180 {
5181 shadow.setOffsetUnit( static_cast< Qgis::RenderUnit >( ddValues.value( QgsPalLayerSettings::Property::ShadowOffsetUnits ).toInt() ) );
5182 changed = true;
5183 }
5184
5185 if ( ddValues.contains( QgsPalLayerSettings::Property::ShadowRadius ) )
5186 {
5187 shadow.setBlurRadius( ddValues.value( QgsPalLayerSettings::Property::ShadowRadius ).toDouble() );
5188 changed = true;
5189 }
5190
5191 if ( ddValues.contains( QgsPalLayerSettings::Property::ShadowRadiusUnits ) )
5192 {
5193 shadow.setBlurRadiusUnit( static_cast< Qgis::RenderUnit >( ddValues.value( QgsPalLayerSettings::Property::ShadowRadiusUnits ).toInt() ) );
5194 changed = true;
5195 }
5196
5197 if ( ddValues.contains( QgsPalLayerSettings::Property::ShadowColor ) )
5198 {
5199 QVariant ddColor = ddValues.value( QgsPalLayerSettings::Property::ShadowColor );
5200 shadow.setColor( ddColor.value<QColor>() );
5201 changed = true;
5202 }
5203
5204 if ( ddValues.contains( QgsPalLayerSettings::Property::ShadowOpacity ) )
5205 {
5206 shadow.setOpacity( ddValues.value( QgsPalLayerSettings::Property::ShadowOpacity ).toDouble() / 100.0 );
5207 changed = true;
5208 }
5209
5210 if ( ddValues.contains( QgsPalLayerSettings::Property::ShadowScale ) )
5211 {
5212 shadow.setScale( ddValues.value( QgsPalLayerSettings::Property::ShadowScale ).toInt() );
5213 changed = true;
5214 }
5215
5216
5217 if ( ddValues.contains( QgsPalLayerSettings::Property::ShadowBlendMode ) )
5218 {
5219 shadow.setBlendMode( static_cast< QPainter::CompositionMode >( ddValues.value( QgsPalLayerSettings::Property::ShadowBlendMode ).toInt() ) );
5220 changed = true;
5221 }
5222
5223 if ( changed )
5224 {
5225 QgsTextFormat format = tmpLyr.format();
5226 format.setShadow( shadow );
5227 tmpLyr.setFormat( format );
5228 }
5229}
5230
5232{
5233 //set both the mime color data, and the text (format settings).
5234
5235 QMimeData *mimeData = new QMimeData;
5236 mimeData->setColorData( QVariant( format().color() ) );
5237
5238 QgsReadWriteContext rwContext;
5239 QDomDocument textDoc;
5240 QDomElement textElem = writeXml( textDoc, rwContext );
5241 textDoc.appendChild( textElem );
5242 mimeData->setData( "application/qgis.labelsettings"_L1, textDoc.toString().toUtf8() );
5243
5244 return mimeData;
5245}
5246
5248{
5249 if ( ok )
5250 *ok = false;
5251 QgsPalLayerSettings settings;
5252 if ( !data || !data->hasFormat( "application/qgis.labelsettings"_L1 ) )
5253 return settings;
5254
5255 QString text = QString::fromUtf8( data->data( "application/qgis.labelsettings"_L1 ) );
5256 if ( !text.isEmpty() )
5257 {
5258 QDomDocument doc;
5259 QDomElement elem;
5260 QgsReadWriteContext rwContext;
5261
5262 if ( doc.setContent( text ) )
5263 {
5264 elem = doc.documentElement();
5265
5266 settings.readXml( elem, rwContext );
5267 if ( ok )
5268 *ok = true;
5269 return settings;
5270 }
5271 }
5272 return settings;
5273}
@ PreferVector
Prefer vector-based rendering, when the result will still be visually near-identical to a raster-base...
Definition qgis.h:2918
@ StretchCharacterSpacingToFitLine
Increases (or decreases) the character spacing used for each label in order to fit the entire text ov...
Definition qgis.h:1329
@ Default
Default curved placement, characters are placed in an optimal position along the line....
Definition qgis.h:1327
@ StretchWordSpacingToFitLine
Increases (or decreases) the word spacing used for each label in order to fit the entire text over th...
Definition qgis.h:1330
@ PlaceCharactersAtVertices
Each individual character from the label text is placed such that their left-baseline position is loc...
Definition qgis.h:1328
@ LabelLargestPartOnly
Place a label only on the largest part from the geometry.
Definition qgis.h:1366
@ SplitLabelTextLinesOverParts
Splits the label text over the parts of the geometry, such that each consecutive part is labeled with...
Definition qgis.h:1368
@ LabelEveryPartWithEntireLabel
Place the (same) entire label over every part from the geometry.
Definition qgis.h:1367
QFlags< VectorRenderingSimplificationFlag > VectorRenderingSimplificationFlags
Simplification flags for vector feature rendering.
Definition qgis.h:3281
@ Success
Operation succeeded.
Definition qgis.h:2213
AngleUnit
Units of angles.
Definition qgis.h:5705
@ Degrees
Degrees.
Definition qgis.h:5706
@ NoSimplification
No simplification can be applied.
Definition qgis.h:3267
LabelOffsetType
Behavior modifier for label offset and distance, only applies in some label placement modes.
Definition qgis.h:1381
@ FromPoint
Offset distance applies from point geometry.
Definition qgis.h:1382
@ FromSymbolBounds
Offset distance applies from rendered symbol bounds.
Definition qgis.h:1383
@ PreferCloser
Prefer closer labels, falling back to alternate positions before larger distances.
Definition qgis.h:1294
LabelPlacement
Placement modes which determine how label candidates are generated for a feature.
Definition qgis.h:1307
@ OverPoint
Arranges candidates over a point (or centroid of a polygon), or at a preset offset from the point....
Definition qgis.h:1309
@ Curved
Arranges candidates following the curvature of a line feature. Applies to line layers only.
Definition qgis.h:1311
@ AroundPoint
Arranges candidates in a circle around a point (or centroid of a polygon). Applies to point or polygo...
Definition qgis.h:1308
@ Line
Arranges candidates parallel to a generalised line representing the feature or parallel to a polygon'...
Definition qgis.h:1310
@ Free
Arranges candidates scattered throughout a polygon feature. Candidates are rotated to respect the pol...
Definition qgis.h:1313
@ OrderedPositionsAroundPoint
Candidates are placed in predefined positions around a point. Preference is given to positions with g...
Definition qgis.h:1314
@ Horizontal
Arranges horizontal candidates scattered throughout a polygon feature or along a line feature....
Definition qgis.h:1312
@ PerimeterCurved
Arranges candidates following the curvature of a polygon's boundary. Applies to polygon layers only.
Definition qgis.h:1315
@ OutsidePolygons
Candidates are placed outside of polygon boundaries. Applies to polygon layers only.
Definition qgis.h:1316
@ AllowPlacementInsideOfPolygon
Labels can be placed inside a polygon feature.
Definition qgis.h:1442
@ AllowPlacementOutsideOfPolygon
Labels can be placed outside of a polygon feature.
Definition qgis.h:1441
VectorSimplificationAlgorithm
Simplification algorithms for vector features.
Definition qgis.h:3250
@ CenterOfText
Anchor using center of text.
Definition qgis.h:1497
@ SkipEmptyInteriorRings
Skip any empty polygon interior ring.
Definition qgis.h:2333
@ Warning
Warning message.
Definition qgis.h:162
QFlags< LabelLinePlacementFlag > LabelLinePlacementFlags
Line placement flags, which control how candidates are generated for a linear feature.
Definition qgis.h:1430
@ Labeling
Labeling-specific layout mode.
Definition qgis.h:3141
@ Rectangle
Text within rectangle layout mode.
Definition qgis.h:3139
Capitalization
String capitalization options.
Definition qgis.h:3639
@ AllSmallCaps
Force all characters to small caps.
Definition qgis.h:3647
@ MixedCase
Mixed case, ie no change.
Definition qgis.h:3640
@ AllLowercase
Convert all characters to lowercase.
Definition qgis.h:3642
@ TitleCase
Simple title case conversion - does not fully grammatically parse the text and uses simple rules only...
Definition qgis.h:3645
@ SmallCaps
Mixed case small caps.
Definition qgis.h:3644
@ ForceFirstLetterToCapital
Convert just the first letter of each word to uppercase, leave the rest untouched.
Definition qgis.h:3643
@ AllUppercase
Convert all characters to uppercase.
Definition qgis.h:3641
QFlags< LabelPolygonPlacementFlag > LabelPolygonPlacementFlags
Polygon placement flags, which control how candidates are generated for a polygon feature.
Definition qgis.h:1452
LabelQuadrantPosition
Label quadrant positions.
Definition qgis.h:1395
@ AboveRight
Above right.
Definition qgis.h:1398
@ BelowLeft
Below left.
Definition qgis.h:1402
@ Above
Above center.
Definition qgis.h:1397
@ BelowRight
Below right.
Definition qgis.h:1404
@ Right
Right middle.
Definition qgis.h:1401
@ AboveLeft
Above left.
Definition qgis.h:1396
@ Below
Below center.
Definition qgis.h:1403
@ Over
Center middle.
Definition qgis.h:1400
TextOrientation
Text orientations.
Definition qgis.h:3123
@ Vertical
Vertically oriented text.
Definition qgis.h:3125
@ RotationBased
Horizontally or vertically oriented text based on rotation (only available for map labeling).
Definition qgis.h:3126
@ Horizontal
Horizontally oriented text.
Definition qgis.h:3124
UnplacedLabelVisibility
Unplaced label visibility.
Definition qgis.h:1256
@ FollowEngineSetting
Respect the label engine setting.
Definition qgis.h:1257
@ AffectsLabeling
If present, indicates that the renderer will participate in the map labeling problem.
Definition qgis.h:911
GeometryType
The geometry types are used to group Qgis::WkbType in a coarse way.
Definition qgis.h:399
@ Point
Points.
Definition qgis.h:400
@ Line
Lines.
Definition qgis.h:401
@ Polygon
Polygons.
Definition qgis.h:402
@ Unknown
Unknown types.
Definition qgis.h:403
LabelMultiLineAlignment
Text alignment for multi-line labels.
Definition qgis.h:1478
@ Center
Center align.
Definition qgis.h:1480
@ FollowPlacement
Alignment follows placement of label, e.g., labels to the left of a feature will be drawn with right ...
Definition qgis.h:1482
@ Group
Composite group layer. Added in QGIS 3.24.
Definition qgis.h:214
@ Plugin
Plugin based layer.
Definition qgis.h:209
@ TiledScene
Tiled scene layer. Added in QGIS 3.34.
Definition qgis.h:215
@ Annotation
Contains freeform, georeferenced annotations. Added in QGIS 3.16.
Definition qgis.h:212
@ Vector
Vector layer.
Definition qgis.h:207
@ VectorTile
Vector tile layer. Added in QGIS 3.14.
Definition qgis.h:211
@ Mesh
Mesh layer. Added in QGIS 3.2.
Definition qgis.h:210
@ Raster
Raster layer.
Definition qgis.h:208
@ PointCloud
Point cloud layer. Added in QGIS 3.18.
Definition qgis.h:213
@ TreatWhitespaceAsCollision
Treat overlapping whitespace text in labels and whitespace overlapping obstacles as collisions.
Definition qgis.h:1282
RenderUnit
Rendering size units.
Definition qgis.h:5770
@ Percentage
Percentage of another measurement (e.g., canvas size, feature size).
Definition qgis.h:5774
@ Millimeters
Millimeters.
Definition qgis.h:5771
@ Points
Points (e.g., for font sizes).
Definition qgis.h:5775
@ MapUnits
Map units.
Definition qgis.h:5772
@ Pixels
Pixels.
Definition qgis.h:5773
@ DrawLabelSelection
Whether vector selections should be change the rendering of associated labels.
Definition qgis.h:3000
@ Antialiasing
Use antialiasing while drawing.
Definition qgis.h:2984
@ Linework
Combines all rings into a set of noded lines and then extracts valid polygons from that linework.
Definition qgis.h:2365
@ Center
Center align.
Definition qgis.h:3181
@ AllowOverlapIfRequired
Avoids overlapping labels when possible, but permit overlaps if labels for features cannot otherwise ...
Definition qgis.h:1270
@ PreventOverlap
Do not allow labels to overlap other labels.
Definition qgis.h:1269
static const double UI_SCALE_FACTOR
UI scaling factor.
Definition qgis.h:7390
@ MiddleLeft
Label on left of point.
Definition qgis.h:1348
@ TopRight
Label on top-right of point.
Definition qgis.h:1347
@ MiddleRight
Label on right of point.
Definition qgis.h:1349
@ TopSlightlyRight
Label on top of point, slightly right of center.
Definition qgis.h:1346
@ BottomRight
Label on bottom right of point.
Definition qgis.h:1354
@ BottomLeft
Label on bottom-left of point.
Definition qgis.h:1350
@ BottomSlightlyRight
Label below point, slightly right of center.
Definition qgis.h:1353
@ TopLeft
Label on top-left of point.
Definition qgis.h:1343
UpsideDownLabelHandling
Handling techniques for upside down labels.
Definition qgis.h:1463
@ FlipUpsideDownLabels
Upside-down labels (90 <= angle < 270) are shown upright.
Definition qgis.h:1464
virtual QgsAbstractGeometry * boundary() const =0
Returns the closure of the combinatorial boundary of the geometry (ie the topological boundary of the...
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.
static QIcon getThemeIcon(const QString &name, const QColor &fillColor=QColor(), const QColor &strokeColor=QColor())
Helper to get a theme icon.
static QgsFontManager * fontManager()
Returns the application font manager, which manages available fonts and font installation for the QGI...
static QgsCalloutRegistry * calloutRegistry()
Returns the application's callout registry, used for managing callout types.
Registry of available callout classes.
static std::unique_ptr< QgsCallout > defaultCallout()
Create a new instance of a callout with default settings.
std::unique_ptr< QgsCallout > createCallout(const QString &type, const QVariantMap &properties=QVariantMap(), const QgsReadWriteContext &context=QgsReadWriteContext()) const
Creates a new instance of a callout, given the callout type and properties.
Contains additional contextual information about the context in which a callout is being rendered.
Definition qgscallout.h:248
Abstract base class for callout renderers.
Definition qgscallout.h:55
bool enabled() const
Returns true if the callout is enabled.
Definition qgscallout.h:321
static QColor colorFromString(const QString &string)
Decodes a string into a color value.
static QString colorToString(const QColor &color)
Encodes a color into a string value.
Represents a coordinate reference system (CRS).
QString userFriendlyIdentifier(Qgis::CrsIdentifierType type=Qgis::CrsIdentifierType::MediumString) const
Returns a user friendly identifier for the CRS.
Handles coordinate transforms between two coordinate systems.
QgsCoordinateReferenceSystem sourceCrs() const
Returns the source coordinate reference system, which the transform will transform coordinates from.
bool isShortCircuited() const
Returns true if the transform short circuits because the source and destination are equivalent.
bool isValid() const
Returns true if the coordinate transform is valid, ie both the source and destination CRS have been s...
Custom exception class for Coordinate Reference System related exceptions.
Curve polygon geometry type.
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
void setOriginalValueVariable(const QVariant &value)
Sets the original value variable value for the context.
Handles parsing and evaluation of expressions (formerly called "search strings").
bool prepare(const QgsExpressionContext *context)
Gets the expression ready for evaluation - find out column indexes.
bool hasParserError() const
Returns true if an error occurred when parsing the input expression.
QString evalErrorString() const
Returns evaluation error.
QString parserErrorString() const
Returns parser error.
QSet< QString > referencedColumns() const
Gets list of columns referenced by the expression.
bool hasEvalError() const
Returns true if an error occurred when evaluating last input.
QVariant evaluate()
Evaluate the feature and return the result.
virtual Qgis::FeatureRendererFlags flags() const
Returns flags associated with the renderer.
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
Definition qgsfeature.h:60
QgsFeatureId id
Definition qgsfeature.h:63
QgsGeometry geometry
Definition qgsfeature.h:66
Q_INVOKABLE QVariant attribute(const QString &name) const
Lookup attribute value by attribute name.
void setGeometry(const QgsGeometry &geometry)
Set the feature's geometry.
Container of fields for a vector layer.
Definition qgsfields.h:45
Q_INVOKABLE int lookupField(const QString &fieldName) const
Looks up field's index from the field name.
QString processFontFamilyName(const QString &name) const
Processes a font family name, applying any matching fontFamilyReplacements() to the name.
static QFont createFont(const QString &family, int pointSize=-1, int weight=-1, bool italic=false)
Creates a font with the specified family.
static bool fontFamilyOnSystem(const QString &family)
Check whether font family is on system in a quick manner, which does not compare [foundry].
static bool updateFontViaStyle(QFont &f, const QString &fontstyle, bool fallback=false)
Updates font with named style and retain all font properties.
Encapsulates parameters under which a geometry operation is performed.
A geometry is the spatial representation of a feature.
static QgsGeometry fromRect(const QgsRectangle &rect)
Creates a new geometry from a QgsRectangle.
QgsGeometry intersection(const QgsGeometry &geometry, const QgsGeometryParameters &parameters=QgsGeometryParameters(), QgsFeedback *feedback=nullptr) const
Returns a geometry representing the points shared by this geometry and other.
double length() const
Returns the planar, 2-dimensional length of geometry.
QgsAbstractGeometry::const_part_iterator const_parts_begin() const
Returns STL-style const iterator pointing to the first part of the geometry.
static QgsGeometry collectGeometry(const QVector< QgsGeometry > &geometries)
Creates a new multipart geometry from a list of QgsGeometry objects.
QgsGeometry mergeLines(const QgsGeometryParameters &parameters=QgsGeometryParameters()) const
Merges any connected lines in a LineString/MultiLineString geometry and converts them to single line ...
QString lastError() const
Returns an error string referring to the last error encountered either when this geometry was created...
QgsGeometry clipped(const QgsRectangle &rectangle, QgsFeedback *feedback=nullptr)
Clips the geometry using the specified rectangle.
bool isAxisParallelRectangle(double maximumDeviation, bool simpleRectanglesOnly=false) const
Returns true if the geometry is a polygon that is almost an axis-parallel rectangle.
Qgis::GeometryOperationResult transform(const QgsCoordinateTransform &ct, Qgis::TransformDirection direction=Qgis::TransformDirection::Forward, bool transformZ=false)
Transforms this geometry as described by the coordinate transform ct.
QVector< QgsGeometry > asGeometryCollection() const
Returns contents of the geometry as a list of geometries.
QgsAbstractGeometry * get()
Returns a modifiable (non-const) reference to the underlying abstract geometry primitive.
const QgsAbstractGeometry * constGet() const
Returns a non-modifiable (const) reference to the underlying abstract geometry primitive.
bool contains(const QgsPointXY *p) const
Returns true if the geometry contains the point p.
QgsPointXY asPoint() const
Returns the contents of the geometry as a 2-dimensional point.
void filterVertices(const std::function< bool(const QgsPoint &) > &filter)
Filters the vertices from the geometry in place, removing any which do not return true for the filter...
bool isGeosValid(Qgis::GeometryValidityFlags flags=Qgis::GeometryValidityFlags()) const
Checks validity of the geometry using GEOS.
static QgsGeometry fromPointXY(const QgsPointXY &point)
Creates a new geometry from a QgsPointXY object.
Qgis::GeometryType type
double area() const
Returns the planar, 2-dimensional area of the geometry.
bool isMultipart() const
Returns true if WKB of the geometry is of WKBMulti* type.
QgsGeometry centroid() const
Returns the center of mass of a geometry.
QgsGeometry makeValid(Qgis::MakeValidMethod method=Qgis::MakeValidMethod::Linework, bool keepCollapsed=false, QgsFeedback *feedback=nullptr) const
Attempts to make an invalid geometry valid without losing vertices.
bool isEmpty() const
Returns true if the geometry is empty (eg a linestring with no vertices, or a collection with no geom...
QgsAbstractGeometry::const_part_iterator const_parts_end() const
Returns STL-style iterator pointing to the imaginary part after the last part of the geometry.
bool convertGeometryCollectionToSubclass(Qgis::GeometryType geomType)
Converts geometry collection to a the desired geometry type subclass (multi-point,...
QgsGeometry simplify(double tolerance, QgsFeedback *feedback=nullptr) const
Returns a simplified version of this geometry using a specified tolerance value.
QgsRectangle boundingBox() const
Returns the bounding box of the geometry.
Qgis::GeometryOperationResult rotate(double rotation, const QgsPointXY &center)
Rotate this geometry around the Z axis.
Q_INVOKABLE QString asWkt(int precision=17) const
Exports the geometry to WKT.
static geos::unique_ptr asGeos(const QgsGeometry &geometry, double precision=0, Qgis::GeosCreationFlags flags=Qgis::GeosCreationFlags())
Returns a geos geometry - caller takes ownership of the object (should be deleted with GEOSGeom_destr...
Definition qgsgeos.cpp:284
Encapsulates details for an individual feature to label.
QgsGeometry obstacleGeometry() const
Returns the (optional) obstacle geometry for the label.
bool isSelected() const
Returns true if the feature associated with the label is currently selected.
const QgsSymbol * symbol() const
Returns the (optional) symbol associated with the feature to label.
void setNoRepeatDistance(double distance)
Sets the minimum distance (in label units) between labels for this feature and other labels with the ...
void setLabelMarginDistance(double distance)
Sets the minimum distance (in label units) between labels for this feature and other labels.
Contains settings related to how the label engine places and formats labels for line features (or pol...
AnchorType
Line anchor types.
AnchorClipping
Clipping behavior for line anchor calculation.
@ UseEntireLine
Entire original feature line geometry is used when calculating the line anchor for labels.
@ UseVisiblePartsOfLine
Only visible parts of lines are considered when calculating the line anchor for labels.
void setDirectionSymbolPlacement(DirectionSymbolPlacement placement)
Sets the placement for direction symbols.
void setLeftDirectionSymbol(const QString &symbol)
Sets the string to use for left direction arrows.
DirectionSymbolPlacement
Placement options for direction symbols.
@ SymbolLeftRight
Place direction symbols on left/right of label.
@ SymbolAbove
Place direction symbols on above label.
@ SymbolBelow
Place direction symbols on below label.
void setRightDirectionSymbol(const QString &symbol)
Sets the string to use for right direction arrows.
bool addDirectionSymbol() const
Returns true if '<' or '>' (or custom strings set via leftDirectionSymbol and rightDirectionSymbol) w...
void setReverseDirectionSymbol(bool reversed)
Sets whether the direction symbols should be reversed.
void setAddDirectionSymbol(bool enabled)
Sets whether '<' or '>' (or custom strings set via leftDirectionSymbol and rightDirectionSymbol) will...
ObstacleType
Valid obstacle types, which affect how features within the layer will act as obstacles for labels.
@ PolygonInterior
Avoid placing labels over interior of polygon (prefer placing labels totally outside or just slightly...
void setIsObstacle(bool isObstacle)
Sets whether features are obstacles to labels of other layers.
void updateDataDefinedProperties(const QgsPropertyCollection &properties, QgsExpressionContext &context)
Updates the obstacle settings to respect any data defined properties set within the specified propert...
void setObstacleGeometry(const QgsGeometry &obstacleGeom)
Sets the label's obstacle geometry, if different to the feature geometry.
Contains general settings related to how labels are placed.
Contains settings related to how the label engine places and formats labels for point-like features.
double minimumFeatureSize() const
Returns the minimum feature size (in millimeters) for a feature to be labelled.
const QgsMapUnitScale & minimumDistanceToDuplicateMapUnitScale() const
Returns the map unit scale for the minimum distance to labels with duplicate text.
int maximumNumberLabels() const
Returns the maximum number of labels which should be drawn for this layer.
bool allowDuplicateRemoval() const
Returns whether duplicate label removal is permitted for this layer.
const QgsMapUnitScale & labelMarginDistanceMapUnitScale() const
Returns the map unit scale for the minimum distance to other labels.
double labelMarginDistance() const
Returns the minimum distance to other labels (i.e.
Qgis::RenderUnit labelMarginDistanceUnit() const
Returns the units for the minimum distance to other labels.
void updateDataDefinedProperties(const QgsPropertyCollection &properties, QgsExpressionContext &context)
Updates the thinning settings to respect any data defined properties set within the specified propert...
double minimumDistanceToDuplicate() const
Returns the minimum distance to labels with duplicate text.
Qgis::RenderUnit minimumDistanceToDuplicateUnit() const
Returns the units for the minimum distance to labels with duplicate text.
bool limitNumberOfLabelsEnabled() const
Returns true if the number of labels drawn for the layer should be limited.
static constexpr double DEFAULT_MINIMUM_DISTANCE_TO_DUPLICATE
Default minimum distance to duplicate labels.
static QString encodePredefinedPositionOrder(const QVector< Qgis::LabelPredefinedPointPosition > &positions)
Encodes an ordered list of predefined point label positions to a string.
static QVector< Qgis::LabelPredefinedPointPosition > decodePredefinedPositionOrder(const QString &positionString)
Decodes a string to an ordered list of predefined point label positions.
Base class for all map layer types.
Definition qgsmaplayer.h:83
Q_INVOKABLE QVariant customProperty(const QString &value, const QVariant &defaultValue=QVariant()) const
Read a custom property from layer.
void removeCustomProperty(const QString &key)
Remove a custom property from layer.
Qgis::LayerType type
Definition qgsmaplayer.h:93
T customEnumProperty(const QString &key, const T &defaultValue)
Returns the property value for a property based on an enum.
Contains configuration for rendering maps.
const QgsMapToPixel & mapToPixel() const
double extentBuffer() const
Returns the buffer in map units to use around the visible extent for rendering symbols whose correspo...
QgsRectangle visibleExtent() const
Returns the actual extent derived from requested extent that takes output image size into account.
double rotation() const
Returns the rotation of the resulting map image, in degrees clockwise.
QgsCoordinateReferenceSystem destinationCrs() const
Returns the destination coordinate reference system for the map render.
QgsCoordinateTransformContext transformContext() const
Returns the coordinate transform context, which stores various information regarding which datum tran...
@ SimplifyEnvelope
The geometries can be fully simplified by its BoundingBox.
Perform transforms between map coordinates and device coordinates.
double mapUnitsPerPixel() const
Returns the current map units per pixel.
double mapRotation() const
Returns the current map rotation in degrees (clockwise).
void setParameters(double mapUnitsPerPixel, double centerX, double centerY, int widthPixels, int heightPixels, double rotation)
Sets parameters for use in transforming coordinates.
Represents a mesh layer supporting display of data on structured or unstructured meshes.
const QgsAbstractMeshLayerLabeling * labeling() const
Access to const labeling configuration.
bool labelsEnabled() const
Returns whether the layer contains labels which are enabled and should be drawn.
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::MessageLevel::Warning, bool notifyUser=true, const char *file=__builtin_FILE(), const char *function=__builtin_FUNCTION(), int line=__builtin_LINE(), Qgis::StringFormat format=Qgis::StringFormat::PlainText)
Adds a message to the log instance (and creates it if necessary).
Multi surface geometry collection.
static QStringList splitToLines(const QString &text, const QString &wrapCharacter, int autoWrapLength=0, bool useMaxLineLengthWhenAutoWrapping=true)
Splits a text string to a list of separate lines, using a specified wrap character (wrapCharacter).
static QgsGeometry prepareGeometry(const QgsGeometry &geometry, QgsRenderContext &context, const QgsCoordinateTransform &ct, const QgsGeometry &clipGeometry=QgsGeometry(), bool mergeLines=false)
Prepares a geometry for registration with PAL.
static bool geometryRequiresPreparation(const QgsGeometry &geometry, QgsRenderContext &context, const QgsCoordinateTransform &ct, const QgsGeometry &clipGeometry=QgsGeometry(), bool mergeLines=false)
Checks whether a geometry requires preparation before registration with PAL.
static bool staticWillUseLayer(const QgsMapLayer *layer)
Called to find out whether a specified layer is used for labeling.
static QStringList splitToGraphemes(const QString &text)
Splits a text string to a list of graphemes, which are the smallest allowable character divisions in ...
Contains settings for how a map layer will be labeled.
bool fitInPolygonOnly
true if only labels which completely fit within a polygon are allowed.
double yOffset
Vertical offset of label.
QgsMapUnitScale labelOffsetMapUnitScale
Map unit scale for label offset.
int fontMaxPixelSize
Maximum pixel size for showing rendered map unit labels (1 - 10000).
const QgsLabelPlacementSettings & placementSettings() const
Returns the label placement settings.
double maxCurvedCharAngleIn
Maximum angle between inside curved label characters (valid range 20.0 to 60.0).
void setFormat(const QgsTextFormat &format)
Sets the label text formatting settings, e.g., font settings, buffer settings, etc.
double zIndex
Z-Index of label, where labels with a higher z-index are rendered on top of labels with a lower z-ind...
const QgsMapToPixel * xform
void startRender(QgsRenderContext &context)
Prepares the label settings for rendering.
QString wrapChar
Wrapping character string.
QSet< QString > referencedFields(const QgsRenderContext &context) const
Returns all field names referenced by the configuration (e.g.
Qgis::LabelOffsetType offsetType
Offset type for layer (only applies in certain placement modes).
double xOffset
Horizontal offset of label.
Qgis::LabelPlacement placement
Label placement mode.
QgsCoordinateTransform ct
bool drawLabels
Whether to draw labels for this layer.
bool fontLimitPixelSize
true if label sizes should be limited by pixel size.
QString legendString() const
legendString
static QgsPalLayerSettings fromMimeData(const QMimeData *data, bool *ok=nullptr)
Attempts to parse the provided mime data as a QgsPalLayerSettings.
double minimumScale
The minimum map scale (i.e.
Q_DECL_DEPRECATED void calculateLabelSize(const QFontMetricsF *fm, const QString &text, double &labelX, double &labelY, const QgsFeature *f=nullptr, QgsRenderContext *context=nullptr, double *rotatedLabelX=nullptr, double *rotatedLabelY=nullptr)
Calculates the space required to render the provided text in map units.
void registerFeature(const QgsFeature &f, QgsRenderContext &context)
Registers a feature for labeling.
QgsPalLayerSettings & operator=(const QgsPalLayerSettings &s)
copy operator - only copies the permanent members
void readXml(const QDomElement &elem, const QgsReadWriteContext &context)
Read settings from a DOM element.
bool scaleVisibility
Set to true to limit label visibility to a range of scales.
double repeatDistance
Distance for repeating labels for a single feature.
QgsPropertyCollection & dataDefinedProperties()
Returns a reference to the label's property collection, used for data defined overrides.
QgsExpression * getLabelExpression() const
Returns the QgsExpression for this label settings.
bool geometryGeneratorEnabled
Defines if the geometry generator is enabled or not. If disabled, the standard geometry will be taken...
QMimeData * toMimeData() const
Returns new mime data representing the labeling settings.
Qgis::LabelMultiLineAlignment multilineAlign
Horizontal alignment of multi-line labels.
bool centroidInside
true if centroid positioned labels must be placed inside their corresponding feature polygon,...
int priority
Label priority.
Qgis::GeometryType geometryGeneratorType
The type of the result geometry of the geometry generator.
QDomElement writeXml(QDomDocument &doc, const QgsReadWriteContext &context) const
Write settings into a DOM element.
QgsCallout * callout() const
Returns the label callout renderer, responsible for drawing label callouts.
int fontMinPixelSize
Minimum pixel size for showing rendered map unit labels (1 - 1000).
double angleOffset
Label rotation, in degrees clockwise.
double maxCurvedCharAngleOut
Maximum angle between outside curved label characters (valid range -20.0 to -95.0).
Qgis::AngleUnit rotationUnit() const
Unit for rotation of labels.
Qgis::GeometryType layerType
Geometry type of layers associated with these settings.
Qgis::RenderUnit offsetUnits
Units for offsets of label.
bool isExpression
true if this label is made from a expression string, e.g., FieldName || 'mm'
bool preserveRotation
True if label rotation should be preserved during label pin/unpin operations.
bool plusSign
Whether '+' signs should be prepended to positive numeric labels.
bool prepare(QgsRenderContext &context, QSet< QString > &attributeNames, const QgsFields &fields, const QgsMapSettings &mapSettings, const QgsCoordinateReferenceSystem &crs)
Prepare for registration of features.
QString geometryGenerator
The geometry generator expression. Null if disabled.
const QgsLabelLineSettings & lineSettings() const
Returns the label line settings, which contain settings related to how the label engine places and fo...
QgsMapUnitScale distMapUnitScale
Map unit scale for label feature distance.
QgsStringReplacementCollection substitutions
Substitution collection for automatic text substitution with labels.
Q_DECL_DEPRECATED QColor previewBkgrdColor
const QgsFeature * mCurFeat
int decimals
Number of decimal places to show for numeric labels.
double dist
Distance from feature to the label.
void setRotationUnit(Qgis::AngleUnit angleUnit)
Set unit for rotation of labels.
QgsMapUnitScale repeatDistanceMapUnitScale
Map unit scale for repeating labels for a single feature.
Qgis::RenderUnit distUnits
Units the distance from feature to the label.
bool centroidWhole
true if feature centroid should be calculated from the whole feature, or false if only the visible pa...
Property
Data definable properties.
@ PositionX
X-coordinate data defined label position.
@ LinePlacementOptions
Line placement flags.
@ MinScale
Min scale (deprecated, for old project compatibility only).
@ WhitespaceCollisionHandling
Whitespace collision handling.
@ ShapeTransparency
Shape transparency (deprecated).
@ AllowDegradedPlacement
Allow degraded label placements.
@ MaskEnabled
Whether the mask is enabled.
@ OverlapHandling
Overlap handling technique.
@ PositionY
Y-coordinate data defined label position.
@ MaximumScale
Maximum map scale (ie most "zoomed in").
@ Vali
Vertical alignment for data defined label position (Bottom, Base, Half, Cap, Top).
@ MinimumScale
Minimum map scale (ie most "zoomed out").
@ FontStretchFactor
Font stretch factor, since QGIS 3.24.
@ PolygonLabelOutside
Whether labels outside a polygon feature are permitted, or should be forced.
@ LineAnchorType
Line anchor type.
@ MaxScale
Max scale (deprecated, for old project compatibility only).
@ BufferTransp
Buffer transparency (deprecated).
@ LineAnchorClipping
Clipping mode for line anchor calculation.
@ LabelAllParts
Multipart geometry behavior.
@ LabelMarginDistance
Minimum distance from labels for this feature to other labels.
@ LineAnchorPercent
Portion along line at which labels should be anchored.
@ Hali
Horizontal alignment for data defined label position (Left, Center, Right).
@ RemoveDuplicateLabelDistance
Minimum distance from labels for this feature to other labels with duplicate text.
@ LineAnchorTextPoint
Line anchor text point.
@ ShadowTransparency
Shadow transparency (deprecated).
@ OverrunDistance
Distance which labels can extend past either end of linear features.
@ MaskBufferUnit
Mask buffer size unit.
@ FontTransp
Text transparency (deprecated).
@ CurvedLabelMode
Mode which determine how curved labels are generated and placed.
@ MaskBufferSize
Mask buffer size.
@ TabStopDistance
Tab stop distance, since QGIS 3.38.
@ RemoveDuplicateLabels
Whether this feature can cause removal of duplicate labels.
@ PositionPoint
Point-coordinate data defined label position.
@ MaximumDistance
Maximum distance of label from feature.
@ Rotation
Label rotation (deprecated, for old project compatibility only).
Qgis::RenderUnit repeatDistanceUnit
Units for repeating labels for a single feature.
Qgis::UpsideDownLabelHandling upsidedownLabels
Controls whether upside down labels are displayed and how they are handled.
QString fieldName
Name of field (or an expression) to use for label text.
static QPixmap labelSettingsPreviewPixmap(const QgsPalLayerSettings &settings, QSize size, const QString &previewText=QString(), int padding=0, const QgsScreenProperties &screen=QgsScreenProperties())
Returns a pixmap preview for label settings.
bool formatNumbers
Set to true to format numeric label text as numbers (e.g.
bool containsAdvancedEffects() const
Returns true if any component of the label settings requires advanced effects such as blend modes,...
const QgsTextFormat & format() const
Returns the label text formatting settings, e.g., font settings, buffer settings, etc.
void setCallout(QgsCallout *callout)
Sets the label callout renderer, responsible for drawing label callouts.
double maximumScale
The maximum map scale (i.e.
int autoWrapLength
If non-zero, indicates that label text should be automatically wrapped to (ideally) the specified num...
Qgis::UnplacedLabelVisibility unplacedVisibility() const
Returns the layer's unplaced label visibility.
bool useMaxLineLengthForAutoWrap
If true, indicates that when auto wrapping label text the autoWrapLength length indicates the maximum...
void setUnplacedVisibility(Qgis::UnplacedLabelVisibility visibility)
Sets the layer's unplaced label visibility.
const QgsLabelPointSettings & pointSettings() const
Returns the label point settings, which contain settings related to how the label engine places and f...
static const QgsPropertiesDefinition & propertyDefinitions()
Returns the labeling property definitions.
void stopRender(QgsRenderContext &context)
Finalises the label settings after use.
std::vector< std::unique_ptr< QgsLabelFeature > > registerFeatureWithDetails(const QgsFeature &feature, QgsRenderContext &context, const QgsLabelFeatureDetails &details)
Registers a feature for labeling.
bool useSubstitutions
True if substitutions should be applied.
Represents a 2D point.
Definition qgspointxy.h:62
Point geometry type, with support for z-dimension and m-values.
Definition qgspoint.h:53
double x
Definition qgspoint.h:56
double y
Definition qgspoint.h:57
QVariant value(int key, const QgsExpressionContext &context, const QVariant &defaultValue=QVariant()) const final
Returns the calculated value of the property with the specified key from within the collection.
bool isActive(int key) const final
Returns true if the collection contains an active property with the specified key.
@ Double
Double value (including negative values).
Definition qgsproperty.h:56
@ Double0To1
Double value between 0-1 (inclusive).
Definition qgsproperty.h:58
@ StrokeWidth
Line stroke width.
Definition qgsproperty.h:71
@ String
Any string value.
Definition qgsproperty.h:60
@ BlendMode
Blend mode.
Definition qgsproperty.h:66
@ Boolean
Boolean value.
Definition qgsproperty.h:52
@ RenderUnits
Render units (eg mm/pixels/map units).
Definition qgsproperty.h:62
@ PenJoinStyle
Pen join style.
Definition qgsproperty.h:65
@ SvgPath
Path to an SVG file.
Definition qgsproperty.h:76
@ IntegerPositiveGreaterZero
Non-zero positive integer values.
Definition qgsproperty.h:55
@ IntegerPositive
Positive integer values (including 0).
Definition qgsproperty.h:54
@ Opacity
Opacity (0-100).
Definition qgsproperty.h:61
@ ColorNoAlpha
Color with no alpha channel.
Definition qgsproperty.h:64
@ Rotation
Rotation (value between 0-360 degrees).
Definition qgsproperty.h:59
@ ColorWithAlpha
Color with alpha channel.
Definition qgsproperty.h:63
@ DoublePositive
Positive double value (including 0).
Definition qgsproperty.h:57
@ Size2D
2D size (width/height different)
Definition qgsproperty.h:69
@ DataTypeString
Property requires a string value.
Definition qgsproperty.h:91
@ DataTypeNumeric
Property requires a numeric value.
Definition qgsproperty.h:98
A store for object properties.
static QgsProperty fromExpression(const QString &expression, bool isActive=true)
Returns a new ExpressionBasedProperty created from the specified expression.
static QgsProperty fromField(const QString &fieldName, bool isActive=true)
Returns a new FieldBasedProperty created from the specified field name.
Represents a raster layer.
const QgsAbstractRasterLayerLabeling * labeling() const
Access to const labeling configuration.
bool labelsEnabled() const
Returns whether the layer contains labels which are enabled and should be drawn.
A container for the context for various read/write operations on objects.
A rectangle specified with double values.
Q_INVOKABLE QString toString(int precision=16) const
Returns a string representation of form xmin,ymin : xmax,ymax Coordinates will be rounded to the spec...
bool contains(const QgsRectangle &rect) const
Returns true when rectangle contains other rectangle.
void grow(double delta)
Grows the rectangle in place by the specified amount.
QgsPointXY center
QgsCoordinateReferenceSystem crs() const
Returns the associated coordinate reference system, or an invalid CRS if no reference system is set.
Contains information about the context of a rendering operation.
double scaleFactor() const
Returns the scaling factor for the render to convert painter units to physical sizes.
double convertToMapUnits(double size, Qgis::RenderUnit unit, const QgsMapUnitScale &scale=QgsMapUnitScale()) const
Converts a size from the specified units to map units.
void setScaleFactor(double factor)
Sets the scaling factor for the render to convert painter units to physical sizes.
QgsVectorSimplifyMethod & vectorSimplifyMethod()
Returns the simplification settings to use when rendering vector layers.
double symbologyReferenceScale() const
Returns the symbology reference scale.
void setDevicePixelRatio(float ratio)
Sets the device pixel ratio.
double convertToPainterUnits(double size, Qgis::RenderUnit unit, const QgsMapUnitScale &scale=QgsMapUnitScale(), Qgis::RenderSubcomponentProperty property=Qgis::RenderSubcomponentProperty::Generic) const
Converts a size from the specified units to painter units (pixels).
double rendererScale() const
Returns the renderer map scale.
QgsExpressionContext & expressionContext()
Gets the expression context.
QgsGeometry featureClipGeometry() const
Returns the geometry to use to clip features at render time.
bool testFlag(Qgis::RenderContextFlag flag) const
Check whether a particular flag is enabled.
QgsRectangle mapExtent() const
Returns the original extent of the map being rendered.
void setRasterizedRenderingPolicy(Qgis::RasterizedRenderingPolicy policy)
Sets the policy controlling when rasterisation of content during renders is permitted.
void setFlag(Qgis::RenderContextFlag flag, bool on=true)
Enable or disable a particular flag (other flags are not affected).
const QgsMapToPixel & mapToPixel() const
Returns the context's map to pixel transform, which transforms between map coordinates and device coo...
QColor selectionColor() const
Returns the color to use when rendering selected features.
void setMapToPixel(const QgsMapToPixel &mtp)
Sets the context's map to pixel transform, which transforms between map coordinates and device coordi...
QgsFeedback * feedback() const
Returns the feedback object that can be queried regularly during rendering to check if rendering shou...
void setPainter(QPainter *p)
Sets the destination QPainter for the render operation.
QgsCoordinateTransform coordinateTransform() const
Returns the current coordinate transform for the context.
const QgsPathResolver & pathResolver() const
Returns the path resolver for conversion between relative and absolute paths during rendering operati...
static bool equalToOrGreaterThanMinimumScale(const double scale, const double minScale)
Returns whether the scale is equal to or greater than the minScale, taking non-round numbers into acc...
static bool lessThanMaximumScale(const double scale, const double maxScale)
Returns whether the scale is less than the maxScale, taking non-round numbers into account.
Stores properties relating to a screen.
double devicePixelRatio() const
Returns the ratio between physical pixels and device-independent pixels for the screen.
bool isValid() const
Returns true if the properties are valid.
void updateRenderContextForScreen(QgsRenderContext &context) const
Updates the settings in a render context to match the screen settings.
static QString wordWrap(const QString &string, int length, bool useMaxLineLength=true, const QString &customDelimiter=QString())
Automatically wraps a string by inserting new line characters at appropriate locations in the string.
QgsTextFormat defaultTextFormat(QgsStyle::TextFormatContext context=QgsStyle::TextFormatContext::Labeling) const
Returns the default text format to use for new text based objects in the specified context.
static QgsStyle * defaultStyle(bool initialize=true)
Returns the default application-wide style.
Definition qgsstyle.cpp:164
@ Labeling
Text format used in labeling.
Definition qgsstyle.h:828
static Qt::PenJoinStyle decodePenJoinStyle(const QString &str)
static QString encodeMapUnitScale(const QgsMapUnitScale &mapUnitScale)
static QPointF toPoint(const QVariant &value, bool *ok=nullptr)
Converts a value to a point.
static QgsMapUnitScale decodeMapUnitScale(const QString &str)
static QSizeF toSize(const QVariant &value, bool *ok=nullptr)
Converts a value to a size.
static QPainter::CompositionMode decodeBlendMode(const QString &s)
static QString encodeSize(QSizeF size)
Encodes a QSizeF to a string.
static QString svgSymbolNameToPath(const QString &name, const QgsPathResolver &pathResolver)
Determines an SVG symbol's path from its name.
static QString encodePoint(QPointF point)
Encodes a QPointF to a string.
static QString encodePenJoinStyle(Qt::PenJoinStyle style)
Abstract base class for all rendered symbols.
Definition qgssymbol.h:227
void setRadiiUnit(Qgis::RenderUnit units)
Sets the units used for the shape's radii.
QString svgFile() const
Returns the absolute path to the background SVG file, if set.
QSizeF size() const
Returns the size of the background shape.
QSizeF radii() const
Returns the radii used for rounding the corners of shapes.
void setOpacity(double opacity)
Sets the background shape's opacity.
void setStrokeColor(const QColor &color)
Sets the color used for outlining the background shape.
Qt::PenJoinStyle joinStyle() const
Returns the join style used for drawing the background shape.
SizeType
Methods for determining the background shape size.
bool enabled() const
Returns whether the background is enabled.
void setJoinStyle(Qt::PenJoinStyle style)
Sets the join style used for drawing the background shape.
double opacity() const
Returns the background shape's opacity.
double rotation() const
Returns the rotation for the background shape, in degrees clockwise.
QColor fillColor() const
Returns the color used for filing the background shape.
void setRadii(QSizeF radii)
Sets the radii used for rounding the corners of shapes.
SizeType sizeType() const
Returns the method used to determine the size of the background shape (e.g., fixed size or buffer aro...
ShapeType type() const
Returns the type of background shape (e.g., square, ellipse, SVG).
double strokeWidth() const
Returns the width of the shape's stroke (stroke).
void setSizeType(SizeType type)
Sets the method used to determine the size of the background shape (e.g., fixed size or buffer around...
void setFillColor(const QColor &color)
Sets the color used for filing the background shape.
void setSizeUnit(Qgis::RenderUnit unit)
Sets the units used for the shape's size.
QColor strokeColor() const
Returns the color used for outlining the background shape.
void setRotationType(RotationType type)
Sets the method used for rotating the background shape.
void setBlendMode(QPainter::CompositionMode mode)
Sets the blending mode used for drawing the background shape.
QgsMapUnitScale sizeMapUnitScale() const
Returns the map unit scale object for the shape size.
void setType(ShapeType type)
Sets the type of background shape to draw (e.g., square, ellipse, SVG).
Qgis::RenderUnit sizeUnit() const
Returns the units used for the shape's size.
RotationType
Methods for determining the rotation of the background shape.
void setEnabled(bool enabled)
Sets whether the text background will be drawn.
QgsMarkerSymbol * markerSymbol() const
Returns the marker symbol to be rendered in the background.
void setRotation(double rotation)
Sets the rotation for the background shape, in degrees clockwise.
void setStrokeWidthUnit(Qgis::RenderUnit units)
Sets the units used for the shape's stroke width.
void setOffsetUnit(Qgis::RenderUnit units)
Sets the units used for the shape's offset.
void setOffset(QPointF offset)
Sets the offset used for drawing the background shape.
void setSize(QSizeF size)
Sets the size of the background shape.
void setSvgFile(const QString &file)
Sets the path to the background SVG file.
void setStrokeWidth(double width)
Sets the width of the shape's stroke (stroke).
QPointF offset() const
Returns the offset used for drawing the background shape.
void setBlendMode(QPainter::CompositionMode mode)
Sets the blending mode used for drawing the buffer.
Qgis::RenderUnit sizeUnit() const
Returns the units for the buffer size.
Qt::PenJoinStyle joinStyle() const
Returns the buffer join style.
double size() const
Returns the size of the buffer.
void setColor(const QColor &color)
Sets the color for the buffer.
void setOpacity(double opacity)
Sets the buffer opacity.
QgsMapUnitScale sizeMapUnitScale() const
Returns the map unit scale object for the buffer size.
bool enabled() const
Returns whether the buffer is enabled.
double opacity() const
Returns the buffer opacity.
void setSizeUnit(Qgis::RenderUnit unit)
Sets the units used for the buffer size.
void setEnabled(bool enabled)
Sets whether the text buffer will be drawn.
QColor color() const
Returns the color of the buffer.
void setJoinStyle(Qt::PenJoinStyle style)
Sets the join style used for drawing the buffer.
void setSize(double size)
Sets the size of the buffer.
Contains pre-calculated metrics of a QgsTextDocument.
QSizeF documentSize(Qgis::TextLayoutMode mode, Qgis::TextOrientation orientation) const
Returns the overall size of the document.
static QgsTextDocumentMetrics calculateMetrics(const QgsTextDocument &document, const QgsTextFormat &format, const QgsRenderContext &context, double scaleFactor=1.0, const QgsTextDocumentRenderContext &documentContext=QgsTextDocumentRenderContext())
Returns precalculated text metrics for a text document, when rendered using the given base format and...
QRectF outerBounds(Qgis::TextLayoutMode mode, Qgis::TextOrientation orientation) const
Returns the outer bounds of the document, which is the documentSize() adjusted to account for any tex...
Represents a document consisting of one or more QgsTextBlock objects.
void splitLines(const QString &wrapCharacter, int autoWrapLength=0, bool useMaxLineLengthWhenAutoWrapping=true)
Splits lines of text in the document to separate lines, using a specified wrap character (wrapCharact...
QStringList toPlainText() const
Returns a list of plain text lines of text representing the document.
QVector< QgsTextDocument > splitBlocksToDocuments() const
Splits the text document, such that each block in the document becomes a separate document of its own...
static QgsTextDocument fromTextAndFormat(const QStringList &lines, const QgsTextFormat &format)
Constructor for QgsTextDocument consisting of a set of lines, respecting settings from a text format.
Container for all settings relating to text rendering.
void setColor(const QColor &color)
Sets the color that text will be rendered in.
void setBlendMode(QPainter::CompositionMode mode)
Sets the blending mode used for drawing the text.
void setSize(double size)
Sets the size for rendered text.
QgsMapUnitScale sizeMapUnitScale() const
Returns the map unit scale object for the size.
void setCapitalization(Qgis::Capitalization capitalization)
Sets the text capitalization style.
void setOrientation(Qgis::TextOrientation orientation)
Sets the orientation for the text.
void setFont(const QFont &font)
Sets the font used for rendering text.
void setSizeUnit(Qgis::RenderUnit unit)
Sets the units for the size of rendered text.
void setShadow(const QgsTextShadowSettings &shadowSettings)
Sets the text's drop shadow settings.
void setMask(const QgsTextMaskSettings &maskSettings)
Sets the text's masking settings.
void setOpacity(double opacity)
Sets the text's opacity.
Qgis::Capitalization capitalization() const
Returns the text capitalization style.
QgsTextMaskSettings & mask()
Returns a reference to the masking settings.
void setBuffer(const QgsTextBufferSettings &bufferSettings)
Sets the text's buffer settings.
QgsTextBackgroundSettings & background()
Returns a reference to the text background settings.
Qgis::RenderUnit sizeUnit() const
Returns the units for the size of rendered text.
void setTabStopDistance(double distance)
Sets the distance for tab stops.
double size() const
Returns the size for rendered text.
QgsTextShadowSettings & shadow()
Returns a reference to the text drop shadow settings.
void setTabPositions(const QList< QgsTextFormat::Tab > &positions)
Sets the list of tab positions for tab stops.
void setBackground(const QgsTextBackgroundSettings &backgroundSettings)
Sets the text's background settings.q.
QFont font() const
Returns the font used for rendering text.
QColor previewBackgroundColor() const
Returns the background color for text previews.
QgsTextBufferSettings & buffer()
Returns a reference to the text buffer settings.
void setLineHeight(double height)
Sets the line height for text.
void setEnabled(bool)
Returns whether the mask is enabled.
void setSize(double size)
Sets the size of the buffer.
double size() const
Returns the size of the buffer.
void setJoinStyle(Qt::PenJoinStyle style)
Sets the join style used for drawing the buffer.
double opacity() const
Returns the mask's opacity.
bool enabled() const
Returns whether the mask is enabled.
void setSizeUnit(Qgis::RenderUnit unit)
Sets the units used for the buffer size.
Qt::PenJoinStyle joinStyle() const
Returns the buffer join style.
void setOpacity(double opacity)
Sets the mask's opacity.
static QgsTextBackgroundSettings::ShapeType decodeShapeType(const QString &string)
Decodes a string representation of a background shape type to a type.
static Qgis::TextOrientation decodeTextOrientation(const QString &name, bool *ok=nullptr)
Attempts to decode a string representation of a text orientation.
static QgsTextShadowSettings::ShadowPlacement decodeShadowPlacementType(const QString &string)
Decodes a string representation of a shadow placement type to a type.
static QgsTextBackgroundSettings::RotationType decodeBackgroundRotationType(const QString &string)
Decodes a string representation of a background rotation type to a type.
static QString encodeTextOrientation(Qgis::TextOrientation orientation)
Encodes a text orientation.
static QgsTextBackgroundSettings::SizeType decodeBackgroundSizeType(const QString &string)
Decodes a string representation of a background size type to a type.
static double textWidth(const QgsRenderContext &context, const QgsTextFormat &format, const QStringList &textLines, QFontMetricsF *fontMetrics=nullptr)
Returns the width of a text based on a given format.
static int sizeToPixel(double size, const QgsRenderContext &c, Qgis::RenderUnit unit, const QgsMapUnitScale &mapUnitScale=QgsMapUnitScale())
Calculates pixel size (considering output size should be in pixel or map units, scale factors and opt...
static void drawText(const QRectF &rect, double rotation, Qgis::TextHorizontalAlignment alignment, const QStringList &textLines, QgsRenderContext &context, const QgsTextFormat &format, bool drawAsOutlines=true, Qgis::TextVerticalAlignment vAlignment=Qgis::TextVerticalAlignment::Top, Qgis::TextRendererFlags flags=Qgis::TextRendererFlags(), Qgis::TextLayoutMode mode=Qgis::TextLayoutMode::Rectangle)
Draws text within a rectangle using the specified settings.
static double textHeight(const QgsRenderContext &context, const QgsTextFormat &format, const QStringList &textLines, Qgis::TextLayoutMode mode=Qgis::TextLayoutMode::Point, QFontMetricsF *fontMetrics=nullptr, Qgis::TextRendererFlags flags=Qgis::TextRendererFlags(), double maxLineWidth=0)
Returns the height of a text based on a given format.
int offsetAngle() const
Returns the angle for offsetting the position of the shadow from the text.
void setBlurRadiusUnit(Qgis::RenderUnit units)
Sets the units used for the shadow's blur radius.
bool enabled() const
Returns whether the shadow is enabled.
void setOffsetUnit(Qgis::RenderUnit units)
Sets the units used for the shadow's offset.
int scale() const
Returns the scaling used for the drop shadow (in percentage of original size).
void setShadowPlacement(QgsTextShadowSettings::ShadowPlacement placement)
Sets the placement for the drop shadow.
double opacity() const
Returns the shadow's opacity.
void setBlendMode(QPainter::CompositionMode mode)
Sets the blending mode used for drawing the drop shadow.
void setColor(const QColor &color)
Sets the color for the drop shadow.
QColor color() const
Returns the color of the drop shadow.
ShadowPlacement
Placement positions for text shadow.
void setScale(int scale)
Sets the scaling used for the drop shadow (in percentage of original size).
double offsetDistance() const
Returns the distance for offsetting the position of the shadow from the text.
void setOffsetDistance(double distance)
Sets the distance for offsetting the position of the shadow from the text.
void setOpacity(double opacity)
Sets the shadow's opacity.
void setOffsetAngle(int angle)
Sets the angle for offsetting the position of the shadow from the text.
double blurRadius() const
Returns the blur radius for the shadow.
void setBlurRadius(double blurRadius)
Sets the blur radius for the shadow.
void setEnabled(bool enabled)
Sets whether the text shadow will be drawn.
static Q_INVOKABLE double fromUnitToUnitFactor(Qgis::DistanceUnit fromUnit, Qgis::DistanceUnit toUnit)
Returns the conversion factor between the specified distance units.
static Q_INVOKABLE Qgis::RenderUnit decodeRenderUnit(const QString &string, bool *ok=nullptr)
Decodes a render unit from a string.
static Q_INVOKABLE QString encodeUnit(Qgis::DistanceUnit unit)
Encodes a distance unit to a string.
static bool isNull(const QVariant &variant, bool silenceNullWarnings=false)
Returns true if the specified variant should be considered a NULL value.
Represents a vector layer which manages a vector based dataset.
bool labelsEnabled() const
Returns whether the layer contains labels which are enabled and should be drawn.
bool diagramsEnabled() const
Returns whether the layer contains diagrams which are enabled and should be drawn.
const QgsAbstractVectorLayerLabeling * labeling() const
Access to const labeling configuration.
QgsFeatureRenderer * renderer()
Returns the feature renderer used for rendering the features in the layer in 2D map views.
Q_INVOKABLE Qgis::GeometryType geometryType() const
Returns point, line or polygon.
const QgsDiagramRenderer * diagramRenderer() const
double tolerance() const
Gets the tolerance of simplification in map units. Represents the maximum distance in map units betwe...
bool forceLocalOptimization() const
Gets where the simplification executes, after fetch the geometries from provider, or when supported,...
Qgis::VectorRenderingSimplificationFlags simplifyHints() const
Gets the simplification hints of the vector layer managed.
Qgis::VectorSimplificationAlgorithm simplifyAlgorithm() const
Gets the local simplification algorithm of the vector layer managed.
Basic labeling configuration for vector tile layers.
Implements a map layer that is dedicated to rendering of vector tiles.
QgsVectorTileLabeling * labeling() const
Returns currently assigned labeling.
std::unique_ptr< GEOSGeometry, GeosDeleter > unique_ptr
Scoped GEOS pointer.
Definition qgsgeos.h:165
T qgsEnumKeyToValue(const QString &key, const T &defaultValue, bool tryValueAsKey=true, bool *returnOk=nullptr)
Returns the value corresponding to the given key of an enum.
Definition qgis.h:8088
#define Q_NOWARN_DEPRECATED_POP
Definition qgis.h:8431
QString qgsEnumValueToKey(const T &value, bool *returnOk=nullptr)
Returns the value for the given key of an enum.
Definition qgis.h:8069
#define Q_NOWARN_DEPRECATED_PUSH
Definition qgis.h:8430
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference).
Definition qgis.h:7795
T qgsgeometry_cast(QgsAbstractGeometry *geom)
constexpr QgsDataDefinedStringDecoder< V, N > makeDataDefinedStringDecoder(const DataDefinedDecoderEntry< V >(&values)[N])
Creates a QgsDataDefinedStringDecoder from a hardcoded lookup table.
Q_GLOBAL_STATIC(QReadWriteLock, sDefinitionCacheLock)
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:80
#define QgsDebugError(str)
Definition qgslogger.h:71
QString updateDataDefinedString(const QString &value)
QVector< Qgis::LabelPredefinedPointPosition > PredefinedPointPositionVector
Q_GLOBAL_STATIC_WITH_ARGS(PredefinedPointPositionVector, DEFAULT_PLACEMENT_ORDER,({ Qgis::LabelPredefinedPointPosition::TopRight, Qgis::LabelPredefinedPointPosition::TopLeft, Qgis::LabelPredefinedPointPosition::BottomRight, Qgis::LabelPredefinedPointPosition::BottomLeft, Qgis::LabelPredefinedPointPosition::MiddleRight, Qgis::LabelPredefinedPointPosition::MiddleLeft, Qgis::LabelPredefinedPointPosition::TopSlightlyRight, Qgis::LabelPredefinedPointPosition::BottomSlightlyRight })) void QgsPalLayerSettings
QMap< int, QgsPropertyDefinition > QgsPropertiesDefinition
Definition of available properties.