summaryrefslogtreecommitdiffstats
path: root/slideshow/source/engine/shapes
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:06:44 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:06:44 +0000
commited5640d8b587fbcfed7dd7967f3de04b37a76f26 (patch)
tree7a5f7c6c9d02226d7471cb3cc8fbbf631b415303 /slideshow/source/engine/shapes
parentInitial commit. (diff)
downloadlibreoffice-upstream/4%7.4.7.tar.xz
libreoffice-upstream/4%7.4.7.zip
Adding upstream version 4:7.4.7.upstream/4%7.4.7upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'slideshow/source/engine/shapes')
-rw-r--r--slideshow/source/engine/shapes/appletshape.cxx288
-rw-r--r--slideshow/source/engine/shapes/appletshape.hxx44
-rw-r--r--slideshow/source/engine/shapes/backgroundshape.cxx299
-rw-r--r--slideshow/source/engine/shapes/backgroundshape.hxx51
-rw-r--r--slideshow/source/engine/shapes/drawinglayeranimation.cxx932
-rw-r--r--slideshow/source/engine/shapes/drawinglayeranimation.hxx39
-rw-r--r--slideshow/source/engine/shapes/drawshape.cxx1228
-rw-r--r--slideshow/source/engine/shapes/drawshape.hxx360
-rw-r--r--slideshow/source/engine/shapes/drawshapesubsetting.cxx808
-rw-r--r--slideshow/source/engine/shapes/drawshapesubsetting.hxx244
-rw-r--r--slideshow/source/engine/shapes/externalshapebase.cxx211
-rw-r--r--slideshow/source/engine/shapes/externalshapebase.hxx131
-rw-r--r--slideshow/source/engine/shapes/gdimtftools.cxx421
-rw-r--r--slideshow/source/engine/shapes/gdimtftools.hxx129
-rw-r--r--slideshow/source/engine/shapes/intrinsicanimationactivity.cxx249
-rw-r--r--slideshow/source/engine/shapes/intrinsicanimationactivity.hxx66
-rw-r--r--slideshow/source/engine/shapes/mediashape.cxx257
-rw-r--r--slideshow/source/engine/shapes/mediashape.hxx44
-rw-r--r--slideshow/source/engine/shapes/shapeimporter.cxx537
-rw-r--r--slideshow/source/engine/shapes/viewappletshape.cxx258
-rw-r--r--slideshow/source/engine/shapes/viewappletshape.hxx164
-rw-r--r--slideshow/source/engine/shapes/viewbackgroundshape.cxx188
-rw-r--r--slideshow/source/engine/shapes/viewbackgroundshape.hxx96
-rw-r--r--slideshow/source/engine/shapes/viewmediashape.cxx499
-rw-r--r--slideshow/source/engine/shapes/viewmediashape.hxx168
-rw-r--r--slideshow/source/engine/shapes/viewshape.cxx856
-rw-r--r--slideshow/source/engine/shapes/viewshape.hxx319
27 files changed, 8886 insertions, 0 deletions
diff --git a/slideshow/source/engine/shapes/appletshape.cxx b/slideshow/source/engine/shapes/appletshape.cxx
new file mode 100644
index 000000000..ba7c6243b
--- /dev/null
+++ b/slideshow/source/engine/shapes/appletshape.cxx
@@ -0,0 +1,288 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#include "appletshape.hxx"
+#include "externalshapebase.hxx"
+#include "viewappletshape.hxx"
+#include <tools.hxx>
+
+#include <o3tl/safeint.hxx>
+#include <osl/diagnose.h>
+
+#include <algorithm>
+
+
+using namespace ::com::sun::star;
+
+
+namespace slideshow::internal
+{
+ namespace {
+
+ /** Represents an applet shape.
+
+ This implementation offers support for applet shapes (both
+ Java applets, and Netscape plugins). Such shapes need
+ special treatment.
+ */
+ class AppletShape : public ExternalShapeBase
+ {
+ public:
+ /** Create a shape for the given XShape for an applet object
+
+ @param xShape
+ The XShape to represent.
+
+ @param nPrio
+ Externally-determined shape priority (used e.g. for
+ paint ordering). This number _must be_ unique!
+
+ @param rServiceName
+ Service name to use, when creating the actual viewer
+ component
+
+ @param pPropCopyTable
+ Table of plain ASCII property names, to copy from
+ xShape to applet.
+
+ @param nNumPropEntries
+ Number of property table entries (in pPropCopyTable)
+ */
+ AppletShape( const css::uno::Reference< css::drawing::XShape >& xShape,
+ double nPrio,
+ const OUString& rServiceName,
+ const char** pPropCopyTable,
+ std::size_t nNumPropEntries,
+ const SlideShowContext& rContext ); // throw ShapeLoadFailedException;
+
+ private:
+
+ // View layer methods
+
+
+ virtual void addViewLayer( const ViewLayerSharedPtr& rNewLayer,
+ bool bRedrawLayer ) override;
+ virtual bool removeViewLayer( const ViewLayerSharedPtr& rNewLayer ) override;
+ virtual void clearAllViewLayers() override;
+
+
+ // ExternalShapeBase methods
+
+
+ virtual bool implRender( const ::basegfx::B2DRange& rCurrBounds ) const override;
+ virtual void implViewChanged( const UnoViewSharedPtr& rView ) override;
+ virtual void implViewsChanged() override;
+ virtual bool implStartIntrinsicAnimation() override;
+ virtual bool implEndIntrinsicAnimation() override;
+ virtual void implPauseIntrinsicAnimation() override;
+ virtual bool implIsIntrinsicAnimationPlaying() const override;
+ virtual void implSetIntrinsicAnimationTime(double) override;
+
+ const OUString maServiceName;
+ const char** mpPropCopyTable;
+ const std::size_t mnNumPropEntries;
+
+ /// the list of active view shapes (one for each registered view layer)
+ typedef ::std::vector< ViewAppletShapeSharedPtr > ViewAppletShapeVector;
+ ViewAppletShapeVector maViewAppletShapes;
+ bool mbIsPlaying;
+ };
+
+ }
+
+ AppletShape::AppletShape( const uno::Reference< drawing::XShape >& xShape,
+ double nPrio,
+ const OUString& rServiceName,
+ const char** pPropCopyTable,
+ std::size_t nNumPropEntries,
+ const SlideShowContext& rContext ) :
+ ExternalShapeBase( xShape, nPrio, rContext ),
+ maServiceName( rServiceName ),
+ mpPropCopyTable( pPropCopyTable ),
+ mnNumPropEntries( nNumPropEntries ),
+ maViewAppletShapes(),
+ mbIsPlaying(false)
+ {
+ }
+
+
+ void AppletShape::implViewChanged( const UnoViewSharedPtr& rView )
+ {
+ const ::basegfx::B2DRectangle& rBounds = getBounds();
+ // determine ViewAppletShape that needs update
+ for( const auto& pViewAppletShape : maViewAppletShapes )
+ {
+ if( pViewAppletShape->getViewLayer()->isOnView( rView ) )
+ pViewAppletShape->resize( rBounds );
+ }
+ }
+
+
+ void AppletShape::implViewsChanged()
+ {
+ // resize all ViewShapes
+ const ::basegfx::B2DRectangle& rBounds = getBounds();
+ for( const auto& pViewAppletShape : maViewAppletShapes )
+ pViewAppletShape->resize( rBounds );
+ }
+
+
+ void AppletShape::addViewLayer( const ViewLayerSharedPtr& rNewLayer,
+ bool bRedrawLayer )
+ {
+ try
+ {
+ maViewAppletShapes.push_back(
+ std::make_shared<ViewAppletShape>( rNewLayer,
+ getXShape(),
+ maServiceName,
+ mpPropCopyTable,
+ mnNumPropEntries,
+ mxComponentContext ));
+
+ // push new size to view shape
+ maViewAppletShapes.back()->resize( getBounds() );
+
+ // render the Shape on the newly added ViewLayer
+ if( bRedrawLayer )
+ maViewAppletShapes.back()->render( getBounds() );
+ }
+ catch(uno::Exception&)
+ {
+ // ignore failed shapes - slideshow should run with
+ // the remaining content
+ }
+ }
+
+
+ bool AppletShape::removeViewLayer( const ViewLayerSharedPtr& rLayer )
+ {
+ const ViewAppletShapeVector::iterator aEnd( maViewAppletShapes.end() );
+
+ OSL_ENSURE( ::std::count_if(maViewAppletShapes.begin(),
+ aEnd,
+ [&rLayer]
+ ( const ViewAppletShapeSharedPtr& pShape )
+ { return rLayer == pShape->getViewLayer(); } ) < 2,
+ "AppletShape::removeViewLayer(): Duplicate ViewLayer entries!" );
+
+ ViewAppletShapeVector::iterator aIter;
+
+ if( (aIter=::std::remove_if( maViewAppletShapes.begin(),
+ aEnd,
+ [&rLayer]
+ ( const ViewAppletShapeSharedPtr& pShape )
+ { return rLayer == pShape->getViewLayer(); } ) ) == aEnd )
+ {
+ // view layer seemingly was not added, failed
+ return false;
+ }
+
+ // actually erase from container
+ maViewAppletShapes.erase( aIter, aEnd );
+
+ return true;
+ }
+
+
+ void AppletShape::clearAllViewLayers()
+ {
+ maViewAppletShapes.clear();
+ }
+
+
+ bool AppletShape::implRender( const ::basegfx::B2DRange& rCurrBounds ) const
+ {
+ // redraw all view shapes, by calling their update() method
+ if( o3tl::make_unsigned(::std::count_if( maViewAppletShapes.begin(),
+ maViewAppletShapes.end(),
+ [&rCurrBounds]
+ ( const ViewAppletShapeSharedPtr& pShape )
+ { return pShape->render( rCurrBounds ); } ))
+ != maViewAppletShapes.size() )
+ {
+ // at least one of the ViewShape::update() calls did return
+ // false - update failed on at least one ViewLayer
+ return false;
+ }
+
+ return true;
+ }
+
+
+ bool AppletShape::implStartIntrinsicAnimation()
+ {
+ const ::basegfx::B2DRectangle& rBounds = getBounds();
+ for( const auto& pViewAppletShape : maViewAppletShapes )
+ pViewAppletShape->startApplet( rBounds );
+
+ mbIsPlaying = true;
+
+ return true;
+ }
+
+
+ bool AppletShape::implEndIntrinsicAnimation()
+ {
+ for( const auto& pViewAppletShape : maViewAppletShapes )
+ pViewAppletShape->endApplet();
+
+ mbIsPlaying = false;
+
+ return true;
+ }
+
+
+ void AppletShape::implPauseIntrinsicAnimation()
+ {
+ // TODO(F1): any way of temporarily disabling/deactivating
+ // applets?
+ }
+
+
+ bool AppletShape::implIsIntrinsicAnimationPlaying() const
+ {
+ return mbIsPlaying;
+ }
+
+
+ void AppletShape::implSetIntrinsicAnimationTime(double)
+ {
+ // No way of doing this, or?
+ }
+
+ std::shared_ptr<Shape> createAppletShape(
+ const uno::Reference< drawing::XShape >& xShape,
+ double nPrio,
+ const OUString& rServiceName,
+ const char** pPropCopyTable,
+ std::size_t nNumPropEntries,
+ const SlideShowContext& rContext )
+ {
+ return std::make_shared<AppletShape>(xShape,
+ nPrio,
+ rServiceName,
+ pPropCopyTable,
+ nNumPropEntries,
+ rContext);
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/slideshow/source/engine/shapes/appletshape.hxx b/slideshow/source/engine/shapes/appletshape.hxx
new file mode 100644
index 000000000..0de63af2c
--- /dev/null
+++ b/slideshow/source/engine/shapes/appletshape.hxx
@@ -0,0 +1,44 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#ifndef INCLUDED_SLIDESHOW_SOURCE_ENGINE_SHAPES_APPLETSHAPE_HXX
+#define INCLUDED_SLIDESHOW_SOURCE_ENGINE_SHAPES_APPLETSHAPE_HXX
+
+#include <com/sun/star/uno/Reference.hxx>
+#include <memory>
+
+namespace com::sun::star::drawing { class XShape; }
+
+namespace slideshow::internal
+{
+ struct SlideShowContext;
+ class Shape;
+
+ std::shared_ptr<Shape> createAppletShape(
+ const css::uno::Reference< css::drawing::XShape >& xShape,
+ double nPrio,
+ const OUString& rServiceName,
+ const char** pPropCopyTable,
+ std::size_t nNumPropEntries,
+ const SlideShowContext& rContext );
+}
+
+#endif // INCLUDED_SLIDESHOW_SOURCE_ENGINE_SHAPES_APPLETSHAPE_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/slideshow/source/engine/shapes/backgroundshape.cxx b/slideshow/source/engine/shapes/backgroundshape.cxx
new file mode 100644
index 000000000..d304b9f90
--- /dev/null
+++ b/slideshow/source/engine/shapes/backgroundshape.cxx
@@ -0,0 +1,299 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#include <com/sun/star/beans/XPropertySet.hpp>
+
+#include <sal/log.hxx>
+#include <o3tl/safeint.hxx>
+#include <osl/diagnose.h>
+
+#include <algorithm>
+
+#include "backgroundshape.hxx"
+#include <slideshowexceptions.hxx>
+#include <slideshowcontext.hxx>
+#include "gdimtftools.hxx"
+#include <shape.hxx>
+#include "viewbackgroundshape.hxx"
+
+
+using namespace ::com::sun::star;
+
+
+namespace slideshow::internal
+{
+ namespace {
+
+ /** Representation of a draw document's background shape.
+
+ This class implements the Shape interface for the
+ background shape. Since the background shape is neither
+ animatable nor attributable, those more specialized
+ derivations of the Shape interface are not implemented
+ here.
+
+ @attention this class is to be treated 'final', i.e. one
+ should not derive from it.
+ */
+ class BackgroundShape : public Shape
+ {
+ public:
+ /** Create the background shape.
+
+ This method creates a shape that handles the
+ peculiarities of the draw API regarding background
+ content.
+ */
+ BackgroundShape( const css::uno::Reference< css::drawing::XDrawPage >& xDrawPage,
+ const css::uno::Reference< css::drawing::XDrawPage >& xMasterPage,
+ const SlideShowContext& rContext ); // throw ShapeLoadFailedException;
+
+ virtual css::uno::Reference<
+ css::drawing::XShape > getXShape() const override;
+
+ // View layer methods
+
+
+ virtual void addViewLayer( const ViewLayerSharedPtr& rNewLayer,
+ bool bRedrawLayer ) override;
+ virtual bool removeViewLayer( const ViewLayerSharedPtr& rNewLayer ) override;
+ virtual void clearAllViewLayers() override;
+
+
+ // attribute methods
+
+
+ virtual ::basegfx::B2DRectangle getBounds() const override;
+ virtual ::basegfx::B2DRectangle getDomBounds() const override;
+ virtual ::basegfx::B2DRectangle getUpdateArea() const override;
+ virtual bool isVisible() const override;
+ virtual double getPriority() const override;
+ virtual bool isForeground() const override { return false; }
+ virtual bool isBackgroundDetached() const override;
+
+
+ // render methods
+
+
+ virtual bool update() const override;
+ virtual bool render() const override;
+ virtual bool isContentChanged() const override;
+
+ private:
+ /// The metafile actually representing the Shape
+ GDIMetaFileSharedPtr mpMtf;
+
+ // The attributes of this Shape
+ ::basegfx::B2DRectangle maBounds; // always needed for rendering
+
+ /// the list of active view shapes (one for each registered view layer)
+ typedef ::std::vector< ViewBackgroundShapeSharedPtr > ViewBackgroundShapeVector;
+ ViewBackgroundShapeVector maViewShapes;
+ };
+
+ }
+
+ BackgroundShape::BackgroundShape( const uno::Reference< drawing::XDrawPage >& xDrawPage,
+ const uno::Reference< drawing::XDrawPage >& xMasterPage,
+ const SlideShowContext& rContext ) :
+ mpMtf(),
+ maBounds(),
+ maViewShapes()
+ {
+ uno::Reference< beans::XPropertySet > xPropSet( xDrawPage,
+ uno::UNO_QUERY_THROW );
+ // first try the page background (overrides
+ // masterpage background), then try masterpage
+ GDIMetaFileSharedPtr xMtf = getMetaFile(uno::Reference<lang::XComponent>(xDrawPage, uno::UNO_QUERY),
+ xDrawPage, MTF_LOAD_BACKGROUND_ONLY,
+ rContext.mxComponentContext);
+
+ if (!xMtf)
+ {
+ xMtf = getMetaFile( uno::Reference<lang::XComponent>(xMasterPage, uno::UNO_QUERY),
+ xDrawPage, MTF_LOAD_BACKGROUND_ONLY,
+ rContext.mxComponentContext );
+ }
+
+ if (!xMtf)
+ {
+ throw ShapeLoadFailedException();
+ }
+
+ // there is a special background shape, add it
+ // as the first one
+
+ sal_Int32 nDocWidth=0;
+ sal_Int32 nDocHeight=0;
+ xPropSet->getPropertyValue("Width") >>= nDocWidth;
+ xPropSet->getPropertyValue("Height") >>= nDocHeight;
+
+ mpMtf = xMtf;
+ maBounds = ::basegfx::B2DRectangle( 0,0,nDocWidth, nDocHeight );
+ }
+
+ uno::Reference< drawing::XShape > BackgroundShape::getXShape() const
+ {
+ // no real XShape representative
+ return uno::Reference< drawing::XShape >();
+ }
+
+ void BackgroundShape::addViewLayer( const ViewLayerSharedPtr& rNewLayer,
+ bool bRedrawLayer )
+ {
+ // already added?
+ if( ::std::any_of( maViewShapes.begin(),
+ maViewShapes.end(),
+ [&rNewLayer]( const ViewBackgroundShapeSharedPtr& pBgShape )
+ { return pBgShape->getViewLayer() == rNewLayer; } ) )
+ {
+ // yes, nothing to do
+ return;
+ }
+
+ maViewShapes.push_back(
+ std::make_shared<ViewBackgroundShape>(
+ rNewLayer, maBounds ) );
+
+ // render the Shape on the newly added ViewLayer
+ if( bRedrawLayer )
+ maViewShapes.back()->render( mpMtf );
+ }
+
+ bool BackgroundShape::removeViewLayer( const ViewLayerSharedPtr& rLayer )
+ {
+ const ViewBackgroundShapeVector::iterator aEnd( maViewShapes.end() );
+
+ OSL_ENSURE( ::std::count_if(maViewShapes.begin(),
+ aEnd,
+ [&rLayer]( const ViewBackgroundShapeSharedPtr& pBgShape )
+ { return pBgShape->getViewLayer() == rLayer; } ) < 2,
+ "BackgroundShape::removeViewLayer(): Duplicate ViewLayer entries!" );
+
+ ViewBackgroundShapeVector::iterator aIter;
+
+ if( (aIter=::std::remove_if( maViewShapes.begin(),
+ aEnd,
+ [&rLayer]( const ViewBackgroundShapeSharedPtr& pBgShape )
+ { return pBgShape->getViewLayer() == rLayer; } )) == aEnd )
+ {
+ // view layer seemingly was not added, failed
+ return false;
+ }
+
+ // actually erase from container
+ maViewShapes.erase( aIter, aEnd );
+
+ return true;
+ }
+
+ void BackgroundShape::clearAllViewLayers()
+ {
+ maViewShapes.clear();
+ }
+
+ ::basegfx::B2DRectangle BackgroundShape::getBounds() const
+ {
+ return maBounds;
+ }
+
+ ::basegfx::B2DRectangle BackgroundShape::getDomBounds() const
+ {
+ return maBounds;
+ }
+
+ ::basegfx::B2DRectangle BackgroundShape::getUpdateArea() const
+ {
+ // TODO(F1): Need to expand background, too, when
+ // antialiasing?
+
+ // no transformation etc. possible for background shape
+ return maBounds;
+ }
+
+ bool BackgroundShape::isVisible() const
+ {
+ return true;
+ }
+
+ double BackgroundShape::getPriority() const
+ {
+ return 0.0; // lowest prio, we're the background
+ }
+
+ bool BackgroundShape::update() const
+ {
+ return render();
+ }
+
+ bool BackgroundShape::render() const
+ {
+ SAL_INFO( "slideshow", "::presentation::internal::BackgroundShape::render()" );
+ SAL_INFO( "slideshow", "::presentation::internal::BackgroundShape: 0x" << std::hex << this );
+
+ // gcc again...
+ const ::basegfx::B2DRectangle& rCurrBounds( BackgroundShape::getBounds() );
+
+ if( rCurrBounds.getRange().equalZero() )
+ {
+ // zero-sized shapes are effectively invisible,
+ // thus, we save us the rendering...
+ return true;
+ }
+
+ // redraw all view shapes, by calling their render() method
+ if( o3tl::make_unsigned(::std::count_if( maViewShapes.begin(),
+ maViewShapes.end(),
+ [this]( const ViewBackgroundShapeSharedPtr& pBgShape )
+ { return pBgShape->render( this->mpMtf ); } ))
+ != maViewShapes.size() )
+ {
+ // at least one of the ViewBackgroundShape::render() calls did return
+ // false - update failed on at least one ViewLayer
+ return false;
+ }
+
+ return true;
+ }
+
+ bool BackgroundShape::isContentChanged() const
+ {
+ return false;
+ }
+
+ bool BackgroundShape::isBackgroundDetached() const
+ {
+ return false; // we're not animatable
+ }
+
+
+ ShapeSharedPtr createBackgroundShape(
+ const uno::Reference< drawing::XDrawPage >& xDrawPage,
+ const uno::Reference< drawing::XDrawPage >& xMasterPage,
+ const SlideShowContext& rContext )
+ {
+ return std::make_shared<BackgroundShape>(
+ xDrawPage,
+ xMasterPage,
+ rContext );
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/slideshow/source/engine/shapes/backgroundshape.hxx b/slideshow/source/engine/shapes/backgroundshape.hxx
new file mode 100644
index 000000000..3e7092498
--- /dev/null
+++ b/slideshow/source/engine/shapes/backgroundshape.hxx
@@ -0,0 +1,51 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#ifndef INCLUDED_SLIDESHOW_SOURCE_ENGINE_SHAPES_BACKGROUNDSHAPE_HXX
+#define INCLUDED_SLIDESHOW_SOURCE_ENGINE_SHAPES_BACKGROUNDSHAPE_HXX
+
+#include <com/sun/star/uno/Reference.hxx>
+
+#include <memory>
+
+namespace com::sun::star::drawing { class XDrawPage; }
+
+namespace slideshow::internal
+ {
+ class Shape;
+ struct SlideShowContext;
+ typedef ::std::shared_ptr< Shape > ShapeSharedPtr;
+
+ /** Representation of a draw document's background shape.
+
+ This function generates the Shape for the background
+ shape. Since the background shape is neither animatable
+ nor attributable, those more specialized derivations of
+ the Shape interface are not implemented here.
+ */
+ ShapeSharedPtr createBackgroundShape(
+ const css::uno::Reference< css::drawing::XDrawPage >& xDrawPage,
+ const css::uno::Reference< css::drawing::XDrawPage >& xMasterPage,
+ const SlideShowContext& rContext ); // throw ShapeLoadFailedException;
+
+}
+
+#endif // INCLUDED_SLIDESHOW_SOURCE_ENGINE_SHAPES_BACKGROUNDSHAPE_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/slideshow/source/engine/shapes/drawinglayeranimation.cxx b/slideshow/source/engine/shapes/drawinglayeranimation.cxx
new file mode 100644
index 000000000..0b37071fb
--- /dev/null
+++ b/slideshow/source/engine/shapes/drawinglayeranimation.cxx
@@ -0,0 +1,932 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#include <tools/diagnose_ex.h>
+#include <tools/gen.hxx>
+#include <tools/helpers.hxx>
+#include <canvas/elapsedtime.hxx>
+#include <basegfx/polygon/b2dpolygontools.hxx>
+
+#include <vcl/canvastools.hxx>
+#include <com/sun/star/beans/XPropertySet.hpp>
+#include <com/sun/star/drawing/TextAnimationKind.hpp>
+#include <com/sun/star/drawing/TextAnimationDirection.hpp>
+
+#include <activity.hxx>
+#include <wakeupevent.hxx>
+#include <eventqueue.hxx>
+#include "drawinglayeranimation.hxx"
+#include "drawshapesubsetting.hxx"
+#include "drawshape.hxx"
+#include <shapeattributelayerholder.hxx>
+#include <slideshowcontext.hxx>
+#include <subsettableshapemanager.hxx>
+#include <tools.hxx>
+#include "gdimtftools.hxx"
+#include <intrinsicanimationeventhandler.hxx>
+
+#include <vector>
+#include <memory>
+
+using namespace com::sun::star;
+using namespace ::slideshow::internal;
+
+namespace {
+
+class ScrollTextAnimNode
+{
+ sal_uInt32 mnDuration; // single duration
+ sal_uInt32 mnRepeat; // 0 -> endless
+ double mfStart;
+ double mfStop;
+ sal_uInt32 mnFrequency; // in ms
+ // forth and back change at mnRepeat%2:
+ bool mbAlternate;
+
+public:
+ ScrollTextAnimNode(
+ sal_uInt32 nDuration, sal_uInt32 nRepeat, double fStart, double fStop,
+ sal_uInt32 nFrequency, bool bAlternate)
+ : mnDuration(nDuration),
+ mnRepeat(nRepeat),
+ mfStart(fStart),
+ mfStop(fStop),
+ mnFrequency(nFrequency),
+ mbAlternate(bAlternate)
+ {}
+
+ sal_uInt32 GetRepeat() const { return mnRepeat; }
+ sal_uInt32 GetFullTime() const { return mnDuration * mnRepeat; }
+ double GetStop() const { return mfStop; }
+ sal_uInt32 GetFrequency() const { return mnFrequency; }
+ bool DoAlternate() const { return mbAlternate; }
+
+ double GetStateAtRelativeTime(sal_uInt32 nRelativeTime) const;
+};
+
+double ScrollTextAnimNode::GetStateAtRelativeTime(
+ sal_uInt32 nRelativeTime) const
+{
+ // Avoid division by zero.
+ if( mnDuration == 0 )
+ return mfStop;
+
+ if(mnRepeat)
+ {
+ // ending
+ const sal_uInt32 nRepeatCount(nRelativeTime / mnDuration);
+ sal_uInt32 nFrameTime(nRelativeTime - (nRepeatCount * mnDuration));
+
+ if(DoAlternate() && (nRepeatCount + 1) % 2L)
+ nFrameTime = mnDuration - nFrameTime;
+
+ return mfStart + ((mfStop - mfStart) *
+ (double(nFrameTime) / mnDuration));
+ }
+ else
+ {
+ // endless
+ sal_uInt32 nFrameTime(nRelativeTime % mnDuration);
+
+ if(DoAlternate())
+ {
+ const sal_uInt32 nRepeatCount(nRelativeTime / mnDuration);
+
+ if((nRepeatCount + 1) % 2L)
+ nFrameTime = mnDuration - nFrameTime;
+ }
+
+ return mfStart + ((mfStop - mfStart) * (double(nFrameTime) / mnDuration));
+ }
+}
+
+class ActivityImpl : public Activity
+{
+public:
+ ActivityImpl(
+ SlideShowContext const& rContext,
+ std::shared_ptr<WakeupEvent> const& pWakeupEvent,
+ std::shared_ptr<DrawShape> const& pDrawShape );
+
+ ActivityImpl(const ActivityImpl&) = delete;
+ ActivityImpl& operator=(const ActivityImpl&) = delete;
+
+ bool enableAnimations();
+
+ // Disposable:
+ virtual void dispose() override;
+ // Activity:
+ virtual double calcTimeLag() const override;
+ virtual bool perform() override;
+ virtual bool isActive() const override;
+ virtual void dequeued() override;
+ virtual void end() override;
+
+private:
+ void updateShapeAttributes( double fTime,
+ basegfx::B2DRectangle const& parentBounds );
+
+ // scroll horizontal? if sal_False, scroll is vertical.
+ bool ScrollHorizontal() const {
+ return (drawing::TextAnimationDirection_LEFT == meDirection ||
+ drawing::TextAnimationDirection_RIGHT == meDirection);
+ }
+
+ // Access to StepWidth in logical units
+ sal_uInt32 GetStepWidthLogic() const;
+
+ // is the animation direction opposite?
+ bool DoScrollForward() const {
+ return (drawing::TextAnimationDirection_RIGHT == meDirection ||
+ drawing::TextAnimationDirection_DOWN == meDirection);
+ }
+
+ // do alternate text directions?
+ bool DoAlternate() const { return mbAlternate; }
+
+ // do scroll in?
+ bool DoScrollIn() const { return mbScrollIn; }
+
+ // Scroll helper methods
+ void ImpForceScrollTextAnimNodes();
+ ScrollTextAnimNode* ImpGetScrollTextAnimNode(
+ sal_uInt32 nTime, sal_uInt32& rRelativeTime );
+ sal_uInt32 ImpRegisterAgainScrollTextMixerState(
+ sal_uInt32 nTime);
+
+ // calculate the MixerState value for given time
+ double GetMixerState(sal_uInt32 nTime);
+
+
+ SlideShowContext maContext;
+ std::shared_ptr<WakeupEvent> mpWakeupEvent;
+ std::weak_ptr<DrawShape> mpParentDrawShape;
+ DrawShapeSharedPtr mpDrawShape;
+ ShapeAttributeLayerHolder maShapeAttrLayer;
+ GDIMetaFileSharedPtr mpMetaFile;
+ IntrinsicAnimationEventHandlerSharedPtr mpListener;
+ canvas::tools::ElapsedTime maTimer;
+ double mfRotationAngle;
+ bool mbIsShapeAnimated;
+ bool mbIsDisposed;
+ bool mbIsActive;
+ drawing::TextAnimationKind meAnimKind;
+
+ // The blink frequency in ms
+ sal_uInt32 mnFrequency;
+
+ // The repeat count, init to 0L which means endless
+ sal_uInt32 mnRepeat;
+
+ // Flag to decide if text will be shown when animation has ended
+ bool mbVisibleWhenStopped;
+ bool mbVisibleWhenStarted;
+
+ // Flag decides if TextScroll alternates. Default is sal_False.
+ bool mbAlternate;
+
+ // Flag to remember if this is a simple scrolling text
+ bool mbScrollIn;
+
+ // The AnimationDirection
+ drawing::TextAnimationDirection meDirection;
+
+ // Get width per Step. Negative means pixel, positive logical units
+ sal_Int32 mnStepWidth;
+
+ // The single anim steps
+ std::vector< ScrollTextAnimNode > maVector;
+
+ // the scroll rectangle
+ tools::Rectangle maScrollRectangleLogic;
+
+ // the paint rectangle
+ tools::Rectangle maPaintRectangleLogic;
+};
+
+
+class IntrinsicAnimationListener : public IntrinsicAnimationEventHandler
+{
+public:
+ explicit IntrinsicAnimationListener( ActivityImpl& rActivity ) :
+ mrActivity( rActivity )
+ {}
+
+ IntrinsicAnimationListener(const IntrinsicAnimationListener&) = delete;
+ IntrinsicAnimationListener& operator=(const IntrinsicAnimationListener&) = delete;
+
+private:
+
+ virtual bool enableAnimations() override { return mrActivity.enableAnimations(); }
+ virtual bool disableAnimations() override { mrActivity.end(); return true; }
+
+ ActivityImpl& mrActivity;
+};
+
+
+double ActivityImpl::GetMixerState( sal_uInt32 nTime )
+{
+ if( meAnimKind == drawing::TextAnimationKind_BLINK )
+ {
+ // from AInfoBlinkText:
+ double fRetval(0.0);
+ bool bDone(false);
+ const sal_uInt32 nLoopTime(2 * mnFrequency);
+
+ if(mnRepeat)
+ {
+ const sal_uInt32 nEndTime(mnRepeat * nLoopTime);
+
+ if(nTime >= nEndTime)
+ {
+ if(mbVisibleWhenStopped)
+ fRetval = 0.0;
+ else
+ fRetval = 1.0;
+
+ bDone = true;
+ }
+ }
+
+ if(!bDone)
+ {
+ sal_uInt32 nTimeInLoop(nTime % nLoopTime);
+ fRetval = double(nTimeInLoop) / nLoopTime;
+ }
+
+ return fRetval;
+ }
+ else
+ {
+ // from AInfoScrollText:
+ double fRetval(0.0);
+ ImpForceScrollTextAnimNodes();
+
+ if(!maVector.empty())
+ {
+ sal_uInt32 nRelativeTime;
+ ScrollTextAnimNode* pNode =
+ ImpGetScrollTextAnimNode(nTime, nRelativeTime);
+
+ if(pNode)
+ {
+ // use node
+ fRetval = pNode->GetStateAtRelativeTime(nRelativeTime);
+ }
+ else
+ {
+ // end of animation, take last entry's end
+ fRetval = maVector[maVector.size() - 1].GetStop();
+ }
+ }
+
+ return fRetval;
+ }
+}
+
+// Access to StepWidth in logical units
+sal_uInt32 ActivityImpl::GetStepWidthLogic() const
+{
+ // #i69847# Assuming higher DPI
+ constexpr sal_uInt32 PIXEL_TO_LOGIC = 30;
+
+ sal_uInt32 nRetval(0);
+
+ if(mnStepWidth < 0)
+ {
+ // is in pixels, convert to logical units
+ nRetval = (-mnStepWidth * PIXEL_TO_LOGIC);
+ }
+ else if(mnStepWidth > 0)
+ {
+ // is in logical units
+ nRetval = mnStepWidth;
+ }
+
+ if(0 == nRetval)
+ {
+ // step 1 pixel, canned value
+
+ // with very high DPIs like in PDF export, this can
+ // still get zero. for that cases, set a default, too (taken
+ // from ainfoscrolltext.cxx)
+ nRetval = 100;
+ }
+
+ return nRetval;
+}
+
+void ActivityImpl::ImpForceScrollTextAnimNodes()
+{
+ if(!maVector.empty())
+ return;
+
+ // prepare values
+ sal_uInt32 nLoopTime;
+ double fZeroLogic, fOneLogic, fInitLogic, fDistanceLogic;
+ double fZeroLogicAlternate = 0.0, fOneLogicAlternate = 0.0;
+ double fZeroRelative, fOneRelative, fInitRelative;
+
+ if(ScrollHorizontal())
+ {
+ if(DoAlternate())
+ {
+ if(maPaintRectangleLogic.GetWidth() >
+ maScrollRectangleLogic.GetWidth())
+ {
+ fZeroLogicAlternate = maScrollRectangleLogic.Right() - maPaintRectangleLogic.GetWidth();
+ fOneLogicAlternate = maScrollRectangleLogic.Left();
+ }
+ else
+ {
+ fZeroLogicAlternate = maScrollRectangleLogic.Left();
+ fOneLogicAlternate = maScrollRectangleLogic.Right() - maPaintRectangleLogic.GetWidth();
+ }
+ }
+
+ fZeroLogic = maScrollRectangleLogic.Left() - maPaintRectangleLogic.GetWidth();
+ fOneLogic = maScrollRectangleLogic.Right();
+ fInitLogic = maPaintRectangleLogic.Left();
+ }
+ else
+ {
+ if(DoAlternate())
+ {
+ if(maPaintRectangleLogic.GetHeight() > maScrollRectangleLogic.GetHeight())
+ {
+ fZeroLogicAlternate = maScrollRectangleLogic.Bottom() - maPaintRectangleLogic.GetHeight();
+ fOneLogicAlternate = maScrollRectangleLogic.Top();
+ }
+ else
+ {
+ fZeroLogicAlternate = maScrollRectangleLogic.Top();
+ fOneLogicAlternate = maScrollRectangleLogic.Bottom() - maPaintRectangleLogic.GetHeight();
+ }
+ }
+
+ fZeroLogic = maScrollRectangleLogic.Top() - maPaintRectangleLogic.GetHeight();
+ fOneLogic = maScrollRectangleLogic.Bottom();
+ fInitLogic = maPaintRectangleLogic.Top();
+ }
+
+ fDistanceLogic = fOneLogic - fZeroLogic;
+ fInitRelative = (fInitLogic - fZeroLogic) / fDistanceLogic;
+
+ if(DoAlternate())
+ {
+ fZeroRelative =
+ (fZeroLogicAlternate - fZeroLogic) / fDistanceLogic;
+ fOneRelative =
+ (fOneLogicAlternate - fZeroLogic) / fDistanceLogic;
+ }
+ else
+ {
+ fZeroRelative = 0.0;
+ fOneRelative = 1.0;
+ }
+
+ if(mbVisibleWhenStarted)
+ {
+ double fRelativeStartValue, fRelativeEndValue,fRelativeDistance;
+
+ if(DoScrollForward())
+ {
+ fRelativeStartValue = fInitRelative;
+ fRelativeEndValue = fOneRelative;
+ fRelativeDistance = fRelativeEndValue - fRelativeStartValue;
+ }
+ else
+ {
+ fRelativeStartValue = fInitRelative;
+ fRelativeEndValue = fZeroRelative;
+ fRelativeDistance = fRelativeStartValue - fRelativeEndValue;
+ }
+
+ const double fNumberSteps =
+ (fRelativeDistance * fDistanceLogic) / GetStepWidthLogic();
+ nLoopTime = FRound(fNumberSteps * mnFrequency);
+
+ // init loop
+ ScrollTextAnimNode aInitNode(
+ nLoopTime, 1,
+ fRelativeStartValue, fRelativeEndValue,
+ mnFrequency, false);
+ maVector.push_back(aInitNode);
+ }
+
+ // prepare main loop values
+ {
+ double fRelativeStartValue, fRelativeEndValue, fRelativeDistance;
+
+ if(DoScrollForward())
+ {
+ fRelativeStartValue = fZeroRelative;
+ fRelativeEndValue = fOneRelative;
+ fRelativeDistance = fRelativeEndValue - fRelativeStartValue;
+ }
+ else
+ {
+ fRelativeStartValue = fOneRelative;
+ fRelativeEndValue = fZeroRelative;
+ fRelativeDistance = fRelativeStartValue - fRelativeEndValue;
+ }
+
+ const double fNumberSteps =
+ (fRelativeDistance * fDistanceLogic) / GetStepWidthLogic();
+ nLoopTime = FRound(fNumberSteps * mnFrequency);
+
+ if(0 == mnRepeat)
+ {
+ if(!DoScrollIn())
+ {
+ // endless main loop
+ ScrollTextAnimNode aMainNode(
+ nLoopTime, 0,
+ fRelativeStartValue, fRelativeEndValue,
+ mnFrequency, DoAlternate());
+ maVector.push_back(aMainNode);
+ }
+ }
+ else
+ {
+ sal_uInt32 nNumRepeat(mnRepeat);
+
+ if(DoAlternate() && (nNumRepeat + 1) % 2L)
+ nNumRepeat += 1;
+
+ // ending main loop
+ ScrollTextAnimNode aMainNode(
+ nLoopTime, nNumRepeat,
+ fRelativeStartValue, fRelativeEndValue,
+ mnFrequency, DoAlternate());
+ maVector.push_back(aMainNode);
+ }
+ }
+
+ if(!mbVisibleWhenStopped)
+ return;
+
+ double fRelativeStartValue, fRelativeEndValue, fRelativeDistance;
+
+ if(DoScrollForward())
+ {
+ fRelativeStartValue = fZeroRelative;
+ fRelativeEndValue = fInitRelative;
+ fRelativeDistance = fRelativeEndValue - fRelativeStartValue;
+ }
+ else
+ {
+ fRelativeStartValue = fOneRelative;
+ fRelativeEndValue = fInitRelative;
+ fRelativeDistance = fRelativeStartValue - fRelativeEndValue;
+ }
+
+ const double fNumberSteps =
+ (fRelativeDistance * fDistanceLogic) / GetStepWidthLogic();
+ nLoopTime = FRound(fNumberSteps * mnFrequency);
+
+ // exit loop
+ ScrollTextAnimNode aExitNode(
+ nLoopTime, 1,
+ fRelativeStartValue, fRelativeEndValue, mnFrequency, false);
+ maVector.push_back(aExitNode);
+}
+
+ScrollTextAnimNode* ActivityImpl::ImpGetScrollTextAnimNode(
+ sal_uInt32 nTime, sal_uInt32& rRelativeTime )
+{
+ ScrollTextAnimNode* pRetval = nullptr;
+ ImpForceScrollTextAnimNodes();
+
+ if(!maVector.empty())
+ {
+ rRelativeTime = nTime;
+
+ for(ScrollTextAnimNode & rNode: maVector)
+ {
+ if(!rNode.GetRepeat())
+ {
+ // endless loop, use it
+ pRetval = &rNode;
+ }
+ else if(rNode.GetFullTime() > rRelativeTime)
+ {
+ // ending node
+ pRetval = &rNode;
+ }
+ else
+ {
+ // look at next
+ rRelativeTime -= rNode.GetFullTime();
+ }
+ }
+ }
+
+ return pRetval;
+}
+
+sal_uInt32 ActivityImpl::ImpRegisterAgainScrollTextMixerState(sal_uInt32 nTime)
+{
+ sal_uInt32 nRetval(0);
+ ImpForceScrollTextAnimNodes();
+
+ if(!maVector.empty())
+ {
+ sal_uInt32 nRelativeTime;
+ ScrollTextAnimNode* pNode = ImpGetScrollTextAnimNode(nTime, nRelativeTime);
+
+ if(pNode)
+ {
+ // take register time
+ nRetval = pNode->GetFrequency();
+ }
+ }
+ else
+ {
+ // #i38135# not initialized, return default
+ nRetval = mnFrequency;
+ }
+
+ return nRetval;
+}
+
+void ActivityImpl::updateShapeAttributes(
+ double fTime, basegfx::B2DRectangle const& parentBounds )
+{
+ OSL_ASSERT( meAnimKind != drawing::TextAnimationKind_NONE );
+ if( meAnimKind == drawing::TextAnimationKind_NONE )
+ return;
+
+ double const fMixerState = GetMixerState(
+ static_cast<sal_uInt32>(fTime * 1000.0) );
+
+ if( meAnimKind == drawing::TextAnimationKind_BLINK )
+ {
+ // show/hide text:
+ maShapeAttrLayer.get()->setVisibility( fMixerState < 0.5 );
+ }
+ else if(mpMetaFile) // scroll mode:
+ {
+
+ // keep care: the below code is highly sensible to changes...
+
+
+ // rectangle of the pure text:
+ double const fPaintWidth = maPaintRectangleLogic.GetWidth();
+ double const fPaintHeight = maPaintRectangleLogic.GetHeight();
+ // rectangle where the scrolling takes place (-> clipping):
+ double const fScrollWidth = maScrollRectangleLogic.GetWidth();
+ double const fScrollHeight = maScrollRectangleLogic.GetHeight();
+
+ basegfx::B2DPoint pos, clipPos;
+
+ if(ScrollHorizontal())
+ {
+ double const fOneEquiv( fScrollWidth );
+ double const fZeroEquiv( -fPaintWidth );
+
+ pos.setX( fZeroEquiv + (fMixerState * (fOneEquiv - fZeroEquiv)) );
+
+ clipPos.setX( -pos.getX() );
+ clipPos.setY( -pos.getY() );
+
+ // #i69844# Compensation for text-wider-than-shape case
+ if( fPaintWidth > fScrollWidth )
+ pos.setX( pos.getX() + (fPaintWidth-fScrollWidth) / 2.0 );
+ }
+ else
+ {
+ // scroll vertical:
+ double const fOneEquiv( fScrollHeight );
+ double const fZeroEquiv( -fPaintHeight );
+
+ pos.setY( fZeroEquiv + (fMixerState * (fOneEquiv - fZeroEquiv)) );
+
+ clipPos.setX( -pos.getX() );
+ clipPos.setY( -pos.getY() );
+
+ // #i69844# Compensation for text-higher-than-shape case
+ if( fPaintHeight > fScrollHeight )
+ pos.setY( pos.getY() + (fPaintHeight-fScrollHeight) / 2.0 );
+ }
+
+ basegfx::B2DPolygon clipPoly(
+ basegfx::utils::createPolygonFromRect(
+ basegfx::B2DRectangle( clipPos.getX(),
+ clipPos.getY(),
+ clipPos.getX() + fScrollWidth,
+ clipPos.getY() + fScrollHeight ) ) );
+
+ if( !::basegfx::fTools::equalZero( mfRotationAngle ))
+ {
+ maShapeAttrLayer.get()->setRotationAngle( mfRotationAngle );
+ double const fRotate = basegfx::deg2rad(mfRotationAngle);
+ basegfx::B2DHomMatrix aTransform;
+ // position:
+ aTransform.rotate( fRotate );
+ pos *= aTransform;
+ }
+
+ pos += parentBounds.getCenter();
+ maShapeAttrLayer.get()->setPosition( pos );
+ maShapeAttrLayer.get()->setClip( basegfx::B2DPolyPolygon(clipPoly) );
+ }
+}
+
+bool ActivityImpl::perform()
+{
+ if( !isActive() )
+ return false;
+
+ ENSURE_OR_RETURN_FALSE(
+ mpDrawShape,
+ "ActivityImpl::perform(): still active, but NULL draw shape" );
+
+ DrawShapeSharedPtr const pParentDrawShape( mpParentDrawShape );
+ if( !pParentDrawShape )
+ return false; // parent has vanished
+
+ if( pParentDrawShape->isVisible() )
+ {
+ if( !mbIsShapeAnimated )
+ {
+ mpDrawShape->setVisibility(true); // shape may be initially hidden
+ maContext.mpSubsettableShapeManager->enterAnimationMode( mpDrawShape );
+ maTimer.reset();
+ mbIsShapeAnimated = true;
+ }
+ // update attributes related to current time:
+ basegfx::B2DRectangle const parentBounds(
+ pParentDrawShape->getBounds() );
+
+ const double nCurrTime( maTimer.getElapsedTime() );
+ updateShapeAttributes( nCurrTime, parentBounds );
+
+ const sal_uInt32 nFrequency(
+ ImpRegisterAgainScrollTextMixerState(
+ static_cast<sal_uInt32>(nCurrTime * 1000.0)) );
+
+ if(nFrequency)
+ {
+ mpWakeupEvent->start();
+ mpWakeupEvent->setNextTimeout(
+ std::max(0.1,nFrequency/1000.0) );
+ maContext.mrEventQueue.addEvent( mpWakeupEvent );
+
+ if( mpDrawShape->isContentChanged() )
+ maContext.mpSubsettableShapeManager->notifyShapeUpdate( mpDrawShape );
+ }
+ // else: finished, not need to wake up again.
+ }
+ else
+ {
+ // busy-wait, until parent shape gets visible
+ mpWakeupEvent->start();
+ mpWakeupEvent->setNextTimeout( 2.0 );
+ }
+
+ // don't reinsert, WakeupEvent will perform that after the given timeout:
+ return false;
+}
+
+ActivityImpl::ActivityImpl(
+ SlideShowContext const& rContext,
+ std::shared_ptr<WakeupEvent> const& pWakeupEvent,
+ std::shared_ptr<DrawShape> const& pParentDrawShape )
+ : maContext(rContext),
+ mpWakeupEvent(pWakeupEvent),
+ mpParentDrawShape(pParentDrawShape),
+ mpListener( std::make_shared<IntrinsicAnimationListener>(*this) ),
+ maTimer(rContext.mrEventQueue.getTimer()),
+ mfRotationAngle(0.0),
+ mbIsShapeAnimated(false),
+ mbIsDisposed(false),
+ mbIsActive(true),
+ meAnimKind(drawing::TextAnimationKind_NONE),
+ mbVisibleWhenStopped(false),
+ mbVisibleWhenStarted(false),
+ mnStepWidth(0)
+{
+ // get doctreenode:
+ sal_Int32 const nNodes = pParentDrawShape->getNumberOfTreeNodes(
+ DocTreeNode::NodeType::LogicalParagraph );
+
+ DocTreeNode scrollTextNode(
+ pParentDrawShape->getTreeNode(
+ 0, DocTreeNode::NodeType::LogicalParagraph ));
+ // xxx todo: remove this hack
+ if( nNodes > 1 )
+ scrollTextNode.setEndIndex(
+ pParentDrawShape->getTreeNode(
+ nNodes - 1,
+ DocTreeNode::NodeType::LogicalParagraph ).getEndIndex());
+
+ // TODO(Q3): Doing this manually, instead of using
+ // ShapeSubset. This is because of lifetime issues (ShapeSubset
+ // generates circular references to parent shape)
+ mpDrawShape = std::dynamic_pointer_cast<DrawShape>(
+ maContext.mpSubsettableShapeManager->getSubsetShape(
+ pParentDrawShape,
+ scrollTextNode ));
+
+ mpMetaFile = mpDrawShape->forceScrollTextMetaFile();
+
+ // make scroll text invisible for slide transition bitmaps
+ mpDrawShape->setVisibility(false);
+
+ basegfx::B2DRectangle aScrollRect, aPaintRect;
+ ENSURE_OR_THROW( getRectanglesFromScrollMtf( aScrollRect,
+ aPaintRect,
+ mpMetaFile ),
+ "ActivityImpl::ActivityImpl(): Could not extract "
+ "scroll anim rectangles from mtf" );
+
+ maScrollRectangleLogic = vcl::unotools::rectangleFromB2DRectangle(
+ aScrollRect );
+ maPaintRectangleLogic = vcl::unotools::rectangleFromB2DRectangle(
+ aPaintRect );
+
+ maShapeAttrLayer.createAttributeLayer(mpDrawShape);
+
+ uno::Reference<drawing::XShape> const xShape( mpDrawShape->getXShape() );
+ uno::Reference<beans::XPropertySet> const xProps( xShape, uno::UNO_QUERY_THROW );
+
+ getPropertyValue( meAnimKind, xProps, "TextAnimationKind" );
+ OSL_ASSERT( meAnimKind != drawing::TextAnimationKind_NONE );
+ mbAlternate = (meAnimKind == drawing::TextAnimationKind_ALTERNATE);
+ mbScrollIn = (meAnimKind == drawing::TextAnimationKind_SLIDE);
+
+ // adopted from in AInfoBlinkText::ImplInit():
+ sal_Int16 nRepeat(0);
+ getPropertyValue( nRepeat, xProps, "TextAnimationCount" );
+ mnRepeat = nRepeat;
+
+ if(mbAlternate)
+ {
+ // force visible when started for scroll-forth-and-back, because
+ // slide has been coming in with visible text in the middle:
+ mbVisibleWhenStarted = true;
+ }
+ else
+ {
+ getPropertyValue( mbVisibleWhenStarted, xProps,
+ "TextAnimationStartInside" );
+ }
+
+ // set visible when stopped
+ getPropertyValue( mbVisibleWhenStopped, xProps,
+ "TextAnimatiogonStopInside" );
+ // rotation:
+ getPropertyValue( mfRotationAngle, xProps,
+ "RotateAngle" );
+ mfRotationAngle /= -100.0; // (switching direction)
+
+ // set frequency
+ sal_Int16 nDelay(0);
+ getPropertyValue( nDelay, xProps, "TextAnimationDelay" );
+ // set delay if not automatic
+ mnFrequency = (nDelay ? nDelay :
+ // default:
+ meAnimKind == drawing::TextAnimationKind_BLINK
+ ? 250 : 50 );
+
+ // adopted from in AInfoScrollText::ImplInit():
+
+ // If it is a simple m_bScrollIn, reset some parameters
+ if( DoScrollIn() )
+ {
+ // most parameters are set correctly from the dialog logic, but
+ // eg VisibleWhenStopped is grayed out and needs to be corrected here.
+ mbVisibleWhenStopped = true;
+ mbVisibleWhenStarted = false;
+ mnRepeat = 0;
+ }
+
+ // Get animation direction
+ getPropertyValue( meDirection, xProps, "TextAnimationDirection" );
+
+ // Get step width. Negative means pixel, positive logical units
+ getPropertyValue( mnStepWidth, xProps, "TextAnimationAmount" );
+
+ maContext.mpSubsettableShapeManager->addIntrinsicAnimationHandler(
+ mpListener );
+}
+
+bool ActivityImpl::enableAnimations()
+{
+ mbIsActive = true;
+ return maContext.mrActivitiesQueue.addActivity( std::dynamic_pointer_cast<Activity>(shared_from_this()) );
+}
+
+void ActivityImpl::dispose()
+{
+ if( mbIsDisposed )
+ return;
+
+ end();
+
+ // only remove subset here, since end() is called on slide end
+ // (and we must not spoil the slide preview bitmap with scroll
+ // text)
+ maShapeAttrLayer.reset();
+ if( mpDrawShape )
+ {
+ // TODO(Q3): Doing this manually, instead of using
+ // ShapeSubset. This is because of lifetime issues
+ // (ShapeSubset generates circular references to parent
+ // shape)
+ DrawShapeSharedPtr pParent( mpParentDrawShape.lock() );
+ if( pParent )
+ maContext.mpSubsettableShapeManager->revokeSubset(
+ pParent,
+ mpDrawShape );
+ }
+
+ mpMetaFile.reset();
+ mpDrawShape.reset();
+ mpParentDrawShape.reset();
+ mpWakeupEvent.reset();
+ maContext.dispose();
+ mbIsDisposed = true;
+
+ maContext.mpSubsettableShapeManager->removeIntrinsicAnimationHandler(
+ mpListener );
+}
+
+double ActivityImpl::calcTimeLag() const
+{
+ return 0.0;
+}
+
+bool ActivityImpl::isActive() const
+{
+ return mbIsActive;
+}
+
+void ActivityImpl::dequeued()
+{
+ // not used here
+}
+
+void ActivityImpl::end()
+{
+ // not used here
+ mbIsActive = false;
+
+ if( mbIsShapeAnimated )
+ {
+ maContext.mpSubsettableShapeManager->leaveAnimationMode( mpDrawShape );
+ mbIsShapeAnimated = false;
+ }
+}
+
+} // anon namespace
+
+namespace slideshow::internal {
+
+std::shared_ptr<Activity> createDrawingLayerAnimActivity(
+ SlideShowContext const& rContext,
+ std::shared_ptr<DrawShape> const& pDrawShape )
+{
+ std::shared_ptr<Activity> pActivity;
+
+ try
+ {
+ auto const pWakeupEvent = std::make_shared<WakeupEvent>( rContext.mrEventQueue.getTimer(),
+ rContext.mrActivitiesQueue );
+ pActivity = std::make_shared<ActivityImpl>( rContext, pWakeupEvent, pDrawShape );
+ pWakeupEvent->setActivity( pActivity );
+ }
+ catch( uno::RuntimeException& )
+ {
+ throw;
+ }
+ catch( uno::Exception& )
+ {
+ // translate any error into empty factory product.
+ TOOLS_WARN_EXCEPTION( "slideshow", "" );
+ }
+
+ return pActivity;
+}
+
+} // namespace presentation
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/slideshow/source/engine/shapes/drawinglayeranimation.hxx b/slideshow/source/engine/shapes/drawinglayeranimation.hxx
new file mode 100644
index 000000000..5a143b087
--- /dev/null
+++ b/slideshow/source/engine/shapes/drawinglayeranimation.hxx
@@ -0,0 +1,39 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#ifndef INCLUDED_SLIDESHOW_SOURCE_ENGINE_SHAPES_DRAWINGLAYERANIMATION_HXX
+#define INCLUDED_SLIDESHOW_SOURCE_ENGINE_SHAPES_DRAWINGLAYERANIMATION_HXX
+
+#include <sal/config.h>
+#include <memory>
+
+namespace slideshow::internal {
+
+class Activity;
+struct SlideShowContext;
+class DrawShape;
+
+std::shared_ptr<Activity> createDrawingLayerAnimActivity(
+ SlideShowContext const& rContext,
+ std::shared_ptr<DrawShape> const& pDrawShape );
+
+} // namespace presentation::internal
+
+#endif // ! defined INCLUDED_SLIDESHOW_SOURCE_ENGINE_SHAPES_DRAWINGLAYERANIMATION_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/slideshow/source/engine/shapes/drawshape.cxx b/slideshow/source/engine/shapes/drawshape.cxx
new file mode 100644
index 000000000..000ffd262
--- /dev/null
+++ b/slideshow/source/engine/shapes/drawshape.cxx
@@ -0,0 +1,1228 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <tools/diagnose_ex.h>
+
+#include <sal/log.hxx>
+#include <com/sun/star/beans/XPropertySet.hpp>
+#include <o3tl/safeint.hxx>
+
+#include <vcl/metaact.hxx>
+#include <vcl/gdimtf.hxx>
+#include <vcl/graph.hxx>
+
+#include <basegfx/numeric/ftools.hxx>
+
+#include <com/sun/star/drawing/TextAnimationKind.hpp>
+
+#include <comphelper/scopeguard.hxx>
+
+#include <algorithm>
+#include <iterator>
+#include <functional>
+
+#include "drawshapesubsetting.hxx"
+#include "drawshape.hxx"
+#include <eventqueue.hxx>
+#include <wakeupevent.hxx>
+#include <subsettableshapemanager.hxx>
+#include "intrinsicanimationactivity.hxx"
+#include <tools.hxx>
+#include "gdimtftools.hxx"
+#include "drawinglayeranimation.hxx"
+
+using namespace ::com::sun::star;
+
+
+namespace slideshow::internal
+{
+
+
+ // Private methods
+
+
+ GDIMetaFileSharedPtr const & DrawShape::forceScrollTextMetaFile()
+ {
+ if ((mnCurrMtfLoadFlags & MTF_LOAD_SCROLL_TEXT_MTF) != MTF_LOAD_SCROLL_TEXT_MTF)
+ {
+ // reload with added flags:
+ mnCurrMtfLoadFlags |= MTF_LOAD_SCROLL_TEXT_MTF;
+ mpCurrMtf = getMetaFile(uno::Reference<lang::XComponent>(mxShape, uno::UNO_QUERY),
+ mxPage, mnCurrMtfLoadFlags,
+ mxComponentContext);
+
+ if (!mpCurrMtf)
+ mpCurrMtf = std::make_shared<GDIMetaFile>();
+
+ // TODO(F1): Currently, the scroll metafile will
+ // never contain any verbose text comments. Thus,
+ // can only display the full mtf content, no
+ // subsets.
+ maSubsetting.reset( mpCurrMtf );
+
+ // adapt maBounds. the requested scroll text metafile
+ // will typically have dimension different from the
+ // actual shape
+ ::basegfx::B2DRectangle aScrollRect, aPaintRect;
+ ENSURE_OR_THROW( getRectanglesFromScrollMtf( aScrollRect,
+ aPaintRect,
+ mpCurrMtf ),
+ "DrawShape::forceScrollTextMetaFile(): Could "
+ "not extract scroll anim rectangles from mtf" );
+
+ // take the larger one of the two rectangles (that
+ // should be the bound rect of the retrieved
+ // metafile)
+ if( aScrollRect.isInside( aPaintRect ) )
+ maBounds = aScrollRect;
+ else
+ maBounds = aPaintRect;
+ }
+ return mpCurrMtf;
+ }
+
+ void DrawShape::updateStateIds() const
+ {
+ // Update the states, we've just redrawn or created a new
+ // attribute layer.
+ if( mpAttributeLayer )
+ {
+ mnAttributeTransformationState = mpAttributeLayer->getTransformationState();
+ mnAttributeClipState = mpAttributeLayer->getClipState();
+ mnAttributeAlphaState = mpAttributeLayer->getAlphaState();
+ mnAttributePositionState = mpAttributeLayer->getPositionState();
+ mnAttributeContentState = mpAttributeLayer->getContentState();
+ mnAttributeVisibilityState = mpAttributeLayer->getVisibilityState();
+ }
+ }
+
+ ViewShape::RenderArgs DrawShape::getViewRenderArgs() const
+ {
+ return ViewShape::RenderArgs(
+ maBounds,
+ getUpdateArea(),
+ getBounds(),
+ getActualUnitShapeBounds(),
+ mpAttributeLayer,
+ maSubsetting.getActiveSubsets(),
+ mnPriority);
+ }
+
+ bool DrawShape::implRender( UpdateFlags nUpdateFlags ) const
+ {
+ SAL_INFO( "slideshow", "::presentation::internal::DrawShape::implRender()" );
+ SAL_INFO( "slideshow", "::presentation::internal::DrawShape: 0x" << std::hex << this );
+
+ // will perform the update now, clear update-enforcing
+ // flags
+ mbForceUpdate = false;
+ mbAttributeLayerRevoked = false;
+
+ ENSURE_OR_RETURN_FALSE( !maViewShapes.empty(),
+ "DrawShape::implRender(): render called on DrawShape without views" );
+
+ if( maBounds.isEmpty() )
+ {
+ // zero-sized shapes are effectively invisible,
+ // thus, we save us the rendering...
+ return true;
+ }
+
+ // redraw all view shapes, by calling their update() method
+ ViewShape::RenderArgs renderArgs( getViewRenderArgs() );
+ bool bVisible = isVisible();
+ if( o3tl::make_unsigned(::std::count_if( maViewShapes.begin(),
+ maViewShapes.end(),
+ [this, &bVisible, &renderArgs, &nUpdateFlags]
+ ( const ViewShapeSharedPtr& pShape )
+ { return pShape->update( this->mpCurrMtf,
+ renderArgs,
+ nUpdateFlags,
+ bVisible ); } ))
+ != maViewShapes.size() )
+ {
+ // at least one of the ViewShape::update() calls did return
+ // false - update failed on at least one ViewLayer
+ return false;
+ }
+
+ // successfully redrawn - update state IDs to detect next changes
+ updateStateIds();
+
+ return true;
+ }
+
+ UpdateFlags DrawShape::getUpdateFlags() const
+ {
+ // default: update nothing, unless ShapeAttributeStack
+ // tells us below, or if the attribute layer was revoked
+ UpdateFlags nUpdateFlags(UpdateFlags::NONE);
+
+ // possibly the whole shape content changed
+ if( mbAttributeLayerRevoked )
+ nUpdateFlags = UpdateFlags::Content;
+
+
+ // determine what has to be updated
+
+
+ // do we have an attribute layer?
+ if( mpAttributeLayer )
+ {
+ // Prevent nUpdateFlags to be modified when the shape is not
+ // visible, except when it just was hidden.
+ if (mpAttributeLayer->getVisibility()
+ || mpAttributeLayer->getVisibilityState() != mnAttributeVisibilityState )
+ {
+ if (mpAttributeLayer->getVisibilityState() != mnAttributeVisibilityState )
+ {
+ // Change of the visibility state is mapped to
+ // content change because when the visibility
+ // changes then usually a sprite is shown or hidden
+ // and the background under has to be painted once.
+ nUpdateFlags |= UpdateFlags::Content;
+ }
+
+ // TODO(P1): This can be done without conditional branching.
+ // See HAKMEM.
+ if( mpAttributeLayer->getPositionState() != mnAttributePositionState )
+ {
+ nUpdateFlags |= UpdateFlags::Position;
+ }
+ if( mpAttributeLayer->getAlphaState() != mnAttributeAlphaState )
+ {
+ nUpdateFlags |= UpdateFlags::Alpha;
+ }
+ if( mpAttributeLayer->getClipState() != mnAttributeClipState )
+ {
+ nUpdateFlags |= UpdateFlags::Clip;
+ }
+ if( mpAttributeLayer->getTransformationState() != mnAttributeTransformationState )
+ {
+ nUpdateFlags |= UpdateFlags::Transformation;
+ }
+ if( mpAttributeLayer->getContentState() != mnAttributeContentState )
+ {
+ nUpdateFlags |= UpdateFlags::Content;
+ }
+ }
+ }
+
+ return nUpdateFlags;
+ }
+
+ ::basegfx::B2DRectangle DrawShape::getActualUnitShapeBounds() const
+ {
+ ENSURE_OR_THROW( !maViewShapes.empty(),
+ "DrawShape::getActualUnitShapeBounds(): called on DrawShape without views" );
+
+ const VectorOfDocTreeNodes& rSubsets(
+ maSubsetting.getActiveSubsets() );
+
+ const ::basegfx::B2DRectangle aDefaultBounds( 0.0,0.0,1.0,1.0 );
+
+ // perform the cheapest check first
+ if( rSubsets.empty() )
+ {
+ // if subset contains the whole shape, no need to call
+ // the somewhat expensive bound calculation, since as
+ // long as the subset is empty, this branch will be
+ // taken.
+ return aDefaultBounds;
+ }
+ else
+ {
+ OSL_ENSURE( rSubsets.size() != 1 ||
+ !rSubsets.front().isEmpty(),
+ "DrawShape::getActualUnitShapeBounds() expects a "
+ "_non-empty_ subset vector for a subsetted shape!" );
+
+ // are the cached bounds still valid?
+ if( !maCurrentShapeUnitBounds )
+ {
+ // no, (re)generate them
+ // =====================
+
+ // setup cached values to defaults (might fail to
+ // retrieve true bounds below)
+ maCurrentShapeUnitBounds = aDefaultBounds;
+
+ // TODO(P2): the subset of the master shape (that from
+ // which the subsets are subtracted) changes
+ // relatively often (every time a subset shape is
+ // added or removed). Maybe we should exclude it here,
+ // always assuming full bounds?
+
+ ::cppcanvas::CanvasSharedPtr pDestinationCanvas(
+ maViewShapes.front()->getViewLayer()->getCanvas() );
+
+ // TODO(Q2): Although this _is_ currently
+ // view-agnostic, it might not stay like
+ // that. Maybe this method should again be moved
+ // to the ViewShape
+ ::cppcanvas::RendererSharedPtr pRenderer(
+ maViewShapes.front()->getRenderer(
+ pDestinationCanvas, mpCurrMtf, mpAttributeLayer ) );
+
+ // If we cannot not prefetch, be defensive and assume
+ // full shape size
+ if( pRenderer )
+ {
+ // temporarily, switch total transformation to identity
+ // (need the bounds in the [0,1]x[0,1] unit coordinate
+ // system.
+ ::basegfx::B2DHomMatrix aEmptyTransformation;
+
+ ::basegfx::B2DHomMatrix aOldTransform( pDestinationCanvas->getTransformation() );
+ pDestinationCanvas->setTransformation( aEmptyTransformation );
+ pRenderer->setTransformation( aEmptyTransformation );
+
+ // restore old transformation when leaving the scope
+ const ::comphelper::ScopeGuard aGuard(
+ [&pDestinationCanvas, &aOldTransform]()
+ { return pDestinationCanvas->setTransformation( aOldTransform ); } );
+
+
+ // retrieve bounds for subset of whole metafile
+
+
+ ::basegfx::B2DRange aTotalBounds;
+
+ // cannot use ::boost::bind, ::basegfx::B2DRange::expand()
+ // is overloaded.
+ for( const auto& rDocTreeNode : rSubsets )
+ aTotalBounds.expand( pRenderer->getSubsetArea(
+ rDocTreeNode.getStartIndex(),
+ rDocTreeNode.getEndIndex() ) );
+
+ OSL_ENSURE( aTotalBounds.getMinX() >= -0.1 &&
+ aTotalBounds.getMinY() >= -0.1 &&
+ aTotalBounds.getMaxX() <= 1.1 &&
+ aTotalBounds.getMaxY() <= 1.1,
+ "DrawShape::getActualUnitShapeBounds(): bounds noticeably larger than original shape - clipping!" );
+
+ // really make sure no shape appears larger than its
+ // original bounds (there _are_ some pathologic cases,
+ // especially when imported from PPT, that have
+ // e.g. obscenely large polygon bounds)
+ aTotalBounds.intersect(
+ ::basegfx::B2DRange( 0.0, 0.0,
+ 1.0, 1.0 ));
+
+ maCurrentShapeUnitBounds = aTotalBounds;
+ }
+ }
+
+ return *maCurrentShapeUnitBounds;
+ }
+ }
+
+ DrawShape::DrawShape( const uno::Reference< drawing::XShape >& xShape,
+ const uno::Reference< drawing::XDrawPage >& xContainingPage,
+ double nPrio,
+ bool bForeignSource,
+ const SlideShowContext& rContext ) :
+ mxShape( xShape ),
+ mxPage( xContainingPage ),
+ maAnimationFrames(), // empty, we don't have no intrinsic animation
+ mnCurrFrame(0),
+ mpCurrMtf(),
+ mnCurrMtfLoadFlags( bForeignSource
+ ? MTF_LOAD_FOREIGN_SOURCE : MTF_LOAD_NONE ),
+ maCurrentShapeUnitBounds(),
+ mnPriority( nPrio ), // TODO(F1): When ZOrder someday becomes usable: make this ( getAPIShapePrio( xShape ) ),
+ maBounds( getAPIShapeBounds( xShape ) ),
+ mpAttributeLayer(),
+ mpIntrinsicAnimationActivity(),
+ mnAttributeTransformationState(0),
+ mnAttributeClipState(0),
+ mnAttributeAlphaState(0),
+ mnAttributePositionState(0),
+ mnAttributeContentState(0),
+ mnAttributeVisibilityState(0),
+ maViewShapes(),
+ mxComponentContext( rContext.mxComponentContext ),
+ maHyperlinkIndices(),
+ maHyperlinkRegions(),
+ maSubsetting(),
+ mnIsAnimatedCount(0),
+ mnAnimationLoopCount(0),
+ mbIsVisible( true ),
+ mbForceUpdate( false ),
+ mbAttributeLayerRevoked( false ),
+ mbDrawingLayerAnim( false ),
+ mbContainsPageField( false )
+ {
+ ENSURE_OR_THROW( mxShape.is(), "DrawShape::DrawShape(): Invalid XShape" );
+ ENSURE_OR_THROW( mxPage.is(), "DrawShape::DrawShape(): Invalid containing page" );
+
+ // check for drawing layer animations:
+ drawing::TextAnimationKind eKind = drawing::TextAnimationKind_NONE;
+ uno::Reference<beans::XPropertySet> xPropSet( mxShape,
+ uno::UNO_QUERY );
+ if( xPropSet.is() )
+ getPropertyValue( eKind, xPropSet,
+ "TextAnimationKind" );
+ mbDrawingLayerAnim = (eKind != drawing::TextAnimationKind_NONE);
+
+ // must NOT be called from within initializer list, uses
+ // state from mnCurrMtfLoadFlags!
+ mpCurrMtf = getMetaFile(uno::Reference<lang::XComponent>(xShape, uno::UNO_QUERY),
+ xContainingPage, mnCurrMtfLoadFlags,
+ mxComponentContext );
+ if (!mpCurrMtf)
+ mpCurrMtf = std::make_shared<GDIMetaFile>();
+
+ maSubsetting.reset( mpCurrMtf );
+
+ prepareHyperlinkIndices();
+
+ if(mbContainsPageField && mpCurrMtf && !maBounds.isEmpty())
+ {
+ // tdf#150402 Use mbContainsPageField that gets set in prepareHyperlinkIndices
+ // which has to be run anyways, so this will cause no harm in execution speed.
+ // It lets us detect the potential error case that a PageField is contained in
+ // the Text of the Shape. That is a hint that maBounds contains the wrong Range
+ // and needs to be corrected. The correct size is in the PrefSize of the metafile.
+ // For more backgrund information please refer to tdf#150402, Comment 16.
+ const double fWidthDiff(fabs(mpCurrMtf->GetPrefSize().Width() - maBounds.getWidth()));
+ const double fHeightDiff(fabs(mpCurrMtf->GetPrefSize().Height() - maBounds.getHeight()));
+
+ if(fWidthDiff > 1.0 || fHeightDiff > 1.0)
+ {
+ maBounds = basegfx::B2DRange(
+ maBounds.getMinX(), maBounds.getMinY(),
+ maBounds.getMinX() + mpCurrMtf->GetPrefSize().Width(),
+ maBounds.getMinY() + mpCurrMtf->GetPrefSize().Height());
+ }
+ }
+ }
+
+ DrawShape::DrawShape( const uno::Reference< drawing::XShape >& xShape,
+ const uno::Reference< drawing::XDrawPage >& xContainingPage,
+ double nPrio,
+ const Graphic& rGraphic,
+ const SlideShowContext& rContext ) :
+ mxShape( xShape ),
+ mxPage( xContainingPage ),
+ maAnimationFrames(),
+ mnCurrFrame(0),
+ mpCurrMtf(),
+ mnCurrMtfLoadFlags( MTF_LOAD_NONE ),
+ maCurrentShapeUnitBounds(),
+ mnPriority( nPrio ), // TODO(F1): When ZOrder someday becomes usable: make this ( getAPIShapePrio( xShape ) ),
+ maBounds( getAPIShapeBounds( xShape ) ),
+ mpAttributeLayer(),
+ mpIntrinsicAnimationActivity(),
+ mnAttributeTransformationState(0),
+ mnAttributeClipState(0),
+ mnAttributeAlphaState(0),
+ mnAttributePositionState(0),
+ mnAttributeContentState(0),
+ mnAttributeVisibilityState(0),
+ maViewShapes(),
+ mxComponentContext( rContext.mxComponentContext ),
+ maHyperlinkIndices(),
+ maHyperlinkRegions(),
+ maSubsetting(),
+ mnIsAnimatedCount(0),
+ mnAnimationLoopCount(0),
+ mbIsVisible( true ),
+ mbForceUpdate( false ),
+ mbAttributeLayerRevoked( false ),
+ mbDrawingLayerAnim( false ),
+ mbContainsPageField( false )
+ {
+ ENSURE_OR_THROW( rGraphic.IsAnimated(),
+ "DrawShape::DrawShape(): Graphic is no animation" );
+
+ getAnimationFromGraphic( maAnimationFrames,
+ mnAnimationLoopCount,
+ rGraphic );
+
+ ENSURE_OR_THROW( !maAnimationFrames.empty() &&
+ maAnimationFrames.front().mpMtf,
+ "DrawShape::DrawShape(): " );
+ mpCurrMtf = maAnimationFrames.front().mpMtf;
+
+ ENSURE_OR_THROW( mxShape.is(), "DrawShape::DrawShape(): Invalid XShape" );
+ ENSURE_OR_THROW( mxPage.is(), "DrawShape::DrawShape(): Invalid containing page" );
+ ENSURE_OR_THROW( mpCurrMtf, "DrawShape::DrawShape(): Invalid metafile" );
+ }
+
+ DrawShape::DrawShape( const DrawShape& rSrc,
+ const DocTreeNode& rTreeNode,
+ double nPrio ) :
+ mxShape( rSrc.mxShape ),
+ mxPage( rSrc.mxPage ),
+ maAnimationFrames(), // don't copy animations for subsets,
+ // only the current frame!
+ mnCurrFrame(0),
+ mpCurrMtf( rSrc.mpCurrMtf ),
+ mnCurrMtfLoadFlags( rSrc.mnCurrMtfLoadFlags ),
+ maCurrentShapeUnitBounds(),
+ mnPriority( nPrio ),
+ maBounds( rSrc.maBounds ),
+ mpAttributeLayer(),
+ mpIntrinsicAnimationActivity(),
+ mnAttributeTransformationState(0),
+ mnAttributeClipState(0),
+ mnAttributeAlphaState(0),
+ mnAttributePositionState(0),
+ mnAttributeContentState(0),
+ mnAttributeVisibilityState(0),
+ maViewShapes(),
+ mxComponentContext( rSrc.mxComponentContext ),
+ maHyperlinkIndices(),
+ maHyperlinkRegions(),
+ maSubsetting( rTreeNode, mpCurrMtf ),
+ mnIsAnimatedCount(0),
+ mnAnimationLoopCount(0),
+ mbIsVisible( rSrc.mbIsVisible ),
+ mbForceUpdate( false ),
+ mbAttributeLayerRevoked( false ),
+ mbDrawingLayerAnim( false ),
+ mbContainsPageField( false )
+ {
+ ENSURE_OR_THROW( mxShape.is(), "DrawShape::DrawShape(): Invalid XShape" );
+ ENSURE_OR_THROW( mpCurrMtf, "DrawShape::DrawShape(): Invalid metafile" );
+
+ // xxx todo: currently not implemented for subsetted shapes;
+ // would mean modifying set of hyperlink regions when
+ // subsetting text portions. N.B.: there's already an
+ // issue for this #i72828#
+ }
+
+
+ // Public methods
+
+
+ DrawShapeSharedPtr DrawShape::create(
+ const uno::Reference< drawing::XShape >& xShape,
+ const uno::Reference< drawing::XDrawPage >& xContainingPage,
+ double nPrio,
+ bool bForeignSource,
+ const SlideShowContext& rContext )
+ {
+ DrawShapeSharedPtr pShape( new DrawShape(xShape,
+ xContainingPage,
+ nPrio,
+ bForeignSource,
+ rContext) );
+
+ if( pShape->hasIntrinsicAnimation() )
+ {
+ OSL_ASSERT( pShape->maAnimationFrames.empty() );
+ if( pShape->getNumberOfTreeNodes(
+ DocTreeNode::NodeType::LogicalParagraph) > 0 )
+ {
+ pShape->mpIntrinsicAnimationActivity =
+ createDrawingLayerAnimActivity(
+ rContext,
+ pShape);
+ }
+ }
+
+ if( pShape->hasHyperlinks() )
+ rContext.mpSubsettableShapeManager->addHyperlinkArea( pShape );
+
+ return pShape;
+ }
+
+ DrawShapeSharedPtr DrawShape::create(
+ const uno::Reference< drawing::XShape >& xShape,
+ const uno::Reference< drawing::XDrawPage >& xContainingPage,
+ double nPrio,
+ const Graphic& rGraphic,
+ const SlideShowContext& rContext )
+ {
+ DrawShapeSharedPtr pShape( new DrawShape(xShape,
+ xContainingPage,
+ nPrio,
+ rGraphic,
+ rContext) );
+
+ if( pShape->hasIntrinsicAnimation() )
+ {
+ OSL_ASSERT( !pShape->maAnimationFrames.empty() );
+
+ std::vector<double> aTimeout;
+ std::transform(
+ pShape->maAnimationFrames.begin(),
+ pShape->maAnimationFrames.end(),
+ std::back_insert_iterator< std::vector<double> >( aTimeout ),
+ std::mem_fn(&MtfAnimationFrame::getDuration) );
+
+ WakeupEventSharedPtr pWakeupEvent =
+ std::make_shared<WakeupEvent>( rContext.mrEventQueue.getTimer(),
+ rContext.mrActivitiesQueue );
+
+ ActivitySharedPtr pActivity =
+ createIntrinsicAnimationActivity(
+ rContext,
+ pShape,
+ pWakeupEvent,
+ std::move(aTimeout),
+ pShape->mnAnimationLoopCount);
+
+ pWakeupEvent->setActivity( pActivity );
+ pShape->mpIntrinsicAnimationActivity = pActivity;
+ }
+
+ OSL_ENSURE( !pShape->hasHyperlinks(),
+ "DrawShape::create(): graphic-only shapes must not have hyperlinks!" );
+
+ return pShape;
+ }
+
+ DrawShape::~DrawShape()
+ {
+ try
+ {
+ // dispose intrinsic animation activity, else, it will
+ // linger forever
+ ActivitySharedPtr pActivity( mpIntrinsicAnimationActivity.lock() );
+ if( pActivity )
+ pActivity->dispose();
+ }
+ catch (uno::Exception const &)
+ {
+ DBG_UNHANDLED_EXCEPTION("slideshow");
+ }
+ }
+
+ uno::Reference< drawing::XShape > DrawShape::getXShape() const
+ {
+ return mxShape;
+ }
+
+ void DrawShape::addViewLayer( const ViewLayerSharedPtr& rNewLayer,
+ bool bRedrawLayer )
+ {
+ // already added?
+ if( ::std::any_of( maViewShapes.begin(),
+ maViewShapes.end(),
+ [&rNewLayer]
+ ( const ViewShapeSharedPtr& pShape )
+ { return rNewLayer == pShape->getViewLayer(); } ) )
+ {
+ // yes, nothing to do
+ return;
+ }
+
+ ViewShapeSharedPtr pNewShape = std::make_shared<ViewShape>( rNewLayer );
+
+ maViewShapes.push_back( pNewShape );
+
+ // pass on animation state
+ if( mnIsAnimatedCount )
+ {
+ for( int i=0; i<mnIsAnimatedCount; ++i )
+ pNewShape->enterAnimationMode();
+ }
+
+ // render the Shape on the newly added ViewLayer
+ if( bRedrawLayer )
+ {
+ pNewShape->update( mpCurrMtf,
+ getViewRenderArgs(),
+ UpdateFlags::Force,
+ isVisible() );
+ }
+ }
+
+ bool DrawShape::removeViewLayer( const ViewLayerSharedPtr& rLayer )
+ {
+ const ViewShapeVector::iterator aEnd( maViewShapes.end() );
+
+ OSL_ENSURE( ::std::count_if(maViewShapes.begin(),
+ aEnd,
+ [&rLayer]
+ ( const ViewShapeSharedPtr& pShape )
+ { return rLayer == pShape->getViewLayer(); } ) < 2,
+ "DrawShape::removeViewLayer(): Duplicate ViewLayer entries!" );
+
+ ViewShapeVector::iterator aIter;
+
+ if( (aIter=::std::remove_if( maViewShapes.begin(),
+ aEnd,
+ [&rLayer]
+ ( const ViewShapeSharedPtr& pShape )
+ { return rLayer == pShape->getViewLayer(); } ) ) == aEnd )
+ {
+ // view layer seemingly was not added, failed
+ return false;
+ }
+
+ // actually erase from container
+ maViewShapes.erase( aIter, aEnd );
+
+ return true;
+ }
+
+ void DrawShape::clearAllViewLayers()
+ {
+ maViewShapes.clear();
+ }
+
+ bool DrawShape::update() const
+ {
+ if( mbForceUpdate )
+ {
+ return render();
+ }
+ else
+ {
+ return implRender( getUpdateFlags() );
+ }
+ }
+
+ bool DrawShape::render() const
+ {
+ // force redraw. Have to also pass on the update flags,
+ // because e.g. content update (regeneration of the
+ // metafile renderer) is normally not performed. A simple
+ // UpdateFlags::Force would only paint the metafile in its
+ // old state.
+ return implRender( UpdateFlags::Force | getUpdateFlags() );
+ }
+
+ bool DrawShape::isContentChanged() const
+ {
+ return mbForceUpdate ||
+ getUpdateFlags() != UpdateFlags::NONE;
+ }
+
+
+ ::basegfx::B2DRectangle DrawShape::getBounds() const
+ {
+ // little optimization: for non-modified shapes, we don't
+ // create an ShapeAttributeStack, and therefore also don't
+ // have to check it.
+ return getShapePosSize( maBounds,
+ mpAttributeLayer );
+ }
+
+ ::basegfx::B2DRectangle DrawShape::getDomBounds() const
+ {
+ return maBounds;
+ }
+
+ ::basegfx::B2DRectangle DrawShape::getUpdateArea() const
+ {
+ ::basegfx::B2DRectangle aBounds;
+
+ // an already empty shape bound need no further
+ // treatment. In fact, any changes applied below would
+ // actually remove the special empty state, thus, don't
+ // change!
+ if( !maBounds.isEmpty() )
+ {
+ basegfx::B2DRectangle aUnitBounds(0.0,0.0,1.0,1.0);
+
+ if( !maViewShapes.empty() )
+ aUnitBounds = getActualUnitShapeBounds();
+
+ if( !aUnitBounds.isEmpty() )
+ {
+ if( mpAttributeLayer )
+ {
+ // calc actual shape area (in user coordinate
+ // space) from the transformation as given by the
+ // shape attribute layer
+ aBounds = getShapeUpdateArea( aUnitBounds,
+ getShapeTransformation( getBounds(),
+ mpAttributeLayer ),
+ mpAttributeLayer );
+ }
+ else
+ {
+ // no attribute layer, thus, the true shape bounds
+ // can be directly derived from the XShape bound
+ // attribute
+ aBounds = getShapeUpdateArea( aUnitBounds,
+ maBounds );
+ }
+
+ if( !maViewShapes.empty() )
+ {
+ // determine border needed for antialiasing the shape
+ ::basegfx::B2DSize aAABorder(0.0,0.0);
+
+ // for every view, get AA border and 'expand' aAABorder
+ // appropriately.
+ for( const auto& rViewShape : maViewShapes )
+ {
+ const ::basegfx::B2DSize rShapeBorder( rViewShape->getAntialiasingBorder() );
+
+ aAABorder.setX( ::std::max(
+ rShapeBorder.getX(),
+ aAABorder.getX() ) );
+ aAABorder.setY( ::std::max(
+ rShapeBorder.getY(),
+ aAABorder.getY() ) );
+ }
+
+ // add calculated AA border to aBounds
+ aBounds = ::basegfx::B2DRectangle( aBounds.getMinX() - aAABorder.getX(),
+ aBounds.getMinY() - aAABorder.getY(),
+ aBounds.getMaxX() + aAABorder.getX(),
+ aBounds.getMaxY() + aAABorder.getY() );
+ }
+ }
+ }
+
+ return aBounds;
+ }
+
+ bool DrawShape::isVisible() const
+ {
+ bool bIsVisible( mbIsVisible );
+
+ if( mpAttributeLayer )
+ {
+ // check whether visibility and alpha are not default
+ // (mpAttributeLayer->isVisibilityValid() returns true
+ // then): bVisible becomes true, if shape visibility
+ // is on and alpha is not 0.0 (fully transparent)
+ if( mpAttributeLayer->isVisibilityValid() )
+ bIsVisible = mpAttributeLayer->getVisibility();
+
+ // only touch bIsVisible, if the shape is still
+ // visible - if getVisibility already made us
+ // invisible, no alpha value will make us appear
+ // again.
+ if( bIsVisible && mpAttributeLayer->isAlphaValid() )
+ bIsVisible = !::basegfx::fTools::equalZero( mpAttributeLayer->getAlpha() );
+ }
+
+ return bIsVisible;
+ }
+
+ double DrawShape::getPriority() const
+ {
+ return mnPriority;
+ }
+
+ bool DrawShape::isBackgroundDetached() const
+ {
+ return mnIsAnimatedCount > 0;
+ }
+
+ bool DrawShape::hasIntrinsicAnimation() const
+ {
+ return (!maAnimationFrames.empty() || mbDrawingLayerAnim);
+ }
+
+ void DrawShape::setIntrinsicAnimationFrame( ::std::size_t nCurrFrame )
+ {
+ ENSURE_OR_RETURN_VOID( nCurrFrame < maAnimationFrames.size(),
+ "DrawShape::setIntrinsicAnimationFrame(): frame index out of bounds" );
+
+ if( mnCurrFrame != nCurrFrame )
+ {
+ mnCurrFrame = nCurrFrame;
+ mpCurrMtf = maAnimationFrames[ mnCurrFrame ].mpMtf;
+ mbForceUpdate = true;
+ }
+ }
+
+ // hyperlink support
+ void DrawShape::prepareHyperlinkIndices() const
+ {
+ if ( !maHyperlinkIndices.empty())
+ {
+ maHyperlinkIndices.clear();
+ maHyperlinkRegions.clear();
+ }
+
+ sal_Int32 nIndex = 0;
+ for ( MetaAction * pCurrAct = mpCurrMtf->FirstAction();
+ pCurrAct != nullptr; pCurrAct = mpCurrMtf->NextAction() )
+ {
+ if (pCurrAct->GetType() == MetaActionType::COMMENT) {
+ MetaCommentAction * pAct =
+ static_cast<MetaCommentAction *>(pCurrAct);
+ // skip comment if not a special XTEXT comment
+ if (pAct->GetComment().equalsIgnoreAsciiCase("FIELD_SEQ_BEGIN") &&
+ // e.g. date field doesn't have data!
+ // currently assuming that only url field, this is
+ // somehow fragile! xxx todo if possible
+ pAct->GetData() != nullptr &&
+ pAct->GetDataSize() > 0)
+ {
+ if (!maHyperlinkIndices.empty() &&
+ maHyperlinkIndices.back().second == -1) {
+ SAL_WARN( "slideshow", "### pending FIELD_SEQ_END!" );
+ maHyperlinkIndices.pop_back();
+ maHyperlinkRegions.pop_back();
+ }
+ maHyperlinkIndices.emplace_back( nIndex + 1,
+ -1 /* to be filled below */ );
+ maHyperlinkRegions.emplace_back(
+ basegfx::B2DRectangle(),
+ OUString(
+ reinterpret_cast<sal_Unicode const*>(
+ pAct->GetData()),
+ pAct->GetDataSize() / sizeof(sal_Unicode) )
+ );
+ }
+ else if (pAct->GetComment().equalsIgnoreAsciiCase("FIELD_SEQ_END") &&
+ // pending end is expected:
+ !maHyperlinkIndices.empty() &&
+ maHyperlinkIndices.back().second == -1)
+ {
+ maHyperlinkIndices.back().second = nIndex;
+ }
+ else if (pAct->GetComment().equalsIgnoreAsciiCase("FIELD_SEQ_BEGIN;PageField"))
+ {
+ mbContainsPageField = true;
+ }
+ ++nIndex;
+ }
+ else
+ nIndex += getNextActionOffset(pCurrAct);
+ }
+ if (!maHyperlinkIndices.empty() &&
+ maHyperlinkIndices.back().second == -1) {
+ SAL_WARN( "slideshow", "### pending FIELD_SEQ_END!" );
+ maHyperlinkIndices.pop_back();
+ maHyperlinkRegions.pop_back();
+ }
+ OSL_ASSERT( maHyperlinkIndices.size() == maHyperlinkRegions.size());
+ }
+
+ bool DrawShape::hasHyperlinks() const
+ {
+ return ! maHyperlinkRegions.empty();
+ }
+
+ HyperlinkArea::HyperlinkRegions DrawShape::getHyperlinkRegions() const
+ {
+ OSL_ASSERT( !maViewShapes.empty() );
+
+ if( !isVisible() )
+ return HyperlinkArea::HyperlinkRegions();
+
+ // late init, determine regions:
+ if( !maHyperlinkRegions.empty() &&
+ !maViewShapes.empty() &&
+ // region already inited?
+ maHyperlinkRegions.front().first.getWidth() == 0 &&
+ maHyperlinkRegions.front().first.getHeight() == 0 &&
+ maHyperlinkRegions.size() == maHyperlinkIndices.size() )
+ {
+ // TODO(Q2): Although this _is_ currently
+ // view-agnostic, it might not stay like that.
+ ViewShapeSharedPtr const& pViewShape = maViewShapes.front();
+ cppcanvas::CanvasSharedPtr const pCanvas(
+ pViewShape->getViewLayer()->getCanvas() );
+
+ // reuse Renderer of first view shape:
+ cppcanvas::RendererSharedPtr const pRenderer(
+ pViewShape->getRenderer(
+ pCanvas, mpCurrMtf, mpAttributeLayer ) );
+
+ OSL_ASSERT( pRenderer );
+
+ if (pRenderer)
+ {
+ basegfx::B2DHomMatrix const aOldTransform(
+ pCanvas->getTransformation() );
+ basegfx::B2DHomMatrix aTransform;
+ pCanvas->setTransformation( aTransform /* empty */ );
+
+
+ ::cppcanvas::Canvas* pTmpCanvas = pCanvas.get();
+ comphelper::ScopeGuard const resetOldTransformation(
+ [&aOldTransform, &pTmpCanvas]()
+ { return pTmpCanvas->setTransformation( aOldTransform ); } );
+
+ aTransform.scale( maBounds.getWidth(),
+ maBounds.getHeight() );
+ pRenderer->setTransformation( aTransform );
+ pRenderer->setClip();
+
+ for( std::size_t pos = maHyperlinkRegions.size(); pos--; )
+ {
+ // get region:
+ HyperlinkIndexPair const& rIndices = maHyperlinkIndices[pos];
+ basegfx::B2DRectangle const region(
+ pRenderer->getSubsetArea( rIndices.first,
+ rIndices.second ));
+ maHyperlinkRegions[pos].first = region;
+ }
+ }
+ }
+
+ // shift shape-relative hyperlink regions to
+ // slide-absolute position
+
+ HyperlinkRegions aTranslatedRegions;
+
+ // increase capacity to same size as the container for
+ // shape-relative hyperlink regions to avoid reallocation
+ aTranslatedRegions.reserve( maHyperlinkRegions.size() );
+ const basegfx::B2DPoint& rOffset(getBounds().getMinimum());
+ for( const auto& cp : maHyperlinkRegions )
+ {
+ basegfx::B2DRange const& relRegion( cp.first );
+ aTranslatedRegions.emplace_back(
+ basegfx::B2DRange(
+ relRegion.getMinimum() + rOffset,
+ relRegion.getMaximum() + rOffset),
+ cp.second );
+ }
+
+ return aTranslatedRegions;
+ }
+
+ double DrawShape::getHyperlinkPriority() const
+ {
+ return getPriority();
+ }
+
+
+ // AnimatableShape methods
+
+
+ void DrawShape::enterAnimationMode()
+ {
+ OSL_ENSURE( !maViewShapes.empty(),
+ "DrawShape::enterAnimationMode(): called on DrawShape without views" );
+
+ if( mnIsAnimatedCount == 0 )
+ {
+ // notify all ViewShapes, by calling their enterAnimationMode method.
+ // We're now entering animation mode
+ for( const auto& rViewShape : maViewShapes )
+ rViewShape->enterAnimationMode();
+ }
+
+ ++mnIsAnimatedCount;
+ }
+
+ void DrawShape::leaveAnimationMode()
+ {
+ OSL_ENSURE( !maViewShapes.empty(),
+ "DrawShape::leaveAnimationMode(): called on DrawShape without views" );
+
+ --mnIsAnimatedCount;
+
+ if( mnIsAnimatedCount == 0 )
+ {
+ // notify all ViewShapes, by calling their leaveAnimationMode method.
+ // we're now leaving animation mode
+ for( const auto& rViewShape : maViewShapes )
+ rViewShape->leaveAnimationMode();
+ }
+ }
+
+
+ // AttributableShape methods
+
+
+ ShapeAttributeLayerSharedPtr DrawShape::createAttributeLayer()
+ {
+ // create new layer, with last as its new child
+ mpAttributeLayer = std::make_shared<ShapeAttributeLayer>( mpAttributeLayer );
+
+ // Update the local state ids to reflect those of the new layer.
+ updateStateIds();
+
+ return mpAttributeLayer;
+ }
+
+ bool DrawShape::revokeAttributeLayer( const ShapeAttributeLayerSharedPtr& rLayer )
+ {
+ if( !mpAttributeLayer )
+ return false; // no layers
+
+ if( mpAttributeLayer == rLayer )
+ {
+ // it's the toplevel layer
+ mpAttributeLayer = mpAttributeLayer->getChildLayer();
+
+ // force content redraw, all state variables have
+ // possibly changed
+ mbAttributeLayerRevoked = true;
+
+ return true;
+ }
+ else
+ {
+ // pass on to the layer, to try its children
+ return mpAttributeLayer->revokeChildLayer( rLayer );
+ }
+ }
+
+ ShapeAttributeLayerSharedPtr DrawShape::getTopmostAttributeLayer() const
+ {
+ return mpAttributeLayer;
+ }
+
+ void DrawShape::setVisibility( bool bVisible )
+ {
+ if( mbIsVisible != bVisible )
+ {
+ mbIsVisible = bVisible;
+ mbForceUpdate = true;
+ }
+ }
+
+ const DocTreeNodeSupplier& DrawShape::getTreeNodeSupplier() const
+ {
+ return *this;
+ }
+
+ DocTreeNodeSupplier& DrawShape::getTreeNodeSupplier()
+ {
+ return *this;
+ }
+
+ DocTreeNode DrawShape::getSubsetNode() const
+ {
+ // forward to delegate
+ return maSubsetting.getSubsetNode();
+ }
+
+ AttributableShapeSharedPtr DrawShape::getSubset( const DocTreeNode& rTreeNode ) const
+ {
+ // forward to delegate
+ return maSubsetting.getSubsetShape( rTreeNode );
+ }
+
+ bool DrawShape::createSubset( AttributableShapeSharedPtr& o_rSubset,
+ const DocTreeNode& rTreeNode )
+ {
+ // subset shape already created for this DocTreeNode?
+ AttributableShapeSharedPtr pSubset( maSubsetting.getSubsetShape( rTreeNode ) );
+
+ // when true, this method has created a new subset
+ // DrawShape
+ bool bNewlyCreated( false );
+
+ if( pSubset )
+ {
+ o_rSubset = pSubset;
+
+ // reusing existing subset
+ }
+ else
+ {
+ // not yet created, init entry
+ o_rSubset.reset( new DrawShape( *this,
+ rTreeNode,
+ // TODO(Q3): That's a
+ // hack. We assume
+ // that start and end
+ // index will always
+ // be less than 65535
+ mnPriority +
+ rTreeNode.getStartIndex()/double(SAL_MAX_INT16) ));
+
+ bNewlyCreated = true; // subset newly created
+ }
+
+ // always register shape at DrawShapeSubsetting, to keep
+ // refcount up-to-date
+ maSubsetting.addSubsetShape( o_rSubset );
+
+ // flush bounds cache
+ maCurrentShapeUnitBounds.reset();
+
+ return bNewlyCreated;
+ }
+
+ bool DrawShape::revokeSubset( const AttributableShapeSharedPtr& rShape )
+ {
+ // flush bounds cache
+ maCurrentShapeUnitBounds.reset();
+
+ // forward to delegate
+ if( maSubsetting.revokeSubsetShape( rShape ) )
+ {
+ // force redraw, our content has possibly changed (as
+ // one of the subsets now display within our shape
+ // again).
+ mbForceUpdate = true;
+
+ // #i47428# TEMP FIX: synchronize visibility of subset
+ // with parent.
+
+ // TODO(F3): Remove here, and implement
+ // TEXT_ONLY/BACKGROUND_ONLY with the proverbial
+ // additional level of indirection: create a
+ // persistent subset, containing all text/only the
+ // background respectively. From _that_ object,
+ // generate the temporary character subset shapes.
+ const ShapeAttributeLayerSharedPtr& rAttrLayer(
+ rShape->getTopmostAttributeLayer() );
+ if( rAttrLayer &&
+ rAttrLayer->isVisibilityValid() &&
+ rAttrLayer->getVisibility() != isVisible() )
+ {
+ const bool bVisibility( rAttrLayer->getVisibility() );
+
+ // visibilities differ - adjust ours, then
+ if( mpAttributeLayer )
+ mpAttributeLayer->setVisibility( bVisibility );
+ else
+ mbIsVisible = bVisibility;
+ }
+
+ // END TEMP FIX
+
+ return true;
+ }
+
+ return false;
+ }
+
+ sal_Int32 DrawShape::getNumberOfTreeNodes( DocTreeNode::NodeType eNodeType ) const // throw ShapeLoadFailedException
+ {
+ return maSubsetting.getNumberOfTreeNodes( eNodeType );
+ }
+
+ DocTreeNode DrawShape::getTreeNode( sal_Int32 nNodeIndex,
+ DocTreeNode::NodeType eNodeType ) const // throw ShapeLoadFailedException
+ {
+ if ( hasHyperlinks())
+ {
+ prepareHyperlinkIndices();
+ }
+
+ return maSubsetting.getTreeNode( nNodeIndex, eNodeType );
+ }
+
+ sal_Int32 DrawShape::getNumberOfSubsetTreeNodes ( const DocTreeNode& rParentNode,
+ DocTreeNode::NodeType eNodeType ) const // throw ShapeLoadFailedException
+ {
+ return maSubsetting.getNumberOfSubsetTreeNodes( rParentNode, eNodeType );
+ }
+
+ DocTreeNode DrawShape::getSubsetTreeNode( const DocTreeNode& rParentNode,
+ sal_Int32 nNodeIndex,
+ DocTreeNode::NodeType eNodeType ) const // throw ShapeLoadFailedException
+ {
+ return maSubsetting.getSubsetTreeNode( rParentNode, nNodeIndex, eNodeType );
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/slideshow/source/engine/shapes/drawshape.hxx b/slideshow/source/engine/shapes/drawshape.hxx
new file mode 100644
index 000000000..8636a7acd
--- /dev/null
+++ b/slideshow/source/engine/shapes/drawshape.hxx
@@ -0,0 +1,360 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#ifndef INCLUDED_SLIDESHOW_SOURCE_ENGINE_SHAPES_DRAWSHAPE_HXX
+#define INCLUDED_SLIDESHOW_SOURCE_ENGINE_SHAPES_DRAWSHAPE_HXX
+
+#include <osl/diagnose.hxx>
+#include <com/sun/star/drawing/XShape.hpp>
+
+#include <attributableshape.hxx>
+#include <doctreenodesupplier.hxx>
+#include "drawshapesubsetting.hxx"
+#include "gdimtftools.hxx"
+#include "viewshape.hxx"
+#include <hyperlinkarea.hxx>
+
+#include <optional>
+#include <vector>
+
+class Graphic;
+
+namespace slideshow::internal
+ {
+ class Activity;
+ struct SlideShowContext;
+ class DrawShapeSubsetting;
+ class DrawShape;
+ typedef ::std::shared_ptr< DrawShape > DrawShapeSharedPtr;
+
+ /** This class is the representation of a draw document's
+ XShape, and implements the Shape, AnimatableShape, and
+ AttributableShape interfaces.
+
+ @attention this class is to be treated 'final', i.e. one
+ should not derive from it.
+ */
+ class DrawShape : public AttributableShape,
+ public DocTreeNodeSupplier,
+ public HyperlinkArea,
+ public ::osl::DebugBase<DrawShape>
+ {
+ public:
+ /** Create a shape for the given XShape
+
+ @param xShape
+ The XShape to represent.
+
+ @param xContainingPage
+ The page that contains this shape. Needed for proper
+ import (currently, the UnoGraphicExporter needs this
+ information).
+
+ @param nPrio
+ Externally-determined shape priority (used e.g. for
+ paint ordering). This number _must be_ unique!
+
+ @param bForeignSource
+ When true, the source of the shape metafile might be a
+ foreign application. The metafile is checked against
+ unsupported content, and, if necessary, returned as a
+ pre-rendered bitmap.
+ */
+ static DrawShapeSharedPtr create(
+ const css::uno::Reference< css::drawing::XShape >& xShape,
+ const css::uno::Reference< css::drawing::XDrawPage >& xContainingPage,
+ double nPrio,
+ bool bForeignSource,
+ const SlideShowContext& rContext ); // throw ShapeLoadFailedException;
+
+ /** Create a shape for the given XShape and graphic content
+
+ @param xShape
+ The XShape to represent.
+
+ @param xContainingPage
+ The page that contains this shape. Needed for proper
+ import (currently, the UnoGraphicExporter needs this
+ information).
+
+ @param nPrio
+ Externally-determined shape priority (used e.g. for
+ paint ordering). This number _must be_ unique!
+
+ @param rGraphic
+ Graphic to display in the shape's bound rect. If this
+ Graphic contains animatable content, the created
+ DrawShape will register itself for intrinsic animation
+ events.
+ */
+ static DrawShapeSharedPtr create(
+ const css::uno::Reference< css::drawing::XShape >& xShape,
+ const css::uno::Reference< css::drawing::XDrawPage >& xContainingPage,
+ double nPrio,
+ const Graphic& rGraphic,
+ const SlideShowContext& rContext ); // throw ShapeLoadFailedException;
+
+ virtual css::uno::Reference< css::drawing::XShape > getXShape() const override;
+
+ virtual ~DrawShape() override;
+
+
+ // View layer methods
+
+
+ virtual void addViewLayer( const ViewLayerSharedPtr& rNewLayer,
+ bool bRedrawLayer ) override;
+ virtual bool removeViewLayer( const ViewLayerSharedPtr& rNewLayer ) override;
+ virtual void clearAllViewLayers() override;
+
+ // attribute methods
+
+
+ virtual ShapeAttributeLayerSharedPtr createAttributeLayer() override;
+ virtual bool revokeAttributeLayer( const ShapeAttributeLayerSharedPtr& rLayer ) override;
+ virtual ShapeAttributeLayerSharedPtr getTopmostAttributeLayer() const override;
+ virtual void setVisibility( bool bVisible ) override;
+ virtual ::basegfx::B2DRectangle getBounds() const override;
+ virtual ::basegfx::B2DRectangle getDomBounds() const override;
+ virtual ::basegfx::B2DRectangle getUpdateArea() const override;
+ virtual bool isVisible() const override;
+ virtual double getPriority() const override;
+
+
+ // animation methods
+
+
+ virtual void enterAnimationMode() override;
+ virtual void leaveAnimationMode() override;
+ virtual bool isBackgroundDetached() const override;
+
+ // render methods
+
+
+ virtual bool update() const override;
+ virtual bool render() const override;
+ virtual bool isContentChanged() const override;
+
+ // Sub item specialities
+
+
+ virtual const DocTreeNodeSupplier& getTreeNodeSupplier() const override;
+ virtual DocTreeNodeSupplier& getTreeNodeSupplier() override;
+
+ virtual DocTreeNode getSubsetNode() const override;
+ virtual AttributableShapeSharedPtr getSubset( const DocTreeNode& rTreeNode ) const override;
+ virtual bool createSubset( AttributableShapeSharedPtr& o_rSubset,
+ const DocTreeNode& rTreeNode ) override;
+ virtual bool revokeSubset( const AttributableShapeSharedPtr& rShape ) override;
+
+
+ // DocTreeNodeSupplier methods
+
+
+ virtual sal_Int32 getNumberOfTreeNodes ( DocTreeNode::NodeType eNodeType ) const override; // throw ShapeLoadFailedException;
+ virtual DocTreeNode getTreeNode ( sal_Int32 nNodeIndex,
+ DocTreeNode::NodeType eNodeType ) const override; // throw ShapeLoadFailedException;
+ virtual sal_Int32 getNumberOfSubsetTreeNodes ( const DocTreeNode& rParentNode,
+ DocTreeNode::NodeType eNodeType ) const override; // throw ShapeLoadFailedException;
+ virtual DocTreeNode getSubsetTreeNode ( const DocTreeNode& rParentNode,
+ sal_Int32 nNodeIndex,
+ DocTreeNode::NodeType eNodeType ) const override; // throw ShapeLoadFailedException;
+
+ // HyperlinkArea methods
+
+
+ virtual HyperlinkRegions getHyperlinkRegions() const override;
+ virtual double getHyperlinkPriority() const override;
+
+
+ // intrinsic animation methods
+
+
+ /** Display next frame of an intrinsic animation.
+
+ Used by IntrinsicAnimationActivity, to show the next
+ animation frame.
+ */
+ void setIntrinsicAnimationFrame( ::std::size_t nCurrFrame );
+
+ /** forces the drawshape to load and return a specially
+ crafted metafile, usable to display drawing layer text
+ animations.
+ */
+ GDIMetaFileSharedPtr const & forceScrollTextMetaFile();
+
+ private:
+ /** Create a shape for the given XShape
+
+ @param xShape
+ The XShape to represent.
+
+ @param xContainingPage
+ The page that contains this shape. Needed for proper
+ import (currently, the UnoGraphicExporter needs this
+ information).
+
+ @param nPrio
+ Externally-determined shape priority (used e.g. for
+ paint ordering). This number _must be_ unique!
+
+ @param bForeignSource
+ When true, the source of the shape metafile might be a
+ foreign application. The metafile is checked against
+ unsupported content, and, if necessary, returned as a
+ pre-rendered bitmap.
+ */
+ DrawShape( const css::uno::Reference<
+ css::drawing::XShape >& xShape,
+ const css::uno::Reference<
+ css::drawing::XDrawPage >& xContainingPage,
+ double nPrio,
+ bool bForeignSource,
+ const SlideShowContext& rContext ); // throw ShapeLoadFailedException;
+
+ /** Create a shape for the given XShape and graphic content
+
+ @param xShape
+ The XShape to represent.
+
+ @param xContainingPage
+ The page that contains this shape. Needed for proper
+ import (currently, the UnoGraphicExporter needs this
+ information).
+
+ @param nPrio
+ Externally-determined shape priority (used e.g. for
+ paint ordering). This number _must be_ unique!
+
+ @param rGraphic
+ Graphic to display in the shape's bound rect. If this
+ Graphic contains animatable content, the created
+ DrawShape will register itself for intrinsic animation
+ events.
+ */
+ DrawShape( const css::uno::Reference< css::drawing::XShape >& xShape,
+ const css::uno::Reference< css::drawing::XDrawPage >& xContainingPage,
+ double nPrio,
+ const Graphic& rGraphic,
+ const SlideShowContext& rContext ); // throw ShapeLoadFailedException;
+
+ /** Private copy constructor
+
+ Used to create subsetted shapes
+ */
+ DrawShape( const DrawShape&, const DocTreeNode& rTreeNode, double nPrio );
+
+ UpdateFlags getUpdateFlags() const;
+ bool implRender( UpdateFlags nUpdateFlags ) const;
+ void updateStateIds() const;
+
+ ViewShape::RenderArgs getViewRenderArgs() const;
+ ::basegfx::B2DRectangle getActualUnitShapeBounds() const;
+
+ bool hasIntrinsicAnimation() const;
+ bool hasHyperlinks() const;
+ void prepareHyperlinkIndices() const;
+
+ /// The associated XShape
+ css::uno::Reference< css::drawing::XShape > mxShape;
+ css::uno::Reference< css::drawing::XDrawPage > mxPage;
+
+ /** A vector of metafiles actually representing the Shape.
+
+ If this shape is not animated, only a single entry is
+ available.
+ */
+ mutable VectorOfMtfAnimationFrames maAnimationFrames;
+ ::std::size_t mnCurrFrame;
+
+ /// Metafile of currently active frame (static for shapes w/o intrinsic animation)
+ mutable GDIMetaFileSharedPtr mpCurrMtf;
+
+ /// loadflags of current meta file
+ mutable int mnCurrMtfLoadFlags;
+
+ /// Contains the current shape bounds, in unit rect space
+ mutable ::std::optional<basegfx::B2DRectangle> maCurrentShapeUnitBounds;
+
+ // The attributes of this Shape
+ const double mnPriority;
+ ::basegfx::B2DRectangle maBounds; // always needed for rendering.
+ // for subset shapes, this member
+ // might change when views are
+ // added, as minimal bounds are
+ // calculated
+
+ // Pointer to modifiable shape attributes
+ ShapeAttributeLayerSharedPtr mpAttributeLayer; // only created lazily
+
+ // held here, to signal our destruction
+ std::weak_ptr<Activity> mpIntrinsicAnimationActivity;
+
+ // The attribute states, to detect attribute changes,
+ // without buffering and querying each single attribute
+ mutable State::StateId mnAttributeTransformationState;
+ mutable State::StateId mnAttributeClipState;
+ mutable State::StateId mnAttributeAlphaState;
+ mutable State::StateId mnAttributePositionState;
+ mutable State::StateId mnAttributeContentState;
+ mutable State::StateId mnAttributeVisibilityState;
+
+ /// the list of active view shapes (one for each registered view layer)
+ typedef ::std::vector< ViewShapeSharedPtr > ViewShapeVector;
+ ViewShapeVector maViewShapes;
+
+ css::uno::Reference< css::uno::XComponentContext> mxComponentContext;
+
+ /// hyperlink support
+ typedef ::std::pair<sal_Int32 /* mtf start */,
+ sal_Int32 /* mtf end */> HyperlinkIndexPair;
+ typedef ::std::vector<HyperlinkIndexPair> HyperlinkIndexPairVector;
+ mutable HyperlinkIndexPairVector maHyperlinkIndices;
+ mutable HyperlinkRegions maHyperlinkRegions;
+
+ /// Delegated subset handling
+ mutable DrawShapeSubsetting maSubsetting;
+
+ /// Whether this shape is currently in animation mode (value != 0)
+ int mnIsAnimatedCount;
+
+ /// Number of times the bitmap animation shall loop
+ sal_uInt32 mnAnimationLoopCount;
+
+ /// Whether shape is visible (without attribute layers)
+ bool mbIsVisible;
+
+ /// Whether redraw is necessary, regardless of state ids
+ mutable bool mbForceUpdate;
+
+ /// Whether attribute layer was revoked (making a redraw necessary)
+ mutable bool mbAttributeLayerRevoked;
+
+ /// whether a drawing layer animation has to be performed
+ bool mbDrawingLayerAnim;
+
+ /// tdf#150402 wether mpCurrMtf contains any Text with a PageField ("FIELD_SEQ_BEGIN;PageField")
+ mutable bool mbContainsPageField;
+ };
+
+}
+
+#endif // INCLUDED_SLIDESHOW_SOURCE_ENGINE_SHAPES_DRAWSHAPE_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/slideshow/source/engine/shapes/drawshapesubsetting.cxx b/slideshow/source/engine/shapes/drawshapesubsetting.cxx
new file mode 100644
index 000000000..fac2e2b57
--- /dev/null
+++ b/slideshow/source/engine/shapes/drawshapesubsetting.cxx
@@ -0,0 +1,808 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+
+#include <o3tl/safeint.hxx>
+#include <tools/diagnose_ex.h>
+
+#include <sal/log.hxx>
+#include <utility>
+#include <vcl/metaact.hxx>
+#include <vcl/gdimtf.hxx>
+
+#include "drawshapesubsetting.hxx"
+#include "gdimtftools.hxx"
+
+#include <algorithm>
+
+using namespace ::com::sun::star;
+
+
+namespace slideshow::internal
+{
+
+
+ // Private methods
+
+
+ void DrawShapeSubsetting::ensureInitializedNodeTree() const
+ {
+ ENSURE_OR_THROW( mpMtf,
+ "DrawShapeSubsetting::ensureInitializedNodeTree(): Invalid mtf" );
+
+ if( mbNodeTreeInitialized )
+ return; // done, already initialized.
+
+ // init doctree vector
+ maActionClassVector.clear();
+ maActionClassVector.reserve( mpMtf->GetActionSize() );
+
+ // search metafile for text output
+ MetaAction* pCurrAct;
+
+ sal_Int32 nActionIndex(0);
+ sal_Int32 nLastTextActionIndex(0);
+ for( pCurrAct = mpMtf->FirstAction(); pCurrAct; pCurrAct = mpMtf->NextAction() )
+ {
+ // check for one of our special text doctree comments
+ switch( pCurrAct->GetType() )
+ {
+ case MetaActionType::COMMENT:
+ {
+ MetaCommentAction* pAct = static_cast<MetaCommentAction*>(pCurrAct);
+
+ // skip comment if not a special XTEXT... comment
+ if( pAct->GetComment().matchIgnoreAsciiCase( "XTEXT" ) )
+ {
+ // fill classification vector with NOOPs,
+ // then insert corresponding classes at
+ // the given index
+ maActionClassVector.resize( nActionIndex+1, CLASS_NOOP );
+
+ if( pAct->GetComment().equalsIgnoreAsciiCase("XTEXT_EOC") )
+ {
+ // special, because can happen
+ // in-between of portions - set
+ // character-end classificator at
+ // given index (relative to last text
+ // action).
+ const sal_Int32 nIndex( nLastTextActionIndex + pAct->GetValue() );
+
+ ENSURE_OR_THROW( o3tl::make_unsigned(nIndex) < maActionClassVector.size(),
+ "DrawShapeSubsetting::ensureInitializedNodeTree(): sentence index out of range" );
+
+ maActionClassVector[ nIndex ] = CLASS_CHARACTER_CELL_END;
+ }
+ else if( pAct->GetComment().equalsIgnoreAsciiCase("XTEXT_EOW") )
+ {
+ // special, because can happen
+ // in-between of portions - set
+ // word-end classificator at given
+ // index (relative to last text
+ // action).
+ const sal_Int32 nIndex( nLastTextActionIndex + pAct->GetValue() );
+
+ ENSURE_OR_THROW( o3tl::make_unsigned(nIndex) < maActionClassVector.size(),
+ "DrawShapeSubsetting::ensureInitializedNodeTree(): sentence index out of range" );
+
+ maActionClassVector[ nIndex ] = CLASS_WORD_END;
+ }
+ else if( pAct->GetComment().equalsIgnoreAsciiCase("XTEXT_EOS") )
+ {
+ // special, because can happen
+ // in-between of portions - set
+ // sentence-end classificator at given
+ // index (relative to last text
+ // action).
+ const sal_Int32 nIndex( nLastTextActionIndex + pAct->GetValue() );
+
+ ENSURE_OR_THROW( o3tl::make_unsigned(nIndex) < maActionClassVector.size(),
+ "DrawShapeSubsetting::ensureInitializedNodeTree(): sentence index out of range" );
+
+ maActionClassVector[ nIndex ] = CLASS_SENTENCE_END;
+ }
+ else if( pAct->GetComment().equalsIgnoreAsciiCase("XTEXT_EOL") )
+ {
+ maActionClassVector[ nActionIndex ] = CLASS_LINE_END;
+ }
+ else if( pAct->GetComment().equalsIgnoreAsciiCase("XTEXT_EOP") )
+ {
+ maActionClassVector[ nActionIndex ] = CLASS_PARAGRAPH_END;
+ }
+ else if( pAct->GetComment().equalsIgnoreAsciiCase("XTEXT_PAINTSHAPE_END") )
+ {
+ maActionClassVector[ nActionIndex ] = CLASS_SHAPE_END;
+ }
+ else if( pAct->GetComment().equalsIgnoreAsciiCase("XTEXT_PAINTSHAPE_BEGIN") )
+ {
+ maActionClassVector[ nActionIndex ] = CLASS_SHAPE_START;
+ }
+ }
+ SAL_INFO(
+ "slideshow",
+ "Shape text structure: " << pAct->GetComment()
+ << " at action #" << nActionIndex);
+ ++nActionIndex;
+ break;
+ }
+ case MetaActionType::TEXT:
+ case MetaActionType::TEXTARRAY:
+ case MetaActionType::STRETCHTEXT:
+ nLastTextActionIndex = nActionIndex;
+ SAL_INFO("slideshow.verbose", "Shape text \"" <<
+ (static_cast<MetaTextAction*>(pCurrAct))->GetText() <<
+ "\" at action #" << nActionIndex );
+ [[fallthrough]];
+ default:
+ // comment action and all actions not
+ // explicitly handled here:
+ nActionIndex += getNextActionOffset(pCurrAct);
+ break;
+ }
+ }
+
+ mbNodeTreeInitialized = true;
+ }
+
+ void DrawShapeSubsetting::excludeSubset(sal_Int32 nExcludedStart, sal_Int32 nExcludedEnd)
+ {
+ // If current subsets are empty, fill it with initial range
+ initCurrentSubsets();
+ if (maCurrentSubsets.empty())
+ {
+ // Non-subsetting mode (not a subset of anything; child subsets subtract content)
+ maCurrentSubsets.emplace_back(0, maActionClassVector.size());
+ }
+
+ slideshow::internal::VectorOfDocTreeNodes aNodesToAppend;
+ for (auto i = maCurrentSubsets.begin(); i != maCurrentSubsets.end();)
+ {
+ if (i->getStartIndex() < nExcludedStart)
+ {
+ if (i->getEndIndex() > nExcludedStart)
+ {
+ // Some overlap -> append new node (if required), and correct this node's end
+ if (i->getEndIndex() > nExcludedEnd)
+ {
+ aNodesToAppend.emplace_back(nExcludedEnd, i->getEndIndex());
+ }
+ i->setEndIndex(nExcludedStart);
+ }
+ ++i;
+ }
+ else if (i->getStartIndex() < nExcludedEnd)
+ {
+ if (i->getEndIndex() > nExcludedEnd)
+ {
+ // Partial overlap; change the node's start
+ i->setStartIndex(nExcludedEnd);
+ ++i;
+ }
+ else
+ {
+ // Node is fully inside the removed range: erase it
+ i = maCurrentSubsets.erase(i);
+ }
+ }
+ else
+ {
+ // Node is fully outside (after) excluded range
+ ++i;
+ }
+ }
+
+ maCurrentSubsets.insert(maCurrentSubsets.end(), aNodesToAppend.begin(),
+ aNodesToAppend.end());
+ // Excluding subsets must not leave an absolutely empty maCurrentSubsets, because it
+ // would mean "non-subsetting" mode unconditionally, with whole object added to subsets.
+ // So to indicate a subset with all parts excluded, add two empty subsets (starting and
+ // ending).
+ if (!maCurrentSubsets.empty())
+ return;
+
+ if (maSubset.isEmpty())
+ {
+ maCurrentSubsets.emplace_back(0, 0);
+ maCurrentSubsets.emplace_back(maActionClassVector.size(),
+ maActionClassVector.size());
+ }
+ else
+ {
+ maCurrentSubsets.emplace_back(maSubset.getStartIndex(),
+ maSubset.getStartIndex());
+ maCurrentSubsets.emplace_back(maSubset.getEndIndex(), maSubset.getEndIndex());
+ }
+ }
+
+ void DrawShapeSubsetting::updateSubsets()
+ {
+ maCurrentSubsets.clear();
+ initCurrentSubsets();
+
+ for (const auto& rSubsetShape : maSubsetShapes)
+ {
+ excludeSubset(rSubsetShape.mnStartActionIndex, rSubsetShape.mnEndActionIndex);
+ }
+ }
+
+
+ // Public methods
+
+
+ DrawShapeSubsetting::DrawShapeSubsetting() :
+ maActionClassVector(),
+ mpMtf(),
+ maSubset(),
+ maSubsetShapes(),
+ maCurrentSubsets(),
+ mbNodeTreeInitialized( false )
+ {
+ }
+
+ DrawShapeSubsetting::DrawShapeSubsetting( const DocTreeNode& rShapeSubset,
+ GDIMetaFileSharedPtr rMtf ) :
+ maActionClassVector(),
+ mpMtf(std::move( rMtf )),
+ maSubset( rShapeSubset ),
+ maSubsetShapes(),
+ maCurrentSubsets(),
+ mbNodeTreeInitialized( false )
+ {
+ ENSURE_OR_THROW( mpMtf,
+ "DrawShapeSubsetting::DrawShapeSubsetting(): Invalid metafile" );
+
+ initCurrentSubsets();
+ }
+
+ void DrawShapeSubsetting::reset()
+ {
+ maActionClassVector.clear();
+ mpMtf.reset();
+ maSubset.reset();
+ maSubsetShapes.clear();
+ maCurrentSubsets.clear();
+ mbNodeTreeInitialized = false;
+ }
+
+ void DrawShapeSubsetting::reset( const ::std::shared_ptr< GDIMetaFile >& rMtf )
+ {
+ reset();
+ mpMtf = rMtf;
+
+ initCurrentSubsets();
+ }
+
+ void DrawShapeSubsetting::initCurrentSubsets()
+ {
+ // only add subset to vector, if vector is empty, and subset is not empty - that's
+ // because the vector's content is later literally used
+ // for e.g. painting.
+ if (maCurrentSubsets.empty() && !maSubset.isEmpty())
+ maCurrentSubsets.push_back( maSubset );
+ }
+
+ const DocTreeNode& DrawShapeSubsetting::getSubsetNode() const
+ {
+ return maSubset;
+ }
+
+ AttributableShapeSharedPtr DrawShapeSubsetting::getSubsetShape( const DocTreeNode& rTreeNode ) const
+ {
+ SAL_INFO( "slideshow", "::presentation::internal::DrawShapeSubsetting::getSubsetShape()" );
+
+ // subset shape already created for this DocTreeNode?
+ SubsetEntry aEntry;
+
+ aEntry.mnStartActionIndex = rTreeNode.getStartIndex();
+ aEntry.mnEndActionIndex = rTreeNode.getEndIndex();
+
+ ShapeSet::const_iterator aIter;
+ if( (aIter=maSubsetShapes.find( aEntry )) != maSubsetShapes.end() )
+ {
+ // already created, return found entry
+ return aIter->mpShape;
+ }
+
+ return AttributableShapeSharedPtr();
+ }
+
+ void DrawShapeSubsetting::addSubsetShape( const AttributableShapeSharedPtr& rShape )
+ {
+ SAL_INFO( "slideshow", "::presentation::internal::DrawShapeSubsetting::addSubsetShape()" );
+
+ // subset shape already created for this DocTreeNode?
+ SubsetEntry aEntry;
+ const DocTreeNode& rEffectiveSubset( rShape->getSubsetNode() );
+
+ aEntry.mnStartActionIndex = rEffectiveSubset.getStartIndex();
+ aEntry.mnEndActionIndex = rEffectiveSubset.getEndIndex();
+
+ ShapeSet::const_iterator aIter;
+ if( (aIter=maSubsetShapes.find( aEntry )) != maSubsetShapes.end() )
+ {
+ // already created, increment use count and return
+
+ // safe cast, since set order does not depend on
+ // mnSubsetQueriedCount
+ const_cast<SubsetEntry&>(*aIter).mnSubsetQueriedCount++;
+ }
+ else
+ {
+ // not yet created, init entry
+ aEntry.mnSubsetQueriedCount = 1;
+ aEntry.mpShape = rShape;
+
+ maSubsetShapes.insert( aEntry );
+
+ excludeSubset(aEntry.mnStartActionIndex, aEntry.mnEndActionIndex);
+ }
+ }
+
+ bool DrawShapeSubsetting::revokeSubsetShape( const AttributableShapeSharedPtr& rShape )
+ {
+ SAL_INFO( "slideshow", "::presentation::internal::DrawShapeSubsetting::revokeSubsetShape()" );
+
+ // lookup subset shape
+ SubsetEntry aEntry;
+ const DocTreeNode& rEffectiveSubset( rShape->getSubsetNode() );
+
+ aEntry.mnStartActionIndex = rEffectiveSubset.getStartIndex();
+ aEntry.mnEndActionIndex = rEffectiveSubset.getEndIndex();
+
+ ShapeSet::iterator aIter;
+ if( (aIter=maSubsetShapes.find( aEntry )) == maSubsetShapes.end() )
+ return false; // not found, subset was never queried
+
+ // last client of the subset revoking?
+ if( aIter->mnSubsetQueriedCount > 1 )
+ {
+ // no, still clients out there. Just decrement use count
+ // safe cast, since order does not depend on mnSubsetQueriedCount
+ const_cast<SubsetEntry&>(*aIter).mnSubsetQueriedCount--;
+
+ SAL_INFO(
+ "slideshow",
+ "Subset summary: shape " << this << ", "
+ << maSubsetShapes.size()
+ << " open subsets, revoked subset has refcount "
+ << aIter->mnSubsetQueriedCount);
+
+ return false; // not the last client
+ }
+
+ SAL_INFO(
+ "slideshow",
+ "Subset summary: shape " << this << ", "
+ << maSubsetShapes.size()
+ << " open subsets, cleared subset has range ["
+ << aEntry.mnStartActionIndex << ","
+ << aEntry.mnEndActionIndex << "]");
+
+ // yes, remove from set
+ maSubsetShapes.erase( aIter );
+
+
+ // update currently active subset for _our_ shape (the
+ // part of this shape that is visible, i.e. not displayed
+ // in subset shapes)
+
+ // TODO(P2): This is quite expensive, when
+ // after every subset effect end, we have to scan
+ // the whole shape set
+
+ updateSubsets();
+
+ return true;
+ }
+
+ namespace
+ {
+ /** Iterate over all action classification entries in the
+ given range, pass each element range found to the
+ given functor.
+
+ This method extracts, for each of the different action
+ classifications, the count and the ranges for each of
+ them, and calls the provided functor with that
+ information.
+
+ @tpl FunctorT
+ This is the functor's operator() calling signature,
+ with eCurrElemClassification denoting the current
+ classification type the functor is called for,
+ nCurrElemCount the running total of elements visited
+ for the given class (starting from 0), and
+ rCurrElemBegin/rCurrElemEnd the range of the current
+ element (i.e. the iterators from the start to the end
+ of this element).
+ <pre>
+ bool operator()( IndexClassificator eCurrElemClassification
+ sal_Int32 nCurrElemCount,
+ const IndexClassificatorVector::const_iterator& rCurrElemBegin,
+ const IndexClassificatorVector::const_iterator& rCurrElemEnd );
+ </pre>
+ If the functor returns false, iteration over the
+ shapes is immediately stopped.
+
+ @param io_pFunctor
+ This functor is called for every shape found.
+
+ @param rBegin
+ Start of range to iterate over
+
+ @param rEnd
+ End of range to iterate over
+
+ @return the number of shapes found in the metafile
+ */
+ template< typename FunctorT > void iterateActionClassifications(
+ FunctorT& io_rFunctor,
+ const DrawShapeSubsetting::IndexClassificatorVector::const_iterator& rBegin,
+ const DrawShapeSubsetting::IndexClassificatorVector::const_iterator& rEnd )
+ {
+ sal_Int32 nCurrShapeCount( 0 );
+ sal_Int32 nCurrParaCount( 0 );
+ sal_Int32 nCurrLineCount( 0 );
+ sal_Int32 nCurrSentenceCount( 0 );
+ sal_Int32 nCurrWordCount( 0 );
+ sal_Int32 nCurrCharCount( 0 );
+
+ DrawShapeSubsetting::IndexClassificatorVector::const_iterator aLastShapeStart(rBegin);
+ DrawShapeSubsetting::IndexClassificatorVector::const_iterator aLastParaStart(rBegin);
+ DrawShapeSubsetting::IndexClassificatorVector::const_iterator aLastLineStart(rBegin);
+ DrawShapeSubsetting::IndexClassificatorVector::const_iterator aLastSentenceStart(rBegin);
+ DrawShapeSubsetting::IndexClassificatorVector::const_iterator aLastWordStart(rBegin);
+ DrawShapeSubsetting::IndexClassificatorVector::const_iterator aLastCharStart(rBegin);
+
+ DrawShapeSubsetting::IndexClassificatorVector::const_iterator aNext;
+ DrawShapeSubsetting::IndexClassificatorVector::const_iterator aCurr( rBegin );
+ while( aCurr != rEnd )
+ {
+ // aNext will hold an iterator to the next element
+ // (or the past-the-end iterator, if aCurr
+ // references the last element). Used to pass a
+ // valid half-open range to the functors.
+ aNext = aCurr;
+ ++aNext;
+
+ switch( *aCurr )
+ {
+ default:
+ ENSURE_OR_THROW( false,
+ "Unexpected type in iterateDocShapes()" );
+ case DrawShapeSubsetting::CLASS_NOOP:
+ // ignore NOOP actions
+ break;
+
+ case DrawShapeSubsetting::CLASS_SHAPE_START:
+ // regardless of ending action
+ // classifications before: a new shape
+ // always also starts contained elements
+ // anew
+ aLastShapeStart =
+ aLastParaStart =
+ aLastLineStart =
+ aLastSentenceStart =
+ aLastWordStart =
+ aLastCharStart = aCurr;
+ break;
+
+ case DrawShapeSubsetting::CLASS_SHAPE_END:
+ if( !io_rFunctor( DrawShapeSubsetting::CLASS_SHAPE_END,
+ nCurrShapeCount,
+ aLastShapeStart,
+ aNext ) )
+ {
+ return;
+ }
+
+ ++nCurrShapeCount;
+ [[fallthrough]]; // shape end also ends lines
+ case DrawShapeSubsetting::CLASS_PARAGRAPH_END:
+ if( !io_rFunctor( DrawShapeSubsetting::CLASS_PARAGRAPH_END,
+ nCurrParaCount,
+ aLastParaStart,
+ aNext ) )
+ {
+ return;
+ }
+
+ ++nCurrParaCount;
+ aLastParaStart = aNext;
+ [[fallthrough]]; // para end also ends line
+ case DrawShapeSubsetting::CLASS_LINE_END:
+ if( !io_rFunctor( DrawShapeSubsetting::CLASS_LINE_END,
+ nCurrLineCount,
+ aLastLineStart,
+ aNext ) )
+ {
+ return;
+ }
+
+ ++nCurrLineCount;
+ aLastLineStart = aNext;
+
+ if( *aCurr == DrawShapeSubsetting::CLASS_LINE_END )
+ {
+ // DON'T fall through here, as a line
+ // does NOT end neither a sentence,
+ // nor a word. OTOH, all parent
+ // structures (paragraph and shape),
+ // which itself fall through to this
+ // code, DO end word, sentence and
+ // character cell.
+
+ // TODO(F1): Maybe a line should end a
+ // character cell, OTOH?
+ break;
+ }
+ [[fallthrough]];
+ case DrawShapeSubsetting::CLASS_SENTENCE_END:
+ if( !io_rFunctor( DrawShapeSubsetting::CLASS_SENTENCE_END,
+ nCurrSentenceCount,
+ aLastSentenceStart,
+ aNext ) )
+ {
+ return;
+ }
+
+ ++nCurrSentenceCount;
+ aLastSentenceStart = aNext;
+ [[fallthrough]];
+ case DrawShapeSubsetting::CLASS_WORD_END:
+ if( !io_rFunctor( DrawShapeSubsetting::CLASS_WORD_END,
+ nCurrWordCount,
+ aLastWordStart,
+ aNext ) )
+ {
+ return;
+ }
+
+ ++nCurrWordCount;
+ aLastWordStart = aNext;
+ [[fallthrough]];
+ case DrawShapeSubsetting::CLASS_CHARACTER_CELL_END:
+ if( !io_rFunctor( DrawShapeSubsetting::CLASS_CHARACTER_CELL_END,
+ nCurrCharCount,
+ aLastCharStart,
+ aNext ) )
+ {
+ return;
+ }
+
+ ++nCurrCharCount;
+ aLastCharStart = aNext;
+ break;
+ }
+
+ aCurr = aNext;
+ }
+ }
+
+ DrawShapeSubsetting::IndexClassificator mapDocTreeNode( DocTreeNode::NodeType eNodeType )
+ {
+ switch( eNodeType )
+ {
+ default:
+ SAL_WARN( "slideshow", "DrawShapeSubsetting::mapDocTreeNode(): unexpected node type");
+ return DrawShapeSubsetting::CLASS_NOOP;
+
+ case DocTreeNode::NodeType::LogicalParagraph:
+ return DrawShapeSubsetting::CLASS_PARAGRAPH_END;
+
+ case DocTreeNode::NodeType::LogicalWord:
+ return DrawShapeSubsetting::CLASS_WORD_END;
+
+ case DocTreeNode::NodeType::LogicalCharacterCell:
+ return DrawShapeSubsetting::CLASS_CHARACTER_CELL_END;
+ };
+ }
+
+ /// Counts number of class occurrences
+ class CountClassFunctor
+ {
+ public:
+ explicit CountClassFunctor( DrawShapeSubsetting::IndexClassificator eClass ) :
+ meClass( eClass ),
+ mnCurrCount(0)
+ {
+ }
+
+ bool operator()( DrawShapeSubsetting::IndexClassificator eCurrElemClassification,
+ sal_Int32 /*nCurrElemCount*/,
+ const DrawShapeSubsetting::IndexClassificatorVector::const_iterator& /*rCurrElemBegin*/,
+ const DrawShapeSubsetting::IndexClassificatorVector::const_iterator& /*rCurrElemEnd*/ )
+ {
+ if( eCurrElemClassification == meClass )
+ ++mnCurrCount;
+
+ return true; // never stop, count all occurrences
+ }
+
+ sal_Int32 getCount() const
+ {
+ return mnCurrCount;
+ }
+
+ private:
+ DrawShapeSubsetting::IndexClassificator meClass;
+ sal_Int32 mnCurrCount;
+ };
+ }
+
+ sal_Int32 DrawShapeSubsetting::implGetNumberOfTreeNodes( const DrawShapeSubsetting::IndexClassificatorVector::const_iterator& rBegin,
+ const DrawShapeSubsetting::IndexClassificatorVector::const_iterator& rEnd,
+ DocTreeNode::NodeType eNodeType )
+ {
+ const IndexClassificator eRequestedClass(
+ mapDocTreeNode( eNodeType ) );
+
+ // create a counting functor for the requested class of
+ // actions
+ CountClassFunctor aFunctor( eRequestedClass );
+
+ // count all occurrences in the given range
+ iterateActionClassifications( aFunctor, rBegin, rEnd );
+
+ return aFunctor.getCount();
+ }
+
+ sal_Int32 DrawShapeSubsetting::getNumberOfTreeNodes( DocTreeNode::NodeType eNodeType ) const
+ {
+ ensureInitializedNodeTree();
+
+ return implGetNumberOfTreeNodes( maActionClassVector.begin(),
+ maActionClassVector.end(),
+ eNodeType );
+ }
+
+ namespace
+ {
+ /** This functor finds the nth occurrence of a given
+ action class.
+
+ The operator() compares the given index value with the
+ requested index, as given on the functor's
+ constructor. Then, the operator() returns false,
+ denoting that the requested action is found.
+ */
+ class FindNthElementFunctor
+ {
+ public:
+ FindNthElementFunctor( sal_Int32 nNodeIndex,
+ DrawShapeSubsetting::IndexClassificatorVector::const_iterator& rLastBegin,
+ DrawShapeSubsetting::IndexClassificatorVector::const_iterator& rLastEnd,
+ DrawShapeSubsetting::IndexClassificator eClass ) :
+ mnNodeIndex( nNodeIndex ),
+ mrLastBegin( rLastBegin ),
+ mrLastEnd( rLastEnd ),
+ meClass( eClass )
+ {
+ }
+
+ bool operator()( DrawShapeSubsetting::IndexClassificator eCurrElemClassification,
+ sal_Int32 nCurrElemCount,
+ const DrawShapeSubsetting::IndexClassificatorVector::const_iterator& rCurrElemBegin,
+ const DrawShapeSubsetting::IndexClassificatorVector::const_iterator& rCurrElemEnd )
+ {
+ if( eCurrElemClassification == meClass &&
+ nCurrElemCount == mnNodeIndex )
+ {
+ mrLastBegin = rCurrElemBegin;
+ mrLastEnd = rCurrElemEnd;
+
+ return false; // abort iteration, we've
+ // already found what we've been
+ // looking for
+ }
+
+ return true; // keep on truckin'
+ }
+
+ private:
+ sal_Int32 mnNodeIndex;
+ DrawShapeSubsetting::IndexClassificatorVector::const_iterator& mrLastBegin;
+ DrawShapeSubsetting::IndexClassificatorVector::const_iterator& mrLastEnd;
+ DrawShapeSubsetting::IndexClassificator meClass;
+ };
+
+ DocTreeNode makeTreeNode( const DrawShapeSubsetting::IndexClassificatorVector::const_iterator& rBegin,
+ const DrawShapeSubsetting::IndexClassificatorVector::const_iterator& rStart,
+ const DrawShapeSubsetting::IndexClassificatorVector::const_iterator& rEnd )
+ {
+ return DocTreeNode( ::std::distance(rBegin,
+ rStart),
+ ::std::distance(rBegin,
+ rEnd) );
+ }
+ }
+
+ DocTreeNode DrawShapeSubsetting::implGetTreeNode( const IndexClassificatorVector::const_iterator& rBegin,
+ const IndexClassificatorVector::const_iterator& rEnd,
+ sal_Int32 nNodeIndex,
+ DocTreeNode::NodeType eNodeType ) const
+ {
+ const IndexClassificator eRequestedClass(
+ mapDocTreeNode( eNodeType ) );
+
+ DrawShapeSubsetting::IndexClassificatorVector::const_iterator aLastBegin(rEnd);
+ DrawShapeSubsetting::IndexClassificatorVector::const_iterator aLastEnd(rEnd);
+
+ // create a nth element functor for the requested class of
+ // actions, and nNodeIndex as the target index
+ FindNthElementFunctor aFunctor( nNodeIndex,
+ aLastBegin,
+ aLastEnd,
+ eRequestedClass );
+
+ // find given index in the given range
+ iterateActionClassifications( aFunctor, rBegin, rEnd );
+
+ return makeTreeNode( maActionClassVector.begin(),
+ aLastBegin, aLastEnd );
+ }
+
+ DocTreeNode DrawShapeSubsetting::getTreeNode( sal_Int32 nNodeIndex,
+ DocTreeNode::NodeType eNodeType ) const
+ {
+ ensureInitializedNodeTree();
+
+ return implGetTreeNode( maActionClassVector.begin(),
+ maActionClassVector.end(),
+ nNodeIndex,
+ eNodeType );
+ }
+
+ sal_Int32 DrawShapeSubsetting::getNumberOfSubsetTreeNodes( const DocTreeNode& rParentNode,
+ DocTreeNode::NodeType eNodeType ) const
+ {
+ ensureInitializedNodeTree();
+
+ // convert from vector indices to vector iterators
+ const DrawShapeSubsetting::IndexClassificatorVector::const_iterator aBegin( maActionClassVector.begin() );
+ const DrawShapeSubsetting::IndexClassificatorVector::const_iterator aParentBegin( aBegin + rParentNode.getStartIndex() );
+ const DrawShapeSubsetting::IndexClassificatorVector::const_iterator aParentEnd( aBegin + rParentNode.getEndIndex() );
+
+ return implGetNumberOfTreeNodes( aParentBegin,
+ aParentEnd,
+ eNodeType );
+ }
+
+ DocTreeNode DrawShapeSubsetting::getSubsetTreeNode( const DocTreeNode& rParentNode,
+ sal_Int32 nNodeIndex,
+ DocTreeNode::NodeType eNodeType ) const
+ {
+ ensureInitializedNodeTree();
+
+ // convert from vector indices to vector iterators
+ const DrawShapeSubsetting::IndexClassificatorVector::const_iterator aBegin( maActionClassVector.begin() );
+ const DrawShapeSubsetting::IndexClassificatorVector::const_iterator aParentBegin( aBegin + rParentNode.getStartIndex() );
+ const DrawShapeSubsetting::IndexClassificatorVector::const_iterator aParentEnd( aBegin + rParentNode.getEndIndex() );
+
+ return implGetTreeNode( aParentBegin,
+ aParentEnd,
+ nNodeIndex,
+ eNodeType );
+ }
+
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/slideshow/source/engine/shapes/drawshapesubsetting.hxx b/slideshow/source/engine/shapes/drawshapesubsetting.hxx
new file mode 100644
index 000000000..fe0347774
--- /dev/null
+++ b/slideshow/source/engine/shapes/drawshapesubsetting.hxx
@@ -0,0 +1,244 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#ifndef INCLUDED_SLIDESHOW_SOURCE_ENGINE_SHAPES_DRAWSHAPESUBSETTING_HXX
+#define INCLUDED_SLIDESHOW_SOURCE_ENGINE_SHAPES_DRAWSHAPESUBSETTING_HXX
+
+#include <doctreenode.hxx>
+#include <attributableshape.hxx>
+
+
+class GDIMetaFile;
+typedef ::std::shared_ptr< GDIMetaFile > GDIMetaFileSharedPtr;
+
+namespace slideshow::internal
+ {
+ /** This class encapsulates the subsetting aspects of a
+ DrawShape.
+ */
+ class DrawShapeSubsetting
+ {
+ public:
+ /** Create empty shape subset handling.
+
+ This method creates a subset handler which contains no
+ subset information. All methods will return default
+ values.
+
+ @param rMtf
+ Metafile to retrieve subset info from (must have been
+ generated with verbose text comments switched on).
+ */
+ DrawShapeSubsetting();
+
+ /** Create new shape subset handling.
+
+ @param rShapeSubset
+ The subset this object represents (can be empty, then
+ denoting 'represents a whole shape')
+
+ @param rMtf
+ Metafile to retrieve subset info from (must have been
+ generated with verbose text comments switched on).
+ */
+ DrawShapeSubsetting( const DocTreeNode& rShapeSubset,
+ GDIMetaFileSharedPtr rMtf );
+
+ /// Forbid copy construction
+ DrawShapeSubsetting(const DrawShapeSubsetting&) = delete;
+
+ /// Forbid copy assignment
+ DrawShapeSubsetting& operator=(const DrawShapeSubsetting&) = delete;
+
+ /** Reset metafile.
+
+ Use this method to completely reset the
+ ShapeSubsetting, with a new metafile. Note that any
+ information previously set will be lost, including
+ added subset shapes!
+
+ @param rMtf
+ Metafile to retrieve subset info from (must have been
+ generated with verbose text comments switched on).
+ */
+ void reset( const ::std::shared_ptr< GDIMetaFile >& rMtf );
+
+ // Shape subsetting methods
+
+
+ /// Return subset node for this shape
+ const DocTreeNode& getSubsetNode () const;
+
+ /// Get subset shape for given node, if any
+ AttributableShapeSharedPtr getSubsetShape ( const DocTreeNode& rTreeNode ) const;
+
+ /// Add child subset shape (or increase use count, if already existent)
+ void addSubsetShape ( const AttributableShapeSharedPtr& rShape );
+
+ /** Revoke subset shape
+
+ This method revokes a subset shape, decrementing the
+ use count for this subset by one. If the use count
+ reaches zero (i.e. when the number of addSubsetShape()
+ matches the number of revokeSubsetShape() calls for
+ the same subset), the subset entry is removed from the
+ internal list, and subsequent getSubsetShape() calls
+ will return the empty pointer for this subset.
+
+ @return true, if the subset shape was physically
+ removed from the list (false is returned, when nothing
+ was removed, either because only the use count was
+ decremented, or there was no such subset found, in the
+ first place).
+ */
+ bool revokeSubsetShape ( const AttributableShapeSharedPtr& rShape );
+
+
+ // Doc tree methods
+
+
+ /// Return overall number of nodes for given type
+ sal_Int32 getNumberOfTreeNodes ( DocTreeNode::NodeType eNodeType ) const;
+
+ /// Return tree node of given index and given type
+ DocTreeNode getTreeNode ( sal_Int32 nNodeIndex,
+ DocTreeNode::NodeType eNodeType ) const;
+
+ /// Return number of nodes of given type, below parent node
+ sal_Int32 getNumberOfSubsetTreeNodes ( const DocTreeNode& rParentNode,
+ DocTreeNode::NodeType eNodeType ) const;
+
+ /// Return tree node of given index and given type, relative to parent node
+ DocTreeNode getSubsetTreeNode ( const DocTreeNode& rParentNode,
+ sal_Int32 nNodeIndex,
+ DocTreeNode::NodeType eNodeType ) const;
+
+ // Helper
+
+
+ /** Return a vector of currently active subsets.
+
+ Needed when rendering a shape, this method provides a
+ vector of subsets currently visible (the range as
+ returned by getEffectiveSubset(), minus the parts that
+ are currently hidden, because displayed by child
+ shapes).
+ */
+ const VectorOfDocTreeNodes& getActiveSubsets() const { return maCurrentSubsets; }
+
+ /** This enum classifies each action index in the
+ metafile.
+
+ Of interest are, of course, the places where
+ structural shape and/or text elements end. The
+ remainder of the action gets classified as 'noop'
+ */
+ enum IndexClassificator
+ {
+ CLASS_NOOP,
+ CLASS_SHAPE_START,
+ CLASS_SHAPE_END,
+
+ CLASS_LINE_END,
+ CLASS_PARAGRAPH_END,
+ CLASS_SENTENCE_END,
+ CLASS_WORD_END,
+ CLASS_CHARACTER_CELL_END
+ };
+
+ typedef ::std::vector< IndexClassificator > IndexClassificatorVector;
+
+ private:
+ /** Entry for subset shape
+
+ This struct contains data for every subset shape
+ generated. Note that for a given start/end action
+ index combination, only one subset instance is
+ generated (and reused for subsequent queries).
+ */
+ struct SubsetEntry
+ {
+ AttributableShapeSharedPtr mpShape;
+ sal_Int32 mnStartActionIndex;
+ sal_Int32 mnEndActionIndex;
+
+ /// Number of times this subset was queried, and not yet revoked
+ int mnSubsetQueriedCount;
+
+ sal_Int32 getHashValue() const
+ {
+ // TODO(Q3): That's a hack. We assume that start
+ // index will always be less than 65535 (if this
+ // assumption is violated, hash map performance
+ // will degrade severely)
+ return mnStartActionIndex*SAL_MAX_INT16 + mnEndActionIndex;
+ }
+
+ /// The shape set is ordered according to this method
+ bool operator<(const SubsetEntry& rOther) const
+ {
+ return getHashValue() < rOther.getHashValue();
+ }
+
+ };
+
+ typedef ::std::set< SubsetEntry > ShapeSet;
+
+ void ensureInitializedNodeTree() const;
+ void excludeSubset(sal_Int32 nExcludedStart, sal_Int32 nExcludedEnd);
+ void updateSubsets();
+ void initCurrentSubsets();
+ void reset();
+
+ static sal_Int32 implGetNumberOfTreeNodes( const IndexClassificatorVector::const_iterator& rBegin,
+ const IndexClassificatorVector::const_iterator& rEnd,
+ DocTreeNode::NodeType eNodeType );
+ DocTreeNode implGetTreeNode( const IndexClassificatorVector::const_iterator& rBegin,
+ const IndexClassificatorVector::const_iterator& rEnd,
+ sal_Int32 nNodeIndex,
+ DocTreeNode::NodeType eNodeType ) const;
+
+ mutable IndexClassificatorVector maActionClassVector;
+
+ /// Metafile to retrieve subset info from
+ ::std::shared_ptr< GDIMetaFile > mpMtf;
+
+ /// Subset of the metafile represented by this object
+ DocTreeNode maSubset;
+
+ /// the list of subset shapes spawned from this one.
+ ShapeSet maSubsetShapes;
+
+ /** Current number of subsets to render (calculated from
+ maSubset and mnMin/MaxSubsetActionIndex).
+
+ Note that this is generally _not_ equivalent to
+ maSubset, as it excludes all active subset children!
+ */
+ mutable VectorOfDocTreeNodes maCurrentSubsets;
+
+ /// Whether the shape's doc tree has been initialized successfully, or not
+ mutable bool mbNodeTreeInitialized;
+ };
+
+}
+
+#endif // INCLUDED_SLIDESHOW_SOURCE_ENGINE_SHAPES_DRAWSHAPESUBSETTING_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/slideshow/source/engine/shapes/externalshapebase.cxx b/slideshow/source/engine/shapes/externalshapebase.cxx
new file mode 100644
index 000000000..440b10d95
--- /dev/null
+++ b/slideshow/source/engine/shapes/externalshapebase.cxx
@@ -0,0 +1,211 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+// must be first
+#include <tools/diagnose_ex.h>
+
+#include "externalshapebase.hxx"
+#include <eventmultiplexer.hxx>
+#include <subsettableshapemanager.hxx>
+#include <vieweventhandler.hxx>
+#include <intrinsicanimationeventhandler.hxx>
+#include <tools.hxx>
+
+
+using namespace ::com::sun::star;
+
+
+namespace slideshow::internal
+{
+ class ExternalShapeBase::ExternalShapeBaseListener : public ViewEventHandler,
+ public IntrinsicAnimationEventHandler
+ {
+ public:
+ explicit ExternalShapeBaseListener( ExternalShapeBase& rBase ) :
+ mrBase( rBase )
+ {}
+ ExternalShapeBaseListener(const ExternalShapeBaseListener&) = delete;
+ ExternalShapeBaseListener& operator=(const ExternalShapeBaseListener&) = delete;
+
+ private:
+ // ViewEventHandler
+
+
+ virtual void viewAdded( const UnoViewSharedPtr& ) override {}
+ virtual void viewRemoved( const UnoViewSharedPtr& ) override {}
+ virtual void viewChanged( const UnoViewSharedPtr& rView ) override
+ {
+ mrBase.implViewChanged(rView);
+ }
+ virtual void viewsChanged() override
+ {
+ mrBase.implViewsChanged();
+ }
+
+
+ // IntrinsicAnimationEventHandler
+
+
+ virtual bool enableAnimations() override
+ {
+ return mrBase.implStartIntrinsicAnimation();
+ }
+ virtual bool disableAnimations() override
+ {
+ return mrBase.implEndIntrinsicAnimation();
+ }
+
+ ExternalShapeBase& mrBase;
+ };
+
+
+ ExternalShapeBase::ExternalShapeBase( const uno::Reference< drawing::XShape >& xShape,
+ double nPrio,
+ const SlideShowContext& rContext ) :
+ mxComponentContext( rContext.mxComponentContext ),
+ mxShape( xShape ),
+ mpListener( std::make_shared<ExternalShapeBaseListener>(*this) ),
+ mpShapeManager( rContext.mpSubsettableShapeManager ),
+ mrEventMultiplexer( rContext.mrEventMultiplexer ),
+ mnPriority( nPrio ), // TODO(F1): When ZOrder someday becomes usable: make this ( getAPIShapePrio( xShape ) ),
+ maBounds( getAPIShapeBounds( xShape ) )
+ {
+ ENSURE_OR_THROW( mxShape.is(), "ExternalShapeBase::ExternalShapeBase(): Invalid XShape" );
+
+ mpShapeManager->addIntrinsicAnimationHandler( mpListener );
+ mrEventMultiplexer.addViewHandler( mpListener );
+ }
+
+
+ ExternalShapeBase::~ExternalShapeBase()
+ {
+ try
+ {
+ mrEventMultiplexer.removeViewHandler( mpListener );
+ mpShapeManager->removeIntrinsicAnimationHandler( mpListener );
+ }
+ catch (uno::Exception &)
+ {
+ TOOLS_WARN_EXCEPTION( "slideshow", "" );
+ }
+ }
+
+
+ uno::Reference< drawing::XShape > ExternalShapeBase::getXShape() const
+ {
+ return mxShape;
+ }
+
+
+ void ExternalShapeBase::play()
+ {
+ implStartIntrinsicAnimation();
+ }
+
+
+ void ExternalShapeBase::stop()
+ {
+ implEndIntrinsicAnimation();
+ }
+
+
+ void ExternalShapeBase::pause()
+ {
+ implPauseIntrinsicAnimation();
+ }
+
+
+ bool ExternalShapeBase::isPlaying() const
+ {
+ return implIsIntrinsicAnimationPlaying();
+ }
+
+
+ void ExternalShapeBase::setMediaTime(double fTime)
+ {
+ implSetIntrinsicAnimationTime(fTime);
+ }
+
+ void ExternalShapeBase::setLooping(bool bLooping) { implSetLooping(bLooping); }
+
+ bool ExternalShapeBase::update() const
+ {
+ return render();
+ }
+
+
+ bool ExternalShapeBase::render() const
+ {
+ if( maBounds.getRange().equalZero() )
+ {
+ // zero-sized shapes are effectively invisible,
+ // thus, we save us the rendering...
+ return true;
+ }
+
+ return implRender( maBounds );
+ }
+
+
+ bool ExternalShapeBase::isContentChanged() const
+ {
+ return true;
+ }
+
+
+ ::basegfx::B2DRectangle ExternalShapeBase::getBounds() const
+ {
+ return maBounds;
+ }
+
+
+ ::basegfx::B2DRectangle ExternalShapeBase::getDomBounds() const
+ {
+ return maBounds;
+ }
+
+
+ ::basegfx::B2DRectangle ExternalShapeBase::getUpdateArea() const
+ {
+ return maBounds;
+ }
+
+
+ bool ExternalShapeBase::isVisible() const
+ {
+ return true;
+ }
+
+
+ double ExternalShapeBase::getPriority() const
+ {
+ return mnPriority;
+ }
+
+
+ bool ExternalShapeBase::isBackgroundDetached() const
+ {
+ // external shapes always have their own window/surface
+ return true;
+ }
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/slideshow/source/engine/shapes/externalshapebase.hxx b/slideshow/source/engine/shapes/externalshapebase.hxx
new file mode 100644
index 000000000..d8c208db2
--- /dev/null
+++ b/slideshow/source/engine/shapes/externalshapebase.hxx
@@ -0,0 +1,131 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#ifndef INCLUDED_SLIDESHOW_SOURCE_ENGINE_SHAPES_EXTERNALSHAPEBASE_HXX
+#define INCLUDED_SLIDESHOW_SOURCE_ENGINE_SHAPES_EXTERNALSHAPEBASE_HXX
+
+#include <iexternalmediashapebase.hxx>
+#include <unoview.hxx>
+#include <slideshowcontext.hxx>
+
+
+namespace slideshow::internal
+ {
+ /** Base class for shapes rendered by external engines.
+
+ Used as the common base for e.g. MediaShape or
+ AppletShape, all of which are rendered by external
+ components (and all employ distinct windows).
+
+ Please note that this base class indeed assumes the shape
+ does not interfere with the internal shapes in any way
+ (including mutual overdraw). It therefore reports yes for
+ the isBackgroundDetached() question.
+ */
+ class ExternalShapeBase : public IExternalMediaShapeBase
+ {
+ public:
+ /** Create a shape for the given XShape for an external shape
+
+ @param xShape
+ The XShape to represent.
+
+ @param nPrio
+ Externally-determined shape priority (used e.g. for
+ paint ordering). This number _must be_ unique!
+ */
+ ExternalShapeBase( const css::uno::Reference< css::drawing::XShape >& xShape,
+ double nPrio,
+ const SlideShowContext& rContext ); // throw ShapeLoadFailedException;
+ virtual ~ExternalShapeBase() override;
+
+ virtual css::uno::Reference< css::drawing::XShape > getXShape() const override;
+
+ // animation methods
+
+
+ virtual void play() override;
+ virtual void stop() override;
+ virtual void pause() override;
+ virtual bool isPlaying() const override;
+ virtual void setMediaTime(double) override;
+ void setLooping(bool bLooping) override;
+
+ // render methods
+
+
+ virtual bool update() const override;
+ virtual bool render() const override;
+ virtual bool isContentChanged() const override;
+
+
+ // Shape attributes
+
+
+ virtual ::basegfx::B2DRectangle getBounds() const override;
+ virtual ::basegfx::B2DRectangle getDomBounds() const override;
+ virtual ::basegfx::B2DRectangle getUpdateArea() const override;
+ virtual bool isVisible() const override;
+ virtual double getPriority() const override;
+ virtual bool isBackgroundDetached() const override;
+
+ protected:
+ const css::uno::Reference<css::uno::XComponentContext> mxComponentContext;
+
+ private:
+ class ExternalShapeBaseListener; friend class ExternalShapeBaseListener;
+
+ /// override in derived class to render preview
+ virtual bool implRender( const ::basegfx::B2DRange& rCurrBounds ) const = 0;
+
+ /// override in derived class to resize
+ virtual void implViewChanged( const UnoViewSharedPtr& rView ) = 0;
+ /// override in derived class to resize
+ virtual void implViewsChanged() = 0;
+
+ /// override in derived class to start external viewer
+ virtual bool implStartIntrinsicAnimation() = 0;
+ /// override in derived class to stop external viewer
+ virtual bool implEndIntrinsicAnimation() = 0;
+ /// override in derived class to pause external viewer
+ virtual void implPauseIntrinsicAnimation() = 0;
+ /// override in derived class to return status of animation
+ virtual bool implIsIntrinsicAnimationPlaying() const = 0;
+ /// override in derived class to set media time
+ virtual void implSetIntrinsicAnimationTime(double) = 0;
+ virtual void implSetLooping(bool /*bLooping*/) {}
+
+
+ /// The associated XShape
+ css::uno::Reference< css::drawing::XShape > mxShape;
+
+ std::shared_ptr<ExternalShapeBaseListener> mpListener;
+
+ SubsettableShapeManagerSharedPtr mpShapeManager;
+ EventMultiplexer& mrEventMultiplexer;
+
+ // The attributes of this Shape
+ const double mnPriority;
+ ::basegfx::B2DRectangle maBounds;
+ };
+}
+
+#endif // INCLUDED_SLIDESHOW_SOURCE_ENGINE_SHAPES_EXTERNALSHAPEBASE_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/slideshow/source/engine/shapes/gdimtftools.cxx b/slideshow/source/engine/shapes/gdimtftools.cxx
new file mode 100644
index 000000000..6a803f757
--- /dev/null
+++ b/slideshow/source/engine/shapes/gdimtftools.cxx
@@ -0,0 +1,421 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#include <sal/log.hxx>
+#include "gdimtftools.hxx"
+
+#include <com/sun/star/graphic/XGraphic.hpp>
+#include <com/sun/star/graphic/XGraphicRenderer.hpp>
+#include <com/sun/star/drawing/GraphicExportFilter.hpp>
+
+#include <cppuhelper/basemutex.hxx>
+#include <cppuhelper/compbase.hxx>
+
+#include <comphelper/fileformat.h>
+#include <comphelper/propertyvalue.hxx>
+
+#include <vcl/canvastools.hxx>
+#include <vcl/metaact.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/gdimtf.hxx>
+#include <vcl/animate/Animation.hxx>
+#include <vcl/graph.hxx>
+
+#include <tools.hxx>
+
+using namespace ::com::sun::star;
+
+
+// free support functions
+// ======================
+
+namespace slideshow::internal
+{
+// TODO(E2): Detect the case when svx/drawing layer is not
+// in-process, or even not on the same machine, and
+// fallback to metafile streaming!
+
+// For fixing #i48102#, have to be a _lot_ more selective
+// on which metafiles to convert to bitmaps. The problem
+// here is that we _always_ get the shape content as a
+// metafile, even if we have a bitmap graphic shape. Thus,
+// calling GetBitmapEx on such a Graphic (see below) will
+// result in one poorly scaled bitmap into another,
+// somewhat arbitrarily sized bitmap.
+static bool hasUnsupportedActions( const GDIMetaFile& rMtf )
+{
+ // search metafile for RasterOp action
+ MetaAction* pCurrAct;
+
+ // TODO(Q3): avoid const-cast
+ for( pCurrAct = const_cast<GDIMetaFile&>(rMtf).FirstAction();
+ pCurrAct;
+ pCurrAct = const_cast<GDIMetaFile&>(rMtf).NextAction() )
+ {
+ switch( pCurrAct->GetType() )
+ {
+ case MetaActionType::RASTEROP:
+ // overpaint is okay - that's the default, anyway
+ if( RasterOp::OverPaint ==
+ static_cast<MetaRasterOpAction*>(pCurrAct)->GetRasterOp() )
+ {
+ break;
+ }
+ [[fallthrough]];
+ case MetaActionType::MOVECLIPREGION:
+ case MetaActionType::REFPOINT:
+ case MetaActionType::WALLPAPER:
+ return true; // at least one unsupported
+ // action encountered
+ default: break;
+ }
+ }
+
+ return false; // no unsupported action found
+}
+
+namespace {
+
+typedef ::cppu::WeakComponentImplHelper< graphic::XGraphicRenderer > DummyRenderer_Base;
+
+class DummyRenderer: public cppu::BaseMutex, public DummyRenderer_Base
+{
+public:
+ DummyRenderer() :
+ DummyRenderer_Base( m_aMutex ),
+ mxGraphic()
+ {
+ }
+
+ //--- XGraphicRenderer -----------------------------------
+ virtual void SAL_CALL render( const uno::Reference< graphic::XGraphic >& rGraphic ) override
+ {
+ ::osl::MutexGuard aGuard( m_aMutex );
+ mxGraphic = rGraphic;
+ }
+
+ /** Retrieve GDIMetaFile from renderer
+
+ @param bForeignSource
+ When true, the source of the metafile might be a
+ foreign application. The metafile is checked
+ against unsupported content, and, if necessary,
+ returned as a pre-rendered bitmap.
+ */
+ GDIMetaFileSharedPtr getMtf( bool bForeignSource ) const
+ {
+ ::osl::MutexGuard aGuard( m_aMutex );
+
+ Graphic aGraphic( mxGraphic );
+
+ if( aGraphic.GetType() == GraphicType::Bitmap ||
+ (bForeignSource &&
+ hasUnsupportedActions(aGraphic.GetGDIMetaFile()) ) )
+ {
+ // wrap bitmap into GDIMetafile
+ GDIMetaFileSharedPtr xMtf = std::make_shared<GDIMetaFile>();
+
+ ::BitmapEx aBmpEx( aGraphic.GetBitmapEx() );
+
+ xMtf->AddAction( new MetaBmpExAction( Point(),
+ aBmpEx ) );
+ xMtf->SetPrefSize( aBmpEx.GetPrefSize() );
+ xMtf->SetPrefMapMode( aBmpEx.GetPrefMapMode() );
+
+ return xMtf;
+ }
+ return std::make_shared<GDIMetaFile>(aGraphic.GetGDIMetaFile());
+ }
+
+private:
+ uno::Reference< graphic::XGraphic > mxGraphic;
+};
+
+} // anon namespace
+
+// Quick'n'dirty way: tunnel Graphic (only works for
+// in-process slideshow, of course)
+GDIMetaFileSharedPtr getMetaFile( const uno::Reference< lang::XComponent >& xSource,
+ const uno::Reference< drawing::XDrawPage >& xContainingPage,
+ int mtfLoadFlags,
+ const uno::Reference< uno::XComponentContext >& rxContext )
+{
+ if (!rxContext.is())
+ {
+ SAL_WARN("slideshow.opengl", "getMetaFile(): Invalid context" );
+ return GDIMetaFileSharedPtr();
+ }
+
+ // create dummy XGraphicRenderer, which receives the
+ // generated XGraphic from the GraphicExporter
+
+ // TODO(P3): Move creation of DummyRenderer out of the
+ // loop! Either by making it static, or transforming
+ // the whole thing here into a class.
+ rtl::Reference<DummyRenderer> xRenderer( new DummyRenderer() );
+
+ // creating the graphic exporter
+ uno::Reference< drawing::XGraphicExportFilter > xExporter =
+ drawing::GraphicExportFilter::create(rxContext);
+
+ uno::Sequence< beans::PropertyValue > aFilterData{
+ comphelper::makePropertyValue("ScrollText",
+ ((mtfLoadFlags & MTF_LOAD_SCROLL_TEXT_MTF) != 0)),
+ comphelper::makePropertyValue("ExportOnlyBackground",
+ ((mtfLoadFlags & MTF_LOAD_BACKGROUND_ONLY) != 0)),
+ comphelper::makePropertyValue("Version", static_cast<sal_Int32>( SOFFICE_FILEFORMAT_50 )),
+ comphelper::makePropertyValue(
+ "CurrentPage", uno::Reference< uno::XInterface >( xContainingPage,
+ uno::UNO_QUERY_THROW ))
+ };
+
+ uno::Sequence< beans::PropertyValue > aProps{
+ comphelper::makePropertyValue("FilterName", OUString("SVM")),
+ comphelper::makePropertyValue("GraphicRenderer", uno::Reference< graphic::XGraphicRenderer >(xRenderer)),
+ comphelper::makePropertyValue("FilterData", aFilterData)
+ };
+
+ xExporter->setSourceDocument( xSource );
+ if( !xExporter->filter( aProps ) )
+ return GDIMetaFileSharedPtr();
+
+ GDIMetaFileSharedPtr xMtf = xRenderer->getMtf( (mtfLoadFlags & MTF_LOAD_FOREIGN_SOURCE) != 0 );
+
+ // pRenderer is automatically destroyed when xRenderer
+ // goes out of scope
+
+ // TODO(E3): Error handling. Exporter might have
+ // generated nothing, a bitmap, threw an exception,
+ // whatever.
+ return xMtf;
+}
+
+sal_Int32 getNextActionOffset( MetaAction * pCurrAct )
+{
+ // Special handling for actions that represent
+ // more than one indexable action
+ // ===========================================
+
+ switch (pCurrAct->GetType()) {
+ case MetaActionType::TEXT: {
+ MetaTextAction * pAct = static_cast<MetaTextAction *>(pCurrAct);
+ sal_Int32 nLen = std::min(pAct->GetLen(), pAct->GetText().getLength() - pAct->GetIndex());
+ return nLen;
+ }
+ case MetaActionType::TEXTARRAY: {
+ MetaTextArrayAction * pAct =
+ static_cast<MetaTextArrayAction *>(pCurrAct);
+ sal_Int32 nLen = std::min(pAct->GetLen(), pAct->GetText().getLength() - pAct->GetIndex());
+ return nLen;
+ }
+ case MetaActionType::STRETCHTEXT: {
+ MetaStretchTextAction * pAct =
+ static_cast<MetaStretchTextAction *>(pCurrAct);
+ sal_Int32 nLen = std::min(pAct->GetLen(), pAct->GetText().getLength() - pAct->GetIndex());
+ return nLen;
+ }
+ case MetaActionType::FLOATTRANSPARENT: {
+ MetaFloatTransparentAction * pAct =
+ static_cast<MetaFloatTransparentAction*>(pCurrAct);
+ // TODO(F2): Recurse into action metafile
+ // (though this is currently not used from the
+ // DrawingLayer - shape transparency gradients
+ // don't affect shape text)
+ return pAct->GetGDIMetaFile().GetActionSize();
+ }
+ default:
+ return 1;
+ }
+}
+
+bool getAnimationFromGraphic( VectorOfMtfAnimationFrames& o_rFrames,
+ sal_uInt32& o_rLoopCount,
+ const Graphic& rGraphic )
+{
+ o_rFrames.clear();
+
+ if( !rGraphic.IsAnimated() )
+ return false;
+
+ // some loop invariants
+ ::Animation aAnimation( rGraphic.GetAnimation() );
+ const Point aEmptyPoint;
+ const Size aAnimSize( aAnimation.GetDisplaySizePixel() );
+
+ // setup VDev, into which all bitmaps are painted (want to
+ // normalize animations to n bitmaps of same size. An Animation,
+ // though, can contain bitmaps of varying sizes and different
+ // update modes)
+ ScopedVclPtrInstance< VirtualDevice > pVDev;
+ pVDev->SetOutputSizePixel( aAnimSize );
+ pVDev->EnableMapMode( false );
+
+ // setup mask VDev (alpha VDev is currently rather slow)
+ ScopedVclPtrInstance<VirtualDevice> pVDevMask(DeviceFormat::DEFAULT);
+ pVDevMask->SetOutputSizePixel( aAnimSize );
+ pVDevMask->EnableMapMode( false );
+
+ o_rLoopCount = aAnimation.GetLoopCount();
+
+ for( sal_uInt16 i=0, nCount=aAnimation.Count(); i<nCount; ++i )
+ {
+ const AnimationBitmap& rAnimationBitmap( aAnimation.Get(i) );
+ switch(rAnimationBitmap.meDisposal)
+ {
+ case Disposal::Not:
+ {
+ pVDev->DrawBitmapEx(rAnimationBitmap.maPositionPixel,
+ rAnimationBitmap.maBitmapEx);
+ Bitmap aMask = rAnimationBitmap.maBitmapEx.GetAlpha();
+
+ if( aMask.IsEmpty() )
+ {
+ const tools::Rectangle aRect(aEmptyPoint,
+ pVDevMask->GetOutputSizePixel());
+ const Wallpaper aWallpaper(COL_BLACK);
+ pVDevMask->DrawWallpaper(aRect,
+ aWallpaper);
+ }
+ else
+ {
+ BitmapEx aTmpMask(aMask, aMask);
+ pVDevMask->DrawBitmapEx(rAnimationBitmap.maPositionPixel,
+ aTmpMask );
+ }
+ break;
+ }
+
+ case Disposal::Back:
+ {
+ // #i70772# react on no mask
+ const Bitmap aMask(rAnimationBitmap.maBitmapEx.GetAlpha());
+ const Bitmap & rContent(rAnimationBitmap.maBitmapEx.GetBitmap());
+
+ pVDevMask->Erase();
+ pVDev->DrawBitmap(rAnimationBitmap.maPositionPixel, rContent);
+
+ if(aMask.IsEmpty())
+ {
+ const tools::Rectangle aRect(rAnimationBitmap.maPositionPixel, rContent.GetSizePixel());
+ pVDevMask->SetFillColor( COL_BLACK);
+ pVDevMask->SetLineColor();
+ pVDevMask->DrawRect(aRect);
+ }
+ else
+ {
+ pVDevMask->DrawBitmap(rAnimationBitmap.maPositionPixel, aMask);
+ }
+ break;
+ }
+
+ case Disposal::Previous :
+ {
+ pVDev->DrawBitmapEx(rAnimationBitmap.maPositionPixel,
+ rAnimationBitmap.maBitmapEx);
+ pVDevMask->DrawBitmap(rAnimationBitmap.maPositionPixel,
+ rAnimationBitmap.maBitmapEx.GetAlpha());
+ break;
+ }
+ }
+
+ // extract current aVDev content into a new animation
+ // frame
+ GDIMetaFileSharedPtr pMtf = std::make_shared<GDIMetaFile>();
+ pMtf->AddAction(
+ new MetaBmpExAction( aEmptyPoint,
+ BitmapEx(
+ pVDev->GetBitmap(
+ aEmptyPoint,
+ aAnimSize ),
+ pVDevMask->GetBitmap(
+ aEmptyPoint,
+ aAnimSize ))));
+
+ // setup mtf dimensions and pref map mode (for
+ // simplicity, keep it all in pixel. the metafile
+ // renderer scales it down to (1, 1) box anyway)
+ pMtf->SetPrefMapMode( MapMode() );
+ pMtf->SetPrefSize( aAnimSize );
+
+ // Take care of special value for MultiPage TIFFs. ATM these shall just
+ // show their first page for _quite_ some time.
+ sal_Int32 nWaitTime100thSeconds(rAnimationBitmap.mnWait);
+ if( ANIMATION_TIMEOUT_ON_CLICK == nWaitTime100thSeconds )
+ {
+ // ATM the huge value would block the timer, so use a long
+ // time to show first page (whole day)
+ nWaitTime100thSeconds = 100 * 60 * 60 * 24;
+ }
+
+ // There are animated GIFs with no WaitTime set. Take 0.1 sec, the
+ // same duration that is used by the edit view.
+ if( nWaitTime100thSeconds == 0 )
+ nWaitTime100thSeconds = 10;
+
+ o_rFrames.emplace_back( pMtf, nWaitTime100thSeconds / 100.0 );
+ }
+
+ return !o_rFrames.empty();
+}
+
+bool getRectanglesFromScrollMtf( ::basegfx::B2DRectangle& o_rScrollRect,
+ ::basegfx::B2DRectangle& o_rPaintRect,
+ const GDIMetaFileSharedPtr& rMtf )
+{
+ // extract bounds: scroll rect, paint rect
+ bool bScrollRectSet(false);
+ bool bPaintRectSet(false);
+
+ for ( MetaAction * pCurrAct = rMtf->FirstAction();
+ pCurrAct != nullptr; pCurrAct = rMtf->NextAction() )
+ {
+ if (pCurrAct->GetType() == MetaActionType::COMMENT)
+ {
+ MetaCommentAction * pAct =
+ static_cast<MetaCommentAction *>(pCurrAct);
+ // skip comment if not a special XTEXT... comment
+ if( pAct->GetComment().matchIgnoreAsciiCase( "XTEXT" ) )
+ {
+ if (pAct->GetComment().equalsIgnoreAsciiCase("XTEXT_SCROLLRECT"))
+ {
+ o_rScrollRect = vcl::unotools::b2DRectangleFromRectangle(
+ *reinterpret_cast<tools::Rectangle const *>(
+ pAct->GetData() ));
+
+ bScrollRectSet = true;
+ }
+ else if (pAct->GetComment().equalsIgnoreAsciiCase("XTEXT_PAINTRECT") )
+ {
+ o_rPaintRect = vcl::unotools::b2DRectangleFromRectangle(
+ *reinterpret_cast<tools::Rectangle const *>(
+ pAct->GetData() ));
+
+ bPaintRectSet = true;
+ }
+ }
+ }
+ }
+
+ return bScrollRectSet && bPaintRectSet;
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/slideshow/source/engine/shapes/gdimtftools.hxx b/slideshow/source/engine/shapes/gdimtftools.hxx
new file mode 100644
index 000000000..7812301f9
--- /dev/null
+++ b/slideshow/source/engine/shapes/gdimtftools.hxx
@@ -0,0 +1,129 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#ifndef INCLUDED_SLIDESHOW_SOURCE_ENGINE_SHAPES_GDIMTFTOOLS_HXX
+#define INCLUDED_SLIDESHOW_SOURCE_ENGINE_SHAPES_GDIMTFTOOLS_HXX
+
+#include <com/sun/star/uno/Reference.hxx>
+#include <com/sun/star/uno/XComponentContext.hpp>
+#include <com/sun/star/lang/XComponent.hpp>
+#include <com/sun/star/drawing/XDrawPage.hpp>
+
+#include <basegfx/range/b2drectangle.hxx>
+
+#include <tools.hxx>
+
+#include <vector>
+
+class MetaAction;
+class GDIMetaFile;
+class Graphic;
+
+
+namespace slideshow::internal
+ {
+ /// meta file loading specialities:
+ enum mtf_load_flags {
+ /// no flags
+ MTF_LOAD_NONE = 0,
+ /// the source of the metafile might be a foreign
+ /// application. The metafile is checked against unsupported
+ /// content, and, if necessary, returned as a pre-rendered
+ /// bitmap.
+ MTF_LOAD_FOREIGN_SOURCE = 2,
+ /// retrieve a meta file for the page background only
+ MTF_LOAD_BACKGROUND_ONLY = 4,
+ /// retrieve the drawing layer scroll text metafile
+ MTF_LOAD_SCROLL_TEXT_MTF = 8
+ };
+
+ // Animation info
+ // ==============
+
+ struct MtfAnimationFrame
+ {
+ MtfAnimationFrame( const GDIMetaFileSharedPtr& rMtf,
+ double nDuration ) :
+ mpMtf( rMtf ),
+ mnDuration( nDuration )
+ {
+ }
+
+ /// Enables STL algos to be used for duration extraction
+ double getDuration() const
+ {
+ return mnDuration;
+ }
+
+ GDIMetaFileSharedPtr mpMtf;
+ double mnDuration;
+ };
+
+ typedef ::std::vector< MtfAnimationFrame > VectorOfMtfAnimationFrames;
+
+
+ /** Retrieve a meta file for the given shape
+
+ @param xShape
+ XShape to retrieve a metafile for.
+
+ @param xContainingPage
+ The page that contains this shape. Needed for proper
+ import (currently, the UnoGraphicExporter needs this
+ information).
+
+ */
+ GDIMetaFileSharedPtr getMetaFile( const css::uno::Reference< css::lang::XComponent >& xSource,
+ const css::uno::Reference< css::drawing::XDrawPage >& xContainingPage,
+ int mtfLoadFlags,
+ const css::uno::Reference< css::uno::XComponentContext >& rxContext );
+
+ /** Gets the next action offset for iterating meta actions which is most
+ often returns 1.
+ */
+ sal_Int32 getNextActionOffset( MetaAction * pCurrAct );
+
+ /** Extract a vector of animation frames from given Graphic.
+
+ @param o_rFrames
+ Resulting vector of animated metafiles
+
+ @param o_rLoopCount
+ Number of times the bitmap animation shall be repeated
+
+ @param rGraphic
+ Input graphic object, to extract animations from
+ */
+ bool getAnimationFromGraphic(VectorOfMtfAnimationFrames& o_rFrames,
+ sal_uInt32& o_rLoopCount,
+ const Graphic& rGraphic);
+
+ /** Retrieve scroll text animation rectangles from given metafile
+
+ @return true, if both rectangles have been found, false
+ otherwise.
+ */
+ bool getRectanglesFromScrollMtf( ::basegfx::B2DRectangle& o_rScrollRect,
+ ::basegfx::B2DRectangle& o_rPaintRect,
+ const GDIMetaFileSharedPtr& rMtf );
+}
+
+#endif // INCLUDED_SLIDESHOW_SOURCE_ENGINE_SHAPES_GDIMTFTOOLS_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/slideshow/source/engine/shapes/intrinsicanimationactivity.cxx b/slideshow/source/engine/shapes/intrinsicanimationactivity.cxx
new file mode 100644
index 000000000..bcc353e0d
--- /dev/null
+++ b/slideshow/source/engine/shapes/intrinsicanimationactivity.cxx
@@ -0,0 +1,249 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#include <tools/diagnose_ex.h>
+
+#include <subsettableshapemanager.hxx>
+#include <eventqueue.hxx>
+#include "intrinsicanimationactivity.hxx"
+#include <intrinsicanimationeventhandler.hxx>
+
+#include <memory>
+
+namespace slideshow::internal
+{
+ namespace {
+
+ /** Activity for intrinsic shape animations
+
+ This is an Activity interface implementation for intrinsic
+ shape animations. Intrinsic shape animations are
+ animations directly within a shape, e.g. drawing layer
+ animations, or GIF animations.
+ */
+ class IntrinsicAnimationActivity : public Activity
+ {
+ public:
+ /** Create an IntrinsicAnimationActivity.
+
+ @param rContext
+ Common slideshow objects
+
+ @param rDrawShape
+ Shape to control the intrinsic animation for
+
+ @param rWakeupEvent
+ Externally generated wakeup event, to set this
+ activity to sleep during inter-frame intervals. Must
+ come from the outside, since wakeup event and this
+ object have mutual references to each other.
+
+ @param rTimeouts
+ Vector of timeout values, to wait before the next
+ frame is shown.
+ */
+ IntrinsicAnimationActivity( const SlideShowContext& rContext,
+ const DrawShapeSharedPtr& rDrawShape,
+ const WakeupEventSharedPtr& rWakeupEvent,
+ ::std::vector<double>&& rTimeouts,
+ ::std::size_t nNumLoops );
+ IntrinsicAnimationActivity(const IntrinsicAnimationActivity&) = delete;
+ IntrinsicAnimationActivity& operator=(const IntrinsicAnimationActivity&) = delete;
+
+ virtual void dispose() override;
+ virtual double calcTimeLag() const override;
+ virtual bool perform() override;
+ virtual bool isActive() const override;
+ virtual void dequeued() override;
+ virtual void end() override;
+
+ bool enableAnimations();
+
+ private:
+ SlideShowContext maContext;
+ std::weak_ptr<DrawShape> mpDrawShape;
+ WakeupEventSharedPtr mpWakeupEvent;
+ IntrinsicAnimationEventHandlerSharedPtr mpListener;
+ ::std::vector<double> maTimeouts;
+ ::std::size_t mnCurrIndex;
+ ::std::size_t mnNumLoops;
+ ::std::size_t mnLoopCount;
+ bool mbIsActive;
+ };
+
+
+ class IntrinsicAnimationListener : public IntrinsicAnimationEventHandler
+ {
+ public:
+ explicit IntrinsicAnimationListener( IntrinsicAnimationActivity& rActivity ) :
+ mrActivity( rActivity )
+ {}
+ IntrinsicAnimationListener(const IntrinsicAnimationListener&) = delete;
+ IntrinsicAnimationListener& operator=(const IntrinsicAnimationListener&) = delete;
+
+ private:
+
+ virtual bool enableAnimations() override { return mrActivity.enableAnimations(); }
+ virtual bool disableAnimations() override { mrActivity.end(); return true; }
+
+ IntrinsicAnimationActivity& mrActivity;
+ };
+
+ }
+
+ IntrinsicAnimationActivity::IntrinsicAnimationActivity( const SlideShowContext& rContext,
+ const DrawShapeSharedPtr& rDrawShape,
+ const WakeupEventSharedPtr& rWakeupEvent,
+ ::std::vector<double>&& rTimeouts,
+ ::std::size_t nNumLoops ) :
+ maContext( rContext ),
+ mpDrawShape( rDrawShape ),
+ mpWakeupEvent( rWakeupEvent ),
+ mpListener( std::make_shared<IntrinsicAnimationListener>(*this) ),
+ maTimeouts( std::move(rTimeouts) ),
+ mnCurrIndex(0),
+ mnNumLoops(nNumLoops),
+ mnLoopCount(0),
+ mbIsActive(false)
+ {
+ ENSURE_OR_THROW( rContext.mpSubsettableShapeManager,
+ "IntrinsicAnimationActivity::IntrinsicAnimationActivity(): Invalid shape manager" );
+ ENSURE_OR_THROW( rDrawShape,
+ "IntrinsicAnimationActivity::IntrinsicAnimationActivity(): Invalid draw shape" );
+ ENSURE_OR_THROW( rWakeupEvent,
+ "IntrinsicAnimationActivity::IntrinsicAnimationActivity(): Invalid wakeup event" );
+ ENSURE_OR_THROW( !maTimeouts.empty(),
+ "IntrinsicAnimationActivity::IntrinsicAnimationActivity(): Empty timeout vector" );
+
+ maContext.mpSubsettableShapeManager->addIntrinsicAnimationHandler(
+ mpListener );
+ }
+
+ void IntrinsicAnimationActivity::dispose()
+ {
+ end();
+
+ if( mpWakeupEvent )
+ mpWakeupEvent->dispose();
+
+ maContext.dispose();
+ mpDrawShape.reset();
+ mpWakeupEvent.reset();
+ maTimeouts.clear();
+ mnCurrIndex = 0;
+
+ maContext.mpSubsettableShapeManager->removeIntrinsicAnimationHandler(
+ mpListener );
+ }
+
+ double IntrinsicAnimationActivity::calcTimeLag() const
+ {
+ return 0.0;
+ }
+
+ bool IntrinsicAnimationActivity::perform()
+ {
+ if( !isActive() )
+ return false;
+
+ DrawShapeSharedPtr pDrawShape( mpDrawShape.lock() );
+ if( !pDrawShape || !mpWakeupEvent )
+ {
+ // event or draw shape vanished, no sense living on ->
+ // commit suicide.
+ dispose();
+ return false;
+ }
+
+ const ::std::size_t nNumFrames(maTimeouts.size());
+
+ // mnNumLoops == 0 means infinite looping
+ if( mnNumLoops != 0 &&
+ mnLoopCount >= mnNumLoops )
+ {
+ // #i55294# After finishing the loops, display the last frame
+ // powerpoint 2013 and firefox etc show the last frame when
+ // the animation ends
+ pDrawShape->setIntrinsicAnimationFrame(nNumFrames - 1);
+ maContext.mpSubsettableShapeManager->notifyShapeUpdate( pDrawShape );
+
+ end();
+
+ return false;
+ }
+
+ ::std::size_t nNewIndex = 0;
+
+ pDrawShape->setIntrinsicAnimationFrame( mnCurrIndex );
+
+ mpWakeupEvent->start();
+ mpWakeupEvent->setNextTimeout( maTimeouts[mnCurrIndex] );
+
+ mnLoopCount += (mnCurrIndex + 1) / nNumFrames;
+ nNewIndex = (mnCurrIndex + 1) % nNumFrames;
+
+ maContext.mrEventQueue.addEvent( mpWakeupEvent );
+ maContext.mpSubsettableShapeManager->notifyShapeUpdate( pDrawShape );
+ mnCurrIndex = nNewIndex;
+
+ return false; // don't reinsert, WakeupEvent will perform
+ // that after the given timeout
+ }
+
+ bool IntrinsicAnimationActivity::isActive() const
+ {
+ return mbIsActive;
+ }
+
+ void IntrinsicAnimationActivity::dequeued()
+ {
+ // not used here
+ }
+
+ void IntrinsicAnimationActivity::end()
+ {
+ // there is no dedicated end state, just become inactive:
+ mbIsActive = false;
+ }
+
+ bool IntrinsicAnimationActivity::enableAnimations()
+ {
+ mbIsActive = true;
+ return maContext.mrActivitiesQueue.addActivity( std::dynamic_pointer_cast<Activity>(shared_from_this()) );
+
+ }
+
+
+ ActivitySharedPtr createIntrinsicAnimationActivity(
+ const SlideShowContext& rContext,
+ const DrawShapeSharedPtr& rDrawShape,
+ const WakeupEventSharedPtr& rWakeupEvent,
+ ::std::vector<double>&& rTimeouts,
+ sal_uInt32 nNumLoops)
+ {
+ return std::make_shared<IntrinsicAnimationActivity>(rContext,
+ rDrawShape,
+ rWakeupEvent,
+ std::move(rTimeouts),
+ nNumLoops);
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/slideshow/source/engine/shapes/intrinsicanimationactivity.hxx b/slideshow/source/engine/shapes/intrinsicanimationactivity.hxx
new file mode 100644
index 000000000..6933c7cff
--- /dev/null
+++ b/slideshow/source/engine/shapes/intrinsicanimationactivity.hxx
@@ -0,0 +1,66 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#ifndef INCLUDED_SLIDESHOW_SOURCE_ENGINE_SHAPES_INTRINSICANIMATIONACTIVITY_HXX
+#define INCLUDED_SLIDESHOW_SOURCE_ENGINE_SHAPES_INTRINSICANIMATIONACTIVITY_HXX
+
+#include <wakeupevent.hxx>
+#include <activity.hxx>
+#include <slideshowcontext.hxx>
+#include "drawshape.hxx"
+
+/* Definition of IntrinsicAnimationActivity class */
+
+namespace slideshow::internal
+ {
+ /** Create an IntrinsicAnimationActivity.
+
+ This is an Activity interface implementation for intrinsic
+ shape animations. Intrinsic shape animations are
+ animations directly within a shape, e.g. drawing layer
+ animations, or GIF animations.
+
+ @param rContext
+ Common slideshow objects
+
+ @param rDrawShape
+ Shape to control the intrinsic animation for
+
+ @param rWakeupEvent
+ Externally generated wakeup event, to set this
+ activity to sleep during inter-frame intervals. Must
+ come from the outside, since wakeup event and this
+ object have mutual references to each other.
+
+ @param rTimeouts
+ Vector of timeout values, to wait before the next
+ frame is shown.
+ */
+ ActivitySharedPtr createIntrinsicAnimationActivity(
+ const SlideShowContext& rContext,
+ const DrawShapeSharedPtr& rDrawShape,
+ const WakeupEventSharedPtr& rWakeupEvent,
+ ::std::vector<double>&& rTimeouts,
+ sal_uInt32 nNumLoops);
+
+}
+
+#endif // INCLUDED_SLIDESHOW_SOURCE_ENGINE_SHAPES_INTRINSICANIMATIONACTIVITY_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/slideshow/source/engine/shapes/mediashape.cxx b/slideshow/source/engine/shapes/mediashape.cxx
new file mode 100644
index 000000000..a5cbb926f
--- /dev/null
+++ b/slideshow/source/engine/shapes/mediashape.cxx
@@ -0,0 +1,257 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#include <com/sun/star/drawing/XShape.hpp>
+#include <o3tl/safeint.hxx>
+#include <osl/diagnose.h>
+
+#include "mediashape.hxx"
+#include "viewmediashape.hxx"
+#include "externalshapebase.hxx"
+#include <slideshowcontext.hxx>
+#include <shape.hxx>
+#include <tools.hxx>
+
+#include <algorithm>
+
+
+using namespace ::com::sun::star;
+
+
+namespace slideshow::internal
+{
+ namespace {
+
+ /** Represents a media shape.
+
+ This implementation offers support for media shapes.
+ Such shapes need special treatment.
+ */
+ class MediaShape : public ExternalShapeBase
+ {
+ public:
+ /** Create a shape for the given XShape for a media object
+
+ @param xShape
+ The XShape to represent.
+
+ @param nPrio
+ Externally-determined shape priority (used e.g. for
+ paint ordering). This number _must be_ unique!
+ */
+ MediaShape( const css::uno::Reference< css::drawing::XShape >& xShape,
+ double nPrio,
+ const SlideShowContext& rContext ); // throw ShapeLoadFailedException;
+
+ private:
+
+ // View layer methods
+
+
+ virtual void addViewLayer( const ViewLayerSharedPtr& rNewLayer,
+ bool bRedrawLayer ) override;
+ virtual bool removeViewLayer( const ViewLayerSharedPtr& rNewLayer ) override;
+ virtual void clearAllViewLayers() override;
+
+
+ // ExternalShapeBase methods
+
+
+ virtual bool implRender( const ::basegfx::B2DRange& rCurrBounds ) const override;
+ virtual void implViewChanged( const UnoViewSharedPtr& rView ) override;
+ virtual void implViewsChanged() override;
+ virtual bool implStartIntrinsicAnimation() override;
+ virtual bool implEndIntrinsicAnimation() override;
+ virtual void implPauseIntrinsicAnimation() override;
+ virtual bool implIsIntrinsicAnimationPlaying() const override;
+ virtual void implSetIntrinsicAnimationTime(double) override;
+ void implSetLooping(bool bLooping) override;
+
+ /// the list of active view shapes (one for each registered view layer)
+ typedef ::std::vector< ViewMediaShapeSharedPtr > ViewMediaShapeVector;
+ ViewMediaShapeVector maViewMediaShapes;
+ bool mbIsPlaying;
+ };
+
+ }
+
+ MediaShape::MediaShape( const uno::Reference< drawing::XShape >& xShape,
+ double nPrio,
+ const SlideShowContext& rContext ) :
+ ExternalShapeBase( xShape, nPrio, rContext ),
+ maViewMediaShapes(),
+ mbIsPlaying(false)
+ {
+ }
+
+
+ void MediaShape::implViewChanged( const UnoViewSharedPtr& rView )
+ {
+ const ::basegfx::B2DRectangle& rBounds = getBounds();
+ // determine ViewMediaShape that needs update
+ for( const auto& pViewMediaShape : maViewMediaShapes )
+ if( pViewMediaShape->getViewLayer()->isOnView( rView ) )
+ pViewMediaShape->resize( rBounds );
+ }
+
+
+ void MediaShape::implViewsChanged()
+ {
+ const ::basegfx::B2DRectangle& rBounds = getBounds();
+ // resize all ViewShapes
+ for( const auto& pViewMediaShape : maViewMediaShapes )
+ pViewMediaShape->resize( rBounds );
+ }
+
+
+ void MediaShape::addViewLayer( const ViewLayerSharedPtr& rNewLayer,
+ bool bRedrawLayer )
+ {
+ maViewMediaShapes.push_back(
+ std::make_shared<ViewMediaShape>( rNewLayer,
+ getXShape(),
+ mxComponentContext ));
+
+ // push new size to view shape
+ maViewMediaShapes.back()->resize( getBounds() );
+
+ // render the Shape on the newly added ViewLayer
+ if( bRedrawLayer )
+ maViewMediaShapes.back()->render( getBounds() );
+ }
+
+
+ bool MediaShape::removeViewLayer( const ViewLayerSharedPtr& rLayer )
+ {
+ const ViewMediaShapeVector::iterator aEnd( maViewMediaShapes.end() );
+
+ OSL_ENSURE( ::std::count_if(maViewMediaShapes.begin(),
+ aEnd,
+ [&rLayer]
+ ( const ViewMediaShapeSharedPtr& pShape )
+ { return rLayer == pShape->getViewLayer(); } ) < 2,
+ "MediaShape::removeViewLayer(): Duplicate ViewLayer entries!" );
+
+ ViewMediaShapeVector::iterator aIter;
+
+ if( (aIter=::std::remove_if( maViewMediaShapes.begin(),
+ aEnd,
+ [&rLayer]
+ ( const ViewMediaShapeSharedPtr& pShape )
+ { return rLayer == pShape->getViewLayer(); } ) ) == aEnd )
+ {
+ // view layer seemingly was not added, failed
+ return false;
+ }
+
+ // actually erase from container
+ maViewMediaShapes.erase( aIter, aEnd );
+
+ return true;
+ }
+
+
+ void MediaShape::clearAllViewLayers()
+ {
+ maViewMediaShapes.clear();
+ }
+
+
+ bool MediaShape::implRender( const ::basegfx::B2DRange& rCurrBounds ) const
+ {
+ // redraw all view shapes, by calling their update() method
+ if( o3tl::make_unsigned(::std::count_if( maViewMediaShapes.begin(),
+ maViewMediaShapes.end(),
+ [&rCurrBounds]
+ ( const ViewMediaShapeSharedPtr& pShape )
+ { return pShape->render( rCurrBounds ); } ))
+ != maViewMediaShapes.size() )
+ {
+ // at least one of the ViewShape::update() calls did return
+ // false - update failed on at least one ViewLayer
+ return false;
+ }
+
+ return true;
+ }
+
+
+ bool MediaShape::implStartIntrinsicAnimation()
+ {
+ for( const auto& pViewMediaShape : maViewMediaShapes )
+ pViewMediaShape->startMedia();
+
+ mbIsPlaying = true;
+
+ return true;
+ }
+
+
+ bool MediaShape::implEndIntrinsicAnimation()
+ {
+ for( const auto& pViewMediaShape : maViewMediaShapes )
+ pViewMediaShape->endMedia();
+
+ mbIsPlaying = false;
+
+ return true;
+ }
+
+
+ void MediaShape::implPauseIntrinsicAnimation()
+ {
+ for( const auto& pViewMediaShape : maViewMediaShapes )
+ pViewMediaShape->pauseMedia();
+
+ mbIsPlaying = false;
+ }
+
+
+ bool MediaShape::implIsIntrinsicAnimationPlaying() const
+ {
+ return mbIsPlaying;
+ }
+
+
+ void MediaShape::implSetIntrinsicAnimationTime(double fTime)
+ {
+ for( const auto& pViewMediaShape : maViewMediaShapes )
+ pViewMediaShape->setMediaTime( fTime );
+ }
+
+ void MediaShape::implSetLooping(bool bLooping)
+ {
+ for (const auto& pViewMediaShape : maViewMediaShapes)
+ {
+ pViewMediaShape->setLooping(bLooping);
+ }
+ }
+
+ ShapeSharedPtr createMediaShape(
+ const uno::Reference< drawing::XShape >& xShape,
+ double nPrio,
+ const SlideShowContext& rContext)
+ {
+ return std::make_shared<MediaShape>(xShape, nPrio, rContext);
+ }
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/slideshow/source/engine/shapes/mediashape.hxx b/slideshow/source/engine/shapes/mediashape.hxx
new file mode 100644
index 000000000..4b2a542ee
--- /dev/null
+++ b/slideshow/source/engine/shapes/mediashape.hxx
@@ -0,0 +1,44 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#ifndef INCLUDED_SLIDESHOW_SOURCE_ENGINE_SHAPES_MEDIASHAPE_HXX
+#define INCLUDED_SLIDESHOW_SOURCE_ENGINE_SHAPES_MEDIASHAPE_HXX
+
+#include <com/sun/star/uno/Reference.hxx>
+#include <memory>
+
+
+namespace com::sun::star::drawing { class XShape; }
+
+namespace slideshow::internal
+{
+ struct SlideShowContext;
+ class Shape;
+ typedef ::std::shared_ptr< Shape > ShapeSharedPtr;
+
+ ShapeSharedPtr createMediaShape(
+ const css::uno::Reference<css::drawing::XShape >& xShape,
+ double nPrio,
+ const SlideShowContext& rContext);
+
+}
+
+#endif // INCLUDED_SLIDESHOW_SOURCE_ENGINE_SHAPES_MEDIASHAPE_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/slideshow/source/engine/shapes/shapeimporter.cxx b/slideshow/source/engine/shapes/shapeimporter.cxx
new file mode 100644
index 000000000..d896caa55
--- /dev/null
+++ b/slideshow/source/engine/shapes/shapeimporter.cxx
@@ -0,0 +1,537 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <vcl/GraphicObject.hxx>
+#include <basegfx/point/b2dpoint.hxx>
+#include <basegfx/polygon/b2dpolygon.hxx>
+#include <cppcanvas/basegfxfactory.hxx>
+#include <cppcanvas/polypolygon.hxx>
+#include <com/sun/star/awt/Rectangle.hpp>
+#include <com/sun/star/drawing/ColorMode.hpp>
+#include <com/sun/star/text/GraphicCrop.hpp>
+#include <com/sun/star/drawing/PointSequenceSequence.hpp>
+#include <com/sun/star/drawing/PointSequence.hpp>
+#include <com/sun/star/drawing/XLayerSupplier.hpp>
+#include <com/sun/star/drawing/XLayerManager.hpp>
+#include <com/sun/star/graphic/XGraphic.hpp>
+#include <com/sun/star/container/XNameAccess.hpp>
+#include <com/sun/star/drawing/XDrawPagesSupplier.hpp>
+
+#include "drawshape.hxx"
+#include "backgroundshape.hxx"
+#include "mediashape.hxx"
+#include "appletshape.hxx"
+#include <shapeimporter.hxx>
+#include <slideshowexceptions.hxx>
+#include <tools.hxx>
+#include <slideshowcontext.hxx>
+#include <unoviewcontainer.hxx>
+
+#include <memory>
+
+using namespace com::sun::star;
+
+namespace slideshow::internal {
+
+namespace {
+
+std::unique_ptr<GraphicObject> importShapeGraphic(uno::Reference<beans::XPropertySet> const& xPropSet)
+{
+ std::unique_ptr<GraphicObject> xRet;
+
+ uno::Reference<graphic::XGraphic> xGraphic;
+ if (!getPropertyValue(xGraphic, xPropSet, "Graphic") || !xGraphic.is())
+ {
+ // no or empty property - cannot import shape graphic
+ return xRet;
+ }
+
+ Graphic aGraphic(xGraphic);
+ xRet.reset(new GraphicObject(aGraphic));
+
+ if (GraphicType::Default == xRet->GetType() || GraphicType::NONE == xRet->GetType())
+ {
+ xRet.reset();
+ }
+ return xRet;
+}
+
+/** This shape implementation just acts as a dummy for the layermanager.
+ Its sole role is for hit test detection of group shapes.
+*/
+class ShapeOfGroup : public Shape
+{
+public:
+ ShapeOfGroup( ShapeSharedPtr const& pGroupShape,
+ uno::Reference<drawing::XShape> const& xShape,
+ uno::Reference<beans::XPropertySet> const& xPropSet,
+ double nPrio );
+
+ // Shape:
+ virtual uno::Reference<drawing::XShape> getXShape() const override;
+ virtual void addViewLayer( ViewLayerSharedPtr const& pNewLayer,
+ bool bRedrawLayer ) override;
+ virtual bool removeViewLayer( ViewLayerSharedPtr const& pNewLayer ) override;
+ virtual void clearAllViewLayers() override;
+ virtual bool update() const override;
+ virtual bool render() const override;
+ virtual bool isContentChanged() const override;
+ virtual basegfx::B2DRectangle getBounds() const override;
+ virtual basegfx::B2DRectangle getDomBounds() const override;
+ virtual basegfx::B2DRectangle getUpdateArea() const override;
+ virtual bool isVisible() const override;
+ virtual double getPriority() const override;
+ virtual bool isBackgroundDetached() const override;
+
+private:
+ ShapeSharedPtr const mpGroupShape;
+ uno::Reference<drawing::XShape> const mxShape;
+ double const mnPrio;
+ basegfx::B2DPoint maPosOffset;
+ double mnWidth;
+ double mnHeight;
+};
+
+ShapeOfGroup::ShapeOfGroup( ShapeSharedPtr const& pGroupShape,
+ uno::Reference<drawing::XShape> const& xShape,
+ uno::Reference<beans::XPropertySet> const& xPropSet,
+ double nPrio ) :
+ mpGroupShape(pGroupShape),
+ mxShape(xShape),
+ mnPrio(nPrio)
+{
+ // read bound rect
+ uno::Any const aTmpRect_( xPropSet->getPropertyValue( "BoundRect" ));
+ awt::Rectangle const aTmpRect( aTmpRect_.get<awt::Rectangle>() );
+ basegfx::B2DRectangle const groupPosSize( pGroupShape->getBounds() );
+ maPosOffset = basegfx::B2DPoint( aTmpRect.X - groupPosSize.getMinX(),
+ aTmpRect.Y - groupPosSize.getMinY() );
+ mnWidth = aTmpRect.Width;
+ mnHeight = aTmpRect.Height;
+}
+
+uno::Reference<drawing::XShape> ShapeOfGroup::getXShape() const
+{
+ return mxShape;
+}
+
+void ShapeOfGroup::addViewLayer( ViewLayerSharedPtr const& /*pNewLayer*/,
+ bool /*bRedrawLayer*/ )
+{
+}
+
+bool ShapeOfGroup::removeViewLayer( ViewLayerSharedPtr const& /*pNewLayer*/ )
+{
+ return true;
+}
+
+void ShapeOfGroup::clearAllViewLayers()
+{
+}
+
+bool ShapeOfGroup::update() const
+{
+ return true;
+}
+
+bool ShapeOfGroup::render() const
+{
+ return true;
+}
+
+bool ShapeOfGroup::isContentChanged() const
+{
+ return false;
+}
+
+basegfx::B2DRectangle ShapeOfGroup::getBounds() const
+{
+ basegfx::B2DRectangle const groupPosSize( mpGroupShape->getBounds() );
+ double const posX = groupPosSize.getMinX() + maPosOffset.getX();
+ double const posY = groupPosSize.getMinY() + maPosOffset.getY();
+ return basegfx::B2DRectangle( posX, posY, posX + mnWidth, posY + mnHeight );
+}
+
+basegfx::B2DRectangle ShapeOfGroup::getDomBounds() const
+{
+ return getBounds();
+}
+
+basegfx::B2DRectangle ShapeOfGroup::getUpdateArea() const
+{
+ return getBounds();
+}
+
+bool ShapeOfGroup::isVisible() const
+{
+ return mpGroupShape->isVisible();
+}
+
+double ShapeOfGroup::getPriority() const
+{
+ return mnPrio;
+}
+
+bool ShapeOfGroup::isBackgroundDetached() const
+{
+ return false;
+}
+
+} // anon namespace
+
+ShapeSharedPtr ShapeImporter::createShape(
+ uno::Reference<drawing::XShape> const& xCurrShape,
+ uno::Reference<beans::XPropertySet> const& xPropSet,
+ std::u16string_view shapeType ) const
+{
+ if( shapeType == u"com.sun.star.drawing.MediaShape" || shapeType == u"com.sun.star.presentation.MediaShape" )
+ {
+ // Media shape (video etc.). This is a special object
+ return createMediaShape(xCurrShape,
+ mnAscendingPrio,
+ mrContext);
+ }
+ else if( shapeType == u"com.sun.star.drawing.AppletShape" )
+ {
+ // PropertyValues to copy from XShape to applet
+ static const char* aPropertyValues[] =
+ {
+ "AppletCodeBase",
+ "AppletName",
+ "AppletCode",
+ "AppletCommands",
+ "AppletIsScript"
+ };
+
+ // (Java)Applet shape. This is a special object
+ return createAppletShape( xCurrShape,
+ mnAscendingPrio,
+ "com.sun.star.comp.sfx2.AppletObject",
+ aPropertyValues,
+ SAL_N_ELEMENTS(aPropertyValues),
+ mrContext );
+ }
+ else if( shapeType == u"com.sun.star.drawing.OLE2Shape" || shapeType == u"com.sun.star.presentation.OLE2Shape" )
+ {
+ // #i46224# Mark OLE shapes as foreign content - scan them for
+ // unsupported actions, and fallback to bitmap, if necessary
+ return DrawShape::create( xCurrShape,
+ mxPage,
+ mnAscendingPrio,
+ true,
+ mrContext );
+ }
+ else if( shapeType == u"com.sun.star.drawing.GraphicObjectShape" || shapeType == u"com.sun.star.presentation.GraphicObjectShape" )
+ {
+ // to get hold of GIF animations, inspect Graphic
+ // objects more thoroughly (the plain-jane shape
+ // metafile of course would only contain the first
+ // animation frame)
+ std::unique_ptr<GraphicObject> xGraphicObject(importShapeGraphic(xPropSet));
+ if (!xGraphicObject)
+ return ShapeSharedPtr(); // error loading graphic -
+ // no placeholders in
+ // slideshow
+
+ if (!xGraphicObject->IsAnimated())
+ {
+ // no animation - simply utilize plain draw shape import
+
+ // import shape as bitmap - either it's a bitmap
+ // anyway, or it's a metafile, which currently the
+ // metafile renderer might not display correctly.
+ return DrawShape::create( xCurrShape,
+ mxPage,
+ mnAscendingPrio,
+ true,
+ mrContext );
+ }
+
+
+ // now extract relevant shape attributes via API
+
+
+ drawing::ColorMode eColorMode( drawing::ColorMode_STANDARD );
+ sal_Int16 nLuminance(0);
+ sal_Int16 nContrast(0);
+ sal_Int16 nRed(0);
+ sal_Int16 nGreen(0);
+ sal_Int16 nBlue(0);
+ double nGamma(1.0);
+ sal_Int16 nTransparency(0);
+ sal_Int32 nRotation(0);
+
+ getPropertyValue( eColorMode, xPropSet, "GraphicColorMode" );
+ getPropertyValue( nLuminance, xPropSet, "AdjustLuminance" );
+ getPropertyValue( nContrast, xPropSet, "AdjustContrast" );
+ getPropertyValue( nRed, xPropSet, "AdjustRed" );
+ getPropertyValue( nGreen, xPropSet, "AdjustGreen" );
+ getPropertyValue( nBlue, xPropSet, "AdjustBlue" );
+ getPropertyValue( nGamma, xPropSet, "Gamma" );
+ getPropertyValue( nTransparency, xPropSet, "Transparency" );
+ getPropertyValue( nRotation, xPropSet, "RotateAngle" );
+
+ GraphicAttr aGraphAttrs;
+ aGraphAttrs.SetDrawMode( static_cast<GraphicDrawMode>(eColorMode) );
+ aGraphAttrs.SetLuminance( nLuminance );
+ aGraphAttrs.SetContrast( nContrast );
+ aGraphAttrs.SetChannelR( nRed );
+ aGraphAttrs.SetChannelG( nGreen );
+ aGraphAttrs.SetChannelB( nBlue );
+ aGraphAttrs.SetGamma( nGamma );
+ aGraphAttrs.SetAlpha( 255 - static_cast<sal_uInt8>(nTransparency) );
+ aGraphAttrs.SetRotation( Degree10(static_cast<sal_Int16>(nRotation*10)) );
+
+ text::GraphicCrop aGraphCrop;
+ if( getPropertyValue( aGraphCrop, xPropSet, "GraphicCrop" ))
+ {
+ aGraphAttrs.SetCrop( aGraphCrop.Left,
+ aGraphCrop.Top,
+ aGraphCrop.Right,
+ aGraphCrop.Bottom );
+ }
+
+ // fetch readily transformed and color-modified
+ // graphic
+
+
+ Graphic aGraphic(
+ xGraphicObject->GetTransformedGraphic(
+ xGraphicObject->GetPrefSize(),
+ xGraphicObject->GetPrefMapMode(),
+ aGraphAttrs ) );
+
+ return DrawShape::create( xCurrShape,
+ mxPage,
+ mnAscendingPrio,
+ aGraphic,
+ mrContext );
+ }
+ else
+ {
+ return DrawShape::create( xCurrShape,
+ mxPage,
+ mnAscendingPrio,
+ false,
+ mrContext );
+ }
+}
+
+bool ShapeImporter::isSkip(
+ uno::Reference<beans::XPropertySet> const& xPropSet,
+ std::u16string_view shapeType,
+ uno::Reference< drawing::XLayer> const& xLayer )
+{
+ // skip empty presentation objects:
+ bool bEmpty = false;
+ if( getPropertyValue( bEmpty,
+ xPropSet,
+ "IsEmptyPresentationObject") &&
+ bEmpty )
+ {
+ return true;
+ }
+
+ //skip shapes which corresponds to annotations
+ if(xLayer.is())
+ {
+ OUString layerName;
+ const uno::Any& a(xLayer->getPropertyValue("Name") );
+ bool const bRet = (a >>= layerName);
+ if(bRet)
+ {
+ if( layerName == "DrawnInSlideshow" )
+ {
+ //Transform shapes into PolyPolygons
+ importPolygons(xPropSet);
+
+ return true;
+ }
+ }
+ }
+
+ // don't export presentation placeholders on masterpage
+ // they can be non empty when user edits the default texts
+ if(mbConvertingMasterPage)
+ {
+ if( shapeType == u"com.sun.star.presentation.TitleTextShape" || shapeType == u"com.sun.star.presentation.OutlinerShape" )
+ {
+ return true;
+ }
+ }
+ return false;
+}
+
+
+void ShapeImporter::importPolygons(uno::Reference<beans::XPropertySet> const& xPropSet) {
+
+ drawing::PointSequenceSequence aRetval;
+ sal_Int32 nLineColor=0;
+ double fLineWidth;
+ getPropertyValue( aRetval, xPropSet, "PolyPolygon" );
+ getPropertyValue( nLineColor, xPropSet, "LineColor" );
+ getPropertyValue( fLineWidth, xPropSet, "LineWidth" );
+
+ const drawing::PointSequence* pOuterSequence = aRetval.getArray();
+
+ ::basegfx::B2DPolygon aPoly;
+ basegfx::B2DPoint aPoint;
+ for( const awt::Point& rPoint : *pOuterSequence )
+ {
+ aPoint.setX(rPoint.X);
+ aPoint.setY(rPoint.Y);
+ aPoly.append( aPoint );
+ }
+ for( const auto& pView : mrContext.mrViewContainer )
+ {
+ ::cppcanvas::PolyPolygonSharedPtr pPolyPoly(
+ ::cppcanvas::BaseGfxFactory::createPolyPolygon( pView->getCanvas(),
+ aPoly ) );
+ if( pPolyPoly )
+ {
+ pPolyPoly->setRGBALineColor( unoColor2RGBColor( nLineColor ).getIntegerColor() );
+ pPolyPoly->setStrokeWidth(fLineWidth);
+ pPolyPoly->draw();
+ maPolygons.push_back(pPolyPoly);
+ }
+ }
+}
+
+ShapeSharedPtr ShapeImporter::importBackgroundShape() // throw (ShapeLoadFailedException)
+{
+ if( maShapesStack.empty() )
+ throw ShapeLoadFailedException();
+
+ XShapesEntry& rTop = maShapesStack.top();
+ ShapeSharedPtr pBgShape(
+ createBackgroundShape(mxPage,
+ uno::Reference<drawing::XDrawPage>(
+ rTop.mxShapes,
+ uno::UNO_QUERY_THROW),
+ mrContext) );
+ mnAscendingPrio += 1.0;
+
+ return pBgShape;
+}
+
+ShapeSharedPtr ShapeImporter::importShape() // throw (ShapeLoadFailedException)
+{
+ ShapeSharedPtr pRet;
+ bool bIsGroupShape = false;
+
+ while( !maShapesStack.empty() && !pRet )
+ {
+ XShapesEntry& rTop = maShapesStack.top();
+ if( rTop.mnPos < rTop.mnCount )
+ {
+ uno::Reference<drawing::XShape> const xCurrShape(
+ rTop.mxShapes->getByIndex( rTop.mnPos ), uno::UNO_QUERY );
+ ++rTop.mnPos;
+ uno::Reference<beans::XPropertySet> xPropSet(
+ xCurrShape, uno::UNO_QUERY );
+ if( !xPropSet.is() )
+ {
+ // we definitely need the properties of
+ // the shape here. This will also fail,
+ // if getByIndex did not return a valid
+ // shape
+ throw ShapeLoadFailedException();
+ }
+
+ //Retrieve the layer for the current shape
+ uno::Reference< drawing::XLayer > xDrawnInSlideshow;
+
+ uno::Reference< drawing::XLayerSupplier > xLayerSupplier(mxPagesSupplier, uno::UNO_QUERY);
+ if(xLayerSupplier.is())
+ {
+ uno::Reference< container::XNameAccess > xNameAccess = xLayerSupplier->getLayerManager();
+
+ uno::Reference< drawing::XLayerManager > xLayerManager(xNameAccess, uno::UNO_QUERY);
+
+ xDrawnInSlideshow = xLayerManager->getLayerForShape(xCurrShape);
+ }
+
+ OUString const shapeType( xCurrShape->getShapeType());
+
+ // is this shape presentation-invisible?
+ if( !isSkip(xPropSet, shapeType, xDrawnInSlideshow) )
+ {
+ bIsGroupShape = shapeType == "com.sun.star.drawing.GroupShape";
+
+ if( rTop.mpGroupShape ) // in group particle mode?
+ {
+ pRet = std::make_shared<ShapeOfGroup>(
+ rTop.mpGroupShape /* container shape */,
+ xCurrShape, xPropSet,
+ mnAscendingPrio );
+ }
+ else
+ {
+ pRet = createShape( xCurrShape, xPropSet, shapeType );
+ }
+ mnAscendingPrio += 1.0;
+ }
+ }
+ if( rTop.mnPos >= rTop.mnCount )
+ {
+ // group or top-level shapes finished:
+ maShapesStack.pop();
+ }
+ if( bIsGroupShape && pRet )
+ {
+ // push new group on the stack: group traversal
+ maShapesStack.push( XShapesEntry( pRet ) );
+ }
+ }
+
+ return pRet;
+}
+
+bool ShapeImporter::isImportDone() const
+{
+ return maShapesStack.empty();
+}
+
+const PolyPolygonVector& ShapeImporter::getPolygons() const
+{
+ return maPolygons;
+}
+
+ShapeImporter::ShapeImporter( uno::Reference<drawing::XDrawPage> const& xPage,
+ uno::Reference<drawing::XDrawPage> const& xActualPage,
+ uno::Reference<drawing::XDrawPagesSupplier> const& xPagesSupplier,
+ const SlideShowContext& rContext,
+ sal_Int32 nOrdNumStart,
+ bool bConvertingMasterPage ) :
+ mxPage( xActualPage ),
+ mxPagesSupplier( xPagesSupplier ),
+ mrContext( rContext ),
+ maPolygons(),
+ maShapesStack(),
+ mnAscendingPrio( nOrdNumStart ),
+ mbConvertingMasterPage( bConvertingMasterPage )
+{
+ uno::Reference<drawing::XShapes> const xShapes(
+ xPage, uno::UNO_QUERY_THROW );
+ maShapesStack.push( XShapesEntry(xShapes) );
+}
+
+} // namespace presentation
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/slideshow/source/engine/shapes/viewappletshape.cxx b/slideshow/source/engine/shapes/viewappletshape.cxx
new file mode 100644
index 000000000..736cb9d94
--- /dev/null
+++ b/slideshow/source/engine/shapes/viewappletshape.cxx
@@ -0,0 +1,258 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#include <tools/diagnose_ex.h>
+
+#include <basegfx/matrix/b2dhommatrix.hxx>
+#include <basegfx/range/b2irange.hxx>
+#include <basegfx/utils/canvastools.hxx>
+
+#include <cppcanvas/canvas.hxx>
+#include <canvas/canvastools.hxx>
+
+#include <com/sun/star/uno/XComponentContext.hpp>
+#include <com/sun/star/lang/XMultiComponentFactory.hpp>
+#include <com/sun/star/rendering/XCanvas.hpp>
+#include <com/sun/star/beans/XPropertySet.hpp>
+#include <com/sun/star/beans/PropertyValue.hpp>
+#include <com/sun/star/util/XCloseable.hpp>
+#include <com/sun/star/awt/WindowDescriptor.hpp>
+#include <com/sun/star/awt/Toolkit.hpp>
+#include <com/sun/star/awt/XWindow2.hpp>
+#include <com/sun/star/awt/XWindowPeer.hpp>
+#include <com/sun/star/awt/WindowAttribute.hpp>
+#include <com/sun/star/awt/VclWindowPeerAttribute.hpp>
+#include <com/sun/star/awt/PosSize.hpp>
+#include <com/sun/star/frame/Frame.hpp>
+#include <com/sun/star/frame/XSynchronousFrameLoader.hpp>
+
+#include "viewappletshape.hxx"
+#include <tools.hxx>
+
+
+using namespace ::com::sun::star;
+
+namespace slideshow::internal
+{
+ ViewAppletShape::ViewAppletShape( const ViewLayerSharedPtr& rViewLayer,
+ const uno::Reference< drawing::XShape >& rxShape,
+ const OUString& rServiceName,
+ const char** pPropCopyTable,
+ std::size_t nNumPropEntries,
+ const uno::Reference< uno::XComponentContext >& rxContext ) :
+ mpViewLayer( rViewLayer ),
+ mxViewer(),
+ mxFrame(),
+ mxComponentContext( rxContext )
+ {
+ ENSURE_OR_THROW( rxShape.is(), "ViewAppletShape::ViewAppletShape(): Invalid Shape" );
+ ENSURE_OR_THROW( mpViewLayer, "ViewAppletShape::ViewAppletShape(): Invalid View" );
+ ENSURE_OR_THROW( mpViewLayer->getCanvas(), "ViewAppletShape::ViewAppletShape(): Invalid ViewLayer canvas" );
+ ENSURE_OR_THROW( mxComponentContext.is(), "ViewAppletShape::ViewAppletShape(): Invalid component context" );
+
+ uno::Reference<lang::XMultiComponentFactory> xFactory(
+ mxComponentContext->getServiceManager(),
+ uno::UNO_SET_THROW );
+
+ mxViewer.set( xFactory->createInstanceWithContext( rServiceName,
+ mxComponentContext),
+ uno::UNO_QUERY_THROW );
+
+ uno::Reference< beans::XPropertySet > xShapePropSet( rxShape,
+ uno::UNO_QUERY_THROW );
+ uno::Reference< beans::XPropertySet > xViewerPropSet( mxViewer,
+ uno::UNO_QUERY_THROW );
+
+ // copy shape properties to applet viewer
+ OUString aPropName;
+ for( std::size_t i=0; i<nNumPropEntries; ++i )
+ {
+ aPropName = OUString::createFromAscii( pPropCopyTable[i] );
+ xViewerPropSet->setPropertyValue( aPropName,
+ xShapePropSet->getPropertyValue(
+ aPropName ));
+ }
+ }
+
+ ViewAppletShape::~ViewAppletShape()
+ {
+ try
+ {
+ endApplet();
+ }
+ catch (const uno::Exception &)
+ {
+ TOOLS_WARN_EXCEPTION("slideshow", "");
+ }
+ }
+
+ const ViewLayerSharedPtr& ViewAppletShape::getViewLayer() const
+ {
+ return mpViewLayer;
+ }
+
+ void ViewAppletShape::startApplet( const ::basegfx::B2DRectangle& rBounds )
+ {
+ ENSURE_OR_RETURN_VOID( mpViewLayer && mpViewLayer->getCanvas() && mpViewLayer->getCanvas()->getUNOCanvas().is(),
+ "ViewAppletShape::startApplet(): Invalid or disposed view" );
+ try
+ {
+ ::cppcanvas::CanvasSharedPtr pCanvas = mpViewLayer->getCanvas();
+
+ uno::Reference< beans::XPropertySet > xPropSet( pCanvas->getUNOCanvas()->getDevice(),
+ uno::UNO_QUERY_THROW );
+
+ uno::Reference< awt::XWindow2 > xParentWindow(
+ xPropSet->getPropertyValue("Window"),
+ uno::UNO_QUERY_THROW );
+
+ uno::Reference<lang::XMultiComponentFactory> xFactory(
+ mxComponentContext->getServiceManager() );
+
+ if( xFactory.is() )
+ {
+ // create an awt window to contain the applet
+ // ==========================================
+
+ uno::Reference< awt::XToolkit2 > xToolkit = awt::Toolkit::create(mxComponentContext);
+
+ awt::WindowDescriptor aOwnWinDescriptor( awt::WindowClass_SIMPLE,
+ OUString(),
+ uno::Reference< awt::XWindowPeer >(xParentWindow,
+ uno::UNO_QUERY_THROW),
+ 0,
+ awt::Rectangle(),
+ awt::WindowAttribute::SHOW
+ | awt::VclWindowPeerAttribute::CLIPCHILDREN );
+
+ uno::Reference< awt::XWindowPeer > xNewWinPeer(
+ xToolkit->createWindow( aOwnWinDescriptor ));
+ uno::Reference< awt::XWindow > xOwnWindow( xNewWinPeer,
+ uno::UNO_QUERY_THROW );
+
+
+ // create a frame, and load the applet into it
+ // ===========================================
+
+ mxFrame = frame::Frame::create( mxComponentContext );
+ mxFrame->initialize( xOwnWindow );
+
+ uno::Reference < frame::XSynchronousFrameLoader > xLoader( mxViewer,
+ uno::UNO_SET_THROW );
+ xLoader->load( uno::Sequence < beans::PropertyValue >(),
+ uno::Reference<frame::XFrame>(mxFrame, uno::UNO_QUERY_THROW) );
+
+
+ // resize surrounding window and applet to current shape size
+ // ==========================================================
+
+ ::basegfx::B2DRange aTmpRange;
+ ::canvas::tools::calcTransformedRectBounds( aTmpRange,
+ rBounds,
+ mpViewLayer->getTransformation() );
+ const ::basegfx::B2IRange& rPixelBounds(
+ ::basegfx::unotools::b2ISurroundingRangeFromB2DRange( aTmpRange ));
+
+ uno::Reference< awt::XWindow > xSurroundingWindow( mxFrame->getContainerWindow() );
+ if( xSurroundingWindow.is() )
+ xSurroundingWindow->setPosSize( rPixelBounds.getMinX(),
+ rPixelBounds.getMinY(),
+ static_cast<sal_Int32>(rPixelBounds.getWidth()),
+ static_cast<sal_Int32>(rPixelBounds.getHeight()),
+ awt::PosSize::POSSIZE );
+
+ uno::Reference< awt::XWindow > xAppletWindow( mxFrame->getComponentWindow() );
+ if( xAppletWindow.is() )
+ xAppletWindow->setPosSize( 0, 0,
+ static_cast<sal_Int32>(rPixelBounds.getWidth()),
+ static_cast<sal_Int32>(rPixelBounds.getHeight()),
+ awt::PosSize::POSSIZE );
+ }
+ }
+ catch (uno::Exception &)
+ {
+ }
+ }
+
+
+ void ViewAppletShape::endApplet()
+ {
+ uno::Reference<util::XCloseable> xCloseable(
+ mxFrame,
+ uno::UNO_QUERY );
+
+ if( xCloseable.is() )
+ {
+ xCloseable->close( true );
+ mxFrame.clear();
+ }
+ }
+
+
+ bool ViewAppletShape::render( const ::basegfx::B2DRectangle& rBounds ) const
+ {
+ ::cppcanvas::CanvasSharedPtr pCanvas = mpViewLayer->getCanvas();
+
+ if( !pCanvas )
+ return false;
+
+ if( !mxFrame.is() )
+ {
+ // fill the shape background with black
+ fillRect( pCanvas,
+ rBounds,
+ 0xFFFFFFFFU );
+ }
+
+ return true;
+ }
+
+ bool ViewAppletShape::resize( const ::basegfx::B2DRectangle& rBounds ) const
+ {
+ if( !mxFrame.is() )
+ return false;
+
+ ::basegfx::B2DRange aTmpRange;
+ ::canvas::tools::calcTransformedRectBounds( aTmpRange,
+ rBounds,
+ mpViewLayer->getTransformation() );
+ const ::basegfx::B2IRange& rPixelBounds(
+ ::basegfx::unotools::b2ISurroundingRangeFromB2DRange( aTmpRange ));
+
+ uno::Reference< awt::XWindow > xFrameWindow( mxFrame->getContainerWindow() );
+ if( xFrameWindow.is() )
+ xFrameWindow->setPosSize( rPixelBounds.getMinX(),
+ rPixelBounds.getMinY(),
+ static_cast<sal_Int32>(rPixelBounds.getWidth()),
+ static_cast<sal_Int32>(rPixelBounds.getHeight()),
+ awt::PosSize::POSSIZE );
+
+ uno::Reference< awt::XWindow > xAppletWindow( mxFrame->getComponentWindow() );
+ if( xAppletWindow.is() )
+ xAppletWindow->setPosSize( 0, 0,
+ static_cast<sal_Int32>(rPixelBounds.getWidth()),
+ static_cast<sal_Int32>(rPixelBounds.getHeight()),
+ awt::PosSize::POSSIZE );
+
+ return true;
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/slideshow/source/engine/shapes/viewappletshape.hxx b/slideshow/source/engine/shapes/viewappletshape.hxx
new file mode 100644
index 000000000..5d1b30743
--- /dev/null
+++ b/slideshow/source/engine/shapes/viewappletshape.hxx
@@ -0,0 +1,164 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#ifndef INCLUDED_SLIDESHOW_SOURCE_ENGINE_SHAPES_VIEWAPPLETSHAPE_HXX
+#define INCLUDED_SLIDESHOW_SOURCE_ENGINE_SHAPES_VIEWAPPLETSHAPE_HXX
+
+#include <basegfx/range/b2drectangle.hxx>
+#include <com/sun/star/frame/XSynchronousFrameLoader.hpp>
+
+#include <memory>
+
+#include <viewlayer.hxx>
+
+namespace com::sun::star {
+ namespace frame {
+ class XSynchronousFrameLoader;
+ class XFrame2;
+ }
+ namespace uno {
+ class XComponentContext;
+ }
+ namespace drawing {
+ class XShape;
+ }
+}
+
+namespace slideshow
+{
+ namespace internal
+ {
+ /** This class is the viewable representation of a draw
+ document's applet object, associated to a specific View
+
+ The class is able to render the associated applet on View
+ implementations.
+ */
+ class ViewAppletShape final
+ {
+ public:
+ /** Create a ViewAppletShape for the given View
+
+ @param rViewLayer
+ The associated View object.
+
+ @param rxShape
+ The associated Shape
+
+ @param rServiceName
+ The service name to use, when actually creating the
+ viewer component
+
+ @param pPropCopyTable
+ Table of plain ASCII property names, to copy from
+ xShape to applet.
+
+ @param nNumPropEntries
+ Number of property table entries (in pPropCopyTable)
+ */
+ ViewAppletShape( const ViewLayerSharedPtr& rViewLayer,
+ const css::uno::Reference< css::drawing::XShape >& rxShape,
+ const OUString& rServiceName,
+ const char** pPropCopyTable,
+ std::size_t nNumPropEntries,
+ const css::uno::Reference< css::uno::XComponentContext >& rxContext );
+
+ /** destroy the object
+ */
+ ~ViewAppletShape();
+
+ /// Forbid copy construction
+ ViewAppletShape(const ViewAppletShape&) = delete;
+ /// Forbid copy assignment
+ ViewAppletShape& operator=(const ViewAppletShape&) = delete;
+
+ /** Query the associated view layer of this shape
+ */
+ const ViewLayerSharedPtr& getViewLayer() const;
+
+ // animation methods
+
+
+ /** Notify the ViewShape that an animation starts now
+
+ This method enters animation mode on the associate
+ target view. The shape can be animated in parallel on
+ different views.
+
+ @param rBounds
+ The current applet shape bounds
+ */
+ void startApplet( const ::basegfx::B2DRectangle& rBounds );
+
+ /** Notify the ViewShape that it is no longer animated
+
+ This methods ends animation mode on the associate
+ target view
+ */
+ void endApplet();
+
+ // render methods
+
+
+ /** Render the ViewShape
+
+ This method renders the ViewAppletShape on the associated view.
+
+ @param rBounds
+ The current applet shape bounds
+
+ @return whether the rendering finished successfully.
+ */
+ bool render( const ::basegfx::B2DRectangle& rBounds ) const;
+
+ /** Resize the ViewShape
+
+ This method resizes the ViewAppletShape on the
+ associated view. It does not render.
+
+ @param rBounds
+ The current applet shape bounds
+
+ @return whether the resize finished successfully.
+ */
+ bool resize( const ::basegfx::B2DRectangle& rBounds ) const;
+
+ private:
+
+ ViewLayerSharedPtr mpViewLayer;
+
+ /// the actual viewer component for this applet
+ css::uno::Reference<
+ css::frame::XSynchronousFrameLoader> mxViewer;
+
+ /// the frame containing the applet
+ css::uno::Reference<
+ css::frame::XFrame2> mxFrame;
+ css::uno::Reference<
+ css::uno::XComponentContext> mxComponentContext;
+ };
+
+ typedef ::std::shared_ptr< ViewAppletShape > ViewAppletShapeSharedPtr;
+
+ }
+}
+
+#endif // INCLUDED_SLIDESHOW_SOURCE_ENGINE_SHAPES_VIEWAPPLETSHAPE_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/slideshow/source/engine/shapes/viewbackgroundshape.cxx b/slideshow/source/engine/shapes/viewbackgroundshape.cxx
new file mode 100644
index 000000000..e0ca333ad
--- /dev/null
+++ b/slideshow/source/engine/shapes/viewbackgroundshape.cxx
@@ -0,0 +1,188 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+// must be first
+#include <tools/diagnose_ex.h>
+#include <sal/log.hxx>
+
+#include "viewbackgroundshape.hxx"
+#include <tools.hxx>
+
+#include <basegfx/numeric/ftools.hxx>
+#include <basegfx/matrix/b2dhommatrix.hxx>
+#include <basegfx/matrix/b2dhommatrixtools.hxx>
+
+#include <com/sun/star/rendering/XCanvas.hpp>
+
+#include <canvas/canvastools.hxx>
+#include <cppcanvas/vclfactory.hxx>
+#include <cppcanvas/basegfxfactory.hxx>
+#include <cppcanvas/renderer.hxx>
+#include <cppcanvas/bitmap.hxx>
+
+using namespace ::com::sun::star;
+
+
+namespace slideshow::internal
+{
+
+ bool ViewBackgroundShape::prefetch( const ::cppcanvas::CanvasSharedPtr& rDestinationCanvas,
+ const GDIMetaFileSharedPtr& rMtf ) const
+ {
+ SAL_INFO( "slideshow", "::presentation::internal::ViewBackgroundShape::prefetch()" );
+ ENSURE_OR_RETURN_FALSE( rMtf,
+ "ViewBackgroundShape::prefetch(): no valid metafile!" );
+
+ const ::basegfx::B2DHomMatrix& rCanvasTransform(
+ mpViewLayer->getTransformation() );
+
+ if( !mxBitmap.is() ||
+ rMtf != mpLastMtf ||
+ rCanvasTransform != maLastTransformation )
+ {
+ // buffered bitmap is invalid, re-create
+
+ // determine transformed page bounds
+ ::basegfx::B2DRectangle aTmpRect;
+ ::canvas::tools::calcTransformedRectBounds( aTmpRect,
+ maBounds,
+ rCanvasTransform );
+
+ // determine pixel size of bitmap (choose it one pixel
+ // larger, as polygon rendering takes one pixel more
+ // to the right and to the bottom)
+ const ::basegfx::B2ISize aBmpSizePixel(
+ ::basegfx::fround( aTmpRect.getRange().getX() + 1),
+ ::basegfx::fround( aTmpRect.getRange().getY() + 1) );
+
+ // create a bitmap of appropriate size
+ ::cppcanvas::BitmapSharedPtr pBitmap(
+ ::cppcanvas::BaseGfxFactory::createBitmap(
+ rDestinationCanvas,
+ aBmpSizePixel ) );
+
+ ENSURE_OR_THROW( pBitmap,
+ "ViewBackgroundShape::prefetch(): Cannot create background bitmap" );
+
+ ::cppcanvas::BitmapCanvasSharedPtr pBitmapCanvas( pBitmap->getBitmapCanvas() );
+
+ ENSURE_OR_THROW( pBitmapCanvas,
+ "ViewBackgroundShape::prefetch(): Cannot create background bitmap canvas" );
+
+ // clear bitmap
+ initSlideBackground( pBitmapCanvas,
+ aBmpSizePixel );
+
+ // apply linear part of destination canvas transformation (linear means in this context:
+ // transformation without any translational components)
+ ::basegfx::B2DHomMatrix aLinearTransform( rCanvasTransform );
+ aLinearTransform.set( 0, 2, 0.0 );
+ aLinearTransform.set( 1, 2, 0.0 );
+ pBitmapCanvas->setTransformation( aLinearTransform );
+
+ const basegfx::B2DHomMatrix aShapeTransform(basegfx::utils::createScaleTranslateB2DHomMatrix(
+ maBounds.getWidth(), maBounds.getHeight(),
+ maBounds.getMinX(), maBounds.getMinY()));
+
+ ::cppcanvas::RendererSharedPtr pRenderer(
+ ::cppcanvas::VCLFactory::createRenderer(
+ pBitmapCanvas,
+ *rMtf,
+ ::cppcanvas::Renderer::Parameters() ) );
+
+ ENSURE_OR_RETURN_FALSE( pRenderer,
+ "ViewBackgroundShape::prefetch(): Could not create Renderer" );
+
+ pRenderer->setTransformation( aShapeTransform );
+ pRenderer->draw();
+
+ mxBitmap = pBitmap->getUNOBitmap();
+ }
+
+ mpLastMtf = rMtf;
+ maLastTransformation = rCanvasTransform;
+
+ return mxBitmap.is();
+ }
+
+ ViewBackgroundShape::ViewBackgroundShape( const ViewLayerSharedPtr& rViewLayer,
+ const ::basegfx::B2DRectangle& rShapeBounds ) :
+ mpViewLayer( rViewLayer ),
+ mxBitmap(),
+ mpLastMtf(),
+ maLastTransformation(),
+ maBounds( rShapeBounds )
+ {
+ ENSURE_OR_THROW( mpViewLayer, "ViewBackgroundShape::ViewBackgroundShape(): Invalid View" );
+ ENSURE_OR_THROW( mpViewLayer->getCanvas(), "ViewBackgroundShape::ViewBackgroundShape(): Invalid ViewLayer canvas" );
+ }
+
+ const ViewLayerSharedPtr& ViewBackgroundShape::getViewLayer() const
+ {
+ return mpViewLayer;
+ }
+
+ bool ViewBackgroundShape::render( const GDIMetaFileSharedPtr& rMtf ) const
+ {
+ SAL_INFO( "slideshow", "::presentation::internal::ViewBackgroundShape::draw()" );
+
+ const ::cppcanvas::CanvasSharedPtr& rDestinationCanvas( mpViewLayer->getCanvas() );
+
+ if( !prefetch( rDestinationCanvas, rMtf ) )
+ return false;
+
+ ENSURE_OR_RETURN_FALSE( mxBitmap.is(),
+ "ViewBackgroundShape::draw(): Invalid background bitmap" );
+
+ ::basegfx::B2DHomMatrix aTransform( mpViewLayer->getTransformation() );
+
+ // invert the linear part of the view transformation
+ // (i.e. the view transformation without translational
+ // components), to be able to leave the canvas
+ // transformation intact (would otherwise destroy possible
+ // clippings, as the clip polygon is relative to the view
+ // coordinate system).
+ aTransform.set(0,2, 0.0 );
+ aTransform.set(1,2, 0.0 );
+ aTransform.invert();
+
+ rendering::RenderState aRenderState;
+ ::canvas::tools::initRenderState( aRenderState );
+
+ ::canvas::tools::setRenderStateTransform( aRenderState, aTransform );
+
+ try
+ {
+ rDestinationCanvas->getUNOCanvas()->drawBitmap( mxBitmap,
+ rDestinationCanvas->getViewState(),
+ aRenderState );
+ }
+ catch( uno::Exception& )
+ {
+ TOOLS_WARN_EXCEPTION( "slideshow", "" );
+ return false;
+ }
+
+ return true;
+ }
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/slideshow/source/engine/shapes/viewbackgroundshape.hxx b/slideshow/source/engine/shapes/viewbackgroundshape.hxx
new file mode 100644
index 000000000..0f5b29646
--- /dev/null
+++ b/slideshow/source/engine/shapes/viewbackgroundshape.hxx
@@ -0,0 +1,96 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#ifndef INCLUDED_SLIDESHOW_SOURCE_ENGINE_SHAPES_VIEWBACKGROUNDSHAPE_HXX
+#define INCLUDED_SLIDESHOW_SOURCE_ENGINE_SHAPES_VIEWBACKGROUNDSHAPE_HXX
+
+#include <com/sun/star/uno/Reference.hxx>
+#include <com/sun/star/rendering/XBitmap.hpp>
+
+#include <basegfx/range/b2drectangle.hxx>
+#include <basegfx/matrix/b2dhommatrix.hxx>
+
+#include <memory>
+
+#include <tools.hxx>
+#include <viewlayer.hxx>
+
+
+namespace slideshow::internal
+ {
+ /** This class is the viewable representation of a draw
+ document's background, associated to a specific View
+
+ The class is able to render the associated background on
+ View implementations.
+ */
+ class ViewBackgroundShape
+ {
+ public:
+ /** Create a ViewBackgroundShape for the given View
+
+ @param rView
+ The associated View object.
+
+ @param rShapeBounds
+ Bounds of the background shape, in document coordinate
+ system.
+ */
+ ViewBackgroundShape( const ViewLayerSharedPtr& rViewLayer,
+ const ::basegfx::B2DRectangle& rShapeBounds );
+ /// Forbid copy construction
+ ViewBackgroundShape(const ViewBackgroundShape&) = delete;
+ /// Forbid copy assignment
+ ViewBackgroundShape& operator=(const ViewBackgroundShape&) = delete;
+
+ /** Query the associated view layer of this shape
+ */
+ const ViewLayerSharedPtr& getViewLayer() const;
+
+ bool render( const GDIMetaFileSharedPtr& rMtf ) const;
+
+ private:
+ /** Prefetch bitmap for given canvas
+ */
+ bool prefetch( const ::cppcanvas::CanvasSharedPtr& rDestinationCanvas,
+ const GDIMetaFileSharedPtr& rMtf ) const;
+
+ /** The view layer this object is part of.
+ */
+ ViewLayerSharedPtr mpViewLayer;
+
+ /// Generated content bitmap, already with correct output size
+ mutable css::uno::Reference< css::rendering::XBitmap > mxBitmap;
+
+ /// The last metafile a render object was generated for
+ mutable GDIMetaFileSharedPtr mpLastMtf;
+
+ /// The canvas, mpRenderer is associated with
+ mutable ::basegfx::B2DHomMatrix maLastTransformation;
+
+ const ::basegfx::B2DRectangle maBounds;
+ };
+
+ typedef ::std::shared_ptr< ViewBackgroundShape > ViewBackgroundShapeSharedPtr;
+
+}
+
+#endif // INCLUDED_SLIDESHOW_SOURCE_ENGINE_SHAPES_VIEWBACKGROUNDSHAPE_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/slideshow/source/engine/shapes/viewmediashape.cxx b/slideshow/source/engine/shapes/viewmediashape.cxx
new file mode 100644
index 000000000..229599fac
--- /dev/null
+++ b/slideshow/source/engine/shapes/viewmediashape.cxx
@@ -0,0 +1,499 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <config_features.h>
+
+#include <tools/diagnose_ex.h>
+
+#include <sal/log.hxx>
+#include <vcl/canvastools.hxx>
+#include <vcl/syschild.hxx>
+#include <vcl/sysdata.hxx>
+#include <vcl/window.hxx>
+#include <vcl/graph.hxx>
+
+#include <basegfx/utils/canvastools.hxx>
+#include <basegfx/matrix/b2dhommatrixtools.hxx>
+#include <basegfx/point/b2dpoint.hxx>
+#include <basegfx/matrix/b2dhommatrix.hxx>
+#include <basegfx/range/b2irange.hxx>
+#include <canvas/canvastools.hxx>
+#include <cppcanvas/canvas.hxx>
+#include <avmedia/mediawindow.hxx>
+#include <svx/svdobj.hxx>
+#include <svx/svdomedia.hxx>
+
+#include <com/sun/star/awt/XWindow.hpp>
+#include <com/sun/star/beans/XPropertySet.hpp>
+#include <com/sun/star/lang/XComponent.hpp>
+#include <com/sun/star/lang/NoSupportException.hpp>
+#include <com/sun/star/media/XPlayer.hpp>
+#include <com/sun/star/media/XPlayerWindow.hpp>
+#include <com/sun/star/presentation/XSlideShowView.hpp>
+#include <com/sun/star/rendering/XCanvas.hpp>
+
+#include "viewmediashape.hxx"
+#include <tools.hxx>
+#include <unoview.hxx>
+
+using namespace ::com::sun::star;
+
+namespace slideshow::internal
+{
+ ViewMediaShape::ViewMediaShape( const ViewLayerSharedPtr& rViewLayer,
+ const uno::Reference< drawing::XShape >& rxShape,
+ const uno::Reference< uno::XComponentContext >& rxContext ) :
+ mpViewLayer( rViewLayer ),
+ maWindowOffset( 0, 0 ),
+ maBounds(),
+ mxShape( rxShape ),
+ mxPlayer(),
+ mxPlayerWindow(),
+ mxComponentContext( rxContext ),
+ mbIsSoundEnabled(true)
+ {
+ ENSURE_OR_THROW( mxShape.is(), "ViewMediaShape::ViewMediaShape(): Invalid Shape" );
+ ENSURE_OR_THROW( mpViewLayer, "ViewMediaShape::ViewMediaShape(): Invalid View" );
+ ENSURE_OR_THROW( mpViewLayer->getCanvas(), "ViewMediaShape::ViewMediaShape(): Invalid ViewLayer canvas" );
+ ENSURE_OR_THROW( mxComponentContext.is(), "ViewMediaShape::ViewMediaShape(): Invalid component context" );
+
+ UnoViewSharedPtr xUnoView(std::dynamic_pointer_cast<UnoView>(rViewLayer));
+ if (xUnoView)
+ {
+ mbIsSoundEnabled = xUnoView->isSoundEnabled();
+ }
+ }
+
+ ViewMediaShape::~ViewMediaShape()
+ {
+ try
+ {
+ endMedia();
+ }
+ catch (const uno::Exception &)
+ {
+ TOOLS_WARN_EXCEPTION("slideshow", "");
+ }
+ }
+
+ const ViewLayerSharedPtr& ViewMediaShape::getViewLayer() const
+ {
+ return mpViewLayer;
+ }
+
+ void ViewMediaShape::startMedia()
+ {
+ if( !mxPlayer.is() )
+ implInitialize( maBounds );
+
+ if (mxPlayer.is())
+ mxPlayer->start();
+ }
+
+ void ViewMediaShape::endMedia()
+ {
+ // shutdown player window
+ if( mxPlayerWindow.is() )
+ {
+ mxPlayerWindow->dispose();
+ mxPlayerWindow.clear();
+ }
+
+ mpMediaWindow.disposeAndClear();
+
+ // shutdown player
+ if( mxPlayer.is() )
+ {
+ mxPlayer->stop();
+
+ uno::Reference< lang::XComponent > xComponent( mxPlayer, uno::UNO_QUERY );
+
+ if( xComponent.is() )
+ xComponent->dispose();
+
+ mxPlayer.clear();
+ }
+ }
+
+ void ViewMediaShape::pauseMedia()
+ {
+ if (mxPlayer.is())
+ mxPlayer->stop();
+ }
+
+ void ViewMediaShape::setMediaTime(double fTime)
+ {
+ if (mxPlayer.is())
+ mxPlayer->setMediaTime(fTime);
+ }
+
+ void ViewMediaShape::setLooping(bool bLooping)
+ {
+ if (mxPlayer.is())
+ {
+ mxPlayer->setPlaybackLoop(bLooping);
+ }
+ }
+
+ bool ViewMediaShape::render( const ::basegfx::B2DRectangle& rBounds ) const
+ {
+#if !HAVE_FEATURE_AVMEDIA
+ (void) rBounds;
+#else
+ ::cppcanvas::CanvasSharedPtr pCanvas = mpViewLayer->getCanvas();
+
+ if( !pCanvas )
+ return false;
+
+ if( !mpMediaWindow && !mxPlayerWindow.is() )
+ {
+ uno::Reference< graphic::XGraphic > xGraphic;
+ uno::Reference< beans::XPropertySet > xPropSet( mxShape, uno::UNO_QUERY );
+ if (xPropSet.is())
+ {
+ xPropSet->getPropertyValue("FallbackGraphic") >>= xGraphic;
+ }
+
+ Graphic aGraphic(xGraphic);
+ const BitmapEx aBmp = aGraphic.GetBitmapEx();
+
+ uno::Reference< rendering::XBitmap > xBitmap(vcl::unotools::xBitmapFromBitmapEx(aBmp));
+
+ rendering::ViewState aViewState;
+ aViewState.AffineTransform = pCanvas->getViewState().AffineTransform;
+
+ rendering::RenderState aRenderState;
+ ::canvas::tools::initRenderState( aRenderState );
+
+ const ::Size aBmpSize( aBmp.GetSizePixel() );
+
+ const ::basegfx::B2DVector aScale( rBounds.getWidth() / aBmpSize.Width(),
+ rBounds.getHeight() / aBmpSize.Height() );
+ const basegfx::B2DHomMatrix aTranslation(basegfx::utils::createScaleTranslateB2DHomMatrix(
+ aScale, rBounds.getMinimum()));
+ ::canvas::tools::setRenderStateTransform( aRenderState, aTranslation );
+
+ pCanvas->getUNOCanvas()->drawBitmap( xBitmap,
+ aViewState,
+ aRenderState );
+ }
+#endif
+ return true;
+ }
+
+ bool ViewMediaShape::resize( const ::basegfx::B2DRectangle& rNewBounds ) const
+ {
+ maBounds = rNewBounds;
+
+ ::cppcanvas::CanvasSharedPtr pCanvas = mpViewLayer->getCanvas();
+
+ if( !pCanvas )
+ return false;
+
+ if( !mxPlayerWindow.is() )
+ return true;
+
+ uno::Reference< beans::XPropertySet > xPropSet( pCanvas->getUNOCanvas()->getDevice(),
+ uno::UNO_QUERY );
+
+ uno::Reference< awt::XWindow > xParentWindow;
+ if( xPropSet.is() &&
+ getPropertyValue( xParentWindow,
+ xPropSet,
+ "Window") )
+ {
+ const awt::Rectangle aRect( xParentWindow->getPosSize() );
+
+ maWindowOffset.X = aRect.X;
+ maWindowOffset.Y = aRect.Y;
+ }
+
+ ::basegfx::B2DRange aTmpRange;
+ ::canvas::tools::calcTransformedRectBounds( aTmpRange,
+ rNewBounds,
+ mpViewLayer->getTransformation() );
+ const ::basegfx::B2IRange& rRangePix(
+ ::basegfx::unotools::b2ISurroundingRangeFromB2DRange( aTmpRange ));
+
+ mxPlayerWindow->setEnable( !rRangePix.isEmpty() );
+
+ if( rRangePix.isEmpty() )
+ return true;
+
+ awt::Rectangle aCanvasArea;
+ UnoViewSharedPtr xUnoView(std::dynamic_pointer_cast<UnoView>(mpViewLayer));
+ if (xUnoView)
+ aCanvasArea = xUnoView->getUnoView()->getCanvasArea();
+
+ const Point aPosPixel( rRangePix.getMinX() + maWindowOffset.X + aCanvasArea.X,
+ rRangePix.getMinY() + maWindowOffset.Y + aCanvasArea.Y );
+ const Size aSizePixel( rRangePix.getMaxX() - rRangePix.getMinX(),
+ rRangePix.getMaxY() - rRangePix.getMinY() );
+
+ if( mpMediaWindow )
+ {
+ mpMediaWindow->SetPosSizePixel( aPosPixel, aSizePixel );
+ mxPlayerWindow->setPosSize( 0, 0,
+ aSizePixel.Width(), aSizePixel.Height(),
+ 0 );
+ }
+ else
+ {
+ mxPlayerWindow->setPosSize( aPosPixel.X(), aPosPixel.Y(),
+ aSizePixel.Width(), aSizePixel.Height(),
+ 0 );
+ }
+
+ return true;
+ }
+
+
+ bool ViewMediaShape::implInitialize( const ::basegfx::B2DRectangle& rBounds )
+ {
+ if( !mxPlayer.is() && mxShape.is() )
+ {
+ ENSURE_OR_RETURN_FALSE( mpViewLayer->getCanvas(),
+ "ViewMediaShape::implInitialize(): Invalid layer canvas" );
+
+ uno::Reference< rendering::XCanvas > xCanvas( mpViewLayer->getCanvas()->getUNOCanvas() );
+
+ if( xCanvas.is() )
+ {
+ uno::Reference< beans::XPropertySet > xPropSet;
+ try
+ {
+ xPropSet.set( mxShape, uno::UNO_QUERY );
+ OUString sMimeType;
+
+ // create Player
+ if (xPropSet.is())
+ {
+ OUString aURL;
+ xPropSet->getPropertyValue("MediaMimeType") >>= sMimeType;
+ if ((xPropSet->getPropertyValue("PrivateTempFileURL") >>= aURL)
+ && !aURL.isEmpty())
+ {
+ implInitializeMediaPlayer( aURL, sMimeType );
+ }
+ else if (xPropSet->getPropertyValue("MediaURL") >>= aURL)
+ {
+ implInitializeMediaPlayer( aURL, sMimeType );
+ }
+ }
+
+ // create visible object
+ uno::Sequence< uno::Any > aDeviceParams;
+
+ if( ::canvas::tools::getDeviceInfo( xCanvas, aDeviceParams ).getLength() > 1 )
+ {
+ implInitializePlayerWindow( rBounds, aDeviceParams );
+ }
+
+ // set player properties
+ implSetMediaProperties( xPropSet );
+ }
+ catch( uno::RuntimeException& )
+ {
+ throw;
+ }
+ catch( uno::Exception& )
+ {
+ TOOLS_WARN_EXCEPTION( "slideshow", "" );
+ }
+ }
+ }
+
+ return mxPlayer.is() || mxPlayerWindow.is();
+ }
+
+
+ void ViewMediaShape::implSetMediaProperties( const uno::Reference< beans::XPropertySet >& rxProps )
+ {
+ if( !mxPlayer.is() )
+ return;
+
+ mxPlayer->setMediaTime( 0.0 );
+
+ if( !rxProps.is() )
+ return;
+
+ bool bLoop( false );
+ getPropertyValue( bLoop,
+ rxProps,
+ "Loop");
+ mxPlayer->setPlaybackLoop( bLoop );
+
+ bool bMute( false );
+ getPropertyValue( bMute,
+ rxProps,
+ "Mute");
+ mxPlayer->setMute( bMute || !mbIsSoundEnabled);
+
+ sal_Int16 nVolumeDB(0);
+ getPropertyValue( nVolumeDB,
+ rxProps,
+ "VolumeDB");
+ mxPlayer->setVolumeDB( nVolumeDB );
+
+ if( mxPlayerWindow.is() )
+ {
+ media::ZoomLevel eZoom(media::ZoomLevel_FIT_TO_WINDOW);
+ getPropertyValue( eZoom,
+ rxProps,
+ "Zoom");
+ mxPlayerWindow->setZoomLevel( eZoom );
+ }
+ }
+
+
+ void ViewMediaShape::implInitializeMediaPlayer( const OUString& rMediaURL, const OUString& rMimeType )
+ {
+#if !HAVE_FEATURE_AVMEDIA
+ (void) rMediaURL;
+ (void) rMimeType;
+#else
+ if( mxPlayer.is() )
+ return;
+
+ try
+ {
+ if( !rMediaURL.isEmpty() )
+ {
+ mxPlayer = avmedia::MediaWindow::createPlayer( rMediaURL, ""/*TODO!*/, &rMimeType );
+ }
+ }
+ catch( uno::RuntimeException& )
+ {
+ throw;
+ }
+ catch( const uno::Exception& )
+ {
+ throw lang::NoSupportException( "No video support for " + rMediaURL );
+ }
+#endif
+ }
+
+
+ void ViewMediaShape::implInitializePlayerWindow( const ::basegfx::B2DRectangle& rBounds,
+ const uno::Sequence< uno::Any >& rVCLDeviceParams )
+ {
+ SAL_INFO("slideshow", "ViewMediaShape::implInitializePlayerWindow" );
+ if( mpMediaWindow || rBounds.isEmpty() )
+ return;
+
+ try
+ {
+ sal_Int64 aVal=0;
+
+ rVCLDeviceParams[ 1 ] >>= aVal;
+
+ OutputDevice* pDevice = reinterpret_cast<OutputDevice*>(aVal);
+ vcl::Window* pWindow = pDevice ? pDevice->GetOwnerWindow() : nullptr;
+
+ if( pWindow )
+ {
+ ::basegfx::B2DRange aTmpRange;
+ ::canvas::tools::calcTransformedRectBounds( aTmpRange,
+ rBounds,
+ mpViewLayer->getTransformation() );
+ const ::basegfx::B2IRange& rRangePix(
+ ::basegfx::unotools::b2ISurroundingRangeFromB2DRange( aTmpRange ));
+
+ if( !rRangePix.isEmpty() )
+ {
+ awt::Rectangle aAWTRect( rRangePix.getMinX(),
+ rRangePix.getMinY(),
+ rRangePix.getMaxX() - rRangePix.getMinX(),
+ rRangePix.getMaxY() - rRangePix.getMinY() );
+ {
+ mpMediaWindow.disposeAndClear();
+ mpMediaWindow = VclPtr<SystemChildWindow>::Create( pWindow, WB_CLIPCHILDREN );
+ UnoViewSharedPtr xUnoView(std::dynamic_pointer_cast<UnoView>(mpViewLayer));
+ if (xUnoView)
+ {
+ awt::Rectangle aCanvasArea = xUnoView->getUnoView()->getCanvasArea();
+ aAWTRect.X += aCanvasArea.X;
+ aAWTRect.Y += aCanvasArea.Y;
+ }
+ mpMediaWindow->SetPosSizePixel( Point( aAWTRect.X, aAWTRect.Y ),
+ Size( aAWTRect.Width, aAWTRect.Height ) );
+ }
+ mpMediaWindow->SetBackground( COL_BLACK );
+ mpMediaWindow->SetParentClipMode( ParentClipMode::NoClip );
+ mpMediaWindow->EnableEraseBackground( false );
+ mpMediaWindow->SetForwardKey( true );
+ mpMediaWindow->SetMouseTransparent( true );
+ mpMediaWindow->Show();
+
+ if( mxPlayer.is() )
+ {
+ sal_IntPtr nParentWindowHandle(0);
+ const SystemEnvData* pEnvData = mpMediaWindow->GetSystemData();
+ // tdf#139609 gtk doesn't need the handle, and fetching it is undesirable
+ if (!pEnvData || pEnvData->toolkit != SystemEnvData::Toolkit::Gtk)
+ nParentWindowHandle = mpMediaWindow->GetParentWindowHandle();
+
+ aAWTRect.X = aAWTRect.Y = 0;
+
+ SdrObject* pObj = SdrObject::getSdrObjectFromXShape(mxShape);
+ auto pMediaObj = dynamic_cast<SdrMediaObj*>(pObj);
+ const avmedia::MediaItem* pMediaItem = nullptr;
+ if (pMediaObj)
+ {
+ pMediaItem = &pMediaObj->getMediaProperties();
+ }
+
+ uno::Sequence< uno::Any > aArgs{
+ uno::Any(nParentWindowHandle),
+ uno::Any(aAWTRect),
+ uno::Any(reinterpret_cast< sal_IntPtr >( mpMediaWindow.get() )),
+ // Media item contains media properties, e.g. cropping.
+ uno::Any(reinterpret_cast< sal_IntPtr >( pMediaItem ))
+ };
+
+ mxPlayerWindow.set( mxPlayer->createPlayerWindow( aArgs ) );
+
+ if( mxPlayerWindow.is() )
+ {
+ mxPlayerWindow->setVisible( true );
+ mxPlayerWindow->setEnable( true );
+ }
+ }
+
+ if( !mxPlayerWindow.is() )
+ {
+ //if there was no playerwindow, then clear the mpMediaWindow too
+ //so that we can draw a placeholder instead in that space
+ mpMediaWindow.disposeAndClear();
+ }
+ }
+ }
+ }
+ catch( uno::RuntimeException& )
+ {
+ throw;
+ }
+ catch( uno::Exception& )
+ {
+ TOOLS_WARN_EXCEPTION( "slideshow", "" );
+ }
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/slideshow/source/engine/shapes/viewmediashape.hxx b/slideshow/source/engine/shapes/viewmediashape.hxx
new file mode 100644
index 000000000..69445a8a5
--- /dev/null
+++ b/slideshow/source/engine/shapes/viewmediashape.hxx
@@ -0,0 +1,168 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#ifndef INCLUDED_SLIDESHOW_SOURCE_ENGINE_SHAPES_VIEWMEDIASHAPE_HXX
+#define INCLUDED_SLIDESHOW_SOURCE_ENGINE_SHAPES_VIEWMEDIASHAPE_HXX
+
+#include <basegfx/range/b2drectangle.hxx>
+#include <com/sun/star/awt/Point.hpp>
+#include <com/sun/star/drawing/XShape.hpp>
+
+#include <memory>
+#include <vcl/vclptr.hxx>
+
+#include <viewlayer.hxx>
+
+class SystemChildWindow;
+
+namespace com::sun::star {
+ namespace drawing {
+ class XShape;
+ }
+ namespace media {
+ class XPlayer;
+ class XPlayerWindow;
+ }
+ namespace uno {
+ class XComponentContext;
+ }
+ namespace beans{
+ class XPropertySet;
+ }
+}
+
+namespace slideshow::internal
+ {
+ /** This class is the viewable representation of a draw
+ document's media object, associated to a specific View
+
+ The class is able to render the associated media shape on
+ View implementations.
+ */
+ class ViewMediaShape final
+ {
+ public:
+ /** Create a ViewMediaShape for the given View
+
+ @param rView
+ The associated View object.
+ */
+ ViewMediaShape( const ViewLayerSharedPtr& rViewLayer,
+ const css::uno::Reference< css::drawing::XShape >& rxShape,
+ const css::uno::Reference< css::uno::XComponentContext >& rxContext );
+
+ /** destroy the object
+ */
+ ~ViewMediaShape();
+
+ /// Forbid copy construction
+ ViewMediaShape(const ViewMediaShape&) = delete;
+ /// Forbid copy assignment
+ ViewMediaShape& operator=(const ViewMediaShape&) = delete;
+
+ /** Query the associated view layer of this shape
+ */
+ const ViewLayerSharedPtr& getViewLayer() const;
+
+ // animation methods
+
+
+ /** Notify the ViewShape that an animation starts now
+
+ This method enters animation mode on the associate
+ target view. The shape can be animated in parallel on
+ different views.
+ */
+ void startMedia();
+
+ /** Notify the ViewShape that it is no longer animated
+
+ This methods ends animation mode on the associate
+ target view
+ */
+ void endMedia();
+
+ /** Notify the ViewShape that it should pause playback
+
+ This methods pauses animation on the associate
+ target view. The content stays visible (for video)
+ */
+ void pauseMedia();
+
+ /** Set current time of media.
+
+ @param fTime
+ Local media time that should now be presented, in seconds.
+ */
+ void setMediaTime(double fTime);
+
+ void setLooping(bool bLooping);
+
+ // render methods
+
+
+ /** Render the ViewShape
+
+ This method renders the ViewMediaShape on the associated view.
+
+ @param rBounds
+ The current media shape bounds
+
+ @return whether the rendering finished successfully.
+ */
+ bool render( const ::basegfx::B2DRectangle& rBounds ) const;
+
+ /** Resize the ViewShape
+
+ This method updates the ViewMediaShape size on the
+ associated view. It does not render.
+
+ @param rBounds
+ The current media shape bounds
+
+ @return whether the resize finished successfully.
+ */
+ bool resize( const ::basegfx::B2DRectangle& rNewBounds ) const;
+
+ private:
+
+ bool implInitialize( const ::basegfx::B2DRectangle& rBounds );
+ void implSetMediaProperties( const css::uno::Reference< css::beans::XPropertySet >& rxProps );
+ void implInitializeMediaPlayer( const OUString& rMediaURL, const OUString& rMimeType );
+ void implInitializePlayerWindow( const ::basegfx::B2DRectangle& rBounds,
+ const css::uno::Sequence< css::uno::Any >& rVCLDeviceParams );
+ ViewLayerSharedPtr mpViewLayer;
+ VclPtr< SystemChildWindow > mpMediaWindow;
+ mutable css::awt::Point maWindowOffset;
+ mutable ::basegfx::B2DRectangle maBounds;
+
+ css::uno::Reference< css::drawing::XShape > mxShape;
+ css::uno::Reference< css::media::XPlayer > mxPlayer;
+ css::uno::Reference< css::media::XPlayerWindow > mxPlayerWindow;
+ css::uno::Reference< css::uno::XComponentContext> mxComponentContext;
+ bool mbIsSoundEnabled;
+ };
+
+ typedef ::std::shared_ptr< ViewMediaShape > ViewMediaShapeSharedPtr;
+
+}
+
+#endif // INCLUDED_SLIDESHOW_SOURCE_ENGINE_SHAPES_VIEWMEDIASHAPE_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/slideshow/source/engine/shapes/viewshape.cxx b/slideshow/source/engine/shapes/viewshape.cxx
new file mode 100644
index 000000000..f2d909524
--- /dev/null
+++ b/slideshow/source/engine/shapes/viewshape.cxx
@@ -0,0 +1,856 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+
+#include <tools/diagnose_ex.h>
+
+#include <algorithm>
+
+#include <rtl/math.hxx>
+#include <sal/log.hxx>
+
+#include <com/sun/star/rendering/PanoseLetterForm.hpp>
+#include <com/sun/star/awt/FontSlant.hpp>
+
+#include <basegfx/numeric/ftools.hxx>
+#include <basegfx/matrix/b2dhommatrix.hxx>
+#include <basegfx/matrix/b2dhommatrixtools.hxx>
+
+#include <canvas/canvastools.hxx>
+#include <cppcanvas/vclfactory.hxx>
+#include <cppcanvas/basegfxfactory.hxx>
+
+#include "viewshape.hxx"
+#include <tools.hxx>
+
+using namespace ::com::sun::star;
+
+namespace slideshow::internal
+{
+
+ // TODO(F2): Provide sensible setup for mtf-related attributes (fill mode,
+ // char rotation etc.). Do that via mtf argument at this object
+
+ bool ViewShape::prefetch( RendererCacheEntry& io_rCacheEntry,
+ const ::cppcanvas::CanvasSharedPtr& rDestinationCanvas,
+ const GDIMetaFileSharedPtr& rMtf,
+ const ShapeAttributeLayerSharedPtr& rAttr )
+ {
+ ENSURE_OR_RETURN_FALSE( rMtf,
+ "ViewShape::prefetch(): no valid metafile!" );
+
+ if( rMtf != io_rCacheEntry.mpMtf ||
+ rDestinationCanvas != io_rCacheEntry.getDestinationCanvas() )
+ {
+ // buffered renderer invalid, re-create
+ ::cppcanvas::Renderer::Parameters aParms;
+
+ // rendering attribute override parameter struct. For
+ // every valid attribute, the corresponding struct
+ // member is filled, which in the metafile renderer
+ // forces rendering with the given attribute.
+ if( rAttr )
+ {
+ if( rAttr->isFillColorValid() )
+ {
+ // convert RGBColor to RGBA32 integer. Note
+ // that getIntegerColor() also truncates
+ // out-of-range values appropriately
+ aParms.maFillColor =
+ rAttr->getFillColor().getIntegerColor();
+ }
+ if( rAttr->isLineColorValid() )
+ {
+ // convert RGBColor to RGBA32 integer. Note
+ // that getIntegerColor() also truncates
+ // out-of-range values appropriately
+ aParms.maLineColor =
+ rAttr->getLineColor().getIntegerColor();
+ }
+ if( rAttr->isCharColorValid() )
+ {
+ // convert RGBColor to RGBA32 integer. Note
+ // that getIntegerColor() also truncates
+ // out-of-range values appropriately
+ aParms.maTextColor =
+ rAttr->getCharColor().getIntegerColor();
+ }
+ if( rAttr->isDimColorValid() )
+ {
+ // convert RGBColor to RGBA32 integer. Note
+ // that getIntegerColor() also truncates
+ // out-of-range values appropriately
+
+ // dim color overrides all other colors
+ aParms.maFillColor =
+ aParms.maLineColor =
+ aParms.maTextColor =
+ rAttr->getDimColor().getIntegerColor();
+ }
+ if( rAttr->isFontFamilyValid() )
+ {
+ aParms.maFontName =
+ rAttr->getFontFamily();
+ }
+ if( rAttr->isCharScaleValid() )
+ {
+ ::basegfx::B2DHomMatrix aMatrix;
+
+ // enlarge text by given scale factor. Do that
+ // with the middle of the shape as the center
+ // of scaling.
+ aMatrix.translate( -0.5, -0.5 );
+ aMatrix.scale( rAttr->getCharScale(),
+ rAttr->getCharScale() );
+ aMatrix.translate( 0.5, 0.5 );
+
+ aParms.maTextTransformation = aMatrix;
+ }
+ if( rAttr->isCharWeightValid() )
+ {
+ aParms.maFontWeight =
+ static_cast< sal_Int8 >(
+ ::basegfx::fround(
+ ::std::max( 0.0,
+ ::std::min( 11.0,
+ rAttr->getCharWeight() / 20.0 ) ) ) );
+ }
+ if( rAttr->isCharPostureValid() )
+ {
+ aParms.maFontLetterForm =
+ rAttr->getCharPosture() == sal_Int16(awt::FontSlant_NONE) ?
+ rendering::PanoseLetterForm::ANYTHING :
+ rendering::PanoseLetterForm::OBLIQUE_CONTACT;
+ }
+ if( rAttr->isUnderlineModeValid() )
+ {
+ aParms.maFontUnderline =
+ rAttr->getUnderlineMode();
+ }
+ }
+
+ io_rCacheEntry.mpRenderer = ::cppcanvas::VCLFactory::createRenderer( rDestinationCanvas,
+ *rMtf,
+ aParms );
+
+ io_rCacheEntry.mpMtf = rMtf;
+ io_rCacheEntry.mpDestinationCanvas = rDestinationCanvas;
+
+ // also invalidate alpha compositing bitmap (created
+ // new renderer, which possibly generates different
+ // output). Do NOT invalidate, if we're incidentally
+ // rendering INTO it.
+ if( rDestinationCanvas != io_rCacheEntry.mpLastBitmapCanvas )
+ {
+ io_rCacheEntry.mpLastBitmapCanvas.reset();
+ io_rCacheEntry.mpLastBitmap.reset();
+ }
+ }
+
+ return static_cast< bool >(io_rCacheEntry.mpRenderer);
+ }
+
+ bool ViewShape::draw( const ::cppcanvas::CanvasSharedPtr& rDestinationCanvas,
+ const GDIMetaFileSharedPtr& rMtf,
+ const ShapeAttributeLayerSharedPtr& rAttr,
+ const ::basegfx::B2DHomMatrix& rTransform,
+ const ::basegfx::B2DPolyPolygon* pClip,
+ const VectorOfDocTreeNodes& rSubsets ) const
+ {
+ ::cppcanvas::RendererSharedPtr pRenderer(
+ getRenderer( rDestinationCanvas, rMtf, rAttr ) );
+
+ ENSURE_OR_RETURN_FALSE( pRenderer, "ViewShape::draw(): Invalid renderer" );
+
+ pRenderer->setTransformation( rTransform );
+#if OSL_DEBUG_LEVEL >= 2
+ rendering::RenderState aRenderState;
+ ::canvas::tools::initRenderState(aRenderState);
+ ::canvas::tools::setRenderStateTransform(aRenderState,
+ rTransform);
+ aRenderState.DeviceColor.realloc(4);
+ aRenderState.DeviceColor[0] = 1.0;
+ aRenderState.DeviceColor[1] = 0.0;
+ aRenderState.DeviceColor[2] = 0.0;
+ aRenderState.DeviceColor[3] = 1.0;
+
+ try
+ {
+ rDestinationCanvas->getUNOCanvas()->drawLine( geometry::RealPoint2D(0.0,0.0),
+ geometry::RealPoint2D(1.0,1.0),
+ rDestinationCanvas->getViewState(),
+ aRenderState );
+ rDestinationCanvas->getUNOCanvas()->drawLine( geometry::RealPoint2D(1.0,0.0),
+ geometry::RealPoint2D(0.0,1.0),
+ rDestinationCanvas->getViewState(),
+ aRenderState );
+ }
+ catch( uno::Exception& )
+ {
+ DBG_UNHANDLED_EXCEPTION("slideshow");
+ }
+#endif
+ if( pClip )
+ pRenderer->setClip( *pClip );
+ else
+ pRenderer->setClip();
+
+ if( rSubsets.empty() )
+ {
+ return pRenderer->draw();
+ }
+ else
+ {
+ // render subsets of whole metafile
+
+
+ bool bRet(true);
+ for( const auto& rSubset : rSubsets )
+ {
+ if( !pRenderer->drawSubset( rSubset.getStartIndex(),
+ rSubset.getEndIndex() ) )
+ bRet = false;
+ }
+
+ return bRet;
+ }
+ }
+
+ namespace
+ {
+ /// Convert untransformed shape update area to device pixel.
+ ::basegfx::B2DRectangle shapeArea2AreaPixel( const ::basegfx::B2DHomMatrix& rCanvasTransformation,
+ const ::basegfx::B2DRectangle& rUntransformedArea )
+ {
+ // convert area to pixel, and add anti-aliasing border
+
+ // TODO(P1): Should the view transform some
+ // day contain rotation/shear, transforming
+ // the original bounds with the total
+ // transformation might result in smaller
+ // overall bounds.
+
+ ::basegfx::B2DRectangle aBoundsPixel;
+ ::canvas::tools::calcTransformedRectBounds( aBoundsPixel,
+ rUntransformedArea,
+ rCanvasTransformation );
+
+ // add antialiasing border around the shape (AA
+ // touches pixel _outside_ the nominal bound rect)
+ aBoundsPixel.grow( ::cppcanvas::Canvas::ANTIALIASING_EXTRA_SIZE );
+
+ return aBoundsPixel;
+ }
+
+ /// Convert shape unit rect to device pixel.
+ ::basegfx::B2DRectangle calcUpdateAreaPixel( const ::basegfx::B2DRectangle& rUnitBounds,
+ const ::basegfx::B2DHomMatrix& rShapeTransformation,
+ const ::basegfx::B2DHomMatrix& rCanvasTransformation,
+ const ShapeAttributeLayerSharedPtr& pAttr )
+ {
+ // calc update area for whole shape (including
+ // character scaling)
+ return shapeArea2AreaPixel( rCanvasTransformation,
+ getShapeUpdateArea( rUnitBounds,
+ rShapeTransformation,
+ pAttr ) );
+ }
+ }
+
+ bool ViewShape::renderSprite( const ViewLayerSharedPtr& rViewLayer,
+ const GDIMetaFileSharedPtr& rMtf,
+ const ::basegfx::B2DRectangle& rOrigBounds,
+ const ::basegfx::B2DRectangle& rBounds,
+ const ::basegfx::B2DRectangle& rUnitBounds,
+ UpdateFlags nUpdateFlags,
+ const ShapeAttributeLayerSharedPtr& pAttr,
+ const VectorOfDocTreeNodes& rSubsets,
+ double nPrio,
+ bool bIsVisible ) const
+ {
+ // TODO(P1): For multiple views, it might pay off to reorg Shape and ViewShape,
+ // in that all the common setup steps here are refactored to Shape (would then
+ // have to be performed only _once_ per Shape paint).
+
+ if( !bIsVisible ||
+ rUnitBounds.isEmpty() ||
+ rOrigBounds.isEmpty() ||
+ rBounds.isEmpty() )
+ {
+ // shape is invisible or has zero size, no need to
+ // update anything.
+ if( mpSprite )
+ mpSprite->hide();
+
+ return true;
+ }
+
+
+ // calc sprite position, size and content transformation
+ // =====================================================
+
+ // the shape transformation for a sprite is always a
+ // simple scale-up to the nominal shape size. Everything
+ // else is handled via the sprite transformation
+ ::basegfx::B2DHomMatrix aNonTranslationalShapeTransformation;
+ aNonTranslationalShapeTransformation.scale( rOrigBounds.getWidth(),
+ rOrigBounds.getHeight() );
+ ::basegfx::B2DHomMatrix aShapeTransformation( aNonTranslationalShapeTransformation );
+ aShapeTransformation.translate( rOrigBounds.getMinX(),
+ rOrigBounds.getMinY() );
+
+ const ::basegfx::B2DHomMatrix& rCanvasTransform(
+ rViewLayer->getSpriteTransformation() );
+
+ // area actually needed for the sprite
+ const ::basegfx::B2DRectangle& rSpriteBoundsPixel(
+ calcUpdateAreaPixel( rUnitBounds,
+ aShapeTransformation,
+ rCanvasTransform,
+ pAttr ) );
+
+ // actual area for the shape (without subsetting, but
+ // including char scaling)
+ const ::basegfx::B2DRectangle& rShapeBoundsPixel(
+ calcUpdateAreaPixel( ::basegfx::B2DRectangle(0.0,0.0,1.0,1.0),
+ aShapeTransformation,
+ rCanvasTransform,
+ pAttr ) );
+
+ // nominal area for the shape (without subsetting, without
+ // char scaling). NOTE: to cancel the shape translation,
+ // contained in rSpriteBoundsPixel, this is _without_ any
+ // translational component.
+ ::basegfx::B2DRectangle aLogShapeBounds;
+ const ::basegfx::B2DRectangle& rNominalShapeBoundsPixel(
+ shapeArea2AreaPixel( rCanvasTransform,
+ ::canvas::tools::calcTransformedRectBounds(
+ aLogShapeBounds,
+ ::basegfx::B2DRectangle(0.0,0.0,1.0,1.0),
+ aNonTranslationalShapeTransformation ) ) );
+
+ // create (or resize) sprite with sprite's pixel size, if
+ // not done already
+ const ::basegfx::B2DSize& rSpriteSizePixel(rSpriteBoundsPixel.getRange());
+ if( !mpSprite )
+ {
+ mpSprite = std::make_shared<AnimatedSprite>( mpViewLayer,
+ rSpriteSizePixel,
+ nPrio );
+ }
+ else
+ {
+ // TODO(F2): when the sprite _actually_ gets resized,
+ // content needs a repaint!
+ mpSprite->resize( rSpriteSizePixel );
+ }
+
+ ENSURE_OR_RETURN_FALSE( mpSprite, "ViewShape::renderSprite(): No sprite" );
+
+ SAL_INFO("slideshow", "ViewShape::renderSprite(): Rendering sprite " <<
+ mpSprite.get() );
+
+
+ // always show the sprite (might have been hidden before)
+ mpSprite->show();
+
+ // determine center of sprite output position in pixel
+ // (assumption here: all shape transformations have the
+ // shape center as the pivot point). From that, subtract
+ // distance of rSpriteBoundsPixel's left, top edge from
+ // rShapeBoundsPixel's center. This moves the sprite at
+ // the appropriate output position within the virtual
+ // rShapeBoundsPixel area.
+ ::basegfx::B2DPoint aSpritePosPixel( rBounds.getCenter() );
+ aSpritePosPixel *= rCanvasTransform;
+ aSpritePosPixel -= rShapeBoundsPixel.getCenter() - rSpriteBoundsPixel.getMinimum();
+
+ // the difference between rShapeBoundsPixel and
+ // rSpriteBoundsPixel upper, left corner is: the offset we
+ // have to move sprite output to the right, top (to make
+ // the desired subset content visible at all)
+ const ::basegfx::B2DSize& rSpriteCorrectionOffset(
+ rSpriteBoundsPixel.getMinimum() - rNominalShapeBoundsPixel.getMinimum() );
+
+ // offset added top, left for anti-aliasing (otherwise,
+ // shapes fully filling the sprite will have anti-aliased
+ // pixel cut off)
+ const ::basegfx::B2DSize aAAOffset(
+ ::cppcanvas::Canvas::ANTIALIASING_EXTRA_SIZE,
+ ::cppcanvas::Canvas::ANTIALIASING_EXTRA_SIZE );
+
+ // set pixel output offset to sprite: we always leave
+ // ANTIALIASING_EXTRA_SIZE room atop and to the left, and,
+ // what's more, for subsetted shapes, we _have_ to cancel
+ // the effect of the shape renderer outputting the subset
+ // at its absolute position inside the shape, instead of
+ // at the origin.
+ // NOTE: As for now, sprites are always positioned on
+ // integer pixel positions on screen, have to round to
+ // nearest integer here, too
+ mpSprite->setPixelOffset(
+ aAAOffset - ::basegfx::B2DSize(
+ ::basegfx::fround( rSpriteCorrectionOffset.getX() ),
+ ::basegfx::fround( rSpriteCorrectionOffset.getY() ) ) );
+
+ // always set sprite position and transformation, since
+ // they do not relate directly to the update flags
+ // (e.g. sprite position changes when sprite size changes)
+ mpSprite->movePixel( aSpritePosPixel );
+ mpSprite->transform( getSpriteTransformation( rSpriteSizePixel,
+ rOrigBounds.getRange(),
+ pAttr ) );
+
+
+ // process flags
+ // =============
+
+ bool bRedrawRequired( mbForceUpdate || (nUpdateFlags & UpdateFlags::Force) );
+
+ if( mbForceUpdate || (nUpdateFlags & UpdateFlags::Alpha) )
+ {
+ mpSprite->setAlpha( (pAttr && pAttr->isAlphaValid()) ?
+ std::clamp(pAttr->getAlpha(),
+ 0.0,
+ 1.0) :
+ 1.0 );
+ }
+ if( mbForceUpdate || (nUpdateFlags & UpdateFlags::Clip) )
+ {
+ if( pAttr && pAttr->isClipValid() )
+ {
+ ::basegfx::B2DPolyPolygon aClipPoly( pAttr->getClip() );
+
+ // extract linear part of canvas view transformation
+ // (linear means: without translational components)
+ ::basegfx::B2DHomMatrix aViewTransform(
+ mpViewLayer->getTransformation() );
+ aViewTransform.set( 0, 2, 0.0 );
+ aViewTransform.set( 1, 2, 0.0 );
+
+ // make the clip 2*ANTIALIASING_EXTRA_SIZE larger
+ // such that it's again centered over the sprite.
+ aViewTransform.scale(rSpriteSizePixel.getX()/
+ (rSpriteSizePixel.getX()-2*::cppcanvas::Canvas::ANTIALIASING_EXTRA_SIZE),
+ rSpriteSizePixel.getY()/
+ (rSpriteSizePixel.getY()-2*::cppcanvas::Canvas::ANTIALIASING_EXTRA_SIZE));
+
+ // transform clip polygon from view to device
+ // coordinate space
+ aClipPoly.transform( aViewTransform );
+
+ mpSprite->clip( aClipPoly );
+ }
+ else
+ mpSprite->clip();
+ }
+ if( mbForceUpdate || (nUpdateFlags & UpdateFlags::Content) )
+ {
+ bRedrawRequired = true;
+
+ // TODO(P1): maybe provide some appearance change methods at
+ // the Renderer interface
+
+ // force the renderer to be regenerated below, for the
+ // different attributes to take effect
+ invalidateRenderer();
+ }
+
+ mbForceUpdate = false;
+
+ if( !bRedrawRequired )
+ return true;
+
+
+ // sprite needs repaint - output to sprite canvas
+ // ==============================================
+
+ ::cppcanvas::CanvasSharedPtr pContentCanvas( mpSprite->getContentCanvas() );
+
+ return draw( pContentCanvas,
+ rMtf,
+ pAttr,
+ aShapeTransformation,
+ nullptr, // clipping is done via Sprite::clip()
+ rSubsets );
+ }
+
+ bool ViewShape::render( const ::cppcanvas::CanvasSharedPtr& rDestinationCanvas,
+ const GDIMetaFileSharedPtr& rMtf,
+ const ::basegfx::B2DRectangle& rBounds,
+ const ::basegfx::B2DRectangle& rUpdateBounds,
+ UpdateFlags nUpdateFlags,
+ const ShapeAttributeLayerSharedPtr& pAttr,
+ const VectorOfDocTreeNodes& rSubsets,
+ bool bIsVisible ) const
+ {
+ // TODO(P1): For multiple views, it might pay off to reorg Shape and ViewShape,
+ // in that all the common setup steps here are refactored to Shape (would then
+ // have to be performed only _once_ per Shape paint).
+
+ if( !bIsVisible )
+ {
+ SAL_INFO("slideshow", "ViewShape::render(): skipping shape " << this );
+
+ // shape is invisible, no need to update anything.
+ return true;
+ }
+
+ // since we have no sprite here, _any_ update request
+ // translates into a required redraw.
+ bool bRedrawRequired( mbForceUpdate || nUpdateFlags != UpdateFlags::NONE );
+
+ if( nUpdateFlags & UpdateFlags::Content )
+ {
+ // TODO(P1): maybe provide some appearance change methods at
+ // the Renderer interface
+
+ // force the renderer to be regenerated below, for the
+ // different attributes to take effect
+ invalidateRenderer();
+ }
+
+ mbForceUpdate = false;
+
+ if( !bRedrawRequired )
+ return true;
+
+ SAL_INFO( "slideshow", "ViewShape::render(): rendering shape " <<
+ this <<
+ " at position (" <<
+ rBounds.getMinX() << "," <<
+ rBounds.getMinY() << ")" );
+
+
+ // shape needs repaint - setup all that's needed
+
+
+ std::optional<basegfx::B2DPolyPolygon> aClip;
+
+ if( pAttr )
+ {
+ // setup clip poly
+ if( pAttr->isClipValid() )
+ aClip = pAttr->getClip();
+
+ // emulate global shape alpha by first rendering into
+ // a temp bitmap, and then to screen (this would have
+ // been much easier if we'd be currently a sprite -
+ // see above)
+ if( pAttr->isAlphaValid() )
+ {
+ const double nAlpha( pAttr->getAlpha() );
+
+ if( !::basegfx::fTools::equalZero( nAlpha ) &&
+ !::rtl::math::approxEqual(nAlpha, 1.0) )
+ {
+ // render with global alpha - have to prepare
+ // a bitmap, and render that with modulated
+ // alpha
+
+
+ const ::basegfx::B2DHomMatrix aTransform(
+ getShapeTransformation( rBounds,
+ pAttr ) );
+
+ // TODO(P1): Should the view transform some
+ // day contain rotation/shear, transforming
+ // the original bounds with the total
+ // transformation might result in smaller
+ // overall bounds.
+
+ // determine output rect of _shape update
+ // area_ in device pixel
+ const ::basegfx::B2DHomMatrix aCanvasTransform(
+ rDestinationCanvas->getTransformation() );
+ ::basegfx::B2DRectangle aTmpRect;
+ ::canvas::tools::calcTransformedRectBounds( aTmpRect,
+ rUpdateBounds,
+ aCanvasTransform );
+
+ // pixel size of cache bitmap: round up to
+ // nearest int
+ const ::basegfx::B2ISize aBmpSize( static_cast<sal_Int32>( aTmpRect.getWidth() )+1,
+ static_cast<sal_Int32>( aTmpRect.getHeight() )+1 );
+
+ // try to fetch temporary surface for alpha
+ // compositing (to achieve the global alpha
+ // blend effect, have to first render shape as
+ // a whole, then blit that surface with global
+ // alpha to the destination)
+ const RendererCacheVector::iterator aCompositingSurface(
+ getCacheEntry( rDestinationCanvas ) );
+
+ if( !aCompositingSurface->mpLastBitmapCanvas ||
+ aCompositingSurface->mpLastBitmapCanvas->getSize() != aBmpSize )
+ {
+ // create a bitmap of appropriate size
+ ::cppcanvas::BitmapSharedPtr pBitmap(
+ ::cppcanvas::BaseGfxFactory::createAlphaBitmap(
+ rDestinationCanvas,
+ aBmpSize ) );
+
+ ENSURE_OR_THROW(pBitmap,
+ "ViewShape::render(): Could not create compositing surface");
+
+ aCompositingSurface->mpDestinationCanvas = rDestinationCanvas;
+ aCompositingSurface->mpLastBitmap = pBitmap;
+ aCompositingSurface->mpLastBitmapCanvas = pBitmap->getBitmapCanvas();
+ }
+
+ // buffer aCompositingSurface iterator content
+ // - said one might get invalidated during
+ // draw() below.
+ ::cppcanvas::BitmapCanvasSharedPtr pBitmapCanvas(
+ aCompositingSurface->mpLastBitmapCanvas );
+
+ ::cppcanvas::BitmapSharedPtr pBitmap(
+ aCompositingSurface->mpLastBitmap);
+
+ // setup bitmap canvas transformation -
+ // which happens to be the destination
+ // canvas transformation without any
+ // translational components.
+
+ // But then, the render transformation as
+ // calculated by getShapeTransformation()
+ // above outputs the shape at its real
+ // destination position. Thus, we have to
+ // offset the output back to the origin,
+ // for which we simply plug in the
+ // negative position of the left, top edge
+ // of the shape's bound rect in device
+ // pixel into aLinearTransform below.
+ ::basegfx::B2DHomMatrix aAdjustedCanvasTransform( aCanvasTransform );
+ aAdjustedCanvasTransform.translate( -aTmpRect.getMinX(),
+ -aTmpRect.getMinY() );
+
+ pBitmapCanvas->setTransformation( aAdjustedCanvasTransform );
+
+ // TODO(P2): If no update flags, or only
+ // alpha_update is set, we can save us the
+ // rendering into the bitmap (uh, it's not
+ // _that_ easy - for a forced redraw,
+ // e.g. when ending an animation, we always
+ // get UPDATE_FORCE here).
+
+ // render into this bitmap
+ if( !draw( pBitmapCanvas,
+ rMtf,
+ pAttr,
+ aTransform,
+ !aClip ? nullptr : &(*aClip),
+ rSubsets ) )
+ {
+ return false;
+ }
+
+ // render bitmap to screen, with given global
+ // alpha. Since the bitmap already contains
+ // pixel-equivalent output, we have to use the
+ // inverse view transformation, adjusted with
+ // the final shape output position (note:
+ // cannot simply change the view
+ // transformation here, as that would affect a
+ // possibly set clip!)
+ ::basegfx::B2DHomMatrix aBitmapTransform( aCanvasTransform );
+ OSL_ENSURE( aBitmapTransform.isInvertible(),
+ "ViewShape::render(): View transformation is singular!" );
+
+ aBitmapTransform.invert();
+
+ const basegfx::B2DHomMatrix aTranslation(basegfx::utils::createTranslateB2DHomMatrix(
+ aTmpRect.getMinX(), aTmpRect.getMinY()));
+
+ aBitmapTransform = aBitmapTransform * aTranslation;
+ pBitmap->setTransformation( aBitmapTransform );
+
+ // finally, render bitmap alpha-modulated
+ pBitmap->drawAlphaModulated( nAlpha );
+
+ return true;
+ }
+ }
+ }
+
+ // retrieve shape transformation, _with_ shape translation
+ // to actual page position.
+ const ::basegfx::B2DHomMatrix aTransform(
+ getShapeTransformation( rBounds,
+ pAttr ) );
+
+ return draw( rDestinationCanvas,
+ rMtf,
+ pAttr,
+ aTransform,
+ !aClip ? nullptr : &(*aClip),
+ rSubsets );
+ }
+
+
+ ViewShape::ViewShape( const ViewLayerSharedPtr& rViewLayer ) :
+ mpViewLayer( rViewLayer ),
+ maRenderers(),
+ mpSprite(),
+ mbAnimationMode( false ),
+ mbForceUpdate( true )
+ {
+ ENSURE_OR_THROW( mpViewLayer, "ViewShape::ViewShape(): Invalid View" );
+ }
+
+ const ViewLayerSharedPtr& ViewShape::getViewLayer() const
+ {
+ return mpViewLayer;
+ }
+
+ ViewShape::RendererCacheVector::iterator ViewShape::getCacheEntry( const ::cppcanvas::CanvasSharedPtr& rDestinationCanvas ) const
+ {
+ // lookup destination canvas - is there already a renderer
+ // created for that target?
+ RendererCacheVector::iterator aIter;
+ const RendererCacheVector::iterator aEnd( maRenderers.end() );
+
+ // already there?
+ if( (aIter=::std::find_if( maRenderers.begin(),
+ aEnd,
+ [&rDestinationCanvas]( const RendererCacheEntry& rCacheEntry )
+ { return rDestinationCanvas == rCacheEntry.getDestinationCanvas(); } ) ) == aEnd )
+ {
+ if( maRenderers.size() >= MAX_RENDER_CACHE_ENTRIES )
+ {
+ // cache size exceeded - prune entries. For now,
+ // simply remove the first one, which of course
+ // breaks for more complex access schemes. But in
+ // general, this leads to most recently used
+ // entries to reside at the end of the vector.
+ maRenderers.erase( maRenderers.begin() );
+
+ // ATTENTION: after this, both aIter and aEnd are
+ // invalid!
+ }
+
+ // not yet in cache - add default-constructed cache
+ // entry, to have something to return
+ maRenderers.emplace_back( );
+ aIter = maRenderers.end()-1;
+ }
+
+ return aIter;
+ }
+
+ ::cppcanvas::RendererSharedPtr ViewShape::getRenderer( const ::cppcanvas::CanvasSharedPtr& rDestinationCanvas,
+ const GDIMetaFileSharedPtr& rMtf,
+ const ShapeAttributeLayerSharedPtr& rAttr ) const
+ {
+ // lookup destination canvas - is there already a renderer
+ // created for that target?
+ const RendererCacheVector::iterator aIter(
+ getCacheEntry( rDestinationCanvas ) );
+
+ // now we have a valid entry, either way. call prefetch()
+ // on it, nevertheless - maybe the metafile changed, and
+ // the renderer still needs an update (prefetch() will
+ // detect that)
+ if( prefetch( *aIter,
+ rDestinationCanvas,
+ rMtf,
+ rAttr ) )
+ {
+ return aIter->mpRenderer;
+ }
+ else
+ {
+ // prefetch failed - renderer is invalid
+ return ::cppcanvas::RendererSharedPtr();
+ }
+ }
+
+ void ViewShape::invalidateRenderer() const
+ {
+ // simply clear the cache. Subsequent getRenderer() calls
+ // will regenerate the Renderers.
+ maRenderers.clear();
+ }
+
+ ::basegfx::B2DSize ViewShape::getAntialiasingBorder() const
+ {
+ ENSURE_OR_THROW( mpViewLayer->getCanvas(),
+ "ViewShape::getAntialiasingBorder(): Invalid ViewLayer canvas" );
+
+ const ::basegfx::B2DHomMatrix& rViewTransform(
+ mpViewLayer->getTransformation() );
+
+ // TODO(F1): As a quick shortcut (did not want to invert
+ // whole matrix here), taking only scale components of
+ // view transformation matrix. This will be wrong when
+ // e.g. shearing is involved.
+ const double nXBorder( ::cppcanvas::Canvas::ANTIALIASING_EXTRA_SIZE / rViewTransform.get(0,0) );
+ const double nYBorder( ::cppcanvas::Canvas::ANTIALIASING_EXTRA_SIZE / rViewTransform.get(1,1) );
+
+ return ::basegfx::B2DSize( nXBorder,
+ nYBorder );
+ }
+
+ void ViewShape::enterAnimationMode()
+ {
+ mbForceUpdate = true;
+ mbAnimationMode = true;
+ }
+
+ void ViewShape::leaveAnimationMode()
+ {
+ mpSprite.reset();
+ mbAnimationMode = false;
+ mbForceUpdate = true;
+ }
+
+ bool ViewShape::update( const GDIMetaFileSharedPtr& rMtf,
+ const RenderArgs& rArgs,
+ UpdateFlags nUpdateFlags,
+ bool bIsVisible ) const
+ {
+ ENSURE_OR_RETURN_FALSE( mpViewLayer->getCanvas(), "ViewShape::update(): Invalid layer canvas" );
+
+ // Shall we render to a sprite, or to a plain canvas?
+ if( mbAnimationMode )
+ return renderSprite( mpViewLayer,
+ rMtf,
+ rArgs.maOrigBounds,
+ rArgs.maBounds,
+ rArgs.maUnitBounds,
+ nUpdateFlags,
+ rArgs.mrAttr,
+ rArgs.mrSubsets,
+ rArgs.mnShapePriority,
+ bIsVisible );
+ else
+ return render( mpViewLayer->getCanvas(),
+ rMtf,
+ rArgs.maBounds,
+ rArgs.maUpdateBounds,
+ nUpdateFlags,
+ rArgs.mrAttr,
+ rArgs.mrSubsets,
+ bIsVisible );
+ }
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/slideshow/source/engine/shapes/viewshape.hxx b/slideshow/source/engine/shapes/viewshape.hxx
new file mode 100644
index 000000000..c7e1d564c
--- /dev/null
+++ b/slideshow/source/engine/shapes/viewshape.hxx
@@ -0,0 +1,319 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#ifndef INCLUDED_SLIDESHOW_SOURCE_ENGINE_SHAPES_VIEWSHAPE_HXX
+#define INCLUDED_SLIDESHOW_SOURCE_ENGINE_SHAPES_VIEWSHAPE_HXX
+
+#include <cppcanvas/renderer.hxx>
+#include <cppcanvas/bitmap.hxx>
+
+#include <basegfx/range/b2drectangle.hxx>
+#include <o3tl/typed_flags_set.hxx>
+
+#include <tools.hxx>
+#include <shapeattributelayer.hxx>
+#include <animatedsprite.hxx>
+#include <viewlayer.hxx>
+#include <doctreenode.hxx>
+
+#include <vector>
+#include <memory>
+
+enum class UpdateFlags
+{
+ NONE = 0x00,
+ Transformation = 0x01,
+ Clip = 0x02,
+ Alpha = 0x04,
+ Position = 0x08,
+ Content = 0x10,
+ Force = 0x20,
+};
+namespace o3tl {
+ template<> struct typed_flags<UpdateFlags> : is_typed_flags<UpdateFlags, 0x3f> {};
+}
+
+
+
+namespace slideshow::internal
+ {
+ /** This class is the viewable representation of a draw
+ document's XShape, associated to a specific View
+
+ The class is able to render the associated XShape on
+ View implementations.
+ */
+ class ViewShape
+ {
+ public:
+ /** Create a ViewShape for the given View
+
+ @param rView
+ The associated View object.
+ */
+ explicit ViewShape( const ViewLayerSharedPtr& rViewLayer );
+
+ ///Forbid copy construction
+ ViewShape(const ViewShape&) = delete;
+ /// Forbid copy assignment
+ ViewShape& operator=(const ViewShape&) = delete;
+
+ /** Query the associated view layer of this shape
+ */
+ const ViewLayerSharedPtr& getViewLayer() const;
+
+ /** Query dimension of a safety border around the shape for AA
+
+ If the view performs antialiasing, this method
+ calculates a safety border around the shape, in the
+ shape coordinate system, which is guaranteed to
+ include every pixel touched when rendering the shape.
+ */
+ ::basegfx::B2DSize getAntialiasingBorder() const;
+
+
+ // animation methods
+
+
+ /** Notify the ViewShape that an animation starts now
+
+ This method enters animation mode on the associate
+ target view. The shape can be animated in parallel on
+ different views.
+ */
+ void enterAnimationMode();
+
+ /** Notify the ViewShape that it is no longer animated
+
+ This methods ends animation mode on the associate
+ target view
+ */
+ void leaveAnimationMode();
+
+
+ // render methods
+
+
+ struct RenderArgs
+ {
+ /** Create render argument struct
+
+ @param rOrigBounds
+ The initial shape bounds
+
+ @param rUpdateBounds
+ The area covered by the shape
+
+ @param rBounds
+ The current shape bounds
+
+ @param rAttr
+ The current shape attribute set. Can be NULL, for
+ default attributes. Attention: stored as a reference,
+ thus, parameter object must stay valid!
+
+ @param rSubsets
+ Vector of subset rendering ranges. Attention:
+ stored as a reference, thus, parameter object must
+ stay valid!
+
+ @param nPrio
+ Shape priority
+ */
+ RenderArgs( const ::basegfx::B2DRectangle& rOrigBounds,
+ const ::basegfx::B2DRectangle& rUpdateBounds,
+ const ::basegfx::B2DRectangle& rBounds,
+ const ::basegfx::B2DRectangle& rUnitBounds,
+ const ShapeAttributeLayerSharedPtr& rAttr,
+ const VectorOfDocTreeNodes& rSubsets,
+ double nPrio ) :
+ maOrigBounds( rOrigBounds ),
+ maUpdateBounds( rUpdateBounds ),
+ maBounds( rBounds ),
+ maUnitBounds( rUnitBounds ),
+ mrAttr( rAttr ),
+ mrSubsets( rSubsets ),
+ mnShapePriority( nPrio )
+ {
+ }
+
+ const ::basegfx::B2DRectangle maOrigBounds;
+ const ::basegfx::B2DRectangle maUpdateBounds;
+ const ::basegfx::B2DRectangle maBounds;
+ const ::basegfx::B2DRectangle maUnitBounds;
+ const ShapeAttributeLayerSharedPtr& mrAttr;
+ const VectorOfDocTreeNodes& mrSubsets;
+ const double mnShapePriority;
+ };
+
+ /** Update the ViewShape
+
+ This method updates the ViewShape on the associated
+ view. If the shape is currently animated, the render
+ target is the sprite, otherwise the view's
+ canvas. This method does not render anything, if the
+ update flags are 0.
+
+ @param rMtf
+ The metafile representation of the shape
+
+ @param rArgs
+ Parameter structure, containing all necessary arguments
+
+ @param nUpdateFlags
+ Bitmask of things to update. Use FORCE to force a repaint.
+
+ @param bIsVisible
+ When false, the shape is fully invisible (and possibly
+ don't need to be painted)
+
+ @return whether the rendering finished successfully.
+ */
+ bool update( const GDIMetaFileSharedPtr& rMtf,
+ const RenderArgs& rArgs,
+ UpdateFlags nUpdateFlags,
+ bool bIsVisible ) const;
+
+ /** Retrieve renderer for given canvas and metafile.
+
+ If necessary, the renderer is created or updated for
+ the metafile and attribute layer.
+
+ @return a renderer that renders to the given
+ destination canvas
+ */
+ ::cppcanvas::RendererSharedPtr getRenderer( const ::cppcanvas::CanvasSharedPtr& rDestinationCanvas,
+ const GDIMetaFileSharedPtr& rMtf,
+ const ShapeAttributeLayerSharedPtr& rAttr ) const;
+
+
+ private:
+ struct RendererCacheEntry
+ {
+ RendererCacheEntry() :
+ mpDestinationCanvas(),
+ mpRenderer(),
+ mpMtf(),
+ mpLastBitmap(),
+ mpLastBitmapCanvas()
+ {
+ }
+
+ const ::cppcanvas::CanvasSharedPtr& getDestinationCanvas() const
+ {
+ return mpDestinationCanvas;
+ }
+
+ ::cppcanvas::CanvasSharedPtr mpDestinationCanvas;
+ ::cppcanvas::RendererSharedPtr mpRenderer;
+ GDIMetaFileSharedPtr mpMtf;
+ ::cppcanvas::BitmapSharedPtr mpLastBitmap;
+ ::cppcanvas::BitmapCanvasSharedPtr mpLastBitmapCanvas;
+ };
+
+ typedef ::std::vector< RendererCacheEntry > RendererCacheVector;
+
+
+ /** Prefetch Renderer for given canvas
+ */
+ static bool prefetch( RendererCacheEntry& io_rCacheEntry,
+ const ::cppcanvas::CanvasSharedPtr& rDestinationCanvas,
+ const GDIMetaFileSharedPtr& rMtf,
+ const ShapeAttributeLayerSharedPtr& rAttr );
+
+ /** Draw with prefetched Renderer to stored canvas
+
+ This method draws prefetched Renderer to its
+ associated canvas (which happens to be mpLastCanvas).
+ */
+ bool draw( const ::cppcanvas::CanvasSharedPtr& rDestinationCanvas,
+ const GDIMetaFileSharedPtr& rMtf,
+ const ShapeAttributeLayerSharedPtr& rAttr,
+ const ::basegfx::B2DHomMatrix& rTransform,
+ const ::basegfx::B2DPolyPolygon* pClip,
+ const VectorOfDocTreeNodes& rSubsets ) const;
+
+ /** Render shape to an active sprite
+ */
+ bool renderSprite( const ViewLayerSharedPtr& rViewLayer,
+ const GDIMetaFileSharedPtr& rMtf,
+ const ::basegfx::B2DRectangle& rOrigBounds,
+ const ::basegfx::B2DRectangle& rBounds,
+ const ::basegfx::B2DRectangle& rUnitBounds,
+ UpdateFlags nUpdateFlags,
+ const ShapeAttributeLayerSharedPtr& pAttr,
+ const VectorOfDocTreeNodes& rSubsets,
+ double nPrio,
+ bool bIsVisible ) const;
+
+ /** Render shape to given canvas
+ */
+ bool render( const ::cppcanvas::CanvasSharedPtr& rDestinationCanvas,
+ const GDIMetaFileSharedPtr& rMtf,
+ const ::basegfx::B2DRectangle& rBounds,
+ const ::basegfx::B2DRectangle& rUpdateBounds,
+ UpdateFlags nUpdateFlags,
+ const ShapeAttributeLayerSharedPtr& pAttr,
+ const VectorOfDocTreeNodes& rSubsets,
+ bool bIsVisible ) const;
+
+ enum{ MAX_RENDER_CACHE_ENTRIES=2 };
+
+ /** Retrieve a valid iterator to renderer cache entry
+
+ This method ensures that an internal limit of
+ MAX_RENDER_CACHE_ENTRIES is not exceeded.
+
+ @param rDestinationCanvas
+ Destination canvas to retrieve cache entry for
+
+ @return a valid iterator to a renderer cache entry for
+ the given canvas. The entry might be
+ default-constructed (if newly added)
+ */
+ RendererCacheVector::iterator getCacheEntry( const ::cppcanvas::CanvasSharedPtr& rDestinationCanvas ) const;
+
+ void invalidateRenderer() const;
+
+ /** The view layer this object is part of.
+
+ Needed for sprite creation
+ */
+ ViewLayerSharedPtr mpViewLayer;
+
+ /// A set of cached mtf/canvas combinations
+ mutable RendererCacheVector maRenderers;
+
+ /// The sprite object
+ mutable AnimatedSpriteSharedPtr mpSprite;
+
+ /// If true, render() calls go to the sprite
+ mutable bool mbAnimationMode;
+
+ /// If true, shape needs full repaint (and the sprite a setup, if any)
+ mutable bool mbForceUpdate;
+ };
+
+ typedef ::std::shared_ptr< ViewShape > ViewShapeSharedPtr;
+
+}
+
+#endif // INCLUDED_SLIDESHOW_SOURCE_ENGINE_SHAPES_VIEWSHAPE_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */