Coverage Report

Created: 2025-12-31 10:39

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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: */