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