tdf#142770 tdf#143031 EMF+ Implement CustomLineCap

Change-Id: I9fae1d259ecdca37a1babac8a8a0e503b2dc0118
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/135960
Tested-by: Jenkins
Reviewed-by: Bartosz Kosiorek <gang65@poczta.onet.pl>
diff --git a/drawinglayer/source/tools/emfpcustomlinecap.cxx b/drawinglayer/source/tools/emfpcustomlinecap.cxx
index 49cc912..e457a36 100644
--- a/drawinglayer/source/tools/emfpcustomlinecap.cxx
+++ b/drawinglayer/source/tools/emfpcustomlinecap.cxx
@@ -21,6 +21,7 @@
#include "emfpcustomlinecap.hxx"
#include "emfppath.hxx"
#include "emfppen.hxx"
#include <basegfx/matrix/b2dhommatrixtools.hxx>

using namespace ::com::sun::star;
using namespace ::basegfx;
@@ -39,6 +40,7 @@ namespace emfplushelper
        , strokeEndCap(0)
        , strokeJoin(0)
        , miterLimit(0.0)
        , widthScale(0.0)
        , mbIsFilled(false)
    {
    }
@@ -57,6 +59,8 @@ namespace emfplushelper
        EMFPPath path(pathPoints);
        path.Read(s, pathFlags);
        polygon = path.GetPolygon(rR, false);
        // rotate polygon by 180 degrees
        polygon.transform(basegfx::utils::createRotateB2DHomMatrix(M_PI));
        mbIsFilled = bFill;
    }

@@ -71,7 +75,6 @@ namespace emfplushelper
        {
            sal_uInt32 customLineCapDataFlags, baseCap;
            float baseInset;
            float widthScale;
            float fillHotSpotX, fillHotSpotY, strokeHotSpotX, strokeHotSpotY;

            s.ReadUInt32(customLineCapDataFlags).ReadUInt32(baseCap).ReadFloat(baseInset)
@@ -82,11 +85,6 @@ namespace emfplushelper
            SAL_INFO("drawinglayer.emf", "EMF+\t\tcustomLineCapDataFlags: 0x" << std::hex << customLineCapDataFlags);
            SAL_INFO("drawinglayer.emf", "EMF+\t\tbaseCap: 0x" << std::hex << baseCap);
            SAL_INFO("drawinglayer.emf", "EMF+\t\tbaseInset: " << baseInset);
            SAL_INFO("drawinglayer.emf", "EMF+\t\tstrokeStartCap: 0x" << std::hex << strokeStartCap);
            SAL_INFO("drawinglayer.emf", "EMF+\t\tstrokeEndCap: 0x" << std::hex << strokeEndCap);
            SAL_INFO("drawinglayer.emf", "EMF+\t\tstrokeJoin: 0x" << std::hex << strokeJoin);
            SAL_INFO("drawinglayer.emf", "EMF+\t\tmiterLimit: " << miterLimit);
            SAL_INFO("drawinglayer.emf", "EMF+\t\twidthScale: " << widthScale);

            if (customLineCapDataFlags & EmfPlusCustomLineCapDataFillPath)
            {
@@ -103,16 +101,20 @@ namespace emfplushelper
            // TODO only reads the data, does not use them [I've had
            // no test document to be able to implement it]

            sal_Int32 width, height, middleInset, fillState, lineStartCap;
            sal_Int32 lineEndCap, lineJoin, widthScale;
            float fillHotSpotX, fillHotSpotY, lineHotSpotX, lineHotSpotY;
            sal_Int32 fillState;
            float width, height, middleInset, unusedHotSpot;

            s.ReadInt32(width).ReadInt32(height).ReadInt32(middleInset).ReadInt32(fillState).ReadInt32(lineStartCap)
                .ReadInt32(lineEndCap).ReadInt32(lineJoin).ReadFloat(miterLimit).ReadInt32(widthScale)
                .ReadFloat(fillHotSpotX).ReadFloat(fillHotSpotY).ReadFloat(lineHotSpotX).ReadFloat(lineHotSpotY);
            s.ReadFloat(width).ReadFloat(height).ReadFloat(middleInset).ReadInt32(fillState).ReadUInt32(strokeStartCap)
                .ReadUInt32(strokeEndCap).ReadUInt32(strokeJoin).ReadFloat(miterLimit).ReadFloat(widthScale)
                .ReadFloat(unusedHotSpot).ReadFloat(unusedHotSpot).ReadFloat(unusedHotSpot).ReadFloat(unusedHotSpot);

            SAL_INFO("drawinglayer.emf", "EMF+\t\tTODO - actually read EmfPlusCustomLineCapArrowData object (section 2.2.2.12)");
        }
        SAL_INFO("drawinglayer.emf", "EMF+\t\tstrokeStartCap: 0x" << std::hex << strokeStartCap);
        SAL_INFO("drawinglayer.emf", "EMF+\t\tstrokeEndCap: 0x" << std::hex << strokeEndCap);
        SAL_INFO("drawinglayer.emf", "EMF+\t\tstrokeJoin: 0x" << std::hex << strokeJoin);
        SAL_INFO("drawinglayer.emf", "EMF+\t\tmiterLimit: " << miterLimit);
        SAL_INFO("drawinglayer.emf", "EMF+\t\twidthScale: " << widthScale);
    }
}

