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