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