diff --git a/drawinglayer/source/tools/emfpcustomlinecap.hxx b/drawinglayer/source/tools/emfpcustomlinecap.hxx
index e6202ae..22ed6be 100644
--- a/drawinglayer/source/tools/emfpcustomlinecap.hxx
+++ b/drawinglayer/source/tools/emfpcustomlinecap.hxx
@@ -27,7 +27,7 @@ namespace emfplushelper
    {
        sal_uInt32 type;
        sal_uInt32 strokeStartCap, strokeEndCap, strokeJoin;
        float miterLimit;
        float miterLimit, widthScale;
        basegfx::B2DPolyPolygon polygon;
        bool mbIsFilled;

diff --git a/drawinglayer/source/tools/emfphelperdata.cxx b/drawinglayer/source/tools/emfphelperdata.cxx
index 9e0c78c..5422595 100644
--- a/drawinglayer/source/tools/emfphelperdata.cxx
+++ b/drawinglayer/source/tools/emfphelperdata.cxx
@@ -603,11 +603,29 @@ namespace emfplushelper

        drawinglayer::attribute::LineStartEndAttribute aStart;
        if (pen->penDataFlags & EmfPlusPenDataStartCap)
            aStart = EmfPlusHelperData::CreateLineEnd(pen->startCap, pen->penWidth);
        {
            if ((pen->penDataFlags & EmfPlusPenDataCustomStartCap)
                && (pen->customStartCap->polygon.begin()->count() > 1))
                aStart = drawinglayer::attribute::LineStartEndAttribute(
                    pen->customStartCap->polygon.getB2DRange().getRange().getX() * mdExtractedXScale
                        * pen->customStartCap->widthScale * pen->penWidth,
                    pen->customStartCap->polygon, false);
            else
                aStart = EmfPlusHelperData::CreateLineEnd(pen->startCap, pen->penWidth);
        }

        drawinglayer::attribute::LineStartEndAttribute aEnd;
        if (pen->penDataFlags & EmfPlusPenDataEndCap)
            aEnd = EmfPlusHelperData::CreateLineEnd(pen->endCap, pen->penWidth);
        {
            if ((pen->penDataFlags & EmfPlusPenDataCustomEndCap)
                && (pen->customEndCap->polygon.begin()->count() > 1))
                aEnd = drawinglayer::attribute::LineStartEndAttribute(
                    pen->customEndCap->polygon.getB2DRange().getRange().getX() * mdExtractedXScale
                        * pen->customEndCap->widthScale * pen->penWidth,
                    pen->customEndCap->polygon, false);
            else
                aEnd = EmfPlusHelperData::CreateLineEnd(pen->endCap, pen->penWidth);
        }

        if (pen->GetColor().IsTransparent())
        {
@@ -644,97 +662,6 @@ namespace emfplushelper
                            pen->GetStrokeAttribute(mdExtractedXScale), aStart, aEnd));
                }
        }

        if ((pen->penDataFlags & EmfPlusPenDataCustomStartCap) && (pen->customStartCap->polygon.begin()->count() > 1))
        {
            SAL_WARN("drawinglayer.emf", "EMF+\tCustom Start Line Cap");
            ::basegfx::B2DPolyPolygon startCapPolygon(pen->customStartCap->polygon);

            // get the gradient of the first line in the polypolygon
            double x1 = polygon.begin()->getB2DPoint(0).getX();
            double y1 = polygon.begin()->getB2DPoint(0).getY();
            double x2 = polygon.begin()->getB2DPoint(1).getX();
            double y2 = polygon.begin()->getB2DPoint(1).getY();

            if ((x2 - x1) != 0)
            {
                double gradient = (y2 - y1) / (x2 - x1);

                // now we get the angle that we need to rotate the arrow by
                double angle = (M_PI / 2) - atan(gradient);

                // rotate the arrow
                startCapPolygon.transform(basegfx::utils::createRotateB2DHomMatrix(angle));
            }

            startCapPolygon.transform(maMapTransform);

            basegfx::B2DHomMatrix tran(pen->penWidth, 0.0, polygon.begin()->getB2DPoint(0).getX(),
                                       0.0, pen->penWidth, polygon.begin()->getB2DPoint(0).getY());
            startCapPolygon.transform(tran);

            if (pen->customStartCap->mbIsFilled)
            {
                mrTargetHolders.Current().append(
                            new drawinglayer::primitive2d::PolyPolygonColorPrimitive2D(
                                std::move(startCapPolygon),
                                pen->GetColor().getBColor()));
            }
            else
            {
                mrTargetHolders.Current().append(
                            new drawinglayer::primitive2d::PolyPolygonStrokePrimitive2D(
                                std::move(startCapPolygon),
                                lineAttribute,
                                pen->GetStrokeAttribute(mdExtractedXScale)));
            }
        }

        if ((pen->penDataFlags & EmfPlusPenDataCustomEndCap) && (pen->customEndCap->polygon.begin()->count() > 1))
        {
            SAL_WARN("drawinglayer.emf", "EMF+\tCustom End Line Cap");

            ::basegfx::B2DPolyPolygon endCapPolygon(pen->customEndCap->polygon);

            // get the gradient of the first line in the polypolygon
            double x1 = polygon.begin()->getB2DPoint(polygon.begin()->count() - 1).getX();
            double y1 = polygon.begin()->getB2DPoint(polygon.begin()->count() - 1).getY();
            double x2 = polygon.begin()->getB2DPoint(polygon.begin()->count() - 2).getX();
            double y2 = polygon.begin()->getB2DPoint(polygon.begin()->count() - 2).getY();

            if ((x2 - x1) != 0)
            {
                double gradient = (y2 - y1) / (x2 - x1);

                // now we get the angle that we need to rotate the arrow by
                double angle = (M_PI / 2) - atan(gradient);

                // rotate the arrow
                endCapPolygon.transform(basegfx::utils::createRotateB2DHomMatrix(angle));
            }

            endCapPolygon.transform(maMapTransform);
            basegfx::B2DHomMatrix tran(pen->penWidth, 0.0, polygon.begin()->getB2DPoint(polygon.begin()->count() - 1).getX(),
                                       0.0, pen->penWidth, polygon.begin()->getB2DPoint(polygon.begin()->count() - 1).getY());
            endCapPolygon.transform(tran);

            if (pen->customEndCap->mbIsFilled)
            {
                mrTargetHolders.Current().append(
                            new drawinglayer::primitive2d::PolyPolygonColorPrimitive2D(
                                std::move(endCapPolygon),
                                pen->GetColor().getBColor()));
            }
            else
            {
                mrTargetHolders.Current().append(
                            new drawinglayer::primitive2d::PolyPolygonStrokePrimitive2D(
                                std::move(endCapPolygon),
                                lineAttribute,
                                pen->GetStrokeAttribute(mdExtractedXScale)));
            }
        }

        mrPropertyHolders.Current().setLineColor(pen->GetColor().getBColor());
        mrPropertyHolders.Current().setLineColorActive(true);
        mrPropertyHolders.Current().setFillColorActive(false);
