basegfx/source/tools/bgradient.cxx                                 |   53 
++++++++
 chart2/qa/extras/chart2geometry.cxx                                |    5 
 include/basegfx/utils/bgradient.hxx                                |    7 +
 xmloff/qa/unit/data/tdf155549_MCGR_AxialGradientCompatible.odt     |binary
 xmloff/qa/unit/data/tdf155549_MCGR_AxialTransparencyCompatible.odt |binary
 xmloff/qa/unit/style.cxx                                           |   63 
++++++++++
 xmloff/source/style/GradientStyle.cxx                              |    5 
 xmloff/source/style/TransGradientStyle.cxx                         |    2 
 8 files changed, 130 insertions(+), 5 deletions(-)

New commits:
commit 6c49886ab46c53398d74610b264e5edb0332a059
Author:     Regina Henschel <rb.hensc...@t-online.de>
AuthorDate: Sat Jun 3 14:56:29 2023 +0200
Commit:     Regina Henschel <rb.hensc...@t-online.de>
CommitDate: Mon Jun 5 17:34:57 2023 +0200

    tdf#155549 MCGR: Recreate 'axial' from symmetric 'linear'
    
    When exporting a shape with an axial gradient fill to OOXML, it is
    converted to a linear gradient with multiple color stops. Versions
    before MCGR had recreated it as axial gradient on import from OOXML.
    But now LO is able to handle multiple color stops and so the linear
    gradient from OOXML is imported as linear gradient in LO.
    
    When such file is then written as ODF, the multiple color stops are
    in elements in extended namespace and versions before MCGR do not
    understand them. They show only the first and last color (which are
    equal) and the gradient is lost.
    
    With this patch LO converts the linear gradient back to an axial gradient
    on export to ODF. The exported axial gradient is rendered in a version
    with MCGR same as the linear gradient when opening the OOXML file. The
    difference is, that versions without MCGR now render an axial gradient
    with two colors.
    
    Change-Id: I2b416b4cdca75d8327107a4f259d63c2e6e97ac3
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/152574
    Tested-by: Jenkins
    Reviewed-by: Regina Henschel <rb.hensc...@t-online.de>

diff --git a/basegfx/source/tools/bgradient.cxx 
b/basegfx/source/tools/bgradient.cxx
index b7ee0780a2cc..b56ef0540d17 100644
--- a/basegfx/source/tools/bgradient.cxx
+++ b/basegfx/source/tools/bgradient.cxx
@@ -657,6 +657,28 @@ double BColorStops::detectPossibleOffsetAtStart() const
     return aColorL->getStopOffset();
 }
 
+//  checks whether the color stops are symmetrical in color and offset.
+bool BColorStops::isSymmetrical() const
+{
+    if (empty())
+        return false;
+    if (1 == size())
+        return basegfx::fTools::equal(0.5, front().getStopOffset());
+
+    BColorStops::const_iterator aIter(begin()); // for going forward
+    BColorStops::const_iterator aRIter(end()); // for going backward
+    --aRIter;
+    // We have at least two elements, so aIter <= aRIter fails before 
iterators no longer point to
+    // an element.
+    while (aIter <= aRIter && 
aIter->getStopColor().equal(aRIter->getStopColor())
+           && basegfx::fTools::equal(aIter->getStopOffset(), 1.0 - 
aRIter->getStopOffset()))
+    {
+        ++aIter;
+        --aRIter;
+    }
+    return aIter > aRIter;
+}
+
 std::string BGradient::GradientStyleToString(css::awt::GradientStyle eStyle)
 {
     switch (eStyle)
@@ -917,7 +939,7 @@ void BGradient::tryToRecreateBorder(basegfx::BColorStops* 
pAssociatedTransparenc
             pAssociatedTransparencyStops->removeSpaceAtStart(fOffset);
 
         // ...and create border value
-        SetBorder(static_cast<sal_uInt16>(fOffset * 100.0));
+        SetBorder(static_cast<sal_uInt16>(std::lround(fOffset * 100.0)));
     }
 
     if (bIsAxial)
@@ -971,6 +993,35 @@ void BGradient::tryToApplyStartEndIntensity()
     SetStartIntens(100);
     SetEndIntens(100);
 }
+
+void BGradient::tryToConvertToAxial()
+{
+    if (css::awt::GradientStyle_LINEAR != GetGradientStyle() || 0 != 
GetBorder()
+        || GetColorStops().empty())
+        return;
+
+    if (!GetColorStops().isSymmetrical())
+        return;
+
+    SetGradientStyle(css::awt::GradientStyle_AXIAL);
+
+    // Stretch the first half of the color stops to double width
+    // and collect them in a new color stops vector.
+    BColorStops aAxialColorStops;
+    aAxialColorStops.reserve(std::ceil(GetColorStops().size() / 2.0));
+    BColorStops::const_iterator aIter(GetColorStops().begin());
+    while (basegfx::fTools::lessOrEqual(aIter->getStopOffset(), 0.5))
+    {
+        BColorStop aNextStop(std::clamp((*aIter).getStopOffset() * 2.0, 0.0, 
1.0),
+                             (*aIter).getStopColor());
+        aAxialColorStops.push_back(aNextStop);
+        ++aIter;
+    }
+    // Axial gradients have outmost color as last color stop.
+    aAxialColorStops.reverseColorStops();
+
+    SetColorStops(aAxialColorStops);
+}
 }
 
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/chart2/qa/extras/chart2geometry.cxx 
b/chart2/qa/extras/chart2geometry.cxx
index d52633c80814..f560d46ba054 100644
--- a/chart2/qa/extras/chart2geometry.cxx
+++ b/chart2/qa/extras/chart2geometry.cxx
@@ -340,9 +340,8 @@ void 
Chart2GeometryTest::testTdf128345Legend_CS_TG_axial_import()
     const OString sAttribute("@draw:name='" + OU2O(sOUOpacityName) + "'");
     const OString 
sStart("//office:document-styles/office:styles/draw:opacity[" + sAttribute);
     assertXPath(pXmlDoc2, sStart + "]", 1);
-    // MCGR: Needs odf im/export for MCGR, then adapt.
-    assertXPath(pXmlDoc2, sStart + " and @draw:style='linear']"); // MCGR: 
axial -> linear
-    assertXPath(pXmlDoc2, sStart + " and @draw:start='100%']"); // MCGR: 0% -> 
100%
+    assertXPath(pXmlDoc2, sStart + " and @draw:style='axial']");
+    assertXPath(pXmlDoc2, sStart + " and @draw:start='0%']");
     assertXPath(pXmlDoc2, sStart + " and @draw:end='100%']");
 }
 
diff --git a/include/basegfx/utils/bgradient.hxx 
b/include/basegfx/utils/bgradient.hxx
index 49598c8266fa..1f42b23c6321 100644
--- a/include/basegfx/utils/bgradient.hxx
+++ b/include/basegfx/utils/bgradient.hxx
@@ -273,6 +273,9 @@ public:
     // try to detect if an empty/no-color-change area exists
     // at the start and return offset to it. Returns 0.0 if not.
     double detectPossibleOffsetAtStart() const;
+
+    // returns true if the color stops are symmetrical in color and offset, 
otherwise false.
+    bool isSymmetrical() const;
 };
 
 class BASEGFX_DLLPUBLIC BGradient final
@@ -338,6 +341,10 @@ public:
     void tryToRecreateBorder(basegfx::BColorStops* 
pAssociatedTransparencyStops = nullptr);
     void tryToApplyBorder();
     void tryToApplyStartEndIntensity();
+
+    // If a linear gradient is symmetrical it is converted to an axial 
gradient.
+    // Does nothing in other cases and for other gradient types.
+    void tryToConvertToAxial();
 };
 }
 
diff --git a/xmloff/qa/unit/data/tdf155549_MCGR_AxialGradientCompatible.odt 
b/xmloff/qa/unit/data/tdf155549_MCGR_AxialGradientCompatible.odt
new file mode 100644
index 000000000000..ca9f49e9069f
Binary files /dev/null and 
b/xmloff/qa/unit/data/tdf155549_MCGR_AxialGradientCompatible.odt differ
diff --git a/xmloff/qa/unit/data/tdf155549_MCGR_AxialTransparencyCompatible.odt 
b/xmloff/qa/unit/data/tdf155549_MCGR_AxialTransparencyCompatible.odt
new file mode 100644
index 000000000000..5fda0c063ffa
Binary files /dev/null and 
b/xmloff/qa/unit/data/tdf155549_MCGR_AxialTransparencyCompatible.odt differ
diff --git a/xmloff/qa/unit/style.cxx b/xmloff/qa/unit/style.cxx
index 3c5c050c0226..bde981d38864 100644
--- a/xmloff/qa/unit/style.cxx
+++ b/xmloff/qa/unit/style.cxx
@@ -590,6 +590,69 @@ CPPUNIT_TEST_FIXTURE(XmloffStyleTest, 
testTransparencyBorderRestoration)
     SetODFDefaultVersion(nCurrentODFVersion);
 }
 
