/* -*- 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 <algorithm>

#include <optional>

#include <o3tl/safeint.hxx>
#include <oox/vml/vmlshape.hxx>
#include <utility>
#include <vcl/wmfexternal.hxx>

#include <com/sun/star/beans/XPropertySet.hpp>
#include <com/sun/star/drawing/EnhancedCustomShapeAdjustmentValue.hpp>
#include <com/sun/star/drawing/FillStyle.hpp>
#include <com/sun/star/drawing/PointSequenceSequence.hpp>
#include <com/sun/star/drawing/PolyPolygonBezierCoords.hpp>
#include <com/sun/star/drawing/TextVerticalAdjust.hpp>
#include <com/sun/star/drawing/XEnhancedCustomShapeDefaulter.hpp>
#include <com/sun/star/drawing/XShapes.hpp>
#include <com/sun/star/drawing/XControlShape.hpp>
#include <com/sun/star/graphic/XGraphic.hpp>
#include <com/sun/star/table/BorderLine2.hpp>
#include <com/sun/star/text/HoriOrientation.hpp>
#include <com/sun/star/text/RelOrientation.hpp>
#include <com/sun/star/text/SizeType.hpp>
#include <com/sun/star/text/VertOrientation.hpp>
#include <com/sun/star/text/WrapTextMode.hpp>
#include <com/sun/star/lang/XServiceInfo.hpp>
#include <com/sun/star/text/TextContentAnchorType.hpp>
#include <com/sun/star/text/GraphicCrop.hpp>
#include <com/sun/star/security/DocumentDigitalSignatures.hpp>
#include <com/sun/star/security/XDocumentDigitalSignatures.hpp>
#include <com/sun/star/text/WritingMode2.hpp>
#include <com/sun/star/drawing/ColorMode.hpp>
#include <sal/log.hxx>
#include <oox/drawingml/shapepropertymap.hxx>
#include <oox/helper/graphichelper.hxx>
#include <oox/helper/propertyset.hxx>
#include <oox/ole/axcontrol.hxx>
#include <oox/ole/axcontrolfragment.hxx>
#include <oox/ole/oleobjecthelper.hxx>
#include <oox/token/properties.hxx>
#include <oox/token/tokens.hxx>
#include <oox/vml/vmldrawing.hxx>
#include <oox/vml/vmlshapecontainer.hxx>
#include <oox/vml/vmltextbox.hxx>
#include <oox/core/xmlfilterbase.hxx>
#include <oox/helper/containerhelper.hxx>
#include <svx/msdffdef.hxx>
#include <svx/sdtagitm.hxx>
#include <svx/svdobj.hxx>
#include <comphelper/sequence.hxx>
#include <comphelper/processfactory.hxx>
#include <comphelper/propertyvalue.hxx>
#include <comphelper/storagehelper.hxx>
#include <o3tl/string_view.hxx>

using ::com::sun::star::beans::XPropertySet;
using ::com::sun::star::uno::Any;

using namespace ::com::sun::star;
using namespace ::com::sun::star::text;

namespace oox::vml {

using namespace ::com::sun::star::drawing;
using namespace ::com::sun::star::graphic;
using namespace ::com::sun::star::uno;
using namespace ::com::sun::star::io;

using ::oox::core::XmlFilterBase;

namespace {

const sal_Int32 VML_SHAPETYPE_PICTUREFRAME  = 75;
const sal_Int32 VML_SHAPETYPE_HOSTCONTROL   = 201;

awt::Point lclGetAbsPoint( const awt::Point& rRelPoint, const awt::Rectangle& rShapeRect, const awt::Rectangle& rCoordSys )
{
    double fWidthRatio = static_cast< double >( rShapeRect.Width ) / rCoordSys.Width;
    double fHeightRatio = static_cast< double >( rShapeRect.Height ) / rCoordSys.Height;
    awt::Point aAbsPoint;
    aAbsPoint.X = static_cast< sal_Int32 >( rShapeRect.X + fWidthRatio * (rRelPoint.X - rCoordSys.X) + 0.5 );
    aAbsPoint.Y = static_cast< sal_Int32 >( rShapeRect.Y + fHeightRatio * (rRelPoint.Y - rCoordSys.Y) + 0.5 );
    return aAbsPoint;
}

awt::Rectangle lclGetAbsRect( const awt::Rectangle& rRelRect, const awt::Rectangle& rShapeRect, const awt::Rectangle& rCoordSys )
{
    double fWidthRatio = static_cast< double >( rShapeRect.Width ) / rCoordSys.Width;
    double fHeightRatio = static_cast< double >( rShapeRect.Height ) / rCoordSys.Height;
    awt::Rectangle aAbsRect;
    aAbsRect.X = static_cast< sal_Int32 >( rShapeRect.X + fWidthRatio * (rRelRect.X - rCoordSys.X) + 0.5 );
    aAbsRect.Y = static_cast< sal_Int32 >( rShapeRect.Y + fHeightRatio * (rRelRect.Y - rCoordSys.Y) + 0.5 );
    aAbsRect.Width = static_cast< sal_Int32 >( fWidthRatio * rRelRect.Width + 0.5 );
    aAbsRect.Height = static_cast< sal_Int32 >( fHeightRatio * rRelRect.Height + 0.5 );
    return aAbsRect;
}

/// Count the crop value based on a crop fraction and a reference size.
sal_Int32 lclConvertCrop(std::u16string_view rCrop, sal_uInt32 nSize)
{
    if (o3tl::ends_with(rCrop, u"f"))
    {
        // Numeric value is specified in 1/65536-ths.
        sal_uInt32 nCrop = o3tl::toUInt32(rCrop.substr(0, rCrop.size() - 1));
        return (nCrop * nSize) / 65536;
    }

    return 0;
}

} // namespace

ShapeTypeModel::ShapeTypeModel():
    mbAutoHeight( false ),
    mbVisible( true )
{
}

void ShapeTypeModel::assignUsed( const ShapeTypeModel& rSource )
{
    assignIfUsed( moShapeType, rSource.moShapeType );
    assignIfUsed( moCoordPos, rSource.moCoordPos );
    assignIfUsed( moCoordSize, rSource.moCoordSize );
    /*  The style properties position, left, top, width, height, margin-left,
        margin-top are not derived from shape template to shape. */
    maStrokeModel.assignUsed( rSource.maStrokeModel );
    maFillModel.assignUsed( rSource.maFillModel );
    assignIfUsed( moGraphicPath, rSource.moGraphicPath );
    assignIfUsed( moGraphicTitle, rSource.moGraphicTitle );
}

ShapeType::ShapeType( Drawing& rDrawing ) :
    mrDrawing( rDrawing )
    , mbTextBox(false)
{
}

ShapeType::~ShapeType()
{
}

sal_Int32 ShapeType::getShapeType() const
{
    return maTypeModel.moShapeType.value_or( 0 );
}

OUString ShapeType::getGraphicPath() const
{
    return maTypeModel.moGraphicPath.value_or( OUString() );
}

awt::Rectangle ShapeType::getCoordSystem() const
{
    Int32Pair aCoordPos = maTypeModel.moCoordPos.value_or( Int32Pair( 0, 0 ) );
    Int32Pair aCoordSize = maTypeModel.moCoordSize.value_or( Int32Pair( 1000, 1000 ) );
    if( aCoordSize.first == 0 )
        aCoordSize.first = 1;
    if( aCoordSize.second == 0 )
        aCoordSize.second = 1;
    return awt::Rectangle( aCoordPos.first, aCoordPos.second, aCoordSize.first, aCoordSize.second );
}

awt::Rectangle ShapeType::getRectangle( const ShapeParentAnchor* pParentAnchor ) const
{
    return pParentAnchor ?
        lclGetAbsRect( getRelRectangle(), pParentAnchor->maShapeRect, pParentAnchor->maCoordSys ) :
        getAbsRectangle();
}

awt::Rectangle ShapeType::getAbsRectangle() const
{
    const GraphicHelper& rGraphicHelper = mrDrawing.getFilter().getGraphicHelper();

    sal_Int32 nWidth = ConversionHelper::decodeMeasureToHmm( rGraphicHelper, maTypeModel.maWidth, 0, true, true );
    if ( nWidth == 0 )
        nWidth = 1;

    sal_Int32 nHeight = ConversionHelper::decodeMeasureToHmm( rGraphicHelper, maTypeModel.maHeight, 0, false, true );
    if ( nHeight == 0 )
        nHeight = 1;

    sal_Int32 nLeft;
    if (o3tl::checked_add<sal_Int32>(ConversionHelper::decodeMeasureToHmm(rGraphicHelper, maTypeModel.maLeft, 0, true, true),
                                     ConversionHelper::decodeMeasureToHmm(rGraphicHelper, maTypeModel.maMarginLeft, 0, true, true),
                                     nLeft))
    {
        SAL_WARN("oox", "overflow in addition");
        nLeft = 0;
    }
    if (nLeft == 0 && maTypeModel.maPosition == "absolute")
        nLeft = 1;

    sal_Int32 nTop;
    if (o3tl::checked_add<sal_Int32>(ConversionHelper::decodeMeasureToHmm(rGraphicHelper, maTypeModel.maTop, 0, false, true),
                                     ConversionHelper::decodeMeasureToHmm(rGraphicHelper, maTypeModel.maMarginTop, 0, false, true),
                                     nTop))
    {
        SAL_WARN("oox", "overflow in addition");
        nTop = 0;
    }

    return awt::Rectangle(nLeft, nTop, nWidth, nHeight);
}

awt::Rectangle ShapeType::getRelRectangle() const
{
    sal_Int32 nWidth = maTypeModel.maWidth.toInt32();
    if ( nWidth == 0 )
        nWidth = 1;

    sal_Int32 nHeight = maTypeModel.maHeight.toInt32();
    if ( nHeight == 0 )
        nHeight = 1;

    return awt::Rectangle(
        maTypeModel.maLeft.toInt32(),
        maTypeModel.maTop.toInt32(),
        nWidth, nHeight );
}

