/* -*- 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/.
 */
 
#include "rtfsdrimport.hxx"
#include <cmath>
#include <com/sun/star/container/XNamed.hpp>
#include <com/sun/star/drawing/FillStyle.hpp>
#include <com/sun/star/drawing/EnhancedCustomShapeParameterPair.hpp>
#include <com/sun/star/drawing/XEnhancedCustomShapeDefaulter.hpp>
#include <com/sun/star/drawing/XDrawPageSupplier.hpp>
#include <com/sun/star/drawing/LineStyle.hpp>
#include <com/sun/star/drawing/EnhancedCustomShapeSegment.hpp>
#include <com/sun/star/drawing/EnhancedCustomShapeSegmentCommand.hpp>
#include <com/sun/star/lang/XServiceInfo.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/text/WritingMode.hpp>
#include <com/sun/star/text/TextContentAnchorType.hpp>
#include <com/sun/star/text/XTextRange.hpp>
#include <com/sun/star/lang/XMultiServiceFactory.hpp>
#include <ooxml/resourceids.hxx>
#include <filter/msfilter/escherex.hxx>
#include <filter/msfilter/util.hxx>
#include <filter/msfilter/rtfutil.hxx>
#include <sal/log.hxx>
#include <svx/svdtrans.hxx>
#include <comphelper/sequence.hxx>
#include <comphelper/propertyvalue.hxx>
#include <comphelper/propertysequence.hxx>
#include "rtfreferenceproperties.hxx"
#include <oox/vml/vmlformatting.hxx>
#include <oox/helper/modelobjecthelper.hxx>
#include <oox/drawingml/shapepropertymap.hxx>
#include <oox/helper/propertyset.hxx>
#include <boost/logic/tribool.hpp>
#include <basegfx/matrix/b2dhommatrix.hxx>
#include <dmapper/GraphicZOrderHelper.hxx>
#include "rtfdocumentimpl.hxx"
 
using namespace com::sun::star;
 
