/* -*- 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 <config_wasm_strip.h>

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

#include <filter/msfilter/util.hxx>
#include <o3tl/string_view.hxx>
#include <o3tl/any.hxx>
#include <oox/core/xmlfilterbase.hxx>
#include <oox/export/shapes.hxx>
#include <oox/export/utils.hxx>
#include <oox/token/namespaces.hxx>
#include <oox/token/relationship.hxx>
#include <oox/token/tokens.hxx>

#include <initializer_list>
#include <string_view>

#include <com/sun/star/beans/PropertyValues.hpp>
#include <com/sun/star/beans/XPropertySet.hpp>
#include <com/sun/star/beans/XPropertySetInfo.hpp>
#include <com/sun/star/beans/XPropertyState.hpp>
#include <com/sun/star/container/XChild.hpp>
#include <com/sun/star/document/XExporter.hpp>
#include <com/sun/star/document/XStorageBasedDocument.hpp>
#include <com/sun/star/drawing/CircleKind.hpp>
#include <com/sun/star/drawing/FillStyle.hpp>
#include <com/sun/star/drawing/ConnectorType.hpp>
#include <com/sun/star/drawing/EnhancedCustomShapeParameterPair.hpp>
#include <com/sun/star/drawing/EnhancedCustomShapeParameterType.hpp>
#include <com/sun/star/embed/Aspects.hpp>
#include <com/sun/star/embed/EmbedStates.hpp>
#include <com/sun/star/embed/XEmbeddedObject.hpp>
#include <com/sun/star/embed/XEmbedPersist.hpp>
#include <com/sun/star/embed/WrongStateException.hpp>
#include <com/sun/star/frame/XStorable.hpp>
#include <com/sun/star/graphic/XGraphic.hpp>
#include <com/sun/star/i18n/ScriptType.hpp>
#include <com/sun/star/io/XOutputStream.hpp>
#include <com/sun/star/text/XSimpleText.hpp>
#include <com/sun/star/text/XText.hpp>
#include <com/sun/star/table/XTable.hpp>
#include <com/sun/star/table/XMergeableCell.hpp>
#include <com/sun/star/chart2/XChartDocument.hpp>
#include <com/sun/star/frame/XModel.hpp>
#include <com/sun/star/uno/XComponentContext.hpp>
#include <com/sun/star/drawing/XDrawPages.hpp>
#include <com/sun/star/drawing/XDrawPagesSupplier.hpp>
#include <com/sun/star/presentation/ClickAction.hpp>
#include <com/sun/star/drawing/XGluePointsSupplier.hpp>
#include <com/sun/star/container/XIdentifierAccess.hpp>
#include <com/sun/star/table/BorderLineStyle.hpp>
#include <tools/globname.hxx>
#include <comphelper/classids.hxx>
#include <comphelper/propertysequence.hxx>
#include <comphelper/storagehelper.hxx>
#include <comphelper/memorystream.hxx>
#include <sot/exchange.hxx>
#include <utility>
#include <vcl/graph.hxx>
#include <vcl/outdev.hxx>
#include <filter/msfilter/escherex.hxx>
#include <svtools/embedhlp.hxx>
#include <svx/svdoashp.hxx>
#include <svx/svdoole2.hxx>
#include <comphelper/diagnose_ex.hxx>
#include <oox/export/chartexport.hxx>
#include <oox/mathml/imexport.hxx>
#include <basegfx/numeric/ftools.hxx>
#include <oox/export/DMLPresetShapeExport.hxx>

#include <frozen/bits/defines.h>
#include <frozen/bits/elsa_std.h>
#include <frozen/set.h>
#include <frozen/unordered_map.h>

#include <sax/fastattribs.hxx>

using namespace ::css;
using namespace ::css::beans;
using namespace ::css::uno;
using namespace ::css::drawing;
using namespace ::css::table;
using namespace ::css::container;
using namespace ::css::document;
using namespace ::css::text;

using ::css::io::XOutputStream;
using ::css::chart2::XChartDocument;
using ::css::frame::XModel;

using ::oox::core::XmlFilterBase;
using ::sax_fastparser::FSHelperPtr;


namespace oox {

static void lcl_ConvertProgID(std::u16string_view rProgID,
    OUString & o_rMediaType, OUString & o_rRelationType, OUString & o_rFileExtension)
{
    if (rProgID == u"Excel.Sheet.12")
    {
        o_rMediaType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
        o_rRelationType = oox::getRelationship(Relationship::PACKAGE);
        o_rFileExtension = "xlsx";
    }
    else if (o3tl::starts_with(rProgID, u"Excel.SheetBinaryMacroEnabled.12") )
    {
        o_rMediaType = "application/vnd.ms-excel.sheet.binary.macroEnabled.12";
        o_rRelationType = oox::getRelationship(Relationship::PACKAGE);
        o_rFileExtension = "xlsb";
    }
    else if (o3tl::starts_with(rProgID, u"Excel.SheetMacroEnabled.12"))
    {
        o_rMediaType = "application/vnd.ms-excel.sheet.macroEnabled.12";
        o_rRelationType = oox::getRelationship(Relationship::PACKAGE);
        o_rFileExtension = "xlsm";
    }
    else if (o3tl::starts_with(rProgID, u"Excel.Sheet"))
    {
        o_rMediaType = "application/vnd.ms-excel";
        o_rRelationType = oox::getRelationship(Relationship::OLEOBJECT);
        o_rFileExtension = "xls";
    }
    else if (rProgID == u"PowerPoint.Show.12")
    {
        o_rMediaType = "application/vnd.openxmlformats-officedocument.presentationml.presentation";
        o_rRelationType = oox::getRelationship(Relationship::PACKAGE);
        o_rFileExtension = "pptx";
    }
    else if (rProgID == u"PowerPoint.ShowMacroEnabled.12")
    {
        o_rMediaType = "application/vnd.ms-powerpoint.presentation.macroEnabled.12";
        o_rRelationType = oox::getRelationship(Relationship::PACKAGE);
        o_rFileExtension = "pptm";
    }
    else if (o3tl::starts_with(rProgID, u"PowerPoint.Show"))
    {
        o_rMediaType = "application/vnd.ms-powerpoint";
        o_rRelationType = oox::getRelationship(Relationship::OLEOBJECT);
        o_rFileExtension = "ppt";
    }
    else if (o3tl::starts_with(rProgID, u"PowerPoint.Slide.12"))
    {
       o_rMediaType = "application/vnd.openxmlformats-officedocument.presentationml.slide";
       o_rRelationType = oox::getRelationship(Relationship::PACKAGE);
       o_rFileExtension = "sldx";
    }
    else if (rProgID == u"PowerPoint.SlideMacroEnabled.12")
    {
       o_rMediaType = "application/vnd.ms-powerpoint.slide.macroEnabled.12";
       o_rRelationType = oox::getRelationship(Relationship::PACKAGE);
       o_rFileExtension = "sldm";
    }
    else if (rProgID == u"Word.DocumentMacroEnabled.12")
    {
        o_rMediaType = "application/vnd.ms-word.document.macroEnabled.12";
        o_rRelationType = oox::getRelationship(Relationship::PACKAGE);
        o_rFileExtension = "docm";
    }
    else if (rProgID == u"Word.Document.12")
    {
        o_rMediaType = "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
        o_rRelationType = oox::getRelationship(Relationship::PACKAGE);
        o_rFileExtension = "docx";
    }
    else if (rProgID == u"Word.Document.8")
    {
        o_rMediaType = "application/msword";
        o_rRelationType = oox::getRelationship(Relationship::OLEOBJECT);
        o_rFileExtension = "doc";
    }
    else if (rProgID == u"Excel.Chart.8")
    {
        o_rMediaType = "application/vnd.ms-excel";
        o_rRelationType = oox::getRelationship(Relationship::OLEOBJECT);
        o_rFileExtension = "xls";
    }
    else if (rProgID == u"AcroExch.Document.11")
    {
        o_rMediaType = "application/pdf";
        o_rRelationType = oox::getRelationship(Relationship::OLEOBJECT);
        o_rFileExtension = "pdf";
    }
    else
    {
        o_rMediaType = "application/vnd.openxmlformats-officedocument.oleObject";
        o_rRelationType = oox::getRelationship(Relationship::OLEOBJECT);
        o_rFileExtension = "bin";
    }
}

static uno::Reference<io::XInputStream> lcl_StoreOwnAsOOXML(
    uno::Reference<embed::XEmbeddedObject> const& xObj,
    char const*& o_rpProgID,
    OUString & o_rMediaType, OUString & o_rRelationType, OUString & o_rSuffix)
{
    static struct {
        struct {
            sal_uInt32 n1;
            sal_uInt16 n2, n3;
            sal_uInt8 b8, b9, b10, b11, b12, b13, b14, b15;
        } ClassId;
        char const* pFilterName;
        char const* pMediaType;
        char const* pProgID;
        char const* pSuffix;
    } const s_Mapping[] = {
        { {SO3_SW_CLASSID_60}, "MS Word 2007 XML", "application/vnd.openxmlformats-officedocument.wordprocessingml.document", "Word.Document.12", "docx" },
        { {SO3_SC_CLASSID_60}, "Calc MS Excel 2007 XML", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "Excel.Sheet.12", "xlsx" },
        { {SO3_SIMPRESS_CLASSID_60}, "Impress MS PowerPoint 2007 XML", "application/vnd.openxmlformats-officedocument.presentationml.presentation", "PowerPoint.Show.12", "pptx" },
        { {SO3_SDRAW_CLASSID}, "draw8", "application/vnd.oasis.opendocument.graphics", "LibreOffice.DrawDocument.1", "odg" },
        { {SO3_SCH_CLASSID_60}, "unused", "", "", "" },
        { {SO3_SM_CLASSID_60}, "unused", "", "", "" },
    };

    const char * pFilterName(nullptr);
    SvGlobalName const classId(xObj->getClassID());
    for (auto & i : s_Mapping)
    {
        auto const& rId(i.ClassId);
        SvGlobalName const temp(rId.n1, rId.n2, rId.n3, rId.b8, rId.b9, rId.b10, rId.b11, rId.b12, rId.b13, rId.b14, rId.b15);
        if (temp == classId)
        {
            assert(SvGlobalName(SO3_SCH_CLASSID_60) != classId); // chart should be written elsewhere!
            assert(SvGlobalName(SO3_SM_CLASSID_60) != classId); // formula should be written elsewhere!
            pFilterName = i.pFilterName;
            o_rMediaType = OUString::createFromAscii(i.pMediaType);
            o_rpProgID = i.pProgID;
            o_rSuffix = OUString::createFromAscii(i.pSuffix);
            o_rRelationType = oox::getRelationship(Relationship::PACKAGE);
            break;
        }
    }

    if (!pFilterName)
    {
        SAL_WARN("oox.shape", "oox::GetOLEObjectStream: unknown ClassId " << classId.GetHexName());
        return nullptr;
    }

    if (embed::EmbedStates::LOADED == xObj->getCurrentState())
    {
        xObj->changeState(embed::EmbedStates::RUNNING);
    }
    // use a temp stream - while it would work to store directly to a
    // fragment stream, an error during export means we'd have to delete it
    rtl::Reference< comphelper::UNOMemoryStream > xTempStream = new comphelper::UNOMemoryStream();
    uno::Sequence<beans::PropertyValue> args( comphelper::InitPropertySequence({
            { "OutputStream", Any(xTempStream->getOutputStream()) },
            { "FilterName", Any(OUString::createFromAscii(pFilterName)) }
        }));
    uno::Reference<frame::XStorable> xStorable(xObj->getComponent(), uno::UNO_QUERY);
    try
    {
        xStorable->storeToURL(u"private:stream"_ustr, args);
    }
    catch (uno::Exception const&)
    {
        TOOLS_WARN_EXCEPTION("oox.shape", "oox::GetOLEObjectStream");
        return nullptr;
    }
    xTempStream->getOutputStream()->closeOutput();
    return xTempStream->getInputStream();
}

uno::Reference<io::XInputStream> GetOLEObjectStream(
        uno::Reference<embed::XEmbeddedObject> const& xObj,
        std::u16string_view i_rProgID,
        OUString & o_rMediaType,
        OUString & o_rRelationType,
        OUString & o_rSuffix,
        const char *& o_rpProgID)
{
    uno::Reference<io::XInputStream> xInStream;
    try
    {
        uno::Reference<document::XStorageBasedDocument> const xParent(
            uno::Reference<container::XChild>(xObj, uno::UNO_QUERY_THROW)->getParent(),
            uno::UNO_QUERY_THROW);
        uno::Reference<embed::XStorage> const xParentStorage(xParent->getDocumentStorage());
        OUString const entryName(
            uno::Reference<embed::XEmbedPersist>(xObj, uno::UNO_QUERY_THROW)->getEntryName());

        if (xParentStorage->isStreamElement(entryName))
        {
            lcl_ConvertProgID(i_rProgID, o_rMediaType, o_rRelationType, o_rSuffix);
            xInStream = xParentStorage->cloneStreamElement(entryName)->getInputStream();
            // TODO: make it possible to take the sMediaType from the stream
        }
        else // the object is ODF - either the whole document is
        {    // ODF, or the OLE was edited so it was converted to ODF
            xInStream = lcl_StoreOwnAsOOXML(xObj,
                    o_rpProgID, o_rMediaType, o_rRelationType, o_rSuffix);
        }
    }
    catch (uno::Exception const&)
    {
        TOOLS_WARN_EXCEPTION("oox.shape", "oox::GetOLEObjectStream");
    }
    return xInStream;
}

} // namespace oox

namespace oox::drawingml {

ShapeExport::ShapeExport( sal_Int32 nXmlNamespace, FSHelperPtr pFS, ShapeHashMap* pShapeMap, XmlFilterBase* pFB, DocumentType eDocumentType, DMLTextExport* pTextExport, bool bUserShapes )
    : DrawingML( std::move(pFS), pFB, eDocumentType, pTextExport )
    , m_nEmbeddedObjects(0)
    , mnShapeIdMax( 1 )
    , mbUserShapes( bUserShapes )
    , mnXmlNamespace( nXmlNamespace )
    , maMapModeSrc( MapUnit::Map100thMM )
    , maMapModeDest( MapUnit::MapInch, Point(), Fraction( 1, 576 ), Fraction( 1, 576 ) )
    , mpShapeMap( pShapeMap ? pShapeMap : &maShapeMap )
{
    mpURLTransformer = std::make_shared<URLTransformer>();
}

void ShapeExport::SetURLTranslator(const std::shared_ptr<URLTransformer>& pTransformer)
{
    mpURLTransformer = pTransformer;
}

awt::Size ShapeExport::MapSize( const awt::Size& rSize ) const
{
    Size aRetSize( OutputDevice::LogicToLogic( Size( rSize.Width, rSize.Height ), maMapModeSrc, maMapModeDest ) );

    if ( !aRetSize.Width() )
        aRetSize.AdjustWidth( 1 );
    if ( !aRetSize.Height() )
        aRetSize.AdjustHeight( 1 );
    return awt::Size( aRetSize.Width(), aRetSize.Height() );
}

static bool IsNonEmptySimpleText(const Reference<XInterface>& xIface)
{
    if (Reference<XSimpleText> xText{ xIface, UNO_QUERY })
        return xText->getString().getLength();

    return false;
}

bool ShapeExport::NonEmptyText( const Reference< XInterface >& xIface )
{
    Reference< XPropertySet > xPropSet( xIface, UNO_QUERY );

    if( xPropSet.is() )
    {
        Reference< XPropertySetInfo > xPropSetInfo = xPropSet->getPropertySetInfo();
        if ( xPropSetInfo.is() )
        {
            if ( xPropSetInfo->hasPropertyByName( u"IsEmptyPresentationObject"_ustr ) )
            {
                bool bIsEmptyPresObj = false;
                if ( xPropSet->getPropertyValue( u"IsEmptyPresentationObject"_ustr ) >>= bIsEmptyPresObj )
                {
                    SAL_INFO("oox.shape", "empty presentation object " << bIsEmptyPresObj << " , props:");
                    if( bIsEmptyPresObj )
                       return true;
                }
            }

            if ( xPropSetInfo->hasPropertyByName( u"IsPresentationObject"_ustr ) )
            {
                bool bIsPresObj = false;
                if ( xPropSet->getPropertyValue( u"IsPresentationObject"_ustr ) >>= bIsPresObj )
                {
                    SAL_INFO("oox.shape", "presentation object " << bIsPresObj << ", props:");
                    if( bIsPresObj )
                       return true;
                }
            }
        }
    }

    return IsNonEmptySimpleText(xIface);
}

static void AddExtLst(FSHelperPtr const& pFS, Reference<XPropertySet> const& xShape)
{
    if (xShape->getPropertySetInfo()->hasPropertyByName(u"Decorative"_ustr)
        && xShape->getPropertyValue(u"Decorative"_ustr).get<bool>())
    {
        pFS->startElementNS(XML_a, XML_extLst);
//            FSNS(XML_xmlns, XML_a), GetExport().GetFilter().getNamespaceURL(OOX_NS(dml)));
        pFS->startElementNS(XML_a, XML_ext,
            // MSO uses this "URI" which is obviously not a URI
            XML_uri, "{C183D7F6-B498-43B3-948B-1728B52AA6E4}");
        pFS->singleElementNS(XML_adec, XML_decorative,
            FSNS(XML_xmlns, XML_adec), "http://schemas.microsoft.com/office/drawing/2017/decorative",
            XML_val, "1");
        pFS->endElementNS(XML_a, XML_ext);
        pFS->endElementNS(XML_a, XML_extLst);
    }
}

ShapeExport& ShapeExport::WritePolyPolygonShape( const Reference< XShape >& xShape, const bool bClosed )
{
    SAL_INFO("oox.shape", "write polypolygon shape");

    FSHelperPtr pFS = GetFS();
    pFS->startElementNS(mnXmlNamespace, (GetDocumentType() != DOCUMENT_DOCX || mbUserShapes ? XML_sp : XML_wsp));

    awt::Point aPos = xShape->getPosition();
    // Position is relative to group for child elements in Word, but absolute in API.
    if (GetDocumentType() == DOCUMENT_DOCX && !mbUserShapes && m_xParent.is())
    {
        awt::Point aParentPos = m_xParent->getPosition();
        aPos.X -= aParentPos.X;
        aPos.Y -= aParentPos.Y;
    }
    awt::Size aSize = xShape->getSize();
    tools::Rectangle aRect(Point(aPos.X, aPos.Y), Size(aSize.Width, aSize.Height));

#if OSL_DEBUG_LEVEL > 0
    tools::PolyPolygon aPolyPolygon = EscherPropertyContainer::GetPolyPolygon(xShape);
    awt::Size size = MapSize( awt::Size( aRect.GetWidth(), aRect.GetHeight() ) );
    SAL_INFO("oox.shape", "poly count " << aPolyPolygon.Count());
    SAL_INFO("oox.shape", "size: " << size.Width << " x " << size.Height);
#endif

    Reference<XPropertySet> const xProps(xShape, UNO_QUERY);
    // non visual shape properties
    if (GetDocumentType() != DOCUMENT_DOCX || mbUserShapes)
    {
        pFS->startElementNS(mnXmlNamespace, XML_nvSpPr);
        pFS->startElementNS(mnXmlNamespace, XML_cNvPr,
                              XML_id, OString::number(GetNewShapeID(xShape)),
                              XML_name, GetShapeName(xShape));
        AddExtLst(pFS, xProps);
        pFS->endElementNS(mnXmlNamespace, XML_cNvPr);
    }
    pFS->singleElementNS(mnXmlNamespace, XML_cNvSpPr);
    if (GetDocumentType() != DOCUMENT_DOCX || mbUserShapes)
    {
        WriteNonVisualProperties( xShape );
        pFS->endElementNS( mnXmlNamespace, XML_nvSpPr );
    }

    // visual shape properties
    pFS->startElementNS(mnXmlNamespace, XML_spPr);
    WriteTransformation( xShape, aRect, XML_a );
    WritePolyPolygon(xShape, bClosed);
    if( xProps.is() ) {
        if( bClosed )
            WriteFill(xProps, aSize);
        WriteOutline( xProps );
    }

    pFS->endElementNS( mnXmlNamespace, XML_spPr );

    // write text
    WriteTextBox( xShape, mnXmlNamespace );

    pFS->endElementNS( mnXmlNamespace, (GetDocumentType() != DOCUMENT_DOCX || mbUserShapes ? XML_sp : XML_wsp) );

    return *this;
}

ShapeExport& ShapeExport::WriteClosedPolyPolygonShape( const Reference< XShape >& xShape )
{
    return WritePolyPolygonShape( xShape, true );
}

ShapeExport& ShapeExport::WriteOpenPolyPolygonShape( const Reference< XShape >& xShape )
{
    return WritePolyPolygonShape( xShape, false );
}

ShapeExport& ShapeExport::WriteGroupShape(const uno::Reference<drawing::XShape>& xShape)
{
    FSHelperPtr pFS = GetFS();

    sal_Int32 nGroupShapeToken = XML_grpSp;
    if (GetDocumentType() == DOCUMENT_DOCX && !mbUserShapes)
    {
        if (!m_xParent.is())
            nGroupShapeToken = XML_wgp; // toplevel
        else
            mnXmlNamespace = XML_wpg;
    }

    pFS->startElementNS(mnXmlNamespace, nGroupShapeToken);

    // non visual properties
    if (GetDocumentType() != DOCUMENT_DOCX || mbUserShapes)
    {
        pFS->startElementNS(mnXmlNamespace, XML_nvGrpSpPr);
        pFS->startElementNS(mnXmlNamespace, XML_cNvPr,
                XML_id, OString::number(GetNewShapeID(xShape)),
                XML_name, GetShapeName(xShape));
        uno::Reference<beans::XPropertySet> const xShapeProps(xShape, uno::UNO_QUERY_THROW);
        AddExtLst(pFS, xShapeProps);
        pFS->endElementNS(mnXmlNamespace, XML_cNvPr);
        pFS->singleElementNS(mnXmlNamespace, XML_cNvGrpSpPr);
        WriteNonVisualProperties(xShape );
        pFS->endElementNS(mnXmlNamespace, XML_nvGrpSpPr);
    }
    else
        pFS->singleElementNS(mnXmlNamespace, XML_cNvGrpSpPr);

    // visual properties
    pFS->startElementNS(mnXmlNamespace, XML_grpSpPr);
    WriteShapeTransformation(xShape, XML_a, false, false, true);
    pFS->endElementNS(mnXmlNamespace, XML_grpSpPr);

    uno::Reference<drawing::XShapes> xGroupShape(xShape, uno::UNO_QUERY_THROW);
    uno::Reference<drawing::XShape> xParent = m_xParent;
    m_xParent = xShape;
    for (sal_Int32 i = 0; i < xGroupShape->getCount(); ++i)
    {
        uno::Reference<drawing::XShape> xChild(xGroupShape->getByIndex(i), uno::UNO_QUERY_THROW);
        sal_Int32 nSavedNamespace = mnXmlNamespace;

        uno::Reference<lang::XServiceInfo> xServiceInfo(xChild, uno::UNO_QUERY_THROW);
        if (GetDocumentType() == DOCUMENT_DOCX && !mbUserShapes)
        {
            // tdf#128820: WriteGraphicObjectShapePart calls WriteTextShape for non-empty simple
            // text objects, which needs writing into wps::wsp element, so make sure to use wps
            // namespace for those objects
            if (xServiceInfo->supportsService(u"com.sun.star.drawing.GraphicObjectShape"_ustr)
                && !IsNonEmptySimpleText(xChild))
                mnXmlNamespace = XML_pic;
            else
                mnXmlNamespace = XML_wps;
        }
        WriteShape(xChild);

        mnXmlNamespace = nSavedNamespace;
    }
    m_xParent = std::move(xParent);

    pFS->endElementNS(mnXmlNamespace, nGroupShapeToken);
    return *this;
}
namespace
{

constexpr frozen::set<std::u16string_view, 57> constDenySet(
{
    u"block-arc",
    u"rectangle",
    u"ellipse",
    u"ring",
    u"can",
    u"cube",
    u"paper",
    u"frame",
    u"forbidden",
    u"smiley",
    u"sun",
    u"flower",
    u"bracket-pair",
    u"brace-pair",
    u"quad-bevel",
    u"round-rectangular-callout",
    u"rectangular-callout",
    u"round-callout",
    u"cloud-callout",
    u"line-callout-1",
    u"line-callout-2",
    u"line-callout-3",
    u"paper",
    u"vertical-scroll",
    u"horizontal-scroll",
    u"mso-spt34",
    u"mso-spt75",
    u"mso-spt164",
    u"mso-spt180",
    u"flowchart-process",
    u"flowchart-alternate-process",
    u"flowchart-decision",
    u"flowchart-data",
    u"flowchart-predefined-process",
    u"flowchart-internal-storage",
    u"flowchart-document",
    u"flowchart-multidocument",
    u"flowchart-terminator",
    u"flowchart-preparation",
    u"flowchart-manual-input",
    u"flowchart-manual-operation",
    u"flowchart-connector",
    u"flowchart-off-page-connector",
    u"flowchart-card",
    u"flowchart-punched-tape",
    u"flowchart-summing-junction",
    u"flowchart-or",
    u"flowchart-collate",
    u"flowchart-sort",
    u"flowchart-extract",
    u"flowchart-merge",
    u"flowchart-stored-data",
    u"flowchart-delay",
    u"flowchart-sequential-access",
    u"flowchart-magnetic-disk",
    u"flowchart-direct-access-storage",
    u"flowchart-display"
});

constexpr frozen::set<std::u16string_view, 6> constAllowSet(
{
    u"heart",
    u"puzzle",
    u"col-60da8460",
    u"col-502ad400",
    u"sinusoid",
    u"mso-spt100"
});

} // end anonymous namespace

static bool lcl_IsOnDenylist(OUString const & rShapeType)
{
    return constDenySet.find(rShapeType) != constDenySet.end();
}

static bool lcl_IsOnAllowlist(OUString const & rShapeType)
{
    return constAllowSet.find(rShapeType) != constAllowSet.end();
}

static bool lcl_GetHandlePosition( sal_Int32 &nValue, const EnhancedCustomShapeParameter &rParam, const Sequence< EnhancedCustomShapeAdjustmentValue > &rSeq)
{
    bool bAdj = false;
    if ( rParam.Value.getValueTypeClass() == TypeClass_DOUBLE )
    {
        double fValue(0.0);
        if ( rParam.Value >>= fValue )
            nValue = static_cast<sal_Int32>(fValue);
    }
    else
        rParam.Value >>= nValue;

    if ( rParam.Type == EnhancedCustomShapeParameterType::ADJUSTMENT)
    {
        bAdj = true;
        sal_Int32 nIdx = nValue;
        if ( nIdx < rSeq.getLength() )
        {
            if ( rSeq[ nIdx ] .Value.getValueTypeClass() == TypeClass_DOUBLE )
            {
                double fValue(0.0);
                rSeq[ nIdx ].Value >>= fValue;
                nValue = fValue;

            }
            else
            {
                rSeq[ nIdx ].Value >>= nValue;
            }
        }
    }
    return bAdj;
}

static void lcl_AnalyzeHandles( const uno::Sequence<beans::PropertyValues> & rHandles,
        std::vector< std::pair< sal_Int32, sal_Int32> > &rHandlePositionList,
        const Sequence< EnhancedCustomShapeAdjustmentValue > &rSeq)
{
    for ( const Sequence< PropertyValue >& rPropSeq : rHandles )
    {
        static constexpr OUStringLiteral sPosition( u"Position"  );
        bool bPosition = false;
        EnhancedCustomShapeParameterPair aPosition;
        for ( const PropertyValue& rPropVal: rPropSeq )
        {
            if ( rPropVal.Name == sPosition )
            {
                if ( rPropVal.Value >>= aPosition )
                    bPosition = true;
            }
        }
        if ( bPosition )
        {
            sal_Int32 nXPosition = 0;
            sal_Int32 nYPosition = 0;
            // For polar handles, nXPosition is radius and nYPosition is angle
            lcl_GetHandlePosition( nXPosition, aPosition.First , rSeq );
            lcl_GetHandlePosition( nYPosition, aPosition.Second, rSeq );
            rHandlePositionList.emplace_back( nXPosition, nYPosition );
        }
    }
}

static void lcl_AppendAdjustmentValue( std::vector< std::pair< sal_Int32, sal_Int32> > &rAvList, sal_Int32 nAdjIdx, sal_Int32 nValue )
{
    rAvList.emplace_back( nAdjIdx , nValue );
}

static sal_Int32 lcl_NormalizeAngle( sal_Int32 nAngle )
{
    nAngle = nAngle % 360;
    return nAngle < 0 ? ( nAngle + 360 ) : nAngle ;
}

static sal_Int32 lcl_CircleAngle2CustomShapeEllipseAngleOOX(const sal_Int32 nInternAngle, const sal_Int32 nWidth, const sal_Int32 nHeight)
{
    if (nWidth != 0 || nHeight != 0)
    {
        double fAngle = basegfx::deg2rad<100>(nInternAngle); // intern 1/100 deg to rad
        fAngle = atan2(nHeight * sin(fAngle), nWidth * cos(fAngle)); // circle to ellipse
        fAngle = basegfx::rad2deg<60000>(fAngle); // rad to OOXML angle unit
        sal_Int32 nAngle = basegfx::fround(fAngle); // normalize
        nAngle = nAngle % 21600000;
        return nAngle < 0 ? (nAngle + 21600000) : nAngle;
    }
    else // should be handled by caller, dummy value
        return 0;
}

static OUString lcl_GetTarget(const css::uno::Reference<css::frame::XModel>& xModel,
                              std::u16string_view rURL)
{
    Reference<drawing::XDrawPagesSupplier> xDPS(xModel, uno::UNO_QUERY_THROW);
    Reference<drawing::XDrawPages> xDrawPages(xDPS->getDrawPages(), uno::UNO_SET_THROW);
    sal_uInt32 nPageCount = xDrawPages->getCount();
    OUString sTarget;

    for (sal_uInt32 i = 0; i < nPageCount; ++i)
    {
        Reference<XDrawPage> xDrawPage;
        xDrawPages->getByIndex(i) >>= xDrawPage;
        Reference<container::XNamed> xNamed(xDrawPage, UNO_QUERY);
        if (!xNamed)
            continue;
        OUString sSlideName = "#" + xNamed->getName();
        OUString sApiName = "#page" + OUString::number(i + 1);
        if (rURL == sSlideName || rURL == sApiName)
        {
            sTarget = "slide" + OUString::number(i + 1) + ".xml";
            break;
        }
        else // If URL is linked to a shape, assign it's slide as target
        {
            Reference<XShapes> xShapes = xDrawPage;
            sal_uInt32 nShapes = xShapes->getCount();
            for (sal_uInt32 j = 0; j < nShapes; j++)
            {
                Reference<XShape> xShape;
                xShapes->getByIndex(j) >>= xShape;
                Reference<container::XNamed> xName(xShape, UNO_QUERY);
                if (!xName)
                    continue;
                OUString sShapeName = "#" + xName->getName();
                if (rURL == sShapeName)
                {
                    sTarget = "slide" + OUString::number(i + 1) + ".xml";
                    break;
                }
            }
        }
    }

    return sTarget;
}

ShapeExport& ShapeExport::WriteCustomShape( const Reference< XShape >& xShape )
{
    SAL_INFO("oox.shape", "write custom shape");
    Reference< XPropertySet > rXPropSet( xShape, UNO_QUERY );
    // First check, if this is a Fontwork-shape. For DrawingML, such a shape is a
    // TextBox shape with body property prstTxWarp.
    if (IsFontworkShape(rXPropSet))
    {
        ShapeExport::WriteTextShape(xShape); // qualifier to prevent PowerPointShapeExport
        return *this;
    }

    bool bHasGeometrySeq(false);
    Sequence< PropertyValue > aGeometrySeq;
    OUString sShapeType(u"non-primitive"_ustr); // default in ODF
    if (GetProperty(rXPropSet, u"CustomShapeGeometry"_ustr))
    {
        SAL_INFO("oox.shape", "got custom shape geometry");
        if (mAny >>= aGeometrySeq)
        {
            bHasGeometrySeq = true;
            SAL_INFO("oox.shape", "got custom shape geometry sequence");
            for (const PropertyValue& rProp : aGeometrySeq)
            {
                SAL_INFO("oox.shape", "geometry property: " << rProp.Name);
                if (rProp.Name == "Type")
                    rProp.Value >>= sShapeType;
            }
        }
    }

    bool bPredefinedHandlesUsed = true;
    bool bHasHandles = false;

    ShapeFlag nMirrorFlags = ShapeFlag::NONE;
    MSO_SPT eShapeType = EscherPropertyContainer::GetCustomShapeType( xShape, nMirrorFlags, sShapeType );
    assert(dynamic_cast< SdrObjCustomShape* >(SdrObject::getSdrObjectFromXShape(xShape)) && "Not a SdrObjCustomShape (!)");
    SdrObjCustomShape& rSdrObjCustomShape(static_cast< SdrObjCustomShape& >(*SdrObject::getSdrObjectFromXShape(xShape)));
    const bool bIsDefaultObject(
        EscherPropertyContainer::IsDefaultObject(
            rSdrObjCustomShape,
            eShapeType));
    OString sPresetShape = msfilter::util::GetOOXMLPresetGeometry(sShapeType);
    SAL_INFO("oox.shape", "custom shape type: " << sShapeType << " ==> " << sPresetShape);

    sal_Int32 nAdjustmentValuesIndex = -1;
    awt::Rectangle aViewBox;
    uno::Sequence<beans::PropertyValues> aHandles;

    bool bFlipH = false;
    bool bFlipV = false;

    if (bHasGeometrySeq)
    {
        for (int i = 0; i < aGeometrySeq.getLength(); i++)
        {
                const PropertyValue& rProp = aGeometrySeq[ i ];
                SAL_INFO("oox.shape", "geometry property: " << rProp.Name);

                if ( rProp.Name == "MirroredX" )
                    rProp.Value >>= bFlipH;

                if ( rProp.Name == "MirroredY" )
                    rProp.Value >>= bFlipV;
                if ( rProp.Name == "AdjustmentValues" )
                    nAdjustmentValuesIndex = i;
                else if ( rProp.Name == "Handles" )
                {
                    rProp.Value >>= aHandles;
                    if ( aHandles.hasElements() )
                        bHasHandles = true;
                    if( !bIsDefaultObject )
                        bPredefinedHandlesUsed = false;
                    // TODO: update nAdjustmentsWhichNeedsToBeConverted here
                }
                else if ( rProp.Name == "ViewBox" )
                    rProp.Value >>= aViewBox;
        }
    }

    FSHelperPtr pFS = GetFS();
    // non visual shape properties
    if (GetDocumentType() != DOCUMENT_DOCX || mbUserShapes)
    {
        // get InteropGrabBag to export attributes stored in the grabbag
        uno::Sequence<beans::PropertyValue> aGrabBagProps;
        rXPropSet->getPropertyValue(u"InteropGrabBag"_ustr) >>= aGrabBagProps;

        bool bUseBackground = false;
        if (GetProperty(rXPropSet, u"FillUseSlideBackground"_ustr))
            mAny >>= bUseBackground;
        if (bUseBackground)
            mpFS->startElementNS(mnXmlNamespace, XML_sp, XML_useBgFill, "1");
        else
        {
            rtl::Reference<sax_fastparser::FastAttributeList> pAttrListSp
                = sax_fastparser::FastSerializerHelper::createAttrList();

            for (auto const& it : aGrabBagProps)
            {
                // export macro attribute of <sp> element
                if (it.Name == u"mso-sp-macro"_ustr)
                {
                    OUString sMacro;
                    it.Value >>= sMacro;

                    if (!sMacro.isEmpty())
                        pAttrListSp->add(XML_macro, sMacro);
                }

                // export textlink attribute of <sp> element
                if (it.Name == u"mso-sp-textlink"_ustr)
                {
                    OUString sTextLink;
                    it.Value >>= sTextLink;

                    if (!sTextLink.isEmpty())
                        pAttrListSp->add(XML_textlink, sTextLink);
                }

                // export fLocksText attribute of <sp> element
                if (it.Name == u"mso-sp-fLocksText"_ustr)
                {
                    bool bFLocksText = true; // default="true"
                    it.Value >>= bFLocksText;
                    pAttrListSp->add(XML_fLocksText, ToPsz10(bFLocksText));
                }

                // export fPublished attribute of <sp> element
                if (it.Name == u"mso-sp-fPublished"_ustr)
                {
                    bool bFPublished = false;
                    it.Value >>= bFPublished;
                    pAttrListSp->add(XML_fPublished, ToPsz10(bFPublished));
                }
            }

            // export <sp> element (with a namespace prefix)
            mpFS->startElementNS(mnXmlNamespace, XML_sp, pAttrListSp);
        }

        bool isVisible = true ;
        if( GetProperty(rXPropSet, u"Visible"_ustr))
        {
            mAny >>= isVisible;
        }
        pFS->startElementNS( mnXmlNamespace, XML_nvSpPr );

        // export descr attribute of <cNvPr> element
        OUString sDescr;
        if (GetProperty(rXPropSet, u"Description"_ustr))
            mAny >>= sDescr;

        // export title attribute of <cNvPr> element
        OUString sTitle;
        if (GetProperty(rXPropSet, u"Title"_ustr))
            mAny >>= sTitle;

        // export <cNvPr> element
        pFS->startElementNS(
            mnXmlNamespace, XML_cNvPr, XML_id,
            OString::number(GetShapeID(xShape) == -1 ? GetNewShapeID(xShape) : GetShapeID(xShape)),
            XML_name, GetShapeName(xShape), XML_hidden, sax_fastparser::UseIf("1", !isVisible),
            XML_descr, sax_fastparser::UseIf(sDescr, !sDescr.isEmpty()), XML_title,
            sax_fastparser::UseIf(sTitle, !sTitle.isEmpty()));

        rtl::Reference<sax_fastparser::FastAttributeList> pAttrListHlinkClick
            = sax_fastparser::FastSerializerHelper::createAttrList();

        for (auto const& it : aGrabBagProps)
        {
            // export tooltip attribute of <hlinkClick> element
            if (it.Name == u"mso-hlinkClick-tooltip"_ustr)
            {
                OUString sTooltip;
                it.Value >>= sTooltip;

                if (!sTooltip.isEmpty())
                    pAttrListHlinkClick->add(XML_tooltip, sTooltip);
            }
        }

        if( GetProperty(rXPropSet, u"URL"_ustr) )
        {
            OUString sURL;
            mAny >>= sURL;
            if( !sURL.isEmpty() )
            {
                OUString sRelId = mpFB->addRelation( mpFS->getOutputStream(),
                        oox::getRelationship(Relationship::HYPERLINK),
                        mpURLTransformer->getTransformedString(sURL),
                        mpURLTransformer->isExternalURL(sURL));

                mpFS->singleElementNS(XML_a, XML_hlinkClick, FSNS(XML_r, XML_id), sRelId);
                // pAttrListHlinkClick->add(FSNS(XML_r, XML_id), sRelId);
            }
        }

        // // export <hlinkClick> element
        // mpFS->singleElementNS(XML_a, XML_hlinkClick, pAttrListHlinkClick);

        OUString sBookmark;
        if (GetProperty(rXPropSet, u"Bookmark"_ustr))
            mAny >>= sBookmark;

        if (GetProperty(rXPropSet, u"OnClick"_ustr))
        {
            OUString sPPAction;
            presentation::ClickAction eClickAction = presentation::ClickAction_NONE;
            mAny >>= eClickAction;
            if (eClickAction != presentation::ClickAction_NONE)
            {
                switch (eClickAction)
                {
                    case presentation::ClickAction_STOPPRESENTATION:
                        sPPAction = "ppaction://hlinkshowjump?jump=endshow";
                        break;
                    case presentation::ClickAction_NEXTPAGE:
                        sPPAction = "ppaction://hlinkshowjump?jump=nextslide";
                        break;
                    case presentation::ClickAction_LASTPAGE:
                        sPPAction = "ppaction://hlinkshowjump?jump=lastslide";
                        break;
                    case presentation::ClickAction_PREVPAGE:
                        sPPAction = "ppaction://hlinkshowjump?jump=previousslide";
                        break;
                    case presentation::ClickAction_FIRSTPAGE:
                        sPPAction = "ppaction://hlinkshowjump?jump=firstslide";
                        break;
                    case presentation::ClickAction_BOOKMARK:
                        sBookmark = "#" + sBookmark;
                        break;
                    default:
                        break;
                }
            }
            if (!sPPAction.isEmpty())
                pFS->singleElementNS(XML_a, XML_hlinkClick, FSNS(XML_r, XML_id), "", XML_action,
                                     sPPAction);
        }
        if (!sBookmark.isEmpty())
        {
            bool bExtURL = URLTransformer().isExternalURL(sBookmark);
            sBookmark = bExtURL ? sBookmark : lcl_GetTarget(GetFB()->getModel(), sBookmark);

            OUString sRelId;
            if (!sBookmark.isEmpty())
            {
                sRelId = mpFB->addRelation(mpFS->getOutputStream(),
                                           bExtURL ? oox::getRelationship(Relationship::HYPERLINK)
                                                   : oox::getRelationship(Relationship::SLIDE),
                                           sBookmark, bExtURL);
            }
            if (bExtURL)
                mpFS->singleElementNS(XML_a, XML_hlinkClick, FSNS(XML_r, XML_id), sRelId);
            else
                mpFS->singleElementNS(
                    XML_a, XML_hlinkClick, FSNS(XML_r, XML_id), sRelId, XML_action,
                    sBookmark.isEmpty() ? "ppaction://noaction" : "ppaction://hlinksldjump");
        }
        AddExtLst(pFS, rXPropSet);
        pFS->endElementNS(mnXmlNamespace, XML_cNvPr);
        pFS->singleElementNS(mnXmlNamespace, XML_cNvSpPr);
        WriteNonVisualProperties( xShape );
        pFS->endElementNS( mnXmlNamespace, XML_nvSpPr );
    }
    else
    {
        pFS->startElementNS(mnXmlNamespace, XML_wsp);
        if (m_xParent.is())
        {
            pFS->startElementNS(mnXmlNamespace, XML_cNvPr, XML_id,
                                OString::number(GetShapeID(xShape) == -1 ? GetNewShapeID(xShape)
                                                                         : GetShapeID(xShape)),
                                XML_name, GetShapeName(xShape));

            if (GetProperty(rXPropSet, u"Hyperlink"_ustr))
            {
                OUString sURL;
                mAny >>= sURL;
                if (!sURL.isEmpty())
                {
                    OUString sRelId = mpFB->addRelation(
                        mpFS->getOutputStream(), oox::getRelationship(Relationship::HYPERLINK),
                        mpURLTransformer->getTransformedString(sURL),
                        mpURLTransformer->isExternalURL(sURL));

                    mpFS->singleElementNS(XML_a, XML_hlinkClick, FSNS(XML_r, XML_id), sRelId);
                }
            }
            AddExtLst(pFS, rXPropSet);
            pFS->endElementNS(mnXmlNamespace, XML_cNvPr);
        }
        pFS->singleElementNS(mnXmlNamespace, XML_cNvSpPr);
    }

    // visual shape properties
    pFS->startElementNS(mnXmlNamespace, XML_spPr);

    // we export non-primitive shapes to custom geometry
    // we also export non-ooxml shapes which have handles/equations to custom geometry, because
    // we cannot convert ODF equations to DrawingML equations. TODO: see what binary DOC export filter does.
    // but our WritePolyPolygon()/WriteCustomGeometry() functions are incomplete, therefore we use a denylist
    // we use a allowlist for shapes where mapping to MSO preset shape is not optimal
    bool bCustGeom = true;
    bool bOnDenylist = false;
    if( sShapeType == "ooxml-non-primitive" )
        bCustGeom = true;
    else if( sShapeType.startsWith("ooxml") )
        bCustGeom = false;
    else if( lcl_IsOnAllowlist(sShapeType) )
        bCustGeom = true;
    else if( lcl_IsOnDenylist(sShapeType) )
    {
        bCustGeom = false;
        bOnDenylist = true;
    }

    bool bPresetWriteSuccessful = false;
    // Let the custom shapes what has name and preset information in OOXML, to be written
    // as preset ones with parameters. Try that with this converter class.
    if (!sShapeType.startsWith("ooxml") && sShapeType != "non-primitive" && !mbUserShapes
        && xShape->getShapeType() == "com.sun.star.drawing.CustomShape"
        && !lcl_IsOnAllowlist(sShapeType))
    {
        DMLPresetShapeExporter aCustomShapeConverter(this, xShape);
        bPresetWriteSuccessful = aCustomShapeConverter.WriteShape();
    }
    // If preset writing has problems try to write the shape as it done before
    if (bPresetWriteSuccessful)
        ;// Already written do nothing.
    else if (bCustGeom)
    {
        WriteShapeTransformation( xShape, XML_a, bFlipH, bFlipV );
        bool bSuccess = WriteCustomGeometry(xShape, rSdrObjCustomShape);
        // In case of Writer, the parent element is <wps:spPr>, and there the <a:custGeom> element
        // is not optional.
        if (!bSuccess && GetDocumentType() == DOCUMENT_DOCX)
        {
            WriteEmptyCustomGeometry();
        }
    }
    else if (bOnDenylist && bHasHandles && nAdjustmentValuesIndex !=-1 && !sShapeType.startsWith("mso-spt"))
    {
        WriteShapeTransformation( xShape, XML_a, bFlipH, bFlipV );
        Sequence< EnhancedCustomShapeAdjustmentValue > aAdjustmentSeq;
        std::vector< std::pair< sal_Int32, sal_Int32> > aHandlePositionList;
        std::vector< std::pair< sal_Int32, sal_Int32> > aAvList;
        aGeometrySeq[ nAdjustmentValuesIndex ].Value >>= aAdjustmentSeq ;

        lcl_AnalyzeHandles( aHandles, aHandlePositionList, aAdjustmentSeq );

        sal_Int32 nXPosition = 0;
        sal_Int32 nYPosition = 0;
        if ( !aHandlePositionList.empty() )
        {
            nXPosition = aHandlePositionList[0].first ;
            nYPosition = aHandlePositionList[0].second ;
        }
        switch( eShapeType )
        {
            case mso_sptBorderCallout1:
            {
                sal_Int32 adj3 =  double(nYPosition)/aViewBox.Height *100000;
                sal_Int32 adj4 =  double(nXPosition)/aViewBox.Width *100000;
                lcl_AppendAdjustmentValue( aAvList, 1, 18750 );
                lcl_AppendAdjustmentValue( aAvList, 2, -8333 );
                lcl_AppendAdjustmentValue( aAvList, 3, adj3 );
                lcl_AppendAdjustmentValue( aAvList, 4, adj4 );
                break;
            }
            case mso_sptBorderCallout2:
            {
                sal_Int32 adj5 =  double(nYPosition)/aViewBox.Height *100000;
                sal_Int32 adj6 =  double(nXPosition)/aViewBox.Width *100000;
                sal_Int32 adj3 =  18750;
                sal_Int32 adj4 =  -16667;
                lcl_AppendAdjustmentValue( aAvList, 1, 18750 );
                lcl_AppendAdjustmentValue( aAvList, 2, -8333 );
                if ( aHandlePositionList.size() > 1 )
                {
                    nXPosition = aHandlePositionList[1].first ;
                    nYPosition = aHandlePositionList[1].second ;
                    adj3 =  double(nYPosition)/aViewBox.Height *100000;
                    adj4 =  double(nXPosition)/aViewBox.Width *100000;
                }
                lcl_AppendAdjustmentValue( aAvList, 3, adj3 );
                lcl_AppendAdjustmentValue( aAvList, 4, adj4 );
                lcl_AppendAdjustmentValue( aAvList, 5, adj5 );
                lcl_AppendAdjustmentValue( aAvList, 6, adj6 );
                break;
            }
            case mso_sptWedgeRectCallout:
            case mso_sptWedgeRRectCallout:
            case mso_sptWedgeEllipseCallout:
            case mso_sptCloudCallout:
            {
                sal_Int32 adj1 =  (double(nXPosition)/aViewBox.Width -0.5) *100000;
                sal_Int32 adj2 =  (double(nYPosition)/aViewBox.Height -0.5) *100000;
                lcl_AppendAdjustmentValue( aAvList, 1, adj1 );
                lcl_AppendAdjustmentValue( aAvList, 2, adj2 );
                if ( eShapeType == mso_sptWedgeRRectCallout)
                {
                    lcl_AppendAdjustmentValue( aAvList, 3, 16667);
                }

                break;
            }
            case mso_sptFoldedCorner:
            {
                sal_Int32 adj =  double( aViewBox.Width - nXPosition) / std::min( aViewBox.Width,aViewBox.Height ) * 100000;
                lcl_AppendAdjustmentValue( aAvList, 0, adj );
                break;
            }
            case mso_sptDonut:
            case mso_sptSun:
            case mso_sptMoon:
            case mso_sptNoSmoking:
            case mso_sptHorizontalScroll:
            case mso_sptBevel:
            case mso_sptBracketPair:
            {
                sal_Int32 adj =  double( nXPosition )/aViewBox.Width*100000 ;
                lcl_AppendAdjustmentValue( aAvList, 0, adj );
                break;
            }
            case mso_sptCan:
            case mso_sptCube:
            case mso_sptBracePair:
            case mso_sptVerticalScroll:
            {
                sal_Int32 adj =  double( nYPosition )/aViewBox.Height *100000 ;
                lcl_AppendAdjustmentValue( aAvList, 0, adj );
                break;
            }
            case mso_sptSmileyFace:
            {
                sal_Int32 adj =  double( nYPosition )/aViewBox.Height *100000 - 76458.0;
                lcl_AppendAdjustmentValue( aAvList, 0, adj );
                break;
            }
            case mso_sptBlockArc:
            {
                sal_Int32 nRadius = 50000 * ( 1 - double(nXPosition) / 10800);
                sal_Int32 nAngleStart = lcl_NormalizeAngle( nYPosition );
                sal_Int32 nAngleEnd = lcl_NormalizeAngle( 180 - nAngleStart );
                lcl_AppendAdjustmentValue( aAvList, 1, 21600000 / 360 * nAngleStart );
                lcl_AppendAdjustmentValue( aAvList, 2, 21600000 / 360 * nAngleEnd );
                lcl_AppendAdjustmentValue( aAvList, 3, nRadius );
                break;
            }
            // case mso_sptNil:
            // case mso_sptBentConnector3:
            // case mso_sptBorderCallout3:
            default:
            {
                if ( sPresetShape == "frame" )
                {
                    sal_Int32 adj1 =  double( nYPosition )/aViewBox.Height *100000 ;
                    lcl_AppendAdjustmentValue( aAvList, 1, adj1 );
                }
                break;
            }
        }
        WritePresetShape( sPresetShape  , aAvList );
    }
    else // preset geometry
    {
        WriteShapeTransformation( xShape, XML_a, bFlipH, bFlipV );
        if( nAdjustmentValuesIndex != -1 )
        {
            WritePresetShape( sPresetShape, eShapeType, bPredefinedHandlesUsed,
                              aGeometrySeq[ nAdjustmentValuesIndex ] );
        }
        else
            WritePresetShape( sPresetShape );
    }
    if( rXPropSet.is() )
    {
        WriteFill(rXPropSet, xShape->getSize());
        WriteOutline( rXPropSet );
        WriteShapeEffects( rXPropSet );

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

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

        if( bHas3DEffectinShape)
            Write3DEffects( rXPropSet, /*bIsText=*/false );
    }

    pFS->endElementNS( mnXmlNamespace, XML_spPr );

    pFS->startElementNS(mnXmlNamespace, XML_style);
    WriteShapeStyle( rXPropSet );
    pFS->endElementNS( mnXmlNamespace, XML_style );

    // write text
    WriteTextBox( xShape, mnXmlNamespace );

    pFS->endElementNS( mnXmlNamespace, (GetDocumentType() != DOCUMENT_DOCX || mbUserShapes ? XML_sp : XML_wsp) );

    return *this;
}

ShapeExport& ShapeExport::WriteEllipseShape( const Reference< XShape >& xShape )
{
    SAL_INFO("oox.shape", "write ellipse shape");

    FSHelperPtr pFS = GetFS();

    pFS->startElementNS(mnXmlNamespace, (GetDocumentType() != DOCUMENT_DOCX || mbUserShapes ? XML_sp : XML_wsp));

    // TODO: connector ?

    Reference<XPropertySet> const xProps(xShape, UNO_QUERY);
    // non visual shape properties
    if (GetDocumentType() != DOCUMENT_DOCX || mbUserShapes)
    {
        pFS->startElementNS(mnXmlNamespace, XML_nvSpPr);
        pFS->startElementNS(mnXmlNamespace, XML_cNvPr,
                XML_id, OString::number(GetNewShapeID(xShape)),
                XML_name, GetShapeName(xShape));
        AddExtLst(pFS, xProps);
        pFS->endElementNS(mnXmlNamespace, XML_cNvPr);
        pFS->singleElementNS( mnXmlNamespace, XML_cNvSpPr );
        WriteNonVisualProperties( xShape );
        pFS->endElementNS( mnXmlNamespace, XML_nvSpPr );
    }
    else
        pFS->singleElementNS(mnXmlNamespace, XML_cNvSpPr);

    CircleKind  eCircleKind(CircleKind_FULL);
    if (xProps.is())
        xProps->getPropertyValue(u"CircleKind"_ustr ) >>= eCircleKind;

    // visual shape properties
    pFS->startElementNS( mnXmlNamespace, XML_spPr );
    WriteShapeTransformation( xShape, XML_a );

    if (CircleKind_FULL == eCircleKind)
        WritePresetShape("ellipse"_ostr);
    else
    {
        sal_Int32 nStartAngleIntern(9000);
        sal_Int32 nEndAngleIntern(0);
        if (xProps.is())
        {
           xProps->getPropertyValue(u"CircleStartAngle"_ustr ) >>= nStartAngleIntern;
           xProps->getPropertyValue(u"CircleEndAngle"_ustr) >>= nEndAngleIntern;
        }
        std::vector< std::pair<sal_Int32,sal_Int32>> aAvList;
        awt::Size aSize = xShape->getSize();
        if (aSize.Width != 0 || aSize.Height != 0)
        {
            // Our arc has 90° up, OOXML has 90° down, so mirror it.
            // API angles are 1/100 degree.
            sal_Int32 nStartAngleOOXML(lcl_CircleAngle2CustomShapeEllipseAngleOOX(36000 - nEndAngleIntern, aSize.Width, aSize.Height));
            sal_Int32 nEndAngleOOXML(lcl_CircleAngle2CustomShapeEllipseAngleOOX(36000 - nStartAngleIntern, aSize.Width, aSize.Height));
            lcl_AppendAdjustmentValue( aAvList, 1, nStartAngleOOXML);
            lcl_AppendAdjustmentValue( aAvList, 2, nEndAngleOOXML);
        }
        switch (eCircleKind)
        {
            case CircleKind_ARC :
                WritePresetShape("arc"_ostr, aAvList);
            break;
            case CircleKind_SECTION :
                WritePresetShape("pie"_ostr, aAvList);
            break;
            case CircleKind_CUT :
                WritePresetShape("chord"_ostr, aAvList);
            break;
        default :
            WritePresetShape("ellipse"_ostr);
        }
    }
    if( xProps.is() )
    {
        if (CircleKind_ARC == eCircleKind)
        {
            // An arc in ODF is never filled, even if a fill style other than
            // "none" is set. OOXML arc can be filled, so set fill explicit to
            // NONE, otherwise some hidden or inherited filling is shown.
            FillStyle eFillStyle(FillStyle_NONE);
            uno::Any aNewValue;
            aNewValue <<= eFillStyle;
            xProps->setPropertyValue(u"FillStyle"_ustr, aNewValue);
        }
        WriteFill( xProps );
        WriteOutline( xProps );
    }
    pFS->endElementNS( mnXmlNamespace, XML_spPr );

    // write text
    WriteTextBox( xShape, mnXmlNamespace );

    pFS->endElementNS( mnXmlNamespace, (GetDocumentType() != DOCUMENT_DOCX || mbUserShapes ? XML_sp : XML_wsp) );

    return *this;
}

ShapeExport& ShapeExport::WriteGraphicObjectShape( const Reference< XShape >& xShape )
{
    WriteGraphicObjectShapePart( xShape );

    return *this;
}

void ShapeExport::WriteGraphicObjectShapePart( const Reference< XShape >& xShape, const Graphic* pGraphic )
{
    SAL_INFO("oox.shape", "write graphic object shape");

    if (IsNonEmptySimpleText(xShape))
    {
        SAL_INFO("oox.shape", "graphicObject: wrote only text");

        WriteTextShape(xShape);

        return;
    }

    SAL_INFO("oox.shape", "graphicObject without text");

    uno::Reference<graphic::XGraphic> xGraphic;
    OUString sMediaURL;

    Reference< XPropertySet > xShapeProps( xShape, UNO_QUERY );

    if (pGraphic)
    {
        xGraphic.set(pGraphic->GetXGraphic());
    }
    else if (xShapeProps.is() && xShapeProps->getPropertySetInfo()->hasPropertyByName(u"Graphic"_ustr))
    {
        xShapeProps->getPropertyValue(u"Graphic"_ustr) >>= xGraphic;
    }

    // tdf#155903 Only for PPTX, Microsoft does not support this feature in Word and Excel.
    bool bHasMediaURL = GetDocumentType() == DOCUMENT_PPTX && xShapeProps.is()
                        && xShapeProps->getPropertySetInfo()->hasPropertyByName(u"MediaURL"_ustr)
                        && (xShapeProps->getPropertyValue(u"MediaURL"_ustr) >>= sMediaURL);

    if (!xGraphic.is() && !bHasMediaURL)
    {
        SAL_INFO("oox.shape", "no graphic or media URL found");
        return;
    }

    FSHelperPtr pFS = GetFS();
    XmlFilterBase* pFB = GetFB();

    if (GetDocumentType() != DOCUMENT_DOCX || mbUserShapes)
        pFS->startElementNS(mnXmlNamespace, XML_pic);
    else
        pFS->startElementNS(mnXmlNamespace, XML_pic,
            FSNS(XML_xmlns, XML_pic), pFB->getNamespaceURL(OOX_NS(dmlPicture)));

    pFS->startElementNS(mnXmlNamespace, XML_nvPicPr);

    presentation::ClickAction eClickAction = presentation::ClickAction_NONE;
    OUString sDescr, sURL, sBookmark, sPPAction;
    bool bVisible = true;

    if ( GetProperty( xShapeProps, u"Description"_ustr ) )
        mAny >>= sDescr;
    if ( GetProperty( xShapeProps, u"URL"_ustr ) )
        mAny >>= sURL;
    if ( GetProperty( xShapeProps, u"Visible"_ustr ) )
        mAny >>= bVisible;
    if (GetProperty(xShapeProps, u"Bookmark"_ustr))
        mAny >>= sBookmark;
    if (GetProperty(xShapeProps, u"OnClick"_ustr))
        mAny >>= eClickAction;

    pFS->startElementNS( mnXmlNamespace, XML_cNvPr,
                         XML_id,     OString::number(GetNewShapeID(xShape)),
                         XML_name,   GetShapeName(xShape),
                         XML_descr,  sax_fastparser::UseIf(sDescr, !sDescr.isEmpty()),
                         XML_hidden, sax_fastparser::UseIf("1", !bVisible));

    if (eClickAction != presentation::ClickAction_NONE)
    {
        switch (eClickAction)
        {
            case presentation::ClickAction_STOPPRESENTATION:
                sPPAction = "ppaction://hlinkshowjump?jump=endshow";
                break;
            case presentation::ClickAction_NEXTPAGE:
                sPPAction = "ppaction://hlinkshowjump?jump=nextslide";
                break;
            case presentation::ClickAction_LASTPAGE:
                sPPAction = "ppaction://hlinkshowjump?jump=lastslide";
                break;
            case presentation::ClickAction_PREVPAGE:
                sPPAction = "ppaction://hlinkshowjump?jump=previousslide";
                break;
            case presentation::ClickAction_FIRSTPAGE:
                sPPAction = "ppaction://hlinkshowjump?jump=firstslide";
                break;
            case presentation::ClickAction_BOOKMARK:
                sBookmark = "#" + sBookmark;
                break;
            default:
                break;
        }
    }

    // OOXTODO: //cNvPr children: XML_extLst, XML_hlinkHover
    if (bHasMediaURL || !sPPAction.isEmpty())
        pFS->singleElementNS(XML_a, XML_hlinkClick, FSNS(XML_r, XML_id), "", XML_action,
                             bHasMediaURL ? u"ppaction://media"_ustr : sPPAction);
    if( !sURL.isEmpty() )
    {
        OUString sRelId = mpFB->addRelation( mpFS->getOutputStream(),
                oox::getRelationship(Relationship::HYPERLINK),
                mpURLTransformer->getTransformedString(sURL),
                mpURLTransformer->isExternalURL(sURL));

        mpFS->singleElementNS(XML_a, XML_hlinkClick, FSNS(XML_r, XML_id), sRelId);
    }

    if (!sBookmark.isEmpty())
    {
        bool bExtURL = URLTransformer().isExternalURL(sBookmark);
        sBookmark = bExtURL ? sBookmark : lcl_GetTarget(GetFB()->getModel(), sBookmark);

        OUString sRelId;
        if (!sBookmark.isEmpty())
        {
            sRelId = mpFB->addRelation(mpFS->getOutputStream(),
                                       bExtURL ? oox::getRelationship(Relationship::HYPERLINK)
                                               : oox::getRelationship(Relationship::SLIDE),
                                       sBookmark, bExtURL);
        }
        if (bExtURL)
            mpFS->singleElementNS(XML_a, XML_hlinkClick, FSNS(XML_r, XML_id), sRelId);
        else
            mpFS->singleElementNS(XML_a, XML_hlinkClick, FSNS(XML_r, XML_id), sRelId, XML_action,
                                  sBookmark.isEmpty() ? "ppaction://noaction"
                                                      : "ppaction://hlinksldjump");
    }
    AddExtLst(pFS, xShapeProps);
    pFS->endElementNS(mnXmlNamespace, XML_cNvPr);

    pFS->singleElementNS(mnXmlNamespace, XML_cNvPicPr
                         // OOXTODO: XML_preferRelativeSize
                        );
    if (bHasMediaURL)
        WriteMediaNonVisualProperties(xShape);
    else
        WriteNonVisualProperties(xShape);

    pFS->endElementNS( mnXmlNamespace, XML_nvPicPr );

    pFS->startElementNS(mnXmlNamespace, XML_blipFill);

    if (xGraphic.is())
    {
        WriteXGraphicBlip(xShapeProps, xGraphic, mbUserShapes);
    }
    else if (bHasMediaURL)
    {
        Reference<graphic::XGraphic> xFallbackGraphic;
        if (xShapeProps->getPropertySetInfo()->hasPropertyByName(u"FallbackGraphic"_ustr))
            xShapeProps->getPropertyValue(u"FallbackGraphic"_ustr) >>= xFallbackGraphic;

        WriteXGraphicBlip(xShapeProps, xFallbackGraphic, mbUserShapes);
    }

    if (xGraphic.is())
    {
        WriteSrcRectXGraphic(xShapeProps, xGraphic);
    }

    // now we stretch always when we get pGraphic (when changing that
    // behavior, test n#780830 for regression, where the OLE sheet might get tiled
    bool bStretch = false;
    if( !pGraphic && GetProperty( xShapeProps, u"FillBitmapStretch"_ustr ) )
        mAny >>= bStretch;

    if ( pGraphic || bStretch )
        pFS->singleElementNS(XML_a, XML_stretch);

    if (bHasMediaURL)
    {
        // Graphic of media shapes is always stretched.
        pFS->startElementNS(XML_a, XML_stretch);
        pFS->singleElementNS(XML_a, XML_fillRect);
        pFS->endElementNS(XML_a, XML_stretch);
    }

    pFS->endElementNS( mnXmlNamespace, XML_blipFill );

    // visual shape properties
    pFS->startElementNS(mnXmlNamespace, XML_spPr);
    bool bFlipH = false;
    if( xShapeProps->getPropertySetInfo()->hasPropertyByName(u"IsMirrored"_ustr) )
    {
        xShapeProps->getPropertyValue(u"IsMirrored"_ustr) >>= bFlipH;
    }
    WriteShapeTransformation( xShape, XML_a, bFlipH, false, false, false, true );
    WritePresetShape( "rect"_ostr );
    WriteFill(xShapeProps);
    // graphic object can come with the frame (bnc#654525)
    WriteOutline( xShapeProps );

    WriteShapeEffects( xShapeProps );
    Write3DEffects( xShapeProps, /*bIsText=*/false );

    pFS->endElementNS( mnXmlNamespace, XML_spPr );

    pFS->endElementNS( mnXmlNamespace, XML_pic );
}

static void lcl_Rotate(sal_Int32 nAngle, Point center, awt::Point& pt)
{
    sal_Int16 nCos, nSin;
    switch (nAngle)
    {
        case 90:
            nCos = 0;
            nSin = 1;
            break;
        case 180:
            nCos = -1;
            nSin = 0;
            break;
        case 270:
            nCos = 0;
            nSin = -1;
            break;
        default:
            return;
    }
    sal_Int32 x = pt.X - center.X();
    sal_Int32 y = pt.Y - center.Y();
    pt.X = center.X() + x * nCos - y * nSin;
    pt.Y = center.Y() + y * nCos + x * nSin;
}

static void lcl_FlipHFlipV(const tools::Polygon& rPoly, sal_Int32 nAngle, bool& rFlipH, bool& rFlipV)
{
    Point aStart = rPoly[0];
    Point aEnd = rPoly[rPoly.GetSize() - 1];

    if (aStart.X() > aEnd.X() && aStart.Y() > aEnd.Y())
    {
        if (nAngle)
        {
            if (nAngle == 90)
                rFlipH = true;
            if (nAngle == 270)
                rFlipV = true;
        }
        else // 0°
        {
            rFlipH = true;
            rFlipV = true;
        }
    }

    if (aStart.X() < aEnd.X() && aStart.Y() < aEnd.Y())
    {
        if (nAngle)
        {
            if (nAngle != 270)
            {
                rFlipH = true;
                rFlipV = true;
            }
            else
                rFlipH = true;
        }
    }

    if (aStart.Y() < aEnd.Y() && aStart.X() > aEnd.X())
    {
        if (nAngle)
        {
            if (nAngle == 180)
                rFlipV = true;
            if (nAngle == 270)
            {
                rFlipV = true;
                rFlipH = true;
            }
        }
        else // 0°
        {
            rFlipH = true;
        }
    }

    if (aStart.Y() > aEnd.Y() && aStart.X() < aEnd.X())
    {
        if (nAngle)
        {
            if (nAngle == 90)
            {
                rFlipH = true;
                rFlipV = true;
            }
            if (nAngle == 180)
                rFlipH = true;
        }
        else // 0°
            rFlipV = true;
    }
}

static sal_Int32 lcl_GetAngle(const tools::Polygon& rPoly)
{
    sal_Int32 nAngle;
    Point aStartPoint = rPoly[0];
    Point aEndPoint = rPoly[rPoly.GetSize() - 1];
    if (aStartPoint.X() == rPoly[1].X())
    {
        if ((aStartPoint.X() < aEndPoint.X() && aStartPoint.Y() > aEndPoint.Y())
            || (aStartPoint.X() > aEndPoint.X() && aStartPoint.Y() < aEndPoint.Y()))
        {
            nAngle = 90;
        }
        else
            nAngle = 270;
    }
    else
    {
        if (aStartPoint.X() > rPoly[1].X())
            nAngle = 180;
        else
            nAngle = 0;
    }

    return nAngle;
}

// Adjust value decide the position, where the connector should turn.
static void lcl_GetConnectorAdjustValue(const Reference<XShape>& xShape, const tools::Polygon& rPoly,
                                        ConnectorType eConnectorType,
                                        std::vector<std::pair<sal_Int32, sal_Int32>>& rAvList)
{
    Reference<XPropertySet> xShapeProps(xShape, UNO_QUERY);
    bool bIsOOXMLCurve(false);
    xShapeProps->getPropertyValue(u"EdgeOOXMLCurve"_ustr) >>= bIsOOXMLCurve;
    sal_Int32 nAdjCount = 0;
    if (eConnectorType == ConnectorType_CURVE)
    {
        if (bIsOOXMLCurve)
        {
            nAdjCount = (rPoly.GetSize() - 4) / 3;
        }
        else if (rPoly.GetSize() == 4)
        {
            if ((rPoly[0].X() == rPoly[1].X() && rPoly[2].X() == rPoly[3].X())
                || (rPoly[0].Y() == rPoly[1].Y() && rPoly[2].Y() == rPoly[3].Y()))
            {
                nAdjCount = 1; // curvedConnector3, control vectors parallel
            }
            else
                nAdjCount = 0; // curvedConnector2, control vectors orthogonal
        }
        else if (rPoly.GetSize() > 4)
        {
            if ((rPoly[2].X() == rPoly[3].X() && rPoly[3].X() == rPoly[4].X())
                || (rPoly[2].Y() == rPoly[3].Y() && rPoly[3].Y() == rPoly[4].Y()))
            {
                nAdjCount = 3; // curvedConnector5
            }
            else
                nAdjCount = 2; // curvedConnector4
        }
    }
    else
    {
        switch (rPoly.GetSize())
        {
            case 3:
                nAdjCount = 0; // bentConnector2
                break;
            case 4:
                nAdjCount = 1; // bentConnector3
                break;
            case 5:
                nAdjCount = 2; // bentConnector4
                break;
            case 6:
                nAdjCount = 3; // bentConnector5
                break;
        }
    }

    if (nAdjCount)
    {
        sal_Int32 nAdjustValue;
        Point aStart = rPoly[0];
        Point aEnd = rPoly[rPoly.GetSize() - 1];

        for (sal_Int32 i = 1; i <= nAdjCount; ++i)
        {
            Point aPt = rPoly[i];

            if (aEnd.Y() == aStart.Y())
                aEnd.setY(aStart.Y() + 1);
            if (aEnd.X() == aStart.X())
                aEnd.setX(aStart.X() + 1);

            bool bVertical = rPoly[1].X() - aStart.X() != 0 ? true : false;
            // vertical and horizon alternate
            if (i % 2 == 1)
                bVertical = !bVertical;

            if (eConnectorType == ConnectorType_CURVE)
            {
                if (bIsOOXMLCurve)
                {
                    aPt = rPoly[3 *  i];
                }
                else
                {
                    awt::Size aSize = xShape->getSize();
                    awt::Point aShapePosition = xShape->getPosition();
                    tools::Rectangle aBoundRect = rPoly.GetBoundRect();

                    if (bVertical)
                    {
                        if ((aBoundRect.GetSize().Height() - aSize.Height) == 1)
                            aPt.setY(rPoly[i + 1].Y());
                        else if (aStart.Y() > aPt.Y())
                            aPt.setY(aShapePosition.Y);
                        else
                            aPt.setY(aShapePosition.Y + aSize.Height);
                    }
                    else
                    {
                        if ((aBoundRect.GetSize().Width() - aSize.Width) == 1)
                            aPt.setX(rPoly[i + 1].X());
                        else if (aStart.X() > aPt.X())
                            aPt.setX(aShapePosition.X);
                        else
                            aPt.setX(aShapePosition.X + aSize.Width);
                    }
                }
            }

            if (bVertical)
                nAdjustValue = ((aPt.Y() - aStart.Y()) * 100000) / (aEnd.Y() - aStart.Y());
            else
                nAdjustValue = ((aPt.X() - aStart.X()) * 100000) / (aEnd.X() - aStart.X());

            rAvList.emplace_back(i, nAdjustValue);
        }
    }
}

static sal_Int32 lcl_GetGluePointId(const Reference<XShape>& xShape, sal_Int32 nGluePointId)
{
    if (nGluePointId > 3)
        return nGluePointId - 4;
    else
    {
        bool bFlipH = false;
        bool bFlipV = false;
        Reference<XPropertySet> xShapeProps(xShape, UNO_QUERY);
        if (xShapeProps.is() && xShapeProps->getPropertySetInfo()
                && xShapeProps->getPropertySetInfo()->hasPropertyByName(u"CustomShapeGeometry"_ustr))
        {
            Sequence<PropertyValue> aGeometrySeq;
            xShapeProps->getPropertyValue(u"CustomShapeGeometry"_ustr) >>= aGeometrySeq;
            for (int i = 0; i < aGeometrySeq.getLength(); i++)
            {
                const PropertyValue& rProp = aGeometrySeq[i];
                if (rProp.Name == "MirroredX")
                    rProp.Value >>= bFlipH;

                if (rProp.Name == "MirroredY")
                    rProp.Value >>= bFlipV;
            }
        }

        if ((!bFlipH && !bFlipV) || (bFlipH && bFlipV))
        {
            // change id of the bounding box (1 <-> 3)
            if (nGluePointId == 1)
                nGluePointId = 3; // Right
            else if (nGluePointId == 3)
                nGluePointId = 1; // Left
        }
    }

    return nGluePointId;
}

ShapeExport& ShapeExport::WriteConnectorShape( const Reference< XShape >& xShape )
{
    bool bFlipH = false;
    bool bFlipV = false;
    sal_Int32 nAngle = 0;
    sal_Int32 nStartGlueId = 0;
    sal_Int32 nEndGlueId = 0;

    SAL_INFO("oox.shape", "write connector shape");

    FSHelperPtr pFS = GetFS();

    OUString sGeometry;
    std::vector<std::pair<sal_Int32, sal_Int32>> aAdjustValueList;
    Reference< XPropertySet > rXPropSet( xShape, UNO_QUERY );
    Reference< XPropertyState > rXPropState( xShape, UNO_QUERY );
    awt::Point aStartPoint, aEndPoint;
    Reference< XShape > rXShapeA;
    Reference< XShape > rXShapeB;
    ConnectorType eConnectorType = ConnectorType_STANDARD;
    if (GetProperty(rXPropSet, u"EdgeKind"_ustr))
        mAny >>= eConnectorType;

    switch( eConnectorType ) {
        case ConnectorType_CURVE:
            sGeometry = "curvedConnector";
            break;
        case ConnectorType_LINES:
        case ConnectorType_STANDARD:
            sGeometry = "bentConnector";
            break;
        default:
        case ConnectorType_LINE:
            sGeometry = "straightConnector1";
            break;
    }

    if (GetDirectProperty( rXPropSet, rXPropState, u"EdgeStartPoint"_ustr ))
    {
        mAny >>= aStartPoint;
        if (GetDirectProperty( rXPropSet, rXPropState, u"EdgeEndPoint"_ustr ))
            mAny >>= aEndPoint;
    }
    if (GetProperty(rXPropSet, u"EdgeStartConnection"_ustr))
        mAny >>= rXShapeA;
    if (GetProperty(rXPropSet, u"EdgeEndConnection"_ustr))
        mAny >>= rXShapeB;

    // Position is relative to group in Word, but relative to anchor of group in API.
    if (GetDocumentType() == DOCUMENT_DOCX && !mbUserShapes && m_xParent.is())
    {
        awt::Point aParentPos = m_xParent->getPosition();
        aStartPoint.X -= aParentPos.X;
        aStartPoint.Y -= aParentPos.Y;
        aEndPoint.X -= aParentPos.X;
        aEndPoint.Y -= aParentPos.Y;
    }
    EscherConnectorListEntry aConnectorEntry( xShape, aStartPoint, rXShapeA, aEndPoint, rXShapeB );

    if (GetProperty(rXPropSet, u"StartGluePointIndex"_ustr))
    {
        mAny >>= nStartGlueId;
        nStartGlueId = (nStartGlueId != -1) ? lcl_GetGluePointId(rXShapeA, nStartGlueId)
                                            : (aConnectorEntry.mXConnectToA.is()
                                                   ? aConnectorEntry.GetConnectorRule(true)
                                                   : -1);
    }

    if (GetProperty(rXPropSet, u"EndGluePointIndex"_ustr))
    {
        mAny >>= nEndGlueId;
        nEndGlueId = (nEndGlueId != -1) ? lcl_GetGluePointId(rXShapeB, nEndGlueId)
                                        : (aConnectorEntry.mXConnectToB.is()
                                               ? aConnectorEntry.GetConnectorRule(false)
                                               : -1);
    }

    if (eConnectorType != ConnectorType_LINE)
    {
        tools::PolyPolygon aPolyPolygon = EscherPropertyContainer::GetPolyPolygon(xShape);
        if (aPolyPolygon.Count() > 0)
        {
            const tools::Polygon& aPoly = aPolyPolygon.GetObject(0);
            lcl_GetConnectorAdjustValue(xShape, aPoly, eConnectorType, aAdjustValueList);
            nAngle = lcl_GetAngle(aPoly);
            lcl_FlipHFlipV(aPoly, nAngle, bFlipH, bFlipV);
            if (nAngle)
            {
                Point center((aEndPoint.X + aStartPoint.X) / 2, (aEndPoint.Y + aStartPoint.Y) / 2);
                lcl_Rotate(nAngle, center, aStartPoint);
                lcl_Rotate(nAngle, center, aEndPoint);
                nAngle *= 60000;
            }
            sGeometry += OUString::number(aAdjustValueList.size() + 2);
        }
    }

    tools::Rectangle aRect( Point( aStartPoint.X, aStartPoint.Y ), Point( aEndPoint.X, aEndPoint.Y ) );
    if( aRect.getOpenWidth() < 0 ) {
        aRect.SetLeft(aEndPoint.X);
        aRect.setWidth( aStartPoint.X - aEndPoint.X );
        if (eConnectorType == ConnectorType_LINE)
            bFlipH = true;
    }

    if( aRect.getOpenHeight() < 0 ) {
        aRect.SetTop(aEndPoint.Y);
        aRect.setHeight( aStartPoint.Y - aEndPoint.Y );
        if (eConnectorType == ConnectorType_LINE)
            bFlipV = true;
    }

    // tdf#99810 connector shape (cxnSp) is not valid with namespace 'wps'
    const auto nShapeNode = (mnXmlNamespace == XML_wps ? XML_wsp : XML_cxnSp);
    pFS->startElementNS(mnXmlNamespace, nShapeNode);

    if (mnXmlNamespace == XML_wps)
    {
        // non visual connector shape drawing properties
        pFS->singleElementNS(mnXmlNamespace, XML_cNvCnPr);
    }
    else
    {
        // non visual shape properties
        pFS->startElementNS(mnXmlNamespace, XML_nvCxnSpPr);
        pFS->startElementNS(mnXmlNamespace, XML_cNvPr,
            XML_id, OString::number(GetNewShapeID(xShape)),
            XML_name, GetShapeName(xShape));
        AddExtLst(pFS, rXPropSet);
        pFS->endElementNS(mnXmlNamespace, XML_cNvPr);
        // non visual connector shape drawing properties
        pFS->startElementNS(mnXmlNamespace, XML_cNvCxnSpPr);

        if (GetShapeID(rXShapeA) == -1)
            GetNewShapeID(rXShapeA);
        if (GetShapeID(rXShapeB) == -1)
            GetNewShapeID(rXShapeB);
        WriteConnectorConnections(nStartGlueId, nEndGlueId, GetShapeID(rXShapeA), GetShapeID(rXShapeB));
        pFS->endElementNS(mnXmlNamespace, XML_cNvCxnSpPr);
        if (GetDocumentType() == DOCUMENT_PPTX)
            pFS->singleElementNS(mnXmlNamespace, XML_nvPr);
        pFS->endElementNS(mnXmlNamespace, XML_nvCxnSpPr);
    }

    // visual shape properties
    pFS->startElementNS(mnXmlNamespace, XML_spPr);
    WriteTransformation( xShape, aRect, XML_a, bFlipH, bFlipV, nAngle );
    // TODO: write adjustments (ppt export doesn't work well there either)
    WritePresetShape( sGeometry.toUtf8(), aAdjustValueList);
    Reference< XPropertySet > xShapeProps( xShape, UNO_QUERY );
    if( xShapeProps.is() )
        WriteOutline( xShapeProps );
    pFS->endElementNS( mnXmlNamespace, XML_spPr );

    // connector shape (cxnSp) cannot contain text (txBody) (according to schema)
    if( nShapeNode != XML_cxnSp )
    {
        // write text
        WriteTextBox( xShape, mnXmlNamespace );
    }

    pFS->endElementNS(mnXmlNamespace, nShapeNode);

    return *this;
}

ShapeExport& ShapeExport::WriteLineShape( const Reference< XShape >& xShape )
{
    bool bFlipH = false;
    bool bFlipV = false;

    SAL_INFO("oox.shape", "write line shape");

    FSHelperPtr pFS = GetFS();

    pFS->startElementNS(mnXmlNamespace, (GetDocumentType() != DOCUMENT_DOCX || mbUserShapes ? XML_sp : XML_wsp));

    tools::PolyPolygon aPolyPolygon = EscherPropertyContainer::GetPolyPolygon( xShape );
    if( aPolyPolygon.Count() == 1 && aPolyPolygon[ 0 ].GetSize() == 2)
    {
        const tools::Polygon& rPoly = aPolyPolygon[ 0 ];

        bFlipH = ( rPoly[ 0 ].X() > rPoly[ 1 ].X() );
        bFlipV = ( rPoly[ 0 ].Y() > rPoly[ 1 ].Y() );
    }

    Reference<XPropertySet> const xShapeProps(xShape, UNO_QUERY);
    // non visual shape properties
    if (GetDocumentType() != DOCUMENT_DOCX || mbUserShapes)
    {
        pFS->startElementNS(mnXmlNamespace, XML_nvSpPr);
        pFS->startElementNS(mnXmlNamespace, XML_cNvPr,
                              XML_id, OString::number(GetNewShapeID(xShape)),
                              XML_name, GetShapeName(xShape));
        AddExtLst(pFS, xShapeProps);
        pFS->endElementNS(mnXmlNamespace, XML_cNvPr);
    }
    pFS->singleElementNS( mnXmlNamespace, XML_cNvSpPr );
    if (GetDocumentType() != DOCUMENT_DOCX || mbUserShapes)
    {
        WriteNonVisualProperties( xShape );
        pFS->endElementNS( mnXmlNamespace, XML_nvSpPr );
    }

    // visual shape properties
    pFS->startElementNS(mnXmlNamespace, XML_spPr);
    WriteShapeTransformation( xShape, XML_a, bFlipH, bFlipV, true);
    WritePresetShape( "line"_ostr );
    if( xShapeProps.is() )
        WriteOutline( xShapeProps );
    pFS->endElementNS( mnXmlNamespace, XML_spPr );

    //write style
    pFS->startElementNS(mnXmlNamespace, XML_style);
    WriteShapeStyle( xShapeProps );
    pFS->endElementNS( mnXmlNamespace, XML_style );

    // write text
    WriteTextBox( xShape, mnXmlNamespace );

    pFS->endElementNS( mnXmlNamespace, (GetDocumentType() != DOCUMENT_DOCX || mbUserShapes ? XML_sp : XML_wsp) );

    return *this;
}

ShapeExport& ShapeExport::WriteNonVisualDrawingProperties( const Reference< XShape >& xShape, const char* pName )
{
    FSHelperPtr pFS = GetFS();

    Reference<XPropertySet> const xShapeProps(xShape, UNO_QUERY);
    pFS->startElementNS(mnXmlNamespace, XML_cNvPr,
                              XML_id, OString::number(GetNewShapeID(xShape)),
                              XML_name, pName );
    AddExtLst(pFS, xShapeProps);
    pFS->endElementNS(mnXmlNamespace, XML_cNvPr);

    return *this;
}

ShapeExport& ShapeExport::WriteNonVisualProperties( const Reference< XShape >& )
{
    // Override to generate //nvPr elements.
    return *this;
}

ShapeExport& ShapeExport::WriteRectangleShape( const Reference< XShape >& xShape )
{
    SAL_INFO("oox.shape", "write rectangle shape");

    FSHelperPtr pFS = GetFS();

    pFS->startElementNS(mnXmlNamespace, (GetDocumentType() != DOCUMENT_DOCX || mbUserShapes ? XML_sp : XML_wsp));

    sal_Int32 nRadius = 0;

    Reference< XPropertySet > xShapeProps( xShape, UNO_QUERY );
    if( xShapeProps.is() )
    {
        xShapeProps->getPropertyValue( u"CornerRadius"_ustr ) >>= nRadius;
    }

    if( nRadius )
    {
        nRadius = MapSize( awt::Size( nRadius, 0 ) ).Width;
    }
    //TODO: use nRadius value more precisely than just deciding whether to use
    // "rect" or "roundRect" preset shape below

    // non visual shape properties
    if (GetDocumentType() == DOCUMENT_DOCX && !mbUserShapes)
        pFS->singleElementNS(mnXmlNamespace, XML_cNvSpPr);
    pFS->startElementNS(mnXmlNamespace, XML_nvSpPr);
    pFS->startElementNS(mnXmlNamespace, XML_cNvPr,
                          XML_id, OString::number(GetNewShapeID(xShape)),
                          XML_name, GetShapeName(xShape));
    AddExtLst(pFS, xShapeProps);
    pFS->endElementNS(mnXmlNamespace, XML_cNvPr);
    pFS->singleElementNS(mnXmlNamespace, XML_cNvSpPr);
    WriteNonVisualProperties( xShape );
    pFS->endElementNS( mnXmlNamespace, XML_nvSpPr );

    // visual shape properties
    pFS->startElementNS(mnXmlNamespace, XML_spPr);
    WriteShapeTransformation( xShape, XML_a );
    WritePresetShape( nRadius == 0 ? "rect" : "roundRect" );
    Reference< XPropertySet > xProps( xShape, UNO_QUERY );
    if( xProps.is() )
    {
        WriteFill( xProps );
        WriteOutline( xProps );
    }
    pFS->endElementNS( mnXmlNamespace, XML_spPr );

    // write text
    WriteTextBox( xShape, mnXmlNamespace );

    pFS->endElementNS( mnXmlNamespace, (GetDocumentType() != DOCUMENT_DOCX || mbUserShapes ? XML_sp : XML_wsp) );

    return *this;
}


typedef ShapeExport& (ShapeExport::*ShapeConverter)( const Reference< XShape >& );
typedef std::unordered_map< const char*, ShapeConverter, rtl::CStringHash, rtl::CStringEqual> NameToConvertMapType;

namespace
{

constexpr auto constMap = frozen::make_unordered_map<std::u16string_view, ShapeConverter>(
{
    { u"com.sun.star.drawing.CaptionShape", &ShapeExport::WriteTextShape },
    { u"com.sun.star.drawing.ClosedBezierShape", &ShapeExport::WriteClosedPolyPolygonShape },
    { u"com.sun.star.drawing.ConnectorShape", &ShapeExport::WriteConnectorShape },
    { u"com.sun.star.drawing.CustomShape", &ShapeExport::WriteCustomShape },
    { u"com.sun.star.drawing.EllipseShape", &ShapeExport::WriteEllipseShape },
    { u"com.sun.star.drawing.GraphicObjectShape", &ShapeExport::WriteGraphicObjectShape },
    { u"com.sun.star.drawing.LineShape", &ShapeExport::WriteLineShape },
    { u"com.sun.star.drawing.MediaShape", &ShapeExport::WriteGraphicObjectShape },
    { u"com.sun.star.drawing.OpenBezierShape", &ShapeExport::WriteOpenPolyPolygonShape },
    { u"com.sun.star.drawing.PolyPolygonShape", &ShapeExport::WriteClosedPolyPolygonShape },
    { u"com.sun.star.drawing.PolyLineShape", &ShapeExport::WriteOpenPolyPolygonShape },
    { u"com.sun.star.drawing.RectangleShape", &ShapeExport::WriteRectangleShape },
    { u"com.sun.star.drawing.OLE2Shape", &ShapeExport::WriteOLE2Shape },
    { u"com.sun.star.drawing.TableShape", &ShapeExport::WriteTableShape },
    { u"com.sun.star.drawing.TextShape", &ShapeExport::WriteTextShape },
    { u"com.sun.star.drawing.GroupShape", &ShapeExport::WriteGroupShape },
    { u"com.sun.star.presentation.GraphicObjectShape", &ShapeExport::WriteGraphicObjectShape },
    { u"com.sun.star.presentation.MediaShape", &ShapeExport::WriteGraphicObjectShape },
    { u"com.sun.star.presentation.ChartShape", &ShapeExport::WriteOLE2Shape },
    { u"com.sun.star.presentation.OLE2Shape", &ShapeExport::WriteOLE2Shape },
    { u"com.sun.star.presentation.TableShape", &ShapeExport::WriteTableShape },
    { u"com.sun.star.presentation.TextShape", &ShapeExport::WriteTextShape },
    { u"com.sun.star.presentation.DateTimeShape", &ShapeExport::WriteTextShape },
    { u"com.sun.star.presentation.FooterShape", &ShapeExport::WriteTextShape },
    { u"com.sun.star.presentation.HeaderShape", &ShapeExport::WriteTextShape },
    { u"com.sun.star.presentation.NotesShape", &ShapeExport::WriteTextShape },
    { u"com.sun.star.presentation.OutlinerShape", &ShapeExport::WriteTextShape },
    { u"com.sun.star.presentation.SlideNumberShape", &ShapeExport::WriteTextShape },
    { u"com.sun.star.presentation.TitleTextShape", &ShapeExport::WriteTextShape },
    { u"com.sun.star.presentation.SubtitleShape", &ShapeExport::WriteTextShape }
});

} // end anonymous namespace


bool ShapeExport::IsShapeTypeKnown(const Reference<XShape>& xShape)
{
    if (!xShape)
        return false;

    const OUString sShapeType = xShape->getShapeType();
    return constMap.contains(sShapeType);
}

ShapeExport& ShapeExport::WriteShape( const Reference< XShape >& xShape )
{
    if (!xShape)
        throw lang::IllegalArgumentException();

    OUString sShapeType = xShape->getShapeType();
    SAL_INFO("oox.shape", "write shape: " << sShapeType);
    auto aConverterIterator = constMap.find(sShapeType);
    if (aConverterIterator == constMap.end())
    {
        SAL_INFO("oox.shape", "unknown shape");
        return WriteUnknownShape( xShape );
    }

    if (GetDocumentType() == DOCUMENT_PPTX)
    {
        Reference< XPropertySet > xShapeProperties(xShape, UNO_QUERY);
        if (xShapeProperties && xShapeProperties->getPropertySetInfo()
            && xShapeProperties->getPropertySetInfo()->hasPropertyByName(u"IsPresentationObject"_ustr)
            && xShapeProperties->getPropertyValue(u"IsPresentationObject"_ustr).hasValue())
            mbPlaceholder = xShapeProperties->getPropertyValue(u"IsPresentationObject"_ustr).get<bool>();
    }

    (this->*(aConverterIterator->second))(xShape);

    return *this;
}

static bool lcl_isTextBox(const Reference<XInterface>& xIface)
{
    uno::Reference<beans::XPropertySet> xPropertySet(xIface, uno::UNO_QUERY);
    if (!xPropertySet.is())
        return false;
    uno::Reference<beans::XPropertySetInfo> xPropertySetInfo = xPropertySet->getPropertySetInfo();
    if (!xPropertySetInfo->hasPropertyByName(u"TextBox"_ustr))
       return false;
    css::uno::Any aTextBox(xPropertySet->getPropertyValue(u"TextBox"_ustr));
    if (!aTextBox.hasValue())
       return false;
    return aTextBox.get<bool>();
}

ShapeExport& ShapeExport::WriteTextBox( const Reference< XInterface >& xIface, sal_Int32 nXmlNamespace, bool bWritePropertiesAsLstStyles )
{
    // In case this shape has an associated textbox, then export that, and we're done.
    if (GetDocumentType() == DOCUMENT_DOCX && !mbUserShapes && GetTextExport())
    {
        if (lcl_isTextBox(xIface))
        {
            GetTextExport()->WriteTextBox(uno::Reference<drawing::XShape>(xIface, uno::UNO_QUERY_THROW));
            WriteText( xIface, /*bBodyPr=*/true, /*bText=*/false, /*nXmlNamespace=*/nXmlNamespace );
            return *this;
        }
    }

    Reference< XText > xXText( xIface, UNO_QUERY );
    if( (NonEmptyText( xIface ) || GetDocumentType() == DOCUMENT_PPTX)
        && xXText.is() )
    {
        FSHelperPtr pFS = GetFS();

        pFS->startElementNS(nXmlNamespace,
                            (GetDocumentType() != DOCUMENT_DOCX || mbUserShapes ? XML_txBody : XML_txbx));
        WriteText(xIface, /*bBodyPr=*/(GetDocumentType() != DOCUMENT_DOCX || mbUserShapes), /*bText=*/true,
                  /*nXmlNamespace=*/0, /*bWritePropertiesAsLstStyles=*/bWritePropertiesAsLstStyles);
        pFS->endElementNS( nXmlNamespace, (GetDocumentType() != DOCUMENT_DOCX || mbUserShapes ? XML_txBody : XML_txbx) );
        if (GetDocumentType() == DOCUMENT_DOCX && !mbUserShapes)
            WriteText( xIface, /*bBodyPr=*/true, /*bText=*/false, /*nXmlNamespace=*/nXmlNamespace );
    }
    else if (GetDocumentType() == DOCUMENT_DOCX && !mbUserShapes)
        mpFS->singleElementNS(nXmlNamespace, XML_bodyPr);

    return *this;
}

void ShapeExport::WriteTable( const Reference< XShape >& rXShape  )
{
    Reference< XTable > xTable;
    Reference< XPropertySet > xPropSet( rXShape, UNO_QUERY );

    mpFS->startElementNS(XML_a, XML_graphic);
    mpFS->startElementNS(XML_a, XML_graphicData,
                         XML_uri, "http://schemas.openxmlformats.org/drawingml/2006/table");

    if ( xPropSet.is() && ( xPropSet->getPropertyValue( u"Model"_ustr ) >>= xTable ) )
    {
        mpFS->startElementNS(XML_a, XML_tbl);
        mpFS->startElementNS(XML_a, XML_tblPr);
        WriteShapeEffects(xPropSet);
        mpFS->endElementNS(XML_a, XML_tblPr);

        Reference< container::XIndexAccess > xColumns( xTable->getColumns(), UNO_QUERY_THROW );
        Reference< container::XIndexAccess > xRows( xTable->getRows(), UNO_QUERY_THROW );
        sal_uInt16 nRowCount = static_cast< sal_uInt16 >( xRows->getCount() );
        sal_uInt16 nColumnCount = static_cast< sal_uInt16 >( xColumns->getCount() );

        mpFS->startElementNS(XML_a, XML_tblGrid);

        for ( sal_Int32 x = 0; x < nColumnCount; x++ )
        {
            Reference< XPropertySet > xColPropSet( xColumns->getByIndex( x ), UNO_QUERY_THROW );
            sal_Int32 nWidth(0);
            xColPropSet->getPropertyValue( u"Width"_ustr ) >>= nWidth;

            mpFS->singleElementNS(XML_a, XML_gridCol,
                                  XML_w, OString::number(oox::drawingml::convertHmmToEmu(nWidth)));
        }

        mpFS->endElementNS( XML_a, XML_tblGrid );

        // map for holding the transpose index of the merged cells and pair<parentTransposeIndex, parentCell>
        typedef std::unordered_map<sal_Int32, std::pair<sal_Int32, Reference< XMergeableCell> > > transposeTableMap;
        transposeTableMap mergedCellMap;

        for( sal_Int32 nRow = 0; nRow < nRowCount; nRow++ )
        {
            Reference< XPropertySet > xRowPropSet( xRows->getByIndex( nRow ), UNO_QUERY_THROW );
            sal_Int32 nRowHeight(0);

            xRowPropSet->getPropertyValue( u"Height"_ustr ) >>= nRowHeight;

            mpFS->startElementNS(XML_a, XML_tr,
                XML_h, OString::number(oox::drawingml::convertHmmToEmu(nRowHeight)));
            for( sal_Int32 nColumn = 0; nColumn < nColumnCount; nColumn++ )
            {
                Reference< XMergeableCell > xCell( xTable->getCellByPosition( nColumn, nRow ),
                                                   UNO_QUERY_THROW );
                sal_Int32 transposedIndexofCell = (nRow * nColumnCount) + nColumn;

                //assume we will open a cell, set to false below if we won't
                bool bCellOpened = true;

                if(xCell->getColumnSpan() > 1 && xCell->getRowSpan() > 1)
                {
                    // having both : horizontal and vertical merge
                    mpFS->startElementNS(XML_a, XML_tc,
                                         XML_gridSpan, OString::number(xCell->getColumnSpan()),
                                         XML_rowSpan, OString::number(xCell->getRowSpan()));
                    // since, XMergeableCell doesn't have the information about
                    // cell having hMerge or vMerge.
                    // So, Populating the merged cell map in-order to use it to
                    // decide the attribute for the individual cell.
                    for(sal_Int32 columnIndex = nColumn; columnIndex < nColumn+xCell->getColumnSpan(); ++columnIndex)
                    {
                        for(sal_Int32 rowIndex = nRow; rowIndex < nRow+xCell->getRowSpan(); ++rowIndex)
                        {
                            sal_Int32 transposeIndexForMergeCell =
                                (rowIndex * nColumnCount) + columnIndex;
                            mergedCellMap[transposeIndexForMergeCell] =
                                std::make_pair(transposedIndexofCell, xCell);
                        }
                    }

                }
                else if(xCell->getColumnSpan() > 1)
                {
                    // having : horizontal merge
                    mpFS->startElementNS(XML_a, XML_tc,
                                         XML_gridSpan, OString::number(xCell->getColumnSpan()));
                    for(sal_Int32 columnIndex = nColumn; columnIndex < nColumn + xCell->getColumnSpan(); ++columnIndex) {
                        sal_Int32 transposeIndexForMergeCell = (nRow*nColumnCount) + columnIndex;
                        mergedCellMap[transposeIndexForMergeCell] =
                            std::make_pair(transposedIndexofCell, xCell);
                    }
                }
                else if(xCell->getRowSpan() > 1)
                {
                    // having : vertical merge
                    mpFS->startElementNS(XML_a, XML_tc,
                                         XML_rowSpan, OString::number(xCell->getRowSpan()));

                    for(sal_Int32 rowIndex = nRow; rowIndex < nRow + xCell->getRowSpan(); ++rowIndex) {
                        sal_Int32 transposeIndexForMergeCell = (rowIndex*nColumnCount) + nColumn;
                        mergedCellMap[transposeIndexForMergeCell] =
                            std::make_pair(transposedIndexofCell, xCell);
                    }
                }
                else
                {
                    // now, the cell can be an independent cell or
                    // it can be a cell which is been merged to some parent cell
                    if(!xCell->isMerged())
                    {
                        // independent cell
                        mpFS->startElementNS(XML_a, XML_tc);
                    }
                    else
                    {
                        // it a merged cell to some parent cell
                        // find the parent cell for the current cell at hand
                        transposeTableMap::iterator it = mergedCellMap.find(transposedIndexofCell);
                        if(it != mergedCellMap.end())
                        {
                            sal_Int32 transposeIndexOfParent = it->second.first;
                            Reference< XMergeableCell > parentCell = it->second.second;
                            // finding the row and column index for the parent cell from transposed index
                            sal_Int32 parentColumnIndex = transposeIndexOfParent % nColumnCount;
                            sal_Int32 parentRowIndex = transposeIndexOfParent / nColumnCount;
                            if(nColumn == parentColumnIndex)
                            {
                                // the cell is vertical merge and it might have gridspan
                                if(parentCell->getColumnSpan() > 1)
                                {
                                    // vMerge and has gridSpan
                                    mpFS->startElementNS(XML_a, XML_tc,
                                                         XML_vMerge, OString::number(1),
                                                         XML_gridSpan, OString::number(xCell->getColumnSpan()));
                                }
                                else
                                {
                                    // only vMerge
                                    mpFS->startElementNS(XML_a, XML_tc,
                                                         XML_vMerge, OString::number(1));
                                }
                            }
                            else if(nRow == parentRowIndex)
                            {
                                // the cell is horizontal merge and it might have rowspan
                                if(parentCell->getRowSpan() > 1)
                                {
                                    // hMerge and has rowspan
                                    mpFS->startElementNS(XML_a, XML_tc,
                                                         XML_hMerge, OString::number(1),
                                                         XML_rowSpan, OString::number(xCell->getRowSpan()));
                                }
                                else
                                {
                                    // only hMerge
                                    mpFS->startElementNS(XML_a, XML_tc,
                                                         XML_hMerge, OString::number(1));
                                }
                            }
                            else
                            {
                                // has hMerge and vMerge
                                mpFS->startElementNS(XML_a, XML_tc,
                                                     XML_vMerge, OString::number(1),
                                                     XML_hMerge, OString::number(1));
                            }
                        }
                        else
                            bCellOpened = false;
                    }
                }

                if (bCellOpened)
                {
                    WriteTextBox( xCell, XML_a );

                    Reference< XPropertySet > xCellPropSet(xCell, UNO_QUERY_THROW);
                    WriteTableCellProperties(xCellPropSet);

                    mpFS->endElementNS( XML_a, XML_tc );
                }
            }

            mpFS->endElementNS( XML_a, XML_tr );
        }

        mpFS->endElementNS( XML_a, XML_tbl );
    }

    mpFS->endElementNS( XML_a, XML_graphicData );
    mpFS->endElementNS( XML_a, XML_graphic );
}

void ShapeExport::WriteTableCellProperties(const Reference< XPropertySet>& xCellPropSet)
{
    sal_Int32 nLeftMargin(0), nRightMargin(0);
    sal_Int32 nTopMargin(0);
    sal_Int32 nBottomMargin(0);
    TextVerticalAdjust eVerticalAlignment;
    const char* sVerticalAlignment;

    Any aLeftMargin = xCellPropSet->getPropertyValue(u"TextLeftDistance"_ustr);
    aLeftMargin >>= nLeftMargin;

    Any aRightMargin = xCellPropSet->getPropertyValue(u"TextRightDistance"_ustr);
    aRightMargin >>= nRightMargin;

    Any aTopMargin = xCellPropSet->getPropertyValue(u"TextUpperDistance"_ustr);
    aTopMargin >>= nTopMargin;

    Any aBottomMargin = xCellPropSet->getPropertyValue(u"TextLowerDistance"_ustr);
    aBottomMargin >>= nBottomMargin;

    Any aVerticalAlignment = xCellPropSet->getPropertyValue(u"TextVerticalAdjust"_ustr);
    aVerticalAlignment >>= eVerticalAlignment;
    sVerticalAlignment = GetTextVerticalAdjust(eVerticalAlignment);

    sal_Int32 nRotateAngle = 0;
    Any aRotateAngle = xCellPropSet->getPropertyValue(u"RotateAngle"_ustr);
    aRotateAngle >>= nRotateAngle;
    std::optional<OString> aTextVerticalValue = GetTextVerticalType(nRotateAngle);

    Sequence<PropertyValue> aGrabBag;
    if( !aTextVerticalValue &&
        (xCellPropSet->getPropertyValue(u"CellInteropGrabBag"_ustr) >>= aGrabBag) )
    {
        for (auto const& rIt : aGrabBag)
        {
            if (rIt.Name == "mso-tcPr-vert-value")
            {
                aTextVerticalValue = rIt.Value.get<OUString>().toUtf8();
                break;
            }
        }
    }

    mpFS->startElementNS(XML_a, XML_tcPr, XML_anchor, sVerticalAlignment,
    XML_vert, aTextVerticalValue,
    XML_marL, OString::number(oox::drawingml::convertHmmToEmu(nLeftMargin)),
    XML_marR, OString::number(oox::drawingml::convertHmmToEmu(nRightMargin)),
    XML_marT, OString::number(oox::drawingml::convertHmmToEmu(nTopMargin)),
    XML_marB, OString::number(oox::drawingml::convertHmmToEmu(nBottomMargin)));

    // Write background fill for table cell.
    // TODO
    // tcW : Table cell width
    WriteTableCellBorders(xCellPropSet);
    DrawingML::WriteFill(xCellPropSet);
    mpFS->endElementNS( XML_a, XML_tcPr );
}

void ShapeExport::WriteBorderLine(const sal_Int32 xml_line_element, const BorderLine2& rBorderLine)
{
// While importing the table cell border line width, it converts EMU->Hmm then divided result by 2.
// To get original value of LineWidth need to multiple by 2.
    sal_Int32 nBorderWidth = rBorderLine.LineWidth;
    nBorderWidth *= 2;
    nBorderWidth = oox::drawingml::convertHmmToEmu( nBorderWidth );

    if ( nBorderWidth > 0 )
    {
        mpFS->startElementNS(XML_a, xml_line_element, XML_w, OString::number(nBorderWidth));
        if ( rBorderLine.Color == sal_Int32( COL_AUTO ) )
            mpFS->singleElementNS(XML_a, XML_noFill);
        else
        {
            ::Color nColor(ColorTransparency, rBorderLine.Color);
            if (nColor.IsTransparent())
                DrawingML::WriteSolidFill( nColor, nColor.GetAlpha() );
            else
                DrawingML::WriteSolidFill( nColor );
        }

        OUString sBorderStyle;
        sal_Int16 nStyle = rBorderLine.LineStyle;
        mAny.setValue(&nStyle, cppu::UnoType<sal_Int16>::get());
        switch (*o3tl::doAccess<sal_Int16>(mAny))
        {
            case ::table::BorderLineStyle::SOLID:
                sBorderStyle = "solid";
                break;
            case ::table::BorderLineStyle::DOTTED:
                sBorderStyle = "dot";
                break;
            case ::table::BorderLineStyle::DASHED:
                sBorderStyle = "dash";
                break;
            case ::table::BorderLineStyle::DASH_DOT:
                sBorderStyle = "dashDot";
                break;
            case ::table::BorderLineStyle::DASH_DOT_DOT:
                sBorderStyle = "sysDashDotDot";
                break;
            default:
                // There is no equivalent so pick something else, since we cannot have empty.
                // There are very few default styles for pptx.
                sBorderStyle = "solid";
                break;
        }
        assert(!sBorderStyle.isEmpty() && "empty is not a valid value for PPTX");
        mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, sBorderStyle);
        mpFS->endElementNS(XML_a, xml_line_element);
    }
    else if( nBorderWidth == 0)
    {
        mpFS->startElementNS(XML_a, xml_line_element);
        mpFS->singleElementNS(XML_a, XML_noFill);
        mpFS->endElementNS(XML_a, xml_line_element);
    }
}

void ShapeExport::WriteTableCellBorders(const Reference< XPropertySet>& xCellPropSet)
{
    BorderLine2 aBorderLine;

// lnL - Left Border Line Properties of table cell
    xCellPropSet->getPropertyValue(u"LeftBorder"_ustr) >>= aBorderLine;
    WriteBorderLine( XML_lnL, aBorderLine );

// lnR - Right Border Line Properties of table cell
    xCellPropSet->getPropertyValue(u"RightBorder"_ustr) >>= aBorderLine;
    WriteBorderLine( XML_lnR, aBorderLine );

// lnT - Top Border Line Properties of table cell
    xCellPropSet->getPropertyValue(u"TopBorder"_ustr) >>= aBorderLine;
    WriteBorderLine( XML_lnT, aBorderLine );

// lnB - Bottom Border Line Properties of table cell
    xCellPropSet->getPropertyValue(u"BottomBorder"_ustr) >>= aBorderLine;
    WriteBorderLine( XML_lnB, aBorderLine );
}

ShapeExport& ShapeExport::WriteTableShape( const Reference< XShape >& xShape )
{
    FSHelperPtr pFS = GetFS();

    pFS->startElementNS(mnXmlNamespace, XML_graphicFrame);

    pFS->startElementNS(mnXmlNamespace, XML_nvGraphicFramePr);

    Reference<XPropertySet> const xShapeProps(xShape, UNO_QUERY);
    pFS->startElementNS(mnXmlNamespace, XML_cNvPr,
                          XML_id, OString::number(GetNewShapeID(xShape)),
                          XML_name,   GetShapeName(xShape));
    AddExtLst(pFS, xShapeProps);
    pFS->endElementNS(mnXmlNamespace, XML_cNvPr);

    pFS->singleElementNS(mnXmlNamespace, XML_cNvGraphicFramePr);

    if( GetDocumentType() == DOCUMENT_PPTX )
        pFS->singleElementNS(mnXmlNamespace, XML_nvPr);
    pFS->endElementNS( mnXmlNamespace, XML_nvGraphicFramePr );

    WriteShapeTransformation( xShape, mnXmlNamespace );
    WriteTable( xShape );

    pFS->endElementNS( mnXmlNamespace, XML_graphicFrame );

    return *this;
}

ShapeExport& ShapeExport::WriteTextShape( const Reference< XShape >& xShape )
{
    FSHelperPtr pFS = GetFS();
    Reference<XPropertySet> xShapeProps(xShape, UNO_QUERY);

    pFS->startElementNS(mnXmlNamespace, (GetDocumentType() != DOCUMENT_DOCX || mbUserShapes ? XML_sp : XML_wsp));

    // non visual shape properties
    if (GetDocumentType() != DOCUMENT_DOCX || mbUserShapes)
    {
        pFS->startElementNS(mnXmlNamespace, XML_nvSpPr);
        pFS->startElementNS(mnXmlNamespace, XML_cNvPr,
                              XML_id, OString::number(GetNewShapeID(xShape)),
                              XML_name, GetShapeName(xShape));
        OUString sURL;
        if (GetProperty(xShapeProps, u"URL"_ustr))
            mAny >>= sURL;

        if (!sURL.isEmpty())
        {
            OUString sRelId = mpFB->addRelation(mpFS->getOutputStream(),
                    oox::getRelationship(Relationship::HYPERLINK),
                    mpURLTransformer->getTransformedString(sURL),
                    mpURLTransformer->isExternalURL(sURL));

            mpFS->singleElementNS(XML_a, XML_hlinkClick, FSNS(XML_r, XML_id), sRelId);
        }
        AddExtLst(pFS, xShapeProps);
        pFS->endElementNS(mnXmlNamespace, XML_cNvPr);
    }
    pFS->singleElementNS(mnXmlNamespace, XML_cNvSpPr, XML_txBox, "1");
    if (GetDocumentType() != DOCUMENT_DOCX || mbUserShapes)
    {
        WriteNonVisualProperties( xShape );
        pFS->endElementNS( mnXmlNamespace, XML_nvSpPr );
    }

    // visual shape properties
    pFS->startElementNS(mnXmlNamespace, XML_spPr);
    WriteShapeTransformation( xShape, XML_a );
    WritePresetShape( "rect"_ostr );
    uno::Reference<beans::XPropertySet> xPropertySet(xShape, UNO_QUERY);
    if (!IsFontworkShape(xShapeProps)) // Fontwork needs fill and outline in run properties instead.
    {
        WriteBlipOrNormalFill(xPropertySet, u"Graphic"_ustr, xShape->getSize());
        WriteOutline(xPropertySet);
        WriteShapeEffects(xPropertySet);
    }
    pFS->endElementNS( mnXmlNamespace, XML_spPr );

    WriteTextBox( xShape, mnXmlNamespace );

    pFS->endElementNS( mnXmlNamespace, (GetDocumentType() != DOCUMENT_DOCX || mbUserShapes ? XML_sp : XML_wsp) );

    return *this;
}

void ShapeExport::WriteMathShape(Reference<XShape> const& xShape)
{
    Reference<XPropertySet> const xPropSet(xShape, UNO_QUERY);
    assert(xPropSet.is());
    Reference<XModel> xMathModel;
    xPropSet->getPropertyValue(u"Model"_ustr) >>= xMathModel;
    assert(xMathModel.is());
    assert(GetDocumentType() != DOCUMENT_DOCX); // should be written in DocxAttributeOutput
    SAL_WARN_IF(GetDocumentType() == DOCUMENT_XLSX, "oox.shape", "Math export to XLSX isn't tested, should it happen here?");
    const OString cNvPr_id = OString::number(GetNewShapeID(xShape));
    const OUString shapeName = GetShapeName(xShape);

    // ECMA standard does not actually allow oMath outside of
    // WordProcessingML so write a MCE like PPT 2010 does
    mpFS->startElementNS(XML_mc, XML_AlternateContent);
    mpFS->startElementNS(XML_mc, XML_Choice,
        FSNS(XML_xmlns, XML_a14), mpFB->getNamespaceURL(OOX_NS(a14)),
        XML_Requires, "a14");
    mpFS->startElementNS(mnXmlNamespace, XML_sp);
    mpFS->startElementNS(mnXmlNamespace, XML_nvSpPr);
    mpFS->startElementNS(mnXmlNamespace, XML_cNvPr, XML_id, cNvPr_id, XML_name, shapeName);
    AddExtLst(mpFS, xPropSet);
    mpFS->endElementNS(mnXmlNamespace, XML_cNvPr);
    mpFS->singleElementNS(mnXmlNamespace, XML_cNvSpPr, XML_txBox, "1");
    mpFS->singleElementNS(mnXmlNamespace, XML_nvPr);
    mpFS->endElementNS(mnXmlNamespace, XML_nvSpPr);
    mpFS->startElementNS(mnXmlNamespace, XML_spPr);
    WriteShapeTransformation(xShape, XML_a);
    WritePresetShape("rect"_ostr);
    mpFS->endElementNS(mnXmlNamespace, XML_spPr);
    mpFS->startElementNS(mnXmlNamespace, XML_txBody);
    mpFS->startElementNS(XML_a, XML_bodyPr);
    mpFS->endElementNS(XML_a, XML_bodyPr);
    mpFS->startElementNS(XML_a, XML_p);
    mpFS->startElementNS(XML_a14, XML_m);

    oox::FormulaImExportBase *const pMagic(
        dynamic_cast<oox::FormulaImExportBase*>(xMathModel.get()));
    assert(pMagic);
    pMagic->writeFormulaOoxml(GetFS(), GetFB()->getVersion(), GetDocumentType(),
        FormulaImExportBase::eFormulaAlign::INLINE);

    mpFS->endElementNS(XML_a14, XML_m);
    mpFS->endElementNS(XML_a, XML_p);
    mpFS->endElementNS(mnXmlNamespace, XML_txBody);
    mpFS->endElementNS(mnXmlNamespace, XML_sp);
    mpFS->endElementNS(XML_mc, XML_Choice);
    mpFS->startElementNS(XML_mc, XML_Fallback);

    svt::EmbeddedObjectRef ref(
        xPropSet->getPropertyValue(u"EmbeddedObject"_ustr).query<css::embed::XEmbeddedObject>(),
        embed::Aspects::MSOLE_CONTENT);
    if (auto* graphic = ref.GetGraphic(); graphic && graphic->GetType() != GraphicType::NONE)
    {
        if (OUString r_id = writeGraphicToStorage(*graphic); !r_id.isEmpty())
        {
            mpFS->startElementNS(mnXmlNamespace, XML_sp);
            mpFS->startElementNS(mnXmlNamespace, XML_nvSpPr);
            mpFS->startElementNS(mnXmlNamespace, XML_cNvPr, XML_id, cNvPr_id, XML_name, shapeName);
            AddExtLst(mpFS, xPropSet);
            mpFS->endElementNS(mnXmlNamespace, XML_cNvPr);
            mpFS->singleElementNS(mnXmlNamespace, XML_cNvSpPr, XML_txBox, "1");
            mpFS->singleElementNS(mnXmlNamespace, XML_nvPr);
            mpFS->endElementNS(mnXmlNamespace, XML_nvSpPr);
            mpFS->startElementNS(mnXmlNamespace, XML_spPr);
            WriteShapeTransformation(xShape, XML_a);
            WritePresetShape("rect"_ostr);
            mpFS->startElementNS(XML_a, XML_blipFill);
            mpFS->singleElementNS(XML_a, XML_blip, FSNS(XML_r, XML_embed), r_id);
            mpFS->startElementNS(XML_a, XML_stretch);
            mpFS->singleElementNS(XML_a, XML_fillRect);
            mpFS->endElementNS(XML_a, XML_stretch);
            mpFS->endElementNS(XML_a, XML_blipFill);
            mpFS->endElementNS(mnXmlNamespace, XML_spPr);
            mpFS->endElementNS(mnXmlNamespace, XML_sp);
        }
    }

    mpFS->endElementNS(XML_mc, XML_Fallback);
    mpFS->endElementNS(XML_mc, XML_AlternateContent);
}

static void WriteEmptyGraphic(FSHelperPtr const& pFS, sal_Int32 nGraphicId)
{
    pFS->startElementNS(XML_p, XML_pic);
    pFS->startElementNS(XML_p, XML_nvPicPr);
    pFS->startElementNS(XML_p, XML_cNvPr, XML_id, OUString::number(nGraphicId), XML_name, "",
                        XML_descr, "");
    pFS->endElementNS(XML_p, XML_cNvPr);
    pFS->startElementNS(XML_p, XML_cNvPicPr);
    pFS->endElementNS(XML_p, XML_cNvPicPr);
    pFS->startElementNS(XML_p, XML_nvPr);
    pFS->endElementNS(XML_p, XML_nvPr);
    pFS->endElementNS(XML_p, XML_nvPicPr);
    pFS->startElementNS(XML_p, XML_blipFill);
    pFS->endElementNS(XML_p, XML_blipFill);
    pFS->startElementNS(XML_p, XML_spPr);
    pFS->endElementNS(XML_p, XML_spPr);
    pFS->endElementNS(XML_p, XML_pic);
}

ShapeExport& ShapeExport::WriteOLE2Shape( const Reference< XShape >& xShape )
{
    Reference< XPropertySet > xPropSet( xShape, UNO_QUERY );
    if (!xPropSet.is())
        return *this;

    enum { CHART, MATH, OTHER } eType(OTHER);
    OUString clsid;
    xPropSet->getPropertyValue(u"CLSID"_ustr) >>= clsid;
    if (!clsid.isEmpty())
    {
        SvGlobalName aClassID;
        bool const isValid = aClassID.MakeId(clsid);
        assert(isValid); (void)isValid;
        if (SotExchange::IsChart(aClassID))
            eType = CHART;
        else if (SotExchange::IsMath(aClassID))
            eType = MATH;
    }

    if (CHART == eType)
    {
        Reference< XChartDocument > xChartDoc;
        xPropSet->getPropertyValue(u"Model"_ustr) >>= xChartDoc;
        assert(xChartDoc.is());
        //export the chart
#if !ENABLE_WASM_STRIP_CHART
        // WASM_CHART change
        // TODO: With Chart extracted this cannot really happen since
        // no Chart could've been added at all
        ChartExport aChartExport( mnXmlNamespace, GetFS(), xChartDoc, GetFB(), GetDocumentType() );
        aChartExport.WriteChartObj( xShape, GetNewShapeID( xShape ), ++mnChartCount );
#endif
        return *this;
    }

    if (MATH == eType)
    {
        WriteMathShape(xShape);
        return *this;
    }

    uno::Reference<embed::XEmbeddedObject> const xObj(
        xPropSet->getPropertyValue(u"EmbeddedObject"_ustr), uno::UNO_QUERY);

    if (!xObj.is())
    {
        SAL_WARN("oox.shape", "ShapeExport::WriteOLE2Shape: no object");

        // tdf#152436 Export the preview graphic of the object if the object is missing.
        SdrObject* pSdrOLE2(SdrObject::getSdrObjectFromXShape(xShape));
        if (auto pOle2Obj = dynamic_cast<SdrOle2Obj*>(pSdrOLE2))
        {
            const Graphic* pGraphic = pOle2Obj->GetGraphic();
            if (pGraphic)
                WriteGraphicObjectShapePart(xShape, pGraphic);
        }

        return *this;
    }

    uno::Sequence<beans::PropertyValue> grabBag;
    OUString entryName;
    uno::Reference<container::XChild> xChild(xObj, uno::UNO_QUERY);
    if (xChild)
    {
        uno::Reference<beans::XPropertySet> const xParent(xChild->getParent(), uno::UNO_QUERY);
        if (xParent)
        {
            xParent->getPropertyValue(u"InteropGrabBag"_ustr) >>= grabBag;
            uno::Reference<embed::XEmbedPersist> xEmbedPersist(xObj, uno::UNO_QUERY);
            if (xEmbedPersist)
            {
                // getEntryName() could throw a WrongStateException
                try
                {
                    entryName = xEmbedPersist->getEntryName();
                }
                catch (embed::WrongStateException const &)
                {
                    TOOLS_WARN_EXCEPTION("oox.shape", "ShapeExport::WriteOLE2Shape");
                }
            }
        }
    }

    OUString progID;

    for (auto const& it : grabBag)
    {
        if (it.Name == "EmbeddedObjects")
        {
            uno::Sequence<beans::PropertyValue> objects;
            it.Value >>= objects;
            for (auto const& object : objects)
            {
                if (object.Name == entryName)
                {
                    uno::Sequence<beans::PropertyValue> props;
                    object.Value >>= props;
                    for (auto const& prop : props)
                    {
                        if (prop.Name == "ProgID")
                        {
                            prop.Value >>= progID;
                            break;
                        }
                    }
                    break;
                }
            }
            break;
        }
    }

    OUString sMediaType;
    OUString sRelationType;
    OUString sSuffix;
    const char * pProgID(nullptr);
    OString anotherProgID;

    uno::Reference<io::XInputStream> const xInStream =
        oox::GetOLEObjectStream(
            xObj, progID,
            sMediaType, sRelationType, sSuffix, pProgID);

    OUString sURL;
    OUString sRelId;
    if (!xInStream.is())
    {
        xPropSet->getPropertyValue(u"LinkURL"_ustr) >>= sURL;
        if (sURL.isEmpty())
            return *this;

        sRelId = mpFB->addRelation(mpFS->getOutputStream(),
                                   oox::getRelationship(Relationship::OLEOBJECT), sURL, true);
    }
    else
    {
        if (!pProgID && !progID.isEmpty())
        {
            anotherProgID = OUStringToOString(progID, RTL_TEXTENCODING_UTF8);
            pProgID = anotherProgID.getStr();
        }

        assert(!sMediaType.isEmpty());
        assert(!sRelationType.isEmpty());
        assert(!sSuffix.isEmpty());

        OUString sNumber = OUString::number(++m_nEmbeddedObjects);
        OUString sFileName = u"embeddings/oleObject"_ustr + sNumber + u"."_ustr + sSuffix;
        OUString sFilePath = GetComponentDir() + u"/"_ustr + sFileName;
        uno::Reference<io::XOutputStream> const xOutStream(mpFB->openFragmentStream(sFilePath, sMediaType));
        assert(xOutStream.is()); // no reason why that could fail

        try
        {
            ::comphelper::OStorageHelper::CopyInputToOutput(xInStream, xOutStream);
        }
        catch (uno::Exception const&)
        {
            TOOLS_WARN_EXCEPTION("oox.shape", "ShapeExport::WriteOLEObject");
        }

        sRelId = mpFB->addRelation(
            mpFS->getOutputStream(), sRelationType,
            Concat2View(GetRelationCompPrefix() + sFileName));
    }

    sal_Int64 nAspect;
    bool bShowAsIcon = (xPropSet->getPropertyValue(u"Aspect"_ustr) >>= nAspect)
                       && nAspect == embed::Aspects::MSOLE_ICON;

    mpFS->startElementNS(mnXmlNamespace, XML_graphicFrame);

    mpFS->startElementNS(mnXmlNamespace, XML_nvGraphicFramePr);

    mpFS->startElementNS(mnXmlNamespace, XML_cNvPr,
                           XML_id,     OString::number(GetNewShapeID(xShape)),
                           XML_name,   GetShapeName(xShape));
    AddExtLst(mpFS, xPropSet);
    mpFS->endElementNS(mnXmlNamespace, XML_cNvPr);

    mpFS->singleElementNS(mnXmlNamespace, XML_cNvGraphicFramePr);

    if (GetDocumentType() == DOCUMENT_PPTX)
        mpFS->singleElementNS(mnXmlNamespace, XML_nvPr);
    mpFS->endElementNS( mnXmlNamespace, XML_nvGraphicFramePr );

    WriteShapeTransformation( xShape, mnXmlNamespace );

    mpFS->startElementNS(XML_a, XML_graphic);
    mpFS->startElementNS(XML_a, XML_graphicData,
                         XML_uri, "http://schemas.openxmlformats.org/presentationml/2006/ole");
    if (pProgID)
    {
        mpFS->startElementNS( mnXmlNamespace, XML_oleObj,
                          XML_showAsIcon, sax_fastparser::UseIf("1", bShowAsIcon),
                          XML_progId, pProgID,
                          FSNS(XML_r, XML_id), sRelId,
                          XML_spid, "" );
    }
    else
    {
        mpFS->startElementNS( mnXmlNamespace, XML_oleObj,
//?                                              XML_name, "Document",
                          XML_showAsIcon, sax_fastparser::UseIf("1", bShowAsIcon),
                          FSNS(XML_r, XML_id), sRelId,
                          // The spec says that this is a required attribute, but PowerPoint can only handle an empty value.
                          XML_spid, "" );
    }

    if (sURL.isEmpty())
        mpFS->singleElementNS(mnXmlNamespace, XML_embed);
    else
        mpFS->singleElementNS(mnXmlNamespace, XML_link, XML_updateAutomatic, "1");

    // pic element
    SdrObject* pSdrOLE2(SdrObject::getSdrObjectFromXShape(xShape));
    if (auto pOle2Obj = dynamic_cast<SdrOle2Obj*>(pSdrOLE2))
    {
        const Graphic* pGraphic = pOle2Obj->GetGraphic();
        if (pGraphic)
            WriteGraphicObjectShapePart(xShape, pGraphic);
        else // Required for MSO
            WriteEmptyGraphic(mpFS, GetNewShapeID(xShape));
    }

    mpFS->endElementNS( mnXmlNamespace, XML_oleObj );

    mpFS->endElementNS( XML_a, XML_graphicData );
    mpFS->endElementNS( XML_a, XML_graphic );

    mpFS->endElementNS( mnXmlNamespace, XML_graphicFrame );

    return *this;
}

ShapeExport& ShapeExport::WriteUnknownShape( const Reference< XShape >& )
{
    // Override this method to do something useful.
    return *this;
}

sal_Int32 ShapeExport::GetNewShapeID( const Reference< XShape >& rXShape )
{
    if( !rXShape.is() )
        return -1;

    sal_Int32 nID = GetFB()->GetUniqueId();

    auto it = mpShapeMap->find(rXShape);
    if (it == mpShapeMap->end())
        (*mpShapeMap)[rXShape] = nID;

    return nID;
}

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

sal_Int32 ShapeExport::GetShapeID( const Reference< XShape >& rXShape, ShapeHashMap* pShapeMap )
{
    if( !rXShape.is() )
        return -1;

    ShapeHashMap::const_iterator aIter = pShapeMap->find( rXShape );

    if( aIter == pShapeMap->end() )
        return -1;

    return aIter->second;
}

OUString ShapeExport::GetShapeName(const Reference<XShape>& xShape)
{
    Reference<XPropertySet> rXPropSet(xShape, UNO_QUERY);

    // Empty name keeps the object unnamed.
    OUString sName;

    if (GetProperty(rXPropSet, u"Name"_ustr))
        mAny >>= sName;
    return sName;
}

}

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