Permalink
Switch branches/tags
Find file
Fetching contributors…
Cannot retrieve contributors at this time
3477 lines (3031 sloc) 144 KB
/***************************************************************************
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 <list>
#include <pal/pal.h>
#include <pal/feature.h>
#include <pal/layer.h>
#include <pal/palexception.h>
#include <pal/problem.h>
#include <pal/labelposition.h>
#include <cmath>
#include <QApplication>
#include <QByteArray>
#include <QString>
#include <QFontMetrics>
#include <QTime>
#include <QPainter>
#include "diagram/qgsdiagram.h"
#include "qgsdiagramrenderer.h"
#include "qgsfontutils.h"
#include "qgslabelsearchtree.h"
#include "qgsexpression.h"
#include "qgslabelingengine.h"
#include "qgsvectorlayerlabeling.h"
#include <qgslogger.h>
#include <qgsvectorlayer.h>
#include <qgsvectordataprovider.h>
#include <qgsvectorlayerdiagramprovider.h>
#include <qgsvectorlayerlabelprovider.h>
#include <qgsgeometry.h>
#include <qgsmarkersymbollayer.h>
#include <qgspainting.h>
#include <qgsproject.h>
#include "qgsproperty.h"
#include "qgssymbollayerutils.h"
#include "qgsmaptopixelgeometrysimplifier.h"
#include <QMessageBox>
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...!)
*/
const QVector< QgsPalLayerSettings::PredefinedPointPosition > QgsPalLayerSettings::DEFAULT_PLACEMENT_ORDER
{
QgsPalLayerSettings::TopRight,
QgsPalLayerSettings::TopLeft,
QgsPalLayerSettings::BottomRight,
QgsPalLayerSettings::BottomLeft,
QgsPalLayerSettings::MiddleRight,
QgsPalLayerSettings::MiddleLeft,
QgsPalLayerSettings::TopSlightlyRight,
QgsPalLayerSettings::BottomSlightlyRight
};
//debugging only - don't use these placements by default
/* << QgsPalLayerSettings::TopSlightlyLeft
<< QgsPalLayerSettings::BottomSlightlyLeft;
<< QgsPalLayerSettings::TopMiddle
<< QgsPalLayerSettings::BottomMiddle;*/
QgsPropertiesDefinition QgsPalLayerSettings::sPropertyDefinitions;
void QgsPalLayerSettings::initPropertyDefinitions()
{
if ( !sPropertyDefinitions.isEmpty() )
return;
sPropertyDefinitions = QgsPropertiesDefinition
{
{ QgsPalLayerSettings::Size, QgsPropertyDefinition( "Size", QObject::tr( "Font size" ), QgsPropertyDefinition::DoublePositive ) },
{ QgsPalLayerSettings::Bold, QgsPropertyDefinition( "Bold", QObject::tr( "Bold style" ), QgsPropertyDefinition::Boolean ) },
{ QgsPalLayerSettings::Italic, QgsPropertyDefinition( "Italic", QObject::tr( "Italic style" ), QgsPropertyDefinition::Boolean ) },
{ QgsPalLayerSettings::Underline, QgsPropertyDefinition( "Underline", QObject::tr( "Draw underline" ), QgsPropertyDefinition::Boolean ) },
{ QgsPalLayerSettings::Color, QgsPropertyDefinition( "Color", QObject::tr( "Text color" ), QgsPropertyDefinition::ColorNoAlpha ) },
{ QgsPalLayerSettings::Strikeout, QgsPropertyDefinition( "Strikeout", QObject::tr( "Draw strikeout" ), QgsPropertyDefinition::Boolean ) },
{
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]" ) )
},
{
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" ) )
},
{ QgsPalLayerSettings::FontSizeUnit, QgsPropertyDefinition( "FontSizeUnit", QObject::tr( "Font size units" ), QgsPropertyDefinition::RenderUnits ) },
{ QgsPalLayerSettings::FontTransp, QgsPropertyDefinition( "FontTransp", QObject::tr( "Text transparency" ), QgsPropertyDefinition::Opacity ) },
{ QgsPalLayerSettings::FontOpacity, QgsPropertyDefinition( "FontOpacity", QObject::tr( "Text opacity" ), QgsPropertyDefinition::Opacity ) },
{ 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>Capitalize</b>]" ) ) },
{ QgsPalLayerSettings::FontLetterSpacing, QgsPropertyDefinition( "FontLetterSpacing", QObject::tr( "Letter spacing" ), QgsPropertyDefinition::Double ) },
{ QgsPalLayerSettings::FontWordSpacing, QgsPropertyDefinition( "FontWordSpacing", QObject::tr( "Word spacing" ), QgsPropertyDefinition::Double ) },
{ QgsPalLayerSettings::FontBlendMode, QgsPropertyDefinition( "FontBlendMode", QObject::tr( "Text blend mode" ), QgsPropertyDefinition::BlendMode ) },
{ QgsPalLayerSettings::MultiLineWrapChar, QgsPropertyDefinition( "MultiLineWrapChar", QObject::tr( "Wrap character" ), QgsPropertyDefinition::String ) },
{ QgsPalLayerSettings::MultiLineHeight, QgsPropertyDefinition( "MultiLineHeight", QObject::tr( "Line height" ), QgsPropertyDefinition::DoublePositive ) },
{ 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>]" ) },
{ QgsPalLayerSettings::DirSymbDraw, QgsPropertyDefinition( "DirSymbDraw", QObject::tr( "Draw direction symbol" ), QgsPropertyDefinition::Boolean ) },
{ QgsPalLayerSettings::DirSymbLeft, QgsPropertyDefinition( "DirSymbLeft", QObject::tr( "Left direction symbol" ), QgsPropertyDefinition::String ) },
{ QgsPalLayerSettings::DirSymbRight, QgsPropertyDefinition( "DirSymbRight", QObject::tr( "Right direction symbol" ), QgsPropertyDefinition::String ) },
{ QgsPalLayerSettings::DirSymbPlacement, QgsPropertyDefinition( "DirSymbPlacement", QgsPropertyDefinition::DataTypeString, QObject::tr( "Direction symbol placement" ), QObject::tr( "string " ) + "[<b>LeftRight</b>|<b>Above</b>|<b>Below</b>]" ) },
{ QgsPalLayerSettings::DirSymbReverse, QgsPropertyDefinition( "DirSymbReverse", QObject::tr( "Reverse direction symbol" ), QgsPropertyDefinition::Boolean ) },
{ QgsPalLayerSettings::NumFormat, QgsPropertyDefinition( "NumFormat", QObject::tr( "Format as number" ), QgsPropertyDefinition::Boolean ) },
{ QgsPalLayerSettings::NumDecimals, QgsPropertyDefinition( "NumDecimals", QObject::tr( "Number of decimal places" ), QgsPropertyDefinition::IntegerPositive ) },
{ QgsPalLayerSettings::NumPlusSign, QgsPropertyDefinition( "NumPlusSign", QObject::tr( "Draw + sign" ), QgsPropertyDefinition::Boolean ) },
{ QgsPalLayerSettings::BufferDraw, QgsPropertyDefinition( "BufferDraw", QObject::tr( "Draw buffer" ), QgsPropertyDefinition::Boolean ) },
{ QgsPalLayerSettings::BufferSize, QgsPropertyDefinition( "BufferSize", QObject::tr( "Symbol size" ), QgsPropertyDefinition::DoublePositive ) },
{ QgsPalLayerSettings::BufferUnit, QgsPropertyDefinition( "BufferUnit", QObject::tr( "Buffer units" ), QgsPropertyDefinition::RenderUnits ) },
{ QgsPalLayerSettings::BufferColor, QgsPropertyDefinition( "BufferColor", QObject::tr( "Buffer color" ), QgsPropertyDefinition::ColorNoAlpha ) },
{ QgsPalLayerSettings::BufferTransp, QgsPropertyDefinition( "BufferTransp", QObject::tr( "Buffer transparency" ), QgsPropertyDefinition::Opacity ) },
{ QgsPalLayerSettings::BufferOpacity, QgsPropertyDefinition( "BufferOpacity", QObject::tr( "Buffer opacity" ), QgsPropertyDefinition::Opacity ) },
{ QgsPalLayerSettings::BufferJoinStyle, QgsPropertyDefinition( "BufferJoinStyle", QObject::tr( "Buffer join style" ), QgsPropertyDefinition::PenJoinStyle ) },
{ QgsPalLayerSettings::BufferBlendMode, QgsPropertyDefinition( "BufferBlendMode", QObject::tr( "Buffer blend mode" ), QgsPropertyDefinition::BlendMode ) },
{ QgsPalLayerSettings::ShapeDraw, QgsPropertyDefinition( "ShapeDraw", QObject::tr( "Draw shape" ), QgsPropertyDefinition::Boolean ) },
{
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>]" ) )
},
{ QgsPalLayerSettings::ShapeSVGFile, QgsPropertyDefinition( "ShapeSVGFile", QObject::tr( "Shape SVG path" ), QgsPropertyDefinition::SvgPath ) },
{ QgsPalLayerSettings::ShapeSizeType, QgsPropertyDefinition( "ShapeSizeType", QgsPropertyDefinition::DataTypeString, QObject::tr( "Shape size type" ), QObject::tr( "string " ) + "[<b>Buffer</b>|<b>Fixed</b>]" ) },
{ QgsPalLayerSettings::ShapeSizeX, QgsPropertyDefinition( "ShapeSizeX", QObject::tr( "Shape size (X)" ), QgsPropertyDefinition::Double ) },
{ QgsPalLayerSettings::ShapeSizeY, QgsPropertyDefinition( "ShapeSizeY", QObject::tr( "Shape size (Y)" ), QgsPropertyDefinition::Double ) },
{ QgsPalLayerSettings::ShapeSizeUnits, QgsPropertyDefinition( "ShapeSizeUnits", QObject::tr( "Shape size units" ), QgsPropertyDefinition::RenderUnits ) },
{ QgsPalLayerSettings::ShapeRotationType, QgsPropertyDefinition( "ShapeRotationType", QgsPropertyDefinition::DataTypeString, QObject::tr( "Shape rotation type" ), QObject::tr( "string " ) + "[<b>Sync</b>|<b>Offset</b>|<b>Fixed</b>]" ) },
{ QgsPalLayerSettings::ShapeRotation, QgsPropertyDefinition( "ShapeRotation", QObject::tr( "Shape rotation" ), QgsPropertyDefinition::Rotation ) },
{ QgsPalLayerSettings::ShapeOffset, QgsPropertyDefinition( "ShapeOffset", QObject::tr( "Shape offset" ), QgsPropertyDefinition::Offset ) },
{ QgsPalLayerSettings::ShapeOffsetUnits, QgsPropertyDefinition( "ShapeOffsetUnits", QObject::tr( "Shape offset units" ), QgsPropertyDefinition::RenderUnits ) },
{ QgsPalLayerSettings::ShapeRadii, QgsPropertyDefinition( "ShapeRadii", QObject::tr( "Shape radii" ), QgsPropertyDefinition::Size2D ) },
{ QgsPalLayerSettings::ShapeRadiiUnits, QgsPropertyDefinition( "ShapeRadiiUnits", QObject::tr( "Symbol radii units" ), QgsPropertyDefinition::RenderUnits ) },
{ QgsPalLayerSettings::ShapeTransparency, QgsPropertyDefinition( "ShapeTransparency", QObject::tr( "Shape transparency" ), QgsPropertyDefinition::Opacity ) },
{ QgsPalLayerSettings::ShapeOpacity, QgsPropertyDefinition( "ShapeOpacity", QObject::tr( "Shape opacity" ), QgsPropertyDefinition::Opacity ) },
{ QgsPalLayerSettings::ShapeBlendMode, QgsPropertyDefinition( "ShapeBlendMode", QObject::tr( "Shape blend mode" ), QgsPropertyDefinition::BlendMode ) },
{ QgsPalLayerSettings::ShapeFillColor, QgsPropertyDefinition( "ShapeFillColor", QObject::tr( "Shape fill color" ), QgsPropertyDefinition::ColorWithAlpha ) },
{ QgsPalLayerSettings::ShapeStrokeColor, QgsPropertyDefinition( "ShapeBorderColor", QObject::tr( "Shape stroke color" ), QgsPropertyDefinition::ColorWithAlpha ) },
{ QgsPalLayerSettings::ShapeStrokeWidth, QgsPropertyDefinition( "ShapeBorderWidth", QObject::tr( "Shape stroke width" ), QgsPropertyDefinition::StrokeWidth ) },
{ QgsPalLayerSettings::ShapeStrokeWidthUnits, QgsPropertyDefinition( "ShapeBorderWidthUnits", QObject::tr( "Shape stroke width units" ), QgsPropertyDefinition::RenderUnits ) },
{ QgsPalLayerSettings::ShapeJoinStyle, QgsPropertyDefinition( "ShapeJoinStyle", QObject::tr( "Shape join style" ), QgsPropertyDefinition::PenJoinStyle ) },
{ QgsPalLayerSettings::ShadowDraw, QgsPropertyDefinition( "ShadowDraw", QObject::tr( "Draw shadow" ), QgsPropertyDefinition::Boolean ) },
{
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>]" ) )
},
{ QgsPalLayerSettings::ShadowOffsetAngle, QgsPropertyDefinition( "ShadowOffsetAngle", QObject::tr( "Shadow offset angle" ), QgsPropertyDefinition::Rotation ) },
{ QgsPalLayerSettings::ShadowOffsetDist, QgsPropertyDefinition( "ShadowOffsetDist", QObject::tr( "Shadow offset distance" ), QgsPropertyDefinition::DoublePositive ) },
{ QgsPalLayerSettings::ShadowOffsetUnits, QgsPropertyDefinition( "ShadowOffsetUnits", QObject::tr( "Shadow offset units" ), QgsPropertyDefinition::RenderUnits ) },
{ QgsPalLayerSettings::ShadowRadius, QgsPropertyDefinition( "ShadowRadius", QObject::tr( "Shadow blur radius" ), QgsPropertyDefinition::DoublePositive ) },
{ QgsPalLayerSettings::ShadowRadiusUnits, QgsPropertyDefinition( "ShadowRadiusUnits", QObject::tr( "Shadow blur units" ), QgsPropertyDefinition::RenderUnits ) },
{ QgsPalLayerSettings::ShadowTransparency, QgsPropertyDefinition( "ShadowTransparency", QObject::tr( "Shadow transparency" ), QgsPropertyDefinition::Opacity ) },
{ QgsPalLayerSettings::ShadowOpacity, QgsPropertyDefinition( "ShadowOpacity", QObject::tr( "Shadow opacity" ), QgsPropertyDefinition::Opacity ) },
{ QgsPalLayerSettings::ShadowScale, QgsPropertyDefinition( "ShadowScale", QObject::tr( "Shadow scale" ), QgsPropertyDefinition::IntegerPositive ) },
{ QgsPalLayerSettings::ShadowColor, QgsPropertyDefinition( "ShadowColor", QObject::tr( "Shadow color" ), QgsPropertyDefinition::ColorNoAlpha ) },
{ QgsPalLayerSettings::ShadowBlendMode, QgsPropertyDefinition( "ShadowBlendMode", QObject::tr( "Shadow blend mode" ), QgsPropertyDefinition::BlendMode ) },
{ QgsPalLayerSettings::CentroidWhole, QgsPropertyDefinition( "CentroidWhole", QgsPropertyDefinition::DataTypeString, QObject::tr( "Centroid of whole shape" ), QObject::tr( "string " ) + "[<b>Visible</b>|<b>Whole</b>]" ) },
{
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]" ) )
},
{ QgsPalLayerSettings::OffsetXY, QgsPropertyDefinition( "OffsetXY", QObject::tr( "Offset" ), QgsPropertyDefinition::Offset ) },
{ QgsPalLayerSettings::OffsetUnits, QgsPropertyDefinition( "OffsetUnits", QObject::tr( "Offset units" ), QgsPropertyDefinition::RenderUnits ) },
{ QgsPalLayerSettings::LabelDistance, QgsPropertyDefinition( "LabelDistance", QObject::tr( "Label distance" ), QgsPropertyDefinition::DoublePositive ) },
{ QgsPalLayerSettings::DistanceUnits, QgsPropertyDefinition( "DistanceUnits", QObject::tr( "Label distance units" ), QgsPropertyDefinition::RenderUnits ) },
{ QgsPalLayerSettings::OffsetRotation, QgsPropertyDefinition( "OffsetRotation", QObject::tr( "Offset rotation" ), QgsPropertyDefinition::Rotation ) },
{ 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]" ) ) },
{ QgsPalLayerSettings::RepeatDistance, QgsPropertyDefinition( "RepeatDistance", QObject::tr( "Repeat distance" ), QgsPropertyDefinition::DoublePositive ) },
{ QgsPalLayerSettings::RepeatDistanceUnit, QgsPropertyDefinition( "RepeatDistanceUnit", QObject::tr( "Repeat distance unit" ), QgsPropertyDefinition::RenderUnits ) },
{ QgsPalLayerSettings::Priority, QgsPropertyDefinition( "Priority", QgsPropertyDefinition::DataTypeString, QObject::tr( "Label priority" ), QObject::tr( "double [0.0-10.0]" ) ) },
{ QgsPalLayerSettings::IsObstacle, QgsPropertyDefinition( "IsObstacle", QObject::tr( "Feature is a label obstacle" ), QgsPropertyDefinition::Boolean ) },
{ QgsPalLayerSettings::ObstacleFactor, QgsPropertyDefinition( "ObstacleFactor", QgsPropertyDefinition::DataTypeNumeric, QObject::tr( "Obstacle factor" ), QObject::tr( "double [0.0-10.0]" ) ) },
{
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]" ) )
},
{ QgsPalLayerSettings::PositionX, QgsPropertyDefinition( "PositionX", QObject::tr( "Position (X)" ), QgsPropertyDefinition::Double ) },
{ QgsPalLayerSettings::PositionY, QgsPropertyDefinition( "PositionY", QObject::tr( "Position (Y)" ), QgsPropertyDefinition::Double ) },
{ QgsPalLayerSettings::Hali, QgsPropertyDefinition( "Hali", QgsPropertyDefinition::DataTypeString, QObject::tr( "Horizontal alignment" ), QObject::tr( "string " ) + "[<b>Left</b>|<b>Center</b>|<b>Right</b>]" ) },
{
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>]" ) )
},
{ QgsPalLayerSettings::Rotation, QgsPropertyDefinition( "Rotation", QObject::tr( "Label rotation (deprecated)" ), QgsPropertyDefinition::Rotation ) },
{ QgsPalLayerSettings::LabelRotation, QgsPropertyDefinition( "LabelRotation", QObject::tr( "Label rotation" ), QgsPropertyDefinition::Rotation ) },
{ QgsPalLayerSettings::ScaleVisibility, QgsPropertyDefinition( "ScaleVisibility", QObject::tr( "Scale based visibility" ), QgsPropertyDefinition::Boolean ) },
{ QgsPalLayerSettings::MinScale, QgsPropertyDefinition( "MinScale", QObject::tr( "Minimum scale (denominator)" ), QgsPropertyDefinition::Double ) },
{ QgsPalLayerSettings::MaxScale, QgsPropertyDefinition( "MaxScale", QObject::tr( "Maximum scale (denominator)" ), QgsPropertyDefinition::Double ) },
{ QgsPalLayerSettings::MinimumScale, QgsPropertyDefinition( "MinimumScale", QObject::tr( "Minimum scale (denominator)" ), QgsPropertyDefinition::Double ) },
{ QgsPalLayerSettings::MaximumScale, QgsPropertyDefinition( "MaximumScale", QObject::tr( "Maximum scale (denominator)" ), QgsPropertyDefinition::Double ) },
{ QgsPalLayerSettings::FontLimitPixel, QgsPropertyDefinition( "FontLimitPixel", QObject::tr( "Limit font pixel size" ), QgsPropertyDefinition::Boolean ) },
{ QgsPalLayerSettings::FontMinPixel, QgsPropertyDefinition( "FontMinPixel", QObject::tr( "Minimum pixel size" ), QgsPropertyDefinition::IntegerPositive ) },
{ QgsPalLayerSettings::FontMaxPixel, QgsPropertyDefinition( "FontMaxPixel", QObject::tr( "Maximum pixel size" ), QgsPropertyDefinition::IntegerPositive ) },
{ QgsPalLayerSettings::ZIndex, QgsPropertyDefinition( "ZIndex", QObject::tr( "Label z-index" ), QgsPropertyDefinition::Double ) },
{ QgsPalLayerSettings::Show, QgsPropertyDefinition( "Show", QObject::tr( "Show label" ), QgsPropertyDefinition::Boolean ) },
{ QgsPalLayerSettings::AlwaysShow, QgsPropertyDefinition( "AlwaysShow", QObject::tr( "Always show label" ), QgsPropertyDefinition::Boolean ) },
};
}
QgsPalLayerSettings::QgsPalLayerSettings()
: upsidedownLabels( Upright )
, mCurFeat( nullptr )
, xform( nullptr )
, extentGeom( nullptr )
, mFeaturesToLabel( 0 )
, mFeatsSendingToPal( 0 )
, mFeatsRegPal( 0 )
, expression( nullptr )
{
initPropertyDefinitions();
drawLabels = true;
isExpression = false;
fieldIndex = 0;
previewBkgrdColor = Qt::white;
useSubstitutions = false;
// text formatting
wrapChar = QLatin1String( "" );
multilineAlign = MultiFollowPlacement;
addDirectionSymbol = false;
leftDirectionSymbol = QStringLiteral( "<" );
rightDirectionSymbol = QStringLiteral( ">" );
reverseDirectionSymbol = false;
placeDirectionSymbol = SymbolLeftRight;
formatNumbers = false;
decimals = 3;
plusSign = false;
// placement
placement = AroundPoint;
placementFlags = AboveLine | MapOrientation;
centroidWhole = false;
centroidInside = false;
predefinedPositionOrder = DEFAULT_PLACEMENT_ORDER;
fitInPolygonOnly = false;
quadOffset = QuadrantOver;
xOffset = 0;
yOffset = 0;
labelOffsetInMapUnits = true;
dist = 0;
distInMapUnits = false;
offsetType = FromPoint;
angleOffset = 0;
preserveRotation = true;
maxCurvedCharAngleIn = 25.0;
maxCurvedCharAngleOut = -25.0;
priority = 5;
repeatDistance = 0;
repeatDistanceUnit = MM;
// rendering
scaleVisibility = false;
maximumScale = 0.0;
minimumScale = 0.0;
fontLimitPixelSize = false;
fontMinPixelSize = 0; //trigger to turn it on by default for map unit labels
fontMaxPixelSize = 10000;
displayAll = false;
upsidedownLabels = Upright;
labelPerPart = false;
mergeLines = false;
minFeatureSize = 0.0;
limitNumLabels = false;
maxNumLabels = 2000;
obstacle = true;
obstacleFactor = 1.0;
obstacleType = PolygonInterior;
zIndex = 0.0;
}
QgsPalLayerSettings::QgsPalLayerSettings( const QgsPalLayerSettings &s )
: mCurFeat( nullptr )
, fieldIndex( 0 )
, xform( nullptr )
, extentGeom( nullptr )
, mFeaturesToLabel( 0 )
, mFeatsSendingToPal( 0 )
, mFeatsRegPal( 0 )
, mDataDefinedProperties( s.mDataDefinedProperties )
, expression( nullptr )
{
*this = s;
}
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;
previewBkgrdColor = s.previewBkgrdColor;
substitutions = s.substitutions;
useSubstitutions = s.useSubstitutions;
// text formatting
wrapChar = s.wrapChar;
multilineAlign = s.multilineAlign;
addDirectionSymbol = s.addDirectionSymbol;
leftDirectionSymbol = s.leftDirectionSymbol;
rightDirectionSymbol = s.rightDirectionSymbol;
reverseDirectionSymbol = s.reverseDirectionSymbol;
placeDirectionSymbol = s.placeDirectionSymbol;
formatNumbers = s.formatNumbers;
decimals = s.decimals;
plusSign = s.plusSign;
// placement
placement = s.placement;
placementFlags = s.placementFlags;
centroidWhole = s.centroidWhole;
centroidInside = s.centroidInside;
predefinedPositionOrder = s.predefinedPositionOrder;
fitInPolygonOnly = s.fitInPolygonOnly;
quadOffset = s.quadOffset;
xOffset = s.xOffset;
yOffset = s.yOffset;
labelOffsetInMapUnits = s.labelOffsetInMapUnits;
labelOffsetMapUnitScale = s.labelOffsetMapUnitScale;
dist = s.dist;
offsetType = s.offsetType;
distInMapUnits = s.distInMapUnits;
distMapUnitScale = s.distMapUnitScale;
angleOffset = s.angleOffset;
preserveRotation = s.preserveRotation;
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;
displayAll = s.displayAll;
upsidedownLabels = s.upsidedownLabels;
labelPerPart = s.labelPerPart;
mergeLines = s.mergeLines && !s.addDirectionSymbol;
minFeatureSize = s.minFeatureSize;
limitNumLabels = s.limitNumLabels;
maxNumLabels = s.maxNumLabels;
obstacle = s.obstacle;
obstacleFactor = s.obstacleFactor;
obstacleType = s.obstacleType;
zIndex = s.zIndex;
mFormat = s.mFormat;
mDataDefinedProperties = s.mDataDefinedProperties;
return *this;
}
QgsPalLayerSettings::~QgsPalLayerSettings()
{
// 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;
}
static QgsPalLayerSettings::SizeUnit _decodeUnits( const QString &str )
{
if ( str.compare( QLatin1String( "Point" ), Qt::CaseInsensitive ) == 0
|| str.compare( QLatin1String( "Points" ), Qt::CaseInsensitive ) == 0 ) return QgsPalLayerSettings::Points;
if ( str.compare( QLatin1String( "MapUnit" ), Qt::CaseInsensitive ) == 0
|| str.compare( QLatin1String( "MapUnits" ), Qt::CaseInsensitive ) == 0 ) return QgsPalLayerSettings::MapUnits;
if ( str.compare( QLatin1String( "Percent" ), Qt::CaseInsensitive ) == 0 ) return QgsPalLayerSettings::Percent;
return QgsPalLayerSettings::MM; // "MM"
}
static Qt::PenJoinStyle _decodePenJoinStyle( const QString &str )
{
if ( str.compare( QLatin1String( "Miter" ), Qt::CaseInsensitive ) == 0 ) return Qt::MiterJoin;
if ( str.compare( QLatin1String( "Round" ), Qt::CaseInsensitive ) == 0 ) return Qt::RoundJoin;
return Qt::BevelJoin; // "Bevel"
}
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 << QLatin1String( "" );
values << value; // all old-style values are only field names
newValue = values.join( QStringLiteral( "~~" ) );
}
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() == QgsWkbTypes::PointGeometry )
placement = 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() == QgsWkbTypes::PolygonGeometry )
placement = 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();
previewBkgrdColor = QColor( layer->customProperty( QStringLiteral( "labeling/previewBkgrdColor" ), QVariant( "#ffffff" ) ).toString() );
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();
multilineAlign = static_cast< MultiLineAlign >( layer->customProperty( QStringLiteral( "labeling/multilineAlign" ), QVariant( MultiFollowPlacement ) ).toUInt() );
addDirectionSymbol = layer->customProperty( QStringLiteral( "labeling/addDirectionSymbol" ) ).toBool();
leftDirectionSymbol = layer->customProperty( QStringLiteral( "labeling/leftDirectionSymbol" ), QVariant( "<" ) ).toString();
rightDirectionSymbol = layer->customProperty( QStringLiteral( "labeling/rightDirectionSymbol" ), QVariant( ">" ) ).toString();
reverseDirectionSymbol = layer->customProperty( QStringLiteral( "labeling/reverseDirectionSymbol" ) ).toBool();
placeDirectionSymbol = static_cast< DirectionSymbols >( layer->customProperty( QStringLiteral( "labeling/placeDirectionSymbol" ), QVariant( 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< Placement >( layer->customProperty( QStringLiteral( "labeling/placement" ) ).toInt() );
placementFlags = 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();
distInMapUnits = layer->customProperty( QStringLiteral( "labeling/distInMapUnits" ) ).toBool();
if ( layer->customProperty( QStringLiteral( "labeling/distMapUnitScale" ) ).toString().isEmpty() )
{
//fallback to older property
double oldMin = layer->customProperty( QStringLiteral( "labeling/distMapUnitMinScale" ), 0.0 ).toDouble();
distMapUnitScale.minScale = oldMin != 0 ? 1.0 / oldMin : 0;
double oldMax = layer->customProperty( QStringLiteral( "labeling/distMapUnitMaxScale" ), 0.0 ).toDouble();
distMapUnitScale.maxScale = oldMax != 0 ? 1.0 / oldMax : 0;
}
else
{
distMapUnitScale = QgsSymbolLayerUtils::decodeMapUnitScale( layer->customProperty( QStringLiteral( "labeling/distMapUnitScale" ) ).toString() );
}
offsetType = static_cast< OffsetType >( layer->customProperty( QStringLiteral( "labeling/offsetType" ), QVariant( FromPoint ) ).toUInt() );
quadOffset = static_cast< QuadrantPosition >( layer->customProperty( QStringLiteral( "labeling/quadOffset" ), QVariant( QuadrantOver ) ).toUInt() );
xOffset = layer->customProperty( QStringLiteral( "labeling/xOffset" ), QVariant( 0.0 ) ).toDouble();
yOffset = layer->customProperty( QStringLiteral( "labeling/yOffset" ), QVariant( 0.0 ) ).toDouble();
labelOffsetInMapUnits = layer->customProperty( QStringLiteral( "labeling/labelOffsetInMapUnits" ), QVariant( true ) ).toBool();
if ( layer->customProperty( QStringLiteral( "labeling/labelOffsetMapUnitScale" ) ).toString().isEmpty() )
{
//fallback to older property
double oldMin = layer->customProperty( QStringLiteral( "labeling/labelOffsetMapUnitMinScale" ), 0.0 ).toDouble();
labelOffsetMapUnitScale.minScale = oldMin != 0 ? 1.0 / oldMin : 0;
double oldMax = layer->customProperty( QStringLiteral( "labeling/labelOffsetMapUnitMaxScale" ), 0.0 ).toDouble();
labelOffsetMapUnitScale.maxScale = 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 = 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();
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();
repeatDistanceUnit = static_cast< SizeUnit >( layer->customProperty( QStringLiteral( "labeling/repeatDistanceUnit" ), QVariant( MM ) ).toUInt() );
if ( layer->customProperty( QStringLiteral( "labeling/repeatDistanceMapUnitScale" ) ).toString().isEmpty() )
{
//fallback to older property
double oldMin = layer->customProperty( QStringLiteral( "labeling/repeatDistanceMapUnitMinScale" ), 0.0 ).toDouble();
repeatDistanceMapUnitScale.minScale = oldMin != 0 ? 1.0 / oldMin : 0;
double oldMax = layer->customProperty( QStringLiteral( "labeling/repeatDistanceMapUnitMaxScale" ), 0.0 ).toDouble();
repeatDistanceMapUnitScale.maxScale = 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();
displayAll = layer->customProperty( QStringLiteral( "labeling/displayAll" ), QVariant( false ) ).toBool();
upsidedownLabels = static_cast< UpsideDownLabels >( layer->customProperty( QStringLiteral( "labeling/upsidedownLabels" ), QVariant( Upright ) ).toUInt() );
labelPerPart = layer->customProperty( QStringLiteral( "labeling/labelPerPart" ) ).toBool();
mergeLines = layer->customProperty( QStringLiteral( "labeling/mergeLines" ) ).toBool();
minFeatureSize = layer->customProperty( QStringLiteral( "labeling/minFeatureSize" ) ).toDouble();
limitNumLabels = layer->customProperty( QStringLiteral( "labeling/limitNumLabels" ), QVariant( false ) ).toBool();
maxNumLabels = layer->customProperty( QStringLiteral( "labeling/maxNumLabels" ), QVariant( 2000 ) ).toInt();
obstacle = layer->customProperty( QStringLiteral( "labeling/obstacle" ), QVariant( true ) ).toBool();
obstacleFactor = layer->customProperty( QStringLiteral( "labeling/obstacleFactor" ), QVariant( 1.0 ) ).toDouble();
obstacleType = static_cast< 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( 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 );
previewBkgrdColor = QColor( textStyleElem.attribute( QStringLiteral( "previewBkgrdColor" ), QStringLiteral( "#ffffff" ) ) );
substitutions.readXml( textStyleElem.firstChildElement( QStringLiteral( "substitutions" ) ) );
useSubstitutions = textStyleElem.attribute( QStringLiteral( "useSubstitutions" ) ).toInt();
// text formatting
QDomElement textFormatElem = elem.firstChildElement( QStringLiteral( "text-format" ) );
wrapChar = textFormatElem.attribute( QStringLiteral( "wrapChar" ) );
multilineAlign = static_cast< MultiLineAlign >( textFormatElem.attribute( QStringLiteral( "multilineAlign" ), QString::number( MultiFollowPlacement ) ).toUInt() );
addDirectionSymbol = textFormatElem.attribute( QStringLiteral( "addDirectionSymbol" ) ).toInt();
leftDirectionSymbol = textFormatElem.attribute( QStringLiteral( "leftDirectionSymbol" ), QStringLiteral( "<" ) );
rightDirectionSymbol = textFormatElem.attribute( QStringLiteral( "rightDirectionSymbol" ), QStringLiteral( ">" ) );
reverseDirectionSymbol = textFormatElem.attribute( QStringLiteral( "reverseDirectionSymbol" ) ).toInt();
placeDirectionSymbol = static_cast< DirectionSymbols >( textFormatElem.attribute( QStringLiteral( "placeDirectionSymbol" ), QString::number( 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< Placement >( placementElem.attribute( QStringLiteral( "placement" ) ).toInt() );
placementFlags = placementElem.attribute( QStringLiteral( "placementFlags" ) ).toUInt();
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();
distInMapUnits = placementElem.attribute( QStringLiteral( "distInMapUnits" ) ).toInt();
if ( !placementElem.hasAttribute( QStringLiteral( "distMapUnitScale" ) ) )
{
//fallback to older property
double oldMin = placementElem.attribute( QStringLiteral( "distMapUnitMinScale" ), QStringLiteral( "0" ) ).toDouble();
distMapUnitScale.minScale = oldMin != 0 ? 1.0 / oldMin : 0;
double oldMax = placementElem.attribute( QStringLiteral( "distMapUnitMaxScale" ), QStringLiteral( "0" ) ).toDouble();
distMapUnitScale.maxScale = oldMax != 0 ? 1.0 / oldMax : 0;
}
else
{
distMapUnitScale = QgsSymbolLayerUtils::decodeMapUnitScale( placementElem.attribute( QStringLiteral( "distMapUnitScale" ) ) );
}
offsetType = static_cast< OffsetType >( placementElem.attribute( QStringLiteral( "offsetType" ), QString::number( FromPoint ) ).toUInt() );
quadOffset = static_cast< QuadrantPosition >( placementElem.attribute( QStringLiteral( "quadOffset" ), QString::number( QuadrantOver ) ).toUInt() );
xOffset = placementElem.attribute( QStringLiteral( "xOffset" ), QStringLiteral( "0" ) ).toDouble();
yOffset = placementElem.attribute( QStringLiteral( "yOffset" ), QStringLiteral( "0" ) ).toDouble();
labelOffsetInMapUnits = placementElem.attribute( QStringLiteral( "labelOffsetInMapUnits" ), QStringLiteral( "1" ) ).toInt();
if ( !placementElem.hasAttribute( QStringLiteral( "labelOffsetMapUnitScale" ) ) )
{
//fallback to older property
double oldMin = placementElem.attribute( QStringLiteral( "labelOffsetMapUnitMinScale" ), QStringLiteral( "0" ) ).toDouble();
labelOffsetMapUnitScale.minScale = oldMin != 0 ? 1.0 / oldMin : 0;
double oldMax = placementElem.attribute( QStringLiteral( "labelOffsetMapUnitMaxScale" ), QStringLiteral( "0" ) ).toDouble();
labelOffsetMapUnitScale.maxScale = oldMax != 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 = fmod( 360 - oldAngle, 360.0 );
}
else
{
angleOffset = placementElem.attribute( QStringLiteral( "rotationAngle" ), QStringLiteral( "0" ) ).toDouble();
}
preserveRotation = placementElem.attribute( QStringLiteral( "preserveRotation" ), QStringLiteral( "1" ) ).toInt();
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();
repeatDistanceUnit = static_cast< SizeUnit >( placementElem.attribute( QStringLiteral( "repeatDistanceUnit" ), QString::number( MM ) ).toUInt() );
if ( !placementElem.hasAttribute( QStringLiteral( "repeatDistanceMapUnitScale" ) ) )
{
//fallback to older property
double oldMin = placementElem.attribute( QStringLiteral( "repeatDistanceMapUnitMinScale" ), QStringLiteral( "0" ) ).toDouble();
repeatDistanceMapUnitScale.minScale = oldMin != 0 ? 1.0 / oldMin : 0;
double oldMax = placementElem.attribute( QStringLiteral( "repeatDistanceMapUnitMaxScale" ), QStringLiteral( "0" ) ).toDouble();
repeatDistanceMapUnitScale.maxScale = oldMax != 0 ? 1.0 / oldMax : 0;
}
else
{
repeatDistanceMapUnitScale = QgsSymbolLayerUtils::decodeMapUnitScale( placementElem.attribute( QStringLiteral( "repeatDistanceMapUnitScale" ) ) );
}
// 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();
displayAll = renderingElem.attribute( QStringLiteral( "displayAll" ), QStringLiteral( "0" ) ).toInt();
upsidedownLabels = static_cast< UpsideDownLabels >( renderingElem.attribute( QStringLiteral( "upsidedownLabels" ), QString::number( Upright ) ).toUInt() );
labelPerPart = renderingElem.attribute( QStringLiteral( "labelPerPart" ) ).toInt();
mergeLines = renderingElem.attribute( QStringLiteral( "mergeLines" ) ).toInt();
minFeatureSize = renderingElem.attribute( QStringLiteral( "minFeatureSize" ) ).toDouble();
limitNumLabels = renderingElem.attribute( QStringLiteral( "limitNumLabels" ), QStringLiteral( "0" ) ).toInt();
maxNumLabels = renderingElem.attribute( QStringLiteral( "maxNumLabels" ), QStringLiteral( "2000" ) ).toInt();
obstacle = renderingElem.attribute( QStringLiteral( "obstacle" ), QStringLiteral( "1" ) ).toInt();
obstacleFactor = renderingElem.attribute( QStringLiteral( "obstacleFactor" ), QStringLiteral( "1" ) ).toDouble();
obstacleType = static_cast< ObstacleType >( renderingElem.attribute( QStringLiteral( "obstacleType" ), QString::number( PolygonInterior ) ).toUInt() );
zIndex = renderingElem.attribute( QStringLiteral( "zIndex" ), QStringLiteral( "0.0" ) ).toDouble();
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() );
}
}
QDomElement QgsPalLayerSettings::writeXml( QDomDocument &doc, const QgsReadWriteContext &context )
{
QDomElement textStyleElem = mFormat.writeXml( doc, context );
// text style
textStyleElem.setAttribute( QStringLiteral( "fieldName" ), fieldName );
textStyleElem.setAttribute( QStringLiteral( "isExpression" ), isExpression );
textStyleElem.setAttribute( QStringLiteral( "previewBkgrdColor" ), previewBkgrdColor.name() );
QDomElement replacementElem = doc.createElement( QStringLiteral( "substitutions" ) );
substitutions.writeXml( replacementElem, doc );
textStyleElem.appendChild( replacementElem );
textStyleElem.setAttribute( QStringLiteral( "useSubstitutions" ), useSubstitutions );
// text formatting
QDomElement textFormatElem = doc.createElement( QStringLiteral( "text-format" ) );
textFormatElem.setAttribute( QStringLiteral( "wrapChar" ), wrapChar );
textFormatElem.setAttribute( QStringLiteral( "multilineAlign" ), static_cast< unsigned int >( multilineAlign ) );
textFormatElem.setAttribute( QStringLiteral( "addDirectionSymbol" ), addDirectionSymbol );
textFormatElem.setAttribute( QStringLiteral( "leftDirectionSymbol" ), leftDirectionSymbol );
textFormatElem.setAttribute( QStringLiteral( "rightDirectionSymbol" ), rightDirectionSymbol );
textFormatElem.setAttribute( QStringLiteral( "reverseDirectionSymbol" ), reverseDirectionSymbol );
textFormatElem.setAttribute( QStringLiteral( "placeDirectionSymbol" ), static_cast< unsigned int >( placeDirectionSymbol ) );
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" ), placement );
placementElem.setAttribute( QStringLiteral( "placementFlags" ), static_cast< unsigned int >( 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( "distInMapUnits" ), distInMapUnits );
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( "labelOffsetInMapUnits" ), labelOffsetInMapUnits );
placementElem.setAttribute( QStringLiteral( "labelOffsetMapUnitScale" ), QgsSymbolLayerUtils::encodeMapUnitScale( labelOffsetMapUnitScale ) );
placementElem.setAttribute( QStringLiteral( "rotationAngle" ), angleOffset );
placementElem.setAttribute( QStringLiteral( "preserveRotation" ), preserveRotation );
placementElem.setAttribute( QStringLiteral( "maxCurvedCharAngleIn" ), maxCurvedCharAngleIn );
placementElem.setAttribute( QStringLiteral( "maxCurvedCharAngleOut" ), maxCurvedCharAngleOut );
placementElem.setAttribute( QStringLiteral( "priority" ), priority );
placementElem.setAttribute( QStringLiteral( "repeatDistance" ), repeatDistance );
placementElem.setAttribute( QStringLiteral( "repeatDistanceUnit" ), repeatDistanceUnit );
placementElem.setAttribute( QStringLiteral( "repeatDistanceMapUnitScale" ), QgsSymbolLayerUtils::encodeMapUnitScale( repeatDistanceMapUnitScale ) );
// 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( "displayAll" ), displayAll );
renderingElem.setAttribute( QStringLiteral( "upsidedownLabels" ), static_cast< unsigned int >( upsidedownLabels ) );
renderingElem.setAttribute( QStringLiteral( "labelPerPart" ), labelPerPart );
renderingElem.setAttribute( QStringLiteral( "mergeLines" ), mergeLines );
renderingElem.setAttribute( QStringLiteral( "minFeatureSize" ), minFeatureSize );
renderingElem.setAttribute( QStringLiteral( "limitNumLabels" ), limitNumLabels );
renderingElem.setAttribute( QStringLiteral( "maxNumLabels" ), maxNumLabels );
renderingElem.setAttribute( QStringLiteral( "obstacle" ), obstacle );
renderingElem.setAttribute( QStringLiteral( "obstacleFactor" ), obstacleFactor );
renderingElem.setAttribute( QStringLiteral( "obstacleType" ), static_cast< unsigned int >( obstacleType ) );
renderingElem.setAttribute( QStringLiteral( "zIndex" ), zIndex );
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 );
return elem;
}
bool QgsPalLayerSettings::checkMinimumSizeMM( const QgsRenderContext &ct, const QgsGeometry &geom, double minSize ) const
{
return QgsPalLabeling::checkMinimumSizeMM( ct, geom, minSize );
}
void QgsPalLayerSettings::calculateLabelSize( const QFontMetricsF *fm, QString text, double &labelX, double &labelY, QgsFeature *f, QgsRenderContext *context )
{
if ( !fm || !f )
{
return;
}
//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;
double multilineH = mFormat.lineHeight();
bool addDirSymb = addDirectionSymbol;
QString leftDirSymb = leftDirectionSymbol;
QString rightDirSymb = rightDirectionSymbol;
QgsPalLayerSettings::DirectionSymbols placeDirSymb = placeDirectionSymbol;
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::MultiLineHeight ) )
{
multilineH = dataDefinedValues.value( QgsPalLayerSettings::MultiLineHeight ).toDouble();
}
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< QgsPalLayerSettings::DirectionSymbols >( dataDefinedValues.value( QgsPalLayerSettings::DirSymbPlacement ).toInt() );
}
}
}
else // called externally with passed-in feature, evaluate data defined
{
rc->expressionContext().setOriginalValueVariable( wrapChar );
wrapchr = mDataDefinedProperties.value( QgsPalLayerSettings::MultiLineWrapChar, rc->expressionContext(), wrapchr ).toString();
rc->expressionContext().setOriginalValueVariable( multilineH );
multilineH = mDataDefinedProperties.valueAsDouble( QgsPalLayerSettings::MultiLineHeight, rc->expressionContext(), multilineH );
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
{
rc->expressionContext().setOriginalValueVariable( leftDirSymb );
leftDirSymb = mDataDefinedProperties.value( QgsPalLayerSettings::DirSymbLeft, rc->expressionContext(), leftDirSymb ).toString();
rc->expressionContext().setOriginalValueVariable( rightDirSymb );
rightDirSymb = mDataDefinedProperties.value( QgsPalLayerSettings::DirSymbLeft, rc->expressionContext(), rightDirSymb ).toString();
rc->expressionContext().setOriginalValueVariable( static_cast< int >( placeDirSymb ) );
placeDirSymb = static_cast< QgsPalLayerSettings::DirectionSymbols >( mDataDefinedProperties.valueAsInt( QgsPalLayerSettings::DirSymbPlacement, rc->expressionContext(), placeDirSymb ) );
}
}
if ( wrapchr.isEmpty() )
{
wrapchr = QStringLiteral( "\n" ); // default to new line delimiter
}
//consider the space needed for the direction symbol
if ( addDirSymb && placement == QgsPalLayerSettings::Line
&& ( !leftDirSymb.isEmpty() || !rightDirSymb.isEmpty() ) )
{
QString dirSym = leftDirSymb;
if ( fm->width( rightDirSymb ) > fm->width( dirSym ) )
dirSym = rightDirSymb;
if ( placeDirSymb == QgsPalLayerSettings::SymbolLeftRight )
{
text.append( dirSym );
}
else
{
text.prepend( dirSym + QStringLiteral( "\n" ) ); // SymbolAbove or SymbolBelow
}
}
double w = 0.0, h = 0.0;
QStringList multiLineSplit = QgsPalLabeling::splitToLines( text, wrapchr );
int lines = multiLineSplit.size();
double labelHeight = fm->ascent() + fm->descent(); // ignore +1 for baseline
h += fm->height() + static_cast< double >( ( lines - 1 ) * labelHeight * multilineH );
for ( int i = 0; i < lines; ++i )
{
double width = fm->width( multiLineSplit.at( i ) );
if ( width > w )
{
w = width;
}
}
#if 0 // XXX strk
QgsPointXY ptSize = xform->toMapCoordinatesF( w, h );
labelX = qAbs( ptSize.x() - ptZero.x() );
labelY = qAbs( ptSize.y() - ptZero.y() );
#else
double uPP = xform->mapUnitsPerPixel();
labelX = w * uPP;
labelY = h * uPP;
#endif
}
void QgsPalLayerSettings::registerFeature( QgsFeature &f, QgsRenderContext &context, QgsLabelFeature **labelFeature, QgsGeometry obstacleGeometry )
{
// either used in QgsPalLabeling (palLayer is set) or in QgsLabelingEngine (labelFeature is set)
Q_ASSERT( labelFeature );
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 = mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::IsObstacle, context.expressionContext(), obstacle ); // default to layer default
if ( !drawLabels )
{
if ( isObstacle )
{
registerObstacleFeature( f, context, labelFeature, obstacleGeometry );
}
return;
}
// mCurFields = &layer->pendingFields();
// 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
context.expressionContext().setOriginalValueVariable( true );
if ( !mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::Show, context.expressionContext(), true ) )
{
return;
}
// data defined scale visibility?
bool useScaleVisibility = mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::ScaleVisibility, context.expressionContext(), scaleVisibility );
if ( useScaleVisibility )
{
// data defined min scale?
context.expressionContext().setOriginalValueVariable( maximumScale );
double maxScale = mDataDefinedProperties.valueAsDouble( QgsPalLayerSettings::MaximumScale, context.expressionContext(), maximumScale );
// scales closer than 1:1
if ( maxScale < 0 )
{
maxScale = 1 / qAbs( maxScale );
}
if ( !qgsDoubleNear( maxScale, 0.0 ) && context.rendererScale() < maxScale )
{
return;
}
// data defined max scale?
context.expressionContext().setOriginalValueVariable( minimumScale );
double minScale = mDataDefinedProperties.valueAsDouble( QgsPalLayerSettings::MinimumScale, context.expressionContext(), minimumScale );
// scales closer than 1:1
if ( minScale < 0 )
{
minScale = 1 / qAbs( minScale );
}
if ( !qgsDoubleNear( minScale, 0.0 ) && context.rendererScale() > minScale )
{
return;
}
}
QFont labelFont = mFormat.font();
// labelFont will be added to label feature for use during label painting
// data defined font units?
QgsUnitTypes::RenderUnit fontunits = mFormat.sizeUnit();
exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::FontSizeUnit, context.expressionContext() );
if ( exprVal.isValid() )
{
QString units = exprVal.toString();
if ( !units.isEmpty() )
{
bool ok;
QgsUnitTypes::RenderUnit res = QgsUnitTypes::decodeRenderUnit( units, &ok );
if ( ok )
fontunits = res;
}
}
//data defined label size?
context.expressionContext().setOriginalValueVariable( mFormat.size() );
double fontSize = mDataDefinedProperties.valueAsDouble( QgsPalLayerSettings::Size, context.expressionContext(), mFormat.size() );
if ( fontSize <= 0.0 )
{
return;
}
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;
}
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 == QgsUnitTypes::RenderMapUnits )
{
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;
}
}
}
// 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 );
parseTextFormatting( context );
parseTextBuffer( 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( QString( "Expression parser error:%1" ).arg( exp->parserErrorString() ), 4 );
return;
}
QVariant result = exp->evaluate( &context.expressionContext() ); // expression prepared in QgsPalLabeling::prepareLayer()
if ( exp->hasEvalError() )
{
QgsDebugMsgLevel( QString( "Expression parser eval error:%1" ).arg( exp->evalErrorString() ), 4 );
return;
}
labelText = result.isNull() ? QLatin1String( "" ) : result.toString();
}
else
{
const QVariant &v = f.attribute( fieldIndex );
labelText = v.isNull() ? QLatin1String( "" ) : v.toString();
}
// apply text replacements
if ( useSubstitutions )
{
labelText = substitutions.process( labelText );
}
// apply capitalization
QgsStringUtils::Capitalization capitalization = QgsStringUtils::MixedCase;
// maintain API - capitalization may have been set in textFont
if ( mFormat.font().capitalization() != QFont::MixedCase )
{
capitalization = static_cast< QgsStringUtils::Capitalization >( mFormat.font().capitalization() );
}
// data defined font capitalization?
exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::FontCase, context.expressionContext() );
if ( exprVal.isValid() )
{
QString fcase = exprVal.toString().trimmed();
QgsDebugMsgLevel( QString( "exprVal FontCase:%1" ).arg( fcase ), 4 );
if ( !fcase.isEmpty() )
{
if ( fcase.compare( QLatin1String( "NoChange" ), Qt::CaseInsensitive ) == 0 )
{
capitalization = QgsStringUtils::MixedCase;
}
else if ( fcase.compare( QLatin1String( "Upper" ), Qt::CaseInsensitive ) == 0 )
{
capitalization = QgsStringUtils::AllUppercase;
}
else if ( fcase.compare( QLatin1String( "Lower" ), Qt::CaseInsensitive ) == 0 )
{
capitalization = QgsStringUtils::AllLowercase;
}
else if ( fcase.compare( QLatin1String( "Capitalize" ), Qt::CaseInsensitive ) == 0 )
{
capitalization = QgsStringUtils::ForceFirstLetterToCapital;
}
}
}
labelText = QgsStringUtils::capitalize( labelText, capitalization );
// format number if label text is coercible to a number
if ( mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::NumFormat, context.expressionContext(), formatNumbers ) )
{
// 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( d, 0, 'f', decimalPlaces );
}
}
// NOTE: this should come AFTER any option that affects font metrics
std::unique_ptr<QFontMetricsF> labelFontMetrics( new QFontMetricsF( labelFont ) );
double labelX, labelY; // will receive label size
calculateLabelSize( labelFontMetrics.get(), labelText, labelX, labelY, mCurFeat, &context );
// 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
if ( placement == QgsPalLayerSettings::Curved || placement == QgsPalLayerSettings::PerimeterCurved )
{
maxcharanglein = maxCurvedCharAngleIn;
maxcharangleout = maxCurvedCharAngleOut;
//data defined maximum angle between curved label characters?
exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::CurvedCharAngleInOut, context.expressionContext() );
if ( exprVal.isValid() )
{
QString ptstr = exprVal.toString().trimmed();
QgsDebugMsgLevel( QString( "exprVal CurvedCharAngleInOut:%1" ).arg( ptstr ), 4 );
if ( !ptstr.isEmpty() )
{
QPointF maxcharanglePt = QgsSymbolLayerUtils::decodePoint( ptstr );
maxcharanglein = qBound( 20.0, static_cast< double >( maxcharanglePt.x() ), 60.0 );
maxcharangleout = qBound( 20.0, static_cast< double >( maxcharanglePt.y() ), 95.0 );
}
}
// make sure maxcharangleout is always negative
maxcharangleout = -( qAbs( maxcharangleout ) );
}
// data defined centroid whole or clipped?
bool wholeCentroid = centroidWhole;
exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::CentroidWhole, context.expressionContext() );
if ( exprVal.isValid() )
{
QString str = exprVal.toString().trimmed();
QgsDebugMsgLevel( QString( "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 = f.geometry();
if ( geom.isNull() )
{
return;
}
// 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() );
QgsGeometry g = geom;
QgsMapToPixelSimplifier simplifier( simplifyHints, simplifyMethod.tolerance(), simplifyAlgorithm );
geom = simplifier.simplify( geom );
}
// whether we're going to create a centroid for polygon
bool centroidPoly = ( ( placement == QgsPalLayerSettings::AroundPoint
|| placement == QgsPalLayerSettings::OverPoint )
&& geom.type() == QgsWkbTypes::PolygonGeometry );
// 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;
}
// 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)
QgsGeometry permissibleZone;
if ( geom.type() == QgsWkbTypes::PolygonGeometry && fitInPolygonOnly )
{
permissibleZone = geom;
if ( QgsPalLabeling::geometryRequiresPreparation( permissibleZone, context, ct, doClip ? extentGeom : QgsGeometry() ) )
{
permissibleZone = QgsPalLabeling::prepareGeometry( permissibleZone, context, ct, doClip ? extentGeom : QgsGeometry() );
}
}
// if using perimeter based labeling for polygons, get the polygon's
// linear boundary and use that for the label geometry
if ( ( geom.type() == QgsWkbTypes::PolygonGeometry )
&& ( placement == Line || placement == PerimeterCurved ) )
{
geom = QgsGeometry( geom.geometry()->boundary() );
}
GEOSGeometry *geos_geom_clone = nullptr;
if ( QgsPalLabeling::geometryRequiresPreparation( geom, context, ct, doClip ? extentGeom : QgsGeometry() ) )
{
geom = QgsPalLabeling::prepareGeometry( geom, context, ct, doClip ? extentGeom : QgsGeometry() );
if ( geom.isNull() )
return;
}
geos_geom_clone = geom.exportToGeos();
if ( isObstacle )
{
if ( !obstacleGeometry.isNull() && QgsPalLabeling::geometryRequiresPreparation( obstacleGeometry, context, ct, doClip ? extentGeom : QgsGeometry() ) )
{
obstacleGeometry = QgsGeometry( QgsPalLabeling::prepareGeometry( obstacleGeometry, context, ct, doClip ? extentGeom : QgsGeometry() ) );
}
}
if ( minFeatureSize > 0 && !checkMinimumSizeMM( context, geom, minFeatureSize ) )
return;
if ( !geos_geom_clone )
return; // 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 ( limitNumLabels )
{
if ( !maxNumLabels )
{
return;
}
if ( mFeatsRegPal >= maxNumLabels )
{
return;
}
int divNum = static_cast< int >( ( static_cast< double >( mFeaturesToLabel ) / maxNumLabels ) + 0.5 ); // NOLINT
if ( divNum && ( mFeatsRegPal == static_cast< int >( mFeatsSendingToPal / divNum ) ) )
{
mFeatsSendingToPal += 1;
if ( divNum && mFeatsSendingToPal % divNum )
{
return;
}
}
}
GEOSGeometry *geosObstacleGeomClone = nullptr;
if ( obstacleGeometry )
{
geosObstacleGeomClone = obstacleGeometry.exportToGeos();
}
//data defined position / alignment / rotation?
bool dataDefinedPosition = false;
bool layerDefinedRotation = false;
bool dataDefinedRotation = false;
double xPos = 0.0, yPos = 0.0, angle = 0.0;
bool ddXPos = false, ddYPos = false;
double quadOffsetX = 0.0, quadOffsetY = 0.0;
double offsetX = 0.0, offsetY = 0.0;
//data defined quadrant offset?
bool ddFixedQuad = false;
QuadrantPosition quadOff = quadOffset;
context.expressionContext().setOriginalValueVariable( static_cast< int >( quadOff ) );
exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::OffsetQuad, context.expressionContext() );
if ( exprVal.isValid() )
{
bool ok;
int quadInt = exprVal.toInt( &ok );
QgsDebugMsgLevel( QString( "exprVal OffsetQuad:%1" ).arg( quadInt ), 4 );
if ( ok && 0 <= quadInt && quadInt <= 8 )
{
quadOff = static_cast< QuadrantPosition >( quadInt );
ddFixedQuad = true;
}
}
// adjust quadrant offset of labels
switch ( quadOff )
{
case QuadrantAboveLeft:
quadOffsetX = -1.0;
quadOffsetY = 1.0;
break;
case QuadrantAbove:
quadOffsetX = 0.0;
quadOffsetY = 1.0;
break;
case QuadrantAboveRight:
quadOffsetX = 1.0;
quadOffsetY = 1.0;
break;
case QuadrantLeft:
quadOffsetX = -1.0;
quadOffsetY = 0.0;
break;
case QuadrantRight:
quadOffsetX = 1.0;
quadOffsetY = 0.0;
break;
case QuadrantBelowLeft:
quadOffsetX = -1.0;
quadOffsetY = -1.0;
break;
case QuadrantBelow:
quadOffsetX = 0.0;
quadOffsetY = -1.0;
break;
case QuadrantBelowRight:
quadOffsetX = 1.0;
quadOffsetY = -1.0;
break;
case QuadrantOver:
default:
break;
}
//data defined label offset?
double xOff = xOffset;
double yOff = yOffset;
context.expressionContext().setOriginalValueVariable( QgsSymbolLayerUtils::encodePoint( QPointF( xOffset, yOffset ) ) );
exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::OffsetXY, context.expressionContext() );
if ( exprVal.isValid() )
{
QString ptstr = exprVal.toString().trimmed();
QgsDebugMsgLevel( QString( "exprVal OffsetXY:%1" ).arg( ptstr ), 4 );
if ( !ptstr.isEmpty() )
{
QPointF ddOffPt = QgsSymbolLayerUtils::decodePoint( ptstr );
xOff = ddOffPt.x();
yOff = ddOffPt.y();
}
}
// data defined label offset units?
bool offinmapunits = labelOffsetInMapUnits;
exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::OffsetUnits, context.expressionContext() );
if ( exprVal.isValid() )
{
QString units = exprVal.toString().trimmed();
QgsDebugMsgLevel( QString( "exprVal OffsetUnits:%1" ).arg( units ), 4 );
if ( !units.isEmpty() )
{
offinmapunits = ( _decodeUnits( units ) == QgsPalLayerSettings::MapUnits );
}
}
// adjust offset of labels to match chosen unit and map scale
// offsets match those of symbology: -x = left, -y = up
double mapUntsPerMM = labelOffsetMapUnitScale.computeMapUnitsPerPixel( context ) * context.scaleFactor();
if ( !qgsDoubleNear( xOff, 0.0 ) )
{
offsetX = xOff; // must be positive to match symbology offset direction
if ( !offinmapunits )
{
offsetX *= mapUntsPerMM; //convert offset from mm to map units
}
}
if ( !qgsDoubleNear( yOff, 0.0 ) )
{
offsetY = -yOff; // must be negative to match symbology offset direction
if ( !offinmapunits )
{
offsetY *= mapUntsPerMM; //convert offset from mm to map units
}
}
// layer defined rotation?
// only rotate non-pinned OverPoint placements until other placements are supported in pal::Feature
if ( placement == QgsPalLayerSettings::OverPoint && !qgsDoubleNear( angleOffset, 0.0 ) )
{
layerDefinedRotation = true;
angle = ( 360 - angleOffset ) * M_PI / 180; // convert to radians counterclockwise
}
const QgsMapToPixel &m2p = context.mapToPixel();
//data defined rotation?
context.expressionContext().setOriginalValueVariable( angleOffset );
exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::LabelRotation, context.expressionContext() );
if ( exprVal.isValid() )
{
bool ok;
double rotD = exprVal.toDouble( &ok );
QgsDebugMsgLevel( QString( "exprVal Rotation:%1" ).arg( rotD ), 4 );
if ( ok )
{
dataDefinedRotation = true;
// TODO: add setting to disable having data defined rotation follow
// map rotation ?
rotD += m2p.mapRotation();
angle = ( 360 - rotD ) * M_PI / 180.0;
}
}
exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::PositionX, context.expressionContext() );
if ( exprVal.isValid() )
{
if ( !exprVal.isNull() )
xPos = exprVal.toDouble( &ddXPos );
QgsDebugMsgLevel( QString( "exprVal PositionX:%1" ).arg( xPos ), 4 );
exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::PositionY, context.expressionContext() );
if ( exprVal.isValid() )
{
//data defined position. But field values could be NULL -> positions will be generated by PAL
if ( !exprVal.isNull() )
yPos = exprVal.toDouble( &ddYPos );
QgsDebugMsgLevel( QString( "exprVal PositionY:%1" ).arg( yPos ), 4 );
if ( ddXPos && ddYPos )
{
dataDefinedPosition = true;
// layer rotation set, but don't rotate pinned labels unless data defined
if ( layerDefinedRotation && !dataDefinedRotation )
{
angle = 0.0;
}
//x/y shift in case of alignment
double xdiff = 0.0;
double ydiff = 0.0;
//horizontal alignment
exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Hali, context.expressionContext() );
if ( exprVal.isValid() )
{
QString haliString = exprVal.toString();
QgsDebugMsgLevel( QString( "exprVal Hali:%1" ).arg( haliString ), 4 );
if ( haliString.compare( QLatin1String( "Center" ), Qt::CaseInsensitive ) == 0 )
{
xdiff -= labelX / 2.0;
}
else if ( haliString.compare( QLatin1String( "Right" ), Qt::CaseInsensitive ) == 0 )
{
xdiff -= labelX;
}
}
//vertical alignment
exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Vali, context.expressionContext() );
if ( exprVal.isValid() )
{
QString valiString = exprVal.toString();
QgsDebugMsgLevel( QString( "exprVal Vali:%1" ).arg( valiString ), 4 );
if ( valiString.compare( QLatin1String( "Bottom" ), Qt::CaseInsensitive ) != 0 )
{
if ( valiString.compare( QLatin1String( "Top" ), Qt::CaseInsensitive ) == 0 )
{
ydiff -= labelY;
}
else
{
double descentRatio = labelFontMetrics->descent() / labelFontMetrics->height();
if ( valiString.compare( QLatin1String( "Base" ), Qt::CaseInsensitive ) == 0 )
{
ydiff -= labelY * descentRatio;
}
else //'Cap' or 'Half'
{
double capHeightRatio = ( labelFontMetrics->boundingRect( 'H' ).height() + 1 + labelFontMetrics->descent() ) / labelFontMetrics->height();
ydiff -= labelY * capHeightRatio;
if ( valiString.compare( QLatin1String( "Half" ), Qt::CaseInsensitive ) == 0 )
{
ydiff += labelY * ( capHeightRatio - descentRatio ) / 2.0;
}
}
}
}
}
if ( dataDefinedRotation )
{
//adjust xdiff and ydiff because the hali/vali point needs to be the rotation center
double xd = xdiff * cos( angle ) - ydiff * sin( angle );
double yd = xdiff * sin( angle ) + ydiff * cos( angle );
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 );
xPos = static_cast< QgsPoint * >( ddPoint.geometry() )->x();
yPos = static_cast< QgsPoint * >( ddPoint.geometry() )->y();
}
xPos += xdiff;
yPos += ydiff;
}
else
{
// only rotate non-pinned OverPoint placements until other placements are supported in pal::Feature
if ( dataDefinedRotation && placement != QgsPalLayerSettings::OverPoint )
{
angle = 0.0;
}
}
}
}
// data defined always show?
bool alwaysShow = mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::AlwaysShow, context.expressionContext(), false );
// set repeat distance
// data defined repeat distance?
context.expressionContext().setOriginalValueVariable( repeatDistance );
double repeatDist = mDataDefinedProperties.valueAsDouble( QgsPalLayerSettings::RepeatDistance, context.expressionContext(), repeatDistance );
// data defined label-repeat distance units?
bool repeatdistinmapunit = repeatDistanceUnit == QgsPalLayerSettings::MapUnits;
exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::RepeatDistanceUnit, context.expressionContext() );
if ( exprVal.isValid() )
{
QString units = exprVal.toString().trimmed();
QgsDebugMsgLevel( QString( "exprVal RepeatDistanceUnits:%1" ).arg( units ), 4 );
if ( !units.isEmpty() )
{
repeatdistinmapunit = ( _decodeUnits( units ) == QgsPalLayerSettings::MapUnits );
}
}
if ( !qgsDoubleNear( repeatDist, 0.0 ) )
{
if ( !repeatdistinmapunit )
{
repeatDist *= mapUntsPerMM; //convert repeat distance from mm to map units
}
}
// feature to the layer
QgsTextLabelFeature *lf = new QgsTextLabelFeature( f.id(), geos_geom_clone, QSizeF( labelX, labelY ) );
mFeatsRegPal++;
*labelFeature = lf;
( *labelFeature )->setHasFixedPosition( dataDefinedPosition );
( *labelFeature )->setFixedPosition( QgsPointXY( xPos, yPos ) );
// use layer-level defined rotation, but not if position fixed
( *labelFeature )->setHasFixedAngle( dataDefinedRotation || ( !dataDefinedPosition && !qgsDoubleNear( angle, 0.0 ) ) );
( *labelFeature )->setFixedAngle( angle );
( *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 );
if ( geosObstacleGeomClone )
{
( *labelFeature )->setObstacleGeometry( geosObstacleGeomClone );
if ( geom.type() == QgsWkbTypes::PointGeometry )
{
//register symbol size
( *labelFeature )->setSymbolSize( QSizeF( obstacleGeometry.boundingBox().width(),
obstacleGeometry.boundingBox().height() ) );
}
}
//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 = qMax( 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( QString( "PAL font stored definedFont: %1, Style: %2" ).arg( labelFont.toString(), labelFont.styleName() ), 4 );
lf->setDefinedFont( labelFont );
// TODO: only for placement which needs character info
// account for any data defined font metrics adjustments
lf->calculateInfo( placement == QgsPalLayerSettings::Curved || placement == QgsPalLayerSettings::PerimeterCurved,
labelFontMetrics.get(), xform, maxcharanglein, maxcharangleout );
// 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?
context.expressionContext().setOriginalValueVariable( dist );
double distance = mDataDefinedProperties.valueAsDouble( QgsPalLayerSettings::LabelDistance, context.expressionContext(), dist );
// data defined label-feature distance units?
bool distinmapunit = distInMapUnits;
exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::DistanceUnits, context.expressionContext() );
if ( exprVal.isValid() )
{
QString units = exprVal.toString().trimmed();
QgsDebugMsgLevel( QString( "exprVal DistanceUnits:%1" ).arg( units ), 4 );
if ( !units.isEmpty() )
{
distinmapunit = ( _decodeUnits( units ) == QgsPalLayerSettings::MapUnits );
}
}
if ( distinmapunit ) //convert distance from mm/map units to pixels
{
distance /= distMapUnitScale.computeMapUnitsPerPixel( context );
}
else //mm
{
distance *= context.scaleFactor();
}
// 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
if ( placement == QgsPalLayerSettings::Line || placement == QgsPalLayerSettings::Curved || placement == QgsPalLayerSettings::PerimeterCurved )
{
distance = qMax( distance, 1.0 );
}
if ( !qgsDoubleNear( distance, 0.0 ) )
{
double d = ptOne.distance( ptZero ) * distance;
( *labelFeature )->setDistLabel( d );
}
if ( ddFixedQuad )
{
( *labelFeature )->setHasFixedQuadrant( true );
}
// data defined z-index?
context.expressionContext().setOriginalValueVariable( zIndex );
double z = mDataDefinedProperties.valueAsDouble( QgsPalLayerSettings::ZIndex, context.expressionContext(), zIndex );
( *labelFeature )->setZIndex( z );
// data defined priority?
context.expressionContext().setOriginalValueVariable( priority );
exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Priority, context.expressionContext() );
if ( exprVal.isValid() )
{
bool ok;
double priorityD = exprVal.toDouble( &ok );
if ( ok )
{
priorityD = qBound( 0.0, priorityD, 10.0 );
priorityD = 1 - priorityD / 10.0; // convert 0..10 --> 1..0
( *labelFeature )->setPriority( priorityD );
}
}
( *labelFeature )->setIsObstacle( isObstacle );
double featObstacleFactor = obstacleFactor;
context.expressionContext().setOriginalValueVariable( obstacleFactor );
exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::ObstacleFactor, context.expressionContext() );
if ( exprVal.isValid() )
{
bool ok;
double factorD = exprVal.toDouble( &ok );
if ( ok )
{
factorD = qBound( 0.0, factorD, 10.0 );
factorD = factorD / 5.0 + 0.0001; // convert 0 -> 10 to 0.0001 -> 2.0
featObstacleFactor = factorD;
}
}
( *labelFeature )->setObstacleFactor( featObstacleFactor );
QVector< QgsPalLayerSettings::PredefinedPointPosition > positionOrder = predefinedPositionOrder;
if ( positionOrder.isEmpty() )
positionOrder = QgsPalLayerSettings::DEFAULT_PLACEMENT_ORDER;
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
lf->setDataDefinedValues( dataDefinedValues );
}
void QgsPalLayerSettings::registerObstacleFeature( QgsFeature &f, QgsRenderContext &context, QgsLabelFeature **obstacleFeature, const QgsGeometry &obstacleGeometry )
{
mCurFeat = &f;
QgsGeometry geom;
if ( obstacleGeometry )
{
geom = obstacleGeometry;
}
else
{
geom = f.geometry();
}
if ( geom.isNull() )
{
return;
}
// 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 );
}
GEOSGeometry *geos_geom_clone = nullptr;
std::unique_ptr<QgsGeometry> scopedPreparedGeom;
if ( QgsPalLabeling::geometryRequiresPreparation( geom, context, ct, extentGeom ) )
{
geom = QgsPalLabeling::prepareGeometry( geom, context, ct, extentGeom );
}
geos_geom_clone = geom.exportToGeos();
if ( !geos_geom_clone )
return; // invalid geometry
// feature to the layer
*obstacleFeature = new QgsLabelFeature( f.id(), geos_geom_clone, QSizeF( 0, 0 ) );
( *obstacleFeature )->setIsObstacle( true );
mFeatsRegPal++;
}
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 ( exprVal.isValid() )
{
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.toDouble( &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 >( _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:
{
QString ptstr = exprVal.toString().trimmed();
if ( !ptstr.isEmpty() )
{
dataDefinedValues.insert( p, QVariant( QgsSymbolLayerUtils::decodePoint( ptstr ) ) );
return true;
}
return false;
}
}
}
return false;
}
void QgsPalLayerSettings::parseTextStyle( QFont &labelFont,
QgsUnitTypes::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;
context.expressionContext().setOriginalValueVariable( labelFont.family() );
exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::Family, context.expressionContext() );
if ( exprVal.isValid() )
{
QString family = exprVal.toString().trimmed();
QgsDebugMsgLevel( QString( "exprVal Font family:%1" ).arg( family ), 4 );
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;
exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::FontStyle, context.expressionContext() );
if ( exprVal.isValid() )
{
QString fontstyle = exprVal.toString().trimmed();
QgsDebugMsgLevel( QString( "exprVal Font style:%1" ).arg( fontstyle ), 4 );
ddFontStyle = fontstyle;
}
// data defined bold font style?
context.expressionContext().setOriginalValueVariable( labelFont.bold() );
bool ddBold = mDataDefinedProperties.valueAsBool( QgsPalLayerSettings::Bold, context.expressionContext(), false );
// data defined italic font style?
context.expressionContext().setOriginalValueVariable( labelFont.italic() );
bool 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 = QFont( !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
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
QFont styledfont = mFontDB.font( ddFontFamily, mFormat.namedStyle(), appFont.pointSize() );
if ( appFont != styledfont )
{
newFont = styledfont;
newFontBuilt = true;
}
}
else
{
newFont = QFont( 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();
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();
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 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();
}
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::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();
}
// data defined multiline height?
dataDefinedValEval( DDDouble, QgsPalLayerSettings::MultiLineHeight, exprVal, context.expressionContext() );
// data defined multiline text align?
context.expressionContext().setOriginalValueVariable( mFormat.lineHeight() );
exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::MultiLineAlignment, context.expressionContext() );
if ( exprVal.isValid() )
{
QString str = exprVal.toString().trimmed();
QgsDebugMsgLevel( QString( "exprVal MultiLineAlignment:%1" ).arg( str ), 4 );
if ( !str.isEmpty() )
{
// "Left"
QgsPalLayerSettings::MultiLineAlign aligntype = QgsPalLayerSettings::MultiLeft;
if ( str.compare( QLatin1String( "Center" ), Qt::CaseInsensitive ) == 0 )
{
aligntype = QgsPalLayerSettings::MultiCenter;
}
else if ( str.compare( QLatin1String( "Right" ), Qt::CaseInsensitive ) == 0 )
{
aligntype = QgsPalLayerSettings::MultiRight;
}
else if ( str.compare( QLatin1String( "Follow" ), Qt::CaseInsensitive ) == 0 )
{
aligntype = QgsPalLayerSettings::MultiFollowPlacement;
}
dataDefinedValues.insert( QgsPalLayerSettings::MultiLineAlignment, QVariant( static_cast< int >( aligntype ) ) );
}
}
// data defined direction symbol?
bool drawDirSymb = addDirectionSymbol;
if ( dataDefinedValEval( DDBool, QgsPalLayerSettings::DirSymbDraw, exprVal, context.expressionContext(), addDirectionSymbol ) )
{
drawDirSymb = exprVal.toBool();
}
if ( drawDirSymb )
{
// data defined direction left symbol?
dataDefinedValEval( DDString, QgsPalLayerSettings::DirSymbLeft, exprVal, context.expressionContext(), leftDirectionSymbol );
// data defined direction right symbol?
dataDefinedValEval( DDString, QgsPalLayerSettings::DirSymbRight, exprVal, context.expressionContext(), rightDirectionSymbol );
// data defined direction symbol placement?
exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::DirSymbPlacement, context.expressionContext() );
if ( exprVal.isValid() )
{
QString str = exprVal.toString().trimmed();
QgsDebugMsgLevel( QString( "exprVal DirSymbPlacement:%1" ).arg( str ), 4 );
if ( !str.isEmpty() )
{
// "LeftRight"
QgsPalLayerSettings::DirectionSymbols placetype = QgsPalLayerSettings::SymbolLeftRight;
if ( str.compare( QLatin1String( "Above" ), Qt::CaseInsensitive ) == 0 )
{
placetype = QgsPalLayerSettings::SymbolAbove;
}
else if ( str.compare( QLatin1String( "Below" ), Qt::CaseInsensitive ) == 0 )
{
placetype = QgsPalLayerSettings::SymbolBelow;
}
dataDefinedValues.insert( QgsPalLayerSettings::DirSymbPlacement, QVariant( static_cast< int >( placetype ) ) );
}
}
// data defined direction symbol reversed?
dataDefinedValEval( DDBool, QgsPalLayerSettings::DirSymbReverse, exprVal, context.expressionContext(), 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();
exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::ShapeKind, context.expressionContext() );
if ( exprVal.isValid() )
{
QString skind = exprVal.toString().trimmed();
QgsDebugMsgLevel( QString( "exprVal ShapeKind:%1" ).arg( skind ), 4 );
if ( !skind.isEmpty() )
{
// "Rectangle"
QgsTextBackgroundSettings::ShapeType shpkind = QgsTextBackgroundSettings::ShapeRectangle;
if ( skind.compare( QLatin1String( "Square" ), Qt::CaseInsensitive ) == 0 )
{
shpkind = QgsTextBackgroundSettings::ShapeSquare;
}
else if ( skind.compare( QLatin1String( "Ellipse" ), Qt::CaseInsensitive ) == 0 )
{
shpkind = QgsTextBackgroundSettings::ShapeEllipse;
}
else if ( skind.compare( QLatin1String( "Circle" ), Qt::CaseInsensitive ) == 0 )
{
shpkind = QgsTextBackgroundSettings::ShapeCircle;
}
else if ( skind.compare( QLatin1String( "SVG" ), Qt::CaseInsensitive ) == 0 )
{
shpkind = QgsTextBackgroundSettings::ShapeSVG;
}
shapeKind = shpkind;
dataDefinedValues.insert( QgsPalLayerSettings::ShapeKind, QVariant( static_cast< int >( shpkind ) ) );
}
}
// data defined shape SVG path?
QString svgPath = background.svgFile();
context.expressionContext().setOriginalValueVariable( svgPath );
exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::ShapeSVGFile, context.expressionContext() );
if ( exprVal.isValid() )
{
QString svgfile = exprVal.toString().trimmed();
QgsDebugMsgLevel( QString( "exprVal ShapeSVGFile:%1" ).arg( svgfile ), 4 );
// '' empty paths are allowed
svgPath = svgfile;
dataDefinedValues.insert( QgsPalLayerSettings::ShapeSVGFile, QVariant( svgfile ) );
}
// data defined shape size type?
QgsTextBackgroundSettings::SizeType shpSizeType = background.sizeType();
exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::ShapeSizeType, context.expressionContext() );
if ( exprVal.isValid() )
{
QString stype = exprVal.toString().trimmed();
QgsDebugMsgLevel( QString( "exprVal ShapeSizeType:%1" ).arg( stype ), 4 );
if ( !stype.isEmpty() )
{
// "Buffer"
QgsTextBackgroundSettings::SizeType sizType = QgsTextBackgroundSettings::SizeBuffer;
if ( stype.compare( QLatin1String( "Fixed" ), Qt::CaseInsensitive ) == 0 )
{
sizType = QgsTextBackgroundSettings::SizeFixed;
}
shpSizeType = sizType;
dataDefinedValues.insert( QgsPalLayerSettings::ShapeSizeType, QVariant( static_cast< int >( sizType ) ) );
}
}
// 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::ShapeSVG
&& 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?
exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::ShapeRotationType, context.expressionContext() );
if ( exprVal.isValid() )
{
QString rotstr = exprVal.toString().trimmed();
QgsDebugMsgLevel( QString( "exprVal ShapeRotationType:%1" ).arg( rotstr ), 4 );
if ( !rotstr.isEmpty() )
{
// "Sync"
QgsTextBackgroundSettings::RotationType rottype = QgsTextBackgroundSettings::RotationSync;
if ( rotstr.compare( QLatin1String( "Offset" ), Qt::CaseInsensitive ) == 0 )
{
rottype = QgsTextBackgroundSettings::RotationOffset;
}
else if ( rotstr.compare( QLatin1String( "Fixed" ), Qt::CaseInsensitive ) == 0 )
{
rottype = QgsTextBackgroundSettings::RotationFixed;
}
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( DDPointF, 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?
exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::ShadowUnder, context.expressionContext() );
if ( exprVal.isValid() )
{
QString str = exprVal.toString().trimmed();
QgsDebugMsgLevel( QString( "exprVal ShadowUnder:%1" ).arg( str ), 4 );
if ( !str.isEmpty() )
{
// "Lowest"
QgsTextShadowSettings::ShadowPlacement shdwtype = QgsTextShadowSettings::ShadowLowest;
if ( str.compare( QLatin1String( "Text" ), Qt::CaseInsensitive ) == 0 )
{
shdwtype = QgsTextShadowSettings::ShadowText;
}
else if ( str.compare( QLatin1String( "Buffer" ), Qt::CaseInsensitive ) == 0 )
{
shdwtype = QgsTextShadowSettings::ShadowBuffer;
}
else if ( str.compare( QLatin1String( "Background" ), Qt::CaseInsensitive ) == 0 )
{
shdwtype = QgsTextShadowSettings::ShadowShape;
}
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( QgsVectorLayer *layer )
{
return layer->labelsEnabled() || layer->diagramsEnabled();
}
bool QgsPalLabeling::geometryRequiresPreparation( const QgsGeometry &geometry, QgsRenderContext &context, const QgsCoordinateTransform &ct, const QgsGeometry &clipGeometry )
{
if ( geometry.isNull() )
{
return false;
}
//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() == QgsWkbTypes::PolygonGeometry && !geometry.isGeosValid() )
return true;
return false;
}
QStringList QgsPalLabeling::splitToLines( const QString &text, const QString &wrapCharacter )
{
QStringList multiLineSplit;
if ( !wrapCharacter.isEmpty() && wrapCharacter != QLatin1String( "\n" ) )
{
//wrap on both the wrapchr and new line characters
Q_FOREACH ( const QString &line, text.split( wrapCharacter ) )
{
multiLineSplit.append( line.split( '\n' ) );
}
}
else
{
multiLineSplit = text.split( '\n' );
}
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 )
{
if ( geometry.isNull() )
{
return QgsGeometry();
}
//don't modify the feature's geometry so that geometry based expressions keep working
QgsGeometry geom = geometry;
//reproject the geometry if necessary
if ( ct.isValid() && !ct.isShortCircuited() )
{
try
{
geom.transform( ct );
}
catch ( QgsCsException &cse )
{
Q_UNUSED( cse );
QgsDebugMsgLevel( QString( "Ignoring feature due to transformation exception" ), 4 );
return QgsGeometry();
}
}
// Rotate the geometry if needed, before clipping
const QgsMapToPixel &m2p = context.mapToPixel();
if ( !qgsDoubleNear( m2p.mapRotation(), 0 ) )
{
QgsPointXY center = context.extent().center();
if ( ct.isValid() && !ct.isShortCircuited() )
{
try
{
center = ct.transform( center );
}
catch ( QgsCsException &cse )
{
Q_UNUSED( cse );
QgsDebugMsgLevel( QString( "Ignoring feature due to transformation exception" ), 4 );
return QgsGeometry();
}
}
if ( geom.rotate( m2p.mapRotation(), center ) )
{
QgsDebugMsg( QString( "Error rotating geometry" ).arg( geom.exportToWkt() ) );
return QgsGeometry();
}
}
// fix invalid polygons
if ( geom.type() == QgsWkbTypes::PolygonGeometry && !geom.isGeosValid() )
{
QgsGeometry bufferGeom = geom.buffer( 0, 0 );
if ( bufferGeom.isNull() )
{
return QgsGeometry();
}
geom = bufferGeom;
}
if ( !clipGeometry.isNull() &&
( ( qgsDoubleNear( m2p.mapRotation(), 0 ) && !clipGeometry.boundingBox().contains( geom.boundingBox() ) )
|| ( !qgsDoubleNear( m2p.mapRotation(), 0 ) && !clipGeometry.contains( geom ) ) ) )
{
QgsGeometry clipGeom = geom.intersection( clipGeometry ); // creates new geometry
if ( clipGeom.isNull() )
{
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;
}
QgsWkbTypes::GeometryType featureType = geom.type();
if ( featureType == QgsWkbTypes::PointGeometry ) //minimum size does not apply to point features
{
return true;
}
double mapUnitsPerMM = context.mapToPixel().mapUnitsPerPixel() * context.scaleFactor();
if ( featureType == QgsWkbTypes::LineGeometry )
{
double length = geom.length();
if ( length >= 0.0 )
{
return ( length >= ( minSize * mapUnitsPerMM ) );
}
}
else if ( featureType == QgsWkbTypes::PolygonGeometry )
{
double area = geom.area();
if ( area >= 0.0 )
{
return ( 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 ( !tmpLyr.wrapChar.isEmpty() || tmpLyr.getLabelExpression()->expression().contains( QLatin1String( "wordwrap" ) ) )
{
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< QgsPalLayerSettings::MultiLineAlign >( ddValues.value( QgsPalLayerSettings::MultiLineAlignment ).toInt() );
}
}
if ( ddValues.contains( QgsPalLayerSettings::DirSymbDraw ) )
{
tmpLyr.addDirectionSymbol = ddValues.value( QgsPalLayerSettings::DirSymbDraw ).toBool();
}
if ( tmpLyr.addDirectionSymbol )
{
if ( ddValues.contains( QgsPalLayerSettings::DirSymbLeft ) )
{
tmpLyr.leftDirectionSymbol = ddValues.value( QgsPalLayerSettings::DirSymbLeft ).toString();
}
if ( ddValues.contains( QgsPalLayerSettings::DirSymbRight ) )
{
tmpLyr.rightDirectionSymbol = ddValues.value( QgsPalLayerSettings::DirSymbRight ).toString();
}
if ( ddValues.contains( QgsPalLayerSettings::DirSymbPlacement ) )
{
tmpLyr.placeDirectionSymbol = static_cast< QgsPalLayerSettings::DirectionSymbols >( ddValues.value( QgsPalLayerSettings::DirSymbPlacement ).toInt() );
}
if ( ddValues.contains( QgsPalLayerSettings::DirSymbReverse ) )
{
tmpLyr.reverseDirectionSymbol = 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 ) )
{
QgsUnitTypes::RenderUnit bufunit = static_cast< QgsUnitTypes::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::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< QgsUnitTypes::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< QgsUnitTypes::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< QgsUnitTypes::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< QgsUnitTypes::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< QgsUnitTypes::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< QgsUnitTypes::RenderUnit >( ddValues.value( QgsPalLayerSettings::ShadowRadiusUnits ).toInt() ) );
changed = true;
}
if ( ddValues.contains( QgsPalLayerSettings::ShadowColor ) )
{
QVariant ddColor = ddValues.value( QgsPalLayerSettings::ShadowCol