/* -*- 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 <hintids.hxx>
#include <tools/urlobj.hxx>
#include <vcl/imapobj.hxx>
#include <vcl/imap.hxx>
#include <svl/urihelper.hxx>
#include <sfx2/progress.hxx>
#include <sfx2/printer.hxx>
#include <editeng/udlnitem.hxx>
#include <editeng/colritem.hxx>
#include <editeng/boxitem.hxx>
#include <fmturl.hxx>
#include <fmtsrnd.hxx>
#include <frmfmt.hxx>
#include <swrect.hxx>
#include <fesh.hxx>
#include <doc.hxx>
#include <IDocumentSettingAccess.hxx>
#include <IDocumentStylePoolAccess.hxx>
#include <IDocumentDeviceAccess.hxx>
#include <IDocumentLayoutAccess.hxx>
#include <flyfrm.hxx>
#include <flyfrms.hxx>
#include <frmtool.hxx>
#include <viewopt.hxx>
#include <viewimp.hxx>
#include <pam.hxx>
#include <hints.hxx>
#include <rootfrm.hxx>
#include <dflyobj.hxx>
#include <pagefrm.hxx>
#include <notxtfrm.hxx>
#include <grfatr.hxx>
#include <charatr.hxx>
#include <ndnotxt.hxx>
#include <ndgrf.hxx>
#include <ndole.hxx>
#include <swregion.hxx>
#include <poolfmt.hxx>
#include <strings.hrc>
#include <accessibilityoptions.hxx>
#include <com/sun/star/embed/EmbedMisc.hpp>
#include <com/sun/star/embed/EmbedStates.hpp>
#include <com/sun/star/embed/XEmbeddedObject.hpp>
#include <svtools/embedhlp.hxx>
#include <dview.hxx>
#include <basegfx/matrix/b2dhommatrix.hxx>
#include <drawinglayer/processor2d/baseprocessor2d.hxx>
#include <drawinglayer/primitive2d/graphicprimitive2d.hxx>
#include <basegfx/matrix/b2dhommatrixtools.hxx>
#include <basegfx/utils/b2dclipstate.hxx>
#include <drawinglayer/processor2d/processor2dtools.hxx>
#include <txtfly.hxx>
#include <drawinglayer/primitive2d/maskprimitive2d.hxx>
#include <drawinglayer/primitive2d/objectinfoprimitive2d.hxx>
#include <osl/diagnose.h>

// MM02 needed for VOC mechanism and getting the OC - may be moved to an own file
#include <svx/sdrpagewindow.hxx>
#include <svx/svdoutl.hxx>
#include <svx/svdpage.hxx>
#include <svx/svdpagv.hxx>
#include <svx/unopage.hxx>
#include <svx/sdr/contact/viewcontact.hxx>
#include <svx/sdr/contact/viewobjectcontact.hxx>
#include <svx/sdr/contact/objectcontact.hxx>
#include <svx/sdr/contact/displayinfo.hxx>

using namespace com::sun::star;

static bool GetRealURL( const SwGrfNode& rNd, OUString& rText )
{
    bool bRet = rNd.GetFileFilterNms( &rText, nullptr );
    if( bRet )
        rText = URIHelper::removePassword( rText, INetURLObject::EncodeMechanism::WasEncoded,
                                           INetURLObject::DecodeMechanism::Unambiguous);
    if (rText.startsWith("data:image")) rText = "inline image";

    return bRet;
}

static void lcl_PaintReplacement( const SwRect &rRect, const OUString &rText,
                           const SwViewShell &rSh, const SwNoTextFrame *pFrame,
                           bool bDefect )
{
    static vcl::Font aFont = []()
    {
        vcl::Font tmp;
        tmp.SetWeight( WEIGHT_BOLD );
        tmp.SetStyleName( OUString() );
        tmp.SetFamilyName(u"Noto Sans"_ustr);
        tmp.SetFamily( FAMILY_SWISS );
        tmp.SetTransparent( true );
        return tmp;
    }();

    Color aCol( COL_RED );
    FontLineStyle eUnderline = LINESTYLE_NONE;
    const SwFormatURL &rURL = pFrame->FindFlyFrame()->GetFormat()->GetURL();
    if( !rURL.GetURL().isEmpty() || rURL.GetMap() )
    {
        bool bVisited = false;
        if ( rURL.GetMap() )
        {
            ImageMap *pMap = const_cast<ImageMap*>(rURL.GetMap());
            for( size_t i = 0; i < pMap->GetIMapObjectCount(); ++i )
            {
                IMapObject *pObj = pMap->GetIMapObject( i );
                if( rSh.GetDoc()->IsVisitedURL( pObj->GetURL() ) )
                {
                    bVisited = true;
                    break;
                }
            }
        }
        else if ( !rURL.GetURL().isEmpty() )
            bVisited = rSh.GetDoc()->IsVisitedURL( rURL.GetURL() );

        SwFormat *pFormat = rSh.GetDoc()->getIDocumentStylePoolAccess().GetFormatFromPool( o3tl::narrowing<sal_uInt16>
            (bVisited ? RES_POOLCHR_INET_VISIT : RES_POOLCHR_INET_NORMAL ) );
        aCol = pFormat->GetColor().GetValue();
        eUnderline = pFormat->GetUnderline().GetLineStyle();
    }

    aFont.SetUnderline( eUnderline );
    aFont.SetColor( aCol );

    const Bitmap& rBmp = bDefect ? const_cast<SwViewShell&>(rSh).GetErrorBitmap()
                                 : const_cast<SwViewShell&>(rSh).GetReplacementBitmap();
    Graphic::DrawEx(*rSh.GetOut(), rText, aFont, rBmp, rRect.Pos(), rRect.SSize());
}

SwNoTextFrame::SwNoTextFrame(SwNoTextNode * const pNode, SwFrame* pSib )
:   SwContentFrame( pNode, pSib )
{
    mnFrameType = SwFrameType::NoTxt;
}

SwContentFrame *SwNoTextNode::MakeFrame( SwFrame* pSib )
{
    return new SwNoTextFrame(this, pSib);
}

void SwNoTextFrame::DestroyImpl()
{
    StopAnimation();

    SwContentFrame::DestroyImpl();
}

SwNoTextFrame::~SwNoTextFrame()
{
}

void SetOutDev( SwViewShell *pSh, OutputDevice *pOut )
{
    pSh->mpOut = pOut;
}

static void lcl_ClearArea( const SwFrame &rFrame,
                    vcl::RenderContext &rOut, const SwRect& rPtArea,
                    const SwRect &rGrfArea )
{
    SwRegionRects aRegion( rPtArea, 4 );
    aRegion -= rGrfArea;

    if ( aRegion.empty() )
        return;

    const SvxBrushItem *pItem;
    std::optional<Color> xCol;
    SwRect aOrigRect;
    drawinglayer::attribute::SdrAllFillAttributesHelperPtr aFillAttributes;

    if ( rFrame.GetBackgroundBrush( aFillAttributes, pItem, xCol, aOrigRect, false, /*bConsiderTextBox=*/false ) )
    {
        SwRegionRects const region(rPtArea);
        basegfx::utils::B2DClipState aClipState;
        const bool bDone(::DrawFillAttributes(aFillAttributes, aOrigRect, region, aClipState, rOut));

        if(!bDone)
        {
            for( const auto &rRegion : aRegion )
            {
                ::DrawGraphic(pItem, rOut, aOrigRect, rRegion);
            }
        }
    }
    else
    {
        auto popIt = rOut.ScopedPush(vcl::PushFlags::FILLCOLOR | vcl::PushFlags::LINECOLOR);
        rOut.SetFillColor( rFrame.getRootFrame()->GetCurrShell()->Imp()->GetRetoucheColor());
        rOut.SetLineColor();
        for( const auto &rRegion : aRegion )
            rOut.DrawRect( rRegion.SVRect() );
    }
}

void SwNoTextFrame::PaintSwFrame(vcl::RenderContext& rRenderContext, SwRect const& rRect, PaintFrameMode) const
{
    if ( getFrameArea().IsEmpty() )
        return;

    const SwViewShell* pSh = getRootFrame()->GetCurrShell();
    if( !pSh->GetViewOptions()->IsGraphic() )
    {
        StopAnimation();
        // #i6467# - no paint of placeholder for page preview
        if ( pSh->GetWin() && !pSh->IsPreview() )
        {
            const SwNoTextNode* pNd = GetNode()->GetNoTextNode();
            OUString aText( pNd->GetTitle() );
            if ( aText.isEmpty() && pNd->IsGrfNode() )
                GetRealURL( *static_cast<const SwGrfNode*>(pNd), aText );
            if( aText.isEmpty() )
                aText = FindFlyFrame()->GetFormat()->GetName().toString();
            lcl_PaintReplacement( getFrameArea(), aText, *pSh, this, false );
        }
        return;
    }

    if( pSh->GetAccessibilityOptions()->IsStopAnimatedGraphics() ||
    // #i9684# Stop animation during printing/pdf export
       !pSh->GetWin() )
        StopAnimation();

    SfxProgress::EnterLock(); // No progress reschedules in paint (SwapIn)

    rRenderContext.Push();
    bool bClip = true;
    tools::PolyPolygon aPoly;

    SwNoTextNode& rNoTNd = const_cast<SwNoTextNode&>(*static_cast<const SwNoTextNode*>(GetNode()));
    SwGrfNode* pGrfNd = rNoTNd.GetGrfNode();
    if( pGrfNd )
        pGrfNd->SetFrameInPaint( true );

    // #i13147# - add 2nd parameter with value <true> to
    // method call <FindFlyFrame().GetContour(..)> to indicate that it is called
    // for paint in order to avoid load of the intrinsic graphic.
    if ( ( !rRenderContext.GetConnectMetaFile() ||
           !pSh->GetWin() ) &&
         FindFlyFrame()->GetContour( aPoly, true )
       )
    {
        // don't clip if related compatibility flag is set
        const IDocumentSettingAccess& rIDSA = pSh->GetDoc()->getIDocumentSettingAccess();
        if (!rIDSA.get(DocumentSettingId::NO_CLIPPING_WITH_WRAP_POLYGON))
            rRenderContext.SetClipRegion(vcl::Region(aPoly));
        bClip = false;
    }

    SwRect aOrigPaint( rRect );
    if ( HasAnimation() && pSh->GetWin() )
    {
        aOrigPaint = getFrameArea(); aOrigPaint += getFramePrintArea().Pos();
    }

    SwRect aGrfArea( getFrameArea() );
    SwRect aPaintArea( aGrfArea );

    // In case the picture fly frm was clipped, render it with the origin
    // size instead of scaling it
    if ( pGrfNd && rNoTNd.getIDocumentSettingAccess()->get( DocumentSettingId::CLIPPED_PICTURES ) )
    {
        auto pFindFly = FindFlyFrame();
        if (pFindFly && pFindFly->IsFlyFreeFrame())
        {
            const SwFlyFreeFrame *pFly = static_cast< const SwFlyFreeFrame* >( pFindFly );
            bool bGetUnclippedFrame=true;
            const SvxBoxItem* pBoxItem;
            if( pFly->GetFormat() && (pBoxItem = pFly->GetFormat()->GetItemIfSet(RES_BOX, false)) )
            {
                if( pBoxItem->HasBorder( /*bTreatPaddingAsBorder*/true) )
                    bGetUnclippedFrame = false;
            }

            if( bGetUnclippedFrame )
                aGrfArea = SwRect( getFrameArea().Pos( ), pFly->GetUnclippedFrame( ).SSize( ) );
        }
    }

    aPaintArea.Intersection_( aOrigPaint );

    SwRect aNormal( getFrameArea().Pos() + getFramePrintArea().Pos(), getFramePrintArea().SSize() );
    aNormal.Justify(); // Normalized rectangle for the comparisons

    if( aPaintArea.Overlaps( aNormal ) )
    {
        // Calculate the four to-be-deleted rectangles
        if( pSh->GetWin() )
            ::lcl_ClearArea( *this, rRenderContext, aPaintArea, aNormal );

        // The intersection of the PaintArea and the Bitmap contains the absolutely visible area of the Frame
        aPaintArea.Intersection_( aNormal );

        if ( bClip )
            rRenderContext.IntersectClipRegion( aPaintArea.SVRect() );
        /// delete unused 3rd parameter
        PaintPicture( &rRenderContext, aGrfArea );
    }
    else
        // If it's not visible, simply delete the given Area
        lcl_ClearArea( *this, rRenderContext, aPaintArea, SwRect() );
    if( pGrfNd )
        pGrfNd->SetFrameInPaint( false );

    rRenderContext.Pop();
    SfxProgress::LeaveLock();
}

/** Calculate the position and the size of the graphic in the Frame,
    corresponding to the current graphic attributes

    @param Point  the position in the Frame (also returned)
    @param Size   the graphic's size (also returned)
    @param nMirror the current mirror attribute
*/
static void lcl_CalcRect( Point& rPt, Size& rDim, MirrorGraph nMirror )
{
    if( nMirror == MirrorGraph::Vertical || nMirror == MirrorGraph::Both )
    {
        rPt.setX(rPt.getX() + rDim.Width() -1);
        rDim.setWidth( -rDim.Width() );
    }

    if( nMirror == MirrorGraph::Horizontal || nMirror == MirrorGraph::Both )
    {
        rPt.setY(rPt.getY() + rDim.Height() -1);
        rDim.setHeight( -rDim.Height() );
    }
}

/** Calculate the Bitmap's position and the size within the passed rectangle */
void SwNoTextFrame::GetGrfArea( SwRect &rRect, SwRect* pOrigRect ) const
{
    // Currently only used for scaling, cropping and mirroring the contour of graphics!
    // Everything else is handled by GraphicObject
    // We put the graphic's visible rectangle into rRect.
    // pOrigRect contains position and size of the whole graphic.

    // RotateFlyFrame3: SwFrame may be transformed. Get untransformed
    // SwRect(s) as base of calculation
    const TransformableSwFrame* pTransformableSwFrame(getTransformableSwFrame());
    const SwRect aFrameArea(pTransformableSwFrame ? pTransformableSwFrame->getUntransformedFrameArea() : getFrameArea());
    const SwRect aFramePrintArea(pTransformableSwFrame ? pTransformableSwFrame->getUntransformedFramePrintArea() : getFramePrintArea());

    const SwAttrSet& rAttrSet = GetNode()->GetSwAttrSet();
    const SwCropGrf& rCrop = rAttrSet.GetCropGrf();
    MirrorGraph nMirror = rAttrSet.GetMirrorGrf().GetValue();

    if( rAttrSet.GetMirrorGrf().IsGrfToggle() )
    {
        if( !(FindPageFrame()->GetVirtPageNum() % 2) )
        {
            switch ( nMirror )
            {
                case MirrorGraph::Dont: nMirror = MirrorGraph::Vertical; break;
                case MirrorGraph::Vertical: nMirror = MirrorGraph::Dont; break;
                case MirrorGraph::Horizontal: nMirror = MirrorGraph::Both; break;
                default: nMirror = MirrorGraph::Horizontal; break;
            }
        }
    }

    // We read graphic from the Node, if needed.
    // It may fail, however.
    tools::Long nLeftCrop, nRightCrop, nTopCrop, nBottomCrop;
    Size aOrigSz( static_cast<const SwNoTextNode*>(GetNode())->GetTwipSize() );
    if ( !aOrigSz.Width() )
    {
        aOrigSz.setWidth( aFramePrintArea.Width() );
        nLeftCrop  = -rCrop.GetLeft();
        nRightCrop = -rCrop.GetRight();
    }
    else
    {
        nLeftCrop = std::max( aOrigSz.Width() -
                            (rCrop.GetRight() + rCrop.GetLeft()), tools::Long(1) );
        const double nScale = double(aFramePrintArea.Width())  / double(nLeftCrop);
        nLeftCrop  = tools::Long(nScale * -rCrop.GetLeft() );
        nRightCrop = tools::Long(nScale * -rCrop.GetRight() );
    }

    // crop values have to be mirrored too
    if( nMirror == MirrorGraph::Vertical || nMirror == MirrorGraph::Both )
    {
        tools::Long nTmpCrop = nLeftCrop;
        nLeftCrop = nRightCrop;
        nRightCrop= nTmpCrop;
    }

    if( !aOrigSz.Height() )
    {
        aOrigSz.setHeight( aFramePrintArea.Height() );
        nTopCrop   = -rCrop.GetTop();
        nBottomCrop= -rCrop.GetBottom();
    }
    else
    {
        nTopCrop = std::max( aOrigSz.Height() - (rCrop.GetTop() + rCrop.GetBottom()), tools::Long(1) );
        const double nScale = double(aFramePrintArea.Height()) / double(nTopCrop);
        nTopCrop   = tools::Long(nScale * -rCrop.GetTop() );
        nBottomCrop= tools::Long(nScale * -rCrop.GetBottom() );
    }

    // crop values have to be mirrored too
    if( nMirror == MirrorGraph::Horizontal || nMirror == MirrorGraph::Both )
    {
        tools::Long nTmpCrop = nTopCrop;
        nTopCrop   = nBottomCrop;
        nBottomCrop= nTmpCrop;
    }

    Size  aVisSz( aFramePrintArea.SSize() );
    Size  aGrfSz( aVisSz );
    Point aVisPt( aFrameArea.Pos() + aFramePrintArea.Pos() );
    Point aGrfPt( aVisPt );

    // Set the "visible" rectangle first
    if ( nLeftCrop > 0 )
    {
        aVisPt.setX(aVisPt.getX() + nLeftCrop);
        aVisSz.AdjustWidth( -nLeftCrop );
    }
    if ( nTopCrop > 0 )
    {
        aVisPt.setY(aVisPt.getY() + nTopCrop);
        aVisSz.AdjustHeight( -nTopCrop );
    }
    if ( nRightCrop > 0 )
        aVisSz.AdjustWidth( -nRightCrop );
    if ( nBottomCrop > 0 )
        aVisSz.AdjustHeight( -nBottomCrop );

    rRect.Pos  ( aVisPt );
    rRect.SSize( aVisSz );

    // Calculate the whole graphic if needed
    if ( !pOrigRect )
        return;

    Size aTmpSz( aGrfSz );
    aGrfPt.setX(aGrfPt.getX() + nLeftCrop);
    aTmpSz.AdjustWidth( -(nLeftCrop + nRightCrop) );
    aGrfPt.setY(aGrfPt.getY() + nTopCrop);
    aTmpSz.AdjustHeight( -(nTopCrop + nBottomCrop) );

    if( MirrorGraph::Dont != nMirror )
        lcl_CalcRect( aGrfPt, aTmpSz, nMirror );

    pOrigRect->Pos  ( aGrfPt );
    pOrigRect->SSize( aTmpSz );
}

/** By returning the surrounding Fly's size which equals the graphic's size */
const Size& SwNoTextFrame::GetSize() const
{
    // Return the Frame's size
    const SwFrame *pFly = FindFlyFrame();
    if( !pFly )
        pFly = this;
    return pFly->getFramePrintArea().SSize();
}

void SwNoTextFrame::MakeAll(vcl::RenderContext* pRenderContext)
{
    // RotateFlyFrame3 - inner frame. Get rotation and check if used
    const double fRotation(getLocalFrameRotation());
    const bool bRotated(!basegfx::fTools::equalZero(fRotation));

    if(bRotated)
    {
        SwFlyFreeFrame* pUpperFly(dynamic_cast< SwFlyFreeFrame* >(GetUpper()));

        if(pUpperFly)
        {
            if(!pUpperFly->isFrameAreaDefinitionValid())
            {
                // RotateFlyFrame3: outer frame *needs* to be layouted first, force this by calling
                // it's ::Calc directly
                pUpperFly->Calc(pRenderContext);
            }

            // Reset outer frame to unrotated state. This is necessary to make the
            // layouting below work as currently implemented in Writer. As expected
            // using Transformations allows to do this on the fly due to all information
            // being included there.
            // The full solution would be to adapt the whole layouting
            // process of Writer to take care of Transformations, but that
            // is currently beyond scope
            if(pUpperFly->isTransformableSwFrame())
            {
                pUpperFly->getTransformableSwFrame()->restoreFrameAreas();
            }
        }

        // Re-layout may be partially (see all isFrameAreaDefinitionValid() flags),
        // so resetting the local SwFrame(s) in the local SwFrameAreaDefinition is also
        // needed (e.g. for PrintPreview).
        // Reset to BoundAreas will be done below automatically
        if(isTransformableSwFrame())
        {
            getTransformableSwFrame()->restoreFrameAreas();
        }
    }

    SwContentNotify aNotify( this );
    SwBorderAttrAccess aAccess( SwFrame::GetCache(), this );
    const SwBorderAttrs &rAttrs = *aAccess.Get();

    while ( !isFrameAreaPositionValid() || !isFrameAreaSizeValid() || !isFramePrintAreaValid() )
    {
        MakePos();

        if ( !isFrameAreaSizeValid() )
        {
            SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this);
            aFrm.Width( GetUpper()->getFramePrintArea().Width() );
        }

        MakePrtArea( rAttrs );

        if ( !isFrameAreaSizeValid() )
        {
            setFrameAreaSizeValid(true);
            Format(getRootFrame()->GetCurrShell()->GetOut());
        }
    }

    // RotateFlyFrame3 - inner frame
    if(bRotated)
    {
        SwFlyFreeFrame* pUpperFly(dynamic_cast< SwFlyFreeFrame* >(GetUpper()));

        if(pUpperFly)
        {
            // restore outer frame back to Transformed state, that means
            // set the SwFrameAreaDefinition(s) back to BoundAreas of
            // the transformed SwFrame. All needed information is part
            // of the already correctly created Transformations of the
            // upper frame, so it can be re-created on the fly
            if(pUpperFly->isTransformableSwFrame())
            {
                pUpperFly->getTransformableSwFrame()->adaptFrameAreasToTransformations();
            }
        }

        // After the unrotated layout is finished, apply possible set rotation to it
        // get center from outer frame (layout frame) to be on the safe side
        const Point aCenter(GetUpper() ? GetUpper()->getFrameArea().Center() : getFrameArea().Center());
        const basegfx::B2DPoint aB2DCenter(aCenter.X(), aCenter.Y());

        if(!mpTransformableSwFrame)
        {
            mpTransformableSwFrame.reset(new TransformableSwFrame(*this));
        }

        getTransformableSwFrame()->createFrameAreaTransformations(
            fRotation,
            aB2DCenter);
        getTransformableSwFrame()->adaptFrameAreasToTransformations();
    }
    else
    {
        // reset transformations to show that they are not used
        mpTransformableSwFrame.reset();
    }
}

