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());
                 }
             }
 

Reply via email to