drawinglayer/source/primitive2d/textdecoratedprimitive2d.cxx | 137 +++-- drawinglayer/source/processor2d/cairopixelprocessor2d.cxx | 251 +++++++--- include/drawinglayer/primitive2d/textdecoratedprimitive2d.hxx | 13 include/drawinglayer/processor2d/cairopixelprocessor2d.hxx | 29 + svx/source/sdr/properties/textproperties.cxx | 16 svx/source/svdraw/svdotextdecomposition.cxx | 34 - 6 files changed, 341 insertions(+), 139 deletions(-)
New commits: commit 4c1aa38025d79400e0aa539fba2ba8c3f51316f5 Author: Armin Le Grand (Collabora) <[email protected]> AuthorDate: Fri Aug 9 12:53:47 2024 +0200 Commit: Armin Le Grand <[email protected]> CommitDate: Fri Aug 9 21:18:42 2024 +0200 CairoSDPR: Support TextDecoration Change-Id: I923069582bb7c5022cfbff7e869c1d577a9123ce Reviewed-on: https://gerrit.libreoffice.org/c/core/+/171691 Reviewed-by: Armin Le Grand <[email protected]> Tested-by: Jenkins diff --git a/drawinglayer/source/primitive2d/textdecoratedprimitive2d.cxx b/drawinglayer/source/primitive2d/textdecoratedprimitive2d.cxx index 125b1e735cc8..41a09c5968ea 100644 --- a/drawinglayer/source/primitive2d/textdecoratedprimitive2d.cxx +++ b/drawinglayer/source/primitive2d/textdecoratedprimitive2d.cxx @@ -52,13 +52,19 @@ namespace drawinglayer::primitive2d getLocale(), getFontColor())); - CreateDecorationGeometryContent(rTarget, rDecTrans, rText, - nTextPosition, nTextLength, - rDXArray); + // create and add decoration + const Primitive2DContainer& rDecorationGeometryContent( + getOrCreateDecorationGeometryContent( + rDecTrans, + rText, + nTextPosition, + nTextLength, + rDXArray)); + + rTarget.insert(rTarget.end(), rDecorationGeometryContent.begin(), rDecorationGeometryContent.end()); } - void TextDecoratedPortionPrimitive2D::CreateDecorationGeometryContent( - Primitive2DContainer& rTarget, + const Primitive2DContainer& TextDecoratedPortionPrimitive2D::getOrCreateDecorationGeometryContent( basegfx::utils::B2DHomMatrixBufferedOnDemandDecompose const & rDecTrans, const OUString& rText, sal_Int32 nTextPosition, @@ -71,7 +77,16 @@ namespace drawinglayer::primitive2d const bool bStrikeoutUsed(TEXT_STRIKEOUT_NONE != getTextStrikeout()); if(!(bUnderlineUsed || bStrikeoutUsed || bOverlineUsed)) - return; + { + // not used, return empty Primitive2DContainer + return maBufferedDecorationGeometry; + } + + if (!maBufferedDecorationGeometry.empty()) + { + // if not empty it is used -> append and return Primitive2DContainer + return maBufferedDecorationGeometry; + } // common preparations TextLayouterDevice aTextLayouter; @@ -107,7 +122,7 @@ namespace drawinglayer::primitive2d if(bOverlineUsed) { // create primitive geometry for overline - rTarget.push_back( + maBufferedDecorationGeometry.push_back( new TextLinePrimitive2D( rDecTrans.getB2DHomMatrix(), fTextWidth, @@ -120,7 +135,7 @@ namespace drawinglayer::primitive2d if(bUnderlineUsed) { // create primitive geometry for underline - rTarget.push_back( + maBufferedDecorationGeometry.push_back( new TextLinePrimitive2D( rDecTrans.getB2DHomMatrix(), fTextWidth, @@ -130,60 +145,73 @@ namespace drawinglayer::primitive2d getTextlineColor())); } - if(!bStrikeoutUsed) - return; - - // create primitive geometry for strikeout - if(TEXT_STRIKEOUT_SLASH == getTextStrikeout() || TEXT_STRIKEOUT_X == getTextStrikeout()) + if(bStrikeoutUsed) { - // strikeout with character - const sal_Unicode aStrikeoutChar(TEXT_STRIKEOUT_SLASH == getTextStrikeout() ? '/' : 'X'); + // create primitive geometry for strikeout + if(TEXT_STRIKEOUT_SLASH == getTextStrikeout() || TEXT_STRIKEOUT_X == getTextStrikeout()) + { + // strikeout with character + const sal_Unicode aStrikeoutChar(TEXT_STRIKEOUT_SLASH == getTextStrikeout() ? '/' : 'X'); + + maBufferedDecorationGeometry.push_back( + new TextCharacterStrikeoutPrimitive2D( + rDecTrans.getB2DHomMatrix(), + fTextWidth, + getFontColor(), + aStrikeoutChar, + getFontAttribute(), + getLocale())); + } + else + { + // strikeout with geometry + maBufferedDecorationGeometry.push_back( + new TextGeometryStrikeoutPrimitive2D( + rDecTrans.getB2DHomMatrix(), + fTextWidth, + getFontColor(), + aTextLayouter.getUnderlineHeight(), + aTextLayouter.getStrikeoutOffset(), + getTextStrikeout())); + } + } - rTarget.push_back( - new TextCharacterStrikeoutPrimitive2D( - rDecTrans.getB2DHomMatrix(), - fTextWidth, - getFontColor(), - aStrikeoutChar, - getFontAttribute(), - getLocale())); + // TODO: Handle Font Emphasis Above/Below + + // append local result and return + return maBufferedDecorationGeometry; + } + + const Primitive2DContainer& TextDecoratedPortionPrimitive2D::getOrCreateBrokenUpText() const + { + if(!getWordLineMode()) + { + // return empty Primitive2DContainer + return maBufferedBrokenUpText; } - else + + if (!maBufferedBrokenUpText.empty()) { - // strikeout with geometry - rTarget.push_back( - new TextGeometryStrikeoutPrimitive2D( - rDecTrans.getB2DHomMatrix(), - fTextWidth, - getFontColor(), - aTextLayouter.getUnderlineHeight(), - aTextLayouter.getStrikeoutOffset(), - getTextStrikeout())); + // if not empty it is used -> return Primitive2DContainer + return maBufferedBrokenUpText; } - // TODO: Handle Font Emphasis Above/Below + // support for single word mode; split to single word primitives + // using TextBreakupHelper + TextBreakupHelper aTextBreakupHelper(*this); + maBufferedBrokenUpText = aTextBreakupHelper.extractResult(BreakupUnit::Word); + return maBufferedBrokenUpText; } Primitive2DReference TextDecoratedPortionPrimitive2D::create2DDecomposition(const geometry::ViewInformation2D& /*rViewInformation*/) const { - if(getWordLineMode()) + if (!getOrCreateBrokenUpText().empty()) { - // support for single word mode; split to single word primitives - // using TextBreakupHelper - TextBreakupHelper aTextBreakupHelper(*this); - Primitive2DContainer aBroken(aTextBreakupHelper.extractResult(BreakupUnit::Word)); - - if(!aBroken.empty()) - { - // was indeed split to several words, use as result - return new GroupPrimitive2D(std::move(aBroken)); - } - else - { - // no split, was already a single word. Continue to - // decompose local entity - } + // if BrokenUpText/WordLineMode is used, go into recursion + Primitive2DContainer aContent(getOrCreateBrokenUpText()); + return new GroupPrimitive2D(std::move(aContent)); } + basegfx::utils::B2DHomMatrixBufferedOnDemandDecompose aDecTrans(getTextTransform()); Primitive2DContainer aRetval; @@ -226,7 +254,12 @@ namespace drawinglayer::primitive2d // shadow parameter values static const double fFactor(1.0 / 24.0); const double fTextShadowOffset(aDecTrans.getScale().getY() * fFactor); - static basegfx::BColor aShadowColor(0.3, 0.3, 0.3); + + // see OutputDevice::ImplDrawSpecialText -> no longer simple fixed color + const basegfx::BColor aBlack(0.0, 0.0, 0.0); + basegfx::BColor aShadowColor(aBlack); + if (aBlack == getFontColor() || getFontColor().luminance() < (8.0 / 255.0)) + aShadowColor = COL_LIGHTGRAY.getBColor(); // prepare shadow transform matrix const basegfx::B2DHomMatrix aShadowTransform(basegfx::utils::createTranslateB2DHomMatrix( @@ -349,6 +382,8 @@ namespace drawinglayer::primitive2d rLocale, rFontColor, rFillColor), + maBufferedBrokenUpText(), + maBufferedDecorationGeometry(), maOverlineColor(rOverlineColor), maTextlineColor(rTextlineColor), meFontOverline(eFontOverline), diff --git a/drawinglayer/source/processor2d/cairopixelprocessor2d.cxx b/drawinglayer/source/processor2d/cairopixelprocessor2d.cxx index a586726f5cf9..34cf345c136e 100644 --- a/drawinglayer/source/processor2d/cairopixelprocessor2d.cxx +++ b/drawinglayer/source/processor2d/cairopixelprocessor2d.cxx @@ -42,6 +42,7 @@ #include <drawinglayer/primitive2d/BitmapAlphaPrimitive2D.hxx> #include <drawinglayer/primitive2d/textprimitive2d.hxx> #include <drawinglayer/primitive2d/textdecoratedprimitive2d.hxx> +#include <drawinglayer/primitive2d/shadowprimitive2d.hxx> #include <drawinglayer/converters.hxx> #include <drawinglayer/primitive2d/textlayoutdevice.hxx> #include <basegfx/curve/b2dcubicbezier.hxx> @@ -2857,7 +2858,7 @@ void CairoPixelProcessor2D::processPolyPolygonAlphaGradientPrimitive2D( aFillGradient, &rAlphaGradient)) })) }; - // render this + // render this. Use container to not trigger decompose for temporary content process(aContainerMaskedFillGradient); } @@ -2882,7 +2883,7 @@ void CairoPixelProcessor2D::processTextSimplePortionPrimitive2D( { if (SAL_LIKELY(mbRenderSimpleTextDirect)) { - renderTextSimpleOrDecoratedPortionPrimitive2D(rCandidate); + renderTextSimpleOrDecoratedPortionPrimitive2D(rCandidate, nullptr); } else { @@ -2891,11 +2892,19 @@ void CairoPixelProcessor2D::processTextSimplePortionPrimitive2D( } void CairoPixelProcessor2D::processTextDecoratedPortionPrimitive2D( - const primitive2d::TextSimplePortionPrimitive2D& rCandidate) + const primitive2d::TextDecoratedPortionPrimitive2D& rCandidate) { if (SAL_LIKELY(mbRenderDecoratedTextDirect)) { - renderTextSimpleOrDecoratedPortionPrimitive2D(rCandidate); + if (!rCandidate.getOrCreateBrokenUpText().empty()) + { + // if BrokenUpText/WordLineMode is used, go into recursion + // with single snippets + process(rCandidate.getOrCreateBrokenUpText()); + return; + } + + renderTextSimpleOrDecoratedPortionPrimitive2D(rCandidate, &rCandidate); } else { @@ -2903,57 +2912,95 @@ void CairoPixelProcessor2D::processTextDecoratedPortionPrimitive2D( } } -void CairoPixelProcessor2D::renderTextSimpleOrDecoratedPortionPrimitive2D( - const primitive2d::TextSimplePortionPrimitive2D& rTextCandidate) +void CairoPixelProcessor2D::renderTextBackground( + const primitive2d::TextSimplePortionPrimitive2D& rTextCandidate, + const primitive2d::TextLayouterDevice& rTextLayouter, const basegfx::B2DHomMatrix& rTransform, + double fTextWidth) { - // decompose matrix to have global position and scale of text - const basegfx::B2DHomMatrix aGlobalFontTransform( - getViewInformation2D().getObjectToViewTransformation() * rTextCandidate.getTextTransform()); - basegfx::B2DVector aGlobalFontScaling, aGlobalFontTranslate; - double fGlobalFontRotate, fGlobalFontShearX; - aGlobalFontTransform.decompose(aGlobalFontScaling, aGlobalFontTranslate, fGlobalFontRotate, - fGlobalFontShearX); + cairo_save(mpRT); + cairo_matrix_t aMatrix; + cairo_matrix_init(&aMatrix, rTransform.a(), rTransform.b(), rTransform.c(), rTransform.d(), + rTransform.e(), rTransform.f()); + cairo_set_matrix(mpRT, &aMatrix); + const basegfx::BColor aFillColor( + maBColorModifierStack.getModifiedColor(rTextCandidate.getTextFillColor().getBColor())); + cairo_set_source_rgb(mpRT, aFillColor.getRed(), aFillColor.getGreen(), aFillColor.getBlue()); + cairo_rectangle(mpRT, 0.0, -rTextLayouter.getFontAscent(), fTextWidth, + rTextLayouter.getTextHeight()); + cairo_fill(mpRT); + cairo_restore(mpRT); +} - // decompose primitive-local matrix to get local font scaling - double fLocalFontRotate, fLocalFontShearX; - basegfx::B2DVector aLocalFontSize, aLocalFontTranslate; - rTextCandidate.getTextTransform().decompose(aLocalFontSize, aLocalFontTranslate, - fLocalFontRotate, fLocalFontShearX); +void CairoPixelProcessor2D::renderSalLayout(const std::unique_ptr<SalLayout>& rSalLayout, + const basegfx::BColor& rTextColor, + const basegfx::B2DHomMatrix& rTransform, + bool bAntiAliase) +{ + cairo_save(mpRT); + cairo_matrix_t aMatrix; + cairo_matrix_init(&aMatrix, rTransform.a(), rTransform.b(), rTransform.c(), rTransform.d(), + rTransform.e(), rTransform.f()); + cairo_set_matrix(mpRT, &aMatrix); + rSalLayout->drawSalLayout(mpRT, rTextColor, bAntiAliase); + cairo_restore(mpRT); +} - // Get the VCL font from existing processor tooling. Do not use - // rotation, for Cairo we can transform the whole text render and - // thus handle the text in its local coordinate system untransformed - vcl::Font aFont(primitive2d::getVclFontFromFontAttribute( - rTextCandidate.getFontAttribute(), aGlobalFontScaling.getX(), aGlobalFontScaling.getY(), - 0.0, rTextCandidate.getLocale())); +void CairoPixelProcessor2D::renderShadowTextDecoration( + const basegfx::BColor& rShadowColor, const basegfx::B2DHomMatrix& rShadowObjectTransform, + const primitive2d::TextDecoratedPortionPrimitive2D& rDecoratedCandidate, + const basegfx::utils::B2DHomMatrixBufferedOnDemandDecompose& rDecTrans) +{ + // modify ColorStack as needed + maBColorModifierStack.push(std::make_shared<basegfx::BColorModifier_replace>(rShadowColor)); - if (aFont.GetFontSize().Height() <= 0) - { - // Don't draw fonts without height, error. use decompose as fallback - process(rTextCandidate); - return; - } + // modify transformation as needed + const geometry::ViewInformation2D aLastViewInformation2D(getViewInformation2D()); + geometry::ViewInformation2D aViewInformation2D(getViewInformation2D()); + aViewInformation2D.setObjectTransformation(rShadowObjectTransform); + updateViewInformation(aViewInformation2D); - // create integer DXArray. As mentioned above we can act in the - // Text's local coordinate system without transformation at all - const ::std::vector<double>& rDXArray(rTextCandidate.getDXArray()); - KernArray aDXArray; + // render same primitives as for non-shadow, but with mods set above + renderTextDecoration(rDecoratedCandidate, rDecTrans); - if (!rDXArray.empty()) + // restore mods + updateViewInformation(aLastViewInformation2D); + maBColorModifierStack.pop(); +} + +void CairoPixelProcessor2D::renderTextDecoration( + const primitive2d::TextDecoratedPortionPrimitive2D& rDecoratedCandidate, + const basegfx::utils::B2DHomMatrixBufferedOnDemandDecompose& rDecTrans) +{ + // get decorations from Primitive, guaranteed the same as + // a decomposition would create + const primitive2d::Primitive2DContainer& rDecorationGeometryContent( + rDecoratedCandidate.getOrCreateDecorationGeometryContent( + rDecTrans, rDecoratedCandidate.getText(), rDecoratedCandidate.getTextPosition(), + rDecoratedCandidate.getTextLength(), rDecoratedCandidate.getDXArray())); + + if (rDecorationGeometryContent.empty()) { - aDXArray.reserve(rDXArray.size()); - for (auto const& elem : rDXArray) - aDXArray.push_back(basegfx::fround(elem)); + // no decoration, done + return; } - // set parameters and paint text snippet - const basegfx::BColor aRGBFontColor( - maBColorModifierStack.getModifiedColor(rTextCandidate.getFontColor())); + process(rDecorationGeometryContent); +} + +void CairoPixelProcessor2D::renderTextSimpleOrDecoratedPortionPrimitive2D( + const primitive2d::TextSimplePortionPrimitive2D& rTextCandidate, + const primitive2d::TextDecoratedPortionPrimitive2D* pDecoratedCandidate) +{ + // decompose primitive-local matrix to get local font scaling + const basegfx::utils::B2DHomMatrixBufferedOnDemandDecompose aDecTrans( + rTextCandidate.getTextTransform()); // create a TextLayouter to access encapsulated VCL Text/Font related tooling primitive2d::TextLayouterDevice aTextLayouter; - aTextLayouter.setFontAttribute(rTextCandidate.getFontAttribute(), aLocalFontSize.getX(), - aLocalFontSize.getY(), rTextCandidate.getLocale()); + aTextLayouter.setFontAttribute(rTextCandidate.getFontAttribute(), aDecTrans.getScale().getX(), + aDecTrans.getScale().getY(), rTextCandidate.getLocale()); + const basegfx::BColor aRGBFontColor( + maBColorModifierStack.getModifiedColor(rTextCandidate.getFontColor())); aTextLayouter.setTextColor(aRGBFontColor); if (rTextCandidate.getFontAttribute().getRTL()) @@ -2973,6 +3020,18 @@ void CairoPixelProcessor2D::renderTextSimpleOrDecoratedPortionPrimitive2D( aTextLayouter.setLayoutMode(nLTRLayoutMode); } + // create integer DXArray. As mentioned above we can act in the + // Text's local coordinate system without transformation at all + const ::std::vector<double>& rDXArray(rTextCandidate.getDXArray()); + KernArray aDXArray; + + if (!rDXArray.empty()) + { + aDXArray.reserve(rDXArray.size()); + for (auto const& elem : rDXArray) + aDXArray.push_back(basegfx::fround(elem)); + } + // create SalLayout. No need for a position, as mentioned text can work // without transformations, so start point is always 0,0 std::unique_ptr<SalLayout> pSalLayout(aTextLayouter.getSalLayout( @@ -2986,35 +3045,97 @@ void CairoPixelProcessor2D::renderTextSimpleOrDecoratedPortionPrimitive2D( return; } - // draw using Cairo, use existing tooling (this tunnels to - // CairoTextRender::ImplDrawTextLayout) - cairo_save(mpRT); + // prepare local transformations const basegfx::B2DHomMatrix aObjTransformWithoutScale( - basegfx::utils::createShearXRotateTranslateB2DHomMatrix(fLocalFontShearX, fLocalFontRotate, - aLocalFontTranslate)); + basegfx::utils::createShearXRotateTranslateB2DHomMatrix( + aDecTrans.getShearX(), aDecTrans.getRotate(), aDecTrans.getTranslate())); const basegfx::B2DHomMatrix aFullTextTransform( getViewInformation2D().getObjectToViewTransformation() * aObjTransformWithoutScale); - cairo_matrix_t aMatrix; - cairo_matrix_init(&aMatrix, aFullTextTransform.a(), aFullTextTransform.b(), - aFullTextTransform.c(), aFullTextTransform.d(), aFullTextTransform.e(), - aFullTextTransform.f()); - cairo_set_matrix(mpRT, &aMatrix); - if (!rTextCandidate.getTextFillColor().IsTransparent()) { - // TextFillColor is set -> text background is filled, paint it - const basegfx::BColor aFillColor( - maBColorModifierStack.getModifiedColor(rTextCandidate.getTextFillColor().getBColor())); - cairo_set_source_rgb(mpRT, aFillColor.getRed(), aFillColor.getGreen(), - aFillColor.getBlue()); - cairo_rectangle(mpRT, 0.0, -aTextLayouter.getFontAscent(), pSalLayout->GetTextWidth(), - aTextLayouter.getTextHeight()); - cairo_fill(mpRT); + // render TextBackground first -> casts no shadow itself, so do independent of + // text shadow being activated + renderTextBackground(rTextCandidate, aTextLayouter, aFullTextTransform, + pSalLayout->GetTextWidth()); } - pSalLayout->drawSalLayout(mpRT, aRGBFontColor, getViewInformation2D().getUseAntiAliasing()); - cairo_restore(mpRT); + // basegfx::utils::B2DHomMatrixBufferedOnDemandDecompose aDecTrans(rTextCandidate.getTextTransform()); + bool bHasTextRelief(false); + bool bHasShadow(false); + bool bHasOutline(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(); + bHasOutline = !bHasTextRelief && pDecoratedCandidate->getFontAttribute().getOutline(); + + // check if TextDecoration is needed + bHasTextDecoration + = primitive2d::TEXT_LINE_NONE != pDecoratedCandidate->getFontOverline() + || primitive2d::TEXT_LINE_NONE != pDecoratedCandidate->getFontUnderline() + || primitive2d::TEXT_STRIKEOUT_NONE != pDecoratedCandidate->getTextStrikeout(); + } + + if (bHasShadow) + { + // 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 + const basegfx::BColor aBlack(0.0, 0.0, 0.0); + basegfx::BColor aShadowColor(aBlack); + if (aBlack == rTextCandidate.getFontColor() + || rTextCandidate.getFontColor().luminance() < (8.0 / 255.0)) + aShadowColor = COL_LIGHTGRAY.getBColor(); + + // create shadow offset + const basegfx::B2DHomMatrix aShadowTransform( + basegfx::utils::createTranslateB2DHomMatrix(fTextShadowOffset, fTextShadowOffset)); + const basegfx::B2DHomMatrix aShadowFullTextTransform( + // right to left: 1st the ObjTrans, then the shadow offset, last ObjToView. That way + // the shadow is always from top-left, independent of text rotation. Independent from + // thinking about if that is wanted (shadow direction *could* rotate with the text) + // this is what the office curently does -> do *not* change visualization (!) + getViewInformation2D().getObjectToViewTransformation() * aShadowTransform + * aObjTransformWithoutScale); + + // render text as shadow + renderSalLayout(pSalLayout, aShadowColor, aShadowFullTextTransform, + getViewInformation2D().getUseAntiAliasing()); + + if (bHasTextDecoration) + { + // this renders the same as renderTextDecoration, but encapsulated + // in temporary ColorStack & transformation modifications + const basegfx::B2DHomMatrix aTransform(getViewInformation2D().getObjectTransformation() + * aShadowTransform); + renderShadowTextDecoration(aShadowColor, aTransform, *pDecoratedCandidate, aDecTrans); + } + } + + if (bHasOutline) + { + // todo + renderSalLayout(pSalLayout, aRGBFontColor, aFullTextTransform, + getViewInformation2D().getUseAntiAliasing()); + } + + // render text + renderSalLayout(pSalLayout, aRGBFontColor, aFullTextTransform, + getViewInformation2D().getUseAntiAliasing()); + + if (bHasTextDecoration) + { + // render using same geometry/primitives that a decompose would + // create -> safe to get the same visualization for both + renderTextDecoration(*pDecoratedCandidate, aDecTrans); + } } void CairoPixelProcessor2D::processBasePrimitive2D(const primitive2d::BasePrimitive2D& rCandidate) @@ -3160,7 +3281,7 @@ void CairoPixelProcessor2D::processBasePrimitive2D(const primitive2d::BasePrimit case PRIMITIVE2D_ID_TEXTDECORATEDPORTIONPRIMITIVE2D: { processTextDecoratedPortionPrimitive2D( - static_cast<const primitive2d::TextSimplePortionPrimitive2D&>(rCandidate)); + static_cast<const primitive2d::TextDecoratedPortionPrimitive2D&>(rCandidate)); break; } diff --git a/include/drawinglayer/primitive2d/textdecoratedprimitive2d.hxx b/include/drawinglayer/primitive2d/textdecoratedprimitive2d.hxx index 739f1ed9bc77..73cfb45349ca 100644 --- a/include/drawinglayer/primitive2d/textdecoratedprimitive2d.hxx +++ b/include/drawinglayer/primitive2d/textdecoratedprimitive2d.hxx @@ -40,6 +40,12 @@ namespace drawinglayer::primitive2d class DRAWINGLAYER_DLLPUBLIC TextDecoratedPortionPrimitive2D final : public TextSimplePortionPrimitive2D { private: + /// a sequence used for buffering broken up text for WordLineMode + mutable Primitive2DContainer maBufferedBrokenUpText; + + /// a sequence used for buffering getOrCreateDecorationGeometryContent + mutable Primitive2DContainer maBufferedDecorationGeometry; + /// decoration definitions basegfx::BColor maOverlineColor; basegfx::BColor maTextlineColor; @@ -112,14 +118,17 @@ namespace drawinglayer::primitive2d bool getEmphasisMarkBelow() const { return mbEmphasisMarkBelow; } bool getShadow() const { return mbShadow; } - void CreateDecorationGeometryContent( - Primitive2DContainer& rTarget, + /// helper top create DecorationGeometry as Primitives + const Primitive2DContainer& getOrCreateDecorationGeometryContent( basegfx::utils::B2DHomMatrixBufferedOnDemandDecompose const & rDecTrans, const OUString& rText, sal_Int32 nTextPosition, sal_Int32 nTextLength, const std::vector< double >& rDXArray) const; + /// helper for break-up text if needed + const Primitive2DContainer& getOrCreateBrokenUpText() const; + /// compare operator virtual bool operator==( const BasePrimitive2D& rPrimitive ) const override; diff --git a/include/drawinglayer/processor2d/cairopixelprocessor2d.hxx b/include/drawinglayer/processor2d/cairopixelprocessor2d.hxx index 76b35d3530a0..d850b20aacd6 100644 --- a/include/drawinglayer/processor2d/cairopixelprocessor2d.hxx +++ b/include/drawinglayer/processor2d/cairopixelprocessor2d.hxx @@ -41,6 +41,8 @@ class PolyPolygonRGBAPrimitive2D; class PolyPolygonAlphaGradientPrimitive2D; class BitmapAlphaPrimitive2D; class TextSimplePortionPrimitive2D; +class TextDecoratedPortionPrimitive2D; +class TextLayouterDevice; } namespace basegfx @@ -48,7 +50,13 @@ namespace basegfx class B2DPolyPolygon; } +namespace basegfx::utils +{ +class B2DHomMatrixBufferedOnDemandDecompose; +} + class BitmapEx; +class SalLayout; namespace drawinglayer::processor2d { @@ -108,12 +116,29 @@ class UNLESS_MERGELIBS(DRAWINGLAYER_DLLPUBLIC) CairoPixelProcessor2D final : pub double fTransparency = 0.0); void processBitmapAlphaPrimitive2D( const primitive2d::BitmapAlphaPrimitive2D& rBitmapAlphaPrimitive2D); + void processTextSimplePortionPrimitive2D( const primitive2d::TextSimplePortionPrimitive2D& rCandidate); void processTextDecoratedPortionPrimitive2D( - const primitive2d::TextSimplePortionPrimitive2D& rCandidate); + const primitive2d::TextDecoratedPortionPrimitive2D& rCandidate); + + // helpers for text rendering void renderTextSimpleOrDecoratedPortionPrimitive2D( - const primitive2d::TextSimplePortionPrimitive2D& rTextCandidate); + const primitive2d::TextSimplePortionPrimitive2D& rTextCandidate, + const primitive2d::TextDecoratedPortionPrimitive2D* pDecoratedCandidate); + void renderTextBackground(const primitive2d::TextSimplePortionPrimitive2D& rTextCandidate, + const primitive2d::TextLayouterDevice& rTextLayouter, + const basegfx::B2DHomMatrix& rTransform, double fTextWidth); + void renderSalLayout(const std::unique_ptr<SalLayout>& rSalLayout, + const basegfx::BColor& rTextColor, const basegfx::B2DHomMatrix& rTransform, + bool bAntiAliase); + void renderShadowTextDecoration( + const basegfx::BColor& rShadowColor, const basegfx::B2DHomMatrix& rShadowObjectTransform, + const primitive2d::TextDecoratedPortionPrimitive2D& rDecoratedCandidate, + const basegfx::utils::B2DHomMatrixBufferedOnDemandDecompose& rDecTrans); + void + renderTextDecoration(const primitive2d::TextDecoratedPortionPrimitive2D& rDecoratedCandidate, + const basegfx::utils::B2DHomMatrixBufferedOnDemandDecompose& rDecTrans); /* the local processor for BasePrimitive2D-Implementation based primitives, called from the common process()-implementation diff --git a/svx/source/sdr/properties/textproperties.cxx b/svx/source/sdr/properties/textproperties.cxx index cce8ee75fc37..cefb8895878f 100644 --- a/svx/source/sdr/properties/textproperties.cxx +++ b/svx/source/sdr/properties/textproperties.cxx @@ -395,12 +395,18 @@ namespace sdr::properties // using existing functionality GetObjectItemSet(); // force ItemSet std::vector<const SfxPoolItem*> aChangedItems; - SfxItemIter aIter(*moItemSet); - for (const SfxPoolItem* pItem = aIter.GetCurItem(); pItem; pItem = aIter.NextItem()) - { - if(!IsInvalidItem(pItem)) - aChangedItems.push_back(pItem); + + { // own scope to get SfxItemIter aIter destroyed ASAP - it maybe detected + // as reading source to the ItemSet when Items get changed below, but it + // is no longer active/needed + SfxItemIter aIter(*moItemSet); + for (const SfxPoolItem* pItem = aIter.GetCurItem(); pItem; pItem = aIter.NextItem()) + { + if(!IsInvalidItem(pItem)) + aChangedItems.push_back(pItem); + } } + ItemSetChanged(aChangedItems, 0); // now the standard TextProperties stuff diff --git a/svx/source/svdraw/svdotextdecomposition.cxx b/svx/source/svdraw/svdotextdecomposition.cxx index 21db5c3da4c0..e0068fd159c7 100644 --- a/svx/source/svdraw/svdotextdecomposition.cxx +++ b/svx/source/svdraw/svdotextdecomposition.cxx @@ -342,24 +342,30 @@ namespace const drawinglayer::primitive2d::TextDecoratedPortionPrimitive2D* pPortion = static_cast<const drawinglayer::primitive2d::TextDecoratedPortionPrimitive2D*>(rPortion.get()); - pPortion->CreateDecorationGeometryContent( - aContainer, - pPortion->getTextTransform(), - caseMappedText, - pPortion->getTextPosition(), - pPortion->getTextLength(), - pPortion->getDXArray()); + // create and add decoration + const drawinglayer::primitive2d::Primitive2DContainer& rDecorationGeometryContent( + pPortion->getOrCreateDecorationGeometryContent( + pPortion->getTextTransform(), + caseMappedText, + pPortion->getTextPosition(), + pPortion->getTextLength(), + pPortion->getDXArray())); + + aContainer.insert(aContainer.end(), rDecorationGeometryContent.begin(), rDecorationGeometryContent.end()); } } else { - pTCPP->CreateDecorationGeometryContent( - aContainer, - pTCPP->getTextTransform(), - caseMappedText, - rInfo.mnTextStart, - rInfo.mnTextLen, - aDXArray); + // create and add decoration + const drawinglayer::primitive2d::Primitive2DContainer& rDecorationGeometryContent( + pTCPP->getOrCreateDecorationGeometryContent( + pTCPP->getTextTransform(), + caseMappedText, + rInfo.mnTextStart, + rInfo.mnTextLen, + aDXArray)); + + aContainer.insert(aContainer.end(), rDecorationGeometryContent.begin(), rDecorationGeometryContent.end()); } }
