QGIS API Documentation 3.99.0-Master (d270888f95f)
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#if QT_VERSION >= QT_VERSION_CHECK(6, 3, 0)
2139 else if ( fcase.compare( "SmallCaps"_L1, Qt::CaseInsensitive ) == 0 )
2140 {
2142 }
2143 else if ( fcase.compare( "AllSmallCaps"_L1, Qt::CaseInsensitive ) == 0 )
2144 {
2146 }
2147#endif
2148 }
2149 }
2150 }
2151
2152 parseTextStyle( labelFont, fontunits, context );
2153 if ( mDataDefinedProperties.hasActiveProperties() )
2154 {
2155 parseTextFormatting( context );
2156 parseTextBuffer( context );
2157 parseTextMask( context );
2158 parseShapeBackground( context );
2159 parseDropShadow( context );
2160 }
2161
2162 if ( dataDefinedValues.contains( QgsPalLayerSettings::Property::TabStopDistance ) )
2163 {
2164 QList<QgsTextFormat::Tab> tabPositions;
2165 if ( dataDefinedValues.value( QgsPalLayerSettings::Property::TabStopDistance ).userType() == QMetaType::Type::QVariantList )
2166 {
2167 const QVariantList parts = dataDefinedValues.value( QgsPalLayerSettings::Property::TabStopDistance ).toList();
2168 for ( const QVariant &part : parts )
2169 {
2170 tabPositions.append( QgsTextFormat::Tab( part.toDouble() ) );
2171 }
2172 evaluatedFormat.setTabPositions( tabPositions );
2173 }
2174 else if ( dataDefinedValues.value( QgsPalLayerSettings::Property::TabStopDistance ).userType() == QMetaType::Type::QStringList )
2175 {
2176 const QStringList parts = dataDefinedValues.value( QgsPalLayerSettings::Property::TabStopDistance ).toStringList();
2177 for ( const QString &part : parts )
2178 {
2179 tabPositions.append( QgsTextFormat::Tab( part.toDouble() ) );
2180 }
2181 evaluatedFormat.setTabPositions( tabPositions );
2182 }
2183 else
2184 {
2185 evaluatedFormat.setTabPositions( tabPositions );
2186 evaluatedFormat.setTabStopDistance( dataDefinedValues.value( QgsPalLayerSettings::Property::TabStopDistance ).toDouble() );
2187 }
2188 }
2189
2190 evaluatedFormat.setFont( labelFont );
2191 // undo scaling by symbology reference scale, as this would have been applied in the previous call to QgsTextRenderer::sizeToPixel and we risk
2192 // double-applying it if we don't re-adjust, since all the text format metric calculations assume an unscaled format font size is present
2193 const double symbologyReferenceScaleFactor = context.symbologyReferenceScale() > 0 ? context.symbologyReferenceScale() / context.rendererScale() : 1;
2194 evaluatedFormat.setSize( labelFont.pixelSize() / symbologyReferenceScaleFactor );
2195 evaluatedFormat.setSizeUnit( Qgis::RenderUnit::Pixels );
2196
2197 return evaluatedFormat;
2198}
2199
2200bool QgsPalLayerSettings::evaluateLabelContent( const QgsFeature &feature, QgsRenderContext &context, bool allowMultipleLines, QString &labelText, QgsTextDocument &document, const QgsTextFormat &format ) const
2201{
2202 // Check to see if we are using an expression string.
2203 if ( isExpression )
2204 {
2205 QgsExpression *exp = getLabelExpression();
2206 if ( exp->hasParserError() )
2207 {
2208 QgsDebugMsgLevel( u"Expression parser error:%1"_s.arg( exp->parserErrorString() ), 4 );
2209 return false;
2210 }
2211
2212 QVariant result = exp->evaluate( &context.expressionContext() ); // expression prepared in QgsPalLabeling::prepareLayer()
2213 if ( exp->hasEvalError() )
2214 {
2215 QgsDebugMsgLevel( u"Expression parser eval error:%1"_s.arg( exp->evalErrorString() ), 4 );
2216 return false;
2217 }
2218 labelText = QgsVariantUtils::isNull( result ) ? QString() : result.toString();
2219 }
2220 else
2221 {
2222 const QVariant &v = feature.attribute( fieldIndex );
2223 labelText = QgsVariantUtils::isNull( v ) ? QString() : v.toString();
2224 }
2225
2226 // TODO -- this is in the wrong place. We should be substituting the text only, ie after we have parsed
2227 // any HTML tags to a text document.
2228
2229 // apply text replacements
2230 if ( useSubstitutions )
2231 {
2232 labelText = substitutions.process( labelText );
2233 }
2234
2235 // TODO -- this is in the wrong place. We should be capitalizing the text only, ie after we have parsed
2236 // any HTML tags to a text document.
2237 labelText = QgsStringUtils::capitalize( labelText, format.capitalization() );
2238
2239 // TODO -- this is in the wrong place. We should be formatting numbers AFTER converting HTML to documents
2240
2241 // format number if label text is coercible to a number
2242 bool evalFormatNumbers = formatNumbers;
2243 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::NumFormat ) )
2244 {
2245 evalFormatNumbers = mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::Property::NumFormat, context.expressionContext(), evalFormatNumbers );
2246 }
2247 if ( evalFormatNumbers )
2248 {
2249 // data defined decimal places?
2250 int decimalPlaces = mDataDefinedProperties.valueAsInt( QgsPalLayerSettings::Property::NumDecimals, context.expressionContext(), decimals );
2251 if ( decimalPlaces <= 0 ) // needs to be positive
2252 decimalPlaces = decimals;
2253
2254 // data defined plus sign?
2255 bool signPlus = mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::Property::NumPlusSign, context.expressionContext(), plusSign );
2256
2257 QVariant textV( labelText );
2258 bool ok;
2259 double d = textV.toDouble( &ok );
2260 if ( ok )
2261 {
2262 QString numberFormat;
2263 if ( d > 0 && signPlus )
2264 {
2265 numberFormat.append( '+' );
2266 }
2267 numberFormat.append( "%1" );
2268 labelText = numberFormat.arg( QLocale().toString( d, 'f', decimalPlaces ) );
2269 }
2270 }
2271
2272 document = QgsTextDocument::fromTextAndFormat( {labelText }, format );
2273
2274 QString wrapchr = wrapChar;
2275 int evalAutoWrapLength = autoWrapLength;
2276 if ( dataDefinedValues.contains( QgsPalLayerSettings::Property::MultiLineWrapChar ) )
2277 {
2278 wrapchr = dataDefinedValues.value( QgsPalLayerSettings::Property::MultiLineWrapChar ).toString();
2279 }
2280 if ( dataDefinedValues.contains( QgsPalLayerSettings::Property::AutoWrapLength ) )
2281 {
2282 evalAutoWrapLength = dataDefinedValues.value( QgsPalLayerSettings::Property::AutoWrapLength, evalAutoWrapLength ).toInt();
2283 }
2284
2285 if ( wrapchr.isEmpty() )
2286 {
2287 wrapchr = u"\n"_s; // default to new line delimiter
2288 }
2289
2290 if ( allowMultipleLines )
2291 {
2292 document.splitLines( wrapchr, evalAutoWrapLength, useMaxLineLengthForAutoWrap );
2293 }
2294
2295 return true;
2296}
2297
2298QgsGeometry QgsPalLayerSettings::evaluateLabelGeometry( const QgsFeature &feature, QgsRenderContext &context, const QgsLabelLineSettings &lineSettings ) const
2299{
2300 QgsGeometry geom = feature.geometry();
2301 if ( geom.isEmpty() )
2302 {
2303 return geom;
2304 }
2305
2306 // simplify?
2307 bool allowSimplify = true;
2308 switch ( placement )
2309 {
2312 {
2313 switch ( lineSettings.curvedLabelMode() )
2314 {
2316 // don't simplify -- this would remove vertices, making characters out of sync with remaining vertices
2317 allowSimplify = false;
2318 break;
2322 break;
2323 }
2324 break;
2325 }
2326
2334 break;
2335 }
2336
2337 const QgsVectorSimplifyMethod &simplifyMethod = context.vectorSimplifyMethod();
2338 std::unique_ptr<QgsGeometry> scopedClonedGeom;
2340 {
2341 unsigned int simplifyHints = simplifyMethod.simplifyHints() | QgsMapToPixelSimplifier::SimplifyEnvelope;
2342 const Qgis::VectorSimplificationAlgorithm simplifyAlgorithm = simplifyMethod.simplifyAlgorithm();
2343 QgsMapToPixelSimplifier simplifier( simplifyHints, simplifyMethod.tolerance(), simplifyAlgorithm );
2344 geom = simplifier.simplify( geom );
2345 }
2346
2347 return geom;
2348}
2349
2350std::vector<std::unique_ptr<QgsLabelFeature> > QgsPalLayerSettings::registerFeatureWithDetails( const QgsFeature &f, QgsRenderContext &context, QgsGeometry obstacleGeometry, const QgsSymbol *symbol )
2351{
2352 QVariant exprVal; // value() is repeatedly nulled on data defined evaluation and replaced when successful
2353 mCurFeat = &f;
2354
2355 // data defined is obstacle? calculate this first, to avoid wasting time working with obstacles we don't require
2356 bool isObstacle = mObstacleSettings.isObstacle();
2357 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::IsObstacle ) )
2358 isObstacle = mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::Property::IsObstacle, context.expressionContext(), isObstacle ); // default to layer default
2359
2360 std::vector<std::unique_ptr<QgsLabelFeature> > res;
2361
2362 // possibly an obstacle-only feature
2363 if ( !drawLabels )
2364 {
2365 if ( isObstacle )
2366 {
2367 std::unique_ptr< QgsLabelFeature > obstacle = registerObstacleFeature( f, context, obstacleGeometry );
2368 if ( obstacle )
2369 res.emplace_back( std::move( obstacle ) );
2370 return res;
2371 }
2372 else
2373 {
2374 return {};
2375 }
2376 }
2377
2378 if ( !isLabelVisible( context ) )
2379 return {};
2380
2381 QgsFeature feature = f;
2383 {
2384 const QgsGeometry geometry = mGeometryGeneratorExpression.evaluate( &context.expressionContext() ).value<QgsGeometry>();
2385 if ( mGeometryGeneratorExpression.hasEvalError() )
2386 QgsMessageLog::logMessage( mGeometryGeneratorExpression.evalErrorString(), QObject::tr( "Labeling" ) );
2387
2388 if ( obstacleGeometry.isNull() )
2389 {
2390 // if an explicit obstacle geometry hasn't been set, we must always use the original feature geometry
2391 // as the obstacle -- because we want to use the geometry which was used to render the symbology
2392 // for the feature as the obstacle for other layers' labels, NOT the generated geometry which is used
2393 // only to place labels for this layer.
2394 obstacleGeometry = f.geometry();
2395 }
2396
2397 feature.setGeometry( geometry );
2398 }
2399
2400 // store data defined-derived values for later adding to label feature for use during rendering
2401 dataDefinedValues.clear();
2402
2403 // evaluate text format and font properties
2404 bool labelIsHidden = false;
2405 QgsTextFormat evaluatedFormat = evaluateTextFormat( context, labelIsHidden );
2406 if ( labelIsHidden )
2407 return {};
2408
2409 QgsLabelPlacementSettings placementSettings = mPlacementSettings;
2410 placementSettings.updateDataDefinedProperties( mDataDefinedProperties, context.expressionContext() );
2411
2412 QString labelText;
2413 QgsTextDocument doc;
2414 bool allowMultipleLines = true;
2415 switch ( placement )
2416 {
2419 {
2420 switch ( placementSettings.multiPartBehavior() )
2421 {
2424 // for these curved placements we only support a single line of text
2425 allowMultipleLines = false;
2426 break;
2427
2429 // this mode permits multiple lines, as we'll be splitting them over the geometry parts
2430 allowMultipleLines = true;
2431 break;
2432 }
2433 break;
2434 }
2435
2443 {
2444 break;
2445 }
2446 }
2447 if ( !evaluateLabelContent( feature, context, allowMultipleLines, labelText, doc, evaluatedFormat ) )
2448 return {};
2449
2450 // geometry
2451 QgsLabelLineSettings lineSettings = mLineSettings;
2452 lineSettings.updateDataDefinedProperties( mDataDefinedProperties, context.expressionContext() );
2453
2454 QgsLabelPointSettings pointSettings = mPointSettings;
2455 pointSettings.updateDataDefinedProperties( mDataDefinedProperties, context.expressionContext() );
2456
2457 QgsGeometry geom = evaluateLabelGeometry( feature, context, lineSettings );
2458 if ( geom.isEmpty() )
2459 return {};
2460
2461
2462 // data defined centroid whole or clipped?
2463 bool wholeCentroid = centroidWhole;
2464 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::CentroidWhole ) )
2465 {
2466 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Property::CentroidWhole, context.expressionContext() );
2467 if ( !QgsVariantUtils::isNull( exprVal ) )
2468 {
2469 QString str = exprVal.toString().trimmed();
2470 QgsDebugMsgLevel( u"exprVal CentroidWhole:%1"_s.arg( str ), 4 );
2471
2472 if ( !str.isEmpty() )
2473 {
2474 if ( str.compare( "Visible"_L1, Qt::CaseInsensitive ) == 0 )
2475 {
2476 wholeCentroid = false;
2477 }
2478 else if ( str.compare( "Whole"_L1, Qt::CaseInsensitive ) == 0 )
2479 {
2480 wholeCentroid = true;
2481 }
2482 }
2483 }
2484 }
2485
2486 // whether we're going to create a centroid for polygon
2487 bool centroidPoly = ( ( placement == Qgis::LabelPlacement::AroundPoint
2489 && geom.type() == Qgis::GeometryType::Polygon );
2490
2491 // CLIP the geometry if it is bigger than the extent
2492 // don't clip if centroid is requested for whole feature
2493 bool doClip = false;
2494 if ( !centroidPoly || !wholeCentroid )
2495 {
2496 doClip = true;
2497 }
2498
2500 {
2501 if ( !obstacleGeometry.isNull() && QgsPalLabeling::geometryRequiresPreparation( obstacleGeometry, context, ct, doClip ? extentGeom : QgsGeometry(), lineSettings.mergeLines() ) )
2502 {
2503 obstacleGeometry = QgsGeometry( QgsPalLabeling::prepareGeometry( obstacleGeometry, context, ct, doClip ? extentGeom : QgsGeometry(), lineSettings.mergeLines() ) );
2504 }
2505 }
2506
2507 switch ( placementSettings.multiPartBehavior() )
2508 {
2511 {
2512 std::unique_ptr< QgsTextLabelFeature > label = generateLabelFeature( context, feature, 0, geom, obstacleGeometry, doc, labelText, evaluatedFormat, symbol, lineSettings, pointSettings, placementSettings, isObstacle, doClip );
2513 if ( label )
2514 res.emplace_back( std::move( label ) );
2515 break;
2516 }
2517
2519 {
2520 const QVector< QgsGeometry > geometryParts = geom.asGeometryCollection();
2521 const QVector< QgsTextDocument > documentParts = doc.splitBlocksToDocuments();
2522 const std::size_t partCount = std::min( geometryParts.size(), documentParts.size() );
2523 res.reserve( partCount );
2524 for ( std::size_t i = 0; i < partCount; ++i )
2525 {
2526 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 );
2527 if ( label )
2528 res.emplace_back( std::move( label ) );
2529 }
2530
2531 break;
2532 }
2533 }
2534
2535 return res;
2536}
2537
2538std::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
2539{
2540 QVariant exprVal;
2541 QSizeF labelSize;
2542 QSizeF rotatedSize;
2543
2544 QgsTextDocumentMetrics documentMetrics;
2545 QRectF outerBounds;
2546 QFontMetricsF labelFontMetrics( evaluatedFormat.font() );
2547 calculateLabelMetrics( labelFontMetrics, context, evaluatedFormat, doc, documentMetrics, labelSize, rotatedSize, outerBounds );
2548
2549 // maximum angle between curved label characters (hardcoded defaults used in QGIS <2.0)
2550 //
2551 double maxcharanglein = 20.0; // range 20.0-60.0
2552 double maxcharangleout = -20.0; // range 20.0-95.0
2553
2554 switch ( placement )
2555 {
2558 {
2559 maxcharanglein = maxCurvedCharAngleIn;
2560 maxcharangleout = maxCurvedCharAngleOut;
2561
2562 //data defined maximum angle between curved label characters?
2563 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::CurvedCharAngleInOut ) )
2564 {
2565 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Property::CurvedCharAngleInOut, context.expressionContext() );
2566 bool ok = false;
2567 const QPointF maxcharanglePt = QgsSymbolLayerUtils::toPoint( exprVal, &ok );
2568 if ( ok )
2569 {
2570 maxcharanglein = std::clamp( static_cast< double >( maxcharanglePt.x() ), 20.0, 60.0 );
2571 maxcharangleout = std::clamp( static_cast< double >( maxcharanglePt.y() ), 20.0, 95.0 );
2572 }
2573 }
2574 // make sure maxcharangleout is always negative
2575 maxcharangleout = -( std::fabs( maxcharangleout ) );
2576 break;
2577 }
2578
2586 break;
2587 }
2588
2589 if ( !context.featureClipGeometry().isEmpty() )
2590 {
2591 const Qgis::GeometryType expectedType = geom.type();
2592 geom = geom.intersection( context.featureClipGeometry() );
2593 geom.convertGeometryCollectionToSubclass( expectedType );
2594 }
2595
2596 Qgis::LabelPolygonPlacementFlags polygonPlacement = mPolygonPlacementFlags;
2597 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::PolygonLabelOutside ) )
2598 {
2599 const QVariant dataDefinedOutside = mDataDefinedProperties.value( QgsPalLayerSettings::Property::PolygonLabelOutside, context.expressionContext() );
2600 if ( !QgsVariantUtils::isNull( dataDefinedOutside ) )
2601 {
2602 if ( dataDefinedOutside.userType() == QMetaType::Type::QString )
2603 {
2604 const QString value = dataDefinedOutside.toString().trimmed();
2605 if ( value.compare( "force"_L1, Qt::CaseInsensitive ) == 0 )
2606 {
2607 // forced outside placement -- remove inside flag, add outside flag
2608 polygonPlacement &= ~static_cast< int >( Qgis::LabelPolygonPlacementFlag::AllowPlacementInsideOfPolygon );
2610 }
2611 else if ( value.compare( "yes"_L1, Qt::CaseInsensitive ) == 0 )
2612 {
2613 // permit outside placement
2615 }
2616 else if ( value.compare( "no"_L1, Qt::CaseInsensitive ) == 0 )
2617 {
2618 // block outside placement
2619 polygonPlacement &= ~static_cast< int >( Qgis::LabelPolygonPlacementFlag::AllowPlacementOutsideOfPolygon );
2620 }
2621 }
2622 else
2623 {
2624 if ( dataDefinedOutside.toBool() )
2625 {
2626 // permit outside placement
2628 }
2629 else
2630 {
2631 // block outside placement
2632 polygonPlacement &= ~static_cast< int >( Qgis::LabelPolygonPlacementFlag::AllowPlacementOutsideOfPolygon );
2633 }
2634 }
2635 }
2636 }
2637
2639 {
2640 switch ( lineSettings.anchorClipping() )
2641 {
2643 break;
2644
2646 doClip = false;
2647 break;
2648 }
2649
2651 {
2652 switch ( lineSettings.curvedLabelMode() )
2653 {
2655 // don't clip geometries when in this mode, or vertices will get out-of-sync with their corresponding
2656 // characters!
2657 doClip = false;
2658 break;
2662 break;
2663 }
2664 }
2665 }
2666
2667 // if using fitInPolygonOnly option, generate the permissible zone (must happen before geometry is modified - e.g.,
2668 // as a result of using perimeter based labeling and the geometry is converted to a boundary)
2669 // note that we also force this if we are permitting labels to be placed outside of polygons too!
2670 QgsGeometry permissibleZone;
2672 {
2673 permissibleZone = geom;
2674 if ( QgsPalLabeling::geometryRequiresPreparation( permissibleZone, context, ct, doClip ? extentGeom : QgsGeometry(), lineSettings.mergeLines() ) )
2675 {
2676 permissibleZone = QgsPalLabeling::prepareGeometry( permissibleZone, context, ct, doClip ? extentGeom : QgsGeometry(), lineSettings.mergeLines() );
2677 }
2678 }
2679
2680 // if using perimeter based labeling for polygons, get the polygon's
2681 // linear boundary and use that for the label geometry
2682 if ( ( geom.type() == Qgis::GeometryType::Polygon )
2684 {
2685 geom = QgsGeometry( geom.constGet()->boundary() );
2686 }
2687
2688 geos::unique_ptr geos_geom_clone;
2689 if ( QgsPalLabeling::geometryRequiresPreparation( geom, context, ct, doClip ? extentGeom : QgsGeometry(), lineSettings.mergeLines() ) )
2690 {
2691 geom = QgsPalLabeling::prepareGeometry( geom, context, ct, doClip ? extentGeom : QgsGeometry(), lineSettings.mergeLines() );
2692
2693 if ( geom.isEmpty() )
2694 return {};
2695 }
2697
2698 QgsLabelThinningSettings featureThinningSettings = mThinningSettings;
2699 featureThinningSettings.updateDataDefinedProperties( mDataDefinedProperties, context.expressionContext() );
2700
2701 double minimumSize = 0.0;
2702 if ( featureThinningSettings.minimumFeatureSize() > 0 )
2703 {
2704 // for minimum feature size on merged lines, we need to delay the filtering after the merging occurred in PAL
2705 if ( geom.type() == Qgis::GeometryType::Line && lineSettings.mergeLines() )
2706 {
2707 minimumSize = context.convertToMapUnits( featureThinningSettings.minimumFeatureSize(), Qgis::RenderUnit::Millimeters );
2708 }
2709 else
2710 {
2711 if ( !checkMinimumSizeMM( context, geom, featureThinningSettings.minimumFeatureSize() ) )
2712 return {};
2713 }
2714 }
2715
2716 if ( !geos_geom_clone )
2717 return {}; // invalid geometry
2718
2719 // likelihood exists label will be registered with PAL and may be drawn
2720 // check if max number of features to label (already registered with PAL) has been reached
2721 // Debug output at end of QgsPalLabeling::drawLabeling(), when deleting temp geometries
2722 if ( featureThinningSettings.limitNumberOfLabelsEnabled() )
2723 {
2724 if ( !featureThinningSettings.maximumNumberLabels() )
2725 {
2726 return {};
2727 }
2728 if ( mFeatsRegPal >= featureThinningSettings.maximumNumberLabels() )
2729 {
2730 return {};
2731 }
2732
2733 int divNum = static_cast< int >( ( static_cast< double >( mFeaturesToLabel ) / featureThinningSettings.maximumNumberLabels() ) + 0.5 ); // NOLINT
2734 if ( divNum && ( mFeatsRegPal == static_cast< int >( mFeatsSendingToPal / divNum ) ) )
2735 {
2736 mFeatsSendingToPal += 1;
2737 if ( divNum && mFeatsSendingToPal % divNum )
2738 {
2739 return {};
2740 }
2741 }
2742 }
2743
2744 //data defined position / alignment / rotation?
2745 bool layerDefinedRotation = false;
2746 bool dataDefinedRotation = false;
2747 double xPos = 0.0, yPos = 0.0;
2748 double angleInRadians = 0.0;
2749 double quadOffsetX = 0.0, quadOffsetY = 0.0;
2750 double offsetX = 0.0, offsetY = 0.0;
2751 QgsPointXY anchorPosition;
2752
2754 {
2755 anchorPosition = geom.centroid().asPoint();
2756 }
2757 //x/y shift in case of alignment
2758 double xdiff = 0.0;
2759 double ydiff = 0.0;
2760
2761 //data defined quadrant offset?
2762 bool ddFixedQuad = false;
2763 Qgis::LabelQuadrantPosition quadOff = pointSettings.quadrant();
2764 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::OffsetQuad ) )
2765 {
2766 context.expressionContext().setOriginalValueVariable( static_cast< int >( quadOff ) );
2767 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Property::OffsetQuad, context.expressionContext() );
2768 if ( !QgsVariantUtils::isNull( exprVal ) )
2769 {
2770 bool ok;
2771 int quadInt = exprVal.toInt( &ok );
2772 if ( ok && 0 <= quadInt && quadInt <= 8 )
2773 {
2774 quadOff = static_cast< Qgis::LabelQuadrantPosition >( quadInt );
2775 ddFixedQuad = true;
2776 }
2777 }
2778 }
2779
2780 // adjust quadrant offset of labels
2781 switch ( quadOff )
2782 {
2784 quadOffsetX = -1.0;
2785 quadOffsetY = 1.0;
2786 break;
2788 quadOffsetX = 0.0;
2789 quadOffsetY = 1.0;
2790 break;
2792 quadOffsetX = 1.0;
2793 quadOffsetY = 1.0;
2794 break;
2796 quadOffsetX = -1.0;
2797 quadOffsetY = 0.0;
2798 break;
2800 quadOffsetX = 1.0;
2801 quadOffsetY = 0.0;
2802 break;
2804 quadOffsetX = -1.0;
2805 quadOffsetY = -1.0;
2806 break;
2808 quadOffsetX = 0.0;
2809 quadOffsetY = -1.0;
2810 break;
2812 quadOffsetX = 1.0;
2813 quadOffsetY = -1.0;
2814 break;
2816 break;
2817 }
2818
2819 //data defined label offset?
2820 double xOff = xOffset;
2821 double yOff = yOffset;
2822 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::OffsetXY ) )
2823 {
2825 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Property::OffsetXY, context.expressionContext() );
2826 bool ok = false;
2827 const QPointF ddOffPt = QgsSymbolLayerUtils::toPoint( exprVal, &ok );
2828 if ( ok )
2829 {
2830 xOff = ddOffPt.x();
2831 yOff = ddOffPt.y();
2832 }
2833 }
2834
2835 // data defined label offset units?
2836 Qgis::RenderUnit offUnit = offsetUnits;
2837 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::OffsetUnits ) )
2838 {
2839 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Property::OffsetUnits, context.expressionContext() );
2840 if ( !QgsVariantUtils::isNull( exprVal ) )
2841 {
2842 QString units = exprVal.toString().trimmed();
2843 if ( !units.isEmpty() )
2844 {
2845 bool ok = false;
2846 Qgis::RenderUnit decodedUnits = QgsUnitTypes::decodeRenderUnit( units, &ok );
2847 if ( ok )
2848 {
2849 offUnit = decodedUnits;
2850 }
2851 }
2852 }
2853 }
2854
2855 // adjust offset of labels to match chosen unit and map scale
2856 // offsets match those of symbology: -x = left, -y = up
2857 offsetX = context.convertToMapUnits( xOff, offUnit, labelOffsetMapUnitScale );
2858 // must be negative to match symbology offset direction
2859 offsetY = context.convertToMapUnits( -yOff, offUnit, labelOffsetMapUnitScale );
2860
2861 // layer defined rotation?
2862 if ( !qgsDoubleNear( angleOffset, 0.0 ) )
2863 {
2864 layerDefinedRotation = true;
2865 angleInRadians = ( 360 - angleOffset ) * M_PI / 180; // convert to radians counterclockwise
2866 }
2867
2868 const QgsMapToPixel &m2p = context.mapToPixel();
2869 //data defined rotation?
2870 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::LabelRotation ) )
2871 {
2873 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Property::LabelRotation, context.expressionContext() );
2874 if ( !QgsVariantUtils::isNull( exprVal ) )
2875 {
2876 bool ok;
2877 const double rotation = exprVal.toDouble( &ok );
2878 if ( ok )
2879 {
2880 dataDefinedRotation = true;
2881
2882 double rotationDegrees = rotation * QgsUnitTypes::fromUnitToUnitFactor( mRotationUnit,
2884
2885 // TODO: add setting to disable having data defined rotation follow
2886 // map rotation ?
2887 rotationDegrees += m2p.mapRotation();
2888 angleInRadians = ( 360 - rotationDegrees ) * M_PI / 180.0;
2889 }
2890 }
2891 }
2892
2893 bool hasDataDefinedPosition = false;
2894 {
2895 bool ddPosition = false;
2896
2897 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::PositionX )
2898 && mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::PositionY ) )
2899 {
2900 const QVariant xPosProperty = mDataDefinedProperties.value( QgsPalLayerSettings::Property::PositionX, context.expressionContext() );
2901 const QVariant yPosProperty = mDataDefinedProperties.value( QgsPalLayerSettings::Property::PositionY, context.expressionContext() );
2902 if ( !QgsVariantUtils::isNull( xPosProperty )
2903 && !QgsVariantUtils::isNull( yPosProperty ) )
2904 {
2905 ddPosition = true;
2906
2907 bool ddXPos = false, ddYPos = false;
2908 xPos = xPosProperty.toDouble( &ddXPos );
2909 yPos = yPosProperty.toDouble( &ddYPos );
2910 if ( ddXPos && ddYPos )
2911 hasDataDefinedPosition = true;
2912 }
2913 }
2914 else if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::PositionPoint ) )
2915 {
2916 const QVariant pointPosProperty = mDataDefinedProperties.value( QgsPalLayerSettings::Property::PositionPoint, context.expressionContext() );
2917 if ( !QgsVariantUtils::isNull( pointPosProperty ) )
2918 {
2919 ddPosition = true;
2920
2921 QgsPoint point;
2922 if ( pointPosProperty.userType() == qMetaTypeId<QgsReferencedGeometry>() )
2923 {
2924 QgsReferencedGeometry referencedGeometryPoint = pointPosProperty.value<QgsReferencedGeometry>();
2925 point = QgsPoint( referencedGeometryPoint.asPoint() );
2926
2927 if ( !referencedGeometryPoint.isNull()
2928 && ct.sourceCrs() != referencedGeometryPoint.crs() )
2929 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 );
2930 }
2931 else if ( pointPosProperty.userType() == qMetaTypeId< QgsGeometry>() )
2932 {
2933 point = QgsPoint( pointPosProperty.value<QgsGeometry>().asPoint() );
2934 }
2935
2936 if ( !point.isEmpty() )
2937 {
2938 hasDataDefinedPosition = true;
2939
2940 xPos = point.x();
2941 yPos = point.y();
2942 }
2943 }
2944 }
2945
2946 if ( ddPosition )
2947 {
2948 //data defined position. But field values could be NULL -> positions will be generated by PAL
2949 if ( hasDataDefinedPosition )
2950 {
2951 // layer rotation set, but don't rotate pinned labels unless data defined
2952 if ( layerDefinedRotation && !dataDefinedRotation )
2953 {
2954 angleInRadians = 0.0;
2955 }
2956
2957 //horizontal alignment
2958 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::Hali ) )
2959 {
2960 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Property::Hali, context.expressionContext() );
2961 if ( !QgsVariantUtils::isNull( exprVal ) )
2962 {
2963 QString haliString = exprVal.toString();
2964 if ( haliString.compare( "Center"_L1, Qt::CaseInsensitive ) == 0 )
2965 {
2966 xdiff -= labelSize.width() / 2.0;
2967 }
2968 else if ( haliString.compare( "Right"_L1, Qt::CaseInsensitive ) == 0 )
2969 {
2970 xdiff -= labelSize.width();
2971 }
2972 }
2973 }
2974
2975 //vertical alignment
2976 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::Vali ) )
2977 {
2978 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Property::Vali, context.expressionContext() );
2979 if ( !QgsVariantUtils::isNull( exprVal ) )
2980 {
2981 QString valiString = exprVal.toString();
2982 if ( valiString.compare( "Bottom"_L1, Qt::CaseInsensitive ) != 0 )
2983 {
2984 if ( valiString.compare( "Top"_L1, Qt::CaseInsensitive ) == 0 )
2985 {
2986 ydiff -= labelSize.height();
2987 }
2988 else
2989 {
2990 double descentRatio = labelFontMetrics.descent() / labelFontMetrics.height();
2991 if ( valiString.compare( "Base"_L1, Qt::CaseInsensitive ) == 0 )
2992 {
2993 ydiff -= labelSize.height() * descentRatio;
2994 }
2995 else //'Cap' or 'Half'
2996 {
2997 double capHeightRatio = ( labelFontMetrics.boundingRect( 'H' ).height() + 1 + labelFontMetrics.descent() ) / labelFontMetrics.height();
2998 ydiff -= labelSize.height() * capHeightRatio;
2999 if ( valiString.compare( "Half"_L1, Qt::CaseInsensitive ) == 0 )
3000 {
3001 ydiff += labelSize.height() * ( capHeightRatio - descentRatio ) / 2.0;
3002 }
3003 }
3004 }
3005 }
3006 }
3007 }
3008
3009 if ( dataDefinedRotation )
3010 {
3011 //adjust xdiff and ydiff because the hali/vali point needs to be the rotation center
3012 double xd = xdiff * std::cos( angleInRadians ) - ydiff * std::sin( angleInRadians );
3013 double yd = xdiff * std::sin( angleInRadians ) + ydiff * std::cos( angleInRadians );
3014 xdiff = xd;
3015 ydiff = yd;
3016 }
3017
3018 //project xPos and yPos from layer to map CRS, handle rotation
3019 QgsGeometry ddPoint( new QgsPoint( xPos, yPos ) );
3020 if ( QgsPalLabeling::geometryRequiresPreparation( ddPoint, context, ct ) )
3021 {
3022 ddPoint = QgsPalLabeling::prepareGeometry( ddPoint, context, ct );
3023 if ( const QgsPoint *point = qgsgeometry_cast< const QgsPoint * >( ddPoint.constGet() ) )
3024 {
3025 xPos = point->x();
3026 yPos = point->y();
3027 anchorPosition = QgsPointXY( xPos, yPos );
3028 }
3029 else
3030 {
3031 QgsMessageLog::logMessage( QObject::tr( "Invalid data defined label position (%1, %2)" ).arg( xPos ).arg( yPos ), QObject::tr( "Labeling" ) );
3032 hasDataDefinedPosition = false;
3033 }
3034 }
3035 else
3036 {
3037 anchorPosition = QgsPointXY( xPos, yPos );
3038 }
3039
3040 xPos += xdiff;
3041 yPos += ydiff;
3042 }
3043 else
3044 {
3045 anchorPosition = QgsPointXY( xPos, yPos );
3046 }
3047 }
3048 }
3049
3050 // data defined always show?
3051 bool alwaysShow = false;
3052 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::AlwaysShow ) )
3053 {
3054 alwaysShow = mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::Property::AlwaysShow, context.expressionContext(), false );
3055 }
3056
3057 // set repeat distance
3058 // data defined repeat distance?
3059 double repeatDist = repeatDistance;
3060 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::RepeatDistance ) )
3061 {
3062 context.expressionContext().setOriginalValueVariable( repeatDist );
3063 repeatDist = mDataDefinedProperties.valueAsDouble( QgsPalLayerSettings::Property::RepeatDistance, context.expressionContext(), repeatDist );
3064 }
3065
3066 // data defined label-repeat distance units?
3068 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::RepeatDistanceUnit ) )
3069 {
3070 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Property::RepeatDistanceUnit, context.expressionContext() );
3071 if ( !QgsVariantUtils::isNull( exprVal ) )
3072 {
3073 QString units = exprVal.toString().trimmed();
3074 if ( !units.isEmpty() )
3075 {
3076 bool ok = false;
3077 Qgis::RenderUnit decodedUnits = QgsUnitTypes::decodeRenderUnit( units, &ok );
3078 if ( ok )
3079 {
3080 repeatUnits = decodedUnits;
3081 }
3082 }
3083 }
3084 }
3085
3086 if ( !qgsDoubleNear( repeatDist, 0.0 ) )
3087 {
3088 if ( repeatUnits != Qgis::RenderUnit::MapUnits )
3089 {
3090 repeatDist = context.convertToMapUnits( repeatDist, repeatUnits, repeatDistanceMapUnitScale );
3091 }
3092 }
3093
3094 // overrun distance
3095 double overrunDistanceEval = lineSettings.overrunDistance();
3096 if ( !qgsDoubleNear( overrunDistanceEval, 0.0 ) )
3097 {
3098 overrunDistanceEval = context.convertToMapUnits( overrunDistanceEval, lineSettings.overrunDistanceUnit(), lineSettings.overrunDistanceMapUnitScale() );
3099 }
3100
3101 // we smooth out the overrun label extensions by 1 mm, to avoid little jaggies right at the start or end of the lines
3102 // causing the overrun extension to extend out in an undesirable direction. This is hard coded, we don't want to overload
3103 // users with options they likely don't need to see...
3104 const double overrunSmoothDist = context.convertToMapUnits( 1, Qgis::RenderUnit::Millimeters );
3105
3106 // maximum distance
3107 double maximumDistanceEval = pointSettings.maximumDistance();
3108 if ( !qgsDoubleNear( maximumDistanceEval, 0.0 ) )
3109 {
3110 maximumDistanceEval = context.convertToMapUnits( maximumDistanceEval, pointSettings.maximumDistanceUnit(), pointSettings.maximumDistanceMapUnitScale() );
3111 }
3112
3113 // feature to the layer
3114 auto labelFeature = std::make_unique< QgsTextLabelFeature>( feature.id(), std::move( geos_geom_clone ), labelSize, subPartId );
3115 labelFeature->setAnchorPosition( anchorPosition );
3116 labelFeature->setFeature( feature );
3117 labelFeature->setSymbol( symbol );
3118 labelFeature->setDocument( doc, documentMetrics );
3119 if ( !qgsDoubleNear( rotatedSize.width(), 0.0 ) && !qgsDoubleNear( rotatedSize.height(), 0.0 ) )
3120 labelFeature->setRotatedSize( rotatedSize );
3121 mFeatsRegPal++;
3122
3123 labelFeature->setHasFixedPosition( hasDataDefinedPosition );
3124 labelFeature->setFixedPosition( QgsPointXY( xPos, yPos ) );
3125 // use layer-level defined rotation, but not if position fixed
3126 labelFeature->setHasFixedAngle( dataDefinedRotation || ( !hasDataDefinedPosition && !qgsDoubleNear( angleInRadians, 0.0 ) ) );
3127 labelFeature->setFixedAngle( angleInRadians );
3128 labelFeature->setQuadOffset( QPointF( quadOffsetX, quadOffsetY ) );
3129 labelFeature->setPositionOffset( QgsPointXY( offsetX, offsetY ) );
3130 labelFeature->setOffsetType( offsetType );
3131 labelFeature->setAlwaysShow( alwaysShow );
3132 labelFeature->setRepeatDistance( repeatDist );
3133 labelFeature->setLabelText( labelText );
3134 labelFeature->setPermissibleZone( permissibleZone );
3135 labelFeature->setOverrunDistance( overrunDistanceEval );
3136 labelFeature->setOverrunSmoothDistance( overrunSmoothDist );
3137 labelFeature->setMaximumDistance( maximumDistanceEval );
3138 labelFeature->setLineAnchorPercent( lineSettings.lineAnchorPercent() );
3139 labelFeature->setLineAnchorType( lineSettings.anchorType() );
3140 labelFeature->setLineAnchorTextPoint( lineSettings.anchorTextPoint() );
3141 labelFeature->setCurvedLabelMode( lineSettings.curvedLabelMode() );
3142 labelFeature->setMultiPartBehavior( placementSettings.multiPartBehavior() );
3143 labelFeature->setOriginalFeatureCrs( context.coordinateTransform().sourceCrs() );
3144 labelFeature->setMinimumSize( minimumSize );
3145 labelFeature->setWhitespaceCollisionHandling( placementSettings.whitespaceCollisionHandling() );
3146 if ( geom.type() == Qgis::GeometryType::Point && !obstacleGeometry.isNull() )
3147 {
3148 //register symbol size
3149 labelFeature->setSymbolSize( QSizeF( obstacleGeometry.boundingBox().width(),
3150 obstacleGeometry.boundingBox().height() ) );
3151 }
3152
3153 if ( outerBounds.left() != 0 || outerBounds.top() != 0 || !qgsDoubleNear( outerBounds.width(), labelSize.width() ) || !qgsDoubleNear( outerBounds.height(), labelSize.height() ) )
3154 {
3155 labelFeature->setOuterBounds( outerBounds );
3156 }
3157
3158 QgsLabelFeatureThinningSettings thinning;
3159 if ( featureThinningSettings.labelMarginDistance() > 0 )
3160 {
3161 thinning.setLabelMarginDistance( context.convertToMapUnits( featureThinningSettings.labelMarginDistance(),
3162 featureThinningSettings.labelMarginDistanceUnit(),
3163 featureThinningSettings.labelMarginDistanceMapUnitScale() ) );
3164 }
3165 if ( featureThinningSettings.allowDuplicateRemoval() )
3166 {
3167 thinning.setNoRepeatDistance( context.convertToMapUnits( featureThinningSettings.minimumDistanceToDuplicate(),
3168 featureThinningSettings.minimumDistanceToDuplicateUnit(),
3169 featureThinningSettings.minimumDistanceToDuplicateMapUnitScale() ) );
3170 }
3171 ( *labelFeature ).setThinningSettings( thinning );
3172
3173 //set label's visual margin so that top visual margin is the leading, and bottom margin is the font's descent
3174 //this makes labels align to the font's baseline or highest character
3175 double topMargin = std::max( 0.25 * labelFontMetrics.ascent(), 0.0 );
3176 double bottomMargin = 1.0 + labelFontMetrics.descent();
3177 QgsMargins vm( 0.0, topMargin, 0.0, bottomMargin );
3178 vm *= xform->mapUnitsPerPixel();
3179 labelFeature->setVisualMargin( vm );
3180
3181 // store the label's calculated font for later use during painting
3182 QgsDebugMsgLevel( u"PAL font stored definedFont: %1, Style: %2"_s.arg( evaluatedFormat.font().toString(), evaluatedFormat.font().styleName() ), 4 );
3183 labelFeature->setDefinedFont( evaluatedFormat.font() );
3184
3185 labelFeature->setMaximumCharacterAngleInside( std::clamp( maxcharanglein, 20.0, 60.0 ) * M_PI / 180 );
3186 labelFeature->setMaximumCharacterAngleOutside( std::clamp( maxcharangleout, -95.0, -20.0 ) * M_PI / 180 );
3187 switch ( placement )
3188 {
3196 // these placements don't require text metrics
3197 break;
3198
3201 labelFeature->setTextMetrics( QgsTextLabelFeature::calculateTextMetrics( xform, context, evaluatedFormat, evaluatedFormat.font(), labelFontMetrics, evaluatedFormat.font().letterSpacing(), evaluatedFormat.font().wordSpacing(), doc, documentMetrics ) );
3202 break;
3203 }
3204
3205 // for labelFeature the LabelInfo is passed to feat when it is registered
3206
3207 // TODO: allow layer-wide feature dist in PAL...?
3208
3209 // data defined label-feature distance?
3210 double distance = dist;
3211 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::LabelDistance ) )
3212 {
3213 context.expressionContext().setOriginalValueVariable( distance );
3214 distance = mDataDefinedProperties.valueAsDouble( QgsPalLayerSettings::Property::LabelDistance, context.expressionContext(), distance );
3215 }
3216
3217 // data defined label-feature distance units?
3218 Qgis::RenderUnit distUnit = distUnits;
3219 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::DistanceUnits ) )
3220 {
3221 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Property::DistanceUnits, context.expressionContext() );
3222 if ( !QgsVariantUtils::isNull( exprVal ) )
3223 {
3224 QString units = exprVal.toString().trimmed();
3225 QgsDebugMsgLevel( u"exprVal DistanceUnits:%1"_s.arg( units ), 4 );
3226 if ( !units.isEmpty() )
3227 {
3228 bool ok = false;
3229 Qgis::RenderUnit decodedUnits = QgsUnitTypes::decodeRenderUnit( units, &ok );
3230 if ( ok )
3231 {
3232 distUnit = decodedUnits;
3233 }
3234 }
3235 }
3236 }
3237 distance = context.convertToPainterUnits( distance, distUnit, distMapUnitScale );
3238
3239 // when using certain placement modes, we force a tiny minimum distance. This ensures that
3240 // candidates are created just offset from a border and avoids candidates being incorrectly flagged as colliding with neighbours
3241 switch ( placement )
3242 {
3246 distance = ( distance < 0 ? -1 : 1 ) * std::max( std::fabs( distance ), 1.0 );
3247 break;
3248
3250 break;
3251
3257 {
3258 distance = std::max( distance, 2.0 );
3259 }
3260 break;
3261
3263 distance = std::max( distance, 2.0 );
3264 break;
3265 }
3266
3267 if ( !qgsDoubleNear( distance, 0.0 ) )
3268 {
3269 double d = ptOne.distance( ptZero ) * distance;
3270 labelFeature->setDistLabel( d );
3271 }
3272
3273 if ( ddFixedQuad )
3274 {
3275 labelFeature->setHasFixedQuadrant( true );
3276 }
3277
3278 labelFeature->setArrangementFlags( lineSettings.placementFlags() );
3279
3280 labelFeature->setPolygonPlacementFlags( polygonPlacement );
3281
3282 // data defined z-index?
3283 double z = zIndex;
3284 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::ZIndex ) )
3285 {
3287 z = mDataDefinedProperties.valueAsDouble( QgsPalLayerSettings::Property::ZIndex, context.expressionContext(), z );
3288 }
3289 labelFeature->setZIndex( z );
3290
3291 // data defined priority?
3292 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::Priority ) )
3293 {
3295 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Property::Priority, context.expressionContext() );
3296 if ( !QgsVariantUtils::isNull( exprVal ) )
3297 {
3298 bool ok;
3299 double priorityD = exprVal.toDouble( &ok );
3300 if ( ok )
3301 {
3302 priorityD = std::clamp( priorityD, 0.0, 10.0 );
3303 priorityD = 1 - priorityD / 10.0; // convert 0..10 --> 1..0
3304 labelFeature->setPriority( priorityD );
3305 }
3306 }
3307 }
3308
3309 labelFeature->setAllowDegradedPlacement( placementSettings.allowDegradedPlacement() );
3310 labelFeature->setOverlapHandling( placementSettings.overlapHandling() );
3311 labelFeature->setPrioritization( placementSettings.prioritization() );
3312
3313 QgsLabelObstacleSettings os = mObstacleSettings;
3314 os.setIsObstacle( isObstacle );
3315 os.updateDataDefinedProperties( mDataDefinedProperties, context.expressionContext() );
3316 os.setObstacleGeometry( obstacleGeometry );
3317 labelFeature->setObstacleSettings( os );
3318
3319 QVector< Qgis::LabelPredefinedPointPosition > positionOrder = pointSettings.predefinedPositionOrder();
3320 if ( positionOrder.isEmpty() )
3321 positionOrder = *DEFAULT_PLACEMENT_ORDER();
3322
3323 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::PredefinedPositionOrder ) )
3324 {
3326 QString dataDefinedOrder = mDataDefinedProperties.valueAsString( QgsPalLayerSettings::Property::PredefinedPositionOrder, context.expressionContext() );
3327 if ( !dataDefinedOrder.isEmpty() )
3328 {
3329 positionOrder = QgsLabelingUtils::decodePredefinedPositionOrder( dataDefinedOrder );
3330 }
3331 }
3332 labelFeature->setPredefinedPositionOrder( positionOrder );
3333
3334 // add parameters for data defined labeling to label feature
3335 labelFeature->setDataDefinedValues( dataDefinedValues );
3336
3337 return labelFeature;
3338}
3339
3340std::unique_ptr<QgsLabelFeature> QgsPalLayerSettings::registerObstacleFeature( const QgsFeature &f, QgsRenderContext &context, const QgsGeometry &obstacleGeometry )
3341{
3342 mCurFeat = &f;
3343
3344 QgsGeometry geom;
3345 if ( !obstacleGeometry.isNull() )
3346 {
3347 geom = obstacleGeometry;
3348 }
3349 else
3350 {
3351 geom = f.geometry();
3352 }
3353
3354 if ( geom.isNull() )
3355 {
3356 return nullptr;
3357 }
3358
3359 // don't even try to register linestrings with only one vertex as an obstacle
3360 if ( const QgsLineString *ls = qgsgeometry_cast< const QgsLineString * >( geom.constGet() ) )
3361 {
3362 if ( ls->numPoints() < 2 )
3363 return nullptr;
3364 }
3365
3366 // simplify?
3367 const QgsVectorSimplifyMethod &simplifyMethod = context.vectorSimplifyMethod();
3368 std::unique_ptr<QgsGeometry> scopedClonedGeom;
3370 {
3371 int simplifyHints = simplifyMethod.simplifyHints() | QgsMapToPixelSimplifier::SimplifyEnvelope;
3372 const Qgis::VectorSimplificationAlgorithm simplifyAlgorithm = simplifyMethod.simplifyAlgorithm();
3373 QgsMapToPixelSimplifier simplifier( simplifyHints, simplifyMethod.tolerance(), simplifyAlgorithm );
3374 geom = simplifier.simplify( geom );
3375 }
3376
3377 geos::unique_ptr geos_geom_clone;
3378 std::unique_ptr<QgsGeometry> scopedPreparedGeom;
3379
3380 if ( QgsPalLabeling::geometryRequiresPreparation( geom, context, ct, extentGeom, mLineSettings.mergeLines() ) )
3381 {
3382 geom = QgsPalLabeling::prepareGeometry( geom, context, ct, extentGeom, mLineSettings.mergeLines() );
3383 }
3384 geos_geom_clone = QgsGeos::asGeos( geom );
3385
3386 if ( !geos_geom_clone )
3387 return nullptr; // invalid geometry
3388
3389 // feature to the layer
3390 auto obstacleFeature = std::make_unique< QgsLabelFeature >( f.id(), std::move( geos_geom_clone ), QSizeF( 0, 0 ) );
3391 obstacleFeature->setFeature( f );
3392
3393 QgsLabelObstacleSettings os = mObstacleSettings;
3394 os.setIsObstacle( true );
3395 os.updateDataDefinedProperties( mDataDefinedProperties, context.expressionContext() );
3396 obstacleFeature->setObstacleSettings( os );
3397
3398 mFeatsRegPal++;
3399 return obstacleFeature;
3400}
3401
3402bool QgsPalLayerSettings::dataDefinedValEval( DataDefinedValueType valType,
3404 QVariant &exprVal, QgsExpressionContext &context, const QVariant &originalValue )
3405{
3406 if ( !mDataDefinedProperties.isActive( p ) )
3407 return false;
3408
3409 context.setOriginalValueVariable( originalValue );
3410 exprVal = mDataDefinedProperties.value( p, context );
3411 if ( !QgsVariantUtils::isNull( exprVal ) )
3412 {
3413 switch ( valType )
3414 {
3415 case DDBool:
3416 {
3417 bool bol = exprVal.toBool();
3418 dataDefinedValues.insert( p, QVariant( bol ) );
3419 return true;
3420 }
3421 case DDInt:
3422 {
3423 bool ok;
3424 int size = exprVal.toInt( &ok );
3425
3426 if ( ok )
3427 {
3428 dataDefinedValues.insert( p, QVariant( size ) );
3429 return true;
3430 }
3431 return false;
3432 }
3433 case DDIntPos:
3434 {
3435 bool ok;
3436 int size = exprVal.toInt( &ok );
3437
3438 if ( ok && size > 0 )
3439 {
3440 dataDefinedValues.insert( p, QVariant( size ) );
3441 return true;
3442 }
3443 return false;
3444 }
3445 case DDDouble:
3446 {
3447 bool ok;
3448 double size = exprVal.toDouble( &ok );
3449
3450 if ( ok )
3451 {
3452 dataDefinedValues.insert( p, QVariant( size ) );
3453 return true;
3454 }
3455 return false;
3456 }
3457 case DDDoublePos:
3458 {
3459 bool ok;
3460 double size = exprVal.toDouble( &ok );
3461
3462 if ( ok && size > 0.0 )
3463 {
3464 dataDefinedValues.insert( p, QVariant( size ) );
3465 return true;
3466 }
3467 return false;
3468 }
3469 case DDRotation180:
3470 {
3471 bool ok;
3472 double rot = exprVal.toDouble( &ok );
3473 if ( ok )
3474 {
3475 if ( rot < -180.0 && rot >= -360 )
3476 {
3477 rot += 360;
3478 }
3479 if ( rot > 180.0 && rot <= 360 )
3480 {
3481 rot -= 360;
3482 }
3483 if ( rot >= -180 && rot <= 180 )
3484 {
3485 dataDefinedValues.insert( p, QVariant( rot ) );
3486 return true;
3487 }
3488 }
3489 return false;
3490 }
3491 case DDOpacity:
3492 {
3493 bool ok;
3494 int size = exprVal.toInt( &ok );
3495 if ( ok && size >= 0 && size <= 100 )
3496 {
3497 dataDefinedValues.insert( p, QVariant( size ) );
3498 return true;
3499 }
3500 return false;
3501 }
3502 case DDString:
3503 {
3504 QString str = exprVal.toString(); // don't trim whitespace
3505
3506 dataDefinedValues.insert( p, QVariant( str ) ); // let it stay empty if it is
3507 return true;
3508 }
3509 case DDUnits:
3510 {
3511 QString unitstr = exprVal.toString().trimmed();
3512
3513 if ( !unitstr.isEmpty() )
3514 {
3515 dataDefinedValues.insert( p, QVariant( static_cast< int >( QgsUnitTypes::decodeRenderUnit( unitstr ) ) ) );
3516 return true;
3517 }
3518 return false;
3519 }
3520 case DDColor:
3521 {
3522 QString colorstr = exprVal.toString().trimmed();
3523 QColor color = QgsColorUtils::colorFromString( colorstr );
3524
3525 if ( color.isValid() )
3526 {
3527 dataDefinedValues.insert( p, QVariant( color ) );
3528 return true;
3529 }
3530 return false;
3531 }
3532 case DDJoinStyle:
3533 {
3534 QString joinstr = exprVal.toString().trimmed();
3535
3536 if ( !joinstr.isEmpty() )
3537 {
3538 dataDefinedValues.insert( p, QVariant( static_cast< int >( QgsSymbolLayerUtils::decodePenJoinStyle( joinstr ) ) ) );
3539 return true;
3540 }
3541 return false;
3542 }
3543 case DDBlendMode:
3544 {
3545 QString blendstr = exprVal.toString().trimmed();
3546
3547 if ( !blendstr.isEmpty() )
3548 {
3549 dataDefinedValues.insert( p, QVariant( static_cast< int >( QgsSymbolLayerUtils::decodeBlendMode( blendstr ) ) ) );
3550 return true;
3551 }
3552 return false;
3553 }
3554 case DDPointF:
3555 {
3556 bool ok = false;
3557 const QPointF res = QgsSymbolLayerUtils::toPoint( exprVal, &ok );
3558 if ( ok )
3559 {
3560 dataDefinedValues.insert( p, res );
3561 return true;
3562 }
3563 return false;
3564 }
3565 case DDSizeF:
3566 {
3567 bool ok = false;
3568 const QSizeF res = QgsSymbolLayerUtils::toSize( exprVal, &ok );
3569 if ( ok )
3570 {
3571 dataDefinedValues.insert( p, res );
3572 return true;
3573 }
3574 return false;
3575 }
3576 }
3577 }
3578 return false;
3579}
3580
3581void QgsPalLayerSettings::parseTextStyle( QFont &labelFont,
3582 Qgis::RenderUnit fontunits,
3583 QgsRenderContext &context )
3584{
3585 // NOTE: labelFont already has pixelSize set, so pointSize or pointSizeF might return -1
3586
3587 QVariant exprVal; // value() is repeatedly nulled on data defined evaluation and replaced when successful
3588
3589 // Two ways to generate new data defined font:
3590 // 1) Family + [bold] + [italic] (named style is ignored and font is built off of base family)
3591 // 2) Family + named style (bold or italic is ignored)
3592
3593 // data defined font family?
3594 QString ddFontFamily;
3595 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::Family ) )
3596 {
3597 context.expressionContext().setOriginalValueVariable( labelFont.family() );
3598 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Property::Family, context.expressionContext() );
3599 if ( !QgsVariantUtils::isNull( exprVal ) )
3600 {
3601 QString family = exprVal.toString().trimmed();
3602 QgsDebugMsgLevel( u"exprVal Font family:%1"_s.arg( family ), 4 );
3603
3605 if ( labelFont.family() != family )
3606 {
3607 // testing for ddFontFamily in QFontDatabase.families() may be slow to do for every feature
3608 // (i.e. don't use QgsFontUtils::fontFamilyMatchOnSystem( family ) here)
3609 if ( QgsFontUtils::fontFamilyOnSystem( family ) )
3610 {
3611 ddFontFamily = family;
3612 }
3613 }
3614 }
3615 }
3616
3617 // data defined named font style?
3618 QString ddFontStyle;
3619 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::FontStyle ) )
3620 {
3621 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Property::FontStyle, context.expressionContext() );
3622 if ( !QgsVariantUtils::isNull( exprVal ) )
3623 {
3624 QString fontstyle = exprVal.toString().trimmed();
3625 QgsDebugMsgLevel( u"exprVal Font style:%1"_s.arg( fontstyle ), 4 );
3626 ddFontStyle = fontstyle;
3627 }
3628 }
3629
3630 // data defined bold font style?
3631 bool ddBold = false;
3632 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::Bold ) )
3633 {
3634 context.expressionContext().setOriginalValueVariable( labelFont.bold() );
3635 ddBold = mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::Property::Bold, context.expressionContext(), false );
3636 }
3637
3638 // data defined italic font style?
3639 bool ddItalic = false;
3640 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::Italic ) )
3641 {
3642 context.expressionContext().setOriginalValueVariable( labelFont.italic() );
3643 ddItalic = mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::Property::Italic, context.expressionContext(), false );
3644 }
3645
3646 // TODO: update when pref for how to resolve missing family (use matching algorithm or just default font) is implemented
3647 // (currently defaults to what has been read in from layer settings)
3648 QFont newFont;
3649 QFont appFont = QApplication::font();
3650 bool newFontBuilt = false;
3651 if ( ddBold || ddItalic )
3652 {
3653 // new font needs built, since existing style needs removed
3654 newFont = QgsFontUtils::createFont( !ddFontFamily.isEmpty() ? ddFontFamily : labelFont.family() );
3655 newFontBuilt = true;
3656 newFont.setBold( ddBold );
3657 newFont.setItalic( ddItalic );
3658 }
3659 else if ( !ddFontStyle.isEmpty()
3660 && ddFontStyle.compare( "Ignore"_L1, Qt::CaseInsensitive ) != 0 )
3661 {
3662 if ( !ddFontFamily.isEmpty() )
3663 {
3664 // both family and style are different, build font from database
3665 if ( !mFontDB )
3666 mFontDB = std::make_unique< QFontDatabase >();
3667
3668 QFont styledfont = mFontDB->font( ddFontFamily, ddFontStyle, appFont.pointSize() );
3669 if ( appFont != styledfont )
3670 {
3671 newFont = styledfont;
3672 newFontBuilt = true;
3673 }
3674 }
3675
3676 // update the font face style
3677 QgsFontUtils::updateFontViaStyle( newFontBuilt ? newFont : labelFont, ddFontStyle );
3678 }
3679 else if ( !ddFontFamily.isEmpty() )
3680 {
3681 if ( ddFontStyle.compare( "Ignore"_L1, Qt::CaseInsensitive ) != 0 )
3682 {
3683 // just family is different, build font from database
3684 if ( !mFontDB )
3685 mFontDB = std::make_unique< QFontDatabase >();
3686 QFont styledfont = mFontDB->font( ddFontFamily, mFormat.namedStyle(), appFont.pointSize() );
3687 if ( appFont != styledfont )
3688 {
3689 newFont = styledfont;
3690 newFontBuilt = true;
3691 }
3692 }
3693 else
3694 {
3695 newFont = QgsFontUtils::createFont( ddFontFamily );
3696 newFontBuilt = true;
3697 }
3698 }
3699
3700 if ( newFontBuilt )
3701 {
3702 // copy over existing font settings
3703 //newFont = newFont.resolve( labelFont ); // should work, but let's be sure what's being copied
3704 newFont.setPixelSize( labelFont.pixelSize() );
3705 newFont.setUnderline( labelFont.underline() );
3706 newFont.setStrikeOut( labelFont.strikeOut() );
3707 newFont.setWordSpacing( labelFont.wordSpacing() );
3708 newFont.setLetterSpacing( QFont::AbsoluteSpacing, labelFont.letterSpacing() );
3709
3710 labelFont = newFont;
3711 }
3712
3713 // data defined word spacing?
3714 double wordspace = labelFont.wordSpacing();
3715 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::FontWordSpacing ) )
3716 {
3717 context.expressionContext().setOriginalValueVariable( wordspace );
3718 wordspace = mDataDefinedProperties.valueAsDouble( QgsPalLayerSettings::Property::FontWordSpacing, context.expressionContext(), wordspace );
3719 }
3720 labelFont.setWordSpacing( context.convertToPainterUnits( wordspace, fontunits, mFormat.sizeMapUnitScale() ) );
3721
3722 // data defined letter spacing?
3723 double letterspace = labelFont.letterSpacing();
3724 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::FontLetterSpacing ) )
3725 {
3726 context.expressionContext().setOriginalValueVariable( letterspace );
3727 letterspace = mDataDefinedProperties.valueAsDouble( QgsPalLayerSettings::Property::FontLetterSpacing, context.expressionContext(), letterspace );
3728 }
3729 labelFont.setLetterSpacing( QFont::AbsoluteSpacing, context.convertToPainterUnits( letterspace, fontunits, mFormat.sizeMapUnitScale() ) );
3730
3731 // data defined strikeout font style?
3732 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::Strikeout ) )
3733 {
3734 context.expressionContext().setOriginalValueVariable( labelFont.strikeOut() );
3735 bool strikeout = mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::Property::Strikeout, context.expressionContext(), false );
3736 labelFont.setStrikeOut( strikeout );
3737 }
3738
3739 // data defined stretch
3740 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::FontStretchFactor ) )
3741 {
3742 context.expressionContext().setOriginalValueVariable( mFormat.stretchFactor() );
3743 labelFont.setStretch( mDataDefinedProperties.valueAsInt( QgsPalLayerSettings::Property::FontStretchFactor, context.expressionContext(), mFormat.stretchFactor() ) );
3744 }
3745
3746 // data defined underline font style?
3747 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::Underline ) )
3748 {
3749 context.expressionContext().setOriginalValueVariable( labelFont.underline() );
3750 bool underline = mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::Property::Underline, context.expressionContext(), false );
3751 labelFont.setUnderline( underline );
3752 }
3753
3754 // pass the rest on to QgsPalLabeling::drawLabeling
3755
3756 // data defined font color?
3757 dataDefinedValEval( DDColor, QgsPalLayerSettings::Property::Color, exprVal, context.expressionContext(), QgsColorUtils::colorToString( mFormat.color() ) );
3758
3759 // data defined font opacity?
3760 dataDefinedValEval( DDOpacity, QgsPalLayerSettings::Property::FontOpacity, exprVal, context.expressionContext(), mFormat.opacity() * 100 );
3761
3762 // data defined font blend mode?
3763 dataDefinedValEval( DDBlendMode, QgsPalLayerSettings::Property::FontBlendMode, exprVal, context.expressionContext() );
3764
3765}
3766
3767void QgsPalLayerSettings::parseTextBuffer( QgsRenderContext &context )
3768{
3769 QVariant exprVal; // value() is repeatedly nulled on data defined evaluation and replaced when successful
3770
3771 QgsTextBufferSettings buffer = mFormat.buffer();
3772
3773 // data defined draw buffer?
3774 bool drawBuffer = mFormat.buffer().enabled();
3775 if ( dataDefinedValEval( DDBool, QgsPalLayerSettings::Property::BufferDraw, exprVal, context.expressionContext(), buffer.enabled() ) )
3776 {
3777 drawBuffer = exprVal.toBool();
3778 }
3779 else if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::BufferDraw ) && QgsVariantUtils::isNull( exprVal ) )
3780 {
3781 dataDefinedValues.insert( QgsPalLayerSettings::Property::BufferDraw, QVariant( drawBuffer ) );
3782 }
3783
3784 if ( !drawBuffer )
3785 {
3786 return;
3787 }
3788
3789 // data defined buffer size?
3790 double bufrSize = buffer.size();
3791 if ( dataDefinedValEval( DDDoublePos, QgsPalLayerSettings::Property::BufferSize, exprVal, context.expressionContext(), buffer.size() ) )
3792 {
3793 bufrSize = exprVal.toDouble();
3794 }
3795
3796 // data defined buffer transparency?
3797 double bufferOpacity = buffer.opacity() * 100;
3798 if ( dataDefinedValEval( DDOpacity, QgsPalLayerSettings::Property::BufferOpacity, exprVal, context.expressionContext(), bufferOpacity ) )
3799 {
3800 bufferOpacity = exprVal.toDouble();
3801 }
3802
3803 drawBuffer = ( drawBuffer && bufrSize > 0.0 && bufferOpacity > 0 );
3804
3805 if ( !drawBuffer )
3806 {
3807 dataDefinedValues.insert( QgsPalLayerSettings::Property::BufferDraw, QVariant( false ) ); // trigger value
3808 dataDefinedValues.remove( QgsPalLayerSettings::Property::BufferSize );
3809 dataDefinedValues.remove( QgsPalLayerSettings::Property::BufferOpacity );
3810 return; // don't bother evaluating values that won't be used
3811 }
3812
3813 // data defined buffer units?
3814 dataDefinedValEval( DDUnits, QgsPalLayerSettings::Property::BufferUnit, exprVal, context.expressionContext() );
3815
3816 // data defined buffer color?
3817 dataDefinedValEval( DDColor, QgsPalLayerSettings::Property::BufferColor, exprVal, context.expressionContext(), QgsColorUtils::colorToString( buffer.color() ) );
3818
3819 // data defined buffer pen join style?
3820 dataDefinedValEval( DDJoinStyle, QgsPalLayerSettings::Property::BufferJoinStyle, exprVal, context.expressionContext(), QgsSymbolLayerUtils::encodePenJoinStyle( buffer.joinStyle() ) );
3821
3822 // data defined buffer blend mode?
3823 dataDefinedValEval( DDBlendMode, QgsPalLayerSettings::Property::BufferBlendMode, exprVal, context.expressionContext() );
3824}
3825
3826void QgsPalLayerSettings::parseTextMask( QgsRenderContext &context )
3827{
3828 QVariant exprVal; // value() is repeatedly nulled on data defined evaluation and replaced when successful
3829
3830 QgsTextMaskSettings mask = mFormat.mask();
3831
3832 // data defined enabled mask?
3833 bool maskEnabled = mask.enabled();
3834 if ( dataDefinedValEval( DDBool, QgsPalLayerSettings::Property::MaskEnabled, exprVal, context.expressionContext(), mask.enabled() ) )
3835 {
3836 maskEnabled = exprVal.toBool();
3837 }
3838
3839 if ( !maskEnabled )
3840 {
3841 return;
3842 }
3843
3844 // data defined buffer size?
3845 double bufrSize = mask.size();
3846 if ( dataDefinedValEval( DDDoublePos, QgsPalLayerSettings::Property::MaskBufferSize, exprVal, context.expressionContext(), mask.size() ) )
3847 {
3848 bufrSize = exprVal.toDouble();
3849 }
3850
3851 // data defined opacity?
3852 double opacity = mask.opacity() * 100;
3853 if ( dataDefinedValEval( DDOpacity, QgsPalLayerSettings::Property::MaskOpacity, exprVal, context.expressionContext(), opacity ) )
3854 {
3855 opacity = exprVal.toDouble();
3856 }
3857
3858 maskEnabled = ( maskEnabled && bufrSize > 0.0 && opacity > 0 );
3859
3860 if ( !maskEnabled )
3861 {
3862 dataDefinedValues.insert( QgsPalLayerSettings::Property::MaskEnabled, QVariant( false ) ); // trigger value
3863 dataDefinedValues.remove( QgsPalLayerSettings::Property::MaskBufferSize );
3864 dataDefinedValues.remove( QgsPalLayerSettings::Property::MaskOpacity );
3865 return; // don't bother evaluating values that won't be used
3866 }
3867
3868 // data defined buffer units?
3869 dataDefinedValEval( DDUnits, QgsPalLayerSettings::Property::MaskBufferUnit, exprVal, context.expressionContext() );
3870
3871 // data defined buffer pen join style?
3872 dataDefinedValEval( DDJoinStyle, QgsPalLayerSettings::Property::MaskJoinStyle, exprVal, context.expressionContext(), QgsSymbolLayerUtils::encodePenJoinStyle( mask.joinStyle() ) );
3873}
3874
3875void QgsPalLayerSettings::parseTextFormatting( QgsRenderContext &context )
3876{
3877 QVariant exprVal; // value() is repeatedly nulled on data defined evaluation and replaced when successful
3878
3879 // data defined multiline wrap character?
3880 QString wrapchr = wrapChar;
3881 if ( dataDefinedValEval( DDString, QgsPalLayerSettings::Property::MultiLineWrapChar, exprVal, context.expressionContext(), wrapChar ) )
3882 {
3883 wrapchr = exprVal.toString();
3884 }
3885
3886 int evalAutoWrapLength = autoWrapLength;
3887 if ( dataDefinedValEval( DDInt, QgsPalLayerSettings::Property::AutoWrapLength, exprVal, context.expressionContext(), evalAutoWrapLength ) )
3888 {
3889 evalAutoWrapLength = exprVal.toInt();
3890 }
3891
3892 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::TabStopDistance ) )
3893 {
3894 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Property::TabStopDistance, context.expressionContext() );
3895 if ( !QgsVariantUtils::isNull( exprVal ) )
3896 {
3897 dataDefinedValues.insert( QgsPalLayerSettings::Property::TabStopDistance, exprVal );
3898 }
3899 }
3900
3901 // data defined multiline height?
3902 dataDefinedValEval( DDDouble, QgsPalLayerSettings::Property::MultiLineHeight, exprVal, context.expressionContext() );
3903
3904 // data defined multiline text align?
3905 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::MultiLineAlignment ) )
3906 {
3907 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Property::MultiLineAlignment, context.expressionContext() );
3908 if ( !QgsVariantUtils::isNull( exprVal ) )
3909 {
3910 QString str = exprVal.toString().trimmed();
3911 QgsDebugMsgLevel( u"exprVal MultiLineAlignment:%1"_s.arg( str ), 4 );
3912
3913 if ( !str.isEmpty() )
3914 {
3915 // "Left"
3917
3918 if ( str.compare( "Center"_L1, Qt::CaseInsensitive ) == 0 )
3919 {
3921 }
3922 else if ( str.compare( "Right"_L1, Qt::CaseInsensitive ) == 0 )
3923 {
3925 }
3926 else if ( str.compare( "Follow"_L1, Qt::CaseInsensitive ) == 0 )
3927 {
3929 }
3930 else if ( str.compare( "Justify"_L1, Qt::CaseInsensitive ) == 0 )
3931 {
3933 }
3934 dataDefinedValues.insert( QgsPalLayerSettings::Property::MultiLineAlignment, QVariant( static_cast< int >( aligntype ) ) );
3935 }
3936 }
3937 }
3938
3939 // text orientation
3940 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::TextOrientation ) )
3941 {
3942 const QString encoded = QgsTextRendererUtils::encodeTextOrientation( mFormat.orientation() );
3943 context.expressionContext().setOriginalValueVariable( encoded );
3944 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Property::TextOrientation, context.expressionContext() );
3945 if ( !QgsVariantUtils::isNull( exprVal ) )
3946 {
3947 QString str = exprVal.toString().trimmed();
3948 if ( !str.isEmpty() )
3949 dataDefinedValues.insert( QgsPalLayerSettings::Property::TextOrientation, str );
3950 }
3951 }
3952
3953 // data defined direction symbol?
3954 bool drawDirSymb = mLineSettings.addDirectionSymbol();
3955 if ( dataDefinedValEval( DDBool, QgsPalLayerSettings::Property::DirSymbDraw, exprVal, context.expressionContext(), drawDirSymb ) )
3956 {
3957 drawDirSymb = exprVal.toBool();
3958 }
3959
3960 if ( drawDirSymb )
3961 {
3962 // data defined direction left symbol?
3963 dataDefinedValEval( DDString, QgsPalLayerSettings::Property::DirSymbLeft, exprVal, context.expressionContext(), mLineSettings.leftDirectionSymbol() );
3964
3965 // data defined direction right symbol?
3966 dataDefinedValEval( DDString, QgsPalLayerSettings::Property::DirSymbRight, exprVal, context.expressionContext(), mLineSettings.rightDirectionSymbol() );
3967
3968 // data defined direction symbol placement?
3969 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Property::DirSymbPlacement, context.expressionContext() );
3970 if ( !QgsVariantUtils::isNull( exprVal ) )
3971 {
3972 QString str = exprVal.toString().trimmed();
3973 QgsDebugMsgLevel( u"exprVal DirSymbPlacement:%1"_s.arg( str ), 4 );
3974
3975 if ( !str.isEmpty() )
3976 {
3977 // "LeftRight"
3979
3980 if ( str.compare( "Above"_L1, Qt::CaseInsensitive ) == 0 )
3981 {
3983 }
3984 else if ( str.compare( "Below"_L1, Qt::CaseInsensitive ) == 0 )
3985 {
3987 }
3988 dataDefinedValues.insert( QgsPalLayerSettings::Property::DirSymbPlacement, QVariant( static_cast< int >( placetype ) ) );
3989 }
3990 }
3991
3992 // data defined direction symbol reversed?
3993 dataDefinedValEval( DDBool, QgsPalLayerSettings::Property::DirSymbReverse, exprVal, context.expressionContext(), mLineSettings.reverseDirectionSymbol() );
3994 }
3995
3996 // formatting for numbers is inline with generation of base label text and not passed to label painting
3997}
3998
3999void QgsPalLayerSettings::parseShapeBackground( QgsRenderContext &context )
4000{
4001 QVariant exprVal; // value() is repeatedly nulled on data defined evaluation and replaced when successful
4002
4003 QgsTextBackgroundSettings background = mFormat.background();
4004
4005 // data defined draw shape?
4006 bool drawShape = background.enabled();
4007 if ( dataDefinedValEval( DDBool, QgsPalLayerSettings::Property::ShapeDraw, exprVal, context.expressionContext(), drawShape ) )
4008 {
4009 drawShape = exprVal.toBool();
4010 }
4011
4012 if ( !drawShape )
4013 {
4014 return;
4015 }
4016
4017 // data defined shape transparency?
4018 double shapeOpacity = background.opacity() * 100;
4019 if ( dataDefinedValEval( DDOpacity, QgsPalLayerSettings::Property::ShapeOpacity, exprVal, context.expressionContext(), shapeOpacity ) )
4020 {
4021 shapeOpacity = 100.0 * exprVal.toDouble();
4022 }
4023
4024 drawShape = ( drawShape && shapeOpacity > 0 ); // size is not taken into account (could be)
4025
4026 if ( !drawShape )
4027 {
4028 dataDefinedValues.insert( QgsPalLayerSettings::Property::ShapeDraw, QVariant( false ) ); // trigger value
4029 dataDefinedValues.remove( QgsPalLayerSettings::Property::ShapeOpacity );
4030 return; // don't bother evaluating values that won't be used
4031 }
4032
4033 // data defined shape kind?
4034 QgsTextBackgroundSettings::ShapeType shapeKind = background.type();
4035 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::ShapeKind ) )
4036 {
4037 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Property::ShapeKind, context.expressionContext() );
4038 if ( !QgsVariantUtils::isNull( exprVal ) )
4039 {
4040 QString skind = exprVal.toString().trimmed();
4041 QgsDebugMsgLevel( u"exprVal ShapeKind:%1"_s.arg( skind ), 4 );
4042
4043 if ( !skind.isEmpty() )
4044 {
4045 shapeKind = QgsTextRendererUtils::decodeShapeType( skind );
4046 dataDefinedValues.insert( QgsPalLayerSettings::Property::ShapeKind, QVariant( static_cast< int >( shapeKind ) ) );
4047 }
4048 }
4049 }
4050
4051 // data defined shape SVG path?
4052 QString svgPath = background.svgFile();
4053 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::ShapeSVGFile ) )
4054 {
4055 context.expressionContext().setOriginalValueVariable( svgPath );
4056 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Property::ShapeSVGFile, context.expressionContext() );
4057 if ( !QgsVariantUtils::isNull( exprVal ) )
4058 {
4059 QString svgfile = exprVal.toString().trimmed();
4060 QgsDebugMsgLevel( u"exprVal ShapeSVGFile:%1"_s.arg( svgfile ), 4 );
4061
4062 // '' empty paths are allowed
4063 svgPath = QgsSymbolLayerUtils::svgSymbolNameToPath( svgfile, context.pathResolver() );
4064 dataDefinedValues.insert( QgsPalLayerSettings::Property::ShapeSVGFile, QVariant( svgPath ) );
4065 }
4066 }
4067
4068 // data defined shape size type?
4069 QgsTextBackgroundSettings::SizeType shpSizeType = background.sizeType();
4070 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::ShapeSizeType ) )
4071 {
4072 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Property::ShapeSizeType, context.expressionContext() );
4073 if ( !QgsVariantUtils::isNull( exprVal ) )
4074 {
4075 QString stype = exprVal.toString().trimmed();
4076 QgsDebugMsgLevel( u"exprVal ShapeSizeType:%1"_s.arg( stype ), 4 );
4077
4078 if ( !stype.isEmpty() )
4079 {
4081 dataDefinedValues.insert( QgsPalLayerSettings::Property::ShapeSizeType, QVariant( static_cast< int >( shpSizeType ) ) );
4082 }
4083 }
4084 }
4085
4086 // data defined shape size X? (SVGs only use X for sizing)
4087 double ddShpSizeX = background.size().width();
4088 if ( dataDefinedValEval( DDDouble, QgsPalLayerSettings::Property::ShapeSizeX, exprVal, context.expressionContext(), ddShpSizeX ) )
4089 {
4090 ddShpSizeX = exprVal.toDouble();
4091 }
4092
4093 // data defined shape size Y?
4094 double ddShpSizeY = background.size().height();
4095 if ( dataDefinedValEval( DDDouble, QgsPalLayerSettings::Property::ShapeSizeY, exprVal, context.expressionContext(), ddShpSizeY ) )
4096 {
4097 ddShpSizeY = exprVal.toDouble();
4098 }
4099
4100 // don't continue under certain circumstances (e.g. size is fixed)
4101 bool skip = false;
4102 if ( shapeKind == QgsTextBackgroundSettings::ShapeSVG
4103 && ( svgPath.isEmpty()
4104 || ( !svgPath.isEmpty()
4105 && shpSizeType == QgsTextBackgroundSettings::SizeFixed
4106 && ddShpSizeX == 0.0 ) ) )
4107 {
4108 skip = true;
4109 }
4111 && ( !background.markerSymbol()
4112 || ( background.markerSymbol()
4113 && shpSizeType == QgsTextBackgroundSettings::SizeFixed
4114 && ddShpSizeX == 0.0 ) ) )
4115 {
4116 skip = true;
4117 }
4118 if ( shapeKind != QgsTextBackgroundSettings::ShapeSVG
4120 && shpSizeType == QgsTextBackgroundSettings::SizeFixed
4121 && ( ddShpSizeX == 0.0 || ddShpSizeY == 0.0 ) )
4122 {
4123 skip = true;
4124 }
4125
4126 if ( skip )
4127 {
4128 dataDefinedValues.insert( QgsPalLayerSettings::Property::ShapeDraw, QVariant( false ) ); // trigger value
4129 dataDefinedValues.remove( QgsPalLayerSettings::Property::ShapeOpacity );
4130 dataDefinedValues.remove( QgsPalLayerSettings::Property::ShapeKind );
4131 dataDefinedValues.remove( QgsPalLayerSettings::Property::ShapeSVGFile );
4132 dataDefinedValues.remove( QgsPalLayerSettings::Property::ShapeSizeX );
4133 dataDefinedValues.remove( QgsPalLayerSettings::Property::ShapeSizeY );
4134 return; // don't bother evaluating values that won't be used
4135 }
4136
4137 // data defined shape size units?
4138 dataDefinedValEval( DDUnits, QgsPalLayerSettings::Property::ShapeSizeUnits, exprVal, context.expressionContext() );
4139
4140 // data defined shape rotation type?
4141 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::ShapeRotationType ) )
4142 {
4143 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Property::ShapeRotationType, context.expressionContext() );
4144 if ( !QgsVariantUtils::isNull( exprVal ) )
4145 {
4146 QString rotstr = exprVal.toString().trimmed();
4147 QgsDebugMsgLevel( u"exprVal ShapeRotationType:%1"_s.arg( rotstr ), 4 );
4148
4149 if ( !rotstr.isEmpty() )
4150 {
4151 // "Sync"
4153 dataDefinedValues.insert( QgsPalLayerSettings::Property::ShapeRotationType, QVariant( static_cast< int >( rottype ) ) );
4154 }
4155 }
4156 }
4157
4158 // data defined shape rotation?
4159 dataDefinedValEval( DDRotation180, QgsPalLayerSettings::Property::ShapeRotation, exprVal, context.expressionContext(), background.rotation() );
4160
4161 // data defined shape offset?
4162 dataDefinedValEval( DDPointF, QgsPalLayerSettings::Property::ShapeOffset, exprVal, context.expressionContext(), QgsSymbolLayerUtils::encodePoint( background.offset() ) );
4163
4164 // data defined shape offset units?
4165 dataDefinedValEval( DDUnits, QgsPalLayerSettings::Property::ShapeOffsetUnits, exprVal, context.expressionContext() );
4166
4167 // data defined shape radii?
4168 dataDefinedValEval( DDSizeF, QgsPalLayerSettings::Property::ShapeRadii, exprVal, context.expressionContext(), QgsSymbolLayerUtils::encodeSize( background.radii() ) );
4169
4170 // data defined shape radii units?
4171 dataDefinedValEval( DDUnits, QgsPalLayerSettings::Property::ShapeRadiiUnits, exprVal, context.expressionContext() );
4172
4173 // data defined shape blend mode?
4174 dataDefinedValEval( DDBlendMode, QgsPalLayerSettings::Property::ShapeBlendMode, exprVal, context.expressionContext() );
4175
4176 // data defined shape fill color?
4177 dataDefinedValEval( DDColor, QgsPalLayerSettings::Property::ShapeFillColor, exprVal, context.expressionContext(), QgsColorUtils::colorToString( background.fillColor() ) );
4178
4179 // data defined shape stroke color?
4180 dataDefinedValEval( DDColor, QgsPalLayerSettings::Property::ShapeStrokeColor, exprVal, context.expressionContext(), QgsColorUtils::colorToString( background.strokeColor() ) );
4181
4182 // data defined shape stroke width?
4183 dataDefinedValEval( DDDoublePos, QgsPalLayerSettings::Property::ShapeStrokeWidth, exprVal, context.expressionContext(), background.strokeWidth() );
4184
4185 // data defined shape stroke width units?
4186 dataDefinedValEval( DDUnits, QgsPalLayerSettings::Property::ShapeStrokeWidthUnits, exprVal, context.expressionContext() );
4187
4188 // data defined shape join style?
4189 dataDefinedValEval( DDJoinStyle, QgsPalLayerSettings::Property::ShapeJoinStyle, exprVal, context.expressionContext(), QgsSymbolLayerUtils::encodePenJoinStyle( background.joinStyle() ) );
4190
4191}
4192
4193void QgsPalLayerSettings::parseDropShadow( QgsRenderContext &context )
4194{
4195 QVariant exprVal; // value() is repeatedly nulled on data defined evaluation and replaced when successful
4196
4197 QgsTextShadowSettings shadow = mFormat.shadow();
4198
4199 // data defined draw shadow?
4200 bool drawShadow = shadow.enabled();
4201 if ( dataDefinedValEval( DDBool, QgsPalLayerSettings::Property::ShadowDraw, exprVal, context.expressionContext(), drawShadow ) )
4202 {
4203 drawShadow = exprVal.toBool();
4204 }
4205
4206 if ( !drawShadow )
4207 {
4208 return;
4209 }
4210
4211 // data defined shadow transparency?
4212 double shadowOpacity = shadow.opacity() * 100;
4213 if ( dataDefinedValEval( DDOpacity, QgsPalLayerSettings::Property::ShadowOpacity, exprVal, context.expressionContext(), shadowOpacity ) )
4214 {
4215 shadowOpacity = exprVal.toDouble();
4216 }
4217
4218 // data defined shadow offset distance?
4219 double shadowOffDist = shadow.offsetDistance();
4220 if ( dataDefinedValEval( DDDoublePos, QgsPalLayerSettings::Property::ShadowOffsetDist, exprVal, context.expressionContext(), shadowOffDist ) )
4221 {
4222 shadowOffDist = exprVal.toDouble();
4223 }
4224
4225 // data defined shadow offset distance?
4226 double shadowRad = shadow.blurRadius();
4227 if ( dataDefinedValEval( DDDoublePos, QgsPalLayerSettings::Property::ShadowRadius, exprVal, context.expressionContext(), shadowRad ) )
4228 {
4229 shadowRad = exprVal.toDouble();
4230 }
4231
4232 drawShadow = ( drawShadow && shadowOpacity > 0 && !( shadowOffDist == 0.0 && shadowRad == 0.0 ) );
4233
4234 if ( !drawShadow )
4235 {
4236 dataDefinedValues.insert( QgsPalLayerSettings::Property::ShadowDraw, QVariant( false ) ); // trigger value
4237 dataDefinedValues.remove( QgsPalLayerSettings::Property::ShadowOpacity );
4238 dataDefinedValues.remove( QgsPalLayerSettings::Property::ShadowOffsetDist );
4239 dataDefinedValues.remove( QgsPalLayerSettings::Property::ShadowRadius );
4240 return; // don't bother evaluating values that won't be used
4241 }
4242
4243 // data defined shadow under type?
4244 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::ShadowUnder ) )
4245 {
4246 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Property::ShadowUnder, context.expressionContext() );
4247 if ( !QgsVariantUtils::isNull( exprVal ) )
4248 {
4249 QString str = exprVal.toString().trimmed();
4250 QgsDebugMsgLevel( u"exprVal ShadowUnder:%1"_s.arg( str ), 4 );
4251
4252 if ( !str.isEmpty() )
4253 {
4255 dataDefinedValues.insert( QgsPalLayerSettings::Property::ShadowUnder, QVariant( static_cast< int >( shdwtype ) ) );
4256 }
4257 }
4258 }
4259
4260 // data defined shadow offset angle?
4261 dataDefinedValEval( DDRotation180, QgsPalLayerSettings::Property::ShadowOffsetAngle, exprVal, context.expressionContext(), shadow.offsetAngle() );
4262
4263 // data defined shadow offset units?
4264 dataDefinedValEval( DDUnits, QgsPalLayerSettings::Property::ShadowOffsetUnits, exprVal, context.expressionContext() );
4265
4266 // data defined shadow radius?
4267 dataDefinedValEval( DDDouble, QgsPalLayerSettings::Property::ShadowRadius, exprVal, context.expressionContext(), shadow.blurRadius() );
4268
4269 // data defined shadow radius units?
4270 dataDefinedValEval( DDUnits, QgsPalLayerSettings::Property::ShadowRadiusUnits, exprVal, context.expressionContext() );
4271
4272 // data defined shadow scale? ( gui bounds to 0-2000, no upper bound here )
4273 dataDefinedValEval( DDIntPos, QgsPalLayerSettings::Property::ShadowScale, exprVal, context.expressionContext(), shadow.scale() );
4274
4275 // data defined shadow color?
4276 dataDefinedValEval( DDColor, QgsPalLayerSettings::Property::ShadowColor, exprVal, context.expressionContext(), QgsColorUtils::colorToString( shadow.color() ) );
4277
4278 // data defined shadow blend mode?
4279 dataDefinedValEval( DDBlendMode, QgsPalLayerSettings::Property::ShadowBlendMode, exprVal, context.expressionContext() );
4280}
4281
4282// -------------
4283
4284
4286{
4287 switch ( layer->type() )
4288 {
4290 {
4291 const QgsVectorLayer *vl = qobject_cast< const QgsVectorLayer * >( layer );
4292 return ( vl->labelsEnabled() && vl->labeling() )
4293 || ( vl->diagramsEnabled() && vl->diagramRenderer() )
4294 || ( vl->renderer() && vl->renderer()->flags().testFlag( Qgis::FeatureRendererFlag::AffectsLabeling ) );
4295 }
4296
4298 {
4299 const QgsVectorTileLayer *vl = qobject_cast< const QgsVectorTileLayer * >( layer );
4300 if ( !vl->labeling() )
4301 return false;
4302
4303 if ( const QgsVectorTileBasicLabeling *labeling = dynamic_cast< const QgsVectorTileBasicLabeling *>( vl->labeling() ) )
4304 return !labeling->styles().empty();
4305
4306 return false;
4307 }
4308
4310 {
4311 const QgsMeshLayer *ml = qobject_cast< const QgsMeshLayer * >( layer );
4312 return ml->labeling() && ml->labelsEnabled();
4313 }
4314
4316 {
4317 const QgsRasterLayer *rl = qobject_cast< const QgsRasterLayer * >( layer );
4318 return rl->labeling() && rl->labelsEnabled();
4319 }
4320
4326 return false;
4327 }
4328 return false;
4329}
4330
4331
4332bool QgsPalLabeling::geometryRequiresPreparation( const QgsGeometry &geometry, QgsRenderContext &context, const QgsCoordinateTransform &ct, const QgsGeometry &clipGeometry, bool mergeLines )
4333{
4334 if ( geometry.isNull() )
4335 {
4336 return false;
4337 }
4338
4339 if ( geometry.type() == Qgis::GeometryType::Line && geometry.isMultipart() && mergeLines )
4340 {
4341 return true;
4342 }
4343
4344 //requires reprojection
4345 if ( ct.isValid() && !ct.isShortCircuited() )
4346 return true;
4347
4348 //requires rotation
4349 const QgsMapToPixel &m2p = context.mapToPixel();
4350 if ( !qgsDoubleNear( m2p.mapRotation(), 0 ) )
4351 return true;
4352
4353 //requires clip
4354 if ( !clipGeometry.isNull() && !clipGeometry.boundingBox().contains( geometry.boundingBox() ) )
4355 return true;
4356
4357 //requires fixing
4358 if ( geometry.type() == Qgis::GeometryType::Polygon && !geometry.isGeosValid() )
4359 return true;
4360
4361 return false;
4362}
4363
4364QStringList QgsPalLabeling::splitToLines( const QString &text, const QString &wrapCharacter, const int autoWrapLength, const bool useMaxLineLengthWhenAutoWrapping )
4365{
4366 QStringList multiLineSplit;
4367 if ( !wrapCharacter.isEmpty() && wrapCharacter != "\n"_L1 )
4368 {
4369 //wrap on both the wrapchr and new line characters
4370 const QStringList lines = text.split( wrapCharacter );
4371 for ( const QString &line : lines )
4372 {
4373 multiLineSplit.append( line.split( '\n' ) );
4374 }
4375 }
4376 else
4377 {
4378 multiLineSplit = text.split( '\n' );
4379 }
4380
4381 // apply auto wrapping to each manually created line
4382 if ( autoWrapLength != 0 )
4383 {
4384 QStringList autoWrappedLines;
4385 autoWrappedLines.reserve( multiLineSplit.count() );
4386 for ( const QString &line : std::as_const( multiLineSplit ) )
4387 {
4388 autoWrappedLines.append( QgsStringUtils::wordWrap( line, autoWrapLength, useMaxLineLengthWhenAutoWrapping ).split( '\n' ) );
4389 }
4390 multiLineSplit = autoWrappedLines;
4391 }
4392 return multiLineSplit;
4393}
4394
4395QStringList QgsPalLabeling::splitToGraphemes( const QString &text )
4396{
4397 QStringList graphemes;
4398 QTextBoundaryFinder boundaryFinder( QTextBoundaryFinder::Grapheme, text );
4399 int currentBoundary = -1;
4400 int previousBoundary = 0;
4401 while ( ( currentBoundary = boundaryFinder.toNextBoundary() ) > 0 )
4402 {
4403 graphemes << text.mid( previousBoundary, currentBoundary - previousBoundary );
4404 previousBoundary = currentBoundary;
4405 }
4406 return graphemes;
4407}
4408
4409QgsGeometry QgsPalLabeling::prepareGeometry( const QgsGeometry &geometry, QgsRenderContext &context, const QgsCoordinateTransform &ct, const QgsGeometry &clipGeometry, bool mergeLines )
4410{
4411 if ( geometry.isNull() )
4412 {
4413 return QgsGeometry();
4414 }
4415
4416 //don't modify the feature's geometry so that geometry based expressions keep working
4417 QgsGeometry geom = geometry;
4418
4419 if ( geom.type() == Qgis::GeometryType::Line && geom.isMultipart() && mergeLines )
4420 {
4421 geom = geom.mergeLines();
4422 }
4423
4424 //reproject the geometry if necessary
4425 if ( ct.isValid() && !ct.isShortCircuited() )
4426 {
4427 try
4428 {
4429 geom.transform( ct );
4430 }
4431 catch ( QgsCsException &cse )
4432 {
4433 Q_UNUSED( cse )
4434 QgsDebugMsgLevel( u"Ignoring feature due to transformation exception"_s, 4 );
4435 return QgsGeometry();
4436 }
4437 // geometry transforms may result in nan points, remove these
4438 geom.filterVertices( []( const QgsPoint & point )->bool
4439 {
4440 return std::isfinite( point.x() ) && std::isfinite( point.y() );
4441 } );
4443 {
4444 cp->removeInvalidRings();
4445 }
4447 {
4448 for ( int i = 0; i < ms->numGeometries(); ++i )
4449 {
4450 if ( QgsCurvePolygon *cp = qgsgeometry_cast< QgsCurvePolygon * >( ms->geometryN( i ) ) )
4451 cp->removeInvalidRings();
4452 }
4453 }
4454 }
4455
4456 // Rotate the geometry if needed
4457 const QgsMapToPixel &m2p = context.mapToPixel();
4458 if ( !qgsDoubleNear( m2p.mapRotation(), 0 ) )
4459 {
4460 QgsPointXY center = context.mapExtent().center();
4461 if ( geom.rotate( m2p.mapRotation(), center ) != Qgis::GeometryOperationResult::Success )
4462 {
4463 QgsDebugError( u"Error rotating geometry"_s.arg( geom.asWkt() ) );
4464 return QgsGeometry();
4465 }
4466 }
4467
4468 const bool mustClip = ( !clipGeometry.isNull() &&
4469 ( ( qgsDoubleNear( m2p.mapRotation(), 0 ) && !clipGeometry.boundingBox().contains( geom.boundingBox() ) )
4470 || ( !qgsDoubleNear( m2p.mapRotation(), 0 ) && !clipGeometry.contains( geom ) ) ) );
4471
4472 bool mustClipExact = false;
4473 if ( mustClip )
4474 {
4475 // nice and fast, but can result in invalid geometries. At least it will potentially strip out a bunch of unwanted vertices upfront!
4476 QgsGeometry clipGeom = geom.clipped( clipGeometry.boundingBox() );
4477 if ( clipGeom.isEmpty() )
4478 return QgsGeometry();
4479
4480 geom = clipGeom;
4481
4482 // we've now clipped against the BOUNDING BOX of clipGeometry. But if clipGeometry is an axis parallel rectangle, then there's no
4483 // need to do an exact (potentially costly) intersection clip as well!
4484 mustClipExact = !clipGeometry.isAxisParallelRectangle( 0.001 );
4485 }
4486
4487 // fix invalid polygons
4488 if ( geom.type() == Qgis::GeometryType::Polygon )
4489 {
4490 if ( geom.isMultipart() )
4491 {
4492 // important -- we need to treat ever part in isolation here. We can't test the validity of the whole geometry
4493 // at once, because touching parts would result in an invalid geometry, and buffering this "dissolves" the parts.
4494 // because the actual label engine treats parts as separate entities, we aren't bound by the usual "touching parts are invalid" rule
4495 // see https://github.com/qgis/QGIS/issues/26763
4496 QVector< QgsGeometry> parts;
4497 parts.reserve( qgsgeometry_cast< const QgsGeometryCollection * >( geom.constGet() )->numGeometries() );
4498 for ( auto it = geom.const_parts_begin(); it != geom.const_parts_end(); ++it )
4499 {
4500 QgsGeometry partGeom( ( *it )->clone() );
4501 if ( !partGeom.isGeosValid() )
4502 {
4503
4504 partGeom = partGeom.makeValid();
4505 }
4506 parts.append( partGeom );
4507 }
4508 geom = QgsGeometry::collectGeometry( parts );
4509 }
4510 else if ( !geom.isGeosValid() )
4511 {
4512
4513 QgsGeometry bufferGeom = geom.makeValid();
4514 if ( bufferGeom.isNull() )
4515 {
4516 QgsDebugError( u"Could not repair geometry: %1"_s.arg( bufferGeom.lastError() ) );
4517 return QgsGeometry();
4518 }
4519 geom = bufferGeom;
4520 }
4521 }
4522
4523 if ( mustClipExact )
4524 {
4525 // now do the real intersection against the actual clip geometry
4526 QgsGeometry clipGeom = geom.intersection( clipGeometry );
4527 if ( clipGeom.isEmpty() )
4528 {
4529 return QgsGeometry();
4530 }
4531 geom = clipGeom;
4532 }
4533
4534 return geom;
4535}
4536
4537bool QgsPalLabeling::checkMinimumSizeMM( const QgsRenderContext &context, const QgsGeometry &geom, double minSize )
4538{
4539 if ( minSize <= 0 )
4540 {
4541 return true;
4542 }
4543
4544 if ( geom.isNull() )
4545 {
4546 return false;
4547 }
4548
4549 Qgis::GeometryType featureType = geom.type();
4550 if ( featureType == Qgis::GeometryType::Point ) //minimum size does not apply to point features
4551 {
4552 return true;
4553 }
4554
4555 double mapUnitsPerMM = context.mapToPixel().mapUnitsPerPixel() * context.scaleFactor();
4556 if ( featureType == Qgis::GeometryType::Line )
4557 {
4558 double length = geom.length();
4559 if ( length >= 0.0 )
4560 {
4561 return ( length >= ( minSize * mapUnitsPerMM ) );
4562 }
4563 }
4564 else if ( featureType == Qgis::GeometryType::Polygon )
4565 {
4566 double area = geom.area();
4567 if ( area >= 0.0 )
4568 {
4569 return ( std::sqrt( area ) >= ( minSize * mapUnitsPerMM ) );
4570 }
4571 }
4572 return true; //should never be reached. Return true in this case to label such geometries anyway.
4573}
4574
4575
4576void QgsPalLabeling::dataDefinedTextStyle( QgsPalLayerSettings &tmpLyr,
4577 const QMap< QgsPalLayerSettings::Property, QVariant > &ddValues )
4578{
4579 QgsTextFormat format = tmpLyr.format();
4580 bool changed = false;
4581
4582 //font color
4583 if ( ddValues.contains( QgsPalLayerSettings::Property::Color ) )
4584 {
4585 QVariant ddColor = ddValues.value( QgsPalLayerSettings::Property::Color );
4586 format.setColor( ddColor.value<QColor>() );
4587 changed = true;
4588 }
4589
4590 //font transparency
4591 if ( ddValues.contains( QgsPalLayerSettings::Property::FontOpacity ) )
4592 {
4593 format.setOpacity( ddValues.value( QgsPalLayerSettings::Property::FontOpacity ).toDouble() / 100.0 );
4594 changed = true;
4595 }
4596
4597 //font blend mode
4598 if ( ddValues.contains( QgsPalLayerSettings::Property::FontBlendMode ) )
4599 {
4600 format.setBlendMode( static_cast< QPainter::CompositionMode >( ddValues.value( QgsPalLayerSettings::Property::FontBlendMode ).toInt() ) );
4601 changed = true;
4602 }
4603
4604 if ( changed )
4605 {
4606 tmpLyr.setFormat( format );
4607 }
4608}
4609
4610void QgsPalLabeling::dataDefinedTextFormatting( QgsPalLayerSettings &tmpLyr,
4611 const QMap< QgsPalLayerSettings::Property, QVariant > &ddValues )
4612{
4613 if ( ddValues.contains( QgsPalLayerSettings::Property::MultiLineWrapChar ) )
4614 {
4615 tmpLyr.wrapChar = ddValues.value( QgsPalLayerSettings::Property::MultiLineWrapChar ).toString();
4616 }
4617
4618 if ( ddValues.contains( QgsPalLayerSettings::Property::AutoWrapLength ) )
4619 {
4620 tmpLyr.autoWrapLength = ddValues.value( QgsPalLayerSettings::Property::AutoWrapLength ).toInt();
4621 }
4622
4623 if ( ddValues.contains( QgsPalLayerSettings::Property::MultiLineHeight ) )
4624 {
4625 QgsTextFormat format = tmpLyr.format();
4626 format.setLineHeight( ddValues.value( QgsPalLayerSettings::Property::MultiLineHeight ).toDouble() );
4627 tmpLyr.setFormat( format );
4628 }
4629
4630 if ( ddValues.contains( QgsPalLayerSettings::Property::TabStopDistance ) )
4631 {
4632 QgsTextFormat format = tmpLyr.format();
4633 QList<QgsTextFormat::Tab> tabPositions;
4634 if ( ddValues.value( QgsPalLayerSettings::Property::TabStopDistance ).userType() == QMetaType::Type::QVariantList )
4635 {
4636 const QVariantList parts = ddValues.value( QgsPalLayerSettings::Property::TabStopDistance ).toList();
4637 for ( const QVariant &part : parts )
4638 {
4639 tabPositions.append( QgsTextFormat::Tab( part.toDouble() ) );
4640 }
4641 format.setTabPositions( tabPositions );
4642 }
4643 else if ( ddValues.value( QgsPalLayerSettings::Property::TabStopDistance ).userType() == QMetaType::Type::QStringList )
4644 {
4645 const QStringList parts = ddValues.value( QgsPalLayerSettings::Property::TabStopDistance ).toStringList();
4646 for ( const QString &part : parts )
4647 {
4648 tabPositions.append( QgsTextFormat::Tab( part.toDouble() ) );
4649 }
4650 format.setTabPositions( tabPositions );
4651 }
4652 else
4653 {
4654 format.setTabPositions( tabPositions );
4655 format.setTabStopDistance( ddValues.value( QgsPalLayerSettings::Property::TabStopDistance ).toDouble() );
4656 }
4657 tmpLyr.setFormat( format );
4658 }
4659
4660 if ( ddValues.contains( QgsPalLayerSettings::Property::MultiLineAlignment ) )
4661 {
4662 tmpLyr.multilineAlign = static_cast< Qgis::LabelMultiLineAlignment >( ddValues.value( QgsPalLayerSettings::Property::MultiLineAlignment ).toInt() );
4663 }
4664
4665 if ( ddValues.contains( QgsPalLayerSettings::Property::TextOrientation ) )
4666 {
4667 QgsTextFormat format = tmpLyr.format();
4669 tmpLyr.setFormat( format );
4670 }
4671
4672 if ( ddValues.contains( QgsPalLayerSettings::Property::DirSymbDraw ) )
4673 {
4675 }
4676
4677 if ( tmpLyr.lineSettings().addDirectionSymbol() )
4678 {
4679
4680 if ( ddValues.contains( QgsPalLayerSettings::Property::DirSymbLeft ) )
4681 {
4683 }
4684 if ( ddValues.contains( QgsPalLayerSettings::Property::DirSymbRight ) )
4685 {
4687 }
4688
4689 if ( ddValues.contains( QgsPalLayerSettings::Property::DirSymbPlacement ) )
4690 {
4692 }
4693
4694 if ( ddValues.contains( QgsPalLayerSettings::Property::DirSymbReverse ) )
4695 {
4697 }
4698 }
4699}
4700
4701void QgsPalLabeling::dataDefinedTextBuffer( QgsPalLayerSettings &tmpLyr,
4702 const QMap< QgsPalLayerSettings::Property, QVariant > &ddValues )
4703{
4704 QgsTextBufferSettings buffer = tmpLyr.format().buffer();
4705 bool changed = false;
4706
4707 //buffer draw
4708 if ( ddValues.contains( QgsPalLayerSettings::Property::BufferDraw ) )
4709 {
4710 buffer.setEnabled( ddValues.value( QgsPalLayerSettings::Property::BufferDraw ).toBool() );
4711 changed = true;
4712 }
4713
4714 if ( !buffer.enabled() )
4715 {
4716 if ( changed )
4717 {
4718 QgsTextFormat format = tmpLyr.format();
4719 format.setBuffer( buffer );
4720 tmpLyr.setFormat( format );
4721 }
4722
4723 // tmpLyr.bufferSize > 0.0 && tmpLyr.bufferTransp < 100 figured in during evaluation
4724 return; // don't continue looking for unused values
4725 }
4726
4727 //buffer size
4728 if ( ddValues.contains( QgsPalLayerSettings::Property::BufferSize ) )
4729 {
4730 buffer.setSize( ddValues.value( QgsPalLayerSettings::Property::BufferSize ).toDouble() );
4731 changed = true;
4732 }
4733
4734 //buffer opacity
4735 if ( ddValues.contains( QgsPalLayerSettings::Property::BufferOpacity ) )
4736 {
4737 buffer.setOpacity( ddValues.value( QgsPalLayerSettings::Property::BufferOpacity ).toDouble() / 100.0 );
4738 changed = true;
4739 }
4740
4741 //buffer size units
4742 if ( ddValues.contains( QgsPalLayerSettings::Property::BufferUnit ) )
4743 {
4744 Qgis::RenderUnit bufunit = static_cast< Qgis::RenderUnit >( ddValues.value( QgsPalLayerSettings::Property::BufferUnit ).toInt() );
4745 buffer.setSizeUnit( bufunit );
4746 changed = true;
4747 }
4748
4749 //buffer color
4750 if ( ddValues.contains( QgsPalLayerSettings::Property::BufferColor ) )
4751 {
4752 QVariant ddColor = ddValues.value( QgsPalLayerSettings::Property::BufferColor );
4753 buffer.setColor( ddColor.value<QColor>() );
4754 changed = true;
4755 }
4756
4757 //buffer pen join style
4758 if ( ddValues.contains( QgsPalLayerSettings::Property::BufferJoinStyle ) )
4759 {
4760 buffer.setJoinStyle( static_cast< Qt::PenJoinStyle >( ddValues.value( QgsPalLayerSettings::Property::BufferJoinStyle ).toInt() ) );
4761 changed = true;
4762 }
4763
4764 //buffer blend mode
4765 if ( ddValues.contains( QgsPalLayerSettings::Property::BufferBlendMode ) )
4766 {
4767 buffer.setBlendMode( static_cast< QPainter::CompositionMode >( ddValues.value( QgsPalLayerSettings::Property::BufferBlendMode ).toInt() ) );
4768 changed = true;
4769 }
4770
4771 if ( changed )
4772 {
4773 QgsTextFormat format = tmpLyr.format();
4774 format.setBuffer( buffer );
4775 tmpLyr.setFormat( format );
4776 }
4777}
4778
4779void QgsPalLabeling::dataDefinedTextMask( QgsPalLayerSettings &tmpLyr,
4780 const QMap< QgsPalLayerSettings::Property, QVariant > &ddValues )
4781{
4782 if ( ddValues.isEmpty() )
4783 return;
4784
4785 QgsTextMaskSettings mask = tmpLyr.format().mask();
4786 bool changed = false;
4787
4788 // enabled ?
4789 if ( ddValues.contains( QgsPalLayerSettings::Property::MaskEnabled ) )
4790 {
4791 mask.setEnabled( ddValues.value( QgsPalLayerSettings::Property::MaskEnabled ).toBool() );
4792 changed = true;
4793 }
4794
4795 if ( !mask.enabled() )
4796 {
4797 if ( changed )
4798 {
4799 QgsTextFormat format = tmpLyr.format();
4800 format.setMask( mask );
4801 tmpLyr.setFormat( format );
4802 }
4803
4804 // tmpLyr.bufferSize > 0.0 && tmpLyr.bufferTransp < 100 figured in during evaluation
4805 return; // don't continue looking for unused values
4806 }
4807
4808 // buffer size
4809 if ( ddValues.contains( QgsPalLayerSettings::Property::MaskBufferSize ) )
4810 {
4811 mask.setSize( ddValues.value( QgsPalLayerSettings::Property::MaskBufferSize ).toDouble() );
4812 changed = true;
4813 }
4814
4815 // opacity
4816 if ( ddValues.contains( QgsPalLayerSettings::Property::MaskOpacity ) )
4817 {
4818 mask.setOpacity( ddValues.value( QgsPalLayerSettings::Property::MaskOpacity ).toDouble() / 100.0 );
4819 changed = true;
4820 }
4821
4822 // buffer size units
4823 if ( ddValues.contains( QgsPalLayerSettings::Property::MaskBufferUnit ) )
4824 {
4825 Qgis::RenderUnit bufunit = static_cast< Qgis::RenderUnit >( ddValues.value( QgsPalLayerSettings::Property::MaskBufferUnit ).toInt() );
4826 mask.setSizeUnit( bufunit );
4827 changed = true;
4828 }
4829
4830 // pen join style
4831 if ( ddValues.contains( QgsPalLayerSettings::Property::MaskJoinStyle ) )
4832 {
4833 mask.setJoinStyle( static_cast< Qt::PenJoinStyle >( ddValues.value( QgsPalLayerSettings::Property::MaskJoinStyle ).toInt() ) );
4834 changed = true;
4835 }
4836
4837 if ( changed )
4838 {
4839 QgsTextFormat format = tmpLyr.format();
4840 format.setMask( mask );
4841 tmpLyr.setFormat( format );
4842 }
4843}
4844
4845void QgsPalLabeling::dataDefinedShapeBackground( QgsPalLayerSettings &tmpLyr,
4846 const QMap< QgsPalLayerSettings::Property, QVariant > &ddValues )
4847{
4848 QgsTextBackgroundSettings background = tmpLyr.format().background();
4849 bool changed = false;
4850
4851 //shape draw
4852 if ( ddValues.contains( QgsPalLayerSettings::Property::ShapeDraw ) )
4853 {
4854 background.setEnabled( ddValues.value( QgsPalLayerSettings::Property::ShapeDraw ).toBool() );
4855 changed = true;
4856 }
4857
4858 if ( !background.enabled() )
4859 {
4860 if ( changed )
4861 {
4862 QgsTextFormat format = tmpLyr.format();
4863 format.setBackground( background );
4864 tmpLyr.setFormat( format );
4865 }
4866 return; // don't continue looking for unused values
4867 }
4868
4869 if ( ddValues.contains( QgsPalLayerSettings::Property::ShapeKind ) )
4870 {
4871 background.setType( static_cast< QgsTextBackgroundSettings::ShapeType >( ddValues.value( QgsPalLayerSettings::Property::ShapeKind ).toInt() ) );
4872 changed = true;
4873 }
4874
4875 if ( ddValues.contains( QgsPalLayerSettings::Property::ShapeSVGFile ) )
4876 {
4877 background.setSvgFile( ddValues.value( QgsPalLayerSettings::Property::ShapeSVGFile ).toString() );
4878 changed = true;
4879 }
4880
4881 if ( ddValues.contains( QgsPalLayerSettings::Property::ShapeSizeType ) )
4882 {
4883 background.setSizeType( static_cast< QgsTextBackgroundSettings::SizeType >( ddValues.value( QgsPalLayerSettings::Property::ShapeSizeType ).toInt() ) );
4884 changed = true;
4885 }
4886
4887 if ( ddValues.contains( QgsPalLayerSettings::Property::ShapeSizeX ) )
4888 {
4889 QSizeF size = background.size();
4890 size.setWidth( ddValues.value( QgsPalLayerSettings::Property::ShapeSizeX ).toDouble() );
4891 background.setSize( size );
4892 changed = true;
4893 }
4894 if ( ddValues.contains( QgsPalLayerSettings::Property::ShapeSizeY ) )
4895 {
4896 QSizeF size = background.size();
4897 size.setHeight( ddValues.value( QgsPalLayerSettings::Property::ShapeSizeY ).toDouble() );
4898 background.setSize( size );
4899 changed = true;
4900 }
4901
4902 if ( ddValues.contains( QgsPalLayerSettings::Property::ShapeSizeUnits ) )
4903 {
4904 background.setSizeUnit( static_cast< Qgis::RenderUnit >( ddValues.value( QgsPalLayerSettings::Property::ShapeSizeUnits ).toInt() ) );
4905 changed = true;
4906 }
4907
4908 if ( ddValues.contains( QgsPalLayerSettings::Property::ShapeRotationType ) )
4909 {
4911 changed = true;
4912 }
4913
4914 if ( ddValues.contains( QgsPalLayerSettings::Property::ShapeRotation ) )
4915 {
4916 background.setRotation( ddValues.value( QgsPalLayerSettings::Property::ShapeRotation ).toDouble() );
4917 changed = true;
4918 }
4919
4920 if ( ddValues.contains( QgsPalLayerSettings::Property::ShapeOffset ) )
4921 {
4922 background.setOffset( ddValues.value( QgsPalLayerSettings::Property::ShapeOffset ).toPointF() );
4923 changed = true;
4924 }
4925
4926 if ( ddValues.contains( QgsPalLayerSettings::Property::ShapeOffsetUnits ) )
4927 {
4928 background.setOffsetUnit( static_cast< Qgis::RenderUnit >( ddValues.value( QgsPalLayerSettings::Property::ShapeOffsetUnits ).toInt() ) );
4929 changed = true;
4930 }
4931
4932 if ( ddValues.contains( QgsPalLayerSettings::Property::ShapeRadii ) )
4933 {
4934 background.setRadii( ddValues.value( QgsPalLayerSettings::Property::ShapeRadii ).toSizeF() );
4935 changed = true;
4936 }
4937
4938 if ( ddValues.contains( QgsPalLayerSettings::Property::ShapeRadiiUnits ) )
4939 {
4940 background.setRadiiUnit( static_cast< Qgis::RenderUnit >( ddValues.value( QgsPalLayerSettings::Property::ShapeRadiiUnits ).toInt() ) );
4941 changed = true;
4942 }
4943
4944 if ( ddValues.contains( QgsPalLayerSettings::Property::ShapeBlendMode ) )
4945 {
4946 background.setBlendMode( static_cast< QPainter::CompositionMode >( ddValues.value( QgsPalLayerSettings::Property::ShapeBlendMode ).toInt() ) );
4947 changed = true;
4948 }
4949
4950 if ( ddValues.contains( QgsPalLayerSettings::Property::ShapeFillColor ) )
4951 {
4952 QVariant ddColor = ddValues.value( QgsPalLayerSettings::Property::ShapeFillColor );
4953 background.setFillColor( ddColor.value<QColor>() );
4954 changed = true;
4955 }
4956
4957 if ( ddValues.contains( QgsPalLayerSettings::Property::ShapeStrokeColor ) )
4958 {
4959 QVariant ddColor = ddValues.value( QgsPalLayerSettings::Property::ShapeStrokeColor );
4960 background.setStrokeColor( ddColor.value<QColor>() );
4961 changed = true;
4962 }
4963
4964 if ( ddValues.contains( QgsPalLayerSettings::Property::ShapeOpacity ) )
4965 {
4966 background.setOpacity( ddValues.value( QgsPalLayerSettings::Property::ShapeOpacity ).toDouble() / 100.0 );
4967 changed = true;
4968 }
4969
4970 if ( ddValues.contains( QgsPalLayerSettings::Property::ShapeStrokeWidth ) )
4971 {
4972 background.setStrokeWidth( ddValues.value( QgsPalLayerSettings::Property::ShapeStrokeWidth ).toDouble() );
4973 changed = true;
4974 }
4975
4977 {
4978 background.setStrokeWidthUnit( static_cast< Qgis::RenderUnit >( ddValues.value( QgsPalLayerSettings::Property::ShapeStrokeWidthUnits ).toInt() ) );
4979 changed = true;
4980 }
4981
4982 if ( ddValues.contains( QgsPalLayerSettings::Property::ShapeJoinStyle ) )
4983 {
4984 background.setJoinStyle( static_cast< Qt::PenJoinStyle >( ddValues.value( QgsPalLayerSettings::Property::ShapeJoinStyle ).toInt() ) );
4985 changed = true;
4986 }
4987
4988 if ( changed )
4989 {
4990 QgsTextFormat format = tmpLyr.format();
4991 format.setBackground( background );
4992 tmpLyr.setFormat( format );
4993 }
4994}
4995
4996void QgsPalLabeling::dataDefinedDropShadow( QgsPalLayerSettings &tmpLyr,
4997 const QMap< QgsPalLayerSettings::Property, QVariant > &ddValues )
4998{
4999 QgsTextShadowSettings shadow = tmpLyr.format().shadow();
5000 bool changed = false;
5001
5002 //shadow draw
5003 if ( ddValues.contains( QgsPalLayerSettings::Property::ShadowDraw ) )
5004 {
5005 shadow.setEnabled( ddValues.value( QgsPalLayerSettings::Property::ShadowDraw ).toBool() );
5006 changed = true;
5007 }
5008
5009 if ( !shadow.enabled() )
5010 {
5011 if ( changed )
5012 {
5013 QgsTextFormat format = tmpLyr.format();
5014 format.setShadow( shadow );
5015 tmpLyr.setFormat( format );
5016 }
5017 return; // don't continue looking for unused values
5018 }
5019
5020 if ( ddValues.contains( QgsPalLayerSettings::Property::ShadowUnder ) )
5021 {
5023 changed = true;
5024 }
5025
5026 if ( ddValues.contains( QgsPalLayerSettings::Property::ShadowOffsetAngle ) )
5027 {
5028 shadow.setOffsetAngle( ddValues.value( QgsPalLayerSettings::Property::ShadowOffsetAngle ).toInt() );
5029 changed = true;
5030 }
5031
5032 if ( ddValues.contains( QgsPalLayerSettings::Property::ShadowOffsetDist ) )
5033 {
5034 shadow.setOffsetDistance( ddValues.value( QgsPalLayerSettings::Property::ShadowOffsetDist ).toDouble() );
5035 changed = true;
5036 }
5037
5038 if ( ddValues.contains( QgsPalLayerSettings::Property::ShadowOffsetUnits ) )
5039 {
5040 shadow.setOffsetUnit( static_cast< Qgis::RenderUnit >( ddValues.value( QgsPalLayerSettings::Property::ShadowOffsetUnits ).toInt() ) );
5041 changed = true;
5042 }
5043
5044 if ( ddValues.contains( QgsPalLayerSettings::Property::ShadowRadius ) )
5045 {
5046 shadow.setBlurRadius( ddValues.value( QgsPalLayerSettings::Property::ShadowRadius ).toDouble() );
5047 changed = true;
5048 }
5049
5050 if ( ddValues.contains( QgsPalLayerSettings::Property::ShadowRadiusUnits ) )
5051 {
5052 shadow.setBlurRadiusUnit( static_cast< Qgis::RenderUnit >( ddValues.value( QgsPalLayerSettings::Property::ShadowRadiusUnits ).toInt() ) );
5053 changed = true;
5054 }
5055
5056 if ( ddValues.contains( QgsPalLayerSettings::Property::ShadowColor ) )
5057 {
5058 QVariant ddColor = ddValues.value( QgsPalLayerSettings::Property::ShadowColor );
5059 shadow.setColor( ddColor.value<QColor>() );
5060 changed = true;
5061 }
5062
5063 if ( ddValues.contains( QgsPalLayerSettings::Property::ShadowOpacity ) )
5064 {
5065 shadow.setOpacity( ddValues.value( QgsPalLayerSettings::Property::ShadowOpacity ).toDouble() / 100.0 );
5066 changed = true;
5067 }
5068
5069 if ( ddValues.contains( QgsPalLayerSettings::Property::ShadowScale ) )
5070 {
5071 shadow.setScale( ddValues.value( QgsPalLayerSettings::Property::ShadowScale ).toInt() );
5072 changed = true;
5073 }
5074
5075
5076 if ( ddValues.contains( QgsPalLayerSettings::Property::ShadowBlendMode ) )
5077 {
5078 shadow.setBlendMode( static_cast< QPainter::CompositionMode >( ddValues.value( QgsPalLayerSettings::Property::ShadowBlendMode ).toInt() ) );
5079 changed = true;
5080 }
5081
5082 if ( changed )
5083 {
5084 QgsTextFormat format = tmpLyr.format();
5085 format.setShadow( shadow );
5086 tmpLyr.setFormat( format );
5087 }
5088}
5089
5091{
5092 //set both the mime color data, and the text (format settings).
5093
5094 QMimeData *mimeData = new QMimeData;
5095 mimeData->setColorData( QVariant( format().color() ) );
5096
5097 QgsReadWriteContext rwContext;
5098 QDomDocument textDoc;
5099 QDomElement textElem = writeXml( textDoc, rwContext );
5100 textDoc.appendChild( textElem );
5101 mimeData->setData( "application/qgis.labelsettings"_L1, textDoc.toString().toUtf8() );
5102
5103 return mimeData;
5104}
5105
5107{
5108 if ( ok )
5109 *ok = false;
5110 QgsPalLayerSettings settings;
5111 if ( !data || !data->hasFormat( "application/qgis.labelsettings"_L1 ) )
5112 return settings;
5113
5114 QString text = QString::fromUtf8( data->data( "application/qgis.labelsettings"_L1 ) );
5115 if ( !text.isEmpty() )
5116 {
5117 QDomDocument doc;
5118 QDomElement elem;
5119 QgsReadWriteContext rwContext;
5120
5121 if ( doc.setContent( text ) )
5122 {
5123 elem = doc.documentElement();
5124
5125 settings.readXml( elem, rwContext );
5126 if ( ok )
5127 *ok = true;
5128 return settings;
5129 }
5130 }
5131 return settings;
5132}
@ 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:5210
@ Degrees
Degrees.
Definition qgis.h:5211
@ 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:3448
@ AllSmallCaps
Force all characters to small caps.
Definition qgis.h:3456
@ MixedCase
Mixed case, ie no change.
Definition qgis.h:3449
@ AllLowercase
Convert all characters to lowercase.
Definition qgis.h:3451
@ TitleCase
Simple title case conversion - does not fully grammatically parse the text and uses simple rules only...
Definition qgis.h:3454
@ SmallCaps
Mixed case small caps.
Definition qgis.h:3453
@ ForceFirstLetterToCapital
Convert just the first letter of each word to uppercase, leave the rest untouched.
Definition qgis.h:3452
@ AllUppercase
Convert all characters to uppercase.
Definition qgis.h:3450
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:5255
@ Percentage
Percentage of another measurement (e.g., canvas size, feature size).
Definition qgis.h:5259
@ Millimeters
Millimeters.
Definition qgis.h:5256
@ Points
Points (e.g., for font sizes).
Definition qgis.h:5260
@ MapUnits
Map units.
Definition qgis.h:5257
@ Pixels
Pixels.
Definition qgis.h:5258
@ 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:6499
@ 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:7110
#define Q_NOWARN_DEPRECATED_POP
Definition qgis.h:7451
QString qgsEnumValueToKey(const T &value, bool *returnOk=nullptr)
Returns the value for the given key of an enum.
Definition qgis.h:7091
#define Q_NOWARN_DEPRECATED_PUSH
Definition qgis.h:7450
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference).
Definition qgis.h:6900
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.