drawinglayer/source/processor2d/vclmetafileprocessor2d.cxx |    2 
 editeng/inc/outleeng.hxx                                   |    4 
 editeng/source/editeng/StripPortionsHelper.cxx             |  267 ++++++++----
 editeng/source/editeng/editeng.cxx                         |   13 
 editeng/source/editeng/impedit.hxx                         |    5 
 editeng/source/editeng/impedit3.cxx                        |   43 -
 editeng/source/outliner/outleeng.cxx                       |    7 
 editeng/source/outliner/outliner.cxx                       |   23 -
 include/editeng/StripPortionsHelper.hxx                    |   44 +
 include/editeng/editeng.hxx                                |    9 
 include/editeng/outliner.hxx                               |   10 
 include/svx/svdotext.hxx                                   |    9 
 include/svx/svdoutl.hxx                                    |   47 ++
 sc/source/ui/view/tabview3.cxx                             |   28 -
 svx/source/svdraw/svdedxv.cxx                              |    6 
 svx/source/svdraw/svdotextdecomposition.cxx                |  288 +------------
 svx/source/svdraw/svdotextpathdecomposition.cxx            |   23 -
 svx/source/svdraw/svdoutl.cxx                              |  105 ++++
 18 files changed, 476 insertions(+), 457 deletions(-)

New commits:
commit 04bd455e36b054001e08a0a3256d508a009ffef3
Author:     Armin Le Grand (Collabora) <armin.le.gr...@me.com>
AuthorDate: Thu Jun 26 14:18:30 2025 +0200
Commit:     Armin Le Grand <armin.le.gr...@me.com>
CommitDate: Thu Jun 26 23:18:44 2025 +0200

    StripPortions: Move tooling closer to EditEngine/Outliner II
    
    Moved central stuff needed for decompose to Primitives
    closer to EditEngine/Outliner. There is now a
    StripPortionsHelper as abstract base class with two virtual
    methods replacing the lambdas used before. That is easily
    implementable to all needs. Adapted all usages. Also the
    methods for creating needed Primitives from DrawPortionInfo
    or DrawBulletInfo is now local/private in EditEngine.
    
    This step should still change nothing in functionality, but
    is more preparation to be able to directly create Primitives
    from Outliner/EditEngine setups for paint and (re)use.
    
    Change-Id: Ie2bcebf4030128f88be229d789944cc2842eafb6
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/187045
    Tested-by: Jenkins
    Reviewed-by: Armin Le Grand <armin.le.gr...@me.com>

