QGIS API Documentation 3.41.0-Master (092dc69654e)
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 "pal/labelposition.h"
29
30#include <cmath>
31
32#include <QApplication>
33#include <QByteArray>
34#include <QString>
35#include <QFontMetrics>
36#include <QTime>
37#include <QPainter>
38#include <QScreen>
39#include <QWidget>
40#include <QTextBoundaryFinder>
41
42#include "qgsfontutils.h"
43#include "qgsexpression.h"
44#include "qgslabelingengine.h"
46#include "qgsmultisurface.h"
47#include "qgslogger.h"
48#include "qgsvectorlayer.h"
49#include "qgsgeometry.h"
51#include "qgsproperty.h"
52#include "qgssymbollayerutils.h"
54#include "qgscurvepolygon.h"
55#include "qgsmessagelog.h"
57#include "callouts/qgscallout.h"
59#include "qgsvectortilelayer.h"
61#include "qgsfontmanager.h"
62#include "qgsvariantutils.h"
63
64using namespace pal;
65
66// -------------
67
68/* ND: Default point label position priority. These are set to match variants of the ideal placement priority described
69 in "Making Maps", Krygier & Wood (2011) (p216),
70 "Elements of Cartography", Robinson et al (1995)
71 and "Designing Better Maps", Brewer (2005) (p76)
72 Note that while they agree on positions 1-4, 5-8 are more contentious so I've selected these placements
73 based on my preferences, and to follow Krygier and Wood's placements more closer. (I'm not going to disagree
74 with Denis Wood on anything cartography related...!)
75*/
76typedef QVector< Qgis::LabelPredefinedPointPosition > PredefinedPointPositionVector;
78{
87} ) )
88//debugging only - don't use these placements by default
89/* << 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", QObject::tr( "Tab stop distance" ), QgsPropertyDefinition::DoublePositive, 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 evaluatedFormat.setFont( labelFont );
2108 // undo scaling by symbology reference scale, as this would have been applied in the previous call to QgsTextRenderer::sizeToPixel and we risk
2109 // double-applying it if we don't re-adjust, since all the text format metric calculations assume an unscaled format font size is present
2110 const double symbologyReferenceScaleFactor = context.symbologyReferenceScale() > 0 ? context.symbologyReferenceScale() / context.rendererScale() : 1;
2111 evaluatedFormat.setSize( labelFont.pixelSize() / symbologyReferenceScaleFactor );
2112 evaluatedFormat.setSizeUnit( Qgis::RenderUnit::Pixels );
2113
2114 QString labelText;
2115
2116 // Check to see if we are a expression string.
2117 if ( isExpression )
2118 {
2120 if ( exp->hasParserError() )
2121 {
2122 QgsDebugMsgLevel( QStringLiteral( "Expression parser error:%1" ).arg( exp->parserErrorString() ), 4 );
2123 return nullptr;
2124 }
2125
2126 QVariant result = exp->evaluate( &context.expressionContext() ); // expression prepared in QgsPalLabeling::prepareLayer()
2127 if ( exp->hasEvalError() )
2128 {
2129 QgsDebugMsgLevel( QStringLiteral( "Expression parser eval error:%1" ).arg( exp->evalErrorString() ), 4 );
2130 return nullptr;
2131 }
2132 labelText = QgsVariantUtils::isNull( result ) ? QString() : result.toString();
2133 }
2134 else
2135 {
2136 const QVariant &v = feature.attribute( fieldIndex );
2137 labelText = QgsVariantUtils::isNull( v ) ? QString() : v.toString();
2138 }
2139
2140 // apply text replacements
2141 if ( useSubstitutions )
2142 {
2143 labelText = substitutions.process( labelText );
2144 }
2145
2146 // apply capitalization
2147 Qgis::Capitalization capitalization = evaluatedFormat.capitalization();
2148 // maintain API - capitalization may have been set in textFont
2149 if ( capitalization == Qgis::Capitalization::MixedCase && mFormat.font().capitalization() != QFont::MixedCase )
2150 {
2151 capitalization = static_cast< Qgis::Capitalization >( mFormat.font().capitalization() );
2152 }
2153 // data defined font capitalization?
2154 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::FontCase ) )
2155 {
2156 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Property::FontCase, context.expressionContext() );
2157 if ( !QgsVariantUtils::isNull( exprVal ) )
2158 {
2159 QString fcase = exprVal.toString().trimmed();
2160 QgsDebugMsgLevel( QStringLiteral( "exprVal FontCase:%1" ).arg( fcase ), 4 );
2161
2162 if ( !fcase.isEmpty() )
2163 {
2164 if ( fcase.compare( QLatin1String( "NoChange" ), Qt::CaseInsensitive ) == 0 )
2165 {
2166 capitalization = Qgis::Capitalization::MixedCase;
2167 }
2168 else if ( fcase.compare( QLatin1String( "Upper" ), Qt::CaseInsensitive ) == 0 )
2169 {
2170 capitalization = Qgis::Capitalization::AllUppercase;
2171 }
2172 else if ( fcase.compare( QLatin1String( "Lower" ), Qt::CaseInsensitive ) == 0 )
2173 {
2174 capitalization = Qgis::Capitalization::AllLowercase;
2175 }
2176 else if ( fcase.compare( QLatin1String( "Capitalize" ), Qt::CaseInsensitive ) == 0 )
2177 {
2179 }
2180 else if ( fcase.compare( QLatin1String( "Title" ), Qt::CaseInsensitive ) == 0 )
2181 {
2182 capitalization = Qgis::Capitalization::TitleCase;
2183 }
2184#if defined(HAS_KDE_QT5_SMALL_CAPS_FIX) || QT_VERSION >= QT_VERSION_CHECK(6, 3, 0)
2185 else if ( fcase.compare( QLatin1String( "SmallCaps" ), Qt::CaseInsensitive ) == 0 )
2186 {
2187 capitalization = Qgis::Capitalization::SmallCaps;
2188 }
2189 else if ( fcase.compare( QLatin1String( "AllSmallCaps" ), Qt::CaseInsensitive ) == 0 )
2190 {
2191 capitalization = Qgis::Capitalization::AllSmallCaps;
2192 }
2193#endif
2194 }
2195 }
2196 }
2197 labelText = QgsStringUtils::capitalize( labelText, capitalization );
2198
2199 // format number if label text is coercible to a number
2200 bool evalFormatNumbers = formatNumbers;
2201 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::NumFormat ) )
2202 {
2203 evalFormatNumbers = mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::Property::NumFormat, context.expressionContext(), evalFormatNumbers );
2204 }
2205 if ( evalFormatNumbers )
2206 {
2207 // data defined decimal places?
2208 int decimalPlaces = mDataDefinedProperties.valueAsInt( QgsPalLayerSettings::Property::NumDecimals, context.expressionContext(), decimals );
2209 if ( decimalPlaces <= 0 ) // needs to be positive
2210 decimalPlaces = decimals;
2211
2212 // data defined plus sign?
2213 bool signPlus = mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::Property::NumPlusSign, context.expressionContext(), plusSign );
2214
2215 QVariant textV( labelText );
2216 bool ok;
2217 double d = textV.toDouble( &ok );
2218 if ( ok )
2219 {
2220 QString numberFormat;
2221 if ( d > 0 && signPlus )
2222 {
2223 numberFormat.append( '+' );
2224 }
2225 numberFormat.append( "%1" );
2226 labelText = numberFormat.arg( QLocale().toString( d, 'f', decimalPlaces ) );
2227 }
2228 }
2229
2230 // NOTE: this should come AFTER any option that affects font metrics
2231 const QFontMetricsF labelFontMetrics( labelFont );
2232 QSizeF labelSize;
2233 QSizeF rotatedSize;
2234
2235 QgsTextDocument doc;
2236 QgsTextDocumentMetrics documentMetrics;
2237 QRectF outerBounds;
2238
2239 switch ( placement )
2240 {
2243 {
2244 // avoid calculating document and metrics if we don't require them for curved labels
2245 if ( evaluatedFormat.allowHtmlFormatting() && !labelText.isEmpty() )
2246 {
2247 doc = QgsTextDocument::fromHtml( QStringList() << labelText );
2248 calculateLabelSize( labelFontMetrics, labelText, context, evaluatedFormat, &doc, &documentMetrics, labelSize, rotatedSize, outerBounds );
2249 }
2250 else
2251 {
2252 calculateLabelSize( labelFontMetrics, labelText, context, evaluatedFormat, nullptr, nullptr, labelSize, rotatedSize, outerBounds );
2253 }
2254 break;
2255 }
2256
2264 {
2265 // non-curved labels always require document and metrics
2266 doc = QgsTextDocument::fromTextAndFormat( {labelText }, evaluatedFormat );
2267 calculateLabelSize( labelFontMetrics, labelText, context, evaluatedFormat, &doc, &documentMetrics, labelSize, rotatedSize, outerBounds );
2268 break;
2269 }
2270 }
2271
2272 // maximum angle between curved label characters (hardcoded defaults used in QGIS <2.0)
2273 //
2274 double maxcharanglein = 20.0; // range 20.0-60.0
2275 double maxcharangleout = -20.0; // range 20.0-95.0
2276
2277 switch ( placement )
2278 {
2281 {
2282 maxcharanglein = maxCurvedCharAngleIn;
2283 maxcharangleout = maxCurvedCharAngleOut;
2284
2285 //data defined maximum angle between curved label characters?
2286 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::CurvedCharAngleInOut ) )
2287 {
2288 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Property::CurvedCharAngleInOut, context.expressionContext() );
2289 bool ok = false;
2290 const QPointF maxcharanglePt = QgsSymbolLayerUtils::toPoint( exprVal, &ok );
2291 if ( ok )
2292 {
2293 maxcharanglein = std::clamp( static_cast< double >( maxcharanglePt.x() ), 20.0, 60.0 );
2294 maxcharangleout = std::clamp( static_cast< double >( maxcharanglePt.y() ), 20.0, 95.0 );
2295 }
2296 }
2297 // make sure maxcharangleout is always negative
2298 maxcharangleout = -( std::fabs( maxcharangleout ) );
2299 break;
2300 }
2301
2309 break;
2310 }
2311
2312 // data defined centroid whole or clipped?
2313 bool wholeCentroid = centroidWhole;
2314 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::CentroidWhole ) )
2315 {
2316 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Property::CentroidWhole, context.expressionContext() );
2317 if ( !QgsVariantUtils::isNull( exprVal ) )
2318 {
2319 QString str = exprVal.toString().trimmed();
2320 QgsDebugMsgLevel( QStringLiteral( "exprVal CentroidWhole:%1" ).arg( str ), 4 );
2321
2322 if ( !str.isEmpty() )
2323 {
2324 if ( str.compare( QLatin1String( "Visible" ), Qt::CaseInsensitive ) == 0 )
2325 {
2326 wholeCentroid = false;
2327 }
2328 else if ( str.compare( QLatin1String( "Whole" ), Qt::CaseInsensitive ) == 0 )
2329 {
2330 wholeCentroid = true;
2331 }
2332 }
2333 }
2334 }
2335
2336 QgsGeometry geom = feature.geometry();
2337 if ( geom.isNull() )
2338 {
2339 return nullptr;
2340 }
2341
2342 // simplify?
2343 const QgsVectorSimplifyMethod &simplifyMethod = context.vectorSimplifyMethod();
2344 std::unique_ptr<QgsGeometry> scopedClonedGeom;
2346 {
2347 unsigned int simplifyHints = simplifyMethod.simplifyHints() | QgsMapToPixelSimplifier::SimplifyEnvelope;
2348 const Qgis::VectorSimplificationAlgorithm simplifyAlgorithm = simplifyMethod.simplifyAlgorithm();
2349 QgsMapToPixelSimplifier simplifier( simplifyHints, simplifyMethod.tolerance(), simplifyAlgorithm );
2350 geom = simplifier.simplify( geom );
2351 }
2352
2353 if ( !context.featureClipGeometry().isEmpty() )
2354 {
2355 const Qgis::GeometryType expectedType = geom.type();
2356 geom = geom.intersection( context.featureClipGeometry() );
2357 geom.convertGeometryCollectionToSubclass( expectedType );
2358 }
2359
2360 // whether we're going to create a centroid for polygon
2361 bool centroidPoly = ( ( placement == Qgis::LabelPlacement::AroundPoint
2363 && geom.type() == Qgis::GeometryType::Polygon );
2364
2365 // CLIP the geometry if it is bigger than the extent
2366 // don't clip if centroid is requested for whole feature
2367 bool doClip = false;
2368 if ( !centroidPoly || !wholeCentroid )
2369 {
2370 doClip = true;
2371 }
2372
2373
2374 Qgis::LabelPolygonPlacementFlags polygonPlacement = mPolygonPlacementFlags;
2375 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::PolygonLabelOutside ) )
2376 {
2377 const QVariant dataDefinedOutside = mDataDefinedProperties.value( QgsPalLayerSettings::Property::PolygonLabelOutside, context.expressionContext() );
2378 if ( !QgsVariantUtils::isNull( dataDefinedOutside ) )
2379 {
2380 if ( dataDefinedOutside.userType() == QMetaType::Type::QString )
2381 {
2382 const QString value = dataDefinedOutside.toString().trimmed();
2383 if ( value.compare( QLatin1String( "force" ), Qt::CaseInsensitive ) == 0 )
2384 {
2385 // forced outside placement -- remove inside flag, add outside flag
2386 polygonPlacement &= ~static_cast< int >( Qgis::LabelPolygonPlacementFlag::AllowPlacementInsideOfPolygon );
2388 }
2389 else if ( value.compare( QLatin1String( "yes" ), Qt::CaseInsensitive ) == 0 )
2390 {
2391 // permit outside placement
2393 }
2394 else if ( value.compare( QLatin1String( "no" ), Qt::CaseInsensitive ) == 0 )
2395 {
2396 // block outside placement
2397 polygonPlacement &= ~static_cast< int >( Qgis::LabelPolygonPlacementFlag::AllowPlacementOutsideOfPolygon );
2398 }
2399 }
2400 else
2401 {
2402 if ( dataDefinedOutside.toBool() )
2403 {
2404 // permit outside placement
2406 }
2407 else
2408 {
2409 // block outside placement
2410 polygonPlacement &= ~static_cast< int >( Qgis::LabelPolygonPlacementFlag::AllowPlacementOutsideOfPolygon );
2411 }
2412 }
2413 }
2414 }
2415
2416 QgsLabelLineSettings lineSettings = mLineSettings;
2417 lineSettings.updateDataDefinedProperties( mDataDefinedProperties, context.expressionContext() );
2418
2419 QgsLabelPointSettings pointSettings = mPointSettings;
2420 pointSettings.updateDataDefinedProperties( mDataDefinedProperties, context.expressionContext() );
2421
2423 {
2424 switch ( lineSettings.anchorClipping() )
2425 {
2427 break;
2428
2430 doClip = false;
2431 break;
2432 }
2433 }
2434
2435 // if using fitInPolygonOnly option, generate the permissible zone (must happen before geometry is modified - e.g.,
2436 // as a result of using perimeter based labeling and the geometry is converted to a boundary)
2437 // note that we also force this if we are permitting labels to be placed outside of polygons too!
2438 QgsGeometry permissibleZone;
2440 {
2441 permissibleZone = geom;
2442 if ( QgsPalLabeling::geometryRequiresPreparation( permissibleZone, context, ct, doClip ? extentGeom : QgsGeometry(), lineSettings.mergeLines() ) )
2443 {
2444 permissibleZone = QgsPalLabeling::prepareGeometry( permissibleZone, context, ct, doClip ? extentGeom : QgsGeometry(), lineSettings.mergeLines() );
2445 }
2446 }
2447
2448 // if using perimeter based labeling for polygons, get the polygon's
2449 // linear boundary and use that for the label geometry
2450 if ( ( geom.type() == Qgis::GeometryType::Polygon )
2452 {
2453 geom = QgsGeometry( geom.constGet()->boundary() );
2454 }
2455
2456 geos::unique_ptr geos_geom_clone;
2458 {
2459 geom = QgsPalLabeling::prepareGeometry( geom, context, ct, doClip ? extentGeom : QgsGeometry(), lineSettings.mergeLines() );
2460
2461 if ( geom.isEmpty() )
2462 return nullptr;
2463 }
2465
2467 {
2468 if ( !obstacleGeometry.isNull() && QgsPalLabeling::geometryRequiresPreparation( obstacleGeometry, context, ct, doClip ? extentGeom : QgsGeometry(), lineSettings.mergeLines() ) )
2469 {
2470 obstacleGeometry = QgsGeometry( QgsPalLabeling::prepareGeometry( obstacleGeometry, context, ct, doClip ? extentGeom : QgsGeometry(), lineSettings.mergeLines() ) );
2471 }
2472 }
2473
2474 QgsLabelThinningSettings featureThinningSettings = mThinningSettings;
2475 featureThinningSettings.updateDataDefinedProperties( mDataDefinedProperties, context.expressionContext() );
2476
2477 double minimumSize = 0.0;
2478 if ( featureThinningSettings.minimumFeatureSize() > 0 )
2479 {
2480 // for minimum feature size on merged lines, we need to delay the filtering after the merging occurred in PAL
2482 {
2483 minimumSize = context.convertToMapUnits( featureThinningSettings.minimumFeatureSize(), Qgis::RenderUnit::Millimeters );
2484 }
2485 else
2486 {
2487 if ( !checkMinimumSizeMM( context, geom, featureThinningSettings.minimumFeatureSize() ) )
2488 return nullptr;
2489 }
2490 }
2491
2492 if ( !geos_geom_clone )
2493 return nullptr; // invalid geometry
2494
2495 // likelihood exists label will be registered with PAL and may be drawn
2496 // check if max number of features to label (already registered with PAL) has been reached
2497 // Debug output at end of QgsPalLabeling::drawLabeling(), when deleting temp geometries
2498 if ( featureThinningSettings.limitNumberOfLabelsEnabled() )
2499 {
2500 if ( !featureThinningSettings.maximumNumberLabels() )
2501 {
2502 return nullptr;
2503 }
2504 if ( mFeatsRegPal >= featureThinningSettings.maximumNumberLabels() )
2505 {
2506 return nullptr;
2507 }
2508
2509 int divNum = static_cast< int >( ( static_cast< double >( mFeaturesToLabel ) / featureThinningSettings.maximumNumberLabels() ) + 0.5 ); // NOLINT
2510 if ( divNum && ( mFeatsRegPal == static_cast< int >( mFeatsSendingToPal / divNum ) ) )
2511 {
2512 mFeatsSendingToPal += 1;
2513 if ( divNum && mFeatsSendingToPal % divNum )
2514 {
2515 return nullptr;
2516 }
2517 }
2518 }
2519
2520 //data defined position / alignment / rotation?
2521 bool layerDefinedRotation = false;
2522 bool dataDefinedRotation = false;
2523 double xPos = 0.0, yPos = 0.0;
2524 double angleInRadians = 0.0;
2525 double quadOffsetX = 0.0, quadOffsetY = 0.0;
2526 double offsetX = 0.0, offsetY = 0.0;
2527 QgsPointXY anchorPosition;
2528
2530 {
2531 anchorPosition = geom.centroid().asPoint();
2532 }
2533 //x/y shift in case of alignment
2534 double xdiff = 0.0;
2535 double ydiff = 0.0;
2536
2537 //data defined quadrant offset?
2538 bool ddFixedQuad = false;
2540 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::OffsetQuad ) )
2541 {
2542 context.expressionContext().setOriginalValueVariable( static_cast< int >( quadOff ) );
2543 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Property::OffsetQuad, context.expressionContext() );
2544 if ( !QgsVariantUtils::isNull( exprVal ) )
2545 {
2546 bool ok;
2547 int quadInt = exprVal.toInt( &ok );
2548 if ( ok && 0 <= quadInt && quadInt <= 8 )
2549 {
2550 quadOff = static_cast< Qgis::LabelQuadrantPosition >( quadInt );
2551 ddFixedQuad = true;
2552 }
2553 }
2554 }
2555
2556 // adjust quadrant offset of labels
2557 switch ( quadOff )
2558 {
2560 quadOffsetX = -1.0;
2561 quadOffsetY = 1.0;
2562 break;
2564 quadOffsetX = 0.0;
2565 quadOffsetY = 1.0;
2566 break;
2568 quadOffsetX = 1.0;
2569 quadOffsetY = 1.0;
2570 break;
2572 quadOffsetX = -1.0;
2573 quadOffsetY = 0.0;
2574 break;
2576 quadOffsetX = 1.0;
2577 quadOffsetY = 0.0;
2578 break;
2580 quadOffsetX = -1.0;
2581 quadOffsetY = -1.0;
2582 break;
2584 quadOffsetX = 0.0;
2585 quadOffsetY = -1.0;
2586 break;
2588 quadOffsetX = 1.0;
2589 quadOffsetY = -1.0;
2590 break;
2592 break;
2593 }
2594
2595 //data defined label offset?
2596 double xOff = xOffset;
2597 double yOff = yOffset;
2598 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::OffsetXY ) )
2599 {
2601 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Property::OffsetXY, context.expressionContext() );
2602 bool ok = false;
2603 const QPointF ddOffPt = QgsSymbolLayerUtils::toPoint( exprVal, &ok );
2604 if ( ok )
2605 {
2606 xOff = ddOffPt.x();
2607 yOff = ddOffPt.y();
2608 }
2609 }
2610
2611 // data defined label offset units?
2612 Qgis::RenderUnit offUnit = offsetUnits;
2613 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::OffsetUnits ) )
2614 {
2615 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Property::OffsetUnits, context.expressionContext() );
2616 if ( !QgsVariantUtils::isNull( exprVal ) )
2617 {
2618 QString units = exprVal.toString().trimmed();
2619 if ( !units.isEmpty() )
2620 {
2621 bool ok = false;
2622 Qgis::RenderUnit decodedUnits = QgsUnitTypes::decodeRenderUnit( units, &ok );
2623 if ( ok )
2624 {
2625 offUnit = decodedUnits;
2626 }
2627 }
2628 }
2629 }
2630
2631 // adjust offset of labels to match chosen unit and map scale
2632 // offsets match those of symbology: -x = left, -y = up
2633 offsetX = context.convertToMapUnits( xOff, offUnit, labelOffsetMapUnitScale );
2634 // must be negative to match symbology offset direction
2635 offsetY = context.convertToMapUnits( -yOff, offUnit, labelOffsetMapUnitScale );
2636
2637 // layer defined rotation?
2638 if ( !qgsDoubleNear( angleOffset, 0.0 ) )
2639 {
2640 layerDefinedRotation = true;
2641 angleInRadians = ( 360 - angleOffset ) * M_PI / 180; // convert to radians counterclockwise
2642 }
2643
2644 const QgsMapToPixel &m2p = context.mapToPixel();
2645 //data defined rotation?
2646 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::LabelRotation ) )
2647 {
2649 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Property::LabelRotation, context.expressionContext() );
2650 if ( !QgsVariantUtils::isNull( exprVal ) )
2651 {
2652 bool ok;
2653 const double rotation = exprVal.toDouble( &ok );
2654 if ( ok )
2655 {
2656 dataDefinedRotation = true;
2657
2658 double rotationDegrees = rotation * QgsUnitTypes::fromUnitToUnitFactor( mRotationUnit,
2660
2661 // TODO: add setting to disable having data defined rotation follow
2662 // map rotation ?
2663 rotationDegrees += m2p.mapRotation();
2664 angleInRadians = ( 360 - rotationDegrees ) * M_PI / 180.0;
2665 }
2666 }
2667 }
2668
2669 bool hasDataDefinedPosition = false;
2670 {
2671 bool ddPosition = false;
2672
2673 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::PositionX )
2674 && mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::PositionY ) )
2675 {
2676 const QVariant xPosProperty = mDataDefinedProperties.value( QgsPalLayerSettings::Property::PositionX, context.expressionContext() );
2677 const QVariant yPosProperty = mDataDefinedProperties.value( QgsPalLayerSettings::Property::PositionY, context.expressionContext() );
2678 if ( !QgsVariantUtils::isNull( xPosProperty )
2679 && !QgsVariantUtils::isNull( yPosProperty ) )
2680 {
2681 ddPosition = true;
2682
2683 bool ddXPos = false, ddYPos = false;
2684 xPos = xPosProperty.toDouble( &ddXPos );
2685 yPos = yPosProperty.toDouble( &ddYPos );
2686 if ( ddXPos && ddYPos )
2687 hasDataDefinedPosition = true;
2688 }
2689 }
2690 else if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::PositionPoint ) )
2691 {
2692 const QVariant pointPosProperty = mDataDefinedProperties.value( QgsPalLayerSettings::Property::PositionPoint, context.expressionContext() );
2693 if ( !QgsVariantUtils::isNull( pointPosProperty ) )
2694 {
2695 ddPosition = true;
2696
2697 QgsPoint point;
2698 if ( pointPosProperty.userType() == qMetaTypeId<QgsReferencedGeometry>() )
2699 {
2700 QgsReferencedGeometry referencedGeometryPoint = pointPosProperty.value<QgsReferencedGeometry>();
2701 point = QgsPoint( referencedGeometryPoint.asPoint() );
2702
2703 if ( !referencedGeometryPoint.isNull()
2704 && ct.sourceCrs() != referencedGeometryPoint.crs() )
2705 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 );
2706 }
2707 else if ( pointPosProperty.userType() == qMetaTypeId< QgsGeometry>() )
2708 {
2709 point = QgsPoint( pointPosProperty.value<QgsGeometry>().asPoint() );
2710 }
2711
2712 if ( !point.isEmpty() )
2713 {
2714 hasDataDefinedPosition = true;
2715
2716 xPos = point.x();
2717 yPos = point.y();
2718 }
2719 }
2720 }
2721
2722 if ( ddPosition )
2723 {
2724 //data defined position. But field values could be NULL -> positions will be generated by PAL
2725 if ( hasDataDefinedPosition )
2726 {
2727 // layer rotation set, but don't rotate pinned labels unless data defined
2728 if ( layerDefinedRotation && !dataDefinedRotation )
2729 {
2730 angleInRadians = 0.0;
2731 }
2732
2733 //horizontal alignment
2734 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::Hali ) )
2735 {
2736 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Property::Hali, context.expressionContext() );
2737 if ( !QgsVariantUtils::isNull( exprVal ) )
2738 {
2739 QString haliString = exprVal.toString();
2740 if ( haliString.compare( QLatin1String( "Center" ), Qt::CaseInsensitive ) == 0 )
2741 {
2742 xdiff -= labelSize.width() / 2.0;
2743 }
2744 else if ( haliString.compare( QLatin1String( "Right" ), Qt::CaseInsensitive ) == 0 )
2745 {
2746 xdiff -= labelSize.width();
2747 }
2748 }
2749 }
2750
2751 //vertical alignment
2752 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::Vali ) )
2753 {
2754 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Property::Vali, context.expressionContext() );
2755 if ( !QgsVariantUtils::isNull( exprVal ) )
2756 {
2757 QString valiString = exprVal.toString();
2758 if ( valiString.compare( QLatin1String( "Bottom" ), Qt::CaseInsensitive ) != 0 )
2759 {
2760 if ( valiString.compare( QLatin1String( "Top" ), Qt::CaseInsensitive ) == 0 )
2761 {
2762 ydiff -= labelSize.height();
2763 }
2764 else
2765 {
2766 double descentRatio = labelFontMetrics.descent() / labelFontMetrics.height();
2767 if ( valiString.compare( QLatin1String( "Base" ), Qt::CaseInsensitive ) == 0 )
2768 {
2769 ydiff -= labelSize.height() * descentRatio;
2770 }
2771 else //'Cap' or 'Half'
2772 {
2773 double capHeightRatio = ( labelFontMetrics.boundingRect( 'H' ).height() + 1 + labelFontMetrics.descent() ) / labelFontMetrics.height();
2774 ydiff -= labelSize.height() * capHeightRatio;
2775 if ( valiString.compare( QLatin1String( "Half" ), Qt::CaseInsensitive ) == 0 )
2776 {
2777 ydiff += labelSize.height() * ( capHeightRatio - descentRatio ) / 2.0;
2778 }
2779 }
2780 }
2781 }
2782 }
2783 }
2784
2785 if ( dataDefinedRotation )
2786 {
2787 //adjust xdiff and ydiff because the hali/vali point needs to be the rotation center
2788 double xd = xdiff * std::cos( angleInRadians ) - ydiff * std::sin( angleInRadians );
2789 double yd = xdiff * std::sin( angleInRadians ) + ydiff * std::cos( angleInRadians );
2790 xdiff = xd;
2791 ydiff = yd;
2792 }
2793
2794 //project xPos and yPos from layer to map CRS, handle rotation
2795 QgsGeometry ddPoint( new QgsPoint( xPos, yPos ) );
2796 if ( QgsPalLabeling::geometryRequiresPreparation( ddPoint, context, ct ) )
2797 {
2798 ddPoint = QgsPalLabeling::prepareGeometry( ddPoint, context, ct );
2799 if ( const QgsPoint *point = qgsgeometry_cast< const QgsPoint * >( ddPoint.constGet() ) )
2800 {
2801 xPos = point->x();
2802 yPos = point->y();
2803 anchorPosition = QgsPointXY( xPos, yPos );
2804 }
2805 else
2806 {
2807 QgsMessageLog::logMessage( QObject::tr( "Invalid data defined label position (%1, %2)" ).arg( xPos ).arg( yPos ), QObject::tr( "Labeling" ) );
2808 hasDataDefinedPosition = false;
2809 }
2810 }
2811 else
2812 {
2813 anchorPosition = QgsPointXY( xPos, yPos );
2814 }
2815
2816 xPos += xdiff;
2817 yPos += ydiff;
2818 }
2819 else
2820 {
2821 anchorPosition = QgsPointXY( xPos, yPos );
2822 }
2823 }
2824 }
2825
2826 // data defined always show?
2827 bool alwaysShow = false;
2828 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::AlwaysShow ) )
2829 {
2830 alwaysShow = mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::Property::AlwaysShow, context.expressionContext(), false );
2831 }
2832
2833 // set repeat distance
2834 // data defined repeat distance?
2835 double repeatDist = repeatDistance;
2836 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::RepeatDistance ) )
2837 {
2838 context.expressionContext().setOriginalValueVariable( repeatDist );
2839 repeatDist = mDataDefinedProperties.valueAsDouble( QgsPalLayerSettings::Property::RepeatDistance, context.expressionContext(), repeatDist );
2840 }
2841
2842 // data defined label-repeat distance units?
2844 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::RepeatDistanceUnit ) )
2845 {
2846 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Property::RepeatDistanceUnit, context.expressionContext() );
2847 if ( !QgsVariantUtils::isNull( exprVal ) )
2848 {
2849 QString units = exprVal.toString().trimmed();
2850 if ( !units.isEmpty() )
2851 {
2852 bool ok = false;
2853 Qgis::RenderUnit decodedUnits = QgsUnitTypes::decodeRenderUnit( units, &ok );
2854 if ( ok )
2855 {
2856 repeatUnits = decodedUnits;
2857 }
2858 }
2859 }
2860 }
2861
2862 if ( !qgsDoubleNear( repeatDist, 0.0 ) )
2863 {
2864 if ( repeatUnits != Qgis::RenderUnit::MapUnits )
2865 {
2866 repeatDist = context.convertToMapUnits( repeatDist, repeatUnits, repeatDistanceMapUnitScale );
2867 }
2868 }
2869
2870 // overrun distance
2871 double overrunDistanceEval = lineSettings.overrunDistance();
2872 if ( !qgsDoubleNear( overrunDistanceEval, 0.0 ) )
2873 {
2874 overrunDistanceEval = context.convertToMapUnits( overrunDistanceEval, lineSettings.overrunDistanceUnit(), lineSettings.overrunDistanceMapUnitScale() );
2875 }
2876
2877 // we smooth out the overrun label extensions by 1 mm, to avoid little jaggies right at the start or end of the lines
2878 // causing the overrun extension to extend out in an undesirable direction. This is hard coded, we don't want to overload
2879 // users with options they likely don't need to see...
2880 const double overrunSmoothDist = context.convertToMapUnits( 1, Qgis::RenderUnit::Millimeters );
2881
2882 bool labelAll = labelPerPart && !hasDataDefinedPosition;
2883 if ( !hasDataDefinedPosition )
2884 {
2885 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::LabelAllParts ) )
2886 {
2888 labelAll = mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::Property::LabelAllParts, context.expressionContext(), labelPerPart );
2889 }
2890 }
2891
2892 // maximum distance
2893 double maximumDistanceEval = pointSettings.maximumDistance();
2894 if ( !qgsDoubleNear( maximumDistanceEval, 0.0 ) )
2895 {
2896 maximumDistanceEval = context.convertToMapUnits( maximumDistanceEval, pointSettings.maximumDistanceUnit(), pointSettings.maximumDistanceMapUnitScale() );
2897 }
2898
2899 // feature to the layer
2900 std::unique_ptr< QgsTextLabelFeature > labelFeature = std::make_unique< QgsTextLabelFeature>( feature.id(), std::move( geos_geom_clone ), labelSize );
2901 labelFeature->setAnchorPosition( anchorPosition );
2902 labelFeature->setFeature( feature );
2903 labelFeature->setSymbol( symbol );
2904 labelFeature->setDocument( doc, documentMetrics );
2905 if ( !qgsDoubleNear( rotatedSize.width(), 0.0 ) && !qgsDoubleNear( rotatedSize.height(), 0.0 ) )
2906 labelFeature->setRotatedSize( rotatedSize );
2907 mFeatsRegPal++;
2908
2909 labelFeature->setHasFixedPosition( hasDataDefinedPosition );
2910 labelFeature->setFixedPosition( QgsPointXY( xPos, yPos ) );
2911 // use layer-level defined rotation, but not if position fixed
2912 labelFeature->setHasFixedAngle( dataDefinedRotation || ( !hasDataDefinedPosition && !qgsDoubleNear( angleInRadians, 0.0 ) ) );
2913 labelFeature->setFixedAngle( angleInRadians );
2914 labelFeature->setQuadOffset( QPointF( quadOffsetX, quadOffsetY ) );
2915 labelFeature->setPositionOffset( QgsPointXY( offsetX, offsetY ) );
2916 labelFeature->setOffsetType( offsetType );
2917 labelFeature->setAlwaysShow( alwaysShow );
2918 labelFeature->setRepeatDistance( repeatDist );
2919 labelFeature->setLabelText( labelText );
2920 labelFeature->setPermissibleZone( permissibleZone );
2921 labelFeature->setOverrunDistance( overrunDistanceEval );
2922 labelFeature->setOverrunSmoothDistance( overrunSmoothDist );
2923 labelFeature->setMaximumDistance( maximumDistanceEval );
2924 labelFeature->setLineAnchorPercent( lineSettings.lineAnchorPercent() );
2925 labelFeature->setLineAnchorType( lineSettings.anchorType() );
2926 labelFeature->setLineAnchorTextPoint( lineSettings.anchorTextPoint() );
2927 labelFeature->setLabelAllParts( labelAll );
2928 labelFeature->setOriginalFeatureCrs( context.coordinateTransform().sourceCrs() );
2929 labelFeature->setMinimumSize( minimumSize );
2930 if ( geom.type() == Qgis::GeometryType::Point && !obstacleGeometry.isNull() )
2931 {
2932 //register symbol size
2933 labelFeature->setSymbolSize( QSizeF( obstacleGeometry.boundingBox().width(),
2934 obstacleGeometry.boundingBox().height() ) );
2935 }
2936
2937 if ( outerBounds.left() != 0 || outerBounds.top() != 0 || !qgsDoubleNear( outerBounds.width(), labelSize.width() ) || !qgsDoubleNear( outerBounds.height(), labelSize.height() ) )
2938 {
2939 labelFeature->setOuterBounds( outerBounds );
2940 }
2941
2942 //set label's visual margin so that top visual margin is the leading, and bottom margin is the font's descent
2943 //this makes labels align to the font's baseline or highest character
2944 double topMargin = std::max( 0.25 * labelFontMetrics.ascent(), 0.0 );
2945 double bottomMargin = 1.0 + labelFontMetrics.descent();
2946 QgsMargins vm( 0.0, topMargin, 0.0, bottomMargin );
2947 vm *= xform->mapUnitsPerPixel();
2948 labelFeature->setVisualMargin( vm );
2949
2950 // store the label's calculated font for later use during painting
2951 QgsDebugMsgLevel( QStringLiteral( "PAL font stored definedFont: %1, Style: %2" ).arg( labelFont.toString(), labelFont.styleName() ), 4 );
2952 labelFeature->setDefinedFont( labelFont );
2953
2954 labelFeature->setMaximumCharacterAngleInside( std::clamp( maxcharanglein, 20.0, 60.0 ) * M_PI / 180 );
2955 labelFeature->setMaximumCharacterAngleOutside( std::clamp( maxcharangleout, -95.0, -20.0 ) * M_PI / 180 );
2956 switch ( placement )
2957 {
2965 // these placements don't require text metrics
2966 break;
2967
2970 labelFeature->setTextMetrics( QgsTextLabelFeature::calculateTextMetrics( xform, context, labelFont, labelFontMetrics, labelFont.letterSpacing(), labelFont.wordSpacing(), labelText, evaluatedFormat.allowHtmlFormatting() ? &doc : nullptr, evaluatedFormat.allowHtmlFormatting() ? &documentMetrics : nullptr ) );
2971 break;
2972 }
2973
2974 // for labelFeature the LabelInfo is passed to feat when it is registered
2975
2976 // TODO: allow layer-wide feature dist in PAL...?
2977
2978 // data defined label-feature distance?
2979 double distance = dist;
2980 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::LabelDistance ) )
2981 {
2982 context.expressionContext().setOriginalValueVariable( distance );
2983 distance = mDataDefinedProperties.valueAsDouble( QgsPalLayerSettings::Property::LabelDistance, context.expressionContext(), distance );
2984 }
2985
2986 // data defined label-feature distance units?
2987 Qgis::RenderUnit distUnit = distUnits;
2988 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::DistanceUnits ) )
2989 {
2990 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Property::DistanceUnits, context.expressionContext() );
2991 if ( !QgsVariantUtils::isNull( exprVal ) )
2992 {
2993 QString units = exprVal.toString().trimmed();
2994 QgsDebugMsgLevel( QStringLiteral( "exprVal DistanceUnits:%1" ).arg( units ), 4 );
2995 if ( !units.isEmpty() )
2996 {
2997 bool ok = false;
2998 Qgis::RenderUnit decodedUnits = QgsUnitTypes::decodeRenderUnit( units, &ok );
2999 if ( ok )
3000 {
3001 distUnit = decodedUnits;
3002 }
3003 }
3004 }
3005 }
3006 distance = context.convertToPainterUnits( distance, distUnit, distMapUnitScale );
3007
3008 // when using certain placement modes, we force a tiny minimum distance. This ensures that
3009 // candidates are created just offset from a border and avoids candidates being incorrectly flagged as colliding with neighbours
3010 switch ( placement )
3011 {
3015 distance = ( distance < 0 ? -1 : 1 ) * std::max( std::fabs( distance ), 1.0 );
3016 break;
3017
3019 break;
3020
3026 {
3027 distance = std::max( distance, 2.0 );
3028 }
3029 break;
3030
3032 distance = std::max( distance, 2.0 );
3033 break;
3034 }
3035
3036 if ( !qgsDoubleNear( distance, 0.0 ) )
3037 {
3038 double d = ptOne.distance( ptZero ) * distance;
3039 labelFeature->setDistLabel( d );
3040 }
3041
3042 if ( ddFixedQuad )
3043 {
3044 labelFeature->setHasFixedQuadrant( true );
3045 }
3046
3047 labelFeature->setArrangementFlags( lineSettings.placementFlags() );
3048
3049 labelFeature->setPolygonPlacementFlags( polygonPlacement );
3050
3051 // data defined z-index?
3052 double z = zIndex;
3053 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::ZIndex ) )
3054 {
3056 z = mDataDefinedProperties.valueAsDouble( QgsPalLayerSettings::Property::ZIndex, context.expressionContext(), z );
3057 }
3058 labelFeature->setZIndex( z );
3059
3060 // data defined priority?
3061 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::Priority ) )
3062 {
3064 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Property::Priority, context.expressionContext() );
3065 if ( !QgsVariantUtils::isNull( exprVal ) )
3066 {
3067 bool ok;
3068 double priorityD = exprVal.toDouble( &ok );
3069 if ( ok )
3070 {
3071 priorityD = std::clamp( priorityD, 0.0, 10.0 );
3072 priorityD = 1 - priorityD / 10.0; // convert 0..10 --> 1..0
3073 labelFeature->setPriority( priorityD );
3074 }
3075 }
3076 }
3077
3078 // data defined allow degraded placement
3079 {
3080 double allowDegradedPlacement = mPlacementSettings.allowDegradedPlacement();
3082 {
3083 context.expressionContext().setOriginalValueVariable( allowDegradedPlacement );
3084 allowDegradedPlacement = mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::Property::AllowDegradedPlacement, context.expressionContext(), allowDegradedPlacement );
3085 }
3086 labelFeature->setAllowDegradedPlacement( allowDegradedPlacement );
3087 }
3088
3089 // data defined overlap handling
3090 {
3091 Qgis::LabelOverlapHandling overlapHandling = mPlacementSettings.overlapHandling();
3092 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::OverlapHandling ) )
3093 {
3094 const QString handlingString = mDataDefinedProperties.valueAsString( QgsPalLayerSettings::Property::OverlapHandling, context.expressionContext() );
3095 const QString cleanedString = handlingString.trimmed();
3096 if ( cleanedString.compare( QLatin1String( "prevent" ), Qt::CaseInsensitive ) == 0 )
3098 else if ( cleanedString.compare( QLatin1String( "allowifneeded" ), Qt::CaseInsensitive ) == 0 )
3100 else if ( cleanedString.compare( QLatin1String( "alwaysallow" ), Qt::CaseInsensitive ) == 0 )
3102 }
3103 labelFeature->setOverlapHandling( overlapHandling );
3104 }
3105
3106 labelFeature->setPrioritization( mPlacementSettings.prioritization() );
3107
3108 QgsLabelObstacleSettings os = mObstacleSettings;
3109 os.setIsObstacle( isObstacle );
3110 os.updateDataDefinedProperties( mDataDefinedProperties, context.expressionContext() );
3111 os.setObstacleGeometry( obstacleGeometry );
3112 labelFeature->setObstacleSettings( os );
3113
3114 QVector< Qgis::LabelPredefinedPointPosition > positionOrder = pointSettings.predefinedPositionOrder();
3115 if ( positionOrder.isEmpty() )
3116 positionOrder = *DEFAULT_PLACEMENT_ORDER();
3117
3119 {
3121 QString dataDefinedOrder = mDataDefinedProperties.valueAsString( QgsPalLayerSettings::Property::PredefinedPositionOrder, context.expressionContext() );
3122 if ( !dataDefinedOrder.isEmpty() )
3123 {
3124 positionOrder = QgsLabelingUtils::decodePredefinedPositionOrder( dataDefinedOrder );
3125 }
3126 }
3127 labelFeature->setPredefinedPositionOrder( positionOrder );
3128
3129 // add parameters for data defined labeling to label feature
3130 labelFeature->setDataDefinedValues( dataDefinedValues );
3131
3132 return labelFeature;
3133}
3134
3135std::unique_ptr<QgsLabelFeature> QgsPalLayerSettings::registerObstacleFeature( const QgsFeature &f, QgsRenderContext &context, const QgsGeometry &obstacleGeometry )
3136{
3137 mCurFeat = &f;
3138
3139 QgsGeometry geom;
3140 if ( !obstacleGeometry.isNull() )
3141 {
3142 geom = obstacleGeometry;
3143 }
3144 else
3145 {
3146 geom = f.geometry();
3147 }
3148
3149 if ( geom.isNull() )
3150 {
3151 return nullptr;
3152 }
3153
3154 // don't even try to register linestrings with only one vertex as an obstacle
3155 if ( const QgsLineString *ls = qgsgeometry_cast< const QgsLineString * >( geom.constGet() ) )
3156 {
3157 if ( ls->numPoints() < 2 )
3158 return nullptr;
3159 }
3160
3161 // simplify?
3162 const QgsVectorSimplifyMethod &simplifyMethod = context.vectorSimplifyMethod();
3163 std::unique_ptr<QgsGeometry> scopedClonedGeom;
3165 {
3166 int simplifyHints = simplifyMethod.simplifyHints() | QgsMapToPixelSimplifier::SimplifyEnvelope;
3167 const Qgis::VectorSimplificationAlgorithm simplifyAlgorithm = simplifyMethod.simplifyAlgorithm();
3168 QgsMapToPixelSimplifier simplifier( simplifyHints, simplifyMethod.tolerance(), simplifyAlgorithm );
3169 geom = simplifier.simplify( geom );
3170 }
3171
3172 geos::unique_ptr geos_geom_clone;
3173 std::unique_ptr<QgsGeometry> scopedPreparedGeom;
3174
3175 if ( QgsPalLabeling::geometryRequiresPreparation( geom, context, ct, extentGeom, mLineSettings.mergeLines() ) )
3176 {
3177 geom = QgsPalLabeling::prepareGeometry( geom, context, ct, extentGeom, mLineSettings.mergeLines() );
3178 }
3179 geos_geom_clone = QgsGeos::asGeos( geom );
3180
3181 if ( !geos_geom_clone )
3182 return nullptr; // invalid geometry
3183
3184 // feature to the layer
3185 std::unique_ptr< QgsLabelFeature > obstacleFeature = std::make_unique< QgsLabelFeature >( f.id(), std::move( geos_geom_clone ), QSizeF( 0, 0 ) );
3186 obstacleFeature->setFeature( f );
3187
3188 QgsLabelObstacleSettings os = mObstacleSettings;
3189 os.setIsObstacle( true );
3190 os.updateDataDefinedProperties( mDataDefinedProperties, context.expressionContext() );
3191 obstacleFeature->setObstacleSettings( os );
3192
3193 mFeatsRegPal++;
3194 return obstacleFeature;
3195}
3196
3197bool QgsPalLayerSettings::dataDefinedValEval( DataDefinedValueType valType,
3199 QVariant &exprVal, QgsExpressionContext &context, const QVariant &originalValue )
3200{
3201 if ( !mDataDefinedProperties.isActive( p ) )
3202 return false;
3203
3204 context.setOriginalValueVariable( originalValue );
3205 exprVal = mDataDefinedProperties.value( p, context );
3206 if ( !QgsVariantUtils::isNull( exprVal ) )
3207 {
3208 switch ( valType )
3209 {
3210 case DDBool:
3211 {
3212 bool bol = exprVal.toBool();
3213 dataDefinedValues.insert( p, QVariant( bol ) );
3214 return true;
3215 }
3216 case DDInt:
3217 {
3218 bool ok;
3219 int size = exprVal.toInt( &ok );
3220
3221 if ( ok )
3222 {
3223 dataDefinedValues.insert( p, QVariant( size ) );
3224 return true;
3225 }
3226 return false;
3227 }
3228 case DDIntPos:
3229 {
3230 bool ok;
3231 int size = exprVal.toInt( &ok );
3232
3233 if ( ok && size > 0 )
3234 {
3235 dataDefinedValues.insert( p, QVariant( size ) );
3236 return true;
3237 }
3238 return false;
3239 }
3240 case DDDouble:
3241 {
3242 bool ok;
3243 double size = exprVal.toDouble( &ok );
3244
3245 if ( ok )
3246 {
3247 dataDefinedValues.insert( p, QVariant( size ) );
3248 return true;
3249 }
3250 return false;
3251 }
3252 case DDDoublePos:
3253 {
3254 bool ok;
3255 double size = exprVal.toDouble( &ok );
3256
3257 if ( ok && size > 0.0 )
3258 {
3259 dataDefinedValues.insert( p, QVariant( size ) );
3260 return true;
3261 }
3262 return false;
3263 }
3264 case DDRotation180:
3265 {
3266 bool ok;
3267 double rot = exprVal.toDouble( &ok );
3268 if ( ok )
3269 {
3270 if ( rot < -180.0 && rot >= -360 )
3271 {
3272 rot += 360;
3273 }
3274 if ( rot > 180.0 && rot <= 360 )
3275 {
3276 rot -= 360;
3277 }
3278 if ( rot >= -180 && rot <= 180 )
3279 {
3280 dataDefinedValues.insert( p, QVariant( rot ) );
3281 return true;
3282 }
3283 }
3284 return false;
3285 }
3286 case DDOpacity:
3287 {
3288 bool ok;
3289 int size = exprVal.toInt( &ok );
3290 if ( ok && size >= 0 && size <= 100 )
3291 {
3292 dataDefinedValues.insert( p, QVariant( size ) );
3293 return true;
3294 }
3295 return false;
3296 }
3297 case DDString:
3298 {
3299 QString str = exprVal.toString(); // don't trim whitespace
3300
3301 dataDefinedValues.insert( p, QVariant( str ) ); // let it stay empty if it is
3302 return true;
3303 }
3304 case DDUnits:
3305 {
3306 QString unitstr = exprVal.toString().trimmed();
3307
3308 if ( !unitstr.isEmpty() )
3309 {
3310 dataDefinedValues.insert( p, QVariant( static_cast< int >( QgsUnitTypes::decodeRenderUnit( unitstr ) ) ) );
3311 return true;
3312 }
3313 return false;
3314 }
3315 case DDColor:
3316 {
3317 QString colorstr = exprVal.toString().trimmed();
3318 QColor color = QgsColorUtils::colorFromString( colorstr );
3319
3320 if ( color.isValid() )
3321 {
3322 dataDefinedValues.insert( p, QVariant( color ) );
3323 return true;
3324 }
3325 return false;
3326 }
3327 case DDJoinStyle:
3328 {
3329 QString joinstr = exprVal.toString().trimmed();
3330
3331 if ( !joinstr.isEmpty() )
3332 {
3333 dataDefinedValues.insert( p, QVariant( static_cast< int >( QgsSymbolLayerUtils::decodePenJoinStyle( joinstr ) ) ) );
3334 return true;
3335 }
3336 return false;
3337 }
3338 case DDBlendMode:
3339 {
3340 QString blendstr = exprVal.toString().trimmed();
3341
3342 if ( !blendstr.isEmpty() )
3343 {
3344 dataDefinedValues.insert( p, QVariant( static_cast< int >( QgsSymbolLayerUtils::decodeBlendMode( blendstr ) ) ) );
3345 return true;
3346 }
3347 return false;
3348 }
3349 case DDPointF:
3350 {
3351 bool ok = false;
3352 const QPointF res = QgsSymbolLayerUtils::toPoint( exprVal, &ok );
3353 if ( ok )
3354 {
3355 dataDefinedValues.insert( p, res );
3356 return true;
3357 }
3358 return false;
3359 }
3360 case DDSizeF:
3361 {
3362 bool ok = false;
3363 const QSizeF res = QgsSymbolLayerUtils::toSize( exprVal, &ok );
3364 if ( ok )
3365 {
3366 dataDefinedValues.insert( p, res );
3367 return true;
3368 }
3369 return false;
3370 }
3371 }
3372 }
3373 return false;
3374}
3375
3376void QgsPalLayerSettings::parseTextStyle( QFont &labelFont,
3377 Qgis::RenderUnit fontunits,
3378 QgsRenderContext &context )
3379{
3380 // NOTE: labelFont already has pixelSize set, so pointSize or pointSizeF might return -1
3381
3382 QVariant exprVal; // value() is repeatedly nulled on data defined evaluation and replaced when successful
3383
3384 // Two ways to generate new data defined font:
3385 // 1) Family + [bold] + [italic] (named style is ignored and font is built off of base family)
3386 // 2) Family + named style (bold or italic is ignored)
3387
3388 // data defined font family?
3389 QString ddFontFamily;
3390 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::Family ) )
3391 {
3392 context.expressionContext().setOriginalValueVariable( labelFont.family() );
3393 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Property::Family, context.expressionContext() );
3394 if ( !QgsVariantUtils::isNull( exprVal ) )
3395 {
3396 QString family = exprVal.toString().trimmed();
3397 QgsDebugMsgLevel( QStringLiteral( "exprVal Font family:%1" ).arg( family ), 4 );
3398
3400 if ( labelFont.family() != family )
3401 {
3402 // testing for ddFontFamily in QFontDatabase.families() may be slow to do for every feature
3403 // (i.e. don't use QgsFontUtils::fontFamilyMatchOnSystem( family ) here)
3404 if ( QgsFontUtils::fontFamilyOnSystem( family ) )
3405 {
3406 ddFontFamily = family;
3407 }
3408 }
3409 }
3410 }
3411
3412 // data defined named font style?
3413 QString ddFontStyle;
3414 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::FontStyle ) )
3415 {
3416 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Property::FontStyle, context.expressionContext() );
3417 if ( !QgsVariantUtils::isNull( exprVal ) )
3418 {
3419 QString fontstyle = exprVal.toString().trimmed();
3420 QgsDebugMsgLevel( QStringLiteral( "exprVal Font style:%1" ).arg( fontstyle ), 4 );
3421 ddFontStyle = fontstyle;
3422 }
3423 }
3424
3425 // data defined bold font style?
3426 bool ddBold = false;
3427 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::Bold ) )
3428 {
3429 context.expressionContext().setOriginalValueVariable( labelFont.bold() );
3430 ddBold = mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::Property::Bold, context.expressionContext(), false );
3431 }
3432
3433 // data defined italic font style?
3434 bool ddItalic = false;
3435 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::Italic ) )
3436 {
3437 context.expressionContext().setOriginalValueVariable( labelFont.italic() );
3438 ddItalic = mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::Property::Italic, context.expressionContext(), false );
3439 }
3440
3441 // TODO: update when pref for how to resolve missing family (use matching algorithm or just default font) is implemented
3442 // (currently defaults to what has been read in from layer settings)
3443 QFont newFont;
3444 QFont appFont = QApplication::font();
3445 bool newFontBuilt = false;
3446 if ( ddBold || ddItalic )
3447 {
3448 // new font needs built, since existing style needs removed
3449 newFont = QgsFontUtils::createFont( !ddFontFamily.isEmpty() ? ddFontFamily : labelFont.family() );
3450 newFontBuilt = true;
3451 newFont.setBold( ddBold );
3452 newFont.setItalic( ddItalic );
3453 }
3454 else if ( !ddFontStyle.isEmpty()
3455 && ddFontStyle.compare( QLatin1String( "Ignore" ), Qt::CaseInsensitive ) != 0 )
3456 {
3457 if ( !ddFontFamily.isEmpty() )
3458 {
3459 // both family and style are different, build font from database
3460 if ( !mFontDB )
3461 mFontDB = std::make_unique< QFontDatabase >();
3462
3463 QFont styledfont = mFontDB->font( ddFontFamily, ddFontStyle, appFont.pointSize() );
3464 if ( appFont != styledfont )
3465 {
3466 newFont = styledfont;
3467 newFontBuilt = true;
3468 }
3469 }
3470
3471 // update the font face style
3472 QgsFontUtils::updateFontViaStyle( newFontBuilt ? newFont : labelFont, ddFontStyle );
3473 }
3474 else if ( !ddFontFamily.isEmpty() )
3475 {
3476 if ( ddFontStyle.compare( QLatin1String( "Ignore" ), Qt::CaseInsensitive ) != 0 )
3477 {
3478 // just family is different, build font from database
3479 if ( !mFontDB )
3480 mFontDB = std::make_unique< QFontDatabase >();
3481 QFont styledfont = mFontDB->font( ddFontFamily, mFormat.namedStyle(), appFont.pointSize() );
3482 if ( appFont != styledfont )
3483 {
3484 newFont = styledfont;
3485 newFontBuilt = true;
3486 }
3487 }
3488 else
3489 {
3490 newFont = QgsFontUtils::createFont( ddFontFamily );
3491 newFontBuilt = true;
3492 }
3493 }
3494
3495 if ( newFontBuilt )
3496 {
3497 // copy over existing font settings
3498 //newFont = newFont.resolve( labelFont ); // should work, but let's be sure what's being copied
3499 newFont.setPixelSize( labelFont.pixelSize() );
3500 newFont.setUnderline( labelFont.underline() );
3501 newFont.setStrikeOut( labelFont.strikeOut() );
3502 newFont.setWordSpacing( labelFont.wordSpacing() );
3503 newFont.setLetterSpacing( QFont::AbsoluteSpacing, labelFont.letterSpacing() );
3504
3505 labelFont = newFont;
3506 }
3507
3508 // data defined word spacing?
3509 double wordspace = labelFont.wordSpacing();
3510 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::FontWordSpacing ) )
3511 {
3512 context.expressionContext().setOriginalValueVariable( wordspace );
3513 wordspace = mDataDefinedProperties.valueAsDouble( QgsPalLayerSettings::Property::FontWordSpacing, context.expressionContext(), wordspace );
3514 }
3515 labelFont.setWordSpacing( context.convertToPainterUnits( wordspace, fontunits, mFormat.sizeMapUnitScale() ) );
3516
3517 // data defined letter spacing?
3518 double letterspace = labelFont.letterSpacing();
3519 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::FontLetterSpacing ) )
3520 {
3521 context.expressionContext().setOriginalValueVariable( letterspace );
3522 letterspace = mDataDefinedProperties.valueAsDouble( QgsPalLayerSettings::Property::FontLetterSpacing, context.expressionContext(), letterspace );
3523 }
3524 labelFont.setLetterSpacing( QFont::AbsoluteSpacing, context.convertToPainterUnits( letterspace, fontunits, mFormat.sizeMapUnitScale() ) );
3525
3526 // data defined strikeout font style?
3527 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::Strikeout ) )
3528 {
3529 context.expressionContext().setOriginalValueVariable( labelFont.strikeOut() );
3530 bool strikeout = mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::Property::Strikeout, context.expressionContext(), false );
3531 labelFont.setStrikeOut( strikeout );
3532 }
3533
3534 // data defined stretch
3535 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::FontStretchFactor ) )
3536 {
3538 labelFont.setStretch( mDataDefinedProperties.valueAsInt( QgsPalLayerSettings::Property::FontStretchFactor, context.expressionContext(), mFormat.stretchFactor() ) );
3539 }
3540
3541 // data defined underline font style?
3542 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::Underline ) )
3543 {
3544 context.expressionContext().setOriginalValueVariable( labelFont.underline() );
3545 bool underline = mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::Property::Underline, context.expressionContext(), false );
3546 labelFont.setUnderline( underline );
3547 }
3548
3549 // pass the rest on to QgsPalLabeling::drawLabeling
3550
3551 // data defined font color?
3552 dataDefinedValEval( DDColor, QgsPalLayerSettings::Property::Color, exprVal, context.expressionContext(), QgsColorUtils::colorToString( mFormat.color() ) );
3553
3554 // data defined font opacity?
3555 dataDefinedValEval( DDOpacity, QgsPalLayerSettings::Property::FontOpacity, exprVal, context.expressionContext(), mFormat.opacity() * 100 );
3556
3557 // data defined font blend mode?
3558 dataDefinedValEval( DDBlendMode, QgsPalLayerSettings::Property::FontBlendMode, exprVal, context.expressionContext() );
3559
3560}
3561
3562void QgsPalLayerSettings::parseTextBuffer( QgsRenderContext &context )
3563{
3564 QVariant exprVal; // value() is repeatedly nulled on data defined evaluation and replaced when successful
3565
3566 QgsTextBufferSettings buffer = mFormat.buffer();
3567
3568 // data defined draw buffer?
3569 bool drawBuffer = mFormat.buffer().enabled();
3570 if ( dataDefinedValEval( DDBool, QgsPalLayerSettings::Property::BufferDraw, exprVal, context.expressionContext(), buffer.enabled() ) )
3571 {
3572 drawBuffer = exprVal.toBool();
3573 }
3574 else if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::BufferDraw ) && QgsVariantUtils::isNull( exprVal ) )
3575 {
3576 dataDefinedValues.insert( QgsPalLayerSettings::Property::BufferDraw, QVariant( drawBuffer ) );
3577 }
3578
3579 if ( !drawBuffer )
3580 {
3581 return;
3582 }
3583
3584 // data defined buffer size?
3585 double bufrSize = buffer.size();
3586 if ( dataDefinedValEval( DDDoublePos, QgsPalLayerSettings::Property::BufferSize, exprVal, context.expressionContext(), buffer.size() ) )
3587 {
3588 bufrSize = exprVal.toDouble();
3589 }
3590
3591 // data defined buffer transparency?
3592 double bufferOpacity = buffer.opacity() * 100;
3593 if ( dataDefinedValEval( DDOpacity, QgsPalLayerSettings::Property::BufferOpacity, exprVal, context.expressionContext(), bufferOpacity ) )
3594 {
3595 bufferOpacity = exprVal.toDouble();
3596 }
3597
3598 drawBuffer = ( drawBuffer && bufrSize > 0.0 && bufferOpacity > 0 );
3599
3600 if ( !drawBuffer )
3601 {
3602 dataDefinedValues.insert( QgsPalLayerSettings::Property::BufferDraw, QVariant( false ) ); // trigger value
3603 dataDefinedValues.remove( QgsPalLayerSettings::Property::BufferSize );
3604 dataDefinedValues.remove( QgsPalLayerSettings::Property::BufferOpacity );
3605 return; // don't bother evaluating values that won't be used
3606 }
3607
3608 // data defined buffer units?
3609 dataDefinedValEval( DDUnits, QgsPalLayerSettings::Property::BufferUnit, exprVal, context.expressionContext() );
3610
3611 // data defined buffer color?
3612 dataDefinedValEval( DDColor, QgsPalLayerSettings::Property::BufferColor, exprVal, context.expressionContext(), QgsColorUtils::colorToString( buffer.color() ) );
3613
3614 // data defined buffer pen join style?
3615 dataDefinedValEval( DDJoinStyle, QgsPalLayerSettings::Property::BufferJoinStyle, exprVal, context.expressionContext(), QgsSymbolLayerUtils::encodePenJoinStyle( buffer.joinStyle() ) );
3616
3617 // data defined buffer blend mode?
3618 dataDefinedValEval( DDBlendMode, QgsPalLayerSettings::Property::BufferBlendMode, exprVal, context.expressionContext() );
3619}
3620
3621void QgsPalLayerSettings::parseTextMask( QgsRenderContext &context )
3622{
3623 QVariant exprVal; // value() is repeatedly nulled on data defined evaluation and replaced when successful
3624
3625 QgsTextMaskSettings mask = mFormat.mask();
3626
3627 // data defined enabled mask?
3628 bool maskEnabled = mask.enabled();
3629 if ( dataDefinedValEval( DDBool, QgsPalLayerSettings::Property::MaskEnabled, exprVal, context.expressionContext(), mask.enabled() ) )
3630 {
3631 maskEnabled = exprVal.toBool();
3632 }
3633
3634 if ( !maskEnabled )
3635 {
3636 return;
3637 }
3638
3639 // data defined buffer size?
3640 double bufrSize = mask.size();
3641 if ( dataDefinedValEval( DDDoublePos, QgsPalLayerSettings::Property::MaskBufferSize, exprVal, context.expressionContext(), mask.size() ) )
3642 {
3643 bufrSize = exprVal.toDouble();
3644 }
3645
3646 // data defined opacity?
3647 double opacity = mask.opacity() * 100;
3648 if ( dataDefinedValEval( DDOpacity, QgsPalLayerSettings::Property::MaskOpacity, exprVal, context.expressionContext(), opacity ) )
3649 {
3650 opacity = exprVal.toDouble();
3651 }
3652
3653 maskEnabled = ( maskEnabled && bufrSize > 0.0 && opacity > 0 );
3654
3655 if ( !maskEnabled )
3656 {
3657 dataDefinedValues.insert( QgsPalLayerSettings::Property::MaskEnabled, QVariant( false ) ); // trigger value
3658 dataDefinedValues.remove( QgsPalLayerSettings::Property::MaskBufferSize );
3659 dataDefinedValues.remove( QgsPalLayerSettings::Property::MaskOpacity );
3660 return; // don't bother evaluating values that won't be used
3661 }
3662
3663 // data defined buffer units?
3664 dataDefinedValEval( DDUnits, QgsPalLayerSettings::Property::MaskBufferUnit, exprVal, context.expressionContext() );
3665
3666 // data defined buffer pen join style?
3667 dataDefinedValEval( DDJoinStyle, QgsPalLayerSettings::Property::MaskJoinStyle, exprVal, context.expressionContext(), QgsSymbolLayerUtils::encodePenJoinStyle( mask.joinStyle() ) );
3668}
3669
3670void QgsPalLayerSettings::parseTextFormatting( QgsRenderContext &context )
3671{
3672 QVariant exprVal; // value() is repeatedly nulled on data defined evaluation and replaced when successful
3673
3674 // data defined multiline wrap character?
3675 QString wrapchr = wrapChar;
3676 if ( dataDefinedValEval( DDString, QgsPalLayerSettings::Property::MultiLineWrapChar, exprVal, context.expressionContext(), wrapChar ) )
3677 {
3678 wrapchr = exprVal.toString();
3679 }
3680
3681 int evalAutoWrapLength = autoWrapLength;
3682 if ( dataDefinedValEval( DDInt, QgsPalLayerSettings::Property::AutoWrapLength, exprVal, context.expressionContext(), evalAutoWrapLength ) )
3683 {
3684 evalAutoWrapLength = exprVal.toInt();
3685 }
3686
3687 // data defined multiline height?
3688 dataDefinedValEval( DDDouble, QgsPalLayerSettings::Property::MultiLineHeight, exprVal, context.expressionContext() );
3689
3690 // data defined multiline text align?
3691 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::MultiLineAlignment ) )
3692 {
3693 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Property::MultiLineAlignment, context.expressionContext() );
3694 if ( !QgsVariantUtils::isNull( exprVal ) )
3695 {
3696 QString str = exprVal.toString().trimmed();
3697 QgsDebugMsgLevel( QStringLiteral( "exprVal MultiLineAlignment:%1" ).arg( str ), 4 );
3698
3699 if ( !str.isEmpty() )
3700 {
3701 // "Left"
3703
3704 if ( str.compare( QLatin1String( "Center" ), Qt::CaseInsensitive ) == 0 )
3705 {
3707 }
3708 else if ( str.compare( QLatin1String( "Right" ), Qt::CaseInsensitive ) == 0 )
3709 {
3711 }
3712 else if ( str.compare( QLatin1String( "Follow" ), Qt::CaseInsensitive ) == 0 )
3713 {
3715 }
3716 else if ( str.compare( QLatin1String( "Justify" ), Qt::CaseInsensitive ) == 0 )
3717 {
3719 }
3720 dataDefinedValues.insert( QgsPalLayerSettings::Property::MultiLineAlignment, QVariant( static_cast< int >( aligntype ) ) );
3721 }
3722 }
3723 }
3724
3725 // text orientation
3726 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::TextOrientation ) )
3727 {
3728 const QString encoded = QgsTextRendererUtils::encodeTextOrientation( mFormat.orientation() );
3729 context.expressionContext().setOriginalValueVariable( encoded );
3730 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Property::TextOrientation, context.expressionContext() );
3731 if ( !QgsVariantUtils::isNull( exprVal ) )
3732 {
3733 QString str = exprVal.toString().trimmed();
3734 if ( !str.isEmpty() )
3735 dataDefinedValues.insert( QgsPalLayerSettings::Property::TextOrientation, str );
3736 }
3737 }
3738
3739 // data defined direction symbol?
3740 bool drawDirSymb = mLineSettings.addDirectionSymbol();
3741 if ( dataDefinedValEval( DDBool, QgsPalLayerSettings::Property::DirSymbDraw, exprVal, context.expressionContext(), drawDirSymb ) )
3742 {
3743 drawDirSymb = exprVal.toBool();
3744 }
3745
3746 if ( drawDirSymb )
3747 {
3748 // data defined direction left symbol?
3749 dataDefinedValEval( DDString, QgsPalLayerSettings::Property::DirSymbLeft, exprVal, context.expressionContext(), mLineSettings.leftDirectionSymbol() );
3750
3751 // data defined direction right symbol?
3752 dataDefinedValEval( DDString, QgsPalLayerSettings::Property::DirSymbRight, exprVal, context.expressionContext(), mLineSettings.rightDirectionSymbol() );
3753
3754 // data defined direction symbol placement?
3755 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Property::DirSymbPlacement, context.expressionContext() );
3756 if ( !QgsVariantUtils::isNull( exprVal ) )
3757 {
3758 QString str = exprVal.toString().trimmed();
3759 QgsDebugMsgLevel( QStringLiteral( "exprVal DirSymbPlacement:%1" ).arg( str ), 4 );
3760
3761 if ( !str.isEmpty() )
3762 {
3763 // "LeftRight"
3765
3766 if ( str.compare( QLatin1String( "Above" ), Qt::CaseInsensitive ) == 0 )
3767 {
3769 }
3770 else if ( str.compare( QLatin1String( "Below" ), Qt::CaseInsensitive ) == 0 )
3771 {
3773 }
3774 dataDefinedValues.insert( QgsPalLayerSettings::Property::DirSymbPlacement, QVariant( static_cast< int >( placetype ) ) );
3775 }
3776 }
3777
3778 // data defined direction symbol reversed?
3779 dataDefinedValEval( DDBool, QgsPalLayerSettings::Property::DirSymbReverse, exprVal, context.expressionContext(), mLineSettings.reverseDirectionSymbol() );
3780 }
3781
3782 // formatting for numbers is inline with generation of base label text and not passed to label painting
3783}
3784
3785void QgsPalLayerSettings::parseShapeBackground( QgsRenderContext &context )
3786{
3787 QVariant exprVal; // value() is repeatedly nulled on data defined evaluation and replaced when successful
3788
3789 QgsTextBackgroundSettings background = mFormat.background();
3790
3791 // data defined draw shape?
3792 bool drawShape = background.enabled();
3793 if ( dataDefinedValEval( DDBool, QgsPalLayerSettings::Property::ShapeDraw, exprVal, context.expressionContext(), drawShape ) )
3794 {
3795 drawShape = exprVal.toBool();
3796 }
3797
3798 if ( !drawShape )
3799 {
3800 return;
3801 }
3802
3803 // data defined shape transparency?
3804 double shapeOpacity = background.opacity() * 100;
3805 if ( dataDefinedValEval( DDOpacity, QgsPalLayerSettings::Property::ShapeOpacity, exprVal, context.expressionContext(), shapeOpacity ) )
3806 {
3807 shapeOpacity = 100.0 * exprVal.toDouble();
3808 }
3809
3810 drawShape = ( drawShape && shapeOpacity > 0 ); // size is not taken into account (could be)
3811
3812 if ( !drawShape )
3813 {
3814 dataDefinedValues.insert( QgsPalLayerSettings::Property::ShapeDraw, QVariant( false ) ); // trigger value
3815 dataDefinedValues.remove( QgsPalLayerSettings::Property::ShapeOpacity );
3816 return; // don't bother evaluating values that won't be used
3817 }
3818
3819 // data defined shape kind?
3820 QgsTextBackgroundSettings::ShapeType shapeKind = background.type();
3821 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::ShapeKind ) )
3822 {
3823 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Property::ShapeKind, context.expressionContext() );
3824 if ( !QgsVariantUtils::isNull( exprVal ) )
3825 {
3826 QString skind = exprVal.toString().trimmed();
3827 QgsDebugMsgLevel( QStringLiteral( "exprVal ShapeKind:%1" ).arg( skind ), 4 );
3828
3829 if ( !skind.isEmpty() )
3830 {
3831 shapeKind = QgsTextRendererUtils::decodeShapeType( skind );
3832 dataDefinedValues.insert( QgsPalLayerSettings::Property::ShapeKind, QVariant( static_cast< int >( shapeKind ) ) );
3833 }
3834 }
3835 }
3836
3837 // data defined shape SVG path?
3838 QString svgPath = background.svgFile();
3839 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::ShapeSVGFile ) )
3840 {
3841 context.expressionContext().setOriginalValueVariable( svgPath );
3842 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Property::ShapeSVGFile, context.expressionContext() );
3843 if ( !QgsVariantUtils::isNull( exprVal ) )
3844 {
3845 QString svgfile = exprVal.toString().trimmed();
3846 QgsDebugMsgLevel( QStringLiteral( "exprVal ShapeSVGFile:%1" ).arg( svgfile ), 4 );
3847
3848 // '' empty paths are allowed
3849 svgPath = QgsSymbolLayerUtils::svgSymbolNameToPath( svgfile, context.pathResolver() );
3850 dataDefinedValues.insert( QgsPalLayerSettings::Property::ShapeSVGFile, QVariant( svgPath ) );
3851 }
3852 }
3853
3854 // data defined shape size type?
3855 QgsTextBackgroundSettings::SizeType shpSizeType = background.sizeType();
3856 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::ShapeSizeType ) )
3857 {
3858 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Property::ShapeSizeType, context.expressionContext() );
3859 if ( !QgsVariantUtils::isNull( exprVal ) )
3860 {
3861 QString stype = exprVal.toString().trimmed();
3862 QgsDebugMsgLevel( QStringLiteral( "exprVal ShapeSizeType:%1" ).arg( stype ), 4 );
3863
3864 if ( !stype.isEmpty() )
3865 {
3867 dataDefinedValues.insert( QgsPalLayerSettings::Property::ShapeSizeType, QVariant( static_cast< int >( shpSizeType ) ) );
3868 }
3869 }
3870 }
3871
3872 // data defined shape size X? (SVGs only use X for sizing)
3873 double ddShpSizeX = background.size().width();
3874 if ( dataDefinedValEval( DDDouble, QgsPalLayerSettings::Property::ShapeSizeX, exprVal, context.expressionContext(), ddShpSizeX ) )
3875 {
3876 ddShpSizeX = exprVal.toDouble();
3877 }
3878
3879 // data defined shape size Y?
3880 double ddShpSizeY = background.size().height();
3881 if ( dataDefinedValEval( DDDouble, QgsPalLayerSettings::Property::ShapeSizeY, exprVal, context.expressionContext(), ddShpSizeY ) )
3882 {
3883 ddShpSizeY = exprVal.toDouble();
3884 }
3885
3886 // don't continue under certain circumstances (e.g. size is fixed)
3887 bool skip = false;
3888 if ( shapeKind == QgsTextBackgroundSettings::ShapeSVG
3889 && ( svgPath.isEmpty()
3890 || ( !svgPath.isEmpty()
3891 && shpSizeType == QgsTextBackgroundSettings::SizeFixed
3892 && ddShpSizeX == 0.0 ) ) )
3893 {
3894 skip = true;
3895 }
3897 && ( !background.markerSymbol()
3898 || ( background.markerSymbol()
3899 && shpSizeType == QgsTextBackgroundSettings::SizeFixed
3900 && ddShpSizeX == 0.0 ) ) )
3901 {
3902 skip = true;
3903 }
3904 if ( shapeKind != QgsTextBackgroundSettings::ShapeSVG
3906 && shpSizeType == QgsTextBackgroundSettings::SizeFixed
3907 && ( ddShpSizeX == 0.0 || ddShpSizeY == 0.0 ) )
3908 {
3909 skip = true;
3910 }
3911
3912 if ( skip )
3913 {
3914 dataDefinedValues.insert( QgsPalLayerSettings::Property::ShapeDraw, QVariant( false ) ); // trigger value
3915 dataDefinedValues.remove( QgsPalLayerSettings::Property::ShapeOpacity );
3916 dataDefinedValues.remove( QgsPalLayerSettings::Property::ShapeKind );
3917 dataDefinedValues.remove( QgsPalLayerSettings::Property::ShapeSVGFile );
3918 dataDefinedValues.remove( QgsPalLayerSettings::Property::ShapeSizeX );
3919 dataDefinedValues.remove( QgsPalLayerSettings::Property::ShapeSizeY );
3920 return; // don't bother evaluating values that won't be used
3921 }
3922
3923 // data defined shape size units?
3924 dataDefinedValEval( DDUnits, QgsPalLayerSettings::Property::ShapeSizeUnits, exprVal, context.expressionContext() );
3925
3926 // data defined shape rotation type?
3927 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::ShapeRotationType ) )
3928 {
3929 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Property::ShapeRotationType, context.expressionContext() );
3930 if ( !QgsVariantUtils::isNull( exprVal ) )
3931 {
3932 QString rotstr = exprVal.toString().trimmed();
3933 QgsDebugMsgLevel( QStringLiteral( "exprVal ShapeRotationType:%1" ).arg( rotstr ), 4 );
3934
3935 if ( !rotstr.isEmpty() )
3936 {
3937 // "Sync"
3939 dataDefinedValues.insert( QgsPalLayerSettings::Property::ShapeRotationType, QVariant( static_cast< int >( rottype ) ) );
3940 }
3941 }
3942 }
3943
3944 // data defined shape rotation?
3945 dataDefinedValEval( DDRotation180, QgsPalLayerSettings::Property::ShapeRotation, exprVal, context.expressionContext(), background.rotation() );
3946
3947 // data defined shape offset?
3948 dataDefinedValEval( DDPointF, QgsPalLayerSettings::Property::ShapeOffset, exprVal, context.expressionContext(), QgsSymbolLayerUtils::encodePoint( background.offset() ) );
3949
3950 // data defined shape offset units?
3951 dataDefinedValEval( DDUnits, QgsPalLayerSettings::Property::ShapeOffsetUnits, exprVal, context.expressionContext() );
3952
3953 // data defined shape radii?
3954 dataDefinedValEval( DDSizeF, QgsPalLayerSettings::Property::ShapeRadii, exprVal, context.expressionContext(), QgsSymbolLayerUtils::encodeSize( background.radii() ) );
3955
3956 // data defined shape radii units?
3957 dataDefinedValEval( DDUnits, QgsPalLayerSettings::Property::ShapeRadiiUnits, exprVal, context.expressionContext() );
3958
3959 // data defined shape blend mode?
3960 dataDefinedValEval( DDBlendMode, QgsPalLayerSettings::Property::ShapeBlendMode, exprVal, context.expressionContext() );
3961
3962 // data defined shape fill color?
3963 dataDefinedValEval( DDColor, QgsPalLayerSettings::Property::ShapeFillColor, exprVal, context.expressionContext(), QgsColorUtils::colorToString( background.fillColor() ) );
3964
3965 // data defined shape stroke color?
3966 dataDefinedValEval( DDColor, QgsPalLayerSettings::Property::ShapeStrokeColor, exprVal, context.expressionContext(), QgsColorUtils::colorToString( background.strokeColor() ) );
3967
3968 // data defined shape stroke width?
3969 dataDefinedValEval( DDDoublePos, QgsPalLayerSettings::Property::ShapeStrokeWidth, exprVal, context.expressionContext(), background.strokeWidth() );
3970
3971 // data defined shape stroke width units?
3972 dataDefinedValEval( DDUnits, QgsPalLayerSettings::Property::ShapeStrokeWidthUnits, exprVal, context.expressionContext() );
3973
3974 // data defined shape join style?
3975 dataDefinedValEval( DDJoinStyle, QgsPalLayerSettings::Property::ShapeJoinStyle, exprVal, context.expressionContext(), QgsSymbolLayerUtils::encodePenJoinStyle( background.joinStyle() ) );
3976
3977}
3978
3979void QgsPalLayerSettings::parseDropShadow( QgsRenderContext &context )
3980{
3981 QVariant exprVal; // value() is repeatedly nulled on data defined evaluation and replaced when successful
3982
3983 QgsTextShadowSettings shadow = mFormat.shadow();
3984
3985 // data defined draw shadow?
3986 bool drawShadow = shadow.enabled();
3987 if ( dataDefinedValEval( DDBool, QgsPalLayerSettings::Property::ShadowDraw, exprVal, context.expressionContext(), drawShadow ) )
3988 {
3989 drawShadow = exprVal.toBool();
3990 }
3991
3992 if ( !drawShadow )
3993 {
3994 return;
3995 }
3996
3997 // data defined shadow transparency?
3998 double shadowOpacity = shadow.opacity() * 100;
3999 if ( dataDefinedValEval( DDOpacity, QgsPalLayerSettings::Property::ShadowOpacity, exprVal, context.expressionContext(), shadowOpacity ) )
4000 {
4001 shadowOpacity = exprVal.toDouble();
4002 }
4003
4004 // data defined shadow offset distance?
4005 double shadowOffDist = shadow.offsetDistance();
4006 if ( dataDefinedValEval( DDDoublePos, QgsPalLayerSettings::Property::ShadowOffsetDist, exprVal, context.expressionContext(), shadowOffDist ) )
4007 {
4008 shadowOffDist = exprVal.toDouble();
4009 }
4010
4011 // data defined shadow offset distance?
4012 double shadowRad = shadow.blurRadius();
4013 if ( dataDefinedValEval( DDDoublePos, QgsPalLayerSettings::Property::ShadowRadius, exprVal, context.expressionContext(), shadowRad ) )
4014 {
4015 shadowRad = exprVal.toDouble();
4016 }
4017
4018 drawShadow = ( drawShadow && shadowOpacity > 0 && !( shadowOffDist == 0.0 && shadowRad == 0.0 ) );
4019
4020 if ( !drawShadow )
4021 {
4022 dataDefinedValues.insert( QgsPalLayerSettings::Property::ShadowDraw, QVariant( false ) ); // trigger value
4023 dataDefinedValues.remove( QgsPalLayerSettings::Property::ShadowOpacity );
4024 dataDefinedValues.remove( QgsPalLayerSettings::Property::ShadowOffsetDist );
4025 dataDefinedValues.remove( QgsPalLayerSettings::Property::ShadowRadius );
4026 return; // don't bother evaluating values that won't be used
4027 }
4028
4029 // data defined shadow under type?
4030 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::ShadowUnder ) )
4031 {
4032 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Property::ShadowUnder, context.expressionContext() );
4033 if ( !QgsVariantUtils::isNull( exprVal ) )
4034 {
4035 QString str = exprVal.toString().trimmed();
4036 QgsDebugMsgLevel( QStringLiteral( "exprVal ShadowUnder:%1" ).arg( str ), 4 );
4037
4038 if ( !str.isEmpty() )
4039 {
4041 dataDefinedValues.insert( QgsPalLayerSettings::Property::ShadowUnder, QVariant( static_cast< int >( shdwtype ) ) );
4042 }
4043 }
4044 }
4045
4046 // data defined shadow offset angle?
4047 dataDefinedValEval( DDRotation180, QgsPalLayerSettings::Property::ShadowOffsetAngle, exprVal, context.expressionContext(), shadow.offsetAngle() );
4048
4049 // data defined shadow offset units?
4050 dataDefinedValEval( DDUnits, QgsPalLayerSettings::Property::ShadowOffsetUnits, exprVal, context.expressionContext() );
4051
4052 // data defined shadow radius?
4053 dataDefinedValEval( DDDouble, QgsPalLayerSettings::Property::ShadowRadius, exprVal, context.expressionContext(), shadow.blurRadius() );
4054
4055 // data defined shadow radius units?
4056 dataDefinedValEval( DDUnits, QgsPalLayerSettings::Property::ShadowRadiusUnits, exprVal, context.expressionContext() );
4057
4058 // data defined shadow scale? ( gui bounds to 0-2000, no upper bound here )
4059 dataDefinedValEval( DDIntPos, QgsPalLayerSettings::Property::ShadowScale, exprVal, context.expressionContext(), shadow.scale() );
4060
4061 // data defined shadow color?
4062 dataDefinedValEval( DDColor, QgsPalLayerSettings::Property::ShadowColor, exprVal, context.expressionContext(), QgsColorUtils::colorToString( shadow.color() ) );
4063
4064 // data defined shadow blend mode?
4065 dataDefinedValEval( DDBlendMode, QgsPalLayerSettings::Property::ShadowBlendMode, exprVal, context.expressionContext() );
4066}
4067
4068// -------------
4069
4070
4072{
4073 switch ( layer->type() )
4074 {
4076 {
4077 const QgsVectorLayer *vl = qobject_cast< const QgsVectorLayer * >( layer );
4078 return vl->labelsEnabled() || vl->diagramsEnabled() || ( vl->renderer() && vl->renderer()->flags().testFlag( Qgis::FeatureRendererFlag::AffectsLabeling ) );
4079 }
4080
4082 {
4083 const QgsVectorTileLayer *vl = qobject_cast< const QgsVectorTileLayer * >( layer );
4084 if ( !vl->labeling() )
4085 return false;
4086
4087 if ( const QgsVectorTileBasicLabeling *labeling = dynamic_cast< const QgsVectorTileBasicLabeling *>( vl->labeling() ) )
4088 return !labeling->styles().empty();
4089
4090 return false;
4091 }
4092
4100 return false;
4101 }
4102 return false;
4103}
4104
4105
4106bool QgsPalLabeling::geometryRequiresPreparation( const QgsGeometry &geometry, QgsRenderContext &context, const QgsCoordinateTransform &ct, const QgsGeometry &clipGeometry, bool mergeLines )
4107{
4108 if ( geometry.isNull() )
4109 {
4110 return false;
4111 }
4112
4113 if ( geometry.type() == Qgis::GeometryType::Line && geometry.isMultipart() && mergeLines )
4114 {
4115 return true;
4116 }
4117
4118 //requires reprojection
4119 if ( ct.isValid() && !ct.isShortCircuited() )
4120 return true;
4121
4122 //requires rotation
4123 const QgsMapToPixel &m2p = context.mapToPixel();
4124 if ( !qgsDoubleNear( m2p.mapRotation(), 0 ) )
4125 return true;
4126
4127 //requires clip
4128 if ( !clipGeometry.isNull() && !clipGeometry.boundingBox().contains( geometry.boundingBox() ) )
4129 return true;
4130
4131 //requires fixing
4132 if ( geometry.type() == Qgis::GeometryType::Polygon && !geometry.isGeosValid() )
4133 return true;
4134
4135 return false;
4136}
4137
4138QStringList QgsPalLabeling::splitToLines( const QString &text, const QString &wrapCharacter, const int autoWrapLength, const bool useMaxLineLengthWhenAutoWrapping )
4139{
4140 QStringList multiLineSplit;
4141 if ( !wrapCharacter.isEmpty() && wrapCharacter != QLatin1String( "\n" ) )
4142 {
4143 //wrap on both the wrapchr and new line characters
4144 const QStringList lines = text.split( wrapCharacter );
4145 for ( const QString &line : lines )
4146 {
4147 multiLineSplit.append( line.split( '\n' ) );
4148 }
4149 }
4150 else
4151 {
4152 multiLineSplit = text.split( '\n' );
4153 }
4154
4155 // apply auto wrapping to each manually created line
4156 if ( autoWrapLength != 0 )
4157 {
4158 QStringList autoWrappedLines;
4159 autoWrappedLines.reserve( multiLineSplit.count() );
4160 for ( const QString &line : std::as_const( multiLineSplit ) )
4161 {
4162 autoWrappedLines.append( QgsStringUtils::wordWrap( line, autoWrapLength, useMaxLineLengthWhenAutoWrapping ).split( '\n' ) );
4163 }
4164 multiLineSplit = autoWrappedLines;
4165 }
4166 return multiLineSplit;
4167}
4168
4169QStringList QgsPalLabeling::splitToGraphemes( const QString &text )
4170{
4171 QStringList graphemes;
4172 QTextBoundaryFinder boundaryFinder( QTextBoundaryFinder::Grapheme, text );
4173 int currentBoundary = -1;
4174 int previousBoundary = 0;
4175 while ( ( currentBoundary = boundaryFinder.toNextBoundary() ) > 0 )
4176 {
4177 graphemes << text.mid( previousBoundary, currentBoundary - previousBoundary );
4178 previousBoundary = currentBoundary;
4179 }
4180 return graphemes;
4181}
4182
4183QgsGeometry QgsPalLabeling::prepareGeometry( const QgsGeometry &geometry, QgsRenderContext &context, const QgsCoordinateTransform &ct, const QgsGeometry &clipGeometry, bool mergeLines )
4184{
4185 if ( geometry.isNull() )
4186 {
4187 return QgsGeometry();
4188 }
4189
4190 //don't modify the feature's geometry so that geometry based expressions keep working
4191 QgsGeometry geom = geometry;
4192
4193 if ( geom.type() == Qgis::GeometryType::Line && geom.isMultipart() && mergeLines )
4194 {
4195 geom = geom.mergeLines();
4196 }
4197
4198 //reproject the geometry if necessary
4199 if ( ct.isValid() && !ct.isShortCircuited() )
4200 {
4201 try
4202 {
4203 geom.transform( ct );
4204 }
4205 catch ( QgsCsException &cse )
4206 {
4207 Q_UNUSED( cse )
4208 QgsDebugMsgLevel( QStringLiteral( "Ignoring feature due to transformation exception" ), 4 );
4209 return QgsGeometry();
4210 }
4211 // geometry transforms may result in nan points, remove these
4212 geom.filterVertices( []( const QgsPoint & point )->bool
4213 {
4214 return std::isfinite( point.x() ) && std::isfinite( point.y() );
4215 } );
4216 if ( QgsCurvePolygon *cp = qgsgeometry_cast< QgsCurvePolygon * >( geom.get() ) )
4217 {
4218 cp->removeInvalidRings();
4219 }
4220 else if ( QgsMultiSurface *ms = qgsgeometry_cast< QgsMultiSurface * >( geom.get() ) )
4221 {
4222 for ( int i = 0; i < ms->numGeometries(); ++i )
4223 {
4224 if ( QgsCurvePolygon *cp = qgsgeometry_cast< QgsCurvePolygon * >( ms->geometryN( i ) ) )
4225 cp->removeInvalidRings();
4226 }
4227 }
4228 }
4229
4230 // Rotate the geometry if needed, before clipping
4231 const QgsMapToPixel &m2p = context.mapToPixel();
4232 if ( !qgsDoubleNear( m2p.mapRotation(), 0 ) )
4233 {
4234 QgsPointXY center = context.mapExtent().center();
4235 if ( geom.rotate( m2p.mapRotation(), center ) != Qgis::GeometryOperationResult::Success )
4236 {
4237 QgsDebugError( QStringLiteral( "Error rotating geometry" ).arg( geom.asWkt() ) );
4238 return QgsGeometry();
4239 }
4240 }
4241
4242 const bool mustClip = ( !clipGeometry.isNull() &&
4243 ( ( qgsDoubleNear( m2p.mapRotation(), 0 ) && !clipGeometry.boundingBox().contains( geom.boundingBox() ) )
4244 || ( !qgsDoubleNear( m2p.mapRotation(), 0 ) && !clipGeometry.contains( geom ) ) ) );
4245
4246 bool mustClipExact = false;
4247 if ( mustClip )
4248 {
4249 // nice and fast, but can result in invalid geometries. At least it will potentially strip out a bunch of unwanted vertices upfront!
4250 QgsGeometry clipGeom = geom.clipped( clipGeometry.boundingBox() );
4251 if ( clipGeom.isEmpty() )
4252 return QgsGeometry();
4253
4254 geom = clipGeom;
4255
4256 // we've now clipped against the BOUNDING BOX of clipGeometry. But if clipGeometry is an axis parallel rectangle, then there's no
4257 // need to do an exact (potentially costly) intersection clip as well!
4258 mustClipExact = !clipGeometry.isAxisParallelRectangle( 0.001 );
4259 }
4260
4261 // fix invalid polygons
4262 if ( geom.type() == Qgis::GeometryType::Polygon )
4263 {
4264 if ( geom.isMultipart() )
4265 {
4266 // important -- we need to treat ever part in isolation here. We can't test the validity of the whole geometry
4267 // at once, because touching parts would result in an invalid geometry, and buffering this "dissolves" the parts.
4268 // because the actual label engine treats parts as separate entities, we aren't bound by the usual "touching parts are invalid" rule
4269 // see https://github.com/qgis/QGIS/issues/26763
4270 QVector< QgsGeometry> parts;
4271 parts.reserve( qgsgeometry_cast< const QgsGeometryCollection * >( geom.constGet() )->numGeometries() );
4272 for ( auto it = geom.const_parts_begin(); it != geom.const_parts_end(); ++it )
4273 {
4274 QgsGeometry partGeom( ( *it )->clone() );
4275 if ( !partGeom.isGeosValid() )
4276 {
4277
4278 partGeom = partGeom.makeValid();
4279 }
4280 parts.append( partGeom );
4281 }
4282 geom = QgsGeometry::collectGeometry( parts );
4283 }
4284 else if ( !geom.isGeosValid() )
4285 {
4286
4287 QgsGeometry bufferGeom = geom.makeValid();
4288 if ( bufferGeom.isNull() )
4289 {
4290 QgsDebugError( QStringLiteral( "Could not repair geometry: %1" ).arg( bufferGeom.lastError() ) );
4291 return QgsGeometry();
4292 }
4293 geom = bufferGeom;
4294 }
4295 }
4296
4297 if ( mustClipExact )
4298 {
4299 // now do the real intersection against the actual clip geometry
4300 QgsGeometry clipGeom = geom.intersection( clipGeometry );
4301 if ( clipGeom.isEmpty() )
4302 {
4303 return QgsGeometry();
4304 }
4305 geom = clipGeom;
4306 }
4307
4308 return geom;
4309}
4310
4311bool QgsPalLabeling::checkMinimumSizeMM( const QgsRenderContext &context, const QgsGeometry &geom, double minSize )
4312{
4313 if ( minSize <= 0 )
4314 {
4315 return true;
4316 }
4317
4318 if ( geom.isNull() )
4319 {
4320 return false;
4321 }
4322
4323 Qgis::GeometryType featureType = geom.type();
4324 if ( featureType == Qgis::GeometryType::Point ) //minimum size does not apply to point features
4325 {
4326 return true;
4327 }
4328
4329 double mapUnitsPerMM = context.mapToPixel().mapUnitsPerPixel() * context.scaleFactor();
4330 if ( featureType == Qgis::GeometryType::Line )
4331 {
4332 double length = geom.length();
4333 if ( length >= 0.0 )
4334 {
4335 return ( length >= ( minSize * mapUnitsPerMM ) );
4336 }
4337 }
4338 else if ( featureType == Qgis::GeometryType::Polygon )
4339 {
4340 double area = geom.area();
4341 if ( area >= 0.0 )
4342 {
4343 return ( std::sqrt( area ) >= ( minSize * mapUnitsPerMM ) );
4344 }
4345 }
4346 return true; //should never be reached. Return true in this case to label such geometries anyway.
4347}
4348
4349
4350void QgsPalLabeling::dataDefinedTextStyle( QgsPalLayerSettings &tmpLyr,
4351 const QMap< QgsPalLayerSettings::Property, QVariant > &ddValues )
4352{
4353 QgsTextFormat format = tmpLyr.format();
4354 bool changed = false;
4355
4356 //font color
4357 if ( ddValues.contains( QgsPalLayerSettings::Property::Color ) )
4358 {
4359 QVariant ddColor = ddValues.value( QgsPalLayerSettings::Property::Color );
4360 format.setColor( ddColor.value<QColor>() );
4361 changed = true;
4362 }
4363
4364 //font transparency
4365 if ( ddValues.contains( QgsPalLayerSettings::Property::FontOpacity ) )
4366 {
4367 format.setOpacity( ddValues.value( QgsPalLayerSettings::Property::FontOpacity ).toDouble() / 100.0 );
4368 changed = true;
4369 }
4370
4371 //font blend mode
4372 if ( ddValues.contains( QgsPalLayerSettings::Property::FontBlendMode ) )
4373 {
4374 format.setBlendMode( static_cast< QPainter::CompositionMode >( ddValues.value( QgsPalLayerSettings::Property::FontBlendMode ).toInt() ) );
4375 changed = true;
4376 }
4377
4378 if ( changed )
4379 {
4380 tmpLyr.setFormat( format );
4381 }
4382}
4383
4384void QgsPalLabeling::dataDefinedTextFormatting( QgsPalLayerSettings &tmpLyr,
4385 const QMap< QgsPalLayerSettings::Property, QVariant > &ddValues )
4386{
4387 if ( ddValues.contains( QgsPalLayerSettings::Property::MultiLineWrapChar ) )
4388 {
4389 tmpLyr.wrapChar = ddValues.value( QgsPalLayerSettings::Property::MultiLineWrapChar ).toString();
4390 }
4391
4392 if ( ddValues.contains( QgsPalLayerSettings::Property::AutoWrapLength ) )
4393 {
4394 tmpLyr.autoWrapLength = ddValues.value( QgsPalLayerSettings::Property::AutoWrapLength ).toInt();
4395 }
4396
4397 if ( ddValues.contains( QgsPalLayerSettings::Property::MultiLineHeight ) )
4398 {
4399 QgsTextFormat format = tmpLyr.format();
4400 format.setLineHeight( ddValues.value( QgsPalLayerSettings::Property::MultiLineHeight ).toDouble() );
4401 tmpLyr.setFormat( format );
4402 }
4403
4404 if ( ddValues.contains( QgsPalLayerSettings::Property::MultiLineAlignment ) )
4405 {
4406 tmpLyr.multilineAlign = static_cast< Qgis::LabelMultiLineAlignment >( ddValues.value( QgsPalLayerSettings::Property::MultiLineAlignment ).toInt() );
4407 }
4408
4409 if ( ddValues.contains( QgsPalLayerSettings::Property::TextOrientation ) )
4410 {
4411 QgsTextFormat format = tmpLyr.format();
4413 tmpLyr.setFormat( format );
4414 }
4415
4416 if ( ddValues.contains( QgsPalLayerSettings::Property::DirSymbDraw ) )
4417 {
4419 }
4420
4421 if ( tmpLyr.lineSettings().addDirectionSymbol() )
4422 {
4423
4424 if ( ddValues.contains( QgsPalLayerSettings::Property::DirSymbLeft ) )
4425 {
4427 }
4428 if ( ddValues.contains( QgsPalLayerSettings::Property::DirSymbRight ) )
4429 {
4431 }
4432
4433 if ( ddValues.contains( QgsPalLayerSettings::Property::DirSymbPlacement ) )
4434 {
4436 }
4437
4438 if ( ddValues.contains( QgsPalLayerSettings::Property::DirSymbReverse ) )
4439 {
4441 }
4442
4443 }
4444}
4445
4446void QgsPalLabeling::dataDefinedTextBuffer( QgsPalLayerSettings &tmpLyr,
4447 const QMap< QgsPalLayerSettings::Property, QVariant > &ddValues )
4448{
4449 QgsTextBufferSettings buffer = tmpLyr.format().buffer();
4450 bool changed = false;
4451
4452 //buffer draw
4453 if ( ddValues.contains( QgsPalLayerSettings::Property::BufferDraw ) )
4454 {
4455 buffer.setEnabled( ddValues.value( QgsPalLayerSettings::Property::BufferDraw ).toBool() );
4456 changed = true;
4457 }
4458
4459 if ( !buffer.enabled() )
4460 {
4461 if ( changed )
4462 {
4463 QgsTextFormat format = tmpLyr.format();
4464 format.setBuffer( buffer );
4465 tmpLyr.setFormat( format );
4466 }
4467
4468 // tmpLyr.bufferSize > 0.0 && tmpLyr.bufferTransp < 100 figured in during evaluation
4469 return; // don't continue looking for unused values
4470 }
4471
4472 //buffer size
4473 if ( ddValues.contains( QgsPalLayerSettings::Property::BufferSize ) )
4474 {
4475 buffer.setSize( ddValues.value( QgsPalLayerSettings::Property::BufferSize ).toDouble() );
4476 changed = true;
4477 }
4478
4479 //buffer opacity
4480 if ( ddValues.contains( QgsPalLayerSettings::Property::BufferOpacity ) )
4481 {
4482 buffer.setOpacity( ddValues.value( QgsPalLayerSettings::Property::BufferOpacity ).toDouble() / 100.0 );
4483 changed = true;
4484 }
4485
4486 //buffer size units
4487 if ( ddValues.contains( QgsPalLayerSettings::Property::BufferUnit ) )
4488 {
4489 Qgis::RenderUnit bufunit = static_cast< Qgis::RenderUnit >( ddValues.value( QgsPalLayerSettings::Property::BufferUnit ).toInt() );
4490 buffer.setSizeUnit( bufunit );
4491 changed = true;
4492 }
4493
4494 //buffer color
4495 if ( ddValues.contains( QgsPalLayerSettings::Property::BufferColor ) )
4496 {
4497 QVariant ddColor = ddValues.value( QgsPalLayerSettings::Property::BufferColor );
4498 buffer.setColor( ddColor.value<QColor>() );
4499 changed = true;
4500 }
4501
4502 //buffer pen join style
4503 if ( ddValues.contains( QgsPalLayerSettings::Property::BufferJoinStyle ) )
4504 {
4505 buffer.setJoinStyle( static_cast< Qt::PenJoinStyle >( ddValues.value( QgsPalLayerSettings::Property::BufferJoinStyle ).toInt() ) );
4506 changed = true;
4507 }
4508
4509 //buffer blend mode
4510 if ( ddValues.contains( QgsPalLayerSettings::Property::BufferBlendMode ) )
4511 {
4512 buffer.setBlendMode( static_cast< QPainter::CompositionMode >( ddValues.value( QgsPalLayerSettings::Property::BufferBlendMode ).toInt() ) );
4513 changed = true;
4514 }
4515
4516 if ( changed )
4517 {
4518 QgsTextFormat format = tmpLyr.format();
4519 format.setBuffer( buffer );
4520 tmpLyr.setFormat( format );
4521 }
4522}
4523
4524void QgsPalLabeling::dataDefinedTextMask( QgsPalLayerSettings &tmpLyr,
4525 const QMap< QgsPalLayerSettings::Property, QVariant > &ddValues )
4526{
4527 if ( ddValues.isEmpty() )
4528 return;
4529
4530 QgsTextMaskSettings mask = tmpLyr.format().mask();
4531 bool changed = false;
4532
4533 // enabled ?
4534 if ( ddValues.conta