summaryrefslogtreecommitdiffstats
path: root/xmloff/qa/unit/style.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'xmloff/qa/unit/style.cxx')
-rw-r--r--xmloff/qa/unit/style.cxx663
1 files changed, 663 insertions, 0 deletions
diff --git a/xmloff/qa/unit/style.cxx b/xmloff/qa/unit/style.cxx
new file mode 100644
index 0000000000..178b0f2161
--- /dev/null
+++ b/xmloff/qa/unit/style.cxx
@@ -0,0 +1,663 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include <sal/config.h>
+
+#include <test/unoapixml_test.hxx>
+
+#include <com/sun/star/awt/ColorStop.hpp>
+#include <com/sun/star/awt/Gradient2.hpp>
+#include <com/sun/star/beans/XPropertySet.hpp>
+#include <com/sun/star/container/XNameContainer.hpp>
+#include <com/sun/star/drawing/XDrawPagesSupplier.hpp>
+#include <com/sun/star/drawing/XShape.hpp>
+#include <com/sun/star/style/XStyleFamiliesSupplier.hpp>
+
+#include <docmodel/uno/UnoComplexColor.hxx>
+#include <officecfg/Office/Common.hxx>
+#include <rtl/character.hxx>
+#include <unotools/saveopt.hxx>
+
+using namespace ::com::sun::star;
+
+/// Covers xmloff/source/style/ fixes.
+class XmloffStyleTest : public UnoApiXmlTest
+{
+public:
+ XmloffStyleTest();
+ uno::Reference<drawing::XShape> getShape(sal_uInt8 nShapeIndex);
+};
+
+XmloffStyleTest::XmloffStyleTest()
+ : UnoApiXmlTest("/xmloff/qa/unit/data/")
+{
+}
+
+uno::Reference<drawing::XShape> XmloffStyleTest::getShape(sal_uInt8 nShapeIndex)
+{
+ uno::Reference<drawing::XDrawPagesSupplier> xDrawPagesSupplier(mxComponent,
+ uno::UNO_QUERY_THROW);
+ uno::Reference<drawing::XDrawPages> xDrawPages(xDrawPagesSupplier->getDrawPages());
+ uno::Reference<drawing::XDrawPage> xDrawPage(xDrawPages->getByIndex(0), uno::UNO_QUERY_THROW);
+ uno::Reference<drawing::XShape> xShape(xDrawPage->getByIndex(nShapeIndex),
+ uno::UNO_QUERY_THROW);
+ return xShape;
+}
+
+CPPUNIT_TEST_FIXTURE(XmloffStyleTest, testFillImageBase64)
+{
+ // Load a flat ODG that has base64-encoded bitmap as a fill style.
+ loadFromFile(u"fill-image-base64.fodg");
+ uno::Reference<lang::XMultiServiceFactory> xFactory(mxComponent, uno::UNO_QUERY);
+ uno::Reference<container::XNameContainer> xBitmaps(
+ xFactory->createInstance("com.sun.star.drawing.BitmapTable"), uno::UNO_QUERY);
+
+ // Without the accompanying fix in place, this test would have failed, as the base64 stream was
+ // not considered when parsing the fill-image style.
+ CPPUNIT_ASSERT(xBitmaps->hasByName("libreoffice_0"));
+}
+
+namespace
+{
+struct XmlFont
+{
+ OString aName;
+ OString aFontFamilyGeneric;
+ bool operator<(const XmlFont& rOther) const
+ {
+ sal_Int32 nRet = aName.compareTo(rOther.aName);
+ if (nRet != 0)
+ {
+ return nRet < 0;
+ }
+
+ return aFontFamilyGeneric.compareTo(rOther.aFontFamilyGeneric) < 0;
+ }
+};
+
+Color asColor(com::sun::star::rendering::RGBColor const& rRGBColor)
+{
+ basegfx::BColor aBColor(rRGBColor.Red, rRGBColor.Green, rRGBColor.Blue);
+ return Color(aBColor);
+}
+}
+
+CPPUNIT_TEST_FIXTURE(XmloffStyleTest, testFontSorting)
+{
+ // Given an empty document with default fonts (Liberation Sans, Lucida Sans, etc):
+ mxComponent = loadFromDesktop("private:factory/swriter");
+
+ // When saving that document to ODT:
+ save("writer8");
+
+ // Then make sure <style:font-face> elements are sorted (by style:name="..."):
+ xmlDocUniquePtr pXmlDoc = parseExport("content.xml");
+ xmlXPathObjectPtr pXPath = getXPathNode(
+ pXmlDoc, "/office:document-content/office:font-face-decls/style:font-face"_ostr);
+ xmlNodeSetPtr pXmlNodes = pXPath->nodesetval;
+ int nNodeCount = xmlXPathNodeSetGetLength(pXmlNodes);
+ std::vector<XmlFont> aXMLFonts;
+ std::vector<XmlFont> aSortedFonts;
+ for (int i = 0; i < nNodeCount; ++i)
+ {
+ xmlNodePtr pXmlNode = pXmlNodes->nodeTab[i];
+ xmlChar* pName = xmlGetProp(pXmlNode, BAD_CAST("name"));
+ OString aName(reinterpret_cast<char const*>(pName));
+
+ // Ignore numbers at the end, those are just appended to make all names unique.
+ while (rtl::isAsciiDigit(static_cast<sal_uInt32>(aName[aName.getLength() - 1])))
+ {
+ aName = aName.copy(0, aName.getLength() - 1);
+ }
+
+ xmlChar* pFontFamilyGeneric = xmlGetProp(pXmlNode, BAD_CAST("font-family-generic"));
+ OString aFontFamilyGeneric;
+ if (pFontFamilyGeneric)
+ {
+ aFontFamilyGeneric = OString(reinterpret_cast<char const*>(pFontFamilyGeneric));
+ }
+
+ aXMLFonts.push_back(XmlFont{ aName, aFontFamilyGeneric });
+ aSortedFonts.push_back(XmlFont{ aName, aFontFamilyGeneric });
+ xmlFree(pName);
+ }
+ std::sort(aSortedFonts.begin(), aSortedFonts.end());
+ size_t nIndex = 0;
+ for (const auto& rFont : aSortedFonts)
+ {
+ // Without the accompanying fix in place, this test would have failed with:
+ // - Expected: Liberation Sans
+ // - Actual : Lucida Sans1
+ // i.e. the output was not lexicographically sorted, "u" was before "i".
+ CPPUNIT_ASSERT_EQUAL(rFont.aName, aXMLFonts[nIndex].aName);
+ // Without the accompanying fix in place, this test would have failed with:
+ // - Expected: swiss
+ // - Actual : system
+ // i.e. the output was not lexicographically sorted when style:name was the same, but
+ // style:font-family-generic was not the same.
+ CPPUNIT_ASSERT_EQUAL(rFont.aFontFamilyGeneric, aXMLFonts[nIndex].aFontFamilyGeneric);
+ ++nIndex;
+ }
+ xmlXPathFreeObject(pXPath);
+}
+
+CPPUNIT_TEST_FIXTURE(XmloffStyleTest, testRtlGutter)
+{
+ // Given a document with a gutter margin and an RTL writing mode:
+ // When loading that document from ODF:
+ loadFromFile(u"rtl-gutter.fodt");
+
+ // Then make sure the page style's RtlGutter property is true.
+ uno::Reference<style::XStyleFamiliesSupplier> xStyleFamiliesSupplier(mxComponent,
+ uno::UNO_QUERY);
+ uno::Reference<container::XNameAccess> xStyleFamilies
+ = xStyleFamiliesSupplier->getStyleFamilies();
+ uno::Reference<container::XNameAccess> xStyleFamily(xStyleFamilies->getByName("PageStyles"),
+ uno::UNO_QUERY);
+ uno::Reference<beans::XPropertySet> xStandard(xStyleFamily->getByName("Standard"),
+ uno::UNO_QUERY);
+ bool bRtlGutter{};
+ xStandard->getPropertyValue("RtlGutter") >>= bRtlGutter;
+ // Without the accompanying fix in place, this test would have failed as
+ // <style:page-layout-properties>'s style:writing-mode="..." did not affect RtlGutter.
+ CPPUNIT_ASSERT(bRtlGutter);
+}
+
+CPPUNIT_TEST_FIXTURE(XmloffStyleTest, testWritingModeBTLR)
+{
+ // Load document. It has a frame style with writing-mode bt-lr.
+ // In ODF 1.3 extended it is written as loext:writing-mode="bt-lr".
+ // In ODF 1.3 strict, there must not be an attribute at all.
+ loadFromFile(u"tdf150407_WritingModeBTLR_style.odt");
+
+ Resetter _([]() {
+ std::shared_ptr<comphelper::ConfigurationChanges> pBatch(
+ comphelper::ConfigurationChanges::create());
+ officecfg::Office::Common::Save::ODF::DefaultVersion::set(3, pBatch);
+ return pBatch->commit();
+ });
+
+ // Save to ODF 1.3 extended. Adapt 3 (=ODFVER_LATEST) to a to be ODFVER_013_EXTENDED when
+ // attribute value "bt-lr" is included in ODF strict.
+ {
+ std::shared_ptr<comphelper::ConfigurationChanges> pBatch(
+ comphelper::ConfigurationChanges::create());
+ officecfg::Office::Common::Save::ODF::DefaultVersion::set(3, pBatch);
+ pBatch->commit();
+ save("writer8");
+
+ // With applied fix for tdf150407 still loext:writing-mode="bt-lr" has to be written.
+ xmlDocUniquePtr pXmlDoc = parseExport("styles.xml");
+ assertXPath(pXmlDoc,
+ "/office:document-styles/office:styles/style:style[@style:name='FrameBTLR']/"
+ "style:graphic-properties[@loext:writing-mode]"_ostr);
+ assertXPath(pXmlDoc,
+ "/office:document-styles/office:styles/style:style[@style:name='FrameBTLR']/"
+ "style:graphic-properties"_ostr,
+ "writing-mode"_ostr, "bt-lr");
+ }
+
+ loadFromFile(u"tdf150407_WritingModeBTLR_style.odt");
+ // Save to ODF 1.3 strict.
+ {
+ std::shared_ptr<comphelper::ConfigurationChanges> pBatch(
+ comphelper::ConfigurationChanges::create());
+ officecfg::Office::Common::Save::ODF::DefaultVersion::set(10, pBatch);
+ pBatch->commit();
+ save("writer8");
+
+ // Without the fix an faulty 'writing-mode="bt-lr"' attribute was written in productive build.
+ // A debug build fails assertion in SvXMLNamespaceMap::GetQNameByKey().
+ xmlDocUniquePtr pXmlDoc = parseExport("styles.xml");
+ assertXPathNoAttribute(pXmlDoc,
+ "/office:document-styles/office:styles/"
+ "style:style[@style:name='FrameBTLR']/style:graphic-properties"_ostr,
+ "writing-mode"_ostr);
+ }
+}
+
+CPPUNIT_TEST_FIXTURE(XmloffStyleTest, testPosRelBottomMargin)
+{
+ // Load document. It has a frame position with vertical position relative to bottom margin.
+ // In ODF 1.3 extended it is written as loext:vertical-rel="page-content-bottom".
+ // In ODF 1.3 strict, there must not be an attribute at all.
+ loadFromFile(u"tdf150407_PosRelBottomMargin.docx");
+
+ Resetter _([]() {
+ std::shared_ptr<comphelper::ConfigurationChanges> pBatch(
+ comphelper::ConfigurationChanges::create());
+ officecfg::Office::Common::Save::ODF::DefaultVersion::set(3, pBatch);
+ return pBatch->commit();
+ });
+
+ // Save to ODF 1.3 extended. Adapt 3 (=ODFVER_LATEST) to a to be ODFVER_013_EXTENDED when
+ // attribute value "page-content-bottom" is included in ODF strict.
+ {
+ std::shared_ptr<comphelper::ConfigurationChanges> pBatch(
+ comphelper::ConfigurationChanges::create());
+ officecfg::Office::Common::Save::ODF::DefaultVersion::set(3, pBatch);
+ pBatch->commit();
+ save("writer8");
+
+ // With applied fix for tdf150407 still loext:vertical-rel="page-content-bottom" has to be
+ // written.
+ xmlDocUniquePtr pXmlDoc = parseExport("content.xml");
+ assertXPath(
+ pXmlDoc,
+ "/office:document-content/office:automatic-styles/style:style[@style:name='gr1']/"
+ "style:graphic-properties[@loext:vertical-rel]"_ostr);
+ assertXPath(
+ pXmlDoc,
+ "/office:document-content/office:automatic-styles/style:style[@style:name='gr1']/"
+ "style:graphic-properties"_ostr,
+ "vertical-rel"_ostr, "page-content-bottom");
+ }
+
+ loadFromFile(u"tdf150407_PosRelBottomMargin.docx");
+ // Save to ODF 1.3 strict.
+ {
+ std::shared_ptr<comphelper::ConfigurationChanges> pBatch(
+ comphelper::ConfigurationChanges::create());
+ officecfg::Office::Common::Save::ODF::DefaultVersion::set(10, pBatch);
+ pBatch->commit();
+ save("writer8");
+
+ // Without the fix an faulty 'vertical-rel="page-content-bottom"' attribute was written in
+ // productive build. A debug build fails assertion in SvXMLNamespaceMap::GetQNameByKey().
+ xmlDocUniquePtr pXmlDoc = parseExport("content.xml");
+ assertXPathNoAttribute(pXmlDoc,
+ "/office:document-content/office:automatic-styles/"
+ "style:style[@style:name='gr1']/style:graphic-properties"_ostr,
+ "vertical-rel"_ostr);
+ }
+}
+
+CPPUNIT_TEST_FIXTURE(XmloffStyleTest, testPosRelTopMargin)
+{
+ // Load document. It has a frame position with vertical position relative to top margin.
+ // In ODF 1.3 extended it is written as loext:vertical-rel="page-content-top".
+ // In ODF 1.3 strict, there must not be an attribute at all.
+ loadFromFile(u"tdf150407_PosRelTopMargin.docx");
+
+ Resetter _([]() {
+ std::shared_ptr<comphelper::ConfigurationChanges> pBatch(
+ comphelper::ConfigurationChanges::create());
+ officecfg::Office::Common::Save::ODF::DefaultVersion::set(3, pBatch);
+ return pBatch->commit();
+ });
+
+ // Save to ODF 1.3 extended. Adapt 3 (=ODFVER_LATEST) to a to be ODFVER_013_EXTENDED when
+ // attribute value "page-content-top" is included in ODF strict.
+ {
+ std::shared_ptr<comphelper::ConfigurationChanges> pBatch(
+ comphelper::ConfigurationChanges::create());
+ officecfg::Office::Common::Save::ODF::DefaultVersion::set(3, pBatch);
+ pBatch->commit();
+ save("writer8");
+
+ // With applied fix for tdf150407 still loext:vertical-rel="page-content-top has to be
+ // written.
+ xmlDocUniquePtr pXmlDoc = parseExport("content.xml");
+ assertXPath(
+ pXmlDoc,
+ "/office:document-content/office:automatic-styles/style:style[@style:name='gr1']/"
+ "style:graphic-properties[@loext:vertical-rel]"_ostr);
+ assertXPath(
+ pXmlDoc,
+ "/office:document-content/office:automatic-styles/style:style[@style:name='gr1']/"
+ "style:graphic-properties"_ostr,
+ "vertical-rel"_ostr, "page-content-top");
+ }
+
+ loadFromFile(u"tdf150407_PosRelTopMargin.docx");
+ // Save to ODF 1.3 strict.
+ {
+ std::shared_ptr<comphelper::ConfigurationChanges> pBatch(
+ comphelper::ConfigurationChanges::create());
+ officecfg::Office::Common::Save::ODF::DefaultVersion::set(10, pBatch);
+ pBatch->commit();
+ save("writer8");
+
+ // Without the fix an faulty 'vertical-rel="page-content-top"' attribute was written in
+ // productive build. A debug build fails assertion in SvXMLNamespaceMap::GetQNameByKey().
+ xmlDocUniquePtr pXmlDoc = parseExport("content.xml");
+ assertXPathNoAttribute(pXmlDoc,
+ "/office:document-content/office:automatic-styles/"
+ "style:style[@style:name='gr1']/style:graphic-properties"_ostr,
+ "vertical-rel"_ostr);
+ }
+}
+
+CPPUNIT_TEST_FIXTURE(XmloffStyleTest, testMCGR_OldToNew)
+{
+ // The file contains a shape with linear gradient fill from red #ff0000 to yellow #ffff00,
+ // named 'red2yellow'
+ loadFromFile(u"MCGR_OldToNew.odg");
+
+ // saveAndReload includes validation and must not fail with the new elements and attributes.
+ saveAndReload("draw8");
+
+ // Examine file markup
+ // For compatibility the file should still have the old attributes 'start-color' and 'end-color'
+ xmlDocUniquePtr pXmlDoc = parseExport("styles.xml");
+ OString sPath
+ = "/office:document-styles/office:styles/draw:gradient[@draw:name='red2yellow']"_ostr;
+ assertXPath(pXmlDoc, sPath, "start-color"_ostr, "#ff0000");
+ assertXPath(pXmlDoc, sPath, "end-color"_ostr, "#ffff00");
+
+ // And it must have the new 'gradient-stop' elements.
+ // The prefix 'loext' needs to be adapted, when the element is available in ODF strict.
+ assertXPath(pXmlDoc, sPath + "/loext:gradient-stop[1]", "offset"_ostr, "0");
+ assertXPath(pXmlDoc, sPath + "/loext:gradient-stop[1]", "color-type"_ostr, "rgb");
+ assertXPath(pXmlDoc, sPath + "/loext:gradient-stop[1]", "color-value"_ostr, "#ff0000");
+ assertXPath(pXmlDoc, sPath + "/loext:gradient-stop[2]", "offset"_ostr, "1");
+ assertXPath(pXmlDoc, sPath + "/loext:gradient-stop[2]", "color-type"_ostr, "rgb");
+ assertXPath(pXmlDoc, sPath + "/loext:gradient-stop[2]", "color-value"_ostr, "#ffff00");
+
+ // Examine reloaded file
+ uno::Reference<drawing::XShape> xShape(getShape(0));
+ CPPUNIT_ASSERT(xShape.is());
+ uno::Reference<beans::XPropertySet> xShapeProperties(xShape, uno::UNO_QUERY);
+
+ // The old properties need to be still available, as they might be used in macros.
+ OUString sGradientName;
+ xShapeProperties->getPropertyValue("FillGradientName") >>= sGradientName;
+ CPPUNIT_ASSERT_EQUAL(u"red2yellow"_ustr, sGradientName);
+ awt::Gradient2 aGradient;
+ xShapeProperties->getPropertyValue("FillGradient") >>= aGradient;
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(0xFF0000), aGradient.StartColor);
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(0xFFFF00), aGradient.EndColor);
+
+ // Test new properties
+ auto aColorStopSeq = aGradient.ColorStops;
+ {
+ awt::ColorStop aColorStop = aColorStopSeq[0];
+ CPPUNIT_ASSERT_EQUAL(0.0, aColorStop.StopOffset);
+ CPPUNIT_ASSERT_EQUAL(Color(0xff0000), asColor(aColorStop.StopColor));
+ }
+ {
+ awt::ColorStop aColorStop = aColorStopSeq[1];
+ CPPUNIT_ASSERT_EQUAL(1.0, aColorStop.StopOffset);
+ CPPUNIT_ASSERT_EQUAL(Color(0xffff00), asColor(aColorStop.StopColor));
+ }
+}
+
+CPPUNIT_TEST_FIXTURE(XmloffStyleTest, testMCGR_OldToNew_opacity)
+{
+ // The file contains a shape with solid fill and a radial transparency gradient with start 90%,
+ // end 0%, border 20% and center at 50%|50%. There is only one draw:opacity element in file.
+ loadFromFile(u"MCGR_OldToNew_opacity.odg");
+
+ // saveAndReload includes validation and must not fail with the new elements and attributes.
+ saveAndReload("draw8");
+
+ // Examine file markup
+ // For compatibility the file should still have the old attributes.
+ xmlDocUniquePtr pXmlDoc = parseExport("styles.xml");
+ OString sPath = "/office:document-styles/office:styles/draw:opacity"_ostr;
+ assertXPath(pXmlDoc, sPath, "start"_ostr, "10%"); // UI 90% transparency
+ assertXPath(pXmlDoc, sPath, "end"_ostr, "100%"); // UI 0% transparency
+ assertXPath(pXmlDoc, sPath, "border"_ostr, "20%");
+ assertXPath(pXmlDoc, sPath, "cx"_ostr, "50%");
+ assertXPath(pXmlDoc, sPath, "cy"_ostr, "50%");
+ assertXPath(pXmlDoc, sPath, "style"_ostr, "radial");
+
+ // And it must have the new 'opacity-stop' elements.
+ // The prefix 'loext' needs to be adapted, when the element is available in ODF strict.
+ OString sFirstStop = sPath + "/loext:opacity-stop[1]";
+ assertXPath(pXmlDoc, sFirstStop, "offset"_ostr, "0");
+ // Because of converting through color, the grade of opacity is not exact "0.1"
+ double fOpacity = getXPathContent(pXmlDoc, sFirstStop + "/@svg:stop-opacity").toDouble();
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(0.1, fOpacity, 0.002);
+
+ assertXPath(pXmlDoc, sPath + "/loext:opacity-stop[2]", "offset"_ostr, "1");
+ assertXPath(pXmlDoc, sPath + "/loext:opacity-stop[2]", "stop-opacity"_ostr, "1");
+
+ // Examine reloaded file
+ uno::Reference<drawing::XShape> xShape(getShape(0));
+ CPPUNIT_ASSERT(xShape.is());
+ uno::Reference<beans::XPropertySet> xShapeProperties(xShape, uno::UNO_QUERY);
+
+ // The old properties need to be still available, as they might be used in macros.
+ awt::Gradient2 aGradient;
+ xShapeProperties->getPropertyValue("FillTransparenceGradient") >>= aGradient;
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(0xE5E5E5), aGradient.StartColor);
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(0), aGradient.EndColor);
+ CPPUNIT_ASSERT_EQUAL(sal_Int16(20), aGradient.Border);
+ CPPUNIT_ASSERT_EQUAL(sal_Int16(50), aGradient.XOffset);
+ CPPUNIT_ASSERT_EQUAL(sal_Int16(50), aGradient.YOffset);
+ CPPUNIT_ASSERT_EQUAL(awt::GradientStyle_RADIAL, aGradient.Style);
+
+ // Test new properties
+ auto aColorStopSeq = aGradient.ColorStops;
+ {
+ awt::ColorStop aColorStop = aColorStopSeq[0];
+ CPPUNIT_ASSERT_EQUAL(0.0, aColorStop.StopOffset);
+ CPPUNIT_ASSERT_EQUAL(Color(0xe5e5e5), asColor(aColorStop.StopColor));
+ }
+ {
+ awt::ColorStop aColorStop = aColorStopSeq[1];
+ CPPUNIT_ASSERT_EQUAL(1.0, aColorStop.StopOffset);
+ CPPUNIT_ASSERT_EQUAL(Color(0x000000), asColor(aColorStop.StopColor));
+ }
+}
+
+CPPUNIT_TEST_FIXTURE(XmloffStyleTest, testMCGR_threeStops)
+{
+ // The file contains a shape with square gradient fill from red #ff0000 over teal #0099bb to
+ // yellow #ffff00, named 'threeStops'. It has 45deg rotation, center 0%|50%, border 10%.
+ loadFromFile(u"MCGR_threeStops.fodt");
+
+ // saveAndReload includes validation and must not fail with the new elements and attributes.
+ saveAndReload("draw8");
+
+ // Examine file markup
+ // For compatibility the file should still have the old attributes 'start-color' and 'end-color'
+ xmlDocUniquePtr pXmlDoc = parseExport("styles.xml");
+ OString sPath
+ = "/office:document-styles/office:styles/draw:gradient[@draw:name='threeStops']"_ostr;
+ assertXPath(pXmlDoc, sPath, "start-color"_ostr, "#ff0000");
+ assertXPath(pXmlDoc, sPath, "end-color"_ostr, "#ffff00");
+ assertXPath(pXmlDoc, sPath, "style"_ostr, "square");
+ assertXPath(pXmlDoc, sPath, "cx"_ostr, "0%");
+ assertXPath(pXmlDoc, sPath, "cy"_ostr, "50%");
+ assertXPath(pXmlDoc, sPath, "angle"_ostr, "45deg");
+ assertXPath(pXmlDoc, sPath, "border"_ostr, "10%");
+
+ // And it must have the new 'gradient-stop' elements.
+ // The prefix 'loext' needs to be adapted, when the element is available in ODF strict.
+ assertXPath(pXmlDoc, sPath + "/loext:gradient-stop[1]", "offset"_ostr, "0");
+ assertXPath(pXmlDoc, sPath + "/loext:gradient-stop[1]", "color-type"_ostr, "rgb");
+ assertXPath(pXmlDoc, sPath + "/loext:gradient-stop[1]", "color-value"_ostr, "#ff0000");
+ assertXPath(pXmlDoc, sPath + "/loext:gradient-stop[2]", "offset"_ostr, "0.3");
+ assertXPath(pXmlDoc, sPath + "/loext:gradient-stop[2]", "color-type"_ostr, "rgb");
+ assertXPath(pXmlDoc, sPath + "/loext:gradient-stop[2]", "color-value"_ostr, "#0099bb");
+ assertXPath(pXmlDoc, sPath + "/loext:gradient-stop[3]", "offset"_ostr, "1");
+ assertXPath(pXmlDoc, sPath + "/loext:gradient-stop[3]", "color-type"_ostr, "rgb");
+ assertXPath(pXmlDoc, sPath + "/loext:gradient-stop[3]", "color-value"_ostr, "#ffff00");
+
+ // Examine reloaded file
+ uno::Reference<drawing::XShape> xShape(getShape(0));
+ CPPUNIT_ASSERT(xShape.is());
+ uno::Reference<beans::XPropertySet> xShapeProperties(xShape, uno::UNO_QUERY);
+
+ // The old properties need to be still available, as they might be used in macros.
+ OUString sGradientName;
+ xShapeProperties->getPropertyValue("FillGradientName") >>= sGradientName;
+ CPPUNIT_ASSERT_EQUAL(u"threeStops"_ustr, sGradientName);
+ awt::Gradient2 aGradient;
+ xShapeProperties->getPropertyValue("FillGradient") >>= aGradient;
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(0xFF0000), aGradient.StartColor);
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(0xFFFF00), aGradient.EndColor);
+ CPPUNIT_ASSERT_EQUAL(awt::GradientStyle_SQUARE, aGradient.Style);
+ CPPUNIT_ASSERT_EQUAL(sal_Int16(0), aGradient.XOffset);
+ CPPUNIT_ASSERT_EQUAL(sal_Int16(50), aGradient.YOffset);
+ CPPUNIT_ASSERT_EQUAL(sal_Int16(450), aGradient.Angle);
+ CPPUNIT_ASSERT_EQUAL(sal_Int16(10), aGradient.Border);
+
+ // Test new properties
+ auto aColorStopSeq = aGradient.ColorStops;
+ {
+ awt::ColorStop aColorStop = aColorStopSeq[0];
+ CPPUNIT_ASSERT_EQUAL(0.0, aColorStop.StopOffset);
+ CPPUNIT_ASSERT_EQUAL(Color(0xff0000), asColor(aColorStop.StopColor));
+ }
+ {
+ awt::ColorStop aColorStop = aColorStopSeq[1];
+ CPPUNIT_ASSERT_EQUAL(0.3, aColorStop.StopOffset);
+ CPPUNIT_ASSERT_EQUAL(Color(0x0099bb), asColor(aColorStop.StopColor));
+ }
+ {
+ awt::ColorStop aColorStop = aColorStopSeq[2];
+ CPPUNIT_ASSERT_EQUAL(1.0, aColorStop.StopOffset);
+ CPPUNIT_ASSERT_EQUAL(Color(0xffff00), asColor(aColorStop.StopColor));
+ }
+}
+
+CPPUNIT_TEST_FIXTURE(XmloffStyleTest, testBorderRestoration)
+{
+ // Load document. It has a shape with color gradient build from color stop yellow at offset 0.5
+ // and color stop red at offset 1.0. For better backward compatibility such gradient has to be
+ // exported to ODF with a border of 50%.
+ // When gradient-stops are integrated in ODF strict, the test needs to be adapted.
+
+ loadFromFile(u"MCGR_Border_restoration.pptx");
+
+ // Backup original ODF default version
+ const SvtSaveOptions::ODFDefaultVersion nCurrentODFVersion(GetODFDefaultVersion());
+
+ // Save to ODF_LATEST which is currently ODF 1.3 extended. Make sure gradient-stop elements have
+ // offsets 0 and 1, and border is written as 50%.
+ SetODFDefaultVersion(SvtSaveOptions::ODFDefaultVersion::ODFVER_LATEST);
+ save("impress8");
+ xmlDocUniquePtr pXmlDoc = parseExport("styles.xml");
+ OString sPath
+ = "/office:document-styles/office:styles/draw:gradient[@draw:name='Gradient_20_1']"_ostr;
+ assertXPath(pXmlDoc, sPath + "/loext:gradient-stop[2]", "color-value"_ostr, "#ff0000");
+ assertXPath(pXmlDoc, sPath + "/loext:gradient-stop[2]", "offset"_ostr, "1");
+ assertXPath(pXmlDoc, sPath + "/loext:gradient-stop[1]", "color-value"_ostr, "#ffff00");
+ assertXPath(pXmlDoc, sPath + "/loext:gradient-stop[1]", "offset"_ostr, "0");
+ assertXPath(pXmlDoc, sPath, "border"_ostr, "50%");
+
+ // Save to ODF 1.3 strict and make sure border, start-color and end-color are suitable set.
+ SetODFDefaultVersion(SvtSaveOptions::ODFDefaultVersion::ODFVER_013);
+ save("impress8");
+ pXmlDoc = parseExport("styles.xml");
+ assertXPath(pXmlDoc, sPath + "/loext:gradient-stop", 0);
+ assertXPath(pXmlDoc, sPath, "start-color"_ostr, "#ffff00");
+ assertXPath(pXmlDoc, sPath, "end-color"_ostr, "#ff0000");
+ assertXPath(pXmlDoc, sPath, "border"_ostr, "50%");
+
+ // Set back to original ODF default version.
+ SetODFDefaultVersion(nCurrentODFVersion);
+}
+
+CPPUNIT_TEST_FIXTURE(XmloffStyleTest, testTransparencyBorderRestoration)
+{
+ // Load document. It has a shape with transparency gradient build from transparency 100% at
+ // offset 0, transparency 100% at offset 0.4 and transparency 10% at offset 1.0. For better
+ // backward compatibility such gradient is exported with a border of 40% in the transparency
+ // gradient. The color itself is the same for all gradient stops.
+ // When transparency gradient-stops are integrated in ODF strict, the test needs to be adapted.
+ loadFromFile(u"MCGR_TransparencyBorder_restoration.pptx");
+
+ // Backup original ODF default version
+ const SvtSaveOptions::ODFDefaultVersion nCurrentODFVersion(GetODFDefaultVersion());
+
+ // Save to ODF_LATEST which is currently ODF 1.3 extended. Make sure transparency gradient-stop
+ //elements are written with offset 0 and 1, and border is written as 40%.
+ SetODFDefaultVersion(SvtSaveOptions::ODFDefaultVersion::ODFVER_LATEST);
+ save("impress8");
+ xmlDocUniquePtr pXmlDoc = parseExport("styles.xml");
+ OString sPath = "/office:document-styles/office:styles/draw:opacity[1]"_ostr;
+ assertXPath(pXmlDoc, sPath + "/loext:opacity-stop[2]", "stop-opacity"_ostr, "0.9");
+ assertXPath(pXmlDoc, sPath + "/loext:opacity-stop[2]", "offset"_ostr, "1");
+ assertXPath(pXmlDoc, sPath + "/loext:opacity-stop[1]", "stop-opacity"_ostr, "0");
+ assertXPath(pXmlDoc, sPath + "/loext:opacity-stop[1]", "offset"_ostr, "0");
+ assertXPath(pXmlDoc, sPath, "border"_ostr, "40%");
+
+ // Save to ODF 1.3 strict and make sure border, start and end opacity are suitable set.
+ SetODFDefaultVersion(SvtSaveOptions::ODFDefaultVersion::ODFVER_013);
+ save("impress8");
+ pXmlDoc = parseExport("styles.xml");
+ assertXPath(pXmlDoc, sPath + "/loext:opacity-stop", 0);
+ assertXPath(pXmlDoc, sPath, "start"_ostr, "0%");
+ assertXPath(pXmlDoc, sPath, "end"_ostr, "90%");
+ assertXPath(pXmlDoc, sPath, "border"_ostr, "40%");
+
+ // Set back to original ODF default version.
+ 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.
+ loadFromFile(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.
+ loadFromFile(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: */