/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the Collabora Office project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */

#include <sal/config.h>
#include <sal/log.hxx>

#include <comphelper/fileformat.h>
#include <o3tl/make_shared.hxx>
#include <tools/fract.hxx>
#include <tools/vcompat.hxx>
#include <tools/urlobj.hxx>
#include <tools/stream.hxx>
#include <unotools/ucbhelper.hxx>
#include <unotools/ucbstreamhelper.hxx>
#include <unotools/tempfile.hxx>
#include <utility>
#include <vcl/filter/SvmReader.hxx>
#include <vcl/filter/SvmWriter.hxx>
#include <vcl/outdev.hxx>
#include <vcl/graphicfilter.hxx>
#include <vcl/virdev.hxx>
#include <vcl/gfxlink.hxx>
#include <vcl/cvtgrf.hxx>
#include <vcl/graph.hxx>
#include <vcl/metaact.hxx>
#include <impgraph.hxx>
#include <com/sun/star/graphic/XPrimitive2D.hpp>
#include <drawinglayer/primitive2d/baseprimitive2d.hxx>
#include <vcl/dibtools.hxx>
#include <map>
#include <memory>
#include <vcl/gdimetafiletools.hxx>
#include <vcl/TypeSerializer.hxx>
#include <vcl/pdfread.hxx>
#include <graphic/VectorGraphicLoader.hxx>

#define GRAPHIC_MTFTOBMP_MAXEXT     2048
#define GRAPHIC_STREAMBUFSIZE       8192UL

#define SWAP_FORMAT_ID COMPAT_FORMAT( 'S', 'W', 'A', 'P' )

using namespace com::sun::star;


class ImpSwapFile
{
private:
    utl::TempFileFast maTempFile;
    OUString maOriginURL;

public:
    ImpSwapFile(OUString aOriginURL)
        : maOriginURL(std::move(aOriginURL))
    {
    }

    SvStream* getStream() { return maTempFile.GetStream(StreamMode::READWRITE); }
    OUString const & getOriginURL() const { return maOriginURL; }
};

SvStream* ImpGraphic::getSwapFileStream() const
{
    if (mpSwapFile)
        return mpSwapFile->getStream();
    return nullptr;
}

ImpGraphic::ImpGraphic(bool bDefault)
    : MemoryManaged(false)
    , meType(bDefault ? GraphicType::Default : GraphicType::NONE)
{
}

ImpGraphic::ImpGraphic(const ImpGraphic& rImpGraphic)
    : MemoryManaged(rImpGraphic)
    , maCachedBitmap(rImpGraphic.maCachedBitmap)
    , maMetaFile(rImpGraphic.maMetaFile)
    , mpBitmapContainer(rImpGraphic.mpBitmapContainer)
    , maSwapInfo(rImpGraphic.maSwapInfo)
    , mpSwapFile(rImpGraphic.mpSwapFile)
    , mpGfxLink(rImpGraphic.mpGfxLink)
    , maVectorGraphicData(rImpGraphic.maVectorGraphicData)
    , meType(rImpGraphic.meType)
    , mnSizeBytes(rImpGraphic.mnSizeBytes)
    , mbSwapOut(rImpGraphic.mbSwapOut)
    , mbDummyContext(rImpGraphic.mbDummyContext)
    , maGraphicExternalLink(rImpGraphic.maGraphicExternalLink)
    , mbPrepared(rImpGraphic.mbPrepared)
{
    updateCurrentSizeInBytes(mnSizeBytes);

    // Special case for animations
    if (rImpGraphic.mpAnimationContainer)
    {
        mpAnimationContainer = std::make_shared<AnimationContainer>(rImpGraphic.mpAnimationContainer->maAnimation);
        maCachedBitmap = mpAnimationContainer->maAnimation.GetBitmap();
    }
}

ImpGraphic::ImpGraphic(ImpGraphic&& rImpGraphic) noexcept
    : MemoryManaged(rImpGraphic)
    , maCachedBitmap(std::move(rImpGraphic.maCachedBitmap))
    , maMetaFile(std::move(rImpGraphic.maMetaFile))
    , mpBitmapContainer(std::move(rImpGraphic.mpBitmapContainer))
    , maSwapInfo(std::move(rImpGraphic.maSwapInfo))
    , mpAnimationContainer(std::move(rImpGraphic.mpAnimationContainer))
    , mpSwapFile(std::move(rImpGraphic.mpSwapFile))
    , mpGfxLink(std::move(rImpGraphic.mpGfxLink))
    , maVectorGraphicData(std::move(rImpGraphic.maVectorGraphicData))
    , meType(rImpGraphic.meType)
    , mnSizeBytes(rImpGraphic.mnSizeBytes)
    , mbSwapOut(rImpGraphic.mbSwapOut)
    , mbDummyContext(rImpGraphic.mbDummyContext)
    , maGraphicExternalLink(rImpGraphic.maGraphicExternalLink)
    , mbPrepared (rImpGraphic.mbPrepared)
{
    updateCurrentSizeInBytes(mnSizeBytes);

    rImpGraphic.clear();
    rImpGraphic.mbDummyContext = false;
}

ImpGraphic::ImpGraphic(std::shared_ptr<GfxLink> xGfxLink, sal_Int32 nPageIndex)
    : MemoryManaged(true)
    , mpGfxLink(std::move(xGfxLink))
    , meType(GraphicType::Bitmap)
    , mbSwapOut(true)
{
    maSwapInfo.mbIsTransparent = true;
    maSwapInfo.mbIsAlpha = true;
    maSwapInfo.mbIsEPS = false;
    maSwapInfo.mbIsAnimated = false;
    maSwapInfo.mnAnimationLoopCount = 0;
    maSwapInfo.mnPageIndex = nPageIndex;

    ensureCurrentSizeInBytes();
}

ImpGraphic::ImpGraphic(GraphicExternalLink aGraphicExternalLink)
    : MemoryManaged(true)
    , meType(GraphicType::Default)
    , maGraphicExternalLink(std::move(aGraphicExternalLink))
{
    ensureCurrentSizeInBytes();
}

ImpGraphic::ImpGraphic(const Bitmap& rBitmap)
    : MemoryManaged(!rBitmap.IsEmpty())
    , mpBitmapContainer(new BitmapContainer(rBitmap))
    , meType(rBitmap.IsEmpty() ? GraphicType::NONE : GraphicType::Bitmap)
{
    ensureCurrentSizeInBytes();
}

ImpGraphic::ImpGraphic(const std::shared_ptr<VectorGraphicData>& rVectorGraphicDataPtr)
    : MemoryManaged(bool(rVectorGraphicDataPtr))
    , maVectorGraphicData(rVectorGraphicDataPtr)
    , meType(rVectorGraphicDataPtr ? GraphicType::Bitmap : GraphicType::NONE)
{
    ensureCurrentSizeInBytes();
}

ImpGraphic::ImpGraphic(const Animation& rAnimation)
    : MemoryManaged(true)
    , maCachedBitmap(rAnimation.GetBitmap())
    , mpAnimationContainer(std::make_shared<AnimationContainer>(rAnimation))
    , meType(GraphicType::Bitmap)
{
    ensureCurrentSizeInBytes();
}