diff --git a/drawinglayer/source/processor2d/vclmetafileprocessor2d.cxx 
b/drawinglayer/source/processor2d/vclmetafileprocessor2d.cxx
index 80436df8cbdd..dc2383c29411 100644
--- a/drawinglayer/source/processor2d/vclmetafileprocessor2d.cxx
+++ b/drawinglayer/source/processor2d/vclmetafileprocessor2d.cxx
@@ -1458,7 +1458,7 @@ void 
VclMetafileProcessor2D::processTextHierarchyBulletPrimitive2D(
 
     // process recursively and add MetaFile comment
     process(rBulletPrimitive);
-    // in Outliner::PaintBullet(), a MetafileComment for bullets is added, 
too. The
+    // in Outliner::PaintOrStripBullet(), a MetafileComment for bullets is 
added, too. The
     // "XTEXT_EOC" is used, use here, too.
     mpMetaFile->AddAction(new MetaCommentAction("XTEXT_EOC"_ostr));
 
diff --git a/editeng/inc/outleeng.hxx b/editeng/inc/outleeng.hxx
index 5415c26810fb..b9def51b9ff0 100644
--- a/editeng/inc/outleeng.hxx
+++ b/editeng/inc/outleeng.hxx
@@ -35,9 +35,7 @@ public:
                         OutlinerEditEng( Outliner* pOwner, SfxItemPool* pPool 
);
                         virtual ~OutlinerEditEng() override;
 
-    virtual void        PaintingFirstLine(sal_Int32 nPara, const Point& 
rStartPos, const Point& rOrigin, Degree10 nOrientation, OutputDevice& rOutDev,
-        const std::function<void(const DrawPortionInfo&)>& rDrawPortion,
-        const std::function<void(const DrawBulletInfo&)>& rDrawBullet) 
override;
+    virtual void        ProcessFirstLineOfParagraph(sal_Int32 nPara, const 
Point& rStartPos, const Point& rOrigin, Degree10 nOrientation, OutputDevice& 
rOutDev, StripPortionsHelper* pStripPortionsHelper) override;
 
     virtual void        ParagraphInserted( sal_Int32 nNewParagraph ) override;
     virtual void        ParagraphDeleted( sal_Int32 nDeletedParagraph ) 
override;
diff --git a/editeng/source/editeng/StripPortionsHelper.cxx 
b/editeng/source/editeng/StripPortionsHelper.cxx
index 2d9312710860..61e9e939ca69 100644
--- a/editeng/source/editeng/StripPortionsHelper.cxx
+++ b/editeng/source/editeng/StripPortionsHelper.cxx
@@ -30,7 +30,7 @@
 #include <drawinglayer/primitive2d/wrongspellprimitive2d.hxx>
 #include <drawinglayer/primitive2d/graphicprimitive2d.hxx>
 
-// anonymous helpers
+// anonymous Outline/EditEngine decompose helpers
 namespace
 {
 rtl::Reference<drawinglayer::primitive2d::BasePrimitive2D>
@@ -83,77 +83,6 @@ 
CheckFieldPrimitive(drawinglayer::primitive2d::BasePrimitive2D* pPrimitive,
     return xRet;
 }
 
-class DoCapitalsDrawPortionInfo : public SvxDoCapitals
-{
-private:
-    drawinglayer::primitive2d::Primitive2DContainer& mrTarget;
-    const basegfx::B2DHomMatrix& mrNewTransformA;
-    const basegfx::B2DHomMatrix& mrNewTransformB;
-    const DrawPortionInfo& m_rInfo;
-    SvxFont m_aFont;
-
-public:
-    DoCapitalsDrawPortionInfo(drawinglayer::primitive2d::Primitive2DContainer& 
rTarget,
-                              const basegfx::B2DHomMatrix& rNewTransformA,
-                              const basegfx::B2DHomMatrix& rNewTransformB,
-                              const DrawPortionInfo& rInfo)
-        : SvxDoCapitals(rInfo.maText, rInfo.mnTextStart, rInfo.mnTextLen)
-        , mrTarget(rTarget)
-        , mrNewTransformA(rNewTransformA)
-        , mrNewTransformB(rNewTransformB)
-        , m_rInfo(rInfo)
-        , m_aFont(rInfo.mrFont)
-    {
-        assert(!m_rInfo.mpDXArray.empty());
-
-        /* turn all these off as they are handled outside subportions for the 
whole portion */
-        m_aFont.SetTransparent(false);
-        m_aFont.SetUnderline(LINESTYLE_NONE);
-        m_aFont.SetOverline(LINESTYLE_NONE);
-        m_aFont.SetStrikeout(STRIKEOUT_NONE);
-
-        m_aFont.SetCaseMap(SvxCaseMap::NotMapped); /* otherwise this would 
call itself */
-    }
-    virtual void Do(const OUString& rSpanTxt, const sal_Int32 nSpanIdx, const 
sal_Int32 nSpanLen,
-                    const bool bUpper) override
-    {
-        sal_uInt8 nProp(0);
-        if (!bUpper)
-        {
-            nProp = m_aFont.GetPropr();
-            m_aFont.SetProprRel(SMALL_CAPS_PERCENTAGE);
-        }
-
-        sal_Int32 nStartOffset = nSpanIdx - nIdx;
-        double nStartX = nStartOffset ? m_rInfo.mpDXArray[nStartOffset - 1] : 
0;
-
-        Point aStartPos(m_rInfo.mrStartPos.X() + nStartX, 
m_rInfo.mrStartPos.Y());
-
-        KernArray aDXArray;
-        aDXArray.resize(nSpanLen);
-        for (sal_Int32 i = 0; i < nSpanLen; ++i)
-            aDXArray[i] = m_rInfo.mpDXArray[nStartOffset + i] - nStartX;
-
-        auto aKashidaArray = !m_rInfo.mpKashidaArray.empty()
-                                 ? std::span<const sal_Bool>(
-                                       m_rInfo.mpKashidaArray.data() + 
nStartOffset, nSpanLen)
-                                 : std::span<const sal_Bool>();
-
-        DrawPortionInfo aInfo(aStartPos, rSpanTxt, nSpanIdx, nSpanLen, 
aDXArray, aKashidaArray,
-                              m_aFont, m_rInfo.mnPara, m_rInfo.mnBiDiLevel,
-                              nullptr, /* no spelling in subportion, handled 
outside */
-                              nullptr, /* no field in subportion, handled 
outside */
-                              false, false, false, m_rInfo.mpLocale, 
m_rInfo.maOverlineColor,
-                              m_rInfo.maTextLineColor);
-
-        CreateTextPortionPrimitivesFromDrawPortionInfo(mrTarget, 
mrNewTransformA, mrNewTransformB,
-                                                       aInfo);
-
-        if (!bUpper)
-            m_aFont.SetPropr(nProp);
-    }
-};
-
 rtl::Reference<drawinglayer::primitive2d::BasePrimitive2D>
 buildTextPortionPrimitive(const DrawPortionInfo& rInfo, const OUString& rText,
                           const drawinglayer::attribute::FontAttribute& 
rFontAttribute,
@@ -287,9 +216,42 @@ buildTextPortionPrimitive(const DrawPortionInfo& rInfo, 
const OUString& rText,
 
     return pNewPrimitive;
 }
-} // end of anonymous namespace
 
-// Outliner helpers
+class DoCapitalsDrawPortionInfo : public SvxDoCapitals
+{
+private:
+    drawinglayer::primitive2d::Primitive2DContainer& mrTarget;
+    const basegfx::B2DHomMatrix& mrNewTransformA;
+    const basegfx::B2DHomMatrix& mrNewTransformB;
+    const DrawPortionInfo& m_rInfo;
+    SvxFont m_aFont;
+
+public:
+    DoCapitalsDrawPortionInfo(drawinglayer::primitive2d::Primitive2DContainer& 
rTarget,
+                              const basegfx::B2DHomMatrix& rNewTransformA,
+                              const basegfx::B2DHomMatrix& rNewTransformB,
+                              const DrawPortionInfo& rInfo)
+        : SvxDoCapitals(rInfo.maText, rInfo.mnTextStart, rInfo.mnTextLen)
+        , mrTarget(rTarget)
+        , mrNewTransformA(rNewTransformA)
+        , mrNewTransformB(rNewTransformB)
+        , m_rInfo(rInfo)
+        , m_aFont(rInfo.mrFont)
+    {
+        assert(!m_rInfo.mpDXArray.empty());
+
+        /* turn all these off as they are handled outside subportions for the 
whole portion */
+        m_aFont.SetTransparent(false);
+        m_aFont.SetUnderline(LINESTYLE_NONE);
+        m_aFont.SetOverline(LINESTYLE_NONE);
+        m_aFont.SetStrikeout(STRIKEOUT_NONE);
+
+        m_aFont.SetCaseMap(SvxCaseMap::NotMapped); /* otherwise this would 
call itself */
+    }
+    virtual void Do(const OUString& rSpanTxt, const sal_Int32 nSpanIdx, const 
sal_Int32 nSpanLen,
+                    const bool bUpper) override;
+};
+
 void CreateTextPortionPrimitivesFromDrawPortionInfo(
     drawinglayer::primitive2d::Primitive2DContainer& rTarget,
     const basegfx::B2DHomMatrix& rNewTransformA, const basegfx::B2DHomMatrix& 
rNewTransformB,
@@ -530,6 +492,44 @@ void CreateTextPortionPrimitivesFromDrawPortionInfo(
     }
 }
 
+void DoCapitalsDrawPortionInfo::Do(const OUString& rSpanTxt, const sal_Int32 
nSpanIdx,
+                                   const sal_Int32 nSpanLen, const bool bUpper)
+{
+    sal_uInt8 nProp(0);
+    if (!bUpper)
+    {
+        nProp = m_aFont.GetPropr();
+        m_aFont.SetProprRel(SMALL_CAPS_PERCENTAGE);
+    }
+
+    sal_Int32 nStartOffset = nSpanIdx - nIdx;
+    double nStartX = nStartOffset ? m_rInfo.mpDXArray[nStartOffset - 1] : 0;
+
+    Point aStartPos(m_rInfo.mrStartPos.X() + nStartX, m_rInfo.mrStartPos.Y());
+
+    KernArray aDXArray;
+    aDXArray.resize(nSpanLen);
+    for (sal_Int32 i = 0; i < nSpanLen; ++i)
+        aDXArray[i] = m_rInfo.mpDXArray[nStartOffset + i] - nStartX;
+
+    auto aKashidaArray
+        = !m_rInfo.mpKashidaArray.empty()
+              ? std::span<const sal_Bool>(m_rInfo.mpKashidaArray.data() + 
nStartOffset, nSpanLen)
+              : std::span<const sal_Bool>();
+
+    DrawPortionInfo aInfo(
+        aStartPos, rSpanTxt, nSpanIdx, nSpanLen, aDXArray, aKashidaArray, 
m_aFont, m_rInfo.mnPara,
+        m_rInfo.mnBiDiLevel, nullptr, /* no spelling in subportion, handled 
outside */
+        nullptr, /* no field in subportion, handled outside */
+        false, false, false, m_rInfo.mpLocale, m_rInfo.maOverlineColor, 
m_rInfo.maTextLineColor);
+
+    CreateTextPortionPrimitivesFromDrawPortionInfo(mrTarget, mrNewTransformA, 
mrNewTransformB,
+                                                   aInfo);
+
+    if (!bUpper)
+        m_aFont.SetPropr(nProp);
+}
+
 void CreateDrawBulletPrimitivesFromDrawBulletInfo(
     drawinglayer::primitive2d::Primitive2DContainer& rTarget,
     const basegfx::B2DHomMatrix& rNewTransformA, const basegfx::B2DHomMatrix& 
rNewTransformB,
@@ -565,5 +565,124 @@ void CreateDrawBulletPrimitivesFromDrawBulletInfo(
     // add to output
     rTarget.push_back(pNewPrimitive);
 }
+} // end of anonymous namespace
+
+void TextHierarchyBreakup::flushTextPortionPrimitivesToLinePrimitives()
+{
+    // only create a line primitive when we had content; there is no need for
+    // empty line primitives (contrary to paragraphs, see below).
+    if (!maTextPortionPrimitives.empty())
+    {
+        maLinePrimitives.push_back(new 
drawinglayer::primitive2d::TextHierarchyLinePrimitive2D(
+            std::move(maTextPortionPrimitives)));
+    }
+}
+
+sal_Int16 TextHierarchyBreakup::getOutlineLevelFromParagraph(sal_Int32 
/*nPara*/) const
+{
+    return -1;
+}
+
+sal_Int32 TextHierarchyBreakup::getParagraphCount() const { return 0; }
+
+void TextHierarchyBreakup::flushLinePrimitivesToParagraphPrimitives(sal_Int32 
nPara)
+{
+    // ALWAYS create a paragraph primitive, even when no content was added. 
This is done to
+    // have the correct paragraph count even with empty paragraphs. Those 
paragraphs will
+    // have an empty sub-PrimitiveSequence.
+    maParagraphPrimitives.push_back(
+        new drawinglayer::primitive2d::TextHierarchyParagraphPrimitive2D(
+            std::move(maLinePrimitives), getOutlineLevelFromParagraph(nPara)));
+}
+
+void TextHierarchyBreakup::processDrawPortionInfo(const DrawPortionInfo& 
rDrawPortionInfo)
+{
+    CreateTextPortionPrimitivesFromDrawPortionInfo(maTextPortionPrimitives, 
maNewTransformA,
+                                                   maNewTransformB, 
rDrawPortionInfo);
+
+    if (rDrawPortionInfo.mbEndOfLine || rDrawPortionInfo.mbEndOfParagraph)
+    {
+        flushTextPortionPrimitivesToLinePrimitives();
+    }
+
+    if (rDrawPortionInfo.mbEndOfParagraph)
+    {
+        flushLinePrimitivesToParagraphPrimitives(rDrawPortionInfo.mnPara);
+    }
+}
+
+void TextHierarchyBreakup::processDrawBulletInfo(const DrawBulletInfo& 
rDrawBulletInfo)
+{
+    CreateDrawBulletPrimitivesFromDrawBulletInfo(maTextPortionPrimitives, 
maNewTransformA,
+                                                 maNewTransformB, 
rDrawBulletInfo);
+    basegfx::B2DHomMatrix aNewTransform;
+
+    // add size to new transform
+    aNewTransform.scale(rDrawBulletInfo.maBulletSize.getWidth(),
+                        rDrawBulletInfo.maBulletSize.getHeight());
+
+    // apply transformA
+    aNewTransform *= maNewTransformA;
+
+    // apply local offset
+    aNewTransform.translate(rDrawBulletInfo.maBulletPosition.X(),
+                            rDrawBulletInfo.maBulletPosition.Y());
+
+    // also apply embedding object's transform
+    aNewTransform *= maNewTransformB;
+
+    // prepare empty GraphicAttr
+    const GraphicAttr aGraphicAttr;
+
+    // create GraphicPrimitive2D
+    const drawinglayer::primitive2d::Primitive2DReference aNewReference(
+        new drawinglayer::primitive2d::GraphicPrimitive2D(
+            aNewTransform, rDrawBulletInfo.maBulletGraphicObject, 
aGraphicAttr));
+
+    // embed in TextHierarchyBulletPrimitive2D
+    drawinglayer::primitive2d::Primitive2DContainer aNewSequence{ 
aNewReference };
+    rtl::Reference<drawinglayer::primitive2d::BasePrimitive2D> pNewPrimitive
+        = new 
drawinglayer::primitive2d::TextHierarchyBulletPrimitive2D(std::move(aNewSequence));
+
+    // add to output
+    maTextPortionPrimitives.push_back(pNewPrimitive);
+}
+
+TextHierarchyBreakup::TextHierarchyBreakup()
+    : maTextPortionPrimitives()
+    , maLinePrimitives()
+    , maParagraphPrimitives()
+    , maNewTransformA()
+    , maNewTransformB()
+{
+}
+
+TextHierarchyBreakup::TextHierarchyBreakup(const basegfx::B2DHomMatrix& 
rNewTransformA,
+                                           const basegfx::B2DHomMatrix& 
rNewTransformB)
+    : maTextPortionPrimitives()
+    , maLinePrimitives()
+    , maParagraphPrimitives()
+    , maNewTransformA(rNewTransformA)
+    , maNewTransformB(rNewTransformB)
+{
+}
+
+const drawinglayer::primitive2d::Primitive2DContainer&
+TextHierarchyBreakup::getTextPortionPrimitives()
+{
+    if (!maTextPortionPrimitives.empty())
+    {
+        // collect non-closed lines
+        flushTextPortionPrimitivesToLinePrimitives();
+    }
+
+    if (!maLinePrimitives.empty())
+    {
+        // collect non-closed paragraphs
+        flushLinePrimitivesToParagraphPrimitives(getParagraphCount() - 1);
+    }
+
+    return maParagraphPrimitives;
+}
 
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/editeng/source/editeng/editeng.cxx 
b/editeng/source/editeng/editeng.cxx
index 8a8a3029d6e1..3ffbecc83842 100644
--- a/editeng/source/editeng/editeng.cxx
+++ b/editeng/source/editeng/editeng.cxx
@@ -1071,13 +1071,8 @@ SvxFont EditEngine::GetStandardSvxFont( sal_Int32 nPara )
     return pNode->GetCharAttribs().GetDefFont();
 }
 
-void EditEngine::StripPortions(
-    const std::function<void(const DrawPortionInfo&)>& rDrawPortion,
-    const std::function<void(const DrawBulletInfo&)>& rDrawBullet)
+void EditEngine::StripPortions(StripPortionsHelper& rStripPortionsHelper)
 {
-    if (!rDrawPortion && !rDrawBullet)
-        return;
-
     ScopedVclPtrInstance< VirtualDevice > aTmpDev;
     tools::Rectangle aBigRect( Point( 0, 0 ), Size( 0x7FFFFFFF, 0x7FFFFFFF ) );
     if ( IsEffectivelyVertical() )
@@ -1094,7 +1089,7 @@ void EditEngine::StripPortions(
         }
     }
 
-    getImpl().Paint(*aTmpDev, aBigRect, Point(), 0_deg10, rDrawPortion, 
rDrawBullet);
+    getImpl().PaintOrStrip(*aTmpDev, aBigRect, Point(), 0_deg10, 
&rStripPortionsHelper);
 }
 
 void EditEngine::GetPortions( sal_Int32 nPara, std::vector<sal_Int32>& rList )
@@ -1584,9 +1579,7 @@ EditEngine::CreateTransferable(const ESelection& 
rSelection)
 
 // ======================    Virtual Methods    ========================
 
-void EditEngine::PaintingFirstLine(sal_Int32, const Point&, const Point&, 
Degree10, OutputDevice&,
-    const std::function<void(const DrawPortionInfo&)>&,
-    const std::function<void(const DrawBulletInfo&)>&)
+void EditEngine::ProcessFirstLineOfParagraph(sal_Int32, const Point&, const 
Point&, Degree10, OutputDevice&, StripPortionsHelper*)
 {
 }
 
diff --git a/editeng/source/editeng/impedit.hxx 
b/editeng/source/editeng/impedit.hxx
index e91e2f4a5191..1119d9b064f9 100644
--- a/editeng/source/editeng/impedit.hxx
+++ b/editeng/source/editeng/impedit.hxx
@@ -1004,10 +1004,7 @@ public:
     void                    UpdateViews( EditView* pCurView = nullptr );
     Point CalculateTextPaintStartPosition(ImpEditView& rView) const;
     void                    Paint( ImpEditView* pView, const tools::Rectangle& 
rRect, OutputDevice* pTargetDevice );
-    void Paint(
-        OutputDevice& rOutDev, tools::Rectangle aClipRect, Point aStartPos, 
Degree10 nOrientation = 0_deg10,
-        const std::function<void(const DrawPortionInfo&)>& rDrawPortion = 
std::function<void(const DrawPortionInfo&)>(),
-        const std::function<void(const DrawBulletInfo&)>& rDrawBullet = 
std::function<void(const DrawBulletInfo&)>());
+    void PaintOrStrip( OutputDevice& rOutDev, tools::Rectangle aClipRect, 
Point aStartPos, Degree10 nOrientation = 0_deg10, StripPortionsHelper* 
pStripPortionsHelper = nullptr);
 
     bool                MouseButtonUp( const MouseEvent& rMouseEvent, 
EditView* pView );
     bool                MouseButtonDown( const MouseEvent& rMouseEvent, 
EditView* pView );
diff --git a/editeng/source/editeng/impedit3.cxx 
b/editeng/source/editeng/impedit3.cxx
index 8eb2f8ee73b0..d4eda028ac23 100644
--- a/editeng/source/editeng/impedit3.cxx
+++ b/editeng/source/editeng/impedit3.cxx
@@ -3365,7 +3365,7 @@ void ImpEditEngine::Draw( OutputDevice& rOutDev, const 
Point& rStartPos, Degree1
         aStartPos.AdjustX(GetPaperSize().Width() );
         rStartPos.RotateAround(aStartPos, nOrientation);
     }
-    Paint(rOutDev, aBigRect, aStartPos, nOrientation);
+    PaintOrStrip(rOutDev, aBigRect, aStartPos, nOrientation);
     if (rOutDev.GetConnectMetaFile())
         rOutDev.Pop();
 }
@@ -3428,7 +3428,7 @@ void ImpEditEngine::Draw( OutputDevice& rOutDev, const 
tools::Rectangle& rOutRec
         }
     }
 
-    Paint(rOutDev, aOutRect, aStartPos);
+    PaintOrStrip(rOutDev, aOutRect, aStartPos);
 
     if ( bMetafile )
         rOutDev.Pop();
@@ -3439,11 +3439,9 @@ void ImpEditEngine::Draw( OutputDevice& rOutDev, const 
tools::Rectangle& rOutRec
 }
 
 // TODO: use IterateLineAreas in ImpEditEngine::Paint, to avoid algorithm 
