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