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