From 0028c2311ca14669ca530cd4db422cd3cf9438ca Mon Sep 17 00:00:00 2001 From: Regina Henschel Date: Sat, 10 Jun 2023 19:09:34 +0200 Subject: [PATCH] MCGR: Use BGradient in export of Fontwork to docx too Transparency values are not exactly like in UI because converting through rgb-color adds rounding inaccuracy. The unit tests are adjusted accordingly. With only start and end values it was possible to use the UI values directly. It would be possible to make special cases for front and back value, but I think it is not worth the effort. The previous solution had the error, that the stops were not mirrored in case of non linear gradient. That is corrected now. The unit tests are adjusted. The previous solution had assumed that our 'intensity' at start or end colors is the same as the 'lumMod' attribute in OOXML. However, this is not the case. So now the 'intensity' is incorporated into the color. Again, the unit tests are adjusted. Change-Id: Id02e455dc09d12c5b453637fcb2bdc4f8f1529d1 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/152839 Tested-by: Jenkins Reviewed-by: Regina Henschel --- oox/CppunitTest_oox_mcgr.mk | 53 ++ oox/Module_oox.mk | 1 + .../unit/data/MCGR_FontworkColorGradient.fodp | 662 ++++++++++++++++++ .../unit/data/MCGR_FontworkColorGradient.fodt | 331 +++++++++ oox/qa/unit/export.cxx | 80 +-- oox/qa/unit/mcgr.cxx | 73 ++ oox/source/drawingml/fontworkhelpers.cxx | 316 +++------ 7 files changed, 1237 insertions(+), 279 deletions(-) create mode 100644 oox/CppunitTest_oox_mcgr.mk create mode 100644 oox/qa/unit/data/MCGR_FontworkColorGradient.fodp create mode 100644 oox/qa/unit/data/MCGR_FontworkColorGradient.fodt create mode 100644 oox/qa/unit/mcgr.cxx diff --git a/oox/CppunitTest_oox_mcgr.mk b/oox/CppunitTest_oox_mcgr.mk new file mode 100644 index 000000000000..ea7692e161fc --- /dev/null +++ b/oox/CppunitTest_oox_mcgr.mk @@ -0,0 +1,53 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +#************************************************************************* +# +# 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/. +# +#************************************************************************* + +$(eval $(call gb_CppunitTest_CppunitTest,oox_mcgr)) + +$(eval $(call gb_CppunitTest_use_externals,oox_mcgr,\ + boost_headers \ + libxml2 \ +)) + +$(eval $(call gb_CppunitTest_add_exception_objects,oox_mcgr, \ + oox/qa/unit/mcgr \ +)) + +$(eval $(call gb_CppunitTest_use_libraries,oox_mcgr, \ + comphelper \ + cppu \ + cppuhelper \ + oox \ + sal \ + subsequenttest \ + test \ + unotest \ + utl \ + tl \ +)) + +$(eval $(call gb_CppunitTest_use_sdk_api,oox_mcgr)) + +$(eval $(call gb_CppunitTest_use_ure,oox_mcgr)) +$(eval $(call gb_CppunitTest_use_vcl,oox_mcgr)) + +$(eval $(call gb_CppunitTest_use_rdb,oox_mcgr,services)) + +$(eval $(call gb_CppunitTest_use_custom_headers,oox_mcgr,\ + officecfg/registry \ +)) + +$(eval $(call gb_CppunitTest_use_configuration,oox_mcgr)) + +$(eval $(call gb_CppunitTest_add_arguments,oox_mcgr, \ + -env:arg-env=$(gb_Helper_LIBRARY_PATH_VAR)"$$$${$(gb_Helper_LIBRARY_PATH_VAR)+=$$$$$(gb_Helper_LIBRARY_PATH_VAR)}" \ +)) + +# vim: set noet sw=4 ts=4: diff --git a/oox/Module_oox.mk b/oox/Module_oox.mk index f868a126a61c..dc07ab913c56 100644 --- a/oox/Module_oox.mk +++ b/oox/Module_oox.mk @@ -34,6 +34,7 @@ $(eval $(call gb_Module_add_check_targets,oox,\ CppunitTest_oox_vml \ CppunitTest_oox_shape \ CppunitTest_oox_export \ + CppunitTest_oox_mcgr \ )) endif diff --git a/oox/qa/unit/data/MCGR_FontworkColorGradient.fodp b/oox/qa/unit/data/MCGR_FontworkColorGradient.fodp new file mode 100644 index 000000000000..e82401442c05 --- /dev/null +++ b/oox/qa/unit/data/MCGR_FontworkColorGradient.fodp @@ -0,0 +1,662 @@ + + + + 2023-06-08T12:15:12.97800000024x16impressPT2M30S2B2020/24.2.0.0.alpha0$Windows_X86_64 LibreOffice_project/ca9341cf60f3f9350662d30b61f6eadefca24667Regina Henschel2023-06-08T12:17:40.279000000Regina Henschel + + + -245 + -3142 + 30369 + 16510 + + + view1 + false + false + true + true + true + true + false + false + true + 1500 + false + Hw== + Hw== + + false + true + false + 0 + 0 + false + true + true + 4 + 0 + -231 + -11545 + 47915 + 16742 + 2000 + 2000 + 500 + 500 + 2000 + 4 + 2000 + 4 + false + 1500 + true + false + true + false + false + + + + + true + 1250 + EPSON6FC99C (WP-4025 Series) + + + de + DE + + + + + + iAv+/0VQU09ONkZDOTlDIChXUC00MDI1IFNlcmllcykAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARVBTT04gV1AtNDAyNSBTZXJpZXMAAAAAAAAAAAAAAAAWAAEAWgoAAAAAAAAEAAhSAAAEdAAAM1ROVwAAAAAKAEUAUABTAE8ATgA2AEYAQwA5ADkAQwAgACgAVwBQAC0ANAAwADIANQAgAFMAZQByAGkAZQBzACkAAAAAAAAAAAABBAAB3AB0CQ+bgAcBAAkAmgs0CGQAAQAHAFgCAgABAFgCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAIAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB0CQAAAQEBAQABAAABAAAAAAAAAAAAAAA4AAAAfAgAALQIAABAAAAA9AgAAIAAAAAAAAAAAAAAAAMACQRFAFAAUwBPAE4AIABXAFAALQA0ADAAMgA1ACAAUwBlAHIAaQBlAHMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAABYAgAAAAAAAAAAAAABAAAAAgAAAAAAAQBYAlgCBwAAAAAACQA0CJoLHgAeAB4AHgA0CJoLOwORBAEAAAAOABYAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAYAAAAAAAAAAAACAAAAAAIAAAMAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAABkAGQANAiaCx4AHgAeAB4ACQAAAAAAAAAAAAAA//8AAAAAAAAAAB4AHgABAAAAAwDgAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAQAAgAAAAAAAAAAAAEAMgAyANT+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABYACoAAAAgAAEAAAAgAAAAQAAAAAYAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADs/wAAAAAAAAAAAABCAAAAAQAAALAAAAAAAAAAAAAAAAAAAAAeAB4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEBARIAQ09NUEFUX0RVUExFWF9NT0RFDwBEdXBsZXhNb2RlOjpPZmYMAFBSSU5URVJfTkFNRRwARVBTT042RkM5OUMgKFdQLTQwMjUgU2VyaWVzKQsARFJJVkVSX05BTUUUAEVQU09OIFdQLTQwMjUgU2VyaWVz + false + false + false + $(inst)/share/palette%3B$(user)/config/standard.sob + false + 0 + 0 + true + false + false + true + false + true + 0 + $(inst)/share/palette/html.soc + $(inst)/share/palette%3B$(user)/config/standard.sod + $(inst)/share/palette%3B$(user)/config/standard.soe + $(inst)/share/palette%3B$(user)/config/standard.soh + $(inst)/share/palette%3B$(user)/config/standard.sog + true + 4 + false + false + true + low-resolution + false + false + false + true + false + true + true + true + false + false + false + 6 + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Simple + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/oox/qa/unit/data/MCGR_FontworkColorGradient.fodt b/oox/qa/unit/data/MCGR_FontworkColorGradient.fodt new file mode 100644 index 000000000000..17052a44b9bf --- /dev/null +++ b/oox/qa/unit/data/MCGR_FontworkColorGradient.fodt @@ -0,0 +1,331 @@ + + + + 2023-06-08T12:08:34.452000000A6querPT2M6S2B2020/24.2.0.0.alpha0$Windows_X86_64 LibreOffice_project/ca9341cf60f3f9350662d30b61f6eadefca24667Regina Henschel2023-06-08T12:10:27.725000000Regina Henschel + + + 0 + 0 + 30249 + 9082 + true + false + + + view2 + 8223 + 1000 + 0 + 0 + 30247 + 9081 + 0 + 1 + false + 100 + false + false + false + true + false + false + + + + + true + + false + false + false + false + true + 1 + true + false + false + false + + false + + false + false + false + + 0 + false + true + true + false + false + false + + 0 + + true + high-resolution + false + false + false + false + true + false + false + true + false + false + true + true + false + false + false + false + false + true + false + false + true + false + false + false + false + false + false + 1621794 + 1306543 + false + false + false + true + false + false + true + true + false + true + true + false + false + false + false + false + true + false + false + false + false + false + false + false + true + 0 + true + false + false + true + false + 0 + true + false + true + true + true + true + false + false + false + false + false + true + + false + true + false + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Simple + + + + + + + + + + + \ No newline at end of file diff --git a/oox/qa/unit/export.cxx b/oox/qa/unit/export.cxx index 3922726306b3..9b62dc337c1b 100644 --- a/oox/qa/unit/export.cxx +++ b/oox/qa/unit/export.cxx @@ -1012,7 +1012,7 @@ CPPUNIT_TEST_FIXTURE(Test, testFontworkDistance) 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 color #ffff00 (Yellow) with 'Intensity' 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.fodt"); @@ -1040,15 +1040,15 @@ CPPUNIT_TEST_FIXTURE(Test, testFontworkLinGradientRGBColor) assertXPath(pXmlDoc, sElement + "w14:textFill/w14:gradFill/w14:lin", "scaled", "0"); // Make sure the color stops have correct position and color + // The 'intensity' property in the UI has a different algorithm than the 'lumMod' attribute in + // OOXML. Therefore it cannot be exported as 'lumMod' but need to be incorporated into the 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", "val", "cccc00"); 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", "val", "cccc00"); assertXPath(pXmlDoc, sElement + "w14:gs[2]/w14:srgbClr/w14:alpha", "val", "30000"); assertXPath(pXmlDoc, sElement + "w14:gs[3]", "pos", "100000"); @@ -1081,7 +1081,7 @@ CPPUNIT_TEST_FIXTURE(Test, testFontworkAxialGradientTransparency) // 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); + assertXPath(pXmlDoc, sElement + "w14:textFill/w14:gradFill/w14:gsLst/w14:gs", 3); // 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"); @@ -1089,31 +1089,21 @@ CPPUNIT_TEST_FIXTURE(Test, testFontworkAxialGradientTransparency) // 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) + for (char ch = '1'; ch <= '3'; ++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"); + // transparency values are not exactly like in UI because converting through rgb-color. + // border 40% in UI means 20% on each side. + assertXPath(pXmlDoc, sElement + "w14:gs[1]", "pos", "20000"); + assertXPath(pXmlDoc, sElement + "w14:gs[1]/w14:schemeClr/w14:alpha", "val", "89800"); + assertXPath(pXmlDoc, sElement + "w14:gs[2]", "pos", "50000"); + assertXPath(pXmlDoc, sElement + "w14:gs[2]/w14:schemeClr/w14:alpha", "val", "4710"); + assertXPath(pXmlDoc, sElement + "w14:gs[3]", "pos", "80000"); + assertXPath(pXmlDoc, sElement + "w14:gs[3]/w14:schemeClr/w14:alpha", "val", "89800"); } CPPUNIT_TEST_FIXTURE(Test, testFontworkRadialGradient) @@ -1147,12 +1137,13 @@ CPPUNIT_TEST_FIXTURE(Test, testFontworkRadialGradient) { { "l", "75000" }, { "t", "20000" }, { "r", "25000" }, { "b", "80000" } }); // Make sure the color stops have correct position and color + // The first stop is duplicated to force Word to render the gradient same as LO. 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[2]", "pos", "0"); + assertXPath(pXmlDoc, sElement + "w14:gs[2]/w14:srgbClr", "val", "ff0000"); + assertXPath(pXmlDoc, sElement + "w14:gs[3]", "pos", "90000"); assertXPath(pXmlDoc, sElement + "w14:gs[3]/w14:srgbClr", "val", "40e0d0"); } @@ -1187,16 +1178,16 @@ CPPUNIT_TEST_FIXTURE(Test, testFontworkEllipticalGradient) { { "l", "50000" }, { "t", "50000" }, { "r", "50000" }, { "b", "50000" } }); // Make sure the color stops have correct position and color + // transparency values are not exactly like in UI because converting through rgb-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"); + // stop is duplicated to force Word to same rendering as LO does. + assertXPath(pXmlDoc, sElement + "w14:gs[2]", "pos", "0"); 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]", "pos", "50000"); assertXPath(pXmlDoc, sElement + "w14:gs[3]/w14:srgbClr", "val", "00008b"); - assertXPath(pXmlDoc, sElement + "w14:gs[3]/w14:srgbClr/w14:alpha", "val", "70000"); + assertXPath(pXmlDoc, sElement + "w14:gs[3]/w14:srgbClr/w14:alpha", "val", "69800"); } CPPUNIT_TEST_FIXTURE(Test, testFontworkSquareGradient) @@ -1230,16 +1221,15 @@ CPPUNIT_TEST_FIXTURE(Test, testFontworkSquareGradient) { { "l", "100000" }, { "t", "50000" }, { "r", "0" }, { "b", "50000" } }); // Make sure the color stops have correct position and color + // The 'intensity' property in the UI has a different algorithm than the 'lumMod' attribute in + // OOXML. Therefore it cannot be exported as 'lumMod' but need to be incorporated into the 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[1]/w14:srgbClr", "val", "e6e663"); 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[2]/w14:srgbClr", "val", "e6e663"); 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"); + assertXPath(pXmlDoc, sElement + "w14:gs[3]/w14:srgbClr", "val", "1d4860"); } CPPUNIT_TEST_FIXTURE(Test, testFontworkRectGradient) @@ -1273,22 +1263,24 @@ CPPUNIT_TEST_FIXTURE(Test, testFontworkRectGradient) { { "l", "50000" }, { "t", "50000" }, { "r", "50000" }, { "b", "50000" } }); // Make sure the color stops have correct position and color + // transparency values are not exactly like in UI because converting through rgb-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[1]/w14:schemeClr/w14:alpha", "val", "4710"); + // The first stop is duplicated to force Word to render the gradient same as LO. + assertXPath(pXmlDoc, sElement + "w14:gs[2]", "pos", "0"); 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[2]/w14:schemeClr/w14:alpha", "val", "4710"); + assertXPath(pXmlDoc, sElement + "w14:gs[3]", "pos", "90000"); 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"); + assertXPath(pXmlDoc, sElement + "w14:gs[3]/w14:schemeClr/w14:alpha", "val", "69800"); } CPPUNIT_TEST_FIXTURE(Test, testThemeColorTransparency) diff --git a/oox/qa/unit/mcgr.cxx b/oox/qa/unit/mcgr.cxx new file mode 100644 index 000000000000..b752f49115f4 --- /dev/null +++ b/oox/qa/unit/mcgr.cxx @@ -0,0 +1,73 @@ +/* -*- 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 + +using namespace ::com::sun::star; + +namespace +{ +/// Covers tests for multi-color gradient (MCGR) feature, available since LO 7.6.0. +class TestMCGR : public UnoApiXmlTest +{ +public: + TestMCGR() + : UnoApiXmlTest("/oox/qa/unit/data/") + { + } +}; + +CPPUNIT_TEST_FIXTURE(TestMCGR, testFontworkColorGradient) +{ + // Given a document with three-color gradient on a Fontwork. + loadFromURL(u"MCGR_FontworkColorGradient.fodp"); + // Save it to PPTX + save("Impress Office Open XML"); + // And make sure a multi-color gradient fill is exported. + xmlDocUniquePtr pXmlDoc = parseExport("ppt/slides/slide1.xml"); + // linear gradient with 30deg angle + assertXPath(pXmlDoc, "//a:r/a:rPr/a:gradFill/a:lin", "ang", "3600000"); + // three color stops, no transparency + const OString sPath = "//a:r/a:rPr/a:gradFill/a:gsLst/"; + assertXPath(pXmlDoc, sPath + "a:gs", 3); + assertXPath(pXmlDoc, sPath + "a:gs[1]", "pos", "0"); + assertXPath(pXmlDoc, sPath + "a:gs[1]/a:srgbClr", "val", "ff1493"); + assertXPath(pXmlDoc, sPath + "a:gs[2]", "pos", "30000"); + assertXPath(pXmlDoc, sPath + "a:gs[2]/a:srgbClr", "val", "ffff00"); + assertXPath(pXmlDoc, sPath + "a:gs[3]", "pos", "100000"); + assertXPath(pXmlDoc, sPath + "a:gs[3]/a:srgbClr", "val", "00ffff"); +} + +CPPUNIT_TEST_FIXTURE(TestMCGR, testFontworkColorGradientWord) +{ + // Fontwork is handled different in Word than in PowerPoint documents. So we need a separate + // test for a text document. + // Given a document with three-color gradient on a Fontwork. + loadFromURL(u"MCGR_FontworkColorGradient.fodt"); + // Save it to DOCX + save("Office Open XML Text"); + // And make sure a multi-color gradient fill is exported. + xmlDocUniquePtr pXmlDoc = parseExport("word/document.xml"); + // linear gradient with 30deg angle + assertXPath(pXmlDoc, "//w14:lin", "ang", "3600000"); + // three color stops, no transparency + const OString sPath = "//w14:gradFill/w14:gsLst/"; + assertXPath(pXmlDoc, sPath + "w14:gs", 3); + assertXPath(pXmlDoc, sPath + "w14:gs[1]", "pos", "0"); + assertXPath(pXmlDoc, sPath + "w14:gs[1]/w14:srgbClr", "val", "ff1493"); + assertXPath(pXmlDoc, sPath + "w14:gs[2]", "pos", "30000"); + assertXPath(pXmlDoc, sPath + "w14:gs[2]/w14:srgbClr", "val", "ffff00"); + assertXPath(pXmlDoc, sPath + "w14:gs[3]", "pos", "100000"); + assertXPath(pXmlDoc, sPath + "w14:gs[3]/w14:srgbClr", "val", "00ffff"); +} +} + +CPPUNIT_PLUGIN_IMPLEMENT(); + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ \ No newline at end of file diff --git a/oox/source/drawingml/fontworkhelpers.cxx b/oox/source/drawingml/fontworkhelpers.cxx index 3c8d28d634c6..43f2b050cdd8 100644 --- a/oox/source/drawingml/fontworkhelpers.cxx +++ b/oox/source/drawingml/fontworkhelpers.cxx @@ -19,6 +19,8 @@ #include +#include +#include #include #include #include @@ -33,7 +35,7 @@ #include #include -#include +#include #include #include #include @@ -1123,8 +1125,8 @@ void lcl_addColorTransformationToGrabBagStack(const model::ComplexColor& rComple void lcl_getGradientsFromShape(const uno::Reference& rXPropSet, const uno::Reference& rXPropSetInfo, - awt::Gradient& rColorGradient, bool& rbHasColorGradient, - awt::Gradient& rTransparenceGradient, + awt::Gradient2& rColorGradient, bool& rbHasColorGradient, + awt::Gradient2& rTransparenceGradient, bool& rbHasTransparenceGradient) { OUString sColorGradientName; @@ -1144,249 +1146,94 @@ void lcl_getGradientsFromShape(const uno::Reference& rXProp && (rXPropSet->getPropertyValue(u"FillTransparenceGradient") >>= rTransparenceGradient); } -// Returns color without transparency and without intensity. rnPos is position in gradient -// definition from 0 (= 0%) to 100 (=100%), without considering the gradient type. The border is at -// 0% side. The caller takes care to use a suitable position and gradient. -::Color lcl_getColorFromColorGradient(const awt::Gradient& rColorGradient, const sal_Int32 rnPos) -{ - sal_Int16 nBorder = rColorGradient.Border; // Border is in percent - ::Color aStartColor(ColorTransparency, rColorGradient.StartColor); - if (rnPos <= 0 || rnPos <= nBorder || nBorder >= 100) - return aStartColor; - - ::Color aEndColor(ColorTransparency, rColorGradient.EndColor); - if (rnPos >= 100) - return aEndColor; - - // linear interpolation for nBorder < rnpos < 100 in each color component - auto ColorInterpolate = [rnPos, nBorder](sal_uInt8 nStartC, sal_uInt8 nEndC) -> sal_uInt8 { - return std::clamp( - std::lround((nStartC * (100 - rnPos) + nEndC * (rnPos - nBorder)) / (100.0 - nBorder)), - 0, 255); - }; - sal_uInt8 nInterpolatedRed = ColorInterpolate(aStartColor.GetRed(), aEndColor.GetRed()); - sal_uInt8 nInterpolatedGreen = ColorInterpolate(aStartColor.GetGreen(), aEndColor.GetGreen()); - sal_uInt8 nInterpolatedBlue = ColorInterpolate(aStartColor.GetBlue(), aEndColor.GetBlue()); - return ::Color(nInterpolatedRed, nInterpolatedGreen, nInterpolatedBlue); -} - -// returns intensity in percent. rnPos is position in gradient definition from -// 0 (= 0%) to 100 (=100%), without considering the gradient type. The border is at 0% side. -// The caller takes care to use a suitable position and gradient. -sal_Int16 lcl_getIntensityFromColorGradient(const awt::Gradient& rColorGradient, - const sal_Int32 rnPos) -{ - sal_Int16 nBorder = rColorGradient.Border; // Border is in percent - sal_Int16 nStartIntensity = rColorGradient.StartIntensity; - if (rnPos <= 0 || rnPos <= nBorder || nBorder >= 100) - return nStartIntensity; - - sal_Int32 nEndIntensity = rColorGradient.EndIntensity; - if (rnPos >= 100) - return nEndIntensity; - - // linear interpolation for nBorder < npos < 100 - return std::lround((nStartIntensity * (100 - rnPos) + nEndIntensity * (rnPos - nBorder)) - / (100.0 - nBorder)); -} - -// returns transparency in percent. rnPos is position in gradient definition from -// 0 (= 0%) to 100 (=100%), without considering the gradient type. The border is at 0% side. -// The caller takes care to use a suitable position and gradient. -sal_Int16 lcl_getAlphaFromTransparenceGradient(const awt::Gradient& rTransparenceGradient, - const sal_Int32 rnPos) -{ - sal_Int16 nBorder = rTransparenceGradient.Border; // Border is in percent - // The transparency is not in Start- or EndIntensity, but encoded into the Color as gray. - ::Color aStartColor(ColorTransparency, rTransparenceGradient.StartColor); - if (rnPos <= 0 || rnPos <= nBorder || nBorder >= 100) - return std::lround(aStartColor.GetRed() * 100 / 255.0); - - ::Color aEndColor(ColorTransparency, rTransparenceGradient.EndColor); - if (rnPos >= 100) - return std::lround(aEndColor.GetRed() * 100 / 255.0); - - // linear interpolation for nBorder < npos < 100 - return std::lround( - (aStartColor.GetRed() * (100 - rnPos) + aEndColor.GetRed() * (rnPos - nBorder)) - / (100.0 - nBorder) * 100 / 255.0); -} - -GradientStopColor -lcl_createGradientStopColor(const uno::Reference& rXPropSet, - const uno::Reference& rXPropSetInfo, - const awt::Gradient& rColorGradient, const bool& rbHasColorGradient, - const awt::Gradient& rTransparenceGradient, - const bool& rbHasTransparenceGradient, const sal_Int32& rnPos) -{ - // Component mnValue of Transformation struct is in 1/100th percent (e.g 80% = 8000) in range - // -10000 to +10000. Constants are used in converting from API values below. - constexpr sal_Int16 nFactorToHthPerc = 100; - constexpr sal_Int16 nMaxHthPerc = 10000; - GradientStopColor aStopColor; - if (rbHasTransparenceGradient) - { - // Color - if (rbHasColorGradient) - { - // a color gradient is yet not enabled to use theme colors - aStopColor.RGBColor = lcl_getColorFromColorGradient(rColorGradient, rnPos); - sal_Int16 nIntensity = lcl_getIntensityFromColorGradient(rColorGradient, rnPos); - if (nIntensity != 100) - aStopColor.TTColor.addTransformation( - { model::TransformationType::LumMod, - std::clamp(nIntensity * nFactorToHthPerc, -nMaxHthPerc, - nMaxHthPerc) }); - } - else // solid color - { - // fill color might be a theme color - if (!(FontworkHelpers::getThemeColorFromShape("FillComplexColor", rXPropSet, - aStopColor.TTColor))) - { - // no theme color, use FillColor - sal_Int32 nFillColor(0); - if (rXPropSetInfo->hasPropertyByName("FillColor")) - rXPropSet->getPropertyValue(u"FillColor") >>= nFillColor; - aStopColor.RGBColor = ::Color(ColorTransparency, nFillColor); - aStopColor.TTColor = model::ComplexColor(); - } - } - - // transparency - // Mixed gradient types for color and transparency are not possible in oox. For now we act as - // if gradient geometries are identical. That is the case if we get the gradient from oox - // import. - sal_Int16 nAlpha = lcl_getAlphaFromTransparenceGradient(rTransparenceGradient, rnPos); - // model::TransformationType::Alpha is designed to be used with a:alpha, which has opacity. - // Therefore convert transparency to opacity. - if (nAlpha > 0) - aStopColor.TTColor.addTransformation( - { model::TransformationType::Alpha, - std::clamp(nMaxHthPerc - nAlpha * nFactorToHthPerc, -nMaxHthPerc, - nMaxHthPerc) }); - - return aStopColor; - } - - // else solid transparency or no transparency - // color - if (rbHasColorGradient) - { - // a color gradient is yet not enabled to use theme colors - aStopColor.RGBColor = lcl_getColorFromColorGradient(rColorGradient, rnPos); - aStopColor.TTColor = model::ComplexColor(); - sal_Int16 nIntensity = lcl_getIntensityFromColorGradient(rColorGradient, rnPos); - if (nIntensity != 100) - aStopColor.TTColor.addTransformation( - { model::TransformationType::LumMod, - std::clamp(nIntensity * nFactorToHthPerc, -nMaxHthPerc, - nMaxHthPerc) }); - } - else - { - // solid color and solid transparency - SAL_WARN("oox.drawingml", "method should not be called in this case"); - if (!(FontworkHelpers::getThemeColorFromShape("FillComplexColor", rXPropSet, - aStopColor.TTColor))) - { - // no theme color, use FillColor - sal_Int32 nFillColor(0); - if (rXPropSetInfo->hasPropertyByName(u"FillColor")) - rXPropSet->getPropertyValue(u"FillColor") >>= nFillColor; - aStopColor.RGBColor = ::Color(ColorTransparency, nFillColor); - aStopColor.TTColor = model::ComplexColor(); - } - } - - // Maybe transparency from FillTransparence - // model::TransformationType::Alpha is designed to be used with a:alpha, which has opacity. - // Therefore convert transparency to opacity. - sal_Int16 nAlpha(0); - if (rXPropSetInfo->hasPropertyByName(u"FillTransparence") - && (rXPropSet->getPropertyValue(u"FillTransparence") >>= nAlpha) && nAlpha > 0) - aStopColor.TTColor.addTransformation( - { model::TransformationType::Alpha, - std::clamp(nMaxHthPerc - nAlpha * nFactorToHthPerc, -nMaxHthPerc, - nMaxHthPerc) }); - - return aStopColor; -} - ColorMapType lcl_createColorMapFromShapeProps( const uno::Reference& rXPropSet, const uno::Reference& rXPropSetInfo, - const awt::Gradient& rColorGradient, const bool& rbHasColorGradient, - const awt::Gradient& rTransparenceGradient, const bool& rbHasTransparenceGradient) + const awt::Gradient2& rColorGradient, const bool& rbHasColorGradient, + const awt::Gradient2& rTransparenceGradient, const bool& rbHasTransparenceGradient) { - ColorMapType aColorMap; - awt::Gradient aColorGradient = rColorGradient; - awt::Gradient aTransparenceGradient = rTransparenceGradient; - // AXIAL has reversed gradient direction. Change it so, that 'border' is at 'start'. - if (rbHasColorGradient && aColorGradient.Style == awt::GradientStyle_AXIAL) - { - std::swap(aColorGradient.StartColor, aColorGradient.EndColor); - std::swap(aColorGradient.StartIntensity, aColorGradient.EndIntensity); - } - if (rbHasTransparenceGradient && aTransparenceGradient.Style == awt::GradientStyle_AXIAL) - { - std::swap(aTransparenceGradient.StartColor, aTransparenceGradient.EndColor); - std::swap(aTransparenceGradient.StartIntensity, - aTransparenceGradient.EndIntensity); - } + // LibreOffice can use color gradients and transparency gradients with different geometries. + // That is not possible in OOXML, so a fill might look different in Word. But a round-trip + // with gradients imported from Word, should work well. - // A GradientStopColor includes color and transparency. - // The key of aColorMap has same unit as the w14:pos attribute of element in oox. - GradientStopColor aStartStopColor - = lcl_createGradientStopColor(rXPropSet, rXPropSetInfo, aColorGradient, rbHasColorGradient, - aTransparenceGradient, rbHasTransparenceGradient, 0); - aColorMap.insert(std::pair{ 0, aStartStopColor }); - GradientStopColor aEndStopColor - = lcl_createGradientStopColor(rXPropSet, rXPropSetInfo, aColorGradient, rbHasColorGradient, - aTransparenceGradient, rbHasTransparenceGradient, 100); - aColorMap.insert(std::pair{ 100000, aEndStopColor }); - - // We add additional GradientStopColor in case of borders. + // Word has transparency not as separate gradient but as color transformation in a color + // gradient. Thus we synchronize the gradients. Then they have same offsets and count. + basegfx::BColor aSingleColor; + basegfx::BGradient aColorBGradient; + basegfx::BColorStops aColorStops; if (rbHasColorGradient) { - // We only use the color border for now. If the transparency gradient has a total different - // geometry than the color gradient, a description is not possible in oox. - // ToDo: If geometries only differ in border, emulation is possible. - sal_Int32 nBorderPos = aColorGradient.Border * 1000; - if (nBorderPos > 0) - aColorMap.insert(std::pair{ nBorderPos, aStartStopColor }); + aColorBGradient = basegfx::BGradient(rColorGradient); + aColorBGradient.tryToApplyStartEndIntensity(); + aColorBGradient.tryToApplyBorder(); + aColorBGradient.tryToApplyAxial(); + basegfx::utils::prepareColorStops(aColorBGradient, aColorStops, aSingleColor); + // All gradient styles but LINEAR and AXIAL (which is already converted to LINEAR) need the + // stops sequence reverse. + if (awt::GradientStyle_LINEAR != aColorBGradient.GetGradientStyle()) + aColorStops.reverseColorStops(); } - else if (rbHasTransparenceGradient) + else { - sal_Int32 nBorderPos = aTransparenceGradient.Border * 1000; - if (nBorderPos > 0) - aColorMap.insert(std::pair{ nBorderPos, aStartStopColor }); + sal_Int32 nFillColor(0); + if (rXPropSetInfo->hasPropertyByName("FillColor")) + rXPropSet->getPropertyValue(u"FillColor") >>= nFillColor; + aSingleColor = ::Color(ColorTransparency, nFillColor).getBColor().clamp(); } - // In case of AXIAL we compress the gradient to half wide and mirror it to the other half. - if ((rbHasColorGradient && aColorGradient.Style == awt::GradientStyle_AXIAL) - || (!rbHasColorGradient && rbHasTransparenceGradient - && aTransparenceGradient.Style == awt::GradientStyle_AXIAL)) + basegfx::BColor aSingleTrans; + basegfx::BGradient aTransBGradient; + basegfx::BColorStops aTransStops; + if (rbHasTransparenceGradient) { - ColorMapType aHelpColorMap(aColorMap); - aColorMap.clear(); - for (auto it = aHelpColorMap.begin(); it != aHelpColorMap.end(); ++it) - { - aColorMap.insert(std::pair{ (*it).first / 2, (*it).second }); - aColorMap.insert(std::pair{ 100000 - (*it).first / 2, (*it).second }); - } + aTransBGradient = basegfx::BGradient(rTransparenceGradient); + aTransBGradient.tryToApplyStartEndIntensity(); // usually 100%, but might be set by macro + aTransBGradient.tryToApplyBorder(); + aTransBGradient.tryToApplyAxial(); + basegfx::utils::prepareColorStops(aTransBGradient, aTransStops, aSingleTrans); + // All gradient styles but LINEAR and AXIAL (which is already converted to LINEAR) need the + // stops sequence reverse. + if (awt::GradientStyle_LINEAR != aTransBGradient.GetGradientStyle()) + aTransStops.reverseColorStops(); } - else if ((rbHasColorGradient && aColorGradient.Style != awt::GradientStyle_LINEAR) - || (!rbHasColorGradient && rbHasTransparenceGradient - && aTransparenceGradient.Style != awt::GradientStyle_LINEAR)) + else { - // only LINEAR has same direction as Word, the others are reverse. - ColorMapType aHelpColorMap(aColorMap); - aColorMap.clear(); - for (auto it = aHelpColorMap.begin(); it != aHelpColorMap.end(); ++it) + sal_Int16 nAPITrans(0); + if (rXPropSetInfo->hasPropertyByName(u"FillTransparence")) + rXPropSet->getPropertyValue(u"FillTransparence") >>= nAPITrans; + // API transparency is in range 0..100, BColor in range [0.0, 1.0]. + aSingleTrans = basegfx::BColor(nAPITrans * 0.01).clamp(); + } + + basegfx::utils::synchronizeColorStops(aColorStops, aTransStops, aSingleColor, aSingleTrans); + + ColorMapType aColorMap; + + // If we have no color gradient, the fix fill color might be a theme color. In that case we use + // it instead of the color from the color stop. + GradientStopColor aFixColor; + bool bUseThemeColor(!rbHasColorGradient + && FontworkHelpers::getThemeColorFromShape("FillComplexColor", rXPropSet, + aFixColor.TTColor)); + + for (auto itC = aColorStops.begin(), itT = aTransStops.begin(); + itC != aColorStops.end() && itT != aTransStops.end(); ++itC, ++itT) + { + GradientStopColor aNextStopColor = aFixColor; + if (!bUseThemeColor) { - aColorMap.insert(std::pair{ 100000 - (*it).first, (*it).second }); + aNextStopColor.TTColor = model::ComplexColor(); + aNextStopColor.RGBColor = ::Color((*itC).getStopColor()); } + // model::TransformationType::Alpha is opacity in range 0..10000, + // BColor is transparency in range [0.0, 1.0] + sal_Int16 nAlpha = std::clamp( + 10000 - std::lround((*itT).getStopColor().luminance() * 10000.0), 0, 10000); + if (nAlpha < 10000) + aNextStopColor.TTColor.addTransformation({ model::TransformationType::Alpha, nAlpha }); + sal_Int32 nPosition + = static_cast(std::lround((*itC).getStopOffset() * 100000.0)); + aColorMap.insert(std::pair{ nPosition, aNextStopColor }); } // If a gradient has only two stops, MS Office renders it with a non-linear method which looks @@ -1397,7 +1244,6 @@ ColorMapType lcl_createColorMapFromShapeProps( auto it = aColorMap.begin(); aColorMap.insert(std::pair{ 0, (*it).second }); } - return aColorMap; } } // end namespace @@ -1439,9 +1285,9 @@ void FontworkHelpers::createCharInteropGrabBagUpdatesFromShapeProps( } case drawing::FillStyle_GRADIENT: { - awt::Gradient aColorGradient; + awt::Gradient2 aColorGradient; bool bHasColorGradient(false); - awt::Gradient aTransparenceGradient; + awt::Gradient2 aTransparenceGradient; bool bHasTransparenceGradient(false); lcl_getGradientsFromShape(rXPropSet, xPropSetInfo, aColorGradient, bHasColorGradient, aTransparenceGradient, bHasTransparenceGradient); @@ -1475,8 +1321,8 @@ void FontworkHelpers::createCharInteropGrabBagUpdatesFromShapeProps( } lcl_addColorTransformationToGrabBagStack((*it).second.TTColor, pGrabBagStack); - pGrabBagStack - ->pop(); // maCurrentElement:'gs', maPropertyList:'attributes', 'srgbClr' or 'schemeClr' + pGrabBagStack->pop(); + // maCurrentElement:'gs', maPropertyList:'attributes', 'srgbClr' or 'schemeClr' pGrabBagStack->pop(); // maCurrentElement:'gsLst', maPropertyList: at least two 'gs' } pGrabBagStack->pop(); // maCurrentElement:'gradFill', maPropertyList: gsLst