diff --git a/python/PyQt6/core/auto_additions/qgis.py b/python/PyQt6/core/auto_additions/qgis.py index 9f09110d5044..1e306c7097b7 100644 --- a/python/PyQt6/core/auto_additions/qgis.py +++ b/python/PyQt6/core/auto_additions/qgis.py @@ -5079,6 +5079,9 @@ """ # -- Qgis.TextComponent.baseClass = Qgis +Qgis.TextComponents = lambda flags=0: Qgis.TextComponent(flags) +Qgis.TextComponents.baseClass = Qgis +TextComponents = Qgis # dirty hack since SIP seems to introduce the flags in module QgsTextRenderer.HAlignment = Qgis.TextHorizontalAlignment # monkey patching scoped based enum QgsTextRenderer.AlignLeft = Qgis.TextHorizontalAlignment.Left diff --git a/python/PyQt6/core/auto_generated/qgis.sip.in b/python/PyQt6/core/auto_generated/qgis.sip.in index de6703eaf11d..c5a53a3fc722 100644 --- a/python/PyQt6/core/auto_generated/qgis.sip.in +++ b/python/PyQt6/core/auto_generated/qgis.sip.in @@ -1591,14 +1591,17 @@ The development version RectangleAscentBased, }; - enum class TextComponent /BaseType=IntEnum/ - { + enum class TextComponent /BaseType=IntFlag/ + { Text, Buffer, Background, Shadow, }; + typedef QFlags TextComponents; + + enum class TextHorizontalAlignment /BaseType=IntEnum/ { Left, @@ -3333,6 +3336,8 @@ QFlags operator|(Qgis::SymbolPreviewFlag f1, QFlags operator|(Qgis::SymbolRenderHint f1, QFlags f2); +QFlags operator|(Qgis::TextComponent f1, QFlags f2); + QFlags operator|(Qgis::TextRendererFlag f1, QFlags f2); QFlags operator|(Qgis::TiledSceneProviderCapability f1, QFlags f2); diff --git a/python/core/auto_additions/qgis.py b/python/core/auto_additions/qgis.py index 6a2d3f4d36a2..c3c71e5ec66e 100644 --- a/python/core/auto_additions/qgis.py +++ b/python/core/auto_additions/qgis.py @@ -5028,6 +5028,8 @@ """ # -- Qgis.TextComponent.baseClass = Qgis +Qgis.TextComponents.baseClass = Qgis +TextComponents = Qgis # dirty hack since SIP seems to introduce the flags in module QgsTextRenderer.HAlignment = Qgis.TextHorizontalAlignment # monkey patching scoped based enum QgsTextRenderer.AlignLeft = Qgis.TextHorizontalAlignment.Left diff --git a/python/core/auto_generated/qgis.sip.in b/python/core/auto_generated/qgis.sip.in index fc9c1c4223fd..d9f73aa862c8 100644 --- a/python/core/auto_generated/qgis.sip.in +++ b/python/core/auto_generated/qgis.sip.in @@ -1592,13 +1592,16 @@ The development version }; enum class TextComponent - { + { Text, Buffer, Background, Shadow, }; + typedef QFlags TextComponents; + + enum class TextHorizontalAlignment { Left, @@ -3333,6 +3336,8 @@ QFlags operator|(Qgis::SymbolPreviewFlag f1, QFlags operator|(Qgis::SymbolRenderHint f1, QFlags f2); +QFlags operator|(Qgis::TextComponent f1, QFlags f2); + QFlags operator|(Qgis::TextRendererFlag f1, QFlags f2); QFlags operator|(Qgis::TiledSceneProviderCapability f1, QFlags f2); diff --git a/src/core/labeling/qgsvectorlayerlabelprovider.cpp b/src/core/labeling/qgsvectorlayerlabelprovider.cpp index 9deb5586edad..a7d76d979c75 100644 --- a/src/core/labeling/qgsvectorlayerlabelprovider.cpp +++ b/src/core/labeling/qgsvectorlayerlabelprovider.cpp @@ -497,6 +497,23 @@ void QgsVectorLayerLabelProvider::drawLabelPrivate( pal::LabelPosition *label, Q // NOTE: this is repeatedly called for multi-part labels QPainter *painter = context.painter(); + Qgis::TextComponents components; + switch ( drawType ) + { + case Qgis::TextComponent::Text: + components = Qgis::TextComponent::Text | Qgis::TextComponent::Shadow; + break; + + case Qgis::TextComponent::Buffer: + components = Qgis::TextComponent::Buffer | Qgis::TextComponent::Shadow; + break; + + case Qgis::TextComponent::Background: + case Qgis::TextComponent::Shadow: + components = drawType; + break; + } + // features are pre-rotated but not scaled/translated, // so we only disable rotation here. Ideally, they'd be // also pre-scaled/translated, as suggested here: @@ -789,7 +806,7 @@ void QgsVectorLayerLabelProvider::drawLabelPrivate( pal::LabelPosition *label, Q QgsScopedRenderContextReferenceScaleOverride referenceScaleOverride( context, -1.0 ); const QgsTextDocumentMetrics metrics = QgsTextDocumentMetrics::calculateMetrics( document, tmpLyr.format(), context ); - QgsTextRenderer::drawTextInternal( drawType, context, tmpLyr.format(), component, document, + QgsTextRenderer::drawTextInternal( components, context, tmpLyr.format(), component, document, metrics, hAlign, Qgis::TextVerticalAlignment::Top, Qgis::TextLayoutMode::Labeling ); break; } @@ -806,7 +823,7 @@ void QgsVectorLayerLabelProvider::drawLabelPrivate( pal::LabelPosition *label, Q component.origin.ry() += verticalAlignOffset; - QgsTextRenderer::drawTextInternal( drawType, context, tmpLyr.format(), component, *document, + QgsTextRenderer::drawTextInternal( components, context, tmpLyr.format(), component, *document, *documentMetrics, hAlign, Qgis::TextVerticalAlignment::Top, Qgis::TextLayoutMode::Labeling ); break; } diff --git a/src/core/qgis.h b/src/core/qgis.h index 70f7e59b7905..17e4e4e5067a 100644 --- a/src/core/qgis.h +++ b/src/core/qgis.h @@ -2712,15 +2712,23 @@ class CORE_EXPORT Qgis * * \since QGIS 3.28 */ - enum class TextComponent SIP_MONKEYPATCH_SCOPEENUM_UNNEST( QgsTextRenderer, TextPart ) : int - { - Text, //!< Text component - Buffer, //!< Buffer component - Background, //!< Background shape - Shadow, //!< Drop shadow + enum class TextComponent SIP_MONKEYPATCH_SCOPEENUM_UNNEST( QgsTextRenderer, TextPart ) : int SIP_ENUM_BASETYPE( IntFlag ) + { + Text = 1 << 0, //!< Text component + Buffer = 1 << 1, //!< Buffer component + Background = 1 << 2, //!< Background shape + Shadow = 1 << 3, //!< Drop shadow }; Q_ENUM( TextComponent ) + /** + * Text components. + * + * \since QGIS 3.42 + */ + Q_DECLARE_FLAGS( TextComponents, TextComponent ) + Q_FLAG( TextComponents ) + /** * Text horizontal alignment. * @@ -5735,6 +5743,7 @@ Q_DECLARE_OPERATORS_FOR_FLAGS( Qgis::SymbolLayerFlags ) Q_DECLARE_OPERATORS_FOR_FLAGS( Qgis::SymbolLayerUserFlags ) Q_DECLARE_OPERATORS_FOR_FLAGS( Qgis::SymbolPreviewFlags ) Q_DECLARE_OPERATORS_FOR_FLAGS( Qgis::SymbolRenderHints ) +Q_DECLARE_OPERATORS_FOR_FLAGS( Qgis::TextComponents ) Q_DECLARE_OPERATORS_FOR_FLAGS( Qgis::TextRendererFlags ) Q_DECLARE_OPERATORS_FOR_FLAGS( Qgis::TiledSceneProviderCapabilities ) Q_DECLARE_OPERATORS_FOR_FLAGS( Qgis::TiledSceneRendererFlags ) diff --git a/src/core/textrenderer/qgstextrenderer.cpp b/src/core/textrenderer/qgstextrenderer.cpp index e8b71284c3ce..4616795bb1f6 100644 --- a/src/core/textrenderer/qgstextrenderer.cpp +++ b/src/core/textrenderer/qgstextrenderer.cpp @@ -96,17 +96,23 @@ void QgsTextRenderer::drawDocument( const QRectF &rect, const QgsTextFormat &for { const QgsTextFormat tmpFormat = updateShadowPosition( format ); + Qgis::TextComponents components = Qgis::TextComponent::Text; if ( tmpFormat.background().enabled() ) { - drawPart( rect, rotation, horizontalAlignment, verticalAlignment, document, metrics, context, tmpFormat, Qgis::TextComponent::Background, mode ); + components |= Qgis::TextComponent::Background; + } + + if ( tmpFormat.shadow().enabled() ) + { + components |= Qgis::TextComponent::Shadow; } if ( tmpFormat.buffer().enabled() ) { - drawPart( rect, rotation, horizontalAlignment, verticalAlignment, document, metrics, context, tmpFormat, Qgis::TextComponent::Buffer, mode ); + components |= Qgis::TextComponent::Buffer; } - drawPart( rect, rotation, horizontalAlignment, verticalAlignment, document, metrics, context, tmpFormat, Qgis::TextComponent::Text, mode ); + drawParts( rect, rotation, horizontalAlignment, verticalAlignment, document, metrics, context, tmpFormat, components, mode ); } void QgsTextRenderer::drawText( QPointF point, double rotation, Qgis::TextHorizontalAlignment alignment, const QStringList &textLines, QgsRenderContext &context, const QgsTextFormat &_format, bool ) @@ -126,17 +132,23 @@ void QgsTextRenderer::drawText( QPointF point, double rotation, Qgis::TextHorizo void QgsTextRenderer::drawDocument( QPointF point, const QgsTextFormat &format, const QgsTextDocument &document, const QgsTextDocumentMetrics &metrics, QgsRenderContext &context, Qgis::TextHorizontalAlignment alignment, double rotation ) { + Qgis::TextComponents components = Qgis::TextComponent::Text; if ( format.background().enabled() ) { - drawPart( point, rotation, alignment, document, metrics, context, format, Qgis::TextComponent::Background, Qgis::TextLayoutMode::Point ); + components |= Qgis::TextComponent::Background; + } + + if ( format.shadow().enabled() ) + { + components |= Qgis::TextComponent::Shadow; } if ( format.buffer().enabled() ) { - drawPart( point, rotation, alignment, document, metrics, context, format, Qgis::TextComponent::Buffer, Qgis::TextLayoutMode::Point ); + components |= Qgis::TextComponent::Buffer; } - drawPart( point, rotation, alignment, document, metrics, context, format, Qgis::TextComponent::Text, Qgis::TextLayoutMode::Point ); + drawParts( point, rotation, alignment, document, metrics, context, format, components, Qgis::TextLayoutMode::Point ); } void QgsTextRenderer::drawTextOnLine( const QPolygonF &line, const QString &text, QgsRenderContext &context, const QgsTextFormat &_format, double offsetAlongLine, double offsetFromLine ) @@ -401,10 +413,10 @@ void QgsTextRenderer::drawPart( const QRectF &rect, double rotation, Qgis::TextH const double fontScale = calculateScaleFactorForFormat( context, format ); const QgsTextDocumentMetrics metrics = QgsTextDocumentMetrics::calculateMetrics( document, format, context, fontScale ); - drawPart( rect, rotation, alignment, Qgis::TextVerticalAlignment::Top, metrics.document(), metrics, context, format, part, Qgis::TextLayoutMode::Rectangle ); + drawParts( rect, rotation, alignment, Qgis::TextVerticalAlignment::Top, metrics.document(), metrics, context, format, part, Qgis::TextLayoutMode::Rectangle ); } -void QgsTextRenderer::drawPart( const QRectF &rect, double rotation, Qgis::TextHorizontalAlignment alignment, Qgis::TextVerticalAlignment vAlignment, const QgsTextDocument &document, const QgsTextDocumentMetrics &metrics, QgsRenderContext &context, const QgsTextFormat &format, Qgis::TextComponent part, Qgis::TextLayoutMode mode ) +void QgsTextRenderer::drawParts( const QRectF &rect, double rotation, Qgis::TextHorizontalAlignment alignment, Qgis::TextVerticalAlignment vAlignment, const QgsTextDocument &document, const QgsTextDocumentMetrics &metrics, QgsRenderContext &context, const QgsTextFormat &format, Qgis::TextComponents parts, Qgis::TextLayoutMode mode ) { if ( !context.painter() ) { @@ -418,62 +430,51 @@ void QgsTextRenderer::drawPart( const QRectF &rect, double rotation, Qgis::TextH component.size = rect.size(); component.hAlign = alignment; - switch ( part ) + if ( ( parts & Qgis::TextComponent::Background ) && format.background().enabled() ) { - case Qgis::TextComponent::Background: + if ( !qgsDoubleNear( rotation, 0.0 ) ) { - if ( !format.background().enabled() ) - return; - - if ( !qgsDoubleNear( rotation, 0.0 ) ) - { - // get rotated label's center point - - double xc = rect.width() / 2.0; - double yc = rect.height() / 2.0; + // get rotated label's center point - double angle = -rotation; - double xd = xc * std::cos( angle ) - yc * std::sin( angle ); - double yd = xc * std::sin( angle ) + yc * std::cos( angle ); + double xc = rect.width() / 2.0; + double yc = rect.height() / 2.0; - component.center = QPointF( component.origin.x() + xd, component.origin.y() + yd ); - } - else - { - component.center = rect.center(); - } + double angle = -rotation; + double xd = xc * std::cos( angle ) - yc * std::sin( angle ); + double yd = xc * std::sin( angle ) + yc * std::cos( angle ); - switch ( vAlignment ) - { - case Qgis::TextVerticalAlignment::Top: - break; - case Qgis::TextVerticalAlignment::VerticalCenter: - component.origin.ry() += ( rect.height() - metrics.documentSize( mode, format.orientation() ).height() ) / 2; - break; - case Qgis::TextVerticalAlignment::Bottom: - component.origin.ry() += ( rect.height() - metrics.documentSize( mode, format.orientation() ).height() ); - break; - } - - QgsTextRenderer::drawBackground( context, component, format, metrics, Qgis::TextLayoutMode::Rectangle ); - - break; + component.center = QPointF( component.origin.x() + xd, component.origin.y() + yd ); } - - case Qgis::TextComponent::Buffer: + else { - if ( !format.buffer().enabled() ) - break; + component.center = rect.center(); } - [[fallthrough]]; - case Qgis::TextComponent::Text: - case Qgis::TextComponent::Shadow: + + switch ( vAlignment ) { - drawTextInternal( part, context, format, component, - document, metrics, - alignment, vAlignment, mode ); - break; + case Qgis::TextVerticalAlignment::Top: + break; + case Qgis::TextVerticalAlignment::VerticalCenter: + component.origin.ry() += ( rect.height() - metrics.documentSize( mode, format.orientation() ).height() ) / 2; + break; + case Qgis::TextVerticalAlignment::Bottom: + component.origin.ry() += ( rect.height() - metrics.documentSize( mode, format.orientation() ).height() ); + break; } + + QgsTextRenderer::drawBackground( context, component, format, metrics, Qgis::TextLayoutMode::Rectangle ); + } + + if ( parts == Qgis::TextComponents( Qgis::TextComponent::Buffer ) && !format.buffer().enabled() ) + { + return; + } + + if ( parts & Qgis::TextComponent::Buffer || parts & Qgis::TextComponent::Text || parts & Qgis::TextComponent::Shadow ) + { + drawTextInternal( parts, context, format, component, + document, metrics, + alignment, vAlignment, mode ); } } @@ -483,10 +484,10 @@ void QgsTextRenderer::drawPart( QPointF origin, double rotation, Qgis::TextHoriz const double fontScale = calculateScaleFactorForFormat( context, format ); const QgsTextDocumentMetrics metrics = QgsTextDocumentMetrics::calculateMetrics( document, format, context, fontScale ); - drawPart( origin, rotation, alignment, metrics.document(), metrics, context, format, part, Qgis::TextLayoutMode::Point ); + drawParts( origin, rotation, alignment, metrics.document(), metrics, context, format, part, Qgis::TextLayoutMode::Point ); } -void QgsTextRenderer::drawPart( QPointF origin, double rotation, Qgis::TextHorizontalAlignment alignment, const QgsTextDocument &document, const QgsTextDocumentMetrics &metrics, QgsRenderContext &context, const QgsTextFormat &format, Qgis::TextComponent part, Qgis::TextLayoutMode mode ) +void QgsTextRenderer::drawParts( QPointF origin, double rotation, Qgis::TextHorizontalAlignment alignment, const QgsTextDocument &document, const QgsTextDocumentMetrics &metrics, QgsRenderContext &context, const QgsTextFormat &format, Qgis::TextComponents parts, Qgis::TextLayoutMode mode ) { if ( !context.painter() ) { @@ -499,33 +500,23 @@ void QgsTextRenderer::drawPart( QPointF origin, double rotation, Qgis::TextHoriz component.rotation = rotation; component.hAlign = alignment; - switch ( part ) + if ( ( parts & Qgis::TextComponent::Background ) && format.background().enabled() ) { - case Qgis::TextComponent::Background: - { - if ( !format.background().enabled() ) - return; + QgsTextRenderer::drawBackground( context, component, format, metrics, mode ); + } - QgsTextRenderer::drawBackground( context, component, format, metrics, mode ); - break; - } + if ( parts == Qgis::TextComponents( Qgis::TextComponent::Buffer ) && !format.buffer().enabled() ) + { + return; + } - case Qgis::TextComponent::Buffer: - { - if ( !format.buffer().enabled() ) - break; - } - [[fallthrough]]; - case Qgis::TextComponent::Text: - case Qgis::TextComponent::Shadow: - { - drawTextInternal( part, context, format, component, - document, - metrics, - alignment, Qgis::TextVerticalAlignment::Top, - mode ); - break; - } + if ( parts & Qgis::TextComponent::Buffer || parts & Qgis::TextComponent::Text || parts & Qgis::TextComponent::Shadow ) + { + drawTextInternal( parts, context, format, component, + document, + metrics, + alignment, Qgis::TextVerticalAlignment::Top, + mode ); } } @@ -586,26 +577,7 @@ double QgsTextRenderer::drawBuffer( QgsRenderContext &context, const QgsTextRend { case Qgis::TextOrientation::Horizontal: { - double xOffset = 0; - int fragmentIndex = 0; - for ( const QgsTextFragment &fragment : component.block ) - { - QFont fragmentFont = metrics.fragmentFont( component.blockIndex, fragmentIndex ); - - if ( !fragment.isWhitespace() && !fragment.isImage() ) - { - if ( component.extraWordSpacing || component.extraLetterSpacing ) - applyExtraSpacingForLineJustification( fragmentFont, component.extraWordSpacing, component.extraLetterSpacing ); - - const double yOffset = metrics.fragmentVerticalOffset( component.blockIndex, fragmentIndex, mode ); - path.addText( xOffset, yOffset, fragmentFont, fragment.text() ); - } - - xOffset += metrics.fragmentHorizontalAdvance( component.blockIndex, fragmentIndex, mode ) * scaleFactor; - - fragmentIndex++; - } - advance = xOffset; + // NOT SUPPORTED BY THIS METHOD ANYMORE -- buffer drawing is handled in drawTextInternalHorizontal since QGIS 3.42 break; } @@ -1393,14 +1365,11 @@ void QgsTextRenderer::drawShadow( QgsRenderContext &context, const QgsTextRender { QgsTextShadowSettings shadow = format.shadow(); - // incoming component sizes should be multiplied by rasterCompressFactor, as - // this allows shadows to be created at paint device dpi (e.g. high resolution), - // then scale device painter by 1.0 / rasterCompressFactor for output - QPainter *p = context.painter(); const double componentWidth = component.size.width(); const double componentHeight = component.size.height(); - double xOffset = component.offset.x(), yOffset = component.offset.y(); + const double xOffset = component.offset.x(); + const double yOffset = component.offset.y(); double pictbuffer = component.pictureBuffer; // generate pixmap representation of label component drawing @@ -1546,7 +1515,7 @@ void QgsTextRenderer::drawShadow( QgsRenderContext &context, const QgsTextRender } -void QgsTextRenderer::drawTextInternal( Qgis::TextComponent drawType, +void QgsTextRenderer::drawTextInternal( Qgis::TextComponents components, QgsRenderContext &context, const QgsTextFormat &format, const Component &component, @@ -1580,14 +1549,18 @@ void QgsTextRenderer::drawTextInternal( Qgis::TextComponent drawType, { case Qgis::TextOrientation::Horizontal: { - drawTextInternalHorizontal( context, format, drawType, mode, component, document, metrics, fontScale, alignment, vAlignment, rotation ); + drawTextInternalHorizontal( context, format, components, mode, component, document, metrics, fontScale, alignment, vAlignment, rotation ); break; } case Qgis::TextOrientation::Vertical: case Qgis::TextOrientation::RotationBased: { - drawTextInternalVertical( context, format, drawType, mode, component, document, metrics, fontScale, alignment, vAlignment, rotation ); + // TODO: vertical text renderer currently doesn't handle one-pass buffer + text drawing + if ( components & Qgis::TextComponent::Buffer ) + drawTextInternalVertical( context, format, Qgis::TextComponent::Buffer, mode, component, document, metrics, fontScale, alignment, vAlignment, rotation ); + if ( components & Qgis::TextComponent::Text ) + drawTextInternalVertical( context, format, Qgis::TextComponent::Text, mode, component, document, metrics, fontScale, alignment, vAlignment, rotation ); break; } } @@ -1672,9 +1645,9 @@ void QgsTextRenderer::applyExtraSpacingForLineJustification( QFont &font, double void QgsTextRenderer::renderBlockHorizontal( const QgsTextBlock &block, int blockIndex, const QgsTextDocumentMetrics &metrics, QgsRenderContext &context, const QgsTextFormat &format, - QPainter *painter, bool usePaths, + QPainter *painter, bool forceRenderAsPaths, double fontScale, double extraWordSpace, double extraLetterSpace, - Qgis::TextLayoutMode mode ) + Qgis::TextLayoutMode mode, DeferredRenderBlock *deferredRenderBlock ) { if ( !metrics.isNullFontSize() ) { @@ -1695,7 +1668,21 @@ void QgsTextRenderer::renderBlockHorizontal( const QgsTextBlock &block, int bloc QColor textColor = fragment.characterFormat().textColor().isValid() ? fragment.characterFormat().textColor() : format.color(); textColor.setAlphaF( fragment.characterFormat().textColor().isValid() ? textColor.alphaF() * format.opacity() : format.opacity() ); - if ( usePaths ) + if ( deferredRenderBlock ) + { + DeferredRenderFragment renderFragment; + renderFragment.color = textColor; + if ( forceRenderAsPaths ) + { + renderFragment.path.setFillRule( Qt::WindingFill ); + renderFragment.path.addText( xOffset, yOffset, fragmentFont, fragment.text() ); + } + renderFragment.font = fragmentFont; + renderFragment.point = QPointF( xOffset, yOffset ); + renderFragment.text = fragment.text(); + deferredRenderBlock->fragments.append( renderFragment ); + } + else if ( forceRenderAsPaths ) { painter->setBrush( textColor ); QPainterPath path; @@ -1774,7 +1761,19 @@ bool QgsTextRenderer::usePathsToRender( const QgsRenderContext &context, const Q } BUILTIN_UNREACHABLE } -void QgsTextRenderer::drawTextInternalHorizontal( QgsRenderContext &context, const QgsTextFormat &format, Qgis::TextComponent drawType, Qgis::TextLayoutMode mode, const Component &component, const QgsTextDocument &document, const QgsTextDocumentMetrics &metrics, double fontScale, const Qgis::TextHorizontalAlignment hAlignment, + +bool QgsTextRenderer::usePictureToRender( const QgsRenderContext &, const QgsTextFormat &, const QgsTextDocument &document ) +{ + return std::any_of( document.begin(), document.end(), []( const QgsTextBlock & block ) + { + return std::any_of( block.begin(), block.end(), []( const QgsTextFragment & fragment ) + { + return fragment.isImage(); + } ); + } ); +} + +void QgsTextRenderer::drawTextInternalHorizontal( QgsRenderContext &context, const QgsTextFormat &format, Qgis::TextComponents components, Qgis::TextLayoutMode mode, const Component &component, const QgsTextDocument &document, const QgsTextDocumentMetrics &metrics, double fontScale, const Qgis::TextHorizontalAlignment hAlignment, Qgis::TextVerticalAlignment vAlignment, double rotation ) { QPainter *maskPainter = context.maskPainter( context.currentMaskId() ); @@ -1782,18 +1781,18 @@ void QgsTextRenderer::drawTextInternalHorizontal( QgsRenderContext &context, con const QSizeF documentSize = metrics.documentSize( mode, Qgis::TextOrientation::Horizontal ); - double labelWidest = 0.0; + double targetWidth = 0.0; switch ( mode ) { case Qgis::TextLayoutMode::Labeling: case Qgis::TextLayoutMode::Point: - labelWidest = documentSize.width(); + targetWidth = documentSize.width(); break; case Qgis::TextLayoutMode::Rectangle: case Qgis::TextLayoutMode::RectangleCapHeightBased: case Qgis::TextLayoutMode::RectangleAscentBased: - labelWidest = component.size.width(); + targetWidth = component.size.width(); break; } @@ -1823,7 +1822,22 @@ void QgsTextRenderer::drawTextInternalHorizontal( QgsRenderContext &context, con } // should we use text or paths for this render? - const bool usePaths = usePathsToRender( context, format, document ); + const bool usePathsForText = usePathsToRender( context, format, document ); + + // TODO -- maybe we can avoid the nested vector? Need to confirm whether painter rotation & translation can be + // done ONCE only, upfront + std::unique_ptr< std::vector< DeferredRenderBlock > > deferredBlocks; + + // Depending on format settings, we may need to render in multiple passes. Eg buffer than text, or shadow than text. + // We try to avoid this if possible as it requires more work, and just do a single pass, rendering text directly as we go. + // If we need to do multi-pass rendering then we'll calculate paths ONCE upfront and defer actually renderring these. + const bool requiresMultiPassRendering = ( components & Qgis::TextComponent::Buffer && format.buffer().enabled() ) + || ( components & Qgis::TextComponent::Shadow && format.shadow().enabled() && ( format.shadow().shadowPlacement() == QgsTextShadowSettings::ShadowText || format.shadow().shadowPlacement() == QgsTextShadowSettings::ShadowBuffer ) ); + if ( requiresMultiPassRendering ) + { + deferredBlocks = std::make_unique< std::vector< DeferredRenderBlock > >(); + deferredBlocks->reserve( document.size() ); + } int blockIndex = 0; for ( const QgsTextBlock &block : document ) @@ -1840,6 +1854,14 @@ void QgsTextRenderer::drawTextInternalHorizontal( QgsRenderContext &context, con const double blockHeight = metrics.blockHeight( blockIndex ); + DeferredRenderBlock *deferredBlock = nullptr; + if ( requiresMultiPassRendering && deferredBlocks ) + { + deferredBlocks->emplace_back( DeferredRenderBlock() ); + deferredBlock = &deferredBlocks->back(); + deferredBlock->fragments.reserve( block.size() ); + } + QgsScopedQPainterState painterState( context.painter() ); context.setPainterFlagsUsingContext(); context.painter()->translate( component.origin ); @@ -1866,18 +1888,18 @@ void QgsTextRenderer::drawTextInternalHorizontal( QgsRenderContext &context, con switch ( blockAlignment ) { case Qgis::TextHorizontalAlignment::Center: - labelWidthDiff = ( labelWidest - blockWidth - metrics.blockLeftMargin( blockIndex ) - metrics.blockRightMargin( blockIndex ) ) * 0.5 + metrics.blockLeftMargin( blockIndex ); + labelWidthDiff = ( targetWidth - blockWidth - metrics.blockLeftMargin( blockIndex ) - metrics.blockRightMargin( blockIndex ) ) * 0.5 + metrics.blockLeftMargin( blockIndex ); break; case Qgis::TextHorizontalAlignment::Right: - labelWidthDiff = labelWidest - blockWidth - metrics.blockRightMargin( blockIndex ); + labelWidthDiff = targetWidth - blockWidth - metrics.blockRightMargin( blockIndex ); break; case Qgis::TextHorizontalAlignment::Justify: - if ( !isFinalLineInParagraph && labelWidest > blockWidth ) + if ( !isFinalLineInParagraph && targetWidth > blockWidth ) { - calculateExtraSpacingForLineJustification( labelWidest - blockWidth, block, extraWordSpace, extraLetterSpace ); - blockWidth = labelWidest; + calculateExtraSpacingForLineJustification( targetWidth - blockWidth, block, extraWordSpace, extraLetterSpace ); + blockWidth = targetWidth; } labelWidthDiff = metrics.blockLeftMargin( blockIndex ); break; @@ -1901,11 +1923,11 @@ void QgsTextRenderer::drawTextInternalHorizontal( QgsRenderContext &context, con switch ( blockAlignment ) { case Qgis::TextHorizontalAlignment::Right: - xMultiLineOffset = labelWidthDiff - labelWidest; + xMultiLineOffset = labelWidthDiff - targetWidth; break; case Qgis::TextHorizontalAlignment::Center: - xMultiLineOffset = labelWidthDiff - labelWidest / 2.0; + xMultiLineOffset = labelWidthDiff - targetWidth / 2.0; break; case Qgis::TextHorizontalAlignment::Left: @@ -1924,9 +1946,13 @@ void QgsTextRenderer::drawTextInternalHorizontal( QgsRenderContext &context, con const double baseLineOffset = metrics.baselineOffset( blockIndex, mode ); - context.painter()->translate( QPointF( xMultiLineOffset, baseLineOffset + verticalAlignOffset ) ); + const QPointF blockOrigin( xMultiLineOffset, baseLineOffset + verticalAlignOffset ); + if ( deferredBlock ) + deferredBlock->origin = blockOrigin; + else + context.painter()->translate( blockOrigin ); if ( maskPainter ) - maskPainter->translate( QPointF( xMultiLineOffset, baseLineOffset + verticalAlignOffset ) ); + maskPainter->translate( blockOrigin ); Component subComponent; subComponent.block = block; @@ -1937,6 +1963,8 @@ void QgsTextRenderer::drawTextInternalHorizontal( QgsRenderContext &context, con subComponent.rotationOffset = 0.0; subComponent.extraWordSpacing = extraWordSpace * fontScale; subComponent.extraLetterSpacing = extraLetterSpace * fontScale; + if ( deferredBlock ) + deferredBlock->component = subComponent; // draw the mask below the text (for preview) if ( format.mask().enabled() ) @@ -1944,17 +1972,14 @@ void QgsTextRenderer::drawTextInternalHorizontal( QgsRenderContext &context, con QgsTextRenderer::drawMask( context, subComponent, format, metrics, mode ); } - if ( drawType == Qgis::TextComponent::Buffer ) - { - QgsTextRenderer::drawBuffer( context, subComponent, format, metrics, mode ); - } - else + if ( ( components & Qgis::TextComponent::Buffer ) + || ( components & Qgis::TextComponent::Text ) + || ( components & Qgis::TextComponent::Shadow ) ) { - // store text's drawing in QPicture for drop shadow call - - const bool drawShadowOnText = format.shadow().enabled() && format.shadow().shadowPlacement() == QgsTextShadowSettings::ShadowText; - // do we need to store text temporarily in a QPicture? Avoid if we can... - const bool requiresPicture = drawShadowOnText; + // if we are drawing both text + buffer, we'll need a path, as we HAVE to render buffers using paths + const bool needsPaths = usePathsForText + || ( ( components & Qgis::TextComponent::Buffer ) && format.buffer().enabled() ) + || ( ( components & Qgis::TextComponent::Shadow ) && format.shadow().enabled() ); std::optional< QgsScopedRenderContextReferenceScaleOverride > referenceScaleOverride; if ( mode == Qgis::TextLayoutMode::Labeling ) @@ -1966,29 +1991,6 @@ void QgsTextRenderer::drawTextInternalHorizontal( QgsRenderContext &context, con referenceScaleOverride.reset(); - std::unique_ptr< QPicture > textPicture; - if ( requiresPicture ) - { - // render to picture - textPicture = std::make_unique< QPicture >(); - QPainter picturePainter( textPicture.get() ); - picturePainter.setPen( Qt::NoPen ); - picturePainter.setBrush( Qt::NoBrush ); - picturePainter.scale( 1 / fontScale, 1 / fontScale ); - renderBlockHorizontal( block, blockIndex, metrics, context, format, &picturePainter, usePaths, - fontScale, extraWordSpace, extraLetterSpace, mode ); - picturePainter.end(); - } - - if ( drawShadowOnText ) - { - subComponent.picture = *textPicture; - subComponent.pictureBuffer = 0.0; // no pen width to deal with - subComponent.origin = QPointF( 0.0, 0.0 ); - - QgsTextRenderer::drawShadow( context, subComponent, format ); - } - // now render the actual text if ( context.useAdvancedEffects() ) { @@ -1998,28 +2000,257 @@ void QgsTextRenderer::drawTextInternalHorizontal( QgsRenderContext &context, con // scale for any print output or image saving @ specific dpi context.painter()->scale( subComponent.dpiRatio, subComponent.dpiRatio ); - if ( textPicture ) + context.painter()->scale( 1 / fontScale, 1 / fontScale ); + context.painter()->setPen( Qt::NoPen ); + context.painter()->setBrush( Qt::NoBrush ); + renderBlockHorizontal( block, blockIndex, metrics, context, format, context.painter(), needsPaths, + fontScale, extraWordSpace, extraLetterSpace, mode, deferredBlock ); + } + if ( maskPainter ) + maskPainter->restore(); + + blockIndex++; + } + + if ( deferredBlocks ) + { + renderDeferredBlocks( + context, format, components, *deferredBlocks, usePathsForText, fontScale, component, rotation + ); + } +} + +void QgsTextRenderer::renderDeferredBlocks( QgsRenderContext &context, + const QgsTextFormat &format, + Qgis::TextComponents components, + const std::vector< DeferredRenderBlock > &deferredBlocks, + bool usePathsForText, + double fontScale, + const Component &component, + double rotation ) +{ + if ( format.buffer().enabled() && ( components & Qgis::TextComponent::Buffer ) ) + { + renderDeferredBuffer( context, format, components, deferredBlocks, fontScale, component, rotation ); + } + + if ( ( components & Qgis::TextComponent::Shadow ) + && format.shadow().enabled() + && format.shadow().shadowPlacement() == QgsTextShadowSettings::ShadowText ) + { + renderDeferredShadowForText( context, format, deferredBlocks, fontScale, component, rotation ); + // TODO: there's an optimisation opportunity here -- if we are ALSO rendering the text component, + // we could move the actual text rendering into renderDeferredShadowForText and use the same + // QPicture as we used for the shadow. But we'd need to ensure that all the settings + // which control whether text is rendered as text or paths also also considered. + } + + if ( components & Qgis::TextComponent::Text ) + { + renderDeferredText( context, deferredBlocks, usePathsForText, fontScale, component, rotation ); + } +} + +void QgsTextRenderer::renderDeferredShadowForText( QgsRenderContext &context, + const QgsTextFormat &format, + const std::vector< DeferredRenderBlock > &deferredBlocks, + double fontScale, + const Component &component, + double rotation ) +{ + QgsScopedQPainterState painterState( context.painter() ); + context.setPainterFlagsUsingContext(); + context.painter()->translate( component.origin ); + if ( !qgsDoubleNear( rotation, 0.0 ) ) + context.painter()->rotate( rotation ); + + context.painter()->setPen( Qt::NoPen ); + context.painter()->setBrush( Qt::NoBrush ); + + for ( const DeferredRenderBlock &block : deferredBlocks ) + { + Component subComponent = block.component; + + QPainter painter( &subComponent.picture ); + painter.setPen( Qt::NoPen ); + painter.setBrush( Qt::NoBrush ); + painter.scale( 1 / fontScale, 1 / fontScale ); + + for ( const DeferredRenderFragment &fragment : std::as_const( block.fragments ) ) + { + if ( !fragment.path.isEmpty() ) { - QgsPainting::applyScaleFixForQPictureDpi( context.painter() ); - context.painter()->drawPicture( 0, 0, *textPicture ); + painter.setBrush( fragment.color ); + painter.drawPath( fragment.path ); } else { - context.painter()->scale( 1 / fontScale, 1 / fontScale ); - context.painter()->setPen( Qt::NoPen ); - context.painter()->setBrush( Qt::NoBrush ); - renderBlockHorizontal( block, blockIndex, metrics, context, format, context.painter(), usePaths, - fontScale, extraWordSpace, extraLetterSpace, mode ); + painter.setPen( fragment.color ); + painter.setFont( fragment.font ); + painter.drawText( fragment.point, fragment.text ); } } - if ( maskPainter ) - maskPainter->restore(); + painter.end(); - blockIndex++; + subComponent.pictureBuffer = 1.0; // no pen width to deal with, but we'll add 1 px for antialiasing + subComponent.origin = QPointF( 0.0, 0.0 ); + const QRectF pictureBoundingRect = subComponent.picture.boundingRect(); + subComponent.size = pictureBoundingRect.size(); + subComponent.offset = QPointF( -pictureBoundingRect.left(), -pictureBoundingRect.height() - pictureBoundingRect.top() ); + + context.painter()->translate( block.origin ); + drawShadow( context, subComponent, format ); + context.painter()->translate( -block.origin ); + } +} + +void QgsTextRenderer::renderDeferredBuffer( QgsRenderContext &context, + const QgsTextFormat &format, + Qgis::TextComponents components, + const std::vector< DeferredRenderBlock > &deferredBlocks, + double fontScale, + const Component &component, + double rotation ) +{ + QgsScopedQPainterState painterState( context.painter() ); + context.setPainterFlagsUsingContext(); + + // do we need a drop shadow effect on the buffer component? If so, we'll render the buffer to a QPicture first and then use this + // to generate the shadow, and then render the QPicture as the buffer on top. If not, avoid the unwanted expense of the temporary QPicture + // and render directly. + const bool needsShadowOnBuffer = ( ( components & Qgis::TextComponent::Shadow ) && format.shadow().enabled() && format.shadow().shadowPlacement() == QgsTextShadowSettings::ShadowBuffer ); + std::unique_ptr< QPicture > bufferPicture; + std::unique_ptr< QPainter > bufferPainter; + QPainter *prevPainter = context.painter(); + if ( needsShadowOnBuffer ) + { + bufferPicture = std::make_unique< QPicture >(); + bufferPainter = std::make_unique< QPainter >( bufferPicture.get() ); + context.setPainter( bufferPainter.get() ); + } + + std::unique_ptr< QgsPaintEffect > tmpEffect; + if ( format.buffer().paintEffect() && format.buffer().paintEffect()->enabled() ) + { + tmpEffect.reset( format.buffer().paintEffect()->clone() ); + tmpEffect->begin( context ); + } + + QColor bufferColor = format.buffer().color(); + bufferColor.setAlphaF( format.buffer().opacity() ); + QPen pen( bufferColor ); + const QgsTextBufferSettings &buffer = format.buffer(); + const double penSize = buffer.sizeUnit() == Qgis::RenderUnit::Percentage + ? context.convertToPainterUnits( format.size(), format.sizeUnit(), format.sizeMapUnitScale() ) * buffer.size() / 100 + : context.convertToPainterUnits( buffer.size(), buffer.sizeUnit(), buffer.sizeMapUnitScale() ); + pen.setWidthF( penSize * fontScale ); + pen.setJoinStyle( buffer.joinStyle() ); + context.painter()->setPen( pen ); + + // honor pref for whether to fill buffer interior + if ( !buffer.fillBufferInterior() ) + { + bufferColor.setAlpha( 0 ); + } + context.painter()->setBrush( bufferColor ); + + context.painter()->translate( component.origin ); + if ( !qgsDoubleNear( rotation, 0.0 ) ) + context.painter()->rotate( rotation ); + + if ( context.useAdvancedEffects() ) + { + context.painter()->setCompositionMode( format.buffer().blendMode() ); + } + + for ( const DeferredRenderBlock &block : deferredBlocks ) + { + context.painter()->translate( block.origin ); + context.painter()->scale( 1 / fontScale, 1 / fontScale ); + for ( const DeferredRenderFragment &fragment : std::as_const( block.fragments ) ) + { + context.painter()->drawPath( fragment.path ); + } + context.painter()->scale( fontScale, fontScale ); + context.painter()->translate( -block.origin ); + } + + if ( tmpEffect ) + { + tmpEffect->end( context ); + } + + if ( needsShadowOnBuffer && bufferPicture ) + { + bufferPainter->end(); + bufferPainter.reset(); + context.setPainter( prevPainter ); + + QgsTextRenderer::Component bufferComponent = component; + bufferComponent.origin = QPointF( 0.0, 0.0 ); + bufferComponent.picture = *bufferPicture; + bufferComponent.pictureBuffer = penSize / 2.0; + const QRectF bufferBoundingBox = bufferPicture->boundingRect(); + bufferComponent.size = bufferBoundingBox.size(); + bufferComponent.offset = QPointF( -bufferBoundingBox.left(), -bufferBoundingBox.height() - bufferBoundingBox.top() ); + + drawShadow( context, bufferComponent, format ); + + // also draw buffer + if ( context.useAdvancedEffects() ) + { + context.painter()->setCompositionMode( buffer.blendMode() ); + } + + // scale for any print output or image saving @ specific dpi + context.painter()->scale( component.dpiRatio, component.dpiRatio ); + QgsPainting::drawPicture( context.painter(), QPointF( 0, 0 ), *bufferPicture ); } } -void QgsTextRenderer::drawTextInternalVertical( QgsRenderContext &context, const QgsTextFormat &format, Qgis::TextComponent drawType, Qgis::TextLayoutMode mode, const QgsTextRenderer::Component &component, const QgsTextDocument &document, const QgsTextDocumentMetrics &metrics, double fontScale, Qgis::TextHorizontalAlignment hAlignment, Qgis::TextVerticalAlignment, double rotation ) +void QgsTextRenderer::renderDeferredText( QgsRenderContext &context, + const std::vector< DeferredRenderBlock > &deferredBlocks, + bool usePathsForText, + double fontScale, + const Component &component, + double rotation ) +{ + QgsScopedQPainterState painterState( context.painter() ); + context.setPainterFlagsUsingContext(); + context.painter()->translate( component.origin ); + if ( !qgsDoubleNear( rotation, 0.0 ) ) + context.painter()->rotate( rotation ); + + context.painter()->setPen( Qt::NoPen ); + context.painter()->setBrush( Qt::NoBrush ); + + // draw the text + for ( const DeferredRenderBlock &block : deferredBlocks ) + { + context.painter()->translate( block.origin ); + context.painter()->scale( 1 / fontScale, 1 / fontScale ); + + for ( const DeferredRenderFragment &fragment : std::as_const( block.fragments ) ) + { + if ( usePathsForText ) + { + context.painter()->setBrush( fragment.color ); + context.painter()->drawPath( fragment.path ); + } + else + { + context.painter()->setPen( fragment.color ); + context.painter()->setFont( fragment.font ); + context.painter()->drawText( fragment.point, fragment.text ); + } + } + + context.painter()->scale( fontScale, fontScale ); + context.painter()->translate( -block.origin ); + } +} + +void QgsTextRenderer::drawTextInternalVertical( QgsRenderContext &context, const QgsTextFormat &format, Qgis::TextComponents components, Qgis::TextLayoutMode mode, const QgsTextRenderer::Component &component, const QgsTextDocument &document, const QgsTextDocumentMetrics &metrics, double fontScale, Qgis::TextHorizontalAlignment hAlignment, Qgis::TextVerticalAlignment, double rotation ) { QPainter *maskPainter = context.maskPainter( context.currentMaskId() ); const QStringList textLines = document.toPlainText(); @@ -2189,11 +2420,11 @@ void QgsTextRenderer::drawTextInternalVertical( QgsRenderContext &context, const #endif } - if ( drawType == Qgis::TextComponent::Buffer ) + if ( components & Qgis::TextComponent::Buffer ) { currentBlockYOffset += QgsTextRenderer::drawBuffer( context, subComponent, format, metrics, mode ); } - else + if ( ( components & Qgis::TextComponent::Text ) || ( components & Qgis::TextComponent::Shadow ) ) { // draw text, QPainterPath method QPainterPath path; diff --git a/src/core/textrenderer/qgstextrenderer.h b/src/core/textrenderer/qgstextrenderer.h index a177f8790170..4e44901bed12 100644 --- a/src/core/textrenderer/qgstextrenderer.h +++ b/src/core/textrenderer/qgstextrenderer.h @@ -23,6 +23,7 @@ #include "qgis.h" #include +#include class QgsTextDocument; class QgsTextDocumentMetrics; @@ -396,7 +397,7 @@ class CORE_EXPORT QgsTextRenderer static double textHeight( const QgsRenderContext &context, const QgsTextFormat &format, const QgsTextDocument &document, Qgis::TextLayoutMode mode = Qgis::TextLayoutMode::Point ); /** - * Draws a single component of rendered text using the specified settings. + * Draws components of rendered text using the specified settings. * \param rect destination rectangle for text * \param rotation text rotation * \param alignment horizontal alignment @@ -405,19 +406,19 @@ class CORE_EXPORT QgsTextRenderer * \param metrics document metrics * \param context render context * \param format text format - * \param part component of text to draw. Note that Shadow parts cannot be drawn + * \param parts components of text to draw. Note that Shadow parts cannot be drawn * individually and instead are drawn with their associated part (e.g., drawn together * with the text or background parts) * \param mode layout mode * \note Not available in Python bindings * \since QGIS 3.14 */ - static void drawPart( const QRectF &rect, double rotation, Qgis::TextHorizontalAlignment alignment, Qgis::TextVerticalAlignment vAlignment, const QgsTextDocument &document, const QgsTextDocumentMetrics &metrics, - QgsRenderContext &context, const QgsTextFormat &format, - Qgis::TextComponent part, Qgis::TextLayoutMode mode ); + static void drawParts( const QRectF &rect, double rotation, Qgis::TextHorizontalAlignment alignment, Qgis::TextVerticalAlignment vAlignment, const QgsTextDocument &document, const QgsTextDocumentMetrics &metrics, + QgsRenderContext &context, const QgsTextFormat &format, + Qgis::TextComponents parts, Qgis::TextLayoutMode mode ); /** - * Draws a single component of rendered text using the specified settings. + * Draws components of rendered text using the specified settings. * \param origin origin for start of text. Y coordinate will be used as baseline. * \param rotation text rotation * \param alignment horizontal alignment @@ -425,18 +426,18 @@ class CORE_EXPORT QgsTextRenderer * \param metrics precalculated document metrics * \param context render context * \param format text format - * \param part component of text to draw. Note that Shadow parts cannot be drawn + * \param parts components of text to draw. Note that Shadow parts cannot be drawn * individually and instead are drawn with their associated part (e.g., drawn together * with the text or background parts) * \param mode layout mode * \note Not available in Python bindings * \since QGIS 3.14 */ - static void drawPart( QPointF origin, double rotation, Qgis::TextHorizontalAlignment alignment, const QgsTextDocument &document, - const QgsTextDocumentMetrics &metrics, - QgsRenderContext &context, const QgsTextFormat &format, - Qgis::TextComponent part, - Qgis::TextLayoutMode mode ); + static void drawParts( QPointF origin, double rotation, Qgis::TextHorizontalAlignment alignment, const QgsTextDocument &document, + const QgsTextDocumentMetrics &metrics, + QgsRenderContext &context, const QgsTextFormat &format, + Qgis::TextComponents parts, + Qgis::TextLayoutMode mode ); static double drawBuffer( QgsRenderContext &context, const Component &component, @@ -464,7 +465,7 @@ class CORE_EXPORT QgsTextRenderer const Component &component, const QgsTextFormat &format ); - static void drawTextInternal( Qgis::TextComponent drawType, + static void drawTextInternal( Qgis::TextComponents components, QgsRenderContext &context, const QgsTextFormat &format, const Component &component, @@ -481,7 +482,7 @@ class CORE_EXPORT QgsTextRenderer static void drawTextInternalHorizontal( QgsRenderContext &context, const QgsTextFormat &format, - Qgis::TextComponent drawType, + Qgis::TextComponents components, Qgis::TextLayoutMode mode, const Component &component, const QgsTextDocument &document, @@ -493,7 +494,7 @@ class CORE_EXPORT QgsTextRenderer static void drawTextInternalVertical( QgsRenderContext &context, const QgsTextFormat &format, - Qgis::TextComponent drawType, + Qgis::TextComponents components, Qgis::TextLayoutMode mode, const Component &component, const QgsTextDocument &document, @@ -503,12 +504,36 @@ class CORE_EXPORT QgsTextRenderer Qgis::TextVerticalAlignment vAlignment, double rotation ); + struct DeferredRenderFragment + { + // mandatory + QColor color; + QPointF point; + // optional + QPainterPath path; + // optional + QFont font; + QString text; + }; + + struct DeferredRenderBlock + { + QPointF origin; + Component component; + QVector< DeferredRenderFragment > fragments; + }; + static void renderBlockHorizontal( const QgsTextBlock &block, int blockIndex, const QgsTextDocumentMetrics &metrics, QgsRenderContext &context, const QgsTextFormat &format, - QPainter *painter, bool usePaths, + QPainter *painter, bool forceRenderAsPaths, double fontScale, double extraWordSpace, double extraLetterSpace, - Qgis::TextLayoutMode mode ); + Qgis::TextLayoutMode mode, + DeferredRenderBlock *deferredRenderBlock ); + static void renderDeferredBlocks( QgsRenderContext &context, const QgsTextFormat &format, Qgis::TextComponents components, const std::vector &deferredBlocks, bool usePathsForText, double fontScale, const Component &component, double rotation ); + static void renderDeferredBuffer( QgsRenderContext &context, const QgsTextFormat &format, Qgis::TextComponents components, const std::vector &deferredBlocks, double fontScale, const Component &component, double rotation ); + static void renderDeferredShadowForText( QgsRenderContext &context, const QgsTextFormat &format, const std::vector &deferredBlocks, double fontScale, const Component &component, double rotation ); + static void renderDeferredText( QgsRenderContext &context, const std::vector &deferredBlocks, bool usePathsForText, double fontScale, const Component &component, double rotation ); friend class QgsVectorLayerLabelProvider; friend class QgsLabelPreview; @@ -519,6 +544,12 @@ class CORE_EXPORT QgsTextRenderer * Returns TRUE if paths should be used to render a document */ static bool usePathsToRender( const QgsRenderContext &context, const QgsTextFormat &format, const QgsTextDocument &document ); + + /** + * Returns TRUE if a picture should be used to render a document + */ + static bool usePictureToRender( const QgsRenderContext &context, const QgsTextFormat &format, const QgsTextDocument &document ); + }; #endif // QGSTEXTRENDERER_H diff --git a/tests/src/python/test_qgstextrenderer.py b/tests/src/python/test_qgstextrenderer.py index 91cb38a6f120..dab1ed1f80be 100644 --- a/tests/src/python/test_qgstextrenderer.py +++ b/tests/src/python/test_qgstextrenderer.py @@ -1513,7 +1513,7 @@ def testDrawShadow(self): format.shadow().setBlurRadius(0) format.shadow().setOffsetDistance(5) format.shadow().setOffsetUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) - self.assertTrue(self.checkRender(format, 'shadow_enabled', QgsTextRenderer.TextPart.Text, text=['test'])) + self.assertTrue(self.checkRender(format, 'shadow_enabled', None, text=['test'])) def testDrawShadowOffsetAngle(self): format = QgsTextFormat() @@ -1528,7 +1528,7 @@ def testDrawShadowOffsetAngle(self): format.shadow().setOffsetDistance(5) format.shadow().setOffsetAngle(0) format.shadow().setOffsetUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) - self.assertTrue(self.checkRender(format, 'shadow_offset_angle', QgsTextRenderer.TextPart.Text, text=['test'])) + self.assertTrue(self.checkRender(format, 'shadow_offset_angle', None, text=['test'])) def testDrawShadowOffsetMapUnits(self): format = QgsTextFormat() @@ -1542,7 +1542,7 @@ def testDrawShadowOffsetMapUnits(self): format.shadow().setBlurRadius(0) format.shadow().setOffsetDistance(10) format.shadow().setOffsetUnit(QgsUnitTypes.RenderUnit.RenderMapUnits) - self.assertTrue(self.checkRender(format, 'shadow_offset_mapunits', QgsTextRenderer.TextPart.Text, text=['test'])) + self.assertTrue(self.checkRender(format, 'shadow_offset_mapunits', None, text=['test'])) def testDrawShadowOffsetPixels(self): format = QgsTextFormat() @@ -1556,7 +1556,7 @@ def testDrawShadowOffsetPixels(self): format.shadow().setBlurRadius(0) format.shadow().setOffsetDistance(10) format.shadow().setOffsetUnit(QgsUnitTypes.RenderUnit.RenderPixels) - self.assertTrue(self.checkRender(format, 'shadow_offset_pixels', QgsTextRenderer.TextPart.Text, text=['test'])) + self.assertTrue(self.checkRender(format, 'shadow_offset_pixels', None, text=['test'])) def testDrawShadowOffsetPercentage(self): format = QgsTextFormat() @@ -1570,7 +1570,7 @@ def testDrawShadowOffsetPercentage(self): format.shadow().setBlurRadius(0) format.shadow().setOffsetDistance(10) format.shadow().setOffsetUnit(QgsUnitTypes.RenderUnit.RenderPercentage) - self.assertTrue(self.checkRender(format, 'shadow_offset_percentage', QgsTextRenderer.TextPart.Text, text=['test'])) + self.assertTrue(self.checkRender(format, 'shadow_offset_percentage', None, text=['test'])) def testDrawShadowBlurRadiusMM(self): format = QgsTextFormat() @@ -1585,7 +1585,7 @@ def testDrawShadowBlurRadiusMM(self): format.shadow().setOffsetUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) format.shadow().setBlurRadius(1) format.shadow().setBlurRadiusUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) - self.assertTrue(self.checkRender(format, 'shadow_radius_mm', QgsTextRenderer.TextPart.Text, text=['test'])) + self.assertTrue(self.checkRender(format, 'shadow_radius_mm', None, text=['test'])) def testDrawShadowBlurRadiusMapUnits(self): format = QgsTextFormat() @@ -1600,7 +1600,7 @@ def testDrawShadowBlurRadiusMapUnits(self): format.shadow().setOffsetUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) format.shadow().setBlurRadius(3) format.shadow().setBlurRadiusUnit(QgsUnitTypes.RenderUnit.RenderMapUnits) - self.assertTrue(self.checkRender(format, 'shadow_radius_mapunits', QgsTextRenderer.TextPart.Text, text=['test'])) + self.assertTrue(self.checkRender(format, 'shadow_radius_mapunits', None, text=['test'])) def testDrawShadowBlurRadiusPixels(self): format = QgsTextFormat() @@ -1615,7 +1615,7 @@ def testDrawShadowBlurRadiusPixels(self): format.shadow().setOffsetUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) format.shadow().setBlurRadius(3) format.shadow().setBlurRadiusUnit(QgsUnitTypes.RenderUnit.RenderPixels) - self.assertTrue(self.checkRender(format, 'shadow_radius_pixels', QgsTextRenderer.TextPart.Text, text=['test'])) + self.assertTrue(self.checkRender(format, 'shadow_radius_pixels', None, text=['test'])) def testDrawShadowBlurRadiusPercentage(self): format = QgsTextFormat() @@ -1630,7 +1630,7 @@ def testDrawShadowBlurRadiusPercentage(self): format.shadow().setOffsetUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) format.shadow().setBlurRadius(5) format.shadow().setBlurRadiusUnit(QgsUnitTypes.RenderUnit.RenderPercentage) - self.assertTrue(self.checkRender(format, 'shadow_radius_percentage', QgsTextRenderer.TextPart.Text, text=['test'])) + self.assertTrue(self.checkRender(format, 'shadow_radius_percentage', None, text=['test'])) def testDrawShadowOpacity(self): format = QgsTextFormat() @@ -1644,7 +1644,7 @@ def testDrawShadowOpacity(self): format.shadow().setBlurRadius(0) format.shadow().setOffsetDistance(5) format.shadow().setOffsetUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) - self.assertTrue(self.checkRender(format, 'shadow_opacity', QgsTextRenderer.TextPart.Text, text=['test'])) + self.assertTrue(self.checkRender(format, 'shadow_opacity', None, text=['test'])) def testDrawShadowColor(self): format = QgsTextFormat() @@ -1658,7 +1658,7 @@ def testDrawShadowColor(self): format.shadow().setBlurRadius(0) format.shadow().setOffsetDistance(5) format.shadow().setOffsetUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) - self.assertTrue(self.checkRender(format, 'shadow_color', QgsTextRenderer.TextPart.Text, text=['test'])) + self.assertTrue(self.checkRender(format, 'shadow_color', None, text=['test'])) def testDrawShadowWithJustifyAlign(self): format = QgsTextFormat() @@ -1687,7 +1687,7 @@ def testDrawShadowScale(self): format.shadow().setBlurRadius(0) format.shadow().setOffsetDistance(5) format.shadow().setOffsetUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) - self.assertTrue(self.checkRender(format, 'shadow_scale_50', QgsTextRenderer.TextPart.Text, text=['test'])) + self.assertTrue(self.checkRender(format, 'shadow_scale_50', None, text=['test'])) def testDrawShadowScaleUp(self): format = QgsTextFormat() @@ -1701,7 +1701,7 @@ def testDrawShadowScaleUp(self): format.shadow().setBlurRadius(0) format.shadow().setOffsetDistance(5) format.shadow().setOffsetUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) - self.assertTrue(self.checkRender(format, 'shadow_scale_150', QgsTextRenderer.TextPart.Text, text=['test'])) + self.assertTrue(self.checkRender(format, 'shadow_scale_150', None, text=['test'])) def testDrawShadowBackgroundPlacement(self): format = QgsTextFormat() @@ -1719,7 +1719,7 @@ def testDrawShadowBackgroundPlacement(self): format.background().setSize(QSizeF(20, 10)) format.background().setSizeType(QgsTextBackgroundSettings.SizeType.SizeFixed) format.background().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMapUnits) - self.assertTrue(self.checkRender(format, 'shadow_placement_background', QgsTextRenderer.TextPart.Background, text=['test'])) + self.assertTrue(self.checkRender(format, 'shadow_placement_background', None, text=['test'])) def testDrawShadowBufferPlacement(self): format = QgsTextFormat() @@ -1735,7 +1735,7 @@ def testDrawShadowBufferPlacement(self): format.buffer().setEnabled(True) format.buffer().setSize(4) format.buffer().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) - self.assertTrue(self.checkRender(format, 'shadow_placement_buffer', QgsTextRenderer.TextPart.Buffer, text=['test'])) + self.assertTrue(self.checkRender(format, 'shadow_placement_buffer', None, text=['test'])) def testDrawTextWithBuffer(self): format = QgsTextFormat() diff --git a/tests/testdata/control_images/expected_pal_canvas/sp_shadow/sp_shadow_mask.png b/tests/testdata/control_images/expected_pal_canvas/sp_shadow/sp_shadow_mask.png index 9ff67ee4bf55..cb176cc2d002 100644 Binary files a/tests/testdata/control_images/expected_pal_canvas/sp_shadow/sp_shadow_mask.png and b/tests/testdata/control_images/expected_pal_canvas/sp_shadow/sp_shadow_mask.png differ diff --git a/tests/testdata/control_images/expected_pal_composer/sp_img_shadow/sp_img_shadow_mask.png b/tests/testdata/control_images/expected_pal_composer/sp_img_shadow/sp_img_shadow_mask.png index 7c0dfd0ddc38..b194c3989ea6 100644 Binary files a/tests/testdata/control_images/expected_pal_composer/sp_img_shadow/sp_img_shadow_mask.png and b/tests/testdata/control_images/expected_pal_composer/sp_img_shadow/sp_img_shadow_mask.png differ diff --git a/tests/testdata/control_images/expected_pal_composer/sp_pdf_shadow/sp_pdf_shadow_mask.png b/tests/testdata/control_images/expected_pal_composer/sp_pdf_shadow/sp_pdf_shadow_mask.png index 7ca1e836d1ae..5e78ca41ca97 100644 Binary files a/tests/testdata/control_images/expected_pal_composer/sp_pdf_shadow/sp_pdf_shadow_mask.png and b/tests/testdata/control_images/expected_pal_composer/sp_pdf_shadow/sp_pdf_shadow_mask.png differ diff --git a/tests/testdata/control_images/expected_pal_composer/sp_svg_shadow/sp_svg_shadow_mask.png b/tests/testdata/control_images/expected_pal_composer/sp_svg_shadow/sp_svg_shadow_mask.png index 58510658f5b9..28cb1afc9f75 100644 Binary files a/tests/testdata/control_images/expected_pal_composer/sp_svg_shadow/sp_svg_shadow_mask.png and b/tests/testdata/control_images/expected_pal_composer/sp_svg_shadow/sp_svg_shadow_mask.png differ diff --git a/tests/testdata/control_images/expected_pal_server/sp_shadow/sp_shadow_mask.png b/tests/testdata/control_images/expected_pal_server/sp_shadow/sp_shadow_mask.png index 8b205a5b90bb..a46390f940a9 100644 Binary files a/tests/testdata/control_images/expected_pal_server/sp_shadow/sp_shadow_mask.png and b/tests/testdata/control_images/expected_pal_server/sp_shadow/sp_shadow_mask.png differ diff --git a/tests/testdata/control_images/text_renderer/draw_document_shadow_lowest/draw_document_shadow_lowest_mask.png b/tests/testdata/control_images/text_renderer/draw_document_shadow_lowest/draw_document_shadow_lowest_mask.png index f8c21f7c7e78..d08f8bc91271 100644 Binary files a/tests/testdata/control_images/text_renderer/draw_document_shadow_lowest/draw_document_shadow_lowest_mask.png and b/tests/testdata/control_images/text_renderer/draw_document_shadow_lowest/draw_document_shadow_lowest_mask.png differ diff --git a/tests/testdata/control_images/text_renderer/rtl_mixed_shadow/rtl_mixed_shadow_mask.png b/tests/testdata/control_images/text_renderer/rtl_mixed_shadow/rtl_mixed_shadow_mask.png index 3e8635c69ab1..6201d2348ba8 100644 Binary files a/tests/testdata/control_images/text_renderer/rtl_mixed_shadow/rtl_mixed_shadow_mask.png and b/tests/testdata/control_images/text_renderer/rtl_mixed_shadow/rtl_mixed_shadow_mask.png differ diff --git a/tests/testdata/control_images/text_renderer/rtl_shadow/rtl_shadow_mask.png b/tests/testdata/control_images/text_renderer/rtl_shadow/rtl_shadow_mask.png index 8e49fac4d537..06b9a3937fc1 100644 Binary files a/tests/testdata/control_images/text_renderer/rtl_shadow/rtl_shadow_mask.png and b/tests/testdata/control_images/text_renderer/rtl_shadow/rtl_shadow_mask.png differ diff --git a/tests/testdata/control_images/text_renderer/shadow_offset_angle/shadow_offset_angle_mask.png b/tests/testdata/control_images/text_renderer/shadow_offset_angle/shadow_offset_angle_mask.png index cef27577e32e..b2ce013db9fe 100644 Binary files a/tests/testdata/control_images/text_renderer/shadow_offset_angle/shadow_offset_angle_mask.png and b/tests/testdata/control_images/text_renderer/shadow_offset_angle/shadow_offset_angle_mask.png differ diff --git a/tests/testdata/control_images/text_renderer/shadow_offset_mapunits/shadow_offset_mapunits_mask.png b/tests/testdata/control_images/text_renderer/shadow_offset_mapunits/shadow_offset_mapunits_mask.png index c8148e47a57c..eaaf14cd832f 100644 Binary files a/tests/testdata/control_images/text_renderer/shadow_offset_mapunits/shadow_offset_mapunits_mask.png and b/tests/testdata/control_images/text_renderer/shadow_offset_mapunits/shadow_offset_mapunits_mask.png differ diff --git a/tests/testdata/control_images/text_renderer/shadow_offset_percentage/shadow_offset_percentage_mask.png b/tests/testdata/control_images/text_renderer/shadow_offset_percentage/shadow_offset_percentage_mask.png index f1f4b52dbb38..7a64a328c464 100644 Binary files a/tests/testdata/control_images/text_renderer/shadow_offset_percentage/shadow_offset_percentage_mask.png and b/tests/testdata/control_images/text_renderer/shadow_offset_percentage/shadow_offset_percentage_mask.png differ diff --git a/tests/testdata/control_images/text_renderer/shadow_placement_background/shadow_placement_background.png b/tests/testdata/control_images/text_renderer/shadow_placement_background/shadow_placement_background.png index 5ee2248da63a..57fcdc4c57bf 100644 Binary files a/tests/testdata/control_images/text_renderer/shadow_placement_background/shadow_placement_background.png and b/tests/testdata/control_images/text_renderer/shadow_placement_background/shadow_placement_background.png differ diff --git a/tests/testdata/control_images/text_renderer/shadow_placement_background/shadow_placement_background_mask.png b/tests/testdata/control_images/text_renderer/shadow_placement_background/shadow_placement_background_mask.png index 0955d12b9781..71220584f24d 100644 Binary files a/tests/testdata/control_images/text_renderer/shadow_placement_background/shadow_placement_background_mask.png and b/tests/testdata/control_images/text_renderer/shadow_placement_background/shadow_placement_background_mask.png differ diff --git a/tests/testdata/control_images/text_renderer/shadow_radius_percentage/shadow_radius_percentage_mask.png b/tests/testdata/control_images/text_renderer/shadow_radius_percentage/shadow_radius_percentage_mask.png index ab1ff935ba26..7ac776704a4a 100644 Binary files a/tests/testdata/control_images/text_renderer/shadow_radius_percentage/shadow_radius_percentage_mask.png and b/tests/testdata/control_images/text_renderer/shadow_radius_percentage/shadow_radius_percentage_mask.png differ diff --git a/tests/testdata/control_images/text_renderer/shadow_scale_150/shadow_scale_150_mask.png b/tests/testdata/control_images/text_renderer/shadow_scale_150/shadow_scale_150_mask.png index e1c68e56a0b5..311102d92558 100644 Binary files a/tests/testdata/control_images/text_renderer/shadow_scale_150/shadow_scale_150_mask.png and b/tests/testdata/control_images/text_renderer/shadow_scale_150/shadow_scale_150_mask.png differ diff --git a/tests/testdata/control_images/text_renderer/shadow_scale_50/shadow_scale_50_mask.png b/tests/testdata/control_images/text_renderer/shadow_scale_50/shadow_scale_50_mask.png index 390e0ca2ff0a..1cd4b902de32 100644 Binary files a/tests/testdata/control_images/text_renderer/shadow_scale_50/shadow_scale_50_mask.png and b/tests/testdata/control_images/text_renderer/shadow_scale_50/shadow_scale_50_mask.png differ diff --git a/tests/testdata/control_images/text_renderer/text_buffer_effect/text_buffer_effect_mask.png b/tests/testdata/control_images/text_renderer/text_buffer_effect/text_buffer_effect_mask.png new file mode 100644 index 000000000000..13f3e219ea19 Binary files /dev/null and b/tests/testdata/control_images/text_renderer/text_buffer_effect/text_buffer_effect_mask.png differ diff --git a/tests/testdata/control_images/text_renderer/text_html_formatting_buffer_shadow/variant1/text_html_formatting_buffer_shadow_mask.png b/tests/testdata/control_images/text_renderer/text_html_formatting_buffer_shadow/variant1/text_html_formatting_buffer_shadow_mask.png index c1fafd92cad5..3b293ec9b283 100644 Binary files a/tests/testdata/control_images/text_renderer/text_html_formatting_buffer_shadow/variant1/text_html_formatting_buffer_shadow_mask.png and b/tests/testdata/control_images/text_renderer/text_html_formatting_buffer_shadow/variant1/text_html_formatting_buffer_shadow_mask.png differ diff --git a/tests/testdata/control_images/text_renderer/text_html_formatting_buffer_shadow/variant2/text_html_formatting_buffer_shadow_mask.png b/tests/testdata/control_images/text_renderer/text_html_formatting_buffer_shadow/variant2/text_html_formatting_buffer_shadow_mask.png index 7129f13cba56..b4adba47e3af 100644 Binary files a/tests/testdata/control_images/text_renderer/text_html_formatting_buffer_shadow/variant2/text_html_formatting_buffer_shadow_mask.png and b/tests/testdata/control_images/text_renderer/text_html_formatting_buffer_shadow/variant2/text_html_formatting_buffer_shadow_mask.png differ diff --git a/tests/testdata/control_images/text_renderer/text_html_formatting_buffer_shadow/variant3/text_html_formatting_buffer_shadow_mask.png b/tests/testdata/control_images/text_renderer/text_html_formatting_buffer_shadow/variant3/text_html_formatting_buffer_shadow_mask.png index 2657ce69080c..15399a174580 100644 Binary files a/tests/testdata/control_images/text_renderer/text_html_formatting_buffer_shadow/variant3/text_html_formatting_buffer_shadow_mask.png and b/tests/testdata/control_images/text_renderer/text_html_formatting_buffer_shadow/variant3/text_html_formatting_buffer_shadow_mask.png differ diff --git a/tests/testdata/control_images/text_renderer/text_html_formatting_buffer_shadow/variant4/text_html_formatting_buffer_shadow_mask.png b/tests/testdata/control_images/text_renderer/text_html_formatting_buffer_shadow/variant4/text_html_formatting_buffer_shadow_mask.png new file mode 100644 index 000000000000..5605b83e09cc Binary files /dev/null and b/tests/testdata/control_images/text_renderer/text_html_formatting_buffer_shadow/variant4/text_html_formatting_buffer_shadow_mask.png differ diff --git a/tests/testdata/control_images/text_renderer/text_html_formatting_shadow/variant1/text_html_formatting_shadow_mask.png b/tests/testdata/control_images/text_renderer/text_html_formatting_shadow/variant1/text_html_formatting_shadow_mask.png index 08744b239a16..e320f7c9ab8a 100644 Binary files a/tests/testdata/control_images/text_renderer/text_html_formatting_shadow/variant1/text_html_formatting_shadow_mask.png and b/tests/testdata/control_images/text_renderer/text_html_formatting_shadow/variant1/text_html_formatting_shadow_mask.png differ diff --git a/tests/testdata/control_images/text_renderer/text_html_formatting_shadow/variant2/text_html_formatting_shadow_mask.png b/tests/testdata/control_images/text_renderer/text_html_formatting_shadow/variant2/text_html_formatting_shadow_mask.png index b1c49afd190a..5abe5fe84ff9 100644 Binary files a/tests/testdata/control_images/text_renderer/text_html_formatting_shadow/variant2/text_html_formatting_shadow_mask.png and b/tests/testdata/control_images/text_renderer/text_html_formatting_shadow/variant2/text_html_formatting_shadow_mask.png differ diff --git a/tests/testdata/control_images/text_renderer/text_html_formatting_shadow/variant3/text_html_formatting_shadow_mask.png b/tests/testdata/control_images/text_renderer/text_html_formatting_shadow/variant3/text_html_formatting_shadow_mask.png index 98c2a4ed01ca..452064b5408e 100644 Binary files a/tests/testdata/control_images/text_renderer/text_html_formatting_shadow/variant3/text_html_formatting_shadow_mask.png and b/tests/testdata/control_images/text_renderer/text_html_formatting_shadow/variant3/text_html_formatting_shadow_mask.png differ diff --git a/tests/testdata/control_images/text_renderer/text_html_formatting_shadow/variant4/text_html_formatting_shadow_mask.png b/tests/testdata/control_images/text_renderer/text_html_formatting_shadow/variant4/text_html_formatting_shadow_mask.png new file mode 100644 index 000000000000..b7315e26865b Binary files /dev/null and b/tests/testdata/control_images/text_renderer/text_html_formatting_shadow/variant4/text_html_formatting_shadow_mask.png differ diff --git a/tests/testdata/control_images/text_renderer/text_html_mixed_metric_formatting_buffer_shadow/variant1/text_html_mixed_metric_formatting_buffer_shadow_mask.png b/tests/testdata/control_images/text_renderer/text_html_mixed_metric_formatting_buffer_shadow/variant1/text_html_mixed_metric_formatting_buffer_shadow_mask.png new file mode 100644 index 000000000000..406fffb632d0 Binary files /dev/null and b/tests/testdata/control_images/text_renderer/text_html_mixed_metric_formatting_buffer_shadow/variant1/text_html_mixed_metric_formatting_buffer_shadow_mask.png differ diff --git a/tests/testdata/control_images/text_renderer/text_html_mixed_metric_formatting_buffer_shadow/variant2/text_html_mixed_metric_formatting_buffer_shadow_mask.png b/tests/testdata/control_images/text_renderer/text_html_mixed_metric_formatting_buffer_shadow/variant2/text_html_mixed_metric_formatting_buffer_shadow_mask.png index e151b8849e34..185f1847186a 100644 Binary files a/tests/testdata/control_images/text_renderer/text_html_mixed_metric_formatting_buffer_shadow/variant2/text_html_mixed_metric_formatting_buffer_shadow_mask.png and b/tests/testdata/control_images/text_renderer/text_html_mixed_metric_formatting_buffer_shadow/variant2/text_html_mixed_metric_formatting_buffer_shadow_mask.png differ diff --git a/tests/testdata/control_images/text_renderer/text_html_mixed_metric_formatting_buffer_shadow/variant3/text_html_mixed_metric_formatting_buffer_shadow_mask.png b/tests/testdata/control_images/text_renderer/text_html_mixed_metric_formatting_buffer_shadow/variant3/text_html_mixed_metric_formatting_buffer_shadow_mask.png index b3371068efa3..388c37b502b9 100644 Binary files a/tests/testdata/control_images/text_renderer/text_html_mixed_metric_formatting_buffer_shadow/variant3/text_html_mixed_metric_formatting_buffer_shadow_mask.png and b/tests/testdata/control_images/text_renderer/text_html_mixed_metric_formatting_buffer_shadow/variant3/text_html_mixed_metric_formatting_buffer_shadow_mask.png differ diff --git a/tests/testdata/control_images/text_renderer/text_html_mixed_metric_formatting_buffer_shadow/variant4/text_html_mixed_metric_formatting_buffer_shadow_mask.png b/tests/testdata/control_images/text_renderer/text_html_mixed_metric_formatting_buffer_shadow/variant4/text_html_mixed_metric_formatting_buffer_shadow_mask.png new file mode 100644 index 000000000000..9369423a9661 Binary files /dev/null and b/tests/testdata/control_images/text_renderer/text_html_mixed_metric_formatting_buffer_shadow/variant4/text_html_mixed_metric_formatting_buffer_shadow_mask.png differ diff --git a/tests/testdata/control_images/text_renderer/text_html_mixed_metric_formatting_shadow/variant1/text_html_mixed_metric_formatting_shadow_mask.png b/tests/testdata/control_images/text_renderer/text_html_mixed_metric_formatting_shadow/variant1/text_html_mixed_metric_formatting_shadow_mask.png new file mode 100644 index 000000000000..043565bda554 Binary files /dev/null and b/tests/testdata/control_images/text_renderer/text_html_mixed_metric_formatting_shadow/variant1/text_html_mixed_metric_formatting_shadow_mask.png differ diff --git a/tests/testdata/control_images/text_renderer/text_html_mixed_metric_formatting_shadow/variant2/text_html_mixed_metric_formatting_shadow_mask.png b/tests/testdata/control_images/text_renderer/text_html_mixed_metric_formatting_shadow/variant2/text_html_mixed_metric_formatting_shadow_mask.png index 80eacc24c141..5b647ee54b32 100644 Binary files a/tests/testdata/control_images/text_renderer/text_html_mixed_metric_formatting_shadow/variant2/text_html_mixed_metric_formatting_shadow_mask.png and b/tests/testdata/control_images/text_renderer/text_html_mixed_metric_formatting_shadow/variant2/text_html_mixed_metric_formatting_shadow_mask.png differ diff --git a/tests/testdata/control_images/text_renderer/text_html_mixed_metric_formatting_shadow/variant3/text_html_mixed_metric_formatting_shadow_mask.png b/tests/testdata/control_images/text_renderer/text_html_mixed_metric_formatting_shadow/variant3/text_html_mixed_metric_formatting_shadow_mask.png index 5d7d682f8040..63bc427f8534 100644 Binary files a/tests/testdata/control_images/text_renderer/text_html_mixed_metric_formatting_shadow/variant3/text_html_mixed_metric_formatting_shadow_mask.png and b/tests/testdata/control_images/text_renderer/text_html_mixed_metric_formatting_shadow/variant3/text_html_mixed_metric_formatting_shadow_mask.png differ diff --git a/tests/testdata/control_images/text_renderer/text_html_supersubscript_buffer_shadow/text_html_supersubscript_buffer_shadow_mask.png b/tests/testdata/control_images/text_renderer/text_html_supersubscript_buffer_shadow/text_html_supersubscript_buffer_shadow_mask.png index 56f3afc05318..3bab2f3d5dba 100644 Binary files a/tests/testdata/control_images/text_renderer/text_html_supersubscript_buffer_shadow/text_html_supersubscript_buffer_shadow_mask.png and b/tests/testdata/control_images/text_renderer/text_html_supersubscript_buffer_shadow/text_html_supersubscript_buffer_shadow_mask.png differ diff --git a/tests/testdata/control_images/text_renderer/text_html_supersubscript_shadow/text_html_supersubscript_shadow_mask.png b/tests/testdata/control_images/text_renderer/text_html_supersubscript_shadow/text_html_supersubscript_shadow_mask.png index 4f45847137ce..d8d7d3fb59d1 100644 Binary files a/tests/testdata/control_images/text_renderer/text_html_supersubscript_shadow/text_html_supersubscript_shadow_mask.png and b/tests/testdata/control_images/text_renderer/text_html_supersubscript_shadow/text_html_supersubscript_shadow_mask.png differ diff --git a/tests/testdata/control_images/text_renderer/text_justify_aligned_with_shadow/text_justify_aligned_with_shadow_mask.png b/tests/testdata/control_images/text_renderer/text_justify_aligned_with_shadow/text_justify_aligned_with_shadow_mask.png index 88786718b255..9d92855d1a21 100644 Binary files a/tests/testdata/control_images/text_renderer/text_justify_aligned_with_shadow/text_justify_aligned_with_shadow_mask.png and b/tests/testdata/control_images/text_renderer/text_justify_aligned_with_shadow/text_justify_aligned_with_shadow_mask.png differ