1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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/.
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>
49 using namespace com::sun::star
;
53 bool lcl_getTextPropsFromFrameText(const uno::Reference
<text::XText
>& xText
,
54 std::vector
<beans::PropertyValue
>& rTextPropVec
)
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())
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())
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())
77 uno::Reference
<beans::XPropertySet
> xRunPropSet(xRun
, uno::UNO_QUERY
);
78 if (!xRunPropSet
.is())
80 auto xRunPropSetInfo
= xRunPropSet
->getPropertySetInfo();
81 if (!xRunPropSetInfo
.is())
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
)));
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()))
109 comphelper::SequenceAsHashMap
aAttributesMap(aAttributesSeq
);
111 if (!(aAttributesMap
.getValue(rName
) >>= sRet
))
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()))
126 comphelper::SequenceAsHashMap
aAttributesMap(aAttributesSeq
);
128 if (!(aAttributesMap
.getValue(rName
) >>= nRet
))
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
);
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
);
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
);
188 sal_Int32 nColor
= oox::AttributeConversion::decodeIntegerHex(sColorString
);
189 rColor
.setSrgbClr(nColor
);
192 // Without color, color transformations are pointless.
194 lcl_getColorTransformationsFromPropSeq(aColorDetailSeq
, rColor
);
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"
203 if (!rTextFillSeq
.hasElements())
205 comphelper::SequenceAsHashMap
aTextFillMap(rTextFillSeq
);
206 if (aTextFillMap
.find(u
"noFill"_ustr
) != aTextFillMap
.end())
208 rFillProperties
.moFillType
= oox::XML_noFill
;
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
);
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
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";
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.
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
))
282 if (!lcl_getAttributeAsNumber(aFillToRectSeq
, u
"t"_ustr
, aRect
.Y1
))
284 if (!lcl_getAttributeAsNumber(aFillToRectSeq
, u
"r"_ustr
, aRect
.X2
))
286 if (!lcl_getAttributeAsNumber(aFillToRectSeq
, u
"b"_ustr
, aRect
.Y2
))
288 rFillProperties
.maGradientProps
.moFillToRect
= aRect
;
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
303 // aTextOulineSeq contains e.g. "attributes" {w, cap, cmpd, ctr}, either
304 // "solidFill" or "gradFill or "noFill", and "prstDash" and "lineJoint" properties.
307 lcl_getFillDetailsFromPropSeq(rTextOutlineSeq
, rLineProperties
.maLineFill
);
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
;
323 rLineProperties
.moLineJoint
= oox::XML_miter
;
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"
332 if (lcl_getAttributeAsString(aDashSeq
, u
"val"_ustr
, sDashKind
))
333 rLineProperties
.moPresetDash
= oox::AttributeConversion::decodeToken(sDashKind
);
336 if (lcl_getAttributeAsString(rTextOutlineSeq
, u
"cap"_ustr
, sCapKind
))
337 rLineProperties
.moLineCap
= oox::AttributeConversion::decodeToken(sCapKind
);
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".
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
);
415 if (aCharInteropGrabBagMap
.getValue("CharThemeColor") >>= sColorString
)
417 sal_Int32 nColorToken
= oox::AttributeConversion::decodeToken(sColorString
);
418 aFillProperties
.maFillColor
.setSchemeClr(nColorToken
);
419 aFillProperties
.maFillColor
.setSchemeName(sColorString
);
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));
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
);
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
;
472 case sal_Int16(style::ParagraphAdjust_LEFT
):
473 eHorzAdjust
= drawing::TextHorizontalAdjust_LEFT
;
475 case sal_Int16(style::ParagraphAdjust_RIGHT
):
476 eHorzAdjust
= drawing::TextHorizontalAdjust_RIGHT
;
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())
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
)
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())
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())
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())
528 uno::Reference
<beans::XPropertySet
> xRunPropSet(xRun
, uno::UNO_QUERY
);
529 if (!xRunPropSet
.is())
531 auto xRunPropSetInfo
= xRunPropSet
->getPropertySetInfo();
532 if (!xRunPropSetInfo
.is())
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
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
))
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;
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
))
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
)
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.
597 mpMasterShapePtr
->getChildren().pop_back();
598 mpMasterShapePtr
->getChildren().push_back(pShape
);
599 return new oox::drawingml::ConnectorShapePropertiesContext(
600 *this, mpShapePtr
, mpShapePtr
->getConnectorShapeProperties());
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
;
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());
688 // Defaults from the spec: left/right: 91440 EMU, top/bottom: 45720 EMU
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
)
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(),
727 uno::Reference
<container::XEnumerationAccess
> runEnumAccess(
728 xParagraph
, uno::UNO_QUERY
);
729 if (!runEnumAccess
.is())
731 if (uno::Reference
<beans::XPropertySet
> xParaPropSet
{ xParagraph
,
733 if ((xParaPropSet
->getPropertyValue("ParaBackColor") >>= aColor
)
734 && aColor
!= COL_AUTO
)
737 uno::Reference
<container::XEnumeration
> runEnum
738 = runEnumAccess
->createEnumeration();
740 while (runEnum
->hasMoreElements())
742 uno::Reference
<text::XTextRange
> xRun(runEnum
->nextElement(),
744 const uno::Reference
<beans::XPropertyState
> xRunState(
745 xRun
, uno::UNO_QUERY
);
747 || xRunState
->getPropertyState("CharColor")
748 == beans::PropertyState_DEFAULT_VALUE
)
750 uno::Reference
<beans::XPropertySet
> xRunPropSet(xRun
,
754 if ((xRunPropSet
->getPropertyValue("CharBackColor") >>= aColor
)
755 && aColor
!= COL_AUTO
)
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
));
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());
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
);
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
));
822 xPropertySet
->setPropertyValue(
823 "TextAutoGrowHeight",
824 uno::Any(getBaseToken(nElementToken
) == XML_spAutoFit
));
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()));
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
);
857 oox::drawingml::LinkedTxbxAttr linkedTxtBoxAttr
;
858 linkedTxtBoxAttr
.id
= id
.value().toInt32();
859 mpShapePtr
->setTxbxHasLinkedTxtBox(true);
860 mpShapePtr
->setLinkedTxbxAttributes(linkedTxtBoxAttr
);
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
);
885 return ShapeContext::onCreateContext(nElementToken
, rAttribs
);
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
)
898 // Make sure all needed parts are available
900 = dynamic_cast<SdrObjCustomShape
*>(SdrObject::getSdrObjectFromXShape(mxShape
));
901 if (!pCustomShape
|| !mpShapePtr
|| !mxShape
.is())
903 uno::Reference
<beans::XPropertySet
> xShapePropertySet(mxShape
, uno::UNO_QUERY
);
904 if (!xShapePropertySet
.is())
906 // This is the text in the frame, associated with the shape
907 uno::Reference
<text::XText
> xText(mxShape
, uno::UNO_QUERY
);
911 OUString sMSPresetType
;
912 comphelper::SequenceAsHashMap
aCustomShapeGeometry(
913 xShapePropertySet
->getPropertyValue("CustomShapeGeometry"));
914 aCustomShapeGeometry
["PresetTextWarp"] >>= sMSPresetType
;
915 if (sMSPresetType
.isEmpty() || sMSPresetType
== u
"textNoShape")
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.
922 aCustomShapeGeometry
["Type"] >>= sType
;
923 if (sType
!= u
"ooxml-rect")
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
))
930 comphelper::SequenceAsHashMap
aTextPropMap(comphelper::containerToSequence(aTextPropVec
));
932 // Copy text content from frame to shape. Since Fontwork uses simple text anyway, we can use
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
);
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: */