mirror of
https://github.com/qgis/QGIS.git
synced 2025-10-08 00:05:09 -04:00
odd characters in their names This can result in the font falling back to a default system font. It's notably an issue for the "ESRI Oil, Gas, & Water" symbol font.
4708 lines
201 KiB
C++
4708 lines
201 KiB
C++
/***************************************************************************
|
|
qgspallabeling.cpp
|
|
Smart labeling for vector layers
|
|
-------------------
|
|
begin : June 2009
|
|
copyright : (C) Martin Dobias
|
|
email : wonder dot sk at gmail dot com
|
|
|
|
***************************************************************************
|
|
* *
|
|
* This program is free software; you can redistribute it and/or modify *
|
|
* it under the terms of the GNU General Public License as published by *
|
|
* the Free Software Foundation; either version 2 of the License, or *
|
|
* (at your option) any later version. *
|
|
* *
|
|
***************************************************************************/
|
|
|
|
#include "qgspallabeling.h"
|
|
#include "qgstextlabelfeature.h"
|
|
#include "qgsunittypes.h"
|
|
#include "qgsexception.h"
|
|
#include "qgsapplication.h"
|
|
#include "qgsstyle.h"
|
|
#include "qgstextrenderer.h"
|
|
|
|
#include <list>
|
|
|
|
#include "pal/labelposition.h"
|
|
|
|
#include <cmath>
|
|
|
|
#include <QApplication>
|
|
#include <QByteArray>
|
|
#include <QString>
|
|
#include <QFontMetrics>
|
|
#include <QTime>
|
|
#include <QPainter>
|
|
#include <QScreen>
|
|
#include <QWidget>
|
|
#include <QTextBoundaryFinder>
|
|
|
|
#include "qgsfontutils.h"
|
|
#include "qgsexpression.h"
|
|
#include "qgslabelingengine.h"
|
|
#include "qgstextrendererutils.h"
|
|
#include "qgsmultisurface.h"
|
|
#include "qgslogger.h"
|
|
#include "qgsvectorlayer.h"
|
|
#include "qgsgeometry.h"
|
|
#include "qgsreferencedgeometry.h"
|
|
#include "qgsproperty.h"
|
|
#include "qgssymbollayerutils.h"
|
|
#include "qgsmaptopixelgeometrysimplifier.h"
|
|
#include "qgscurvepolygon.h"
|
|
#include "qgsmessagelog.h"
|
|
#include "qgsgeometrycollection.h"
|
|
#include "callouts/qgscallout.h"
|
|
#include "callouts/qgscalloutsregistry.h"
|
|
#include "qgsvectortilelayer.h"
|
|
#include "qgsvectortilebasiclabeling.h"
|
|
#include "qgsfontmanager.h"
|
|
#include "qgsvariantutils.h"
|
|
|
|
using namespace pal;
|
|
|
|
// -------------
|
|
|
|
/* ND: Default point label position priority. These are set to match variants of the ideal placement priority described
|
|
in "Making Maps", Krygier & Wood (2011) (p216),
|
|
"Elements of Cartography", Robinson et al (1995)
|
|
and "Designing Better Maps", Brewer (2005) (p76)
|
|
Note that while they agree on positions 1-4, 5-8 are more contentious so I've selected these placements
|
|
based on my preferences, and to follow Krygier and Wood's placements more closer. (I'm not going to disagree
|
|
with Denis Wood on anything cartography related...!)
|
|
*/
|
|
typedef 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
|
|
} ) )
|
|
//debugging only - don't use these placements by default
|
|
/* << QgsPalLayerSettings::TopSlightlyLeft
|
|
<< QgsPalLayerSettings::BottomSlightlyLeft;
|
|
<< QgsPalLayerSettings::TopMiddle
|
|
<< QgsPalLayerSettings::BottomMiddle;*/
|
|
|
|
Q_GLOBAL_STATIC( QgsPropertiesDefinition, sPropertyDefinitions )
|
|
|
|
void QgsPalLayerSettings::initPropertyDefinitions()
|
|
{
|
|
if ( !sPropertyDefinitions()->isEmpty() )
|
|
return;
|
|
|
|
const QString origin = QStringLiteral( "labeling" );
|
|
|
|
*sPropertyDefinitions() = QgsPropertiesDefinition
|
|
{
|
|
{ QgsPalLayerSettings::Size, QgsPropertyDefinition( "Size", QObject::tr( "Font size" ), QgsPropertyDefinition::DoublePositive, origin ) },
|
|
{ QgsPalLayerSettings::Bold, QgsPropertyDefinition( "Bold", QObject::tr( "Bold style" ), QgsPropertyDefinition::Boolean, origin ) },
|
|
{ QgsPalLayerSettings::Italic, QgsPropertyDefinition( "Italic", QObject::tr( "Italic style" ), QgsPropertyDefinition::Boolean, origin ) },
|
|
{ QgsPalLayerSettings::Underline, QgsPropertyDefinition( "Underline", QObject::tr( "Draw underline" ), QgsPropertyDefinition::Boolean, origin ) },
|
|
{ QgsPalLayerSettings::Color, QgsPropertyDefinition( "Color", QObject::tr( "Text color" ), QgsPropertyDefinition::ColorNoAlpha, origin ) },
|
|
{ QgsPalLayerSettings::Strikeout, QgsPropertyDefinition( "Strikeout", QObject::tr( "Draw strikeout" ), QgsPropertyDefinition::Boolean, origin ) },
|
|
{
|
|
QgsPalLayerSettings::Family, QgsPropertyDefinition( "Family", QgsPropertyDefinition::DataTypeString, QObject::tr( "Font family" ), QObject::tr( "string " ) + QObject::tr( "[<b>family</b>|<b>family[foundry]</b>],<br>"
|
|
"e.g. Helvetica or Helvetica [Cronyx]" ), origin )
|
|
},
|
|
{
|
|
QgsPalLayerSettings::FontStyle, QgsPropertyDefinition( "FontStyle", QgsPropertyDefinition::DataTypeString, QObject::tr( "Font style" ), QObject::tr( "string " ) + QObject::tr( "[<b>font style name</b>|<b>Ignore</b>],<br>"
|
|
"e.g. Bold Condensed or Light Italic" ), origin )
|
|
},
|
|
{ QgsPalLayerSettings::FontSizeUnit, QgsPropertyDefinition( "FontSizeUnit", QObject::tr( "Font size units" ), QgsPropertyDefinition::RenderUnits, origin ) },
|
|
{ QgsPalLayerSettings::FontTransp, QgsPropertyDefinition( "FontTransp", QObject::tr( "Text transparency" ), QgsPropertyDefinition::Opacity, origin ) },
|
|
{ QgsPalLayerSettings::FontOpacity, QgsPropertyDefinition( "FontOpacity", QObject::tr( "Text opacity" ), QgsPropertyDefinition::Opacity, origin ) },
|
|
{ QgsPalLayerSettings::FontStretchFactor, QgsPropertyDefinition( "FontStretchFactor", QObject::tr( "Font stretch factor" ), QgsPropertyDefinition::IntegerPositiveGreaterZero, origin ) },
|
|
{ QgsPalLayerSettings::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 ) },
|
|
{ QgsPalLayerSettings::FontLetterSpacing, QgsPropertyDefinition( "FontLetterSpacing", QObject::tr( "Letter spacing" ), QgsPropertyDefinition::Double, origin ) },
|
|
{ QgsPalLayerSettings::FontWordSpacing, QgsPropertyDefinition( "FontWordSpacing", QObject::tr( "Word spacing" ), QgsPropertyDefinition::Double, origin ) },
|
|
{ QgsPalLayerSettings::FontBlendMode, QgsPropertyDefinition( "FontBlendMode", QObject::tr( "Text blend mode" ), QgsPropertyDefinition::BlendMode, origin ) },
|
|
{ QgsPalLayerSettings::MultiLineWrapChar, QgsPropertyDefinition( "MultiLineWrapChar", QObject::tr( "Wrap character" ), QgsPropertyDefinition::String, origin ) },
|
|
{ QgsPalLayerSettings::AutoWrapLength, QgsPropertyDefinition( "AutoWrapLength", QObject::tr( "Automatic word wrap line length" ), QgsPropertyDefinition::IntegerPositive, origin ) },
|
|
{ QgsPalLayerSettings::MultiLineHeight, QgsPropertyDefinition( "MultiLineHeight", QObject::tr( "Line height" ), QgsPropertyDefinition::DoublePositive, origin ) },
|
|
{ QgsPalLayerSettings::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 ) },
|
|
{ QgsPalLayerSettings::TextOrientation, QgsPropertyDefinition( "TextOrientation", QgsPropertyDefinition::DataTypeString, QObject::tr( "Text orientation" ), QObject::tr( "string " ) + "[<b>horizontal</b>|<b>vertical</b>]", origin ) },
|
|
{ QgsPalLayerSettings::DirSymbDraw, QgsPropertyDefinition( "DirSymbDraw", QObject::tr( "Draw direction symbol" ), QgsPropertyDefinition::Boolean, origin ) },
|
|
{ QgsPalLayerSettings::DirSymbLeft, QgsPropertyDefinition( "DirSymbLeft", QObject::tr( "Left direction symbol" ), QgsPropertyDefinition::String, origin ) },
|
|
{ QgsPalLayerSettings::DirSymbRight, QgsPropertyDefinition( "DirSymbRight", QObject::tr( "Right direction symbol" ), QgsPropertyDefinition::String, origin ) },
|
|
{ QgsPalLayerSettings::DirSymbPlacement, QgsPropertyDefinition( "DirSymbPlacement", QgsPropertyDefinition::DataTypeString, QObject::tr( "Direction symbol placement" ), QObject::tr( "string " ) + "[<b>LeftRight</b>|<b>Above</b>|<b>Below</b>]", origin ) },
|
|
{ QgsPalLayerSettings::DirSymbReverse, QgsPropertyDefinition( "DirSymbReverse", QObject::tr( "Reverse direction symbol" ), QgsPropertyDefinition::Boolean, origin ) },
|
|
{ QgsPalLayerSettings::NumFormat, QgsPropertyDefinition( "NumFormat", QObject::tr( "Format as number" ), QgsPropertyDefinition::Boolean, origin ) },
|
|
{ QgsPalLayerSettings::NumDecimals, QgsPropertyDefinition( "NumDecimals", QObject::tr( "Number of decimal places" ), QgsPropertyDefinition::IntegerPositive, origin ) },
|
|
{ QgsPalLayerSettings::NumPlusSign, QgsPropertyDefinition( "NumPlusSign", QObject::tr( "Draw + sign" ), QgsPropertyDefinition::Boolean, origin ) },
|
|
{ QgsPalLayerSettings::BufferDraw, QgsPropertyDefinition( "BufferDraw", QObject::tr( "Draw buffer" ), QgsPropertyDefinition::Boolean, origin ) },
|
|
{ QgsPalLayerSettings::BufferSize, QgsPropertyDefinition( "BufferSize", QObject::tr( "Symbol size" ), QgsPropertyDefinition::DoublePositive, origin ) },
|
|
{ QgsPalLayerSettings::BufferUnit, QgsPropertyDefinition( "BufferUnit", QObject::tr( "Buffer units" ), QgsPropertyDefinition::RenderUnits, origin ) },
|
|
{ QgsPalLayerSettings::BufferColor, QgsPropertyDefinition( "BufferColor", QObject::tr( "Buffer color" ), QgsPropertyDefinition::ColorNoAlpha, origin ) },
|
|
{ QgsPalLayerSettings::BufferTransp, QgsPropertyDefinition( "BufferTransp", QObject::tr( "Buffer transparency" ), QgsPropertyDefinition::Opacity, origin ) },
|
|
{ QgsPalLayerSettings::BufferOpacity, QgsPropertyDefinition( "BufferOpacity", QObject::tr( "Buffer opacity" ), QgsPropertyDefinition::Opacity, origin ) },
|
|
{ QgsPalLayerSettings::BufferJoinStyle, QgsPropertyDefinition( "BufferJoinStyle", QObject::tr( "Buffer join style" ), QgsPropertyDefinition::PenJoinStyle, origin ) },
|
|
{ QgsPalLayerSettings::BufferBlendMode, QgsPropertyDefinition( "BufferBlendMode", QObject::tr( "Buffer blend mode" ), QgsPropertyDefinition::BlendMode, origin ) },
|
|
|
|
{ QgsPalLayerSettings::MaskEnabled, QgsPropertyDefinition( "MaskEnabled", QObject::tr( "Enable mask" ), QgsPropertyDefinition::Boolean, origin ) },
|
|
{ QgsPalLayerSettings::MaskBufferSize, QgsPropertyDefinition( "MaskBufferSize", QObject::tr( "Mask buffer size" ), QgsPropertyDefinition::DoublePositive, origin ) },
|
|
{ QgsPalLayerSettings::MaskBufferUnit, QgsPropertyDefinition( "MaskBufferUnit", QObject::tr( "Mask buffer unit" ), QgsPropertyDefinition::RenderUnits, origin ) },
|
|
{ QgsPalLayerSettings::MaskOpacity, QgsPropertyDefinition( "MaskOpacity", QObject::tr( "Mask opacity" ), QgsPropertyDefinition::Opacity, origin ) },
|
|
{ QgsPalLayerSettings::MaskJoinStyle, QgsPropertyDefinition( "MaskJoinStyle", QObject::tr( "Mask join style" ), QgsPropertyDefinition::PenJoinStyle, origin ) },
|
|
|
|
{ QgsPalLayerSettings::ShapeDraw, QgsPropertyDefinition( "ShapeDraw", QObject::tr( "Draw shape" ), QgsPropertyDefinition::Boolean, origin ) },
|
|
{
|
|
QgsPalLayerSettings::ShapeKind, QgsPropertyDefinition( "ShapeKind", QgsPropertyDefinition::DataTypeString, QObject::tr( "Shape type" ), QObject::tr( "string " ) + QStringLiteral( "[<b>Rectangle</b>|<b>Square</b>|<br>"
|
|
"<b>Ellipse</b>|<b>Circle</b>|<b>SVG</b>]" ), origin )
|
|
},
|
|
{ QgsPalLayerSettings::ShapeSVGFile, QgsPropertyDefinition( "ShapeSVGFile", QObject::tr( "Shape SVG path" ), QgsPropertyDefinition::SvgPath, origin ) },
|
|
{ QgsPalLayerSettings::ShapeSizeType, QgsPropertyDefinition( "ShapeSizeType", QgsPropertyDefinition::DataTypeString, QObject::tr( "Shape size type" ), QObject::tr( "string " ) + "[<b>Buffer</b>|<b>Fixed</b>]", origin ) },
|
|
{ QgsPalLayerSettings::ShapeSizeX, QgsPropertyDefinition( "ShapeSizeX", QObject::tr( "Shape size (X)" ), QgsPropertyDefinition::Double, origin ) },
|
|
{ QgsPalLayerSettings::ShapeSizeY, QgsPropertyDefinition( "ShapeSizeY", QObject::tr( "Shape size (Y)" ), QgsPropertyDefinition::Double, origin ) },
|
|
{ QgsPalLayerSettings::ShapeSizeUnits, QgsPropertyDefinition( "ShapeSizeUnits", QObject::tr( "Shape size units" ), QgsPropertyDefinition::RenderUnits, origin ) },
|
|
{ QgsPalLayerSettings::ShapeRotationType, QgsPropertyDefinition( "ShapeRotationType", QgsPropertyDefinition::DataTypeString, QObject::tr( "Shape rotation type" ), QObject::tr( "string " ) + "[<b>Sync</b>|<b>Offset</b>|<b>Fixed</b>]", origin ) },
|
|
{ QgsPalLayerSettings::ShapeRotation, QgsPropertyDefinition( "ShapeRotation", QObject::tr( "Shape rotation" ), QgsPropertyDefinition::Rotation, origin ) },
|
|
{ QgsPalLayerSettings::ShapeOffset, QgsPropertyDefinition( "ShapeOffset", QObject::tr( "Shape offset" ), QgsPropertyDefinition::Offset, origin ) },
|
|
{ QgsPalLayerSettings::ShapeOffsetUnits, QgsPropertyDefinition( "ShapeOffsetUnits", QObject::tr( "Shape offset units" ), QgsPropertyDefinition::RenderUnits, origin ) },
|
|
{ QgsPalLayerSettings::ShapeRadii, QgsPropertyDefinition( "ShapeRadii", QObject::tr( "Shape radii" ), QgsPropertyDefinition::Size2D, origin ) },
|
|
{ QgsPalLayerSettings::ShapeRadiiUnits, QgsPropertyDefinition( "ShapeRadiiUnits", QObject::tr( "Symbol radii units" ), QgsPropertyDefinition::RenderUnits, origin ) },
|
|
{ QgsPalLayerSettings::ShapeTransparency, QgsPropertyDefinition( "ShapeTransparency", QObject::tr( "Shape transparency" ), QgsPropertyDefinition::Opacity, origin ) },
|
|
{ QgsPalLayerSettings::ShapeOpacity, QgsPropertyDefinition( "ShapeOpacity", QObject::tr( "Shape opacity" ), QgsPropertyDefinition::Opacity, origin ) },
|
|
{ QgsPalLayerSettings::ShapeBlendMode, QgsPropertyDefinition( "ShapeBlendMode", QObject::tr( "Shape blend mode" ), QgsPropertyDefinition::BlendMode, origin ) },
|
|
{ QgsPalLayerSettings::ShapeFillColor, QgsPropertyDefinition( "ShapeFillColor", QObject::tr( "Shape fill color" ), QgsPropertyDefinition::ColorWithAlpha, origin ) },
|
|
{ QgsPalLayerSettings::ShapeStrokeColor, QgsPropertyDefinition( "ShapeBorderColor", QObject::tr( "Shape stroke color" ), QgsPropertyDefinition::ColorWithAlpha, origin ) },
|
|
{ QgsPalLayerSettings::ShapeStrokeWidth, QgsPropertyDefinition( "ShapeBorderWidth", QObject::tr( "Shape stroke width" ), QgsPropertyDefinition::StrokeWidth, origin ) },
|
|
{ QgsPalLayerSettings::ShapeStrokeWidthUnits, QgsPropertyDefinition( "ShapeBorderWidthUnits", QObject::tr( "Shape stroke width units" ), QgsPropertyDefinition::RenderUnits, origin ) },
|
|
{ QgsPalLayerSettings::ShapeJoinStyle, QgsPropertyDefinition( "ShapeJoinStyle", QObject::tr( "Shape join style" ), QgsPropertyDefinition::PenJoinStyle, origin ) },
|
|
{ QgsPalLayerSettings::ShadowDraw, QgsPropertyDefinition( "ShadowDraw", QObject::tr( "Draw shadow" ), QgsPropertyDefinition::Boolean, origin ) },
|
|
{
|
|
QgsPalLayerSettings::ShadowUnder, QgsPropertyDefinition( "ShadowUnder", QgsPropertyDefinition::DataTypeString, QObject::tr( "Symbol size" ), QObject::tr( "string " ) + QStringLiteral( "[<b>Lowest</b>|<b>Text</b>|<br>"
|
|
"<b>Buffer</b>|<b>Background</b>]" ), origin )
|
|
},
|
|
{ QgsPalLayerSettings::ShadowOffsetAngle, QgsPropertyDefinition( "ShadowOffsetAngle", QObject::tr( "Shadow offset angle" ), QgsPropertyDefinition::Rotation, origin ) },
|
|
{ QgsPalLayerSettings::ShadowOffsetDist, QgsPropertyDefinition( "ShadowOffsetDist", QObject::tr( "Shadow offset distance" ), QgsPropertyDefinition::DoublePositive, origin ) },
|
|
{ QgsPalLayerSettings::ShadowOffsetUnits, QgsPropertyDefinition( "ShadowOffsetUnits", QObject::tr( "Shadow offset units" ), QgsPropertyDefinition::RenderUnits, origin ) },
|
|
{ QgsPalLayerSettings::ShadowRadius, QgsPropertyDefinition( "ShadowRadius", QObject::tr( "Shadow blur radius" ), QgsPropertyDefinition::DoublePositive, origin ) },
|
|
{ QgsPalLayerSettings::ShadowRadiusUnits, QgsPropertyDefinition( "ShadowRadiusUnits", QObject::tr( "Shadow blur units" ), QgsPropertyDefinition::RenderUnits, origin ) },
|
|
{ QgsPalLayerSettings::ShadowTransparency, QgsPropertyDefinition( "ShadowTransparency", QObject::tr( "Shadow transparency" ), QgsPropertyDefinition::Opacity, origin ) },
|
|
{ QgsPalLayerSettings::ShadowOpacity, QgsPropertyDefinition( "ShadowOpacity", QObject::tr( "Shadow opacity" ), QgsPropertyDefinition::Opacity, origin ) },
|
|
{ QgsPalLayerSettings::ShadowScale, QgsPropertyDefinition( "ShadowScale", QObject::tr( "Shadow scale" ), QgsPropertyDefinition::IntegerPositive, origin ) },
|
|
{ QgsPalLayerSettings::ShadowColor, QgsPropertyDefinition( "ShadowColor", QObject::tr( "Shadow color" ), QgsPropertyDefinition::ColorNoAlpha, origin ) },
|
|
{ QgsPalLayerSettings::ShadowBlendMode, QgsPropertyDefinition( "ShadowBlendMode", QObject::tr( "Shadow blend mode" ), QgsPropertyDefinition::BlendMode, origin ) },
|
|
|
|
{ QgsPalLayerSettings::CentroidWhole, QgsPropertyDefinition( "CentroidWhole", QgsPropertyDefinition::DataTypeString, QObject::tr( "Centroid of whole shape" ), QObject::tr( "string " ) + "[<b>Visible</b>|<b>Whole</b>]", origin ) },
|
|
{
|
|
QgsPalLayerSettings::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>"
|
|
"<b>3</b>=Left|<b>4</b>=Over|<b>5</b>=Right|<br>"
|
|
"<b>6</b>=Below Left|<b>7</b>=Below|<b>8</b>=Below Right]" ), origin )
|
|
},
|
|
{ QgsPalLayerSettings::OffsetXY, QgsPropertyDefinition( "OffsetXY", QObject::tr( "Offset" ), QgsPropertyDefinition::Offset, origin ) },
|
|
{ QgsPalLayerSettings::OffsetUnits, QgsPropertyDefinition( "OffsetUnits", QObject::tr( "Offset units" ), QgsPropertyDefinition::RenderUnits, origin ) },
|
|
{ QgsPalLayerSettings::LabelDistance, QgsPropertyDefinition( "LabelDistance", QObject::tr( "Label distance" ), QgsPropertyDefinition::Double, origin ) },
|
|
{ QgsPalLayerSettings::DistanceUnits, QgsPropertyDefinition( "DistanceUnits", QObject::tr( "Label distance units" ), QgsPropertyDefinition::RenderUnits, origin ) },
|
|
{ QgsPalLayerSettings::OffsetRotation, QgsPropertyDefinition( "OffsetRotation", QObject::tr( "Offset rotation" ), QgsPropertyDefinition::Rotation, origin ) },
|
|
{ QgsPalLayerSettings::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 ) },
|
|
{ QgsPalLayerSettings::RepeatDistance, QgsPropertyDefinition( "RepeatDistance", QObject::tr( "Repeat distance" ), QgsPropertyDefinition::DoublePositive, origin ) },
|
|
{ QgsPalLayerSettings::RepeatDistanceUnit, QgsPropertyDefinition( "RepeatDistanceUnit", QObject::tr( "Repeat distance unit" ), QgsPropertyDefinition::RenderUnits, origin ) },
|
|
{ QgsPalLayerSettings::OverrunDistance, QgsPropertyDefinition( "OverrunDistance", QObject::tr( "Overrun distance" ), QgsPropertyDefinition::DoublePositive, origin ) },
|
|
{ QgsPalLayerSettings::LineAnchorPercent, QgsPropertyDefinition( "LineAnchorPercent", QObject::tr( "Line anchor percentage, as fraction from 0.0 to 1.0" ), QgsPropertyDefinition::Double0To1, origin ) },
|
|
{ QgsPalLayerSettings::LineAnchorClipping, QgsPropertyDefinition( "LineAnchorClipping", QgsPropertyDefinition::DataTypeString, QObject::tr( "Line anchor clipping mode" ), QObject::tr( "string " ) + QStringLiteral( "[<b>visible</b>|<b>entire</b>]" ), origin ) },
|
|
{ QgsPalLayerSettings::LineAnchorType, QgsPropertyDefinition( "LineAnchorType", QgsPropertyDefinition::DataTypeString, QObject::tr( "Line anchor type" ), QObject::tr( "string " ) + QStringLiteral( "[<b>hint</b>|<b>strict</b>]" ), origin ) },
|
|
{ QgsPalLayerSettings::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 ) },
|
|
{ QgsPalLayerSettings::Priority, QgsPropertyDefinition( "Priority", QgsPropertyDefinition::DataTypeNumeric, QObject::tr( "Label priority" ), QObject::tr( "double [0.0-10.0]" ), origin ) },
|
|
{ QgsPalLayerSettings::IsObstacle, QgsPropertyDefinition( "IsObstacle", QObject::tr( "Feature is a label obstacle" ), QgsPropertyDefinition::Boolean, origin ) },
|
|
{ QgsPalLayerSettings::ObstacleFactor, QgsPropertyDefinition( "ObstacleFactor", QgsPropertyDefinition::DataTypeNumeric, QObject::tr( "Obstacle factor" ), QObject::tr( "double [0.0-10.0]" ), origin ) },
|
|
{
|
|
QgsPalLayerSettings::PredefinedPositionOrder, QgsPropertyDefinition( "PredefinedPositionOrder", QgsPropertyDefinition::DataTypeString, QObject::tr( "Predefined position order" ), QObject::tr( "Comma separated list of placements in order of priority<br>" )
|
|
+ QStringLiteral( "[<b>TL</b>=Top left|<b>TSL</b>=Top, slightly left|<b>T</b>=Top middle|<br>"
|
|
"<b>TSR</b>=Top, slightly right|<b>TR</b>=Top right|<br>"
|
|
"<b>L</b>=Left|<b>R</b>=Right|<br>"
|
|
"<b>BL</b>=Bottom left|<b>BSL</b>=Bottom, slightly left|<b>B</b>=Bottom middle|<br>"
|
|
"<b>BSR</b>=Bottom, slightly right|<b>BR</b>=Bottom right]" ), origin )
|
|
},
|
|
{
|
|
QgsPalLayerSettings::LinePlacementOptions, QgsPropertyDefinition( "LinePlacementFlags", QgsPropertyDefinition::DataTypeString, QObject::tr( "Line placement options" ), QObject::tr( "Comma separated list of placement options<br>" )
|
|
+ QStringLiteral( "[<b>OL</b>=On line|<b>AL</b>=Above line|<b>BL</b>=Below line|<br>"
|
|
"<b>LO</b>=Respect line orientation]" ), origin )
|
|
},
|
|
{ QgsPalLayerSettings::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 ) },
|
|
{ QgsPalLayerSettings::PositionX, QgsPropertyDefinition( "PositionX", QObject::tr( "Position (X)" ), QgsPropertyDefinition::Double, origin ) },
|
|
{ QgsPalLayerSettings::PositionY, QgsPropertyDefinition( "PositionY", QObject::tr( "Position (Y)" ), QgsPropertyDefinition::Double, origin ) },
|
|
{ QgsPalLayerSettings::PositionPoint, QgsPropertyDefinition( "PositionPoint", QgsPropertyDefinition::DataTypeString, QObject::tr( "Position (point)" ), QObject::tr( "A point geometry" ), origin ) },
|
|
{ QgsPalLayerSettings::Hali, QgsPropertyDefinition( "Hali", QgsPropertyDefinition::DataTypeString, QObject::tr( "Horizontal alignment" ), QObject::tr( "string " ) + "[<b>Left</b>|<b>Center</b>|<b>Right</b>]", origin ) },
|
|
{
|
|
QgsPalLayerSettings::Vali, QgsPropertyDefinition( "Vali", QgsPropertyDefinition::DataTypeString, QObject::tr( "Vertical alignment" ), QObject::tr( "string " ) + QStringLiteral( "[<b>Bottom</b>|<b>Base</b>|<br>"
|
|
"<b>Half</b>|<b>Cap</b>|<b>Top</b>]" ), origin )
|
|
},
|
|
{ QgsPalLayerSettings::Rotation, QgsPropertyDefinition( "Rotation", QObject::tr( "Label rotation (deprecated)" ), QgsPropertyDefinition::Rotation, origin ) },
|
|
{ QgsPalLayerSettings::LabelRotation, QgsPropertyDefinition( "LabelRotation", QObject::tr( "Label rotation" ), QgsPropertyDefinition::Rotation, origin ) },
|
|
{ QgsPalLayerSettings::ScaleVisibility, QgsPropertyDefinition( "ScaleVisibility", QObject::tr( "Scale based visibility" ), QgsPropertyDefinition::Boolean, origin ) },
|
|
{ QgsPalLayerSettings::MinScale, QgsPropertyDefinition( "MinScale", QObject::tr( "Minimum scale (denominator)" ), QgsPropertyDefinition::Double, origin ) },
|
|
{ QgsPalLayerSettings::MaxScale, QgsPropertyDefinition( "MaxScale", QObject::tr( "Maximum scale (denominator)" ), QgsPropertyDefinition::Double, origin ) },
|
|
{ QgsPalLayerSettings::MinimumScale, QgsPropertyDefinition( "MinimumScale", QObject::tr( "Minimum scale (denominator)" ), QgsPropertyDefinition::Double, origin ) },
|
|
{ QgsPalLayerSettings::MaximumScale, QgsPropertyDefinition( "MaximumScale", QObject::tr( "Maximum scale (denominator)" ), QgsPropertyDefinition::Double, origin ) },
|
|
|
|
{ QgsPalLayerSettings::FontLimitPixel, QgsPropertyDefinition( "FontLimitPixel", QObject::tr( "Limit font pixel size" ), QgsPropertyDefinition::Boolean, origin ) },
|
|
{ QgsPalLayerSettings::FontMinPixel, QgsPropertyDefinition( "FontMinPixel", QObject::tr( "Minimum pixel size" ), QgsPropertyDefinition::IntegerPositive, origin ) },
|
|
{ QgsPalLayerSettings::FontMaxPixel, QgsPropertyDefinition( "FontMaxPixel", QObject::tr( "Maximum pixel size" ), QgsPropertyDefinition::IntegerPositive, origin ) },
|
|
{ QgsPalLayerSettings::ZIndex, QgsPropertyDefinition( "ZIndex", QObject::tr( "Label z-index" ), QgsPropertyDefinition::Double, origin ) },
|
|
{ QgsPalLayerSettings::Show, QgsPropertyDefinition( "Show", QObject::tr( "Show label" ), QgsPropertyDefinition::Boolean, origin ) },
|
|
{ QgsPalLayerSettings::AlwaysShow, QgsPropertyDefinition( "AlwaysShow", QObject::tr( "Always show label" ), QgsPropertyDefinition::Boolean, origin ) },
|
|
{ QgsPalLayerSettings::CalloutDraw, QgsPropertyDefinition( "CalloutDraw", QObject::tr( "Draw callout" ), QgsPropertyDefinition::Boolean, origin ) },
|
|
{ QgsPalLayerSettings::LabelAllParts, QgsPropertyDefinition( "LabelAllParts", QObject::tr( "Label all parts" ), QgsPropertyDefinition::Boolean, origin ) },
|
|
{ QgsPalLayerSettings::AllowDegradedPlacement, QgsPropertyDefinition( "AllowDegradedPlacement", QObject::tr( "Allow inferior fallback placements" ), QgsPropertyDefinition::Boolean, origin ) },
|
|
{ QgsPalLayerSettings::OverlapHandling, QgsPropertyDefinition( "OverlapHandling", QgsPropertyDefinition::DataTypeString, QObject::tr( "Overlap handing" ), QObject::tr( "string " ) + "[<b>Prevent</b>|<b>AllowIfNeeded</b>|<b>AlwaysAllow</b>]", origin ) },
|
|
};
|
|
}
|
|
|
|
Q_NOWARN_DEPRECATED_PUSH // because of deprecated members
|
|
QgsPalLayerSettings::QgsPalLayerSettings()
|
|
: predefinedPositionOrder( *DEFAULT_PLACEMENT_ORDER() )
|
|
, mCallout( QgsCalloutRegistry::defaultCallout() )
|
|
{
|
|
initPropertyDefinitions();
|
|
|
|
mFormat = QgsStyle::defaultStyle()->defaultTextFormat( QgsStyle::TextFormatContext::Labeling );
|
|
}
|
|
Q_NOWARN_DEPRECATED_POP
|
|
|
|
Q_NOWARN_DEPRECATED_PUSH // because of deprecated members
|
|
QgsPalLayerSettings::QgsPalLayerSettings( const QgsPalLayerSettings &s )
|
|
: fieldIndex( 0 )
|
|
, mDataDefinedProperties( s.mDataDefinedProperties )
|
|
{
|
|
*this = s;
|
|
}
|
|
Q_NOWARN_DEPRECATED_POP
|
|
|
|
QgsPalLayerSettings &QgsPalLayerSettings::operator=( const QgsPalLayerSettings &s )
|
|
{
|
|
if ( this == &s )
|
|
return *this;
|
|
|
|
// copy only permanent stuff
|
|
|
|
drawLabels = s.drawLabels;
|
|
|
|
// text style
|
|
fieldName = s.fieldName;
|
|
isExpression = s.isExpression;
|
|
Q_NOWARN_DEPRECATED_PUSH
|
|
previewBkgrdColor = s.previewBkgrdColor;
|
|
Q_NOWARN_DEPRECATED_POP
|
|
substitutions = s.substitutions;
|
|
useSubstitutions = s.useSubstitutions;
|
|
|
|
// text formatting
|
|
wrapChar = s.wrapChar;
|
|
autoWrapLength = s.autoWrapLength;
|
|
useMaxLineLengthForAutoWrap = s.useMaxLineLengthForAutoWrap;
|
|
multilineAlign = s.multilineAlign;
|
|
formatNumbers = s.formatNumbers;
|
|
decimals = s.decimals;
|
|
plusSign = s.plusSign;
|
|
|
|
// placement
|
|
placement = s.placement;
|
|
mPolygonPlacementFlags = s.mPolygonPlacementFlags;
|
|
centroidWhole = s.centroidWhole;
|
|
centroidInside = s.centroidInside;
|
|
predefinedPositionOrder = s.predefinedPositionOrder;
|
|
fitInPolygonOnly = s.fitInPolygonOnly;
|
|
quadOffset = s.quadOffset;
|
|
xOffset = s.xOffset;
|
|
yOffset = s.yOffset;
|
|
offsetUnits = s.offsetUnits;
|
|
labelOffsetMapUnitScale = s.labelOffsetMapUnitScale;
|
|
dist = s.dist;
|
|
offsetType = s.offsetType;
|
|
distUnits = s.distUnits;
|
|
distMapUnitScale = s.distMapUnitScale;
|
|
angleOffset = s.angleOffset;
|
|
preserveRotation = s.preserveRotation;
|
|
mRotationUnit = s.mRotationUnit;
|
|
maxCurvedCharAngleIn = s.maxCurvedCharAngleIn;
|
|
maxCurvedCharAngleOut = s.maxCurvedCharAngleOut;
|
|
priority = s.priority;
|
|
repeatDistance = s.repeatDistance;
|
|
repeatDistanceUnit = s.repeatDistanceUnit;
|
|
repeatDistanceMapUnitScale = s.repeatDistanceMapUnitScale;
|
|
|
|
// rendering
|
|
scaleVisibility = s.scaleVisibility;
|
|
maximumScale = s.maximumScale;
|
|
minimumScale = s.minimumScale;
|
|
fontLimitPixelSize = s.fontLimitPixelSize;
|
|
fontMinPixelSize = s.fontMinPixelSize;
|
|
fontMaxPixelSize = s.fontMaxPixelSize;
|
|
upsidedownLabels = s.upsidedownLabels;
|
|
|
|
labelPerPart = s.labelPerPart;
|
|
zIndex = s.zIndex;
|
|
|
|
mFormat = s.mFormat;
|
|
mDataDefinedProperties = s.mDataDefinedProperties;
|
|
|
|
mCallout.reset( s.mCallout ? s.mCallout->clone() : nullptr );
|
|
|
|
mPlacementSettings = s.mPlacementSettings;
|
|
mLineSettings = s.mLineSettings;
|
|
mObstacleSettings = s.mObstacleSettings;
|
|
mThinningSettings = s.mThinningSettings;
|
|
|
|
geometryGenerator = s.geometryGenerator;
|
|
geometryGeneratorEnabled = s.geometryGeneratorEnabled;
|
|
geometryGeneratorType = s.geometryGeneratorType;
|
|
layerType = s.layerType;
|
|
|
|
mLegendString = s.mLegendString;
|
|
|
|
mUnplacedVisibility = s.mUnplacedVisibility;
|
|
|
|
return *this;
|
|
}
|
|
|
|
bool QgsPalLayerSettings::prepare( QgsRenderContext &context, QSet<QString> &attributeNames, const QgsFields &fields, const QgsMapSettings &mapSettings, const QgsCoordinateReferenceSystem &crs )
|
|
{
|
|
if ( drawLabels )
|
|
{
|
|
if ( fieldName.isEmpty() )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if ( isExpression )
|
|
{
|
|
QgsExpression exp( fieldName );
|
|
if ( exp.hasEvalError() )
|
|
{
|
|
QgsDebugMsgLevel( "Prepare error:" + exp.evalErrorString(), 4 );
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// If we aren't an expression, we check to see if we can find the column.
|
|
if ( fields.lookupField( fieldName ) == -1 )
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
mCurFields = fields;
|
|
|
|
if ( drawLabels || mObstacleSettings.isObstacle() )
|
|
{
|
|
if ( drawLabels )
|
|
{
|
|
// add field indices for label's text, from expression or field
|
|
if ( isExpression )
|
|
{
|
|
// prepare expression for use in QgsPalLayerSettings::registerFeature()
|
|
QgsExpression *exp = getLabelExpression();
|
|
exp->prepare( &context.expressionContext() );
|
|
if ( exp->hasEvalError() )
|
|
{
|
|
QgsDebugMsgLevel( "Prepare error:" + exp->evalErrorString(), 4 );
|
|
}
|
|
const auto referencedColumns = exp->referencedColumns();
|
|
for ( const QString &name : referencedColumns )
|
|
{
|
|
attributeNames.insert( name );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
attributeNames.insert( fieldName );
|
|
}
|
|
}
|
|
|
|
mDataDefinedProperties.prepare( context.expressionContext() );
|
|
// add field indices of data defined expression or field
|
|
attributeNames.unite( dataDefinedProperties().referencedFields( context.expressionContext() ) );
|
|
}
|
|
|
|
// NOW INITIALIZE QgsPalLayerSettings
|
|
|
|
// TODO: ideally these (non-configuration) members should get out of QgsPalLayerSettings to QgsVectorLayerLabelProvider::prepare
|
|
// (together with registerFeature() & related methods) and QgsPalLayerSettings just stores config
|
|
|
|
// save the pal layer to our layer context (with some additional info)
|
|
fieldIndex = fields.lookupField( fieldName );
|
|
|
|
xform = &mapSettings.mapToPixel();
|
|
ct = QgsCoordinateTransform();
|
|
if ( context.coordinateTransform().isValid() )
|
|
// this is context for layer rendering
|
|
ct = context.coordinateTransform();
|
|
else
|
|
{
|
|
// otherwise fall back to creating our own CT
|
|
ct = QgsCoordinateTransform( crs, mapSettings.destinationCrs(), mapSettings.transformContext() );
|
|
}
|
|
ptZero = xform->toMapCoordinates( 0, 0 );
|
|
ptOne = xform->toMapCoordinates( 1, 0 );
|
|
|
|
// rect for clipping
|
|
QgsRectangle r1 = mapSettings.visibleExtent();
|
|
r1.grow( mapSettings.extentBuffer() );
|
|
extentGeom = QgsGeometry::fromRect( r1 );
|
|
|
|
if ( !qgsDoubleNear( mapSettings.rotation(), 0.0 ) )
|
|
{
|
|
//PAL features are prerotated, so extent also needs to be unrotated
|
|
extentGeom.rotate( -mapSettings.rotation(), mapSettings.visibleExtent().center() );
|
|
}
|
|
|
|
mFeatsSendingToPal = 0;
|
|
|
|
if ( geometryGeneratorEnabled )
|
|
{
|
|
mGeometryGeneratorExpression = QgsExpression( geometryGenerator );
|
|
mGeometryGeneratorExpression.prepare( &context.expressionContext() );
|
|
if ( mGeometryGeneratorExpression.hasParserError() )
|
|
{
|
|
QgsMessageLog::logMessage( mGeometryGeneratorExpression.parserErrorString(), QObject::tr( "Labeling" ) );
|
|
return false;
|
|
}
|
|
|
|
const auto referencedColumns = mGeometryGeneratorExpression.referencedColumns();
|
|
for ( const QString &name : referencedColumns )
|
|
{
|
|
attributeNames.insert( name );
|
|
}
|
|
}
|
|
attributeNames.unite( mFormat.referencedFields( context ) );
|
|
|
|
if ( mCallout )
|
|
{
|
|
const auto referencedColumns = mCallout->referencedFields( context );
|
|
for ( const QString &name : referencedColumns )
|
|
{
|
|
attributeNames.insert( name );
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
QSet<QString> QgsPalLayerSettings::referencedFields( const QgsRenderContext &context ) const
|
|
{
|
|
QSet<QString> referenced;
|
|
if ( drawLabels )
|
|
{
|
|
if ( isExpression )
|
|
{
|
|
referenced.unite( QgsExpression( fieldName ).referencedColumns() );
|
|
}
|
|
else
|
|
{
|
|
referenced.insert( fieldName );
|
|
}
|
|
}
|
|
|
|
referenced.unite( mFormat.referencedFields( context ) );
|
|
|
|
// calling referencedFields() with ignoreContext=true because in our expression context
|
|
// we do not have valid QgsFields yet - because of that the field names from expressions
|
|
// wouldn't get reported
|
|
referenced.unite( mDataDefinedProperties.referencedFields( context.expressionContext(), true ) );
|
|
|
|
if ( geometryGeneratorEnabled )
|
|
{
|
|
QgsExpression geomGeneratorExpr( geometryGenerator );
|
|
referenced.unite( geomGeneratorExpr.referencedColumns() );
|
|
}
|
|
|
|
if ( mCallout )
|
|
{
|
|
referenced.unite( mCallout->referencedFields( context ) );
|
|
}
|
|
|
|
return referenced;
|
|
}
|
|
|
|
void QgsPalLayerSettings::startRender( QgsRenderContext &context )
|
|
{
|
|
if ( mRenderStarted )
|
|
{
|
|
qWarning( "Start render called for when a previous render was already underway!!" );
|
|
return;
|
|
}
|
|
|
|
switch ( placement )
|
|
{
|
|
case Qgis::LabelPlacement::PerimeterCurved:
|
|
case Qgis::LabelPlacement::Curved:
|
|
{
|
|
// force horizontal orientation, other orientation modes aren't unsupported for curved placement
|
|
mFormat.setOrientation( Qgis::TextOrientation::Horizontal );
|
|
mDataDefinedProperties.property( QgsPalLayerSettings::TextOrientation ).setActive( false );
|
|
break;
|
|
}
|
|
|
|
case Qgis::LabelPlacement::AroundPoint:
|
|
case Qgis::LabelPlacement::OverPoint:
|
|
case Qgis::LabelPlacement::Line:
|
|
case Qgis::LabelPlacement::Horizontal:
|
|
case Qgis::LabelPlacement::Free:
|
|
case Qgis::LabelPlacement::OrderedPositionsAroundPoint:
|
|
case Qgis::LabelPlacement::OutsidePolygons:
|
|
break;
|
|
}
|
|
|
|
if ( mCallout )
|
|
{
|
|
mCallout->startRender( context );
|
|
}
|
|
|
|
mRenderStarted = true;
|
|
}
|
|
|
|
void QgsPalLayerSettings::stopRender( QgsRenderContext &context )
|
|
{
|
|
if ( !mRenderStarted )
|
|
{
|
|
qWarning( "Stop render called for QgsPalLayerSettings without a startRender call!" );
|
|
return;
|
|
}
|
|
|
|
if ( mCallout )
|
|
{
|
|
mCallout->stopRender( context );
|
|
}
|
|
|
|
mRenderStarted = false;
|
|
}
|
|
|
|
bool QgsPalLayerSettings::containsAdvancedEffects() const
|
|
{
|
|
return mFormat.containsAdvancedEffects() || mCallout->containsAdvancedEffects();
|
|
}
|
|
|
|
QgsPalLayerSettings::~QgsPalLayerSettings()
|
|
{
|
|
if ( mRenderStarted )
|
|
{
|
|
qWarning( "stopRender was not called on QgsPalLayerSettings object!" );
|
|
}
|
|
|
|
// pal layer is deleted internally in PAL
|
|
|
|
delete expression;
|
|
}
|
|
|
|
|
|
const QgsPropertiesDefinition &QgsPalLayerSettings::propertyDefinitions()
|
|
{
|
|
initPropertyDefinitions();
|
|
return *sPropertyDefinitions();
|
|
}
|
|
|
|
QgsExpression *QgsPalLayerSettings::getLabelExpression()
|
|
{
|
|
if ( !expression )
|
|
{
|
|
expression = new QgsExpression( fieldName );
|
|
}
|
|
return expression;
|
|
}
|
|
|
|
Qgis::AngleUnit QgsPalLayerSettings::rotationUnit() const
|
|
{
|
|
return mRotationUnit;
|
|
}
|
|
|
|
void QgsPalLayerSettings::setRotationUnit( Qgis::AngleUnit angleUnit )
|
|
{
|
|
mRotationUnit = angleUnit;
|
|
}
|
|
|
|
QString updateDataDefinedString( const QString &value )
|
|
{
|
|
// TODO: update or remove this when project settings for labeling are migrated to better XML layout
|
|
QString newValue = value;
|
|
if ( !value.isEmpty() && !value.contains( QLatin1String( "~~" ) ) )
|
|
{
|
|
QStringList values;
|
|
values << QStringLiteral( "1" ); // all old-style values are active if not empty
|
|
values << QStringLiteral( "0" );
|
|
values << QString();
|
|
values << value; // all old-style values are only field names
|
|
newValue = values.join( QLatin1String( "~~" ) );
|
|
}
|
|
|
|
return newValue;
|
|
}
|
|
|
|
void QgsPalLayerSettings::readOldDataDefinedProperty( QgsVectorLayer *layer, QgsPalLayerSettings::Property p )
|
|
{
|
|
QString newPropertyName = "labeling/dataDefined/" + sPropertyDefinitions()->value( p ).name();
|
|
QVariant newPropertyField = layer->customProperty( newPropertyName, QVariant() );
|
|
|
|
if ( !newPropertyField.isValid() )
|
|
return;
|
|
|
|
QString ddString = newPropertyField.toString();
|
|
|
|
if ( !ddString.isEmpty() && ddString != QLatin1String( "0~~0~~~~" ) )
|
|
{
|
|
// TODO: update this when project settings for labeling are migrated to better XML layout
|
|
QString newStyleString = updateDataDefinedString( ddString );
|
|
QStringList ddv = newStyleString.split( QStringLiteral( "~~" ) );
|
|
|
|
bool active = ddv.at( 0 ).toInt();
|
|
if ( ddv.at( 1 ).toInt() )
|
|
{
|
|
mDataDefinedProperties.setProperty( p, QgsProperty::fromExpression( ddv.at( 2 ), active ) );
|
|
}
|
|
else
|
|
{
|
|
mDataDefinedProperties.setProperty( p, QgsProperty::fromField( ddv.at( 3 ), active ) );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// remove unused properties
|
|
layer->removeCustomProperty( newPropertyName );
|
|
}
|
|
}
|
|
|
|
void QgsPalLayerSettings::readOldDataDefinedPropertyMap( QgsVectorLayer *layer, QDomElement *parentElem )
|
|
{
|
|
if ( !layer && !parentElem )
|
|
{
|
|
return;
|
|
}
|
|
|
|
QgsPropertiesDefinition::const_iterator i = sPropertyDefinitions()->constBegin();
|
|
for ( ; i != sPropertyDefinitions()->constEnd(); ++i )
|
|
{
|
|
if ( layer )
|
|
{
|
|
// reading from layer's custom properties
|
|
readOldDataDefinedProperty( layer, static_cast< Property >( i.key() ) );
|
|
}
|
|
else if ( parentElem )
|
|
{
|
|
// reading from XML
|
|
QDomElement e = parentElem->firstChildElement( i.value().name() );
|
|
if ( !e.isNull() )
|
|
{
|
|
bool active = e.attribute( QStringLiteral( "active" ) ).compare( QLatin1String( "true" ), Qt::CaseInsensitive ) == 0;
|
|
bool isExpression = e.attribute( QStringLiteral( "useExpr" ) ).compare( QLatin1String( "true" ), Qt::CaseInsensitive ) == 0;
|
|
if ( isExpression )
|
|
{
|
|
mDataDefinedProperties.setProperty( i.key(), QgsProperty::fromExpression( e.attribute( QStringLiteral( "expr" ) ), active ) );
|
|
}
|
|
else
|
|
{
|
|
mDataDefinedProperties.setProperty( i.key(), QgsProperty::fromField( e.attribute( QStringLiteral( "field" ) ), active ) );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void QgsPalLayerSettings::readFromLayerCustomProperties( QgsVectorLayer *layer )
|
|
{
|
|
if ( layer->customProperty( QStringLiteral( "labeling" ) ).toString() != QLatin1String( "pal" ) )
|
|
{
|
|
if ( layer->geometryType() == Qgis::GeometryType::Point )
|
|
placement = Qgis::LabelPlacement::OrderedPositionsAroundPoint;
|
|
|
|
// for polygons the "over point" (over centroid) placement is better than the default
|
|
// "around point" (around centroid) which is more suitable for points
|
|
if ( layer->geometryType() == Qgis::GeometryType::Polygon )
|
|
placement = Qgis::LabelPlacement::OverPoint;
|
|
|
|
return; // there's no information available
|
|
}
|
|
|
|
// NOTE: set defaults for newly added properties, for backwards compatibility
|
|
|
|
drawLabels = layer->customProperty( QStringLiteral( "labeling/drawLabels" ), true ).toBool();
|
|
|
|
mFormat.readFromLayer( layer );
|
|
|
|
// text style
|
|
fieldName = layer->customProperty( QStringLiteral( "labeling/fieldName" ) ).toString();
|
|
isExpression = layer->customProperty( QStringLiteral( "labeling/isExpression" ) ).toBool();
|
|
Q_NOWARN_DEPRECATED_PUSH
|
|
previewBkgrdColor = QColor( layer->customProperty( QStringLiteral( "labeling/previewBkgrdColor" ), QVariant( "#ffffff" ) ).toString() );
|
|
Q_NOWARN_DEPRECATED_POP
|
|
QDomDocument doc( QStringLiteral( "substitutions" ) );
|
|
doc.setContent( layer->customProperty( QStringLiteral( "labeling/substitutions" ) ).toString() );
|
|
QDomElement replacementElem = doc.firstChildElement( QStringLiteral( "substitutions" ) );
|
|
substitutions.readXml( replacementElem );
|
|
useSubstitutions = layer->customProperty( QStringLiteral( "labeling/useSubstitutions" ) ).toBool();
|
|
|
|
// text formatting
|
|
wrapChar = layer->customProperty( QStringLiteral( "labeling/wrapChar" ) ).toString();
|
|
autoWrapLength = layer->customProperty( QStringLiteral( "labeling/autoWrapLength" ) ).toInt();
|
|
useMaxLineLengthForAutoWrap = layer->customProperty( QStringLiteral( "labeling/useMaxLineLengthForAutoWrap" ), QStringLiteral( "1" ) ).toBool();
|
|
|
|
multilineAlign = static_cast< Qgis::LabelMultiLineAlignment >( layer->customProperty( QStringLiteral( "labeling/multilineAlign" ), QVariant( static_cast< int >( Qgis::LabelMultiLineAlignment::FollowPlacement ) ) ).toUInt() );
|
|
mLineSettings.setAddDirectionSymbol( layer->customProperty( QStringLiteral( "labeling/addDirectionSymbol" ) ).toBool() );
|
|
mLineSettings.setLeftDirectionSymbol( layer->customProperty( QStringLiteral( "labeling/leftDirectionSymbol" ), QVariant( "<" ) ).toString() );
|
|
mLineSettings.setRightDirectionSymbol( layer->customProperty( QStringLiteral( "labeling/rightDirectionSymbol" ), QVariant( ">" ) ).toString() );
|
|
mLineSettings.setReverseDirectionSymbol( layer->customProperty( QStringLiteral( "labeling/reverseDirectionSymbol" ) ).toBool() );
|
|
mLineSettings.setDirectionSymbolPlacement( static_cast< QgsLabelLineSettings::DirectionSymbolPlacement >( layer->customProperty( QStringLiteral( "labeling/placeDirectionSymbol" ), QVariant( static_cast< int >( QgsLabelLineSettings::DirectionSymbolPlacement::SymbolLeftRight ) ) ).toUInt() ) );
|
|
formatNumbers = layer->customProperty( QStringLiteral( "labeling/formatNumbers" ) ).toBool();
|
|
decimals = layer->customProperty( QStringLiteral( "labeling/decimals" ) ).toInt();
|
|
plusSign = layer->customProperty( QStringLiteral( "labeling/plussign" ) ).toBool();
|
|
|
|
// placement
|
|
placement = static_cast< Qgis::LabelPlacement >( layer->customProperty( QStringLiteral( "labeling/placement" ) ).toInt() );
|
|
mLineSettings.setPlacementFlags( static_cast< Qgis::LabelLinePlacementFlags >( layer->customProperty( QStringLiteral( "labeling/placementFlags" ) ).toUInt() ) );
|
|
centroidWhole = layer->customProperty( QStringLiteral( "labeling/centroidWhole" ), QVariant( false ) ).toBool();
|
|
centroidInside = layer->customProperty( QStringLiteral( "labeling/centroidInside" ), QVariant( false ) ).toBool();
|
|
predefinedPositionOrder = QgsLabelingUtils::decodePredefinedPositionOrder( layer->customProperty( QStringLiteral( "labeling/predefinedPositionOrder" ) ).toString() );
|
|
if ( predefinedPositionOrder.isEmpty() )
|
|
predefinedPositionOrder = *DEFAULT_PLACEMENT_ORDER();
|
|
fitInPolygonOnly = layer->customProperty( QStringLiteral( "labeling/fitInPolygonOnly" ), QVariant( false ) ).toBool();
|
|
dist = layer->customProperty( QStringLiteral( "labeling/dist" ) ).toDouble();
|
|
distUnits = layer->customProperty( QStringLiteral( "labeling/distInMapUnits" ) ).toBool() ? Qgis::RenderUnit::MapUnits : Qgis::RenderUnit::Millimeters;
|
|
if ( layer->customProperty( QStringLiteral( "labeling/distMapUnitScale" ) ).toString().isEmpty() )
|
|
{
|
|
//fallback to older property
|
|
double oldMin = layer->customProperty( QStringLiteral( "labeling/distMapUnitMinScale" ), 0.0 ).toDouble();
|
|
distMapUnitScale.minScale = !qgsDoubleNear( oldMin, 0.0 ) ? 1.0 / oldMin : 0;
|
|
double oldMax = layer->customProperty( QStringLiteral( "labeling/distMapUnitMaxScale" ), 0.0 ).toDouble();
|
|
distMapUnitScale.maxScale = !qgsDoubleNear( oldMax, 0.0 ) ? 1.0 / oldMax : 0;
|
|
}
|
|
else
|
|
{
|
|
distMapUnitScale = QgsSymbolLayerUtils::decodeMapUnitScale( layer->customProperty( QStringLiteral( "labeling/distMapUnitScale" ) ).toString() );
|
|
}
|
|
offsetType = static_cast< Qgis::LabelOffsetType >( layer->customProperty( QStringLiteral( "labeling/offsetType" ), QVariant( static_cast< int >( Qgis::LabelOffsetType::FromPoint ) ) ).toUInt() );
|
|
quadOffset = static_cast< Qgis::LabelQuadrantPosition >( layer->customProperty( QStringLiteral( "labeling/quadOffset" ), QVariant( static_cast< int >( Qgis::LabelQuadrantPosition::Over ) ) ).toUInt() );
|
|
xOffset = layer->customProperty( QStringLiteral( "labeling/xOffset" ), QVariant( 0.0 ) ).toDouble();
|
|
yOffset = layer->customProperty( QStringLiteral( "labeling/yOffset" ), QVariant( 0.0 ) ).toDouble();
|
|
if ( layer->customProperty( QStringLiteral( "labeling/labelOffsetInMapUnits" ), QVariant( true ) ).toBool() )
|
|
offsetUnits = Qgis::RenderUnit::MapUnits;
|
|
else
|
|
offsetUnits = Qgis::RenderUnit::Millimeters;
|
|
|
|
if ( layer->customProperty( QStringLiteral( "labeling/labelOffsetMapUnitScale" ) ).toString().isEmpty() )
|
|
{
|
|
//fallback to older property
|
|
double oldMin = layer->customProperty( QStringLiteral( "labeling/labelOffsetMapUnitMinScale" ), 0.0 ).toDouble();
|
|
labelOffsetMapUnitScale.minScale = !qgsDoubleNear( oldMin, 0.0 ) ? 1.0 / oldMin : 0;
|
|
double oldMax = layer->customProperty( QStringLiteral( "labeling/labelOffsetMapUnitMaxScale" ), 0.0 ).toDouble();
|
|
labelOffsetMapUnitScale.maxScale = !qgsDoubleNear( oldMax, 0 ) ? 1.0 / oldMax : 0;
|
|
}
|
|
else
|
|
{
|
|
labelOffsetMapUnitScale = QgsSymbolLayerUtils::decodeMapUnitScale( layer->customProperty( QStringLiteral( "labeling/labelOffsetMapUnitScale" ) ).toString() );
|
|
}
|
|
|
|
QVariant tempAngle = layer->customProperty( QStringLiteral( "labeling/angleOffset" ), QVariant() );
|
|
if ( tempAngle.isValid() )
|
|
{
|
|
double oldAngle = layer->customProperty( QStringLiteral( "labeling/angleOffset" ), QVariant( 0.0 ) ).toDouble();
|
|
angleOffset = std::fmod( 360 - oldAngle, 360.0 );
|
|
}
|
|
else
|
|
{
|
|
angleOffset = layer->customProperty( QStringLiteral( "labeling/rotationAngle" ), QVariant( 0.0 ) ).toDouble();
|
|
}
|
|
|
|
preserveRotation = layer->customProperty( QStringLiteral( "labeling/preserveRotation" ), QVariant( true ) ).toBool();
|
|
mRotationUnit = layer->customEnumProperty( QStringLiteral( "labeling/rotationUnit" ), Qgis::AngleUnit::Degrees );
|
|
maxCurvedCharAngleIn = layer->customProperty( QStringLiteral( "labeling/maxCurvedCharAngleIn" ), QVariant( 25.0 ) ).toDouble();
|
|
maxCurvedCharAngleOut = layer->customProperty( QStringLiteral( "labeling/maxCurvedCharAngleOut" ), QVariant( -25.0 ) ).toDouble();
|
|
priority = layer->customProperty( QStringLiteral( "labeling/priority" ) ).toInt();
|
|
repeatDistance = layer->customProperty( QStringLiteral( "labeling/repeatDistance" ), 0.0 ).toDouble();
|
|
switch ( layer->customProperty( QStringLiteral( "labeling/repeatDistanceUnit" ), QVariant( 1 ) ).toUInt() )
|
|
{
|
|
case 0:
|
|
repeatDistanceUnit = Qgis::RenderUnit::Points;
|
|
break;
|
|
case 1:
|
|
repeatDistanceUnit = Qgis::RenderUnit::Millimeters;
|
|
break;
|
|
case 2:
|
|
repeatDistanceUnit = Qgis::RenderUnit::MapUnits;
|
|
break;
|
|
case 3:
|
|
repeatDistanceUnit = Qgis::RenderUnit::Percentage;
|
|
break;
|
|
}
|
|
if ( layer->customProperty( QStringLiteral( "labeling/repeatDistanceMapUnitScale" ) ).toString().isEmpty() )
|
|
{
|
|
//fallback to older property
|
|
double oldMin = layer->customProperty( QStringLiteral( "labeling/repeatDistanceMapUnitMinScale" ), 0.0 ).toDouble();
|
|
repeatDistanceMapUnitScale.minScale = !qgsDoubleNear( oldMin, 0 ) ? 1.0 / oldMin : 0;
|
|
double oldMax = layer->customProperty( QStringLiteral( "labeling/repeatDistanceMapUnitMaxScale" ), 0.0 ).toDouble();
|
|
repeatDistanceMapUnitScale.maxScale = !qgsDoubleNear( oldMax, 0 ) ? 1.0 / oldMax : 0;
|
|
}
|
|
else
|
|
{
|
|
repeatDistanceMapUnitScale = QgsSymbolLayerUtils::decodeMapUnitScale( layer->customProperty( QStringLiteral( "labeling/repeatDistanceMapUnitScale" ) ).toString() );
|
|
}
|
|
|
|
// rendering
|
|
double scalemn = layer->customProperty( QStringLiteral( "labeling/scaleMin" ), QVariant( 0 ) ).toDouble();
|
|
double scalemx = layer->customProperty( QStringLiteral( "labeling/scaleMax" ), QVariant( 0 ) ).toDouble();
|
|
|
|
// fix for scale visibility limits being keyed off of just its values in the past (<2.0)
|
|
QVariant scalevis = layer->customProperty( QStringLiteral( "labeling/scaleVisibility" ), QVariant() );
|
|
if ( scalevis.isValid() )
|
|
{
|
|
scaleVisibility = scalevis.toBool();
|
|
maximumScale = scalemn;
|
|
minimumScale = scalemx;
|
|
}
|
|
else if ( scalemn > 0 || scalemx > 0 )
|
|
{
|
|
scaleVisibility = true;
|
|
maximumScale = scalemn;
|
|
minimumScale = scalemx;
|
|
}
|
|
else
|
|
{
|
|
// keep scaleMin and scaleMax at new 1.0 defaults (1 and 10000000, were 0 and 0)
|
|
scaleVisibility = false;
|
|
}
|
|
|
|
|
|
fontLimitPixelSize = layer->customProperty( QStringLiteral( "labeling/fontLimitPixelSize" ), QVariant( false ) ).toBool();
|
|
fontMinPixelSize = layer->customProperty( QStringLiteral( "labeling/fontMinPixelSize" ), QVariant( 0 ) ).toInt();
|
|
fontMaxPixelSize = layer->customProperty( QStringLiteral( "labeling/fontMaxPixelSize" ), QVariant( 10000 ) ).toInt();
|
|
if ( layer->customProperty( QStringLiteral( "labeling/displayAll" ), QVariant( false ) ).toBool() )
|
|
{
|
|
mPlacementSettings.setOverlapHandling( Qgis::LabelOverlapHandling::AllowOverlapIfRequired );
|
|
mPlacementSettings.setAllowDegradedPlacement( true );
|
|
}
|
|
else
|
|
{
|
|
mPlacementSettings.setOverlapHandling( Qgis::LabelOverlapHandling::PreventOverlap );
|
|
mPlacementSettings.setAllowDegradedPlacement( false );
|
|
}
|
|
upsidedownLabels = static_cast< Qgis::UpsideDownLabelHandling >( layer->customProperty( QStringLiteral( "labeling/upsidedownLabels" ), QVariant( static_cast< int >( Qgis::UpsideDownLabelHandling::FlipUpsideDownLabels ) ) ).toUInt() );
|
|
|
|
labelPerPart = layer->customProperty( QStringLiteral( "labeling/labelPerPart" ) ).toBool();
|
|
mLineSettings.setMergeLines( layer->customProperty( QStringLiteral( "labeling/mergeLines" ) ).toBool() );
|
|
mThinningSettings.setMinimumFeatureSize( layer->customProperty( QStringLiteral( "labeling/minFeatureSize" ) ).toDouble() );
|
|
mThinningSettings.setLimitNumberLabelsEnabled( layer->customProperty( QStringLiteral( "labeling/limitNumLabels" ), QVariant( false ) ).toBool() );
|
|
mThinningSettings.setMaximumNumberLabels( layer->customProperty( QStringLiteral( "labeling/maxNumLabels" ), QVariant( 2000 ) ).toInt() );
|
|
mObstacleSettings.setIsObstacle( layer->customProperty( QStringLiteral( "labeling/obstacle" ), QVariant( true ) ).toBool() );
|
|
mObstacleSettings.setFactor( layer->customProperty( QStringLiteral( "labeling/obstacleFactor" ), QVariant( 1.0 ) ).toDouble() );
|
|
mObstacleSettings.setType( static_cast< QgsLabelObstacleSettings::ObstacleType >( layer->customProperty( QStringLiteral( "labeling/obstacleType" ), QVariant( PolygonInterior ) ).toUInt() ) );
|
|
zIndex = layer->customProperty( QStringLiteral( "labeling/zIndex" ), QVariant( 0.0 ) ).toDouble();
|
|
|
|
mDataDefinedProperties.clear();
|
|
if ( layer->customProperty( QStringLiteral( "labeling/ddProperties" ) ).isValid() )
|
|
{
|
|
QDomDocument doc( QStringLiteral( "dd" ) );
|
|
doc.setContent( layer->customProperty( QStringLiteral( "labeling/ddProperties" ) ).toString() );
|
|
QDomElement elem = doc.firstChildElement( QStringLiteral( "properties" ) );
|
|
mDataDefinedProperties.readXml( elem, *sPropertyDefinitions() );
|
|
}
|
|
else
|
|
{
|
|
// read QGIS 2.x style data defined properties
|
|
readOldDataDefinedPropertyMap( layer, nullptr );
|
|
}
|
|
// upgrade older data defined settings
|
|
if ( mDataDefinedProperties.isActive( FontTransp ) )
|
|
{
|
|
mDataDefinedProperties.setProperty( FontOpacity, QgsProperty::fromExpression( QStringLiteral( "100 - (%1)" ).arg( mDataDefinedProperties.property( FontTransp ).asExpression() ) ) );
|
|
mDataDefinedProperties.setProperty( FontTransp, QgsProperty() );
|
|
}
|
|
if ( mDataDefinedProperties.isActive( BufferTransp ) )
|
|
{
|
|
mDataDefinedProperties.setProperty( BufferOpacity, QgsProperty::fromExpression( QStringLiteral( "100 - (%1)" ).arg( mDataDefinedProperties.property( BufferTransp ).asExpression() ) ) );
|
|
mDataDefinedProperties.setProperty( BufferTransp, QgsProperty() );
|
|
}
|
|
if ( mDataDefinedProperties.isActive( ShapeTransparency ) )
|
|
{
|
|
mDataDefinedProperties.setProperty( ShapeOpacity, QgsProperty::fromExpression( QStringLiteral( "100 - (%1)" ).arg( mDataDefinedProperties.property( ShapeTransparency ).asExpression() ) ) );
|
|
mDataDefinedProperties.setProperty( ShapeTransparency, QgsProperty() );
|
|
}
|
|
if ( mDataDefinedProperties.isActive( ShadowTransparency ) )
|
|
{
|
|
mDataDefinedProperties.setProperty( ShadowOpacity, QgsProperty::fromExpression( QStringLiteral( "100 - (%1)" ).arg( mDataDefinedProperties.property( ShadowTransparency ).asExpression() ) ) );
|
|
mDataDefinedProperties.setProperty( ShadowTransparency, QgsProperty() );
|
|
}
|
|
if ( mDataDefinedProperties.isActive( Rotation ) )
|
|
{
|
|
mDataDefinedProperties.setProperty( LabelRotation, QgsProperty::fromExpression( QStringLiteral( "360 - (%1)" ).arg( mDataDefinedProperties.property( Rotation ).asExpression() ) ) );
|
|
mDataDefinedProperties.setProperty( Rotation, QgsProperty() );
|
|
}
|
|
// older 2.x projects had min/max scale flipped - so change them here.
|
|
if ( mDataDefinedProperties.isActive( MinScale ) )
|
|
{
|
|
mDataDefinedProperties.setProperty( MaximumScale, mDataDefinedProperties.property( MinScale ) );
|
|
mDataDefinedProperties.setProperty( MinScale, QgsProperty() );
|
|
}
|
|
if ( mDataDefinedProperties.isActive( MaxScale ) )
|
|
{
|
|
mDataDefinedProperties.setProperty( MinimumScale, mDataDefinedProperties.property( MaxScale ) );
|
|
mDataDefinedProperties.setProperty( MaxScale, QgsProperty() );
|
|
}
|
|
}
|
|
|
|
void QgsPalLayerSettings::readXml( const QDomElement &elem, const QgsReadWriteContext &context )
|
|
{
|
|
// text style
|
|
QDomElement textStyleElem = elem.firstChildElement( QStringLiteral( "text-style" ) );
|
|
fieldName = textStyleElem.attribute( QStringLiteral( "fieldName" ) );
|
|
isExpression = textStyleElem.attribute( QStringLiteral( "isExpression" ) ).toInt();
|
|
|
|
mFormat.readXml( elem, context );
|
|
Q_NOWARN_DEPRECATED_PUSH
|
|
previewBkgrdColor = QColor( textStyleElem.attribute( QStringLiteral( "previewBkgrdColor" ), QStringLiteral( "#ffffff" ) ) );
|
|
Q_NOWARN_DEPRECATED_POP
|
|
substitutions.readXml( textStyleElem.firstChildElement( QStringLiteral( "substitutions" ) ) );
|
|
useSubstitutions = textStyleElem.attribute( QStringLiteral( "useSubstitutions" ) ).toInt();
|
|
mLegendString = textStyleElem.attribute( QStringLiteral( "legendString" ), QObject::tr( "Aa" ) );
|
|
|
|
// text formatting
|
|
QDomElement textFormatElem = elem.firstChildElement( QStringLiteral( "text-format" ) );
|
|
wrapChar = textFormatElem.attribute( QStringLiteral( "wrapChar" ) );
|
|
autoWrapLength = textFormatElem.attribute( QStringLiteral( "autoWrapLength" ), QStringLiteral( "0" ) ).toInt();
|
|
useMaxLineLengthForAutoWrap = textFormatElem.attribute( QStringLiteral( "useMaxLineLengthForAutoWrap" ), QStringLiteral( "1" ) ).toInt();
|
|
multilineAlign = static_cast< Qgis::LabelMultiLineAlignment >( textFormatElem.attribute( QStringLiteral( "multilineAlign" ), QString::number( static_cast< int >( Qgis::LabelMultiLineAlignment::FollowPlacement ) ) ).toUInt() );
|
|
mLineSettings.setAddDirectionSymbol( textFormatElem.attribute( QStringLiteral( "addDirectionSymbol" ) ).toInt() );
|
|
mLineSettings.setLeftDirectionSymbol( textFormatElem.attribute( QStringLiteral( "leftDirectionSymbol" ), QStringLiteral( "<" ) ) );
|
|
mLineSettings.setRightDirectionSymbol( textFormatElem.attribute( QStringLiteral( "rightDirectionSymbol" ), QStringLiteral( ">" ) ) );
|
|
mLineSettings.setReverseDirectionSymbol( textFormatElem.attribute( QStringLiteral( "reverseDirectionSymbol" ) ).toInt() );
|
|
mLineSettings.setDirectionSymbolPlacement( static_cast< QgsLabelLineSettings::DirectionSymbolPlacement >( textFormatElem.attribute( QStringLiteral( "placeDirectionSymbol" ), QString::number( static_cast< int >( QgsLabelLineSettings::DirectionSymbolPlacement::SymbolLeftRight ) ) ).toUInt() ) );
|
|
formatNumbers = textFormatElem.attribute( QStringLiteral( "formatNumbers" ) ).toInt();
|
|
decimals = textFormatElem.attribute( QStringLiteral( "decimals" ) ).toInt();
|
|
plusSign = textFormatElem.attribute( QStringLiteral( "plussign" ) ).toInt();
|
|
|
|
// placement
|
|
QDomElement placementElem = elem.firstChildElement( QStringLiteral( "placement" ) );
|
|
placement = static_cast< Qgis::LabelPlacement >( placementElem.attribute( QStringLiteral( "placement" ) ).toInt() );
|
|
mLineSettings.setPlacementFlags( static_cast< Qgis::LabelLinePlacementFlags >( placementElem.attribute( QStringLiteral( "placementFlags" ) ).toUInt() ) );
|
|
mPolygonPlacementFlags = static_cast< Qgis::LabelPolygonPlacementFlags >( placementElem.attribute( QStringLiteral( "polygonPlacementFlags" ), QString::number( static_cast< int >( Qgis::LabelPolygonPlacementFlag::AllowPlacementInsideOfPolygon ) ) ).toInt() );
|
|
|
|
centroidWhole = placementElem.attribute( QStringLiteral( "centroidWhole" ), QStringLiteral( "0" ) ).toInt();
|
|
centroidInside = placementElem.attribute( QStringLiteral( "centroidInside" ), QStringLiteral( "0" ) ).toInt();
|
|
predefinedPositionOrder = QgsLabelingUtils::decodePredefinedPositionOrder( placementElem.attribute( QStringLiteral( "predefinedPositionOrder" ) ) );
|
|
if ( predefinedPositionOrder.isEmpty() )
|
|
predefinedPositionOrder = *DEFAULT_PLACEMENT_ORDER();
|
|
fitInPolygonOnly = placementElem.attribute( QStringLiteral( "fitInPolygonOnly" ), QStringLiteral( "0" ) ).toInt();
|
|
dist = placementElem.attribute( QStringLiteral( "dist" ) ).toDouble();
|
|
if ( !placementElem.hasAttribute( QStringLiteral( "distUnits" ) ) )
|
|
{
|
|
if ( placementElem.attribute( QStringLiteral( "distInMapUnits" ) ).toInt() )
|
|
distUnits = Qgis::RenderUnit::MapUnits;
|
|
else
|
|
distUnits = Qgis::RenderUnit::Millimeters;
|
|
}
|
|
else
|
|
{
|
|
distUnits = QgsUnitTypes::decodeRenderUnit( placementElem.attribute( QStringLiteral( "distUnits" ) ) );
|
|
}
|
|
if ( !placementElem.hasAttribute( QStringLiteral( "distMapUnitScale" ) ) )
|
|
{
|
|
//fallback to older property
|
|
double oldMin = placementElem.attribute( QStringLiteral( "distMapUnitMinScale" ), QStringLiteral( "0" ) ).toDouble();
|
|
distMapUnitScale.minScale = !qgsDoubleNear( oldMin, 0 ) ? 1.0 / oldMin : 0;
|
|
double oldMax = placementElem.attribute( QStringLiteral( "distMapUnitMaxScale" ), QStringLiteral( "0" ) ).toDouble();
|
|
distMapUnitScale.maxScale = !qgsDoubleNear( oldMax, 0 ) ? 1.0 / oldMax : 0;
|
|
}
|
|
else
|
|
{
|
|
distMapUnitScale = QgsSymbolLayerUtils::decodeMapUnitScale( placementElem.attribute( QStringLiteral( "distMapUnitScale" ) ) );
|
|
}
|
|
offsetType = static_cast< Qgis::LabelOffsetType >( placementElem.attribute( QStringLiteral( "offsetType" ), QString::number( static_cast< int >( Qgis::LabelOffsetType::FromPoint ) ) ).toUInt() );
|
|
quadOffset = static_cast< Qgis::LabelQuadrantPosition >( placementElem.attribute( QStringLiteral( "quadOffset" ), QString::number( static_cast< int >( Qgis::LabelQuadrantPosition::Over ) ) ).toUInt() );
|
|
xOffset = placementElem.attribute( QStringLiteral( "xOffset" ), QStringLiteral( "0" ) ).toDouble();
|
|
yOffset = placementElem.attribute( QStringLiteral( "yOffset" ), QStringLiteral( "0" ) ).toDouble();
|
|
if ( !placementElem.hasAttribute( QStringLiteral( "offsetUnits" ) ) )
|
|
{
|
|
offsetUnits = placementElem.attribute( QStringLiteral( "labelOffsetInMapUnits" ), QStringLiteral( "1" ) ).toInt() ? Qgis::RenderUnit::MapUnits : Qgis::RenderUnit::Millimeters;
|
|
}
|
|
else
|
|
{
|
|
offsetUnits = QgsUnitTypes::decodeRenderUnit( placementElem.attribute( QStringLiteral( "offsetUnits" ) ) );
|
|
}
|
|
if ( !placementElem.hasAttribute( QStringLiteral( "labelOffsetMapUnitScale" ) ) )
|
|
{
|
|
//fallback to older property
|
|
double oldMin = placementElem.attribute( QStringLiteral( "labelOffsetMapUnitMinScale" ), QStringLiteral( "0" ) ).toDouble();
|
|
labelOffsetMapUnitScale.minScale = !qgsDoubleNear( oldMin, 0.0 ) ? 1.0 / oldMin : 0;
|
|
double oldMax = placementElem.attribute( QStringLiteral( "labelOffsetMapUnitMaxScale" ), QStringLiteral( "0" ) ).toDouble();
|
|
labelOffsetMapUnitScale.maxScale = !qgsDoubleNear( oldMax, 0.0 ) ? 1.0 / oldMax : 0;
|
|
}
|
|
else
|
|
{
|
|
labelOffsetMapUnitScale = QgsSymbolLayerUtils::decodeMapUnitScale( placementElem.attribute( QStringLiteral( "labelOffsetMapUnitScale" ) ) );
|
|
}
|
|
|
|
if ( placementElem.hasAttribute( QStringLiteral( "angleOffset" ) ) )
|
|
{
|
|
double oldAngle = placementElem.attribute( QStringLiteral( "angleOffset" ), QStringLiteral( "0" ) ).toDouble();
|
|
angleOffset = std::fmod( 360 - oldAngle, 360.0 );
|
|
}
|
|
else
|
|
{
|
|
angleOffset = placementElem.attribute( QStringLiteral( "rotationAngle" ), QStringLiteral( "0" ) ).toDouble();
|
|
}
|
|
|
|
preserveRotation = placementElem.attribute( QStringLiteral( "preserveRotation" ), QStringLiteral( "1" ) ).toInt();
|
|
{
|
|
QString rotationUnitString = placementElem.attribute( QStringLiteral( "rotationUnit" ), qgsEnumValueToKey( Qgis::AngleUnit::Degrees ) );
|
|
if ( rotationUnitString.startsWith( QLatin1String( "Angle" ) ) )
|
|
{
|
|
// compatibility with QGIS < 3.30
|
|
rotationUnitString = rotationUnitString.mid( 5 );
|
|
}
|
|
|
|
mRotationUnit = qgsEnumKeyToValue( rotationUnitString, Qgis::AngleUnit::Degrees );
|
|
}
|
|
maxCurvedCharAngleIn = placementElem.attribute( QStringLiteral( "maxCurvedCharAngleIn" ), QStringLiteral( "25" ) ).toDouble();
|
|
maxCurvedCharAngleOut = placementElem.attribute( QStringLiteral( "maxCurvedCharAngleOut" ), QStringLiteral( "-25" ) ).toDouble();
|
|
priority = placementElem.attribute( QStringLiteral( "priority" ) ).toInt();
|
|
repeatDistance = placementElem.attribute( QStringLiteral( "repeatDistance" ), QStringLiteral( "0" ) ).toDouble();
|
|
if ( !placementElem.hasAttribute( QStringLiteral( "repeatDistanceUnits" ) ) )
|
|
{
|
|
// upgrade old setting
|
|
switch ( placementElem.attribute( QStringLiteral( "repeatDistanceUnit" ), QString::number( 1 ) ).toUInt() )
|
|
{
|
|
case 0:
|
|
repeatDistanceUnit = Qgis::RenderUnit::Points;
|
|
break;
|
|
case 1:
|
|
repeatDistanceUnit = Qgis::RenderUnit::Millimeters;
|
|
break;
|
|
case 2:
|
|
repeatDistanceUnit = Qgis::RenderUnit::MapUnits;
|
|
break;
|
|
case 3:
|
|
repeatDistanceUnit = Qgis::RenderUnit::Percentage;
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
repeatDistanceUnit = QgsUnitTypes::decodeRenderUnit( placementElem.attribute( QStringLiteral( "repeatDistanceUnits" ) ) );
|
|
}
|
|
if ( !placementElem.hasAttribute( QStringLiteral( "repeatDistanceMapUnitScale" ) ) )
|
|
{
|
|
//fallback to older property
|
|
double oldMin = placementElem.attribute( QStringLiteral( "repeatDistanceMapUnitMinScale" ), QStringLiteral( "0" ) ).toDouble();
|
|
repeatDistanceMapUnitScale.minScale = !qgsDoubleNear( oldMin, 0.0 ) ? 1.0 / oldMin : 0;
|
|
double oldMax = placementElem.attribute( QStringLiteral( "repeatDistanceMapUnitMaxScale" ), QStringLiteral( "0" ) ).toDouble();
|
|
repeatDistanceMapUnitScale.maxScale = !qgsDoubleNear( oldMax, 0.0 ) ? 1.0 / oldMax : 0;
|
|
}
|
|
else
|
|
{
|
|
repeatDistanceMapUnitScale = QgsSymbolLayerUtils::decodeMapUnitScale( placementElem.attribute( QStringLiteral( "repeatDistanceMapUnitScale" ) ) );
|
|
}
|
|
|
|
mLineSettings.setOverrunDistance( placementElem.attribute( QStringLiteral( "overrunDistance" ), QStringLiteral( "0" ) ).toDouble() );
|
|
mLineSettings.setOverrunDistanceUnit( QgsUnitTypes::decodeRenderUnit( placementElem.attribute( QStringLiteral( "overrunDistanceUnit" ) ) ) );
|
|
mLineSettings.setOverrunDistanceMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( placementElem.attribute( QStringLiteral( "overrunDistanceMapUnitScale" ) ) ) );
|
|
mLineSettings.setLineAnchorPercent( placementElem.attribute( QStringLiteral( "lineAnchorPercent" ), QStringLiteral( "0.5" ) ).toDouble() );
|
|
mLineSettings.setAnchorType( static_cast< QgsLabelLineSettings::AnchorType >( placementElem.attribute( QStringLiteral( "lineAnchorType" ), QStringLiteral( "0" ) ).toInt() ) );
|
|
mLineSettings.setAnchorClipping( static_cast< QgsLabelLineSettings::AnchorClipping >( placementElem.attribute( QStringLiteral( "lineAnchorClipping" ), QStringLiteral( "0" ) ).toInt() ) );
|
|
// when reading the anchor text point we default to center mode, to keep same result as for proejcts created in < 3.26
|
|
mLineSettings.setAnchorTextPoint( qgsEnumKeyToValue( placementElem.attribute( QStringLiteral( "lineAnchorTextPoint" ) ), QgsLabelLineSettings::AnchorTextPoint::CenterOfText ) );
|
|
|
|
geometryGenerator = placementElem.attribute( QStringLiteral( "geometryGenerator" ) );
|
|
geometryGeneratorEnabled = placementElem.attribute( QStringLiteral( "geometryGeneratorEnabled" ) ).toInt();
|
|
{
|
|
QString geometryTypeKey = placementElem.attribute( QStringLiteral( "geometryGeneratorType" ) );
|
|
// maintain compatibility with < 3.3.0
|
|
if ( geometryTypeKey.endsWith( QLatin1String( "Geometry" ) ) )
|
|
geometryTypeKey.chop( 8 );
|
|
|
|
geometryGeneratorType = qgsEnumKeyToValue( geometryTypeKey, Qgis::GeometryType::Point );
|
|
}
|
|
{
|
|
QString layerTypeKey = placementElem.attribute( QStringLiteral( "layerType" ) );
|
|
// maintain compatibility with < 3.3.0
|
|
if ( layerTypeKey.endsWith( QLatin1String( "Geometry" ) ) )
|
|
layerTypeKey.chop( 8 );
|
|
|
|
layerType = qgsEnumKeyToValue( layerTypeKey, Qgis::GeometryType::Unknown );
|
|
}
|
|
|
|
mPlacementSettings.setAllowDegradedPlacement( placementElem.attribute( QStringLiteral( "allowDegraded" ), QStringLiteral( "0" ) ).toInt() );
|
|
|
|
// rendering
|
|
QDomElement renderingElem = elem.firstChildElement( QStringLiteral( "rendering" ) );
|
|
|
|
drawLabels = renderingElem.attribute( QStringLiteral( "drawLabels" ), QStringLiteral( "1" ) ).toInt();
|
|
|
|
maximumScale = renderingElem.attribute( QStringLiteral( "scaleMin" ), QStringLiteral( "0" ) ).toDouble();
|
|
minimumScale = renderingElem.attribute( QStringLiteral( "scaleMax" ), QStringLiteral( "0" ) ).toDouble();
|
|
scaleVisibility = renderingElem.attribute( QStringLiteral( "scaleVisibility" ) ).toInt();
|
|
|
|
fontLimitPixelSize = renderingElem.attribute( QStringLiteral( "fontLimitPixelSize" ), QStringLiteral( "0" ) ).toInt();
|
|
fontMinPixelSize = renderingElem.attribute( QStringLiteral( "fontMinPixelSize" ), QStringLiteral( "0" ) ).toInt();
|
|
fontMaxPixelSize = renderingElem.attribute( QStringLiteral( "fontMaxPixelSize" ), QStringLiteral( "10000" ) ).toInt();
|
|
|
|
if ( placementElem.hasAttribute( QStringLiteral( "overlapHandling" ) ) )
|
|
{
|
|
mPlacementSettings.setOverlapHandling( qgsEnumKeyToValue( placementElem.attribute( QStringLiteral( "overlapHandling" ) ), Qgis::LabelOverlapHandling::PreventOverlap ) );
|
|
}
|
|
else
|
|
{
|
|
// legacy setting
|
|
if ( renderingElem.attribute( QStringLiteral( "displayAll" ), QStringLiteral( "0" ) ).toInt() )
|
|
{
|
|
mPlacementSettings.setOverlapHandling( Qgis::LabelOverlapHandling::AllowOverlapIfRequired );
|
|
mPlacementSettings.setAllowDegradedPlacement( true );
|
|
}
|
|
else
|
|
{
|
|
mPlacementSettings.setOverlapHandling( Qgis::LabelOverlapHandling::PreventOverlap );
|
|
mPlacementSettings.setAllowDegradedPlacement( false );
|
|
}
|
|
}
|
|
upsidedownLabels = static_cast< Qgis::UpsideDownLabelHandling >( renderingElem.attribute( QStringLiteral( "upsidedownLabels" ), QString::number( static_cast< int >( Qgis::UpsideDownLabelHandling::FlipUpsideDownLabels ) ) ).toUInt() );
|
|
|
|
labelPerPart = renderingElem.attribute( QStringLiteral( "labelPerPart" ) ).toInt();
|
|
mLineSettings.setMergeLines( renderingElem.attribute( QStringLiteral( "mergeLines" ) ).toInt() );
|
|
mThinningSettings.setMinimumFeatureSize( renderingElem.attribute( QStringLiteral( "minFeatureSize" ) ).toDouble() );
|
|
mThinningSettings.setLimitNumberLabelsEnabled( renderingElem.attribute( QStringLiteral( "limitNumLabels" ), QStringLiteral( "0" ) ).toInt() );
|
|
mThinningSettings.setMaximumNumberLabels( renderingElem.attribute( QStringLiteral( "maxNumLabels" ), QStringLiteral( "2000" ) ).toInt() );
|
|
mObstacleSettings.setIsObstacle( renderingElem.attribute( QStringLiteral( "obstacle" ), QStringLiteral( "1" ) ).toInt() );
|
|
mObstacleSettings.setFactor( renderingElem.attribute( QStringLiteral( "obstacleFactor" ), QStringLiteral( "1" ) ).toDouble() );
|
|
mObstacleSettings.setType( static_cast< QgsLabelObstacleSettings::ObstacleType >( renderingElem.attribute( QStringLiteral( "obstacleType" ), QString::number( PolygonInterior ) ).toUInt() ) );
|
|
zIndex = renderingElem.attribute( QStringLiteral( "zIndex" ), QStringLiteral( "0.0" ) ).toDouble();
|
|
mUnplacedVisibility = static_cast< Qgis::UnplacedLabelVisibility >( renderingElem.attribute( QStringLiteral( "unplacedVisibility" ), QString::number( static_cast< int >( Qgis::UnplacedLabelVisibility::FollowEngineSetting ) ) ).toInt() );
|
|
|
|
QDomElement ddElem = elem.firstChildElement( QStringLiteral( "dd_properties" ) );
|
|
if ( !ddElem.isNull() )
|
|
{
|
|
mDataDefinedProperties.readXml( ddElem, *sPropertyDefinitions() );
|
|
}
|
|
else
|
|
{
|
|
// upgrade 2.x style dd project
|
|
mDataDefinedProperties.clear();
|
|
QDomElement ddElem = elem.firstChildElement( QStringLiteral( "data-defined" ) );
|
|
readOldDataDefinedPropertyMap( nullptr, &ddElem );
|
|
}
|
|
// upgrade older data defined settings
|
|
if ( mDataDefinedProperties.isActive( FontTransp ) )
|
|
{
|
|
mDataDefinedProperties.setProperty( FontOpacity, QgsProperty::fromExpression( QStringLiteral( "100 - (%1)" ).arg( mDataDefinedProperties.property( FontTransp ).asExpression() ) ) );
|
|
mDataDefinedProperties.setProperty( FontTransp, QgsProperty() );
|
|
}
|
|
if ( mDataDefinedProperties.isActive( BufferTransp ) )
|
|
{
|
|
mDataDefinedProperties.setProperty( BufferOpacity, QgsProperty::fromExpression( QStringLiteral( "100 - (%1)" ).arg( mDataDefinedProperties.property( BufferTransp ).asExpression() ) ) );
|
|
mDataDefinedProperties.setProperty( BufferTransp, QgsProperty() );
|
|
}
|
|
if ( mDataDefinedProperties.isActive( ShapeTransparency ) )
|
|
{
|
|
mDataDefinedProperties.setProperty( ShapeOpacity, QgsProperty::fromExpression( QStringLiteral( "100 - (%1)" ).arg( mDataDefinedProperties.property( ShapeTransparency ).asExpression() ) ) );
|
|
mDataDefinedProperties.setProperty( ShapeTransparency, QgsProperty() );
|
|
}
|
|
if ( mDataDefinedProperties.isActive( ShadowTransparency ) )
|
|
{
|
|
mDataDefinedProperties.setProperty( ShadowOpacity, QgsProperty::fromExpression( QStringLiteral( "100 - (%1)" ).arg( mDataDefinedProperties.property( ShadowTransparency ).asExpression() ) ) );
|
|
mDataDefinedProperties.setProperty( ShadowTransparency, QgsProperty() );
|
|
}
|
|
if ( mDataDefinedProperties.isActive( Rotation ) )
|
|
{
|
|
mDataDefinedProperties.setProperty( LabelRotation, QgsProperty::fromExpression( QStringLiteral( "360 - (%1)" ).arg( mDataDefinedProperties.property( Rotation ).asExpression() ) ) );
|
|
mDataDefinedProperties.setProperty( Rotation, QgsProperty() );
|
|
}
|
|
// older 2.x projects had min/max scale flipped - so change them here.
|
|
if ( mDataDefinedProperties.isActive( MinScale ) )
|
|
{
|
|
mDataDefinedProperties.setProperty( MaximumScale, mDataDefinedProperties.property( MinScale ) );
|
|
mDataDefinedProperties.setProperty( MinScale, QgsProperty() );
|
|
}
|
|
if ( mDataDefinedProperties.isActive( MaxScale ) )
|
|
{
|
|
mDataDefinedProperties.setProperty( MinimumScale, mDataDefinedProperties.property( MaxScale ) );
|
|
mDataDefinedProperties.setProperty( MaxScale, QgsProperty() );
|
|
}
|
|
|
|
// TODO - replace with registry when multiple callout styles exist
|
|
const QString calloutType = elem.attribute( QStringLiteral( "calloutType" ) );
|
|
if ( calloutType.isEmpty() )
|
|
mCallout.reset( QgsCalloutRegistry::defaultCallout() );
|
|
else
|
|
{
|
|
mCallout.reset( QgsApplication::calloutRegistry()->createCallout( calloutType, elem.firstChildElement( QStringLiteral( "callout" ) ), context ) );
|
|
if ( !mCallout )
|
|
mCallout.reset( QgsCalloutRegistry::defaultCallout() );
|
|
}
|
|
}
|
|
|
|
QDomElement QgsPalLayerSettings::writeXml( QDomDocument &doc, const QgsReadWriteContext &context ) const
|
|
{
|
|
QDomElement textStyleElem = mFormat.writeXml( doc, context );
|
|
|
|
// text style
|
|
textStyleElem.setAttribute( QStringLiteral( "fieldName" ), fieldName );
|
|
textStyleElem.setAttribute( QStringLiteral( "isExpression" ), isExpression );
|
|
QDomElement replacementElem = doc.createElement( QStringLiteral( "substitutions" ) );
|
|
substitutions.writeXml( replacementElem, doc );
|
|
textStyleElem.appendChild( replacementElem );
|
|
textStyleElem.setAttribute( QStringLiteral( "useSubstitutions" ), useSubstitutions );
|
|
textStyleElem.setAttribute( QStringLiteral( "legendString" ), mLegendString );
|
|
|
|
// text formatting
|
|
QDomElement textFormatElem = doc.createElement( QStringLiteral( "text-format" ) );
|
|
textFormatElem.setAttribute( QStringLiteral( "wrapChar" ), wrapChar );
|
|
textFormatElem.setAttribute( QStringLiteral( "autoWrapLength" ), autoWrapLength );
|
|
textFormatElem.setAttribute( QStringLiteral( "useMaxLineLengthForAutoWrap" ), useMaxLineLengthForAutoWrap );
|
|
textFormatElem.setAttribute( QStringLiteral( "multilineAlign" ), static_cast< unsigned int >( multilineAlign ) );
|
|
textFormatElem.setAttribute( QStringLiteral( "addDirectionSymbol" ), mLineSettings.addDirectionSymbol() );
|
|
textFormatElem.setAttribute( QStringLiteral( "leftDirectionSymbol" ), mLineSettings.leftDirectionSymbol() );
|
|
textFormatElem.setAttribute( QStringLiteral( "rightDirectionSymbol" ), mLineSettings.rightDirectionSymbol() );
|
|
textFormatElem.setAttribute( QStringLiteral( "reverseDirectionSymbol" ), mLineSettings.reverseDirectionSymbol() );
|
|
textFormatElem.setAttribute( QStringLiteral( "placeDirectionSymbol" ), static_cast< unsigned int >( mLineSettings.directionSymbolPlacement() ) );
|
|
textFormatElem.setAttribute( QStringLiteral( "formatNumbers" ), formatNumbers );
|
|
textFormatElem.setAttribute( QStringLiteral( "decimals" ), decimals );
|
|
textFormatElem.setAttribute( QStringLiteral( "plussign" ), plusSign );
|
|
|
|
// placement
|
|
QDomElement placementElem = doc.createElement( QStringLiteral( "placement" ) );
|
|
placementElem.setAttribute( QStringLiteral( "placement" ), static_cast< int >( placement ) );
|
|
placementElem.setAttribute( QStringLiteral( "polygonPlacementFlags" ), static_cast< int >( mPolygonPlacementFlags ) );
|
|
placementElem.setAttribute( QStringLiteral( "placementFlags" ), static_cast< unsigned int >( mLineSettings.placementFlags() ) );
|
|
placementElem.setAttribute( QStringLiteral( "centroidWhole" ), centroidWhole );
|
|
placementElem.setAttribute( QStringLiteral( "centroidInside" ), centroidInside );
|
|
placementElem.setAttribute( QStringLiteral( "predefinedPositionOrder" ), QgsLabelingUtils::encodePredefinedPositionOrder( predefinedPositionOrder ) );
|
|
placementElem.setAttribute( QStringLiteral( "fitInPolygonOnly" ), fitInPolygonOnly );
|
|
placementElem.setAttribute( QStringLiteral( "dist" ), dist );
|
|
placementElem.setAttribute( QStringLiteral( "distUnits" ), QgsUnitTypes::encodeUnit( distUnits ) );
|
|
placementElem.setAttribute( QStringLiteral( "distMapUnitScale" ), QgsSymbolLayerUtils::encodeMapUnitScale( distMapUnitScale ) );
|
|
placementElem.setAttribute( QStringLiteral( "offsetType" ), static_cast< unsigned int >( offsetType ) );
|
|
placementElem.setAttribute( QStringLiteral( "quadOffset" ), static_cast< unsigned int >( quadOffset ) );
|
|
placementElem.setAttribute( QStringLiteral( "xOffset" ), xOffset );
|
|
placementElem.setAttribute( QStringLiteral( "yOffset" ), yOffset );
|
|
placementElem.setAttribute( QStringLiteral( "offsetUnits" ), QgsUnitTypes::encodeUnit( offsetUnits ) );
|
|
placementElem.setAttribute( QStringLiteral( "labelOffsetMapUnitScale" ), QgsSymbolLayerUtils::encodeMapUnitScale( labelOffsetMapUnitScale ) );
|
|
placementElem.setAttribute( QStringLiteral( "rotationAngle" ), angleOffset );
|
|
placementElem.setAttribute( QStringLiteral( "preserveRotation" ), preserveRotation );
|
|
{
|
|
// append Angle prefix to maintain compatibility with QGIS < 3.30
|
|
const QString rotationUnitString = QStringLiteral( "Angle" ) + qgsEnumValueToKey( mRotationUnit );
|
|
placementElem.setAttribute( QStringLiteral( "rotationUnit" ), rotationUnitString );
|
|
}
|
|
placementElem.setAttribute( QStringLiteral( "maxCurvedCharAngleIn" ), maxCurvedCharAngleIn );
|
|
placementElem.setAttribute( QStringLiteral( "maxCurvedCharAngleOut" ), maxCurvedCharAngleOut );
|
|
placementElem.setAttribute( QStringLiteral( "priority" ), priority );
|
|
placementElem.setAttribute( QStringLiteral( "repeatDistance" ), repeatDistance );
|
|
placementElem.setAttribute( QStringLiteral( "repeatDistanceUnits" ), QgsUnitTypes::encodeUnit( repeatDistanceUnit ) );
|
|
placementElem.setAttribute( QStringLiteral( "repeatDistanceMapUnitScale" ), QgsSymbolLayerUtils::encodeMapUnitScale( repeatDistanceMapUnitScale ) );
|
|
placementElem.setAttribute( QStringLiteral( "overrunDistance" ), mLineSettings.overrunDistance() );
|
|
placementElem.setAttribute( QStringLiteral( "overrunDistanceUnit" ), QgsUnitTypes::encodeUnit( mLineSettings.overrunDistanceUnit() ) );
|
|
placementElem.setAttribute( QStringLiteral( "overrunDistanceMapUnitScale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mLineSettings.overrunDistanceMapUnitScale() ) );
|
|
placementElem.setAttribute( QStringLiteral( "lineAnchorPercent" ), mLineSettings.lineAnchorPercent() );
|
|
placementElem.setAttribute( QStringLiteral( "lineAnchorType" ), static_cast< int >( mLineSettings.anchorType() ) );
|
|
placementElem.setAttribute( QStringLiteral( "lineAnchorClipping" ), static_cast< int >( mLineSettings.anchorClipping() ) );
|
|
placementElem.setAttribute( QStringLiteral( "lineAnchorTextPoint" ), qgsEnumValueToKey( mLineSettings.anchorTextPoint() ) );
|
|
|
|
placementElem.setAttribute( QStringLiteral( "geometryGenerator" ), geometryGenerator );
|
|
placementElem.setAttribute( QStringLiteral( "geometryGeneratorEnabled" ), geometryGeneratorEnabled );
|
|
placementElem.setAttribute( QStringLiteral( "geometryGeneratorType" ), qgsEnumValueToKey( geometryGeneratorType ) + QStringLiteral( "Geometry" ) );
|
|
|
|
placementElem.setAttribute( QStringLiteral( "layerType" ), qgsEnumValueToKey( layerType ) + QStringLiteral( "Geometry" ) );
|
|
|
|
placementElem.setAttribute( QStringLiteral( "overlapHandling" ), qgsEnumValueToKey( mPlacementSettings.overlapHandling() ) );
|
|
placementElem.setAttribute( QStringLiteral( "allowDegraded" ), mPlacementSettings.allowDegradedPlacement() ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
|
|
|
|
// rendering
|
|
QDomElement renderingElem = doc.createElement( QStringLiteral( "rendering" ) );
|
|
renderingElem.setAttribute( QStringLiteral( "drawLabels" ), drawLabels );
|
|
renderingElem.setAttribute( QStringLiteral( "scaleVisibility" ), scaleVisibility );
|
|
renderingElem.setAttribute( QStringLiteral( "scaleMin" ), maximumScale );
|
|
renderingElem.setAttribute( QStringLiteral( "scaleMax" ), minimumScale );
|
|
renderingElem.setAttribute( QStringLiteral( "fontLimitPixelSize" ), fontLimitPixelSize );
|
|
renderingElem.setAttribute( QStringLiteral( "fontMinPixelSize" ), fontMinPixelSize );
|
|
renderingElem.setAttribute( QStringLiteral( "fontMaxPixelSize" ), fontMaxPixelSize );
|
|
renderingElem.setAttribute( QStringLiteral( "upsidedownLabels" ), static_cast< unsigned int >( upsidedownLabels ) );
|
|
|
|
renderingElem.setAttribute( QStringLiteral( "labelPerPart" ), labelPerPart );
|
|
renderingElem.setAttribute( QStringLiteral( "mergeLines" ), mLineSettings.mergeLines() );
|
|
renderingElem.setAttribute( QStringLiteral( "minFeatureSize" ), mThinningSettings.minimumFeatureSize() );
|
|
renderingElem.setAttribute( QStringLiteral( "limitNumLabels" ), mThinningSettings.limitNumberOfLabelsEnabled() );
|
|
renderingElem.setAttribute( QStringLiteral( "maxNumLabels" ), mThinningSettings.maximumNumberLabels() );
|
|
renderingElem.setAttribute( QStringLiteral( "obstacle" ), mObstacleSettings.isObstacle() );
|
|
renderingElem.setAttribute( QStringLiteral( "obstacleFactor" ), mObstacleSettings.factor() );
|
|
renderingElem.setAttribute( QStringLiteral( "obstacleType" ), static_cast< unsigned int >( mObstacleSettings.type() ) );
|
|
renderingElem.setAttribute( QStringLiteral( "zIndex" ), zIndex );
|
|
renderingElem.setAttribute( QStringLiteral( "unplacedVisibility" ), static_cast< int >( mUnplacedVisibility ) );
|
|
|
|
QDomElement ddElem = doc.createElement( QStringLiteral( "dd_properties" ) );
|
|
mDataDefinedProperties.writeXml( ddElem, *sPropertyDefinitions() );
|
|
|
|
QDomElement elem = doc.createElement( QStringLiteral( "settings" ) );
|
|
elem.appendChild( textStyleElem );
|
|
elem.appendChild( textFormatElem );
|
|
elem.appendChild( placementElem );
|
|
elem.appendChild( renderingElem );
|
|
elem.appendChild( ddElem );
|
|
|
|
if ( mCallout )
|
|
{
|
|
elem.setAttribute( QStringLiteral( "calloutType" ), mCallout->type() );
|
|
mCallout->saveProperties( doc, elem, context );
|
|
}
|
|
|
|
return elem;
|
|
}
|
|
|
|
void QgsPalLayerSettings::setCallout( QgsCallout *callout )
|
|
{
|
|
mCallout.reset( callout );
|
|
}
|
|
|
|
QPixmap QgsPalLayerSettings::labelSettingsPreviewPixmap( const QgsPalLayerSettings &settings, QSize size, const QString &previewText, int padding, const QgsScreenProperties &screen )
|
|
{
|
|
const double devicePixelRatio = screen.isValid() ? screen.devicePixelRatio() : 1;
|
|
|
|
// for now, just use format
|
|
QgsTextFormat tempFormat = settings.format();
|
|
QPixmap pixmap( size * devicePixelRatio );
|
|
pixmap.fill( Qt::transparent );
|
|
pixmap.setDevicePixelRatio( devicePixelRatio );
|
|
QPainter painter;
|
|
painter.begin( &pixmap );
|
|
|
|
painter.setRenderHint( QPainter::Antialiasing );
|
|
|
|
const QRectF rect( 0, 0, size.width(), size.height() );
|
|
|
|
// shameless eye candy - use a subtle gradient when drawing background
|
|
painter.setPen( Qt::NoPen );
|
|
QColor background1 = tempFormat.previewBackgroundColor();
|
|
if ( ( background1.lightnessF() < 0.7 ) )
|
|
{
|
|
background1 = background1.darker( 125 );
|
|
}
|
|
else
|
|
{
|
|
background1 = background1.lighter( 125 );
|
|
}
|
|
QColor background2 = tempFormat.previewBackgroundColor();
|
|
QLinearGradient linearGrad( QPointF( 0, 0 ), QPointF( 0, rect.height() ) );
|
|
linearGrad.setColorAt( 0, background1 );
|
|
linearGrad.setColorAt( 1, background2 );
|
|
painter.setBrush( QBrush( linearGrad ) );
|
|
if ( size.width() > 30 )
|
|
{
|
|
painter.drawRoundedRect( rect, 6, 6 );
|
|
}
|
|
else
|
|
{
|
|
// don't use rounded rect for small previews
|
|
painter.drawRect( rect );
|
|
}
|
|
painter.setBrush( Qt::NoBrush );
|
|
painter.setPen( Qt::NoPen );
|
|
padding += 1; // move text away from background border
|
|
|
|
QgsRenderContext context;
|
|
QgsMapToPixel newCoordXForm;
|
|
newCoordXForm.setParameters( 1, 0, 0, 0, 0, 0 );
|
|
context.setMapToPixel( newCoordXForm );
|
|
context.setFlag( Qgis::RenderContextFlag::Antialiasing, true );
|
|
|
|
if ( screen.isValid() )
|
|
{
|
|
screen.updateRenderContextForScreen( context );
|
|
}
|
|
else
|
|
{
|
|
QWidget *activeWindow = QApplication::activeWindow();
|
|
if ( QScreen *screen = activeWindow ? activeWindow->screen() : nullptr )
|
|
{
|
|
context.setScaleFactor( screen->physicalDotsPerInch() / 25.4 );
|
|
context.setDevicePixelRatio( screen->devicePixelRatio() );
|
|
}
|
|
else
|
|
{
|
|
context.setScaleFactor( 96.0 / 25.4 );
|
|
context.setDevicePixelRatio( 1.0 );
|
|
}
|
|
}
|
|
|
|
context.setUseAdvancedEffects( true );
|
|
context.setPainter( &painter );
|
|
|
|
// slightly inset text to account for buffer/background
|
|
const double fontSize = context.convertToPainterUnits( tempFormat.size(), tempFormat.sizeUnit(), tempFormat.sizeMapUnitScale() );
|
|
double xtrans = 0;
|
|
if ( tempFormat.buffer().enabled() )
|
|
xtrans = tempFormat.buffer().sizeUnit() == Qgis::RenderUnit::Percentage
|
|
? fontSize * tempFormat.buffer().size() / 100
|
|
: context.convertToPainterUnits( tempFormat.buffer().size(), tempFormat.buffer().sizeUnit(), tempFormat.buffer().sizeMapUnitScale() );
|
|
if ( tempFormat.background().enabled() && tempFormat.background().sizeType() != QgsTextBackgroundSettings::SizeFixed )
|
|
xtrans = std::max( xtrans, context.convertToPainterUnits( tempFormat.background().size().width(), tempFormat.background().sizeUnit(), tempFormat.background().sizeMapUnitScale() ) );
|
|
|
|
double ytrans = 0.0;
|
|
if ( tempFormat.buffer().enabled() )
|
|
ytrans = std::max( ytrans, tempFormat.buffer().sizeUnit() == Qgis::RenderUnit::Percentage
|
|
? fontSize * tempFormat.buffer().size() / 100
|
|
: context.convertToPainterUnits( tempFormat.buffer().size(), tempFormat.buffer().sizeUnit(), tempFormat.buffer().sizeMapUnitScale() ) );
|
|
if ( tempFormat.background().enabled() )
|
|
ytrans = std::max( ytrans, context.convertToPainterUnits( tempFormat.background().size().height(), tempFormat.background().sizeUnit(), tempFormat.background().sizeMapUnitScale() ) );
|
|
|
|
const QStringList text = QStringList() << ( previewText.isEmpty() ? settings.legendString() : previewText );
|
|
const double textHeight = QgsTextRenderer::textHeight( context, tempFormat, text, Qgis::TextLayoutMode::Rectangle );
|
|
QRectF textRect = rect;
|
|
textRect.setLeft( xtrans + padding );
|
|
textRect.setWidth( rect.width() - xtrans - 2 * padding );
|
|
|
|
if ( textRect.width() > 2000 )
|
|
textRect.setWidth( 2000 - 2 * padding );
|
|
|
|
const double bottom = textRect.height() / 2 + textHeight / 2;
|
|
textRect.setTop( bottom - textHeight );
|
|
textRect.setBottom( bottom );
|
|
|
|
const double iconWidth = QFontMetricsF( QFont() ).horizontalAdvance( 'X' ) * Qgis::UI_SCALE_FACTOR;
|
|
|
|
if ( settings.callout() && settings.callout()->enabled() )
|
|
{
|
|
// draw callout preview
|
|
const double textWidth = QgsTextRenderer::textWidth( context, tempFormat, text );
|
|
QgsCallout *callout = settings.callout();
|
|
callout->startRender( context );
|
|
QgsCallout::QgsCalloutContext calloutContext;
|
|
QRectF labelRect( textRect.left() + ( textRect.width() - textWidth ) / 2.0, textRect.top(), textWidth, textRect.height() );
|
|
callout->render( context, labelRect, 0, QgsGeometry::fromPointXY( QgsPointXY( labelRect.left() - iconWidth * 1.5, labelRect.bottom() + iconWidth ) ), calloutContext );
|
|
callout->stopRender( context );
|
|
}
|
|
|
|
QgsTextRenderer::drawText( textRect, 0, Qgis::TextHorizontalAlignment::Center, text, context, tempFormat );
|
|
|
|
if ( size.width() > 30 )
|
|
{
|
|
// draw a label icon
|
|
|
|
QgsApplication::getThemeIcon( QStringLiteral( "labelingSingle.svg" ) ).paint( &painter, QRect(
|
|
rect.width() - iconWidth * 3, rect.height() - iconWidth * 3,
|
|
iconWidth * 2, iconWidth * 2 ), Qt::AlignRight | Qt::AlignBottom );
|
|
}
|
|
|
|
// draw border on top of text
|
|
painter.setBrush( Qt::NoBrush );
|
|
painter.setPen( QPen( tempFormat.previewBackgroundColor().darker( 150 ), 0 ) );
|
|
if ( size.width() > 30 )
|
|
{
|
|
painter.drawRoundedRect( rect, 6, 6 );
|
|
}
|
|
else
|
|
{
|
|
// don't use rounded rect for small previews
|
|
painter.drawRect( rect );
|
|
}
|
|
|
|
painter.end();
|
|
return pixmap;
|
|
}
|
|
|
|
Qgis::UnplacedLabelVisibility QgsPalLayerSettings::unplacedVisibility() const
|
|
{
|
|
return mUnplacedVisibility;
|
|
}
|
|
|
|
void QgsPalLayerSettings::setUnplacedVisibility( Qgis::UnplacedLabelVisibility visibility )
|
|
{
|
|
mUnplacedVisibility = visibility;
|
|
}
|
|
|
|
bool QgsPalLayerSettings::checkMinimumSizeMM( const QgsRenderContext &ct, const QgsGeometry &geom, double minSize ) const
|
|
{
|
|
return QgsPalLabeling::checkMinimumSizeMM( ct, geom, minSize );
|
|
}
|
|
|
|
void 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 )
|
|
{
|
|
if ( !fm || !f )
|
|
{
|
|
return;
|
|
}
|
|
|
|
QString textCopy( text );
|
|
|
|
//try to keep < 2.12 API - handle no passed render context
|
|
std::unique_ptr< QgsRenderContext > scopedRc;
|
|
if ( !context )
|
|
{
|
|
scopedRc.reset( new QgsRenderContext() );
|
|
if ( f )
|
|
scopedRc->expressionContext().setFeature( *f );
|
|
}
|
|
QgsRenderContext *rc = context ? context : scopedRc.get();
|
|
|
|
QString wrapchr = wrapChar;
|
|
int evalAutoWrapLength = autoWrapLength;
|
|
double multilineH = mFormat.lineHeight();
|
|
Qgis::TextOrientation orientation = mFormat.orientation();
|
|
|
|
bool addDirSymb = mLineSettings.addDirectionSymbol();
|
|
QString leftDirSymb = mLineSettings.leftDirectionSymbol();
|
|
QString rightDirSymb = mLineSettings.rightDirectionSymbol();
|
|
QgsLabelLineSettings::DirectionSymbolPlacement placeDirSymb = mLineSettings.directionSymbolPlacement();
|
|
|
|
if ( f == mCurFeat ) // called internally, use any stored data defined values
|
|
{
|
|
if ( dataDefinedValues.contains( QgsPalLayerSettings::MultiLineWrapChar ) )
|
|
{
|
|
wrapchr = dataDefinedValues.value( QgsPalLayerSettings::MultiLineWrapChar ).toString();
|
|
}
|
|
|
|
if ( dataDefinedValues.contains( QgsPalLayerSettings::AutoWrapLength ) )
|
|
{
|
|
evalAutoWrapLength = dataDefinedValues.value( QgsPalLayerSettings::AutoWrapLength, evalAutoWrapLength ).toInt();
|
|
}
|
|
|
|
if ( dataDefinedValues.contains( QgsPalLayerSettings::MultiLineHeight ) )
|
|
{
|
|
multilineH = dataDefinedValues.value( QgsPalLayerSettings::MultiLineHeight ).toDouble();
|
|
}
|
|
|
|
if ( dataDefinedValues.contains( QgsPalLayerSettings::TextOrientation ) )
|
|
{
|
|
orientation = QgsTextRendererUtils::decodeTextOrientation( dataDefinedValues.value( QgsPalLayerSettings::TextOrientation ).toString() );
|
|
}
|
|
|
|
if ( dataDefinedValues.contains( QgsPalLayerSettings::DirSymbDraw ) )
|
|
{
|
|
addDirSymb = dataDefinedValues.value( QgsPalLayerSettings::DirSymbDraw ).toBool();
|
|
}
|
|
|
|
if ( addDirSymb )
|
|
{
|
|
|
|
if ( dataDefinedValues.contains( QgsPalLayerSettings::DirSymbLeft ) )
|
|
{
|
|
leftDirSymb = dataDefinedValues.value( QgsPalLayerSettings::DirSymbLeft ).toString();
|
|
}
|
|
if ( dataDefinedValues.contains( QgsPalLayerSettings::DirSymbRight ) )
|
|
{
|
|
rightDirSymb = dataDefinedValues.value( QgsPalLayerSettings::DirSymbRight ).toString();
|
|
}
|
|
|
|
if ( dataDefinedValues.contains( QgsPalLayerSettings::DirSymbPlacement ) )
|
|
{
|
|
placeDirSymb = static_cast< QgsLabelLineSettings::DirectionSymbolPlacement >( dataDefinedValues.value( QgsPalLayerSettings::DirSymbPlacement ).toInt() );
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
else // called externally with passed-in feature, evaluate data defined
|
|
{
|
|
if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::MultiLineWrapChar ) )
|
|
{
|
|
rc->expressionContext().setOriginalValueVariable( wrapChar );
|
|
wrapchr = mDataDefinedProperties.value( QgsPalLayerSettings::MultiLineWrapChar, rc->expressionContext(), wrapchr ).toString();
|
|
}
|
|
|
|
if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::AutoWrapLength ) )
|
|
{
|
|
rc->expressionContext().setOriginalValueVariable( evalAutoWrapLength );
|
|
evalAutoWrapLength = mDataDefinedProperties.value( QgsPalLayerSettings::AutoWrapLength, rc->expressionContext(), evalAutoWrapLength ).toInt();
|
|
}
|
|
|
|
if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::MultiLineHeight ) )
|
|
{
|
|
rc->expressionContext().setOriginalValueVariable( multilineH );
|
|
multilineH = mDataDefinedProperties.valueAsDouble( QgsPalLayerSettings::MultiLineHeight, rc->expressionContext(), multilineH );
|
|
}
|
|
|
|
if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::TextOrientation ) )
|
|
{
|
|
QString encoded = QgsTextRendererUtils::encodeTextOrientation( orientation );
|
|
rc->expressionContext().setOriginalValueVariable( encoded );
|
|
orientation = QgsTextRendererUtils::decodeTextOrientation( mDataDefinedProperties.valueAsString( QgsPalLayerSettings::TextOrientation, rc->expressionContext(), encoded ) );
|
|
}
|
|
|
|
if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::DirSymbDraw ) )
|
|
{
|
|
rc->expressionContext().setOriginalValueVariable( addDirSymb );
|
|
addDirSymb = mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::DirSymbDraw, rc->expressionContext(), addDirSymb );
|
|
}
|
|
|
|
if ( addDirSymb ) // don't do extra evaluations if not adding a direction symbol
|
|
{
|
|
if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::DirSymbLeft ) )
|
|
{
|
|
rc->expressionContext().setOriginalValueVariable( leftDirSymb );
|
|
leftDirSymb = mDataDefinedProperties.value( QgsPalLayerSettings::DirSymbLeft, rc->expressionContext(), leftDirSymb ).toString();
|
|
}
|
|
|
|
if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::DirSymbRight ) )
|
|
{
|
|
rc->expressionContext().setOriginalValueVariable( rightDirSymb );
|
|
rightDirSymb = mDataDefinedProperties.value( QgsPalLayerSettings::DirSymbRight, rc->expressionContext(), rightDirSymb ).toString();
|
|
}
|
|
|
|
if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::DirSymbPlacement ) )
|
|
{
|
|
rc->expressionContext().setOriginalValueVariable( static_cast< int >( placeDirSymb ) );
|
|
placeDirSymb = static_cast< QgsLabelLineSettings::DirectionSymbolPlacement >( mDataDefinedProperties.valueAsInt( QgsPalLayerSettings::DirSymbPlacement, rc->expressionContext(), static_cast< int >( placeDirSymb ) ) );
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( wrapchr.isEmpty() )
|
|
{
|
|
wrapchr = QStringLiteral( "\n" ); // default to new line delimiter
|
|
}
|
|
|
|
//consider the space needed for the direction symbol
|
|
if ( addDirSymb && placement == Qgis::LabelPlacement::Line
|
|
&& ( !leftDirSymb.isEmpty() || !rightDirSymb.isEmpty() ) )
|
|
{
|
|
QString dirSym = leftDirSymb;
|
|
|
|
if ( fm->horizontalAdvance( rightDirSymb ) > fm->horizontalAdvance( dirSym ) )
|
|
dirSym = rightDirSymb;
|
|
|
|
switch ( placeDirSymb )
|
|
{
|
|
case QgsLabelLineSettings::DirectionSymbolPlacement::SymbolLeftRight:
|
|
textCopy.append( dirSym );
|
|
break;
|
|
|
|
case QgsLabelLineSettings::DirectionSymbolPlacement::SymbolAbove:
|
|
case QgsLabelLineSettings::DirectionSymbolPlacement::SymbolBelow:
|
|
textCopy.prepend( dirSym + QStringLiteral( "\n" ) );
|
|
break;
|
|
}
|
|
}
|
|
|
|
double w = 0.0, h = 0.0, rw = 0.0, rh = 0.0;
|
|
double labelHeight = fm->ascent() + fm->descent(); // ignore +1 for baseline
|
|
|
|
if ( document )
|
|
{
|
|
document->splitLines( wrapchr, evalAutoWrapLength, useMaxLineLengthForAutoWrap );
|
|
|
|
*documentMetrics = QgsTextDocumentMetrics::calculateMetrics( *document, mFormat, *rc );
|
|
const QSizeF size = documentMetrics->documentSize( Qgis::TextLayoutMode::Labeling, orientation );
|
|
w = size.width();
|
|
h = size.height();
|
|
}
|
|
else
|
|
{
|
|
const QStringList multiLineSplit = QgsPalLabeling::splitToLines( textCopy, wrapchr, evalAutoWrapLength, useMaxLineLengthForAutoWrap );
|
|
const int lines = multiLineSplit.size();
|
|
|
|
const double lineHeightPainterUnits = rc->convertToPainterUnits( mFormat.lineHeight(), mFormat.lineHeightUnit() );
|
|
|
|
switch ( orientation )
|
|
{
|
|
case Qgis::TextOrientation::Horizontal:
|
|
{
|
|
h += fm->height() + static_cast< double >( ( lines - 1 ) * ( mFormat.lineHeightUnit() == Qgis::RenderUnit::Percentage ? ( labelHeight * multilineH ) : lineHeightPainterUnits ) );
|
|
|
|
for ( const QString &line : std::as_const( multiLineSplit ) )
|
|
{
|
|
w = std::max( w, fm->horizontalAdvance( line ) );
|
|
}
|
|
break;
|
|
}
|
|
|
|
case Qgis::TextOrientation::Vertical:
|
|
{
|
|
double letterSpacing = mFormat.scaledFont( *context ).letterSpacing();
|
|
double labelWidth = fm->maxWidth();
|
|
w = labelWidth + ( lines - 1 ) * ( mFormat.lineHeightUnit() == Qgis::RenderUnit::Percentage ? ( labelWidth * multilineH ) : lineHeightPainterUnits );
|
|
|
|
int maxLineLength = 0;
|
|
for ( const QString &line : std::as_const( multiLineSplit ) )
|
|
{
|
|
maxLineLength = std::max( maxLineLength, static_cast<int>( line.length() ) );
|
|
}
|
|
h = fm->ascent() * maxLineLength + ( maxLineLength - 1 ) * letterSpacing;
|
|
break;
|
|
}
|
|
|
|
case Qgis::TextOrientation::RotationBased:
|
|
{
|
|
double widthHorizontal = 0.0;
|
|
for ( const QString &line : std::as_const( multiLineSplit ) )
|
|
{
|
|
widthHorizontal = std::max( w, fm->horizontalAdvance( line ) );
|
|
}
|
|
|
|
double widthVertical = 0.0;
|
|
double letterSpacing = mFormat.scaledFont( *context ).letterSpacing();
|
|
double labelWidth = fm->maxWidth();
|
|
widthVertical = labelWidth + ( lines - 1 ) * ( mFormat.lineHeightUnit() == Qgis::RenderUnit::Percentage ? ( labelWidth * multilineH ) : lineHeightPainterUnits );
|
|
|
|
double heightHorizontal = 0.0;
|
|
heightHorizontal += fm->height() + static_cast< double >( ( lines - 1 ) * ( mFormat.lineHeightUnit() == Qgis::RenderUnit::Percentage ? ( labelHeight * multilineH ) : lineHeightPainterUnits ) );
|
|
|
|
double heightVertical = 0.0;
|
|
int maxLineLength = 0;
|
|
for ( const QString &line : std::as_const( multiLineSplit ) )
|
|
{
|
|
maxLineLength = std::max( maxLineLength, static_cast<int>( line.length() ) );
|
|
}
|
|
heightVertical = fm->ascent() * maxLineLength + ( maxLineLength - 1 ) * letterSpacing;
|
|
|
|
w = widthHorizontal;
|
|
rw = heightVertical;
|
|
h = heightHorizontal;
|
|
rh = widthVertical;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
#if 0 // XXX strk
|
|
QgsPointXY ptSize = xform->toMapCoordinatesF( w, h );
|
|
labelX = std::fabs( ptSize.x() - ptZero.x() );
|
|
labelY = std::fabs( ptSize.y() - ptZero.y() );
|
|
#else
|
|
double uPP = xform->mapUnitsPerPixel();
|
|
labelX = w * uPP;
|
|
labelY = h * uPP;
|
|
if ( rotatedLabelX && rotatedLabelY )
|
|
{
|
|
*rotatedLabelX = rw * uPP;
|
|
*rotatedLabelY = rh * uPP;
|
|
}
|
|
#endif
|
|
|
|
if ( outerBounds && documentMetrics )
|
|
{
|
|
const QRectF outerBoundsPixels = documentMetrics->outerBounds( Qgis::TextLayoutMode::Labeling, orientation );
|
|
|
|
*outerBounds = QRectF( outerBoundsPixels.left() * uPP,
|
|
outerBoundsPixels.top() * uPP,
|
|
outerBoundsPixels.width() * uPP,
|
|
outerBoundsPixels.height() * uPP );
|
|
}
|
|
}
|
|
|
|
void QgsPalLayerSettings::registerFeature( const QgsFeature &f, QgsRenderContext &context )
|
|
{
|
|
registerFeatureWithDetails( f, context, QgsGeometry(), nullptr );
|
|
}
|
|
|
|
std::unique_ptr<QgsLabelFeature> QgsPalLayerSettings::registerFeatureWithDetails( const QgsFeature &f, QgsRenderContext &context, QgsGeometry obstacleGeometry, const QgsSymbol *symbol )
|
|
{
|
|
QVariant exprVal; // value() is repeatedly nulled on data defined evaluation and replaced when successful
|
|
mCurFeat = &f;
|
|
|
|
// data defined is obstacle? calculate this first, to avoid wasting time working with obstacles we don't require
|
|
bool isObstacle = mObstacleSettings.isObstacle();
|
|
if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::IsObstacle ) )
|
|
isObstacle = mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::IsObstacle, context.expressionContext(), isObstacle ); // default to layer default
|
|
|
|
if ( !drawLabels )
|
|
{
|
|
if ( isObstacle )
|
|
{
|
|
return registerObstacleFeature( f, context, obstacleGeometry );
|
|
}
|
|
else
|
|
{
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
QgsFeature feature = f;
|
|
if ( geometryGeneratorEnabled )
|
|
{
|
|
const QgsGeometry geometry = mGeometryGeneratorExpression.evaluate( &context.expressionContext() ).value<QgsGeometry>();
|
|
if ( mGeometryGeneratorExpression.hasEvalError() )
|
|
QgsMessageLog::logMessage( mGeometryGeneratorExpression.evalErrorString(), QObject::tr( "Labeling" ) );
|
|
|
|
if ( obstacleGeometry.isNull() )
|
|
{
|
|
// if an explicit obstacle geometry hasn't been set, we must always use the original feature geometry
|
|
// as the obstacle -- because we want to use the geometry which was used to render the symbology
|
|
// for the feature as the obstacle for other layers' labels, NOT the generated geometry which is used
|
|
// only to place labels for this layer.
|
|
obstacleGeometry = f.geometry();
|
|
}
|
|
|
|
feature.setGeometry( geometry );
|
|
}
|
|
|
|
// store data defined-derived values for later adding to label feature for use during rendering
|
|
dataDefinedValues.clear();
|
|
|
|
// data defined show label? defaults to show label if not set
|
|
if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Show ) )
|
|
{
|
|
context.expressionContext().setOriginalValueVariable( true );
|
|
if ( !mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::Show, context.expressionContext(), true ) )
|
|
{
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
// data defined scale visibility?
|
|
bool useScaleVisibility = scaleVisibility;
|
|
if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::ScaleVisibility ) )
|
|
useScaleVisibility = mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::ScaleVisibility, context.expressionContext(), scaleVisibility );
|
|
|
|
if ( useScaleVisibility )
|
|
{
|
|
// data defined min scale?
|
|
double maxScale = maximumScale;
|
|
if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::MaximumScale ) )
|
|
{
|
|
context.expressionContext().setOriginalValueVariable( maximumScale );
|
|
maxScale = mDataDefinedProperties.valueAsDouble( QgsPalLayerSettings::MaximumScale, context.expressionContext(), maxScale );
|
|
}
|
|
|
|
// scales closer than 1:1
|
|
if ( maxScale < 0 )
|
|
{
|
|
maxScale = 1 / std::fabs( maxScale );
|
|
}
|
|
|
|
if ( !qgsDoubleNear( maxScale, 0.0 ) && context.rendererScale() < maxScale )
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
// data defined min scale?
|
|
double minScale = minimumScale;
|
|
if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::MinimumScale ) )
|
|
{
|
|
context.expressionContext().setOriginalValueVariable( minimumScale );
|
|
minScale = mDataDefinedProperties.valueAsDouble( QgsPalLayerSettings::MinimumScale, context.expressionContext(), minScale );
|
|
}
|
|
|
|
// scales closer than 1:1
|
|
if ( minScale < 0 )
|
|
{
|
|
minScale = 1 / std::fabs( minScale );
|
|
}
|
|
|
|
if ( !qgsDoubleNear( minScale, 0.0 ) && context.rendererScale() > minScale )
|
|
{
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
QFont labelFont = mFormat.font();
|
|
// labelFont will be added to label feature for use during label painting
|
|
|
|
// data defined font units?
|
|
Qgis::RenderUnit fontunits = mFormat.sizeUnit();
|
|
exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::FontSizeUnit, context.expressionContext() );
|
|
if ( !QgsVariantUtils::isNull( exprVal ) )
|
|
{
|
|
QString units = exprVal.toString();
|
|
if ( !units.isEmpty() )
|
|
{
|
|
bool ok;
|
|
Qgis::RenderUnit res = QgsUnitTypes::decodeRenderUnit( units, &ok );
|
|
if ( ok )
|
|
fontunits = res;
|
|
}
|
|
}
|
|
|
|
//data defined label size?
|
|
double fontSize = mFormat.size();
|
|
if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Size ) )
|
|
{
|
|
context.expressionContext().setOriginalValueVariable( fontSize );
|
|
fontSize = mDataDefinedProperties.valueAsDouble( QgsPalLayerSettings::Size, context.expressionContext(), fontSize );
|
|
}
|
|
if ( fontSize <= 0.0 )
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
int fontPixelSize = QgsTextRenderer::sizeToPixel( fontSize, context, fontunits, mFormat.sizeMapUnitScale() );
|
|
// don't try to show font sizes less than 1 pixel (Qt complains)
|
|
if ( fontPixelSize < 1 )
|
|
{
|
|
return nullptr;
|
|
}
|
|
labelFont.setPixelSize( fontPixelSize );
|
|
|
|
// NOTE: labelFont now always has pixelSize set, so pointSize or pointSizeF might return -1
|
|
|
|
// defined 'minimum/maximum pixel font size'?
|
|
if ( fontunits == Qgis::RenderUnit::MapUnits )
|
|
{
|
|
if ( mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::FontLimitPixel, context.expressionContext(), fontLimitPixelSize ) )
|
|
{
|
|
int fontMinPixel = mDataDefinedProperties.valueAsInt( QgsPalLayerSettings::FontMinPixel, context.expressionContext(), fontMinPixelSize );
|
|
int fontMaxPixel = mDataDefinedProperties.valueAsInt( QgsPalLayerSettings::FontMaxPixel, context.expressionContext(), fontMaxPixelSize );
|
|
|
|
if ( fontMinPixel > labelFont.pixelSize() || labelFont.pixelSize() > fontMaxPixel )
|
|
{
|
|
return nullptr;
|
|
}
|
|
}
|
|
}
|
|
|
|
// NOTE: the following parsing functions calculate and store any data defined values for later use in QgsPalLabeling::drawLabeling
|
|
// this is done to provide clarity, and because such parsing is not directly related to PAL feature registration calculations
|
|
|
|
// calculate rest of font attributes and store any data defined values
|
|
// this is done here for later use in making label backgrounds part of collision management (when implemented)
|
|
labelFont.setCapitalization( QFont::MixedCase ); // reset this - we don't use QFont's handling as it breaks with curved labels
|
|
|
|
parseTextStyle( labelFont, fontunits, context );
|
|
if ( mDataDefinedProperties.hasActiveProperties() )
|
|
{
|
|
parseTextFormatting( context );
|
|
parseTextBuffer( context );
|
|
parseTextMask( context );
|
|
parseShapeBackground( context );
|
|
parseDropShadow( context );
|
|
}
|
|
|
|
QString labelText;
|
|
|
|
// Check to see if we are a expression string.
|
|
if ( isExpression )
|
|
{
|
|
QgsExpression *exp = getLabelExpression();
|
|
if ( exp->hasParserError() )
|
|
{
|
|
QgsDebugMsgLevel( QStringLiteral( "Expression parser error:%1" ).arg( exp->parserErrorString() ), 4 );
|
|
return nullptr;
|
|
}
|
|
|
|
QVariant result = exp->evaluate( &context.expressionContext() ); // expression prepared in QgsPalLabeling::prepareLayer()
|
|
if ( exp->hasEvalError() )
|
|
{
|
|
QgsDebugMsgLevel( QStringLiteral( "Expression parser eval error:%1" ).arg( exp->evalErrorString() ), 4 );
|
|
return nullptr;
|
|
}
|
|
labelText = QgsVariantUtils::isNull( result ) ? QString() : result.toString();
|
|
}
|
|
else
|
|
{
|
|
const QVariant &v = feature.attribute( fieldIndex );
|
|
labelText = QgsVariantUtils::isNull( v ) ? QString() : v.toString();
|
|
}
|
|
|
|
// apply text replacements
|
|
if ( useSubstitutions )
|
|
{
|
|
labelText = substitutions.process( labelText );
|
|
}
|
|
|
|
// apply capitalization
|
|
Qgis::Capitalization capitalization = mFormat.capitalization();
|
|
// maintain API - capitalization may have been set in textFont
|
|
if ( capitalization == Qgis::Capitalization::MixedCase && mFormat.font().capitalization() != QFont::MixedCase )
|
|
{
|
|
capitalization = static_cast< Qgis::Capitalization >( mFormat.font().capitalization() );
|
|
}
|
|
// data defined font capitalization?
|
|
if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::FontCase ) )
|
|
{
|
|
exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::FontCase, context.expressionContext() );
|
|
if ( !QgsVariantUtils::isNull( exprVal ) )
|
|
{
|
|
QString fcase = exprVal.toString().trimmed();
|
|
QgsDebugMsgLevel( QStringLiteral( "exprVal FontCase:%1" ).arg( fcase ), 4 );
|
|
|
|
if ( !fcase.isEmpty() )
|
|
{
|
|
if ( fcase.compare( QLatin1String( "NoChange" ), Qt::CaseInsensitive ) == 0 )
|
|
{
|
|
capitalization = Qgis::Capitalization::MixedCase;
|
|
}
|
|
else if ( fcase.compare( QLatin1String( "Upper" ), Qt::CaseInsensitive ) == 0 )
|
|
{
|
|
capitalization = Qgis::Capitalization::AllUppercase;
|
|
}
|
|
else if ( fcase.compare( QLatin1String( "Lower" ), Qt::CaseInsensitive ) == 0 )
|
|
{
|
|
capitalization = Qgis::Capitalization::AllLowercase;
|
|
}
|
|
else if ( fcase.compare( QLatin1String( "Capitalize" ), Qt::CaseInsensitive ) == 0 )
|
|
{
|
|
capitalization = Qgis::Capitalization::ForceFirstLetterToCapital;
|
|
}
|
|
else if ( fcase.compare( QLatin1String( "Title" ), Qt::CaseInsensitive ) == 0 )
|
|
{
|
|
capitalization = Qgis::Capitalization::TitleCase;
|
|
}
|
|
#if defined(HAS_KDE_QT5_SMALL_CAPS_FIX) || QT_VERSION >= QT_VERSION_CHECK(6, 3, 0)
|
|
else if ( fcase.compare( QLatin1String( "SmallCaps" ), Qt::CaseInsensitive ) == 0 )
|
|
{
|
|
capitalization = Qgis::Capitalization::SmallCaps;
|
|
}
|
|
else if ( fcase.compare( QLatin1String( "AllSmallCaps" ), Qt::CaseInsensitive ) == 0 )
|
|
{
|
|
capitalization = Qgis::Capitalization::AllSmallCaps;
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
labelText = QgsStringUtils::capitalize( labelText, capitalization );
|
|
|
|
// format number if label text is coercible to a number
|
|
bool evalFormatNumbers = formatNumbers;
|
|
if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::NumFormat ) )
|
|
{
|
|
evalFormatNumbers = mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::NumFormat, context.expressionContext(), evalFormatNumbers );
|
|
}
|
|
if ( evalFormatNumbers )
|
|
{
|
|
// data defined decimal places?
|
|
int decimalPlaces = mDataDefinedProperties.valueAsInt( QgsPalLayerSettings::NumDecimals, context.expressionContext(), decimals );
|
|
if ( decimalPlaces <= 0 ) // needs to be positive
|
|
decimalPlaces = decimals;
|
|
|
|
// data defined plus sign?
|
|
bool signPlus = mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::NumPlusSign, context.expressionContext(), plusSign );
|
|
|
|
QVariant textV( labelText );
|
|
bool ok;
|
|
double d = textV.toDouble( &ok );
|
|
if ( ok )
|
|
{
|
|
QString numberFormat;
|
|
if ( d > 0 && signPlus )
|
|
{
|
|
numberFormat.append( '+' );
|
|
}
|
|
numberFormat.append( "%1" );
|
|
labelText = numberFormat.arg( QLocale().toString( d, 'f', decimalPlaces ) );
|
|
}
|
|
}
|
|
|
|
// NOTE: this should come AFTER any option that affects font metrics
|
|
std::unique_ptr<QFontMetricsF> labelFontMetrics( new QFontMetricsF( labelFont ) );
|
|
double labelWidth;
|
|
double labelHeight;
|
|
double rotatedLabelX;
|
|
double rotatedLabelY;
|
|
|
|
QgsTextDocument doc;
|
|
QgsTextDocumentMetrics documentMetrics;
|
|
QRectF outerBounds;
|
|
if ( format().allowHtmlFormatting() && !labelText.isEmpty() )
|
|
{
|
|
doc = QgsTextDocument::fromHtml( QStringList() << labelText );
|
|
// also applies the line split to doc and calculates document metrics!
|
|
calculateLabelSize( labelFontMetrics.get(), labelText, labelWidth, labelHeight, mCurFeat, &context, &rotatedLabelX, &rotatedLabelY, &doc, &documentMetrics, &outerBounds );
|
|
}
|
|
else
|
|
{
|
|
calculateLabelSize( labelFontMetrics.get(), labelText, labelWidth, labelHeight, mCurFeat, &context, &rotatedLabelX, &rotatedLabelY, nullptr, nullptr, &outerBounds );
|
|
}
|
|
|
|
// maximum angle between curved label characters (hardcoded defaults used in QGIS <2.0)
|
|
//
|
|
double maxcharanglein = 20.0; // range 20.0-60.0
|
|
double maxcharangleout = -20.0; // range 20.0-95.0
|
|
|
|
switch ( placement )
|
|
{
|
|
case Qgis::LabelPlacement::PerimeterCurved:
|
|
case Qgis::LabelPlacement::Curved:
|
|
{
|
|
maxcharanglein = maxCurvedCharAngleIn;
|
|
maxcharangleout = maxCurvedCharAngleOut;
|
|
|
|
//data defined maximum angle between curved label characters?
|
|
if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::CurvedCharAngleInOut ) )
|
|
{
|
|
exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::CurvedCharAngleInOut, context.expressionContext() );
|
|
bool ok = false;
|
|
const QPointF maxcharanglePt = QgsSymbolLayerUtils::toPoint( exprVal, &ok );
|
|
if ( ok )
|
|
{
|
|
maxcharanglein = std::clamp( static_cast< double >( maxcharanglePt.x() ), 20.0, 60.0 );
|
|
maxcharangleout = std::clamp( static_cast< double >( maxcharanglePt.y() ), 20.0, 95.0 );
|
|
}
|
|
}
|
|
// make sure maxcharangleout is always negative
|
|
maxcharangleout = -( std::fabs( maxcharangleout ) );
|
|
break;
|
|
}
|
|
|
|
case Qgis::LabelPlacement::AroundPoint:
|
|
case Qgis::LabelPlacement::OverPoint:
|
|
case Qgis::LabelPlacement::Line:
|
|
case Qgis::LabelPlacement::Horizontal:
|
|
case Qgis::LabelPlacement::Free:
|
|
case Qgis::LabelPlacement::OrderedPositionsAroundPoint:
|
|
case Qgis::LabelPlacement::OutsidePolygons:
|
|
break;
|
|
}
|
|
|
|
// data defined centroid whole or clipped?
|
|
bool wholeCentroid = centroidWhole;
|
|
if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::CentroidWhole ) )
|
|
{
|
|
exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::CentroidWhole, context.expressionContext() );
|
|
if ( !QgsVariantUtils::isNull( exprVal ) )
|
|
{
|
|
QString str = exprVal.toString().trimmed();
|
|
QgsDebugMsgLevel( QStringLiteral( "exprVal CentroidWhole:%1" ).arg( str ), 4 );
|
|
|
|
if ( !str.isEmpty() )
|
|
{
|
|
if ( str.compare( QLatin1String( "Visible" ), Qt::CaseInsensitive ) == 0 )
|
|
{
|
|
wholeCentroid = false;
|
|
}
|
|
else if ( str.compare( QLatin1String( "Whole" ), Qt::CaseInsensitive ) == 0 )
|
|
{
|
|
wholeCentroid = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
QgsGeometry geom = feature.geometry();
|
|
if ( geom.isNull() )
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
// simplify?
|
|
const QgsVectorSimplifyMethod &simplifyMethod = context.vectorSimplifyMethod();
|
|
std::unique_ptr<QgsGeometry> scopedClonedGeom;
|
|
if ( simplifyMethod.simplifyHints() != QgsVectorSimplifyMethod::NoSimplification && simplifyMethod.forceLocalOptimization() )
|
|
{
|
|
unsigned int simplifyHints = simplifyMethod.simplifyHints() | QgsMapToPixelSimplifier::SimplifyEnvelope;
|
|
QgsMapToPixelSimplifier::SimplifyAlgorithm simplifyAlgorithm = static_cast< QgsMapToPixelSimplifier::SimplifyAlgorithm >( simplifyMethod.simplifyAlgorithm() );
|
|
QgsMapToPixelSimplifier simplifier( simplifyHints, simplifyMethod.tolerance(), simplifyAlgorithm );
|
|
geom = simplifier.simplify( geom );
|
|
}
|
|
|
|
if ( !context.featureClipGeometry().isEmpty() )
|
|
{
|
|
const Qgis::GeometryType expectedType = geom.type();
|
|
geom = geom.intersection( context.featureClipGeometry() );
|
|
geom.convertGeometryCollectionToSubclass( expectedType );
|
|
}
|
|
|
|
// whether we're going to create a centroid for polygon
|
|
bool centroidPoly = ( ( placement == Qgis::LabelPlacement::AroundPoint
|
|
|| placement == Qgis::LabelPlacement::OverPoint )
|
|
&& geom.type() == Qgis::GeometryType::Polygon );
|
|
|
|
// CLIP the geometry if it is bigger than the extent
|
|
// don't clip if centroid is requested for whole feature
|
|
bool doClip = false;
|
|
if ( !centroidPoly || !wholeCentroid )
|
|
{
|
|
doClip = true;
|
|
}
|
|
|
|
|
|
Qgis::LabelPolygonPlacementFlags polygonPlacement = mPolygonPlacementFlags;
|
|
if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::PolygonLabelOutside ) )
|
|
{
|
|
const QVariant dataDefinedOutside = mDataDefinedProperties.value( QgsPalLayerSettings::PolygonLabelOutside, context.expressionContext() );
|
|
if ( !QgsVariantUtils::isNull( dataDefinedOutside ) )
|
|
{
|
|
if ( dataDefinedOutside.type() == QVariant::String )
|
|
{
|
|
const QString value = dataDefinedOutside.toString().trimmed();
|
|
if ( value.compare( QLatin1String( "force" ), Qt::CaseInsensitive ) == 0 )
|
|
{
|
|
// forced outside placement -- remove inside flag, add outside flag
|
|
polygonPlacement &= ~static_cast< int >( Qgis::LabelPolygonPlacementFlag::AllowPlacementInsideOfPolygon );
|
|
polygonPlacement |= Qgis::LabelPolygonPlacementFlag::AllowPlacementOutsideOfPolygon;
|
|
}
|
|
else if ( value.compare( QLatin1String( "yes" ), Qt::CaseInsensitive ) == 0 )
|
|
{
|
|
// permit outside placement
|
|
polygonPlacement |= Qgis::LabelPolygonPlacementFlag::AllowPlacementOutsideOfPolygon;
|
|
}
|
|
else if ( value.compare( QLatin1String( "no" ), Qt::CaseInsensitive ) == 0 )
|
|
{
|
|
// block outside placement
|
|
polygonPlacement &= ~static_cast< int >( Qgis::LabelPolygonPlacementFlag::AllowPlacementOutsideOfPolygon );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( dataDefinedOutside.toBool() )
|
|
{
|
|
// permit outside placement
|
|
polygonPlacement |= Qgis::LabelPolygonPlacementFlag::AllowPlacementOutsideOfPolygon;
|
|
}
|
|
else
|
|
{
|
|
// block outside placement
|
|
polygonPlacement &= ~static_cast< int >( Qgis::LabelPolygonPlacementFlag::AllowPlacementOutsideOfPolygon );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
QgsLabelLineSettings lineSettings = mLineSettings;
|
|
lineSettings.updateDataDefinedProperties( mDataDefinedProperties, context.expressionContext() );
|
|
|
|
if ( geom.type() == Qgis::GeometryType::Line || placement == Qgis::LabelPlacement::Line || placement == Qgis::LabelPlacement::PerimeterCurved )
|
|
{
|
|
switch ( lineSettings.anchorClipping() )
|
|
{
|
|
case QgsLabelLineSettings::AnchorClipping::UseVisiblePartsOfLine:
|
|
break;
|
|
|
|
case QgsLabelLineSettings::AnchorClipping::UseEntireLine:
|
|
doClip = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// if using fitInPolygonOnly option, generate the permissible zone (must happen before geometry is modified - e.g.,
|
|
// as a result of using perimeter based labeling and the geometry is converted to a boundary)
|
|
// note that we also force this if we are permitting labels to be placed outside of polygons too!
|
|
QgsGeometry permissibleZone;
|
|
if ( geom.type() == Qgis::GeometryType::Polygon && ( fitInPolygonOnly || polygonPlacement & Qgis::LabelPolygonPlacementFlag::AllowPlacementOutsideOfPolygon ) )
|
|
{
|
|
permissibleZone = geom;
|
|
if ( QgsPalLabeling::geometryRequiresPreparation( permissibleZone, context, ct, doClip ? extentGeom : QgsGeometry(), lineSettings.mergeLines() ) )
|
|
{
|
|
permissibleZone = QgsPalLabeling::prepareGeometry( permissibleZone, context, ct, doClip ? extentGeom : QgsGeometry(), lineSettings.mergeLines() );
|
|
}
|
|
}
|
|
|
|
// if using perimeter based labeling for polygons, get the polygon's
|
|
// linear boundary and use that for the label geometry
|
|
if ( ( geom.type() == Qgis::GeometryType::Polygon )
|
|
&& ( placement == Qgis::LabelPlacement::Line || placement == Qgis::LabelPlacement::PerimeterCurved ) )
|
|
{
|
|
geom = QgsGeometry( geom.constGet()->boundary() );
|
|
}
|
|
|
|
geos::unique_ptr geos_geom_clone;
|
|
if ( QgsPalLabeling::geometryRequiresPreparation( geom, context, ct, doClip ? extentGeom : QgsGeometry(), lineSettings.mergeLines() ) )
|
|
{
|
|
geom = QgsPalLabeling::prepareGeometry( geom, context, ct, doClip ? extentGeom : QgsGeometry(), lineSettings.mergeLines() );
|
|
|
|
if ( geom.isEmpty() )
|
|
return nullptr;
|
|
}
|
|
geos_geom_clone = QgsGeos::asGeos( geom );
|
|
|
|
if ( isObstacle || ( geom.type() == Qgis::GeometryType::Point && offsetType == Qgis::LabelOffsetType::FromSymbolBounds ) )
|
|
{
|
|
if ( !obstacleGeometry.isNull() && QgsPalLabeling::geometryRequiresPreparation( obstacleGeometry, context, ct, doClip ? extentGeom : QgsGeometry(), lineSettings.mergeLines() ) )
|
|
{
|
|
obstacleGeometry = QgsGeometry( QgsPalLabeling::prepareGeometry( obstacleGeometry, context, ct, doClip ? extentGeom : QgsGeometry(), lineSettings.mergeLines() ) );
|
|
}
|
|
}
|
|
|
|
QgsLabelThinningSettings featureThinningSettings = mThinningSettings;
|
|
featureThinningSettings.updateDataDefinedProperties( mDataDefinedProperties, context.expressionContext() );
|
|
|
|
double minimumSize = 0.0;
|
|
if ( featureThinningSettings.minimumFeatureSize() > 0 )
|
|
{
|
|
// for minimum feature size on merged lines, we need to delay the filtering after the merging occurred in PAL
|
|
if ( geom.type() == Qgis::GeometryType::Line && mLineSettings.mergeLines() )
|
|
{
|
|
minimumSize = context.convertToMapUnits( featureThinningSettings.minimumFeatureSize(), Qgis::RenderUnit::Millimeters );
|
|
}
|
|
else
|
|
{
|
|
if ( !checkMinimumSizeMM( context, geom, featureThinningSettings.minimumFeatureSize() ) )
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
if ( !geos_geom_clone )
|
|
return nullptr; // invalid geometry
|
|
|
|
// likelihood exists label will be registered with PAL and may be drawn
|
|
// check if max number of features to label (already registered with PAL) has been reached
|
|
// Debug output at end of QgsPalLabeling::drawLabeling(), when deleting temp geometries
|
|
if ( featureThinningSettings.limitNumberOfLabelsEnabled() )
|
|
{
|
|
if ( !featureThinningSettings.maximumNumberLabels() )
|
|
{
|
|
return nullptr;
|
|
}
|
|
if ( mFeatsRegPal >= featureThinningSettings.maximumNumberLabels() )
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
int divNum = static_cast< int >( ( static_cast< double >( mFeaturesToLabel ) / featureThinningSettings.maximumNumberLabels() ) + 0.5 ); // NOLINT
|
|
if ( divNum && ( mFeatsRegPal == static_cast< int >( mFeatsSendingToPal / divNum ) ) )
|
|
{
|
|
mFeatsSendingToPal += 1;
|
|
if ( divNum && mFeatsSendingToPal % divNum )
|
|
{
|
|
return nullptr;
|
|
}
|
|
}
|
|
}
|
|
|
|
//data defined position / alignment / rotation?
|
|
bool layerDefinedRotation = false;
|
|
bool dataDefinedRotation = false;
|
|
double xPos = 0.0, yPos = 0.0;
|
|
double angleInRadians = 0.0;
|
|
double quadOffsetX = 0.0, quadOffsetY = 0.0;
|
|
double offsetX = 0.0, offsetY = 0.0;
|
|
QgsPointXY anchorPosition;
|
|
|
|
if ( placement == Qgis::LabelPlacement::OverPoint )
|
|
{
|
|
anchorPosition = geom.centroid().asPoint();
|
|
}
|
|
//x/y shift in case of alignment
|
|
double xdiff = 0.0;
|
|
double ydiff = 0.0;
|
|
|
|
//data defined quadrant offset?
|
|
bool ddFixedQuad = false;
|
|
Qgis::LabelQuadrantPosition quadOff = quadOffset;
|
|
if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::OffsetQuad ) )
|
|
{
|
|
context.expressionContext().setOriginalValueVariable( static_cast< int >( quadOff ) );
|
|
exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::OffsetQuad, context.expressionContext() );
|
|
if ( !QgsVariantUtils::isNull( exprVal ) )
|
|
{
|
|
bool ok;
|
|
int quadInt = exprVal.toInt( &ok );
|
|
if ( ok && 0 <= quadInt && quadInt <= 8 )
|
|
{
|
|
quadOff = static_cast< Qgis::LabelQuadrantPosition >( quadInt );
|
|
ddFixedQuad = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// adjust quadrant offset of labels
|
|
switch ( quadOff )
|
|
{
|
|
case Qgis::LabelQuadrantPosition::AboveLeft:
|
|
quadOffsetX = -1.0;
|
|
quadOffsetY = 1.0;
|
|
break;
|
|
case Qgis::LabelQuadrantPosition::Above:
|
|
quadOffsetX = 0.0;
|
|
quadOffsetY = 1.0;
|
|
break;
|
|
case Qgis::LabelQuadrantPosition::AboveRight:
|
|
quadOffsetX = 1.0;
|
|
quadOffsetY = 1.0;
|
|
break;
|
|
case Qgis::LabelQuadrantPosition::Left:
|
|
quadOffsetX = -1.0;
|
|
quadOffsetY = 0.0;
|
|
break;
|
|
case Qgis::LabelQuadrantPosition::Right:
|
|
quadOffsetX = 1.0;
|
|
quadOffsetY = 0.0;
|
|
break;
|
|
case Qgis::LabelQuadrantPosition::BelowLeft:
|
|
quadOffsetX = -1.0;
|
|
quadOffsetY = -1.0;
|
|
break;
|
|
case Qgis::LabelQuadrantPosition::Below:
|
|
quadOffsetX = 0.0;
|
|
quadOffsetY = -1.0;
|
|
break;
|
|
case Qgis::LabelQuadrantPosition::BelowRight:
|
|
quadOffsetX = 1.0;
|
|
quadOffsetY = -1.0;
|
|
break;
|
|
case Qgis::LabelQuadrantPosition::Over:
|
|
break;
|
|
}
|
|
|
|
//data defined label offset?
|
|
double xOff = xOffset;
|
|
double yOff = yOffset;
|
|
if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::OffsetXY ) )
|
|
{
|
|
context.expressionContext().setOriginalValueVariable( QgsSymbolLayerUtils::encodePoint( QPointF( xOffset, yOffset ) ) );
|
|
exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::OffsetXY, context.expressionContext() );
|
|
bool ok = false;
|
|
const QPointF ddOffPt = QgsSymbolLayerUtils::toPoint( exprVal, &ok );
|
|
if ( ok )
|
|
{
|
|
xOff = ddOffPt.x();
|
|
yOff = ddOffPt.y();
|
|
}
|
|
}
|
|
|
|
// data defined label offset units?
|
|
Qgis::RenderUnit offUnit = offsetUnits;
|
|
if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::OffsetUnits ) )
|
|
{
|
|
exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::OffsetUnits, context.expressionContext() );
|
|
if ( !QgsVariantUtils::isNull( exprVal ) )
|
|
{
|
|
QString units = exprVal.toString().trimmed();
|
|
if ( !units.isEmpty() )
|
|
{
|
|
bool ok = false;
|
|
Qgis::RenderUnit decodedUnits = QgsUnitTypes::decodeRenderUnit( units, &ok );
|
|
if ( ok )
|
|
{
|
|
offUnit = decodedUnits;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// adjust offset of labels to match chosen unit and map scale
|
|
// offsets match those of symbology: -x = left, -y = up
|
|
offsetX = context.convertToMapUnits( xOff, offUnit, labelOffsetMapUnitScale );
|
|
// must be negative to match symbology offset direction
|
|
offsetY = context.convertToMapUnits( -yOff, offUnit, labelOffsetMapUnitScale );
|
|
|
|
// layer defined rotation?
|
|
if ( !qgsDoubleNear( angleOffset, 0.0 ) )
|
|
{
|
|
layerDefinedRotation = true;
|
|
angleInRadians = ( 360 - angleOffset ) * M_PI / 180; // convert to radians counterclockwise
|
|
}
|
|
|
|
const QgsMapToPixel &m2p = context.mapToPixel();
|
|
//data defined rotation?
|
|
if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::LabelRotation ) )
|
|
{
|
|
context.expressionContext().setOriginalValueVariable( angleOffset );
|
|
exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::LabelRotation, context.expressionContext() );
|
|
if ( !QgsVariantUtils::isNull( exprVal ) )
|
|
{
|
|
bool ok;
|
|
const double rotation = exprVal.toDouble( &ok );
|
|
if ( ok )
|
|
{
|
|
dataDefinedRotation = true;
|
|
|
|
double rotationDegrees = rotation * QgsUnitTypes::fromUnitToUnitFactor( mRotationUnit,
|
|
Qgis::AngleUnit::Degrees );
|
|
|
|
// TODO: add setting to disable having data defined rotation follow
|
|
// map rotation ?
|
|
rotationDegrees += m2p.mapRotation();
|
|
angleInRadians = ( 360 - rotationDegrees ) * M_PI / 180.0;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool hasDataDefinedPosition = false;
|
|
{
|
|
bool ddPosition = false;
|
|
|
|
if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::PositionX )
|
|
&& mDataDefinedProperties.isActive( QgsPalLayerSettings::PositionY ) )
|
|
{
|
|
const QVariant xPosProperty = mDataDefinedProperties.value( QgsPalLayerSettings::PositionX, context.expressionContext() );
|
|
const QVariant yPosProperty = mDataDefinedProperties.value( QgsPalLayerSettings::PositionY, context.expressionContext() );
|
|
if ( !QgsVariantUtils::isNull( xPosProperty )
|
|
&& !QgsVariantUtils::isNull( yPosProperty ) )
|
|
{
|
|
ddPosition = true;
|
|
|
|
bool ddXPos = false, ddYPos = false;
|
|
xPos = xPosProperty.toDouble( &ddXPos );
|
|
yPos = yPosProperty.toDouble( &ddYPos );
|
|
if ( ddXPos && ddYPos )
|
|
hasDataDefinedPosition = true;
|
|
}
|
|
}
|
|
else if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::PositionPoint ) )
|
|
{
|
|
const QVariant pointPosProperty = mDataDefinedProperties.value( QgsPalLayerSettings::PositionPoint, context.expressionContext() );
|
|
if ( !QgsVariantUtils::isNull( pointPosProperty ) )
|
|
{
|
|
ddPosition = true;
|
|
|
|
QgsPoint point;
|
|
if ( pointPosProperty.userType() == QMetaType::type( "QgsReferencedGeometry" ) )
|
|
{
|
|
QgsReferencedGeometry referencedGeometryPoint = pointPosProperty.value<QgsReferencedGeometry>();
|
|
point = QgsPoint( referencedGeometryPoint.asPoint() );
|
|
|
|
if ( !referencedGeometryPoint.isNull()
|
|
&& ct.sourceCrs() != referencedGeometryPoint.crs() )
|
|
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 );
|
|
}
|
|
else if ( pointPosProperty.userType() == QMetaType::type( "QgsGeometry" ) )
|
|
{
|
|
point = QgsPoint( pointPosProperty.value<QgsGeometry>().asPoint() );
|
|
}
|
|
|
|
if ( !point.isEmpty() )
|
|
{
|
|
hasDataDefinedPosition = true;
|
|
|
|
xPos = point.x();
|
|
yPos = point.y();
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( ddPosition )
|
|
{
|
|
//data defined position. But field values could be NULL -> positions will be generated by PAL
|
|
if ( hasDataDefinedPosition )
|
|
{
|
|
// layer rotation set, but don't rotate pinned labels unless data defined
|
|
if ( layerDefinedRotation && !dataDefinedRotation )
|
|
{
|
|
angleInRadians = 0.0;
|
|
}
|
|
|
|
//horizontal alignment
|
|
if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Hali ) )
|
|
{
|
|
exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Hali, context.expressionContext() );
|
|
if ( !QgsVariantUtils::isNull( exprVal ) )
|
|
{
|
|
QString haliString = exprVal.toString();
|
|
if ( haliString.compare( QLatin1String( "Center" ), Qt::CaseInsensitive ) == 0 )
|
|
{
|
|
xdiff -= labelWidth / 2.0;
|
|
}
|
|
else if ( haliString.compare( QLatin1String( "Right" ), Qt::CaseInsensitive ) == 0 )
|
|
{
|
|
xdiff -= labelWidth;
|
|
}
|
|
}
|
|
}
|
|
|
|
//vertical alignment
|
|
if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Vali ) )
|
|
{
|
|
exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Vali, context.expressionContext() );
|
|
if ( !QgsVariantUtils::isNull( exprVal ) )
|
|
{
|
|
QString valiString = exprVal.toString();
|
|
if ( valiString.compare( QLatin1String( "Bottom" ), Qt::CaseInsensitive ) != 0 )
|
|
{
|
|
if ( valiString.compare( QLatin1String( "Top" ), Qt::CaseInsensitive ) == 0 )
|
|
{
|
|
ydiff -= labelHeight;;
|
|
}
|
|
else
|
|
{
|
|
double descentRatio = labelFontMetrics->descent() / labelFontMetrics->height();
|
|
if ( valiString.compare( QLatin1String( "Base" ), Qt::CaseInsensitive ) == 0 )
|
|
{
|
|
ydiff -= labelHeight * descentRatio;
|
|
}
|
|
else //'Cap' or 'Half'
|
|
{
|
|
double capHeightRatio = ( labelFontMetrics->boundingRect( 'H' ).height() + 1 + labelFontMetrics->descent() ) / labelFontMetrics->height();
|
|
ydiff -= labelHeight * capHeightRatio;
|
|
if ( valiString.compare( QLatin1String( "Half" ), Qt::CaseInsensitive ) == 0 )
|
|
{
|
|
ydiff += labelHeight * ( capHeightRatio - descentRatio ) / 2.0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( dataDefinedRotation )
|
|
{
|
|
//adjust xdiff and ydiff because the hali/vali point needs to be the rotation center
|
|
double xd = xdiff * std::cos( angleInRadians ) - ydiff * std::sin( angleInRadians );
|
|
double yd = xdiff * std::sin( angleInRadians ) + ydiff * std::cos( angleInRadians );
|
|
xdiff = xd;
|
|
ydiff = yd;
|
|
}
|
|
|
|
//project xPos and yPos from layer to map CRS, handle rotation
|
|
QgsGeometry ddPoint( new QgsPoint( xPos, yPos ) );
|
|
if ( QgsPalLabeling::geometryRequiresPreparation( ddPoint, context, ct ) )
|
|
{
|
|
ddPoint = QgsPalLabeling::prepareGeometry( ddPoint, context, ct );
|
|
if ( const QgsPoint *point = qgsgeometry_cast< const QgsPoint * >( ddPoint.constGet() ) )
|
|
{
|
|
xPos = point->x();
|
|
yPos = point->y();
|
|
anchorPosition = QgsPointXY( xPos, yPos );
|
|
}
|
|
else
|
|
{
|
|
QgsMessageLog::logMessage( QObject::tr( "Invalid data defined label position (%1, %2)" ).arg( xPos ).arg( yPos ), QObject::tr( "Labeling" ) );
|
|
hasDataDefinedPosition = false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
anchorPosition = QgsPointXY( xPos, yPos );
|
|
}
|
|
|
|
xPos += xdiff;
|
|
yPos += ydiff;
|
|
}
|
|
else
|
|
{
|
|
anchorPosition = QgsPointXY( xPos, yPos );
|
|
}
|
|
}
|
|
}
|
|
|
|
// data defined always show?
|
|
bool alwaysShow = false;
|
|
if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::AlwaysShow ) )
|
|
{
|
|
alwaysShow = mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::AlwaysShow, context.expressionContext(), false );
|
|
}
|
|
|
|
// set repeat distance
|
|
// data defined repeat distance?
|
|
double repeatDist = repeatDistance;
|
|
if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::RepeatDistance ) )
|
|
{
|
|
context.expressionContext().setOriginalValueVariable( repeatDist );
|
|
repeatDist = mDataDefinedProperties.valueAsDouble( QgsPalLayerSettings::RepeatDistance, context.expressionContext(), repeatDist );
|
|
}
|
|
|
|
// data defined label-repeat distance units?
|
|
Qgis::RenderUnit repeatUnits = repeatDistanceUnit;
|
|
if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::RepeatDistanceUnit ) )
|
|
{
|
|
exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::RepeatDistanceUnit, context.expressionContext() );
|
|
if ( !QgsVariantUtils::isNull( exprVal ) )
|
|
{
|
|
QString units = exprVal.toString().trimmed();
|
|
if ( !units.isEmpty() )
|
|
{
|
|
bool ok = false;
|
|
Qgis::RenderUnit decodedUnits = QgsUnitTypes::decodeRenderUnit( units, &ok );
|
|
if ( ok )
|
|
{
|
|
repeatUnits = decodedUnits;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( !qgsDoubleNear( repeatDist, 0.0 ) )
|
|
{
|
|
if ( repeatUnits != Qgis::RenderUnit::MapUnits )
|
|
{
|
|
repeatDist = context.convertToMapUnits( repeatDist, repeatUnits, repeatDistanceMapUnitScale );
|
|
}
|
|
}
|
|
|
|
// overrun distance
|
|
double overrunDistanceEval = lineSettings.overrunDistance();
|
|
if ( !qgsDoubleNear( overrunDistanceEval, 0.0 ) )
|
|
{
|
|
overrunDistanceEval = context.convertToMapUnits( overrunDistanceEval, lineSettings.overrunDistanceUnit(), lineSettings.overrunDistanceMapUnitScale() );
|
|
}
|
|
|
|
// we smooth out the overrun label extensions by 1 mm, to avoid little jaggies right at the start or end of the lines
|
|
// causing the overrun extension to extend out in an undesirable direction. This is hard coded, we don't want to overload
|
|
// users with options they likely don't need to see...
|
|
const double overrunSmoothDist = context.convertToMapUnits( 1, Qgis::RenderUnit::Millimeters );
|
|
|
|
bool labelAll = labelPerPart && !hasDataDefinedPosition;
|
|
if ( !hasDataDefinedPosition )
|
|
{
|
|
if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::LabelAllParts ) )
|
|
{
|
|
context.expressionContext().setOriginalValueVariable( labelPerPart );
|
|
labelAll = mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::LabelAllParts, context.expressionContext(), labelPerPart );
|
|
}
|
|
}
|
|
|
|
// feature to the layer
|
|
std::unique_ptr< QgsTextLabelFeature > labelFeature = std::make_unique< QgsTextLabelFeature>( feature.id(), std::move( geos_geom_clone ), QSizeF( labelWidth, labelHeight ) );
|
|
labelFeature->setAnchorPosition( anchorPosition );
|
|
labelFeature->setFeature( feature );
|
|
labelFeature->setSymbol( symbol );
|
|
labelFeature->setDocument( doc, documentMetrics );
|
|
if ( !qgsDoubleNear( rotatedLabelX, 0.0 ) && !qgsDoubleNear( rotatedLabelY, 0.0 ) )
|
|
labelFeature->setRotatedSize( QSizeF( rotatedLabelX, rotatedLabelY ) );
|
|
mFeatsRegPal++;
|
|
|
|
labelFeature->setHasFixedPosition( hasDataDefinedPosition );
|
|
labelFeature->setFixedPosition( QgsPointXY( xPos, yPos ) );
|
|
// use layer-level defined rotation, but not if position fixed
|
|
labelFeature->setHasFixedAngle( dataDefinedRotation || ( !hasDataDefinedPosition && !qgsDoubleNear( angleInRadians, 0.0 ) ) );
|
|
labelFeature->setFixedAngle( angleInRadians );
|
|
labelFeature->setQuadOffset( QPointF( quadOffsetX, quadOffsetY ) );
|
|
labelFeature->setPositionOffset( QgsPointXY( offsetX, offsetY ) );
|
|
labelFeature->setOffsetType( offsetType );
|
|
labelFeature->setAlwaysShow( alwaysShow );
|
|
labelFeature->setRepeatDistance( repeatDist );
|
|
labelFeature->setLabelText( labelText );
|
|
labelFeature->setPermissibleZone( permissibleZone );
|
|
labelFeature->setOverrunDistance( overrunDistanceEval );
|
|
labelFeature->setOverrunSmoothDistance( overrunSmoothDist );
|
|
labelFeature->setLineAnchorPercent( lineSettings.lineAnchorPercent() );
|
|
labelFeature->setLineAnchorType( lineSettings.anchorType() );
|
|
labelFeature->setLineAnchorTextPoint( lineSettings.anchorTextPoint() );
|
|
labelFeature->setLabelAllParts( labelAll );
|
|
labelFeature->setOriginalFeatureCrs( context.coordinateTransform().sourceCrs() );
|
|
labelFeature->setMinimumSize( minimumSize );
|
|
if ( geom.type() == Qgis::GeometryType::Point && !obstacleGeometry.isNull() )
|
|
{
|
|
//register symbol size
|
|
labelFeature->setSymbolSize( QSizeF( obstacleGeometry.boundingBox().width(),
|
|
obstacleGeometry.boundingBox().height() ) );
|
|
}
|
|
|
|
if ( outerBounds.left() != 0 || outerBounds.top() != 0 || !qgsDoubleNear( outerBounds.width(), labelWidth ) || !qgsDoubleNear( outerBounds.height(), labelHeight ) )
|
|
{
|
|
labelFeature->setOuterBounds( outerBounds );
|
|
}
|
|
|
|
//set label's visual margin so that top visual margin is the leading, and bottom margin is the font's descent
|
|
//this makes labels align to the font's baseline or highest character
|
|
double topMargin = std::max( 0.25 * labelFontMetrics->ascent(), 0.0 );
|
|
double bottomMargin = 1.0 + labelFontMetrics->descent();
|
|
QgsMargins vm( 0.0, topMargin, 0.0, bottomMargin );
|
|
vm *= xform->mapUnitsPerPixel();
|
|
labelFeature->setVisualMargin( vm );
|
|
|
|
// store the label's calculated font for later use during painting
|
|
QgsDebugMsgLevel( QStringLiteral( "PAL font stored definedFont: %1, Style: %2" ).arg( labelFont.toString(), labelFont.styleName() ), 4 );
|
|
labelFeature->setDefinedFont( labelFont );
|
|
|
|
labelFeature->setMaximumCharacterAngleInside( std::clamp( maxcharanglein, 20.0, 60.0 ) * M_PI / 180 );
|
|
labelFeature->setMaximumCharacterAngleOutside( std::clamp( maxcharangleout, -95.0, -20.0 ) * M_PI / 180 );
|
|
switch ( placement )
|
|
{
|
|
case Qgis::LabelPlacement::AroundPoint:
|
|
case Qgis::LabelPlacement::OverPoint:
|
|
case Qgis::LabelPlacement::Line:
|
|
case Qgis::LabelPlacement::Horizontal:
|
|
case Qgis::LabelPlacement::Free:
|
|
case Qgis::LabelPlacement::OrderedPositionsAroundPoint:
|
|
case Qgis::LabelPlacement::OutsidePolygons:
|
|
// these placements don't require text metrics
|
|
break;
|
|
|
|
case Qgis::LabelPlacement::Curved:
|
|
case Qgis::LabelPlacement::PerimeterCurved:
|
|
labelFeature->setTextMetrics( QgsTextLabelFeature::calculateTextMetrics( xform, context, labelFont, *labelFontMetrics, labelFont.letterSpacing(), labelFont.wordSpacing(), labelText, format().allowHtmlFormatting() ? &doc : nullptr, format().allowHtmlFormatting() ? &documentMetrics : nullptr ) );
|
|
break;
|
|
}
|
|
|
|
// for labelFeature the LabelInfo is passed to feat when it is registered
|
|
|
|
// TODO: allow layer-wide feature dist in PAL...?
|
|
|
|
// data defined label-feature distance?
|
|
double distance = dist;
|
|
if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::LabelDistance ) )
|
|
{
|
|
context.expressionContext().setOriginalValueVariable( distance );
|
|
distance = mDataDefinedProperties.valueAsDouble( QgsPalLayerSettings::LabelDistance, context.expressionContext(), distance );
|
|
}
|
|
|
|
// data defined label-feature distance units?
|
|
Qgis::RenderUnit distUnit = distUnits;
|
|
if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::DistanceUnits ) )
|
|
{
|
|
exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::DistanceUnits, context.expressionContext() );
|
|
if ( !QgsVariantUtils::isNull( exprVal ) )
|
|
{
|
|
QString units = exprVal.toString().trimmed();
|
|
QgsDebugMsgLevel( QStringLiteral( "exprVal DistanceUnits:%1" ).arg( units ), 4 );
|
|
if ( !units.isEmpty() )
|
|
{
|
|
bool ok = false;
|
|
Qgis::RenderUnit decodedUnits = QgsUnitTypes::decodeRenderUnit( units, &ok );
|
|
if ( ok )
|
|
{
|
|
distUnit = decodedUnits;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
distance = context.convertToPainterUnits( distance, distUnit, distMapUnitScale );
|
|
|
|
// when using certain placement modes, we force a tiny minimum distance. This ensures that
|
|
// candidates are created just offset from a border and avoids candidates being incorrectly flagged as colliding with neighbours
|
|
switch ( placement )
|
|
{
|
|
case Qgis::LabelPlacement::Line:
|
|
case Qgis::LabelPlacement::Curved:
|
|
case Qgis::LabelPlacement::PerimeterCurved:
|
|
distance = ( distance < 0 ? -1 : 1 ) * std::max( std::fabs( distance ), 1.0 );
|
|
break;
|
|
|
|
case Qgis::LabelPlacement::OrderedPositionsAroundPoint:
|
|
break;
|
|
|
|
case Qgis::LabelPlacement::AroundPoint:
|
|
case Qgis::LabelPlacement::OverPoint:
|
|
case Qgis::LabelPlacement::Horizontal:
|
|
case Qgis::LabelPlacement::Free:
|
|
if ( polygonPlacement & Qgis::LabelPolygonPlacementFlag::AllowPlacementOutsideOfPolygon )
|
|
{
|
|
distance = std::max( distance, 2.0 );
|
|
}
|
|
break;
|
|
|
|
case Qgis::LabelPlacement::OutsidePolygons:
|
|
distance = std::max( distance, 2.0 );
|
|
break;
|
|
}
|
|
|
|
if ( !qgsDoubleNear( distance, 0.0 ) )
|
|
{
|
|
double d = ptOne.distance( ptZero ) * distance;
|
|
labelFeature->setDistLabel( d );
|
|
}
|
|
|
|
if ( ddFixedQuad )
|
|
{
|
|
labelFeature->setHasFixedQuadrant( true );
|
|
}
|
|
|
|
labelFeature->setArrangementFlags( lineSettings.placementFlags() );
|
|
|
|
labelFeature->setPolygonPlacementFlags( polygonPlacement );
|
|
|
|
// data defined z-index?
|
|
double z = zIndex;
|
|
if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::ZIndex ) )
|
|
{
|
|
context.expressionContext().setOriginalValueVariable( z );
|
|
z = mDataDefinedProperties.valueAsDouble( QgsPalLayerSettings::ZIndex, context.expressionContext(), z );
|
|
}
|
|
labelFeature->setZIndex( z );
|
|
|
|
// data defined priority?
|
|
if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Priority ) )
|
|
{
|
|
context.expressionContext().setOriginalValueVariable( priority );
|
|
exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Priority, context.expressionContext() );
|
|
if ( !QgsVariantUtils::isNull( exprVal ) )
|
|
{
|
|
bool ok;
|
|
double priorityD = exprVal.toDouble( &ok );
|
|
if ( ok )
|
|
{
|
|
priorityD = std::clamp( priorityD, 0.0, 10.0 );
|
|
priorityD = 1 - priorityD / 10.0; // convert 0..10 --> 1..0
|
|
labelFeature->setPriority( priorityD );
|
|
}
|
|
}
|
|
}
|
|
|
|
// data defined allow degraded placement
|
|
{
|
|
double allowDegradedPlacement = mPlacementSettings.allowDegradedPlacement();
|
|
if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::AllowDegradedPlacement ) )
|
|
{
|
|
context.expressionContext().setOriginalValueVariable( allowDegradedPlacement );
|
|
allowDegradedPlacement = mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::AllowDegradedPlacement, context.expressionContext(), allowDegradedPlacement );
|
|
}
|
|
labelFeature->setAllowDegradedPlacement( allowDegradedPlacement );
|
|
}
|
|
|
|
// data defined overlap handling
|
|
{
|
|
Qgis::LabelOverlapHandling overlapHandling = mPlacementSettings.overlapHandling();
|
|
if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::OverlapHandling ) )
|
|
{
|
|
const QString handlingString = mDataDefinedProperties.valueAsString( QgsPalLayerSettings::OverlapHandling, context.expressionContext() );
|
|
const QString cleanedString = handlingString.trimmed();
|
|
if ( cleanedString.compare( QLatin1String( "prevent" ), Qt::CaseInsensitive ) == 0 )
|
|
overlapHandling = Qgis::LabelOverlapHandling::PreventOverlap;
|
|
else if ( cleanedString.compare( QLatin1String( "allowifneeded" ), Qt::CaseInsensitive ) == 0 )
|
|
overlapHandling = Qgis::LabelOverlapHandling::AllowOverlapIfRequired;
|
|
else if ( cleanedString.compare( QLatin1String( "alwaysallow" ), Qt::CaseInsensitive ) == 0 )
|
|
overlapHandling = Qgis::LabelOverlapHandling::AllowOverlapAtNoCost;
|
|
}
|
|
labelFeature->setOverlapHandling( overlapHandling );
|
|
}
|
|
|
|
QgsLabelObstacleSettings os = mObstacleSettings;
|
|
os.setIsObstacle( isObstacle );
|
|
os.updateDataDefinedProperties( mDataDefinedProperties, context.expressionContext() );
|
|
os.setObstacleGeometry( obstacleGeometry );
|
|
labelFeature->setObstacleSettings( os );
|
|
|
|
QVector< Qgis::LabelPredefinedPointPosition > positionOrder = predefinedPositionOrder;
|
|
if ( positionOrder.isEmpty() )
|
|
positionOrder = *DEFAULT_PLACEMENT_ORDER();
|
|
|
|
if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::PredefinedPositionOrder ) )
|
|
{
|
|
context.expressionContext().setOriginalValueVariable( QgsLabelingUtils::encodePredefinedPositionOrder( predefinedPositionOrder ) );
|
|
QString dataDefinedOrder = mDataDefinedProperties.valueAsString( QgsPalLayerSettings::PredefinedPositionOrder, context.expressionContext() );
|
|
if ( !dataDefinedOrder.isEmpty() )
|
|
{
|
|
positionOrder = QgsLabelingUtils::decodePredefinedPositionOrder( dataDefinedOrder );
|
|
}
|
|
}
|
|
labelFeature->setPredefinedPositionOrder( positionOrder );
|
|
|
|
// add parameters for data defined labeling to label feature
|
|
labelFeature->setDataDefinedValues( dataDefinedValues );
|
|
|
|
return labelFeature;
|
|
}
|
|
|
|
std::unique_ptr<QgsLabelFeature> QgsPalLayerSettings::registerObstacleFeature( const QgsFeature &f, QgsRenderContext &context, const QgsGeometry &obstacleGeometry )
|
|
{
|
|
mCurFeat = &f;
|
|
|
|
QgsGeometry geom;
|
|
if ( !obstacleGeometry.isNull() )
|
|
{
|
|
geom = obstacleGeometry;
|
|
}
|
|
else
|
|
{
|
|
geom = f.geometry();
|
|
}
|
|
|
|
if ( geom.isNull() )
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
// don't even try to register linestrings with only one vertex as an obstacle
|
|
if ( const QgsLineString *ls = qgsgeometry_cast< const QgsLineString * >( geom.constGet() ) )
|
|
{
|
|
if ( ls->numPoints() < 2 )
|
|
return nullptr;
|
|
}
|
|
|
|
// simplify?
|
|
const QgsVectorSimplifyMethod &simplifyMethod = context.vectorSimplifyMethod();
|
|
std::unique_ptr<QgsGeometry> scopedClonedGeom;
|
|
if ( simplifyMethod.simplifyHints() != QgsVectorSimplifyMethod::NoSimplification && simplifyMethod.forceLocalOptimization() )
|
|
{
|
|
int simplifyHints = simplifyMethod.simplifyHints() | QgsMapToPixelSimplifier::SimplifyEnvelope;
|
|
QgsMapToPixelSimplifier::SimplifyAlgorithm simplifyAlgorithm = static_cast< QgsMapToPixelSimplifier::SimplifyAlgorithm >( simplifyMethod.simplifyAlgorithm() );
|
|
QgsMapToPixelSimplifier simplifier( simplifyHints, simplifyMethod.tolerance(), simplifyAlgorithm );
|
|
geom = simplifier.simplify( geom );
|
|
}
|
|
|
|
geos::unique_ptr geos_geom_clone;
|
|
std::unique_ptr<QgsGeometry> scopedPreparedGeom;
|
|
|
|
if ( QgsPalLabeling::geometryRequiresPreparation( geom, context, ct, extentGeom, mLineSettings.mergeLines() ) )
|
|
{
|
|
geom = QgsPalLabeling::prepareGeometry( geom, context, ct, extentGeom, mLineSettings.mergeLines() );
|
|
}
|
|
geos_geom_clone = QgsGeos::asGeos( geom );
|
|
|
|
if ( !geos_geom_clone )
|
|
return nullptr; // invalid geometry
|
|
|
|
// feature to the layer
|
|
std::unique_ptr< QgsLabelFeature > obstacleFeature = std::make_unique< QgsLabelFeature >( f.id(), std::move( geos_geom_clone ), QSizeF( 0, 0 ) );
|
|
obstacleFeature->setFeature( f );
|
|
|
|
QgsLabelObstacleSettings os = mObstacleSettings;
|
|
os.setIsObstacle( true );
|
|
os.updateDataDefinedProperties( mDataDefinedProperties, context.expressionContext() );
|
|
obstacleFeature->setObstacleSettings( os );
|
|
|
|
mFeatsRegPal++;
|
|
return obstacleFeature;
|
|
}
|
|
|
|
bool QgsPalLayerSettings::dataDefinedValEval( DataDefinedValueType valType,
|
|
QgsPalLayerSettings::Property p,
|
|
QVariant &exprVal, QgsExpressionContext &context, const QVariant &originalValue )
|
|
{
|
|
if ( !mDataDefinedProperties.isActive( p ) )
|
|
return false;
|
|
|
|
context.setOriginalValueVariable( originalValue );
|
|
exprVal = mDataDefinedProperties.value( p, context );
|
|
if ( !QgsVariantUtils::isNull( exprVal ) )
|
|
{
|
|
switch ( valType )
|
|
{
|
|
case DDBool:
|
|
{
|
|
bool bol = exprVal.toBool();
|
|
dataDefinedValues.insert( p, QVariant( bol ) );
|
|
return true;
|
|
}
|
|
case DDInt:
|
|
{
|
|
bool ok;
|
|
int size = exprVal.toInt( &ok );
|
|
|
|
if ( ok )
|
|
{
|
|
dataDefinedValues.insert( p, QVariant( size ) );
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
case DDIntPos:
|
|
{
|
|
bool ok;
|
|
int size = exprVal.toInt( &ok );
|
|
|
|
if ( ok && size > 0 )
|
|
{
|
|
dataDefinedValues.insert( p, QVariant( size ) );
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
case DDDouble:
|
|
{
|
|
bool ok;
|
|
double size = exprVal.toDouble( &ok );
|
|
|
|
if ( ok )
|
|
{
|
|
dataDefinedValues.insert( p, QVariant( size ) );
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
case DDDoublePos:
|
|
{
|
|
bool ok;
|
|
double size = exprVal.toDouble( &ok );
|
|
|
|
if ( ok && size > 0.0 )
|
|
{
|
|
dataDefinedValues.insert( p, QVariant( size ) );
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
case DDRotation180:
|
|
{
|
|
bool ok;
|
|
double rot = exprVal.toDouble( &ok );
|
|
if ( ok )
|
|
{
|
|
if ( rot < -180.0 && rot >= -360 )
|
|
{
|
|
rot += 360;
|
|
}
|
|
if ( rot > 180.0 && rot <= 360 )
|
|
{
|
|
rot -= 360;
|
|
}
|
|
if ( rot >= -180 && rot <= 180 )
|
|
{
|
|
dataDefinedValues.insert( p, QVariant( rot ) );
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
case DDOpacity:
|
|
{
|
|
bool ok;
|
|
int size = exprVal.toInt( &ok );
|
|
if ( ok && size >= 0 && size <= 100 )
|
|
{
|
|
dataDefinedValues.insert( p, QVariant( size ) );
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
case DDString:
|
|
{
|
|
QString str = exprVal.toString(); // don't trim whitespace
|
|
|
|
dataDefinedValues.insert( p, QVariant( str ) ); // let it stay empty if it is
|
|
return true;
|
|
}
|
|
case DDUnits:
|
|
{
|
|
QString unitstr = exprVal.toString().trimmed();
|
|
|
|
if ( !unitstr.isEmpty() )
|
|
{
|
|
dataDefinedValues.insert( p, QVariant( static_cast< int >( QgsUnitTypes::decodeRenderUnit( unitstr ) ) ) );
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
case DDColor:
|
|
{
|
|
QString colorstr = exprVal.toString().trimmed();
|
|
QColor color = QgsSymbolLayerUtils::decodeColor( colorstr );
|
|
|
|
if ( color.isValid() )
|
|
{
|
|
dataDefinedValues.insert( p, QVariant( color ) );
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
case DDJoinStyle:
|
|
{
|
|
QString joinstr = exprVal.toString().trimmed();
|
|
|
|
if ( !joinstr.isEmpty() )
|
|
{
|
|
dataDefinedValues.insert( p, QVariant( static_cast< int >( QgsSymbolLayerUtils::decodePenJoinStyle( joinstr ) ) ) );
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
case DDBlendMode:
|
|
{
|
|
QString blendstr = exprVal.toString().trimmed();
|
|
|
|
if ( !blendstr.isEmpty() )
|
|
{
|
|
dataDefinedValues.insert( p, QVariant( static_cast< int >( QgsSymbolLayerUtils::decodeBlendMode( blendstr ) ) ) );
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
case DDPointF:
|
|
{
|
|
bool ok = false;
|
|
const QPointF res = QgsSymbolLayerUtils::toPoint( exprVal, &ok );
|
|
if ( ok )
|
|
{
|
|
dataDefinedValues.insert( p, res );
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
case DDSizeF:
|
|
{
|
|
bool ok = false;
|
|
const QSizeF res = QgsSymbolLayerUtils::toSize( exprVal, &ok );
|
|
if ( ok )
|
|
{
|
|
dataDefinedValues.insert( p, res );
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void QgsPalLayerSettings::parseTextStyle( QFont &labelFont,
|
|
Qgis::RenderUnit fontunits,
|
|
QgsRenderContext &context )
|
|
{
|
|
// NOTE: labelFont already has pixelSize set, so pointSize or pointSizeF might return -1
|
|
|
|
QVariant exprVal; // value() is repeatedly nulled on data defined evaluation and replaced when successful
|
|
|
|
// Two ways to generate new data defined font:
|
|
// 1) Family + [bold] + [italic] (named style is ignored and font is built off of base family)
|
|
// 2) Family + named style (bold or italic is ignored)
|
|
|
|
// data defined font family?
|
|
QString ddFontFamily;
|
|
if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Family ) )
|
|
{
|
|
context.expressionContext().setOriginalValueVariable( labelFont.family() );
|
|
exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Family, context.expressionContext() );
|
|
if ( !QgsVariantUtils::isNull( exprVal ) )
|
|
{
|
|
QString family = exprVal.toString().trimmed();
|
|
QgsDebugMsgLevel( QStringLiteral( "exprVal Font family:%1" ).arg( family ), 4 );
|
|
|
|
family = QgsApplication::fontManager()->processFontFamilyName( family );
|
|
if ( labelFont.family() != family )
|
|
{
|
|
// testing for ddFontFamily in QFontDatabase.families() may be slow to do for every feature
|
|
// (i.e. don't use QgsFontUtils::fontFamilyMatchOnSystem( family ) here)
|
|
if ( QgsFontUtils::fontFamilyOnSystem( family ) )
|
|
{
|
|
ddFontFamily = family;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// data defined named font style?
|
|
QString ddFontStyle;
|
|
if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::FontStyle ) )
|
|
{
|
|
exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::FontStyle, context.expressionContext() );
|
|
if ( !QgsVariantUtils::isNull( exprVal ) )
|
|
{
|
|
QString fontstyle = exprVal.toString().trimmed();
|
|
QgsDebugMsgLevel( QStringLiteral( "exprVal Font style:%1" ).arg( fontstyle ), 4 );
|
|
ddFontStyle = fontstyle;
|
|
}
|
|
}
|
|
|
|
// data defined bold font style?
|
|
bool ddBold = false;
|
|
if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Bold ) )
|
|
{
|
|
context.expressionContext().setOriginalValueVariable( labelFont.bold() );
|
|
ddBold = mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::Bold, context.expressionContext(), false );
|
|
}
|
|
|
|
// data defined italic font style?
|
|
bool ddItalic = false;
|
|
if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Italic ) )
|
|
{
|
|
context.expressionContext().setOriginalValueVariable( labelFont.italic() );
|
|
ddItalic = mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::Italic, context.expressionContext(), false );
|
|
}
|
|
|
|
// TODO: update when pref for how to resolve missing family (use matching algorithm or just default font) is implemented
|
|
// (currently defaults to what has been read in from layer settings)
|
|
QFont newFont;
|
|
QFont appFont = QApplication::font();
|
|
bool newFontBuilt = false;
|
|
if ( ddBold || ddItalic )
|
|
{
|
|
// new font needs built, since existing style needs removed
|
|
newFont = QgsFontUtils::createFont( !ddFontFamily.isEmpty() ? ddFontFamily : labelFont.family() );
|
|
newFontBuilt = true;
|
|
newFont.setBold( ddBold );
|
|
newFont.setItalic( ddItalic );
|
|
}
|
|
else if ( !ddFontStyle.isEmpty()
|
|
&& ddFontStyle.compare( QLatin1String( "Ignore" ), Qt::CaseInsensitive ) != 0 )
|
|
{
|
|
if ( !ddFontFamily.isEmpty() )
|
|
{
|
|
// both family and style are different, build font from database
|
|
if ( !mFontDB )
|
|
mFontDB = std::make_unique< QFontDatabase >();
|
|
|
|
QFont styledfont = mFontDB->font( ddFontFamily, ddFontStyle, appFont.pointSize() );
|
|
if ( appFont != styledfont )
|
|
{
|
|
newFont = styledfont;
|
|
newFontBuilt = true;
|
|
}
|
|
}
|
|
|
|
// update the font face style
|
|
QgsFontUtils::updateFontViaStyle( newFontBuilt ? newFont : labelFont, ddFontStyle );
|
|
}
|
|
else if ( !ddFontFamily.isEmpty() )
|
|
{
|
|
if ( ddFontStyle.compare( QLatin1String( "Ignore" ), Qt::CaseInsensitive ) != 0 )
|
|
{
|
|
// just family is different, build font from database
|
|
if ( !mFontDB )
|
|
mFontDB = std::make_unique< QFontDatabase >();
|
|
QFont styledfont = mFontDB->font( ddFontFamily, mFormat.namedStyle(), appFont.pointSize() );
|
|
if ( appFont != styledfont )
|
|
{
|
|
newFont = styledfont;
|
|
newFontBuilt = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
newFont = QgsFontUtils::createFont( ddFontFamily );
|
|
newFontBuilt = true;
|
|
}
|
|
}
|
|
|
|
if ( newFontBuilt )
|
|
{
|
|
// copy over existing font settings
|
|
//newFont = newFont.resolve( labelFont ); // should work, but let's be sure what's being copied
|
|
newFont.setPixelSize( labelFont.pixelSize() );
|
|
newFont.setUnderline( labelFont.underline() );
|
|
newFont.setStrikeOut( labelFont.strikeOut() );
|
|
newFont.setWordSpacing( labelFont.wordSpacing() );
|
|
newFont.setLetterSpacing( QFont::AbsoluteSpacing, labelFont.letterSpacing() );
|
|
|
|
labelFont = newFont;
|
|
}
|
|
|
|
// data defined word spacing?
|
|
double wordspace = labelFont.wordSpacing();
|
|
if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::FontWordSpacing ) )
|
|
{
|
|
context.expressionContext().setOriginalValueVariable( wordspace );
|
|
wordspace = mDataDefinedProperties.valueAsDouble( QgsPalLayerSettings::FontWordSpacing, context.expressionContext(), wordspace );
|
|
}
|
|
labelFont.setWordSpacing( context.convertToPainterUnits( wordspace, fontunits, mFormat.sizeMapUnitScale() ) );
|
|
|
|
// data defined letter spacing?
|
|
double letterspace = labelFont.letterSpacing();
|
|
if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::FontLetterSpacing ) )
|
|
{
|
|
context.expressionContext().setOriginalValueVariable( letterspace );
|
|
letterspace = mDataDefinedProperties.valueAsDouble( QgsPalLayerSettings::FontLetterSpacing, context.expressionContext(), letterspace );
|
|
}
|
|
labelFont.setLetterSpacing( QFont::AbsoluteSpacing, context.convertToPainterUnits( letterspace, fontunits, mFormat.sizeMapUnitScale() ) );
|
|
|
|
// data defined strikeout font style?
|
|
if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Strikeout ) )
|
|
{
|
|
context.expressionContext().setOriginalValueVariable( labelFont.strikeOut() );
|
|
bool strikeout = mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::Strikeout, context.expressionContext(), false );
|
|
labelFont.setStrikeOut( strikeout );
|
|
}
|
|
|
|
// data defined stretch
|
|
if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::FontStretchFactor ) )
|
|
{
|
|
context.expressionContext().setOriginalValueVariable( mFormat.stretchFactor() );
|
|
labelFont.setStretch( mDataDefinedProperties.valueAsInt( QgsPalLayerSettings::FontStretchFactor, context.expressionContext(), mFormat.stretchFactor() ) );
|
|
}
|
|
|
|
// data defined underline font style?
|
|
if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Underline ) )
|
|
{
|
|
context.expressionContext().setOriginalValueVariable( labelFont.underline() );
|
|
bool underline = mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::Underline, context.expressionContext(), false );
|
|
labelFont.setUnderline( underline );
|
|
}
|
|
|
|
// pass the rest on to QgsPalLabeling::drawLabeling
|
|
|
|
// data defined font color?
|
|
dataDefinedValEval( DDColor, QgsPalLayerSettings::Color, exprVal, context.expressionContext(), QgsSymbolLayerUtils::encodeColor( mFormat.color() ) );
|
|
|
|
// data defined font opacity?
|
|
dataDefinedValEval( DDOpacity, QgsPalLayerSettings::FontOpacity, exprVal, context.expressionContext(), mFormat.opacity() * 100 );
|
|
|
|
// data defined font blend mode?
|
|
dataDefinedValEval( DDBlendMode, QgsPalLayerSettings::FontBlendMode, exprVal, context.expressionContext() );
|
|
|
|
}
|
|
|
|
void QgsPalLayerSettings::parseTextBuffer( QgsRenderContext &context )
|
|
{
|
|
QVariant exprVal; // value() is repeatedly nulled on data defined evaluation and replaced when successful
|
|
|
|
QgsTextBufferSettings buffer = mFormat.buffer();
|
|
|
|
// data defined draw buffer?
|
|
bool drawBuffer = mFormat.buffer().enabled();
|
|
if ( dataDefinedValEval( DDBool, QgsPalLayerSettings::BufferDraw, exprVal, context.expressionContext(), buffer.enabled() ) )
|
|
{
|
|
drawBuffer = exprVal.toBool();
|
|
}
|
|
else if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::BufferDraw ) && QgsVariantUtils::isNull( exprVal ) )
|
|
{
|
|
dataDefinedValues.insert( QgsPalLayerSettings::BufferDraw, QVariant( drawBuffer ) );
|
|
}
|
|
|
|
if ( !drawBuffer )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// data defined buffer size?
|
|
double bufrSize = buffer.size();
|
|
if ( dataDefinedValEval( DDDoublePos, QgsPalLayerSettings::BufferSize, exprVal, context.expressionContext(), buffer.size() ) )
|
|
{
|
|
bufrSize = exprVal.toDouble();
|
|
}
|
|
|
|
// data defined buffer transparency?
|
|
double bufferOpacity = buffer.opacity() * 100;
|
|
if ( dataDefinedValEval( DDOpacity, QgsPalLayerSettings::BufferOpacity, exprVal, context.expressionContext(), bufferOpacity ) )
|
|
{
|
|
bufferOpacity = exprVal.toDouble();
|
|
}
|
|
|
|
drawBuffer = ( drawBuffer && bufrSize > 0.0 && bufferOpacity > 0 );
|
|
|
|
if ( !drawBuffer )
|
|
{
|
|
dataDefinedValues.insert( QgsPalLayerSettings::BufferDraw, QVariant( false ) ); // trigger value
|
|
dataDefinedValues.remove( QgsPalLayerSettings::BufferSize );
|
|
dataDefinedValues.remove( QgsPalLayerSettings::BufferOpacity );
|
|
return; // don't bother evaluating values that won't be used
|
|
}
|
|
|
|
// data defined buffer units?
|
|
dataDefinedValEval( DDUnits, QgsPalLayerSettings::BufferUnit, exprVal, context.expressionContext() );
|
|
|
|
// data defined buffer color?
|
|
dataDefinedValEval( DDColor, QgsPalLayerSettings::BufferColor, exprVal, context.expressionContext(), QgsSymbolLayerUtils::encodeColor( buffer.color() ) );
|
|
|
|
// data defined buffer pen join style?
|
|
dataDefinedValEval( DDJoinStyle, QgsPalLayerSettings::BufferJoinStyle, exprVal, context.expressionContext(), QgsSymbolLayerUtils::encodePenJoinStyle( buffer.joinStyle() ) );
|
|
|
|
// data defined buffer blend mode?
|
|
dataDefinedValEval( DDBlendMode, QgsPalLayerSettings::BufferBlendMode, exprVal, context.expressionContext() );
|
|
}
|
|
|
|
void QgsPalLayerSettings::parseTextMask( QgsRenderContext &context )
|
|
{
|
|
QVariant exprVal; // value() is repeatedly nulled on data defined evaluation and replaced when successful
|
|
|
|
QgsTextMaskSettings mask = mFormat.mask();
|
|
|
|
// data defined enabled mask?
|
|
bool maskEnabled = mask.enabled();
|
|
if ( dataDefinedValEval( DDBool, QgsPalLayerSettings::MaskEnabled, exprVal, context.expressionContext(), mask.enabled() ) )
|
|
{
|
|
maskEnabled = exprVal.toBool();
|
|
}
|
|
|
|
if ( !maskEnabled )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// data defined buffer size?
|
|
double bufrSize = mask.size();
|
|
if ( dataDefinedValEval( DDDoublePos, QgsPalLayerSettings::MaskBufferSize, exprVal, context.expressionContext(), mask.size() ) )
|
|
{
|
|
bufrSize = exprVal.toDouble();
|
|
}
|
|
|
|
// data defined opacity?
|
|
double opacity = mask.opacity() * 100;
|
|
if ( dataDefinedValEval( DDOpacity, QgsPalLayerSettings::MaskOpacity, exprVal, context.expressionContext(), opacity ) )
|
|
{
|
|
opacity = exprVal.toDouble();
|
|
}
|
|
|
|
maskEnabled = ( maskEnabled && bufrSize > 0.0 && opacity > 0 );
|
|
|
|
if ( !maskEnabled )
|
|
{
|
|
dataDefinedValues.insert( QgsPalLayerSettings::MaskEnabled, QVariant( false ) ); // trigger value
|
|
dataDefinedValues.remove( QgsPalLayerSettings::MaskBufferSize );
|
|
dataDefinedValues.remove( QgsPalLayerSettings::MaskOpacity );
|
|
return; // don't bother evaluating values that won't be used
|
|
}
|
|
|
|
// data defined buffer units?
|
|
dataDefinedValEval( DDUnits, QgsPalLayerSettings::MaskBufferUnit, exprVal, context.expressionContext() );
|
|
|
|
// data defined buffer pen join style?
|
|
dataDefinedValEval( DDJoinStyle, QgsPalLayerSettings::MaskJoinStyle, exprVal, context.expressionContext(), QgsSymbolLayerUtils::encodePenJoinStyle( mask.joinStyle() ) );
|
|
}
|
|
|
|
void QgsPalLayerSettings::parseTextFormatting( QgsRenderContext &context )
|
|
{
|
|
QVariant exprVal; // value() is repeatedly nulled on data defined evaluation and replaced when successful
|
|
|
|
// data defined multiline wrap character?
|
|
QString wrapchr = wrapChar;
|
|
if ( dataDefinedValEval( DDString, QgsPalLayerSettings::MultiLineWrapChar, exprVal, context.expressionContext(), wrapChar ) )
|
|
{
|
|
wrapchr = exprVal.toString();
|
|
}
|
|
|
|
int evalAutoWrapLength = autoWrapLength;
|
|
if ( dataDefinedValEval( DDInt, QgsPalLayerSettings::AutoWrapLength, exprVal, context.expressionContext(), evalAutoWrapLength ) )
|
|
{
|
|
evalAutoWrapLength = exprVal.toInt();
|
|
}
|
|
|
|
// data defined multiline height?
|
|
dataDefinedValEval( DDDouble, QgsPalLayerSettings::MultiLineHeight, exprVal, context.expressionContext() );
|
|
|
|
// data defined multiline text align?
|
|
if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::MultiLineAlignment ) )
|
|
{
|
|
context.expressionContext().setOriginalValueVariable( mFormat.lineHeight() );
|
|
exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::MultiLineAlignment, context.expressionContext() );
|
|
if ( !QgsVariantUtils::isNull( exprVal ) )
|
|
{
|
|
QString str = exprVal.toString().trimmed();
|
|
QgsDebugMsgLevel( QStringLiteral( "exprVal MultiLineAlignment:%1" ).arg( str ), 4 );
|
|
|
|
if ( !str.isEmpty() )
|
|
{
|
|
// "Left"
|
|
Qgis::LabelMultiLineAlignment aligntype = Qgis::LabelMultiLineAlignment::Left;
|
|
|
|
if ( str.compare( QLatin1String( "Center" ), Qt::CaseInsensitive ) == 0 )
|
|
{
|
|
aligntype = Qgis::LabelMultiLineAlignment::Center;
|
|
}
|
|
else if ( str.compare( QLatin1String( "Right" ), Qt::CaseInsensitive ) == 0 )
|
|
{
|
|
aligntype = Qgis::LabelMultiLineAlignment::Right;
|
|
}
|
|
else if ( str.compare( QLatin1String( "Follow" ), Qt::CaseInsensitive ) == 0 )
|
|
{
|
|
aligntype = Qgis::LabelMultiLineAlignment::FollowPlacement;
|
|
}
|
|
else if ( str.compare( QLatin1String( "Justify" ), Qt::CaseInsensitive ) == 0 )
|
|
{
|
|
aligntype = Qgis::LabelMultiLineAlignment::Justify;
|
|
}
|
|
dataDefinedValues.insert( QgsPalLayerSettings::MultiLineAlignment, QVariant( static_cast< int >( aligntype ) ) );
|
|
}
|
|
}
|
|
}
|
|
|
|
// text orientation
|
|
if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::TextOrientation ) )
|
|
{
|
|
const QString encoded = QgsTextRendererUtils::encodeTextOrientation( mFormat.orientation() );
|
|
context.expressionContext().setOriginalValueVariable( encoded );
|
|
exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::TextOrientation, context.expressionContext() );
|
|
if ( !QgsVariantUtils::isNull( exprVal ) )
|
|
{
|
|
QString str = exprVal.toString().trimmed();
|
|
if ( !str.isEmpty() )
|
|
dataDefinedValues.insert( QgsPalLayerSettings::TextOrientation, str );
|
|
}
|
|
}
|
|
|
|
// data defined direction symbol?
|
|
bool drawDirSymb = mLineSettings.addDirectionSymbol();
|
|
if ( dataDefinedValEval( DDBool, QgsPalLayerSettings::DirSymbDraw, exprVal, context.expressionContext(), drawDirSymb ) )
|
|
{
|
|
drawDirSymb = exprVal.toBool();
|
|
}
|
|
|
|
if ( drawDirSymb )
|
|
{
|
|
// data defined direction left symbol?
|
|
dataDefinedValEval( DDString, QgsPalLayerSettings::DirSymbLeft, exprVal, context.expressionContext(), mLineSettings.leftDirectionSymbol() );
|
|
|
|
// data defined direction right symbol?
|
|
dataDefinedValEval( DDString, QgsPalLayerSettings::DirSymbRight, exprVal, context.expressionContext(), mLineSettings.rightDirectionSymbol() );
|
|
|
|
// data defined direction symbol placement?
|
|
exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::DirSymbPlacement, context.expressionContext() );
|
|
if ( !QgsVariantUtils::isNull( exprVal ) )
|
|
{
|
|
QString str = exprVal.toString().trimmed();
|
|
QgsDebugMsgLevel( QStringLiteral( "exprVal DirSymbPlacement:%1" ).arg( str ), 4 );
|
|
|
|
if ( !str.isEmpty() )
|
|
{
|
|
// "LeftRight"
|
|
QgsLabelLineSettings::DirectionSymbolPlacement placetype = QgsLabelLineSettings::DirectionSymbolPlacement::SymbolLeftRight;
|
|
|
|
if ( str.compare( QLatin1String( "Above" ), Qt::CaseInsensitive ) == 0 )
|
|
{
|
|
placetype = QgsLabelLineSettings::DirectionSymbolPlacement::SymbolAbove;
|
|
}
|
|
else if ( str.compare( QLatin1String( "Below" ), Qt::CaseInsensitive ) == 0 )
|
|
{
|
|
placetype = QgsLabelLineSettings::DirectionSymbolPlacement::SymbolBelow;
|
|
}
|
|
dataDefinedValues.insert( QgsPalLayerSettings::DirSymbPlacement, QVariant( static_cast< int >( placetype ) ) );
|
|
}
|
|
}
|
|
|
|
// data defined direction symbol reversed?
|
|
dataDefinedValEval( DDBool, QgsPalLayerSettings::DirSymbReverse, exprVal, context.expressionContext(), mLineSettings.reverseDirectionSymbol() );
|
|
}
|
|
|
|
// formatting for numbers is inline with generation of base label text and not passed to label painting
|
|
}
|
|
|
|
void QgsPalLayerSettings::parseShapeBackground( QgsRenderContext &context )
|
|
{
|
|
QVariant exprVal; // value() is repeatedly nulled on data defined evaluation and replaced when successful
|
|
|
|
QgsTextBackgroundSettings background = mFormat.background();
|
|
|
|
// data defined draw shape?
|
|
bool drawShape = background.enabled();
|
|
if ( dataDefinedValEval( DDBool, QgsPalLayerSettings::ShapeDraw, exprVal, context.expressionContext(), drawShape ) )
|
|
{
|
|
drawShape = exprVal.toBool();
|
|
}
|
|
|
|
if ( !drawShape )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// data defined shape transparency?
|
|
double shapeOpacity = background.opacity() * 100;
|
|
if ( dataDefinedValEval( DDOpacity, QgsPalLayerSettings::ShapeOpacity, exprVal, context.expressionContext(), shapeOpacity ) )
|
|
{
|
|
shapeOpacity = 100.0 * exprVal.toDouble();
|
|
}
|
|
|
|
drawShape = ( drawShape && shapeOpacity > 0 ); // size is not taken into account (could be)
|
|
|
|
if ( !drawShape )
|
|
{
|
|
dataDefinedValues.insert( QgsPalLayerSettings::ShapeDraw, QVariant( false ) ); // trigger value
|
|
dataDefinedValues.remove( QgsPalLayerSettings::ShapeOpacity );
|
|
return; // don't bother evaluating values that won't be used
|
|
}
|
|
|
|
// data defined shape kind?
|
|
QgsTextBackgroundSettings::ShapeType shapeKind = background.type();
|
|
if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::ShapeKind ) )
|
|
{
|
|
exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::ShapeKind, context.expressionContext() );
|
|
if ( !QgsVariantUtils::isNull( exprVal ) )
|
|
{
|
|
QString skind = exprVal.toString().trimmed();
|
|
QgsDebugMsgLevel( QStringLiteral( "exprVal ShapeKind:%1" ).arg( skind ), 4 );
|
|
|
|
if ( !skind.isEmpty() )
|
|
{
|
|
shapeKind = QgsTextRendererUtils::decodeShapeType( skind );
|
|
dataDefinedValues.insert( QgsPalLayerSettings::ShapeKind, QVariant( static_cast< int >( shapeKind ) ) );
|
|
}
|
|
}
|
|
}
|
|
|
|
// data defined shape SVG path?
|
|
QString svgPath = background.svgFile();
|
|
if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::ShapeSVGFile ) )
|
|
{
|
|
context.expressionContext().setOriginalValueVariable( svgPath );
|
|
exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::ShapeSVGFile, context.expressionContext() );
|
|
if ( !QgsVariantUtils::isNull( exprVal ) )
|
|
{
|
|
QString svgfile = exprVal.toString().trimmed();
|
|
QgsDebugMsgLevel( QStringLiteral( "exprVal ShapeSVGFile:%1" ).arg( svgfile ), 4 );
|
|
|
|
// '' empty paths are allowed
|
|
svgPath = QgsSymbolLayerUtils::svgSymbolNameToPath( svgfile, context.pathResolver() );
|
|
dataDefinedValues.insert( QgsPalLayerSettings::ShapeSVGFile, QVariant( svgPath ) );
|
|
}
|
|
}
|
|
|
|
// data defined shape size type?
|
|
QgsTextBackgroundSettings::SizeType shpSizeType = background.sizeType();
|
|
if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::ShapeSizeType ) )
|
|
{
|
|
exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::ShapeSizeType, context.expressionContext() );
|
|
if ( !QgsVariantUtils::isNull( exprVal ) )
|
|
{
|
|
QString stype = exprVal.toString().trimmed();
|
|
QgsDebugMsgLevel( QStringLiteral( "exprVal ShapeSizeType:%1" ).arg( stype ), 4 );
|
|
|
|
if ( !stype.isEmpty() )
|
|
{
|
|
shpSizeType = QgsTextRendererUtils::decodeBackgroundSizeType( stype );
|
|
dataDefinedValues.insert( QgsPalLayerSettings::ShapeSizeType, QVariant( static_cast< int >( shpSizeType ) ) );
|
|
}
|
|
}
|
|
}
|
|
|
|
// data defined shape size X? (SVGs only use X for sizing)
|
|
double ddShpSizeX = background.size().width();
|
|
if ( dataDefinedValEval( DDDouble, QgsPalLayerSettings::ShapeSizeX, exprVal, context.expressionContext(), ddShpSizeX ) )
|
|
{
|
|
ddShpSizeX = exprVal.toDouble();
|
|
}
|
|
|
|
// data defined shape size Y?
|
|
double ddShpSizeY = background.size().height();
|
|
if ( dataDefinedValEval( DDDouble, QgsPalLayerSettings::ShapeSizeY, exprVal, context.expressionContext(), ddShpSizeY ) )
|
|
{
|
|
ddShpSizeY = exprVal.toDouble();
|
|
}
|
|
|
|
// don't continue under certain circumstances (e.g. size is fixed)
|
|
bool skip = false;
|
|
if ( shapeKind == QgsTextBackgroundSettings::ShapeSVG
|
|
&& ( svgPath.isEmpty()
|
|
|| ( !svgPath.isEmpty()
|
|
&& shpSizeType == QgsTextBackgroundSettings::SizeFixed
|
|
&& ddShpSizeX == 0.0 ) ) )
|
|
{
|
|
skip = true;
|
|
}
|
|
if ( shapeKind == QgsTextBackgroundSettings::ShapeMarkerSymbol
|
|
&& ( !background.markerSymbol()
|
|
|| ( background.markerSymbol()
|
|
&& shpSizeType == QgsTextBackgroundSettings::SizeFixed
|
|
&& ddShpSizeX == 0.0 ) ) )
|
|
{
|
|
skip = true;
|
|
}
|
|
if ( shapeKind != QgsTextBackgroundSettings::ShapeSVG
|
|
&& shapeKind != QgsTextBackgroundSettings::ShapeMarkerSymbol
|
|
&& shpSizeType == QgsTextBackgroundSettings::SizeFixed
|
|
&& ( ddShpSizeX == 0.0 || ddShpSizeY == 0.0 ) )
|
|
{
|
|
skip = true;
|
|
}
|
|
|
|
if ( skip )
|
|
{
|
|
dataDefinedValues.insert( QgsPalLayerSettings::ShapeDraw, QVariant( false ) ); // trigger value
|
|
dataDefinedValues.remove( QgsPalLayerSettings::ShapeOpacity );
|
|
dataDefinedValues.remove( QgsPalLayerSettings::ShapeKind );
|
|
dataDefinedValues.remove( QgsPalLayerSettings::ShapeSVGFile );
|
|
dataDefinedValues.remove( QgsPalLayerSettings::ShapeSizeX );
|
|
dataDefinedValues.remove( QgsPalLayerSettings::ShapeSizeY );
|
|
return; // don't bother evaluating values that won't be used
|
|
}
|
|
|
|
// data defined shape size units?
|
|
dataDefinedValEval( DDUnits, QgsPalLayerSettings::ShapeSizeUnits, exprVal, context.expressionContext() );
|
|
|
|
// data defined shape rotation type?
|
|
if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::ShapeRotationType ) )
|
|
{
|
|
exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::ShapeRotationType, context.expressionContext() );
|
|
if ( !QgsVariantUtils::isNull( exprVal ) )
|
|
{
|
|
QString rotstr = exprVal.toString().trimmed();
|
|
QgsDebugMsgLevel( QStringLiteral( "exprVal ShapeRotationType:%1" ).arg( rotstr ), 4 );
|
|
|
|
if ( !rotstr.isEmpty() )
|
|
{
|
|
// "Sync"
|
|
QgsTextBackgroundSettings::RotationType rottype = QgsTextRendererUtils::decodeBackgroundRotationType( rotstr );
|
|
dataDefinedValues.insert( QgsPalLayerSettings::ShapeRotationType, QVariant( static_cast< int >( rottype ) ) );
|
|
}
|
|
}
|
|
}
|
|
|
|
// data defined shape rotation?
|
|
dataDefinedValEval( DDRotation180, QgsPalLayerSettings::ShapeRotation, exprVal, context.expressionContext(), background.rotation() );
|
|
|
|
// data defined shape offset?
|
|
dataDefinedValEval( DDPointF, QgsPalLayerSettings::ShapeOffset, exprVal, context.expressionContext(), QgsSymbolLayerUtils::encodePoint( background.offset() ) );
|
|
|
|
// data defined shape offset units?
|
|
dataDefinedValEval( DDUnits, QgsPalLayerSettings::ShapeOffsetUnits, exprVal, context.expressionContext() );
|
|
|
|
// data defined shape radii?
|
|
dataDefinedValEval( DDSizeF, QgsPalLayerSettings::ShapeRadii, exprVal, context.expressionContext(), QgsSymbolLayerUtils::encodeSize( background.radii() ) );
|
|
|
|
// data defined shape radii units?
|
|
dataDefinedValEval( DDUnits, QgsPalLayerSettings::ShapeRadiiUnits, exprVal, context.expressionContext() );
|
|
|
|
// data defined shape blend mode?
|
|
dataDefinedValEval( DDBlendMode, QgsPalLayerSettings::ShapeBlendMode, exprVal, context.expressionContext() );
|
|
|
|
// data defined shape fill color?
|
|
dataDefinedValEval( DDColor, QgsPalLayerSettings::ShapeFillColor, exprVal, context.expressionContext(), QgsSymbolLayerUtils::encodeColor( background.fillColor() ) );
|
|
|
|
// data defined shape stroke color?
|
|
dataDefinedValEval( DDColor, QgsPalLayerSettings::ShapeStrokeColor, exprVal, context.expressionContext(), QgsSymbolLayerUtils::encodeColor( background.strokeColor() ) );
|
|
|
|
// data defined shape stroke width?
|
|
dataDefinedValEval( DDDoublePos, QgsPalLayerSettings::ShapeStrokeWidth, exprVal, context.expressionContext(), background.strokeWidth() );
|
|
|
|
// data defined shape stroke width units?
|
|
dataDefinedValEval( DDUnits, QgsPalLayerSettings::ShapeStrokeWidthUnits, exprVal, context.expressionContext() );
|
|
|
|
// data defined shape join style?
|
|
dataDefinedValEval( DDJoinStyle, QgsPalLayerSettings::ShapeJoinStyle, exprVal, context.expressionContext(), QgsSymbolLayerUtils::encodePenJoinStyle( background.joinStyle() ) );
|
|
|
|
}
|
|
|
|
void QgsPalLayerSettings::parseDropShadow( QgsRenderContext &context )
|
|
{
|
|
QVariant exprVal; // value() is repeatedly nulled on data defined evaluation and replaced when successful
|
|
|
|
QgsTextShadowSettings shadow = mFormat.shadow();
|
|
|
|
// data defined draw shadow?
|
|
bool drawShadow = shadow.enabled();
|
|
if ( dataDefinedValEval( DDBool, QgsPalLayerSettings::ShadowDraw, exprVal, context.expressionContext(), drawShadow ) )
|
|
{
|
|
drawShadow = exprVal.toBool();
|
|
}
|
|
|
|
if ( !drawShadow )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// data defined shadow transparency?
|
|
double shadowOpacity = shadow.opacity() * 100;
|
|
if ( dataDefinedValEval( DDOpacity, QgsPalLayerSettings::ShadowOpacity, exprVal, context.expressionContext(), shadowOpacity ) )
|
|
{
|
|
shadowOpacity = exprVal.toDouble();
|
|
}
|
|
|
|
// data defined shadow offset distance?
|
|
double shadowOffDist = shadow.offsetDistance();
|
|
if ( dataDefinedValEval( DDDoublePos, QgsPalLayerSettings::ShadowOffsetDist, exprVal, context.expressionContext(), shadowOffDist ) )
|
|
{
|
|
shadowOffDist = exprVal.toDouble();
|
|
}
|
|
|
|
// data defined shadow offset distance?
|
|
double shadowRad = shadow.blurRadius();
|
|
if ( dataDefinedValEval( DDDoublePos, QgsPalLayerSettings::ShadowRadius, exprVal, context.expressionContext(), shadowRad ) )
|
|
{
|
|
shadowRad = exprVal.toDouble();
|
|
}
|
|
|
|
drawShadow = ( drawShadow && shadowOpacity > 0 && !( shadowOffDist == 0.0 && shadowRad == 0.0 ) );
|
|
|
|
if ( !drawShadow )
|
|
{
|
|
dataDefinedValues.insert( QgsPalLayerSettings::ShadowDraw, QVariant( false ) ); // trigger value
|
|
dataDefinedValues.remove( QgsPalLayerSettings::ShadowOpacity );
|
|
dataDefinedValues.remove( QgsPalLayerSettings::ShadowOffsetDist );
|
|
dataDefinedValues.remove( QgsPalLayerSettings::ShadowRadius );
|
|
return; // don't bother evaluating values that won't be used
|
|
}
|
|
|
|
// data defined shadow under type?
|
|
if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::ShadowUnder ) )
|
|
{
|
|
exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::ShadowUnder, context.expressionContext() );
|
|
if ( !QgsVariantUtils::isNull( exprVal ) )
|
|
{
|
|
QString str = exprVal.toString().trimmed();
|
|
QgsDebugMsgLevel( QStringLiteral( "exprVal ShadowUnder:%1" ).arg( str ), 4 );
|
|
|
|
if ( !str.isEmpty() )
|
|
{
|
|
QgsTextShadowSettings::ShadowPlacement shdwtype = QgsTextRendererUtils::decodeShadowPlacementType( str );
|
|
dataDefinedValues.insert( QgsPalLayerSettings::ShadowUnder, QVariant( static_cast< int >( shdwtype ) ) );
|
|
}
|
|
}
|
|
}
|
|
|
|
// data defined shadow offset angle?
|
|
dataDefinedValEval( DDRotation180, QgsPalLayerSettings::ShadowOffsetAngle, exprVal, context.expressionContext(), shadow.offsetAngle() );
|
|
|
|
// data defined shadow offset units?
|
|
dataDefinedValEval( DDUnits, QgsPalLayerSettings::ShadowOffsetUnits, exprVal, context.expressionContext() );
|
|
|
|
// data defined shadow radius?
|
|
dataDefinedValEval( DDDouble, QgsPalLayerSettings::ShadowRadius, exprVal, context.expressionContext(), shadow.blurRadius() );
|
|
|
|
// data defined shadow radius units?
|
|
dataDefinedValEval( DDUnits, QgsPalLayerSettings::ShadowRadiusUnits, exprVal, context.expressionContext() );
|
|
|
|
// data defined shadow scale? ( gui bounds to 0-2000, no upper bound here )
|
|
dataDefinedValEval( DDIntPos, QgsPalLayerSettings::ShadowScale, exprVal, context.expressionContext(), shadow.scale() );
|
|
|
|
// data defined shadow color?
|
|
dataDefinedValEval( DDColor, QgsPalLayerSettings::ShadowColor, exprVal, context.expressionContext(), QgsSymbolLayerUtils::encodeColor( shadow.color() ) );
|
|
|
|
// data defined shadow blend mode?
|
|
dataDefinedValEval( DDBlendMode, QgsPalLayerSettings::ShadowBlendMode, exprVal, context.expressionContext() );
|
|
}
|
|
|
|
// -------------
|
|
|
|
|
|
bool QgsPalLabeling::staticWillUseLayer( const QgsMapLayer *layer )
|
|
{
|
|
switch ( layer->type() )
|
|
{
|
|
case Qgis::LayerType::Vector:
|
|
{
|
|
const QgsVectorLayer *vl = qobject_cast< const QgsVectorLayer * >( layer );
|
|
return vl->labelsEnabled() || vl->diagramsEnabled();
|
|
}
|
|
|
|
case Qgis::LayerType::VectorTile:
|
|
{
|
|
const QgsVectorTileLayer *vl = qobject_cast< const QgsVectorTileLayer * >( layer );
|
|
if ( !vl->labeling() )
|
|
return false;
|
|
|
|
if ( const QgsVectorTileBasicLabeling *labeling = dynamic_cast< const QgsVectorTileBasicLabeling *>( vl->labeling() ) )
|
|
return !labeling->styles().empty();
|
|
|
|
return false;
|
|
}
|
|
|
|
case Qgis::LayerType::Raster:
|
|
case Qgis::LayerType::Plugin:
|
|
case Qgis::LayerType::Mesh:
|
|
case Qgis::LayerType::PointCloud:
|
|
case Qgis::LayerType::Annotation:
|
|
case Qgis::LayerType::Group:
|
|
case Qgis::LayerType::TiledScene:
|
|
return false;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
bool QgsPalLabeling::geometryRequiresPreparation( const QgsGeometry &geometry, QgsRenderContext &context, const QgsCoordinateTransform &ct, const QgsGeometry &clipGeometry, bool mergeLines )
|
|
{
|
|
if ( geometry.isNull() )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if ( geometry.type() == Qgis::GeometryType::Line && geometry.isMultipart() && mergeLines )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
//requires reprojection
|
|
if ( ct.isValid() && !ct.isShortCircuited() )
|
|
return true;
|
|
|
|
//requires rotation
|
|
const QgsMapToPixel &m2p = context.mapToPixel();
|
|
if ( !qgsDoubleNear( m2p.mapRotation(), 0 ) )
|
|
return true;
|
|
|
|
//requires clip
|
|
if ( !clipGeometry.isNull() && !clipGeometry.boundingBox().contains( geometry.boundingBox() ) )
|
|
return true;
|
|
|
|
//requires fixing
|
|
if ( geometry.type() == Qgis::GeometryType::Polygon && !geometry.isGeosValid() )
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
QStringList QgsPalLabeling::splitToLines( const QString &text, const QString &wrapCharacter, const int autoWrapLength, const bool useMaxLineLengthWhenAutoWrapping )
|
|
{
|
|
QStringList multiLineSplit;
|
|
if ( !wrapCharacter.isEmpty() && wrapCharacter != QLatin1String( "\n" ) )
|
|
{
|
|
//wrap on both the wrapchr and new line characters
|
|
const QStringList lines = text.split( wrapCharacter );
|
|
for ( const QString &line : lines )
|
|
{
|
|
multiLineSplit.append( line.split( '\n' ) );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
multiLineSplit = text.split( '\n' );
|
|
}
|
|
|
|
// apply auto wrapping to each manually created line
|
|
if ( autoWrapLength != 0 )
|
|
{
|
|
QStringList autoWrappedLines;
|
|
autoWrappedLines.reserve( multiLineSplit.count() );
|
|
for ( const QString &line : std::as_const( multiLineSplit ) )
|
|
{
|
|
autoWrappedLines.append( QgsStringUtils::wordWrap( line, autoWrapLength, useMaxLineLengthWhenAutoWrapping ).split( '\n' ) );
|
|
}
|
|
multiLineSplit = autoWrappedLines;
|
|
}
|
|
return multiLineSplit;
|
|
}
|
|
|
|
QStringList QgsPalLabeling::splitToGraphemes( const QString &text )
|
|
{
|
|
QStringList graphemes;
|
|
QTextBoundaryFinder boundaryFinder( QTextBoundaryFinder::Grapheme, text );
|
|
int currentBoundary = -1;
|
|
int previousBoundary = 0;
|
|
while ( ( currentBoundary = boundaryFinder.toNextBoundary() ) > 0 )
|
|
{
|
|
graphemes << text.mid( previousBoundary, currentBoundary - previousBoundary );
|
|
previousBoundary = currentBoundary;
|
|
}
|
|
return graphemes;
|
|
}
|
|
|
|
QgsGeometry QgsPalLabeling::prepareGeometry( const QgsGeometry &geometry, QgsRenderContext &context, const QgsCoordinateTransform &ct, const QgsGeometry &clipGeometry, bool mergeLines )
|
|
{
|
|
if ( geometry.isNull() )
|
|
{
|
|
return QgsGeometry();
|
|
}
|
|
|
|
//don't modify the feature's geometry so that geometry based expressions keep working
|
|
QgsGeometry geom = geometry;
|
|
|
|
if ( geom.type() == Qgis::GeometryType::Line && geom.isMultipart() && mergeLines )
|
|
{
|
|
geom = geom.mergeLines();
|
|
}
|
|
|
|
//reproject the geometry if necessary
|
|
if ( ct.isValid() && !ct.isShortCircuited() )
|
|
{
|
|
try
|
|
{
|
|
geom.transform( ct );
|
|
}
|
|
catch ( QgsCsException &cse )
|
|
{
|
|
Q_UNUSED( cse )
|
|
QgsDebugMsgLevel( QStringLiteral( "Ignoring feature due to transformation exception" ), 4 );
|
|
return QgsGeometry();
|
|
}
|
|
// geometry transforms may result in nan points, remove these
|
|
geom.filterVertices( []( const QgsPoint & point )->bool
|
|
{
|
|
return std::isfinite( point.x() ) && std::isfinite( point.y() );
|
|
} );
|
|
if ( QgsCurvePolygon *cp = qgsgeometry_cast< QgsCurvePolygon * >( geom.get() ) )
|
|
{
|
|
cp->removeInvalidRings();
|
|
}
|
|
else if ( QgsMultiSurface *ms = qgsgeometry_cast< QgsMultiSurface * >( geom.get() ) )
|
|
{
|
|
for ( int i = 0; i < ms->numGeometries(); ++i )
|
|
{
|
|
if ( QgsCurvePolygon *cp = qgsgeometry_cast< QgsCurvePolygon * >( ms->geometryN( i ) ) )
|
|
cp->removeInvalidRings();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Rotate the geometry if needed, before clipping
|
|
const QgsMapToPixel &m2p = context.mapToPixel();
|
|
if ( !qgsDoubleNear( m2p.mapRotation(), 0 ) )
|
|
{
|
|
QgsPointXY center = context.mapExtent().center();
|
|
if ( geom.rotate( m2p.mapRotation(), center ) != Qgis::GeometryOperationResult::Success )
|
|
{
|
|
QgsDebugError( QStringLiteral( "Error rotating geometry" ).arg( geom.asWkt() ) );
|
|
return QgsGeometry();
|
|
}
|
|
}
|
|
|
|
const bool mustClip = ( !clipGeometry.isNull() &&
|
|
( ( qgsDoubleNear( m2p.mapRotation(), 0 ) && !clipGeometry.boundingBox().contains( geom.boundingBox() ) )
|
|
|| ( !qgsDoubleNear( m2p.mapRotation(), 0 ) && !clipGeometry.contains( geom ) ) ) );
|
|
|
|
bool mustClipExact = false;
|
|
if ( mustClip )
|
|
{
|
|
// nice and fast, but can result in invalid geometries. At least it will potentially strip out a bunch of unwanted vertices upfront!
|
|
QgsGeometry clipGeom = geom.clipped( clipGeometry.boundingBox() );
|
|
if ( clipGeom.isEmpty() )
|
|
return QgsGeometry();
|
|
|
|
geom = clipGeom;
|
|
|
|
// we've now clipped against the BOUNDING BOX of clipGeometry. But if clipGeometry is an axis parallel rectangle, then there's no
|
|
// need to do an exact (potentially costly) intersection clip as well!
|
|
mustClipExact = !clipGeometry.isAxisParallelRectangle( 0.001 );
|
|
}
|
|
|
|
// fix invalid polygons
|
|
if ( geom.type() == Qgis::GeometryType::Polygon )
|
|
{
|
|
if ( geom.isMultipart() )
|
|
{
|
|
// important -- we need to treat ever part in isolation here. We can't test the validity of the whole geometry
|
|
// at once, because touching parts would result in an invalid geometry, and buffering this "dissolves" the parts.
|
|
// because the actual label engine treats parts as separate entities, we aren't bound by the usual "touching parts are invalid" rule
|
|
// see https://github.com/qgis/QGIS/issues/26763
|
|
QVector< QgsGeometry> parts;
|
|
parts.reserve( qgsgeometry_cast< const QgsGeometryCollection * >( geom.constGet() )->numGeometries() );
|
|
for ( auto it = geom.const_parts_begin(); it != geom.const_parts_end(); ++it )
|
|
{
|
|
QgsGeometry partGeom( ( *it )->clone() );
|
|
if ( !partGeom.isGeosValid() )
|
|
{
|
|
|
|
partGeom = partGeom.makeValid();
|
|
}
|
|
parts.append( partGeom );
|
|
}
|
|
geom = QgsGeometry::collectGeometry( parts );
|
|
}
|
|
else if ( !geom.isGeosValid() )
|
|
{
|
|
|
|
QgsGeometry bufferGeom = geom.makeValid();
|
|
if ( bufferGeom.isNull() )
|
|
{
|
|
QgsDebugError( QStringLiteral( "Could not repair geometry: %1" ).arg( bufferGeom.lastError() ) );
|
|
return QgsGeometry();
|
|
}
|
|
geom = bufferGeom;
|
|
}
|
|
}
|
|
|
|
if ( mustClipExact )
|
|
{
|
|
// now do the real intersection against the actual clip geometry
|
|
QgsGeometry clipGeom = geom.intersection( clipGeometry );
|
|
if ( clipGeom.isEmpty() )
|
|
{
|
|
return QgsGeometry();
|
|
}
|
|
geom = clipGeom;
|
|
}
|
|
|
|
return geom;
|
|
}
|
|
|
|
bool QgsPalLabeling::checkMinimumSizeMM( const QgsRenderContext &context, const QgsGeometry &geom, double minSize )
|
|
{
|
|
if ( minSize <= 0 )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if ( geom.isNull() )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
Qgis::GeometryType featureType = geom.type();
|
|
if ( featureType == Qgis::GeometryType::Point ) //minimum size does not apply to point features
|
|
{
|
|
return true;
|
|
}
|
|
|
|
double mapUnitsPerMM = context.mapToPixel().mapUnitsPerPixel() * context.scaleFactor();
|
|
if ( featureType == Qgis::GeometryType::Line )
|
|
{
|
|
double length = geom.length();
|
|
if ( length >= 0.0 )
|
|
{
|
|
return ( length >= ( minSize * mapUnitsPerMM ) );
|
|
}
|
|
}
|
|
else if ( featureType == Qgis::GeometryType::Polygon )
|
|
{
|
|
double area = geom.area();
|
|
if ( area >= 0.0 )
|
|
{
|
|
return ( std::sqrt( area ) >= ( minSize * mapUnitsPerMM ) );
|
|
}
|
|
}
|
|
return true; //should never be reached. Return true in this case to label such geometries anyway.
|
|
}
|
|
|
|
|
|
void QgsPalLabeling::dataDefinedTextStyle( QgsPalLayerSettings &tmpLyr,
|
|
const QMap< QgsPalLayerSettings::Property, QVariant > &ddValues )
|
|
{
|
|
QgsTextFormat format = tmpLyr.format();
|
|
bool changed = false;
|
|
|
|
//font color
|
|
if ( ddValues.contains( QgsPalLayerSettings::Color ) )
|
|
{
|
|
QVariant ddColor = ddValues.value( QgsPalLayerSettings::Color );
|
|
format.setColor( ddColor.value<QColor>() );
|
|
changed = true;
|
|
}
|
|
|
|
//font transparency
|
|
if ( ddValues.contains( QgsPalLayerSettings::FontOpacity ) )
|
|
{
|
|
format.setOpacity( ddValues.value( QgsPalLayerSettings::FontOpacity ).toDouble() / 100.0 );
|
|
changed = true;
|
|
}
|
|
|
|
//font blend mode
|
|
if ( ddValues.contains( QgsPalLayerSettings::FontBlendMode ) )
|
|
{
|
|
format.setBlendMode( static_cast< QPainter::CompositionMode >( ddValues.value( QgsPalLayerSettings::FontBlendMode ).toInt() ) );
|
|
changed = true;
|
|
}
|
|
|
|
if ( changed )
|
|
{
|
|
tmpLyr.setFormat( format );
|
|
}
|
|
}
|
|
|
|
void QgsPalLabeling::dataDefinedTextFormatting( QgsPalLayerSettings &tmpLyr,
|
|
const QMap< QgsPalLayerSettings::Property, QVariant > &ddValues )
|
|
{
|
|
if ( ddValues.contains( QgsPalLayerSettings::MultiLineWrapChar ) )
|
|
{
|
|
tmpLyr.wrapChar = ddValues.value( QgsPalLayerSettings::MultiLineWrapChar ).toString();
|
|
}
|
|
|
|
if ( ddValues.contains( QgsPalLayerSettings::AutoWrapLength ) )
|
|
{
|
|
tmpLyr.autoWrapLength = ddValues.value( QgsPalLayerSettings::AutoWrapLength ).toInt();
|
|
}
|
|
|
|
if ( ddValues.contains( QgsPalLayerSettings::MultiLineHeight ) )
|
|
{
|
|
QgsTextFormat format = tmpLyr.format();
|
|
format.setLineHeight( ddValues.value( QgsPalLayerSettings::MultiLineHeight ).toDouble() );
|
|
tmpLyr.setFormat( format );
|
|
}
|
|
|
|
if ( ddValues.contains( QgsPalLayerSettings::MultiLineAlignment ) )
|
|
{
|
|
tmpLyr.multilineAlign = static_cast< Qgis::LabelMultiLineAlignment >( ddValues.value( QgsPalLayerSettings::MultiLineAlignment ).toInt() );
|
|
}
|
|
|
|
if ( ddValues.contains( QgsPalLayerSettings::TextOrientation ) )
|
|
{
|
|
QgsTextFormat format = tmpLyr.format();
|
|
format.setOrientation( QgsTextRendererUtils::decodeTextOrientation( ddValues.value( QgsPalLayerSettings::TextOrientation ).toString() ) );
|
|
tmpLyr.setFormat( format );
|
|
}
|
|
|
|
if ( ddValues.contains( QgsPalLayerSettings::DirSymbDraw ) )
|
|
{
|
|
tmpLyr.lineSettings().setAddDirectionSymbol( ddValues.value( QgsPalLayerSettings::DirSymbDraw ).toBool() );
|
|
}
|
|
|
|
if ( tmpLyr.lineSettings().addDirectionSymbol() )
|
|
{
|
|
|
|
if ( ddValues.contains( QgsPalLayerSettings::DirSymbLeft ) )
|
|
{
|
|
tmpLyr.lineSettings().setLeftDirectionSymbol( ddValues.value( QgsPalLayerSettings::DirSymbLeft ).toString() );
|
|
}
|
|
if ( ddValues.contains( QgsPalLayerSettings::DirSymbRight ) )
|
|
{
|
|
tmpLyr.lineSettings().setRightDirectionSymbol( ddValues.value( QgsPalLayerSettings::DirSymbRight ).toString() );
|
|
}
|
|
|
|
if ( ddValues.contains( QgsPalLayerSettings::DirSymbPlacement ) )
|
|
{
|
|
tmpLyr.lineSettings().setDirectionSymbolPlacement( static_cast< QgsLabelLineSettings::DirectionSymbolPlacement >( ddValues.value( QgsPalLayerSettings::DirSymbPlacement ).toInt() ) );
|
|
}
|
|
|
|
if ( ddValues.contains( QgsPalLayerSettings::DirSymbReverse ) )
|
|
{
|
|
tmpLyr.lineSettings().setReverseDirectionSymbol( ddValues.value( QgsPalLayerSettings::DirSymbReverse ).toBool() );
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
void QgsPalLabeling::dataDefinedTextBuffer( QgsPalLayerSettings &tmpLyr,
|
|
const QMap< QgsPalLayerSettings::Property, QVariant > &ddValues )
|
|
{
|
|
QgsTextBufferSettings buffer = tmpLyr.format().buffer();
|
|
bool changed = false;
|
|
|
|
//buffer draw
|
|
if ( ddValues.contains( QgsPalLayerSettings::BufferDraw ) )
|
|
{
|
|
buffer.setEnabled( ddValues.value( QgsPalLayerSettings::BufferDraw ).toBool() );
|
|
changed = true;
|
|
}
|
|
|
|
if ( !buffer.enabled() )
|
|
{
|
|
if ( changed )
|
|
{
|
|
QgsTextFormat format = tmpLyr.format();
|
|
format.setBuffer( buffer );
|
|
tmpLyr.setFormat( format );
|
|
}
|
|
|
|
// tmpLyr.bufferSize > 0.0 && tmpLyr.bufferTransp < 100 figured in during evaluation
|
|
return; // don't continue looking for unused values
|
|
}
|
|
|
|
//buffer size
|
|
if ( ddValues.contains( QgsPalLayerSettings::BufferSize ) )
|
|
{
|
|
buffer.setSize( ddValues.value( QgsPalLayerSettings::BufferSize ).toDouble() );
|
|
changed = true;
|
|
}
|
|
|
|
//buffer opacity
|
|
if ( ddValues.contains( QgsPalLayerSettings::BufferOpacity ) )
|
|
{
|
|
buffer.setOpacity( ddValues.value( QgsPalLayerSettings::BufferOpacity ).toDouble() / 100.0 );
|
|
changed = true;
|
|
}
|
|
|
|
//buffer size units
|
|
if ( ddValues.contains( QgsPalLayerSettings::BufferUnit ) )
|
|
{
|
|
Qgis::RenderUnit bufunit = static_cast< Qgis::RenderUnit >( ddValues.value( QgsPalLayerSettings::BufferUnit ).toInt() );
|
|
buffer.setSizeUnit( bufunit );
|
|
changed = true;
|
|
}
|
|
|
|
//buffer color
|
|
if ( ddValues.contains( QgsPalLayerSettings::BufferColor ) )
|
|
{
|
|
QVariant ddColor = ddValues.value( QgsPalLayerSettings::BufferColor );
|
|
buffer.setColor( ddColor.value<QColor>() );
|
|
changed = true;
|
|
}
|
|
|
|
//buffer pen join style
|
|
if ( ddValues.contains( QgsPalLayerSettings::BufferJoinStyle ) )
|
|
{
|
|
buffer.setJoinStyle( static_cast< Qt::PenJoinStyle >( ddValues.value( QgsPalLayerSettings::BufferJoinStyle ).toInt() ) );
|
|
changed = true;
|
|
}
|
|
|
|
//buffer blend mode
|
|
if ( ddValues.contains( QgsPalLayerSettings::BufferBlendMode ) )
|
|
{
|
|
buffer.setBlendMode( static_cast< QPainter::CompositionMode >( ddValues.value( QgsPalLayerSettings::BufferBlendMode ).toInt() ) );
|
|
changed = true;
|
|
}
|
|
|
|
if ( changed )
|
|
{
|
|
QgsTextFormat format = tmpLyr.format();
|
|
format.setBuffer( buffer );
|
|
tmpLyr.setFormat( format );
|
|
}
|
|
}
|
|
|
|
void QgsPalLabeling::dataDefinedTextMask( QgsPalLayerSettings &tmpLyr,
|
|
const QMap< QgsPalLayerSettings::Property, QVariant > &ddValues )
|
|
{
|
|
if ( ddValues.isEmpty() )
|
|
return;
|
|
|
|
QgsTextMaskSettings mask = tmpLyr.format().mask();
|
|
bool changed = false;
|
|
|
|
// enabled ?
|
|
if ( ddValues.contains( QgsPalLayerSettings::MaskEnabled ) )
|
|
{
|
|
mask.setEnabled( ddValues.value( QgsPalLayerSettings::MaskEnabled ).toBool() );
|
|
changed = true;
|
|
}
|
|
|
|
if ( !mask.enabled() )
|
|
{
|
|
if ( changed )
|
|
{
|
|
QgsTextFormat format = tmpLyr.format();
|
|
format.setMask( mask );
|
|
tmpLyr.setFormat( format );
|
|
}
|
|
|
|
// tmpLyr.bufferSize > 0.0 && tmpLyr.bufferTransp < 100 figured in during evaluation
|
|
return; // don't continue looking for unused values
|
|
}
|
|
|
|
// buffer size
|
|
if ( ddValues.contains( QgsPalLayerSettings::MaskBufferSize ) )
|
|
{
|
|
mask.setSize( ddValues.value( QgsPalLayerSettings::MaskBufferSize ).toDouble() );
|
|
changed = true;
|
|
}
|
|
|
|
// opacity
|
|
if ( ddValues.contains( QgsPalLayerSettings::MaskOpacity ) )
|
|
{
|
|
mask.setOpacity( ddValues.value( QgsPalLayerSettings::MaskOpacity ).toDouble() / 100.0 );
|
|
changed = true;
|
|
}
|
|
|
|
// buffer size units
|
|
if ( ddValues.contains( QgsPalLayerSettings::MaskBufferUnit ) )
|
|
{
|
|
Qgis::RenderUnit bufunit = static_cast< Qgis::RenderUnit >( ddValues.value( QgsPalLayerSettings::MaskBufferUnit ).toInt() );
|
|
mask.setSizeUnit( bufunit );
|
|
changed = true;
|
|
}
|
|
|
|
// pen join style
|
|
if ( ddValues.contains( QgsPalLayerSettings::MaskJoinStyle ) )
|
|
{
|
|
mask.setJoinStyle( static_cast< Qt::PenJoinStyle >( ddValues.value( QgsPalLayerSettings::MaskJoinStyle ).toInt() ) );
|
|
changed = true;
|
|
}
|
|
|
|
if ( changed )
|
|
{
|
|
QgsTextFormat format = tmpLyr.format();
|
|
format.setMask( mask );
|
|
tmpLyr.setFormat( format );
|
|
}
|
|
}
|
|
|
|
void QgsPalLabeling::dataDefinedShapeBackground( QgsPalLayerSettings &tmpLyr,
|
|
const QMap< QgsPalLayerSettings::Property, QVariant > &ddValues )
|
|
{
|
|
QgsTextBackgroundSettings background = tmpLyr.format().background();
|
|
bool changed = false;
|
|
|
|
//shape draw
|
|
if ( ddValues.contains( QgsPalLayerSettings::ShapeDraw ) )
|
|
{
|
|
background.setEnabled( ddValues.value( QgsPalLayerSettings::ShapeDraw ).toBool() );
|
|
changed = true;
|
|
}
|
|
|
|
if ( !background.enabled() )
|
|
{
|
|
if ( changed )
|
|
{
|
|
QgsTextFormat format = tmpLyr.format();
|
|
format.setBackground( background );
|
|
tmpLyr.setFormat( format );
|
|
}
|
|
return; // don't continue looking for unused values
|
|
}
|
|
|
|
if ( ddValues.contains( QgsPalLayerSettings::ShapeKind ) )
|
|
{
|
|
background.setType( static_cast< QgsTextBackgroundSettings::ShapeType >( ddValues.value( QgsPalLayerSettings::ShapeKind ).toInt() ) );
|
|
changed = true;
|
|
}
|
|
|
|
if ( ddValues.contains( QgsPalLayerSettings::ShapeSVGFile ) )
|
|
{
|
|
background.setSvgFile( ddValues.value( QgsPalLayerSettings::ShapeSVGFile ).toString() );
|
|
changed = true;
|
|
}
|
|
|
|
if ( ddValues.contains( QgsPalLayerSettings::ShapeSizeType ) )
|
|
{
|
|
background.setSizeType( static_cast< QgsTextBackgroundSettings::SizeType >( ddValues.value( QgsPalLayerSettings::ShapeSizeType ).toInt() ) );
|
|
changed = true;
|
|
}
|
|
|
|
if ( ddValues.contains( QgsPalLayerSettings::ShapeSizeX ) )
|
|
{
|
|
QSizeF size = background.size();
|
|
size.setWidth( ddValues.value( QgsPalLayerSettings::ShapeSizeX ).toDouble() );
|
|
background.setSize( size );
|
|
changed = true;
|
|
}
|
|
if ( ddValues.contains( QgsPalLayerSettings::ShapeSizeY ) )
|
|
{
|
|
QSizeF size = background.size();
|
|
size.setHeight( ddValues.value( QgsPalLayerSettings::ShapeSizeY ).toDouble() );
|
|
background.setSize( size );
|
|
changed = true;
|
|
}
|
|
|
|
if ( ddValues.contains( QgsPalLayerSettings::ShapeSizeUnits ) )
|
|
{
|
|
background.setSizeUnit( static_cast< Qgis::RenderUnit >( ddValues.value( QgsPalLayerSettings::ShapeSizeUnits ).toInt() ) );
|
|
changed = true;
|
|
}
|
|
|
|
if ( ddValues.contains( QgsPalLayerSettings::ShapeRotationType ) )
|
|
{
|
|
background.setRotationType( static_cast< QgsTextBackgroundSettings::RotationType >( ddValues.value( QgsPalLayerSettings::ShapeRotationType ).toInt() ) );
|
|
changed = true;
|
|
}
|
|
|
|
if ( ddValues.contains( QgsPalLayerSettings::ShapeRotation ) )
|
|
{
|
|
background.setRotation( ddValues.value( QgsPalLayerSettings::ShapeRotation ).toDouble() );
|
|
changed = true;
|
|
}
|
|
|
|
if ( ddValues.contains( QgsPalLayerSettings::ShapeOffset ) )
|
|
{
|
|
background.setOffset( ddValues.value( QgsPalLayerSettings::ShapeOffset ).toPointF() );
|
|
changed = true;
|
|
}
|
|
|
|
if ( ddValues.contains( QgsPalLayerSettings::ShapeOffsetUnits ) )
|
|
{
|
|
background.setOffsetUnit( static_cast< Qgis::RenderUnit >( ddValues.value( QgsPalLayerSettings::ShapeOffsetUnits ).toInt() ) );
|
|
changed = true;
|
|
}
|
|
|
|
if ( ddValues.contains( QgsPalLayerSettings::ShapeRadii ) )
|
|
{
|
|
background.setRadii( ddValues.value( QgsPalLayerSettings::ShapeRadii ).toSizeF() );
|
|
changed = true;
|
|
}
|
|
|
|
if ( ddValues.contains( QgsPalLayerSettings::ShapeRadiiUnits ) )
|
|
{
|
|
background.setRadiiUnit( static_cast< Qgis::RenderUnit >( ddValues.value( QgsPalLayerSettings::ShapeRadiiUnits ).toInt() ) );
|
|
changed = true;
|
|
}
|
|
|
|
if ( ddValues.contains( QgsPalLayerSettings::ShapeBlendMode ) )
|
|
{
|
|
background.setBlendMode( static_cast< QPainter::CompositionMode >( ddValues.value( QgsPalLayerSettings::ShapeBlendMode ).toInt() ) );
|
|
changed = true;
|
|
}
|
|
|
|
if ( ddValues.contains( QgsPalLayerSettings::ShapeFillColor ) )
|
|
{
|
|
QVariant ddColor = ddValues.value( QgsPalLayerSettings::ShapeFillColor );
|
|
background.setFillColor( ddColor.value<QColor>() );
|
|
changed = true;
|
|
}
|
|
|
|
if ( ddValues.contains( QgsPalLayerSettings::ShapeStrokeColor ) )
|
|
{
|
|
QVariant ddColor = ddValues.value( QgsPalLayerSettings::ShapeStrokeColor );
|
|
background.setStrokeColor( ddColor.value<QColor>() );
|
|
changed = true;
|
|
}
|
|
|
|
if ( ddValues.contains( QgsPalLayerSettings::ShapeOpacity ) )
|
|
{
|
|
background.setOpacity( ddValues.value( QgsPalLayerSettings::ShapeOpacity ).toDouble() / 100.0 );
|
|
changed = true;
|
|
}
|
|
|
|
if ( ddValues.contains( QgsPalLayerSettings::ShapeStrokeWidth ) )
|
|
{
|
|
background.setStrokeWidth( ddValues.value( QgsPalLayerSettings::ShapeStrokeWidth ).toDouble() );
|
|
changed = true;
|
|
}
|
|
|
|
if ( ddValues.contains( QgsPalLayerSettings::ShapeStrokeWidthUnits ) )
|
|
{
|
|
background.setStrokeWidthUnit( static_cast< Qgis::RenderUnit >( ddValues.value( QgsPalLayerSettings::ShapeStrokeWidthUnits ).toInt() ) );
|
|
changed = true;
|
|
}
|
|
|
|
if ( ddValues.contains( QgsPalLayerSettings::ShapeJoinStyle ) )
|
|
{
|
|
background.setJoinStyle( static_cast< Qt::PenJoinStyle >( ddValues.value( QgsPalLayerSettings::ShapeJoinStyle ).toInt() ) );
|
|
changed = true;
|
|
}
|
|
|
|
if ( changed )
|
|
{
|
|
QgsTextFormat format = tmpLyr.format();
|
|
format.setBackground( background );
|
|
tmpLyr.setFormat( format );
|
|
}
|
|
}
|
|
|
|
void QgsPalLabeling::dataDefinedDropShadow( QgsPalLayerSettings &tmpLyr,
|
|
const QMap< QgsPalLayerSettings::Property, QVariant > &ddValues )
|
|
{
|
|
QgsTextShadowSettings shadow = tmpLyr.format().shadow();
|
|
bool changed = false;
|
|
|
|
//shadow draw
|
|
if ( ddValues.contains( QgsPalLayerSettings::ShadowDraw ) )
|
|
{
|
|
shadow.setEnabled( ddValues.value( QgsPalLayerSettings::ShadowDraw ).toBool() );
|
|
changed = true;
|
|
}
|
|
|
|
if ( !shadow.enabled() )
|
|
{
|
|
if ( changed )
|
|
{
|
|
QgsTextFormat format = tmpLyr.format();
|
|
format.setShadow( shadow );
|
|
tmpLyr.setFormat( format );
|
|
}
|
|
return; // don't continue looking for unused values
|
|
}
|
|
|
|
if ( ddValues.contains( QgsPalLayerSettings::ShadowUnder ) )
|
|
{
|
|
shadow.setShadowPlacement( static_cast< QgsTextShadowSettings::ShadowPlacement >( ddValues.value( QgsPalLayerSettings::ShadowUnder ).toInt() ) );
|
|
changed = true;
|
|
}
|
|
|
|
if ( ddValues.contains( QgsPalLayerSettings::ShadowOffsetAngle ) )
|
|
{
|
|
shadow.setOffsetAngle( ddValues.value( QgsPalLayerSettings::ShadowOffsetAngle ).toInt() );
|
|
changed = true;
|
|
}
|
|
|
|
if ( ddValues.contains( QgsPalLayerSettings::ShadowOffsetDist ) )
|
|
{
|
|
shadow.setOffsetDistance( ddValues.value( QgsPalLayerSettings::ShadowOffsetDist ).toDouble() );
|
|
changed = true;
|
|
}
|
|
|
|
if ( ddValues.contains( QgsPalLayerSettings::ShadowOffsetUnits ) )
|
|
{
|
|
shadow.setOffsetUnit( static_cast< Qgis::RenderUnit >( ddValues.value( QgsPalLayerSettings::ShadowOffsetUnits ).toInt() ) );
|
|
changed = true;
|
|
}
|
|
|
|
if ( ddValues.contains( QgsPalLayerSettings::ShadowRadius ) )
|
|
{
|
|
shadow.setBlurRadius( ddValues.value( QgsPalLayerSettings::ShadowRadius ).toDouble() );
|
|
changed = true;
|
|
}
|
|
|
|
if ( ddValues.contains( QgsPalLayerSettings::ShadowRadiusUnits ) )
|
|
{
|
|
shadow.setBlurRadiusUnit( static_cast< Qgis::RenderUnit >( ddValues.value( QgsPalLayerSettings::ShadowRadiusUnits ).toInt() ) );
|
|
changed = true;
|
|
}
|
|
|
|
if ( ddValues.contains( QgsPalLayerSettings::ShadowColor ) )
|
|
{
|
|
QVariant ddColor = ddValues.value( QgsPalLayerSettings::ShadowColor );
|
|
shadow.setColor( ddColor.value<QColor>() );
|
|
changed = true;
|
|
}
|
|
|
|
if ( ddValues.contains( QgsPalLayerSettings::ShadowOpacity ) )
|
|
{
|
|
shadow.setOpacity( ddValues.value( QgsPalLayerSettings::ShadowOpacity ).toDouble() / 100.0 );
|
|
changed = true;
|
|
}
|
|
|
|
if ( ddValues.contains( QgsPalLayerSettings::ShadowScale ) )
|
|
{
|
|
shadow.setScale( ddValues.value( QgsPalLayerSettings::ShadowScale ).toInt() );
|
|
changed = true;
|
|
}
|
|
|
|
|
|
if ( ddValues.contains( QgsPalLayerSettings::ShadowBlendMode ) )
|
|
{
|
|
shadow.setBlendMode( static_cast< QPainter::CompositionMode >( ddValues.value( QgsPalLayerSettings::ShadowBlendMode ).toInt() ) );
|
|
changed = true;
|
|
}
|
|
|
|
if ( changed )
|
|
{
|
|
QgsTextFormat format = tmpLyr.format();
|
|
format.setShadow( shadow );
|
|
tmpLyr.setFormat( format );
|
|
}
|
|
}
|
|
|
|
void QgsPalLabeling::drawLabelCandidateRect( pal::LabelPosition *lp, QPainter *painter, const QgsMapToPixel *xform, QList<QgsLabelCandidate> *candidates )
|
|
{
|
|
QgsPointXY outPt = xform->transform( lp->getX(), lp->getY() );
|
|
|
|
painter->save();
|
|
|
|
#if 0 // TODO: generalize some of this
|
|
double w = lp->getWidth();
|
|
double h = lp->getHeight();
|
|
double cx = lp->getX() + w / 2.0;
|
|
double cy = lp->getY() + h / 2.0;
|
|
double scale = 1.0 / xform->mapUnitsPerPixel();
|
|
double rotation = xform->mapRotation();
|
|
double sw = w * scale;
|
|
double sh = h * scale;
|
|
QRectF rect( -sw / 2, -sh / 2, sw, sh );
|
|
|
|
painter->translate( xform->transform( QPointF( cx, cy ) ).toQPointF() );
|
|
if ( rotation )
|
|
{
|
|
// Only if not horizontal
|
|
if ( lp->getFeaturePart()->getLayer()->getArrangement() != P_POINT &&
|
|
lp->getFeaturePart()->getLayer()->getArrangement() != P_POINT_OVER &&
|
|
lp->getFeaturePart()->getLayer()->getArrangement() != P_HORIZ )
|
|
{
|
|
painter->rotate( rotation );
|
|
}
|
|
}
|
|
painter->translate( rect.bottomLeft() );
|
|
painter->rotate( -lp->getAlpha() * 180 / M_PI );
|
|
painter->translate( -rect.bottomLeft() );
|
|
#else
|
|
QgsPointXY outPt2 = xform->transform( lp->getX() + lp->getWidth(), lp->getY() + lp->getHeight() );
|
|
QRectF rect( 0, 0, outPt2.x() - outPt.x(), outPt2.y() - outPt.y() );
|
|
painter->translate( QPointF( outPt.x(), outPt.y() ) );
|
|
painter->rotate( -lp->getAlpha() * 180 / M_PI );
|
|
#endif
|
|
|
|
if ( lp->conflictsWithObstacle() )
|
|
{
|
|
painter->setPen( QColor( 255, 0, 0, 64 ) );
|
|
}
|
|
else
|
|
{
|
|
painter->setPen( QColor( 0, 0, 0, 64 ) );
|
|
}
|
|
painter->drawRect( rect );
|
|
painter->restore();
|
|
|
|
// save the rect
|
|
rect.moveTo( outPt.x(), outPt.y() );
|
|
if ( candidates )
|
|
candidates->append( QgsLabelCandidate( rect, lp->cost() * 1000 ) );
|
|
|
|
// show all parts of the multipart label
|
|
if ( lp->nextPart() )
|
|
drawLabelCandidateRect( lp->nextPart(), painter, xform, candidates );
|
|
}
|