tdf#55058 tdf#143875 EMF+ Fix display of dashed lines and line joints

With previous implementation, empty spaces between dashes
were too long.
Additionally line joints were not working correctly, after
EMF+ reworking: tdf#111486

This commit fixes all these issues and additionally it is
covering it with tests.
Change-Id: I9404e566d2d7d3405ab817268ad9b1f538c200eb

Change-Id: I523f92a928ab592ff175d0d01c1ad1a3bc22e324
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/133207
Tested-by: Jenkins
Reviewed-by: Bartosz Kosiorek <gang65@poczta.onet.pl>
diff --git a/compilerplugins/clang/redundantfcast.cxx b/compilerplugins/clang/redundantfcast.cxx
index be9565c..1378d9e 100644
--- a/compilerplugins/clang/redundantfcast.cxx
+++ b/compilerplugins/clang/redundantfcast.cxx
@@ -335,6 +335,9 @@ public:
        // tdf#145203: FIREBIRD cannot create a table
        if (fn == SRCDIR "/connectivity/source/drivers/firebird/DatabaseMetaData.cxx")
            return false;
        // false positive during using contructor drawinglayer::attribute::StrokeAttribute({ 3 * pw, pw })
        if (fn == SRCDIR "/drawinglayer/source/tools/emfppen.cxx")
            return false;
        return true;
    }

diff --git a/drawinglayer/source/tools/emfpcustomlinecap.cxx b/drawinglayer/source/tools/emfpcustomlinecap.cxx
index e24cbcc..49cc912 100644
--- a/drawinglayer/source/tools/emfpcustomlinecap.cxx
+++ b/drawinglayer/source/tools/emfpcustomlinecap.cxx
@@ -43,15 +43,6 @@ namespace emfplushelper
    {
    }

    void EMFPCustomLineCap::SetAttributes(rendering::StrokeAttributes& aAttributes)
    {
        aAttributes.StartCapType = EMFPPen::lcl_convertStrokeCap(strokeStartCap);
        aAttributes.EndCapType = EMFPPen::lcl_convertStrokeCap(strokeEndCap);
        aAttributes.JoinType = EMFPPen::lcl_convertLineJoinType(strokeJoin);

        aAttributes.MiterLimit = miterLimit;
    }

    void EMFPCustomLineCap::ReadPath(SvStream& s, EmfPlusHelperData const & rR, bool bFill)
    {
        sal_Int32 pathLength;
diff --git a/drawinglayer/source/tools/emfpcustomlinecap.hxx b/drawinglayer/source/tools/emfpcustomlinecap.hxx
index a42e0ab..e6202ae 100644
--- a/drawinglayer/source/tools/emfpcustomlinecap.hxx
+++ b/drawinglayer/source/tools/emfpcustomlinecap.hxx
@@ -19,7 +19,6 @@

#pragma once

#include <com/sun/star/rendering/StrokeAttributes.hpp>
#include "emfphelperdata.hxx"

namespace emfplushelper
@@ -34,7 +33,6 @@ namespace emfplushelper

        EMFPCustomLineCap();

        void SetAttributes(com::sun::star::rendering::StrokeAttributes& aAttributes);
        void ReadPath(SvStream& s, EmfPlusHelperData const & rR, bool bFill);
        void Read(SvStream& s, EmfPlusHelperData const & rR);
    };
diff --git a/drawinglayer/source/tools/emfphelperdata.cxx b/drawinglayer/source/tools/emfphelperdata.cxx
index e60f881..84d848e 100644
--- a/drawinglayer/source/tools/emfphelperdata.cxx
+++ b/drawinglayer/source/tools/emfphelperdata.cxx
@@ -523,13 +523,6 @@ namespace emfplushelper
        if (!(pen && polygon.count()))
            return;

        // we need a line join attribute
        basegfx::B2DLineJoin lineJoin = basegfx::B2DLineJoin::Round;
        if (pen->penDataFlags & EmfPlusPenDataJoin) // additional line join information
        {
            lineJoin = static_cast<basegfx::B2DLineJoin>(EMFPPen::lcl_convertLineJoinType(pen->lineJoin));
        }

        // we need a line cap attribute
        css::drawing::LineCap lineCap = css::drawing::LineCap_BUTT;
        if (pen->penDataFlags & EmfPlusPenDataStartCap) // additional line cap information
