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 <basegfx/matrix/b2dhommatrix.hxx>
13 #include <basegfx/tuple/b2dtuple.hxx>
14 #include <comphelper/propertyvalue.hxx>
15 #include <comphelper/sequence.hxx>
16 #include <comphelper/sequenceashashmap.hxx>
17 #include <drawingml/customshapegeometry.hxx>
18 #include <drawingml/customshapeproperties.hxx>
19 #include <drawingml/fontworkhelpers.hxx>
20 #include <drawingml/textbody.hxx>
21 #include <drawingml/textbodyproperties.hxx>
22 #include <oox/drawingml/color.hxx>
23 #include <oox/drawingml/drawingmltypes.hxx>
24 #include <oox/drawingml/shape.hxx>
25 #include <oox/drawingml/shapepropertymap.hxx>
26 #include <oox/helper/attributelist.hxx>
27 #include <oox/token/namespaces.hxx>
28 #include <oox/token/tokens.hxx>
29 #include <svx/svdoashp.hxx>
31 #include <com/sun/star/beans/PropertyAttribute.hpp>
32 #include <com/sun/star/beans/XPropertySet.hpp>
33 #include <com/sun/star/beans/XPropertySetInfo.hpp>
34 #include <com/sun/star/beans/XPropertyState.hpp>
35 #include <com/sun/star/container/XEnumerationAccess.hpp>
36 #include <com/sun/star/drawing/HomogenMatrix3.hpp>
37 #include <com/sun/star/drawing/TextHorizontalAdjust.hpp>
38 #include <com/sun/star/geometry/IntegerRectangle2D.hpp>
39 #include <com/sun/star/lang/XServiceInfo.hpp>
40 #include <com/sun/star/text/XText.hpp>
41 #include <com/sun/star/text/XTextCursor.hpp>
42 #include <com/sun/star/text/WritingMode.hpp>
43 #include <com/sun/star/text/WritingMode2.hpp>
47 using namespace com::sun::star
;
51 bool lcl_getTextPropsFromFrameText(const uno::Reference
<text::XText
>& xText
,
52 std::vector
<beans::PropertyValue
>& rTextPropVec
)
56 uno::Reference
<text::XTextCursor
> xTextCursor
= xText
->createTextCursor();
57 xTextCursor
->gotoStart(false);
58 xTextCursor
->gotoEnd(true);
59 uno::Reference
<container::XEnumerationAccess
> paraEnumAccess(xText
, uno::UNO_QUERY
);
60 if (!paraEnumAccess
.is())
62 uno::Reference
<container::XEnumeration
> paraEnum(paraEnumAccess
->createEnumeration());
63 while (paraEnum
->hasMoreElements())
65 uno::Reference
<text::XTextRange
> xParagraph(paraEnum
->nextElement(), uno::UNO_QUERY
);
66 uno::Reference
<container::XEnumerationAccess
> runEnumAccess(xParagraph
, uno::UNO_QUERY
);
67 if (!runEnumAccess
.is())
69 uno::Reference
<container::XEnumeration
> runEnum
= runEnumAccess
->createEnumeration();
70 while (runEnum
->hasMoreElements())
72 uno::Reference
<text::XTextRange
> xRun(runEnum
->nextElement(), uno::UNO_QUERY
);
73 if (xRun
->getString().isEmpty())
75 uno::Reference
<beans::XPropertySet
> xRunPropSet(xRun
, uno::UNO_QUERY
);
76 if (!xRunPropSet
.is())
78 auto xRunPropSetInfo
= xRunPropSet
->getPropertySetInfo();
79 if (!xRunPropSetInfo
.is())
82 // We have found a non-empty run. Collect its properties.
83 auto aRunPropInfoSequence
= xRunPropSetInfo
->getProperties();
84 for (const beans::Property
& aProp
: aRunPropInfoSequence
)
86 rTextPropVec
.push_back(comphelper::makePropertyValue(
87 aProp
.Name
, xRunPropSet
->getPropertyValue(aProp
.Name
)));
95 // CharInteropGrabBag puts all attributes of an element into a property with Name="attributes" and
96 // Value being a sequence of the attributes. This methods finds the value of an individual rName
97 // attribute and puts it into rValue parameter. If it does not find it, rValue is unchanged and
98 // the method returns false, otherwise it returns true.
99 bool lcl_getAttributeAsString(const uno::Sequence
<beans::PropertyValue
>& aPropertyValueAsSeq
,
100 const OUString
& rName
, OUString
& rValue
)
102 comphelper::SequenceAsHashMap
aPropertyValueAsMap(aPropertyValueAsSeq
);
103 uno::Sequence
<beans::PropertyValue
> aAttributesSeq
;
104 if (!((aPropertyValueAsMap
.getValue("attributes") >>= aAttributesSeq
)
105 && aAttributesSeq
.hasElements()))
107 comphelper::SequenceAsHashMap
aAttributesMap(aAttributesSeq
);
109 if (!(aAttributesMap
.getValue(rName
) >>= sRet
))
115 // Same as above for a number as attribute value
116 bool lcl_getAttributeAsNumber(const uno::Sequence
<beans::PropertyValue
>& rPropertyValueAsSeq
,
117 const OUString
& rName
, sal_Int32
& rValue
)
119 comphelper::SequenceAsHashMap
aPropertyValueAsMap(rPropertyValueAsSeq
);
120 uno::Sequence
<beans::PropertyValue
> aAttributesSeq
;
121 if (!((aPropertyValueAsMap
.getValue("attributes") >>= aAttributesSeq
)
122 && aAttributesSeq
.hasElements()))
124 comphelper::SequenceAsHashMap
aAttributesMap(aAttributesSeq
);
126 if (!(aAttributesMap
.getValue(rName
) >>= nRet
))
132 void lcl_getColorTransformationsFromPropSeq(const uno::Sequence
<beans::PropertyValue
>& rPropSeq
,
133 oox::drawingml::Color
& rColor
)
135 auto isValidPropName
= [](const OUString
& rName
) -> bool {
136 return rName
== u
"tint" || rName
== u
"shade" || rName
== u
"alpha" || rName
== u
"hueMod"
137 || rName
== u
"sat" || rName
== u
"satOff" || rName
== u
"satMod" || rName
== u
"lum"
138 || rName
== u
"lumOff" || rName
== u
"lumMod";
140 for (auto it
= rPropSeq
.begin(); it
< rPropSeq
.end(); ++it
)
142 if (isValidPropName((*it
).Name
))
144 uno::Sequence
<beans::PropertyValue
> aValueSeq
;
145 sal_Int32
nNumber(0); // dummy value to make compiler happy, "val" should exist
146 if (((*it
).Value
>>= aValueSeq
) && lcl_getAttributeAsNumber(aValueSeq
, u
"val", nNumber
))
148 // char w14:alpha contains transparency, whereas shape fill a:alpha contains opacity.
149 if ((*it
).Name
== u
"alpha")
150 rColor
.addTransformation(
151 oox::NMSP_dml
| oox::AttributeConversion::decodeToken((*it
).Name
),
152 oox::drawingml::MAX_PERCENT
- nNumber
);
154 rColor
.addTransformation(
155 oox::NMSP_w14
| oox::AttributeConversion::decodeToken((*it
).Name
), nNumber
);
161 // Expected: rPropSeq contains a property "schemeClr" or a property "srgbClr".
162 bool lcl_getColorFromPropSeq(const uno::Sequence
<beans::PropertyValue
>& rPropSeq
,
163 oox::drawingml::Color
& rColor
)
165 bool bColorFound
= false;
166 comphelper::SequenceAsHashMap
aPropMap(rPropSeq
);
167 uno::Sequence
<beans::PropertyValue
> aColorDetailSeq
;
168 if (aPropMap
.getValue(u
"schemeClr") >>= aColorDetailSeq
)
170 OUString sColorString
;
171 bColorFound
= lcl_getAttributeAsString(aColorDetailSeq
, u
"val", sColorString
);
174 sal_Int32 nColorToken
= oox::AttributeConversion::decodeToken(sColorString
);
175 rColor
.setSchemeClr(nColorToken
);
176 rColor
.setSchemeName(sColorString
);
179 if (!bColorFound
&& (aPropMap
.getValue(u
"srgbClr") >>= aColorDetailSeq
))
181 OUString sColorString
;
182 bColorFound
= lcl_getAttributeAsString(aColorDetailSeq
, u
"val", sColorString
);
185 sal_Int32 nColor
= oox::AttributeConversion::decodeIntegerHex(sColorString
);
186 rColor
.setSrgbClr(nColor
);
189 // Without color, color transformations are pointless.
191 lcl_getColorTransformationsFromPropSeq(aColorDetailSeq
, rColor
);
195 void lcl_getFillDetailsFromPropSeq(const uno::Sequence
<beans::PropertyValue
>& rTextFillSeq
,
196 oox::drawingml::FillProperties
& rFillProperties
)
198 // rTextFillSeq should have an item containing either "noFill" or "solidFill" or "gradFill"
200 if (!rTextFillSeq
.hasElements())
202 comphelper::SequenceAsHashMap
aTextFillMap(rTextFillSeq
);
203 if (aTextFillMap
.find(u
"noFill") != aTextFillMap
.end())
205 rFillProperties
.moFillType
= oox::XML_noFill
;
209 uno::Sequence
<beans::PropertyValue
> aPropSeq
;
210 if ((aTextFillMap
.getValue(u
"solidFill") >>= aPropSeq
) && aPropSeq
.hasElements())
212 rFillProperties
.moFillType
= oox::XML_solidFill
;
213 lcl_getColorFromPropSeq(aPropSeq
, rFillProperties
.maFillColor
);
217 if ((aTextFillMap
.getValue(u
"gradFill") >>= aPropSeq
) && aPropSeq
.hasElements())
219 rFillProperties
.moFillType
= oox::XML_gradFill
;
220 // aPropSeq should have two items. One is "gsLst" for the stop colors, the other is
221 // either "lin" or "path" for the kind of gradient.
222 // First get stop colors
223 comphelper::SequenceAsHashMap
aPropMap(aPropSeq
);
224 uno::Sequence
<beans::PropertyValue
> aGsLstSeq
;
225 if (aPropMap
.getValue("gsLst") >>= aGsLstSeq
)
227 for (auto it
= aGsLstSeq
.begin(); it
< aGsLstSeq
.end(); ++it
)
229 // (*it) is a bean::PropertyValue with Name="gs". Its Value is a property sequence.
230 uno::Sequence
<beans::PropertyValue
> aColorStopSeq
;
231 if ((*it
).Value
>>= aColorStopSeq
)
233 // aColorStopSeq should have an item for the color and an item for the position
235 oox::drawingml::Color aColor
;
236 if (lcl_getAttributeAsNumber(aColorStopSeq
, u
"pos", nPos
)
237 && lcl_getColorFromPropSeq(aColorStopSeq
, aColor
))
239 // The position in maGradientStops is relative, thus in range [0.0;1.0].
240 double fPos
= nPos
/ 100000.0;
241 rFillProperties
.maGradientProps
.maGradientStops
.insert({ fPos
, aColor
});
246 // Now determine kind of gradient.
247 uno::Sequence
<beans::PropertyValue
> aKindSeq
;
248 if (aPropMap
.getValue("lin") >>= aKindSeq
)
250 // aKindSeq contains the attributes "ang" and "scaled"
251 sal_Int32 nAngle
; // in 1/60000 deg
252 if (lcl_getAttributeAsNumber(aKindSeq
, "ang", nAngle
))
253 rFillProperties
.maGradientProps
.moShadeAngle
= nAngle
;
254 OUString sScaledString
;
255 if (lcl_getAttributeAsString(aKindSeq
, "scaled", sScaledString
))
256 rFillProperties
.maGradientProps
.moShadeScaled
257 = sScaledString
== u
"1" || sScaledString
== u
"true";
260 if (aPropMap
.getValue("path") >>= aKindSeq
)
262 // aKindSeq contains the attribute "path" for the kind of path and a property "fillToRect"
263 // which defines the center rectangle of the gradient. The property "a:tileRect" known from
264 // fill of shapes does not exist in w14 namespace.
266 if (lcl_getAttributeAsString(aKindSeq
, "path", sKind
))
267 rFillProperties
.maGradientProps
.moGradientPath
268 = oox::AttributeConversion::decodeToken(sKind
);
269 comphelper::SequenceAsHashMap
aKindMap(aKindSeq
);
270 uno::Sequence
<beans::PropertyValue
> aFillToRectSeq
;
271 if (aKindMap
.getValue("fillToRect") >>= aFillToRectSeq
)
273 // The values l, t, r and b are not coordinates, but determine an offset from the
274 // edge of the bounding box of the shape. This unusual meaning of X1, Y1, X2 and
275 // Y2 is needed for method pushToPropMap() of FillProperties.
276 geometry::IntegerRectangle2D aRect
;
277 if (!lcl_getAttributeAsNumber(aFillToRectSeq
, u
"l", aRect
.X1
))
279 if (!lcl_getAttributeAsNumber(aFillToRectSeq
, u
"t", aRect
.Y1
))
281 if (!lcl_getAttributeAsNumber(aFillToRectSeq
, u
"r", aRect
.X2
))
283 if (!lcl_getAttributeAsNumber(aFillToRectSeq
, u
"b", aRect
.Y2
))
285 rFillProperties
.maGradientProps
.moFillToRect
= aRect
;
292 void lcl_getLineDetailsFromPropSeq(const uno::Sequence
<beans::PropertyValue
>& rTextOutlineSeq
,
293 oox::drawingml::LineProperties
& rLineProperties
)
295 if (!rTextOutlineSeq
.hasElements())
297 rLineProperties
.maLineFill
.moFillType
= oox::XML_noFill
; // MS Office default
300 // aTextOulineSeq contains e.g. "attributes" {w, cap, cmpd, ctr}, either
301 // "solidFill" or "gradFill or "noFill", and "prstDash" and "lineJoint" properties.
304 lcl_getFillDetailsFromPropSeq(rTextOutlineSeq
, rLineProperties
.maLineFill
);
307 comphelper::SequenceAsHashMap
aTextOutlineMap(rTextOutlineSeq
);
308 if (aTextOutlineMap
.find(u
"bevel") != aTextOutlineMap
.end())
309 rLineProperties
.moLineJoint
= oox::XML_bevel
;
310 else if (aTextOutlineMap
.find(u
"round") != aTextOutlineMap
.end())
311 rLineProperties
.moLineJoint
= oox::XML_round
;
312 else if (aTextOutlineMap
.find(u
"miter") != aTextOutlineMap
.end())
314 // LineProperties has no member to store a miter limit. Therefore some heuristic is
315 // added here. 0 is default for attribute "lim" in MS Office. It is rendered same as bevel.
316 sal_Int32 nMiterLimit
= aTextOutlineMap
.getUnpackedValueOrDefault("lim", sal_Int32(0));
317 if (nMiterLimit
== 0)
318 rLineProperties
.moLineJoint
= oox::XML_bevel
;
320 rLineProperties
.moLineJoint
= oox::XML_miter
;
324 uno::Sequence
<beans::PropertyValue
> aDashSeq
;
325 if (aTextOutlineMap
.getValue(u
"prstDash") >>= aDashSeq
)
327 // aDashSeq contains the attribute "val" with the kind of dash, e.g. "sysDot"
329 if (lcl_getAttributeAsString(aDashSeq
, u
"val", sDashKind
))
330 rLineProperties
.moPresetDash
= oox::AttributeConversion::decodeToken(sDashKind
);
333 if (lcl_getAttributeAsString(rTextOutlineSeq
, u
"cap", sCapKind
))
334 rLineProperties
.moLineCap
= oox::AttributeConversion::decodeToken(sCapKind
);
337 sal_Int32 nWidth
; // EMU
338 if (lcl_getAttributeAsNumber(rTextOutlineSeq
, u
"w", nWidth
))
339 rLineProperties
.moLineWidth
= nWidth
;
341 // Compound. LineProperties has a member for it, however Fontwork can currently only render "sng".
342 OUString sCompoundKind
;
343 if (lcl_getAttributeAsString(rTextOutlineSeq
, u
"cmpd", sCompoundKind
))
344 rLineProperties
.moLineCompound
= oox::AttributeConversion::decodeToken(sCompoundKind
);
346 // Align. LineProperties has no member for attribute "algn".
351 oox::drawingml::LineProperties
352 lcl_generateLinePropertiesFromTextProps(const comphelper::SequenceAsHashMap
& aTextPropMap
)
354 oox::drawingml::LineProperties aLineProperties
;
355 aLineProperties
.maLineFill
.moFillType
= oox::XML_noFill
; // default
357 // Get property "textOutline" from aTextPropMap
358 uno::Sequence
<beans::PropertyValue
> aCharInteropGrabBagSeq
;
359 if (!(aTextPropMap
.getValue(u
"CharInteropGrabBag") >>= aCharInteropGrabBagSeq
))
360 return aLineProperties
;
361 if (!aCharInteropGrabBagSeq
.hasElements())
362 return aLineProperties
;
363 comphelper::SequenceAsHashMap
aCharInteropGrabBagMap(aCharInteropGrabBagSeq
);
364 beans::PropertyValue aProp
;
365 if (!(aCharInteropGrabBagMap
.getValue(u
"CharTextOutlineTextEffect") >>= aProp
))
366 return aLineProperties
;
367 uno::Sequence
<beans::PropertyValue
> aTextOutlineSeq
;
368 if (!(aProp
.Name
== "textOutline" && (aProp
.Value
>>= aTextOutlineSeq
)
369 && aTextOutlineSeq
.hasElements()))
370 return aLineProperties
;
372 // Copy line properties from aTextOutlineSeq to aLineProperties
373 lcl_getLineDetailsFromPropSeq(aTextOutlineSeq
, aLineProperties
);
374 return aLineProperties
;
377 oox::drawingml::FillProperties
378 lcl_generateFillPropertiesFromTextProps(const comphelper::SequenceAsHashMap
& rTextPropMap
)
380 oox::drawingml::FillProperties aFillProperties
;
381 aFillProperties
.moFillType
= oox::XML_solidFill
; // default
382 // Theme color supersedes direct color. textFill supersedes theme color. Theme color and textFill
383 // are in CharInteropGrabBag.
384 uno::Sequence
<beans::PropertyValue
> aCharInteropGrabBagSeq
;
385 if ((rTextPropMap
.getValue(u
"CharInteropGrabBag") >>= aCharInteropGrabBagSeq
)
386 && aCharInteropGrabBagSeq
.hasElements())
388 // Handle case textFill
389 comphelper::SequenceAsHashMap
aCharInteropGrabBagMap(aCharInteropGrabBagSeq
);
390 beans::PropertyValue aProp
;
391 if (aCharInteropGrabBagMap
.getValue(u
"CharTextFillTextEffect") >>= aProp
)
393 uno::Sequence
<beans::PropertyValue
> aTextFillSeq
;
394 if (aProp
.Name
== "textFill" && (aProp
.Value
>>= aTextFillSeq
)
395 && aTextFillSeq
.hasElements())
397 // Copy fill properties from aTextFillSeq to aFillProperties
398 lcl_getFillDetailsFromPropSeq(aTextFillSeq
, aFillProperties
);
399 return aFillProperties
;
403 // no textFill, look for theme color, tint and shade
404 bool bColorFound(false);
405 OUString sColorString
;
406 if (aCharInteropGrabBagMap
.getValue("CharThemeOriginalColor") >>= sColorString
)
408 sal_Int32 nThemeOrigColor
= oox::AttributeConversion::decodeIntegerHex(sColorString
);
409 aFillProperties
.maFillColor
.setSrgbClr(nThemeOrigColor
);
412 if (aCharInteropGrabBagMap
.getValue("CharThemeColor") >>= sColorString
)
414 sal_Int32 nColorToken
= oox::AttributeConversion::decodeToken(sColorString
);
415 aFillProperties
.maFillColor
.setSchemeClr(nColorToken
);
416 aFillProperties
.maFillColor
.setSchemeName(sColorString
);
418 // A character color has shade or tint, a shape color has lumMod and lumOff.
419 OUString sTransformString
;
420 if (aCharInteropGrabBagMap
.getValue("CharThemeColorTint") >>= sTransformString
)
422 double fTint
= oox::AttributeConversion::decodeIntegerHex(sTransformString
);
423 fTint
= fTint
/ 255.0 * oox::drawingml::MAX_PERCENT
;
424 aFillProperties
.maFillColor
.addTransformation(OOX_TOKEN(w14
, lumMod
),
425 static_cast<sal_Int32
>(fTint
+ 0.5));
426 double fOff
= oox::drawingml::MAX_PERCENT
- fTint
;
427 aFillProperties
.maFillColor
.addTransformation(OOX_TOKEN(w14
, lumOff
),
428 static_cast<sal_Int32
>(fOff
+ 0.5));
430 else if (aCharInteropGrabBagMap
.getValue("CharThemeColorShade") >>= sTransformString
)
432 double fShade
= oox::AttributeConversion::decodeIntegerHex(sTransformString
);
433 fShade
= fShade
/ 255.0 * oox::drawingml::MAX_PERCENT
;
434 aFillProperties
.maFillColor
.addTransformation(OOX_TOKEN(w14
, lumMod
),
435 static_cast<sal_Int32
>(fShade
+ 0.5));
439 return aFillProperties
;
442 // Neither textFill nor theme color. Look for direct color.
443 sal_Int32 aCharColor
= 0;
444 if (rTextPropMap
.getValue(u
"CharColor") >>= aCharColor
)
445 aFillProperties
.maFillColor
.setSrgbClr(aCharColor
);
447 aFillProperties
.maFillColor
.setUnused();
448 return aFillProperties
;
451 void lcl_applyShapePropsToShape(const uno::Reference
<beans::XPropertySet
>& xShapePropertySet
,
452 const oox::drawingml::ShapePropertyMap
& rShapeProps
)
454 for (const auto& rProp
: rShapeProps
.makePropertyValueSequence())
456 xShapePropertySet
->setPropertyValue(rProp
.Name
, rProp
.Value
);
460 void lcl_setTextAnchorFromTextProps(const uno::Reference
<beans::XPropertySet
>& xShapePropertySet
,
461 const comphelper::SequenceAsHashMap
& aTextPropMap
)
463 // Fontwork does not evaluate paragraph alignment but uses text anchor instead
464 auto eHorzAdjust(drawing::TextHorizontalAdjust_CENTER
);
465 sal_Int16 nParaAlign
= sal_Int16(drawing::TextHorizontalAdjust_CENTER
);
466 aTextPropMap
.getValue("ParaAdjust") >>= nParaAlign
;
469 case sal_Int16(style::ParagraphAdjust_LEFT
):
470 eHorzAdjust
= drawing::TextHorizontalAdjust_LEFT
;
472 case sal_Int16(style::ParagraphAdjust_RIGHT
):
473 eHorzAdjust
= drawing::TextHorizontalAdjust_RIGHT
;
476 eHorzAdjust
= drawing::TextHorizontalAdjust_CENTER
;
478 xShapePropertySet
->setPropertyValue("TextHorizontalAdjust", uno::Any(eHorzAdjust
));
479 xShapePropertySet
->setPropertyValue("TextVerticalAdjust",
480 uno::Any(drawing::TextVerticalAdjust_TOP
));
483 void lcl_setTextPropsToShape(const uno::Reference
<beans::XPropertySet
>& xShapePropertySet
,
484 std::vector
<beans::PropertyValue
>& aTextPropVec
)
486 auto xShapePropertySetInfo
= xShapePropertySet
->getPropertySetInfo();
487 if (!xShapePropertySetInfo
.is())
489 for (size_t i
= 0; i
< aTextPropVec
.size(); ++i
)
491 if (xShapePropertySetInfo
->hasPropertyByName(aTextPropVec
[i
].Name
)
492 && !(xShapePropertySetInfo
->getPropertyByName(aTextPropVec
[i
].Name
).Attributes
493 & beans::PropertyAttribute::READONLY
)
494 && aTextPropVec
[i
].Name
!= u
"CharInteropGrabBag")
496 xShapePropertySet
->setPropertyValue(aTextPropVec
[i
].Name
, aTextPropVec
[i
].Value
);
501 void lcl_applyUsedTextPropsToAllTextRuns(uno::Reference
<text::XText
>& xText
,
502 const std::vector
<beans::PropertyValue
>& aTextPropVec
)
506 uno::Reference
<text::XTextCursor
> xTextCursor
= xText
->createTextCursor();
507 xTextCursor
->gotoStart(false);
508 xTextCursor
->gotoEnd(true);
509 uno::Reference
<container::XEnumerationAccess
> paraEnumAccess(xText
, uno::UNO_QUERY
);
510 if (!paraEnumAccess
.is())
512 uno::Reference
<container::XEnumeration
> paraEnum(paraEnumAccess
->createEnumeration());
513 while (paraEnum
->hasMoreElements())
515 uno::Reference
<text::XTextRange
> xParagraph(paraEnum
->nextElement(), uno::UNO_QUERY
);
516 uno::Reference
<container::XEnumerationAccess
> runEnumAccess(xParagraph
, uno::UNO_QUERY
);
517 if (!runEnumAccess
.is())
519 uno::Reference
<container::XEnumeration
> runEnum
= runEnumAccess
->createEnumeration();
520 while (runEnum
->hasMoreElements())
522 uno::Reference
<text::XTextRange
> xRun(runEnum
->nextElement(), uno::UNO_QUERY
);
523 if (xRun
->getString().isEmpty())
525 uno::Reference
<beans::XPropertySet
> xRunPropSet(xRun
, uno::UNO_QUERY
);
526 if (!xRunPropSet
.is())
528 auto xRunPropSetInfo
= xRunPropSet
->getPropertySetInfo();
529 if (!xRunPropSetInfo
.is())
532 for (size_t i
= 0; i
< aTextPropVec
.size(); ++i
)
534 if (xRunPropSetInfo
->hasPropertyByName(aTextPropVec
[i
].Name
)
535 && !(xRunPropSetInfo
->getPropertyByName(aTextPropVec
[i
].Name
).Attributes
536 & beans::PropertyAttribute::READONLY
))
537 xRunPropSet
->setPropertyValue(aTextPropVec
[i
].Name
, aTextPropVec
[i
].Value
);
542 } // anonymous namespace
546 WpsContext::WpsContext(ContextHandler2Helper
const& rParent
, uno::Reference
<drawing::XShape
> xShape
,
547 const drawingml::ShapePtr
& pMasterShapePtr
,
548 const drawingml::ShapePtr
& pShapePtr
)
549 : ShapeContext(rParent
, pMasterShapePtr
, pShapePtr
)
550 , mxShape(std::move(xShape
))
553 mpShapePtr
->setWps(true);
555 if (const auto pParent
= dynamic_cast<const WpgContext
*>(&rParent
))
556 m_bHasWPGParent
= pParent
->isFullWPGSupport();
558 m_bHasWPGParent
= false;
561 WpsContext::~WpsContext() = default;
563 oox::core::ContextHandlerRef
WpsContext::onCreateContext(sal_Int32 nElementToken
,
564 const oox::AttributeList
& rAttribs
)
566 switch (getBaseToken(nElementToken
))
574 // no evaluation of attribute XML_rot, because Word ignores it, as of 2022-07.
576 uno::Reference
<lang::XServiceInfo
> xServiceInfo(mxShape
, uno::UNO_QUERY
);
577 uno::Reference
<beans::XPropertySet
> xPropertySet(mxShape
, uno::UNO_QUERY
);
578 sal_Int32 nVert
= rAttribs
.getToken(XML_vert
, XML_horz
);
579 // Values 'wordArtVert' and 'wordArtVertRtl' are not implemented.
580 // Map them to other vert values.
581 if (nVert
== XML_eaVert
|| nVert
== XML_wordArtVertRtl
)
583 xPropertySet
->setPropertyValue("TextWritingMode",
584 uno::Any(text::WritingMode_TB_RL
));
585 xPropertySet
->setPropertyValue("WritingMode",
586 uno::Any(text::WritingMode2::TB_RL
));
588 else if (nVert
== XML_mongolianVert
|| nVert
== XML_wordArtVert
)
590 xPropertySet
->setPropertyValue("WritingMode",
591 uno::Any(text::WritingMode2::TB_LR
));
593 else if (nVert
!= XML_horz
) // cases XML_vert and XML_vert270
595 // Hack to get same rendering as after the fix for tdf#87924. If shape rotation
596 // plus text direction results in upright text, use horizontal text direction.
597 // Remove hack when frame is able to rotate.
599 // Need transformation matrix since RotateAngle does not contain flip.
600 drawing::HomogenMatrix3 aMatrix
;
601 xPropertySet
->getPropertyValue("Transformation") >>= aMatrix
;
602 basegfx::B2DHomMatrix aTransformation
;
603 aTransformation
.set(0, 0, aMatrix
.Line1
.Column1
);
604 aTransformation
.set(0, 1, aMatrix
.Line1
.Column2
);
605 aTransformation
.set(0, 2, aMatrix
.Line1
.Column3
);
606 aTransformation
.set(1, 0, aMatrix
.Line2
.Column1
);
607 aTransformation
.set(1, 1, aMatrix
.Line2
.Column2
);
608 aTransformation
.set(1, 2, aMatrix
.Line2
.Column3
);
609 // For this to be a valid 2D transform matrix, the last row must be [0,0,1]
610 assert(aMatrix
.Line3
.Column1
== 0);
611 assert(aMatrix
.Line3
.Column2
== 0);
612 assert(aMatrix
.Line3
.Column3
== 1);
613 basegfx::B2DTuple aScale
;
614 basegfx::B2DTuple aTranslate
;
617 aTransformation
.decompose(aScale
, aTranslate
, fRotate
, fShearX
);
618 auto nRotate(static_cast<sal_uInt16
>(NormAngle360(basegfx::rad2deg(fRotate
))));
619 if ((nVert
== XML_vert
&& nRotate
== 270)
620 || (nVert
== XML_vert270
&& nRotate
== 90))
622 xPropertySet
->setPropertyValue("WritingMode",
623 uno::Any(text::WritingMode2::LR_TB
));
624 // ToDo: Remember original vert value and remove hack on export.
626 else if (nVert
== XML_vert
)
627 xPropertySet
->setPropertyValue("WritingMode",
628 uno::Any(text::WritingMode2::TB_RL90
));
629 else // nVert == XML_vert270
630 xPropertySet
->setPropertyValue("WritingMode",
631 uno::Any(text::WritingMode2::BT_LR
));
634 if (bool bUpright
= rAttribs
.getBool(XML_upright
, false))
636 uno::Sequence
<beans::PropertyValue
> aGrabBag
;
637 xPropertySet
->getPropertyValue("InteropGrabBag") >>= aGrabBag
;
638 sal_Int32 length
= aGrabBag
.getLength();
639 aGrabBag
.realloc(length
+ 1);
640 auto pGrabBag
= aGrabBag
.getArray();
641 pGrabBag
[length
].Name
= "Upright";
642 pGrabBag
[length
].Value
<<= bUpright
;
643 xPropertySet
->setPropertyValue("InteropGrabBag", uno::Any(aGrabBag
));
646 if (xServiceInfo
.is())
648 // Handle inset attributes for Writer textframes.
649 sal_Int32 aInsets
[] = { XML_lIns
, XML_tIns
, XML_rIns
, XML_bIns
};
650 std::optional
<sal_Int32
> oInsets
[4];
651 for (std::size_t i
= 0; i
< SAL_N_ELEMENTS(aInsets
); ++i
)
653 std::optional
<OUString
> oValue
= rAttribs
.getString(aInsets
[i
]);
654 if (oValue
.has_value())
655 oInsets
[i
] = oox::drawingml::GetCoordinate(oValue
.value());
657 // Defaults from the spec: left/right: 91440 EMU, top/bottom: 45720 EMU
659 = (aInsets
[i
] == XML_lIns
|| aInsets
[i
] == XML_rIns
) ? 254 : 127;
661 const OUString aShapeProps
[]
662 = { OUString("TextLeftDistance"), OUString("TextUpperDistance"),
663 OUString("TextRightDistance"), OUString("TextLowerDistance") };
664 for (std::size_t i
= 0; i
< SAL_N_ELEMENTS(aShapeProps
); ++i
)
666 xPropertySet
->setPropertyValue(aShapeProps
[i
], uno::Any(*oInsets
[i
]));
669 // Handle text vertical adjustment inside a text frame
670 if (rAttribs
.hasAttribute(XML_anchor
))
672 drawing::TextVerticalAdjust eAdjust
673 = drawingml::GetTextVerticalAdjust(rAttribs
.getToken(XML_anchor
, XML_t
));
674 xPropertySet
->setPropertyValue("TextVerticalAdjust", uno::Any(eAdjust
));
677 // Apply character color of the shape to the shape's textbox.
678 uno::Reference
<text::XText
> xText(mxShape
, uno::UNO_QUERY
);
679 uno::Any xCharColor
= xPropertySet
->getPropertyValue("CharColor");
680 Color aColor
= COL_AUTO
;
681 if ((xCharColor
>>= aColor
) && aColor
!= COL_AUTO
)
683 // tdf#135923 Apply character color of the shape to the textrun
684 // when the character color of the textrun is default.
685 // tdf#153791 But only if the run has no background color (shd element in OOXML)
686 if (uno::Reference
<container::XEnumerationAccess
> paraEnumAccess
{
687 xText
, uno::UNO_QUERY
})
689 uno::Reference
<container::XEnumeration
> paraEnum(
690 paraEnumAccess
->createEnumeration());
692 while (paraEnum
->hasMoreElements())
694 uno::Reference
<text::XTextRange
> xParagraph(paraEnum
->nextElement(),
696 uno::Reference
<container::XEnumerationAccess
> runEnumAccess(
697 xParagraph
, uno::UNO_QUERY
);
698 if (!runEnumAccess
.is())
700 if (uno::Reference
<beans::XPropertySet
> xParaPropSet
{ xParagraph
,
702 if ((xParaPropSet
->getPropertyValue("ParaBackColor") >>= aColor
)
703 && aColor
!= COL_AUTO
)
706 uno::Reference
<container::XEnumeration
> runEnum
707 = runEnumAccess
->createEnumeration();
709 while (runEnum
->hasMoreElements())
711 uno::Reference
<text::XTextRange
> xRun(runEnum
->nextElement(),
713 const uno::Reference
<beans::XPropertyState
> xRunState(
714 xRun
, uno::UNO_QUERY
);
716 || xRunState
->getPropertyState("CharColor")
717 == beans::PropertyState_DEFAULT_VALUE
)
719 uno::Reference
<beans::XPropertySet
> xRunPropSet(xRun
,
723 if ((xRunPropSet
->getPropertyValue("CharBackColor") >>= aColor
)
724 && aColor
!= COL_AUTO
)
726 if (!(xRunPropSet
->getPropertyValue("CharColor") >>= aColor
)
727 || aColor
== COL_AUTO
)
728 xRunPropSet
->setPropertyValue("CharColor", xCharColor
);
735 auto nWrappingType
= rAttribs
.getToken(XML_wrap
, XML_square
);
736 xPropertySet
->setPropertyValue("TextWordWrap",
737 uno::Any(nWrappingType
== XML_square
));
741 else if (m_bHasWPGParent
&& mpShapePtr
)
743 // this WPS context has to be inside a WPG shape, so the <BodyPr> element
744 // cannot be applied to mxShape member, use mpShape instead, and after the
745 // the parent shape finished, apply it for its children.
746 mpShapePtr
->setWPGChild(true);
747 oox::drawingml::TextBodyPtr pTextBody
;
748 pTextBody
.reset(new oox::drawingml::TextBody());
750 if (rAttribs
.hasAttribute(XML_anchor
))
752 drawing::TextVerticalAdjust eAdjust
753 = drawingml::GetTextVerticalAdjust(rAttribs
.getToken(XML_anchor
, XML_t
));
754 pTextBody
->getTextProperties().meVA
= eAdjust
;
757 sal_Int32 aInsets
[] = { XML_lIns
, XML_tIns
, XML_rIns
, XML_bIns
};
758 for (int i
= 0; i
< 4; ++i
)
760 if (rAttribs
.hasAttribute(XML_lIns
))
762 std::optional
<OUString
> oValue
= rAttribs
.getString(aInsets
[i
]);
763 if (oValue
.has_value())
764 pTextBody
->getTextProperties().moInsets
[i
]
765 = oox::drawingml::GetCoordinate(oValue
.value());
767 // Defaults from the spec: left/right: 91440 EMU, top/bottom: 45720 EMU
768 pTextBody
->getTextProperties().moInsets
[i
]
769 = (aInsets
[i
] == XML_lIns
|| aInsets
[i
] == XML_rIns
) ? 254 : 127;
773 mpShapePtr
->setTextBody(pTextBody
);
779 uno::Reference
<lang::XServiceInfo
> xServiceInfo(mxShape
, uno::UNO_QUERY
);
780 // We can't use oox::drawingml::TextBodyPropertiesContext here, as this
781 // is a child context of bodyPr, so the shape is already sent: we need
782 // to alter the XShape directly.
783 uno::Reference
<beans::XPropertySet
> xPropertySet(mxShape
, uno::UNO_QUERY
);
784 if (xPropertySet
.is())
786 if (xServiceInfo
->supportsService("com.sun.star.text.TextFrame"))
787 xPropertySet
->setPropertyValue(
788 "FrameIsAutomaticHeight",
789 uno::Any(getBaseToken(nElementToken
) == XML_spAutoFit
));
791 xPropertySet
->setPropertyValue(
792 "TextAutoGrowHeight",
793 uno::Any(getBaseToken(nElementToken
) == XML_spAutoFit
));
798 if (rAttribs
.hasAttribute(XML_prst
))
800 uno::Reference
<beans::XPropertySet
> xPropertySet(mxShape
, uno::UNO_QUERY
);
801 if (xPropertySet
.is())
803 std::optional
<OUString
> presetShapeName
= rAttribs
.getString(XML_prst
);
804 const OUString
& preset
= presetShapeName
.value();
805 comphelper::SequenceAsHashMap
aCustomShapeGeometry(
806 xPropertySet
->getPropertyValue("CustomShapeGeometry"));
807 aCustomShapeGeometry
["PresetTextWarp"] <<= preset
;
808 xPropertySet
->setPropertyValue(
809 "CustomShapeGeometry",
810 uno::Any(aCustomShapeGeometry
.getAsConstPropertyValueList()));
813 return new oox::drawingml::PresetTextShapeContext(
814 *this, rAttribs
, *(getShape()->getCustomShapeProperties()));
817 mpShapePtr
->getCustomShapeProperties()->setShapeTypeOverride(true);
818 mpShapePtr
->setTextBox(true);
819 //in case if the textbox is linked, save the attributes
820 //for further processing.
821 if (rAttribs
.hasAttribute(XML_id
))
823 std::optional
<OUString
> id
= rAttribs
.getString(XML_id
);
826 oox::drawingml::LinkedTxbxAttr linkedTxtBoxAttr
;
827 linkedTxtBoxAttr
.id
= id
.value().toInt32();
828 mpShapePtr
->setTxbxHasLinkedTxtBox(true);
829 mpShapePtr
->setLinkedTxbxAttributes(linkedTxtBoxAttr
);
837 //in case if the textbox is linked, save the attributes
838 //for further processing.
839 mpShapePtr
->getCustomShapeProperties()->setShapeTypeOverride(true);
840 mpShapePtr
->setTextBox(true);
841 std::optional
<OUString
> id
= rAttribs
.getString(XML_id
);
842 std::optional
<OUString
> seq
= rAttribs
.getString(XML_seq
);
843 if (id
.has_value() && seq
.has_value())
845 oox::drawingml::LinkedTxbxAttr linkedTxtBoxAttr
;
846 linkedTxtBoxAttr
.id
= id
.value().toInt32();
847 linkedTxtBoxAttr
.seq
= seq
.value().toInt32();
848 mpShapePtr
->setTxbxHasLinkedTxtBox(true);
849 mpShapePtr
->setLinkedTxbxAttributes(linkedTxtBoxAttr
);
854 return ShapeContext::onCreateContext(nElementToken
, rAttribs
);
859 void WpsContext::onEndElement()
861 // Convert shape to Fontwork shape if necessary and meaningful.
862 // Only at end of bodyPr all needed info is available.
864 if (getBaseToken(getCurrentElement()) != XML_bodyPr
)
867 // Make sure all needed parts are available
869 = dynamic_cast<SdrObjCustomShape
*>(SdrObject::getSdrObjectFromXShape(mxShape
));
870 if (!pCustomShape
|| !mpShapePtr
|| !mxShape
.is())
872 uno::Reference
<beans::XPropertySet
> xShapePropertySet(mxShape
, uno::UNO_QUERY
);
873 if (!xShapePropertySet
.is())
875 // This is the text in the frame, associated with the shape
876 uno::Reference
<text::XText
> xText(mxShape
, uno::UNO_QUERY
);
880 OUString sMSPresetType
;
881 comphelper::SequenceAsHashMap
aCustomShapeGeometry(
882 xShapePropertySet
->getPropertyValue("CustomShapeGeometry"));
883 aCustomShapeGeometry
["PresetTextWarp"] >>= sMSPresetType
;
884 if (sMSPresetType
.isEmpty() || sMSPresetType
== u
"textNoShape")
887 // Word can combine its "abc Transform" with a lot of shape types. LibreOffice can only render
888 // the old kind WordArt, which is based on a rectangle. In case of non rectangular shape we keep
889 // the shape and do not convert the text to Fontwork.
891 aCustomShapeGeometry
["Type"] >>= sType
;
892 if (sType
!= u
"ooxml-rect")
895 // Copy properties from frame text to have them available after the frame is removed.
896 std::vector
<beans::PropertyValue
> aTextPropVec
;
897 if (!lcl_getTextPropsFromFrameText(xText
, aTextPropVec
))
899 comphelper::SequenceAsHashMap
aTextPropMap(comphelper::containerToSequence(aTextPropVec
));
901 // Copy text content from frame to shape. Since Fontwork uses simple text anyway, we can use
903 OUString
sFrameContent(xText
->getString());
904 pCustomShape
->NbcSetText(sFrameContent
);
906 // Setting the property "TextBox" to false includes removing the attached frame from the shape.
907 xShapePropertySet
->setPropertyValue("TextBox", uno::Any(false));
909 // Set the shape into text path mode, so that the text is drawn as Fontwork. Word renders a legacy
910 // "text on path" without the legacy stretching, therefore use false for bFromWordArt.
911 mpShapePtr
->getCustomShapeProperties()->setShapeTypeOverride(true);
912 FontworkHelpers::putCustomShapeIntoTextPathMode(mxShape
, getShape()->getCustomShapeProperties(),
913 sMSPresetType
, /*bFromWordArt*/ false);
915 // Apply the text props to the fontwork shape
916 lcl_setTextPropsToShape(xShapePropertySet
, aTextPropVec
); // includes e.g. FontName
917 lcl_setTextAnchorFromTextProps(xShapePropertySet
, aTextPropMap
);
919 // Fontwork in LO uses fill and stroke of the shape and cannot style text portions individually.
920 // "abc Transform" in Word uses fill and outline of the characters.
921 // We need to copy the properties from a run to the shape.
922 oox::drawingml::ShapePropertyMap
aStrokeShapeProps(getFilter().getModelObjectHelper());
923 oox::drawingml::LineProperties aCreatedLineProperties
924 = lcl_generateLinePropertiesFromTextProps(aTextPropMap
);
925 aCreatedLineProperties
.pushToPropMap(aStrokeShapeProps
, getFilter().getGraphicHelper());
926 lcl_applyShapePropsToShape(xShapePropertySet
, aStrokeShapeProps
);
928 oox::drawingml::ShapePropertyMap
aFillShapeProps(getFilter().getModelObjectHelper());
929 oox::drawingml::FillProperties aCreatedFillProperties
930 = lcl_generateFillPropertiesFromTextProps(aTextPropMap
);
931 aCreatedFillProperties
.pushToPropMap(aFillShapeProps
, getFilter().getGraphicHelper(),
932 /*nShapeRotation*/ 0,
933 /*nPhClr*/ API_RGB_TRANSPARENT
,
934 /*aShapeSize*/ css::awt::Size(0, 0), /*nPhClrTheme*/ -1,
935 pCustomShape
->IsMirroredX(), pCustomShape
->IsMirroredY(),
936 /*bIsCustomShape*/ true);
937 lcl_applyShapePropsToShape(xShapePropertySet
, aFillShapeProps
);
939 // Copying the text content from frame to shape as string has lost the styles. Apply the used text
940 // properties back to all runs in the text.
941 uno::Reference
<text::XText
> xNewText(pCustomShape
->getUnoShape(), uno::UNO_QUERY
);
943 lcl_applyUsedTextPropsToAllTextRuns(xNewText
, aTextPropVec
);
945 // Fontwork stretches the text to the given path. So adapt shape size to text is nonsensical.
946 xShapePropertySet
->setPropertyValue("TextAutoGrowHeight", uno::Any(false));
947 xShapePropertySet
->setPropertyValue("TextAutoGrowWidth", uno::Any(false));
951 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */