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