From cd6529a0501d49e0cd6344523d509fa7186687c9 Mon Sep 17 00:00:00 2001 From: "Armin Le Grand (Collabora)" Date: Thu, 15 Aug 2024 17:30:23 +0200 Subject: CairoSDPR: Add support for Relief for Text Rendering Change-Id: Idcb33e0f799b2219a4e737d0421bc21d85b5c7aa Reviewed-on: https://gerrit.libreoffice.org/c/core/+/171915 Reviewed-by: Armin Le Grand Tested-by: Jenkins --- .../primitive2d/textdecoratedprimitive2d.cxx | 70 ++++++++++---- .../source/primitive2d/textprimitive2d.cxx | 24 +++++ .../source/processor2d/cairopixelprocessor2d.cxx | 102 +++++++++++---------- 3 files changed, 132 insertions(+), 64 deletions(-) (limited to 'drawinglayer') diff --git a/drawinglayer/source/primitive2d/textdecoratedprimitive2d.cxx b/drawinglayer/source/primitive2d/textdecoratedprimitive2d.cxx index 2f0792fb7aa0..bfa5ebbb7eea 100644 --- a/drawinglayer/source/primitive2d/textdecoratedprimitive2d.cxx +++ b/drawinglayer/source/primitive2d/textdecoratedprimitive2d.cxx @@ -120,6 +120,11 @@ namespace drawinglayer::primitive2d if(bOverlineUsed) { + // for Relief we have to manipulate the OverlineColor + basegfx::BColor aOverlineColor(getOverlineColor()); + if (hasTextRelief() && COL_BLACK.getBColor() == aOverlineColor) + aOverlineColor = COL_WHITE.getBColor(); + // create primitive geometry for overline maBufferedDecorationGeometry.push_back( new TextLinePrimitive2D( @@ -128,11 +133,16 @@ namespace drawinglayer::primitive2d aTextLayouter.getOverlineOffset(), aTextLayouter.getOverlineHeight(), getFontOverline(), - getOverlineColor())); + aOverlineColor)); } if(bUnderlineUsed) { + // for Relief we have to manipulate the TextlineColor + basegfx::BColor aTextlineColor(getTextlineColor()); + if (hasTextRelief() && COL_BLACK.getBColor() == aTextlineColor) + aTextlineColor = COL_WHITE.getBColor(); + // create primitive geometry for underline maBufferedDecorationGeometry.push_back( new TextLinePrimitive2D( @@ -141,11 +151,16 @@ namespace drawinglayer::primitive2d aTextLayouter.getUnderlineOffset(), aTextLayouter.getUnderlineHeight(), getFontUnderline(), - getTextlineColor())); + aTextlineColor)); } if(bStrikeoutUsed) { + // for Relief we have to manipulate the FontColor + basegfx::BColor aFontColor(getFontColor()); + if (hasTextRelief() && COL_BLACK.getBColor() == aFontColor) + aFontColor = COL_WHITE.getBColor(); + // create primitive geometry for strikeout if(TEXT_STRIKEOUT_SLASH == getTextStrikeout() || TEXT_STRIKEOUT_X == getTextStrikeout()) { @@ -156,7 +171,7 @@ namespace drawinglayer::primitive2d new TextCharacterStrikeoutPrimitive2D( rDecTrans.getB2DHomMatrix(), fTextWidth, - getFontColor(), + aFontColor, aStrikeoutChar, getFontAttribute(), getLocale())); @@ -168,7 +183,7 @@ namespace drawinglayer::primitive2d new TextGeometryStrikeoutPrimitive2D( rDecTrans.getB2DHomMatrix(), fTextWidth, - getFontColor(), + aFontColor, aTextLayouter.getUnderlineHeight(), aTextLayouter.getStrikeoutOffset(), getTextStrikeout())); @@ -182,6 +197,11 @@ namespace drawinglayer::primitive2d if (pSalLayout) { + // for Relief we have to manipulate the FontColor + basegfx::BColor aFontColor(getFontColor()); + if (hasTextRelief() && COL_BLACK.getBColor() == aFontColor) + aFontColor = COL_WHITE.getBColor(); + // placeholders for repeated content, only created once Primitive2DReference aShape; Primitive2DReference aRect1; @@ -198,7 +218,7 @@ namespace drawinglayer::primitive2d // the callback from OutputDevice::createEmphasisMarks providing the data // for each EmphasisMark - auto aEmphasisCallback([this, &aShape, &aRect1, &aRect2, &aEmphasisContent, &aObjTransformWithoutScale]( + auto aEmphasisCallback([&aShape, &aRect1, &aRect2, &aEmphasisContent, &aObjTransformWithoutScale, &aFontColor]( const basegfx::B2DPoint& rOutPoint, const basegfx::B2DPolyPolygon& rShape, bool isPolyLine, const tools::Rectangle& rRect1, const tools::Rectangle& rRect2) { @@ -212,9 +232,9 @@ namespace drawinglayer::primitive2d if (!aShape) { if (isPolyLine) - aShape = new PolyPolygonHairlinePrimitive2D(rShape, getFontColor()); + aShape = new PolyPolygonHairlinePrimitive2D(rShape, aFontColor); else - aShape = new PolyPolygonColorPrimitive2D(rShape, getFontColor()); + aShape = new PolyPolygonColorPrimitive2D(rShape, aFontColor); } aEmphasisContent.push_back( @@ -228,7 +248,7 @@ namespace drawinglayer::primitive2d // create Rectangle1 if provided if (!aRect1) aRect1 = new FilledRectanglePrimitive2D( - basegfx::B2DRange(rRect1.Left(), rRect1.Top(), rRect1.Right(), rRect1.Bottom()), getFontColor()); + basegfx::B2DRange(rRect1.Left(), rRect1.Top(), rRect1.Right(), rRect1.Bottom()), aFontColor); aEmphasisContent.push_back( new TransformPrimitive2D( @@ -241,7 +261,7 @@ namespace drawinglayer::primitive2d // create Rectangle2 if provided if (!aRect2) aRect2 = new FilledRectanglePrimitive2D( - basegfx::B2DRange(rRect2.Left(), rRect2.Top(), rRect2.Right(), rRect2.Bottom()), getFontColor()); + basegfx::B2DRange(rRect2.Left(), rRect2.Top(), rRect2.Right(), rRect2.Bottom()), aFontColor); aEmphasisContent.push_back( new TransformPrimitive2D( @@ -328,16 +348,11 @@ namespace drawinglayer::primitive2d if(aRetval.empty()) return nullptr; - // outline AND shadow depend on NO TextRelief (see dialog) - const bool bHasTextRelief(TEXT_RELIEF_NONE != getTextRelief()); - const bool bHasShadow(!bHasTextRelief && getShadow()); - const bool bHasOutline(!bHasTextRelief && getFontAttribute().getOutline()); - - if(bHasShadow || bHasTextRelief || bHasOutline) + if(hasShadow() || hasTextRelief() || hasOutline()) { Primitive2DReference aShadow; - if(bHasShadow) + if(hasShadow()) { // create shadow with current content (in aRetval). Text shadow // is constant, relative to font size, rotated with the text and has a @@ -364,7 +379,7 @@ namespace drawinglayer::primitive2d Primitive2DContainer(aRetval)); } - if(bHasTextRelief) + if(hasTextRelief()) { // create emboss using an own helper primitive since this will // be view-dependent @@ -412,7 +427,7 @@ namespace drawinglayer::primitive2d aTextEffectStyle2D)) }; } - else if(bHasOutline) + else if(hasOutline()) { // create outline using an own helper primitive since this will // be view-dependent @@ -490,6 +505,25 @@ namespace drawinglayer::primitive2d { } + bool TextDecoratedPortionPrimitive2D::hasTextRelief() const + { + return TEXT_RELIEF_NONE != getTextRelief(); + } + + bool TextDecoratedPortionPrimitive2D::hasShadow() const + { + // not allowed with TextRelief, else defined in FontAttributes + return !hasTextRelief() && getShadow(); + } + + bool TextDecoratedPortionPrimitive2D::hasTextDecoration() const + { + return TEXT_LINE_NONE != getFontOverline() + || TEXT_LINE_NONE != getFontUnderline() + || TEXT_STRIKEOUT_NONE != getTextStrikeout() + || TEXT_FONT_EMPHASIS_MARK_NONE != getTextEmphasisMark(); + } + bool TextDecoratedPortionPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const { if(TextSimplePortionPrimitive2D::operator==(rPrimitive)) diff --git a/drawinglayer/source/primitive2d/textprimitive2d.cxx b/drawinglayer/source/primitive2d/textprimitive2d.cxx index ec2d18a79204..db9a6359ccf2 100644 --- a/drawinglayer/source/primitive2d/textprimitive2d.cxx +++ b/drawinglayer/source/primitive2d/textprimitive2d.cxx @@ -231,6 +231,30 @@ bool LocalesAreEqual(const css::lang::Locale& rA, const css::lang::Locale& rB) return (rA.Language == rB.Language && rA.Country == rB.Country && rA.Variant == rB.Variant); } +bool TextSimplePortionPrimitive2D::hasTextRelief() const +{ + // not possible for TextSimplePortionPrimitive2D + return false; +} + +bool TextSimplePortionPrimitive2D::hasShadow() const +{ + // not possible for TextSimplePortionPrimitive2D + return false; +} + +bool TextSimplePortionPrimitive2D::hasTextDecoration() const +{ + // not possible for TextSimplePortionPrimitive2D + return false; +} + +bool TextSimplePortionPrimitive2D::hasOutline() const +{ + // not allowed with TextRelief, else defined in FontAttributes + return !hasTextRelief() && getFontAttribute().getOutline(); +} + bool TextSimplePortionPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const { if (BufferedDecompositionPrimitive2D::operator==(rPrimitive)) diff --git a/drawinglayer/source/processor2d/cairopixelprocessor2d.cxx b/drawinglayer/source/processor2d/cairopixelprocessor2d.cxx index a212ac270906..083f6ab51cde 100644 --- a/drawinglayer/source/processor2d/cairopixelprocessor2d.cxx +++ b/drawinglayer/source/processor2d/cairopixelprocessor2d.cxx @@ -3030,38 +3030,14 @@ void CairoPixelProcessor2D::renderTextSimpleOrDecoratedPortionPrimitive2D( pSalLayout->GetTextWidth()); } - // prepare flags that are only needed if Text is Decorated - bool bHasTextRelief(false); - bool bHasShadow(false); - bool bHasTextDecoration(false); - - if (nullptr != pDecoratedCandidate) - { - // outline AND shadow depend on NO TextRelief (see dialog) - bHasTextRelief = primitive2d::TEXT_RELIEF_NONE != pDecoratedCandidate->getTextRelief(); - bHasShadow = !bHasTextRelief && pDecoratedCandidate->getShadow(); - - // check if TextDecoration is needed - bHasTextDecoration - = primitive2d::TEXT_LINE_NONE != pDecoratedCandidate->getFontOverline() - || primitive2d::TEXT_LINE_NONE != pDecoratedCandidate->getFontUnderline() - || primitive2d::TEXT_STRIKEOUT_NONE != pDecoratedCandidate->getTextStrikeout() - || primitive2d::TEXT_FONT_EMPHASIS_MARK_NONE - != pDecoratedCandidate->getTextEmphasisMark(); - } - - // prepare flags for non-decorated after that, these *might* be - // dependent on flags above - bool bHasOutline(!bHasTextRelief && rTextCandidate.getFontAttribute().getOutline()); - - if (bHasShadow) + if (rTextCandidate.hasShadow()) { // Text shadow is constant, relative to font size, *not* rotated with // text (always from top-left!) static const double fFactor(1.0 / 24.0); const double fTextShadowOffset(aDecTrans.getScale().getY() * fFactor); - // see OutputDevice::ImplDrawSpecialText -> no longer simple fixed color + // see ::ImplDrawSpecialText -> no longer simple fixed color const basegfx::BColor aBlack(0.0, 0.0, 0.0); basegfx::BColor aShadowColor(aBlack); if (aBlack == rTextCandidate.getFontColor() @@ -3084,7 +3060,7 @@ void CairoPixelProcessor2D::renderTextSimpleOrDecoratedPortionPrimitive2D( renderSalLayout(pSalLayout, aShadowColor, aShadowFullTextTransform, getViewInformation2D().getUseAntiAliasing()); - if (bHasTextDecoration) + if (rTextCandidate.hasTextDecoration()) { const basegfx::B2DHomMatrix aTransform(getViewInformation2D().getObjectTransformation() * aShadowTransform); @@ -3092,16 +3068,13 @@ void CairoPixelProcessor2D::renderTextSimpleOrDecoratedPortionPrimitive2D( &aTransform, &aShadowColor); } } + // get TextColor early, may have to be modified + basegfx::BColor aTextColor(rTextCandidate.getFontColor()); - if (bHasTextRelief) - { - // todo - } - else if (bHasOutline) + if (rTextCandidate.hasOutline()) { // render as outline - basegfx::BColor aTextColor( - maBColorModifierStack.getModifiedColor(rTextCandidate.getFontColor())); + aTextColor = maBColorModifierStack.getModifiedColor(aTextColor); basegfx::B2DHomMatrix aInvViewTransform; // discrete offsets defined here to easily allow to change them, @@ -3117,7 +3090,7 @@ void CairoPixelProcessor2D::renderTextSimpleOrDecoratedPortionPrimitive2D( std::pair{ fZero, fPlus }, std::pair{ fPlus, fPlus } }; - if (bHasTextDecoration) + if (rTextCandidate.hasTextDecoration()) { // to use discrete offset (pixels) we will need the back-transform from // discrete view coordinates to 'world' coordinates (logic view coordinates), @@ -3137,7 +3110,7 @@ void CairoPixelProcessor2D::renderTextSimpleOrDecoratedPortionPrimitive2D( basegfx::utils::createTranslateB2DHomMatrix(offset.first, offset.second)); renderSalLayout(pSalLayout, aTextColor, aDiscreteOffset * aFullTextTransform, getViewInformation2D().getUseAntiAliasing()); - if (bHasTextDecoration) + if (rTextCandidate.hasTextDecoration()) { const basegfx::B2DHomMatrix aTransform( aInvViewTransform * aDiscreteOffset @@ -3151,27 +3124,64 @@ void CairoPixelProcessor2D::renderTextSimpleOrDecoratedPortionPrimitive2D( aTextColor = maBColorModifierStack.getModifiedColor(COL_WHITE.getBColor()); renderSalLayout(pSalLayout, aTextColor, aFullTextTransform, getViewInformation2D().getUseAntiAliasing()); - if (bHasTextDecoration) + if (rTextCandidate.hasTextDecoration()) { renderTextDecorationWithOptionalTransformAndColor(*pDecoratedCandidate, aDecTrans, nullptr, &aTextColor); } + + // paint is complete, Outline and TextRelief cannot be combined, return + return; } - else + + if (rTextCandidate.hasTextRelief()) { - // render text - const basegfx::BColor aTextColor( - maBColorModifierStack.getModifiedColor(rTextCandidate.getFontColor())); - renderSalLayout(pSalLayout, aTextColor, aFullTextTransform, + // manipulate TextColor for final text paint below (see ::ImplDrawSpecialText) + if (aTextColor == COL_BLACK.getBColor()) + aTextColor = COL_WHITE.getBColor(); + + // relief offset defined here to easily allow to change them + // see ::ImplDrawSpecialText and the coment @ 'nOff += mnDPIX/300' + const bool bEmboss(primitive2d::TEXT_RELIEF_EMBOSSED + == pDecoratedCandidate->getTextRelief()); + constexpr double fReliefOffset(1.1); + const double fOffset(bEmboss ? fReliefOffset : -fReliefOffset); + const basegfx::B2DHomMatrix aDiscreteOffset( + basegfx::utils::createTranslateB2DHomMatrix(fOffset, fOffset)); + + // see aReliefColor in ::ImplDrawSpecialText + basegfx::BColor aReliefColor(COL_LIGHTGRAY.getBColor()); + if (COL_WHITE.getBColor() == aTextColor) + aReliefColor = COL_BLACK.getBColor(); + aReliefColor = maBColorModifierStack.getModifiedColor(aReliefColor); + + // render relief text with offset + renderSalLayout(pSalLayout, aReliefColor, aDiscreteOffset * aFullTextTransform, getViewInformation2D().getUseAntiAliasing()); - if (bHasTextDecoration) + if (rTextCandidate.hasTextDecoration()) { - // render using same geometry/primitives that a decompose would - // create -> safe to get the same visualization for both - renderTextDecorationWithOptionalTransformAndColor(*pDecoratedCandidate, aDecTrans); + basegfx::B2DHomMatrix aInvViewTransform(getViewInformation2D().getViewTransformation()); + aInvViewTransform.invert(); + const basegfx::B2DHomMatrix aTransform( + aInvViewTransform * aDiscreteOffset + * getViewInformation2D().getObjectToViewTransformation()); + renderTextDecorationWithOptionalTransformAndColor(*pDecoratedCandidate, aDecTrans, + &aTransform, &aReliefColor); } } + + // render text + aTextColor = maBColorModifierStack.getModifiedColor(aTextColor); + renderSalLayout(pSalLayout, aTextColor, aFullTextTransform, + getViewInformation2D().getUseAntiAliasing()); + + if (rTextCandidate.hasTextDecoration()) + { + // render using same geometry/primitives that a decompose would + // create -> safe to get the same visualization for both + renderTextDecorationWithOptionalTransformAndColor(*pDecoratedCandidate, aDecTrans); + } } void CairoPixelProcessor2D::processBasePrimitive2D(const primitive2d::BasePrimitive2D& rCandidate) -- cgit