duplication
-void ImpEditEngine::Paint( OutputDevice& rOutDev, tools::Rectangle aClipRect, 
Point aStartPos, Degree10 nOrientation,
-    const std::function<void(const DrawPortionInfo&)>& rDrawPortion,
-    const std::function<void(const DrawBulletInfo&)>& rDrawBullet)
+void ImpEditEngine::PaintOrStrip( OutputDevice& rOutDev, tools::Rectangle 
aClipRect, Point aStartPos, Degree10 nOrientation, StripPortionsHelper* 
pStripPortionsHelper)
 {
-    if ( !IsUpdateLayout() && !rDrawPortion )
+    if ( !IsUpdateLayout() && !pStripPortionsHelper )
         return;
 
     if ( !IsFormatted() )
@@ -3527,14 +3525,13 @@ void ImpEditEngine::Paint( OutputDevice& rOutDev, 
tools::Rectangle aClipRect, Po
 
                     // Why not just also call when stripping portions? This 
will give the correct values
                     // and needs no position corrections in 
OutlinerEditEng::DrawingText which tries to call
-                    // PaintBullet correctly; exactly what 
GetEditEnginePtr()->PaintingFirstLine
+                    // PaintOrStripBullet correctly; exactly what 
GetEditEnginePtr()->ProcessFirstLineOfParagraph
                     // does, too. No change for not-layouting (painting).
                     if(0 == nLine) // && !bStripOnly)
                     {
                         Point aLineStart(aStartPos);
                         adjustYDirectionAware(aLineStart, -nLineHeight);
-                        GetEditEnginePtr()->PaintingFirstLine(nParaPortion, 
aLineStart, aOrigin, nOrientation, rOutDev,
-                            rDrawPortion, rDrawBullet);
+                        
GetEditEnginePtr()->ProcessFirstLineOfParagraph(nParaPortion, aLineStart, 
aOrigin, nOrientation, rOutDev, pStripPortionsHelper);//, rDrawPortion, 
rDrawBullet);
 
                         // Remember whether a bullet was painted.
                         const SfxBoolItem& rBulletState = 
mpEditEngine->GetParaAttrib(nParaPortion, EE_PARA_BULLETSTATE);
@@ -3576,9 +3573,8 @@ void ImpEditEngine::Paint( OutputDevice& rOutDev, 
tools::Rectangle aClipRect, Po
                             {
                                 SeekCursor(rParaPortion.GetNode(), nIndex, 
aTmpFont, &rOutDev);
 
-                                const auto* pRuby
-                                    = static_cast<const 
SvxRubyItem*>(pRubyAttr->GetItem());
-                                if (rDrawPortion)
+                                const auto* pRuby = static_cast<const 
SvxRubyItem*>(pRubyAttr->GetItem());
+                                if (pStripPortionsHelper)
                                 {
                                     const bool bEndOfLine(nPortion == 
pLine->GetEndPortion());
                                     const bool bEndOfParagraph(bEndOfLine && 
nLine + 1 == nLines);
@@ -3598,8 +3594,7 @@ void ImpEditEngine::Paint( OutputDevice& rOutDev, 
tools::Rectangle aClipRect, Po
                                         {}, {}, aTmpFont, nParaPortion, 0, 
nullptr, nullptr,
                                         bEndOfLine, bEndOfParagraph, false, 
nullptr, aOverlineColor,
                                         aTextLineColor);
-                                    rDrawPortion(aInfo);
-
+                                    
pStripPortionsHelper->processDrawPortionInfo(aInfo);
                                     aTmpFont.SetFontSize(nPrevSz);
                                 }
                             }
@@ -3759,7 +3754,7 @@ void ImpEditEngine::Paint( OutputDevice& rOutDev, 
tools::Rectangle aClipRect, Po
                                     //It is not perfect, it still use 
lineBreaksList, so it won’t seek
                                     //word ends to wrap text there, but it 
would be difficult to change
                                     //this due to needed adaptations in 
EditEngine
-                                    if (rDrawPortion && !bParsingFields && 
pExtraInfo && !pExtraInfo->lineBreaksList.empty())
+                                    if (pStripPortionsHelper && 
!bParsingFields && pExtraInfo && !pExtraInfo->lineBreaksList.empty())
                                     {
                                         bParsingFields = true;
                                         itSubLines = 
pExtraInfo->lineBreaksList.begin();
@@ -3863,7 +3858,7 @@ void ImpEditEngine::Paint( OutputDevice& rOutDev, 
tools::Rectangle aClipRect, Po
                                 if (rTextPortion.IsRightToLeft())
                                     
aRedLineTmpPos.AdjustX(rTextPortion.GetSize().Width() );
 
-                                if ( rDrawPortion )
+                                if (pStripPortionsHelper)
                                 {
                                     EEngineData::WrongSpellVector 
aWrongSpellVector;
 
@@ -3956,7 +3951,7 @@ void ImpEditEngine::Paint( OutputDevice& rOutDev, 
tools::Rectangle aClipRect, Po
                                         &aLocale,
                                         aOverlineColor,
                                         aTextLineColor);
-                                    rDrawPortion(aInfo);
+                                    
pStripPortionsHelper->processDrawPortionInfo(aInfo);
 
                                     // #108052# remember that EOP is written 
already for this ParaPortion
                                     if(bEndOfParagraph)
@@ -4181,7 +4176,7 @@ void ImpEditEngine::Paint( OutputDevice& rOutDev, 
tools::Rectangle aClipRect, Po
                                     comphelper::string::padToLength(aBuf, 
nChars, rTextPortion.GetExtraValue());
                                     OUString aText(aBuf.makeStringAndClear());
 
-                                    if ( rDrawPortion )
+                                    if (pStripPortionsHelper)
                                     {
                                         // create EOL and EOP bools
                                         const bool bEndOfLine(nPortion == 
pLine->GetEndPortion());
@@ -4200,7 +4195,7 @@ void ImpEditEngine::Paint( OutputDevice& rOutDev, 
tools::Rectangle aClipRect, Po
                                             nullptr,
                                             aOverlineColor,
                                             aTextLineColor);
-                                        rDrawPortion(aInfo);
+                                        
pStripPortionsHelper->processDrawPortionInfo(aInfo);
                                     }
                                     else
                                     {
@@ -4209,7 +4204,7 @@ void ImpEditEngine::Paint( OutputDevice& rOutDev, 
tools::Rectangle aClipRect, Po
                                         rOutDev.DrawStretchText( aTmpPos, 
rTextPortion.GetSize().Width(), aText );
                                     }
                                 }
-                                else if ( rDrawPortion )
+                                else if (pStripPortionsHelper)
                                 {
                                     // #i108052# When stripping, a callback 
for _empty_ paragraphs is also needed.
                                     // This was optimized away (by not 
rendering the space-only tab portion), so do
@@ -4229,7 +4224,7 @@ void ImpEditEngine::Paint( OutputDevice& rOutDev, 
tools::Rectangle aClipRect, Po
                                         nullptr,
                                         aOverlineColor,
                                         aTextLineColor);
-                                    rDrawPortion(aInfo);
+                                    
pStripPortionsHelper->processDrawPortionInfo(aInfo);
                                 }
                             }
                             break;
@@ -4265,7 +4260,7 @@ void ImpEditEngine::Paint( OutputDevice& rOutDev, 
tools::Rectangle aClipRect, Po
             // that the reason for #i108052# was fixed/removed again, so this 
is a try to fix
             // the number of paragraphs (and counting empty ones) now 
independent from the
             // changes in EditEngine behaviour.
-            if(!bEndOfParagraphWritten && !bPaintBullet && rDrawPortion)
+            if(!bEndOfParagraphWritten && !bPaintBullet && 
pStripPortionsHelper)
             {
                 const Color aOverlineColor(rOutDev.GetOverlineColor());
                 const Color aTextLineColor(rOutDev.GetTextLineColor());
@@ -4279,7 +4274,7 @@ void ImpEditEngine::Paint( OutputDevice& rOutDev, 
tools::Rectangle aClipRect, Po
                     nullptr,
                     aOverlineColor,
                     aTextLineColor);
-                rDrawPortion(aInfo);
+                pStripPortionsHelper->processDrawPortionInfo(aInfo);
             }
         }
         else
@@ -4348,7 +4343,7 @@ void ImpEditEngine::Paint( ImpEditView* pView, const 
tools::Rectangle& rRect, Ou
     vcl::Region aOldRegion = rTarget.GetClipRegion();
     rTarget.IntersectClipRegion( aClipRect );
 
-    Paint(rTarget, aClipRect, aStartPos);
+    PaintOrStrip(rTarget, aClipRect, aStartPos);
 
     if ( bClipRegion )
         rTarget.SetClipRegion( aOldRegion );
diff --git a/editeng/source/outliner/outleeng.cxx 
b/editeng/source/outliner/outleeng.cxx
index 68e3dcca07ae..a4c193eb66ab 100644
--- a/editeng/source/outliner/outleeng.cxx
+++ b/editeng/source/outliner/outleeng.cxx
@@ -39,10 +39,7 @@ OutlinerEditEng::~OutlinerEditEng()
 {
 }
 
-void OutlinerEditEng::PaintingFirstLine(sal_Int32 nPara, const Point& 
rStartPos, const Point& rOrigin, Degree10 nOrientation, OutputDevice& rOutDev,
-    const std::function<void(const DrawPortionInfo&)>& rDrawPortion,
-    const std::function<void(const DrawBulletInfo&)>& rDrawBullet
-)
+void OutlinerEditEng::ProcessFirstLineOfParagraph(sal_Int32 nPara, const 
Point& rStartPos, const Point& rOrigin, Degree10 nOrientation, OutputDevice& 
rOutDev, StripPortionsHelper* pStripPortionsHelper)//,
 {
     if( GetControlWord() & EEControlBits::OUTLINER )
     {
@@ -50,7 +47,7 @@ void OutlinerEditEng::PaintingFirstLine(sal_Int32 nPara, 
const Point& rStartPos,
         pOwner->maPaintFirstLineHdl.Call( &aInfo );
     }
 
-    pOwner->PaintBullet(nPara, rStartPos, rOrigin, nOrientation, rOutDev, 
rDrawPortion, rDrawBullet);
+    pOwner->PaintOrStripBullet(nPara, rStartPos, rOrigin, nOrientation, 
rOutDev, pStripPortionsHelper);
 }
 
 const SvxNumberFormat* OutlinerEditEng::GetNumberFormat( sal_Int32 nPara ) 
const
diff --git a/editeng/source/outliner/outliner.cxx 
b/editeng/source/outliner/outliner.cxx
index 785ed710ff7d..f9f5ac0f72ab 100644
--- a/editeng/source/outliner/outliner.cxx
+++ b/editeng/source/outliner/outliner.cxx
@@ -870,11 +870,10 @@ vcl::Font Outliner::ImpCalcBulletFont( sal_Int32 nPara ) 
const
     return aBulletFont;
 }
 
-void Outliner::PaintBullet(
+void Outliner::PaintOrStripBullet(
     sal_Int32 nPara, const Point& rStartPos, const Point& rOrigin,
     Degree10 nOrientation, OutputDevice& rOutDev,
-    const std::function<void(const DrawPortionInfo&)>& rDrawPortion,
-    const std::function<void(const DrawBulletInfo&)>& rDrawBullet)
+    StripPortionsHelper* pStripPortionsHelper)
 {
 
     bool bDrawBullet = false;
@@ -958,7 +957,7 @@ void Outliner::PaintBullet(
                 nLayoutMode |= vcl::text::ComplexTextLayoutFlags::BiDiRtl | 
vcl::text::ComplexTextLayoutFlags::TextOriginLeft | 
vcl::text::ComplexTextLayoutFlags::BiDiStrong;
             rOutDev.SetLayoutMode( nLayoutMode );
 
-            if(rDrawPortion)
+            if (pStripPortionsHelper)
             {
                 const SvxFont aSvxFont(rOutDev.GetFont());
                 KernArray aBuf;
@@ -974,7 +973,7 @@ void Outliner::PaintBullet(
                 const DrawPortionInfo aInfo(
                     aTextPos, pPara->GetText(), 0, 
pPara->GetText().getLength(), aBuf, {},
                     aSvxFont, nPara, bRightToLeftPara ? 1 : 0, nullptr, 
nullptr, false, false, true, nullptr, Color(), Color());
-                rDrawPortion(aInfo);
+                pStripPortionsHelper->processDrawPortionInfo(aInfo);
             }
             else
             {
@@ -1010,7 +1009,7 @@ void Outliner::PaintBullet(
                     }
                 }
 
-                if(rDrawBullet)
+                if (pStripPortionsHelper)
                 {
                     // call something analog to aDrawPortionHdl (if set) and 
feed it something
                     // analog to DrawPortionInfo...
@@ -1020,7 +1019,7 @@ void Outliner::PaintBullet(
                         *pFmt->GetBrush()->GetGraphicObject(),
                         aBulletPos,
                         pPara->aBulSize);
-                    rDrawBullet(aDrawBulletInfo);
+                    
pStripPortionsHelper->processDrawBulletInfo(aDrawBulletInfo);
                 }
                 else
                 {
@@ -1031,7 +1030,7 @@ void Outliner::PaintBullet(
     }
 
     // In case of collapsed subparagraphs paint a line before the text.
-    if( !pParaList->HasChildren(pPara) || pParaList->HasVisibleChildren(pPara) 
|| rDrawPortion || nOrientation )
+    if( !pParaList->HasChildren(pPara) || pParaList->HasVisibleChildren(pPara) 
|| pStripPortionsHelper || nOrientation )
         return;
 
     tools::Long nWidth = rOutDev.PixelToLogic( Size( 10, 0 ) ).Width();
@@ -1514,7 +1513,7 @@ tools::Rectangle Outliner::ImpCalcBulletArea( sal_Int32 
nPara, bool bAdjust, boo
         ParagraphInfos aInfos = pEditEngine->GetParagraphInfos( nPara );
         if ( aInfos.bValid )
         {
-            aTopLeft.setY( /* aInfos.nFirstLineOffset + */ // nFirstLineOffset 
is already added to the StartPos (PaintBullet) from the EditEngine
+            aTopLeft.setY( /* aInfos.nFirstLineOffset + */ // nFirstLineOffset 
is already added to the StartPos (PaintOrStripBullet) from the EditEngine
                             aInfos.nFirstLineHeight - 
aInfos.nFirstLineTextHeight
                             + aInfos.nFirstLineTextHeight / 2
                             - aBulletSize.Height() / 2 );
@@ -1636,11 +1635,9 @@ void Outliner::Remove( Paragraph const * pPara, 
sal_Int32 nParaCount )
     }
 }
 
-void Outliner::StripPortions(
-    const std::function<void(const DrawPortionInfo&)>& rDrawPortion,
-    const std::function<void(const DrawBulletInfo&)>& rDrawBullet)
+void Outliner::StripPortions(StripPortionsHelper& rStripPortionsHelper)
 {
-    pEditEngine->StripPortions(rDrawPortion, rDrawBullet);
+    pEditEngine->StripPortions(rStripPortionsHelper);
 }
 
 bool Outliner::RemovingPagesHdl( OutlinerView* pView )
diff --git a/include/editeng/StripPortionsHelper.hxx 
b/include/editeng/StripPortionsHelper.hxx
index c9129cfd0d65..7da5cc88d516 100644
--- a/include/editeng/StripPortionsHelper.hxx
+++ b/include/editeng/StripPortionsHelper.hxx
@@ -30,6 +30,8 @@
 #include <editeng/svxfont.hxx>
 #include <editeng/eedata.hxx>
 #include <editeng/flditem.hxx>
+#include <drawinglayer/primitive2d/Primitive2DContainer.hxx>
+#include <basegfx/matrix/b2dhommatrix.hxx>
 
 namespace com::sun::star::lang
 {
@@ -103,23 +105,37 @@ public:
     }
 };
 
-namespace drawinglayer::primitive2d
+class EDITENG_DLLPUBLIC StripPortionsHelper
 {
-class Primitive2DContainer;
-}
-namespace basegfx
+public:
+    virtual void processDrawPortionInfo(const DrawPortionInfo&) = 0;
+    virtual void processDrawBulletInfo(const DrawBulletInfo&) = 0;
+};
+
+class EDITENG_DLLPUBLIC TextHierarchyBreakup : public StripPortionsHelper
 {
-class B2DHomMatrix;
-}
+    drawinglayer::primitive2d::Primitive2DContainer maTextPortionPrimitives;
+    drawinglayer::primitive2d::Primitive2DContainer maLinePrimitives;
+    drawinglayer::primitive2d::Primitive2DContainer maParagraphPrimitives;
+    basegfx::B2DHomMatrix maNewTransformA;
+    basegfx::B2DHomMatrix maNewTransformB;
 
-void EDITENG_DLLPUBLIC CreateTextPortionPrimitivesFromDrawPortionInfo(
-    drawinglayer::primitive2d::Primitive2DContainer& rTarget,
-    const basegfx::B2DHomMatrix& rNewTransformA, const basegfx::B2DHomMatrix& 
rNewTransformB,
-    const DrawPortionInfo& rInfo);
-void EDITENG_DLLPUBLIC CreateDrawBulletPrimitivesFromDrawBulletInfo(
-    drawinglayer::primitive2d::Primitive2DContainer& rTarget,
-    const basegfx::B2DHomMatrix& rNewTransformA, const basegfx::B2DHomMatrix& 
rNewTransformB,
-    const DrawBulletInfo& rInfo);
+protected:
+    void flushTextPortionPrimitivesToLinePrimitives();
+    virtual sal_Int16 getOutlineLevelFromParagraph(sal_Int32 nPara) const;
+    virtual sal_Int32 getParagraphCount() const;
+    void flushLinePrimitivesToParagraphPrimitives(sal_Int32 nPara);
+
+public:
+    virtual void processDrawPortionInfo(const DrawPortionInfo& 
rDrawPortionInfo);
+    virtual void processDrawBulletInfo(const DrawBulletInfo& rDrawBulletInfo);
+
+    TextHierarchyBreakup();
+    TextHierarchyBreakup(const basegfx::B2DHomMatrix& rNewTransformA,
+                         const basegfx::B2DHomMatrix& rNewTransformB);
+
+    const drawinglayer::primitive2d::Primitive2DContainer& 
getTextPortionPrimitives();
+};
 
 #endif // INCLUDED_EDITENG_STRIPPORTIONSHELPER_HXX
 
diff --git a/include/editeng/editeng.hxx b/include/editeng/editeng.hxx
index 3b0277f9335d..c50203fe4665 100644
--- a/include/editeng/editeng.hxx
+++ b/include/editeng/editeng.hxx
@@ -116,6 +116,7 @@ enum class TransliterationFlags;
 class LinkParamNone;
 class DrawPortionInfo;
 class DrawBulletInfo;
+class StripPortionsHelper;
 
 /** values for:
        SfxItemSet GetAttribs( const ESelection& rSel, EditEngineAttribs 
nOnlyHardAttrib = EditEngineAttribs::All );
@@ -369,9 +370,7 @@ public:
 
     bool            IsInSelectionMode() const;
 
-    void            StripPortions(
-        const std::function<void(const DrawPortionInfo&)>& rDrawPortion,
-        const std::function<void(const DrawBulletInfo&)>& rDrawBullet);
+    void StripPortions(StripPortionsHelper& rStripPortionsHelper);
     void            GetPortions( sal_Int32 nPara, std::vector<sal_Int32>& 
rList );
 
     SAL_DLLPRIVATE tools::Long            GetFirstLineStartX( sal_Int32 
nParagraph );
@@ -492,9 +491,7 @@ public:
     SAL_DLLPRIVATE void            SetBeginPasteOrDropHdl( const 
Link<PasteOrDropInfos&,void>& rLink );
     SAL_DLLPRIVATE void            SetEndPasteOrDropHdl( const 
Link<PasteOrDropInfos&,void>& rLink );
 
-    virtual void    PaintingFirstLine(sal_Int32 nPara, const Point& rStartPos, 
const Point& rOrigin, Degree10 nOrientation, OutputDevice& rOutDev,
-        const std::function<void(const DrawPortionInfo&)>& rDrawPortion,
-        const std::function<void(const DrawBulletInfo&)>& rDrawBullet);
+    virtual void    ProcessFirstLineOfParagraph(sal_Int32 nPara, const Point& 
rStartPos, const Point& rOrigin, Degree10 nOrientation, OutputDevice& rOutDev, 
StripPortionsHelper* pStripPortionsHelper);
 
     virtual void    ParagraphInserted( sal_Int32 nNewParagraph );
     virtual void    ParagraphDeleted( sal_Int32 nDeletedParagraph );
diff --git a/include/editeng/outliner.hxx b/include/editeng/outliner.hxx
index b2eb8b97bc1a..179e678a2add 100644
--- a/include/editeng/outliner.hxx
+++ b/include/editeng/outliner.hxx
@@ -82,6 +82,7 @@ enum class TextRotation;
 enum class SdrCompatibilityFlag;
 class DrawPortionInfo;
 class DrawBulletInfo;
+class StripPortionsHelper;
 
 namespace com::sun::star::linguistic2 {
     class XSpellChecker1;
@@ -577,12 +578,11 @@ protected:
     SAL_DLLPRIVATE void            StyleSheetChanged( SfxStyleSheet const * 
pStyle );
 
     SAL_DLLPRIVATE void            InvalidateBullet(sal_Int32 nPara);
-    SAL_DLLPRIVATE void            PaintBullet(
+    SAL_DLLPRIVATE void            PaintOrStripBullet(
         sal_Int32 nPara, const Point& rStartPos,
         const Point& rOrigin, Degree10 nOrientation,
         OutputDevice& rOutDev,
-        const std::function<void(const DrawPortionInfo&)>& rDrawPortion,
-        const std::function<void(const DrawBulletInfo&)>& rDrawBullet);
+        StripPortionsHelper* pStripPortionsHelper);
 
     // used by OutlinerEditEng. Allows Outliner objects to provide
     // bullet access to the EditEngine.
@@ -747,9 +747,7 @@ public:
     OUString const & GetWordDelimiters() const;
     OUString        GetWord( const EPaM& rPos );
 
-    void            StripPortions(
-        const std::function<void(const DrawPortionInfo&)>& rDrawPortion,
-        const std::function<void(const DrawBulletInfo&)>& rDrawBullet);
+    void StripPortions(StripPortionsHelper& rStripPortionsHelper);
 
     Size            CalcTextSize();
 
diff --git a/include/svx/svdotext.hxx b/include/svx/svdotext.hxx
index 70a6de3d5bec..872474fc0cef 100644
--- a/include/svx/svdotext.hxx
+++ b/include/svx/svdotext.hxx
@@ -629,15 +629,6 @@ public:
     void impGetBlinkTextTiming(drawinglayer::animation::AnimationEntryList& 
rAnimList) const;
     void impGetScrollTextTiming(drawinglayer::animation::AnimationEntryList& 
rAnimList, double fFrameLength, double fTextLength) const;
 
-    // Direct decomposer for text visualization when you already have a 
prepared
-    // Outliner containing all the needed information
-    static void impDecomposeBlockTextPrimitiveDirect(
-        drawinglayer::primitive2d::Primitive2DContainer& rTarget,
-        SdrOutliner& rOutliner,
-        const basegfx::B2DHomMatrix& rNewTransformA,
-        const basegfx::B2DHomMatrix& rNewTransformB,
-        const basegfx::B2DRange& rClipRange);
-
     /** returns false if the given pointer is NULL
         or if the given SdrOutliner contains no text.
         Also checks for one empty paragraph.
diff --git a/include/svx/svdoutl.hxx b/include/svx/svdoutl.hxx
index 422399e17c23..5ef5985ef26b 100644
--- a/include/svx/svdoutl.hxx
+++ b/include/svx/svdoutl.hxx
@@ -23,6 +23,7 @@
 #include <optional>
 #include <svx/svxdllapi.h>
 #include <unotools/weakref.hxx>
+#include <editeng/StripPortionsHelper.hxx>
 
 class SdrTextObj;
 class SdrPage;
@@ -51,5 +52,51 @@ public:
     virtual std::optional<bool> GetCompatFlag(SdrCompatibilityFlag eFlag) 
const override;
 };
 
+class TextHierarchyBreakupOutliner : public TextHierarchyBreakup
+{
+    SdrOutliner&                mrOutliner;
+
+protected:
+    virtual sal_Int16 getOutlineLevelFromParagraph(sal_Int32 nPara) const;
+    virtual sal_Int32 getParagraphCount() const;
+
+public:
+    TextHierarchyBreakupOutliner(
+        SdrOutliner& rOutliner,
+        const basegfx::B2DHomMatrix& rNewTransformA,
+        const basegfx::B2DHomMatrix& rNewTransformB);
+};
+
+class TextHierarchyBreakupBlockText : public TextHierarchyBreakupOutliner
+{
+    // ClipRange for BlockText decomposition; only text portions completely
+    // inside are to be accepted, so this is different from geometric clipping
+    // (which would allow e.g. upper parts of portions to remain)
+    const basegfx::B2DRange&    mrClipRange;
+
+public:
+    virtual void processDrawPortionInfo(const DrawPortionInfo& 
rDrawPortionInfo);
+
+    TextHierarchyBreakupBlockText(
+        SdrOutliner& rOutliner,
+        const basegfx::B2DHomMatrix& rNewTransformA,
+        const basegfx::B2DHomMatrix& rNewTransformB,
+        const basegfx::B2DRange& rClipRange);
+};
+
+class TextHierarchyBreakupContourText : public TextHierarchyBreakupOutliner
+{
+    // the visible area for contour text decomposition
+    basegfx::B2DVector              maScale;
+
+public:
+    virtual void processDrawPortionInfo(const DrawPortionInfo& 
rDrawPortionInfo);
+
+    TextHierarchyBreakupContourText(
+        SdrOutliner& rOutliner,
+        const basegfx::B2DHomMatrix& rNewTransformA,
+        const basegfx::B2DHomMatrix& rNewTransformB,
+        const basegfx::B2DVector& rScale);
+};
 
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/ui/view/tabview3.cxx b/sc/source/ui/view/tabview3.cxx
index 1bbb272cced5..4509f7dc31a8 100644
--- a/sc/source/ui/view/tabview3.cxx
+++ b/sc/source/ui/view/tabview3.cxx
@@ -2299,32 +2299,18 @@ drawinglayer::primitive2d::Primitive2DContainer 
ScTextEditOverlayObject::createO
     const EditView* pEditView(rScViewData.GetEditView(maScSplitPos));
     assert(pEditView && "NO access to EditView in ScTextEditOverlayObject!");
 
-    // use no transformations. The result will be in logic coordinates
-    // based on aEditRectangle and the EditEngine setup, see
-    // ScViewData::SetEditEngine
-    basegfx::B2DHomMatrix aNewTransformA;
-    basegfx::B2DHomMatrix aNewTransformB;
-
     // get text data in LogicMode
     OutputDevice& rOutDev(pEditView->GetOutputDevice());
     const MapMode aOrig(rOutDev.GetMapMode());
     rOutDev.SetMapMode(rScViewData.GetLogicMode());
 
-    pEditView->getEditEngine().StripPortions(
-        [&aRetval, &aNewTransformA, &aNewTransformB](const DrawPortionInfo& 
rInfo){
-            CreateTextPortionPrimitivesFromDrawPortionInfo(
-                aRetval,
-                aNewTransformA,
-                aNewTransformB,
-                rInfo);
-        },
-        [&aRetval, &aNewTransformA, &aNewTransformB](const DrawBulletInfo& 
rInfo){
-            CreateDrawBulletPrimitivesFromDrawBulletInfo(
-                aRetval,
-                aNewTransformA,
-                aNewTransformB,
-                rInfo);
-        });
+    // StripPortions from EditEngine.
+    // use no transformations. The result will be in logic coordinates
+    // based on aEditRectangle and the EditEngine setup, see
+    // ScViewData::SetEditEngine
+    TextHierarchyBreakup aBreakup;
+    pEditView->getEditEngine().StripPortions(aBreakup);
+    aRetval = aBreakup.getTextPortionPrimitives();
 
     rOutDev.SetMapMode(aOrig);
     return aRetval;
diff --git a/svx/source/svdraw/svdedxv.cxx b/svx/source/svdraw/svdedxv.cxx
index 3fd2c4a345cb..9f5ad0ea3695 100644
--- a/svx/source/svdraw/svdedxv.cxx
+++ b/svx/source/svdraw/svdedxv.cxx
@@ -783,8 +783,10 @@ void TextEditOverlayObject::checkDataChange(const 
basegfx::B2DRange& rMinTextEdi
         // of this mechanism, it *may* be possible to buffer layouted
         // primitives per ParaPortion with/in/dependent on the EditEngine
         // content if needed. For now, get and compare
-        SdrTextObj::impDecomposeBlockTextPrimitiveDirect(
-            aNewTextPrimitives, *pSdrOutliner, aNewTransformA, aNewTransformB, 
aClipRange);
+        TextHierarchyBreakupBlockText aBreakup(*pSdrOutliner, aNewTransformA, 
aNewTransformB,
+                                               aClipRange);
+        pSdrOutliner->StripPortions(aBreakup);
+        aNewTextPrimitives.append(aBreakup.getTextPortionPrimitives());
 
         if (aNewTextPrimitives != maTextPrimitives)
         {
diff --git a/svx/source/svdraw/svdotextdecomposition.cxx 
b/svx/source/svdraw/svdotextdecomposition.cxx
index a90694947368..7a6001d628e4 100644
--- a/svx/source/svdraw/svdotextdecomposition.cxx
+++ b/svx/source/svdraw/svdotextdecomposition.cxx
@@ -61,225 +61,6 @@
 
 using namespace com::sun::star;
 
-namespace
-{
-    class impTextBreakupHandler
-    {
-    private:
-        drawinglayer::primitive2d::Primitive2DContainer             
maTextPortionPrimitives;
-        drawinglayer::primitive2d::Primitive2DContainer             
maLinePrimitives;
-        drawinglayer::primitive2d::Primitive2DContainer             
maParagraphPrimitives;
-
-        SdrOutliner&                                                mrOutliner;
-        basegfx::B2DHomMatrix                                       
maNewTransformA;
-        basegfx::B2DHomMatrix                                       
maNewTransformB;
-
-        // the visible area for contour text decomposition
-        basegfx::B2DVector                                          maScale;
-
-        // ClipRange for BlockText decomposition; only text portions completely
-        // inside are to be accepted, so this is different from geometric 
clipping
-        // (which would allow e.g. upper parts of portions to remain). Only 
used for
-        // BlockText (see there)
-        basegfx::B2DRange                                           
maClipRange;
-
-        void impFlushTextPortionPrimitivesToLinePrimitives();
-        void impFlushLinePrimitivesToParagraphPrimitives(sal_Int32 nPara);
-        void impHandleDrawPortionInfo(const DrawPortionInfo& rInfo);
-        void impHandleDrawBulletInfo(const DrawBulletInfo& rInfo);
-
-    public:
-        explicit impTextBreakupHandler(SdrOutliner& rOutliner)
-        :   mrOutliner(rOutliner)
-        {
-        }
-
-        void decomposeContourTextPrimitive(const basegfx::B2DHomMatrix& 
rNewTransformA, const basegfx::B2DHomMatrix& rNewTransformB, const 
basegfx::B2DVector& rScale)
-        {
-            maScale = rScale;
-            maNewTransformA = rNewTransformA;
-            maNewTransformB = rNewTransformB;
-
-            mrOutliner.StripPortions(
-                [this](const DrawPortionInfo& rInfo){
-                    // for contour text, ignore (clip away) all portions which 
are below
-                    // the visible area given by maScale
-                    if(static_cast<double>(rInfo.mrStartPos.Y()) < 
maScale.getY())
-                    {
-                        impHandleDrawPortionInfo(rInfo);
-                    }
-                },
-                [this](const DrawBulletInfo& rInfo){ 
impHandleDrawBulletInfo(rInfo); });
-        }
-
-        void decomposeBlockTextPrimitive(
-            const basegfx::B2DHomMatrix& rNewTransformA,
-            const basegfx::B2DHomMatrix& rNewTransformB,
-            const basegfx::B2DRange& rClipRange)
-        {
-            maNewTransformA = rNewTransformA;
-            maNewTransformB = rNewTransformB;
-            maClipRange = rClipRange;
-
-            mrOutliner.StripPortions(
-                [this](const DrawPortionInfo& rInfo){
-                    // Is clipping wanted? This is text clipping; only accept 
a portion
-                    // if it's completely in the range
-                    if(!maClipRange.isEmpty())
-                    {
-                        // Test start position first; this allows to not get 
the text range at
-                        // all if text is far outside
-                        const basegfx::B2DPoint 
aStartPosition(rInfo.mrStartPos.X(), rInfo.mrStartPos.Y());
-
-                        if(!maClipRange.isInside(aStartPosition))
-                        {
-                            return;
-                        }
-
-                        // Start position is inside. Get TextBoundRect and 
TopLeft next
-                        drawinglayer::primitive2d::TextLayouterDevice 
aTextLayouterDevice;
-                        aTextLayouterDevice.setFont(rInfo.mrFont);
-
-                        const basegfx::B2DRange aTextBoundRect(
-                            aTextLayouterDevice.getTextBoundRect(
-                                rInfo.maText, rInfo.mnTextStart, 
rInfo.mnTextLen));
-                        const basegfx::B2DPoint 
aTopLeft(aTextBoundRect.getMinimum() + aStartPosition);
-
-                        if(!maClipRange.isInside(aTopLeft))
-                        {
-                            return;
-                        }
-
-                        // TopLeft is inside. Get BottomRight and check
-                        const basegfx::B2DPoint 
aBottomRight(aTextBoundRect.getMaximum() + aStartPosition);
-
-                        if(!maClipRange.isInside(aBottomRight))
-                        {
-                            return;
-                        }
-
-                        // all inside, clip was successful
-                    }
-                    impHandleDrawPortionInfo(rInfo);
-                },
-                [this](const DrawBulletInfo& rInfo){ 
impHandleDrawBulletInfo(rInfo); });
-        }
-
-        void decomposeStretchTextPrimitive(const basegfx::B2DHomMatrix& 
rNewTransformA, const basegfx::B2DHomMatrix& rNewTransformB)
-        {
-            maNewTransformA = rNewTransformA;
-            maNewTransformB = rNewTransformB;
-
-            mrOutliner.StripPortions(
-                [this](const DrawPortionInfo& rInfo){ 
impHandleDrawPortionInfo(rInfo); },
-                [this](const DrawBulletInfo& rInfo){ 
impHandleDrawBulletInfo(rInfo); });
-        }
-
-        drawinglayer::primitive2d::Primitive2DContainer 
extractPrimitive2DSequence();
-    };
-
-    void impTextBreakupHandler::impFlushTextPortionPrimitivesToLinePrimitives()
-    {
-        // only create a line primitive when we had content; there is no need 
for
-        // empty line primitives (contrary to paragraphs, see below).
-        if(!maTextPortionPrimitives.empty())
-        {
-            maLinePrimitives.push_back(new 
drawinglayer::primitive2d::TextHierarchyLinePrimitive2D(std::move(maTextPortionPrimitives)));
-        }
-    }
-
-    void 
impTextBreakupHandler::impFlushLinePrimitivesToParagraphPrimitives(sal_Int32 
nPara)
-    {
-        sal_Int16 nDepth = mrOutliner.GetDepth(nPara);
-        EBulletInfo eInfo = mrOutliner.GetBulletInfo(nPara);
-        // Pass -1 to signal VclMetafileProcessor2D that there is no active
-        // bullets/numbering in this paragraph (i.e. this is normal text)
-        const sal_Int16 nOutlineLevel( eInfo.bVisible ?  nDepth : -1);
-
-        // ALWAYS create a paragraph primitive, even when no content was 
added. This is done to
-        // have the correct paragraph count even with empty paragraphs. Those 
paragraphs will
-        // have an empty sub-PrimitiveSequence.
-        maParagraphPrimitives.push_back(
-            new drawinglayer::primitive2d::TextHierarchyParagraphPrimitive2D(
-                std::move(maLinePrimitives),
-                nOutlineLevel));
-    }
-
-    void impTextBreakupHandler::impHandleDrawPortionInfo(const 
DrawPortionInfo& rInfo)
-    {
-        CreateTextPortionPrimitivesFromDrawPortionInfo(
-            maTextPortionPrimitives,
-            maNewTransformA,
-            maNewTransformB,
-            rInfo);
-
-        if(rInfo.mbEndOfLine || rInfo.mbEndOfParagraph)
-        {
-            impFlushTextPortionPrimitivesToLinePrimitives();
-        }
-
-        if(rInfo.mbEndOfParagraph)
-        {
-            impFlushLinePrimitivesToParagraphPrimitives(rInfo.mnPara);
-        }
-    }
-
-    void impTextBreakupHandler::impHandleDrawBulletInfo(const DrawBulletInfo& 
rInfo)
-    {
-        CreateDrawBulletPrimitivesFromDrawBulletInfo(
-            maTextPortionPrimitives,
-            maNewTransformA,
-            maNewTransformB,
-            rInfo);
-        basegfx::B2DHomMatrix aNewTransform;
-
-        // add size to new transform
-        aNewTransform.scale(rInfo.maBulletSize.getWidth(), 
rInfo.maBulletSize.getHeight());
-
-        // apply transformA
-        aNewTransform *= maNewTransformA;
-
-        // apply local offset
-        aNewTransform.translate(rInfo.maBulletPosition.X(), 
rInfo.maBulletPosition.Y());
-
-        // also apply embedding object's transform
-        aNewTransform *= maNewTransformB;
-
-        // prepare empty GraphicAttr
-        const GraphicAttr aGraphicAttr;
-
-        // create GraphicPrimitive2D
-        const drawinglayer::primitive2d::Primitive2DReference 
aNewReference(new drawinglayer::primitive2d::GraphicPrimitive2D(
-            aNewTransform,
-            rInfo.maBulletGraphicObject,
-            aGraphicAttr));
-
-        // embed in TextHierarchyBulletPrimitive2D
-        drawinglayer::primitive2d::Primitive2DContainer aNewSequence { 
aNewReference };
-        rtl::Reference<drawinglayer::primitive2d::BasePrimitive2D> 
pNewPrimitive = new 
drawinglayer::primitive2d::TextHierarchyBulletPrimitive2D(std::move(aNewSequence));
-
-        // add to output
-        maTextPortionPrimitives.push_back(pNewPrimitive);
-    }
-
-    drawinglayer::primitive2d::Primitive2DContainer 
impTextBreakupHandler::extractPrimitive2DSequence()
-    {
-        if(!maTextPortionPrimitives.empty())
-        {
-            // collect non-closed lines
-            impFlushTextPortionPrimitivesToLinePrimitives();
-        }
-
-        if(!maLinePrimitives.empty())
-        {
-            // collect non-closed paragraphs
-            
impFlushLinePrimitivesToParagraphPrimitives(mrOutliner.GetParagraphCount() - 1);
-        }
-
-        return std::move(maParagraphPrimitives);
-    }
-} // end of anonymous namespace
-
 // primitive decompositions
 void SdrTextObj::impDecomposeContourTextPrimitive(
     drawinglayer::primitive2d::Primitive2DContainer& rTarget,
@@ -370,15 +151,17 @@ void SdrTextObj::impDecomposeContourTextPrimitive(
     // now break up text primitives. If it has a fat stroke, 
createTextPrimitive() has created a
     // ScaledUnitPolyPolygon. Thus aPolyPolygon might be smaller than aScale 
from aObjectMatrix. We
     // use this smaller size for the text area, otherwise the text will reach 
into the stroke.
-    impTextBreakupHandler aConverter(rOutliner);
-    aConverter.decomposeContourTextPrimitive(aNewTransformA, aNewTransformB,
-                                             
aPolyPolygon.getB2DRange().getRange());
+    TextHierarchyBreakupContourText aBreakup(
+        rOutliner,
+        aNewTransformA,
+        aNewTransformB,
+        aPolyPolygon.getB2DRange().getRange());
+    rOutliner.StripPortions(aBreakup);
+    rTarget = aBreakup.getTextPortionPrimitives();
 
     // cleanup outliner
     rOutliner.Clear();
     rOutliner.setVisualizedPage(nullptr);
-
-    rTarget = aConverter.extractPrimitive2DSequence();
 }
 
 void SdrTextObj::impDecomposeAutoFitTextPrimitive(
@@ -519,16 +302,19 @@ void SdrTextObj::impDecomposeAutoFitTextPrimitive(
     basegfx::B2DRange aClipRange;
 
     // now break up text primitives.
-    impTextBreakupHandler aConverter(rOutliner);
-    aConverter.decomposeBlockTextPrimitive(aNewTransformA, aNewTransformB, 
aClipRange);
+    TextHierarchyBreakupBlockText aBreakup(
+        rOutliner,
+        aNewTransformA,
+        aNewTransformB,
+        aClipRange);
+    rOutliner.StripPortions(aBreakup);
+    rTarget = aBreakup.getTextPortionPrimitives();
 
     // cleanup outliner
     rOutliner.SetBackgroundColor(aOriginalBackColor);
     rOutliner.Clear();
     rOutliner.setVisualizedPage(nullptr);
     rOutliner.SetControlWord(nOriginalControlWord);
-
-    rTarget = aConverter.extractPrimitive2DSequence();
 }
 
 // Resolves: fdo#35779 set background color of this shape as the editeng 
background if there
@@ -833,15 +619,18 @@ void SdrTextObj::impDecomposeBlockTextPrimitive(
         aClipRange = {0, 0, std::numeric_limits<double>::max(), 
aAnchorTextRange.getHeight()};
 
     // now break up text primitives.
-    impTextBreakupHandler aConverter(rOutliner);
-    aConverter.decomposeBlockTextPrimitive(aNewTransformA, aNewTransformB, 
aClipRange);
+    TextHierarchyBreakupBlockText aBreakup(
+        rOutliner,
+        aNewTransformA,
+        aNewTransformB,
+        aClipRange);
+    rOutliner.StripPortions(aBreakup);
+    rTarget = aBreakup.getTextPortionPrimitives();
 
     // cleanup outliner
     rOutliner.SetBackgroundColor(aOriginalBackColor);
     rOutliner.Clear();
     rOutliner.setVisualizedPage(nullptr);
-
-    rTarget = aConverter.extractPrimitive2DSequence();
 }
 
 void SdrTextObj::impDecomposeStretchTextPrimitive(
@@ -914,15 +703,17 @@ void SdrTextObj::impDecomposeStretchTextPrimitive(
         fShearX, fRotate, aTranslate.getX(), aTranslate.getY()));
 
     // now break up text primitives.
-    impTextBreakupHandler aConverter(rOutliner);
-    aConverter.decomposeStretchTextPrimitive(aNewTransformA, aNewTransformB);
+    TextHierarchyBreakupOutliner aBreakup(
+        rOutliner,
+        aNewTransformA,
+        aNewTransformB);
+    rOutliner.StripPortions(aBreakup);
+    rTarget = aBreakup.getTextPortionPrimitives();
 
     // cleanup outliner
     rOutliner.SetControlWord(nOriginalControlWord);
     rOutliner.Clear();
     rOutliner.setVisualizedPage(nullptr);
-
-    rTarget = aConverter.extractPrimitive2DSequence();
 }
 
 
@@ -1341,29 +1132,18 @@ void SdrTextObj::impDecomposeChainedTextPrimitive(
     basegfx::B2DRange aClipRange;
 
     // now break up text primitives.
-    impTextBreakupHandler aConverter(rOutliner);
-    aConverter.decomposeBlockTextPrimitive(aNewTransformA, aNewTransformB, 
aClipRange);
+    TextHierarchyBreakupBlockText aBreakup(
+        rOutliner,
+        aNewTransformA,
+        aNewTransformB,
+        aClipRange);
+    rOutliner.StripPortions(aBreakup);
+    rTarget = aBreakup.getTextPortionPrimitives();
 
     // cleanup outliner
     rOutliner.Clear();
     rOutliner.setVisualizedPage(nullptr);
     rOutliner.SetControlWord(nOriginalControlWord);
-
-    rTarget = aConverter.extractPrimitive2DSequence();
-}
-
-// Direct decomposer for text visualization when you already have a prepared
-// Outliner containing all the needed information
-void SdrTextObj::impDecomposeBlockTextPrimitiveDirect(
-    drawinglayer::primitive2d::Primitive2DContainer& rTarget,
-    SdrOutliner& rOutliner,
-    const basegfx::B2DHomMatrix& rNewTransformA,
-    const basegfx::B2DHomMatrix& rNewTransformB,
-    const basegfx::B2DRange& rClipRange)
-{
-    impTextBreakupHandler aConverter(rOutliner);
-    aConverter.decomposeBlockTextPrimitive(rNewTransformA, rNewTransformB, 
rClipRange);
-    rTarget.append(aConverter.extractPrimitive2DSequence());
 }
 
 double SdrTextObj::GetCameraZRotation() const
diff --git a/svx/source/svdraw/svdotextpathdecomposition.cxx 
b/svx/source/svdraw/svdotextpathdecomposition.cxx
index 8d3876f57810..14c9693b3d64 100644
--- a/svx/source/svdraw/svdotextpathdecomposition.cxx
+++ b/svx/source/svdraw/svdotextpathdecomposition.cxx
@@ -149,24 +149,24 @@ namespace
 
 namespace
 {
-    class impTextBreakupHandler
+    class TextHierarchyBreakupPathTextPortions : public StripPortionsHelper
     {
-        SdrOutliner&                                mrOutliner;
         ::std::vector< impPathTextPortion >         maPathTextPortions;
 
     public:
-        explicit impTextBreakupHandler(SdrOutliner& rOutliner)
-        :   mrOutliner(rOutliner)
+        virtual void processDrawPortionInfo(const DrawPortionInfo& 
rDrawPortionInfo)
         {
+            // extract and add data for TextOnPath further processing
+            maPathTextPortions.emplace_back(rDrawPortionInfo);
         }
 
-        const ::std::vector< impPathTextPortion >& 
decompositionPathTextPrimitive()
+        virtual void processDrawBulletInfo(const DrawBulletInfo&)
         {
-            // strip portions to maPathTextPortions
-            mrOutliner.StripPortions(
-                [this](const DrawPortionInfo& rInfo){ 
maPathTextPortions.emplace_back(rInfo); },
-                std::function<void(const DrawBulletInfo&)>());
+            // nothing to do here, bullets are for now ignored for TextOnLine
+        }
 
+        const ::std::vector< impPathTextPortion >& sortAndGetPathTextPortions()
+        {
             if(!maPathTextPortions.empty())
             {
                 // sort portions by paragraph, x and y
@@ -635,8 +635,9 @@ void SdrTextObj::impDecomposePathTextPrimitive(
     
rOutliner.setVisualizedPage(GetSdrPageFromXDrawPage(aViewInformation.getVisualizedPage()));
 
     // now break up to text portions
-    impTextBreakupHandler aConverter(rOutliner);
-    const ::std::vector< impPathTextPortion > rPathTextPortions = 
aConverter.decompositionPathTextPrimitive();
+    TextHierarchyBreakupPathTextPortions aBreakup;
+    rOutliner.StripPortions(aBreakup);
+    const ::std::vector< impPathTextPortion > 
rPathTextPortions(aBreakup.sortAndGetPathTextPortions());
 
     if(!rPathTextPortions.empty())
     {
diff --git a/svx/source/svdraw/svdoutl.cxx b/svx/source/svdraw/svdoutl.cxx
index 2002b5f05392..7b4cf2499e36 100644
--- a/svx/source/svdraw/svdoutl.cxx
+++ b/svx/source/svdraw/svdoutl.cxx
@@ -27,6 +27,7 @@
 #include <svl/itempool.hxx>
 #include <editeng/editview.hxx>
 #include <editeng/editeng.hxx>
+#include <drawinglayer/primitive2d/textlayoutdevice.hxx>
 
 
 SdrOutliner::SdrOutliner( SfxItemPool* pItemPool, OutlinerMode nMode )
@@ -121,4 +122,108 @@ std::optional<bool> 
SdrOutliner::GetCompatFlag(SdrCompatibilityFlag eFlag) const
     return {};
 }
 
+sal_Int16 TextHierarchyBreakupOutliner::getOutlineLevelFromParagraph(sal_Int32 
nPara) const
+{
+    sal_Int16 nDepth(mrOutliner.GetDepth(nPara));
+    EBulletInfo eInfo(mrOutliner.GetBulletInfo(nPara));
+    // Pass -1 to signal VclMetafileProcessor2D that there is no active
+    // bullets/numbering in this paragraph (i.e. this is normal text)
+    return eInfo.bVisible ?  nDepth : -1;
+}
+
+sal_Int32 TextHierarchyBreakupOutliner::getParagraphCount() const
+{
+    return mrOutliner.GetParagraphCount();
+}
+
+TextHierarchyBreakupOutliner::TextHierarchyBreakupOutliner(
+    SdrOutliner& rOutliner,
+    const basegfx::B2DHomMatrix& rNewTransformA,
+    const basegfx::B2DHomMatrix& rNewTransformB)
+: TextHierarchyBreakup(
+    rNewTransformA,
+    rNewTransformB)
+, mrOutliner(rOutliner)
+{
+}
+
+void TextHierarchyBreakupBlockText::processDrawPortionInfo(const 
DrawPortionInfo& rDrawPortionInfo)
+{
+    // Is clipping wanted? This is text clipping; only accept a portion
+    // if it's completely in the range
+    if(!mrClipRange.isEmpty())
+    {
+        // Test start position first; this allows to not get the text range at
+        // all if text is far outside
+        const basegfx::B2DPoint 
aStartPosition(rDrawPortionInfo.mrStartPos.X(), 
rDrawPortionInfo.mrStartPos.Y());
+
+        if(!mrClipRange.isInside(aStartPosition))
+        {
+            return;
+        }
+
+        // Start position is inside. Get TextBoundRect and TopLeft next
+        drawinglayer::primitive2d::TextLayouterDevice aTextLayouterDevice;
+        aTextLayouterDevice.setFont(rDrawPortionInfo.mrFont);
+
+        const basegfx::B2DRange aTextBoundRect(
+            aTextLayouterDevice.getTextBoundRect(
+                rDrawPortionInfo.maText, rDrawPortionInfo.mnTextStart, 
rDrawPortionInfo.mnTextLen));
+        const basegfx::B2DPoint aTopLeft(aTextBoundRect.getMinimum() + 
aStartPosition);
+
+        if(!mrClipRange.isInside(aTopLeft))
+        {
+            return;
+        }
+
+        // TopLeft is inside. Get BottomRight and check
+        const basegfx::B2DPoint aBottomRight(aTextBoundRect.getMaximum() + 
aStartPosition);
+
+        if(!mrClipRange.isInside(aBottomRight))
+        {
+            return;
+        }
+
+        // all inside, clip was successful
+    }
+
+    TextHierarchyBreakupOutliner::processDrawPortionInfo(rDrawPortionInfo);
+}
+
+TextHierarchyBreakupBlockText::TextHierarchyBreakupBlockText(
+    SdrOutliner& rOutliner,
+    const basegfx::B2DHomMatrix& rNewTransformA,
+    const basegfx::B2DHomMatrix& rNewTransformB,
+    const basegfx::B2DRange& rClipRange)
+: TextHierarchyBreakupOutliner(
+    rOutliner,
+    rNewTransformA,
+    rNewTransformB)
+, mrClipRange(rClipRange)
+{
+}
+
+void TextHierarchyBreakupContourText::processDrawPortionInfo(const 
DrawPortionInfo& rDrawPortionInfo)
+    {
+        // for contour text, ignore (clip away) all portions which are below
+        // the visible area given by maScale
+        if(static_cast<double>(rDrawPortionInfo.mrStartPos.Y()) < 
maScale.getY())
+        {
+            
TextHierarchyBreakupOutliner::processDrawPortionInfo(rDrawPortionInfo);
+        }
+    }
+
+TextHierarchyBreakupContourText::TextHierarchyBreakupContourText(
+    SdrOutliner& rOutliner,
+    const basegfx::B2DHomMatrix& rNewTransformA,
+    const basegfx::B2DHomMatrix& rNewTransformB,
+    const basegfx::B2DVector& rScale)
+: TextHierarchyBreakupOutliner(
+    rOutliner,
+    rNewTransformA,
+    rNewTransformB)
+, maScale(rScale)
+{
+}
+
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */

Reply via email to