// RotateFlyFrame3 - Support for Transformations - outer frame
basegfx::B2DHomMatrix SwNoTextFrame::getFrameAreaTransformation() const
{
    if(isTransformableSwFrame())
    {
        // use pre-created transformation
        return getTransformableSwFrame()->getLocalFrameAreaTransformation();
    }

    // call parent
    return SwContentFrame::getFrameAreaTransformation();
}

basegfx::B2DHomMatrix SwNoTextFrame::getFramePrintAreaTransformation() const
{
    if(isTransformableSwFrame())
    {
        // use pre-created transformation
        return getTransformableSwFrame()->getLocalFramePrintAreaTransformation();
    }

    // call parent
    return SwContentFrame::getFramePrintAreaTransformation();
}

// RotateFlyFrame3 - Support for Transformations
void SwNoTextFrame::transform_translate(const Point& rOffset)
{
    // call parent - this will do the basic transform for SwRect(s)
    // in the SwFrameAreaDefinition
    SwContentFrame::transform_translate(rOffset);

    // check if the Transformations need to be adapted
    if(isTransformableSwFrame())
    {
        const basegfx::B2DHomMatrix aTransform(
            basegfx::utils::createTranslateB2DHomMatrix(
                rOffset.X(), rOffset.Y()));

        // transform using TransformableSwFrame
        getTransformableSwFrame()->transform(aTransform);
    }
}

// RotateFlyFrame3 - inner frame
// Check if we contain a SwGrfNode and get possible rotation from it
double SwNoTextFrame::getLocalFrameRotation() const
{
    const SwNoTextNode* pSwNoTextNode(nullptr != GetNode() ? GetNode()->GetNoTextNode() : nullptr);

    if(nullptr != pSwNoTextNode)
    {
        const SwGrfNode* pSwGrfNode(pSwNoTextNode->GetGrfNode());

        if(nullptr != pSwGrfNode)
        {
            const SwAttrSet& rSwAttrSet(pSwGrfNode->GetSwAttrSet());
            const SwRotationGrf& rSwRotationGrf(rSwAttrSet.GetRotationGrf());
            const double fRotate = -toRadians(rSwRotationGrf.GetValue());

            return basegfx::normalizeToRange(fRotate, 2 * M_PI);
        }
    }

    // no rotation
    return 0.0;
}

void SwNoTextFrame::dumpAsXml(xmlTextWriterPtr writer) const
{
    (void)xmlTextWriterStartElement(writer, reinterpret_cast<const xmlChar*>("notxt"));
    dumpAsXmlAttributes(writer);

    (void)xmlTextWriterStartElement(writer, BAD_CAST("infos"));
    dumpInfosAsXml(writer);
    (void)xmlTextWriterEndElement(writer);
    dumpChildrenAsXml(writer);

    (void)xmlTextWriterEndElement(writer);
}

/** Calculate the Bitmap's site, if needed */
void SwNoTextFrame::Format( vcl::RenderContext* /*pRenderContext*/, const SwBorderAttrs * )
{
    const Size aNewSize( GetSize() );

    // Did the height change?
    SwTwips nChgHght = IsVertical() ?
        static_cast<SwTwips>(aNewSize.Width() - getFramePrintArea().Width()) :
        static_cast<SwTwips>(aNewSize.Height() - getFramePrintArea().Height());
    if( nChgHght > 0)
        Grow( nChgHght );
    else if( nChgHght < 0)
        Shrink( std::min(getFramePrintArea().Height(), tools::Long(-nChgHght)) );
}

bool SwNoTextFrame::GetCharRect( SwRect &rRect, const SwPosition& rPos,
                              SwCursorMoveState *pCMS, bool /*bAllowFarAway*/ ) const
{
    if ( &rPos.GetNode() != static_cast<SwNode const *>(GetNode()) )
        return false;

    Calc(getRootFrame()->GetCurrShell()->GetOut());
    SwRect aFrameRect( getFrameArea() );
    rRect = aFrameRect;
    rRect.Pos( getFrameArea().Pos() + getFramePrintArea().Pos() );
    rRect.SSize( getFramePrintArea().SSize() );

    rRect.Justify();

    // Is the Bitmap in the visible area at all?
    if( !aFrameRect.Overlaps( rRect ) )
    {
        // If not, then the Cursor is on the Frame
        rRect = aFrameRect;
        rRect.Width( 1 );
    }
    else
        rRect.Intersection_( aFrameRect );

    if ( pCMS && pCMS->m_bRealHeight )
    {
        pCMS->m_aRealHeight.setY(rRect.Height());
        pCMS->m_aRealHeight.setX(0);
    }

    return true;
}

bool SwNoTextFrame::GetModelPositionForViewPoint(SwPosition* pPos, Point& ,
                             SwCursorMoveState*, bool ) const
{
    pPos->Assign(*GetNode());
    return true;
}

void SwNoTextFrame::ClearCache()
{
    SwFlyFrame* pFly = FindFlyFrame();
    if( pFly && pFly->GetFormat()->GetSurround().IsContour() )
    {
        ClrContourCache( pFly->GetVirtDrawObj() );
        pFly->NotifyBackground( FindPageFrame(), getFramePrintArea(), PrepareHint::FlyFrameAttributesChanged );
    }
}

void SwNoTextFrame::OnGraphicArrived()
{
    if(GetNode()->GetNodeType() != SwNodeType::Grf)
    {
        InvalidatePrt();
        SetCompletePaint();
        return;
    }
    SwGrfNode* pNd = static_cast<SwGrfNode*>(GetNode());
    ClearCache();
    auto pVSh = pNd->GetDoc().getIDocumentLayoutAccess().GetCurrentViewShell();
    if(pVSh)
        pVSh->OnGraphicArrived(getFrameArea());
}