diff --git a/emfio/qa/cppunit/emf/EmfImportTest.cxx b/emfio/qa/cppunit/emf/EmfImportTest.cxx
index 40db85d..b423eae 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 TestEmfPlusBrushPathGradientWithBlendColors();
    void TestEmfPlusGetDC();
    void TestEmfPlusSave();
    void TestEmfPlusDrawPathWithCustomCap();
    void TestEmfPlusDrawPathWithMiterLimit();
    void TestEmfPlusFillClosedCurve();
    void TestExtTextOutOpaqueAndClipTransform();
@@ -119,6 +120,7 @@ public:
    CPPUNIT_TEST(TestEmfPlusBrushPathGradientWithBlendColors);
    CPPUNIT_TEST(TestEmfPlusGetDC);
    CPPUNIT_TEST(TestEmfPlusSave);
    CPPUNIT_TEST(TestEmfPlusDrawPathWithCustomCap);
    CPPUNIT_TEST(TestEmfPlusDrawPathWithMiterLimit);
    CPPUNIT_TEST(TestEmfPlusFillClosedCurve);
    CPPUNIT_TEST(TestExtTextOutOpaqueAndClipTransform);
@@ -1046,6 +1048,30 @@ void Test::TestEmfPlusSave()
                       "12832.6557236512,4907.54325697157");
}

void Test::TestEmfPlusDrawPathWithCustomCap()
{
    // tdf#142261 EMF+ records: DrawPath, SetWorldTransform, Object (Brush, Pen, Path)
    // Check if CustomEndCap is displayed correctly
    Primitive2DSequence aSequence
        = parseEmf(u"emfio/qa/cppunit/emf/data/TestEmfPlusDrawPathWithCustomCap.emf");
    CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequence.getLength()));
    drawinglayer::Primitive2dXmlDump dumper;
    xmlDocUniquePtr pDocument = dumper.dumpAndParse(Primitive2DContainer(aSequence));
    CPPUNIT_ASSERT(pDocument);

    assertXPathContent(pDocument, aXPathPrefix + "polygonstrokearrow/polygon",
                       "1423.297394625,1268.98481214025 830.006276132353,558.656004112967");
    assertXPath(pDocument, aXPathPrefix + "polygonstrokearrow/line", "color", "#cc0000");
    assertXPath(pDocument, aXPathPrefix + "polygonstrokearrow/line", "width", "96");
    assertXPath(pDocument, aXPathPrefix + "polygonstrokearrow/line", "linecap", "BUTT");
    assertXPath(pDocument, aXPathPrefix + "polygonstrokearrow/stroke", 0);
    assertXPath(pDocument, aXPathPrefix + "polygonstrokearrow/linestartattribute", 0);

    assertXPath(pDocument, aXPathPrefix + "polygonstrokearrow/lineendattribute", "centered", "0");
    assertXPath(pDocument, aXPathPrefix + "polygonstrokearrow/lineendattribute/polypolygon", "path",
                "m-1.5 3 1.5-3 1.5 3z");
}

void Test::TestEmfPlusDrawPathWithMiterLimit()
{
    // tdf#142261 EMF+ records: DrawPath, TranslateWorldTransform, Object (Brush, Pen, Path)
diff --git a/emfio/qa/cppunit/emf/data/TestEmfPlusDrawPathWithCustomCap.emf b/emfio/qa/cppunit/emf/data/TestEmfPlusDrawPathWithCustomCap.emf
new file mode 100644
index 0000000..e9498643
--- /dev/null
+++ b/emfio/qa/cppunit/emf/data/TestEmfPlusDrawPathWithCustomCap.emf
Binary files differ