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