ClientData::ClientData() :
    mnObjType( XML_TOKEN_INVALID ),
    mnTextHAlign( XML_Left ),
    mnTextVAlign( XML_Top ),
    mnCol( -1 ),
    mnRow( -1 ),
    mnChecked( VML_CLIENTDATA_UNCHECKED ),
    mnDropStyle( XML_Combo ),
    mnDropLines( 1 ),
    mnVal( 0 ),
    mnMin( 0 ),
    mnMax( 0 ),
    mnInc( 0 ),
    mnPage( 0 ),
    mnSelType( XML_Single ),
    mnVTEdit( VML_CLIENTDATA_TEXT ),
    mbPrintObject( true ),
    mbVisible( false ),
    mbDde( false ),
    mbNo3D( false ),
    mbNo3D2( false ),
    mbMultiLine( false ),
    mbVScroll( false ),
    mbSecretEdit( false ),
    mbMoveWithCells( true ),
    mbSizeWithCells( true )
{
}

ShapeModel::ShapeModel()
    : mbIsSignatureLine(false)
    , mbSignatureLineShowSignDate(true)
    , mbSignatureLineCanAddComment(false)
    , mbInGroup(false)
{
}

ShapeModel::~ShapeModel()
{
}

TextBox& ShapeModel::createTextBox(ShapeTypeModel& rModel)
{
    mxTextBox.reset( new TextBox(rModel) );
    return *mxTextBox;
}

ClientData& ShapeModel::createClientData()
{
    mxClientData.reset( new ClientData );
    return *mxClientData;
}

ShapeBase::ShapeBase( Drawing& rDrawing ) :
    ShapeType( rDrawing )
{
}

void ShapeBase::finalizeFragmentImport()
{
    if( maShapeModel.maType.getLength() <= 1 )
        return;

    OUString aType = maShapeModel.maType;
    if (aType[ 0 ] == '#')
        aType = aType.copy(1);
    if( const ShapeType* pShapeType = mrDrawing.getShapes().getShapeTypeById( aType ) )
    {
        // Make sure that the props from maTypeModel have priority over the props from
        // the shape type.
        StrokeModel aMergedStrokeModel;
        aMergedStrokeModel.assignUsed(pShapeType->getTypeModel().maStrokeModel);
        aMergedStrokeModel.assignUsed(maTypeModel.maStrokeModel);
        FillModel aMergedFillModel;
        aMergedFillModel.assignUsed(pShapeType->getTypeModel().maFillModel);
        aMergedFillModel.assignUsed(maTypeModel.maFillModel);

        maTypeModel.assignUsed( pShapeType->getTypeModel() );
        maTypeModel.maStrokeModel = std::move(aMergedStrokeModel);
        maTypeModel.maFillModel = std::move(aMergedFillModel);
    }
    else {
        // Temporary fix, shapetype not found if referenced from different substream
        // FIXME: extend scope of ShapeContainer to store all shapetypes from the document
        static constexpr OUString sShapeTypePrefix = u"shapetype_"_ustr;
        std::u16string_view tmp;
        if (aType.startsWith(sShapeTypePrefix)) {
            maTypeModel.moShapeType = o3tl::toInt32(aType.subView(sShapeTypePrefix.getLength()));
        }
        else if (aType.startsWith("_x0000_t", &tmp)) {
            maTypeModel.moShapeType = o3tl::toInt32(tmp);
        }
    }
}

OUString ShapeBase::getShapeName() const
{
    if( !maTypeModel.maShapeName.isEmpty() )
        return maTypeModel.maShapeName;

    OUString aBaseName = mrDrawing.getShapeBaseName( *this );
    if( !aBaseName.isEmpty() )
    {
        sal_Int32 nShapeIdx = mrDrawing.getLocalShapeIndex( getShapeId() );
        if( nShapeIdx > 0 )
            return aBaseName + OUStringChar(' ') + OUString::number( nShapeIdx );
    }

    return OUString();
}

const ShapeType* ShapeBase::getChildTypeById( const OUString& ) const
{
    return nullptr;
}

const ShapeBase* ShapeBase::getChildById( const OUString& ) const
{
    return nullptr;
}

Reference< XShape > ShapeBase::convertAndInsert( const Reference< XShapes >& rxShapes, const ShapeParentAnchor* pParentAnchor ) const
{
    Reference< XShape > xShape;
    if( mrDrawing.isShapeSupported( *this ) )
    {
        /*  Calculate shape rectangle. Applications may do something special
            according to some imported shape client data (e.g. Excel cell anchor). */
        awt::Rectangle aShapeRect = calcShapeRectangle( pParentAnchor );

        if( ((aShapeRect.Width > 0) || (aShapeRect.Height > 0)) && rxShapes.is() )
        {
            xShape = implConvertAndInsert( rxShapes, aShapeRect );
            if( xShape.is() )
            {
                // set imported or generated shape name (not supported by form controls)
                PropertySet aShapeProp( xShape );
                if( aShapeProp.hasProperty( PROP_Name ) )
                    aShapeProp.setProperty( PROP_Name, getShapeName() );
                uno::Reference< lang::XServiceInfo > xSInfo( xShape, uno::UNO_QUERY_THROW );

                OUString sLinkChainName = getTypeModel().maLegacyId;
                sal_Int32 id = 0;
                sal_Int32 idPos = sLinkChainName.indexOf("_x");
                sal_Int32 seq = 0;
                if (idPos >= 0)
                {
                    sal_Int32 seqPos = sLinkChainName.indexOf("_s",idPos);
                    if (idPos < seqPos)
                    {
                        auto idPosEnd = idPos+2;
                        id = o3tl::toInt32(sLinkChainName.subView(idPosEnd, seqPos - idPosEnd));
                        seq = o3tl::toInt32(sLinkChainName.subView(seqPos+2));
                    }
                }

                OUString s_mso_next_textbox;
                if( getTextBox() )
                    s_mso_next_textbox = getTextBox()->msNextTextbox;
                if( s_mso_next_textbox.startsWith("#") )
                    s_mso_next_textbox = s_mso_next_textbox.copy(1);

                if (xSInfo->supportsService(u"com.sun.star.text.TextFrame"_ustr))
                {
                    uno::Reference<beans::XPropertySet> propertySet (xShape, uno::UNO_QUERY);
                    uno::Any aAny = propertySet->getPropertyValue(u"FrameInteropGrabBag"_ustr);
                    auto aGrabBag = comphelper::sequenceToContainer< std::vector<beans::PropertyValue> >(aAny.get< uno::Sequence<beans::PropertyValue> >());

                    aGrabBag.push_back(comphelper::makePropertyValue(u"VML-Z-ORDER"_ustr, maTypeModel.maZIndex.toInt32()));

                    if( !s_mso_next_textbox.isEmpty() )
                        aGrabBag.push_back(comphelper::makePropertyValue(u"mso-next-textbox"_ustr, s_mso_next_textbox));

                    if( !sLinkChainName.isEmpty() )
                    {
                        aGrabBag.push_back(comphelper::makePropertyValue(u"TxbxHasLink"_ustr, true));
                        aGrabBag.push_back(comphelper::makePropertyValue(u"Txbx-Id"_ustr, id));
                        aGrabBag.push_back(comphelper::makePropertyValue(u"Txbx-Seq"_ustr, seq));
                        aGrabBag.push_back(comphelper::makePropertyValue(u"LinkChainName"_ustr, sLinkChainName));
                    }

                    if(!maTypeModel.maRotation.isEmpty())
                        aGrabBag.push_back(comphelper::makePropertyValue(u"mso-rotation-angle"_ustr, ConversionHelper::decodeRotation(maTypeModel.maRotation).get()));
                    propertySet->setPropertyValue(u"FrameInteropGrabBag"_ustr, uno::Any(comphelper::containerToSequence(aGrabBag)));
                    sal_Int32 backColorTransparency = 0;
                    propertySet->getPropertyValue(u"BackColorTransparency"_ustr)
                        >>= backColorTransparency;
                    if (propertySet->getPropertyValue(u"FillStyle"_ustr) == FillStyle_NONE &&
                        backColorTransparency == 100)
                    {
                        // If there is no fill, the Word default is 100% transparency.
                        propertySet->setPropertyValue(u"FillTransparence"_ustr, Any(sal_Int16(100)));
                    }
                }
                else
                {
                    if( maTypeModel.maZIndex.toInt32() )
                    {
                        uno::Sequence<beans::PropertyValue> aGrabBag;
                        uno::Reference<beans::XPropertySet> propertySet (xShape, uno::UNO_QUERY);
                        propertySet->getPropertyValue(u"InteropGrabBag"_ustr) >>= aGrabBag;
                        sal_Int32 length;

                        length = aGrabBag.getLength();
                        aGrabBag.realloc( length+1 );
                        auto pGrabBag = aGrabBag.getArray();
                        pGrabBag[length].Name = "VML-Z-ORDER";
                        pGrabBag[length].Value <<= maTypeModel.maZIndex.toInt32();

                        if( !s_mso_next_textbox.isEmpty() )
                        {
                            length = aGrabBag.getLength();
                            aGrabBag.realloc( length+1 );
                            pGrabBag = aGrabBag.getArray();
                            pGrabBag[length].Name = "mso-next-textbox";
                            pGrabBag[length].Value <<= s_mso_next_textbox;
                        }

                        if( !sLinkChainName.isEmpty() )
                        {
                            length = aGrabBag.getLength();
                            aGrabBag.realloc( length+4 );
                            pGrabBag = aGrabBag.getArray();
                            pGrabBag[length].Name   = "TxbxHasLink";
                            pGrabBag[length].Value   <<= true;
                            pGrabBag[length+1].Name = "Txbx-Id";
                            pGrabBag[length+1].Value <<= id;
                            pGrabBag[length+2].Name = "Txbx-Seq";
                            pGrabBag[length+2].Value <<= seq;
                            pGrabBag[length+3].Name = "LinkChainName";
                            pGrabBag[length+3].Value <<= sLinkChainName;
                        }
                        propertySet->setPropertyValue( u"InteropGrabBag"_ustr, uno::Any(aGrabBag) );
                    }
                }
                Reference< XControlShape > xControlShape( xShape, uno::UNO_QUERY );
                if ( xControlShape.is() && !getTypeModel().mbVisible )
                {
                    PropertySet aControlShapeProp( xControlShape->getControl() );
                    aControlShapeProp.setProperty( PROP_EnableVisible, uno::Any( false ) );
                }

                xShape = finalImplConvertAndInsert(xShape);
                /*  Notify the drawing that a new shape has been inserted. For
                    convenience, pass the rectangle that contains position and
                    size of the shape. */
                bool bGroupChild = pParentAnchor != nullptr;
                mrDrawing.notifyXShapeInserted( xShape, aShapeRect, *this, bGroupChild );
            }
        }
        else
            SAL_WARN("oox", "not converting shape, as calculated rectangle is empty");
    }
    return xShape;
}

