tdf#131506 tdf#143031 EMF+ Fix displaying PathGradient fill

With previous implementation, the EMF+ import is calculating
gradient positions wrongly. It is causing warning:

  SvgGradientHelper got invalid SvgGradientEntries outside [0.0 .. 1.0]

and the gradient was not displayed at all.
This patch fixes that and gradient is displayed correctly

Change-Id: I6229c516165436d0c7ae187d9eb69b5494da396f
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/135607
Tested-by: Jenkins
Reviewed-by: Bartosz Kosiorek <gang65@poczta.onet.pl>
(cherry picked from commit 7b12c659842eb53b96dd98ecea65c6071506dfbb)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/135746
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
diff --git a/drawinglayer/source/tools/emfpbrush.cxx b/drawinglayer/source/tools/emfpbrush.cxx
index 7d6204a..c79b0de 100644
--- a/drawinglayer/source/tools/emfpbrush.cxx
+++ b/drawinglayer/source/tools/emfpbrush.cxx
@@ -182,6 +182,12 @@ namespace emfplushelper
                    SAL_INFO("drawinglayer.emf", "EMF+\t\t\t\tUse brush transformation: " << brush_transformation);
                }

                // BrushDataPresetColors and BrushDataBlendFactorsH
                if ((additionalFlags & 0x04) && (additionalFlags & 0x08))
                {
                    SAL_WARN("drawinglayer.emf", "EMF+\t Brush must not contain both BrushDataPresetColors and BrushDataBlendFactorsH");
                    return;
                }
                if (additionalFlags & 0x08) // BrushDataBlendFactorsH
                {
                    s.ReadUInt32(blendPoints);
@@ -250,6 +256,12 @@ namespace emfplushelper
                    hasTransformation = true;
                    SAL_INFO("drawinglayer.emf", "EMF+\t\t\t\tUse brush transformation: " << brush_transformation);
                }
                // BrushDataPresetColors and BrushDataBlendFactorsH
                if ((additionalFlags & 0x04) && (additionalFlags & 0x08))
                {
                    SAL_WARN("drawinglayer.emf", "EMF+\t Brush must not contain both BrushDataPresetColors and BrushDataBlendFactorsH");
                    return;
                }

                if (additionalFlags & 0x08) // BrushDataBlendFactorsH
                {
diff --git a/drawinglayer/source/tools/emfphelperdata.cxx b/drawinglayer/source/tools/emfphelperdata.cxx
index d76e8f3..94a50fc 100644
--- a/drawinglayer/source/tools/emfphelperdata.cxx
+++ b/drawinglayer/source/tools/emfphelperdata.cxx
@@ -880,21 +880,12 @@ namespace emfplushelper
                    // store the blendpoints in the vector
                    for (sal_uInt32 i = 0; i < brush->blendPoints; i++)
                    {
                        double aBlendPoint;
                        const double aBlendPoint = brush->blendPositions[i];
                        basegfx::BColor aColor;
                        if (brush->type == BrushTypeLinearGradient)
                        {
                            aBlendPoint = brush->blendPositions [i];
                        }
                        else
                        {
                            // seems like SvgRadialGradientPrimitive2D needs doubled, inverted radius
                            aBlendPoint = 2. * ( 1. - brush->blendPositions [i] );
                        }
                        aColor.setGreen( aStartColor.getGreen() + brush->blendFactors[i] * ( aEndColor.getGreen() - aStartColor.getGreen() ) );
                        aColor.setBlue ( aStartColor.getBlue()  + brush->blendFactors[i] * ( aEndColor.getBlue() - aStartColor.getBlue() ) );
                        aColor.setRed  ( aStartColor.getRed()   + brush->blendFactors[i] * ( aEndColor.getRed() - aStartColor.getRed() ) );
                        const double aAlpha = brush->solidColor.GetAlpha() + brush->blendFactors[i] * ( brush->secondColor.GetAlpha() - brush->solidColor.GetAlpha() );
                        aColor.setGreen(aStartColor.getGreen() + brush->blendFactors[i] * (aEndColor.getGreen() - aStartColor.getGreen()));
                        aColor.setBlue (aStartColor.getBlue()  + brush->blendFactors[i] * (aEndColor.getBlue() - aStartColor.getBlue()));
                        aColor.setRed  (aStartColor.getRed()   + brush->blendFactors[i] * (aEndColor.getRed() - aStartColor.getRed()));
                        const double aAlpha = brush->solidColor.GetAlpha() + brush->blendFactors[i] * (brush->secondColor.GetAlpha() - brush->solidColor.GetAlpha());
                        aVector.emplace_back(aBlendPoint, aColor, aAlpha / 255.0);
                    }
                }
@@ -905,33 +896,15 @@ namespace emfplushelper
                    // store the colorBlends in the vector
                    for (sal_uInt32 i = 0; i < brush->colorblendPoints; i++)
                    {
                        double aBlendPoint;
                        basegfx::BColor aColor;
                        if (brush->type == BrushTypeLinearGradient)
                        {
                            aBlendPoint = brush->colorblendPositions [i];
                        }
                        else
                        {
                            // seems like SvgRadialGradientPrimitive2D needs doubled, inverted radius
                            aBlendPoint = 2. * ( 1. - brush->colorblendPositions [i] );
                        }
                        aColor = brush->colorblendColors[i].getBColor();
                        aVector.emplace_back(aBlendPoint, aColor, brush->colorblendColors[i].GetAlpha() / 255.0 );
                        const double aBlendPoint = brush->colorblendPositions[i];
                        const basegfx::BColor aColor = brush->colorblendColors[i].getBColor();
                        aVector.emplace_back(aBlendPoint, aColor, brush->colorblendColors[i].GetAlpha() / 255.0);
                    }
                }
                else // ok, no extra points: just start and end
                {
                    if (brush->type == BrushTypeLinearGradient)
                    {
                        aVector.emplace_back(0.0, aStartColor, brush->solidColor.GetAlpha() / 255.0);
                        aVector.emplace_back(1.0, aEndColor, brush->secondColor.GetAlpha() / 255.0);
                    }
                    else // again, here reverse
                    {
                        aVector.emplace_back(0.0, aEndColor, brush->secondColor.GetAlpha() / 255.0);
                        aVector.emplace_back(1.0, aStartColor, brush->solidColor.GetAlpha() / 255.0);
                    }
                    aVector.emplace_back(0.0, aStartColor, brush->solidColor.GetAlpha() / 255.0);
                    aVector.emplace_back(1.0, aEndColor, brush->secondColor.GetAlpha() / 255.0);
                }

                // get the polygon range to be able to map the start/end/center point correctly
@@ -982,7 +955,7 @@ namespace emfplushelper
                            aSpreadMethod));
                }
                else // BrushTypePathGradient
                {
                { // TODO The PathGradient is not implemented, and Radial Gradient is used instead
                    basegfx::B2DPoint aCenterPoint = Map(brush->firstPointX, brush->firstPointY);
                    aCenterPoint = aPolygonTransformation * aCenterPoint;

@@ -993,9 +966,9 @@ namespace emfplushelper
                            polygon,
                            std::move(aVector),
                            aCenterPoint,
                            0.5,                   // relative radius
                            true,                  // use UnitCoordinates to stretch the gradient
                            drawinglayer::primitive2d::SpreadMethod::Repeat,
                            0.7, // relative radius little bigger to cover all elements
                            true, // use UnitCoordinates to stretch the gradient
                            drawinglayer::primitive2d::SpreadMethod::Pad,
                            nullptr));
                }
            }
diff --git a/drawinglayer/source/tools/primitive2dxmldump.cxx b/drawinglayer/source/tools/primitive2dxmldump.cxx
index 4e35d59..6356297 100644
--- a/drawinglayer/source/tools/primitive2dxmldump.cxx
+++ b/drawinglayer/source/tools/primitive2dxmldump.cxx
@@ -464,6 +464,25 @@ void writeMaterialAttribute(::tools::XmlWriter& rWriter,
    rWriter.endElement();
}

void writeSpreadMethod(::tools::XmlWriter& rWriter,
                       const drawinglayer::primitive2d::SpreadMethod& rSpreadMethod)
{
    switch (rSpreadMethod)
    {
        case drawinglayer::primitive2d::SpreadMethod::Pad:
            rWriter.attribute("spreadmethod", "pad");
            break;
        case drawinglayer::primitive2d::SpreadMethod::Reflect:
            rWriter.attribute("spreadmethod", "reflect");
            break;
        case drawinglayer::primitive2d::SpreadMethod::Repeat:
            rWriter.attribute("spreadmethod", "repeat");
            break;
        default:
            rWriter.attribute("spreadmethod", "unknown");
    }
}

} // end anonymous namespace

