oox/Library_oox.mk | 1 oox/inc/drawingml/fontworkhelpers.hxx | 44 ++ oox/qa/unit/data/tdf125885_WordArt.docx |binary oox/qa/unit/data/tdf125885_WordArt2.docx |binary oox/qa/unit/data/tdf125885_WordArt3.docx |binary oox/qa/unit/shape.cxx | 189 +++++++++ oox/source/drawingml/fontworkhelpers.cxx | 172 ++++++++ oox/source/drawingml/shape.cxx | 154 ------- oox/source/export/drawingml.cxx | 3 oox/source/shape/WpsContext.cxx | 607 ++++++++++++++++++++++++++++++- oox/source/shape/WpsContext.hxx | 1 11 files changed, 1013 insertions(+), 158 deletions(-)
New commits: commit cbf30153a5c776e6d1ee26f2f83c8f77503eceb9 Author: Regina Henschel <rb.hensc...@t-online.de> AuthorDate: Sat Dec 3 15:37:49 2022 +0100 Commit: Miklos Vajna <vmik...@collabora.com> CommitDate: Mon Jan 2 08:19:37 2023 +0000 tdf#125885 Conversion WordArt to Fontwork in docx import docx has the information, that a shape is a WordArt shape, after the text content. So in import of such file there is already a frame attached to the shape, which makes it impossible to set it into text path mode. The patch detects that it should be a WordArt shape. It transfers the text from frame to shape, removes the frame and then sets the shape into text path mode. WordArt in OOXML has the same closed set of types as we have for MS binary import. But MS Word can combine them with arbitrary shapes. The patch does only convert rectangles. The text is copied from frame to the shape as string. Thus it looses all styles. But our Fontwork cannot use different styles for portions of text, so I think that is acceptable. Fontwork uses not the styles of the text but styles set at the shape. The patch copies the styles from the first not empty run. That should give sufficient results in most cases. These text styles are set at the shape, which will result in a paragraph style referenced by the draw:text-style-name attribute of the draw:custom-shape element in ODF. The patch does not yet include export to docx. The current 'restore old shape' on resave to docx is lost. ToDo: Patch for export. Change-Id: I880ee7c7616db50524032e4b1443730a2d0a7361 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/143615 Tested-by: Jenkins Reviewed-by: Miklos Vajna <vmik...@collabora.com> diff --git a/oox/Library_oox.mk b/oox/Library_oox.mk index 6e031a35c5a0..aba27c786f8d 100644 --- a/oox/Library_oox.mk +++ b/oox/Library_oox.mk @@ -165,6 +165,7 @@ $(eval $(call gb_Library_add_exception_objects,oox,\ oox/source/drawingml/effectpropertiescontext \ oox/source/drawingml/embeddedwavaudiofile \ oox/source/drawingml/fillproperties \ + oox/source/drawingml/fontworkhelpers \ oox/source/drawingml/misccontexts \ oox/source/drawingml/graphicshapecontext \ oox/source/drawingml/guidcontext \ diff --git a/oox/inc/drawingml/fontworkhelpers.hxx b/oox/inc/drawingml/fontworkhelpers.hxx new file mode 100755 index 000000000000..2a48f422abbf --- /dev/null +++ b/oox/inc/drawingml/fontworkhelpers.hxx @@ -0,0 +1,44 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + +#include <rtl/ustring.hxx> +#include <sal/types.h> + +#include "customshapeproperties.hxx" + +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/drawing/XShape.hpp> + +namespace FontworkHelpers +{ +/** Removes the property specified by rName from the rPropVec vector of properties */ +void resetPropertyValueInVec(std::vector<css::beans::PropertyValue>& rPropVec, + const OUString& rName); + +/** Changes the EnhancedCustomShapeGeometry of xShape shape so, that it becomes a LO Fontwork + shape corresponding to the OOXML shape specified by sMSPresetType. + Precondition: xShape is a custom shape.*/ +void putCustomShapeIntoTextPathMode( + const css::uno::Reference<css::drawing::XShape>& xShape, + const oox::drawingml::CustomShapePropertiesPtr& pCustomShapePropertiesPtr, + const OUString& sMSPresetType, const bool bFromWordArt); +} +/* 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/tdf125885_WordArt.docx b/oox/qa/unit/data/tdf125885_WordArt.docx new file mode 100644 index 000000000000..1aeecc3f0394 Binary files /dev/null and b/oox/qa/unit/data/tdf125885_WordArt.docx differ diff --git a/oox/qa/unit/data/tdf125885_WordArt2.docx b/oox/qa/unit/data/tdf125885_WordArt2.docx new file mode 100644 index 000000000000..9e00e46d4d51 Binary files /dev/null and b/oox/qa/unit/data/tdf125885_WordArt2.docx differ diff --git a/oox/qa/unit/data/tdf125885_WordArt3.docx b/oox/qa/unit/data/tdf125885_WordArt3.docx new file mode 100644 index 000000000000..f14b54644e40 Binary files /dev/null and b/oox/qa/unit/data/tdf125885_WordArt3.docx differ diff --git a/oox/qa/unit/shape.cxx b/oox/qa/unit/shape.cxx index 21261dc39930..bc341faf9683 100644 --- a/oox/qa/unit/shape.cxx +++ b/oox/qa/unit/shape.cxx @@ -13,16 +13,26 @@ #include <string_view> +#include <com/sun/star/awt/FontWeight.hpp> +#include <com/sun/star/awt/Gradient.hpp> #include <com/sun/star/awt/Size.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/drawing/FillStyle.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/TextHorizontalAdjust.hpp> #include <com/sun/star/drawing/TextVerticalAdjust.hpp> #include <com/sun/star/drawing/XDrawPagesSupplier.hpp> -#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/lang/Locale.hpp> +#include <com/sun/star/text/XTextFrame.hpp> #include <com/sun/star/text/XTextRange.hpp> +#include <comphelper/sequenceashashmap.hxx> #include <officecfg/Office/Common.hxx> #include <rtl/math.hxx> #include <svx/svdoashp.hxx> +#include <tools/color.hxx> using namespace ::com::sun::star; @@ -313,6 +323,183 @@ CPPUNIT_TEST_FIXTURE(OoxShapeTest, testTdf54095_SmartArtThemeTextColor) } } +CPPUNIT_TEST_FIXTURE(OoxShapeTest, testWriterFontwork) +{ + loadFromURL(u"tdf125885_WordArt.docx"); + // Without the patch WordArt in text document was imported as rectangular custome shape with + // attached frame. So there was no artistic text at all. Now it is imported as Fontwork. + // This test covers some basic properties. + + uno::Reference<drawing::XDrawPagesSupplier> xDrawPagesSupplier(mxComponent, uno::UNO_QUERY); + uno::Reference<drawing::XDrawPage> xDrawPage(xDrawPagesSupplier->getDrawPages()->getByIndex(0), + uno::UNO_QUERY); + uno::Reference<beans::XPropertySet> xShapeProps(xDrawPage->getByIndex(0), uno::UNO_QUERY); + + // Is it a Fontwork? + bool bTextBox; + xShapeProps->getPropertyValue(u"TextBox") >>= bTextBox; + CPPUNIT_ASSERT(!bTextBox); + + uno::Reference<css::text::XTextFrame> xTextFrame; + xShapeProps->getPropertyValue(u"TextBoxContent") >>= xTextFrame; + CPPUNIT_ASSERT(!xTextFrame.is()); + + uno::Sequence<beans::PropertyValue> aGeoPropSeq; + xShapeProps->getPropertyValue(u"CustomShapeGeometry") >>= aGeoPropSeq; + CPPUNIT_ASSERT(aGeoPropSeq.getLength() > 0); + comphelper::SequenceAsHashMap aGeoPropMap(aGeoPropSeq); + + uno::Sequence<beans::PropertyValue> aTextPathSeq; + aGeoPropMap.getValue(u"TextPath") >>= aTextPathSeq; + CPPUNIT_ASSERT(aTextPathSeq.getLength() > 0); + + comphelper::SequenceAsHashMap aTextPathPropMap(aTextPathSeq); + bool bTextPathOn; + aTextPathPropMap.getValue(u"TextPath") >>= bTextPathOn; + CPPUNIT_ASSERT(bTextPathOn); + + // Is it the correct kind of Fontwork? + uno::Sequence<drawing::EnhancedCustomShapeAdjustmentValue> aAdjustmentSeq; + aGeoPropMap.getValue(u"AdjustmentValues") >>= aAdjustmentSeq; + CPPUNIT_ASSERT_EQUAL(sal_Int32(2), aAdjustmentSeq.getLength()); + + uno::Sequence<uno::Sequence<beans::PropertyValue>> aHandleSeq; + aGeoPropMap.getValue(u"Handles") >>= aHandleSeq; + CPPUNIT_ASSERT_EQUAL(sal_Int32(2), aHandleSeq.getLength()); + + awt::Rectangle aViewBox; + aGeoPropMap.getValue(u"ViewBox") >>= aViewBox; + CPPUNIT_ASSERT_EQUAL(sal_Int32(21600), aViewBox.Width); + CPPUNIT_ASSERT_EQUAL(sal_Int32(21600), aViewBox.Height); + + CPPUNIT_ASSERT_EQUAL(uno::Any(OUString(u"textDoubleWave1")), + aGeoPropMap.getValue(u"PresetTextWarp")); + + CPPUNIT_ASSERT_EQUAL(uno::Any(OUString(u"mso-spt158")), aGeoPropMap.getValue(u"Type")); + + // Are properties correctly copied to shape? + CPPUNIT_ASSERT_EQUAL(uno::Any(Color(0, 0, 255)), xShapeProps->getPropertyValue(u"FillColor")); + + CPPUNIT_ASSERT_EQUAL(uno::Any(drawing::FillStyle_SOLID), + xShapeProps->getPropertyValue(u"FillStyle")); + + CPPUNIT_ASSERT_EQUAL(uno::Any(OUString(u"Courier New")), + xShapeProps->getPropertyValue(u"CharFontName")); + + CPPUNIT_ASSERT_EQUAL(uno::Any(float(awt::FontWeight::BOLD)), + xShapeProps->getPropertyValue("CharWeight")); + + lang::Locale aCharLocale; + xShapeProps->getPropertyValue(u"CharLocale") >>= aCharLocale; + CPPUNIT_ASSERT_EQUAL(OUString("en"), aCharLocale.Language); + CPPUNIT_ASSERT_EQUAL(OUString("US"), aCharLocale.Country); + + CPPUNIT_ASSERT_EQUAL(uno::Any(drawing::TextHorizontalAdjust_RIGHT), + xShapeProps->getPropertyValue(u"TextHorizontalAdjust")); +} + +CPPUNIT_TEST_FIXTURE(OoxShapeTest, testWriterFontwork2) +{ + loadFromURL(u"tdf125885_WordArt2.docx"); + // Without the patch WordArt in text document was imported as rectangular custome shape with + // attached frame. So there was no artistic text at all. Now it is imported as Fontwork. + // This test covers whether theme color properties are correctly converted on import. + + uno::Reference<drawing::XDrawPagesSupplier> xDrawPagesSupplier(mxComponent, uno::UNO_QUERY); + uno::Reference<drawing::XDrawPage> xDrawPage(xDrawPagesSupplier->getDrawPages()->getByIndex(0), + uno::UNO_QUERY); + uno::Reference<beans::XPropertySet> xShapeProps(xDrawPage->getByIndex(0), uno::UNO_QUERY); + + // Fill + CPPUNIT_ASSERT_EQUAL(uno::Any(sal_Int16(4000)), + xShapeProps->getPropertyValue(u"FillColorLumMod")); + CPPUNIT_ASSERT_EQUAL(uno::Any(sal_Int16(6000)), + xShapeProps->getPropertyValue(u"FillColorLumOff")); + // ID "6" for the theme "accent3" is not yet in API, but defined in enum PredefinedClrSchemeID + // in drawingml/clrscheme.hxx. + CPPUNIT_ASSERT_EQUAL(uno::Any(sal_Int16(6)), xShapeProps->getPropertyValue(u"FillColorTheme")); + CPPUNIT_ASSERT_EQUAL(uno::Any(Color(215, 228, 189)), + xShapeProps->getPropertyValue(u"FillColor")); + + // Stroke + CPPUNIT_ASSERT_EQUAL(uno::Any(drawing::LineCap_ROUND), + xShapeProps->getPropertyValue(u"LineCap")); + CPPUNIT_ASSERT_EQUAL(uno::Any(drawing::LineStyle_DASH), + xShapeProps->getPropertyValue(u"LineStyle")); + // Stroke has only the resulted color, but no reference to the used theme color "accent2". + CPPUNIT_ASSERT_EQUAL(uno::Any(Color(149, 55, 53)), xShapeProps->getPropertyValue(u"LineColor")); + drawing::LineDash aLineDash; + xShapeProps->getPropertyValue(u"LineDash") >>= aLineDash; + CPPUNIT_ASSERT_EQUAL(drawing::DashStyle_ROUNDRELATIVE, aLineDash.Style); + CPPUNIT_ASSERT_EQUAL(sal_Int16(1), aLineDash.Dots); + CPPUNIT_ASSERT_EQUAL(sal_Int32(1), aLineDash.DotLen); + CPPUNIT_ASSERT_EQUAL(sal_Int16(0), aLineDash.Dashes); + CPPUNIT_ASSERT_EQUAL(sal_Int32(0), aLineDash.DashLen); + CPPUNIT_ASSERT_EQUAL(sal_Int32(199), aLineDash.Distance); + CPPUNIT_ASSERT_EQUAL(uno::Any(sal_Int32(635)), xShapeProps->getPropertyValue(u"LineWidth")); + CPPUNIT_ASSERT_EQUAL(uno::Any(sal_Int16(20)), + xShapeProps->getPropertyValue(u"LineTransparence")); + CPPUNIT_ASSERT_EQUAL(uno::Any(drawing::LineJoint_BEVEL), + xShapeProps->getPropertyValue(u"LineJoint")); +} + +CPPUNIT_TEST_FIXTURE(OoxShapeTest, testWriterFontwork3) +{ + loadFromURL(u"tdf125885_WordArt3.docx"); + // Without the patch WordArt in text document was imported as rectangular custome shape with + // attached frame. So there was no artistic text at all. Now it is imported as Fontwork. + // This test covers some aspects of import of gradient fill. + + uno::Reference<drawing::XDrawPagesSupplier> xDrawPagesSupplier(mxComponent, uno::UNO_QUERY); + uno::Reference<drawing::XDrawPage> xDrawPage(xDrawPagesSupplier->getDrawPages()->getByIndex(0), + uno::UNO_QUERY); + + // linear gradient, MSO UI 21deg + { + uno::Reference<beans::XPropertySet> xShapeProps(xDrawPage->getByIndex(0), uno::UNO_QUERY); + CPPUNIT_ASSERT_EQUAL(uno::Any(drawing::FillStyle_GRADIENT), + xShapeProps->getPropertyValue(u"FillStyle")); + awt::Gradient aGradient; + xShapeProps->getPropertyValue(u"FillGradient") >>= aGradient; + CPPUNIT_ASSERT_EQUAL(sal_Int16(690), aGradient.Angle); + CPPUNIT_ASSERT_EQUAL(sal_Int32(255), aGradient.StartColor); + CPPUNIT_ASSERT_EQUAL(sal_Int32(16225862), aGradient.EndColor); + CPPUNIT_ASSERT_EQUAL(sal_Int16(0), aGradient.XOffset); + CPPUNIT_ASSERT_EQUAL(sal_Int16(0), aGradient.YOffset); + CPPUNIT_ASSERT_EQUAL(awt::GradientStyle_LINEAR, aGradient.Style); + } + + // radial gradient, centered + { + uno::Reference<beans::XPropertySet> xShapeProps(xDrawPage->getByIndex(1), uno::UNO_QUERY); + CPPUNIT_ASSERT_EQUAL(uno::Any(drawing::FillStyle_GRADIENT), + xShapeProps->getPropertyValue(u"FillStyle")); + awt::Gradient aGradient; + xShapeProps->getPropertyValue(u"FillGradient") >>= aGradient; + CPPUNIT_ASSERT_EQUAL(sal_Int16(900), aGradient.Angle); + CPPUNIT_ASSERT_EQUAL(sal_Int32(255), aGradient.EndColor); + CPPUNIT_ASSERT_EQUAL(sal_Int32(16225862), aGradient.StartColor); + CPPUNIT_ASSERT_EQUAL(sal_Int16(50), aGradient.XOffset); + CPPUNIT_ASSERT_EQUAL(sal_Int16(50), aGradient.YOffset); + CPPUNIT_ASSERT_EQUAL(awt::GradientStyle_RADIAL, aGradient.Style); + } + + // rectangular gradient, focus right, bottom + { + uno::Reference<beans::XPropertySet> xShapeProps(xDrawPage->getByIndex(2), uno::UNO_QUERY); + CPPUNIT_ASSERT_EQUAL(uno::Any(drawing::FillStyle_GRADIENT), + xShapeProps->getPropertyValue(u"FillStyle")); + awt::Gradient aGradient; + xShapeProps->getPropertyValue(u"FillGradient") >>= aGradient; + CPPUNIT_ASSERT_EQUAL(sal_Int16(900), aGradient.Angle); + CPPUNIT_ASSERT_EQUAL(sal_Int32(255), aGradient.EndColor); + CPPUNIT_ASSERT_EQUAL(sal_Int32(16225862), aGradient.StartColor); + CPPUNIT_ASSERT_EQUAL(sal_Int16(100), aGradient.XOffset); + CPPUNIT_ASSERT_EQUAL(sal_Int16(100), aGradient.YOffset); + CPPUNIT_ASSERT_EQUAL(awt::GradientStyle_RECT, aGradient.Style); + } +} + CPPUNIT_PLUGIN_IMPLEMENT(); /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/oox/source/drawingml/fontworkhelpers.cxx b/oox/source/drawingml/fontworkhelpers.cxx new file mode 100755 index 000000000000..da430c33879f --- /dev/null +++ b/oox/source/drawingml/fontworkhelpers.cxx @@ -0,0 +1,172 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <drawingml/fontworkhelpers.hxx> + +#include <comphelper/propertysequence.hxx> +#include <comphelper/propertyvalue.hxx> +#include <comphelper/sequence.hxx> +#include <drawingml/customshapeproperties.hxx> +#include <drawingml/presetgeometrynames.hxx> +#include <tools/helpers.hxx> + +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/drawing/EnhancedCustomShapeAdjustmentValue.hpp> +#include <com/sun/star/drawing/EnhancedCustomShapeTextPathMode.hpp> +#include <com/sun/star/drawing/XEnhancedCustomShapeDefaulter.hpp> +#include <com/sun/star/drawing/XShape.hpp> + +using namespace com::sun::star; + +void FontworkHelpers::resetPropertyValueInVec(std::vector<beans::PropertyValue>& rPropVec, + const OUString& rName) +{ + auto aIterator = std::find_if( + rPropVec.begin(), rPropVec.end(), + [rName](const beans::PropertyValue& rValue) { return rValue.Name == rName; }); + + if (aIterator != rPropVec.end()) + rPropVec.erase(aIterator); +} + +void FontworkHelpers::putCustomShapeIntoTextPathMode( + const css::uno::Reference<drawing::XShape>& xShape, + const oox::drawingml::CustomShapePropertiesPtr& pCustomShapePropertiesPtr, + const OUString& sMSPresetType, const bool bFromWordArt) +{ + if (!xShape.is() || !pCustomShapePropertiesPtr || sMSPresetType == u"textNoShape") + return; + + uno::Reference<drawing::XEnhancedCustomShapeDefaulter> xDefaulter(xShape, uno::UNO_QUERY); + if (!xDefaulter.is()) + return; + + uno::Reference<beans::XPropertySet> xSet(xShape, uno::UNO_QUERY); + if (!xSet.is()) + return; + + // The DrawingML shapes from the presetTextWarpDefinitions are mapped to the definitions + // in svx/../EnhancedCustomShapeGeometry.cxx, which are used for WordArt shapes from + // binary MS Office. Therefore all adjustment values need to be adapted. + const OUString sFontworkType = PresetGeometryTypeNames::GetFontworkType(sMSPresetType); + auto aAdjGdList = pCustomShapePropertiesPtr->getAdjustmentGuideList(); + uno::Sequence<drawing::EnhancedCustomShapeAdjustmentValue> aAdjustment( + !aAdjGdList.empty() ? aAdjGdList.size() : 1); + auto pAdjustment = aAdjustment.getArray(); + int nIndex = 0; + for (const auto& aEntry : aAdjGdList) + { + double fValue = aEntry.maFormula.toDouble(); + // then: polar-handle, else: XY-handle + // There exist only 8 polar-handles at all in presetTextWarp. + if ((sFontworkType == "fontwork-arch-down-curve") + || (sFontworkType == "fontwork-arch-down-pour" && aEntry.maName == "adj1") + || (sFontworkType == "fontwork-arch-up-curve") + || (sFontworkType == "fontwork-arch-up-pour" && aEntry.maName == "adj1") + || (sFontworkType == "fontwork-open-circle-curve") + || (sFontworkType == "fontwork-open-circle-pour" && aEntry.maName == "adj1") + || (sFontworkType == "fontwork-circle-curve") + || (sFontworkType == "fontwork-circle-pour" && aEntry.maName == "adj1")) + { + // DrawingML has 1/60000 degree unit, but WordArt simple degree. Range [0..360[ + // or range ]-180..180] doesn't matter, because only cos(angle) and + // sin(angle) are used. + fValue = NormAngle360(fValue / 60000.0); + } + else + { + // DrawingML writes adjustment guides as relative value with 100% = 100000, + // but WordArt definitions use values absolute in viewBox 0 0 21600 21600, + // so scale with 21600/100000 = 0.216, with two exceptions: + // X-handles of waves describe increase/decrease relative to horizontal center. + // The gdRefR of pour-shapes is not relative to viewBox but to radius. + if ((sFontworkType == "mso-spt158" && aEntry.maName == "adj2") // textDoubleWave1 + || (sFontworkType == "fontwork-wave" && aEntry.maName == "adj2") // textWave1 + || (sFontworkType == "mso-spt157" && aEntry.maName == "adj2") // textWave2 + || (sFontworkType == "mso-spt159" && aEntry.maName == "adj2")) // textWave4 + { + fValue = (fValue + 50000.0) * 0.216; + } + else if ((sFontworkType == "fontwork-arch-down-pour" && aEntry.maName == "adj2") + || (sFontworkType == "fontwork-arch-up-pour" && aEntry.maName == "adj2") + || (sFontworkType == "fontwork-open-circle-pour" && aEntry.maName == "adj2") + || (sFontworkType == "fontwork-circle-pour" && aEntry.maName == "adj2")) + { + fValue *= 0.108; + } + else + { + fValue *= 0.216; + } + } + + pAdjustment[nIndex].Value <<= fValue; + pAdjustment[nIndex++].State = css::beans::PropertyState_DIRECT_VALUE; + } + + // Set attributes in CustomShapeGeometry + xDefaulter->createCustomShapeDefaults(sFontworkType); + + auto aGeomPropSeq + = xSet->getPropertyValue("CustomShapeGeometry").get<uno::Sequence<beans::PropertyValue>>(); + auto aGeomPropVec + = comphelper::sequenceToContainer<std::vector<beans::PropertyValue>>(aGeomPropSeq); + + // Reset old properties + static const OUStringLiteral sTextPath(u"TextPath"); + static const OUStringLiteral sAdjustmentValues(u"AdjustmentValues"); + static const OUStringLiteral sPresetTextWarp(u"PresetTextWarp"); + + resetPropertyValueInVec(aGeomPropVec, u"CoordinateSize"); + resetPropertyValueInVec(aGeomPropVec, u"Equations"); + resetPropertyValueInVec(aGeomPropVec, u"Path"); + resetPropertyValueInVec(aGeomPropVec, sAdjustmentValues); + resetPropertyValueInVec(aGeomPropVec, u"ViewBox"); + resetPropertyValueInVec(aGeomPropVec, u"Handles"); + resetPropertyValueInVec(aGeomPropVec, sTextPath); + resetPropertyValueInVec(aGeomPropVec, sPresetTextWarp); + + bool bScaleX(false); + if (!bFromWordArt + && (sMSPresetType == u"textArchDown" || sMSPresetType == u"textArchUp" + || sMSPresetType == u"textCircle" || sMSPresetType == u"textButton")) + { + bScaleX = true; + } + + // Apply new properties + uno::Sequence<beans::PropertyValue> aPropertyValues(comphelper::InitPropertySequence( + { { sTextPath, uno::Any(true) }, + { u"TextPathMode", uno::Any(drawing::EnhancedCustomShapeTextPathMode_PATH) }, + { u"ScaleX", uno::Any(bScaleX) } })); + aGeomPropVec.push_back(comphelper::makePropertyValue(sTextPath, aPropertyValues)); + + aGeomPropVec.push_back(comphelper::makePropertyValue(sPresetTextWarp, sMSPresetType)); + + if (!aAdjGdList.empty()) + { + aGeomPropVec.push_back(comphelper::makePropertyValue(sAdjustmentValues, aAdjustment)); + } + + xSet->setPropertyValue(u"CustomShapeGeometry", + uno::Any(comphelper::containerToSequence(aGeomPropVec))); +} + +/* 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/drawingml/shape.cxx b/oox/source/drawingml/shape.cxx index 7c2920eabcbf..fcc1e9016170 100644 --- a/oox/source/drawingml/shape.cxx +++ b/oox/source/drawingml/shape.cxx @@ -23,6 +23,7 @@ #include <drawingml/customshapeproperties.hxx> #include <oox/drawingml/theme.hxx> #include <drawingml/fillproperties.hxx> +#include <drawingml/fontworkhelpers.hxx> #include <drawingml/graphicproperties.hxx> #include <drawingml/lineproperties.hxx> #include <drawingml/presetgeometrynames.hxx> @@ -529,24 +530,6 @@ void Shape::addChildren( } } -static void lcl_resetPropertyValue( std::vector<beans::PropertyValue>& rPropVec, const OUString& rName ) -{ - auto aIterator = std::find_if( rPropVec.begin(), rPropVec.end(), - [rName]( const beans::PropertyValue& rValue ) { return rValue.Name == rName; } ); - - if (aIterator != rPropVec.end()) - rPropVec.erase( aIterator ); -} - -static void lcl_setPropertyValue( std::vector<beans::PropertyValue>& rPropVec, - const OUString& rName, - const beans::PropertyValue& rPropertyValue ) -{ - lcl_resetPropertyValue( rPropVec, rName ); - - rPropVec.push_back( rPropertyValue ); -} - static SdrTextHorzAdjust lcl_convertAdjust( ParagraphAdjust eAdjust ) { if (eAdjust == ParagraphAdjust_LEFT) @@ -558,134 +541,6 @@ static SdrTextHorzAdjust lcl_convertAdjust( ParagraphAdjust eAdjust ) return SDRTEXTHORZADJUST_LEFT; } -static void -lcl_putCustomShapeIntoTextPathMode(const uno::Reference<drawing::XShape>& xShape, - const CustomShapePropertiesPtr& pCustomShapePropertiesPtr, - const TextBodyPtr& pTextBody) -{ - if (!xShape.is() || !pCustomShapePropertiesPtr || !pTextBody) - return; - - uno::Reference<drawing::XEnhancedCustomShapeDefaulter> xDefaulter(xShape, uno::UNO_QUERY); - if (!xDefaulter.is()) - return; - - Reference<XPropertySet> xSet(xShape, UNO_QUERY); - if (!xSet.is()) - return; - - const OUString sMSPresetType = pTextBody->getTextProperties().msPrst; - const OUString sFontworkType = PresetGeometryTypeNames::GetFontworkType(sMSPresetType); - - // The DrawingML shapes from the presetTextWarpDefinitions are mapped to the definitions - // in svx/../EnhancedCustomShapeGeometry.cxx, which are used for WordArt shapes from - // binary MS Office. Therefore all adjustment values need to be adapted. - auto aAdjGdList = pCustomShapePropertiesPtr->getAdjustmentGuideList(); - Sequence<drawing::EnhancedCustomShapeAdjustmentValue> aAdjustment( - !aAdjGdList.empty() ? aAdjGdList.size() : 1); - auto pAdjustment = aAdjustment.getArray(); - int nIndex = 0; - for (const auto& aEntry : aAdjGdList) - { - double fValue = aEntry.maFormula.toDouble(); - // then: polar-handle, else: XY-handle - // There exist only 8 polar-handles at all in presetTextWarp. - if ((sFontworkType == "fontwork-arch-down-curve") - || (sFontworkType == "fontwork-arch-down-pour" && aEntry.maName == "adj1") - || (sFontworkType == "fontwork-arch-up-curve") - || (sFontworkType == "fontwork-arch-up-pour" && aEntry.maName == "adj1") - || (sFontworkType == "fontwork-open-circle-curve") - || (sFontworkType == "fontwork-open-circle-pour" && aEntry.maName == "adj1") - || (sFontworkType == "fontwork-circle-curve") - || (sFontworkType == "fontwork-circle-pour" && aEntry.maName == "adj1")) - { - // DrawingML has 1/60000 degree unit, but WordArt simple degree. Range [0..360[ - // or range ]-180..180] doesn't matter, because only cos(angle) and - // sin(angle) are used. - fValue = NormAngle360(fValue / 60000.0); - } - else - { - // DrawingML writes adjustment guides as relative value with 100% = 100000, - // but WordArt definitions use values absolute in viewBox 0 0 21600 21600, - // so scale with 21600/100000 = 0.216, with two exceptions: - // X-handles of waves describe increase/decrease relative to horizontal center. - // The gdRefR of pour-shapes is not relative to viewBox but to radius. - if ((sFontworkType == "mso-spt158" && aEntry.maName == "adj2") // textDoubleWave1 - || (sFontworkType == "fontwork-wave" && aEntry.maName == "adj2") // textWave1 - || (sFontworkType == "mso-spt157" && aEntry.maName == "adj2") // textWave2 - || (sFontworkType == "mso-spt159" && aEntry.maName == "adj2")) // textWave4 - { - fValue = (fValue + 50000.0) * 0.216; - } - else if ((sFontworkType == "fontwork-arch-down-pour" && aEntry.maName == "adj2") - || (sFontworkType == "fontwork-arch-up-pour" && aEntry.maName == "adj2") - || (sFontworkType == "fontwork-open-circle-pour" && aEntry.maName == "adj2") - || (sFontworkType == "fontwork-circle-pour" && aEntry.maName == "adj2")) - { - fValue *= 0.108; - } - else - { - fValue *= 0.216; - } - } - - pAdjustment[nIndex].Value <<= fValue; - pAdjustment[nIndex++].State = css::beans::PropertyState_DIRECT_VALUE; - } - - // Set attributes in CustomShapeGeometry - xDefaulter->createCustomShapeDefaults(sFontworkType); - - auto aGeomPropSeq - = xSet->getPropertyValue("CustomShapeGeometry").get<uno::Sequence<beans::PropertyValue>>(); - auto aGeomPropVec - = comphelper::sequenceToContainer<std::vector<beans::PropertyValue>>(aGeomPropSeq); - - // Reset old properties - static const OUStringLiteral sTextPath(u"TextPath"); - static const OUStringLiteral sAdjustmentValues(u"AdjustmentValues"); - static const OUStringLiteral sPresetTextWarp(u"PresetTextWarp"); - - lcl_resetPropertyValue(aGeomPropVec, "CoordinateSize"); - lcl_resetPropertyValue(aGeomPropVec, "Equations"); - lcl_resetPropertyValue(aGeomPropVec, "Path"); - lcl_resetPropertyValue(aGeomPropVec, sAdjustmentValues); - - bool bFromWordArt(false); - pTextBody->getTextProperties().maPropertyMap.getProperty(PROP_FromWordArt) >>= bFromWordArt; - - bool bScaleX(false); - if (!bFromWordArt - && (sMSPresetType == "textArchDown" || sMSPresetType == "textArchUp" - || sMSPresetType == "textCircle" || sMSPresetType == "textButton")) - { - bScaleX = true; - } - - // Apply new properties - uno::Sequence<beans::PropertyValue> aPropertyValues(comphelper::InitPropertySequence( - { { sTextPath, uno::Any(true) }, - { "TextPathMode", uno::Any(drawing::EnhancedCustomShapeTextPathMode_PATH) }, - { "ScaleX", uno::Any(bScaleX) } })); - - lcl_setPropertyValue(aGeomPropVec, sTextPath, - comphelper::makePropertyValue(sTextPath, aPropertyValues)); - - lcl_setPropertyValue(aGeomPropVec, sPresetTextWarp, - comphelper::makePropertyValue(sPresetTextWarp, sMSPresetType)); - - if (!aAdjGdList.empty()) - { - lcl_setPropertyValue(aGeomPropVec, sAdjustmentValues, - comphelper::makePropertyValue(sAdjustmentValues, aAdjustment)); - } - - xSet->setPropertyValue("CustomShapeGeometry", - uno::Any(comphelper::containerToSequence(aGeomPropVec))); -} - // LO does not interpret properties in styles belonging to the text content of a FontWork shape, // but only those in the shape style. This method copies properties from the text content styles to // the shape style. @@ -1914,7 +1769,12 @@ Reference< XShape > const & Shape::createAndInsert( if (mpTextBody && !mpTextBody->getTextProperties().msPrst.isEmpty() && mpTextBody->getTextProperties().msPrst != u"textNoShape") { - lcl_putCustomShapeIntoTextPathMode(mxShape, mpCustomShapePropertiesPtr, mpTextBody); + bool bFromWordArt(aShapeProps.hasProperty(PROP_FromWordArt) + ? aShapeProps.getProperty(PROP_FromWordArt).get<bool>() + : false); + FontworkHelpers::putCustomShapeIntoTextPathMode( + mxShape, mpCustomShapePropertiesPtr, mpTextBody->getTextProperties().msPrst, + bFromWordArt); } } else if( getTextBody() ) diff --git a/oox/source/export/drawingml.cxx b/oox/source/export/drawingml.cxx index 86b9b5ac91dc..b98a785eb582 100644 --- a/oox/source/export/drawingml.cxx +++ b/oox/source/export/drawingml.cxx @@ -3784,7 +3784,8 @@ void DrawingML::WriteText(const Reference<XInterface>& rXIface, bool bBodyPr, bo // TextShape's automatic word wrapping, then we need to set // wrapping to square. uno::Reference<lang::XServiceInfo> xServiceInfo(rXIface, uno::UNO_QUERY); - if (xServiceInfo.is() && xServiceInfo->supportsService("com.sun.star.drawing.TextShape")) + if ((xServiceInfo.is() && xServiceInfo->supportsService("com.sun.star.drawing.TextShape")) + || bIsFontworkShape) pWrap = "square"; } diff --git a/oox/source/shape/WpsContext.cxx b/oox/source/shape/WpsContext.cxx index 99656195075b..86e02d24b146 100644 --- a/oox/source/shape/WpsContext.cxx +++ b/oox/source/shape/WpsContext.cxx @@ -11,30 +11,528 @@ #include "WpgContext.hxx" #include <basegfx/matrix/b2dhommatrix.hxx> #include <basegfx/tuple/b2dtuple.hxx> +#include <comphelper/propertyvalue.hxx> +#include <comphelper/sequence.hxx> #include <comphelper/sequenceashashmap.hxx> +#include <drawingml/customshapegeometry.hxx> #include <drawingml/customshapeproperties.hxx> +#include <drawingml/fontworkhelpers.hxx> +#include <drawingml/textbody.hxx> +#include <drawingml/textbodyproperties.hxx> +#include <oox/drawingml/color.hxx> +#include <oox/drawingml/drawingmltypes.hxx> +#include <oox/drawingml/shape.hxx> +#include <oox/drawingml/shapepropertymap.hxx> +#include <oox/helper/attributelist.hxx> +#include <oox/token/namespaces.hxx> +#include <oox/token/tokens.hxx> +#include <svx/svdoashp.hxx> + +#include <com/sun/star/beans/PropertyAttribute.hpp> #include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/beans/XPropertySetInfo.hpp> #include <com/sun/star/beans/XPropertyState.hpp> #include <com/sun/star/container/XEnumerationAccess.hpp> +#include <com/sun/star/drawing/FillStyle.hpp> #include <com/sun/star/drawing/HomogenMatrix3.hpp> +#include <com/sun/star/drawing/LineStyle.hpp> +#include <com/sun/star/drawing/TextHorizontalAdjust.hpp> +#include <com/sun/star/geometry/IntegerRectangle2D.hpp> #include <com/sun/star/lang/XServiceInfo.hpp> #include <com/sun/star/text/XText.hpp> #include <com/sun/star/text/XTextCursor.hpp> #include <com/sun/star/text/WritingMode.hpp> #include <com/sun/star/text/WritingMode2.hpp> -#include <oox/helper/attributelist.hxx> -#include <oox/token/namespaces.hxx> -#include <oox/token/tokens.hxx> -#include <oox/drawingml/shape.hxx> -#include <oox/drawingml/drawingmltypes.hxx> -#include <drawingml/textbody.hxx> -#include <drawingml/textbodyproperties.hxx> -#include <tools/helpers.hxx> #include <optional> using namespace com::sun::star; +namespace +{ +bool lcl_getTextPropsFromFrameText(const uno::Reference<text::XText>& xText, + std::vector<beans::PropertyValue>& rTextPropVec) +{ + if (!xText.is()) + return false; + uno::Reference<text::XTextCursor> xTextCursor = xText->createTextCursor(); + xTextCursor->gotoStart(false); + xTextCursor->gotoEnd(true); + uno::Reference<container::XEnumerationAccess> paraEnumAccess(xText, uno::UNO_QUERY); + if (!paraEnumAccess.is()) + return false; + 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 properties. + auto aRunPropInfoSequence = xRunPropSetInfo->getProperties(); + for (const beans::Property& aProp : aRunPropInfoSequence) + { + rTextPropVec.push_back(comphelper::makePropertyValue( + aProp.Name, xRunPropSet->getPropertyValue(aProp.Name))); + } + return true; + } + } + return false; +} + +// CharInteropGrabBag puts all attributes of an element into a property with Name="attributes" and +// Value being a sequence of the attributes. This methods finds the value of an individual rName +// attribute and puts it into rValue paramenter. If it does not find it, rValue is unchanged and +// the methode returns false, otherwise it returns true. +bool lcl_getAttributeAsString(const uno::Sequence<beans::PropertyValue>& aPropertyValueAsSeq, + const OUString& rName, OUString& rValue) +{ + comphelper::SequenceAsHashMap aPropertyValueAsMap(aPropertyValueAsSeq); + uno::Sequence<beans::PropertyValue> aAttributesSeq; + if (!((aPropertyValueAsMap.getValue("attributes") >>= aAttributesSeq) + && aAttributesSeq.hasElements())) + return false; + comphelper::SequenceAsHashMap aAttributesMap(aAttributesSeq); + OUString sRet; + if (!(aAttributesMap.getValue(rName) >>= sRet)) + return false; + rValue = sRet; + return true; +} + +// Same as above for a number as attribute value +bool lcl_getAttributeAsNumber(const uno::Sequence<beans::PropertyValue>& rPropertyValueAsSeq, + const OUString& rName, sal_Int32& rValue) +{ + comphelper::SequenceAsHashMap aPropertyValueAsMap(rPropertyValueAsSeq); + uno::Sequence<beans::PropertyValue> aAttributesSeq; + if (!((aPropertyValueAsMap.getValue("attributes") >>= aAttributesSeq) + && aAttributesSeq.hasElements())) + return false; + comphelper::SequenceAsHashMap aAttributesMap(aAttributesSeq); + sal_Int32 nRet; + if (!(aAttributesMap.getValue(rName) >>= nRet)) + return false; + rValue = nRet; + return true; +} + +void lcl_getColorTransformationsFromPropSeq(const uno::Sequence<beans::PropertyValue>& rPropSeq, + oox::drawingml::Color& rColor) +{ + auto isValidPropName = [](const OUString& rName) -> bool { + return rName == u"tint" || rName == u"shade" || rName == u"alpha" || rName == u"hueMod" + || rName == u"sat" || rName == u"satOff" || rName == u"satMod" || rName == u"lum" + || rName == u"lumOff" || rName == u"lumMod"; + }; + for (auto it = rPropSeq.begin(); it < rPropSeq.end(); ++it) + { + if (isValidPropName((*it).Name)) + { + uno::Sequence<beans::PropertyValue> aValueSeq; + sal_Int32 nNumber(0); // dummy value to make compiler happy, "val" should exist + if (((*it).Value >>= aValueSeq) && lcl_getAttributeAsNumber(aValueSeq, u"val", nNumber)) + { + // char w14:alpha contains transparency, whereas shape fill a:alpha contains opacity. + if ((*it).Name == u"alpha") + rColor.addTransformation( + oox::NMSP_dml | oox::AttributeConversion::decodeToken((*it).Name), + oox::drawingml::MAX_PERCENT - nNumber); + else + rColor.addTransformation( + oox::NMSP_w14 | oox::AttributeConversion::decodeToken((*it).Name), nNumber); + } + } + } +} + +// Expected: rPropSeq contains a property "schemeClr" or a property "srgbClr". +bool lcl_getColorFromPropSeq(const uno::Sequence<beans::PropertyValue>& rPropSeq, + oox::drawingml::Color& rColor) +{ + bool bColorFound = false; + comphelper::SequenceAsHashMap aPropMap(rPropSeq); + uno::Sequence<beans::PropertyValue> aColorDetailSeq; + if (aPropMap.getValue(u"schemeClr") >>= aColorDetailSeq) + { + OUString sColorString; + bColorFound = lcl_getAttributeAsString(aColorDetailSeq, u"val", sColorString); + if (bColorFound) + { + sal_Int32 nColorToken = oox::AttributeConversion::decodeToken(sColorString); + rColor.setSchemeClr(nColorToken); + rColor.setSchemeName(sColorString); + } + } + if (!bColorFound && (aPropMap.getValue(u"srgbClr") >>= aColorDetailSeq)) + { + OUString sColorString; + bColorFound = lcl_getAttributeAsString(aColorDetailSeq, u"val", sColorString); + if (bColorFound) + { + sal_Int32 nColor = oox::AttributeConversion::decodeIntegerHex(sColorString); + rColor.setSrgbClr(nColor); + } + } + // Without color, color transformations are pointless. + if (bColorFound) + lcl_getColorTransformationsFromPropSeq(aColorDetailSeq, rColor); + return bColorFound; +} + +void lcl_getFillDetailsFromPropSeq(const uno::Sequence<beans::PropertyValue>& rTextFillSeq, + oox::drawingml::FillProperties& rFillProperties) +{ + // rTextFillSeq should have an item containing either "noFill" or "solidFill" or "gradFill" + // property. + if (!rTextFillSeq.hasElements()) + return; + comphelper::SequenceAsHashMap aTextFillMap(rTextFillSeq); + if (aTextFillMap.find(u"noFill") != aTextFillMap.end()) + { + rFillProperties.moFillType = oox::XML_noFill; + return; + } + + uno::Sequence<beans::PropertyValue> aPropSeq; + if ((aTextFillMap.getValue(u"solidFill") >>= aPropSeq) && aPropSeq.hasElements()) + { + rFillProperties.moFillType = oox::XML_solidFill; + lcl_getColorFromPropSeq(aPropSeq, rFillProperties.maFillColor); + return; + } + + if ((aTextFillMap.getValue(u"gradFill") >>= aPropSeq) && aPropSeq.hasElements()) + { + rFillProperties.moFillType = oox::XML_gradFill; + // aPropSeq should have two items. One ist "gsLst" for the stop colors, the other is + // either "lin" or "path" for the kind of gradient. + // First get stop colors + comphelper::SequenceAsHashMap aPropMap(aPropSeq); + uno::Sequence<beans::PropertyValue> aGsLstSeq; + if (aPropMap.getValue("gsLst") >>= aGsLstSeq) + { + for (auto it = aGsLstSeq.begin(); it < aGsLstSeq.end(); ++it) + { + // (*it) is a bean::PropertyValue with Name="gs". Its Value is a property sequence. + uno::Sequence<beans::PropertyValue> aColorStopSeq; + if ((*it).Value >>= aColorStopSeq) + { + // aColorStopSeq should have an item for the color and an item for the position + sal_Int32 nPos; + oox::drawingml::Color aColor; + if (lcl_getAttributeAsNumber(aColorStopSeq, u"pos", nPos) + && lcl_getColorFromPropSeq(aColorStopSeq, aColor)) + { + // The position in maGradientStops is relative, thus in range [0.0;1.0]. + double fPos = nPos / 100000.0; + rFillProperties.maGradientProps.maGradientStops.insert({ fPos, aColor }); + } + } + } + } + // Now determine kind of gradient. + uno::Sequence<beans::PropertyValue> aKindSeq; + if (aPropMap.getValue("lin") >>= aKindSeq) + { + // aKindSeq contains the attributes "ang" and "scaled" + sal_Int32 nAngle; // in 1/60000 deg + if (lcl_getAttributeAsNumber(aKindSeq, "ang", nAngle)) + rFillProperties.maGradientProps.moShadeAngle = nAngle; + OUString sScaledString; + if (lcl_getAttributeAsString(aKindSeq, "scaled", sScaledString)) + rFillProperties.maGradientProps.moShadeScaled + = sScaledString == u"1" || sScaledString == u"true"; + return; + } + if (aPropMap.getValue("path") >>= aKindSeq) + { + // aKindSeq contains the attribute "path" for the kind of path and a property "fillToRect" + // which defines the center rectangle of the gradient. The property "a:tileRect" known from + // fill of shapes does not exist in w14 namespace. + OUString sKind; + if (lcl_getAttributeAsString(aKindSeq, "path", sKind)) + rFillProperties.maGradientProps.moGradientPath + = oox::AttributeConversion::decodeToken(sKind); + comphelper::SequenceAsHashMap aKindMap(aKindSeq); + uno::Sequence<beans::PropertyValue> aFillToRectSeq; + if (aKindMap.getValue("fillToRect") >>= aFillToRectSeq) + { + // The values l, t, r and b are not coordinates, but determine an offset from the + // edge of the bounding box of the shape. This unusual meaning of X1, Y1, X2 and + // Y2 is needed for method pushToPropMap() of FillProperties. + geometry::IntegerRectangle2D aRect; + if (!lcl_getAttributeAsNumber(aFillToRectSeq, u"l", aRect.X1)) + aRect.X1 = 0; + if (!lcl_getAttributeAsNumber(aFillToRectSeq, u"t", aRect.Y1)) + aRect.Y1 = 0; + if (!lcl_getAttributeAsNumber(aFillToRectSeq, u"r", aRect.X2)) + aRect.X2 = 0; + if (!lcl_getAttributeAsNumber(aFillToRectSeq, u"b", aRect.Y2)) + aRect.Y2 = 0; + rFillProperties.maGradientProps.moFillToRect = aRect; + } + } + return; + } +} + +void lcl_getLineDetailsFromPropSeq(const uno::Sequence<beans::PropertyValue>& rTextOutlineSeq, + oox::drawingml::LineProperties& rLineProperties) +{ + if (!rTextOutlineSeq.hasElements()) + { + rLineProperties.maLineFill.moFillType = oox::XML_noFill; // MS Office default + return; + } + // aTextOulineSeq contains e.g. "attributes" {w, cap, cmpd, ctr}, either + // "solidFill" or "gradFill or "noFill", and "prstDash" and "lineJoint" properties. + + // Fill + lcl_getFillDetailsFromPropSeq(rTextOutlineSeq, rLineProperties.maLineFill); + + // LineJoint + comphelper::SequenceAsHashMap aTextOutlineMap(rTextOutlineSeq); + if (aTextOutlineMap.find(u"bevel") != aTextOutlineMap.end()) + rLineProperties.moLineJoint = oox::XML_bevel; + else if (aTextOutlineMap.find(u"round") != aTextOutlineMap.end()) + rLineProperties.moLineJoint = oox::XML_round; + else if (aTextOutlineMap.find(u"miter") != aTextOutlineMap.end()) + { + // LineProperties has no member to store a miter limit. Therefore some heuristic is + // added here. 0 is default for attribute "lim" in MS Office. It is rendered same as bevel. + sal_Int32 nMiterLimit = aTextOutlineMap.getUnpackedValueOrDefault("lim", sal_Int32(0)); + if (nMiterLimit == 0) + rLineProperties.moLineJoint = oox::XML_bevel; + else + rLineProperties.moLineJoint = oox::XML_miter; + } + + // Dash + uno::Sequence<beans::PropertyValue> aDashSeq; + if (aTextOutlineMap.getValue(u"prstDash") >>= aDashSeq) + { + // aDashSeq contains the attribute "val" with the kind of dash, e.g. "sysDot" + OUString sDashKind; + if (lcl_getAttributeAsString(aDashSeq, u"val", sDashKind)) + rLineProperties.moPresetDash = oox::AttributeConversion::decodeToken(sDashKind); + } + OUString sCapKind; + if (lcl_getAttributeAsString(rTextOutlineSeq, u"cap", sCapKind)) + rLineProperties.moLineCap = oox::AttributeConversion::decodeToken(sCapKind); + + // Width + sal_Int32 nWidth; // EMU + if (lcl_getAttributeAsNumber(rTextOutlineSeq, u"w", nWidth)) + rLineProperties.moLineWidth = nWidth; + + // Compound. LineProperties has a member for it, however Fontwork can currently only render "sng". + OUString sCompoundKind; + if (lcl_getAttributeAsString(rTextOutlineSeq, u"cmpd", sCompoundKind)) + rLineProperties.moLineCompound = oox::AttributeConversion::decodeToken(sCompoundKind); + + // Align. LineProperties has no member for attribute "algn". + + return; +} + +oox::drawingml::LineProperties +lcl_generateLinePropertiesFromTextProps(const comphelper::SequenceAsHashMap& aTextPropMap) +{ + oox::drawingml::LineProperties aLineProperties; + aLineProperties.maLineFill.moFillType = oox::XML_noFill; // default + + // Get property "textOutline" from aTextPropMap + uno::Sequence<beans::PropertyValue> aCharInteropGrabBagSeq; + if (!(aTextPropMap.getValue(u"CharInteropGrabBag") >>= aCharInteropGrabBagSeq)) + return aLineProperties; + if (!aCharInteropGrabBagSeq.hasElements()) + return aLineProperties; + comphelper::SequenceAsHashMap aCharInteropGrabBagMap(aCharInteropGrabBagSeq); + beans::PropertyValue aProp; + if (!(aCharInteropGrabBagMap.getValue(u"CharTextOutlineTextEffect") >>= aProp)) + return aLineProperties; + uno::Sequence<beans::PropertyValue> aTextOutlineSeq; + if (!(aProp.Name == "textOutline" && (aProp.Value >>= aTextOutlineSeq) + && aTextOutlineSeq.hasElements())) + return aLineProperties; + + // Copy line properties from aTextOutlineSeq to aLineProperties + lcl_getLineDetailsFromPropSeq(aTextOutlineSeq, aLineProperties); + return aLineProperties; +} + +oox::drawingml::FillProperties +lcl_generateFillPropertiesFromTextProps(const comphelper::SequenceAsHashMap& rTextPropMap) +{ + oox::drawingml::FillProperties aFillProperties; + aFillProperties.moFillType = oox::XML_solidFill; // default + sal_Int32 aCharColor = 0; + if (rTextPropMap.getValue(u"CharColor") >>= aCharColor) + aFillProperties.maFillColor.setSrgbClr(aCharColor); + else + aFillProperties.maFillColor.setUnused(); + + // Theme color superseds direct color. textFill superseds theme color. Theme color and textfill + // are in CharInteropGrabBag + uno::Sequence<beans::PropertyValue> aCharInteropGrabBagSeq; + if (!((rTextPropMap.getValue(u"CharInteropGrabBag") >>= aCharInteropGrabBagSeq) + && aCharInteropGrabBagSeq.hasElements())) + return aFillProperties; + comphelper::SequenceAsHashMap aCharInteropGrabBagMap(aCharInteropGrabBagSeq); + + // Handle theme color, tint and shade. + OUString sColorString; + if (aCharInteropGrabBagMap.getValue("CharThemeOriginalColor") >>= sColorString) + { + sal_Int32 nThemeOrigColor = oox::AttributeConversion::decodeIntegerHex(sColorString); + aFillProperties.maFillColor.setSrgbClr(nThemeOrigColor); + } + if (aCharInteropGrabBagMap.getValue("CharThemeColor") >>= sColorString) + { + sal_Int32 nColorToken = oox::AttributeConversion::decodeToken(sColorString); + aFillProperties.maFillColor.setSchemeClr(nColorToken); + aFillProperties.maFillColor.setSchemeName(sColorString); + // A character color has shade and tint, a shape color has lumMod and lumOff. + OUString sTransformString; + if (aCharInteropGrabBagMap.getValue("CharThemeColorTint") >>= sTransformString) + { + double fTint = oox::AttributeConversion::decodeIntegerHex(sTransformString); + fTint = fTint / 255.0 * oox::drawingml::MAX_PERCENT; + aFillProperties.maFillColor.addTransformation(OOX_TOKEN(w14, lumMod), + static_cast<sal_Int32>(fTint + 0.5)); + double fOff = oox::drawingml::MAX_PERCENT - fTint; + aFillProperties.maFillColor.addTransformation(OOX_TOKEN(w14, lumOff), + static_cast<sal_Int32>(fOff + 0.5)); + } + else if (aCharInteropGrabBagMap.getValue("CharThemeColorShade") >>= sTransformString) + { + double fShade = oox::AttributeConversion::decodeIntegerHex(sTransformString); + fShade = fShade / 255.0 * oox::drawingml::MAX_PERCENT; + aFillProperties.maFillColor.addTransformation(OOX_TOKEN(w14, lumMod), + static_cast<sal_Int32>(fShade + 0.5)); + } + } + + // Handle textFill + beans::PropertyValue aProp; + if (!(aCharInteropGrabBagMap.getValue(u"CharTextFillTextEffect") >>= aProp)) + return aFillProperties; + uno::Sequence<beans::PropertyValue> aTextFillSeq; + if (!(aProp.Name == "textFill" && (aProp.Value >>= aTextFillSeq) && aTextFillSeq.hasElements())) + return aFillProperties; + // Copy fill properties from aTextFillSeq to aFillProperties + lcl_getFillDetailsFromPropSeq(aTextFillSeq, aFillProperties); + return aFillProperties; +} + +void lcl_applyShapePropsToShape(const uno::Reference<beans::XPropertySet>& xShapePropertySet, + const oox::drawingml::ShapePropertyMap& rShapeProps) +{ + for (const auto& rProp : rShapeProps.makePropertyValueSequence()) + { + xShapePropertySet->setPropertyValue(rProp.Name, rProp.Value); + } +} + +void lcl_setTextAnchorFromTextProps(const uno::Reference<beans::XPropertySet>& xShapePropertySet, + const comphelper::SequenceAsHashMap& aTextPropMap) +{ + // Fontwork does not evaluate paragraph alignment but uses text anchor instead + auto eHorzAdjust(drawing::TextHorizontalAdjust_CENTER); + sal_Int16 nParaAlign = sal_Int16(drawing::TextHorizontalAdjust_CENTER); + aTextPropMap.getValue("ParaAdjust") >>= nParaAlign; + switch (nParaAlign) + { + case sal_Int16(style::ParagraphAdjust_LEFT): + eHorzAdjust = drawing::TextHorizontalAdjust_LEFT; + break; + case sal_Int16(style::ParagraphAdjust_RIGHT): + eHorzAdjust = drawing::TextHorizontalAdjust_RIGHT; + break; + default: + eHorzAdjust = drawing::TextHorizontalAdjust_CENTER; + } + xShapePropertySet->setPropertyValue("TextHorizontalAdjust", uno::Any(eHorzAdjust)); + xShapePropertySet->setPropertyValue("TextVerticalAdjust", + uno::Any(drawing::TextVerticalAdjust_TOP)); +} + +void lcl_setTextPropsToShape(const uno::Reference<beans::XPropertySet>& xShapePropertySet, + std::vector<beans::PropertyValue>& aTextPropVec) +{ + auto xShapePropertySetInfo = xShapePropertySet->getPropertySetInfo(); + if (!xShapePropertySetInfo.is()) + return; + for (size_t i = 0; i < aTextPropVec.size(); ++i) + { + if (xShapePropertySetInfo->hasPropertyByName(aTextPropVec[i].Name) + && !(xShapePropertySetInfo->getPropertyByName(aTextPropVec[i].Name).Attributes + & beans::PropertyAttribute::READONLY) + && aTextPropVec[i].Name != u"CharInteropGrabBag") + { + xShapePropertySet->setPropertyValue(aTextPropVec[i].Name, aTextPropVec[i].Value); + } + } +} + +void lcl_applyUsedTextPropsToAllTextRuns(uno::Reference<text::XText>& xText, + const std::vector<beans::PropertyValue>& aTextPropVec) +{ + if (!xText.is()) + return; + uno::Reference<text::XTextCursor> xTextCursor = xText->createTextCursor(); + xTextCursor->gotoStart(false); + xTextCursor->gotoEnd(true); + uno::Reference<container::XEnumerationAccess> paraEnumAccess(xText, 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; + + for (size_t i = 0; i < aTextPropVec.size(); ++i) + { + if (xRunPropSetInfo->hasPropertyByName(aTextPropVec[i].Name) + && !(xRunPropSetInfo->getPropertyByName(aTextPropVec[i].Name).Attributes + & beans::PropertyAttribute::READONLY)) + xRunPropSet->setPropertyValue(aTextPropVec[i].Name, aTextPropVec[i].Value); + } + } + } +} +} // anonymous namespace + namespace oox::shape { WpsContext::WpsContext(ContextHandler2Helper const& rParent, uno::Reference<drawing::XShape> xShape, @@ -309,7 +807,8 @@ oox::core::ContextHandlerRef WpsContext::onCreateContext(sal_Int32 nElementToken uno::Any(aCustomShapeGeometry.getAsConstPropertyValueList())); } } - break; + return new oox::drawingml::PresetTextShapeContext( + *this, rAttribs, *(getShape()->getCustomShapeProperties())); case XML_txbx: { mpShapePtr->getCustomShapeProperties()->setShapeTypeOverride(true); @@ -353,6 +852,96 @@ oox::core::ContextHandlerRef WpsContext::onCreateContext(sal_Int32 nElementToken } return nullptr; } + +void WpsContext::onEndElement() +{ + // Convert shape to Fontwork shape if necessary and meaningful. + // Only at end of bodyPr all needed info is available. + + if (getBaseToken(getCurrentElement()) != XML_bodyPr) + return; + + // Make sure all needed parts are available + auto* pCustomShape + = dynamic_cast<SdrObjCustomShape*>(SdrObject::getSdrObjectFromXShape(mxShape)); + if (!pCustomShape || !mpShapePtr || !mxShape.is()) + return; + uno::Reference<beans::XPropertySet> xShapePropertySet(mxShape, uno::UNO_QUERY); + if (!xShapePropertySet.is()) + return; + // This is the text in the frame, associated with the shape + uno::Reference<text::XText> xText(mxShape, uno::UNO_QUERY); + if (!xText.is()) + return; + + OUString sMSPresetType; + comphelper::SequenceAsHashMap aCustomShapeGeometry( + xShapePropertySet->getPropertyValue("CustomShapeGeometry")); + aCustomShapeGeometry["PresetTextWarp"] >>= sMSPresetType; + if (sMSPresetType.isEmpty() || sMSPresetType == u"textNoShape") + return; + + // Word can combine its "abc Transform" with a lot of shape types. LibreOffice can only render + // the old kind WordArt, which is based on a rectangle. In case of non rectangular shape we keep + // the shape and do not convert the text to Fontwork. + OUString sType; + aCustomShapeGeometry["Type"] >>= sType; + if (sType != u"ooxml-rect") + return; + + // Copy properties from frame text to have them available after the frame is removed. + std::vector<beans::PropertyValue> aTextPropVec; + if (!lcl_getTextPropsFromFrameText(xText, aTextPropVec)) + return; + comphelper::SequenceAsHashMap aTextPropMap(comphelper::containerToSequence(aTextPropVec)); + + // Copy text content from frame to shape. Since Fontwork uses simple text anyway, we can use + // a string. + OUString sFrameContent(xText->getString()); + pCustomShape->NbcSetText(sFrameContent); + + // Setting the property "TextBox" to false includes removing the attached frame from the shape. + xShapePropertySet->setPropertyValue("TextBox", uno::Any(false)); + + // Set the shape into text path mode, so that the text is drawn as Fontwork. Word renders a legacy + // "text on path" without the legacy stretching, therefore use false for bFromWordArt. + mpShapePtr->getCustomShapeProperties()->setShapeTypeOverride(true); + FontworkHelpers::putCustomShapeIntoTextPathMode(mxShape, getShape()->getCustomShapeProperties(), + sMSPresetType, /*bFromWordArt*/ false); + + // Apply the text props to the fontwork shape + lcl_setTextPropsToShape(xShapePropertySet, aTextPropVec); // includes e.g. FontName + lcl_setTextAnchorFromTextProps(xShapePropertySet, aTextPropMap); + + // Fontwork in LO uses fill and stroke of the shape and cannot style text portions individually. + // "abc Transform" in Word uses fill and outline of the characters. + // We need to copy the properties from a run to the shape. + oox::drawingml::ShapePropertyMap aStrokeShapeProps(getFilter().getModelObjectHelper()); + oox::drawingml::LineProperties aCreatedLineProperties + = lcl_generateLinePropertiesFromTextProps(aTextPropMap); + aCreatedLineProperties.pushToPropMap(aStrokeShapeProps, getFilter().getGraphicHelper()); + lcl_applyShapePropsToShape(xShapePropertySet, aStrokeShapeProps); + + oox::drawingml::ShapePropertyMap aFillShapeProps(getFilter().getModelObjectHelper()); + oox::drawingml::FillProperties aCreatedFillProperties + = lcl_generateFillPropertiesFromTextProps(aTextPropMap); + aCreatedFillProperties.pushToPropMap(aFillShapeProps, getFilter().getGraphicHelper(), + /*nShapeRotation*/ 0, + /*nPhClr*/ API_RGB_TRANSPARENT, /*nPhClrTheme*/ -1, + pCustomShape->IsMirroredX(), pCustomShape->IsMirroredY(), + /*bIsCustomShape*/ true); + lcl_applyShapePropsToShape(xShapePropertySet, aFillShapeProps); + + // Copying the text content from frame to shape as string has lost the styles. Apply the used text + // properties back to all runs in the text. + uno::Reference<text::XText> xNewText(pCustomShape->getUnoShape(), uno::UNO_QUERY); + if (xNewText.is()) + lcl_applyUsedTextPropsToAllTextRuns(xNewText, aTextPropVec); + + // Fontwork stretches the text to the given path. So adapt shape size to text is nonsensical. + xShapePropertySet->setPropertyValue("TextAutoGrowHeight", uno::Any(false)); + xShapePropertySet->setPropertyValue("TextAutoGrowWidth", uno::Any(false)); +} } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/oox/source/shape/WpsContext.hxx b/oox/source/shape/WpsContext.hxx index 29110b6fbf8e..16108b3733fa 100644 --- a/oox/source/shape/WpsContext.hxx +++ b/oox/source/shape/WpsContext.hxx @@ -33,6 +33,7 @@ public: oox::core::ContextHandlerRef onCreateContext(sal_Int32 nElementToken, const oox::AttributeList& rAttribs) override; + virtual void onEndElement() override; private: css::uno::Reference<css::drawing::XShape> mxShape;