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