LCOV - code coverage report
Current view: top level - core/textrenderer - qgstextrenderer.cpp (source / functions) Hit Total Coverage
Test: coverage.info.cleaned Lines: 0 1033 0.0 %
Date: 2021-03-26 12:19:53 Functions: 0 0 -
Branches: 0 0 -

           Branch data     Line data    Source code
       1                 :            : /***************************************************************************
       2                 :            :   qgstextrenderer.cpp
       3                 :            :   -------------------
       4                 :            :    begin                : September 2015
       5                 :            :    copyright            : (C) Nyall Dawson
       6                 :            :    email                : nyall dot dawson at gmail dot com
       7                 :            :  ***************************************************************************
       8                 :            :  *                                                                         *
       9                 :            :  *   This program is free software; you can redistribute it and/or modify  *
      10                 :            :  *   it under the terms of the GNU General Public License as published by  *
      11                 :            :  *   the Free Software Foundation; either version 2 of the License, or     *
      12                 :            :  *   (at your option) any later version.                                   *
      13                 :            :  *                                                                         *
      14                 :            :  ***************************************************************************/
      15                 :            : 
      16                 :            : #include "qgstextrenderer.h"
      17                 :            : #include "qgsvectorlayer.h"
      18                 :            : #include "qgstextformat.h"
      19                 :            : #include "qgstextdocument.h"
      20                 :            : #include "qgstextfragment.h"
      21                 :            : #include "qgspallabeling.h"
      22                 :            : #include "qgspainteffect.h"
      23                 :            : #include "qgspainterswapper.h"
      24                 :            : #include "qgsmarkersymbollayer.h"
      25                 :            : #include "qgssymbollayerutils.h"
      26                 :            : 
      27                 :            : #include <QTextBoundaryFinder>
      28                 :            : 
      29                 :            : Q_GUI_EXPORT extern int qt_defaultDpiX();
      30                 :            : Q_GUI_EXPORT extern int qt_defaultDpiY();
      31                 :            : 
      32                 :          0 : static void _fixQPictureDPI( QPainter *p )
      33                 :            : {
      34                 :            :   // QPicture makes an assumption that we drawing to it with system DPI.
      35                 :            :   // Then when being drawn, it scales the painter. The following call
      36                 :            :   // negates the effect. There is no way of setting QPicture's DPI.
      37                 :            :   // See QTBUG-20361
      38                 :          0 :   p->scale( static_cast< double >( qt_defaultDpiX() ) / p->device()->logicalDpiX(),
      39                 :          0 :             static_cast< double >( qt_defaultDpiY() ) / p->device()->logicalDpiY() );
      40                 :          0 : }
      41                 :            : 
      42                 :          0 : QgsTextRenderer::HAlignment QgsTextRenderer::convertQtHAlignment( Qt::Alignment alignment )
      43                 :            : {
      44                 :          0 :   if ( alignment & Qt::AlignLeft )
      45                 :          0 :     return AlignLeft;
      46                 :          0 :   else if ( alignment & Qt::AlignRight )
      47                 :          0 :     return AlignRight;
      48                 :          0 :   else if ( alignment & Qt::AlignHCenter )
      49                 :          0 :     return AlignCenter;
      50                 :          0 :   else if ( alignment & Qt::AlignJustify )
      51                 :          0 :     return AlignJustify;
      52                 :            : 
      53                 :            :   // not supported?
      54                 :          0 :   return AlignLeft;
      55                 :          0 : }
      56                 :            : 
      57                 :          0 : QgsTextRenderer::VAlignment QgsTextRenderer::convertQtVAlignment( Qt::Alignment alignment )
      58                 :            : {
      59                 :          0 :   if ( alignment & Qt::AlignTop )
      60                 :          0 :     return AlignTop;
      61                 :          0 :   else if ( alignment & Qt::AlignBottom )
      62                 :          0 :     return AlignBottom;
      63                 :          0 :   else if ( alignment & Qt::AlignVCenter )
      64                 :          0 :     return AlignVCenter;
      65                 :            :   //not supported
      66                 :          0 :   else if ( alignment & Qt::AlignBaseline )
      67                 :          0 :     return AlignBottom;
      68                 :            : 
      69                 :          0 :   return AlignTop;
      70                 :          0 : }
      71                 :            : 
      72                 :          0 : int QgsTextRenderer::sizeToPixel( double size, const QgsRenderContext &c, QgsUnitTypes::RenderUnit unit, const QgsMapUnitScale &mapUnitScale )
      73                 :            : {
      74                 :          0 :   return static_cast< int >( c.convertToPainterUnits( size, unit, mapUnitScale ) + 0.5 ); //NOLINT
      75                 :            : }
      76                 :            : 
      77                 :          0 : void QgsTextRenderer::drawText( const QRectF &rect, double rotation, QgsTextRenderer::HAlignment alignment, const QStringList &textLines, QgsRenderContext &context, const QgsTextFormat &format, bool, VAlignment vAlignment )
      78                 :            : {
      79                 :          0 :   QgsTextFormat tmpFormat = format;
      80                 :          0 :   if ( format.dataDefinedProperties().hasActiveProperties() ) // note, we use format instead of tmpFormat here, it's const and potentially avoids a detach
      81                 :          0 :     tmpFormat.updateDataDefinedProperties( context );
      82                 :          0 :   tmpFormat = updateShadowPosition( tmpFormat );
      83                 :            : 
      84                 :          0 :   QgsTextDocument document = format.allowHtmlFormatting() ? QgsTextDocument::fromHtml( textLines ) : QgsTextDocument::fromPlainText( textLines );
      85                 :          0 :   document.applyCapitalization( format.capitalization() );
      86                 :            : 
      87                 :          0 :   if ( tmpFormat.background().enabled() )
      88                 :            :   {
      89                 :          0 :     drawPart( rect, rotation, alignment, vAlignment, document, context, tmpFormat, Background );
      90                 :          0 :   }
      91                 :            : 
      92                 :          0 :   if ( tmpFormat.buffer().enabled() )
      93                 :            :   {
      94                 :          0 :     drawPart( rect, rotation, alignment, vAlignment, document, context, tmpFormat, Buffer );
      95                 :          0 :   }
      96                 :            : 
      97                 :          0 :   drawPart( rect, rotation, alignment, vAlignment, document, context, tmpFormat, Text );
      98                 :          0 : }
      99                 :            : 
     100                 :          0 : void QgsTextRenderer::drawText( QPointF point, double rotation, QgsTextRenderer::HAlignment alignment, const QStringList &textLines, QgsRenderContext &context, const QgsTextFormat &format, bool )
     101                 :            : {
     102                 :          0 :   QgsTextFormat tmpFormat = format;
     103                 :          0 :   if ( format.dataDefinedProperties().hasActiveProperties() ) // note, we use format instead of tmpFormat here, it's const and potentially avoids a detach
     104                 :          0 :     tmpFormat.updateDataDefinedProperties( context );
     105                 :          0 :   tmpFormat = updateShadowPosition( tmpFormat );
     106                 :            : 
     107                 :          0 :   QgsTextDocument document = format.allowHtmlFormatting() ? QgsTextDocument::fromHtml( textLines ) : QgsTextDocument::fromPlainText( textLines );
     108                 :          0 :   document.applyCapitalization( format.capitalization() );
     109                 :            : 
     110                 :          0 :   if ( tmpFormat.background().enabled() )
     111                 :            :   {
     112                 :          0 :     drawPart( point, rotation, alignment, document, context, tmpFormat, Background );
     113                 :          0 :   }
     114                 :            : 
     115                 :          0 :   if ( tmpFormat.buffer().enabled() )
     116                 :            :   {
     117                 :          0 :     drawPart( point, rotation, alignment, document, context, tmpFormat, Buffer );
     118                 :          0 :   }
     119                 :            : 
     120                 :          0 :   drawPart( point, rotation, alignment, document, context, tmpFormat, Text );
     121                 :          0 : }
     122                 :            : 
     123                 :          0 : QgsTextFormat QgsTextRenderer::updateShadowPosition( const QgsTextFormat &format )
     124                 :            : {
     125                 :          0 :   if ( !format.shadow().enabled() || format.shadow().shadowPlacement() != QgsTextShadowSettings::ShadowLowest )
     126                 :          0 :     return format;
     127                 :            : 
     128                 :          0 :   QgsTextFormat tmpFormat = format;
     129                 :          0 :   if ( tmpFormat.background().enabled() && tmpFormat.background().type() != QgsTextBackgroundSettings::ShapeMarkerSymbol ) // background shadow not compatible with marker symbol backgrounds
     130                 :            :   {
     131                 :          0 :     tmpFormat.shadow().setShadowPlacement( QgsTextShadowSettings::ShadowShape );
     132                 :          0 :   }
     133                 :          0 :   else if ( tmpFormat.buffer().enabled() )
     134                 :            :   {
     135                 :          0 :     tmpFormat.shadow().setShadowPlacement( QgsTextShadowSettings::ShadowBuffer );
     136                 :          0 :   }
     137                 :            :   else
     138                 :            :   {
     139                 :          0 :     tmpFormat.shadow().setShadowPlacement( QgsTextShadowSettings::ShadowText );
     140                 :            :   }
     141                 :          0 :   return tmpFormat;
     142                 :          0 : }
     143                 :            : 
     144                 :          0 : void QgsTextRenderer::drawPart( const QRectF &rect, double rotation, HAlignment alignment,
     145                 :            :                                 const QStringList &textLines, QgsRenderContext &context, const QgsTextFormat &format, QgsTextRenderer::TextPart part, bool )
     146                 :            : {
     147                 :          0 :   const QgsTextDocument document = format.allowHtmlFormatting() ? QgsTextDocument::fromHtml( textLines ) : QgsTextDocument::fromPlainText( textLines );
     148                 :            : 
     149                 :          0 :   drawPart( rect, rotation, alignment, AlignTop, document, context, format, part );
     150                 :          0 : }
     151                 :            : 
     152                 :          0 : void QgsTextRenderer::drawPart( const QRectF &rect, double rotation, QgsTextRenderer::HAlignment alignment, VAlignment vAlignment, const QgsTextDocument &document, QgsRenderContext &context, const QgsTextFormat &format, QgsTextRenderer::TextPart part )
     153                 :            : {
     154                 :          0 :   if ( !context.painter() )
     155                 :            :   {
     156                 :          0 :     return;
     157                 :            :   }
     158                 :            : 
     159                 :          0 :   Component component;
     160                 :          0 :   component.dpiRatio = 1.0;
     161                 :          0 :   component.origin = rect.topLeft();
     162                 :          0 :   component.rotation = rotation;
     163                 :          0 :   component.size = rect.size();
     164                 :          0 :   component.hAlign = alignment;
     165                 :            : 
     166                 :          0 :   switch ( part )
     167                 :            :   {
     168                 :            :     case Background:
     169                 :            :     {
     170                 :          0 :       if ( !format.background().enabled() )
     171                 :          0 :         return;
     172                 :            : 
     173                 :          0 :       if ( !qgsDoubleNear( rotation, 0.0 ) )
     174                 :            :       {
     175                 :            :         // get rotated label's center point
     176                 :            : 
     177                 :          0 :         double xc = rect.width() / 2.0;
     178                 :          0 :         double yc = rect.height() / 2.0;
     179                 :            : 
     180                 :          0 :         double angle = -rotation;
     181                 :          0 :         double xd = xc * std::cos( angle ) - yc * std::sin( angle );
     182                 :          0 :         double yd = xc * std::sin( angle ) + yc * std::cos( angle );
     183                 :            : 
     184                 :          0 :         component.center = QPointF( component.origin.x() + xd, component.origin.y() + yd );
     185                 :          0 :       }
     186                 :            :       else
     187                 :            :       {
     188                 :          0 :         component.center = rect.center();
     189                 :            :       }
     190                 :            : 
     191                 :          0 :       QgsTextRenderer::drawBackground( context, component, format, document, Rect );
     192                 :            : 
     193                 :          0 :       break;
     194                 :            :     }
     195                 :            : 
     196                 :            :     case Buffer:
     197                 :            :     {
     198                 :          0 :       if ( !format.buffer().enabled() )
     199                 :          0 :         break;
     200                 :          0 :     }
     201                 :            :     FALLTHROUGH
     202                 :            :     case Text:
     203                 :            :     case Shadow:
     204                 :            :     {
     205                 :          0 :       drawTextInternal( part, context, format, component,
     206                 :          0 :                         document,
     207                 :            :                         nullptr,
     208                 :          0 :                         alignment, vAlignment );
     209                 :          0 :       break;
     210                 :            :     }
     211                 :            :   }
     212                 :          0 : }
     213                 :            : 
     214                 :          0 : void QgsTextRenderer::drawPart( QPointF origin, double rotation, QgsTextRenderer::HAlignment alignment, const QStringList &textLines, QgsRenderContext &context, const QgsTextFormat &format, QgsTextRenderer::TextPart part, bool )
     215                 :            : {
     216                 :          0 :   const QgsTextDocument document = format.allowHtmlFormatting() ? QgsTextDocument::fromHtml( textLines ) : QgsTextDocument::fromPlainText( textLines );
     217                 :          0 :   drawPart( origin, rotation, alignment, document, context, format, part );
     218                 :          0 : }
     219                 :            : 
     220                 :          0 : void QgsTextRenderer::drawPart( QPointF origin, double rotation, QgsTextRenderer::HAlignment alignment, const QgsTextDocument &document, QgsRenderContext &context, const QgsTextFormat &format, QgsTextRenderer::TextPart part )
     221                 :            : {
     222                 :          0 :   if ( !context.painter() )
     223                 :            :   {
     224                 :          0 :     return;
     225                 :            :   }
     226                 :            : 
     227                 :          0 :   Component component;
     228                 :          0 :   component.dpiRatio = 1.0;
     229                 :          0 :   component.origin = origin;
     230                 :          0 :   component.rotation = rotation;
     231                 :          0 :   component.hAlign = alignment;
     232                 :            : 
     233                 :          0 :   switch ( part )
     234                 :            :   {
     235                 :            :     case Background:
     236                 :            :     {
     237                 :          0 :       if ( !format.background().enabled() )
     238                 :          0 :         return;
     239                 :            : 
     240                 :          0 :       QgsTextRenderer::drawBackground( context, component, format, document, Point );
     241                 :          0 :       break;
     242                 :            :     }
     243                 :            : 
     244                 :            :     case Buffer:
     245                 :            :     {
     246                 :          0 :       if ( !format.buffer().enabled() )
     247                 :          0 :         break;
     248                 :          0 :     }
     249                 :            :     FALLTHROUGH
     250                 :            :     case Text:
     251                 :            :     case Shadow:
     252                 :            :     {
     253                 :          0 :       drawTextInternal( part, context, format, component,
     254                 :          0 :                         document,
     255                 :            :                         nullptr,
     256                 :          0 :                         alignment, AlignTop,
     257                 :            :                         Point );
     258                 :          0 :       break;
     259                 :            :     }
     260                 :            :   }
     261                 :          0 : }
     262                 :            : 
     263                 :          0 : QFontMetricsF QgsTextRenderer::fontMetrics( QgsRenderContext &context, const QgsTextFormat &format, const double scaleFactor )
     264                 :            : {
     265                 :          0 :   return QFontMetricsF( format.scaledFont( context, scaleFactor ), context.painter() ? context.painter()->device() : nullptr );
     266                 :          0 : }
     267                 :            : 
     268                 :          0 : double QgsTextRenderer::drawBuffer( QgsRenderContext &context, const QgsTextRenderer::Component &component, const QgsTextFormat &format )
     269                 :            : {
     270                 :          0 :   QPainter *p = context.painter();
     271                 :            : 
     272                 :          0 :   QgsTextFormat::TextOrientation orientation = format.orientation();
     273                 :          0 :   if ( format.orientation() == QgsTextFormat::RotationBasedOrientation )
     274                 :            :   {
     275                 :          0 :     if ( component.rotation >= -315 && component.rotation < -90 )
     276                 :            :     {
     277                 :          0 :       orientation = QgsTextFormat::VerticalOrientation;
     278                 :          0 :     }
     279                 :          0 :     else if ( component.rotation >= -90 && component.rotation < -45 )
     280                 :            :     {
     281                 :          0 :       orientation = QgsTextFormat::VerticalOrientation;
     282                 :          0 :     }
     283                 :            :     else
     284                 :            :     {
     285                 :          0 :       orientation = QgsTextFormat::HorizontalOrientation;
     286                 :            :     }
     287                 :          0 :   }
     288                 :            : 
     289                 :          0 :   QgsTextBufferSettings buffer = format.buffer();
     290                 :            : 
     291                 :          0 :   const double penSize = context.convertToPainterUnits( buffer.size(), buffer.sizeUnit(), buffer.sizeMapUnitScale() );
     292                 :            : 
     293                 :          0 :   const double scaleFactor = ( context.flags() & QgsRenderContext::ApplyScalingWorkaroundForTextRendering ) ? FONT_WORKAROUND_SCALE : 1.0;
     294                 :          0 :   const QFont font = format.scaledFont( context, scaleFactor );
     295                 :            : 
     296                 :          0 :   QPainterPath path;
     297                 :          0 :   path.setFillRule( Qt::WindingFill );
     298                 :          0 :   double advance = 0;
     299                 :          0 :   switch ( orientation )
     300                 :            :   {
     301                 :            :     case QgsTextFormat::HorizontalOrientation:
     302                 :            :     {
     303                 :          0 :       double xOffset = 0;
     304                 :          0 :       for ( const QgsTextFragment &fragment : component.block )
     305                 :            :       {
     306                 :          0 :         QFont fragmentFont = font;
     307                 :          0 :         fragment.characterFormat().updateFontForFormat( fragmentFont, scaleFactor );
     308                 :            : 
     309                 :          0 :         if ( component.extraWordSpacing || component.extraLetterSpacing )
     310                 :          0 :           applyExtraSpacingForLineJustification( fragmentFont, component.extraWordSpacing, component.extraLetterSpacing );
     311                 :            : 
     312                 :          0 :         path.addText( xOffset, 0, fragmentFont, fragment.text() );
     313                 :            : 
     314                 :          0 :         xOffset += fragment.horizontalAdvance( fragmentFont, true, scaleFactor );
     315                 :          0 :       }
     316                 :          0 :       advance = xOffset;
     317                 :          0 :       break;
     318                 :            :     }
     319                 :            : 
     320                 :            :     case QgsTextFormat::VerticalOrientation:
     321                 :            :     case QgsTextFormat::RotationBasedOrientation:
     322                 :            :     {
     323                 :          0 :       double letterSpacing = font.letterSpacing();
     324                 :          0 :       double partYOffset = component.offset.y() * scaleFactor;
     325                 :          0 :       for ( const QgsTextFragment &fragment : component.block )
     326                 :            :       {
     327                 :          0 :         QFont fragmentFont = font;
     328                 :          0 :         fragment.characterFormat().updateFontForFormat( fragmentFont, scaleFactor );
     329                 :            : 
     330                 :          0 :         QFontMetricsF fragmentMetrics( fragmentFont );
     331                 :          0 :         const double labelWidth = fragmentMetrics.maxWidth();
     332                 :            : 
     333                 :          0 :         const QStringList parts = QgsPalLabeling::splitToGraphemes( fragment.text() );
     334                 :          0 :         for ( const QString &part : parts )
     335                 :            :         {
     336                 :          0 :           double partXOffset = ( labelWidth - ( fragmentMetrics.horizontalAdvance( part ) - letterSpacing ) ) / 2;
     337                 :          0 :           path.addText( partXOffset, partYOffset, fragmentFont, part );
     338                 :          0 :           partYOffset += fragmentMetrics.ascent() + letterSpacing;
     339                 :            :         }
     340                 :          0 :       }
     341                 :          0 :       advance = partYOffset - component.offset.y() * scaleFactor;
     342                 :          0 :       break;
     343                 :            :     }
     344                 :            :   }
     345                 :            : 
     346                 :          0 :   QColor bufferColor = buffer.color();
     347                 :          0 :   bufferColor.setAlphaF( buffer.opacity() );
     348                 :          0 :   QPen pen( bufferColor );
     349                 :          0 :   pen.setWidthF( penSize * scaleFactor );
     350                 :          0 :   pen.setJoinStyle( buffer.joinStyle() );
     351                 :          0 :   QColor tmpColor( bufferColor );
     352                 :            :   // honor pref for whether to fill buffer interior
     353                 :          0 :   if ( !buffer.fillBufferInterior() )
     354                 :            :   {
     355                 :          0 :     tmpColor.setAlpha( 0 );
     356                 :          0 :   }
     357                 :            : 
     358                 :            :   // store buffer's drawing in QPicture for drop shadow call
     359                 :          0 :   QPicture buffPict;
     360                 :          0 :   QPainter buffp;
     361                 :          0 :   buffp.begin( &buffPict );
     362                 :          0 :   if ( buffer.paintEffect() && buffer.paintEffect()->enabled() )
     363                 :            :   {
     364                 :          0 :     context.setPainter( &buffp );
     365                 :          0 :     std::unique_ptr< QgsPaintEffect > tmpEffect( buffer.paintEffect()->clone() );
     366                 :            : 
     367                 :          0 :     tmpEffect->begin( context );
     368                 :          0 :     context.painter()->setPen( pen );
     369                 :          0 :     context.painter()->setBrush( tmpColor );
     370                 :          0 :     if ( scaleFactor != 1.0 )
     371                 :          0 :       context.painter()->scale( 1 / scaleFactor, 1 / scaleFactor );
     372                 :          0 :     context.painter()->drawPath( path );
     373                 :          0 :     if ( scaleFactor != 1.0 )
     374                 :          0 :       context.painter()->scale( scaleFactor, scaleFactor );
     375                 :          0 :     tmpEffect->end( context );
     376                 :            : 
     377                 :          0 :     context.setPainter( p );
     378                 :          0 :   }
     379                 :            :   else
     380                 :            :   {
     381                 :          0 :     if ( scaleFactor != 1.0 )
     382                 :          0 :       buffp.scale( 1 / scaleFactor, 1 / scaleFactor );
     383                 :          0 :     buffp.setPen( pen );
     384                 :          0 :     buffp.setBrush( tmpColor );
     385                 :          0 :     buffp.drawPath( path );
     386                 :            :   }
     387                 :          0 :   buffp.end();
     388                 :            : 
     389                 :          0 :   if ( format.shadow().enabled() && format.shadow().shadowPlacement() == QgsTextShadowSettings::ShadowBuffer )
     390                 :            :   {
     391                 :          0 :     QgsTextRenderer::Component bufferComponent = component;
     392                 :          0 :     bufferComponent.origin = QPointF( 0.0, 0.0 );
     393                 :          0 :     bufferComponent.picture = buffPict;
     394                 :          0 :     bufferComponent.pictureBuffer = penSize / 2.0;
     395                 :            : 
     396                 :          0 :     if ( format.orientation() == QgsTextFormat::VerticalOrientation || format.orientation() == QgsTextFormat::RotationBasedOrientation )
     397                 :            :     {
     398                 :          0 :       bufferComponent.offset.setY( bufferComponent.offset.y() - bufferComponent.size.height() );
     399                 :          0 :     }
     400                 :          0 :     drawShadow( context, bufferComponent, format );
     401                 :          0 :   }
     402                 :            : 
     403                 :          0 :   QgsScopedQPainterState painterState( p );
     404                 :          0 :   context.setPainterFlagsUsingContext( p );
     405                 :            : 
     406                 :          0 :   if ( context.useAdvancedEffects() )
     407                 :            :   {
     408                 :          0 :     p->setCompositionMode( buffer.blendMode() );
     409                 :          0 :   }
     410                 :            : 
     411                 :            :   // scale for any print output or image saving @ specific dpi
     412                 :          0 :   p->scale( component.dpiRatio, component.dpiRatio );
     413                 :          0 :   _fixQPictureDPI( p );
     414                 :          0 :   p->drawPicture( 0, 0, buffPict );
     415                 :            : 
     416                 :          0 :   return advance / scaleFactor;
     417                 :          0 : }
     418                 :            : 
     419                 :          0 : void QgsTextRenderer::drawMask( QgsRenderContext &context, const QgsTextRenderer::Component &component, const QgsTextFormat &format )
     420                 :            : {
     421                 :          0 :   QgsTextMaskSettings mask = format.mask();
     422                 :            : 
     423                 :            :   // the mask is drawn to a side painter
     424                 :            :   // or to the main painter for preview
     425                 :          0 :   QPainter *p = context.isGuiPreview() ? context.painter() : context.maskPainter( context.currentMaskId() );
     426                 :          0 :   if ( ! p )
     427                 :          0 :     return;
     428                 :            : 
     429                 :          0 :   double penSize = context.convertToPainterUnits( mask.size(), mask.sizeUnit(), mask.sizeMapUnitScale() );
     430                 :            : 
     431                 :            :   // buffer: draw the text with a big pen
     432                 :          0 :   QPainterPath path;
     433                 :          0 :   path.setFillRule( Qt::WindingFill );
     434                 :            : 
     435                 :          0 :   const double scaleFactor = ( context.flags() & QgsRenderContext::ApplyScalingWorkaroundForTextRendering ) ? FONT_WORKAROUND_SCALE : 1.0;
     436                 :            : 
     437                 :            :   // TODO: vertical text mode was ignored when masking feature was added.
     438                 :            :   // Hopefully Oslandia come back and fix this? Hint hint...
     439                 :            : 
     440                 :          0 :   const QFont font = format.scaledFont( context, scaleFactor );
     441                 :          0 :   double xOffset = 0;
     442                 :          0 :   for ( const QgsTextFragment &fragment : component.block )
     443                 :            :   {
     444                 :          0 :     QFont fragmentFont = font;
     445                 :          0 :     fragment.characterFormat().updateFontForFormat( fragmentFont, scaleFactor );
     446                 :            : 
     447                 :          0 :     path.addText( xOffset, 0, fragmentFont, fragment.text() );
     448                 :            : 
     449                 :          0 :     xOffset += fragment.horizontalAdvance( fragmentFont, true );
     450                 :          0 :   }
     451                 :            : 
     452                 :          0 :   QColor bufferColor( Qt::gray );
     453                 :          0 :   bufferColor.setAlphaF( mask.opacity() );
     454                 :            : 
     455                 :          0 :   QPen pen;
     456                 :          0 :   QBrush brush;
     457                 :          0 :   brush.setColor( bufferColor );
     458                 :          0 :   pen.setColor( bufferColor );
     459                 :          0 :   pen.setWidthF( penSize * scaleFactor );
     460                 :          0 :   pen.setJoinStyle( mask.joinStyle() );
     461                 :            : 
     462                 :          0 :   QgsScopedQPainterState painterState( p );
     463                 :          0 :   context.setPainterFlagsUsingContext( p );
     464                 :            : 
     465                 :            :   // scale for any print output or image saving @ specific dpi
     466                 :          0 :   p->scale( component.dpiRatio, component.dpiRatio );
     467                 :          0 :   if ( mask.paintEffect() && mask.paintEffect()->enabled() )
     468                 :            :   {
     469                 :          0 :     QgsPainterSwapper swapper( context, p );
     470                 :            :     {
     471                 :          0 :       QgsEffectPainter effectPainter( context, mask.paintEffect() );
     472                 :          0 :       if ( scaleFactor != 1.0 )
     473                 :          0 :         context.painter()->scale( 1 / scaleFactor, 1 / scaleFactor );
     474                 :          0 :       context.painter()->setPen( pen );
     475                 :          0 :       context.painter()->setBrush( brush );
     476                 :          0 :       context.painter()->drawPath( path );
     477                 :          0 :       if ( scaleFactor != 1.0 )
     478                 :          0 :         context.painter()->scale( scaleFactor, scaleFactor );
     479                 :          0 :     }
     480                 :          0 :   }
     481                 :            :   else
     482                 :            :   {
     483                 :          0 :     if ( scaleFactor != 1.0 )
     484                 :          0 :       p->scale( 1 / scaleFactor, 1 / scaleFactor );
     485                 :          0 :     p->setPen( pen );
     486                 :          0 :     p->setBrush( brush );
     487                 :          0 :     p->drawPath( path );
     488                 :          0 :     if ( scaleFactor != 1.0 )
     489                 :          0 :       p->scale( scaleFactor, scaleFactor );
     490                 :            : 
     491                 :            :   }
     492                 :          0 : }
     493                 :            : 
     494                 :          0 : double QgsTextRenderer::textWidth( const QgsRenderContext &context, const QgsTextFormat &format, const QStringList &textLines, QFontMetricsF * )
     495                 :            : {
     496                 :          0 :   QgsTextDocument doc;
     497                 :          0 :   if ( !format.allowHtmlFormatting() )
     498                 :            :   {
     499                 :          0 :     doc = QgsTextDocument::fromPlainText( textLines );
     500                 :          0 :   }
     501                 :            :   else
     502                 :            :   {
     503                 :          0 :     doc = QgsTextDocument::fromHtml( textLines );
     504                 :            :   }
     505                 :          0 :   doc.applyCapitalization( format.capitalization() );
     506                 :          0 :   return textWidth( context, format, doc );
     507                 :          0 : }
     508                 :            : 
     509                 :          0 : double QgsTextRenderer::textWidth( const QgsRenderContext &context, const QgsTextFormat &format, const QgsTextDocument &document )
     510                 :            : {
     511                 :            :   //calculate max width of text lines
     512                 :          0 :   const double scaleFactor = ( context.flags() & QgsRenderContext::ApplyScalingWorkaroundForTextRendering ) ? FONT_WORKAROUND_SCALE : 1.0;
     513                 :          0 :   const QFont baseFont = format.scaledFont( context, scaleFactor );
     514                 :            : 
     515                 :          0 :   double width = 0;
     516                 :          0 :   switch ( format.orientation() )
     517                 :            :   {
     518                 :            :     case QgsTextFormat::HorizontalOrientation:
     519                 :            :     {
     520                 :          0 :       double maxLineWidth = 0;
     521                 :          0 :       for ( const QgsTextBlock &block : document )
     522                 :            :       {
     523                 :          0 :         double blockWidth = 0;
     524                 :          0 :         for ( const QgsTextFragment &fragment : block )
     525                 :            :         {
     526                 :          0 :           blockWidth += fragment.horizontalAdvance( baseFont, scaleFactor );
     527                 :            :         }
     528                 :          0 :         maxLineWidth = std::max( maxLineWidth, blockWidth );
     529                 :            :       }
     530                 :          0 :       width = maxLineWidth;
     531                 :          0 :       break;
     532                 :            :     }
     533                 :            : 
     534                 :            :     case QgsTextFormat::VerticalOrientation:
     535                 :            :     {
     536                 :          0 :       double totalLineWidth = 0;
     537                 :          0 :       int blockIndex = 0;
     538                 :          0 :       for ( const QgsTextBlock &block : document )
     539                 :            :       {
     540                 :          0 :         double blockWidth = 0;
     541                 :          0 :         for ( const QgsTextFragment &fragment : block )
     542                 :            :         {
     543                 :          0 :           QFont fragmentFont = baseFont;
     544                 :          0 :           fragment.characterFormat().updateFontForFormat( fragmentFont, scaleFactor );
     545                 :          0 :           blockWidth = std::max( QFontMetricsF( fragmentFont ).maxWidth(), blockWidth );
     546                 :          0 :         }
     547                 :            : 
     548                 :          0 :         totalLineWidth += blockIndex == 0 ? blockWidth : blockWidth * format.lineHeight();
     549                 :          0 :         blockIndex++;
     550                 :            :       }
     551                 :          0 :       width = totalLineWidth;
     552                 :          0 :       break;
     553                 :            :     }
     554                 :            : 
     555                 :            :     case QgsTextFormat::RotationBasedOrientation:
     556                 :            :     {
     557                 :            :       // label mode only
     558                 :          0 :       break;
     559                 :            :     }
     560                 :            :   }
     561                 :            : 
     562                 :          0 :   return width / scaleFactor;
     563                 :          0 : }
     564                 :            : 
     565                 :          0 : double QgsTextRenderer::textHeight( const QgsRenderContext &context, const QgsTextFormat &format, const QStringList &textLines, DrawMode mode, QFontMetricsF * )
     566                 :            : {
     567                 :          0 :   if ( !format.allowHtmlFormatting() )
     568                 :            :   {
     569                 :          0 :     return textHeight( context, format, QgsTextDocument::fromPlainText( textLines ), mode );
     570                 :            :   }
     571                 :            :   else
     572                 :            :   {
     573                 :          0 :     return textHeight( context, format, QgsTextDocument::fromHtml( textLines ), mode );
     574                 :            :   }
     575                 :          0 : }
     576                 :            : 
     577                 :          0 : double QgsTextRenderer::textHeight( const QgsRenderContext &context, const QgsTextFormat &format, QChar character, bool includeEffects )
     578                 :            : {
     579                 :          0 :   const double scaleFactor = ( context.flags() & QgsRenderContext::ApplyScalingWorkaroundForTextRendering ) ? FONT_WORKAROUND_SCALE : 1.0;
     580                 :          0 :   const QFont baseFont = format.scaledFont( context, scaleFactor );
     581                 :          0 :   const QFontMetrics fm( baseFont );
     582                 :          0 :   const double height = ( character.isNull() ? fm.height() : fm.boundingRect( character ).height() ) / scaleFactor;
     583                 :            : 
     584                 :          0 :   if ( !includeEffects )
     585                 :          0 :     return height;
     586                 :            : 
     587                 :          0 :   double maxExtension = 0;
     588                 :          0 :   if ( format.buffer().enabled() )
     589                 :            :   {
     590                 :          0 :     maxExtension += context.convertToPainterUnits( format.buffer().size(), format.buffer().sizeUnit(), format.buffer().sizeMapUnitScale() );
     591                 :          0 :   }
     592                 :          0 :   if ( format.shadow().enabled() )
     593                 :            :   {
     594                 :          0 :     maxExtension += context.convertToPainterUnits( format.shadow().offsetDistance(), format.shadow().offsetUnit(), format.shadow().offsetMapUnitScale() )
     595                 :          0 :                     + context.convertToPainterUnits( format.shadow().blurRadius(), format.shadow().blurRadiusUnit(), format.shadow().blurRadiusMapUnitScale() );
     596                 :          0 :   }
     597                 :          0 :   if ( format.background().enabled() )
     598                 :            :   {
     599                 :          0 :     maxExtension += context.convertToPainterUnits( std::fabs( format.background().offset().y() ), format.background().offsetUnit(), format.background().offsetMapUnitScale() )
     600                 :          0 :                     + context.convertToPainterUnits( format.background().strokeWidth(), format.background().strokeWidthUnit(), format.background().strokeWidthMapUnitScale() ) / 2.0;
     601                 :          0 :     if ( format.background().sizeType() == QgsTextBackgroundSettings::SizeBuffer && format.background().size().height() > 0 )
     602                 :            :     {
     603                 :          0 :       maxExtension += context.convertToPainterUnits( format.background().size().height(), format.background().sizeUnit(), format.background().sizeMapUnitScale() );
     604                 :          0 :     }
     605                 :          0 :   }
     606                 :            : 
     607                 :          0 :   return height + maxExtension;
     608                 :          0 : }
     609                 :            : 
     610                 :          0 : double QgsTextRenderer::textHeight( const QgsRenderContext &context, const QgsTextFormat &format, const QgsTextDocument &doc, DrawMode mode )
     611                 :            : {
     612                 :          0 :   QgsTextDocument document = doc;
     613                 :          0 :   document.applyCapitalization( format.capitalization() );
     614                 :            : 
     615                 :            :   //calculate max height of text lines
     616                 :          0 :   const double scaleFactor = ( context.flags() & QgsRenderContext::ApplyScalingWorkaroundForTextRendering ) ? FONT_WORKAROUND_SCALE : 1.0;
     617                 :            : 
     618                 :          0 :   const QFont baseFont = format.scaledFont( context, scaleFactor );
     619                 :            : 
     620                 :          0 :   switch ( format.orientation() )
     621                 :            :   {
     622                 :            :     case QgsTextFormat::HorizontalOrientation:
     623                 :            :     {
     624                 :          0 :       int blockIndex = 0;
     625                 :          0 :       double totalHeight = 0;
     626                 :          0 :       double lastLineLeading = 0;
     627                 :          0 :       for ( const QgsTextBlock &block : document )
     628                 :            :       {
     629                 :          0 :         double maxBlockHeight = 0;
     630                 :          0 :         double maxBlockLineSpacing = 0;
     631                 :          0 :         double maxBlockLeading = 0;
     632                 :          0 :         for ( const QgsTextFragment &fragment : block )
     633                 :            :         {
     634                 :          0 :           QFont fragmentFont = baseFont;
     635                 :          0 :           fragment.characterFormat().updateFontForFormat( fragmentFont, scaleFactor );
     636                 :          0 :           const QFontMetricsF fm( fragmentFont );
     637                 :            : 
     638                 :          0 :           const double fragmentHeight = fm.ascent() + fm.descent(); // ignore +1 for baseline
     639                 :            : 
     640                 :          0 :           maxBlockHeight = std::max( maxBlockHeight, fragmentHeight );
     641                 :          0 :           if ( fm.lineSpacing() > maxBlockLineSpacing )
     642                 :            :           {
     643                 :          0 :             maxBlockLineSpacing = fm.lineSpacing();
     644                 :          0 :             maxBlockLeading = fm.leading();
     645                 :          0 :           }
     646                 :          0 :         }
     647                 :            : 
     648                 :          0 :         switch ( mode )
     649                 :            :         {
     650                 :            :           case Label:
     651                 :            :             // rendering labels needs special handling - in this case text should be
     652                 :            :             // drawn with the bottom left corner coinciding with origin, vs top left
     653                 :            :             // for standard text rendering. Line height is also slightly different.
     654                 :          0 :             totalHeight += blockIndex == 0 ? maxBlockHeight : maxBlockHeight * format.lineHeight();
     655                 :          0 :             break;
     656                 :            : 
     657                 :            :           case Rect:
     658                 :            :           case Point:
     659                 :            :             // standard rendering - designed to exactly replicate QPainter's drawText method
     660                 :          0 :             totalHeight += blockIndex == 0 ? maxBlockHeight : maxBlockLineSpacing * format.lineHeight();
     661                 :          0 :             if ( blockIndex > 0 )
     662                 :          0 :               lastLineLeading = maxBlockLeading;
     663                 :          0 :             break;
     664                 :            :         }
     665                 :            : 
     666                 :          0 :         blockIndex++;
     667                 :            :       }
     668                 :            : 
     669                 :          0 :       return ( totalHeight - lastLineLeading ) / scaleFactor;
     670                 :            :     }
     671                 :            : 
     672                 :            :     case QgsTextFormat::VerticalOrientation:
     673                 :            :     {
     674                 :          0 :       double maxBlockHeight = 0;
     675                 :          0 :       for ( const QgsTextBlock &block : document )
     676                 :            :       {
     677                 :          0 :         double blockHeight = 0;
     678                 :          0 :         int fragmentIndex = 0;
     679                 :          0 :         for ( const QgsTextFragment &fragment : block )
     680                 :            :         {
     681                 :          0 :           QFont fragmentFont = baseFont;
     682                 :          0 :           fragment.characterFormat().updateFontForFormat( fragmentFont, scaleFactor );
     683                 :          0 :           const QFontMetricsF fm( fragmentFont );
     684                 :            : 
     685                 :          0 :           const double labelHeight = fm.ascent();
     686                 :          0 :           const double letterSpacing = fragmentFont.letterSpacing();
     687                 :            : 
     688                 :          0 :           blockHeight += fragmentIndex = 0 ? labelHeight * fragment.text().size() + ( fragment.text().size() - 1 ) * letterSpacing
     689                 :          0 :                                          : fragment.text().size() * ( labelHeight + letterSpacing );
     690                 :          0 :           fragmentIndex++;
     691                 :          0 :         }
     692                 :          0 :         maxBlockHeight = std::max( maxBlockHeight, blockHeight );
     693                 :            :       }
     694                 :            : 
     695                 :          0 :       return maxBlockHeight / scaleFactor;
     696                 :            :     }
     697                 :            : 
     698                 :            :     case QgsTextFormat::RotationBasedOrientation:
     699                 :            :     {
     700                 :            :       // label mode only
     701                 :          0 :       break;
     702                 :            :     }
     703                 :            :   }
     704                 :            : 
     705                 :          0 :   return 0;
     706                 :          0 : }
     707                 :            : 
     708                 :          0 : void QgsTextRenderer::drawBackground( QgsRenderContext &context, QgsTextRenderer::Component component, const QgsTextFormat &format, const QgsTextDocument &document, QgsTextRenderer::DrawMode mode )
     709                 :            : {
     710                 :          0 :   QgsTextBackgroundSettings background = format.background();
     711                 :            : 
     712                 :          0 :   QPainter *prevP = context.painter();
     713                 :          0 :   QPainter *p = context.painter();
     714                 :          0 :   std::unique_ptr< QgsPaintEffect > tmpEffect;
     715                 :          0 :   if ( background.paintEffect() && background.paintEffect()->enabled() )
     716                 :            :   {
     717                 :          0 :     tmpEffect.reset( background.paintEffect()->clone() );
     718                 :          0 :     tmpEffect->begin( context );
     719                 :          0 :     p = context.painter();
     720                 :          0 :   }
     721                 :            : 
     722                 :            :   //QgsDebugMsgLevel( QStringLiteral( "Background label rotation: %1" ).arg( component.rotation() ), 4 );
     723                 :            : 
     724                 :            :   // shared calculations between shapes and SVG
     725                 :            : 
     726                 :            :   // configure angles, set component rotation and rotationOffset
     727                 :          0 :   if ( background.rotationType() != QgsTextBackgroundSettings::RotationFixed )
     728                 :            :   {
     729                 :          0 :     component.rotation = -( component.rotation * 180 / M_PI ); // RotationSync
     730                 :          0 :     component.rotationOffset =
     731                 :          0 :       background.rotationType() == QgsTextBackgroundSettings::RotationOffset ? background.rotation() : 0.0;
     732                 :          0 :   }
     733                 :            :   else // RotationFixed
     734                 :            :   {
     735                 :          0 :     component.rotation = 0.0; // don't use label's rotation
     736                 :          0 :     component.rotationOffset = background.rotation();
     737                 :            :   }
     738                 :            : 
     739                 :          0 :   const double scaleFactor = ( context.flags() & QgsRenderContext::ApplyScalingWorkaroundForTextRendering ) ? FONT_WORKAROUND_SCALE : 1;
     740                 :            : 
     741                 :          0 :   if ( mode != Label )
     742                 :            :   {
     743                 :            :     // need to calculate size of text
     744                 :          0 :     QFontMetricsF fm( format.scaledFont( context, scaleFactor ) );
     745                 :          0 :     double width = textWidth( context, format, document );
     746                 :          0 :     double height = textHeight( context, format, document, mode );
     747                 :            : 
     748                 :          0 :     switch ( mode )
     749                 :            :     {
     750                 :            :       case Rect:
     751                 :          0 :         switch ( component.hAlign )
     752                 :            :         {
     753                 :            :           case AlignLeft:
     754                 :            :           case AlignJustify:
     755                 :          0 :             component.center = QPointF( component.origin.x() + width / 2.0,
     756                 :          0 :                                         component.origin.y() + height / 2.0 );
     757                 :          0 :             break;
     758                 :            : 
     759                 :            :           case AlignCenter:
     760                 :          0 :             component.center = QPointF( component.origin.x() + component.size.width() / 2.0,
     761                 :          0 :                                         component.origin.y() + height / 2.0 );
     762                 :          0 :             break;
     763                 :            : 
     764                 :            :           case AlignRight:
     765                 :          0 :             component.center = QPointF( component.origin.x() + component.size.width() - width / 2.0,
     766                 :          0 :                                         component.origin.y() + height / 2.0 );
     767                 :          0 :             break;
     768                 :            :         }
     769                 :          0 :         break;
     770                 :            : 
     771                 :            :       case Point:
     772                 :            :       {
     773                 :          0 :         double originAdjust = fm.ascent() / scaleFactor / 2.0 - fm.leading() / scaleFactor / 2.0;
     774                 :          0 :         switch ( component.hAlign )
     775                 :            :         {
     776                 :            :           case AlignLeft:
     777                 :            :           case AlignJustify:
     778                 :          0 :             component.center = QPointF( component.origin.x() + width / 2.0,
     779                 :          0 :                                         component.origin.y() - height / 2.0 + originAdjust );
     780                 :          0 :             break;
     781                 :            : 
     782                 :            :           case AlignCenter:
     783                 :          0 :             component.center = QPointF( component.origin.x(),
     784                 :          0 :                                         component.origin.y() - height / 2.0 + originAdjust );
     785                 :          0 :             break;
     786                 :            : 
     787                 :            :           case AlignRight:
     788                 :          0 :             component.center = QPointF( component.origin.x() - width / 2.0,
     789                 :          0 :                                         component.origin.y() - height / 2.0 + originAdjust );
     790                 :          0 :             break;
     791                 :            :         }
     792                 :          0 :         break;
     793                 :            :       }
     794                 :            : 
     795                 :            :       case Label:
     796                 :          0 :         break;
     797                 :            :     }
     798                 :            : 
     799                 :          0 :     if ( format.background().sizeType() != QgsTextBackgroundSettings::SizeFixed )
     800                 :          0 :       component.size = QSizeF( width, height );
     801                 :          0 :   }
     802                 :            : 
     803                 :            :   // TODO: the following label-buffered generated shapes and SVG symbols should be moved into marker symbology classes
     804                 :            : 
     805                 :          0 :   switch ( background.type() )
     806                 :            :   {
     807                 :            :     case QgsTextBackgroundSettings::ShapeSVG:
     808                 :            :     case QgsTextBackgroundSettings::ShapeMarkerSymbol:
     809                 :            :     {
     810                 :            :       // all calculations done in shapeSizeUnits, which are then passed to symbology class for painting
     811                 :            : 
     812                 :          0 :       if ( background.type() == QgsTextBackgroundSettings::ShapeSVG && background.svgFile().isEmpty() )
     813                 :          0 :         return;
     814                 :            : 
     815                 :          0 :       if ( background.type() == QgsTextBackgroundSettings::ShapeMarkerSymbol && !background.markerSymbol() )
     816                 :          0 :         return;
     817                 :            : 
     818                 :          0 :       double sizeOut = 0.0;
     819                 :            :       // only one size used for SVG/marker symbol sizing/scaling (no use of shapeSize.y() or Y field in gui)
     820                 :          0 :       if ( background.sizeType() == QgsTextBackgroundSettings::SizeFixed )
     821                 :            :       {
     822                 :          0 :         sizeOut = context.convertToPainterUnits( background.size().width(), background.sizeUnit(), background.sizeMapUnitScale() );
     823                 :          0 :       }
     824                 :          0 :       else if ( background.sizeType() == QgsTextBackgroundSettings::SizeBuffer )
     825                 :            :       {
     826                 :          0 :         sizeOut = std::max( component.size.width(), component.size.height() );
     827                 :          0 :         double bufferSize = context.convertToPainterUnits( background.size().width(), background.sizeUnit(), background.sizeMapUnitScale() );
     828                 :            : 
     829                 :            :         // add buffer
     830                 :          0 :         sizeOut += bufferSize * 2;
     831                 :          0 :       }
     832                 :            : 
     833                 :            :       // don't bother rendering symbols smaller than 1x1 pixels in size
     834                 :            :       // TODO: add option to not show any svgs under/over a certain size
     835                 :          0 :       if ( sizeOut < 1.0 )
     836                 :          0 :         return;
     837                 :            : 
     838                 :          0 :       std::unique_ptr< QgsMarkerSymbol > renderedSymbol;
     839                 :          0 :       if ( background.type() == QgsTextBackgroundSettings::ShapeSVG )
     840                 :            :       {
     841                 :          0 :         QVariantMap map; // for SVG symbology marker
     842                 :          0 :         map[QStringLiteral( "name" )] = background.svgFile().trimmed();
     843                 :          0 :         map[QStringLiteral( "size" )] = QString::number( sizeOut );
     844                 :          0 :         map[QStringLiteral( "size_unit" )] = QgsUnitTypes::encodeUnit( QgsUnitTypes::RenderPixels );
     845                 :          0 :         map[QStringLiteral( "angle" )] = QString::number( 0.0 ); // angle is handled by this local painter
     846                 :            : 
     847                 :            :         // offset is handled by this local painter
     848                 :            :         // TODO: see why the marker renderer doesn't seem to translate offset *after* applying rotation
     849                 :            :         //map["offset"] = QgsSymbolLayerUtils::encodePoint( tmpLyr.shapeOffset );
     850                 :            :         //map["offset_unit"] = QgsUnitTypes::encodeUnit(
     851                 :            :         //                       tmpLyr.shapeOffsetUnits == QgsPalLayerSettings::MapUnits ? QgsUnitTypes::MapUnit : QgsUnitTypes::MM );
     852                 :            : 
     853                 :          0 :         map[QStringLiteral( "fill" )] = background.fillColor().name();
     854                 :          0 :         map[QStringLiteral( "outline" )] = background.strokeColor().name();
     855                 :          0 :         map[QStringLiteral( "outline-width" )] = QString::number( background.strokeWidth() );
     856                 :          0 :         map[QStringLiteral( "outline_width_unit" )] = QgsUnitTypes::encodeUnit( background.strokeWidthUnit() );
     857                 :            : 
     858                 :          0 :         if ( format.shadow().enabled() && format.shadow().shadowPlacement() == QgsTextShadowSettings::ShadowShape )
     859                 :            :         {
     860                 :          0 :           QgsTextShadowSettings shadow = format.shadow();
     861                 :            :           // configure SVG shadow specs
     862                 :          0 :           QVariantMap shdwmap( map );
     863                 :          0 :           shdwmap[QStringLiteral( "fill" )] = shadow.color().name();
     864                 :          0 :           shdwmap[QStringLiteral( "outline" )] = shadow.color().name();
     865                 :          0 :           shdwmap[QStringLiteral( "size" )] = QString::number( sizeOut );
     866                 :            : 
     867                 :            :           // store SVG's drawing in QPicture for drop shadow call
     868                 :          0 :           QPicture svgPict;
     869                 :          0 :           QPainter svgp;
     870                 :          0 :           svgp.begin( &svgPict );
     871                 :            : 
     872                 :            :           // draw shadow symbol
     873                 :            : 
     874                 :            :           // clone current render context map unit/mm conversion factors, but not
     875                 :            :           // other map canvas parameters, then substitute this painter for use in symbology painting
     876                 :            :           // NOTE: this is because the shadow needs to be scaled correctly for output to map canvas,
     877                 :            :           //       but will be created relative to the SVG's computed size, not the current map canvas
     878                 :          0 :           QgsRenderContext shdwContext;
     879                 :          0 :           shdwContext.setMapToPixel( context.mapToPixel() );
     880                 :          0 :           shdwContext.setScaleFactor( context.scaleFactor() );
     881                 :          0 :           shdwContext.setPainter( &svgp );
     882                 :            : 
     883                 :          0 :           std::unique_ptr< QgsSymbolLayer > symShdwL( QgsSvgMarkerSymbolLayer::create( shdwmap ) );
     884                 :          0 :           QgsSvgMarkerSymbolLayer *svgShdwM = static_cast<QgsSvgMarkerSymbolLayer *>( symShdwL.get() );
     885                 :          0 :           QgsSymbolRenderContext svgShdwContext( shdwContext, QgsUnitTypes::RenderUnknownUnit, background.opacity() );
     886                 :            : 
     887                 :          0 :           svgShdwM->renderPoint( QPointF( sizeOut / 2, -sizeOut / 2 ), svgShdwContext );
     888                 :          0 :           svgp.end();
     889                 :            : 
     890                 :          0 :           component.picture = svgPict;
     891                 :            :           // TODO: when SVG symbol's stroke width/units is fixed in QgsSvgCache, adjust for it here
     892                 :          0 :           component.pictureBuffer = 0.0;
     893                 :            : 
     894                 :          0 :           component.size = QSizeF( sizeOut, sizeOut );
     895                 :          0 :           component.offset = QPointF( 0.0, 0.0 );
     896                 :            : 
     897                 :            :           // rotate about origin center of SVG
     898                 :          0 :           QgsScopedQPainterState painterState( p );
     899                 :          0 :           context.setPainterFlagsUsingContext( p );
     900                 :            : 
     901                 :          0 :           p->translate( component.center.x(), component.center.y() );
     902                 :          0 :           p->rotate( component.rotation );
     903                 :          0 :           double xoff = context.convertToPainterUnits( background.offset().x(), background.offsetUnit(), background.offsetMapUnitScale() );
     904                 :          0 :           double yoff = context.convertToPainterUnits( background.offset().y(), background.offsetUnit(), background.offsetMapUnitScale() );
     905                 :          0 :           p->translate( QPointF( xoff, yoff ) );
     906                 :          0 :           p->rotate( component.rotationOffset );
     907                 :          0 :           p->translate( -sizeOut / 2, sizeOut / 2 );
     908                 :            : 
     909                 :          0 :           drawShadow( context, component, format );
     910                 :          0 :         }
     911                 :          0 :         renderedSymbol.reset( );
     912                 :            : 
     913                 :          0 :         QgsSymbolLayer *symL = QgsSvgMarkerSymbolLayer::create( map );
     914                 :          0 :         renderedSymbol.reset( new QgsMarkerSymbol( QgsSymbolLayerList() << symL ) );
     915                 :          0 :       }
     916                 :            :       else
     917                 :            :       {
     918                 :          0 :         renderedSymbol.reset( background.markerSymbol()->clone() );
     919                 :          0 :         renderedSymbol->setSize( sizeOut );
     920                 :          0 :         renderedSymbol->setSizeUnit( QgsUnitTypes::RenderPixels );
     921                 :            :       }
     922                 :            : 
     923                 :          0 :       renderedSymbol->setOpacity( background.opacity() );
     924                 :            : 
     925                 :            :       // draw the actual symbol
     926                 :          0 :       QgsScopedQPainterState painterState( p );
     927                 :          0 :       context.setPainterFlagsUsingContext( p );
     928                 :            : 
     929                 :          0 :       if ( context.useAdvancedEffects() )
     930                 :            :       {
     931                 :          0 :         p->setCompositionMode( background.blendMode() );
     932                 :          0 :       }
     933                 :          0 :       p->translate( component.center.x(), component.center.y() );
     934                 :          0 :       p->rotate( component.rotation );
     935                 :          0 :       double xoff = context.convertToPainterUnits( background.offset().x(), background.offsetUnit(), background.offsetMapUnitScale() );
     936                 :          0 :       double yoff = context.convertToPainterUnits( background.offset().y(), background.offsetUnit(), background.offsetMapUnitScale() );
     937                 :          0 :       p->translate( QPointF( xoff, yoff ) );
     938                 :          0 :       p->rotate( component.rotationOffset );
     939                 :            : 
     940                 :          0 :       const QgsFeature f = context.expressionContext().feature();
     941                 :          0 :       renderedSymbol->startRender( context, context.expressionContext().fields() );
     942                 :          0 :       renderedSymbol->renderPoint( QPointF( 0, 0 ), &f, context );
     943                 :          0 :       renderedSymbol->stopRender( context );
     944                 :          0 :       p->setCompositionMode( QPainter::CompositionMode_SourceOver ); // just to be sure
     945                 :            : 
     946                 :            :       break;
     947                 :          0 :     }
     948                 :            : 
     949                 :            :     case QgsTextBackgroundSettings::ShapeRectangle:
     950                 :            :     case QgsTextBackgroundSettings::ShapeCircle:
     951                 :            :     case QgsTextBackgroundSettings::ShapeSquare:
     952                 :            :     case QgsTextBackgroundSettings::ShapeEllipse:
     953                 :            :     {
     954                 :          0 :       double w = component.size.width();
     955                 :          0 :       double h = component.size.height();
     956                 :            : 
     957                 :          0 :       if ( background.sizeType() == QgsTextBackgroundSettings::SizeFixed )
     958                 :            :       {
     959                 :          0 :         w = context.convertToPainterUnits( background.size().width(), background.sizeUnit(),
     960                 :          0 :                                            background.sizeMapUnitScale() );
     961                 :          0 :         h = context.convertToPainterUnits( background.size().height(), background.sizeUnit(),
     962                 :          0 :                                            background.sizeMapUnitScale() );
     963                 :          0 :       }
     964                 :          0 :       else if ( background.sizeType() == QgsTextBackgroundSettings::SizeBuffer )
     965                 :            :       {
     966                 :          0 :         if ( background.type() == QgsTextBackgroundSettings::ShapeSquare )
     967                 :            :         {
     968                 :          0 :           if ( w > h )
     969                 :          0 :             h = w;
     970                 :          0 :           else if ( h > w )
     971                 :          0 :             w = h;
     972                 :          0 :         }
     973                 :          0 :         else if ( background.type() == QgsTextBackgroundSettings::ShapeCircle )
     974                 :            :         {
     975                 :            :           // start with label bound by circle
     976                 :          0 :           h = std::sqrt( std::pow( w, 2 ) + std::pow( h, 2 ) );
     977                 :          0 :           w = h;
     978                 :          0 :         }
     979                 :          0 :         else if ( background.type() == QgsTextBackgroundSettings::ShapeEllipse )
     980                 :            :         {
     981                 :            :           // start with label bound by ellipse
     982                 :          0 :           h = h * M_SQRT1_2 * 2;
     983                 :          0 :           w = w * M_SQRT1_2 * 2;
     984                 :          0 :         }
     985                 :            : 
     986                 :          0 :         double bufferWidth = context.convertToPainterUnits( background.size().width(), background.sizeUnit(),
     987                 :          0 :                              background.sizeMapUnitScale() );
     988                 :          0 :         double bufferHeight = context.convertToPainterUnits( background.size().height(), background.sizeUnit(),
     989                 :          0 :                               background.sizeMapUnitScale() );
     990                 :            : 
     991                 :          0 :         w += bufferWidth * 2;
     992                 :          0 :         h += bufferHeight * 2;
     993                 :          0 :       }
     994                 :            : 
     995                 :            :       // offsets match those of symbology: -x = left, -y = up
     996                 :          0 :       QRectF rect( -w / 2.0, - h / 2.0, w, h );
     997                 :            : 
     998                 :          0 :       if ( rect.isNull() )
     999                 :          0 :         return;
    1000                 :            : 
    1001                 :          0 :       QgsScopedQPainterState painterState( p );
    1002                 :          0 :       context.setPainterFlagsUsingContext( p );
    1003                 :            : 
    1004                 :          0 :       p->translate( QPointF( component.center.x(), component.center.y() ) );
    1005                 :          0 :       p->rotate( component.rotation );
    1006                 :          0 :       double xoff = context.convertToPainterUnits( background.offset().x(), background.offsetUnit(), background.offsetMapUnitScale() );
    1007                 :          0 :       double yoff = context.convertToPainterUnits( background.offset().y(), background.offsetUnit(), background.offsetMapUnitScale() );
    1008                 :          0 :       p->translate( QPointF( xoff, yoff ) );
    1009                 :          0 :       p->rotate( component.rotationOffset );
    1010                 :            : 
    1011                 :          0 :       double penSize = context.convertToPainterUnits( background.strokeWidth(), background.strokeWidthUnit(), background.strokeWidthMapUnitScale() );
    1012                 :            : 
    1013                 :          0 :       QPen pen;
    1014                 :          0 :       if ( background.strokeWidth() > 0 )
    1015                 :            :       {
    1016                 :          0 :         pen.setColor( background.strokeColor() );
    1017                 :          0 :         pen.setWidthF( penSize );
    1018                 :          0 :         if ( background.type() == QgsTextBackgroundSettings::ShapeRectangle )
    1019                 :          0 :           pen.setJoinStyle( background.joinStyle() );
    1020                 :          0 :       }
    1021                 :            :       else
    1022                 :            :       {
    1023                 :          0 :         pen = Qt::NoPen;
    1024                 :            :       }
    1025                 :            : 
    1026                 :            :       // store painting in QPicture for shadow drawing
    1027                 :          0 :       QPicture shapePict;
    1028                 :          0 :       QPainter shapep;
    1029                 :          0 :       shapep.begin( &shapePict );
    1030                 :          0 :       shapep.setPen( pen );
    1031                 :          0 :       shapep.setBrush( background.fillColor() );
    1032                 :            : 
    1033                 :          0 :       if ( background.type() == QgsTextBackgroundSettings::ShapeRectangle
    1034                 :          0 :            || background.type() == QgsTextBackgroundSettings::ShapeSquare )
    1035                 :            :       {
    1036                 :          0 :         if ( background.radiiUnit() == QgsUnitTypes::RenderPercentage )
    1037                 :            :         {
    1038                 :          0 :           shapep.drawRoundedRect( rect, background.radii().width(), background.radii().height(), Qt::RelativeSize );
    1039                 :          0 :         }
    1040                 :            :         else
    1041                 :            :         {
    1042                 :          0 :           double xRadius = context.convertToPainterUnits( background.radii().width(), background.radiiUnit(), background.radiiMapUnitScale() );
    1043                 :          0 :           double yRadius = context.convertToPainterUnits( background.radii().height(), background.radiiUnit(), background.radiiMapUnitScale() );
    1044                 :          0 :           shapep.drawRoundedRect( rect, xRadius, yRadius );
    1045                 :            :         }
    1046                 :          0 :       }
    1047                 :          0 :       else if ( background.type() == QgsTextBackgroundSettings::ShapeEllipse
    1048                 :          0 :                 || background.type() == QgsTextBackgroundSettings::ShapeCircle )
    1049                 :            :       {
    1050                 :          0 :         shapep.drawEllipse( rect );
    1051                 :          0 :       }
    1052                 :          0 :       shapep.end();
    1053                 :            : 
    1054                 :          0 :       if ( format.shadow().enabled() && format.shadow().shadowPlacement() == QgsTextShadowSettings::ShadowShape )
    1055                 :            :       {
    1056                 :          0 :         component.picture = shapePict;
    1057                 :          0 :         component.pictureBuffer = penSize / 2.0;
    1058                 :            : 
    1059                 :          0 :         component.size = rect.size();
    1060                 :          0 :         component.offset = QPointF( rect.width() / 2, -rect.height() / 2 );
    1061                 :          0 :         drawShadow( context, component, format );
    1062                 :          0 :       }
    1063                 :            : 
    1064                 :          0 :       p->setOpacity( background.opacity() );
    1065                 :          0 :       if ( context.useAdvancedEffects() )
    1066                 :            :       {
    1067                 :          0 :         p->setCompositionMode( background.blendMode() );
    1068                 :          0 :       }
    1069                 :            : 
    1070                 :            :       // scale for any print output or image saving @ specific dpi
    1071                 :          0 :       p->scale( component.dpiRatio, component.dpiRatio );
    1072                 :          0 :       _fixQPictureDPI( p );
    1073                 :          0 :       p->drawPicture( 0, 0, shapePict );
    1074                 :            :       break;
    1075                 :          0 :     }
    1076                 :            :   }
    1077                 :            : 
    1078                 :          0 :   if ( tmpEffect )
    1079                 :            :   {
    1080                 :          0 :     tmpEffect->end( context );
    1081                 :          0 :     context.setPainter( prevP );
    1082                 :          0 :   }
    1083                 :          0 : }
    1084                 :            : 
    1085                 :          0 : void QgsTextRenderer::drawShadow( QgsRenderContext &context, const QgsTextRenderer::Component &component, const QgsTextFormat &format )
    1086                 :            : {
    1087                 :          0 :   QgsTextShadowSettings shadow = format.shadow();
    1088                 :            : 
    1089                 :            :   // incoming component sizes should be multiplied by rasterCompressFactor, as
    1090                 :            :   // this allows shadows to be created at paint device dpi (e.g. high resolution),
    1091                 :            :   // then scale device painter by 1.0 / rasterCompressFactor for output
    1092                 :            : 
    1093                 :          0 :   QPainter *p = context.painter();
    1094                 :          0 :   double componentWidth = component.size.width(), componentHeight = component.size.height();
    1095                 :          0 :   double xOffset = component.offset.x(), yOffset = component.offset.y();
    1096                 :          0 :   double pictbuffer = component.pictureBuffer;
    1097                 :            : 
    1098                 :            :   // generate pixmap representation of label component drawing
    1099                 :          0 :   bool mapUnits = shadow.blurRadiusUnit() == QgsUnitTypes::RenderMapUnits;
    1100                 :          0 :   double radius = context.convertToPainterUnits( shadow.blurRadius(), shadow.blurRadiusUnit(), shadow.blurRadiusMapUnitScale() );
    1101                 :          0 :   radius /= ( mapUnits ? context.scaleFactor() / component.dpiRatio : 1 );
    1102                 :          0 :   radius = static_cast< int >( radius + 0.5 ); //NOLINT
    1103                 :            : 
    1104                 :            :   // TODO: add labeling gui option to adjust blurBufferClippingScale to minimize pixels, or
    1105                 :            :   //       to ensure shadow isn't clipped too tight. (Or, find a better method of buffering)
    1106                 :          0 :   double blurBufferClippingScale = 3.75;
    1107                 :          0 :   int blurbuffer = ( radius > 17 ? 16 : radius ) * blurBufferClippingScale;
    1108                 :            : 
    1109                 :          0 :   QImage blurImg( componentWidth + ( pictbuffer * 2.0 ) + ( blurbuffer * 2.0 ),
    1110                 :          0 :                   componentHeight + ( pictbuffer * 2.0 ) + ( blurbuffer * 2.0 ),
    1111                 :            :                   QImage::Format_ARGB32_Premultiplied );
    1112                 :            : 
    1113                 :            :   // TODO: add labeling gui option to not show any shadows under/over a certain size
    1114                 :            :   // keep very small QImages from causing paint device issues, i.e. must be at least > 1
    1115                 :          0 :   int minBlurImgSize = 1;
    1116                 :            :   // max limitation on QgsSvgCache is 10,000 for screen, which will probably be reasonable for future caching here, too
    1117                 :            :   // 4 x QgsSvgCache limit for output to print/image at higher dpi
    1118                 :            :   // TODO: should it be higher, scale with dpi, or have no limit? Needs testing with very large labels rendered at high dpi output
    1119                 :          0 :   int maxBlurImgSize = 40000;
    1120                 :          0 :   if ( blurImg.isNull()
    1121                 :          0 :        || ( blurImg.width() < minBlurImgSize || blurImg.height() < minBlurImgSize )
    1122                 :          0 :        || ( blurImg.width() > maxBlurImgSize || blurImg.height() > maxBlurImgSize ) )
    1123                 :          0 :     return;
    1124                 :            : 
    1125                 :          0 :   blurImg.fill( QColor( Qt::transparent ).rgba() );
    1126                 :          0 :   QPainter pictp;
    1127                 :          0 :   if ( !pictp.begin( &blurImg ) )
    1128                 :          0 :     return;
    1129                 :          0 :   pictp.setRenderHints( QPainter::Antialiasing | QPainter::SmoothPixmapTransform );
    1130                 :          0 :   QPointF imgOffset( blurbuffer + pictbuffer + xOffset,
    1131                 :          0 :                      blurbuffer + pictbuffer + componentHeight + yOffset );
    1132                 :            : 
    1133                 :          0 :   pictp.drawPicture( imgOffset,
    1134                 :          0 :                      component.picture );
    1135                 :            : 
    1136                 :            :   // overlay shadow color
    1137                 :          0 :   pictp.setCompositionMode( QPainter::CompositionMode_SourceIn );
    1138                 :          0 :   pictp.fillRect( blurImg.rect(), shadow.color() );
    1139                 :          0 :   pictp.end();
    1140                 :            : 
    1141                 :            :   // blur the QImage in-place
    1142                 :          0 :   if ( shadow.blurRadius() > 0.0 && radius > 0 )
    1143                 :            :   {
    1144                 :          0 :     QgsSymbolLayerUtils::blurImageInPlace( blurImg, blurImg.rect(), radius, shadow.blurAlphaOnly() );
    1145                 :          0 :   }
    1146                 :            : 
    1147                 :            : #if 0
    1148                 :            :   // debug rect for QImage shadow registration and clipping visualization
    1149                 :            :   QPainter picti;
    1150                 :            :   picti.begin( &blurImg );
    1151                 :            :   picti.setBrush( Qt::Dense7Pattern );
    1152                 :            :   QPen imgPen( QColor( 0, 0, 255, 255 ) );
    1153                 :            :   imgPen.setWidth( 1 );
    1154                 :            :   picti.setPen( imgPen );
    1155                 :            :   picti.setOpacity( 0.1 );
    1156                 :            :   picti.drawRect( 0, 0, blurImg.width(), blurImg.height() );
    1157                 :            :   picti.end();
    1158                 :            : #endif
    1159                 :            : 
    1160                 :          0 :   double offsetDist = context.convertToPainterUnits( shadow.offsetDistance(), shadow.offsetUnit(), shadow.offsetMapUnitScale() );
    1161                 :          0 :   double angleRad = shadow.offsetAngle() * M_PI / 180; // to radians
    1162                 :          0 :   if ( shadow.offsetGlobal() )
    1163                 :            :   {
    1164                 :            :     // TODO: check for differences in rotation origin and cw/ccw direction,
    1165                 :            :     //       when this shadow function is used for something other than labels
    1166                 :            : 
    1167                 :            :     // it's 0-->cw-->360 for labels
    1168                 :            :     //QgsDebugMsgLevel( QStringLiteral( "Shadow aggregated label rotation (degrees): %1" ).arg( component.rotation() + component.rotationOffset() ), 4 );
    1169                 :          0 :     angleRad -= ( component.rotation * M_PI / 180 + component.rotationOffset * M_PI / 180 );
    1170                 :          0 :   }
    1171                 :            : 
    1172                 :          0 :   QPointF transPt( -offsetDist * std::cos( angleRad + M_PI_2 ),
    1173                 :          0 :                    -offsetDist * std::sin( angleRad + M_PI_2 ) );
    1174                 :            : 
    1175                 :          0 :   p->save();
    1176                 :          0 :   p->setRenderHint( QPainter::SmoothPixmapTransform );
    1177                 :          0 :   context.setPainterFlagsUsingContext( p );
    1178                 :          0 :   if ( context.useAdvancedEffects() )
    1179                 :            :   {
    1180                 :          0 :     p->setCompositionMode( shadow.blendMode() );
    1181                 :          0 :   }
    1182                 :          0 :   p->setOpacity( shadow.opacity() );
    1183                 :            : 
    1184                 :          0 :   double scale = shadow.scale() / 100.0;
    1185                 :            :   // TODO: scale from center/center, left/center or left/top, instead of default left/bottom?
    1186                 :          0 :   p->scale( scale, scale );
    1187                 :          0 :   if ( component.useOrigin )
    1188                 :            :   {
    1189                 :          0 :     p->translate( component.origin.x(), component.origin.y() );
    1190                 :          0 :   }
    1191                 :          0 :   p->translate( transPt );
    1192                 :          0 :   p->translate( -imgOffset.x(),
    1193                 :          0 :                 -imgOffset.y() );
    1194                 :          0 :   p->drawImage( 0, 0, blurImg );
    1195                 :          0 :   p->restore();
    1196                 :            : 
    1197                 :            :   // debug rects
    1198                 :            : #if 0
    1199                 :            :   // draw debug rect for QImage painting registration
    1200                 :            :   p->save();
    1201                 :            :   p->setBrush( Qt::NoBrush );
    1202                 :            :   QPen imgPen( QColor( 255, 0, 0, 10 ) );
    1203                 :            :   imgPen.setWidth( 2 );
    1204                 :            :   imgPen.setStyle( Qt::DashLine );
    1205                 :            :   p->setPen( imgPen );
    1206                 :            :   p->scale( scale, scale );
    1207                 :            :   if ( component.useOrigin() )
    1208                 :            :   {
    1209                 :            :     p->translate( component.origin().x(), component.origin().y() );
    1210                 :            :   }
    1211                 :            :   p->translate( transPt );
    1212                 :            :   p->translate( -imgOffset.x(),
    1213                 :            :                 -imgOffset.y() );
    1214                 :            :   p->drawRect( 0, 0, blurImg.width(), blurImg.height() );
    1215                 :            :   p->restore();
    1216                 :            : 
    1217                 :            :   // draw debug rect for passed in component dimensions
    1218                 :            :   p->save();
    1219                 :            :   p->setBrush( Qt::NoBrush );
    1220                 :            :   QPen componentRectPen( QColor( 0, 255, 0, 70 ) );
    1221                 :            :   componentRectPen.setWidth( 1 );
    1222                 :            :   if ( component.useOrigin() )
    1223                 :            :   {
    1224                 :            :     p->translate( component.origin().x(), component.origin().y() );
    1225                 :            :   }
    1226                 :            :   p->setPen( componentRectPen );
    1227                 :            :   p->drawRect( QRect( -xOffset, -componentHeight - yOffset, componentWidth, componentHeight ) );
    1228                 :            :   p->restore();
    1229                 :            : #endif
    1230                 :          0 : }
    1231                 :            : 
    1232                 :            : 
    1233                 :          0 : void QgsTextRenderer::drawTextInternal( TextPart drawType,
    1234                 :            :                                         QgsRenderContext &context,
    1235                 :            :                                         const QgsTextFormat &format,
    1236                 :            :                                         const Component &component,
    1237                 :            :                                         const QgsTextDocument &document,
    1238                 :            :                                         const QFontMetricsF *fontMetrics,
    1239                 :            :                                         HAlignment alignment, VAlignment vAlignment, DrawMode mode )
    1240                 :            : {
    1241                 :          0 :   if ( !context.painter() )
    1242                 :            :   {
    1243                 :          0 :     return;
    1244                 :            :   }
    1245                 :            : 
    1246                 :          0 :   double fontScale = 1.0;
    1247                 :          0 :   std::unique_ptr< QFontMetricsF > tmpMetrics;
    1248                 :          0 :   if ( !fontMetrics )
    1249                 :            :   {
    1250                 :          0 :     fontScale = ( context.flags() & QgsRenderContext::ApplyScalingWorkaroundForTextRendering ) ? FONT_WORKAROUND_SCALE : 1.0;
    1251                 :          0 :     const QFont f = format.scaledFont( context, fontScale );
    1252                 :          0 :     tmpMetrics = std::make_unique< QFontMetricsF >( f );
    1253                 :          0 :     fontMetrics = tmpMetrics.get();
    1254                 :          0 :   }
    1255                 :            : 
    1256                 :          0 :   double rotation = 0;
    1257                 :          0 :   const QgsTextFormat::TextOrientation orientation = calculateRotationAndOrientationForComponent( format, component, rotation );
    1258                 :          0 :   switch ( orientation )
    1259                 :            :   {
    1260                 :            :     case QgsTextFormat::HorizontalOrientation:
    1261                 :            :     {
    1262                 :          0 :       drawTextInternalHorizontal( context, format, drawType, mode, component, document, fontScale, fontMetrics, alignment, vAlignment, rotation );
    1263                 :          0 :       break;
    1264                 :            :     }
    1265                 :            : 
    1266                 :            :     case QgsTextFormat::VerticalOrientation:
    1267                 :            :     case QgsTextFormat::RotationBasedOrientation:
    1268                 :            :     {
    1269                 :          0 :       drawTextInternalVertical( context, format, drawType, mode, component, document, fontScale, fontMetrics, alignment, vAlignment, rotation );
    1270                 :          0 :       break;
    1271                 :            :     }
    1272                 :            :   }
    1273                 :          0 : }
    1274                 :            : 
    1275                 :          0 : QgsTextFormat::TextOrientation QgsTextRenderer::calculateRotationAndOrientationForComponent( const QgsTextFormat &format, const QgsTextRenderer::Component &component, double &rotation )
    1276                 :            : {
    1277                 :          0 :   rotation = -component.rotation * 180 / M_PI;
    1278                 :            : 
    1279                 :          0 :   switch ( format.orientation() )
    1280                 :            :   {
    1281                 :            :     case QgsTextFormat::RotationBasedOrientation:
    1282                 :            :     {
    1283                 :            :       // Between 45 to 135 and 235 to 315 degrees, rely on vertical orientation
    1284                 :          0 :       if ( rotation >= -315 && rotation < -90 )
    1285                 :            :       {
    1286                 :          0 :         rotation -= 90;
    1287                 :          0 :         return QgsTextFormat::VerticalOrientation;
    1288                 :            :       }
    1289                 :          0 :       else if ( rotation >= -90 && rotation < -45 )
    1290                 :            :       {
    1291                 :          0 :         rotation += 90;
    1292                 :          0 :         return QgsTextFormat::VerticalOrientation;
    1293                 :            :       }
    1294                 :            : 
    1295                 :          0 :       return QgsTextFormat::HorizontalOrientation;
    1296                 :            :     }
    1297                 :            : 
    1298                 :            :     case QgsTextFormat::HorizontalOrientation:
    1299                 :            :     case QgsTextFormat::VerticalOrientation:
    1300                 :          0 :       return format.orientation();
    1301                 :            :   }
    1302                 :          0 :   return QgsTextFormat::HorizontalOrientation;
    1303                 :          0 : }
    1304                 :            : 
    1305                 :          0 : void QgsTextRenderer::calculateExtraSpacingForLineJustification( const double spaceToDistribute, const QgsTextBlock &block, double &extraWordSpace, double &extraLetterSpace )
    1306                 :            : {
    1307                 :          0 :   const QString blockText = block.toPlainText();
    1308                 :          0 :   QTextBoundaryFinder finder( QTextBoundaryFinder::Word, blockText );
    1309                 :          0 :   finder.toStart();
    1310                 :          0 :   int wordBoundaries = 0;
    1311                 :          0 :   while ( finder.toNextBoundary() != -1 )
    1312                 :            :   {
    1313                 :          0 :     if ( finder.boundaryReasons() & QTextBoundaryFinder::StartOfItem )
    1314                 :          0 :       wordBoundaries++;
    1315                 :            :   }
    1316                 :            : 
    1317                 :          0 :   if ( wordBoundaries > 0 )
    1318                 :            :   {
    1319                 :            :     // word boundaries found => justify by padding word spacing
    1320                 :          0 :     extraWordSpace = spaceToDistribute / wordBoundaries;
    1321                 :          0 :   }
    1322                 :            :   else
    1323                 :            :   {
    1324                 :            :     // no word boundaries found => justify by letter spacing
    1325                 :          0 :     QTextBoundaryFinder finder( QTextBoundaryFinder::Grapheme, blockText );
    1326                 :          0 :     finder.toStart();
    1327                 :            : 
    1328                 :          0 :     int graphemeBoundaries = 0;
    1329                 :          0 :     while ( finder.toNextBoundary() != -1 )
    1330                 :            :     {
    1331                 :          0 :       if ( finder.boundaryReasons() & QTextBoundaryFinder::StartOfItem )
    1332                 :          0 :         graphemeBoundaries++;
    1333                 :            :     }
    1334                 :            : 
    1335                 :          0 :     if ( graphemeBoundaries > 0 )
    1336                 :            :     {
    1337                 :          0 :       extraLetterSpace = spaceToDistribute / graphemeBoundaries;
    1338                 :          0 :     }
    1339                 :          0 :   }
    1340                 :          0 : }
    1341                 :            : 
    1342                 :          0 : void QgsTextRenderer::applyExtraSpacingForLineJustification( QFont &font, double extraWordSpace, double extraLetterSpace )
    1343                 :            : {
    1344                 :          0 :   const double prevWordSpace = font.wordSpacing();
    1345                 :          0 :   font.setWordSpacing( prevWordSpace + extraWordSpace );
    1346                 :          0 :   const double prevLetterSpace = font.letterSpacing();
    1347                 :          0 :   font.setLetterSpacing( QFont::AbsoluteSpacing, prevLetterSpace + extraLetterSpace );
    1348                 :          0 : }
    1349                 :            : 
    1350                 :          0 : void QgsTextRenderer::drawTextInternalHorizontal( QgsRenderContext &context, const QgsTextFormat &format, TextPart drawType, DrawMode mode, const Component &component, const QgsTextDocument &document, double fontScale, const QFontMetricsF *fontMetrics, HAlignment hAlignment,
    1351                 :            :     VAlignment vAlignment, double rotation )
    1352                 :            : {
    1353                 :          0 :   QPainter *maskPainter = context.maskPainter( context.currentMaskId() );
    1354                 :          0 :   const QStringList textLines = document.toPlainText();
    1355                 :            : 
    1356                 :          0 :   double labelWidest = 0.0;
    1357                 :          0 :   switch ( mode )
    1358                 :            :   {
    1359                 :            :     case Label:
    1360                 :            :     case Point:
    1361                 :          0 :       for ( const QString &line : textLines )
    1362                 :            :       {
    1363                 :          0 :         double labelWidth = fontMetrics->horizontalAdvance( line ) / fontScale;
    1364                 :          0 :         if ( labelWidth > labelWidest )
    1365                 :            :         {
    1366                 :          0 :           labelWidest = labelWidth;
    1367                 :          0 :         }
    1368                 :            :       }
    1369                 :          0 :       break;
    1370                 :            : 
    1371                 :            :     case Rect:
    1372                 :          0 :       labelWidest = component.size.width();
    1373                 :          0 :       break;
    1374                 :            :   }
    1375                 :            : 
    1376                 :          0 :   double labelHeight = ( fontMetrics->ascent() + fontMetrics->descent() ) / fontScale; // ignore +1 for baseline
    1377                 :            :   //  double labelHighest = labelfm->height() + ( double )(( lines - 1 ) * labelHeight * tmpLyr.multilineHeight );
    1378                 :            : 
    1379                 :            :   // needed to move bottom of text's descender to within bottom edge of label
    1380                 :          0 :   double ascentOffset = 0.25 * fontMetrics->ascent() / fontScale; // labelfm->descent() is not enough
    1381                 :            : 
    1382                 :          0 :   int i = 0;
    1383                 :            : 
    1384                 :          0 :   bool adjustForAlignment = hAlignment != AlignLeft && ( mode != Label || textLines.size() > 1 );
    1385                 :            : 
    1386                 :          0 :   if ( mode == Rect && vAlignment != AlignTop )
    1387                 :            :   {
    1388                 :            :     // need to calculate overall text height in advance so that we can adjust for vertical alignment
    1389                 :          0 :     const double overallHeight = textHeight( context, format, textLines, Rect );
    1390                 :          0 :     switch ( vAlignment )
    1391                 :            :     {
    1392                 :            :       case AlignTop:
    1393                 :          0 :         break;
    1394                 :            : 
    1395                 :            :       case AlignVCenter:
    1396                 :          0 :         ascentOffset = -( component.size.height() - overallHeight ) * 0.5 + ascentOffset;
    1397                 :          0 :         break;
    1398                 :            : 
    1399                 :            :       case AlignBottom:
    1400                 :          0 :         ascentOffset = -( component.size.height() - overallHeight ) + ascentOffset;
    1401                 :          0 :         break;
    1402                 :            :     }
    1403                 :          0 :   }
    1404                 :            : 
    1405                 :          0 :   for ( const QString &line : std::as_const( textLines ) )
    1406                 :            :   {
    1407                 :          0 :     const QgsTextBlock block = document.at( i );
    1408                 :            : 
    1409                 :          0 :     const bool isFinalLine = i == document.size() - 1;
    1410                 :            : 
    1411                 :          0 :     QgsScopedQPainterState painterState( context.painter() );
    1412                 :          0 :     context.setPainterFlagsUsingContext();
    1413                 :          0 :     context.painter()->translate( component.origin );
    1414                 :          0 :     if ( !qgsDoubleNear( rotation, 0.0 ) )
    1415                 :          0 :       context.painter()->rotate( rotation );
    1416                 :            : 
    1417                 :            :     // apply to the mask painter the same transformations
    1418                 :          0 :     if ( maskPainter )
    1419                 :            :     {
    1420                 :          0 :       maskPainter->save();
    1421                 :          0 :       maskPainter->translate( component.origin );
    1422                 :          0 :       if ( !qgsDoubleNear( rotation, 0.0 ) )
    1423                 :          0 :         maskPainter->rotate( rotation );
    1424                 :          0 :     }
    1425                 :            : 
    1426                 :            :     // figure x offset for horizontal alignment of multiple lines
    1427                 :          0 :     double xMultiLineOffset = 0.0;
    1428                 :          0 :     double labelWidth = fontMetrics->horizontalAdvance( line ) / fontScale;
    1429                 :          0 :     double extraWordSpace = 0;
    1430                 :          0 :     double extraLetterSpace = 0;
    1431                 :          0 :     if ( adjustForAlignment )
    1432                 :            :     {
    1433                 :          0 :       double labelWidthDiff = 0;
    1434                 :          0 :       switch ( hAlignment )
    1435                 :            :       {
    1436                 :            :         case AlignCenter:
    1437                 :          0 :           labelWidthDiff = ( labelWidest - labelWidth ) * 0.5;
    1438                 :          0 :           break;
    1439                 :            : 
    1440                 :            :         case AlignRight:
    1441                 :          0 :           labelWidthDiff = labelWidest - labelWidth;
    1442                 :          0 :           break;
    1443                 :            : 
    1444                 :            :         case AlignJustify:
    1445                 :          0 :           if ( !isFinalLine && labelWidest > labelWidth )
    1446                 :            :           {
    1447                 :          0 :             calculateExtraSpacingForLineJustification( labelWidest - labelWidth, block, extraWordSpace, extraLetterSpace );
    1448                 :          0 :           }
    1449                 :          0 :           break;
    1450                 :            : 
    1451                 :            :         case AlignLeft:
    1452                 :          0 :           break;
    1453                 :            :       }
    1454                 :            : 
    1455                 :          0 :       switch ( mode )
    1456                 :            :       {
    1457                 :            :         case Label:
    1458                 :            :         case Rect:
    1459                 :          0 :           xMultiLineOffset = labelWidthDiff;
    1460                 :          0 :           break;
    1461                 :            : 
    1462                 :            :         case Point:
    1463                 :            :         {
    1464                 :          0 :           switch ( hAlignment )
    1465                 :            :           {
    1466                 :            :             case AlignRight:
    1467                 :          0 :               xMultiLineOffset = labelWidthDiff - labelWidest;
    1468                 :          0 :               break;
    1469                 :            : 
    1470                 :            :             case AlignCenter:
    1471                 :          0 :               xMultiLineOffset = labelWidthDiff - labelWidest / 2.0;
    1472                 :          0 :               break;
    1473                 :            : 
    1474                 :            :             case AlignLeft:
    1475                 :            :             case AlignJustify:
    1476                 :          0 :               break;
    1477                 :            :           }
    1478                 :            :         }
    1479                 :          0 :         break;
    1480                 :            :       }
    1481                 :          0 :     }
    1482                 :            : 
    1483                 :          0 :     double yMultiLineOffset = ascentOffset;
    1484                 :          0 :     switch ( mode )
    1485                 :            :     {
    1486                 :            :       case Label:
    1487                 :            :         // rendering labels needs special handling - in this case text should be
    1488                 :            :         // drawn with the bottom left corner coinciding with origin, vs top left
    1489                 :            :         // for standard text rendering. Line height is also slightly different.
    1490                 :          0 :         yMultiLineOffset = - ascentOffset - ( textLines.size() - 1 - i ) * labelHeight * format.lineHeight();
    1491                 :          0 :         break;
    1492                 :            : 
    1493                 :            :       case Rect:
    1494                 :            :         // standard rendering - designed to exactly replicate QPainter's drawText method
    1495                 :          0 :         yMultiLineOffset = - ascentOffset + labelHeight - 1 /*baseline*/ + format.lineHeight() * fontMetrics->lineSpacing() * i / fontScale;
    1496                 :          0 :         break;
    1497                 :            : 
    1498                 :            :       case Point:
    1499                 :            :         // standard rendering - designed to exactly replicate QPainter's drawText rect method
    1500                 :          0 :         yMultiLineOffset = 0 - ( textLines.size() - 1 - i ) * fontMetrics->lineSpacing() * format.lineHeight() / fontScale;
    1501                 :          0 :         break;
    1502                 :            : 
    1503                 :            :     }
    1504                 :            : 
    1505                 :          0 :     context.painter()->translate( QPointF( xMultiLineOffset, yMultiLineOffset ) );
    1506                 :          0 :     if ( maskPainter )
    1507                 :          0 :       maskPainter->translate( QPointF( xMultiLineOffset, yMultiLineOffset ) );
    1508                 :            : 
    1509                 :          0 :     Component subComponent;
    1510                 :          0 :     subComponent.block = block;
    1511                 :          0 :     subComponent.size = QSizeF( labelWidth, labelHeight );
    1512                 :          0 :     subComponent.offset = QPointF( 0.0, -ascentOffset );
    1513                 :          0 :     subComponent.rotation = -component.rotation * 180 / M_PI;
    1514                 :          0 :     subComponent.rotationOffset = 0.0;
    1515                 :          0 :     subComponent.extraWordSpacing = extraWordSpace * fontScale;
    1516                 :          0 :     subComponent.extraLetterSpacing = extraLetterSpace * fontScale;
    1517                 :            : 
    1518                 :            :     // draw the mask below the text (for preview)
    1519                 :          0 :     if ( format.mask().enabled() )
    1520                 :            :     {
    1521                 :          0 :       QgsTextRenderer::drawMask( context, subComponent, format );
    1522                 :          0 :     }
    1523                 :            : 
    1524                 :          0 :     if ( drawType == QgsTextRenderer::Buffer )
    1525                 :            :     {
    1526                 :          0 :       QgsTextRenderer::drawBuffer( context, subComponent, format );
    1527                 :          0 :     }
    1528                 :            :     else
    1529                 :            :     {
    1530                 :            :       // store text's drawing in QPicture for drop shadow call
    1531                 :          0 :       QPicture textPict;
    1532                 :          0 :       QPainter textp;
    1533                 :          0 :       textp.begin( &textPict );
    1534                 :          0 :       textp.setPen( Qt::NoPen );
    1535                 :          0 :       const QFont font = format.scaledFont( context, fontScale );
    1536                 :          0 :       textp.scale( 1 / fontScale, 1 / fontScale );
    1537                 :            : 
    1538                 :          0 :       double xOffset = 0;
    1539                 :          0 :       for ( const QgsTextFragment &fragment : block )
    1540                 :            :       {
    1541                 :            :         // draw text, QPainterPath method
    1542                 :          0 :         QPainterPath path;
    1543                 :          0 :         path.setFillRule( Qt::WindingFill );
    1544                 :            : 
    1545                 :          0 :         QFont fragmentFont = font;
    1546                 :          0 :         fragment.characterFormat().updateFontForFormat( fragmentFont, fontScale );
    1547                 :            : 
    1548                 :          0 :         if ( extraWordSpace || extraLetterSpace )
    1549                 :          0 :           applyExtraSpacingForLineJustification( fragmentFont, extraWordSpace * fontScale, extraLetterSpace * fontScale );
    1550                 :            : 
    1551                 :          0 :         path.addText( xOffset, 0, fragmentFont, fragment.text() );
    1552                 :            : 
    1553                 :          0 :         QColor textColor = fragment.characterFormat().textColor().isValid() ? fragment.characterFormat().textColor() : format.color();
    1554                 :          0 :         textColor.setAlphaF( format.opacity() );
    1555                 :          0 :         textp.setBrush( textColor );
    1556                 :          0 :         textp.drawPath( path );
    1557                 :            : 
    1558                 :          0 :         xOffset += fragment.horizontalAdvance( fragmentFont, true );
    1559                 :          0 :       }
    1560                 :          0 :       textp.end();
    1561                 :            : 
    1562                 :          0 :       if ( format.shadow().enabled() && format.shadow().shadowPlacement() == QgsTextShadowSettings::ShadowText )
    1563                 :            :       {
    1564                 :          0 :         subComponent.picture = textPict;
    1565                 :          0 :         subComponent.pictureBuffer = 0.0; // no pen width to deal with
    1566                 :          0 :         subComponent.origin = QPointF( 0.0, 0.0 );
    1567                 :            : 
    1568                 :          0 :         QgsTextRenderer::drawShadow( context, subComponent, format );
    1569                 :          0 :       }
    1570                 :            : 
    1571                 :            :       // paint the text
    1572                 :          0 :       if ( context.useAdvancedEffects() )
    1573                 :            :       {
    1574                 :          0 :         context.painter()->setCompositionMode( format.blendMode() );
    1575                 :          0 :       }
    1576                 :            : 
    1577                 :            :       // scale for any print output or image saving @ specific dpi
    1578                 :          0 :       context.painter()->scale( subComponent.dpiRatio, subComponent.dpiRatio );
    1579                 :            : 
    1580                 :          0 :       switch ( context.textRenderFormat() )
    1581                 :            :       {
    1582                 :            :         case QgsRenderContext::TextFormatAlwaysOutlines:
    1583                 :            :         {
    1584                 :            :           // draw outlined text
    1585                 :          0 :           _fixQPictureDPI( context.painter() );
    1586                 :          0 :           context.painter()->drawPicture( 0, 0, textPict );
    1587                 :          0 :           break;
    1588                 :            :         }
    1589                 :            : 
    1590                 :            :         case QgsRenderContext::TextFormatAlwaysText:
    1591                 :            :         {
    1592                 :          0 :           double xOffset = 0;
    1593                 :          0 :           for ( const QgsTextFragment &fragment : block )
    1594                 :            :           {
    1595                 :          0 :             QFont fragmentFont = font;
    1596                 :          0 :             fragment.characterFormat().updateFontForFormat( fragmentFont, fontScale );
    1597                 :            : 
    1598                 :          0 :             if ( extraWordSpace || extraLetterSpace )
    1599                 :          0 :               applyExtraSpacingForLineJustification( fragmentFont, extraWordSpace * fontScale, extraLetterSpace * fontScale );
    1600                 :            : 
    1601                 :          0 :             QColor textColor = fragment.characterFormat().textColor().isValid() ? fragment.characterFormat().textColor() : format.color();
    1602                 :          0 :             textColor.setAlphaF( format.opacity() );
    1603                 :            : 
    1604                 :          0 :             context.painter()->setPen( textColor );
    1605                 :          0 :             context.painter()->setFont( fragmentFont );
    1606                 :          0 :             context.painter()->setRenderHint( QPainter::TextAntialiasing );
    1607                 :            : 
    1608                 :          0 :             context.painter()->scale( 1 / fontScale, 1 / fontScale );
    1609                 :          0 :             context.painter()->drawText( xOffset, 0, fragment.text() );
    1610                 :          0 :             context.painter()->scale( fontScale, fontScale );
    1611                 :            : 
    1612                 :          0 :             xOffset += fragment.horizontalAdvance( fragmentFont, true, fontScale );
    1613                 :          0 :           }
    1614                 :            :         }
    1615                 :          0 :       }
    1616                 :          0 :     }
    1617                 :          0 :     if ( maskPainter )
    1618                 :          0 :       maskPainter->restore();
    1619                 :          0 :     i++;
    1620                 :          0 :   }
    1621                 :          0 : }
    1622                 :            : 
    1623                 :          0 : void QgsTextRenderer::drawTextInternalVertical( QgsRenderContext &context, const QgsTextFormat &format, QgsTextRenderer::TextPart drawType, QgsTextRenderer::DrawMode mode, const QgsTextRenderer::Component &component, const QgsTextDocument &document, double fontScale, const QFontMetricsF *fontMetrics, QgsTextRenderer::HAlignment hAlignment, QgsTextRenderer::VAlignment, double rotation )
    1624                 :            : {
    1625                 :          0 :   QPainter *maskPainter = context.maskPainter( context.currentMaskId() );
    1626                 :          0 :   const QStringList textLines = document.toPlainText();
    1627                 :            : 
    1628                 :          0 :   const QFont font = format.scaledFont( context, fontScale );
    1629                 :          0 :   double letterSpacing = font.letterSpacing() / fontScale;
    1630                 :            : 
    1631                 :          0 :   double labelWidth = fontMetrics->maxWidth() / fontScale; // label width represents the width of one line of a multi-line label
    1632                 :          0 :   double actualLabelWidest = labelWidth + ( textLines.size() - 1 ) * labelWidth * format.lineHeight();
    1633                 :          0 :   double labelWidest = 0.0;
    1634                 :          0 :   switch ( mode )
    1635                 :            :   {
    1636                 :            :     case Label:
    1637                 :            :     case Point:
    1638                 :          0 :       labelWidest = actualLabelWidest;
    1639                 :          0 :       break;
    1640                 :            : 
    1641                 :            :     case Rect:
    1642                 :          0 :       labelWidest = component.size.width();
    1643                 :          0 :       break;
    1644                 :            :   }
    1645                 :            : 
    1646                 :          0 :   int maxLineLength = 0;
    1647                 :          0 :   for ( const QString &line : std::as_const( textLines ) )
    1648                 :            :   {
    1649                 :          0 :     maxLineLength = std::max( maxLineLength, static_cast<int>( line.length() ) );
    1650                 :            :   }
    1651                 :          0 :   double actualLabelHeight = fontMetrics->ascent() / fontScale + ( fontMetrics->ascent() / fontScale + letterSpacing ) * ( maxLineLength - 1 );
    1652                 :          0 :   double ascentOffset = fontMetrics->ascent() / fontScale;
    1653                 :            : 
    1654                 :          0 :   int i = 0;
    1655                 :            : 
    1656                 :          0 :   bool adjustForAlignment = hAlignment != AlignLeft && ( mode != Label || textLines.size() > 1 );
    1657                 :            : 
    1658                 :          0 :   for ( const QgsTextBlock &block : document )
    1659                 :            :   {
    1660                 :          0 :     QgsScopedQPainterState painterState( context.painter() );
    1661                 :          0 :     context.setPainterFlagsUsingContext();
    1662                 :            : 
    1663                 :          0 :     context.painter()->translate( component.origin );
    1664                 :          0 :     if ( !qgsDoubleNear( rotation, 0.0 ) )
    1665                 :          0 :       context.painter()->rotate( rotation );
    1666                 :            : 
    1667                 :            :     // apply to the mask painter the same transformations
    1668                 :          0 :     if ( maskPainter )
    1669                 :            :     {
    1670                 :          0 :       maskPainter->save();
    1671                 :          0 :       maskPainter->translate( component.origin );
    1672                 :          0 :       if ( !qgsDoubleNear( rotation, 0.0 ) )
    1673                 :          0 :         maskPainter->rotate( rotation );
    1674                 :          0 :     }
    1675                 :            : 
    1676                 :            :     // figure x offset of multiple lines
    1677                 :          0 :     double xOffset = actualLabelWidest - labelWidth - ( i * labelWidth * format.lineHeight() );
    1678                 :          0 :     if ( adjustForAlignment )
    1679                 :            :     {
    1680                 :          0 :       double labelWidthDiff = 0;
    1681                 :          0 :       switch ( hAlignment )
    1682                 :            :       {
    1683                 :            :         case AlignCenter:
    1684                 :          0 :           labelWidthDiff = ( labelWidest - actualLabelWidest ) * 0.5;
    1685                 :          0 :           break;
    1686                 :            : 
    1687                 :            :         case AlignRight:
    1688                 :          0 :           labelWidthDiff = labelWidest - actualLabelWidest;
    1689                 :          0 :           break;
    1690                 :            : 
    1691                 :            :         case AlignLeft:
    1692                 :            :         case AlignJustify:
    1693                 :          0 :           break;
    1694                 :            :       }
    1695                 :            : 
    1696                 :          0 :       switch ( mode )
    1697                 :            :       {
    1698                 :            :         case Label:
    1699                 :            :         case Rect:
    1700                 :          0 :           xOffset += labelWidthDiff;
    1701                 :          0 :           break;
    1702                 :            : 
    1703                 :            :         case Point:
    1704                 :          0 :           break;
    1705                 :            :       }
    1706                 :          0 :     }
    1707                 :            : 
    1708                 :          0 :     double yOffset = 0.0;
    1709                 :          0 :     switch ( mode )
    1710                 :            :     {
    1711                 :            :       case Label:
    1712                 :          0 :         if ( format.orientation() == QgsTextFormat::RotationBasedOrientation )
    1713                 :            :         {
    1714                 :          0 :           if ( rotation >= -405 && rotation < -180 )
    1715                 :            :           {
    1716                 :          0 :             yOffset = ascentOffset;
    1717                 :          0 :           }
    1718                 :          0 :           else if ( rotation >= 0 && rotation < 45 )
    1719                 :            :           {
    1720                 :          0 :             xOffset -= actualLabelWidest;
    1721                 :          0 :             yOffset = -actualLabelHeight + ascentOffset + fontMetrics->descent() / fontScale;
    1722                 :          0 :           }
    1723                 :          0 :         }
    1724                 :            :         else
    1725                 :            :         {
    1726                 :          0 :           yOffset = -actualLabelHeight + ascentOffset;
    1727                 :            :         }
    1728                 :          0 :         break;
    1729                 :            : 
    1730                 :            :       case Point:
    1731                 :          0 :         yOffset = -actualLabelHeight + ascentOffset;
    1732                 :          0 :         break;
    1733                 :            : 
    1734                 :            :       case Rect:
    1735                 :          0 :         yOffset = ascentOffset;
    1736                 :          0 :         break;
    1737                 :            :     }
    1738                 :            : 
    1739                 :          0 :     context.painter()->translate( QPointF( xOffset, yOffset ) );
    1740                 :            : 
    1741                 :          0 :     double fragmentYOffset = 0;
    1742                 :          0 :     for ( const QgsTextFragment &fragment : block )
    1743                 :            :     {
    1744                 :            :       // apply some character replacement to draw symbols in vertical presentation
    1745                 :          0 :       const QString line = QgsStringUtils::substituteVerticalCharacters( fragment.text() );
    1746                 :            : 
    1747                 :          0 :       QFont fragmentFont( font );
    1748                 :          0 :       fragment.characterFormat().updateFontForFormat( fragmentFont, fontScale );
    1749                 :            : 
    1750                 :          0 :       QFontMetricsF fragmentMetrics( fragmentFont );
    1751                 :            : 
    1752                 :          0 :       double labelHeight = fragmentMetrics.ascent() / fontScale + ( fragmentMetrics.ascent() / fontScale + letterSpacing ) * ( line.length() - 1 );
    1753                 :            : 
    1754                 :          0 :       Component subComponent;
    1755                 :          0 :       subComponent.block = QgsTextBlock( fragment );
    1756                 :          0 :       subComponent.size = QSizeF( labelWidth, labelHeight );
    1757                 :          0 :       subComponent.offset = QPointF( 0.0, fragmentYOffset );
    1758                 :          0 :       subComponent.rotation = -component.rotation * 180 / M_PI;
    1759                 :          0 :       subComponent.rotationOffset = 0.0;
    1760                 :            : 
    1761                 :            :       // draw the mask below the text (for preview)
    1762                 :          0 :       if ( format.mask().enabled() )
    1763                 :            :       {
    1764                 :            :         // WARNING: totally broken! (has been since mask was introduced)
    1765                 :            : #if 0
    1766                 :            :         QgsTextRenderer::drawMask( context, subComponent, format );
    1767                 :            : #endif
    1768                 :          0 :       }
    1769                 :            : 
    1770                 :          0 :       if ( drawType == QgsTextRenderer::Buffer )
    1771                 :            :       {
    1772                 :          0 :         fragmentYOffset += QgsTextRenderer::drawBuffer( context, subComponent, format );
    1773                 :          0 :       }
    1774                 :            :       else
    1775                 :            :       {
    1776                 :            :         // draw text, QPainterPath method
    1777                 :          0 :         QPainterPath path;
    1778                 :          0 :         path.setFillRule( Qt::WindingFill );
    1779                 :          0 :         const QStringList parts = QgsPalLabeling::splitToGraphemes( fragment.text() );
    1780                 :          0 :         double partYOffset = 0.0;
    1781                 :          0 :         for ( const auto &part : parts )
    1782                 :            :         {
    1783                 :          0 :           double partXOffset = ( labelWidth - ( fragmentMetrics.horizontalAdvance( part ) / fontScale - letterSpacing ) ) / 2;
    1784                 :          0 :           path.addText( partXOffset * fontScale, partYOffset * fontScale, fragmentFont, part );
    1785                 :          0 :           partYOffset += fragmentMetrics.ascent() / fontScale + letterSpacing;
    1786                 :            :         }
    1787                 :            : 
    1788                 :            :         // store text's drawing in QPicture for drop shadow call
    1789                 :          0 :         QPicture textPict;
    1790                 :          0 :         QPainter textp;
    1791                 :          0 :         textp.begin( &textPict );
    1792                 :          0 :         textp.setPen( Qt::NoPen );
    1793                 :          0 :         QColor textColor = fragment.characterFormat().textColor().isValid() ? fragment.characterFormat().textColor() : format.color();
    1794                 :          0 :         textColor.setAlphaF( format.opacity() );
    1795                 :          0 :         textp.setBrush( textColor );
    1796                 :          0 :         textp.scale( 1 / fontScale, 1 / fontScale );
    1797                 :          0 :         textp.drawPath( path );
    1798                 :            :         // TODO: why are some font settings lost on drawPicture() when using drawText() inside QPicture?
    1799                 :            :         //       e.g. some capitalization options, but not others
    1800                 :            :         //textp.setFont( tmpLyr.textFont );
    1801                 :            :         //textp.setPen( tmpLyr.textColor );
    1802                 :            :         //textp.drawText( 0, 0, component.text() );
    1803                 :          0 :         textp.end();
    1804                 :            : 
    1805                 :          0 :         if ( format.shadow().enabled() && format.shadow().shadowPlacement() == QgsTextShadowSettings::ShadowText )
    1806                 :            :         {
    1807                 :          0 :           subComponent.picture = textPict;
    1808                 :          0 :           subComponent.pictureBuffer = 0.0; // no pen width to deal with
    1809                 :          0 :           subComponent.origin = QPointF( 0.0, fragmentYOffset );
    1810                 :          0 :           const double prevY = subComponent.offset.y();
    1811                 :          0 :           subComponent.offset = QPointF( 0, -labelHeight );
    1812                 :          0 :           subComponent.useOrigin = true;
    1813                 :          0 :           QgsTextRenderer::drawShadow( context, subComponent, format );
    1814                 :          0 :           subComponent.useOrigin = false;
    1815                 :          0 :           subComponent.offset = QPointF( 0, prevY );
    1816                 :          0 :         }
    1817                 :            : 
    1818                 :            :         // paint the text
    1819                 :          0 :         if ( context.useAdvancedEffects() )
    1820                 :            :         {
    1821                 :          0 :           context.painter()->setCompositionMode( format.blendMode() );
    1822                 :          0 :         }
    1823                 :            : 
    1824                 :            :         // scale for any print output or image saving @ specific dpi
    1825                 :          0 :         context.painter()->scale( subComponent.dpiRatio, subComponent.dpiRatio );
    1826                 :            : 
    1827                 :          0 :         switch ( context.textRenderFormat() )
    1828                 :            :         {
    1829                 :            :           case QgsRenderContext::TextFormatAlwaysOutlines:
    1830                 :            :           {
    1831                 :            :             // draw outlined text
    1832                 :          0 :             _fixQPictureDPI( context.painter() );
    1833                 :          0 :             context.painter()->drawPicture( 0, fragmentYOffset, textPict );
    1834                 :          0 :             fragmentYOffset += partYOffset;
    1835                 :          0 :             break;
    1836                 :            :           }
    1837                 :            : 
    1838                 :            :           case QgsRenderContext::TextFormatAlwaysText:
    1839                 :            :           {
    1840                 :          0 :             context.painter()->setFont( fragmentFont );
    1841                 :          0 :             context.painter()->setPen( textColor );
    1842                 :          0 :             context.painter()->setRenderHint( QPainter::TextAntialiasing );
    1843                 :            : 
    1844                 :          0 :             double partYOffset = 0.0;
    1845                 :          0 :             for ( const QString &part : parts )
    1846                 :            :             {
    1847                 :          0 :               double partXOffset = ( labelWidth - ( fragmentMetrics.horizontalAdvance( part ) / fontScale - letterSpacing ) ) / 2;
    1848                 :          0 :               context.painter()->scale( 1 / fontScale, 1 / fontScale );
    1849                 :          0 :               context.painter()->drawText( partXOffset * fontScale, ( fragmentYOffset + partYOffset ) * fontScale, part );
    1850                 :          0 :               context.painter()->scale( fontScale, fontScale );
    1851                 :          0 :               partYOffset += fragmentMetrics.ascent() / fontScale + letterSpacing;
    1852                 :            :             }
    1853                 :          0 :             fragmentYOffset += partYOffset;
    1854                 :            :           }
    1855                 :          0 :         }
    1856                 :          0 :       }
    1857                 :          0 :     }
    1858                 :            : 
    1859                 :          0 :     if ( maskPainter )
    1860                 :          0 :       maskPainter->restore();
    1861                 :          0 :     i++;
    1862                 :          0 :   }
    1863                 :          0 : }

Generated by: LCOV version 1.14