@@ -541,57 +534,16 @@ namespace emfplushelper
        const double transformedPenWidth = maMapTransform.get(0, 0) * pen->penWidth;
        drawinglayer::attribute::LineAttribute lineAttribute(pen->GetColor().getBColor(),
                                                             transformedPenWidth,
                                                             lineJoin,
                                                             lineCap);

        drawinglayer::attribute::StrokeAttribute aStrokeAttribute;
        if (pen->penDataFlags & EmfPlusPenDataLineStyle && pen->dashStyle != EmfPlusLineStyleCustom) // pen has a predefined line style
        {
            // short writing
            const double pw = maMapTransform.get(1, 1) * pen->penWidth;
            // taken from the old cppcanvas implementation and multiplied with pen width
            const std::vector<double> dash = { 3*pw, 3*pw };
            const std::vector<double> dot = { pw, 3*pw };
            const std::vector<double> dashdot = { 3*pw, 3*pw, pw, 3*pw };
            const std::vector<double> dashdotdot = { 3*pw, 3*pw, pw, 3*pw, pw, 3*pw };

            switch (pen->dashStyle)
            {
                case EmfPlusLineStyleSolid: // do nothing special, use default stroke attribute
                    break;
                case EmfPlusLineStyleDash:
                    aStrokeAttribute = drawinglayer::attribute::StrokeAttribute(std::vector(dash));
                    break;
                case EmfPlusLineStyleDot:
                    aStrokeAttribute = drawinglayer::attribute::StrokeAttribute(std::vector(dot));
                    break;
                case EmfPlusLineStyleDashDot:
                    aStrokeAttribute = drawinglayer::attribute::StrokeAttribute(std::vector(dashdot));
                    break;
                case EmfPlusLineStyleDashDotDot:
                    aStrokeAttribute = drawinglayer::attribute::StrokeAttribute(std::vector(dashdotdot));
                    break;
            }
        }
        else if (pen->penDataFlags & EmfPlusPenDataDashedLine) // pen has a custom dash line
        {
            // StrokeAttribute needs a double vector while the pen provides a float vector
            std::vector<double> aPattern(pen->dashPattern.size());
            for (size_t i=0; i<aPattern.size(); i++)
            {
                // convert from float to double and multiply with the adjusted pen width
                aPattern[i] = maMapTransform.get(1, 1) * pen->penWidth * pen->dashPattern[i];
            }
            aStrokeAttribute = drawinglayer::attribute::StrokeAttribute(std::move(aPattern));
        }

                                                             pen->GetLineJoinType(),
                                                             lineCap,
                                                             basegfx::deg2rad(15.0)); // TODO Add MiterLimit support
        if (!pen->GetColor().IsTransparent())
        {
            mrTargetHolders.Current().append(
                new drawinglayer::primitive2d::PolyPolygonStrokePrimitive2D(
                    polygon,
                    lineAttribute,
                    aStrokeAttribute));
                    pen->GetStrokeAttribute(maMapTransform.get(1, 1))));
        }
        else
        {
@@ -599,7 +551,7 @@ namespace emfplushelper
                        new drawinglayer::primitive2d::PolyPolygonStrokePrimitive2D(
                            polygon,
                            lineAttribute,
                            aStrokeAttribute));
                            pen->GetStrokeAttribute(maMapTransform.get(1, 1))));

            mrTargetHolders.Current().append(
                        new drawinglayer::primitive2d::UnifiedTransparencePrimitive2D(
@@ -648,7 +600,7 @@ namespace emfplushelper
                            new drawinglayer::primitive2d::PolyPolygonStrokePrimitive2D(
                                startCapPolygon,
                                lineAttribute,
                                aStrokeAttribute));
                                pen->GetStrokeAttribute(maMapTransform.get(1, 1))));
            }
        }

@@ -693,7 +645,7 @@ namespace emfplushelper
                            new drawinglayer::primitive2d::PolyPolygonStrokePrimitive2D(
                                endCapPolygon,
                                lineAttribute,
                                aStrokeAttribute));
                                pen->GetStrokeAttribute(maMapTransform.get(1, 1))));
            }
        }

diff --git a/drawinglayer/source/tools/emfppen.cxx b/drawinglayer/source/tools/emfppen.cxx
index d41dca6..f348391 100644
--- a/drawinglayer/source/tools/emfppen.cxx
+++ b/drawinglayer/source/tools/emfppen.cxx
@@ -18,7 +18,6 @@
 */

#include <com/sun/star/rendering/PathCapType.hpp>
#include <com/sun/star/rendering/PathJoinType.hpp>
#include <o3tl/safeint.hxx>
#include <sal/log.hxx>
#include <rtl/ustrbuf.hxx>
@@ -179,27 +178,70 @@ namespace emfplushelper
    {
        switch (nEmfStroke)
        {
            case EmfPlusLineCapTypeSquare: return rendering::PathCapType::SQUARE;
            case EmfPlusLineCapTypeRound:  return rendering::PathCapType::ROUND;
            case EmfPlusLineCapTypeSquare:
                return rendering::PathCapType::SQUARE;
            // we have no mapping for EmfPlusLineCapTypeTriangle,
            // but it is similar to Round
            case EmfPlusLineCapTypeTriangle: // fall-through
            case EmfPlusLineCapTypeRound:
                return rendering::PathCapType::ROUND;
        }

        // we have no mapping for EmfPlusLineCapTypeTriangle = 0x00000003,
        // so return BUTT always
        return rendering::PathCapType::BUTT;
    }

    sal_Int8 EMFPPen::lcl_convertLineJoinType(sal_uInt32 nEmfLineJoin)
    basegfx::B2DLineJoin EMFPPen::GetLineJoinType() const
    {
        switch (nEmfLineJoin)
        if (penDataFlags & EmfPlusPenDataJoin) // additional line join information
        {
            case EmfPlusLineJoinTypeMiter:        // fall-through
            case EmfPlusLineJoinTypeMiterClipped: return rendering::PathJoinType::MITER;
            case EmfPlusLineJoinTypeBevel:        return rendering::PathJoinType::BEVEL;
            case EmfPlusLineJoinTypeRound:        return rendering::PathJoinType::ROUND;
            switch (lineJoin)
            {
                case EmfPlusLineJoinTypeMiter: // fall-through
                case EmfPlusLineJoinTypeMiterClipped:
                    return basegfx::B2DLineJoin::Miter;
                case EmfPlusLineJoinTypeBevel:
                    return basegfx::B2DLineJoin::Bevel;
                case EmfPlusLineJoinTypeRound:
                    return basegfx::B2DLineJoin::Round;
            }
        }
        // If nothing set, then miter applied with no limit
        return basegfx::B2DLineJoin::Miter;
    }

        assert(false); // Line Join type isn't in specification.
        return 0;
    drawinglayer::attribute::StrokeAttribute
    EMFPPen::GetStrokeAttribute(const double aTransformation) const
    {
        if (penDataFlags & EmfPlusPenDataLineStyle // pen has a predefined line style
            && dashStyle != EmfPlusLineStyleCustom)
        {
            const double pw = aTransformation * penWidth;
            switch (dashStyle)
            {
                case EmfPlusLineStyleDash:
                    return drawinglayer::attribute::StrokeAttribute({ 3 * pw, pw });
                case EmfPlusLineStyleDot:
                    return drawinglayer::attribute::StrokeAttribute({ pw, pw });
                case EmfPlusLineStyleDashDot:
                    return drawinglayer::attribute::StrokeAttribute({ 3 * pw, pw, pw, pw });
                case EmfPlusLineStyleDashDotDot:
                    return drawinglayer::attribute::StrokeAttribute({ 3 * pw, pw, pw, pw, pw, pw });
            }
        }
        else if (penDataFlags & EmfPlusPenDataDashedLine) // pen has a custom dash line
        {
            const double pw = aTransformation * penWidth;
            // StrokeAttribute needs a double vector while the pen provides a float vector
            std::vector<double> aPattern(dashPattern.size());
            for (size_t i = 0; i < aPattern.size(); i++)
            {
                // convert from float to double and multiply with the adjusted pen width
                aPattern[i] = pw * dashPattern[i];
            }
            return drawinglayer::attribute::StrokeAttribute(std::move(aPattern));
        }
        //  EmfPlusLineStyleSolid: - do nothing special, use default stroke attribute
        return drawinglayer::attribute::StrokeAttribute();
    }

    void EMFPPen::Read(SvStream& s, EmfPlusHelperData const & rR)