namespace writerfilter
{
namespace rtftok
{
RTFSdrImport::RTFSdrImport(RTFDocumentImpl& rDocument,
                           uno::Reference<lang::XComponent> const& xDstDoc)
    : m_rImport(rDocument)
    , m_bTextFrame(false)
    , m_bTextGraphicObject(false)
    , m_bFakePict(false)
{
    uno::Reference<drawing::XDrawPageSupplier> xDrawings(xDstDoc, uno::UNO_QUERY);
    if (xDrawings.is())
        m_aParents.push(xDrawings->getDrawPage());
    m_aGraphicZOrderHelpers.push(writerfilter::dmapper::GraphicZOrderHelper());
}
 
RTFSdrImport::~RTFSdrImport()
{
    if (!m_aGraphicZOrderHelpers.empty())
        m_aGraphicZOrderHelpers.pop();
    if (!m_aParents.empty())
        m_aParents.pop();
}
 
void RTFSdrImport::createShape(const OUString& rService, uno::Reference<drawing::XShape>& xShape,
                               uno::Reference<beans::XPropertySet>& xPropertySet)
{
    if (m_rImport.getModelFactory().is())
        xShape.set(m_rImport.getModelFactory()->createInstance(rService), uno::UNO_QUERY);
    xPropertySet.set(xShape, uno::UNO_QUERY);
}
 
std::vector<beans::PropertyValue> RTFSdrImport::getTextFrameDefaults(bool bNew)
{
    std::vector<beans::PropertyValue> aRet;
    beans::PropertyValue aPropertyValue;
 
    aPropertyValue.Name = "HoriOrient";
    aPropertyValue.Value <<= text::HoriOrientation::NONE;
    aRet.push_back(aPropertyValue);
    aPropertyValue.Name = "HoriOrientRelation";
    aPropertyValue.Value <<= text::RelOrientation::FRAME;
    aRet.push_back(aPropertyValue);
    aPropertyValue.Name = "VertOrient";
    aPropertyValue.Value <<= text::VertOrientation::NONE;
    aRet.push_back(aPropertyValue);
    aPropertyValue.Name = "VertOrientRelation";
    aPropertyValue.Value <<= text::RelOrientation::FRAME;
    aRet.push_back(aPropertyValue);
    if (!bNew)
    {
        aPropertyValue.Name = "BackColorTransparency";
        aPropertyValue.Value <<= sal_Int32(100);
        aRet.push_back(aPropertyValue);
    }
    // See the spec, new-style frame default margins are specified in EMUs.
    aPropertyValue.Name = "LeftBorderDistance";
    aPropertyValue.Value <<= sal_Int32(bNew ? (91440 / 360) : 0);
    aRet.push_back(aPropertyValue);
    aPropertyValue.Name = "RightBorderDistance";
    aPropertyValue.Value <<= sal_Int32(bNew ? (91440 / 360) : 0);
    aRet.push_back(aPropertyValue);
    aPropertyValue.Name = "TopBorderDistance";
    aPropertyValue.Value <<= sal_Int32(bNew ? (45720 / 360) : 0);
    aRet.push_back(aPropertyValue);
    aPropertyValue.Name = "BottomBorderDistance";
    aPropertyValue.Value <<= sal_Int32(bNew ? (45720 / 360) : 0);
    aRet.push_back(aPropertyValue);
    aPropertyValue.Name = "SizeType";
    aPropertyValue.Value <<= text::SizeType::FIX;
    aRet.push_back(aPropertyValue);
    return aRet;
}
 
void RTFSdrImport::pushParent(uno::Reference<drawing::XShapes> const& xParent)
{
    m_aParents.push(xParent);
    m_aGraphicZOrderHelpers.push(writerfilter::dmapper::GraphicZOrderHelper());
}
 
void RTFSdrImport::popParent()
{
    if (!m_aGraphicZOrderHelpers.empty())
        m_aGraphicZOrderHelpers.pop();
    if (!m_aParents.empty())
        m_aParents.pop();
}
 
void RTFSdrImport::resolveDhgt(uno::Reference<beans::XPropertySet> const& xPropertySet,
                               sal_Int32 const nZOrder, bool const bOldStyle)
{
    if (!m_aGraphicZOrderHelpers.empty())
    {
        writerfilter::dmapper::GraphicZOrderHelper& rHelper = m_aGraphicZOrderHelpers.top();
        xPropertySet->setPropertyValue("ZOrder",
                                       uno::makeAny(rHelper.findZOrder(nZOrder, bOldStyle)));
        rHelper.addItem(xPropertySet, nZOrder);
    }
}
 
void RTFSdrImport::resolveLineColorAndWidth(bool bTextFrame,
                                            const uno::Reference<beans::XPropertySet>& xPropertySet,
                                            uno::Any const& rLineColor, uno::Any const& rLineWidth)
{
    if (!bTextFrame)
    {
        xPropertySet->setPropertyValue("LineColor", rLineColor);
        xPropertySet->setPropertyValue("LineWidth", rLineWidth);
    }
    else
    {
        static const char* aBorders[]
            = { "TopBorder", "LeftBorder", "BottomBorder", "RightBorder" };
        for (const char* pBorder : aBorders)
        {
            auto aBorderLine = xPropertySet->getPropertyValue(OUString::createFromAscii(pBorder))
                                   .get<table::BorderLine2>();
            if (rLineColor.hasValue())
                aBorderLine.Color = rLineColor.get<sal_Int32>();
            if (rLineWidth.hasValue())
                aBorderLine.LineWidth = rLineWidth.get<sal_Int32>();
            xPropertySet->setPropertyValue(OUString::createFromAscii(pBorder),
                                           uno::makeAny(aBorderLine));
        }
    }
}
 
void RTFSdrImport::resolveFLine(uno::Reference<beans::XPropertySet> const& xPropertySet,
                                sal_Int32 const nFLine)
{
    if (nFLine == 0)
        xPropertySet->setPropertyValue("LineStyle", uno::makeAny(drawing::LineStyle_NONE));
    else
        xPropertySet->setPropertyValue("LineStyle", uno::makeAny(drawing::LineStyle_SOLID));
}
 
void RTFSdrImport::applyProperty(uno::Reference<drawing::XShape> const& xShape,
                                 const OUString& aKey, const OUString& aValue)
{
    uno::Reference<beans::XPropertySet> xPropertySet(xShape, uno::UNO_QUERY);
    sal_Int16 nHoriOrient = 0;
    sal_Int16 nVertOrient = 0;
    boost::logic::tribool obFitShapeToText(boost::logic::indeterminate);
    bool bFilled = true;
 
    if (aKey == "posh")
    {
        switch (aValue.toInt32())
        {
            case 1:
                nHoriOrient = text::HoriOrientation::LEFT;
                break;
            case 2:
                nHoriOrient = text::HoriOrientation::CENTER;
                break;
            case 3:
                nHoriOrient = text::HoriOrientation::RIGHT;
                break;
            case 4:
                nHoriOrient = text::HoriOrientation::INSIDE;
                break;
            case 5:
                nHoriOrient = text::HoriOrientation::OUTSIDE;
                break;
            default:
                break;
        }
    }
    else if (aKey == "posv")
    {
        switch (aValue.toInt32())
        {
            case 1:
                nVertOrient = text::VertOrientation::TOP;
                break;
            case 2:
                nVertOrient = text::VertOrientation::CENTER;
                break;
            case 3:
                nVertOrient = text::VertOrientation::BOTTOM;
                break;
            default:
                break;
        }
    }
    else if (aKey == "fFitShapeToText")
        obFitShapeToText = aValue.toInt32() == 1;
    else if (aKey == "fFilled")
        bFilled = aValue.toInt32() == 1;
    else if (aKey == "rotation")
    {
        // See DffPropertyReader::Fix16ToAngle(): in RTF, positive rotation angles are clockwise, we have them as counter-clockwise.
        // Additionally, RTF type is 0..360*2^16, our is 0..360*100.
        sal_Int32 nRotation = aValue.toInt32() * 100 / RTF_MULTIPLIER;
        uno::Reference<lang::XServiceInfo> xServiceInfo(xShape, uno::UNO_QUERY);
        if (!xServiceInfo->supportsService("com.sun.star.text.TextFrame"))
            xPropertySet->setPropertyValue(
                "RotateAngle",
                uno::makeAny(sal_Int32(NormAngle36000(static_cast<long>(nRotation) * -1))));
    }
 
    if (nHoriOrient != 0 && xPropertySet.is())
        xPropertySet->setPropertyValue("HoriOrient", uno::makeAny(nHoriOrient));
    if (nVertOrient != 0 && xPropertySet.is())
        xPropertySet->setPropertyValue("VertOrient", uno::makeAny(nVertOrient));
    if (!boost::logic::indeterminate(obFitShapeToText) && xPropertySet.is())
    {
        xPropertySet->setPropertyValue(
            "SizeType", uno::makeAny(obFitShapeToText ? text::SizeType::MIN : text::SizeType::FIX));
        xPropertySet->setPropertyValue("FrameIsAutomaticHeight",
                                       uno::makeAny(static_cast<bool>(obFitShapeToText)));
    }
    if (!bFilled && xPropertySet.is())
    {
        if (m_bTextFrame)
            xPropertySet->setPropertyValue("BackColorTransparency", uno::makeAny(sal_Int32(100)));
        else
            xPropertySet->setPropertyValue("FillStyle", uno::makeAny(drawing::FillStyle_NONE));
    }
}
 
int RTFSdrImport::initShape(uno::Reference<drawing::XShape>& o_xShape,
                            uno::Reference<beans::XPropertySet>& o_xPropSet, bool& o_rIsCustomShape,
                            RTFShape const& rShape, bool const bClose,
                            ShapeOrPict const shapeOrPict)
{
    assert(!o_xShape.is());
    assert(!o_xPropSet.is());
    o_rIsCustomShape = false;
    m_bFakePict = false;
 
    // first, find the shape type
    int nType = -1;
    auto iter = std::find_if(
        rShape.aProperties.begin(), rShape.aProperties.end(),
        [](std::pair<OUString, OUString> aProperty) { return aProperty.first == "shapeType"; });
 
    if (iter == rShape.aProperties.end())
    {
        if (SHAPE == shapeOrPict)
        {
            // The spec doesn't state what is the default for shapeType,
            // Word seems to implement it as a rectangle.
            nType = ESCHER_ShpInst_Rectangle;
        }
        else
        {
            // pict is picture by default but can be a rectangle too fdo#79319
            nType = ESCHER_ShpInst_PictureFrame;
        }
    }
    else
    {
        nType = iter->second.toInt32();
        if (PICT == shapeOrPict && ESCHER_ShpInst_PictureFrame != nType)
        {
            m_bFakePict = true;
        }
    }
 
    switch (nType)
    {
        case ESCHER_ShpInst_PictureFrame:
            createShape("com.sun.star.drawing.GraphicObjectShape", o_xShape, o_xPropSet);
            m_bTextGraphicObject = true;
            break;
        case ESCHER_ShpInst_Line:
            createShape("com.sun.star.drawing.LineShape", o_xShape, o_xPropSet);
            break;
        case ESCHER_ShpInst_Rectangle:
        case ESCHER_ShpInst_TextBox:
            // If we're inside a groupshape, can't use text frames.
            if (!bClose && m_aParents.size() == 1)
            {
                createShape("com.sun.star.text.TextFrame", o_xShape, o_xPropSet);
                m_bTextFrame = true;
                std::vector<beans::PropertyValue> aDefaults = getTextFrameDefaults(true);
                for (beans::PropertyValue& i : aDefaults)
                    o_xPropSet->setPropertyValue(i.Name, i.Value);
                break;
            }
            SAL_FALLTHROUGH;
        default:
            createShape("com.sun.star.drawing.CustomShape", o_xShape, o_xPropSet);
            o_rIsCustomShape = true;
            break;
    }
 
    // Defaults
    if (o_xPropSet.is() && !m_bTextFrame)
    {
        o_xPropSet->setPropertyValue(
            "FillColor",
            uno::makeAny(sal_uInt32(0xffffff))); // White in Word, kind of blue in Writer.
        o_xPropSet->setPropertyValue("VertOrient", uno::makeAny(text::VertOrientation::NONE));
    }
 
    return nType;
}
 
void RTFSdrImport::resolve(RTFShape& rShape, bool bClose, ShapeOrPict const shapeOrPict)
{
    bool bPib = false;
    m_bTextFrame = false;
    m_bTextGraphicObject = false;
 
    uno::Reference<drawing::XShape> xShape;
    uno::Reference<beans::XPropertySet> xPropertySet;
    uno::Any aAny;
    beans::PropertyValue aPropertyValue;
    awt::Rectangle aViewBox;
    std::vector<beans::PropertyValue> aPath;
    // Default line color is black in Word, blue in Writer.
    uno::Any aLineColor = uno::makeAny(COL_BLACK);
    // Default line width is 0.75 pt (26 mm100) in Word, 0 in Writer.
    uno::Any aLineWidth = uno::makeAny(sal_Int32(26));
    text::WritingMode eWritingMode = text::WritingMode_LR_TB;
    // Groupshape support
    boost::optional<sal_Int32> oGroupLeft, oGroupTop, oGroupRight, oGroupBottom;
    boost::optional<sal_Int32> oRelLeft, oRelTop, oRelRight, oRelBottom;
 
    // Importing these are not trivial, let the VML import do the hard work.
    oox::vml::FillModel aFillModel; // Gradient.
    oox::vml::ShadowModel aShadowModel; // Shadow.
 
    bool bOpaque = true;
 
    boost::optional<sal_Int16> oRelativeWidth, oRelativeHeight;
    sal_Int16 nRelativeWidthRelation = text::RelOrientation::PAGE_FRAME;
    sal_Int16 nRelativeHeightRelation = text::RelOrientation::PAGE_FRAME;
    boost::logic::tribool obRelFlipV(boost::logic::indeterminate);
    boost::logic::tribool obFlipH(boost::logic::indeterminate);
    boost::logic::tribool obFlipV(boost::logic::indeterminate);
 
    OUString aShapeText = "";
    OUString aFontFamily = "";
    float nFontSize = 1.0;
 
    bool bCustom(false);
    int const nType = initShape(xShape, xPropertySet, bCustom, rShape, bClose, shapeOrPict);
 
    for (auto& rProperty : rShape.aProperties)
    {
        if (rProperty.first == "shapeType")
        {
            continue; // ignore: already handled by initShape
        }
        if (rProperty.first == "wzName")
        {
            if (m_bTextFrame)
            {
                uno::Reference<container::XNamed> xNamed(xShape, uno::UNO_QUERY);
                xNamed->setName(rProperty.second);
            }
            else
                xPropertySet->setPropertyValue("Name", uno::makeAny(rProperty.second));
        }
        else if (rProperty.first == "wzDescription")
            xPropertySet->setPropertyValue("Description", uno::makeAny(rProperty.second));
        else if (rProperty.first == "gtextUNICODE")
            aShapeText = rProperty.second;
        else if (rProperty.first == "gtextFont")
            aFontFamily = rProperty.second;
        else if (rProperty.first == "gtextSize")
        {
            // RTF size is multiplied by 2^16
            nFontSize = static_cast<float>(rProperty.second.toUInt32()) / RTF_MULTIPLIER;
        }
        else if (rProperty.first == "pib")
        {
            m_rImport.setDestinationText(rProperty.second);
            bPib = true;
        }
        else if (rProperty.first == "fillColor" && xPropertySet.is())
        {
            aAny <<= msfilter::util::BGRToRGB(rProperty.second.toUInt32());
            if (m_bTextFrame)
                xPropertySet->setPropertyValue("BackColor", aAny);
            else
                xPropertySet->setPropertyValue("FillColor", aAny);
 
            // fillType will decide, possible it'll be the start color of a gradient.
            aFillModel.moColor.set(
                "#" + OUString::fromUtf8(msfilter::util::ConvertColor(aAny.get<sal_Int32>())));
        }
        else if (rProperty.first == "fillBackColor")
            // fillType will decide, possible it'll be the end color of a gradient.
            aFillModel.moColor2.set("#"
                                    + OUString::fromUtf8(msfilter::util::ConvertColor(
                                          msfilter::util::BGRToRGB(rProperty.second.toInt32()))));
        else if (rProperty.first == "lineColor")
            aLineColor <<= msfilter::util::BGRToRGB(rProperty.second.toInt32());
        else if (rProperty.first == "lineBackColor")
            ; // Ignore: complementer of lineColor
        else if (rProperty.first == "txflTextFlow" && xPropertySet.is())
        {
            if (rProperty.second.toInt32() == 1)
                eWritingMode = text::WritingMode_TB_RL;
        }
        else if (rProperty.first == "fLine" && xPropertySet.is())
            resolveFLine(xPropertySet, rProperty.second.toInt32());
        else if (rProperty.first == "fillOpacity" && xPropertySet.is())
        {
            int opacity = 100 - (rProperty.second.toInt32()) * 100 / RTF_MULTIPLIER;
            xPropertySet->setPropertyValue("FillTransparence", uno::Any(sal_uInt32(opacity)));
        }
        else if (rProperty.first == "lineWidth")
            aLineWidth <<= rProperty.second.toInt32() / 360;
        else if (rProperty.first == "pVerticies")
        {
            std::vector<drawing::EnhancedCustomShapeParameterPair> aCoordinates;
            sal_Int32 nSize = 0; // Size of a token (its value is hardwired in the exporter)
            sal_Int32 nCount = 0; // Number of tokens
            sal_Int32 nCharIndex = 0; // Character index
            do
            {
                OUString aToken = rProperty.second.getToken(0, ';', nCharIndex);
                if (!nSize)
                    nSize = aToken.toInt32();
                else if (!nCount)
                    nCount = aToken.toInt32();
                else if (aToken.getLength())
                {
                    // The coordinates are in an (x,y) form.
                    aToken = aToken.copy(1, aToken.getLength() - 2);
                    sal_Int32 nI = 0;
                    boost::optional<sal_Int32> oX;
                    boost::optional<sal_Int32> oY;
                    do
                    {
                        OUString aPoint = aToken.getToken(0, ',', nI);
                        if (!oX)
                            oX.reset(aPoint.toInt32());
                        else
                            oY.reset(aPoint.toInt32());
                    } while (nI >= 0);
                    drawing::EnhancedCustomShapeParameterPair aPair;
                    aPair.First.Value <<= *oX;
                    aPair.Second.Value <<= *oY;
                    aCoordinates.push_back(aPair);
                }
            } while (nCharIndex >= 0);
            aPropertyValue.Name = "Coordinates";
            aPropertyValue.Value <<= comphelper::containerToSequence(aCoordinates);
            aPath.push_back(aPropertyValue);
        }
        else if (rProperty.first == "pSegmentInfo")
        {
            std::vector<drawing::EnhancedCustomShapeSegment> aSegments;
            sal_Int32 nSize = 0;
            sal_Int32 nCount = 0;
            sal_Int32 nCharIndex = 0;
            do
            {
                sal_Int32 nSeg = rProperty.second.getToken(0, ';', nCharIndex).toInt32();
                if (!nSize)
                    nSize = nSeg;
                else if (!nCount)
                    nCount = nSeg;
                else
                {
                    sal_Int32 nPoints = 1;
                    if (nSeg >= 0x2000 && nSeg < 0x20FF)
                    {
                        nPoints = nSeg & 0x0FFF;
                        nSeg &= 0xFF00;
                    }
 
                    drawing::EnhancedCustomShapeSegment aSegment;
                    switch (nSeg)
                    {
                        case 0x0001: // lineto
                            aSegment.Command = drawing::EnhancedCustomShapeSegmentCommand::LINETO;
                            aSegment.Count = sal_Int32(1);
                            aSegments.push_back(aSegment);
                            break;
                        case 0x4000: // moveto
                            aSegment.Command = drawing::EnhancedCustomShapeSegmentCommand::MOVETO;
                            aSegment.Count = sal_Int32(1);
                            aSegments.push_back(aSegment);
                            break;
                        case 0x2000: // curveto
                            aSegment.Command = drawing::EnhancedCustomShapeSegmentCommand::CURVETO;
                            aSegment.Count = nPoints;
                            aSegments.push_back(aSegment);
                            break;
                        case 0xb300: // arcto
                            aSegment.Command = drawing::EnhancedCustomShapeSegmentCommand::ARCTO;
                            aSegment.Count = sal_Int32(0);
                            aSegments.push_back(aSegment);
                            break;
                        case 0xac00:
                        case 0xaa00: // nofill
                        case 0xab00: // nostroke
                        case 0x6001: // close
                            break;
                        case 0x8000: // end
                            aSegment.Command
                                = drawing::EnhancedCustomShapeSegmentCommand::ENDSUBPATH;
                            aSegment.Count = sal_Int32(0);
                            aSegments.push_back(aSegment);
                            break;
                        default: // given number of lineto elements
                            aSegment.Command = drawing::EnhancedCustomShapeSegmentCommand::LINETO;
                            aSegment.Count = nSeg;
                            aSegments.push_back(aSegment);
                            break;
                    }
                }
            } while (nCharIndex >= 0);
            aPropertyValue.Name = "Segments";
            aPropertyValue.Value <<= comphelper::containerToSequence(aSegments);
            aPath.push_back(aPropertyValue);
        }
        else if (rProperty.first == "geoLeft")
            aViewBox.X = rProperty.second.toInt32();
        else if (rProperty.first == "geoTop")
            aViewBox.Y = rProperty.second.toInt32();
        else if (rProperty.first == "geoRight")
            aViewBox.Width = rProperty.second.toInt32();
        else if (rProperty.first == "geoBottom")
            aViewBox.Height = rProperty.second.toInt32();
        else if (rProperty.first == "dhgt")
        {
            // dhgt is Word 2007, \shpz is Word 97-2003, the later has priority.
            if (!rShape.oZ)
                resolveDhgt(xPropertySet, rProperty.second.toInt32(), /*bOldStyle=*/false);
        }
        // These are in EMU, convert to mm100.
        else if (rProperty.first == "dxTextLeft")
        {
            if (xPropertySet.is())
                xPropertySet->setPropertyValue("LeftBorderDistance",
                                               uno::makeAny(rProperty.second.toInt32() / 360));
        }
        else if (rProperty.first == "dyTextTop")
        {
            if (xPropertySet.is())
                xPropertySet->setPropertyValue("TopBorderDistance",
                                               uno::makeAny(rProperty.second.toInt32() / 360));
        }
        else if (rProperty.first == "dxTextRight")
        {
            if (xPropertySet.is())
                xPropertySet->setPropertyValue("RightBorderDistance",
                                               uno::makeAny(rProperty.second.toInt32() / 360));
        }
        else if (rProperty.first == "dyTextBottom")
        {
            if (xPropertySet.is())
                xPropertySet->setPropertyValue("BottomBorderDistance",
                                               uno::makeAny(rProperty.second.toInt32() / 360));
        }
        else if (rProperty.first == "dxWrapDistLeft")
        {
            if (m_bTextGraphicObject)
                rShape.aAnchorAttributes.set(NS_ooxml::LN_CT_Anchor_distL,
                                             new RTFValue(rProperty.second.toInt32()));
            else if (xPropertySet.is())
                xPropertySet->setPropertyValue("LeftMargin",
                                               uno::makeAny(rProperty.second.toInt32() / 360));
        }
        else if (rProperty.first == "dyWrapDistTop")
        {
            if (m_bTextGraphicObject)
                rShape.aAnchorAttributes.set(NS_ooxml::LN_CT_Anchor_distT,
                                             new RTFValue(rProperty.second.toInt32()));
            else if (xPropertySet.is())
                xPropertySet->setPropertyValue("TopMargin",
                                               uno::makeAny(rProperty.second.toInt32() / 360));
        }
        else if (rProperty.first == "dxWrapDistRight")
        {
            if (m_bTextGraphicObject)
                rShape.aAnchorAttributes.set(NS_ooxml::LN_CT_Anchor_distR,
                                             new RTFValue(rProperty.second.toInt32()));
            else if (xPropertySet.is())
                xPropertySet->setPropertyValue("RightMargin",
                                               uno::makeAny(rProperty.second.toInt32() / 360));
        }
        else if (rProperty.first == "dyWrapDistBottom")
        {
            if (m_bTextGraphicObject)
                rShape.aAnchorAttributes.set(NS_ooxml::LN_CT_Anchor_distB,
                                             new RTFValue(rProperty.second.toInt32()));
            else if (xPropertySet.is())
                xPropertySet->setPropertyValue("BottomMargin",
                                               uno::makeAny(rProperty.second.toInt32() / 360));
        }
        else if (rProperty.first == "fillType")
        {
            switch (rProperty.second.toInt32())
            {
                case 7: // Shade using the fillAngle
                    aFillModel.moType.set(oox::XML_gradient);
                    break;
                default:
                    SAL_INFO("writerfilter",
                             "TODO handle fillType value '" << rProperty.second << "'");
                    break;
            }
        }
        else if (rProperty.first == "fillFocus")
            aFillModel.moFocus.set(rProperty.second.toDouble() / 100); // percent
        else if (rProperty.first == "fShadow" && xPropertySet.is())
        {
            if (rProperty.second.toInt32() == 1)
                aShadowModel.mbHasShadow = true;
        }
        else if (rProperty.first == "shadowColor")
            aShadowModel.moColor.set("#"
                                     + OUString::fromUtf8(msfilter::util::ConvertColor(
                                           msfilter::util::BGRToRGB(rProperty.second.toInt32()))));
        else if (rProperty.first == "shadowOffsetX")
            // EMUs to points
            aShadowModel.moOffset.set(OUString::number(rProperty.second.toDouble() / 12700) + "pt");
        else if (rProperty.first == "posh" || rProperty.first == "posv"
                 || rProperty.first == "fFitShapeToText" || rProperty.first == "fFilled"
                 || rProperty.first == "rotation")
            applyProperty(xShape, rProperty.first, rProperty.second);
        else if (rProperty.first == "posrelh")
        {
            switch (rProperty.second.toInt32())
            {
                case 1:
                    rShape.nHoriOrientRelation = text::RelOrientation::PAGE_FRAME;
                    break;
                default:
                    break;
            }
        }
        else if (rProperty.first == "posrelv")
        {
            switch (rProperty.second.toInt32())
            {
                case 1:
                    rShape.nVertOrientRelation = text::RelOrientation::PAGE_FRAME;
                    break;
                default:
                    break;
            }
        }
        else if (rProperty.first == "groupLeft")
            oGroupLeft.reset(convertTwipToMm100(rProperty.second.toInt32()));
        else if (rProperty.first == "groupTop")
            oGroupTop.reset(convertTwipToMm100(rProperty.second.toInt32()));
        else if (rProperty.first == "groupRight")
            oGroupRight.reset(convertTwipToMm100(rProperty.second.toInt32()));
        else if (rProperty.first == "groupBottom")
            oGroupBottom.reset(convertTwipToMm100(rProperty.second.toInt32()));
        else if (rProperty.first == "relLeft")
            oRelLeft.reset(convertTwipToMm100(rProperty.second.toInt32()));
        else if (rProperty.first == "relTop")
            oRelTop.reset(convertTwipToMm100(rProperty.second.toInt32()));
        else if (rProperty.first == "relRight")
            oRelRight.reset(convertTwipToMm100(rProperty.second.toInt32()));
        else if (rProperty.first == "relBottom")
            oRelBottom.reset(convertTwipToMm100(rProperty.second.toInt32()));
        else if (rProperty.first == "fBehindDocument")
            bOpaque = !rProperty.second.toInt32();
        else if (rProperty.first == "pctHoriz" || rProperty.first == "pctVert")
        {
            sal_Int16 nPercentage = rtl::math::round(rProperty.second.toDouble() / 10);
            if (nPercentage)
            {
                boost::optional<sal_Int16>& rPercentage
                    = rProperty.first == "pctHoriz" ? oRelativeWidth : oRelativeHeight;
                rPercentage = nPercentage;
            }
        }
        else if (rProperty.first == "sizerelh")
        {
            if (xPropertySet.is())
            {
                switch (rProperty.second.toInt32())
                {
                    case 0: // margin
                        nRelativeWidthRelation = text::RelOrientation::FRAME;
                        break;
                    case 1: // page
                        nRelativeWidthRelation = text::RelOrientation::PAGE_FRAME;
                        break;
                    default:
                        SAL_WARN("writerfilter", "RTFSdrImport::resolve: unhandled sizerelh value: "
                                                     << rProperty.second);
                        break;
                }
            }
        }
        else if (rProperty.first == "sizerelv")
        {
            if (xPropertySet.is())
            {
                switch (rProperty.second.toInt32())
                {
                    case 0: // margin
                        nRelativeHeightRelation = text::RelOrientation::FRAME;
                        break;
                    case 1: // page
                        nRelativeHeightRelation = text::RelOrientation::PAGE_FRAME;
                        break;
                    default:
                        SAL_WARN("writerfilter", "RTFSdrImport::resolve: unhandled sizerelv value: "
                                                     << rProperty.second);
                        break;
                }
            }
        }
        else if (rProperty.first == "fHorizRule") // TODO: what does "fStandardHR" do?
        {
            // horizontal rule: relative width defaults to 100% of paragraph
            // TODO: does it have a default height?
            if (!oRelativeWidth)
            {
                oRelativeWidth = 100;
            }
            nRelativeWidthRelation = text::RelOrientation::FRAME;
            sal_Int16 const nVertOrient = text::VertOrientation::CENTER;
            if (xPropertySet.is())
            {
                xPropertySet->setPropertyValue("VertOrient", uno::makeAny(nVertOrient));
            }
        }
        else if (rProperty.first == "pctHR")
        {
            // horizontal rule relative width in permille
            oRelativeWidth = rProperty.second.toInt32() / 10;
        }
        else if (rProperty.first == "dxHeightHR")
        {
            // horizontal rule height
            sal_uInt32 const nHeight(convertTwipToMm100(rProperty.second.toInt32()));
            rShape.nBottom = rShape.nTop + nHeight;
        }
        else if (rProperty.first == "dxWidthHR")
        {
            // horizontal rule width
            sal_uInt32 const nWidth(convertTwipToMm100(rProperty.second.toInt32()));
            rShape.nRight = rShape.nLeft + nWidth;
        }
        else if (rProperty.first == "alignHR")
        {
            // horizontal orientation *for horizontal rule*
            sal_Int16 nHoriOrient = text::HoriOrientation::NONE;
            switch (rProperty.second.toInt32())
            {
                case 0:
                    nHoriOrient = text::HoriOrientation::LEFT;
                    break;
                case 1:
                    nHoriOrient = text::HoriOrientation::CENTER;
                    break;
                case 2:
                    nHoriOrient = text::HoriOrientation::RIGHT;
                    break;
            }
            if (xPropertySet.is() && text::HoriOrientation::NONE != nHoriOrient)
            {
                xPropertySet->setPropertyValue("HoriOrient", uno::makeAny(nHoriOrient));
            }
        }
        else if (rProperty.first == "pWrapPolygonVertices")
        {
            RTFSprms aPolygonSprms;
            sal_Int32 nSize = 0; // Size of a token
            sal_Int32 nCount = 0; // Number of tokens
            sal_Int32 nCharIndex = 0; // Character index
            do
            {
                OUString aToken = rProperty.second.getToken(0, ';', nCharIndex);
                if (!nSize)
                    nSize = aToken.toInt32();
                else if (!nCount)
                    nCount = aToken.toInt32();
                else if (aToken.getLength())
                {
                    // The coordinates are in an (x,y) form.
                    aToken = aToken.copy(1, aToken.getLength() - 2);
                    sal_Int32 nI = 0;
                    boost::optional<sal_Int32> oX;
                    boost::optional<sal_Int32> oY;
                    do
                    {
                        OUString aPoint = aToken.getToken(0, ',', nI);
                        if (!oX)
                            oX.reset(aPoint.toInt32());
                        else
                            oY.reset(aPoint.toInt32());
                    } while (nI >= 0);
                    RTFSprms aPathAttributes;
                    aPathAttributes.set(NS_ooxml::LN_CT_Point2D_x, new RTFValue(*oX));
                    aPathAttributes.set(NS_ooxml::LN_CT_Point2D_y, new RTFValue(*oY));
                    aPolygonSprms.set(NS_ooxml::LN_CT_WrapPath_lineTo,
                                      new RTFValue(aPathAttributes), RTFOverwrite::NO_APPEND);
                }
            } while (nCharIndex >= 0);
            rShape.aWrapPolygonSprms = aPolygonSprms;
        }
        else if (rProperty.first == "fRelFlipV")
            obRelFlipV = rProperty.second.toInt32() == 1;
        else if (rProperty.first == "fFlipH")
            obFlipH = rProperty.second.toInt32() == 1;
        else if (rProperty.first == "fFlipV")
            obFlipV = rProperty.second.toInt32() == 1;
        else
            SAL_INFO("writerfilter", "TODO handle shape property '" << rProperty.first << "':'"
                                                                    << rProperty.second << "'");
    }
 
    if (xPropertySet.is())
    {
        resolveLineColorAndWidth(m_bTextFrame, xPropertySet, aLineColor, aLineWidth);
        if (rShape.oZ)
        {
            bool bOldStyle = m_aParents.size() > 1;
            resolveDhgt(xPropertySet, *rShape.oZ, bOldStyle);
        }
        if (m_bTextFrame)
            // Writer textframes implement text::WritingMode2, which is a different data type.
            xPropertySet->setPropertyValue("WritingMode", uno::makeAny(sal_Int16(eWritingMode)));
        else
            xPropertySet->setPropertyValue("TextWritingMode", uno::makeAny(eWritingMode));
    }
 
    if (!m_aParents.empty() && m_aParents.top().is() && !m_bTextFrame)
        m_aParents.top()->add(xShape);
 
    if (bPib)
    {
        m_rImport.resolvePict(false, xShape);
    }
 
    if (nType == ESCHER_ShpInst_PictureFrame) // picture frame
    {
        assert(!m_bTextFrame);
        if (!bPib) // ??? not sure if the early return should be removed on else?
        {
            m_xShape = xShape; // store it for later resolvePict call
        }
 
        // Handle horizontal flip.
        if (obFlipH == true)
        {
            if (xPropertySet.is())
                xPropertySet->setPropertyValue("IsMirrored", uno::makeAny(true));
        }
        return;
    }
 
    if (bCustom && xShape.is() && !bPib)
    {
        uno::Reference<drawing::XEnhancedCustomShapeDefaulter> xDefaulter(xShape, uno::UNO_QUERY);
        xDefaulter->createCustomShapeDefaults(OUString::number(nType));
    }
 
    // Set shape text
    if (bCustom && !aShapeText.isEmpty())
    {
        uno::Reference<text::XTextRange> xTextRange(xShape, uno::UNO_QUERY);
        if (xTextRange.is())
            xTextRange->setString(aShapeText);
 
        xPropertySet->setPropertyValue("CharFontName", uno::makeAny(aFontFamily));
        xPropertySet->setPropertyValue("CharHeight", uno::makeAny(nFontSize));
    }
 
    // Creating CustomShapeGeometry property
    std::vector<beans::PropertyValue> aGeometry;
    if (aViewBox.X || aViewBox.Y || aViewBox.Width || aViewBox.Height)
    {
        aViewBox.Width -= aViewBox.X;
        aViewBox.Height -= aViewBox.Y;
        aPropertyValue.Name = "ViewBox";
        aPropertyValue.Value <<= aViewBox;
        aGeometry.push_back(aPropertyValue);
    }
    if (!aPath.empty())
    {
        aPropertyValue.Name = "Path";
        aPropertyValue.Value <<= comphelper::containerToSequence(aPath);
        aGeometry.push_back(aPropertyValue);
    }
    if (!aGeometry.empty() && xPropertySet.is() && !m_bTextFrame)
        xPropertySet->setPropertyValue("CustomShapeGeometry",
                                       uno::Any(comphelper::containerToSequence(aGeometry)));
 
    if (!aShapeText.isEmpty())
    {
        auto aGeomPropSeq = xPropertySet->getPropertyValue("CustomShapeGeometry")
                                .get<uno::Sequence<beans::PropertyValue>>();
        auto aGeomPropVec
            = comphelper::sequenceToContainer<std::vector<beans::PropertyValue>>(aGeomPropSeq);
        uno::Sequence<beans::PropertyValue> aPropertyValues(comphelper::InitPropertySequence({
            { "TextPath", uno::makeAny(true) },
        }));
        auto it = std::find_if(
            aGeomPropVec.begin(), aGeomPropVec.end(),
            [](const beans::PropertyValue& rValue) { return rValue.Name == "TextPath"; });
        if (it == aGeomPropVec.end())
            aGeomPropVec.push_back(comphelper::makePropertyValue("TextPath", aPropertyValues));
        else
            it->Value <<= aPropertyValues;
 
        xPropertySet->setPropertyValue("CustomShapeGeometry",
                                       uno::makeAny(comphelper::containerToSequence(aGeomPropVec)));
        xPropertySet->setPropertyValue("TextAutoGrowHeight", uno::makeAny(false));
        xPropertySet->setPropertyValue("TextAutoGrowWidth", uno::makeAny(false));
    }
 
    if (!boost::logic::indeterminate(obRelFlipV) && xPropertySet.is())
    {
        if (nType == ESCHER_ShpInst_Line)
        {
            // Line shape inside group shape: get the polygon sequence and transform it.
            uno::Sequence<uno::Sequence<awt::Point>> aPolyPolySequence;
            if ((xPropertySet->getPropertyValue("PolyPolygon") >>= aPolyPolySequence)
                && aPolyPolySequence.hasElements())
            {
                uno::Sequence<awt::Point>& rPolygon = aPolyPolySequence[0];
                basegfx::B2DPolygon aPoly;
                for (sal_Int32 i = 0; i < rPolygon.getLength(); ++i)
                {
                    const awt::Point& rPoint = rPolygon[i];
                    aPoly.insert(i, basegfx::B2DPoint(rPoint.X, rPoint.Y));
                }
                basegfx::B2DHomMatrix aTransformation;
                aTransformation.scale(1.0, obRelFlipV ? -1.0 : 1.0);
                aPoly.transform(aTransformation);
                for (sal_Int32 i = 0; i < rPolygon.getLength(); ++i)
                {
                    basegfx::B2DPoint aPoint(aPoly.getB2DPoint(i));
                    rPolygon[i]
                        = awt::Point(static_cast<sal_Int32>(convertMm100ToTwip(aPoint.getX())),
                                     static_cast<sal_Int32>(convertMm100ToTwip(aPoint.getY())));
                }
                xPropertySet->setPropertyValue("PolyPolygon", uno::makeAny(aPolyPolySequence));
            }
        }
    }
 
    // Set position and size
    if (xShape.is())
    {
        sal_Int32 nLeft = rShape.nLeft;
        sal_Int32 nTop = rShape.nTop;
 
        bool bInShapeGroup = oGroupLeft && oGroupTop && oGroupRight && oGroupBottom && oRelLeft
                             && oRelTop && oRelRight && oRelBottom;
        awt::Size aSize;
        if (bInShapeGroup)
        {
            // See lclGetAbsPoint() in the VML import: rShape is the group shape, oGroup is its coordinate system, oRel is the relative child shape.
            sal_Int32 nShapeWidth = rShape.nRight - rShape.nLeft;
            sal_Int32 nShapeHeight = rShape.nBottom - rShape.nTop;
            sal_Int32 nCoordSysWidth = *oGroupRight - *oGroupLeft;
            sal_Int32 nCoordSysHeight = *oGroupBottom - *oGroupTop;
            double fWidthRatio = static_cast<double>(nShapeWidth) / nCoordSysWidth;
            double fHeightRatio = static_cast<double>(nShapeHeight) / nCoordSysHeight;
            nLeft = static_cast<sal_Int32>(rShape.nLeft + fWidthRatio * (*oRelLeft - *oGroupLeft));
            nTop = static_cast<sal_Int32>(rShape.nTop + fHeightRatio * (*oRelTop - *oGroupTop));
 
            // See lclGetAbsRect() in the VML import.
            aSize.Width = std::lround(fWidthRatio * (*oRelRight - *oRelLeft));
            aSize.Height = std::lround(fHeightRatio * (*oRelBottom - *oRelTop));
        }
 
        if (m_bTextFrame)
        {
            xPropertySet->setPropertyValue("HoriOrientPosition", uno::makeAny(nLeft));
            xPropertySet->setPropertyValue("VertOrientPosition", uno::makeAny(nTop));
        }
        else
            xShape->setPosition(awt::Point(nLeft, nTop));
 
        if (bInShapeGroup)
            xShape->setSize(aSize);
        else
            xShape->setSize(awt::Size(rShape.nRight - rShape.nLeft, rShape.nBottom - rShape.nTop));
 
        if (obFlipH == true || obFlipV == true)
        {
            // Line shapes have no CustomShapeGeometry.
            if (nType != ESCHER_ShpInst_Line)
            {
                // This has to be set after position and size is set, otherwise flip will affect the position.
                comphelper::SequenceAsHashMap aCustomShapeGeometry(
                    xPropertySet->getPropertyValue("CustomShapeGeometry"));
                if (obFlipH == true)
                    aCustomShapeGeometry["MirroredX"] <<= true;
                if (obFlipV == true)
                    aCustomShapeGeometry["MirroredY"] <<= true;
                xPropertySet->setPropertyValue(
                    "CustomShapeGeometry",
                    uno::makeAny(aCustomShapeGeometry.getAsConstPropertyValueList()));
            }
        }
 
        if (rShape.nHoriOrientRelation != 0)
            xPropertySet->setPropertyValue("HoriOrientRelation",
                                           uno::makeAny(rShape.nHoriOrientRelation));
        if (rShape.nVertOrientRelation != 0)
            xPropertySet->setPropertyValue("VertOrientRelation",
                                           uno::makeAny(rShape.nVertOrientRelation));
        if (rShape.nWrap != text::WrapTextMode::WrapTextMode_MAKE_FIXED_SIZE)
            xPropertySet->setPropertyValue("Surround",
                                           uno::makeAny(text::WrapTextMode(rShape.nWrap)));
        oox::ModelObjectHelper aModelObjectHelper(m_rImport.getModelFactory());
        if (aFillModel.moType.has())
        {
            oox::drawingml::ShapePropertyMap aPropMap(aModelObjectHelper);
            aFillModel.pushToPropMap(aPropMap, m_rImport.getGraphicHelper());
            // Sets the FillStyle and FillGradient UNO properties.
            oox::PropertySet(xShape).setProperties(aPropMap);
        }
 
        if (aShadowModel.mbHasShadow)
        {
            oox::drawingml::ShapePropertyMap aPropMap(aModelObjectHelper);
            aShadowModel.pushToPropMap(aPropMap, m_rImport.getGraphicHelper());
            // Sets the ShadowFormat UNO property.
            oox::PropertySet(xShape).setProperties(aPropMap);
        }
        xPropertySet->setPropertyValue("AnchorType",
                                       uno::makeAny(text::TextContentAnchorType_AT_CHARACTER));
        xPropertySet->setPropertyValue("Opaque", uno::makeAny(bOpaque));
        if (oRelativeWidth)
        {
            xPropertySet->setPropertyValue("RelativeWidth", uno::makeAny(*oRelativeWidth));
            xPropertySet->setPropertyValue("RelativeWidthRelation",
                                           uno::makeAny(nRelativeWidthRelation));
        }
        if (oRelativeHeight)
        {
            xPropertySet->setPropertyValue("RelativeHeight", uno::makeAny(*oRelativeHeight));
            xPropertySet->setPropertyValue("RelativeHeightRelation",
                                           uno::makeAny(nRelativeHeightRelation));
        }
    }
 
    if (m_rImport.isInBackground())
    {
        RTFSprms aAttributes;
        aAttributes.set(NS_ooxml::LN_CT_Background_color,
                        new RTFValue(xPropertySet->getPropertyValue("FillColor").get<sal_Int32>()));
        m_rImport.Mapper().props(new RTFReferenceProperties(aAttributes));
 
        uno::Reference<lang::XComponent> xComponent(xShape, uno::UNO_QUERY);
        xComponent->dispose();
        return;
    }
 
    // Send it to dmapper
    m_rImport.Mapper().startShape(xShape);
    if (bClose)
    {
        m_rImport.Mapper().endShape();
    }
    m_xShape = xShape;
}
 
void RTFSdrImport::close() { m_rImport.Mapper().endShape(); }
 
void RTFSdrImport::append(const OUString& aKey, const OUString& aValue)
{
    applyProperty(m_xShape, aKey, aValue);
}
 
void RTFSdrImport::appendGroupProperty(const OUString& aKey, const OUString& aValue)
{
    if (m_aParents.empty())
        return;
    uno::Reference<drawing::XShape> xShape(m_aParents.top(), uno::UNO_QUERY);
    if (xShape.is())
        applyProperty(xShape, aKey, aValue);
}
 
} // namespace rtftok
} // namespace writerfilter
 
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

V1007 The value from the uninitialized optional 'oRelLeft' is used. Probably it is a mistake.

V1007 The value from the uninitialized optional 'oRelTop' is used. Probably it is a mistake.

V1007 The value from the uninitialized optional 'oGroupLeft' is used. Probably it is a mistake.

V1007 The value from the uninitialized optional 'oRelLeft' is used. Probably it is a mistake.

V1007 The value from the uninitialized optional 'oGroupTop' is used. Probably it is a mistake.

V1007 The value from the uninitialized optional 'oGroupBottom' is used. Probably it is a mistake.

V1007 The value from the uninitialized optional 'oGroupLeft' is used. Probably it is a mistake.

V1007 The value from the uninitialized optional 'oGroupRight' is used. Probably it is a mistake.

V1007 The value from the uninitialized optional 'oY' is used. Probably it is a mistake.

V1007 The value from the uninitialized optional 'oX' is used. Probably it is a mistake.

V1007 The value from the uninitialized optional 'oY' is used. Probably it is a mistake.

V1007 The value from the uninitialized optional 'oX' is used. Probably it is a mistake.

V1007 The value from the uninitialized optional 'oRelBottom' is used. Probably it is a mistake.

V1007 The value from the uninitialized optional 'oRelTop' is used. Probably it is a mistake.

V1007 The value from the uninitialized optional 'oGroupTop' is used. Probably it is a mistake.

V1007 The value from the uninitialized optional 'oRelRight' is used. Probably it is a mistake.

V560 A part of conditional expression is always false: oRelLeft.

V560 A part of conditional expression is always false: oGroupBottom.

V560 A part of conditional expression is always false: oGroupRight.

V560 A part of conditional expression is always false: oGroupTop.

V560 A part of conditional expression is always false: oGroupLeft.

V547 Expression '!oX' is always true.

V547 Expression '!oX' is always true.

V560 A part of conditional expression is always false: oRelRight.

V560 A part of conditional expression is always false: oRelBottom.

V547 Expression 'bInShapeGroup' is always false.

V547 Expression 'bInShapeGroup' is always false.

V560 A part of conditional expression is always false: oRelTop.