tdf#51195, tdf#100348 Convert Fontwork to TextWarp on export

LibreOffice has Fontwork as property text-path in enhanced-custom-
geometry. OOXML has the similar TextWarp as property of a textbox. The
patch converts the custom shape to a textbox and sets the attribute
prstTxWarp. Fill and outline of the Fontwork is lost. The import and
export of fill and outline is tracked in tdf#119221 and still needs
to be fixed.

Change-Id: I8ea7b305d7d0a8367d61c1789f22b56d274a311d
Reviewed-on: https://gerrit.libreoffice.org/74057
Tested-by: Jenkins
Reviewed-by: Thorsten Behrens <Thorsten.Behrens@CIB.de>
Reviewed-by: Regina Henschel <rb.henschel@t-online.de>
diff --git a/oox/inc/drawingml/presetgeometrynames.hxx b/oox/inc/drawingml/presetgeometrynames.hxx
index 51721e4..358fc9a 100644
--- a/oox/inc/drawingml/presetgeometrynames.hxx
+++ b/oox/inc/drawingml/presetgeometrynames.hxx
@@ -16,6 +16,7 @@
namespace PresetGeometryTypeNames
{
OOX_DLLPUBLIC OUString GetFontworkType(const OUString& rMsoType);
OOX_DLLPUBLIC OUString GetMsoName(const OUString& rFontworkType);
}

#endif
diff --git a/oox/source/drawingml/presetgeometrynames.cxx b/oox/source/drawingml/presetgeometrynames.cxx
index 272094d..dea972d 100644
--- a/oox/source/drawingml/presetgeometrynames.cxx
+++ b/oox/source/drawingml/presetgeometrynames.cxx
@@ -91,4 +91,25 @@ OUString PresetGeometryTypeNames::GetFontworkType(const OUString& rMsoType)
    return OUString(pRetValue, strlen(pRetValue), RTL_TEXTENCODING_ASCII_US);
}

OUString PresetGeometryTypeNames::GetMsoName(const OUString& rFontworkType)
{
    static const PresetGeometryHashMap s_HashMapInv = []() {
        PresetGeometryHashMap aHInv;
        for (const auto& item : pPresetGeometryNameArray)
            aHInv[item.pFontworkType] = item.pMsoName;
        return aHInv;
    }();
    const char* pRetValue = "";
    int i, nLen = rFontworkType.getLength();
    std::unique_ptr<char[]> pBuf(new char[nLen + 1]);
    for (i = 0; i < nLen; i++)
        pBuf[i] = static_cast<char>(rFontworkType[i]);
    pBuf[i] = 0;
    PresetGeometryHashMap::const_iterator aHashIter(s_HashMapInv.find(pBuf.get()));
    if (aHashIter != s_HashMapInv.end())
        pRetValue = (*aHashIter).second;

    return OUString(pRetValue, strlen(pRetValue), RTL_TEXTENCODING_ASCII_US);
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/oox/source/export/drawingml.cxx b/oox/source/export/drawingml.cxx
index c87cb3a..575878a 100644
--- a/oox/source/export/drawingml.cxx
+++ b/oox/source/export/drawingml.cxx
@@ -2544,6 +2544,7 @@ void DrawingML::WriteText( const Reference< XInterface >& rXIface, const OUStrin
        return;

    sal_Int32 nTextRotateAngle = 0;
    bool bIsFontworkShape(presetWarp.startsWith("text") && (presetWarp != "textNoShape"));

#define DEFLRINS 254
#define DEFTBINS 127
@@ -2582,6 +2583,8 @@ void DrawingML::WriteText( const Reference< XInterface >& rXIface, const OUStrin
        }
    }

    Sequence<drawing::EnhancedCustomShapeAdjustmentValue>aAdjustmentSeq;

    if (GetProperty(rXPropSet, "CustomShapeGeometry"))
    {
        Sequence< PropertyValue > aProps;
@@ -2601,8 +2604,11 @@ void DrawingML::WriteText( const Reference< XInterface >& rXIface, const OUStrin
                        sWritingMode = "vert270";
                        bVertical = true;
                    }
                    break;
                    if (!bIsFontworkShape)
                        break;
                }
                else if (aProps[i].Name == "AdjustmentValues")
                    aProps[i].Value >>= aAdjustmentSeq;
            }
        }
    }
@@ -2647,10 +2653,60 @@ void DrawingML::WriteText( const Reference< XInterface >& rXIface, const OUStrin
                               XML_anchorCtr, bHorizontalCenter ? "1" : nullptr,
                               XML_vert, sWritingMode,
                               XML_rot, (nTextRotateAngle != 0) ? oox::drawingml::calcRotationValue( nTextRotateAngle * 100 ).getStr() : nullptr );
        if( !presetWarp.isEmpty())
        if (bIsFontworkShape)
        {
            mpFS->singleElementNS(XML_a, XML_prstTxWarp, XML_prst, presetWarp.toUtf8());
            if (aAdjustmentSeq.getLength() > 0)
            {
                mpFS->startElementNS(XML_a, XML_prstTxWarp, XML_prst, presetWarp.toUtf8());
                mpFS->startElementNS(XML_a, XML_avLst);
                for (sal_Int32 i = 0, nElems = aAdjustmentSeq.getLength(); i < nElems; ++i )
                {
                    OString sName = OString("adj") + (( nElems > 1 ) ? OString::number(i + 1) : OString());
                    double fValue(0.0);
                    if (aAdjustmentSeq[i].Value.getValueTypeClass() == TypeClass_DOUBLE)
                        aAdjustmentSeq[i].Value >>= fValue;
                    else
                    {
                        sal_Int32 nNumber(0);
                        aAdjustmentSeq[i].Value >>= nNumber;
                        fValue = static_cast<double>(nNumber);
                    }
                    // Convert from binary coordinate system with viewBox "0 0 21600 21600" and simple degree
                    // to DrawingML with coordinate range 0..100000 and angle in 1/60000 degree.
                    // Reverse to conversion in lcl_createPresetShape in drawingml/shape.cxx on import.
                    if (presetWarp == "textArchDown" || presetWarp == "textArchUp"
                        || presetWarp == "textButton" || presetWarp == "textCircle"
                        || ((i == 0) && (presetWarp == "textArchDownPour" || presetWarp == "textArchUpPour"
                        || presetWarp == "textButtonPour" || presetWarp == "textCirclePour")))
                    {
                        fValue *= 60000.0;
                    }
                    else if ((i == 1) && (presetWarp == "textDoubleWave1" || presetWarp == "textWave1"
                            || presetWarp == "textWave2" || presetWarp == "textWave4"))
                    {
                        fValue = fValue / 0.216 - 50000.0;
                    }
                    else if ((i == 1) && (presetWarp == "textArchDownPour" || presetWarp == "textArchUpPour"
                        || presetWarp == "textButtonPour" || presetWarp == "textCirclePour"))
                    {
                        fValue /= 0.108;
                    }
                    else
                    {
                        fValue /= 0.216;
                    }
                    OString sFmla = OString("val ") + OString::number(std::lround(fValue));
                    mpFS->singleElementNS(XML_a, XML_gd, XML_name, sName, XML_fmla, sFmla);
                }
                mpFS->endElementNS( XML_a, XML_avLst );
                mpFS->endElementNS(XML_a, XML_prstTxWarp);
            }
            else
            {
                mpFS->singleElementNS(XML_a, XML_prstTxWarp, XML_prst, presetWarp.toUtf8());
            }
        }

        if (GetDocumentType() == DOCUMENT_DOCX || GetDocumentType() == DOCUMENT_XLSX)
        {
            bool bTextAutoGrowHeight = false;
diff --git a/oox/source/export/shapes.cxx b/oox/source/export/shapes.cxx
index d441406..14e5b84 100644
--- a/oox/source/export/shapes.cxx
+++ b/oox/source/export/shapes.cxx
@@ -101,6 +101,7 @@
#include <svx/unoapi.hxx>
#include <oox/export/chartexport.hxx>
#include <oox/mathml/export.hxx>
#include <drawingml/presetgeometrynames.hxx>

using namespace ::css;
using namespace ::css::beans;
@@ -698,13 +699,56 @@ static sal_Int32 lcl_NormalizeAngle( sal_Int32 nAngle )

ShapeExport& ShapeExport::WriteCustomShape( const Reference< XShape >& xShape )
{
    // First check, if this is a Fontwork-shape. For DrawingML, such a shape is a
    // TextBox shape with body property prstTxWarp.
    SAL_INFO("oox.shape", "write custom shape");

    Reference< XPropertySet > rXPropSet( xShape, UNO_QUERY );
    bool bIsFontworkShape(false);
    bool bHasGeometrySeq(false);
    Sequence< PropertyValue > aGeometrySeq;
    OUString sShapeType;
    if (GETA(CustomShapeGeometry))
    {
        SAL_INFO("oox.shape", "got custom shape geometry");
        if (mAny >>= aGeometrySeq)
        {
            bHasGeometrySeq = true;
            SAL_INFO("oox.shape", "got custom shape geometry sequence");
            for (int i = 0; i < aGeometrySeq.getLength(); i++)
            {
                const PropertyValue& rProp = aGeometrySeq[i];
                SAL_INFO("oox.shape", "geometry property: " << rProp.Name);
                if (rProp.Name == "TextPath")
                {
                    uno::Sequence<beans::PropertyValue> aTextPathSeq;
                    rProp.Value >>= aTextPathSeq;
                    for (int k = 0; k < aTextPathSeq.getLength(); k++)
                    {
                        const PropertyValue& rTextProp = aTextPathSeq[k];
                        if (rTextProp.Name == "TextPath")
                        {
                            rTextProp.Value >>= bIsFontworkShape;
                        }
                    }
                }
                else if (rProp.Name == "Type")
                    rProp.Value >>= sShapeType;
            }
        }
    }
    if (bIsFontworkShape)
    {
        // write the correct type to m_presetWarp, WriteTextShape() needs it
        // to set TextWarp.
        m_presetWarp = PresetGeometryTypeNames::GetMsoName(sShapeType);
        ShapeExport::WriteTextShape(xShape); // qualifier to prevent PowerPointShapeExport
        return *this;
    }


    bool bPredefinedHandlesUsed = true;
    bool bHasHandles = false;

    OUString sShapeType;
    ShapeFlag nMirrorFlags = ShapeFlag::NONE;
    MSO_SPT eShapeType = EscherPropertyContainer::GetCustomShapeType( xShape, nMirrorFlags, sShapeType );
    OSL_ENSURE(nullptr != dynamic_cast< SdrObjCustomShape* >(GetSdrObjectFromXShape(xShape)), "Not a SdrObjCustomShape (!)");
@@ -715,7 +759,7 @@ ShapeExport& ShapeExport::WriteCustomShape( const Reference< XShape >& xShape )
            eShapeType));
    const char* sPresetShape = msfilter::util::GetOOXMLPresetGeometry(sShapeType.toUtf8().getStr());
    SAL_INFO("oox.shape", "custom shape type: " << sShapeType << " ==> " << sPresetShape);
    Sequence< PropertyValue > aGeometrySeq;

    sal_Int32 nAdjustmentValuesIndex = -1;
    awt::Rectangle aViewBox;
    uno::Sequence<beans::PropertyValues> aHandles;
@@ -726,12 +770,10 @@ ShapeExport& ShapeExport::WriteCustomShape( const Reference< XShape >& xShape )
    // Avoid interference of preset type to the next shape
    m_presetWarp = "";

    if( GETA( CustomShapeGeometry ) ) {
        SAL_INFO("oox.shape", "got custom shape geometry");
        if( mAny >>= aGeometrySeq ) {

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

@@ -757,7 +799,6 @@ ShapeExport& ShapeExport::WriteCustomShape( const Reference< XShape >& xShape )
                }
                else if ( rProp.Name == "ViewBox" )
                    rProp.Value >>= aViewBox;
            }
        }
    }

@@ -986,9 +1027,7 @@ ShapeExport& ShapeExport::WriteCustomShape( const Reference< XShape >& xShape )
    }
    if( rXPropSet.is() )
    {
        // Preset shape with text has no fill
        if( m_presetWarp.isEmpty() || !m_presetWarp.startsWith( "text" ) || m_presetWarp == "textNoShape" )
            WriteFill( rXPropSet );
        WriteFill( rXPropSet );
        WriteOutline( rXPropSet );
        WriteShapeEffects( rXPropSet );
        WriteShape3DEffects( rXPropSet );
@@ -1793,6 +1832,7 @@ ShapeExport& ShapeExport::WriteTableShape( const Reference< XShape >& xShape )

ShapeExport& ShapeExport::WriteTextShape( const Reference< XShape >& xShape )
{
    bool bIsFontworkShape(m_presetWarp.startsWith("text") && m_presetWarp != "textNoShape");
    FSHelperPtr pFS = GetFS();
    Reference<XPropertySet> xShapeProps(xShape, UNO_QUERY);
    pFS->startElementNS(mnXmlNamespace, (GetDocumentType() != DOCUMENT_DOCX ? XML_sp : XML_wsp));
@@ -1831,8 +1871,11 @@ ShapeExport& ShapeExport::WriteTextShape( const Reference< XShape >& xShape )
    WriteShapeTransformation( xShape, XML_a );
    WritePresetShape( "rect" );
    uno::Reference<beans::XPropertySet> xPropertySet(xShape, UNO_QUERY);
    WriteBlipOrNormalFill(xPropertySet, "Graphic");
    WriteOutline(xPropertySet);
    if (!bIsFontworkShape) // Fontwork needs fill and outline on char instead.
    {
        WriteBlipOrNormalFill(xPropertySet, "Graphic");
        WriteOutline(xPropertySet);
    }
    WriteShapeEffects(xPropertySet);
    pFS->endElementNS( mnXmlNamespace, XML_spPr );