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;