/src/libreoffice/oox/source/shape/WpsContext.cxx
Line | Count | Source |
1 | | /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ |
2 | | /* |
3 | | * This file is part of the LibreOffice project. |
4 | | * |
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 | | */ |
9 | | |
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> |
32 | | |
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> |
46 | | |
47 | | #include <optional> |
48 | | |
49 | | using namespace com::sun::star; |
50 | | |
51 | | namespace |
52 | | { |
53 | | bool lcl_getTextPropsFromFrameText(const uno::Reference<text::XText>& xText, |
54 | | std::vector<beans::PropertyValue>& rTextPropVec) |
55 | 0 | { |
56 | 0 | if (!xText.is()) |
57 | 0 | return false; |
58 | 0 | uno::Reference<text::XTextCursor> xTextCursor = xText->createTextCursor(); |
59 | 0 | xTextCursor->gotoStart(false); |
60 | 0 | xTextCursor->gotoEnd(true); |
61 | 0 | uno::Reference<container::XEnumerationAccess> paraEnumAccess(xText, uno::UNO_QUERY); |
62 | 0 | if (!paraEnumAccess.is()) |
63 | 0 | return false; |
64 | 0 | uno::Reference<container::XEnumeration> paraEnum(paraEnumAccess->createEnumeration()); |
65 | 0 | while (paraEnum->hasMoreElements()) |
66 | 0 | { |
67 | 0 | uno::Reference<text::XTextRange> xParagraph(paraEnum->nextElement(), uno::UNO_QUERY); |
68 | 0 | uno::Reference<container::XEnumerationAccess> runEnumAccess(xParagraph, uno::UNO_QUERY); |
69 | 0 | if (!runEnumAccess.is()) |
70 | 0 | continue; |
71 | 0 | uno::Reference<container::XEnumeration> runEnum = runEnumAccess->createEnumeration(); |
72 | 0 | while (runEnum->hasMoreElements()) |
73 | 0 | { |
74 | 0 | uno::Reference<text::XTextRange> xRun(runEnum->nextElement(), uno::UNO_QUERY); |
75 | 0 | if (xRun->getString().isEmpty()) |
76 | 0 | continue; |
77 | 0 | uno::Reference<beans::XPropertySet> xRunPropSet(xRun, uno::UNO_QUERY); |
78 | 0 | if (!xRunPropSet.is()) |
79 | 0 | continue; |
80 | 0 | auto xRunPropSetInfo = xRunPropSet->getPropertySetInfo(); |
81 | 0 | if (!xRunPropSetInfo.is()) |
82 | 0 | continue; |
83 | | |
84 | | // We have found a non-empty run. Collect its properties. |
85 | 0 | auto aRunPropInfoSequence = xRunPropSetInfo->getProperties(); |
86 | 0 | for (const beans::Property& aProp : aRunPropInfoSequence) |
87 | 0 | { |
88 | 0 | rTextPropVec.push_back(comphelper::makePropertyValue( |
89 | 0 | aProp.Name, xRunPropSet->getPropertyValue(aProp.Name))); |
90 | 0 | } |
91 | 0 | return true; |
92 | 0 | } |
93 | 0 | } |
94 | 0 | return false; |
95 | 0 | } |
96 | | |
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) |
103 | 0 | { |
104 | 0 | comphelper::SequenceAsHashMap aPropertyValueAsMap(aPropertyValueAsSeq); |
105 | 0 | uno::Sequence<beans::PropertyValue> aAttributesSeq; |
106 | 0 | if (!((aPropertyValueAsMap.getValue(u"attributes"_ustr) >>= aAttributesSeq) |
107 | 0 | && aAttributesSeq.hasElements())) |
108 | 0 | return false; |
109 | 0 | comphelper::SequenceAsHashMap aAttributesMap(aAttributesSeq); |
110 | 0 | OUString sRet; |
111 | 0 | if (!(aAttributesMap.getValue(rName) >>= sRet)) |
112 | 0 | return false; |
113 | 0 | rValue = sRet; |
114 | 0 | return true; |
115 | 0 | } |
116 | | |
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) |
120 | 0 | { |
121 | 0 | comphelper::SequenceAsHashMap aPropertyValueAsMap(rPropertyValueAsSeq); |
122 | 0 | uno::Sequence<beans::PropertyValue> aAttributesSeq; |
123 | 0 | if (!((aPropertyValueAsMap.getValue(u"attributes"_ustr) >>= aAttributesSeq) |
124 | 0 | && aAttributesSeq.hasElements())) |
125 | 0 | return false; |
126 | 0 | comphelper::SequenceAsHashMap aAttributesMap(aAttributesSeq); |
127 | 0 | sal_Int32 nRet; |
128 | 0 | if (!(aAttributesMap.getValue(rName) >>= nRet)) |
129 | 0 | return false; |
130 | 0 | rValue = nRet; |
131 | 0 | return true; |
132 | 0 | } |
133 | | |
134 | | void lcl_getColorTransformationsFromPropSeq(const uno::Sequence<beans::PropertyValue>& rPropSeq, |
135 | | oox::drawingml::Color& rColor) |
136 | 0 | { |
137 | 0 | auto isValidPropName = [](const OUString& rName) -> bool { |
138 | 0 | return rName == u"tint" || rName == u"shade" || rName == u"alpha" || rName == u"hueMod" |
139 | 0 | || rName == u"sat" || rName == u"satOff" || rName == u"satMod" || rName == u"lum" |
140 | 0 | || rName == u"lumOff" || rName == u"lumMod"; |
141 | 0 | }; |
142 | 0 | for (auto it = rPropSeq.begin(); it < rPropSeq.end(); ++it) |
143 | 0 | { |
144 | 0 | if (isValidPropName((*it).Name)) |
145 | 0 | { |
146 | 0 | uno::Sequence<beans::PropertyValue> aValueSeq; |
147 | 0 | sal_Int32 nNumber(0); // dummy value to make compiler happy, "val" should exist |
148 | 0 | if (((*it).Value >>= aValueSeq) |
149 | 0 | && lcl_getAttributeAsNumber(aValueSeq, u"val"_ustr, nNumber)) |
150 | 0 | { |
151 | | // char w14:alpha contains transparency, whereas shape fill a:alpha contains opacity. |
152 | 0 | if ((*it).Name == u"alpha") |
153 | 0 | rColor.addTransformation( |
154 | 0 | oox::NMSP_dml | oox::AttributeConversion::decodeToken((*it).Name), |
155 | 0 | oox::drawingml::MAX_PERCENT - nNumber); |
156 | 0 | else |
157 | 0 | rColor.addTransformation( |
158 | 0 | oox::NMSP_w14 | oox::AttributeConversion::decodeToken((*it).Name), nNumber); |
159 | 0 | } |
160 | 0 | } |
161 | 0 | } |
162 | 0 | } |
163 | | |
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) |
167 | 0 | { |
168 | 0 | bool bColorFound = false; |
169 | 0 | comphelper::SequenceAsHashMap aPropMap(rPropSeq); |
170 | 0 | uno::Sequence<beans::PropertyValue> aColorDetailSeq; |
171 | 0 | if (aPropMap.getValue(u"schemeClr"_ustr) >>= aColorDetailSeq) |
172 | 0 | { |
173 | 0 | OUString sColorString; |
174 | 0 | bColorFound = lcl_getAttributeAsString(aColorDetailSeq, u"val"_ustr, sColorString); |
175 | 0 | if (bColorFound) |
176 | 0 | { |
177 | 0 | sal_Int32 nColorToken = oox::AttributeConversion::decodeToken(sColorString); |
178 | 0 | rColor.setSchemeClr(nColorToken); |
179 | 0 | rColor.setSchemeName(sColorString); |
180 | 0 | } |
181 | 0 | } |
182 | 0 | if (!bColorFound && (aPropMap.getValue(u"srgbClr"_ustr) >>= aColorDetailSeq)) |
183 | 0 | { |
184 | 0 | OUString sColorString; |
185 | 0 | bColorFound = lcl_getAttributeAsString(aColorDetailSeq, u"val"_ustr, sColorString); |
186 | 0 | if (bColorFound) |
187 | 0 | { |
188 | 0 | sal_Int32 nColor = oox::AttributeConversion::decodeIntegerHex(sColorString); |
189 | 0 | rColor.setSrgbClr(nColor); |
190 | 0 | } |
191 | 0 | } |
192 | | // Without color, color transformations are pointless. |
193 | 0 | if (bColorFound) |
194 | 0 | lcl_getColorTransformationsFromPropSeq(aColorDetailSeq, rColor); |
195 | 0 | return bColorFound; |
196 | 0 | } |
197 | | |
198 | | void lcl_getFillDetailsFromPropSeq(const uno::Sequence<beans::PropertyValue>& rTextFillSeq, |
199 | | oox::drawingml::FillProperties& rFillProperties) |
200 | 0 | { |
201 | | // rTextFillSeq should have an item containing either "noFill" or "solidFill" or "gradFill" |
202 | | // property. |
203 | 0 | if (!rTextFillSeq.hasElements()) |
204 | 0 | return; |
205 | 0 | comphelper::SequenceAsHashMap aTextFillMap(rTextFillSeq); |
206 | 0 | if (aTextFillMap.contains(u"noFill"_ustr)) |
207 | 0 | { |
208 | 0 | rFillProperties.moFillType = oox::XML_noFill; |
209 | 0 | return; |
210 | 0 | } |
211 | | |
212 | 0 | uno::Sequence<beans::PropertyValue> aPropSeq; |
213 | 0 | if ((aTextFillMap.getValue(u"solidFill"_ustr) >>= aPropSeq) && aPropSeq.hasElements()) |
214 | 0 | { |
215 | 0 | rFillProperties.moFillType = oox::XML_solidFill; |
216 | 0 | lcl_getColorFromPropSeq(aPropSeq, rFillProperties.maFillColor); |
217 | 0 | return; |
218 | 0 | } |
219 | | |
220 | 0 | if ((aTextFillMap.getValue(u"gradFill"_ustr) >>= aPropSeq) && aPropSeq.hasElements()) |
221 | 0 | { |
222 | 0 | 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 | 0 | comphelper::SequenceAsHashMap aPropMap(aPropSeq); |
227 | 0 | uno::Sequence<beans::PropertyValue> aGsLstSeq; |
228 | 0 | if (aPropMap.getValue(u"gsLst"_ustr) >>= aGsLstSeq) |
229 | 0 | { |
230 | 0 | for (auto it = aGsLstSeq.begin(); it < aGsLstSeq.end(); ++it) |
231 | 0 | { |
232 | | // (*it) is a bean::PropertyValue with Name="gs". Its Value is a property sequence. |
233 | 0 | uno::Sequence<beans::PropertyValue> aColorStopSeq; |
234 | 0 | if ((*it).Value >>= aColorStopSeq) |
235 | 0 | { |
236 | | // aColorStopSeq should have an item for the color and an item for the position |
237 | 0 | sal_Int32 nPos; |
238 | 0 | oox::drawingml::Color aColor; |
239 | 0 | if (lcl_getAttributeAsNumber(aColorStopSeq, u"pos"_ustr, nPos) |
240 | 0 | && lcl_getColorFromPropSeq(aColorStopSeq, aColor)) |
241 | 0 | { |
242 | | // The position in maGradientStops is relative, thus in range [0.0;1.0]. |
243 | 0 | double fPos = nPos / 100000.0; |
244 | 0 | rFillProperties.maGradientProps.maGradientStops.insert({ fPos, aColor }); |
245 | 0 | } |
246 | 0 | } |
247 | 0 | } |
248 | 0 | } |
249 | | // Now determine kind of gradient. |
250 | 0 | uno::Sequence<beans::PropertyValue> aKindSeq; |
251 | 0 | if (aPropMap.getValue(u"lin"_ustr) >>= aKindSeq) |
252 | 0 | { |
253 | | // aKindSeq contains the attributes "ang" and "scaled" |
254 | 0 | sal_Int32 nAngle; // in 1/60000 deg |
255 | 0 | if (lcl_getAttributeAsNumber(aKindSeq, u"ang"_ustr, nAngle)) |
256 | 0 | rFillProperties.maGradientProps.moShadeAngle = nAngle; |
257 | 0 | OUString sScaledString; |
258 | 0 | if (lcl_getAttributeAsString(aKindSeq, u"scaled"_ustr, sScaledString)) |
259 | 0 | rFillProperties.maGradientProps.moShadeScaled |
260 | 0 | = sScaledString == u"1" || sScaledString == u"true"; |
261 | 0 | return; |
262 | 0 | } |
263 | 0 | if (aPropMap.getValue(u"path"_ustr) >>= aKindSeq) |
264 | 0 | { |
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 | 0 | OUString sKind; |
269 | 0 | if (lcl_getAttributeAsString(aKindSeq, u"path"_ustr, sKind)) |
270 | 0 | rFillProperties.maGradientProps.moGradientPath |
271 | 0 | = oox::AttributeConversion::decodeToken(sKind); |
272 | 0 | comphelper::SequenceAsHashMap aKindMap(aKindSeq); |
273 | 0 | uno::Sequence<beans::PropertyValue> aFillToRectSeq; |
274 | 0 | if (aKindMap.getValue(u"fillToRect"_ustr) >>= aFillToRectSeq) |
275 | 0 | { |
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 | 0 | geometry::IntegerRectangle2D aRect; |
280 | 0 | if (!lcl_getAttributeAsNumber(aFillToRectSeq, u"l"_ustr, aRect.X1)) |
281 | 0 | aRect.X1 = 0; |
282 | 0 | if (!lcl_getAttributeAsNumber(aFillToRectSeq, u"t"_ustr, aRect.Y1)) |
283 | 0 | aRect.Y1 = 0; |
284 | 0 | if (!lcl_getAttributeAsNumber(aFillToRectSeq, u"r"_ustr, aRect.X2)) |
285 | 0 | aRect.X2 = 0; |
286 | 0 | if (!lcl_getAttributeAsNumber(aFillToRectSeq, u"b"_ustr, aRect.Y2)) |
287 | 0 | aRect.Y2 = 0; |
288 | 0 | rFillProperties.maGradientProps.moFillToRect = aRect; |
289 | 0 | } |
290 | 0 | } |
291 | 0 | return; |
292 | 0 | } |
293 | 0 | } |
294 | | |
295 | | void lcl_getLineDetailsFromPropSeq(const uno::Sequence<beans::PropertyValue>& rTextOutlineSeq, |
296 | | oox::drawingml::LineProperties& rLineProperties) |
297 | 0 | { |
298 | 0 | if (!rTextOutlineSeq.hasElements()) |
299 | 0 | { |
300 | 0 | rLineProperties.maLineFill.moFillType = oox::XML_noFill; // MS Office default |
301 | 0 | return; |
302 | 0 | } |
303 | | // aTextOulineSeq contains e.g. "attributes" {w, cap, cmpd, ctr}, either |
304 | | // "solidFill" or "gradFill or "noFill", and "prstDash" and "lineJoint" properties. |
305 | | |
306 | | // Fill |
307 | 0 | lcl_getFillDetailsFromPropSeq(rTextOutlineSeq, rLineProperties.maLineFill); |
308 | | |
309 | | // LineJoint |
310 | 0 | comphelper::SequenceAsHashMap aTextOutlineMap(rTextOutlineSeq); |
311 | 0 | if (aTextOutlineMap.contains(u"bevel"_ustr)) |
312 | 0 | rLineProperties.moLineJoint = oox::XML_bevel; |
313 | 0 | else if (aTextOutlineMap.contains(u"round"_ustr)) |
314 | 0 | rLineProperties.moLineJoint = oox::XML_round; |
315 | 0 | else if (aTextOutlineMap.contains(u"miter"_ustr)) |
316 | 0 | { |
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 | 0 | sal_Int32 nMiterLimit |
320 | 0 | = aTextOutlineMap.getUnpackedValueOrDefault(u"lim"_ustr, sal_Int32(0)); |
321 | 0 | if (nMiterLimit == 0) |
322 | 0 | rLineProperties.moLineJoint = oox::XML_bevel; |
323 | 0 | else |
324 | 0 | rLineProperties.moLineJoint = oox::XML_miter; |
325 | 0 | } |
326 | | |
327 | | // Dash |
328 | 0 | uno::Sequence<beans::PropertyValue> aDashSeq; |
329 | 0 | if (aTextOutlineMap.getValue(u"prstDash"_ustr) >>= aDashSeq) |
330 | 0 | { |
331 | | // aDashSeq contains the attribute "val" with the kind of dash, e.g. "sysDot" |
332 | 0 | OUString sDashKind; |
333 | 0 | if (lcl_getAttributeAsString(aDashSeq, u"val"_ustr, sDashKind)) |
334 | 0 | rLineProperties.moPresetDash = oox::AttributeConversion::decodeToken(sDashKind); |
335 | 0 | } |
336 | 0 | OUString sCapKind; |
337 | 0 | if (lcl_getAttributeAsString(rTextOutlineSeq, u"cap"_ustr, sCapKind)) |
338 | 0 | rLineProperties.moLineCap = oox::AttributeConversion::decodeToken(sCapKind); |
339 | | |
340 | | // Width |
341 | 0 | sal_Int32 nWidth; // EMU |
342 | 0 | if (lcl_getAttributeAsNumber(rTextOutlineSeq, u"w"_ustr, nWidth)) |
343 | 0 | rLineProperties.moLineWidth = nWidth; |
344 | | |
345 | | // Compound. LineProperties has a member for it, however Fontwork can currently only render "sng". |
346 | 0 | OUString sCompoundKind; |
347 | 0 | if (lcl_getAttributeAsString(rTextOutlineSeq, u"cmpd"_ustr, sCompoundKind)) |
348 | 0 | rLineProperties.moLineCompound = oox::AttributeConversion::decodeToken(sCompoundKind); |
349 | | |
350 | | // Align. LineProperties has no member for attribute "algn". |
351 | |
|
352 | 0 | return; |
353 | 0 | } |
354 | | |
355 | | oox::drawingml::LineProperties |
356 | | lcl_generateLinePropertiesFromTextProps(const comphelper::SequenceAsHashMap& aTextPropMap) |
357 | 0 | { |
358 | 0 | oox::drawingml::LineProperties aLineProperties; |
359 | 0 | aLineProperties.maLineFill.moFillType = oox::XML_noFill; // default |
360 | | |
361 | | // Get property "textOutline" from aTextPropMap |
362 | 0 | uno::Sequence<beans::PropertyValue> aCharInteropGrabBagSeq; |
363 | 0 | if (!(aTextPropMap.getValue(u"CharInteropGrabBag"_ustr) >>= aCharInteropGrabBagSeq)) |
364 | 0 | return aLineProperties; |
365 | 0 | if (!aCharInteropGrabBagSeq.hasElements()) |
366 | 0 | return aLineProperties; |
367 | 0 | comphelper::SequenceAsHashMap aCharInteropGrabBagMap(aCharInteropGrabBagSeq); |
368 | 0 | beans::PropertyValue aProp; |
369 | 0 | if (!(aCharInteropGrabBagMap.getValue(u"CharTextOutlineTextEffect"_ustr) >>= aProp)) |
370 | 0 | return aLineProperties; |
371 | 0 | uno::Sequence<beans::PropertyValue> aTextOutlineSeq; |
372 | 0 | if (!(aProp.Name == "textOutline" && (aProp.Value >>= aTextOutlineSeq) |
373 | 0 | && aTextOutlineSeq.hasElements())) |
374 | 0 | return aLineProperties; |
375 | | |
376 | | // Copy line properties from aTextOutlineSeq to aLineProperties |
377 | 0 | lcl_getLineDetailsFromPropSeq(aTextOutlineSeq, aLineProperties); |
378 | 0 | return aLineProperties; |
379 | 0 | } |
380 | | |
381 | | oox::drawingml::FillProperties |
382 | | lcl_generateFillPropertiesFromTextProps(const comphelper::SequenceAsHashMap& rTextPropMap) |
383 | 0 | { |
384 | 0 | oox::drawingml::FillProperties aFillProperties; |
385 | 0 | aFillProperties.moFillType = oox::XML_solidFill; // default |
386 | | // Theme color supersedes direct color. textFill supersedes theme color. Theme color and textFill |
387 | | // are in CharInteropGrabBag. |
388 | 0 | uno::Sequence<beans::PropertyValue> aCharInteropGrabBagSeq; |
389 | 0 | if ((rTextPropMap.getValue(u"CharInteropGrabBag"_ustr) >>= aCharInteropGrabBagSeq) |
390 | 0 | && aCharInteropGrabBagSeq.hasElements()) |
391 | 0 | { |
392 | | // Handle case textFill |
393 | 0 | comphelper::SequenceAsHashMap aCharInteropGrabBagMap(aCharInteropGrabBagSeq); |
394 | 0 | beans::PropertyValue aProp; |
395 | 0 | if (aCharInteropGrabBagMap.getValue(u"CharTextFillTextEffect"_ustr) >>= aProp) |
396 | 0 | { |
397 | 0 | uno::Sequence<beans::PropertyValue> aTextFillSeq; |
398 | 0 | if (aProp.Name == "textFill" && (aProp.Value >>= aTextFillSeq) |
399 | 0 | && aTextFillSeq.hasElements()) |
400 | 0 | { |
401 | | // Copy fill properties from aTextFillSeq to aFillProperties |
402 | 0 | lcl_getFillDetailsFromPropSeq(aTextFillSeq, aFillProperties); |
403 | 0 | return aFillProperties; |
404 | 0 | } |
405 | 0 | } |
406 | | |
407 | | // no textFill, look for theme color, tint and shade |
408 | 0 | bool bColorFound(false); |
409 | 0 | OUString sColorString; |
410 | 0 | if (aCharInteropGrabBagMap.getValue(u"CharThemeOriginalColor"_ustr) >>= sColorString) |
411 | 0 | { |
412 | 0 | sal_Int32 nThemeOrigColor = oox::AttributeConversion::decodeIntegerHex(sColorString); |
413 | 0 | aFillProperties.maFillColor.setSrgbClr(nThemeOrigColor); |
414 | 0 | bColorFound = true; |
415 | 0 | } |
416 | 0 | if (aCharInteropGrabBagMap.getValue(u"CharThemeColor"_ustr) >>= sColorString) |
417 | 0 | { |
418 | 0 | sal_Int32 nColorToken = oox::AttributeConversion::decodeToken(sColorString); |
419 | 0 | aFillProperties.maFillColor.setSchemeClr(nColorToken); |
420 | 0 | aFillProperties.maFillColor.setSchemeName(sColorString); |
421 | 0 | bColorFound = true; |
422 | | // A character color has shade or tint, a shape color has lumMod and lumOff. |
423 | 0 | OUString sTransformString; |
424 | 0 | if (aCharInteropGrabBagMap.getValue(u"CharThemeColorTint"_ustr) >>= sTransformString) |
425 | 0 | { |
426 | 0 | double fTint = oox::AttributeConversion::decodeIntegerHex(sTransformString); |
427 | 0 | fTint = fTint / 255.0 * oox::drawingml::MAX_PERCENT; |
428 | 0 | aFillProperties.maFillColor.addTransformation(OOX_TOKEN(w14, lumMod), |
429 | 0 | static_cast<sal_Int32>(fTint + 0.5)); |
430 | 0 | double fOff = oox::drawingml::MAX_PERCENT - fTint; |
431 | 0 | aFillProperties.maFillColor.addTransformation(OOX_TOKEN(w14, lumOff), |
432 | 0 | static_cast<sal_Int32>(fOff + 0.5)); |
433 | 0 | } |
434 | 0 | else if (aCharInteropGrabBagMap.getValue(u"CharThemeColorShade"_ustr) |
435 | 0 | >>= sTransformString) |
436 | 0 | { |
437 | 0 | double fShade = oox::AttributeConversion::decodeIntegerHex(sTransformString); |
438 | 0 | fShade = fShade / 255.0 * oox::drawingml::MAX_PERCENT; |
439 | 0 | aFillProperties.maFillColor.addTransformation(OOX_TOKEN(w14, lumMod), |
440 | 0 | static_cast<sal_Int32>(fShade + 0.5)); |
441 | 0 | } |
442 | 0 | } |
443 | 0 | if (bColorFound) |
444 | 0 | return aFillProperties; |
445 | 0 | } |
446 | | |
447 | | // Neither textFill nor theme color. Look for direct color. |
448 | 0 | sal_Int32 aCharColor = 0; |
449 | 0 | if (rTextPropMap.getValue(u"CharColor"_ustr) >>= aCharColor) |
450 | 0 | aFillProperties.maFillColor.setSrgbClr(aCharColor); |
451 | 0 | else |
452 | 0 | aFillProperties.maFillColor.setUnused(); |
453 | 0 | return aFillProperties; |
454 | 0 | } |
455 | | |
456 | | void lcl_applyShapePropsToShape(const uno::Reference<beans::XPropertySet>& xShapePropertySet, |
457 | | const oox::drawingml::ShapePropertyMap& rShapeProps) |
458 | 0 | { |
459 | 0 | for (const auto& rProp : rShapeProps.makePropertyValueSequence()) |
460 | 0 | { |
461 | 0 | xShapePropertySet->setPropertyValue(rProp.Name, rProp.Value); |
462 | 0 | } |
463 | 0 | } |
464 | | |
465 | | void lcl_setTextAnchorFromTextProps(const uno::Reference<beans::XPropertySet>& xShapePropertySet, |
466 | | const comphelper::SequenceAsHashMap& aTextPropMap) |
467 | 0 | { |
468 | | // Fontwork does not evaluate paragraph alignment but uses text anchor instead |
469 | 0 | auto eHorzAdjust(drawing::TextHorizontalAdjust_CENTER); |
470 | 0 | sal_Int16 nParaAlign = sal_Int16(drawing::TextHorizontalAdjust_CENTER); |
471 | 0 | aTextPropMap.getValue(u"ParaAdjust"_ustr) >>= nParaAlign; |
472 | 0 | switch (nParaAlign) |
473 | 0 | { |
474 | 0 | case sal_Int16(style::ParagraphAdjust_LEFT): |
475 | 0 | eHorzAdjust = drawing::TextHorizontalAdjust_LEFT; |
476 | 0 | break; |
477 | 0 | case sal_Int16(style::ParagraphAdjust_RIGHT): |
478 | 0 | eHorzAdjust = drawing::TextHorizontalAdjust_RIGHT; |
479 | 0 | break; |
480 | 0 | default: |
481 | 0 | eHorzAdjust = drawing::TextHorizontalAdjust_CENTER; |
482 | 0 | } |
483 | 0 | xShapePropertySet->setPropertyValue(u"TextHorizontalAdjust"_ustr, uno::Any(eHorzAdjust)); |
484 | 0 | xShapePropertySet->setPropertyValue(u"TextVerticalAdjust"_ustr, |
485 | 0 | uno::Any(drawing::TextVerticalAdjust_TOP)); |
486 | 0 | } |
487 | | |
488 | | void lcl_setTextPropsToShape(const uno::Reference<beans::XPropertySet>& xShapePropertySet, |
489 | | std::vector<beans::PropertyValue>& aTextPropVec) |
490 | 0 | { |
491 | 0 | auto xShapePropertySetInfo = xShapePropertySet->getPropertySetInfo(); |
492 | 0 | if (!xShapePropertySetInfo.is()) |
493 | 0 | return; |
494 | 0 | for (size_t i = 0; i < aTextPropVec.size(); ++i) |
495 | 0 | { |
496 | 0 | if (xShapePropertySetInfo->hasPropertyByName(aTextPropVec[i].Name) |
497 | 0 | && !(xShapePropertySetInfo->getPropertyByName(aTextPropVec[i].Name).Attributes |
498 | 0 | & beans::PropertyAttribute::READONLY) |
499 | 0 | && aTextPropVec[i].Name != u"CharInteropGrabBag") |
500 | 0 | { |
501 | 0 | xShapePropertySet->setPropertyValue(aTextPropVec[i].Name, aTextPropVec[i].Value); |
502 | 0 | } |
503 | 0 | } |
504 | 0 | } |
505 | | |
506 | | void lcl_applyUsedTextPropsToAllTextRuns(const uno::Reference<text::XText>& xText, |
507 | | const std::vector<beans::PropertyValue>& aTextPropVec) |
508 | 0 | { |
509 | 0 | if (!xText.is()) |
510 | 0 | return; |
511 | 0 | uno::Reference<text::XTextCursor> xTextCursor = xText->createTextCursor(); |
512 | 0 | xTextCursor->gotoStart(false); |
513 | 0 | xTextCursor->gotoEnd(true); |
514 | 0 | uno::Reference<container::XEnumerationAccess> paraEnumAccess(xText, uno::UNO_QUERY); |
515 | 0 | if (!paraEnumAccess.is()) |
516 | 0 | return; |
517 | 0 | uno::Reference<container::XEnumeration> paraEnum(paraEnumAccess->createEnumeration()); |
518 | 0 | while (paraEnum->hasMoreElements()) |
519 | 0 | { |
520 | 0 | uno::Reference<text::XTextRange> xParagraph(paraEnum->nextElement(), uno::UNO_QUERY); |
521 | 0 | uno::Reference<container::XEnumerationAccess> runEnumAccess(xParagraph, uno::UNO_QUERY); |
522 | 0 | if (!runEnumAccess.is()) |
523 | 0 | continue; |
524 | 0 | uno::Reference<container::XEnumeration> runEnum = runEnumAccess->createEnumeration(); |
525 | 0 | while (runEnum->hasMoreElements()) |
526 | 0 | { |
527 | 0 | uno::Reference<text::XTextRange> xRun(runEnum->nextElement(), uno::UNO_QUERY); |
528 | 0 | if (xRun->getString().isEmpty()) |
529 | 0 | continue; |
530 | 0 | uno::Reference<beans::XPropertySet> xRunPropSet(xRun, uno::UNO_QUERY); |
531 | 0 | if (!xRunPropSet.is()) |
532 | 0 | continue; |
533 | 0 | auto xRunPropSetInfo = xRunPropSet->getPropertySetInfo(); |
534 | 0 | if (!xRunPropSetInfo.is()) |
535 | 0 | continue; |
536 | | |
537 | 0 | for (size_t i = 0; i < aTextPropVec.size(); ++i) |
538 | 0 | { |
539 | 0 | if (xRunPropSetInfo->hasPropertyByName(aTextPropVec[i].Name) |
540 | 0 | && !(xRunPropSetInfo->getPropertyByName(aTextPropVec[i].Name).Attributes |
541 | 0 | & beans::PropertyAttribute::READONLY)) |
542 | 0 | xRunPropSet->setPropertyValue(aTextPropVec[i].Name, aTextPropVec[i].Value); |
543 | 0 | } |
544 | 0 | } |
545 | 0 | } |
546 | 0 | } |
547 | | } // anonymous namespace |
548 | | |
549 | | namespace oox::shape |
550 | | { |
551 | | WpsContext::WpsContext(ContextHandler2Helper const& rParent, uno::Reference<drawing::XShape> xShape, |
552 | | const drawingml::ShapePtr& pMasterShapePtr, |
553 | | const drawingml::ShapePtr& pShapePtr) |
554 | 1.50k | : ShapeContext(rParent, pMasterShapePtr, pShapePtr) |
555 | 1.50k | , mxShape(std::move(xShape)) |
556 | 1.50k | { |
557 | 1.50k | if (mpShapePtr) |
558 | 1.50k | mpShapePtr->setWps(true); |
559 | | |
560 | 1.50k | if (const auto pParent = dynamic_cast<const WpgContext*>(&rParent)) |
561 | 274 | m_bHasWPGParent = pParent->isFullWPGSupport(); |
562 | 1.23k | else if (dynamic_cast<const WordprocessingCanvasContext*>(&rParent)) |
563 | 0 | m_bHasWPGParent = true; |
564 | 1.23k | else |
565 | 1.23k | m_bHasWPGParent = false; |
566 | | |
567 | 1.50k | if ((pMasterShapePtr && pMasterShapePtr->isInWordprocessingCanvas()) |
568 | 1.50k | || dynamic_cast<const WordprocessingCanvasContext*>(&rParent) != nullptr) |
569 | 0 | pShapePtr->setWordprocessingCanvas(true); |
570 | 1.50k | } |
571 | | |
572 | 1.50k | WpsContext::~WpsContext() = default; |
573 | | |
574 | | oox::core::ContextHandlerRef WpsContext::onCreateContext(sal_Int32 nElementToken, |
575 | | const oox::AttributeList& rAttribs) |
576 | 5.39k | { |
577 | 5.39k | switch (getBaseToken(nElementToken)) |
578 | 5.39k | { |
579 | 719 | case XML_wsp: |
580 | 719 | break; |
581 | 0 | case XML_cNvCnPr: |
582 | 0 | { |
583 | | // It might be a connector shape in a wordprocessing canvas |
584 | | // Replace the custom shape with a connector shape. |
585 | 0 | if (!mpShapePtr || !mpShapePtr->isInWordprocessingCanvas() || !mpMasterShapePtr) |
586 | 0 | break; |
587 | | // Generate new shape |
588 | 0 | oox::drawingml::ShapePtr pShape = std::make_shared<oox::drawingml::Shape>( |
589 | 0 | u"com.sun.star.drawing.ConnectorShape"_ustr, false); |
590 | 0 | pShape->setConnectorShape(true); |
591 | 0 | pShape->setWps(true); |
592 | 0 | pShape->setWordprocessingCanvas(true); |
593 | | // ToDo: Can only copy infos from mpShapePtr to pShape for which getter available. |
594 | 0 | pShape->setName(mpShapePtr->getName()); |
595 | 0 | pShape->setId(mpShapePtr->getId()); |
596 | 0 | pShape->setWPGChild(mpShapePtr->isWPGChild()); |
597 | | // And actually replace the shape. |
598 | 0 | mpShapePtr = pShape; |
599 | 0 | mpMasterShapePtr->getChildren().pop_back(); |
600 | 0 | mpMasterShapePtr->getChildren().push_back(std::move(pShape)); |
601 | 0 | return new oox::drawingml::ConnectorShapePropertiesContext( |
602 | 0 | *this, mpShapePtr, mpShapePtr->getConnectorShapeProperties()); |
603 | 0 | } |
604 | 993 | case XML_bodyPr: |
605 | 993 | if (mxShape.is()) |
606 | 513 | { |
607 | | // no evaluation of attribute XML_rot, because Word ignores it, as of 2022-07. |
608 | | |
609 | 513 | uno::Reference<lang::XServiceInfo> xServiceInfo(mxShape, uno::UNO_QUERY); |
610 | 513 | uno::Reference<beans::XPropertySet> xPropertySet(mxShape, uno::UNO_QUERY); |
611 | 513 | sal_Int32 nVert = rAttribs.getToken(XML_vert, XML_horz); |
612 | 513 | if (nVert == XML_eaVert) |
613 | 0 | { |
614 | 0 | xPropertySet->setPropertyValue(u"TextWritingMode"_ustr, |
615 | 0 | uno::Any(text::WritingMode_TB_RL)); |
616 | 0 | xPropertySet->setPropertyValue(u"WritingMode"_ustr, |
617 | 0 | uno::Any(text::WritingMode2::TB_RL)); |
618 | 0 | } |
619 | 513 | else if (nVert == XML_mongolianVert) |
620 | 0 | { |
621 | 0 | xPropertySet->setPropertyValue(u"WritingMode"_ustr, |
622 | 0 | uno::Any(text::WritingMode2::TB_LR)); |
623 | 0 | } |
624 | 513 | else if (nVert == XML_wordArtVert || nVert == XML_wordArtVertRtl) |
625 | 0 | { |
626 | | // Multiline wordArtVert is not implemented yet. |
627 | | // It will render all the text in 1 line. |
628 | | // Map 'wordArtVertRtl' to 'wordArtVert', as they are the same now. |
629 | 0 | xPropertySet->setPropertyValue(u"WritingMode"_ustr, |
630 | 0 | uno::Any(text::WritingMode2::STACKED)); |
631 | 0 | } |
632 | 513 | else if (nVert != XML_horz) // cases XML_vert and XML_vert270 |
633 | 2 | { |
634 | | // Hack to get same rendering as after the fix for tdf#87924. If shape rotation |
635 | | // plus text direction results in upright text, use horizontal text direction. |
636 | | // Remove hack when frame is able to rotate. |
637 | | |
638 | | // Need transformation matrix since RotateAngle does not contain flip. |
639 | 2 | drawing::HomogenMatrix3 aMatrix; |
640 | 2 | xPropertySet->getPropertyValue(u"Transformation"_ustr) >>= aMatrix; |
641 | 2 | basegfx::B2DHomMatrix aTransformation; |
642 | 2 | aTransformation.set(0, 0, aMatrix.Line1.Column1); |
643 | 2 | aTransformation.set(0, 1, aMatrix.Line1.Column2); |
644 | 2 | aTransformation.set(0, 2, aMatrix.Line1.Column3); |
645 | 2 | aTransformation.set(1, 0, aMatrix.Line2.Column1); |
646 | 2 | aTransformation.set(1, 1, aMatrix.Line2.Column2); |
647 | 2 | aTransformation.set(1, 2, aMatrix.Line2.Column3); |
648 | | // For this to be a valid 2D transform matrix, the last row must be [0,0,1] |
649 | 2 | assert(aMatrix.Line3.Column1 == 0); |
650 | 2 | assert(aMatrix.Line3.Column2 == 0); |
651 | 2 | assert(aMatrix.Line3.Column3 == 1); |
652 | 2 | basegfx::B2DTuple aScale; |
653 | 2 | basegfx::B2DTuple aTranslate; |
654 | 2 | double fRotate = 0; |
655 | 2 | double fShearX = 0; |
656 | 2 | aTransformation.decompose(aScale, aTranslate, fRotate, fShearX); |
657 | 2 | auto nRotate(static_cast<sal_uInt16>(NormAngle360(basegfx::rad2deg(fRotate)))); |
658 | 2 | if ((nVert == XML_vert && nRotate == 270) |
659 | 2 | || (nVert == XML_vert270 && nRotate == 90)) |
660 | 0 | { |
661 | 0 | xPropertySet->setPropertyValue(u"WritingMode"_ustr, |
662 | 0 | uno::Any(text::WritingMode2::LR_TB)); |
663 | | // ToDo: Remember original vert value and remove hack on export. |
664 | 0 | } |
665 | 2 | else if (nVert == XML_vert) |
666 | 0 | xPropertySet->setPropertyValue(u"WritingMode"_ustr, |
667 | 0 | uno::Any(text::WritingMode2::TB_RL90)); |
668 | 2 | else // nVert == XML_vert270 |
669 | 2 | xPropertySet->setPropertyValue(u"WritingMode"_ustr, |
670 | 2 | uno::Any(text::WritingMode2::BT_LR)); |
671 | 2 | } |
672 | | |
673 | 513 | if (bool bUpright = rAttribs.getBool(XML_upright, false)) |
674 | 6 | { |
675 | 6 | uno::Sequence<beans::PropertyValue> aGrabBag; |
676 | 6 | xPropertySet->getPropertyValue(u"InteropGrabBag"_ustr) >>= aGrabBag; |
677 | 6 | sal_Int32 length = aGrabBag.getLength(); |
678 | 6 | aGrabBag.realloc(length + 1); |
679 | 6 | auto pGrabBag = aGrabBag.getArray(); |
680 | 6 | pGrabBag[length].Name = "Upright"; |
681 | 6 | pGrabBag[length].Value <<= bUpright; |
682 | 6 | xPropertySet->setPropertyValue(u"InteropGrabBag"_ustr, uno::Any(aGrabBag)); |
683 | 6 | } |
684 | | |
685 | 513 | auto xPropertySetInfo = xPropertySet->getPropertySetInfo(); |
686 | | |
687 | 513 | if (xServiceInfo.is()) |
688 | 513 | { |
689 | | // Handle inset attributes for Writer textframes. |
690 | 513 | sal_Int32 aInsets[] = { XML_lIns, XML_tIns, XML_rIns, XML_bIns }; |
691 | 513 | std::optional<sal_Int32> oInsets[4]; |
692 | 2.56k | for (std::size_t i = 0; i < SAL_N_ELEMENTS(aInsets); ++i) |
693 | 2.05k | { |
694 | 2.05k | std::optional<OUString> oValue = rAttribs.getString(aInsets[i]); |
695 | 2.05k | if (oValue.has_value()) |
696 | 2.05k | oInsets[i] = oox::drawingml::GetCoordinate(oValue.value()); |
697 | 0 | else |
698 | | // Defaults from the spec: left/right: 91440 EMU, top/bottom: 45720 EMU |
699 | 0 | oInsets[i] |
700 | 0 | = (aInsets[i] == XML_lIns || aInsets[i] == XML_rIns) ? 254 : 127; |
701 | 2.05k | } |
702 | 513 | const OUString aShapeProps[] |
703 | 513 | = { u"TextLeftDistance"_ustr, u"TextUpperDistance"_ustr, |
704 | 513 | u"TextRightDistance"_ustr, u"TextLowerDistance"_ustr }; |
705 | 2.56k | for (std::size_t i = 0; i < SAL_N_ELEMENTS(aShapeProps); ++i) |
706 | 2.05k | { |
707 | 2.05k | if (!oInsets[i]) |
708 | 0 | continue; |
709 | 2.05k | if (xPropertySetInfo && xPropertySetInfo->hasPropertyByName(aShapeProps[i])) |
710 | 2.05k | xPropertySet->setPropertyValue(aShapeProps[i], uno::Any(*oInsets[i])); |
711 | 0 | else |
712 | 2.05k | SAL_WARN("oox", "Property: " << aShapeProps[i] << " not supported"); |
713 | 2.05k | } |
714 | 513 | } |
715 | | |
716 | | // Handle text vertical adjustment inside a text frame |
717 | 513 | if (rAttribs.hasAttribute(XML_anchor)) |
718 | 393 | { |
719 | 393 | if (xPropertySetInfo |
720 | 393 | && xPropertySetInfo->hasPropertyByName(u"TextVerticalAdjust"_ustr)) |
721 | 393 | { |
722 | 393 | drawing::TextVerticalAdjust eAdjust = drawingml::GetTextVerticalAdjust( |
723 | 393 | rAttribs.getToken(XML_anchor, XML_t)); |
724 | 393 | xPropertySet->setPropertyValue(u"TextVerticalAdjust"_ustr, |
725 | 393 | uno::Any(eAdjust)); |
726 | 393 | } |
727 | 0 | else |
728 | 393 | SAL_WARN("oox", "Property: TextVerticalAdjust not supported"); |
729 | 393 | } |
730 | | |
731 | | // Apply character color of the shape to the shape's textbox. |
732 | 513 | uno::Reference<text::XText> xText(mxShape, uno::UNO_QUERY); |
733 | 513 | if (xText) |
734 | 513 | { |
735 | 513 | uno::Any xCharColor = xPropertySet->getPropertyValue(u"CharColor"_ustr); |
736 | 513 | Color aColor = COL_AUTO; |
737 | 513 | if ((xCharColor >>= aColor) && aColor != COL_AUTO) |
738 | 298 | { |
739 | | // tdf#135923 Apply character color of the shape to the textrun |
740 | | // when the character color of the textrun is default. |
741 | | // tdf#153791 But only if the run has no background color (shd element in OOXML) |
742 | 298 | if (uno::Reference<container::XEnumerationAccess> paraEnumAccess{ |
743 | 298 | xText, uno::UNO_QUERY }) |
744 | 298 | { |
745 | 298 | uno::Reference<container::XEnumeration> paraEnum( |
746 | 298 | paraEnumAccess->createEnumeration()); |
747 | | |
748 | 949 | while (paraEnum->hasMoreElements()) |
749 | 651 | { |
750 | 651 | uno::Reference<text::XTextRange> xParagraph(paraEnum->nextElement(), |
751 | 651 | uno::UNO_QUERY); |
752 | 651 | uno::Reference<container::XEnumerationAccess> runEnumAccess( |
753 | 651 | xParagraph, uno::UNO_QUERY); |
754 | 651 | if (!runEnumAccess.is()) |
755 | 4 | continue; |
756 | 647 | if (uno::Reference<beans::XPropertySet> xParaPropSet{ |
757 | 647 | xParagraph, uno::UNO_QUERY }) |
758 | 647 | if ((xParaPropSet->getPropertyValue(u"ParaBackColor"_ustr) |
759 | 647 | >>= aColor) |
760 | 647 | && aColor != COL_AUTO) |
761 | 0 | continue; |
762 | | |
763 | 647 | uno::Reference<container::XEnumeration> runEnum |
764 | 647 | = runEnumAccess->createEnumeration(); |
765 | | |
766 | 1.35k | while (runEnum->hasMoreElements()) |
767 | 707 | { |
768 | 707 | uno::Reference<text::XTextRange> xRun(runEnum->nextElement(), |
769 | 707 | uno::UNO_QUERY); |
770 | 707 | const uno::Reference<beans::XPropertyState> xRunState( |
771 | 707 | xRun, uno::UNO_QUERY); |
772 | 707 | if (!xRunState |
773 | 707 | || xRunState->getPropertyState(u"CharColor"_ustr) |
774 | 707 | == beans::PropertyState_DEFAULT_VALUE) |
775 | 627 | { |
776 | 627 | uno::Reference<beans::XPropertySet> xRunPropSet( |
777 | 627 | xRun, uno::UNO_QUERY); |
778 | 627 | if (!xRunPropSet) |
779 | 0 | continue; |
780 | 627 | if ((xRunPropSet->getPropertyValue(u"CharBackColor"_ustr) |
781 | 627 | >>= aColor) |
782 | 627 | && aColor != COL_AUTO) |
783 | 0 | continue; |
784 | 627 | if (!(xRunPropSet->getPropertyValue(u"CharColor"_ustr) |
785 | 627 | >>= aColor) |
786 | 627 | || aColor == COL_AUTO) |
787 | 611 | xRunPropSet->setPropertyValue(u"CharColor"_ustr, |
788 | 611 | xCharColor); |
789 | 627 | } |
790 | 707 | } |
791 | 647 | } |
792 | 298 | } |
793 | 298 | } |
794 | 513 | } |
795 | | |
796 | 513 | if (xPropertySetInfo && xPropertySetInfo->hasPropertyByName(u"TextWordWrap"_ustr)) |
797 | 513 | { |
798 | 513 | auto nWrappingType = rAttribs.getToken(XML_wrap, XML_square); |
799 | 513 | xPropertySet->setPropertyValue(u"TextWordWrap"_ustr, |
800 | 513 | uno::Any(nWrappingType == XML_square)); |
801 | 513 | } |
802 | 0 | else |
803 | 513 | SAL_WARN("oox", "Property: TextWordWrap not supported"); |
804 | | |
805 | 513 | return this; |
806 | 513 | } |
807 | 480 | else if (m_bHasWPGParent && mpShapePtr) |
808 | 274 | { |
809 | | // this WPS context has to be inside a WPG shape, so the <BodyPr> element |
810 | | // cannot be applied to mxShape member, use mpShape instead, and after the |
811 | | // the parent shape finished, apply it for its children. |
812 | 274 | mpShapePtr->setWPGChild(true); |
813 | 274 | oox::drawingml::TextBodyPtr pTextBody; |
814 | 274 | pTextBody.reset(new oox::drawingml::TextBody()); |
815 | | |
816 | 274 | if (rAttribs.hasAttribute(XML_anchor)) |
817 | 0 | { |
818 | 0 | drawing::TextVerticalAdjust eAdjust |
819 | 0 | = drawingml::GetTextVerticalAdjust(rAttribs.getToken(XML_anchor, XML_t)); |
820 | 0 | pTextBody->getTextProperties().meVA = eAdjust; |
821 | 0 | } |
822 | | |
823 | 274 | sal_Int32 aInsets[] = { XML_lIns, XML_tIns, XML_rIns, XML_bIns }; |
824 | 1.37k | for (int i = 0; i < 4; ++i) |
825 | 1.09k | { |
826 | 1.09k | if (rAttribs.hasAttribute(XML_lIns)) |
827 | 0 | { |
828 | 0 | std::optional<OUString> oValue = rAttribs.getString(aInsets[i]); |
829 | 0 | if (oValue.has_value()) |
830 | 0 | pTextBody->getTextProperties().moInsets[i] |
831 | 0 | = oox::drawingml::GetCoordinate(oValue.value()); |
832 | 0 | else |
833 | | // Defaults from the spec: left/right: 91440 EMU, top/bottom: 45720 EMU |
834 | 0 | pTextBody->getTextProperties().moInsets[i] |
835 | 0 | = (aInsets[i] == XML_lIns || aInsets[i] == XML_rIns) ? 254 : 127; |
836 | 0 | } |
837 | 1.09k | } |
838 | | |
839 | 274 | mpShapePtr->setTextBody(pTextBody); |
840 | 274 | } |
841 | 480 | break; |
842 | 480 | case XML_noAutofit: |
843 | 513 | case XML_spAutoFit: |
844 | 513 | { |
845 | 513 | uno::Reference<lang::XServiceInfo> xServiceInfo(mxShape, uno::UNO_QUERY); |
846 | | // We can't use oox::drawingml::TextBodyPropertiesContext here, as this |
847 | | // is a child context of bodyPr, so the shape is already sent: we need |
848 | | // to alter the XShape directly. |
849 | 513 | uno::Reference<beans::XPropertySet> xPropertySet(mxShape, uno::UNO_QUERY); |
850 | 513 | if (xPropertySet.is()) |
851 | 513 | { |
852 | 513 | if (xServiceInfo->supportsService(u"com.sun.star.text.TextFrame"_ustr)) |
853 | 0 | xPropertySet->setPropertyValue( |
854 | 0 | u"FrameIsAutomaticHeight"_ustr, |
855 | 0 | uno::Any(getBaseToken(nElementToken) == XML_spAutoFit)); |
856 | 513 | else |
857 | 513 | { |
858 | 513 | auto xPropertySetInfo = xPropertySet->getPropertySetInfo(); |
859 | 513 | if (xPropertySetInfo |
860 | 513 | && xPropertySetInfo->hasPropertyByName(u"TextAutoGrowHeight"_ustr)) |
861 | 513 | { |
862 | 513 | xPropertySet->setPropertyValue( |
863 | 513 | u"TextAutoGrowHeight"_ustr, |
864 | 513 | uno::Any(getBaseToken(nElementToken) == XML_spAutoFit)); |
865 | 513 | } |
866 | 0 | else |
867 | 513 | SAL_WARN("oox", "Property: TextAutoGrowHeight not supported"); |
868 | 513 | } |
869 | 513 | } |
870 | 513 | } |
871 | 513 | break; |
872 | 513 | case XML_prstTxWarp: |
873 | 24 | if (rAttribs.hasAttribute(XML_prst)) |
874 | 24 | { |
875 | 24 | uno::Reference<beans::XPropertySet> xPropertySet(mxShape, uno::UNO_QUERY); |
876 | 24 | if (xPropertySet.is()) |
877 | 24 | { |
878 | 24 | std::optional<OUString> presetShapeName = rAttribs.getString(XML_prst); |
879 | 24 | const OUString& preset = presetShapeName.value(); |
880 | 24 | comphelper::SequenceAsHashMap aCustomShapeGeometry( |
881 | 24 | xPropertySet->getPropertyValue(u"CustomShapeGeometry"_ustr)); |
882 | 24 | aCustomShapeGeometry[u"PresetTextWarp"_ustr] <<= preset; |
883 | 24 | xPropertySet->setPropertyValue( |
884 | 24 | u"CustomShapeGeometry"_ustr, |
885 | 24 | uno::Any(aCustomShapeGeometry.getAsConstPropertyValueList())); |
886 | 24 | } |
887 | 24 | } |
888 | 24 | return new oox::drawingml::PresetTextShapeContext( |
889 | 24 | *this, rAttribs, *(getShape()->getCustomShapeProperties())); |
890 | 513 | case XML_txbx: |
891 | 513 | { |
892 | 513 | mpShapePtr->getCustomShapeProperties()->setShapeTypeOverride(true); |
893 | 513 | mpShapePtr->setTextBox(true); |
894 | | //in case if the textbox is linked, save the attributes |
895 | | //for further processing. |
896 | 513 | if (rAttribs.hasAttribute(XML_id)) |
897 | 0 | { |
898 | 0 | std::optional<OUString> id = rAttribs.getString(XML_id); |
899 | 0 | if (id.has_value()) |
900 | 0 | { |
901 | 0 | oox::drawingml::LinkedTxbxAttr linkedTxtBoxAttr; |
902 | 0 | linkedTxtBoxAttr.id = id.value().toInt32(); |
903 | 0 | mpShapePtr->setTxbxHasLinkedTxtBox(true); |
904 | 0 | mpShapePtr->setLinkedTxbxAttributes(linkedTxtBoxAttr); |
905 | 0 | } |
906 | 0 | } |
907 | 513 | return this; |
908 | 513 | } |
909 | 0 | break; |
910 | 0 | case XML_linkedTxbx: |
911 | 0 | { |
912 | | //in case if the textbox is linked, save the attributes |
913 | | //for further processing. |
914 | 0 | mpShapePtr->getCustomShapeProperties()->setShapeTypeOverride(true); |
915 | 0 | mpShapePtr->setTextBox(true); |
916 | 0 | std::optional<OUString> id = rAttribs.getString(XML_id); |
917 | 0 | std::optional<OUString> seq = rAttribs.getString(XML_seq); |
918 | 0 | if (id.has_value() && seq.has_value()) |
919 | 0 | { |
920 | 0 | oox::drawingml::LinkedTxbxAttr linkedTxtBoxAttr; |
921 | 0 | linkedTxtBoxAttr.id = id.value().toInt32(); |
922 | 0 | linkedTxtBoxAttr.seq = seq.value().toInt32(); |
923 | 0 | mpShapePtr->setTxbxHasLinkedTxtBox(true); |
924 | 0 | mpShapePtr->setLinkedTxbxAttributes(linkedTxtBoxAttr); |
925 | 0 | } |
926 | 0 | } |
927 | 0 | break; |
928 | 2.63k | default: |
929 | 2.63k | return ShapeContext::onCreateContext(nElementToken, rAttribs); |
930 | 5.39k | } |
931 | 1.71k | return nullptr; |
932 | 5.39k | } |
933 | | |
934 | | void WpsContext::onEndElement() |
935 | 3.01k | { |
936 | | // Convert shape to Fontwork shape if necessary and meaningful. |
937 | | // Only at end of bodyPr all needed info is available. |
938 | | |
939 | 3.01k | if (getBaseToken(getCurrentElement()) != XML_bodyPr) |
940 | 2.50k | return; |
941 | | |
942 | | // Make sure all needed parts are available |
943 | 513 | auto* pCustomShape |
944 | 513 | = dynamic_cast<SdrObjCustomShape*>(SdrObject::getSdrObjectFromXShape(mxShape)); |
945 | 513 | if (!pCustomShape || !mpShapePtr || !mxShape.is()) |
946 | 0 | return; |
947 | 513 | uno::Reference<beans::XPropertySet> xShapePropertySet(mxShape, uno::UNO_QUERY); |
948 | 513 | if (!xShapePropertySet.is()) |
949 | 0 | return; |
950 | | // This is the text in the frame, associated with the shape |
951 | 513 | uno::Reference<text::XText> xText(mxShape, uno::UNO_QUERY); |
952 | 513 | if (!xText.is()) |
953 | 0 | return; |
954 | | |
955 | 513 | OUString sMSPresetType; |
956 | 513 | comphelper::SequenceAsHashMap aCustomShapeGeometry( |
957 | 513 | xShapePropertySet->getPropertyValue(u"CustomShapeGeometry"_ustr)); |
958 | 513 | aCustomShapeGeometry[u"PresetTextWarp"_ustr] >>= sMSPresetType; |
959 | 513 | if (sMSPresetType.isEmpty() || sMSPresetType == u"textNoShape") |
960 | 513 | return; |
961 | | |
962 | | // Word can combine its "abc Transform" with a lot of shape types. LibreOffice can only render |
963 | | // the old kind WordArt, which is based on a rectangle. In case of non rectangular shape we keep |
964 | | // the shape and do not convert the text to Fontwork. |
965 | 0 | OUString sType; |
966 | 0 | aCustomShapeGeometry[u"Type"_ustr] >>= sType; |
967 | 0 | if (sType != u"ooxml-rect") |
968 | 0 | return; |
969 | | |
970 | | // Copy properties from frame text to have them available after the frame is removed. |
971 | 0 | std::vector<beans::PropertyValue> aTextPropVec; |
972 | 0 | if (!lcl_getTextPropsFromFrameText(xText, aTextPropVec)) |
973 | 0 | return; |
974 | 0 | comphelper::SequenceAsHashMap aTextPropMap(comphelper::containerToSequence(aTextPropVec)); |
975 | | |
976 | | // Copy text content from frame to shape. Since Fontwork uses simple text anyway, we can use |
977 | | // a string. |
978 | 0 | OUString sFrameContent(xText->getString()); |
979 | 0 | pCustomShape->NbcSetText(sFrameContent); |
980 | | |
981 | | // Setting the property "TextBox" to false includes removing the attached frame from the shape. |
982 | 0 | xShapePropertySet->setPropertyValue(u"TextBox"_ustr, uno::Any(false)); |
983 | | |
984 | | // Set the shape into text path mode, so that the text is drawn as Fontwork. Word renders a legacy |
985 | | // "text on path" without the legacy stretching, therefore use false for bFromWordArt. |
986 | 0 | mpShapePtr->getCustomShapeProperties()->setShapeTypeOverride(true); |
987 | 0 | FontworkHelpers::putCustomShapeIntoTextPathMode(mxShape, getShape()->getCustomShapeProperties(), |
988 | 0 | sMSPresetType, /*bFromWordArt*/ false); |
989 | | |
990 | | // Apply the text props to the fontwork shape |
991 | 0 | lcl_setTextPropsToShape(xShapePropertySet, aTextPropVec); // includes e.g. FontName |
992 | 0 | lcl_setTextAnchorFromTextProps(xShapePropertySet, aTextPropMap); |
993 | | |
994 | | // Fontwork in LO uses fill and stroke of the shape and cannot style text portions individually. |
995 | | // "abc Transform" in Word uses fill and outline of the characters. |
996 | | // We need to copy the properties from a run to the shape. |
997 | 0 | oox::drawingml::ShapePropertyMap aStrokeShapeProps(getFilter().getModelObjectHelper()); |
998 | 0 | oox::drawingml::LineProperties aCreatedLineProperties |
999 | 0 | = lcl_generateLinePropertiesFromTextProps(aTextPropMap); |
1000 | 0 | aCreatedLineProperties.pushToPropMap(aStrokeShapeProps, getFilter().getGraphicHelper()); |
1001 | 0 | lcl_applyShapePropsToShape(xShapePropertySet, aStrokeShapeProps); |
1002 | |
|
1003 | 0 | oox::drawingml::ShapePropertyMap aFillShapeProps(getFilter().getModelObjectHelper()); |
1004 | 0 | oox::drawingml::FillProperties aCreatedFillProperties |
1005 | 0 | = lcl_generateFillPropertiesFromTextProps(aTextPropMap); |
1006 | 0 | aCreatedFillProperties.pushToPropMap(aFillShapeProps, getFilter().getGraphicHelper(), |
1007 | 0 | /*nShapeRotation*/ 0, |
1008 | 0 | /*nPhClr*/ API_RGB_TRANSPARENT, |
1009 | 0 | /*aShapeSize*/ css::awt::Size(0, 0), /*nPhClrTheme*/ -1, |
1010 | 0 | pCustomShape->IsMirroredX(), pCustomShape->IsMirroredY(), |
1011 | 0 | /*bIsCustomShape*/ true); |
1012 | 0 | lcl_applyShapePropsToShape(xShapePropertySet, aFillShapeProps); |
1013 | | |
1014 | | // Copying the text content from frame to shape as string has lost the styles. Apply the used text |
1015 | | // properties back to all runs in the text. |
1016 | 0 | uno::Reference<text::XText> xNewText(pCustomShape->getUnoShape(), uno::UNO_QUERY); |
1017 | 0 | if (xNewText.is()) |
1018 | 0 | lcl_applyUsedTextPropsToAllTextRuns(xNewText, aTextPropVec); |
1019 | | |
1020 | | // Fontwork stretches the text to the given path. So adapt shape size to text is nonsensical. |
1021 | 0 | xShapePropertySet->setPropertyValue(u"TextAutoGrowHeight"_ustr, uno::Any(false)); |
1022 | 0 | xShapePropertySet->setPropertyValue(u"TextAutoGrowWidth"_ustr, uno::Any(false)); |
1023 | 0 | } |
1024 | | } |
1025 | | |
1026 | | /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |