drawinglayer/source/processor2d/vclpixelprocessor2d.cxx                        
                 |    4 
 include/animations/animationnodehelper.hxx                                     
                 |   54 -
 include/drawinglayer/primitive2d/baseprimitive2d.hxx                           
                 |    6 
 sd/qa/unit/tiledrendering/data/SlideRenderingTest_Animations.odp               
                 |binary
 
sd/qa/unit/tiledrendering/data/SlideRenderingTest_Animations_SecondParagraphMultipleEffects.odp
 |binary
 sd/qa/unit/tiledrendering/data/SlideRenderingTest_TextBox.odp                  
                 |binary
 sd/qa/unit/tiledrendering/tiledrendering.cxx                                   
                 |  126 +++
 sd/source/ui/inc/SlideshowLayerRenderer.hxx                                    
                 |   69 +
 sd/source/ui/tools/SlideshowLayerRenderer.cxx                                  
                 |  417 +++++++---
 9 files changed, 525 insertions(+), 151 deletions(-)

New commits:
commit 0a78f5de99ae80e84d3ab33ddef6cf7642743425
Author:     Tomaž Vajngerl <tomaz.vajng...@collabora.co.uk>
AuthorDate: Wed Sep 25 15:10:05 2024 +0200
Commit:     Miklos Vajna <vmik...@collabora.com>
CommitDate: Thu Jan 2 16:19:12 2025 +0100

    slideshow: Render each animated paragraph in its own layer
    
    We need to render each animated paragraph of a SdrTextObj in its
    own layer. This is not easy to do, so modify the PrimitiveContainer
    after rendering and turn off all the paragraphs that shouldn't be
    rendered. In addition also turn off rendering of the object itself
    (the fill and stroke). This can be done by setting the newly added
    setVisible method on the desired TextHierarchyParagraphPrimitive2D
    to false (and others to true), and turning the primitives to render
    the object to setVisible = false. If the primitive is not "visible"
    it will be skipped in the when rendering with VclPixelProcessor2D.
    
    Also changes the SlideshowLayerRenderer to take into account the
    rendering of paragraphs. We remember all the paragraphs for an text
    object that are animated, then we take this into account when we
    do render passes.
    
    We also need to take care of the paragraph hash, which needs to be
    set consistently for all animated paragraphs (so the layers can be
    identified correctly for animated paragraphs).
    
    Change-Id: Ice3b9e1f586c3fca81cf5da809fa88c4b9c87c37
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/179599
    Tested-by: Jenkins
    Reviewed-by: Miklos Vajna <vmik...@collabora.com>

diff --git a/drawinglayer/source/processor2d/vclpixelprocessor2d.cxx 
b/drawinglayer/source/processor2d/vclpixelprocessor2d.cxx
index 0ff0ca53c560..579940313d6b 100644
--- a/drawinglayer/source/processor2d/vclpixelprocessor2d.cxx
+++ b/drawinglayer/source/processor2d/vclpixelprocessor2d.cxx
@@ -192,6 +192,10 @@ bool 
VclPixelProcessor2D::tryDrawPolygonStrokePrimitive2DDirect(
 
 void VclPixelProcessor2D::processBasePrimitive2D(const 
primitive2d::BasePrimitive2D& rCandidate)
 {
+    // Skip, if not visible
+    if (!rCandidate.getVisible())
+        return;
+
     switch (rCandidate.getPrimitive2DID())
     {
         case PRIMITIVE2D_ID_TEXTSIMPLEPORTIONPRIMITIVE2D:
diff --git a/include/animations/animationnodehelper.hxx 
b/include/animations/animationnodehelper.hxx
index 8c79342c789b..11ac2abd4ef8 100644
--- a/include/animations/animationnodehelper.hxx
+++ b/include/animations/animationnodehelper.hxx
@@ -79,36 +79,42 @@ namespace anim
         }
     }
 
-    inline bool getVisibilityProperty(
-        const css::uno::Reference< css::animations::XAnimate >& xAnimateNode, 
bool& bReturn)
+    inline bool getVisibilityPropertyForAny(css::uno::Any const& rAny)
     {
-        if( 
xAnimateNode->getAttributeName().equalsIgnoreAsciiCase("visibility") )
-        {
-            bool bVisible( false );
-            css::uno::Any aAny( xAnimateNode->getTo() );
+        bool bVisible = false;
+        css::uno::Any aAny(rAny);
 
-            // try to extract bool value
-            if( !(aAny >>= bVisible) )
+        // try to extract bool value
+        if (!(aAny >>= bVisible))
+        {
+            // try to extract string
+            OUString aString;
+            if (aAny >>= aString)
             {
-                // try to extract string
-                OUString aString;
-                if( aAny >>= aString )
+                // we also take the strings "true" and "false",
+                // as well as "on" and "off" here
+                if (aString.equalsIgnoreAsciiCase("true") ||
+                    aString.equalsIgnoreAsciiCase("on"))
                 {
-                    // we also take the strings "true" and "false",
-                    // as well as "on" and "off" here
-                    if( aString.equalsIgnoreAsciiCase("true") ||
-                        aString.equalsIgnoreAsciiCase("on") )
-                    {
-                        bVisible = true;
-                    }
-                    if( aString.equalsIgnoreAsciiCase("false") ||
-                        aString.equalsIgnoreAsciiCase("off") )
-                    {
-                        bVisible = false;
-                    }
+                    bVisible = true;
+                }
+                if (aString.equalsIgnoreAsciiCase("false") ||
+                    aString.equalsIgnoreAsciiCase("off"))
+                {
+                    bVisible = false;
                 }
             }
-            bReturn = bVisible;
+        }
+        return bVisible;
+    }
+
+    inline bool getVisibilityProperty(
+        const css::uno::Reference< css::animations::XAnimate >& xAnimateNode, 
bool& bReturn)
+    {
+        if 
(xAnimateNode->getAttributeName().equalsIgnoreAsciiCase("visibility"))
+        {
+            css::uno::Any aAny(xAnimateNode->getTo());
+            bReturn = getVisibilityPropertyForAny(aAny);
             return true;
         }
 
diff --git a/include/drawinglayer/primitive2d/baseprimitive2d.hxx 
b/include/drawinglayer/primitive2d/baseprimitive2d.hxx
index 276e8adf817c..b53fd73d7711 100644
--- a/include/drawinglayer/primitive2d/baseprimitive2d.hxx
+++ b/include/drawinglayer/primitive2d/baseprimitive2d.hxx
@@ -120,6 +120,8 @@ namespace drawinglayer::primitive2d
 */
 class DRAWINGLAYERCORE_DLLPUBLIC BasePrimitive2D : public 
salhelper::SimpleReferenceObject
 {
+    bool mbVisible = true;
+
     BasePrimitive2D(const BasePrimitive2D&) = delete;
     BasePrimitive2D& operator=(const BasePrimitive2D&) = delete;
 
@@ -165,6 +167,10 @@ public:
 
     // XAccounting
     virtual sal_Int64 estimateUsage();
+
+    void setVisible(bool bVisible) { mbVisible = bVisible; }
+
+    bool getVisible() const { return mbVisible; }
 };
 
 /**
diff --git a/sd/qa/unit/tiledrendering/data/SlideRenderingTest_Animations.odp 
b/sd/qa/unit/tiledrendering/data/SlideRenderingTest_Animations.odp
index b827e0a94cf6..73267d19ff44 100644
Binary files a/sd/qa/unit/tiledrendering/data/SlideRenderingTest_Animations.odp 
and b/sd/qa/unit/tiledrendering/data/SlideRenderingTest_Animations.odp differ
diff --git 
a/sd/qa/unit/tiledrendering/data/SlideRenderingTest_Animations_SecondParagraphMultipleEffects.odp
 
b/sd/qa/unit/tiledrendering/data/SlideRenderingTest_Animations_SecondParagraphMultipleEffects.odp
new file mode 100644
index 000000000000..1c8cb4b4d600
Binary files /dev/null and 
b/sd/qa/unit/tiledrendering/data/SlideRenderingTest_Animations_SecondParagraphMultipleEffects.odp
 differ
diff --git a/sd/qa/unit/tiledrendering/data/SlideRenderingTest_TextBox.odp 
b/sd/qa/unit/tiledrendering/data/SlideRenderingTest_TextBox.odp
new file mode 100644
index 000000000000..e7a59e7c44ea
Binary files /dev/null and 
b/sd/qa/unit/tiledrendering/data/SlideRenderingTest_TextBox.odp differ
diff --git a/sd/qa/unit/tiledrendering/tiledrendering.cxx 
b/sd/qa/unit/tiledrendering/tiledrendering.cxx
index a071a56e2e2a..cfa136fd3a31 100644
--- a/sd/qa/unit/tiledrendering/tiledrendering.cxx
+++ b/sd/qa/unit/tiledrendering/tiledrendering.cxx
@@ -3260,6 +3260,132 @@ CPPUNIT_TEST_FIXTURE(SdTiledRenderingTest, 
testSlideshowLayeredRendering_Animati
     pXImpressDocument->postSlideshowCleanup();
 }
 
+CPPUNIT_TEST_FIXTURE(SdTiledRenderingTest, 
testSlideshowLayeredRendering_Animation_TextBox)
+{
+    SdXImpressDocument* pXImpressDocument = 
createDoc("SlideRenderingTest_TextBox.odp");
+    
pXImpressDocument->initializeForTiledRendering(uno::Sequence<beans::PropertyValue>());
+    sd::ViewShell* pViewShell = 
pXImpressDocument->GetDocShell()->GetViewShell();
+    CPPUNIT_ASSERT(pViewShell);
+    SdPage* pPage = pViewShell->GetActualPage();
+    CPPUNIT_ASSERT(pPage);
+    sal_Int32 nViewWidth = 2000;
+    sal_Int32 nViewHeight = 2000;
+
+    std::string sHash = GetInterfaceHash(GetXDrawPageForSdrPage(pPage));
+    CPPUNIT_ASSERT(pXImpressDocument->createSlideRenderer(sHash.c_str(), 0, 
nViewWidth, nViewHeight, true, true));
+    CPPUNIT_ASSERT_EQUAL(2000, nViewWidth);
+    CPPUNIT_ASSERT_EQUAL(1125, nViewHeight);
+
+    {
+        std::vector<sal_uInt8> pBuffer(nViewWidth * nViewHeight * 4);
+        bool bIsBitmapLayer = false;
+        OUString rJsonMsg;
+        
CPPUNIT_ASSERT(!pXImpressDocument->renderNextSlideLayer(pBuffer.data(), 
bIsBitmapLayer, rJsonMsg));
+
+        debugWriteImageToFile(1, pBuffer, nViewWidth, nViewHeight, 
rJsonMsg.toUtf8().getStr());
+    }
+
+    {
+        std::vector<sal_uInt8> pBuffer(nViewWidth * nViewHeight * 4);
+        bool bIsBitmapLayer = false;
+        OUString rJsonMsg;
+        
CPPUNIT_ASSERT(!pXImpressDocument->renderNextSlideLayer(pBuffer.data(), 
bIsBitmapLayer, rJsonMsg));
+
+        debugWriteImageToFile(2, pBuffer, nViewWidth, nViewHeight, 
rJsonMsg.toUtf8().getStr());
+    }
+
+    {
+        std::vector<sal_uInt8> pBuffer(nViewWidth * nViewHeight * 4);
+        bool bIsBitmapLayer = false;
+        OUString rJsonMsg;
+        
CPPUNIT_ASSERT(!pXImpressDocument->renderNextSlideLayer(pBuffer.data(), 
bIsBitmapLayer, rJsonMsg));
+
+        debugWriteImageToFile(3, pBuffer, nViewWidth, nViewHeight, 
rJsonMsg.toUtf8().getStr());
+    }
+
+    {
+        std::vector<sal_uInt8> pBuffer(nViewWidth * nViewHeight * 4);
+        bool bIsBitmapLayer = false;
+        OUString rJsonMsg;
+        
CPPUNIT_ASSERT(!pXImpressDocument->renderNextSlideLayer(pBuffer.data(), 
bIsBitmapLayer, rJsonMsg));
+
+        debugWriteImageToFile(4, pBuffer, nViewWidth, nViewHeight, 
rJsonMsg.toUtf8().getStr());
+    }
+
+    {
+        std::vector<sal_uInt8> pBuffer(nViewWidth * nViewHeight * 4);
+        bool bIsBitmapLayer = false;
+        OUString rJsonMsg;
+        
CPPUNIT_ASSERT(!pXImpressDocument->renderNextSlideLayer(pBuffer.data(), 
bIsBitmapLayer, rJsonMsg));
+
+        debugWriteImageToFile(5, pBuffer, nViewWidth, nViewHeight, 
rJsonMsg.toUtf8().getStr());
+    }
+
+    // Check we are done
+    {
+        std::vector<sal_uInt8> pBuffer(nViewWidth * nViewHeight * 4);
+        bool bIsBitmapLayer = false;
+        OUString rJsonMsg;
+        CPPUNIT_ASSERT(pXImpressDocument->renderNextSlideLayer(pBuffer.data(), 
bIsBitmapLayer, rJsonMsg));
+    }
+
+    pXImpressDocument->postSlideshowCleanup();
+}
+
+CPPUNIT_TEST_FIXTURE(SdTiledRenderingTest, 
testSlideshowLayeredRendering_Animation_TextBox_SecondParagraphMultipleEffects)
+{
+    SdXImpressDocument* pXImpressDocument = 
createDoc("SlideRenderingTest_Animations_SecondParagraphMultipleEffects.odp");
+    
pXImpressDocument->initializeForTiledRendering(uno::Sequence<beans::PropertyValue>());
+    sd::ViewShell* pViewShell = 
pXImpressDocument->GetDocShell()->GetViewShell();
+    CPPUNIT_ASSERT(pViewShell);
+    SdPage* pPage = pViewShell->GetActualPage();
+    CPPUNIT_ASSERT(pPage);
+    sal_Int32 nViewWidth = 2000;
+    sal_Int32 nViewHeight = 2000;
+
+    std::string sHash = GetInterfaceHash(GetXDrawPageForSdrPage(pPage));
+    CPPUNIT_ASSERT(pXImpressDocument->createSlideRenderer(sHash.c_str(), 0, 
nViewWidth, nViewHeight, true, true));
+    CPPUNIT_ASSERT_EQUAL(2000, nViewWidth);
+    CPPUNIT_ASSERT_EQUAL(1125, nViewHeight);
+
+    {
+        std::vector<sal_uInt8> pBuffer(nViewWidth * nViewHeight * 4);
+        bool bIsBitmapLayer = false;
+        OUString rJsonMsg;
+        
CPPUNIT_ASSERT(!pXImpressDocument->renderNextSlideLayer(pBuffer.data(), 
bIsBitmapLayer, rJsonMsg));
+
+        debugWriteImageToFile(1, pBuffer, nViewWidth, nViewHeight, 
rJsonMsg.toUtf8().getStr());
+    }
+
+    {
+        std::vector<sal_uInt8> pBuffer(nViewWidth * nViewHeight * 4);
+        bool bIsBitmapLayer = false;
+        OUString rJsonMsg;
+        
CPPUNIT_ASSERT(!pXImpressDocument->renderNextSlideLayer(pBuffer.data(), 
bIsBitmapLayer, rJsonMsg));
+
+        debugWriteImageToFile(2, pBuffer, nViewWidth, nViewHeight, 
rJsonMsg.toUtf8().getStr());
+    }
+
+    {
+        std::vector<sal_uInt8> pBuffer(nViewWidth * nViewHeight * 4);
+        bool bIsBitmapLayer = false;
+        OUString rJsonMsg;
+        
CPPUNIT_ASSERT(!pXImpressDocument->renderNextSlideLayer(pBuffer.data(), 
bIsBitmapLayer, rJsonMsg));
+
+        debugWriteImageToFile(3, pBuffer, nViewWidth, nViewHeight, 
rJsonMsg.toUtf8().getStr());
+    }
+
+    // Check we are done
+    {
+        std::vector<sal_uInt8> pBuffer(nViewWidth * nViewHeight * 4);
+        bool bIsBitmapLayer = false;
+        OUString rJsonMsg;
+        CPPUNIT_ASSERT(pXImpressDocument->renderNextSlideLayer(pBuffer.data(), 
bIsBitmapLayer, rJsonMsg));
+    }
+
+    pXImpressDocument->postSlideshowCleanup();
+}
+
 CPPUNIT_PLUGIN_IMPLEMENT();
 
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/inc/SlideshowLayerRenderer.hxx 
b/sd/source/ui/inc/SlideshowLayerRenderer.hxx
index 5cd590e920e7..e83eae573a40 100644
--- a/sd/source/ui/inc/SlideshowLayerRenderer.hxx
+++ b/sd/source/ui/inc/SlideshowLayerRenderer.hxx
@@ -16,16 +16,24 @@
 #include <tools/gen.hxx>
 #include <tools/helpers.hxx>
 
+#include <CustomAnimationEffect.hxx>
+
 #include <deque>
-#include <map>
+#include <vector>
+#include <optional>
+#include <unordered_map>
 #include <unordered_set>
 
 class SdrPage;
 class SdrModel;
 class SdrObject;
-
 class Size;
 
+namespace com::sun::star::animations
+{
+class XAnimate;
+}
+
 namespace sd
 {
 struct RenderContext;
@@ -39,6 +47,19 @@ enum class RenderStage
     Count
 };
 
+struct AnimationLayerInfo
+{
+    OString msHash;
+    std::optional<bool> moInitiallyVisible;
+};
+
+struct AnimationRenderInfo
+{
+    std::optional<AnimationLayerInfo> moObjectInfo;
+    std::vector<sal_Int32> maParagraphs;
+    std::unordered_map<sal_Int32, AnimationLayerInfo> maParagraphInfos;
+};
+
 /** Holds rendering state, properties and switches through all rendering 
passes */
 struct RenderState
 {
@@ -47,18 +68,21 @@ struct RenderState
     bool mbStopRenderingWhenField = true;
 
     std::unordered_set<SdrObject*> maObjectsDone;
-    std::unordered_set<SdrObject*> maInAnimation;
-    std::map<SdrObject*, OString> maAnimationTargetHash;
-    std::map<SdrObject*, bool> maInitiallyVisible;
+
+    std::unordered_map<SdrObject*, AnimationRenderInfo> 
maAnimationRenderInfoList;
+
     sal_Int32 mnIndex[static_cast<unsigned>(RenderStage::Count)] = { 0, 0, 0, 
0 };
     SdrObject* mpCurrentTarget = nullptr;
+    sal_Int32 mnCurrentTargetParagraph = -1;
+
+    sal_Int32 mnRenderedObjectsInPass = 0;
 
-    bool mbFirstObjectInPass = true;
-    bool mbPassHasOutput = false;
     bool mbSkipAllInThisPass = false;
 
     sal_Int32 mnCurrentPass = 0;
 
+    std::deque<sal_Int32> maParagraphsToRender;
+
     /// increments index depending on the current render stage
     void incrementIndex() { mnIndex[static_cast<unsigned>(meStage)]++; }
 
@@ -80,20 +104,28 @@ struct RenderState
     /// returns the current target element for which layer is created if any
     SdrObject* currentTarget() const { return mpCurrentTarget; }
 
+    /// returns the current target paragraph index or -1 if paragraph is not 
relevant
+    sal_Int32 currentTargetParagraph() const { return 
mnCurrentTargetParagraph; }
+
     /// resets properties that are valid for one pass
     void resetPass()
     {
-        mbFirstObjectInPass = true;
-        mbPassHasOutput = false;
+        mnRenderedObjectsInPass = 0;
         mbSkipAllInThisPass = false;
         mpCurrentTarget = nullptr;
+        mnCurrentTargetParagraph = -1;
     }
 
+    bool hasPassAnyRenderedOutput() const { return mnRenderedObjectsInPass > 
0; }
+
+    /// is first rendered object in pass
+    bool isFirstObjectInPass() const { return mnRenderedObjectsInPass == 0; }
+
     /// return if there was no rendering output in the pass
     bool noMoreOutput() const
     {
-        // no output and we don't skip anything
-        return !mbPassHasOutput && !mbSkipAllInThisPass;
+        // no output and we didn't skip anything and nothing was rendered
+        return !hasPassAnyRenderedOutput() && !mbSkipAllInThisPass;
     }
 
     /// should include background in rendering
@@ -104,19 +136,6 @@ struct RenderState
         return maObjectsDone.find(pObject) != maObjectsDone.end();
     }
 
-    bool isObjectInAnimation(SdrObject* pObject) const
-    {
-        return maInAnimation.find(pObject) != maInAnimation.end();
-    }
-
-    bool isObjectInitiallyVisible(SdrObject* pObject) const
-    {
-        bool bInitiallyVisible = true;
-        if (maInitiallyVisible.contains(pObject))
-            bInitiallyVisible = maInitiallyVisible.at(pObject);
-        return bInitiallyVisible;
-    }
-
     static std::string getObjectHash(SdrObject* pObject)
     {
         css::uno::Reference<css::drawing::XShape> xShape = 
GetXShapeForSdrObject(pObject);
@@ -144,7 +163,9 @@ private:
 
     void createViewAndDraw(RenderContext& rRenderContext);
     void writeJSON(OString& rJsonMsg);
+
     void setupAnimations();
+    void resolveEffect(CustomAnimationEffectPtr const& rEffect);
 
 public:
     SlideshowLayerRenderer(SdrPage& rPage);
diff --git a/sd/source/ui/tools/SlideshowLayerRenderer.cxx 
b/sd/source/ui/tools/SlideshowLayerRenderer.cxx
index bf5e34a65a6a..89084a9aef47 100644
--- a/sd/source/ui/tools/SlideshowLayerRenderer.cxx
+++ b/sd/source/ui/tools/SlideshowLayerRenderer.cxx
@@ -8,7 +8,6 @@
  */
 
 #include <SlideshowLayerRenderer.hxx>
-#include <CustomAnimationEffect.hxx>
 #include <svx/svdpage.hxx>
 #include <svx/svdmodel.hxx>
 #include <svx/svdview.hxx>
@@ -17,9 +16,15 @@
 #include <svx/sdr/contact/objectcontact.hxx>
 #include <svx/svdoutl.hxx>
 #include <svx/svdpagv.hxx>
+#include <svx/sdr/primitive2d/svx_primitivetypes2d.hxx>
+#include <svx/unoshape.hxx>
+
 #include <vcl/virdev.hxx>
 #include <tools/json_writer.hxx>
 #include <editeng/editeng.hxx>
+#include <animations/animationnodehelper.hxx>
+#include <sdpage.hxx>
+#include <comphelper/servicehelper.hxx>
 
 #include <com/sun/star/animations/XAnimate.hpp>
 #include <com/sun/star/animations/XAnimationNode.hpp>
@@ -27,10 +32,13 @@
 #include <com/sun/star/drawing/XShape.hpp>
 #include <com/sun/star/presentation/ParagraphTarget.hpp>
 
-#include <animations/animationnodehelper.hxx>
-#include <sdpage.hxx>
-#include <comphelper/servicehelper.hxx>
-#include <svx/unoshape.hxx>
+#include <drawinglayer/primitive2d/Primitive2DContainer.hxx>
+#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx>
+#include <drawinglayer/primitive2d/BufferedDecompositionPrimitive2D.hxx>
+#include <drawinglayer/primitive2d/texthierarchyprimitive2d.hxx>
+#include <drawinglayer/primitive2d/texthierarchyprimitive2d.hxx>
+
+#include <drawinglayer/tools/primitive2dxmldump.hxx>
 
 using namespace ::com::sun::star;
 
@@ -96,6 +104,115 @@ bool hasFields(SdrObject* pObject)
     return false;
 }
 
+void changePolyPolys(drawinglayer::primitive2d::Primitive2DContainer& 
rContainer,
+                     bool bRenderObject)
+{
+    for (auto& pBasePrimitive : rContainer)
+    {
+        if (pBasePrimitive->getPrimitive2DID() == 
PRIMITIVE2D_ID_POLYPOLYGONCOLORPRIMITIVE2D
+            || pBasePrimitive->getPrimitive2DID() == 
PRIMITIVE2D_ID_POLYPOLYGONGRADIENTPRIMITIVE2D
+            || pBasePrimitive->getPrimitive2DID() == 
PRIMITIVE2D_ID_POLYPOLYGONGRAPHICPRIMITIVE2D
+            || pBasePrimitive->getPrimitive2DID() == 
PRIMITIVE2D_ID_POLYPOLYGONHATCHPRIMITIVE2D
+            || pBasePrimitive->getPrimitive2DID() == 
PRIMITIVE2D_ID_POLYPOLYGONHAIRLINEPRIMITIVE2D)
+        {
+            pBasePrimitive->setVisible(bRenderObject);
+        }
+    }
+}
+
+void changeBackground(drawinglayer::primitive2d::Primitive2DContainer const& 
rContainer,
+                      bool bRenderObject)
+{
+    for (size_t i = 0; i < rContainer.size(); i++)
+    {
+        drawinglayer::primitive2d::BasePrimitive2D* pBasePrimitive = 
rContainer[i].get();
+        if (pBasePrimitive->getPrimitive2DID() == 
PRIMITIVE2D_ID_SDRRECTANGLEPRIMITIVE2D)
+        {
+            drawinglayer::primitive2d::Primitive2DContainer 
aPrimitiveContainer;
+            pBasePrimitive->get2DDecomposition(aPrimitiveContainer,
+                                               
drawinglayer::geometry::ViewInformation2D());
+            changePolyPolys(aPrimitiveContainer, bRenderObject);
+        }
+    }
+}
+
+drawinglayer::primitive2d::TextHierarchyBlockPrimitive2D*
+findTextBlock(drawinglayer::primitive2d::Primitive2DContainer const& 
rContainer)
+{
+    for (size_t i = 0; i < rContainer.size(); i++)
+    {
+        drawinglayer::primitive2d::BasePrimitive2D* pBasePrimitive = 
rContainer[i].get();
+        if (pBasePrimitive->getPrimitive2DID() == 
PRIMITIVE2D_ID_TEXTHIERARCHYBLOCKPRIMITIVE2D)
+        {
+            auto* pPrimitive
+                = 
dynamic_cast<drawinglayer::primitive2d::TextHierarchyBlockPrimitive2D*>(
+                    pBasePrimitive);
+            if (pPrimitive)
+                return pPrimitive;
+        }
+        else
+        {
+            auto* pGroupPrimitive
+                = 
dynamic_cast<drawinglayer::primitive2d::GroupPrimitive2D*>(pBasePrimitive);
+            if (pGroupPrimitive)
+            {
+                auto* pTextBlock = 
findTextBlock(pGroupPrimitive->getChildren());
+                if (pTextBlock)
+                    return pTextBlock;
+            }
+
+            auto* pBufferedPrimitive
+                = 
dynamic_cast<drawinglayer::primitive2d::BufferedDecompositionPrimitive2D*>(
+                    pBasePrimitive);
+            if (pBufferedPrimitive)
+            {
+                // try to decompose
+                drawinglayer::primitive2d::Primitive2DContainer 
aPrimitiveContainer;
+                pBasePrimitive->get2DDecomposition(aPrimitiveContainer,
+                                                   
drawinglayer::geometry::ViewInformation2D());
+                auto* pTextBlock = findTextBlock(aPrimitiveContainer);
+                if (pTextBlock)
+                    return pTextBlock;
+            }
+        }
+    }
+    return nullptr;
+}
+
+void modifyParagraphs(drawinglayer::primitive2d::Primitive2DContainer& 
rContainer,
+                      std::deque<sal_Int32> const& rPreserveIndices, bool 
bRenderObject)
+{
+    auto* pTextBlock = findTextBlock(rContainer);
+
+    if (pTextBlock)
+    {
+        auto& rPrimitives = 
const_cast<drawinglayer::primitive2d::Primitive2DContainer&>(
+            pTextBlock->getChildren());
+        sal_Int32 nIndex = 0;
+        for (auto& pPrimitive : rPrimitives)
+        {
+            if (pPrimitive->getPrimitive2DID() == 
PRIMITIVE2D_ID_TEXTHIERARCHYPARAGRAPHPRIMITIVE2D)
+            {
+                auto& pParagraphPrimitive2d
+                    = 
static_cast<drawinglayer::primitive2d::TextHierarchyParagraphPrimitive2D&>(
+                        *pPrimitive);
+
+                // find the index
+                auto aIterator
+                    = std::find(rPreserveIndices.begin(), 
rPreserveIndices.end(), nIndex);
+
+                // is index in preserve list - if false, hide the primitive
+                bool bHideIndex = aIterator == rPreserveIndices.end();
+
+                pParagraphPrimitive2d.setVisible(!bHideIndex);
+            }
+            nIndex++;
+        }
+
+        changeBackground(rContainer, bRenderObject);
+    }
+}
+
 /** VOC redirector to control which object should be rendered and which not */
 class ObjectRedirector : public sdr::contact::ViewObjectContactRedirector
 {
@@ -116,7 +233,7 @@ public:
         // Generate single pass for background layer
         if (mrRenderState.meStage == RenderStage::Background)
         {
-            mrRenderState.mbPassHasOutput = true;
+            mrRenderState.mnRenderedObjectsInPass++;
             mrRenderState.mbSkipAllInThisPass = true;
             return;
         }
@@ -148,7 +265,7 @@ public:
         // Check if we are in correct stage
         if (mrRenderState.meStage == RenderStage::Master && 
!pPage->IsMasterPage())
         {
-            if (mrRenderState.mbFirstObjectInPass)
+            if (mrRenderState.isFirstObjectInPass())
             {
                 // if this is the first object - change from master to slide
                 // means we are done with rendering of master layers
@@ -162,18 +279,45 @@ public:
             }
         }
 
-        if (mrRenderState.isObjectInAnimation(pObject))
+        // Paragraph rendering switches
+        bool bRenderOtherParagraphs = false;
+        std::deque<sal_Int32> nOtherParagraphs;
+
+        // check if object is in animation
+        auto aIterator = mrRenderState.maAnimationRenderInfoList.find(pObject);
+        if (aIterator != mrRenderState.maAnimationRenderInfoList.end())
         {
             // Animated object has to be only one in the render
-            mrRenderState.mbSkipAllInThisPass = true;
+            mrRenderState.mbSkipAllInThisPass = true; // skip all next objects
 
-            // Animated object cannot be attached to the previous object
-            if (!mrRenderState.mbFirstObjectInPass)
+            // Force a new layer
+            if (!mrRenderState.isFirstObjectInPass())
                 return;
-        }
 
-        if (mrRenderState.meStage == RenderStage::Master && hasFields(pObject)
-            && mrRenderState.mbStopRenderingWhenField && 
!mrRenderState.mbFirstObjectInPass)
+            AnimationRenderInfo aInfo = aIterator->second;
+
+            if (mrRenderState.maParagraphsToRender.empty()
+                && !aInfo.maParagraphs.empty()) // we need to render paragraphs
+            {
+                auto* pTextObject = dynamic_cast<SdrTextObj*>(pObject);
+                if (pTextObject)
+                {
+                    sal_Int32 nNumberOfParagraphs = 
pTextObject->GetOutlinerParaObject()->Count();
+
+                    for (sal_Int32 nParagraph = 0; nParagraph < 
nNumberOfParagraphs; ++nParagraph)
+                        nOtherParagraphs.push_back(nParagraph);
+
+                    for (sal_Int32 nParagraph : aInfo.maParagraphs)
+                    {
+                        
mrRenderState.maParagraphsToRender.push_back(nParagraph);
+                        std::erase(nOtherParagraphs, nParagraph);
+                    }
+                    bRenderOtherParagraphs = true;
+                }
+            }
+        }
+        else if (mrRenderState.meStage == RenderStage::Master && 
hasFields(pObject)
+                 && mrRenderState.mbStopRenderingWhenField && 
!mrRenderState.isFirstObjectInPass())
         {
             mrRenderState.mbStopRenderingWhenField = false;
             mrRenderState.mbSkipAllInThisPass = true;
@@ -186,9 +330,33 @@ public:
         
sdr::contact::ViewObjectContactRedirector::createRedirectedPrimitive2DSequence(
             rOriginal, rDisplayInfo, rVisitor);
 
-        mrRenderState.mbFirstObjectInPass = false;
-        mrRenderState.maObjectsDone.insert(pObject);
-        mrRenderState.mbPassHasOutput = true;
+        if (!mrRenderState.maParagraphsToRender.empty())
+        {
+            auto rContainer
+                = 
static_cast<drawinglayer::primitive2d::Primitive2DContainer&>(rVisitor);
+
+            if (bRenderOtherParagraphs)
+            {
+                modifyParagraphs(rContainer, nOtherParagraphs, true); // 
render the object
+                mrRenderState.mnCurrentTargetParagraph = -1;
+            }
+            else
+            {
+                sal_Int32 nParagraph = 
mrRenderState.maParagraphsToRender.front();
+                mrRenderState.maParagraphsToRender.pop_front();
+
+                std::deque<sal_Int32> aPreserveParagraphs{ nParagraph };
+                mrRenderState.mnCurrentTargetParagraph = nParagraph;
+                modifyParagraphs(rContainer, aPreserveParagraphs,
+                                 false); // render only the paragraphs
+            }
+        }
+
+        if (mrRenderState.maParagraphsToRender.empty())
+        {
+            mrRenderState.mnRenderedObjectsInPass++;
+            mrRenderState.maObjectsDone.insert(pObject);
+        }
     }
 };
 
@@ -207,6 +375,16 @@ bool hasEmptyMaster(SdrPage const& rPage)
     return true;
 }
 
+SdrObject* getObjectForShape(uno::Reference<drawing::XShape> const& xShape)
+{
+    if (!xShape.is())
+        return nullptr;
+    SvxShape* pShape = comphelper::getFromUnoTunnel<SvxShape>(xShape);
+    if (pShape)
+        return pShape->GetSdrObject();
+    return nullptr;
+}
+
 } // end anonymous namespace
 
 SlideshowLayerRenderer::SlideshowLayerRenderer(SdrPage& rPage)
@@ -217,92 +395,90 @@ SlideshowLayerRenderer::SlideshowLayerRenderer(SdrPage& 
rPage)
     setupAnimations();
 }
 
-void SlideshowLayerRenderer::setupAnimations()
+void SlideshowLayerRenderer::resolveEffect(CustomAnimationEffectPtr const& 
rEffect)
 {
-    auto* pSdPage = dynamic_cast<SdPage*>(&mrPage);
+    SdrObject* pObject = nullptr;
+    sal_Int32 nParagraph = -1;
+    uno::Reference<drawing::XShape> xShape;
 
-    if (!pSdPage)
+    uno::Any aTargetAny(rEffect->getTarget());
+
+    if ((aTargetAny >>= xShape) && xShape.is())
+    {
+        pObject = getObjectForShape(xShape);
+    }
+    else // if target is not a shape - could be paragraph target containing a 
shape
+    {
+        presentation::ParagraphTarget aParagraphTarget;
+        if ((aTargetAny >>= aParagraphTarget) && aParagraphTarget.Shape.is())
+        {
+            nParagraph = aParagraphTarget.Paragraph;
+            pObject = getObjectForShape(aParagraphTarget.Shape);
+        }
+    }
+
+    if (!pObject)
         return;
 
-    std::vector<uno::Reference<animations::XAnimationNode>> aAnimationVector;
-    anim::create_deep_vector(pSdPage->getAnimationNode(), aAnimationVector);
+    AnimationRenderInfo aAnimationInfo;
+    auto aIterator = maRenderState.maAnimationRenderInfoList.find(pObject);
+    if (aIterator != maRenderState.maAnimationRenderInfoList.end())
+        aAnimationInfo = aIterator->second;
+
+    AnimationLayerInfo aLayerInfo;
 
-    for (uno::Reference<animations::XAnimationNode> const& rNode : 
aAnimationVector)
+    std::optional<bool> bVisible;
+    uno::Any aAny
+        = rEffect->getProperty(animations::AnimationNodeType::SET, 
u"Visibility", EValue::To);
+    if (aAny.hasValue())
     {
-        switch (rNode->getType())
-        {
-            // filter out the most obvious
-            case animations::AnimationNodeType::CUSTOM:
-            case animations::AnimationNodeType::ANIMATE:
-            case animations::AnimationNodeType::SET:
-            case animations::AnimationNodeType::ANIMATEMOTION:
-            case animations::AnimationNodeType::ANIMATECOLOR:
-            case animations::AnimationNodeType::ANIMATETRANSFORM:
-            case animations::AnimationNodeType::TRANSITIONFILTER:
-            case animations::AnimationNodeType::ANIMATEPHYSICS:
-            {
-                uno::Reference<animations::XAnimate> xAnimate(rNode, 
uno::UNO_QUERY);
-                if (xAnimate.is())
-                {
-                    uno::Any aAny = xAnimate->getTarget();
+        // if initial anim sets shape visible, set it
+        // to invisible. If we're asked for the final
+        // state, don't do anything obviously
+        bVisible = !anim::getVisibilityPropertyForAny(aAny);
+    }
+    aLayerInfo.moInitiallyVisible = bVisible;
 
-                    uno::Reference<drawing::XShape> xShape;
-                    SvxShape* pShape = nullptr;
-                    SdrObject* pObject = nullptr;
+    OStringBuffer aStringBuffer;
+    anim::convertTarget(aStringBuffer, aTargetAny);
+    aLayerInfo.msHash = aStringBuffer.makeStringAndClear();
 
-                    if ((aAny >>= xShape) && xShape.is())
-                    {
-                        pShape = 
comphelper::getFromUnoTunnel<SvxShape>(xShape);
-                        if (pShape)
-                        {
-                            pObject = pShape->GetSdrObject();
-                            maRenderState.maInAnimation.insert(pObject);
-                        }
-                    }
-                    else // if target is not a shape
-                    {
-                        presentation::ParagraphTarget aParagraphTarget;
-                        if ((aAny >>= aParagraphTarget) && 
aParagraphTarget.Shape.is())
-                        {
-                            //sal_Int32 nParagraph = 
aParagraphTarget.Paragraph;
-
-                            xShape = aParagraphTarget.Shape;
-
-                            pShape = 
comphelper::getFromUnoTunnel<SvxShape>(xShape);
-                            if (pShape)
-                            {
-                                pObject = pShape->GetSdrObject();
-                                maRenderState.maInAnimation.insert(pObject);
-                            }
-                        }
-                    }
+    // We have paragraphs
+    if (nParagraph >= 0)
+    {
+        auto aParagraphIterator = 
std::find(aAnimationInfo.maParagraphs.begin(),
+                                            aAnimationInfo.maParagraphs.end(), 
nParagraph);
 
-                    if (pObject)
-                    {
-                        bool bVisible;
-
-                        if (anim::getVisibilityProperty(xAnimate, bVisible))
-                        {
-                            // if initial anim sets shape visible, set it
-                            // to invisible. If we're asked for the final
-                            // state, don't do anything obviously
-                            bVisible = !bVisible;
-
-                            maRenderState.maInitiallyVisible[pObject] = 
bVisible;
-                        }
-
-                        if (aAny.hasValue())
-                        {
-                            OStringBuffer sTmp;
-                            anim::convertTarget(sTmp, aAny);
-                            maRenderState.maAnimationTargetHash[pObject]
-                                = static_cast<OString>(sTmp);
-                        }
-                    }
-                }
-            }
+        // Check if paragraph already exists
+        if (aParagraphIterator == aAnimationInfo.maParagraphs.end())
+        {
+            // We have a paragraph, so write the hash for the paragraph
+            aAnimationInfo.maParagraphs.push_back(nParagraph);
+            aAnimationInfo.maParagraphInfos.emplace(nParagraph, aLayerInfo);
         }
     }
+    else
+    {
+        if (!aAnimationInfo.moObjectInfo)
+            aAnimationInfo.moObjectInfo = aLayerInfo;
+    }
+
+    maRenderState.maAnimationRenderInfoList[pObject] = aAnimationInfo;
+}
+
+void SlideshowLayerRenderer::setupAnimations()
+{
+    auto* pSdPage = dynamic_cast<SdPage*>(&mrPage);
+
+    if (!pSdPage)
+        return;
+
+    auto const& rMain = pSdPage->getMainSequence();
+
+    for (auto const& rEffect : rMain->getSequence())
+    {
+        resolveEffect(rEffect);
+    }
 }
 
 Size SlideshowLayerRenderer::calculateAndSetSizePixel(Size const& 
rDesiredSizePixel)
@@ -335,14 +511,16 @@ void 
SlideshowLayerRenderer::createViewAndDraw(RenderContext& rRenderContext)
     aView.CompleteRedraw(rRenderContext.maVirtualDevice, aRegion, 
&aRedirector);
 }
 
-static void writeContentNode(::tools::JsonWriter& aJsonWriter)
+namespace
+{
+void writeContentNode(::tools::JsonWriter& aJsonWriter)
 {
     auto aContentNode = aJsonWriter.startNode("content");
     aJsonWriter.put("type", "%IMAGETYPE%");
     aJsonWriter.put("checksum", "%IMAGECHECKSUM%");
 }
 
-static void writeBoundingBox(::tools::JsonWriter& aJsonWriter, SdrObject* 
pObject)
+void writeBoundingBox(::tools::JsonWriter& aJsonWriter, SdrObject* pObject)
 {
     auto aContentNode = aJsonWriter.startNode("bounds");
     ::tools::Rectangle aRectmm100 = pObject->GetCurrentBoundRect();
@@ -353,6 +531,26 @@ static void writeBoundingBox(::tools::JsonWriter& 
aJsonWriter, SdrObject* pObjec
     aJsonWriter.put("height", aRect.GetHeight());
 }
 
+void writeAnimated(::tools::JsonWriter& aJsonWriter, AnimationLayerInfo const& 
rLayerInfo,
+                   SdrObject* pObject)
+{
+    aJsonWriter.put("type", "animated");
+    {
+        bool bInitiallyVisible = true;
+        if (rLayerInfo.moInitiallyVisible.has_value())
+            bInitiallyVisible = *rLayerInfo.moInitiallyVisible;
+
+        auto aContentNode = aJsonWriter.startNode("content");
+        aJsonWriter.put("hash", rLayerInfo.msHash);
+        aJsonWriter.put("initVisible", bInitiallyVisible);
+        aJsonWriter.put("type", "bitmap");
+        writeContentNode(aJsonWriter);
+        writeBoundingBox(aJsonWriter, pObject);
+    }
+}
+
+} // end anonymous namespace
+
 void SlideshowLayerRenderer::writeJSON(OString& rJsonMsg)
 {
     ::tools::JsonWriter aJsonWriter;
@@ -361,19 +559,30 @@ void SlideshowLayerRenderer::writeJSON(OString& rJsonMsg)
     aJsonWriter.put("slideHash", 
GetInterfaceHash(GetXDrawPageForSdrPage(&mrPage)));
 
     SdrObject* pObject = maRenderState.currentTarget();
+    sal_Int32 nParagraph = maRenderState.currentTargetParagraph();
 
-    bool bIsAnimated = maRenderState.isObjectInAnimation(pObject);
-    if (bIsAnimated)
+    auto aIterator = maRenderState.maAnimationRenderInfoList.find(pObject);
+    if (aIterator != maRenderState.maAnimationRenderInfoList.end())
     {
+        AnimationRenderInfo& rInfo = aIterator->second;
         assert(pObject);
-        aJsonWriter.put("type", "animated");
+
+        if (nParagraph >= 0)
+        {
+            auto aParagraphInfoIterator = 
rInfo.maParagraphInfos.find(nParagraph);
+            if (aParagraphInfoIterator != rInfo.maParagraphInfos.end())
+            {
+                writeAnimated(aJsonWriter, aParagraphInfoIterator->second, 
pObject);
+            }
+        }
+        else if (rInfo.moObjectInfo)
+        {
+            writeAnimated(aJsonWriter, *rInfo.moObjectInfo, pObject);
+        }
+        else // No object hash and paragraph hash -> Non-animated part of the 
object (non-animated paragraphs)
         {
-            auto aContentNode = aJsonWriter.startNode("content");
-            aJsonWriter.put("hash", 
maRenderState.maAnimationTargetHash.at(pObject));
-            aJsonWriter.put("initVisible", 
maRenderState.isObjectInitiallyVisible(pObject));
             aJsonWriter.put("type", "bitmap");
             writeContentNode(aJsonWriter);
-            writeBoundingBox(aJsonWriter, pObject);
         }
     }
     else
@@ -391,13 +600,15 @@ void SlideshowLayerRenderer::writeJSON(OString& rJsonMsg)
 
 bool SlideshowLayerRenderer::render(unsigned char* pBuffer, OString& rJsonMsg)
 {
-    // Reset state
+    // We want to render one pass (one iteration through objects)
+
+    // Reset state for this pass
     maRenderState.resetPass();
 
     RenderContext aRenderContext(pBuffer, mrModel, mrPage, maSlideSize);
     createViewAndDraw(aRenderContext);
 
-    // Check if we are done rendering all passes
+    // Check if we are done rendering all passes and there is no more output
     if (maRenderState.noMoreOutput())
         return false;
 

Reply via email to