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