diff options
author | Tomaž Vajngerl <tomaz.vajngerl@collabora.co.uk> | 2024-09-25 15:10:05 +0200 |
---|---|---|
committer | Szymon Kłos <szymon.klos@collabora.com> | 2024-10-10 18:15:44 +0200 |
commit | 959bbd2dddd6ab4bdb1f0e5548126db83fc19964 (patch) | |
tree | dae9913deaff30348040e9ca0e384b67421442d8 | |
parent | 8903ddff872d579b06bd3acc413d3d14f4284228 (diff) |
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/+/173939
Tested-by: Jenkins CollaboraOffice <jenkinscollaboraoffice@gmail.com>
Reviewed-by: Szymon Kłos <szymon.klos@collabora.com>
-rw-r--r-- | drawinglayer/source/processor2d/vclpixelprocessor2d.cxx | 4 | ||||
-rw-r--r-- | include/animations/animationnodehelper.hxx | 54 | ||||
-rw-r--r-- | include/drawinglayer/primitive2d/baseprimitive2d.hxx | 6 | ||||
-rw-r--r-- | sd/qa/unit/tiledrendering/data/SlideRenderingTest_Animations.odp | bin | 15242 -> 15118 bytes | |||
-rw-r--r-- | sd/qa/unit/tiledrendering/data/SlideRenderingTest_Animations_SecondParagraphMultipleEffects.odp | bin | 0 -> 32126 bytes | |||
-rw-r--r-- | sd/qa/unit/tiledrendering/data/SlideRenderingTest_TextBox.odp | bin | 0 -> 31935 bytes | |||
-rw-r--r-- | sd/qa/unit/tiledrendering/tiledrendering.cxx | 126 | ||||
-rw-r--r-- | sd/source/ui/inc/SlideshowLayerRenderer.hxx | 69 | ||||
-rw-r--r-- | sd/source/ui/tools/SlideshowLayerRenderer.cxx | 417 |
9 files changed, 525 insertions, 151 deletions
diff --git a/drawinglayer/source/processor2d/vclpixelprocessor2d.cxx b/drawinglayer/source/processor2d/vclpixelprocessor2d.cxx index 8d199f696a40..6877a1a72f2c 100644 --- a/drawinglayer/source/processor2d/vclpixelprocessor2d.cxx +++ b/drawinglayer/source/processor2d/vclpixelprocessor2d.cxx @@ -195,6 +195,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_WRONGSPELLPRIMITIVE2D: diff --git a/include/animations/animationnodehelper.hxx b/include/animations/animationnodehelper.hxx index 3da2e8fa2d39..8dbb30c05a5c 100644 --- a/include/animations/animationnodehelper.hxx +++ b/include/animations/animationnodehelper.hxx @@ -80,36 +80,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 4d6d6250a52c..3b77ec890eb0 100644 --- a/include/drawinglayer/primitive2d/baseprimitive2d.hxx +++ b/include/drawinglayer/primitive2d/baseprimitive2d.hxx @@ -119,6 +119,8 @@ namespace drawinglayer::primitive2d */ class DRAWINGLAYERCORE_DLLPUBLIC BasePrimitive2D : public salhelper::SimpleReferenceObject { + bool mbVisible = true; + BasePrimitive2D(const BasePrimitive2D&) = delete; BasePrimitive2D& operator=(const BasePrimitive2D&) = delete; @@ -164,6 +166,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 Binary files differindex b827e0a94cf6..73267d19ff44 100644 --- a/sd/qa/unit/tiledrendering/data/SlideRenderingTest_Animations.odp +++ b/sd/qa/unit/tiledrendering/data/SlideRenderingTest_Animations.odp diff --git a/sd/qa/unit/tiledrendering/data/SlideRenderingTest_Animations_SecondParagraphMultipleEffects.odp b/sd/qa/unit/tiledrendering/data/SlideRenderingTest_Animations_SecondParagraphMultipleEffects.odp Binary files differnew file mode 100644 index 000000000000..1c8cb4b4d600 --- /dev/null +++ b/sd/qa/unit/tiledrendering/data/SlideRenderingTest_Animations_SecondParagraphMultipleEffects.odp diff --git a/sd/qa/unit/tiledrendering/data/SlideRenderingTest_TextBox.odp b/sd/qa/unit/tiledrendering/data/SlideRenderingTest_TextBox.odp Binary files differnew file mode 100644 index 000000000000..e7a59e7c44ea --- /dev/null +++ b/sd/qa/unit/tiledrendering/data/SlideRenderingTest_TextBox.odp diff --git a/sd/qa/unit/tiledrendering/tiledrendering.cxx b/sd/qa/unit/tiledrendering/tiledrendering.cxx index a545e7becd62..0003f5378d9a 100644 --- a/sd/qa/unit/tiledrendering/tiledrendering.cxx +++ b/sd/qa/unit/tiledrendering/tiledrendering.cxx @@ -3675,6 +3675,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 e75e6ed40f89..7a48e3366c1f 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 803801a5436e..39adde615331 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; @@ -98,6 +106,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()); + size_t 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 { @@ -118,7 +235,7 @@ public: // Generate single pass for background layer if (mrRenderState.meStage == RenderStage::Background) { - mrRenderState.mbPassHasOutput = true; + mrRenderState.mnRenderedObjectsInPass++; mrRenderState.mbSkipAllInThisPass = true; return; } @@ -150,7 +267,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 @@ -164,18 +281,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; @@ -188,9 +332,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); + } } }; @@ -209,6 +377,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) @@ -219,92 +397,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) @@ -337,14 +513,16 @@ void SlideshowLayerRenderer::createViewAndDraw(RenderContext& rRenderContext) aView.CompleteRedraw(rRenderContext.maVirtualDevice, aRegion, &aRedirector); } -static void writeContentNode(::tools::JsonWriter& aJsonWriter) +namespace +{ +void writeContentNode(::tools::JsonWriter& aJsonWriter) { ::tools::ScopedJsonWriterNode 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) { ::tools::ScopedJsonWriterNode aContentNode = aJsonWriter.startNode("bounds"); ::tools::Rectangle aRectmm100 = pObject->GetCurrentBoundRect(); @@ -355,6 +533,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; + + ::tools::ScopedJsonWriterNode 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; @@ -363,19 +561,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) { - ::tools::ScopedJsonWriterNode 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 @@ -393,13 +602,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; |