/* -*- 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 <stdio.h>
#include <oox/drawingml/clrscheme.hxx>
#include <oox/token/namespaces.hxx>
#include <oox/token/tokens.hxx>
#include <oox/token/relationship.hxx>
#include <oox/ole/vbaproject.hxx>
#include "epptooxml.hxx"
#include <oox/export/shapes.hxx>
#include <svx/svdlayer.hxx>
#include <xmloff/autolayout.hxx>
#include <unokywds.hxx>
#include <osl/file.hxx>

#include <editeng/fontitem.hxx>
#include <editeng/eeitem.hxx>
#include <drawdoc.hxx>
#include <svl/itempool.hxx>
#include <editeng/section.hxx>
#include <editeng/editeng.hxx>

#include <comphelper/sequenceashashmap.hxx>
#include <comphelper/storagehelper.hxx>
#include <comphelper/string.hxx>
#include <comphelper/xmltools.hxx>
#include <sax/fshelper.hxx>
#include <sax/fastattribs.hxx>
#include <rtl/ustrbuf.hxx>
#include <sal/log.hxx>
#include <tools/UnitConversion.hxx>
#include <tools/urlobj.hxx>
#include <tools/datetime.hxx>
#include <unotools/securityoptions.hxx>
#include <com/sun/star/animations/TransitionType.hpp>
#include <com/sun/star/animations/TransitionSubType.hpp>
#include <com/sun/star/beans/XPropertySetInfo.hpp>
#include <com/sun/star/drawing/FillStyle.hpp>
#include <com/sun/star/drawing/XDrawPages.hpp>
#include <com/sun/star/drawing/XDrawPagesSupplier.hpp>
#include <com/sun/star/drawing/XMasterPageTarget.hpp>
#include <com/sun/star/drawing/XMasterPagesSupplier.hpp>
#include <com/sun/star/embed/ElementModes.hpp>
#include <com/sun/star/geometry/RealPoint2D.hpp>
#include <com/sun/star/office/XAnnotationEnumeration.hpp>
#include <com/sun/star/office/XAnnotationAccess.hpp>
#include <com/sun/star/presentation/AnimationSpeed.hpp>
#include <com/sun/star/util/DateTime.hpp>
#include <com/sun/star/task/XStatusIndicator.hpp>
#include <com/sun/star/frame/XModel.hpp>
#include <com/sun/star/lang/XMultiServiceFactory.hpp>
#include <com/sun/star/presentation/XCustomPresentationSupplier.hpp>
#include <com/sun/star/container/XIndexContainer.hpp>
#include <com/sun/star/container/XNamed.hpp>
#include <com/sun/star/presentation/XPresentationSupplier.hpp>
#include <comphelper/diagnose_ex.hxx>
#include <comphelper/hash.hxx>
#include <vcl/embeddedfontsmanager.hxx>

#include <oox/export/utils.hxx>
#include <oox/export/ThemeExport.hxx>
#include <docmodel/theme/Theme.hxx>
#include <ModelTraverser.hxx>

#include "pptx-animations.hxx"
#include "../ppt/pptanimations.hxx"

#include <i18nlangtag/languagetag.hxx>
#include <svx/sdrmasterpagedescriptor.hxx>
#include <svx/svdpage.hxx>
#include <svx/unoapi.hxx>
#include <svx/svdogrp.hxx>
#include <svx/ColorSets.hxx>
#include <sdmod.hxx>
#include <sdpage.hxx>
#include <unomodel.hxx>

#include <vcl/svapp.hxx>
#include <vcl/settings.hxx>
#include <vcl/font/EOTConverter.hxx>

#include <com/sun/star/document/XDocumentPropertiesSupplier.hpp>
#include <com/sun/star/document/XStorageBasedDocument.hpp>
#include <utility>
#include <unordered_map>
#include <unordered_set>

#if OSL_DEBUG_LEVEL > 1
#include <com/sun/star/drawing/RectanglePoint.hpp>
#endif

using namespace sax_fastparser;
using namespace ::com::sun::star;
using namespace ::com::sun::star::animations;
using namespace ::com::sun::star::beans;
using namespace ::com::sun::star::container;
using namespace ::com::sun::star::drawing;
using namespace ::com::sun::star::geometry;
using namespace ::com::sun::star::presentation;
using namespace ::com::sun::star::office;
using namespace ::com::sun::star::text;
using namespace ::com::sun::star::uno;
using namespace ::com::sun::star::util;
using namespace ::ppt;
using ::com::sun::star::beans::XPropertySet;
using ::com::sun::star::beans::XPropertySetInfo;
using ::sax_fastparser::FSHelperPtr;
using namespace oox::drawingml;
using namespace oox::core;
using namespace oox;

namespace
{
rtl::Reference<FastAttributeList> presentationNamespaces(oox::core::XmlFilterBase& rFilter)
{
    rtl::Reference<FastAttributeList> pAttributes = FastSerializerHelper::createAttrList();
    pAttributes->add(FSNS(XML_xmlns, XML_a), OUStringToOString(rFilter.getNamespaceURL(OOX_NS(dml)), RTL_TEXTENCODING_UTF8));
    pAttributes->add(FSNS(XML_xmlns, XML_p), OUStringToOString(rFilter.getNamespaceURL(OOX_NS(ppt)), RTL_TEXTENCODING_UTF8));
    pAttributes->add(FSNS(XML_xmlns, XML_r), OUStringToOString(rFilter.getNamespaceURL(OOX_NS(officeRel)), RTL_TEXTENCODING_UTF8));
    pAttributes->add(FSNS(XML_xmlns, XML_p14), OUStringToOString(rFilter.getNamespaceURL(OOX_NS(p14)), RTL_TEXTENCODING_UTF8));
    pAttributes->add(FSNS(XML_xmlns, XML_p15), OUStringToOString(rFilter.getNamespaceURL(OOX_NS(p15)), RTL_TEXTENCODING_UTF8));
    pAttributes->add(FSNS(XML_xmlns, XML_mc), OUStringToOString(rFilter.getNamespaceURL(OOX_NS(mce)), RTL_TEXTENCODING_UTF8));
    return pAttributes;
}

rtl::Reference<FastAttributeList> presentationPrNamespaces(oox::core::XmlFilterBase& rFilter)
{
    rtl::Reference<FastAttributeList> pAttributes = FastSerializerHelper::createAttrList();
    pAttributes->add(FSNS(XML_xmlns, XML_a), OUStringToOString(rFilter.getNamespaceURL(OOX_NS(dml)), RTL_TEXTENCODING_UTF8));
    pAttributes->add(FSNS(XML_xmlns, XML_p), OUStringToOString(rFilter.getNamespaceURL(OOX_NS(ppt)), RTL_TEXTENCODING_UTF8));
    pAttributes->add(FSNS(XML_xmlns, XML_r), OUStringToOString(rFilter.getNamespaceURL(OOX_NS(officeRel)), RTL_TEXTENCODING_UTF8));
    return pAttributes;
}
};

#if OSL_DEBUG_LEVEL > 1
void dump_pset(Reference< XPropertySet > const& rXPropSet);
#endif

namespace oox::core
{

class PowerPointShapeExport : public ShapeExport
{
    PowerPointExport&   mrExport;
    PageType            mePageType;
    bool                mbMaster;
public:
    PowerPointShapeExport(FSHelperPtr pFS, ShapeHashMap* pShapeMap, PowerPointExport* pFB);
    void                SetMaster(bool bMaster);
    void                SetPageType(PageType ePageType);
    ShapeExport&        WriteNonVisualProperties(const Reference< XShape >& xShape) override;
    ShapeExport&        WriteTextShape(const Reference< XShape >& xShape) override;
    ShapeExport&        WriteUnknownShape(const Reference< XShape >& xShape) override;
    ShapeExport&        WritePlaceholderShape(const Reference< XShape >& xShape, PlaceholderType ePlaceholder);
    /** Writes a placeholder shape that references the placeholder on the master slide */
    ShapeExport&        WritePlaceholderReferenceShape(PlaceholderType ePlaceholder, sal_Int32 nReferencedPlaceholderIdx, PageType ePageType, const Reference<XPropertySet>& rXPagePropSet);
    ShapeExport&        WritePageShape(const Reference< XShape >& xShape, PageType ePageType, bool bPresObj);
    /** Writes textbody of a placeholder that references the placeholder on the master slide */
    ShapeExport&        WritePlaceholderReferenceTextBody(PlaceholderType ePlaceholder, PageType ePageType, const Reference<XPropertySet>& xPagePropSet);

    // helper parts
    bool WritePlaceholder(const Reference< XShape >& xShape, PlaceholderType ePlaceholder, bool bMaster);
};


namespace
{
void WriteSndAc(const FSHelperPtr& pFS, const OUString& sSoundRelId, const OUString& sSoundName)
{
        pFS->startElementNS(XML_p, XML_sndAc);
        pFS->startElementNS(XML_p, XML_stSnd);
        pFS->singleElementNS(XML_p, XML_snd, FSNS(XML_r, XML_embed),
                             sax_fastparser::UseIf(sSoundRelId, !sSoundRelId.isEmpty()), XML_name,
                             sax_fastparser::UseIf(sSoundName, !sSoundName.isEmpty()));
        pFS->endElement(FSNS(XML_p, XML_stSnd));
        pFS->endElement(FSNS(XML_p, XML_sndAc));
}

const char* getPlaceholderTypeName(PlaceholderType ePlaceholder)
{
    switch (ePlaceholder)
    {
        case SlideImage:
            return "sldImg";
        case Notes:
            return "body";
        case Header:
            return "hdr";
        case Footer:
            return "ftr";
        case SlideNumber:
            return "sldNum";
        case DateAndTime:
            return "dt";
        case Outliner:
            return "body";
        case Title:
            return "title";
        case Subtitle:
            return "subTitle";
        default:
            SAL_INFO("sd.eppt", "warning: unhandled placeholder type: " << ePlaceholder);
            return "";
    }
}
}
}

namespace {

struct PPTXLayoutInfo
{
    int nType;
    const char* sName;
    const char* sType;
};

}

// the function 'SlidePersist::getLayoutFromValueToken()'
// likely needs to be updated after any changes here
// unsupported PPTX layouts: txAndMedia, twoColTx, twoTxTwoObj,
//  twoObjAndObj, objTx, picTx, secHead, objOnly, objAndTwoObj,
//  mediaAndTx, dgm, cust
const PPTXLayoutInfo aLayoutInfo[] =
{
    { 0, "Title Slide", "title" },
    { 1, "Title and text", "tx" },
    { 2, "Title and chart", "chart" },
    { 3, "Title, text on left, text on right", "twoObj" },
    { 4, "Title, text on left and chart on right", "txAndChart" },
    { 20, "Blank Slide", "blank" },
    { 6, "Title, text on left, clip art on right", "txAndClipArt" },
    { 7, "Title, chart on left and text on right", "chartAndTx" },
    { 8, "Title and table", "tbl" },
    { 9, "Title, clipart on left, text on right", "clipArtAndTx" },
    { 10, "Title, text on left, object on right", "txAndObj" },
    {  1, "Title and object", "obj" },
    { 12, "Title, text on left, two objects on right", "txAndTwoObj" },
    { 13, "Title, object on left, text on right", "objAndTx" },
    { 14, "Title, object on top, text on bottom", "objOverTx" },
    { 15, "Title, two objects on left, text on right", "twoObjAndTx" },
    { 16, "Title, two objects on top, text on bottom", "twoObjOverTx" },
    { 17, "Title, text on top, object on bottom", "txOverObj" },
    { 18, "Title and four objects", "fourObj" },
    { 19, "Title Only", "titleOnly" },
    { 20, "Blank Slide", "blank" },
    { 20, "Blank Slide", "blank" },
    { 20, "Blank Slide", "blank" },
    { 20, "Blank Slide", "blank" },
    { 20, "Blank Slide", "blank" },
    { 20, "Blank Slide", "blank" },
    { 20, "Blank Slide", "blank" },
    { 27, "Vertical title on right, vertical text on top, chart on bottom", "vertTitleAndTxOverChart" },
    { 28, "Vertical title on right, vertical text on left", "vertTitleAndTx" },
    { 29, "Title and vertical text body", "vertTx" },
    { 30, "Title, clip art on left, vertical text on right", "clipArtAndVertTx" },
    { 20, "Blank Slide", "blank" },
    { 32, "Object only", "objOnly" },
    { 20, "Blank Slide", "blank" },
    { 20, "Blank Slide", "blank" }
};
static_assert(std::size(aLayoutInfo) == AUTOLAYOUT_END,
              "aLayoutInfo must have a corresponding item for each AUTOLAYOUT");

PowerPointShapeExport::PowerPointShapeExport(FSHelperPtr pFS, ShapeHashMap* pShapeMap,
        PowerPointExport* pFB)
    : ShapeExport(XML_p, std::move(pFS), pShapeMap, pFB)
    , mrExport(*pFB)
    , mePageType(UNDEFINED)
    , mbMaster(false)
{
}

void PowerPointShapeExport::SetMaster(bool bMaster)
{
    mbMaster = bMaster;
}

void PowerPointShapeExport::SetPageType(PageType ePageType)
{
    mePageType = ePageType;
}

ShapeExport& PowerPointShapeExport::WriteNonVisualProperties(const Reference< XShape >&)
{
    GetFS()->singleElementNS(XML_p, XML_nvPr);

    return *this;
}

ShapeExport& PowerPointShapeExport::WriteTextShape(const Reference< XShape >& xShape)
{
    OUString sShapeType = xShape->getShapeType();

    SAL_INFO("sd.eppt", "shape(text) : " << sShapeType.toUtf8());

    if (sShapeType == "com.sun.star.drawing.TextShape" || sShapeType == "com.sun.star.drawing.GraphicObjectShape")
    {
        ShapeExport::WriteTextShape(xShape);
    }
    else if (sShapeType == "com.sun.star.presentation.DateTimeShape")
    {
        if (!WritePlaceholder(xShape, DateAndTime, mbMaster))
            ShapeExport::WriteTextShape(xShape);
    }
    else if (sShapeType == "com.sun.star.presentation.FooterShape")
    {
        if (!WritePlaceholder(xShape, Footer, mbMaster))
            ShapeExport::WriteTextShape(xShape);
    }
    else if (sShapeType == "com.sun.star.presentation.HeaderShape")
    {
        if (!WritePlaceholder(xShape, Header, mbMaster))
            ShapeExport::WriteTextShape(xShape);
    }
    else if (sShapeType == "com.sun.star.presentation.NotesShape")
    {
        if (mePageType == NOTICE && mrExport.GetPresObj())
            WritePlaceholderShape(xShape, Notes);
        else
            ShapeExport::WriteTextShape(xShape);
    }
    else if (sShapeType == "com.sun.star.presentation.OutlinerShape")
    {
        if (!WritePlaceholder(xShape, Outliner, mbMaster))
            ShapeExport::WriteTextShape(xShape);
    }
    else if (sShapeType == "com.sun.star.presentation.SlideNumberShape")
    {
        if (!WritePlaceholder(xShape, SlideNumber, mbMaster))
            ShapeExport::WriteTextShape(xShape);
    }
    else if (sShapeType == "com.sun.star.presentation.TitleTextShape")
    {
        if (!WritePlaceholder(xShape, Title, mbMaster))
            ShapeExport::WriteTextShape(xShape);
    }
    else if (sShapeType == "com.sun.star.presentation.SubtitleShape")
    {
        // TODO: handle subtitle shape: see tdf#112557 workaround
        // MSO does not like subtitles on master slides
        if (mePageType != MASTER)
        {
            if (!WritePlaceholder(xShape, Subtitle, mbMaster))
                ShapeExport::WriteTextShape(xShape);
        }
    }
    else
        SAL_WARN("sd.eppt", "PowerPointShapeExport::WriteTextShape: shape of type '" << sShapeType << "' is ignored");

    return *this;
}

