Coverage Report

Created: 2026-08-14 10:22

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/oox/source/vml/vmlshapecontext.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
 * This file incorporates work covered by the following license notice:
10
 *
11
 *   Licensed to the Apache Software Foundation (ASF) under one or more
12
 *   contributor license agreements. See the NOTICE file distributed
13
 *   with this work for additional information regarding copyright
14
 *   ownership. The ASF licenses this file to you under the Apache
15
 *   License, Version 2.0 (the "License"); you may not use this file
16
 *   except in compliance with the License. You may obtain a copy of
17
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
18
 */
19
20
#include <sal/config.h>
21
22
#include <string_view>
23
24
#include <oox/vml/vmlshapecontext.hxx>
25
26
#include <oox/core/xmlfilterbase.hxx>
27
#include <oox/helper/attributelist.hxx>
28
#include <oox/helper/helper.hxx>
29
#include <oox/token/namespaces.hxx>
30
#include <oox/token/tokens.hxx>
31
#include <oox/vml/vmldrawing.hxx>
32
#include <oox/vml/vmlshape.hxx>
33
#include <oox/vml/vmlshapecontainer.hxx>
34
#include <oox/vml/vmltextboxcontext.hxx>
35
36
#include <osl/diagnose.h>
37
#include <filter/msfilter/escherex.hxx>
38
#include <o3tl/string_view.hxx>
39
40
namespace oox::vml {
41
42
using namespace ::com::sun::star;
43
44
using ::oox::core::ContextHandler2;
45
using ::oox::core::ContextHandler2Helper;
46
using ::oox::core::ContextHandlerRef;
47
48
namespace {
49
50
/** Returns the boolean value from the specified VML attribute (if present).
51
 */
52
std::optional< bool > lclDecodeBool( const AttributeList& rAttribs, sal_Int32 nToken )
53
41.2k
{
54
41.2k
    std::optional< OUString > oValue = rAttribs.getString( nToken );
55
41.2k
    if( oValue.has_value() ) return std::optional< bool >( ConversionHelper::decodeBool( oValue.value() ) );
56
34.3k
    return std::optional< bool >();
57
41.2k
}
58
59
/** Returns the percentage value from the specified VML attribute (if present).
60
    The value will be normalized (1.0 is returned for 100%).
61
 */
62
std::optional< double > lclDecodePercent( const AttributeList& rAttribs, sal_Int32 nToken, double fDefValue )
63
10.0k
{
64
10.0k
    std::optional< OUString > oValue = rAttribs.getString( nToken );
65
10.0k
    if( oValue.has_value() ) return std::optional< double >( ConversionHelper::decodePercent( oValue.value(), fDefValue ) );
66
10.0k
    return std::optional< double >();
67
10.0k
}
68
69
/** #119750# Special method for opacity; it *should* be a percentage value, but there are cases
70
    where a value relative to 0xffff (65536) is used, ending with an 'f'
71
 */
72
std::optional< double > lclDecodeOpacity( const AttributeList& rAttribs, sal_Int32 nToken, double fDefValue )
73
12.1k
{
74
12.1k
    std::optional< OUString > oValue = rAttribs.getString( nToken );
75
12.1k
    double fRetval(fDefValue);
76
77
12.1k
    if( oValue.has_value() )
78
43
    {
79
43
        const OUString& aString(oValue.value());
80
43
        const sal_Int32 nLength(aString.getLength());
81
82
43
        if(nLength > 0)
83
43
        {
84
43
            if(aString.endsWith("f"))
85
29
            {
86
29
                fRetval = std::clamp(aString.toDouble() / 65536.0, 0.0, 1.0);
87
29
            }
88
14
            else
89
14
            {
90
14
                fRetval = ConversionHelper::decodePercent( aString, fDefValue );
91
14
            }
92
43
        }
93
43
    }
94
95
12.1k
    return std::optional< double >(fRetval);
96
12.1k
}
97
98
/** Returns the integer value pair from the specified VML attribute (if present).
99
 */
100
std::optional< Int32Pair > lclDecodeInt32Pair( const AttributeList& rAttribs, sal_Int32 nToken )
101
16.0k
{
102
16.0k
    std::optional< OUString > oValue = rAttribs.getString( nToken );
103
16.0k
    std::optional< Int32Pair > oRetValue;
104
16.0k
    if( oValue.has_value() )
105
1.48k
    {
106
1.48k
        std::u16string_view aValue1, aValue2;
107
1.48k
        ConversionHelper::separatePair( aValue1, aValue2, oValue.value(), ',' );
108
1.48k
        oRetValue = Int32Pair( o3tl::toInt32(aValue1), o3tl::toInt32(aValue2) );
109
1.48k
    }
110
16.0k
    return oRetValue;
111
16.0k
}
112
113
/** Returns the percentage pair from the specified VML attribute (if present).
114
 */
115
std::optional< DoublePair > lclDecodePercentPair( const AttributeList& rAttribs, sal_Int32 nToken )
116
10.3k
{
117
10.3k
    std::optional< OUString > oValue = rAttribs.getString( nToken );
118
10.3k
    std::optional< DoublePair > oRetValue;
119
10.3k
    if( oValue.has_value() )
120
22
    {
121
22
        std::u16string_view aValue1, aValue2;
122
22
        ConversionHelper::separatePair( aValue1, aValue2, oValue.value(), ',' );
123
22
        oRetValue = DoublePair(
124
22
            ConversionHelper::decodePercent( aValue1, 0.0 ),
125
22
            ConversionHelper::decodePercent( aValue2, 0.0 ) );
126
22
    }
127
10.3k
    return oRetValue;
128
10.3k
}
129
130
/** Returns the boolean value from the passed string of an attribute in the x:
131
    namespace (VML for spreadsheets). Supported values: f, t, False, True.
132
    @param bDefaultForEmpty  Default value for the empty string.
133
 */
134
bool lclDecodeVmlxBool( std::u16string_view rValue, bool bDefaultForEmpty )
135
9.49k
{
136
9.49k
    if( rValue.empty() ) return bDefaultForEmpty;
137
4.14k
    sal_Int32 nToken = AttributeConversion::decodeToken( rValue );
138
    // anything else than 't' or 'True' is considered to be false, as specified
139
4.14k
    return (nToken == XML_t) || (nToken == XML_True);
140
9.49k
}
141
142
} // namespace
143
144
ShapeLayoutContext::ShapeLayoutContext( ContextHandler2Helper const & rParent, Drawing& rDrawing ) :
145
204
    ContextHandler2( rParent ),
146
204
    mrDrawing( rDrawing )
147
204
{
148
204
}
149
150
ContextHandlerRef ShapeLayoutContext::onCreateContext( sal_Int32 nElement, const AttributeList& rAttribs )
151
204
{
152
204
    switch( nElement )
153
204
    {
154
204
        case O_TOKEN( idmap ):
155
204
        {
156
204
            OUString aBlockIds = rAttribs.getStringDefaulted( XML_data);
157
204
            sal_Int32 nIndex = 0;
158
408
            while( nIndex >= 0 )
159
204
            {
160
204
                std::u16string_view aToken = o3tl::trim(o3tl::getToken(aBlockIds, 0, ' ', nIndex ));
161
204
                if( !aToken.empty() )
162
204
                    mrDrawing.registerBlockId( o3tl::toInt32(aToken) );
163
204
            }
164
204
        }
165
204
        break;
166
204
    }
167
204
    return nullptr;
168
204
}
169
170
ClientDataContext::ClientDataContext( ContextHandler2Helper const & rParent,
171
        ClientData& rClientData, const AttributeList& rAttribs ) :
172
4.76k
    ContextHandler2( rParent ),
173
4.76k
    mrClientData( rClientData )
174
4.76k
{
175
4.76k
    mrClientData.mnObjType = rAttribs.getToken( XML_ObjectType, XML_TOKEN_INVALID );
176
4.76k
}
177
178
ContextHandlerRef ClientDataContext::onCreateContext( sal_Int32 /*nElement*/, const AttributeList& /*rAttribs*/ )
179
40.8k
{
180
40.8k
    if( isRootElement() )
181
40.7k
    {
182
40.7k
        maElementText.clear();
183
40.7k
        return this;
184
40.7k
    }
185
44
    return nullptr;
186
40.8k
}
187
188
void ClientDataContext::onCharacters( const OUString& rChars )
189
35.3k
{
190
    /*  Empty but existing elements have special meaning, e.g. 'true'. Collect
191
        existing text and convert it in onEndElement(). */
192
35.3k
    maElementText = rChars;
193
35.3k
}
194
195
void ClientDataContext::onEndElement()
196
45.4k
{
197
45.4k
    switch( getCurrentElement() )
198
45.4k
    {
199
4.54k
        case VMLX_TOKEN(Anchor):
200
4.54k
            mrClientData.maAnchor = maElementText;
201
4.54k
            break;
202
0
        case VMLX_TOKEN(FmlaMacro):
203
0
            mrClientData.maFmlaMacro = maElementText;
204
0
            break;
205
0
        case VMLX_TOKEN(FmlaPict):
206
0
            mrClientData.maFmlaPict = maElementText;
207
0
            break;
208
10
        case VMLX_TOKEN(FmlaLink):
209
10
            mrClientData.maFmlaLink = maElementText;
210
10
            break;
211
4
        case VMLX_TOKEN(FmlaRange):
212
4
            mrClientData.maFmlaRange = maElementText;
213
4
            break;
214
0
        case VMLX_TOKEN(FmlaGroup):
215
0
            mrClientData.maFmlaGroup = maElementText;
216
0
            break;
217
0
        case VMLX_TOKEN(TextHAlign):
218
0
            mrClientData.mnTextHAlign = AttributeConversion::decodeToken(maElementText);
219
0
            break;
220
18
        case VMLX_TOKEN(TextVAlign):
221
18
            mrClientData.mnTextVAlign = AttributeConversion::decodeToken(maElementText);
222
18
            break;
223
4.65k
        case VMLX_TOKEN(Column):
224
4.65k
            mrClientData.mnCol = maElementText.toInt32();
225
4.65k
            break;
226
4.66k
        case VMLX_TOKEN(Row):
227
4.66k
            mrClientData.mnRow = maElementText.toInt32();
228
4.66k
            break;
229
1
        case VMLX_TOKEN(Checked):
230
1
            mrClientData.mnChecked = maElementText.toInt32();
231
1
            break;
232
0
        case VMLX_TOKEN(DropStyle):
233
0
            mrClientData.mnDropStyle = AttributeConversion::decodeToken(maElementText);
234
0
            break;
235
0
        case VMLX_TOKEN(DropLines):
236
0
            mrClientData.mnDropLines = maElementText.toInt32();
237
0
            break;
238
10
        case VMLX_TOKEN(Val):
239
10
            mrClientData.mnVal = maElementText.toInt32();
240
10
            break;
241
10
        case VMLX_TOKEN(Min):
242
10
            mrClientData.mnMin = maElementText.toInt32();
243
10
            break;
244
10
        case VMLX_TOKEN(Max):
245
10
            mrClientData.mnMax = maElementText.toInt32();
246
10
            break;
247
10
        case VMLX_TOKEN(Inc):
248
10
            mrClientData.mnInc = maElementText.toInt32();
249
10
            break;
250
10
        case VMLX_TOKEN(Page):
251
10
            mrClientData.mnPage = maElementText.toInt32();
252
10
            break;
253
4
        case VMLX_TOKEN(SelType):
254
4
            mrClientData.mnSelType = AttributeConversion::decodeToken(maElementText);
255
4
            break;
256
0
        case VMLX_TOKEN(VTEdit):
257
0
            mrClientData.mnVTEdit = maElementText.toInt32();
258
0
            break;
259
4.14k
        case VMLX_TOKEN(PrintObject):
260
4.14k
            mrClientData.mbPrintObject = lclDecodeVmlxBool(maElementText, true);
261
4.14k
            break;
262
41
        case VMLX_TOKEN(Visible):
263
41
            mrClientData.mbVisible = lclDecodeVmlxBool(maElementText, true);
264
41
            break;
265
7
        case VMLX_TOKEN(DDE):
266
7
            mrClientData.mbDde = lclDecodeVmlxBool(maElementText, true);
267
7
            break;
268
1
        case VMLX_TOKEN(NoThreeD):
269
1
            mrClientData.mbNo3D = lclDecodeVmlxBool(maElementText, true);
270
1
            break;
271
4
        case VMLX_TOKEN(NoThreeD2):
272
4
            mrClientData.mbNo3D2 = lclDecodeVmlxBool(maElementText, true);
273
4
            break;
274
0
        case VMLX_TOKEN(MultiLine):
275
0
            mrClientData.mbMultiLine = lclDecodeVmlxBool(maElementText, true);
276
0
            break;
277
0
        case VMLX_TOKEN(VScroll):
278
0
            mrClientData.mbVScroll = lclDecodeVmlxBool(maElementText, true);
279
0
            break;
280
0
        case VMLX_TOKEN(SecretEdit):
281
0
            mrClientData.mbSecretEdit = lclDecodeVmlxBool(maElementText, true);
282
0
            break;
283
        // Oddly, Excel will declare MoveWithCells and SizeWithCells with no value if these are false (therefore, bDefaultForEmpty == false)
284
        // When MoveWithCells and SizeWithCells are NOT declared they should be assumed true.
285
531
        case VMLX_TOKEN(MoveWithCells):
286
531
            mrClientData.mbMoveWithCells = lclDecodeVmlxBool(maElementText, false);
287
531
            break;
288
4.76k
        case VMLX_TOKEN(SizeWithCells):
289
4.76k
            mrClientData.mbSizeWithCells = lclDecodeVmlxBool(maElementText, false);
290
4.76k
            break;
291
45.4k
    }
292
45.4k
}
293
294
ShapeContextBase::ShapeContextBase( ContextHandler2Helper const & rParent ) :
295
8.04k
    ContextHandler2( rParent )
296
8.04k
{
297
8.04k
}
298
299
ContextHandlerRef ShapeContextBase::createShapeContext( ContextHandler2Helper const & rParent,
300
        ShapeContainer& rShapes, sal_Int32 nElement, const AttributeList& rAttribs )
301
8.34k
{
302
8.34k
    switch( nElement )
303
8.34k
    {
304
204
        case O_TOKEN( shapelayout ):
305
204
            return new ShapeLayoutContext( rParent, rShapes.getDrawing() );
306
307
791
        case VML_TOKEN( shapetype ):
308
791
            return new ShapeTypeContext( rParent, rShapes.createShapeType(), rAttribs );
309
115
        case VML_TOKEN( group ):
310
115
            return new GroupShapeContext( rParent, rShapes.createShape< GroupShape >(), rAttribs );
311
6.07k
        case VML_TOKEN( shape ):
312
6.07k
            if (rAttribs.hasAttribute(XML_path) &&
313
                    // tdf#122563 skip in the case of empty path
314
467
                    !rAttribs.getStringDefaulted(XML_path).isEmpty())
315
467
                return new ShapeContext( rParent, rShapes.createShape< BezierShape >(), rAttribs );
316
5.61k
            else
317
5.61k
                return new ShapeContext( rParent, rShapes.createShape< ComplexShape >(), rAttribs );
318
0
        case VML_TOKEN(background):
319
613
        case VML_TOKEN( rect ):
320
613
            return new RectangleShapeContext( rParent, rAttribs, rShapes.createShape< RectangleShape >() );
321
30
        case VML_TOKEN( roundrect ):
322
30
            return new ShapeContext( rParent, rShapes.createShape< RectangleShape >(), rAttribs );
323
162
        case VML_TOKEN( oval ):
324
162
            return new ShapeContext( rParent, rShapes.createShape< EllipseShape >(), rAttribs );
325
0
        case VML_TOKEN( polyline ):
326
0
            return new ShapeContext( rParent, rShapes.createShape< PolyLineShape >(), rAttribs );
327
251
        case VML_TOKEN( line ):
328
251
            return new ShapeContext( rParent, rShapes.createShape< LineShape >(), rAttribs );
329
0
        case VML_TOKEN( curve ):
330
0
            return new ShapeContext( rParent, rShapes.createShape< BezierShape >(), rAttribs );
331
332
        // TODO:
333
0
        case VML_TOKEN( arc ):
334
0
        case VML_TOKEN( diagram ):
335
0
        case VML_TOKEN( image ):
336
0
            return new ShapeContext( rParent, rShapes.createShape< ComplexShape >(), rAttribs );
337
338
0
        case W_TOKEN(control):
339
0
            return new ControlShapeContext( rParent, rShapes, rAttribs );
340
8.34k
    }
341
103
    return nullptr;
342
8.34k
}
343
344
ShapeTypeContext::ShapeTypeContext(ContextHandler2Helper const & rParent,
345
        std::shared_ptr<ShapeType> const& pShapeType,
346
        const AttributeList& rAttribs)
347
8.04k
    : ShapeContextBase(rParent)
348
8.04k
    , m_pShapeType(pShapeType) // tdf#112311 keep it alive
349
8.04k
    , mrTypeModel( pShapeType->getTypeModel() )
350
8.04k
{
351
    // shape identifier and shape name
352
8.04k
    bool bHasOspid = rAttribs.hasAttribute( O_TOKEN( spid ) );
353
8.04k
    mrTypeModel.maShapeId = rAttribs.getXString( bHasOspid ? O_TOKEN( spid ) : XML_id, OUString() );
354
8.04k
    mrTypeModel.maLegacyId = rAttribs.getStringDefaulted( XML_id);
355
8.04k
    OSL_ENSURE( !mrTypeModel.maShapeId.isEmpty(), "ShapeTypeContext::ShapeTypeContext - missing shape identifier" );
356
    // builtin shape type identifier
357
8.04k
    mrTypeModel.moShapeType = rAttribs.getInteger( O_TOKEN( spt ) );
358
    // if the o:spid attribute exists, the id attribute contains the user-defined shape name
359
8.04k
    if( bHasOspid )
360
132
    {
361
132
        mrTypeModel.maShapeName = rAttribs.getXString( XML_id, OUString() );
362
        // get ShapeType and ShapeId from name for compatibility
363
132
        static constexpr OUString sShapeTypePrefix = u"shapetype_"_ustr;
364
132
        std::u16string_view tmp;
365
132
        if( mrTypeModel.maShapeName.startsWith( sShapeTypePrefix ) )
366
0
        {
367
0
            mrTypeModel.maShapeId = mrTypeModel.maShapeName;
368
0
            mrTypeModel.moShapeType = o3tl::toInt32(mrTypeModel.maShapeName.subView(sShapeTypePrefix.getLength()));
369
0
        }
370
132
        else if (mrTypeModel.maShapeName.startsWith("_x0000_t", &tmp))
371
0
        {
372
0
            mrTypeModel.maShapeId = mrTypeModel.maShapeName;
373
0
            mrTypeModel.moShapeType = o3tl::toInt32(tmp);
374
0
        }
375
132
    }
376
377
    // coordinate system position/size, CSS style
378
8.04k
    mrTypeModel.moCoordPos = lclDecodeInt32Pair( rAttribs, XML_coordorigin );
379
8.04k
    mrTypeModel.moCoordSize = lclDecodeInt32Pair( rAttribs, XML_coordsize );
380
8.04k
    setStyle( rAttribs.getStringDefaulted( XML_style) );
381
8.04k
    if( lclDecodeBool( rAttribs, O_TOKEN( hr )).value_or( false ))
382
0
    {   // MSO's handling of o:hr width is nowhere near what the spec says:
383
        // - o:hrpct is not in % but in 0.1%
384
        // - if o:hrpct is not given, 100% width is assumed
385
        // - given width is used only if explicit o:hrpct="0" is given
386
0
        OUString hrpct = rAttribs.getString( O_TOKEN( hrpct ), u"1000"_ustr );
387
0
        if( hrpct != "0" )
388
0
            mrTypeModel.maWidthPercent = OUString::number( hrpct.toInt32() );
389
0
        mrTypeModel.maWrapDistanceLeft = "0";
390
0
        mrTypeModel.maWrapDistanceRight = "0";
391
0
        mrTypeModel.maPositionHorizontal = rAttribs.getString( O_TOKEN( hralign ), u"left"_ustr );
392
0
        mrTypeModel.moWrapType = "topAndBottom";
393
0
    }
394
395
    // stroke settings (may be overridden by v:stroke element later)
396
8.04k
    mrTypeModel.maStrokeModel.moStroked = lclDecodeBool( rAttribs, XML_stroked );
397
8.04k
    mrTypeModel.maStrokeModel.moColor = rAttribs.getString( XML_strokecolor );
398
8.04k
    mrTypeModel.maStrokeModel.moWeight = rAttribs.getString( XML_strokeweight );
399
400
    // fill settings (may be overridden by v:fill element later)
401
8.04k
    mrTypeModel.maFillModel.moFilled = lclDecodeBool( rAttribs, XML_filled );
402
8.04k
    mrTypeModel.maFillModel.moColor = rAttribs.getString( XML_fillcolor );
403
404
    // For roundrect we may have an arcsize attribute to read
405
8.04k
    mrTypeModel.maArcsize = rAttribs.getStringDefaulted(XML_arcsize);
406
    // editas
407
8.04k
    mrTypeModel.maEditAs = rAttribs.getStringDefaulted(XML_editas);
408
409
8.04k
    mrTypeModel.maAdjustments = rAttribs.getStringDefaulted(XML_adj);
410
8.04k
}
411
412
ContextHandlerRef ShapeTypeContext::onCreateContext( sal_Int32 nElement, const AttributeList& rAttribs )
413
19.6k
{
414
19.6k
    if( isRootElement() ) switch( nElement )
415
19.6k
    {
416
1.79k
        case VML_TOKEN( stroke ):
417
1.79k
            assignIfUsed( mrTypeModel.maStrokeModel.moStroked, lclDecodeBool( rAttribs, XML_on ) );
418
1.79k
            mrTypeModel.maStrokeModel.maStartArrow.moArrowType = rAttribs.getToken( XML_startarrow );
419
1.79k
            mrTypeModel.maStrokeModel.maStartArrow.moArrowWidth = rAttribs.getToken( XML_startarrowwidth );
420
1.79k
            mrTypeModel.maStrokeModel.maStartArrow.moArrowLength = rAttribs.getToken( XML_startarrowlength );
421
1.79k
            mrTypeModel.maStrokeModel.maEndArrow.moArrowType = rAttribs.getToken( XML_endarrow );
422
1.79k
            mrTypeModel.maStrokeModel.maEndArrow.moArrowWidth = rAttribs.getToken( XML_endarrowwidth );
423
1.79k
            mrTypeModel.maStrokeModel.maEndArrow.moArrowLength = rAttribs.getToken( XML_endarrowlength );
424
1.79k
            assignIfUsed( mrTypeModel.maStrokeModel.moColor, rAttribs.getString( XML_color ) );
425
1.79k
            mrTypeModel.maStrokeModel.moOpacity = lclDecodeOpacity( rAttribs, XML_opacity, 1.0 );
426
1.79k
            assignIfUsed( mrTypeModel.maStrokeModel.moWeight, rAttribs.getString( XML_weight ) );
427
1.79k
            mrTypeModel.maStrokeModel.moDashStyle = rAttribs.getString( XML_dashstyle );
428
1.79k
            mrTypeModel.maStrokeModel.moLineStyle = rAttribs.getToken( XML_linestyle );
429
1.79k
            mrTypeModel.maStrokeModel.moEndCap = rAttribs.getToken( XML_endcap );
430
1.79k
            mrTypeModel.maStrokeModel.moJoinStyle = rAttribs.getToken( XML_joinstyle );
431
1.79k
        break;
432
5.15k
        case VML_TOKEN( fill ):
433
5.15k
        {
434
            // in DOCX shapes use r:id for the relationship id
435
            // in XLSX they use o:relid
436
5.15k
            bool bHasORelId = rAttribs.hasAttribute( O_TOKEN(relid) );
437
5.15k
            assignIfUsed( mrTypeModel.maFillModel.moFilled, lclDecodeBool( rAttribs, XML_on ) );
438
5.15k
            assignIfUsed( mrTypeModel.maFillModel.moColor, rAttribs.getString( XML_color ) );
439
5.15k
            mrTypeModel.maFillModel.moOpacity = lclDecodeOpacity( rAttribs, XML_opacity, 1.0 );
440
5.15k
            mrTypeModel.maFillModel.moColor2 = rAttribs.getString( XML_color2 );
441
5.15k
            mrTypeModel.maFillModel.moOpacity2 = lclDecodeOpacity( rAttribs, XML_opacity2, 1.0 );
442
5.15k
            mrTypeModel.maFillModel.moType = rAttribs.getToken( XML_type );
443
5.15k
            mrTypeModel.maFillModel.moAngle = rAttribs.getInteger( XML_angle );
444
5.15k
            mrTypeModel.maFillModel.moFocus = lclDecodePercent( rAttribs, XML_focus, 0.0 );
445
5.15k
            mrTypeModel.maFillModel.moFocusPos = lclDecodePercentPair( rAttribs, XML_focusposition );
446
5.15k
            mrTypeModel.maFillModel.moFocusSize = lclDecodePercentPair( rAttribs, XML_focussize );
447
5.15k
            mrTypeModel.maFillModel.moBitmapPath = decodeFragmentPath( rAttribs, bHasORelId ? O_TOKEN(relid) : R_TOKEN(id) );
448
5.15k
            mrTypeModel.maFillModel.moRotate = lclDecodeBool( rAttribs, XML_rotate );
449
5.15k
            break;
450
0
        }
451
425
        case VML_TOKEN( imagedata ):
452
425
        {
453
            // shapes in docx use r:id for the relationship id
454
            // in xlsx it they use o:relid
455
425
            bool bHasORelId = rAttribs.hasAttribute( O_TOKEN( relid ) );
456
425
            mrTypeModel.moGraphicPath = decodeFragmentPath( rAttribs, bHasORelId ? O_TOKEN( relid ) : R_TOKEN( id ) );
457
425
            mrTypeModel.moGraphicTitle = rAttribs.getString( O_TOKEN( title ) );
458
459
            // Get crop attributes.
460
425
            mrTypeModel.moCropBottom = rAttribs.getString(XML_cropbottom);
461
425
            mrTypeModel.moCropLeft = rAttribs.getString(XML_cropleft);
462
425
            mrTypeModel.moCropRight = rAttribs.getString(XML_cropright);
463
425
            mrTypeModel.moCropTop = rAttribs.getString(XML_croptop);
464
465
            // Gain / contrast.
466
425
            std::optional<OUString> oGain = rAttribs.getString(XML_gain);
467
425
            sal_Int32 nGain = 0x10000;
468
425
            if (oGain.has_value() && oGain.value().endsWith("f"))
469
0
            {
470
0
                nGain = oGain.value().toInt32();
471
0
            }
472
425
            if (nGain < 0x10000)
473
0
            {
474
0
                nGain *= 101; // 100 + 1 to round
475
0
                nGain /= 0x10000;
476
0
                nGain -= 100;
477
0
            }
478
425
            mrTypeModel.mnGain = nGain;
479
480
            // Blacklevel / brightness.
481
425
            std::optional<OUString> oBlacklevel = rAttribs.getString(XML_blacklevel);
482
425
            sal_Int16 nBlacklevel = 0;
483
425
            if (oBlacklevel.has_value() && oBlacklevel.value().endsWith("f"))
484
0
            {
485
0
                nBlacklevel = oBlacklevel.value().toInt32();
486
0
            }
487
425
            if (nBlacklevel != 0)
488
0
            {
489
0
                nBlacklevel /= 327;
490
0
            }
491
425
            mrTypeModel.mnBlacklevel = nBlacklevel;
492
425
        }
493
425
        break;
494
199
        case NMSP_vmlWord | XML_wrap:
495
199
            mrTypeModel.moWrapAnchorX = rAttribs.getString(XML_anchorx);
496
199
            mrTypeModel.moWrapAnchorY = rAttribs.getString(XML_anchory);
497
199
            mrTypeModel.moWrapType = rAttribs.getString(XML_type);
498
199
            mrTypeModel.moWrapSide = rAttribs.getString(XML_side);
499
199
        break;
500
4.94k
        case VML_TOKEN( shadow ):
501
4.94k
        {
502
4.94k
            mrTypeModel.maShadowModel.mbHasShadow = true;
503
4.94k
            mrTypeModel.maShadowModel.moShadowOn = lclDecodeBool(rAttribs, XML_on).value_or(false);
504
4.94k
            assignIfUsed(mrTypeModel.maShadowModel.moColor, rAttribs.getString(XML_color));
505
4.94k
            assignIfUsed(mrTypeModel.maShadowModel.moOffset, rAttribs.getString(XML_offset));
506
4.94k
            mrTypeModel.maShadowModel.moOpacity = lclDecodePercent(rAttribs, XML_opacity, 1.0);
507
4.94k
        }
508
4.94k
        break;
509
55
        case VML_TOKEN( textpath ):
510
55
            assignIfUsed(mrTypeModel.maTextpathModel.moString, rAttribs.getString(XML_string));
511
55
            assignIfUsed(mrTypeModel.maTextpathModel.moStyle, rAttribs.getString(XML_style));
512
55
            assignIfUsed(mrTypeModel.maTextpathModel.moTrim, lclDecodeBool(rAttribs, XML_trim));
513
55
        break;
514
19.6k
    }
515
19.6k
    return nullptr;
516
19.6k
}
517
518
std::optional< OUString > ShapeTypeContext::decodeFragmentPath( const AttributeList& rAttribs, sal_Int32 nToken ) const
519
5.57k
{
520
5.57k
    std::optional< OUString > oFragmentPath;
521
5.57k
    std::optional< OUString > oRelId = rAttribs.getString( nToken );
522
5.57k
    if( oRelId.has_value() )
523
436
        oFragmentPath = getFragmentPathFromRelId( oRelId.value() );
524
5.57k
    return oFragmentPath;
525
5.57k
}
526
527
void ShapeTypeContext::setStyle( std::u16string_view rStyle )
528
8.04k
{
529
8.04k
    sal_Int32 nIndex = 0;
530
66.1k
    while( nIndex >= 0 )
531
58.1k
    {
532
58.1k
        std::u16string_view aName, aValue;
533
58.1k
        if( ConversionHelper::separatePair( aName, aValue, o3tl::getToken(rStyle, 0, ';', nIndex ), ':' ) )
534
57.3k
        {
535
57.3k
            if( aName == u"position" )      mrTypeModel.maPosition = aValue;
536
50.4k
            else if( aName == u"z-index" )        mrTypeModel.maZIndex = aValue;
537
44.7k
            else if( aName == u"left" )           mrTypeModel.maLeft = aValue;
538
43.9k
            else if( aName == u"top" )            mrTypeModel.maTop = aValue;
539
43.3k
            else if( aName == u"width" )          mrTypeModel.maWidth = aValue;
540
36.5k
            else if( aName == u"height" )         mrTypeModel.maHeight = aValue;
541
29.5k
            else if( aName == u"margin-left" )    mrTypeModel.maMarginLeft = aValue;
542
23.5k
            else if( aName == u"margin-top" )     mrTypeModel.maMarginTop = aValue;
543
17.5k
            else if( aName == u"mso-position-vertical-relative" )  mrTypeModel.maPositionVerticalRelative = aValue;
544
17.0k
            else if( aName == u"mso-position-horizontal-relative" )  mrTypeModel.maPositionHorizontalRelative = aValue;
545
16.6k
            else if( aName == u"mso-position-horizontal" ) mrTypeModel.maPositionHorizontal = aValue;
546
16.4k
            else if( aName == u"mso-position-vertical" ) mrTypeModel.maPositionVertical = aValue;
547
16.2k
            else if( aName == u"mso-width-percent" ) mrTypeModel.maWidthPercent = aValue;
548
16.1k
            else if( aName == u"mso-width-relative" ) mrTypeModel.maWidthRelative = aValue;
549
15.7k
            else if( aName == u"mso-height-percent" ) mrTypeModel.maHeightPercent = aValue;
550
15.7k
            else if( aName == u"mso-height-relative" ) mrTypeModel.maHeightRelative = aValue;
551
15.3k
            else if( aName == u"mso-fit-shape-to-text" )           mrTypeModel.mbAutoHeight = true;
552
15.3k
            else if( aName == u"rotation" )       mrTypeModel.maRotation = aValue;
553
15.2k
            else if( aName == u"flip" )       mrTypeModel.maFlip = aValue;
554
15.0k
            else if( aName == u"visibility" )
555
5.19k
                mrTypeModel.mbVisible = aValue != u"hidden";
556
9.80k
            else if( aName == u"mso-wrap-style" ) mrTypeModel.maWrapStyle = aValue;
557
5.67k
            else if ( aName == u"v-text-anchor" ) mrTypeModel.maVTextAnchor = aValue;
558
1.42k
            else if ( aName == u"mso-wrap-distance-left" ) mrTypeModel.maWrapDistanceLeft = aValue;
559
1.19k
            else if ( aName == u"mso-wrap-distance-right" ) mrTypeModel.maWrapDistanceRight = aValue;
560
951
            else if ( aName == u"mso-wrap-distance-top" ) mrTypeModel.maWrapDistanceTop = aValue;
561
712
            else if ( aName == u"mso-wrap-distance-bottom" ) mrTypeModel.maWrapDistanceBottom = aValue;
562
57.3k
        }
563
58.1k
    }
564
8.04k
}
565
566
ShapeContext::ShapeContext(ContextHandler2Helper const& rParent,
567
                           const std::shared_ptr<ShapeBase>& pShape, const AttributeList& rAttribs)
568
7.25k
    : ShapeTypeContext(rParent, pShape, rAttribs)
569
7.25k
    , mrShape(*pShape)
570
7.25k
    , mrShapeModel(pShape->getShapeModel())
571
7.25k
{
572
    // collect shape specific attributes
573
7.25k
    mrShapeModel.maType = rAttribs.getXString( XML_type, OUString() );
574
    // polyline path
575
7.25k
    setPoints( rAttribs.getStringDefaulted( XML_points) );
576
    // line start and end positions
577
7.25k
    setFrom(rAttribs.getStringDefaulted(XML_from));
578
7.25k
    setTo(rAttribs.getStringDefaulted(XML_to));
579
7.25k
    setControl1(rAttribs.getStringDefaulted(XML_control1));
580
7.25k
    setControl2(rAttribs.getStringDefaulted(XML_control2));
581
7.25k
    setVmlPath(rAttribs.getStringDefaulted(XML_path));
582
7.25k
    setHyperlink(rAttribs.getStringDefaulted(XML_href));
583
7.25k
}
584
585
ContextHandlerRef ShapeContext::onCreateContext( sal_Int32 nElement, const AttributeList& rAttribs )
586
28.2k
{
587
    // Excel specific shape client data
588
28.2k
    if( isRootElement() ) switch( nElement )
589
28.2k
    {
590
5.78k
        case VML_TOKEN( textbox ):
591
5.78k
        {
592
            // Calculate the shape type: map both <rect> and <v:shape> with a textbox shape type to
593
            // a TextShape.
594
5.78k
            sal_Int32 nShapeType = 0;
595
5.78k
            if (ShapeContainer* pShapeContainer = mrShape.getContainer())
596
5.78k
            {
597
5.78k
                OUString aType = mrShapeModel.maType;
598
5.78k
                if (!aType.isEmpty() && aType[0] == '#')
599
5.15k
                {
600
5.15k
                    aType = aType.copy(1);
601
5.15k
                }
602
5.78k
                if (const ShapeType* pShapeType = pShapeContainer->getShapeTypeById(aType))
603
5.14k
                {
604
5.14k
                    nShapeType = pShapeType->getTypeModel().moShapeType.value();
605
5.14k
                }
606
5.78k
            }
607
5.78k
            mrShapeModel.mbInGroup = (getParentElement() == VML_TOKEN(group));
608
609
            // FIXME: the shape with textbox should be used for the next cases
610
5.78k
            if (getCurrentElement() == VML_TOKEN(rect) || nShapeType == ESCHER_ShpInst_TextBox)
611
5.32k
            {
612
5.32k
                if (mrShapeModel.mbInGroup)
613
                    // FIXME: without this a text will be added into the group-shape instead of its
614
                    // parent shape
615
24
                {
616
24
                    if (mrShape.isTextBox())
617
0
                        dynamic_cast<SimpleShape&>(mrShape).setService(
618
0
                            u"com.sun.star.drawing.CustomShape"_ustr);
619
24
                    else
620
24
                        dynamic_cast<SimpleShape&>(mrShape).setService(
621
24
                            u"com.sun.star.drawing.TextShape"_ustr);
622
24
                }
623
5.30k
                else
624
                    // FIXME: without this we does not handle some properties like shadow
625
5.30k
                    dynamic_cast<SimpleShape&>(mrShape).setService(u"com.sun.star.text.TextFrame"_ustr);
626
5.32k
            }
627
5.78k
            return new TextBoxContext( *this, mrShapeModel.createTextBox(mrShape.getTypeModel()), rAttribs,
628
5.78k
                mrShape.getDrawing().getFilter().getGraphicHelper());
629
0
        }
630
4.76k
        case VMLX_TOKEN( ClientData ):
631
            // tdf#41466 ActiveX control shapes with a textbox are transformed into a frame
632
            // (see unit test testActiveXOptionButtonGroup)
633
4.76k
            dynamic_cast<SimpleShape&>(mrShape).setService(u"com.sun.star.text.TextFrame"_ustr);
634
4.76k
            return new ClientDataContext( *this, mrShapeModel.createClientData(), rAttribs );
635
0
        case VMLPPT_TOKEN( textdata ):
636
            // Force RectangleShape, this is ugly :(
637
            // and is there because of the lines above which change it to TextFrame
638
0
            dynamic_cast< SimpleShape& >( mrShape ).setService(
639
0
                    u"com.sun.star.drawing.RectangleShape"_ustr);
640
0
            mrShapeModel.maLegacyDiagramPath = getFragmentPathFromRelId(rAttribs.getStringDefaulted(XML_id));
641
0
            break;
642
2
        case O_TOKEN( signatureline ):
643
2
            mrShapeModel.mbIsSignatureLine = true;
644
2
            mrShapeModel.maSignatureId = rAttribs.getStringDefaulted(XML_id);
645
2
            mrShapeModel.maSignatureLineSuggestedSignerName
646
2
                = rAttribs.getStringDefaulted(O_TOKEN(suggestedsigner));
647
2
            mrShapeModel.maSignatureLineSuggestedSignerTitle
648
2
                = rAttribs.getStringDefaulted(O_TOKEN(suggestedsigner2));
649
2
            mrShapeModel.maSignatureLineSuggestedSignerEmail
650
2
                = rAttribs.getStringDefaulted(O_TOKEN(suggestedsigneremail));
651
2
            mrShapeModel.maSignatureLineSigningInstructions
652
2
                = rAttribs.getStringDefaulted(XML_signinginstructions);
653
            // we used to save this with an "o:" prefix, which is incorrect, so to support older
654
            // data, try the older way if the correct way is empty.
655
2
            if (mrShapeModel.maSignatureLineSigningInstructions.isEmpty())
656
2
                mrShapeModel.maSignatureLineSigningInstructions
657
2
                    = rAttribs.getStringDefaulted(O_TOKEN(signinginstructions));
658
2
            mrShapeModel.mbSignatureLineShowSignDate = ConversionHelper::decodeBool(
659
2
                rAttribs.getString(XML_showsigndate, u"t"_ustr)); // default is true
660
2
            mrShapeModel.mbSignatureLineCanAddComment = ConversionHelper::decodeBool(
661
2
                rAttribs.getString(XML_allowcomments, u"f"_ustr)); // default is false
662
2
            break;
663
25
        case O_TOKEN( lock ):
664
            // TODO
665
25
            break;
666
28.2k
    }
667
    // handle remaining stuff in base class
668
17.6k
    return ShapeTypeContext::onCreateContext( nElement, rAttribs );
669
28.2k
}
670
void ShapeContext::setWriterShape()
671
4
{
672
4
    mrShape.setTextBox(true);
673
4
}
674
675
void ShapeContext::setPoints(std::u16string_view rPoints)
676
7.25k
{
677
7.25k
    mrShapeModel.maPoints.clear();
678
7.25k
    sal_Int32 nIndex = 0;
679
680
14.5k
    while (nIndex >= 0)
681
7.25k
    {
682
7.25k
        sal_Int32 nX = ConversionHelper::decodeMeasureToTwip(
683
7.25k
            mrShape.getDrawing().getFilter().getGraphicHelper(), o3tl::getToken(rPoints, 0, ',', nIndex),
684
7.25k
            0, true, true);
685
7.25k
        sal_Int32 nY = ConversionHelper::decodeMeasureToTwip(
686
7.25k
            mrShape.getDrawing().getFilter().getGraphicHelper(), o3tl::getToken(rPoints, 0, ',', nIndex),
687
7.25k
            0, false, true);
688
7.25k
        mrShapeModel.maPoints.emplace_back(nX, nY);
689
7.25k
    }
690
    // VML polyline has no size in its style attribute. Word writes the size to attribute
691
    // coordsize with values in twip but without unit. For others we get size from points.
692
7.25k
    if (!mrShape.getTypeModel().maWidth.isEmpty() || !mrShape.getTypeModel().maHeight.isEmpty())
693
6.97k
        return;
694
695
279
    if (mrShape.getTypeModel().moCoordSize.has_value())
696
0
    {
697
0
        double fWidth = mrShape.getTypeModel().moCoordSize.value().first;
698
0
        fWidth = o3tl::convert(fWidth, o3tl::Length::twip, o3tl::Length::pt);
699
0
        double fHeight = mrShape.getTypeModel().moCoordSize.value().second;
700
0
        fHeight = o3tl::convert(fHeight, o3tl::Length::twip, o3tl::Length::pt);
701
0
        mrShape.getTypeModel().maWidth = OUString::number(fWidth) + "pt";
702
0
        mrShape.getTypeModel().maHeight = OUString::number(fHeight) + "pt";
703
0
    }
704
279
    else if (mrShapeModel.maPoints.size())
705
279
    {
706
279
        double fMinX = mrShapeModel.maPoints[0].X;
707
279
        double fMaxX = mrShapeModel.maPoints[0].X;
708
279
        double fMinY = mrShapeModel.maPoints[0].Y;
709
279
        double fMaxY = mrShapeModel.maPoints[0].Y;
710
279
        for (const auto& rPoint : mrShapeModel.maPoints)
711
279
        {
712
279
            if (rPoint.X < fMinX)
713
0
                fMinX = rPoint.X;
714
279
            else if (rPoint.X > fMaxX)
715
0
                fMaxX = rPoint.X;
716
279
            if (rPoint.Y < fMinY)
717
0
                fMinY = rPoint.Y;
718
279
            else if (rPoint.Y > fMaxY)
719
0
                fMaxY = rPoint.Y;
720
279
        }
721
279
        mrShape.getTypeModel().maWidth
722
279
            = OUString::number(
723
279
                  o3tl::convert(fMaxX - fMinX, o3tl::Length::twip, o3tl::Length::pt))
724
279
              + "pt";
725
279
        mrShape.getTypeModel().maHeight
726
279
            = OUString::number(
727
279
                  o3tl::convert(fMaxY - fMinY, o3tl::Length::twip, o3tl::Length::pt))
728
279
              + "pt";
729
        // Set moCoordSize, otherwise default (1000,1000) is used.
730
279
        mrShape.getTypeModel().moCoordSize =
731
279
            Int32Pair(basegfx::fround(fMaxX - fMinX), basegfx::fround(fMaxY - fMinY));
732
279
    }
733
279
}
734
735
void ShapeContext::setFrom( const OUString& rPoints )
736
7.25k
{
737
7.25k
    if (!rPoints.isEmpty())
738
251
        mrShapeModel.maFrom = rPoints;
739
7.25k
}
740
741
void ShapeContext::setTo( const OUString& rPoints )
742
7.25k
{
743
7.25k
    if (!rPoints.isEmpty())
744
251
        mrShapeModel.maTo = rPoints;
745
7.25k
}
746
747
void ShapeContext::setControl1( const OUString& rPoints )
748
7.25k
{
749
7.25k
    if (!rPoints.isEmpty())
750
0
        mrShapeModel.maControl1 = rPoints;
751
7.25k
}
752
753
void ShapeContext::setControl2( const OUString& rPoints )
754
7.25k
{
755
7.25k
    if (!rPoints.isEmpty())
756
0
        mrShapeModel.maControl2 = rPoints;
757
7.25k
}
758
void ShapeContext::setVmlPath( const OUString& rPath )
759
7.25k
{
760
7.25k
    if (!rPath.isEmpty())
761
467
        mrShapeModel.maVmlPath = rPath;
762
7.25k
}
763
764
void ShapeContext::setHyperlink( const OUString& rHyperlink )
765
7.25k
{
766
7.25k
    if (!rHyperlink.isEmpty())
767
5
        mrShapeModel.maHyperlink = rHyperlink;
768
7.25k
}
769
770
GroupShapeContext::GroupShapeContext(ContextHandler2Helper const& rParent,
771
                                     const std::shared_ptr<GroupShape>& pShape,
772
                                     const AttributeList& rAttribs)
773
115
    : ShapeContext(rParent, pShape, rAttribs)
774
115
    , mrShapes(pShape->getChildren())
775
115
{
776
115
}
777
778
ContextHandlerRef GroupShapeContext::onCreateContext( sal_Int32 nElement, const AttributeList& rAttribs )
779
722
{
780
    // try to create a context of an embedded shape
781
722
    ContextHandlerRef xContext = createShapeContext( *this, mrShapes, nElement, rAttribs );
782
    // handle remaining stuff of this shape in base class
783
722
    return xContext ? xContext : ShapeContext::onCreateContext( nElement, rAttribs );
784
722
}
785
786
RectangleShapeContext::RectangleShapeContext(ContextHandler2Helper const& rParent,
787
                                             const AttributeList& rAttribs,
788
                                             const std::shared_ptr<RectangleShape>& pShape)
789
613
    : ShapeContext(rParent, pShape, rAttribs)
790
613
{
791
613
}
792
793
ContextHandlerRef RectangleShapeContext::onCreateContext( sal_Int32 nElement, const AttributeList& rAttribs )
794
786
{
795
    // The parent class's context is fine
796
786
    return ShapeContext::onCreateContext( nElement, rAttribs );
797
786
}
798
799
ControlShapeContext::ControlShapeContext( ::oox::core::ContextHandler2Helper const & rParent, ShapeContainer& rShapes, const AttributeList& rAttribs )
800
0
    : ShapeContextBase (rParent)
801
0
{
802
0
    ::oox::vml::ControlInfo aInfo;
803
0
    aInfo.maShapeId = rAttribs.getXString( W_TOKEN( shapeid ), OUString() );
804
0
    aInfo.maFragmentPath = getFragmentPathFromRelId(rAttribs.getStringDefaulted( R_TOKEN(id)));
805
0
    aInfo.maName = rAttribs.getStringDefaulted( W_TOKEN( name ));
806
0
    aInfo.mbTextContentShape = true;
807
0
    rShapes.getDrawing().registerControl(aInfo);
808
0
}
809
810
} // namespace oox
811
812
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */