QGIS API Documentation 3.33.0-Master (bf22a165b3)
Loading...
Searching...
No Matches
qgspallabeling.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgspallabeling.cpp
3 Smart labeling for vector layers
4 -------------------
5 begin : June 2009
6 copyright : (C) Martin Dobias
7 email : wonder dot sk at gmail dot com
8
9 ***************************************************************************
10 * *
11 * This program is free software; you can redistribute it and/or modify *
12 * it under the terms of the GNU General Public License as published by *
13 * the Free Software Foundation; either version 2 of the License, or *
14 * (at your option) any later version. *
15 * *
16 ***************************************************************************/
17
18#include "qgspallabeling.h"
19#include "qgstextlabelfeature.h"
20#include "qgsunittypes.h"
21#include "qgsexception.h"
22#include "qgsapplication.h"
23#include "qgsstyle.h"
24#include "qgstextrenderer.h"
25
26#include <list>
27
28#include "pal/labelposition.h"
29
30#include <cmath>
31
32#include <QApplication>
33#include <QByteArray>
34#include <QString>
35#include <QFontMetrics>
36#include <QTime>
37#include <QPainter>
38#include <QScreen>
39#include <QWidget>
40#include <QTextBoundaryFinder>
41
42#include "qgsfontutils.h"
43#include "qgsexpression.h"
44#include "qgslabelingengine.h"
46#include "qgsmultisurface.h"
47#include "qgslogger.h"
48#include "qgsvectorlayer.h"
49#include "qgsgeometry.h"
51#include "qgsproperty.h"
52#include "qgssymbollayerutils.h"
54#include "qgscurvepolygon.h"
55#include "qgsmessagelog.h"
57#include "callouts/qgscallout.h"
59#include "qgsvectortilelayer.h"
61#include "qgsfontmanager.h"
62#include "qgsvariantutils.h"
63
64using namespace pal;
65
66// -------------
67
68/* ND: Default point label position priority. These are set to match variants of the ideal placement priority described
69 in "Making Maps", Krygier & Wood (2011) (p216),
70 "Elements of Cartography", Robinson et al (1995)
71 and "Designing Better Maps", Brewer (2005) (p76)
72 Note that while they agree on positions 1-4, 5-8 are more contentious so I've selected these placements
73 based on my preferences, and to follow Krygier and Wood's placements more closer. (I'm not going to disagree
74 with Denis Wood on anything cartography related...!)
75*/
76typedef QVector< Qgis::LabelPredefinedPointPosition > PredefinedPointPositionVector;
78{
87} ) )
88//debugging only - don't use these placements by default
89/* << QgsPalLayerSettings::TopSlightlyLeft
90<< QgsPalLayerSettings::BottomSlightlyLeft;
91<< QgsPalLayerSettings::TopMiddle
92<< QgsPalLayerSettings::BottomMiddle;*/
93
94Q_GLOBAL_STATIC( QgsPropertiesDefinition, sPropertyDefinitions )
95
96void QgsPalLayerSettings::initPropertyDefinitions()
97{
98 if ( !sPropertyDefinitions()->isEmpty() )
99 return;
100
101 const QString origin = QStringLiteral( "labeling" );
102
103 *sPropertyDefinitions() = QgsPropertiesDefinition
104 {
105 { QgsPalLayerSettings::Size, QgsPropertyDefinition( "Size", QObject::tr( "Font size" ), QgsPropertyDefinition::DoublePositive, origin ) },
106 { QgsPalLayerSettings::Bold, QgsPropertyDefinition( "Bold", QObject::tr( "Bold style" ), QgsPropertyDefinition::Boolean, origin ) },
107 { QgsPalLayerSettings::Italic, QgsPropertyDefinition( "Italic", QObject::tr( "Italic style" ), QgsPropertyDefinition::Boolean, origin ) },
108 { QgsPalLayerSettings::Underline, QgsPropertyDefinition( "Underline", QObject::tr( "Draw underline" ), QgsPropertyDefinition::Boolean, origin ) },
109 { QgsPalLayerSettings::Color, QgsPropertyDefinition( "Color", QObject::tr( "Text color" ), QgsPropertyDefinition::ColorNoAlpha, origin ) },
110 { QgsPalLayerSettings::Strikeout, QgsPropertyDefinition( "Strikeout", QObject::tr( "Draw strikeout" ), QgsPropertyDefinition::Boolean, origin ) },
111 {
112 QgsPalLayerSettings::Family, QgsPropertyDefinition( "Family", QgsPropertyDefinition::DataTypeString, QObject::tr( "Font family" ), QObject::tr( "string " ) + QObject::tr( "[<b>family</b>|<b>family[foundry]</b>],<br>"
113 "e.g. Helvetica or Helvetica [Cronyx]" ), origin )
114 },
115 {
116 QgsPalLayerSettings::FontStyle, QgsPropertyDefinition( "FontStyle", QgsPropertyDefinition::DataTypeString, QObject::tr( "Font style" ), QObject::tr( "string " ) + QObject::tr( "[<b>font style name</b>|<b>Ignore</b>],<br>"
117 "e.g. Bold Condensed or Light Italic" ), origin )
118 },
119 { QgsPalLayerSettings::FontSizeUnit, QgsPropertyDefinition( "FontSizeUnit", QObject::tr( "Font size units" ), QgsPropertyDefinition::RenderUnits, origin ) },
120 { QgsPalLayerSettings::FontTransp, QgsPropertyDefinition( "FontTransp", QObject::tr( "Text transparency" ), QgsPropertyDefinition::Opacity, origin ) },
121 { QgsPalLayerSettings::FontOpacity, QgsPropertyDefinition( "FontOpacity", QObject::tr( "Text opacity" ), QgsPropertyDefinition::Opacity, origin ) },
122 { QgsPalLayerSettings::FontStretchFactor, QgsPropertyDefinition( "FontStretchFactor", QObject::tr( "Font stretch factor" ), QgsPropertyDefinition::IntegerPositiveGreaterZero, origin ) },
123 { QgsPalLayerSettings::FontCase, QgsPropertyDefinition( "FontCase", QgsPropertyDefinition::DataTypeString, QObject::tr( "Font case" ), QObject::tr( "string " ) + QStringLiteral( "[<b>NoChange</b>|<b>Upper</b>|<br><b>Lower</b>|<b>Title</b>|<b>Capitalize</b>|<b>SmallCaps</b>|<b>AllSmallCaps</b>]" ), origin ) },
124 { QgsPalLayerSettings::FontLetterSpacing, QgsPropertyDefinition( "FontLetterSpacing", QObject::tr( "Letter spacing" ), QgsPropertyDefinition::Double, origin ) },
125 { QgsPalLayerSettings::FontWordSpacing, QgsPropertyDefinition( "FontWordSpacing", QObject::tr( "Word spacing" ), QgsPropertyDefinition::Double, origin ) },
126 { QgsPalLayerSettings::FontBlendMode, QgsPropertyDefinition( "FontBlendMode", QObject::tr( "Text blend mode" ), QgsPropertyDefinition::BlendMode, origin ) },
127 { QgsPalLayerSettings::MultiLineWrapChar, QgsPropertyDefinition( "MultiLineWrapChar", QObject::tr( "Wrap character" ), QgsPropertyDefinition::String, origin ) },
128 { QgsPalLayerSettings::AutoWrapLength, QgsPropertyDefinition( "AutoWrapLength", QObject::tr( "Automatic word wrap line length" ), QgsPropertyDefinition::IntegerPositive, origin ) },
129 { QgsPalLayerSettings::MultiLineHeight, QgsPropertyDefinition( "MultiLineHeight", QObject::tr( "Line height" ), QgsPropertyDefinition::DoublePositive, origin ) },
130 { QgsPalLayerSettings::MultiLineAlignment, QgsPropertyDefinition( "MultiLineAlignment", QgsPropertyDefinition::DataTypeString, QObject::tr( "Line alignment" ), QObject::tr( "string " ) + "[<b>Left</b>|<b>Center</b>|<b>Right</b>|<b>Follow</b>]", origin ) },
131 { QgsPalLayerSettings::TextOrientation, QgsPropertyDefinition( "TextOrientation", QgsPropertyDefinition::DataTypeString, QObject::tr( "Text orientation" ), QObject::tr( "string " ) + "[<b>horizontal</b>|<b>vertical</b>]", origin ) },
132 { QgsPalLayerSettings::DirSymbDraw, QgsPropertyDefinition( "DirSymbDraw", QObject::tr( "Draw direction symbol" ), QgsPropertyDefinition::Boolean, origin ) },
133 { QgsPalLayerSettings::DirSymbLeft, QgsPropertyDefinition( "DirSymbLeft", QObject::tr( "Left direction symbol" ), QgsPropertyDefinition::String, origin ) },
134 { QgsPalLayerSettings::DirSymbRight, QgsPropertyDefinition( "DirSymbRight", QObject::tr( "Right direction symbol" ), QgsPropertyDefinition::String, origin ) },
135 { QgsPalLayerSettings::DirSymbPlacement, QgsPropertyDefinition( "DirSymbPlacement", QgsPropertyDefinition::DataTypeString, QObject::tr( "Direction symbol placement" ), QObject::tr( "string " ) + "[<b>LeftRight</b>|<b>Above</b>|<b>Below</b>]", origin ) },
136 { QgsPalLayerSettings::DirSymbReverse, QgsPropertyDefinition( "DirSymbReverse", QObject::tr( "Reverse direction symbol" ), QgsPropertyDefinition::Boolean, origin ) },
137 { QgsPalLayerSettings::NumFormat, QgsPropertyDefinition( "NumFormat", QObject::tr( "Format as number" ), QgsPropertyDefinition::Boolean, origin ) },
138 { QgsPalLayerSettings::NumDecimals, QgsPropertyDefinition( "NumDecimals", QObject::tr( "Number of decimal places" ), QgsPropertyDefinition::IntegerPositive, origin ) },
139 { QgsPalLayerSettings::NumPlusSign, QgsPropertyDefinition( "NumPlusSign", QObject::tr( "Draw + sign" ), QgsPropertyDefinition::Boolean, origin ) },
140 { QgsPalLayerSettings::BufferDraw, QgsPropertyDefinition( "BufferDraw", QObject::tr( "Draw buffer" ), QgsPropertyDefinition::Boolean, origin ) },
141 { QgsPalLayerSettings::BufferSize, QgsPropertyDefinition( "BufferSize", QObject::tr( "Symbol size" ), QgsPropertyDefinition::DoublePositive, origin ) },
142 { QgsPalLayerSettings::BufferUnit, QgsPropertyDefinition( "BufferUnit", QObject::tr( "Buffer units" ), QgsPropertyDefinition::RenderUnits, origin ) },
143 { QgsPalLayerSettings::BufferColor, QgsPropertyDefinition( "BufferColor", QObject::tr( "Buffer color" ), QgsPropertyDefinition::ColorNoAlpha, origin ) },
144 { QgsPalLayerSettings::BufferTransp, QgsPropertyDefinition( "BufferTransp", QObject::tr( "Buffer transparency" ), QgsPropertyDefinition::Opacity, origin ) },
145 { QgsPalLayerSettings::BufferOpacity, QgsPropertyDefinition( "BufferOpacity", QObject::tr( "Buffer opacity" ), QgsPropertyDefinition::Opacity, origin ) },
146 { QgsPalLayerSettings::BufferJoinStyle, QgsPropertyDefinition( "BufferJoinStyle", QObject::tr( "Buffer join style" ), QgsPropertyDefinition::PenJoinStyle, origin ) },
147 { QgsPalLayerSettings::BufferBlendMode, QgsPropertyDefinition( "BufferBlendMode", QObject::tr( "Buffer blend mode" ), QgsPropertyDefinition::BlendMode, origin ) },
148
149 { QgsPalLayerSettings::MaskEnabled, QgsPropertyDefinition( "MaskEnabled", QObject::tr( "Enable mask" ), QgsPropertyDefinition::Boolean, origin ) },
150 { QgsPalLayerSettings::MaskBufferSize, QgsPropertyDefinition( "MaskBufferSize", QObject::tr( "Mask buffer size" ), QgsPropertyDefinition::DoublePositive, origin ) },
151 { QgsPalLayerSettings::MaskBufferUnit, QgsPropertyDefinition( "MaskBufferUnit", QObject::tr( "Mask buffer unit" ), QgsPropertyDefinition::RenderUnits, origin ) },
152 { QgsPalLayerSettings::MaskOpacity, QgsPropertyDefinition( "MaskOpacity", QObject::tr( "Mask opacity" ), QgsPropertyDefinition::Opacity, origin ) },
153 { QgsPalLayerSettings::MaskJoinStyle, QgsPropertyDefinition( "MaskJoinStyle", QObject::tr( "Mask join style" ), QgsPropertyDefinition::PenJoinStyle, origin ) },
154
155 { QgsPalLayerSettings::ShapeDraw, QgsPropertyDefinition( "ShapeDraw", QObject::tr( "Draw shape" ), QgsPropertyDefinition::Boolean, origin ) },
156 {
157 QgsPalLayerSettings::ShapeKind, QgsPropertyDefinition( "ShapeKind", QgsPropertyDefinition::DataTypeString, QObject::tr( "Shape type" ), QObject::tr( "string " ) + QStringLiteral( "[<b>Rectangle</b>|<b>Square</b>|<br>"
158 "<b>Ellipse</b>|<b>Circle</b>|<b>SVG</b>]" ), origin )
159 },
160 { QgsPalLayerSettings::ShapeSVGFile, QgsPropertyDefinition( "ShapeSVGFile", QObject::tr( "Shape SVG path" ), QgsPropertyDefinition::SvgPath, origin ) },
161 { QgsPalLayerSettings::ShapeSizeType, QgsPropertyDefinition( "ShapeSizeType", QgsPropertyDefinition::DataTypeString, QObject::tr( "Shape size type" ), QObject::tr( "string " ) + "[<b>Buffer</b>|<b>Fixed</b>]", origin ) },
162 { QgsPalLayerSettings::ShapeSizeX, QgsPropertyDefinition( "ShapeSizeX", QObject::tr( "Shape size (X)" ), QgsPropertyDefinition::Double, origin ) },
163 { QgsPalLayerSettings::ShapeSizeY, QgsPropertyDefinition( "ShapeSizeY", QObject::tr( "Shape size (Y)" ), QgsPropertyDefinition::Double, origin ) },
164 { QgsPalLayerSettings::ShapeSizeUnits, QgsPropertyDefinition( "ShapeSizeUnits", QObject::tr( "Shape size units" ), QgsPropertyDefinition::RenderUnits, origin ) },
165 { QgsPalLayerSettings::ShapeRotationType, QgsPropertyDefinition( "ShapeRotationType", QgsPropertyDefinition::DataTypeString, QObject::tr( "Shape rotation type" ), QObject::tr( "string " ) + "[<b>Sync</b>|<b>Offset</b>|<b>Fixed</b>]", origin ) },
166 { QgsPalLayerSettings::ShapeRotation, QgsPropertyDefinition( "ShapeRotation", QObject::tr( "Shape rotation" ), QgsPropertyDefinition::Rotation, origin ) },
167 { QgsPalLayerSettings::ShapeOffset, QgsPropertyDefinition( "ShapeOffset", QObject::tr( "Shape offset" ), QgsPropertyDefinition::Offset, origin ) },
168 { QgsPalLayerSettings::ShapeOffsetUnits, QgsPropertyDefinition( "ShapeOffsetUnits", QObject::tr( "Shape offset units" ), QgsPropertyDefinition::RenderUnits, origin ) },
169 { QgsPalLayerSettings::ShapeRadii, QgsPropertyDefinition( "ShapeRadii", QObject::tr( "Shape radii" ), QgsPropertyDefinition::Size2D, origin ) },
170 { QgsPalLayerSettings::ShapeRadiiUnits, QgsPropertyDefinition( "ShapeRadiiUnits", QObject::tr( "Symbol radii units" ), QgsPropertyDefinition::RenderUnits, origin ) },
171 { QgsPalLayerSettings::ShapeTransparency, QgsPropertyDefinition( "ShapeTransparency", QObject::tr( "Shape transparency" ), QgsPropertyDefinition::Opacity, origin ) },
172 { QgsPalLayerSettings::ShapeOpacity, QgsPropertyDefinition( "ShapeOpacity", QObject::tr( "Shape opacity" ), QgsPropertyDefinition::Opacity, origin ) },
173 { QgsPalLayerSettings::ShapeBlendMode, QgsPropertyDefinition( "ShapeBlendMode", QObject::tr( "Shape blend mode" ), QgsPropertyDefinition::BlendMode, origin ) },
174 { QgsPalLayerSettings::ShapeFillColor, QgsPropertyDefinition( "ShapeFillColor", QObject::tr( "Shape fill color" ), QgsPropertyDefinition::ColorWithAlpha, origin ) },
175 { QgsPalLayerSettings::ShapeStrokeColor, QgsPropertyDefinition( "ShapeBorderColor", QObject::tr( "Shape stroke color" ), QgsPropertyDefinition::ColorWithAlpha, origin ) },
176 { QgsPalLayerSettings::ShapeStrokeWidth, QgsPropertyDefinition( "ShapeBorderWidth", QObject::tr( "Shape stroke width" ), QgsPropertyDefinition::StrokeWidth, origin ) },
177 { QgsPalLayerSettings::ShapeStrokeWidthUnits, QgsPropertyDefinition( "ShapeBorderWidthUnits", QObject::tr( "Shape stroke width units" ), QgsPropertyDefinition::RenderUnits, origin ) },
178 { QgsPalLayerSettings::ShapeJoinStyle, QgsPropertyDefinition( "ShapeJoinStyle", QObject::tr( "Shape join style" ), QgsPropertyDefinition::PenJoinStyle, origin ) },
179 { QgsPalLayerSettings::ShadowDraw, QgsPropertyDefinition( "ShadowDraw", QObject::tr( "Draw shadow" ), QgsPropertyDefinition::Boolean, origin ) },
180 {
181 QgsPalLayerSettings::ShadowUnder, QgsPropertyDefinition( "ShadowUnder", QgsPropertyDefinition::DataTypeString, QObject::tr( "Symbol size" ), QObject::tr( "string " ) + QStringLiteral( "[<b>Lowest</b>|<b>Text</b>|<br>"
182 "<b>Buffer</b>|<b>Background</b>]" ), origin )
183 },
184 { QgsPalLayerSettings::ShadowOffsetAngle, QgsPropertyDefinition( "ShadowOffsetAngle", QObject::tr( "Shadow offset angle" ), QgsPropertyDefinition::Rotation, origin ) },
185 { QgsPalLayerSettings::ShadowOffsetDist, QgsPropertyDefinition( "ShadowOffsetDist", QObject::tr( "Shadow offset distance" ), QgsPropertyDefinition::DoublePositive, origin ) },
186 { QgsPalLayerSettings::ShadowOffsetUnits, QgsPropertyDefinition( "ShadowOffsetUnits", QObject::tr( "Shadow offset units" ), QgsPropertyDefinition::RenderUnits, origin ) },
187 { QgsPalLayerSettings::ShadowRadius, QgsPropertyDefinition( "ShadowRadius", QObject::tr( "Shadow blur radius" ), QgsPropertyDefinition::DoublePositive, origin ) },
188 { QgsPalLayerSettings::ShadowRadiusUnits, QgsPropertyDefinition( "ShadowRadiusUnits", QObject::tr( "Shadow blur units" ), QgsPropertyDefinition::RenderUnits, origin ) },
189 { QgsPalLayerSettings::ShadowTransparency, QgsPropertyDefinition( "ShadowTransparency", QObject::tr( "Shadow transparency" ), QgsPropertyDefinition::Opacity, origin ) },
190 { QgsPalLayerSettings::ShadowOpacity, QgsPropertyDefinition( "ShadowOpacity", QObject::tr( "Shadow opacity" ), QgsPropertyDefinition::Opacity, origin ) },
191 { QgsPalLayerSettings::ShadowScale, QgsPropertyDefinition( "ShadowScale", QObject::tr( "Shadow scale" ), QgsPropertyDefinition::IntegerPositive, origin ) },
192 { QgsPalLayerSettings::ShadowColor, QgsPropertyDefinition( "ShadowColor", QObject::tr( "Shadow color" ), QgsPropertyDefinition::ColorNoAlpha, origin ) },
193 { QgsPalLayerSettings::ShadowBlendMode, QgsPropertyDefinition( "ShadowBlendMode", QObject::tr( "Shadow blend mode" ), QgsPropertyDefinition::BlendMode, origin ) },
194
195 { QgsPalLayerSettings::CentroidWhole, QgsPropertyDefinition( "CentroidWhole", QgsPropertyDefinition::DataTypeString, QObject::tr( "Centroid of whole shape" ), QObject::tr( "string " ) + "[<b>Visible</b>|<b>Whole</b>]", origin ) },
196 {
197 QgsPalLayerSettings::OffsetQuad, QgsPropertyDefinition( "OffsetQuad", QgsPropertyDefinition::DataTypeString, QObject::tr( "Offset quadrant" ), QObject::tr( "int<br>" ) + QStringLiteral( "[<b>0</b>=Above Left|<b>1</b>=Above|<b>2</b>=Above Right|<br>"
198 "<b>3</b>=Left|<b>4</b>=Over|<b>5</b>=Right|<br>"
199 "<b>6</b>=Below Left|<b>7</b>=Below|<b>8</b>=Below Right]" ), origin )
200 },
201 { QgsPalLayerSettings::OffsetXY, QgsPropertyDefinition( "OffsetXY", QObject::tr( "Offset" ), QgsPropertyDefinition::Offset, origin ) },
202 { QgsPalLayerSettings::OffsetUnits, QgsPropertyDefinition( "OffsetUnits", QObject::tr( "Offset units" ), QgsPropertyDefinition::RenderUnits, origin ) },
203 { QgsPalLayerSettings::LabelDistance, QgsPropertyDefinition( "LabelDistance", QObject::tr( "Label distance" ), QgsPropertyDefinition::Double, origin ) },
204 { QgsPalLayerSettings::DistanceUnits, QgsPropertyDefinition( "DistanceUnits", QObject::tr( "Label distance units" ), QgsPropertyDefinition::RenderUnits, origin ) },
205 { QgsPalLayerSettings::OffsetRotation, QgsPropertyDefinition( "OffsetRotation", QObject::tr( "Offset rotation" ), QgsPropertyDefinition::Rotation, origin ) },
206 { QgsPalLayerSettings::CurvedCharAngleInOut, QgsPropertyDefinition( "CurvedCharAngleInOut", QgsPropertyDefinition::DataTypeString, QObject::tr( "Curved character angles" ), QObject::tr( "double coord [<b>in,out</b> as 20.0-60.0,20.0-95.0]" ), origin ) },
207 { QgsPalLayerSettings::RepeatDistance, QgsPropertyDefinition( "RepeatDistance", QObject::tr( "Repeat distance" ), QgsPropertyDefinition::DoublePositive, origin ) },
208 { QgsPalLayerSettings::RepeatDistanceUnit, QgsPropertyDefinition( "RepeatDistanceUnit", QObject::tr( "Repeat distance unit" ), QgsPropertyDefinition::RenderUnits, origin ) },
209 { QgsPalLayerSettings::OverrunDistance, QgsPropertyDefinition( "OverrunDistance", QObject::tr( "Overrun distance" ), QgsPropertyDefinition::DoublePositive, origin ) },
210 { QgsPalLayerSettings::LineAnchorPercent, QgsPropertyDefinition( "LineAnchorPercent", QObject::tr( "Line anchor percentage, as fraction from 0.0 to 1.0" ), QgsPropertyDefinition::Double0To1, origin ) },
211 { QgsPalLayerSettings::LineAnchorClipping, QgsPropertyDefinition( "LineAnchorClipping", QgsPropertyDefinition::DataTypeString, QObject::tr( "Line anchor clipping mode" ), QObject::tr( "string " ) + QStringLiteral( "[<b>visible</b>|<b>entire</b>]" ), origin ) },
212 { QgsPalLayerSettings::LineAnchorType, QgsPropertyDefinition( "LineAnchorType", QgsPropertyDefinition::DataTypeString, QObject::tr( "Line anchor type" ), QObject::tr( "string " ) + QStringLiteral( "[<b>hint</b>|<b>strict</b>]" ), origin ) },
213 { QgsPalLayerSettings::LineAnchorTextPoint, QgsPropertyDefinition( "LineAnchorTextPoint", QgsPropertyDefinition::DataTypeString, QObject::tr( "Line anchor text point" ), QObject::tr( "string " ) + QStringLiteral( "[<b>follow</b>|<b>start</b>|<b>center</b>|<b>end</b>]" ), origin ) },
214 { QgsPalLayerSettings::Priority, QgsPropertyDefinition( "Priority", QgsPropertyDefinition::DataTypeNumeric, QObject::tr( "Label priority" ), QObject::tr( "double [0.0-10.0]" ), origin ) },
215 { QgsPalLayerSettings::IsObstacle, QgsPropertyDefinition( "IsObstacle", QObject::tr( "Feature is a label obstacle" ), QgsPropertyDefinition::Boolean, origin ) },
216 { QgsPalLayerSettings::ObstacleFactor, QgsPropertyDefinition( "ObstacleFactor", QgsPropertyDefinition::DataTypeNumeric, QObject::tr( "Obstacle factor" ), QObject::tr( "double [0.0-10.0]" ), origin ) },
217 {
218 QgsPalLayerSettings::PredefinedPositionOrder, QgsPropertyDefinition( "PredefinedPositionOrder", QgsPropertyDefinition::DataTypeString, QObject::tr( "Predefined position order" ), QObject::tr( "Comma separated list of placements in order of priority<br>" )
219 + QStringLiteral( "[<b>TL</b>=Top left|<b>TSL</b>=Top, slightly left|<b>T</b>=Top middle|<br>"
220 "<b>TSR</b>=Top, slightly right|<b>TR</b>=Top right|<br>"
221 "<b>L</b>=Left|<b>R</b>=Right|<br>"
222 "<b>BL</b>=Bottom left|<b>BSL</b>=Bottom, slightly left|<b>B</b>=Bottom middle|<br>"
223 "<b>BSR</b>=Bottom, slightly right|<b>BR</b>=Bottom right]" ), origin )
224 },
225 {
226 QgsPalLayerSettings::LinePlacementOptions, QgsPropertyDefinition( "LinePlacementFlags", QgsPropertyDefinition::DataTypeString, QObject::tr( "Line placement options" ), QObject::tr( "Comma separated list of placement options<br>" )
227 + QStringLiteral( "[<b>OL</b>=On line|<b>AL</b>=Above line|<b>BL</b>=Below line|<br>"
228 "<b>LO</b>=Respect line orientation]" ), origin )
229 },
230 { QgsPalLayerSettings::PolygonLabelOutside, QgsPropertyDefinition( "PolygonLabelOutside", QgsPropertyDefinition::DataTypeString, QObject::tr( "Label outside polygons" ), QObject::tr( "string " ) + "[<b>yes</b> (allow placing outside)|<b>no</b> (never place outside)|<b>force</b> (always place outside)]", origin ) },
231 { QgsPalLayerSettings::PositionX, QgsPropertyDefinition( "PositionX", QObject::tr( "Position (X)" ), QgsPropertyDefinition::Double, origin ) },
232 { QgsPalLayerSettings::PositionY, QgsPropertyDefinition( "PositionY", QObject::tr( "Position (Y)" ), QgsPropertyDefinition::Double, origin ) },
233 { QgsPalLayerSettings::PositionPoint, QgsPropertyDefinition( "PositionPoint", QgsPropertyDefinition::DataTypeString, QObject::tr( "Position (point)" ), QObject::tr( "A point geometry" ), origin ) },
234 { QgsPalLayerSettings::Hali, QgsPropertyDefinition( "Hali", QgsPropertyDefinition::DataTypeString, QObject::tr( "Horizontal alignment" ), QObject::tr( "string " ) + "[<b>Left</b>|<b>Center</b>|<b>Right</b>]", origin ) },
235 {
236 QgsPalLayerSettings::Vali, QgsPropertyDefinition( "Vali", QgsPropertyDefinition::DataTypeString, QObject::tr( "Vertical alignment" ), QObject::tr( "string " ) + QStringLiteral( "[<b>Bottom</b>|<b>Base</b>|<br>"
237 "<b>Half</b>|<b>Cap</b>|<b>Top</b>]" ), origin )
238 },
239 { QgsPalLayerSettings::Rotation, QgsPropertyDefinition( "Rotation", QObject::tr( "Label rotation (deprecated)" ), QgsPropertyDefinition::Rotation, origin ) },
240 { QgsPalLayerSettings::LabelRotation, QgsPropertyDefinition( "LabelRotation", QObject::tr( "Label rotation" ), QgsPropertyDefinition::Rotation, origin ) },
241 { QgsPalLayerSettings::ScaleVisibility, QgsPropertyDefinition( "ScaleVisibility", QObject::tr( "Scale based visibility" ), QgsPropertyDefinition::Boolean, origin ) },
242 { QgsPalLayerSettings::MinScale, QgsPropertyDefinition( "MinScale", QObject::tr( "Minimum scale (denominator)" ), QgsPropertyDefinition::Double, origin ) },
243 { QgsPalLayerSettings::MaxScale, QgsPropertyDefinition( "MaxScale", QObject::tr( "Maximum scale (denominator)" ), QgsPropertyDefinition::Double, origin ) },
244 { QgsPalLayerSettings::MinimumScale, QgsPropertyDefinition( "MinimumScale", QObject::tr( "Minimum scale (denominator)" ), QgsPropertyDefinition::Double, origin ) },
245 { QgsPalLayerSettings::MaximumScale, QgsPropertyDefinition( "MaximumScale", QObject::tr( "Maximum scale (denominator)" ), QgsPropertyDefinition::Double, origin ) },
246
247 { QgsPalLayerSettings::FontLimitPixel, QgsPropertyDefinition( "FontLimitPixel", QObject::tr( "Limit font pixel size" ), QgsPropertyDefinition::Boolean, origin ) },
248 { QgsPalLayerSettings::FontMinPixel, QgsPropertyDefinition( "FontMinPixel", QObject::tr( "Minimum pixel size" ), QgsPropertyDefinition::IntegerPositive, origin ) },
249 { QgsPalLayerSettings::FontMaxPixel, QgsPropertyDefinition( "FontMaxPixel", QObject::tr( "Maximum pixel size" ), QgsPropertyDefinition::IntegerPositive, origin ) },
250 { QgsPalLayerSettings::ZIndex, QgsPropertyDefinition( "ZIndex", QObject::tr( "Label z-index" ), QgsPropertyDefinition::Double, origin ) },
251 { QgsPalLayerSettings::Show, QgsPropertyDefinition( "Show", QObject::tr( "Show label" ), QgsPropertyDefinition::Boolean, origin ) },
252 { QgsPalLayerSettings::AlwaysShow, QgsPropertyDefinition( "AlwaysShow", QObject::tr( "Always show label" ), QgsPropertyDefinition::Boolean, origin ) },
253 { QgsPalLayerSettings::CalloutDraw, QgsPropertyDefinition( "CalloutDraw", QObject::tr( "Draw callout" ), QgsPropertyDefinition::Boolean, origin ) },
254 { QgsPalLayerSettings::LabelAllParts, QgsPropertyDefinition( "LabelAllParts", QObject::tr( "Label all parts" ), QgsPropertyDefinition::Boolean, origin ) },
255 { QgsPalLayerSettings::AllowDegradedPlacement, QgsPropertyDefinition( "AllowDegradedPlacement", QObject::tr( "Allow inferior fallback placements" ), QgsPropertyDefinition::Boolean, origin ) },
256 { QgsPalLayerSettings::OverlapHandling, QgsPropertyDefinition( "OverlapHandling", QgsPropertyDefinition::DataTypeString, QObject::tr( "Overlap handing" ), QObject::tr( "string " ) + "[<b>Prevent</b>|<b>AllowIfNeeded</b>|<b>AlwaysAllow</b>]", origin ) },
257 };
258}
259
260Q_NOWARN_DEPRECATED_PUSH // because of deprecated members
262 : predefinedPositionOrder( *DEFAULT_PLACEMENT_ORDER() )
263 , mCallout( QgsCalloutRegistry::defaultCallout() )
264{
265 initPropertyDefinitions();
266
268}
270
271Q_NOWARN_DEPRECATED_PUSH // because of deprecated members
273 : fieldIndex( 0 )
274 , mDataDefinedProperties( s.mDataDefinedProperties )
275{
276 *this = s;
277}
279
281{
282 if ( this == &s )
283 return *this;
284
285 // copy only permanent stuff
286
288
289 // text style
297
298 // text formatting
299 wrapChar = s.wrapChar;
304 decimals = s.decimals;
305 plusSign = s.plusSign;
306
307 // placement
309 mPolygonPlacementFlags = s.mPolygonPlacementFlags;
315 xOffset = s.xOffset;
316 yOffset = s.yOffset;
319 dist = s.dist;
325 mRotationUnit = s.mRotationUnit;
328 priority = s.priority;
332
333 // rendering
341
343 zIndex = s.zIndex;
344
345 mFormat = s.mFormat;
346 mDataDefinedProperties = s.mDataDefinedProperties;
347
348 mCallout.reset( s.mCallout ? s.mCallout->clone() : nullptr );
349
350 mPlacementSettings = s.mPlacementSettings;
351 mLineSettings = s.mLineSettings;
352 mObstacleSettings = s.mObstacleSettings;
353 mThinningSettings = s.mThinningSettings;
354
359
360 mLegendString = s.mLegendString;
361
362 mUnplacedVisibility = s.mUnplacedVisibility;
363
364 return *this;
365}
366
367bool QgsPalLayerSettings::prepare( QgsRenderContext &context, QSet<QString> &attributeNames, const QgsFields &fields, const QgsMapSettings &mapSettings, const QgsCoordinateReferenceSystem &crs )
368{
369 if ( drawLabels )
370 {
371 if ( fieldName.isEmpty() )
372 {
373 return false;
374 }
375
376 if ( isExpression )
377 {
379 if ( exp.hasEvalError() )
380 {
381 QgsDebugMsgLevel( "Prepare error:" + exp.evalErrorString(), 4 );
382 return false;
383 }
384 }
385 else
386 {
387 // If we aren't an expression, we check to see if we can find the column.
388 if ( fields.lookupField( fieldName ) == -1 )
389 {
390 return false;
391 }
392 }
393 }
394
395 mCurFields = fields;
396
397 if ( drawLabels || mObstacleSettings.isObstacle() )
398 {
399 if ( drawLabels )
400 {
401 // add field indices for label's text, from expression or field
402 if ( isExpression )
403 {
404 // prepare expression for use in QgsPalLayerSettings::registerFeature()
406 exp->prepare( &context.expressionContext() );
407 if ( exp->hasEvalError() )
408 {
409 QgsDebugMsgLevel( "Prepare error:" + exp->evalErrorString(), 4 );
410 }
411 const auto referencedColumns = exp->referencedColumns();
412 for ( const QString &name : referencedColumns )
413 {
414 attributeNames.insert( name );
415 }
416 }
417 else
418 {
419 attributeNames.insert( fieldName );
420 }
421 }
422
423 mDataDefinedProperties.prepare( context.expressionContext() );
424 // add field indices of data defined expression or field
425 attributeNames.unite( dataDefinedProperties().referencedFields( context.expressionContext() ) );
426 }
427
428 // NOW INITIALIZE QgsPalLayerSettings
429
430 // TODO: ideally these (non-configuration) members should get out of QgsPalLayerSettings to QgsVectorLayerLabelProvider::prepare
431 // (together with registerFeature() & related methods) and QgsPalLayerSettings just stores config
432
433 // save the pal layer to our layer context (with some additional info)
434 fieldIndex = fields.lookupField( fieldName );
435
436 xform = &mapSettings.mapToPixel();
438 if ( context.coordinateTransform().isValid() )
439 // this is context for layer rendering
440 ct = context.coordinateTransform();
441 else
442 {
443 // otherwise fall back to creating our own CT
444 ct = QgsCoordinateTransform( crs, mapSettings.destinationCrs(), mapSettings.transformContext() );
445 }
446 ptZero = xform->toMapCoordinates( 0, 0 );
447 ptOne = xform->toMapCoordinates( 1, 0 );
448
449 // rect for clipping
450 QgsRectangle r1 = mapSettings.visibleExtent();
451 r1.grow( mapSettings.extentBuffer() );
453
454 if ( !qgsDoubleNear( mapSettings.rotation(), 0.0 ) )
455 {
456 //PAL features are prerotated, so extent also needs to be unrotated
457 extentGeom.rotate( -mapSettings.rotation(), mapSettings.visibleExtent().center() );
458 }
459
461
463 {
464 mGeometryGeneratorExpression = QgsExpression( geometryGenerator );
465 mGeometryGeneratorExpression.prepare( &context.expressionContext() );
466 if ( mGeometryGeneratorExpression.hasParserError() )
467 {
468 QgsMessageLog::logMessage( mGeometryGeneratorExpression.parserErrorString(), QObject::tr( "Labeling" ) );
469 return false;
470 }
471
472 const auto referencedColumns = mGeometryGeneratorExpression.referencedColumns();
473 for ( const QString &name : referencedColumns )
474 {
475 attributeNames.insert( name );
476 }
477 }
478 attributeNames.unite( mFormat.referencedFields( context ) );
479
480 if ( mCallout )
481 {
482 const auto referencedColumns = mCallout->referencedFields( context );
483 for ( const QString &name : referencedColumns )
484 {
485 attributeNames.insert( name );
486 }
487 }
488
489 return true;
490}
491
492QSet<QString> QgsPalLayerSettings::referencedFields( const QgsRenderContext &context ) const
493{
494 QSet<QString> referenced;
495 if ( drawLabels )
496 {
497 if ( isExpression )
498 {
499 referenced.unite( QgsExpression( fieldName ).referencedColumns() );
500 }
501 else
502 {
503 referenced.insert( fieldName );
504 }
505 }
506
507 referenced.unite( mFormat.referencedFields( context ) );
508
509 // calling referencedFields() with ignoreContext=true because in our expression context
510 // we do not have valid QgsFields yet - because of that the field names from expressions
511 // wouldn't get reported
512 referenced.unite( mDataDefinedProperties.referencedFields( context.expressionContext(), true ) );
513
515 {
516 QgsExpression geomGeneratorExpr( geometryGenerator );
517 referenced.unite( geomGeneratorExpr.referencedColumns() );
518 }
519
520 if ( mCallout )
521 {
522 referenced.unite( mCallout->referencedFields( context ) );
523 }
524
525 return referenced;
526}
527
529{
530 if ( mRenderStarted )
531 {
532 qWarning( "Start render called for when a previous render was already underway!!" );
533 return;
534 }
535
536 switch ( placement )
537 {
540 {
541 // force horizontal orientation, other orientation modes aren't unsupported for curved placement
543 mDataDefinedProperties.property( QgsPalLayerSettings::TextOrientation ).setActive( false );
544 break;
545 }
546
554 break;
555 }
556
557 if ( mCallout )
558 {
559 mCallout->startRender( context );
560 }
561
562 mRenderStarted = true;
563}
564
566{
567 if ( !mRenderStarted )
568 {
569 qWarning( "Stop render called for QgsPalLayerSettings without a startRender call!" );
570 return;
571 }
572
573 if ( mCallout )
574 {
575 mCallout->stopRender( context );
576 }
577
578 mRenderStarted = false;
579}
580
582{
583 return mFormat.containsAdvancedEffects() || mCallout->containsAdvancedEffects();
584}
585
587{
588 if ( mRenderStarted )
589 {
590 qWarning( "stopRender was not called on QgsPalLayerSettings object!" );
591 }
592
593 // pal layer is deleted internally in PAL
594
595 delete expression;
596}
597
598
600{
601 initPropertyDefinitions();
602 return *sPropertyDefinitions();
603}
604
606{
607 if ( !expression )
608 {
609 expression = new QgsExpression( fieldName );
610 }
611 return expression;
612}
613
615{
616 return mRotationUnit;
617}
618
620{
621 mRotationUnit = angleUnit;
622}
623
624QString updateDataDefinedString( const QString &value )
625{
626 // TODO: update or remove this when project settings for labeling are migrated to better XML layout
627 QString newValue = value;
628 if ( !value.isEmpty() && !value.contains( QLatin1String( "~~" ) ) )
629 {
630 QStringList values;
631 values << QStringLiteral( "1" ); // all old-style values are active if not empty
632 values << QStringLiteral( "0" );
633 values << QString();
634 values << value; // all old-style values are only field names
635 newValue = values.join( QLatin1String( "~~" ) );
636 }
637
638 return newValue;
639}
640
641void QgsPalLayerSettings::readOldDataDefinedProperty( QgsVectorLayer *layer, QgsPalLayerSettings::Property p )
642{
643 QString newPropertyName = "labeling/dataDefined/" + sPropertyDefinitions()->value( p ).name();
644 QVariant newPropertyField = layer->customProperty( newPropertyName, QVariant() );
645
646 if ( !newPropertyField.isValid() )
647 return;
648
649 QString ddString = newPropertyField.toString();
650
651 if ( !ddString.isEmpty() && ddString != QLatin1String( "0~~0~~~~" ) )
652 {
653 // TODO: update this when project settings for labeling are migrated to better XML layout
654 QString newStyleString = updateDataDefinedString( ddString );
655 QStringList ddv = newStyleString.split( QStringLiteral( "~~" ) );
656
657 bool active = ddv.at( 0 ).toInt();
658 if ( ddv.at( 1 ).toInt() )
659 {
660 mDataDefinedProperties.setProperty( p, QgsProperty::fromExpression( ddv.at( 2 ), active ) );
661 }
662 else
663 {
664 mDataDefinedProperties.setProperty( p, QgsProperty::fromField( ddv.at( 3 ), active ) );
665 }
666 }
667 else
668 {
669 // remove unused properties
670 layer->removeCustomProperty( newPropertyName );
671 }
672}
673
674void QgsPalLayerSettings::readOldDataDefinedPropertyMap( QgsVectorLayer *layer, QDomElement *parentElem )
675{
676 if ( !layer && !parentElem )
677 {
678 return;
679 }
680
681 QgsPropertiesDefinition::const_iterator i = sPropertyDefinitions()->constBegin();
682 for ( ; i != sPropertyDefinitions()->constEnd(); ++i )
683 {
684 if ( layer )
685 {
686 // reading from layer's custom properties
687 readOldDataDefinedProperty( layer, static_cast< Property >( i.key() ) );
688 }
689 else if ( parentElem )
690 {
691 // reading from XML
692 QDomElement e = parentElem->firstChildElement( i.value().name() );
693 if ( !e.isNull() )
694 {
695 bool active = e.attribute( QStringLiteral( "active" ) ).compare( QLatin1String( "true" ), Qt::CaseInsensitive ) == 0;
696 bool isExpression = e.attribute( QStringLiteral( "useExpr" ) ).compare( QLatin1String( "true" ), Qt::CaseInsensitive ) == 0;
697 if ( isExpression )
698 {
699 mDataDefinedProperties.setProperty( i.key(), QgsProperty::fromExpression( e.attribute( QStringLiteral( "expr" ) ), active ) );
700 }
701 else
702 {
703 mDataDefinedProperties.setProperty( i.key(), QgsProperty::fromField( e.attribute( QStringLiteral( "field" ) ), active ) );
704 }
705 }
706 }
707 }
708}
709
710void QgsPalLayerSettings::readFromLayerCustomProperties( QgsVectorLayer *layer )
711{
712 if ( layer->customProperty( QStringLiteral( "labeling" ) ).toString() != QLatin1String( "pal" ) )
713 {
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
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() )
787 else
789
790 if ( layer->customProperty( QStringLiteral( "labeling/labelOffsetMapUnitScale" ) ).toString().isEmpty() )
791 {
792 //fallback to older property
793 double oldMin = layer->customProperty( QStringLiteral( "labeling/labelOffsetMapUnitMinScale" ), 0.0 ).toDouble();
794 labelOffsetMapUnitScale.minScale = !qgsDoubleNear( oldMin, 0.0 ) ? 1.0 / oldMin : 0;
795 double oldMax = layer->customProperty( QStringLiteral( "labeling/labelOffsetMapUnitMaxScale" ), 0.0 ).toDouble();
796 labelOffsetMapUnitScale.maxScale = !qgsDoubleNear( oldMax, 0 ) ? 1.0 / oldMax : 0;
797 }
798 else
799 {
800 labelOffsetMapUnitScale = QgsSymbolLayerUtils::decodeMapUnitScale( layer->customProperty( QStringLiteral( "labeling/labelOffsetMapUnitScale" ) ).toString() );
801 }
802
803 QVariant tempAngle = layer->customProperty( QStringLiteral( "labeling/angleOffset" ), QVariant() );
804 if ( tempAngle.isValid() )
805 {
806 double oldAngle = layer->customProperty( QStringLiteral( "labeling/angleOffset" ), QVariant( 0.0 ) ).toDouble();
807 angleOffset = std::fmod( 360 - oldAngle, 360.0 );
808 }
809 else
810 {
811 angleOffset = layer->customProperty( QStringLiteral( "labeling/rotationAngle" ), QVariant( 0.0 ) ).toDouble();
812 }
813
814 preserveRotation = layer->customProperty( QStringLiteral( "labeling/preserveRotation" ), QVariant( true ) ).toBool();
815 mRotationUnit = layer->customEnumProperty( QStringLiteral( "labeling/rotationUnit" ), 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:
824 break;
825 case 1:
827 break;
828 case 2:
830 break;
831 case 3:
833 break;
834 }
835 if ( layer->customProperty( QStringLiteral( "labeling/repeatDistanceMapUnitScale" ) ).toString().isEmpty() )
836 {
837 //fallback to older property
838 double oldMin = layer->customProperty( QStringLiteral( "labeling/repeatDistanceMapUnitMinScale" ), 0.0 ).toDouble();
839 repeatDistanceMapUnitScale.minScale = !qgsDoubleNear( oldMin, 0 ) ? 1.0 / oldMin : 0;
840 double oldMax = layer->customProperty( QStringLiteral( "labeling/repeatDistanceMapUnitMaxScale" ), 0.0 ).toDouble();
841 repeatDistanceMapUnitScale.maxScale = !qgsDoubleNear( oldMax, 0 ) ? 1.0 / oldMax : 0;
842 }
843 else
844 {
845 repeatDistanceMapUnitScale = QgsSymbolLayerUtils::decodeMapUnitScale( layer->customProperty( QStringLiteral( "labeling/repeatDistanceMapUnitScale" ) ).toString() );
846 }
847
848 // rendering
849 double scalemn = layer->customProperty( QStringLiteral( "labeling/scaleMin" ), QVariant( 0 ) ).toDouble();
850 double scalemx = layer->customProperty( QStringLiteral( "labeling/scaleMax" ), QVariant( 0 ) ).toDouble();
851
852 // fix for scale visibility limits being keyed off of just its values in the past (<2.0)
853 QVariant scalevis = layer->customProperty( QStringLiteral( "labeling/scaleVisibility" ), QVariant() );
854 if ( scalevis.isValid() )
855 {
856 scaleVisibility = scalevis.toBool();
857 maximumScale = scalemn;
858 minimumScale = scalemx;
859 }
860 else if ( scalemn > 0 || scalemx > 0 )
861 {
862 scaleVisibility = true;
863 maximumScale = scalemn;
864 minimumScale = scalemx;
865 }
866 else
867 {
868 // keep scaleMin and scaleMax at new 1.0 defaults (1 and 10000000, were 0 and 0)
869 scaleVisibility = false;
870 }
871
872
873 fontLimitPixelSize = layer->customProperty( QStringLiteral( "labeling/fontLimitPixelSize" ), QVariant( false ) ).toBool();
874 fontMinPixelSize = layer->customProperty( QStringLiteral( "labeling/fontMinPixelSize" ), QVariant( 0 ) ).toInt();
875 fontMaxPixelSize = layer->customProperty( QStringLiteral( "labeling/fontMaxPixelSize" ), QVariant( 10000 ) ).toInt();
876 if ( layer->customProperty( QStringLiteral( "labeling/displayAll" ), QVariant( false ) ).toBool() )
877 {
879 mPlacementSettings.setAllowDegradedPlacement( true );
880 }
881 else
882 {
884 mPlacementSettings.setAllowDegradedPlacement( false );
885 }
886 upsidedownLabels = static_cast< Qgis::UpsideDownLabelHandling >( layer->customProperty( QStringLiteral( "labeling/upsidedownLabels" ), QVariant( static_cast< int >( Qgis::UpsideDownLabelHandling::FlipUpsideDownLabels ) ) ).toUInt() );
887
888 labelPerPart = layer->customProperty( QStringLiteral( "labeling/labelPerPart" ) ).toBool();
889 mLineSettings.setMergeLines( layer->customProperty( QStringLiteral( "labeling/mergeLines" ) ).toBool() );
890 mThinningSettings.setMinimumFeatureSize( layer->customProperty( QStringLiteral( "labeling/minFeatureSize" ) ).toDouble() );
891 mThinningSettings.setLimitNumberLabelsEnabled( layer->customProperty( QStringLiteral( "labeling/limitNumLabels" ), QVariant( false ) ).toBool() );
892 mThinningSettings.setMaximumNumberLabels( layer->customProperty( QStringLiteral( "labeling/maxNumLabels" ), QVariant( 2000 ) ).toInt() );
893 mObstacleSettings.setIsObstacle( layer->customProperty( QStringLiteral( "labeling/obstacle" ), QVariant( true ) ).toBool() );
894 mObstacleSettings.setFactor( layer->customProperty( QStringLiteral( "labeling/obstacleFactor" ), QVariant( 1.0 ) ).toDouble() );
895 mObstacleSettings.setType( static_cast< QgsLabelObstacleSettings::ObstacleType >( layer->customProperty( QStringLiteral( "labeling/obstacleType" ), QVariant( PolygonInterior ) ).toUInt() ) );
896 zIndex = layer->customProperty( QStringLiteral( "labeling/zIndex" ), QVariant( 0.0 ) ).toDouble();
897
898 mDataDefinedProperties.clear();
899 if ( layer->customProperty( QStringLiteral( "labeling/ddProperties" ) ).isValid() )
900 {
901 QDomDocument doc( QStringLiteral( "dd" ) );
902 doc.setContent( layer->customProperty( QStringLiteral( "labeling/ddProperties" ) ).toString() );
903 QDomElement elem = doc.firstChildElement( QStringLiteral( "properties" ) );
904 mDataDefinedProperties.readXml( elem, *sPropertyDefinitions() );
905 }
906 else
907 {
908 // read QGIS 2.x style data defined properties
909 readOldDataDefinedPropertyMap( layer, nullptr );
910 }
911 // upgrade older data defined settings
912 if ( mDataDefinedProperties.isActive( FontTransp ) )
913 {
914 mDataDefinedProperties.setProperty( FontOpacity, QgsProperty::fromExpression( QStringLiteral( "100 - (%1)" ).arg( mDataDefinedProperties.property( FontTransp ).asExpression() ) ) );
915 mDataDefinedProperties.setProperty( FontTransp, QgsProperty() );
916 }
917 if ( mDataDefinedProperties.isActive( BufferTransp ) )
918 {
919 mDataDefinedProperties.setProperty( BufferOpacity, QgsProperty::fromExpression( QStringLiteral( "100 - (%1)" ).arg( mDataDefinedProperties.property( BufferTransp ).asExpression() ) ) );
920 mDataDefinedProperties.setProperty( BufferTransp, QgsProperty() );
921 }
922 if ( mDataDefinedProperties.isActive( ShapeTransparency ) )
923 {
924 mDataDefinedProperties.setProperty( ShapeOpacity, QgsProperty::fromExpression( QStringLiteral( "100 - (%1)" ).arg( mDataDefinedProperties.property( ShapeTransparency ).asExpression() ) ) );
925 mDataDefinedProperties.setProperty( ShapeTransparency, QgsProperty() );
926 }
927 if ( mDataDefinedProperties.isActive( ShadowTransparency ) )
928 {
929 mDataDefinedProperties.setProperty( ShadowOpacity, QgsProperty::fromExpression( QStringLiteral( "100 - (%1)" ).arg( mDataDefinedProperties.property( ShadowTransparency ).asExpression() ) ) );
930 mDataDefinedProperties.setProperty( ShadowTransparency, QgsProperty() );
931 }
932 if ( mDataDefinedProperties.isActive( Rotation ) )
933 {
934 mDataDefinedProperties.setProperty( LabelRotation, QgsProperty::fromExpression( QStringLiteral( "360 - (%1)" ).arg( mDataDefinedProperties.property( Rotation ).asExpression() ) ) );
935 mDataDefinedProperties.setProperty( Rotation, QgsProperty() );
936 }
937 // older 2.x projects had min/max scale flipped - so change them here.
938 if ( mDataDefinedProperties.isActive( MinScale ) )
939 {
940 mDataDefinedProperties.setProperty( MaximumScale, mDataDefinedProperties.property( MinScale ) );
941 mDataDefinedProperties.setProperty( MinScale, QgsProperty() );
942 }
943 if ( mDataDefinedProperties.isActive( MaxScale ) )
944 {
945 mDataDefinedProperties.setProperty( MinimumScale, mDataDefinedProperties.property( MaxScale ) );
946 mDataDefinedProperties.setProperty( MaxScale, QgsProperty() );
947 }
948}
949
950void QgsPalLayerSettings::readXml( const QDomElement &elem, const QgsReadWriteContext &context )
951{
952 // text style
953 QDomElement textStyleElem = elem.firstChildElement( QStringLiteral( "text-style" ) );
954 fieldName = textStyleElem.attribute( QStringLiteral( "fieldName" ) );
955 isExpression = textStyleElem.attribute( QStringLiteral( "isExpression" ) ).toInt();
956
957 mFormat.readXml( elem, context );
959 previewBkgrdColor = QColor( textStyleElem.attribute( QStringLiteral( "previewBkgrdColor" ), QStringLiteral( "#ffffff" ) ) );
961 substitutions.readXml( textStyleElem.firstChildElement( QStringLiteral( "substitutions" ) ) );
962 useSubstitutions = textStyleElem.attribute( QStringLiteral( "useSubstitutions" ) ).toInt();
963 mLegendString = textStyleElem.attribute( QStringLiteral( "legendString" ), QObject::tr( "Aa" ) );
964
965 // text formatting
966 QDomElement textFormatElem = elem.firstChildElement( QStringLiteral( "text-format" ) );
967 wrapChar = textFormatElem.attribute( QStringLiteral( "wrapChar" ) );
968 autoWrapLength = textFormatElem.attribute( QStringLiteral( "autoWrapLength" ), QStringLiteral( "0" ) ).toInt();
969 useMaxLineLengthForAutoWrap = textFormatElem.attribute( QStringLiteral( "useMaxLineLengthForAutoWrap" ), QStringLiteral( "1" ) ).toInt();
970 multilineAlign = static_cast< Qgis::LabelMultiLineAlignment >( textFormatElem.attribute( QStringLiteral( "multilineAlign" ), QString::number( static_cast< int >( Qgis::LabelMultiLineAlignment::FollowPlacement ) ) ).toUInt() );
971 mLineSettings.setAddDirectionSymbol( textFormatElem.attribute( QStringLiteral( "addDirectionSymbol" ) ).toInt() );
972 mLineSettings.setLeftDirectionSymbol( textFormatElem.attribute( QStringLiteral( "leftDirectionSymbol" ), QStringLiteral( "<" ) ) );
973 mLineSettings.setRightDirectionSymbol( textFormatElem.attribute( QStringLiteral( "rightDirectionSymbol" ), QStringLiteral( ">" ) ) );
974 mLineSettings.setReverseDirectionSymbol( textFormatElem.attribute( QStringLiteral( "reverseDirectionSymbol" ) ).toInt() );
975 mLineSettings.setDirectionSymbolPlacement( static_cast< QgsLabelLineSettings::DirectionSymbolPlacement >( textFormatElem.attribute( QStringLiteral( "placeDirectionSymbol" ), QString::number( static_cast< int >( QgsLabelLineSettings::DirectionSymbolPlacement::SymbolLeftRight ) ) ).toUInt() ) );
976 formatNumbers = textFormatElem.attribute( QStringLiteral( "formatNumbers" ) ).toInt();
977 decimals = textFormatElem.attribute( QStringLiteral( "decimals" ) ).toInt();
978 plusSign = textFormatElem.attribute( QStringLiteral( "plussign" ) ).toInt();
979
980 // placement
981 QDomElement placementElem = elem.firstChildElement( QStringLiteral( "placement" ) );
982 placement = static_cast< Qgis::LabelPlacement >( placementElem.attribute( QStringLiteral( "placement" ) ).toInt() );
983 mLineSettings.setPlacementFlags( static_cast< 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() )
997 else
999 }
1000 else
1001 {
1002 distUnits = QgsUnitTypes::decodeRenderUnit( placementElem.attribute( QStringLiteral( "distUnits" ) ) );
1003 }
1004 if ( !placementElem.hasAttribute( QStringLiteral( "distMapUnitScale" ) ) )
1005 {
1006 //fallback to older property
1007 double oldMin = placementElem.attribute( QStringLiteral( "distMapUnitMinScale" ), QStringLiteral( "0" ) ).toDouble();
1008 distMapUnitScale.minScale = !qgsDoubleNear( oldMin, 0 ) ? 1.0 / oldMin : 0;
1009 double oldMax = placementElem.attribute( QStringLiteral( "distMapUnitMaxScale" ), QStringLiteral( "0" ) ).toDouble();
1010 distMapUnitScale.maxScale = !qgsDoubleNear( oldMax, 0 ) ? 1.0 / oldMax : 0;
1011 }
1012 else
1013 {
1014 distMapUnitScale = QgsSymbolLayerUtils::decodeMapUnitScale( placementElem.attribute( QStringLiteral( "distMapUnitScale" ) ) );
1015 }
1016 offsetType = static_cast< Qgis::LabelOffsetType >( placementElem.attribute( QStringLiteral( "offsetType" ), QString::number( static_cast< int >( Qgis::LabelOffsetType::FromPoint ) ) ).toUInt() );
1017 quadOffset = static_cast< Qgis::LabelQuadrantPosition >( placementElem.attribute( QStringLiteral( "quadOffset" ), QString::number( static_cast< int >( Qgis::LabelQuadrantPosition::Over ) ) ).toUInt() );
1018 xOffset = placementElem.attribute( QStringLiteral( "xOffset" ), QStringLiteral( "0" ) ).toDouble();
1019 yOffset = placementElem.attribute( QStringLiteral( "yOffset" ), QStringLiteral( "0" ) ).toDouble();
1020 if ( !placementElem.hasAttribute( QStringLiteral( "offsetUnits" ) ) )
1021 {
1022 offsetUnits = placementElem.attribute( QStringLiteral( "labelOffsetInMapUnits" ), QStringLiteral( "1" ) ).toInt() ? 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:
1073 break;
1074 case 1:
1076 break;
1077 case 2:
1079 break;
1080 case 3:
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
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
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 {
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
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
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( QLocale().toString( d, '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
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;
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
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;
2348 double angleInRadians = 0.0;
2349 double quadOffsetX = 0.0, quadOffsetY = 0.0;
2350 double offsetX = 0.0, offsetY = 0.0;
2351 QgsPointXY anchorPosition;
2352
2354 {
2355 anchorPosition = geom.centroid().asPoint();
2356 }
2357 //x/y shift in case of alignment
2358 double xdiff = 0.0;
2359 double ydiff = 0.0;
2360
2361 //data defined quadrant offset?
2362 bool ddFixedQuad = false;
2364 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::OffsetQuad ) )
2365 {
2366 context.expressionContext().setOriginalValueVariable( static_cast< int >( quadOff ) );
2367 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::OffsetQuad, context.expressionContext() );
2368 if ( !QgsVariantUtils::isNull( exprVal ) )
2369 {
2370 bool ok;
2371 int quadInt = exprVal.toInt( &ok );
2372 if ( ok && 0 <= quadInt && quadInt <= 8 )
2373 {
2374 quadOff = static_cast< Qgis::LabelQuadrantPosition >( quadInt );
2375 ddFixedQuad = true;
2376 }
2377 }
2378 }
2379
2380 // adjust quadrant offset of labels
2381 switch ( quadOff )
2382 {
2384 quadOffsetX = -1.0;
2385 quadOffsetY = 1.0;
2386 break;
2388 quadOffsetX = 0.0;
2389 quadOffsetY = 1.0;
2390 break;
2392 quadOffsetX = 1.0;
2393 quadOffsetY = 1.0;
2394 break;
2396 quadOffsetX = -1.0;
2397 quadOffsetY = 0.0;
2398 break;
2400 quadOffsetX = 1.0;
2401 quadOffsetY = 0.0;
2402 break;
2404 quadOffsetX = -1.0;
2405 quadOffsetY = -1.0;
2406 break;
2408 quadOffsetX = 0.0;
2409 quadOffsetY = -1.0;
2410 break;
2412 quadOffsetX = 1.0;
2413 quadOffsetY = -1.0;
2414 break;
2416 break;
2417 }
2418
2419 //data defined label offset?
2420 double xOff = xOffset;
2421 double yOff = yOffset;
2422 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::OffsetXY ) )
2423 {
2425 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::OffsetXY, context.expressionContext() );
2426 bool ok = false;
2427 const QPointF ddOffPt = QgsSymbolLayerUtils::toPoint( exprVal, &ok );
2428 if ( ok )
2429 {
2430 xOff = ddOffPt.x();
2431 yOff = ddOffPt.y();
2432 }
2433 }
2434
2435 // data defined label offset units?
2436 Qgis::RenderUnit offUnit = offsetUnits;
2437 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::OffsetUnits ) )
2438 {
2439 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::OffsetUnits, context.expressionContext() );
2440 if ( !QgsVariantUtils::isNull( exprVal ) )
2441 {
2442 QString units = exprVal.toString().trimmed();
2443 if ( !units.isEmpty() )
2444 {
2445 bool ok = false;
2446 Qgis::RenderUnit decodedUnits = QgsUnitTypes::decodeRenderUnit( units, &ok );
2447 if ( ok )
2448 {
2449 offUnit = decodedUnits;
2450 }
2451 }
2452 }
2453 }
2454
2455 // adjust offset of labels to match chosen unit and map scale
2456 // offsets match those of symbology: -x = left, -y = up
2457 offsetX = context.convertToMapUnits( xOff, offUnit, labelOffsetMapUnitScale );
2458 // must be negative to match symbology offset direction
2459 offsetY = context.convertToMapUnits( -yOff, offUnit, labelOffsetMapUnitScale );
2460
2461 // layer defined rotation?
2462 if ( !qgsDoubleNear( angleOffset, 0.0 ) )
2463 {
2464 layerDefinedRotation = true;
2465 angleInRadians = ( 360 - angleOffset ) * M_PI / 180; // convert to radians counterclockwise
2466 }
2467
2468 const QgsMapToPixel &m2p = context.mapToPixel();
2469 //data defined rotation?
2470 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::LabelRotation ) )
2471 {
2473 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::LabelRotation, context.expressionContext() );
2474 if ( !QgsVariantUtils::isNull( exprVal ) )
2475 {
2476 bool ok;
2477 const double rotation = exprVal.toDouble( &ok );
2478 if ( ok )
2479 {
2480 dataDefinedRotation = true;
2481
2482 double rotationDegrees = rotation * QgsUnitTypes::fromUnitToUnitFactor( mRotationUnit,
2484
2485 // TODO: add setting to disable having data defined rotation follow
2486 // map rotation ?
2487 rotationDegrees += m2p.mapRotation();
2488 angleInRadians = ( 360 - rotationDegrees ) * M_PI / 180.0;
2489 }
2490 }
2491 }
2492
2493 bool hasDataDefinedPosition = false;
2494 {
2495 bool ddPosition = false;
2496
2497 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::PositionX )
2498 && mDataDefinedProperties.isActive( QgsPalLayerSettings::PositionY ) )
2499 {
2500 const QVariant xPosProperty = mDataDefinedProperties.value( QgsPalLayerSettings::PositionX, context.expressionContext() );
2501 const QVariant yPosProperty = mDataDefinedProperties.value( QgsPalLayerSettings::PositionY, context.expressionContext() );
2502 if ( !QgsVariantUtils::isNull( xPosProperty )
2503 && !QgsVariantUtils::isNull( yPosProperty ) )
2504 {
2505 ddPosition = true;
2506
2507 bool ddXPos = false, ddYPos = false;
2508 xPos = xPosProperty.toDouble( &ddXPos );
2509 yPos = yPosProperty.toDouble( &ddYPos );
2510 if ( ddXPos && ddYPos )
2511 hasDataDefinedPosition = true;
2512 }
2513 }
2514 else if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::PositionPoint ) )
2515 {
2516 const QVariant pointPosProperty = mDataDefinedProperties.value( QgsPalLayerSettings::PositionPoint, context.expressionContext() );
2517 if ( !QgsVariantUtils::isNull( pointPosProperty ) )
2518 {
2519 ddPosition = true;
2520
2521 QgsPoint point;
2522 if ( pointPosProperty.userType() == QMetaType::type( "QgsReferencedGeometry" ) )
2523 {
2524 QgsReferencedGeometry referencedGeometryPoint = pointPosProperty.value<QgsReferencedGeometry>();
2525 point = QgsPoint( referencedGeometryPoint.asPoint() );
2526
2527 if ( !referencedGeometryPoint.isNull()
2528 && ct.sourceCrs() != referencedGeometryPoint.crs() )
2529 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 );
2530 }
2531 else if ( pointPosProperty.userType() == QMetaType::type( "QgsGeometry" ) )
2532 {
2533 point = QgsPoint( pointPosProperty.value<QgsGeometry>().asPoint() );
2534 }
2535
2536 if ( !point.isEmpty() )
2537 {
2538 hasDataDefinedPosition = true;
2539
2540 xPos = point.x();
2541 yPos = point.y();
2542 }
2543 }
2544 }
2545
2546 if ( ddPosition )
2547 {
2548 //data defined position. But field values could be NULL -> positions will be generated by PAL
2549 if ( hasDataDefinedPosition )
2550 {
2551 // layer rotation set, but don't rotate pinned labels unless data defined
2552 if ( layerDefinedRotation && !dataDefinedRotation )
2553 {
2554 angleInRadians = 0.0;
2555 }
2556
2557 //horizontal alignment
2558 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Hali ) )
2559 {
2560 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Hali, context.expressionContext() );
2561 if ( !QgsVariantUtils::isNull( exprVal ) )
2562 {
2563 QString haliString = exprVal.toString();
2564 if ( haliString.compare( QLatin1String( "Center" ), Qt::CaseInsensitive ) == 0 )
2565 {
2566 xdiff -= labelWidth / 2.0;
2567 }
2568 else if ( haliString.compare( QLatin1String( "Right" ), Qt::CaseInsensitive ) == 0 )
2569 {
2570 xdiff -= labelWidth;
2571 }
2572 }
2573 }
2574
2575 //vertical alignment
2576 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Vali ) )
2577 {
2578 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Vali, context.expressionContext() );
2579 if ( !QgsVariantUtils::isNull( exprVal ) )
2580 {
2581 QString valiString = exprVal.toString();
2582 if ( valiString.compare( QLatin1String( "Bottom" ), Qt::CaseInsensitive ) != 0 )
2583 {
2584 if ( valiString.compare( QLatin1String( "Top" ), Qt::CaseInsensitive ) == 0 )
2585 {
2586 ydiff -= labelHeight;;
2587 }
2588 else
2589 {
2590 double descentRatio = labelFontMetrics->descent() / labelFontMetrics->height();
2591 if ( valiString.compare( QLatin1String( "Base" ), Qt::CaseInsensitive ) == 0 )
2592 {
2593 ydiff -= labelHeight * descentRatio;
2594 }
2595 else //'Cap' or 'Half'
2596 {
2597 double capHeightRatio = ( labelFontMetrics->boundingRect( 'H' ).height() + 1 + labelFontMetrics->descent() ) / labelFontMetrics->height();
2598 ydiff -= labelHeight * capHeightRatio;
2599 if ( valiString.compare( QLatin1String( "Half" ), Qt::CaseInsensitive ) == 0 )
2600 {
2601 ydiff += labelHeight * ( capHeightRatio - descentRatio ) / 2.0;
2602 }
2603 }
2604 }
2605 }
2606 }
2607 }
2608
2609 if ( dataDefinedRotation )
2610 {
2611 //adjust xdiff and ydiff because the hali/vali point needs to be the rotation center
2612 double xd = xdiff * std::cos( angleInRadians ) - ydiff * std::sin( angleInRadians );
2613 double yd = xdiff * std::sin( angleInRadians ) + ydiff * std::cos( angleInRadians );
2614 xdiff = xd;
2615 ydiff = yd;
2616 }
2617
2618 //project xPos and yPos from layer to map CRS, handle rotation
2619 QgsGeometry ddPoint( new QgsPoint( xPos, yPos ) );
2620 if ( QgsPalLabeling::geometryRequiresPreparation( ddPoint, context, ct ) )
2621 {
2622 ddPoint = QgsPalLabeling::prepareGeometry( ddPoint, context, ct );
2623 if ( const QgsPoint *point = qgsgeometry_cast< const QgsPoint * >( ddPoint.constGet() ) )
2624 {
2625 xPos = point->x();
2626 yPos = point->y();
2627 anchorPosition = QgsPointXY( xPos, yPos );
2628 }
2629 else
2630 {
2631 QgsMessageLog::logMessage( QObject::tr( "Invalid data defined label position (%1, %2)" ).arg( xPos ).arg( yPos ), QObject::tr( "Labeling" ) );
2632 hasDataDefinedPosition = false;
2633 }
2634 }
2635 else
2636 {
2637 anchorPosition = QgsPointXY( xPos, yPos );
2638 }
2639
2640 xPos += xdiff;
2641 yPos += ydiff;
2642 }
2643 else
2644 {
2645 anchorPosition = QgsPointXY( xPos, yPos );
2646 }
2647 }
2648 }
2649
2650 // data defined always show?
2651 bool alwaysShow = false;
2652 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::AlwaysShow ) )
2653 {
2654 alwaysShow = mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::AlwaysShow, context.expressionContext(), false );
2655 }
2656
2657 // set repeat distance
2658 // data defined repeat distance?
2659 double repeatDist = repeatDistance;
2660 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::RepeatDistance ) )
2661 {
2662 context.expressionContext().setOriginalValueVariable( repeatDist );
2663 repeatDist = mDataDefinedProperties.valueAsDouble( QgsPalLayerSettings::RepeatDistance, context.expressionContext(), repeatDist );
2664 }
2665
2666 // data defined label-repeat distance units?
2668 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::RepeatDistanceUnit ) )
2669 {
2670 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::RepeatDistanceUnit, context.expressionContext() );
2671 if ( !QgsVariantUtils::isNull( exprVal ) )
2672 {
2673 QString units = exprVal.toString().trimmed();
2674 if ( !units.isEmpty() )
2675 {
2676 bool ok = false;
2677 Qgis::RenderUnit decodedUnits = QgsUnitTypes::decodeRenderUnit( units, &ok );
2678 if ( ok )
2679 {
2680 repeatUnits = decodedUnits;
2681 }
2682 }
2683 }
2684 }
2685
2686 if ( !qgsDoubleNear( repeatDist, 0.0 ) )
2687 {
2688 if ( repeatUnits != Qgis::RenderUnit::MapUnits )
2689 {
2690 repeatDist = context.convertToMapUnits( repeatDist, repeatUnits, repeatDistanceMapUnitScale );
2691 }
2692 }
2693
2694 // overrun distance
2695 double overrunDistanceEval = lineSettings.overrunDistance();
2696 if ( !qgsDoubleNear( overrunDistanceEval, 0.0 ) )
2697 {
2698 overrunDistanceEval = context.convertToMapUnits( overrunDistanceEval, lineSettings.overrunDistanceUnit(), lineSettings.overrunDistanceMapUnitScale() );
2699 }
2700
2701 // we smooth out the overrun label extensions by 1 mm, to avoid little jaggies right at the start or end of the lines
2702 // causing the overrun extension to extend out in an undesirable direction. This is hard coded, we don't want to overload
2703 // users with options they likely don't need to see...
2704 const double overrunSmoothDist = context.convertToMapUnits( 1, Qgis::RenderUnit::Millimeters );
2705
2706 bool labelAll = labelPerPart && !hasDataDefinedPosition;
2707 if ( !hasDataDefinedPosition )
2708 {
2709 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::LabelAllParts ) )
2710 {
2712 labelAll = mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::LabelAllParts, context.expressionContext(), labelPerPart );
2713 }
2714 }
2715
2716 // feature to the layer
2717 std::unique_ptr< QgsTextLabelFeature > labelFeature = std::make_unique< QgsTextLabelFeature>( feature.id(), std::move( geos_geom_clone ), QSizeF( labelWidth, labelHeight ) );
2718 labelFeature->setAnchorPosition( anchorPosition );
2719 labelFeature->setFeature( feature );
2720 labelFeature->setSymbol( symbol );
2721 labelFeature->setDocument( doc, documentMetrics );
2722 if ( !qgsDoubleNear( rotatedLabelX, 0.0 ) && !qgsDoubleNear( rotatedLabelY, 0.0 ) )
2723 labelFeature->setRotatedSize( QSizeF( rotatedLabelX, rotatedLabelY ) );
2724 mFeatsRegPal++;
2725
2726 labelFeature->setHasFixedPosition( hasDataDefinedPosition );
2727 labelFeature->setFixedPosition( QgsPointXY( xPos, yPos ) );
2728 // use layer-level defined rotation, but not if position fixed
2729 labelFeature->setHasFixedAngle( dataDefinedRotation || ( !hasDataDefinedPosition && !qgsDoubleNear( angleInRadians, 0.0 ) ) );
2730 labelFeature->setFixedAngle( angleInRadians );
2731 labelFeature->setQuadOffset( QPointF( quadOffsetX, quadOffsetY ) );
2732 labelFeature->setPositionOffset( QgsPointXY( offsetX, offsetY ) );
2733 labelFeature->setOffsetType( offsetType );
2734 labelFeature->setAlwaysShow( alwaysShow );
2735 labelFeature->setRepeatDistance( repeatDist );
2736 labelFeature->setLabelText( labelText );
2737 labelFeature->setPermissibleZone( permissibleZone );
2738 labelFeature->setOverrunDistance( overrunDistanceEval );
2739 labelFeature->setOverrunSmoothDistance( overrunSmoothDist );
2740 labelFeature->setLineAnchorPercent( lineSettings.lineAnchorPercent() );
2741 labelFeature->setLineAnchorType( lineSettings.anchorType() );
2742 labelFeature->setLineAnchorTextPoint( lineSettings.anchorTextPoint() );
2743 labelFeature->setLabelAllParts( labelAll );
2744 labelFeature->setOriginalFeatureCrs( context.coordinateTransform().sourceCrs() );
2745 labelFeature->setMinimumSize( minimumSize );
2746 if ( geom.type() == Qgis::GeometryType::Point && !obstacleGeometry.isNull() )
2747 {
2748 //register symbol size
2749 labelFeature->setSymbolSize( QSizeF( obstacleGeometry.boundingBox().width(),
2750 obstacleGeometry.boundingBox().height() ) );
2751 }
2752
2753 if ( outerBounds.left() != 0 || outerBounds.top() != 0 || !qgsDoubleNear( outerBounds.width(), labelWidth ) || !qgsDoubleNear( outerBounds.height(), labelHeight ) )
2754 {
2755 labelFeature->setOuterBounds( outerBounds );
2756 }
2757
2758 //set label's visual margin so that top visual margin is the leading, and bottom margin is the font's descent
2759 //this makes labels align to the font's baseline or highest character
2760 double topMargin = std::max( 0.25 * labelFontMetrics->ascent(), 0.0 );
2761 double bottomMargin = 1.0 + labelFontMetrics->descent();
2762 QgsMargins vm( 0.0, topMargin, 0.0, bottomMargin );
2763 vm *= xform->mapUnitsPerPixel();
2764 labelFeature->setVisualMargin( vm );
2765
2766 // store the label's calculated font for later use during painting
2767 QgsDebugMsgLevel( QStringLiteral( "PAL font stored definedFont: %1, Style: %2" ).arg( labelFont.toString(), labelFont.styleName() ), 4 );
2768 labelFeature->setDefinedFont( labelFont );
2769
2770 labelFeature->setMaximumCharacterAngleInside( std::clamp( maxcharanglein, 20.0, 60.0 ) * M_PI / 180 );
2771 labelFeature->setMaximumCharacterAngleOutside( std::clamp( maxcharangleout, -95.0, -20.0 ) * M_PI / 180 );
2772 switch ( placement )
2773 {
2781 // these placements don't require text metrics
2782 break;
2783
2786 labelFeature->setTextMetrics( QgsTextLabelFeature::calculateTextMetrics( xform, context, labelFont, *labelFontMetrics, labelFont.letterSpacing(), labelFont.wordSpacing(), labelText, format().allowHtmlFormatting() ? &doc : nullptr, format().allowHtmlFormatting() ? &documentMetrics : nullptr ) );
2787 break;
2788 }
2789
2790 // for labelFeature the LabelInfo is passed to feat when it is registered
2791
2792 // TODO: allow layer-wide feature dist in PAL...?
2793
2794 // data defined label-feature distance?
2795 double distance = dist;
2796 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::LabelDistance ) )
2797 {
2798 context.expressionContext().setOriginalValueVariable( distance );
2799 distance = mDataDefinedProperties.valueAsDouble( QgsPalLayerSettings::LabelDistance, context.expressionContext(), distance );
2800 }
2801
2802 // data defined label-feature distance units?
2803 Qgis::RenderUnit distUnit = distUnits;
2804 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::DistanceUnits ) )
2805 {
2806 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::DistanceUnits, context.expressionContext() );
2807 if ( !QgsVariantUtils::isNull( exprVal ) )
2808 {
2809 QString units = exprVal.toString().trimmed();
2810 QgsDebugMsgLevel( QStringLiteral( "exprVal DistanceUnits:%1" ).arg( units ), 4 );
2811 if ( !units.isEmpty() )
2812 {
2813 bool ok = false;
2814 Qgis::RenderUnit decodedUnits = QgsUnitTypes::decodeRenderUnit( units, &ok );
2815 if ( ok )
2816 {
2817 distUnit = decodedUnits;
2818 }
2819 }
2820 }
2821 }
2822 distance = context.convertToPainterUnits( distance, distUnit, distMapUnitScale );
2823
2824 // when using certain placement modes, we force a tiny minimum distance. This ensures that
2825 // candidates are created just offset from a border and avoids candidates being incorrectly flagged as colliding with neighbours
2826 switch ( placement )
2827 {
2831 distance = ( distance < 0 ? -1 : 1 ) * std::max( std::fabs( distance ), 1.0 );
2832 break;
2833
2835 break;
2836
2842 {
2843 distance = std::max( distance, 2.0 );
2844 }
2845 break;
2846
2848 distance = std::max( distance, 2.0 );
2849 break;
2850 }
2851
2852 if ( !qgsDoubleNear( distance, 0.0 ) )
2853 {
2854 double d = ptOne.distance( ptZero ) * distance;
2855 labelFeature->setDistLabel( d );
2856 }
2857
2858 if ( ddFixedQuad )
2859 {
2860 labelFeature->setHasFixedQuadrant( true );
2861 }
2862
2863 labelFeature->setArrangementFlags( lineSettings.placementFlags() );
2864
2865 labelFeature->setPolygonPlacementFlags( polygonPlacement );
2866
2867 // data defined z-index?
2868 double z = zIndex;
2869 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::ZIndex ) )
2870 {
2872 z = mDataDefinedProperties.valueAsDouble( QgsPalLayerSettings::ZIndex, context.expressionContext(), z );
2873 }
2874 labelFeature->setZIndex( z );
2875
2876 // data defined priority?
2877 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Priority ) )
2878 {
2880 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Priority, context.expressionContext() );
2881 if ( !QgsVariantUtils::isNull( exprVal ) )
2882 {
2883 bool ok;
2884 double priorityD = exprVal.toDouble( &ok );
2885 if ( ok )
2886 {
2887 priorityD = std::clamp( priorityD, 0.0, 10.0 );
2888 priorityD = 1 - priorityD / 10.0; // convert 0..10 --> 1..0
2889 labelFeature->setPriority( priorityD );
2890 }
2891 }
2892 }
2893
2894 // data defined allow degraded placement
2895 {
2896 double allowDegradedPlacement = mPlacementSettings.allowDegradedPlacement();
2897 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::AllowDegradedPlacement ) )
2898 {
2899 context.expressionContext().setOriginalValueVariable( allowDegradedPlacement );
2900 allowDegradedPlacement = mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::AllowDegradedPlacement, context.expressionContext(), allowDegradedPlacement );
2901 }
2902 labelFeature->setAllowDegradedPlacement( allowDegradedPlacement );
2903 }
2904
2905 // data defined overlap handling
2906 {
2907 Qgis::LabelOverlapHandling overlapHandling = mPlacementSettings.overlapHandling();
2908 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::OverlapHandling ) )
2909 {
2910 const QString handlingString = mDataDefinedProperties.valueAsString( QgsPalLayerSettings::OverlapHandling, context.expressionContext() );
2911 const QString cleanedString = handlingString.trimmed();
2912 if ( cleanedString.compare( QLatin1String( "prevent" ), Qt::CaseInsensitive ) == 0 )
2914 else if ( cleanedString.compare( QLatin1String( "allowifneeded" ), Qt::CaseInsensitive ) == 0 )
2916 else if ( cleanedString.compare( QLatin1String( "alwaysallow" ), Qt::CaseInsensitive ) == 0 )
2918 }
2919 labelFeature->setOverlapHandling( overlapHandling );
2920 }
2921
2922 QgsLabelObstacleSettings os = mObstacleSettings;
2923 os.setIsObstacle( isObstacle );
2924 os.updateDataDefinedProperties( mDataDefinedProperties, context.expressionContext() );
2925 os.setObstacleGeometry( obstacleGeometry );
2926 labelFeature->setObstacleSettings( os );
2927
2928 QVector< Qgis::LabelPredefinedPointPosition > positionOrder = predefinedPositionOrder;
2929 if ( positionOrder.isEmpty() )
2930 positionOrder = *DEFAULT_PLACEMENT_ORDER();
2931
2932 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::PredefinedPositionOrder ) )
2933 {
2935 QString dataDefinedOrder = mDataDefinedProperties.valueAsString( QgsPalLayerSettings::PredefinedPositionOrder, context.expressionContext() );
2936 if ( !dataDefinedOrder.isEmpty() )
2937 {
2938 positionOrder = QgsLabelingUtils::decodePredefinedPositionOrder( dataDefinedOrder );
2939 }
2940 }
2941 labelFeature->setPredefinedPositionOrder( positionOrder );
2942
2943 // add parameters for data defined labeling to label feature
2944 labelFeature->setDataDefinedValues( dataDefinedValues );
2945
2946 return labelFeature;
2947}
2948
2949std::unique_ptr<QgsLabelFeature> QgsPalLayerSettings::registerObstacleFeature( const QgsFeature &f, QgsRenderContext &context, const QgsGeometry &obstacleGeometry )
2950{
2951 mCurFeat = &f;
2952
2953 QgsGeometry geom;
2954 if ( !obstacleGeometry.isNull() )
2955 {
2956 geom = obstacleGeometry;
2957 }
2958 else
2959 {
2960 geom = f.geometry();
2961 }
2962
2963 if ( geom.isNull() )
2964 {
2965 return nullptr;
2966 }
2967
2968 // don't even try to register linestrings with only one vertex as an obstacle
2969 if ( const QgsLineString *ls = qgsgeometry_cast< const QgsLineString * >( geom.constGet() ) )
2970 {
2971 if ( ls->numPoints() < 2 )
2972 return nullptr;
2973 }
2974
2975 // simplify?
2976 const QgsVectorSimplifyMethod &simplifyMethod = context.vectorSimplifyMethod();
2977 std::unique_ptr<QgsGeometry> scopedClonedGeom;
2978 if ( simplifyMethod.simplifyHints() != QgsVectorSimplifyMethod::NoSimplification && simplifyMethod.forceLocalOptimization() )
2979 {
2980 int simplifyHints = simplifyMethod.simplifyHints() | QgsMapToPixelSimplifier::SimplifyEnvelope;
2982 QgsMapToPixelSimplifier simplifier( simplifyHints, simplifyMethod.tolerance(), simplifyAlgorithm );
2983 geom = simplifier.simplify( geom );
2984 }
2985
2986 geos::unique_ptr geos_geom_clone;
2987 std::unique_ptr<QgsGeometry> scopedPreparedGeom;
2988
2989 if ( QgsPalLabeling::geometryRequiresPreparation( geom, context, ct, extentGeom, mLineSettings.mergeLines() ) )
2990 {
2991 geom = QgsPalLabeling::prepareGeometry( geom, context, ct, extentGeom, mLineSettings.mergeLines() );
2992 }
2993 geos_geom_clone = QgsGeos::asGeos( geom );
2994
2995 if ( !geos_geom_clone )
2996 return nullptr; // invalid geometry
2997
2998 // feature to the layer
2999 std::unique_ptr< QgsLabelFeature > obstacleFeature = std::make_unique< QgsLabelFeature >( f.id(), std::move( geos_geom_clone ), QSizeF( 0, 0 ) );
3000 obstacleFeature->setFeature( f );
3001
3002 QgsLabelObstacleSettings os = mObstacleSettings;
3003 os.setIsObstacle( true );
3004 os.updateDataDefinedProperties( mDataDefinedProperties, context.expressionContext() );
3005 obstacleFeature->setObstacleSettings( os );
3006
3007 mFeatsRegPal++;
3008 return obstacleFeature;
3009}
3010
3011bool QgsPalLayerSettings::dataDefinedValEval( DataDefinedValueType valType,
3013 QVariant &exprVal, QgsExpressionContext &context, const QVariant &originalValue )
3014{
3015 if ( !mDataDefinedProperties.isActive( p ) )
3016 return false;
3017
3018 context.setOriginalValueVariable( originalValue );
3019 exprVal = mDataDefinedProperties.value( p, context );
3020 if ( !QgsVariantUtils::isNull( exprVal ) )
3021 {
3022 switch ( valType )
3023 {
3024 case DDBool:
3025 {
3026 bool bol = exprVal.toBool();
3027 dataDefinedValues.insert( p, QVariant( bol ) );
3028 return true;
3029 }
3030 case DDInt:
3031 {
3032 bool ok;
3033 int size = exprVal.toInt( &ok );
3034
3035 if ( ok )
3036 {
3037 dataDefinedValues.insert( p, QVariant( size ) );
3038 return true;
3039 }
3040 return false;
3041 }
3042 case DDIntPos:
3043 {
3044 bool ok;
3045 int size = exprVal.toInt( &ok );
3046
3047 if ( ok && size > 0 )
3048 {
3049 dataDefinedValues.insert( p, QVariant( size ) );
3050 return true;
3051 }
3052 return false;
3053 }
3054 case DDDouble:
3055 {
3056 bool ok;
3057 double size = exprVal.toDouble( &ok );
3058
3059 if ( ok )
3060 {
3061 dataDefinedValues.insert( p, QVariant( size ) );
3062 return true;
3063 }
3064 return false;
3065 }
3066 case DDDoublePos:
3067 {
3068 bool ok;
3069 double size = exprVal.toDouble( &ok );
3070
3071 if ( ok && size > 0.0 )
3072 {
3073 dataDefinedValues.insert( p, QVariant( size ) );
3074 return true;
3075 }
3076 return false;
3077 }
3078 case DDRotation180:
3079 {
3080 bool ok;
3081 double rot = exprVal.toDouble( &ok );
3082 if ( ok )
3083 {
3084 if ( rot < -180.0 && rot >= -360 )
3085 {
3086 rot += 360;
3087 }
3088 if ( rot > 180.0 && rot <= 360 )
3089 {
3090 rot -= 360;
3091 }
3092 if ( rot >= -180 && rot <= 180 )
3093 {
3094 dataDefinedValues.insert( p, QVariant( rot ) );
3095 return true;
3096 }
3097 }
3098 return false;
3099 }
3100 case DDOpacity:
3101 {
3102 bool ok;
3103 int size = exprVal.toInt( &ok );
3104 if ( ok && size >= 0 && size <= 100 )
3105 {
3106 dataDefinedValues.insert( p, QVariant( size ) );
3107 return true;
3108 }
3109 return false;
3110 }
3111 case DDString:
3112 {
3113 QString str = exprVal.toString(); // don't trim whitespace
3114
3115 dataDefinedValues.insert( p, QVariant( str ) ); // let it stay empty if it is
3116 return true;
3117 }
3118 case DDUnits:
3119 {
3120 QString unitstr = exprVal.toString().trimmed();
3121
3122 if ( !unitstr.isEmpty() )
3123 {
3124 dataDefinedValues.insert( p, QVariant( static_cast< int >( QgsUnitTypes::decodeRenderUnit( unitstr ) ) ) );
3125 return true;
3126 }
3127 return false;
3128 }
3129 case DDColor:
3130 {
3131 QString colorstr = exprVal.toString().trimmed();
3132 QColor color = QgsSymbolLayerUtils::decodeColor( colorstr );
3133
3134 if ( color.isValid() )
3135 {
3136 dataDefinedValues.insert( p, QVariant( color ) );
3137 return true;
3138 }
3139 return false;
3140 }
3141 case DDJoinStyle:
3142 {
3143 QString joinstr = exprVal.toString().trimmed();
3144
3145 if ( !joinstr.isEmpty() )
3146 {
3147 dataDefinedValues.insert( p, QVariant( static_cast< int >( QgsSymbolLayerUtils::decodePenJoinStyle( joinstr ) ) ) );
3148 return true;
3149 }
3150 return false;
3151 }
3152 case DDBlendMode:
3153 {
3154 QString blendstr = exprVal.toString().trimmed();
3155
3156 if ( !blendstr.isEmpty() )
3157 {
3158 dataDefinedValues.insert( p, QVariant( static_cast< int >( QgsSymbolLayerUtils::decodeBlendMode( blendstr ) ) ) );
3159 return true;
3160 }
3161 return false;
3162 }
3163 case DDPointF:
3164 {
3165 bool ok = false;
3166 const QPointF res = QgsSymbolLayerUtils::toPoint( exprVal, &ok );
3167 if ( ok )
3168 {
3169 dataDefinedValues.insert( p, res );
3170 return true;
3171 }
3172 return false;
3173 }
3174 case DDSizeF:
3175 {
3176 bool ok = false;
3177 const QSizeF res = QgsSymbolLayerUtils::toSize( exprVal, &ok );
3178 if ( ok )
3179 {
3180 dataDefinedValues.insert( p, res );
3181 return true;
3182 }
3183 return false;
3184 }
3185 }
3186 }
3187 return false;
3188}
3189
3190void QgsPalLayerSettings::parseTextStyle( QFont &labelFont,
3191 Qgis::RenderUnit fontunits,
3192 QgsRenderContext &context )
3193{
3194 // NOTE: labelFont already has pixelSize set, so pointSize or pointSizeF might return -1
3195
3196 QVariant exprVal; // value() is repeatedly nulled on data defined evaluation and replaced when successful
3197
3198 // Two ways to generate new data defined font:
3199 // 1) Family + [bold] + [italic] (named style is ignored and font is built off of base family)
3200 // 2) Family + named style (bold or italic is ignored)
3201
3202 // data defined font family?
3203 QString ddFontFamily;
3204 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Family ) )
3205 {
3206 context.expressionContext().setOriginalValueVariable( labelFont.family() );
3207 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Family, context.expressionContext() );
3208 if ( !QgsVariantUtils::isNull( exprVal ) )
3209 {
3210 QString family = exprVal.toString().trimmed();
3211 QgsDebugMsgLevel( QStringLiteral( "exprVal Font family:%1" ).arg( family ), 4 );
3212
3214 if ( labelFont.family() != family )
3215 {
3216 // testing for ddFontFamily in QFontDatabase.families() may be slow to do for every feature
3217 // (i.e. don't use QgsFontUtils::fontFamilyMatchOnSystem( family ) here)
3218 if ( QgsFontUtils::fontFamilyOnSystem( family ) )
3219 {
3220 ddFontFamily = family;
3221 }
3222 }
3223 }
3224 }
3225
3226 // data defined named font style?
3227 QString ddFontStyle;
3228 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::FontStyle ) )
3229 {
3230 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::FontStyle, context.expressionContext() );
3231 if ( !QgsVariantUtils::isNull( exprVal ) )
3232 {
3233 QString fontstyle = exprVal.toString().trimmed();
3234 QgsDebugMsgLevel( QStringLiteral( "exprVal Font style:%1" ).arg( fontstyle ), 4 );
3235 ddFontStyle = fontstyle;
3236 }
3237 }
3238
3239 // data defined bold font style?
3240 bool ddBold = false;
3241 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Bold ) )
3242 {
3243 context.expressionContext().setOriginalValueVariable( labelFont.bold() );
3244 ddBold = mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::Bold, context.expressionContext(), false );
3245 }
3246
3247 // data defined italic font style?
3248 bool ddItalic = false;
3249 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Italic ) )
3250 {
3251 context.expressionContext().setOriginalValueVariable( labelFont.italic() );
3252 ddItalic = mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::Italic, context.expressionContext(), false );
3253 }
3254
3255 // TODO: update when pref for how to resolve missing family (use matching algorithm or just default font) is implemented
3256 // (currently defaults to what has been read in from layer settings)
3257 QFont newFont;
3258 QFont appFont = QApplication::font();
3259 bool newFontBuilt = false;
3260 if ( ddBold || ddItalic )
3261 {
3262 // new font needs built, since existing style needs removed
3263 newFont = QFont( !ddFontFamily.isEmpty() ? ddFontFamily : labelFont.family() );
3264 newFontBuilt = true;
3265 newFont.setBold( ddBold );
3266 newFont.setItalic( ddItalic );
3267 }
3268 else if ( !ddFontStyle.isEmpty()
3269 && ddFontStyle.compare( QLatin1String( "Ignore" ), Qt::CaseInsensitive ) != 0 )
3270 {
3271 if ( !ddFontFamily.isEmpty() )
3272 {
3273 // both family and style are different, build font from database
3274 if ( !mFontDB )
3275 mFontDB = std::make_unique< QFontDatabase >();
3276
3277 QFont styledfont = mFontDB->font( ddFontFamily, ddFontStyle, appFont.pointSize() );
3278 if ( appFont != styledfont )
3279 {
3280 newFont = styledfont;
3281 newFontBuilt = true;
3282 }
3283 }
3284
3285 // update the font face style
3286 QgsFontUtils::updateFontViaStyle( newFontBuilt ? newFont : labelFont, ddFontStyle );
3287 }
3288 else if ( !ddFontFamily.isEmpty() )
3289 {
3290 if ( ddFontStyle.compare( QLatin1String( "Ignore" ), Qt::CaseInsensitive ) != 0 )
3291 {
3292 // just family is different, build font from database
3293 if ( !mFontDB )
3294 mFontDB = std::make_unique< QFontDatabase >();
3295 QFont styledfont = mFontDB->font( ddFontFamily, mFormat.namedStyle(), appFont.pointSize() );
3296 if ( appFont != styledfont )
3297 {
3298 newFont = styledfont;
3299 newFontBuilt = true;
3300 }
3301 }
3302 else
3303 {
3304 newFont = QFont( ddFontFamily );
3305 newFontBuilt = true;
3306 }
3307 }
3308
3309 if ( newFontBuilt )
3310 {
3311 // copy over existing font settings
3312 //newFont = newFont.resolve( labelFont ); // should work, but let's be sure what's being copied
3313 newFont.setPixelSize( labelFont.pixelSize() );
3314 newFont.setUnderline( labelFont.underline() );
3315 newFont.setStrikeOut( labelFont.strikeOut() );
3316 newFont.setWordSpacing( labelFont.wordSpacing() );
3317 newFont.setLetterSpacing( QFont::AbsoluteSpacing, labelFont.letterSpacing() );
3318
3319 labelFont = newFont;
3320 }
3321
3322 // data defined word spacing?
3323 double wordspace = labelFont.wordSpacing();
3324 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::FontWordSpacing ) )
3325 {
3326 context.expressionContext().setOriginalValueVariable( wordspace );
3327 wordspace = mDataDefinedProperties.valueAsDouble( QgsPalLayerSettings::FontWordSpacing, context.expressionContext(), wordspace );
3328 }
3329 labelFont.setWordSpacing( context.convertToPainterUnits( wordspace, fontunits, mFormat.sizeMapUnitScale() ) );
3330
3331 // data defined letter spacing?
3332 double letterspace = labelFont.letterSpacing();
3333 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::FontLetterSpacing ) )
3334 {
3335 context.expressionContext().setOriginalValueVariable( letterspace );
3336 letterspace = mDataDefinedProperties.valueAsDouble( QgsPalLayerSettings::FontLetterSpacing, context.expressionContext(), letterspace );
3337 }
3338 labelFont.setLetterSpacing( QFont::AbsoluteSpacing, context.convertToPainterUnits( letterspace, fontunits, mFormat.sizeMapUnitScale() ) );
3339
3340 // data defined strikeout font style?
3341 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Strikeout ) )
3342 {
3343 context.expressionContext().setOriginalValueVariable( labelFont.strikeOut() );
3344 bool strikeout = mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::Strikeout, context.expressionContext(), false );
3345 labelFont.setStrikeOut( strikeout );
3346 }
3347
3348 // data defined stretch
3349 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::FontStretchFactor ) )
3350 {
3352 labelFont.setStretch( mDataDefinedProperties.valueAsInt( QgsPalLayerSettings::FontStretchFactor, context.expressionContext(), mFormat.stretchFactor() ) );
3353 }
3354
3355 // data defined underline font style?
3356 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Underline ) )
3357 {
3358 context.expressionContext().setOriginalValueVariable( labelFont.underline() );
3359 bool underline = mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::Underline, context.expressionContext(), false );
3360 labelFont.setUnderline( underline );
3361 }
3362
3363 // pass the rest on to QgsPalLabeling::drawLabeling
3364
3365 // data defined font color?
3366 dataDefinedValEval( DDColor, QgsPalLayerSettings::Color, exprVal, context.expressionContext(), QgsSymbolLayerUtils::encodeColor( mFormat.color() ) );
3367
3368 // data defined font opacity?
3369 dataDefinedValEval( DDOpacity, QgsPalLayerSettings::FontOpacity, exprVal, context.expressionContext(), mFormat.opacity() * 100 );
3370
3371 // data defined font blend mode?
3372 dataDefinedValEval( DDBlendMode, QgsPalLayerSettings::FontBlendMode, exprVal, context.expressionContext() );
3373
3374}
3375
3376void QgsPalLayerSettings::parseTextBuffer( QgsRenderContext &context )
3377{
3378 QVariant exprVal; // value() is repeatedly nulled on data defined evaluation and replaced when successful
3379
3380 QgsTextBufferSettings buffer = mFormat.buffer();
3381
3382 // data defined draw buffer?
3383 bool drawBuffer = mFormat.buffer().enabled();
3384 if ( dataDefinedValEval( DDBool, QgsPalLayerSettings::BufferDraw, exprVal, context.expressionContext(), buffer.enabled() ) )
3385 {
3386 drawBuffer = exprVal.toBool();
3387 }
3388 else if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::BufferDraw ) && QgsVariantUtils::isNull( exprVal ) )
3389 {
3390 dataDefinedValues.insert( QgsPalLayerSettings::BufferDraw, QVariant( drawBuffer ) );
3391 }
3392
3393 if ( !drawBuffer )
3394 {
3395 return;
3396 }
3397
3398 // data defined buffer size?
3399 double bufrSize = buffer.size();
3400 if ( dataDefinedValEval( DDDoublePos, QgsPalLayerSettings::BufferSize, exprVal, context.expressionContext(), buffer.size() ) )
3401 {
3402 bufrSize = exprVal.toDouble();
3403 }
3404
3405 // data defined buffer transparency?
3406 double bufferOpacity = buffer.opacity() * 100;
3407 if ( dataDefinedValEval( DDOpacity, QgsPalLayerSettings::BufferOpacity, exprVal, context.expressionContext(), bufferOpacity ) )
3408 {
3409 bufferOpacity = exprVal.toDouble();
3410 }
3411
3412 drawBuffer = ( drawBuffer && bufrSize > 0.0 && bufferOpacity > 0 );
3413
3414 if ( !drawBuffer )
3415 {
3416 dataDefinedValues.insert( QgsPalLayerSettings::BufferDraw, QVariant( false ) ); // trigger value
3417 dataDefinedValues.remove( QgsPalLayerSettings::BufferSize );
3418 dataDefinedValues.remove( QgsPalLayerSettings::BufferOpacity );
3419 return; // don't bother evaluating values that won't be used
3420 }
3421
3422 // data defined buffer units?
3423 dataDefinedValEval( DDUnits, QgsPalLayerSettings::BufferUnit, exprVal, context.expressionContext() );
3424
3425 // data defined buffer color?
3426 dataDefinedValEval( DDColor, QgsPalLayerSettings::BufferColor, exprVal, context.expressionContext(), QgsSymbolLayerUtils::encodeColor( buffer.color() ) );
3427
3428 // data defined buffer pen join style?
3429 dataDefinedValEval( DDJoinStyle, QgsPalLayerSettings::BufferJoinStyle, exprVal, context.expressionContext(), QgsSymbolLayerUtils::encodePenJoinStyle( buffer.joinStyle() ) );
3430
3431 // data defined buffer blend mode?
3432 dataDefinedValEval( DDBlendMode, QgsPalLayerSettings::BufferBlendMode, exprVal, context.expressionContext() );
3433}
3434
3435void QgsPalLayerSettings::parseTextMask( QgsRenderContext &context )
3436{
3437 QVariant exprVal; // value() is repeatedly nulled on data defined evaluation and replaced when successful
3438
3439 QgsTextMaskSettings mask = mFormat.mask();
3440
3441 // data defined enabled mask?
3442 bool maskEnabled = mask.enabled();
3443 if ( dataDefinedValEval( DDBool, QgsPalLayerSettings::MaskEnabled, exprVal, context.expressionContext(), mask.enabled() ) )
3444 {
3445 maskEnabled = exprVal.toBool();
3446 }
3447
3448 if ( !maskEnabled )
3449 {
3450 return;
3451 }
3452
3453 // data defined buffer size?
3454 double bufrSize = mask.size();
3455 if ( dataDefinedValEval( DDDoublePos, QgsPalLayerSettings::MaskBufferSize, exprVal, context.expressionContext(), mask.size() ) )
3456 {
3457 bufrSize = exprVal.toDouble();
3458 }
3459
3460 // data defined opacity?
3461 double opacity = mask.opacity() * 100;
3462 if ( dataDefinedValEval( DDOpacity, QgsPalLayerSettings::MaskOpacity, exprVal, context.expressionContext(), opacity ) )
3463 {
3464 opacity = exprVal.toDouble();
3465 }
3466
3467 maskEnabled = ( maskEnabled && bufrSize > 0.0 && opacity > 0 );
3468
3469 if ( !maskEnabled )
3470 {
3471 dataDefinedValues.insert( QgsPalLayerSettings::MaskEnabled, QVariant( false ) ); // trigger value
3472 dataDefinedValues.remove( QgsPalLayerSettings::MaskBufferSize );
3473 dataDefinedValues.remove( QgsPalLayerSettings::MaskOpacity );
3474 return; // don't bother evaluating values that won't be used
3475 }
3476
3477 // data defined buffer units?
3478 dataDefinedValEval( DDUnits, QgsPalLayerSettings::MaskBufferUnit, exprVal, context.expressionContext() );
3479
3480 // data defined buffer pen join style?
3481 dataDefinedValEval( DDJoinStyle, QgsPalLayerSettings::MaskJoinStyle, exprVal, context.expressionContext(), QgsSymbolLayerUtils::encodePenJoinStyle( mask.joinStyle() ) );
3482}
3483
3484void QgsPalLayerSettings::parseTextFormatting( QgsRenderContext &context )
3485{
3486 QVariant exprVal; // value() is repeatedly nulled on data defined evaluation and replaced when successful
3487
3488 // data defined multiline wrap character?
3489 QString wrapchr = wrapChar;
3490 if ( dataDefinedValEval( DDString, QgsPalLayerSettings::MultiLineWrapChar, exprVal, context.expressionContext(), wrapChar ) )
3491 {
3492 wrapchr = exprVal.toString();
3493 }
3494
3495 int evalAutoWrapLength = autoWrapLength;
3496 if ( dataDefinedValEval( DDInt, QgsPalLayerSettings::AutoWrapLength, exprVal, context.expressionContext(), evalAutoWrapLength ) )
3497 {
3498 evalAutoWrapLength = exprVal.toInt();
3499 }
3500
3501 // data defined multiline height?
3502 dataDefinedValEval( DDDouble, QgsPalLayerSettings::MultiLineHeight, exprVal, context.expressionContext() );
3503
3504 // data defined multiline text align?
3505 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::MultiLineAlignment ) )
3506 {
3508 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::MultiLineAlignment, context.expressionContext() );
3509 if ( !QgsVariantUtils::isNull( exprVal ) )
3510 {
3511 QString str = exprVal.toString().trimmed();
3512 QgsDebugMsgLevel( QStringLiteral( "exprVal MultiLineAlignment:%1" ).arg( str ), 4 );
3513
3514 if ( !str.isEmpty() )
3515 {
3516 // "Left"
3518
3519 if ( str.compare( QLatin1String( "Center" ), Qt::CaseInsensitive ) == 0 )
3520 {
3522 }
3523 else if ( str.compare( QLatin1String( "Right" ), Qt::CaseInsensitive ) == 0 )
3524 {
3526 }
3527 else if ( str.compare( QLatin1String( "Follow" ), Qt::CaseInsensitive ) == 0 )
3528 {
3530 }
3531 else if ( str.compare( QLatin1String( "Justify" ), Qt::CaseInsensitive ) == 0 )
3532 {
3534 }
3535 dataDefinedValues.insert( QgsPalLayerSettings::MultiLineAlignment, QVariant( static_cast< int >( aligntype ) ) );
3536 }
3537 }
3538 }
3539
3540 // text orientation
3541 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::TextOrientation ) )
3542 {
3543 const QString encoded = QgsTextRendererUtils::encodeTextOrientation( mFormat.orientation() );
3544 context.expressionContext().setOriginalValueVariable( encoded );
3545 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::TextOrientation, context.expressionContext() );
3546 if ( !QgsVariantUtils::isNull( exprVal ) )
3547 {
3548 QString str = exprVal.toString().trimmed();
3549 if ( !str.isEmpty() )
3550 dataDefinedValues.insert( QgsPalLayerSettings::TextOrientation, str );
3551 }
3552 }
3553
3554 // data defined direction symbol?
3555 bool drawDirSymb = mLineSettings.addDirectionSymbol();
3556 if ( dataDefinedValEval( DDBool, QgsPalLayerSettings::DirSymbDraw, exprVal, context.expressionContext(), drawDirSymb ) )
3557 {
3558 drawDirSymb = exprVal.toBool();
3559 }
3560
3561 if ( drawDirSymb )
3562 {
3563 // data defined direction left symbol?
3564 dataDefinedValEval( DDString, QgsPalLayerSettings::DirSymbLeft, exprVal, context.expressionContext(), mLineSettings.leftDirectionSymbol() );
3565
3566 // data defined direction right symbol?
3567 dataDefinedValEval( DDString, QgsPalLayerSettings::DirSymbRight, exprVal, context.expressionContext(), mLineSettings.rightDirectionSymbol() );
3568
3569 // data defined direction symbol placement?
3570 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::DirSymbPlacement, context.expressionContext() );
3571 if ( !QgsVariantUtils::isNull( exprVal ) )
3572 {
3573 QString str = exprVal.toString().trimmed();
3574 QgsDebugMsgLevel( QStringLiteral( "exprVal DirSymbPlacement:%1" ).arg( str ), 4 );
3575
3576 if ( !str.isEmpty() )
3577 {
3578 // "LeftRight"
3580
3581 if ( str.compare( QLatin1String( "Above" ), Qt::CaseInsensitive ) == 0 )
3582 {
3584 }
3585 else if ( str.compare( QLatin1String( "Below" ), Qt::CaseInsensitive ) == 0 )
3586 {
3588 }
3589 dataDefinedValues.insert( QgsPalLayerSettings::DirSymbPlacement, QVariant( static_cast< int >( placetype ) ) );
3590 }
3591 }
3592
3593 // data defined direction symbol reversed?
3594 dataDefinedValEval( DDBool, QgsPalLayerSettings::DirSymbReverse, exprVal, context.expressionContext(), mLineSettings.reverseDirectionSymbol() );
3595 }
3596
3597 // formatting for numbers is inline with generation of base label text and not passed to label painting
3598}
3599
3600void QgsPalLayerSettings::parseShapeBackground( QgsRenderContext &context )
3601{
3602 QVariant exprVal; // value() is repeatedly nulled on data defined evaluation and replaced when successful
3603
3604 QgsTextBackgroundSettings background = mFormat.background();
3605
3606 // data defined draw shape?
3607 bool drawShape = background.enabled();
3608 if ( dataDefinedValEval( DDBool, QgsPalLayerSettings::ShapeDraw, exprVal, context.expressionContext(), drawShape ) )
3609 {
3610 drawShape = exprVal.toBool();
3611 }
3612
3613 if ( !drawShape )
3614 {
3615 return;
3616 }
3617
3618 // data defined shape transparency?
3619 double shapeOpacity = background.opacity() * 100;
3620 if ( dataDefinedValEval( DDOpacity, QgsPalLayerSettings::ShapeOpacity, exprVal, context.expressionContext(), shapeOpacity ) )
3621 {
3622 shapeOpacity = 100.0 * exprVal.toDouble();
3623 }
3624
3625 drawShape = ( drawShape && shapeOpacity > 0 ); // size is not taken into account (could be)
3626
3627 if ( !drawShape )
3628 {
3629 dataDefinedValues.insert( QgsPalLayerSettings::ShapeDraw, QVariant( false ) ); // trigger value
3630 dataDefinedValues.remove( QgsPalLayerSettings::ShapeOpacity );
3631 return; // don't bother evaluating values that won't be used
3632 }
3633
3634 // data defined shape kind?
3635 QgsTextBackgroundSettings::ShapeType shapeKind = background.type();
3636 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::ShapeKind ) )
3637 {
3638 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::ShapeKind, context.expressionContext() );
3639 if ( !QgsVariantUtils::isNull( exprVal ) )
3640 {
3641 QString skind = exprVal.toString().trimmed();
3642 QgsDebugMsgLevel( QStringLiteral( "exprVal ShapeKind:%1" ).arg( skind ), 4 );
3643
3644 if ( !skind.isEmpty() )
3645 {
3646 shapeKind = QgsTextRendererUtils::decodeShapeType( skind );
3647 dataDefinedValues.insert( QgsPalLayerSettings::ShapeKind, QVariant( static_cast< int >( shapeKind ) ) );
3648 }
3649 }
3650 }
3651
3652 // data defined shape SVG path?
3653 QString svgPath = background.svgFile();
3654 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::ShapeSVGFile ) )
3655 {
3656 context.expressionContext().setOriginalValueVariable( svgPath );
3657 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::ShapeSVGFile, context.expressionContext() );
3658 if ( !QgsVariantUtils::isNull( exprVal ) )
3659 {
3660 QString svgfile = exprVal.toString().trimmed();
3661 QgsDebugMsgLevel( QStringLiteral( "exprVal ShapeSVGFile:%1" ).arg( svgfile ), 4 );
3662
3663 // '' empty paths are allowed
3664 svgPath = QgsSymbolLayerUtils::svgSymbolNameToPath( svgfile, context.pathResolver() );
3665 dataDefinedValues.insert( QgsPalLayerSettings::ShapeSVGFile, QVariant( svgPath ) );
3666 }
3667 }
3668
3669 // data defined shape size type?
3670 QgsTextBackgroundSettings::SizeType shpSizeType = background.sizeType();
3671 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::ShapeSizeType ) )
3672 {
3673 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::ShapeSizeType, context.expressionContext() );
3674 if ( !QgsVariantUtils::isNull( exprVal ) )
3675 {
3676 QString stype = exprVal.toString().trimmed();
3677 QgsDebugMsgLevel( QStringLiteral( "exprVal ShapeSizeType:%1" ).arg( stype ), 4 );
3678
3679 if ( !stype.isEmpty() )
3680 {
3682 dataDefinedValues.insert( QgsPalLayerSettings::ShapeSizeType, QVariant( static_cast< int >( shpSizeType ) ) );
3683 }
3684 }
3685 }
3686
3687 // data defined shape size X? (SVGs only use X for sizing)
3688 double ddShpSizeX = background.size().width();
3689 if ( dataDefinedValEval( DDDouble, QgsPalLayerSettings::ShapeSizeX, exprVal, context.expressionContext(), ddShpSizeX ) )
3690 {
3691 ddShpSizeX = exprVal.toDouble();
3692 }
3693
3694 // data defined shape size Y?
3695 double ddShpSizeY = background.size().height();
3696 if ( dataDefinedValEval( DDDouble, QgsPalLayerSettings::ShapeSizeY, exprVal, context.expressionContext(), ddShpSizeY ) )
3697 {
3698 ddShpSizeY = exprVal.toDouble();
3699 }
3700
3701 // don't continue under certain circumstances (e.g. size is fixed)
3702 bool skip = false;
3703 if ( shapeKind == QgsTextBackgroundSettings::ShapeSVG
3704 && ( svgPath.isEmpty()
3705 || ( !svgPath.isEmpty()
3706 && shpSizeType == QgsTextBackgroundSettings::SizeFixed
3707 && ddShpSizeX == 0.0 ) ) )
3708 {
3709 skip = true;
3710 }
3712 && ( !background.markerSymbol()
3713 || ( background.markerSymbol()
3714 && shpSizeType == QgsTextBackgroundSettings::SizeFixed
3715 && ddShpSizeX == 0.0 ) ) )
3716 {
3717 skip = true;
3718 }
3719 if ( shapeKind != QgsTextBackgroundSettings::ShapeSVG
3721 && shpSizeType == QgsTextBackgroundSettings::SizeFixed
3722 && ( ddShpSizeX == 0.0 || ddShpSizeY == 0.0 ) )
3723 {
3724 skip = true;
3725 }
3726
3727 if ( skip )
3728 {
3729 dataDefinedValues.insert( QgsPalLayerSettings::ShapeDraw, QVariant( false ) ); // trigger value
3730 dataDefinedValues.remove( QgsPalLayerSettings::ShapeOpacity );
3731 dataDefinedValues.remove( QgsPalLayerSettings::ShapeKind );
3732 dataDefinedValues.remove( QgsPalLayerSettings::ShapeSVGFile );
3733 dataDefinedValues.remove( QgsPalLayerSettings::ShapeSizeX );
3734 dataDefinedValues.remove( QgsPalLayerSettings::ShapeSizeY );
3735 return; // don't bother evaluating values that won't be used
3736 }
3737
3738 // data defined shape size units?
3739 dataDefinedValEval( DDUnits, QgsPalLayerSettings::ShapeSizeUnits, exprVal, context.expressionContext() );
3740
3741 // data defined shape rotation type?
3742 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::ShapeRotationType ) )
3743 {
3744 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::ShapeRotationType, context.expressionContext() );
3745 if ( !QgsVariantUtils::isNull( exprVal ) )
3746 {
3747 QString rotstr = exprVal.toString().trimmed();
3748 QgsDebugMsgLevel( QStringLiteral( "exprVal ShapeRotationType:%1" ).arg( rotstr ), 4 );
3749
3750 if ( !rotstr.isEmpty() )
3751 {
3752 // "Sync"
3754 dataDefinedValues.insert( QgsPalLayerSettings::ShapeRotationType, QVariant( static_cast< int >( rottype ) ) );
3755 }
3756 }
3757 }
3758
3759 // data defined shape rotation?
3760 dataDefinedValEval( DDRotation180, QgsPalLayerSettings::ShapeRotation, exprVal, context.expressionContext(), background.rotation() );
3761
3762 // data defined shape offset?
3763 dataDefinedValEval( DDPointF, QgsPalLayerSettings::ShapeOffset, exprVal, context.expressionContext(), QgsSymbolLayerUtils::encodePoint( background.offset() ) );
3764
3765 // data defined shape offset units?
3766 dataDefinedValEval( DDUnits, QgsPalLayerSettings::ShapeOffsetUnits, exprVal, context.expressionContext() );
3767
3768 // data defined shape radii?
3769 dataDefinedValEval( DDSizeF, QgsPalLayerSettings::ShapeRadii, exprVal, context.expressionContext(), QgsSymbolLayerUtils::encodeSize( background.radii() ) );
3770
3771 // data defined shape radii units?
3772 dataDefinedValEval( DDUnits, QgsPalLayerSettings::ShapeRadiiUnits, exprVal, context.expressionContext() );
3773
3774 // data defined shape blend mode?
3775 dataDefinedValEval( DDBlendMode, QgsPalLayerSettings::ShapeBlendMode, exprVal, context.expressionContext() );
3776
3777 // data defined shape fill color?
3778 dataDefinedValEval( DDColor, QgsPalLayerSettings::ShapeFillColor, exprVal, context.expressionContext(), QgsSymbolLayerUtils::encodeColor( background.fillColor() ) );
3779
3780 // data defined shape stroke color?
3781 dataDefinedValEval( DDColor, QgsPalLayerSettings::ShapeStrokeColor, exprVal, context.expressionContext(), QgsSymbolLayerUtils::encodeColor( background.strokeColor() ) );
3782
3783 // data defined shape stroke width?
3784 dataDefinedValEval( DDDoublePos, QgsPalLayerSettings::ShapeStrokeWidth, exprVal, context.expressionContext(), background.strokeWidth() );
3785
3786 // data defined shape stroke width units?
3787 dataDefinedValEval( DDUnits, QgsPalLayerSettings::ShapeStrokeWidthUnits, exprVal, context.expressionContext() );
3788
3789 // data defined shape join style?
3790 dataDefinedValEval( DDJoinStyle, QgsPalLayerSettings::ShapeJoinStyle, exprVal, context.expressionContext(), QgsSymbolLayerUtils::encodePenJoinStyle( background.joinStyle() ) );
3791
3792}
3793
3794void QgsPalLayerSettings::parseDropShadow( QgsRenderContext &context )
3795{
3796 QVariant exprVal; // value() is repeatedly nulled on data defined evaluation and replaced when successful
3797
3798 QgsTextShadowSettings shadow = mFormat.shadow();
3799
3800 // data defined draw shadow?
3801 bool drawShadow = shadow.enabled();
3802 if ( dataDefinedValEval( DDBool, QgsPalLayerSettings::ShadowDraw, exprVal, context.expressionContext(), drawShadow ) )
3803 {
3804 drawShadow = exprVal.toBool();
3805 }
3806
3807 if ( !drawShadow )
3808 {
3809 return;
3810 }
3811
3812 // data defined shadow transparency?
3813 double shadowOpacity = shadow.opacity() * 100;
3814 if ( dataDefinedValEval( DDOpacity, QgsPalLayerSettings::ShadowOpacity, exprVal, context.expressionContext(), shadowOpacity ) )
3815 {
3816 shadowOpacity = exprVal.toDouble();
3817 }
3818
3819 // data defined shadow offset distance?
3820 double shadowOffDist = shadow.offsetDistance();
3821 if ( dataDefinedValEval( DDDoublePos, QgsPalLayerSettings::ShadowOffsetDist, exprVal, context.expressionContext(), shadowOffDist ) )
3822 {
3823 shadowOffDist = exprVal.toDouble();
3824 }
3825
3826 // data defined shadow offset distance?
3827 double shadowRad = shadow.blurRadius();
3828 if ( dataDefinedValEval( DDDoublePos, QgsPalLayerSettings::ShadowRadius, exprVal, context.expressionContext(), shadowRad ) )
3829 {
3830 shadowRad = exprVal.toDouble();
3831 }
3832
3833 drawShadow = ( drawShadow && shadowOpacity > 0 && !( shadowOffDist == 0.0 && shadowRad == 0.0 ) );
3834
3835 if ( !drawShadow )
3836 {
3837 dataDefinedValues.insert( QgsPalLayerSettings::ShadowDraw, QVariant( false ) ); // trigger value
3838 dataDefinedValues.remove( QgsPalLayerSettings::ShadowOpacity );
3839 dataDefinedValues.remove( QgsPalLayerSettings::ShadowOffsetDist );
3840 dataDefinedValues.remove( QgsPalLayerSettings::ShadowRadius );
3841 return; // don't bother evaluating values that won't be used
3842 }
3843
3844 // data defined shadow under type?
3845 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::ShadowUnder ) )
3846 {
3847 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::ShadowUnder, context.expressionContext() );
3848 if ( !QgsVariantUtils::isNull( exprVal ) )
3849 {
3850 QString str = exprVal.toString().trimmed();
3851 QgsDebugMsgLevel( QStringLiteral( "exprVal ShadowUnder:%1" ).arg( str ), 4 );
3852
3853 if ( !str.isEmpty() )
3854 {
3856 dataDefinedValues.insert( QgsPalLayerSettings::ShadowUnder, QVariant( static_cast< int >( shdwtype ) ) );
3857 }
3858 }
3859 }
3860
3861 // data defined shadow offset angle?
3862 dataDefinedValEval( DDRotation180, QgsPalLayerSettings::ShadowOffsetAngle, exprVal, context.expressionContext(), shadow.offsetAngle() );
3863
3864 // data defined shadow offset units?
3865 dataDefinedValEval( DDUnits, QgsPalLayerSettings::ShadowOffsetUnits, exprVal, context.expressionContext() );
3866
3867 // data defined shadow radius?
3868 dataDefinedValEval( DDDouble, QgsPalLayerSettings::ShadowRadius, exprVal, context.expressionContext(), shadow.blurRadius() );
3869
3870 // data defined shadow radius units?
3871 dataDefinedValEval( DDUnits, QgsPalLayerSettings::ShadowRadiusUnits, exprVal, context.expressionContext() );
3872
3873 // data defined shadow scale? ( gui bounds to 0-2000, no upper bound here )
3874 dataDefinedValEval( DDIntPos, QgsPalLayerSettings::ShadowScale, exprVal, context.expressionContext(), shadow.scale() );
3875
3876 // data defined shadow color?
3877 dataDefinedValEval( DDColor, QgsPalLayerSettings::ShadowColor, exprVal, context.expressionContext(), QgsSymbolLayerUtils::encodeColor( shadow.color() ) );
3878
3879 // data defined shadow blend mode?
3880 dataDefinedValEval( DDBlendMode, QgsPalLayerSettings::ShadowBlendMode, exprVal, context.expressionContext() );
3881}
3882
3883// -------------
3884
3885
3887{
3888 switch ( layer->type() )
3889 {
3891 {
3892 const QgsVectorLayer *vl = qobject_cast< const QgsVectorLayer * >( layer );
3893 return vl->labelsEnabled() || vl->diagramsEnabled();
3894 }
3895
3897 {
3898 const QgsVectorTileLayer *vl = qobject_cast< const QgsVectorTileLayer * >( layer );
3899 if ( !vl->labeling() )
3900 return false;
3901
3902 if ( const QgsVectorTileBasicLabeling *labeling = dynamic_cast< const QgsVectorTileBasicLabeling *>( vl->labeling() ) )
3903 return !labeling->styles().empty();
3904
3905 return false;
3906 }
3907
3915 return false;
3916 }
3917 return false;
3918}
3919
3920
3921bool QgsPalLabeling::geometryRequiresPreparation( const QgsGeometry &geometry, QgsRenderContext &context, const QgsCoordinateTransform &ct, const QgsGeometry &clipGeometry, bool mergeLines )
3922{
3923 if ( geometry.isNull() )
3924 {
3925 return false;
3926 }
3927
3928 if ( geometry.type() == Qgis::GeometryType::Line && geometry.isMultipart() && mergeLines )
3929 {
3930 return true;
3931 }
3932
3933 //requires reprojection
3934 if ( ct.isValid() && !ct.isShortCircuited() )
3935 return true;
3936
3937 //requires rotation
3938 const QgsMapToPixel &m2p = context.mapToPixel();
3939 if ( !qgsDoubleNear( m2p.mapRotation(), 0 ) )
3940 return true;
3941
3942 //requires clip
3943 if ( !clipGeometry.isNull() && !clipGeometry.boundingBox().contains( geometry.boundingBox() ) )
3944 return true;
3945
3946 //requires fixing
3947 if ( geometry.type() == Qgis::GeometryType::Polygon && !geometry.isGeosValid() )
3948 return true;
3949
3950 return false;
3951}
3952
3953QStringList QgsPalLabeling::splitToLines( const QString &text, const QString &wrapCharacter, const int autoWrapLength, const bool useMaxLineLengthWhenAutoWrapping )
3954{
3955 QStringList multiLineSplit;
3956 if ( !wrapCharacter.isEmpty() && wrapCharacter != QLatin1String( "\n" ) )
3957 {
3958 //wrap on both the wrapchr and new line characters
3959 const QStringList lines = text.split( wrapCharacter );
3960 for ( const QString &line : lines )
3961 {
3962 multiLineSplit.append( line.split( '\n' ) );
3963 }
3964 }
3965 else
3966 {
3967 multiLineSplit = text.split( '\n' );
3968 }
3969
3970 // apply auto wrapping to each manually created line
3971 if ( autoWrapLength != 0 )
3972 {
3973 QStringList autoWrappedLines;
3974 autoWrappedLines.reserve( multiLineSplit.count() );
3975 for ( const QString &line : std::as_const( multiLineSplit ) )
3976 {
3977 autoWrappedLines.append( QgsStringUtils::wordWrap( line, autoWrapLength, useMaxLineLengthWhenAutoWrapping ).split( '\n' ) );
3978 }
3979 multiLineSplit = autoWrappedLines;
3980 }
3981 return multiLineSplit;
3982}
3983
3984QStringList QgsPalLabeling::splitToGraphemes( const QString &text )
3985{
3986 QStringList graphemes;
3987 QTextBoundaryFinder boundaryFinder( QTextBoundaryFinder::Grapheme, text );
3988 int currentBoundary = -1;
3989 int previousBoundary = 0;
3990 while ( ( currentBoundary = boundaryFinder.toNextBoundary() ) > 0 )
3991 {
3992 graphemes << text.mid( previousBoundary, currentBoundary - previousBoundary );
3993 previousBoundary = currentBoundary;
3994 }
3995 return graphemes;
3996}
3997
3998QgsGeometry QgsPalLabeling::prepareGeometry( const QgsGeometry &geometry, QgsRenderContext &context, const QgsCoordinateTransform &ct, const QgsGeometry &clipGeometry, bool mergeLines )
3999{
4000 if ( geometry.isNull() )
4001 {
4002 return QgsGeometry();
4003 }
4004
4005 //don't modify the feature's geometry so that geometry based expressions keep working
4006 QgsGeometry geom = geometry;
4007
4008 if ( geom.type() == Qgis::GeometryType::Line && geom.isMultipart() && mergeLines )
4009 {
4010 geom = geom.mergeLines();
4011 }
4012
4013 //reproject the geometry if necessary
4014 if ( ct.isValid() && !ct.isShortCircuited() )
4015 {
4016 try
4017 {
4018 geom.transform( ct );
4019 }
4020 catch ( QgsCsException &cse )
4021 {
4022 Q_UNUSED( cse )
4023 QgsDebugMsgLevel( QStringLiteral( "Ignoring feature due to transformation exception" ), 4 );
4024 return QgsGeometry();
4025 }
4026 // geometry transforms may result in nan points, remove these
4027 geom.filterVertices( []( const QgsPoint & point )->bool
4028 {
4029 return std::isfinite( point.x() ) && std::isfinite( point.y() );
4030 } );
4031 if ( QgsCurvePolygon *cp = qgsgeometry_cast< QgsCurvePolygon * >( geom.get() ) )
4032 {
4033 cp->removeInvalidRings();
4034 }
4035 else if ( QgsMultiSurface *ms = qgsgeometry_cast< QgsMultiSurface * >( geom.get() ) )
4036 {
4037 for ( int i = 0; i < ms->numGeometries(); ++i )
4038 {
4039 if ( QgsCurvePolygon *cp = qgsgeometry_cast< QgsCurvePolygon * >( ms->geometryN( i ) ) )
4040 cp->removeInvalidRings();
4041 }
4042 }
4043 }
4044
4045 // Rotate the geometry if needed, before clipping
4046 const QgsMapToPixel &m2p = context.mapToPixel();
4047 if ( !qgsDoubleNear( m2p.mapRotation(), 0 ) )
4048 {
4049 QgsPointXY center = context.mapExtent().center();
4050 if ( geom.rotate( m2p.mapRotation(), center ) != Qgis::GeometryOperationResult::Success )
4051 {
4052 QgsDebugError( QStringLiteral( "Error rotating geometry" ).arg( geom.asWkt() ) );
4053 return QgsGeometry();
4054 }
4055 }
4056
4057 const bool mustClip = ( !clipGeometry.isNull() &&
4058 ( ( qgsDoubleNear( m2p.mapRotation(), 0 ) && !clipGeometry.boundingBox().contains( geom.boundingBox() ) )
4059 || ( !qgsDoubleNear( m2p.mapRotation(), 0 ) && !clipGeometry.contains( geom ) ) ) );
4060
4061 bool mustClipExact = false;
4062 if ( mustClip )
4063 {
4064 // nice and fast, but can result in invalid geometries. At least it will potentially strip out a bunch of unwanted vertices upfront!
4065 QgsGeometry clipGeom = geom.clipped( clipGeometry.boundingBox() );
4066 if ( clipGeom.isEmpty() )
4067 return QgsGeometry();
4068
4069 geom = clipGeom;
4070
4071 // we've now clipped against the BOUNDING BOX of clipGeometry. But if clipGeometry is an axis parallel rectangle, then there's no
4072 // need to do an exact (potentially costly) intersection clip as well!
4073 mustClipExact = !clipGeometry.isAxisParallelRectangle( 0.001 );
4074 }
4075
4076 // fix invalid polygons
4077 if ( geom.type() == Qgis::GeometryType::Polygon )
4078 {
4079 if ( geom.isMultipart() )
4080 {
4081 // important -- we need to treat ever part in isolation here. We can't test the validity of the whole geometry
4082 // at once, because touching parts would result in an invalid geometry, and buffering this "dissolves" the parts.
4083 // because the actual label engine treats parts as separate entities, we aren't bound by the usual "touching parts are invalid" rule
4084 // see https://github.com/qgis/QGIS/issues/26763
4085 QVector< QgsGeometry> parts;
4086 parts.reserve( qgsgeometry_cast< const QgsGeometryCollection * >( geom.constGet() )->numGeometries() );
4087 for ( auto it = geom.const_parts_begin(); it != geom.const_parts_end(); ++it )
4088 {
4089 QgsGeometry partGeom( ( *it )->clone() );
4090 if ( !partGeom.isGeosValid() )
4091 {
4092
4093 partGeom = partGeom.makeValid();
4094 }
4095 parts.append( partGeom );
4096 }
4097 geom = QgsGeometry::collectGeometry( parts );
4098 }
4099 else if ( !geom.isGeosValid() )
4100 {
4101
4102 QgsGeometry bufferGeom = geom.makeValid();
4103 if ( bufferGeom.isNull() )
4104 {
4105 QgsDebugError( QStringLiteral( "Could not repair geometry: %1" ).arg( bufferGeom.lastError() ) );
4106 return QgsGeometry();
4107 }
4108 geom = bufferGeom;
4109 }
4110 }
4111
4112 if ( mustClipExact )
4113 {
4114 // now do the real intersection against the actual clip geometry
4115 QgsGeometry clipGeom = geom.intersection( clipGeometry );
4116 if ( clipGeom.isEmpty() )
4117 {
4118 return QgsGeometry();
4119 }
4120 geom = clipGeom;
4121 }
4122
4123 return geom;
4124}
4125
4126bool QgsPalLabeling::checkMinimumSizeMM( const QgsRenderContext &context, const QgsGeometry &geom, double minSize )
4127{
4128 if ( minSize <= 0 )
4129 {
4130 return true;
4131 }
4132
4133 if ( geom.isNull() )
4134 {
4135 return false;
4136 }
4137
4138 Qgis::GeometryType featureType = geom.type();
4139 if ( featureType == Qgis::GeometryType::Point ) //minimum size does not apply to point features
4140 {
4141 return true;
4142 }
4143
4144 double mapUnitsPerMM = context.mapToPixel().mapUnitsPerPixel() * context.scaleFactor();
4145 if ( featureType == Qgis::GeometryType::Line )
4146 {
4147 double length = geom.length();
4148 if ( length >= 0.0 )
4149 {
4150 return ( length >= ( minSize * mapUnitsPerMM ) );
4151 }
4152 }
4153 else if ( featureType == Qgis::GeometryType::Polygon )
4154 {
4155 double area = geom.area();
4156 if ( area >= 0.0 )
4157 {
4158 return ( std::sqrt( area ) >= ( minSize * mapUnitsPerMM ) );
4159 }
4160 }
4161 return true; //should never be reached. Return true in this case to label such geometries anyway.
4162}
4163
4164
4165void QgsPalLabeling::dataDefinedTextStyle( QgsPalLayerSettings &tmpLyr,
4166 const QMap< QgsPalLayerSettings::Property, QVariant > &ddValues )
4167{
4168 QgsTextFormat format = tmpLyr.format();
4169 bool changed = false;
4170
4171 //font color
4172 if ( ddValues.contains( QgsPalLayerSettings::Color ) )
4173 {
4174 QVariant ddColor = ddValues.value( QgsPalLayerSettings::Color );
4175 format.setColor( ddColor.value<QColor>() );
4176 changed = true;
4177 }
4178
4179 //font transparency
4180 if ( ddValues.contains( QgsPalLayerSettings::FontOpacity ) )
4181 {
4182 format.setOpacity( ddValues.value( QgsPalLayerSettings::FontOpacity ).toDouble() / 100.0 );
4183 changed = true;
4184 }
4185
4186 //font blend mode
4187 if ( ddValues.contains( QgsPalLayerSettings::FontBlendMode ) )
4188 {
4189 format.setBlendMode( static_cast< QPainter::CompositionMode >( ddValues.value( QgsPalLayerSettings::FontBlendMode ).toInt() ) );
4190 changed = true;
4191 }
4192
4193 if ( changed )
4194 {
4195 tmpLyr.setFormat( format );
4196 }
4197}
4198
4199void QgsPalLabeling::dataDefinedTextFormatting( QgsPalLayerSettings &tmpLyr,
4200 const QMap< QgsPalLayerSettings::Property, QVariant > &ddValues )
4201{
4202 if ( ddValues.contains( QgsPalLayerSettings::MultiLineWrapChar ) )
4203 {
4204 tmpLyr.wrapChar = ddValues.value( QgsPalLayerSettings::MultiLineWrapChar ).toString();
4205 }
4206
4207 if ( ddValues.contains( QgsPalLayerSettings::AutoWrapLength ) )
4208 {
4209 tmpLyr.autoWrapLength = ddValues.value( QgsPalLayerSettings::AutoWrapLength ).toInt();
4210 }
4211
4212 if ( ddValues.contains( QgsPalLayerSettings::MultiLineHeight ) )
4213 {
4214 QgsTextFormat format = tmpLyr.format();
4215 format.setLineHeight( ddValues.value( QgsPalLayerSettings::MultiLineHeight ).toDouble() );
4216 tmpLyr.setFormat( format );
4217 }
4218
4219 if ( ddValues.contains( QgsPalLayerSettings::MultiLineAlignment ) )
4220 {
4221 tmpLyr.multilineAlign = static_cast< Qgis::LabelMultiLineAlignment >( ddValues.value( QgsPalLayerSettings::MultiLineAlignment ).toInt() );
4222 }
4223
4224 if ( ddValues.contains( QgsPalLayerSettings::TextOrientation ) )
4225 {
4226 QgsTextFormat format = tmpLyr.format();
4228 tmpLyr.setFormat( format );
4229 }
4230
4231 if ( ddValues.contains( QgsPalLayerSettings::DirSymbDraw ) )
4232 {
4233 tmpLyr.lineSettings().setAddDirectionSymbol( ddValues.value( QgsPalLayerSettings::DirSymbDraw ).toBool() );
4234 }
4235
4236 if ( tmpLyr.lineSettings().addDirectionSymbol() )
4237 {
4238
4239 if ( ddValues.contains( QgsPalLayerSettings::DirSymbLeft ) )
4240 {
4241 tmpLyr.lineSettings().setLeftDirectionSymbol( ddValues.value( QgsPalLayerSettings::DirSymbLeft ).toString() );
4242 }
4243 if ( ddValues.contains( QgsPalLayerSettings::DirSymbRight ) )
4244 {
4245 tmpLyr.lineSettings().setRightDirectionSymbol( ddValues.value( QgsPalLayerSettings::DirSymbRight ).toString() );
4246 }
4247
4248 if ( ddValues.contains( QgsPalLayerSettings::DirSymbPlacement ) )
4249 {
4251 }
4252
4253 if ( ddValues.contains( QgsPalLayerSettings::DirSymbReverse ) )
4254 {
4255 tmpLyr.lineSettings().setReverseDirectionSymbol( ddValues.value( QgsPalLayerSettings::DirSymbReverse ).toBool() );
4256 }
4257
4258 }
4259}
4260
4261void QgsPalLabeling::dataDefinedTextBuffer( QgsPalLayerSettings &tmpLyr,
4262 const QMap< QgsPalLayerSettings::Property, QVariant > &ddValues )
4263{
4264 QgsTextBufferSettings buffer = tmpLyr.format().buffer();
4265 bool changed = false;
4266
4267 //buffer draw
4268 if ( ddValues.contains( QgsPalLayerSettings::BufferDraw ) )
4269 {
4270 buffer.setEnabled( ddValues.value( QgsPalLayerSettings::BufferDraw ).toBool() );
4271 changed = true;
4272 }
4273
4274 if ( !buffer.enabled() )
4275 {
4276 if ( changed )
4277 {
4278 QgsTextFormat format = tmpLyr.format();
4279 format.setBuffer( buffer );
4280 tmpLyr.setFormat( format );
4281 }
4282
4283 // tmpLyr.bufferSize > 0.0 && tmpLyr.bufferTransp < 100 figured in during evaluation
4284 return; // don't continue looking for unused values
4285 }
4286
4287 //buffer size
4288 if ( ddValues.contains( QgsPalLayerSettings::BufferSize ) )
4289 {
4290 buffer.setSize( ddValues.value( QgsPalLayerSettings::BufferSize ).toDouble() );
4291 changed = true;
4292 }
4293
4294 //buffer opacity
4295 if ( ddValues.contains( QgsPalLayerSettings::BufferOpacity ) )
4296 {
4297 buffer.setOpacity( ddValues.value( QgsPalLayerSettings::BufferOpacity ).toDouble() / 100.0 );
4298 changed = true;
4299 }
4300
4301 //buffer size units
4302 if ( ddValues.contains( QgsPalLayerSettings::BufferUnit ) )
4303 {
4304 Qgis::RenderUnit bufunit = static_cast< Qgis::RenderUnit >( ddValues.value( QgsPalLayerSettings::BufferUnit ).toInt() );
4305 buffer.setSizeUnit( bufunit );
4306 changed = true;
4307 }
4308
4309 //buffer color
4310 if ( ddValues.contains( QgsPalLayerSettings::BufferColor ) )
4311 {
4312 QVariant ddColor = ddValues.value( QgsPalLayerSettings::BufferColor );
4313 buffer.setColor( ddColor.value<QColor>() );
4314 changed = true;
4315 }
4316
4317 //buffer pen join style
4318 if ( ddValues.contains( QgsPalLayerSettings::BufferJoinStyle ) )
4319 {
4320 buffer.setJoinStyle( static_cast< Qt::PenJoinStyle >( ddValues.value( QgsPalLayerSettings::BufferJoinStyle ).toInt() ) );
4321 changed = true;
4322 }
4323
4324 //buffer blend mode
4325 if ( ddValues.contains( QgsPalLayerSettings::BufferBlendMode ) )
4326 {
4327 buffer.setBlendMode( static_cast< QPainter::CompositionMode >( ddValues.value( QgsPalLayerSettings::BufferBlendMode ).toInt() ) );
4328 changed = true;
4329 }
4330
4331 if ( changed )
4332 {
4333 QgsTextFormat format = tmpLyr.format();
4334 format.setBuffer( buffer );
4335 tmpLyr.setFormat( format );
4336 }
4337}
4338
4339void QgsPalLabeling::dataDefinedTextMask( QgsPalLayerSettings &tmpLyr,
4340 const QMap< QgsPalLayerSettings::Property, QVariant > &ddValues )
4341{
4342 if ( ddValues.isEmpty() )
4343 return;
4344
4345 QgsTextMaskSettings mask = tmpLyr.format().mask();
4346 bool changed = false;
4347
4348 // enabled ?
4349 if ( ddValues.contains( QgsPalLayerSettings::MaskEnabled ) )
4350 {
4351 mask.setEnabled( ddValues.value( QgsPalLayerSettings::MaskEnabled ).toBool() );
4352 changed = true;
4353 }
4354
4355 if ( !mask.enabled() )
4356 {
4357 if ( changed )
4358 {
4359 QgsTextFormat format = tmpLyr.format();
4360 format.setMask( mask );
4361 tmpLyr.setFormat( format );
4362 }
4363
4364 // tmpLyr.bufferSize > 0.0 && tmpLyr.bufferTransp < 100 figured in during evaluation
4365 return; // don't continue looking for unused values
4366 }
4367
4368 // buffer size
4369 if ( ddValues.contains( QgsPalLayerSettings::MaskBufferSize ) )
4370 {
4371 mask.setSize( ddValues.value( QgsPalLayerSettings::MaskBufferSize ).toDouble() );
4372 changed = true;
4373 }
4374
4375 // opacity
4376 if ( ddValues.contains( QgsPalLayerSettings::MaskOpacity ) )
4377 {
4378 mask.setOpacity( ddValues.value( QgsPalLayerSettings::MaskOpacity ).toDouble() / 100.0 );
4379 changed = true;
4380 }
4381
4382 // buffer size units
4383 if ( ddValues.contains( QgsPalLayerSettings::MaskBufferUnit ) )
4384 {
4385 Qgis::RenderUnit bufunit = static_cast< Qgis::RenderUnit >( ddValues.value( QgsPalLayerSettings::MaskBufferUnit ).toInt() );
4386 mask.setSizeUnit( bufunit );
4387 changed = true;
4388 }
4389
4390 // pen join style
4391 if ( ddValues.contains( QgsPalLayerSettings::MaskJoinStyle ) )
4392 {
4393 mask.setJoinStyle( static_cast< Qt::PenJoinStyle >( ddValues.value( QgsPalLayerSettings::MaskJoinStyle ).toInt() ) );
4394 changed = true;
4395 }
4396
4397 if ( changed )
4398 {
4399 QgsTextFormat format = tmpLyr.format();
4400 format.setMask( mask );
4401 tmpLyr.setFormat( format );
4402 }
4403}
4404
4405void QgsPalLabeling::dataDefinedShapeBackground( QgsPalLayerSettings &tmpLyr,
4406 const QMap< QgsPalLayerSettings::Property, QVariant > &ddValues )
4407{
4408 QgsTextBackgroundSettings background = tmpLyr.format().background();
4409 bool changed = false;
4410
4411 //shape draw
4412 if ( ddValues.contains( QgsPalLayerSettings::ShapeDraw ) )
4413 {
4414 background.setEnabled( ddValues.value( QgsPalLayerSettings::ShapeDraw ).toBool() );
4415 changed = true;
4416 }
4417
4418 if ( !background.enabled() )
4419 {
4420 if ( changed )
4421 {
4422 QgsTextFormat format = tmpLyr.format();
4423 format.setBackground( background );
4424 tmpLyr.setFormat( format );
4425 }
4426 return; // don't continue looking for unused values
4427 }
4428
4429 if ( ddValues.contains( QgsPalLayerSettings::ShapeKind ) )
4430 {
4431 background.setType( static_cast< QgsTextBackgroundSettings::ShapeType >( ddValues.value( QgsPalLayerSettings::ShapeKind ).toInt() ) );
4432 changed = true;
4433 }
4434
4435 if ( ddValues.contains( QgsPalLayerSettings::ShapeSVGFile ) )
4436 {
4437 background.setSvgFile( ddValues.value( QgsPalLayerSettings::ShapeSVGFile ).toString() );
4438 changed = true;
4439 }
4440
4441 if ( ddValues.contains( QgsPalLayerSettings::ShapeSizeType ) )
4442 {
4443 background.setSizeType( static_cast< QgsTextBackgroundSettings::SizeType >( ddValues.value( QgsPalLayerSettings::ShapeSizeType ).toInt() ) );
4444 changed = true;
4445 }
4446
4447 if ( ddValues.contains( QgsPalLayerSettings::ShapeSizeX ) )
4448 {
4449 QSizeF size = background.size();
4450 size.setWidth( ddValues.value( QgsPalLayerSettings::ShapeSizeX ).toDouble() );
4451 background.setSize( size );
4452 changed = true;
4453 }
4454 if ( ddValues.contains( QgsPalLayerSettings::ShapeSizeY ) )
4455 {
4456 QSizeF size = background.size();
4457 size.setHeight( ddValues.value( QgsPalLayerSettings::ShapeSizeY ).toDouble() );
4458 background.setSize( size );
4459 changed = true;
4460 }
4461
4462 if ( ddValues.contains( QgsPalLayerSettings::ShapeSizeUnits ) )
4463 {
4464 background.setSizeUnit( static_cast< Qgis::RenderUnit >( ddValues.value( QgsPalLayerSettings::ShapeSizeUnits ).toInt() ) );
4465 changed = true;
4466 }
4467
4468 if ( ddValues.contains( QgsPalLayerSettings::ShapeRotationType ) )
4469 {
4470 background.setRotationType( static_cast< QgsTextBackgroundSettings::RotationType >( ddValues.value( QgsPalLayerSettings::ShapeRotationType ).toInt() ) );
4471 changed = true;
4472 }
4473
4474 if ( ddValues.contains( QgsPalLayerSettings::ShapeRotation ) )
4475 {
4476 background.setRotation( ddValues.value( QgsPalLayerSettings::ShapeRotation ).toDouble() );
4477 changed = true;
4478 }
4479
4480 if ( ddValues.contains( QgsPalLayerSettings::ShapeOffset ) )
4481 {
4482 background.setOffset( ddValues.value( QgsPalLayerSettings::ShapeOffset ).toPointF() );
4483 changed = true;
4484 }
4485
4486 if ( ddValues.contains( QgsPalLayerSettings::ShapeOffsetUnits ) )
4487 {
4488 background.setOffsetUnit( static_cast< Qgis::RenderUnit >( ddValues.value( QgsPalLayerSettings::ShapeOffsetUnits ).toInt() ) );
4489 changed = true;
4490 }
4491
4492 if ( ddValues.contains( QgsPalLayerSettings::ShapeRadii ) )
4493 {
4494 background.setRadii( ddValues.value( QgsPalLayerSettings::ShapeRadii ).toSizeF() );
4495 changed = true;
4496 }
4497
4498 if ( ddValues.contains( QgsPalLayerSettings::ShapeRadiiUnits ) )
4499 {
4500 background.setRadiiUnit( static_cast< Qgis::RenderUnit >( ddValues.value( QgsPalLayerSettings::ShapeRadiiUnits ).toInt() ) );
4501 changed = true;
4502 }
4503
4504 if ( ddValues.contains( QgsPalLayerSettings::ShapeBlendMode ) )
4505 {
4506 background.setBlendMode( static_cast< QPainter::CompositionMode >( ddValues.value( QgsPalLayerSettings::ShapeBlendMode ).toInt() ) );
4507 changed = true;
4508 }
4509
4510 if ( ddValues.contains( QgsPalLayerSettings::ShapeFillColor ) )
4511 {
4512 QVariant ddColor = ddValues.value( QgsPalLayerSettings::ShapeFillColor );
4513 background.setFillColor( ddColor.value<QColor>() );
4514 changed = true;
4515 }
4516
4517 if ( ddValues.contains( QgsPalLayerSettings::ShapeStrokeColor ) )
4518 {
4519 QVariant ddColor = ddValues.value( QgsPalLayerSettings::ShapeStrokeColor );
4520 background.setStrokeColor( ddColor.value<QColor>() );
4521 changed = true;
4522 }
4523
4524 if ( ddValues.contains( QgsPalLayerSettings::ShapeOpacity ) )
4525 {
4526 background.setOpacity( ddValues.value( QgsPalLayerSettings::ShapeOpacity ).toDouble() / 100.0 );
4527 changed = true;
4528 }
4529
4530 if ( ddValues.contains( QgsPalLayerSettings::ShapeStrokeWidth ) )
4531 {
4532 background.setStrokeWidth( ddValues.value( QgsPalLayerSettings::ShapeStrokeWidth ).toDouble() );
4533 changed = true;
4534 }
4535
4536 if ( ddValues.contains( QgsPalLayerSettings::ShapeStrokeWidthUnits ) )
4537 {
4538 background.setStrokeWidthUnit( static_cast< Qgis::RenderUnit >( ddValues.value( QgsPalLayerSettings::ShapeStrokeWidthUnits ).toInt() ) );
4539 changed = true;
4540 }
4541
4542 if ( ddValues.contains( QgsPalLayerSettings::ShapeJoinStyle ) )
4543 {
4544 background.setJoinStyle( static_cast< Qt::PenJoinStyle >( ddValues.value( QgsPalLayerSettings::ShapeJoinStyle ).toInt() ) );
4545 changed = true;
4546 }
4547
4548 if ( changed )
4549 {
4550 QgsTextFormat format = tmpLyr.format();
4551 format.setBackground( background );
4552 tmpLyr.setFormat( format );
4553 }
4554}
4555
4556void QgsPalLabeling::dataDefinedDropShadow( QgsPalLayerSettings &tmpLyr,
4557 const QMap< QgsPalLayerSettings::Property, QVariant > &ddValues )
4558{
4559 QgsTextShadowSettings shadow = tmpLyr.format().shadow();
4560 bool changed = false;
4561
4562 //shadow draw
4563 if ( ddValues.contains( QgsPalLayerSettings::ShadowDraw ) )
4564 {
4565 shadow.setEnabled( ddValues.value( QgsPalLayerSettings::ShadowDraw ).toBool() );
4566 changed = true;
4567 }
4568
4569 if ( !shadow.enabled() )
4570 {
4571 if ( changed )
4572 {
4573 QgsTextFormat format = tmpLyr.format();
4574 format.setShadow( shadow );
4575 tmpLyr.setFormat( format );
4576 }
4577 return; // don't continue looking for unused values
4578 }
4579
4580 if ( ddValues.contains( QgsPalLayerSettings::ShadowUnder ) )
4581 {
4582 shadow.setShadowPlacement( static_cast< QgsTextShadowSettings::ShadowPlacement >( ddValues.value( QgsPalLayerSettings::ShadowUnder ).toInt() ) );
4583 changed = true;
4584 }
4585
4586 if ( ddValues.contains( QgsPalLayerSettings::ShadowOffsetAngle ) )
4587 {
4588 shadow.setOffsetAngle( ddValues.value( QgsPalLayerSettings::ShadowOffsetAngle ).toInt() );
4589 changed = true;
4590 }
4591
4592 if ( ddValues.contains( QgsPalLayerSettings::ShadowOffsetDist ) )
4593 {
4594 shadow.setOffsetDistance( ddValues.value( QgsPalLayerSettings::ShadowOffsetDist ).toDouble() );
4595 changed = true;
4596 }
4597
4598 if ( ddValues.contains( QgsPalLayerSettings::ShadowOffsetUnits ) )
4599 {
4600 shadow.setOffsetUnit( static_cast< Qgis::RenderUnit >( ddValues.value( QgsPalLayerSettings::ShadowOffsetUnits ).toInt() ) );
4601 changed = true;
4602 }
4603
4604 if ( ddValues.contains( QgsPalLayerSettings::ShadowRadius ) )
4605 {
4606 shadow.setBlurRadius( ddValues.value( QgsPalLayerSettings::ShadowRadius ).toDouble() );
4607 changed = true;
4608 }
4609
4610 if ( ddValues.contains( QgsPalLayerSettings::ShadowRadiusUnits ) )
4611 {
4612 shadow.setBlurRadiusUnit( static_cast< Qgis::RenderUnit >( ddValues.value( QgsPalLayerSettings::ShadowRadiusUnits ).toInt() ) );
4613 changed = true;
4614 }
4615
4616 if ( ddValues.contains( QgsPalLayerSettings::ShadowColor ) )
4617 {
4618 QVariant ddColor = ddValues.value( QgsPalLayerSettings::ShadowColor );
4619 shadow.setColor( ddColor.value<QColor>() );
4620 changed = true;
4621 }
4622
4623 if ( ddValues.contains( QgsPalLayerSettings::ShadowOpacity ) )
4624 {
4625 shadow.setOpacity( ddValues.value( QgsPalLayerSettings::ShadowOpacity ).toDouble() / 100.0 );
4626 changed = true;
4627 }
4628
4629 if ( ddValues.contains( QgsPalLayerSettings::ShadowScale ) )
4630 {
4631 shadow.setScale( ddValues.value( QgsPalLayerSettings::ShadowScale ).toInt() );
4632 changed = true;
4633 }
4634
4635
4636 if ( ddValues.contains( QgsPalLayerSettings::ShadowBlendMode ) )
4637 {
4638 shadow.setBlendMode( static_cast< QPainter::CompositionMode >( ddValues.value( QgsPalLayerSettings::ShadowBlendMode ).toInt() ) );
4639 changed = true;
4640 }
4641
4642 if ( changed )
4643 {
4644 QgsTextFormat format = tmpLyr.format();
4645 format.setShadow( shadow );
4646 tmpLyr.setFormat( format );
4647 }
4648}
4649
4650void QgsPalLabeling::drawLabelCandidateRect( pal::LabelPosition *lp, QPainter *painter, const QgsMapToPixel *xform, QList<QgsLabelCandidate> *candidates )
4651{
4652 QgsPointXY outPt = xform->transform( lp->getX(), lp->getY() );
4653
4654 painter->save();
4655
4656#if 0 // TODO: generalize some of this
4657 double w = lp->getWidth();
4658 double h = lp->getHeight();
4659 double cx = lp->getX() + w / 2.0;
4660 double cy = lp->getY() + h / 2.0;
4661 double scale = 1.0 / xform->mapUnitsPerPixel();
4662 double rotation = xform->