Bump version to 24.04.3.4
[LibreOffice.git] / oox / source / shape / WpsContext.cxx
blobfae7048563717f36bac5010f12d17c313123062a
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 */
10 #include "WpsContext.hxx"
11 #include "WpgContext.hxx"
12 #include "WordprocessingCanvasContext.hxx"
13 #include <basegfx/matrix/b2dhommatrix.hxx>
14 #include <basegfx/tuple/b2dtuple.hxx>
15 #include <comphelper/propertyvalue.hxx>
16 #include <comphelper/sequence.hxx>
17 #include <comphelper/sequenceashashmap.hxx>
18 #include <drawingml/customshapegeometry.hxx>
19 #include <drawingml/customshapeproperties.hxx>
20 #include <drawingml/fontworkhelpers.hxx>
21 #include <drawingml/textbody.hxx>
22 #include <drawingml/textbodyproperties.hxx>
23 #include <oox/drawingml/color.hxx>
24 #include <oox/drawingml/connectorshapecontext.hxx>
25 #include <oox/drawingml/drawingmltypes.hxx>
26 #include <oox/drawingml/shape.hxx>
27 #include <oox/drawingml/shapepropertymap.hxx>
28 #include <oox/helper/attributelist.hxx>
29 #include <oox/token/namespaces.hxx>
30 #include <oox/token/tokens.hxx>
31 #include <svx/svdoashp.hxx>
33 #include <com/sun/star/beans/PropertyAttribute.hpp>
34 #include <com/sun/star/beans/XPropertySet.hpp>
35 #include <com/sun/star/beans/XPropertySetInfo.hpp>
36 #include <com/sun/star/beans/XPropertyState.hpp>
37 #include <com/sun/star/container/XEnumerationAccess.hpp>
38 #include <com/sun/star/drawing/HomogenMatrix3.hpp>
39 #include <com/sun/star/drawing/TextHorizontalAdjust.hpp>
40 #include <com/sun/star/geometry/IntegerRectangle2D.hpp>
41 #include <com/sun/star/lang/XServiceInfo.hpp>
42 #include <com/sun/star/text/XText.hpp>
43 #include <com/sun/star/text/XTextCursor.hpp>
44 #include <com/sun/star/text/WritingMode.hpp>
45 #include <com/sun/star/text/WritingMode2.hpp>
47 #include <optional>
49 using namespace com::sun::star;
51 namespace
53 bool lcl_getTextPropsFromFrameText(const uno::Reference<text::XText>& xText,
54 std::vector<beans::PropertyValue>& rTextPropVec)
56 if (!xText.is())
57 return false;
58 uno::Reference<text::XTextCursor> xTextCursor = xText->createTextCursor();
59 xTextCursor->gotoStart(false);
60 xTextCursor->gotoEnd(true);
61 uno::Reference<container::XEnumerationAccess> paraEnumAccess(xText, uno::UNO_QUERY);
62 if (!paraEnumAccess.is())
63 return false;
64 uno::Reference<container::XEnumeration> paraEnum(paraEnumAccess->createEnumeration());
65 while (paraEnum->hasMoreElements())
67 uno::Reference<text::XTextRange> xParagraph(paraEnum->nextElement(), uno::UNO_QUERY);
68 uno::Reference<container::XEnumerationAccess> runEnumAccess(xParagraph, uno::UNO_QUERY);
69 if (!runEnumAccess.is())
70 continue;
71 uno::Reference<container::XEnumeration> runEnum = runEnumAccess->createEnumeration();
72 while (runEnum->hasMoreElements())
74 uno::Reference<text::XTextRange> xRun(runEnum->nextElement(), uno::UNO_QUERY);
75 if (xRun->getString().isEmpty())
76 continue;
77 uno::Reference<beans::XPropertySet> xRunPropSet(xRun, uno::UNO_QUERY);
78 if (!xRunPropSet.is())
79 continue;
80 auto xRunPropSetInfo = xRunPropSet->getPropertySetInfo();
81 if (!xRunPropSetInfo.is())
82 continue;
84 // We have found a non-empty run. Collect its properties.
85 auto aRunPropInfoSequence = xRunPropSetInfo->getProperties();
86 for (const beans::Property& aProp : aRunPropInfoSequence)
88 rTextPropVec.push_back(comphelper::makePropertyValue(
89 aProp.Name, xRunPropSet->getPropertyValue(aProp.Name)));
91 return true;
94 return false;
97 // CharInteropGrabBag puts all attributes of an element into a property with Name="attributes" and
98 // Value being a sequence of the attributes. This methods finds the value of an individual rName
99 // attribute and puts it into rValue parameter. If it does not find it, rValue is unchanged and
100 // the method returns false, otherwise it returns true.
101 bool lcl_getAttributeAsString(const uno::Sequence<beans::PropertyValue>& aPropertyValueAsSeq,
102 const OUString& rName, OUString& rValue)
104 comphelper::SequenceAsHashMap aPropertyValueAsMap(aPropertyValueAsSeq);
105 uno::Sequence<beans::PropertyValue> aAttributesSeq;
106 if (!((aPropertyValueAsMap.getValue("attributes") >>= aAttributesSeq)
107 && aAttributesSeq.hasElements()))
108 return false;
109 comphelper::SequenceAsHashMap aAttributesMap(aAttributesSeq);
110 OUString sRet;
111 if (!(aAttributesMap.getValue(rName) >>= sRet))
112 return false;
113 rValue = sRet;
114 return true;
117 // Same as above for a number as attribute value
118 bool lcl_getAttributeAsNumber(const uno::Sequence<beans::PropertyValue>& rPropertyValueAsSeq,
119 const OUString& rName, sal_Int32& rValue)
121 comphelper::SequenceAsHashMap aPropertyValueAsMap(rPropertyValueAsSeq);
122 uno::Sequence<beans::PropertyValue> aAttributesSeq;
123 if (!((aPropertyValueAsMap.getValue("attributes") >>= aAttributesSeq)
124 && aAttributesSeq.hasElements()))
125 return false;
126 comphelper::SequenceAsHashMap aAttributesMap(aAttributesSeq);
127 sal_Int32 nRet;
128 if (!(aAttributesMap.getValue(rName) >>= nRet))
129 return false;
130 rValue = nRet;
131 return true;
134 void lcl_getColorTransformationsFromPropSeq(const uno::Sequence<beans::PropertyValue>& rPropSeq,
135 oox::drawingml::Color& rColor)
137 auto isValidPropName = [](const OUString& rName) -> bool {
138 return rName == u"tint" || rName == u"shade" || rName == u"alpha" || rName == u"hueMod"
139 || rName == u"sat" || rName == u"satOff" || rName == u"satMod" || rName == u"lum"
140 || rName == u"lumOff" || rName == u"lumMod";
142 for (auto it = rPropSeq.begin(); it < rPropSeq.end(); ++it)
144 if (isValidPropName((*it).Name))
146 uno::Sequence<beans::PropertyValue> aValueSeq;
147 sal_Int32 nNumber(0); // dummy value to make compiler happy, "val" should exist
148 if (((*it).Value >>= aValueSeq)
149 && lcl_getAttributeAsNumber(aValueSeq, u"val"_ustr, nNumber))
151 // char w14:alpha contains transparency, whereas shape fill a:alpha contains opacity.
152 if ((*it).Name == u"alpha")
153 rColor.addTransformation(
154 oox::NMSP_dml | oox::AttributeConversion::decodeToken((*it).Name),
155 oox::drawingml::MAX_PERCENT - nNumber);
156 else
157 rColor.addTransformation(
158 oox::NMSP_w14 | oox::AttributeConversion::decodeToken((*it).Name), nNumber);
164 // Expected: rPropSeq contains a property "schemeClr" or a property "srgbClr".
165 bool lcl_getColorFromPropSeq(const uno::Sequence<beans::PropertyValue>& rPropSeq,
166 oox::drawingml::Color& rColor)
168 bool bColorFound = false;
169 comphelper::SequenceAsHashMap aPropMap(rPropSeq);
170 uno::Sequence<beans::PropertyValue> aColorDetailSeq;
171 if (aPropMap.getValue(u"schemeClr"_ustr) >>= aColorDetailSeq)
173 OUString sColorString;
174 bColorFound = lcl_getAttributeAsString(aColorDetailSeq, u"val"_ustr, sColorString);
175 if (bColorFound)
177 sal_Int32 nColorToken = oox::AttributeConversion::decodeToken(sColorString);
178 rColor.setSchemeClr(nColorToken);
179 rColor.setSchemeName(sColorString);
182 if (!bColorFound && (aPropMap.getValue(u"srgbClr"_ustr) >>= aColorDetailSeq))
184 OUString sColorString;
185 bColorFound = lcl_getAttributeAsString(aColorDetailSeq, u"val"_ustr, sColorString);
186 if (bColorFound)
188 sal_Int32 nColor = oox::AttributeConversion::decodeIntegerHex(sColorString);
189 rColor.setSrgbClr(nColor);
192 // Without color, color transformations are pointless.
193 if (bColorFound)
194 lcl_getColorTransformationsFromPropSeq(aColorDetailSeq, rColor);
195 return bColorFound;
198 void lcl_getFillDetailsFromPropSeq(const uno::Sequence<beans::PropertyValue>& rTextFillSeq,
199 oox::drawingml::FillProperties& rFillProperties)
201 // rTextFillSeq should have an item containing either "noFill" or "solidFill" or "gradFill"
202 // property.
203 if (!rTextFillSeq.hasElements())
204 return;
205 comphelper::SequenceAsHashMap aTextFillMap(rTextFillSeq);
206 if (aTextFillMap.find(u"noFill"_ustr) != aTextFillMap.end())
208 rFillProperties.moFillType = oox::XML_noFill;
209 return;
212 uno::Sequence<beans::PropertyValue> aPropSeq;
213 if ((aTextFillMap.getValue(u"solidFill"_ustr) >>= aPropSeq) && aPropSeq.hasElements())
215 rFillProperties.moFillType = oox::XML_solidFill;
216 lcl_getColorFromPropSeq(aPropSeq, rFillProperties.maFillColor);
217 return;
220 if ((aTextFillMap.getValue(u"gradFill"_ustr) >>= aPropSeq) && aPropSeq.hasElements())
222 rFillProperties.moFillType = oox::XML_gradFill;
223 // aPropSeq should have two items. One is "gsLst" for the stop colors, the other is
224 // either "lin" or "path" for the kind of gradient.
225 // First get stop colors
226 comphelper::SequenceAsHashMap aPropMap(aPropSeq);
227 uno::Sequence<beans::PropertyValue> aGsLstSeq;
228 if (aPropMap.getValue("gsLst") >>= aGsLstSeq)
230 for (auto it = aGsLstSeq.begin(); it < aGsLstSeq.end(); ++it)
232 // (*it) is a bean::PropertyValue with Name="gs". Its Value is a property sequence.
233 uno::Sequence<beans::PropertyValue> aColorStopSeq;
234 if ((*it).Value >>= aColorStopSeq)
236 // aColorStopSeq should have an item for the color and an item for the position
237 sal_Int32 nPos;
238 oox::drawingml::Color aColor;
239 if (lcl_getAttributeAsNumber(aColorStopSeq, u"pos"_ustr, nPos)
240 && lcl_getColorFromPropSeq(aColorStopSeq, aColor))
242 // The position in maGradientStops is relative, thus in range [0.0;1.0].
243 double fPos = nPos / 100000.0;
244 rFillProperties.maGradientProps.maGradientStops.insert({ fPos, aColor });
249 // Now determine kind of gradient.
250 uno::Sequence<beans::PropertyValue> aKindSeq;
251 if (aPropMap.getValue("lin") >>= aKindSeq)
253 // aKindSeq contains the attributes "ang" and "scaled"
254 sal_Int32 nAngle; // in 1/60000 deg
255 if (lcl_getAttributeAsNumber(aKindSeq, "ang", nAngle))
256 rFillProperties.maGradientProps.moShadeAngle = nAngle;
257 OUString sScaledString;
258 if (lcl_getAttributeAsString(aKindSeq, "scaled", sScaledString))
259 rFillProperties.maGradientProps.moShadeScaled
260 = sScaledString == u"1" || sScaledString == u"true";
261 return;
263 if (aPropMap.getValue("path") >>= aKindSeq)
265 // aKindSeq contains the attribute "path" for the kind of path and a property "fillToRect"
266 // which defines the center rectangle of the gradient. The property "a:tileRect" known from
267 // fill of shapes does not exist in w14 namespace.
268 OUString sKind;
269 if (lcl_getAttributeAsString(aKindSeq, "path", sKind))
270 rFillProperties.maGradientProps.moGradientPath
271 = oox::AttributeConversion::decodeToken(sKind);
272 comphelper::SequenceAsHashMap aKindMap(aKindSeq);
273 uno::Sequence<beans::PropertyValue> aFillToRectSeq;
274 if (aKindMap.getValue("fillToRect") >>= aFillToRectSeq)
276 // The values l, t, r and b are not coordinates, but determine an offset from the
277 // edge of the bounding box of the shape. This unusual meaning of X1, Y1, X2 and
278 // Y2 is needed for method pushToPropMap() of FillProperties.
279 geometry::IntegerRectangle2D aRect;
280 if (!lcl_getAttributeAsNumber(aFillToRectSeq, u"l"_ustr, aRect.X1))
281 aRect.X1 = 0;
282 if (!lcl_getAttributeAsNumber(aFillToRectSeq, u"t"_ustr, aRect.Y1))
283 aRect.Y1 = 0;
284 if (!lcl_getAttributeAsNumber(aFillToRectSeq, u"r"_ustr, aRect.X2))
285 aRect.X2 = 0;
286 if (!lcl_getAttributeAsNumber(aFillToRectSeq, u"b"_ustr, aRect.Y2))
287 aRect.Y2 = 0;
288 rFillProperties.maGradientProps.moFillToRect = aRect;
291 return;
295 void lcl_getLineDetailsFromPropSeq(const uno::Sequence<beans::PropertyValue>& rTextOutlineSeq,
296 oox::drawingml::LineProperties& rLineProperties)
298 if (!rTextOutlineSeq.hasElements())
300 rLineProperties.maLineFill.moFillType = oox::XML_noFill; // MS Office default
301 return;
303 // aTextOulineSeq contains e.g. "attributes" {w, cap, cmpd, ctr}, either
304 // "solidFill" or "gradFill or "noFill", and "prstDash" and "lineJoint" properties.
306 // Fill
307 lcl_getFillDetailsFromPropSeq(rTextOutlineSeq, rLineProperties.maLineFill);
309 // LineJoint
310 comphelper::SequenceAsHashMap aTextOutlineMap(rTextOutlineSeq);
311 if (aTextOutlineMap.find(u"bevel"_ustr) != aTextOutlineMap.end())
312 rLineProperties.moLineJoint = oox::XML_bevel;
313 else if (aTextOutlineMap.find(u"round"_ustr) != aTextOutlineMap.end())
314 rLineProperties.moLineJoint = oox::XML_round;
315 else if (aTextOutlineMap.find(u"miter"_ustr) != aTextOutlineMap.end())
317 // LineProperties has no member to store a miter limit. Therefore some heuristic is
318 // added here. 0 is default for attribute "lim" in MS Office. It is rendered same as bevel.
319 sal_Int32 nMiterLimit = aTextOutlineMap.getUnpackedValueOrDefault("lim", sal_Int32(0));
320 if (nMiterLimit == 0)
321 rLineProperties.moLineJoint = oox::XML_bevel;
322 else
323 rLineProperties.moLineJoint = oox::XML_miter;
326 // Dash
327 uno::Sequence<beans::PropertyValue> aDashSeq;
328 if (aTextOutlineMap.getValue(u"prstDash"_ustr) >>= aDashSeq)
330 // aDashSeq contains the attribute "val" with the kind of dash, e.g. "sysDot"
331 OUString sDashKind;
332 if (lcl_getAttributeAsString(aDashSeq, u"val"_ustr, sDashKind))
333 rLineProperties.moPresetDash = oox::AttributeConversion::decodeToken(sDashKind);
335 OUString sCapKind;
336 if (lcl_getAttributeAsString(rTextOutlineSeq, u"cap"_ustr, sCapKind))
337 rLineProperties.moLineCap = oox::AttributeConversion::decodeToken(sCapKind);
339 // Width
340 sal_Int32 nWidth; // EMU
341 if (lcl_getAttributeAsNumber(rTextOutlineSeq, u"w"_ustr, nWidth))
342 rLineProperties.moLineWidth = nWidth;
344 // Compound. LineProperties has a member for it, however Fontwork can currently only render "sng".
345 OUString sCompoundKind;
346 if (lcl_getAttributeAsString(rTextOutlineSeq, u"cmpd"_ustr, sCompoundKind))
347 rLineProperties.moLineCompound = oox::AttributeConversion::decodeToken(sCompoundKind);
349 // Align. LineProperties has no member for attribute "algn".
351 return;
354 oox::drawingml::LineProperties
355 lcl_generateLinePropertiesFromTextProps(const comphelper::SequenceAsHashMap& aTextPropMap)
357 oox::drawingml::LineProperties aLineProperties;
358 aLineProperties.maLineFill.moFillType = oox::XML_noFill; // default
360 // Get property "textOutline" from aTextPropMap
361 uno::Sequence<beans::PropertyValue> aCharInteropGrabBagSeq;
362 if (!(aTextPropMap.getValue(u"CharInteropGrabBag"_ustr) >>= aCharInteropGrabBagSeq))
363 return aLineProperties;
364 if (!aCharInteropGrabBagSeq.hasElements())
365 return aLineProperties;
366 comphelper::SequenceAsHashMap aCharInteropGrabBagMap(aCharInteropGrabBagSeq);
367 beans::PropertyValue aProp;
368 if (!(aCharInteropGrabBagMap.getValue(u"CharTextOutlineTextEffect"_ustr) >>= aProp))
369 return aLineProperties;
370 uno::Sequence<beans::PropertyValue> aTextOutlineSeq;
371 if (!(aProp.Name == "textOutline" && (aProp.Value >>= aTextOutlineSeq)
372 && aTextOutlineSeq.hasElements()))
373 return aLineProperties;
375 // Copy line properties from aTextOutlineSeq to aLineProperties
376 lcl_getLineDetailsFromPropSeq(aTextOutlineSeq, aLineProperties);
377 return aLineProperties;
380 oox::drawingml::FillProperties
381 lcl_generateFillPropertiesFromTextProps(const comphelper::SequenceAsHashMap& rTextPropMap)
383 oox::drawingml::FillProperties aFillProperties;
384 aFillProperties.moFillType = oox::XML_solidFill; // default
385 // Theme color supersedes direct color. textFill supersedes theme color. Theme color and textFill
386 // are in CharInteropGrabBag.
387 uno::Sequence<beans::PropertyValue> aCharInteropGrabBagSeq;
388 if ((rTextPropMap.getValue(u"CharInteropGrabBag"_ustr) >>= aCharInteropGrabBagSeq)
389 && aCharInteropGrabBagSeq.hasElements())
391 // Handle case textFill
392 comphelper::SequenceAsHashMap aCharInteropGrabBagMap(aCharInteropGrabBagSeq);
393 beans::PropertyValue aProp;
394 if (aCharInteropGrabBagMap.getValue(u"CharTextFillTextEffect"_ustr) >>= aProp)
396 uno::Sequence<beans::PropertyValue> aTextFillSeq;
397 if (aProp.Name == "textFill" && (aProp.Value >>= aTextFillSeq)
398 && aTextFillSeq.hasElements())
400 // Copy fill properties from aTextFillSeq to aFillProperties
401 lcl_getFillDetailsFromPropSeq(aTextFillSeq, aFillProperties);
402 return aFillProperties;
406 // no textFill, look for theme color, tint and shade
407 bool bColorFound(false);
408 OUString sColorString;
409 if (aCharInteropGrabBagMap.getValue("CharThemeOriginalColor") >>= sColorString)
411 sal_Int32 nThemeOrigColor = oox::AttributeConversion::decodeIntegerHex(sColorString);
412 aFillProperties.maFillColor.setSrgbClr(nThemeOrigColor);
413 bColorFound = true;
415 if (aCharInteropGrabBagMap.getValue("CharThemeColor") >>= sColorString)
417 sal_Int32 nColorToken = oox::AttributeConversion::decodeToken(sColorString);
418 aFillProperties.maFillColor.setSchemeClr(nColorToken);
419 aFillProperties.maFillColor.setSchemeName(sColorString);
420 bColorFound = true;
421 // A character color has shade or tint, a shape color has lumMod and lumOff.
422 OUString sTransformString;
423 if (aCharInteropGrabBagMap.getValue("CharThemeColorTint") >>= sTransformString)
425 double fTint = oox::AttributeConversion::decodeIntegerHex(sTransformString);
426 fTint = fTint / 255.0 * oox::drawingml::MAX_PERCENT;
427 aFillProperties.maFillColor.addTransformation(OOX_TOKEN(w14, lumMod),
428 static_cast<sal_Int32>(fTint + 0.5));
429 double fOff = oox::drawingml::MAX_PERCENT - fTint;
430 aFillProperties.maFillColor.addTransformation(OOX_TOKEN(w14, lumOff),
431 static_cast<sal_Int32>(fOff + 0.5));
433 else if (aCharInteropGrabBagMap.getValue("CharThemeColorShade") >>= sTransformString)
435 double fShade = oox::AttributeConversion::decodeIntegerHex(sTransformString);
436 fShade = fShade / 255.0 * oox::drawingml::MAX_PERCENT;
437 aFillProperties.maFillColor.addTransformation(OOX_TOKEN(w14, lumMod),
438 static_cast<sal_Int32>(fShade + 0.5));
441 if (bColorFound)
442 return aFillProperties;
445 // Neither textFill nor theme color. Look for direct color.
446 sal_Int32 aCharColor = 0;
447 if (rTextPropMap.getValue(u"CharColor"_ustr) >>= aCharColor)
448 aFillProperties.maFillColor.setSrgbClr(aCharColor);
449 else
450 aFillProperties.maFillColor.setUnused();
451 return aFillProperties;
454 void lcl_applyShapePropsToShape(const uno::Reference<beans::XPropertySet>& xShapePropertySet,
455 const oox::drawingml::ShapePropertyMap& rShapeProps)
457 for (const auto& rProp : rShapeProps.makePropertyValueSequence())
459 xShapePropertySet->setPropertyValue(rProp.Name, rProp.Value);
463 void lcl_setTextAnchorFromTextProps(const uno::Reference<beans::XPropertySet>& xShapePropertySet,
464 const comphelper::SequenceAsHashMap& aTextPropMap)
466 // Fontwork does not evaluate paragraph alignment but uses text anchor instead
467 auto eHorzAdjust(drawing::TextHorizontalAdjust_CENTER);
468 sal_Int16 nParaAlign = sal_Int16(drawing::TextHorizontalAdjust_CENTER);
469 aTextPropMap.getValue("ParaAdjust") >>= nParaAlign;
470 switch (nParaAlign)
472 case sal_Int16(style::ParagraphAdjust_LEFT):
473 eHorzAdjust = drawing::TextHorizontalAdjust_LEFT;
474 break;
475 case sal_Int16(style::ParagraphAdjust_RIGHT):
476 eHorzAdjust = drawing::TextHorizontalAdjust_RIGHT;
477 break;
478 default:
479 eHorzAdjust = drawing::TextHorizontalAdjust_CENTER;
481 xShapePropertySet->setPropertyValue("TextHorizontalAdjust", uno::Any(eHorzAdjust));
482 xShapePropertySet->setPropertyValue("TextVerticalAdjust",
483 uno::Any(drawing::TextVerticalAdjust_TOP));
486 void lcl_setTextPropsToShape(const uno::Reference<beans::XPropertySet>& xShapePropertySet,
487 std::vector<beans::PropertyValue>& aTextPropVec)
489 auto xShapePropertySetInfo = xShapePropertySet->getPropertySetInfo();
490 if (!xShapePropertySetInfo.is())
491 return;
492 for (size_t i = 0; i < aTextPropVec.size(); ++i)
494 if (xShapePropertySetInfo->hasPropertyByName(aTextPropVec[i].Name)
495 && !(xShapePropertySetInfo->getPropertyByName(aTextPropVec[i].Name).Attributes
496 & beans::PropertyAttribute::READONLY)
497 && aTextPropVec[i].Name != u"CharInteropGrabBag")
499 xShapePropertySet->setPropertyValue(aTextPropVec[i].Name, aTextPropVec[i].Value);
504 void lcl_applyUsedTextPropsToAllTextRuns(uno::Reference<text::XText>& xText,
505 const std::vector<beans::PropertyValue>& aTextPropVec)
507 if (!xText.is())
508 return;
509 uno::Reference<text::XTextCursor> xTextCursor = xText->createTextCursor();
510 xTextCursor->gotoStart(false);
511 xTextCursor->gotoEnd(true);
512 uno::Reference<container::XEnumerationAccess> paraEnumAccess(xText, uno::UNO_QUERY);
513 if (!paraEnumAccess.is())
514 return;
515 uno::Reference<container::XEnumeration> paraEnum(paraEnumAccess->createEnumeration());
516 while (paraEnum->hasMoreElements())
518 uno::Reference<text::XTextRange> xParagraph(paraEnum->nextElement(), uno::UNO_QUERY);
519 uno::Reference<container::XEnumerationAccess> runEnumAccess(xParagraph, uno::UNO_QUERY);
520 if (!runEnumAccess.is())
521 continue;
522 uno::Reference<container::XEnumeration> runEnum = runEnumAccess->createEnumeration();
523 while (runEnum->hasMoreElements())
525 uno::Reference<text::XTextRange> xRun(runEnum->nextElement(), uno::UNO_QUERY);
526 if (xRun->getString().isEmpty())
527 continue;
528 uno::Reference<beans::XPropertySet> xRunPropSet(xRun, uno::UNO_QUERY);
529 if (!xRunPropSet.is())
530 continue;
531 auto xRunPropSetInfo = xRunPropSet->getPropertySetInfo();
532 if (!xRunPropSetInfo.is())
533 continue;
535 for (size_t i = 0; i < aTextPropVec.size(); ++i)
537 if (xRunPropSetInfo->hasPropertyByName(aTextPropVec[i].Name)
538 && !(xRunPropSetInfo->getPropertyByName(aTextPropVec[i].Name).Attributes
539 & beans::PropertyAttribute::READONLY))
540 xRunPropSet->setPropertyValue(aTextPropVec[i].Name, aTextPropVec[i].Value);
545 } // anonymous namespace
547 namespace oox::shape
549 WpsContext::WpsContext(ContextHandler2Helper const& rParent, uno::Reference<drawing::XShape> xShape,
550 const drawingml::ShapePtr& pMasterShapePtr,
551 const drawingml::ShapePtr& pShapePtr)
552 : ShapeContext(rParent, pMasterShapePtr, pShapePtr)
553 , mxShape(std::move(xShape))
555 if (mpShapePtr)
556 mpShapePtr->setWps(true);
558 if (const auto pParent = dynamic_cast<const WpgContext*>(&rParent))
559 m_bHasWPGParent = pParent->isFullWPGSupport();
560 else if (dynamic_cast<const WordprocessingCanvasContext*>(&rParent))
561 m_bHasWPGParent = true;
562 else
563 m_bHasWPGParent = false;
565 if ((pMasterShapePtr && pMasterShapePtr->isInWordprocessingCanvas())
566 || dynamic_cast<const WordprocessingCanvasContext*>(&rParent) != nullptr)
567 pShapePtr->setWordprocessingCanvas(true);
570 WpsContext::~WpsContext() = default;
572 oox::core::ContextHandlerRef WpsContext::onCreateContext(sal_Int32 nElementToken,
573 const oox::AttributeList& rAttribs)
575 switch (getBaseToken(nElementToken))
577 case XML_wsp:
578 break;
579 case XML_cNvCnPr:
581 // It might be a connector shape in a wordprocessing canvas
582 // Replace the custom shape with a connector shape.
583 if (!mpShapePtr || !mpShapePtr->isInWordprocessingCanvas() || !mpMasterShapePtr)
584 break;
585 // Generate new shape
586 oox::drawingml::ShapePtr pShape = std::make_shared<oox::drawingml::Shape>(
587 "com.sun.star.drawing.ConnectorShape", false);
588 pShape->setConnectorShape(true);
589 pShape->setWps(true);
590 pShape->setWordprocessingCanvas(true);
591 // ToDo: Can only copy infos from mpShapePtr to pShape for which getter available.
592 pShape->setName(mpShapePtr->getName());
593 pShape->setId(mpShapePtr->getId());
594 pShape->setWPGChild(mpShapePtr->isWPGChild());
595 // And actually replace the shape.
596 mpShapePtr = pShape;
597 mpMasterShapePtr->getChildren().pop_back();
598 mpMasterShapePtr->getChildren().push_back(pShape);
599 return new oox::drawingml::ConnectorShapePropertiesContext(
600 *this, mpShapePtr, mpShapePtr->getConnectorShapeProperties());
602 case XML_bodyPr:
603 if (mxShape.is())
605 // no evaluation of attribute XML_rot, because Word ignores it, as of 2022-07.
607 uno::Reference<lang::XServiceInfo> xServiceInfo(mxShape, uno::UNO_QUERY);
608 uno::Reference<beans::XPropertySet> xPropertySet(mxShape, uno::UNO_QUERY);
609 sal_Int32 nVert = rAttribs.getToken(XML_vert, XML_horz);
610 // Values 'wordArtVert' and 'wordArtVertRtl' are not implemented.
611 // Map them to other vert values.
612 if (nVert == XML_eaVert || nVert == XML_wordArtVertRtl)
614 xPropertySet->setPropertyValue("TextWritingMode",
615 uno::Any(text::WritingMode_TB_RL));
616 xPropertySet->setPropertyValue("WritingMode",
617 uno::Any(text::WritingMode2::TB_RL));
619 else if (nVert == XML_mongolianVert || nVert == XML_wordArtVert)
621 xPropertySet->setPropertyValue("WritingMode",
622 uno::Any(text::WritingMode2::TB_LR));
624 else if (nVert != XML_horz) // cases XML_vert and XML_vert270
626 // Hack to get same rendering as after the fix for tdf#87924. If shape rotation
627 // plus text direction results in upright text, use horizontal text direction.
628 // Remove hack when frame is able to rotate.
630 // Need transformation matrix since RotateAngle does not contain flip.
631 drawing::HomogenMatrix3 aMatrix;
632 xPropertySet->getPropertyValue("Transformation") >>= aMatrix;
633 basegfx::B2DHomMatrix aTransformation;
634 aTransformation.set(0, 0, aMatrix.Line1.Column1);
635 aTransformation.set(0, 1, aMatrix.Line1.Column2);
636 aTransformation.set(0, 2, aMatrix.Line1.Column3);
637 aTransformation.set(1, 0, aMatrix.Line2.Column1);
638 aTransformation.set(1, 1, aMatrix.Line2.Column2);
639 aTransformation.set(1, 2, aMatrix.Line2.Column3);
640 // For this to be a valid 2D transform matrix, the last row must be [0,0,1]
641 assert(aMatrix.Line3.Column1 == 0);
642 assert(aMatrix.Line3.Column2 == 0);
643 assert(aMatrix.Line3.Column3 == 1);
644 basegfx::B2DTuple aScale;
645 basegfx::B2DTuple aTranslate;
646 double fRotate = 0;
647 double fShearX = 0;
648 aTransformation.decompose(aScale, aTranslate, fRotate, fShearX);
649 auto nRotate(static_cast<sal_uInt16>(NormAngle360(basegfx::rad2deg(fRotate))));
650 if ((nVert == XML_vert && nRotate == 270)
651 || (nVert == XML_vert270 && nRotate == 90))
653 xPropertySet->setPropertyValue("WritingMode",
654 uno::Any(text::WritingMode2::LR_TB));
655 // ToDo: Remember original vert value and remove hack on export.
657 else if (nVert == XML_vert)
658 xPropertySet->setPropertyValue("WritingMode",
659 uno::Any(text::WritingMode2::TB_RL90));
660 else // nVert == XML_vert270
661 xPropertySet->setPropertyValue("WritingMode",
662 uno::Any(text::WritingMode2::BT_LR));
665 if (bool bUpright = rAttribs.getBool(XML_upright, false))
667 uno::Sequence<beans::PropertyValue> aGrabBag;
668 xPropertySet->getPropertyValue("InteropGrabBag") >>= aGrabBag;
669 sal_Int32 length = aGrabBag.getLength();
670 aGrabBag.realloc(length + 1);
671 auto pGrabBag = aGrabBag.getArray();
672 pGrabBag[length].Name = "Upright";
673 pGrabBag[length].Value <<= bUpright;
674 xPropertySet->setPropertyValue("InteropGrabBag", uno::Any(aGrabBag));
677 if (xServiceInfo.is())
679 // Handle inset attributes for Writer textframes.
680 sal_Int32 aInsets[] = { XML_lIns, XML_tIns, XML_rIns, XML_bIns };
681 std::optional<sal_Int32> oInsets[4];
682 for (std::size_t i = 0; i < SAL_N_ELEMENTS(aInsets); ++i)
684 std::optional<OUString> oValue = rAttribs.getString(aInsets[i]);
685 if (oValue.has_value())
686 oInsets[i] = oox::drawingml::GetCoordinate(oValue.value());
687 else
688 // Defaults from the spec: left/right: 91440 EMU, top/bottom: 45720 EMU
689 oInsets[i]
690 = (aInsets[i] == XML_lIns || aInsets[i] == XML_rIns) ? 254 : 127;
692 const OUString aShapeProps[]
693 = { OUString("TextLeftDistance"), OUString("TextUpperDistance"),
694 OUString("TextRightDistance"), OUString("TextLowerDistance") };
695 for (std::size_t i = 0; i < SAL_N_ELEMENTS(aShapeProps); ++i)
696 if (oInsets[i])
697 xPropertySet->setPropertyValue(aShapeProps[i], uno::Any(*oInsets[i]));
700 // Handle text vertical adjustment inside a text frame
701 if (rAttribs.hasAttribute(XML_anchor))
703 drawing::TextVerticalAdjust eAdjust
704 = drawingml::GetTextVerticalAdjust(rAttribs.getToken(XML_anchor, XML_t));
705 xPropertySet->setPropertyValue("TextVerticalAdjust", uno::Any(eAdjust));
708 // Apply character color of the shape to the shape's textbox.
709 uno::Reference<text::XText> xText(mxShape, uno::UNO_QUERY);
710 uno::Any xCharColor = xPropertySet->getPropertyValue("CharColor");
711 Color aColor = COL_AUTO;
712 if ((xCharColor >>= aColor) && aColor != COL_AUTO)
714 // tdf#135923 Apply character color of the shape to the textrun
715 // when the character color of the textrun is default.
716 // tdf#153791 But only if the run has no background color (shd element in OOXML)
717 if (uno::Reference<container::XEnumerationAccess> paraEnumAccess{
718 xText, uno::UNO_QUERY })
720 uno::Reference<container::XEnumeration> paraEnum(
721 paraEnumAccess->createEnumeration());
723 while (paraEnum->hasMoreElements())
725 uno::Reference<text::XTextRange> xParagraph(paraEnum->nextElement(),
726 uno::UNO_QUERY);
727 uno::Reference<container::XEnumerationAccess> runEnumAccess(
728 xParagraph, uno::UNO_QUERY);
729 if (!runEnumAccess.is())
730 continue;
731 if (uno::Reference<beans::XPropertySet> xParaPropSet{ xParagraph,
732 uno::UNO_QUERY })
733 if ((xParaPropSet->getPropertyValue("ParaBackColor") >>= aColor)
734 && aColor != COL_AUTO)
735 continue;
737 uno::Reference<container::XEnumeration> runEnum
738 = runEnumAccess->createEnumeration();
740 while (runEnum->hasMoreElements())
742 uno::Reference<text::XTextRange> xRun(runEnum->nextElement(),
743 uno::UNO_QUERY);
744 const uno::Reference<beans::XPropertyState> xRunState(
745 xRun, uno::UNO_QUERY);
746 if (!xRunState
747 || xRunState->getPropertyState("CharColor")
748 == beans::PropertyState_DEFAULT_VALUE)
750 uno::Reference<beans::XPropertySet> xRunPropSet(xRun,
751 uno::UNO_QUERY);
752 if (!xRunPropSet)
753 continue;
754 if ((xRunPropSet->getPropertyValue("CharBackColor") >>= aColor)
755 && aColor != COL_AUTO)
756 continue;
757 if (!(xRunPropSet->getPropertyValue("CharColor") >>= aColor)
758 || aColor == COL_AUTO)
759 xRunPropSet->setPropertyValue("CharColor", xCharColor);
766 auto nWrappingType = rAttribs.getToken(XML_wrap, XML_square);
767 xPropertySet->setPropertyValue("TextWordWrap",
768 uno::Any(nWrappingType == XML_square));
770 return this;
772 else if (m_bHasWPGParent && mpShapePtr)
774 // this WPS context has to be inside a WPG shape, so the <BodyPr> element
775 // cannot be applied to mxShape member, use mpShape instead, and after the
776 // the parent shape finished, apply it for its children.
777 mpShapePtr->setWPGChild(true);
778 oox::drawingml::TextBodyPtr pTextBody;
779 pTextBody.reset(new oox::drawingml::TextBody());
781 if (rAttribs.hasAttribute(XML_anchor))
783 drawing::TextVerticalAdjust eAdjust
784 = drawingml::GetTextVerticalAdjust(rAttribs.getToken(XML_anchor, XML_t));
785 pTextBody->getTextProperties().meVA = eAdjust;
788 sal_Int32 aInsets[] = { XML_lIns, XML_tIns, XML_rIns, XML_bIns };
789 for (int i = 0; i < 4; ++i)
791 if (rAttribs.hasAttribute(XML_lIns))
793 std::optional<OUString> oValue = rAttribs.getString(aInsets[i]);
794 if (oValue.has_value())
795 pTextBody->getTextProperties().moInsets[i]
796 = oox::drawingml::GetCoordinate(oValue.value());
797 else
798 // Defaults from the spec: left/right: 91440 EMU, top/bottom: 45720 EMU
799 pTextBody->getTextProperties().moInsets[i]
800 = (aInsets[i] == XML_lIns || aInsets[i] == XML_rIns) ? 254 : 127;
804 mpShapePtr->setTextBody(pTextBody);
806 break;
807 case XML_noAutofit:
808 case XML_spAutoFit:
810 uno::Reference<lang::XServiceInfo> xServiceInfo(mxShape, uno::UNO_QUERY);
811 // We can't use oox::drawingml::TextBodyPropertiesContext here, as this
812 // is a child context of bodyPr, so the shape is already sent: we need
813 // to alter the XShape directly.
814 uno::Reference<beans::XPropertySet> xPropertySet(mxShape, uno::UNO_QUERY);
815 if (xPropertySet.is())
817 if (xServiceInfo->supportsService("com.sun.star.text.TextFrame"))
818 xPropertySet->setPropertyValue(
819 "FrameIsAutomaticHeight",
820 uno::Any(getBaseToken(nElementToken) == XML_spAutoFit));
821 else
822 xPropertySet->setPropertyValue(
823 "TextAutoGrowHeight",
824 uno::Any(getBaseToken(nElementToken) == XML_spAutoFit));
827 break;
828 case XML_prstTxWarp:
829 if (rAttribs.hasAttribute(XML_prst))
831 uno::Reference<beans::XPropertySet> xPropertySet(mxShape, uno::UNO_QUERY);
832 if (xPropertySet.is())
834 std::optional<OUString> presetShapeName = rAttribs.getString(XML_prst);
835 const OUString& preset = presetShapeName.value();
836 comphelper::SequenceAsHashMap aCustomShapeGeometry(
837 xPropertySet->getPropertyValue("CustomShapeGeometry"));
838 aCustomShapeGeometry["PresetTextWarp"] <<= preset;
839 xPropertySet->setPropertyValue(
840 "CustomShapeGeometry",
841 uno::Any(aCustomShapeGeometry.getAsConstPropertyValueList()));
844 return new oox::drawingml::PresetTextShapeContext(
845 *this, rAttribs, *(getShape()->getCustomShapeProperties()));
846 case XML_txbx:
848 mpShapePtr->getCustomShapeProperties()->setShapeTypeOverride(true);
849 mpShapePtr->setTextBox(true);
850 //in case if the textbox is linked, save the attributes
851 //for further processing.
852 if (rAttribs.hasAttribute(XML_id))
854 std::optional<OUString> id = rAttribs.getString(XML_id);
855 if (id.has_value())
857 oox::drawingml::LinkedTxbxAttr linkedTxtBoxAttr;
858 linkedTxtBoxAttr.id = id.value().toInt32();
859 mpShapePtr->setTxbxHasLinkedTxtBox(true);
860 mpShapePtr->setLinkedTxbxAttributes(linkedTxtBoxAttr);
863 return this;
865 break;
866 case XML_linkedTxbx:
868 //in case if the textbox is linked, save the attributes
869 //for further processing.
870 mpShapePtr->getCustomShapeProperties()->setShapeTypeOverride(true);
871 mpShapePtr->setTextBox(true);
872 std::optional<OUString> id = rAttribs.getString(XML_id);
873 std::optional<OUString> seq = rAttribs.getString(XML_seq);
874 if (id.has_value() && seq.has_value())
876 oox::drawingml::LinkedTxbxAttr linkedTxtBoxAttr;
877 linkedTxtBoxAttr.id = id.value().toInt32();
878 linkedTxtBoxAttr.seq = seq.value().toInt32();
879 mpShapePtr->setTxbxHasLinkedTxtBox(true);
880 mpShapePtr->setLinkedTxbxAttributes(linkedTxtBoxAttr);
883 break;
884 default:
885 return ShapeContext::onCreateContext(nElementToken, rAttribs);
887 return nullptr;
890 void WpsContext::onEndElement()
892 // Convert shape to Fontwork shape if necessary and meaningful.
893 // Only at end of bodyPr all needed info is available.
895 if (getBaseToken(getCurrentElement()) != XML_bodyPr)
896 return;
898 // Make sure all needed parts are available
899 auto* pCustomShape
900 = dynamic_cast<SdrObjCustomShape*>(SdrObject::getSdrObjectFromXShape(mxShape));
901 if (!pCustomShape || !mpShapePtr || !mxShape.is())
902 return;
903 uno::Reference<beans::XPropertySet> xShapePropertySet(mxShape, uno::UNO_QUERY);
904 if (!xShapePropertySet.is())
905 return;
906 // This is the text in the frame, associated with the shape
907 uno::Reference<text::XText> xText(mxShape, uno::UNO_QUERY);
908 if (!xText.is())
909 return;
911 OUString sMSPresetType;
912 comphelper::SequenceAsHashMap aCustomShapeGeometry(
913 xShapePropertySet->getPropertyValue("CustomShapeGeometry"));
914 aCustomShapeGeometry["PresetTextWarp"] >>= sMSPresetType;
915 if (sMSPresetType.isEmpty() || sMSPresetType == u"textNoShape")
916 return;
918 // Word can combine its "abc Transform" with a lot of shape types. LibreOffice can only render
919 // the old kind WordArt, which is based on a rectangle. In case of non rectangular shape we keep
920 // the shape and do not convert the text to Fontwork.
921 OUString sType;
922 aCustomShapeGeometry["Type"] >>= sType;
923 if (sType != u"ooxml-rect")
924 return;
926 // Copy properties from frame text to have them available after the frame is removed.
927 std::vector<beans::PropertyValue> aTextPropVec;
928 if (!lcl_getTextPropsFromFrameText(xText, aTextPropVec))
929 return;
930 comphelper::SequenceAsHashMap aTextPropMap(comphelper::containerToSequence(aTextPropVec));
932 // Copy text content from frame to shape. Since Fontwork uses simple text anyway, we can use
933 // a string.
934 OUString sFrameContent(xText->getString());
935 pCustomShape->NbcSetText(sFrameContent);
937 // Setting the property "TextBox" to false includes removing the attached frame from the shape.
938 xShapePropertySet->setPropertyValue("TextBox", uno::Any(false));
940 // Set the shape into text path mode, so that the text is drawn as Fontwork. Word renders a legacy
941 // "text on path" without the legacy stretching, therefore use false for bFromWordArt.
942 mpShapePtr->getCustomShapeProperties()->setShapeTypeOverride(true);
943 FontworkHelpers::putCustomShapeIntoTextPathMode(mxShape, getShape()->getCustomShapeProperties(),
944 sMSPresetType, /*bFromWordArt*/ false);
946 // Apply the text props to the fontwork shape
947 lcl_setTextPropsToShape(xShapePropertySet, aTextPropVec); // includes e.g. FontName
948 lcl_setTextAnchorFromTextProps(xShapePropertySet, aTextPropMap);
950 // Fontwork in LO uses fill and stroke of the shape and cannot style text portions individually.
951 // "abc Transform" in Word uses fill and outline of the characters.
952 // We need to copy the properties from a run to the shape.
953 oox::drawingml::ShapePropertyMap aStrokeShapeProps(getFilter().getModelObjectHelper());
954 oox::drawingml::LineProperties aCreatedLineProperties
955 = lcl_generateLinePropertiesFromTextProps(aTextPropMap);
956 aCreatedLineProperties.pushToPropMap(aStrokeShapeProps, getFilter().getGraphicHelper());
957 lcl_applyShapePropsToShape(xShapePropertySet, aStrokeShapeProps);
959 oox::drawingml::ShapePropertyMap aFillShapeProps(getFilter().getModelObjectHelper());
960 oox::drawingml::FillProperties aCreatedFillProperties
961 = lcl_generateFillPropertiesFromTextProps(aTextPropMap);
962 aCreatedFillProperties.pushToPropMap(aFillShapeProps, getFilter().getGraphicHelper(),
963 /*nShapeRotation*/ 0,
964 /*nPhClr*/ API_RGB_TRANSPARENT,
965 /*aShapeSize*/ css::awt::Size(0, 0), /*nPhClrTheme*/ -1,
966 pCustomShape->IsMirroredX(), pCustomShape->IsMirroredY(),
967 /*bIsCustomShape*/ true);
968 lcl_applyShapePropsToShape(xShapePropertySet, aFillShapeProps);
970 // Copying the text content from frame to shape as string has lost the styles. Apply the used text
971 // properties back to all runs in the text.
972 uno::Reference<text::XText> xNewText(pCustomShape->getUnoShape(), uno::UNO_QUERY);
973 if (xNewText.is())
974 lcl_applyUsedTextPropsToAllTextRuns(xNewText, aTextPropVec);
976 // Fontwork stretches the text to the given path. So adapt shape size to text is nonsensical.
977 xShapePropertySet->setPropertyValue("TextAutoGrowHeight", uno::Any(false));
978 xShapePropertySet->setPropertyValue("TextAutoGrowWidth", uno::Any(false));
982 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */