QGIS API Documentation 3.99.0-Master (b3fe4c4eded)
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
20#include <cmath>
21#include <memory>
22
23#include "callouts/qgscallout.h"
25#include "qgsapplication.h"
26#include "qgscolorutils.h"
27#include "qgscurvepolygon.h"
28#include "qgsexception.h"
29#include "qgsexpression.h"
30#include "qgsfontmanager.h"
31#include "qgsfontutils.h"
32#include "qgsgeometry.h"
34#include "qgslabelingengine.h"
35#include "qgslogger.h"
37#include "qgsmeshlayer.h"
38#include "qgsmessagelog.h"
39#include "qgsmultisurface.h"
40#include "qgsproperty.h"
41#include "qgsrasterlayer.h"
43#include "qgsscaleutils.h"
44#include "qgsstyle.h"
45#include "qgssymbollayerutils.h"
46#include "qgstextlabelfeature.h"
47#include "qgstextrenderer.h"
49#include "qgsunittypes.h"
50#include "qgsvariantutils.h"
51#include "qgsvectorlayer.h"
53#include "qgsvectortilelayer.h"
54
55#include <QApplication>
56#include <QByteArray>
57#include <QFontMetrics>
58#include <QMimeData>
59#include <QPainter>
60#include <QScreen>
61#include <QString>
62#include <QTextBoundaryFinder>
63#include <QTime>
64#include <QWidget>
65
66using namespace Qt::StringLiterals;
67
68using namespace pal;
69
70// -------------
71
72/* ND: Default point label position priority. These are set to match variants of the ideal placement priority described
73 in "Making Maps", Krygier & Wood (2011) (p216),
74 "Elements of Cartography", Robinson et al (1995)
75 and "Designing Better Maps", Brewer (2005) (p76)
76 Note that while they agree on positions 1-4, 5-8 are more contentious so I've selected these placements
77 based on my preferences, and to follow Krygier and Wood's placements more closer. (I'm not going to disagree
78 with Denis Wood on anything cartography related...!)
79*/
80typedef QVector< Qgis::LabelPredefinedPointPosition > PredefinedPointPositionVector;
82{
91} ) )
92//debugging only - don't use these placements by default
93/* << static_cast< int >( QgsPalLayerSettings::Property::TopSlightlyLeft )
94<< static_cast< int >( QgsPalLayerSettings::Property::BottomSlightlyLeft );
95<< static_cast< int >( QgsPalLayerSettings::Property::TopMiddle )
96<< static_cast< int >( QgsPalLayerSettings::Property::BottomMiddle );*/
97
98Q_GLOBAL_STATIC( QgsPropertiesDefinition, sPropertyDefinitions )
99
100void QgsPalLayerSettings::initPropertyDefinitions()
101{
102 if ( !sPropertyDefinitions()->isEmpty() )
103 return;
104
105 const QString origin = u"labeling"_s;
106
107 *sPropertyDefinitions() = QgsPropertiesDefinition
108 {
109 { static_cast< int >( QgsPalLayerSettings::Property::Size ), QgsPropertyDefinition( "Size", QObject::tr( "Font size" ), QgsPropertyDefinition::DoublePositive, origin ) },
110 { static_cast< int >( QgsPalLayerSettings::Property::Bold ), QgsPropertyDefinition( "Bold", QObject::tr( "Bold style" ), QgsPropertyDefinition::Boolean, origin ) },
111 { static_cast< int >( QgsPalLayerSettings::Property::Italic ), QgsPropertyDefinition( "Italic", QObject::tr( "Italic style" ), QgsPropertyDefinition::Boolean, origin ) },
112 { static_cast< int >( QgsPalLayerSettings::Property::Underline ), QgsPropertyDefinition( "Underline", QObject::tr( "Draw underline" ), QgsPropertyDefinition::Boolean, origin ) },
113 { static_cast< int >( QgsPalLayerSettings::Property::Color ), QgsPropertyDefinition( "Color", QObject::tr( "Text color" ), QgsPropertyDefinition::ColorNoAlpha, origin ) },
114 { static_cast< int >( QgsPalLayerSettings::Property::Strikeout ), QgsPropertyDefinition( "Strikeout", QObject::tr( "Draw strikeout" ), QgsPropertyDefinition::Boolean, origin ) },
115 {
116 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>"
117 "e.g. Helvetica or Helvetica [Cronyx]" ), origin )
118 },
119 {
120 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>"
121 "e.g. Bold Condensed or Light Italic" ), origin )
122 },
123 { static_cast< int >( QgsPalLayerSettings::Property::FontSizeUnit ), QgsPropertyDefinition( "FontSizeUnit", QObject::tr( "Font size units" ), QgsPropertyDefinition::RenderUnits, origin ) },
124 { static_cast< int >( QgsPalLayerSettings::Property::FontTransp ), QgsPropertyDefinition( "FontTransp", QObject::tr( "Text transparency" ), QgsPropertyDefinition::Opacity, origin ) },
125 { static_cast< int >( QgsPalLayerSettings::Property::FontOpacity ), QgsPropertyDefinition( "FontOpacity", QObject::tr( "Text opacity" ), QgsPropertyDefinition::Opacity, origin ) },
126 { static_cast< int >( QgsPalLayerSettings::Property::FontStretchFactor ), QgsPropertyDefinition( "FontStretchFactor", QObject::tr( "Font stretch factor" ), QgsPropertyDefinition::IntegerPositiveGreaterZero, origin ) },
127 { static_cast< int >( QgsPalLayerSettings::Property::FontCase ), QgsPropertyDefinition( "FontCase", QgsPropertyDefinition::DataTypeString, QObject::tr( "Font case" ), QObject::tr( "string " ) + u"[<b>NoChange</b>|<b>Upper</b>|<br><b>Lower</b>|<b>Title</b>|<b>Capitalize</b>|<b>SmallCaps</b>|<b>AllSmallCaps</b>]"_s, origin ) },
128 { static_cast< int >( QgsPalLayerSettings::Property::FontLetterSpacing ), QgsPropertyDefinition( "FontLetterSpacing", QObject::tr( "Letter spacing" ), QgsPropertyDefinition::Double, origin ) },
129 { static_cast< int >( QgsPalLayerSettings::Property::FontWordSpacing ), QgsPropertyDefinition( "FontWordSpacing", QObject::tr( "Word spacing" ), QgsPropertyDefinition::Double, origin ) },
130 { static_cast< int >( QgsPalLayerSettings::Property::FontBlendMode ), QgsPropertyDefinition( "FontBlendMode", QObject::tr( "Text blend mode" ), QgsPropertyDefinition::BlendMode, origin ) },
131 { static_cast< int >( QgsPalLayerSettings::Property::MultiLineWrapChar ), QgsPropertyDefinition( "MultiLineWrapChar", QObject::tr( "Wrap character" ), QgsPropertyDefinition::String, origin ) },
132 { static_cast< int >( QgsPalLayerSettings::Property::AutoWrapLength ), QgsPropertyDefinition( "AutoWrapLength", QObject::tr( "Automatic word wrap line length" ), QgsPropertyDefinition::IntegerPositive, origin ) },
133 { static_cast< int >( QgsPalLayerSettings::Property::MultiLineHeight ), QgsPropertyDefinition( "MultiLineHeight", QObject::tr( "Line height" ), QgsPropertyDefinition::DoublePositive, origin ) },
134 { 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 ) },
135 { static_cast< int >( QgsPalLayerSettings::Property::TabStopDistance ), QgsPropertyDefinition( "TabStopDistance", QgsPropertyDefinition::DataTypeNumeric, QObject::tr( "Tab stop distance(s)" ), QObject::tr( "Numeric distance or array of distances" ), origin ) },
136 { static_cast< int >( QgsPalLayerSettings::Property::TextOrientation ), QgsPropertyDefinition( "TextOrientation", QgsPropertyDefinition::DataTypeString, QObject::tr( "Text orientation" ), QObject::tr( "string " ) + "[<b>horizontal</b>|<b>vertical</b>]", origin ) },
137 { static_cast< int >( QgsPalLayerSettings::Property::DirSymbDraw ), QgsPropertyDefinition( "DirSymbDraw", QObject::tr( "Draw direction symbol" ), QgsPropertyDefinition::Boolean, origin ) },
138 { static_cast< int >( QgsPalLayerSettings::Property::DirSymbLeft ), QgsPropertyDefinition( "DirSymbLeft", QObject::tr( "Left direction symbol" ), QgsPropertyDefinition::String, origin ) },
139 { static_cast< int >( QgsPalLayerSettings::Property::DirSymbRight ), QgsPropertyDefinition( "DirSymbRight", QObject::tr( "Right direction symbol" ), QgsPropertyDefinition::String, origin ) },
140 { 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 ) },
141 { static_cast< int >( QgsPalLayerSettings::Property::DirSymbReverse ), QgsPropertyDefinition( "DirSymbReverse", QObject::tr( "Reverse direction symbol" ), QgsPropertyDefinition::Boolean, origin ) },
142 { static_cast< int >( QgsPalLayerSettings::Property::NumFormat ), QgsPropertyDefinition( "NumFormat", QObject::tr( "Format as number" ), QgsPropertyDefinition::Boolean, origin ) },
143 { static_cast< int >( QgsPalLayerSettings::Property::NumDecimals ), QgsPropertyDefinition( "NumDecimals", QObject::tr( "Number of decimal places" ), QgsPropertyDefinition::IntegerPositive, origin ) },
144 { static_cast< int >( QgsPalLayerSettings::Property::NumPlusSign ), QgsPropertyDefinition( "NumPlusSign", QObject::tr( "Draw + sign" ), QgsPropertyDefinition::Boolean, origin ) },
145 { static_cast< int >( QgsPalLayerSettings::Property::BufferDraw ), QgsPropertyDefinition( "BufferDraw", QObject::tr( "Draw buffer" ), QgsPropertyDefinition::Boolean, origin ) },
146 { static_cast< int >( QgsPalLayerSettings::Property::BufferSize ), QgsPropertyDefinition( "BufferSize", QObject::tr( "Symbol size" ), QgsPropertyDefinition::DoublePositive, origin ) },
147 { static_cast< int >( QgsPalLayerSettings::Property::BufferUnit ), QgsPropertyDefinition( "BufferUnit", QObject::tr( "Buffer units" ), QgsPropertyDefinition::RenderUnits, origin ) },
148 { static_cast< int >( QgsPalLayerSettings::Property::BufferColor ), QgsPropertyDefinition( "BufferColor", QObject::tr( "Buffer color" ), QgsPropertyDefinition::ColorNoAlpha, origin ) },
149 { static_cast< int >( QgsPalLayerSettings::Property::BufferTransp ), QgsPropertyDefinition( "BufferTransp", QObject::tr( "Buffer transparency" ), QgsPropertyDefinition::Opacity, origin ) },
150 { static_cast< int >( QgsPalLayerSettings::Property::BufferOpacity ), QgsPropertyDefinition( "BufferOpacity", QObject::tr( "Buffer opacity" ), QgsPropertyDefinition::Opacity, origin ) },
151 { static_cast< int >( QgsPalLayerSettings::Property::BufferJoinStyle ), QgsPropertyDefinition( "BufferJoinStyle", QObject::tr( "Buffer join style" ), QgsPropertyDefinition::PenJoinStyle, origin ) },
152 { static_cast< int >( QgsPalLayerSettings::Property::BufferBlendMode ), QgsPropertyDefinition( "BufferBlendMode", QObject::tr( "Buffer blend mode" ), QgsPropertyDefinition::BlendMode, origin ) },
153
154 { static_cast< int >( QgsPalLayerSettings::Property::MaskEnabled ), QgsPropertyDefinition( "MaskEnabled", QObject::tr( "Enable mask" ), QgsPropertyDefinition::Boolean, origin ) },
155 { static_cast< int >( QgsPalLayerSettings::Property::MaskBufferSize ), QgsPropertyDefinition( "MaskBufferSize", QObject::tr( "Mask buffer size" ), QgsPropertyDefinition::DoublePositive, origin ) },
156 { static_cast< int >( QgsPalLayerSettings::Property::MaskBufferUnit ), QgsPropertyDefinition( "MaskBufferUnit", QObject::tr( "Mask buffer unit" ), QgsPropertyDefinition::RenderUnits, origin ) },
157 { static_cast< int >( QgsPalLayerSettings::Property::MaskOpacity ), QgsPropertyDefinition( "MaskOpacity", QObject::tr( "Mask opacity" ), QgsPropertyDefinition::Opacity, origin ) },
158 { static_cast< int >( QgsPalLayerSettings::Property::MaskJoinStyle ), QgsPropertyDefinition( "MaskJoinStyle", QObject::tr( "Mask join style" ), QgsPropertyDefinition::PenJoinStyle, origin ) },
159
160 { static_cast< int >( QgsPalLayerSettings::Property::ShapeDraw ), QgsPropertyDefinition( "ShapeDraw", QObject::tr( "Draw shape" ), QgsPropertyDefinition::Boolean, origin ) },
161 {
162 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>"
163 "<b>Ellipse</b>|<b>Circle</b>|<b>SVG</b>]" ), origin )
164 },
165 { static_cast< int >( QgsPalLayerSettings::Property::ShapeSVGFile ), QgsPropertyDefinition( "ShapeSVGFile", QObject::tr( "Shape SVG path" ), QgsPropertyDefinition::SvgPath, origin ) },
166 { 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 ) },
167 { static_cast< int >( QgsPalLayerSettings::Property::ShapeSizeX ), QgsPropertyDefinition( "ShapeSizeX", QObject::tr( "Shape size (X)" ), QgsPropertyDefinition::Double, origin ) },
168 { static_cast< int >( QgsPalLayerSettings::Property::ShapeSizeY ), QgsPropertyDefinition( "ShapeSizeY", QObject::tr( "Shape size (Y)" ), QgsPropertyDefinition::Double, origin ) },
169 { static_cast< int >( QgsPalLayerSettings::Property::ShapeSizeUnits ), QgsPropertyDefinition( "ShapeSizeUnits", QObject::tr( "Shape size units" ), QgsPropertyDefinition::RenderUnits, origin ) },
170 { 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 ) },
171 { static_cast< int >( QgsPalLayerSettings::Property::ShapeRotation ), QgsPropertyDefinition( "ShapeRotation", QObject::tr( "Shape rotation" ), QgsPropertyDefinition::Rotation, origin ) },
172 { static_cast< int >( QgsPalLayerSettings::Property::ShapeOffset ), QgsPropertyDefinition( "ShapeOffset", QObject::tr( "Shape offset" ), QgsPropertyDefinition::Offset, origin ) },
173 { static_cast< int >( QgsPalLayerSettings::Property::ShapeOffsetUnits ), QgsPropertyDefinition( "ShapeOffsetUnits", QObject::tr( "Shape offset units" ), QgsPropertyDefinition::RenderUnits, origin ) },
174 { static_cast< int >( QgsPalLayerSettings::Property::ShapeRadii ), QgsPropertyDefinition( "ShapeRadii", QObject::tr( "Shape radii" ), QgsPropertyDefinition::Size2D, origin ) },
175 { static_cast< int >( QgsPalLayerSettings::Property::ShapeRadiiUnits ), QgsPropertyDefinition( "ShapeRadiiUnits", QObject::tr( "Symbol radii units" ), QgsPropertyDefinition::RenderUnits, origin ) },
176 { static_cast< int >( QgsPalLayerSettings::Property::ShapeTransparency ), QgsPropertyDefinition( "ShapeTransparency", QObject::tr( "Shape transparency" ), QgsPropertyDefinition::Opacity, origin ) },
177 { static_cast< int >( QgsPalLayerSettings::Property::ShapeOpacity ), QgsPropertyDefinition( "ShapeOpacity", QObject::tr( "Shape opacity" ), QgsPropertyDefinition::Opacity, origin ) },
178 { static_cast< int >( QgsPalLayerSettings::Property::ShapeBlendMode ), QgsPropertyDefinition( "ShapeBlendMode", QObject::tr( "Shape blend mode" ), QgsPropertyDefinition::BlendMode, origin ) },
179 { static_cast< int >( QgsPalLayerSettings::Property::ShapeFillColor ), QgsPropertyDefinition( "ShapeFillColor", QObject::tr( "Shape fill color" ), QgsPropertyDefinition::ColorWithAlpha, origin ) },
180 { static_cast< int >( QgsPalLayerSettings::Property::ShapeStrokeColor ), QgsPropertyDefinition( "ShapeBorderColor", QObject::tr( "Shape stroke color" ), QgsPropertyDefinition::ColorWithAlpha, origin ) },
181 { static_cast< int >( QgsPalLayerSettings::Property::ShapeStrokeWidth ), QgsPropertyDefinition( "ShapeBorderWidth", QObject::tr( "Shape stroke width" ), QgsPropertyDefinition::StrokeWidth, origin ) },
182 { static_cast< int >( QgsPalLayerSettings::Property::ShapeStrokeWidthUnits ), QgsPropertyDefinition( "ShapeBorderWidthUnits", QObject::tr( "Shape stroke width units" ), QgsPropertyDefinition::RenderUnits, origin ) },
183 { static_cast< int >( QgsPalLayerSettings::Property::ShapeJoinStyle ), QgsPropertyDefinition( "ShapeJoinStyle", QObject::tr( "Shape join style" ), QgsPropertyDefinition::PenJoinStyle, origin ) },
184 { static_cast< int >( QgsPalLayerSettings::Property::ShadowDraw ), QgsPropertyDefinition( "ShadowDraw", QObject::tr( "Draw shadow" ), QgsPropertyDefinition::Boolean, origin ) },
185 {
186 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>"
187 "<b>Buffer</b>|<b>Background</b>]" ), origin )
188 },
189 { static_cast< int >( QgsPalLayerSettings::Property::ShadowOffsetAngle ), QgsPropertyDefinition( "ShadowOffsetAngle", QObject::tr( "Shadow offset angle" ), QgsPropertyDefinition::Rotation, origin ) },
190 { static_cast< int >( QgsPalLayerSettings::Property::ShadowOffsetDist ), QgsPropertyDefinition( "ShadowOffsetDist", QObject::tr( "Shadow offset distance" ), QgsPropertyDefinition::DoublePositive, origin ) },
191 { static_cast< int >( QgsPalLayerSettings::Property::ShadowOffsetUnits ), QgsPropertyDefinition( "ShadowOffsetUnits", QObject::tr( "Shadow offset units" ), QgsPropertyDefinition::RenderUnits, origin ) },
192 { static_cast< int >( QgsPalLayerSettings::Property::ShadowRadius ), QgsPropertyDefinition( "ShadowRadius", QObject::tr( "Shadow blur radius" ), QgsPropertyDefinition::DoublePositive, origin ) },
193 { static_cast< int >( QgsPalLayerSettings::Property::ShadowRadiusUnits ), QgsPropertyDefinition( "ShadowRadiusUnits", QObject::tr( "Shadow blur units" ), QgsPropertyDefinition::RenderUnits, origin ) },
194 { static_cast< int >( QgsPalLayerSettings::Property::ShadowTransparency ), QgsPropertyDefinition( "ShadowTransparency", QObject::tr( "Shadow transparency" ), QgsPropertyDefinition::Opacity, origin ) },
195 { static_cast< int >( QgsPalLayerSettings::Property::ShadowOpacity ), QgsPropertyDefinition( "ShadowOpacity", QObject::tr( "Shadow opacity" ), QgsPropertyDefinition::Opacity, origin ) },
196 { static_cast< int >( QgsPalLayerSettings::Property::ShadowScale ), QgsPropertyDefinition( "ShadowScale", QObject::tr( "Shadow scale" ), QgsPropertyDefinition::IntegerPositive, origin ) },
197 { static_cast< int >( QgsPalLayerSettings::Property::ShadowColor ), QgsPropertyDefinition( "ShadowColor", QObject::tr( "Shadow color" ), QgsPropertyDefinition::ColorNoAlpha, origin ) },
198 { static_cast< int >( QgsPalLayerSettings::Property::ShadowBlendMode ), QgsPropertyDefinition( "ShadowBlendMode", QObject::tr( "Shadow blend mode" ), QgsPropertyDefinition::BlendMode, origin ) },
199
200 { 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 ) },
201 {
202 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>"
203 "<b>3</b>=Left|<b>4</b>=Over|<b>5</b>=Right|<br>"
204 "<b>6</b>=Below Left|<b>7</b>=Below|<b>8</b>=Below Right]" ), origin )
205 },
206 { static_cast< int >( QgsPalLayerSettings::Property::OffsetXY ), QgsPropertyDefinition( "OffsetXY", QObject::tr( "Offset" ), QgsPropertyDefinition::Offset, origin ) },
207 { static_cast< int >( QgsPalLayerSettings::Property::OffsetUnits ), QgsPropertyDefinition( "OffsetUnits", QObject::tr( "Offset units" ), QgsPropertyDefinition::RenderUnits, origin ) },
208 { static_cast< int >( QgsPalLayerSettings::Property::LabelDistance ), QgsPropertyDefinition( "LabelDistance", QObject::tr( "Label distance" ), QgsPropertyDefinition::Double, origin ) },
209 { static_cast< int >( QgsPalLayerSettings::Property::DistanceUnits ), QgsPropertyDefinition( "DistanceUnits", QObject::tr( "Label distance units" ), QgsPropertyDefinition::RenderUnits, origin ) },
210 { static_cast< int >( QgsPalLayerSettings::Property::OffsetRotation ), QgsPropertyDefinition( "OffsetRotation", QObject::tr( "Offset rotation" ), QgsPropertyDefinition::Rotation, origin ) },
211 { 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 ) },
212 { static_cast< int >( QgsPalLayerSettings::Property::RepeatDistance ), QgsPropertyDefinition( "RepeatDistance", QObject::tr( "Repeat distance" ), QgsPropertyDefinition::DoublePositive, origin ) },
213 { static_cast< int >( QgsPalLayerSettings::Property::RepeatDistanceUnit ), QgsPropertyDefinition( "RepeatDistanceUnit", QObject::tr( "Repeat distance unit" ), QgsPropertyDefinition::RenderUnits, origin ) },
214 { static_cast< int >( QgsPalLayerSettings::Property::OverrunDistance ), QgsPropertyDefinition( "OverrunDistance", QObject::tr( "Overrun distance" ), QgsPropertyDefinition::DoublePositive, origin ) },
215 { static_cast< int >( QgsPalLayerSettings::Property::LineAnchorPercent ), QgsPropertyDefinition( "LineAnchorPercent", QObject::tr( "Line anchor percentage, as fraction from 0.0 to 1.0" ), QgsPropertyDefinition::Double0To1, origin ) },
216 { static_cast< int >( QgsPalLayerSettings::Property::LineAnchorClipping ), QgsPropertyDefinition( "LineAnchorClipping", QgsPropertyDefinition::DataTypeString, QObject::tr( "Line anchor clipping mode" ), QObject::tr( "string " ) + u"[<b>visible</b>|<b>entire</b>]"_s, origin ) },
217 { static_cast< int >( QgsPalLayerSettings::Property::LineAnchorType ), QgsPropertyDefinition( "LineAnchorType", QgsPropertyDefinition::DataTypeString, QObject::tr( "Line anchor type" ), QObject::tr( "string " ) + u"[<b>hint</b>|<b>strict</b>]"_s, origin ) },
218 { static_cast< int >( QgsPalLayerSettings::Property::LineAnchorTextPoint ), QgsPropertyDefinition( "LineAnchorTextPoint", QgsPropertyDefinition::DataTypeString, QObject::tr( "Line anchor text point" ), QObject::tr( "string " ) + u"[<b>follow</b>|<b>start</b>|<b>center</b>|<b>end</b>]"_s, origin ) },
219 { static_cast< int >( QgsPalLayerSettings::Property::CurvedLabelMode ), QgsPropertyDefinition( "CurvedLabelMode", QgsPropertyDefinition::DataTypeString, QObject::tr( "Curved label mode" ), QObject::tr( "string " ) + u"[<b>Default</b>|<b>CharactersAtVertices</b>|<b>StretchCharacterSpacingToFit</b>|<b>StretchWordSpacingToFit</b>]"_s, origin ) },
220 { static_cast< int >( QgsPalLayerSettings::Property::Priority ), QgsPropertyDefinition( "Priority", QgsPropertyDefinition::DataTypeNumeric, QObject::tr( "Label priority" ), QObject::tr( "double [0.0-10.0]" ), origin ) },
221 { static_cast< int >( QgsPalLayerSettings::Property::IsObstacle ), QgsPropertyDefinition( "IsObstacle", QObject::tr( "Feature is a label obstacle" ), QgsPropertyDefinition::Boolean, origin ) },
222 { static_cast< int >( QgsPalLayerSettings::Property::ObstacleFactor ), QgsPropertyDefinition( "ObstacleFactor", QgsPropertyDefinition::DataTypeNumeric, QObject::tr( "Obstacle factor" ), QObject::tr( "double [0.0-10.0]" ), origin ) },
223 {
224 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>" )
225 + QStringLiteral( "[<b>TL</b>=Top left|<b>TSL</b>=Top, slightly left|<b>T</b>=Top middle|<br>"
226 "<b>TSR</b>=Top, slightly right|<b>TR</b>=Top right|<br>"
227 "<b>L</b>=Left|<b>R</b>=Right|<br>"
228 "<b>BL</b>=Bottom left|<b>BSL</b>=Bottom, slightly left|<b>B</b>=Bottom middle|<br>"
229 "<b>BSR</b>=Bottom, slightly right|<b>BR</b>=Bottom right|<b>O</b>=Over point]" ), origin )
230 },
231 {
232 static_cast< int >( QgsPalLayerSettings::Property::LinePlacementOptions ), QgsPropertyDefinition( "LinePlacementFlags", QgsPropertyDefinition::DataTypeString, QObject::tr( "Line placement options" ), QObject::tr( "Comma separated list of placement options<br>" )
233 + QStringLiteral( "[<b>OL</b>=On line|<b>AL</b>=Above line|<b>BL</b>=Below line|<br>"
234 "<b>LO</b>=Respect line orientation]" ), origin )
235 },
236 { 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 ) },
237 { static_cast< int >( QgsPalLayerSettings::Property::PositionX ), QgsPropertyDefinition( "PositionX", QObject::tr( "Position (X)" ), QgsPropertyDefinition::Double, origin ) },
238 { static_cast< int >( QgsPalLayerSettings::Property::PositionY ), QgsPropertyDefinition( "PositionY", QObject::tr( "Position (Y)" ), QgsPropertyDefinition::Double, origin ) },
239 { static_cast< int >( QgsPalLayerSettings::Property::PositionPoint ), QgsPropertyDefinition( "PositionPoint", QgsPropertyDefinition::DataTypeString, QObject::tr( "Position (point)" ), QObject::tr( "A point geometry" ), origin ) },
240 { 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 ) },
241 {
242 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>"
243 "<b>Half</b>|<b>Cap</b>|<b>Top</b>]" ), origin )
244 },
245 { static_cast< int >( QgsPalLayerSettings::Property::Rotation ), QgsPropertyDefinition( "Rotation", QObject::tr( "Label rotation (deprecated)" ), QgsPropertyDefinition::Rotation, origin ) },
246 { static_cast< int >( QgsPalLayerSettings::Property::LabelRotation ), QgsPropertyDefinition( "LabelRotation", QObject::tr( "Label rotation" ), QgsPropertyDefinition::Rotation, origin ) },
247 { static_cast< int >( QgsPalLayerSettings::Property::ScaleVisibility ), QgsPropertyDefinition( "ScaleVisibility", QObject::tr( "Scale based visibility" ), QgsPropertyDefinition::Boolean, origin ) },
248 { static_cast< int >( QgsPalLayerSettings::Property::MinScale ), QgsPropertyDefinition( "MinScale", QObject::tr( "Minimum scale (denominator)" ), QgsPropertyDefinition::Double, origin ) },
249 { static_cast< int >( QgsPalLayerSettings::Property::MaxScale ), QgsPropertyDefinition( "MaxScale", QObject::tr( "Maximum scale (denominator)" ), QgsPropertyDefinition::Double, origin ) },
250 { static_cast< int >( QgsPalLayerSettings::Property::MinimumScale ), QgsPropertyDefinition( "MinimumScale", QObject::tr( "Minimum scale (denominator)" ), QgsPropertyDefinition::Double, origin ) },
251 { static_cast< int >( QgsPalLayerSettings::Property::MaximumScale ), QgsPropertyDefinition( "MaximumScale", QObject::tr( "Maximum scale (denominator)" ), QgsPropertyDefinition::Double, origin ) },
252
253 { static_cast< int >( QgsPalLayerSettings::Property::FontLimitPixel ), QgsPropertyDefinition( "FontLimitPixel", QObject::tr( "Limit font pixel size" ), QgsPropertyDefinition::Boolean, origin ) },
254 { static_cast< int >( QgsPalLayerSettings::Property::FontMinPixel ), QgsPropertyDefinition( "FontMinPixel", QObject::tr( "Minimum pixel size" ), QgsPropertyDefinition::IntegerPositive, origin ) },
255 { static_cast< int >( QgsPalLayerSettings::Property::FontMaxPixel ), QgsPropertyDefinition( "FontMaxPixel", QObject::tr( "Maximum pixel size" ), QgsPropertyDefinition::IntegerPositive, origin ) },
256 { static_cast< int >( QgsPalLayerSettings::Property::ZIndex ), QgsPropertyDefinition( "ZIndex", QObject::tr( "Label z-index" ), QgsPropertyDefinition::Double, origin ) },
257 { static_cast< int >( QgsPalLayerSettings::Property::Show ), QgsPropertyDefinition( "Show", QObject::tr( "Show label" ), QgsPropertyDefinition::Boolean, origin ) },
258 { static_cast< int >( QgsPalLayerSettings::Property::AlwaysShow ), QgsPropertyDefinition( "AlwaysShow", QObject::tr( "Always show label" ), QgsPropertyDefinition::Boolean, origin ) },
259 { static_cast< int >( QgsPalLayerSettings::Property::CalloutDraw ), QgsPropertyDefinition( "CalloutDraw", QObject::tr( "Draw callout" ), QgsPropertyDefinition::Boolean, origin ) },
260 { static_cast< int >( QgsPalLayerSettings::Property::LabelAllParts ), QgsPropertyDefinition( "LabelAllParts", QgsPropertyDefinition::DataTypeString, QObject::tr( "Multipart geometry behavior" ), QObject::tr( "string " ) + "[<b>LargestPartOnly</b>|<b>LabelEveryPart</b>|<b>SplitLabelTextLinesOverParts</b>]", origin ) },
261 { static_cast< int >( QgsPalLayerSettings::Property::AllowDegradedPlacement ), QgsPropertyDefinition( "AllowDegradedPlacement", QObject::tr( "Allow inferior fallback placements" ), QgsPropertyDefinition::Boolean, origin ) },
262 { static_cast< int >( QgsPalLayerSettings::Property::OverlapHandling ), QgsPropertyDefinition( "OverlapHandling", QgsPropertyDefinition::DataTypeString, QObject::tr( "Overlap handling" ), QObject::tr( "string " ) + "[<b>Prevent</b>|<b>AllowIfNeeded</b>|<b>AlwaysAllow</b>]", origin ) },
263
264 { static_cast< int >( QgsPalLayerSettings::Property::WhitespaceCollisionHandling ), QgsPropertyDefinition( "WhitespaceCollisionHandling", QgsPropertyDefinition::DataTypeString, QObject::tr( "Whitespace collision handling" ), QObject::tr( "string " ) + u"[<b>TreatWhitespaceAsCollision</b>|<b>IgnoreWhitespaceCollisions</b>]"_s, origin ) },
265
266 { static_cast< int >( QgsPalLayerSettings::Property::MaximumDistance ), QgsPropertyDefinition( "MaximumDistance", QObject::tr( "Maximum distance" ), QgsPropertyDefinition::DoublePositive, origin ) },
267
268 { static_cast< int >( QgsPalLayerSettings::Property::LabelMarginDistance ), QgsPropertyDefinition( "LabelMarginDistance", QObject::tr( "Minimum distance to other labels" ), QgsPropertyDefinition::DoublePositive, origin ) },
269 { static_cast< int >( QgsPalLayerSettings::Property::RemoveDuplicateLabels ), QgsPropertyDefinition( "RemoveDuplicates", QObject::tr( "Remove duplicate labels" ), QgsPropertyDefinition::Boolean, origin ) },
270 { static_cast< int >( QgsPalLayerSettings::Property::RemoveDuplicateLabelDistance ), QgsPropertyDefinition( "RemoveDuplicateDistance", QObject::tr( "Minimum distance to duplicate labels" ), QgsPropertyDefinition::DoublePositive, origin ) },
271
272 };
273}
274
275Q_NOWARN_DEPRECATED_PUSH // because of deprecated members
277 : mCallout( QgsCalloutRegistry::defaultCallout() )
278{
279 mPointSettings.setPredefinedPositionOrder( *DEFAULT_PLACEMENT_ORDER() );
280
281 initPropertyDefinitions();
282
284}
286
287Q_NOWARN_DEPRECATED_PUSH // because of deprecated members
289 : mDataDefinedProperties( s.mDataDefinedProperties )
290{
291 *this = s;
292}
294
296{
297 if ( this == &s )
298 return *this;
299
300 // copy only permanent stuff
301
303
304 // text style
312
313 // text formatting
314 wrapChar = s.wrapChar;
319 decimals = s.decimals;
320 plusSign = s.plusSign;
321
322 // placement
324 mPolygonPlacementFlags = s.mPolygonPlacementFlags;
328 xOffset = s.xOffset;
329 yOffset = s.yOffset;
332 dist = s.dist;
338 mRotationUnit = s.mRotationUnit;
341 priority = s.priority;
345
346 // rendering
354
355 zIndex = s.zIndex;
356
357 mFormat = s.mFormat;
358 mDataDefinedProperties = s.mDataDefinedProperties;
359
360 mCallout.reset( s.mCallout ? s.mCallout->clone() : nullptr );
361
362 mPlacementSettings = s.mPlacementSettings;
363 mLineSettings = s.mLineSettings;
364 mPointSettings = s.mPointSettings;
365 mObstacleSettings = s.mObstacleSettings;
366 mThinningSettings = s.mThinningSettings;
367
372
373 mLegendString = s.mLegendString;
374
375 mUnplacedVisibility = s.mUnplacedVisibility;
376
377 return *this;
378}
379
380bool QgsPalLayerSettings::prepare( QgsRenderContext &context, QSet<QString> &attributeNames, const QgsFields &fields, const QgsMapSettings &mapSettings, const QgsCoordinateReferenceSystem &crs )
381{
382 if ( drawLabels )
383 {
384 if ( fieldName.isEmpty() )
385 {
386 return false;
387 }
388
389 if ( isExpression )
390 {
392 if ( exp.hasEvalError() )
393 {
394 QgsDebugMsgLevel( "Prepare error:" + exp.evalErrorString(), 4 );
395 return false;
396 }
397 }
398 else
399 {
400 // If we aren't an expression, we check to see if we can find the column.
401 if ( fields.lookupField( fieldName ) == -1 )
402 {
403 return false;
404 }
405 }
406 }
407
408 mCurFields = fields;
409
410 if ( drawLabels || mObstacleSettings.isObstacle() )
411 {
412 if ( drawLabels )
413 {
414 // add field indices for label's text, from expression or field
415 if ( isExpression )
416 {
417 // prepare expression for use in static_cast< int >( QgsPalLayerSettings::Property::registerFeature )()
419 exp->prepare( &context.expressionContext() );
420 if ( exp->hasEvalError() )
421 {
422 QgsDebugMsgLevel( "Prepare error:" + exp->evalErrorString(), 4 );
423 }
424 const auto referencedColumns = exp->referencedColumns();
425 for ( const QString &name : referencedColumns )
426 {
427 attributeNames.insert( name );
428 }
429 }
430 else
431 {
432 attributeNames.insert( fieldName );
433 }
434 }
435
436 mDataDefinedProperties.prepare( context.expressionContext() );
437 // add field indices of data defined expression or field
438 attributeNames.unite( dataDefinedProperties().referencedFields( context.expressionContext() ) );
439 }
440
441 // NOW INITIALIZE QgsPalLayerSettings
442
443 // TODO: ideally these (non-configuration) members should get out of QgsPalLayerSettings to QgsVectorLayerLabelProvider::prepare
444 // (together with registerFeature() & related methods) and QgsPalLayerSettings just stores config
445
446 // save the pal layer to our layer context (with some additional info)
447 fieldIndex = fields.lookupField( fieldName );
448
449 xform = &mapSettings.mapToPixel();
451 if ( context.coordinateTransform().isValid() )
452 // this is context for layer rendering
453 ct = context.coordinateTransform();
454 else
455 {
456 // otherwise fall back to creating our own CT
457 ct = QgsCoordinateTransform( crs, mapSettings.destinationCrs(), mapSettings.transformContext() );
458 }
459 ptZero = xform->toMapCoordinates( 0, 0 );
460 ptOne = xform->toMapCoordinates( 1, 0 );
461
462 // rect for clipping
463 QgsRectangle r1 = mapSettings.visibleExtent();
464 QgsDebugMsgLevel( u"Visible extent: %1"_s.arg( r1.toString() ), 2 );
465 QgsDebugMsgLevel( u"mapSetting extentBuffer: %1"_s.arg( mapSettings.extentBuffer() ), 2 );
466 r1.grow( mapSettings.extentBuffer() );
467 QgsDebugMsgLevel( u"Grown visible extent: %1"_s.arg( r1.toString() ), 2 );
469 QgsDebugMsgLevel( u"Extent geom %1"_s.arg( extentGeom.asWkt() ), 2 );
470
471 if ( !qgsDoubleNear( mapSettings.rotation(), 0.0 ) )
472 {
473 //PAL features are prerotated, so extent also needs to be unrotated
474 extentGeom.rotate( -mapSettings.rotation(), mapSettings.visibleExtent().center() );
475 QgsDebugMsgLevel( u"Rotated extent geom %1"_s.arg( extentGeom.asWkt() ), 2 );
476 }
477
479
481 {
482 mGeometryGeneratorExpression = QgsExpression( geometryGenerator );
483 mGeometryGeneratorExpression.prepare( &context.expressionContext() );
484 if ( mGeometryGeneratorExpression.hasParserError() )
485 {
486 QgsMessageLog::logMessage( mGeometryGeneratorExpression.parserErrorString(), QObject::tr( "Labeling" ) );
487 return false;
488 }
489
490 const auto referencedColumns = mGeometryGeneratorExpression.referencedColumns();
491 for ( const QString &name : referencedColumns )
492 {
493 attributeNames.insert( name );
494 }
495 }
496 attributeNames.unite( mFormat.referencedFields( context ) );
497
498 if ( mCallout )
499 {
500 const auto referencedColumns = mCallout->referencedFields( context );
501 for ( const QString &name : referencedColumns )
502 {
503 attributeNames.insert( name );
504 }
505 }
506
507 return true;
508}
509
510QSet<QString> QgsPalLayerSettings::referencedFields( const QgsRenderContext &context ) const
511{
512 QSet<QString> referenced;
513 if ( drawLabels )
514 {
515 if ( isExpression )
516 {
517 referenced.unite( QgsExpression( fieldName ).referencedColumns() );
518 }
519 else
520 {
521 referenced.insert( fieldName );
522 }
523 }
524
525 referenced.unite( mFormat.referencedFields( context ) );
526
527 // calling referencedFields() with ignoreContext=true because in our expression context
528 // we do not have valid QgsFields yet - because of that the field names from expressions
529 // wouldn't get reported
530 referenced.unite( mDataDefinedProperties.referencedFields( context.expressionContext(), true ) );
531
533 {
534 QgsExpression geomGeneratorExpr( geometryGenerator );
535 referenced.unite( geomGeneratorExpr.referencedColumns() );
536 }
537
538 if ( mCallout )
539 {
540 referenced.unite( mCallout->referencedFields( context ) );
541 }
542
543 return referenced;
544}
545
547{
548 if ( mRenderStarted )
549 {
550 qWarning( "Start render called for when a previous render was already underway!!" );
551 return;
552 }
553
554 switch ( placement )
555 {
558 {
559 // force horizontal orientation, other orientation modes aren't unsupported for curved placement
560 mFormat.setOrientation( Qgis::TextOrientation::Horizontal );
561 mDataDefinedProperties.property( QgsPalLayerSettings::Property::TextOrientation ).setActive( false );
562 break;
563 }
564
572 break;
573 }
574
575 if ( mCallout )
576 {
577 mCallout->startRender( context );
578 }
579
580 mRenderStarted = true;
581}
582
584{
585 if ( !mRenderStarted )
586 {
587 qWarning( "Stop render called for QgsPalLayerSettings without a startRender call!" );
588 return;
589 }
590
591 if ( mCallout )
592 {
593 mCallout->stopRender( context );
594 }
595
596 mRenderStarted = false;
597}
598
600{
601 return mFormat.containsAdvancedEffects() || mCallout->containsAdvancedEffects();
602}
603
605{
606 if ( mRenderStarted )
607 {
608 qWarning( "stopRender was not called on QgsPalLayerSettings object!" );
609 }
610}
611
612
614{
615 initPropertyDefinitions();
616 return *sPropertyDefinitions();
617}
618
620{
621 if ( !expression )
622 {
623 expression = std::make_unique<QgsExpression>( fieldName );
624 }
625 return expression.get();
626}
627
629{
630 return mRotationUnit;
631}
632
634{
635 mRotationUnit = angleUnit;
636}
637
638QString updateDataDefinedString( const QString &value )
639{
640 // TODO: update or remove this when project settings for labeling are migrated to better XML layout
641 QString newValue = value;
642 if ( !value.isEmpty() && !value.contains( "~~"_L1 ) )
643 {
644 QStringList values;
645 values << u"1"_s; // all old-style values are active if not empty
646 values << u"0"_s;
647 values << QString();
648 values << value; // all old-style values are only field names
649 newValue = values.join( "~~"_L1 );
650 }
651
652 return newValue;
653}
654
655void QgsPalLayerSettings::readOldDataDefinedProperty( QgsVectorLayer *layer, QgsPalLayerSettings::Property p )
656{
657 QString newPropertyName = "labeling/dataDefined/" + sPropertyDefinitions()->value( static_cast< int >( p ) ).name();
658 QVariant newPropertyField = layer->customProperty( newPropertyName, QVariant() );
659
660 if ( !newPropertyField.isValid() )
661 return;
662
663 QString ddString = newPropertyField.toString();
664
665 if ( !ddString.isEmpty() && ddString != "0~~0~~~~"_L1 )
666 {
667 // TODO: update this when project settings for labeling are migrated to better XML layout
668 QString newStyleString = updateDataDefinedString( ddString );
669 QStringList ddv = newStyleString.split( u"~~"_s );
670
671 bool active = ddv.at( 0 ).toInt();
672 if ( ddv.at( 1 ).toInt() )
673 {
674 mDataDefinedProperties.setProperty( p, QgsProperty::fromExpression( ddv.at( 2 ), active ) );
675 }
676 else
677 {
678 mDataDefinedProperties.setProperty( p, QgsProperty::fromField( ddv.at( 3 ), active ) );
679 }
680 }
681 else
682 {
683 // remove unused properties
684 layer->removeCustomProperty( newPropertyName );
685 }
686}
687
688void QgsPalLayerSettings::readOldDataDefinedPropertyMap( QgsVectorLayer *layer, QDomElement *parentElem )
689{
690 if ( !layer && !parentElem )
691 {
692 return;
693 }
694
695 QgsPropertiesDefinition::const_iterator i = sPropertyDefinitions()->constBegin();
696 for ( ; i != sPropertyDefinitions()->constEnd(); ++i )
697 {
698 if ( layer )
699 {
700 // reading from layer's custom properties
701 readOldDataDefinedProperty( layer, static_cast< Property >( i.key() ) );
702 }
703 else if ( parentElem )
704 {
705 // reading from XML
706 QDomElement e = parentElem->firstChildElement( i.value().name() );
707 if ( !e.isNull() )
708 {
709 bool active = e.attribute( u"active"_s ).compare( "true"_L1, Qt::CaseInsensitive ) == 0;
710 bool isExpression = e.attribute( u"useExpr"_s ).compare( "true"_L1, Qt::CaseInsensitive ) == 0;
711 if ( isExpression )
712 {
713 mDataDefinedProperties.setProperty( i.key(), QgsProperty::fromExpression( e.attribute( u"expr"_s ), active ) );
714 }
715 else
716 {
717 mDataDefinedProperties.setProperty( i.key(), QgsProperty::fromField( e.attribute( u"field"_s ), active ) );
718 }
719 }
720 }
721 }
722}
723
724void QgsPalLayerSettings::readFromLayerCustomProperties( QgsVectorLayer *layer )
725{
726 if ( layer->customProperty( u"labeling"_s ).toString() != "pal"_L1 )
727 {
728 if ( layer->geometryType() == Qgis::GeometryType::Point )
730
731 // for polygons the "over point" (over centroid) placement is better than the default
732 // "around point" (around centroid) which is more suitable for points
735
736 return; // there's no information available
737 }
738
739 // NOTE: set defaults for newly added properties, for backwards compatibility
740
741 drawLabels = layer->customProperty( u"labeling/drawLabels"_s, true ).toBool();
742
743 mFormat.readFromLayer( layer );
744
745 // text style
746 fieldName = layer->customProperty( u"labeling/fieldName"_s ).toString();
747 isExpression = layer->customProperty( u"labeling/isExpression"_s ).toBool();
749 previewBkgrdColor = QColor( layer->customProperty( u"labeling/previewBkgrdColor"_s, QVariant( "#ffffff" ) ).toString() );
751 QDomDocument doc( u"substitutions"_s );
752 doc.setContent( layer->customProperty( u"labeling/substitutions"_s ).toString() );
753 QDomElement replacementElem = doc.firstChildElement( u"substitutions"_s );
754 substitutions.readXml( replacementElem );
755 useSubstitutions = layer->customProperty( u"labeling/useSubstitutions"_s ).toBool();
756
757 // text formatting
758 wrapChar = layer->customProperty( u"labeling/wrapChar"_s ).toString();
759 autoWrapLength = layer->customProperty( u"labeling/autoWrapLength"_s ).toInt();
760 useMaxLineLengthForAutoWrap = layer->customProperty( u"labeling/useMaxLineLengthForAutoWrap"_s, u"1"_s ).toBool();
761
762 multilineAlign = static_cast< Qgis::LabelMultiLineAlignment >( layer->customProperty( u"labeling/multilineAlign"_s, QVariant( static_cast< int >( Qgis::LabelMultiLineAlignment::FollowPlacement ) ) ).toUInt() );
763 mLineSettings.setAddDirectionSymbol( layer->customProperty( u"labeling/addDirectionSymbol"_s ).toBool() );
764 mLineSettings.setLeftDirectionSymbol( layer->customProperty( u"labeling/leftDirectionSymbol"_s, QVariant( "<" ) ).toString() );
765 mLineSettings.setRightDirectionSymbol( layer->customProperty( u"labeling/rightDirectionSymbol"_s, QVariant( ">" ) ).toString() );
766 mLineSettings.setReverseDirectionSymbol( layer->customProperty( u"labeling/reverseDirectionSymbol"_s ).toBool() );
767 mLineSettings.setDirectionSymbolPlacement( static_cast< QgsLabelLineSettings::DirectionSymbolPlacement >( layer->customProperty( u"labeling/placeDirectionSymbol"_s, QVariant( static_cast< int >( QgsLabelLineSettings::DirectionSymbolPlacement::SymbolLeftRight ) ) ).toUInt() ) );
768 formatNumbers = layer->customProperty( u"labeling/formatNumbers"_s ).toBool();
769 decimals = layer->customProperty( u"labeling/decimals"_s ).toInt();
770 plusSign = layer->customProperty( u"labeling/plussign"_s ).toBool();
771
772 // placement
773 placement = static_cast< Qgis::LabelPlacement >( layer->customProperty( u"labeling/placement"_s ).toInt() );
774 mLineSettings.setPlacementFlags( static_cast< Qgis::LabelLinePlacementFlags >( layer->customProperty( u"labeling/placementFlags"_s ).toUInt() ) );
775 centroidWhole = layer->customProperty( u"labeling/centroidWhole"_s, QVariant( false ) ).toBool();
776 centroidInside = layer->customProperty( u"labeling/centroidInside"_s, QVariant( false ) ).toBool();
777
778 QVector<Qgis::LabelPredefinedPointPosition> predefinedPositionOrder = QgsLabelingUtils::decodePredefinedPositionOrder( layer->customProperty( u"labeling/predefinedPositionOrder"_s ).toString() );
779 if ( predefinedPositionOrder.isEmpty() )
780 predefinedPositionOrder = *DEFAULT_PLACEMENT_ORDER();
781 mPointSettings.setPredefinedPositionOrder( predefinedPositionOrder );
782
783 fitInPolygonOnly = layer->customProperty( u"labeling/fitInPolygonOnly"_s, QVariant( false ) ).toBool();
784 dist = layer->customProperty( u"labeling/dist"_s ).toDouble();
785 distUnits = layer->customProperty( u"labeling/distInMapUnits"_s ).toBool() ? Qgis::RenderUnit::MapUnits : Qgis::RenderUnit::Millimeters;
786 if ( layer->customProperty( u"labeling/distMapUnitScale"_s ).toString().isEmpty() )
787 {
788 //fallback to older property
789 double oldMin = layer->customProperty( u"labeling/distMapUnitMinScale"_s, 0.0 ).toDouble();
790 distMapUnitScale.minScale = !qgsDoubleNear( oldMin, 0.0 ) ? 1.0 / oldMin : 0;
791 double oldMax = layer->customProperty( u"labeling/distMapUnitMaxScale"_s, 0.0 ).toDouble();
792 distMapUnitScale.maxScale = !qgsDoubleNear( oldMax, 0.0 ) ? 1.0 / oldMax : 0;
793 }
794 else
795 {
796 distMapUnitScale = QgsSymbolLayerUtils::decodeMapUnitScale( layer->customProperty( u"labeling/distMapUnitScale"_s ).toString() );
797 }
798 offsetType = static_cast< Qgis::LabelOffsetType >( layer->customProperty( u"labeling/offsetType"_s, QVariant( static_cast< int >( Qgis::LabelOffsetType::FromPoint ) ) ).toUInt() );
799 mPointSettings.setQuadrant( static_cast< Qgis::LabelQuadrantPosition >( layer->customProperty( u"labeling/quadOffset"_s, QVariant( static_cast< int >( Qgis::LabelQuadrantPosition::Over ) ) ).toUInt() ) );
800 xOffset = layer->customProperty( u"labeling/xOffset"_s, QVariant( 0.0 ) ).toDouble();
801 yOffset = layer->customProperty( u"labeling/yOffset"_s, QVariant( 0.0 ) ).toDouble();
802 if ( layer->customProperty( u"labeling/labelOffsetInMapUnits"_s, QVariant( true ) ).toBool() )
804 else
806
807 if ( layer->customProperty( u"labeling/labelOffsetMapUnitScale"_s ).toString().isEmpty() )
808 {
809 //fallback to older property
810 double oldMin = layer->customProperty( u"labeling/labelOffsetMapUnitMinScale"_s, 0.0 ).toDouble();
811 labelOffsetMapUnitScale.minScale = !qgsDoubleNear( oldMin, 0.0 ) ? 1.0 / oldMin : 0;
812 double oldMax = layer->customProperty( u"labeling/labelOffsetMapUnitMaxScale"_s, 0.0 ).toDouble();
813 labelOffsetMapUnitScale.maxScale = !qgsDoubleNear( oldMax, 0 ) ? 1.0 / oldMax : 0;
814 }
815 else
816 {
817 labelOffsetMapUnitScale = QgsSymbolLayerUtils::decodeMapUnitScale( layer->customProperty( u"labeling/labelOffsetMapUnitScale"_s ).toString() );
818 }
819
820 QVariant tempAngle = layer->customProperty( u"labeling/angleOffset"_s, QVariant() );
821 if ( tempAngle.isValid() )
822 {
823 double oldAngle = layer->customProperty( u"labeling/angleOffset"_s, QVariant( 0.0 ) ).toDouble();
824 angleOffset = std::fmod( 360 - oldAngle, 360.0 );
825 }
826 else
827 {
828 angleOffset = layer->customProperty( u"labeling/rotationAngle"_s, QVariant( 0.0 ) ).toDouble();
829 }
830
831 preserveRotation = layer->customProperty( u"labeling/preserveRotation"_s, QVariant( true ) ).toBool();
832 mRotationUnit = layer->customEnumProperty( u"labeling/rotationUnit"_s, Qgis::AngleUnit::Degrees );
833 maxCurvedCharAngleIn = layer->customProperty( u"labeling/maxCurvedCharAngleIn"_s, QVariant( 25.0 ) ).toDouble();
834 maxCurvedCharAngleOut = layer->customProperty( u"labeling/maxCurvedCharAngleOut"_s, QVariant( -25.0 ) ).toDouble();
835 priority = layer->customProperty( u"labeling/priority"_s ).toInt();
836 repeatDistance = layer->customProperty( u"labeling/repeatDistance"_s, 0.0 ).toDouble();
837 switch ( layer->customProperty( u"labeling/repeatDistanceUnit"_s, QVariant( 1 ) ).toUInt() )
838 {
839 case 0:
841 break;
842 case 1:
844 break;
845 case 2:
847 break;
848 case 3:
850 break;
851 }
852 if ( layer->customProperty( u"labeling/repeatDistanceMapUnitScale"_s ).toString().isEmpty() )
853 {
854 //fallback to older property
855 double oldMin = layer->customProperty( u"labeling/repeatDistanceMapUnitMinScale"_s, 0.0 ).toDouble();
856 repeatDistanceMapUnitScale.minScale = !qgsDoubleNear( oldMin, 0 ) ? 1.0 / oldMin : 0;
857 double oldMax = layer->customProperty( u"labeling/repeatDistanceMapUnitMaxScale"_s, 0.0 ).toDouble();
858 repeatDistanceMapUnitScale.maxScale = !qgsDoubleNear( oldMax, 0 ) ? 1.0 / oldMax : 0;
859 }
860 else
861 {
862 repeatDistanceMapUnitScale = QgsSymbolLayerUtils::decodeMapUnitScale( layer->customProperty( u"labeling/repeatDistanceMapUnitScale"_s ).toString() );
863 }
864
865 // rendering
866 double scalemn = layer->customProperty( u"labeling/scaleMin"_s, QVariant( 0 ) ).toDouble();
867 double scalemx = layer->customProperty( u"labeling/scaleMax"_s, QVariant( 0 ) ).toDouble();
868
869 // fix for scale visibility limits being keyed off of just its values in the past (<2.0)
870 QVariant scalevis = layer->customProperty( u"labeling/scaleVisibility"_s, QVariant() );
871 if ( scalevis.isValid() )
872 {
873 scaleVisibility = scalevis.toBool();
874 maximumScale = scalemn;
875 minimumScale = scalemx;
876 }
877 else if ( scalemn > 0 || scalemx > 0 )
878 {
879 scaleVisibility = true;
880 maximumScale = scalemn;
881 minimumScale = scalemx;
882 }
883 else
884 {
885 // keep scaleMin and scaleMax at new 1.0 defaults (1 and 10000000, were 0 and 0)
886 scaleVisibility = false;
887 }
888
889
890 fontLimitPixelSize = layer->customProperty( u"labeling/fontLimitPixelSize"_s, QVariant( false ) ).toBool();
891 fontMinPixelSize = layer->customProperty( u"labeling/fontMinPixelSize"_s, QVariant( 0 ) ).toInt();
892 fontMaxPixelSize = layer->customProperty( u"labeling/fontMaxPixelSize"_s, QVariant( 10000 ) ).toInt();
893 if ( layer->customProperty( u"labeling/displayAll"_s, QVariant( false ) ).toBool() )
894 {
895 mPlacementSettings.setOverlapHandling( Qgis::LabelOverlapHandling::AllowOverlapIfRequired );
896 mPlacementSettings.setAllowDegradedPlacement( true );
897 }
898 else
899 {
900 mPlacementSettings.setOverlapHandling( Qgis::LabelOverlapHandling::PreventOverlap );
901 mPlacementSettings.setAllowDegradedPlacement( false );
902 }
903 upsidedownLabels = static_cast< Qgis::UpsideDownLabelHandling >( layer->customProperty( u"labeling/upsidedownLabels"_s, QVariant( static_cast< int >( Qgis::UpsideDownLabelHandling::FlipUpsideDownLabels ) ) ).toUInt() );
904
905 mLineSettings.setMergeLines( layer->customProperty( u"labeling/mergeLines"_s ).toBool() );
906 mThinningSettings.setMinimumFeatureSize( layer->customProperty( u"labeling/minFeatureSize"_s ).toDouble() );
907 mThinningSettings.setLimitNumberLabelsEnabled( layer->customProperty( u"labeling/limitNumLabels"_s, QVariant( false ) ).toBool() );
908 mThinningSettings.setMaximumNumberLabels( layer->customProperty( u"labeling/maxNumLabels"_s, QVariant( 2000 ) ).toInt() );
909 mObstacleSettings.setIsObstacle( layer->customProperty( u"labeling/obstacle"_s, QVariant( true ) ).toBool() );
910 mObstacleSettings.setFactor( layer->customProperty( u"labeling/obstacleFactor"_s, QVariant( 1.0 ) ).toDouble() );
911 mObstacleSettings.setType( static_cast< QgsLabelObstacleSettings::ObstacleType >( layer->customProperty( u"labeling/obstacleType"_s, QVariant( static_cast< int >( QgsLabelObstacleSettings::ObstacleType::PolygonInterior ) ) ).toUInt() ) );
912 zIndex = layer->customProperty( u"labeling/zIndex"_s, QVariant( 0.0 ) ).toDouble();
913
914 mDataDefinedProperties.clear();
915 if ( layer->customProperty( u"labeling/ddProperties"_s ).isValid() )
916 {
917 QDomDocument doc( u"dd"_s );
918 doc.setContent( layer->customProperty( u"labeling/ddProperties"_s ).toString() );
919 QDomElement elem = doc.firstChildElement( u"properties"_s );
920 mDataDefinedProperties.readXml( elem, *sPropertyDefinitions() );
921 }
922 else
923 {
924 // read QGIS 2.x style data defined properties
925 readOldDataDefinedPropertyMap( layer, nullptr );
926 }
927 // upgrade older data defined settings
928 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::FontTransp ) )
929 {
930 mDataDefinedProperties.setProperty( QgsPalLayerSettings::Property::FontOpacity, QgsProperty::fromExpression( u"100 - (%1)"_s.arg( mDataDefinedProperties.property( QgsPalLayerSettings::Property::FontTransp ).asExpression() ) ) );
931 mDataDefinedProperties.setProperty( QgsPalLayerSettings::Property::FontTransp, QgsProperty() );
932 }
933 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::BufferTransp ) )
934 {
935 mDataDefinedProperties.setProperty( QgsPalLayerSettings::Property::BufferOpacity, QgsProperty::fromExpression( u"100 - (%1)"_s.arg( mDataDefinedProperties.property( QgsPalLayerSettings::Property::BufferTransp ).asExpression() ) ) );
936 mDataDefinedProperties.setProperty( QgsPalLayerSettings::Property::BufferTransp, QgsProperty() );
937 }
938 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::ShapeTransparency ) )
939 {
940 mDataDefinedProperties.setProperty( QgsPalLayerSettings::Property::ShapeOpacity, QgsProperty::fromExpression( u"100 - (%1)"_s.arg( mDataDefinedProperties.property( QgsPalLayerSettings::Property::ShapeTransparency ).asExpression() ) ) );
941 mDataDefinedProperties.setProperty( QgsPalLayerSettings::Property::ShapeTransparency, QgsProperty() );
942 }
943 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::ShadowTransparency ) )
944 {
945 mDataDefinedProperties.setProperty( QgsPalLayerSettings::Property::ShadowOpacity, QgsProperty::fromExpression( u"100 - (%1)"_s.arg( mDataDefinedProperties.property( QgsPalLayerSettings::Property::ShadowTransparency ).asExpression() ) ) );
946 mDataDefinedProperties.setProperty( QgsPalLayerSettings::Property::ShadowTransparency, QgsProperty() );
947 }
948 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::Rotation ) )
949 {
950 mDataDefinedProperties.setProperty( QgsPalLayerSettings::Property::LabelRotation, QgsProperty::fromExpression( u"360 - (%1)"_s.arg( mDataDefinedProperties.property( QgsPalLayerSettings::Property::Rotation ).asExpression() ) ) );
951 mDataDefinedProperties.setProperty( QgsPalLayerSettings::Property::Rotation, QgsProperty() );
952 }
953 // older 2.x projects had min/max scale flipped - so change them here.
954 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::MinScale ) )
955 {
956 mDataDefinedProperties.setProperty( QgsPalLayerSettings::Property::MaximumScale, mDataDefinedProperties.property( QgsPalLayerSettings::Property::MinScale ) );
957 mDataDefinedProperties.setProperty( QgsPalLayerSettings::Property::MinScale, QgsProperty() );
958 }
959 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::MaxScale ) )
960 {
961 mDataDefinedProperties.setProperty( QgsPalLayerSettings::Property::MinimumScale, mDataDefinedProperties.property( QgsPalLayerSettings::Property::MaxScale ) );
962 mDataDefinedProperties.setProperty( QgsPalLayerSettings::Property::MaxScale, QgsProperty() );
963 }
964}
965
966void QgsPalLayerSettings::readXml( const QDomElement &elem, const QgsReadWriteContext &context )
967{
968 // text style
969 QDomElement textStyleElem = elem.firstChildElement( u"text-style"_s );
970 fieldName = textStyleElem.attribute( u"fieldName"_s );
971 isExpression = textStyleElem.attribute( u"isExpression"_s ).toInt();
972
973 mFormat.readXml( elem, context );
975 previewBkgrdColor = QColor( textStyleElem.attribute( u"previewBkgrdColor"_s, u"#ffffff"_s ) );
977 substitutions.readXml( textStyleElem.firstChildElement( u"substitutions"_s ) );
978 useSubstitutions = textStyleElem.attribute( u"useSubstitutions"_s ).toInt();
979 mLegendString = textStyleElem.attribute( u"legendString"_s, QObject::tr( "Aa" ) );
980
981 // text formatting
982 QDomElement textFormatElem = elem.firstChildElement( u"text-format"_s );
983 wrapChar = textFormatElem.attribute( u"wrapChar"_s );
984 autoWrapLength = textFormatElem.attribute( u"autoWrapLength"_s, u"0"_s ).toInt();
985 useMaxLineLengthForAutoWrap = textFormatElem.attribute( u"useMaxLineLengthForAutoWrap"_s, u"1"_s ).toInt();
986 multilineAlign = static_cast< Qgis::LabelMultiLineAlignment >( textFormatElem.attribute( u"multilineAlign"_s, QString::number( static_cast< int >( Qgis::LabelMultiLineAlignment::FollowPlacement ) ) ).toUInt() );
987 mLineSettings.setAddDirectionSymbol( textFormatElem.attribute( u"addDirectionSymbol"_s ).toInt() );
988 mLineSettings.setLeftDirectionSymbol( textFormatElem.attribute( u"leftDirectionSymbol"_s, u"<"_s ) );
989 mLineSettings.setRightDirectionSymbol( textFormatElem.attribute( u"rightDirectionSymbol"_s, u">"_s ) );
990 mLineSettings.setReverseDirectionSymbol( textFormatElem.attribute( u"reverseDirectionSymbol"_s ).toInt() );
991 mLineSettings.setDirectionSymbolPlacement( static_cast< QgsLabelLineSettings::DirectionSymbolPlacement >( textFormatElem.attribute( u"placeDirectionSymbol"_s, QString::number( static_cast< int >( QgsLabelLineSettings::DirectionSymbolPlacement::SymbolLeftRight ) ) ).toUInt() ) );
992 formatNumbers = textFormatElem.attribute( u"formatNumbers"_s ).toInt();
993 decimals = textFormatElem.attribute( u"decimals"_s ).toInt();
994 plusSign = textFormatElem.attribute( u"plussign"_s ).toInt();
995
996 // placement
997 QDomElement placementElem = elem.firstChildElement( u"placement"_s );
998 placement = static_cast< Qgis::LabelPlacement >( placementElem.attribute( u"placement"_s ).toInt() );
999 mLineSettings.setPlacementFlags( static_cast< Qgis::LabelLinePlacementFlags >( placementElem.attribute( u"placementFlags"_s ).toUInt() ) );
1000 mPolygonPlacementFlags = static_cast< Qgis::LabelPolygonPlacementFlags >( placementElem.attribute( u"polygonPlacementFlags"_s, QString::number( static_cast< int >( Qgis::LabelPolygonPlacementFlag::AllowPlacementInsideOfPolygon ) ) ).toInt() );
1001
1002 centroidWhole = placementElem.attribute( u"centroidWhole"_s, u"0"_s ).toInt();
1003 centroidInside = placementElem.attribute( u"centroidInside"_s, u"0"_s ).toInt();
1004
1005 QVector<Qgis::LabelPredefinedPointPosition> predefinedPositionOrder = QgsLabelingUtils::decodePredefinedPositionOrder( placementElem.attribute( u"predefinedPositionOrder"_s ) );
1006 if ( predefinedPositionOrder.isEmpty() )
1007 predefinedPositionOrder = *DEFAULT_PLACEMENT_ORDER();
1008 mPointSettings.setPredefinedPositionOrder( predefinedPositionOrder );
1009
1010 fitInPolygonOnly = placementElem.attribute( u"fitInPolygonOnly"_s, u"0"_s ).toInt();
1011 dist = placementElem.attribute( u"dist"_s ).toDouble();
1012 if ( !placementElem.hasAttribute( u"distUnits"_s ) )
1013 {
1014 if ( placementElem.attribute( u"distInMapUnits"_s ).toInt() )
1016 else
1018 }
1019 else
1020 {
1021 distUnits = QgsUnitTypes::decodeRenderUnit( placementElem.attribute( u"distUnits"_s ) );
1022 }
1023 if ( !placementElem.hasAttribute( u"distMapUnitScale"_s ) )
1024 {
1025 //fallback to older property
1026 double oldMin = placementElem.attribute( u"distMapUnitMinScale"_s, u"0"_s ).toDouble();
1027 distMapUnitScale.minScale = !qgsDoubleNear( oldMin, 0 ) ? 1.0 / oldMin : 0;
1028 double oldMax = placementElem.attribute( u"distMapUnitMaxScale"_s, u"0"_s ).toDouble();
1029 distMapUnitScale.maxScale = !qgsDoubleNear( oldMax, 0 ) ? 1.0 / oldMax : 0;
1030 }
1031 else
1032 {
1033 distMapUnitScale = QgsSymbolLayerUtils::decodeMapUnitScale( placementElem.attribute( u"distMapUnitScale"_s ) );
1034 }
1035 offsetType = static_cast< Qgis::LabelOffsetType >( placementElem.attribute( u"offsetType"_s, QString::number( static_cast< int >( Qgis::LabelOffsetType::FromPoint ) ) ).toUInt() );
1036 mPointSettings.setQuadrant( static_cast< Qgis::LabelQuadrantPosition >( placementElem.attribute( u"quadOffset"_s, QString::number( static_cast< int >( Qgis::LabelQuadrantPosition::Over ) ) ).toUInt() ) );
1037 xOffset = placementElem.attribute( u"xOffset"_s, u"0"_s ).toDouble();
1038 yOffset = placementElem.attribute( u"yOffset"_s, u"0"_s ).toDouble();
1039 if ( !placementElem.hasAttribute( u"offsetUnits"_s ) )
1040 {
1041 offsetUnits = placementElem.attribute( u"labelOffsetInMapUnits"_s, u"1"_s ).toInt() ? Qgis::RenderUnit::MapUnits : Qgis::RenderUnit::Millimeters;
1042 }
1043 else
1044 {
1045 offsetUnits = QgsUnitTypes::decodeRenderUnit( placementElem.attribute( u"offsetUnits"_s ) );
1046 }
1047 if ( !placementElem.hasAttribute( u"labelOffsetMapUnitScale"_s ) )
1048 {
1049 //fallback to older property
1050 double oldMin = placementElem.attribute( u"labelOffsetMapUnitMinScale"_s, u"0"_s ).toDouble();
1051 labelOffsetMapUnitScale.minScale = !qgsDoubleNear( oldMin, 0.0 ) ? 1.0 / oldMin : 0;
1052 double oldMax = placementElem.attribute( u"labelOffsetMapUnitMaxScale"_s, u"0"_s ).toDouble();
1053 labelOffsetMapUnitScale.maxScale = !qgsDoubleNear( oldMax, 0.0 ) ? 1.0 / oldMax : 0;
1054 }
1055 else
1056 {
1057 labelOffsetMapUnitScale = QgsSymbolLayerUtils::decodeMapUnitScale( placementElem.attribute( u"labelOffsetMapUnitScale"_s ) );
1058 }
1059
1060 if ( placementElem.hasAttribute( u"angleOffset"_s ) )
1061 {
1062 double oldAngle = placementElem.attribute( u"angleOffset"_s, u"0"_s ).toDouble();
1063 angleOffset = std::fmod( 360 - oldAngle, 360.0 );
1064 }
1065 else
1066 {
1067 angleOffset = placementElem.attribute( u"rotationAngle"_s, u"0"_s ).toDouble();
1068 }
1069
1070 preserveRotation = placementElem.attribute( u"preserveRotation"_s, u"1"_s ).toInt();
1071 {
1072 QString rotationUnitString = placementElem.attribute( u"rotationUnit"_s, qgsEnumValueToKey( Qgis::AngleUnit::Degrees ) );
1073 if ( rotationUnitString.startsWith( "Angle"_L1 ) )
1074 {
1075 // compatibility with QGIS < 3.30
1076 rotationUnitString = rotationUnitString.mid( 5 );
1077 }
1078
1079 mRotationUnit = qgsEnumKeyToValue( rotationUnitString, Qgis::AngleUnit::Degrees );
1080 }
1081 maxCurvedCharAngleIn = placementElem.attribute( u"maxCurvedCharAngleIn"_s, u"25"_s ).toDouble();
1082 maxCurvedCharAngleOut = placementElem.attribute( u"maxCurvedCharAngleOut"_s, u"-25"_s ).toDouble();
1083 priority = placementElem.attribute( u"priority"_s ).toInt();
1084 repeatDistance = placementElem.attribute( u"repeatDistance"_s, u"0"_s ).toDouble();
1085 if ( !placementElem.hasAttribute( u"repeatDistanceUnits"_s ) )
1086 {
1087 // upgrade old setting
1088 switch ( placementElem.attribute( u"repeatDistanceUnit"_s, QString::number( 1 ) ).toUInt() )
1089 {
1090 case 0:
1092 break;
1093 case 1:
1095 break;
1096 case 2:
1098 break;
1099 case 3:
1101 break;
1102 }
1103 }
1104 else
1105 {
1106 repeatDistanceUnit = QgsUnitTypes::decodeRenderUnit( placementElem.attribute( u"repeatDistanceUnits"_s ) );
1107 }
1108 if ( !placementElem.hasAttribute( u"repeatDistanceMapUnitScale"_s ) )
1109 {
1110 //fallback to older property
1111 double oldMin = placementElem.attribute( u"repeatDistanceMapUnitMinScale"_s, u"0"_s ).toDouble();
1112 repeatDistanceMapUnitScale.minScale = !qgsDoubleNear( oldMin, 0.0 ) ? 1.0 / oldMin : 0;
1113 double oldMax = placementElem.attribute( u"repeatDistanceMapUnitMaxScale"_s, u"0"_s ).toDouble();
1114 repeatDistanceMapUnitScale.maxScale = !qgsDoubleNear( oldMax, 0.0 ) ? 1.0 / oldMax : 0;
1115 }
1116 else
1117 {
1118 repeatDistanceMapUnitScale = QgsSymbolLayerUtils::decodeMapUnitScale( placementElem.attribute( u"repeatDistanceMapUnitScale"_s ) );
1119 }
1120
1121 mLineSettings.setOverrunDistance( placementElem.attribute( u"overrunDistance"_s, u"0"_s ).toDouble() );
1122 mLineSettings.setOverrunDistanceUnit( QgsUnitTypes::decodeRenderUnit( placementElem.attribute( u"overrunDistanceUnit"_s ) ) );
1123 mLineSettings.setOverrunDistanceMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( placementElem.attribute( u"overrunDistanceMapUnitScale"_s ) ) );
1124 mLineSettings.setLineAnchorPercent( placementElem.attribute( u"lineAnchorPercent"_s, u"0.5"_s ).toDouble() );
1125 mLineSettings.setAnchorType( static_cast< QgsLabelLineSettings::AnchorType >( placementElem.attribute( u"lineAnchorType"_s, u"0"_s ).toInt() ) );
1126 mLineSettings.setAnchorClipping( static_cast< QgsLabelLineSettings::AnchorClipping >( placementElem.attribute( u"lineAnchorClipping"_s, u"0"_s ).toInt() ) );
1127 // when reading the anchor text point we default to center mode, to keep same result as for projects created in < 3.26
1128 mLineSettings.setAnchorTextPoint( qgsEnumKeyToValue( placementElem.attribute( u"lineAnchorTextPoint"_s ), QgsLabelLineSettings::AnchorTextPoint::CenterOfText ) );
1129 mLineSettings.setCurvedLabelMode( qgsEnumKeyToValue( placementElem.attribute( u"curvedLabelMode"_s ), Qgis::CurvedLabelMode::Default ) );
1130
1131 mPointSettings.setMaximumDistance( placementElem.attribute( u"maximumDistance"_s, u"0"_s ).toDouble() );
1132 mPointSettings.setMaximumDistanceUnit( QgsUnitTypes::decodeRenderUnit( placementElem.attribute( u"maximumDistanceUnit"_s ) ) );
1133 mPointSettings.setMaximumDistanceMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( placementElem.attribute( u"maximumDistanceMapUnitScale"_s ) ) );
1134
1135 geometryGenerator = placementElem.attribute( u"geometryGenerator"_s );
1136 geometryGeneratorEnabled = placementElem.attribute( u"geometryGeneratorEnabled"_s ).toInt();
1137 {
1138 QString geometryTypeKey = placementElem.attribute( u"geometryGeneratorType"_s );
1139 // maintain compatibility with < 3.3.0
1140 if ( geometryTypeKey.endsWith( "Geometry"_L1 ) )
1141 geometryTypeKey.chop( 8 );
1142
1144 }
1145 {
1146 QString layerTypeKey = placementElem.attribute( u"layerType"_s );
1147 // maintain compatibility with < 3.3.0
1148 if ( layerTypeKey.endsWith( "Geometry"_L1 ) )
1149 layerTypeKey.chop( 8 );
1150
1152 }
1153
1154 mPlacementSettings.setAllowDegradedPlacement( placementElem.attribute( u"allowDegraded"_s, u"0"_s ).toInt() );
1155
1156 // rendering
1157 QDomElement renderingElem = elem.firstChildElement( u"rendering"_s );
1158
1159 drawLabels = renderingElem.attribute( u"drawLabels"_s, u"1"_s ).toInt();
1160
1161 maximumScale = renderingElem.attribute( u"scaleMin"_s, u"0"_s ).toDouble();
1162 minimumScale = renderingElem.attribute( u"scaleMax"_s, u"0"_s ).toDouble();
1163 scaleVisibility = renderingElem.attribute( u"scaleVisibility"_s ).toInt();
1164
1165 fontLimitPixelSize = renderingElem.attribute( u"fontLimitPixelSize"_s, u"0"_s ).toInt();
1166 fontMinPixelSize = renderingElem.attribute( u"fontMinPixelSize"_s, u"0"_s ).toInt();
1167 fontMaxPixelSize = renderingElem.attribute( u"fontMaxPixelSize"_s, u"10000"_s ).toInt();
1168
1169 if ( placementElem.hasAttribute( u"overlapHandling"_s ) )
1170 {
1171 mPlacementSettings.setOverlapHandling( qgsEnumKeyToValue( placementElem.attribute( u"overlapHandling"_s ), Qgis::LabelOverlapHandling::PreventOverlap ) );
1172 }
1173 else
1174 {
1175 // legacy setting
1176 if ( renderingElem.attribute( u"displayAll"_s, u"0"_s ).toInt() )
1177 {
1178 mPlacementSettings.setOverlapHandling( Qgis::LabelOverlapHandling::AllowOverlapIfRequired );
1179 mPlacementSettings.setAllowDegradedPlacement( true );
1180 }
1181 else
1182 {
1183 mPlacementSettings.setOverlapHandling( Qgis::LabelOverlapHandling::PreventOverlap );
1184 mPlacementSettings.setAllowDegradedPlacement( false );
1185 }
1186 }
1187
1188 mPlacementSettings.setPrioritization( qgsEnumKeyToValue( placementElem.attribute( u"prioritization"_s ), Qgis::LabelPrioritization::PreferCloser ) );
1189
1190 upsidedownLabels = static_cast< Qgis::UpsideDownLabelHandling >( renderingElem.attribute( u"upsidedownLabels"_s, QString::number( static_cast< int >( Qgis::UpsideDownLabelHandling::FlipUpsideDownLabels ) ) ).toUInt() );
1191
1192 if ( placementElem.hasAttribute( u"multipartBehavior"_s ) )
1193 {
1194 mPlacementSettings.setMultiPartBehavior( qgsEnumKeyToValue( placementElem.attribute( u"multipartBehavior"_s ), Qgis::MultiPartLabelingBehavior::LabelLargestPartOnly ) );
1195 }
1196 else
1197 {
1198 // legacy setting
1199 if ( renderingElem.attribute( u"labelPerPart"_s, u"0"_s ).toInt() )
1200 {
1201 mPlacementSettings.setMultiPartBehavior( Qgis::MultiPartLabelingBehavior::LabelEveryPartWithEntireLabel );
1202 }
1203 else
1204 {
1205 mPlacementSettings.setMultiPartBehavior( Qgis::MultiPartLabelingBehavior::LabelLargestPartOnly );
1206 }
1207 }
1208
1209 mPlacementSettings.setWhitespaceCollisionHandling( qgsEnumKeyToValue( placementElem.attribute( u"whitespaceCollisions"_s ), Qgis::LabelWhitespaceCollisionHandling::TreatWhitespaceAsCollision ) );
1210
1211 mLineSettings.setMergeLines( renderingElem.attribute( u"mergeLines"_s ).toInt() );
1212 mThinningSettings.setMinimumFeatureSize( renderingElem.attribute( u"minFeatureSize"_s ).toDouble() );
1213 mThinningSettings.setLimitNumberLabelsEnabled( renderingElem.attribute( u"limitNumLabels"_s, u"0"_s ).toInt() );
1214 mThinningSettings.setMaximumNumberLabels( renderingElem.attribute( u"maxNumLabels"_s, u"2000"_s ).toInt() );
1215
1216 mThinningSettings.setLabelMarginDistance( placementElem.attribute( u"labelMarginDistance"_s, u"0"_s ).toDouble() );
1217 mThinningSettings.setLabelMarginDistanceUnit( QgsUnitTypes::decodeRenderUnit( placementElem.attribute( u"labelMarginDistanceUnit"_s ) ) );
1218 mThinningSettings.setLabelMarginDistanceMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( placementElem.attribute( u"labelMarginDistanceMapUnitScale"_s ) ) );
1219
1220 mThinningSettings.setMinimumDistanceToDuplicate( placementElem.attribute( u"minDistanceToDuplicates"_s, QString::number( QgsLabelThinningSettings::DEFAULT_MINIMUM_DISTANCE_TO_DUPLICATE ) ).toDouble() );
1221 mThinningSettings.setMinimumDistanceToDuplicateUnit( QgsUnitTypes::decodeRenderUnit( placementElem.attribute( u"minDistanceToDuplicatesUnit"_s ) ) );
1222 mThinningSettings.setMinimumDistanceToDuplicateMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( placementElem.attribute( u"minDistanceToDuplicateMapUnitScale"_s ) ) );
1223 mThinningSettings.setAllowDuplicateRemoval( placementElem.attribute( u"allowDuplicateRemoval"_s, u"0"_s ).toInt() );
1224
1225 mObstacleSettings.setIsObstacle( renderingElem.attribute( u"obstacle"_s, u"1"_s ).toInt() );
1226 mObstacleSettings.setFactor( renderingElem.attribute( u"obstacleFactor"_s, u"1"_s ).toDouble() );
1227 mObstacleSettings.setType( static_cast< QgsLabelObstacleSettings::ObstacleType >( renderingElem.attribute( u"obstacleType"_s, QString::number( static_cast< int >( QgsLabelObstacleSettings::ObstacleType::PolygonInterior ) ) ).toUInt() ) );
1228 zIndex = renderingElem.attribute( u"zIndex"_s, u"0.0"_s ).toDouble();
1229 mUnplacedVisibility = static_cast< Qgis::UnplacedLabelVisibility >( renderingElem.attribute( u"unplacedVisibility"_s, QString::number( static_cast< int >( Qgis::UnplacedLabelVisibility::FollowEngineSetting ) ) ).toInt() );
1230
1231 QDomElement ddElem = elem.firstChildElement( u"dd_properties"_s );
1232 if ( !ddElem.isNull() )
1233 {
1234 mDataDefinedProperties.readXml( ddElem, *sPropertyDefinitions() );
1235 }
1236 else
1237 {
1238 // upgrade 2.x style dd project
1239 mDataDefinedProperties.clear();
1240 QDomElement ddElem = elem.firstChildElement( u"data-defined"_s );
1241 readOldDataDefinedPropertyMap( nullptr, &ddElem );
1242 }
1243 // upgrade older data defined settings
1244 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::FontTransp ) )
1245 {
1246 mDataDefinedProperties.setProperty( QgsPalLayerSettings::Property::FontOpacity, QgsProperty::fromExpression( u"100 - (%1)"_s.arg( mDataDefinedProperties.property( QgsPalLayerSettings::Property::FontTransp ).asExpression() ) ) );
1247 mDataDefinedProperties.setProperty( QgsPalLayerSettings::Property::FontTransp, QgsProperty() );
1248 }
1249 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::BufferTransp ) )
1250 {
1251 mDataDefinedProperties.setProperty( QgsPalLayerSettings::Property::BufferOpacity, QgsProperty::fromExpression( u"100 - (%1)"_s.arg( mDataDefinedProperties.property( QgsPalLayerSettings::Property::BufferTransp ).asExpression() ) ) );
1252 mDataDefinedProperties.setProperty( QgsPalLayerSettings::Property::BufferTransp, QgsProperty() );
1253 }
1254 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::ShapeTransparency ) )
1255 {
1256 mDataDefinedProperties.setProperty( QgsPalLayerSettings::Property::ShapeOpacity, QgsProperty::fromExpression( u"100 - (%1)"_s.arg( mDataDefinedProperties.property( QgsPalLayerSettings::Property::ShapeTransparency ).asExpression() ) ) );
1257 mDataDefinedProperties.setProperty( QgsPalLayerSettings::Property::ShapeTransparency, QgsProperty() );
1258 }
1259 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::ShadowTransparency ) )
1260 {
1261 mDataDefinedProperties.setProperty( QgsPalLayerSettings::Property::ShadowOpacity, QgsProperty::fromExpression( u"100 - (%1)"_s.arg( mDataDefinedProperties.property( QgsPalLayerSettings::Property::ShadowTransparency ).asExpression() ) ) );
1262 mDataDefinedProperties.setProperty( QgsPalLayerSettings::Property::ShadowTransparency, QgsProperty() );
1263 }
1264 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::Rotation ) )
1265 {
1266 mDataDefinedProperties.setProperty( QgsPalLayerSettings::Property::LabelRotation, QgsProperty::fromExpression( u"360 - (%1)"_s.arg( mDataDefinedProperties.property( QgsPalLayerSettings::Property::Rotation ).asExpression() ) ) );
1267 mDataDefinedProperties.setProperty( QgsPalLayerSettings::Property::Rotation, QgsProperty() );
1268 }
1269 // older 2.x projects had min/max scale flipped - so change them here.
1270 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::MinScale ) )
1271 {
1272 mDataDefinedProperties.setProperty( QgsPalLayerSettings::Property::MaximumScale, mDataDefinedProperties.property( QgsPalLayerSettings::Property::MinScale ) );
1273 mDataDefinedProperties.setProperty( QgsPalLayerSettings::Property::MinScale, QgsProperty() );
1274 }
1275 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::MaxScale ) )
1276 {
1277 mDataDefinedProperties.setProperty( QgsPalLayerSettings::Property::MinimumScale, mDataDefinedProperties.property( QgsPalLayerSettings::Property::MaxScale ) );
1278 mDataDefinedProperties.setProperty( QgsPalLayerSettings::Property::MaxScale, QgsProperty() );
1279 }
1280
1281 // TODO - replace with registry when multiple callout styles exist
1282 const QString calloutType = elem.attribute( u"calloutType"_s );
1283 if ( calloutType.isEmpty() )
1284 mCallout.reset( QgsCalloutRegistry::defaultCallout() );
1285 else
1286 {
1287 mCallout.reset( QgsApplication::calloutRegistry()->createCallout( calloutType, elem.firstChildElement( u"callout"_s ), context ) );
1288 if ( !mCallout )
1289 mCallout.reset( QgsCalloutRegistry::defaultCallout() );
1290 }
1291}
1292
1293QDomElement QgsPalLayerSettings::writeXml( QDomDocument &doc, const QgsReadWriteContext &context ) const
1294{
1295 QDomElement textStyleElem = mFormat.writeXml( doc, context );
1296
1297 // text style
1298 textStyleElem.setAttribute( u"fieldName"_s, fieldName );
1299 textStyleElem.setAttribute( u"isExpression"_s, isExpression );
1300 QDomElement replacementElem = doc.createElement( u"substitutions"_s );
1301 substitutions.writeXml( replacementElem, doc );
1302 textStyleElem.appendChild( replacementElem );
1303 textStyleElem.setAttribute( u"useSubstitutions"_s, useSubstitutions );
1304 textStyleElem.setAttribute( u"legendString"_s, mLegendString );
1305
1306 // text formatting
1307 QDomElement textFormatElem = doc.createElement( u"text-format"_s );
1308 textFormatElem.setAttribute( u"wrapChar"_s, wrapChar );
1309 textFormatElem.setAttribute( u"autoWrapLength"_s, autoWrapLength );
1310 textFormatElem.setAttribute( u"useMaxLineLengthForAutoWrap"_s, useMaxLineLengthForAutoWrap );
1311 textFormatElem.setAttribute( u"multilineAlign"_s, static_cast< unsigned int >( multilineAlign ) );
1312 textFormatElem.setAttribute( u"addDirectionSymbol"_s, mLineSettings.addDirectionSymbol() );
1313 textFormatElem.setAttribute( u"leftDirectionSymbol"_s, mLineSettings.leftDirectionSymbol() );
1314 textFormatElem.setAttribute( u"rightDirectionSymbol"_s, mLineSettings.rightDirectionSymbol() );
1315 textFormatElem.setAttribute( u"reverseDirectionSymbol"_s, mLineSettings.reverseDirectionSymbol() );
1316 textFormatElem.setAttribute( u"placeDirectionSymbol"_s, static_cast< unsigned int >( mLineSettings.directionSymbolPlacement() ) );
1317 textFormatElem.setAttribute( u"formatNumbers"_s, formatNumbers );
1318 textFormatElem.setAttribute( u"decimals"_s, decimals );
1319 textFormatElem.setAttribute( u"plussign"_s, plusSign );
1320
1321 // placement
1322 QDomElement placementElem = doc.createElement( u"placement"_s );
1323 placementElem.setAttribute( u"placement"_s, static_cast< int >( placement ) );
1324 placementElem.setAttribute( u"polygonPlacementFlags"_s, static_cast< int >( mPolygonPlacementFlags ) );
1325 placementElem.setAttribute( u"placementFlags"_s, static_cast< unsigned int >( mLineSettings.placementFlags() ) );
1326 placementElem.setAttribute( u"centroidWhole"_s, centroidWhole );
1327 placementElem.setAttribute( u"centroidInside"_s, centroidInside );
1328 placementElem.setAttribute( u"predefinedPositionOrder"_s, QgsLabelingUtils::encodePredefinedPositionOrder( mPointSettings.predefinedPositionOrder() ) );
1329 placementElem.setAttribute( u"fitInPolygonOnly"_s, fitInPolygonOnly );
1330 placementElem.setAttribute( u"dist"_s, dist );
1331 placementElem.setAttribute( u"distUnits"_s, QgsUnitTypes::encodeUnit( distUnits ) );
1332 placementElem.setAttribute( u"distMapUnitScale"_s, QgsSymbolLayerUtils::encodeMapUnitScale( distMapUnitScale ) );
1333 placementElem.setAttribute( u"offsetType"_s, static_cast< unsigned int >( offsetType ) );
1334 placementElem.setAttribute( u"quadOffset"_s, static_cast< unsigned int >( mPointSettings.quadrant() ) );
1335 placementElem.setAttribute( u"xOffset"_s, xOffset );
1336 placementElem.setAttribute( u"yOffset"_s, yOffset );
1337 placementElem.setAttribute( u"offsetUnits"_s, QgsUnitTypes::encodeUnit( offsetUnits ) );
1338 placementElem.setAttribute( u"labelOffsetMapUnitScale"_s, QgsSymbolLayerUtils::encodeMapUnitScale( labelOffsetMapUnitScale ) );
1339 placementElem.setAttribute( u"rotationAngle"_s, angleOffset );
1340 placementElem.setAttribute( u"preserveRotation"_s, preserveRotation );
1341 {
1342 // append Angle prefix to maintain compatibility with QGIS < 3.30
1343 const QString rotationUnitString = u"Angle"_s + qgsEnumValueToKey( mRotationUnit );
1344 placementElem.setAttribute( u"rotationUnit"_s, rotationUnitString );
1345 }
1346 placementElem.setAttribute( u"maxCurvedCharAngleIn"_s, maxCurvedCharAngleIn );
1347 placementElem.setAttribute( u"maxCurvedCharAngleOut"_s, maxCurvedCharAngleOut );
1348 placementElem.setAttribute( u"priority"_s, priority );
1349 placementElem.setAttribute( u"repeatDistance"_s, repeatDistance );
1350 placementElem.setAttribute( u"repeatDistanceUnits"_s, QgsUnitTypes::encodeUnit( repeatDistanceUnit ) );
1351 placementElem.setAttribute( u"repeatDistanceMapUnitScale"_s, QgsSymbolLayerUtils::encodeMapUnitScale( repeatDistanceMapUnitScale ) );
1352 placementElem.setAttribute( u"overrunDistance"_s, mLineSettings.overrunDistance() );
1353 placementElem.setAttribute( u"overrunDistanceUnit"_s, QgsUnitTypes::encodeUnit( mLineSettings.overrunDistanceUnit() ) );
1354 placementElem.setAttribute( u"overrunDistanceMapUnitScale"_s, QgsSymbolLayerUtils::encodeMapUnitScale( mLineSettings.overrunDistanceMapUnitScale() ) );
1355 placementElem.setAttribute( u"lineAnchorPercent"_s, mLineSettings.lineAnchorPercent() );
1356 placementElem.setAttribute( u"lineAnchorType"_s, static_cast< int >( mLineSettings.anchorType() ) );
1357 placementElem.setAttribute( u"lineAnchorClipping"_s, static_cast< int >( mLineSettings.anchorClipping() ) );
1358 placementElem.setAttribute( u"lineAnchorTextPoint"_s, qgsEnumValueToKey( mLineSettings.anchorTextPoint() ) );
1359 if ( mLineSettings.curvedLabelMode() != Qgis::CurvedLabelMode::Default )
1360 {
1361 placementElem.setAttribute( u"curvedLabelMode"_s, qgsEnumValueToKey( mLineSettings.curvedLabelMode() ) );
1362 }
1363
1364 placementElem.setAttribute( u"maximumDistance"_s, mPointSettings.maximumDistance() );
1365 placementElem.setAttribute( u"maximumDistanceUnit"_s, QgsUnitTypes::encodeUnit( mPointSettings.maximumDistanceUnit() ) );
1366 placementElem.setAttribute( u"maximumDistanceMapUnitScale"_s, QgsSymbolLayerUtils::encodeMapUnitScale( mPointSettings.maximumDistanceMapUnitScale() ) );
1367
1368 placementElem.setAttribute( u"geometryGenerator"_s, geometryGenerator );
1369 placementElem.setAttribute( u"geometryGeneratorEnabled"_s, geometryGeneratorEnabled );
1370 placementElem.setAttribute( u"geometryGeneratorType"_s, qgsEnumValueToKey( geometryGeneratorType ) + u"Geometry"_s );
1371
1372 placementElem.setAttribute( u"layerType"_s, qgsEnumValueToKey( layerType ) + u"Geometry"_s );
1373
1374 placementElem.setAttribute( u"overlapHandling"_s, qgsEnumValueToKey( mPlacementSettings.overlapHandling() ) );
1375 placementElem.setAttribute( u"prioritization"_s, qgsEnumValueToKey( mPlacementSettings.prioritization() ) );
1376 placementElem.setAttribute( u"allowDegraded"_s, mPlacementSettings.allowDegradedPlacement() ? u"1"_s : u"0"_s );
1377
1378 // rendering
1379 QDomElement renderingElem = doc.createElement( u"rendering"_s );
1380 renderingElem.setAttribute( u"drawLabels"_s, drawLabels );
1381 renderingElem.setAttribute( u"scaleVisibility"_s, scaleVisibility );
1382 renderingElem.setAttribute( u"scaleMin"_s, maximumScale );
1383 renderingElem.setAttribute( u"scaleMax"_s, minimumScale );
1384 renderingElem.setAttribute( u"fontLimitPixelSize"_s, fontLimitPixelSize );
1385 renderingElem.setAttribute( u"fontMinPixelSize"_s, fontMinPixelSize );
1386 renderingElem.setAttribute( u"fontMaxPixelSize"_s, fontMaxPixelSize );
1387 renderingElem.setAttribute( u"upsidedownLabels"_s, static_cast< unsigned int >( upsidedownLabels ) );
1388 placementElem.setAttribute( u"multipartBehavior"_s, qgsEnumValueToKey( mPlacementSettings.multiPartBehavior() ) );
1389 if ( mPlacementSettings.whitespaceCollisionHandling() != Qgis::LabelWhitespaceCollisionHandling::TreatWhitespaceAsCollision )
1390 placementElem.setAttribute( u"whitespaceCollisions"_s, qgsEnumValueToKey( mPlacementSettings.whitespaceCollisionHandling() ) );
1391 renderingElem.setAttribute( u"mergeLines"_s, mLineSettings.mergeLines() );
1392 renderingElem.setAttribute( u"minFeatureSize"_s, mThinningSettings.minimumFeatureSize() );
1393 renderingElem.setAttribute( u"limitNumLabels"_s, mThinningSettings.limitNumberOfLabelsEnabled() );
1394 renderingElem.setAttribute( u"maxNumLabels"_s, mThinningSettings.maximumNumberLabels() );
1395
1396 if ( mThinningSettings.labelMarginDistance() > 0 )
1397 {
1398 placementElem.setAttribute( u"labelMarginDistance"_s, mThinningSettings.labelMarginDistance() );
1399 }
1400 if ( mThinningSettings.labelMarginDistanceUnit() != Qgis::RenderUnit::Millimeters )
1401 {
1402 placementElem.setAttribute( u"labelMarginDistanceUnit"_s, QgsUnitTypes::encodeUnit( mThinningSettings.labelMarginDistanceUnit() ) );
1403 }
1404 if ( !mThinningSettings.labelMarginDistanceMapUnitScale().isNull() )
1405 {
1406 placementElem.setAttribute( u"labelMarginDistanceMapUnitScale"_s, QgsSymbolLayerUtils::encodeMapUnitScale( mThinningSettings.labelMarginDistanceMapUnitScale() ) );
1407 }
1408
1409 if ( mThinningSettings.minimumDistanceToDuplicate() != QgsLabelThinningSettings::DEFAULT_MINIMUM_DISTANCE_TO_DUPLICATE )
1410 {
1411 placementElem.setAttribute( u"minDistanceToDuplicates"_s, mThinningSettings.minimumDistanceToDuplicate() );
1412 }
1413 if ( mThinningSettings.minimumDistanceToDuplicateUnit() != Qgis::RenderUnit::Millimeters )
1414 {
1415 placementElem.setAttribute( u"minDistanceToDuplicatesUnit"_s, QgsUnitTypes::encodeUnit( mThinningSettings.minimumDistanceToDuplicateUnit() ) );
1416 }
1417 if ( !mThinningSettings.minimumDistanceToDuplicateMapUnitScale().isNull() )
1418 {
1419 placementElem.setAttribute( u"minDistanceToDuplicateMapUnitScale"_s, QgsSymbolLayerUtils::encodeMapUnitScale( mThinningSettings.minimumDistanceToDuplicateMapUnitScale() ) );
1420 }
1421 if ( mThinningSettings.allowDuplicateRemoval() )
1422 {
1423 placementElem.setAttribute( u"allowDuplicateRemoval"_s, u"1"_s );
1424 }
1425
1426 renderingElem.setAttribute( u"obstacle"_s, mObstacleSettings.isObstacle() );
1427 renderingElem.setAttribute( u"obstacleFactor"_s, mObstacleSettings.factor() );
1428 renderingElem.setAttribute( u"obstacleType"_s, static_cast< unsigned int >( mObstacleSettings.type() ) );
1429 renderingElem.setAttribute( u"zIndex"_s, zIndex );
1430 renderingElem.setAttribute( u"unplacedVisibility"_s, static_cast< int >( mUnplacedVisibility ) );
1431
1432 QDomElement ddElem = doc.createElement( u"dd_properties"_s );
1433 mDataDefinedProperties.writeXml( ddElem, *sPropertyDefinitions() );
1434
1435 QDomElement elem = doc.createElement( u"settings"_s );
1436 elem.appendChild( textStyleElem );
1437 elem.appendChild( textFormatElem );
1438 elem.appendChild( placementElem );
1439 elem.appendChild( renderingElem );
1440 elem.appendChild( ddElem );
1441
1442 if ( mCallout )
1443 {
1444 elem.setAttribute( u"calloutType"_s, mCallout->type() );
1445 mCallout->saveProperties( doc, elem, context );
1446 }
1447
1448 return elem;
1449}
1450
1452{
1453 mCallout.reset( callout );
1454}
1455
1456QPixmap QgsPalLayerSettings::labelSettingsPreviewPixmap( const QgsPalLayerSettings &settings, QSize size, const QString &previewText, int padding, const QgsScreenProperties &screen )
1457{
1458 const double devicePixelRatio = screen.isValid() ? screen.devicePixelRatio() : 1;
1459
1460 // for now, just use format
1461 QgsTextFormat tempFormat = settings.format();
1462 QPixmap pixmap( size * devicePixelRatio );
1463 pixmap.fill( Qt::transparent );
1464 pixmap.setDevicePixelRatio( devicePixelRatio );
1465 QPainter painter;
1466 painter.begin( &pixmap );
1467
1468 painter.setRenderHint( QPainter::Antialiasing );
1469
1470 const QRectF rect( 0, 0, size.width(), size.height() );
1471
1472 // shameless eye candy - use a subtle gradient when drawing background
1473 painter.setPen( Qt::NoPen );
1474 QColor background1 = tempFormat.previewBackgroundColor();
1475 if ( ( background1.lightnessF() < 0.7 ) )
1476 {
1477 background1 = background1.darker( 125 );
1478 }
1479 else
1480 {
1481 background1 = background1.lighter( 125 );
1482 }
1483 QColor background2 = tempFormat.previewBackgroundColor();
1484 QLinearGradient linearGrad( QPointF( 0, 0 ), QPointF( 0, rect.height() ) );
1485 linearGrad.setColorAt( 0, background1 );
1486 linearGrad.setColorAt( 1, background2 );
1487 painter.setBrush( QBrush( linearGrad ) );
1488 if ( size.width() > 30 )
1489 {
1490 painter.drawRoundedRect( rect, 6, 6 );
1491 }
1492 else
1493 {
1494 // don't use rounded rect for small previews
1495 painter.drawRect( rect );
1496 }
1497 painter.setBrush( Qt::NoBrush );
1498 painter.setPen( Qt::NoPen );
1499 padding += 1; // move text away from background border
1500
1501 QgsRenderContext context;
1502 QgsMapToPixel newCoordXForm;
1503 newCoordXForm.setParameters( 1, 0, 0, 0, 0, 0 );
1504 context.setMapToPixel( newCoordXForm );
1506
1507 if ( screen.isValid() )
1508 {
1509 screen.updateRenderContextForScreen( context );
1510 }
1511 else
1512 {
1513 QWidget *activeWindow = QApplication::activeWindow();
1514 if ( QScreen *screen = activeWindow ? activeWindow->screen() : nullptr )
1515 {
1516 context.setScaleFactor( screen->physicalDotsPerInch() / 25.4 );
1517 context.setDevicePixelRatio( screen->devicePixelRatio() );
1518 }
1519 else
1520 {
1521 context.setScaleFactor( 96.0 / 25.4 );
1522 context.setDevicePixelRatio( 1.0 );
1523 }
1524 }
1525
1527 context.setPainter( &painter );
1528
1529 // slightly inset text to account for buffer/background
1530 const double fontSize = context.convertToPainterUnits( tempFormat.size(), tempFormat.sizeUnit(), tempFormat.sizeMapUnitScale() );
1531 double xtrans = 0;
1532 if ( tempFormat.buffer().enabled() )
1533 xtrans = tempFormat.buffer().sizeUnit() == Qgis::RenderUnit::Percentage
1534 ? fontSize * tempFormat.buffer().size() / 100
1535 : context.convertToPainterUnits( tempFormat.buffer().size(), tempFormat.buffer().sizeUnit(), tempFormat.buffer().sizeMapUnitScale() );
1536 if ( tempFormat.background().enabled() && tempFormat.background().sizeType() != QgsTextBackgroundSettings::SizeFixed )
1537 xtrans = std::max( xtrans, context.convertToPainterUnits( tempFormat.background().size().width(), tempFormat.background().sizeUnit(), tempFormat.background().sizeMapUnitScale() ) );
1538
1539 double ytrans = 0.0;
1540 if ( tempFormat.buffer().enabled() )
1541 ytrans = std::max( ytrans, tempFormat.buffer().sizeUnit() == Qgis::RenderUnit::Percentage
1542 ? fontSize * tempFormat.buffer().size() / 100
1543 : context.convertToPainterUnits( tempFormat.buffer().size(), tempFormat.buffer().sizeUnit(), tempFormat.buffer().sizeMapUnitScale() ) );
1544 if ( tempFormat.background().enabled() )
1545 ytrans = std::max( ytrans, context.convertToPainterUnits( tempFormat.background().size().height(), tempFormat.background().sizeUnit(), tempFormat.background().sizeMapUnitScale() ) );
1546
1547 const QStringList text = QStringList() << ( previewText.isEmpty() ? settings.legendString() : previewText );
1548 const double textHeight = QgsTextRenderer::textHeight( context, tempFormat, text, Qgis::TextLayoutMode::Rectangle );
1549 QRectF textRect = rect;
1550 textRect.setLeft( xtrans + padding );
1551 textRect.setWidth( rect.width() - xtrans - 2 * padding );
1552
1553 if ( textRect.width() > 2000 )
1554 textRect.setWidth( 2000 - 2 * padding );
1555
1556 const double bottom = textRect.height() / 2 + textHeight / 2;
1557 textRect.setTop( bottom - textHeight );
1558 textRect.setBottom( bottom );
1559
1560 const double iconWidth = QFontMetricsF( QFont() ).horizontalAdvance( 'X' ) * Qgis::UI_SCALE_FACTOR;
1561
1562 if ( settings.callout() && settings.callout()->enabled() )
1563 {
1564 // draw callout preview
1565 const double textWidth = QgsTextRenderer::textWidth( context, tempFormat, text );
1566 QgsCallout *callout = settings.callout();
1567 callout->startRender( context );
1568 QgsCallout::QgsCalloutContext calloutContext;
1569 QRectF labelRect( textRect.left() + ( textRect.width() - textWidth ) / 2.0, textRect.top(), textWidth, textRect.height() );
1570 callout->render( context, labelRect, 0, QgsGeometry::fromPointXY( QgsPointXY( labelRect.left() - iconWidth * 1.5, labelRect.bottom() + iconWidth ) ), calloutContext );
1571 callout->stopRender( context );
1572 }
1573
1574 QgsTextRenderer::drawText( textRect, 0, Qgis::TextHorizontalAlignment::Center, text, context, tempFormat );
1575
1576 if ( size.width() > 30 )
1577 {
1578 // draw a label icon
1579
1580 QgsApplication::getThemeIcon( u"labelingSingle.svg"_s ).paint( &painter, QRect(
1581 rect.width() - iconWidth * 3, rect.height() - iconWidth * 3,
1582 iconWidth * 2, iconWidth * 2 ), Qt::AlignRight | Qt::AlignBottom );
1583 }
1584
1585 // draw border on top of text
1586 painter.setBrush( Qt::NoBrush );
1587 painter.setPen( QPen( tempFormat.previewBackgroundColor().darker( 150 ), 0 ) );
1588 if ( size.width() > 30 )
1589 {
1590 painter.drawRoundedRect( rect, 6, 6 );
1591 }
1592 else
1593 {
1594 // don't use rounded rect for small previews
1595 painter.drawRect( rect );
1596 }
1597
1598 painter.end();
1599 return pixmap;
1600}
1601
1603{
1604 return mUnplacedVisibility;
1605}
1606
1608{
1609 mUnplacedVisibility = visibility;
1610}
1611
1612bool QgsPalLayerSettings::checkMinimumSizeMM( const QgsRenderContext &ct, const QgsGeometry &geom, double minSize ) const
1613{
1614 return QgsPalLabeling::checkMinimumSizeMM( ct, geom, minSize );
1615}
1616
1617void QgsPalLayerSettings::calculateLabelSize( const QFontMetricsF *fm, const QString &text, double &labelX, double &labelY, const QgsFeature *f, QgsRenderContext *context, double *rotatedLabelX, double *rotatedLabelY )
1618{
1619 // NOTE: This whole method is deprecated is only kept for 3.x API stability.
1620 // There is no requirement to update the logic here, just leave it as is till we can completely remove for 4.0
1621 if ( !fm || !f )
1622 {
1623 return;
1624 }
1625
1626 QString textCopy( text );
1627
1628 //try to keep < 2.12 API - handle no passed render context
1629 std::unique_ptr< QgsRenderContext > scopedRc;
1630 if ( !context )
1631 {
1632 scopedRc = std::make_unique<QgsRenderContext>( );
1633 if ( f )
1634 scopedRc->expressionContext().setFeature( *f );
1635 }
1636 QgsRenderContext *rc = context ? context : scopedRc.get();
1637
1638 QString wrapchr = wrapChar;
1639 int evalAutoWrapLength = autoWrapLength;
1640 double multilineH = mFormat.lineHeight();
1641 Qgis::TextOrientation orientation = mFormat.orientation();
1642
1643 bool addDirSymb = mLineSettings.addDirectionSymbol();
1644 QString leftDirSymb = mLineSettings.leftDirectionSymbol();
1645 QString rightDirSymb = mLineSettings.rightDirectionSymbol();
1646 QgsLabelLineSettings::DirectionSymbolPlacement placeDirSymb = mLineSettings.directionSymbolPlacement();
1647
1648 if ( f == mCurFeat ) // called internally, use any stored data defined values
1649 {
1650 if ( dataDefinedValues.contains( QgsPalLayerSettings::Property::MultiLineWrapChar ) )
1651 {
1652 wrapchr = dataDefinedValues.value( QgsPalLayerSettings::Property::MultiLineWrapChar ).toString();
1653 }
1654
1655 if ( dataDefinedValues.contains( QgsPalLayerSettings::Property::AutoWrapLength ) )
1656 {
1657 evalAutoWrapLength = dataDefinedValues.value( QgsPalLayerSettings::Property::AutoWrapLength, evalAutoWrapLength ).toInt();
1658 }
1659
1660 if ( dataDefinedValues.contains( QgsPalLayerSettings::Property::MultiLineHeight ) )
1661 {
1662 multilineH = dataDefinedValues.value( QgsPalLayerSettings::Property::MultiLineHeight ).toDouble();
1663 }
1664
1665 if ( dataDefinedValues.contains( QgsPalLayerSettings::Property::TextOrientation ) )
1666 {
1667 orientation = QgsTextRendererUtils::decodeTextOrientation( dataDefinedValues.value( QgsPalLayerSettings::Property::TextOrientation ).toString() );
1668 }
1669
1670 if ( dataDefinedValues.contains( QgsPalLayerSettings::Property::DirSymbDraw ) )
1671 {
1672 addDirSymb = dataDefinedValues.value( QgsPalLayerSettings::Property::DirSymbDraw ).toBool();
1673 }
1674
1675 if ( addDirSymb )
1676 {
1677
1678 if ( dataDefinedValues.contains( QgsPalLayerSettings::Property::DirSymbLeft ) )
1679 {
1680 leftDirSymb = dataDefinedValues.value( QgsPalLayerSettings::Property::DirSymbLeft ).toString();
1681 }
1682 if ( dataDefinedValues.contains( QgsPalLayerSettings::Property::DirSymbRight ) )
1683 {
1684 rightDirSymb = dataDefinedValues.value( QgsPalLayerSettings::Property::DirSymbRight ).toString();
1685 }
1686
1687 if ( dataDefinedValues.contains( QgsPalLayerSettings::Property::DirSymbPlacement ) )
1688 {
1689 placeDirSymb = static_cast< QgsLabelLineSettings::DirectionSymbolPlacement >( dataDefinedValues.value( QgsPalLayerSettings::Property::DirSymbPlacement ).toInt() );
1690 }
1691
1692 }
1693
1694 }
1695 else // called externally with passed-in feature, evaluate data defined
1696 {
1697 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::MultiLineWrapChar ) )
1698 {
1700 wrapchr = mDataDefinedProperties.value( QgsPalLayerSettings::Property::MultiLineWrapChar, rc->expressionContext(), wrapchr ).toString();
1701 }
1702
1703 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::AutoWrapLength ) )
1704 {
1705 rc->expressionContext().setOriginalValueVariable( evalAutoWrapLength );
1706 evalAutoWrapLength = mDataDefinedProperties.value( QgsPalLayerSettings::Property::AutoWrapLength, rc->expressionContext(), evalAutoWrapLength ).toInt();
1707 }
1708
1709 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::MultiLineHeight ) )
1710 {
1711 rc->expressionContext().setOriginalValueVariable( multilineH );
1712 multilineH = mDataDefinedProperties.valueAsDouble( QgsPalLayerSettings::Property::MultiLineHeight, rc->expressionContext(), multilineH );
1713 }
1714
1715 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::TextOrientation ) )
1716 {
1717 QString encoded = QgsTextRendererUtils::encodeTextOrientation( orientation );
1719 orientation = QgsTextRendererUtils::decodeTextOrientation( mDataDefinedProperties.valueAsString( QgsPalLayerSettings::Property::TextOrientation, rc->expressionContext(), encoded ) );
1720 }
1721
1722 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::DirSymbDraw ) )
1723 {
1724 rc->expressionContext().setOriginalValueVariable( addDirSymb );
1725 addDirSymb = mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::Property::DirSymbDraw, rc->expressionContext(), addDirSymb );
1726 }
1727
1728 if ( addDirSymb ) // don't do extra evaluations if not adding a direction symbol
1729 {
1730 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::DirSymbLeft ) )
1731 {
1732 rc->expressionContext().setOriginalValueVariable( leftDirSymb );
1733 leftDirSymb = mDataDefinedProperties.value( QgsPalLayerSettings::Property::DirSymbLeft, rc->expressionContext(), leftDirSymb ).toString();
1734 }
1735
1736 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::DirSymbRight ) )
1737 {
1738 rc->expressionContext().setOriginalValueVariable( rightDirSymb );
1739 rightDirSymb = mDataDefinedProperties.value( QgsPalLayerSettings::Property::DirSymbRight, rc->expressionContext(), rightDirSymb ).toString();
1740 }
1741
1742 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::DirSymbPlacement ) )
1743 {
1744 rc->expressionContext().setOriginalValueVariable( static_cast< int >( placeDirSymb ) );
1745 placeDirSymb = static_cast< QgsLabelLineSettings::DirectionSymbolPlacement >( mDataDefinedProperties.valueAsInt( QgsPalLayerSettings::Property::DirSymbPlacement, rc->expressionContext(), static_cast< int >( placeDirSymb ) ) );
1746 }
1747 }
1748 }
1749
1750 if ( wrapchr.isEmpty() )
1751 {
1752 wrapchr = u"\n"_s; // default to new line delimiter
1753 }
1754
1755 //consider the space needed for the direction symbol
1756 if ( addDirSymb && placement == Qgis::LabelPlacement::Line
1757 && ( !leftDirSymb.isEmpty() || !rightDirSymb.isEmpty() ) )
1758 {
1759 QString dirSym = leftDirSymb;
1760
1761 if ( fm->horizontalAdvance( rightDirSymb ) > fm->horizontalAdvance( dirSym ) )
1762 dirSym = rightDirSymb;
1763
1764 switch ( placeDirSymb )
1765 {
1767 textCopy.append( dirSym );
1768 break;
1769
1772 textCopy.prepend( dirSym + u"\n"_s );
1773 break;
1774 }
1775 }
1776
1777 double w = 0.0, h = 0.0, rw = 0.0, rh = 0.0;
1778 double labelHeight = fm->ascent() + fm->descent(); // ignore +1 for baseline
1779
1780 const QStringList multiLineSplit = QgsPalLabeling::splitToLines( textCopy, wrapchr, evalAutoWrapLength, useMaxLineLengthForAutoWrap );
1781 const int lines = multiLineSplit.size();
1782
1783 const double lineHeightPainterUnits = rc->convertToPainterUnits( mFormat.lineHeight(), mFormat.lineHeightUnit() );
1784
1785 switch ( orientation )
1786 {
1788 {
1789 h += fm->height() + static_cast< double >( ( lines - 1 ) * ( mFormat.lineHeightUnit() == Qgis::RenderUnit::Percentage ? ( labelHeight * multilineH ) : lineHeightPainterUnits ) );
1790
1791 for ( const QString &line : std::as_const( multiLineSplit ) )
1792 {
1793 w = std::max( w, fm->horizontalAdvance( line ) );
1794 }
1795 break;
1796 }
1797
1799 {
1800 double letterSpacing = mFormat.scaledFont( *rc ).letterSpacing();
1801 double labelWidth = fm->maxWidth();
1802 w = labelWidth + ( lines - 1 ) * ( mFormat.lineHeightUnit() == Qgis::RenderUnit::Percentage ? ( labelWidth * multilineH ) : lineHeightPainterUnits );
1803
1804 int maxLineLength = 0;
1805 for ( const QString &line : std::as_const( multiLineSplit ) )
1806 {
1807 maxLineLength = std::max( maxLineLength, static_cast<int>( line.length() ) );
1808 }
1809 h = fm->ascent() * maxLineLength + ( maxLineLength - 1 ) * letterSpacing;
1810 break;
1811 }
1812
1814 {
1815 double widthHorizontal = 0.0;
1816 for ( const QString &line : std::as_const( multiLineSplit ) )
1817 {
1818 widthHorizontal = std::max( widthHorizontal, fm->horizontalAdvance( line ) );
1819 }
1820
1821 double widthVertical = 0.0;
1822 double letterSpacing = mFormat.scaledFont( *rc ).letterSpacing();
1823 double labelWidth = fm->maxWidth();
1824 widthVertical = labelWidth + ( lines - 1 ) * ( mFormat.lineHeightUnit() == Qgis::RenderUnit::Percentage ? ( labelWidth * multilineH ) : lineHeightPainterUnits );
1825
1826 double heightHorizontal = 0.0;
1827 heightHorizontal += fm->height() + static_cast< double >( ( lines - 1 ) * ( mFormat.lineHeightUnit() == Qgis::RenderUnit::Percentage ? ( labelHeight * multilineH ) : lineHeightPainterUnits ) );
1828
1829 double heightVertical = 0.0;
1830 int maxLineLength = 0;
1831 for ( const QString &line : std::as_const( multiLineSplit ) )
1832 {
1833 maxLineLength = std::max( maxLineLength, static_cast<int>( line.length() ) );
1834 }
1835 heightVertical = fm->ascent() * maxLineLength + ( maxLineLength - 1 ) * letterSpacing;
1836
1837 w = widthHorizontal;
1838 rw = heightVertical;
1839 h = heightHorizontal;
1840 rh = widthVertical;
1841 break;
1842 }
1843 }
1844
1845 double uPP = xform->mapUnitsPerPixel();
1846 labelX = w * uPP;
1847 labelY = h * uPP;
1848 if ( rotatedLabelX && rotatedLabelY )
1849 {
1850 *rotatedLabelX = rw * uPP;
1851 *rotatedLabelY = rh * uPP;
1852 }
1853}
1854
1855void QgsPalLayerSettings::calculateLabelMetrics( const QFontMetricsF &fm, QgsRenderContext &context, const QgsTextFormat &format, QgsTextDocument &document, QgsTextDocumentMetrics &documentMetrics, QSizeF &size, QSizeF &rotatedSize, QRectF &outerBounds ) const
1856{
1857 if ( !mCurFeat )
1858 {
1859 return;
1860 }
1861
1862 Qgis::TextOrientation orientation = format.orientation();
1863
1864 bool addDirSymb = mLineSettings.addDirectionSymbol();
1865 QString leftDirSymb = mLineSettings.leftDirectionSymbol();
1866 QString rightDirSymb = mLineSettings.rightDirectionSymbol();
1867 QgsLabelLineSettings::DirectionSymbolPlacement placeDirSymb = mLineSettings.directionSymbolPlacement();
1868 double multilineH = mFormat.lineHeight();
1869
1870 if ( dataDefinedValues.contains( QgsPalLayerSettings::Property::TextOrientation ) )
1871 {
1872 orientation = QgsTextRendererUtils::decodeTextOrientation( dataDefinedValues.value( QgsPalLayerSettings::Property::TextOrientation ).toString() );
1873 }
1874
1875 if ( dataDefinedValues.contains( QgsPalLayerSettings::Property::MultiLineHeight ) )
1876 {
1877 multilineH = dataDefinedValues.value( QgsPalLayerSettings::Property::MultiLineHeight ).toDouble();
1878 }
1879
1880 if ( dataDefinedValues.contains( QgsPalLayerSettings::Property::DirSymbDraw ) )
1881 {
1882 addDirSymb = dataDefinedValues.value( QgsPalLayerSettings::Property::DirSymbDraw ).toBool();
1883 }
1884
1885 if ( addDirSymb )
1886 {
1887
1888 if ( dataDefinedValues.contains( QgsPalLayerSettings::Property::DirSymbLeft ) )
1889 {
1890 leftDirSymb = dataDefinedValues.value( QgsPalLayerSettings::Property::DirSymbLeft ).toString();
1891 }
1892 if ( dataDefinedValues.contains( QgsPalLayerSettings::Property::DirSymbRight ) )
1893 {
1894 rightDirSymb = dataDefinedValues.value( QgsPalLayerSettings::Property::DirSymbRight ).toString();
1895 }
1896
1897 if ( dataDefinedValues.contains( QgsPalLayerSettings::Property::DirSymbPlacement ) )
1898 {
1899 placeDirSymb = static_cast< QgsLabelLineSettings::DirectionSymbolPlacement >( dataDefinedValues.value( QgsPalLayerSettings::Property::DirSymbPlacement ).toInt() );
1900 }
1901 }
1902
1903 const double lineHeightPainterUnits = context.convertToPainterUnits( mFormat.lineHeight(), mFormat.lineHeightUnit() );
1904
1905 //consider the space needed for the direction symbol
1906 QSizeF maximumExtraSpaceAllowance( 0, 0 );
1907 QSizeF minimumSize( 0, 0 );
1908 if ( addDirSymb && placement == Qgis::LabelPlacement::Line
1909 && ( !leftDirSymb.isEmpty() || !rightDirSymb.isEmpty() ) )
1910 {
1911 // we don't know which symbol we'll be rendering yet, so just assume the worst and that
1912 // we'll be rendering the larger one
1913 const QString dirSym = fm.horizontalAdvance( rightDirSymb ) > fm.horizontalAdvance( leftDirSymb )
1914 ? rightDirSymb : leftDirSymb;
1915
1916 switch ( placeDirSymb )
1917 {
1919 maximumExtraSpaceAllowance = QSizeF( fm.horizontalAdvance( dirSym ), 0 );
1920 break;
1921
1924 maximumExtraSpaceAllowance = QSizeF( 0, ( mFormat.lineHeightUnit() == Qgis::RenderUnit::Percentage ? ( ( fm.ascent() + fm.descent() ) * multilineH ) : lineHeightPainterUnits ) );
1925 minimumSize = QSizeF( fm.horizontalAdvance( dirSym ), 0 );
1926 break;
1927 }
1928 }
1929
1930 double w = 0.0;
1931 double h = 0.0;
1932 double rw = 0.0;
1933 double rh = 0.0;
1934
1935 documentMetrics = QgsTextDocumentMetrics::calculateMetrics( document, format, context );
1936 const QSizeF documentSize = documentMetrics.documentSize( Qgis::TextLayoutMode::Labeling, orientation != Qgis::TextOrientation::RotationBased ? orientation : Qgis::TextOrientation::Horizontal );
1937 w = std::max( minimumSize.width(), documentSize.width() + maximumExtraSpaceAllowance.width() );
1938 h = std::max( minimumSize.height(), documentSize.height() + maximumExtraSpaceAllowance.height() );
1939
1940 if ( orientation == Qgis::TextOrientation::RotationBased )
1941 {
1942 const QSizeF rotatedSize = documentMetrics.documentSize( Qgis::TextLayoutMode::Labeling, Qgis::TextOrientation::Vertical );
1943 rh = std::max( minimumSize.width(), rotatedSize.width() + maximumExtraSpaceAllowance.width() );
1944 rw = std::max( minimumSize.height(), rotatedSize.height() + maximumExtraSpaceAllowance.height() );
1945 }
1946
1947 const double uPP = xform->mapUnitsPerPixel();
1948 size = QSizeF( w * uPP, h * uPP );
1949 rotatedSize = QSizeF( rw * uPP, rh * uPP );
1950
1951 // TODO -- does this need to account for maximumExtraSpaceAllowance / minimumSize ? Right now the size
1952 // of line direction symbols will be ignored
1953 const QRectF outerBoundsPixels = documentMetrics.outerBounds( Qgis::TextLayoutMode::Labeling, orientation );
1954
1955 outerBounds = QRectF( outerBoundsPixels.left() * uPP,
1956 outerBoundsPixels.top() * uPP,
1957 outerBoundsPixels.width() * uPP,
1958 outerBoundsPixels.height() * uPP );
1959}
1960
1962{
1963 registerFeatureWithDetails( f, context, QgsGeometry(), nullptr );
1964}
1965
1966bool QgsPalLayerSettings::isLabelVisible( QgsRenderContext &context ) const
1967{
1968// data defined show label? defaults to show label if not set
1969 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::Show ) )
1970 {
1972 if ( !mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::Property::Show, context.expressionContext(), true ) )
1973 {
1974 return false;
1975 }
1976 }
1977
1978// data defined scale visibility?
1979 bool useScaleVisibility = scaleVisibility;
1980 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::ScaleVisibility ) )
1981 useScaleVisibility = mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::Property::ScaleVisibility, context.expressionContext(), scaleVisibility );
1982
1983 if ( useScaleVisibility )
1984 {
1985 // data defined min scale?
1986 double maxScale = maximumScale;
1987 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::MaximumScale ) )
1988 {
1990 maxScale = mDataDefinedProperties.valueAsDouble( QgsPalLayerSettings::Property::MaximumScale, context.expressionContext(), maxScale );
1991 }
1992
1993 // scales closer than 1:1
1994 if ( maxScale < 0 )
1995 {
1996 maxScale = 1 / std::fabs( maxScale );
1997 }
1998
1999 // maxScale is inclusive ( < --> no label )
2000 if ( !qgsDoubleNear( maxScale, 0.0 ) && QgsScaleUtils::lessThanMaximumScale( context.rendererScale(), maxScale ) )
2001 {
2002 return false;
2003 }
2004
2005 // data defined min scale?
2006 double minScale = minimumScale;
2007 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::MinimumScale ) )
2008 {
2010 minScale = mDataDefinedProperties.valueAsDouble( QgsPalLayerSettings::Property::MinimumScale, context.expressionContext(), minScale );
2011 }
2012
2013 // scales closer than 1:1
2014 if ( minScale < 0 )
2015 {
2016 minScale = 1 / std::fabs( minScale );
2017 }
2018
2019 // minScale is exclusive ( >= --> no label )
2020 if ( !qgsDoubleNear( minScale, 0.0 ) && QgsScaleUtils::equalToOrGreaterThanMinimumScale( context.rendererScale(), minScale ) )
2021 {
2022 return false;
2023 }
2024 }
2025
2026 return true;
2027}
2028
2029QgsTextFormat QgsPalLayerSettings::evaluateTextFormat( QgsRenderContext &context, bool &labelIsHidden )
2030{
2031 labelIsHidden = false;
2032 QgsTextFormat evaluatedFormat = mFormat;
2033
2034 QFont labelFont = evaluatedFormat.font();
2035 // labelFont will be added to label feature for use during label painting
2036
2037 // data defined font units?
2038 Qgis::RenderUnit fontunits = evaluatedFormat.sizeUnit();
2039 const QVariant exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Property::FontSizeUnit, context.expressionContext() );
2040 if ( !QgsVariantUtils::isNull( exprVal ) )
2041 {
2042 QString units = exprVal.toString();
2043 if ( !units.isEmpty() )
2044 {
2045 bool ok;
2047 if ( ok )
2048 fontunits = res;
2049 }
2050 }
2051
2052 //data defined label size?
2053 double fontSize = evaluatedFormat.size();
2054 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::Size ) )
2055 {
2056 context.expressionContext().setOriginalValueVariable( fontSize );
2057 fontSize = mDataDefinedProperties.valueAsDouble( QgsPalLayerSettings::Property::Size, context.expressionContext(), fontSize );
2058 }
2059 if ( fontSize <= 0.0 )
2060 {
2061 labelIsHidden = true;
2062 return evaluatedFormat;
2063 }
2064
2065 int fontPixelSize = QgsTextRenderer::sizeToPixel( fontSize, context, fontunits, evaluatedFormat.sizeMapUnitScale() );
2066 // don't try to show font sizes less than 1 pixel (Qt complains)
2067 if ( fontPixelSize < 1 )
2068 {
2069 labelIsHidden = true;
2070 return evaluatedFormat;
2071 }
2072 labelFont.setPixelSize( fontPixelSize );
2073
2074 // NOTE: labelFont now always has pixelSize set, so pointSize or pointSizeF might return -1
2075
2076 // defined 'minimum/maximum pixel font size'?
2077 if ( fontunits == Qgis::RenderUnit::MapUnits )
2078 {
2079 if ( mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::Property::FontLimitPixel, context.expressionContext(), fontLimitPixelSize ) )
2080 {
2081 int fontMinPixel = mDataDefinedProperties.valueAsInt( QgsPalLayerSettings::Property::FontMinPixel, context.expressionContext(), fontMinPixelSize );
2082 int fontMaxPixel = mDataDefinedProperties.valueAsInt( QgsPalLayerSettings::Property::FontMaxPixel, context.expressionContext(), fontMaxPixelSize );
2083
2084 if ( fontMinPixel > labelFont.pixelSize() || labelFont.pixelSize() > fontMaxPixel )
2085 {
2086 labelIsHidden = true;
2087 return evaluatedFormat;
2088 }
2089 }
2090 }
2091
2092 // NOTE: the following parsing functions calculate and store any data defined values for later use in QgsPalLabeling::drawLabeling
2093 // this is done to provide clarity, and because such parsing is not directly related to PAL feature registration calculations
2094
2095 // calculate rest of font attributes and store any data defined values
2096 // this is done here for later use in making label backgrounds part of collision management (when implemented)
2097
2098 // maintain API - capitalization may have been set in textFont
2099 if ( evaluatedFormat.capitalization() == Qgis::Capitalization::MixedCase && mFormat.font().capitalization() != QFont::MixedCase )
2100 {
2101 evaluatedFormat.setCapitalization( static_cast< Qgis::Capitalization >( mFormat.font().capitalization() ) );
2102 }
2103 labelFont.setCapitalization( QFont::MixedCase ); // reset this - we don't use QFont's handling
2104 // as above REQUIRED for maintaining API
2105 evaluatedFormat.setCapitalization( evaluatedFormat.capitalization() );
2106
2107 // data defined font capitalization?
2108 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::FontCase ) )
2109 {
2110 const QVariant exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Property::FontCase, context.expressionContext() );
2111 if ( !QgsVariantUtils::isNull( exprVal ) )
2112 {
2113 QString fcase = exprVal.toString().trimmed();
2114 QgsDebugMsgLevel( u"exprVal FontCase:%1"_s.arg( fcase ), 4 );
2115
2116 if ( !fcase.isEmpty() )
2117 {
2118 if ( fcase.compare( "NoChange"_L1, Qt::CaseInsensitive ) == 0 )
2119 {
2121 }
2122 else if ( fcase.compare( "Upper"_L1, Qt::CaseInsensitive ) == 0 )
2123 {
2125 }
2126 else if ( fcase.compare( "Lower"_L1, Qt::CaseInsensitive ) == 0 )
2127 {
2129 }
2130 else if ( fcase.compare( "Capitalize"_L1, Qt::CaseInsensitive ) == 0 )
2131 {
2133 }
2134 else if ( fcase.compare( "Title"_L1, Qt::CaseInsensitive ) == 0 )
2135 {
2137 }
2138 else if ( fcase.compare( "SmallCaps"_L1, Qt::CaseInsensitive ) == 0 )
2139 {
2141 }
2142 else if ( fcase.compare( "AllSmallCaps"_L1, Qt::CaseInsensitive ) == 0 )
2143 {
2145 }
2146 }
2147 }
2148 }
2149
2150 parseTextStyle( labelFont, fontunits, context );
2151 if ( mDataDefinedProperties.hasActiveProperties() )
2152 {
2153 parseTextFormatting( context );
2154 parseTextBuffer( context );
2155 parseTextMask( context );
2156 parseShapeBackground( context );
2157 parseDropShadow( context );
2158 }
2159
2160 if ( dataDefinedValues.contains( QgsPalLayerSettings::Property::TabStopDistance ) )
2161 {
2162 QList<QgsTextFormat::Tab> tabPositions;
2163 if ( dataDefinedValues.value( QgsPalLayerSettings::Property::TabStopDistance ).userType() == QMetaType::Type::QVariantList )
2164 {
2165 const QVariantList parts = dataDefinedValues.value( QgsPalLayerSettings::Property::TabStopDistance ).toList();
2166 for ( const QVariant &part : parts )
2167 {
2168 tabPositions.append( QgsTextFormat::Tab( part.toDouble() ) );
2169 }
2170 evaluatedFormat.setTabPositions( tabPositions );
2171 }
2172 else if ( dataDefinedValues.value( QgsPalLayerSettings::Property::TabStopDistance ).userType() == QMetaType::Type::QStringList )
2173 {
2174 const QStringList parts = dataDefinedValues.value( QgsPalLayerSettings::Property::TabStopDistance ).toStringList();
2175 for ( const QString &part : parts )
2176 {
2177 tabPositions.append( QgsTextFormat::Tab( part.toDouble() ) );
2178 }
2179 evaluatedFormat.setTabPositions( tabPositions );
2180 }
2181 else
2182 {
2183 evaluatedFormat.setTabPositions( tabPositions );
2184 evaluatedFormat.setTabStopDistance( dataDefinedValues.value( QgsPalLayerSettings::Property::TabStopDistance ).toDouble() );
2185 }
2186 }
2187
2188 evaluatedFormat.setFont( labelFont );
2189 // undo scaling by symbology reference scale, as this would have been applied in the previous call to QgsTextRenderer::sizeToPixel and we risk
2190 // double-applying it if we don't re-adjust, since all the text format metric calculations assume an unscaled format font size is present
2191 const double symbologyReferenceScaleFactor = context.symbologyReferenceScale() > 0 ? context.symbologyReferenceScale() / context.rendererScale() : 1;
2192 evaluatedFormat.setSize( labelFont.pixelSize() / symbologyReferenceScaleFactor );
2193 evaluatedFormat.setSizeUnit( Qgis::RenderUnit::Pixels );
2194
2195 return evaluatedFormat;
2196}
2197
2198bool QgsPalLayerSettings::evaluateLabelContent( const QgsFeature &feature, QgsRenderContext &context, bool allowMultipleLines, QString &labelText, QgsTextDocument &document, const QgsTextFormat &format ) const
2199{
2200 // Check to see if we are using an expression string.
2201 if ( isExpression )
2202 {
2203 QgsExpression *exp = getLabelExpression();
2204 if ( exp->hasParserError() )
2205 {
2206 QgsDebugMsgLevel( u"Expression parser error:%1"_s.arg( exp->parserErrorString() ), 4 );
2207 return false;
2208 }
2209
2210 QVariant result = exp->evaluate( &context.expressionContext() ); // expression prepared in QgsPalLabeling::prepareLayer()
2211 if ( exp->hasEvalError() )
2212 {
2213 QgsDebugMsgLevel( u"Expression parser eval error:%1"_s.arg( exp->evalErrorString() ), 4 );
2214 return false;
2215 }
2216 labelText = QgsVariantUtils::isNull( result ) ? QString() : result.toString();
2217 }
2218 else
2219 {
2220 const QVariant &v = feature.attribute( fieldIndex );
2221 labelText = QgsVariantUtils::isNull( v ) ? QString() : v.toString();
2222 }
2223
2224 // TODO -- this is in the wrong place. We should be substituting the text only, ie after we have parsed
2225 // any HTML tags to a text document.
2226
2227 // apply text replacements
2228 if ( useSubstitutions )
2229 {
2230 labelText = substitutions.process( labelText );
2231 }
2232
2233 // TODO -- this is in the wrong place. We should be capitalizing the text only, ie after we have parsed
2234 // any HTML tags to a text document.
2235 labelText = QgsStringUtils::capitalize( labelText, format.capitalization() );
2236
2237 // TODO -- this is in the wrong place. We should be formatting numbers AFTER converting HTML to documents
2238
2239 // format number if label text is coercible to a number
2240 bool evalFormatNumbers = formatNumbers;
2241 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::NumFormat ) )
2242 {
2243 evalFormatNumbers = mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::Property::NumFormat, context.expressionContext(), evalFormatNumbers );
2244 }
2245 if ( evalFormatNumbers )
2246 {
2247 // data defined decimal places?
2248 int decimalPlaces = mDataDefinedProperties.valueAsInt( QgsPalLayerSettings::Property::NumDecimals, context.expressionContext(), decimals );
2249 if ( decimalPlaces <= 0 ) // needs to be positive
2250 decimalPlaces = decimals;
2251
2252 // data defined plus sign?
2253 bool signPlus = mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::Property::NumPlusSign, context.expressionContext(), plusSign );
2254
2255 QVariant textV( labelText );
2256 bool ok;
2257 double d = textV.toDouble( &ok );
2258 if ( ok )
2259 {
2260 QString numberFormat;
2261 if ( d > 0 && signPlus )
2262 {
2263 numberFormat.append( '+' );
2264 }
2265 numberFormat.append( "%1" );
2266 labelText = numberFormat.arg( QLocale().toString( d, 'f', decimalPlaces ) );
2267 }
2268 }
2269
2270 document = QgsTextDocument::fromTextAndFormat( {labelText }, format );
2271
2272 QString wrapchr = wrapChar;
2273 int evalAutoWrapLength = autoWrapLength;
2274 if ( dataDefinedValues.contains( QgsPalLayerSettings::Property::MultiLineWrapChar ) )
2275 {
2276 wrapchr = dataDefinedValues.value( QgsPalLayerSettings::Property::MultiLineWrapChar ).toString();
2277 }
2278 if ( dataDefinedValues.contains( QgsPalLayerSettings::Property::AutoWrapLength ) )
2279 {
2280 evalAutoWrapLength = dataDefinedValues.value( QgsPalLayerSettings::Property::AutoWrapLength, evalAutoWrapLength ).toInt();
2281 }
2282
2283 if ( wrapchr.isEmpty() )
2284 {
2285 wrapchr = u"\n"_s; // default to new line delimiter
2286 }
2287
2288 if ( allowMultipleLines )
2289 {
2290 document.splitLines( wrapchr, evalAutoWrapLength, useMaxLineLengthForAutoWrap );
2291 }
2292
2293 return true;
2294}
2295
2296QgsGeometry QgsPalLayerSettings::evaluateLabelGeometry( const QgsFeature &feature, QgsRenderContext &context, const QgsLabelLineSettings &lineSettings ) const
2297{
2298 QgsGeometry geom = feature.geometry();
2299 if ( geom.isEmpty() )
2300 {
2301 return geom;
2302 }
2303
2304 // simplify?
2305 bool allowSimplify = true;
2306 switch ( placement )
2307 {
2310 {
2311 switch ( lineSettings.curvedLabelMode() )
2312 {
2314 // don't simplify -- this would remove vertices, making characters out of sync with remaining vertices
2315 allowSimplify = false;
2316 break;
2320 break;
2321 }
2322 break;
2323 }
2324
2332 break;
2333 }
2334
2335 const QgsVectorSimplifyMethod &simplifyMethod = context.vectorSimplifyMethod();
2336 std::unique_ptr<QgsGeometry> scopedClonedGeom;
2338 {
2339 unsigned int simplifyHints = simplifyMethod.simplifyHints() | QgsMapToPixelSimplifier::SimplifyEnvelope;
2340 const Qgis::VectorSimplificationAlgorithm simplifyAlgorithm = simplifyMethod.simplifyAlgorithm();
2341 QgsMapToPixelSimplifier simplifier( simplifyHints, simplifyMethod.tolerance(), simplifyAlgorithm );
2342 geom = simplifier.simplify( geom );
2343 }
2344
2345 return geom;
2346}
2347
2348std::vector<std::unique_ptr<QgsLabelFeature> > QgsPalLayerSettings::registerFeatureWithDetails( const QgsFeature &f, QgsRenderContext &context, QgsGeometry obstacleGeometry, const QgsSymbol *symbol )
2349{
2350 QVariant exprVal; // value() is repeatedly nulled on data defined evaluation and replaced when successful
2351 mCurFeat = &f;
2352
2353 // data defined is obstacle? calculate this first, to avoid wasting time working with obstacles we don't require
2354 bool isObstacle = mObstacleSettings.isObstacle();
2355 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::IsObstacle ) )
2356 isObstacle = mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::Property::IsObstacle, context.expressionContext(), isObstacle ); // default to layer default
2357
2358 std::vector<std::unique_ptr<QgsLabelFeature> > res;
2359
2360 // possibly an obstacle-only feature
2361 if ( !drawLabels )
2362 {
2363 if ( isObstacle )
2364 {
2365 std::unique_ptr< QgsLabelFeature > obstacle = registerObstacleFeature( f, context, obstacleGeometry );
2366 if ( obstacle )
2367 res.emplace_back( std::move( obstacle ) );
2368 return res;
2369 }
2370 else
2371 {
2372 return {};
2373 }
2374 }
2375
2376 if ( !isLabelVisible( context ) )
2377 return {};
2378
2379 QgsFeature feature = f;
2381 {
2382 const QgsGeometry geometry = mGeometryGeneratorExpression.evaluate( &context.expressionContext() ).value<QgsGeometry>();
2383 if ( mGeometryGeneratorExpression.hasEvalError() )
2384 QgsMessageLog::logMessage( mGeometryGeneratorExpression.evalErrorString(), QObject::tr( "Labeling" ) );
2385
2386 if ( obstacleGeometry.isNull() )
2387 {
2388 // if an explicit obstacle geometry hasn't been set, we must always use the original feature geometry
2389 // as the obstacle -- because we want to use the geometry which was used to render the symbology
2390 // for the feature as the obstacle for other layers' labels, NOT the generated geometry which is used
2391 // only to place labels for this layer.
2392 obstacleGeometry = f.geometry();
2393 }
2394
2395 feature.setGeometry( geometry );
2396 }
2397
2398 // store data defined-derived values for later adding to label feature for use during rendering
2399 dataDefinedValues.clear();
2400
2401 // evaluate text format and font properties
2402 bool labelIsHidden = false;
2403 QgsTextFormat evaluatedFormat = evaluateTextFormat( context, labelIsHidden );
2404 if ( labelIsHidden )
2405 return {};
2406
2407 QgsLabelPlacementSettings placementSettings = mPlacementSettings;
2408 placementSettings.updateDataDefinedProperties( mDataDefinedProperties, context.expressionContext() );
2409
2410 QString labelText;
2411 QgsTextDocument doc;
2412 bool allowMultipleLines = true;
2413 switch ( placement )
2414 {
2417 {
2418 switch ( placementSettings.multiPartBehavior() )
2419 {
2422 // for these curved placements we only support a single line of text
2423 allowMultipleLines = false;
2424 break;
2425
2427 // this mode permits multiple lines, as we'll be splitting them over the geometry parts
2428 allowMultipleLines = true;
2429 break;
2430 }
2431 break;
2432 }
2433
2441 {
2442 break;
2443 }
2444 }
2445 if ( !evaluateLabelContent( feature, context, allowMultipleLines, labelText, doc, evaluatedFormat ) )
2446 return {};
2447
2448 // geometry
2449 QgsLabelLineSettings lineSettings = mLineSettings;
2450 lineSettings.updateDataDefinedProperties( mDataDefinedProperties, context.expressionContext() );
2451
2452 QgsLabelPointSettings pointSettings = mPointSettings;
2453 pointSettings.updateDataDefinedProperties( mDataDefinedProperties, context.expressionContext() );
2454
2455 QgsGeometry geom = evaluateLabelGeometry( feature, context, lineSettings );
2456 if ( geom.isEmpty() )
2457 return {};
2458
2459
2460 // data defined centroid whole or clipped?
2461 bool wholeCentroid = centroidWhole;
2462 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::CentroidWhole ) )
2463 {
2464 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Property::CentroidWhole, context.expressionContext() );
2465 if ( !QgsVariantUtils::isNull( exprVal ) )
2466 {
2467 QString str = exprVal.toString().trimmed();
2468 QgsDebugMsgLevel( u"exprVal CentroidWhole:%1"_s.arg( str ), 4 );
2469
2470 if ( !str.isEmpty() )
2471 {
2472 if ( str.compare( "Visible"_L1, Qt::CaseInsensitive ) == 0 )
2473 {
2474 wholeCentroid = false;
2475 }
2476 else if ( str.compare( "Whole"_L1, Qt::CaseInsensitive ) == 0 )
2477 {
2478 wholeCentroid = true;
2479 }
2480 }
2481 }
2482 }
2483
2484 // whether we're going to create a centroid for polygon
2485 bool centroidPoly = ( ( placement == Qgis::LabelPlacement::AroundPoint
2487 && geom.type() == Qgis::GeometryType::Polygon );
2488
2489 // CLIP the geometry if it is bigger than the extent
2490 // don't clip if centroid is requested for whole feature
2491 bool doClip = false;
2492 if ( !centroidPoly || !wholeCentroid )
2493 {
2494 doClip = true;
2495 }
2496
2498 {
2499 if ( !obstacleGeometry.isNull() && QgsPalLabeling::geometryRequiresPreparation( obstacleGeometry, context, ct, doClip ? extentGeom : QgsGeometry(), lineSettings.mergeLines() ) )
2500 {
2501 obstacleGeometry = QgsGeometry( QgsPalLabeling::prepareGeometry( obstacleGeometry, context, ct, doClip ? extentGeom : QgsGeometry(), lineSettings.mergeLines() ) );
2502 }
2503 }
2504
2505 switch ( placementSettings.multiPartBehavior() )
2506 {
2509 {
2510 std::unique_ptr< QgsTextLabelFeature > label = generateLabelFeature( context, feature, 0, geom, obstacleGeometry, doc, labelText, evaluatedFormat, symbol, lineSettings, pointSettings, placementSettings, isObstacle, doClip );
2511 if ( label )
2512 res.emplace_back( std::move( label ) );
2513 break;
2514 }
2515
2517 {
2518 const QVector< QgsGeometry > geometryParts = geom.asGeometryCollection();
2519 const QVector< QgsTextDocument > documentParts = doc.splitBlocksToDocuments();
2520 const std::size_t partCount = std::min( geometryParts.size(), documentParts.size() );
2521 res.reserve( partCount );
2522 for ( std::size_t i = 0; i < partCount; ++i )
2523 {
2524 std::unique_ptr< QgsTextLabelFeature > label = generateLabelFeature( context, feature, static_cast< int >( i ), geometryParts[i], obstacleGeometry, documentParts[i], labelText, evaluatedFormat, symbol, lineSettings, pointSettings, placementSettings, isObstacle, doClip );
2525 if ( label )
2526 res.emplace_back( std::move( label ) );
2527 }
2528
2529 break;
2530 }
2531 }
2532
2533 return res;
2534}
2535
2536std::unique_ptr< QgsTextLabelFeature> QgsPalLayerSettings::generateLabelFeature( QgsRenderContext &context, const QgsFeature &feature, int subPartId, QgsGeometry geom, const QgsGeometry &obstacleGeometry, QgsTextDocument doc, const QString &labelText, const QgsTextFormat &evaluatedFormat, const QgsSymbol *symbol, const QgsLabelLineSettings &lineSettings, const QgsLabelPointSettings &pointSettings, const QgsLabelPlacementSettings &placementSettings, bool isObstacle, bool doClip ) const
2537{
2538 QVariant exprVal;
2539 QSizeF labelSize;
2540 QSizeF rotatedSize;
2541
2542 QgsTextDocumentMetrics documentMetrics;
2543 QRectF outerBounds;
2544 QFontMetricsF labelFontMetrics( evaluatedFormat.font() );
2545 calculateLabelMetrics( labelFontMetrics, context, evaluatedFormat, doc, documentMetrics, labelSize, rotatedSize, outerBounds );
2546
2547 // maximum angle between curved label characters (hardcoded defaults used in QGIS <2.0)
2548 //
2549 double maxcharanglein = 20.0; // range 20.0-60.0
2550 double maxcharangleout = -20.0; // range 20.0-95.0
2551
2552 switch ( placement )
2553 {
2556 {
2557 maxcharanglein = maxCurvedCharAngleIn;
2558 maxcharangleout = maxCurvedCharAngleOut;
2559
2560 //data defined maximum angle between curved label characters?
2561 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::CurvedCharAngleInOut ) )
2562 {
2563 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Property::CurvedCharAngleInOut, context.expressionContext() );
2564 bool ok = false;
2565 const QPointF maxcharanglePt = QgsSymbolLayerUtils::toPoint( exprVal, &ok );
2566 if ( ok )
2567 {
2568 maxcharanglein = std::clamp( static_cast< double >( maxcharanglePt.x() ), 20.0, 60.0 );
2569 maxcharangleout = std::clamp( static_cast< double >( maxcharanglePt.y() ), 20.0, 95.0 );
2570 }
2571 }
2572 // make sure maxcharangleout is always negative
2573 maxcharangleout = -( std::fabs( maxcharangleout ) );
2574 break;
2575 }
2576
2584 break;
2585 }
2586
2587 if ( !context.featureClipGeometry().isEmpty() )
2588 {
2589 const Qgis::GeometryType expectedType = geom.type();
2590 geom = geom.intersection( context.featureClipGeometry() );
2591 geom.convertGeometryCollectionToSubclass( expectedType );
2592 }
2593
2594 Qgis::LabelPolygonPlacementFlags polygonPlacement = mPolygonPlacementFlags;
2595 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::PolygonLabelOutside ) )
2596 {
2597 const QVariant dataDefinedOutside = mDataDefinedProperties.value( QgsPalLayerSettings::Property::PolygonLabelOutside, context.expressionContext() );
2598 if ( !QgsVariantUtils::isNull( dataDefinedOutside ) )
2599 {
2600 if ( dataDefinedOutside.userType() == QMetaType::Type::QString )
2601 {
2602 const QString value = dataDefinedOutside.toString().trimmed();
2603 if ( value.compare( "force"_L1, Qt::CaseInsensitive ) == 0 )
2604 {
2605 // forced outside placement -- remove inside flag, add outside flag
2606 polygonPlacement &= ~static_cast< int >( Qgis::LabelPolygonPlacementFlag::AllowPlacementInsideOfPolygon );
2608 }
2609 else if ( value.compare( "yes"_L1, Qt::CaseInsensitive ) == 0 )
2610 {
2611 // permit outside placement
2613 }
2614 else if ( value.compare( "no"_L1, Qt::CaseInsensitive ) == 0 )
2615 {
2616 // block outside placement
2617 polygonPlacement &= ~static_cast< int >( Qgis::LabelPolygonPlacementFlag::AllowPlacementOutsideOfPolygon );
2618 }
2619 }
2620 else
2621 {
2622 if ( dataDefinedOutside.toBool() )
2623 {
2624 // permit outside placement
2626 }
2627 else
2628 {
2629 // block outside placement
2630 polygonPlacement &= ~static_cast< int >( Qgis::LabelPolygonPlacementFlag::AllowPlacementOutsideOfPolygon );
2631 }
2632 }
2633 }
2634 }
2635
2637 {
2638 switch ( lineSettings.anchorClipping() )
2639 {
2641 break;
2642
2644 doClip = false;
2645 break;
2646 }
2647
2649 {
2650 switch ( lineSettings.curvedLabelMode() )
2651 {
2653 // don't clip geometries when in this mode, or vertices will get out-of-sync with their corresponding
2654 // characters!
2655 doClip = false;
2656 break;
2660 break;
2661 }
2662 }
2663 }
2664
2665 // if using fitInPolygonOnly option, generate the permissible zone (must happen before geometry is modified - e.g.,
2666 // as a result of using perimeter based labeling and the geometry is converted to a boundary)
2667 // note that we also force this if we are permitting labels to be placed outside of polygons too!
2668 QgsGeometry permissibleZone;
2670 {
2671 permissibleZone = geom;
2672 if ( QgsPalLabeling::geometryRequiresPreparation( permissibleZone, context, ct, doClip ? extentGeom : QgsGeometry(), lineSettings.mergeLines() ) )
2673 {
2674 permissibleZone = QgsPalLabeling::prepareGeometry( permissibleZone, context, ct, doClip ? extentGeom : QgsGeometry(), lineSettings.mergeLines() );
2675 }
2676 }
2677
2678 // if using perimeter based labeling for polygons, get the polygon's
2679 // linear boundary and use that for the label geometry
2680 if ( ( geom.type() == Qgis::GeometryType::Polygon )
2682 {
2683 geom = QgsGeometry( geom.constGet()->boundary() );
2684 }
2685
2686 geos::unique_ptr geos_geom_clone;
2687 if ( QgsPalLabeling::geometryRequiresPreparation( geom, context, ct, doClip ? extentGeom : QgsGeometry(), lineSettings.mergeLines() ) )
2688 {
2689 geom = QgsPalLabeling::prepareGeometry( geom, context, ct, doClip ? extentGeom : QgsGeometry(), lineSettings.mergeLines() );
2690
2691 if ( geom.isEmpty() )
2692 return {};
2693 }
2695
2696 QgsLabelThinningSettings featureThinningSettings = mThinningSettings;
2697 featureThinningSettings.updateDataDefinedProperties( mDataDefinedProperties, context.expressionContext() );
2698
2699 double minimumSize = 0.0;
2700 if ( featureThinningSettings.minimumFeatureSize() > 0 )
2701 {
2702 // for minimum feature size on merged lines, we need to delay the filtering after the merging occurred in PAL
2703 if ( geom.type() == Qgis::GeometryType::Line && lineSettings.mergeLines() )
2704 {
2705 minimumSize = context.convertToMapUnits( featureThinningSettings.minimumFeatureSize(), Qgis::RenderUnit::Millimeters );
2706 }
2707 else
2708 {
2709 if ( !checkMinimumSizeMM( context, geom, featureThinningSettings.minimumFeatureSize() ) )
2710 return {};
2711 }
2712 }
2713
2714 if ( !geos_geom_clone )
2715 return {}; // invalid geometry
2716
2717 // likelihood exists label will be registered with PAL and may be drawn
2718 // check if max number of features to label (already registered with PAL) has been reached
2719 // Debug output at end of QgsPalLabeling::drawLabeling(), when deleting temp geometries
2720 if ( featureThinningSettings.limitNumberOfLabelsEnabled() )
2721 {
2722 if ( !featureThinningSettings.maximumNumberLabels() )
2723 {
2724 return {};
2725 }
2726 if ( mFeatsRegPal >= featureThinningSettings.maximumNumberLabels() )
2727 {
2728 return {};
2729 }
2730
2731 int divNum = static_cast< int >( ( static_cast< double >( mFeaturesToLabel ) / featureThinningSettings.maximumNumberLabels() ) + 0.5 ); // NOLINT
2732 if ( divNum && ( mFeatsRegPal == static_cast< int >( mFeatsSendingToPal / divNum ) ) )
2733 {
2734 mFeatsSendingToPal += 1;
2735 if ( divNum && mFeatsSendingToPal % divNum )
2736 {
2737 return {};
2738 }
2739 }
2740 }
2741
2742 //data defined position / alignment / rotation?
2743 bool layerDefinedRotation = false;
2744 bool dataDefinedRotation = false;
2745 double xPos = 0.0, yPos = 0.0;
2746 double angleInRadians = 0.0;
2747 double quadOffsetX = 0.0, quadOffsetY = 0.0;
2748 double offsetX = 0.0, offsetY = 0.0;
2749 QgsPointXY anchorPosition;
2750
2752 {
2753 anchorPosition = geom.centroid().asPoint();
2754 }
2755 //x/y shift in case of alignment
2756 double xdiff = 0.0;
2757 double ydiff = 0.0;
2758
2759 //data defined quadrant offset?
2760 bool ddFixedQuad = false;
2761 Qgis::LabelQuadrantPosition quadOff = pointSettings.quadrant();
2762 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::OffsetQuad ) )
2763 {
2764 context.expressionContext().setOriginalValueVariable( static_cast< int >( quadOff ) );
2765 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Property::OffsetQuad, context.expressionContext() );
2766 if ( !QgsVariantUtils::isNull( exprVal ) )
2767 {
2768 bool ok;
2769 int quadInt = exprVal.toInt( &ok );
2770 if ( ok && 0 <= quadInt && quadInt <= 8 )
2771 {
2772 quadOff = static_cast< Qgis::LabelQuadrantPosition >( quadInt );
2773 ddFixedQuad = true;
2774 }
2775 }
2776 }
2777
2778 // adjust quadrant offset of labels
2779 switch ( quadOff )
2780 {
2782 quadOffsetX = -1.0;
2783 quadOffsetY = 1.0;
2784 break;
2786 quadOffsetX = 0.0;
2787 quadOffsetY = 1.0;
2788 break;
2790 quadOffsetX = 1.0;
2791 quadOffsetY = 1.0;
2792 break;
2794 quadOffsetX = -1.0;
2795 quadOffsetY = 0.0;
2796 break;
2798 quadOffsetX = 1.0;
2799 quadOffsetY = 0.0;
2800 break;
2802 quadOffsetX = -1.0;
2803 quadOffsetY = -1.0;
2804 break;
2806 quadOffsetX = 0.0;
2807 quadOffsetY = -1.0;
2808 break;
2810 quadOffsetX = 1.0;
2811 quadOffsetY = -1.0;
2812 break;
2814 break;
2815 }
2816
2817 //data defined label offset?
2818 double xOff = xOffset;
2819 double yOff = yOffset;
2820 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::OffsetXY ) )
2821 {
2823 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Property::OffsetXY, context.expressionContext() );
2824 bool ok = false;
2825 const QPointF ddOffPt = QgsSymbolLayerUtils::toPoint( exprVal, &ok );
2826 if ( ok )
2827 {
2828 xOff = ddOffPt.x();
2829 yOff = ddOffPt.y();
2830 }
2831 }
2832
2833 // data defined label offset units?
2834 Qgis::RenderUnit offUnit = offsetUnits;
2835 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::OffsetUnits ) )
2836 {
2837 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Property::OffsetUnits, context.expressionContext() );
2838 if ( !QgsVariantUtils::isNull( exprVal ) )
2839 {
2840 QString units = exprVal.toString().trimmed();
2841 if ( !units.isEmpty() )
2842 {
2843 bool ok = false;
2844 Qgis::RenderUnit decodedUnits = QgsUnitTypes::decodeRenderUnit( units, &ok );
2845 if ( ok )
2846 {
2847 offUnit = decodedUnits;
2848 }
2849 }
2850 }
2851 }
2852
2853 // adjust offset of labels to match chosen unit and map scale
2854 // offsets match those of symbology: -x = left, -y = up
2855 offsetX = context.convertToMapUnits( xOff, offUnit, labelOffsetMapUnitScale );
2856 // must be negative to match symbology offset direction
2857 offsetY = context.convertToMapUnits( -yOff, offUnit, labelOffsetMapUnitScale );
2858
2859 // layer defined rotation?
2860 if ( !qgsDoubleNear( angleOffset, 0.0 ) )
2861 {
2862 layerDefinedRotation = true;
2863 angleInRadians = ( 360 - angleOffset ) * M_PI / 180; // convert to radians counterclockwise
2864 }
2865
2866 const QgsMapToPixel &m2p = context.mapToPixel();
2867 //data defined rotation?
2868 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::LabelRotation ) )
2869 {
2871 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Property::LabelRotation, context.expressionContext() );
2872 if ( !QgsVariantUtils::isNull( exprVal ) )
2873 {
2874 bool ok;
2875 const double rotation = exprVal.toDouble( &ok );
2876 if ( ok )
2877 {
2878 dataDefinedRotation = true;
2879
2880 double rotationDegrees = rotation * QgsUnitTypes::fromUnitToUnitFactor( mRotationUnit,
2882
2883 // TODO: add setting to disable having data defined rotation follow
2884 // map rotation ?
2885 rotationDegrees += m2p.mapRotation();
2886 angleInRadians = ( 360 - rotationDegrees ) * M_PI / 180.0;
2887 }
2888 }
2889 }
2890
2891 bool hasDataDefinedPosition = false;
2892 {
2893 bool ddPosition = false;
2894
2895 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::PositionX )
2896 && mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::PositionY ) )
2897 {
2898 const QVariant xPosProperty = mDataDefinedProperties.value( QgsPalLayerSettings::Property::PositionX, context.expressionContext() );
2899 const QVariant yPosProperty = mDataDefinedProperties.value( QgsPalLayerSettings::Property::PositionY, context.expressionContext() );
2900 if ( !QgsVariantUtils::isNull( xPosProperty )
2901 && !QgsVariantUtils::isNull( yPosProperty ) )
2902 {
2903 ddPosition = true;
2904
2905 bool ddXPos = false, ddYPos = false;
2906 xPos = xPosProperty.toDouble( &ddXPos );
2907 yPos = yPosProperty.toDouble( &ddYPos );
2908 if ( ddXPos && ddYPos )
2909 hasDataDefinedPosition = true;
2910 }
2911 }
2912 else if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::PositionPoint ) )
2913 {
2914 const QVariant pointPosProperty = mDataDefinedProperties.value( QgsPalLayerSettings::Property::PositionPoint, context.expressionContext() );
2915 if ( !QgsVariantUtils::isNull( pointPosProperty ) )
2916 {
2917 ddPosition = true;
2918
2919 QgsPoint point;
2920 if ( pointPosProperty.userType() == qMetaTypeId<QgsReferencedGeometry>() )
2921 {
2922 QgsReferencedGeometry referencedGeometryPoint = pointPosProperty.value<QgsReferencedGeometry>();
2923 point = QgsPoint( referencedGeometryPoint.asPoint() );
2924
2925 if ( !referencedGeometryPoint.isNull()
2926 && ct.sourceCrs() != referencedGeometryPoint.crs() )
2927 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 );
2928 }
2929 else if ( pointPosProperty.userType() == qMetaTypeId< QgsGeometry>() )
2930 {
2931 point = QgsPoint( pointPosProperty.value<QgsGeometry>().asPoint() );
2932 }
2933
2934 if ( !point.isEmpty() )
2935 {
2936 hasDataDefinedPosition = true;
2937
2938 xPos = point.x();
2939 yPos = point.y();
2940 }
2941 }
2942 }
2943
2944 if ( ddPosition )
2945 {
2946 //data defined position. But field values could be NULL -> positions will be generated by PAL
2947 if ( hasDataDefinedPosition )
2948 {
2949 // layer rotation set, but don't rotate pinned labels unless data defined
2950 if ( layerDefinedRotation && !dataDefinedRotation )
2951 {
2952 angleInRadians = 0.0;
2953 }
2954
2955 //horizontal alignment
2956 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::Hali ) )
2957 {
2958 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Property::Hali, context.expressionContext() );
2959 if ( !QgsVariantUtils::isNull( exprVal ) )
2960 {
2961 QString haliString = exprVal.toString();
2962 if ( haliString.compare( "Center"_L1, Qt::CaseInsensitive ) == 0 )
2963 {
2964 xdiff -= labelSize.width() / 2.0;
2965 }
2966 else if ( haliString.compare( "Right"_L1, Qt::CaseInsensitive ) == 0 )
2967 {
2968 xdiff -= labelSize.width();
2969 }
2970 }
2971 }
2972
2973 //vertical alignment
2974 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::Vali ) )
2975 {
2976 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Property::Vali, context.expressionContext() );
2977 if ( !QgsVariantUtils::isNull( exprVal ) )
2978 {
2979 QString valiString = exprVal.toString();
2980 if ( valiString.compare( "Bottom"_L1, Qt::CaseInsensitive ) != 0 )
2981 {
2982 if ( valiString.compare( "Top"_L1, Qt::CaseInsensitive ) == 0 )
2983 {
2984 ydiff -= labelSize.height();
2985 }
2986 else
2987 {
2988 double descentRatio = labelFontMetrics.descent() / labelFontMetrics.height();
2989 if ( valiString.compare( "Base"_L1, Qt::CaseInsensitive ) == 0 )
2990 {
2991 ydiff -= labelSize.height() * descentRatio;
2992 }
2993 else //'Cap' or 'Half'
2994 {
2995 double capHeightRatio = ( labelFontMetrics.boundingRect( 'H' ).height() + 1 + labelFontMetrics.descent() ) / labelFontMetrics.height();
2996 ydiff -= labelSize.height() * capHeightRatio;
2997 if ( valiString.compare( "Half"_L1, Qt::CaseInsensitive ) == 0 )
2998 {
2999 ydiff += labelSize.height() * ( capHeightRatio - descentRatio ) / 2.0;
3000 }
3001 }
3002 }
3003 }
3004 }
3005 }
3006
3007 if ( dataDefinedRotation )
3008 {
3009 //adjust xdiff and ydiff because the hali/vali point needs to be the rotation center
3010 double xd = xdiff * std::cos( angleInRadians ) - ydiff * std::sin( angleInRadians );
3011 double yd = xdiff * std::sin( angleInRadians ) + ydiff * std::cos( angleInRadians );
3012 xdiff = xd;
3013 ydiff = yd;
3014 }
3015
3016 //project xPos and yPos from layer to map CRS, handle rotation
3017 QgsGeometry ddPoint( new QgsPoint( xPos, yPos ) );
3018 if ( QgsPalLabeling::geometryRequiresPreparation( ddPoint, context, ct ) )
3019 {
3020 ddPoint = QgsPalLabeling::prepareGeometry( ddPoint, context, ct );
3021 if ( const QgsPoint *point = qgsgeometry_cast< const QgsPoint * >( ddPoint.constGet() ) )
3022 {
3023 xPos = point->x();
3024 yPos = point->y();
3025 anchorPosition = QgsPointXY( xPos, yPos );
3026 }
3027 else
3028 {
3029 QgsMessageLog::logMessage( QObject::tr( "Invalid data defined label position (%1, %2)" ).arg( xPos ).arg( yPos ), QObject::tr( "Labeling" ) );
3030 hasDataDefinedPosition = false;
3031 }
3032 }
3033 else
3034 {
3035 anchorPosition = QgsPointXY( xPos, yPos );
3036 }
3037
3038 xPos += xdiff;
3039 yPos += ydiff;
3040 }
3041 else
3042 {
3043 anchorPosition = QgsPointXY( xPos, yPos );
3044 }
3045 }
3046 }
3047
3048 // data defined always show?
3049 bool alwaysShow = false;
3050 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::AlwaysShow ) )
3051 {
3052 alwaysShow = mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::Property::AlwaysShow, context.expressionContext(), false );
3053 }
3054
3055 // set repeat distance
3056 // data defined repeat distance?
3057 double repeatDist = repeatDistance;
3058 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::RepeatDistance ) )
3059 {
3060 context.expressionContext().setOriginalValueVariable( repeatDist );
3061 repeatDist = mDataDefinedProperties.valueAsDouble( QgsPalLayerSettings::Property::RepeatDistance, context.expressionContext(), repeatDist );
3062 }
3063
3064 // data defined label-repeat distance units?
3066 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::RepeatDistanceUnit ) )
3067 {
3068 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Property::RepeatDistanceUnit, context.expressionContext() );
3069 if ( !QgsVariantUtils::isNull( exprVal ) )
3070 {
3071 QString units = exprVal.toString().trimmed();
3072 if ( !units.isEmpty() )
3073 {
3074 bool ok = false;
3075 Qgis::RenderUnit decodedUnits = QgsUnitTypes::decodeRenderUnit( units, &ok );
3076 if ( ok )
3077 {
3078 repeatUnits = decodedUnits;
3079 }
3080 }
3081 }
3082 }
3083
3084 if ( !qgsDoubleNear( repeatDist, 0.0 ) )
3085 {
3086 if ( repeatUnits != Qgis::RenderUnit::MapUnits )
3087 {
3088 repeatDist = context.convertToMapUnits( repeatDist, repeatUnits, repeatDistanceMapUnitScale );
3089 }
3090 }
3091
3092 // overrun distance
3093 double overrunDistanceEval = lineSettings.overrunDistance();
3094 if ( !qgsDoubleNear( overrunDistanceEval, 0.0 ) )
3095 {
3096 overrunDistanceEval = context.convertToMapUnits( overrunDistanceEval, lineSettings.overrunDistanceUnit(), lineSettings.overrunDistanceMapUnitScale() );
3097 }
3098
3099 // we smooth out the overrun label extensions by 1 mm, to avoid little jaggies right at the start or end of the lines
3100 // causing the overrun extension to extend out in an undesirable direction. This is hard coded, we don't want to overload
3101 // users with options they likely don't need to see...
3102 const double overrunSmoothDist = context.convertToMapUnits( 1, Qgis::RenderUnit::Millimeters );
3103
3104 // maximum distance
3105 double maximumDistanceEval = pointSettings.maximumDistance();
3106 if ( !qgsDoubleNear( maximumDistanceEval, 0.0 ) )
3107 {
3108 maximumDistanceEval = context.convertToMapUnits( maximumDistanceEval, pointSettings.maximumDistanceUnit(), pointSettings.maximumDistanceMapUnitScale() );
3109 }
3110
3111 // feature to the layer
3112 auto labelFeature = std::make_unique< QgsTextLabelFeature>( feature.id(), std::move( geos_geom_clone ), labelSize, subPartId );
3113 labelFeature->setAnchorPosition( anchorPosition );
3114 labelFeature->setFeature( feature );
3115 labelFeature->setSymbol( symbol );
3116 labelFeature->setDocument( doc, documentMetrics );
3117 if ( !qgsDoubleNear( rotatedSize.width(), 0.0 ) && !qgsDoubleNear( rotatedSize.height(), 0.0 ) )
3118 labelFeature->setRotatedSize( rotatedSize );
3119 mFeatsRegPal++;
3120
3121 labelFeature->setHasFixedPosition( hasDataDefinedPosition );
3122 labelFeature->setFixedPosition( QgsPointXY( xPos, yPos ) );
3123 // use layer-level defined rotation, but not if position fixed
3124 labelFeature->setHasFixedAngle( dataDefinedRotation || ( !hasDataDefinedPosition && !qgsDoubleNear( angleInRadians, 0.0 ) ) );
3125 labelFeature->setFixedAngle( angleInRadians );
3126 labelFeature->setQuadOffset( QPointF( quadOffsetX, quadOffsetY ) );
3127 labelFeature->setPositionOffset( QgsPointXY( offsetX, offsetY ) );
3128 labelFeature->setOffsetType( offsetType );
3129 labelFeature->setAlwaysShow( alwaysShow );
3130 labelFeature->setRepeatDistance( repeatDist );
3131 labelFeature->setLabelText( labelText );
3132 labelFeature->setPermissibleZone( permissibleZone );
3133 labelFeature->setOverrunDistance( overrunDistanceEval );
3134 labelFeature->setOverrunSmoothDistance( overrunSmoothDist );
3135 labelFeature->setMaximumDistance( maximumDistanceEval );
3136 labelFeature->setLineAnchorPercent( lineSettings.lineAnchorPercent() );
3137 labelFeature->setLineAnchorType( lineSettings.anchorType() );
3138 labelFeature->setLineAnchorTextPoint( lineSettings.anchorTextPoint() );
3139 labelFeature->setCurvedLabelMode( lineSettings.curvedLabelMode() );
3140 labelFeature->setMultiPartBehavior( placementSettings.multiPartBehavior() );
3141 labelFeature->setOriginalFeatureCrs( context.coordinateTransform().sourceCrs() );
3142 labelFeature->setMinimumSize( minimumSize );
3143 labelFeature->setWhitespaceCollisionHandling( placementSettings.whitespaceCollisionHandling() );
3144 if ( geom.type() == Qgis::GeometryType::Point && !obstacleGeometry.isNull() )
3145 {
3146 //register symbol size
3147 labelFeature->setSymbolSize( QSizeF( obstacleGeometry.boundingBox().width(),
3148 obstacleGeometry.boundingBox().height() ) );
3149 }
3150
3151 if ( outerBounds.left() != 0 || outerBounds.top() != 0 || !qgsDoubleNear( outerBounds.width(), labelSize.width() ) || !qgsDoubleNear( outerBounds.height(), labelSize.height() ) )
3152 {
3153 labelFeature->setOuterBounds( outerBounds );
3154 }
3155
3156 QgsLabelFeatureThinningSettings thinning;
3157 if ( featureThinningSettings.labelMarginDistance() > 0 )
3158 {
3159 thinning.setLabelMarginDistance( context.convertToMapUnits( featureThinningSettings.labelMarginDistance(),
3160 featureThinningSettings.labelMarginDistanceUnit(),
3161 featureThinningSettings.labelMarginDistanceMapUnitScale() ) );
3162 }
3163 if ( featureThinningSettings.allowDuplicateRemoval() )
3164 {
3165 thinning.setNoRepeatDistance( context.convertToMapUnits( featureThinningSettings.minimumDistanceToDuplicate(),
3166 featureThinningSettings.minimumDistanceToDuplicateUnit(),
3167 featureThinningSettings.minimumDistanceToDuplicateMapUnitScale() ) );
3168 }
3169 ( *labelFeature ).setThinningSettings( thinning );
3170
3171 //set label's visual margin so that top visual margin is the leading, and bottom margin is the font's descent
3172 //this makes labels align to the font's baseline or highest character
3173 double topMargin = std::max( 0.25 * labelFontMetrics.ascent(), 0.0 );
3174 double bottomMargin = 1.0 + labelFontMetrics.descent();
3175 QgsMargins vm( 0.0, topMargin, 0.0, bottomMargin );
3176 vm *= xform->mapUnitsPerPixel();
3177 labelFeature->setVisualMargin( vm );
3178
3179 // store the label's calculated font for later use during painting
3180 QgsDebugMsgLevel( u"PAL font stored definedFont: %1, Style: %2"_s.arg( evaluatedFormat.font().toString(), evaluatedFormat.font().styleName() ), 4 );
3181 labelFeature->setDefinedFont( evaluatedFormat.font() );
3182
3183 labelFeature->setMaximumCharacterAngleInside( std::clamp( maxcharanglein, 20.0, 60.0 ) * M_PI / 180 );
3184 labelFeature->setMaximumCharacterAngleOutside( std::clamp( maxcharangleout, -95.0, -20.0 ) * M_PI / 180 );
3185 switch ( placement )
3186 {
3194 // these placements don't require text metrics
3195 break;
3196
3199 labelFeature->setTextMetrics( QgsTextLabelFeature::calculateTextMetrics( xform, context, evaluatedFormat, evaluatedFormat.font(), labelFontMetrics, evaluatedFormat.font().letterSpacing(), evaluatedFormat.font().wordSpacing(), doc, documentMetrics ) );
3200 break;
3201 }
3202
3203 // for labelFeature the LabelInfo is passed to feat when it is registered
3204
3205 // TODO: allow layer-wide feature dist in PAL...?
3206
3207 // data defined label-feature distance?
3208 double distance = dist;
3209 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::LabelDistance ) )
3210 {
3211 context.expressionContext().setOriginalValueVariable( distance );
3212 distance = mDataDefinedProperties.valueAsDouble( QgsPalLayerSettings::Property::LabelDistance, context.expressionContext(), distance );
3213 }
3214
3215 // data defined label-feature distance units?
3216 Qgis::RenderUnit distUnit = distUnits;
3217 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::DistanceUnits ) )
3218 {
3219 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Property::DistanceUnits, context.expressionContext() );
3220 if ( !QgsVariantUtils::isNull( exprVal ) )
3221 {
3222 QString units = exprVal.toString().trimmed();
3223 QgsDebugMsgLevel( u"exprVal DistanceUnits:%1"_s.arg( units ), 4 );
3224 if ( !units.isEmpty() )
3225 {
3226 bool ok = false;
3227 Qgis::RenderUnit decodedUnits = QgsUnitTypes::decodeRenderUnit( units, &ok );
3228 if ( ok )
3229 {
3230 distUnit = decodedUnits;
3231 }
3232 }
3233 }
3234 }
3235 distance = context.convertToPainterUnits( distance, distUnit, distMapUnitScale );
3236
3237 // when using certain placement modes, we force a tiny minimum distance. This ensures that
3238 // candidates are created just offset from a border and avoids candidates being incorrectly flagged as colliding with neighbours
3239 switch ( placement )
3240 {
3244 distance = ( distance < 0 ? -1 : 1 ) * std::max( std::fabs( distance ), 1.0 );
3245 break;
3246
3248 break;
3249
3255 {
3256 distance = std::max( distance, 2.0 );
3257 }
3258 break;
3259
3261 distance = std::max( distance, 2.0 );
3262 break;
3263 }
3264
3265 if ( !qgsDoubleNear( distance, 0.0 ) )
3266 {
3267 double d = ptOne.distance( ptZero ) * distance;
3268 labelFeature->setDistLabel( d );
3269 }
3270
3271 if ( ddFixedQuad )
3272 {
3273 labelFeature->setHasFixedQuadrant( true );
3274 }
3275
3276 labelFeature->setArrangementFlags( lineSettings.placementFlags() );
3277
3278 labelFeature->setPolygonPlacementFlags( polygonPlacement );
3279
3280 // data defined z-index?
3281 double z = zIndex;
3282 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::ZIndex ) )
3283 {
3285 z = mDataDefinedProperties.valueAsDouble( QgsPalLayerSettings::Property::ZIndex, context.expressionContext(), z );
3286 }
3287 labelFeature->setZIndex( z );
3288
3289 // data defined priority?
3290 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::Priority ) )
3291 {
3293 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Property::Priority, context.expressionContext() );
3294 if ( !QgsVariantUtils::isNull( exprVal ) )
3295 {
3296 bool ok;
3297 double priorityD = exprVal.toDouble( &ok );
3298 if ( ok )
3299 {
3300 priorityD = std::clamp( priorityD, 0.0, 10.0 );
3301 priorityD = 1 - priorityD / 10.0; // convert 0..10 --> 1..0
3302 labelFeature->setPriority( priorityD );
3303 }
3304 }
3305 }
3306
3307 labelFeature->setAllowDegradedPlacement( placementSettings.allowDegradedPlacement() );
3308 labelFeature->setOverlapHandling( placementSettings.overlapHandling() );
3309 labelFeature->setPrioritization( placementSettings.prioritization() );
3310
3311 QgsLabelObstacleSettings os = mObstacleSettings;
3312 os.setIsObstacle( isObstacle );
3313 os.updateDataDefinedProperties( mDataDefinedProperties, context.expressionContext() );
3314 os.setObstacleGeometry( obstacleGeometry );
3315 labelFeature->setObstacleSettings( os );
3316
3317 QVector< Qgis::LabelPredefinedPointPosition > positionOrder = pointSettings.predefinedPositionOrder();
3318 if ( positionOrder.isEmpty() )
3319 positionOrder = *DEFAULT_PLACEMENT_ORDER();
3320
3321 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::PredefinedPositionOrder ) )
3322 {
3324 QString dataDefinedOrder = mDataDefinedProperties.valueAsString( QgsPalLayerSettings::Property::PredefinedPositionOrder, context.expressionContext() );
3325 if ( !dataDefinedOrder.isEmpty() )
3326 {
3327 positionOrder = QgsLabelingUtils::decodePredefinedPositionOrder( dataDefinedOrder );
3328 }
3329 }
3330 labelFeature->setPredefinedPositionOrder( positionOrder );
3331
3332 // add parameters for data defined labeling to label feature
3333 labelFeature->setDataDefinedValues( dataDefinedValues );
3334
3335 return labelFeature;
3336}
3337
3338std::unique_ptr<QgsLabelFeature> QgsPalLayerSettings::registerObstacleFeature( const QgsFeature &f, QgsRenderContext &context, const QgsGeometry &obstacleGeometry )
3339{
3340 mCurFeat = &f;
3341
3342 QgsGeometry geom;
3343 if ( !obstacleGeometry.isNull() )
3344 {
3345 geom = obstacleGeometry;
3346 }
3347 else
3348 {
3349 geom = f.geometry();
3350 }
3351
3352 if ( geom.isNull() )
3353 {
3354 return nullptr;
3355 }
3356
3357 // don't even try to register linestrings with only one vertex as an obstacle
3358 if ( const QgsLineString *ls = qgsgeometry_cast< const QgsLineString * >( geom.constGet() ) )
3359 {
3360 if ( ls->numPoints() < 2 )
3361 return nullptr;
3362 }
3363
3364 // simplify?
3365 const QgsVectorSimplifyMethod &simplifyMethod = context.vectorSimplifyMethod();
3366 std::unique_ptr<QgsGeometry> scopedClonedGeom;
3368 {
3369 int simplifyHints = simplifyMethod.simplifyHints() | QgsMapToPixelSimplifier::SimplifyEnvelope;
3370 const Qgis::VectorSimplificationAlgorithm simplifyAlgorithm = simplifyMethod.simplifyAlgorithm();
3371 QgsMapToPixelSimplifier simplifier( simplifyHints, simplifyMethod.tolerance(), simplifyAlgorithm );
3372 geom = simplifier.simplify( geom );
3373 }
3374
3375 geos::unique_ptr geos_geom_clone;
3376 std::unique_ptr<QgsGeometry> scopedPreparedGeom;
3377
3378 if ( QgsPalLabeling::geometryRequiresPreparation( geom, context, ct, extentGeom, mLineSettings.mergeLines() ) )
3379 {
3380 geom = QgsPalLabeling::prepareGeometry( geom, context, ct, extentGeom, mLineSettings.mergeLines() );
3381 }
3382 geos_geom_clone = QgsGeos::asGeos( geom );
3383
3384 if ( !geos_geom_clone )
3385 return nullptr; // invalid geometry
3386
3387 // feature to the layer
3388 auto obstacleFeature = std::make_unique< QgsLabelFeature >( f.id(), std::move( geos_geom_clone ), QSizeF( 0, 0 ) );
3389 obstacleFeature->setFeature( f );
3390
3391 QgsLabelObstacleSettings os = mObstacleSettings;
3392 os.setIsObstacle( true );
3393 os.updateDataDefinedProperties( mDataDefinedProperties, context.expressionContext() );
3394 obstacleFeature->setObstacleSettings( os );
3395
3396 mFeatsRegPal++;
3397 return obstacleFeature;
3398}
3399
3400bool QgsPalLayerSettings::dataDefinedValEval( DataDefinedValueType valType,
3402 QVariant &exprVal, QgsExpressionContext &context, const QVariant &originalValue )
3403{
3404 if ( !mDataDefinedProperties.isActive( p ) )
3405 return false;
3406
3407 context.setOriginalValueVariable( originalValue );
3408 exprVal = mDataDefinedProperties.value( p, context );
3409 if ( !QgsVariantUtils::isNull( exprVal ) )
3410 {
3411 switch ( valType )
3412 {
3413 case DDBool:
3414 {
3415 bool bol = exprVal.toBool();
3416 dataDefinedValues.insert( p, QVariant( bol ) );
3417 return true;
3418 }
3419 case DDInt:
3420 {
3421 bool ok;
3422 int size = exprVal.toInt( &ok );
3423
3424 if ( ok )
3425 {
3426 dataDefinedValues.insert( p, QVariant( size ) );
3427 return true;
3428 }
3429 return false;
3430 }
3431 case DDIntPos:
3432 {
3433 bool ok;
3434 int size = exprVal.toInt( &ok );
3435
3436 if ( ok && size > 0 )
3437 {
3438 dataDefinedValues.insert( p, QVariant( size ) );
3439 return true;
3440 }
3441 return false;
3442 }
3443 case DDDouble:
3444 {
3445 bool ok;
3446 double size = exprVal.toDouble( &ok );
3447
3448 if ( ok )
3449 {
3450 dataDefinedValues.insert( p, QVariant( size ) );
3451 return true;
3452 }
3453 return false;
3454 }
3455 case DDDoublePos:
3456 {
3457 bool ok;
3458 double size = exprVal.toDouble( &ok );
3459
3460 if ( ok && size > 0.0 )
3461 {
3462 dataDefinedValues.insert( p, QVariant( size ) );
3463 return true;
3464 }
3465 return false;
3466 }
3467 case DDRotation180:
3468 {
3469 bool ok;
3470 double rot = exprVal.toDouble( &ok );
3471 if ( ok )
3472 {
3473 if ( rot < -180.0 && rot >= -360 )
3474 {
3475 rot += 360;
3476 }
3477 if ( rot > 180.0 && rot <= 360 )
3478 {
3479 rot -= 360;
3480 }
3481 if ( rot >= -180 && rot <= 180 )
3482 {
3483 dataDefinedValues.insert( p, QVariant( rot ) );
3484 return true;
3485 }
3486 }
3487 return false;
3488 }
3489 case DDOpacity:
3490 {
3491 bool ok;
3492 int size = exprVal.toInt( &ok );
3493 if ( ok && size >= 0 && size <= 100 )
3494 {
3495 dataDefinedValues.insert( p, QVariant( size ) );
3496 return true;
3497 }
3498 return false;
3499 }
3500 case DDString:
3501 {
3502 QString str = exprVal.toString(); // don't trim whitespace
3503
3504 dataDefinedValues.insert( p, QVariant( str ) ); // let it stay empty if it is
3505 return true;
3506 }
3507 case DDUnits:
3508 {
3509 QString unitstr = exprVal.toString().trimmed();
3510
3511 if ( !unitstr.isEmpty() )
3512 {
3513 dataDefinedValues.insert( p, QVariant( static_cast< int >( QgsUnitTypes::decodeRenderUnit( unitstr ) ) ) );
3514 return true;
3515 }
3516 return false;
3517 }
3518 case DDColor:
3519 {
3520 QString colorstr = exprVal.toString().trimmed();
3521 QColor color = QgsColorUtils::colorFromString( colorstr );
3522
3523 if ( color.isValid() )
3524 {
3525 dataDefinedValues.insert( p, QVariant( color ) );
3526 return true;
3527 }
3528 return false;
3529 }
3530 case DDJoinStyle:
3531 {
3532 QString joinstr = exprVal.toString().trimmed();
3533
3534 if ( !joinstr.isEmpty() )
3535 {
3536 dataDefinedValues.insert( p, QVariant( static_cast< int >( QgsSymbolLayerUtils::decodePenJoinStyle( joinstr ) ) ) );
3537 return true;
3538 }
3539 return false;
3540 }
3541 case DDBlendMode:
3542 {
3543 QString blendstr = exprVal.toString().trimmed();
3544
3545 if ( !blendstr.isEmpty() )
3546 {
3547 dataDefinedValues.insert( p, QVariant( static_cast< int >( QgsSymbolLayerUtils::decodeBlendMode( blendstr ) ) ) );
3548 return true;
3549 }
3550 return false;
3551 }
3552 case DDPointF:
3553 {
3554 bool ok = false;
3555 const QPointF res = QgsSymbolLayerUtils::toPoint( exprVal, &ok );
3556 if ( ok )
3557 {
3558 dataDefinedValues.insert( p, res );
3559 return true;
3560 }
3561 return false;
3562 }
3563 case DDSizeF:
3564 {
3565 bool ok = false;
3566 const QSizeF res = QgsSymbolLayerUtils::toSize( exprVal, &ok );
3567 if ( ok )
3568 {
3569 dataDefinedValues.insert( p, res );
3570 return true;
3571 }
3572 return false;
3573 }
3574 }
3575 }
3576 return false;
3577}
3578
3579void QgsPalLayerSettings::parseTextStyle( QFont &labelFont,
3580 Qgis::RenderUnit fontunits,
3581 QgsRenderContext &context )
3582{
3583 // NOTE: labelFont already has pixelSize set, so pointSize or pointSizeF might return -1
3584
3585 QVariant exprVal; // value() is repeatedly nulled on data defined evaluation and replaced when successful
3586
3587 // Two ways to generate new data defined font:
3588 // 1) Family + [bold] + [italic] (named style is ignored and font is built off of base family)
3589 // 2) Family + named style (bold or italic is ignored)
3590
3591 // data defined font family?
3592 QString ddFontFamily;
3593 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::Family ) )
3594 {
3595 context.expressionContext().setOriginalValueVariable( labelFont.family() );
3596 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Property::Family, context.expressionContext() );
3597 if ( !QgsVariantUtils::isNull( exprVal ) )
3598 {
3599 QString family = exprVal.toString().trimmed();
3600 QgsDebugMsgLevel( u"exprVal Font family:%1"_s.arg( family ), 4 );
3601
3603 if ( labelFont.family() != family )
3604 {
3605 // testing for ddFontFamily in QFontDatabase.families() may be slow to do for every feature
3606 // (i.e. don't use QgsFontUtils::fontFamilyMatchOnSystem( family ) here)
3607 if ( QgsFontUtils::fontFamilyOnSystem( family ) )
3608 {
3609 ddFontFamily = family;
3610 }
3611 }
3612 }
3613 }
3614
3615 // data defined named font style?
3616 QString ddFontStyle;
3617 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::FontStyle ) )
3618 {
3619 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Property::FontStyle, context.expressionContext() );
3620 if ( !QgsVariantUtils::isNull( exprVal ) )
3621 {
3622 QString fontstyle = exprVal.toString().trimmed();
3623 QgsDebugMsgLevel( u"exprVal Font style:%1"_s.arg( fontstyle ), 4 );
3624 ddFontStyle = fontstyle;
3625 }
3626 }
3627
3628 // data defined bold font style?
3629 bool ddBold = false;
3630 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::Bold ) )
3631 {
3632 context.expressionContext().setOriginalValueVariable( labelFont.bold() );
3633 ddBold = mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::Property::Bold, context.expressionContext(), false );
3634 }
3635
3636 // data defined italic font style?
3637 bool ddItalic = false;
3638 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::Italic ) )
3639 {
3640 context.expressionContext().setOriginalValueVariable( labelFont.italic() );
3641 ddItalic = mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::Property::Italic, context.expressionContext(), false );
3642 }
3643
3644 // TODO: update when pref for how to resolve missing family (use matching algorithm or just default font) is implemented
3645 // (currently defaults to what has been read in from layer settings)
3646 QFont newFont;
3647 QFont appFont = QApplication::font();
3648 bool newFontBuilt = false;
3649 if ( ddBold || ddItalic )
3650 {
3651 // new font needs built, since existing style needs removed
3652 newFont = QgsFontUtils::createFont( !ddFontFamily.isEmpty() ? ddFontFamily : labelFont.family() );
3653 newFontBuilt = true;
3654 newFont.setBold( ddBold );
3655 newFont.setItalic( ddItalic );
3656 }
3657 else if ( !ddFontStyle.isEmpty()
3658 && ddFontStyle.compare( "Ignore"_L1, Qt::CaseInsensitive ) != 0 )
3659 {
3660 if ( !ddFontFamily.isEmpty() )
3661 {
3662 // both family and style are different, build font from database
3663 if ( !mFontDB )
3664 mFontDB = std::make_unique< QFontDatabase >();
3665
3666 QFont styledfont = mFontDB->font( ddFontFamily, ddFontStyle, appFont.pointSize() );
3667 if ( appFont != styledfont )
3668 {
3669 newFont = styledfont;
3670 newFontBuilt = true;
3671 }
3672 }
3673
3674 // update the font face style
3675 QgsFontUtils::updateFontViaStyle( newFontBuilt ? newFont : labelFont, ddFontStyle );
3676 }
3677 else if ( !ddFontFamily.isEmpty() )
3678 {
3679 if ( ddFontStyle.compare( "Ignore"_L1, Qt::CaseInsensitive ) != 0 )
3680 {
3681 // just family is different, build font from database
3682 if ( !mFontDB )
3683 mFontDB = std::make_unique< QFontDatabase >();
3684 QFont styledfont = mFontDB->font( ddFontFamily, mFormat.namedStyle(), appFont.pointSize() );
3685 if ( appFont != styledfont )
3686 {
3687 newFont = styledfont;
3688 newFontBuilt = true;
3689 }
3690 }
3691 else
3692 {
3693 newFont = QgsFontUtils::createFont( ddFontFamily );
3694 newFontBuilt = true;
3695 }
3696 }
3697
3698 if ( newFontBuilt )
3699 {
3700 // copy over existing font settings
3701 //newFont = newFont.resolve( labelFont ); // should work, but let's be sure what's being copied
3702 newFont.setPixelSize( labelFont.pixelSize() );
3703 newFont.setUnderline( labelFont.underline() );
3704 newFont.setStrikeOut( labelFont.strikeOut() );
3705 newFont.setWordSpacing( labelFont.wordSpacing() );
3706 newFont.setLetterSpacing( QFont::AbsoluteSpacing, labelFont.letterSpacing() );
3707
3708 labelFont = newFont;
3709 }
3710
3711 // data defined word spacing?
3712 double wordspace = labelFont.wordSpacing();
3713 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::FontWordSpacing ) )
3714 {
3715 context.expressionContext().setOriginalValueVariable( wordspace );
3716 wordspace = mDataDefinedProperties.valueAsDouble( QgsPalLayerSettings::Property::FontWordSpacing, context.expressionContext(), wordspace );
3717 }
3718 labelFont.setWordSpacing( context.convertToPainterUnits( wordspace, fontunits, mFormat.sizeMapUnitScale() ) );
3719
3720 // data defined letter spacing?
3721 double letterspace = labelFont.letterSpacing();
3722 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::FontLetterSpacing ) )
3723 {
3724 context.expressionContext().setOriginalValueVariable( letterspace );
3725 letterspace = mDataDefinedProperties.valueAsDouble( QgsPalLayerSettings::Property::FontLetterSpacing, context.expressionContext(), letterspace );
3726 }
3727 labelFont.setLetterSpacing( QFont::AbsoluteSpacing, context.convertToPainterUnits( letterspace, fontunits, mFormat.sizeMapUnitScale() ) );
3728
3729 // data defined strikeout font style?
3730 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::Strikeout ) )
3731 {
3732 context.expressionContext().setOriginalValueVariable( labelFont.strikeOut() );
3733 bool strikeout = mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::Property::Strikeout, context.expressionContext(), false );
3734 labelFont.setStrikeOut( strikeout );
3735 }
3736
3737 // data defined stretch
3738 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::FontStretchFactor ) )
3739 {
3740 context.expressionContext().setOriginalValueVariable( mFormat.stretchFactor() );
3741 labelFont.setStretch( mDataDefinedProperties.valueAsInt( QgsPalLayerSettings::Property::FontStretchFactor, context.expressionContext(), mFormat.stretchFactor() ) );
3742 }
3743
3744 // data defined underline font style?
3745 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::Underline ) )
3746 {
3747 context.expressionContext().setOriginalValueVariable( labelFont.underline() );
3748 bool underline = mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::Property::Underline, context.expressionContext(), false );
3749 labelFont.setUnderline( underline );
3750 }
3751
3752 // pass the rest on to QgsPalLabeling::drawLabeling
3753
3754 // data defined font color?
3755 dataDefinedValEval( DDColor, QgsPalLayerSettings::Property::Color, exprVal, context.expressionContext(), QgsColorUtils::colorToString( mFormat.color() ) );
3756
3757 // data defined font opacity?
3758 dataDefinedValEval( DDOpacity, QgsPalLayerSettings::Property::FontOpacity, exprVal, context.expressionContext(), mFormat.opacity() * 100 );
3759
3760 // data defined font blend mode?
3761 dataDefinedValEval( DDBlendMode, QgsPalLayerSettings::Property::FontBlendMode, exprVal, context.expressionContext() );
3762
3763}
3764
3765void QgsPalLayerSettings::parseTextBuffer( QgsRenderContext &context )
3766{
3767 QVariant exprVal; // value() is repeatedly nulled on data defined evaluation and replaced when successful
3768
3769 QgsTextBufferSettings buffer = mFormat.buffer();
3770
3771 // data defined draw buffer?
3772 bool drawBuffer = mFormat.buffer().enabled();
3773 if ( dataDefinedValEval( DDBool, QgsPalLayerSettings::Property::BufferDraw, exprVal, context.expressionContext(), buffer.enabled() ) )
3774 {
3775 drawBuffer = exprVal.toBool();
3776 }
3777 else if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::BufferDraw ) && QgsVariantUtils::isNull( exprVal ) )
3778 {
3779 dataDefinedValues.insert( QgsPalLayerSettings::Property::BufferDraw, QVariant( drawBuffer ) );
3780 }
3781
3782 if ( !drawBuffer )
3783 {
3784 return;
3785 }
3786
3787 // data defined buffer size?
3788 double bufrSize = buffer.size();
3789 if ( dataDefinedValEval( DDDoublePos, QgsPalLayerSettings::Property::BufferSize, exprVal, context.expressionContext(), buffer.size() ) )
3790 {
3791 bufrSize = exprVal.toDouble();
3792 }
3793
3794 // data defined buffer transparency?
3795 double bufferOpacity = buffer.opacity() * 100;
3796 if ( dataDefinedValEval( DDOpacity, QgsPalLayerSettings::Property::BufferOpacity, exprVal, context.expressionContext(), bufferOpacity ) )
3797 {
3798 bufferOpacity = exprVal.toDouble();
3799 }
3800
3801 drawBuffer = ( drawBuffer && bufrSize > 0.0 && bufferOpacity > 0 );
3802
3803 if ( !drawBuffer )
3804 {
3805 dataDefinedValues.insert( QgsPalLayerSettings::Property::BufferDraw, QVariant( false ) ); // trigger value
3806 dataDefinedValues.remove( QgsPalLayerSettings::Property::BufferSize );
3807 dataDefinedValues.remove( QgsPalLayerSettings::Property::BufferOpacity );
3808 return; // don't bother evaluating values that won't be used
3809 }
3810
3811 // data defined buffer units?
3812 dataDefinedValEval( DDUnits, QgsPalLayerSettings::Property::BufferUnit, exprVal, context.expressionContext() );
3813
3814 // data defined buffer color?
3815 dataDefinedValEval( DDColor, QgsPalLayerSettings::Property::BufferColor, exprVal, context.expressionContext(), QgsColorUtils::colorToString( buffer.color() ) );
3816
3817 // data defined buffer pen join style?
3818 dataDefinedValEval( DDJoinStyle, QgsPalLayerSettings::Property::BufferJoinStyle, exprVal, context.expressionContext(), QgsSymbolLayerUtils::encodePenJoinStyle( buffer.joinStyle() ) );
3819
3820 // data defined buffer blend mode?
3821 dataDefinedValEval( DDBlendMode, QgsPalLayerSettings::Property::BufferBlendMode, exprVal, context.expressionContext() );
3822}
3823
3824void QgsPalLayerSettings::parseTextMask( QgsRenderContext &context )
3825{
3826 QVariant exprVal; // value() is repeatedly nulled on data defined evaluation and replaced when successful
3827
3828 QgsTextMaskSettings mask = mFormat.mask();
3829
3830 // data defined enabled mask?
3831 bool maskEnabled = mask.enabled();
3832 if ( dataDefinedValEval( DDBool, QgsPalLayerSettings::Property::MaskEnabled, exprVal, context.expressionContext(), mask.enabled() ) )
3833 {
3834 maskEnabled = exprVal.toBool();
3835 }
3836
3837 if ( !maskEnabled )
3838 {
3839 return;
3840 }
3841
3842 // data defined buffer size?
3843 double bufrSize = mask.size();
3844 if ( dataDefinedValEval( DDDoublePos, QgsPalLayerSettings::Property::MaskBufferSize, exprVal, context.expressionContext(), mask.size() ) )
3845 {
3846 bufrSize = exprVal.toDouble();
3847 }
3848
3849 // data defined opacity?
3850 double opacity = mask.opacity() * 100;
3851 if ( dataDefinedValEval( DDOpacity, QgsPalLayerSettings::Property::MaskOpacity, exprVal, context.expressionContext(), opacity ) )
3852 {
3853 opacity = exprVal.toDouble();
3854 }
3855
3856 maskEnabled = ( maskEnabled && bufrSize > 0.0 && opacity > 0 );
3857
3858 if ( !maskEnabled )
3859 {
3860 dataDefinedValues.insert( QgsPalLayerSettings::Property::MaskEnabled, QVariant( false ) ); // trigger value
3861 dataDefinedValues.remove( QgsPalLayerSettings::Property::MaskBufferSize );
3862 dataDefinedValues.remove( QgsPalLayerSettings::Property::MaskOpacity );
3863 return; // don't bother evaluating values that won't be used
3864 }
3865
3866 // data defined buffer units?
3867 dataDefinedValEval( DDUnits, QgsPalLayerSettings::Property::MaskBufferUnit, exprVal, context.expressionContext() );
3868
3869 // data defined buffer pen join style?
3870 dataDefinedValEval( DDJoinStyle, QgsPalLayerSettings::Property::MaskJoinStyle, exprVal, context.expressionContext(), QgsSymbolLayerUtils::encodePenJoinStyle( mask.joinStyle() ) );
3871}
3872
3873void QgsPalLayerSettings::parseTextFormatting( QgsRenderContext &context )
3874{
3875 QVariant exprVal; // value() is repeatedly nulled on data defined evaluation and replaced when successful
3876
3877 // data defined multiline wrap character?
3878 QString wrapchr = wrapChar;
3879 if ( dataDefinedValEval( DDString, QgsPalLayerSettings::Property::MultiLineWrapChar, exprVal, context.expressionContext(), wrapChar ) )
3880 {
3881 wrapchr = exprVal.toString();
3882 }
3883
3884 int evalAutoWrapLength = autoWrapLength;
3885 if ( dataDefinedValEval( DDInt, QgsPalLayerSettings::Property::AutoWrapLength, exprVal, context.expressionContext(), evalAutoWrapLength ) )
3886 {
3887 evalAutoWrapLength = exprVal.toInt();
3888 }
3889
3890 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::TabStopDistance ) )
3891 {
3892 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Property::TabStopDistance, context.expressionContext() );
3893 if ( !QgsVariantUtils::isNull( exprVal ) )
3894 {
3895 dataDefinedValues.insert( QgsPalLayerSettings::Property::TabStopDistance, exprVal );
3896 }
3897 }
3898
3899 // data defined multiline height?
3900 dataDefinedValEval( DDDouble, QgsPalLayerSettings::Property::MultiLineHeight, exprVal, context.expressionContext() );
3901
3902 // data defined multiline text align?
3903 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::MultiLineAlignment ) )
3904 {
3905 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Property::MultiLineAlignment, context.expressionContext() );
3906 if ( !QgsVariantUtils::isNull( exprVal ) )
3907 {
3908 QString str = exprVal.toString().trimmed();
3909 QgsDebugMsgLevel( u"exprVal MultiLineAlignment:%1"_s.arg( str ), 4 );
3910
3911 if ( !str.isEmpty() )
3912 {
3913 // "Left"
3915
3916 if ( str.compare( "Center"_L1, Qt::CaseInsensitive ) == 0 )
3917 {
3919 }
3920 else if ( str.compare( "Right"_L1, Qt::CaseInsensitive ) == 0 )
3921 {
3923 }
3924 else if ( str.compare( "Follow"_L1, Qt::CaseInsensitive ) == 0 )
3925 {
3927 }
3928 else if ( str.compare( "Justify"_L1, Qt::CaseInsensitive ) == 0 )
3929 {
3931 }
3932 dataDefinedValues.insert( QgsPalLayerSettings::Property::MultiLineAlignment, QVariant( static_cast< int >( aligntype ) ) );
3933 }
3934 }
3935 }
3936
3937 // text orientation
3938 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::TextOrientation ) )
3939 {
3940 const QString encoded = QgsTextRendererUtils::encodeTextOrientation( mFormat.orientation() );
3941 context.expressionContext().setOriginalValueVariable( encoded );
3942 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Property::TextOrientation, context.expressionContext() );
3943 if ( !QgsVariantUtils::isNull( exprVal ) )
3944 {
3945 QString str = exprVal.toString().trimmed();
3946 if ( !str.isEmpty() )
3947 dataDefinedValues.insert( QgsPalLayerSettings::Property::TextOrientation, str );
3948 }
3949 }
3950
3951 // data defined direction symbol?
3952 bool drawDirSymb = mLineSettings.addDirectionSymbol();
3953 if ( dataDefinedValEval( DDBool, QgsPalLayerSettings::Property::DirSymbDraw, exprVal, context.expressionContext(), drawDirSymb ) )
3954 {
3955 drawDirSymb = exprVal.toBool();
3956 }
3957
3958 if ( drawDirSymb )
3959 {
3960 // data defined direction left symbol?
3961 dataDefinedValEval( DDString, QgsPalLayerSettings::Property::DirSymbLeft, exprVal, context.expressionContext(), mLineSettings.leftDirectionSymbol() );
3962
3963 // data defined direction right symbol?
3964 dataDefinedValEval( DDString, QgsPalLayerSettings::Property::DirSymbRight, exprVal, context.expressionContext(), mLineSettings.rightDirectionSymbol() );
3965
3966 // data defined direction symbol placement?
3967 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Property::DirSymbPlacement, context.expressionContext() );
3968 if ( !QgsVariantUtils::isNull( exprVal ) )
3969 {
3970 QString str = exprVal.toString().trimmed();
3971 QgsDebugMsgLevel( u"exprVal DirSymbPlacement:%1"_s.arg( str ), 4 );
3972
3973 if ( !str.isEmpty() )
3974 {
3975 // "LeftRight"
3977
3978 if ( str.compare( "Above"_L1, Qt::CaseInsensitive ) == 0 )
3979 {
3981 }
3982 else if ( str.compare( "Below"_L1, Qt::CaseInsensitive ) == 0 )
3983 {
3985 }
3986 dataDefinedValues.insert( QgsPalLayerSettings::Property::DirSymbPlacement, QVariant( static_cast< int >( placetype ) ) );
3987 }
3988 }
3989
3990 // data defined direction symbol reversed?
3991 dataDefinedValEval( DDBool, QgsPalLayerSettings::Property::DirSymbReverse, exprVal, context.expressionContext(), mLineSettings.reverseDirectionSymbol() );
3992 }
3993
3994 // formatting for numbers is inline with generation of base label text and not passed to label painting
3995}
3996
3997void QgsPalLayerSettings::parseShapeBackground( QgsRenderContext &context )
3998{
3999 QVariant exprVal; // value() is repeatedly nulled on data defined evaluation and replaced when successful
4000
4001 QgsTextBackgroundSettings background = mFormat.background();
4002
4003 // data defined draw shape?
4004 bool drawShape = background.enabled();
4005 if ( dataDefinedValEval( DDBool, QgsPalLayerSettings::Property::ShapeDraw, exprVal, context.expressionContext(), drawShape ) )
4006 {
4007 drawShape = exprVal.toBool();
4008 }
4009
4010 if ( !drawShape )
4011 {
4012 return;
4013 }
4014
4015 // data defined shape transparency?
4016 double shapeOpacity = background.opacity() * 100;
4017 if ( dataDefinedValEval( DDOpacity, QgsPalLayerSettings::Property::ShapeOpacity, exprVal, context.expressionContext(), shapeOpacity ) )
4018 {
4019 shapeOpacity = 100.0 * exprVal.toDouble();
4020 }
4021
4022 drawShape = ( drawShape && shapeOpacity > 0 ); // size is not taken into account (could be)
4023
4024 if ( !drawShape )
4025 {
4026 dataDefinedValues.insert( QgsPalLayerSettings::Property::ShapeDraw, QVariant( false ) ); // trigger value
4027 dataDefinedValues.remove( QgsPalLayerSettings::Property::ShapeOpacity );
4028 return; // don't bother evaluating values that won't be used
4029 }
4030
4031 // data defined shape kind?
4032 QgsTextBackgroundSettings::ShapeType shapeKind = background.type();
4033 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::ShapeKind ) )
4034 {
4035 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Property::ShapeKind, context.expressionContext() );
4036 if ( !QgsVariantUtils::isNull( exprVal ) )
4037 {
4038 QString skind = exprVal.toString().trimmed();
4039 QgsDebugMsgLevel( u"exprVal ShapeKind:%1"_s.arg( skind ), 4 );
4040
4041 if ( !skind.isEmpty() )
4042 {
4043 shapeKind = QgsTextRendererUtils::decodeShapeType( skind );
4044 dataDefinedValues.insert( QgsPalLayerSettings::Property::ShapeKind, QVariant( static_cast< int >( shapeKind ) ) );
4045 }
4046 }
4047 }
4048
4049 // data defined shape SVG path?
4050 QString svgPath = background.svgFile();
4051 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::ShapeSVGFile ) )
4052 {
4053 context.expressionContext().setOriginalValueVariable( svgPath );
4054 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Property::ShapeSVGFile, context.expressionContext() );
4055 if ( !QgsVariantUtils::isNull( exprVal ) )
4056 {
4057 QString svgfile = exprVal.toString().trimmed();
4058 QgsDebugMsgLevel( u"exprVal ShapeSVGFile:%1"_s.arg( svgfile ), 4 );
4059
4060 // '' empty paths are allowed
4061 svgPath = QgsSymbolLayerUtils::svgSymbolNameToPath( svgfile, context.pathResolver() );
4062 dataDefinedValues.insert( QgsPalLayerSettings::Property::ShapeSVGFile, QVariant( svgPath ) );
4063 }
4064 }
4065
4066 // data defined shape size type?
4067 QgsTextBackgroundSettings::SizeType shpSizeType = background.sizeType();
4068 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::ShapeSizeType ) )
4069 {
4070 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Property::ShapeSizeType, context.expressionContext() );
4071 if ( !QgsVariantUtils::isNull( exprVal ) )
4072 {
4073 QString stype = exprVal.toString().trimmed();
4074 QgsDebugMsgLevel( u"exprVal ShapeSizeType:%1"_s.arg( stype ), 4 );
4075
4076 if ( !stype.isEmpty() )
4077 {
4079 dataDefinedValues.insert( QgsPalLayerSettings::Property::ShapeSizeType, QVariant( static_cast< int >( shpSizeType ) ) );
4080 }
4081 }
4082 }
4083
4084 // data defined shape size X? (SVGs only use X for sizing)
4085 double ddShpSizeX = background.size().width();
4086 if ( dataDefinedValEval( DDDouble, QgsPalLayerSettings::Property::ShapeSizeX, exprVal, context.expressionContext(), ddShpSizeX ) )
4087 {
4088 ddShpSizeX = exprVal.toDouble();
4089 }
4090
4091 // data defined shape size Y?
4092 double ddShpSizeY = background.size().height();
4093 if ( dataDefinedValEval( DDDouble, QgsPalLayerSettings::Property::ShapeSizeY, exprVal, context.expressionContext(), ddShpSizeY ) )
4094 {
4095 ddShpSizeY = exprVal.toDouble();
4096 }
4097
4098 // don't continue under certain circumstances (e.g. size is fixed)
4099 bool skip = false;
4100 if ( shapeKind == QgsTextBackgroundSettings::ShapeSVG
4101 && ( svgPath.isEmpty()
4102 || ( !svgPath.isEmpty()
4103 && shpSizeType == QgsTextBackgroundSettings::SizeFixed
4104 && ddShpSizeX == 0.0 ) ) )
4105 {
4106 skip = true;
4107 }
4109 && ( !background.markerSymbol()
4110 || ( background.markerSymbol()
4111 && shpSizeType == QgsTextBackgroundSettings::SizeFixed
4112 && ddShpSizeX == 0.0 ) ) )
4113 {
4114 skip = true;
4115 }
4116 if ( shapeKind != QgsTextBackgroundSettings::ShapeSVG
4118 && shpSizeType == QgsTextBackgroundSettings::SizeFixed
4119 && ( ddShpSizeX == 0.0 || ddShpSizeY == 0.0 ) )
4120 {
4121 skip = true;
4122 }
4123
4124 if ( skip )
4125 {
4126 dataDefinedValues.insert( QgsPalLayerSettings::Property::ShapeDraw, QVariant( false ) ); // trigger value
4127 dataDefinedValues.remove( QgsPalLayerSettings::Property::ShapeOpacity );
4128 dataDefinedValues.remove( QgsPalLayerSettings::Property::ShapeKind );
4129 dataDefinedValues.remove( QgsPalLayerSettings::Property::ShapeSVGFile );
4130 dataDefinedValues.remove( QgsPalLayerSettings::Property::ShapeSizeX );
4131 dataDefinedValues.remove( QgsPalLayerSettings::Property::ShapeSizeY );
4132 return; // don't bother evaluating values that won't be used
4133 }
4134
4135 // data defined shape size units?
4136 dataDefinedValEval( DDUnits, QgsPalLayerSettings::Property::ShapeSizeUnits, exprVal, context.expressionContext() );
4137
4138 // data defined shape rotation type?
4139 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::ShapeRotationType ) )
4140 {
4141 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Property::ShapeRotationType, context.expressionContext() );
4142 if ( !QgsVariantUtils::isNull( exprVal ) )
4143 {
4144 QString rotstr = exprVal.toString().trimmed();
4145 QgsDebugMsgLevel( u"exprVal ShapeRotationType:%1"_s.arg( rotstr ), 4 );
4146
4147 if ( !rotstr.isEmpty() )
4148 {
4149 // "Sync"
4151 dataDefinedValues.insert( QgsPalLayerSettings::Property::ShapeRotationType, QVariant( static_cast< int >( rottype ) ) );
4152 }
4153 }
4154 }
4155
4156 // data defined shape rotation?
4157 dataDefinedValEval( DDRotation180, QgsPalLayerSettings::Property::ShapeRotation, exprVal, context.expressionContext(), background.rotation() );
4158
4159 // data defined shape offset?
4160 dataDefinedValEval( DDPointF, QgsPalLayerSettings::Property::ShapeOffset, exprVal, context.expressionContext(), QgsSymbolLayerUtils::encodePoint( background.offset() ) );
4161
4162 // data defined shape offset units?
4163 dataDefinedValEval( DDUnits, QgsPalLayerSettings::Property::ShapeOffsetUnits, exprVal, context.expressionContext() );
4164
4165 // data defined shape radii?
4166 dataDefinedValEval( DDSizeF, QgsPalLayerSettings::Property::ShapeRadii, exprVal, context.expressionContext(), QgsSymbolLayerUtils::encodeSize( background.radii() ) );
4167
4168 // data defined shape radii units?
4169 dataDefinedValEval( DDUnits, QgsPalLayerSettings::Property::ShapeRadiiUnits, exprVal, context.expressionContext() );
4170
4171 // data defined shape blend mode?
4172 dataDefinedValEval( DDBlendMode, QgsPalLayerSettings::Property::ShapeBlendMode, exprVal, context.expressionContext() );
4173
4174 // data defined shape fill color?
4175 dataDefinedValEval( DDColor, QgsPalLayerSettings::Property::ShapeFillColor, exprVal, context.expressionContext(), QgsColorUtils::colorToString( background.fillColor() ) );
4176
4177 // data defined shape stroke color?
4178 dataDefinedValEval( DDColor, QgsPalLayerSettings::Property::ShapeStrokeColor, exprVal, context.expressionContext(), QgsColorUtils::colorToString( background.strokeColor() ) );
4179
4180 // data defined shape stroke width?
4181 dataDefinedValEval( DDDoublePos, QgsPalLayerSettings::Property::ShapeStrokeWidth, exprVal, context.expressionContext(), background.strokeWidth() );
4182
4183 // data defined shape stroke width units?
4184 dataDefinedValEval( DDUnits, QgsPalLayerSettings::Property::ShapeStrokeWidthUnits, exprVal, context.expressionContext() );
4185
4186 // data defined shape join style?
4187 dataDefinedValEval( DDJoinStyle, QgsPalLayerSettings::Property::ShapeJoinStyle, exprVal, context.expressionContext(), QgsSymbolLayerUtils::encodePenJoinStyle( background.joinStyle() ) );
4188
4189}
4190
4191void QgsPalLayerSettings::parseDropShadow( QgsRenderContext &context )
4192{
4193 QVariant exprVal; // value() is repeatedly nulled on data defined evaluation and replaced when successful
4194
4195 QgsTextShadowSettings shadow = mFormat.shadow();
4196
4197 // data defined draw shadow?
4198 bool drawShadow = shadow.enabled();
4199 if ( dataDefinedValEval( DDBool, QgsPalLayerSettings::Property::ShadowDraw, exprVal, context.expressionContext(), drawShadow ) )
4200 {
4201 drawShadow = exprVal.toBool();
4202 }
4203
4204 if ( !drawShadow )
4205 {
4206 return;
4207 }
4208
4209 // data defined shadow transparency?
4210 double shadowOpacity = shadow.opacity() * 100;
4211 if ( dataDefinedValEval( DDOpacity, QgsPalLayerSettings::Property::ShadowOpacity, exprVal, context.expressionContext(), shadowOpacity ) )
4212 {
4213 shadowOpacity = exprVal.toDouble();
4214 }
4215
4216 // data defined shadow offset distance?
4217 double shadowOffDist = shadow.offsetDistance();
4218 if ( dataDefinedValEval( DDDoublePos, QgsPalLayerSettings::Property::ShadowOffsetDist, exprVal, context.expressionContext(), shadowOffDist ) )
4219 {
4220 shadowOffDist = exprVal.toDouble();
4221 }
4222
4223 // data defined shadow offset distance?
4224 double shadowRad = shadow.blurRadius();
4225 if ( dataDefinedValEval( DDDoublePos, QgsPalLayerSettings::Property::ShadowRadius, exprVal, context.expressionContext(), shadowRad ) )
4226 {
4227 shadowRad = exprVal.toDouble();
4228 }
4229
4230 drawShadow = ( drawShadow && shadowOpacity > 0 && !( shadowOffDist == 0.0 && shadowRad == 0.0 ) );
4231
4232 if ( !drawShadow )
4233 {
4234 dataDefinedValues.insert( QgsPalLayerSettings::Property::ShadowDraw, QVariant( false ) ); // trigger value
4235 dataDefinedValues.remove( QgsPalLayerSettings::Property::ShadowOpacity );
4236 dataDefinedValues.remove( QgsPalLayerSettings::Property::ShadowOffsetDist );
4237 dataDefinedValues.remove( QgsPalLayerSettings::Property::ShadowRadius );
4238 return; // don't bother evaluating values that won't be used
4239 }
4240
4241 // data defined shadow under type?
4242 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::ShadowUnder ) )
4243 {
4244 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Property::ShadowUnder, context.expressionContext() );
4245 if ( !QgsVariantUtils::isNull( exprVal ) )
4246 {
4247 QString str = exprVal.toString().trimmed();
4248 QgsDebugMsgLevel( u"exprVal ShadowUnder:%1"_s.arg( str ), 4 );
4249
4250 if ( !str.isEmpty() )
4251 {
4253 dataDefinedValues.insert( QgsPalLayerSettings::Property::ShadowUnder, QVariant( static_cast< int >( shdwtype ) ) );
4254 }
4255 }
4256 }
4257
4258 // data defined shadow offset angle?
4259 dataDefinedValEval( DDRotation180, QgsPalLayerSettings::Property::ShadowOffsetAngle, exprVal, context.expressionContext(), shadow.offsetAngle() );
4260
4261 // data defined shadow offset units?
4262 dataDefinedValEval( DDUnits, QgsPalLayerSettings::Property::ShadowOffsetUnits, exprVal, context.expressionContext() );
4263
4264 // data defined shadow radius?
4265 dataDefinedValEval( DDDouble, QgsPalLayerSettings::Property::ShadowRadius, exprVal, context.expressionContext(), shadow.blurRadius() );
4266
4267 // data defined shadow radius units?
4268 dataDefinedValEval( DDUnits, QgsPalLayerSettings::Property::ShadowRadiusUnits, exprVal, context.expressionContext() );
4269
4270 // data defined shadow scale? ( gui bounds to 0-2000, no upper bound here )
4271 dataDefinedValEval( DDIntPos, QgsPalLayerSettings::Property::ShadowScale, exprVal, context.expressionContext(), shadow.scale() );
4272
4273 // data defined shadow color?
4274 dataDefinedValEval( DDColor, QgsPalLayerSettings::Property::ShadowColor, exprVal, context.expressionContext(), QgsColorUtils::colorToString( shadow.color() ) );
4275
4276 // data defined shadow blend mode?
4277 dataDefinedValEval( DDBlendMode, QgsPalLayerSettings::Property::ShadowBlendMode, exprVal, context.expressionContext() );
4278}
4279
4280// -------------
4281
4282
4284{
4285 switch ( layer->type() )
4286 {
4288 {
4289 const QgsVectorLayer *vl = qobject_cast< const QgsVectorLayer * >( layer );
4290 return ( vl->labelsEnabled() && vl->labeling() )
4291 || ( vl->diagramsEnabled() && vl->diagramRenderer() )
4292 || ( vl->renderer() && vl->renderer()->flags().testFlag( Qgis::FeatureRendererFlag::AffectsLabeling ) );
4293 }
4294
4296 {
4297 const QgsVectorTileLayer *vl = qobject_cast< const QgsVectorTileLayer * >( layer );
4298 if ( !vl->labeling() )
4299 return false;
4300
4301 if ( const QgsVectorTileBasicLabeling *labeling = dynamic_cast< const QgsVectorTileBasicLabeling *>( vl->labeling() ) )
4302 return !labeling->styles().empty();
4303
4304 return false;
4305 }
4306
4308 {
4309 const QgsMeshLayer *ml = qobject_cast< const QgsMeshLayer * >( layer );
4310 return ml->labeling() && ml->labelsEnabled();
4311 }
4312
4314 {
4315 const QgsRasterLayer *rl = qobject_cast< const QgsRasterLayer * >( layer );
4316 return rl->labeling() && rl->labelsEnabled();
4317 }
4318
4324 return false;
4325 }
4326 return false;
4327}
4328
4329
4330bool QgsPalLabeling::geometryRequiresPreparation( const QgsGeometry &geometry, QgsRenderContext &context, const QgsCoordinateTransform &ct, const QgsGeometry &clipGeometry, bool mergeLines )
4331{
4332 if ( geometry.isNull() )
4333 {
4334 return false;
4335 }
4336
4337 if ( geometry.type() == Qgis::GeometryType::Line && geometry.isMultipart() && mergeLines )
4338 {
4339 return true;
4340 }
4341
4342 //requires reprojection
4343 if ( ct.isValid() && !ct.isShortCircuited() )
4344 return true;
4345
4346 //requires rotation
4347 const QgsMapToPixel &m2p = context.mapToPixel();
4348 if ( !qgsDoubleNear( m2p.mapRotation(), 0 ) )
4349 return true;
4350
4351 //requires clip
4352 if ( !clipGeometry.isNull() && !clipGeometry.boundingBox().contains( geometry.boundingBox() ) )
4353 return true;
4354
4355 //requires fixing
4356 if ( geometry.type() == Qgis::GeometryType::Polygon && !geometry.isGeosValid() )
4357 return true;
4358
4359 return false;
4360}
4361
4362QStringList QgsPalLabeling::splitToLines( const QString &text, const QString &wrapCharacter, const int autoWrapLength, const bool useMaxLineLengthWhenAutoWrapping )
4363{
4364 QStringList multiLineSplit;
4365 if ( !wrapCharacter.isEmpty() && wrapCharacter != "\n"_L1 )
4366 {
4367 //wrap on both the wrapchr and new line characters
4368 const QStringList lines = text.split( wrapCharacter );
4369 for ( const QString &line : lines )
4370 {
4371 multiLineSplit.append( line.split( '\n' ) );
4372 }
4373 }
4374 else
4375 {
4376 multiLineSplit = text.split( '\n' );
4377 }
4378
4379 // apply auto wrapping to each manually created line
4380 if ( autoWrapLength != 0 )
4381 {
4382 QStringList autoWrappedLines;
4383 autoWrappedLines.reserve( multiLineSplit.count() );
4384 for ( const QString &line : std::as_const( multiLineSplit ) )
4385 {
4386 autoWrappedLines.append( QgsStringUtils::wordWrap( line, autoWrapLength, useMaxLineLengthWhenAutoWrapping ).split( '\n' ) );
4387 }
4388 multiLineSplit = autoWrappedLines;
4389 }
4390 return multiLineSplit;
4391}
4392
4393QStringList QgsPalLabeling::splitToGraphemes( const QString &text )
4394{
4395 QStringList graphemes;
4396 QTextBoundaryFinder boundaryFinder( QTextBoundaryFinder::Grapheme, text );
4397 int currentBoundary = -1;
4398 int previousBoundary = 0;
4399 while ( ( currentBoundary = boundaryFinder.toNextBoundary() ) > 0 )
4400 {
4401 graphemes << text.mid( previousBoundary, currentBoundary - previousBoundary );
4402 previousBoundary = currentBoundary;
4403 }
4404 return graphemes;
4405}
4406
4407QgsGeometry QgsPalLabeling::prepareGeometry( const QgsGeometry &geometry, QgsRenderContext &context, const QgsCoordinateTransform &ct, const QgsGeometry &clipGeometry, bool mergeLines )
4408{
4409 if ( geometry.isNull() )
4410 {
4411 return QgsGeometry();
4412 }
4413
4414 //don't modify the feature's geometry so that geometry based expressions keep working
4415 QgsGeometry geom = geometry;
4416
4417 if ( geom.type() == Qgis::GeometryType::Line && geom.isMultipart() && mergeLines )
4418 {
4419 geom = geom.mergeLines();
4420 }
4421
4422 //reproject the geometry if necessary
4423 if ( ct.isValid() && !ct.isShortCircuited() )
4424 {
4425 try
4426 {
4427 geom.transform( ct );
4428 }
4429 catch ( QgsCsException &cse )
4430 {
4431 Q_UNUSED( cse )
4432 QgsDebugMsgLevel( u"Ignoring feature due to transformation exception"_s, 4 );
4433 return QgsGeometry();
4434 }
4435 // geometry transforms may result in nan points, remove these
4436 geom.filterVertices( []( const QgsPoint & point )->bool
4437 {
4438 return std::isfinite( point.x() ) && std::isfinite( point.y() );
4439 } );
4441 {
4442 cp->removeInvalidRings();
4443 }
4445 {
4446 for ( int i = 0; i < ms->numGeometries(); ++i )
4447 {
4448 if ( QgsCurvePolygon *cp = qgsgeometry_cast< QgsCurvePolygon * >( ms->geometryN( i ) ) )
4449 cp->removeInvalidRings();
4450 }
4451 }
4452 }
4453
4454 // Rotate the geometry if needed
4455 const QgsMapToPixel &m2p = context.mapToPixel();
4456 if ( !qgsDoubleNear( m2p.mapRotation(), 0 ) )
4457 {
4458 QgsPointXY center = context.mapExtent().center();
4459 if ( geom.rotate( m2p.mapRotation(), center ) != Qgis::GeometryOperationResult::Success )
4460 {
4461 QgsDebugError( u"Error rotating geometry"_s.arg( geom.asWkt() ) );
4462 return QgsGeometry();
4463 }
4464 }
4465
4466 const bool mustClip = ( !clipGeometry.isNull() &&
4467 ( ( qgsDoubleNear( m2p.mapRotation(), 0 ) && !clipGeometry.boundingBox().contains( geom.boundingBox() ) )
4468 || ( !qgsDoubleNear( m2p.mapRotation(), 0 ) && !clipGeometry.contains( geom ) ) ) );
4469
4470 bool mustClipExact = false;
4471 if ( mustClip )
4472 {
4473 // nice and fast, but can result in invalid geometries. At least it will potentially strip out a bunch of unwanted vertices upfront!
4474 QgsGeometry clipGeom = geom.clipped( clipGeometry.boundingBox() );
4475 if ( clipGeom.isEmpty() )
4476 return QgsGeometry();
4477
4478 geom = clipGeom;
4479
4480 // we've now clipped against the BOUNDING BOX of clipGeometry. But if clipGeometry is an axis parallel rectangle, then there's no
4481 // need to do an exact (potentially costly) intersection clip as well!
4482 mustClipExact = !clipGeometry.isAxisParallelRectangle( 0.001 );
4483 }
4484
4485 // fix invalid polygons
4486 if ( geom.type() == Qgis::GeometryType::Polygon )
4487 {
4488 if ( geom.isMultipart() )
4489 {
4490 // important -- we need to treat ever part in isolation here. We can't test the validity of the whole geometry
4491 // at once, because touching parts would result in an invalid geometry, and buffering this "dissolves" the parts.
4492 // because the actual label engine treats parts as separate entities, we aren't bound by the usual "touching parts are invalid" rule
4493 // see https://github.com/qgis/QGIS/issues/26763
4494 QVector< QgsGeometry> parts;
4495 parts.reserve( qgsgeometry_cast< const QgsGeometryCollection * >( geom.constGet() )->numGeometries() );
4496 for ( auto it = geom.const_parts_begin(); it != geom.const_parts_end(); ++it )
4497 {
4498 QgsGeometry partGeom( ( *it )->clone() );
4499 if ( !partGeom.isGeosValid() )
4500 {
4501
4502 partGeom = partGeom.makeValid();
4503 }
4504 parts.append( partGeom );
4505 }
4506 geom = QgsGeometry::collectGeometry( parts );
4507 }
4508 else if ( !geom.isGeosValid() )
4509 {
4510
4511 QgsGeometry bufferGeom = geom.makeValid();
4512 if ( bufferGeom.isNull() )
4513 {
4514 QgsDebugError( u"Could not repair geometry: %1"_s.arg( bufferGeom.lastError() ) );
4515 return QgsGeometry();
4516 }
4517 geom = bufferGeom;
4518 }
4519 }
4520
4521 if ( mustClipExact )
4522 {
4523 // now do the real intersection against the actual clip geometry
4524 QgsGeometry clipGeom = geom.intersection( clipGeometry );
4525 if ( clipGeom.isEmpty() )
4526 {
4527 return QgsGeometry();
4528 }
4529 geom = clipGeom;
4530 }
4531
4532 return geom;
4533}
4534
4535bool QgsPalLabeling::checkMinimumSizeMM( const QgsRenderContext &context, const QgsGeometry &geom, double minSize )
4536{
4537 if ( minSize <= 0 )
4538 {
4539 return true;
4540 }
4541
4542 if ( geom.isNull() )
4543 {
4544 return false;
4545 }
4546
4547 Qgis::GeometryType featureType = geom.type();
4548 if ( featureType == Qgis::GeometryType::Point ) //minimum size does not apply to point features
4549 {
4550 return true;
4551 }
4552
4553 double mapUnitsPerMM = context.mapToPixel().mapUnitsPerPixel() * context.scaleFactor();
4554 if ( featureType == Qgis::GeometryType::Line )
4555 {
4556 double length = geom.length();
4557 if ( length >= 0.0 )
4558 {
4559 return ( length >= ( minSize * mapUnitsPerMM ) );
4560 }
4561 }
4562 else if ( featureType == Qgis::GeometryType::Polygon )
4563 {
4564 double area = geom.area();
4565 if ( area >= 0.0 )
4566 {
4567 return ( std::sqrt( area ) >= ( minSize * mapUnitsPerMM ) );
4568 }
4569 }
4570 return true; //should never be reached. Return true in this case to label such geometries anyway.
4571}
4572
4573
4574void QgsPalLabeling::dataDefinedTextStyle( QgsPalLayerSettings &tmpLyr,
4575 const QMap< QgsPalLayerSettings::Property, QVariant > &ddValues )
4576{
4577 QgsTextFormat format = tmpLyr.format();
4578 bool changed = false;
4579
4580 //font color
4581 if ( ddValues.contains( QgsPalLayerSettings::Property::Color ) )
4582 {
4583 QVariant ddColor = ddValues.value( QgsPalLayerSettings::Property::Color );
4584 format.setColor( ddColor.value<QColor>() );
4585 changed = true;
4586 }
4587
4588 //font transparency
4589 if ( ddValues.contains( QgsPalLayerSettings::Property::FontOpacity ) )
4590 {
4591 format.setOpacity( ddValues.value( QgsPalLayerSettings::Property::FontOpacity ).toDouble() / 100.0 );
4592 changed = true;
4593 }
4594
4595 //font blend mode
4596 if ( ddValues.contains( QgsPalLayerSettings::Property::FontBlendMode ) )
4597 {
4598 format.setBlendMode( static_cast< QPainter::CompositionMode >( ddValues.value( QgsPalLayerSettings::Property::FontBlendMode ).toInt() ) );
4599 changed = true;
4600 }
4601
4602 if ( changed )
4603 {
4604 tmpLyr.setFormat( format );
4605 }
4606}
4607
4608void QgsPalLabeling::dataDefinedTextFormatting( QgsPalLayerSettings &tmpLyr,
4609 const QMap< QgsPalLayerSettings::Property, QVariant > &ddValues )
4610{
4611 if ( ddValues.contains( QgsPalLayerSettings::Property::MultiLineWrapChar ) )
4612 {
4613 tmpLyr.wrapChar = ddValues.value( QgsPalLayerSettings::Property::MultiLineWrapChar ).toString();
4614 }
4615
4616 if ( ddValues.contains( QgsPalLayerSettings::Property::AutoWrapLength ) )
4617 {
4618 tmpLyr.autoWrapLength = ddValues.value( QgsPalLayerSettings::Property::AutoWrapLength ).toInt();
4619 }
4620
4621 if ( ddValues.contains( QgsPalLayerSettings::Property::MultiLineHeight ) )
4622 {
4623 QgsTextFormat format = tmpLyr.format();
4624 format.setLineHeight( ddValues.value( QgsPalLayerSettings::Property::MultiLineHeight ).toDouble() );
4625 tmpLyr.setFormat( format );
4626 }
4627
4628 if ( ddValues.contains( QgsPalLayerSettings::Property::TabStopDistance ) )
4629 {
4630 QgsTextFormat format = tmpLyr.format();
4631 QList<QgsTextFormat::Tab> tabPositions;
4632 if ( ddValues.value( QgsPalLayerSettings::Property::TabStopDistance ).userType() == QMetaType::Type::QVariantList )
4633 {
4634 const QVariantList parts = ddValues.value( QgsPalLayerSettings::Property::TabStopDistance ).toList();
4635 for ( const QVariant &part : parts )
4636 {
4637 tabPositions.append( QgsTextFormat::Tab( part.toDouble() ) );
4638 }
4639 format.setTabPositions( tabPositions );
4640 }
4641 else if ( ddValues.value( QgsPalLayerSettings::Property::TabStopDistance ).userType() == QMetaType::Type::QStringList )
4642 {
4643 const QStringList parts = ddValues.value( QgsPalLayerSettings::Property::TabStopDistance ).toStringList();
4644 for ( const QString &part : parts )
4645 {
4646 tabPositions.append( QgsTextFormat::Tab( part.toDouble() ) );
4647 }
4648 format.setTabPositions( tabPositions );
4649 }
4650 else
4651 {
4652 format.setTabPositions( tabPositions );
4653 format.setTabStopDistance( ddValues.value( QgsPalLayerSettings::Property::TabStopDistance ).toDouble() );
4654 }
4655 tmpLyr.setFormat( format );
4656 }
4657
4658 if ( ddValues.contains( QgsPalLayerSettings::Property::MultiLineAlignment ) )
4659 {
4660 tmpLyr.multilineAlign = static_cast< Qgis::LabelMultiLineAlignment >( ddValues.value( QgsPalLayerSettings::Property::MultiLineAlignment ).toInt() );
4661 }
4662
4663 if ( ddValues.contains( QgsPalLayerSettings::Property::TextOrientation ) )
4664 {
4665 QgsTextFormat format = tmpLyr.format();
4667 tmpLyr.setFormat( format );
4668 }
4669
4670 if ( ddValues.contains( QgsPalLayerSettings::Property::DirSymbDraw ) )
4671 {
4673 }
4674
4675 if ( tmpLyr.lineSettings().addDirectionSymbol() )
4676 {
4677
4678 if ( ddValues.contains( QgsPalLayerSettings::Property::DirSymbLeft ) )
4679 {
4681 }
4682 if ( ddValues.contains( QgsPalLayerSettings::Property::DirSymbRight ) )
4683 {
4685 }
4686
4687 if ( ddValues.contains( QgsPalLayerSettings::Property::DirSymbPlacement ) )
4688 {
4690 }
4691
4692 if ( ddValues.contains( QgsPalLayerSettings::Property::DirSymbReverse ) )
4693 {
4695 }
4696 }
4697}
4698
4699void QgsPalLabeling::dataDefinedTextBuffer( QgsPalLayerSettings &tmpLyr,
4700 const QMap< QgsPalLayerSettings::Property, QVariant > &ddValues )
4701{
4702 QgsTextBufferSettings buffer = tmpLyr.format().buffer();
4703 bool changed = false;
4704
4705 //buffer draw
4706 if ( ddValues.contains( QgsPalLayerSettings::Property::BufferDraw ) )
4707 {
4708 buffer.setEnabled( ddValues.value( QgsPalLayerSettings::Property::BufferDraw ).toBool() );
4709 changed = true;
4710 }
4711
4712 if ( !buffer.enabled() )
4713 {
4714 if ( changed )
4715 {
4716 QgsTextFormat format = tmpLyr.format();
4717 format.setBuffer( buffer );
4718 tmpLyr.setFormat( format );
4719 }
4720
4721 // tmpLyr.bufferSize > 0.0 && tmpLyr.bufferTransp < 100 figured in during evaluation
4722 return; // don't continue looking for unused values
4723 }
4724
4725 //buffer size
4726 if ( ddValues.contains( QgsPalLayerSettings::Property::BufferSize ) )
4727 {
4728 buffer.setSize( ddValues.value( QgsPalLayerSettings::Property::BufferSize ).toDouble() );
4729 changed = true;
4730 }
4731
4732 //buffer opacity
4733 if ( ddValues.contains( QgsPalLayerSettings::Property::BufferOpacity ) )
4734 {
4735 buffer.setOpacity( ddValues.value( QgsPalLayerSettings::Property::BufferOpacity ).toDouble() / 100.0 );
4736 changed = true;
4737 }
4738
4739 //buffer size units
4740 if ( ddValues.contains( QgsPalLayerSettings::Property::BufferUnit ) )
4741 {
4742 Qgis::RenderUnit bufunit = static_cast< Qgis::RenderUnit >( ddValues.value( QgsPalLayerSettings::Property::BufferUnit ).toInt() );
4743 buffer.setSizeUnit( bufunit );
4744 changed = true;
4745 }
4746
4747 //buffer color
4748 if ( ddValues.contains( QgsPalLayerSettings::Property::BufferColor ) )
4749 {
4750 QVariant ddColor = ddValues.value( QgsPalLayerSettings::Property::BufferColor );
4751 buffer.setColor( ddColor.value<QColor>() );
4752 changed = true;
4753 }
4754
4755 //buffer pen join style
4756 if ( ddValues.contains( QgsPalLayerSettings::Property::BufferJoinStyle ) )
4757 {
4758 buffer.setJoinStyle( static_cast< Qt::PenJoinStyle >( ddValues.value( QgsPalLayerSettings::Property::BufferJoinStyle ).toInt() ) );
4759 changed = true;
4760 }
4761
4762 //buffer blend mode
4763 if ( ddValues.contains( QgsPalLayerSettings::Property::BufferBlendMode ) )
4764 {
4765 buffer.setBlendMode( static_cast< QPainter::CompositionMode >( ddValues.value( QgsPalLayerSettings::Property::BufferBlendMode ).toInt() ) );
4766 changed = true;
4767 }
4768
4769 if ( changed )
4770 {
4771 QgsTextFormat format = tmpLyr.format();
4772 format.setBuffer( buffer );
4773 tmpLyr.setFormat( format );
4774 }
4775}
4776
4777void QgsPalLabeling::dataDefinedTextMask( QgsPalLayerSettings &tmpLyr,
4778 const QMap< QgsPalLayerSettings::Property, QVariant > &ddValues )
4779{
4780 if ( ddValues.isEmpty() )
4781 return;
4782
4783 QgsTextMaskSettings mask = tmpLyr.format().mask();
4784 bool changed = false;
4785
4786 // enabled ?
4787 if ( ddValues.contains( QgsPalLayerSettings::Property::MaskEnabled ) )
4788 {
4789 mask.setEnabled( ddValues.value( QgsPalLayerSettings::Property::MaskEnabled ).toBool() );
4790 changed = true;
4791 }
4792
4793 if ( !mask.enabled() )
4794 {
4795 if ( changed )
4796 {
4797 QgsTextFormat format = tmpLyr.format();
4798 format.setMask( mask );
4799 tmpLyr.setFormat( format );
4800 }
4801
4802 // tmpLyr.bufferSize > 0.0 && tmpLyr.bufferTransp < 100 figured in during evaluation
4803 return; // don't continue looking for unused values
4804 }
4805
4806 // buffer size
4807 if ( ddValues.contains( QgsPalLayerSettings::Property::MaskBufferSize ) )
4808 {
4809 mask.setSize( ddValues.value( QgsPalLayerSettings::Property::MaskBufferSize ).toDouble() );
4810 changed = true;
4811 }
4812
4813 // opacity
4814 if ( ddValues.contains( QgsPalLayerSettings::Property::MaskOpacity ) )
4815 {
4816 mask.setOpacity( ddValues.value( QgsPalLayerSettings::Property::MaskOpacity ).toDouble() / 100.0 );
4817 changed = true;
4818 }
4819
4820 // buffer size units
4821 if ( ddValues.contains( QgsPalLayerSettings::Property::MaskBufferUnit ) )
4822 {
4823 Qgis::RenderUnit bufunit = static_cast< Qgis::RenderUnit >( ddValues.value( QgsPalLayerSettings::Property::MaskBufferUnit ).toInt() );
4824 mask.setSizeUnit( bufunit );
4825 changed = true;
4826 }
4827
4828 // pen join style
4829 if ( ddValues.contains( QgsPalLayerSettings::Property::MaskJoinStyle ) )
4830 {
4831 mask.setJoinStyle( static_cast< Qt::PenJoinStyle >( ddValues.value( QgsPalLayerSettings::Property::MaskJoinStyle ).toInt() ) );
4832 changed = true;
4833 }
4834
4835 if ( changed )
4836 {
4837 QgsTextFormat format = tmpLyr.format();
4838 format.setMask( mask );
4839 tmpLyr.setFormat( format );
4840 }
4841}
4842
4843void QgsPalLabeling::dataDefinedShapeBackground( QgsPalLayerSettings &tmpLyr,
4844 const QMap< QgsPalLayerSettings::Property, QVariant > &ddValues )
4845{
4846 QgsTextBackgroundSettings background = tmpLyr.format().background();
4847 bool changed = false;
4848
4849 //shape draw
4850 if ( ddValues.contains( QgsPalLayerSettings::Property::ShapeDraw ) )
4851 {
4852 background.setEnabled( ddValues.value( QgsPalLayerSettings::Property::ShapeDraw ).toBool() );
4853 changed = true;
4854 }
4855
4856 if ( !background.enabled() )
4857 {
4858 if ( changed )
4859 {
4860 QgsTextFormat format = tmpLyr.format();
4861 format.setBackground( background );
4862 tmpLyr.setFormat( format );
4863 }
4864 return; // don't continue looking for unused values
4865 }
4866
4867 if ( ddValues.contains( QgsPalLayerSettings::Property::ShapeKind ) )
4868 {
4869 background.setType( static_cast< QgsTextBackgroundSettings::ShapeType >( ddValues.value( QgsPalLayerSettings::Property::ShapeKind ).toInt() ) );
4870 changed = true;
4871 }
4872
4873 if ( ddValues.contains( QgsPalLayerSettings::Property::ShapeSVGFile ) )
4874 {
4875 background.setSvgFile( ddValues.value( QgsPalLayerSettings::Property::ShapeSVGFile ).toString() );
4876 changed = true;
4877 }
4878
4879 if ( ddValues.contains( QgsPalLayerSettings::Property::ShapeSizeType ) )
4880 {
4881 background.setSizeType( static_cast< QgsTextBackgroundSettings::SizeType >( ddValues.value( QgsPalLayerSettings::Property::ShapeSizeType ).toInt() ) );
4882 changed = true;
4883 }
4884
4885 if ( ddValues.contains( QgsPalLayerSettings::Property::ShapeSizeX ) )
4886 {
4887 QSizeF size = background.size();
4888 size.setWidth( ddValues.value( QgsPalLayerSettings::Property::ShapeSizeX ).toDouble() );
4889 background.setSize( size );
4890 changed = true;
4891 }
4892 if ( ddValues.contains( QgsPalLayerSettings::Property::ShapeSizeY ) )
4893 {
4894 QSizeF size = background.size();
4895 size.setHeight( ddValues.value( QgsPalLayerSettings::Property::ShapeSizeY ).toDouble() );
4896 background.setSize( size );
4897 changed = true;
4898 }
4899
4900 if ( ddValues.contains( QgsPalLayerSettings::Property::ShapeSizeUnits ) )
4901 {
4902 background.setSizeUnit( static_cast< Qgis::RenderUnit >( ddValues.value( QgsPalLayerSettings::Property::ShapeSizeUnits ).toInt() ) );
4903 changed = true;
4904 }
4905
4906 if ( ddValues.contains( QgsPalLayerSettings::Property::ShapeRotationType ) )
4907 {
4909 changed = true;
4910 }
4911
4912 if ( ddValues.contains( QgsPalLayerSettings::Property::ShapeRotation ) )
4913 {
4914 background.setRotation( ddValues.value( QgsPalLayerSettings::Property::ShapeRotation ).toDouble() );
4915 changed = true;
4916 }
4917
4918 if ( ddValues.contains( QgsPalLayerSettings::Property::ShapeOffset ) )
4919 {
4920 background.setOffset( ddValues.value( QgsPalLayerSettings::Property::ShapeOffset ).toPointF() );
4921 changed = true;
4922 }
4923
4924 if ( ddValues.contains( QgsPalLayerSettings::Property::ShapeOffsetUnits ) )
4925 {
4926 background.setOffsetUnit( static_cast< Qgis::RenderUnit >( ddValues.value( QgsPalLayerSettings::Property::ShapeOffsetUnits ).toInt() ) );
4927 changed = true;
4928 }
4929
4930 if ( ddValues.contains( QgsPalLayerSettings::Property::ShapeRadii ) )
4931 {
4932 background.setRadii( ddValues.value( QgsPalLayerSettings::Property::ShapeRadii ).toSizeF() );
4933 changed = true;
4934 }
4935
4936 if ( ddValues.contains( QgsPalLayerSettings::Property::ShapeRadiiUnits ) )
4937 {
4938 background.setRadiiUnit( static_cast< Qgis::RenderUnit >( ddValues.value( QgsPalLayerSettings::Property::ShapeRadiiUnits ).toInt() ) );
4939 changed = true;
4940 }
4941
4942 if ( ddValues.contains( QgsPalLayerSettings::Property::ShapeBlendMode ) )
4943 {
4944 background.setBlendMode( static_cast< QPainter::CompositionMode >( ddValues.value( QgsPalLayerSettings::Property::ShapeBlendMode ).toInt() ) );
4945 changed = true;
4946 }
4947
4948 if ( ddValues.contains( QgsPalLayerSettings::Property::ShapeFillColor ) )
4949 {
4950 QVariant ddColor = ddValues.value( QgsPalLayerSettings::Property::ShapeFillColor );
4951 background.setFillColor( ddColor.value<QColor>() );
4952 changed = true;
4953 }
4954
4955 if ( ddValues.contains( QgsPalLayerSettings::Property::ShapeStrokeColor ) )
4956 {
4957 QVariant ddColor = ddValues.value( QgsPalLayerSettings::Property::ShapeStrokeColor );
4958 background.setStrokeColor( ddColor.value<QColor>() );
4959 changed = true;
4960 }
4961
4962 if ( ddValues.contains( QgsPalLayerSettings::Property::ShapeOpacity ) )
4963 {
4964 background.setOpacity( ddValues.value( QgsPalLayerSettings::Property::ShapeOpacity ).toDouble() / 100.0 );
4965 changed = true;
4966 }
4967
4968 if ( ddValues.contains( QgsPalLayerSettings::Property::ShapeStrokeWidth ) )
4969 {
4970 background.setStrokeWidth( ddValues.value( QgsPalLayerSettings::Property::ShapeStrokeWidth ).toDouble() );
4971 changed = true;
4972 }
4973
4975 {
4976 background.setStrokeWidthUnit( static_cast< Qgis::RenderUnit >( ddValues.value( QgsPalLayerSettings::Property::ShapeStrokeWidthUnits ).toInt() ) );
4977 changed = true;
4978 }
4979
4980 if ( ddValues.contains( QgsPalLayerSettings::Property::ShapeJoinStyle ) )
4981 {
4982 background.setJoinStyle( static_cast< Qt::PenJoinStyle >( ddValues.value( QgsPalLayerSettings::Property::ShapeJoinStyle ).toInt() ) );
4983 changed = true;
4984 }
4985
4986 if ( changed )
4987 {
4988 QgsTextFormat format = tmpLyr.format();
4989 format.setBackground( background );
4990 tmpLyr.setFormat( format );
4991 }
4992}
4993
4994void QgsPalLabeling::dataDefinedDropShadow( QgsPalLayerSettings &tmpLyr,
4995 const QMap< QgsPalLayerSettings::Property, QVariant > &ddValues )
4996{
4997 QgsTextShadowSettings shadow = tmpLyr.format().shadow();
4998 bool changed = false;
4999
5000 //shadow draw
5001 if ( ddValues.contains( QgsPalLayerSettings::Property::ShadowDraw ) )
5002 {
5003 shadow.setEnabled( ddValues.value( QgsPalLayerSettings::Property::ShadowDraw ).toBool() );
5004 changed = true;
5005 }
5006
5007 if ( !shadow.enabled() )
5008 {
5009 if ( changed )
5010 {
5011 QgsTextFormat format = tmpLyr.format();
5012 format.setShadow( shadow );
5013 tmpLyr.setFormat( format );
5014 }
5015 return; // don't continue looking for unused values
5016 }
5017
5018 if ( ddValues.contains( QgsPalLayerSettings::Property::ShadowUnder ) )
5019 {
5021 changed = true;
5022 }
5023
5024 if ( ddValues.contains( QgsPalLayerSettings::Property::ShadowOffsetAngle ) )
5025 {
5026 shadow.setOffsetAngle( ddValues.value( QgsPalLayerSettings::Property::ShadowOffsetAngle ).toInt() );
5027 changed = true;
5028 }
5029
5030 if ( ddValues.contains( QgsPalLayerSettings::Property::ShadowOffsetDist ) )
5031 {
5032 shadow.setOffsetDistance( ddValues.value( QgsPalLayerSettings::Property::ShadowOffsetDist ).toDouble() );
5033 changed = true;
5034 }
5035
5036 if ( ddValues.contains( QgsPalLayerSettings::Property::ShadowOffsetUnits ) )
5037 {
5038 shadow.setOffsetUnit( static_cast< Qgis::RenderUnit >( ddValues.value( QgsPalLayerSettings::Property::ShadowOffsetUnits ).toInt() ) );
5039 changed = true;
5040 }
5041
5042 if ( ddValues.contains( QgsPalLayerSettings::Property::ShadowRadius ) )
5043 {
5044 shadow.setBlurRadius( ddValues.value( QgsPalLayerSettings::Property::ShadowRadius ).toDouble() );
5045 changed = true;
5046 }
5047
5048 if ( ddValues.contains( QgsPalLayerSettings::Property::ShadowRadiusUnits ) )
5049 {
5050 shadow.setBlurRadiusUnit( static_cast< Qgis::RenderUnit >( ddValues.value( QgsPalLayerSettings::Property::ShadowRadiusUnits ).toInt() ) );
5051 changed = true;
5052 }
5053
5054 if ( ddValues.contains( QgsPalLayerSettings::Property::ShadowColor ) )
5055 {
5056 QVariant ddColor = ddValues.value( QgsPalLayerSettings::Property::ShadowColor );
5057 shadow.setColor( ddColor.value<QColor>() );
5058 changed = true;
5059 }
5060
5061 if ( ddValues.contains( QgsPalLayerSettings::Property::ShadowOpacity ) )
5062 {
5063 shadow.setOpacity( ddValues.value( QgsPalLayerSettings::Property::ShadowOpacity ).toDouble() / 100.0 );
5064 changed = true;
5065 }
5066
5067 if ( ddValues.contains( QgsPalLayerSettings::Property::ShadowScale ) )
5068 {
5069 shadow.setScale( ddValues.value( QgsPalLayerSettings::Property::ShadowScale ).toInt() );
5070 changed = true;
5071 }
5072
5073
5074 if ( ddValues.contains( QgsPalLayerSettings::Property::ShadowBlendMode ) )
5075 {
5076 shadow.setBlendMode( static_cast< QPainter::CompositionMode >( ddValues.value( QgsPalLayerSettings::Property::ShadowBlendMode ).toInt() ) );
5077 changed = true;
5078 }
5079
5080 if ( changed )
5081 {
5082 QgsTextFormat format = tmpLyr.format();
5083 format.setShadow( shadow );
5084 tmpLyr.setFormat( format );
5085 }
5086}
5087
5089{
5090 //set both the mime color data, and the text (format settings).
5091
5092 QMimeData *mimeData = new QMimeData;
5093 mimeData->setColorData( QVariant( format().color() ) );
5094
5095 QgsReadWriteContext rwContext;
5096 QDomDocument textDoc;
5097 QDomElement textElem = writeXml( textDoc, rwContext );
5098 textDoc.appendChild( textElem );
5099 mimeData->setData( "application/qgis.labelsettings"_L1, textDoc.toString().toUtf8() );
5100
5101 return mimeData;
5102}
5103
5105{
5106 if ( ok )
5107 *ok = false;
5108 QgsPalLayerSettings settings;
5109 if ( !data || !data->hasFormat( "application/qgis.labelsettings"_L1 ) )
5110 return settings;
5111
5112 QString text = QString::fromUtf8( data->data( "application/qgis.labelsettings"_L1 ) );
5113 if ( !text.isEmpty() )
5114 {
5115 QDomDocument doc;
5116 QDomElement elem;
5117 QgsReadWriteContext rwContext;
5118
5119 if ( doc.setContent( text ) )
5120 {
5121 elem = doc.documentElement();
5122
5123 settings.readXml( elem, rwContext );
5124 if ( ok )
5125 *ok = true;
5126 return settings;
5127 }
5128 }
5129 return settings;
5130}
@ PreferVector
Prefer vector-based rendering, when the result will still be visually near-identical to a raster-base...
Definition qgis.h:2763
@ StretchCharacterSpacingToFitLine
Increases (or decreases) the character spacing used for each label in order to fit the entire text ov...
Definition qgis.h:1247
@ Default
Default curved placement, characters are placed in an optimal position along the line....
Definition qgis.h:1245
@ StretchWordSpacingToFitLine
Increases (or decreases) the word spacing used for each label in order to fit the entire text over th...
Definition qgis.h:1248
@ PlaceCharactersAtVertices
Each individual character from the label text is placed such that their left-baseline position is loc...
Definition qgis.h:1246
@ LabelLargestPartOnly
Place a label only on the largest part from the geometry.
Definition qgis.h:1284
@ SplitLabelTextLinesOverParts
Splits the label text over the parts of the geometry, such that each consecutive part is labeled with...
Definition qgis.h:1286
@ LabelEveryPartWithEntireLabel
Place the (same) entire label over every part from the geometry.
Definition qgis.h:1285
QFlags< VectorRenderingSimplificationFlag > VectorRenderingSimplificationFlags
Simplification flags for vector feature rendering.
Definition qgis.h:3102
@ Success
Operation succeeded.
Definition qgis.h:2101
AngleUnit
Units of angles.
Definition qgis.h:5245
@ Degrees
Degrees.
Definition qgis.h:5246
@ NoSimplification
No simplification can be applied.
Definition qgis.h:3088
LabelOffsetType
Behavior modifier for label offset and distance, only applies in some label placement modes.
Definition qgis.h:1299
@ FromPoint
Offset distance applies from point geometry.
Definition qgis.h:1300
@ FromSymbolBounds
Offset distance applies from rendered symbol bounds.
Definition qgis.h:1301
@ PreferCloser
Prefer closer labels, falling back to alternate positions before larger distances.
Definition qgis.h:1212
LabelPlacement
Placement modes which determine how label candidates are generated for a feature.
Definition qgis.h:1225
@ OverPoint
Arranges candidates over a point (or centroid of a polygon), or at a preset offset from the point....
Definition qgis.h:1227
@ Curved
Arranges candidates following the curvature of a line feature. Applies to line layers only.
Definition qgis.h:1229
@ AroundPoint
Arranges candidates in a circle around a point (or centroid of a polygon). Applies to point or polygo...
Definition qgis.h:1226
@ Line
Arranges candidates parallel to a generalised line representing the feature or parallel to a polygon'...
Definition qgis.h:1228
@ Free
Arranges candidates scattered throughout a polygon feature. Candidates are rotated to respect the pol...
Definition qgis.h:1231
@ OrderedPositionsAroundPoint
Candidates are placed in predefined positions around a point. Preference is given to positions with g...
Definition qgis.h:1232
@ Horizontal
Arranges horizontal candidates scattered throughout a polygon feature. Applies to polygon layers only...
Definition qgis.h:1230
@ PerimeterCurved
Arranges candidates following the curvature of a polygon's boundary. Applies to polygon layers only.
Definition qgis.h:1233
@ OutsidePolygons
Candidates are placed outside of polygon boundaries. Applies to polygon layers only.
Definition qgis.h:1234
@ AllowPlacementInsideOfPolygon
Labels can be placed inside a polygon feature.
Definition qgis.h:1360
@ AllowPlacementOutsideOfPolygon
Labels can be placed outside of a polygon feature.
Definition qgis.h:1359
VectorSimplificationAlgorithm
Simplification algorithms for vector features.
Definition qgis.h:3071
@ SkipEmptyInteriorRings
Skip any empty polygon interior ring.
Definition qgis.h:2207
@ Warning
Warning message.
Definition qgis.h:161
QFlags< LabelLinePlacementFlag > LabelLinePlacementFlags
Line placement flags, which control how candidates are generated for a linear feature.
Definition qgis.h:1348
@ Labeling
Labeling-specific layout mode.
Definition qgis.h:2962
@ Rectangle
Text within rectangle layout mode.
Definition qgis.h:2960
Capitalization
String capitalization options.
Definition qgis.h:3460
@ AllSmallCaps
Force all characters to small caps.
Definition qgis.h:3468
@ MixedCase
Mixed case, ie no change.
Definition qgis.h:3461
@ AllLowercase
Convert all characters to lowercase.
Definition qgis.h:3463
@ TitleCase
Simple title case conversion - does not fully grammatically parse the text and uses simple rules only...
Definition qgis.h:3466
@ SmallCaps
Mixed case small caps.
Definition qgis.h:3465
@ ForceFirstLetterToCapital
Convert just the first letter of each word to uppercase, leave the rest untouched.
Definition qgis.h:3464
@ AllUppercase
Convert all characters to uppercase.
Definition qgis.h:3462
QFlags< LabelPolygonPlacementFlag > LabelPolygonPlacementFlags
Polygon placement flags, which control how candidates are generated for a polygon feature.
Definition qgis.h:1370
LabelQuadrantPosition
Label quadrant positions.
Definition qgis.h:1313
@ AboveRight
Above right.
Definition qgis.h:1316
@ BelowLeft
Below left.
Definition qgis.h:1320
@ Above
Above center.
Definition qgis.h:1315
@ BelowRight
Below right.
Definition qgis.h:1322
@ Right
Right middle.
Definition qgis.h:1319
@ AboveLeft
Above left.
Definition qgis.h:1314
@ Below
Below center.
Definition qgis.h:1321
@ Over
Center middle.
Definition qgis.h:1318
TextOrientation
Text orientations.
Definition qgis.h:2944
@ Vertical
Vertically oriented text.
Definition qgis.h:2946
@ RotationBased
Horizontally or vertically oriented text based on rotation (only available for map labeling).
Definition qgis.h:2947
@ Horizontal
Horizontally oriented text.
Definition qgis.h:2945
UnplacedLabelVisibility
Unplaced label visibility.
Definition qgis.h:1174
@ FollowEngineSetting
Respect the label engine setting.
Definition qgis.h:1175
@ AffectsLabeling
If present, indicates that the renderer will participate in the map labeling problem.
Definition qgis.h:848
GeometryType
The geometry types are used to group Qgis::WkbType in a coarse way.
Definition qgis.h:365
@ Point
Points.
Definition qgis.h:366
@ Line
Lines.
Definition qgis.h:367
@ Polygon
Polygons.
Definition qgis.h:368
@ Unknown
Unknown types.
Definition qgis.h:369
LabelMultiLineAlignment
Text alignment for multi-line labels.
Definition qgis.h:1396
@ Center
Center align.
Definition qgis.h:1398
@ FollowPlacement
Alignment follows placement of label, e.g., labels to the left of a feature will be drawn with right ...
Definition qgis.h:1400
@ Group
Composite group layer. Added in QGIS 3.24.
Definition qgis.h:201
@ Plugin
Plugin based layer.
Definition qgis.h:196
@ TiledScene
Tiled scene layer. Added in QGIS 3.34.
Definition qgis.h:202
@ Annotation
Contains freeform, georeferenced annotations. Added in QGIS 3.16.
Definition qgis.h:199
@ Vector
Vector layer.
Definition qgis.h:194
@ VectorTile
Vector tile layer. Added in QGIS 3.14.
Definition qgis.h:198
@ Mesh
Mesh layer. Added in QGIS 3.2.
Definition qgis.h:197
@ Raster
Raster layer.
Definition qgis.h:195
@ PointCloud
Point cloud layer. Added in QGIS 3.18.
Definition qgis.h:200
@ TreatWhitespaceAsCollision
Treat overlapping whitespace text in labels and whitespace overlapping obstacles as collisions.
Definition qgis.h:1200
RenderUnit
Rendering size units.
Definition qgis.h:5290
@ Percentage
Percentage of another measurement (e.g., canvas size, feature size).
Definition qgis.h:5294
@ Millimeters
Millimeters.
Definition qgis.h:5291
@ Points
Points (e.g., for font sizes).
Definition qgis.h:5295
@ MapUnits
Map units.
Definition qgis.h:5292
@ Pixels
Pixels.
Definition qgis.h:5293
@ Antialiasing
Use antialiasing while drawing.
Definition qgis.h:2814
@ Center
Center align.
Definition qgis.h:3002
@ AllowOverlapIfRequired
Avoids overlapping labels when possible, but permit overlaps if labels for features cannot otherwise ...
Definition qgis.h:1188
@ PreventOverlap
Do not allow labels to overlap other labels.
Definition qgis.h:1187
static const double UI_SCALE_FACTOR
UI scaling factor.
Definition qgis.h:6534
@ MiddleLeft
Label on left of point.
Definition qgis.h:1266
@ TopRight
Label on top-right of point.
Definition qgis.h:1265
@ MiddleRight
Label on right of point.
Definition qgis.h:1267
@ TopSlightlyRight
Label on top of point, slightly right of center.
Definition qgis.h:1264
@ BottomRight
Label on bottom right of point.
Definition qgis.h:1272
@ BottomLeft
Label on bottom-left of point.
Definition qgis.h:1268
@ BottomSlightlyRight
Label below point, slightly right of center.
Definition qgis.h:1271
@ TopLeft
Label on top-left of point.
Definition qgis.h:1261
UpsideDownLabelHandling
Handling techniques for upside down labels.
Definition qgis.h:1381
@ FlipUpsideDownLabels
Upside-down labels (90 <= angle < 270) are shown upright.
Definition qgis.h:1382
virtual QgsAbstractGeometry * boundary() const =0
Returns the closure of the combinatorial boundary of the geometry (ie the topological boundary of the...
bool valueAsBool(int key, const QgsExpressionContext &context, bool defaultValue=false, bool *ok=nullptr) const
Calculates the current value of the property with the specified key and interprets it as an boolean.
static QIcon getThemeIcon(const QString &name, const QColor &fillColor=QColor(), const QColor &strokeColor=QColor())
Helper to get a theme icon.
static QgsFontManager * fontManager()
Returns the application font manager, which manages available fonts and font installation for the QGI...
static QgsCalloutRegistry * calloutRegistry()
Returns the application's callout registry, used for managing callout types.
Registry of available callout classes.
static QgsCallout * defaultCallout()
Create a new instance of a callout with default settings.
Contains additional contextual information about the context in which a callout is being rendered.
Definition qgscallout.h:250
Abstract base class for callout renderers.
Definition qgscallout.h:55
bool enabled() const
Returns true if the callout is enabled.
Definition qgscallout.h:323
static QColor colorFromString(const QString &string)
Decodes a string into a color value.
static QString colorToString(const QColor &color)
Encodes a color into a string value.
Represents a coordinate reference system (CRS).
QString userFriendlyIdentifier(Qgis::CrsIdentifierType type=Qgis::CrsIdentifierType::MediumString) const
Returns a user friendly identifier for the CRS.
Handles coordinate transforms between two coordinate systems.
QgsCoordinateReferenceSystem sourceCrs() const
Returns the source coordinate reference system, which the transform will transform coordinates from.
bool isShortCircuited() const
Returns true if the transform short circuits because the source and destination are equivalent.
bool isValid() const
Returns true if the coordinate transform is valid, ie both the source and destination CRS have been s...
Custom exception class for Coordinate Reference System related exceptions.
Curve polygon geometry type.
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
void setOriginalValueVariable(const QVariant &value)
Sets the original value variable value for the context.
Handles parsing and evaluation of expressions (formerly called "search strings").
bool prepare(const QgsExpressionContext *context)
Gets the expression ready for evaluation - find out column indexes.
bool hasParserError() const
Returns true if an error occurred when parsing the input expression.
QString evalErrorString() const
Returns evaluation error.
QString parserErrorString() const
Returns parser error.
QSet< QString > referencedColumns() const
Gets list of columns referenced by the expression.
bool hasEvalError() const
Returns true if an error occurred when evaluating last input.
QVariant evaluate()
Evaluate the feature and return the result.
virtual Qgis::FeatureRendererFlags flags() const
Returns flags associated with the renderer.
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
Definition qgsfeature.h:60
QgsFeatureId id
Definition qgsfeature.h:68
QgsGeometry geometry
Definition qgsfeature.h:71
Q_INVOKABLE QVariant attribute(const QString &name) const
Lookup attribute value by attribute name.
void setGeometry(const QgsGeometry &geometry)
Set the feature's geometry.
Container of fields for a vector layer.
Definition qgsfields.h:46
Q_INVOKABLE int lookupField(const QString &fieldName) const
Looks up field's index from the field name.
QString processFontFamilyName(const QString &name) const
Processes a font family name, applying any matching fontFamilyReplacements() to the name.
static QFont createFont(const QString &family, int pointSize=-1, int weight=-1, bool italic=false)
Creates a font with the specified family.
static bool fontFamilyOnSystem(const QString &family)
Check whether font family is on system in a quick manner, which does not compare [foundry].
static bool updateFontViaStyle(QFont &f, const QString &fontstyle, bool fallback=false)
Updates font with named style and retain all font properties.
A geometry is the spatial representation of a feature.
QgsGeometry clipped(const QgsRectangle &rectangle)
Clips the geometry using the specified rectangle.
static QgsGeometry fromRect(const QgsRectangle &rect)
Creates a new geometry from a QgsRectangle.
double length() const
Returns the planar, 2-dimensional length of geometry.
QgsAbstractGeometry::const_part_iterator const_parts_begin() const
Returns STL-style const iterator pointing to the first part of the geometry.
static QgsGeometry collectGeometry(const QVector< QgsGeometry > &geometries)
Creates a new multipart geometry from a list of QgsGeometry objects.
QgsGeometry mergeLines(const QgsGeometryParameters &parameters=QgsGeometryParameters()) const
Merges any connected lines in a LineString/MultiLineString geometry and converts them to single line ...
QgsGeometry makeValid(Qgis::MakeValidMethod method=Qgis::MakeValidMethod::Linework, bool keepCollapsed=false) const
Attempts to make an invalid geometry valid without losing vertices.
QString lastError() const
Returns an error string referring to the last error encountered either when this geometry was created...
bool isAxisParallelRectangle(double maximumDeviation, bool simpleRectanglesOnly=false) const
Returns true if the geometry is a polygon that is almost an axis-parallel rectangle.
Qgis::GeometryOperationResult transform(const QgsCoordinateTransform &ct, Qgis::TransformDirection direction=Qgis::TransformDirection::Forward, bool transformZ=false)
Transforms this geometry as described by the coordinate transform ct.
QVector< QgsGeometry > asGeometryCollection() const
Returns contents of the geometry as a list of geometries.
QgsAbstractGeometry * get()
Returns a modifiable (non-const) reference to the underlying abstract geometry primitive.
const QgsAbstractGeometry * constGet() const
Returns a non-modifiable (const) reference to the underlying abstract geometry primitive.
bool contains(const QgsPointXY *p) const
Returns true if the geometry contains the point p.
QgsPointXY asPoint() const
Returns the contents of the geometry as a 2-dimensional point.
void filterVertices(const std::function< bool(const QgsPoint &) > &filter)
Filters the vertices from the geometry in place, removing any which do not return true for the filter...
bool isGeosValid(Qgis::GeometryValidityFlags flags=Qgis::GeometryValidityFlags()) const
Checks validity of the geometry using GEOS.
static QgsGeometry fromPointXY(const QgsPointXY &point)
Creates a new geometry from a QgsPointXY object.
Qgis::GeometryType type
double area() const
Returns the planar, 2-dimensional area of the geometry.
bool isMultipart() const
Returns true if WKB of the geometry is of WKBMulti* type.
QgsGeometry centroid() const
Returns the center of mass of a geometry.
QgsGeometry intersection(const QgsGeometry &geometry, const QgsGeometryParameters &parameters=QgsGeometryParameters()) const
Returns a geometry representing the points shared by this geometry and other.
bool isEmpty() const
Returns true if the geometry is empty (eg a linestring with no vertices, or a collection with no geom...
QgsAbstractGeometry::const_part_iterator const_parts_end() const
Returns STL-style iterator pointing to the imaginary part after the last part of the geometry.
bool convertGeometryCollectionToSubclass(Qgis::GeometryType geomType)
Converts geometry collection to a the desired geometry type subclass (multi-point,...
QgsGeometry simplify(double tolerance) const
Returns a simplified version of this geometry using a specified tolerance value.
QgsRectangle boundingBox() const
Returns the bounding box of the geometry.
Qgis::GeometryOperationResult rotate(double rotation, const QgsPointXY &center)
Rotate this geometry around the Z axis.
Q_INVOKABLE QString asWkt(int precision=17) const
Exports the geometry to WKT.
static geos::unique_ptr asGeos(const QgsGeometry &geometry, double precision=0, Qgis::GeosCreationFlags flags=Qgis::GeosCreationFlags())
Returns a geos geometry - caller takes ownership of the object (should be deleted with GEOSGeom_destr...
Definition qgsgeos.cpp:256
void setNoRepeatDistance(double distance)
Sets the minimum distance (in label units) between labels for this feature and other labels with the ...
void setLabelMarginDistance(double distance)
Sets the minimum distance (in label units) between labels for this feature and other labels.
Contains settings related to how the label engine places and formats labels for line features (or pol...
AnchorType
Line anchor types.
AnchorClipping
Clipping behavior for line anchor calculation.
@ UseEntireLine
Entire original feature line geometry is used when calculating the line anchor for labels.
@ UseVisiblePartsOfLine
Only visible parts of lines are considered when calculating the line anchor for labels.
void setDirectionSymbolPlacement(DirectionSymbolPlacement placement)
Sets the placement for direction symbols.
void setLeftDirectionSymbol(const QString &symbol)
Sets the string to use for left direction arrows.
DirectionSymbolPlacement
Placement options for direction symbols.
@ SymbolLeftRight
Place direction symbols on left/right of label.
@ SymbolAbove
Place direction symbols on above label.
@ SymbolBelow
Place direction symbols on below label.
void setRightDirectionSymbol(const QString &symbol)
Sets the string to use for right direction arrows.
@ CenterOfText
Anchor using center of text.
bool addDirectionSymbol() const
Returns true if '<' or '>' (or custom strings set via leftDirectionSymbol and rightDirectionSymbol) w...
void setReverseDirectionSymbol(bool reversed)
Sets whether the direction symbols should be reversed.
void setAddDirectionSymbol(bool enabled)
Sets whether '<' or '>' (or custom strings set via leftDirectionSymbol and rightDirectionSymbol) will...
ObstacleType
Valid obstacle types, which affect how features within the layer will act as obstacles for labels.
@ PolygonInterior
Avoid placing labels over interior of polygon (prefer placing labels totally outside or just slightly...
void setIsObstacle(bool isObstacle)
Sets whether features are obstacles to labels of other layers.
void updateDataDefinedProperties(const QgsPropertyCollection &properties, QgsExpressionContext &context)
Updates the obstacle settings to respect any data defined properties set within the specified propert...
void setObstacleGeometry(const QgsGeometry &obstacleGeom)
Sets the label's obstacle geometry, if different to the feature geometry.
Contains general settings related to how labels are placed.
Contains settings related to how the label engine places and formats labels for point-like features.
double minimumFeatureSize() const
Returns the minimum feature size (in millimeters) for a feature to be labelled.
const QgsMapUnitScale & minimumDistanceToDuplicateMapUnitScale() const
Returns the map unit scale for the minimum distance to labels with duplicate text.
int maximumNumberLabels() const
Returns the maximum number of labels which should be drawn for this layer.
bool allowDuplicateRemoval() const
Returns whether duplicate label removal is permitted for this layer.
const QgsMapUnitScale & labelMarginDistanceMapUnitScale() const
Returns the map unit scale for the minimum distance to other labels.
double labelMarginDistance() const
Returns the minimum distance to other labels (i.e.
Qgis::RenderUnit labelMarginDistanceUnit() const
Returns the units for the minimum distance to other labels.
void updateDataDefinedProperties(const QgsPropertyCollection &properties, QgsExpressionContext &context)
Updates the thinning settings to respect any data defined properties set within the specified propert...
double minimumDistanceToDuplicate() const
Returns the minimum distance to labels with duplicate text.
Qgis::RenderUnit minimumDistanceToDuplicateUnit() const
Returns the units for the minimum distance to labels with duplicate text.
bool limitNumberOfLabelsEnabled() const
Returns true if the number of labels drawn for the layer should be limited.
static constexpr double DEFAULT_MINIMUM_DISTANCE_TO_DUPLICATE
Default minimum distance to duplicate labels.
static QString encodePredefinedPositionOrder(const QVector< Qgis::LabelPredefinedPointPosition > &positions)
Encodes an ordered list of predefined point label positions to a string.
static QVector< Qgis::LabelPredefinedPointPosition > decodePredefinedPositionOrder(const QString &positionString)
Decodes a string to an ordered list of predefined point label positions.
Base class for all map layer types.
Definition qgsmaplayer.h:83
Q_INVOKABLE QVariant customProperty(const QString &value, const QVariant &defaultValue=QVariant()) const
Read a custom property from layer.
void removeCustomProperty(const QString &key)
Remove a custom property from layer.
Qgis::LayerType type
Definition qgsmaplayer.h:93
T customEnumProperty(const QString &key, const T &defaultValue)
Returns the property value for a property based on an enum.
Contains configuration for rendering maps.
const QgsMapToPixel & mapToPixel() const
double extentBuffer() const
Returns the buffer in map units to use around the visible extent for rendering symbols whose correspo...
QgsRectangle visibleExtent() const
Returns the actual extent derived from requested extent that takes output image size into account.
double rotation() const
Returns the rotation of the resulting map image, in degrees clockwise.
QgsCoordinateReferenceSystem destinationCrs() const
Returns the destination coordinate reference system for the map render.
QgsCoordinateTransformContext transformContext() const
Returns the coordinate transform context, which stores various information regarding which datum tran...
@ SimplifyEnvelope
The geometries can be fully simplified by its BoundingBox.
Perform transforms between map coordinates and device coordinates.
double mapUnitsPerPixel() const
Returns the current map units per pixel.
double mapRotation() const
Returns the current map rotation in degrees (clockwise).
void setParameters(double mapUnitsPerPixel, double centerX, double centerY, int widthPixels, int heightPixels, double rotation)
Sets parameters for use in transforming coordinates.
Represents a mesh layer supporting display of data on structured or unstructured meshes.
const QgsAbstractMeshLayerLabeling * labeling() const
Access to const labeling configuration.
bool labelsEnabled() const
Returns whether the layer contains labels which are enabled and should be drawn.
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::MessageLevel::Warning, bool notifyUser=true, const char *file=__builtin_FILE(), const char *function=__builtin_FUNCTION(), int line=__builtin_LINE())
Adds a message to the log instance (and creates it if necessary).
Multi surface geometry collection.
static QStringList splitToLines(const QString &text, const QString &wrapCharacter, int autoWrapLength=0, bool useMaxLineLengthWhenAutoWrapping=true)
Splits a text string to a list of separate lines, using a specified wrap character (wrapCharacter).
static QgsGeometry prepareGeometry(const QgsGeometry &geometry, QgsRenderContext &context, const QgsCoordinateTransform &ct, const QgsGeometry &clipGeometry=QgsGeometry(), bool mergeLines=false)
Prepares a geometry for registration with PAL.
static bool geometryRequiresPreparation(const QgsGeometry &geometry, QgsRenderContext &context, const QgsCoordinateTransform &ct, const QgsGeometry &clipGeometry=QgsGeometry(), bool mergeLines=false)
Checks whether a geometry requires preparation before registration with PAL.
static bool staticWillUseLayer(const QgsMapLayer *layer)
Called to find out whether a specified layer is used for labeling.
static QStringList splitToGraphemes(const QString &text)
Splits a text string to a list of graphemes, which are the smallest allowable character divisions in ...
Contains settings for how a map layer will be labeled.
bool fitInPolygonOnly
true if only labels which completely fit within a polygon are allowed.
double yOffset
Vertical offset of label.
QgsMapUnitScale labelOffsetMapUnitScale
Map unit scale for label offset.
int fontMaxPixelSize
Maximum pixel size for showing rendered map unit labels (1 - 10000).
const QgsLabelPlacementSettings & placementSettings() const
Returns the label placement settings.
double maxCurvedCharAngleIn
Maximum angle between inside curved label characters (valid range 20.0 to 60.0).
void setFormat(const QgsTextFormat &format)
Sets the label text formatting settings, e.g., font settings, buffer settings, etc.
double zIndex
Z-Index of label, where labels with a higher z-index are rendered on top of labels with a lower z-ind...
std::vector< std::unique_ptr< QgsLabelFeature > > registerFeatureWithDetails(const QgsFeature &feature, QgsRenderContext &context, QgsGeometry obstacleGeometry=QgsGeometry(), const QgsSymbol *symbol=nullptr)
Registers a feature for labeling.
const QgsMapToPixel * xform
void startRender(QgsRenderContext &context)
Prepares the label settings for rendering.
QString wrapChar
Wrapping character string.
QSet< QString > referencedFields(const QgsRenderContext &context) const
Returns all field names referenced by the configuration (e.g.
Qgis::LabelOffsetType offsetType
Offset type for layer (only applies in certain placement modes).
double xOffset
Horizontal offset of label.
Qgis::LabelPlacement placement
Label placement mode.
QgsCoordinateTransform ct
bool drawLabels
Whether to draw labels for this layer.
bool fontLimitPixelSize
true if label sizes should be limited by pixel size.
QString legendString() const
legendString
static QgsPalLayerSettings fromMimeData(const QMimeData *data, bool *ok=nullptr)
Attempts to parse the provided mime data as a QgsPalLayerSettings.
double minimumScale
The minimum map scale (i.e.
Q_DECL_DEPRECATED void calculateLabelSize(const QFontMetricsF *fm, const QString &text, double &labelX, double &labelY, const QgsFeature *f=nullptr, QgsRenderContext *context=nullptr, double *rotatedLabelX=nullptr, double *rotatedLabelY=nullptr)
Calculates the space required to render the provided text in map units.
void registerFeature(const QgsFeature &f, QgsRenderContext &context)
Registers a feature for labeling.
QgsPalLayerSettings & operator=(const QgsPalLayerSettings &s)
copy operator - only copies the permanent members
void readXml(const QDomElement &elem, const QgsReadWriteContext &context)
Read settings from a DOM element.
bool scaleVisibility
Set to true to limit label visibility to a range of scales.
double repeatDistance
Distance for repeating labels for a single feature.
QgsPropertyCollection & dataDefinedProperties()
Returns a reference to the label's property collection, used for data defined overrides.
QgsExpression * getLabelExpression() const
Returns the QgsExpression for this label settings.
bool geometryGeneratorEnabled
Defines if the geometry generator is enabled or not. If disabled, the standard geometry will be taken...
QMimeData * toMimeData() const
Returns new mime data representing the labeling settings.
Qgis::LabelMultiLineAlignment multilineAlign
Horizontal alignment of multi-line labels.
bool centroidInside
true if centroid positioned labels must be placed inside their corresponding feature polygon,...
int priority
Label priority.
Qgis::GeometryType geometryGeneratorType
The type of the result geometry of the geometry generator.
QDomElement writeXml(QDomDocument &doc, const QgsReadWriteContext &context) const
Write settings into a DOM element.
QgsCallout * callout() const
Returns the label callout renderer, responsible for drawing label callouts.
int fontMinPixelSize
Minimum pixel size for showing rendered map unit labels (1 - 1000).
double angleOffset
Label rotation, in degrees clockwise.
double maxCurvedCharAngleOut
Maximum angle between outside curved label characters (valid range -20.0 to -95.0).
Qgis::AngleUnit rotationUnit() const
Unit for rotation of labels.
Qgis::GeometryType layerType
Geometry type of layers associated with these settings.
Qgis::RenderUnit offsetUnits
Units for offsets of label.
bool isExpression
true if this label is made from a expression string, e.g., FieldName || 'mm'
bool preserveRotation
True if label rotation should be preserved during label pin/unpin operations.
bool plusSign
Whether '+' signs should be prepended to positive numeric labels.
bool prepare(QgsRenderContext &context, QSet< QString > &attributeNames, const QgsFields &fields, const QgsMapSettings &mapSettings, const QgsCoordinateReferenceSystem &crs)
Prepare for registration of features.
QString geometryGenerator
The geometry generator expression. Null if disabled.
const QgsLabelLineSettings & lineSettings() const
Returns the label line settings, which contain settings related to how the label engine places and fo...
QgsMapUnitScale distMapUnitScale
Map unit scale for label feature distance.
QgsStringReplacementCollection substitutions
Substitution collection for automatic text substitution with labels.
Q_DECL_DEPRECATED QColor previewBkgrdColor
const QgsFeature * mCurFeat
int decimals
Number of decimal places to show for numeric labels.
double dist
Distance from feature to the label.
void setRotationUnit(Qgis::AngleUnit angleUnit)
Set unit for rotation of labels.
QgsMapUnitScale repeatDistanceMapUnitScale
Map unit scale for repeating labels for a single feature.
Qgis::RenderUnit distUnits
Units the distance from feature to the label.
bool centroidWhole
true if feature centroid should be calculated from the whole feature, or false if only the visible pa...
Property
Data definable properties.
@ PositionX
X-coordinate data defined label position.
@ LinePlacementOptions
Line placement flags.
@ MinScale
Min scale (deprecated, for old project compatibility only).
@ WhitespaceCollisionHandling
Whitespace collision handling.
@ ShapeTransparency
Shape transparency (deprecated).
@ AllowDegradedPlacement
Allow degraded label placements.
@ MaskEnabled
Whether the mask is enabled.
@ OverlapHandling
Overlap handling technique.
@ PositionY
Y-coordinate data defined label position.
@ MaximumScale
Maximum map scale (ie most "zoomed in").
@ Vali
Vertical alignment for data defined label position (Bottom, Base, Half, Cap, Top).
@ MinimumScale
Minimum map scale (ie most "zoomed out").
@ FontStretchFactor
Font stretch factor, since QGIS 3.24.
@ PolygonLabelOutside
Whether labels outside a polygon feature are permitted, or should be forced.
@ LineAnchorType
Line anchor type.
@ MaxScale
Max scale (deprecated, for old project compatibility only).
@ BufferTransp
Buffer transparency (deprecated).
@ LineAnchorClipping
Clipping mode for line anchor calculation.
@ LabelAllParts
Multipart geometry behavior.
@ LabelMarginDistance
Minimum distance from labels for this feature to other labels.
@ LineAnchorPercent
Portion along line at which labels should be anchored.
@ Hali
Horizontal alignment for data defined label position (Left, Center, Right).
@ RemoveDuplicateLabelDistance
Minimum distance from labels for this feature to other labels with duplicate text.
@ LineAnchorTextPoint
Line anchor text point.
@ ShadowTransparency
Shadow transparency (deprecated).
@ OverrunDistance
Distance which labels can extend past either end of linear features.
@ MaskBufferUnit
Mask buffer size unit.
@ FontTransp
Text transparency (deprecated).
@ CurvedLabelMode
Mode which determine how curved labels are generated and placed.
@ MaskBufferSize
Mask buffer size.
@ TabStopDistance
Tab stop distance, since QGIS 3.38.
@ RemoveDuplicateLabels
Whether this feature can cause removal of duplicate labels.
@ PositionPoint
Point-coordinate data defined label position.
@ MaximumDistance
Maximum distance of label from feature.
@ Rotation
Label rotation (deprecated, for old project compatibility only).
Qgis::RenderUnit repeatDistanceUnit
Units for repeating labels for a single feature.
Qgis::UpsideDownLabelHandling upsidedownLabels
Controls whether upside down labels are displayed and how they are handled.
QString fieldName
Name of field (or an expression) to use for label text.
static QPixmap labelSettingsPreviewPixmap(const QgsPalLayerSettings &settings, QSize size, const QString &previewText=QString(), int padding=0, const QgsScreenProperties &screen=QgsScreenProperties())
Returns a pixmap preview for label settings.
bool formatNumbers
Set to true to format numeric label text as numbers (e.g.
bool containsAdvancedEffects() const
Returns true if any component of the label settings requires advanced effects such as blend modes,...
const QgsTextFormat & format() const
Returns the label text formatting settings, e.g., font settings, buffer settings, etc.
void setCallout(QgsCallout *callout)
Sets the label callout renderer, responsible for drawing label callouts.
double maximumScale
The maximum map scale (i.e.
int autoWrapLength
If non-zero, indicates that label text should be automatically wrapped to (ideally) the specified num...
Qgis::UnplacedLabelVisibility unplacedVisibility() const
Returns the layer's unplaced label visibility.
bool useMaxLineLengthForAutoWrap
If true, indicates that when auto wrapping label text the autoWrapLength length indicates the maximum...
void setUnplacedVisibility(Qgis::UnplacedLabelVisibility visibility)
Sets the layer's unplaced label visibility.
const QgsLabelPointSettings & pointSettings() const
Returns the label point settings, which contain settings related to how the label engine places and f...
static const QgsPropertiesDefinition & propertyDefinitions()
Returns the labeling property definitions.
void stopRender(QgsRenderContext &context)
Finalises the label settings after use.
bool useSubstitutions
True if substitutions should be applied.
Represents a 2D point.
Definition qgspointxy.h:62
Point geometry type, with support for z-dimension and m-values.
Definition qgspoint.h:53
double x
Definition qgspoint.h:56
double y
Definition qgspoint.h:57
QVariant value(int key, const QgsExpressionContext &context, const QVariant &defaultValue=QVariant()) const final
Returns the calculated value of the property with the specified key from within the collection.
bool isActive(int key) const final
Returns true if the collection contains an active property with the specified key.
@ Double
Double value (including negative values).
Definition qgsproperty.h:57
@ Double0To1
Double value between 0-1 (inclusive).
Definition qgsproperty.h:59
@ StrokeWidth
Line stroke width.
Definition qgsproperty.h:72
@ String
Any string value.
Definition qgsproperty.h:61
@ BlendMode
Blend mode.
Definition qgsproperty.h:67
@ Boolean
Boolean value.
Definition qgsproperty.h:53
@ RenderUnits
Render units (eg mm/pixels/map units).
Definition qgsproperty.h:63
@ PenJoinStyle
Pen join style.
Definition qgsproperty.h:66
@ SvgPath
Path to an SVG file.
Definition qgsproperty.h:77
@ IntegerPositiveGreaterZero
Non-zero positive integer values.
Definition qgsproperty.h:56
@ IntegerPositive
Positive integer values (including 0).
Definition qgsproperty.h:55
@ Opacity
Opacity (0-100).
Definition qgsproperty.h:62
@ ColorNoAlpha
Color with no alpha channel.
Definition qgsproperty.h:65
@ Rotation
Rotation (value between 0-360 degrees).
Definition qgsproperty.h:60
@ ColorWithAlpha
Color with alpha channel.
Definition qgsproperty.h:64
@ DoublePositive
Positive double value (including 0).
Definition qgsproperty.h:58
@ Size2D
2D size (width/height different)
Definition qgsproperty.h:70
@ DataTypeString
Property requires a string value.
Definition qgsproperty.h:92
@ DataTypeNumeric
Property requires a numeric value.
Definition qgsproperty.h:99
A store for object properties.
static QgsProperty fromExpression(const QString &expression, bool isActive=true)
Returns a new ExpressionBasedProperty created from the specified expression.
static QgsProperty fromField(const QString &fieldName, bool isActive=true)
Returns a new FieldBasedProperty created from the specified field name.
Represents a raster layer.
const QgsAbstractRasterLayerLabeling * labeling() const
Access to const labeling configuration.
bool labelsEnabled() const
Returns whether the layer contains labels which are enabled and should be drawn.
A container for the context for various read/write operations on objects.
A rectangle specified with double values.
Q_INVOKABLE QString toString(int precision=16) const
Returns a string representation of form xmin,ymin : xmax,ymax Coordinates will be truncated to the sp...
bool contains(const QgsRectangle &rect) const
Returns true when rectangle contains other rectangle.
void grow(double delta)
Grows the rectangle in place by the specified amount.
QgsPointXY center
QgsCoordinateReferenceSystem crs() const
Returns the associated coordinate reference system, or an invalid CRS if no reference system is set.
Contains information about the context of a rendering operation.
double scaleFactor() const
Returns the scaling factor for the render to convert painter units to physical sizes.
double convertToMapUnits(double size, Qgis::RenderUnit unit, const QgsMapUnitScale &scale=QgsMapUnitScale()) const
Converts a size from the specified units to map units.
void setScaleFactor(double factor)
Sets the scaling factor for the render to convert painter units to physical sizes.
QgsVectorSimplifyMethod & vectorSimplifyMethod()
Returns the simplification settings to use when rendering vector layers.
double symbologyReferenceScale() const
Returns the symbology reference scale.
void setDevicePixelRatio(float ratio)
Sets the device pixel ratio.
double convertToPainterUnits(double size, Qgis::RenderUnit unit, const QgsMapUnitScale &scale=QgsMapUnitScale(), Qgis::RenderSubcomponentProperty property=Qgis::RenderSubcomponentProperty::Generic) const
Converts a size from the specified units to painter units (pixels).
double rendererScale() const
Returns the renderer map scale.
QgsExpressionContext & expressionContext()
Gets the expression context.
QgsGeometry featureClipGeometry() const
Returns the geometry to use to clip features at render time.
QgsRectangle mapExtent() const
Returns the original extent of the map being rendered.
void setRasterizedRenderingPolicy(Qgis::RasterizedRenderingPolicy policy)
Sets the policy controlling when rasterisation of content during renders is permitted.
void setFlag(Qgis::RenderContextFlag flag, bool on=true)
Enable or disable a particular flag (other flags are not affected).
const QgsMapToPixel & mapToPixel() const
Returns the context's map to pixel transform, which transforms between map coordinates and device coo...
void setMapToPixel(const QgsMapToPixel &mtp)
Sets the context's map to pixel transform, which transforms between map coordinates and device coordi...
void setPainter(QPainter *p)
Sets the destination QPainter for the render operation.
QgsCoordinateTransform coordinateTransform() const
Returns the current coordinate transform for the context.
const QgsPathResolver & pathResolver() const
Returns the path resolver for conversion between relative and absolute paths during rendering operati...
static bool equalToOrGreaterThanMinimumScale(const double scale, const double minScale)
Returns whether the scale is equal to or greater than the minScale, taking non-round numbers into acc...
static bool lessThanMaximumScale(const double scale, const double maxScale)
Returns whether the scale is less than the maxScale, taking non-round numbers into account.
Stores properties relating to a screen.
double devicePixelRatio() const
Returns the ratio between physical pixels and device-independent pixels for the screen.
bool isValid() const
Returns true if the properties are valid.
void updateRenderContextForScreen(QgsRenderContext &context) const
Updates the settings in a render context to match the screen settings.
static QString capitalize(const QString &string, Qgis::Capitalization capitalization)
Converts a string by applying capitalization rules to the string.
static QString wordWrap(const QString &string, int length, bool useMaxLineLength=true, const QString &customDelimiter=QString())
Automatically wraps a string by inserting new line characters at appropriate locations in the string.
QgsTextFormat defaultTextFormat(QgsStyle::TextFormatContext context=QgsStyle::TextFormatContext::Labeling) const
Returns the default text format to use for new text based objects in the specified context.
static QgsStyle * defaultStyle(bool initialize=true)
Returns the default application-wide style.
Definition qgsstyle.cpp:150
@ Labeling
Text format used in labeling.
Definition qgsstyle.h:813
static Qt::PenJoinStyle decodePenJoinStyle(const QString &str)
static QString encodeMapUnitScale(const QgsMapUnitScale &mapUnitScale)
static QPointF toPoint(const QVariant &value, bool *ok=nullptr)
Converts a value to a point.
static QgsMapUnitScale decodeMapUnitScale(const QString &str)
static QSizeF toSize(const QVariant &value, bool *ok=nullptr)
Converts a value to a size.
static QPainter::CompositionMode decodeBlendMode(const QString &s)
static QString encodeSize(QSizeF size)
Encodes a QSizeF to a string.
static QString svgSymbolNameToPath(const QString &name, const QgsPathResolver &pathResolver)
Determines an SVG symbol's path from its name.
static QString encodePoint(QPointF point)
Encodes a QPointF to a string.
static QString encodePenJoinStyle(Qt::PenJoinStyle style)
Abstract base class for all rendered symbols.
Definition qgssymbol.h:231
void setRadiiUnit(Qgis::RenderUnit units)
Sets the units used for the shape's radii.
QString svgFile() const
Returns the absolute path to the background SVG file, if set.
QSizeF size() const
Returns the size of the background shape.
QSizeF radii() const
Returns the radii used for rounding the corners of shapes.
void setOpacity(double opacity)
Sets the background shape's opacity.
void setStrokeColor(const QColor &color)
Sets the color used for outlining the background shape.
Qt::PenJoinStyle joinStyle() const
Returns the join style used for drawing the background shape.
SizeType
Methods for determining the background shape size.
bool enabled() const
Returns whether the background is enabled.
void setJoinStyle(Qt::PenJoinStyle style)
Sets the join style used for drawing the background shape.
double opacity() const
Returns the background shape's opacity.
double rotation() const
Returns the rotation for the background shape, in degrees clockwise.
QColor fillColor() const
Returns the color used for filing the background shape.
void setRadii(QSizeF radii)
Sets the radii used for rounding the corners of shapes.
SizeType sizeType() const
Returns the method used to determine the size of the background shape (e.g., fixed size or buffer aro...
ShapeType type() const
Returns the type of background shape (e.g., square, ellipse, SVG).
double strokeWidth() const
Returns the width of the shape's stroke (stroke).
void setSizeType(SizeType type)
Sets the method used to determine the size of the background shape (e.g., fixed size or buffer around...
void setFillColor(const QColor &color)
Sets the color used for filing the background shape.
void setSizeUnit(Qgis::RenderUnit unit)
Sets the units used for the shape's size.
QColor strokeColor() const
Returns the color used for outlining the background shape.
void setRotationType(RotationType type)
Sets the method used for rotating the background shape.
void setBlendMode(QPainter::CompositionMode mode)
Sets the blending mode used for drawing the background shape.
QgsMapUnitScale sizeMapUnitScale() const
Returns the map unit scale object for the shape size.
void setType(ShapeType type)
Sets the type of background shape to draw (e.g., square, ellipse, SVG).
Qgis::RenderUnit sizeUnit() const
Returns the units used for the shape's size.
RotationType
Methods for determining the rotation of the background shape.
void setEnabled(bool enabled)
Sets whether the text background will be drawn.
QgsMarkerSymbol * markerSymbol() const
Returns the marker symbol to be rendered in the background.
void setRotation(double rotation)
Sets the rotation for the background shape, in degrees clockwise.
void setStrokeWidthUnit(Qgis::RenderUnit units)
Sets the units used for the shape's stroke width.
void setOffsetUnit(Qgis::RenderUnit units)
Sets the units used for the shape's offset.
void setOffset(QPointF offset)
Sets the offset used for drawing the background shape.
void setSize(QSizeF size)
Sets the size of the background shape.
void setSvgFile(const QString &file)
Sets the path to the background SVG file.
void setStrokeWidth(double width)
Sets the width of the shape's stroke (stroke).
QPointF offset() const
Returns the offset used for drawing the background shape.
void setBlendMode(QPainter::CompositionMode mode)
Sets the blending mode used for drawing the buffer.
Qgis::RenderUnit sizeUnit() const
Returns the units for the buffer size.
Qt::PenJoinStyle joinStyle() const
Returns the buffer join style.
double size() const
Returns the size of the buffer.
void setColor(const QColor &color)
Sets the color for the buffer.
void setOpacity(double opacity)
Sets the buffer opacity.
QgsMapUnitScale sizeMapUnitScale() const
Returns the map unit scale object for the buffer size.
bool enabled() const
Returns whether the buffer is enabled.
double opacity() const
Returns the buffer opacity.
void setSizeUnit(Qgis::RenderUnit unit)
Sets the units used for the buffer size.
void setEnabled(bool enabled)
Sets whether the text buffer will be drawn.
QColor color() const
Returns the color of the buffer.
void setJoinStyle(Qt::PenJoinStyle style)
Sets the join style used for drawing the buffer.
void setSize(double size)
Sets the size of the buffer.
Contains pre-calculated metrics of a QgsTextDocument.
QSizeF documentSize(Qgis::TextLayoutMode mode, Qgis::TextOrientation orientation) const
Returns the overall size of the document.
static QgsTextDocumentMetrics calculateMetrics(const QgsTextDocument &document, const QgsTextFormat &format, const QgsRenderContext &context, double scaleFactor=1.0, const QgsTextDocumentRenderContext &documentContext=QgsTextDocumentRenderContext())
Returns precalculated text metrics for a text document, when rendered using the given base format and...
QRectF outerBounds(Qgis::TextLayoutMode mode, Qgis::TextOrientation orientation) const
Returns the outer bounds of the document, which is the documentSize() adjusted to account for any tex...
Represents a document consisting of one or more QgsTextBlock objects.
void splitLines(const QString &wrapCharacter, int autoWrapLength=0, bool useMaxLineLengthWhenAutoWrapping=true)
Splits lines of text in the document to separate lines, using a specified wrap character (wrapCharact...
QVector< QgsTextDocument > splitBlocksToDocuments() const
Splits the text document, such that each block in the document becomes a separate document of its own...
static QgsTextDocument fromTextAndFormat(const QStringList &lines, const QgsTextFormat &format)
Constructor for QgsTextDocument consisting of a set of lines, respecting settings from a text format.
Container for all settings relating to text rendering.
void setColor(const QColor &color)
Sets the color that text will be rendered in.
void setBlendMode(QPainter::CompositionMode mode)
Sets the blending mode used for drawing the text.
void setSize(double size)
Sets the size for rendered text.
QgsMapUnitScale sizeMapUnitScale() const
Returns the map unit scale object for the size.
void setCapitalization(Qgis::Capitalization capitalization)
Sets the text capitalization style.
void setOrientation(Qgis::TextOrientation orientation)
Sets the orientation for the text.
void setFont(const QFont &font)
Sets the font used for rendering text.
void setSizeUnit(Qgis::RenderUnit unit)
Sets the units for the size of rendered text.
void setShadow(const QgsTextShadowSettings &shadowSettings)
Sets the text's drop shadow settings.
void setMask(const QgsTextMaskSettings &maskSettings)
Sets the text's masking settings.
void setOpacity(double opacity)
Sets the text's opacity.
Qgis::Capitalization capitalization() const
Returns the text capitalization style.
QgsTextMaskSettings & mask()
Returns a reference to the masking settings.
void setBuffer(const QgsTextBufferSettings &bufferSettings)
Sets the text's buffer settings.
QgsTextBackgroundSettings & background()
Returns a reference to the text background settings.
Qgis::RenderUnit sizeUnit() const
Returns the units for the size of rendered text.
void setTabStopDistance(double distance)
Sets the distance for tab stops.
double size() const
Returns the size for rendered text.
QgsTextShadowSettings & shadow()
Returns a reference to the text drop shadow settings.
void setTabPositions(const QList< QgsTextFormat::Tab > &positions)
Sets the list of tab positions for tab stops.
void setBackground(const QgsTextBackgroundSettings &backgroundSettings)
Sets the text's background settings.q.
QFont font() const
Returns the font used for rendering text.
QColor previewBackgroundColor() const
Returns the background color for text previews.
QgsTextBufferSettings & buffer()
Returns a reference to the text buffer settings.
void setLineHeight(double height)
Sets the line height for text.
static QgsPrecalculatedTextMetrics calculateTextMetrics(const QgsMapToPixel *xform, const QgsRenderContext &context, const QgsTextFormat &format, const QFont &baseFont, const QFontMetricsF &fontMetrics, double letterSpacing, double wordSpacing, const QgsTextDocument &document, const QgsTextDocumentMetrics &metrics)
Calculate text metrics for later retrieval via textMetrics().
void setEnabled(bool)
Returns whether the mask is enabled.
void setSize(double size)
Sets the size of the buffer.
double size() const
Returns the size of the buffer.
void setJoinStyle(Qt::PenJoinStyle style)
Sets the join style used for drawing the buffer.
double opacity() const
Returns the mask's opacity.
bool enabled() const
Returns whether the mask is enabled.
void setSizeUnit(Qgis::RenderUnit unit)
Sets the units used for the buffer size.
Qt::PenJoinStyle joinStyle() const
Returns the buffer join style.
void setOpacity(double opacity)
Sets the mask's opacity.
static QgsTextBackgroundSettings::ShapeType decodeShapeType(const QString &string)
Decodes a string representation of a background shape type to a type.
static Qgis::TextOrientation decodeTextOrientation(const QString &name, bool *ok=nullptr)
Attempts to decode a string representation of a text orientation.
static QgsTextShadowSettings::ShadowPlacement decodeShadowPlacementType(const QString &string)
Decodes a string representation of a shadow placement type to a type.
static QgsTextBackgroundSettings::RotationType decodeBackgroundRotationType(const QString &string)
Decodes a string representation of a background rotation type to a type.
static QString encodeTextOrientation(Qgis::TextOrientation orientation)
Encodes a text orientation.
static QgsTextBackgroundSettings::SizeType decodeBackgroundSizeType(const QString &string)
Decodes a string representation of a background size type to a type.
static double textWidth(const QgsRenderContext &context, const QgsTextFormat &format, const QStringList &textLines, QFontMetricsF *fontMetrics=nullptr)
Returns the width of a text based on a given format.
static int sizeToPixel(double size, const QgsRenderContext &c, Qgis::RenderUnit unit, const QgsMapUnitScale &mapUnitScale=QgsMapUnitScale())
Calculates pixel size (considering output size should be in pixel or map units, scale factors and opt...
static void drawText(const QRectF &rect, double rotation, Qgis::TextHorizontalAlignment alignment, const QStringList &textLines, QgsRenderContext &context, const QgsTextFormat &format, bool drawAsOutlines=true, Qgis::TextVerticalAlignment vAlignment=Qgis::TextVerticalAlignment::Top, Qgis::TextRendererFlags flags=Qgis::TextRendererFlags(), Qgis::TextLayoutMode mode=Qgis::TextLayoutMode::Rectangle)
Draws text within a rectangle using the specified settings.
static double textHeight(const QgsRenderContext &context, const QgsTextFormat &format, const QStringList &textLines, Qgis::TextLayoutMode mode=Qgis::TextLayoutMode::Point, QFontMetricsF *fontMetrics=nullptr, Qgis::TextRendererFlags flags=Qgis::TextRendererFlags(), double maxLineWidth=0)
Returns the height of a text based on a given format.
int offsetAngle() const
Returns the angle for offsetting the position of the shadow from the text.
void setBlurRadiusUnit(Qgis::RenderUnit units)
Sets the units used for the shadow's blur radius.
bool enabled() const
Returns whether the shadow is enabled.
void setOffsetUnit(Qgis::RenderUnit units)
Sets the units used for the shadow's offset.
int scale() const
Returns the scaling used for the drop shadow (in percentage of original size).
void setShadowPlacement(QgsTextShadowSettings::ShadowPlacement placement)
Sets the placement for the drop shadow.
double opacity() const
Returns the shadow's opacity.
void setBlendMode(QPainter::CompositionMode mode)
Sets the blending mode used for drawing the drop shadow.
void setColor(const QColor &color)
Sets the color for the drop shadow.
QColor color() const
Returns the color of the drop shadow.
ShadowPlacement
Placement positions for text shadow.
void setScale(int scale)
Sets the scaling used for the drop shadow (in percentage of original size).
double offsetDistance() const
Returns the distance for offsetting the position of the shadow from the text.
void setOffsetDistance(double distance)
Sets the distance for offsetting the position of the shadow from the text.
void setOpacity(double opacity)
Sets the shadow's opacity.
void setOffsetAngle(int angle)
Sets the angle for offsetting the position of the shadow from the text.
double blurRadius() const
Returns the blur radius for the shadow.
void setBlurRadius(double blurRadius)
Sets the blur radius for the shadow.
void setEnabled(bool enabled)
Sets whether the text shadow will be drawn.
static Q_INVOKABLE double fromUnitToUnitFactor(Qgis::DistanceUnit fromUnit, Qgis::DistanceUnit toUnit)
Returns the conversion factor between the specified distance units.
static Q_INVOKABLE Qgis::RenderUnit decodeRenderUnit(const QString &string, bool *ok=nullptr)
Decodes a render unit from a string.
static Q_INVOKABLE QString encodeUnit(Qgis::DistanceUnit unit)
Encodes a distance unit to a string.
static bool isNull(const QVariant &variant, bool silenceNullWarnings=false)
Returns true if the specified variant should be considered a NULL value.
Represents a vector layer which manages a vector based dataset.
bool labelsEnabled() const
Returns whether the layer contains labels which are enabled and should be drawn.
bool diagramsEnabled() const
Returns whether the layer contains diagrams which are enabled and should be drawn.
const QgsAbstractVectorLayerLabeling * labeling() const
Access to const labeling configuration.
QgsFeatureRenderer * renderer()
Returns the feature renderer used for rendering the features in the layer in 2D map views.
Q_INVOKABLE Qgis::GeometryType geometryType() const
Returns point, line or polygon.
const QgsDiagramRenderer * diagramRenderer() const
double tolerance() const
Gets the tolerance of simplification in map units. Represents the maximum distance in map units betwe...
bool forceLocalOptimization() const
Gets where the simplification executes, after fetch the geometries from provider, or when supported,...
Qgis::VectorRenderingSimplificationFlags simplifyHints() const
Gets the simplification hints of the vector layer managed.
Qgis::VectorSimplificationAlgorithm simplifyAlgorithm() const
Gets the local simplification algorithm of the vector layer managed.
Basic labeling configuration for vector tile layers.
Implements a map layer that is dedicated to rendering of vector tiles.
QgsVectorTileLabeling * labeling() const
Returns currently assigned labeling.
std::unique_ptr< GEOSGeometry, GeosDeleter > unique_ptr
Scoped GEOS pointer.
Definition qgsgeos.h:114
T qgsEnumKeyToValue(const QString &key, const T &defaultValue, bool tryValueAsKey=true, bool *returnOk=nullptr)
Returns the value corresponding to the given key of an enum.
Definition qgis.h:7145
#define Q_NOWARN_DEPRECATED_POP
Definition qgis.h:7486
QString qgsEnumValueToKey(const T &value, bool *returnOk=nullptr)
Returns the value for the given key of an enum.
Definition qgis.h:7126
#define Q_NOWARN_DEPRECATED_PUSH
Definition qgis.h:7485
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference).
Definition qgis.h:6935
T qgsgeometry_cast(QgsAbstractGeometry *geom)
Q_GLOBAL_STATIC(QReadWriteLock, sDefinitionCacheLock)
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:63
#define QgsDebugError(str)
Definition qgslogger.h:59
QString updateDataDefinedString(const QString &value)
QVector< Qgis::LabelPredefinedPointPosition > PredefinedPointPositionVector
Q_GLOBAL_STATIC_WITH_ARGS(PredefinedPointPositionVector, DEFAULT_PLACEMENT_ORDER,({ Qgis::LabelPredefinedPointPosition::TopRight, Qgis::LabelPredefinedPointPosition::TopLeft, Qgis::LabelPredefinedPointPosition::BottomRight, Qgis::LabelPredefinedPointPosition::BottomLeft, Qgis::LabelPredefinedPointPosition::MiddleRight, Qgis::LabelPredefinedPointPosition::MiddleLeft, Qgis::LabelPredefinedPointPosition::TopSlightlyRight, Qgis::LabelPredefinedPointPosition::BottomSlightlyRight })) void QgsPalLayerSettings
QMap< int, QgsPropertyDefinition > QgsPropertiesDefinition
Definition of available properties.