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