bnc#637947 improve DrawingML export of custom shapes

Change-Id: Iaa880528cf3c899ce66e4349c6d989dfbe5cbeb6
Reviewed-on: https://gerrit.libreoffice.org/14346
Reviewed-by: Miklos Vajna <vmiklos@collabora.co.uk>
Tested-by: Miklos Vajna <vmiklos@collabora.co.uk>
diff --git a/filter/source/msfilter/util.cxx b/filter/source/msfilter/util.cxx
index 686eee4..958f0b8 100644
--- a/filter/source/msfilter/util.cxx
+++ b/filter/source/msfilter/util.cxx
@@ -701,6 +701,7 @@ struct CustomShapeTypeTranslationTable
static const CustomShapeTypeTranslationTable pCustomShapeTypeTranslationTable[] =
{
    // { "non-primitive", mso_sptMin },
    { "frame", "frame" },
    { "rectangle", "rect" },
    { "round-rectangle", "roundRect" },
    { "ellipse", "ellipse" },
diff --git a/oox/source/export/shapes.cxx b/oox/source/export/shapes.cxx
index a41ea2b..a96831e 100644
--- a/oox/source/export/shapes.cxx
+++ b/oox/source/export/shapes.cxx
@@ -279,12 +279,87 @@ ShapeExport& ShapeExport::WriteGroupShape(uno::Reference<drawing::XShape> xShape
    return *this;
}

static bool lcl_IsOnBlacklist(OUString& rShapeType)
{
    OUString aBlacklist[] = {
            "ring",
            "can",
            "cube",
            "paper",
            "frame",
            "smiley",
            "sun",
            "flower",
            "forbidden",
            "bracket-pair",
            "brace-pair",
            "col-60da8460",
            "col-502ad400",
            "quad-bevel",
            "cloud-callout",
            "line-callout-1",
            "line-callout-2",
            "line-callout-3",
            "paper",
            "vertical-scroll",
            "horizontal-scroll",
            "mso-spt34",
            "mso-spt75",
            "mso-spt164",
            "mso-spt180",
            "flowchart-process",
            "flowchart-alternate-process",
            "flowchart-decision",
            "flowchart-data",
            "flowchart-predefined-process",
            "flowchart-internal-storage",
            "flowchart-document",
            "flowchart-multidocument",
            "flowchart-terminator",
            "flowchart-preparation",
            "flowchart-manual-input",
            "flowchart-manual-operation",
            "flowchart-connector",
            "flowchart-off-page-connector",
            "flowchart-card",
            "flowchart-punched-tape",
            "flowchart-summing-junction",
            "flowchart-or",
            "flowchart-collate",
            "flowchart-sort",
            "flowchart-extract",
            "flowchart-merge",
            "flowchart-stored-data",
            "flowchart-delay",
            "flowchart-sequential-access",
            "flowchart-magnetic-disk",
            "flowchart-direct-access-storage",
            "flowchart-display"
    };
    std::vector<OUString> vBlacklist(aBlacklist, aBlacklist + SAL_N_ELEMENTS(aBlacklist));

    return std::find(vBlacklist.begin(), vBlacklist.end(), rShapeType) != vBlacklist.end();
}

static bool lcl_IsOnWhitelist(OUString& rShapeType)
{
    OUString aWhitelist[] = {
            "heart",
            "puzzle"
    };
    std::vector<OUString> vWhitelist(aWhitelist, aWhitelist + SAL_N_ELEMENTS(aWhitelist));

    return std::find(vWhitelist.begin(), vWhitelist.end(), rShapeType) != vWhitelist.end();
}


ShapeExport& ShapeExport::WriteCustomShape( Reference< XShape > xShape )
{
    DBG(fprintf(stderr, "write custom shape\n"));

    Reference< XPropertySet > rXPropSet( xShape, UNO_QUERY );
    bool bPredefinedHandlesUsed = true;
    bool bHasHandles = false;
    OUString sShapeType;
    sal_uInt32 nMirrorFlags = 0;
    MSO_SPT eShapeType = EscherPropertyContainer::GetCustomShapeType( xShape, nMirrorFlags, sShapeType );
@@ -315,6 +390,7 @@ ShapeExport& ShapeExport::WriteCustomShape( Reference< XShape > xShape )
                if ( rProp.Name == "AdjustmentValues" )
                    nAdjustmentValuesIndex = i;
                else if ( rProp.Name == "Handles" ) {
                    bHasHandles = true;
                    if( !bIsDefaultObject )
                        bPredefinedHandlesUsed = false;
                    // TODO: update nAdjustmentsWhichNeedsToBeConverted here
@@ -347,11 +423,40 @@ ShapeExport& ShapeExport::WriteCustomShape( Reference< XShape > xShape )

    // visual shape properties
    pFS->startElementNS( mnXmlNamespace, XML_spPr, FSEND );
    WriteShapeTransformation( xShape, XML_a, bFlipH, bFlipV, false);
    // moon is flipped in MSO, and mso-spt89 (right up arrow) is mapped to leftUpArrow
    if ( sShapeType == "moon" || sShapeType == "mso-spt89" )
        WriteShapeTransformation( xShape, XML_a, !bFlipH, bFlipV, false);
    else
        WriteShapeTransformation( xShape, XML_a, bFlipH, bFlipV, false);

    if( sShapeType == "ooxml-non-primitive" ) // non-primitiv -> custom geometry
    // we export non-primitive shapes to custom geometry
    // we also export non-ooxml shapes which have handles/equations to custom geometry, because
    // we cannot convert ODF equations to DrawingML equations. TODO: see what binary DOC export filter does.
    // but our WritePolyPolygon() function is incomplete, therefore we use a blacklist
    // we use a whitelist for shapes where mapping to MSO preset shape is not optimal
    bool bCustGeom = true;
    if( sShapeType == "ooxml-non-primitive" )
        bCustGeom = true;
    else if( sShapeType.startsWith("ooxml") )
        bCustGeom = false;
    else if( lcl_IsOnWhitelist(sShapeType) )
        bCustGeom = true;
    else if( lcl_IsOnBlacklist(sShapeType) )
        bCustGeom = false;
    else if( bHasHandles )
        bCustGeom = true;

    if( bCustGeom )
    {
        WritePolyPolygon( EscherPropertyContainer::GetPolyPolygon( xShape ) );
        basegfx::B2DPolyPolygon aB2DPolyPolygon = pShape->GetLineGeometry(true);
        tools::PolyPolygon aPolyPolygon;
        for( sal_uInt32 i = 0; i < aB2DPolyPolygon.count(); ++i )
        {
            basegfx::B2DPolygon aB2DPolygon = aB2DPolyPolygon.getB2DPolygon(i);
            aPolyPolygon.Insert( Polygon( aB2DPolygon ), POLYPOLY_APPEND );
        }

        WritePolyPolygon( aPolyPolygon );
    }
    else // preset geometry
    {
diff --git a/sw/qa/extras/ooxmlexport/data/dml-customgeometry-cubicbezier.docx b/sw/qa/extras/ooxmlexport/data/dml-customgeometry-cubicbezier.docx
index baa47f4..9cc84a9 100644
--- a/sw/qa/extras/ooxmlexport/data/dml-customgeometry-cubicbezier.docx
+++ b/sw/qa/extras/ooxmlexport/data/dml-customgeometry-cubicbezier.docx
Binary files differ
diff --git a/sw/qa/extras/ooxmlexport/ooxmlexport4.cxx b/sw/qa/extras/ooxmlexport/ooxmlexport4.cxx
index e3043e6..21140f3 100644
--- a/sw/qa/extras/ooxmlexport/ooxmlexport4.cxx
+++ b/sw/qa/extras/ooxmlexport/ooxmlexport4.cxx
@@ -708,6 +708,10 @@ DECLARE_OOXMLEXPORT_TEST(testSdtContent, "SdtContent.docx")
    assertXPath(pXmlDoc, "/w:hdr[1]/w:sdt[1]/w:sdtContent[1]/w:p[1]/w:del[1]");
}

#if 0
// Currently LibreOffice exports custom geometry for this up arrow, not preset shape.
// When LibreOffice can export preset shape with correct modifiers, then this test can be re-enabled.

DECLARE_OOXMLEXPORT_TEST(testFdo76016, "fdo76016.docx")
{
    // check XML
@@ -717,6 +721,7 @@ DECLARE_OOXMLEXPORT_TEST(testFdo76016, "fdo76016.docx")
    assertXPath(pXmlDoc, "//a:graphic/a:graphicData/wps:wsp/wps:spPr/a:prstGeom/a:avLst/a:gd[1]", "name", "adj1");
    assertXPath(pXmlDoc, "//a:graphic/a:graphicData/wps:wsp/wps:spPr/a:prstGeom/a:avLst/a:gd[2]", "name", "adj2");
}
#endif

DECLARE_OOXMLEXPORT_TEST(testFileWithInvalidImageLink, "FileWithInvalidImageLink.docx")
{
diff --git a/sw/qa/extras/ooxmlexport/ooxmlsdrexport.cxx b/sw/qa/extras/ooxmlexport/ooxmlsdrexport.cxx
index 8e6fa93..dc763f8 100644
--- a/sw/qa/extras/ooxmlexport/ooxmlsdrexport.cxx
+++ b/sw/qa/extras/ooxmlexport/ooxmlsdrexport.cxx
@@ -161,28 +161,28 @@ DECLARE_OOXMLEXPORT_TEST(testDMLCustomGeometry, "dml-customgeometry-cubicbezier.
    CPPUNIT_ASSERT_EQUAL(nLength, aPairs.getLength());
    std::pair<sal_Int32,sal_Int32> aCoordinates[] =
    {
        std::pair<sal_Int32,sal_Int32>(607, 0),
        std::pair<sal_Int32,sal_Int32>(450, 44),
        std::pair<sal_Int32,sal_Int32>(300, 57),
        std::pair<sal_Int32,sal_Int32>(176, 57),
        std::pair<sal_Int32,sal_Int32>(109, 57),
        std::pair<sal_Int32,sal_Int32>(49, 53),
        std::pair<sal_Int32,sal_Int32>(0, 48),
        std::pair<sal_Int32,sal_Int32>(66, 58),
        std::pair<sal_Int32,sal_Int32>(152, 66),
        std::pair<sal_Int32,sal_Int32>(251, 66),
        std::pair<sal_Int32,sal_Int32>(358, 66),
        std::pair<sal_Int32,sal_Int32>(480, 56),
        std::pair<sal_Int32,sal_Int32>(607, 27),
        std::pair<sal_Int32,sal_Int32>(607, 0),
        std::pair<sal_Int32,sal_Int32>(607, 0),
        std::pair<sal_Int32,sal_Int32>(607, 0)
        std::pair<sal_Int32,sal_Int32>(9084, 0),
        std::pair<sal_Int32,sal_Int32>(6734, 689),
        std::pair<sal_Int32,sal_Int32>(4489, 893),
        std::pair<sal_Int32,sal_Int32>(2633, 893),
        std::pair<sal_Int32,sal_Int32>(1631, 893),
        std::pair<sal_Int32,sal_Int32>(733, 830),
        std::pair<sal_Int32,sal_Int32>(0, 752),
        std::pair<sal_Int32,sal_Int32>(987, 908),
        std::pair<sal_Int32,sal_Int32>(2274, 1034),
        std::pair<sal_Int32,sal_Int32>(3756, 1034),
        std::pair<sal_Int32,sal_Int32>(5357, 1034),
        std::pair<sal_Int32,sal_Int32>(7183, 877),
        std::pair<sal_Int32,sal_Int32>(9084, 423),
        std::pair<sal_Int32,sal_Int32>(9084, 0),
        std::pair<sal_Int32,sal_Int32>(9084, 0),
        std::pair<sal_Int32,sal_Int32>(9084, 0)
    };

    for( int i = 0; i < nLength; ++i )
    {
        CPPUNIT_ASSERT_EQUAL(aCoordinates[i].first, aPairs[i].First.Value.get<sal_Int32>());
        CPPUNIT_ASSERT_EQUAL(aCoordinates[i].second, aPairs[i].Second.Value.get<sal_Int32>());
        CPPUNIT_ASSERT(abs(aCoordinates[i].first - aPairs[i].First.Value.get<sal_Int32>()) < 20);
        CPPUNIT_ASSERT(abs(aCoordinates[i].second - aPairs[i].Second.Value.get<sal_Int32>()) < 20);
    }
}

@@ -1443,6 +1443,10 @@ DECLARE_OOXMLEXPORT_TEST(testNestedAlternateContent, "nestedAlternateContent.doc
    assertXPath(pXmlDoc,"/w:document[1]/w:body[1]/w:p[1]/w:r[1]/mc:AlternateContent[1]/mc:Choice[1]/w:drawing[1]/wp:anchor[1]/a:graphic[1]/a:graphicData[1]/wpg:wgp[1]/wps:wsp[2]/wps:txbx[1]/w:txbxContent[1]/w:p[1]/w:r[2]/mc:AlternateContent[1]",0);
}

#if 0
// Currently LibreOffice exports custom geometry for this hexagon, not preset shape.
// When LibreOffice can export preset shapes with correct modifiers, then this test can be re-enabled.

DECLARE_OOXMLEXPORT_TEST(test76317, "test76317.docx")
{
    xmlDocPtr pXmlDoc = parseExport("word/document.xml");
@@ -1450,6 +1454,8 @@ DECLARE_OOXMLEXPORT_TEST(test76317, "test76317.docx")
    assertXPath(pXmlDoc, "/w:document[1]/w:body[1]/w:p[1]/w:r[1]/mc:AlternateContent[1]/mc:Choice[1]/w:drawing[1]/wp:anchor[1]/a:graphic[1]/a:graphicData[1]/wps:wsp[1]/wps:spPr[1]/a:prstGeom[1]", "prst", "hexagon");
}

#endif

DECLARE_OOXMLEXPORT_TEST(fdo76591, "fdo76591.docx")
{
    xmlDocPtr pXmlDoc = parseExport("word/document.xml");