summaryrefslogtreecommitdiff
path: root/oox/source/shape
diff options
context:
space:
mode:
authorRegina Henschel <rb.henschel@t-online.de>2022-12-03 15:37:49 +0100
committerMiklos Vajna <vmiklos@collabora.com>2023-01-02 08:19:37 +0000
commitcbf30153a5c776e6d1ee26f2f83c8f77503eceb9 (patch)
tree8e1fa1816ffa877ee3765a5f83e73dc782c0afc7 /oox/source/shape
parent54cb5990e694385dd269dc2e946c58d64390986a (diff)
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 <vmiklos@collabora.com>
Diffstat (limited to 'oox/source/shape')
-rw-r--r--oox/source/shape/WpsContext.cxx607
-rw-r--r--oox/source/shape/WpsContext.hxx1
2 files changed, 599 insertions, 9 deletions
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;