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