void SwNoTextFrame::SwClientNotify(const SwModify& rModify, const SfxHint& rHint)
{
    if(rHint.GetId() == SfxHintId::SwGrfRereadAndInCache)
    {
        if(SwNodeType::Grf != GetNode()->GetNodeType())
        {
            InvalidatePrt();
            SetCompletePaint();
        }
    }
    else if(rHint.GetId() == SfxHintId::SwPreGraphicArrived
            || rHint.GetId() == SfxHintId::SwGraphicPieceArrived
            || rHint.GetId() == SfxHintId::SwLinkedGraphicStreamArrived)
    {
        OnGraphicArrived();
    }
    else if (rHint.GetId() == SfxHintId::SwFormatChange)
    {
        ClearCache();
        InvalidatePrt();
        SetCompletePaint();
    }
    else if (rHint.GetId() == SfxHintId::SwAttrSetChange)
    {
        auto pChangeHint = static_cast<const sw::AttrSetChangeHint*>(&rHint);
        SwContentFrame::SwClientNotify(rModify, rHint);

        sal_uInt16 n;
        for( n = RES_GRFATR_BEGIN; n < RES_GRFATR_END; ++n )
            if( SfxItemState::SET == pChangeHint->m_pOld->GetChgSet()->
                            GetItemState( n, false ))
            {
                ClearCache();

                if(RES_GRFATR_ROTATION == n)
                {
                    // RotGrfFlyFrame: Update Handles in view, these may be rotation-dependent
                    // (e.g. crop handles) and need a visualisation update
                    if ( GetNode()->GetNodeType() == SwNodeType::Grf )
                    {
                        SwGrfNode* pNd = static_cast<SwGrfNode*>( GetNode());
                        SwViewShell *pVSh = pNd->GetDoc().getIDocumentLayoutAccess().GetCurrentViewShell();

                        if(pVSh)
                        {
                            SdrView* pDrawView = pVSh->GetDrawView();

                            if(pDrawView)
                            {
                                pDrawView->AdjustMarkHdl(nullptr);
                            }
                        }

                        // RotateFlyFrame3 - invalidate needed for ContentFrame (inner, this)
                        // and LayoutFrame (outer, GetUpper). It is possible to only invalidate
                        // the outer frame, but that leads to an in-between state that gets
                        // potentially painted
                        if(GetUpper())
                        {
                            GetUpper()->InvalidateAll_();
                        }

                        InvalidateAll_();
                    }
                }
                break;
            }
        if( RES_GRFATR_END == n )           // not found
            return ;

        InvalidatePrt();
        SetCompletePaint();
    }
    else if (rHint.GetId() == SfxHintId::SwObjectDying)
    {
        SwContentFrame::SwClientNotify(rModify, rHint);
        InvalidatePrt();
        SetCompletePaint();
    }
    else if (rHint.GetId() == SfxHintId::SwUpdateAttr)
    {
        SwContentFrame::SwClientNotify(rModify, rHint);
        if (GetNode()->GetNodeType() == SwNodeType::Grf)
            ClearCache();
        InvalidatePrt();
        SetCompletePaint();
    }
    else if (rHint.GetId() == SfxHintId::SwLegacyModify)
    {
        auto pLegacy = static_cast<const sw::LegacyModifyHint*>(&rHint);
        sal_uInt16 nWhich = pLegacy->GetWhich();

        SwContentFrame::SwClientNotify(rModify, rHint);

        if ( !pLegacy->m_pNew || !isGRFATR(nWhich) )
            return;

        InvalidatePrt();
        SetCompletePaint();
    }
}

static void lcl_correctlyAlignRect( SwRect& rAlignedGrfArea, const SwRect& rInArea, vcl::RenderContext const * pOut )
{

    if(!pOut)
        return;
    tools::Rectangle aPxRect = pOut->LogicToPixel( rInArea.SVRect() );
    tools::Rectangle aNewPxRect( aPxRect );
    while( aNewPxRect.Left() < aPxRect.Left() )
    {
        rAlignedGrfArea.AddLeft( 1 );
        aNewPxRect = pOut->LogicToPixel( rAlignedGrfArea.SVRect() );
    }
    while( aNewPxRect.Top() < aPxRect.Top() )
    {
        rAlignedGrfArea.AddTop(+1);
        aNewPxRect = pOut->LogicToPixel( rAlignedGrfArea.SVRect() );
    }
    while( aNewPxRect.Bottom() > aPxRect.Bottom() )
    {
        rAlignedGrfArea.AddBottom( -1 );
        aNewPxRect = pOut->LogicToPixel( rAlignedGrfArea.SVRect() );
    }
    while( aNewPxRect.Right() > aPxRect.Right() )
    {
        rAlignedGrfArea.AddRight(-1);
        aNewPxRect = pOut->LogicToPixel( rAlignedGrfArea.SVRect() );
    }
}

static bool paintUsingPrimitivesHelper(
    vcl::RenderContext& rOutputDevice,
    const drawinglayer::primitive2d::Primitive2DContainer& rSequence,
    const basegfx::B2DRange& rSourceRange,
    const basegfx::B2DRange& rTargetRange)
{
    if(!rSequence.empty() && !basegfx::fTools::equalZero(rSourceRange.getWidth()) && !basegfx::fTools::equalZero(rSourceRange.getHeight()))
    {
        if(!basegfx::fTools::equalZero(rTargetRange.getWidth()) && !basegfx::fTools::equalZero(rTargetRange.getHeight()))
        {
            // map graphic range to target range. This will e.g. automatically include
            // the mapping from 1/100th mm content to twips if needed when the target
            // range is defined in twips
            const basegfx::B2DHomMatrix aMappingTransform(
                basegfx::utils::createSourceRangeTargetRangeTransform(
                    rSourceRange,
                    rTargetRange));

            // Fill ViewInformation. Use MappingTransform here, so there is no need to
            // embed the primitives to it. Use original TargetRange here so there is also
            // no need to embed the primitives to a MaskPrimitive for cropping. This works
            // only in this case where the graphic object cannot be rotated, though.
            drawinglayer::geometry::ViewInformation2D aViewInformation2D;
            aViewInformation2D.setObjectTransformation(aMappingTransform);
            aViewInformation2D.setViewTransformation(rOutputDevice.GetViewTransformation());
            aViewInformation2D.setViewport(rTargetRange);

            // get a primitive processor for rendering
            std::unique_ptr<drawinglayer::processor2d::BaseProcessor2D> pProcessor2D(
                drawinglayer::processor2d::createProcessor2DFromOutputDevice(
                                                rOutputDevice, aViewInformation2D) );

            // render and cleanup
            pProcessor2D->process(rSequence);
            return true;
        }
    }

    return false;
}

// MM02 original using fallback to VOC and primitive-based version
void paintGraphicUsingPrimitivesHelper(
    vcl::RenderContext & rOutputDevice,
    GraphicObject const& rGrfObj,
    GraphicAttr const& rGraphicAttr,
    const basegfx::B2DHomMatrix& rGraphicTransform,
    const OUString& rName,
    const OUString& rTitle,
    const OUString& rDescription)
{
    // RotGrfFlyFrame: unify using GraphicPrimitive2D
    // -> the primitive handles all crop and mirror stuff
    // -> the primitive renderer will create the needed pdf export data
    // -> if bitmap content, it will be cached system-dependent
    drawinglayer::primitive2d::Primitive2DContainer aContent {
        new drawinglayer::primitive2d::GraphicPrimitive2D(
            rGraphicTransform,
            rGrfObj,
            rGraphicAttr) };

    // MM02 use primitive-based version for visualization
    paintGraphicUsingPrimitivesHelper(
        rOutputDevice,
        aContent,
        rGraphicTransform,
        rName,
        rTitle,
        rDescription);
}

// MM02 new VOC and primitive-based version
void paintGraphicUsingPrimitivesHelper(
    vcl::RenderContext & rOutputDevice,
    drawinglayer::primitive2d::Primitive2DContainer& rContent,
    const basegfx::B2DHomMatrix& rGraphicTransform,
    const OUString& rName,
    const OUString& rTitle,
    const OUString& rDescription)
{
    // RotateFlyFrame3: If ClipRegion is set at OutputDevice, we
    // need to use that. Usually the renderer would be a VCL-based
    // PrimitiveRenderer, but there are system-specific shortcuts that
    // will *not* use the VCL-Paint of Bitmap and thus ignore this.
    // Anyways, indirectly using a CLipRegion set at the target OutDev
    // when using a PrimitiveRenderer is a non-valid implication.
    // First tried only to use when HasPolyPolygonOrB2DPolyPolygon(),
    // but there is an optimization at ClipRegion creation that detects
    // a single Rectangle in a tools::PolyPolygon and forces to a simple
    // RegionBand-based implementation, so cannot use it here.
    if(rOutputDevice.IsClipRegion())
    {
        basegfx::B2DPolyPolygon aClip(rOutputDevice.GetClipRegion().GetAsB2DPolyPolygon());

        if(0 != aClip.count())
        {
            rContent = drawinglayer::primitive2d::Primitive2DContainer {
                new drawinglayer::primitive2d::MaskPrimitive2D(
                    std::move(aClip),
                    std::move(rContent)) };
        }
    }

    if(!rName.isEmpty() || !rTitle.isEmpty() || !rDescription.isEmpty())
    {
        // Embed to ObjectInfoPrimitive2D when we have Name/Title/Description
        // information available
        rContent = drawinglayer::primitive2d::Primitive2DContainer {
            new drawinglayer::primitive2d::ObjectInfoPrimitive2D(
                std::move(rContent),
                rName,
                rTitle,
                rDescription) };
    }

    basegfx::B2DRange aTargetRange(0.0, 0.0, 1.0, 1.0);
    aTargetRange.transform(rGraphicTransform);

    paintUsingPrimitivesHelper(
        rOutputDevice,
        rContent,
        aTargetRange,
        aTargetRange);
}

// DrawContact section
namespace { // anonymous namespace
class ViewObjectContactOfSwNoTextFrame : public sdr::contact::ViewObjectContact
{
protected:
    virtual void createPrimitive2DSequence(
        const sdr::contact::DisplayInfo& rDisplayInfo,
        drawinglayer::primitive2d::Primitive2DDecompositionVisitor& rVisitor) const override;

    // tdf#155190 disable this so superclass doesn't wrongly produce NonStruct
    virtual bool isExportPDFTags() const override { return false; }

public:
    ViewObjectContactOfSwNoTextFrame(
        sdr::contact::ObjectContact& rObjectContact,
        sdr::contact::ViewContact& rViewContact);
};

class ViewContactOfSwNoTextFrame : public sdr::contact::ViewContact
{
private:
    // owner
    const SwNoTextFrame&        mrSwNoTextFrame;

protected:
    // Create an Object-Specific ViewObjectContact, set ViewContact and
    // ObjectContact. Always needs to return something.
    virtual sdr::contact::ViewObjectContact& CreateObjectSpecificViewObjectContact(
        sdr::contact::ObjectContact& rObjectContact) override;

public:
    // read-access to owner
    const SwNoTextFrame& getSwNoTextFrame() const { return mrSwNoTextFrame; }

    // basic constructor, used from SwNoTextFrame.
    explicit ViewContactOfSwNoTextFrame(const SwNoTextFrame& rSwNoTextFrame);
};

void ViewObjectContactOfSwNoTextFrame::createPrimitive2DSequence(
    const sdr::contact::DisplayInfo& /*rDisplayInfo*/,
    drawinglayer::primitive2d::Primitive2DDecompositionVisitor& rVisitor) const
{
    // MM02 get all the parameters formally used in paintGraphicUsingPrimitivesHelper
    ViewContactOfSwNoTextFrame& rVCOfNTF(static_cast<ViewContactOfSwNoTextFrame&>(GetViewContact()));
    const SwNoTextFrame& rSwNoTextFrame(rVCOfNTF.getSwNoTextFrame());
    SwNoTextNode& rNoTNd(const_cast<SwNoTextNode&>(*static_cast<const SwNoTextNode*>(rSwNoTextFrame.GetNode())));
    SwGrfNode* pGrfNd(rNoTNd.GetGrfNode());

    if(nullptr != pGrfNd)
    {
        const bool bPrn(GetObjectContact().isOutputToPrinter() || GetObjectContact().isOutputToRecordingMetaFile());
        const GraphicObject& rGrfObj(pGrfNd->GetGrfObj(bPrn));
        GraphicAttr aGraphicAttr;
        pGrfNd->GetGraphicAttr(aGraphicAttr, &rSwNoTextFrame);
        const basegfx::B2DHomMatrix aGraphicTransform(rSwNoTextFrame.getFrameAreaTransformation());

        // MM02 this is the right place in the VOC-Mechanism to create
        // the primitives for visualization - these will be automatically
        // buffered and reused
        rVisitor.visit(new drawinglayer::primitive2d::GraphicPrimitive2D(
            aGraphicTransform,
            rGrfObj,
            aGraphicAttr));
    }
}

ViewObjectContactOfSwNoTextFrame::ViewObjectContactOfSwNoTextFrame(
    sdr::contact::ObjectContact& rObjectContact,
    sdr::contact::ViewContact& rViewContact)
:   sdr::contact::ViewObjectContact(rObjectContact, rViewContact)
{
}

sdr::contact::ViewObjectContact& ViewContactOfSwNoTextFrame::CreateObjectSpecificViewObjectContact(
    sdr::contact::ObjectContact& rObjectContact)
{
    sdr::contact::ViewObjectContact* pRetval = new ViewObjectContactOfSwNoTextFrame(rObjectContact, *this);
    return *pRetval;
}

ViewContactOfSwNoTextFrame::ViewContactOfSwNoTextFrame(
    const SwNoTextFrame& rSwNoTextFrame
)
: mrSwNoTextFrame(rSwNoTextFrame)
{
}
} // end of anonymous namespace

sdr::contact::ViewContact& SwNoTextFrame::GetViewContact() const
{
    if(!mpViewContact)
    {
        const_cast< SwNoTextFrame* >(this)->mpViewContact =
            std::make_unique<ViewContactOfSwNoTextFrame>(*this);
    }

    return *mpViewContact;
}

/** Paint the graphic.

    We require either a QuickDraw-Bitmap or a graphic here. If we do not have
    either, we return a replacement.

    @todo use aligned rectangle for drawing graphic.
    @todo pixel-align coordinations for drawing graphic. */
void SwNoTextFrame::PaintPicture( vcl::RenderContext* pOut, const SwRect &rGrfArea ) const
{
    SwViewShell* pShell = getRootFrame()->GetCurrShell();

    SwNoTextNode& rNoTNd = const_cast<SwNoTextNode&>(*static_cast<const SwNoTextNode*>(GetNode()));
    SwGrfNode* pGrfNd = rNoTNd.GetGrfNode();
    SwOLENode* pOLENd = rNoTNd.GetOLENode();

    const bool bPrn = pOut == rNoTNd.getIDocumentDeviceAccess().getPrinter( false ) ||
                          pOut->GetConnectMetaFile();

    const bool bIsChart = pOLENd && pOLENd->GetOLEObj().GetObject().IsChart();

    // calculate aligned rectangle from parameter <rGrfArea>.
    //     Use aligned rectangle <aAlignedGrfArea> instead of <rGrfArea> in
    //     the following code.
    SwRect aAlignedGrfArea = rGrfArea;
    ::SwAlignRect( aAlignedGrfArea,  pShell, pOut );

    if( !bIsChart )
    {
        // Because for drawing a graphic left-top-corner and size coordinations are
        // used, these coordinations have to be determined on pixel level.
        ::SwAlignGrfRect( &aAlignedGrfArea, *pOut );
    }
    else //if( bIsChart )
    {
        // #i78025# charts own borders are not completely visible
        // the above pixel correction is not correct - at least not for charts
        // so a different pixel correction is chosen here
        // this might be a good idea for all other OLE objects also,
        // but as I cannot oversee the consequences I fix it only for charts for now
        lcl_correctlyAlignRect( aAlignedGrfArea, rGrfArea, pOut );
    }

    if( pGrfNd )
    {
        // Fix for bug fdo#33781
        const AntialiasingFlags nFormerAntialiasingAtOutput( pOut->GetAntialiasing() );
        if (SwDrawView::IsAntiAliasing())
            pOut->SetAntialiasing( nFormerAntialiasingAtOutput | AntialiasingFlags::Enable );

        ImplPaintPictureGraphic( pOut, pGrfNd, bPrn, aAlignedGrfArea, pShell, rNoTNd );

        if ( SwDrawView::IsAntiAliasing() )
            pOut->SetAntialiasing( nFormerAntialiasingAtOutput );
    }
    else // bIsChart || pOLENd
    {
        // Fix for bug fdo#33781
        const AntialiasingFlags nFormerAntialiasingAtOutput( pOut->GetAntialiasing() );
        if (SwDrawView::IsAntiAliasing())
        {
            AntialiasingFlags nNewAntialiasingAtOutput = nFormerAntialiasingAtOutput | AntialiasingFlags::Enable;

            // #i99665#
            // Adjust AntiAliasing mode at output device for chart OLE
            if (pOLENd && pOLENd->IsChart())
                nNewAntialiasingAtOutput |= AntialiasingFlags::PixelSnapHairline;

            pOut->SetAntialiasing( nNewAntialiasingAtOutput );
        }

        ImplPaintPictureBitmap( pOut, pOLENd, bIsChart, bPrn, aAlignedGrfArea, pShell );

        // see #i99665#
        if (SwDrawView::IsAntiAliasing())
        {
            pOut->SetAntialiasing( nFormerAntialiasingAtOutput );
        }
    }
}