ImpGraphic::ImpGraphic(const GDIMetaFile& rMetafile)
    : MemoryManaged(true)
    , maMetaFile(rMetafile)
    , meType(GraphicType::GdiMetafile)
{
    ensureCurrentSizeInBytes();
}

ImpGraphic::~ImpGraphic()
{
}

ImpGraphic& ImpGraphic::operator=(const ImpGraphic& rImpGraphic)
{
    if( &rImpGraphic != this )
    {
        maMetaFile = rImpGraphic.maMetaFile;
        meType = rImpGraphic.meType;
        mnSizeBytes = rImpGraphic.mnSizeBytes;
        updateCurrentSizeInBytes(mnSizeBytes);

        maSwapInfo = rImpGraphic.maSwapInfo;
        mbDummyContext = rImpGraphic.mbDummyContext;
        maGraphicExternalLink = rImpGraphic.maGraphicExternalLink;

        mpAnimationContainer.reset();
        if (rImpGraphic.mpAnimationContainer)
        {
            mpAnimationContainer = std::make_shared<AnimationContainer>(*rImpGraphic.mpAnimationContainer);
            maCachedBitmap = mpAnimationContainer->maAnimation.GetBitmap();
        }
        else
        {
            maCachedBitmap = rImpGraphic.maCachedBitmap;
        }

        mpBitmapContainer.reset();
        if (rImpGraphic.mpBitmapContainer)
            mpBitmapContainer = rImpGraphic.mpBitmapContainer;

        mbSwapOut = rImpGraphic.mbSwapOut;
        mpSwapFile = rImpGraphic.mpSwapFile;
        mbPrepared = rImpGraphic.mbPrepared;

        mpGfxLink = rImpGraphic.mpGfxLink;

        maVectorGraphicData.reset();
        if (rImpGraphic.maVectorGraphicData)
            maVectorGraphicData = rImpGraphic.maVectorGraphicData;

        resetLastUsed();

        changeExisting(mnSizeBytes);
    }

    return *this;
}

ImpGraphic& ImpGraphic::operator=(ImpGraphic&& rImpGraphic)
{
    maMetaFile = std::move(rImpGraphic.maMetaFile);
    meType = rImpGraphic.meType;
    mnSizeBytes = rImpGraphic.mnSizeBytes;
    maSwapInfo = std::move(rImpGraphic.maSwapInfo);
    mbDummyContext = rImpGraphic.mbDummyContext;
    maCachedBitmap = std::move(rImpGraphic.maCachedBitmap);
    mpAnimationContainer = std::move(rImpGraphic.mpAnimationContainer);
    mpBitmapContainer = std::move(rImpGraphic.mpBitmapContainer);
    mbSwapOut = rImpGraphic.mbSwapOut;
    mpSwapFile = std::move(rImpGraphic.mpSwapFile);
    mpGfxLink = std::move(rImpGraphic.mpGfxLink);
    maVectorGraphicData = std::move(rImpGraphic.maVectorGraphicData);
    maGraphicExternalLink = rImpGraphic.maGraphicExternalLink;
    mbPrepared = rImpGraphic.mbPrepared;

    rImpGraphic.clear();
    rImpGraphic.mbDummyContext = false;
    resetLastUsed();

    changeExisting(mnSizeBytes);

    return *this;
}

bool ImpGraphic::operator==( const ImpGraphic& rOther ) const
{
    if( this == &rOther )
        return true;

    if (mbPrepared && rOther.mbPrepared)
        return (*mpGfxLink == *rOther.mpGfxLink);

    if (!isAvailable() || !rOther.isAvailable())
        return false;

    if ( meType != rOther.meType )
        return false;

    bool bRet = false;
    switch( meType )
    {
        case GraphicType::NONE:
        case GraphicType::Default:
            return true;

        case GraphicType::GdiMetafile:
            return ( rOther.maMetaFile == maMetaFile );

        case GraphicType::Bitmap:
        {
            if (maVectorGraphicData)
            {
                if (maVectorGraphicData == rOther.maVectorGraphicData)
                    // equal instances
                    bRet = true;
                else if (rOther.maVectorGraphicData)
                    // equal content
                    bRet = (*maVectorGraphicData) == (*rOther.maVectorGraphicData);
            }
            else if (mpAnimationContainer && rOther.mpAnimationContainer && (*mpAnimationContainer == *rOther.mpAnimationContainer))
            {
                bRet = true;
            }
            else if (mpBitmapContainer && rOther.mpBitmapContainer && (*mpBitmapContainer == *rOther.mpBitmapContainer))
            {
                bRet = true;
            }
        }
        break;
    }

    return bRet;
}

const std::shared_ptr<VectorGraphicData>& ImpGraphic::getVectorGraphicData() const
{
    // If the type is clearly a bitmap, we know the vector graphic data will be empty, no need to
    // swap in.
    if (mpGfxLink)
    {
        switch (mpGfxLink->GetType())
        {
            case GfxLinkType::NativePng:
            case GfxLinkType::NativeJpg:
                return maVectorGraphicData;
            default:
                break;
        }
    }

    ensureAvailable();

    return maVectorGraphicData;
}

void BitmapContainer::createSwapInfo(SwapInfo& rSwapInfo)
{
    rSwapInfo.maSizePixel = maBitmap.GetSizePixel();

    rSwapInfo.maPrefMapMode = getPrefMapMode();
    rSwapInfo.maPrefSize = getPrefSize();
    rSwapInfo.mbIsAnimated = false;
    rSwapInfo.mbIsEPS = false;
    rSwapInfo.mbIsTransparent = isAlpha();
    rSwapInfo.mbIsAlpha = isAlpha();
    rSwapInfo.mnAnimationLoopCount = 0;
    rSwapInfo.mnPageIndex = -1;
}

void AnimationContainer::createSwapInfo(SwapInfo& rSwapInfo)
{
    rSwapInfo.maSizePixel = maAnimation.GetBitmap().GetSizePixel();

    rSwapInfo.maPrefMapMode = getPrefMapMode();
    rSwapInfo.maPrefSize = getPrefSize();
    rSwapInfo.mbIsAnimated = true;
    rSwapInfo.mbIsEPS = false;
    rSwapInfo.mbIsTransparent = isTransparent();
    rSwapInfo.mbIsAlpha = false;
    rSwapInfo.mnAnimationLoopCount = getLoopCount();
    rSwapInfo.mnPageIndex = -1;
}

void ImpGraphic::createSwapInfo()
{
    if (isSwappedOut())
        return;

    if (mpBitmapContainer)
    {
        return mpBitmapContainer->createSwapInfo(maSwapInfo);
    }
    else if (mpAnimationContainer)
    {
        return mpAnimationContainer->createSwapInfo(maSwapInfo);
    }

    else if (!maCachedBitmap.IsEmpty())
        maSwapInfo.maSizePixel = maCachedBitmap.GetSizePixel();
    else
        maSwapInfo.maSizePixel = Size();

    maSwapInfo.maPrefMapMode = getPrefMapMode();
    maSwapInfo.maPrefSize = getPrefSize();
    maSwapInfo.mbIsAnimated = isAnimated();
    maSwapInfo.mbIsEPS = isEPS();
    maSwapInfo.mbIsTransparent = isTransparent();
    maSwapInfo.mbIsAlpha = isAlpha();
    maSwapInfo.mnAnimationLoopCount = getAnimationLoopCount();
    maSwapInfo.mnPageIndex = getPageNumber();
}

void ImpGraphic::clearGraphics()
{
    maCachedBitmap = Bitmap();
    mpBitmapContainer.reset();
    maMetaFile.Clear();
    mpAnimationContainer.reset();
    maVectorGraphicData.reset();
}

void ImpGraphic::setPrepared(bool bAnimated, const Size* pSizeHint)
{
    mbPrepared = true;
    mbSwapOut = true;
    meType = GraphicType::Bitmap;

    SvMemoryStream aMemoryStream(const_cast<sal_uInt8*>(mpGfxLink->GetData()), mpGfxLink->GetDataSize(), StreamMode::READ | StreamMode::WRITE);

    if (pSizeHint)
    {
        maSwapInfo.maPrefSize = *pSizeHint;
        maSwapInfo.maPrefMapMode = MapMode(MapUnit::Map100thMM);
    }

    GraphicDescriptor aDescriptor(aMemoryStream, nullptr);
    if (aDescriptor.Detect(true))
    {
        if (!pSizeHint)
        {
            // If we have logic size, work with that, as later pixel -> logic
            // conversion will work with the output device DPI, not the graphic
            // DPI.
            Size aLogSize = aDescriptor.GetSize_100TH_MM();
            if (aDescriptor.GetPreferredLogSize() && aDescriptor.GetPreferredMapMode())
            {
                maSwapInfo.maPrefSize = *aDescriptor.GetPreferredLogSize();
                maSwapInfo.maPrefMapMode = *aDescriptor.GetPreferredMapMode();
            }
            else if (aLogSize.getWidth() && aLogSize.getHeight())
            {
                maSwapInfo.maPrefSize = aLogSize;
                maSwapInfo.maPrefMapMode = MapMode(MapUnit::Map100thMM);
            }
            else
            {
                maSwapInfo.maPrefSize = aDescriptor.GetSizePixel();
                maSwapInfo.maPrefMapMode = MapMode(MapUnit::MapPixel);
            }
        }

        maSwapInfo.maSizePixel = aDescriptor.GetSizePixel();
        maSwapInfo.mbIsTransparent = aDescriptor.IsTransparent();
        maSwapInfo.mbIsAlpha = aDescriptor.IsAlpha();
    } else {
        maSwapInfo.mbIsTransparent = false;
        maSwapInfo.mbIsAlpha = false;
    }

    maSwapInfo.mnAnimationLoopCount = 0;
    maSwapInfo.mbIsEPS = false;
    maSwapInfo.mbIsAnimated = bAnimated;

    if (maVectorGraphicData)
        maSwapInfo.mnPageIndex = maVectorGraphicData->getPageIndex();

    // tdf#167007 Add animated graphic to cache when prepared
    // For some reason, after an animation has been swapped out by
    // MemoryManager::loopAndReduceMemory(), the animation repeatedly
    // creates a new ImpGraphic instance, swaps it in, but it never
    // gets registered in the cache. Since it is not in the cache, new
    // ImpGraphic instances get deleted almost immediately after they
    // are created.
    // So prevent immediate deletion by ensuring that animated
    // ImpGraphic instances are registered when they are prepared.
    if (maSwapInfo.mbIsAnimated)
        registerIntoManager();
}

void ImpGraphic::clear()
{
    mpSwapFile.reset();
    mbSwapOut = false;
    mbPrepared = false;

    // cleanup
    clearGraphics();
    meType = GraphicType::NONE;
    mnSizeBytes = 0;

    changeExisting(mnSizeBytes);
    maGraphicExternalLink.msURL.clear();
}

bool ImpGraphic::isSupportedGraphic() const
{
    return meType != GraphicType::NONE;
}

bool ImpGraphic::isTransparent() const
{
    if (mbSwapOut)
    {
        return maSwapInfo.mbIsTransparent;
    }
    else if (meType == GraphicType::Bitmap)
    {
        if (maVectorGraphicData)
            return true;
        else if (mpBitmapContainer)
            return mpBitmapContainer->isAlpha();
        else if (mpAnimationContainer)
            return mpAnimationContainer->isTransparent();
    }

    return true;
}

bool ImpGraphic::isAlpha() const
{
    if (mbSwapOut)
        return maSwapInfo.mbIsAlpha;

    if (meType == GraphicType::Bitmap)
    {
        if (maVectorGraphicData)
            return true;
        else if (mpBitmapContainer)
            return mpBitmapContainer->isAlpha();
    }
    return false;
}

bool ImpGraphic::isAnimated() const
{
    return mbSwapOut ? maSwapInfo.mbIsAnimated : mpAnimationContainer != nullptr;
}

bool ImpGraphic::isEPS() const
{
    if (mbSwapOut)
        return maSwapInfo.mbIsEPS;

    return( ( meType == GraphicType::GdiMetafile ) &&
            ( maMetaFile.GetActionSize() > 0 ) &&
            ( maMetaFile.GetAction( 0 )->GetType() == MetaActionType::EPS ) );
}

bool ImpGraphic::isAvailable() const
{
    return !mbPrepared && !mbSwapOut;
}

bool ImpGraphic::makeAvailable()
{
    return ensureAvailable();
}

void ImpGraphic::updateBitmapFromVectorGraphic(const Size& pixelSize) const
{
    assert (maVectorGraphicData);
    auto* pThisRW = const_cast<ImpGraphic*>(this);
    // use maBitmapEx as local buffer for rendered vector image
    if (pixelSize.Width() && pixelSize.Height())
    {
        if (maCachedBitmap.IsEmpty() || maCachedBitmap.GetSizePixel() != pixelSize)
            pThisRW->maCachedBitmap = maVectorGraphicData->getBitmap(pixelSize);
    }
    else // maVectorGraphicData caches the replacement, so updating unconditionally is cheap
    {
        pThisRW->maCachedBitmap = maVectorGraphicData->getReplacement();
    }

    if (maExPrefSize.getWidth() && maExPrefSize.getHeight())
        pThisRW->maCachedBitmap.SetPrefSize(maExPrefSize);
}

Bitmap ImpGraphic::getBitmap(const GraphicConversionParameters& rParameters) const
{
    Bitmap aRetBmp;

    ensureAvailable();

    if (meType == GraphicType::Bitmap)
    {
        if (!mpAnimationContainer && maVectorGraphicData)
            updateBitmapFromVectorGraphic(rParameters.getSizePixel());

        if (mpAnimationContainer)
            aRetBmp = mpAnimationContainer->maAnimation.GetBitmap();
        else if (mpBitmapContainer)
            aRetBmp = mpBitmapContainer->maBitmap;
        else
            aRetBmp = maCachedBitmap;

        if (rParameters.getSizePixel().Width() || rParameters.getSizePixel().Height())
            aRetBmp.Scale(rParameters.getSizePixel());
    }
    else if( ( meType != GraphicType::Default ) && isSupportedGraphic() )
    {
        if (maCachedBitmap.IsEmpty())
        {
            // calculate size
            ScopedVclPtrInstance< VirtualDevice > aVDev;
            Size aDrawSize(aVDev->LogicToPixel(maMetaFile.GetPrefSize(), maMetaFile.GetPrefMapMode()));

            if(rParameters.getSizePixel().Width() && rParameters.getSizePixel().Height())
            {
                // apply given size if exists
                aDrawSize = rParameters.getSizePixel();
            }

            if(aDrawSize.Width() && aDrawSize.Height() && !rParameters.getUnlimitedSize()
                && (aDrawSize.Width() > GRAPHIC_MTFTOBMP_MAXEXT || aDrawSize.Height() > GRAPHIC_MTFTOBMP_MAXEXT))
            {
                // limit bitmap size to a maximum of GRAPHIC_MTFTOBMP_MAXEXT x GRAPHIC_MTFTOBMP_MAXEXT
                double fWH(static_cast<double>(aDrawSize.Width()) / static_cast<double>(aDrawSize.Height()));

                if(fWH <= 1.0)
                {
                    aDrawSize.setWidth(basegfx::fround<tools::Long>(GRAPHIC_MTFTOBMP_MAXEXT * fWH));
                    aDrawSize.setHeight(GRAPHIC_MTFTOBMP_MAXEXT);
                }
                else
                {
                    aDrawSize.setWidth(GRAPHIC_MTFTOBMP_MAXEXT);
                    aDrawSize.setHeight(basegfx::fround<tools::Long>(GRAPHIC_MTFTOBMP_MAXEXT / fWH));
                }
            }

            // calculate pixel size. Normally, it's the same as aDrawSize, but may
            // need to be extended when hairlines are on the right or bottom edge
            Size aPixelSize(aDrawSize);

            if(GraphicType::GdiMetafile == getType())
            {
                // tdf#126319 Removed correction based on hairline-at-the-extremes of
                // the metafile. The task shows that this is no longer sufficient since
                // less hairlines get used in general - what is good, but breaks that
                // old fix. Anyways, hairlines are a left-over from non-AA times
                // when it was not possible to paint lines taller than one pixel.
                // This might need to be corrected further using primitives and
                // the possibility to get better-quality ranges for correction. For
                // now, always add that one pixel.
                aPixelSize.setWidth(aPixelSize.getWidth() + 1);
                aPixelSize.setHeight(aPixelSize.getHeight() + 1);
            }

            if(aVDev->SetOutputSizePixel(aPixelSize))
            {
                if(rParameters.getAntiAliase())
                {
                    aVDev->SetAntialiasing(aVDev->GetAntialiasing() | AntialiasingFlags::Enable);
                }

                if(rParameters.getSnapHorVerLines())
                {
                    aVDev->SetAntialiasing(aVDev->GetAntialiasing() | AntialiasingFlags::PixelSnapHairline);
                }

                draw(*aVDev, Point(), aDrawSize);

                // use maBitmapEx as local buffer for rendered metafile
                const_cast<ImpGraphic*>(this)->maCachedBitmap = aVDev->GetBitmap( Point(), aVDev->GetOutputSizePixel() );
            }
        }

        aRetBmp = maCachedBitmap;
    }

    if( !aRetBmp.IsEmpty() )
    {
        aRetBmp.SetPrefMapMode(getPrefMapMode());
        aRetBmp.SetPrefSize(getPrefSize());
    }

    return aRetBmp;
}

Animation ImpGraphic::getAnimation() const
{
    Animation aAnimation;

    ensureAvailable();

    if (mpAnimationContainer)
        aAnimation = mpAnimationContainer->maAnimation;

    return aAnimation;
}

const Bitmap& ImpGraphic::getBitmapRef() const
{
    ensureAvailable();

    if (mpBitmapContainer)
        return mpBitmapContainer->getBitmapRef();
    else
        return maCachedBitmap;
}

const GDIMetaFile& ImpGraphic::getGDIMetaFile() const
{
    ensureAvailable();
    if (!maMetaFile.GetActionSize()
        && maVectorGraphicData
        && (VectorGraphicDataType::Emf == maVectorGraphicData->getType()
            || VectorGraphicDataType::Wmf == maVectorGraphicData->getType()))
    {
        // If we have a Emf/Wmf VectorGraphic object, we
        // need a way to get the Metafile data out of the primitive
        // representation. Use a strict virtual hook (MetafileAccessor)
        // to access the MetafilePrimitive2D directly. Also see comments in
        // XEmfParser about this.
        const std::deque< css::uno::Reference< css::graphic::XPrimitive2D > > aSequence(maVectorGraphicData->getPrimitive2DSequence());

        if (1 == aSequence.size())
        {
            // try to cast to MetafileAccessor implementation
            const css::uno::Reference< css::graphic::XPrimitive2D >& xReference(aSequence[0]);
            auto pUnoPrimitive = static_cast< const drawinglayer::primitive2d::UnoPrimitive2D* >(xReference.get());
            if (pUnoPrimitive)
            {
                const MetafileAccessor* pMetafileAccessor = dynamic_cast< const MetafileAccessor* >(pUnoPrimitive->getBasePrimitive2D().get());

                if (pMetafileAccessor)
                {
                    // it is a MetafileAccessor implementation, get Metafile
                    pMetafileAccessor->accessMetafile(const_cast< ImpGraphic* >(this)->maMetaFile);
                }
            }
        }
    }

    if (GraphicType::Bitmap == meType && !maMetaFile.GetActionSize())
    {
        if (maVectorGraphicData)
            updateBitmapFromVectorGraphic();

        // #i119735#
        // Use the local maMetaFile as container for a metafile-representation
        // of the bitmap graphic. This will be done only once, thus be buffered.
        // I checked all usages of maMetaFile, it is only used when type is not
        // GraphicType::Bitmap. In operator= it will get copied, thus buffering will
        // survive copying (change this if not wanted)
        ImpGraphic* pThat = const_cast< ImpGraphic* >(this);

        Bitmap aBitmap = mpBitmapContainer ? mpBitmapContainer->maBitmap : maCachedBitmap;

        // #123983# directly create a metafile with the same PrefSize and PrefMapMode
        // the bitmap has, this will be an always correct metafile
        if (aBitmap.HasAlpha())
        {
            pThat->maMetaFile.AddAction(new MetaBmpExScaleAction(Point(), aBitmap.GetPrefSize(), aBitmap));
        }
        else
        {
            pThat->maMetaFile.AddAction(new MetaBmpScaleAction(Point(), aBitmap.GetPrefSize(), aBitmap));
        }

        pThat->maMetaFile.Stop();
        pThat->maMetaFile.WindStart();
        pThat->maMetaFile.SetPrefSize(aBitmap.GetPrefSize());
        pThat->maMetaFile.SetPrefMapMode(aBitmap.GetPrefMapMode());
    }

    return maMetaFile;
}

Size ImpGraphic::getSizePixel() const
{
    Size aSize;

    if (isSwappedOut())
        aSize = maSwapInfo.maSizePixel;
    else
        aSize = getBitmap(GraphicConversionParameters()).GetSizePixel();

    return aSize;
}

bool ImpGraphic::hasAlpha() const
{
    if (isSwappedOut())
        return maSwapInfo.mbIsAlpha;

    return getBitmap(GraphicConversionParameters()).HasAlpha();
}

Size ImpGraphic::getPrefSize() const
{
    Size aSize;

    if (isSwappedOut())
    {
        aSize = maSwapInfo.maPrefSize;
    }
    else
    {
        switch (meType)
        {
            case GraphicType::Bitmap:
            {
                if (maVectorGraphicData)
                {
                    if (maCachedBitmap.IsEmpty())
                    {
                        if (!maExPrefSize.getWidth() || !maExPrefSize.getHeight())
                        {
                            // svg not yet buffered in maBitmapEx, return size derived from range
                            const basegfx::B2DRange& rRange = maVectorGraphicData->getRange();

    #ifdef MACOSX
                            // tdf#157680 scale down estimated size of embedded PDF
                            // For some unknown reason, the embedded PDF sizes
                            // are 20x larger than expected. This only occurs on
                            // macOS so possibly there is some special conversion
                            // from MapUnit::MapPoint to MapUnit::MapTwip elsewhere
                            // in the code.
                            if (maVectorGraphicData->getType() == VectorGraphicDataType::Pdf)
                                aSize = Size(basegfx::fround(rRange.getWidth() / 20.0f), basegfx::fround(rRange.getHeight() / 20.0f));
                            else
    #endif
                                aSize = Size(basegfx::fround<tools::Long>(rRange.getWidth()), basegfx::fround<tools::Long>(rRange.getHeight()));
                        }
                        else
                        {
                            aSize = maExPrefSize;
                        }
                    }
                    else
                    {
                        aSize = maCachedBitmap.GetPrefSize();

                        if (!aSize.Width() || !aSize.Height())
                            aSize = maCachedBitmap.GetSizePixel();
                    }
                }
                else if (mpBitmapContainer)
                {
                    aSize = mpBitmapContainer->getPrefSize();
                }
                else if (mpAnimationContainer)
                {
                    aSize = mpAnimationContainer->getPrefSize();
                }
            }
            break;

            case GraphicType::GdiMetafile:
            {
                aSize = maMetaFile.GetPrefSize();
            }
            break;

            case GraphicType::NONE:
            case GraphicType::Default:
                break;
        }
    }

    return aSize;
}

void ImpGraphic::setValuesForPrefSize(const Size& rPrefSize)
{
    switch (meType)
    {
        case GraphicType::Bitmap:
        {
            // used when importing a writer FlyFrame with SVG as graphic, added conversion
            // to allow setting the PrefSize at the Bitmap to hold it
            if (maVectorGraphicData)
            {
                maExPrefSize = rPrefSize;
                maCachedBitmap.SetPrefSize(rPrefSize);
            }
            // #108077# Push through pref size to animation object,
            // will be lost on copy otherwise
            else if (mpAnimationContainer)
            {
                const_cast<Bitmap&>(mpAnimationContainer->maAnimation.GetBitmap()).SetPrefSize(rPrefSize);
                maCachedBitmap.SetPrefSize(rPrefSize);
            }
            else if (mpBitmapContainer)
            {
                mpBitmapContainer->maBitmap.SetPrefSize(rPrefSize);
            }
        }
        break;

        case GraphicType::GdiMetafile:
        {
            if (isSupportedGraphic())
                maMetaFile.SetPrefSize(rPrefSize);
        }
        break;

        case GraphicType::NONE:
        case GraphicType::Default:
            break;
    }
}

void ImpGraphic::setPrefSize(const Size& rPrefSize)
{
    ensureAvailable();
    setValuesForPrefSize(rPrefSize);
}

MapMode ImpGraphic::getPrefMapMode() const
{
    MapMode aMapMode;

    if (isSwappedOut())
    {
        aMapMode = maSwapInfo.maPrefMapMode;
    }
    else
    {
        switch (meType)
        {
            case GraphicType::Bitmap:
            {
                if (maVectorGraphicData)
                {
                    if (maCachedBitmap.IsEmpty())
                    {
                        // svg not yet buffered in maBitmapEx, return default PrefMapMode
                        aMapMode = MapMode(MapUnit::Map100thMM);
                    }
                    else
                    {
                        const Size aSize = maCachedBitmap.GetPrefSize();
                        if (aSize.Width() && aSize.Height())
                            aMapMode = maCachedBitmap.GetPrefMapMode();
                    }
                }
                else if (mpBitmapContainer)
                {
                    aMapMode = mpBitmapContainer->getPrefMapMode();
                }
                else if (mpAnimationContainer)
                {
                    aMapMode = mpAnimationContainer->getPrefMapMode();
                }
            }
            break;

            case GraphicType::GdiMetafile:
            {
                return maMetaFile.GetPrefMapMode();
            }
            break;

            case GraphicType::NONE:
            case GraphicType::Default:
                break;
        }
    }

    return aMapMode;
}

void ImpGraphic::setValuesForPrefMapMod(const MapMode& rPrefMapMode)
{
    switch (meType)
    {
        case GraphicType::Bitmap:
        {
            if (maVectorGraphicData)
            {
                // ignore for Vector Graphic Data. If this is really used (except the grfcache)
                // it can be extended by using maBitmapEx as buffer for updateBitmapFromVectorGraphic()
            }

            // #108077# Push through pref mapmode to animation object,
            // will be lost on copy otherwise
            else if (mpAnimationContainer)
            {
                const_cast<Bitmap&>(mpAnimationContainer->maAnimation.GetBitmap()).SetPrefMapMode(rPrefMapMode);
                maCachedBitmap.SetPrefMapMode(rPrefMapMode);
            }
            else if (mpBitmapContainer)
            {
                mpBitmapContainer->maBitmap.SetPrefMapMode(rPrefMapMode);
            }
        }
        break;

        case GraphicType::GdiMetafile:
        {
            maMetaFile.SetPrefMapMode(rPrefMapMode);
        }
        break;

        case GraphicType::NONE:
        case GraphicType::Default:
            break;
    }
}

void ImpGraphic::setPrefMapMode(const MapMode& rPrefMapMode)
{
    ensureAvailable();
    setValuesForPrefMapMod(rPrefMapMode);
}

void ImpGraphic::ensureCurrentSizeInBytes()
{
    if (isAvailable())
        changeExisting(getSizeBytes());
    else
        changeExisting(0);
}

