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