void SwNoTextFrame::ImplPaintPictureGraphic( vcl::RenderContext* pOut,
    SwGrfNode* pGrfNd, bool bPrn,
    const SwRect& rAlignedGrfArea, SwViewShell* pShell,
    const SwNoTextNode& rNoTNd ) const
{
    bool bContinue = true;
    const GraphicObject& rGrfObj = pGrfNd->GetGrfObj(bPrn);

    GraphicAttr aGrfAttr;
    pGrfNd->GetGraphicAttr( aGrfAttr, this );

    if( !bPrn )
    {
        // #i73788#
        if ( pGrfNd->IsLinkedInputStreamReady() )
        {
            pGrfNd->UpdateLinkWithInputStream();
        }
        // #i85717#, #i90395# - check, if asynchronous retrieval
        // if input stream for the graphic is possible
        else if ( ( rGrfObj.GetType() == GraphicType::Default ||
                    rGrfObj.GetType() == GraphicType::NONE ) &&
                  pGrfNd->IsLinkedFile() &&
                  pGrfNd->IsAsyncRetrieveInputStreamPossible() )
        {
            Size aTmpSz;
            ::sfx2::SvLinkSource* pGrfObj = pGrfNd->GetLink()->GetObj();
            if( !pGrfObj ||
                !pGrfObj->IsDataComplete() ||
                !(aTmpSz = pGrfNd->GetTwipSize()).Width() ||
                !aTmpSz.Height())
            {
                pGrfNd->TriggerAsyncRetrieveInputStream(); // #i73788#
            }
            OUString aText( pGrfNd->GetTitle() );
            if ( aText.isEmpty() )
                GetRealURL( *pGrfNd, aText );
            ::lcl_PaintReplacement( rAlignedGrfArea, aText, *pShell, this, false );
            bContinue = false;
        }
    }

    if( !bContinue )
        return;

    if( !rGrfObj.GetGraphic().IsSupportedGraphic())
    {
        ImplPaintPictureReplacement(rGrfObj, pGrfNd, rAlignedGrfArea, pShell);
        return;
    }

    const bool bAnimate = rGrfObj.IsAnimated() &&
                             !pShell->IsPreview() &&
                             !pShell->GetAccessibilityOptions()->IsStopAnimatedGraphics() &&
    // #i9684# Stop animation during printing/pdf export
                              pShell->GetWin();
    if( bAnimate &&
        FindFlyFrame() != ::GetFlyFromMarked( nullptr, pShell ))
    {
        ImplPaintPictureAnimate(pOut, pShell, pGrfNd, rAlignedGrfArea);
        return;
    }

    // MM02 To allow system-dependent buffering of the involved
    // bitmaps it is necessary to re-use the involved primitives
    // and their already executed decomposition (also for
    // performance reasons). This is usually done in DrawingLayer
    // by using the VOC-Mechanism (see descriptions elsewhere).
    // To get that here, make the involved SwNoTextFrame (this)
    // a sdr::contact::ViewContact supplier by supporting
    // a GetViewContact() - call. For ObjectContact we can use
    // the already existing ObjectContact from the involved
    // DrawingLayer. For this, the helper classes
    //     ViewObjectContactOfSwNoTextFrame
    //     ViewContactOfSwNoTextFrame
    // are created which support the VOC-mechanism in its minimal
    // form. This allows automatic and view-dependent (multiple edit
    // windows, print, etc.) re-use of the created primitives.
    // Also: Will be very useful when completely changing the Writer
    // repaint to VOC and Primitives, too.
    static const char* pDisableMM02Goodies(getenv("SAL_DISABLE_MM02_GOODIES"));
    static bool bUseViewObjectContactMechanism(nullptr == pDisableMM02Goodies);
    // tdf#130951 for safety reasons use fallback if ViewObjectContactMechanism
    // fails for some reason - usually could only be not to find the correct
    // SdrPageWindow
    bool bSucceeded(false);

    if(bUseViewObjectContactMechanism)
    {
        // MM02 use VOC-mechanism and buffer primitives
        SwViewShellImp* pImp(pShell->Imp());
        SdrPageView* pPageView(nullptr != pImp
            ? pImp->GetPageView()
            : nullptr);
        // tdf#130951 caution - target may be Window, use the correct OutputDevice
        OutputDevice* pTarget((pShell->isOutputToWindow() && pShell->GetWin())
            ? pShell->GetWin()->GetOutDev()
            : pShell->GetOut());
        SdrPageWindow* pPageWindow(nullptr != pPageView && nullptr != pTarget
            ? pPageView->FindPageWindow(*pTarget)
            : nullptr);

        if(nullptr != pPageWindow)
        {
            sdr::contact::ObjectContact& rOC(pPageWindow->GetObjectContact());
            sdr::contact::ViewContact& rVC(GetViewContact());
            sdr::contact::ViewObjectContact& rVOC(rVC.GetViewObjectContact(rOC));
            sdr::contact::DisplayInfo aDisplayInfo;

            drawinglayer::primitive2d::Primitive2DContainer aPrimitives(rVOC.getPrimitive2DSequence(aDisplayInfo));
            const basegfx::B2DHomMatrix aGraphicTransform(getFrameAreaTransformation());

            paintGraphicUsingPrimitivesHelper(
                *pOut,
                aPrimitives,
                aGraphicTransform,
                nullptr == pGrfNd->GetFlyFormat() ? OUString() : pGrfNd->GetFlyFormat()->GetName().toString(),
                rNoTNd.GetTitle(),
                rNoTNd.GetDescription());
            bSucceeded = true;
        }
    }

    if(!bSucceeded)
    {
        // MM02 fallback to direct paint with primitive-recreation
        // which will block reusage of system-dependent bitmap data
        const basegfx::B2DHomMatrix aGraphicTransform(getFrameAreaTransformation());

        paintGraphicUsingPrimitivesHelper(
            *pOut,
            rGrfObj,
            aGrfAttr,
            aGraphicTransform,
            nullptr == pGrfNd->GetFlyFormat() ? OUString() : pGrfNd->GetFlyFormat()->GetName().toString(),
            rNoTNd.GetTitle(),
            rNoTNd.GetDescription());
    }
}

void SwNoTextFrame::ImplPaintPictureAnimate(vcl::RenderContext* pOut, const SwViewShell* pShell,
        SwGrfNode* pGrfNd, const SwRect& rAlignedGrfArea) const
{
    OutputDevice* pVout;
    if( pOut == pShell->GetOut() && SwRootFrame::FlushVout() )
    {
        pVout = pOut;
        pOut = pShell->GetOut();
    }
    else if( pShell->GetWin() && pOut->IsVirtual() )
    {
        pVout = pOut;
        pOut = pShell->GetWin()->GetOutDev();
    }
    else
        pVout = nullptr;

    OSL_ENSURE( !pOut->IsVirtual() ||
            pShell->GetViewOptions()->IsPDFExport() || pShell->isOutputToWindow(),
            "pOut should not be a virtual device" );

    pGrfNd->StartGraphicAnimation(pOut, rAlignedGrfArea.Pos(),
                        rAlignedGrfArea.SSize(), reinterpret_cast<sal_IntPtr>(this),
                        pVout );
}

void SwNoTextFrame::ImplPaintPictureReplacement(const GraphicObject& rGrfObj, const SwGrfNode* pGrfNd,
        const SwRect& rAlignedGrfArea, const SwViewShell* pShell) const
{
    TranslateId pResId;

    if( GraphicType::NONE == rGrfObj.GetType() )
        pResId = STR_COMCORE_READERROR;
    else if ( !rGrfObj.GetGraphic().IsSupportedGraphic() )
        pResId = STR_COMCORE_CANT_SHOW;

    OUString aText;
    if ( !pResId &&
         (aText = pGrfNd->GetTitle()).isEmpty() &&
         (!GetRealURL( *pGrfNd, aText ) || aText.isEmpty()))
    {
        pResId = STR_COMCORE_READERROR;
    }
    if (pResId)
        aText = SwResId(pResId);

    ::lcl_PaintReplacement( rAlignedGrfArea, aText, *pShell, this, true );
}

void SwNoTextFrame::ImplPaintPictureBitmap( vcl::RenderContext* pOut,
    SwOLENode* pOLENd, bool bIsChart, bool bPrn, const SwRect& rAlignedGrfArea,
    SwViewShell* pShell ) const
{
    bool bDone(false);

    if(bIsChart)
    {
        basegfx::B2DRange aSourceRange;
        const drawinglayer::primitive2d::Primitive2DContainer aSequence(
            pOLENd->GetOLEObj().tryToGetChartContentAsPrimitive2DSequence(
                aSourceRange,
                bPrn));

        if(!aSequence.empty() && !aSourceRange.isEmpty())
        {
            const basegfx::B2DRange aTargetRange(
                rAlignedGrfArea.Left(), rAlignedGrfArea.Top(),
                rAlignedGrfArea.Right(), rAlignedGrfArea.Bottom());

            Color aOldBackColor;
            SvxDrawPage* pDrawPage = pOLENd->GetOLEObj().tryToGetChartDrawPage();
            SdrPage* pPage = pDrawPage ? pDrawPage->GetSdrPage() : nullptr;
            if (pPage)
            {
                SdrModel& rModel = pPage->getSdrModelFromSdrPage();
                SdrOutliner& rOutl = rModel.GetDrawOutliner();
                aOldBackColor = rOutl.GetBackgroundColor();
                rOutl.SetBackgroundColor(pPage->GetPageBackgroundColor());
            }

            bDone = paintUsingPrimitivesHelper(
                *pOut,
                aSequence,
                aSourceRange,
                aTargetRange);

            if (pPage)
                pPage->getSdrModelFromSdrPage().GetDrawOutliner().SetBackgroundColor(aOldBackColor);
        }
    }

    if(bDone || !pOLENd)
        return;

    // SwOLENode does not have a known GraphicObject, need to
    // work with Graphic instead
    const Graphic* pGraphic = pOLENd->GetGraphic();
    const Point aPosition(rAlignedGrfArea.Pos());
    const Size aSize(rAlignedGrfArea.SSize());

    uno::Reference<embed::XEmbeddedObject> xObj = pOLENd->GetOLEObj().GetOleRef();

    if ( pGraphic && pGraphic->GetType() != GraphicType::NONE )
    {
        pGraphic->Draw(*pOut, aPosition, aSize);

        // shade the representation if the object is activated outplace
        if ( xObj.is() && xObj->getCurrentState() == embed::EmbedStates::ACTIVE )
        {

            ::svt::EmbeddedObjectRef::DrawShading(
                tools::Rectangle(
                    aPosition,
                    aSize),
                pOut);
        }
    }
    else
    {
        ::svt::EmbeddedObjectRef::DrawPaintReplacement(
            tools::Rectangle(aPosition, aSize),
            pOLENd->GetOLEObj().GetCurrentPersistName(),
            pOut);
    }

    sal_Int64 nMiscStatus = xObj ? xObj->getStatus(pOLENd->GetAspect()) : 0;
    if ( !bPrn && dynamic_cast< const SwCursorShell *>( pShell ) !=  nullptr &&
            (nMiscStatus & embed::EmbedMisc::MS_EMBED_ACTIVATEWHENVISIBLE))
    {
        const SwFlyFrame *pFly = FindFlyFrame();
        assert( pFly != nullptr );
        static_cast<SwFEShell*>(pShell)->ConnectObj( pOLENd->GetOLEObj().GetObject(), pFly->getFramePrintArea(), pFly->getFrameArea());
    }
}

bool SwNoTextFrame::IsTransparent() const
{
    const SwViewShell* pSh = getRootFrame()->GetCurrShell();

    if ( !pSh || !pSh->GetViewOptions()->IsGraphic() )
    {
        return true;
    }

    const SwGrfNode *pNd;

    if( nullptr != (pNd = GetNode()->GetGrfNode()) )
    {
        if(pNd->IsTransparent())
        {
            return true;
        }
    }

    // RotateFlyFrame3: If we are transformed, there are 'free' areas between
    // the Graphic and the Border/Padding stuff - at least as long as those
    // (Border and Padding) are not transformed, too
    if(isTransformableSwFrame())
    {
        // we can be more specific - rotations of multiples of
        // 90 degrees will leave no gaps. Go from [0.0 .. 2PI]
        // to [0 .. 360] and check modulo 90
        const tools::Long nRot(static_cast<tools::Long>(basegfx::rad2deg(getLocalFrameRotation())));
        const bool bMultipleOf90(0 == (nRot % 90));

        if(!bMultipleOf90)
        {
            return true;
        }
    }

    //#29381# OLE are always transparent
    if(nullptr != GetNode()->GetOLENode())
    {
        return true;
    }

    // return false by default to avoid background paint
    return false;
}

void SwNoTextFrame::StopAnimation( const OutputDevice* pOut ) const
{
    // Stop animated graphics
    const SwGrfNode* pGrfNd = GetNode()->GetGrfNode();

    if( pGrfNd && pGrfNd->IsAnimated() )
    {
        const_cast< SwGrfNode* >(pGrfNd)->StopGraphicAnimation( pOut, reinterpret_cast<sal_IntPtr>(this) );
    }
}

bool SwNoTextFrame::HasAnimation() const
{
    const SwGrfNode* pGrfNd = GetNode()->GetGrfNode();
    return pGrfNd && pGrfNd->IsAnimated();
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
