summaryrefslogtreecommitdiffstats
path: root/slideshow/source/engine/shapes/drawshape.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'slideshow/source/engine/shapes/drawshape.cxx')
-rw-r--r--slideshow/source/engine/shapes/drawshape.cxx1228
1 files changed, 1228 insertions, 0 deletions
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: */