ShapeExport& PowerPointShapeExport::WriteUnknownShape(const Reference< XShape >& xShape)
{
    OUString sShapeType = xShape->getShapeType();

    SAL_INFO("sd.eppt", "shape(unknown): " << sShapeType.toUtf8());

    if (sShapeType == "com.sun.star.presentation.PageShape")
    {
        WritePageShape(xShape, mePageType, mrExport.GetPresObj());
    }
    else
        SAL_WARN("sd.eppt", "unknown shape not handled: " << sShapeType.toUtf8());

    return *this;
}

PowerPointExport::PowerPointExport(const Reference< XComponentContext >& rContext, const uno::Sequence<uno::Any>& rArguments)
    : XmlFilterBase(rContext)
    , mpAuthorIDs( new SvtSecurityMapPersonalInfo )
    , mnLayoutFileIdMax(1)
    , mnSlideIdMax(1 << 8)
    , mnSlideMasterIdMax(1U << 31)
    , mnAnimationNodeIdMax(1)
    , mnThemeIdMax(0)
    , mnDiagramId(1)
    , mbCreateNotes(false)
    , mnPlaceholderIndexMax(1)
{
    comphelper::SequenceAsHashMap aArgumentsMap(rArguments);
    mbPptm = aArgumentsMap.getUnpackedValueOrDefault(u"IsPPTM"_ustr, false);
    mbExportTemplate = aArgumentsMap.getUnpackedValueOrDefault(u"IsTemplate"_ustr, false);
}

PowerPointExport::~PowerPointExport()
{
}

void PowerPointExport::writeDocumentProperties()
{
    uno::Reference<document::XDocumentProperties> xDocProps = mXModel->getDocumentProperties();

    if (xDocProps.is())
    {
        bool bSecurityOptOpenReadOnly = false;
        uno::Reference< beans::XPropertySet > xSettings(mXModel->createInstance(u"com.sun.star.document.Settings"_ustr), uno::UNO_QUERY);
        try
        {
            xSettings->getPropertyValue(u"LoadReadonly"_ustr) >>= bSecurityOptOpenReadOnly;

            xSettings->getPropertyValue(u"EmbedFonts"_ustr) >>= mbEmbedFonts;
            xSettings->getPropertyValue(u"EmbedOnlyUsedFonts"_ustr) >>= mbEmbedUsedOnly;
            xSettings->getPropertyValue(u"EmbedLatinScriptFonts"_ustr)  >>= mbEmbedLatinScript;
            xSettings->getPropertyValue(u"EmbedAsianScriptFonts"_ustr)  >>= mbEmbedAsianScript;
            xSettings->getPropertyValue(u"EmbedComplexScriptFonts"_ustr) >>= mbEmbedComplexScript;
        }
        catch( Exception& )
        {
        }
        exportDocumentProperties(xDocProps, bSecurityOptOpenReadOnly);
    }

    exportCustomFragments();
}

bool PowerPointExport::importDocument() noexcept
{
    return false;
}

bool PowerPointExport::exportDocument()
{
    drawingml::DrawingML::ResetMlCounters();
    auto& rGraphicExportCache = drawingml::GraphicExportCache::get();

    rGraphicExportCache.push();

    maShapeMap.clear();

    auto pDoc = dynamic_cast<SdXImpressDocument*>(getModel().get());
    assert(pDoc);
    mXModel = pDoc;

    //write document properties
    writeDocumentProperties();

    addRelation(oox::getRelationship(Relationship::OFFICEDOCUMENT), u"ppt/presentation.xml");

    OUString aMediaType;
    if (mbPptm)
    {
        if (mbExportTemplate)
        {
            aMediaType = "application/vnd.ms-powerpoint.template.macroEnabled.main+xml";
        }
        else
        {
            aMediaType = "application/vnd.ms-powerpoint.presentation.macroEnabled.main+xml";
        }
    }
    else
    {
        if (mbExportTemplate)
        {
            aMediaType = "application/vnd.openxmlformats-officedocument.presentationml.template.main+xml";
        }
        else
        {
            aMediaType = "application/vnd.openxmlformats-officedocument.presentationml.presentation.main+xml";
        }
    }

    mPresentationFS = openFragmentStreamWithSerializer(u"ppt/presentation.xml"_ustr, aMediaType);

    addRelation(mPresentationFS->getOutputStream(),
                oox::getRelationship(Relationship::THEME),
                u"theme/theme1.xml");

    auto pAttributes = presentationNamespaces(*this);
    if (mbEmbedFonts)
        pAttributes->add(XML_embedTrueTypeFonts, "1");
    mPresentationFS->startElementNS(XML_p, XML_presentation, pAttributes);

    mXStatusIndicator = getStatusIndicator();

    std::vector< PropertyValue > aProperties;
    PropertyValue aProperty;
    aProperty.Name = "BaseURI";
    aProperty.Value <<= getFileUrl();
    aProperties.push_back(aProperty);

    exportPPT(aProperties);

    // clamp to minimum values supported by PPTX
    sal_Int64 nDestPageWidth = std::max(sal_Int64(914400), PPTtoEMU(maDestPageSize.Width));
    sal_Int64 nDestPageHeight = std::max(sal_Int64(914400), PPTtoEMU(maDestPageSize.Height));
    mPresentationFS->singleElementNS(XML_p, XML_sldSz,
                                     XML_cx, OString::number(nDestPageWidth),
                                     XML_cy, OString::number(nDestPageHeight));
    // for some reason if added before slides list it will not load the slides (alas with error reports) in mso
    mPresentationFS->singleElementNS(XML_p, XML_notesSz,
                                     XML_cx, OString::number(PPTtoEMU(maNotesPageSize.Width)),
                                     XML_cy, OString::number(PPTtoEMU(maNotesPageSize.Height)));
    WriteEmbeddedFontList();

    WriteCustomSlideShow();

    WritePresentationProps();

    WriteAuthors();

    WriteVBA();

    WriteModifyVerifier();

    mPresentationFS->endElementNS(XML_p, XML_presentation);
    mPresentationFS->endDocument();
    mPresentationFS.reset();
    // Free all FSHelperPtr, to flush data before committing storage
    for (auto& serializer : mpSlidesFSArray)
    {
        if (!serializer)
            continue;
        serializer->endDocument();
    }
    mpSlidesFSArray.clear();

    commitStorage();

    rGraphicExportCache.pop();

    maShapeMap.clear();
    maAuthors.clear();
    maRelId.clear();

    return true;
}

::oox::ole::VbaProject* PowerPointExport::implCreateVbaProject() const
{
    return new ::oox::ole::VbaProject(getComponentContext(), getModel(), u"Impress");
}

namespace
{
struct EmbeddedFont
{
    OUString sFamilyName;

    OString aRegularRelID;
    OString aBoldRelID;
    OString aItalicRelID;
    OString aBoldItalicRelID;
};
} // end anonymous namespace

namespace
{

/** Collects font names of fonts used in the document
 *
 * Uses the model traverser to traverse the document model.
 * Can be parameterized to filter latin, asian or complex scripts.
 */
class FontNameCollector : public sd::ModelTraverseHandler
{
private:
    std::unordered_set<OUString>& mrUsedFontNames;
    bool mbEmbedLatinScript;
    bool mbEmbedAsianScript;
    bool mbEmbedComplexScript;

    static const SvxFontItem* getFontItem(const editeng::Section& rSection, sal_uInt16 eFontWhich)
    {
        auto iterator = std::find_if(rSection.maAttributes.begin(), rSection.maAttributes.end(), [eFontWhich] (const SfxPoolItem* pPoolItem) {
            return pPoolItem->Which() == eFontWhich;
        });

        if (iterator != rSection.maAttributes.end())
            return static_cast<const SvxFontItem*>(*iterator);
        return nullptr;
    }

    void addFontItem(const SvxFontItem* pItem)
    {
        OUString aFontName = pItem->GetFamilyName();
        mrUsedFontNames.insert(aFontName);
    }

    void traverseEditEng(SdrTextObj* pTextObject)
    {
        OutlinerParaObject* pOutlinerParagraphObject = pTextObject->GetOutlinerParaObject();
        if (!pOutlinerParagraphObject)
            return;

        const EditTextObject& rEditText = pOutlinerParagraphObject->GetTextObject();
        std::vector<editeng::Section> aSections;
        rEditText.GetAllSections(aSections);

        {
            SfxStyleSheet* pStyleSheet = pTextObject->getSdrPageFromSdrObject()->GetTextStyleSheetForObject(pTextObject);
            if (pStyleSheet)
            {
                const SfxItemSet& rItemSet = pStyleSheet->GetItemSet();
                if (const SvxFontItem* pItem = rItemSet.GetItemIfSet(EE_CHAR_FONTINFO, false))
                {
                    addFontItem(pItem);
                }
                if (const SvxFontItem* pItem = rItemSet.GetItemIfSet(EE_CHAR_FONTINFO_CJK, false))
                {
                    addFontItem(pItem);
                }
                if (const SvxFontItem* pItem = rItemSet.GetItemIfSet(EE_CHAR_FONTINFO_CTL, false))
                {
                    addFontItem(pItem);
                }
            }
        }

        for (editeng::Section const& rSection : aSections)
        {
            if (SvxFontItem const* pFontItem = getFontItem(rSection, EE_CHAR_FONTINFO); pFontItem && mbEmbedLatinScript)
            {
                addFontItem(pFontItem);
            }
            if (SvxFontItem const * pFontItem = getFontItem(rSection, EE_CHAR_FONTINFO_CJK); pFontItem && mbEmbedAsianScript)
            {
                addFontItem(pFontItem);
            }
            if (SvxFontItem const* pFontItem = getFontItem(rSection, EE_CHAR_FONTINFO_CTL); pFontItem && mbEmbedComplexScript)
            {
                addFontItem(pFontItem);
            }
        }
    }

public:
    FontNameCollector(std::unordered_set<OUString>& rUsedFontNames, bool bEmbedLatinScript, bool bEmbedAsianScript, bool bEmbedComplexScript)
        : mrUsedFontNames(rUsedFontNames)
        , mbEmbedLatinScript(bEmbedLatinScript)
        , mbEmbedAsianScript(bEmbedAsianScript)
        , mbEmbedComplexScript(bEmbedComplexScript)
    {}

protected:
    void handleSdrObject(SdrObject* pObject) override
    {
        SdrTextObj* pTextShape = DynCastSdrTextObj(pObject);
        if (pTextShape)
        {
            auto& rItemSet = pTextShape->GetMergedItemSet();

            if (SvxFontItem const* pFontItem = rItemSet.GetItemIfSet(EE_CHAR_FONTINFO, true); pFontItem && mbEmbedLatinScript)
            {
                addFontItem(pFontItem);
            }
            if (SvxFontItem const* pFontItem = rItemSet.GetItemIfSet(EE_CHAR_FONTINFO_CJK, true); pFontItem && mbEmbedAsianScript)
            {
                addFontItem(pFontItem);
            }
            if (SvxFontItem const* pFontItem = rItemSet.GetItemIfSet(EE_CHAR_FONTINFO_CTL, true); pFontItem && mbEmbedComplexScript)
            {
                addFontItem(pFontItem);
            }

            traverseEditEng(pTextShape);
        }
    }
};

} // end anonymous namespace


std::unordered_set<OUString> PowerPointExport::getUsedFontList()
{
    std::unordered_set<OUString> aReturnSet;

    SdDrawDocument* pDocument = mXModel->GetDoc();
    if (!pDocument)
        return aReturnSet;

    auto pCollector = std::make_shared<FontNameCollector>(aReturnSet, mbEmbedLatinScript, mbEmbedAsianScript, mbEmbedComplexScript);
    sd::ModelTraverser aModelTraverser(pDocument, { .mbMasterPages = true });
    aModelTraverser.addNodeHandler(pCollector);
    aModelTraverser.traverse();

    return aReturnSet;
}

