LCOV - code coverage report
Current view: top level - core/labeling - qgspallabeling.cpp (source / functions) Hit Total Coverage
Test: coverage.info.cleaned Lines: 0 2630 0.0 %
Date: 2021-04-10 08:29:14 Functions: 0 0 -
Branches: 0 0 -

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

Generated by: LCOV version 1.14