31 #include <QTextBoundaryFinder>
36 static void _fixQPictureDPI( QPainter *p )
42 p->scale(
static_cast< double >(
qt_defaultDpiX() ) / p->device()->logicalDpiX(),
43 static_cast< double >(
qt_defaultDpiY() ) / p->device()->logicalDpiY() );
48 if ( alignment & Qt::AlignLeft )
50 else if ( alignment & Qt::AlignRight )
52 else if ( alignment & Qt::AlignHCenter )
54 else if ( alignment & Qt::AlignJustify )
63 if ( alignment & Qt::AlignTop )
65 else if ( alignment & Qt::AlignBottom )
67 else if ( alignment & Qt::AlignVCenter )
70 else if ( alignment & Qt::AlignBaseline )
78 return static_cast< int >(
c.convertToPainterUnits( size, unit, mapUnitScale ) + 0.5 );
86 tmpFormat = updateShadowPosition( tmpFormat );
88 QStringList textLines;
89 for (
const QString &line : text )
93 textLines.append(
wrappedText( context, line, rect.width(), format ) );
97 textLines.append( line );
106 drawPart( rect, rotation, alignment, vAlignment, document, context, tmpFormat,
Background );
111 drawPart( rect, rotation, alignment, vAlignment, document, context, tmpFormat,
Buffer );
114 drawPart( rect, rotation, alignment, vAlignment, document, context, tmpFormat,
Text );
122 tmpFormat = updateShadowPosition( tmpFormat );
134 drawPart( point, rotation, alignment, document, context, tmpFormat,
Buffer );
137 drawPart( point, rotation, alignment, document, context, tmpFormat,
Text );
166 drawPart( rect, rotation, alignment,
AlignTop, document, context, format, part );
177 component.dpiRatio = 1.0;
178 component.origin = rect.topLeft();
179 component.rotation = rotation;
180 component.size = rect.size();
181 component.hAlign = alignment;
194 double xc = rect.width() / 2.0;
195 double yc = rect.height() / 2.0;
197 double angle = -rotation;
198 double xd = xc * std::cos(
angle ) - yc * std::sin(
angle );
199 double yd = xc * std::sin(
angle ) + yc * std::cos(
angle );
201 component.center = QPointF( component.origin.x() + xd, component.origin.y() + yd );
205 component.center = rect.center();
208 QgsTextRenderer::drawBackground( context, component, format, document,
Rect );
222 drawTextInternal( part, context, format, component,
225 alignment, vAlignment );
234 drawPart( origin, rotation, alignment, document, context, format, part );
245 component.dpiRatio = 1.0;
246 component.origin = origin;
247 component.rotation = rotation;
248 component.hAlign = alignment;
257 QgsTextRenderer::drawBackground( context, component, format, document,
Point );
270 drawTextInternal( part, context, format, component,
282 return QFontMetricsF( format.
scaledFont( context, scaleFactor ), context.
painter() ? context.
painter()->device() :
nullptr );
288 QPainter *p = context.
painter();
293 if ( component.rotation >= -315 && component.rotation < -90 )
297 else if ( component.rotation >= -90 && component.rotation < -45 )
313 const double scaleFactor = calculateScaleFactorForFormat( context, format );
315 std::optional< QgsScopedRenderContextReferenceScaleOverride > referenceScaleOverride;
323 bool isNullSize =
false;
324 const QFont font = format.
scaledFont( context, scaleFactor, &isNullSize );
328 referenceScaleOverride.reset();
331 path.setFillRule( Qt::WindingFill );
333 switch ( orientation )
340 QFont fragmentFont = font;
343 if ( component.extraWordSpacing || component.extraLetterSpacing )
344 applyExtraSpacingForLineJustification( fragmentFont, component.extraWordSpacing, component.extraLetterSpacing );
346 path.addText( xOffset, 0, fragmentFont, fragment.
text() );
357 double letterSpacing = font.letterSpacing();
358 double partYOffset = component.offset.y() * scaleFactor;
361 QFont fragmentFont = font;
364 QFontMetricsF fragmentMetrics( fragmentFont );
365 const double labelWidth = fragmentMetrics.maxWidth();
368 for (
const QString &part : parts )
370 double partXOffset = ( labelWidth - ( fragmentMetrics.horizontalAdvance( part ) - letterSpacing ) ) / 2;
371 path.addText( partXOffset, partYOffset, fragmentFont, part );
372 partYOffset += fragmentMetrics.ascent() + letterSpacing;
375 advance = partYOffset - component.offset.y() * scaleFactor;
380 QColor bufferColor = buffer.
color();
381 bufferColor.setAlphaF( buffer.
opacity() );
382 QPen pen( bufferColor );
383 pen.setWidthF( penSize * scaleFactor );
385 QColor tmpColor( bufferColor );
389 tmpColor.setAlpha( 0 );
395 buffp.begin( &buffPict );
399 std::unique_ptr< QgsPaintEffect > tmpEffect( buffer.
paintEffect()->
clone() );
401 tmpEffect->begin( context );
402 context.
painter()->setPen( pen );
403 context.
painter()->setBrush( tmpColor );
404 if ( scaleFactor != 1.0 )
405 context.
painter()->scale( 1 / scaleFactor, 1 / scaleFactor );
406 context.
painter()->drawPath( path );
407 if ( scaleFactor != 1.0 )
408 context.
painter()->scale( scaleFactor, scaleFactor );
409 tmpEffect->end( context );
415 if ( scaleFactor != 1.0 )
416 buffp.scale( 1 / scaleFactor, 1 / scaleFactor );
418 buffp.setBrush( tmpColor );
419 buffp.drawPath( path );
425 QgsTextRenderer::Component bufferComponent = component;
426 bufferComponent.origin = QPointF( 0.0, 0.0 );
427 bufferComponent.picture = buffPict;
428 bufferComponent.pictureBuffer = penSize / 2.0;
432 bufferComponent.offset.setY( bufferComponent.offset.y() - bufferComponent.size.height() );
434 drawShadow( context, bufferComponent, format );
442 p->setCompositionMode( buffer.
blendMode() );
446 p->scale( component.dpiRatio, component.dpiRatio );
447 _fixQPictureDPI( p );
448 p->drawPicture( 0, 0, buffPict );
450 return advance / scaleFactor;
470 path.setFillRule( Qt::WindingFill );
472 const double scaleFactor = calculateScaleFactorForFormat( context, format );
477 std::optional< QgsScopedRenderContextReferenceScaleOverride > referenceScaleOverride;
485 bool isNullSize =
false;
486 const QFont font = format.
scaledFont( context, scaleFactor, &isNullSize );
490 referenceScaleOverride.reset();
495 QFont fragmentFont = font;
498 path.addText( xOffset, 0, fragmentFont, fragment.
text() );
503 QColor bufferColor( Qt::gray );
504 bufferColor.setAlphaF( mask.
opacity() );
508 brush.setColor( bufferColor );
509 pen.setColor( bufferColor );
510 pen.setWidthF( penSize * scaleFactor );
517 p->scale( component.dpiRatio, component.dpiRatio );
523 if ( scaleFactor != 1.0 )
524 context.
painter()->scale( 1 / scaleFactor, 1 / scaleFactor );
525 context.
painter()->setPen( pen );
526 context.
painter()->setBrush( brush );
527 context.
painter()->drawPath( path );
528 if ( scaleFactor != 1.0 )
529 context.
painter()->scale( scaleFactor, scaleFactor );
534 if ( scaleFactor != 1.0 )
535 p->scale( 1 / scaleFactor, 1 / scaleFactor );
537 p->setBrush( brush );
539 if ( scaleFactor != 1.0 )
540 p->scale( scaleFactor, scaleFactor );
557 return textWidth( context, format, doc );
563 const double scaleFactor = calculateScaleFactorForFormat( context, format );
565 bool isNullSize =
false;
566 const QFont baseFont = format.
scaledFont( context, scaleFactor, &isNullSize );
575 double maxLineWidth = 0;
578 double blockWidth = 0;
583 maxLineWidth = std::max( maxLineWidth, blockWidth );
585 width = maxLineWidth;
591 double totalLineWidth = 0;
595 double blockWidth = 0;
598 QFont fragmentFont = baseFont;
600 blockWidth = std::max( QFontMetricsF( fragmentFont ).maxWidth(), blockWidth );
603 totalLineWidth += blockIndex == 0 ? blockWidth : blockWidth * format.
lineHeight();
606 width = totalLineWidth;
617 return width / scaleFactor;
623 for (
const QString &line : textLines )
627 lines.append(
wrappedText( context, line, maxLineWidth, format ) );
631 lines.append( line );
647 const double scaleFactor = calculateScaleFactorForFormat( context, format );
648 bool isNullSize =
false;
649 const QFont baseFont = format.
scaledFont( context, scaleFactor, &isNullSize );
653 const QFontMetrics fm( baseFont );
654 const double height = ( character.isNull() ? fm.height() : fm.boundingRect( character ).height() ) / scaleFactor;
656 if ( !includeEffects )
659 double maxExtension = 0;
688 return height + maxExtension;
696 const QStringList multiLineSplit = text.split(
'\n' );
698 return currentTextWidth > width;
703 const QStringList lines = text.split(
'\n' );
704 QStringList outLines;
705 for (
const QString &line : lines )
710 const QStringList words = line.split(
' ' );
711 QStringList linesToProcess;
712 QString wordsInCurrentLine;
713 for (
const QString &word : words )
718 if ( !wordsInCurrentLine.isEmpty() )
719 linesToProcess << wordsInCurrentLine;
720 wordsInCurrentLine.clear();
721 linesToProcess << word;
725 if ( !wordsInCurrentLine.isEmpty() )
726 wordsInCurrentLine.append(
' ' );
727 wordsInCurrentLine.append( word );
730 if ( !wordsInCurrentLine.isEmpty() )
731 linesToProcess << wordsInCurrentLine;
733 for (
const QString &line : std::as_const( linesToProcess ) )
735 QString remainingText = line;
736 int lastPos = remainingText.lastIndexOf(
' ' );
737 while ( lastPos > -1 )
747 outLines << remainingText.left( lastPos );
748 remainingText = remainingText.mid( lastPos + 1 );
751 lastPos = remainingText.lastIndexOf(
' ', lastPos - 1 );
753 outLines << remainingText;
771 const double scaleFactor = calculateScaleFactorForFormat( context, format );
773 bool isNullSize =
false;
774 const QFont baseFont = format.
scaledFont( context, scaleFactor, &isNullSize );
783 double totalHeight = 0;
784 double lastLineLeading = 0;
787 double maxBlockHeight = 0;
788 double maxBlockLineSpacing = 0;
789 double maxBlockLeading = 0;
792 QFont fragmentFont = baseFont;
794 const QFontMetricsF fm( fragmentFont );
796 const double fragmentHeight = fm.ascent() + fm.descent();
798 maxBlockHeight = std::max( maxBlockHeight, fragmentHeight );
799 if ( fm.lineSpacing() > maxBlockLineSpacing )
801 maxBlockLineSpacing = fm.lineSpacing();
802 maxBlockLeading = fm.leading();
812 totalHeight += blockIndex == 0 ? maxBlockHeight : maxBlockHeight * format.
lineHeight();
818 totalHeight += blockIndex == 0 ? maxBlockHeight : maxBlockLineSpacing * format.
lineHeight();
819 if ( blockIndex > 0 )
820 lastLineLeading = maxBlockLeading;
827 return ( totalHeight - lastLineLeading ) / scaleFactor;
832 double maxBlockHeight = 0;
835 double blockHeight = 0;
836 int fragmentIndex = 0;
839 QFont fragmentFont = baseFont;
841 const QFontMetricsF fm( fragmentFont );
843 const double labelHeight = fm.ascent();
844 const double letterSpacing = fragmentFont.letterSpacing();
846 blockHeight += fragmentIndex = 0 ? labelHeight * fragment.
text().size() + ( fragment.
text().size() - 1 ) * letterSpacing
847 : fragment.
text().size() * ( labelHeight + letterSpacing );
850 maxBlockHeight = std::max( maxBlockHeight, blockHeight );
853 return maxBlockHeight / scaleFactor;
870 QPainter *prevP = context.
painter();
871 QPainter *p = context.
painter();
872 std::unique_ptr< QgsPaintEffect > tmpEffect;
876 tmpEffect->begin( context );
885 const double originAdjustRotationRadians = -component.rotation;
888 component.rotation = -( component.rotation * 180 / M_PI );
889 component.rotationOffset =
894 component.rotation = 0.0;
895 component.rotationOffset = background.
rotation();
898 const double scaleFactor = calculateScaleFactorForFormat( context, format );
903 double width =
textWidth( context, format, document );
904 double height =
textHeight( context, format, document, mode );
909 switch ( component.hAlign )
913 component.center = QPointF( component.origin.x() + width / 2.0,
914 component.origin.y() + height / 2.0 );
918 component.center = QPointF( component.origin.x() + component.size.width() / 2.0,
919 component.origin.y() + height / 2.0 );
923 component.center = QPointF( component.origin.x() + component.size.width() - width / 2.0,
924 component.origin.y() + height / 2.0 );
931 bool isNullSize =
false;
932 QFontMetricsF fm( format.
scaledFont( context, scaleFactor, &isNullSize ) );
933 double originAdjust = isNullSize ? 0 : ( fm.ascent() / scaleFactor / 2.0 - fm.leading() / scaleFactor / 2.0 );
934 switch ( component.hAlign )
938 component.center = QPointF( component.origin.x() + width / 2.0,
939 component.origin.y() - height / 2.0 + originAdjust );
943 component.center = QPointF( component.origin.x(),
944 component.origin.y() - height / 2.0 + originAdjust );
948 component.center = QPointF( component.origin.x() - width / 2.0,
949 component.origin.y() - height / 2.0 + originAdjust );
956 const double dx = component.center.x() - component.origin.x();
957 const double dy = component.center.y() - component.origin.y();
958 component.center.setX( component.origin.x() + ( std::cos( originAdjustRotationRadians ) * dx - std::sin( originAdjustRotationRadians ) * dy ) );
959 component.center.setY( component.origin.y() + ( std::sin( originAdjustRotationRadians ) * dx + std::cos( originAdjustRotationRadians ) * dy ) );
969 component.size = QSizeF( width, height );
974 switch ( background.
type() )
987 double sizeOut = 0.0;
995 sizeOut = std::max( component.size.width(), component.size.height() );
999 sizeOut += bufferSize * 2;
1004 if ( sizeOut < 1.0 )
1007 std::unique_ptr< QgsMarkerSymbol > renderedSymbol;
1011 map[QStringLiteral(
"name" )] = background.
svgFile().trimmed();
1012 map[QStringLiteral(
"size" )] = QString::number( sizeOut );
1014 map[QStringLiteral(
"angle" )] = QString::number( 0.0 );
1022 map[QStringLiteral(
"fill" )] = background.
fillColor().name();
1023 map[QStringLiteral(
"outline" )] = background.
strokeColor().name();
1024 map[QStringLiteral(
"outline-width" )] = QString::number( background.
strokeWidth() );
1031 QVariantMap shdwmap( map );
1032 shdwmap[QStringLiteral(
"fill" )] = shadow.
color().name();
1033 shdwmap[QStringLiteral(
"outline" )] = shadow.
color().name();
1034 shdwmap[QStringLiteral(
"size" )] = QString::number( sizeOut );
1039 svgp.begin( &svgPict );
1056 svgShdwM->
renderPoint( QPointF( sizeOut / 2, -sizeOut / 2 ), svgShdwContext );
1059 component.picture = svgPict;
1061 component.pictureBuffer = 0.0;
1063 component.size = QSizeF( sizeOut, sizeOut );
1064 component.offset = QPointF( 0.0, 0.0 );
1070 p->translate( component.center.x(), component.center.y() );
1071 p->rotate( component.rotation );
1074 p->translate( QPointF( xoff, yoff ) );
1075 p->rotate( component.rotationOffset );
1076 p->translate( -sizeOut / 2, sizeOut / 2 );
1078 drawShadow( context, component, format );
1080 renderedSymbol.reset( );
1088 renderedSymbol->setSize( sizeOut );
1092 renderedSymbol->setOpacity( renderedSymbol->opacity() * background.
opacity() );
1100 p->setCompositionMode( background.
blendMode() );
1102 p->translate( component.center.x(), component.center.y() );
1103 p->rotate( component.rotation );
1106 p->translate( QPointF( xoff, yoff ) );
1107 p->rotate( component.rotationOffset );
1111 renderedSymbol->renderPoint( QPointF( 0, 0 ), &f, context );
1112 renderedSymbol->stopRender( context );
1113 p->setCompositionMode( QPainter::CompositionMode_SourceOver );
1123 double w = component.size.width();
1124 double h = component.size.height();
1145 h = std::sqrt( std::pow( w, 2 ) + std::pow( h, 2 ) );
1151 h = h * M_SQRT1_2 * 2;
1152 w = w * M_SQRT1_2 * 2;
1160 w += bufferWidth * 2;
1161 h += bufferHeight * 2;
1165 QRectF rect( -w / 2.0, - h / 2.0, w, h );
1167 if ( rect.isNull() )
1173 p->translate( QPointF( component.center.x(), component.center.y() ) );
1174 p->rotate( component.rotation );
1177 p->translate( QPointF( xoff, yoff ) );
1178 p->rotate( component.rotationOffset );
1184 QTransform t = QTransform::fromScale( 10, 10 );
1186 QTransform ti = t.inverted();
1193 path.addRoundedRect( rect, background.
radii().width(), background.
radii().height(), Qt::RelativeSize );
1199 path.addRoundedRect( rect, xRadius, yRadius );
1205 path.addEllipse( rect );
1207 QPolygonF tempPolygon = path.toFillPolygon( t );
1208 QPolygonF polygon = ti.map( tempPolygon );
1210 QPainter *oldp = context.
painter();
1213 shapep.begin( &shapePict );
1216 std::unique_ptr< QgsFillSymbol > renderedSymbol;
1218 renderedSymbol->setOpacity( renderedSymbol->opacity() * background.
opacity() );
1222 renderedSymbol->renderPolygon( polygon,
nullptr, &f, context );
1223 renderedSymbol->stopRender( context );
1230 component.picture = shapePict;
1233 component.size = rect.size();
1234 component.offset = QPointF( rect.width() / 2, -rect.height() / 2 );
1235 drawShadow( context, component, format );
1240 p->setCompositionMode( background.
blendMode() );
1244 p->scale( component.dpiRatio, component.dpiRatio );
1245 _fixQPictureDPI( p );
1246 p->drawPicture( 0, 0, shapePict );
1247 p->setCompositionMode( QPainter::CompositionMode_SourceOver );
1254 tmpEffect->end( context );
1267 QPainter *p = context.
painter();
1268 double componentWidth = component.size.width(), componentHeight = component.size.height();
1269 double xOffset = component.offset.x(), yOffset = component.offset.y();
1270 double pictbuffer = component.pictureBuffer;
1279 radius /= ( mapUnits ? context.
scaleFactor() / component.dpiRatio : 1 );
1280 radius =
static_cast< int >( radius + 0.5 );
1284 double blurBufferClippingScale = 3.75;
1285 int blurbuffer = ( radius > 17 ? 16 : radius ) * blurBufferClippingScale;
1287 QImage blurImg( componentWidth + ( pictbuffer * 2.0 ) + ( blurbuffer * 2.0 ),
1288 componentHeight + ( pictbuffer * 2.0 ) + ( blurbuffer * 2.0 ),
1289 QImage::Format_ARGB32_Premultiplied );
1293 int minBlurImgSize = 1;
1297 int maxBlurImgSize = 40000;
1298 if ( blurImg.isNull()
1299 || ( blurImg.width() < minBlurImgSize || blurImg.height() < minBlurImgSize )
1300 || ( blurImg.width() > maxBlurImgSize || blurImg.height() > maxBlurImgSize ) )
1303 blurImg.fill( QColor( Qt::transparent ).rgba() );
1305 if ( !pictp.begin( &blurImg ) )
1307 pictp.setRenderHints( QPainter::Antialiasing | QPainter::SmoothPixmapTransform );
1308 QPointF imgOffset( blurbuffer + pictbuffer + xOffset,
1309 blurbuffer + pictbuffer + componentHeight + yOffset );
1311 pictp.drawPicture( imgOffset,
1312 component.picture );
1315 pictp.setCompositionMode( QPainter::CompositionMode_SourceIn );
1316 pictp.fillRect( blurImg.rect(), shadow.
color() );
1320 if ( shadow.
blurRadius() > 0.0 && radius > 0 )
1328 picti.begin( &blurImg );
1329 picti.setBrush( Qt::Dense7Pattern );
1330 QPen imgPen( QColor( 0, 0, 255, 255 ) );
1331 imgPen.setWidth( 1 );
1332 picti.setPen( imgPen );
1333 picti.setOpacity( 0.1 );
1334 picti.drawRect( 0, 0, blurImg.width(), blurImg.height() );
1341 double angleRad = shadow.
offsetAngle() * M_PI / 180;
1349 angleRad -= ( component.rotation * M_PI / 180 + component.rotationOffset * M_PI / 180 );
1352 QPointF transPt( -offsetDist * std::cos( angleRad + M_PI_2 ),
1353 -offsetDist * std::sin( angleRad + M_PI_2 ) );
1359 p->setRenderHint( QPainter::SmoothPixmapTransform );
1362 p->setCompositionMode( shadow.
blendMode() );
1364 p->setOpacity( shadow.
opacity() );
1366 double scale = shadow.
scale() / 100.0;
1368 p->scale( scale, scale );
1369 if ( component.useOrigin )
1371 p->translate( component.origin.x(), component.origin.y() );
1373 p->translate( transPt );
1374 p->translate( -imgOffset.x(),
1376 p->drawImage( 0, 0, blurImg );
1383 p->setBrush( Qt::NoBrush );
1384 QPen imgPen( QColor( 255, 0, 0, 10 ) );
1385 imgPen.setWidth( 2 );
1386 imgPen.setStyle( Qt::DashLine );
1387 p->setPen( imgPen );
1388 p->scale( scale, scale );
1389 if ( component.useOrigin() )
1391 p->translate( component.origin().x(), component.origin().y() );
1393 p->translate( transPt );
1394 p->translate( -imgOffset.x(),
1396 p->drawRect( 0, 0, blurImg.width(), blurImg.height() );
1401 p->setBrush( Qt::NoBrush );
1402 QPen componentRectPen( QColor( 0, 255, 0, 70 ) );
1403 componentRectPen.setWidth( 1 );
1404 if ( component.useOrigin() )
1406 p->translate( component.origin().x(), component.origin().y() );
1408 p->setPen( componentRectPen );
1409 p->drawRect( QRect( -xOffset, -componentHeight - yOffset, componentWidth, componentHeight ) );
1415 void QgsTextRenderer::drawTextInternal( TextPart drawType,
1418 const Component &component,
1420 const QFontMetricsF *fontMetrics,
1421 HAlignment alignment, VAlignment vAlignment, DrawMode mode )
1428 double fontScale = 1.0;
1429 std::unique_ptr< QFontMetricsF > tmpMetrics;
1432 fontScale = calculateScaleFactorForFormat( context, format );
1434 std::optional< QgsScopedRenderContextReferenceScaleOverride > referenceScaleOverride;
1435 if ( mode ==
Label )
1442 bool isNullSize =
false;
1443 const QFont f = format.
scaledFont( context, fontScale, &isNullSize );
1447 tmpMetrics = std::make_unique< QFontMetricsF >( f );
1450 referenceScaleOverride.reset();
1453 double rotation = 0;
1455 switch ( orientation )
1459 drawTextInternalHorizontal( context, format, drawType, mode, component, document, fontScale,
fontMetrics, alignment, vAlignment, rotation );
1466 drawTextInternalVertical( context, format, drawType, mode, component, document, fontScale,
fontMetrics, alignment, vAlignment, rotation );
1474 rotation = -component.rotation * 180 / M_PI;
1481 if ( rotation >= -315 && rotation < -90 )
1486 else if ( rotation >= -90 && rotation < -45 )
1502 void QgsTextRenderer::calculateExtraSpacingForLineJustification(
const double spaceToDistribute,
const QgsTextBlock &block,
double &extraWordSpace,
double &extraLetterSpace )
1505 QTextBoundaryFinder
finder( QTextBoundaryFinder::Word, blockText );
1507 int wordBoundaries = 0;
1508 while (
finder.toNextBoundary() != -1 )
1510 if (
finder.boundaryReasons() & QTextBoundaryFinder::StartOfItem )
1514 if ( wordBoundaries > 0 )
1517 extraWordSpace = spaceToDistribute / wordBoundaries;
1522 QTextBoundaryFinder
finder( QTextBoundaryFinder::Grapheme, blockText );
1525 int graphemeBoundaries = 0;
1526 while (
finder.toNextBoundary() != -1 )
1528 if (
finder.boundaryReasons() & QTextBoundaryFinder::StartOfItem )
1529 graphemeBoundaries++;
1532 if ( graphemeBoundaries > 0 )
1534 extraLetterSpace = spaceToDistribute / graphemeBoundaries;
1539 void QgsTextRenderer::applyExtraSpacingForLineJustification( QFont &font,
double extraWordSpace,
double extraLetterSpace )
1541 const double prevWordSpace = font.wordSpacing();
1542 font.setWordSpacing( prevWordSpace + extraWordSpace );
1543 const double prevLetterSpace = font.letterSpacing();
1544 font.setLetterSpacing( QFont::AbsoluteSpacing, prevLetterSpace + extraLetterSpace );
1547 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,
1548 VAlignment vAlignment,
double rotation )
1551 const QStringList textLines = document.
toPlainText();
1553 double labelWidest = 0.0;
1558 for (
const QString &line : textLines )
1560 double labelWidth =
fontMetrics->horizontalAdvance( line ) / fontScale;
1561 if ( labelWidth > labelWidest )
1563 labelWidest = labelWidth;
1569 labelWidest = component.size.width();
1577 double ascentOffset = 0.25 *
fontMetrics->ascent() / fontScale;
1581 bool adjustForAlignment = hAlignment !=
AlignLeft && ( mode !=
Label || textLines.size() > 1 );
1585 std::optional< QgsScopedRenderContextReferenceScaleOverride > referenceScaleOverride;
1587 const double overallHeight =
textHeight( context, format, textLines,
Rect );
1588 switch ( vAlignment )
1594 ascentOffset = -( component.size.height() - overallHeight ) * 0.5 + ascentOffset;
1598 ascentOffset = -( component.size.height() - overallHeight ) + ascentOffset;
1601 referenceScaleOverride.reset();
1604 for (
const QString &line : std::as_const( textLines ) )
1608 const bool isFinalLineInParagraph = ( i == document.
size() - 1 )
1613 context.
painter()->translate( component.origin );
1615 context.
painter()->rotate( rotation );
1620 maskPainter->save();
1621 maskPainter->translate( component.origin );
1623 maskPainter->rotate( rotation );
1627 double xMultiLineOffset = 0.0;
1628 double labelWidth =
fontMetrics->horizontalAdvance( line ) / fontScale;
1629 double extraWordSpace = 0;
1630 double extraLetterSpace = 0;
1631 if ( adjustForAlignment )
1633 double labelWidthDiff = 0;
1634 switch ( hAlignment )
1637 labelWidthDiff = ( labelWidest - labelWidth ) * 0.5;
1641 labelWidthDiff = labelWidest - labelWidth;
1645 if ( !isFinalLineInParagraph && labelWidest > labelWidth )
1647 calculateExtraSpacingForLineJustification( labelWidest - labelWidth, block, extraWordSpace, extraLetterSpace );
1648 labelWidth = labelWidest;
1660 xMultiLineOffset = labelWidthDiff;
1665 switch ( hAlignment )
1668 xMultiLineOffset = labelWidthDiff - labelWidest;
1672 xMultiLineOffset = labelWidthDiff - labelWidest / 2.0;
1684 double yMultiLineOffset = ascentOffset;
1691 yMultiLineOffset = - ascentOffset - ( textLines.size() - 1 - i ) * labelHeight * format.
lineHeight();
1696 yMultiLineOffset = - ascentOffset + labelHeight - 1 + format.
lineHeight() *
fontMetrics->lineSpacing() * i / fontScale;
1701 yMultiLineOffset = 0 - ( textLines.size() - 1 - i ) *
fontMetrics->lineSpacing() * format.
lineHeight() / fontScale;
1706 context.
painter()->translate( QPointF( xMultiLineOffset, yMultiLineOffset ) );
1708 maskPainter->translate( QPointF( xMultiLineOffset, yMultiLineOffset ) );
1710 Component subComponent;
1711 subComponent.block = block;
1712 subComponent.
size = QSizeF( labelWidth, labelHeight );
1713 subComponent.offset = QPointF( 0.0, -ascentOffset );
1714 subComponent.rotation = -component.rotation * 180 / M_PI;
1715 subComponent.rotationOffset = 0.0;
1716 subComponent.extraWordSpacing = extraWordSpace * fontScale;
1717 subComponent.extraLetterSpacing = extraLetterSpace * fontScale;
1722 QgsTextRenderer::drawMask( context, subComponent, format, mode );
1727 QgsTextRenderer::drawBuffer( context, subComponent, format, mode );
1734 textp.begin( &textPict );
1735 textp.setPen( Qt::NoPen );
1737 std::optional< QgsScopedRenderContextReferenceScaleOverride > referenceScaleOverride;
1738 if ( mode ==
Label )
1744 bool isNullSize =
false;
1745 const QFont font = format.
scaledFont( context, fontScale, &isNullSize );
1746 referenceScaleOverride.reset();
1750 textp.scale( 1 / fontScale, 1 / fontScale );
1757 path.setFillRule( Qt::WindingFill );
1759 QFont fragmentFont = font;
1762 if ( extraWordSpace || extraLetterSpace )
1763 applyExtraSpacingForLineJustification( fragmentFont, extraWordSpace * fontScale, extraLetterSpace * fontScale );
1765 path.addText( xOffset, 0, fragmentFont, fragment.
text() );
1769 textp.setBrush( textColor );
1770 textp.drawPath( path );
1779 subComponent.picture = textPict;
1780 subComponent.pictureBuffer = 0.0;
1781 subComponent.origin = QPointF( 0.0, 0.0 );
1783 QgsTextRenderer::drawShadow( context, subComponent, format );
1793 context.
painter()->scale( subComponent.dpiRatio, subComponent.dpiRatio );
1797 case Qgis::TextRenderFormat::AlwaysOutlines:
1800 _fixQPictureDPI( context.
painter() );
1801 context.
painter()->drawPicture( 0, 0, textPict );
1805 case Qgis::TextRenderFormat::AlwaysText:
1810 QFont fragmentFont = font;
1813 if ( extraWordSpace || extraLetterSpace )
1814 applyExtraSpacingForLineJustification( fragmentFont, extraWordSpace * fontScale, extraLetterSpace * fontScale );
1819 context.
painter()->setPen( textColor );
1820 context.
painter()->setFont( fragmentFont );
1821 context.
painter()->setRenderHint( QPainter::TextAntialiasing );
1823 context.
painter()->scale( 1 / fontScale, 1 / fontScale );
1824 context.
painter()->drawText( xOffset, 0, fragment.
text() );
1825 context.
painter()->scale( fontScale, fontScale );
1833 maskPainter->restore();
1838 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 )
1841 const QStringList textLines = document.
toPlainText();
1843 std::optional< QgsScopedRenderContextReferenceScaleOverride > referenceScaleOverride;
1844 if ( mode ==
Label )
1851 bool isNullSize =
false;
1852 const QFont font = format.
scaledFont( context, fontScale, &isNullSize );
1856 referenceScaleOverride.reset();
1858 double letterSpacing = font.letterSpacing() / fontScale;
1860 double labelWidth =
fontMetrics->maxWidth() / fontScale;
1861 double actualLabelWidest = labelWidth + ( textLines.size() - 1 ) * labelWidth * format.
lineHeight();
1862 double labelWidest = 0.0;
1867 labelWidest = actualLabelWidest;
1871 labelWidest = component.size.width();
1875 int maxLineLength = 0;
1876 for (
const QString &line : std::as_const( textLines ) )
1878 maxLineLength = std::max( maxLineLength,
static_cast<int>( line.length() ) );
1880 double actualLabelHeight =
fontMetrics->ascent() / fontScale + (
fontMetrics->ascent() / fontScale + letterSpacing ) * ( maxLineLength - 1 );
1881 double ascentOffset =
fontMetrics->ascent() / fontScale;
1885 bool adjustForAlignment = hAlignment !=
AlignLeft && ( mode !=
Label || textLines.size() > 1 );
1892 context.
painter()->translate( component.origin );
1894 context.
painter()->rotate( rotation );
1899 maskPainter->save();
1900 maskPainter->translate( component.origin );
1902 maskPainter->rotate( rotation );
1906 double xOffset = actualLabelWidest - labelWidth - ( i * labelWidth * format.
lineHeight() );
1907 if ( adjustForAlignment )
1909 double labelWidthDiff = 0;
1910 switch ( hAlignment )
1913 labelWidthDiff = ( labelWidest - actualLabelWidest ) * 0.5;
1917 labelWidthDiff = labelWidest - actualLabelWidest;
1929 xOffset += labelWidthDiff;
1937 double yOffset = 0.0;
1943 if ( rotation >= -405 && rotation < -180 )
1945 yOffset = ascentOffset;
1947 else if ( rotation >= 0 && rotation < 45 )
1949 xOffset -= actualLabelWidest;
1950 yOffset = -actualLabelHeight + ascentOffset +
fontMetrics->descent() / fontScale;
1955 yOffset = -actualLabelHeight + ascentOffset;
1960 yOffset = -actualLabelHeight + ascentOffset;
1964 yOffset = ascentOffset;
1968 context.
painter()->translate( QPointF( xOffset, yOffset ) );
1970 double fragmentYOffset = 0;
1976 QFont fragmentFont( font );
1979 QFontMetricsF fragmentMetrics( fragmentFont );
1981 double labelHeight = fragmentMetrics.ascent() / fontScale + ( fragmentMetrics.ascent() / fontScale + letterSpacing ) * ( line.length() - 1 );
1983 Component subComponent;
1985 subComponent.size = QSizeF( labelWidth, labelHeight );
1986 subComponent.offset = QPointF( 0.0, fragmentYOffset );
1987 subComponent.rotation = -component.rotation * 180 / M_PI;
1988 subComponent.rotationOffset = 0.0;
1995 QgsTextRenderer::drawMask( context, subComponent, format );
2001 fragmentYOffset += QgsTextRenderer::drawBuffer( context, subComponent, format, mode );
2007 path.setFillRule( Qt::WindingFill );
2009 double partYOffset = 0.0;
2010 for (
const auto &part : parts )
2012 double partXOffset = ( labelWidth - ( fragmentMetrics.horizontalAdvance( part ) / fontScale - letterSpacing ) ) / 2;
2013 path.addText( partXOffset * fontScale, partYOffset * fontScale, fragmentFont, part );
2014 partYOffset += fragmentMetrics.ascent() / fontScale + letterSpacing;
2020 textp.begin( &textPict );
2021 textp.setPen( Qt::NoPen );
2024 textp.setBrush( textColor );
2025 textp.scale( 1 / fontScale, 1 / fontScale );
2026 textp.drawPath( path );
2036 subComponent.picture = textPict;
2037 subComponent.pictureBuffer = 0.0;
2038 subComponent.origin = QPointF( 0.0, fragmentYOffset );
2039 const double prevY = subComponent.offset.y();
2040 subComponent.offset = QPointF( 0, -labelHeight );
2041 subComponent.useOrigin =
true;
2042 QgsTextRenderer::drawShadow( context, subComponent, format );
2043 subComponent.useOrigin =
false;
2044 subComponent.offset = QPointF( 0, prevY );
2054 context.
painter()->scale( subComponent.dpiRatio, subComponent.dpiRatio );
2058 case Qgis::TextRenderFormat::AlwaysOutlines:
2061 _fixQPictureDPI( context.
painter() );
2062 context.
painter()->drawPicture( 0, fragmentYOffset, textPict );
2063 fragmentYOffset += partYOffset;
2067 case Qgis::TextRenderFormat::AlwaysText:
2069 context.
painter()->setFont( fragmentFont );
2070 context.
painter()->setPen( textColor );
2071 context.
painter()->setRenderHint( QPainter::TextAntialiasing );
2073 double partYOffset = 0.0;
2074 for (
const QString &part : parts )
2076 double partXOffset = ( labelWidth - ( fragmentMetrics.horizontalAdvance( part ) / fontScale - letterSpacing ) ) / 2;
2077 context.
painter()->scale( 1 / fontScale, 1 / fontScale );
2078 context.
painter()->drawText( partXOffset * fontScale, ( fragmentYOffset + partYOffset ) * fontScale, part );
2079 context.
painter()->scale( fontScale, fontScale );
2080 partYOffset += fragmentMetrics.ascent() / fontScale + letterSpacing;
2082 fragmentYOffset += partYOffset;
2089 maskPainter->restore();
2104 if ( pixelSize < 50 )
2108 else if ( pixelSize > 200 )
2109 return 200 / pixelSize;