// Writers the list of all embedded fonts and reference to the fonts
void PowerPointExport::WriteEmbeddedFontList()
{
    if (!mbEmbedFonts)
        return;

    std::unordered_set<OUString> aUsedFonts;
    if (mbEmbedUsedOnly)
        aUsedFonts = getUsedFontList();

    int nextFontId = 1;

    std::unordered_set<OUString> aFontFamilyNameSet;
    std::unordered_map<std::string, OString> aFontDeduplicationMap;

    std::vector<EmbeddedFont> aEmbeddedFontInfo;

    uno::Sequence<uno::Any> aAnySeq;
    if (!(mXModel->getPropertyValue("Fonts") >>= aAnySeq))
        return;

    if (aAnySeq.getLength() % 5 != 0)
        return;

    int nLen = aAnySeq.getLength() / 5;
    int nSeqIndex = 0;

    for (int i = 0; i < nLen; i++)
    {
        OUString sFamilyName;
        OUString sStyleName;
        sal_uInt16 eFamily = FAMILY_DONTKNOW;
        sal_uInt16 ePitch = PITCH_DONTKNOW;
        sal_uInt16 eCharSet = RTL_TEXTENCODING_DONTKNOW;

        aAnySeq[nSeqIndex++] >>= sFamilyName;
        aAnySeq[nSeqIndex++] >>= sStyleName;
        aAnySeq[nSeqIndex++] >>= eFamily;
        aAnySeq[nSeqIndex++] >>= ePitch;
        aAnySeq[nSeqIndex++] >>= eCharSet;

        if (EmbeddedFontsManager::isCommonFont(sFamilyName))
            continue;

        if (mbEmbedUsedOnly && !aUsedFonts.contains(sFamilyName))
            continue;

        if (aFontFamilyNameSet.contains(sFamilyName))
            continue;

        static std::vector<std::pair<FontItalic, FontWeight>> aFontVariantCombinations =
        {
            { ITALIC_NONE, WEIGHT_NORMAL },
            { ITALIC_NONE, WEIGHT_BOLD },
            { ITALIC_NORMAL, WEIGHT_NORMAL },
            { ITALIC_NORMAL, WEIGHT_BOLD }
        };

        EmbeddedFont aInfo;
        aInfo.sFamilyName = sFamilyName;
        SAL_INFO("sd.eppt", "Embedding font: " << sFamilyName);

        for (auto [eItalic, eWeight] : aFontVariantCombinations)
        {
            OUString sFontUrl = EmbeddedFontsManager::fontFileUrl(
                                    sFamilyName, FontFamily(eFamily), eItalic, eWeight, FontPitch(ePitch),
                                    EmbeddedFontsManager::FontRights::ViewingAllowed);

            if (sFontUrl.isEmpty())
                continue;

            osl::File aFile(sFontUrl);
            if (aFile.open(osl_File_OpenFlag_Read ) != osl::File::E_None)
                continue;

            std::vector<sal_uInt8> rFontData;

            std::array<sal_uInt8, 4096> buffer;
            sal_uInt64 readSize;

            comphelper::Hash aHashCalc(comphelper::HashType::SHA256);

            OString uRelID;

            // Read file
            for(;;)
            {
                sal_Bool eof;

                if (aFile.isEndOfFile(&eof) != osl::File::E_None)
                {
                    SAL_WARN("sd.eppt", "Error reading font file " << sFontUrl);
                    break;
                }
                if (eof)
                    break;

                if (aFile.read(buffer.data(), 4096, readSize) != osl::File::E_None)
                {
                    SAL_WARN("sd.eppt", "Error reading font file " << sFontUrl);
                    break;
                }

                if (readSize == 0)
                    break;

                rFontData.insert(rFontData.end(), buffer.data(), buffer.data() + readSize);
                aHashCalc.update(buffer.data(), readSize);
            }

            std::string aHash = comphelper::hashToString(aHashCalc.finalize());
            auto iterator = aFontDeduplicationMap.find(aHash);
            if (iterator == aFontDeduplicationMap.end())
            {
                std::vector<sal_uInt8> rEOT;

                font::FontDataContainer aContainer(rFontData);
                font::EOTConverter aConverter(aContainer);
                if (!aConverter.convert(rEOT))
                    continue;

                OUString sFontType = u"Regular"_ustr;
                if (eItalic == ITALIC_NONE && eWeight == WEIGHT_BOLD)
                    sFontType = u"Bold"_ustr;
                else if (eItalic == ITALIC_NORMAL && eWeight == WEIGHT_NORMAL)
                    sFontType = u"Italic"_ustr;
                else if (eItalic == ITALIC_NORMAL && eWeight == WEIGHT_BOLD)
                    sFontType = u"BoldItalic"_ustr;

                OUString sFontFileName = "Font_" + OUString::number(nextFontId) + "_" +
                    sFamilyName.replaceAll(" ", "_") + "_" + sFontType +".fntdata";

                OUString sArchivePath = "ppt/fonts/" + sFontFileName;

                uno::Reference<css::io::XOutputStream> xOutStream = openFragmentStream(sArchivePath, u"application/x-fontdata"_ustr);
                xOutStream->writeBytes(uno::Sequence<sal_Int8>(reinterpret_cast<const sal_Int8*>(rEOT.data()), rEOT.size()));
                xOutStream->closeOutput();

                OUString sRelID = addRelation(mPresentationFS->getOutputStream(),
                                              oox::getRelationship(Relationship::FONT),
                                              Concat2View("fonts/" + sFontFileName));

                ++nextFontId;

                uRelID = OUStringToOString(sRelID, RTL_TEXTENCODING_UTF8);
                aFontDeduplicationMap.emplace(aHash, uRelID);
            }
            else
            {
                uRelID = iterator->second;
            }

            if (eItalic == ITALIC_NONE && eWeight == WEIGHT_NORMAL)
                aInfo.aRegularRelID = uRelID;
            else if (eItalic == ITALIC_NONE && eWeight == WEIGHT_BOLD)
                aInfo.aBoldRelID = uRelID;
            else if (eItalic == ITALIC_NORMAL && eWeight == WEIGHT_NORMAL)
                aInfo.aItalicRelID = uRelID;
            else if (eItalic == ITALIC_NORMAL && eWeight == WEIGHT_BOLD)
                aInfo.aBoldItalicRelID = uRelID;
        }

        aEmbeddedFontInfo.push_back(aInfo);
        aFontFamilyNameSet.insert(sFamilyName);
    }

    // if there are fonts to embed and font embedding enabled
    mPresentationFS->startElementNS(XML_p, XML_embeddedFontLst);
    for (auto const& rInfo : aEmbeddedFontInfo)
    {
        mPresentationFS->startElementNS(XML_p, XML_embeddedFont);

        mPresentationFS->singleElementNS(XML_p, XML_font,
            //XML_charset, OUString::number(0),
            XML_typeface, rInfo.sFamilyName);

        if (!rInfo.aRegularRelID.isEmpty())
            mPresentationFS->singleElementNS(XML_p, XML_regular, FSNS(XML_r, XML_id), rInfo.aRegularRelID);

        if (!rInfo.aBoldRelID.isEmpty())
            mPresentationFS->singleElementNS(XML_p, XML_bold, FSNS(XML_r, XML_id), rInfo.aBoldRelID);

        if (!rInfo.aItalicRelID.isEmpty())
            mPresentationFS->singleElementNS(XML_p, XML_italic, FSNS(XML_r, XML_id), rInfo.aItalicRelID);

        if (!rInfo.aBoldItalicRelID.isEmpty())
            mPresentationFS->singleElementNS(XML_p, XML_boldItalic, FSNS(XML_r, XML_id), rInfo.aBoldItalicRelID);

        mPresentationFS->endElementNS(XML_p, XML_embeddedFont);
    }
    mPresentationFS->endElementNS(XML_p, XML_embeddedFontLst);
}

void PowerPointExport::WriteCustomSlideShow()
{
    if (!mXModel->getCustomPresentations()->hasElements())
        return;

    mPresentationFS->startElementNS(XML_p, XML_custShowLst);

    Reference<XDrawPages> xDrawPages(mXModel->getDrawPages(), uno::UNO_SET_THROW);
    Reference<XNameContainer> aXNameCont(mXModel->getCustomPresentations());
    const Sequence<OUString> aNameSeq(aXNameCont->getElementNames());

    OUString sRelId;
    sal_uInt32 nCustomShowIndex = 0;
    sal_Int32 nSlideCount = xDrawPages->getCount();

    for (OUString const& customShowName : aNameSeq)
    {
        mPresentationFS->startElementNS(XML_p, XML_custShow, XML_name, customShowName, XML_id,
                                        OUString::number(nCustomShowIndex++));

        mAny = aXNameCont->getByName(customShowName);
        Reference<XIndexContainer> aXIContainer;
        if (mAny >>= aXIContainer)
        {
            mPresentationFS->startElementNS(XML_p, XML_sldLst);

            sal_Int32 nCustomShowSlideCount = aXIContainer->getCount();
            for (sal_Int32 i = 0; i < nCustomShowSlideCount; ++i)
            {
                Reference<XDrawPage> aXCustomShowDrawPage;
                aXIContainer->getByIndex(i) >>= aXCustomShowDrawPage;
                Reference<XNamed> aXName(aXCustomShowDrawPage, UNO_QUERY_THROW);
                OUString sCustomShowSlideName = aXName->getName();

                for (sal_Int32 j = 0; j < nSlideCount; ++j)
                {
                    Reference<XDrawPage> xDrawPage;
                    xDrawPages->getByIndex(j) >>= xDrawPage;
                    Reference<XNamed> xNamed(xDrawPage, UNO_QUERY_THROW);
                    OUString sSlideName = xNamed->getName();

                    if (sCustomShowSlideName == sSlideName)
                    {
                        sRelId = maRelId[j];
                        break;
                    }
                }
                mPresentationFS->singleElementNS(XML_p, XML_sld, FSNS(XML_r, XML_id), sRelId);
            }
            mPresentationFS->endElementNS(XML_p, XML_sldLst);
        }
        mPresentationFS->endElementNS(XML_p, XML_custShow);
    }
    mPresentationFS->endElementNS(XML_p, XML_custShowLst);
}

void PowerPointExport::ImplWriteBackground(const FSHelperPtr& pFS, const Reference< XPropertySet >& rXPropSet)
{
    FillStyle aFillStyle(FillStyle_NONE);
    if (ImplGetPropertyValue(rXPropSet, u"FillStyle"_ustr))
        mAny >>= aFillStyle;

    if (aFillStyle == FillStyle_NONE ||
            aFillStyle == FillStyle_HATCH)
        return;

    // For BITMAP fill style, check if graphic is valid before writing bgPr
    if (aFillStyle == FillStyle_BITMAP
        && (!ImplGetPropertyValue(rXPropSet, u"FillBitmap"_ustr)
            || !mAny.has<uno::Reference<graphic::XGraphic>>()))
        return;

    pFS->startElementNS(XML_p, XML_bg);
    pFS->startElementNS(XML_p, XML_bgPr);

    PowerPointShapeExport aDML(pFS, &maShapeMap, this);
    aDML.SetBackgroundDark(mbIsBackgroundDark);
    aDML.WriteFill(rXPropSet, maPageSize);

    pFS->endElementNS(XML_p, XML_bgPr);
    pFS->endElementNS(XML_p, XML_bg);
}

#define MAIN_GROUP \
     "<p:nvGrpSpPr>\
        <p:cNvPr id=\"1\" name=\"\"/>\
        <p:cNvGrpSpPr/>\
        <p:nvPr/>\
      </p:nvGrpSpPr>\
      <p:grpSpPr>\
        <a:xfrm>\
          <a:off x=\"0\" y=\"0\"/>\
          <a:ext cx=\"0\" cy=\"0\"/>\
          <a:chOff x=\"0\" y=\"0\"/>\
          <a:chExt cx=\"0\" cy=\"0\"/>\
        </a:xfrm>\
      </p:grpSpPr>"

const char* PowerPointExport::GetSideDirection(sal_uInt8 nDirection)
{
    const char* pDirection = nullptr;

    switch (nDirection)
    {
    case 0:
        pDirection = "l";
        break;
    case 1:
        pDirection = "u";
        break;
    case 2:
        pDirection = "r";
        break;
    case 3:
        pDirection = "d";
        break;
    }

    return pDirection;
}

const char* PowerPointExport::GetCornerDirection(sal_uInt8 nDirection)
{
    const char* pDirection = nullptr;

    switch (nDirection)
    {
    case 4:
        pDirection = "lu";
        break;
    case 5:
        pDirection = "ru";
        break;
    case 6:
        pDirection = "ld";
        break;
    case 7:
        pDirection = "rd";
        break;
    }

    return pDirection;
}

const char* PowerPointExport::Get8Direction(sal_uInt8 nDirection)
{
    const char* pDirection = GetSideDirection(nDirection);

    if (!pDirection)
        pDirection = GetCornerDirection(nDirection);

    return pDirection;
}