sal_Int64 ImpGraphic::getSizeBytes() const
{
    if (mnSizeBytes > 0)
        return mnSizeBytes;

    if (mbPrepared)
        ensureAvailable();

    switch (meType)
    {
        case GraphicType::Bitmap:
        {
            if (maVectorGraphicData)
            {
                std::pair<VectorGraphicData::State, size_t> aPair(maVectorGraphicData->getSizeBytes());
                if (VectorGraphicData::State::UNPARSED == aPair.first)
                {
                    return aPair.second; // don't cache it until Vector Graphic Data is parsed
                }
                mnSizeBytes = aPair.second;
            }
            else
            {
                if (mpAnimationContainer)
                    mnSizeBytes = mpAnimationContainer->getSizeBytes();
                else if (mpBitmapContainer)
                    mnSizeBytes = mpBitmapContainer->getSizeBytes();
            }
        }
        break;

        case GraphicType::GdiMetafile:
        {
            mnSizeBytes = maMetaFile.GetSizeBytes();
        }
        break;

        case GraphicType::NONE:
        case GraphicType::Default:
            break;
    }

    return mnSizeBytes;
}

void ImpGraphic::draw(OutputDevice& rOutDev,
                      const Point& rDestPt, const Size& rDestSize) const
{
    ensureAvailable();

    if (isSwappedOut())
        return;

    switch (meType)
    {
        case GraphicType::Bitmap:
        {
            if (mpAnimationContainer)
            {
                mpAnimationContainer->maAnimation.Draw(rOutDev, rDestPt, rDestSize);
            }
            else if (mpBitmapContainer)
            {
                mpBitmapContainer->getBitmapRef().Draw(&rOutDev, rDestPt, rDestSize);
            }
            else if (maVectorGraphicData)
            {
                updateBitmapFromVectorGraphic(rOutDev.LogicToPixel(rDestSize));
                getBitmapRef().Draw(&rOutDev, rDestPt, rDestSize);
            }
        }
        break;

        case GraphicType::GdiMetafile:
        {
            const_cast<ImpGraphic*>(this)->maMetaFile.WindStart();
            const_cast<ImpGraphic*>(this)->maMetaFile.Play(rOutDev, rDestPt, rDestSize);
            const_cast<ImpGraphic*>(this)->maMetaFile.WindStart();
        }
        break;

        case GraphicType::Default:
        case GraphicType::NONE:
            break;
    }
}

void ImpGraphic::startAnimation(OutputDevice& rOutDev, const Point& rDestPt,
                                const Size& rDestSize, tools::Long nRendererId,
                                OutputDevice* pFirstFrameOutDev )
{
    ensureAvailable();

    if (isSupportedGraphic() && !isSwappedOut() && mpAnimationContainer)
        mpAnimationContainer->maAnimation.Start(rOutDev, rDestPt, rDestSize, nRendererId, pFirstFrameOutDev);
}

void ImpGraphic::stopAnimation( const OutputDevice* pOutDev, tools::Long nRendererId )
{
    ensureAvailable();

    if (isSupportedGraphic() && !isSwappedOut() && mpAnimationContainer)
        mpAnimationContainer->maAnimation.Stop( pOutDev, nRendererId );
}

void ImpGraphic::setAnimationNotifyHdl( const Link<Animation*,void>& rLink )
{
    ensureAvailable();

    if (mpAnimationContainer)
        mpAnimationContainer->maAnimation.SetNotifyHdl( rLink );
}

Link<Animation*,void> ImpGraphic::getAnimationNotifyHdl() const
{
    Link<Animation*,void> aLink;

    ensureAvailable();

    if (mpAnimationContainer)
        aLink = mpAnimationContainer->maAnimation.GetNotifyHdl();

    return aLink;
}

sal_uInt32 ImpGraphic::getAnimationLoopCount() const
{
    if (mbSwapOut)
        return maSwapInfo.mnAnimationLoopCount;

    return mpAnimationContainer ? mpAnimationContainer->getLoopCount() : 0;
}

bool ImpGraphic::swapInContent(SvStream& rStream)
{
    bool bRet = false;

    sal_uInt32 nId;
    sal_Int32 nType;
    sal_Int32 nLength;

    rStream.ReadUInt32(nId);

    // check version
    if (SWAP_FORMAT_ID != nId)
    {
        SAL_WARN("vcl", "Incompatible swap file!");
        return false;
    }

    rStream.ReadInt32(nType);
    rStream.ReadInt32(nLength);

    meType = static_cast<GraphicType>(nType);

    if (meType == GraphicType::NONE  || meType == GraphicType::Default)
    {
        return true;
    }
    else
    {
        bRet = swapInGraphic(rStream);
    }

    return bRet;
}

bool ImpGraphic::swapOutGraphic(SvStream& rStream)
{
    if (rStream.GetError())
        return false;

    ensureAvailable();

    if (isSwappedOut())
    {
        rStream.SetError(SVSTREAM_GENERALERROR);
        return false;
    }

    switch (meType)
    {
        case GraphicType::GdiMetafile:
        {
            if(!rStream.GetError())
            {
                SvmWriter aWriter(rStream);
                aWriter.Write(maMetaFile);
            }
        }
        break;

        case GraphicType::Bitmap:
        {
            if (maVectorGraphicData)
            {
                rStream.WriteInt32(sal_Int32(GraphicContentType::Vector));
                // stream out Vector Graphic defining data (length, byte array and evtl. path)
                // this is used e.g. in swapping out graphic data and in transporting it over UNO API
                // as sequence of bytes, but AFAIK not written anywhere to any kind of file, so it should be
                // no problem to extend it; only used at runtime
                switch (maVectorGraphicData->getType())
                {
                    case VectorGraphicDataType::Wmf:
                    {
                        rStream.WriteUInt32(constWmfMagic);
                        break;
                    }
                    case VectorGraphicDataType::Emf:
                    {
                        rStream.WriteUInt32(constEmfMagic);
                        break;
                    }
                    case VectorGraphicDataType::Svg:
                    {
                        rStream.WriteUInt32(constSvgMagic);
                        break;
                    }
                    case VectorGraphicDataType::Pdf:
                    {
                        rStream.WriteUInt32(constPdfMagic);
                        break;
                    }
                }

                rStream.WriteUInt32(maVectorGraphicData->getBinaryDataContainer().getSize());
                maVectorGraphicData->getBinaryDataContainer().writeToStream(rStream);
            }
            else if (mpAnimationContainer)
            {
                rStream.WriteInt32(sal_Int32(GraphicContentType::Animation));
                WriteAnimation(rStream, mpAnimationContainer->maAnimation);
            }
            else if (mpBitmapContainer)
            {
                rStream.WriteInt32(sal_Int32(GraphicContentType::Bitmap));
                WriteDIBBitmapEx(mpBitmapContainer->maBitmap, rStream);
            }
        }
        break;

        case GraphicType::NONE:
        case GraphicType::Default:
            break;
    }

    if (mpGfxLink)
        mpGfxLink->getDataContainer().swapOut();

    return true;
}

bool ImpGraphic::swapOutContent(SvStream& rStream)
{
    ensureAvailable();

    bool bRet = false;

    if (meType == GraphicType::NONE || meType == GraphicType::Default || isSwappedOut())
        return false;

    sal_uLong nDataFieldPos;

    // Write the SWAP ID
    rStream.WriteUInt32(SWAP_FORMAT_ID);

    rStream.WriteInt32(static_cast<sal_Int32>(meType));

    // data size is updated later
    nDataFieldPos = rStream.Tell();
    rStream.WriteInt32(0);

    // write data block
    const sal_uInt64 nDataStart = rStream.Tell();

    swapOutGraphic(rStream);

    if (!rStream.GetError())
    {
        // Write the written length th the header
        const sal_uInt64 nCurrentPosition = rStream.Tell();
        rStream.Seek(nDataFieldPos);
        rStream.WriteInt32(nCurrentPosition - nDataStart);
        rStream.Seek(nCurrentPosition);
        bRet = true;
    }

    return bRet;
}

bool ImpGraphic::swapOut()
{
    if (isSwappedOut())
        return false;

    bool bResult = false;

    // We have GfxLink so we have the source available
    if (mpGfxLink && mpGfxLink->IsNative())
    {
        createSwapInfo();

        clearGraphics();

        // reset the swap file
        mpSwapFile.reset();

        mpGfxLink->getDataContainer().swapOut();

        // mark as swapped out
        mbSwapOut = true;

        bResult = true;
    }
    else
    {
        // Create a swap file
        auto pSwapFile = std::make_shared<ImpSwapFile>(getOriginURL());

        // Open a stream to write the swap file to
        {
            SvStream* pOutputStream = pSwapFile->getStream();

            if (!pOutputStream)
                return false;

            // Write to stream
            pOutputStream->SetVersion(SOFFICE_FILEFORMAT_50);
            pOutputStream->SetCompressMode(SvStreamCompressFlags::NATIVE);
            pOutputStream->SetBufferSize(GRAPHIC_STREAMBUFSIZE);

            if (!pOutputStream->GetError() && swapOutContent(*pOutputStream))
            {
                pOutputStream->FlushBuffer();
                bResult = !pOutputStream->GetError();
            }
        }

        // Check if writing was successful
        if (bResult)
        {
            // We have swapped out, so can clean memory and prepare swap info
            createSwapInfo();
            clearGraphics();

            mpSwapFile = std::move(pSwapFile);
            mbSwapOut = true;
        }
    }

    if (bResult)
    {
        // Signal to manager that we have swapped out
        swappedOut(0);
    }

    return bResult;
}

bool ImpGraphic::ensureAvailable() const
{
    bool bResult = true;

    if (isSwappedOut())
    {
        auto pThis = const_cast<ImpGraphic*>(this);
        pThis->registerIntoManager();

        bResult = pThis->swapIn();
    }

    resetLastUsed();
    return bResult;
}

void ImpGraphic::updateFromLoadedGraphic(const ImpGraphic* pGraphic)
{
    if (mbPrepared)
    {
        GraphicExternalLink aLink = maGraphicExternalLink;
        Size aPrefSize = maSwapInfo.maPrefSize;
        MapMode aPrefMapMode = maSwapInfo.maPrefMapMode;
        *this = *pGraphic;
        if (aPrefSize.getWidth() && aPrefSize.getHeight() && aPrefMapMode == getPrefMapMode())
        {
            // Use custom preferred size if it was set when the graphic was still unloaded.
            // Only set the size in case the unloaded and loaded unit matches.
            setPrefSize(aPrefSize);
        }
        maGraphicExternalLink = std::move(aLink);
    }
    else
    {
        // Move over only graphic content
        mpAnimationContainer.reset();

        if (pGraphic->mpAnimationContainer)
        {
            mpAnimationContainer = std::make_shared<AnimationContainer>(*pGraphic->mpAnimationContainer);
            maCachedBitmap = mpAnimationContainer->maAnimation.GetBitmap();
        }
        else if (pGraphic->mpBitmapContainer)
        {
            mpBitmapContainer = pGraphic->mpBitmapContainer;
        }
        else
        {
            maCachedBitmap = pGraphic->maCachedBitmap;
        }

        maMetaFile = pGraphic->maMetaFile;
        maVectorGraphicData = pGraphic->maVectorGraphicData;

        // Set to 0, to force recalculation
        mnSizeBytes = 0;
        mnChecksum = 0;

        restoreFromSwapInfo();

        mbSwapOut = false;
    }
}

void ImpGraphic::dumpState(rtl::OStringBuffer &rState)
{
    if (meType == GraphicType::NONE && mnSizeBytes == 0)
        return; // uninteresting.

    rState.append("\n\t");

    if (mbSwapOut)
        rState.append("swapped\t");
    else
        rState.append("loaded\t");

    rState.append(static_cast<sal_Int32>(meType));
    rState.append("\tsize:\t");
    rState.append(mnSizeBytes);
    rState.append("\tgfxl:\t");
    rState.append(static_cast<sal_Int64>(mpGfxLink ? mpGfxLink->getSizeBytes() : -1));
    rState.append("\t");
    rState.append(static_cast<sal_Int32>(maSwapInfo.maSizePixel.Width()));
    rState.append("x");
    rState.append(static_cast<sal_Int32>(maSwapInfo.maSizePixel.Height()));
    rState.append("\t");
    rState.append(static_cast<sal_Int32>(maExPrefSize.Width()));
    rState.append("x");
    rState.append(static_cast<sal_Int32>(maExPrefSize.Height()));
}

void ImpGraphic::restoreFromSwapInfo()
{
    setValuesForPrefMapMod(maSwapInfo.maPrefMapMode);
    setValuesForPrefSize(maSwapInfo.maPrefSize);

    if (maVectorGraphicData)
    {
        maVectorGraphicData->setPageIndex(maSwapInfo.mnPageIndex);
    }
}

namespace
{

std::optional<VectorGraphicDataType> lclConvertToVectorGraphicType(GfxLink const & rLink)
{
    switch(rLink.GetType())
    {
        case GfxLinkType::NativePdf:
            return VectorGraphicDataType::Pdf;

        case GfxLinkType::NativeWmf:
            if (rLink.IsEMF())
                return VectorGraphicDataType::Emf;
            else
                return VectorGraphicDataType::Wmf;

        case GfxLinkType::NativeSvg:
            return VectorGraphicDataType::Svg;

        default:
            break;
    }
    return std::optional<VectorGraphicDataType>();
}

} // end namespace

