diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
commit | ed5640d8b587fbcfed7dd7967f3de04b37a76f26 (patch) | |
tree | 7a5f7c6c9d02226d7471cb3cc8fbbf631b415303 /slideshow/source/engine/shapes | |
parent | Initial commit. (diff) | |
download | libreoffice-ed5640d8b587fbcfed7dd7967f3de04b37a76f26.tar.xz libreoffice-ed5640d8b587fbcfed7dd7967f3de04b37a76f26.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')
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: */ |