QGIS API Documentation 3.28.14-Firenze (exported)
Loading...
Searching...
No Matches
qgspallabeling.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgspallabeling.cpp
3 Smart labeling for vector layers
4 -------------------
5 begin : June 2009
6 copyright : (C) Martin Dobias
7 email : wonder dot sk at gmail dot com
8
9 ***************************************************************************
10 * *
11 * This program is free software; you can redistribute it and/or modify *
12 * it under the terms of the GNU General Public License as published by *
13 * the Free Software Foundation; either version 2 of the License, or *
14 * (at your option) any later version. *
15 * *
16 ***************************************************************************/
17
18#include "qgspallabeling.h"
19#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
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 {
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
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() ? QgsUnitTypes::RenderMapUnits : QgsUnitTypes::RenderMillimeters;
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() )
787 else
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" ), QgsUnitTypes::AngleDegrees );
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:
824 break;
825 case 1:
827 break;
828 case 2:
830 break;
831 case 3:
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() )
997 else
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() ? QgsUnitTypes::RenderMapUnits : QgsUnitTypes::RenderMillimeters;
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 mRotationUnit = qgsEnumKeyToValue( placementElem.attribute( QStringLiteral( "rotationUnit" ), qgsEnumValueToKey( QgsUnitTypes::AngleDegrees ) ), QgsUnitTypes::AngleDegrees );
1053 maxCurvedCharAngleIn = placementElem.attribute( QStringLiteral( "maxCurvedCharAngleIn" ), QStringLiteral( "25" ) ).toDouble();
1054 maxCurvedCharAngleOut = placementElem.attribute( QStringLiteral( "maxCurvedCharAngleOut" ), QStringLiteral( "-25" ) ).toDouble();
1055 priority = placementElem.attribute( QStringLiteral( "priority" ) ).toInt();
1056 repeatDistance = placementElem.attribute( QStringLiteral( "repeatDistance" ), QStringLiteral( "0" ) ).toDouble();
1057 if ( !placementElem.hasAttribute( QStringLiteral( "repeatDistanceUnits" ) ) )
1058 {
1059 // upgrade old setting
1060 switch ( placementElem.attribute( QStringLiteral( "repeatDistanceUnit" ), QString::number( 1 ) ).toUInt() )
1061 {
1062 case 0:
1064 break;
1065 case 1:
1067 break;
1068 case 2:
1070 break;
1071 case 3:
1073 break;
1074 }
1075 }
1076 else
1077 {
1078 repeatDistanceUnit = QgsUnitTypes::decodeRenderUnit( placementElem.attribute( QStringLiteral( "repeatDistanceUnits" ) ) );
1079 }
1080 if ( !placementElem.hasAttribute( QStringLiteral( "repeatDistanceMapUnitScale" ) ) )
1081 {
1082 //fallback to older property
1083 double oldMin = placementElem.attribute( QStringLiteral( "repeatDistanceMapUnitMinScale" ), QStringLiteral( "0" ) ).toDouble();
1084 repeatDistanceMapUnitScale.minScale = !qgsDoubleNear( oldMin, 0.0 ) ? 1.0 / oldMin : 0;
1085 double oldMax = placementElem.attribute( QStringLiteral( "repeatDistanceMapUnitMaxScale" ), QStringLiteral( "0" ) ).toDouble();
1086 repeatDistanceMapUnitScale.maxScale = !qgsDoubleNear( oldMax, 0.0 ) ? 1.0 / oldMax : 0;
1087 }
1088 else
1089 {
1090 repeatDistanceMapUnitScale = QgsSymbolLayerUtils::decodeMapUnitScale( placementElem.attribute( QStringLiteral( "repeatDistanceMapUnitScale" ) ) );
1091 }
1092
1093 mLineSettings.setOverrunDistance( placementElem.attribute( QStringLiteral( "overrunDistance" ), QStringLiteral( "0" ) ).toDouble() );
1094 mLineSettings.setOverrunDistanceUnit( QgsUnitTypes::decodeRenderUnit( placementElem.attribute( QStringLiteral( "overrunDistanceUnit" ) ) ) );
1095 mLineSettings.setOverrunDistanceMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( placementElem.attribute( QStringLiteral( "overrunDistanceMapUnitScale" ) ) ) );
1096 mLineSettings.setLineAnchorPercent( placementElem.attribute( QStringLiteral( "lineAnchorPercent" ), QStringLiteral( "0.5" ) ).toDouble() );
1097 mLineSettings.setAnchorType( static_cast< QgsLabelLineSettings::AnchorType >( placementElem.attribute( QStringLiteral( "lineAnchorType" ), QStringLiteral( "0" ) ).toInt() ) );
1098 mLineSettings.setAnchorClipping( static_cast< QgsLabelLineSettings::AnchorClipping >( placementElem.attribute( QStringLiteral( "lineAnchorClipping" ), QStringLiteral( "0" ) ).toInt() ) );
1099 // when reading the anchor text point we default to center mode, to keep same result as for proejcts created in < 3.26
1100 mLineSettings.setAnchorTextPoint( qgsEnumKeyToValue( placementElem.attribute( QStringLiteral( "lineAnchorTextPoint" ) ), QgsLabelLineSettings::AnchorTextPoint::CenterOfText ) );
1101
1102 geometryGenerator = placementElem.attribute( QStringLiteral( "geometryGenerator" ) );
1103 geometryGeneratorEnabled = placementElem.attribute( QStringLiteral( "geometryGeneratorEnabled" ) ).toInt();
1104 geometryGeneratorType = qgsEnumKeyToValue( placementElem.attribute( QStringLiteral( "geometryGeneratorType" ) ), QgsWkbTypes::PointGeometry );
1105
1106 layerType = qgsEnumKeyToValue( placementElem.attribute( QStringLiteral( "layerType" ) ), QgsWkbTypes::UnknownGeometry );
1107
1108 mPlacementSettings.setAllowDegradedPlacement( placementElem.attribute( QStringLiteral( "allowDegraded" ), QStringLiteral( "0" ) ).toInt() );
1109
1110 // rendering
1111 QDomElement renderingElem = elem.firstChildElement( QStringLiteral( "rendering" ) );
1112
1113 drawLabels = renderingElem.attribute( QStringLiteral( "drawLabels" ), QStringLiteral( "1" ) ).toInt();
1114
1115 maximumScale = renderingElem.attribute( QStringLiteral( "scaleMin" ), QStringLiteral( "0" ) ).toDouble();
1116 minimumScale = renderingElem.attribute( QStringLiteral( "scaleMax" ), QStringLiteral( "0" ) ).toDouble();
1117 scaleVisibility = renderingElem.attribute( QStringLiteral( "scaleVisibility" ) ).toInt();
1118
1119 fontLimitPixelSize = renderingElem.attribute( QStringLiteral( "fontLimitPixelSize" ), QStringLiteral( "0" ) ).toInt();
1120 fontMinPixelSize = renderingElem.attribute( QStringLiteral( "fontMinPixelSize" ), QStringLiteral( "0" ) ).toInt();
1121 fontMaxPixelSize = renderingElem.attribute( QStringLiteral( "fontMaxPixelSize" ), QStringLiteral( "10000" ) ).toInt();
1122
1123 if ( placementElem.hasAttribute( QStringLiteral( "overlapHandling" ) ) )
1124 {
1125 mPlacementSettings.setOverlapHandling( qgsEnumKeyToValue( placementElem.attribute( QStringLiteral( "overlapHandling" ) ), Qgis::LabelOverlapHandling::PreventOverlap ) );
1126 }
1127 else
1128 {
1129 // legacy setting
1130 if ( renderingElem.attribute( QStringLiteral( "displayAll" ), QStringLiteral( "0" ) ).toInt() )
1131 {
1133 mPlacementSettings.setAllowDegradedPlacement( true );
1134 }
1135 else
1136 {
1138 mPlacementSettings.setAllowDegradedPlacement( false );
1139 }
1140 }
1141 upsidedownLabels = static_cast< Qgis::UpsideDownLabelHandling >( renderingElem.attribute( QStringLiteral( "upsidedownLabels" ), QString::number( static_cast< int >( Qgis::UpsideDownLabelHandling::FlipUpsideDownLabels ) ) ).toUInt() );
1142
1143 labelPerPart = renderingElem.attribute( QStringLiteral( "labelPerPart" ) ).toInt();
1144 mLineSettings.setMergeLines( renderingElem.attribute( QStringLiteral( "mergeLines" ) ).toInt() );
1145 mThinningSettings.setMinimumFeatureSize( renderingElem.attribute( QStringLiteral( "minFeatureSize" ) ).toDouble() );
1146 mThinningSettings.setLimitNumberLabelsEnabled( renderingElem.attribute( QStringLiteral( "limitNumLabels" ), QStringLiteral( "0" ) ).toInt() );
1147 mThinningSettings.setMaximumNumberLabels( renderingElem.attribute( QStringLiteral( "maxNumLabels" ), QStringLiteral( "2000" ) ).toInt() );
1148 mObstacleSettings.setIsObstacle( renderingElem.attribute( QStringLiteral( "obstacle" ), QStringLiteral( "1" ) ).toInt() );
1149 mObstacleSettings.setFactor( renderingElem.attribute( QStringLiteral( "obstacleFactor" ), QStringLiteral( "1" ) ).toDouble() );
1150 mObstacleSettings.setType( static_cast< QgsLabelObstacleSettings::ObstacleType >( renderingElem.attribute( QStringLiteral( "obstacleType" ), QString::number( PolygonInterior ) ).toUInt() ) );
1151 zIndex = renderingElem.attribute( QStringLiteral( "zIndex" ), QStringLiteral( "0.0" ) ).toDouble();
1152 mUnplacedVisibility = static_cast< Qgis::UnplacedLabelVisibility >( renderingElem.attribute( QStringLiteral( "unplacedVisibility" ), QString::number( static_cast< int >( Qgis::UnplacedLabelVisibility::FollowEngineSetting ) ) ).toInt() );
1153
1154 QDomElement ddElem = elem.firstChildElement( QStringLiteral( "dd_properties" ) );
1155 if ( !ddElem.isNull() )
1156 {
1157 mDataDefinedProperties.readXml( ddElem, *sPropertyDefinitions() );
1158 }
1159 else
1160 {
1161 // upgrade 2.x style dd project
1162 mDataDefinedProperties.clear();
1163 QDomElement ddElem = elem.firstChildElement( QStringLiteral( "data-defined" ) );
1164 readOldDataDefinedPropertyMap( nullptr, &ddElem );
1165 }
1166 // upgrade older data defined settings
1167 if ( mDataDefinedProperties.isActive( FontTransp ) )
1168 {
1169 mDataDefinedProperties.setProperty( FontOpacity, QgsProperty::fromExpression( QStringLiteral( "100 - (%1)" ).arg( mDataDefinedProperties.property( FontTransp ).asExpression() ) ) );
1170 mDataDefinedProperties.setProperty( FontTransp, QgsProperty() );
1171 }
1172 if ( mDataDefinedProperties.isActive( BufferTransp ) )
1173 {
1174 mDataDefinedProperties.setProperty( BufferOpacity, QgsProperty::fromExpression( QStringLiteral( "100 - (%1)" ).arg( mDataDefinedProperties.property( BufferTransp ).asExpression() ) ) );
1175 mDataDefinedProperties.setProperty( BufferTransp, QgsProperty() );
1176 }
1177 if ( mDataDefinedProperties.isActive( ShapeTransparency ) )
1178 {
1179 mDataDefinedProperties.setProperty( ShapeOpacity, QgsProperty::fromExpression( QStringLiteral( "100 - (%1)" ).arg( mDataDefinedProperties.property( ShapeTransparency ).asExpression() ) ) );
1180 mDataDefinedProperties.setProperty( ShapeTransparency, QgsProperty() );
1181 }
1182 if ( mDataDefinedProperties.isActive( ShadowTransparency ) )
1183 {
1184 mDataDefinedProperties.setProperty( ShadowOpacity, QgsProperty::fromExpression( QStringLiteral( "100 - (%1)" ).arg( mDataDefinedProperties.property( ShadowTransparency ).asExpression() ) ) );
1185 mDataDefinedProperties.setProperty( ShadowTransparency, QgsProperty() );
1186 }
1187 if ( mDataDefinedProperties.isActive( Rotation ) )
1188 {
1189 mDataDefinedProperties.setProperty( LabelRotation, QgsProperty::fromExpression( QStringLiteral( "360 - (%1)" ).arg( mDataDefinedProperties.property( Rotation ).asExpression() ) ) );
1190 mDataDefinedProperties.setProperty( Rotation, QgsProperty() );
1191 }
1192 // older 2.x projects had min/max scale flipped - so change them here.
1193 if ( mDataDefinedProperties.isActive( MinScale ) )
1194 {
1195 mDataDefinedProperties.setProperty( MaximumScale, mDataDefinedProperties.property( MinScale ) );
1196 mDataDefinedProperties.setProperty( MinScale, QgsProperty() );
1197 }
1198 if ( mDataDefinedProperties.isActive( MaxScale ) )
1199 {
1200 mDataDefinedProperties.setProperty( MinimumScale, mDataDefinedProperties.property( MaxScale ) );
1201 mDataDefinedProperties.setProperty( MaxScale, QgsProperty() );
1202 }
1203
1204 // TODO - replace with registry when multiple callout styles exist
1205 const QString calloutType = elem.attribute( QStringLiteral( "calloutType" ) );
1206 if ( calloutType.isEmpty() )
1207 mCallout.reset( QgsCalloutRegistry::defaultCallout() );
1208 else
1209 {
1210 mCallout.reset( QgsApplication::calloutRegistry()->createCallout( calloutType, elem.firstChildElement( QStringLiteral( "callout" ) ), context ) );
1211 if ( !mCallout )
1212 mCallout.reset( QgsCalloutRegistry::defaultCallout() );
1213 }
1214}
1215
1216QDomElement QgsPalLayerSettings::writeXml( QDomDocument &doc, const QgsReadWriteContext &context ) const
1217{
1218 QDomElement textStyleElem = mFormat.writeXml( doc, context );
1219
1220 // text style
1221 textStyleElem.setAttribute( QStringLiteral( "fieldName" ), fieldName );
1222 textStyleElem.setAttribute( QStringLiteral( "isExpression" ), isExpression );
1223 QDomElement replacementElem = doc.createElement( QStringLiteral( "substitutions" ) );
1224 substitutions.writeXml( replacementElem, doc );
1225 textStyleElem.appendChild( replacementElem );
1226 textStyleElem.setAttribute( QStringLiteral( "useSubstitutions" ), useSubstitutions );
1227 textStyleElem.setAttribute( QStringLiteral( "legendString" ), mLegendString );
1228
1229 // text formatting
1230 QDomElement textFormatElem = doc.createElement( QStringLiteral( "text-format" ) );
1231 textFormatElem.setAttribute( QStringLiteral( "wrapChar" ), wrapChar );
1232 textFormatElem.setAttribute( QStringLiteral( "autoWrapLength" ), autoWrapLength );
1233 textFormatElem.setAttribute( QStringLiteral( "useMaxLineLengthForAutoWrap" ), useMaxLineLengthForAutoWrap );
1234 textFormatElem.setAttribute( QStringLiteral( "multilineAlign" ), static_cast< unsigned int >( multilineAlign ) );
1235 textFormatElem.setAttribute( QStringLiteral( "addDirectionSymbol" ), mLineSettings.addDirectionSymbol() );
1236 textFormatElem.setAttribute( QStringLiteral( "leftDirectionSymbol" ), mLineSettings.leftDirectionSymbol() );
1237 textFormatElem.setAttribute( QStringLiteral( "rightDirectionSymbol" ), mLineSettings.rightDirectionSymbol() );
1238 textFormatElem.setAttribute( QStringLiteral( "reverseDirectionSymbol" ), mLineSettings.reverseDirectionSymbol() );
1239 textFormatElem.setAttribute( QStringLiteral( "placeDirectionSymbol" ), static_cast< unsigned int >( mLineSettings.directionSymbolPlacement() ) );
1240 textFormatElem.setAttribute( QStringLiteral( "formatNumbers" ), formatNumbers );
1241 textFormatElem.setAttribute( QStringLiteral( "decimals" ), decimals );
1242 textFormatElem.setAttribute( QStringLiteral( "plussign" ), plusSign );
1243
1244 // placement
1245 QDomElement placementElem = doc.createElement( QStringLiteral( "placement" ) );
1246 placementElem.setAttribute( QStringLiteral( "placement" ), static_cast< int >( placement ) );
1247 placementElem.setAttribute( QStringLiteral( "polygonPlacementFlags" ), static_cast< int >( mPolygonPlacementFlags ) );
1248 placementElem.setAttribute( QStringLiteral( "placementFlags" ), static_cast< unsigned int >( mLineSettings.placementFlags() ) );
1249 placementElem.setAttribute( QStringLiteral( "centroidWhole" ), centroidWhole );
1250 placementElem.setAttribute( QStringLiteral( "centroidInside" ), centroidInside );
1251 placementElem.setAttribute( QStringLiteral( "predefinedPositionOrder" ), QgsLabelingUtils::encodePredefinedPositionOrder( predefinedPositionOrder ) );
1252 placementElem.setAttribute( QStringLiteral( "fitInPolygonOnly" ), fitInPolygonOnly );
1253 placementElem.setAttribute( QStringLiteral( "dist" ), dist );
1254 placementElem.setAttribute( QStringLiteral( "distUnits" ), QgsUnitTypes::encodeUnit( distUnits ) );
1255 placementElem.setAttribute( QStringLiteral( "distMapUnitScale" ), QgsSymbolLayerUtils::encodeMapUnitScale( distMapUnitScale ) );
1256 placementElem.setAttribute( QStringLiteral( "offsetType" ), static_cast< unsigned int >( offsetType ) );
1257 placementElem.setAttribute( QStringLiteral( "quadOffset" ), static_cast< unsigned int >( quadOffset ) );
1258 placementElem.setAttribute( QStringLiteral( "xOffset" ), xOffset );
1259 placementElem.setAttribute( QStringLiteral( "yOffset" ), yOffset );
1260 placementElem.setAttribute( QStringLiteral( "offsetUnits" ), QgsUnitTypes::encodeUnit( offsetUnits ) );
1261 placementElem.setAttribute( QStringLiteral( "labelOffsetMapUnitScale" ), QgsSymbolLayerUtils::encodeMapUnitScale( labelOffsetMapUnitScale ) );
1262 placementElem.setAttribute( QStringLiteral( "rotationAngle" ), angleOffset );
1263 placementElem.setAttribute( QStringLiteral( "preserveRotation" ), preserveRotation );
1264 placementElem.setAttribute( QStringLiteral( "rotationUnit" ), qgsEnumValueToKey( mRotationUnit ) );
1265 placementElem.setAttribute( QStringLiteral( "maxCurvedCharAngleIn" ), maxCurvedCharAngleIn );
1266 placementElem.setAttribute( QStringLiteral( "maxCurvedCharAngleOut" ), maxCurvedCharAngleOut );
1267 placementElem.setAttribute( QStringLiteral( "priority" ), priority );
1268 placementElem.setAttribute( QStringLiteral( "repeatDistance" ), repeatDistance );
1269 placementElem.setAttribute( QStringLiteral( "repeatDistanceUnits" ), QgsUnitTypes::encodeUnit( repeatDistanceUnit ) );
1270 placementElem.setAttribute( QStringLiteral( "repeatDistanceMapUnitScale" ), QgsSymbolLayerUtils::encodeMapUnitScale( repeatDistanceMapUnitScale ) );
1271 placementElem.setAttribute( QStringLiteral( "overrunDistance" ), mLineSettings.overrunDistance() );
1272 placementElem.setAttribute( QStringLiteral( "overrunDistanceUnit" ), QgsUnitTypes::encodeUnit( mLineSettings.overrunDistanceUnit() ) );
1273 placementElem.setAttribute( QStringLiteral( "overrunDistanceMapUnitScale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mLineSettings.overrunDistanceMapUnitScale() ) );
1274 placementElem.setAttribute( QStringLiteral( "lineAnchorPercent" ), mLineSettings.lineAnchorPercent() );
1275 placementElem.setAttribute( QStringLiteral( "lineAnchorType" ), static_cast< int >( mLineSettings.anchorType() ) );
1276 placementElem.setAttribute( QStringLiteral( "lineAnchorClipping" ), static_cast< int >( mLineSettings.anchorClipping() ) );
1277 placementElem.setAttribute( QStringLiteral( "lineAnchorTextPoint" ), qgsEnumValueToKey( mLineSettings.anchorTextPoint() ) );
1278
1279 placementElem.setAttribute( QStringLiteral( "geometryGenerator" ), geometryGenerator );
1280 placementElem.setAttribute( QStringLiteral( "geometryGeneratorEnabled" ), geometryGeneratorEnabled );
1281 const QMetaEnum metaEnum( QMetaEnum::fromType<QgsWkbTypes::GeometryType>() );
1282 placementElem.setAttribute( QStringLiteral( "geometryGeneratorType" ), metaEnum.valueToKey( geometryGeneratorType ) );
1283
1284 placementElem.setAttribute( QStringLiteral( "layerType" ), metaEnum.valueToKey( layerType ) );
1285
1286 placementElem.setAttribute( QStringLiteral( "overlapHandling" ), qgsEnumValueToKey( mPlacementSettings.overlapHandling() ) );
1287 placementElem.setAttribute( QStringLiteral( "allowDegraded" ), mPlacementSettings.allowDegradedPlacement() ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
1288
1289 // rendering
1290 QDomElement renderingElem = doc.createElement( QStringLiteral( "rendering" ) );
1291 renderingElem.setAttribute( QStringLiteral( "drawLabels" ), drawLabels );
1292 renderingElem.setAttribute( QStringLiteral( "scaleVisibility" ), scaleVisibility );
1293 renderingElem.setAttribute( QStringLiteral( "scaleMin" ), maximumScale );
1294 renderingElem.setAttribute( QStringLiteral( "scaleMax" ), minimumScale );
1295 renderingElem.setAttribute( QStringLiteral( "fontLimitPixelSize" ), fontLimitPixelSize );
1296 renderingElem.setAttribute( QStringLiteral( "fontMinPixelSize" ), fontMinPixelSize );
1297 renderingElem.setAttribute( QStringLiteral( "fontMaxPixelSize" ), fontMaxPixelSize );
1298 renderingElem.setAttribute( QStringLiteral( "upsidedownLabels" ), static_cast< unsigned int >( upsidedownLabels ) );
1299
1300 renderingElem.setAttribute( QStringLiteral( "labelPerPart" ), labelPerPart );
1301 renderingElem.setAttribute( QStringLiteral( "mergeLines" ), mLineSettings.mergeLines() );
1302 renderingElem.setAttribute( QStringLiteral( "minFeatureSize" ), mThinningSettings.minimumFeatureSize() );
1303 renderingElem.setAttribute( QStringLiteral( "limitNumLabels" ), mThinningSettings.limitNumberOfLabelsEnabled() );
1304 renderingElem.setAttribute( QStringLiteral( "maxNumLabels" ), mThinningSettings.maximumNumberLabels() );
1305 renderingElem.setAttribute( QStringLiteral( "obstacle" ), mObstacleSettings.isObstacle() );
1306 renderingElem.setAttribute( QStringLiteral( "obstacleFactor" ), mObstacleSettings.factor() );
1307 renderingElem.setAttribute( QStringLiteral( "obstacleType" ), static_cast< unsigned int >( mObstacleSettings.type() ) );
1308 renderingElem.setAttribute( QStringLiteral( "zIndex" ), zIndex );
1309 renderingElem.setAttribute( QStringLiteral( "unplacedVisibility" ), static_cast< int >( mUnplacedVisibility ) );
1310
1311 QDomElement ddElem = doc.createElement( QStringLiteral( "dd_properties" ) );
1312 mDataDefinedProperties.writeXml( ddElem, *sPropertyDefinitions() );
1313
1314 QDomElement elem = doc.createElement( QStringLiteral( "settings" ) );
1315 elem.appendChild( textStyleElem );
1316 elem.appendChild( textFormatElem );
1317 elem.appendChild( placementElem );
1318 elem.appendChild( renderingElem );
1319 elem.appendChild( ddElem );
1320
1321 if ( mCallout )
1322 {
1323 elem.setAttribute( QStringLiteral( "calloutType" ), mCallout->type() );
1324 mCallout->saveProperties( doc, elem, context );
1325 }
1326
1327 return elem;
1328}
1329
1331{
1332 mCallout.reset( callout );
1333}
1334
1335QPixmap QgsPalLayerSettings::labelSettingsPreviewPixmap( const QgsPalLayerSettings &settings, QSize size, const QString &previewText, int padding )
1336{
1337 // for now, just use format
1338 QgsTextFormat tempFormat = settings.format();
1339 QPixmap pixmap( size );
1340 pixmap.fill( Qt::transparent );
1341 QPainter painter;
1342 painter.begin( &pixmap );
1343
1344 painter.setRenderHint( QPainter::Antialiasing );
1345
1346 QRect rect( 0, 0, size.width(), size.height() );
1347
1348 // shameless eye candy - use a subtle gradient when drawing background
1349 painter.setPen( Qt::NoPen );
1350 QColor background1 = tempFormat.previewBackgroundColor();
1351 if ( ( background1.lightnessF() < 0.7 ) )
1352 {
1353 background1 = background1.darker( 125 );
1354 }
1355 else
1356 {
1357 background1 = background1.lighter( 125 );
1358 }
1359 QColor background2 = tempFormat.previewBackgroundColor();
1360 QLinearGradient linearGrad( QPointF( 0, 0 ), QPointF( 0, rect.height() ) );
1361 linearGrad.setColorAt( 0, background1 );
1362 linearGrad.setColorAt( 1, background2 );
1363 painter.setBrush( QBrush( linearGrad ) );
1364 if ( size.width() > 30 )
1365 {
1366 painter.drawRoundedRect( rect, 6, 6 );
1367 }
1368 else
1369 {
1370 // don't use rounded rect for small previews
1371 painter.drawRect( rect );
1372 }
1373 painter.setBrush( Qt::NoBrush );
1374 painter.setPen( Qt::NoPen );
1375 padding += 1; // move text away from background border
1376
1377 QgsRenderContext context;
1378 QgsMapToPixel newCoordXForm;
1379 newCoordXForm.setParameters( 1, 0, 0, 0, 0, 0 );
1380 context.setMapToPixel( newCoordXForm );
1382
1383 QWidget *activeWindow = QApplication::activeWindow();
1384 const double logicalDpiX = activeWindow && activeWindow->screen() ? activeWindow->screen()->logicalDotsPerInchX() : 96.0;
1385 context.setScaleFactor( logicalDpiX / 25.4 );
1386 context.setUseAdvancedEffects( true );
1387 context.setPainter( &painter );
1388
1389 // slightly inset text to account for buffer/background
1390 const double fontSize = context.convertToPainterUnits( tempFormat.size(), tempFormat.sizeUnit(), tempFormat.sizeMapUnitScale() );
1391 double xtrans = 0;
1392 if ( tempFormat.buffer().enabled() )
1393 xtrans = tempFormat.buffer().sizeUnit() == QgsUnitTypes::RenderPercentage
1394 ? fontSize * tempFormat.buffer().size() / 100
1395 : context.convertToPainterUnits( tempFormat.buffer().size(), tempFormat.buffer().sizeUnit(), tempFormat.buffer().sizeMapUnitScale() );
1396 if ( tempFormat.background().enabled() && tempFormat.background().sizeType() != QgsTextBackgroundSettings::SizeFixed )
1397 xtrans = std::max( xtrans, context.convertToPainterUnits( tempFormat.background().size().width(), tempFormat.background().sizeUnit(), tempFormat.background().sizeMapUnitScale() ) );
1398
1399 double ytrans = 0.0;
1400 if ( tempFormat.buffer().enabled() )
1401 ytrans = std::max( ytrans, tempFormat.buffer().sizeUnit() == QgsUnitTypes::RenderPercentage
1402 ? fontSize * tempFormat.buffer().size() / 100
1403 : context.convertToPainterUnits( tempFormat.buffer().size(), tempFormat.buffer().sizeUnit(), tempFormat.buffer().sizeMapUnitScale() ) );
1404 if ( tempFormat.background().enabled() )
1405 ytrans = std::max( ytrans, context.convertToPainterUnits( tempFormat.background().size().height(), tempFormat.background().sizeUnit(), tempFormat.background().sizeMapUnitScale() ) );
1406
1407 const QStringList text = QStringList() << ( previewText.isEmpty() ? settings.legendString() : previewText );
1408 const double textHeight = QgsTextRenderer::textHeight( context, tempFormat, text, Qgis::TextLayoutMode::Rectangle );
1409 QRectF textRect = rect;
1410 textRect.setLeft( xtrans + padding );
1411 textRect.setWidth( rect.width() - xtrans - 2 * padding );
1412
1413 if ( textRect.width() > 2000 )
1414 textRect.setWidth( 2000 - 2 * padding );
1415
1416 const double bottom = textRect.height() / 2 + textHeight / 2;
1417 textRect.setTop( bottom - textHeight );
1418 textRect.setBottom( bottom );
1419
1420 const double iconWidth = QFontMetricsF( QFont() ).horizontalAdvance( 'X' ) * Qgis::UI_SCALE_FACTOR;
1421
1422 if ( settings.callout() && settings.callout()->enabled() )
1423 {
1424 // draw callout preview
1425 const double textWidth = QgsTextRenderer::textWidth( context, tempFormat, text );
1426 QgsCallout *callout = settings.callout();
1427 callout->startRender( context );
1428 QgsCallout::QgsCalloutContext calloutContext;
1429 QRectF labelRect( textRect.left() + ( textRect.width() - textWidth ) / 2.0, textRect.top(), textWidth, textRect.height() );
1430 callout->render( context, labelRect, 0, QgsGeometry::fromPointXY( QgsPointXY( labelRect.left() - iconWidth * 1.5, labelRect.bottom() + iconWidth ) ), calloutContext );
1431 callout->stopRender( context );
1432 }
1433
1434 QgsTextRenderer::drawText( textRect, 0, Qgis::TextHorizontalAlignment::Center, text, context, tempFormat );
1435
1436 if ( size.width() > 30 )
1437 {
1438 // draw a label icon
1439
1440 QgsApplication::getThemeIcon( QStringLiteral( "labelingSingle.svg" ) ).paint( &painter, QRect(
1441 rect.width() - iconWidth * 3, rect.height() - iconWidth * 3,
1442 iconWidth * 2, iconWidth * 2 ), Qt::AlignRight | Qt::AlignBottom );
1443 }
1444
1445 // draw border on top of text
1446 painter.setBrush( Qt::NoBrush );
1447 painter.setPen( QPen( tempFormat.previewBackgroundColor().darker( 150 ), 0 ) );
1448 if ( size.width() > 30 )
1449 {
1450 painter.drawRoundedRect( rect, 6, 6 );
1451 }
1452 else
1453 {
1454 // don't use rounded rect for small previews
1455 painter.drawRect( rect );
1456 }
1457
1458 painter.end();
1459 return pixmap;
1460}
1461
1463{
1464 return mUnplacedVisibility;
1465}
1466
1468{
1469 mUnplacedVisibility = visibility;
1470}
1471
1472bool QgsPalLayerSettings::checkMinimumSizeMM( const QgsRenderContext &ct, const QgsGeometry &geom, double minSize ) const
1473{
1474 return QgsPalLabeling::checkMinimumSizeMM( ct, geom, minSize );
1475}
1476
1477void 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 )
1478{
1479 if ( !fm || !f )
1480 {
1481 return;
1482 }
1483
1484 QString textCopy( text );
1485
1486 //try to keep < 2.12 API - handle no passed render context
1487 std::unique_ptr< QgsRenderContext > scopedRc;
1488 if ( !context )
1489 {
1490 scopedRc.reset( new QgsRenderContext() );
1491 if ( f )
1492 scopedRc->expressionContext().setFeature( *f );
1493 }
1494 QgsRenderContext *rc = context ? context : scopedRc.get();
1495
1496 QString wrapchr = wrapChar;
1497 int evalAutoWrapLength = autoWrapLength;
1498 double multilineH = mFormat.lineHeight();
1499 Qgis::TextOrientation orientation = mFormat.orientation();
1500
1501 bool addDirSymb = mLineSettings.addDirectionSymbol();
1502 QString leftDirSymb = mLineSettings.leftDirectionSymbol();
1503 QString rightDirSymb = mLineSettings.rightDirectionSymbol();
1505
1506 if ( f == mCurFeat ) // called internally, use any stored data defined values
1507 {
1508 if ( dataDefinedValues.contains( QgsPalLayerSettings::MultiLineWrapChar ) )
1509 {
1510 wrapchr = dataDefinedValues.value( QgsPalLayerSettings::MultiLineWrapChar ).toString();
1511 }
1512
1513 if ( dataDefinedValues.contains( QgsPalLayerSettings::AutoWrapLength ) )
1514 {
1515 evalAutoWrapLength = dataDefinedValues.value( QgsPalLayerSettings::AutoWrapLength, evalAutoWrapLength ).toInt();
1516 }
1517
1518 if ( dataDefinedValues.contains( QgsPalLayerSettings::MultiLineHeight ) )
1519 {
1520 multilineH = dataDefinedValues.value( QgsPalLayerSettings::MultiLineHeight ).toDouble();
1521 }
1522
1523 if ( dataDefinedValues.contains( QgsPalLayerSettings::TextOrientation ) )
1524 {
1525 orientation = QgsTextRendererUtils::decodeTextOrientation( dataDefinedValues.value( QgsPalLayerSettings::TextOrientation ).toString() );
1526 }
1527
1528 if ( dataDefinedValues.contains( QgsPalLayerSettings::DirSymbDraw ) )
1529 {
1530 addDirSymb = dataDefinedValues.value( QgsPalLayerSettings::DirSymbDraw ).toBool();
1531 }
1532
1533 if ( addDirSymb )
1534 {
1535
1536 if ( dataDefinedValues.contains( QgsPalLayerSettings::DirSymbLeft ) )
1537 {
1538 leftDirSymb = dataDefinedValues.value( QgsPalLayerSettings::DirSymbLeft ).toString();
1539 }
1540 if ( dataDefinedValues.contains( QgsPalLayerSettings::DirSymbRight ) )
1541 {
1542 rightDirSymb = dataDefinedValues.value( QgsPalLayerSettings::DirSymbRight ).toString();
1543 }
1544
1545 if ( dataDefinedValues.contains( QgsPalLayerSettings::DirSymbPlacement ) )
1546 {
1547 placeDirSymb = static_cast< QgsLabelLineSettings::DirectionSymbolPlacement >( dataDefinedValues.value( QgsPalLayerSettings::DirSymbPlacement ).toInt() );
1548 }
1549
1550 }
1551
1552 }
1553 else // called externally with passed-in feature, evaluate data defined
1554 {
1555 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::MultiLineWrapChar ) )
1556 {
1558 wrapchr = mDataDefinedProperties.value( QgsPalLayerSettings::MultiLineWrapChar, rc->expressionContext(), wrapchr ).toString();
1559 }
1560
1561 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::AutoWrapLength ) )
1562 {
1563 rc->expressionContext().setOriginalValueVariable( evalAutoWrapLength );
1564 evalAutoWrapLength = mDataDefinedProperties.value( QgsPalLayerSettings::AutoWrapLength, rc->expressionContext(), evalAutoWrapLength ).toInt();
1565 }
1566
1567 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::MultiLineHeight ) )
1568 {
1569 rc->expressionContext().setOriginalValueVariable( multilineH );
1570 multilineH = mDataDefinedProperties.valueAsDouble( QgsPalLayerSettings::MultiLineHeight, rc->expressionContext(), multilineH );
1571 }
1572
1573 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::TextOrientation ) )
1574 {
1575 QString encoded = QgsTextRendererUtils::encodeTextOrientation( orientation );
1578 }
1579
1580 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::DirSymbDraw ) )
1581 {
1582 rc->expressionContext().setOriginalValueVariable( addDirSymb );
1583 addDirSymb = mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::DirSymbDraw, rc->expressionContext(), addDirSymb );
1584 }
1585
1586 if ( addDirSymb ) // don't do extra evaluations if not adding a direction symbol
1587 {
1588 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::DirSymbLeft ) )
1589 {
1590 rc->expressionContext().setOriginalValueVariable( leftDirSymb );
1591 leftDirSymb = mDataDefinedProperties.value( QgsPalLayerSettings::DirSymbLeft, rc->expressionContext(), leftDirSymb ).toString();
1592 }
1593
1594 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::DirSymbRight ) )
1595 {
1596 rc->expressionContext().setOriginalValueVariable( rightDirSymb );
1597 rightDirSymb = mDataDefinedProperties.value( QgsPalLayerSettings::DirSymbRight, rc->expressionContext(), rightDirSymb ).toString();
1598 }
1599
1600 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::DirSymbPlacement ) )
1601 {
1602 rc->expressionContext().setOriginalValueVariable( static_cast< int >( placeDirSymb ) );
1603 placeDirSymb = static_cast< QgsLabelLineSettings::DirectionSymbolPlacement >( mDataDefinedProperties.valueAsInt( QgsPalLayerSettings::DirSymbPlacement, rc->expressionContext(), static_cast< int >( placeDirSymb ) ) );
1604 }
1605 }
1606 }
1607
1608 if ( wrapchr.isEmpty() )
1609 {
1610 wrapchr = QStringLiteral( "\n" ); // default to new line delimiter
1611 }
1612
1613 //consider the space needed for the direction symbol
1614 if ( addDirSymb && placement == Qgis::LabelPlacement::Line
1615 && ( !leftDirSymb.isEmpty() || !rightDirSymb.isEmpty() ) )
1616 {
1617 QString dirSym = leftDirSymb;
1618
1619 if ( fm->horizontalAdvance( rightDirSymb ) > fm->horizontalAdvance( dirSym ) )
1620 dirSym = rightDirSymb;
1621
1622 switch ( placeDirSymb )
1623 {
1625 textCopy.append( dirSym );
1626 break;
1627
1630 textCopy.prepend( dirSym + QStringLiteral( "\n" ) );
1631 break;
1632 }
1633 }
1634
1635 double w = 0.0, h = 0.0, rw = 0.0, rh = 0.0;
1636 double labelHeight = fm->ascent() + fm->descent(); // ignore +1 for baseline
1637
1638 QStringList multiLineSplit;
1639
1640 if ( document )
1641 {
1642 document->splitLines( wrapchr, evalAutoWrapLength, useMaxLineLengthForAutoWrap );
1643
1644 *documentMetrics = QgsTextDocumentMetrics::calculateMetrics( *document, mFormat, *rc );
1645 const QSizeF size = documentMetrics->documentSize( Qgis::TextLayoutMode::Labeling, orientation );
1646 w = size.width();
1647 h = size.height();
1648 }
1649 else
1650 {
1651 const QStringList multiLineSplit = QgsPalLabeling::splitToLines( textCopy, wrapchr, evalAutoWrapLength, useMaxLineLengthForAutoWrap );
1652 const int lines = multiLineSplit.size();
1653
1654 const double lineHeightPainterUnits = rc->convertToPainterUnits( mFormat.lineHeight(), mFormat.lineHeightUnit() );
1655
1656 switch ( orientation )
1657 {
1659 {
1660 h += fm->height() + static_cast< double >( ( lines - 1 ) * ( mFormat.lineHeightUnit() == QgsUnitTypes::RenderPercentage ? ( labelHeight * multilineH ) : lineHeightPainterUnits ) );
1661
1662 for ( const QString &line : std::as_const( multiLineSplit ) )
1663 {
1664 w = std::max( w, fm->horizontalAdvance( line ) );
1665 }
1666 break;
1667 }
1668
1670 {
1671 double letterSpacing = mFormat.scaledFont( *context ).letterSpacing();
1672 double labelWidth = fm->maxWidth();
1673 w = labelWidth + ( lines - 1 ) * ( mFormat.lineHeightUnit() == QgsUnitTypes::RenderPercentage ? ( labelWidth * multilineH ) : lineHeightPainterUnits );
1674
1675 int maxLineLength = 0;
1676 for ( const QString &line : std::as_const( multiLineSplit ) )
1677 {
1678 maxLineLength = std::max( maxLineLength, static_cast<int>( line.length() ) );
1679 }
1680 h = fm->ascent() * maxLineLength + ( maxLineLength - 1 ) * letterSpacing;
1681 break;
1682 }
1683
1685 {
1686 double widthHorizontal = 0.0;
1687 for ( const QString &line : std::as_const( multiLineSplit ) )
1688 {
1689 widthHorizontal = std::max( w, fm->horizontalAdvance( line ) );
1690 }
1691
1692 double widthVertical = 0.0;
1693 double letterSpacing = mFormat.scaledFont( *context ).letterSpacing();
1694 double labelWidth = fm->maxWidth();
1695 widthVertical = labelWidth + ( lines - 1 ) * ( mFormat.lineHeightUnit() == QgsUnitTypes::RenderPercentage ? ( labelWidth * multilineH ) : lineHeightPainterUnits );
1696
1697 double heightHorizontal = 0.0;
1698 heightHorizontal += fm->height() + static_cast< double >( ( lines - 1 ) * ( mFormat.lineHeightUnit() == QgsUnitTypes::RenderPercentage ? ( labelHeight * multilineH ) : lineHeightPainterUnits ) );
1699
1700 double heightVertical = 0.0;
1701 int maxLineLength = 0;
1702 for ( const QString &line : std::as_const( multiLineSplit ) )
1703 {
1704 maxLineLength = std::max( maxLineLength, static_cast<int>( line.length() ) );
1705 }
1706 heightVertical = fm->ascent() * maxLineLength + ( maxLineLength - 1 ) * letterSpacing;
1707
1708 w = widthHorizontal;
1709 rw = heightVertical;
1710 h = heightHorizontal;
1711 rh = widthVertical;
1712 break;
1713 }
1714 }
1715 }
1716
1717#if 0 // XXX strk
1718 QgsPointXY ptSize = xform->toMapCoordinatesF( w, h );
1719 labelX = std::fabs( ptSize.x() - ptZero.x() );
1720 labelY = std::fabs( ptSize.y() - ptZero.y() );
1721#else
1722 double uPP = xform->mapUnitsPerPixel();
1723 labelX = w * uPP;
1724 labelY = h * uPP;
1725 if ( rotatedLabelX && rotatedLabelY )
1726 {
1727 *rotatedLabelX = rw * uPP;
1728 *rotatedLabelY = rh * uPP;
1729 }
1730#endif
1731}
1732
1734{
1735 registerFeatureWithDetails( f, context, QgsGeometry(), nullptr );
1736}
1737
1738std::unique_ptr<QgsLabelFeature> QgsPalLayerSettings::registerFeatureWithDetails( const QgsFeature &f, QgsRenderContext &context, QgsGeometry obstacleGeometry, const QgsSymbol *symbol )
1739{
1740 QVariant exprVal; // value() is repeatedly nulled on data defined evaluation and replaced when successful
1741 mCurFeat = &f;
1742
1743 // data defined is obstacle? calculate this first, to avoid wasting time working with obstacles we don't require
1744 bool isObstacle = mObstacleSettings.isObstacle();
1745 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::IsObstacle ) )
1746 isObstacle = mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::IsObstacle, context.expressionContext(), isObstacle ); // default to layer default
1747
1748 if ( !drawLabels )
1749 {
1750 if ( isObstacle )
1751 {
1752 return registerObstacleFeature( f, context, obstacleGeometry );
1753 }
1754 else
1755 {
1756 return nullptr;
1757 }
1758 }
1759
1760 QgsFeature feature = f;
1762 {
1763 const QgsGeometry geometry = mGeometryGeneratorExpression.evaluate( &context.expressionContext() ).value<QgsGeometry>();
1764 if ( mGeometryGeneratorExpression.hasEvalError() )
1765 QgsMessageLog::logMessage( mGeometryGeneratorExpression.evalErrorString(), QObject::tr( "Labeling" ) );
1766
1767 if ( obstacleGeometry.isNull() )
1768 {
1769 // if an explicit obstacle geometry hasn't been set, we must always use the original feature geometry
1770 // as the obstacle -- because we want to use the geometry which was used to render the symbology
1771 // for the feature as the obstacle for other layers' labels, NOT the generated geometry which is used
1772 // only to place labels for this layer.
1773 obstacleGeometry = f.geometry();
1774 }
1775
1776 feature.setGeometry( geometry );
1777 }
1778
1779 // store data defined-derived values for later adding to label feature for use during rendering
1780 dataDefinedValues.clear();
1781
1782 // data defined show label? defaults to show label if not set
1783 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Show ) )
1784 {
1786 if ( !mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::Show, context.expressionContext(), true ) )
1787 {
1788 return nullptr;
1789 }
1790 }
1791
1792 // data defined scale visibility?
1793 bool useScaleVisibility = scaleVisibility;
1794 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::ScaleVisibility ) )
1795 useScaleVisibility = mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::ScaleVisibility, context.expressionContext(), scaleVisibility );
1796
1797 if ( useScaleVisibility )
1798 {
1799 // data defined min scale?
1800 double maxScale = maximumScale;
1801 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::MaximumScale ) )
1802 {
1804 maxScale = mDataDefinedProperties.valueAsDouble( QgsPalLayerSettings::MaximumScale, context.expressionContext(), maxScale );
1805 }
1806
1807 // scales closer than 1:1
1808 if ( maxScale < 0 )
1809 {
1810 maxScale = 1 / std::fabs( maxScale );
1811 }
1812
1813 if ( !qgsDoubleNear( maxScale, 0.0 ) && context.rendererScale() < maxScale )
1814 {
1815 return nullptr;
1816 }
1817
1818 // data defined min scale?
1819 double minScale = minimumScale;
1820 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::MinimumScale ) )
1821 {
1823 minScale = mDataDefinedProperties.valueAsDouble( QgsPalLayerSettings::MinimumScale, context.expressionContext(), minScale );
1824 }
1825
1826 // scales closer than 1:1
1827 if ( minScale < 0 )
1828 {
1829 minScale = 1 / std::fabs( minScale );
1830 }
1831
1832 if ( !qgsDoubleNear( minScale, 0.0 ) && context.rendererScale() > minScale )
1833 {
1834 return nullptr;
1835 }
1836 }
1837
1838 QFont labelFont = mFormat.font();
1839 // labelFont will be added to label feature for use during label painting
1840
1841 // data defined font units?
1842 QgsUnitTypes::RenderUnit fontunits = mFormat.sizeUnit();
1843 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::FontSizeUnit, context.expressionContext() );
1844 if ( !QgsVariantUtils::isNull( exprVal ) )
1845 {
1846 QString units = exprVal.toString();
1847 if ( !units.isEmpty() )
1848 {
1849 bool ok;
1851 if ( ok )
1852 fontunits = res;
1853 }
1854 }
1855
1856 //data defined label size?
1857 double fontSize = mFormat.size();
1858 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Size ) )
1859 {
1860 context.expressionContext().setOriginalValueVariable( fontSize );
1861 fontSize = mDataDefinedProperties.valueAsDouble( QgsPalLayerSettings::Size, context.expressionContext(), fontSize );
1862 }
1863 if ( fontSize <= 0.0 )
1864 {
1865 return nullptr;
1866 }
1867
1868 int fontPixelSize = QgsTextRenderer::sizeToPixel( fontSize, context, fontunits, mFormat.sizeMapUnitScale() );
1869 // don't try to show font sizes less than 1 pixel (Qt complains)
1870 if ( fontPixelSize < 1 )
1871 {
1872 return nullptr;
1873 }
1874 labelFont.setPixelSize( fontPixelSize );
1875
1876 // NOTE: labelFont now always has pixelSize set, so pointSize or pointSizeF might return -1
1877
1878 // defined 'minimum/maximum pixel font size'?
1879 if ( fontunits == QgsUnitTypes::RenderMapUnits )
1880 {
1881 if ( mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::FontLimitPixel, context.expressionContext(), fontLimitPixelSize ) )
1882 {
1883 int fontMinPixel = mDataDefinedProperties.valueAsInt( QgsPalLayerSettings::FontMinPixel, context.expressionContext(), fontMinPixelSize );
1884 int fontMaxPixel = mDataDefinedProperties.valueAsInt( QgsPalLayerSettings::FontMaxPixel, context.expressionContext(), fontMaxPixelSize );
1885
1886 if ( fontMinPixel > labelFont.pixelSize() || labelFont.pixelSize() > fontMaxPixel )
1887 {
1888 return nullptr;
1889 }
1890 }
1891 }
1892
1893 // NOTE: the following parsing functions calculate and store any data defined values for later use in QgsPalLabeling::drawLabeling
1894 // this is done to provide clarity, and because such parsing is not directly related to PAL feature registration calculations
1895
1896 // calculate rest of font attributes and store any data defined values
1897 // this is done here for later use in making label backgrounds part of collision management (when implemented)
1898 labelFont.setCapitalization( QFont::MixedCase ); // reset this - we don't use QFont's handling as it breaks with curved labels
1899
1900 parseTextStyle( labelFont, fontunits, context );
1901 if ( mDataDefinedProperties.hasActiveProperties() )
1902 {
1903 parseTextFormatting( context );
1904 parseTextBuffer( context );
1905 parseTextMask( context );
1906 parseShapeBackground( context );
1907 parseDropShadow( context );
1908 }
1909
1910 QString labelText;
1911
1912 // Check to see if we are a expression string.
1913 if ( isExpression )
1914 {
1916 if ( exp->hasParserError() )
1917 {
1918 QgsDebugMsgLevel( QStringLiteral( "Expression parser error:%1" ).arg( exp->parserErrorString() ), 4 );
1919 return nullptr;
1920 }
1921
1922 QVariant result = exp->evaluate( &context.expressionContext() ); // expression prepared in QgsPalLabeling::prepareLayer()
1923 if ( exp->hasEvalError() )
1924 {
1925 QgsDebugMsgLevel( QStringLiteral( "Expression parser eval error:%1" ).arg( exp->evalErrorString() ), 4 );
1926 return nullptr;
1927 }
1928 labelText = QgsVariantUtils::isNull( result ) ? QString() : result.toString();
1929 }
1930 else
1931 {
1932 const QVariant &v = feature.attribute( fieldIndex );
1933 labelText = QgsVariantUtils::isNull( v ) ? QString() : v.toString();
1934 }
1935
1936 // apply text replacements
1937 if ( useSubstitutions )
1938 {
1939 labelText = substitutions.process( labelText );
1940 }
1941
1942 // apply capitalization
1943 Qgis::Capitalization capitalization = mFormat.capitalization();
1944 // maintain API - capitalization may have been set in textFont
1945 if ( capitalization == Qgis::Capitalization::MixedCase && mFormat.font().capitalization() != QFont::MixedCase )
1946 {
1947 capitalization = static_cast< Qgis::Capitalization >( mFormat.font().capitalization() );
1948 }
1949 // data defined font capitalization?
1950 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::FontCase ) )
1951 {
1952 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::FontCase, context.expressionContext() );
1953 if ( !QgsVariantUtils::isNull( exprVal ) )
1954 {
1955 QString fcase = exprVal.toString().trimmed();
1956 QgsDebugMsgLevel( QStringLiteral( "exprVal FontCase:%1" ).arg( fcase ), 4 );
1957
1958 if ( !fcase.isEmpty() )
1959 {
1960 if ( fcase.compare( QLatin1String( "NoChange" ), Qt::CaseInsensitive ) == 0 )
1961 {
1962 capitalization = Qgis::Capitalization::MixedCase;
1963 }
1964 else if ( fcase.compare( QLatin1String( "Upper" ), Qt::CaseInsensitive ) == 0 )
1965 {
1966 capitalization = Qgis::Capitalization::AllUppercase;
1967 }
1968 else if ( fcase.compare( QLatin1String( "Lower" ), Qt::CaseInsensitive ) == 0 )
1969 {
1970 capitalization = Qgis::Capitalization::AllLowercase;
1971 }
1972 else if ( fcase.compare( QLatin1String( "Capitalize" ), Qt::CaseInsensitive ) == 0 )
1973 {
1975 }
1976 else if ( fcase.compare( QLatin1String( "Title" ), Qt::CaseInsensitive ) == 0 )
1977 {
1978 capitalization = Qgis::Capitalization::TitleCase;
1979 }
1980#if defined(HAS_KDE_QT5_SMALL_CAPS_FIX) || QT_VERSION >= QT_VERSION_CHECK(6, 3, 0)
1981 else if ( fcase.compare( QLatin1String( "SmallCaps" ), Qt::CaseInsensitive ) == 0 )
1982 {
1983 capitalization = Qgis::Capitalization::SmallCaps;
1984 }
1985 else if ( fcase.compare( QLatin1String( "AllSmallCaps" ), Qt::CaseInsensitive ) == 0 )
1986 {
1987 capitalization = Qgis::Capitalization::AllSmallCaps;
1988 }
1989#endif
1990 }
1991 }
1992 }
1993 labelText = QgsStringUtils::capitalize( labelText, capitalization );
1994
1995 // format number if label text is coercible to a number
1996 bool evalFormatNumbers = formatNumbers;
1997 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::NumFormat ) )
1998 {
1999 evalFormatNumbers = mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::NumFormat, context.expressionContext(), evalFormatNumbers );
2000 }
2001 if ( evalFormatNumbers )
2002 {
2003 // data defined decimal places?
2004 int decimalPlaces = mDataDefinedProperties.valueAsInt( QgsPalLayerSettings::NumDecimals, context.expressionContext(), decimals );
2005 if ( decimalPlaces <= 0 ) // needs to be positive
2006 decimalPlaces = decimals;
2007
2008 // data defined plus sign?
2009 bool signPlus = mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::NumPlusSign, context.expressionContext(), plusSign );
2010
2011 QVariant textV( labelText );
2012 bool ok;
2013 double d = textV.toDouble( &ok );
2014 if ( ok )
2015 {
2016 QString numberFormat;
2017 if ( d > 0 && signPlus )
2018 {
2019 numberFormat.append( '+' );
2020 }
2021 numberFormat.append( "%1" );
2022 labelText = numberFormat.arg( d, 0, 'f', decimalPlaces );
2023 }
2024 }
2025
2026 // NOTE: this should come AFTER any option that affects font metrics
2027 std::unique_ptr<QFontMetricsF> labelFontMetrics( new QFontMetricsF( labelFont ) );
2028 double labelWidth;
2029 double labelHeight;
2030 double rotatedLabelX;
2031 double rotatedLabelY;
2032
2033 QgsTextDocument doc;
2034 QgsTextDocumentMetrics documentMetrics;
2035 if ( format().allowHtmlFormatting() && !labelText.isEmpty() )
2036 {
2037 doc = QgsTextDocument::fromHtml( QStringList() << labelText );
2038 // also applies the line split to doc and calculates document metrics!
2039 calculateLabelSize( labelFontMetrics.get(), labelText, labelWidth, labelHeight, mCurFeat, &context, &rotatedLabelX, &rotatedLabelY, &doc, &documentMetrics );
2040 }
2041 else
2042 {
2043 calculateLabelSize( labelFontMetrics.get(), labelText, labelWidth, labelHeight, mCurFeat, &context, &rotatedLabelX, &rotatedLabelY, nullptr, nullptr );
2044 }
2045
2046 // maximum angle between curved label characters (hardcoded defaults used in QGIS <2.0)
2047 //
2048 double maxcharanglein = 20.0; // range 20.0-60.0
2049 double maxcharangleout = -20.0; // range 20.0-95.0
2050
2051 switch ( placement )
2052 {
2055 {
2056 maxcharanglein = maxCurvedCharAngleIn;
2057 maxcharangleout = maxCurvedCharAngleOut;
2058
2059 //data defined maximum angle between curved label characters?
2060 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::CurvedCharAngleInOut ) )
2061 {
2062 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::CurvedCharAngleInOut, context.expressionContext() );
2063 bool ok = false;
2064 const QPointF maxcharanglePt = QgsSymbolLayerUtils::toPoint( exprVal, &ok );
2065 if ( ok )
2066 {
2067 maxcharanglein = std::clamp( static_cast< double >( maxcharanglePt.x() ), 20.0, 60.0 );
2068 maxcharangleout = std::clamp( static_cast< double >( maxcharanglePt.y() ), 20.0, 95.0 );
2069 }
2070 }
2071 // make sure maxcharangleout is always negative
2072 maxcharangleout = -( std::fabs( maxcharangleout ) );
2073 break;
2074 }
2075
2083 break;
2084 }
2085
2086 // data defined centroid whole or clipped?
2087 bool wholeCentroid = centroidWhole;
2088 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::CentroidWhole ) )
2089 {
2090 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::CentroidWhole, context.expressionContext() );
2091 if ( !QgsVariantUtils::isNull( exprVal ) )
2092 {
2093 QString str = exprVal.toString().trimmed();
2094 QgsDebugMsgLevel( QStringLiteral( "exprVal CentroidWhole:%1" ).arg( str ), 4 );
2095
2096 if ( !str.isEmpty() )
2097 {
2098 if ( str.compare( QLatin1String( "Visible" ), Qt::CaseInsensitive ) == 0 )
2099 {
2100 wholeCentroid = false;
2101 }
2102 else if ( str.compare( QLatin1String( "Whole" ), Qt::CaseInsensitive ) == 0 )
2103 {
2104 wholeCentroid = true;
2105 }
2106 }
2107 }
2108 }
2109
2110 QgsGeometry geom = feature.geometry();
2111 if ( geom.isNull() )
2112 {
2113 return nullptr;
2114 }
2115
2116 // simplify?
2117 const QgsVectorSimplifyMethod &simplifyMethod = context.vectorSimplifyMethod();
2118 std::unique_ptr<QgsGeometry> scopedClonedGeom;
2119 if ( simplifyMethod.simplifyHints() != QgsVectorSimplifyMethod::NoSimplification && simplifyMethod.forceLocalOptimization() )
2120 {
2121 unsigned int simplifyHints = simplifyMethod.simplifyHints() | QgsMapToPixelSimplifier::SimplifyEnvelope;
2123 QgsMapToPixelSimplifier simplifier( simplifyHints, simplifyMethod.tolerance(), simplifyAlgorithm );
2124 geom = simplifier.simplify( geom );
2125 }
2126
2127 if ( !context.featureClipGeometry().isEmpty() )
2128 {
2129 const QgsWkbTypes::GeometryType expectedType = geom.type();
2130 geom = geom.intersection( context.featureClipGeometry() );
2131 geom.convertGeometryCollectionToSubclass( expectedType );
2132 }
2133
2134 // whether we're going to create a centroid for polygon
2135 bool centroidPoly = ( ( placement == Qgis::LabelPlacement::AroundPoint
2137 && geom.type() == QgsWkbTypes::PolygonGeometry );
2138
2139 // CLIP the geometry if it is bigger than the extent
2140 // don't clip if centroid is requested for whole feature
2141 bool doClip = false;
2142 if ( !centroidPoly || !wholeCentroid )
2143 {
2144 doClip = true;
2145 }
2146
2147
2148 QgsLabeling::PolygonPlacementFlags polygonPlacement = mPolygonPlacementFlags;
2149 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::PolygonLabelOutside ) )
2150 {
2151 const QVariant dataDefinedOutside = mDataDefinedProperties.value( QgsPalLayerSettings::PolygonLabelOutside, context.expressionContext() );
2152 if ( !QgsVariantUtils::isNull( dataDefinedOutside ) )
2153 {
2154 if ( dataDefinedOutside.type() == QVariant::String )
2155 {
2156 const QString value = dataDefinedOutside.toString().trimmed();
2157 if ( value.compare( QLatin1String( "force" ), Qt::CaseInsensitive ) == 0 )
2158 {
2159 // forced outside placement -- remove inside flag, add outside flag
2160 polygonPlacement &= ~static_cast< int >( QgsLabeling::PolygonPlacementFlag::AllowPlacementInsideOfPolygon );
2162 }
2163 else if ( value.compare( QLatin1String( "yes" ), Qt::CaseInsensitive ) == 0 )
2164 {
2165 // permit outside placement
2167 }
2168 else if ( value.compare( QLatin1String( "no" ), Qt::CaseInsensitive ) == 0 )
2169 {
2170 // block outside placement
2171 polygonPlacement &= ~static_cast< int >( QgsLabeling::PolygonPlacementFlag::AllowPlacementOutsideOfPolygon );
2172 }
2173 }
2174 else
2175 {
2176 if ( dataDefinedOutside.toBool() )
2177 {
2178 // permit outside placement
2180 }
2181 else
2182 {
2183 // block outside placement
2184 polygonPlacement &= ~static_cast< int >( QgsLabeling::PolygonPlacementFlag::AllowPlacementOutsideOfPolygon );
2185 }
2186 }
2187 }
2188 }
2189
2190 QgsLabelLineSettings lineSettings = mLineSettings;
2191 lineSettings.updateDataDefinedProperties( mDataDefinedProperties, context.expressionContext() );
2192
2193 if ( geom.type() == QgsWkbTypes::LineGeometry )
2194 {
2195 switch ( lineSettings.anchorClipping() )
2196 {
2198 break;
2199
2201 doClip = false;
2202 break;
2203 }
2204 }
2205
2206 // if using fitInPolygonOnly option, generate the permissible zone (must happen before geometry is modified - e.g.,
2207 // as a result of using perimeter based labeling and the geometry is converted to a boundary)
2208 // note that we also force this if we are permitting labels to be placed outside of polygons too!
2209 QgsGeometry permissibleZone;
2211 {
2212 permissibleZone = geom;
2213 if ( QgsPalLabeling::geometryRequiresPreparation( permissibleZone, context, ct, doClip ? extentGeom : QgsGeometry(), lineSettings.mergeLines() ) )
2214 {
2215 permissibleZone = QgsPalLabeling::prepareGeometry( permissibleZone, context, ct, doClip ? extentGeom : QgsGeometry(), lineSettings.mergeLines() );
2216 }
2217 }
2218
2219 // if using perimeter based labeling for polygons, get the polygon's
2220 // linear boundary and use that for the label geometry
2221 if ( ( geom.type() == QgsWkbTypes::PolygonGeometry )
2223 {
2224 geom = QgsGeometry( geom.constGet()->boundary() );
2225 }
2226
2227 geos::unique_ptr geos_geom_clone;
2229 {
2230 geom = QgsPalLabeling::prepareGeometry( geom, context, ct, doClip ? extentGeom : QgsGeometry(), lineSettings.mergeLines() );
2231
2232 if ( geom.isEmpty() )
2233 return nullptr;
2234 }
2235 geos_geom_clone = QgsGeos::asGeos( geom );
2236
2238 {
2239 if ( !obstacleGeometry.isNull() && QgsPalLabeling::geometryRequiresPreparation( obstacleGeometry, context, ct, doClip ? extentGeom : QgsGeometry(), lineSettings.mergeLines() ) )
2240 {
2241 obstacleGeometry = QgsGeometry( QgsPalLabeling::prepareGeometry( obstacleGeometry, context, ct, doClip ? extentGeom : QgsGeometry(), lineSettings.mergeLines() ) );
2242 }
2243 }
2244
2245 QgsLabelThinningSettings featureThinningSettings = mThinningSettings;
2246 featureThinningSettings.updateDataDefinedProperties( mDataDefinedProperties, context.expressionContext() );
2247
2248 double minimumSize = 0.0;
2249 if ( featureThinningSettings.minimumFeatureSize() > 0 )
2250 {
2251 // for minimum feature size on merged lines, we need to delay the filtering after the merging occurred in PAL
2252 if ( geom.type() == QgsWkbTypes::LineGeometry && mLineSettings.mergeLines() )
2253 {
2254 minimumSize = context.convertToMapUnits( featureThinningSettings.minimumFeatureSize(), QgsUnitTypes::RenderMillimeters );
2255 }
2256 else
2257 {
2258 if ( !checkMinimumSizeMM( context, geom, featureThinningSettings.minimumFeatureSize() ) )
2259 return nullptr;
2260 }
2261 }
2262
2263 if ( !geos_geom_clone )
2264 return nullptr; // invalid geometry
2265
2266 // likelihood exists label will be registered with PAL and may be drawn
2267 // check if max number of features to label (already registered with PAL) has been reached
2268 // Debug output at end of QgsPalLabeling::drawLabeling(), when deleting temp geometries
2269 if ( featureThinningSettings.limitNumberOfLabelsEnabled() )
2270 {
2271 if ( !featureThinningSettings.maximumNumberLabels() )
2272 {
2273 return nullptr;
2274 }
2275 if ( mFeatsRegPal >= featureThinningSettings.maximumNumberLabels() )
2276 {
2277 return nullptr;
2278 }
2279
2280 int divNum = static_cast< int >( ( static_cast< double >( mFeaturesToLabel ) / featureThinningSettings.maximumNumberLabels() ) + 0.5 ); // NOLINT
2281 if ( divNum && ( mFeatsRegPal == static_cast< int >( mFeatsSendingToPal / divNum ) ) )
2282 {
2283 mFeatsSendingToPal += 1;
2284 if ( divNum && mFeatsSendingToPal % divNum )
2285 {
2286 return nullptr;
2287 }
2288 }
2289 }
2290
2291 //data defined position / alignment / rotation?
2292 bool layerDefinedRotation = false;
2293 bool dataDefinedRotation = false;
2294 double xPos = 0.0, yPos = 0.0;
2295 double angleInRadians = 0.0;
2296 double quadOffsetX = 0.0, quadOffsetY = 0.0;
2297 double offsetX = 0.0, offsetY = 0.0;
2298 QgsPointXY anchorPosition;
2299
2301 {
2302 anchorPosition = geom.centroid().asPoint();
2303 }
2304 //x/y shift in case of alignment
2305 double xdiff = 0.0;
2306 double ydiff = 0.0;
2307
2308 //data defined quadrant offset?
2309 bool ddFixedQuad = false;
2311 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::OffsetQuad ) )
2312 {
2313 context.expressionContext().setOriginalValueVariable( static_cast< int >( quadOff ) );
2314 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::OffsetQuad, context.expressionContext() );
2315 if ( !QgsVariantUtils::isNull( exprVal ) )
2316 {
2317 bool ok;
2318 int quadInt = exprVal.toInt( &ok );
2319 if ( ok && 0 <= quadInt && quadInt <= 8 )
2320 {
2321 quadOff = static_cast< Qgis::LabelQuadrantPosition >( quadInt );
2322 ddFixedQuad = true;
2323 }
2324 }
2325 }
2326
2327 // adjust quadrant offset of labels
2328 switch ( quadOff )
2329 {
2331 quadOffsetX = -1.0;
2332 quadOffsetY = 1.0;
2333 break;
2335 quadOffsetX = 0.0;
2336 quadOffsetY = 1.0;
2337 break;
2339 quadOffsetX = 1.0;
2340 quadOffsetY = 1.0;
2341 break;
2343 quadOffsetX = -1.0;
2344 quadOffsetY = 0.0;
2345 break;
2347 quadOffsetX = 1.0;
2348 quadOffsetY = 0.0;
2349 break;
2351 quadOffsetX = -1.0;
2352 quadOffsetY = -1.0;
2353 break;
2355 quadOffsetX = 0.0;
2356 quadOffsetY = -1.0;
2357 break;
2359 quadOffsetX = 1.0;
2360 quadOffsetY = -1.0;
2361 break;
2363 break;
2364 }
2365
2366 //data defined label offset?
2367 double xOff = xOffset;
2368 double yOff = yOffset;
2369 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::OffsetXY ) )
2370 {
2372 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::OffsetXY, context.expressionContext() );
2373 bool ok = false;
2374 const QPointF ddOffPt = QgsSymbolLayerUtils::toPoint( exprVal, &ok );
2375 if ( ok )
2376 {
2377 xOff = ddOffPt.x();
2378 yOff = ddOffPt.y();
2379 }
2380 }
2381
2382 // data defined label offset units?
2384 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::OffsetUnits ) )
2385 {
2386 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::OffsetUnits, context.expressionContext() );
2387 if ( !QgsVariantUtils::isNull( exprVal ) )
2388 {
2389 QString units = exprVal.toString().trimmed();
2390 if ( !units.isEmpty() )
2391 {
2392 bool ok = false;
2393 QgsUnitTypes::RenderUnit decodedUnits = QgsUnitTypes::decodeRenderUnit( units, &ok );
2394 if ( ok )
2395 {
2396 offUnit = decodedUnits;
2397 }
2398 }
2399 }
2400 }
2401
2402 // adjust offset of labels to match chosen unit and map scale
2403 // offsets match those of symbology: -x = left, -y = up
2404 offsetX = context.convertToMapUnits( xOff, offUnit, labelOffsetMapUnitScale );
2405 // must be negative to match symbology offset direction
2406 offsetY = context.convertToMapUnits( -yOff, offUnit, labelOffsetMapUnitScale );
2407
2408 // layer defined rotation?
2409 if ( !qgsDoubleNear( angleOffset, 0.0 ) )
2410 {
2411 layerDefinedRotation = true;
2412 angleInRadians = ( 360 - angleOffset ) * M_PI / 180; // convert to radians counterclockwise
2413 }
2414
2415 const QgsMapToPixel &m2p = context.mapToPixel();
2416 //data defined rotation?
2417 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::LabelRotation ) )
2418 {
2420 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::LabelRotation, context.expressionContext() );
2421 if ( !QgsVariantUtils::isNull( exprVal ) )
2422 {
2423 bool ok;
2424 const double rotation = exprVal.toDouble( &ok );
2425 if ( ok )
2426 {
2427 dataDefinedRotation = true;
2428
2429 double rotationDegrees = rotation * QgsUnitTypes::fromUnitToUnitFactor( mRotationUnit,
2431
2432 // TODO: add setting to disable having data defined rotation follow
2433 // map rotation ?
2434 rotationDegrees += m2p.mapRotation();
2435 angleInRadians = ( 360 - rotationDegrees ) * M_PI / 180.0;
2436 }
2437 }
2438 }
2439
2440 bool hasDataDefinedPosition = false;
2441 {
2442 bool ddPosition = false;
2443
2444 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::PositionX )
2445 && mDataDefinedProperties.isActive( QgsPalLayerSettings::PositionY ) )
2446 {
2447 const QVariant xPosProperty = mDataDefinedProperties.value( QgsPalLayerSettings::PositionX, context.expressionContext() );
2448 const QVariant yPosProperty = mDataDefinedProperties.value( QgsPalLayerSettings::PositionY, context.expressionContext() );
2449 if ( !QgsVariantUtils::isNull( xPosProperty )
2450 && !QgsVariantUtils::isNull( yPosProperty ) )
2451 {
2452 ddPosition = true;
2453
2454 bool ddXPos = false, ddYPos = false;
2455 xPos = xPosProperty.toDouble( &ddXPos );
2456 yPos = yPosProperty.toDouble( &ddYPos );
2457 if ( ddXPos && ddYPos )
2458 hasDataDefinedPosition = true;
2459 }
2460 }
2461 else if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::PositionPoint ) )
2462 {
2463 const QVariant pointPosProperty = mDataDefinedProperties.value( QgsPalLayerSettings::PositionPoint, context.expressionContext() );
2464 if ( !QgsVariantUtils::isNull( pointPosProperty ) )
2465 {
2466 ddPosition = true;
2467
2468 QgsPoint point;
2469 if ( pointPosProperty.userType() == QMetaType::type( "QgsReferencedGeometry" ) )
2470 {
2471 QgsReferencedGeometry referencedGeometryPoint = pointPosProperty.value<QgsReferencedGeometry>();
2472 point = QgsPoint( referencedGeometryPoint.asPoint() );
2473
2474 if ( !referencedGeometryPoint.isNull()
2475 && ct.sourceCrs() != referencedGeometryPoint.crs() )
2476 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 );
2477 }
2478 else if ( pointPosProperty.userType() == QMetaType::type( "QgsGeometry" ) )
2479 {
2480 point = QgsPoint( pointPosProperty.value<QgsGeometry>().asPoint() );
2481 }
2482
2483 if ( !point.isEmpty() )
2484 {
2485 hasDataDefinedPosition = true;
2486
2487 xPos = point.x();
2488 yPos = point.y();
2489 }
2490 }
2491 }
2492
2493 if ( ddPosition )
2494 {
2495 //data defined position. But field values could be NULL -> positions will be generated by PAL
2496 if ( hasDataDefinedPosition )
2497 {
2498 // layer rotation set, but don't rotate pinned labels unless data defined
2499 if ( layerDefinedRotation && !dataDefinedRotation )
2500 {
2501 angleInRadians = 0.0;
2502 }
2503
2504 //horizontal alignment
2505 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Hali ) )
2506 {
2507 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Hali, context.expressionContext() );
2508 if ( !QgsVariantUtils::isNull( exprVal ) )
2509 {
2510 QString haliString = exprVal.toString();
2511 if ( haliString.compare( QLatin1String( "Center" ), Qt::CaseInsensitive ) == 0 )
2512 {
2513 xdiff -= labelWidth / 2.0;
2514 }
2515 else if ( haliString.compare( QLatin1String( "Right" ), Qt::CaseInsensitive ) == 0 )
2516 {
2517 xdiff -= labelWidth;
2518 }
2519 }
2520 }
2521
2522 //vertical alignment
2523 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Vali ) )
2524 {
2525 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Vali, context.expressionContext() );
2526 if ( !QgsVariantUtils::isNull( exprVal ) )
2527 {
2528 QString valiString = exprVal.toString();
2529 if ( valiString.compare( QLatin1String( "Bottom" ), Qt::CaseInsensitive ) != 0 )
2530 {
2531 if ( valiString.compare( QLatin1String( "Top" ), Qt::CaseInsensitive ) == 0 )
2532 {
2533 ydiff -= labelHeight;;
2534 }
2535 else
2536 {
2537 double descentRatio = labelFontMetrics->descent() / labelFontMetrics->height();
2538 if ( valiString.compare( QLatin1String( "Base" ), Qt::CaseInsensitive ) == 0 )
2539 {
2540 ydiff -= labelHeight * descentRatio;
2541 }
2542 else //'Cap' or 'Half'
2543 {
2544 double capHeightRatio = ( labelFontMetrics->boundingRect( 'H' ).height() + 1 + labelFontMetrics->descent() ) / labelFontMetrics->height();
2545 ydiff -= labelHeight * capHeightRatio;
2546 if ( valiString.compare( QLatin1String( "Half" ), Qt::CaseInsensitive ) == 0 )
2547 {
2548 ydiff += labelHeight * ( capHeightRatio - descentRatio ) / 2.0;
2549 }
2550 }
2551 }
2552 }
2553 }
2554 }
2555
2556 if ( dataDefinedRotation )
2557 {
2558 //adjust xdiff and ydiff because the hali/vali point needs to be the rotation center
2559 double xd = xdiff * std::cos( angleInRadians ) - ydiff * std::sin( angleInRadians );
2560 double yd = xdiff * std::sin( angleInRadians ) + ydiff * std::cos( angleInRadians );
2561 xdiff = xd;
2562 ydiff = yd;
2563 }
2564
2565 //project xPos and yPos from layer to map CRS, handle rotation
2566 QgsGeometry ddPoint( new QgsPoint( xPos, yPos ) );
2567 if ( QgsPalLabeling::geometryRequiresPreparation( ddPoint, context, ct ) )
2568 {
2569 ddPoint = QgsPalLabeling::prepareGeometry( ddPoint, context, ct );
2570 if ( const QgsPoint *point = qgsgeometry_cast< const QgsPoint * >( ddPoint.constGet() ) )
2571 {
2572 xPos = point->x();
2573 yPos = point->y();
2574 anchorPosition = QgsPointXY( xPos, yPos );
2575 }
2576 else
2577 {
2578 QgsMessageLog::logMessage( QObject::tr( "Invalid data defined label position (%1, %2)" ).arg( xPos ).arg( yPos ), QObject::tr( "Labeling" ) );
2579 hasDataDefinedPosition = false;
2580 }
2581 }
2582 else
2583 {
2584 anchorPosition = QgsPointXY( xPos, yPos );
2585 }
2586
2587 xPos += xdiff;
2588 yPos += ydiff;
2589 }
2590 else
2591 {
2592 anchorPosition = QgsPointXY( xPos, yPos );
2593 }
2594 }
2595 }
2596
2597 // data defined always show?
2598 bool alwaysShow = false;
2599 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::AlwaysShow ) )
2600 {
2601 alwaysShow = mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::AlwaysShow, context.expressionContext(), false );
2602 }
2603
2604 // set repeat distance
2605 // data defined repeat distance?
2606 double repeatDist = repeatDistance;
2607 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::RepeatDistance ) )
2608 {
2609 context.expressionContext().setOriginalValueVariable( repeatDist );
2610 repeatDist = mDataDefinedProperties.valueAsDouble( QgsPalLayerSettings::RepeatDistance, context.expressionContext(), repeatDist );
2611 }
2612
2613 // data defined label-repeat distance units?
2615 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::RepeatDistanceUnit ) )
2616 {
2617 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::RepeatDistanceUnit, context.expressionContext() );
2618 if ( !QgsVariantUtils::isNull( exprVal ) )
2619 {
2620 QString units = exprVal.toString().trimmed();
2621 if ( !units.isEmpty() )
2622 {
2623 bool ok = false;
2624 QgsUnitTypes::RenderUnit decodedUnits = QgsUnitTypes::decodeRenderUnit( units, &ok );
2625 if ( ok )
2626 {
2627 repeatUnits = decodedUnits;
2628 }
2629 }
2630 }
2631 }
2632
2633 if ( !qgsDoubleNear( repeatDist, 0.0 ) )
2634 {
2635 if ( repeatUnits != QgsUnitTypes::RenderMapUnits )
2636 {
2637 repeatDist = context.convertToMapUnits( repeatDist, repeatUnits, repeatDistanceMapUnitScale );
2638 }
2639 }
2640
2641 // overrun distance
2642 double overrunDistanceEval = lineSettings.overrunDistance();
2643 if ( !qgsDoubleNear( overrunDistanceEval, 0.0 ) )
2644 {
2645 overrunDistanceEval = context.convertToMapUnits( overrunDistanceEval, lineSettings.overrunDistanceUnit(), lineSettings.overrunDistanceMapUnitScale() );
2646 }
2647
2648 // we smooth out the overrun label extensions by 1 mm, to avoid little jaggies right at the start or end of the lines
2649 // causing the overrun extension to extend out in an undesirable direction. This is hard coded, we don't want to overload
2650 // users with options they likely don't need to see...
2651 const double overrunSmoothDist = context.convertToMapUnits( 1, QgsUnitTypes::RenderMillimeters );
2652
2653 bool labelAll = labelPerPart && !hasDataDefinedPosition;
2654 if ( !hasDataDefinedPosition )
2655 {
2656 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::LabelAllParts ) )
2657 {
2659 labelAll = mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::LabelAllParts, context.expressionContext(), labelPerPart );
2660 }
2661 }
2662
2663 // feature to the layer
2664 std::unique_ptr< QgsTextLabelFeature > labelFeature = std::make_unique< QgsTextLabelFeature>( feature.id(), std::move( geos_geom_clone ), QSizeF( labelWidth, labelHeight ) );
2665 labelFeature->setAnchorPosition( anchorPosition );
2666 labelFeature->setFeature( feature );
2667 labelFeature->setSymbol( symbol );
2668 labelFeature->setDocument( doc, documentMetrics );
2669 if ( !qgsDoubleNear( rotatedLabelX, 0.0 ) && !qgsDoubleNear( rotatedLabelY, 0.0 ) )
2670 labelFeature->setRotatedSize( QSizeF( rotatedLabelX, rotatedLabelY ) );
2671 mFeatsRegPal++;
2672
2673 labelFeature->setHasFixedPosition( hasDataDefinedPosition );
2674 labelFeature->setFixedPosition( QgsPointXY( xPos, yPos ) );
2675 // use layer-level defined rotation, but not if position fixed
2676 labelFeature->setHasFixedAngle( dataDefinedRotation || ( !hasDataDefinedPosition && !qgsDoubleNear( angleInRadians, 0.0 ) ) );
2677 labelFeature->setFixedAngle( angleInRadians );
2678 labelFeature->setQuadOffset( QPointF( quadOffsetX, quadOffsetY ) );
2679 labelFeature->setPositionOffset( QgsPointXY( offsetX, offsetY ) );
2680 labelFeature->setOffsetType( offsetType );
2681 labelFeature->setAlwaysShow( alwaysShow );
2682 labelFeature->setRepeatDistance( repeatDist );
2683 labelFeature->setLabelText( labelText );
2684 labelFeature->setPermissibleZone( permissibleZone );
2685 labelFeature->setOverrunDistance( overrunDistanceEval );
2686 labelFeature->setOverrunSmoothDistance( overrunSmoothDist );
2687 labelFeature->setLineAnchorPercent( lineSettings.lineAnchorPercent() );
2688 labelFeature->setLineAnchorType( lineSettings.anchorType() );
2689 labelFeature->setLineAnchorTextPoint( lineSettings.anchorTextPoint() );
2690 labelFeature->setLabelAllParts( labelAll );
2691 labelFeature->setOriginalFeatureCrs( context.coordinateTransform().sourceCrs() );
2692 labelFeature->setMinimumSize( minimumSize );
2693 if ( geom.type() == QgsWkbTypes::PointGeometry && !obstacleGeometry.isNull() )
2694 {
2695 //register symbol size
2696 labelFeature->setSymbolSize( QSizeF( obstacleGeometry.boundingBox().width(),
2697 obstacleGeometry.boundingBox().height() ) );
2698 }
2699
2700 //set label's visual margin so that top visual margin is the leading, and bottom margin is the font's descent
2701 //this makes labels align to the font's baseline or highest character
2702 double topMargin = std::max( 0.25 * labelFontMetrics->ascent(), 0.0 );
2703 double bottomMargin = 1.0 + labelFontMetrics->descent();
2704 QgsMargins vm( 0.0, topMargin, 0.0, bottomMargin );
2705 vm *= xform->mapUnitsPerPixel();
2706 labelFeature->setVisualMargin( vm );
2707
2708 // store the label's calculated font for later use during painting
2709 QgsDebugMsgLevel( QStringLiteral( "PAL font stored definedFont: %1, Style: %2" ).arg( labelFont.toString(), labelFont.styleName() ), 4 );
2710 labelFeature->setDefinedFont( labelFont );
2711
2712 labelFeature->setMaximumCharacterAngleInside( std::clamp( maxcharanglein, 20.0, 60.0 ) * M_PI / 180 );
2713 labelFeature->setMaximumCharacterAngleOutside( std::clamp( maxcharangleout, -95.0, -20.0 ) * M_PI / 180 );
2714 switch ( placement )
2715 {
2723 // these placements don't require text metrics
2724 break;
2725
2728 labelFeature->setTextMetrics( QgsTextLabelFeature::calculateTextMetrics( xform, context, labelFont, *labelFontMetrics, labelFont.letterSpacing(), labelFont.wordSpacing(), labelText, format().allowHtmlFormatting() ? &doc : nullptr, format().allowHtmlFormatting() ? &documentMetrics : nullptr ) );
2729 break;
2730 }
2731
2732 // for labelFeature the LabelInfo is passed to feat when it is registered
2733
2734 // TODO: allow layer-wide feature dist in PAL...?
2735
2736 // data defined label-feature distance?
2737 double distance = dist;
2738 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::LabelDistance ) )
2739 {
2740 context.expressionContext().setOriginalValueVariable( distance );
2741 distance = mDataDefinedProperties.valueAsDouble( QgsPalLayerSettings::LabelDistance, context.expressionContext(), distance );
2742 }
2743
2744 // data defined label-feature distance units?
2746 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::DistanceUnits ) )
2747 {
2748 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::DistanceUnits, context.expressionContext() );
2749 if ( !QgsVariantUtils::isNull( exprVal ) )
2750 {
2751 QString units = exprVal.toString().trimmed();
2752 QgsDebugMsgLevel( QStringLiteral( "exprVal DistanceUnits:%1" ).arg( units ), 4 );
2753 if ( !units.isEmpty() )
2754 {
2755 bool ok = false;
2756 QgsUnitTypes::RenderUnit decodedUnits = QgsUnitTypes::decodeRenderUnit( units, &ok );
2757 if ( ok )
2758 {
2759 distUnit = decodedUnits;
2760 }
2761 }
2762 }
2763 }
2764 distance = context.convertToPainterUnits( distance, distUnit, distMapUnitScale );
2765
2766 // when using certain placement modes, we force a tiny minimum distance. This ensures that
2767 // candidates are created just offset from a border and avoids candidates being incorrectly flagged as colliding with neighbours
2768 switch ( placement )
2769 {
2773 distance = ( distance < 0 ? -1 : 1 ) * std::max( std::fabs( distance ), 1.0 );
2774 break;
2775
2777 break;
2778
2784 {
2785 distance = std::max( distance, 2.0 );
2786 }
2787 break;
2788
2790 distance = std::max( distance, 2.0 );
2791 break;
2792 }
2793
2794 if ( !qgsDoubleNear( distance, 0.0 ) )
2795 {
2796 double d = ptOne.distance( ptZero ) * distance;
2797 labelFeature->setDistLabel( d );
2798 }
2799
2800 if ( ddFixedQuad )
2801 {
2802 labelFeature->setHasFixedQuadrant( true );
2803 }
2804
2805 labelFeature->setArrangementFlags( lineSettings.placementFlags() );
2806
2807 labelFeature->setPolygonPlacementFlags( polygonPlacement );
2808
2809 // data defined z-index?
2810 double z = zIndex;
2811 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::ZIndex ) )
2812 {
2814 z = mDataDefinedProperties.valueAsDouble( QgsPalLayerSettings::ZIndex, context.expressionContext(), z );
2815 }
2816 labelFeature->setZIndex( z );
2817
2818 // data defined priority?
2819 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Priority ) )
2820 {
2822 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Priority, context.expressionContext() );
2823 if ( !QgsVariantUtils::isNull( exprVal ) )
2824 {
2825 bool ok;
2826 double priorityD = exprVal.toDouble( &ok );
2827 if ( ok )
2828 {
2829 priorityD = std::clamp( priorityD, 0.0, 10.0 );
2830 priorityD = 1 - priorityD / 10.0; // convert 0..10 --> 1..0
2831 labelFeature->setPriority( priorityD );
2832 }
2833 }
2834 }
2835
2836 // data defined allow degraded placement
2837 {
2838 double allowDegradedPlacement = mPlacementSettings.allowDegradedPlacement();
2839 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::AllowDegradedPlacement ) )
2840 {
2841 context.expressionContext().setOriginalValueVariable( allowDegradedPlacement );
2842 allowDegradedPlacement = mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::AllowDegradedPlacement, context.expressionContext(), allowDegradedPlacement );
2843 }
2844 labelFeature->setAllowDegradedPlacement( allowDegradedPlacement );
2845 }
2846
2847 // data defined overlap handling
2848 {
2849 Qgis::LabelOverlapHandling overlapHandling = mPlacementSettings.overlapHandling();
2850 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::OverlapHandling ) )
2851 {
2852 const QString handlingString = mDataDefinedProperties.valueAsString( QgsPalLayerSettings::OverlapHandling, context.expressionContext() );
2853 const QString cleanedString = handlingString.trimmed();
2854 if ( cleanedString.compare( QLatin1String( "prevent" ), Qt::CaseInsensitive ) == 0 )
2856 else if ( cleanedString.compare( QLatin1String( "allowifneeded" ), Qt::CaseInsensitive ) == 0 )
2858 else if ( cleanedString.compare( QLatin1String( "alwaysallow" ), Qt::CaseInsensitive ) == 0 )
2860 }
2861 labelFeature->setOverlapHandling( overlapHandling );
2862 }
2863
2864 QgsLabelObstacleSettings os = mObstacleSettings;
2865 os.setIsObstacle( isObstacle );
2866 os.updateDataDefinedProperties( mDataDefinedProperties, context.expressionContext() );
2867 os.setObstacleGeometry( obstacleGeometry );
2868 labelFeature->setObstacleSettings( os );
2869
2870 QVector< Qgis::LabelPredefinedPointPosition > positionOrder = predefinedPositionOrder;
2871 if ( positionOrder.isEmpty() )
2872 positionOrder = *DEFAULT_PLACEMENT_ORDER();
2873
2874 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::PredefinedPositionOrder ) )
2875 {
2877 QString dataDefinedOrder = mDataDefinedProperties.valueAsString( QgsPalLayerSettings::PredefinedPositionOrder, context.expressionContext() );
2878 if ( !dataDefinedOrder.isEmpty() )
2879 {
2880 positionOrder = QgsLabelingUtils::decodePredefinedPositionOrder( dataDefinedOrder );
2881 }
2882 }
2883 labelFeature->setPredefinedPositionOrder( positionOrder );
2884
2885 // add parameters for data defined labeling to label feature
2886 labelFeature->setDataDefinedValues( dataDefinedValues );
2887
2888 return labelFeature;
2889}
2890
2891std::unique_ptr<QgsLabelFeature> QgsPalLayerSettings::registerObstacleFeature( const QgsFeature &f, QgsRenderContext &context, const QgsGeometry &obstacleGeometry )
2892{
2893 mCurFeat = &f;
2894
2895 QgsGeometry geom;
2896 if ( !obstacleGeometry.isNull() )
2897 {
2898 geom = obstacleGeometry;
2899 }
2900 else
2901 {
2902 geom = f.geometry();
2903 }
2904
2905 if ( geom.isNull() )
2906 {
2907 return nullptr;
2908 }
2909
2910 // don't even try to register linestrings with only one vertex as an obstacle
2911 if ( const QgsLineString *ls = qgsgeometry_cast< const QgsLineString * >( geom.constGet() ) )
2912 {
2913 if ( ls->numPoints() < 2 )
2914 return nullptr;
2915 }
2916
2917 // simplify?
2918 const QgsVectorSimplifyMethod &simplifyMethod = context.vectorSimplifyMethod();
2919 std::unique_ptr<QgsGeometry> scopedClonedGeom;
2920 if ( simplifyMethod.simplifyHints() != QgsVectorSimplifyMethod::NoSimplification && simplifyMethod.forceLocalOptimization() )
2921 {
2922 int simplifyHints = simplifyMethod.simplifyHints() | QgsMapToPixelSimplifier::SimplifyEnvelope;
2924 QgsMapToPixelSimplifier simplifier( simplifyHints, simplifyMethod.tolerance(), simplifyAlgorithm );
2925 geom = simplifier.simplify( geom );
2926 }
2927
2928 geos::unique_ptr geos_geom_clone;
2929 std::unique_ptr<QgsGeometry> scopedPreparedGeom;
2930
2931 if ( QgsPalLabeling::geometryRequiresPreparation( geom, context, ct, extentGeom, mLineSettings.mergeLines() ) )
2932 {
2933 geom = QgsPalLabeling::prepareGeometry( geom, context, ct, extentGeom, mLineSettings.mergeLines() );
2934 }
2935 geos_geom_clone = QgsGeos::asGeos( geom );
2936
2937 if ( !geos_geom_clone )
2938 return nullptr; // invalid geometry
2939
2940 // feature to the layer
2941 std::unique_ptr< QgsLabelFeature > obstacleFeature = std::make_unique< QgsLabelFeature >( f.id(), std::move( geos_geom_clone ), QSizeF( 0, 0 ) );
2942 obstacleFeature->setFeature( f );
2943
2944 QgsLabelObstacleSettings os = mObstacleSettings;
2945 os.setIsObstacle( true );
2946 os.updateDataDefinedProperties( mDataDefinedProperties, context.expressionContext() );
2947 obstacleFeature->setObstacleSettings( os );
2948
2949 mFeatsRegPal++;
2950 return obstacleFeature;
2951}
2952
2953bool QgsPalLayerSettings::dataDefinedValEval( DataDefinedValueType valType,
2955 QVariant &exprVal, QgsExpressionContext &context, const QVariant &originalValue )
2956{
2957 if ( !mDataDefinedProperties.isActive( p ) )
2958 return false;
2959
2960 context.setOriginalValueVariable( originalValue );
2961 exprVal = mDataDefinedProperties.value( p, context );
2962 if ( !QgsVariantUtils::isNull( exprVal ) )
2963 {
2964 switch ( valType )
2965 {
2966 case DDBool:
2967 {
2968 bool bol = exprVal.toBool();
2969 dataDefinedValues.insert( p, QVariant( bol ) );
2970 return true;
2971 }
2972 case DDInt:
2973 {
2974 bool ok;
2975 int size = exprVal.toInt( &ok );
2976
2977 if ( ok )
2978 {
2979 dataDefinedValues.insert( p, QVariant( size ) );
2980 return true;
2981 }
2982 return false;
2983 }
2984 case DDIntPos:
2985 {
2986 bool ok;
2987 int size = exprVal.toInt( &ok );
2988
2989 if ( ok && size > 0 )
2990 {
2991 dataDefinedValues.insert( p, QVariant( size ) );
2992 return true;
2993 }
2994 return false;
2995 }
2996 case DDDouble:
2997 {
2998 bool ok;
2999 double size = exprVal.toDouble( &ok );
3000
3001 if ( ok )
3002 {
3003 dataDefinedValues.insert( p, QVariant( size ) );
3004 return true;
3005 }
3006 return false;
3007 }
3008 case DDDoublePos:
3009 {
3010 bool ok;
3011 double size = exprVal.toDouble( &ok );
3012
3013 if ( ok && size > 0.0 )
3014 {
3015 dataDefinedValues.insert( p, QVariant( size ) );
3016 return true;
3017 }
3018 return false;
3019 }
3020 case DDRotation180:
3021 {
3022 bool ok;
3023 double rot = exprVal.toDouble( &ok );
3024 if ( ok )
3025 {
3026 if ( rot < -180.0 && rot >= -360 )
3027 {
3028 rot += 360;
3029 }
3030 if ( rot > 180.0 && rot <= 360 )
3031 {
3032 rot -= 360;
3033 }
3034 if ( rot >= -180 && rot <= 180 )
3035 {
3036 dataDefinedValues.insert( p, QVariant( rot ) );
3037 return true;
3038 }
3039 }
3040 return false;
3041 }
3042 case DDOpacity:
3043 {
3044 bool ok;
3045 int size = exprVal.toInt( &ok );
3046 if ( ok && size >= 0 && size <= 100 )
3047 {
3048 dataDefinedValues.insert( p, QVariant( size ) );
3049 return true;
3050 }
3051 return false;
3052 }
3053 case DDString:
3054 {
3055 QString str = exprVal.toString(); // don't trim whitespace
3056
3057 dataDefinedValues.insert( p, QVariant( str ) ); // let it stay empty if it is
3058 return true;
3059 }
3060 case DDUnits:
3061 {
3062 QString unitstr = exprVal.toString().trimmed();
3063
3064 if ( !unitstr.isEmpty() )
3065 {
3066 dataDefinedValues.insert( p, QVariant( static_cast< int >( QgsUnitTypes::decodeRenderUnit( unitstr ) ) ) );
3067 return true;
3068 }
3069 return false;
3070 }
3071 case DDColor:
3072 {
3073 QString colorstr = exprVal.toString().trimmed();
3074 QColor color = QgsSymbolLayerUtils::decodeColor( colorstr );
3075
3076 if ( color.isValid() )
3077 {
3078 dataDefinedValues.insert( p, QVariant( color ) );
3079 return true;
3080 }
3081 return false;
3082 }
3083 case DDJoinStyle:
3084 {
3085 QString joinstr = exprVal.toString().trimmed();
3086
3087 if ( !joinstr.isEmpty() )
3088 {
3089 dataDefinedValues.insert( p, QVariant( static_cast< int >( QgsSymbolLayerUtils::decodePenJoinStyle( joinstr ) ) ) );
3090 return true;
3091 }
3092 return false;
3093 }
3094 case DDBlendMode:
3095 {
3096 QString blendstr = exprVal.toString().trimmed();
3097
3098 if ( !blendstr.isEmpty() )
3099 {
3100 dataDefinedValues.insert( p, QVariant( static_cast< int >( QgsSymbolLayerUtils::decodeBlendMode( blendstr ) ) ) );
3101 return true;
3102 }
3103 return false;
3104 }
3105 case DDPointF:
3106 {
3107 bool ok = false;
3108 const QPointF res = QgsSymbolLayerUtils::toPoint( exprVal, &ok );
3109 if ( ok )
3110 {
3111 dataDefinedValues.insert( p, res );
3112 return true;
3113 }
3114 return false;
3115 }
3116 case DDSizeF:
3117 {
3118 bool ok = false;
3119 const QSizeF res = QgsSymbolLayerUtils::toSize( exprVal, &ok );
3120 if ( ok )
3121 {
3122 dataDefinedValues.insert( p, res );
3123 return true;
3124 }
3125 return false;
3126 }
3127 }
3128 }
3129 return false;
3130}
3131
3132void QgsPalLayerSettings::parseTextStyle( QFont &labelFont,
3133 QgsUnitTypes::RenderUnit fontunits,
3134 QgsRenderContext &context )
3135{
3136 // NOTE: labelFont already has pixelSize set, so pointSize or pointSizeF might return -1
3137
3138 QVariant exprVal; // value() is repeatedly nulled on data defined evaluation and replaced when successful
3139
3140 // Two ways to generate new data defined font:
3141 // 1) Family + [bold] + [italic] (named style is ignored and font is built off of base family)
3142 // 2) Family + named style (bold or italic is ignored)
3143
3144 // data defined font family?
3145 QString ddFontFamily;
3146 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Family ) )
3147 {
3148 context.expressionContext().setOriginalValueVariable( labelFont.family() );
3149 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Family, context.expressionContext() );
3150 if ( !QgsVariantUtils::isNull( exprVal ) )
3151 {
3152 QString family = exprVal.toString().trimmed();
3153 QgsDebugMsgLevel( QStringLiteral( "exprVal Font family:%1" ).arg( family ), 4 );
3154
3156 if ( labelFont.family() != family )
3157 {
3158 // testing for ddFontFamily in QFontDatabase.families() may be slow to do for every feature
3159 // (i.e. don't use QgsFontUtils::fontFamilyMatchOnSystem( family ) here)
3160 if ( QgsFontUtils::fontFamilyOnSystem( family ) )
3161 {
3162 ddFontFamily = family;
3163 }
3164 }
3165 }
3166 }
3167
3168 // data defined named font style?
3169 QString ddFontStyle;
3170 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::FontStyle ) )
3171 {
3172 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::FontStyle, context.expressionContext() );
3173 if ( !QgsVariantUtils::isNull( exprVal ) )
3174 {
3175 QString fontstyle = exprVal.toString().trimmed();
3176 QgsDebugMsgLevel( QStringLiteral( "exprVal Font style:%1" ).arg( fontstyle ), 4 );
3177 ddFontStyle = fontstyle;
3178 }
3179 }
3180
3181 // data defined bold font style?
3182 bool ddBold = false;
3183 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Bold ) )
3184 {
3185 context.expressionContext().setOriginalValueVariable( labelFont.bold() );
3186 ddBold = mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::Bold, context.expressionContext(), false );
3187 }
3188
3189 // data defined italic font style?
3190 bool ddItalic = false;
3191 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Italic ) )
3192 {
3193 context.expressionContext().setOriginalValueVariable( labelFont.italic() );
3194 ddItalic = mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::Italic, context.expressionContext(), false );
3195 }
3196
3197 // TODO: update when pref for how to resolve missing family (use matching algorithm or just default font) is implemented
3198 // (currently defaults to what has been read in from layer settings)
3199 QFont newFont;
3200 QFont appFont = QApplication::font();
3201 bool newFontBuilt = false;
3202 if ( ddBold || ddItalic )
3203 {
3204 // new font needs built, since existing style needs removed
3205 newFont = QFont( !ddFontFamily.isEmpty() ? ddFontFamily : labelFont.family() );
3206 newFontBuilt = true;
3207 newFont.setBold( ddBold );
3208 newFont.setItalic( ddItalic );
3209 }
3210 else if ( !ddFontStyle.isEmpty()
3211 && ddFontStyle.compare( QLatin1String( "Ignore" ), Qt::CaseInsensitive ) != 0 )
3212 {
3213 if ( !ddFontFamily.isEmpty() )
3214 {
3215 // both family and style are different, build font from database
3216 if ( !mFontDB )
3217 mFontDB = std::make_unique< QFontDatabase >();
3218
3219 QFont styledfont = mFontDB->font( ddFontFamily, ddFontStyle, appFont.pointSize() );
3220 if ( appFont != styledfont )
3221 {
3222 newFont = styledfont;
3223 newFontBuilt = true;
3224 }
3225 }
3226
3227 // update the font face style
3228 QgsFontUtils::updateFontViaStyle( newFontBuilt ? newFont : labelFont, ddFontStyle );
3229 }
3230 else if ( !ddFontFamily.isEmpty() )
3231 {
3232 if ( ddFontStyle.compare( QLatin1String( "Ignore" ), Qt::CaseInsensitive ) != 0 )
3233 {
3234 // just family is different, build font from database
3235 if ( !mFontDB )
3236 mFontDB = std::make_unique< QFontDatabase >();
3237 QFont styledfont = mFontDB->font( ddFontFamily, mFormat.namedStyle(), appFont.pointSize() );
3238 if ( appFont != styledfont )
3239 {
3240 newFont = styledfont;
3241 newFontBuilt = true;
3242 }
3243 }
3244 else
3245 {
3246 newFont = QFont( ddFontFamily );
3247 newFontBuilt = true;
3248 }
3249 }
3250
3251 if ( newFontBuilt )
3252 {
3253 // copy over existing font settings
3254 //newFont = newFont.resolve( labelFont ); // should work, but let's be sure what's being copied
3255 newFont.setPixelSize( labelFont.pixelSize() );
3256 newFont.setUnderline( labelFont.underline() );
3257 newFont.setStrikeOut( labelFont.strikeOut() );
3258 newFont.setWordSpacing( labelFont.wordSpacing() );
3259 newFont.setLetterSpacing( QFont::AbsoluteSpacing, labelFont.letterSpacing() );
3260
3261 labelFont = newFont;
3262 }
3263
3264 // data defined word spacing?
3265 double wordspace = labelFont.wordSpacing();
3266 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::FontWordSpacing ) )
3267 {
3268 context.expressionContext().setOriginalValueVariable( wordspace );
3269 wordspace = mDataDefinedProperties.valueAsDouble( QgsPalLayerSettings::FontWordSpacing, context.expressionContext(), wordspace );
3270 }
3271 labelFont.setWordSpacing( context.convertToPainterUnits( wordspace, fontunits, mFormat.sizeMapUnitScale() ) );
3272
3273 // data defined letter spacing?
3274 double letterspace = labelFont.letterSpacing();
3275 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::FontLetterSpacing ) )
3276 {
3277 context.expressionContext().setOriginalValueVariable( letterspace );
3278 letterspace = mDataDefinedProperties.valueAsDouble( QgsPalLayerSettings::FontLetterSpacing, context.expressionContext(), letterspace );
3279 }
3280 labelFont.setLetterSpacing( QFont::AbsoluteSpacing, context.convertToPainterUnits( letterspace, fontunits, mFormat.sizeMapUnitScale() ) );
3281
3282 // data defined strikeout font style?
3283 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Strikeout ) )
3284 {
3285 context.expressionContext().setOriginalValueVariable( labelFont.strikeOut() );
3286 bool strikeout = mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::Strikeout, context.expressionContext(), false );
3287 labelFont.setStrikeOut( strikeout );
3288 }
3289
3290 // data defined stretch
3291 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::FontStretchFactor ) )
3292 {
3294 labelFont.setStretch( mDataDefinedProperties.valueAsInt( QgsPalLayerSettings::FontStretchFactor, context.expressionContext(), mFormat.stretchFactor() ) );
3295 }
3296
3297 // data defined underline font style?
3298 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Underline ) )
3299 {
3300 context.expressionContext().setOriginalValueVariable( labelFont.underline() );
3301 bool underline = mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::Underline, context.expressionContext(), false );
3302 labelFont.setUnderline( underline );
3303 }
3304
3305 // pass the rest on to QgsPalLabeling::drawLabeling
3306
3307 // data defined font color?
3308 dataDefinedValEval( DDColor, QgsPalLayerSettings::Color, exprVal, context.expressionContext(), QgsSymbolLayerUtils::encodeColor( mFormat.color() ) );
3309
3310 // data defined font opacity?
3311 dataDefinedValEval( DDOpacity, QgsPalLayerSettings::FontOpacity, exprVal, context.expressionContext(), mFormat.opacity() * 100 );
3312
3313 // data defined font blend mode?
3314 dataDefinedValEval( DDBlendMode, QgsPalLayerSettings::FontBlendMode, exprVal, context.expressionContext() );
3315
3316}
3317
3318void QgsPalLayerSettings::parseTextBuffer( QgsRenderContext &context )
3319{
3320 QVariant exprVal; // value() is repeatedly nulled on data defined evaluation and replaced when successful
3321
3322 QgsTextBufferSettings buffer = mFormat.buffer();
3323
3324 // data defined draw buffer?
3325 bool drawBuffer = mFormat.buffer().enabled();
3326 if ( dataDefinedValEval( DDBool, QgsPalLayerSettings::BufferDraw, exprVal, context.expressionContext(), buffer.enabled() ) )
3327 {
3328 drawBuffer = exprVal.toBool();
3329 }
3330 else if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::BufferDraw ) && QgsVariantUtils::isNull( exprVal ) )
3331 {
3332 dataDefinedValues.insert( QgsPalLayerSettings::BufferDraw, QVariant( drawBuffer ) );
3333 }
3334
3335 if ( !drawBuffer )
3336 {
3337 return;
3338 }
3339
3340 // data defined buffer size?
3341 double bufrSize = buffer.size();
3342 if ( dataDefinedValEval( DDDoublePos, QgsPalLayerSettings::BufferSize, exprVal, context.expressionContext(), buffer.size() ) )
3343 {
3344 bufrSize = exprVal.toDouble();
3345 }
3346
3347 // data defined buffer transparency?
3348 double bufferOpacity = buffer.opacity() * 100;
3349 if ( dataDefinedValEval( DDOpacity, QgsPalLayerSettings::BufferOpacity, exprVal, context.expressionContext(), bufferOpacity ) )
3350 {
3351 bufferOpacity = exprVal.toDouble();
3352 }
3353
3354 drawBuffer = ( drawBuffer && bufrSize > 0.0 && bufferOpacity > 0 );
3355
3356 if ( !drawBuffer )
3357 {
3358 dataDefinedValues.insert( QgsPalLayerSettings::BufferDraw, QVariant( false ) ); // trigger value
3359 dataDefinedValues.remove( QgsPalLayerSettings::BufferSize );
3360 dataDefinedValues.remove( QgsPalLayerSettings::BufferOpacity );
3361 return; // don't bother evaluating values that won't be used
3362 }
3363
3364 // data defined buffer units?
3365 dataDefinedValEval( DDUnits, QgsPalLayerSettings::BufferUnit, exprVal, context.expressionContext() );
3366
3367 // data defined buffer color?
3368 dataDefinedValEval( DDColor, QgsPalLayerSettings::BufferColor, exprVal, context.expressionContext(), QgsSymbolLayerUtils::encodeColor( buffer.color() ) );
3369
3370 // data defined buffer pen join style?
3371 dataDefinedValEval( DDJoinStyle, QgsPalLayerSettings::BufferJoinStyle, exprVal, context.expressionContext(), QgsSymbolLayerUtils::encodePenJoinStyle( buffer.joinStyle() ) );
3372
3373 // data defined buffer blend mode?
3374 dataDefinedValEval( DDBlendMode, QgsPalLayerSettings::BufferBlendMode, exprVal, context.expressionContext() );
3375}
3376
3377void QgsPalLayerSettings::parseTextMask( QgsRenderContext &context )
3378{
3379 QVariant exprVal; // value() is repeatedly nulled on data defined evaluation and replaced when successful
3380
3381 QgsTextMaskSettings mask = mFormat.mask();
3382
3383 // data defined enabled mask?
3384 bool maskEnabled = mask.enabled();
3385 if ( dataDefinedValEval( DDBool, QgsPalLayerSettings::MaskEnabled, exprVal, context.expressionContext(), mask.enabled() ) )
3386 {
3387 maskEnabled = exprVal.toBool();
3388 }
3389
3390 if ( !maskEnabled )
3391 {
3392 return;
3393 }
3394
3395 // data defined buffer size?
3396 double bufrSize = mask.size();
3397 if ( dataDefinedValEval( DDDoublePos, QgsPalLayerSettings::MaskBufferSize, exprVal, context.expressionContext(), mask.size() ) )
3398 {
3399 bufrSize = exprVal.toDouble();
3400 }
3401
3402 // data defined opacity?
3403 double opacity = mask.opacity() * 100;
3404 if ( dataDefinedValEval( DDOpacity, QgsPalLayerSettings::MaskOpacity, exprVal, context.expressionContext(), opacity ) )
3405 {
3406 opacity = exprVal.toDouble();
3407 }
3408
3409 maskEnabled = ( maskEnabled && bufrSize > 0.0 && opacity > 0 );
3410
3411 if ( !maskEnabled )
3412 {
3413 dataDefinedValues.insert( QgsPalLayerSettings::MaskEnabled, QVariant( false ) ); // trigger value
3414 dataDefinedValues.remove( QgsPalLayerSettings::MaskBufferSize );
3415 dataDefinedValues.remove( QgsPalLayerSettings::MaskOpacity );
3416 return; // don't bother evaluating values that won't be used
3417 }
3418
3419 // data defined buffer units?
3420 dataDefinedValEval( DDUnits, QgsPalLayerSettings::MaskBufferUnit, exprVal, context.expressionContext() );
3421
3422 // data defined buffer pen join style?
3423 dataDefinedValEval( DDJoinStyle, QgsPalLayerSettings::MaskJoinStyle, exprVal, context.expressionContext(), QgsSymbolLayerUtils::encodePenJoinStyle( mask.joinStyle() ) );
3424}
3425
3426void QgsPalLayerSettings::parseTextFormatting( QgsRenderContext &context )
3427{
3428 QVariant exprVal; // value() is repeatedly nulled on data defined evaluation and replaced when successful
3429
3430 // data defined multiline wrap character?
3431 QString wrapchr = wrapChar;
3432 if ( dataDefinedValEval( DDString, QgsPalLayerSettings::MultiLineWrapChar, exprVal, context.expressionContext(), wrapChar ) )
3433 {
3434 wrapchr = exprVal.toString();
3435 }
3436
3437 int evalAutoWrapLength = autoWrapLength;
3438 if ( dataDefinedValEval( DDInt, QgsPalLayerSettings::AutoWrapLength, exprVal, context.expressionContext(), evalAutoWrapLength ) )
3439 {
3440 evalAutoWrapLength = exprVal.toInt();
3441 }
3442
3443 // data defined multiline height?
3444 dataDefinedValEval( DDDouble, QgsPalLayerSettings::MultiLineHeight, exprVal, context.expressionContext() );
3445
3446 // data defined multiline text align?
3447 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::MultiLineAlignment ) )
3448 {
3450 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::MultiLineAlignment, context.expressionContext() );
3451 if ( !QgsVariantUtils::isNull( exprVal ) )
3452 {
3453 QString str = exprVal.toString().trimmed();
3454 QgsDebugMsgLevel( QStringLiteral( "exprVal MultiLineAlignment:%1" ).arg( str ), 4 );
3455
3456 if ( !str.isEmpty() )
3457 {
3458 // "Left"
3460
3461 if ( str.compare( QLatin1String( "Center" ), Qt::CaseInsensitive ) == 0 )
3462 {
3464 }
3465 else if ( str.compare( QLatin1String( "Right" ), Qt::CaseInsensitive ) == 0 )
3466 {
3468 }
3469 else if ( str.compare( QLatin1String( "Follow" ), Qt::CaseInsensitive ) == 0 )
3470 {
3472 }
3473 else if ( str.compare( QLatin1String( "Justify" ), Qt::CaseInsensitive ) == 0 )
3474 {
3476 }
3477 dataDefinedValues.insert( QgsPalLayerSettings::MultiLineAlignment, QVariant( static_cast< int >( aligntype ) ) );
3478 }
3479 }
3480 }
3481
3482 // text orientation
3483 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::TextOrientation ) )
3484 {
3485 const QString encoded = QgsTextRendererUtils::encodeTextOrientation( mFormat.orientation() );
3486 context.expressionContext().setOriginalValueVariable( encoded );
3487 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::TextOrientation, context.expressionContext() );
3488 if ( !QgsVariantUtils::isNull( exprVal ) )
3489 {
3490 QString str = exprVal.toString().trimmed();
3491 if ( !str.isEmpty() )
3492 dataDefinedValues.insert( QgsPalLayerSettings::TextOrientation, str );
3493 }
3494 }
3495
3496 // data defined direction symbol?
3497 bool drawDirSymb = mLineSettings.addDirectionSymbol();
3498 if ( dataDefinedValEval( DDBool, QgsPalLayerSettings::DirSymbDraw, exprVal, context.expressionContext(), drawDirSymb ) )
3499 {
3500 drawDirSymb = exprVal.toBool();
3501 }
3502
3503 if ( drawDirSymb )
3504 {
3505 // data defined direction left symbol?
3506 dataDefinedValEval( DDString, QgsPalLayerSettings::DirSymbLeft, exprVal, context.expressionContext(), mLineSettings.leftDirectionSymbol() );
3507
3508 // data defined direction right symbol?
3509 dataDefinedValEval( DDString, QgsPalLayerSettings::DirSymbRight, exprVal, context.expressionContext(), mLineSettings.rightDirectionSymbol() );
3510
3511 // data defined direction symbol placement?
3512 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::DirSymbPlacement, context.expressionContext() );
3513 if ( !QgsVariantUtils::isNull( exprVal ) )
3514 {
3515 QString str = exprVal.toString().trimmed();
3516 QgsDebugMsgLevel( QStringLiteral( "exprVal DirSymbPlacement:%1" ).arg( str ), 4 );
3517
3518 if ( !str.isEmpty() )
3519 {
3520 // "LeftRight"
3522
3523 if ( str.compare( QLatin1String( "Above" ), Qt::CaseInsensitive ) == 0 )
3524 {
3526 }
3527 else if ( str.compare( QLatin1String( "Below" ), Qt::CaseInsensitive ) == 0 )
3528 {
3530 }
3531 dataDefinedValues.insert( QgsPalLayerSettings::DirSymbPlacement, QVariant( static_cast< int >( placetype ) ) );
3532 }
3533 }
3534
3535 // data defined direction symbol reversed?
3536 dataDefinedValEval( DDBool, QgsPalLayerSettings::DirSymbReverse, exprVal, context.expressionContext(), mLineSettings.reverseDirectionSymbol() );
3537 }
3538
3539 // formatting for numbers is inline with generation of base label text and not passed to label painting
3540}
3541
3542void QgsPalLayerSettings::parseShapeBackground( QgsRenderContext &context )
3543{
3544 QVariant exprVal; // value() is repeatedly nulled on data defined evaluation and replaced when successful
3545
3546 QgsTextBackgroundSettings background = mFormat.background();
3547
3548 // data defined draw shape?
3549 bool drawShape = background.enabled();
3550 if ( dataDefinedValEval( DDBool, QgsPalLayerSettings::ShapeDraw, exprVal, context.expressionContext(), drawShape ) )
3551 {
3552 drawShape = exprVal.toBool();
3553 }
3554
3555 if ( !drawShape )
3556 {
3557 return;
3558 }
3559
3560 // data defined shape transparency?
3561 double shapeOpacity = background.opacity() * 100;
3562 if ( dataDefinedValEval( DDOpacity, QgsPalLayerSettings::ShapeOpacity, exprVal, context.expressionContext(), shapeOpacity ) )
3563 {
3564 shapeOpacity = 100.0 * exprVal.toDouble();
3565 }
3566
3567 drawShape = ( drawShape && shapeOpacity > 0 ); // size is not taken into account (could be)
3568
3569 if ( !drawShape )
3570 {
3571 dataDefinedValues.insert( QgsPalLayerSettings::ShapeDraw, QVariant( false ) ); // trigger value
3572 dataDefinedValues.remove( QgsPalLayerSettings::ShapeOpacity );
3573 return; // don't bother evaluating values that won't be used
3574 }
3575
3576 // data defined shape kind?
3577 QgsTextBackgroundSettings::ShapeType shapeKind = background.type();
3578 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::ShapeKind ) )
3579 {
3580 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::ShapeKind, context.expressionContext() );
3581 if ( !QgsVariantUtils::isNull( exprVal ) )
3582 {
3583 QString skind = exprVal.toString().trimmed();
3584 QgsDebugMsgLevel( QStringLiteral( "exprVal ShapeKind:%1" ).arg( skind ), 4 );
3585
3586 if ( !skind.isEmpty() )
3587 {
3588 shapeKind = QgsTextRendererUtils::decodeShapeType( skind );
3589 dataDefinedValues.insert( QgsPalLayerSettings::ShapeKind, QVariant( static_cast< int >( shapeKind ) ) );
3590 }
3591 }
3592 }
3593
3594 // data defined shape SVG path?
3595 QString svgPath = background.svgFile();
3596 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::ShapeSVGFile ) )
3597 {
3598 context.expressionContext().setOriginalValueVariable( svgPath );
3599 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::ShapeSVGFile, context.expressionContext() );
3600 if ( !QgsVariantUtils::isNull( exprVal ) )
3601 {
3602 QString svgfile = exprVal.toString().trimmed();
3603 QgsDebugMsgLevel( QStringLiteral( "exprVal ShapeSVGFile:%1" ).arg( svgfile ), 4 );
3604
3605 // '' empty paths are allowed
3606 svgPath = QgsSymbolLayerUtils::svgSymbolNameToPath( svgfile, context.pathResolver() );
3607 dataDefinedValues.insert( QgsPalLayerSettings::ShapeSVGFile, QVariant( svgPath ) );
3608 }
3609 }
3610
3611 // data defined shape size type?
3612 QgsTextBackgroundSettings::SizeType shpSizeType = background.sizeType();
3613 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::ShapeSizeType ) )
3614 {
3615 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::ShapeSizeType, context.expressionContext() );
3616 if ( !QgsVariantUtils::isNull( exprVal ) )
3617 {
3618 QString stype = exprVal.toString().trimmed();
3619 QgsDebugMsgLevel( QStringLiteral( "exprVal ShapeSizeType:%1" ).arg( stype ), 4 );
3620
3621 if ( !stype.isEmpty() )
3622 {
3624 dataDefinedValues.insert( QgsPalLayerSettings::ShapeSizeType, QVariant( static_cast< int >( shpSizeType ) ) );
3625 }
3626 }
3627 }
3628
3629 // data defined shape size X? (SVGs only use X for sizing)
3630 double ddShpSizeX = background.size().width();
3631 if ( dataDefinedValEval( DDDouble, QgsPalLayerSettings::ShapeSizeX, exprVal, context.expressionContext(), ddShpSizeX ) )
3632 {
3633 ddShpSizeX = exprVal.toDouble();
3634 }
3635
3636 // data defined shape size Y?
3637 double ddShpSizeY = background.size().height();
3638 if ( dataDefinedValEval( DDDouble, QgsPalLayerSettings::ShapeSizeY, exprVal, context.expressionContext(), ddShpSizeY ) )
3639 {
3640 ddShpSizeY = exprVal.toDouble();
3641 }
3642
3643 // don't continue under certain circumstances (e.g. size is fixed)
3644 bool skip = false;
3645 if ( shapeKind == QgsTextBackgroundSettings::ShapeSVG
3646 && ( svgPath.isEmpty()
3647 || ( !svgPath.isEmpty()
3648 && shpSizeType == QgsTextBackgroundSettings::SizeFixed
3649 && ddShpSizeX == 0.0 ) ) )
3650 {
3651 skip = true;
3652 }
3654 && ( !background.markerSymbol()
3655 || ( background.markerSymbol()
3656 && shpSizeType == QgsTextBackgroundSettings::SizeFixed
3657 && ddShpSizeX == 0.0 ) ) )
3658 {
3659 skip = true;
3660 }
3661 if ( shapeKind != QgsTextBackgroundSettings::ShapeSVG
3663 && shpSizeType == QgsTextBackgroundSettings::SizeFixed
3664 && ( ddShpSizeX == 0.0 || ddShpSizeY == 0.0 ) )
3665 {
3666 skip = true;
3667 }
3668
3669 if ( skip )
3670 {
3671 dataDefinedValues.insert( QgsPalLayerSettings::ShapeDraw, QVariant( false ) ); // trigger value
3672 dataDefinedValues.remove( QgsPalLayerSettings::ShapeOpacity );
3673 dataDefinedValues.remove( QgsPalLayerSettings::ShapeKind );
3674 dataDefinedValues.remove( QgsPalLayerSettings::ShapeSVGFile );
3675 dataDefinedValues.remove( QgsPalLayerSettings::ShapeSizeX );
3676 dataDefinedValues.remove( QgsPalLayerSettings::ShapeSizeY );
3677 return; // don't bother evaluating values that won't be used
3678 }
3679
3680 // data defined shape size units?
3681 dataDefinedValEval( DDUnits, QgsPalLayerSettings::ShapeSizeUnits, exprVal, context.expressionContext() );
3682
3683 // data defined shape rotation type?
3684 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::ShapeRotationType ) )
3685 {
3686 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::ShapeRotationType, context.expressionContext() );
3687 if ( !QgsVariantUtils::isNull( exprVal ) )
3688 {
3689 QString rotstr = exprVal.toString().trimmed();
3690 QgsDebugMsgLevel( QStringLiteral( "exprVal ShapeRotationType:%1" ).arg( rotstr ), 4 );
3691
3692 if ( !rotstr.isEmpty() )
3693 {
3694 // "Sync"
3696 dataDefinedValues.insert( QgsPalLayerSettings::ShapeRotationType, QVariant( static_cast< int >( rottype ) ) );
3697 }
3698 }
3699 }
3700
3701 // data defined shape rotation?
3702 dataDefinedValEval( DDRotation180, QgsPalLayerSettings::ShapeRotation, exprVal, context.expressionContext(), background.rotation() );
3703
3704 // data defined shape offset?
3705 dataDefinedValEval( DDPointF, QgsPalLayerSettings::ShapeOffset, exprVal, context.expressionContext(), QgsSymbolLayerUtils::encodePoint( background.offset() ) );
3706
3707 // data defined shape offset units?
3708 dataDefinedValEval( DDUnits, QgsPalLayerSettings::ShapeOffsetUnits, exprVal, context.expressionContext() );
3709
3710 // data defined shape radii?
3711 dataDefinedValEval( DDSizeF, QgsPalLayerSettings::ShapeRadii, exprVal, context.expressionContext(), QgsSymbolLayerUtils::encodeSize( background.radii() ) );
3712
3713 // data defined shape radii units?
3714 dataDefinedValEval( DDUnits, QgsPalLayerSettings::ShapeRadiiUnits, exprVal, context.expressionContext() );
3715
3716 // data defined shape blend mode?
3717 dataDefinedValEval( DDBlendMode, QgsPalLayerSettings::ShapeBlendMode, exprVal, context.expressionContext() );
3718
3719 // data defined shape fill color?
3720 dataDefinedValEval( DDColor, QgsPalLayerSettings::ShapeFillColor, exprVal, context.expressionContext(), QgsSymbolLayerUtils::encodeColor( background.fillColor() ) );
3721
3722 // data defined shape stroke color?
3723 dataDefinedValEval( DDColor, QgsPalLayerSettings::ShapeStrokeColor, exprVal, context.expressionContext(), QgsSymbolLayerUtils::encodeColor( background.strokeColor() ) );
3724
3725 // data defined shape stroke width?
3726 dataDefinedValEval( DDDoublePos, QgsPalLayerSettings::ShapeStrokeWidth, exprVal, context.expressionContext(), background.strokeWidth() );
3727
3728 // data defined shape stroke width units?
3729 dataDefinedValEval( DDUnits, QgsPalLayerSettings::ShapeStrokeWidthUnits, exprVal, context.expressionContext() );
3730
3731 // data defined shape join style?
3732 dataDefinedValEval( DDJoinStyle, QgsPalLayerSettings::ShapeJoinStyle, exprVal, context.expressionContext(), QgsSymbolLayerUtils::encodePenJoinStyle( background.joinStyle() ) );
3733
3734}
3735
3736void QgsPalLayerSettings::parseDropShadow( QgsRenderContext &context )
3737{
3738 QVariant exprVal; // value() is repeatedly nulled on data defined evaluation and replaced when successful
3739
3740 QgsTextShadowSettings shadow = mFormat.shadow();
3741
3742 // data defined draw shadow?
3743 bool drawShadow = shadow.enabled();
3744 if ( dataDefinedValEval( DDBool, QgsPalLayerSettings::ShadowDraw, exprVal, context.expressionContext(), drawShadow ) )
3745 {
3746 drawShadow = exprVal.toBool();
3747 }
3748
3749 if ( !drawShadow )
3750 {
3751 return;
3752 }
3753
3754 // data defined shadow transparency?
3755 double shadowOpacity = shadow.opacity() * 100;
3756 if ( dataDefinedValEval( DDOpacity, QgsPalLayerSettings::ShadowOpacity, exprVal, context.expressionContext(), shadowOpacity ) )
3757 {
3758 shadowOpacity = exprVal.toDouble();
3759 }
3760
3761 // data defined shadow offset distance?
3762 double shadowOffDist = shadow.offsetDistance();
3763 if ( dataDefinedValEval( DDDoublePos, QgsPalLayerSettings::ShadowOffsetDist, exprVal, context.expressionContext(), shadowOffDist ) )
3764 {
3765 shadowOffDist = exprVal.toDouble();
3766 }
3767
3768 // data defined shadow offset distance?
3769 double shadowRad = shadow.blurRadius();
3770 if ( dataDefinedValEval( DDDoublePos, QgsPalLayerSettings::ShadowRadius, exprVal, context.expressionContext(), shadowRad ) )
3771 {
3772 shadowRad = exprVal.toDouble();
3773 }
3774
3775 drawShadow = ( drawShadow && shadowOpacity > 0 && !( shadowOffDist == 0.0 && shadowRad == 0.0 ) );
3776
3777 if ( !drawShadow )
3778 {
3779 dataDefinedValues.insert( QgsPalLayerSettings::ShadowDraw, QVariant( false ) ); // trigger value
3780 dataDefinedValues.remove( QgsPalLayerSettings::ShadowOpacity );
3781 dataDefinedValues.remove( QgsPalLayerSettings::ShadowOffsetDist );
3782 dataDefinedValues.remove( QgsPalLayerSettings::ShadowRadius );
3783 return; // don't bother evaluating values that won't be used
3784 }
3785
3786 // data defined shadow under type?
3787 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::ShadowUnder ) )
3788 {
3789 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::ShadowUnder, context.expressionContext() );
3790 if ( !QgsVariantUtils::isNull( exprVal ) )
3791 {
3792 QString str = exprVal.toString().trimmed();
3793 QgsDebugMsgLevel( QStringLiteral( "exprVal ShadowUnder:%1" ).arg( str ), 4 );
3794
3795 if ( !str.isEmpty() )
3796 {
3798 dataDefinedValues.insert( QgsPalLayerSettings::ShadowUnder, QVariant( static_cast< int >( shdwtype ) ) );
3799 }
3800 }
3801 }
3802
3803 // data defined shadow offset angle?
3804 dataDefinedValEval( DDRotation180, QgsPalLayerSettings::ShadowOffsetAngle, exprVal, context.expressionContext(), shadow.offsetAngle() );
3805
3806 // data defined shadow offset units?
3807 dataDefinedValEval( DDUnits, QgsPalLayerSettings::ShadowOffsetUnits, exprVal, context.expressionContext() );
3808
3809 // data defined shadow radius?
3810 dataDefinedValEval( DDDouble, QgsPalLayerSettings::ShadowRadius, exprVal, context.expressionContext(), shadow.blurRadius() );
3811
3812 // data defined shadow radius units?
3813 dataDefinedValEval( DDUnits, QgsPalLayerSettings::ShadowRadiusUnits, exprVal, context.expressionContext() );
3814
3815 // data defined shadow scale? ( gui bounds to 0-2000, no upper bound here )
3816 dataDefinedValEval( DDIntPos, QgsPalLayerSettings::ShadowScale, exprVal, context.expressionContext(), shadow.scale() );
3817
3818 // data defined shadow color?
3819 dataDefinedValEval( DDColor, QgsPalLayerSettings::ShadowColor, exprVal, context.expressionContext(), QgsSymbolLayerUtils::encodeColor( shadow.color() ) );
3820
3821 // data defined shadow blend mode?
3822 dataDefinedValEval( DDBlendMode, QgsPalLayerSettings::ShadowBlendMode, exprVal, context.expressionContext() );
3823}
3824
3825// -------------
3826
3827
3829{
3830 switch ( layer->type() )
3831 {
3833 {
3834 const QgsVectorLayer *vl = qobject_cast< const QgsVectorLayer * >( layer );
3835 return vl->labelsEnabled() || vl->diagramsEnabled();
3836 }
3837
3839 {
3840 const QgsVectorTileLayer *vl = qobject_cast< const QgsVectorTileLayer * >( layer );
3841 if ( !vl->labeling() )
3842 return false;
3843
3844 if ( const QgsVectorTileBasicLabeling *labeling = dynamic_cast< const QgsVectorTileBasicLabeling *>( vl->labeling() ) )
3845 return !labeling->styles().empty();
3846
3847 return false;
3848 }
3849
3856 return false;
3857 }
3858 return false;
3859}
3860
3861
3862bool QgsPalLabeling::geometryRequiresPreparation( const QgsGeometry &geometry, QgsRenderContext &context, const QgsCoordinateTransform &ct, const QgsGeometry &clipGeometry, bool mergeLines )
3863{
3864 if ( geometry.isNull() )
3865 {
3866 return false;
3867 }
3868
3869 if ( geometry.type() == QgsWkbTypes::LineGeometry && geometry.isMultipart() && mergeLines )
3870 {
3871 return true;
3872 }
3873
3874 //requires reprojection
3875 if ( ct.isValid() && !ct.isShortCircuited() )
3876 return true;
3877
3878 //requires rotation
3879 const QgsMapToPixel &m2p = context.mapToPixel();
3880 if ( !qgsDoubleNear( m2p.mapRotation(), 0 ) )
3881 return true;
3882
3883 //requires clip
3884 if ( !clipGeometry.isNull() && !clipGeometry.boundingBox().contains( geometry.boundingBox() ) )
3885 return true;
3886
3887 //requires fixing
3888 if ( geometry.type() == QgsWkbTypes::PolygonGeometry && !geometry.isGeosValid() )
3889 return true;
3890
3891 return false;
3892}
3893
3894QStringList QgsPalLabeling::splitToLines( const QString &text, const QString &wrapCharacter, const int autoWrapLength, const bool useMaxLineLengthWhenAutoWrapping )
3895{
3896 QStringList multiLineSplit;
3897 if ( !wrapCharacter.isEmpty() && wrapCharacter != QLatin1String( "\n" ) )
3898 {
3899 //wrap on both the wrapchr and new line characters
3900 const QStringList lines = text.split( wrapCharacter );
3901 for ( const QString &line : lines )
3902 {
3903 multiLineSplit.append( line.split( '\n' ) );
3904 }
3905 }
3906 else
3907 {
3908 multiLineSplit = text.split( '\n' );
3909 }
3910
3911 // apply auto wrapping to each manually created line
3912 if ( autoWrapLength != 0 )
3913 {
3914 QStringList autoWrappedLines;
3915 autoWrappedLines.reserve( multiLineSplit.count() );
3916 for ( const QString &line : std::as_const( multiLineSplit ) )
3917 {
3918 autoWrappedLines.append( QgsStringUtils::wordWrap( line, autoWrapLength, useMaxLineLengthWhenAutoWrapping ).split( '\n' ) );
3919 }
3920 multiLineSplit = autoWrappedLines;
3921 }
3922 return multiLineSplit;
3923}
3924
3925QStringList QgsPalLabeling::splitToGraphemes( const QString &text )
3926{
3927 QStringList graphemes;
3928 QTextBoundaryFinder boundaryFinder( QTextBoundaryFinder::Grapheme, text );
3929 int currentBoundary = -1;
3930 int previousBoundary = 0;
3931 while ( ( currentBoundary = boundaryFinder.toNextBoundary() ) > 0 )
3932 {
3933 graphemes << text.mid( previousBoundary, currentBoundary - previousBoundary );
3934 previousBoundary = currentBoundary;
3935 }
3936 return graphemes;
3937}
3938
3939QgsGeometry QgsPalLabeling::prepareGeometry( const QgsGeometry &geometry, QgsRenderContext &context, const QgsCoordinateTransform &ct, const QgsGeometry &clipGeometry, bool mergeLines )
3940{
3941 if ( geometry.isNull() )
3942 {
3943 return QgsGeometry();
3944 }
3945
3946 //don't modify the feature's geometry so that geometry based expressions keep working
3947 QgsGeometry geom = geometry;
3948
3949 if ( geom.type() == QgsWkbTypes::LineGeometry && geom.isMultipart() && mergeLines )
3950 {
3951 geom = geom.mergeLines();
3952 }
3953
3954 //reproject the geometry if necessary
3955 if ( ct.isValid() && !ct.isShortCircuited() )
3956 {
3957 try
3958 {
3959 geom.transform( ct );
3960 }
3961 catch ( QgsCsException &cse )
3962 {
3963 Q_UNUSED( cse )
3964 QgsDebugMsgLevel( QStringLiteral( "Ignoring feature due to transformation exception" ), 4 );
3965 return QgsGeometry();
3966 }
3967 // geometry transforms may result in nan points, remove these
3968 geom.filterVertices( []( const QgsPoint & point )->bool
3969 {
3970 return std::isfinite( point.x() ) && std::isfinite( point.y() );
3971 } );
3972 if ( QgsCurvePolygon *cp = qgsgeometry_cast< QgsCurvePolygon * >( geom.get() ) )
3973 {
3974 cp->removeInvalidRings();
3975 }
3976 else if ( QgsMultiSurface *ms = qgsgeometry_cast< QgsMultiSurface * >( geom.get() ) )
3977 {
3978 for ( int i = 0; i < ms->numGeometries(); ++i )
3979 {
3980 if ( QgsCurvePolygon *cp = qgsgeometry_cast< QgsCurvePolygon * >( ms->geometryN( i ) ) )
3981 cp->removeInvalidRings();
3982 }
3983 }
3984 }
3985
3986 // Rotate the geometry if needed, before clipping
3987 const QgsMapToPixel &m2p = context.mapToPixel();
3988 if ( !qgsDoubleNear( m2p.mapRotation(), 0 ) )
3989 {
3990 QgsPointXY center = context.mapExtent().center();
3991 if ( geom.rotate( m2p.mapRotation(), center ) != Qgis::GeometryOperationResult::Success )
3992 {
3993 QgsDebugMsg( QStringLiteral( "Error rotating geometry" ).arg( geom.asWkt() ) );
3994 return QgsGeometry();
3995 }
3996 }
3997
3998 const bool mustClip = ( !clipGeometry.isNull() &&
3999 ( ( qgsDoubleNear( m2p.mapRotation(), 0 ) && !clipGeometry.boundingBox().contains( geom.boundingBox() ) )
4000 || ( !qgsDoubleNear( m2p.mapRotation(), 0 ) && !clipGeometry.contains( geom ) ) ) );
4001
4002 bool mustClipExact = false;
4003 if ( mustClip )
4004 {
4005 // nice and fast, but can result in invalid geometries. At least it will potentially strip out a bunch of unwanted vertices upfront!
4006 QgsGeometry clipGeom = geom.clipped( clipGeometry.boundingBox() );
4007 if ( clipGeom.isEmpty() )
4008 return QgsGeometry();
4009
4010 geom = clipGeom;
4011
4012 // we've now clipped against the BOUNDING BOX of clipGeometry. But if clipGeometry is an axis parallel rectangle, then there's no
4013 // need to do an exact (potentially costly) intersection clip as well!
4014 mustClipExact = !clipGeometry.isAxisParallelRectangle( 0.001 );
4015 }
4016
4017 // fix invalid polygons
4018 if ( geom.type() == QgsWkbTypes::PolygonGeometry )
4019 {
4020 if ( geom.isMultipart() )
4021 {
4022 // important -- we need to treat ever part in isolation here. We can't test the validity of the whole geometry
4023 // at once, because touching parts would result in an invalid geometry, and buffering this "dissolves" the parts.
4024 // because the actual label engine treats parts as separate entities, we aren't bound by the usual "touching parts are invalid" rule
4025 // see https://github.com/qgis/QGIS/issues/26763
4026 QVector< QgsGeometry> parts;
4027 parts.reserve( qgsgeometry_cast< const QgsGeometryCollection * >( geom.constGet() )->numGeometries() );
4028 for ( auto it = geom.const_parts_begin(); it != geom.const_parts_end(); ++it )
4029 {
4030 QgsGeometry partGeom( ( *it )->clone() );
4031 if ( !partGeom.isGeosValid() )
4032 {
4033
4034 partGeom = partGeom.makeValid();
4035 }
4036 parts.append( partGeom );
4037 }
4038 geom = QgsGeometry::collectGeometry( parts );
4039 }
4040 else if ( !geom.isGeosValid() )
4041 {
4042
4043 QgsGeometry bufferGeom = geom.makeValid();
4044 if ( bufferGeom.isNull() )
4045 {
4046 QgsDebugMsg( QStringLiteral( "Could not repair geometry: %1" ).arg( bufferGeom.lastError() ) );
4047 return QgsGeometry();
4048 }
4049 geom = bufferGeom;
4050 }
4051 }
4052
4053 if ( mustClipExact )
4054 {
4055 // now do the real intersection against the actual clip geometry
4056 QgsGeometry clipGeom = geom.intersection( clipGeometry );
4057 if ( clipGeom.isEmpty() )
4058 {
4059 return QgsGeometry();
4060 }
4061 geom = clipGeom;
4062 }
4063
4064 return geom;
4065}
4066
4067bool QgsPalLabeling::checkMinimumSizeMM( const QgsRenderContext &context, const QgsGeometry &geom, double minSize )
4068{
4069 if ( minSize <= 0 )
4070 {
4071 return true;
4072 }
4073
4074 if ( geom.isNull() )
4075 {
4076 return false;
4077 }
4078
4079 QgsWkbTypes::GeometryType featureType = geom.type();
4080 if ( featureType == QgsWkbTypes::PointGeometry ) //minimum size does not apply to point features
4081 {
4082 return true;
4083 }
4084
4085 double mapUnitsPerMM = context.mapToPixel().mapUnitsPerPixel() * context.scaleFactor();
4086 if ( featureType == QgsWkbTypes::LineGeometry )
4087 {
4088 double length = geom.length();
4089 if ( length >= 0.0 )
4090 {
4091 return ( length >= ( minSize * mapUnitsPerMM ) );
4092 }
4093 }
4094 else if ( featureType == QgsWkbTypes::PolygonGeometry )
4095 {
4096 double area = geom.area();
4097 if ( area >= 0.0 )
4098 {
4099 return ( std::sqrt( area ) >= ( minSize * mapUnitsPerMM ) );
4100 }
4101 }
4102 return true; //should never be reached. Return true in this case to label such geometries anyway.
4103}
4104
4105
4106void QgsPalLabeling::dataDefinedTextStyle( QgsPalLayerSettings &tmpLyr,
4107 const QMap< QgsPalLayerSettings::Property, QVariant > &ddValues )
4108{
4109 QgsTextFormat format = tmpLyr.format();
4110 bool changed = false;
4111
4112 //font color
4113 if ( ddValues.contains( QgsPalLayerSettings::Color ) )
4114 {
4115 QVariant ddColor = ddValues.value( QgsPalLayerSettings::Color );
4116 format.setColor( ddColor.value<QColor>() );
4117 changed = true;
4118 }
4119
4120 //font transparency
4121 if ( ddValues.contains( QgsPalLayerSettings::FontOpacity ) )
4122 {
4123 format.setOpacity( ddValues.value( QgsPalLayerSettings::FontOpacity ).toDouble() / 100.0 );
4124 changed = true;
4125 }
4126
4127 //font blend mode
4128 if ( ddValues.contains( QgsPalLayerSettings::FontBlendMode ) )
4129 {
4130 format.setBlendMode( static_cast< QPainter::CompositionMode >( ddValues.value( QgsPalLayerSettings::FontBlendMode ).toInt() ) );
4131 changed = true;
4132 }
4133
4134 if ( changed )
4135 {
4136 tmpLyr.setFormat( format );
4137 }
4138}
4139
4140void QgsPalLabeling::dataDefinedTextFormatting( QgsPalLayerSettings &tmpLyr,
4141 const QMap< QgsPalLayerSettings::Property, QVariant > &ddValues )
4142{
4143 if ( ddValues.contains( QgsPalLayerSettings::MultiLineWrapChar ) )
4144 {
4145 tmpLyr.wrapChar = ddValues.value( QgsPalLayerSettings::MultiLineWrapChar ).toString();
4146 }
4147
4148 if ( ddValues.contains( QgsPalLayerSettings::AutoWrapLength ) )
4149 {
4150 tmpLyr.autoWrapLength = ddValues.value( QgsPalLayerSettings::AutoWrapLength ).toInt();
4151 }
4152
4153 if ( ddValues.contains( QgsPalLayerSettings::MultiLineHeight ) )
4154 {
4155 QgsTextFormat format = tmpLyr.format();
4156 format.setLineHeight( ddValues.value( QgsPalLayerSettings::MultiLineHeight ).toDouble() );
4157 tmpLyr.setFormat( format );
4158 }
4159
4160 if ( ddValues.contains( QgsPalLayerSettings::MultiLineAlignment ) )
4161 {
4162 tmpLyr.multilineAlign = static_cast< Qgis::LabelMultiLineAlignment >( ddValues.value( QgsPalLayerSettings::MultiLineAlignment ).toInt() );
4163 }
4164
4165 if ( ddValues.contains( QgsPalLayerSettings::TextOrientation ) )
4166 {
4167 QgsTextFormat format = tmpLyr.format();
4169 tmpLyr.setFormat( format );
4170 }
4171
4172 if ( ddValues.contains( QgsPalLayerSettings::DirSymbDraw ) )
4173 {
4174 tmpLyr.lineSettings().setAddDirectionSymbol( ddValues.value( QgsPalLayerSettings::DirSymbDraw ).toBool() );
4175 }
4176
4177 if ( tmpLyr.lineSettings().addDirectionSymbol() )
4178 {
4179
4180 if ( ddValues.contains( QgsPalLayerSettings::DirSymbLeft ) )
4181 {
4182 tmpLyr.lineSettings().setLeftDirectionSymbol( ddValues.value( QgsPalLayerSettings::DirSymbLeft ).toString() );
4183 }
4184 if ( ddValues.contains( QgsPalLayerSettings::DirSymbRight ) )
4185 {
4186 tmpLyr.lineSettings().setRightDirectionSymbol( ddValues.value( QgsPalLayerSettings::DirSymbRight ).toString() );
4187 }
4188
4189 if ( ddValues.contains( QgsPalLayerSettings::DirSymbPlacement ) )
4190 {
4192 }
4193
4194 if ( ddValues.contains( QgsPalLayerSettings::DirSymbReverse ) )
4195 {
4196 tmpLyr.lineSettings().setReverseDirectionSymbol( ddValues.value( QgsPalLayerSettings::DirSymbReverse ).toBool() );
4197 }
4198
4199 }
4200}
4201
4202void QgsPalLabeling::dataDefinedTextBuffer( QgsPalLayerSettings &tmpLyr,
4203 const QMap< QgsPalLayerSettings::Property, QVariant > &ddValues )
4204{
4205 QgsTextBufferSettings buffer = tmpLyr.format().buffer();
4206 bool changed = false;
4207
4208 //buffer draw
4209 if ( ddValues.contains( QgsPalLayerSettings::BufferDraw ) )
4210 {
4211 buffer.setEnabled( ddValues.value( QgsPalLayerSettings::BufferDraw ).toBool() );
4212 changed = true;
4213 }
4214
4215 if ( !buffer.enabled() )
4216 {
4217 if ( changed )
4218 {
4219 QgsTextFormat format = tmpLyr.format();
4220 format.setBuffer( buffer );
4221 tmpLyr.setFormat( format );
4222 }
4223
4224 // tmpLyr.bufferSize > 0.0 && tmpLyr.bufferTransp < 100 figured in during evaluation
4225 return; // don't continue looking for unused values
4226 }
4227
4228 //buffer size
4229 if ( ddValues.contains( QgsPalLayerSettings::BufferSize ) )
4230 {
4231 buffer.setSize( ddValues.value( QgsPalLayerSettings::BufferSize ).toDouble() );
4232 changed = true;
4233 }
4234
4235 //buffer opacity
4236 if ( ddValues.contains( QgsPalLayerSettings::BufferOpacity ) )
4237 {
4238 buffer.setOpacity( ddValues.value( QgsPalLayerSettings::BufferOpacity ).toDouble() / 100.0 );
4239 changed = true;
4240 }
4241
4242 //buffer size units
4243 if ( ddValues.contains( QgsPalLayerSettings::BufferUnit ) )
4244 {
4245 QgsUnitTypes::RenderUnit bufunit = static_cast< QgsUnitTypes::RenderUnit >( ddValues.value( QgsPalLayerSettings::BufferUnit ).toInt() );
4246 buffer.setSizeUnit( bufunit );
4247 changed = true;
4248 }
4249
4250 //buffer color
4251 if ( ddValues.contains( QgsPalLayerSettings::BufferColor ) )
4252 {
4253 QVariant ddColor = ddValues.value( QgsPalLayerSettings::BufferColor );
4254 buffer.setColor( ddColor.value<QColor>() );
4255 changed = true;
4256 }
4257
4258 //buffer pen join style
4259 if ( ddValues.contains( QgsPalLayerSettings::BufferJoinStyle ) )
4260 {
4261 buffer.setJoinStyle( static_cast< Qt::PenJoinStyle >( ddValues.value( QgsPalLayerSettings::BufferJoinStyle ).toInt() ) );
4262 changed = true;
4263 }
4264
4265 //buffer blend mode
4266 if ( ddValues.contains( QgsPalLayerSettings::BufferBlendMode ) )
4267 {
4268 buffer.setBlendMode( static_cast< QPainter::CompositionMode >( ddValues.value( QgsPalLayerSettings::BufferBlendMode ).toInt() ) );
4269 changed = true;
4270 }
4271
4272 if ( changed )
4273 {
4274 QgsTextFormat format = tmpLyr.format();
4275 format.setBuffer( buffer );
4276 tmpLyr.setFormat( format );
4277 }
4278}
4279
4280void QgsPalLabeling::dataDefinedTextMask( QgsPalLayerSettings &tmpLyr,
4281 const QMap< QgsPalLayerSettings::Property, QVariant > &ddValues )
4282{
4283 if ( ddValues.isEmpty() )
4284 return;
4285
4286 QgsTextMaskSettings mask = tmpLyr.format().mask();
4287 bool changed = false;
4288
4289 // enabled ?
4290 if ( ddValues.contains( QgsPalLayerSettings::MaskEnabled ) )
4291 {
4292 mask.setEnabled( ddValues.value( QgsPalLayerSettings::MaskEnabled ).toBool() );
4293 changed = true;
4294 }
4295
4296 if ( !mask.enabled() )
4297 {
4298 if ( changed )
4299 {
4300 QgsTextFormat format = tmpLyr.format();
4301 format.setMask( mask );
4302 tmpLyr.setFormat( format );
4303 }
4304
4305 // tmpLyr.bufferSize > 0.0 && tmpLyr.bufferTransp < 100 figured in during evaluation
4306 return; // don't continue looking for unused values
4307 }
4308
4309 // buffer size
4310 if ( ddValues.contains( QgsPalLayerSettings::MaskBufferSize ) )
4311 {
4312 mask.setSize( ddValues.value( QgsPalLayerSettings::MaskBufferSize ).toDouble() );
4313 changed = true;
4314 }
4315
4316 // opacity
4317 if ( ddValues.contains( QgsPalLayerSettings::MaskOpacity ) )
4318 {
4319 mask.setOpacity( ddValues.value( QgsPalLayerSettings::MaskOpacity ).toDouble() / 100.0 );
4320 changed = true;
4321 }
4322
4323 // buffer size units
4324 if ( ddValues.contains( QgsPalLayerSettings::MaskBufferUnit ) )
4325 {
4326 QgsUnitTypes::RenderUnit bufunit = static_cast< QgsUnitTypes::RenderUnit >( ddValues.value( QgsPalLayerSettings::MaskBufferUnit ).toInt() );
4327 mask.setSizeUnit( bufunit );
4328 changed = true;
4329 }
4330
4331 // pen join style
4332 if ( ddValues.contains( QgsPalLayerSettings::MaskJoinStyle ) )
4333 {
4334 mask.setJoinStyle( static_cast< Qt::PenJoinStyle >( ddValues.value( QgsPalLayerSettings::MaskJoinStyle ).toInt() ) );
4335 changed = true;
4336 }
4337
4338 if ( changed )
4339 {
4340 QgsTextFormat format = tmpLyr.format();
4341 format.setMask( mask );
4342 tmpLyr.setFormat( format );
4343 }
4344}
4345
4346void QgsPalLabeling::dataDefinedShapeBackground( QgsPalLayerSettings &tmpLyr,
4347 const QMap< QgsPalLayerSettings::Property, QVariant > &ddValues )
4348{
4349 QgsTextBackgroundSettings background = tmpLyr.format().background();
4350 bool changed = false;
4351
4352 //shape draw
4353 if ( ddValues.contains( QgsPalLayerSettings::ShapeDraw ) )
4354 {
4355 background.setEnabled( ddValues.value( QgsPalLayerSettings::ShapeDraw ).toBool() );
4356 changed = true;
4357 }
4358
4359 if ( !background.enabled() )
4360 {
4361 if ( changed )
4362 {
4363 QgsTextFormat format = tmpLyr.format();
4364 format.setBackground( background );
4365 tmpLyr.setFormat( format );
4366 }
4367 return; // don't continue looking for unused values
4368 }
4369
4370 if ( ddValues.contains( QgsPalLayerSettings::ShapeKind ) )
4371 {
4372 background.setType( static_cast< QgsTextBackgroundSettings::ShapeType >( ddValues.value( QgsPalLayerSettings::ShapeKind ).toInt() ) );
4373 changed = true;
4374 }
4375
4376 if ( ddValues.contains( QgsPalLayerSettings::ShapeSVGFile ) )
4377 {
4378 background.setSvgFile( ddValues.value( QgsPalLayerSettings::ShapeSVGFile ).toString() );
4379 changed = true;
4380 }
4381
4382 if ( ddValues.contains( QgsPalLayerSettings::ShapeSizeType ) )
4383 {
4384 background.setSizeType( static_cast< QgsTextBackgroundSettings::SizeType >( ddValues.value( QgsPalLayerSettings::ShapeSizeType ).toInt() ) );
4385 changed = true;
4386 }
4387
4388 if ( ddValues.contains( QgsPalLayerSettings::ShapeSizeX ) )
4389 {
4390 QSizeF size = background.size();
4391 size.setWidth( ddValues.value( QgsPalLayerSettings::ShapeSizeX ).toDouble() );
4392 background.setSize( size );
4393 changed = true;
4394 }
4395 if ( ddValues.contains( QgsPalLayerSettings::ShapeSizeY ) )
4396 {
4397 QSizeF size = background.size();
4398 size.setHeight( ddValues.value( QgsPalLayerSettings::ShapeSizeY ).toDouble() );
4399 background.setSize( size );
4400 changed = true;
4401 }
4402
4403 if ( ddValues.contains( QgsPalLayerSettings::ShapeSizeUnits ) )
4404 {
4405 background.setSizeUnit( static_cast< QgsUnitTypes::RenderUnit >( ddValues.value( QgsPalLayerSettings::ShapeSizeUnits ).toInt() ) );
4406 changed = true;
4407 }
4408
4409 if ( ddValues.contains( QgsPalLayerSettings::ShapeRotationType ) )
4410 {
4411 background.setRotationType( static_cast< QgsTextBackgroundSettings::RotationType >( ddValues.value( QgsPalLayerSettings::ShapeRotationType ).toInt() ) );
4412 changed = true;
4413 }
4414
4415 if ( ddValues.contains( QgsPalLayerSettings::ShapeRotation ) )
4416 {
4417 background.setRotation( ddValues.value( QgsPalLayerSettings::ShapeRotation ).toDouble() );
4418 changed = true;
4419 }
4420
4421 if ( ddValues.contains( QgsPalLayerSettings::ShapeOffset ) )
4422 {
4423 background.setOffset( ddValues.value( QgsPalLayerSettings::ShapeOffset ).toPointF() );
4424 changed = true;
4425 }
4426
4427 if ( ddValues.contains( QgsPalLayerSettings::ShapeOffsetUnits ) )
4428 {
4429 background.setOffsetUnit( static_cast< QgsUnitTypes::RenderUnit >( ddValues.value( QgsPalLayerSettings::ShapeOffsetUnits ).toInt() ) );
4430 changed = true;
4431 }
4432
4433 if ( ddValues.contains( QgsPalLayerSettings::ShapeRadii ) )
4434 {
4435 background.setRadii( ddValues.value( QgsPalLayerSettings::ShapeRadii ).toSizeF() );
4436 changed = true;
4437 }
4438
4439 if ( ddValues.contains( QgsPalLayerSettings::ShapeRadiiUnits ) )
4440 {
4441 background.setRadiiUnit( static_cast< QgsUnitTypes::RenderUnit >( ddValues.value( QgsPalLayerSettings::ShapeRadiiUnits ).toInt() ) );
4442 changed = true;
4443 }
4444
4445 if ( ddValues.contains( QgsPalLayerSettings::ShapeBlendMode ) )
4446 {
4447 background.setBlendMode( static_cast< QPainter::CompositionMode >( ddValues.value( QgsPalLayerSettings::ShapeBlendMode ).toInt() ) );
4448 changed = true;
4449 }
4450
4451 if ( ddValues.contains( QgsPalLayerSettings::ShapeFillColor ) )
4452 {
4453 QVariant ddColor = ddValues.value( QgsPalLayerSettings::ShapeFillColor );
4454 background.setFillColor( ddColor.value<QColor>() );
4455 changed = true;
4456 }
4457
4458 if ( ddValues.contains( QgsPalLayerSettings::ShapeStrokeColor ) )
4459 {
4460 QVariant ddColor = ddValues.value( QgsPalLayerSettings::ShapeStrokeColor );
4461 background.setStrokeColor( ddColor.value<QColor>() );
4462 changed = true;
4463 }
4464
4465 if ( ddValues.contains( QgsPalLayerSettings::ShapeOpacity ) )
4466 {
4467 background.setOpacity( ddValues.value( QgsPalLayerSettings::ShapeOpacity ).toDouble() / 100.0 );
4468 changed = true;
4469 }
4470
4471 if ( ddValues.contains( QgsPalLayerSettings::ShapeStrokeWidth ) )
4472 {
4473 background.setStrokeWidth( ddValues.value( QgsPalLayerSettings::ShapeStrokeWidth ).toDouble() );
4474 changed = true;
4475 }
4476
4477 if ( ddValues.contains( QgsPalLayerSettings::ShapeStrokeWidthUnits ) )
4478 {
4479 background.setStrokeWidthUnit( static_cast< QgsUnitTypes::RenderUnit >( ddValues.value( QgsPalLayerSettings::ShapeStrokeWidthUnits ).toInt() ) );
4480 changed = true;
4481 }
4482
4483 if ( ddValues.contains( QgsPalLayerSettings::ShapeJoinStyle ) )
4484 {
4485 background.setJoinStyle( static_cast< Qt::PenJoinStyle >( ddValues.value( QgsPalLayerSettings::ShapeJoinStyle ).toInt() ) );
4486 changed = true;
4487 }
4488
4489 if ( changed )
4490 {
4491 QgsTextFormat format = tmpLyr.format();
4492 format.setBackground( background );
4493 tmpLyr.setFormat( format );
4494 }
4495}
4496
4497void QgsPalLabeling::dataDefinedDropShadow( QgsPalLayerSettings &tmpLyr,
4498 const QMap< QgsPalLayerSettings::Property, QVariant > &ddValues )
4499{
4500 QgsTextShadowSettings shadow = tmpLyr.format().shadow();
4501 bool changed = false;
4502
4503 //shadow draw
4504 if ( ddValues.contains( QgsPalLayerSettings::ShadowDraw ) )
4505 {
4506 shadow.setEnabled( ddValues.value( QgsPalLayerSettings::ShadowDraw ).toBool() );
4507 changed = true;
4508 }
4509
4510 if ( !shadow.enabled() )
4511 {
4512 if ( changed )
4513 {
4514 QgsTextFormat format = tmpLyr.format();
4515 format.setShadow( shadow );
4516 tmpLyr.setFormat( format );
4517 }
4518 return; // don't continue looking for unused values
4519 }
4520
4521 if ( ddValues.contains( QgsPalLayerSettings::ShadowUnder ) )
4522 {
4523 shadow.setShadowPlacement( static_cast< QgsTextShadowSettings::ShadowPlacement >( ddValues.value( QgsPalLayerSettings::ShadowUnder ).toInt() ) );
4524 changed = true;
4525 }
4526
4527 if ( ddValues.contains( QgsPalLayerSettings::ShadowOffsetAngle ) )
4528 {
4529 shadow.setOffsetAngle( ddValues.value( QgsPalLayerSettings::ShadowOffsetAngle ).toInt() );
4530 changed = true;
4531 }
4532
4533 if ( ddValues.contains( QgsPalLayerSettings::ShadowOffsetDist ) )
4534 {
4535 shadow.setOffsetDistance( ddValues.value( QgsPalLayerSettings::ShadowOffsetDist ).toDouble() );
4536 changed = true;
4537 }
4538
4539 if ( ddValues.contains( QgsPalLayerSettings::ShadowOffsetUnits ) )
4540 {
4541 shadow.setOffsetUnit( static_cast< QgsUnitTypes::RenderUnit >( ddValues.value( QgsPalLayerSettings::ShadowOffsetUnits ).toInt() ) );
4542 changed = true;
4543 }
4544
4545 if ( ddValues.contains( QgsPalLayerSettings::ShadowRadius ) )
4546 {
4547 shadow.setBlurRadius( ddValues.value( QgsPalLayerSettings::ShadowRadius ).toDouble() );
4548 changed = true;
4549 }
4550
4551 if ( ddValues.contains( QgsPalLayerSettings::ShadowRadiusUnits ) )
4552 {
4553 shadow.setBlurRadiusUnit( static_cast< QgsUnitTypes::RenderUnit >( ddValues.value( QgsPalLayerSettings::ShadowRadiusUnits ).toInt() ) );
4554 changed = true;
4555 }
4556
4557 if ( ddValues.contains( QgsPalLayerSettings::ShadowColor ) )
4558 {
4559 QVariant ddColor = ddValues.value( QgsPalLayerSettings::ShadowColor );
4560 shadow.setColor( ddColor.value<QColor>() );
4561 changed = true;
4562 }
4563
4564 if ( ddValues.contains( QgsPalLayerSettings::ShadowOpacity ) )
4565 {
4566 shadow.setOpacity( ddValues.value( QgsPalLayerSettings::ShadowOpacity ).toDouble() / 100.0 );
4567 changed = true;
4568 }
4569
4570 if ( ddValues.contains( QgsPalLayerSettings::ShadowScale ) )
4571 {
4572 shadow.setScale( ddValues.value( QgsPalLayerSettings::ShadowScale ).toInt() );
4573 changed = true;
4574 }
4575
4576
4577 if ( ddValues.contains( QgsPalLayerSettings::ShadowBlendMode ) )
4578 {
4579 shadow.setBlendMode( static_cast< QPainter::CompositionMode >( ddValues.value( QgsPalLayerSettings::ShadowBlendMode ).toInt() ) );
4580 changed = true;
4581 }
4582
4583 if ( changed )
4584 {
4585 QgsTextFormat format = tmpLyr.format();
4586 format.setShadow( shadow );
4587 tmpLyr.setFormat( format );
4588 }
4589}
4590
4591void QgsPalLabeling::drawLabelCandidateRect( pal::LabelPosition *lp, QPainter *painter, const QgsMapToPixel *xform, QList<QgsLabelCandidate> *candidates )
4592{
4593 QgsPointXY outPt = xform->transform( lp->getX(), lp->getY() );
4594
4595 painter->save();
4596
4597#if 0 // TODO: generalize some of this
4598 double w = lp->getWidth();
4599 double h = lp->getHeight();
4600 double cx = lp->getX() + w / 2.0;
4601 double cy = lp->getY() + h / 2.0;
4602 double scale = 1.0 / xform->mapUnitsPerPixel();
4603 double rotation = xform->mapRotation();
4604 double sw = w * scale;
4605 double sh = h * scale;
4606 QRectF rect( -sw / 2, -sh / 2, sw, sh );
4607
4608 painter->translate( xform->transform( QPointF( cx, cy ) ).toQPointF() );
4609 if ( rotation )
4610 {
4611 // Only if not horizontal
4612 if ( lp->getFeaturePart()->getLayer()->getArrangement() != P_POINT &&
4613 lp->getFeaturePart()->getLayer()->getArrangement() != P_POINT_OVER &&
4614 lp->getFeaturePart()->getLayer()->getArrangement() != P_HORIZ )
4615 {
4616 painter->rotate( rotation );
4617 }
4618 }
4619 painter->translate( rect.bottomLeft() );
4620 painter->rotate( -lp->getAlpha() * 180 / M_PI );
4621 painter->translate( -rect.bottomLeft() );
4622#else
4623 QgsPointXY outPt2 = xform->transform( lp->getX() + lp->getWidth(), lp->getY() + lp->getHeight() );
4624 QRectF rect( 0, 0, outPt2.x() - outPt.x(), outPt2.y() - outPt.y() );
4625 painter->translate( QPointF( outPt.x(), outPt.y() ) );
4626 painter->rotate( -lp->getAlpha() * 180 / M_PI );
4627#endif
4628
4629 if ( lp->conflictsWithObstacle() )
4630 {
4631 painter->setPen( QColor( 255, 0, 0, 64 ) );
4632 }
4633 else
4634 {
4635 painter->setPen( QColor( 0, 0, 0, 64 ) );
4636 }
4637 painter->drawRect( rect );
4638 painter->restore();
4639
4640 // save the rect
4641 rect.moveTo( outPt.x(), outPt.y() );
4642 if ( candidates )
4643 candidates->append( QgsLabelCandidate( rect, lp->cost() * 1000 ) );
4644
4645 // show all parts of the multipart label
4646 if ( lp->nextPart() )
4647 drawLabelCandidateRect( lp->nextPart(), painter, xform, candidates );
4648}
@ Success
Operation succeeded.
LabelOffsetType
Behavior modifier for label offset and distance, only applies in some label placement modes.
Definition qgis.h:608
@ FromPoint
Offset distance applies from point geometry.
@ FromSymbolBounds
Offset distance applies from rendered symbol bounds.
LabelPlacement
Placement modes which determine how label candidates are generated for a feature.
Definition qgis.h:561
@ OverPoint
Arranges candidates over a point (or centroid of a polygon), or at a preset offset from the point....
@ Curved
Arranges candidates following the curvature of a line feature. Applies to line layers only.
@ AroundPoint
Arranges candidates in a circle around a point (or centroid of a polygon). Applies to point or polygo...
@ Line
Arranges candidates parallel to a generalised line representing the feature or parallel to a polygon'...
@ Free
Arranges candidates scattered throughout a polygon feature. Candidates are rotated to respect the pol...
@ OrderedPositionsAroundPoint
Candidates are placed in predefined positions around a point. Preference is given to positions with g...
@ Horizontal
Arranges horizontal candidates scattered throughout a polygon feature. Applies to polygon layers only...
@ PerimeterCurved
Arranges candidates following the curvature of a polygon's boundary. Applies to polygon layers only.
@ OutsidePolygons
Candidates are placed outside of polygon boundaries. Applies to polygon layers only....
@ Warning
Warning message.
Definition qgis.h:117
@ Labeling
Labeling-specific layout mode.
@ Rectangle
Text within rectangle layout mode.
Capitalization
String capitalization options.
Definition qgis.h:1762
@ AllSmallCaps
Force all characters to small caps (since QGIS 3.24)
@ MixedCase
Mixed case, ie no change.
@ AllLowercase
Convert all characters to lowercase.
@ TitleCase
Simple title case conversion - does not fully grammatically parse the text and uses simple rules only...
@ SmallCaps
Mixed case small caps (since QGIS 3.24)
@ ForceFirstLetterToCapital
Convert just the first letter of each word to uppercase, leave the rest untouched.
@ AllUppercase
Convert all characters to uppercase.
LabelQuadrantPosition
Label quadrant positions.
Definition qgis.h:622
TextOrientation
Text orientations.
Definition qgis.h:1430
@ Vertical
Vertically oriented text.
@ RotationBased
Horizontally or vertically oriented text based on rotation (only available for map labeling)
@ Horizontal
Horizontally oriented text.
UnplacedLabelVisibility
Unplaced label visibility.
Definition qgis.h:534
@ FollowEngineSetting
Respect the label engine setting.
LabelMultiLineAlignment
Text alignment for multi-line labels.
Definition qgis.h:658
@ FollowPlacement
Alignment follows placement of label, e.g., labels to the left of a feature will be drawn with right ...
@ Antialiasing
Use antialiasing while drawing.
LabelOverlapHandling
Label overlap handling.
Definition qgis.h:546
@ AllowOverlapAtNoCost
Labels may freely overlap other labels, at no cost.
@ AllowOverlapIfRequired
Avoids overlapping labels when possible, but permit overlaps if labels for features cannot otherwise ...
@ PreventOverlap
Do not allow labels to overlap other labels.
static const double UI_SCALE_FACTOR
UI scaling factor.
Definition qgis.h:2304
@ MiddleLeft
Label on left of point.
@ TopRight
Label on top-right of point.
@ MiddleRight
Label on right of point.
@ TopSlightlyRight
Label on top of point, slightly right of center.
@ BottomRight
Label on bottom right of point.
@ BottomLeft
Label on bottom-left of point.
@ BottomSlightlyRight
Label below point, slightly right of center.
@ TopLeft
Label on top-left of point.
UpsideDownLabelHandling
Handling techniques for upside down labels.
Definition qgis.h:643
@ FlipUpsideDownLabels
Upside-down labels (90 <= angle < 270) are shown upright.
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.
void render(QgsRenderContext &context, const QRectF &rect, const double angle, const QgsGeometry &anchor, QgsCalloutContext &calloutContext)
Renders the callout onto the specified render context.
virtual void startRender(QgsRenderContext &context)
Prepares the callout for rendering on the specified render context.
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.
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
QgsFeatureId id
Definition qgsfeature.h:64
QgsGeometry geometry
Definition qgsfeature.h:67
QVariant attribute(const QString &name) const
Lookup attribute value by attribute name.
void setGeometry(const QgsGeometry &geometry)
Set the feature's geometry.
Container of fields for a vector layer.
Definition qgsfields.h:45
int lookupField(const QString &fieldName) const
Looks up field's index from the field name.
QString processFontFamilyName(const QString &name) const
Processes a font family name, applying any matching fontFamilyReplacements() to the name.
static 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.
QgsGeometry clipped(const QgsRectangle &rectangle)
Clips the geometry using the specified rectangle.
static QgsGeometry fromRect(const QgsRectangle &rect)
Creates a new geometry from a QgsRectangle.
double length() const
Returns the planar, 2-dimensional length of geometry.
QgsAbstractGeometry::const_part_iterator const_parts_begin() const
Returns STL-style const iterator pointing to the first part of the geometry.
static QgsGeometry collectGeometry(const QVector< QgsGeometry > &geometries)
Creates a new multipart geometry from a list of QgsGeometry objects.
QgsGeometry makeValid(Qgis::MakeValidMethod method=Qgis::MakeValidMethod::Linework, bool keepCollapsed=false) const
Attempts to make an invalid geometry valid without losing vertices.
QString lastError() const
Returns an error string referring to the last error encountered either when this geometry was created...
bool isAxisParallelRectangle(double maximumDeviation, bool simpleRectanglesOnly=false) const
Returns true if the geometry is a polygon that is almost an axis-parallel rectangle.
Qgis::GeometryOperationResult transform(const QgsCoordinateTransform &ct, Qgis::TransformDirection direction=Qgis::TransformDirection::Forward, bool transformZ=false)
Transforms this geometry as described by the coordinate transform ct.
QgsAbstractGeometry * get()
Returns a modifiable (non-const) reference to the underlying abstract geometry primitive.
const QgsAbstractGeometry * constGet() const
Returns a non-modifiable (const) reference to the underlying abstract geometry primitive.
bool contains(const QgsPointXY *p) const
Returns true if the geometry contains the point p.
QgsPointXY asPoint() const
Returns the contents of the geometry as a 2-dimensional point.
void filterVertices(const std::function< bool(const QgsPoint &) > &filter)
Filters the vertices from the geometry in place, removing any which do not return true for the filter...
bool isGeosValid(Qgis::GeometryValidityFlags flags=Qgis::GeometryValidityFlags()) const
Checks validity of the geometry using GEOS.
static QgsGeometry fromPointXY(const QgsPointXY &point)
Creates a new geometry from a QgsPointXY object.
QgsWkbTypes::GeometryType type
double area() const
Returns the planar, 2-dimensional area of the geometry.
bool isMultipart() const
Returns true if WKB of the geometry is of WKBMulti* type.
QgsGeometry centroid() const
Returns the center of mass of a geometry.
QgsGeometry intersection(const QgsGeometry &geometry, const QgsGeometryParameters &parameters=QgsGeometryParameters()) const
Returns a geometry representing the points shared by this geometry and other.
QgsGeometry mergeLines() const
Merges any connected lines in a LineString/MultiLineString geometry and converts them to single line ...
bool isEmpty() const
Returns true if the geometry is empty (eg a linestring with no vertices, or a collection with no geom...
QgsAbstractGeometry::const_part_iterator const_parts_end() const
Returns STL-style iterator pointing to the imaginary part after the last part of the geometry.
QgsGeometry simplify(double tolerance) const
Returns a simplified version of this geometry using a specified tolerance value.
QgsRectangle boundingBox() const
Returns the bounding box of the geometry.
Qgis::GeometryOperationResult rotate(double rotation, const QgsPointXY &center)
Rotate this geometry around the Z axis.
bool convertGeometryCollectionToSubclass(QgsWkbTypes::GeometryType geomType)
Converts geometry collection to a the desired geometry type subclass (multi-point,...
QString asWkt(int precision=17) const
Exports the geometry to WKT.
static geos::unique_ptr asGeos(const QgsGeometry &geometry, double precision=0)
Returns a geos geometry - caller takes ownership of the object (should be deleted with GEOSGeom_destr...
Definition qgsgeos.cpp:220
Represents a label candidate.
Contains settings related to how the label engine places and formats labels for line features (or pol...
AnchorType
Line anchor types.
void setPlacementFlags(QgsLabeling::LinePlacementFlags flags)
Returns the line placement flags, which dictate how line labels can be placed above or below the line...
QgsLabeling::LinePlacementFlags placementFlags() const
Returns the line placement flags, which dictate how line labels can be placed above or below the line...
bool reverseDirectionSymbol() const
Returns true if direction symbols should be reversed.
void setLineAnchorPercent(double percent)
Sets the percent along the line at which labels should be placed.
DirectionSymbolPlacement directionSymbolPlacement() const
Returns the placement for direction symbols.
AnchorClipping
Clipping behavior for line anchor calculation.
@ UseEntireLine
Entire original feature line geometry is used when calculating the line anchor for labels.
@ UseVisiblePartsOfLine
Only visible parts of lines are considered when calculating the line anchor for labels.
void setDirectionSymbolPlacement(DirectionSymbolPlacement placement)
Sets the placement for direction symbols.
AnchorType anchorType() const
Returns the line anchor type, which dictates how the lineAnchorPercent() setting is handled.
QString leftDirectionSymbol() const
Returns the string to use for left direction arrows.
void setAnchorTextPoint(AnchorTextPoint point)
Sets the line anchor text point, which dictates which part of the label text should be placed at the ...
void setLeftDirectionSymbol(const QString &symbol)
Sets the string to use for left direction arrows.
QgsMapUnitScale overrunDistanceMapUnitScale() const
Returns the map unit scale for label overrun distance.
AnchorTextPoint anchorTextPoint() const
Returns the line anchor text point, which dictates which part of the label text should be placed at t...
double overrunDistance() const
Returns the distance which labels are allowed to overrun past the start or end of line features.
QgsUnitTypes::RenderUnit overrunDistanceUnit() const
Returns the units for label overrun distance.
void setMergeLines(bool merge)
Sets whether connected line features with identical label text should be merged prior to generating l...
DirectionSymbolPlacement
Placement options for direction symbols.
@ SymbolLeftRight
Place direction symbols on left/right of label.
@ SymbolAbove
Place direction symbols on above label.
@ SymbolBelow
Place direction symbols on below label.
void setRightDirectionSymbol(const QString &symbol)
Sets the string to use for right direction arrows.
@ CenterOfText
Anchor using center of text.
QString rightDirectionSymbol() const
Returns the string to use for right direction arrows.
void setAnchorClipping(AnchorClipping clipping)
Sets the line anchor clipping mode, which dictates how line strings are clipped before calculating th...
void setOverrunDistanceMapUnitScale(const QgsMapUnitScale &scale)
Sets the map unit scale for label overrun distance.
bool addDirectionSymbol() const
Returns true if '<' or '>' (or custom strings set via leftDirectionSymbol and rightDirectionSymbol) w...
double lineAnchorPercent() const
Returns the percent along the line at which labels should be placed.
bool mergeLines() const
Returns true if connected line features with identical label text should be merged prior to generatin...
void setAnchorType(AnchorType type)
Sets the line anchor type, which dictates how the lineAnchorPercent() setting is handled.
void setOverrunDistance(double distance)
Sets the distance which labels are allowed to overrun past the start or end of line features.
AnchorClipping anchorClipping() const
Returns the line anchor clipping mode, which dictates how line strings are clipped before calculating...
void setOverrunDistanceUnit(const QgsUnitTypes::RenderUnit &unit)
Sets the unit for label overrun distance.
void setReverseDirectionSymbol(bool reversed)
Sets whether the direction symbols should be reversed.
void setAddDirectionSymbol(bool enabled)
Sets whether '<' or '>' (or custom strings set via leftDirectionSymbol and rightDirectionSymbol) will...
void updateDataDefinedProperties(const QgsPropertyCollection &properties, QgsExpressionContext &context)
Updates the thinning settings to respect any data defined properties set within the specified propert...
Contains settings related to how the label engine treats features as obstacles.
double factor() const
Returns the obstacle factor, where 1.0 = default, < 1.0 more likely to be covered by labels,...
void setType(ObstacleType type)
Controls how features act as obstacles for labels.
ObstacleType type() const
Returns how features act as obstacles for labels.
void setIsObstacle(bool isObstacle)
Sets whether features are obstacles to labels of other layers.
void setFactor(double factor)
Sets the obstacle factor, where 1.0 = default, < 1.0 more likely to be covered by labels,...
ObstacleType
Valid obstacle types, which affect how features within the layer will act as obstacles for labels.
void updateDataDefinedProperties(const QgsPropertyCollection &properties, QgsExpressionContext &context)
Updates the obstacle settings to respect any data defined properties set within the specified propert...
void setObstacleGeometry(const QgsGeometry &obstacleGeom)
Sets the label's obstacle geometry, if different to the feature geometry.
bool isObstacle() const
Returns true if the features are obstacles to labels of other layers.
void setOverlapHandling(Qgis::LabelOverlapHandling handling)
Sets the technique used to handle overlapping labels.
Qgis::LabelOverlapHandling overlapHandling() const
Returns the technique used to handle overlapping labels.
bool allowDegradedPlacement() const
Returns true if labels can be placed in inferior fallback positions if they cannot otherwise be place...
void setAllowDegradedPlacement(bool allow)
Sets whether labels can be placed in inferior fallback positions if they cannot otherwise be placed.
Contains settings related to how the label engine removes candidate label positions and reduces the n...
void setMaximumNumberLabels(int number)
Sets the maximum number of labels which should be drawn for this layer.
double minimumFeatureSize() const
Returns the minimum feature size (in millimeters) for a feature to be labelled.
int maximumNumberLabels() const
Returns the maximum number of labels which should be drawn for this layer.
void updateDataDefinedProperties(const QgsPropertyCollection &properties, QgsExpressionContext &context)
Updates the thinning settings to respect any data defined properties set within the specified propert...
void setLimitNumberLabelsEnabled(bool enabled)
Sets whether the the number of labels drawn for the layer should be limited.
bool limitNumberOfLabelsEnabled() const
Returns true if the number of labels drawn for the layer should be limited.
void setMinimumFeatureSize(double size)
Sets the minimum feature size (in millimeters) for a feature to be labelled.
static QString encodePredefinedPositionOrder(const QVector< Qgis::LabelPredefinedPointPosition > &positions)
Encodes an ordered list of predefined point label positions to a string.
static QVector< Qgis::LabelPredefinedPointPosition > decodePredefinedPositionOrder(const QString &positionString)
Decodes a string to an ordered list of predefined point label positions.
@ AllowPlacementInsideOfPolygon
Labels can be placed inside a polygon feature.
Definition qgslabeling.h:55
@ AllowPlacementOutsideOfPolygon
Labels can be placed outside of a polygon feature.
Definition qgslabeling.h:54
Line string geometry type, with support for z-dimension and m-values.
Base class for all map layer types.
Definition qgsmaplayer.h:73
Q_INVOKABLE QVariant customProperty(const QString &value, const QVariant &defaultValue=QVariant()) const
Read a custom property from layer.
void removeCustomProperty(const QString &key)
Remove a custom property from layer.
QgsMapLayerType type
Definition qgsmaplayer.h:80
T customEnumProperty(const QString &key, const T &defaultValue)
Returns the property value for a property based on an enum.
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.
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.
double mapRotation() const
Returns the current map rotation in degrees (clockwise).
void setParameters(double mapUnitsPerPixel, double centerX, double centerY, int widthPixels, int heightPixels, double rotation)
Sets parameters for use in transforming coordinates.
double maxScale
The maximum scale, or 0.0 if unset.
double minScale
The minimum scale, or 0.0 if unset.
The QgsMargins class defines the four margins of a rectangle.
Definition qgsmargins.h:38
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::MessageLevel::Warning, bool notifyUser=true)
Adds a message to the log instance (and creates it if necessary).
Multi surface geometry collection.
static void drawLabelCandidateRect(pal::LabelPosition *lp, QPainter *painter, const QgsMapToPixel *xform, QList< QgsLabelCandidate > *candidates=nullptr)
static QStringList splitToLines(const QString &text, const QString &wrapCharacter, int autoWrapLength=0, bool useMaxLineLengthWhenAutoWrapping=true)
Splits a text string to a list of separate lines, using a specified wrap character (wrapCharacter).
static QgsGeometry prepareGeometry(const QgsGeometry &geometry, QgsRenderContext &context, const QgsCoordinateTransform &ct, const QgsGeometry &clipGeometry=QgsGeometry(), bool mergeLines=false)
Prepares a geometry for registration with PAL.
static bool geometryRequiresPreparation(const QgsGeometry &geometry, QgsRenderContext &context, const QgsCoordinateTransform &ct, const QgsGeometry &clipGeometry=QgsGeometry(), bool mergeLines=false)
Checks whether a geometry requires preparation before registration with PAL.
static bool staticWillUseLayer(const QgsMapLayer *layer)
Called to find out whether a specified layer is used for labeling.
static QStringList splitToGraphemes(const QString &text)
Splits a text string to a list of graphemes, which are the smallest allowable character divisions in ...
Contains settings for how a map layer will be labeled.
bool fitInPolygonOnly
true if only labels which completely fit within a polygon are allowed.
double yOffset
Vertical offset of label.
QgsMapUnitScale labelOffsetMapUnitScale
Map unit scale for label offset.
int fontMaxPixelSize
Maximum pixel size for showing rendered map unit labels (1 - 10000).
std::unique_ptr< QgsLabelFeature > registerFeatureWithDetails(const QgsFeature &feature, QgsRenderContext &context, QgsGeometry obstacleGeometry=QgsGeometry(), const QgsSymbol *symbol=nullptr)
Registers a feature for labeling.
double maxCurvedCharAngleIn
Maximum angle between inside curved label characters (valid range 20.0 to 60.0).
void setFormat(const QgsTextFormat &format)
Sets the label text formatting settings, e.g., font settings, buffer settings, etc.
double zIndex
Z-Index of label, where labels with a higher z-index are rendered on top of labels with a lower z-ind...
const QgsMapToPixel * xform
void startRender(QgsRenderContext &context)
Prepares the label settings for rendering.
QString wrapChar
Wrapping character string.
QSet< QString > referencedFields(const QgsRenderContext &context) const
Returns all field names referenced by the configuration (e.g.
Qgis::LabelOffsetType offsetType
Offset type for layer (only applies in certain placement modes)
double xOffset
Horizontal offset of label.
Qgis::LabelPlacement placement
Label placement mode.
QgsCoordinateTransform ct
bool drawLabels
Whether to draw labels for this layer.
bool fontLimitPixelSize
true if label sizes should be limited by pixel size.
QgsExpression * getLabelExpression()
Returns the QgsExpression for this label settings.
QString legendString() const
legendString
double minimumScale
The minimum map scale (i.e.
void registerFeature(const QgsFeature &f, QgsRenderContext &context)
Registers a feature for labeling.
QgsPalLayerSettings & operator=(const QgsPalLayerSettings &s)
copy operator - only copies the permanent members
QgsWkbTypes::GeometryType geometryGeneratorType
The type of the result geometry of the geometry generator.
void readXml(const QDomElement &elem, const QgsReadWriteContext &context)
Read settings from a DOM element.
QgsUnitTypes::RenderUnit offsetUnits
Units for offsets of label.
Qgis::LabelQuadrantPosition quadOffset
Sets the quadrant in which to offset labels from feature.
bool scaleVisibility
Set to true to limit label visibility to a range of scales.
double repeatDistance
Distance for repeating labels for a single feature.
QgsPropertyCollection & dataDefinedProperties()
Returns a reference to the label's property collection, used for data defined overrides.
QgsUnitTypes::AngleUnit rotationUnit() const
Unit for rotation of labels.
bool geometryGeneratorEnabled
Defines if the geometry generator is enabled or not. If disabled, the standard geometry will be taken...
Qgis::LabelMultiLineAlignment multilineAlign
Horizontal alignment of multi-line labels.
bool centroidInside
true if centroid positioned labels must be placed inside their corresponding feature polygon,...
int priority
Label priority.
QDomElement writeXml(QDomDocument &doc, const QgsReadWriteContext &context) const
Write settings into a DOM element.
bool labelPerPart
true if every part of a multi-part feature should be labeled.
QVector< Qgis::LabelPredefinedPointPosition > predefinedPositionOrder
Ordered list of predefined label positions for points.
QgsUnitTypes::RenderUnit distUnits
Units the distance from feature to the label.
QgsCallout * callout() const
Returns the label callout renderer, responsible for drawing label callouts.
QgsUnitTypes::RenderUnit repeatDistanceUnit
Units for repeating labels for a single feature.
int fontMinPixelSize
Minimum pixel size for showing rendered map unit labels (1 - 1000).
double angleOffset
Label rotation, in degrees clockwise.
double maxCurvedCharAngleOut
Maximum angle between outside curved label characters (valid range -20.0 to -95.0)
Property
Data definable properties.
@ MaskEnabled
Whether the mask is enabled.
@ LabelRotation
Label rotation.
@ PositionY
Y-coordinate data defined label position.
@ OverlapHandling
Overlap handling technique (since QGIS 3.26)
@ OverrunDistance
Distance which labels can extend past either end of linear features.
@ Strikeout
Use strikeout.
@ FontStyle
Font style name.
@ PositionX
X-coordinate data defined label position.
@ CalloutDraw
Show callout.
@ Underline
Use underline.
@ FontLetterSpacing
Letter spacing.
@ MaskJoinStyle
Mask join style.
@ PositionPoint
Point-coordinate data defined label position.
@ Bold
Use bold style.
@ MaxScale
Max scale (deprecated, for old project compatibility only)
@ Hali
Horizontal alignment for data defined label position (Left, Center, Right)
@ FontStretchFactor
Font stretch factor, since QGIS 3.24.
@ FontTransp
Text transparency (deprecated)
@ MaskBufferUnit
Mask buffer size unit.
@ LabelAllParts
Whether all parts of multi-part features should be labeled.
@ AllowDegradedPlacement
Allow degraded label placements (since QGIS 3.26)
@ ShadowOpacity
Shadow opacity.
@ BufferOpacity
Buffer opacity.
@ LineAnchorPercent
Portion along line at which labels should be anchored (since QGIS 3.16)
@ ShapeOpacity
Shape opacity.
@ FontSizeUnit
Font size units.
@ Rotation
Label rotation (deprecated, for old project compatibility only)
@ Italic
Use italic style.
@ ShapeTransparency
Shape transparency (deprecated)
@ ShadowTransparency
Shadow transparency (deprecated)
@ MinScale
Min scale (deprecated, for old project compatibility only)
@ FontWordSpacing
Word spacing.
@ MaskBufferSize
Mask buffer size.
@ LineAnchorType
Line anchor type (since QGIS 3.26)
@ LineAnchorClipping
Clipping mode for line anchor calculation (since QGIS 3.20)
@ MinimumScale
Minimum map scale (ie most "zoomed out")
@ FontBlendMode
Text blend mode.
@ MaximumScale
Maximum map scale (ie most "zoomed in")
@ MaskOpacity
Mask opacity.
@ BufferTransp
Buffer transparency (deprecated)
@ PolygonLabelOutside
Whether labels outside a polygon feature are permitted, or should be forced (since QGIS 3....
@ FontCase
Label text case.
@ LineAnchorTextPoint
Line anchor text point (since QGIS 3.26)
@ LinePlacementOptions
Line placement flags.
@ Vali
Vertical alignment for data defined label position (Bottom, Base, Half, Cap, Top)
@ FontOpacity
Text opacity.
void setRotationUnit(QgsUnitTypes::AngleUnit angleUnit)
Set unit for rotation of labels.
bool isExpression
true if this label is made from a expression string, e.g., FieldName || 'mm'
bool preserveRotation
True if label rotation should be preserved during label pin/unpin operations.
static QPixmap labelSettingsPreviewPixmap(const QgsPalLayerSettings &settings, QSize size, const QString &previewText=QString(), int padding=0)
Returns a pixmap preview for label settings.
bool plusSign
Whether '+' signs should be prepended to positive numeric labels.
bool prepare(QgsRenderContext &context, QSet< QString > &attributeNames, const QgsFields &fields, const QgsMapSettings &mapSettings, const QgsCoordinateReferenceSystem &crs)
Prepare for registration of features.
QString geometryGenerator
The geometry generator expression. Null if disabled.
const QgsLabelLineSettings & lineSettings() const
Returns the label line settings, which contain settings related to how the label engine places and fo...
QgsMapUnitScale distMapUnitScale
Map unit scale for label feature distance.
QgsStringReplacementCollection substitutions
Substitution collection for automatic text substitution with labels.
Q_DECL_DEPRECATED QColor previewBkgrdColor
const QgsFeature * mCurFeat
int decimals
Number of decimal places to show for numeric labels.
double dist
Distance from feature to the label.
QgsMapUnitScale repeatDistanceMapUnitScale
Map unit scale for repeating labels for a single feature.
bool centroidWhole
true if feature centroid should be calculated from the whole feature, or false if only the visible pa...
Qgis::UpsideDownLabelHandling upsidedownLabels
Controls whether upside down labels are displayed and how they are handled.
QString fieldName
Name of field (or an expression) to use for label text.
void calculateLabelSize(const QFontMetricsF *fm, const QString &text, double &labelX, double &labelY, const QgsFeature *f=nullptr, QgsRenderContext *context=nullptr, double *rotatedLabelX=nullptr, double *rotatedLabelY=nullptr, QgsTextDocument *document=nullptr, QgsTextDocumentMetrics *documentMetrics=nullptr)
Calculates the space required to render the provided text in map units.
bool formatNumbers
Set to true to format numeric label text as numbers (e.g.
bool containsAdvancedEffects() const
Returns true if any component of the label settings requires advanced effects such as blend modes,...
QgsWkbTypes::GeometryType layerType
Geometry type of layers associated with these settings.
const QgsTextFormat & format() const
Returns the label text formatting settings, e.g., font settings, buffer settings, etc.
void setCallout(QgsCallout *callout)
Sets the label callout renderer, responsible for drawing label callouts.
double maximumScale
The maximum map scale (i.e.
int autoWrapLength
If non-zero, indicates that label text should be automatically wrapped to (ideally) the specified num...
Qgis::UnplacedLabelVisibility unplacedVisibility() const
Returns the layer's unplaced label visibility.
bool useMaxLineLengthForAutoWrap
If true, indicates that when auto wrapping label text the autoWrapLength length indicates the maximum...
void setUnplacedVisibility(Qgis::UnplacedLabelVisibility visibility)
Sets the layer's unplaced label visibility.
static const QgsPropertiesDefinition & propertyDefinitions()
Returns the labeling property definitions.
void stopRender(QgsRenderContext &context)
Finalises the label settings after use.
bool useSubstitutions
True if substitutions should be applied.
A class to represent a 2D point.
Definition qgspointxy.h:59
double distance(double x, double y) const
Returns the distance between this point and a specified x, y coordinate.
Definition qgspointxy.h:211
double y
Definition qgspointxy.h:63
double x
Definition qgspointxy.h:62
Point geometry type, with support for z-dimension and m-values.
Definition qgspoint.h:49
double x
Definition qgspoint.h:52
bool isEmpty() const override
Returns true if the geometry is empty.
Definition qgspoint.cpp:767
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.
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.
bool contains(const QgsRectangle &rect) const
Returns true when rectangle contains other rectangle.
double width() const
Returns the width of the rectangle.
QgsPointXY center() const
Returns the center point of the rectangle.
void grow(double delta)
Grows the rectangle in place by the specified amount.
double height() const
Returns the height of the rectangle.
QgsCoordinateReferenceSystem crs() const
Returns the associated coordinate reference system, or an invalid CRS if no reference system is set.
A QgsGeometry with associated coordinate reference system.
Contains information about the context of a rendering operation.
double scaleFactor() const
Returns the scaling factor for the render to convert painter units to physical sizes.
void setScaleFactor(double factor)
Sets the scaling factor for the render to convert painter units to physical sizes.
void setUseAdvancedEffects(bool enabled)
Used to enable or disable advanced effects such as blend modes.
double rendererScale() const
Returns the renderer map scale.
double convertToPainterUnits(double size, QgsUnitTypes::RenderUnit unit, const QgsMapUnitScale &scale=QgsMapUnitScale(), Qgis::RenderSubcomponentProperty property=Qgis::RenderSubcomponentProperty::Generic) const
Converts a size from the specified units to painter units (pixels).
QgsExpressionContext & expressionContext()
Gets the expression context.
QgsGeometry featureClipGeometry() const
Returns the geometry to use to clip features at render time.
QgsRectangle mapExtent() const
Returns the original extent of the map being rendered.
void setFlag(Qgis::RenderContextFlag flag, bool on=true)
Enable or disable a particular flag (other flags are not affected)
const QgsVectorSimplifyMethod & vectorSimplifyMethod() const
Returns the simplification settings to use when rendering vector layers.
const QgsMapToPixel & mapToPixel() const
Returns the context's map to pixel transform, which transforms between map coordinates and device coo...
void setMapToPixel(const QgsMapToPixel &mtp)
Sets the context's map to pixel transform, which transforms between map coordinates and device coordi...
double convertToMapUnits(double size, QgsUnitTypes::RenderUnit unit, const QgsMapUnitScale &scale=QgsMapUnitScale()) const
Converts a size from the specified units to map units.
void setPainter(QPainter *p)
Sets the destination QPainter for the render operation.
QgsCoordinateTransform coordinateTransform() const
Returns the current coordinate transform for the context.
const QgsPathResolver & pathResolver() const
Returns the path resolver for conversion between relative and absolute paths during rendering operati...
void readXml(const QDomElement &elem)
Reads the collection state from an XML element.
QString process(const QString &input) const
Processes a given input string, applying any valid replacements which should be made using QgsStringR...
void writeXml(QDomElement &elem, QDomDocument &doc) const
Writes the collection state to an XML element.
static QString capitalize(const QString &string, Qgis::Capitalization capitalization)
Converts a string by applying capitalization rules to the string.
static QString wordWrap(const QString &string, int length, bool useMaxLineLength=true, const QString &customDelimiter=QString())
Automatically wraps a string by inserting new line characters at appropriate locations in the string.
QgsTextFormat defaultTextFormat(QgsStyle::TextFormatContext context=QgsStyle::TextFormatContext::Labeling) const
Returns the default text format to use for new text based objects in the specified context.
static QgsStyle * defaultStyle()
Returns default application-wide style.
Definition qgsstyle.cpp:145
@ Labeling
Text format used in labeling.
static Qt::PenJoinStyle decodePenJoinStyle(const QString &str)
static QString encodeMapUnitScale(const QgsMapUnitScale &mapUnitScale)
static QColor decodeColor(const QString &str)
static QPointF toPoint(const QVariant &value, bool *ok=nullptr)
Converts a value to a point.
static QgsMapUnitScale decodeMapUnitScale(const QString &str)
static QSizeF toSize(const QVariant &value, bool *ok=nullptr)
Converts a value to a size.
static QPainter::CompositionMode decodeBlendMode(const QString &s)
static QString encodeSize(QSizeF size)
Encodes a QSizeF to a string.
static QString svgSymbolNameToPath(const QString &name, const QgsPathResolver &pathResolver)
Determines an SVG symbol's path from its name.
static QString encodeColor(const QColor &color)
static QString encodePoint(QPointF point)
Encodes a QPointF to a string.
static QString encodePenJoinStyle(Qt::PenJoinStyle style)
Abstract base class for all rendered symbols.
Definition qgssymbol.h:93
Container for settings relating to a text background object.
QString svgFile() const
Returns the absolute path to the background SVG file, if set.
QSizeF size() const
Returns the size of the background shape.
QSizeF radii() const
Returns the radii used for rounding the corners of shapes.
void setOpacity(double opacity)
Sets the background shape's opacity.
void setStrokeColor(const QColor &color)
Sets the color used for outlining the background shape.
Qt::PenJoinStyle joinStyle() const
Returns the join style used for drawing the background shape.
SizeType
Methods for determining the background shape size.
bool enabled() const
Returns whether the background is enabled.
void setJoinStyle(Qt::PenJoinStyle style)
Sets the join style used for drawing the background shape.
void setRadiiUnit(QgsUnitTypes::RenderUnit units)
Sets the units used for the shape's radii.
double opacity() const
Returns the background shape's opacity.
double rotation() const
Returns the rotation for the background shape, in degrees clockwise.
QColor fillColor() const
Returns the color used for filing the background shape.
void setRadii(QSizeF radii)
Sets the radii used for rounding the corners of shapes.
SizeType sizeType() const
Returns the method used to determine the size of the background shape (e.g., fixed size or buffer aro...
ShapeType type() const
Returns the type of background shape (e.g., square, ellipse, SVG).
double strokeWidth() const
Returns the width of the shape's stroke (stroke).
void setSizeType(SizeType type)
Sets the method used to determine the size of the background shape (e.g., fixed size or buffer around...
void setFillColor(const QColor &color)
Sets the color used for filing the background shape.
void setStrokeWidthUnit(QgsUnitTypes::RenderUnit units)
Sets the units used for the shape's stroke width.
QColor strokeColor() const
Returns the color used for outlining the background shape.
void setRotationType(RotationType type)
Sets the method used for rotating the background shape.
void setBlendMode(QPainter::CompositionMode mode)
Sets the blending mode used for drawing the background shape.
QgsMapUnitScale sizeMapUnitScale() const
Returns the map unit scale object for the shape size.
void setType(ShapeType type)
Sets the type of background shape to draw (e.g., square, ellipse, SVG).
QgsUnitTypes::RenderUnit sizeUnit() const
Returns the units used for the shape's size.
RotationType
Methods for determining the rotation of the background shape.
void setEnabled(bool enabled)
Sets whether the text background will be drawn.
QgsMarkerSymbol * markerSymbol() const
Returns the marker symbol to be rendered in the background.
void setRotation(double rotation)
Sets the rotation for the background shape, in degrees clockwise.
void setOffset(QPointF offset)
Sets the offset used for drawing the background shape.
void setSize(QSizeF size)
Sets the size of the background shape.
void setSizeUnit(QgsUnitTypes::RenderUnit unit)
Sets the units used for the shape's size.
void setSvgFile(const QString &file)
Sets the path to the background SVG file.
void setStrokeWidth(double width)
Sets the width of the shape's stroke (stroke).
QPointF offset() const
Returns the offset used for drawing the background shape.
void setOffsetUnit(QgsUnitTypes::RenderUnit units)
Sets the units used for the shape's offset.
Container for settings relating to a text buffer.
void setBlendMode(QPainter::CompositionMode mode)
Sets the blending mode used for drawing the buffer.
Qt::PenJoinStyle joinStyle() const
Returns the buffer join style.
double size() const
Returns the size of the buffer.
void setColor(const QColor &color)
Sets the color for the buffer.
void setOpacity(double opacity)
Sets the buffer opacity.
QgsMapUnitScale sizeMapUnitScale() const
Returns the map unit scale object for the buffer size.
bool enabled() const
Returns whether the buffer is enabled.
double opacity() const
Returns the buffer opacity.
void setEnabled(bool enabled)
Sets whether the text buffer will be drawn.
QgsUnitTypes::RenderUnit sizeUnit() const
Returns the units for the buffer size.
QColor color() const
Returns the color of the buffer.
void setSizeUnit(QgsUnitTypes::RenderUnit unit)
Sets the units used for the buffer size.
void setJoinStyle(Qt::PenJoinStyle style)
Sets the join style used for drawing the buffer.
void setSize(double size)
Sets the size of the buffer.
Contains pre-calculated metrics of a QgsTextDocument.
QSizeF documentSize(Qgis::TextLayoutMode mode, Qgis::TextOrientation orientation) const
Returns the overall size of the document.
static QgsTextDocumentMetrics calculateMetrics(const QgsTextDocument &document, const QgsTextFormat &format, const QgsRenderContext &context, double scaleFactor=1.0)
Returns precalculated text metrics for a text document, when rendered using the given base format and...
Represents a document consisting of one or more QgsTextBlock objects.
void splitLines(const QString &wrapCharacter, int autoWrapLength=0, bool useMaxLineLengthWhenAutoWrapping=true)
Splits lines of text in the document to separate lines, using a specified wrap character (wrapCharact...
static QgsTextDocument fromHtml(const QStringList &lines)
Constructor for QgsTextDocument consisting of a set of HTML formatted lines.
Container for all settings relating to text rendering.
void setColor(const QColor &color)
Sets the color that text will be rendered in.
void setBlendMode(QPainter::CompositionMode mode)
Sets the blending mode used for drawing the text.
QgsMapUnitScale sizeMapUnitScale() const
Returns the map unit scale object for the size.
void setOrientation(Qgis::TextOrientation orientation)
Sets the orientation for the text.
QSet< QString > referencedFields(const QgsRenderContext &context) const
Returns all field names referenced by the configuration (e.g.
double lineHeight() const
Returns the line height for text.
QgsUnitTypes::RenderUnit sizeUnit() const
Returns the units for the size of rendered text.
int stretchFactor() const
Returns the text's stretch factor.
void setShadow(const QgsTextShadowSettings &shadowSettings)
Sets the text's drop shadow settings.
void setMask(const QgsTextMaskSettings &maskSettings)
Sets the text's masking settings.
QFont scaledFont(const QgsRenderContext &context, double scaleFactor=1.0, bool *isZeroSize=nullptr) const
Returns a font with the size scaled to match the format's size settings (including units and map unit...
void setOpacity(double opacity)
Sets the text's opacity.
void readXml(const QDomElement &elem, const QgsReadWriteContext &context)
Read settings from a DOM element.
QgsUnitTypes::RenderUnit lineHeightUnit() const
Returns the units for the line height for text.
Qgis::Capitalization capitalization() const
Returns the text capitalization style.
QgsTextMaskSettings & mask()
Returns a reference to the masking settings.
void setBuffer(const QgsTextBufferSettings &bufferSettings)
Sets the text's buffer settings.
QgsTextBackgroundSettings & background()
Returns a reference to the text background settings.
bool allowHtmlFormatting() const
Returns true if text should be treated as a HTML document and HTML tags should be used for formatting...
double opacity() const
Returns the text's opacity.
Qgis::TextOrientation orientation() const
Returns the orientation of the text.
QString namedStyle() const
Returns the named style for the font used for rendering text (e.g., "bold").
double size() const
Returns the size for rendered text.
QgsTextShadowSettings & shadow()
Returns a reference to the text drop shadow settings.
void setBackground(const QgsTextBackgroundSettings &backgroundSettings)
Sets the text's background settings.q.
QDomElement writeXml(QDomDocument &doc, const QgsReadWriteContext &context) const
Write settings into a DOM element.
QColor color() const
Returns the color that text will be rendered in.
QFont font() const
Returns the font used for rendering text.
void readFromLayer(QgsVectorLayer *layer)
Reads settings from a layer's custom properties (for QGIS 2.x projects).
QColor previewBackgroundColor() const
Returns the background color for text previews.
bool containsAdvancedEffects() const
Returns true if any component of the font format requires advanced effects such as blend modes,...
QgsTextBufferSettings & buffer()
Returns a reference to the text buffer settings.
void setLineHeight(double height)
Sets the line height for text.
static QgsPrecalculatedTextMetrics calculateTextMetrics(const QgsMapToPixel *xform, const QgsRenderContext &context, const QFont &baseFont, const QFontMetricsF &fontMetrics, double letterSpacing, double wordSpacing, const QString &text=QString(), QgsTextDocument *document=nullptr, QgsTextDocumentMetrics *metrics=nullptr)
Calculate text metrics for later retrieval via textMetrics().
Container for settings relating to a selective masking around a text.
void setEnabled(bool)
Returns whether the mask is enabled.
void setSize(double size)
Sets the size of the buffer.
double size() const
Returns the size of the buffer.
void setSizeUnit(QgsUnitTypes::RenderUnit unit)
Sets the units used for the buffer size.
void setJoinStyle(Qt::PenJoinStyle style)
Sets the join style used for drawing the buffer.
double opacity() const
Returns the mask's opacity.
bool enabled() const
Returns whether the mask is enabled.
Qt::PenJoinStyle joinStyle() const
Returns the buffer join style.
void setOpacity(double opacity)
Sets the mask's opacity.
static QgsTextBackgroundSettings::ShapeType decodeShapeType(const QString &string)
Decodes a string representation of a background shape type to a type.
static Qgis::TextOrientation decodeTextOrientation(const QString &name, bool *ok=nullptr)
Attempts to decode a string representation of a text orientation.
static QgsTextShadowSettings::ShadowPlacement decodeShadowPlacementType(const QString &string)
Decodes a string representation of a shadow placement type to a type.
static QgsTextBackgroundSettings::RotationType decodeBackgroundRotationType(const QString &string)
Decodes a string representation of a background rotation type to a type.
static QString encodeTextOrientation(Qgis::TextOrientation orientation)
Encodes a text orientation.
static QgsTextBackgroundSettings::SizeType decodeBackgroundSizeType(const QString &string)
Decodes a string representation of a background size type to a type.
static double textWidth(const QgsRenderContext &context, const QgsTextFormat &format, const QStringList &textLines, QFontMetricsF *fontMetrics=nullptr)
Returns the width of a text based on a given format.
static int sizeToPixel(double size, const QgsRenderContext &c, QgsUnitTypes::RenderUnit unit, const QgsMapUnitScale &mapUnitScale=QgsMapUnitScale())
Calculates pixel size (considering output size should be in pixel or map units, scale factors and opt...
static double textHeight(const QgsRenderContext &context, const QgsTextFormat &format, const QStringList &textLines, Qgis::TextLayoutMode mode=Qgis::TextLayoutMode::Point, QFontMetricsF *fontMetrics=nullptr, Qgis::TextRendererFlags flags=Qgis::TextRendererFlags(), double maxLineWidth=0)
Returns the height of a text based on a given format.
static void drawText(const QRectF &rect, double rotation, Qgis::TextHorizontalAlignment alignment, const QStringList &textLines, QgsRenderContext &context, const QgsTextFormat &format, bool drawAsOutlines=true, Qgis::TextVerticalAlignment vAlignment=Qgis::TextVerticalAlignment::Top, Qgis::TextRendererFlags flags=Qgis::TextRendererFlags())
Draws text within a rectangle using the specified settings.
Container for settings relating to a text shadow.
int offsetAngle() const
Returns the angle for offsetting the position of the shadow from the text.
bool enabled() const
Returns whether the shadow is enabled.
int scale() const
Returns the scaling used for the drop shadow (in percentage of original size).
void setShadowPlacement(QgsTextShadowSettings::ShadowPlacement placement)
Sets the placement for the drop shadow.
double opacity() const
Returns the shadow's opacity.
void setBlendMode(QPainter::CompositionMode mode)
Sets the blending mode used for drawing the drop shadow.
void setColor(const QColor &color)
Sets the color for the drop shadow.
QColor color() const
Returns the color of the drop shadow.
ShadowPlacement
Placement positions for text shadow.
void setScale(int scale)
Sets the scaling used for the drop shadow (in percentage of original size).
double offsetDistance() const
Returns the distance for offsetting the position of the shadow from the text.
void setOffsetDistance(double distance)
Sets the distance for offsetting the position of the shadow from the text.
void setOpacity(double opacity)
Sets the shadow's opacity.
void setOffsetAngle(int angle)
Sets the angle for offsetting the position of the shadow from the text.
void setBlurRadiusUnit(QgsUnitTypes::RenderUnit units)
Sets the units used for the shadow's blur radius.
double blurRadius() const
Returns the blur radius for the shadow.
void setBlurRadius(double blurRadius)
Sets the blur radius for the shadow.
void setOffsetUnit(QgsUnitTypes::RenderUnit units)
Sets the units used for the shadow's offset.
void setEnabled(bool enabled)
Sets whether the text shadow will be drawn.
AngleUnit
Units of angles.
@ AngleDegrees
Degrees.
static Q_INVOKABLE QString encodeUnit(QgsUnitTypes::DistanceUnit unit)
Encodes a distance unit to a string.
static Q_INVOKABLE double fromUnitToUnitFactor(QgsUnitTypes::DistanceUnit fromUnit, QgsUnitTypes::DistanceUnit toUnit)
Returns the conversion factor between the specified distance units.
static Q_INVOKABLE QgsUnitTypes::RenderUnit decodeRenderUnit(const QString &string, bool *ok=nullptr)
Decodes a render unit from a string.
RenderUnit
Rendering size units.
@ RenderPercentage
Percentage of another measurement (e.g., canvas size, feature size)
@ RenderPoints
Points (e.g., for font sizes)
@ RenderMillimeters
Millimeters.
@ RenderMapUnits
Map units.
static bool isNull(const QVariant &variant)
Returns true if the specified variant should be considered a NULL value.
Represents a vector layer which manages a vector based data sets.
bool labelsEnabled() const
Returns whether the layer contains labels which are enabled and should be drawn.
Q_INVOKABLE QgsWkbTypes::GeometryType geometryType() const
Returns point, line or polygon.
bool diagramsEnabled() const
Returns whether the layer contains diagrams which are enabled and should be drawn.
This class contains information how to simplify geometries fetched from a vector layer.
double tolerance() const
Gets the tolerance of simplification in map units. Represents the maximum distance in map units betwe...
bool forceLocalOptimization() const
Gets where the simplification executes, after fetch the geometries from provider, or when supported,...
SimplifyHints simplifyHints() const
Gets the simplification hints of the vector layer managed.
SimplifyAlgorithm simplifyAlgorithm() const
Gets the local simplification algorithm of the vector layer managed.
@ NoSimplification
No simplification can be applied.
Basic labeling configuration for vector tile layers.
Implements a map layer that is dedicated to rendering of vector tiles.
QgsVectorTileLabeling * labeling() const
Returns currently assigned labeling.
GeometryType
The geometry types are used to group QgsWkbTypes::Type in a coarse way.
LabelPosition is a candidate feature label position.
double getAlpha() const
Returns the angle to rotate text (in radians).
double getHeight() const
double cost() const
Returns the candidate label position's geographical cost.
bool conflictsWithObstacle() const
Returns whether the position is marked as conflicting with an obstacle feature.
double getWidth() const
FeaturePart * getFeaturePart() const
Returns the feature corresponding to this labelposition.
double getX(int i=0) const
Returns the down-left x coordinate.
double getY(int i=0) const
Returns the down-left y coordinate.
LabelPosition * nextPart() const
Returns the next part of this label position (i.e.
@ PointCloudLayer
Point cloud layer. Added in QGIS 3.18.
@ MeshLayer
Mesh layer. Added in QGIS 3.2.
@ VectorLayer
Vector layer.
@ RasterLayer
Raster layer.
@ GroupLayer
Composite group layer. Added in QGIS 3.24.
@ VectorTileLayer
Vector tile layer. Added in QGIS 3.14.
@ AnnotationLayer
Contains freeform, georeferenced annotations. Added in QGIS 3.16.
@ PluginLayer
Plugin based layer.
std::unique_ptr< GEOSGeometry, GeosDeleter > unique_ptr
Scoped GEOS pointer.
Definition qgsgeos.h:74
#define str(x)
Definition qgis.cpp:37
T qgsEnumKeyToValue(const QString &key, const T &defaultValue, bool tryValueAsKey=true, bool *returnOk=nullptr)
Returns the value corresponding to the given key of an enum.
Definition qgis.h:2700
#define Q_NOWARN_DEPRECATED_POP
Definition qgis.h:3061
QString qgsEnumValueToKey(const T &value, bool *returnOk=nullptr)
Returns the value for the given key of an enum.
Definition qgis.h:2681
#define Q_NOWARN_DEPRECATED_PUSH
Definition qgis.h:3060
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition qgis.h:2527
Q_GLOBAL_STATIC(QReadWriteLock, sDefinitionCacheLock)
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:39
#define QgsDebugMsg(str)
Definition qgslogger.h:38
QString updateDataDefinedString(const QString &value)
QVector< Qgis::LabelPredefinedPointPosition > PredefinedPointPositionVector
Q_GLOBAL_STATIC_WITH_ARGS(PredefinedPointPositionVector, DEFAULT_PLACEMENT_ORDER,({ Qgis::LabelPredefinedPointPosition::TopRight, Qgis::LabelPredefinedPointPosition::TopLeft, Qgis::LabelPredefinedPointPosition::BottomRight, Qgis::LabelPredefinedPointPosition::BottomLeft, Qgis::LabelPredefinedPointPosition::MiddleRight, Qgis::LabelPredefinedPointPosition::MiddleLeft, Qgis::LabelPredefinedPointPosition::TopSlightlyRight, Qgis::LabelPredefinedPointPosition::BottomSlightlyRight })) void QgsPalLayerSettings
QMap< int, QgsPropertyDefinition > QgsPropertiesDefinition
Definition of available properties.
const QgsCoordinateReferenceSystem & crs