@@ -249,7 +291,7 @@ namespace emfplushelper
        if (penDataFlags & PenDataJoin)
        {
            s.ReadInt32(lineJoin);
            SAL_WARN("drawinglayer.emf", "EMF+\t\tTODO PenDataJoin: " << LineJoinTypeToString(lineJoin) << " (0x" << std::hex << lineJoin << ")");
            SAL_WARN("drawinglayer.emf", "EMF+\t\t LineJoin: " << LineJoinTypeToString(lineJoin) << " (0x" << std::hex << lineJoin << ")");
        }
        else
        {
diff --git a/drawinglayer/source/tools/emfppen.hxx b/drawinglayer/source/tools/emfppen.hxx
index 05b2fc3..29ece63 100644
--- a/drawinglayer/source/tools/emfppen.hxx
+++ b/drawinglayer/source/tools/emfppen.hxx
@@ -19,6 +19,7 @@

#pragma once

#include <drawinglayer/attribute/strokeattribute.hxx>
#include "emfpbrush.hxx"
#include <vector>

@@ -26,6 +27,7 @@ namespace emfplushelper
{
    const sal_uInt32 EmfPlusLineCapTypeSquare = 0x00000001;
    const sal_uInt32 EmfPlusLineCapTypeRound = 0x00000002;
    const sal_uInt32 EmfPlusLineCapTypeTriangle = 0x00000003;

    const sal_uInt32 EmfPlusLineJoinTypeMiter = 0x00000000;
    const sal_uInt32 EmfPlusLineJoinTypeBevel = 0x00000001;
@@ -122,7 +124,8 @@ namespace emfplushelper
        void Read(SvStream& s, EmfPlusHelperData const & rR);

        static sal_Int8 lcl_convertStrokeCap(sal_uInt32 nEmfStroke);
        static sal_Int8 lcl_convertLineJoinType(sal_uInt32 nEmfLineJoin);
        drawinglayer::attribute::StrokeAttribute GetStrokeAttribute(const double aTransformation) const;
        basegfx::B2DLineJoin GetLineJoinType() const;
    };
}

diff --git a/drawinglayer/source/tools/primitive2dxmldump.cxx b/drawinglayer/source/tools/primitive2dxmldump.cxx
index 26ee634..6126449 100644
--- a/drawinglayer/source/tools/primitive2dxmldump.cxx
+++ b/drawinglayer/source/tools/primitive2dxmldump.cxx
@@ -13,6 +13,7 @@
#include <tools/stream.hxx>
#include <tools/XmlWriter.hxx>

#include <math.h>
#include <memory>
#include <sal/log.hxx>

@@ -132,6 +133,24 @@ void writePolyPolygon(::tools::XmlWriter& rWriter, const basegfx::B2DPolyPolygon
    rWriter.endElement();
}

void writeStrokeAttribute(::tools::XmlWriter& rWriter,
                          const drawinglayer::attribute::StrokeAttribute& rStrokeAttribute)
{
    if (!rStrokeAttribute.getDotDashArray().empty())
    {
        rWriter.startElement("stroke");

        OUString sDotDash;
        for (double fDotDash : rStrokeAttribute.getDotDashArray())
        {
            sDotDash += OUString::number(round(100.0 * fDotDash)) + " ";
        }
        rWriter.attribute("dotDashArray", sDotDash);
        rWriter.attribute("fullDotDashLength", rStrokeAttribute.getFullDotDashLen());
        rWriter.endElement();
    }
}

void writeLineAttribute(::tools::XmlWriter& rWriter,
                        const drawinglayer::attribute::LineAttribute& rLineAttribute)
{
@@ -715,14 +734,7 @@ void Primitive2dXmlDump::decomposeAndWrite(
                rWriter.endElement();

                writeLineAttribute(rWriter, rPolygonStrokePrimitive2D.getLineAttribute());

                rWriter.startElement("stroke");
                const drawinglayer::attribute::StrokeAttribute& aStrokeAttribute
                    = rPolygonStrokePrimitive2D.getStrokeAttribute();
                rWriter.attribute("fulldotdashlen", aStrokeAttribute.getFullDotDashLen());
                //rWriter.attribute("dotdasharray", aStrokeAttribute.getDotDashArray());
                rWriter.endElement();

                writeStrokeAttribute(rWriter, rPolygonStrokePrimitive2D.getStrokeAttribute());
                rWriter.endElement();
            }
            break;
@@ -733,9 +745,7 @@ void Primitive2dXmlDump::decomposeAndWrite(
                rWriter.startElement("polypolygonstroke");

                writeLineAttribute(rWriter, rPolyPolygonStrokePrimitive2D.getLineAttribute());

                //getStrokeAttribute()

                writeStrokeAttribute(rWriter, rPolyPolygonStrokePrimitive2D.getStrokeAttribute());
                writePolyPolygon(rWriter, rPolyPolygonStrokePrimitive2D.getB2DPolyPolygon());

                rWriter.endElement();
diff --git a/emfio/qa/cppunit/emf/EmfImportTest.cxx b/emfio/qa/cppunit/emf/EmfImportTest.cxx
index 0f28169..6c4785b 100644
--- a/emfio/qa/cppunit/emf/EmfImportTest.cxx
+++ b/emfio/qa/cppunit/emf/EmfImportTest.cxx
@@ -51,6 +51,7 @@ class Test : public test::BootstrapFixture, public XmlTestTools, public unotest:
    void TestDrawStringTransparent();
    void TestDrawStringWithBrush();
    void TestDrawLine();
    void TestDrawLineWithDash();
    void TestLinearGradient();
    void TestTextMapMode();
    void TestEnglishMapMode();
@@ -96,6 +97,7 @@ public:
    CPPUNIT_TEST(TestDrawStringTransparent);
    CPPUNIT_TEST(TestDrawStringWithBrush);
    CPPUNIT_TEST(TestDrawLine);
    CPPUNIT_TEST(TestDrawLineWithDash);
    CPPUNIT_TEST(TestLinearGradient);
    CPPUNIT_TEST(TestTextMapMode);
    CPPUNIT_TEST(TestEnglishMapMode);
@@ -386,6 +388,37 @@ void Test::TestDrawLine()
    assertXPath(pDocument, aXPathPrefix + "polypolygonstroke/line", "width", "33");
}

void Test::TestDrawLineWithDash()
{
    // EMF+ with records: DrawLine
    // The lines with different dash styles
    Primitive2DSequence aSequence
        = parseEmf(u"/emfio/qa/cppunit/emf/data/TestEmfPlusDrawLineWithDash.emf");
    CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequence.getLength()));
    drawinglayer::Primitive2dXmlDump dumper;
    xmlDocUniquePtr pDocument = dumper.dumpAndParse(Primitive2DContainer(aSequence));
    CPPUNIT_ASSERT(pDocument);

    // check correct import of the DrawLine: color and width of the line
    assertXPath(pDocument, aXPathPrefix + "polypolygonstroke", 10);
    assertXPath(pDocument, aXPathPrefix + "polypolygonstroke[1]/line", "color", "#000000");
    assertXPath(pDocument, aXPathPrefix + "polypolygonstroke[1]/line", "width", "132");
    assertXPath(pDocument, aXPathPrefix + "polypolygonstroke[1]/stroke", 0);

    assertXPath(pDocument, aXPathPrefix + "polypolygonstroke[2]/line", "width", "132");
    assertXPath(pDocument, aXPathPrefix + "polypolygonstroke[2]/stroke", "dotDashArray",
                "13225 13225 ");
    assertXPath(pDocument, aXPathPrefix + "polypolygonstroke[3]/stroke", "dotDashArray",
                "39674 13225 ");
    assertXPath(pDocument, aXPathPrefix + "polypolygonstroke[4]/stroke", "dotDashArray",
                "39674 13225 13225 13225 ");
    assertXPath(pDocument, aXPathPrefix + "polypolygonstroke[5]/stroke", "dotDashArray",
                "39674 13225 13225 13225 13225 13225 ");
    //TODO polypolygonstroke[6-9]/stroke add support for PenDataDashedLineOffset
    assertXPath(pDocument, aXPathPrefix + "polypolygonstroke[10]/stroke", "dotDashArray",
                "66124 26450 198372 52899 ");
}

void Test::TestLinearGradient()
{
    // EMF+ file with LinearGradient brush
diff --git a/emfio/qa/cppunit/emf/data/TestEmfPlusDrawLineWithDash.emf b/emfio/qa/cppunit/emf/data/TestEmfPlusDrawLineWithDash.emf
new file mode 100644
index 0000000..dc5af59
--- /dev/null
+++ b/emfio/qa/cppunit/emf/data/TestEmfPlusDrawLineWithDash.emf
Binary files differ
diff --git a/svx/qa/unit/svdraw.cxx b/svx/qa/unit/svdraw.cxx
index a063ef7..a730bb3 100644
--- a/svx/qa/unit/svdraw.cxx
+++ b/svx/qa/unit/svdraw.cxx
@@ -338,7 +338,8 @@ CPPUNIT_TEST_FIXTURE(SvdrawTest, testRectangleObject)

    assertXPathContent(pXmlDoc, aBasePath + "/polygon", "49.5,99 0,99 0,0 99,0 99,99");

    assertXPath(pXmlDoc, aBasePath + "/stroke", "fulldotdashlen", "0");
    // If solid line, then there is no line stroke information
    assertXPath(pXmlDoc, aBasePath + "/stroke", 0);

    pPage->RemoveObject(0);