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