diff options
Diffstat (limited to 'oox/source/shape/WpsContext.cxx')
-rw-r--r-- | oox/source/shape/WpsContext.cxx | 982 |
1 files changed, 982 insertions, 0 deletions
diff --git a/oox/source/shape/WpsContext.cxx b/oox/source/shape/WpsContext.cxx new file mode 100644 index 0000000000..fae7048563 --- /dev/null +++ b/oox/source/shape/WpsContext.cxx @@ -0,0 +1,982 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include "WpsContext.hxx" +#include "WpgContext.hxx" +#include "WordprocessingCanvasContext.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/connectorshapecontext.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/HomogenMatrix3.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 <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 parameter. If it does not find it, rValue is unchanged and +// the method 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"_ustr, 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"_ustr) >>= aColorDetailSeq) + { + OUString sColorString; + bColorFound = lcl_getAttributeAsString(aColorDetailSeq, u"val"_ustr, sColorString); + if (bColorFound) + { + sal_Int32 nColorToken = oox::AttributeConversion::decodeToken(sColorString); + rColor.setSchemeClr(nColorToken); + rColor.setSchemeName(sColorString); + } + } + if (!bColorFound && (aPropMap.getValue(u"srgbClr"_ustr) >>= aColorDetailSeq)) + { + OUString sColorString; + bColorFound = lcl_getAttributeAsString(aColorDetailSeq, u"val"_ustr, 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"_ustr) != aTextFillMap.end()) + { + rFillProperties.moFillType = oox::XML_noFill; + return; + } + + uno::Sequence<beans::PropertyValue> aPropSeq; + if ((aTextFillMap.getValue(u"solidFill"_ustr) >>= aPropSeq) && aPropSeq.hasElements()) + { + rFillProperties.moFillType = oox::XML_solidFill; + lcl_getColorFromPropSeq(aPropSeq, rFillProperties.maFillColor); + return; + } + + if ((aTextFillMap.getValue(u"gradFill"_ustr) >>= aPropSeq) && aPropSeq.hasElements()) + { + rFillProperties.moFillType = oox::XML_gradFill; + // aPropSeq should have two items. One is "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"_ustr, 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"_ustr, aRect.X1)) + aRect.X1 = 0; + if (!lcl_getAttributeAsNumber(aFillToRectSeq, u"t"_ustr, aRect.Y1)) + aRect.Y1 = 0; + if (!lcl_getAttributeAsNumber(aFillToRectSeq, u"r"_ustr, aRect.X2)) + aRect.X2 = 0; + if (!lcl_getAttributeAsNumber(aFillToRectSeq, u"b"_ustr, 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"_ustr) != aTextOutlineMap.end()) + rLineProperties.moLineJoint = oox::XML_bevel; + else if (aTextOutlineMap.find(u"round"_ustr) != aTextOutlineMap.end()) + rLineProperties.moLineJoint = oox::XML_round; + else if (aTextOutlineMap.find(u"miter"_ustr) != 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"_ustr) >>= aDashSeq) + { + // aDashSeq contains the attribute "val" with the kind of dash, e.g. "sysDot" + OUString sDashKind; + if (lcl_getAttributeAsString(aDashSeq, u"val"_ustr, sDashKind)) + rLineProperties.moPresetDash = oox::AttributeConversion::decodeToken(sDashKind); + } + OUString sCapKind; + if (lcl_getAttributeAsString(rTextOutlineSeq, u"cap"_ustr, sCapKind)) + rLineProperties.moLineCap = oox::AttributeConversion::decodeToken(sCapKind); + + // Width + sal_Int32 nWidth; // EMU + if (lcl_getAttributeAsNumber(rTextOutlineSeq, u"w"_ustr, 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"_ustr, 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"_ustr) >>= aCharInteropGrabBagSeq)) + return aLineProperties; + if (!aCharInteropGrabBagSeq.hasElements()) + return aLineProperties; + comphelper::SequenceAsHashMap aCharInteropGrabBagMap(aCharInteropGrabBagSeq); + beans::PropertyValue aProp; + if (!(aCharInteropGrabBagMap.getValue(u"CharTextOutlineTextEffect"_ustr) >>= 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 + // Theme color supersedes direct color. textFill supersedes theme color. Theme color and textFill + // are in CharInteropGrabBag. + uno::Sequence<beans::PropertyValue> aCharInteropGrabBagSeq; + if ((rTextPropMap.getValue(u"CharInteropGrabBag"_ustr) >>= aCharInteropGrabBagSeq) + && aCharInteropGrabBagSeq.hasElements()) + { + // Handle case textFill + comphelper::SequenceAsHashMap aCharInteropGrabBagMap(aCharInteropGrabBagSeq); + beans::PropertyValue aProp; + if (aCharInteropGrabBagMap.getValue(u"CharTextFillTextEffect"_ustr) >>= aProp) + { + uno::Sequence<beans::PropertyValue> aTextFillSeq; + if (aProp.Name == "textFill" && (aProp.Value >>= aTextFillSeq) + && aTextFillSeq.hasElements()) + { + // Copy fill properties from aTextFillSeq to aFillProperties + lcl_getFillDetailsFromPropSeq(aTextFillSeq, aFillProperties); + return aFillProperties; + } + } + + // no textFill, look for theme color, tint and shade + bool bColorFound(false); + OUString sColorString; + if (aCharInteropGrabBagMap.getValue("CharThemeOriginalColor") >>= sColorString) + { + sal_Int32 nThemeOrigColor = oox::AttributeConversion::decodeIntegerHex(sColorString); + aFillProperties.maFillColor.setSrgbClr(nThemeOrigColor); + bColorFound = true; + } + if (aCharInteropGrabBagMap.getValue("CharThemeColor") >>= sColorString) + { + sal_Int32 nColorToken = oox::AttributeConversion::decodeToken(sColorString); + aFillProperties.maFillColor.setSchemeClr(nColorToken); + aFillProperties.maFillColor.setSchemeName(sColorString); + bColorFound = true; + // A character color has shade or 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)); + } + } + if (bColorFound) + return aFillProperties; + } + + // Neither textFill nor theme color. Look for direct color. + sal_Int32 aCharColor = 0; + if (rTextPropMap.getValue(u"CharColor"_ustr) >>= aCharColor) + aFillProperties.maFillColor.setSrgbClr(aCharColor); + else + aFillProperties.maFillColor.setUnused(); + 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, + const drawingml::ShapePtr& pMasterShapePtr, + const drawingml::ShapePtr& pShapePtr) + : ShapeContext(rParent, pMasterShapePtr, pShapePtr) + , mxShape(std::move(xShape)) +{ + if (mpShapePtr) + mpShapePtr->setWps(true); + + if (const auto pParent = dynamic_cast<const WpgContext*>(&rParent)) + m_bHasWPGParent = pParent->isFullWPGSupport(); + else if (dynamic_cast<const WordprocessingCanvasContext*>(&rParent)) + m_bHasWPGParent = true; + else + m_bHasWPGParent = false; + + if ((pMasterShapePtr && pMasterShapePtr->isInWordprocessingCanvas()) + || dynamic_cast<const WordprocessingCanvasContext*>(&rParent) != nullptr) + pShapePtr->setWordprocessingCanvas(true); +} + +WpsContext::~WpsContext() = default; + +oox::core::ContextHandlerRef WpsContext::onCreateContext(sal_Int32 nElementToken, + const oox::AttributeList& rAttribs) +{ + switch (getBaseToken(nElementToken)) + { + case XML_wsp: + break; + case XML_cNvCnPr: + { + // It might be a connector shape in a wordprocessing canvas + // Replace the custom shape with a connector shape. + if (!mpShapePtr || !mpShapePtr->isInWordprocessingCanvas() || !mpMasterShapePtr) + break; + // Generate new shape + oox::drawingml::ShapePtr pShape = std::make_shared<oox::drawingml::Shape>( + "com.sun.star.drawing.ConnectorShape", false); + pShape->setConnectorShape(true); + pShape->setWps(true); + pShape->setWordprocessingCanvas(true); + // ToDo: Can only copy infos from mpShapePtr to pShape for which getter available. + pShape->setName(mpShapePtr->getName()); + pShape->setId(mpShapePtr->getId()); + pShape->setWPGChild(mpShapePtr->isWPGChild()); + // And actually replace the shape. + mpShapePtr = pShape; + mpMasterShapePtr->getChildren().pop_back(); + mpMasterShapePtr->getChildren().push_back(pShape); + return new oox::drawingml::ConnectorShapePropertiesContext( + *this, mpShapePtr, mpShapePtr->getConnectorShapeProperties()); + } + case XML_bodyPr: + if (mxShape.is()) + { + // no evaluation of attribute XML_rot, because Word ignores it, as of 2022-07. + + uno::Reference<lang::XServiceInfo> xServiceInfo(mxShape, uno::UNO_QUERY); + uno::Reference<beans::XPropertySet> xPropertySet(mxShape, uno::UNO_QUERY); + sal_Int32 nVert = rAttribs.getToken(XML_vert, XML_horz); + // Values 'wordArtVert' and 'wordArtVertRtl' are not implemented. + // Map them to other vert values. + if (nVert == XML_eaVert || nVert == XML_wordArtVertRtl) + { + xPropertySet->setPropertyValue("TextWritingMode", + uno::Any(text::WritingMode_TB_RL)); + xPropertySet->setPropertyValue("WritingMode", + uno::Any(text::WritingMode2::TB_RL)); + } + else if (nVert == XML_mongolianVert || nVert == XML_wordArtVert) + { + xPropertySet->setPropertyValue("WritingMode", + uno::Any(text::WritingMode2::TB_LR)); + } + else if (nVert != XML_horz) // cases XML_vert and XML_vert270 + { + // Hack to get same rendering as after the fix for tdf#87924. If shape rotation + // plus text direction results in upright text, use horizontal text direction. + // Remove hack when frame is able to rotate. + + // Need transformation matrix since RotateAngle does not contain flip. + drawing::HomogenMatrix3 aMatrix; + xPropertySet->getPropertyValue("Transformation") >>= aMatrix; + basegfx::B2DHomMatrix aTransformation; + aTransformation.set(0, 0, aMatrix.Line1.Column1); + aTransformation.set(0, 1, aMatrix.Line1.Column2); + aTransformation.set(0, 2, aMatrix.Line1.Column3); + aTransformation.set(1, 0, aMatrix.Line2.Column1); + aTransformation.set(1, 1, aMatrix.Line2.Column2); + aTransformation.set(1, 2, aMatrix.Line2.Column3); + // For this to be a valid 2D transform matrix, the last row must be [0,0,1] + assert(aMatrix.Line3.Column1 == 0); + assert(aMatrix.Line3.Column2 == 0); + assert(aMatrix.Line3.Column3 == 1); + basegfx::B2DTuple aScale; + basegfx::B2DTuple aTranslate; + double fRotate = 0; + double fShearX = 0; + aTransformation.decompose(aScale, aTranslate, fRotate, fShearX); + auto nRotate(static_cast<sal_uInt16>(NormAngle360(basegfx::rad2deg(fRotate)))); + if ((nVert == XML_vert && nRotate == 270) + || (nVert == XML_vert270 && nRotate == 90)) + { + xPropertySet->setPropertyValue("WritingMode", + uno::Any(text::WritingMode2::LR_TB)); + // ToDo: Remember original vert value and remove hack on export. + } + else if (nVert == XML_vert) + xPropertySet->setPropertyValue("WritingMode", + uno::Any(text::WritingMode2::TB_RL90)); + else // nVert == XML_vert270 + xPropertySet->setPropertyValue("WritingMode", + uno::Any(text::WritingMode2::BT_LR)); + } + + if (bool bUpright = rAttribs.getBool(XML_upright, false)) + { + uno::Sequence<beans::PropertyValue> aGrabBag; + xPropertySet->getPropertyValue("InteropGrabBag") >>= aGrabBag; + sal_Int32 length = aGrabBag.getLength(); + aGrabBag.realloc(length + 1); + auto pGrabBag = aGrabBag.getArray(); + pGrabBag[length].Name = "Upright"; + pGrabBag[length].Value <<= bUpright; + xPropertySet->setPropertyValue("InteropGrabBag", uno::Any(aGrabBag)); + } + + if (xServiceInfo.is()) + { + // Handle inset attributes for Writer textframes. + sal_Int32 aInsets[] = { XML_lIns, XML_tIns, XML_rIns, XML_bIns }; + std::optional<sal_Int32> oInsets[4]; + for (std::size_t i = 0; i < SAL_N_ELEMENTS(aInsets); ++i) + { + std::optional<OUString> oValue = rAttribs.getString(aInsets[i]); + if (oValue.has_value()) + oInsets[i] = oox::drawingml::GetCoordinate(oValue.value()); + else + // Defaults from the spec: left/right: 91440 EMU, top/bottom: 45720 EMU + oInsets[i] + = (aInsets[i] == XML_lIns || aInsets[i] == XML_rIns) ? 254 : 127; + } + const OUString aShapeProps[] + = { OUString("TextLeftDistance"), OUString("TextUpperDistance"), + OUString("TextRightDistance"), OUString("TextLowerDistance") }; + for (std::size_t i = 0; i < SAL_N_ELEMENTS(aShapeProps); ++i) + if (oInsets[i]) + xPropertySet->setPropertyValue(aShapeProps[i], uno::Any(*oInsets[i])); + } + + // Handle text vertical adjustment inside a text frame + if (rAttribs.hasAttribute(XML_anchor)) + { + drawing::TextVerticalAdjust eAdjust + = drawingml::GetTextVerticalAdjust(rAttribs.getToken(XML_anchor, XML_t)); + xPropertySet->setPropertyValue("TextVerticalAdjust", uno::Any(eAdjust)); + } + + // Apply character color of the shape to the shape's textbox. + uno::Reference<text::XText> xText(mxShape, uno::UNO_QUERY); + uno::Any xCharColor = xPropertySet->getPropertyValue("CharColor"); + Color aColor = COL_AUTO; + if ((xCharColor >>= aColor) && aColor != COL_AUTO) + { + // tdf#135923 Apply character color of the shape to the textrun + // when the character color of the textrun is default. + // tdf#153791 But only if the run has no background color (shd element in OOXML) + if (uno::Reference<container::XEnumerationAccess> paraEnumAccess{ + xText, uno::UNO_QUERY }) + { + 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; + if (uno::Reference<beans::XPropertySet> xParaPropSet{ xParagraph, + uno::UNO_QUERY }) + if ((xParaPropSet->getPropertyValue("ParaBackColor") >>= aColor) + && aColor != COL_AUTO) + continue; + + uno::Reference<container::XEnumeration> runEnum + = runEnumAccess->createEnumeration(); + + while (runEnum->hasMoreElements()) + { + uno::Reference<text::XTextRange> xRun(runEnum->nextElement(), + uno::UNO_QUERY); + const uno::Reference<beans::XPropertyState> xRunState( + xRun, uno::UNO_QUERY); + if (!xRunState + || xRunState->getPropertyState("CharColor") + == beans::PropertyState_DEFAULT_VALUE) + { + uno::Reference<beans::XPropertySet> xRunPropSet(xRun, + uno::UNO_QUERY); + if (!xRunPropSet) + continue; + if ((xRunPropSet->getPropertyValue("CharBackColor") >>= aColor) + && aColor != COL_AUTO) + continue; + if (!(xRunPropSet->getPropertyValue("CharColor") >>= aColor) + || aColor == COL_AUTO) + xRunPropSet->setPropertyValue("CharColor", xCharColor); + } + } + } + } + } + + auto nWrappingType = rAttribs.getToken(XML_wrap, XML_square); + xPropertySet->setPropertyValue("TextWordWrap", + uno::Any(nWrappingType == XML_square)); + + return this; + } + else if (m_bHasWPGParent && mpShapePtr) + { + // this WPS context has to be inside a WPG shape, so the <BodyPr> element + // cannot be applied to mxShape member, use mpShape instead, and after the + // the parent shape finished, apply it for its children. + mpShapePtr->setWPGChild(true); + oox::drawingml::TextBodyPtr pTextBody; + pTextBody.reset(new oox::drawingml::TextBody()); + + if (rAttribs.hasAttribute(XML_anchor)) + { + drawing::TextVerticalAdjust eAdjust + = drawingml::GetTextVerticalAdjust(rAttribs.getToken(XML_anchor, XML_t)); + pTextBody->getTextProperties().meVA = eAdjust; + } + + sal_Int32 aInsets[] = { XML_lIns, XML_tIns, XML_rIns, XML_bIns }; + for (int i = 0; i < 4; ++i) + { + if (rAttribs.hasAttribute(XML_lIns)) + { + std::optional<OUString> oValue = rAttribs.getString(aInsets[i]); + if (oValue.has_value()) + pTextBody->getTextProperties().moInsets[i] + = oox::drawingml::GetCoordinate(oValue.value()); + else + // Defaults from the spec: left/right: 91440 EMU, top/bottom: 45720 EMU + pTextBody->getTextProperties().moInsets[i] + = (aInsets[i] == XML_lIns || aInsets[i] == XML_rIns) ? 254 : 127; + } + } + + mpShapePtr->setTextBody(pTextBody); + } + break; + case XML_noAutofit: + case XML_spAutoFit: + { + uno::Reference<lang::XServiceInfo> xServiceInfo(mxShape, uno::UNO_QUERY); + // We can't use oox::drawingml::TextBodyPropertiesContext here, as this + // is a child context of bodyPr, so the shape is already sent: we need + // to alter the XShape directly. + uno::Reference<beans::XPropertySet> xPropertySet(mxShape, uno::UNO_QUERY); + if (xPropertySet.is()) + { + if (xServiceInfo->supportsService("com.sun.star.text.TextFrame")) + xPropertySet->setPropertyValue( + "FrameIsAutomaticHeight", + uno::Any(getBaseToken(nElementToken) == XML_spAutoFit)); + else + xPropertySet->setPropertyValue( + "TextAutoGrowHeight", + uno::Any(getBaseToken(nElementToken) == XML_spAutoFit)); + } + } + break; + case XML_prstTxWarp: + if (rAttribs.hasAttribute(XML_prst)) + { + uno::Reference<beans::XPropertySet> xPropertySet(mxShape, uno::UNO_QUERY); + if (xPropertySet.is()) + { + std::optional<OUString> presetShapeName = rAttribs.getString(XML_prst); + const OUString& preset = presetShapeName.value(); + comphelper::SequenceAsHashMap aCustomShapeGeometry( + xPropertySet->getPropertyValue("CustomShapeGeometry")); + aCustomShapeGeometry["PresetTextWarp"] <<= preset; + xPropertySet->setPropertyValue( + "CustomShapeGeometry", + uno::Any(aCustomShapeGeometry.getAsConstPropertyValueList())); + } + } + return new oox::drawingml::PresetTextShapeContext( + *this, rAttribs, *(getShape()->getCustomShapeProperties())); + case XML_txbx: + { + mpShapePtr->getCustomShapeProperties()->setShapeTypeOverride(true); + mpShapePtr->setTextBox(true); + //in case if the textbox is linked, save the attributes + //for further processing. + if (rAttribs.hasAttribute(XML_id)) + { + std::optional<OUString> id = rAttribs.getString(XML_id); + if (id.has_value()) + { + oox::drawingml::LinkedTxbxAttr linkedTxtBoxAttr; + linkedTxtBoxAttr.id = id.value().toInt32(); + mpShapePtr->setTxbxHasLinkedTxtBox(true); + mpShapePtr->setLinkedTxbxAttributes(linkedTxtBoxAttr); + } + } + return this; + } + break; + case XML_linkedTxbx: + { + //in case if the textbox is linked, save the attributes + //for further processing. + mpShapePtr->getCustomShapeProperties()->setShapeTypeOverride(true); + mpShapePtr->setTextBox(true); + std::optional<OUString> id = rAttribs.getString(XML_id); + std::optional<OUString> seq = rAttribs.getString(XML_seq); + if (id.has_value() && seq.has_value()) + { + oox::drawingml::LinkedTxbxAttr linkedTxtBoxAttr; + linkedTxtBoxAttr.id = id.value().toInt32(); + linkedTxtBoxAttr.seq = seq.value().toInt32(); + mpShapePtr->setTxbxHasLinkedTxtBox(true); + mpShapePtr->setLinkedTxbxAttributes(linkedTxtBoxAttr); + } + } + break; + default: + return ShapeContext::onCreateContext(nElementToken, rAttribs); + } + 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, + /*aShapeSize*/ css::awt::Size(0, 0), /*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: */ |