awt::Rectangle ShapeBase::getShapeRectangle() const
{
    /*  Calculate shape rectangle. Applications may do something special
        according to some imported shape client data (e.g. Excel cell anchor). */
    return calcShapeRectangle(nullptr);
}

void ShapeBase::setContainer(ShapeContainer* pContainer) { mpContainer = pContainer; }

ShapeContainer* ShapeBase::getContainer() const { return mpContainer; }

// protected ------------------------------------------------------------------

awt::Rectangle ShapeBase::calcShapeRectangle( const ShapeParentAnchor* pParentAnchor ) const
{
    /*  Calculate shape rectangle. Applications may do something special
        according to some imported shape client data (e.g. Excel cell anchor). */
    awt::Rectangle aShapeRect;
    const ClientData* pClientData = getClientData();
    if( !pClientData || !mrDrawing.convertClientAnchor( aShapeRect, pClientData->maAnchor ) )
        aShapeRect = getRectangle( pParentAnchor );
    return aShapeRect;
}

::oox::drawingml::ShapePropertyMap ShapeBase::makeShapePropertyMap() const
{
    ::oox::drawingml::ShapePropertyMap aPropMap( mrDrawing.getFilter().getModelObjectHelper() );
    const GraphicHelper& rGraphicHelper = mrDrawing.getFilter().getGraphicHelper();
    maTypeModel.maStrokeModel.pushToPropMap( aPropMap, rGraphicHelper );
    maTypeModel.maFillModel.pushToPropMap( aPropMap, rGraphicHelper );
    return aPropMap;
}

void ShapeBase::convertShapeProperties( const Reference< XShape >& rxShape ) const
{
    ::oox::drawingml::ShapePropertyMap aPropMap(makeShapePropertyMap());

    uno::Reference<lang::XServiceInfo> xSInfo(rxShape, uno::UNO_QUERY_THROW);
    if (xSInfo->supportsService(u"com.sun.star.text.TextFrame"_ustr))
    {
        const GraphicHelper& rGraphicHelper = mrDrawing.getFilter().getGraphicHelper();
        // Any other service supporting the ShadowFormat property?
        maTypeModel.maShadowModel.pushToPropMap(aPropMap, rGraphicHelper);
        // TextFrames have BackColor, not FillColor
        if (aPropMap.hasProperty(PROP_FillColor))
        {
            aPropMap.setAnyProperty(PROP_BackColor, aPropMap.getProperty(PROP_FillColor));
            aPropMap.erase(PROP_FillColor);
        }
        // TextFrames have BackColorTransparency, not FillTransparence
        if (aPropMap.hasProperty(PROP_FillTransparence))
        {
            aPropMap.setAnyProperty(PROP_BackColorTransparency, aPropMap.getProperty(PROP_FillTransparence));
            aPropMap.erase(PROP_FillTransparence);
        }
        // And no LineColor property; individual borders can have colors and widths
        std::optional<sal_Int32> oLineWidth;
        if (maTypeModel.maStrokeModel.moWeight.has_value())
            oLineWidth = ConversionHelper::decodeMeasureToHmm(
                rGraphicHelper, maTypeModel.maStrokeModel.moWeight.value(), 0, false, false);
        if (aPropMap.hasProperty(PROP_LineColor))
        {
            uno::Reference<beans::XPropertySet> xPropertySet(rxShape, uno::UNO_QUERY);
            static const sal_Int32 aBorders[] = {
                PROP_TopBorder, PROP_LeftBorder, PROP_BottomBorder, PROP_RightBorder
            };
            for (sal_Int32 nBorder : aBorders)
            {
                table::BorderLine2 aBorderLine = xPropertySet->getPropertyValue(PropertyMap::getPropertyName(nBorder)).get<table::BorderLine2>();
                aBorderLine.Color = aPropMap.getProperty(PROP_LineColor).get<sal_Int32>();
                if (oLineWidth)
                    aBorderLine.LineWidth = *oLineWidth;
                aPropMap.setProperty(nBorder, aBorderLine);
            }
            aPropMap.erase(PROP_LineColor);
        }
    }
    else if (xSInfo->supportsService(u"com.sun.star.drawing.CustomShape"_ustr))
    {
        const GraphicHelper& rGraphicHelper = mrDrawing.getFilter().getGraphicHelper();
        maTypeModel.maTextpathModel.pushToPropMap(aPropMap, rxShape, rGraphicHelper);
    }

    PropertySet( rxShape ).setProperties( aPropMap );
}

SimpleShape::SimpleShape( Drawing& rDrawing, OUString aService ) :
    ShapeBase( rDrawing ),
    maService(std::move( aService ))
{
}

static void lcl_setSurround(PropertySet& rPropSet, const ShapeTypeModel& rTypeModel, const GraphicHelper& rGraphicHelper)
{
    OUString aWrapType = rTypeModel.moWrapType.value_or("");

    // Extreme negative top margin? Then the shape will end up at the top of the page, it's pointless to perform any kind of wrapping.
    sal_Int32 nMarginTop = ConversionHelper::decodeMeasureToHmm(rGraphicHelper, rTypeModel.maMarginTop, 0, false, true);
    if (nMarginTop < -35277) // Less than 1000 points.
        aWrapType.clear();

    css::text::WrapTextMode nSurround = css::text::WrapTextMode_THROUGH;
    if ( aWrapType == "square" || aWrapType == "tight" ||
         aWrapType == "through" )
    {
        nSurround = css::text::WrapTextMode_PARALLEL;
        if ( !rTypeModel.moWrapSide.has_value() )
            ; // leave as PARALLEL
        else if ( rTypeModel.moWrapSide.value() == "left" )
            nSurround = css::text::WrapTextMode_LEFT;
        else if ( rTypeModel.moWrapSide.value() == "right" )
            nSurround = css::text::WrapTextMode_RIGHT;
    }
    else if ( aWrapType == "topAndBottom" )
        nSurround = css::text::WrapTextMode_NONE;

    rPropSet.setProperty(PROP_Surround, static_cast<sal_Int32>(nSurround));
    rPropSet.setProperty(PROP_SurroundContour, aWrapType == "tight");
}

static void lcl_SetAnchorType(PropertySet& rPropSet, const ShapeTypeModel& rTypeModel, const GraphicHelper& rGraphicHelper)
{
    if ( rTypeModel.maPosition == "absolute" )
    {
        // Word supports as-character (inline) and at-character only, absolute can't be inline.
        rPropSet.setProperty(PROP_AnchorType, text::TextContentAnchorType_AT_CHARACTER);
        // anchor is set after insertion, so reset to NONE
        rPropSet.setAnyProperty(PROP_VertOrient, Any(text::VertOrientation::NONE));

        if ( rTypeModel.maPositionVerticalRelative == "page" )
        {
            rPropSet.setProperty(PROP_VertOrientRelation, text::RelOrientation::PAGE_FRAME);
        }
        else if ( rTypeModel.maPositionVerticalRelative == "margin" )
        {
            rPropSet.setProperty(PROP_VertOrientRelation, text::RelOrientation::PAGE_PRINT_AREA);
        }
        else if (rTypeModel.maPositionVerticalRelative == "top-margin-area")
        {
            rPropSet.setProperty(PROP_VertOrientRelation, text::RelOrientation::PAGE_PRINT_AREA_TOP);
        }
        else if (rTypeModel.maPositionVerticalRelative == "bottom-margin-area")
        {
            rPropSet.setProperty(PROP_VertOrientRelation, text::RelOrientation::PAGE_PRINT_AREA_BOTTOM);
        }
        else
        {
            rPropSet.setProperty(PROP_VertOrientRelation, text::RelOrientation::FRAME);
        }
    }
    else if( rTypeModel.maPosition == "relative" )
    {   // I'm not very sure this is correct either.
        rPropSet.setProperty(PROP_AnchorType, text::TextContentAnchorType_AT_PARAGRAPH);
        // anchor is set after insertion, so reset to NONE
        rPropSet.setAnyProperty(PROP_VertOrient, Any(text::VertOrientation::NONE));
    }
    else // static (is the default) means anchored inline
    {
        rPropSet.setProperty(PROP_AnchorType, text::TextContentAnchorType_AS_CHARACTER);
        // Use top orientation, this one seems similar to what MSO uses as inline
        rPropSet.setAnyProperty(PROP_VertOrient, Any(text::VertOrientation::TOP));
    }

    // if the anchor is not inline, and is relative to left or right, then apply the margins
    bool bHonorMargins = rTypeModel.maPosition == "relative" || rTypeModel.maPosition == "absolute";
    if ( rTypeModel.maPositionHorizontal == "center" )
    {
        rPropSet.setAnyProperty(PROP_HoriOrient, Any(text::HoriOrientation::CENTER));
        bHonorMargins = false;
    }
    else if ( rTypeModel.maPositionHorizontal == "left" )
        rPropSet.setAnyProperty(PROP_HoriOrient, Any(text::HoriOrientation::LEFT));
    else if ( rTypeModel.maPositionHorizontal == "right" )
        rPropSet.setAnyProperty(PROP_HoriOrient, Any(text::HoriOrientation::RIGHT));
    else if ( rTypeModel.maPositionHorizontal == "inside" )
    {
        rPropSet.setAnyProperty(PROP_HoriOrient, Any(text::HoriOrientation::LEFT));
        rPropSet.setAnyProperty(PROP_PageToggle, Any(true));
    }
    else if ( rTypeModel.maPositionHorizontal == "outside" )
    {
        rPropSet.setAnyProperty(PROP_HoriOrient, Any(text::HoriOrientation::RIGHT));
        rPropSet.setAnyProperty(PROP_PageToggle, Any(true));
    }
    else
        bHonorMargins = false;

    if ( rTypeModel.maPositionHorizontalRelative == "page" )
        rPropSet.setAnyProperty(PROP_HoriOrientRelation, Any(text::RelOrientation::PAGE_FRAME));
    else if ( rTypeModel.maPositionHorizontalRelative == "margin" )
        rPropSet.setProperty(PROP_HoriOrientRelation, text::RelOrientation::PAGE_PRINT_AREA);
    else if (rTypeModel.maPositionHorizontalRelative == "right-margin-area" ||
             rTypeModel.maPositionHorizontalRelative == "inner-margin-area")
        rPropSet.setProperty(PROP_HoriOrientRelation, text::RelOrientation::PAGE_RIGHT);
    else if (rTypeModel.maPositionHorizontalRelative == "left-margin-area" ||
             rTypeModel.maPositionHorizontalRelative == "outer-margin-area")
        rPropSet.setProperty(PROP_HoriOrientRelation, text::RelOrientation::PAGE_LEFT);
    else // "text"
    {
        if (bHonorMargins)
            rPropSet.setProperty(PROP_HoriOrientRelation, text::RelOrientation::PRINT_AREA);
        else
            rPropSet.setProperty(PROP_HoriOrientRelation, text::RelOrientation::FRAME);
    }
    if ( rTypeModel.maPositionVertical == "center" )
        rPropSet.setAnyProperty(PROP_VertOrient, Any(text::VertOrientation::CENTER));
    else if ( rTypeModel.maPositionVertical == "top" )
        rPropSet.setAnyProperty(PROP_VertOrient, Any(text::VertOrientation::TOP));
    else if ( rTypeModel.maPositionVertical == "bottom" )
        rPropSet.setAnyProperty(PROP_VertOrient, Any(text::VertOrientation::BOTTOM));
    else if ( rTypeModel.maPositionVertical == "inside" )
        rPropSet.setAnyProperty(PROP_VertOrient, Any(text::VertOrientation::TOP));
    else if ( rTypeModel.maPositionVertical == "outside" )
        rPropSet.setAnyProperty(PROP_VertOrient, Any(text::VertOrientation::BOTTOM));

    lcl_setSurround( rPropSet, rTypeModel, rGraphicHelper );
}

Reference< XShape > SimpleShape::implConvertAndInsert( const Reference< XShapes >& rxShapes, const awt::Rectangle& rShapeRect ) const
{
    awt::Rectangle aShapeRect(rShapeRect);
    std::optional<Degree100> oRotation;
    bool bFlipX = false, bFlipY = false;
    // tdf#137765: skip this rotation for line shapes
    if (!maTypeModel.maRotation.isEmpty() && maService != "com.sun.star.drawing.LineShape")
        oRotation = ConversionHelper::decodeRotation(maTypeModel.maRotation);
    if (!maTypeModel.maFlip.isEmpty())
    {
        if (maTypeModel.maFlip.startsWith("x"))
        {
            bFlipX = true;
        }
        if (maTypeModel.maFlip.endsWith("y"))
        {
            bFlipY = true;
        }
    }

    Reference< XShape > xShape = mrDrawing.createAndInsertXShape( maService, rxShapes, aShapeRect );
    SdrObject* pShape = SdrObject::getSdrObjectFromXShape(xShape);
    if( pShape && getShapeType() >= 0 )
    {
        //The resize autoshape to fit text attr of FontWork/Word-Art should always be false
        //for the fallback geometry.
        sal_Int32 nType = getShapeType();
        if((mso_sptTextSimple <= nType && nType <= mso_sptTextOnRing)
            || (mso_sptTextPlainText <= nType && nType <= mso_sptTextCanDown))
        {
            pShape->SetMergedItem(makeSdrTextAutoGrowHeightItem(false));
            pShape->SetMergedItem(makeSdrTextAutoGrowWidthItem(false));
        }
    }
    convertShapeProperties( xShape );

    // Handle left/right/top/bottom wrap distance.
    // Default value of mso-wrap-distance-left/right is supposed to be 0 (see
    // 19.1.2.19 of the VML spec), but Word implements a non-zero value.
    // [MS-ODRAW] says the below default value in 2.3.4.9.
    const GraphicHelper& rGraphicHelper = mrDrawing.getFilter().getGraphicHelper();
    OUString aWrapDistanceLeft = OUString::number(0x0001BE7C);
    if (!maTypeModel.maWrapDistanceLeft.isEmpty())
        aWrapDistanceLeft = maTypeModel.maWrapDistanceLeft;
    sal_Int32 nWrapDistanceLeft = ConversionHelper::decodeMeasureToHmm(rGraphicHelper, aWrapDistanceLeft, 0, true, false);
    PropertySet(xShape).setAnyProperty(PROP_LeftMargin, uno::Any(nWrapDistanceLeft));
    OUString aWrapDistanceRight = OUString::number(0x0001BE7C);
    if (!maTypeModel.maWrapDistanceRight.isEmpty())
        aWrapDistanceRight = maTypeModel.maWrapDistanceRight;
    sal_Int32 nWrapDistanceRight = ConversionHelper::decodeMeasureToHmm(rGraphicHelper, aWrapDistanceRight, 0, true, false);
    PropertySet(xShape).setAnyProperty(PROP_RightMargin, uno::Any(nWrapDistanceRight));
    sal_Int32 nWrapDistanceTop = 0;
    if (!maTypeModel.maWrapDistanceTop.isEmpty())
        nWrapDistanceTop = ConversionHelper::decodeMeasureToHmm(rGraphicHelper, maTypeModel.maWrapDistanceTop, 0, false, true);
    PropertySet(xShape).setAnyProperty(PROP_TopMargin, uno::Any(nWrapDistanceTop));
    sal_Int32 nWrapDistanceBottom = 0;
    if (!maTypeModel.maWrapDistanceBottom.isEmpty())
        nWrapDistanceBottom = ConversionHelper::decodeMeasureToHmm(rGraphicHelper, maTypeModel.maWrapDistanceBottom, 0, false, true);
    PropertySet(xShape).setAnyProperty(PROP_BottomMargin, uno::Any(nWrapDistanceBottom));

    if ( maService == "com.sun.star.text.TextFrame" )
    {
        PropertySet( xShape ).setAnyProperty( PROP_FrameIsAutomaticHeight, Any( maTypeModel.mbAutoHeight ) );
        PropertySet( xShape ).setAnyProperty( PROP_SizeType, Any( maTypeModel.mbAutoHeight ? SizeType::MIN : SizeType::FIX ) );
        if( getTextBox()->borderDistanceSet )
        {
            PropertySet( xShape ).setAnyProperty( PROP_LeftBorderDistance, Any( sal_Int32( getTextBox()->borderDistanceLeft )));
            PropertySet( xShape ).setAnyProperty( PROP_TopBorderDistance, Any( sal_Int32( getTextBox()->borderDistanceTop )));
            PropertySet( xShape ).setAnyProperty( PROP_RightBorderDistance, Any( sal_Int32( getTextBox()->borderDistanceRight )));
            PropertySet( xShape ).setAnyProperty( PROP_BottomBorderDistance, Any( sal_Int32( getTextBox()->borderDistanceBottom )));
        }

        sal_Int16 nWritingMode = text::WritingMode2::LR_TB;
        if (getTextBox()->maLayoutFlow == "vertical" && maTypeModel.maLayoutFlowAlt.isEmpty())
        {
            nWritingMode = text::WritingMode2::TB_RL;
        }
        else if (maTypeModel.maLayoutFlowAlt == "bottom-to-top")
        {
            nWritingMode = text::WritingMode2::BT_LR;
        }
        if (nWritingMode != text::WritingMode2::LR_TB)
        {
            PropertySet(xShape).setAnyProperty(PROP_WritingMode, uno::Any(nWritingMode));
        }
        // tdf#123626
        if (!maShapeModel.maHyperlink.isEmpty())
            PropertySet(xShape).setAnyProperty(PROP_HyperLinkURL, Any(maShapeModel.maHyperlink));
    }
    else
    {
        // FIXME Setting the relative width/height only for everything but text frames as
        // TextFrames already have relative width/height feature... but currently not working
        // in the way we need.

        // Set the relative width / height if any
        if ( !maTypeModel.maWidthPercent.isEmpty( ) )
        {
            // Only page-relative width is supported ATM
            if ( maTypeModel.maWidthRelative.isEmpty() || maTypeModel.maWidthRelative == "page" )
            {
                sal_Int16 nWidth = maTypeModel.maWidthPercent.toInt32() / 10;
                // Only apply if nWidth != 0
                if ( nWidth )
                    PropertySet( xShape ).setAnyProperty(PROP_RelativeWidth, Any( nWidth ) );
            }
        }
        if ( !maTypeModel.maHeightPercent.isEmpty( ) )
        {
            // Only page-relative height is supported ATM
            if ( maTypeModel.maHeightRelative.isEmpty() || maTypeModel.maHeightRelative == "page" )
            {
                sal_Int16 nHeight = maTypeModel.maHeightPercent.toInt32() / 10;
                // Only apply if nHeight != 0
                if ( nHeight )
                    PropertySet( xShape ).setAnyProperty(PROP_RelativeHeight, Any( nHeight ) );
            }
        }

        // drawinglayer default is center, MSO default is top.
        drawing::TextVerticalAdjust eTextVerticalAdjust = drawing::TextVerticalAdjust_TOP;
        if (maTypeModel.maVTextAnchor == "middle")
            eTextVerticalAdjust = drawing::TextVerticalAdjust_CENTER;
        else if (maTypeModel.maVTextAnchor == "bottom")
            eTextVerticalAdjust = drawing::TextVerticalAdjust_BOTTOM;
        PropertySet(xShape).setAnyProperty(PROP_TextVerticalAdjust, Any(eTextVerticalAdjust));

        // tdf#97618
        const bool bWrap = maTypeModel.maWrapStyle != "none";
        PropertySet(xShape).setAnyProperty(PROP_TextWordWrap, Any(bWrap));


        // tdf#123626
        if (!maShapeModel.maHyperlink.isEmpty())
            PropertySet(xShape).setAnyProperty(PROP_Hyperlink, Any(maShapeModel.maHyperlink));

        PropertySet(xShape).setAnyProperty(PROP_TextAutoGrowHeight,
                                           Any(maTypeModel.mbAutoHeight));

        if (getTextBox())
        {
            getTextBox()->convert(xShape);
            if (getTextBox()->borderDistanceSet)
            {
                awt::Size aSize = xShape->getSize();
                PropertySet(xShape).setAnyProperty(PROP_TextLeftDistance, Any(sal_Int32(getTextBox()->borderDistanceLeft)));
                PropertySet(xShape).setAnyProperty(PROP_TextUpperDistance, Any(sal_Int32(getTextBox()->borderDistanceTop)));
                PropertySet(xShape).setAnyProperty(PROP_TextRightDistance, Any(sal_Int32(getTextBox()->borderDistanceRight)));
                PropertySet(xShape).setAnyProperty(PROP_TextLowerDistance, Any(sal_Int32(getTextBox()->borderDistanceBottom)));
                xShape->setSize(aSize);
            }

            if (mbTextBox)
            {
                // similar to drawingml::Shape::createAndInsert(...)
                PropertySet(xShape).setAnyProperty(PROP_TextBox, Any(true));
            }

        }
    }

    // Import Legacy Fragments (if any)
    if( xShape.is() && !maShapeModel.maLegacyDiagramPath.isEmpty() )
    {
        Reference< XInputStream > xInStrm( mrDrawing.getFilter().openInputStream( maShapeModel.maLegacyDiagramPath ), UNO_SET_THROW );
        if( xInStrm.is() )
            PropertySet( xShape ).setProperty( PROP_LegacyFragment, xInStrm );
    }

    PropertySet aPropertySet(xShape);
    if (xShape.is())
    {
        if (oRotation)
        {
            aPropertySet.setAnyProperty(PROP_RotateAngle, Any((*oRotation).get()));
            uno::Reference<lang::XServiceInfo> xServiceInfo(rxShapes, uno::UNO_QUERY);
            if (!xServiceInfo->supportsService(u"com.sun.star.drawing.GroupShape"_ustr))
            {
                // If rotation is used, simple setPosition() is not enough.
                aPropertySet.setAnyProperty(PROP_HoriOrientPosition, Any(aShapeRect.X));
                aPropertySet.setAnyProperty(PROP_VertOrientPosition, Any(aShapeRect.Y));
            }
        }
        if (!maTypeModel.mbVisible)
        {
            aPropertySet.setAnyProperty(PROP_Visible, uno::Any(false));
            aPropertySet.setAnyProperty(PROP_Printable, uno::Any(false));
        }
        // custom shape geometry attributes
        std::vector<css::beans::PropertyValue> aPropVec;

        // When flip has 'x' or 'y', the associated ShapeRect will be changed but direction change doesn't occur.
        // It might occur internally in SdrObject of "sw" module, not here.
        // The associated properties "PROP_MirroredX" and "PROP_MirroredY" have to be set here so that direction change will occur internally.
        if (bFlipX)
            aPropVec.push_back(comphelper::makePropertyValue(u"MirroredX"_ustr, true));
        if (bFlipY)
            aPropVec.push_back(comphelper::makePropertyValue(u"MirroredY"_ustr, true));

        if (!maTypeModel.maAdjustments.isEmpty())
        {
            std::vector<drawing::EnhancedCustomShapeAdjustmentValue> aAdjustmentValues;
            sal_Int32 nIndex = 0;
            do
            {
                std::u16string_view aToken = o3tl::getToken(maTypeModel.maAdjustments, 0, ',', nIndex);
                drawing::EnhancedCustomShapeAdjustmentValue aAdjustmentValue;
                if (aToken.empty())
                    aAdjustmentValue.State = css::beans::PropertyState::PropertyState_DEFAULT_VALUE;
                else
                    aAdjustmentValue.Value <<= o3tl::toInt32(aToken);
                aAdjustmentValues.push_back(aAdjustmentValue);
            } while (nIndex >= 0);

            css::beans::PropertyValue aProp;
            aProp.Name = "AdjustmentValues";
            aProp.Value <<= comphelper::containerToSequence(aAdjustmentValues);
            aPropVec.push_back(aProp);
        }

        if (!aPropVec.empty())
            aPropertySet.setAnyProperty(PROP_CustomShapeGeometry, Any(comphelper::containerToSequence(aPropVec)));
    }

    lcl_SetAnchorType(aPropertySet, maTypeModel, rGraphicHelper );

    return xShape;
}

Reference<XShape> SimpleShape::finalImplConvertAndInsert(const css::uno::Reference<css::drawing::XShape>& rxShape) const
{
    // tdf#41466 This setting must be done here, because the position of textbox will be set as an
    // effect of the PROP_TextBox property setting, and if we do this setting earlier (setting of
    // properties of position and size) then the position of textbox will be set with wrong data.
    // TODO: TextShape is set if we have rect shape in group; we should use the shape-with-textbox
    // mechanism to handle this situation
    if (getTextBox() && maService != "com.sun.star.text.TextFrame" && maService != "com.sun.star.drawing.TextShape"
        && !maShapeModel.mbInGroup)
    {
        const GraphicHelper& rGraphicHelper = mrDrawing.getFilter().getGraphicHelper();
        const auto nLeft = ConversionHelper::decodeMeasureToHmm(
            rGraphicHelper, maTypeModel.maMarginLeft, 0, true, true);
        PropertySet aPropertySet(rxShape);
        aPropertySet.setProperty(PROP_HoriOrientPosition, nLeft);
        const auto nTop = ConversionHelper::decodeMeasureToHmm(
            rGraphicHelper, maTypeModel.maMarginTop, 0, true, true);
        aPropertySet.setProperty(PROP_VertOrientPosition, nTop);
        aPropertySet.setProperty(PROP_TextBox, true);

        // And these properties must be set after textbox creation (set PROP_Textbox property).
        // Note: if you set a new property then you have to handle it in the proper
        // SwTextBoxHelper::syncProperty function.
        if (maTypeModel.maLayoutFlowAlt == "bottom-to-top")
            aPropertySet.setAnyProperty(PROP_TextWritingMode, uno::Any(text::WritingMode2::BT_LR));
    }
    return ShapeBase::finalImplConvertAndInsert(rxShape);
}
Reference< XShape > SimpleShape::createEmbeddedPictureObject( const Reference< XShapes >& rxShapes, const awt::Rectangle& rShapeRect, OUString const & rGraphicPath ) const
{
    Reference<XGraphic> xGraphic = mrDrawing.getFilter().getGraphicHelper().importEmbeddedGraphic(rGraphicPath);
    return SimpleShape::createPictureObject(rxShapes, rShapeRect, xGraphic);
}

Reference< XShape > SimpleShape::createPictureObject(const Reference< XShapes >& rxShapes,
                                                     const awt::Rectangle& rShapeRect,
                                                     uno::Reference<graphic::XGraphic> const & rxGraphic) const
{
    Reference< XShape > xShape = mrDrawing.createAndInsertXShape( u"com.sun.star.drawing.GraphicObjectShape"_ustr, rxShapes, rShapeRect );
    if( xShape.is() )
    {
        PropertySet aPropSet(xShape);
        if (rxGraphic.is())
        {
            aPropSet.setProperty(PROP_Graphic, rxGraphic);
        }
        uno::Reference< lang::XServiceInfo > xServiceInfo(rxShapes, uno::UNO_QUERY);
        // If the shape has an absolute position, set the properties accordingly, unless we're inside a group shape.
        if ( maTypeModel.maPosition == "absolute" && !xServiceInfo->supportsService(u"com.sun.star.drawing.GroupShape"_ustr))
        {
            aPropSet.setProperty(PROP_HoriOrientPosition, rShapeRect.X);
            aPropSet.setProperty(PROP_VertOrientPosition, rShapeRect.Y);
            aPropSet.setProperty(PROP_Opaque, false);
        }
        // fdo#70457: preserve rotation information
        if ( !maTypeModel.maRotation.isEmpty() )
            aPropSet.setAnyProperty(PROP_RotateAngle, Any(ConversionHelper::decodeRotation(maTypeModel.maRotation).get()));

        const GraphicHelper& rGraphicHelper = mrDrawing.getFilter().getGraphicHelper();
        lcl_SetAnchorType(aPropSet, maTypeModel, rGraphicHelper);

        const sal_Int32 nWrapDistanceLeft = ConversionHelper::decodeMeasureToHmm(rGraphicHelper, maTypeModel.maWrapDistanceLeft, 0, true, true);
        const sal_Int32 nWrapDistanceRight = ConversionHelper::decodeMeasureToHmm(rGraphicHelper, maTypeModel.maWrapDistanceRight, 0, true, true);
        const sal_Int32 nWrapDistanceTop = ConversionHelper::decodeMeasureToHmm(rGraphicHelper, maTypeModel.maWrapDistanceTop, 0, false, true);
        const sal_Int32 nWrapDistanceBottom = ConversionHelper::decodeMeasureToHmm(rGraphicHelper, maTypeModel.maWrapDistanceBottom, 0, false, true);
        aPropSet.setProperty(PROP_LeftMargin, uno::Any(nWrapDistanceLeft));
        aPropSet.setProperty(PROP_RightMargin, uno::Any(nWrapDistanceRight));
        aPropSet.setProperty(PROP_TopMargin, uno::Any(nWrapDistanceTop));
        aPropSet.setProperty(PROP_BottomMargin, uno::Any(nWrapDistanceBottom));

        if (maTypeModel.moCropBottom.has_value() || maTypeModel.moCropLeft.has_value() || maTypeModel.moCropRight.has_value() || maTypeModel.moCropTop.has_value())
        {
            text::GraphicCrop aGraphicCrop;
            awt::Size aOriginalSize = rGraphicHelper.getOriginalSize(rxGraphic);

            if (maTypeModel.moCropBottom.has_value())
                aGraphicCrop.Bottom = lclConvertCrop(maTypeModel.moCropBottom.value(), aOriginalSize.Height);
            if (maTypeModel.moCropLeft.has_value())
                aGraphicCrop.Left = lclConvertCrop(maTypeModel.moCropLeft.value(), aOriginalSize.Width);
            if (maTypeModel.moCropRight.has_value())
                aGraphicCrop.Right = lclConvertCrop(maTypeModel.moCropRight.value(), aOriginalSize.Width);
            if (maTypeModel.moCropTop.has_value())
                aGraphicCrop.Top = lclConvertCrop(maTypeModel.moCropTop.value(), aOriginalSize.Height);

            aPropSet.setProperty(PROP_GraphicCrop, aGraphicCrop);
        }

        if (maTypeModel.mnGain == -70 && maTypeModel.mnBlacklevel == 70)
        {
            // Map MSO 'washout' to our watermark colormode.
            aPropSet.setProperty(PROP_GraphicColorMode, uno::Any(drawing::ColorMode_WATERMARK));
        }
    }
    return xShape;
}

RectangleShape::RectangleShape( Drawing& rDrawing ) :
    SimpleShape( rDrawing, u"com.sun.star.drawing.RectangleShape"_ustr )
{
}

Reference<XShape> RectangleShape::implConvertAndInsert(const Reference<XShapes>& rxShapes, const awt::Rectangle& rShapeRect) const
{
    OUString aGraphicPath = getGraphicPath();

    // try to create a picture object
    if(!aGraphicPath.isEmpty())
        return SimpleShape::createEmbeddedPictureObject(rxShapes, rShapeRect, aGraphicPath);

    // default: try to create a rectangle shape
    Reference<XShape> xShape = SimpleShape::implConvertAndInsert(rxShapes, rShapeRect);
    OUString sArcsize = maTypeModel.maArcsize;
    if ( !sArcsize.isEmpty( ) )
    {
        sal_Unicode cLastChar = sArcsize[sArcsize.getLength() - 1];
        sal_Int32 nValue = o3tl::toInt32(sArcsize.subView( 0, sArcsize.getLength() - 1 ));
        // Get the smallest half-side
        double size = std::min( rShapeRect.Height, rShapeRect.Width ) / 2.0;
        sal_Int32 nRadius = 0;
        if ( cLastChar == 'f' )
            nRadius = size * nValue / 65536;
        else if ( cLastChar == '%' )
            nRadius = size * nValue / 100;
        PropertySet( xShape ).setAnyProperty( PROP_CornerRadius, Any( nRadius ) );
    }
    return xShape;
}

EllipseShape::EllipseShape( Drawing& rDrawing ) :
    SimpleShape( rDrawing, u"com.sun.star.drawing.EllipseShape"_ustr )
{
}

PolyLineShape::PolyLineShape( Drawing& rDrawing ) :
    SimpleShape( rDrawing, u"com.sun.star.drawing.PolyLineShape"_ustr )
{
}

Reference< XShape > PolyLineShape::implConvertAndInsert( const Reference< XShapes >& rxShapes, const awt::Rectangle& rShapeRect ) const
{
    ::std::vector<awt::Point> aAbsPoints;
    awt::Rectangle aCoordSys = getCoordSystem();
    if (!maShapeModel.maPoints.empty() && (aCoordSys.Width > 0) && (aCoordSys.Height > 0))
    {
        for (auto const& point : maShapeModel.maPoints)
            aAbsPoints.push_back(lclGetAbsPoint(point, rShapeRect, aCoordSys));
        // A polyline cannot be filled but only a polygon. We treat first point == last point as
        // indicator for being closed. In that case we force to type PolyPolygonShape.
        if (aAbsPoints.size() > 2 && aAbsPoints.front().X == aAbsPoints.back().X
            && aAbsPoints.front().Y == aAbsPoints.back().Y)
        {
            const_cast<PolyLineShape*>(this)->setService(u"com.sun.star.drawing.PolyPolygonShape"_ustr);
        }
    }

    Reference<XShape> xShape = SimpleShape::implConvertAndInsert(rxShapes, rShapeRect);

    // polygon path

    if (!aAbsPoints.empty())
    {
        PointSequenceSequence aPointSeq{ comphelper::containerToSequence( aAbsPoints ) };
        PropertySet aPropSet( xShape );
        aPropSet.setProperty( PROP_PolyPolygon, aPointSeq );
    }
    return xShape;
}

namespace
{
    void doMirrorX(SdrObject* pShape)
    {
        Point aCenter(pShape->GetSnapRect().Center());
        Point aPoint2(aCenter);
        aPoint2.setY(aPoint2.getY() + 1);
        pShape->NbcMirror(aCenter, aPoint2);
    }

    void doMirrorY(SdrObject* pShape)
    {
        Point aCenter(pShape->GetSnapRect().Center());
        Point aPoint2(aCenter);
        aPoint2.setX(aPoint2.getX() + 1);
        pShape->NbcMirror(aCenter, aPoint2);
    }

    void handleMirroring(const ShapeTypeModel& rTypeModel, const Reference<XShape>& rxShape)
    {
        if (!rTypeModel.maFlip.isEmpty())
        {
            if (SdrObject* pShape = SdrObject::getSdrObjectFromXShape(rxShape))
            {
                if (rTypeModel.maFlip.startsWith("x"))
                    doMirrorX(pShape);
                if (rTypeModel.maFlip.endsWith("y"))
                    doMirrorY(pShape);
            }
        }
    }

    void handleRotation(const ShapeTypeModel& rTypeModel, const Reference<XShape>& rxShape)
    {
        if (!rTypeModel.maRotation.isEmpty())
        {
            if (SdrObject* pShape = SdrObject::getSdrObjectFromXShape(rxShape))
            {
                // The needed factor -1 for opposite direction and factor 100 for Degree100 is
                // contained in method decodeRotation().
                Degree100 nAngle(ConversionHelper::decodeRotation(rTypeModel.maRotation));
                pShape->NbcRotate(pShape->GetSnapRect().Center(), nAngle);
            }
        }
    }
}

LineShape::LineShape(Drawing& rDrawing)
    : SimpleShape(rDrawing, u"com.sun.star.drawing.LineShape"_ustr)
{
}

Reference<XShape> LineShape::implConvertAndInsert(const Reference<XShapes>& rxShapes, const awt::Rectangle& rShapeRect) const
{
    Reference<XShape> xShape = SimpleShape::implConvertAndInsert(rxShapes, rShapeRect);
    // tdf#137765
    handleRotation(maTypeModel, xShape);
    // tdf#97517 tdf#137678
    // The MirroredX and MirroredY properties (in the CustomShapeGeometry property) are not
    // supported for the LineShape by UNO, so we have to make the mirroring here.
    handleMirroring(maTypeModel, xShape);
    return xShape;
}

awt::Rectangle LineShape::getAbsRectangle() const
{
    const GraphicHelper& rGraphicHelper = mrDrawing.getFilter().getGraphicHelper();
    awt::Rectangle aShapeRect;
    sal_Int32 nIndex = 0;

    aShapeRect.X = ConversionHelper::decodeMeasureToHmm(rGraphicHelper, o3tl::getToken(maShapeModel.maFrom, 0, ',', nIndex), 0, true, true);
    aShapeRect.Y = ConversionHelper::decodeMeasureToHmm(rGraphicHelper, o3tl::getToken(maShapeModel.maFrom, 0, ',', nIndex), 0, false, true);
    nIndex = 0;
    aShapeRect.Width = ConversionHelper::decodeMeasureToHmm(rGraphicHelper, o3tl::getToken(maShapeModel.maTo, 0, ',', nIndex), 0, true, true) - aShapeRect.X;
    aShapeRect.Height = ConversionHelper::decodeMeasureToHmm(rGraphicHelper, o3tl::getToken(maShapeModel.maTo, 0, ',', nIndex), 0, false, true) - aShapeRect.Y;
    return aShapeRect;
}

awt::Rectangle LineShape::getRelRectangle() const
{
    awt::Rectangle aShapeRect;
    sal_Int32 nIndex = 0;

    aShapeRect.X = o3tl::toInt32(o3tl::getToken(maShapeModel.maFrom, 0, ',', nIndex));
    aShapeRect.Y = o3tl::toInt32(o3tl::getToken(maShapeModel.maFrom, 0, ',', nIndex));
    nIndex = 0;
    aShapeRect.Width = o3tl::toInt32(o3tl::getToken(maShapeModel.maTo, 0, ',', nIndex)) - aShapeRect.X;
    aShapeRect.Height = o3tl::toInt32(o3tl::getToken(maShapeModel.maTo, 0, ',', nIndex)) - aShapeRect.Y;
    return aShapeRect;
}

BezierShape::BezierShape(Drawing& rDrawing)
    : SimpleShape(rDrawing, u"com.sun.star.drawing.OpenBezierShape"_ustr)
{
}

Reference< XShape > BezierShape::implConvertAndInsert( const Reference< XShapes >& rxShapes, const awt::Rectangle& rShapeRect ) const
{
    // If we have an 'x' in the last part of the path it means it is closed...
    sal_Int32 nPos = maShapeModel.maVmlPath.lastIndexOf(',');
    if ( nPos != -1 && maShapeModel.maVmlPath.indexOf('x', nPos) != -1 )
    {
        const_cast<BezierShape*>( this )->setService( u"com.sun.star.drawing.ClosedBezierShape"_ustr );
    }

    awt::Rectangle aCoordSys = getCoordSystem();
    PolyPolygonBezierCoords aBezierCoords;

    if( (aCoordSys.Width > 0) && (aCoordSys.Height > 0) )
    {
        const GraphicHelper& rGraphicHelper = mrDrawing.getFilter().getGraphicHelper();

        // Bezier paths may consist of one or more sub-paths
        typedef ::std::vector< ::std::vector< PolygonFlags > > FlagsList;
        std::vector< ::std::vector< awt::Point > > aCoordLists;
        FlagsList aFlagLists;

        // Curve defined by to, from, control1 and control2 attributes
        if ( maShapeModel.maVmlPath.isEmpty() )
        {
            aCoordLists.emplace_back( );
            aFlagLists.emplace_back( );
            sal_Int32 nIndex = 0;

            // Start point
            aCoordLists[ 0 ].emplace_back(
                  ConversionHelper::decodeMeasureToHmm( rGraphicHelper, o3tl::getToken(maShapeModel.maFrom, 0, ',', nIndex ), 0, true, true ),
                  ConversionHelper::decodeMeasureToHmm( rGraphicHelper, o3tl::getToken(maShapeModel.maFrom, 0, ',', nIndex ), 0, false, true ) );
            // Control point 1
            aCoordLists[ 0 ].emplace_back(
                      ConversionHelper::decodeMeasureToHmm( rGraphicHelper, o3tl::getToken(maShapeModel.maControl1, 0, ',', nIndex ), 0, true, true ),
                      ConversionHelper::decodeMeasureToHmm( rGraphicHelper, o3tl::getToken(maShapeModel.maControl1, 0, ',', nIndex ), 0, false, true ) );
            // Control point 2
            aCoordLists[ 0 ].emplace_back(
                      ConversionHelper::decodeMeasureToHmm( rGraphicHelper, o3tl::getToken(maShapeModel.maControl2, 0, ',', nIndex ), 0, true, true ),
                      ConversionHelper::decodeMeasureToHmm( rGraphicHelper, o3tl::getToken(maShapeModel.maControl2, 0, ',', nIndex ), 0, false, true ) );
            // End point
            aCoordLists[ 0 ].emplace_back(
                      ConversionHelper::decodeMeasureToHmm( rGraphicHelper, o3tl::getToken(maShapeModel.maTo, 0, ',', nIndex ), 0, true, true ),
                      ConversionHelper::decodeMeasureToHmm( rGraphicHelper, o3tl::getToken(maShapeModel.maTo, 0, ',', nIndex ), 0, false, true ) );

            // First and last points are normals, points 2 and 4 are controls
            aFlagLists[ 0 ].resize( aCoordLists[ 0 ].size(), PolygonFlags_CONTROL );
            aFlagLists[ 0 ][ 0 ] = PolygonFlags_NORMAL;
            aFlagLists[ 0 ].back() = PolygonFlags_NORMAL;
        }
        // Curve defined by path attribute
        else
        {
            // Parse VML path string and convert to absolute coordinates
            ConversionHelper::decodeVmlPath( aCoordLists, aFlagLists, maShapeModel.maVmlPath );

            for (auto & coordList : aCoordLists)
                for (auto & point : coordList)
                {
                    point = lclGetAbsPoint( point, rShapeRect, aCoordSys );
                }
        }

        aBezierCoords.Coordinates.realloc( aCoordLists.size() );
        auto pCoordinates = aBezierCoords.Coordinates.getArray();
        for ( size_t i = 0; i < aCoordLists.size(); i++ )
            pCoordinates[i] = comphelper::containerToSequence( aCoordLists[i] );

        aBezierCoords.Flags.realloc( aFlagLists.size() );
        auto pFlags = aBezierCoords.Flags.getArray();
        for ( size_t i = 0; i < aFlagLists.size(); i++ )
            pFlags[i] = comphelper::containerToSequence( aFlagLists[i] );

        if( !aCoordLists.front().empty() && !aCoordLists.back().empty()
            && aCoordLists.front().front().X == aCoordLists.back().back().X
            && aCoordLists.front().front().Y == aCoordLists.back().back().Y )
        { // HACK: If the shape is in fact closed, which can be found out only when the path is known,
          // force to closed bezier shape (otherwise e.g. fill won't work).
            const_cast< BezierShape* >( this )->setService( u"com.sun.star.drawing.ClosedBezierShape"_ustr );
        }
    }

    Reference< XShape > xShape = SimpleShape::implConvertAndInsert( rxShapes, rShapeRect );

    if( aBezierCoords.Coordinates.hasElements())
    {
        PropertySet aPropSet( xShape );
        aPropSet.setProperty( PROP_PolyPolygonBezier, aBezierCoords );
    }

    // tdf#105875 handle rotation
    // Note: must rotate before flip!
    handleRotation(maTypeModel, xShape);

    // Handle horizontal and vertical flip.
    handleMirroring(maTypeModel, xShape);

    return xShape;
}

CustomShape::CustomShape( Drawing& rDrawing ) :
    SimpleShape( rDrawing, u"com.sun.star.drawing.CustomShape"_ustr )
{
}

Reference< XShape > CustomShape::implConvertAndInsert( const Reference< XShapes >& rxShapes, const awt::Rectangle& rShapeRect ) const
{
    // try to create a custom shape
    Reference< XShape > xShape = SimpleShape::implConvertAndInsert( rxShapes, rShapeRect );
    if( xShape.is() ) try
    {
        // create the custom shape geometry
        Reference< XEnhancedCustomShapeDefaulter > xDefaulter( xShape, UNO_QUERY_THROW );
        xDefaulter->createCustomShapeDefaults( OUString::number( getShapeType() ) );
        // convert common properties
        convertShapeProperties( xShape );
    }
    catch( Exception& )
    {
    }
    return xShape;
}

ComplexShape::ComplexShape( Drawing& rDrawing ) :
    CustomShape( rDrawing )
{
}

Reference< XShape > ComplexShape::implConvertAndInsert( const Reference< XShapes >& rxShapes, const awt::Rectangle& rShapeRect ) const
{
    XmlFilterBase& rFilter = mrDrawing.getFilter();
    sal_Int32 nShapeType = getShapeType();
    OUString aGraphicPath = getGraphicPath();

    // try to find registered OLE object info
    if( const OleObjectInfo* pOleObjectInfo = mrDrawing.getOleObjectInfo( maTypeModel.maShapeId ) )
    {
        SAL_WARN_IF(
            nShapeType != VML_SHAPETYPE_PICTUREFRAME, "oox",
            "ComplexShape::implConvertAndInsert - unexpected shape type");

        // if OLE object is embedded into a DrawingML shape (PPTX), do not create it here
        if( pOleObjectInfo->mbDmlShape )
            return Reference< XShape >();

        PropertyMap aOleProps;
        awt::Size aOleSize( rShapeRect.Width, rShapeRect.Height );
        if( rFilter.getOleObjectHelper().importOleObject( aOleProps, *pOleObjectInfo, aOleSize ) )
        {
            Reference< XShape > xShape = mrDrawing.createAndInsertXShape( u"com.sun.star.drawing.OLE2Shape"_ustr, rxShapes, rShapeRect );
            if( xShape.is() )
            {
                // set the replacement graphic
                if( !aGraphicPath.isEmpty() )
                {
                    WmfExternal aExtHeader;
                    aExtHeader.mapMode = 8;
                    aExtHeader.xExt = rShapeRect.Width;
                    aExtHeader.yExt = rShapeRect.Height;

                    Reference< XGraphic > xGraphic = rFilter.getGraphicHelper().importEmbeddedGraphic(aGraphicPath, &aExtHeader);
                    if (xGraphic.is())
                        aOleProps.setProperty( PROP_Graphic, xGraphic);
                }

                PropertySet aPropSet( xShape );
                aPropSet.setProperties( aOleProps );

                return xShape;
            }
        }
    }

    // try to find registered form control info
    const ControlInfo* pControlInfo = mrDrawing.getControlInfo( maTypeModel.maShapeId );
    if( pControlInfo && !pControlInfo->maFragmentPath.isEmpty() )
    {
        if( !pControlInfo->maName.isEmpty() )
        {
            // load the control properties from fragment
            ::oox::ole::EmbeddedControl aControl(pControlInfo->maName);
            if( rFilter.importFragment( new ::oox::ole::AxControlFragment( rFilter, pControlInfo->maFragmentPath, aControl ) ) )
            {
                // create and return the control shape (including control model)
                sal_Int32 nCtrlIndex = -1;
                Reference< XShape > xShape = mrDrawing.createAndInsertXControlShape( aControl, rxShapes, rShapeRect, nCtrlIndex );

                if (pControlInfo->mbTextContentShape)
                {
                    PropertySet aPropertySet(xShape);
                    lcl_SetAnchorType(aPropertySet, maTypeModel, mrDrawing.getFilter().getGraphicHelper());
                }
                // on error, proceed and try to create picture from replacement image
                if( xShape.is() )
                    return xShape;
            }
        }
    }

    // host application wants to create the shape (do not try failed OLE controls again)
    if( (nShapeType == VML_SHAPETYPE_HOSTCONTROL) && !pControlInfo )
    {
        OSL_ENSURE( getClientData(), "ComplexShape::implConvertAndInsert - missing client data" );
        Reference< XShape > xShape = mrDrawing.createAndInsertClientXShape( *this, rxShapes, rShapeRect );
        if( xShape.is() )
            return xShape;
    }


    if( getShapeModel().mbIsSignatureLine )
    {
        uno::Reference<graphic::XGraphic> xGraphic;
        bool bIsSigned(false);
        try
        {
            // Get the document signatures
            Reference<security::XDocumentDigitalSignatures> xSignatures(
                security::DocumentDigitalSignatures::createDefault(
                    comphelper::getProcessComponentContext()));

            uno::Reference<embed::XStorage> xStorage
                = comphelper::OStorageHelper::GetStorageOfFormatFromURL(
                    ZIP_STORAGE_FORMAT_STRING, mrDrawing.getFilter().getFileUrl(),
                    embed::ElementModes::READ);
            SAL_WARN_IF(!xStorage.is(), "oox.vml", "No xStorage!");

            const uno::Sequence<security::DocumentSignatureInformation> xSignatureInfo
                = xSignatures->verifyScriptingContentSignatures(xStorage,
                                                                uno::Reference<io::XInputStream>());

            // Try to find matching signature line image - if none exists that is fine,
            // then the signature line is not digitally signed.
            auto pSignInfo = std::find_if(xSignatureInfo.begin(), xSignatureInfo.end(),
                [this](const security::DocumentSignatureInformation& rSigInfo) {
                    return rSigInfo.SignatureLineId == getShapeModel().maSignatureId; });
            if (pSignInfo != xSignatureInfo.end())
            {
                bIsSigned = true;
                if (pSignInfo->SignatureIsValid)
                {
                    // Signature is valid, use the 'valid' image
                    SAL_WARN_IF(!pSignInfo->ValidSignatureLineImage.is(), "oox.vml",
                                "No ValidSignatureLineImage!");
                    xGraphic = pSignInfo->ValidSignatureLineImage;
                }
                else
                {
                    // Signature is invalid, use the 'invalid' image
                    SAL_WARN_IF(!pSignInfo->InvalidSignatureLineImage.is(), "oox.vml",
                                "No InvalidSignatureLineImage!");
                    xGraphic = pSignInfo->InvalidSignatureLineImage;
                }
            }
        }
        catch (css::uno::Exception&)
        {
            // DocumentDigitalSignatures service not available.
            // We continue by rendering the "unsigned" shape instead.
        }

        Reference< XShape > xShape;
        if (xGraphic.is())
        {
            // If available, use the signed image from the signature
            xShape = SimpleShape::createPictureObject(rxShapes, rShapeRect, xGraphic);
        }
        else
        {
            // Create shape with the fallback "unsigned" image
            xShape = SimpleShape::createEmbeddedPictureObject(rxShapes, rShapeRect, aGraphicPath);
        }

        // Store signature line properties
        uno::Reference<beans::XPropertySet> xPropertySet(xShape, uno::UNO_QUERY);
        xPropertySet->setPropertyValue(u"IsSignatureLine"_ustr, uno::Any(true));
        xPropertySet->setPropertyValue(u"SignatureLineId"_ustr,
                                        uno::Any(getShapeModel().maSignatureId));
        xPropertySet->setPropertyValue(
            u"SignatureLineSuggestedSignerName"_ustr,
            uno::Any(getShapeModel().maSignatureLineSuggestedSignerName));
        xPropertySet->setPropertyValue(
            u"SignatureLineSuggestedSignerTitle"_ustr,
            uno::Any(getShapeModel().maSignatureLineSuggestedSignerTitle));
        xPropertySet->setPropertyValue(
            u"SignatureLineSuggestedSignerEmail"_ustr,
            uno::Any(getShapeModel().maSignatureLineSuggestedSignerEmail));
        xPropertySet->setPropertyValue(
            u"SignatureLineSigningInstructions"_ustr,
            uno::Any(getShapeModel().maSignatureLineSigningInstructions));
        xPropertySet->setPropertyValue(
            u"SignatureLineShowSignDate"_ustr,
            uno::Any(getShapeModel().mbSignatureLineShowSignDate));
        xPropertySet->setPropertyValue(
            u"SignatureLineCanAddComment"_ustr,
            uno::Any(getShapeModel().mbSignatureLineCanAddComment));
        xPropertySet->setPropertyValue(u"SignatureLineIsSigned"_ustr, uno::Any(bIsSigned));

        if (!aGraphicPath.isEmpty())
        {
            xGraphic = rFilter.getGraphicHelper().importEmbeddedGraphic(aGraphicPath);
            xPropertySet->setPropertyValue(u"SignatureLineUnsignedImage"_ustr, uno::Any(xGraphic));
        }
        return xShape;
    }

    // try to create a picture object
    if( !aGraphicPath.isEmpty() )
    {
        Reference<XShape> xShape = SimpleShape::createEmbeddedPictureObject(rxShapes, rShapeRect, aGraphicPath);
        // AS_CHARACTER shape: vertical orientation default is bottom, MSO default is top.
        if ( maTypeModel.maPosition != "absolute" && maTypeModel.maPosition != "relative" )
            PropertySet( xShape ).setAnyProperty( PROP_VertOrient, Any(text::VertOrientation::TOP));

        // Apply stroke props from the type model.
        oox::drawingml::ShapePropertyMap aPropMap(mrDrawing.getFilter().getModelObjectHelper());
        const GraphicHelper& rGraphicHelper = mrDrawing.getFilter().getGraphicHelper();
        maTypeModel.maStrokeModel.pushToPropMap(aPropMap, rGraphicHelper);
        // And, fill-color properties as well...
        maTypeModel.maFillModel.pushToPropMap(aPropMap, rGraphicHelper);
        PropertySet(xShape).setProperties(aPropMap);

        return xShape;
    }

    // default: try to create a custom shape
    return CustomShape::implConvertAndInsert( rxShapes, rShapeRect );
}

GroupShape::GroupShape( Drawing& rDrawing ) :
    ShapeBase( rDrawing ),
    mxChildren( new ShapeContainer( rDrawing ) )
{
}

GroupShape::~GroupShape()
{
}

void GroupShape::finalizeFragmentImport()
{
    // basic shape processing
    ShapeBase::finalizeFragmentImport();
    // finalize all child shapes
    mxChildren->finalizeFragmentImport();
}

const ShapeType* GroupShape::getChildTypeById( const OUString& rShapeId ) const
{
    return mxChildren->getShapeTypeById( rShapeId );
}

const ShapeBase* GroupShape::getChildById( const OUString& rShapeId ) const
{
    return mxChildren->getShapeById( rShapeId );
}

Reference< XShape > GroupShape::implConvertAndInsert( const Reference< XShapes >& rxShapes, const awt::Rectangle& rShapeRect ) const
{
    Reference< XShape > xGroupShape;
    // check that this shape contains children and a valid coordinate system
    ShapeParentAnchor aParentAnchor;
    aParentAnchor.maShapeRect = rShapeRect;
    aParentAnchor.maCoordSys = getCoordSystem();
    if( !mxChildren->empty() && (aParentAnchor.maCoordSys.Width > 0) && (aParentAnchor.maCoordSys.Height > 0) ) try
    {
        xGroupShape = mrDrawing.createAndInsertXShape( u"com.sun.star.drawing.GroupShape"_ustr, rxShapes, rShapeRect );
        Reference< XShapes > xChildShapes( xGroupShape, UNO_QUERY_THROW );
        mxChildren->convertAndInsert( xChildShapes, &aParentAnchor );
        if( !xChildShapes->hasElements() )
        {
            SAL_WARN("oox", "no child shape has been created - deleting the group shape");
            rxShapes->remove( xGroupShape );
            xGroupShape.clear();
        }
    }
    catch( Exception& )
    {
    }

    uno::Reference<beans::XPropertySet> xPropertySet;
    if (!maTypeModel.maEditAs.isEmpty())
        xPropertySet = uno::Reference<beans::XPropertySet>(xGroupShape, uno::UNO_QUERY);
    if (xPropertySet.is())
    {
        uno::Sequence<beans::PropertyValue> aGrabBag;
        xPropertySet->getPropertyValue(u"InteropGrabBag"_ustr) >>= aGrabBag;
        sal_Int32 nLength = aGrabBag.getLength();
        aGrabBag.realloc(nLength + 1);
        aGrabBag.getArray()[nLength] = comphelper::makePropertyValue(u"mso-edit-as"_ustr, maTypeModel.maEditAs);
        xPropertySet->setPropertyValue(u"InteropGrabBag"_ustr, uno::Any(aGrabBag));
    }
    // Make sure group shapes are inline as well, unless there is an explicit different style.
    PropertySet aPropertySet(xGroupShape);
    const GraphicHelper& rGraphicHelper = mrDrawing.getFilter().getGraphicHelper();
    lcl_SetAnchorType(aPropertySet, maTypeModel, rGraphicHelper);
    if (!maTypeModel.maRotation.isEmpty())
        aPropertySet.setAnyProperty(PROP_RotateAngle, Any(ConversionHelper::decodeRotation(maTypeModel.maRotation).get()));
    return xGroupShape;
}

} // namespace oox

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