QGIS API Documentation 3.37.0-Master (614ebf5f6b2)
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
27#include "pal/labelposition.h"
28
29#include <cmath>
30
31#include <QApplication>
32#include <QByteArray>
33#include <QString>
34#include <QFontMetrics>
35#include <QTime>
36#include <QPainter>
37#include <QScreen>
38#include <QWidget>
39#include <QTextBoundaryFinder>
40
41#include "qgsfontutils.h"
42#include "qgsexpression.h"
43#include "qgslabelingengine.h"
45#include "qgsmultisurface.h"
46#include "qgslogger.h"
47#include "qgsvectorlayer.h"
48#include "qgsgeometry.h"
50#include "qgsproperty.h"
51#include "qgssymbollayerutils.h"
53#include "qgscurvepolygon.h"
54#include "qgsmessagelog.h"
56#include "callouts/qgscallout.h"
58#include "qgsvectortilelayer.h"
60#include "qgsfontmanager.h"
61#include "qgsvariantutils.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", QObject::tr( "Tab stop distance" ), QgsPropertyDefinition::DoublePositive, 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, QgsTextFormat *specifiedFormat, QgsTextDocument *document, QgsTextDocumentMetrics *documentMetrics, QRectF *outerBounds )
1546{
1547 if ( !fm || !f )
1548 {
1549 return;
1550 }
1551
1552 const QgsTextFormat *format = specifiedFormat ? specifiedFormat : &mFormat;
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 = format->lineHeight();
1569 Qgis::TextOrientation orientation = format->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 if ( document )
1709 {
1710 document->splitLines( wrapchr, evalAutoWrapLength, useMaxLineLengthForAutoWrap );
1711
1712 *documentMetrics = QgsTextDocumentMetrics::calculateMetrics( *document, *format, *rc );
1713 const QSizeF size = documentMetrics->documentSize( Qgis::TextLayoutMode::Labeling, orientation != Qgis::TextOrientation::RotationBased ? orientation : Qgis::TextOrientation::Horizontal );
1714 w = size.width();
1715 h = size.height();
1716
1717 if ( orientation == Qgis::TextOrientation::RotationBased )
1718 {
1719 const QSizeF rotatedSize = documentMetrics->documentSize( Qgis::TextLayoutMode::Labeling, Qgis::TextOrientation::Vertical );
1720 rh = rotatedSize.width();
1721 rw = rotatedSize.height();
1722 }
1723 }
1724 else
1725 {
1726 const QStringList multiLineSplit = QgsPalLabeling::splitToLines( textCopy, wrapchr, evalAutoWrapLength, useMaxLineLengthForAutoWrap );
1727 const int lines = multiLineSplit.size();
1728
1729 const double lineHeightPainterUnits = rc->convertToPainterUnits( format->lineHeight(), format->lineHeightUnit() );
1730
1731 switch ( orientation )
1732 {
1734 {
1735 h += fm->height() + static_cast< double >( ( lines - 1 ) * ( format->lineHeightUnit() == Qgis::RenderUnit::Percentage ? ( labelHeight * multilineH ) : lineHeightPainterUnits ) );
1736
1737 for ( const QString &line : std::as_const( multiLineSplit ) )
1738 {
1739 w = std::max( w, fm->horizontalAdvance( line ) );
1740 }
1741 break;
1742 }
1743
1745 {
1746 double letterSpacing = format->scaledFont( *context ).letterSpacing();
1747 double labelWidth = fm->maxWidth();
1748 w = labelWidth + ( lines - 1 ) * ( format->lineHeightUnit() == Qgis::RenderUnit::Percentage ? ( labelWidth * multilineH ) : lineHeightPainterUnits );
1749
1750 int maxLineLength = 0;
1751 for ( const QString &line : std::as_const( multiLineSplit ) )
1752 {
1753 maxLineLength = std::max( maxLineLength, static_cast<int>( line.length() ) );
1754 }
1755 h = fm->ascent() * maxLineLength + ( maxLineLength - 1 ) * letterSpacing;
1756 break;
1757 }
1758
1760 {
1761 double widthHorizontal = 0.0;
1762 for ( const QString &line : std::as_const( multiLineSplit ) )
1763 {
1764 widthHorizontal = std::max( w, fm->horizontalAdvance( line ) );
1765 }
1766
1767 double widthVertical = 0.0;
1768 double letterSpacing = format->scaledFont( *context ).letterSpacing();
1769 double labelWidth = fm->maxWidth();
1770 widthVertical = labelWidth + ( lines - 1 ) * ( format->lineHeightUnit() == Qgis::RenderUnit::Percentage ? ( labelWidth * multilineH ) : lineHeightPainterUnits );
1771
1772 double heightHorizontal = 0.0;
1773 heightHorizontal += fm->height() + static_cast< double >( ( lines - 1 ) * ( format->lineHeightUnit() == Qgis::RenderUnit::Percentage ? ( labelHeight * multilineH ) : lineHeightPainterUnits ) );
1774
1775 double heightVertical = 0.0;
1776 int maxLineLength = 0;
1777 for ( const QString &line : std::as_const( multiLineSplit ) )
1778 {
1779 maxLineLength = std::max( maxLineLength, static_cast<int>( line.length() ) );
1780 }
1781 heightVertical = fm->ascent() * maxLineLength + ( maxLineLength - 1 ) * letterSpacing;
1782
1783 w = widthHorizontal;
1784 rw = heightVertical;
1785 h = heightHorizontal;
1786 rh = widthVertical;
1787 break;
1788 }
1789 }
1790 }
1791
1792#if 0 // XXX strk
1793 QgsPointXY ptSize = xform->toMapCoordinatesF( w, h );
1794 labelX = std::fabs( ptSize.x() - ptZero.x() );
1795 labelY = std::fabs( ptSize.y() - ptZero.y() );
1796#else
1797 double uPP = xform->mapUnitsPerPixel();
1798 labelX = w * uPP;
1799 labelY = h * uPP;
1800 if ( rotatedLabelX && rotatedLabelY )
1801 {
1802 *rotatedLabelX = rw * uPP;
1803 *rotatedLabelY = rh * uPP;
1804 }
1805#endif
1806
1807 if ( outerBounds && documentMetrics )
1808 {
1809 const QRectF outerBoundsPixels = documentMetrics->outerBounds( Qgis::TextLayoutMode::Labeling, orientation );
1810
1811 *outerBounds = QRectF( outerBoundsPixels.left() * uPP,
1812 outerBoundsPixels.top() * uPP,
1813 outerBoundsPixels.width() * uPP,
1814 outerBoundsPixels.height() * uPP );
1815 }
1816}
1817
1819{
1820 registerFeatureWithDetails( f, context, QgsGeometry(), nullptr );
1821}
1822
1823std::unique_ptr<QgsLabelFeature> QgsPalLayerSettings::registerFeatureWithDetails( const QgsFeature &f, QgsRenderContext &context, QgsGeometry obstacleGeometry, const QgsSymbol *symbol )
1824{
1825 QVariant exprVal; // value() is repeatedly nulled on data defined evaluation and replaced when successful
1826 mCurFeat = &f;
1827
1828 // data defined is obstacle? calculate this first, to avoid wasting time working with obstacles we don't require
1829 bool isObstacle = mObstacleSettings.isObstacle();
1830 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::IsObstacle ) )
1831 isObstacle = mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::Property::IsObstacle, context.expressionContext(), isObstacle ); // default to layer default
1832
1833 if ( !drawLabels )
1834 {
1835 if ( isObstacle )
1836 {
1837 return registerObstacleFeature( f, context, obstacleGeometry );
1838 }
1839 else
1840 {
1841 return nullptr;
1842 }
1843 }
1844
1845 QgsFeature feature = f;
1847 {
1848 const QgsGeometry geometry = mGeometryGeneratorExpression.evaluate( &context.expressionContext() ).value<QgsGeometry>();
1849 if ( mGeometryGeneratorExpression.hasEvalError() )
1850 QgsMessageLog::logMessage( mGeometryGeneratorExpression.evalErrorString(), QObject::tr( "Labeling" ) );
1851
1852 if ( obstacleGeometry.isNull() )
1853 {
1854 // if an explicit obstacle geometry hasn't been set, we must always use the original feature geometry
1855 // as the obstacle -- because we want to use the geometry which was used to render the symbology
1856 // for the feature as the obstacle for other layers' labels, NOT the generated geometry which is used
1857 // only to place labels for this layer.
1858 obstacleGeometry = f.geometry();
1859 }
1860
1861 feature.setGeometry( geometry );
1862 }
1863
1864 // store data defined-derived values for later adding to label feature for use during rendering
1865 dataDefinedValues.clear();
1866
1867 // data defined show label? defaults to show label if not set
1868 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::Show ) )
1869 {
1871 if ( !mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::Property::Show, context.expressionContext(), true ) )
1872 {
1873 return nullptr;
1874 }
1875 }
1876
1877 // data defined scale visibility?
1878 bool useScaleVisibility = scaleVisibility;
1879 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::ScaleVisibility ) )
1880 useScaleVisibility = mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::Property::ScaleVisibility, context.expressionContext(), scaleVisibility );
1881
1882 if ( useScaleVisibility )
1883 {
1884 // data defined min scale?
1885 double maxScale = maximumScale;
1886 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::MaximumScale ) )
1887 {
1889 maxScale = mDataDefinedProperties.valueAsDouble( QgsPalLayerSettings::Property::MaximumScale, context.expressionContext(), maxScale );
1890 }
1891
1892 // scales closer than 1:1
1893 if ( maxScale < 0 )
1894 {
1895 maxScale = 1 / std::fabs( maxScale );
1896 }
1897
1898 if ( !qgsDoubleNear( maxScale, 0.0 ) && context.rendererScale() < maxScale )
1899 {
1900 return nullptr;
1901 }
1902
1903 // data defined min scale?
1904 double minScale = minimumScale;
1905 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::MinimumScale ) )
1906 {
1908 minScale = mDataDefinedProperties.valueAsDouble( QgsPalLayerSettings::Property::MinimumScale, context.expressionContext(), minScale );
1909 }
1910
1911 // scales closer than 1:1
1912 if ( minScale < 0 )
1913 {
1914 minScale = 1 / std::fabs( minScale );
1915 }
1916
1917 if ( !qgsDoubleNear( minScale, 0.0 ) && context.rendererScale() > minScale )
1918 {
1919 return nullptr;
1920 }
1921 }
1922
1923 QgsTextFormat evaluatedFormat = mFormat;
1924
1925 QFont labelFont = evaluatedFormat.font();
1926 // labelFont will be added to label feature for use during label painting
1927
1928 // data defined font units?
1929 Qgis::RenderUnit fontunits = evaluatedFormat.sizeUnit();
1930 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Property::FontSizeUnit, context.expressionContext() );
1931 if ( !QgsVariantUtils::isNull( exprVal ) )
1932 {
1933 QString units = exprVal.toString();
1934 if ( !units.isEmpty() )
1935 {
1936 bool ok;
1938 if ( ok )
1939 fontunits = res;
1940 }
1941 }
1942
1943 //data defined label size?
1944 double fontSize = evaluatedFormat.size();
1945 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::Size ) )
1946 {
1947 context.expressionContext().setOriginalValueVariable( fontSize );
1948 fontSize = mDataDefinedProperties.valueAsDouble( QgsPalLayerSettings::Property::Size, context.expressionContext(), fontSize );
1949 }
1950 if ( fontSize <= 0.0 )
1951 {
1952 return nullptr;
1953 }
1954
1955 int fontPixelSize = QgsTextRenderer::sizeToPixel( fontSize, context, fontunits, evaluatedFormat.sizeMapUnitScale() );
1956 // don't try to show font sizes less than 1 pixel (Qt complains)
1957 if ( fontPixelSize < 1 )
1958 {
1959 return nullptr;
1960 }
1961 labelFont.setPixelSize( fontPixelSize );
1962
1963 // NOTE: labelFont now always has pixelSize set, so pointSize or pointSizeF might return -1
1964
1965 // defined 'minimum/maximum pixel font size'?
1966 if ( fontunits == Qgis::RenderUnit::MapUnits )
1967 {
1969 {
1970 int fontMinPixel = mDataDefinedProperties.valueAsInt( QgsPalLayerSettings::Property::FontMinPixel, context.expressionContext(), fontMinPixelSize );
1971 int fontMaxPixel = mDataDefinedProperties.valueAsInt( QgsPalLayerSettings::Property::FontMaxPixel, context.expressionContext(), fontMaxPixelSize );
1972
1973 if ( fontMinPixel > labelFont.pixelSize() || labelFont.pixelSize() > fontMaxPixel )
1974 {
1975 return nullptr;
1976 }
1977 }
1978 }
1979
1980 // NOTE: the following parsing functions calculate and store any data defined values for later use in QgsPalLabeling::drawLabeling
1981 // this is done to provide clarity, and because such parsing is not directly related to PAL feature registration calculations
1982
1983 // calculate rest of font attributes and store any data defined values
1984 // this is done here for later use in making label backgrounds part of collision management (when implemented)
1985 labelFont.setCapitalization( QFont::MixedCase ); // reset this - we don't use QFont's handling as it breaks with curved labels
1986
1987 parseTextStyle( labelFont, fontunits, context );
1988 if ( mDataDefinedProperties.hasActiveProperties() )
1989 {
1990 parseTextFormatting( context );
1991 parseTextBuffer( context );
1992 parseTextMask( context );
1993 parseShapeBackground( context );
1994 parseDropShadow( context );
1995 }
1996
1997 evaluatedFormat.setFont( labelFont );
1998 // undo scaling by symbology reference scale, as this would have been applied in the previous call to QgsTextRenderer::sizeToPixel and we risk
1999 // double-applying it if we don't re-adjust, since all the text format metric calculations assume an unscaled format font size is present
2000 const double symbologyReferenceScaleFactor = context.symbologyReferenceScale() > 0 ? context.symbologyReferenceScale() / context.rendererScale() : 1;
2001 evaluatedFormat.setSize( labelFont.pixelSize() / symbologyReferenceScaleFactor );
2002 evaluatedFormat.setSizeUnit( Qgis::RenderUnit::Pixels );
2003
2004 QString labelText;
2005
2006 // Check to see if we are a expression string.
2007 if ( isExpression )
2008 {
2010 if ( exp->hasParserError() )
2011 {
2012 QgsDebugMsgLevel( QStringLiteral( "Expression parser error:%1" ).arg( exp->parserErrorString() ), 4 );
2013 return nullptr;
2014 }
2015
2016 QVariant result = exp->evaluate( &context.expressionContext() ); // expression prepared in QgsPalLabeling::prepareLayer()
2017 if ( exp->hasEvalError() )
2018 {
2019 QgsDebugMsgLevel( QStringLiteral( "Expression parser eval error:%1" ).arg( exp->evalErrorString() ), 4 );
2020 return nullptr;
2021 }
2022 labelText = QgsVariantUtils::isNull( result ) ? QString() : result.toString();
2023 }
2024 else
2025 {
2026 const QVariant &v = feature.attribute( fieldIndex );
2027 labelText = QgsVariantUtils::isNull( v ) ? QString() : v.toString();
2028 }
2029
2030 // apply text replacements
2031 if ( useSubstitutions )
2032 {
2033 labelText = substitutions.process( labelText );
2034 }
2035
2036 // apply capitalization
2037 Qgis::Capitalization capitalization = evaluatedFormat.capitalization();
2038 // maintain API - capitalization may have been set in textFont
2039 if ( capitalization == Qgis::Capitalization::MixedCase && mFormat.font().capitalization() != QFont::MixedCase )
2040 {
2041 capitalization = static_cast< Qgis::Capitalization >( mFormat.font().capitalization() );
2042 }
2043 // data defined font capitalization?
2044 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::FontCase ) )
2045 {
2046 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Property::FontCase, context.expressionContext() );
2047 if ( !QgsVariantUtils::isNull( exprVal ) )
2048 {
2049 QString fcase = exprVal.toString().trimmed();
2050 QgsDebugMsgLevel( QStringLiteral( "exprVal FontCase:%1" ).arg( fcase ), 4 );
2051
2052 if ( !fcase.isEmpty() )
2053 {
2054 if ( fcase.compare( QLatin1String( "NoChange" ), Qt::CaseInsensitive ) == 0 )
2055 {
2056 capitalization = Qgis::Capitalization::MixedCase;
2057 }
2058 else if ( fcase.compare( QLatin1String( "Upper" ), Qt::CaseInsensitive ) == 0 )
2059 {
2060 capitalization = Qgis::Capitalization::AllUppercase;
2061 }
2062 else if ( fcase.compare( QLatin1String( "Lower" ), Qt::CaseInsensitive ) == 0 )
2063 {
2064 capitalization = Qgis::Capitalization::AllLowercase;
2065 }
2066 else if ( fcase.compare( QLatin1String( "Capitalize" ), Qt::CaseInsensitive ) == 0 )
2067 {
2069 }
2070 else if ( fcase.compare( QLatin1String( "Title" ), Qt::CaseInsensitive ) == 0 )
2071 {
2072 capitalization = Qgis::Capitalization::TitleCase;
2073 }
2074#if defined(HAS_KDE_QT5_SMALL_CAPS_FIX) || QT_VERSION >= QT_VERSION_CHECK(6, 3, 0)
2075 else if ( fcase.compare( QLatin1String( "SmallCaps" ), Qt::CaseInsensitive ) == 0 )
2076 {
2077 capitalization = Qgis::Capitalization::SmallCaps;
2078 }
2079 else if ( fcase.compare( QLatin1String( "AllSmallCaps" ), Qt::CaseInsensitive ) == 0 )
2080 {
2081 capitalization = Qgis::Capitalization::AllSmallCaps;
2082 }
2083#endif
2084 }
2085 }
2086 }
2087 labelText = QgsStringUtils::capitalize( labelText, capitalization );
2088
2089 // format number if label text is coercible to a number
2090 bool evalFormatNumbers = formatNumbers;
2091 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::NumFormat ) )
2092 {
2093 evalFormatNumbers = mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::Property::NumFormat, context.expressionContext(), evalFormatNumbers );
2094 }
2095 if ( evalFormatNumbers )
2096 {
2097 // data defined decimal places?
2098 int decimalPlaces = mDataDefinedProperties.valueAsInt( QgsPalLayerSettings::Property::NumDecimals, context.expressionContext(), decimals );
2099 if ( decimalPlaces <= 0 ) // needs to be positive
2100 decimalPlaces = decimals;
2101
2102 // data defined plus sign?
2103 bool signPlus = mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::Property::NumPlusSign, context.expressionContext(), plusSign );
2104
2105 QVariant textV( labelText );
2106 bool ok;
2107 double d = textV.toDouble( &ok );
2108 if ( ok )
2109 {
2110 QString numberFormat;
2111 if ( d > 0 && signPlus )
2112 {
2113 numberFormat.append( '+' );
2114 }
2115 numberFormat.append( "%1" );
2116 labelText = numberFormat.arg( QLocale().toString( d, 'f', decimalPlaces ) );
2117 }
2118 }
2119
2120 // NOTE: this should come AFTER any option that affects font metrics
2121 std::unique_ptr<QFontMetricsF> labelFontMetrics( new QFontMetricsF( labelFont ) );
2122 double labelWidth;
2123 double labelHeight;
2124 double rotatedLabelX;
2125 double rotatedLabelY;
2126
2127 QgsTextDocument doc;
2128 QgsTextDocumentMetrics documentMetrics;
2129 QRectF outerBounds;
2130
2131 switch ( placement )
2132 {
2135 {
2136 // avoid calculating document and metrics if we don't require them for curved labels
2137 if ( evaluatedFormat.allowHtmlFormatting() && !labelText.isEmpty() )
2138 {
2139 doc = QgsTextDocument::fromHtml( QStringList() << labelText );
2140 calculateLabelSize( labelFontMetrics.get(), labelText, labelWidth, labelHeight, mCurFeat, &context, &rotatedLabelX, &rotatedLabelY, &evaluatedFormat, &doc, &documentMetrics, &outerBounds );
2141 }
2142 else
2143 {
2144 calculateLabelSize( labelFontMetrics.get(), labelText, labelWidth, labelHeight, mCurFeat, &context, &rotatedLabelX, &rotatedLabelY, &evaluatedFormat, nullptr, nullptr, &outerBounds );
2145 }
2146 break;
2147 }
2148
2156 {
2157 // non-curved labels always require document and metrics
2158 if ( evaluatedFormat.allowHtmlFormatting() && !labelText.isEmpty() )
2159 {
2160 doc = QgsTextDocument::fromHtml( QStringList() << labelText );
2161 }
2162 else
2163 {
2164 doc = QgsTextDocument::fromPlainText( { labelText } );
2165 }
2166 calculateLabelSize( labelFontMetrics.get(), labelText, labelWidth, labelHeight, mCurFeat, &context, &rotatedLabelX, &rotatedLabelY, &evaluatedFormat, &doc, &documentMetrics, &outerBounds );
2167 break;
2168 }
2169 }
2170
2171 // maximum angle between curved label characters (hardcoded defaults used in QGIS <2.0)
2172 //
2173 double maxcharanglein = 20.0; // range 20.0-60.0
2174 double maxcharangleout = -20.0; // range 20.0-95.0
2175
2176 switch ( placement )
2177 {
2180 {
2181 maxcharanglein = maxCurvedCharAngleIn;
2182 maxcharangleout = maxCurvedCharAngleOut;
2183
2184 //data defined maximum angle between curved label characters?
2185 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::CurvedCharAngleInOut ) )
2186 {
2187 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Property::CurvedCharAngleInOut, context.expressionContext() );
2188 bool ok = false;
2189 const QPointF maxcharanglePt = QgsSymbolLayerUtils::toPoint( exprVal, &ok );
2190 if ( ok )
2191 {
2192 maxcharanglein = std::clamp( static_cast< double >( maxcharanglePt.x() ), 20.0, 60.0 );
2193 maxcharangleout = std::clamp( static_cast< double >( maxcharanglePt.y() ), 20.0, 95.0 );
2194 }
2195 }
2196 // make sure maxcharangleout is always negative
2197 maxcharangleout = -( std::fabs( maxcharangleout ) );
2198 break;
2199 }
2200
2208 break;
2209 }
2210
2211 // data defined centroid whole or clipped?
2212 bool wholeCentroid = centroidWhole;
2213 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::CentroidWhole ) )
2214 {
2215 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Property::CentroidWhole, context.expressionContext() );
2216 if ( !QgsVariantUtils::isNull( exprVal ) )
2217 {
2218 QString str = exprVal.toString().trimmed();
2219 QgsDebugMsgLevel( QStringLiteral( "exprVal CentroidWhole:%1" ).arg( str ), 4 );
2220
2221 if ( !str.isEmpty() )
2222 {
2223 if ( str.compare( QLatin1String( "Visible" ), Qt::CaseInsensitive ) == 0 )
2224 {
2225 wholeCentroid = false;
2226 }
2227 else if ( str.compare( QLatin1String( "Whole" ), Qt::CaseInsensitive ) == 0 )
2228 {
2229 wholeCentroid = true;
2230 }
2231 }
2232 }
2233 }
2234
2235 QgsGeometry geom = feature.geometry();
2236 if ( geom.isNull() )
2237 {
2238 return nullptr;
2239 }
2240
2241 // simplify?
2242 const QgsVectorSimplifyMethod &simplifyMethod = context.vectorSimplifyMethod();
2243 std::unique_ptr<QgsGeometry> scopedClonedGeom;
2245 {
2246 unsigned int simplifyHints = simplifyMethod.simplifyHints() | QgsMapToPixelSimplifier::SimplifyEnvelope;
2247 const Qgis::VectorSimplificationAlgorithm simplifyAlgorithm = simplifyMethod.simplifyAlgorithm();
2248 QgsMapToPixelSimplifier simplifier( simplifyHints, simplifyMethod.tolerance(), simplifyAlgorithm );
2249 geom = simplifier.simplify( geom );
2250 }
2251
2252 if ( !context.featureClipGeometry().isEmpty() )
2253 {
2254 const Qgis::GeometryType expectedType = geom.type();
2255 geom = geom.intersection( context.featureClipGeometry() );
2256 geom.convertGeometryCollectionToSubclass( expectedType );
2257 }
2258
2259 // whether we're going to create a centroid for polygon
2260 bool centroidPoly = ( ( placement == Qgis::LabelPlacement::AroundPoint
2262 && geom.type() == Qgis::GeometryType::Polygon );
2263
2264 // CLIP the geometry if it is bigger than the extent
2265 // don't clip if centroid is requested for whole feature
2266 bool doClip = false;
2267 if ( !centroidPoly || !wholeCentroid )
2268 {
2269 doClip = true;
2270 }
2271
2272
2273 Qgis::LabelPolygonPlacementFlags polygonPlacement = mPolygonPlacementFlags;
2274 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::PolygonLabelOutside ) )
2275 {
2276 const QVariant dataDefinedOutside = mDataDefinedProperties.value( QgsPalLayerSettings::Property::PolygonLabelOutside, context.expressionContext() );
2277 if ( !QgsVariantUtils::isNull( dataDefinedOutside ) )
2278 {
2279 if ( dataDefinedOutside.userType() == QMetaType::Type::QString )
2280 {
2281 const QString value = dataDefinedOutside.toString().trimmed();
2282 if ( value.compare( QLatin1String( "force" ), Qt::CaseInsensitive ) == 0 )
2283 {
2284 // forced outside placement -- remove inside flag, add outside flag
2285 polygonPlacement &= ~static_cast< int >( Qgis::LabelPolygonPlacementFlag::AllowPlacementInsideOfPolygon );
2287 }
2288 else if ( value.compare( QLatin1String( "yes" ), Qt::CaseInsensitive ) == 0 )
2289 {
2290 // permit outside placement
2292 }
2293 else if ( value.compare( QLatin1String( "no" ), Qt::CaseInsensitive ) == 0 )
2294 {
2295 // block outside placement
2296 polygonPlacement &= ~static_cast< int >( Qgis::LabelPolygonPlacementFlag::AllowPlacementOutsideOfPolygon );
2297 }
2298 }
2299 else
2300 {
2301 if ( dataDefinedOutside.toBool() )
2302 {
2303 // permit outside placement
2305 }
2306 else
2307 {
2308 // block outside placement
2309 polygonPlacement &= ~static_cast< int >( Qgis::LabelPolygonPlacementFlag::AllowPlacementOutsideOfPolygon );
2310 }
2311 }
2312 }
2313 }
2314
2315 QgsLabelLineSettings lineSettings = mLineSettings;
2316 lineSettings.updateDataDefinedProperties( mDataDefinedProperties, context.expressionContext() );
2317
2318 QgsLabelPointSettings pointSettings = mPointSettings;
2319 pointSettings.updateDataDefinedProperties( mDataDefinedProperties, context.expressionContext() );
2320
2322 {
2323 switch ( lineSettings.anchorClipping() )
2324 {
2326 break;
2327
2329 doClip = false;
2330 break;
2331 }
2332 }
2333
2334 // if using fitInPolygonOnly option, generate the permissible zone (must happen before geometry is modified - e.g.,
2335 // as a result of using perimeter based labeling and the geometry is converted to a boundary)
2336 // note that we also force this if we are permitting labels to be placed outside of polygons too!
2337 QgsGeometry permissibleZone;
2339 {
2340 permissibleZone = geom;
2341 if ( QgsPalLabeling::geometryRequiresPreparation( permissibleZone, context, ct, doClip ? extentGeom : QgsGeometry(), lineSettings.mergeLines() ) )
2342 {
2343 permissibleZone = QgsPalLabeling::prepareGeometry( permissibleZone, context, ct, doClip ? extentGeom : QgsGeometry(), lineSettings.mergeLines() );
2344 }
2345 }
2346
2347 // if using perimeter based labeling for polygons, get the polygon's
2348 // linear boundary and use that for the label geometry
2349 if ( ( geom.type() == Qgis::GeometryType::Polygon )
2351 {
2352 geom = QgsGeometry( geom.constGet()->boundary() );
2353 }
2354
2355 geos::unique_ptr geos_geom_clone;
2357 {
2358 geom = QgsPalLabeling::prepareGeometry( geom, context, ct, doClip ? extentGeom : QgsGeometry(), lineSettings.mergeLines() );
2359
2360 if ( geom.isEmpty() )
2361 return nullptr;
2362 }
2363 geos_geom_clone = QgsGeos::asGeos( geom );
2364
2366 {
2367 if ( !obstacleGeometry.isNull() && QgsPalLabeling::geometryRequiresPreparation( obstacleGeometry, context, ct, doClip ? extentGeom : QgsGeometry(), lineSettings.mergeLines() ) )
2368 {
2369 obstacleGeometry = QgsGeometry( QgsPalLabeling::prepareGeometry( obstacleGeometry, context, ct, doClip ? extentGeom : QgsGeometry(), lineSettings.mergeLines() ) );
2370 }
2371 }
2372
2373 QgsLabelThinningSettings featureThinningSettings = mThinningSettings;
2374 featureThinningSettings.updateDataDefinedProperties( mDataDefinedProperties, context.expressionContext() );
2375
2376 double minimumSize = 0.0;
2377 if ( featureThinningSettings.minimumFeatureSize() > 0 )
2378 {
2379 // for minimum feature size on merged lines, we need to delay the filtering after the merging occurred in PAL
2381 {
2382 minimumSize = context.convertToMapUnits( featureThinningSettings.minimumFeatureSize(), Qgis::RenderUnit::Millimeters );
2383 }
2384 else
2385 {
2386 if ( !checkMinimumSizeMM( context, geom, featureThinningSettings.minimumFeatureSize() ) )
2387 return nullptr;
2388 }
2389 }
2390
2391 if ( !geos_geom_clone )
2392 return nullptr; // invalid geometry
2393
2394 // likelihood exists label will be registered with PAL and may be drawn
2395 // check if max number of features to label (already registered with PAL) has been reached
2396 // Debug output at end of QgsPalLabeling::drawLabeling(), when deleting temp geometries
2397 if ( featureThinningSettings.limitNumberOfLabelsEnabled() )
2398 {
2399 if ( !featureThinningSettings.maximumNumberLabels() )
2400 {
2401 return nullptr;
2402 }
2403 if ( mFeatsRegPal >= featureThinningSettings.maximumNumberLabels() )
2404 {
2405 return nullptr;
2406 }
2407
2408 int divNum = static_cast< int >( ( static_cast< double >( mFeaturesToLabel ) / featureThinningSettings.maximumNumberLabels() ) + 0.5 ); // NOLINT
2409 if ( divNum && ( mFeatsRegPal == static_cast< int >( mFeatsSendingToPal / divNum ) ) )
2410 {
2411 mFeatsSendingToPal += 1;
2412 if ( divNum && mFeatsSendingToPal % divNum )
2413 {
2414 return nullptr;
2415 }
2416 }
2417 }
2418
2419 //data defined position / alignment / rotation?
2420 bool layerDefinedRotation = false;
2421 bool dataDefinedRotation = false;
2422 double xPos = 0.0, yPos = 0.0;
2423 double angleInRadians = 0.0;
2424 double quadOffsetX = 0.0, quadOffsetY = 0.0;
2425 double offsetX = 0.0, offsetY = 0.0;
2426 QgsPointXY anchorPosition;
2427
2429 {
2430 anchorPosition = geom.centroid().asPoint();
2431 }
2432 //x/y shift in case of alignment
2433 double xdiff = 0.0;
2434 double ydiff = 0.0;
2435
2436 //data defined quadrant offset?
2437 bool ddFixedQuad = false;
2439 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::OffsetQuad ) )
2440 {
2441 context.expressionContext().setOriginalValueVariable( static_cast< int >( quadOff ) );
2442 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Property::OffsetQuad, context.expressionContext() );
2443 if ( !QgsVariantUtils::isNull( exprVal ) )
2444 {
2445 bool ok;
2446 int quadInt = exprVal.toInt( &ok );
2447 if ( ok && 0 <= quadInt && quadInt <= 8 )
2448 {
2449 quadOff = static_cast< Qgis::LabelQuadrantPosition >( quadInt );
2450 ddFixedQuad = true;
2451 }
2452 }
2453 }
2454
2455 // adjust quadrant offset of labels
2456 switch ( quadOff )
2457 {
2459 quadOffsetX = -1.0;
2460 quadOffsetY = 1.0;
2461 break;
2463 quadOffsetX = 0.0;
2464 quadOffsetY = 1.0;
2465 break;
2467 quadOffsetX = 1.0;
2468 quadOffsetY = 1.0;
2469 break;
2471 quadOffsetX = -1.0;
2472 quadOffsetY = 0.0;
2473 break;
2475 quadOffsetX = 1.0;
2476 quadOffsetY = 0.0;
2477 break;
2479 quadOffsetX = -1.0;
2480 quadOffsetY = -1.0;
2481 break;
2483 quadOffsetX = 0.0;
2484 quadOffsetY = -1.0;
2485 break;
2487 quadOffsetX = 1.0;
2488 quadOffsetY = -1.0;
2489 break;
2491 break;
2492 }
2493
2494 //data defined label offset?
2495 double xOff = xOffset;
2496 double yOff = yOffset;
2497 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::OffsetXY ) )
2498 {
2500 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Property::OffsetXY, context.expressionContext() );
2501 bool ok = false;
2502 const QPointF ddOffPt = QgsSymbolLayerUtils::toPoint( exprVal, &ok );
2503 if ( ok )
2504 {
2505 xOff = ddOffPt.x();
2506 yOff = ddOffPt.y();
2507 }
2508 }
2509
2510 // data defined label offset units?
2511 Qgis::RenderUnit offUnit = offsetUnits;
2512 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::OffsetUnits ) )
2513 {
2514 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Property::OffsetUnits, context.expressionContext() );
2515 if ( !QgsVariantUtils::isNull( exprVal ) )
2516 {
2517 QString units = exprVal.toString().trimmed();
2518 if ( !units.isEmpty() )
2519 {
2520 bool ok = false;
2521 Qgis::RenderUnit decodedUnits = QgsUnitTypes::decodeRenderUnit( units, &ok );
2522 if ( ok )
2523 {
2524 offUnit = decodedUnits;
2525 }
2526 }
2527 }
2528 }
2529
2530 // adjust offset of labels to match chosen unit and map scale
2531 // offsets match those of symbology: -x = left, -y = up
2532 offsetX = context.convertToMapUnits( xOff, offUnit, labelOffsetMapUnitScale );
2533 // must be negative to match symbology offset direction
2534 offsetY = context.convertToMapUnits( -yOff, offUnit, labelOffsetMapUnitScale );
2535
2536 // layer defined rotation?
2537 if ( !qgsDoubleNear( angleOffset, 0.0 ) )
2538 {
2539 layerDefinedRotation = true;
2540 angleInRadians = ( 360 - angleOffset ) * M_PI / 180; // convert to radians counterclockwise
2541 }
2542
2543 const QgsMapToPixel &m2p = context.mapToPixel();
2544 //data defined rotation?
2545 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::LabelRotation ) )
2546 {
2548 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Property::LabelRotation, context.expressionContext() );
2549 if ( !QgsVariantUtils::isNull( exprVal ) )
2550 {
2551 bool ok;
2552 const double rotation = exprVal.toDouble( &ok );
2553 if ( ok )
2554 {
2555 dataDefinedRotation = true;
2556
2557 double rotationDegrees = rotation * QgsUnitTypes::fromUnitToUnitFactor( mRotationUnit,
2559
2560 // TODO: add setting to disable having data defined rotation follow
2561 // map rotation ?
2562 rotationDegrees += m2p.mapRotation();
2563 angleInRadians = ( 360 - rotationDegrees ) * M_PI / 180.0;
2564 }
2565 }
2566 }
2567
2568 bool hasDataDefinedPosition = false;
2569 {
2570 bool ddPosition = false;
2571
2572 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::PositionX )
2573 && mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::PositionY ) )
2574 {
2575 const QVariant xPosProperty = mDataDefinedProperties.value( QgsPalLayerSettings::Property::PositionX, context.expressionContext() );
2576 const QVariant yPosProperty = mDataDefinedProperties.value( QgsPalLayerSettings::Property::PositionY, context.expressionContext() );
2577 if ( !QgsVariantUtils::isNull( xPosProperty )
2578 && !QgsVariantUtils::isNull( yPosProperty ) )
2579 {
2580 ddPosition = true;
2581
2582 bool ddXPos = false, ddYPos = false;
2583 xPos = xPosProperty.toDouble( &ddXPos );
2584 yPos = yPosProperty.toDouble( &ddYPos );
2585 if ( ddXPos && ddYPos )
2586 hasDataDefinedPosition = true;
2587 }
2588 }
2589 else if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::PositionPoint ) )
2590 {
2591 const QVariant pointPosProperty = mDataDefinedProperties.value( QgsPalLayerSettings::Property::PositionPoint, context.expressionContext() );
2592 if ( !QgsVariantUtils::isNull( pointPosProperty ) )
2593 {
2594 ddPosition = true;
2595
2596 QgsPoint point;
2597 if ( pointPosProperty.userType() == QMetaType::type( "QgsReferencedGeometry" ) )
2598 {
2599 QgsReferencedGeometry referencedGeometryPoint = pointPosProperty.value<QgsReferencedGeometry>();
2600 point = QgsPoint( referencedGeometryPoint.asPoint() );
2601
2602 if ( !referencedGeometryPoint.isNull()
2603 && ct.sourceCrs() != referencedGeometryPoint.crs() )
2604 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 );
2605 }
2606 else if ( pointPosProperty.userType() == QMetaType::type( "QgsGeometry" ) )
2607 {
2608 point = QgsPoint( pointPosProperty.value<QgsGeometry>().asPoint() );
2609 }
2610
2611 if ( !point.isEmpty() )
2612 {
2613 hasDataDefinedPosition = true;
2614
2615 xPos = point.x();
2616 yPos = point.y();
2617 }
2618 }
2619 }
2620
2621 if ( ddPosition )
2622 {
2623 //data defined position. But field values could be NULL -> positions will be generated by PAL
2624 if ( hasDataDefinedPosition )
2625 {
2626 // layer rotation set, but don't rotate pinned labels unless data defined
2627 if ( layerDefinedRotation && !dataDefinedRotation )
2628 {
2629 angleInRadians = 0.0;
2630 }
2631
2632 //horizontal alignment
2633 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::Hali ) )
2634 {
2635 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Property::Hali, context.expressionContext() );
2636 if ( !QgsVariantUtils::isNull( exprVal ) )
2637 {
2638 QString haliString = exprVal.toString();
2639 if ( haliString.compare( QLatin1String( "Center" ), Qt::CaseInsensitive ) == 0 )
2640 {
2641 xdiff -= labelWidth / 2.0;
2642 }
2643 else if ( haliString.compare( QLatin1String( "Right" ), Qt::CaseInsensitive ) == 0 )
2644 {
2645 xdiff -= labelWidth;
2646 }
2647 }
2648 }
2649
2650 //vertical alignment
2651 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::Vali ) )
2652 {
2653 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Property::Vali, context.expressionContext() );
2654 if ( !QgsVariantUtils::isNull( exprVal ) )
2655 {
2656 QString valiString = exprVal.toString();
2657 if ( valiString.compare( QLatin1String( "Bottom" ), Qt::CaseInsensitive ) != 0 )
2658 {
2659 if ( valiString.compare( QLatin1String( "Top" ), Qt::CaseInsensitive ) == 0 )
2660 {
2661 ydiff -= labelHeight;;
2662 }
2663 else
2664 {
2665 double descentRatio = labelFontMetrics->descent() / labelFontMetrics->height();
2666 if ( valiString.compare( QLatin1String( "Base" ), Qt::CaseInsensitive ) == 0 )
2667 {
2668 ydiff -= labelHeight * descentRatio;
2669 }
2670 else //'Cap' or 'Half'
2671 {
2672 double capHeightRatio = ( labelFontMetrics->boundingRect( 'H' ).height() + 1 + labelFontMetrics->descent() ) / labelFontMetrics->height();
2673 ydiff -= labelHeight * capHeightRatio;
2674 if ( valiString.compare( QLatin1String( "Half" ), Qt::CaseInsensitive ) == 0 )
2675 {
2676 ydiff += labelHeight * ( capHeightRatio - descentRatio ) / 2.0;
2677 }
2678 }
2679 }
2680 }
2681 }
2682 }
2683
2684 if ( dataDefinedRotation )
2685 {
2686 //adjust xdiff and ydiff because the hali/vali point needs to be the rotation center
2687 double xd = xdiff * std::cos( angleInRadians ) - ydiff * std::sin( angleInRadians );
2688 double yd = xdiff * std::sin( angleInRadians ) + ydiff * std::cos( angleInRadians );
2689 xdiff = xd;
2690 ydiff = yd;
2691 }
2692
2693 //project xPos and yPos from layer to map CRS, handle rotation
2694 QgsGeometry ddPoint( new QgsPoint( xPos, yPos ) );
2695 if ( QgsPalLabeling::geometryRequiresPreparation( ddPoint, context, ct ) )
2696 {
2697 ddPoint = QgsPalLabeling::prepareGeometry( ddPoint, context, ct );
2698 if ( const QgsPoint *point = qgsgeometry_cast< const QgsPoint * >( ddPoint.constGet() ) )
2699 {
2700 xPos = point->x();
2701 yPos = point->y();
2702 anchorPosition = QgsPointXY( xPos, yPos );
2703 }
2704 else
2705 {
2706 QgsMessageLog::logMessage( QObject::tr( "Invalid data defined label position (%1, %2)" ).arg( xPos ).arg( yPos ), QObject::tr( "Labeling" ) );
2707 hasDataDefinedPosition = false;
2708 }
2709 }
2710 else
2711 {
2712 anchorPosition = QgsPointXY( xPos, yPos );
2713 }
2714
2715 xPos += xdiff;
2716 yPos += ydiff;
2717 }
2718 else
2719 {
2720 anchorPosition = QgsPointXY( xPos, yPos );
2721 }
2722 }
2723 }
2724
2725 // data defined always show?
2726 bool alwaysShow = false;
2727 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::AlwaysShow ) )
2728 {
2729 alwaysShow = mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::Property::AlwaysShow, context.expressionContext(), false );
2730 }
2731
2732 // set repeat distance
2733 // data defined repeat distance?
2734 double repeatDist = repeatDistance;
2735 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::RepeatDistance ) )
2736 {
2737 context.expressionContext().setOriginalValueVariable( repeatDist );
2738 repeatDist = mDataDefinedProperties.valueAsDouble( QgsPalLayerSettings::Property::RepeatDistance, context.expressionContext(), repeatDist );
2739 }
2740
2741 // data defined label-repeat distance units?
2743 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::RepeatDistanceUnit ) )
2744 {
2745 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Property::RepeatDistanceUnit, context.expressionContext() );
2746 if ( !QgsVariantUtils::isNull( exprVal ) )
2747 {
2748 QString units = exprVal.toString().trimmed();
2749 if ( !units.isEmpty() )
2750 {
2751 bool ok = false;
2752 Qgis::RenderUnit decodedUnits = QgsUnitTypes::decodeRenderUnit( units, &ok );
2753 if ( ok )
2754 {
2755 repeatUnits = decodedUnits;
2756 }
2757 }
2758 }
2759 }
2760
2761 if ( !qgsDoubleNear( repeatDist, 0.0 ) )
2762 {
2763 if ( repeatUnits != Qgis::RenderUnit::MapUnits )
2764 {
2765 repeatDist = context.convertToMapUnits( repeatDist, repeatUnits, repeatDistanceMapUnitScale );
2766 }
2767 }
2768
2769 // overrun distance
2770 double overrunDistanceEval = lineSettings.overrunDistance();
2771 if ( !qgsDoubleNear( overrunDistanceEval, 0.0 ) )
2772 {
2773 overrunDistanceEval = context.convertToMapUnits( overrunDistanceEval, lineSettings.overrunDistanceUnit(), lineSettings.overrunDistanceMapUnitScale() );
2774 }
2775
2776 // we smooth out the overrun label extensions by 1 mm, to avoid little jaggies right at the start or end of the lines
2777 // causing the overrun extension to extend out in an undesirable direction. This is hard coded, we don't want to overload
2778 // users with options they likely don't need to see...
2779 const double overrunSmoothDist = context.convertToMapUnits( 1, Qgis::RenderUnit::Millimeters );
2780
2781 bool labelAll = labelPerPart && !hasDataDefinedPosition;
2782 if ( !hasDataDefinedPosition )
2783 {
2784 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::LabelAllParts ) )
2785 {
2787 labelAll = mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::Property::LabelAllParts, context.expressionContext(), labelPerPart );
2788 }
2789 }
2790
2791 // maximum distance
2792 double maximumDistanceEval = pointSettings.maximumDistance();
2793 if ( !qgsDoubleNear( maximumDistanceEval, 0.0 ) )
2794 {
2795 maximumDistanceEval = context.convertToMapUnits( maximumDistanceEval, pointSettings.maximumDistanceUnit(), pointSettings.maximumDistanceMapUnitScale() );
2796 }
2797
2798 // feature to the layer
2799 std::unique_ptr< QgsTextLabelFeature > labelFeature = std::make_unique< QgsTextLabelFeature>( feature.id(), std::move( geos_geom_clone ), QSizeF( labelWidth, labelHeight ) );
2800 labelFeature->setAnchorPosition( anchorPosition );
2801 labelFeature->setFeature( feature );
2802 labelFeature->setSymbol( symbol );
2803 labelFeature->setDocument( doc, documentMetrics );
2804 if ( !qgsDoubleNear( rotatedLabelX, 0.0 ) && !qgsDoubleNear( rotatedLabelY, 0.0 ) )
2805 labelFeature->setRotatedSize( QSizeF( rotatedLabelX, rotatedLabelY ) );
2806 mFeatsRegPal++;
2807
2808 labelFeature->setHasFixedPosition( hasDataDefinedPosition );
2809 labelFeature->setFixedPosition( QgsPointXY( xPos, yPos ) );
2810 // use layer-level defined rotation, but not if position fixed
2811 labelFeature->setHasFixedAngle( dataDefinedRotation || ( !hasDataDefinedPosition && !qgsDoubleNear( angleInRadians, 0.0 ) ) );
2812 labelFeature->setFixedAngle( angleInRadians );
2813 labelFeature->setQuadOffset( QPointF( quadOffsetX, quadOffsetY ) );
2814 labelFeature->setPositionOffset( QgsPointXY( offsetX, offsetY ) );
2815 labelFeature->setOffsetType( offsetType );
2816 labelFeature->setAlwaysShow( alwaysShow );
2817 labelFeature->setRepeatDistance( repeatDist );
2818 labelFeature->setLabelText( labelText );
2819 labelFeature->setPermissibleZone( permissibleZone );
2820 labelFeature->setOverrunDistance( overrunDistanceEval );
2821 labelFeature->setOverrunSmoothDistance( overrunSmoothDist );
2822 labelFeature->setMaximumDistance( maximumDistanceEval );
2823 labelFeature->setLineAnchorPercent( lineSettings.lineAnchorPercent() );
2824 labelFeature->setLineAnchorType( lineSettings.anchorType() );
2825 labelFeature->setLineAnchorTextPoint( lineSettings.anchorTextPoint() );
2826 labelFeature->setLabelAllParts( labelAll );
2827 labelFeature->setOriginalFeatureCrs( context.coordinateTransform().sourceCrs() );
2828 labelFeature->setMinimumSize( minimumSize );
2829 if ( geom.type() == Qgis::GeometryType::Point && !obstacleGeometry.isNull() )
2830 {
2831 //register symbol size
2832 labelFeature->setSymbolSize( QSizeF( obstacleGeometry.boundingBox().width(),
2833 obstacleGeometry.boundingBox().height() ) );
2834 }
2835
2836 if ( outerBounds.left() != 0 || outerBounds.top() != 0 || !qgsDoubleNear( outerBounds.width(), labelWidth ) || !qgsDoubleNear( outerBounds.height(), labelHeight ) )
2837 {
2838 labelFeature->setOuterBounds( outerBounds );
2839 }
2840
2841 //set label's visual margin so that top visual margin is the leading, and bottom margin is the font's descent
2842 //this makes labels align to the font's baseline or highest character
2843 double topMargin = std::max( 0.25 * labelFontMetrics->ascent(), 0.0 );
2844 double bottomMargin = 1.0 + labelFontMetrics->descent();
2845 QgsMargins vm( 0.0, topMargin, 0.0, bottomMargin );
2846 vm *= xform->mapUnitsPerPixel();
2847 labelFeature->setVisualMargin( vm );
2848
2849 // store the label's calculated font for later use during painting
2850 QgsDebugMsgLevel( QStringLiteral( "PAL font stored definedFont: %1, Style: %2" ).arg( labelFont.toString(), labelFont.styleName() ), 4 );
2851 labelFeature->setDefinedFont( labelFont );
2852
2853 labelFeature->setMaximumCharacterAngleInside( std::clamp( maxcharanglein, 20.0, 60.0 ) * M_PI / 180 );
2854 labelFeature->setMaximumCharacterAngleOutside( std::clamp( maxcharangleout, -95.0, -20.0 ) * M_PI / 180 );
2855 switch ( placement )
2856 {
2864 // these placements don't require text metrics
2865 break;
2866
2869 labelFeature->setTextMetrics( QgsTextLabelFeature::calculateTextMetrics( xform, context, labelFont, *labelFontMetrics, labelFont.letterSpacing(), labelFont.wordSpacing(), labelText, evaluatedFormat.allowHtmlFormatting() ? &doc : nullptr, evaluatedFormat.allowHtmlFormatting() ? &documentMetrics : nullptr ) );
2870 break;
2871 }
2872
2873 // for labelFeature the LabelInfo is passed to feat when it is registered
2874
2875 // TODO: allow layer-wide feature dist in PAL...?
2876
2877 // data defined label-feature distance?
2878 double distance = dist;
2879 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::LabelDistance ) )
2880 {
2881 context.expressionContext().setOriginalValueVariable( distance );
2882 distance = mDataDefinedProperties.valueAsDouble( QgsPalLayerSettings::Property::LabelDistance, context.expressionContext(), distance );
2883 }
2884
2885 // data defined label-feature distance units?
2886 Qgis::RenderUnit distUnit = distUnits;
2887 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::DistanceUnits ) )
2888 {
2889 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Property::DistanceUnits, context.expressionContext() );
2890 if ( !QgsVariantUtils::isNull( exprVal ) )
2891 {
2892 QString units = exprVal.toString().trimmed();
2893 QgsDebugMsgLevel( QStringLiteral( "exprVal DistanceUnits:%1" ).arg( units ), 4 );
2894 if ( !units.isEmpty() )
2895 {
2896 bool ok = false;
2897 Qgis::RenderUnit decodedUnits = QgsUnitTypes::decodeRenderUnit( units, &ok );
2898 if ( ok )
2899 {
2900 distUnit = decodedUnits;
2901 }
2902 }
2903 }
2904 }
2905 distance = context.convertToPainterUnits( distance, distUnit, distMapUnitScale );
2906
2907 // when using certain placement modes, we force a tiny minimum distance. This ensures that
2908 // candidates are created just offset from a border and avoids candidates being incorrectly flagged as colliding with neighbours
2909 switch ( placement )
2910 {
2914 distance = ( distance < 0 ? -1 : 1 ) * std::max( std::fabs( distance ), 1.0 );
2915 break;
2916
2918 break;
2919
2925 {
2926 distance = std::max( distance, 2.0 );
2927 }
2928 break;
2929
2931 distance = std::max( distance, 2.0 );
2932 break;
2933 }
2934
2935 if ( !qgsDoubleNear( distance, 0.0 ) )
2936 {
2937 double d = ptOne.distance( ptZero ) * distance;
2938 labelFeature->setDistLabel( d );
2939 }
2940
2941 if ( ddFixedQuad )
2942 {
2943 labelFeature->setHasFixedQuadrant( true );
2944 }
2945
2946 labelFeature->setArrangementFlags( lineSettings.placementFlags() );
2947
2948 labelFeature->setPolygonPlacementFlags( polygonPlacement );
2949
2950 // data defined z-index?
2951 double z = zIndex;
2952 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::ZIndex ) )
2953 {
2955 z = mDataDefinedProperties.valueAsDouble( QgsPalLayerSettings::Property::ZIndex, context.expressionContext(), z );
2956 }
2957 labelFeature->setZIndex( z );
2958
2959 // data defined priority?
2960 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::Priority ) )
2961 {
2963 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Property::Priority, context.expressionContext() );
2964 if ( !QgsVariantUtils::isNull( exprVal ) )
2965 {
2966 bool ok;
2967 double priorityD = exprVal.toDouble( &ok );
2968 if ( ok )
2969 {
2970 priorityD = std::clamp( priorityD, 0.0, 10.0 );
2971 priorityD = 1 - priorityD / 10.0; // convert 0..10 --> 1..0
2972 labelFeature->setPriority( priorityD );
2973 }
2974 }
2975 }
2976
2977 // data defined allow degraded placement
2978 {
2979 double allowDegradedPlacement = mPlacementSettings.allowDegradedPlacement();
2981 {
2982 context.expressionContext().setOriginalValueVariable( allowDegradedPlacement );
2983 allowDegradedPlacement = mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::Property::AllowDegradedPlacement, context.expressionContext(), allowDegradedPlacement );
2984 }
2985 labelFeature->setAllowDegradedPlacement( allowDegradedPlacement );
2986 }
2987
2988 // data defined overlap handling
2989 {
2990 Qgis::LabelOverlapHandling overlapHandling = mPlacementSettings.overlapHandling();
2991 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::OverlapHandling ) )
2992 {
2993 const QString handlingString = mDataDefinedProperties.valueAsString( QgsPalLayerSettings::Property::OverlapHandling, context.expressionContext() );
2994 const QString cleanedString = handlingString.trimmed();
2995 if ( cleanedString.compare( QLatin1String( "prevent" ), Qt::CaseInsensitive ) == 0 )
2997 else if ( cleanedString.compare( QLatin1String( "allowifneeded" ), Qt::CaseInsensitive ) == 0 )
2999 else if ( cleanedString.compare( QLatin1String( "alwaysallow" ), Qt::CaseInsensitive ) == 0 )
3001 }
3002 labelFeature->setOverlapHandling( overlapHandling );
3003 }
3004
3005 labelFeature->setPrioritization( mPlacementSettings.prioritization() );
3006
3007 QgsLabelObstacleSettings os = mObstacleSettings;
3008 os.setIsObstacle( isObstacle );
3009 os.updateDataDefinedProperties( mDataDefinedProperties, context.expressionContext() );
3010 os.setObstacleGeometry( obstacleGeometry );
3011 labelFeature->setObstacleSettings( os );
3012
3013 QVector< Qgis::LabelPredefinedPointPosition > positionOrder = pointSettings.predefinedPositionOrder();
3014 if ( positionOrder.isEmpty() )
3015 positionOrder = *DEFAULT_PLACEMENT_ORDER();
3016
3018 {
3020 QString dataDefinedOrder = mDataDefinedProperties.valueAsString( QgsPalLayerSettings::Property::PredefinedPositionOrder, context.expressionContext() );
3021 if ( !dataDefinedOrder.isEmpty() )
3022 {
3023 positionOrder = QgsLabelingUtils::decodePredefinedPositionOrder( dataDefinedOrder );
3024 }
3025 }
3026 labelFeature->setPredefinedPositionOrder( positionOrder );
3027
3028 // add parameters for data defined labeling to label feature
3029 labelFeature->setDataDefinedValues( dataDefinedValues );
3030
3031 return labelFeature;
3032}
3033
3034std::unique_ptr<QgsLabelFeature> QgsPalLayerSettings::registerObstacleFeature( const QgsFeature &f, QgsRenderContext &context, const QgsGeometry &obstacleGeometry )
3035{
3036 mCurFeat = &f;
3037
3038 QgsGeometry geom;
3039 if ( !obstacleGeometry.isNull() )
3040 {
3041 geom = obstacleGeometry;
3042 }
3043 else
3044 {
3045 geom = f.geometry();
3046 }
3047
3048 if ( geom.isNull() )
3049 {
3050 return nullptr;
3051 }
3052
3053 // don't even try to register linestrings with only one vertex as an obstacle
3054 if ( const QgsLineString *ls = qgsgeometry_cast< const QgsLineString * >( geom.constGet() ) )
3055 {
3056 if ( ls->numPoints() < 2 )
3057 return nullptr;
3058 }
3059
3060 // simplify?
3061 const QgsVectorSimplifyMethod &simplifyMethod = context.vectorSimplifyMethod();
3062 std::unique_ptr<QgsGeometry> scopedClonedGeom;
3064 {
3065 int simplifyHints = simplifyMethod.simplifyHints() | QgsMapToPixelSimplifier::SimplifyEnvelope;
3066 const Qgis::VectorSimplificationAlgorithm simplifyAlgorithm = simplifyMethod.simplifyAlgorithm();
3067 QgsMapToPixelSimplifier simplifier( simplifyHints, simplifyMethod.tolerance(), simplifyAlgorithm );
3068 geom = simplifier.simplify( geom );
3069 }
3070
3071 geos::unique_ptr geos_geom_clone;
3072 std::unique_ptr<QgsGeometry> scopedPreparedGeom;
3073
3074 if ( QgsPalLabeling::geometryRequiresPreparation( geom, context, ct, extentGeom, mLineSettings.mergeLines() ) )
3075 {
3076 geom = QgsPalLabeling::prepareGeometry( geom, context, ct, extentGeom, mLineSettings.mergeLines() );
3077 }
3078 geos_geom_clone = QgsGeos::asGeos( geom );
3079
3080 if ( !geos_geom_clone )
3081 return nullptr; // invalid geometry
3082
3083 // feature to the layer
3084 std::unique_ptr< QgsLabelFeature > obstacleFeature = std::make_unique< QgsLabelFeature >( f.id(), std::move( geos_geom_clone ), QSizeF( 0, 0 ) );
3085 obstacleFeature->setFeature( f );
3086
3087 QgsLabelObstacleSettings os = mObstacleSettings;
3088 os.setIsObstacle( true );
3089 os.updateDataDefinedProperties( mDataDefinedProperties, context.expressionContext() );
3090 obstacleFeature->setObstacleSettings( os );
3091
3092 mFeatsRegPal++;
3093 return obstacleFeature;
3094}
3095
3096bool QgsPalLayerSettings::dataDefinedValEval( DataDefinedValueType valType,
3098 QVariant &exprVal, QgsExpressionContext &context, const QVariant &originalValue )
3099{
3100 if ( !mDataDefinedProperties.isActive( p ) )
3101 return false;
3102
3103 context.setOriginalValueVariable( originalValue );
3104 exprVal = mDataDefinedProperties.value( p, context );
3105 if ( !QgsVariantUtils::isNull( exprVal ) )
3106 {
3107 switch ( valType )
3108 {
3109 case DDBool:
3110 {
3111 bool bol = exprVal.toBool();
3112 dataDefinedValues.insert( p, QVariant( bol ) );
3113 return true;
3114 }
3115 case DDInt:
3116 {
3117 bool ok;
3118 int size = exprVal.toInt( &ok );
3119
3120 if ( ok )
3121 {
3122 dataDefinedValues.insert( p, QVariant( size ) );
3123 return true;
3124 }
3125 return false;
3126 }
3127 case DDIntPos:
3128 {
3129 bool ok;
3130 int size = exprVal.toInt( &ok );
3131
3132 if ( ok && size > 0 )
3133 {
3134 dataDefinedValues.insert( p, QVariant( size ) );
3135 return true;
3136 }
3137 return false;
3138 }
3139 case DDDouble:
3140 {
3141 bool ok;
3142 double size = exprVal.toDouble( &ok );
3143
3144 if ( ok )
3145 {
3146 dataDefinedValues.insert( p, QVariant( size ) );
3147 return true;
3148 }
3149 return false;
3150 }
3151 case DDDoublePos:
3152 {
3153 bool ok;
3154 double size = exprVal.toDouble( &ok );
3155
3156 if ( ok && size > 0.0 )
3157 {
3158 dataDefinedValues.insert( p, QVariant( size ) );
3159 return true;
3160 }
3161 return false;
3162 }
3163 case DDRotation180:
3164 {
3165 bool ok;
3166 double rot = exprVal.toDouble( &ok );
3167 if ( ok )
3168 {
3169 if ( rot < -180.0 && rot >= -360 )
3170 {
3171 rot += 360;
3172 }
3173 if ( rot > 180.0 && rot <= 360 )
3174 {
3175 rot -= 360;
3176 }
3177 if ( rot >= -180 && rot <= 180 )
3178 {
3179 dataDefinedValues.insert( p, QVariant( rot ) );
3180 return true;
3181 }
3182 }
3183 return false;
3184 }
3185 case DDOpacity:
3186 {
3187 bool ok;
3188 int size = exprVal.toInt( &ok );
3189 if ( ok && size >= 0 && size <= 100 )
3190 {
3191 dataDefinedValues.insert( p, QVariant( size ) );
3192 return true;
3193 }
3194 return false;
3195 }
3196 case DDString:
3197 {
3198 QString str = exprVal.toString(); // don't trim whitespace
3199
3200 dataDefinedValues.insert( p, QVariant( str ) ); // let it stay empty if it is
3201 return true;
3202 }
3203 case DDUnits:
3204 {
3205 QString unitstr = exprVal.toString().trimmed();
3206
3207 if ( !unitstr.isEmpty() )
3208 {
3209 dataDefinedValues.insert( p, QVariant( static_cast< int >( QgsUnitTypes::decodeRenderUnit( unitstr ) ) ) );
3210 return true;
3211 }
3212 return false;
3213 }
3214 case DDColor:
3215 {
3216 QString colorstr = exprVal.toString().trimmed();
3217 QColor color = QgsColorUtils::colorFromString( colorstr );
3218
3219 if ( color.isValid() )
3220 {
3221 dataDefinedValues.insert( p, QVariant( color ) );
3222 return true;
3223 }
3224 return false;
3225 }
3226 case DDJoinStyle:
3227 {
3228 QString joinstr = exprVal.toString().trimmed();
3229
3230 if ( !joinstr.isEmpty() )
3231 {
3232 dataDefinedValues.insert( p, QVariant( static_cast< int >( QgsSymbolLayerUtils::decodePenJoinStyle( joinstr ) ) ) );
3233 return true;
3234 }
3235 return false;
3236 }
3237 case DDBlendMode:
3238 {
3239 QString blendstr = exprVal.toString().trimmed();
3240
3241 if ( !blendstr.isEmpty() )
3242 {
3243 dataDefinedValues.insert( p, QVariant( static_cast< int >( QgsSymbolLayerUtils::decodeBlendMode( blendstr ) ) ) );
3244 return true;
3245 }
3246 return false;
3247 }
3248 case DDPointF:
3249 {
3250 bool ok = false;
3251 const QPointF res = QgsSymbolLayerUtils::toPoint( exprVal, &ok );
3252 if ( ok )
3253 {
3254 dataDefinedValues.insert( p, res );
3255 return true;
3256 }
3257 return false;
3258 }
3259 case DDSizeF:
3260 {
3261 bool ok = false;
3262 const QSizeF res = QgsSymbolLayerUtils::toSize( exprVal, &ok );
3263 if ( ok )
3264 {
3265 dataDefinedValues.insert( p, res );
3266 return true;
3267 }
3268 return false;
3269 }
3270 }
3271 }
3272 return false;
3273}
3274
3275void QgsPalLayerSettings::parseTextStyle( QFont &labelFont,
3276 Qgis::RenderUnit fontunits,
3277 QgsRenderContext &context )
3278{
3279 // NOTE: labelFont already has pixelSize set, so pointSize or pointSizeF might return -1
3280
3281 QVariant exprVal; // value() is repeatedly nulled on data defined evaluation and replaced when successful
3282
3283 // Two ways to generate new data defined font:
3284 // 1) Family + [bold] + [italic] (named style is ignored and font is built off of base family)
3285 // 2) Family + named style (bold or italic is ignored)
3286
3287 // data defined font family?
3288 QString ddFontFamily;
3289 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::Family ) )
3290 {
3291 context.expressionContext().setOriginalValueVariable( labelFont.family() );
3292 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Property::Family, context.expressionContext() );
3293 if ( !QgsVariantUtils::isNull( exprVal ) )
3294 {
3295 QString family = exprVal.toString().trimmed();
3296 QgsDebugMsgLevel( QStringLiteral( "exprVal Font family:%1" ).arg( family ), 4 );
3297
3299 if ( labelFont.family() != family )
3300 {
3301 // testing for ddFontFamily in QFontDatabase.families() may be slow to do for every feature
3302 // (i.e. don't use QgsFontUtils::fontFamilyMatchOnSystem( family ) here)
3303 if ( QgsFontUtils::fontFamilyOnSystem( family ) )
3304 {
3305 ddFontFamily = family;
3306 }
3307 }
3308 }
3309 }
3310
3311 // data defined named font style?
3312 QString ddFontStyle;
3313 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::FontStyle ) )
3314 {
3315 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Property::FontStyle, context.expressionContext() );
3316 if ( !QgsVariantUtils::isNull( exprVal ) )
3317 {
3318 QString fontstyle = exprVal.toString().trimmed();
3319 QgsDebugMsgLevel( QStringLiteral( "exprVal Font style:%1" ).arg( fontstyle ), 4 );
3320 ddFontStyle = fontstyle;
3321 }
3322 }
3323
3324 // data defined bold font style?
3325 bool ddBold = false;
3326 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::Bold ) )
3327 {
3328 context.expressionContext().setOriginalValueVariable( labelFont.bold() );
3329 ddBold = mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::Property::Bold, context.expressionContext(), false );
3330 }
3331
3332 // data defined italic font style?
3333 bool ddItalic = false;
3334 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::Italic ) )
3335 {
3336 context.expressionContext().setOriginalValueVariable( labelFont.italic() );
3337 ddItalic = mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::Property::Italic, context.expressionContext(), false );
3338 }
3339
3340 // TODO: update when pref for how to resolve missing family (use matching algorithm or just default font) is implemented
3341 // (currently defaults to what has been read in from layer settings)
3342 QFont newFont;
3343 QFont appFont = QApplication::font();
3344 bool newFontBuilt = false;
3345 if ( ddBold || ddItalic )
3346 {
3347 // new font needs built, since existing style needs removed
3348 newFont = QgsFontUtils::createFont( !ddFontFamily.isEmpty() ? ddFontFamily : labelFont.family() );
3349 newFontBuilt = true;
3350 newFont.setBold( ddBold );
3351 newFont.setItalic( ddItalic );
3352 }
3353 else if ( !ddFontStyle.isEmpty()
3354 && ddFontStyle.compare( QLatin1String( "Ignore" ), Qt::CaseInsensitive ) != 0 )
3355 {
3356 if ( !ddFontFamily.isEmpty() )
3357 {
3358 // both family and style are different, build font from database
3359 if ( !mFontDB )
3360 mFontDB = std::make_unique< QFontDatabase >();
3361
3362 QFont styledfont = mFontDB->font( ddFontFamily, ddFontStyle, appFont.pointSize() );
3363 if ( appFont != styledfont )
3364 {
3365 newFont = styledfont;
3366 newFontBuilt = true;
3367 }
3368 }
3369
3370 // update the font face style
3371 QgsFontUtils::updateFontViaStyle( newFontBuilt ? newFont : labelFont, ddFontStyle );
3372 }
3373 else if ( !ddFontFamily.isEmpty() )
3374 {
3375 if ( ddFontStyle.compare( QLatin1String( "Ignore" ), Qt::CaseInsensitive ) != 0 )
3376 {
3377 // just family is different, build font from database
3378 if ( !mFontDB )
3379 mFontDB = std::make_unique< QFontDatabase >();
3380 QFont styledfont = mFontDB->font( ddFontFamily, mFormat.namedStyle(), appFont.pointSize() );
3381 if ( appFont != styledfont )
3382 {
3383 newFont = styledfont;
3384 newFontBuilt = true;
3385 }
3386 }
3387 else
3388 {
3389 newFont = QgsFontUtils::createFont( ddFontFamily );
3390 newFontBuilt = true;
3391 }
3392 }
3393
3394 if ( newFontBuilt )
3395 {
3396 // copy over existing font settings
3397 //newFont = newFont.resolve( labelFont ); // should work, but let's be sure what's being copied
3398 newFont.setPixelSize( labelFont.pixelSize() );
3399 newFont.setUnderline( labelFont.underline() );
3400 newFont.setStrikeOut( labelFont.strikeOut() );
3401 newFont.setWordSpacing( labelFont.wordSpacing() );
3402 newFont.setLetterSpacing( QFont::AbsoluteSpacing, labelFont.letterSpacing() );
3403
3404 labelFont = newFont;
3405 }
3406
3407 // data defined word spacing?
3408 double wordspace = labelFont.wordSpacing();
3409 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::FontWordSpacing ) )
3410 {
3411 context.expressionContext().setOriginalValueVariable( wordspace );
3412 wordspace = mDataDefinedProperties.valueAsDouble( QgsPalLayerSettings::Property::FontWordSpacing, context.expressionContext(), wordspace );
3413 }
3414 labelFont.setWordSpacing( context.convertToPainterUnits( wordspace, fontunits, mFormat.sizeMapUnitScale() ) );
3415
3416 // data defined letter spacing?
3417 double letterspace = labelFont.letterSpacing();
3418 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::FontLetterSpacing ) )
3419 {
3420 context.expressionContext().setOriginalValueVariable( letterspace );
3421 letterspace = mDataDefinedProperties.valueAsDouble( QgsPalLayerSettings::Property::FontLetterSpacing, context.expressionContext(), letterspace );
3422 }
3423 labelFont.setLetterSpacing( QFont::AbsoluteSpacing, context.convertToPainterUnits( letterspace, fontunits, mFormat.sizeMapUnitScale() ) );
3424
3425 // data defined strikeout font style?
3426 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::Strikeout ) )
3427 {
3428 context.expressionContext().setOriginalValueVariable( labelFont.strikeOut() );
3429 bool strikeout = mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::Property::Strikeout, context.expressionContext(), false );
3430 labelFont.setStrikeOut( strikeout );
3431 }
3432
3433 // data defined stretch
3434 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::FontStretchFactor ) )
3435 {
3437 labelFont.setStretch( mDataDefinedProperties.valueAsInt( QgsPalLayerSettings::Property::FontStretchFactor, context.expressionContext(), mFormat.stretchFactor() ) );
3438 }
3439
3440 // data defined underline font style?
3441 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::Underline ) )
3442 {
3443 context.expressionContext().setOriginalValueVariable( labelFont.underline() );
3444 bool underline = mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::Property::Underline, context.expressionContext(), false );
3445 labelFont.setUnderline( underline );
3446 }
3447
3448 // pass the rest on to QgsPalLabeling::drawLabeling
3449
3450 // data defined font color?
3451 dataDefinedValEval( DDColor, QgsPalLayerSettings::Property::Color, exprVal, context.expressionContext(), QgsColorUtils::colorToString( mFormat.color() ) );
3452
3453 // data defined font opacity?
3454 dataDefinedValEval( DDOpacity, QgsPalLayerSettings::Property::FontOpacity, exprVal, context.expressionContext(), mFormat.opacity() * 100 );
3455
3456 // data defined font blend mode?
3457 dataDefinedValEval( DDBlendMode, QgsPalLayerSettings::Property::FontBlendMode, exprVal, context.expressionContext() );
3458
3459}
3460
3461void QgsPalLayerSettings::parseTextBuffer( QgsRenderContext &context )
3462{
3463 QVariant exprVal; // value() is repeatedly nulled on data defined evaluation and replaced when successful
3464
3465 QgsTextBufferSettings buffer = mFormat.buffer();
3466
3467 // data defined draw buffer?
3468 bool drawBuffer = mFormat.buffer().enabled();
3469 if ( dataDefinedValEval( DDBool, QgsPalLayerSettings::Property::BufferDraw, exprVal, context.expressionContext(), buffer.enabled() ) )
3470 {
3471 drawBuffer = exprVal.toBool();
3472 }
3473 else if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::BufferDraw ) && QgsVariantUtils::isNull( exprVal ) )
3474 {
3475 dataDefinedValues.insert( QgsPalLayerSettings::Property::BufferDraw, QVariant( drawBuffer ) );
3476 }
3477
3478 if ( !drawBuffer )
3479 {
3480 return;
3481 }
3482
3483 // data defined buffer size?
3484 double bufrSize = buffer.size();
3485 if ( dataDefinedValEval( DDDoublePos, QgsPalLayerSettings::Property::BufferSize, exprVal, context.expressionContext(), buffer.size() ) )
3486 {
3487 bufrSize = exprVal.toDouble();
3488 }
3489
3490 // data defined buffer transparency?
3491 double bufferOpacity = buffer.opacity() * 100;
3492 if ( dataDefinedValEval( DDOpacity, QgsPalLayerSettings::Property::BufferOpacity, exprVal, context.expressionContext(), bufferOpacity ) )
3493 {
3494 bufferOpacity = exprVal.toDouble();
3495 }
3496
3497 drawBuffer = ( drawBuffer && bufrSize > 0.0 && bufferOpacity > 0 );
3498
3499 if ( !drawBuffer )
3500 {
3501 dataDefinedValues.insert( QgsPalLayerSettings::Property::BufferDraw, QVariant( false ) ); // trigger value
3502 dataDefinedValues.remove( QgsPalLayerSettings::Property::BufferSize );
3503 dataDefinedValues.remove( QgsPalLayerSettings::Property::BufferOpacity );
3504 return; // don't bother evaluating values that won't be used
3505 }
3506
3507 // data defined buffer units?
3508 dataDefinedValEval( DDUnits, QgsPalLayerSettings::Property::BufferUnit, exprVal, context.expressionContext() );
3509
3510 // data defined buffer color?
3511 dataDefinedValEval( DDColor, QgsPalLayerSettings::Property::BufferColor, exprVal, context.expressionContext(), QgsColorUtils::colorToString( buffer.color() ) );
3512
3513 // data defined buffer pen join style?
3514 dataDefinedValEval( DDJoinStyle, QgsPalLayerSettings::Property::BufferJoinStyle, exprVal, context.expressionContext(), QgsSymbolLayerUtils::encodePenJoinStyle( buffer.joinStyle() ) );
3515
3516 // data defined buffer blend mode?
3517 dataDefinedValEval( DDBlendMode, QgsPalLayerSettings::Property::BufferBlendMode, exprVal, context.expressionContext() );
3518}
3519
3520void QgsPalLayerSettings::parseTextMask( QgsRenderContext &context )
3521{
3522 QVariant exprVal; // value() is repeatedly nulled on data defined evaluation and replaced when successful
3523
3524 QgsTextMaskSettings mask = mFormat.mask();
3525
3526 // data defined enabled mask?
3527 bool maskEnabled = mask.enabled();
3528 if ( dataDefinedValEval( DDBool, QgsPalLayerSettings::Property::MaskEnabled, exprVal, context.expressionContext(), mask.enabled() ) )
3529 {
3530 maskEnabled = exprVal.toBool();
3531 }
3532
3533 if ( !maskEnabled )
3534 {
3535 return;
3536 }
3537
3538 // data defined buffer size?
3539 double bufrSize = mask.size();
3540 if ( dataDefinedValEval( DDDoublePos, QgsPalLayerSettings::Property::MaskBufferSize, exprVal, context.expressionContext(), mask.size() ) )
3541 {
3542 bufrSize = exprVal.toDouble();
3543 }
3544
3545 // data defined opacity?
3546 double opacity = mask.opacity() * 100;
3547 if ( dataDefinedValEval( DDOpacity, QgsPalLayerSettings::Property::MaskOpacity, exprVal, context.expressionContext(), opacity ) )
3548 {
3549 opacity = exprVal.toDouble();
3550 }
3551
3552 maskEnabled = ( maskEnabled && bufrSize > 0.0 && opacity > 0 );
3553
3554 if ( !maskEnabled )
3555 {
3556 dataDefinedValues.insert( QgsPalLayerSettings::Property::MaskEnabled, QVariant( false ) ); // trigger value
3557 dataDefinedValues.remove( QgsPalLayerSettings::Property::MaskBufferSize );
3558 dataDefinedValues.remove( QgsPalLayerSettings::Property::MaskOpacity );
3559 return; // don't bother evaluating values that won't be used
3560 }
3561
3562 // data defined buffer units?
3563 dataDefinedValEval( DDUnits, QgsPalLayerSettings::Property::MaskBufferUnit, exprVal, context.expressionContext() );
3564
3565 // data defined buffer pen join style?
3566 dataDefinedValEval( DDJoinStyle, QgsPalLayerSettings::Property::MaskJoinStyle, exprVal, context.expressionContext(), QgsSymbolLayerUtils::encodePenJoinStyle( mask.joinStyle() ) );
3567}
3568
3569void QgsPalLayerSettings::parseTextFormatting( QgsRenderContext &context )
3570{
3571 QVariant exprVal; // value() is repeatedly nulled on data defined evaluation and replaced when successful
3572
3573 // data defined multiline wrap character?
3574 QString wrapchr = wrapChar;
3575 if ( dataDefinedValEval( DDString, QgsPalLayerSettings::Property::MultiLineWrapChar, exprVal, context.expressionContext(), wrapChar ) )
3576 {
3577 wrapchr = exprVal.toString();
3578 }
3579
3580 int evalAutoWrapLength = autoWrapLength;
3581 if ( dataDefinedValEval( DDInt, QgsPalLayerSettings::Property::AutoWrapLength, exprVal, context.expressionContext(), evalAutoWrapLength ) )
3582 {
3583 evalAutoWrapLength = exprVal.toInt();
3584 }
3585
3586 // data defined multiline height?
3587 dataDefinedValEval( DDDouble, QgsPalLayerSettings::Property::MultiLineHeight, exprVal, context.expressionContext() );
3588
3589 // data defined multiline text align?
3590 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::MultiLineAlignment ) )
3591 {
3592 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Property::MultiLineAlignment, context.expressionContext() );
3593 if ( !QgsVariantUtils::isNull( exprVal ) )
3594 {
3595 QString str = exprVal.toString().trimmed();
3596 QgsDebugMsgLevel( QStringLiteral( "exprVal MultiLineAlignment:%1" ).arg( str ), 4 );
3597
3598 if ( !str.isEmpty() )
3599 {
3600 // "Left"
3602
3603 if ( str.compare( QLatin1String( "Center" ), Qt::CaseInsensitive ) == 0 )
3604 {
3606 }
3607 else if ( str.compare( QLatin1String( "Right" ), Qt::CaseInsensitive ) == 0 )
3608 {
3610 }
3611 else if ( str.compare( QLatin1String( "Follow" ), Qt::CaseInsensitive ) == 0 )
3612 {
3614 }
3615 else if ( str.compare( QLatin1String( "Justify" ), Qt::CaseInsensitive ) == 0 )
3616 {
3618 }
3619 dataDefinedValues.insert( QgsPalLayerSettings::Property::MultiLineAlignment, QVariant( static_cast< int >( aligntype ) ) );
3620 }
3621 }
3622 }
3623
3624 // text orientation
3625 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::TextOrientation ) )
3626 {
3627 const QString encoded = QgsTextRendererUtils::encodeTextOrientation( mFormat.orientation() );
3628 context.expressionContext().setOriginalValueVariable( encoded );
3629 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Property::TextOrientation, context.expressionContext() );
3630 if ( !QgsVariantUtils::isNull( exprVal ) )
3631 {
3632 QString str = exprVal.toString().trimmed();
3633 if ( !str.isEmpty() )
3634 dataDefinedValues.insert( QgsPalLayerSettings::Property::TextOrientation, str );
3635 }
3636 }
3637
3638 // data defined direction symbol?
3639 bool drawDirSymb = mLineSettings.addDirectionSymbol();
3640 if ( dataDefinedValEval( DDBool, QgsPalLayerSettings::Property::DirSymbDraw, exprVal, context.expressionContext(), drawDirSymb ) )
3641 {
3642 drawDirSymb = exprVal.toBool();
3643 }
3644
3645 if ( drawDirSymb )
3646 {
3647 // data defined direction left symbol?
3648 dataDefinedValEval( DDString, QgsPalLayerSettings::Property::DirSymbLeft, exprVal, context.expressionContext(), mLineSettings.leftDirectionSymbol() );
3649
3650 // data defined direction right symbol?
3651 dataDefinedValEval( DDString, QgsPalLayerSettings::Property::DirSymbRight, exprVal, context.expressionContext(), mLineSettings.rightDirectionSymbol() );
3652
3653 // data defined direction symbol placement?
3654 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Property::DirSymbPlacement, context.expressionContext() );
3655 if ( !QgsVariantUtils::isNull( exprVal ) )
3656 {
3657 QString str = exprVal.toString().trimmed();
3658 QgsDebugMsgLevel( QStringLiteral( "exprVal DirSymbPlacement:%1" ).arg( str ), 4 );
3659
3660 if ( !str.isEmpty() )
3661 {
3662 // "LeftRight"
3664
3665 if ( str.compare( QLatin1String( "Above" ), Qt::CaseInsensitive ) == 0 )
3666 {
3668 }
3669 else if ( str.compare( QLatin1String( "Below" ), Qt::CaseInsensitive ) == 0 )
3670 {
3672 }
3673 dataDefinedValues.insert( QgsPalLayerSettings::Property::DirSymbPlacement, QVariant( static_cast< int >( placetype ) ) );
3674 }
3675 }
3676
3677 // data defined direction symbol reversed?
3678 dataDefinedValEval( DDBool, QgsPalLayerSettings::Property::DirSymbReverse, exprVal, context.expressionContext(), mLineSettings.reverseDirectionSymbol() );
3679 }
3680
3681 // formatting for numbers is inline with generation of base label text and not passed to label painting
3682}
3683
3684void QgsPalLayerSettings::parseShapeBackground( QgsRenderContext &context )
3685{
3686 QVariant exprVal; // value() is repeatedly nulled on data defined evaluation and replaced when successful
3687
3688 QgsTextBackgroundSettings background = mFormat.background();
3689
3690 // data defined draw shape?
3691 bool drawShape = background.enabled();
3692 if ( dataDefinedValEval( DDBool, QgsPalLayerSettings::Property::ShapeDraw, exprVal, context.expressionContext(), drawShape ) )
3693 {
3694 drawShape = exprVal.toBool();
3695 }
3696
3697 if ( !drawShape )
3698 {
3699 return;
3700 }
3701
3702 // data defined shape transparency?
3703 double shapeOpacity = background.opacity() * 100;
3704 if ( dataDefinedValEval( DDOpacity, QgsPalLayerSettings::Property::ShapeOpacity, exprVal, context.expressionContext(), shapeOpacity ) )
3705 {
3706 shapeOpacity = 100.0 * exprVal.toDouble();
3707 }
3708
3709 drawShape = ( drawShape && shapeOpacity > 0 ); // size is not taken into account (could be)
3710
3711 if ( !drawShape )
3712 {
3713 dataDefinedValues.insert( QgsPalLayerSettings::Property::ShapeDraw, QVariant( false ) ); // trigger value
3714 dataDefinedValues.remove( QgsPalLayerSettings::Property::ShapeOpacity );
3715 return; // don't bother evaluating values that won't be used
3716 }
3717
3718 // data defined shape kind?
3719 QgsTextBackgroundSettings::ShapeType shapeKind = background.type();
3720 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::ShapeKind ) )
3721 {
3722 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Property::ShapeKind, context.expressionContext() );
3723 if ( !QgsVariantUtils::isNull( exprVal ) )
3724 {
3725 QString skind = exprVal.toString().trimmed();
3726 QgsDebugMsgLevel( QStringLiteral( "exprVal ShapeKind:%1" ).arg( skind ), 4 );
3727
3728 if ( !skind.isEmpty() )
3729 {
3730 shapeKind = QgsTextRendererUtils::decodeShapeType( skind );
3731 dataDefinedValues.insert( QgsPalLayerSettings::Property::ShapeKind, QVariant( static_cast< int >( shapeKind ) ) );
3732 }
3733 }
3734 }
3735
3736 // data defined shape SVG path?
3737 QString svgPath = background.svgFile();
3738 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::ShapeSVGFile ) )
3739 {
3740 context.expressionContext().setOriginalValueVariable( svgPath );
3741 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Property::ShapeSVGFile, context.expressionContext() );
3742 if ( !QgsVariantUtils::isNull( exprVal ) )
3743 {
3744 QString svgfile = exprVal.toString().trimmed();
3745 QgsDebugMsgLevel( QStringLiteral( "exprVal ShapeSVGFile:%1" ).arg( svgfile ), 4 );
3746
3747 // '' empty paths are allowed
3748 svgPath = QgsSymbolLayerUtils::svgSymbolNameToPath( svgfile, context.pathResolver() );
3749 dataDefinedValues.insert( QgsPalLayerSettings::Property::ShapeSVGFile, QVariant( svgPath ) );
3750 }
3751 }
3752
3753 // data defined shape size type?
3754 QgsTextBackgroundSettings::SizeType shpSizeType = background.sizeType();
3755 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::ShapeSizeType ) )
3756 {
3757 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Property::ShapeSizeType, context.expressionContext() );
3758 if ( !QgsVariantUtils::isNull( exprVal ) )
3759 {
3760 QString stype = exprVal.toString().trimmed();
3761 QgsDebugMsgLevel( QStringLiteral( "exprVal ShapeSizeType:%1" ).arg( stype ), 4 );
3762
3763 if ( !stype.isEmpty() )
3764 {
3766 dataDefinedValues.insert( QgsPalLayerSettings::Property::ShapeSizeType, QVariant( static_cast< int >( shpSizeType ) ) );
3767 }
3768 }
3769 }
3770
3771 // data defined shape size X? (SVGs only use X for sizing)
3772 double ddShpSizeX = background.size().width();
3773 if ( dataDefinedValEval( DDDouble, QgsPalLayerSettings::Property::ShapeSizeX, exprVal, context.expressionContext(), ddShpSizeX ) )
3774 {
3775 ddShpSizeX = exprVal.toDouble();
3776 }
3777
3778 // data defined shape size Y?
3779 double ddShpSizeY = background.size().height();
3780 if ( dataDefinedValEval( DDDouble, QgsPalLayerSettings::Property::ShapeSizeY, exprVal, context.expressionContext(), ddShpSizeY ) )
3781 {
3782 ddShpSizeY = exprVal.toDouble();
3783 }
3784
3785 // don't continue under certain circumstances (e.g. size is fixed)
3786 bool skip = false;
3787 if ( shapeKind == QgsTextBackgroundSettings::ShapeSVG
3788 && ( svgPath.isEmpty()
3789 || ( !svgPath.isEmpty()
3790 && shpSizeType == QgsTextBackgroundSettings::SizeFixed
3791 && ddShpSizeX == 0.0 ) ) )
3792 {
3793 skip = true;
3794 }
3796 && ( !background.markerSymbol()
3797 || ( background.markerSymbol()
3798 && shpSizeType == QgsTextBackgroundSettings::SizeFixed
3799 && ddShpSizeX == 0.0 ) ) )
3800 {
3801 skip = true;
3802 }
3803 if ( shapeKind != QgsTextBackgroundSettings::ShapeSVG
3805 && shpSizeType == QgsTextBackgroundSettings::SizeFixed
3806 && ( ddShpSizeX == 0.0 || ddShpSizeY == 0.0 ) )
3807 {
3808 skip = true;
3809 }
3810
3811 if ( skip )
3812 {
3813 dataDefinedValues.insert( QgsPalLayerSettings::Property::ShapeDraw, QVariant( false ) ); // trigger value
3814 dataDefinedValues.remove( QgsPalLayerSettings::Property::ShapeOpacity );
3815 dataDefinedValues.remove( QgsPalLayerSettings::Property::ShapeKind );
3816 dataDefinedValues.remove( QgsPalLayerSettings::Property::ShapeSVGFile );
3817 dataDefinedValues.remove( QgsPalLayerSettings::Property::ShapeSizeX );
3818 dataDefinedValues.remove( QgsPalLayerSettings::Property::ShapeSizeY );
3819 return; // don't bother evaluating values that won't be used
3820 }
3821
3822 // data defined shape size units?
3823 dataDefinedValEval( DDUnits, QgsPalLayerSettings::Property::ShapeSizeUnits, exprVal, context.expressionContext() );
3824
3825 // data defined shape rotation type?
3826 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::ShapeRotationType ) )
3827 {
3828 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Property::ShapeRotationType, context.expressionContext() );
3829 if ( !QgsVariantUtils::isNull( exprVal ) )
3830 {
3831 QString rotstr = exprVal.toString().trimmed();
3832 QgsDebugMsgLevel( QStringLiteral( "exprVal ShapeRotationType:%1" ).arg( rotstr ), 4 );
3833
3834 if ( !rotstr.isEmpty() )
3835 {
3836 // "Sync"
3838 dataDefinedValues.insert( QgsPalLayerSettings::Property::ShapeRotationType, QVariant( static_cast< int >( rottype ) ) );
3839 }
3840 }
3841 }
3842
3843 // data defined shape rotation?
3844 dataDefinedValEval( DDRotation180, QgsPalLayerSettings::Property::ShapeRotation, exprVal, context.expressionContext(), background.rotation() );
3845
3846 // data defined shape offset?
3847 dataDefinedValEval( DDPointF, QgsPalLayerSettings::Property::ShapeOffset, exprVal, context.expressionContext(), QgsSymbolLayerUtils::encodePoint( background.offset() ) );
3848
3849 // data defined shape offset units?
3850 dataDefinedValEval( DDUnits, QgsPalLayerSettings::Property::ShapeOffsetUnits, exprVal, context.expressionContext() );
3851
3852 // data defined shape radii?
3853 dataDefinedValEval( DDSizeF, QgsPalLayerSettings::Property::ShapeRadii, exprVal, context.expressionContext(), QgsSymbolLayerUtils::encodeSize( background.radii() ) );
3854
3855 // data defined shape radii units?
3856 dataDefinedValEval( DDUnits, QgsPalLayerSettings::Property::ShapeRadiiUnits, exprVal, context.expressionContext() );
3857
3858 // data defined shape blend mode?
3859 dataDefinedValEval( DDBlendMode, QgsPalLayerSettings::Property::ShapeBlendMode, exprVal, context.expressionContext() );
3860
3861 // data defined shape fill color?
3862 dataDefinedValEval( DDColor, QgsPalLayerSettings::Property::ShapeFillColor, exprVal, context.expressionContext(), QgsColorUtils::colorToString( background.fillColor() ) );
3863
3864 // data defined shape stroke color?
3865 dataDefinedValEval( DDColor, QgsPalLayerSettings::Property::ShapeStrokeColor, exprVal, context.expressionContext(), QgsColorUtils::colorToString( background.strokeColor() ) );
3866
3867 // data defined shape stroke width?
3868 dataDefinedValEval( DDDoublePos, QgsPalLayerSettings::Property::ShapeStrokeWidth, exprVal, context.expressionContext(), background.strokeWidth() );
3869
3870 // data defined shape stroke width units?
3871 dataDefinedValEval( DDUnits, QgsPalLayerSettings::Property::ShapeStrokeWidthUnits, exprVal, context.expressionContext() );
3872
3873 // data defined shape join style?
3874 dataDefinedValEval( DDJoinStyle, QgsPalLayerSettings::Property::ShapeJoinStyle, exprVal, context.expressionContext(), QgsSymbolLayerUtils::encodePenJoinStyle( background.joinStyle() ) );
3875
3876}
3877
3878void QgsPalLayerSettings::parseDropShadow( QgsRenderContext &context )
3879{
3880 QVariant exprVal; // value() is repeatedly nulled on data defined evaluation and replaced when successful
3881
3882 QgsTextShadowSettings shadow = mFormat.shadow();
3883
3884 // data defined draw shadow?
3885 bool drawShadow = shadow.enabled();
3886 if ( dataDefinedValEval( DDBool, QgsPalLayerSettings::Property::ShadowDraw, exprVal, context.expressionContext(), drawShadow ) )
3887 {
3888 drawShadow = exprVal.toBool();
3889 }
3890
3891 if ( !drawShadow )
3892 {
3893 return;
3894 }
3895
3896 // data defined shadow transparency?
3897 double shadowOpacity = shadow.opacity() * 100;
3898 if ( dataDefinedValEval( DDOpacity, QgsPalLayerSettings::Property::ShadowOpacity, exprVal, context.expressionContext(), shadowOpacity ) )
3899 {
3900 shadowOpacity = exprVal.toDouble();
3901 }
3902
3903 // data defined shadow offset distance?
3904 double shadowOffDist = shadow.offsetDistance();
3905 if ( dataDefinedValEval( DDDoublePos, QgsPalLayerSettings::Property::ShadowOffsetDist, exprVal, context.expressionContext(), shadowOffDist ) )
3906 {
3907 shadowOffDist = exprVal.toDouble();
3908 }
3909
3910 // data defined shadow offset distance?
3911 double shadowRad = shadow.blurRadius();
3912 if ( dataDefinedValEval( DDDoublePos, QgsPalLayerSettings::Property::ShadowRadius, exprVal, context.expressionContext(), shadowRad ) )
3913 {
3914 shadowRad = exprVal.toDouble();
3915 }
3916
3917 drawShadow = ( drawShadow && shadowOpacity > 0 && !( shadowOffDist == 0.0 && shadowRad == 0.0 ) );
3918
3919 if ( !drawShadow )
3920 {
3921 dataDefinedValues.insert( QgsPalLayerSettings::Property::ShadowDraw, QVariant( false ) ); // trigger value
3922 dataDefinedValues.remove( QgsPalLayerSettings::Property::ShadowOpacity );
3923 dataDefinedValues.remove( QgsPalLayerSettings::Property::ShadowOffsetDist );
3924 dataDefinedValues.remove( QgsPalLayerSettings::Property::ShadowRadius );
3925 return; // don't bother evaluating values that won't be used
3926 }
3927
3928 // data defined shadow under type?
3929 if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Property::ShadowUnder ) )
3930 {
3931 exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Property::ShadowUnder, context.expressionContext() );
3932 if ( !QgsVariantUtils::isNull( exprVal ) )
3933 {
3934 QString str = exprVal.toString().trimmed();
3935 QgsDebugMsgLevel( QStringLiteral( "exprVal ShadowUnder:%1" ).arg( str ), 4 );
3936
3937 if ( !str.isEmpty() )
3938 {
3940 dataDefinedValues.insert( QgsPalLayerSettings::Property::ShadowUnder, QVariant( static_cast< int >( shdwtype ) ) );
3941 }
3942 }
3943 }
3944
3945 // data defined shadow offset angle?
3946 dataDefinedValEval( DDRotation180, QgsPalLayerSettings::Property::ShadowOffsetAngle, exprVal, context.expressionContext(), shadow.offsetAngle() );
3947
3948 // data defined shadow offset units?
3949 dataDefinedValEval( DDUnits, QgsPalLayerSettings::Property::ShadowOffsetUnits, exprVal, context.expressionContext() );
3950
3951 // data defined shadow radius?
3952 dataDefinedValEval( DDDouble, QgsPalLayerSettings::Property::ShadowRadius, exprVal, context.expressionContext(), shadow.blurRadius() );
3953
3954 // data defined shadow radius units?
3955 dataDefinedValEval( DDUnits, QgsPalLayerSettings::Property::ShadowRadiusUnits, exprVal, context.expressionContext() );
3956
3957 // data defined shadow scale? ( gui bounds to 0-2000, no upper bound here )
3958 dataDefinedValEval( DDIntPos, QgsPalLayerSettings::Property::ShadowScale, exprVal, context.expressionContext(), shadow.scale() );
3959
3960 // data defined shadow color?
3961 dataDefinedValEval( DDColor, QgsPalLayerSettings::Property::ShadowColor, exprVal, context.expressionContext(), QgsColorUtils::colorToString( shadow.color() ) );
3962
3963 // data defined shadow blend mode?
3964 dataDefinedValEval( DDBlendMode, QgsPalLayerSettings::Property::ShadowBlendMode, exprVal, context.expressionContext() );
3965}
3966
3967// -------------
3968
3969
3971{
3972 switch ( layer->type() )
3973 {
3975 {
3976 const QgsVectorLayer *vl = qobject_cast< const QgsVectorLayer * >( layer );
3977 return vl->labelsEnabled() || vl->diagramsEnabled();
3978 }
3979
3981 {
3982 const QgsVectorTileLayer *vl = qobject_cast< const QgsVectorTileLayer * >( layer );
3983 if ( !vl->labeling() )
3984 return false;
3985
3986 if ( const QgsVectorTileBasicLabeling *labeling = dynamic_cast< const QgsVectorTileBasicLabeling *>( vl->labeling() ) )
3987 return !labeling->styles().empty();
3988
3989 return false;
3990 }
3991
3999 return false;
4000 }
4001 return false;
4002}
4003
4004
4005bool QgsPalLabeling::geometryRequiresPreparation( const QgsGeometry &geometry, QgsRenderContext &context, const QgsCoordinateTransform &ct, const QgsGeometry &clipGeometry, bool mergeLines )
4006{
4007 if ( geometry.isNull() )
4008 {
4009 return false;
4010 }
4011
4012 if ( geometry.type() == Qgis::GeometryType::Line && geometry.isMultipart() && mergeLines )
4013 {
4014 return true;
4015 }
4016
4017 //requires reprojection
4018 if ( ct.isValid() && !ct.isShortCircuited() )
4019 return true;
4020
4021 //requires rotation
4022 const QgsMapToPixel &m2p = context.mapToPixel();
4023 if ( !qgsDoubleNear( m2p.mapRotation(), 0 ) )
4024 return true;
4025
4026 //requires clip
4027 if ( !clipGeometry.isNull() && !clipGeometry.boundingBox().contains( geometry.boundingBox() ) )
4028 return true;
4029
4030 //requires fixing
4031 if ( geometry.type() == Qgis::GeometryType::Polygon && !geometry.isGeosValid() )
4032 return true;
4033
4034 return false;
4035}
4036
4037QStringList QgsPalLabeling::splitToLines( const QString &text, const QString &wrapCharacter, const int autoWrapLength, const bool useMaxLineLengthWhenAutoWrapping )
4038{
4039 QStringList multiLineSplit;
4040 if ( !wrapCharacter.isEmpty() && wrapCharacter != QLatin1String( "\n" ) )
4041 {
4042 //wrap on both the wrapchr and new line characters
4043 const QStringList lines = text.split( wrapCharacter );
4044 for ( const QString &line : lines )
4045 {
4046 multiLineSplit.append( line.split( '\n' ) );
4047 }
4048 }
4049 else
4050 {
4051 multiLineSplit = text.split( '\n' );
4052 }
4053
4054 // apply auto wrapping to each manually created line
4055 if ( autoWrapLength != 0 )
4056 {
4057 QStringList autoWrappedLines;
4058 autoWrappedLines.reserve( multiLineSplit.count() );
4059 for ( const QString &line : std::as_const( multiLineSplit ) )
4060 {
4061 autoWrappedLines.append( QgsStringUtils::wordWrap( line, autoWrapLength, useMaxLineLengthWhenAutoWrapping ).split( '\n' ) );
4062 }
4063 multiLineSplit = autoWrappedLines;
4064 }
4065 return multiLineSplit;
4066}
4067
4068QStringList QgsPalLabeling::splitToGraphemes( const QString &text )
4069{
4070 QStringList graphemes;
4071 QTextBoundaryFinder boundaryFinder( QTextBoundaryFinder::Grapheme, text );
4072 int currentBoundary = -1;
4073 int previousBoundary = 0;
4074 while ( ( currentBoundary = boundaryFinder.toNextBoundary() ) > 0 )
4075 {
4076 graphemes << text.mid( previousBoundary, currentBoundary - previousBoundary );
4077 previousBoundary = currentBoundary;
4078 }
4079 return graphemes;
4080}
4081
4082QgsGeometry QgsPalLabeling::prepareGeometry( const QgsGeometry &geometry, QgsRenderContext &context, const QgsCoordinateTransform &ct, const QgsGeometry &clipGeometry, bool mergeLines )
4083{
4084 if ( geometry.isNull() )
4085 {
4086 return QgsGeometry();
4087 }
4088
4089 //don't modify the feature's geometry so that geometry based expressions keep working
4090 QgsGeometry geom = geometry;
4091
4092 if ( geom.type() == Qgis::GeometryType::Line && geom.isMultipart() && mergeLines )
4093 {
4094 geom = geom.mergeLines();
4095 }
4096
4097 //reproject the geometry if necessary
4098 if ( ct.isValid() && !ct.isShortCircuited() )
4099 {
4100 try
4101 {
4102 geom.transform( ct );
4103 }
4104 catch ( QgsCsException &cse )
4105 {
4106 Q_UNUSED( cse )
4107 QgsDebugMsgLevel( QStringLiteral( "Ignoring feature due to transformation exception" ), 4 );
4108 return QgsGeometry();
4109 }
4110 // geometry transforms may result in nan points, remove these
4111 geom.filterVertices( []( const QgsPoint & point )->bool
4112 {
4113 return std::isfinite( point.x() ) && std::isfinite( point.y() );
4114 } );
4115 if ( QgsCurvePolygon *cp = qgsgeometry_cast< QgsCurvePolygon * >( geom.get() ) )
4116 {
4117 cp->removeInvalidRings();
4118 }
4119 else if ( QgsMultiSurface *ms = qgsgeometry_cast< QgsMultiSurface * >( geom.get() ) )
4120 {
4121 for ( int i = 0; i < ms->numGeometries(); ++i )
4122 {
4123 if ( QgsCurvePolygon *cp = qgsgeometry_cast< QgsCurvePolygon * >( ms->geometryN( i ) ) )
4124 cp->removeInvalidRings();
4125 }
4126 }
4127 }
4128
4129 // Rotate the geometry if needed, before clipping
4130 const QgsMapToPixel &m2p = context.mapToPixel();
4131 if ( !qgsDoubleNear( m2p.mapRotation(), 0 ) )
4132 {
4133 QgsPointXY center = context.mapExtent().center();
4134 if ( geom.rotate( m2p.mapRotation(), center ) != Qgis::GeometryOperationResult::Success )
4135 {
4136 QgsDebugError( QStringLiteral( "Error rotating geometry" ).arg( geom.asWkt() ) );
4137 return QgsGeometry();
4138 }
4139 }
4140
4141 const bool mustClip = ( !clipGeometry.isNull() &&
4142 ( ( qgsDoubleNear( m2p.mapRotation(), 0 ) && !clipGeometry.boundingBox().contains( geom.boundingBox() ) )
4143 || ( !qgsDoubleNear( m2p.mapRotation(), 0 ) && !clipGeometry.contains( geom ) ) ) );
4144
4145 bool mustClipExact = false;
4146 if ( mustClip )
4147 {
4148 // nice and fast, but can result in invalid geometries. At least it will potentially strip out a bunch of unwanted vertices upfront!
4149 QgsGeometry clipGeom = geom.clipped( clipGeometry.boundingBox() );
4150 if ( clipGeom.isEmpty() )
4151 return QgsGeometry();
4152
4153 geom = clipGeom;
4154
4155 // we've now clipped against the BOUNDING BOX of clipGeometry. But if clipGeometry is an axis parallel rectangle, then there's no
4156 // need to do an exact (potentially costly) intersection clip as well!
4157 mustClipExact = !clipGeometry.isAxisParallelRectangle( 0.001 );
4158 }
4159
4160 // fix invalid polygons
4161 if ( geom.type() == Qgis::GeometryType::Polygon )
4162 {
4163 if ( geom.isMultipart() )
4164 {
4165 // important -- we need to treat ever part in isolation here. We can't test the validity of the whole geometry
4166 // at once, because touching parts would result in an invalid geometry, and buffering this "dissolves" the parts.
4167 // because the actual label engine treats parts as separate entities, we aren't bound by the usual "touching parts are invalid" rule
4168 // see https://github.com/qgis/QGIS/issues/26763
4169 QVector< QgsGeometry> parts;
4170 parts.reserve( qgsgeometry_cast< const QgsGeometryCollection * >( geom.constGet() )->numGeometries() );
4171 for ( auto it = geom.const_parts_begin(); it != geom.const_parts_end(); ++it )
4172 {
4173 QgsGeometry partGeom( ( *it )->clone() );
4174 if ( !partGeom.isGeosValid() )
4175 {
4176
4177 partGeom = partGeom.makeValid();
4178 }
4179 parts.append( partGeom );
4180 }
4181 geom = QgsGeometry::collectGeometry( parts );
4182 }
4183 else if ( !geom.isGeosValid() )
4184 {
4185
4186 QgsGeometry bufferGeom = geom.makeValid();
4187 if ( bufferGeom.isNull() )
4188 {
4189 QgsDebugError( QStringLiteral( "Could not repair geometry: %1" ).arg( bufferGeom.lastError() ) );
4190 return QgsGeometry();
4191 }
4192 geom = bufferGeom;
4193 }
4194 }
4195
4196 if ( mustClipExact )
4197 {
4198 // now do the real intersection against the actual clip geometry
4199 QgsGeometry clipGeom = geom.intersection( clipGeometry );
4200 if ( clipGeom.isEmpty() )
4201 {
4202 return QgsGeometry();
4203 }
4204 geom = clipGeom;
4205 }
4206
4207 return geom;
4208}
4209
4210bool QgsPalLabeling::checkMinimumSizeMM( const QgsRenderContext &context, const QgsGeometry &geom, double minSize )
4211{
4212 if ( minSize <= 0 )
4213 {
4214 return true;
4215 }
4216
4217 if ( geom.isNull() )
4218 {
4219 return false;
4220 }
4221
4222 Qgis::GeometryType featureType = geom.type();
4223 if ( featureType == Qgis::GeometryType::Point ) //minimum size does not apply to point features
4224 {
4225 return true;
4226 }
4227
4228 double mapUnitsPerMM = context.mapToPixel().mapUnitsPerPixel() * context.scaleFactor();
4229 if ( featureType == Qgis::GeometryType::Line )
4230 {
4231 double length = geom.length();
4232 if ( length >= 0.0 )
4233 {
4234 return ( length >= ( minSize * mapUnitsPerMM ) );
4235 }
4236 }
4237 else if ( featureType == Qgis::GeometryType::Polygon )
4238 {
4239 double area = geom.area();
4240 if ( area >= 0.0 )
4241 {
4242 return ( std::sqrt( area ) >= ( minSize * mapUnitsPerMM ) );
4243 }
4244 }
4245 return true; //should never be reached. Return true in this case to label such geometries anyway.
4246}
4247
4248
4249void QgsPalLabeling::dataDefinedTextStyle( QgsPalLayerSettings &tmpLyr,
4250 const QMap< QgsPalLayerSettings::Property, QVariant > &ddValues )
4251{
4252 QgsTextFormat format = tmpLyr.format();
4253 bool changed = false;
4254
4255 //font color
4256 if ( ddValues.contains( QgsPalLayerSettings::Property::Color ) )
4257 {
4258 QVariant ddColor = ddValues.value( QgsPalLayerSettings::Property::Color );
4259 format.setColor( ddColor.value<QColor>() );
4260 changed = true;
4261 }
4262
4263 //font transparency
4264 if ( ddValues.contains( QgsPalLayerSettings::Property::FontOpacity ) )
4265 {
4266 format.setOpacity( ddValues.value( QgsPalLayerSettings::Property::FontOpacity ).toDouble() / 100.0 );
4267 changed = true;
4268 }
4269
4270 //font blend mode
4271 if ( ddValues.contains( QgsPalLayerSettings::Property::FontBlendMode ) )
4272 {
4273 format.setBlendMode( static_cast< QPainter::CompositionMode >( ddValues.value( QgsPalLayerSettings::Property::FontBlendMode ).toInt() ) );
4274 changed = true;
4275 }
4276
4277 if ( changed )
4278 {
4279 tmpLyr.setFormat( format );
4280 }
4281}
4282
4283void QgsPalLabeling::dataDefinedTextFormatting( QgsPalLayerSettings &tmpLyr,
4284 const QMap< QgsPalLayerSettings::Property, QVariant > &ddValues )
4285{
4286 if ( ddValues.contains( QgsPalLayerSettings::Property::MultiLineWrapChar ) )
4287 {
4288 tmpLyr.wrapChar = ddValues.value( QgsPalLayerSettings::Property::MultiLineWrapChar ).toString();
4289 }
4290
4291 if ( ddValues.contains( QgsPalLayerSettings::Property::AutoWrapLength ) )
4292 {
4293 tmpLyr.autoWrapLength = ddValues.value( QgsPalLayerSettings::Property::AutoWrapLength ).toInt();
4294 }
4295
4296 if ( ddValues.contains( QgsPalLayerSettings::Property::MultiLineHeight ) )
4297 {
4298 QgsTextFormat format = tmpLyr.format();
4299 format.setLineHeight( ddValues.value( QgsPalLayerSettings::Property::MultiLineHeight ).toDouble() );
4300 tmpLyr.setFormat( format );
4301 }
4302
4303 if ( ddValues.contains( QgsPalLayerSettings::Property::MultiLineAlignment ) )
4304 {
4305 tmpLyr.multilineAlign = static_cast< Qgis::LabelMultiLineAlignment >( ddValues.value( QgsPalLayerSettings::Property::MultiLineAlignment ).toInt() );
4306 }
4307
4308 if ( ddValues.contains( QgsPalLayerSettings::Property::TextOrientation ) )
4309 {
4310 QgsTextFormat format = tmpLyr.format();
4312 tmpLyr.setFormat( format );
4313 }
4314
4315 if ( ddValues.contains( QgsPalLayerSettings::Property::DirSymbDraw ) )
4316 {
4318 }
4319
4320 if ( tmpLyr.lineSettings().addDirectionSymbol() )
4321 {
4322
4323 if ( ddValues.contains( QgsPalLayerSettings::Property::DirSymbLeft ) )
4324 {
4326 }
4327 if ( ddValues.contains( QgsPalLayerSettings::Property::DirSymbRight ) )
4328 {
4330 }
4331
4332 if ( ddValues.contains( QgsPalLayerSettings::Property::DirSymbPlacement ) )
4333 {
4335 }
4336
4337 if ( ddValues.contains( QgsPalLayerSettings::Property::DirSymbReverse ) )
4338 {
4340 }
4341
4342 }
4343}
4344
4345void QgsPalLabeling::dataDefinedTextBuffer( QgsPalLayerSettings &tmpLyr,
4346 const QMap< QgsPalLayerSettings::Property, QVariant > &ddValues )
4347{
4348 QgsTextBufferSettings buffer = tmpLyr.format().buffer();
4349 bool changed = false;
4350
4351 //buffer draw
4352 if ( ddValues.contains( QgsPalLayerSettings::Property::BufferDraw ) )
4353 {
4354 buffer.setEnabled( ddValues.value( QgsPalLayerSettings::Property::BufferDraw ).toBool() );
4355 changed = true;
4356 }
4357
4358 if ( !buffer.enabled() )
4359 {
4360 if ( changed )
4361 {
4362 QgsTextFormat format = tmpLyr.format();
4363 format.setBuffer( buffer );
4364 tmpLyr.setFormat( format );
4365 }
4366
4367 // tmpLyr.bufferSize > 0.0 && tmpLyr.bufferTransp < 100 figured in during evaluation
4368 return; // don't continue looking for unused values
4369 }
4370
4371 //buffer size
4372 if ( ddValues.contains( QgsPalLayerSettings::Property::BufferSize ) )
4373 {
4374 buffer.setSize( ddValues.value( QgsPalLayerSettings::Property::BufferSize ).toDouble() );
4375 changed = true;
4376 }
4377
4378 //buffer opacity
4379 if ( ddValues.contains( QgsPalLayerSettings::Property::BufferOpacity ) )
4380 {
4381 buffer.setOpacity( ddValues.value( QgsPalLayerSettings::Property::BufferOpacity ).toDouble() / 100.0 );
4382 changed = true;
4383 }
4384
4385 //buffer size units
4386 if ( ddValues.contains( QgsPalLayerSettings::Property::BufferUnit ) )
4387 {
4388 Qgis::RenderUnit bufunit = static_cast< Qgis::RenderUnit >( ddValues.value( QgsPalLayerSettings::Property::BufferUnit ).toInt() );
4389 buffer.setSizeUnit( bufunit );
4390 changed = true;
4391 }
4392
4393 //buffer color
4394 if ( ddValues.contains( QgsPalLayerSettings::Property::BufferColor ) )
4395 {
4396 QVariant ddColor = ddValues.value( QgsPalLayerSettings::Property::BufferColor );
4397 buffer.setColor( ddColor.value<QColor>() );
4398 changed = true;
4399 }
4400
4401 //buffer pen join style
4402 if ( ddValues.contains( QgsPalLayerSettings::Property::BufferJoinStyle ) )
4403 {
4404 buffer.setJoinStyle( static_cast< Qt::PenJoinStyle >( ddValues.value( QgsPalLayerSettings::Property::BufferJoinStyle ).toInt() ) );
4405 changed = true;
4406 }
4407
4408 //buffer blend mode
4409 if ( ddValues.contains( QgsPalLayerSettings::Property::BufferBlendMode ) )
4410 {
4411 buffer.setBlendMode( static_cast< QPainter::CompositionMode >( ddValues.value( QgsPalLayerSettings::Property::BufferBlendMode ).toInt() ) );
4412 changed = true;
4413 }
4414
4415 if ( changed )
4416 {
4417 QgsTextFormat format = tmpLyr.format();
4418 format.setBuffer( buffer );
4419 tmpLyr.setFormat( format );
4420 }
4421}
4422
4423void QgsPalLabeling::dataDefinedTextMask( QgsPalLayerSettings &tmpLyr,
4424 const QMap< QgsPalLayerSettings::Property, QVariant > &ddValues )
4425{
4426 if ( ddValues.isEmpty() )
4427 return;
4428
4429 QgsTextMaskSettings mask = tmpLyr.format().mask();
4430 bool changed = false;
4431
4432 // enabled ?
4433 if ( ddValues.contains( QgsPalLayerSettings::Property::MaskEnabled ) )
4434 {
4435 mask.setEnabled( ddValues.value( QgsPalLayerSettings::Property::MaskEnabled ).toBool() );
4436 changed = true;
4437 }
4438
4439 if ( !mask.enabled() )
4440 {
4441 if ( changed )
4442 {
4443 QgsTextFormat format = tmpLyr.format();
4444 format.setMask( mask );
4445 tmpLyr.setFormat( format );
4446 }
4447
4448 // tmpLyr.bufferSize > 0.0 && tmpLyr.bufferTransp < 100 figured in during evaluation
4449 return; // don't continue looking for unused values
4450 }
4451
4452 // buffer size
4453 if ( ddValues.contains( QgsPalLayerSettings::Property::MaskBufferSize ) )
4454 {
4455 mask.setSize( ddValues.value( QgsPalLayerSettings::Property::MaskBufferSize ).toDouble() );
4456 changed = true;
4457 }
4458
4459 // opacity
4460 if ( ddValues.contains( QgsPalLayerSettings::Property::MaskOpacity ) )
4461 {
4462 mask.setOpacity( ddValues.value( QgsPalLayerSettings::Property::MaskOpacity ).toDouble() / 100.0 );
4463 changed = true;
4464 }
4465
4466 // buffer size units
4467 if ( ddValues.contains( QgsPalLayerSettings::Property::MaskBufferUnit ) )
4468 {
4469 Qgis::RenderUnit bufunit = static_cast< Qgis::RenderUnit >( ddValues.value( QgsPalLayerSettings::Property::MaskBufferUnit ).toInt() );
4470 mask.setSizeUnit( bufunit );
4471 changed = true;
4472 }
4473
4474 // pen join style
4475 if ( ddValues.contains( QgsPalLayerSettings::Property::MaskJoinStyle ) )
4476 {
4477 mask.setJoinStyle( static_cast< Qt::PenJoinStyle >( ddValues.value( QgsPalLayerSettings::Property::MaskJoinStyle ).toInt() ) );
4478 changed = true;
4479 }
4480
4481 if ( changed )
4482 {
4483 QgsTextFormat format = tmpLyr.format();
4484 format.setMask( mask );
4485 tmpLyr.setFormat( format );
4486 }
4487}
4488
4489void QgsPalLabeling::dataDefinedShapeBackground( QgsPalLayerSettings &tmpLyr,
4490 const QMap< QgsPalLayerSettings::Property, QVariant > &ddValues )
4491{
4492 QgsTextBackgroundSettings background = tmpLyr.format().background();
4493 bool changed = false;
4494
4495 //shape draw
4496 if ( ddValues.contains( QgsPalLayerSettings::Property::ShapeDraw ) )
4497 {
4498 background.setEnabled( ddValues.value( QgsPalLayerSettings::Property::ShapeDraw ).toBool() );
4499 changed = true;
4500 }
4501
4502 if ( !background.enabled() )
4503 {
4504 if ( changed )
4505 {
4506 QgsTextFormat format = tmpLyr.format();
4507 format.setBackground( background );
4508 tmpLyr.setFormat( format );
4509 }
4510 return; // don't continue looking for unused values
4511 }
4512
4513 if ( ddValues.contains( QgsPalLayerSettings::Property::ShapeKind ) )
4514 {
4515 background.setType( static_cast< QgsTextBackgroundSettings::ShapeType >( ddValues.value( QgsPalLayerSettings::Property::ShapeKind ).toInt() ) );
4516 changed = true;
4517 }
4518
4519 if ( ddValues.contains( QgsPalLayerSettings::Property::ShapeSVGFile ) )
4520 {
4521 background.setSvgFile( ddValues.value( QgsPalLayerSettings::Property::ShapeSVGFile ).toString() );
4522 changed = true;
4523 }
4524
4525 if ( ddValues.contains( QgsPalLayerSettings::Property::ShapeSizeType ) )
4526 {
4527 background.setSizeType( static_cast< QgsTextBackgroundSettings::SizeType >( ddValues.value( QgsPalLayerSettings::Property::ShapeSizeType ).toInt() ) );
4528 changed = true;
4529 }
4530
4531 if ( ddValues.contains( QgsPalLayerSettings::Property::ShapeSizeX ) )
4532 {
4533 QSizeF size = background.size();
4534 size.setWidth( ddValues.value( QgsPalLayerSettings::Property::ShapeSizeX ).toDouble() );
4535 background.setSize( size );
4536 changed = true;
4537 }
4538 if ( ddValues.contains( QgsPalLayerSettings::Property::ShapeSizeY ) )
4539 {
4540 QSizeF size = background.size();
4541 size.setHeight( ddValues.value( QgsPalLayerSettings::Property::ShapeSizeY ).toDouble() );
4542 background.setSize( size );
4543 changed = true;
4544 }
4545
4546 if ( ddValues.contains( QgsPalLayerSettings::Property::ShapeSizeUnits ) )
4547 {
4548 background.setSizeUnit( static_cast< Qgis::RenderUnit >( ddValues.value( QgsPalLayerSettings::Property::ShapeSizeUnits ).toInt() ) );
4549 changed = true;
4550 }
4551
4552 if ( ddValues.contains( QgsPalLayerSettings::Property::ShapeRotationType ) )
4553 {
4555 changed = true;
4556 }
4557
4558 if ( ddValues.contains( QgsPalLayerSettings::Property::ShapeRotation ) )
4559 {
4560 background.setRotation( ddValues.value( QgsPalLayerSettings::Property::ShapeRotation ).toDouble() );
4561 changed = true;
4562 }
4563
4564 if ( ddValues.contains( QgsPalLayerSettings::Property::ShapeOffset ) )
4565 {
4566 background.setOffset( ddValues.value( QgsPalLayerSettings::Property::ShapeOffset ).toPointF() );
4567 changed = true;
4568 }
4569
4570 if ( ddValues.contains( QgsPalLayerSettings::Property::ShapeOffsetUnits ) )
4571 {
4572 background.setOffsetUnit( static_cast< Qgis::RenderUnit >( ddValues.value( QgsPalLayerSettings::Property::ShapeOffsetUnits ).toInt() ) );
4573 changed = true;
4574 }
4575
4576 if ( ddValues.contains( QgsPalLayerSettings::Property::ShapeRadii ) )
4577 {
4578 background.setRadii( ddValues.value( QgsPalLayerSettings::Property::ShapeRadii ).toSizeF() );
4579 changed = true;
4580 }
4581
4582 if ( ddValues.contains( QgsPalLayerSettings::Property::ShapeRadiiUnits ) )
4583 {
4584 background.setRadiiUnit( static_cast< Qgis::RenderUnit >( ddValues.value( QgsPalLayerSettings::Property::ShapeRadiiUnits ).toInt() ) );
4585 changed = true;
4586 }
4587
4588 if ( ddValues.contains( QgsPalLayerSettings::Property::ShapeBlendMode ) )
4589 {
4590 background.setBlendMode( static_cast< QPainter::CompositionMode >( ddValues.value( QgsPalLayerSettings::Property::ShapeBlendMode ).toInt() ) );
4591 changed = true;
4592 }
4593
4594 if ( ddValues.contains( QgsPalLayerSettings::Property::ShapeFillColor ) )
4595 {
4596 QVariant ddColor = ddValues.value( QgsPalLayerSettings::Property::ShapeFillColor );
4597 background.setFillColor( ddColor.value<QColor>() );
4598 changed = true;
4599 }
4600
4601 if ( ddValues.contains( QgsPalLayerSettings::Property::ShapeStrokeColor ) )
4602 {
4603 QVariant ddColor = ddValues.value( QgsPalLayerSettings::Property::ShapeStrokeColor );
4604 background.setStrokeColor( ddColor.value<QColor>() );
4605 changed = true;
4606 }
4607
4608 if ( ddValues.contains( QgsPalLayerSettings::Property::ShapeOpacity ) )
4609 {
4610 background.setOpacity( ddValues.value( QgsPalLayerSettings::Property::ShapeOpacity ).toDouble() / 100.0 );
4611 changed = true;
4612 }
4613
4614 if ( ddValues.contains( QgsPalLayerSettings::Property::ShapeStrokeWidth ) )
4615 {
4616 background.setStrokeWidth( ddValues.value( QgsPalLayerSettings::Property::ShapeStrokeWidth ).toDouble() );
4617 changed = true;
4618 }
4619
4621 {
4622 background.setStrokeWidthUnit( static_cast< Qgis::RenderUnit >( ddValues.value( QgsPalLayerSettings::Property::ShapeStrokeWidthUnits ).toInt() ) );
4623 changed = true;
4624 }
4625
4626 if ( ddValues.contains( QgsPalLayerSettings::Property::ShapeJoinStyle ) )
4627 {
4628 background.setJoinStyle( static_cast< Qt::PenJoinStyle >( ddValues.value( QgsPalLayerSettings::Property::ShapeJoinStyle ).toInt() ) );
4629 changed = true;
4630 }
4631
4632 if ( changed )
4633 {
4634 QgsTextFormat format = tmpLyr.format();
4635 format.setBackground( background );
4636 tmpLyr.setFormat( format );
4637 }
4638}
4639
4640void QgsPalLabeling::dataDefinedDropShadow( QgsPalLayerSettings &tmpLyr,
4641 const QMap< QgsPalLayerSettings::Property, QVariant > &ddValues )
4642{
4643 QgsTextShadowSettings shadow = tmpLyr.format().shadow();
4644 bool changed = false;
4645
4646 //shadow draw
4647 if ( ddValues.contains( QgsPalLayerSettings::Property::ShadowDraw ) )
4648 {
4649 shadow.setEnabled( ddValues.value( QgsPalLayerSettings::Property::ShadowDraw ).toBool() );
4650 changed = true;
4651 }
4652
4653 if ( !shadow.enabled() )
4654 {
4655 if ( changed )
4656 {
4657 QgsTextFormat format = tmpLyr.format();
4658 format.setShadow( shadow );
4659 tmpLyr.setFormat( format );
4660 }
4661 return; // don't continue looking for unused values
4662 }
4663
4664 if ( ddValues.contains( QgsPalLayerSettings::Property::ShadowUnder ) )
4665 {
4667 changed = true;
4668 }
4669
4670 if ( ddValues.contains( QgsPalLayerSettings::Property::ShadowOffsetAngle ) )
4671 {
4672 shadow.setOffsetAngle( ddValues.value( QgsPalLayerSettings::Property::ShadowOffsetAngle ).toInt() );
4673 changed = true;
4674 }
4675
4676 if ( ddValues.contains( QgsPalLayerSettings::Property::ShadowOffsetDist ) )
4677 {
4678 shadow.setOffsetDistance( ddValues.value( QgsPalLayerSettings::Property::ShadowOffsetDist ).toDouble() );
4679 changed = true;
4680 }
4681
4682 if ( ddValues.contains( QgsPalLayerSettings::Property::ShadowOffsetUnits ) )
4683 {
4684 shadow.setOffsetUnit( static_cast< Qgis::RenderUnit >( ddValues.value( QgsPalLayerSettings::Property::ShadowOffsetUnits ).toInt() ) );
4685 changed = true;
4686 }
4687
4688 if ( ddValues.contains( QgsPalLayerSettings::Property::ShadowRadius ) )
4689 {
4690 shadow.setBlurRadius( ddValues.value( QgsPalLayerSettings::Property::ShadowRadius ).toDouble() );
4691 changed = true;
4692 }
4693
4694 if ( ddValues.contains( QgsPalLayerSettings::Property::ShadowRadiusUnits ) )
4695 {
4696 shadow.setBlurRadiusUnit( static_cast< Qgis::RenderUnit >( ddValues.value( QgsPalLayerSettings::Property::ShadowRadiusUnits ).toInt() ) );
4697 changed = true;
4698 }
4699
4700 if ( ddValues.contains( QgsPalLayerSettings::Property::ShadowColor ) )
4701 {
4702 QVariant ddColor = ddValues.value( QgsPalLayerSettings::Property::ShadowColor );
4703 shadow.setColor( ddColor.value<QColor>() );
4704 changed = true;
4705 }
4706
4707 if ( ddValues.contains( QgsPalLayerSettings::Property::ShadowOpacity ) )
4708 {
4709 shadow.setOpacity( ddValues.value( QgsPalLayerSettings::Property::ShadowOpacity ).toDouble() / 100.0 );
4710 changed = true;
4711 }
4712
4713 if ( ddValues.contains( QgsPalLayerSettings::Property::ShadowScale ) )
4714 {
4715 shadow.setScale( ddValues.value( QgsPalLayerSettings::Property::ShadowScale ).toInt() );
4716 changed = true;
4717 }
4718
4719
4720 if ( ddValues.contains( QgsPalLayerSettings::Property::ShadowBlendMode ) )
4721 {
4722 shadow.setBlendMode( static_cast< QPainter::CompositionMode >( ddValues.value( QgsPalLayerSettings::Property::ShadowBlendMode ).toInt() ) );
4723 changed = true;
4724 }
4725
4726 if ( changed )
4727 {
4728 QgsTextFormat format = tmpLyr.format();
4729 format.setShadow( shadow );
4730 tmpLyr.setFormat( format );
4731 }
4732}
4733
4734void QgsPalLabeling::drawLabelCandidateRect( pal::LabelPosition *lp, QPainter *painter, const QgsMapToPixel *xform, QList<QgsLabelCandidate> *candidates )
4735{
4736 QgsPointXY outPt = xform->transform( lp->getX(), lp->getY() );
4737
4738 painter->save();
4739
4740#if 0 // TODO: generalize some of this
4741 double w = lp->getWidth();
4742 double h = lp->getHeight();
4743 double cx = lp->getX() + w / 2.0;
4744 double cy = lp->getY() + h / 2.0;
4745 double scale = 1.0 / xform->mapUnitsPerPixel();
4746 double rotation = xform->mapRotation();
4747 double sw = w * scale;
4748 double sh = h * scale;
4749 QRectF rect( -sw / 2, -sh / 2, sw, sh );
4750
4751 painter->translate( xform->transform( QPointF( cx, cy ) ).toQPointF() );
4752 if ( rotation )
4753 {
4754 // Only if not horizontal
4755 if ( lp->getFeaturePart()->getLayer()->getArrangement() != P_POINT &&
4756 lp->getFeaturePart()->getLayer()->getArrangement() != P_POINT_OVER &&
4757 lp->getFeaturePart()->getLayer()->getArrangement() != P_HORIZ )
4758 {
4759 painter->rotate( rotation );
4760 }
4761 }
4762 painter->translate( rect.bottomLeft() );
4763 painter->rotate( -lp->getAlpha() * 180 / M_PI );
4764 painter->translate( -rect.bottomLeft() );
4765#else
4766 QgsPointXY outPt2 = xform->transform( lp->getX() + lp->getWidth(), lp->getY() + lp->getHeight() );
4767 QRectF rect( 0, 0, outPt2.x() - outPt.x(), outPt2.y() - outPt.y() );
4768 painter->translate( QPointF( outPt.x(), outPt.y() ) );
4769 painter->rotate( -lp->getAlpha() * 180 / M_PI );
4770#endif
4771
4772 if ( lp->conflictsWithObstacle() )
4773 {
4774 painter->setPen( QColor( 255, 0, 0, 64 ) );
4775 }
4776 else
4777 {
4778 painter->setPen( QColor( 0, 0, 0, 64 ) );
4779 }
4780 painter->drawRect( rect );
4781 painter->restore();
4782
4783 // save the rect
4784 rect.moveTo( outPt.x(), outPt.y() );
4785 if ( candidates )
4786 candidates->append( QgsLabelCandidate( rect, lp->cost() * 1000 ) );
4787
4788 // show all parts of the multipart label
4789 if ( lp->nextPart() )
4790 drawLabelCandidateRect( lp->nextPart(), painter, xform, candidates );
4791}
QFlags< VectorRenderingSimplificationFlag > VectorRenderingSimplificationFlags
Simplification flags for vector feature rendering.
Definition qgis.h:2592
@ Success
Operation succeeded.
AngleUnit
Units of angles.
Definition qgis.h:4444
@ 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:992
@ 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:945
@ 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:2561
@ Warning
Warning message.
Definition qgis.h:101
QFlags< LabelLinePlacementFlag > LabelLinePlacementFlags
Line placement flags, which control how candidates are generated for a linear feature.
Definition qgis.h:1041
@ Labeling
Labeling-specific layout mode.
@ Rectangle
Text within rectangle layout mode.
Capitalization
String capitalization options.
Definition qgis.h:2884
@ AllSmallCaps
Force all characters to small caps (since QGIS 3.24)
@ 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 (since QGIS 3.24)
@ 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:1063
LabelQuadrantPosition
Label quadrant positions.
Definition qgis.h:1006
TextOrientation
Text orientations.
Definition qgis.h:2463
@ 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:906
@ FollowEngineSetting
Respect the label engine setting.
GeometryType
The geometry types are used to group Qgis::WkbType in a coarse way.
Definition qgis.h:274
@ Polygon
Polygons.
@ Unknown
Unknown types.
LabelMultiLineAlignment
Text alignment for multi-line labels.
Definition qgis.h:1089
@ 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:4489
@ 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:918
@ 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:5162
@ 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:1074
@ 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:248
Abstract base class for callout renderers.
Definition qgscallout.h:53
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:321
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.
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
Definition qgsfeature.h:56
QgsFeatureId id
Definition qgsfeature.h:64
QgsGeometry geometry
Definition qgsfeature.h:67
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)
Returns a geos geometry - caller takes ownership of the object (should be deleted with GEOSGeom_destr...
Definition qgsgeos.cpp:255
Represents a label candidate.
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:75
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:85
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.
QgsPointXY transform(const QgsPointXY &p) const
Transforms a point p from map (world) coordinates to device 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
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 void drawLabelCandidateRect(pal::LabelPosition *lp, QPainter *painter, const QgsMapToPixel *xform, QList< QgsLabelCandidate > *candidates=nullptr)
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.
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.
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, QgsTextFormat *format=nullptr, QgsTextDocument *document=nullptr, QgsTextDocumentMetrics *documentMetrics=nullptr, QRectF *outerBounds=nullptr)
Calculates the space required to render the provided text in map units.
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 (since QGIS 3.26)
@ MaskEnabled
Whether the mask is enabled.
@ OverlapHandling
Overlap handling technique (since QGIS 3.26)
@ 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 (since QGIS 3....
@ LineAnchorType
Line anchor type (since QGIS 3.26)
@ MaxScale
Max scale (deprecated, for old project compatibility only)
@ BufferOpacity
Buffer opacity.
@ BufferTransp
Buffer transparency (deprecated)
@ LineAnchorClipping
Clipping mode for line anchor calculation (since QGIS 3.20)
@ 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 (since QGIS 3.16)
@ FontCase
Label text case.
@ Hali
Horizontal alignment for data defined label position (Left, Center, Right)
@ LineAnchorTextPoint
Line anchor text point (since QGIS 3.26)
@ 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:207
double y
Definition qgspointxy.h:64
double x
Definition qgspointxy.h:63
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:733
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.
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.
double width() const
Returns the width of the rectangle.
QgsPointXY center() const
Returns the center point of the rectangle.
void grow(double delta)
Grows the rectangle in place by the specified amount.
double height() const
Returns the height of the rectangle.
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.
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 QgsVectorSimplifyMethod & vectorSimplifyMethod() const
Returns the simplification settings to use when rendering vector layers.
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...
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:145
@ 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:94
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.
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...
static QgsTextDocumentMetrics calculateMetrics(const QgsTextDocument &document, const QgsTextFormat &format, const QgsRenderContext &context, double scaleFactor=1.0)
Returns precalculated text metrics for a text document, when rendered using the given base format and...
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 fromPlainText(const QStringList &lines)
Constructor for QgsTextDocument consisting of a set of plain text lines.
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 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.
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.
LabelPosition is a candidate feature label position.
double getAlpha() const
Returns the angle to rotate text (in radians).
double getHeight() const
double cost() const
Returns the candidate label position's geographical cost.
bool conflictsWithObstacle() const
Returns whether the position is marked as conflicting with an obstacle feature.
double getWidth() const
FeaturePart * getFeaturePart() const
Returns the feature corresponding to this labelposition.
double getX(int i=0) const
Returns the down-left x coordinate.
double getY(int i=0) const
Returns the down-left y coordinate.
LabelPosition * nextPart() const
Returns the next part of this label position (i.e.
#define str(x)
Definition qgis.cpp:38
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:5655
#define Q_NOWARN_DEPRECATED_POP
Definition qgis.h:6022
QString qgsEnumValueToKey(const T &value, bool *returnOk=nullptr)
Returns the value for the given key of an enum.
Definition qgis.h:5636
#define Q_NOWARN_DEPRECATED_PUSH
Definition qgis.h:6021
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition qgis.h:5445
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