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