tdf#143876 EMF+ Add DrawClosedCurve and FillClosedCurve support

With this commit EmfPlusDrawClosedCurve and EmfPlusFillClosedCurve
support was added. There is still missing Filling Mode (it
is always set to Even Odd Alternate:
https://en.wikipedia.org/wiki/Even%E2%80%93odd_rule )
and Tension support for spline bends.
The graphics is displayed as Tension=0.
A value of Tension=0 specifies that the spline is a sequence of straight lines.
As the value increases, the curve becomes more rounded.
For more information, see [SPLINE77] and [PETZOLD].

Change-Id: Ibccfd584e3d55cd0ca8a29da9f450916d56705d5
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/134333
Tested-by: Jenkins
Reviewed-by: Bartosz Kosiorek <gang65@poczta.onet.pl>
diff --git a/drawinglayer/source/tools/emfphelperdata.cxx b/drawinglayer/source/tools/emfphelperdata.cxx
index 0734dad..231e86e 100644
--- a/drawinglayer/source/tools/emfphelperdata.cxx
+++ b/drawinglayer/source/tools/emfphelperdata.cxx
@@ -80,6 +80,7 @@ namespace emfplushelper
            case EmfPlusRecordTypeDrawRects: return "EmfPlusRecordTypeDrawRects";
            case EmfPlusRecordTypeFillPolygon: return "EmfPlusRecordTypeFillPolygon";
            case EmfPlusRecordTypeDrawLines: return "EmfPlusRecordTypeDrawLines";
            case EmfPlusRecordTypeFillClosedCurve: return "EmfPlusRecordTypeFillClosedCurve";
            case EmfPlusRecordTypeFillEllipse: return "EmfPlusRecordTypeFillEllipse";
            case EmfPlusRecordTypeDrawEllipse: return "EmfPlusRecordTypeDrawEllipse";
            case EmfPlusRecordTypeFillPie: return "EmfPlusRecordTypeFillPie";
@@ -89,6 +90,7 @@ namespace emfplushelper
            case EmfPlusRecordTypeFillPath: return "EmfPlusRecordTypeFillPath";
            case EmfPlusRecordTypeDrawPath: return "EmfPlusRecordTypeDrawPath";
            case EmfPlusRecordTypeDrawBeziers: return "EmfPlusRecordTypeDrawBeziers";
            case EmfPlusRecordTypeDrawClosedCurve: return "EmfPlusRecordTypeDrawClosedCurve";
            case EmfPlusRecordTypeDrawImage: return "EmfPlusRecordTypeDrawImage";
            case EmfPlusRecordTypeDrawImagePoints: return "EmfPlusRecordTypeDrawImagePoints";
            case EmfPlusRecordTypeDrawString: return "EmfPlusRecordTypeDrawString";
@@ -610,8 +612,11 @@ namespace emfplushelper
        if (pen->GetColor().IsTransparent())
        {
            drawinglayer::primitive2d::Primitive2DContainer aContainer;
            if ((pen->penDataFlags & EmfPlusPenDataStartCap)
                || (pen->penDataFlags & EmfPlusPenDataEndCap))
            if (aStart.isDefault() && aEnd.isDefault())
                aContainer.append(drawinglayer::primitive2d::Primitive2DReference(
                    new drawinglayer::primitive2d::PolyPolygonStrokePrimitive2D(
                        polygon, lineAttribute, pen->GetStrokeAttribute(mdExtractedXScale))));
            else
            {
                aContainer.resize(polygon.count());
                for (sal_uInt32 i = 0; i < polygon.count(); i++)
@@ -620,18 +625,17 @@ namespace emfplushelper
                            polygon.getB2DPolygon(i), lineAttribute,
                            pen->GetStrokeAttribute(mdExtractedXScale), aStart, aEnd));
            }
            else
                aContainer.append(drawinglayer::primitive2d::Primitive2DReference(
                    new drawinglayer::primitive2d::PolyPolygonStrokePrimitive2D(
                        polygon, lineAttribute, pen->GetStrokeAttribute(mdExtractedXScale))));
            mrTargetHolders.Current().append(
                new drawinglayer::primitive2d::UnifiedTransparencePrimitive2D(
                    std::move(aContainer), (255 - pen->GetColor().GetAlpha()) / 255.0));
        }
        else
        {
            if ((pen->penDataFlags & EmfPlusPenDataStartCap)
                || (pen->penDataFlags & EmfPlusPenDataEndCap))
            if (aStart.isDefault() && aEnd.isDefault())
                mrTargetHolders.Current().append(
                    new drawinglayer::primitive2d::PolyPolygonStrokePrimitive2D(
                        polygon, lineAttribute, pen->GetStrokeAttribute(mdExtractedXScale)));
            else
                for (sal_uInt32 i = 0; i < polygon.count(); i++)
                {
                    mrTargetHolders.Current().append(
@@ -639,10 +643,6 @@ namespace emfplushelper
                            polygon.getB2DPolygon(i), lineAttribute,
                            pen->GetStrokeAttribute(mdExtractedXScale), aStart, aEnd));
                }
            else
                mrTargetHolders.Current().append(
                    new drawinglayer::primitive2d::PolyPolygonStrokePrimitive2D(
                        polygon, lineAttribute, pen->GetStrokeAttribute(mdExtractedXScale)));
        }

        if ((pen->penDataFlags & EmfPlusPenDataCustomStartCap) && (pen->customStartCap->polygon.begin()->count() > 1))
@@ -1393,26 +1393,24 @@ namespace emfplushelper
                    }
                    case EmfPlusRecordTypeFillPolygon:
                    {
                        const sal_uInt8 index = flags & 0xff;
                        sal_uInt32 brushIndexOrColor, points;

                        rMS.ReadUInt32(brushIndexOrColor);
                        rMS.ReadUInt32(points);
                        SAL_INFO("drawinglayer.emf", "EMF+\t FillPolygon in slot: " << index << " points: " << points);
                        SAL_INFO("drawinglayer.emf", "EMF+\t Points: " << points);
                        SAL_INFO("drawinglayer.emf", "EMF+\t " << ((flags & 0x8000) ? "Color" : "Brush index") << " : 0x" << std::hex << brushIndexOrColor << std::dec);

                        EMFPPath path(points, true);
                        path.Read(rMS, flags);

                        EMFPPlusFillPolygon(path.GetPolygon(*this), flags & 0x8000, brushIndexOrColor);

                        break;
                    }
                    case EmfPlusRecordTypeDrawLines:
                    {
                        sal_uInt32 points;
                        rMS.ReadUInt32(points);
                        SAL_INFO("drawinglayer.emf", "EMF+\t DrawLines in slot: " << (flags & 0xff) << " points: " << points);
                        SAL_INFO("drawinglayer.emf", "EMF+\t Points: " << points);
                        EMFPPath path(points, true);
                        path.Read(rMS, flags);

@@ -1482,6 +1480,40 @@ namespace emfplushelper
                        }
                        break;
                    }
                    case EmfPlusRecordTypeDrawClosedCurve:
                    case EmfPlusRecordTypeFillClosedCurve:
                    {
                        // Silent MSVC warning C4701: potentially uninitialized local variable 'brushIndexOrColor' used
                        sal_uInt32 brushIndexOrColor = 999, points;
                        float aTension;
                        if (type == EmfPlusRecordTypeFillClosedCurve)
                        {
                            rMS.ReadUInt32(brushIndexOrColor);
                            SAL_INFO("drawinglayer.emf",
                                "EMF+\t Fill Mode: " << (flags & 0x2000 ? "Winding" : "Alternate"));
                        }
                        rMS.ReadFloat(aTension);
                        rMS.ReadUInt32(points);
                        SAL_WARN("drawinglayer.emf",
                                 "EMF+\t Tension: " << aTension << " Points: " << points);
                        SAL_WARN_IF(aTension != 0, "drawinglayer.emf",
                                    "EMF+\t TODO Add support for tension different than 0");
                        SAL_INFO("drawinglayer.emf",
                                 "EMF+\t " << (flags & 0x8000 ? "Color" : "Brush index") << " : 0x"
                                           << std::hex << brushIndexOrColor << std::dec);

                        EMFPPath path(points, true);
                        path.Read(rMS, flags);
                        if (type == EmfPlusRecordTypeFillClosedCurve)
                            EMFPPlusFillPolygon(path.GetPolygon(*this, /* bMapIt */ true,
                                                                /*bAddLineToCloseShape */ true),
                                                flags & 0x8000, brushIndexOrColor);
                        else
                            EMFPPlusDrawPolygon(path.GetPolygon(*this, /* bMapIt */ true,
                                                                /*bAddLineToCloseShape */ true),
                                                flags & 0xff);
                        break;
                    }
                    case EmfPlusRecordTypeDrawImage:
                    case EmfPlusRecordTypeDrawImagePoints:
                    {
diff --git a/drawinglayer/source/tools/emfphelperdata.hxx b/drawinglayer/source/tools/emfphelperdata.hxx
index 796cbff..ef870f3 100644
--- a/drawinglayer/source/tools/emfphelperdata.hxx
+++ b/drawinglayer/source/tools/emfphelperdata.hxx
@@ -54,8 +54,8 @@ namespace emfplushelper
    #define EmfPlusRecordTypeFillRegion 0x4013
    #define EmfPlusRecordTypeFillPath 0x4014
    #define EmfPlusRecordTypeDrawPath 0x4015
    //TODO EmfPlusRecordTypeFillClosedCurve 0x4016
    //TODO EmfPlusRecordTypeDrawClosedCurve 0x4017
    #define EmfPlusRecordTypeFillClosedCurve 0x4016
    #define EmfPlusRecordTypeDrawClosedCurve 0x4017
    //TODO EmfPlusRecordTypeDrawCurve 0x4018
    #define EmfPlusRecordTypeDrawBeziers 0x4019
    #define EmfPlusRecordTypeDrawImage 0x401A
diff --git a/emfio/qa/cppunit/emf/EmfImportTest.cxx b/emfio/qa/cppunit/emf/EmfImportTest.cxx
index 6c5e77b..cf999c2 100644
--- a/emfio/qa/cppunit/emf/EmfImportTest.cxx
+++ b/emfio/qa/cppunit/emf/EmfImportTest.cxx
@@ -68,6 +68,7 @@ class Test : public test::BootstrapFixture, public XmlTestTools, public unotest:
    void TestEmfPlusGetDC();
    void TestEmfPlusSave();
    void TestEmfPlusDrawPathWithMiterLimit();
    void TestEmfPlusFillClosedCurve();
    void TestExtTextOutOpaqueAndClipTransform();

    void TestBitBltStretchBltWMF();
@@ -117,6 +118,7 @@ public:
    CPPUNIT_TEST(TestEmfPlusGetDC);
    CPPUNIT_TEST(TestEmfPlusSave);
    CPPUNIT_TEST(TestEmfPlusDrawPathWithMiterLimit);
    CPPUNIT_TEST(TestEmfPlusFillClosedCurve);
    CPPUNIT_TEST(TestExtTextOutOpaqueAndClipTransform);

    CPPUNIT_TEST(TestBitBltStretchBltWMF);
@@ -1069,6 +1071,52 @@ void Test::TestEmfPlusDrawPathWithMiterLimit()
    assertXPath(pDocument, aXPathPrefix + "polypolygonstroke[3]/stroke", 0);
}

void Test::TestEmfPlusFillClosedCurve()
{
    // tdf#143876 EMF+ records: SetWorldTransform, FillClosedCurve, DrawClosedCurve
    Primitive2DSequence aSequence
        = parseEmf(u"emfio/qa/cppunit/emf/data/TestEmfPlusFillClosedCurve.emf");
    CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequence.getLength()));
    drawinglayer::Primitive2dXmlDump dumper;
    xmlDocUniquePtr pDocument = dumper.dumpAndParse(Primitive2DContainer(aSequence));
    CPPUNIT_ASSERT(pDocument);

    assertXPath(pDocument, aXPathPrefix + "polypolygoncolor", 2);
    assertXPath(pDocument, aXPathPrefix + "polypolygoncolor[1]", "color", "#808080");
    assertXPath(pDocument, aXPathPrefix + "polypolygoncolor[1]/polypolygon", "path",
                "m18202.841744243 13758.4401790456 1269.96570308672 "
                "3175.02465670283-2539.93140617345-2116.68310446856h2539.93140617345l-2539."
                "93140617345 2116.68310446856z");

    assertXPath(pDocument, aXPathPrefix + "polypolygonstroke", 2);
    assertXPath(pDocument, aXPathPrefix + "polypolygonstroke[1]/line", "color", "#000000");
    assertXPath(pDocument, aXPathPrefix + "polypolygonstroke[1]/line", "width", "10");
    assertXPath(pDocument, aXPathPrefix + "polypolygonstroke[1]/line", "linejoin", "Miter");
    assertXPath(pDocument, aXPathPrefix + "polypolygonstroke[1]/line", "miterangle", "3");
    assertXPath(pDocument, aXPathPrefix + "polypolygonstroke[1]/line", "linecap", "BUTT");
    assertXPath(pDocument, aXPathPrefix + "polypolygonstroke[1]/polypolygon", "path",
                "m18202.841744243 13758.4401790456 1269.96570308672 "
                "3175.02465670283-2539.93140617345-2116.68310446856h2539.93140617345l-2539."
                "93140617345 2116.68310446856z");

    assertXPath(pDocument, aXPathPrefix + "polypolygoncolor[2]", "color", "#808080");
    //TODO Check path with implemented Winding
    assertXPath(pDocument, aXPathPrefix + "polypolygoncolor[2]/polypolygon", "path",
                "m22012.7388535032 13758.4401790456 1269.96570308672 "
                "3175.02465670283-2539.93140617344-2116.68310446856h2539.93140617344l-2539."
                "93140617344 2116.68310446856z");

    assertXPath(pDocument, aXPathPrefix + "polypolygonstroke[2]/line", "color", "#000000");
    assertXPath(pDocument, aXPathPrefix + "polypolygonstroke[2]/line", "width", "10");
    assertXPath(pDocument, aXPathPrefix + "polypolygonstroke[2]/line", "linejoin", "Miter");
    assertXPath(pDocument, aXPathPrefix + "polypolygonstroke[2]/line", "miterangle", "3");
    assertXPath(pDocument, aXPathPrefix + "polypolygonstroke[2]/line", "linecap", "BUTT");
    assertXPath(pDocument, aXPathPrefix + "polypolygonstroke[2]/polypolygon", "path",
                "m22012.7388535032 13758.4401790456 1269.96570308672 "
                "3175.02465670283-2539.93140617344-2116.68310446856h2539.93140617344l-2539."
                "93140617344 2116.68310446856z");
}

void Test::TestExtTextOutOpaqueAndClipTransform()
{
    // tdf#142495 EMF records: SETBKCOLOR, SELECTOBJECT, EXTTEXTOUTW, MODIFYWORLDTRANSFORM, CREATEFONTINDIRECT.
diff --git a/emfio/qa/cppunit/emf/data/TestEmfPlusFillClosedCurve.emf b/emfio/qa/cppunit/emf/data/TestEmfPlusFillClosedCurve.emf
new file mode 100644
index 0000000..ad9140a
--- /dev/null
+++ b/emfio/qa/cppunit/emf/data/TestEmfPlusFillClosedCurve.emf
Binary files differ