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

#include <vcl/alpha.hxx>
#include <vcl/canvastools.hxx>
#include <vcl/pdfextoutdevdata.hxx>
#include <vcl/graph.hxx>
#include <vcl/outdev.hxx>
#include <vcl/gfxlink.hxx>
#include <vcl/metaact.hxx>
#include <vcl/graphicfilter.hxx>
#include <vcl/graphic/GraphicMetadata.hxx>
#include <vcl/pdf/PDFNote.hxx>
#include <basegfx/polygon/b2dpolygon.hxx>
#include <basegfx/polygon/b2dpolygontools.hxx>
#include <sal/log.hxx>
#include <o3tl/safeint.hxx>
#include <osl/diagnose.h>
#include <tools/stream.hxx>

#include <memory>
#include <map>
#include <variant>

namespace vcl
{
namespace {

struct CreateNamedDest {
    OUString maDestName;
    MapMode maParaMapMode;
    pdf::PDFWriter::DestAreaType mnParaDestAreaType;
    tools::Rectangle maParaRect;
    sal_Int32 mnPage;
};
struct CreateDest {
    MapMode maParaMapMode;
    pdf::PDFWriter::DestAreaType mnParaDestAreaType;
    tools::Rectangle maParaRect;
    sal_Int32 mnPage;
};
struct CreateControlLink { sal_Int32 mnControlId; };
struct CreateLink {
    OUString maAltText;
    MapMode maParaMapMode;
    tools::Rectangle maParaRect;
    sal_Int32 mnPage;
};
struct CreateScreen {
    OUString maAltText;
    OUString maMimeType;
    MapMode maParaMapMode;
    tools::Rectangle maParaRect;
    sal_Int32 mnPage;
};
struct SetLinkDest {
    sal_Int32 mnLinkId;
    sal_Int32 mnDestId;
};
struct SetLinkURL {
    OUString maLinkURL;
    sal_Int32 mnLinkId;
};
struct SetScreenURL {
    OUString maScreenURL;
    sal_Int32 mnScreenId;
};
struct SetScreenStream {
    OUString maScreenStream;
    sal_Int32 mnScreenId;
};
struct RegisterDest { sal_Int32 mnDestId; };
struct CreateOutlineItem {
    OUString maText;
    sal_Int32 mnParent;
    sal_Int32 mnDestID;
};

struct CreateNote {
    MapMode maParaMapMode;
    vcl::pdf::PDFNote maParaPDFNote;
    tools::Rectangle maParaRect;
    tools::Rectangle maPopupRect;
    sal_Int32 mnPage;
};

struct SetPageTransition {
    pdf::PDFWriter::PageTransition maParaPageTransition;
    sal_uInt32 mnMilliSec;
    sal_Int32 mnPage;
};
struct EnsureStructureElement { sal_Int32 mnId; };
struct InitStructureElement
{
    vcl::pdf::StructElement mParaStructElement;
    OUString maAlias;
    sal_Int32 mnId;
};
struct BeginStructureElement { sal_Int32 mnId; };
struct EndStructureElement{};
struct SetCurrentStructureElement { sal_Int32 mnStructId; };
struct SetStructureAttribute {
    pdf::PDFWriter::StructAttribute mParaStructAttribute;
    pdf::PDFWriter::StructAttributeValue mParaStructAttributeValue;
};
struct SetStructureAttributeNumerical { pdf::PDFWriter::StructAttribute mParaStructAttribute; sal_Int32 mnId; };
struct SetStructureBoundingBox { tools::Rectangle mRect; };
struct SetStructureAnnotIds {
    ::std::vector<sal_Int32> annotIds;
};
struct SetActualText { OUString maText; };
struct SetAlternateText { OUString maText; };
struct CreateControl {
    std::shared_ptr< pdf::PDFWriter::AnyWidget > mxControl;
};
struct BeginGroup {};
struct EndGroupGfxLink {
    Graphic maGraphic;
    tools::Rectangle maOutputRect, maVisibleOutputRect;
    sal_Int32 mnTransparency;
};

typedef std::variant<CreateNamedDest,
                    CreateDest,
                    CreateControlLink,
                    CreateLink,
                    CreateScreen,
                    SetLinkDest,
                    SetLinkURL,
                    SetScreenURL,
                    SetScreenStream,
                    RegisterDest,
                    CreateOutlineItem,
                    CreateNote,
                    SetPageTransition> GlobalActionData;

typedef std::variant<EnsureStructureElement,
                    InitStructureElement,
                    BeginStructureElement,
                    EndStructureElement,
                    SetCurrentStructureElement,
                    SetStructureAttribute,
                    SetStructureAttributeNumerical,
                    SetStructureBoundingBox,
                    SetStructureAnnotIds,
                    SetActualText,
                    SetAlternateText,
                    CreateControl,
                    BeginGroup,
                    EndGroupGfxLink> PageActionData;

struct PDFExtOutDevDataSyncPage
{
    sal_uInt32  nIdx;
    PageActionData  eAct;
};

struct PDFLinkDestination
{
    tools::Rectangle               mRect;
    MapMode                 mMapMode;
    sal_Int32               mPageNr;
    pdf::PDFWriter::DestAreaType mAreaType;
};
}

struct GlobalSyncData
{
    std::deque< GlobalActionData >              mActions;
    ::std::map< sal_Int32, PDFLinkDestination > mFutureDestinations;