bool ImpGraphic::swapIn()
{
    if (!isSwappedOut())
        return false;

    bool bReturn = false;

    if (mbPrepared)
    {
        Graphic aGraphic;
        if (!mpGfxLink->LoadNative(aGraphic, getPageNumber()))
            return false;

        updateFromLoadedGraphic(aGraphic.ImplGetImpGraphic());

        resetLastUsed();
        bReturn = true;
    }
    else if (mpGfxLink && mpGfxLink->IsNative())
    {
        std::optional<VectorGraphicDataType> oType = lclConvertToVectorGraphicType(*mpGfxLink);
        if (oType)
        {
            maVectorGraphicData = vcl::loadVectorGraphic(mpGfxLink->getDataContainer(), *oType);

            // Set to 0, to force recalculation
            mnSizeBytes = 0;
            mnChecksum = 0;

            restoreFromSwapInfo();

            mbSwapOut = false;
        }
        else
        {
            Graphic aGraphic;
            if (!mpGfxLink->LoadNative(aGraphic))
                return false;

            ImpGraphic* pImpGraphic = aGraphic.ImplGetImpGraphic();
            if (meType != pImpGraphic->meType)
                return false;

            updateFromLoadedGraphic(pImpGraphic);
        }

        resetLastUsed();
        bReturn = true;
    }
    else
    {
        SvStream* pStream = nullptr;

        if (mpSwapFile)
            pStream = mpSwapFile->getStream();

        if (pStream)
        {
            pStream->SetVersion(SOFFICE_FILEFORMAT_50);
            pStream->SetCompressMode(SvStreamCompressFlags::NATIVE);
            pStream->SetBufferSize(GRAPHIC_STREAMBUFSIZE);
            pStream->Seek(STREAM_SEEK_TO_BEGIN);

            bReturn = swapInFromStream(*pStream);

            restoreFromSwapInfo();

            setOriginURL(mpSwapFile->getOriginURL());

            mpSwapFile.reset();
        }
    }

    if (bReturn)
    {
        swappedIn(getSizeBytes());
    }

    return bReturn;
}

bool ImpGraphic::swapInFromStream(SvStream& rStream)
{
    bool bRet = false;

    if (rStream.GetError())
        return false;

    clearGraphics();
    mnSizeBytes = 0;
    mnChecksum = 0;

    bRet = swapInContent(rStream);

    if (!bRet)
    {
        //throw away swapfile, etc.
        clear();
    }

    mbSwapOut = false;

    return bRet;
}

bool ImpGraphic::swapInGraphic(SvStream& rStream)
{
    bool bReturn = false;

    if (rStream.GetError())
        return bReturn;

    if (meType == GraphicType::Bitmap)
    {
        sal_Int32 nContentType = -1;
        rStream.ReadInt32(nContentType);
        if (nContentType < 0)
            return false;

        auto eContentType = static_cast<GraphicContentType>(nContentType);

        switch (eContentType)
        {
            case GraphicContentType::Bitmap:
            {
                Bitmap aBitmap;
                ReadDIBBitmapEx(aBitmap, rStream);
                if (!rStream.GetError())
                {
                    mpBitmapContainer = std::make_shared<BitmapContainer>(aBitmap);
                    bReturn = true;
                }
            }
            break;

            case GraphicContentType::Animation:
            {
                Animation aAnimation;
                ReadAnimation(rStream, aAnimation);
                if (!rStream.GetError())
                {
                    mpAnimationContainer = std::make_shared<AnimationContainer>(aAnimation);
                    maCachedBitmap = mpAnimationContainer->maAnimation.GetBitmap();
                    bReturn = true;
                }
            }
            break;

            case GraphicContentType::Vector:
            {
                // try to stream in Svg defining data (length, byte array and evtl. path)
                // See below (operator<<) for more information
                sal_uInt32 nMagic;
                rStream.ReadUInt32(nMagic);

                if (constSvgMagic == nMagic || constWmfMagic == nMagic || constEmfMagic == nMagic || constPdfMagic == nMagic)
                {
                    sal_uInt32 nVectorGraphicDataSize(0);
                    rStream.ReadUInt32(nVectorGraphicDataSize);

                    if (nVectorGraphicDataSize)
                    {
                        BinaryDataContainer aDataContainer(rStream, nVectorGraphicDataSize);

                        if (rStream.GetError())
                            return false;

                        VectorGraphicDataType aDataType;

                        switch (nMagic)
                        {
                            case constSvgMagic:
                                aDataType = VectorGraphicDataType::Svg;
                                break;
                            case constWmfMagic:
                                aDataType = VectorGraphicDataType::Wmf;
                                break;
                            case constEmfMagic:
                                aDataType = VectorGraphicDataType::Emf;
                                break;
                            case constPdfMagic:
                                aDataType = VectorGraphicDataType::Pdf;
                                break;
                            default:
                                return false;
                        }

                        auto aVectorGraphicDataPtr = std::make_shared<VectorGraphicData>(aDataContainer, aDataType);

                        if (!rStream.GetError())
                        {
                            maVectorGraphicData = std::move(aVectorGraphicDataPtr);
                            bReturn = true;
                        }
                    }
                }
            }
            break;
        }
    }
    else if (meType == GraphicType::GdiMetafile)
    {
        GDIMetaFile aMetaFile;
        SvmReader aReader(rStream);
        aReader.Read(aMetaFile);
        if (!rStream.GetError())
        {
            maMetaFile = aMetaFile;
            bReturn = true;
        }
    }
    return bReturn;
}

void ImpGraphic::setGfxLink(const std::shared_ptr<GfxLink>& rGfxLink)
{
    ensureAvailable();

    mpGfxLink = rGfxLink;
}

const std::shared_ptr<GfxLink> & ImpGraphic::getSharedGfxLink() const
{
    return mpGfxLink;
}

GfxLink ImpGraphic::getGfxLink() const
{
    ensureAvailable();

    return( mpGfxLink ? *mpGfxLink : GfxLink() );
}

bool ImpGraphic::isGfxLink() const
{
    return ( bool(mpGfxLink) );
}

BitmapChecksum ImpGraphic::getChecksum() const
{
    if (mnChecksum != 0)
        return mnChecksum;

    if (mpGfxLink && mpGfxLink->GetDataSize())
    {
        // We have a compressed stream: then do the CRC on it to avoid
        // decompressing just for checksum purposes.
        mnChecksum = mpGfxLink->getDataContainer().getChecksum();
        if (mnChecksum != 0)
            return mnChecksum;
    }

    ensureAvailable();

    switch (meType)
    {
        case GraphicType::NONE:
        case GraphicType::Default:
            break;

        case GraphicType::Bitmap:
        {
            if (maVectorGraphicData)
                mnChecksum = maVectorGraphicData->GetChecksum();
            else if (mpAnimationContainer)
                mnChecksum = mpAnimationContainer->getChecksum();
            else if (mpBitmapContainer)
                mnChecksum = mpBitmapContainer->getChecksum();
        }
        break;

        case GraphicType::GdiMetafile:
        {
            mnChecksum = SvmWriter::GetChecksum(maMetaFile);
        }
        break;
    }
    return mnChecksum;
}

sal_Int32 ImpGraphic::getPageNumber() const
{
    if (isSwappedOut())
        return maSwapInfo.mnPageIndex;

    if (maVectorGraphicData)
        return maVectorGraphicData->getPageIndex();
    return -1;
}

bool ImpGraphic::canReduceMemory() const
{
    return !isSwappedOut();
}

bool ImpGraphic::reduceMemory()
{
    return swapOut();
}

std::chrono::high_resolution_clock::time_point ImpGraphic::getLastUsed() const
{
    return maLastUsed;
}

void ImpGraphic::resetLastUsed() const
{
    maLastUsed = std::chrono::high_resolution_clock::now();
}

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