+CPPUNIT_TEST_FIXTURE(XmloffStyleTest, testAxialGradientCompatible)
+{
+    // tdf#155549. An axial gradient with Border, StartColor A and EndColor B 
is exported to OOXML as
+    // symmetrical linear gradient with three stops, colors B A B. After the 
changes for multi-color
+    // gradients (MCGR) this is imported as linear gradient with colors B A B. 
So a consumer not able
+    // of MCGR would get a linear gradient with start and end color B. For 
better compatibility
+    // ODF export writes an axial gradient. with colors A and B.
+    // This test needs to be adapted when color stops are available in ODF 
strict and widely
+    // supported in even older LibreOffice versions.
+    loadFromURL(u"tdf155549_MCGR_AxialGradientCompatible.odt");
+
+    //Round-trip through OOXML.
+    // FixMe tdf#153183. Here "Attribute 'ID' is not allowed to appear in 
element 'v:rect'".
+    skipValidation();
+    saveAndReload("Office Open XML Text");
+    saveAndReload("writer8");
+
+    // Examine reloaded file
+    uno::Reference<drawing::XShape> xShape(getShape(0));
+    CPPUNIT_ASSERT_MESSAGE("No shape", xShape.is());
+    uno::Reference<beans::XPropertySet> xShapeProperties(xShape, 
uno::UNO_QUERY);
+
+    // Without fix these would have failed with Style=0 (=LINEAR), 
StartColor=0xFFFF00 and Border=0.
+    awt::Gradient2 aGradient;
+    xShapeProperties->getPropertyValue("FillGradient") >>= aGradient;
+    CPPUNIT_ASSERT_EQUAL_MESSAGE("gradient style", awt::GradientStyle_AXIAL, 
aGradient.Style);
+    CPPUNIT_ASSERT_EQUAL_MESSAGE("EndColor", sal_Int32(0xFFFF00), 
aGradient.EndColor);
+    CPPUNIT_ASSERT_EQUAL_MESSAGE("StartColor", sal_Int32(0x1E90FF), 
aGradient.StartColor);
+    CPPUNIT_ASSERT_EQUAL_MESSAGE("Border", sal_Int16(20), aGradient.Border);
+}
+
+CPPUNIT_TEST_FIXTURE(XmloffStyleTest, testAxialTransparencyCompatible)
+{
+    // tdf#155549. The shape in the document has a solid color and an axial 
transparency gradient
+    // with 'Transition start 60%', 'Start value 10%' and 'End value 80%'. The 
gradient is exported
+    // to OOXML as linear symmetrical gradient with three gradient stops. 
After the changes for
+    // multi-color gradients (MCGR) this is imported as linear transparency 
gradient. For better
+    // compatibility with consumers not able to use MCGR, the ODF export 
writes the transparency as
+    // axial transparency gradient that is same as in the original document.
+    // This test needs to be adapted when color stops are available in ODF 
strict and widely
+    // supported in even older LibreOffice versions.
+    loadFromURL(u"tdf155549_MCGR_AxialTransparencyCompatible.odt");
+
+    //Round-trip through OOXML.
+    // FixMe tdf#153183, and error in charSpace and in CharacterSet
+    //skipValidation();
+    saveAndReload("Office Open XML Text");
+    saveAndReload("writer8");
+
+    // Examine reloaded file
+    uno::Reference<drawing::XShape> xShape(getShape(0));
+    CPPUNIT_ASSERT(xShape.is());
+    uno::Reference<beans::XPropertySet> xShapeProperties(xShape, 
uno::UNO_QUERY);
+
+    // Without fix these would have failed with Style=LINEAR, 
StartColor=0xCCCCCC and wrong Border.
+    awt::Gradient2 aTransGradient;
+    xShapeProperties->getPropertyValue("FillTransparenceGradient") >>= 
aTransGradient;
+    CPPUNIT_ASSERT_EQUAL_MESSAGE("gradient style", awt::GradientStyle_AXIAL, 
aTransGradient.Style);
+    CPPUNIT_ASSERT_EQUAL_MESSAGE("EndColor", sal_Int32(0xCCCCCC), 
aTransGradient.EndColor);
+    CPPUNIT_ASSERT_EQUAL_MESSAGE("StartColor", sal_Int32(0x191919), 
aTransGradient.StartColor);
+    CPPUNIT_ASSERT_EQUAL_MESSAGE("Border", sal_Int16(60), 
aTransGradient.Border);
+}
+
 CPPUNIT_PLUGIN_IMPLEMENT();
 
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/xmloff/source/style/GradientStyle.cxx 
b/xmloff/source/style/GradientStyle.cxx
index 7598074fc409..d80b3f866482 100644
--- a/xmloff/source/style/GradientStyle.cxx
+++ b/xmloff/source/style/GradientStyle.cxx
@@ -228,6 +228,11 @@ void XMLGradientStyleExport::exportXML(
 
     basegfx::BGradient aGradient(rValue);
 
+    // Export of axial gradient to OOXML produces a symmetrical linear 
multi-color gradient. Import
+    // does not regenerate it as 'axial' because that is not needed for MCGR. 
For export to ODF we
+    // try to regenerate 'axial' for to get a better compatibility with LO 
versions before MCGR.
+    aGradient.tryToConvertToAxial();
+
     // MCGR: For better compatibility with LO versions before MCGR, try
     // to re-create a 'border' value based on the existing gradient stops.
     // With MCGR we do not need 'border' anymore in quite some cases since
diff --git a/xmloff/source/style/TransGradientStyle.cxx 
b/xmloff/source/style/TransGradientStyle.cxx
index 9c268a21ff85..3e89edb683f5 100644
--- a/xmloff/source/style/TransGradientStyle.cxx
+++ b/xmloff/source/style/TransGradientStyle.cxx
@@ -180,7 +180,7 @@ void XMLTransGradientStyleExport::exportXML(
 
     basegfx::BGradient aGradient(rValue);
 
-    // ToDo: aGradient.tryToConvertToAxial();
+    aGradient.tryToConvertToAxial();
 
     aGradient.tryToRecreateBorder(nullptr);
 

Reply via email to