    sal_Int32 GetMappedId(sal_Int32 nLinkId);

    /** the way this appears to work: (only) everything that increments mCurId
        at recording time must put an item into mParaIds at playback time,
        so that the mCurId becomes the eventual index into mParaIds.
     */
    sal_Int32                   mCurId;
    std::vector< sal_Int32 >    mParaIds;
    std::map<void const*, sal_Int32> mSEMap;

    sal_Int32                   mCurrentStructElement;
    std::vector< sal_Int32 >    mStructParents;
    GlobalSyncData() :
            mCurId ( 0 ),
            mCurrentStructElement( 0 )
    {
        mStructParents.push_back(0); // because PDFWriterImpl has a dummy root
    }
    void PlayGlobalActions( pdf::PDFWriter& rWriter );
};

sal_Int32 GlobalSyncData::GetMappedId(sal_Int32 nLinkId)
{
    /*  negative values are intentionally passed as invalid IDs
     *  e.g. to create a new top level outline item
     */
    if( nLinkId >= 0 )
    {
        if ( o3tl::make_unsigned(nLinkId) < mParaIds.size() )
            nLinkId = mParaIds[ nLinkId ];
        else
            nLinkId = -1;

        SAL_WARN_IF( nLinkId < 0, "vcl", "unmapped id in GlobalSyncData" );
    }

    return nLinkId;
}

void GlobalSyncData::PlayGlobalActions( pdf::PDFWriter& rWriter )
{
    for (auto const& action : mActions)
    {
        if (std::holds_alternative<CreateNamedDest>(action)) { //i56629
            const vcl::CreateNamedDest& rCreateNamedDest = std::get<CreateNamedDest>(action);
            rWriter.Push( PushFlags::MAPMODE );
            rWriter.SetMapMode( rCreateNamedDest.maParaMapMode );
            mParaIds.push_back( rWriter.CreateNamedDest( rCreateNamedDest.maDestName, rCreateNamedDest.maParaRect, rCreateNamedDest.mnPage, rCreateNamedDest.mnParaDestAreaType ) );
            rWriter.Pop();
        }
        else if (std::holds_alternative<CreateDest>(action)) {
            const vcl::CreateDest& rCreateDest = std::get<CreateDest>(action);
            rWriter.Push( PushFlags::MAPMODE );
            rWriter.SetMapMode( rCreateDest.maParaMapMode );
            mParaIds.push_back( rWriter.CreateDest( rCreateDest.maParaRect, rCreateDest.mnPage, rCreateDest.mnParaDestAreaType ) );
            rWriter.Pop();
        }
        else if (std::holds_alternative<CreateControlLink>(action)) {
            const vcl::CreateControlLink& rCreateControlLink = std::get<CreateControlLink>(action);
            // tdf#157397: this must be called *in order* with CreateLink etc.
            rWriter.SetLinkPropertyID(rCreateControlLink.mnControlId, sal_Int32(mParaIds.size()));
            mParaIds.push_back(rCreateControlLink.mnControlId);
        }
        else if (std::holds_alternative<CreateLink>(action)) {
            const vcl::CreateLink& rCreateLink = std::get<CreateLink>(action);
            rWriter.Push( PushFlags::MAPMODE );
            rWriter.SetMapMode( rCreateLink.maParaMapMode );
            mParaIds.push_back( rWriter.CreateLink(rCreateLink.maParaRect, rCreateLink.mnPage, rCreateLink.maAltText) );
            // resolve LinkAnnotation structural attribute
            rWriter.SetLinkPropertyID( mParaIds.back(), sal_Int32(mParaIds.size()-1) );
            rWriter.Pop();
        }
        else if (std::holds_alternative<CreateScreen>(action)) {
            const vcl::CreateScreen& rCreateScreen = std::get<CreateScreen>(action);
            rWriter.Push(PushFlags::MAPMODE);
            rWriter.SetMapMode(rCreateScreen.maParaMapMode);
            mParaIds.push_back(rWriter.CreateScreen(rCreateScreen.maParaRect, rCreateScreen.mnPage, rCreateScreen.maAltText, rCreateScreen.maMimeType));
            // resolve AnnotIds structural attribute
            rWriter.SetLinkPropertyID(mParaIds.back(), sal_Int32(mParaIds.size()-1));
            rWriter.Pop();
        }
        else if (std::holds_alternative<SetLinkDest>(action)) {
            const vcl::SetLinkDest& rSetLinkDest = std::get<SetLinkDest>(action);
            sal_Int32 nLinkId = GetMappedId(rSetLinkDest.mnLinkId);
            sal_Int32 nDestId = GetMappedId(rSetLinkDest.mnDestId);
            rWriter.SetLinkDest( nLinkId, nDestId );
        }
        else if (std::holds_alternative<SetLinkURL>(action)) {
            const vcl::SetLinkURL& rSetLinkURL = std::get<SetLinkURL>(action);
            sal_Int32 nLinkId = GetMappedId(rSetLinkURL.mnLinkId);
            rWriter.SetLinkURL( nLinkId, rSetLinkURL.maLinkURL );
        }
        else if (std::holds_alternative<SetScreenURL>(action)) {
            const vcl::SetScreenURL& rSetScreenURL = std::get<SetScreenURL>(action);
            sal_Int32 nScreenId = GetMappedId(rSetScreenURL.mnScreenId);
            rWriter.SetScreenURL(nScreenId, rSetScreenURL.maScreenURL);
        }
        else if (std::holds_alternative<SetScreenStream>(action)) {
            const vcl::SetScreenStream& rSetScreenStream = std::get<SetScreenStream>(action);
            sal_Int32 nScreenId = GetMappedId(rSetScreenStream.mnScreenId);
            rWriter.SetScreenStream(nScreenId, rSetScreenStream.maScreenStream);
        }
        else if (std::holds_alternative<RegisterDest>(action)) {
            const vcl::RegisterDest& rRegisterDest = std::get<RegisterDest>(action);
            const sal_Int32 nDestId = rRegisterDest.mnDestId;
            OSL_ENSURE( mFutureDestinations.find( nDestId ) != mFutureDestinations.end(),
                "GlobalSyncData::PlayGlobalActions: DescribeRegisteredRequest has not been called for that destination!" );

            PDFLinkDestination& rDest = mFutureDestinations[ nDestId ];

            rWriter.Push( PushFlags::MAPMODE );
            rWriter.SetMapMode( rDest.mMapMode );
            mParaIds.push_back( rWriter.RegisterDestReference( nDestId, rDest.mRect, rDest.mPageNr, rDest.mAreaType ) );
            rWriter.Pop();
        }
        else if (std::holds_alternative<CreateOutlineItem>(action)) {
            const vcl::CreateOutlineItem& rCreateOutlineItem = std::get<CreateOutlineItem>(action);
            sal_Int32 nParent = GetMappedId(rCreateOutlineItem.mnParent);
            sal_Int32 nLinkId = GetMappedId(rCreateOutlineItem.mnDestID);
            mParaIds.push_back( rWriter.CreateOutlineItem( nParent, rCreateOutlineItem.maText, nLinkId ) );
        }
        else if (std::holds_alternative<CreateNote>(action)) {
            const vcl::CreateNote& rCreateNote = std::get<CreateNote>(action);
            rWriter.Push( PushFlags::MAPMODE );
            rWriter.SetMapMode( rCreateNote.maParaMapMode );
            mParaIds.push_back(rWriter.CreateNote(rCreateNote.maParaRect, rCreateNote.maPopupRect, rCreateNote.maParaPDFNote, rCreateNote.mnPage));
            rWriter.SetLinkPropertyID(mParaIds.back(), sal_Int32(mParaIds.size() - 1));
            rWriter.Pop();
        }
        else if (std::holds_alternative<SetPageTransition>(action)) {
            const vcl::SetPageTransition& rSetPageTransition = std::get<SetPageTransition>(action);
            rWriter.SetPageTransition( rSetPageTransition.maParaPageTransition, rSetPageTransition.mnMilliSec, rSetPageTransition.mnPage );
        }
    }
}

struct PageSyncData
{
    std::deque< PDFExtOutDevDataSyncPage >          mActions;
    Graphic                                         mCurrentGraphic;
    GlobalSyncData*                                 mpGlobalData;

