diff options
author | Regina Henschel <rb.henschel@t-online.de> | 2023-02-14 01:36:50 +0100 |
---|---|---|
committer | Miklos Vajna <vmiklos@collabora.com> | 2023-02-22 07:12:19 +0000 |
commit | b94336ac3d29180504e4bd7ada4d29ed5b6d3fda (patch) | |
tree | ccd07586b0d6cb4c6697fdced79efc063518ef42 /oox | |
parent | 63784053ccd321c8f3fc7fe60f4e50fda597f553 (diff) |
tdf#51195 Improve DML export of Fontwork shapes for docx
Fontwork in Writer has fill and outline as shape properties. The 'abc
transform' in Word uses character properties. Output of character
properties happens in DocxAttributeOutput methods in LO.
The patch modifies the character properties of the Fontwork shape,
in particular the contents of the CharInteropGrabBag, so that
DocxAttributeOutput produces a reasonably suitable markup.
Change-Id: Ic9285d9a4d6dc21cd256e448ace058f1026a2780
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/146964
Tested-by: Jenkins
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
Diffstat (limited to 'oox')
-rw-r--r-- | oox/inc/drawingml/fontworkhelpers.hxx | 52 | ||||
-rw-r--r-- | oox/qa/unit/data/tdf51195_Fontwork_DirectColor.odt | bin | 0 -> 19099 bytes | |||
-rw-r--r-- | oox/qa/unit/data/tdf51195_Fontwork_Distance.odt | bin | 0 -> 14952 bytes | |||
-rw-r--r-- | oox/qa/unit/data/tdf51195_Fontwork_ThemeColor.odt | bin | 0 -> 19003 bytes | |||
-rw-r--r-- | oox/qa/unit/export.cxx | 105 | ||||
-rw-r--r-- | oox/source/drawingml/fontworkhelpers.cxx | 609 | ||||
-rw-r--r-- | oox/source/export/drawingml.cxx | 36 |
7 files changed, 801 insertions, 1 deletions
diff --git a/oox/inc/drawingml/fontworkhelpers.hxx b/oox/inc/drawingml/fontworkhelpers.hxx index f1861f266bde..7d8d0a9692dd 100644 --- a/oox/inc/drawingml/fontworkhelpers.hxx +++ b/oox/inc/drawingml/fontworkhelpers.hxx @@ -19,6 +19,7 @@ #pragma once +#include <docmodel/theme/ThemeColor.hxx> #include <rtl/ustring.hxx> #include <sal/types.h> #include <svx/msdffdef.hxx> @@ -26,7 +27,11 @@ #include "customshapeproperties.hxx" #include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/drawing/LineCap.hpp> +#include <com/sun/star/drawing/LineDash.hpp> #include <com/sun/star/drawing/XShape.hpp> +#include <com/sun/star/text/XText.hpp> namespace FontworkHelpers { @@ -46,5 +51,52 @@ void putCustomShapeIntoTextPathMode( If eShapeType is not a Fontwork shape type or the special type is not yet implemented, it returns an empty string.*/ OString GetVMLFontworkShapetypeMarkup(const MSO_SPT eShapeType); + +/** Collects the properties "CharColor", "CharLumMod", "CharLumOff", "CharColorTheme", + "CharColorThemeReference" and "CharTransparence" from the first non-empty run in rXText and puts + them into rCharPropVec.*/ +void collectCharColorProps(const css::uno::Reference<css::text::XText>& rXText, + std::vector<css::beans::PropertyValue>& rCharPropVec); + +/** Applies all properties in rTextPropVec excluding "CharInteropGrabBag" to all non-empty runs in + rXText.*/ +void applyPropsToRuns(const std::vector<css::beans::PropertyValue>& rTextPropVec, + css::uno::Reference<css::text::XText>& rXText); + +/** Generates the properties "CharColor", "CharLumMod", "CharLumOff", "CharColorTheme", + "CharColorThemeReference" and "CharTransparence" from the shape properties "FillColor", + "FillColorLumMod, "FillColorLumOff", "FillColorTheme", "FillColorThemeReference" and + "FillTransparence" and puts them into rCharPropVec.*/ +void createCharFillPropsFromShape(const css::uno::Reference<css::beans::XPropertySet>& rXPropSet, + std::vector<css::beans::PropertyValue>& rCharPropVec); + +/** Creates the properties "CharTextFillTextEffect", "CharTextOutlineTextEffect", "CharThemeColor", + "CharThemeColorShade" or "CharThemeColorTint", and "CharThemeOriginalColor" from the FillFoo and + LineBar properties of the shape and puts them into rUpdatePropVec.*/ +void createCharInteropGrabBagUpdatesFromShapeProps( + const css::uno::Reference<css::beans::XPropertySet>& rXPropSet, + std::vector<css::beans::PropertyValue>& rUpdatePropVec); + +/** Puts all properties in rUpdatePropVec into the "CharInteropGrabBag" of all non-empty runs in rXText. + Existing properties are overwritten.*/ +void applyUpdatesToCharInteropGrabBag(const std::vector<css::beans::PropertyValue>& rUpdatePropVec, + css::uno::Reference<css::text::XText>& rXText); + +// ToDo: This is essentially the same as contained in methode DrawingML::WriteOutline. Change it +// there to use this method too, perhaps move this method to a common location. +/** Uses LineDash and LineCap to detect, whether the dashing comes from a prstDash in MS Office. + If prstDash is detected, the method puts the corresponding string for markup into rsPrstDash + and returns true. + If no prstDash is detected, the method leaves rsPrstDash unchanged and returns false. */ +bool createPrstDashFromLineDash(const css::drawing::LineDash& rLineDash, + const css::drawing::LineCap& rLineCap, OUString& rsPrstDash); + +/** Returns true if a theme color with other type than model::ThemeColorType::Unknown was found. + The theme color is then in aThemeColor. + Returns false otherwise. aThemeColor is then unchanged or its type is + model::ThemeColorType::Unknown */ +bool getThemeColorFromShape(const OUString& rPropertyName, + const css::uno::Reference<css::beans::XPropertySet>& xPropertySet, + model::ThemeColor& aThemeColor); } /* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
\ No newline at end of file diff --git a/oox/qa/unit/data/tdf51195_Fontwork_DirectColor.odt b/oox/qa/unit/data/tdf51195_Fontwork_DirectColor.odt Binary files differnew file mode 100644 index 000000000000..6388b07675ec --- /dev/null +++ b/oox/qa/unit/data/tdf51195_Fontwork_DirectColor.odt diff --git a/oox/qa/unit/data/tdf51195_Fontwork_Distance.odt b/oox/qa/unit/data/tdf51195_Fontwork_Distance.odt Binary files differnew file mode 100644 index 000000000000..e747afd9554d --- /dev/null +++ b/oox/qa/unit/data/tdf51195_Fontwork_Distance.odt diff --git a/oox/qa/unit/data/tdf51195_Fontwork_ThemeColor.odt b/oox/qa/unit/data/tdf51195_Fontwork_ThemeColor.odt Binary files differnew file mode 100644 index 000000000000..0a6aa1baeb79 --- /dev/null +++ b/oox/qa/unit/data/tdf51195_Fontwork_ThemeColor.odt diff --git a/oox/qa/unit/export.cxx b/oox/qa/unit/export.cxx index 50c953135623..645ffe386dd1 100644 --- a/oox/qa/unit/export.cxx +++ b/oox/qa/unit/export.cxx @@ -910,6 +910,111 @@ CPPUNIT_TEST_FIXTURE(Test, testVMLAdjustmentExport) OUString sRadius = sAdjustments.copy(nTokenStart); CPPUNIT_ASSERT_DOUBLES_EQUAL(sal_Int32(5296), sRadius.toInt32(), 2); } + +CPPUNIT_TEST_FIXTURE(Test, testFontworkDirectColor) +{ + // The document has a Fontwork shape with fill #2e8b57 plus 60% transparency and line color + // #ff7f50 with 20% transparency. When exporting to docx, line color was not exported at all + // and fill color was replaced with character color. + loadFromURL(u"tdf51195_Fontwork_DirectColor.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"); + const OString sElement = "//w:txbxContent/w:p/w:r/w:rPr/"; + + // Make sure the fallback in <w:color> has correct value. + assertXPath(pXmlDoc, sElement + "w:color", "val", "2E8B57"); + + // ... and <w14:textOutline> exists and has correct values. + assertXPath(pXmlDoc, sElement + "w14:textOutline", 1); + assertXPath(pXmlDoc, sElement + "w14:textOutline/w14:solidFill/w14:srgbClr", "val", "ff7f50"); + assertXPath(pXmlDoc, sElement + "w14:textOutline/w14:solidFill/w14:srgbClr/w14:alpha", "val", + "20000"); + assertXPath(pXmlDoc, sElement + "w14:textOutline/w14:round", 1); + + // ... and w14:textFill exists and has correct values. + assertXPath(pXmlDoc, sElement + "w14:textFill", 1); + assertXPath(pXmlDoc, sElement + "w14:textFill/w14:solidFill/w14:srgbClr", "val", "2e8b57"); + assertXPath(pXmlDoc, sElement + "w14:textFill/w14:solidFill/w14:srgbClr/w14:alpha", "val", + "60000"); +} + +CPPUNIT_TEST_FIXTURE(Test, testFontworkThemeColor) +{ + // The document has a Fontwork shape with fill theme color 'lt2' and 50% darker, and a Fontwork + // shape with theme color 'dk2' and 40% lighter. When exporting to docx, fill color was not + // exported as theme color at all, and it was replaced with character color. + loadFromURL(u"tdf51195_Fontwork_ThemeColor.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"); + + // shape with 'darker' + OString sElement = "/w:document/w:body/w:p[2]/w:r/mc:AlternateContent[1]/mc:Choice/w:drawing/" + "wp:anchor/a:graphic/a:graphicData/wps:wsp/wps:txbx/w:txbxContent/w:p/w:r/" + "w:rPr/"; + + // Make sure the fallback in <w:color> has correct values + assertXPath(pXmlDoc, sElement + "w:color", "val", "948A54"); + assertXPath(pXmlDoc, sElement + "w:color", "themeColor", "background2"); + assertXPath(pXmlDoc, sElement + "w:color", "themeShade", "80"); + + // ... and w14:textFill exists and has correct values. + assertXPath(pXmlDoc, sElement + "w14:textFill/w14:solidFill/w14:schemeClr", "val", "bg2"); + assertXPath(pXmlDoc, sElement + "w14:textFill/w14:solidFill/w14:schemeClr/w14:lumMod", "val", + "50000"); + + // shape with 'lighter' + sElement = "/w:document/w:body/w:p[2]/w:r/mc:AlternateContent[2]/mc:Choice/w:drawing/wp:anchor/" + "a:graphic/a:graphicData/wps:wsp/wps:txbx/w:txbxContent/w:p/w:r/w:rPr/"; + + // Make sure the fallback in <w:color> has correct values + assertXPath(pXmlDoc, sElement + "w:color", "val", "558ED5"); + assertXPath(pXmlDoc, sElement + "w:color", "themeColor", "text2"); + assertXPath(pXmlDoc, sElement + "w:color", "themeTint", "99"); + + // ... and w14:textFill exists and has correct values. + assertXPath(pXmlDoc, sElement + "w14:textFill/w14:solidFill/w14:schemeClr", "val", "tx2"); + assertXPath(pXmlDoc, sElement + "w14:textFill/w14:solidFill/w14:schemeClr/w14:lumMod", "val", + "60000"); + assertXPath(pXmlDoc, sElement + "w14:textFill/w14:solidFill/w14:schemeClr/w14:lumOff", "val", + "40000"); +} + +CPPUNIT_TEST_FIXTURE(Test, testFontworkDistance) +{ + // LO ignores 'Spacing to Borders' of the 'Text Attributes' for Fontwork shapes. Word interprets + // them. Make sure we force them to zero on export to docx, otherwise Word might wrap the text. + loadFromURL(u"tdf51195_Fontwork_Distance.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"); + assertXPathAttrs(pXmlDoc, + "/w:document/w:body/w:p/w:r/mc:AlternateContent/mc:Choice/w:drawing/" + "wp:anchor/a:graphic/a:graphicData/wps:wsp/wps:bodyPr", + { { "lIns", "0" }, { "rIns", "0" }, { "tIns", "0" }, { "bIns", "0" } }); +} } CPPUNIT_PLUGIN_IMPLEMENT(); diff --git a/oox/source/drawingml/fontworkhelpers.cxx b/oox/source/drawingml/fontworkhelpers.cxx index 84779dbe9290..0e616eedcb48 100644 --- a/oox/source/drawingml/fontworkhelpers.cxx +++ b/oox/source/drawingml/fontworkhelpers.cxx @@ -22,18 +22,35 @@ #include <comphelper/propertysequence.hxx> #include <comphelper/propertyvalue.hxx> #include <comphelper/sequence.hxx> +#include <comphelper/sequenceashashmap.hxx> +#include <docmodel/uno/UnoThemeColor.hxx> #include <drawingml/customshapeproperties.hxx> #include <drawingml/presetgeometrynames.hxx> +#include <oox/helper/grabbagstack.hxx> #include <svx/msdffdef.hxx> +#include <tools/color.hxx> #include <tools/helpers.hxx> +#include <com/sun/star/beans/PropertyAttribute.hpp> #include <com/sun/star/beans/PropertyValue.hpp> #include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/container/XEnumeration.hpp> +#include <com/sun/star/container/XEnumerationAccess.hpp> +#include <com/sun/star/drawing/DashStyle.hpp> #include <com/sun/star/drawing/EnhancedCustomShapeAdjustmentValue.hpp> #include <com/sun/star/drawing/EnhancedCustomShapeTextPathMode.hpp> +#include <com/sun/star/drawing/FillStyle.hpp> +#include <com/sun/star/drawing/LineCap.hpp> +#include <com/sun/star/drawing/LineDash.hpp> +#include <com/sun/star/drawing/LineJoint.hpp> +#include <com/sun/star/drawing/LineStyle.hpp> #include <com/sun/star/drawing/XEnhancedCustomShapeDefaulter.hpp> #include <com/sun/star/drawing/XShape.hpp> +#include <com/sun/star/text/XText.hpp> +#include <com/sun/star/text/XTextRange.hpp> +#include <com/sun/star/util/XThemeColor.hpp> +#include <array> #include <map> using namespace com::sun::star; @@ -797,4 +814,596 @@ OString FontworkHelpers::GetVMLFontworkShapetypeMarkup(const MSO_SPT eShapeType) return i == aTypeToMarkupMap.end() ? OString() : i->second; } +void FontworkHelpers::collectCharColorProps(const uno::Reference<text::XText>& rXText, + std::vector<beans::PropertyValue>& rCharPropVec) +{ + if (!rXText.is()) + return; + uno::Reference<text::XTextCursor> rXTextCursor = rXText->createTextCursor(); + rXTextCursor->gotoStart(false); + rXTextCursor->gotoEnd(true); + uno::Reference<container::XEnumerationAccess> paraEnumAccess(rXText, uno::UNO_QUERY); + if (!paraEnumAccess.is()) + return; + uno::Reference<container::XEnumeration> paraEnum(paraEnumAccess->createEnumeration()); + while (paraEnum->hasMoreElements()) + { + uno::Reference<text::XTextRange> xParagraph(paraEnum->nextElement(), uno::UNO_QUERY); + uno::Reference<container::XEnumerationAccess> runEnumAccess(xParagraph, uno::UNO_QUERY); + if (!runEnumAccess.is()) + continue; + uno::Reference<container::XEnumeration> runEnum = runEnumAccess->createEnumeration(); + while (runEnum->hasMoreElements()) + { + uno::Reference<text::XTextRange> xRun(runEnum->nextElement(), uno::UNO_QUERY); + if (xRun->getString().isEmpty()) + continue; + uno::Reference<beans::XPropertySet> xRunPropSet(xRun, uno::UNO_QUERY); + if (!xRunPropSet.is()) + continue; + auto xRunPropSetInfo = xRunPropSet->getPropertySetInfo(); + if (!xRunPropSetInfo.is()) + continue; + + // We have found a non-empty run. Collect its simple color properties. + const std::array<OUString, 6> aNamesArray = { u"CharColor", + u"CharLumMod", + u"CharLumOff", + u"CharColorTheme", + u"CharColorThemeReference", + u"CharTransparence" }; + for (const auto& propName : aNamesArray) + { + if (xRunPropSetInfo->hasPropertyByName(propName)) + rCharPropVec.push_back(comphelper::makePropertyValue( + propName, xRunPropSet->getPropertyValue(propName))); + } + return; + } + } +} + +void FontworkHelpers::applyPropsToRuns(const std::vector<beans::PropertyValue>& rTextPropVec, + uno::Reference<text::XText>& rXText) +{ + if (!rXText.is()) + return; + uno::Reference<text::XTextCursor> xTextCursor = rXText->createTextCursor(); + xTextCursor->gotoStart(false); + xTextCursor->gotoEnd(true); + uno::Reference<container::XEnumerationAccess> paraEnumAccess(rXText, uno::UNO_QUERY); + if (!paraEnumAccess.is()) + return; + uno::Reference<container::XEnumeration> paraEnum(paraEnumAccess->createEnumeration()); + while (paraEnum->hasMoreElements()) + { + uno::Reference<text::XTextRange> xParagraph(paraEnum->nextElement(), uno::UNO_QUERY); + uno::Reference<container::XEnumerationAccess> runEnumAccess(xParagraph, uno::UNO_QUERY); + if (!runEnumAccess.is()) + continue; + uno::Reference<container::XEnumeration> runEnum = runEnumAccess->createEnumeration(); + while (runEnum->hasMoreElements()) + { + uno::Reference<text::XTextRange> xRun(runEnum->nextElement(), uno::UNO_QUERY); + uno::Reference<beans::XPropertySet> xRunPropSet(xRun, uno::UNO_QUERY); + if (!xRunPropSet.is()) + continue; + auto xRunPropSetInfo = xRunPropSet->getPropertySetInfo(); + if (!xRunPropSetInfo.is()) + continue; + + for (const beans::PropertyValue& rProp : rTextPropVec) + { + if (xRunPropSetInfo->hasPropertyByName(rProp.Name) + && !(xRunPropSetInfo->getPropertyByName(rProp.Name).Attributes + & beans::PropertyAttribute::READONLY) + && rProp.Name != u"CharInteropGrabBag") + { + xRunPropSet->setPropertyValue(rProp.Name, rProp.Value); + } + } + } + } +} + +void FontworkHelpers::createCharFillPropsFromShape( + const uno::Reference<beans::XPropertySet>& rXPropSet, + std::vector<beans::PropertyValue>& rCharPropVec) +{ + auto xPropSetInfo = rXPropSet->getPropertySetInfo(); + if (!xPropSetInfo.is()) + return; + // CharColor contains the color including all color transformations + // FillColor contains darken and lighten but not transparency + sal_Int32 nColorRGB = 0; + if (xPropSetInfo->hasPropertyByName(u"FillColor") + && (rXPropSet->getPropertyValue(u"FillColor") >>= nColorRGB)) + { + ::Color aColor(ColorTransparency, nColorRGB); + sal_Int16 nTransPercent = 0; + if (xPropSetInfo->hasPropertyByName(u"FillTransparence") + && (rXPropSet->getPropertyValue(u"FillTransparence") >>= nTransPercent)) + { + sal_uInt8 nAlpha = 255 - sal_uInt8(std::lround(double(nTransPercent) * 2.55)); + aColor.SetAlpha(nAlpha); + } + rCharPropVec.push_back(comphelper::makePropertyValue(u"CharColor", sal_Int32(aColor))); + } + + const std::array<OUString, 5> aCharPropNames + = { u"CharColorLumMod", u"CharColorLumOff", u"CharColorTheme", u"CharColorThemeReference", + u"CharTransparence" }; + const std::array<OUString, 5> aShapePropNames + = { u"FillColorLumMod", u"FillColorLumOff", u"FillColorTheme", u"FillColorThemeReference", + u"FillTransparence" }; + for (size_t i = 0; i < 5; i++) + { + if (xPropSetInfo->hasPropertyByName(aShapePropNames[i])) + rCharPropVec.push_back(comphelper::makePropertyValue( + aCharPropNames[i], rXPropSet->getPropertyValue(aShapePropNames[i]))); + } +} + +bool FontworkHelpers::createPrstDashFromLineDash(const drawing::LineDash& rLineDash, + const drawing::LineCap& rLineCap, + OUString& rsPrstDash) +{ + bool bIsConverted = false; + + bool bIsRelative(rLineDash.Style == drawing::DashStyle_RECTRELATIVE + || rLineDash.Style == drawing::DashStyle_ROUNDRELATIVE); + if (bIsRelative && rLineDash.Dots == 1) + { // The length were tweaked on import in case of prstDash. Revert it here. + sal_uInt32 nDotLen = rLineDash.DotLen; + sal_uInt32 nDashLen = rLineDash.DashLen; + sal_uInt32 nDistance = rLineDash.Distance; + if (rLineCap != drawing::LineCap_BUTT && nDistance >= 99) + { + nDistance -= 99; + nDotLen += 99; + if (nDashLen > 0) + nDashLen += 99; + } + + // LO uses length 0 for 100%, if the attribute is missing in ODF. + // Other applications might write 100%. Make is unique for the conditions. + if (nDotLen == 0) + nDotLen = 100; + if (nDashLen == 0 && rLineDash.Dashes > 0) + nDashLen = 100; + + bIsConverted = true; + if (nDotLen == 100 && rLineDash.Dashes == 0 && nDashLen == 0 && nDistance == 300) + rsPrstDash = u"dot"; + else if (nDotLen == 400 && rLineDash.Dashes == 0 && nDashLen == 0 && nDistance == 300) + rsPrstDash = u"dash"; + else if (nDotLen == 400 && rLineDash.Dashes == 1 && nDashLen == 100 && nDistance == 300) + rsPrstDash = u"dashDot"; + else if (nDotLen == 800 && rLineDash.Dashes == 0 && nDashLen == 0 && nDistance == 300) + rsPrstDash = u"lgDash"; + else if (nDotLen == 800 && rLineDash.Dashes == 1 && nDashLen == 100 && nDistance == 300) + rsPrstDash = u"lgDashDot"; + else if (nDotLen == 800 && rLineDash.Dashes == 2 && nDashLen == 100 && nDistance == 300) + rsPrstDash = u"lgDashDotDot"; + else if (nDotLen == 100 && rLineDash.Dashes == 0 && nDashLen == 0 && nDistance == 100) + rsPrstDash = u"sysDot"; + else if (nDotLen == 300 && rLineDash.Dashes == 0 && nDashLen == 0 && nDistance == 100) + rsPrstDash = u"sysDash"; + else if (nDotLen == 300 && rLineDash.Dashes == 1 && nDashLen == 100 && nDistance == 100) + rsPrstDash = u"sysDashDot"; + else if (nDotLen == 300 && rLineDash.Dashes == 2 && nDashLen == 100 && nDistance == 100) + rsPrstDash = "sysDashDotDot"; + else + bIsConverted = false; + } + return bIsConverted; +} + +bool FontworkHelpers::getThemeColorFromShape( + OUString const& rPropertyName, const uno::Reference<beans::XPropertySet>& xPropertySet, + model::ThemeColor& aThemeColor) +{ + auto xPropSetInfo = xPropertySet->getPropertySetInfo(); + if (!xPropSetInfo.is()) + return false; + uno::Reference<util::XThemeColor> xThemeColor; + if (xPropSetInfo->hasPropertyByName(rPropertyName) + && (xPropertySet->getPropertyValue(rPropertyName) >>= xThemeColor) && xThemeColor.is()) + { + model::theme::setFromXThemeColor(aThemeColor, xThemeColor); + if (aThemeColor.getType() == model::ThemeColorType::Unknown) + return false; + else + return true; + } + return false; +} + +namespace +{ +// Returns the string to be used in w14:schemeClr in case of w14:textOutline or w14:textFill +OUString lcl_getW14MarkupStringForThemeColor(const model::ThemeColor& rThemeColor) +{ + const std::array<OUString, 12> W14ColorNames + = { u"tx1", u"bg1", u"tx2", u"bg2", u"accent1", u"accent2", + u"accent3", u"accent4", u"accent5", u"accent6", u"hlink", u"folHlink" }; + const sal_uInt8 nClrNameIndex = std::clamp<sal_uInt8>( + sal_Int32(rThemeColor.getType()), sal_Int32(model::ThemeColorType::Dark1), + sal_Int32(model::ThemeColorType::FollowedHyperlink)); + return W14ColorNames[nClrNameIndex]; +} + +// Returns the string to be used in w:themeColor. It is exported via CharThemeColor. +OUString lcl_getWMarkupStringForThemeColor(const model::ThemeColor& rThemeColor) +{ + const std::array<OUString, 12> WColorNames + = { u"text1", u"background1", u"text2", u"background2", + u"accent1", u"accent2", u"accent3", u"accent4", + u"accent5", u"accent6", u"hyperlink", u"followedHyperlink" }; + const sal_uInt8 nClrNameIndex = std::clamp<sal_uInt8>( + sal_Int32(rThemeColor.getType()), sal_Int32(model::ThemeColorType::Dark1), + sal_Int32(model::ThemeColorType::FollowedHyperlink)); + return WColorNames[nClrNameIndex]; +} + +// Puts the value of the first occurancy of rType in rThemeColor into rValue and returns true. +// If such does not exist, rValue is unchanged and the method returns false. +bool lcl_getThemeColorTransformationValue(const model::ThemeColor& rThemeColor, + const model::TransformationType& rType, sal_Int16& rValue) +{ + const std::vector<model::Transformation> aTransVec(rThemeColor.getTransformations()); + auto bItemFound + = [rType](const model::Transformation& rTrans) { return rType == rTrans.meType; }; + auto pIt = std::find_if(aTransVec.begin(), aTransVec.end(), bItemFound); + if (pIt == aTransVec.end()) + return false; + rValue = (*pIt).mnValue; + return true; +} + +// Adds the child elements 'lumMod' and/or 'lumOff' to 'schemeClr' maCurrentElement of +// pGrabStack, if such exist in rThemeColor. As of Feb 2023, 'alpha' is not contained in the +// the maTransformations of rThemeColor. +void lcl_addColorTransformationToGrabBagStack(const model::ThemeColor& rThemeColor, + std::unique_ptr<oox::GrabBagStack>& pGrabBagStack) +{ + if (pGrabBagStack == nullptr) + return; + for (auto const& rColorTransform : rThemeColor.getTransformations()) + { + switch (rColorTransform.meType) + { + case model::TransformationType::LumMod: + pGrabBagStack->push("lumMod"); + pGrabBagStack->push("attributes"); + pGrabBagStack->addInt32("val", rColorTransform.mnValue * 10); + pGrabBagStack->pop(); + pGrabBagStack->pop(); + break; + case model::TransformationType::LumOff: + pGrabBagStack->push("lumOff"); + pGrabBagStack->push("attributes"); + pGrabBagStack->addInt32("val", rColorTransform.mnValue * 10); + pGrabBagStack->pop(); + pGrabBagStack->pop(); + break; + default: // other child element can be added later if needed for Fontwork + break; + } + } +} +} // end namespace + +void FontworkHelpers::createCharInteropGrabBagUpdatesFromShapeProps( + const uno::Reference<beans::XPropertySet>& rXPropSet, + std::vector<beans::PropertyValue>& rUpdatePropVec) +{ + auto xPropSetInfo = rXPropSet->getPropertySetInfo(); + if (!xPropSetInfo.is()) + return; + + // GrabBagStack is a special tool for handling the hierachy in a GrabBag + std::unique_ptr<oox::GrabBagStack> pGrabBagStack; + + // CharTextFillTextEffect + pGrabBagStack.reset(new oox::GrabBagStack("textFill")); + drawing::FillStyle eFillStyle = drawing::FillStyle_SOLID; + if (xPropSetInfo->hasPropertyByName(u"FillStyle")) + rXPropSet->getPropertyValue(u"FillStyle") >>= eFillStyle; + switch (eFillStyle) + { + case drawing::FillStyle_NONE: + { + pGrabBagStack->appendElement("noFill", uno::Any()); + break; + } + case drawing::FillStyle_GRADIENT: // ToDo + { + // fallback + pGrabBagStack->push("solidFill"); + pGrabBagStack->push("srgbClr"); + pGrabBagStack->push("attributes"); + ::Color aColor(ColorTransparency, 7512015); // LO default fill + pGrabBagStack->addString("val", aColor.AsRGBHexString()); + // pop() calls are in the final getRootProperty() method + break; + } + case drawing::FillStyle_SOLID: + { + pGrabBagStack->push("solidFill"); + model::ThemeColor aThemeColor; + // It is either "schemeClr" or "srgbClr". + if (FontworkHelpers::getThemeColorFromShape("FillColorThemeReference", rXPropSet, + aThemeColor)) + { + pGrabBagStack->push("schemeClr"); + pGrabBagStack->push("attributes"); + pGrabBagStack->addString("val", lcl_getW14MarkupStringForThemeColor(aThemeColor)); + pGrabBagStack->pop(); // maCurrentElement:'schemeClr', maPropertyList:'attributes' + lcl_addColorTransformationToGrabBagStack(aThemeColor, pGrabBagStack); + // maCurrentElement:'schemeClr', maPropertyList:'attributes', maybe 'lumMod' and + // maybe 'lumOff' + } + else + { + pGrabBagStack->push("srgbClr"); + sal_Int32 nFillColor(0); + if (xPropSetInfo->hasPropertyByName(u"FillColor")) + rXPropSet->getPropertyValue(u"FillColor") >>= nFillColor; + pGrabBagStack->push("attributes"); + ::Color aColor(ColorTransparency, nFillColor); + pGrabBagStack->addString("val", aColor.AsRGBHexString()); + pGrabBagStack->pop(); + // maCurrentElement:'srgbClr', maPropertyList:'attributes' + } + + sal_Int16 nFillTransparence(0); + if (xPropSetInfo->hasPropertyByName(u"FillTransparence")) + rXPropSet->getPropertyValue(u"FillTransparence") >>= nFillTransparence; + if (nFillTransparence != 0) + { + pGrabBagStack->push("alpha"); + pGrabBagStack->push("attributes"); + pGrabBagStack->addInt32("val", nFillTransparence * 1000); + } + // all remaining pop() calls are in the final getRootProperty() method + break; + } + default: // BITMAP is VML only export and does not arrive here. HATCH has to be VML only + // export too, but is not yet implemented. + break; + } + // resolve the stack and put resulting PropertyValue into the update vector + beans::PropertyValue aCharTextFillTextEffect; + aCharTextFillTextEffect.Name = "CharTextFillTextEffect"; + aCharTextFillTextEffect.Value <<= pGrabBagStack->getRootProperty(); + rUpdatePropVec.push_back(aCharTextFillTextEffect); + + // CharTextOutlineTextEffect + pGrabBagStack.reset(new oox::GrabBagStack("textOutline")); + + // attributes + pGrabBagStack->push("attributes"); + // line width + sal_Int32 nLineWidth(0); + if (xPropSetInfo->hasPropertyByName(u"LineWidth")) + rXPropSet->getPropertyValue(u"LineWidth") >>= nLineWidth; + pGrabBagStack->addInt32("w", nLineWidth * 360); + // cap for dashes + drawing::LineCap eLineCap = drawing::LineCap_BUTT; + if (xPropSetInfo->hasPropertyByName(u"LineCap")) + rXPropSet->getPropertyValue(u"LineCap") >>= eLineCap; + OUString sCap = u"flat"; + if (eLineCap == drawing::LineCap_ROUND) + sCap = u"rnd"; + else if (eLineCap == drawing::LineCap_SQUARE) + sCap = u"sq"; + pGrabBagStack->addString("cap", sCap); + // LO has no compound lines and always centers the lines + pGrabBagStack->addString("cmpd", u"sng"); + pGrabBagStack->addString("alng", u"ctr"); + pGrabBagStack->pop(); + // maCurrentElement:'textOutline', maPropertyList:'attributes' + + // style + drawing::LineStyle eLineStyle = drawing::LineStyle_NONE; + if (xPropSetInfo->hasPropertyByName(u"LineStyle")) + rXPropSet->getPropertyValue(u"LineStyle") >>= eLineStyle; + // 'dashed' is not a separate style in Word. Word has a style 'gradFill', but that is not yet + // implemented in LO. So only 'noFill' and 'solidFill'. + if (eLineStyle == drawing::LineStyle_NONE) + { + pGrabBagStack->appendElement("noFill", uno::Any()); + } + else + { + pGrabBagStack->push("solidFill"); + // It is either "schemeClr" or "srgbClr". + model::ThemeColor aThemeColor; + if (FontworkHelpers::getThemeColorFromShape("LineColorThemeReference", rXPropSet, + aThemeColor)) + { + pGrabBagStack->push("schemeClr"); + pGrabBagStack->push("attributes"); + pGrabBagStack->addString("val", lcl_getW14MarkupStringForThemeColor(aThemeColor)); + pGrabBagStack->pop(); + lcl_addColorTransformationToGrabBagStack(aThemeColor, pGrabBagStack); + // maCurrentElement:'schemeClr', maPropertylist:'attributes' + } + else // not a theme color + { + pGrabBagStack->push("srgbClr"); + pGrabBagStack->push("attributes"); + sal_Int32 nLineColor(0); + if (xPropSetInfo->hasPropertyByName(u"LineColor")) + rXPropSet->getPropertyValue(u"LineColor") >>= nLineColor; + ::Color aColor(ColorTransparency, nLineColor); + pGrabBagStack->addString("val", aColor.AsRGBHexString()); + pGrabBagStack->pop(); + // maCurrentElement:'srgbClr', maPropertylist:'attributes' + } + + sal_Int16 nLineTransparence(0); + if (xPropSetInfo->hasPropertyByName(u"LineTransparence")) + rXPropSet->getPropertyValue(u"LineTransparence") >>= nLineTransparence; + if (nLineTransparence != 0) + { + pGrabBagStack->push("alpha"); + pGrabBagStack->push("attributes"); + pGrabBagStack->addInt32("val", nLineTransparence * 1000); + pGrabBagStack->pop(); // maCurrentElement: 'alpha' + pGrabBagStack->pop(); // maCurrentElement: 'srgbClr' or 'schemeClr' + } + pGrabBagStack->pop(); + // maCurrentElement:'solidFill', maPropertyList:either 'srgbClr' or 'schemeClr + pGrabBagStack->pop(); + } + // maCurrentElement:'textOutline', maPropertyList:'attributes' and either 'noFill' or 'solidFill' + + // prstDash + if (eLineStyle == drawing::LineStyle_DASH) + { + pGrabBagStack->push("prstDash"); + OUString sPrstDash = u"sysDot"; + drawing::LineDash aLineDash; + if (xPropSetInfo->hasPropertyByName(u"LineDash") + && (rXPropSet->getPropertyValue(u"LineDash") >>= aLineDash)) + { + // The outline of abc-transform in Word is not able to use custDash. But we know the line + // is dashed. We keep "sysDot" as fallback in case no prstDash is detected. + FontworkHelpers::createPrstDashFromLineDash(aLineDash, eLineCap, sPrstDash); + } + else + { + // ToDo: There may be a named dash style, but that is unlikely for Fontwork shapes. So + // I skip it for now and use the "sysDot" fallback. + } + pGrabBagStack->push("attributes"); + pGrabBagStack->addString("val", sPrstDash); + pGrabBagStack->pop(); // maCurrentElement:'prstDash' + pGrabBagStack->pop(); // maCurrentElement:'textOutline' + } + // maCurrentElement:'textOutline', maPropertyList:'attributes', either 'noFill' or 'solidFill', + // and maybe 'prstDash'. + + // LineJoint, can be 'round', 'bevel' or 'miter' in Word + drawing::LineJoint eLineJoint = drawing::LineJoint_NONE; + if (xPropSetInfo->hasPropertyByName(u"LineJoint")) + rXPropSet->getPropertyValue(u"LineJoint") >>= eLineJoint; + if (eLineJoint == drawing::LineJoint_NONE || eLineJoint == drawing::LineJoint_BEVEL) + pGrabBagStack->appendElement("bevel", uno::Any()); + else if (eLineJoint == drawing::LineJoint_ROUND) + pGrabBagStack->appendElement("round", uno::Any()); + else // MITER or deprecated MIDDLE + { + pGrabBagStack->push("miter"); + pGrabBagStack->push("attributes"); + pGrabBagStack->addInt32("lim", 0); // As of Feb. 2023 LO cannot render other values. + pGrabBagStack->pop(); // maCurrentElement:'attributes' + pGrabBagStack->pop(); // maCurrentElement:'miter' + } + // maCurrentElement:'textOutline', maPropertyList:'attributes', either 'noFill' or + // 'solidFill', maybe 'prstDash', and either 'bevel', 'round' or 'miter'. + + // resolve the stack and put resulting PropertyValue into the update vector + beans::PropertyValue aCharTextOutlineTextEffect; + aCharTextOutlineTextEffect.Name = "CharTextOutlineTextEffect"; + aCharTextOutlineTextEffect.Value <<= pGrabBagStack->getRootProperty(); + rUpdatePropVec.push_back(aCharTextOutlineTextEffect); + + // CharThemeOriginalColor, CharThemeColor, and CharThemeColorShade or CharThemeColorTint will be + // used for <w:color> element. That is evaluated by applications, which do not understand w14 + // namespace, or if w14:textFill is omitted. + model::ThemeColor aThemeColor; + if (FontworkHelpers::getThemeColorFromShape("FillColorThemeReference", rXPropSet, aThemeColor)) + { + // CharThemeColor + beans::PropertyValue aCharThemeColor; + aCharThemeColor.Name = u"CharThemeColor"; + aCharThemeColor.Value <<= lcl_getWMarkupStringForThemeColor(aThemeColor); + rUpdatePropVec.push_back(aCharThemeColor); + + // CharThemeColorShade or CharThemeColorTint + // MS Office uses themeTint and themeShade on the luminance in a HSL color space, see 2.1.72 + // in [MS-OI29500]. That is different from OOXML specification. + // We made two assumption here: (1) If LumOff exists and is not zero, it is a 'tint'. + // (2) LumMod + LumOff == 10000; + sal_Int16 nLumMod; + if (lcl_getThemeColorTransformationValue(aThemeColor, model::TransformationType::LumMod, + nLumMod)) + { + sal_Int16 nLumOff; + bool bIsTint = lcl_getThemeColorTransformationValue( + aThemeColor, model::TransformationType::LumOff, nLumOff) + && nLumOff != 0; + sal_uInt8 nValue + = std::clamp<sal_uInt8>(lround(double(nLumMod) * 255.0 / 10000.0), 0, 255); + OUString sValue = OUString::number(nValue, 16); + + beans::PropertyValue aCharThemeTintOrShade; + aCharThemeTintOrShade.Name = bIsTint ? u"CharThemeColorTint" : u"CharThemeColorShade"; + aCharThemeTintOrShade.Value <<= sValue; + rUpdatePropVec.push_back(aCharThemeTintOrShade); + } + } + // ToDo: Are FillColorLumMod, FillColorLumOff and FillColorTheme possible without + // FillColorThemeReference? If yes, we need an 'else' part here. + + // CharThemeOriginalColor. + beans::PropertyValue aCharThemeOriginalColor; + sal_Int32 nFillColor(0); + if (xPropSetInfo->hasPropertyByName(u"FillColor")) + rXPropSet->getPropertyValue(u"FillColor") >>= nFillColor; + aCharThemeOriginalColor.Name = u"CharThemeOriginalColor"; + ::Color aColor(ColorTransparency, nFillColor); + aCharThemeOriginalColor.Value <<= aColor.AsRGBHEXString(); + rUpdatePropVec.push_back(aCharThemeOriginalColor); +} + +void FontworkHelpers::applyUpdatesToCharInteropGrabBag( + const std::vector<beans::PropertyValue>& rUpdatePropVec, uno::Reference<text::XText>& rXText) +{ + if (!rXText.is()) + return; + uno::Reference<text::XTextCursor> rXTextCursor = rXText->createTextCursor(); + rXTextCursor->gotoStart(false); + rXTextCursor->gotoEnd(true); + uno::Reference<container::XEnumerationAccess> paraEnumAccess(rXText, uno::UNO_QUERY); + if (!paraEnumAccess.is()) + return; + uno::Reference<container::XEnumeration> paraEnum(paraEnumAccess->createEnumeration()); + while (paraEnum->hasMoreElements()) + { + uno::Reference<text::XTextRange> xParagraph(paraEnum->nextElement(), uno::UNO_QUERY); + uno::Reference<container::XEnumerationAccess> runEnumAccess(xParagraph, uno::UNO_QUERY); + if (!runEnumAccess.is()) + continue; + uno::Reference<container::XEnumeration> runEnum = runEnumAccess->createEnumeration(); + while (runEnum->hasMoreElements()) + { + uno::Reference<text::XTextRange> xRun(runEnum->nextElement(), uno::UNO_QUERY); + if (xRun->getString().isEmpty()) + continue; + uno::Reference<beans::XPropertySet> xRunPropSet(xRun, uno::UNO_QUERY); + if (!xRunPropSet.is()) + continue; + auto xRunPropSetInfo = xRunPropSet->getPropertySetInfo(); + if (!xRunPropSetInfo.is()) + continue; + + // Now apply the updates to the CharInteropGrabBag of this run + uno::Sequence<beans::PropertyValue> aCharInteropGrabBagSeq; + if (xRunPropSetInfo->hasPropertyByName("CharInteropGrabBag")) + xRunPropSet->getPropertyValue("CharInteropGrabBag") >>= aCharInteropGrabBagSeq; + comphelper::SequenceAsHashMap aGrabBagMap(aCharInteropGrabBagSeq); + for (const auto& rProp : rUpdatePropVec) + { + aGrabBagMap[rProp.Name] = rProp.Value; // [] inserts if not exists + } + xRunPropSet->setPropertyValue("CharInteropGrabBag", + uno::Any(aGrabBagMap.getAsConstPropertyValueList())); + } + } +} + /* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
\ No newline at end of file diff --git a/oox/source/export/drawingml.cxx b/oox/source/export/drawingml.cxx index 8e57e9cecb36..7edbf8f17146 100644 --- a/oox/source/export/drawingml.cxx +++ b/oox/source/export/drawingml.cxx @@ -28,6 +28,7 @@ #include <oox/helper/propertyset.hxx> #include <oox/drawingml/color.hxx> #include <drawingml/fillproperties.hxx> +#include <drawingml/fontworkhelpers.hxx> #include <drawingml/textparagraph.hxx> #include <oox/token/namespaces.hxx> #include <oox/token/properties.hxx> @@ -65,6 +66,7 @@ #include <com/sun/star/drawing/EnhancedCustomShapeParameterType.hpp> #include <com/sun/star/drawing/EnhancedCustomShapeSegment.hpp> #include <com/sun/star/drawing/EnhancedCustomShapeSegmentCommand.hpp> +#include <com/sun/star/drawing/FillStyle.hpp> #include <com/sun/star/drawing/Hatch.hpp> #include <com/sun/star/drawing/LineDash.hpp> #include <com/sun/star/drawing/LineJoint.hpp> @@ -74,7 +76,6 @@ #include <com/sun/star/drawing/TextVerticalAdjust.hpp> #include <com/sun/star/drawing/XShape.hpp> #include <com/sun/star/drawing/XShapes.hpp> -#include <com/sun/star/drawing/FillStyle.hpp> #include <com/sun/star/frame/XModel.hpp> #include <com/sun/star/graphic/XGraphic.hpp> #include <com/sun/star/i18n/ScriptType.hpp> @@ -3588,6 +3589,15 @@ void DrawingML::WriteText(const Reference<XInterface>& rXIface, bool bBodyPr, bo && ( sPresetWarp == "textArchDown" || sPresetWarp == "textArchUp" || sPresetWarp == "textButton" || sPresetWarp == "textCircle"); + // Fontwork shapes in LO ignore insets in rendering, Word interpets them. + if (GetDocumentType() == DOCUMENT_DOCX && bIsFontworkShape) + { + nLeft = 0; + nRight = 0; + nTop = 0; + nBottom = 0; + } + if (bUpright) { Degree100 nShapeRotateAngleDeg100(0_deg100); @@ -3924,6 +3934,27 @@ void DrawingML::WriteText(const Reference<XInterface>& rXIface, bool bBodyPr, bo const SdrTextObj* pTxtObj = DynCastSdrTextObj( pSdrObject ); if (pTxtObj && mpTextExport) { + std::vector<beans::PropertyValue> aOldCharFillPropVec; + if (bIsFontworkShape) + { + // Users may have set the character fill properties for more convenient editing. + // Save the properties before changing them for Fontwork export. + FontworkHelpers::collectCharColorProps(xXText, aOldCharFillPropVec); + // Word has properties for abc-transform in the run properties of the text of the shape. + // Writer has the Fontwork properties as shape properties. Create the character fill + // properties needed for export from the shape fill properties + // and apply them to all runs. + std::vector<beans::PropertyValue> aExportCharFillPropVec; + FontworkHelpers::createCharFillPropsFromShape(rXPropSet, aExportCharFillPropVec); + FontworkHelpers::applyPropsToRuns(aExportCharFillPropVec, xXText); + // Import has converted some items from CharInteropGrabBag to fill and line + // properties of the shape. For export we convert them back because users might have + // changed them. And we create them in case we come from an odt document. + std::vector<beans::PropertyValue> aUpdatePropVec; + FontworkHelpers::createCharInteropGrabBagUpdatesFromShapeProps(rXPropSet, aUpdatePropVec); + FontworkHelpers::applyUpdatesToCharInteropGrabBag(aUpdatePropVec, xXText); + } + std::optional<OutlinerParaObject> pParaObj; /* @@ -3943,6 +3974,9 @@ void DrawingML::WriteText(const Reference<XInterface>& rXIface, bool bBodyPr, bo // this is reached only in case some text is attached to the shape mpTextExport->WriteOutliner(*pParaObj); } + + if (bIsFontworkShape) + FontworkHelpers::applyPropsToRuns(aOldCharFillPropVec, xXText); return; } |