void PowerPointExport::WriteTransition(const FSHelperPtr& pFS)
{
    FadeEffect eFadeEffect = FadeEffect_NONE;
    if (ImplGetPropertyValue(mXPagePropSet, u"Effect"_ustr))
        mAny >>= eFadeEffect;

    sal_Int16 nTransitionType = 0, nTransitionSubtype = 0;
    sal_Int8 nPPTTransitionType = 0;
    sal_uInt8 nDirection = 0;

    OUString sSoundUrl;
    OUString sSoundRelId;
    OUString sSoundName;

    if (ImplGetPropertyValue(mXPagePropSet, u"TransitionType"_ustr) && (mAny >>= nTransitionType) &&
            ImplGetPropertyValue(mXPagePropSet, u"TransitionSubtype"_ustr) && (mAny >>= nTransitionSubtype))
    {
        // FADEOVERCOLOR with black -> fade, with white -> flash
        sal_Int32 nTransitionFadeColor = 0;
        if( ImplGetPropertyValue(mXPagePropSet, u"TransitionFadeColor"_ustr))
            mAny >>= nTransitionFadeColor;
        nPPTTransitionType = GetTransition(nTransitionType, nTransitionSubtype, eFadeEffect, nTransitionFadeColor, nDirection);
    }

    if (!nPPTTransitionType && eFadeEffect != FadeEffect_NONE)
        nPPTTransitionType = GetTransition(eFadeEffect, nDirection);

    if (ImplGetPropertyValue(mXPagePropSet, u"Sound"_ustr) && (mAny >>= sSoundUrl))
        embedEffectAudio(pFS, sSoundUrl, sSoundRelId, sSoundName);

    bool bOOXmlSpecificTransition = false;

    sal_Int32 nTransition = 0;
    const char* pDirection = nullptr;
    const char* pOrientation = nullptr;
    const char* pThruBlk = nullptr;
    const char* pSpokes = nullptr;

    char pSpokesTmp[2] = "0";

    // p14
    sal_Int32 nTransition14 = 0;
    const char* pDirection14 = nullptr;
    const char* pInverted = nullptr;
    const char* pPattern = nullptr; // diamond or hexagon

    //p15
    const char* pPresetTransition = nullptr;

    if (!nPPTTransitionType)
    {
        switch (nTransitionType)
        {
        case animations::TransitionType::BARWIPE:
        {
            if (nTransitionSubtype == animations::TransitionSubType::FADEOVERCOLOR)
            {
                nTransition = XML_cut;
                pThruBlk = "true";
                bOOXmlSpecificTransition = true;
            }
            break;
        }
        case animations::TransitionType::MISCSHAPEWIPE:
        {
            switch (nTransitionSubtype)
            {
            case animations::TransitionSubType::TOPTOBOTTOM: // Turn around
                nTransition = XML_fade;
                nTransition14 = XML_flip;
                pDirection14 = "l";
                bOOXmlSpecificTransition = true;
                break;
            case animations::TransitionSubType::BOTTOMRIGHT: // Rochade
                nTransition = XML_fade;
                nTransition14 = XML_switch;
                pDirection14 = "r";
                bOOXmlSpecificTransition = true;
                break;
            case animations::TransitionSubType::VERTICAL: // Vortex
                nTransition = XML_fade;
                nTransition14 = XML_vortex;
                bOOXmlSpecificTransition = true;
                break;
            case animations::TransitionSubType::HORIZONTAL: // Ripple
                nTransition = XML_fade;
                nTransition14 = XML_ripple;
                bOOXmlSpecificTransition = true;
                break;
            case animations::TransitionSubType::LEFTTORIGHT: // Fall
                nTransition = XML_fade;
                pPresetTransition = "fallOver";
                bOOXmlSpecificTransition = true;
                break;
            case animations::TransitionSubType::CORNERSIN: // Inside turning cube
                pInverted = "true";
                [[fallthrough]];
            case animations::TransitionSubType::CORNERSOUT: // Outside turning cube
                nTransition = XML_fade;
                nTransition14 = XML_prism;
                bOOXmlSpecificTransition = true;
                break;
            case animations::TransitionSubType::DIAMOND: // Glitter
                nTransition = XML_fade;
                nTransition14 = XML_glitter;
                pDirection14 = "l";
                pPattern = "hexagon";
                bOOXmlSpecificTransition = true;
                break;
            case animations::TransitionSubType::HEART: // Honeycomb
                nTransition = XML_fade;
                nTransition14 = XML_honeycomb;
                bOOXmlSpecificTransition = true;
                break;
            }
            break;
        }
        }
    }

    AnimationSpeed animationSpeed = AnimationSpeed_MEDIUM;
    const char* speed = nullptr;
    sal_Int32 advanceTiming = -1;
    sal_Int32 changeType = 0;

    sal_Int32 nTransitionDuration = -1;
    bool isTransitionDurationSet = false;

    // try to use TransitionDuration instead of old Speed property
    if (ImplGetPropertyValue(mXPagePropSet, u"TransitionDuration"_ustr))
    {
        double fTransitionDuration = -1.0;
        mAny >>= fTransitionDuration;
        if (fTransitionDuration >= 0)
        {
            nTransitionDuration = fTransitionDuration * 1000.0;

            // override values because in MS formats meaning of fast/medium/slow is different
            if (nTransitionDuration <= 500)
            {
                // fast is default
                speed = nullptr;
            }
            else if (nTransitionDuration >= 1000)
            {
                speed = "slow";
            }
            else
            {
                speed = "med";
            }

            bool isStandardValue = nTransitionDuration == 500
                || nTransitionDuration == 750
                || nTransitionDuration == 1000;

            if(!isStandardValue)
                isTransitionDurationSet = true;
        }
    }
    else if (ImplGetPropertyValue(mXPagePropSet, u"Speed"_ustr))
    {
        mAny >>= animationSpeed;

        switch (animationSpeed)
        {
        default:
        case AnimationSpeed_MEDIUM:
            speed = "med";
            break;
        case AnimationSpeed_SLOW:
            speed = "slow";
            break;
        case AnimationSpeed_FAST:
            break;
        }
    }

    // check if we resolved what transition to export or time is set
    if (!nPPTTransitionType && !bOOXmlSpecificTransition && !isTransitionDurationSet)
        return;

    if (ImplGetPropertyValue(mXPagePropSet, u"Change"_ustr))
        mAny >>= changeType;

    // 1 means automatic, 2 half automatic - not sure what it means - at least I don't see it in UI
    if (changeType == 1 && ImplGetPropertyValue(mXPagePropSet, u"Duration"_ustr))
        mAny >>= advanceTiming;

    if (!bOOXmlSpecificTransition)
    {
        switch (nPPTTransitionType)
        {
        case PPT_TRANSITION_TYPE_BLINDS:
            nTransition = XML_blinds;
            pDirection = (nDirection == 0) ? "vert" : "horz";
            break;
        case PPT_TRANSITION_TYPE_CHECKER:
            nTransition = XML_checker;
            pDirection = (nDirection == 1) ? "vert" : "horz";
            break;
        case PPT_TRANSITION_TYPE_CIRCLE:
            nTransition = XML_circle;
            break;
        case PPT_TRANSITION_TYPE_COMB:
            nTransition = XML_comb;
            pDirection = (nDirection == 1) ? "vert" : "horz";
            break;
        case PPT_TRANSITION_TYPE_COVER:
            nTransition = XML_cover;
            pDirection = Get8Direction(nDirection);
            break;
        case PPT_TRANSITION_TYPE_DIAMOND:
            nTransition = XML_diamond;
            break;
        case PPT_TRANSITION_TYPE_DISSOLVE:
            nTransition = XML_dissolve;
            break;
        case PPT_TRANSITION_TYPE_FADE:
            nTransition = XML_fade;
            pThruBlk = "true";
            break;
        case PPT_TRANSITION_TYPE_SMOOTHFADE:
            nTransition = XML_fade;
            break;
        case PPT_TRANSITION_TYPE_NEWSFLASH:
            nTransition = XML_newsflash;
            break;
        case PPT_TRANSITION_TYPE_PLUS:
            nTransition = XML_plus;
            break;
        case PPT_TRANSITION_TYPE_PULL:
            nTransition = XML_pull;
            pDirection = Get8Direction(nDirection);
            break;
        case PPT_TRANSITION_TYPE_PUSH:
            nTransition = XML_push;
            pDirection = GetSideDirection(nDirection);
            break;
        case PPT_TRANSITION_TYPE_RANDOM:
            nTransition = XML_random;
            break;
        case PPT_TRANSITION_TYPE_RANDOM_BARS:
            nTransition = XML_randomBar;
            pDirection = (nDirection == 1) ? "vert" : "horz";
            break;
        case PPT_TRANSITION_TYPE_SPLIT:
            nTransition = XML_split;
            pDirection = (nDirection & 1) ? "in" : "out";
            pOrientation = (nDirection < 2) ? "horz" : "vert";
            break;
        case PPT_TRANSITION_TYPE_STRIPS:
            nTransition = XML_strips;
            pDirection = GetCornerDirection(nDirection);
            break;
        case PPT_TRANSITION_TYPE_WEDGE:
            nTransition = XML_wedge;
            break;
        case PPT_TRANSITION_TYPE_WHEEL:
            nTransition = XML_wheel;
            if (nDirection != 4 && nDirection <= 9)
            {
                pSpokesTmp[0] = '0' + nDirection;
                pSpokes = pSpokesTmp;
            }
            break;
        case PPT_TRANSITION_TYPE_WIPE:
            nTransition = XML_wipe;
            pDirection = GetSideDirection(nDirection);
            break;
        case PPT_TRANSITION_TYPE_ZOOM:
            nTransition = XML_zoom;
            pDirection = (nDirection == 1) ? "in" : "out";
            break;
        case PPT_TRANSITION_TYPE_FLASH:
            nTransition14 = XML_flash;
            nTransition = XML_fade;
            bOOXmlSpecificTransition = true;
            break;
        // coverity[dead_error_line] - following conditions exist to avoid compiler warning
        case PPT_TRANSITION_TYPE_NONE:
        default:
            nTransition = 0;
            break;
        }
    }

    bool isAdvanceTimingSet = advanceTiming != -1;
    if (nTransition14 || pPresetTransition || isTransitionDurationSet)
    {
        const char* pRequiresNS = (nTransition14 || isTransitionDurationSet) ? "p14" : "p15";

        pFS->startElement(FSNS(XML_mc, XML_AlternateContent));
        pFS->startElement(FSNS(XML_mc, XML_Choice), XML_Requires, pRequiresNS);

        if(isTransitionDurationSet && isAdvanceTimingSet)
        {
            pFS->startElementNS(XML_p, XML_transition,
                XML_spd, speed,
                XML_advTm, OString::number(advanceTiming * 1000),
                FSNS(XML_p14, XML_dur), OString::number(nTransitionDuration));
        }
        else if(isTransitionDurationSet)
        {
            pFS->startElementNS(XML_p, XML_transition,
                XML_spd, speed,
                FSNS(XML_p14, XML_dur), OString::number(nTransitionDuration));
        }
        else if(isAdvanceTimingSet)
        {
            pFS->startElementNS(XML_p, XML_transition,
                XML_spd, speed,
                XML_advTm, OString::number(advanceTiming * 1000));
        }
        else
        {
            pFS->startElementNS(XML_p, XML_transition, XML_spd, speed);
        }

        if (nTransition14)
        {
            pFS->singleElementNS(XML_p14, nTransition14,
                XML_isInverted, pInverted,
                XML_dir, pDirection14,
                XML_pattern, pPattern);
        }
        else if (pPresetTransition)
        {
            pFS->singleElementNS(XML_p15, XML_prstTrans,
                XML_prst, pPresetTransition);
        }
        else if (isTransitionDurationSet && nTransition)
        {
            pFS->singleElementNS(XML_p, nTransition,
                XML_dir, pDirection,
                XML_orient, pOrientation,
                XML_spokes, pSpokes,
                XML_thruBlk, pThruBlk);
        }

        if (!sSoundRelId.isEmpty())
            WriteSndAc(pFS, sSoundRelId, sSoundName);

        pFS->endElement(FSNS(XML_p, XML_transition));

        pFS->endElement(FSNS(XML_mc, XML_Choice));
        pFS->startElement(FSNS(XML_mc, XML_Fallback));
    }

    pFS->startElementNS(XML_p, XML_transition,
        XML_spd, speed,
        XML_advTm, sax_fastparser::UseIf(OString::number(advanceTiming * 1000), isAdvanceTimingSet));

    if (nTransition)
    {
        pFS->singleElementNS(XML_p, nTransition,
                             XML_dir, pDirection,
                             XML_orient, pOrientation,
                             XML_spokes, pSpokes,
                             XML_thruBlk, pThruBlk);
    }

    if (!sSoundRelId.isEmpty())
        WriteSndAc(pFS, sSoundRelId, sSoundName);

    pFS->endElementNS(XML_p, XML_transition);

    if (nTransition14 || pPresetTransition || isTransitionDurationSet)
    {
        pFS->endElement(FSNS(XML_mc, XML_Fallback));
        pFS->endElement(FSNS(XML_mc, XML_AlternateContent));
    }
}

void PowerPointExport::WriteAuthors()
{
    if (maAuthors.empty())
        return;

    FSHelperPtr pFS = openFragmentStreamWithSerializer(u"ppt/commentAuthors.xml"_ustr,
                      u"application/vnd.openxmlformats-officedocument.presentationml.commentAuthors+xml"_ustr);
    addRelation(mPresentationFS->getOutputStream(),
                oox::getRelationship(Relationship::COMMENTAUTHORS),
                u"commentAuthors.xml");

    pFS->startElementNS(XML_p, XML_cmAuthorLst,
                        FSNS(XML_xmlns, XML_p), getNamespaceURL(OOX_NS(ppt)));

    for (const AuthorsMap::value_type& i : maAuthors)
    {
        pFS->singleElementNS(XML_p, XML_cmAuthor,
                             XML_id, OString::number(i.second.nId),
                             XML_name, i.first,
                             XML_initials, i.second.sInitials,
                             XML_lastIdx, OString::number(i.second.nLastIndex),
                             XML_clrIdx, OString::number(i.second.nId));
    }

    pFS->endElementNS(XML_p, XML_cmAuthorLst);

    pFS->endDocument();
}

sal_Int32 PowerPointExport::GetAuthorIdAndLastIndex(const OUString& sAuthor,
                                                    const OUString& sInitials,
                                                    sal_Int32& nLastIndex)
{
    if (maAuthors.count(sAuthor) <= 0)
        maAuthors.emplace(sAuthor, AuthorComments(maAuthors.size(), 0, sInitials));

    nLastIndex = ++maAuthors[ sAuthor ].nLastIndex;

    return maAuthors[ sAuthor ].nId;
}

void PowerPointExport::WritePresentationProps()
{
    Reference<beans::XPropertySet> xPresentationProps(mXModel->getPresentation(),
                                                      uno::UNO_QUERY);
    bool bEndlessVal = xPresentationProps->getPropertyValue(u"IsEndless"_ustr).get<bool>();
    bool bChangeManually = xPresentationProps->getPropertyValue(u"IsAutomatic"_ustr).get<bool>();
    OUString sFirstPage = xPresentationProps->getPropertyValue(u"FirstPage"_ustr).get<OUString>();
    OUString sCustomShow = xPresentationProps->getPropertyValue(u"CustomShow"_ustr).get<OUString>();

    FSHelperPtr pFS = openFragmentStreamWithSerializer(
        u"ppt/presProps.xml"_ustr,
        u"application/vnd.openxmlformats-officedocument.presentationml.presProps+xml"_ustr);

    addRelation(mPresentationFS->getOutputStream(),
                oox::getRelationship(Relationship::PRESPROPS), u"presProps.xml");

    pFS->startElementNS(XML_p, XML_presentationPr, presentationPrNamespaces(*this));

    pFS->startElementNS(XML_p, XML_showPr, XML_loop, sax_fastparser::UseIf("1", bEndlessVal),
                        XML_useTimings, sax_fastparser::UseIf("0", bChangeManually),
                        XML_showNarration, "1");

    Reference<drawing::XDrawPages> xDrawPages(mXModel->getDrawPages(), uno::UNO_SET_THROW);
    if (!sFirstPage.isEmpty())
    {
        sal_Int32 nStartSlide = 1;
        sal_Int32 nEndSlide = xDrawPages->getCount();
        for (sal_Int32 i = 0; i < nEndSlide; i++)
        {
            Reference<drawing::XDrawPage> xDrawPage;
            xDrawPages->getByIndex(i) >>= xDrawPage;
            Reference<container::XNamed> xNamed(xDrawPage, uno::UNO_QUERY_THROW);
            if (xNamed->getName() == sFirstPage)
            {
                nStartSlide = i + 1;
                break;
            }
        }

        pFS->singleElementNS(XML_p, XML_sldRg, XML_st, OUString::number(nStartSlide), XML_end,
                             OUString::number(nEndSlide));
    }

    if (!sCustomShow.isEmpty())
    {
        css::uno::Reference<css::container::XNameContainer> mxCustShows;
        mxCustShows = mXModel->getCustomPresentations();
        const css::uno::Sequence<OUString> aNameSeq(mxCustShows->getElementNames());

        sal_Int32 nCustShowIndex = 0;
        for (sal_Int32 i = 0; i < aNameSeq.getLength(); i++)
        {
            if (aNameSeq[i] == sCustomShow)
            {
                nCustShowIndex = i;
                break;
            }
        }

        pFS->singleElementNS(XML_p, XML_custShow, XML_id, OUString::number(nCustShowIndex));
    }

    pFS->endElementNS(XML_p, XML_showPr);

    pFS->endElementNS(XML_p, XML_presentationPr);

    pFS->endDocument();
}

bool PowerPointExport::WriteComments(sal_uInt32 nPageNum)
{
    Reference< XAnnotationAccess > xAnnotationAccess(mXDrawPage, uno::UNO_QUERY);
    if (xAnnotationAccess.is())
    {
        Reference< XAnnotationEnumeration > xAnnotationEnumeration(xAnnotationAccess->createAnnotationEnumeration());

        if (xAnnotationEnumeration->hasMoreElements())
        {
            bool bRemoveCommentAuthorDates
                = SvtSecurityOptions::IsOptionSet(
                      SvtSecurityOptions::EOption::DocWarnRemovePersonalInfo)
                  && !SvtSecurityOptions::IsOptionSet(
                         SvtSecurityOptions::EOption::DocWarnKeepNoteAuthorDateInfo);

            FSHelperPtr pFS = openFragmentStreamWithSerializer(
                              "ppt/comments/comment" + OUString::number(nPageNum + 1) + ".xml",
                              u"application/vnd.openxmlformats-officedocument.presentationml.comments+xml"_ustr);

            pFS->startElementNS(XML_p, XML_cmLst,
                                FSNS(XML_xmlns, XML_p), this->getNamespaceURL(OOX_NS(ppt)));

            do
            {
                Reference< XAnnotation > xAnnotation(xAnnotationEnumeration->nextElement());
                util::DateTime aDateTime(xAnnotation->getDateTime());
                RealPoint2D aRealPoint2D(xAnnotation->getPosition());
                Reference< XText > xText(xAnnotation->getTextRange());
                sal_Int32 nLastIndex;
                OUString sAuthor(bRemoveCommentAuthorDates
                                     ? "Author"
                                           + OUString::number(GetInfoID(xAnnotation->getAuthor()))
                                     : xAnnotation->getAuthor());
                OUString sInitials(bRemoveCommentAuthorDates
                                       ? "A" + OUString::number(GetInfoID(xAnnotation->getAuthor()))
                                       : xAnnotation->getInitials());
                sal_Int32 nId = GetAuthorIdAndLastIndex(sAuthor, sInitials, nLastIndex);
                char cDateTime[sizeof("-32768-65535-65535T65535:65535:65535.4294967295")];
                    // reserve enough space for hypothetical max length

                snprintf(cDateTime, sizeof cDateTime, "%02" SAL_PRIdINT32 "-%02" SAL_PRIuUINT32 "-%02" SAL_PRIuUINT32 "T%02" SAL_PRIuUINT32 ":%02" SAL_PRIuUINT32 ":%02" SAL_PRIuUINT32 ".%09" SAL_PRIuUINT32, sal_Int32(aDateTime.Year), sal_uInt32(aDateTime.Month), sal_uInt32(aDateTime.Day), sal_uInt32(aDateTime.Hours), sal_uInt32(aDateTime.Minutes), sal_uInt32(aDateTime.Seconds), aDateTime.NanoSeconds);

                util::DateTime aEmptyDate;
                if (bRemoveCommentAuthorDates || aDateTime == aEmptyDate)
                {
                    pFS->startElementNS(XML_p, XML_cm,
                                        XML_authorId, OString::number(nId),
                                        XML_idx, OString::number(nLastIndex));
                }
                else
                {
                    pFS->startElementNS(XML_p, XML_cm,
                                        XML_authorId, OString::number(nId),
                                        XML_dt, cDateTime,
                                        XML_idx, OString::number(nLastIndex));
                }

                pFS->singleElementNS(XML_p, XML_pos,
                                     XML_x, OString::number(std::round(convertMm100ToMasterUnit(aRealPoint2D.X * 100))),
                                     XML_y, OString::number(std::round(convertMm100ToMasterUnit(aRealPoint2D.Y * 100))));

                pFS->startElementNS(XML_p, XML_text);
                pFS->writeEscaped(xText->getString());
                pFS->endElementNS(XML_p, XML_text);

                pFS->endElementNS(XML_p, XML_cm);

            }
            while (xAnnotationEnumeration->hasMoreElements());

            pFS->endElementNS(XML_p, XML_cmLst);

            pFS->endDocument();

            return true;
        }
    }

    return false;
}

void PowerPointExport::WriteVBA()
{
    if (!mbPptm)
        return;

    uno::Reference<embed::XStorage> xDocumentStorage = mXModel->getDocumentStorage();
    OUString aMacrosName(u"_MS_VBA_Macros"_ustr);
    if (!xDocumentStorage.is() || !xDocumentStorage->hasByName(aMacrosName))
        return;

    const sal_Int32 nOpenMode = embed::ElementModes::READ;
    uno::Reference<io::XInputStream> xMacrosStream(xDocumentStorage->openStreamElement(aMacrosName, nOpenMode), uno::UNO_QUERY);
    if (!xMacrosStream.is())
        return;

    uno::Reference<io::XOutputStream> xOutputStream = openFragmentStream(u"ppt/vbaProject.bin"_ustr, u"application/vnd.ms-office.vbaProject"_ustr);
    comphelper::OStorageHelper::CopyInputToOutput(xMacrosStream, xOutputStream);

    // Write the relationship.
    addRelation(mPresentationFS->getOutputStream(), oox::getRelationship(Relationship::VBAPROJECT), u"vbaProject.bin");
}

void PowerPointExport::WriteModifyVerifier()
{
    Sequence<PropertyValue> aInfo;

    try
    {
        Reference<XPropertySet> xDocSettings(
            mXModel->createInstance(u"com.sun.star.document.Settings"_ustr), UNO_QUERY);
        xDocSettings->getPropertyValue(u"ModifyPasswordInfo"_ustr) >>= aInfo;
    }
    catch (const Exception&)
    {
    }

    if (aInfo.hasElements())
    {
        OUString sAlgorithm, sSalt, sHash;
        sal_Int32 nCount = 0;
        for (auto& prop : aInfo)
        {
            if (prop.Name == "algorithm-name")
                prop.Value >>= sAlgorithm;
            else if (prop.Name == "salt")
                prop.Value >>= sSalt;
            else if (prop.Name == "iteration-count")
                prop.Value >>= nCount;
            else if (prop.Name == "hash")
                prop.Value >>= sHash;
        }
        if (!sAlgorithm.isEmpty() && !sSalt.isEmpty() && !sHash.isEmpty())
        {
            sal_Int32 nAlgorithmSid = 0;
            if (sAlgorithm == "MD2")
                nAlgorithmSid = 1;
            else if (sAlgorithm == "MD4")
                nAlgorithmSid = 2;
            else if (sAlgorithm == "MD5")
                nAlgorithmSid = 3;
            else if (sAlgorithm == "SHA-1")
                nAlgorithmSid = 4;
            else if (sAlgorithm == "MAC")
                nAlgorithmSid = 5;
            else if (sAlgorithm == "RIPEMD")
                nAlgorithmSid = 6;
            else if (sAlgorithm == "RIPEMD-160")
                nAlgorithmSid = 7;
            else if (sAlgorithm == "HMAC")
                nAlgorithmSid = 9;
            else if (sAlgorithm == "SHA-256")
                nAlgorithmSid = 12;
            else if (sAlgorithm == "SHA-384")
                nAlgorithmSid = 13;
            else if (sAlgorithm == "SHA-512")
                nAlgorithmSid = 14;

            if (nAlgorithmSid != 0)
                mPresentationFS->singleElementNS(XML_p, XML_modifyVerifier,
                    XML_cryptProviderType, "rsaAES",
                    XML_cryptAlgorithmClass, "hash",
                    XML_cryptAlgorithmType, "typeAny",
                    XML_cryptAlgorithmSid, OString::number(nAlgorithmSid),
                    XML_spinCount, OString::number(nCount),
                    XML_saltData, sSalt,
                    XML_hashData, sHash);
        }
    }
}

void PowerPointExport::ImplWriteSlide(sal_uInt32 nPageNum, sal_uInt32 nMasterNum, sal_uInt16 /* nMode */,
                                      bool bHasBackground, Reference< XPropertySet > const& aXBackgroundPropSet)
{
    SAL_INFO("sd.eppt", "write slide: " << nPageNum << "\n----------------");

    // slides list
    if (nPageNum == 0)
        mPresentationFS->startElementNS(XML_p, XML_sldIdLst);

    // add explicit relation of presentation to this slide
    OUString sRelId = addRelation(mPresentationFS->getOutputStream(),
                                  oox::getRelationship(Relationship::SLIDE),
                                  Concat2View("slides/slide" + OUString::number(nPageNum + 1) +".xml"));

    mPresentationFS->singleElementNS(XML_p, XML_sldId,
                                     XML_id, OString::number(GetNewSlideId()),
                                     FSNS(XML_r, XML_id), sRelId);

    maRelId.push_back(sRelId);

    if (nPageNum == mnPages - 1)
        mPresentationFS->endElementNS(XML_p, XML_sldIdLst);

    FSHelperPtr pFS = openFragmentStreamWithSerializer(
                        "ppt/slides/slide" + OUString::number(nPageNum + 1) + ".xml",
                        u"application/vnd.openxmlformats-officedocument.presentationml.slide+xml"_ustr);

    if (mpSlidesFSArray.size() < mnPages)
        mpSlidesFSArray.resize(mnPages);
    mpSlidesFSArray[ nPageNum ] = pFS;

    const char* pShow = nullptr;
    const char* pShowMasterShape = nullptr;

    if (ImplGetPropertyValue(mXPagePropSet, u"Visible"_ustr))
    {
        bool bShow(false);
        if ((mAny >>= bShow) && !bShow)
            pShow = "0";
    }

    if (ImplGetPropertyValue(mXPagePropSet, u"IsBackgroundObjectsVisible"_ustr))
    {
        bool bShowMasterShape(false);
        if ((mAny >>= bShowMasterShape) && !bShowMasterShape)
            pShowMasterShape = "0";
    }

    auto pAttributes = presentationNamespaces(*this);
    if (pShow)
        pAttributes->add(XML_show, pShow);
    if (pShowMasterShape)
        pAttributes->add(XML_showMasterSp, pShowMasterShape);

    pFS->startElementNS(XML_p, XML_sld, pAttributes);

    pFS->startElementNS(XML_p, XML_cSld);

    // background
    if (bHasBackground)
    {
        ImplWriteBackground(pFS, aXBackgroundPropSet);
    }

    WriteShapeTree(pFS, NORMAL, false);

    pFS->endElementNS(XML_p, XML_cSld);

    WriteTransition(pFS);
    WriteAnimations(pFS, mXDrawPage, *this);

    pFS->endElementNS(XML_p, XML_sld);

    // add implicit relation to slide layout
    addRelation(pFS->getOutputStream(),
                oox::getRelationship(Relationship::SLIDELAYOUT),
                Concat2View("../slideLayouts/slideLayout" +
                    OUString::number(GetLayoutFileId(GetLayoutOffset(mXPagePropSet), nMasterNum)) +
                    ".xml"));

    if (WriteComments(nPageNum))
        // add implicit relation to slide comments
        addRelation(pFS->getOutputStream(),
                    oox::getRelationship(Relationship::COMMENTS),
                    Concat2View("../comments/comment" + OUString::number(nPageNum + 1) + ".xml"));

    SAL_INFO("sd.eppt", "----------------");
}

void PowerPointExport::ImplWriteNotes(sal_uInt32 nPageNum)
{
    if (!mbCreateNotes || !ContainsOtherShapeThanPlaceholders())
        return;

    SAL_INFO("sd.eppt", "write Notes " << nPageNum << "\n----------------");

    FSHelperPtr pFS = openFragmentStreamWithSerializer(
                        "ppt/notesSlides/notesSlide" +
                        OUString::number(nPageNum + 1) +
                        ".xml",
                        u"application/vnd.openxmlformats-officedocument.presentationml.notesSlide+xml"_ustr);

    pFS->startElementNS(XML_p, XML_notes, presentationNamespaces(*this));

    pFS->startElementNS(XML_p, XML_cSld);

    WriteShapeTree(pFS, NOTICE, false);

    pFS->endElementNS(XML_p, XML_cSld);

    pFS->endElementNS(XML_p, XML_notes);

    // add implicit relation to slide
    addRelation(pFS->getOutputStream(),
                oox::getRelationship(Relationship::SLIDE),
                Concat2View("../slides/slide" + OUString::number(nPageNum + 1) + ".xml"));

    // add slide implicit relation to notes
    if (nPageNum < mpSlidesFSArray.size())
        addRelation(mpSlidesFSArray[ nPageNum ]->getOutputStream(),
                    oox::getRelationship(Relationship::NOTESSLIDE),
                    Concat2View("../notesSlides/notesSlide" + OUString::number(nPageNum + 1) + ".xml"));

    // add implicit relation to notes master
    addRelation(pFS->getOutputStream(),
                oox::getRelationship(Relationship::NOTESMASTER),
                u"../notesMasters/notesMaster1.xml");

    SAL_INFO("sd.eppt", "-----------------");

    pFS->endDocument();
}

void PowerPointExport::AddLayoutIdAndRelation(const FSHelperPtr& pFS, sal_Int32 nLayoutFileId)
{
    // add implicit relation of slide master to slide layout
    OUString sRelId = addRelation(pFS->getOutputStream(),
                                  oox::getRelationship(Relationship::SLIDELAYOUT),
                                  Concat2View("../slideLayouts/slideLayout" + OUString::number(nLayoutFileId) + ".xml"));

    pFS->singleElementNS(XML_p, XML_sldLayoutId,
                         XML_id, OString::number(GetNewSlideMasterId()),
                         FSNS(XML_r, XML_id), sRelId);
}

static bool lcl_ContainsEquivalentObject(const SdrPage* pPage, const SdrObject* pObj)
{
    bool bFound = false;

    if (!pPage || !pObj)
        return bFound;

    for (size_t nObj = 0; nObj < pPage->GetObjCount(); ++nObj)
    {
        SdrObject* pObjNext = pPage->GetObj(nObj);
        if (pObjNext && pObjNext->GetMergedItemSet().Equals(pObj->GetMergedItemSet(), false))
        {
            bFound = true;
            break;
        }
    }

    return bFound;
}

static bool lcl_ComparePageObjects(const SdrPage* pMasterPage, SdrPage* pMasterNext)
{
    if (!pMasterPage || !pMasterNext)
        return false;

    SdrObject* pObjNext;
    SdrLayerID aLayer = pMasterNext->GetLayerAdmin().GetLayerID(sUNO_LayerName_background_objects);

    for (size_t nObj = 0; nObj < pMasterPage->GetObjCount(); ++nObj)
    {
        pObjNext = pMasterPage->GetObj(nObj);
        if (!pObjNext || pObjNext->GetLayer() == aLayer)
            continue;

        if (!lcl_ContainsEquivalentObject(pMasterNext, pObjNext))
            return false;
    }

    // No differences found
    return true;
}

static bool lcl_ComparePageProperties(SdrPage* pMasterPage, SdrPage* pMasterNext)
{
    if (!pMasterPage || !pMasterNext)
        return false;

    SdrPageProperties& rProperties = pMasterPage->getSdrPageProperties();
    SdrPageProperties& rNextProperties = pMasterNext->getSdrPageProperties();

    return rProperties.GetItemSet().Equals(rNextProperties.GetItemSet(), false)
           && rProperties.getTheme().get() == rNextProperties.getTheme().get();
}

void PowerPointExport::FindEquivalentMasterPages()
{
    css::uno::Reference<css::drawing::XDrawPages> xDrawPages(
        mXModel->getMasterPages());
    maMastersLayouts.resize(mnMasterPages);
    maEquivalentMasters.resize(mnMasterPages, SAL_MAX_UINT32);
    for (sal_uInt32 i = 0; i < mnMasterPages; i++)
    {
        css::uno::Reference<css::drawing::XDrawPage> xDrawPage;
        uno::Any aAny(xDrawPages->getByIndex(i));
        aAny >>= xDrawPage;
        if (!xDrawPage.is())
            continue;

        maMastersLayouts[i] = std::make_pair(SdPage::getImplementation(xDrawPage), -1);
        uno::Reference<beans::XPropertySet> xPagePropSet(xDrawPage, uno::UNO_QUERY_THROW);
        if (xPagePropSet.is())
        {
            uno::Any aLayout = xPagePropSet->getPropertyValue("SlideLayout");
            if (aLayout.hasValue())
            {
                aLayout >>= maMastersLayouts[i].second;
            }
        }
    }

    for (sal_uInt32 i = 0; i < mnMasterPages; i++)
    {
        if (!maMastersLayouts[i].first || maEquivalentMasters[i] != SAL_MAX_UINT32)
            continue;
        for (sal_uInt32 j = i + 1; j < mnMasterPages; j++)
        {
            if (!maMastersLayouts[j].first || maEquivalentMasters[j] != SAL_MAX_UINT32)
                continue;

            if (lcl_ComparePageProperties(maMastersLayouts[i].first, maMastersLayouts[j].first)
                && lcl_ComparePageObjects(maMastersLayouts[i].first, maMastersLayouts[j].first))
            {
                // If both masters have the same properties and objects,
                // we assume they are the same and only export the first one
                maEquivalentMasters[j] = i;
                maEquivalentMasters[i] = i;
            }
        }
    }
}

sal_uInt32 PowerPointExport::GetEquivalentMasterPage(sal_uInt32 nMasterPage)
{
    if (maEquivalentMasters.size() == 0)
        FindEquivalentMasterPages();
    return maEquivalentMasters[nMasterPage];
}

void PowerPointExport::ImplWriteSlideMaster(sal_uInt32 nPageNum, Reference< XPropertySet > const& aXBackgroundPropSet)
{
    SAL_INFO("sd.eppt", "write master slide: " << nPageNum << "\n--------------");

    if (nPageNum != GetEquivalentMasterPage(nPageNum)
        && GetEquivalentMasterPage(nPageNum) != SAL_MAX_UINT32)
    {
        // It's equivalent to an already written master, write only the layouts files
        OUString aSlideName;
        Reference<XNamed> xNamed(mXDrawPage, UNO_QUERY);
        if (xNamed.is())
            aSlideName = xNamed->getName();
        for (int i = 0; i < AUTOLAYOUT_END; ++i)
        {
            if (mLayoutInfo[i].mnFileIdArray.size() > nPageNum
                && mLayoutInfo[i].mnFileIdArray[nPageNum] > 0)
            {
                ImplWritePPTXLayoutWithContent(i, nPageNum, aSlideName, aXBackgroundPropSet);
            }
        }

        // Close the list tag if it was the last one
        if (nPageNum == mnMasterPages - 1)
            mPresentationFS->endElementNS(XML_p, XML_sldMasterIdLst);

        return;
    }

    // slides list
    if (nPageNum == 0)
        mPresentationFS->startElementNS(XML_p, XML_sldMasterIdLst);

    OUString sRelId = addRelation(mPresentationFS->getOutputStream(),
                                  oox::getRelationship(Relationship::SLIDEMASTER),
                                  Concat2View("slideMasters/slideMaster" + OUString::number(nPageNum + 1) + ".xml"));

    mPresentationFS->singleElementNS(XML_p, XML_sldMasterId,
                                     XML_id, OString::number(GetNewSlideMasterId()),
                                     FSNS(XML_r, XML_id), sRelId);

    if (nPageNum == mnMasterPages - 1)
        mPresentationFS->endElementNS(XML_p, XML_sldMasterIdLst);

    FSHelperPtr pFS =
        openFragmentStreamWithSerializer("ppt/slideMasters/slideMaster" +
                                          OUString::number(nPageNum + 1) + ".xml",
                                         u"application/vnd.openxmlformats-officedocument.presentationml.slideMaster+xml"_ustr);

    SdrPage* pMasterPage = SdPage::getImplementation(mXDrawPage);
    model::Theme* pTheme = nullptr;
    if (pMasterPage)
    {
        pTheme = pMasterPage->getSdrPageProperties().getTheme().get();
    }

    // write theme per master
    WriteTheme(mnThemeIdMax, pTheme);

    // add implicit relation to the presentation theme
    addRelation(pFS->getOutputStream(), oox::getRelationship(Relationship::THEME),
                Concat2View("../theme/theme" + OUString::number(++mnThemeIdMax) + ".xml"));

    pFS->startElementNS(XML_p, XML_sldMaster, presentationNamespaces(*this));

    pFS->startElementNS(XML_p, XML_cSld);

    if (GetEquivalentMasterPage(nPageNum) != nPageNum)
    {
        if (aXBackgroundPropSet)
            ImplWriteBackground(pFS, aXBackgroundPropSet);
        WriteShapeTree(pFS, MASTER, true);
    }
    else
    {
        // Minimal shape tree, the actual one will be written in the layout file.
        pFS->startElementNS(XML_p, XML_spTree);
        pFS->write(MAIN_GROUP);
        pFS->endElementNS(XML_p, XML_spTree);
    }

    pFS->endElementNS(XML_p, XML_cSld);

    uno::Sequence<beans::PropertyValue> aGrabBag;
    if (mXModel->getPropertySetInfo()->hasPropertyByName(u"InteropGrabBag"_ustr))
        mXModel->getPropertyValue(u"InteropGrabBag"_ustr) >>= aGrabBag;

    std::vector<OUString> aClrMap;
    aClrMap.reserve(12);
    uno::Sequence<beans::PropertyValue> aClrMapPropValue;
    if(aGrabBag.hasElements())
    {
        for (const auto& rProp : aGrabBag)
        {
            if (rProp.Name == "OOXColorMap")
            {
                rProp.Value >>= aClrMapPropValue;
                break;
            }
        }
    }

    if (aClrMapPropValue.getLength())
    {
        OUString sName;
        sal_Int32 nToken = XML_TOKEN_INVALID;
        for(const auto& item : aClrMapPropValue)
        {
            item.Value >>= nToken;
            switch (nToken)
            {
                case XML_dk1:      sName = u"dk1"_ustr;      break;
                case XML_lt1:      sName = u"lt1"_ustr;      break;
                case XML_dk2:      sName = u"dk2"_ustr;      break;
                case XML_lt2:      sName = u"lt2"_ustr;      break;
                case XML_accent1:  sName = u"accent1"_ustr;  break;
                case XML_accent2:  sName = u"accent2"_ustr;  break;
                case XML_accent3:  sName = u"accent3"_ustr;  break;
                case XML_accent4:  sName = u"accent4"_ustr;  break;
                case XML_accent5:  sName = u"accent5"_ustr;  break;
                case XML_accent6:  sName = u"accent6"_ustr;  break;
                case XML_hlink:    sName = u"hlink"_ustr;    break;
                case XML_folHlink: sName = u"folHlink"_ustr; break;
            }
            aClrMap.push_back(sName);
        }
        assert(aClrMap.size() == 12 && "missing entries for ClrMap");
    }
    else
    {
        // default clrMap to export ".odp" files to ".pptx"
        aClrMap = { u"lt1"_ustr,     u"dk1"_ustr,     u"lt2"_ustr,     u"dk2"_ustr,
                    u"accent1"_ustr, u"accent2"_ustr, u"accent3"_ustr, u"accent4"_ustr,
                    u"accent5"_ustr, u"accent6"_ustr, u"hlink"_ustr,   u"folHlink"_ustr };
    }

    pFS->singleElementNS(XML_p, XML_clrMap,
                         XML_bg1, aClrMap[0],
                         XML_tx1, aClrMap[1],
                         XML_bg2, aClrMap[2],
                         XML_tx2, aClrMap[3],
                         XML_accent1, aClrMap[4],
                         XML_accent2, aClrMap[5],
                         XML_accent3, aClrMap[6],
                         XML_accent4, aClrMap[7],
                         XML_accent5, aClrMap[8],
                         XML_accent6, aClrMap[9],
                         XML_hlink, aClrMap[10],
                         XML_folHlink, aClrMap[11]);

    // use master's id type as they have same range, mso does that as well
    pFS->startElementNS(XML_p, XML_sldLayoutIdLst);

    auto getLayoutsUsedForMaster = [](SdrPage* pMaster) -> std::unordered_set<sal_Int32>
    {
        if (!pMaster)
            return {};

        std::unordered_set<sal_Int32> aUsedLayouts{};
        for (const auto* pPageUser : pMaster->GetPageUsers())
        {
            const auto* pMasterPageDescriptor
                = dynamic_cast<const sdr::MasterPageDescriptor*>(pPageUser);

            if (!pMasterPageDescriptor)
                continue;

            AutoLayout eLayout
                = static_cast<SdPage&>(pMasterPageDescriptor->GetOwnerPage()).GetAutoLayout();
            aUsedLayouts.insert(eLayout);
        }
        return aUsedLayouts;
    };

    std::unordered_set<sal_Int32> aLayouts = getLayoutsUsedForMaster(pMasterPage);

    css::uno::Reference< css::beans::XPropertySet > xPagePropSet;
    xPagePropSet.set(mXDrawPage, UNO_QUERY);
    if (xPagePropSet.is())
    {
        uno::Any aAny;
        if (GetPropertyValue(aAny, xPagePropSet, u"SlideLayout"_ustr))
            aLayouts.insert(aAny.get<sal_Int32>());
    }

    OUString aSlideName;
    Reference< XNamed > xNamed(mXDrawPage, UNO_QUERY);
    if (xNamed.is())
        aSlideName = xNamed->getName();

    for (auto nLayout : aLayouts)
    {
        if (GetEquivalentMasterPage(nPageNum) == nPageNum)
            ImplWritePPTXLayoutWithContent(nLayout, nPageNum, aSlideName, aXBackgroundPropSet);
        else
            ImplWritePPTXLayout(nLayout, nPageNum, aSlideName);
        AddLayoutIdAndRelation(pFS, GetLayoutFileId(nLayout, nPageNum));
    }

    // Add layouts of other Impress masters that came from a single pptx master with multiple layouts
    for (sal_uInt32 i = 0; i < mnMasterPages; i++)
    {
        if (i != nPageNum && maEquivalentMasters[i] == nPageNum)
        {
            aLayouts = getLayoutsUsedForMaster(maMastersLayouts[i].first);
            if (maMastersLayouts[i].second != -1)
                aLayouts.insert(maMastersLayouts[i].second);

            for (auto nLayout : aLayouts)
            {
                // Reserve layout file Id to be written later
                if (mLayoutInfo[nLayout].mnFileIdArray.size() < mnMasterPages)
                    mLayoutInfo[nLayout].mnFileIdArray.resize(mnMasterPages);
                mLayoutInfo[nLayout].mnFileIdArray[i] = mnLayoutFileIdMax;
                mnLayoutFileIdMax++;

                AddLayoutIdAndRelation(pFS, GetLayoutFileId(nLayout, i));
            }
        }
    }

    pFS->endElementNS(XML_p, XML_sldLayoutIdLst);

    pFS->endElementNS(XML_p, XML_sldMaster);

    SAL_INFO("sd.eppt", "----------------");

    pFS->endDocument();
}

sal_Int32 PowerPointExport::GetLayoutFileId(sal_Int32 nOffset, sal_uInt32 nMasterNum)
{
    SAL_INFO("sd.eppt", "GetLayoutFileId offset: " << nOffset << " master: " << nMasterNum);
    if (mLayoutInfo[ nOffset ].mnFileIdArray.size() <= nMasterNum)
        return 0;

    return mLayoutInfo[ nOffset ].mnFileIdArray[ nMasterNum ];
}

void PowerPointExport::ImplWritePPTXLayout(sal_Int32 nOffset, sal_uInt32 nMasterNum, const OUString& aSlideName)
{
    SAL_INFO("sd.eppt", "write layout: " << nOffset);

    Reference< drawing::XDrawPages > xDrawPages = mXModel->getDrawPages();
    Reference< drawing::XDrawPage > xSlide = xDrawPages->insertNewByIndex(xDrawPages->getCount());

#if OSL_DEBUG_LEVEL >= 2
    if (xSlide.is())
        printf("new page created\n");
#endif

    Reference< beans::XPropertySet > xPropSet(xSlide, uno::UNO_QUERY);
    xPropSet->setPropertyValue(u"Layout"_ustr, Any(short(aLayoutInfo[ nOffset ].nType)));
#if OSL_DEBUG_LEVEL > 1
    dump_pset(xPropSet);
#endif
    mXPagePropSet.set(xSlide, UNO_QUERY);
    mXShapes = xSlide;

    if (mLayoutInfo[ nOffset ].mnFileIdArray.size() < mnMasterPages)
    {
        mLayoutInfo[ nOffset ].mnFileIdArray.resize(mnMasterPages);
    }

    if (mLayoutInfo[ nOffset ].mnFileIdArray[ nMasterNum ] != 0)
        return;

    FSHelperPtr pFS
        = openFragmentStreamWithSerializer("ppt/slideLayouts/slideLayout" +
                                            OUString::number(mnLayoutFileIdMax) + ".xml",
                                           u"application/vnd.openxmlformats-officedocument.presentationml.slideLayout+xml"_ustr);

    // add implicit relation of slide layout to slide master
    addRelation(
        pFS->getOutputStream(), oox::getRelationship(Relationship::SLIDEMASTER),
        Concat2View("../slideMasters/slideMaster" + OUString::number(nMasterNum + 1) + ".xml"));

    auto pAttributes = presentationNamespaces(*this);
    pAttributes->add(XML_type, aLayoutInfo[nOffset].sType);
    pAttributes->add(XML_preserve, "1");

    pFS->startElementNS(XML_p, XML_sldLayout, pAttributes);

    if (!aSlideName.isEmpty())
    {
        pFS->startElementNS(XML_p, XML_cSld,
            XML_name, aSlideName);
    }
    else
    {
        pFS->startElementNS(XML_p, XML_cSld,
            XML_name, aLayoutInfo[nOffset].sName);
    }
    //pFS->write( MINIMAL_SPTREE ); // TODO: write actual shape tree
    WriteShapeTree(pFS, LAYOUT, true);

    pFS->endElementNS(XML_p, XML_cSld);

    pFS->endElementNS(XML_p, XML_sldLayout);

    mLayoutInfo[ nOffset ].mnFileIdArray[ nMasterNum ] = mnLayoutFileIdMax;

    mnLayoutFileIdMax ++;

    xDrawPages->remove(xSlide);

    pFS->endDocument();
}

void PowerPointExport::ImplWritePPTXLayoutWithContent(
    sal_Int32 nOffset, sal_uInt32 nMasterNum, const OUString& aSlideName,
    Reference<XPropertySet> const& aXBackgroundPropSet)
{
    SAL_INFO("sd.eppt", "write layout: " << nOffset);

    if (mLayoutInfo[nOffset].mnFileIdArray.size() < mnMasterPages)
    {
        mLayoutInfo[nOffset].mnFileIdArray.resize(mnMasterPages);
    }

    if (mLayoutInfo[nOffset].mnFileIdArray[nMasterNum] == 0)
    {
        mLayoutInfo[nOffset].mnFileIdArray[nMasterNum] = mnLayoutFileIdMax;
        mnLayoutFileIdMax++;
    }
    sal_Int32 nLayoutFileId = mLayoutInfo[nOffset].mnFileIdArray[nMasterNum];

    FSHelperPtr pFS = openFragmentStreamWithSerializer(
        "ppt/slideLayouts/slideLayout" + OUString::number(nLayoutFileId) + ".xml",
        "application/vnd.openxmlformats-officedocument.presentationml.slideLayout+xml");

    // add implicit relation of slide layout to slide master
    addRelation(pFS->getOutputStream(), oox::getRelationship(Relationship::SLIDEMASTER),
                Concat2View("../slideMasters/slideMaster"
                            + OUString::number(GetEquivalentMasterPage(nMasterNum) + 1) + ".xml"));

    auto pAttributes = presentationNamespaces(*this);
    pAttributes->add(XML_type, aLayoutInfo[nOffset].sType);
    pAttributes->add(XML_preserve, "1");

    pFS->startElementNS(XML_p, XML_sldLayout, pAttributes);

    if (!aSlideName.isEmpty())
    {
        pFS->startElementNS(XML_p, XML_cSld, XML_name, aSlideName);
    }
    else
    {
        pFS->startElementNS(XML_p, XML_cSld, XML_name, aLayoutInfo[nOffset].sName);
    }

    if (aXBackgroundPropSet)
        ImplWriteBackground(pFS, aXBackgroundPropSet);

    WriteShapeTree(pFS, MASTER, true);

    pFS->endElementNS(XML_p, XML_cSld);

    pFS->endElementNS(XML_p, XML_sldLayout);

    pFS->endDocument();
}

void PowerPointExport::WriteShapeTree(const FSHelperPtr& pFS, PageType ePageType, bool bMaster)
{
    PowerPointShapeExport aDML(pFS, &maShapeMap, this);
    aDML.SetMaster(bMaster);
    aDML.SetPageType(ePageType);
    aDML.SetBackgroundDark(mbIsBackgroundDark);

    pFS->startElementNS(XML_p, XML_spTree);
    pFS->write(MAIN_GROUP);

    // MS Office complains if a shape ID is duplicated, and MAIN_GROUP specified p:cNvPr id="1"
    if (GetMaxDocId() < 2)
        SetMaxDocId(2); // this will be the next GetUniqueId()

    ResetGroupTable(mXShapes->getCount());

    while (GetNextGroupEntry())
    {

        sal_uInt32 nGroups = GetGroupsClosed();
        for (sal_uInt32 i = 0; i < nGroups; i++)
        {
            SAL_INFO("sd.eppt", "leave group");
        }

        if (GetShapeByIndex(GetCurrentGroupIndex(), true))
        {
            SAL_INFO("sd.eppt", "mType: " << mType);
            const SdrObjGroup* pDiagramCandidate(dynamic_cast<const SdrObjGroup*>(SdrObject::getSdrObjectFromXShape(mXShape)));
            const bool bIsDiagram(nullptr != pDiagramCandidate && pDiagramCandidate->isDiagram());

            if (bIsDiagram)
                WriteDiagram(pFS, aDML, mXShape, mnDiagramId++);
            else
                aDML.WriteShape(mXShape);
        }
    }

    if ( ePageType == NORMAL || ePageType == LAYOUT )
        WritePlaceholderReferenceShapes(aDML, ePageType);
    pFS->endElementNS(XML_p, XML_spTree);
}

ShapeExport& PowerPointShapeExport::WritePageShape(const Reference< XShape >& xShape, PageType ePageType, bool bPresObj)
{
    if ((ePageType == NOTICE && bPresObj) || ePageType == LAYOUT || ePageType == MASTER)
        return WritePlaceholderShape(xShape, SlideImage);

    return WriteTextShape(xShape);
}

bool PowerPointShapeExport::WritePlaceholder(const Reference< XShape >& xShape, PlaceholderType ePlaceholder, bool bMaster)
{
    SAL_INFO("sd.eppt", "WritePlaceholder " << bMaster << " " << ShapeExport::NonEmptyText(xShape));
    if (!xShape)
        return false;
    try
    {
        Reference<XPropertySet> xShapeProps(xShape, UNO_QUERY);
        if (xShapeProps->getPropertyValue(u"IsPresentationObject"_ustr).get<bool>())
        {
            WritePlaceholderShape(xShape, ePlaceholder);

            return true;
        }
    }
    catch (Exception&)
    {
        return false;
    }
    return false;
}

ShapeExport& PowerPointShapeExport::WritePlaceholderShape(const Reference< XShape >& xShape, PlaceholderType ePlaceholder)
{
    Reference<XPropertySet> xProps(xShape, UNO_QUERY);
    bool bUseBackground(false);
    if (xProps.is() && xProps->getPropertySetInfo()->hasPropertyByName(u"FillUseSlideBackground"_ustr))
        xProps->getPropertyValue(u"FillUseSlideBackground"_ustr) >>= bUseBackground;

    if (bUseBackground)
        mpFS->startElementNS(XML_p, XML_sp, XML_useBgFill, "1");
    else
        mpFS->startElementNS(XML_p, XML_sp);

    // non visual shape properties
    mpFS->startElementNS(XML_p, XML_nvSpPr);
    const OString aPlaceholderID("PlaceHolder " + OString::number(mnShapeIdMax++));
    WriteNonVisualDrawingProperties(xShape, aPlaceholderID.getStr());
    mpFS->startElementNS(XML_p, XML_cNvSpPr);
    mpFS->singleElementNS(XML_a, XML_spLocks, XML_noGrp, "1");
    mpFS->endElementNS(XML_p, XML_cNvSpPr);
    mpFS->startElementNS(XML_p, XML_nvPr);

    bool bUsePlaceholderIndex
        = ePlaceholder == Footer || ePlaceholder == DateAndTime || ePlaceholder == SlideNumber;
    const char* pType = getPlaceholderTypeName(ePlaceholder);

    SAL_INFO("sd.eppt", "write placeholder " << pType);

    // export Custom Prompt
    bool bUseCustomPrompt(false);
    if (xProps.is() && xProps->getPropertySetInfo()->hasPropertyByName(u"CustomPromptText"_ustr))
    {
        OUString aCustomPromptText;
        xProps->getPropertyValue(u"CustomPromptText"_ustr) >>= aCustomPromptText;
        if (!aCustomPromptText.isEmpty())
            bUseCustomPrompt = true;
    }

    if (bUsePlaceholderIndex)
    {
        mpFS->singleElementNS(
            XML_p, XML_ph, XML_type, pType, XML_idx,
            OString::number(
                static_cast<PowerPointExport*>(GetFB())->CreateNewPlaceholderIndex(xShape)),
                    XML_hasCustomPrompt, sax_fastparser::UseIf("1", bUseCustomPrompt));
    }
    else
    {
        if ((mePageType == PageType::LAYOUT || mePageType == PageType::NORMAL)
            && ePlaceholder == Outliner)
            mpFS->singleElementNS(XML_p, XML_ph, XML_hasCustomPrompt, sax_fastparser::UseIf("1", bUseCustomPrompt));
        else
            mpFS->singleElementNS(XML_p, XML_ph, XML_type, pType, XML_hasCustomPrompt, sax_fastparser::UseIf("1", bUseCustomPrompt));
    }
    mpFS->endElementNS(XML_p, XML_nvPr);
    mpFS->endElementNS(XML_p, XML_nvSpPr);

    // visual shape properties
    mpFS->startElementNS(XML_p, XML_spPr);
    WriteShapeTransformation(xShape, XML_a);
    WritePresetShape("rect"_ostr);
    if (xProps.is())
    {
        WriteBlipFill(xProps, u"Graphic"_ustr);
        // Do not forget to export the visible properties.
        WriteFill( xProps, xShape->getSize());
        WriteOutline( xProps );
        WriteShapeEffects( xProps );

        bool bHas3DEffectinShape = false;
        uno::Sequence<beans::PropertyValue> grabBag;
        if (xProps->getPropertySetInfo()->hasPropertyByName(u"InteropGrabBag"_ustr))
            xProps->getPropertyValue(u"InteropGrabBag"_ustr) >>= grabBag;

        for (auto const& it : grabBag)
            if (it.Name == "3DEffectProperties")
                bHas3DEffectinShape = true;

        if( bHas3DEffectinShape)
            Write3DEffects( xProps, /*bIsText=*/false );
    }
    mpFS->endElementNS(XML_p, XML_spPr);

    bool bWritePropertiesAsLstStyles
        = (mePageType == PageType::MASTER)
          && (ePlaceholder == Title || ePlaceholder == Subtitle || ePlaceholder == Outliner);

    WriteTextBox(xShape, XML_p,
                 bUsePlaceholderIndex || bWritePropertiesAsLstStyles);

    mpFS->endElementNS(XML_p, XML_sp);

    return *this;
}

ShapeExport& PowerPointShapeExport::WritePlaceholderReferenceShape(
    PlaceholderType ePlaceholder, sal_Int32 nReferencedPlaceholderIdx, PageType ePageType,
    const Reference<XPropertySet>& rXPagePropSet)
{
    mpFS->startElementNS(XML_p, XML_sp);

    // non visual shape properties
    mpFS->startElementNS(XML_p, XML_nvSpPr);
    const OString aPlaceholderID("PlaceHolder " + OString::number(mnShapeIdMax++));
    GetFS()->singleElementNS(XML_p, XML_cNvPr, XML_id, OString::number(mnShapeIdMax), XML_name,
                             aPlaceholderID);

    mpFS->startElementNS(XML_p, XML_cNvSpPr);
    mpFS->singleElementNS(XML_a, XML_spLocks, XML_noGrp, "1");
    mpFS->endElementNS(XML_p, XML_cNvSpPr);
    mpFS->startElementNS(XML_p, XML_nvPr);

    const char* pType = getPlaceholderTypeName(ePlaceholder);
    mpFS->singleElementNS(XML_p, XML_ph, XML_type, pType, XML_idx,
                          OString::number(nReferencedPlaceholderIdx));
    mpFS->endElementNS(XML_p, XML_nvPr);
    mpFS->endElementNS(XML_p, XML_nvSpPr);

    // visual shape properties
    mpFS->startElementNS(XML_p, XML_spPr);
    mpFS->endElementNS(XML_p, XML_spPr);

    WritePlaceholderReferenceTextBody(ePlaceholder, ePageType, rXPagePropSet);

    mpFS->endElementNS(XML_p, XML_sp);

    return *this;
}

ShapeExport& PowerPointShapeExport::WritePlaceholderReferenceTextBody(
    PlaceholderType ePlaceholder, PageType ePageType, const Reference<XPropertySet>& xPagePropSet)
{
    mpFS->startElementNS(XML_p, XML_txBody);
    mpFS->singleElementNS(XML_a, XML_bodyPr);
    mpFS->startElementNS(XML_a, XML_p);

    switch (ePlaceholder)
    {
        case Header:
            break;
        case Footer:
        {
            OUString aFooterText;
            if (ePageType == LAYOUT)
            {
                aFooterText = "Footer";
            }
            else
            {
                xPagePropSet->getPropertyValue(u"FooterText"_ustr) >>= aFooterText;
            }
            mpFS->startElementNS(XML_a, XML_r);
            mpFS->startElementNS(XML_a, XML_t);
            mpFS->writeEscaped(aFooterText);
            mpFS->endElementNS(XML_a, XML_t);
            mpFS->endElementNS(XML_a, XML_r);
            break;
        }
        case SlideNumber:
        {
            OUString aSlideNum;
            sal_Int32 nSlideNum = 0;
            if (ePageType == LAYOUT)
            {
                aSlideNum = "<#>";
            }
            else
            {
                xPagePropSet->getPropertyValue(u"Number"_ustr) >>= nSlideNum;
                aSlideNum = OUString::number(nSlideNum);
            }
            OString aUUID(comphelper::xml::generateGUIDString());
            mpFS->startElementNS(XML_a, XML_fld, XML_id, aUUID, XML_type, "slidenum");
            mpFS->startElementNS(XML_a, XML_t);
            mpFS->writeEscaped(aSlideNum);
            mpFS->endElementNS(XML_a, XML_t);
            mpFS->endElementNS(XML_a, XML_fld);
            break;
        }
        case DateAndTime:
        {
            OUString aDateTimeType = u"datetime1"_ustr;
            bool bIsDateTimeFixed = false;
            xPagePropSet->getPropertyValue(u"IsDateTimeFixed"_ustr) >>= bIsDateTimeFixed;

            OUString aDateTimeText = u"Date"_ustr;
            const LanguageTag& rLanguageTag = Application::GetSettings().GetLanguageTag();

            if(ePageType != LAYOUT && !bIsDateTimeFixed)
            {
                sal_Int32 nDateTimeFormat = 0;
                xPagePropSet->getPropertyValue(u"DateTimeFormat"_ustr) >>= nDateTimeFormat;

                // 4 LSBs represent the date
                SvxDateFormat eDate = static_cast<SvxDateFormat>(nDateTimeFormat & 0x0f);
                // the 4 bits after the date bits represent the time
                SvxTimeFormat eTime = static_cast<SvxTimeFormat>(nDateTimeFormat >> 4);
                aDateTimeType = GetDatetimeTypeFromDateTime(eDate, eTime);

                if (aDateTimeType == "datetime")
                    aDateTimeType = "datetime1";

                ::DateTime aDateTime( ::DateTime::SYSTEM );

                aDateTimeText = SvxDateTimeField::GetFormatted(
                    aDateTime, aDateTime, eDate,
                    eTime, *(SdModule::get()->GetNumberFormatter()),
                    rLanguageTag.getLanguageType());
            }

            if(!bIsDateTimeFixed)
            {
                OString aUUID(comphelper::xml::generateGUIDString());
                mpFS->startElementNS(XML_a, XML_fld, XML_id, aUUID, XML_type, aDateTimeType);
            }
            else
            {
                xPagePropSet->getPropertyValue(u"DateTimeText"_ustr) >>= aDateTimeText;
                mpFS->startElementNS(XML_a, XML_r);
            }

            mpFS->startElementNS(XML_a, XML_rPr, XML_lang, rLanguageTag.getBcp47MS());
            mpFS->endElementNS(XML_a, XML_rPr);

            mpFS->startElementNS(XML_a, XML_t);
            mpFS->writeEscaped(aDateTimeText);
            mpFS->endElementNS(XML_a, XML_t);

            mpFS->endElementNS(XML_a, bIsDateTimeFixed ? XML_r : XML_fld);
            break;
        }
        default:
            SAL_INFO("sd.eppt", "warning: no defined textbody for referenced placeholder type: "
                                    << ePlaceholder);
    }
    mpFS->endElementNS(XML_a, XML_p);
    mpFS->endElementNS(XML_p, XML_txBody);

    return *this;
}