    bool                                        mbGroupIgnoreGDIMtfActions;


    explicit PageSyncData( GlobalSyncData* pGlobal )
        : mbGroupIgnoreGDIMtfActions ( false )
    { mpGlobalData = pGlobal; }

    void PushAction( const OutputDevice& rOutDev, PageActionData eAct );
    bool PlaySyncPageAct( pdf::PDFWriter& rWriter, sal_uInt32& rCurGDIMtfAction, const GDIMetaFile& rMtf, const PDFExtOutDevData& rOutDevData );
};

void PageSyncData::PushAction( const OutputDevice& rOutDev, PageActionData eAct )
{
    GDIMetaFile* pMtf = rOutDev.GetConnectMetaFile();
    SAL_WARN_IF( !pMtf, "vcl", "PageSyncData::PushAction -> no ConnectMetaFile !!!" );

    PDFExtOutDevDataSyncPage aSync;
    aSync.eAct = std::move(eAct);
    if ( pMtf )
        aSync.nIdx = pMtf->GetActionSize();
    else
        aSync.nIdx = 0x7fffffff;    // sync not possible
    mActions.emplace_back( std::move(aSync) );
}
bool PageSyncData::PlaySyncPageAct( pdf::PDFWriter& rWriter, sal_uInt32& rCurGDIMtfAction, const GDIMetaFile& rMtf, const PDFExtOutDevData& rOutDevData )
{
    bool bRet = false;
    if ( !mActions.empty() && ( mActions.front().nIdx == rCurGDIMtfAction ) )
    {
        bRet = true;
        PDFExtOutDevDataSyncPage aDataSync = std::move(mActions.front());
        mActions.pop_front();
        if (std::holds_alternative<EnsureStructureElement>(aDataSync.eAct)) {
#ifndef NDEBUG
            const vcl::EnsureStructureElement& rEnsureStructureElement = std::get<EnsureStructureElement>(aDataSync.eAct);
            sal_Int32 const id =
#endif
                rWriter.EnsureStructureElement();
            assert(id == -1 || id == rEnsureStructureElement.mnId); // identity mapping
        }
        else if (std::holds_alternative<InitStructureElement>(aDataSync.eAct)) {
            const vcl::InitStructureElement& rInitStructureElement = std::get<InitStructureElement>(aDataSync.eAct);
            rWriter.InitStructureElement(rInitStructureElement.mnId, rInitStructureElement.mParaStructElement, rInitStructureElement.maAlias);
        }
        else if (std::holds_alternative<BeginStructureElement>(aDataSync.eAct)) {
            const vcl::BeginStructureElement& rBeginStructureElement = std::get<BeginStructureElement>(aDataSync.eAct);
            rWriter.BeginStructureElement(rBeginStructureElement.mnId);
        }
        else if (std::holds_alternative<EndStructureElement>(aDataSync.eAct)) {
            rWriter.EndStructureElement();
        }
        else if (std::holds_alternative<SetCurrentStructureElement>(aDataSync.eAct)) {
            const vcl::SetCurrentStructureElement& rSetCurrentStructureElement = std::get<SetCurrentStructureElement>(aDataSync.eAct);
            rWriter.SetCurrentStructureElement(rSetCurrentStructureElement.mnStructId);
        }
        else if (std::holds_alternative<SetStructureAttribute>(aDataSync.eAct)) {
            const vcl::SetStructureAttribute& rSetStructureAttribute = std::get<SetStructureAttribute>(aDataSync.eAct);
            rWriter.SetStructureAttribute( rSetStructureAttribute.mParaStructAttribute, rSetStructureAttribute.mParaStructAttributeValue );
        }
        else if (std::holds_alternative<SetStructureAttributeNumerical>(aDataSync.eAct)) {
            const vcl::SetStructureAttributeNumerical& rSetStructureAttributeNumerical = std::get<SetStructureAttributeNumerical>(aDataSync.eAct);
            rWriter.SetStructureAttributeNumerical( rSetStructureAttributeNumerical.mParaStructAttribute, rSetStructureAttributeNumerical.mnId );
        }
        else if (std::holds_alternative<SetStructureBoundingBox>(aDataSync.eAct)) {
            const vcl::SetStructureBoundingBox& rSetStructureBoundingBox = std::get<SetStructureBoundingBox>(aDataSync.eAct);
            rWriter.SetStructureBoundingBox( rSetStructureBoundingBox.mRect );
        }
        else if (std::holds_alternative<SetStructureAnnotIds>(aDataSync.eAct)) {
            const vcl::SetStructureAnnotIds& rSetStructureAnnotIds = std::get<SetStructureAnnotIds>(aDataSync.eAct);
            rWriter.SetStructureAnnotIds(rSetStructureAnnotIds.annotIds);
        }
        else if (std::holds_alternative<SetActualText>(aDataSync.eAct)) {
            const vcl::SetActualText& rSetActualText = std::get<SetActualText>(aDataSync.eAct);
            rWriter.SetActualText( rSetActualText.maText );
        }
        else if (std::holds_alternative<SetAlternateText>(aDataSync.eAct)) {
            const vcl::SetAlternateText& rSetAlternateText = std::get<SetAlternateText>(aDataSync.eAct);
            rWriter.SetAlternateText( rSetAlternateText.maText );
        }
        else if (std::holds_alternative<CreateControl>(aDataSync.eAct)) {
            const vcl::CreateControl& rCreateControl = std::get<CreateControl>(aDataSync.eAct);
            std::shared_ptr< pdf::PDFWriter::AnyWidget > pControl( rCreateControl.mxControl );
            SAL_WARN_IF( !pControl, "vcl", "PageSyncData::PlaySyncPageAct: invalid widget!" );
            if ( pControl )
            {
                sal_Int32 const n = rWriter.CreateControl(*pControl);
                // resolve AnnotIds structural attribute
                ::std::vector<sal_Int32> const annotIds{ sal_Int32(mpGlobalData->mCurId) };
                rWriter.SetStructureAnnotIds(annotIds);
                // tdf#157397: this must be called *in order* with CreateLink etc.
                mpGlobalData->mActions.push_back(CreateControlLink{n});
                mpGlobalData->mCurId++;
            }
        }
        else if (std::holds_alternative<BeginGroup>(aDataSync.eAct)) {
            /* first determining if this BeginGroup is starting a GfxLink,
               by searching for an EndGroup or an EndGroupGfxLink */
            mbGroupIgnoreGDIMtfActions = false;
            auto itStartingGfxLink = std::find_if(mActions.begin(), mActions.end(),
                [](const PDFExtOutDevDataSyncPage& rAction) { return std::holds_alternative<EndGroupGfxLink>(rAction.eAct); });
            if ( itStartingGfxLink != mActions.end() )
            {
                EndGroupGfxLink& rEndGroup = std::get<EndGroupGfxLink>(itStartingGfxLink->eAct);
                Graphic& rGraphic = rEndGroup.maGraphic;
                if ( rGraphic.IsGfxLink() )
                {
                    GfxLinkType eType = rGraphic.GetGfxLink().GetType();
                    if ( eType == GfxLinkType::NativeJpg )
                    {
                        mbGroupIgnoreGDIMtfActions = rOutDevData.HasAdequateCompression(rGraphic, rEndGroup.maOutputRect, rEndGroup.maVisibleOutputRect);
                        if ( !mbGroupIgnoreGDIMtfActions )
                            mCurrentGraphic = rGraphic;
                    }
                    else if ( eType == GfxLinkType::NativePng || eType == GfxLinkType::NativePdf )
                    {
                        if ( eType == GfxLinkType::NativePdf || rOutDevData.HasAdequateCompression(rGraphic, rEndGroup.maOutputRect, rEndGroup.maVisibleOutputRect) )
                            mCurrentGraphic = rGraphic;
                    }
                }
            }
        }
        else if (std::holds_alternative<EndGroupGfxLink>(aDataSync.eAct)) {
            EndGroupGfxLink& rEndGroup = std::get<EndGroupGfxLink>(aDataSync.eAct);
            tools::Rectangle aOutputRect, aVisibleOutputRect;
            Graphic   aGraphic( rEndGroup.maGraphic );

            sal_Int32 nTransparency = rEndGroup.mnTransparency;
            aOutputRect = rEndGroup.maOutputRect;
            aVisibleOutputRect = rEndGroup.maVisibleOutputRect;

            if ( mbGroupIgnoreGDIMtfActions )
            {
                bool bClippingNeeded = ( aOutputRect != aVisibleOutputRect ) && !aVisibleOutputRect.IsEmpty();

                GfxLink   aGfxLink( aGraphic.GetGfxLink() );
                if ( aGfxLink.GetType() == GfxLinkType::NativeJpg )
                {
                    if ( bClippingNeeded )
                    {
                        rWriter.Push();
                        basegfx::B2DPolyPolygon aRect( basegfx::utils::createPolygonFromRect(
                            vcl::unotools::b2DRectangleFromRectangle(aVisibleOutputRect) ) );
                        rWriter.SetClipRegion( aRect);
                    }

                    AlphaMask aAlphaMask;
                    if (nTransparency)
                    {
                        aAlphaMask = AlphaMask(aGraphic.GetSizePixel());
                        aAlphaMask.Erase(nTransparency);
                    }

                    SvMemoryStream aTmp;
                    const sal_uInt8* pData = aGfxLink.GetData();
                    sal_uInt32 nBytes = aGfxLink.GetDataSize();
                    if( pData && nBytes )
                    {
                        aTmp.WriteBytes( pData, nBytes );

                        // Look up the output rectangle from the previous
                        // bitmap scale action if possible. This has the
                        // correct position and size for images with a
                        // custom translation (Writer header) or scaling
                        // (Impress notes page).
                        if (rCurGDIMtfAction > 0)
                        {
                            const MetaAction* pAction = rMtf.GetAction(rCurGDIMtfAction - 1);
                            if (pAction && pAction->GetType() == MetaActionType::BMPSCALE)
                            {
                                const MetaBmpScaleAction* pA
                                    = static_cast<const MetaBmpScaleAction*>(pAction);
                                aOutputRect.SetPos(pA->GetPoint());
                                aOutputRect.SetSize(pA->GetSize());
                            }
                        }
                        auto ePixelFormat = aGraphic.GetBitmap().getPixelFormat();
                        rWriter.DrawJPGBitmap(aTmp, ePixelFormat > vcl::PixelFormat::N8_BPP, aGraphic.GetSizePixel(), aOutputRect, aAlphaMask, aGraphic);
                    }

                    if ( bClippingNeeded )
                        rWriter.Pop();
                }
                mbGroupIgnoreGDIMtfActions = false;
            }
            mCurrentGraphic.Clear();
        }
    }
    else if ( mbGroupIgnoreGDIMtfActions )
    {
        rCurGDIMtfAction++;
        bRet = true;
    }
    return bRet;
}

PDFExtOutDevData::PDFExtOutDevData( const OutputDevice& rOutDev ) :
    mrOutDev                ( rOutDev ),
    mbTaggedPDF             ( false ),
    mbExportNotes           ( true ),
    mbExportNotesInMargin   ( false ),
    mbExportNotesPages      ( false ),
    mbTransitionEffects     ( true ),
    mbUseLosslessCompression( true ),
    mbReduceImageResolution ( false ),
    mbExportFormFields      ( false ),
    mbExportBookmarks       ( false ),
    mbExportHiddenSlides    ( false ),
    mbSinglePageSheets      ( false ),
    mbExportNDests          ( false ),
    mnPage                  ( -1 ),
    mnCompressionQuality    ( DefaultPDFJPEGQuality ),
    mpGlobalSyncData        ( new GlobalSyncData() )
{
    mpPageSyncData.reset( new PageSyncData( mpGlobalSyncData.get() ) );
}

PDFExtOutDevData::~PDFExtOutDevData()
{
    mpPageSyncData.reset();
    mpGlobalSyncData.reset();
}

const Graphic& PDFExtOutDevData::GetCurrentGraphic() const
{
    return mpPageSyncData->mCurrentGraphic;
}

void PDFExtOutDevData::SetDocumentLocale( const css::lang::Locale& rLoc )
{
    maDocLocale = rLoc;
}
void PDFExtOutDevData::SetCurrentPageNumber( const sal_Int32 nPage )
{
    mnPage = nPage;
}
void PDFExtOutDevData::SetIsLosslessCompression( const bool bUseLosslessCompression )
{
    mbUseLosslessCompression = bUseLosslessCompression;
}
void PDFExtOutDevData::SetCompressionQuality( const sal_Int32 nQuality )
{
    mnCompressionQuality = nQuality;
}
void PDFExtOutDevData::SetIsReduceImageResolution( const bool bReduceImageResolution )
{
    mbReduceImageResolution = bReduceImageResolution;
}
void PDFExtOutDevData::SetIsExportNotes( const bool bExportNotes )
{
    mbExportNotes = bExportNotes;
}
void PDFExtOutDevData::SetIsExportNotesInMargin( const bool bExportNotesInMargin )
{
    mbExportNotesInMargin = bExportNotesInMargin;
}
void PDFExtOutDevData::SetIsExportNotesPages( const bool bExportNotesPages )
{
    mbExportNotesPages = bExportNotesPages;
}
void PDFExtOutDevData::SetIsExportTaggedPDF( const bool bTaggedPDF )
{
    mbTaggedPDF = bTaggedPDF;
}
void PDFExtOutDevData::SetIsExportTransitionEffects( const bool bTransitionEffects )
{
    mbTransitionEffects = bTransitionEffects;
}
void PDFExtOutDevData::SetIsExportFormFields( const bool bExportFomtFields )
{
    mbExportFormFields = bExportFomtFields;
}
void PDFExtOutDevData::SetIsExportBookmarks( const bool bExportBookmarks )
{
    mbExportBookmarks = bExportBookmarks;
}
void PDFExtOutDevData::SetIsExportHiddenSlides( const bool bExportHiddenSlides )
{
    mbExportHiddenSlides = bExportHiddenSlides;
}
void PDFExtOutDevData::SetIsSinglePageSheets( const bool bSinglePageSheets )
{
    mbSinglePageSheets = bSinglePageSheets;
}
void PDFExtOutDevData::SetIsExportNamedDestinations( const bool bExportNDests )
{
    mbExportNDests = bExportNDests;
}
void PDFExtOutDevData::ResetSyncData(pdf::PDFWriter *const pWriter)
{
    if (pWriter != nullptr)
    {
        // tdf#157182 HACK: all PDF actions on this page will be deleted; to have
        // matching SE IDs on the *next* page, replay EnsureStructureElement actions
        for (PDFExtOutDevDataSyncPage const& rAction : mpPageSyncData->mActions)
        {
            if (std::holds_alternative<struct EnsureStructureElement>(rAction.eAct))
            {
                pWriter->EnsureStructureElement();
            }
        }
    }
    *mpPageSyncData = PageSyncData( mpGlobalSyncData.get() );
}
bool PDFExtOutDevData::PlaySyncPageAct( pdf::PDFWriter& rWriter, sal_uInt32& rIdx, const GDIMetaFile& rMtf )
{
    return mpPageSyncData->PlaySyncPageAct( rWriter, rIdx, rMtf, *this );
}
void PDFExtOutDevData::PlayGlobalActions( pdf::PDFWriter& rWriter )
{
    mpGlobalSyncData->PlayGlobalActions( rWriter );
}

/* global actions, synchronisation to the recorded metafile isn't needed,
   all actions will be played after the last page was recorded
*/
//--->i56629
sal_Int32 PDFExtOutDevData::CreateNamedDest(const OUString& sDestName,  const tools::Rectangle& rRect, sal_Int32 nPageNr )
{
    mpGlobalSyncData->mActions.push_back(
        vcl::CreateNamedDest{ sDestName, mrOutDev.GetMapMode(), pdf::PDFWriter::DestAreaType::XYZ, rRect, nPageNr == -1 ? mnPage : nPageNr } );

    return mpGlobalSyncData->mCurId++;
}
//<---i56629
sal_Int32 PDFExtOutDevData::RegisterDest()
{
    const sal_Int32 nLinkDestID = mpGlobalSyncData->mCurId++;
    mpGlobalSyncData->mActions.push_back( vcl::RegisterDest{ nLinkDestID } );

    return nLinkDestID;
}
void PDFExtOutDevData::DescribeRegisteredDest( sal_Int32 nDestId, const tools::Rectangle& rRect, sal_Int32 nPageNr, pdf::PDFWriter::DestAreaType eType )
{
    OSL_PRECOND( nDestId != -1, "PDFExtOutDevData::DescribeRegisteredDest: invalid destination Id!" );
    PDFLinkDestination aLinkDestination;
    aLinkDestination.mRect = rRect;
    aLinkDestination.mMapMode = mrOutDev.GetMapMode();
    aLinkDestination.mPageNr = nPageNr == -1 ? mnPage : nPageNr;
    aLinkDestination.mAreaType = eType;
    mpGlobalSyncData->mFutureDestinations[ nDestId ] = std::move(aLinkDestination);
}
sal_Int32 PDFExtOutDevData::CreateDest( const tools::Rectangle& rRect, sal_Int32 nPageNr, pdf::PDFWriter::DestAreaType eType )
{
    mpGlobalSyncData->mActions.push_back(
        vcl::CreateDest{ mrOutDev.GetMapMode(), eType, rRect, nPageNr == -1 ? mnPage : nPageNr } );
    return mpGlobalSyncData->mCurId++;
}
sal_Int32 PDFExtOutDevData::CreateLink(const tools::Rectangle& rRect, OUString const& rAltText, sal_Int32 nPageNr)
{
    mpGlobalSyncData->mActions.push_back(
        vcl::CreateLink{rAltText, mrOutDev.GetMapMode(), rRect, nPageNr == -1 ? mnPage : nPageNr } );
    return mpGlobalSyncData->mCurId++;
}

sal_Int32 PDFExtOutDevData::CreateScreen(const tools::Rectangle& rRect,
        OUString const& rAltText, OUString const& rMimeType,
        sal_Int32 nPageNr, SdrObject const*const pObj)
{
    mpGlobalSyncData->mActions.push_back(vcl::CreateScreen{ rAltText, rMimeType, mrOutDev.GetMapMode(), rRect, nPageNr });
    auto const ret(mpGlobalSyncData->mCurId++);
    m_ScreenAnnotations[pObj].push_back(ret);
    return ret;
}

::std::vector<sal_Int32> const& PDFExtOutDevData::GetScreenAnnotIds(SdrObject const*const pObj) const
{
    auto const it(m_ScreenAnnotations.find(pObj));
    if (it == m_ScreenAnnotations.end())
    {
        assert(false); // expected?
    }
    return it->second;
}

void PDFExtOutDevData::SetLinkDest( sal_Int32 nLinkId, sal_Int32 nDestId )
{
    mpGlobalSyncData->mActions.push_back( vcl::SetLinkDest{ nLinkId, nDestId } );
}
void PDFExtOutDevData::SetLinkURL( sal_Int32 nLinkId, const OUString& rURL )
{
    mpGlobalSyncData->mActions.push_back( vcl::SetLinkURL{ rURL, nLinkId } );
}

void PDFExtOutDevData::SetScreenURL(sal_Int32 nScreenId, const OUString& rURL)
{
    mpGlobalSyncData->mActions.push_back(vcl::SetScreenURL{ rURL, nScreenId });
}

void PDFExtOutDevData::SetScreenStream(sal_Int32 nScreenId, const OUString& rURL)
{
    mpGlobalSyncData->mActions.push_back(vcl::SetScreenStream{ rURL, nScreenId });
}

sal_Int32 PDFExtOutDevData::CreateOutlineItem( sal_Int32 nParent, const OUString& rText, sal_Int32 nDestID )
{
    if (nParent == -1)
        // Has no parent, it's a chapter / heading 1.
        maChapterNames.push_back(rText);

    mpGlobalSyncData->mActions.push_back( vcl::CreateOutlineItem{ rText, nParent, nDestID } );
    return mpGlobalSyncData->mCurId++;
}
sal_Int32 PDFExtOutDevData::CreateNote(const tools::Rectangle& rRect,
                                       const vcl::pdf::PDFNote& rNote,
                                       const tools::Rectangle& rPopupRect, sal_Int32 nPageNr)
{
    mpGlobalSyncData->mActions.push_back(vcl::CreateNote{
        mrOutDev.GetMapMode(), rNote, rRect, rPopupRect, nPageNr == -1 ? mnPage : nPageNr });
    return mpGlobalSyncData->mCurId++;
}
void PDFExtOutDevData::SetPageTransition( pdf::PDFWriter::PageTransition eType, sal_uInt32 nMilliSec )
{
    mpGlobalSyncData->mActions.push_back( vcl::SetPageTransition{ eType, nMilliSec, mnPage } );
}

/* local (page), actions have to be played synchronously to the actions of
   of the recorded metafile (created by each xRenderable->render()) */

sal_Int32 PDFExtOutDevData::EnsureStructureElement(void const*const key)
{
    sal_Int32 id(-1);
    if (key != nullptr)
    {
        auto const it(mpGlobalSyncData->mSEMap.find(key));
        if (it != mpGlobalSyncData->mSEMap.end())
        {
            id = it->second;
        }
    }
    if (id == -1)
    {
        id = mpGlobalSyncData->mStructParents.size();
        mpPageSyncData->PushAction(mrOutDev, vcl::EnsureStructureElement{ id });
        mpGlobalSyncData->mStructParents.push_back(mpGlobalSyncData->mCurrentStructElement);
        if (key != nullptr)
        {
            mpGlobalSyncData->mSEMap.emplace(key, id);
        }
    }
    return id;
}

void PDFExtOutDevData::InitStructureElement(sal_Int32 const id,
        vcl::pdf::StructElement const eType, const OUString& rAlias)
{
    mpPageSyncData->PushAction(mrOutDev, vcl::InitStructureElement{ eType, rAlias, id });
    // update parent: required for hell fly anchor frames in sw, so that on the actual
    // anchor frame EndStructureElement() resets mCurrentStructElement properly.
    mpGlobalSyncData->mStructParents[id] = mpGlobalSyncData->mCurrentStructElement;
}

void PDFExtOutDevData::BeginStructureElement(sal_Int32 const id)
{
    mpPageSyncData->PushAction( mrOutDev, vcl::BeginStructureElement{ id } );
    mpGlobalSyncData->mCurrentStructElement = id;
}

sal_Int32 PDFExtOutDevData::WrapBeginStructureElement(
        vcl::pdf::StructElement const eType, const OUString& rAlias)
{
    sal_Int32 const id = EnsureStructureElement(nullptr);
    InitStructureElement(id, eType, rAlias);
    BeginStructureElement(id);
    return id;
}

void PDFExtOutDevData::EndStructureElement()
{
    assert(mpGlobalSyncData->mCurrentStructElement != 0); // underflow?
    mpPageSyncData->PushAction( mrOutDev, vcl::EndStructureElement{} );
    mpGlobalSyncData->mCurrentStructElement = mpGlobalSyncData->mStructParents[ mpGlobalSyncData->mCurrentStructElement ];
}

void PDFExtOutDevData::SetCurrentStructureElement(sal_Int32 const nStructId)
{
    assert(o3tl::make_unsigned(nStructId) < mpGlobalSyncData->mStructParents.size());
    mpGlobalSyncData->mCurrentStructElement = nStructId;
    mpPageSyncData->PushAction( mrOutDev, vcl::SetCurrentStructureElement{ nStructId } );
}

sal_Int32 PDFExtOutDevData::GetCurrentStructureElement() const
{
    return mpGlobalSyncData->mCurrentStructElement;
}

void PDFExtOutDevData::SetStructureAttribute( pdf::PDFWriter::StructAttribute eAttr, pdf::PDFWriter::StructAttributeValue eVal )
{
    mpPageSyncData->PushAction( mrOutDev, vcl::SetStructureAttribute{ eAttr, eVal } );
}
void PDFExtOutDevData::SetStructureAttributeNumerical( pdf::PDFWriter::StructAttribute eAttr, sal_Int32 nValue )
{
    mpPageSyncData->PushAction( mrOutDev, vcl::SetStructureAttributeNumerical { eAttr, nValue } );
}
void PDFExtOutDevData::SetStructureBoundingBox( const tools::Rectangle& rRect )
{
    mpPageSyncData->PushAction( mrOutDev, vcl::SetStructureBoundingBox{ rRect } );
}

void PDFExtOutDevData::SetStructureAnnotIds(::std::vector<sal_Int32> const& rAnnotIds)
{
    mpPageSyncData->PushAction(mrOutDev, vcl::SetStructureAnnotIds{ rAnnotIds });
}

void PDFExtOutDevData::SetActualText( const OUString& rText )
{
    mpPageSyncData->PushAction( mrOutDev, vcl::SetActualText{ rText } );
}
void PDFExtOutDevData::SetAlternateText( const OUString& rText )
{
    mpPageSyncData->PushAction( mrOutDev, vcl::SetAlternateText{ rText } );
}

void PDFExtOutDevData::CreateControl( const pdf::PDFWriter::AnyWidget& rControlType )
{
    mpPageSyncData->PushAction(mrOutDev, vcl::CreateControl{ rControlType.Clone() });
}

void PDFExtOutDevData::BeginGroup()
{
    mpPageSyncData->PushAction( mrOutDev, vcl::BeginGroup{} );
}

void PDFExtOutDevData::EndGroup( const Graphic&     rGraphic,
                                 sal_uInt8          nTransparency,
                                 const tools::Rectangle&   rOutputRect,
                                 const tools::Rectangle&   rVisibleOutputRect )
{
    mpPageSyncData->PushAction( mrOutDev,
        vcl::EndGroupGfxLink{rGraphic, rOutputRect, rVisibleOutputRect, nTransparency} );
}

// Avoids expensive de-compression and re-compression of large images.
bool PDFExtOutDevData::HasAdequateCompression( const Graphic &rGraphic,
                                               const tools::Rectangle & rOutputRect,
                                               const tools::Rectangle & rVisibleOutputRect ) const
{
    assert(rGraphic.IsGfxLink() &&
           (rGraphic.GetGfxLink().GetType() == GfxLinkType::NativeJpg ||
            rGraphic.GetGfxLink().GetType() == GfxLinkType::NativePng ||
            rGraphic.GetGfxLink().GetType() == GfxLinkType::NativePdf));

    if (rOutputRect != rVisibleOutputRect)
        // rOutputRect is the crop rectangle, re-compress cropped image.
        return false;

    if (mbReduceImageResolution)
        // Reducing resolution was requested, implies that re-compressing is
        // wanted.
        return false;

    auto nSize = rGraphic.GetGfxLink().GetDataSize();
    if (nSize == 0)
        return false;

    GfxLink aLink = rGraphic.GetGfxLink();
    SvMemoryStream aMemoryStream(const_cast<sal_uInt8*>(aLink.GetData()), aLink.GetDataSize(),
                                 StreamMode::READ | StreamMode::WRITE);
    GraphicDescriptor aDescriptor(aMemoryStream, nullptr);
    if (aDescriptor.Detect(true) && aDescriptor.GetNumberOfImageComponents() == 4)
        // 4 means CMYK, which is not handled.
        return false;

    const Size aSize = rGraphic.GetSizePixel();

    // small items better off as PNG anyway
    if ( aSize.Width() < 32 &&
         aSize.Height() < 32 )
        return false;

    if (GetIsLosslessCompression())
        return !GetIsReduceImageResolution();

    // FIXME: ideally we'd also pre-empt the DPI related scaling too.
    sal_Int32 nCurrentRatio = (100 * aSize.Width() * aSize.Height() * 4) /
                               nSize;

    static const struct {
        sal_Int32 mnQuality;
        sal_Int32 mnRatio;
    } aRatios[] = { // minimum tolerable compression ratios
        { 100, 400 }, { 95, 700 }, { 90, 1000 }, { 85, 1200 },
        { 80, 1500 }, { 75, 1700 }
    };
    sal_Int32 nTargetRatio = 10000;
    bool bIsTargetRatioReached = false;
    for (auto & rRatio : aRatios)
    {
        if ( mnCompressionQuality > rRatio.mnQuality )
        {
            bIsTargetRatioReached = true;
            break;
        }
        nTargetRatio = rRatio.mnRatio;
    }

    return ((nCurrentRatio > nTargetRatio) && bIsTargetRatioReached);
}

}

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