/* -*- 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace com::sun::star; namespace { // animated extractor // Necessary to filter a sequence of animated primitives from // a sequence of primitives to find out if animated or not. The decision for // what to decompose is hard-coded and only done for knowingly animated primitives // to not decompose too deeply and unnecessarily. This implies that the list // which is view-specific needs to be expanded by hand when new animated objects // are added. This may eventually be changed to a dynamically configurable approach // if necessary. class AnimatedExtractingProcessor2D : public drawinglayer::processor2d::BaseProcessor2D { protected: // the found animated primitives drawinglayer::primitive2d::Primitive2DContainer maPrimitive2DSequence; // text animation allowed? bool const mbTextAnimationAllowed : 1; // graphic animation allowed? bool const mbGraphicAnimationAllowed : 1; // as tooling, the process() implementation takes over API handling and calls this // virtual render method when the primitive implementation is BasePrimitive2D-based. virtual void processBasePrimitive2D(const drawinglayer::primitive2d::BasePrimitive2D& rCandidate) override; public: AnimatedExtractingProcessor2D( const drawinglayer::geometry::ViewInformation2D& rViewInformation, bool bTextAnimationAllowed, bool bGraphicAnimationAllowed); // data access const drawinglayer::primitive2d::Primitive2DContainer& getPrimitive2DSequence() const { return maPrimitive2DSequence; } }; AnimatedExtractingProcessor2D::AnimatedExtractingProcessor2D( const drawinglayer::geometry::ViewInformation2D& rViewInformation, bool bTextAnimationAllowed, bool bGraphicAnimationAllowed) : drawinglayer::processor2d::BaseProcessor2D(rViewInformation), maPrimitive2DSequence(), mbTextAnimationAllowed(bTextAnimationAllowed), mbGraphicAnimationAllowed(bGraphicAnimationAllowed) { } void AnimatedExtractingProcessor2D::processBasePrimitive2D(const drawinglayer::primitive2d::BasePrimitive2D& rCandidate) { // known implementation, access directly switch(rCandidate.getPrimitive2DID()) { // add and accept animated primitives directly, no need to decompose case PRIMITIVE2D_ID_ANIMATEDSWITCHPRIMITIVE2D : case PRIMITIVE2D_ID_ANIMATEDBLINKPRIMITIVE2D : case PRIMITIVE2D_ID_ANIMATEDINTERPOLATEPRIMITIVE2D : { const drawinglayer::primitive2d::AnimatedSwitchPrimitive2D& rSwitchPrimitive = static_cast< const drawinglayer::primitive2d::AnimatedSwitchPrimitive2D& >(rCandidate); if((rSwitchPrimitive.isTextAnimation() && mbTextAnimationAllowed) || (rSwitchPrimitive.isGraphicAnimation() && mbGraphicAnimationAllowed)) { const drawinglayer::primitive2d::Primitive2DReference xReference(const_cast< drawinglayer::primitive2d::BasePrimitive2D* >(&rCandidate)); maPrimitive2DSequence.push_back(xReference); } break; } // decompose animated gifs where SdrGrafPrimitive2D produces a GraphicPrimitive2D // which then produces the animation infos (all when used/needed) case PRIMITIVE2D_ID_SDRGRAFPRIMITIVE2D : case PRIMITIVE2D_ID_GRAPHICPRIMITIVE2D : // decompose SdrObjects with evtl. animated text case PRIMITIVE2D_ID_SDRCAPTIONPRIMITIVE2D : case PRIMITIVE2D_ID_SDRCONNECTORPRIMITIVE2D : case PRIMITIVE2D_ID_SDRCUSTOMSHAPEPRIMITIVE2D : case PRIMITIVE2D_ID_SDRELLIPSEPRIMITIVE2D : case PRIMITIVE2D_ID_SDRELLIPSESEGMENTPRIMITIVE2D : case PRIMITIVE2D_ID_SDRMEASUREPRIMITIVE2D : case PRIMITIVE2D_ID_SDRPATHPRIMITIVE2D : case PRIMITIVE2D_ID_SDRRECTANGLEPRIMITIVE2D : // #121194# With Graphic as Bitmap FillStyle, also check // for primitives filled with animated graphics case PRIMITIVE2D_ID_POLYPOLYGONGRAPHICPRIMITIVE2D: case PRIMITIVE2D_ID_FILLGRAPHICPRIMITIVE2D: case PRIMITIVE2D_ID_TRANSFORMPRIMITIVE2D: // decompose evtl. animated text contained in MaskPrimitive2D // or group primitives case PRIMITIVE2D_ID_MASKPRIMITIVE2D : case PRIMITIVE2D_ID_GROUPPRIMITIVE2D : { process(rCandidate); break; } default : { // nothing to do for the rest break; } } } } // end of anonymous namespace namespace sdr { namespace contact { ViewObjectContact::ViewObjectContact(ObjectContact& rObjectContact, ViewContact& rViewContact) : mrObjectContact(rObjectContact), mrViewContact(rViewContact), maObjectRange(), mxPrimitive2DSequence(), maGridOffset(0.0, 0.0), mbLazyInvalidate(false) { // make the ViewContact remember me mrViewContact.AddViewObjectContact(*this); // make the ObjectContact remember me mrObjectContact.AddViewObjectContact(*this); } ViewObjectContact::~ViewObjectContact() { // invalidate in view if(!maObjectRange.isEmpty()) { GetObjectContact().InvalidatePartOfView(maObjectRange); } // delete PrimitiveAnimation mpPrimitiveAnimation.reset(); // take care of remembered ObjectContact. Remove from // OC first. The VC removal (below) CAN trigger a StopGettingViewed() // which (depending of its implementation) may destroy other OCs. This // can trigger the deletion of the helper OC of a page visualising object // which IS the OC of this object. Eventually StopGettingViewed() needs // to get asynchron later GetObjectContact().RemoveViewObjectContact(*this); // take care of remembered ViewContact GetViewContact().RemoveViewObjectContact(*this); } const basegfx::B2DRange& ViewObjectContact::getObjectRange() const { if(maObjectRange.isEmpty()) { const drawinglayer::geometry::ViewInformation2D& rViewInfo2D = GetObjectContact().getViewInformation2D(); basegfx::B2DRange aTempRange = GetViewContact().getRange(rViewInfo2D); if (!aTempRange.isEmpty()) { const_cast< ViewObjectContact* >(this)->maObjectRange = aTempRange; } else { // if range is not computed (new or LazyInvalidate objects), force it const DisplayInfo aDisplayInfo; const drawinglayer::primitive2d::Primitive2DContainer xSequence(getPrimitive2DSequence(aDisplayInfo)); if(!xSequence.empty()) { const_cast< ViewObjectContact* >(this)->maObjectRange = xSequence.getB2DRange(rViewInfo2D); } } } return maObjectRange; } void ViewObjectContact::ActionChanged() { if(!mbLazyInvalidate) { // set local flag mbLazyInvalidate = true; // force ObjectRange getObjectRange(); if(!maObjectRange.isEmpty()) { // invalidate current valid range GetObjectContact().InvalidatePartOfView(maObjectRange); // reset ObjectRange, it needs to be recalculated maObjectRange.reset(); } // register at OC for lazy invalidate GetObjectContact().setLazyInvalidate(*this); } } void ViewObjectContact::triggerLazyInvalidate() { if(mbLazyInvalidate) { // reset flag mbLazyInvalidate = false; // force ObjectRange getObjectRange(); if(!maObjectRange.isEmpty()) { // invalidate current valid range GetObjectContact().InvalidatePartOfView(maObjectRange); } } } // Take some action when new objects are inserted void ViewObjectContact::ActionChildInserted(ViewContact& rChild) { // force creation of the new VOC and trigger it's refresh, so it // will take part in LazyInvalidate immediately rChild.GetViewObjectContact(GetObjectContact()).ActionChanged(); // forward action to ObjectContact // const ViewObjectContact& rChildVOC = rChild.GetViewObjectContact(GetObjectContact()); // GetObjectContact().InvalidatePartOfView(rChildVOC.getObjectRange()); } void ViewObjectContact::checkForPrimitive2DAnimations() { // remove old one mpPrimitiveAnimation.reset(); // check for animated primitives if(!mxPrimitive2DSequence.empty()) { const bool bTextAnimationAllowed(GetObjectContact().IsTextAnimationAllowed()); const bool bGraphicAnimationAllowed(GetObjectContact().IsGraphicAnimationAllowed()); if(bTextAnimationAllowed || bGraphicAnimationAllowed) { AnimatedExtractingProcessor2D aAnimatedExtractor(GetObjectContact().getViewInformation2D(), bTextAnimationAllowed, bGraphicAnimationAllowed); aAnimatedExtractor.process(mxPrimitive2DSequence); if(!aAnimatedExtractor.getPrimitive2DSequence().empty()) { // derived primitiveList is animated, setup new PrimitiveAnimation mpPrimitiveAnimation.reset( new sdr::animation::PrimitiveAnimation(*this, aAnimatedExtractor.getPrimitive2DSequence()) ); } } } } drawinglayer::primitive2d::Primitive2DContainer ViewObjectContact::createPrimitive2DSequence(const DisplayInfo& rDisplayInfo) const { // get the view-independent Primitive from the viewContact drawinglayer::primitive2d::Primitive2DContainer xRetval(GetViewContact().getViewIndependentPrimitive2DContainer()); if(!xRetval.empty()) { // handle GluePoint if(!GetObjectContact().isOutputToPrinter() && GetObjectContact().AreGluePointsVisible()) { const drawinglayer::primitive2d::Primitive2DContainer xGlue(GetViewContact().createGluePointPrimitive2DSequence()); if(!xGlue.empty()) { xRetval.append(xGlue); } } // handle ghosted if(isPrimitiveGhosted(rDisplayInfo)) { const basegfx::BColor aRGBWhite(1.0, 1.0, 1.0); const basegfx::BColorModifierSharedPtr aBColorModifier( new basegfx::BColorModifier_interpolate( aRGBWhite, 0.5)); const drawinglayer::primitive2d::Primitive2DReference xReference( new drawinglayer::primitive2d::ModifiedColorPrimitive2D( xRetval, aBColorModifier)); xRetval = drawinglayer::primitive2d::Primitive2DContainer { xReference }; } } return xRetval; } drawinglayer::primitive2d::Primitive2DContainer const & ViewObjectContact::getPrimitive2DSequence(const DisplayInfo& rDisplayInfo) const { drawinglayer::primitive2d::Primitive2DContainer xNewPrimitiveSequence; // take care of redirectors and create new list ViewObjectContactRedirector* pRedirector = GetObjectContact().GetViewObjectContactRedirector(); if(pRedirector) { xNewPrimitiveSequence = pRedirector->createRedirectedPrimitive2DSequence(*this, rDisplayInfo); } else { xNewPrimitiveSequence = createPrimitive2DSequence(rDisplayInfo); } // local up-to-date checks. New list different from local one? if(mxPrimitive2DSequence != xNewPrimitiveSequence) { // has changed, copy content const_cast< ViewObjectContact* >(this)->mxPrimitive2DSequence = xNewPrimitiveSequence; // check for animated stuff const_cast< ViewObjectContact* >(this)->checkForPrimitive2DAnimations(); // always update object range when PrimitiveSequence changes const drawinglayer::geometry::ViewInformation2D& rViewInformation2D(GetObjectContact().getViewInformation2D()); const_cast< ViewObjectContact* >(this)->maObjectRange = mxPrimitive2DSequence.getB2DRange(rViewInformation2D); // check and eventually embed to GridOffset transform primitive if(GetObjectContact().supportsGridOffsets()) { const basegfx::B2DVector& rGridOffset(getGridOffset()); if(0.0 != rGridOffset.getX() || 0.0 != rGridOffset.getY()) { const basegfx::B2DHomMatrix aTranslateGridOffset( basegfx::utils::createTranslateB2DHomMatrix( rGridOffset)); const drawinglayer::primitive2d::Primitive2DReference aEmbed( new drawinglayer::primitive2d::TransformPrimitive2D( aTranslateGridOffset, mxPrimitive2DSequence)); // Set values at local data. So for now, the mechanism is to reset some of the // defining things (mxPrimitive2DSequence, maGridOffset) and re-create the // buffered data (including maObjectRange). It *could* be changed to keep // the unmodified PrimitiveSequence and only update the GridOffset, but this // would require a 2nd instance of maObjectRange and mxPrimitive2DSequence. I // started doing so, but it just makes the code more complicated. For now, // just allow re-creation of the PrimitiveSequence (and removing buffered // decomposed content of it). May be optimized, though. OTOH it only happens // in calc which traditionally does not have a huge amount of DrawObjects anyways. const_cast< ViewObjectContact* >(this)->mxPrimitive2DSequence = drawinglayer::primitive2d::Primitive2DContainer { aEmbed }; const_cast< ViewObjectContact* >(this)->maObjectRange.transform(aTranslateGridOffset); } } } // return current Primitive2DContainer return mxPrimitive2DSequence; } bool ViewObjectContact::isPrimitiveVisible(const DisplayInfo& /*rDisplayInfo*/) const { // default: always visible return true; } bool ViewObjectContact::isPrimitiveGhosted(const DisplayInfo& rDisplayInfo) const { // default: standard check return (GetObjectContact().DoVisualizeEnteredGroup() && !GetObjectContact().isOutputToPrinter() && rDisplayInfo.IsGhostedDrawModeActive()); } drawinglayer::primitive2d::Primitive2DContainer ViewObjectContact::getPrimitive2DSequenceHierarchy(DisplayInfo& rDisplayInfo) const { drawinglayer::primitive2d::Primitive2DContainer xRetval; // check model-view visibility if(isPrimitiveVisible(rDisplayInfo)) { xRetval = getPrimitive2DSequence(rDisplayInfo); if(!xRetval.empty()) { // get ranges const drawinglayer::geometry::ViewInformation2D& rViewInformation2D(GetObjectContact().getViewInformation2D()); const basegfx::B2DRange aObjectRange(xRetval.getB2DRange(rViewInformation2D)); const basegfx::B2DRange aViewRange(rViewInformation2D.getViewport()); // check geometrical visibility bool bVisible = aViewRange.isEmpty() || aViewRange.overlaps(aObjectRange); if(!bVisible) { // not visible, release xRetval.clear(); } } } return xRetval; } drawinglayer::primitive2d::Primitive2DContainer ViewObjectContact::getPrimitive2DSequenceSubHierarchy(DisplayInfo& rDisplayInfo) const { const sal_uInt32 nSubHierarchyCount(GetViewContact().GetObjectCount()); drawinglayer::primitive2d::Primitive2DContainer xSeqRetval; for(sal_uInt32 a(0); a < nSubHierarchyCount; a++) { const ViewObjectContact& rCandidate(GetViewContact().GetViewContact(a).GetViewObjectContact(GetObjectContact())); xSeqRetval.append(rCandidate.getPrimitive2DSequenceHierarchy(rDisplayInfo)); } return xSeqRetval; } // Support getting a GridOffset per object and view for non-linear ViewToDevice // transformation (calc). On-demand created by delegating to the ObjectContact // (->View) that has then all needed information const basegfx::B2DVector& ViewObjectContact::getGridOffset() const { if(0.0 == maGridOffset.getX() && 0.0 == maGridOffset.getY() && GetObjectContact().supportsGridOffsets()) { // create on-demand GetObjectContact().calculateGridOffsetForViewOjectContact(const_cast(this)->maGridOffset, *this); } return maGridOffset; } void ViewObjectContact::resetGridOffset() { // reset buffered GridOffset itself maGridOffset.setX(0.0); maGridOffset.setY(0.0); // also reset sequence to get a re-calculation when GridOffset changes mxPrimitive2DSequence.clear(); } }} /* vim:set shiftwidth=4 softtabstop=4 expandtab: */