diff options
author | Regina Henschel <rb.henschel@t-online.de> | 2023-03-06 16:45:35 +0100 |
---|---|---|
committer | Miklos Vajna <vmiklos@collabora.com> | 2023-03-09 08:25:51 +0000 |
commit | d95a09c1fca70d658207b8c48761af32dd2df213 (patch) | |
tree | 76c5828969b3309cc2298430e81d92b0f880ca63 /oox/qa | |
parent | 78b1631e9649402e29c906c7023f55ed2cbe84f9 (diff) |
tdf#51195 add docx export of gradient fill of Fontwork shapes
FillGradient, which is a awt::Gradient, has many features which cannot
be represented in the <w14:textFill> element in docx. Therefore often
only workarounds are possible.
ELLIPTICAL and RADIAL are exported to 'circle', SQUARE and RECT to
'rect'. 'Angle' is ignored. A focus point is used instead of a focus
line.
LINEAR and AXIAL are exported to 'lin'. AXIAL is done be compress and
mirroring the color stops. Using Words feature of reflecting a gradient
would prevent detecting 'axial' in the current import filter.
'Border' is exported by introducing additional color stops.
'StepCount' is ignored. A workaround using additional color stops is
possible, but would require a simultaneous change of the import filter.
'StartIntensity' and 'EndIntensity' are exported as 'lumMod'.
Theme colors are considered where they can currently occur. But
tdf#151882 is yet not fixed, so Word will not render them because of
missing Theme folder.
To allow 'lumMod' and theme color and RGB color as well, the color of
a color stop is hold in a struct.
In case of two color stops, the color stop at position 0% is doubled.
That way Word uses the same linear color transition as LO and not its
quadratic one. AXIAL too introduces two color stops at position 50%.
Emulating 'StepCount' would produce two color stops at same position
too. Therefore a std::multimap is used for the color stops.
The implementation has a lot local parts. If they should be useful
for Fontwork shapes in Impress/Draw, they can be moved and adapted
later. The implementation separates the calculation of the required
color stops from the generation of the markup, so using parts in
Impress/Draw is likely possible.
Change-Id: I1032ab8d37b6f112d66f85a30210ebda3ae54486
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/148354
Tested-by: Jenkins
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
Diffstat (limited to 'oox/qa')
-rw-r--r-- | oox/qa/unit/data/tdf51195_Fontwork_axialGradient.odt | bin | 0 -> 17469 bytes | |||
-rw-r--r-- | oox/qa/unit/data/tdf51195_Fontwork_ellipticalGradient.odt | bin | 0 -> 48423 bytes | |||
-rw-r--r-- | oox/qa/unit/data/tdf51195_Fontwork_linearGradient.odt | bin | 0 -> 24421 bytes | |||
-rw-r--r-- | oox/qa/unit/data/tdf51195_Fontwork_radialGradient.odt | bin | 0 -> 21169 bytes | |||
-rw-r--r-- | oox/qa/unit/data/tdf51195_Fontwork_rectGradient.odt | bin | 0 -> 17882 bytes | |||
-rw-r--r-- | oox/qa/unit/data/tdf51195_Fontwork_squareGradient.odt | bin | 0 -> 20571 bytes | |||
-rw-r--r-- | oox/qa/unit/export.cxx | 282 |
7 files changed, 282 insertions, 0 deletions
diff --git a/oox/qa/unit/data/tdf51195_Fontwork_axialGradient.odt b/oox/qa/unit/data/tdf51195_Fontwork_axialGradient.odt Binary files differnew file mode 100644 index 000000000000..99fe4d8a49da --- /dev/null +++ b/oox/qa/unit/data/tdf51195_Fontwork_axialGradient.odt diff --git a/oox/qa/unit/data/tdf51195_Fontwork_ellipticalGradient.odt b/oox/qa/unit/data/tdf51195_Fontwork_ellipticalGradient.odt Binary files differnew file mode 100644 index 000000000000..c036e13953c6 --- /dev/null +++ b/oox/qa/unit/data/tdf51195_Fontwork_ellipticalGradient.odt diff --git a/oox/qa/unit/data/tdf51195_Fontwork_linearGradient.odt b/oox/qa/unit/data/tdf51195_Fontwork_linearGradient.odt Binary files differnew file mode 100644 index 000000000000..bc821db884d9 --- /dev/null +++ b/oox/qa/unit/data/tdf51195_Fontwork_linearGradient.odt diff --git a/oox/qa/unit/data/tdf51195_Fontwork_radialGradient.odt b/oox/qa/unit/data/tdf51195_Fontwork_radialGradient.odt Binary files differnew file mode 100644 index 000000000000..746b60b4d31d --- /dev/null +++ b/oox/qa/unit/data/tdf51195_Fontwork_radialGradient.odt diff --git a/oox/qa/unit/data/tdf51195_Fontwork_rectGradient.odt b/oox/qa/unit/data/tdf51195_Fontwork_rectGradient.odt Binary files differnew file mode 100644 index 000000000000..d4daee9632d6 --- /dev/null +++ b/oox/qa/unit/data/tdf51195_Fontwork_rectGradient.odt diff --git a/oox/qa/unit/data/tdf51195_Fontwork_squareGradient.odt b/oox/qa/unit/data/tdf51195_Fontwork_squareGradient.odt Binary files differnew file mode 100644 index 000000000000..86e71ba6ae41 --- /dev/null +++ b/oox/qa/unit/data/tdf51195_Fontwork_squareGradient.odt diff --git a/oox/qa/unit/export.cxx b/oox/qa/unit/export.cxx index 645ffe386dd1..5a90126bfbe1 100644 --- a/oox/qa/unit/export.cxx +++ b/oox/qa/unit/export.cxx @@ -1015,6 +1015,288 @@ CPPUNIT_TEST_FIXTURE(Test, testFontworkDistance) "wp:anchor/a:graphic/a:graphicData/wps:wsp/wps:bodyPr", { { "lIns", "0" }, { "rIns", "0" }, { "tIns", "0" }, { "bIns", "0" } }); } + +CPPUNIT_TEST_FIXTURE(Test, testFontworkLinGradientRGBColor) +{ + // The document has a Fontwork shape with UI settings: linear gradient fill with angle 330deg, + // start color #ffff00 (Yellow) with 'Brightness' 80%, end color #4682B4 (Steel Blue), Transition + // Start 25% and solid transparency 30%. + // Without fix the gradient was not exported at all. + loadFromURL(u"tdf51195_Fontwork_linearGradient.odt"); + + // FIXME: tdf#153183 validation error in OOXML export: Errors: 1 + // Attribute 'ID' is not allowed to appear in element 'v:shape'. + skipValidation(); + + // Save to DOCX: + save("Office Open XML Text"); + + // Examine the saved markup. + xmlDocUniquePtr pXmlDoc = parseExport("word/document.xml"); + + // path to shape text run properties + OString sElement = "/w:document/w:body/w:p/w:r/mc:AlternateContent/mc:Choice/w:drawing/" + "wp:anchor/a:graphic/a:graphicData/wps:wsp/wps:txbx/w:txbxContent/w:p/w:r/" + "w:rPr/"; + + // Make sure w14:textFill and w14:gradFill elements exist with child elements + assertXPath(pXmlDoc, sElement + "w14:textFill/w14:gradFill/w14:gsLst", 1); + assertXPath(pXmlDoc, sElement + "w14:textFill/w14:gradFill/w14:gsLst/w14:gs", 3); + // 330deg gradient rotation = 120deg color transition direction + assertXPath(pXmlDoc, sElement + "w14:textFill/w14:gradFill/w14:lin", "ang", "7200000"); + assertXPath(pXmlDoc, sElement + "w14:textFill/w14:gradFill/w14:lin", "scaled", "0"); + + // Make sure the color stops have correct position and color + sElement += "w14:textFill/w14:gradFill/w14:gsLst/"; + assertXPath(pXmlDoc, sElement + "w14:gs[1]", "pos", "0"); + assertXPath(pXmlDoc, sElement + "w14:gs[1]/w14:srgbClr", "val", "ffff00"); + assertXPath(pXmlDoc, sElement + "w14:gs[1]/w14:srgbClr/w14:lumMod", "val", "80000"); + assertXPath(pXmlDoc, sElement + "w14:gs[1]/w14:srgbClr/w14:alpha", "val", "30000"); + + assertXPath(pXmlDoc, sElement + "w14:gs[2]", "pos", "25000"); + assertXPath(pXmlDoc, sElement + "w14:gs[2]/w14:srgbClr", "val", "ffff00"); + assertXPath(pXmlDoc, sElement + "w14:gs[2]/w14:srgbClr/w14:lumMod", "val", "80000"); + assertXPath(pXmlDoc, sElement + "w14:gs[2]/w14:srgbClr/w14:alpha", "val", "30000"); + + assertXPath(pXmlDoc, sElement + "w14:gs[3]", "pos", "100000"); + assertXPath(pXmlDoc, sElement + "w14:gs[3]/w14:srgbClr", "val", "4682b4"); + assertXPath(pXmlDoc, sElement + "w14:gs[3]/w14:srgbClr/w14:alpha", "val", "30000"); +} + +CPPUNIT_TEST_FIXTURE(Test, testFontworkAxialGradientTransparency) +{ + // The document has a Fontwork shape with UI settings: solid fill theme color Accen3 25% darker, + // Transparency gradient Type Axial with Angle 160deg, Transition start 40%, + // Start value 5%, End value 90% + // Without fix the gradient was not exported at all. + loadFromURL(u"tdf51195_Fontwork_axialGradient.odt"); + + // FIXME: tdf#153183 validation error in OOXML export: Errors: 1 + // Attribute 'ID' is not allowed to appear in element 'v:shape'. + skipValidation(); + + // Save to DOCX: + save("Office Open XML Text"); + + // Examine the saved markup. + xmlDocUniquePtr pXmlDoc = parseExport("word/document.xml"); + + // path to shape text run properties + OString sElement = "/w:document/w:body/w:p/w:r/mc:AlternateContent/mc:Choice/w:drawing/" + "wp:anchor/a:graphic/a:graphicData/wps:wsp/wps:txbx/w:txbxContent/w:p/w:r/" + "w:rPr/"; + + // Make sure w14:textFill and w14:gradFill elements exist with child elements + assertXPath(pXmlDoc, sElement + "w14:textFill/w14:gradFill/w14:gsLst", 1); + assertXPath(pXmlDoc, sElement + "w14:textFill/w14:gradFill/w14:gsLst/w14:gs", 6); + // 160deg gradient rotation = 290deg (360deg-160deg+90deg) color transition direction + assertXPath(pXmlDoc, sElement + "w14:textFill/w14:gradFill/w14:lin", "ang", "17400000"); + assertXPath(pXmlDoc, sElement + "w14:textFill/w14:gradFill/w14:lin", "scaled", "0"); + + // Make sure the color stops have correct position and color + sElement += "w14:textFill/w14:gradFill/w14:gsLst/"; + // gradient is in transparency, color is always the same. + for (char ch = '1'; ch <= '6'; ++ch) + { + assertXPath(pXmlDoc, sElement + "w14:gs[" + OStringChar(ch) + "]/w14:schemeClr", "val", + "accent3"); + assertXPath(pXmlDoc, sElement + "w14:gs[" + OStringChar(ch) + "]/w14:schemeClr/w14:lumMod", + "val", "75000"); + } + // outer transparency + assertXPath(pXmlDoc, sElement + "w14:gs[1]", "pos", "0"); + assertXPath(pXmlDoc, sElement + "w14:gs[1]/w14:schemeClr/w14:alpha", "val", "90000"); + // border, same transparency + assertXPath(pXmlDoc, sElement + "w14:gs[2]", "pos", "20000"); + assertXPath(pXmlDoc, sElement + "w14:gs[2]/w14:schemeClr/w14:alpha", "val", "90000"); + // gradient to inner transparency at center + assertXPath(pXmlDoc, sElement + "w14:gs[3]", "pos", "50000"); + assertXPath(pXmlDoc, sElement + "w14:gs[3]/w14:schemeClr/w14:alpha", "val", "5000"); + // from inner transparency at center + assertXPath(pXmlDoc, sElement + "w14:gs[4]", "pos", "50000"); + assertXPath(pXmlDoc, sElement + "w14:gs[4]/w14:schemeClr/w14:alpha", "val", "5000"); + // mirrored gradient to outer transparency + assertXPath(pXmlDoc, sElement + "w14:gs[5]", "pos", "80000"); + assertXPath(pXmlDoc, sElement + "w14:gs[5]/w14:schemeClr/w14:alpha", "val", "90000"); + // mirrored border + assertXPath(pXmlDoc, sElement + "w14:gs[6]", "pos", "100000"); + assertXPath(pXmlDoc, sElement + "w14:gs[6]/w14:schemeClr/w14:alpha", "val", "90000"); +} + +CPPUNIT_TEST_FIXTURE(Test, testFontworkRadialGradient) +{ + // The document has a Fontwork shape with UI settings: gradient fill, Type radial, + // From Color #40E0D0, To Color #FF0000, Center x|y 75%|20%, no transparency + // Transition start 10% + // Without fix the gradient was not exported at all. + loadFromURL(u"tdf51195_Fontwork_radialGradient.odt"); + + // FIXME: tdf#153183 validation error in OOXML export: Errors: 1 + // Attribute 'ID' is not allowed to appear in element 'v:shape'. + skipValidation(); + + // Save to DOCX: + save("Office Open XML Text"); + + // Examine the saved markup. + xmlDocUniquePtr pXmlDoc = parseExport("word/document.xml"); + + // path to shape text run properties + OString sElement = "/w:document/w:body/w:p/w:r/mc:AlternateContent/mc:Choice/w:drawing/" + "wp:anchor/a:graphic/a:graphicData/wps:wsp/wps:txbx/w:txbxContent/w:p/w:r/" + "w:rPr/"; + + // Make sure w14:textFill and w14:gradFill elements exist with child elements + assertXPath(pXmlDoc, sElement + "w14:textFill/w14:gradFill/w14:gsLst", 1); + assertXPath(pXmlDoc, sElement + "w14:textFill/w14:gradFill/w14:gsLst/w14:gs", 3); + assertXPath(pXmlDoc, sElement + "w14:textFill/w14:gradFill/w14:path", "path", "circle"); + assertXPathAttrs(pXmlDoc, sElement + "w14:textFill/w14:gradFill/w14:path/w14:fillToRect", + { { "l", "75000" }, { "t", "20000" }, { "r", "25000" }, { "b", "80000" } }); + + // Make sure the color stops have correct position and color + sElement += "w14:textFill/w14:gradFill/w14:gsLst/"; + assertXPath(pXmlDoc, sElement + "w14:gs[1]", "pos", "0"); + assertXPath(pXmlDoc, sElement + "w14:gs[1]/w14:srgbClr", "val", "ff0000"); + assertXPath(pXmlDoc, sElement + "w14:gs[2]", "pos", "90000"); + assertXPath(pXmlDoc, sElement + "w14:gs[2]/w14:srgbClr", "val", "40e0d0"); + assertXPath(pXmlDoc, sElement + "w14:gs[3]", "pos", "100000"); + assertXPath(pXmlDoc, sElement + "w14:gs[3]/w14:srgbClr", "val", "40e0d0"); +} + +CPPUNIT_TEST_FIXTURE(Test, testFontworkEllipticalGradient) +{ + // The document has a Fontwork shape with UI settings: solid fill, Color #00008B (deep blue), + // transparency gradient type Ellipsoid, Center x|y 50%|50%, Transition Start 50%, + // Start 70%, End 0%. + // Without fix the gradient was not exported at all. + loadFromURL(u"tdf51195_Fontwork_ellipticalGradient.odt"); + + // FIXME: tdf#153183 validation error in OOXML export: Errors: 1 + // Attribute 'ID' is not allowed to appear in element 'v:shape'. + skipValidation(); + + // Save to DOCX: + save("Office Open XML Text"); + + // Examine the saved markup. + xmlDocUniquePtr pXmlDoc = parseExport("word/document.xml"); + + // path to shape text run properties + OString sElement = "/w:document/w:body/w:p/w:r/mc:AlternateContent/mc:Choice/w:drawing/" + "wp:anchor/a:graphic/a:graphicData/wps:wsp/wps:txbx/w:txbxContent/w:p/w:r/" + "w:rPr/"; + + // Make sure w14:textFill and w14:gradFill elements exist with child elements + assertXPath(pXmlDoc, sElement + "w14:textFill/w14:gradFill/w14:gsLst", 1); + assertXPath(pXmlDoc, sElement + "w14:textFill/w14:gradFill/w14:gsLst/w14:gs", 3); + assertXPath(pXmlDoc, sElement + "w14:textFill/w14:gradFill/w14:path", "path", "circle"); + assertXPathAttrs(pXmlDoc, sElement + "w14:textFill/w14:gradFill/w14:path/w14:fillToRect", + { { "l", "50000" }, { "t", "50000" }, { "r", "50000" }, { "b", "50000" } }); + + // Make sure the color stops have correct position and color + sElement += "w14:textFill/w14:gradFill/w14:gsLst/"; + assertXPath(pXmlDoc, sElement + "w14:gs[1]", "pos", "0"); + assertXPath(pXmlDoc, sElement + "w14:gs[1]/w14:srgbClr", "val", "00008b"); + assertXPath(pXmlDoc, sElement + "w14:gs[1]/w14:srgbClr/w14:alpha", 0); + assertXPath(pXmlDoc, sElement + "w14:gs[2]", "pos", "50000"); + assertXPath(pXmlDoc, sElement + "w14:gs[2]/w14:srgbClr", "val", "00008b"); + assertXPath(pXmlDoc, sElement + "w14:gs[2]/w14:srgbClr/w14:alpha", "val", "70000"); + assertXPath(pXmlDoc, sElement + "w14:gs[3]", "pos", "100000"); + assertXPath(pXmlDoc, sElement + "w14:gs[3]/w14:srgbClr", "val", "00008b"); + assertXPath(pXmlDoc, sElement + "w14:gs[3]/w14:srgbClr/w14:alpha", "val", "70000"); +} + +CPPUNIT_TEST_FIXTURE(Test, testFontworkSquareGradient) +{ + // The document has a Fontwork shape with UI settings: gradient fill Type "Quadratic" (which is + // "square" in ODF and API), From Color #4963ef 40%, To Color #ffff6e 90%, Center x|y 100%|50%, + // no transparency + // Without fix the gradient was not exported at all. + loadFromURL(u"tdf51195_Fontwork_squareGradient.odt"); + + // FIXME: tdf#153183 validation error in OOXML export: Errors: 1 + // Attribute 'ID' is not allowed to appear in element 'v:shape'. + skipValidation(); + + // Save to DOCX: + save("Office Open XML Text"); + + // Examine the saved markup. + xmlDocUniquePtr pXmlDoc = parseExport("word/document.xml"); + + // path to shape text run properties + OString sElement = "/w:document/w:body/w:p/w:r/mc:AlternateContent/mc:Choice/w:drawing/" + "wp:anchor/a:graphic/a:graphicData/wps:wsp/wps:txbx/w:txbxContent/w:p/w:r/" + "w:rPr/"; + + // Make sure w14:textFill and w14:gradFill elements exist with child elements + assertXPath(pXmlDoc, sElement + "w14:textFill/w14:gradFill/w14:gsLst", 1); + assertXPath(pXmlDoc, sElement + "w14:textFill/w14:gradFill/w14:gsLst/w14:gs", 3); + assertXPath(pXmlDoc, sElement + "w14:textFill/w14:gradFill/w14:path", "path", "rect"); + assertXPathAttrs(pXmlDoc, sElement + "w14:textFill/w14:gradFill/w14:path/w14:fillToRect", + { { "l", "100000" }, { "t", "50000" }, { "r", "0" }, { "b", "50000" } }); + + // Make sure the color stops have correct position and color + sElement += "w14:textFill/w14:gradFill/w14:gsLst/"; + assertXPath(pXmlDoc, sElement + "w14:gs[1]", "pos", "0"); + assertXPath(pXmlDoc, sElement + "w14:gs[1]/w14:srgbClr", "val", "ffff6e"); + assertXPath(pXmlDoc, sElement + "w14:gs[1]/w14:srgbClr/w14:lumMod", "val", "90000"); + assertXPath(pXmlDoc, sElement + "w14:gs[2]", "pos", "0"); + assertXPath(pXmlDoc, sElement + "w14:gs[2]/w14:srgbClr", "val", "ffff6e"); + assertXPath(pXmlDoc, sElement + "w14:gs[2]/w14:srgbClr/w14:lumMod", "val", "90000"); + assertXPath(pXmlDoc, sElement + "w14:gs[3]", "pos", "100000"); + assertXPath(pXmlDoc, sElement + "w14:gs[3]/w14:srgbClr", "val", "49b3ef"); + assertXPath(pXmlDoc, sElement + "w14:gs[3]/w14:srgbClr/w14:lumMod", "val", "40000"); +} + +CPPUNIT_TEST_FIXTURE(Test, testFontworkRectGradient) +{ + // The document has a Fontwork shape with UI settings: solid color theme Accent 4 60% lighter, + // transparency gradient Type "Square" (which is "rectangle" in ODF and API, tdf#154071), + // Center x|y 50%|50%, Transition start 10%, Start value 70%, End value 5%. + // Without fix the gradient was not exported at all. + loadFromURL(u"tdf51195_Fontwork_rectGradient.odt"); + + // FIXME: tdf#153183 validation error in OOXML export: Errors: 1 + // Attribute 'ID' is not allowed to appear in element 'v:shape'. + skipValidation(); + + // Save to DOCX: + save("Office Open XML Text"); + + // Examine the saved markup. + xmlDocUniquePtr pXmlDoc = parseExport("word/document.xml"); + + // path to shape text run properties + OString sElement = "/w:document/w:body/w:p/w:r/mc:AlternateContent/mc:Choice/w:drawing/" + "wp:anchor/a:graphic/a:graphicData/wps:wsp/wps:txbx/w:txbxContent/w:p/w:r/" + "w:rPr/"; + + // Make sure w14:textFill and w14:gradFill elements exist with child elements + assertXPath(pXmlDoc, sElement + "w14:textFill/w14:gradFill/w14:gsLst", 1); + assertXPath(pXmlDoc, sElement + "w14:textFill/w14:gradFill/w14:gsLst/w14:gs", 3); + assertXPath(pXmlDoc, sElement + "w14:textFill/w14:gradFill/w14:path", "path", "rect"); + assertXPathAttrs(pXmlDoc, sElement + "w14:textFill/w14:gradFill/w14:path/w14:fillToRect", + { { "l", "50000" }, { "t", "50000" }, { "r", "50000" }, { "b", "50000" } }); + + // Make sure the color stops have correct position and color + sElement += "w14:textFill/w14:gradFill/w14:gsLst/"; + assertXPath(pXmlDoc, sElement + "w14:gs[1]", "pos", "0"); + assertXPath(pXmlDoc, sElement + "w14:gs[1]/w14:schemeClr", "val", "accent4"); + assertXPath(pXmlDoc, sElement + "w14:gs[1]/w14:schemeClr/w14:lumMod", "val", "40000"); + assertXPath(pXmlDoc, sElement + "w14:gs[1]/w14:schemeClr/w14:lumOff", "val", "60000"); + assertXPath(pXmlDoc, sElement + "w14:gs[1]/w14:schemeClr/w14:alpha", "val", "5000"); + assertXPath(pXmlDoc, sElement + "w14:gs[2]", "pos", "90000"); + assertXPath(pXmlDoc, sElement + "w14:gs[2]/w14:schemeClr", "val", "accent4"); + assertXPath(pXmlDoc, sElement + "w14:gs[2]/w14:schemeClr/w14:lumMod", "val", "40000"); + assertXPath(pXmlDoc, sElement + "w14:gs[2]/w14:schemeClr/w14:lumOff", "val", "60000"); + assertXPath(pXmlDoc, sElement + "w14:gs[2]/w14:schemeClr/w14:alpha", "val", "70000"); + assertXPath(pXmlDoc, sElement + "w14:gs[3]", "pos", "100000"); + assertXPath(pXmlDoc, sElement + "w14:gs[3]/w14:schemeClr", "val", "accent4"); + assertXPath(pXmlDoc, sElement + "w14:gs[3]/w14:schemeClr/w14:lumMod", "val", "40000"); + assertXPath(pXmlDoc, sElement + "w14:gs[3]/w14:schemeClr/w14:lumOff", "val", "60000"); + assertXPath(pXmlDoc, sElement + "w14:gs[3]/w14:schemeClr/w14:alpha", "val", "70000"); +} } CPPUNIT_PLUGIN_IMPLEMENT(); |