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