void PowerPointExport::WriteDefaultColorSchemes(const FSHelperPtr& pFS)
{
    for (int nId = PredefinedClrSchemeId::dk2; nId != PredefinedClrSchemeId::Count; nId++)
    {
        OUString sName(getPredefinedClrNames(static_cast<PredefinedClrSchemeId>(nId)));
        sal_Int32 nColor = 0;

        switch (nId)
        {
        case PredefinedClrSchemeId::dk2:
            nColor = 0x1F497D;
            break;
        case PredefinedClrSchemeId::lt2:
            nColor = 0xEEECE1;
            break;
        case PredefinedClrSchemeId::accent1:
            nColor = 0x4F81BD;
            break;
        case PredefinedClrSchemeId::accent2:
            nColor = 0xC0504D;
            break;
        case PredefinedClrSchemeId::accent3:
            nColor = 0x9BBB59;
            break;
        case PredefinedClrSchemeId::accent4:
            nColor = 0x8064A2;
            break;
        case PredefinedClrSchemeId::accent5:
            nColor = 0x4BACC6;
            break;
        case PredefinedClrSchemeId::accent6:
            nColor = 0xF79646;
            break;
        case PredefinedClrSchemeId::hlink:
            nColor = 0x0000FF;
            break;
        case PredefinedClrSchemeId::folHlink:
            nColor = 0x800080;
            break;
        }

        OUString sOpenColorScheme = "<a:" + sName + ">";
        pFS->write(sOpenColorScheme);

        pFS->singleElementNS(XML_a, XML_srgbClr, XML_val, I32SHEX(nColor));

        OUString sCloseColorScheme = "</a:" + sName + ">";
        pFS->write(sCloseColorScheme);
    }
}

void PowerPointExport::WriteTheme(sal_Int32 nThemeNum, const model::Theme* pTheme)
{
    if (!pTheme)
        return;
    OUString sThemePath = "ppt/theme/theme" + OUString::number(nThemeNum + 1) + ".xml";

    oox::ThemeExport aThemeExport(this, DOCUMENT_PPTX);

    aThemeExport.write(sThemePath, *pTheme);
}

bool PowerPointExport::ImplCreateDocument()
{
    mbCreateNotes = false;

    for (sal_uInt32 i = 0; i < mnPages; i++)
    {
        if (!GetPageByIndex(i, NOTICE))
            return false;

        if (ContainsOtherShapeThanPlaceholders())
        {
            mbCreateNotes = true;
            break;
        }
    }

    return true;
}

void PowerPointExport::WriteNotesMaster()
{
    SAL_INFO("sd.eppt", "write Notes master\n---------------");

    mPresentationFS->startElementNS(XML_p, XML_notesMasterIdLst);

    OUString sRelId = addRelation(mPresentationFS->getOutputStream(),
                                  oox::getRelationship(Relationship::NOTESMASTER),
                                  u"notesMasters/notesMaster1.xml");

    mPresentationFS->singleElementNS(XML_p, XML_notesMasterId,
                                     FSNS(XML_r, XML_id), sRelId);

    mPresentationFS->endElementNS(XML_p, XML_notesMasterIdLst);

    FSHelperPtr pFS =
        openFragmentStreamWithSerializer(u"ppt/notesMasters/notesMaster1.xml"_ustr,
                                         u"application/vnd.openxmlformats-officedocument.presentationml.notesMaster+xml"_ustr);
    // write theme per master

    // TODO: Need to implement theme support for note master, so the
    // note master has his own theme associated.

    // For now just use the default theme
    auto const* pDefaultColorSet = svx::ColorSets::get().getColorSet(u"LibreOffice");
    if (pDefaultColorSet)
    {
        auto pTheme = std::make_shared<model::Theme>("Office Theme");
        pTheme->setColorSet(std::make_shared<model::ColorSet>(*pDefaultColorSet));

        WriteTheme(mnMasterPages, pTheme.get());

        // add implicit relation to the presentation theme
        addRelation(pFS->getOutputStream(),
                    oox::getRelationship(Relationship::THEME),
                    Concat2View("../theme/theme" + OUString::number(mnMasterPages + 1) + ".xml"));
    }

    pFS->startElementNS(XML_p, XML_notesMaster, presentationNamespaces(*this));

    pFS->startElementNS(XML_p, XML_cSld);

    Reference< XPropertySet > aXBackgroundPropSet;
    if (ImplGetPropertyValue(mXPagePropSet, u"Background"_ustr) &&
            (mAny >>= aXBackgroundPropSet))
        ImplWriteBackground(pFS, aXBackgroundPropSet);

    WriteShapeTree(pFS, NOTICE, true);

    pFS->endElementNS(XML_p, XML_cSld);

    // color map - now it uses colors from hardcoded theme, once we eventually generate theme, this might need update
    pFS->singleElementNS(XML_p, XML_clrMap,
                         XML_bg1, "lt1",
                         XML_bg2, "lt2",
                         XML_tx1, "dk1",
                         XML_tx2, "dk2",
                         XML_accent1, "accent1",
                         XML_accent2, "accent2",
                         XML_accent3, "accent3",
                         XML_accent4, "accent4",
                         XML_accent5, "accent5",
                         XML_accent6, "accent6",
                         XML_hlink, "hlink",
                         XML_folHlink, "folHlink");

    pFS->endElementNS(XML_p, XML_notesMaster);

    SAL_INFO("sd.eppt", "----------------");

    pFS->endDocument();
}

void PowerPointExport::embedEffectAudio(const FSHelperPtr& pFS, const OUString& sUrl, OUString& sRelId, OUString& sName)
{
    comphelper::LifecycleProxy aProxy;

    if (!sUrl.endsWithIgnoreAsciiCase(".wav"))
        return;

    uno::Reference<io::XInputStream> xAudioStream;
    try
    {
        if (sUrl.startsWith("vnd.sun.star.Package:"))
        {
            uno::Reference<embed::XStorage> xDocumentStorage = mXModel->getDocumentStorage();
            if (!xDocumentStorage.is())
                return;

            uno::Reference<io::XStream> xStream = comphelper::OStorageHelper::GetStreamAtPackageURL(xDocumentStorage, sUrl,
                                                        css::embed::ElementModes::READ, aProxy);

            if (xStream.is())
                xAudioStream = xStream->getInputStream();
        }
        else
            xAudioStream = comphelper::OStorageHelper::GetInputStreamFromURL(sUrl, getComponentContext());
    }
    catch (const Exception&)
    {
        TOOLS_WARN_EXCEPTION("sd", "PowerPointExport::embedEffectAudio");
    }

    if (!xAudioStream.is())
        return;

    sName = INetURLObject::decode(sUrl, INetURLObject::DecodeMechanism::ToIUri, RTL_TEXTENCODING_UTF8);
    int nLastSlash = sName.lastIndexOf('/');
    sName = sName.copy(nLastSlash >= 0 ? nLastSlash + 1 : 0);

    // MS PowerPoint reports a corrupt file if the media name contains non-ascii characters
    OUString sAsciiName;
    if (comphelper::string::isValidAsciiFilename(sName))
        sAsciiName = sName;
    else
    {
        // create an ASCII name - using a hash to try and keep it unique and yet non-random
        comphelper::Hash aHash(comphelper::HashType::MD5);
        sal_Int32 nBytesToRead = std::clamp<sal_Int32>(xAudioStream->available(), 0, 32000);
        uno::Sequence<sal_Int8> aTempBuf(nBytesToRead);
        if ((nBytesToRead = xAudioStream->readBytes(aTempBuf, nBytesToRead)))
            aHash.update(aTempBuf.getConstArray(), nBytesToRead);
        else // safety fallback: use the name to create a hash: should never happen
        {
            const OString sUtf(OUStringToOString(sName, RTL_TEXTENCODING_UTF8));
            aHash.update(sUtf.getStr(), sUtf.getLength());
        }
        sAsciiName
            = "audio_" + OUString::fromUtf8(comphelper::hashToString(aHash.finalize())) + ".wav";
    }

    OUString sPath = "../media/" + sAsciiName;
    sRelId = addRelation(pFS->getOutputStream(),
                        oox::getRelationship(Relationship::AUDIO), sPath);

    uno::Reference<io::XOutputStream> xOutputStream = openFragmentStream(sPath.replaceAt(0, 2, u"/ppt"),
            u"audio/x-wav"_ustr);

    comphelper::OStorageHelper::CopyInputToOutput(xAudioStream, xOutputStream);
}

sal_Int32 PowerPointExport::GetShapeID(const Reference<XShape>& rXShape)
{
    return ShapeExport::GetShapeID(rXShape, &maShapeMap);
}

sal_Int32 PowerPointExport::GetNextAnimationNodeID()
{
    return mnAnimationNodeIdMax++;
}

bool PowerPointExport::ImplCreateMainNotes()
{
    if (mbCreateNotes)
        WriteNotesMaster();

    return true;
}

OUString PowerPointExport::getImplementationName()
{
    return u"com.sun.star.comp.Impress.oox.PowerPointExport"_ustr;
}

void PowerPointExport::WriteDiagram(const FSHelperPtr& pFS, PowerPointShapeExport& rDML,
                                    const css::uno::Reference<css::drawing::XShape>& rXShape,
                                    sal_Int32 nDiagramId)
{
    sal_Int32 nShapeId = rDML.GetNewShapeID(rXShape);
    SAL_INFO("sd.eppt", "writing Diagram " + OUString::number(nDiagramId) + " with Shape Id "
                            + OUString::number(nShapeId));
    pFS->startElementNS(XML_p, XML_graphicFrame);
    rDML.WriteDiagram(rXShape, nDiagramId, nShapeId);
    pFS->endElementNS(XML_p, XML_graphicFrame);
}

void PowerPointExport::WritePlaceholderReferenceShapes(PowerPointShapeExport& rDML, PageType ePageType)
{
    bool bCheckProps = ePageType == NORMAL;
    Reference<XShape> xShape;
    Any aAny;
    OUString aText;
    if (ePageType == LAYOUT
        || (bCheckProps && PropValue::GetPropertyValue(aAny, mXPagePropSet, u"IsFooterVisible"_ustr, true)
            && aAny == true && GetPropertyValue(aAny, mXPagePropSet, u"FooterText"_ustr, true)
            && (aAny >>= aText) && !aText.isEmpty()))
    {
        if ((xShape = GetReferencedPlaceholderXShape(Footer, ePageType)))
        {
            const auto iter = maPlaceholderShapeToIndexMap.find(xShape);
            assert(iter != maPlaceholderShapeToIndexMap.end());
            rDML.WritePlaceholderReferenceShape(Footer,
                                                iter->second,
                                                ePageType, mXPagePropSet);
        }
    }

    if (ePageType == LAYOUT
        || (bCheckProps
            && PropValue::GetPropertyValue(aAny, mXPagePropSet, u"IsPageNumberVisible"_ustr, true)
            && aAny == true))
    {
        if ((xShape = GetReferencedPlaceholderXShape(SlideNumber, ePageType)))
        {
            const auto iter = maPlaceholderShapeToIndexMap.find(xShape);
            assert(iter != maPlaceholderShapeToIndexMap.end());
            rDML.WritePlaceholderReferenceShape(SlideNumber,
                                                iter->second,
                                                ePageType, mXPagePropSet);
        }
    }

    if (ePageType == LAYOUT
        || (bCheckProps
            && PropValue::GetPropertyValue(aAny, mXPagePropSet, u"IsDateTimeVisible"_ustr, true)
            && aAny == true
            && ((GetPropertyValue(aAny, mXPagePropSet, u"DateTimeText"_ustr, true) && (aAny >>= aText)
                 && !aText.isEmpty())
                || mXPagePropSet->getPropertyValue(u"IsDateTimeFixed"_ustr) == false)))
    {
        if ((xShape = GetReferencedPlaceholderXShape(DateAndTime, ePageType)))
        {
            const auto iter = maPlaceholderShapeToIndexMap.find(xShape);
            assert(iter != maPlaceholderShapeToIndexMap.end());
            rDML.WritePlaceholderReferenceShape(DateAndTime,
                                                iter->second,
                                                ePageType, mXPagePropSet);
        }
    }
}

sal_Int32 PowerPointExport::CreateNewPlaceholderIndex(const css::uno::Reference<XShape> &rXShape)
{
    maPlaceholderShapeToIndexMap.insert({rXShape, mnPlaceholderIndexMax});
    return mnPlaceholderIndexMax++;
}

Reference<XShape> PowerPointExport::GetReferencedPlaceholderXShape(const PlaceholderType eType,
                                                        PageType ePageType) const
{
    PresObjKind ePresObjKind = PresObjKind::NONE;
    switch (eType)
    {
        case oox::core::None:
            break;
        case oox::core::SlideImage:
            break;
        case oox::core::Notes:
            break;
        case oox::core::Header:
            ePresObjKind = PresObjKind::Header;
            break;
        case oox::core::Footer:
            ePresObjKind = PresObjKind::Footer;
            break;
        case oox::core::SlideNumber:
            ePresObjKind = PresObjKind::SlideNumber;
            break;
        case oox::core::DateAndTime:
            ePresObjKind = PresObjKind::DateTime;
            break;
        case oox::core::Outliner:
            break;
        case oox::core::Title:
            ePresObjKind = PresObjKind::Title;
            break;
        case oox::core::Subtitle:
            break;
    }
    if (ePresObjKind != PresObjKind::NONE)
    {
        SdrPage* pPage;
        if (ePageType == LAYOUT)
        {
            // since Layout pages do not have drawpages themselves - mXDrawPage is still the master they reference to..
            pPage = SdPage::getImplementation(mXDrawPage);
        }
        else
        {
            pPage = &SdPage::getImplementation(mXDrawPage)->TRG_GetMasterPage();
        }
        SdPage* pMasterPage = dynamic_cast<SdPage*>(pPage);
        if (SdrObject* pMasterFooter
            = (pMasterPage ? pMasterPage->GetPresObj(ePresObjKind) : nullptr))
            return GetXShapeForSdrObject(pMasterFooter);
    }
    return nullptr;
}

// UNO component
extern "C" SAL_DLLPUBLIC_EXPORT uno::XInterface*
css_comp_Impress_oox_PowerPointExport(uno::XComponentContext* rxCtxt,
                                      uno::Sequence<css::uno::Any> const& rArguments)
{
    return cppu::acquire(new PowerPointExport(rxCtxt, rArguments));
}

#if OSL_DEBUG_LEVEL > 1
void dump_pset(Reference< XPropertySet > const& rXPropSet)
{
    Reference< XPropertySetInfo > info = rXPropSet->getPropertySetInfo();
    Sequence< beans::Property > props = info->getProperties();

    for (int i=0; i < props.getLength(); i++)
    {
        OString name = OUStringToOString(props [i].Name, RTL_TEXTENCODING_UTF8);

        Any value = rXPropSet->getPropertyValue(props [i].Name);

        OUString strValue;
        sal_Int32 intValue;
        bool boolValue;
        RectanglePoint pointValue;

        if (value >>= strValue)
            SAL_INFO("sd.eppt", name << " = \"" << strValue << "\"");
        else if (value >>= intValue)
            SAL_INFO("sd.eppt", name << " = " << intValue << "(hex : " << std::hex << intValue << ")");
        else if (value >>= boolValue)
            SAL_INFO("sd.eppt", name << " = " << boolValue << "           (bool)");
        else if (value >>= pointValue)
            SAL_INFO("sd.eppt", name << " = " << static_cast<int>(pointValue) << "    (RectanglePoint)");
        else
            SAL_INFO("sd.eppt", "???          <unhandled type>");
    }
}
#endif

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