Primitive2dXmlDump::Primitive2dXmlDump()
@@ -933,13 +952,28 @@ void Primitive2dXmlDump::decomposeAndWrite(
                const SvgRadialGradientPrimitive2D& rSvgRadialGradientPrimitive2D
                    = dynamic_cast<const SvgRadialGradientPrimitive2D&>(*pBasePrimitive);
                rWriter.startElement("svgradialgradient");
                basegfx::B2DPoint aFocusAttribute = rSvgRadialGradientPrimitive2D.getFocal();
                if (rSvgRadialGradientPrimitive2D.isFocalSet())
                {
                    basegfx::B2DPoint aFocalAttribute = rSvgRadialGradientPrimitive2D.getFocal();
                    rWriter.attribute("focalx", aFocalAttribute.getX());
                    rWriter.attribute("focaly", aFocalAttribute.getY());
                }

                basegfx::B2DPoint aStartPoint = rSvgRadialGradientPrimitive2D.getStart();
                rWriter.attribute("startx", aStartPoint.getX());
                rWriter.attribute("starty", aStartPoint.getY());
                rWriter.attribute("radius",
                                  OString::number(rSvgRadialGradientPrimitive2D.getRadius()));
                rWriter.attribute("focusx", aFocusAttribute.getX());
                rWriter.attribute("focusy", aFocusAttribute.getY());
                writeSpreadMethod(rWriter, rSvgRadialGradientPrimitive2D.getSpreadMethod());
                rWriter.attributeDouble(
                    "opacity",
                    rSvgRadialGradientPrimitive2D.getGradientEntries().front().getOpacity());

                rWriter.startElement("transform");
                writeMatrix(rWriter, rSvgRadialGradientPrimitive2D.getGradientTransform());
                rWriter.endElement();

                writePolyPolygon(rWriter, rSvgRadialGradientPrimitive2D.getPolyPolygon());
                rWriter.endElement();
            }
            break;
@@ -956,7 +990,7 @@ void Primitive2dXmlDump::decomposeAndWrite(
                rWriter.attribute("starty", aStartAttribute.getY());
                rWriter.attribute("endx", aEndAttribute.getX());
                rWriter.attribute("endy", aEndAttribute.getY());
                //rWriter.attribute("spreadmethod", (int)rSvgLinearGradientPrimitive2D.getSpreadMethod());
                writeSpreadMethod(rWriter, rSvgLinearGradientPrimitive2D.getSpreadMethod());
                rWriter.attributeDouble(
                    "opacity",
                    rSvgLinearGradientPrimitive2D.getGradientEntries().front().getOpacity());
diff --git a/emfio/qa/cppunit/emf/EmfImportTest.cxx b/emfio/qa/cppunit/emf/EmfImportTest.cxx
index cf999c2..40db85d 100644
--- a/emfio/qa/cppunit/emf/EmfImportTest.cxx
+++ b/emfio/qa/cppunit/emf/EmfImportTest.cxx
@@ -65,6 +65,7 @@ class Test : public test::BootstrapFixture, public XmlTestTools, public unotest:
    void TestSetArcDirection();
    void TestDrawPolyLine16WithClip();
    void TestFillRegion();
    void TestEmfPlusBrushPathGradientWithBlendColors();
    void TestEmfPlusGetDC();
    void TestEmfPlusSave();
    void TestEmfPlusDrawPathWithMiterLimit();
@@ -115,6 +116,7 @@ public:
    CPPUNIT_TEST(TestSetArcDirection);
    CPPUNIT_TEST(TestDrawPolyLine16WithClip);
    CPPUNIT_TEST(TestFillRegion);
    CPPUNIT_TEST(TestEmfPlusBrushPathGradientWithBlendColors);
    CPPUNIT_TEST(TestEmfPlusGetDC);
    CPPUNIT_TEST(TestEmfPlusSave);
    CPPUNIT_TEST(TestEmfPlusDrawPathWithMiterLimit);
@@ -532,6 +534,7 @@ void Test::TestLinearGradient()
    assertXPath(pDocument, aXPathPrefix + "mask/polypolygon", "width", "15232");
    assertXPath(pDocument, aXPathPrefix + "mask/polypolygon", "path", "m0 0h15232v7610h-15232z");

    assertXPath(pDocument, aXPathPrefix + "mask/svglineargradient[1]", "spreadmethod", "repeat");
    assertXPath(pDocument, aXPathPrefix + "mask/svglineargradient[1]", "startx", "0");
    assertXPath(pDocument, aXPathPrefix + "mask/svglineargradient[1]", "starty", "-1");
    assertXPath(pDocument, aXPathPrefix + "mask/svglineargradient[1]", "endx", "0");
@@ -540,6 +543,8 @@ void Test::TestLinearGradient()
                "0.392156862745098");
    assertXPath(pDocument, aXPathPrefix + "mask/svglineargradient[1]/polypolygon", "path",
                "m0 0.216110019646294h7615.75822989746v7610.21611001965h-7615.75822989746z");

    assertXPath(pDocument, aXPathPrefix + "mask/svglineargradient[2]", "spreadmethod", "repeat");
    assertXPath(pDocument, aXPathPrefix + "mask/svglineargradient[2]", "startx", "-1");
    assertXPath(pDocument, aXPathPrefix + "mask/svglineargradient[2]", "starty", "-1");
    assertXPath(pDocument, aXPathPrefix + "mask/svglineargradient[2]", "endx", "0");
@@ -959,6 +964,24 @@ void Test::TestPolylinetoCloseStroke()
    assertXPath(pDocument, aXPathPrefix + "polygonhairline[2]", "color", "#000000");
}

void Test::TestEmfPlusBrushPathGradientWithBlendColors()
{
    // tdf#131506 EMF+ records: FillRects, Brush with PathGradient and BlendColor, FillRects
    Primitive2DSequence aSequence
        = parseEmf(u"emfio/qa/cppunit/emf/data/TestEmfPlusBrushPathGradientWithBlendColors.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 + "svgradialgradient", "radius", "0.7");
    assertXPath(pDocument, aXPathPrefix + "svgradialgradient/focalx", 0);
    assertXPath(pDocument, aXPathPrefix + "svgradialgradient/focaly", 0);
    assertXPath(pDocument, aXPathPrefix + "svgradialgradient", "startx", "0");
    assertXPath(pDocument, aXPathPrefix + "svgradialgradient", "starty", "0");
    assertXPath(pDocument, aXPathPrefix + "svgradialgradient", "spreadmethod", "pad");
}

void Test::TestEmfPlusGetDC()
{
    // tdf#147818 EMF+ records: GetDC, DrawPath, FillRects
diff --git a/emfio/qa/cppunit/emf/data/TestEmfPlusBrushPathGradientWithBlendColors.emf b/emfio/qa/cppunit/emf/data/TestEmfPlusBrushPathGradientWithBlendColors.emf
new file mode 100644
index 0000000..caa9876
--- /dev/null
+++ b/emfio/qa/cppunit/emf/data/TestEmfPlusBrushPathGradientWithBlendColors.emf
Binary files differ
diff --git a/svgio/qa/cppunit/SvgImportTest.cxx b/svgio/qa/cppunit/SvgImportTest.cxx
index e4f8ffe..4f9ee58 100644
--- a/svgio/qa/cppunit/SvgImportTest.cxx
+++ b/svgio/qa/cppunit/SvgImportTest.cxx
@@ -415,9 +415,13 @@ void Test::testTdf97542_2()

    CPPUNIT_ASSERT (pDocument);

    assertXPath(pDocument, "/primitive2D/transform/objectinfo/svgradialgradient[1]", "focusx", "1");
    assertXPath(pDocument, "/primitive2D/transform/objectinfo/svgradialgradient[1]", "focusy", "1");
    assertXPath(pDocument, "/primitive2D/transform/objectinfo/svgradialgradient[1]", "radius", "3");
    assertXPath(pDocument, "/primitive2D/transform/objectinfo/svgradialgradient", "startx", "1");
    assertXPath(pDocument, "/primitive2D/transform/objectinfo/svgradialgradient", "starty", "1");
    assertXPath(pDocument, "/primitive2D/transform/objectinfo/svgradialgradient/focalx", 0);
    assertXPath(pDocument, "/primitive2D/transform/objectinfo/svgradialgradient/focaly", 0);
    assertXPath(pDocument, "/primitive2D/transform/objectinfo/svgradialgradient", "radius", "3");
    assertXPath(pDocument, "/primitive2D/transform/objectinfo/svgradialgradient", "spreadmethod", "pad");
    assertXPath(pDocument, "/primitive2D/transform/objectinfo/svgradialgradient", "opacity", "1");
}

void Test::testTdf97543()
@@ -741,8 +745,12 @@ void Test::testTdf94765()
    CPPUNIT_ASSERT (pDocument);

    //Check that both rectangles use the gradient as fill
    assertXPath(pDocument, "/primitive2D/transform/transform/svglineargradient[1]", "startx", "1");
    assertXPath(pDocument, "/primitive2D/transform/transform/svglineargradient[1]", "starty", "1");
    assertXPath(pDocument, "/primitive2D/transform/transform/svglineargradient[1]", "endx", "2");
    assertXPath(pDocument, "/primitive2D/transform/transform/svglineargradient[1]", "endy", "1");
    assertXPath(pDocument, "/primitive2D/transform/transform/svglineargradient[2]", "startx", "0");
    assertXPath(pDocument, "/primitive2D/transform/transform/svglineargradient[2]", "starty", "0");
    assertXPath(pDocument, "/primitive2D/transform/transform/svglineargradient[2]", "endx", "0");
    assertXPath(pDocument, "/primitive2D/transform/transform/svglineargradient[2]", "endy", "0");
}