Coverage Report

Created: 2026-02-14 09:37

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.0k
{
54
41.0k
    std::optional< OUString > oValue = rAttribs.getString( nToken );
55
41.0k
    if( oValue.has_value() ) return std::optional< bool >( ConversionHelper::decodeBool( oValue.value() ) );
56
34.2k
    return std::optional< bool >();
57
41.0k
}
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.0k
{
74
12.0k
    std::optional< OUString > oValue = rAttribs.getString( nToken );
75
12.0k
    double fRetval(fDefValue);
76
77
12.0k
    if( oValue.has_value() )
78
39
    {
79
39
        const OUString& aString(oValue.value());
80
39
        const sal_Int32 nLength(aString.getLength());
81
82
39
        if(nLength > 0)
83
39
        {
84
39
            if(aString.endsWith("f"))
85
32
            {
86
32
                fRetval = std::clamp(aString.toDouble() / 65536.0, 0.0, 1.0);
87
32
            }
88
7
            else
89
7
            {
90
7
                fRetval = ConversionHelper::decodePercent( aString, fDefValue );
91
7
            }
92
39
        }
93
39
    }
94
95
12.0k
    return std::optional< double >(fRetval);
96
12.0k
}
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.2k
{
117
10.2k
    std::optional< OUString > oValue = rAttribs.getString( nToken );
118
10.2k
    std::optional< DoublePair > oRetValue;
119
10.2k
    if( oValue.has_value() )
120
8
    {
121
8
        std::u16string_view aValue1, aValue2;
122
8
        ConversionHelper::separatePair( aValue1, aValue2, oValue.value(), ',' );
123
8
        oRetValue = DoublePair(
124
8
            ConversionHelper::decodePercent( aValue1, 0.0 ),
125
8
            ConversionHelper::decodePercent( aValue2, 0.0 ) );
126
8
    }
127
10.2k
    return oRetValue;
128
10.2k
}
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.51k
{
136
9.51k
    if( rValue.empty() ) return bDefaultForEmpty;
137
4.15k
    sal_Int32 nToken = AttributeConversion::decodeToken( rValue );
138
    // anything else than 't' or 'True' is considered to be false, as specified
139
4.15k
    return (nToken == XML_t) || (nToken == XML_True);
140
9.51k
}
141
142
} // namespace
143
144
ShapeLayoutContext::ShapeLayoutContext( ContextHandler2Helper const & rParent, Drawing& rDrawing ) :
145
200
    ContextHandler2( rParent ),
146
200
    mrDrawing( rDrawing )
147
200
{
148
200
}
149
150
ContextHandlerRef ShapeLayoutContext::onCreateContext( sal_Int32 nElement, const AttributeList& rAttribs )
151
200
{
152
200
    switch( nElement )
153
200
    {
154
200
        case O_TOKEN( idmap ):
155
200
        {
156
200
            OUString aBlockIds = rAttribs.getStringDefaulted( XML_data);
157
200
            sal_Int32 nIndex = 0;
158
400
            while( nIndex >= 0 )
159
200
            {
160
200
                std::u16string_view aToken = o3tl::trim(o3tl::getToken(aBlockIds, 0, ' ', nIndex ));
161
200
                if( !aToken.empty() )
162
200
                    mrDrawing.registerBlockId( o3tl::toInt32(aToken) );
163
200
            }
164
200
        }
165
200
        break;
166
200
    }
167
200
    return nullptr;
168
200
}
169
170
ClientDataContext::ClientDataContext( ContextHandler2Helper const & rParent,
171
        ClientData& rClientData, const AttributeList& rAttribs ) :
172
4.77k
    ContextHandler2( rParent ),
173
4.77k
    mrClientData( rClientData )
174
4.77k
{
175
4.77k
    mrClientData.mnObjType = rAttribs.getToken( XML_ObjectType, XML_TOKEN_INVALID );
176
4.77k
}
177
178
ContextHandlerRef ClientDataContext::onCreateContext( sal_Int32 /*nElement*/, const AttributeList& /*rAttribs*/ )
179
40.9k
{
180
40.9k
    if( isRootElement() )
181
40.9k
    {
182
40.9k
        maElementText.clear();
183
40.9k
        return this;
184
40.9k
    }
185
33
    return nullptr;
186
40.9k
}
187
188
void ClientDataContext::onCharacters( const OUString& rChars )
189
35.4k
{
190
    /*  Empty but existing elements have special meaning, e.g. 'true'. Collect
191
        existing text and convert it in onEndElement(). */
192
35.4k
    maElementText = rChars;
193
35.4k
}
194
195
void ClientDataContext::onEndElement()
196
45.6k
{
197
45.6k
    switch( getCurrentElement() )
198
45.6k
    {
199
4.58k
        case VMLX_TOKEN(Anchor):
200
4.58k
            mrClientData.maAnchor = maElementText;
201
4.58k
            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
19
        case VMLX_TOKEN(TextVAlign):
221
19
            mrClientData.mnTextVAlign = AttributeConversion::decodeToken(maElementText);
222
19
            break;
223
4.67k
        case VMLX_TOKEN(Column):
224
4.67k
            mrClientData.mnCol = maElementText.toInt32();
225
4.67k
            break;
226
4.68k
        case VMLX_TOKEN(Row):
227
4.68k
            mrClientData.mnRow = maElementText.toInt32();
228
4.68k
            break;
229
2
        case VMLX_TOKEN(Checked):
230
2
            mrClientData.mnChecked = maElementText.toInt32();
231
2
            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.15k
        case VMLX_TOKEN(PrintObject):
260
4.15k
            mrClientData.mbPrintObject = lclDecodeVmlxBool(maElementText, true);
261
4.15k
            break;
262
41
        case VMLX_TOKEN(Visible):
263
41
            mrClientData.mbVisible = lclDecodeVmlxBool(maElementText, true);
264
41
            break;
265
6
        case VMLX_TOKEN(DDE):
266
6
            mrClientData.mbDde = lclDecodeVmlxBool(maElementText, true);
267
6
            break;
268
2
        case VMLX_TOKEN(NoThreeD):
269
2
            mrClientData.mbNo3D = lclDecodeVmlxBool(maElementText, true);
270
2
            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
535
        case VMLX_TOKEN(MoveWithCells):
286
535
            mrClientData.mbMoveWithCells = lclDecodeVmlxBool(maElementText, false);
287
535
            break;
288
4.77k
        case VMLX_TOKEN(SizeWithCells):
289
4.77k
            mrClientData.mbSizeWithCells = lclDecodeVmlxBool(maElementText, false);
290
4.77k
            break;
291
45.6k
    }
292
45.6k
}
293
294
ShapeContextBase::ShapeContextBase( ContextHandler2Helper const & rParent ) :
295
8.02k
    ContextHandler2( rParent )
296
8.02k
{
297
8.02k
}
298
299
ContextHandlerRef ShapeContextBase::createShapeContext( ContextHandler2Helper const & rParent,
300
        ShapeContainer& rShapes, sal_Int32 nElement, const AttributeList& rAttribs )
301
8.33k
{
302
8.33k
    switch( nElement )
303
8.33k
    {
304
200
        case O_TOKEN( shapelayout ):
305
200
            return new ShapeLayoutContext( rParent, rShapes.getDrawing() );
306
307
802
        case VML_TOKEN( shapetype ):
308
802
            return new ShapeTypeContext( rParent, rShapes.createShapeType(), rAttribs );
309
110
        case VML_TOKEN( group ):
310
110
            return new GroupShapeContext( rParent, rShapes.createShape< GroupShape >(), rAttribs );
311
6.08k
        case VML_TOKEN( shape ):
312
6.08k
            if (rAttribs.hasAttribute(XML_path) &&
313
                    // tdf#122563 skip in the case of empty path
314
462
                    !rAttribs.getStringDefaulted(XML_path).isEmpty())
315
462
                return new ShapeContext( rParent, rShapes.createShape< BezierShape >(), rAttribs );
316
5.62k
            else
317
5.62k
                return new ShapeContext( rParent, rShapes.createShape< ComplexShape >(), rAttribs );
318
0
        case VML_TOKEN(background):
319
562
        case VML_TOKEN( rect ):
320
562
            return new RectangleShapeContext( rParent, rAttribs, rShapes.createShape< RectangleShape >() );
321
24
        case VML_TOKEN( roundrect ):
322
24
            return new ShapeContext( rParent, rShapes.createShape< RectangleShape >(), rAttribs );
323
169
        case VML_TOKEN( oval ):
324
169
            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
271
        case VML_TOKEN( line ):
328
271
            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.33k
    }
341
107
    return nullptr;
342
8.33k
}
343
344
ShapeTypeContext::ShapeTypeContext(ContextHandler2Helper const & rParent,
345
        std::shared_ptr<ShapeType> const& pShapeType,
346
        const AttributeList& rAttribs)
347
8.02k
    : ShapeContextBase(rParent)
348
8.02k
    , m_pShapeType(pShapeType) // tdf#112311 keep it alive
349
8.02k
    , mrTypeModel( pShapeType->getTypeModel() )
350
8.02k
{
351
    // shape identifier and shape name
352
8.02k
    bool bHasOspid = rAttribs.hasAttribute( O_TOKEN( spid ) );
353
8.02k
    mrTypeModel.maShapeId = rAttribs.getXString( bHasOspid ? O_TOKEN( spid ) : XML_id, OUString() );
354
8.02k
    mrTypeModel.maLegacyId = rAttribs.getStringDefaulted( XML_id);
355
8.02k
    OSL_ENSURE( !mrTypeModel.maShapeId.isEmpty(), "ShapeTypeContext::ShapeTypeContext - missing shape identifier" );
356
    // builtin shape type identifier
357
8.02k
    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.02k
    if( bHasOspid )
360
141
    {
361
141
        mrTypeModel.maShapeName = rAttribs.getXString( XML_id, OUString() );
362
        // get ShapeType and ShapeId from name for compatibility
363
141
        static constexpr OUString sShapeTypePrefix = u"shapetype_"_ustr;
364
141
        std::u16string_view tmp;
365
141
        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
141
        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
141
    }
376
377
    // coordinate system position/size, CSS style
378
8.02k
    mrTypeModel.moCoordPos = lclDecodeInt32Pair( rAttribs, XML_coordorigin );
379
8.02k
    mrTypeModel.moCoordSize = lclDecodeInt32Pair( rAttribs, XML_coordsize );
380
8.02k
    setStyle( rAttribs.getStringDefaulted( XML_style) );
381
8.02k
    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.02k
    mrTypeModel.maStrokeModel.moStroked = lclDecodeBool( rAttribs, XML_stroked );
397
8.02k
    mrTypeModel.maStrokeModel.moColor = rAttribs.getString( XML_strokecolor );
398
8.02k
    mrTypeModel.maStrokeModel.moWeight = rAttribs.getString( XML_strokeweight );
399
400
    // fill settings (may be overridden by v:fill element later)
401
8.02k
    mrTypeModel.maFillModel.moFilled = lclDecodeBool( rAttribs, XML_filled );
402
8.02k
    mrTypeModel.maFillModel.moColor = rAttribs.getString( XML_fillcolor );
403
404
    // For roundrect we may have an arcsize attribute to read
405
8.02k
    mrTypeModel.maArcsize = rAttribs.getStringDefaulted(XML_arcsize);
406
    // editas
407
8.02k
    mrTypeModel.maEditAs = rAttribs.getStringDefaulted(XML_editas);
408
409
8.02k
    mrTypeModel.maAdjustments = rAttribs.getStringDefaulted(XML_adj);
410
8.02k
}
411
412
ContextHandlerRef ShapeTypeContext::onCreateContext( sal_Int32 nElement, const AttributeList& rAttribs )
413
19.5k
{
414
19.5k
    if( isRootElement() ) switch( nElement )
415
19.5k
    {
416
1.80k
        case VML_TOKEN( stroke ):
417
1.80k
            assignIfUsed( mrTypeModel.maStrokeModel.moStroked, lclDecodeBool( rAttribs, XML_on ) );
418
1.80k
            mrTypeModel.maStrokeModel.maStartArrow.moArrowType = rAttribs.getToken( XML_startarrow );
419
1.80k
            mrTypeModel.maStrokeModel.maStartArrow.moArrowWidth = rAttribs.getToken( XML_startarrowwidth );
420
1.80k
            mrTypeModel.maStrokeModel.maStartArrow.moArrowLength = rAttribs.getToken( XML_startarrowlength );
421
1.80k
            mrTypeModel.maStrokeModel.maEndArrow.moArrowType = rAttribs.getToken( XML_endarrow );
422
1.80k
            mrTypeModel.maStrokeModel.maEndArrow.moArrowWidth = rAttribs.getToken( XML_endarrowwidth );
423
1.80k
            mrTypeModel.maStrokeModel.maEndArrow.moArrowLength = rAttribs.getToken( XML_endarrowlength );
424
1.80k
            assignIfUsed( mrTypeModel.maStrokeModel.moColor, rAttribs.getString( XML_color ) );
425
1.80k
            mrTypeModel.maStrokeModel.moOpacity = lclDecodeOpacity( rAttribs, XML_opacity, 1.0 );
426
1.80k
            assignIfUsed( mrTypeModel.maStrokeModel.moWeight, rAttribs.getString( XML_weight ) );
427
1.80k
            mrTypeModel.maStrokeModel.moDashStyle = rAttribs.getString( XML_dashstyle );
428
1.80k
            mrTypeModel.maStrokeModel.moLineStyle = rAttribs.getToken( XML_linestyle );
429
1.80k
            mrTypeModel.maStrokeModel.moEndCap = rAttribs.getToken( XML_endcap );
430
1.80k
            mrTypeModel.maStrokeModel.moJoinStyle = rAttribs.getToken( XML_joinstyle );
431
1.80k
        break;
432
5.11k
        case VML_TOKEN( fill ):
433
5.11k
        {
434
            // in DOCX shapes use r:id for the relationship id
435
            // in XLSX they use o:relid
436
5.11k
            bool bHasORelId = rAttribs.hasAttribute( O_TOKEN(relid) );
437
5.11k
            assignIfUsed( mrTypeModel.maFillModel.moFilled, lclDecodeBool( rAttribs, XML_on ) );
438
5.11k
            assignIfUsed( mrTypeModel.maFillModel.moColor, rAttribs.getString( XML_color ) );
439
5.11k
            mrTypeModel.maFillModel.moOpacity = lclDecodeOpacity( rAttribs, XML_opacity, 1.0 );
440
5.11k
            mrTypeModel.maFillModel.moColor2 = rAttribs.getString( XML_color2 );
441
5.11k
            mrTypeModel.maFillModel.moOpacity2 = lclDecodeOpacity( rAttribs, XML_opacity2, 1.0 );
442
5.11k
            mrTypeModel.maFillModel.moType = rAttribs.getToken( XML_type );
443
5.11k
            mrTypeModel.maFillModel.moAngle = rAttribs.getInteger( XML_angle );
444
5.11k
            mrTypeModel.maFillModel.moFocus = lclDecodePercent( rAttribs, XML_focus, 0.0 );
445
5.11k
            mrTypeModel.maFillModel.moFocusPos = lclDecodePercentPair( rAttribs, XML_focusposition );
446
5.11k
            mrTypeModel.maFillModel.moFocusSize = lclDecodePercentPair( rAttribs, XML_focussize );
447
5.11k
            mrTypeModel.maFillModel.moBitmapPath = decodeFragmentPath( rAttribs, bHasORelId ? O_TOKEN(relid) : R_TOKEN(id) );
448
5.11k
            mrTypeModel.maFillModel.moRotate = lclDecodeBool( rAttribs, XML_rotate );
449
5.11k
            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
184
        case NMSP_vmlWord | XML_wrap:
495
184
            mrTypeModel.moWrapAnchorX = rAttribs.getString(XML_anchorx);
496
184
            mrTypeModel.moWrapAnchorY = rAttribs.getString(XML_anchory);
497
184
            mrTypeModel.moWrapType = rAttribs.getString(XML_type);
498
184
            mrTypeModel.moWrapSide = rAttribs.getString(XML_side);
499
184
        break;
500
4.95k
        case VML_TOKEN( shadow ):
501
4.95k
        {
502
4.95k
            mrTypeModel.maShadowModel.mbHasShadow = true;
503
4.95k
            mrTypeModel.maShadowModel.moShadowOn = lclDecodeBool(rAttribs, XML_on).value_or(false);
504
4.95k
            assignIfUsed(mrTypeModel.maShadowModel.moColor, rAttribs.getString(XML_color));
505
4.95k
            assignIfUsed(mrTypeModel.maShadowModel.moOffset, rAttribs.getString(XML_offset));
506
4.95k
            mrTypeModel.maShadowModel.moOpacity = lclDecodePercent(rAttribs, XML_opacity, 1.0);
507
4.95k
        }
508
4.95k
        break;
509
20
        case VML_TOKEN( textpath ):
510
20
            assignIfUsed(mrTypeModel.maTextpathModel.moString, rAttribs.getString(XML_string));
511
20
            assignIfUsed(mrTypeModel.maTextpathModel.moStyle, rAttribs.getString(XML_style));
512
20
            assignIfUsed(mrTypeModel.maTextpathModel.moTrim, lclDecodeBool(rAttribs, XML_trim));
513
20
        break;
514
19.5k
    }
515
19.5k
    return nullptr;
516
19.5k
}
517
518
std::optional< OUString > ShapeTypeContext::decodeFragmentPath( const AttributeList& rAttribs, sal_Int32 nToken ) const
519
5.54k
{
520
5.54k
    std::optional< OUString > oFragmentPath;
521
5.54k
    std::optional< OUString > oRelId = rAttribs.getString( nToken );
522
5.54k
    if( oRelId.has_value() )
523
429
        oFragmentPath = getFragmentPathFromRelId( oRelId.value() );
524
5.54k
    return oFragmentPath;
525
5.54k
}
526
527
void ShapeTypeContext::setStyle( std::u16string_view rStyle )
528
8.02k
{
529
8.02k
    sal_Int32 nIndex = 0;
530
66.0k
    while( nIndex >= 0 )
531
58.0k
    {
532
58.0k
        std::u16string_view aName, aValue;
533
58.0k
        if( ConversionHelper::separatePair( aName, aValue, o3tl::getToken(rStyle, 0, ';', nIndex ), ':' ) )
534
57.2k
        {
535
57.2k
            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.4k
            else if( aName == u"width" )          mrTypeModel.maWidth = aValue;
540
36.4k
            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.6k
            else if( aName == u"mso-position-vertical-relative" )  mrTypeModel.maPositionVerticalRelative = aValue;
544
17.1k
            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.5k
            else if( aName == u"mso-position-vertical" ) mrTypeModel.maPositionVertical = aValue;
547
16.3k
            else if( aName == u"mso-width-percent" ) mrTypeModel.maWidthPercent = aValue;
548
16.2k
            else if( aName == u"mso-width-relative" ) mrTypeModel.maWidthRelative = aValue;
549
15.8k
            else if( aName == u"mso-height-percent" ) mrTypeModel.maHeightPercent = aValue;
550
15.8k
            else if( aName == u"mso-height-relative" ) mrTypeModel.maHeightRelative = aValue;
551
15.4k
            else if( aName == u"mso-fit-shape-to-text" )           mrTypeModel.mbAutoHeight = true;
552
15.4k
            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.20k
                mrTypeModel.mbVisible = aValue != u"hidden";
556
9.82k
            else if( aName == u"mso-wrap-style" ) mrTypeModel.maWrapStyle = aValue;
557
5.56k
            else if ( aName == u"v-text-anchor" ) mrTypeModel.maVTextAnchor = aValue;
558
1.32k
            else if ( aName == u"mso-wrap-distance-left" ) mrTypeModel.maWrapDistanceLeft = aValue;
559
1.05k
            else if ( aName == u"mso-wrap-distance-right" ) mrTypeModel.maWrapDistanceRight = aValue;
560
787
            else if ( aName == u"mso-wrap-distance-top" ) mrTypeModel.maWrapDistanceTop = aValue;
561
519
            else if ( aName == u"mso-wrap-distance-bottom" ) mrTypeModel.maWrapDistanceBottom = aValue;
562
57.2k
        }
563
58.0k
    }
564
8.02k
}
565
566
ShapeContext::ShapeContext(ContextHandler2Helper const& rParent,
567
                           const std::shared_ptr<ShapeBase>& pShape, const AttributeList& rAttribs)
568
7.22k
    : ShapeTypeContext(rParent, pShape, rAttribs)
569
7.22k
    , mrShape(*pShape)
570
7.22k
    , mrShapeModel(pShape->getShapeModel())
571
7.22k
{
572
    // collect shape specific attributes
573
7.22k
    mrShapeModel.maType = rAttribs.getXString( XML_type, OUString() );
574
    // polyline path
575
7.22k
    setPoints( rAttribs.getStringDefaulted( XML_points) );
576
    // line start and end positions
577
7.22k
    setFrom(rAttribs.getStringDefaulted(XML_from));
578
7.22k
    setTo(rAttribs.getStringDefaulted(XML_to));
579
7.22k
    setControl1(rAttribs.getStringDefaulted(XML_control1));
580
7.22k
    setControl2(rAttribs.getStringDefaulted(XML_control2));
581
7.22k
    setVmlPath(rAttribs.getStringDefaulted(XML_path));
582
7.22k
    setHyperlink(rAttribs.getStringDefaulted(XML_href));
583
7.22k
}
584
585
ContextHandlerRef ShapeContext::onCreateContext( sal_Int32 nElement, const AttributeList& rAttribs )
586
28.1k
{
587
    // Excel specific shape client data
588
28.1k
    if( isRootElement() ) switch( nElement )
589
28.1k
    {
590
5.80k
        case VML_TOKEN( textbox ):
591
5.80k
        {
592
            // Calculate the shape type: map both <rect> and <v:shape> with a textbox shape type to
593
            // a TextShape.
594
5.80k
            sal_Int32 nShapeType = 0;
595
5.80k
            if (ShapeContainer* pShapeContainer = mrShape.getContainer())
596
5.80k
            {
597
5.80k
                OUString aType = mrShapeModel.maType;
598
5.80k
                if (!aType.isEmpty() && aType[0] == '#')
599
5.17k
                {
600
5.17k
                    aType = aType.copy(1);
601
5.17k
                }
602
5.80k
                if (const ShapeType* pShapeType = pShapeContainer->getShapeTypeById(aType))
603
5.16k
                {
604
5.16k
                    nShapeType = pShapeType->getTypeModel().moShapeType.value();
605
5.16k
                }
606
5.80k
            }
607
5.80k
            mrShapeModel.mbInGroup = (getParentElement() == VML_TOKEN(group));
608
609
            // FIXME: the shape with textbox should be used for the next cases
610
5.80k
            if (getCurrentElement() == VML_TOKEN(rect) || nShapeType == ESCHER_ShpInst_TextBox)
611
5.33k
            {
612
5.33k
                if (mrShapeModel.mbInGroup)
613
                    // FIXME: without this a text will be added into the group-shape instead of its
614
                    // parent shape
615
23
                {
616
23
                    if (mrShape.isTextBox())
617
0
                        dynamic_cast<SimpleShape&>(mrShape).setService(
618
0
                            u"com.sun.star.drawing.CustomShape"_ustr);
619
23
                    else
620
23
                        dynamic_cast<SimpleShape&>(mrShape).setService(
621
23
                            u"com.sun.star.drawing.TextShape"_ustr);
622
23
                }
623
5.31k
                else
624
                    // FIXME: without this we does not handle some properties like shadow
625
5.31k
                    dynamic_cast<SimpleShape&>(mrShape).setService(u"com.sun.star.text.TextFrame"_ustr);
626
5.33k
            }
627
5.80k
            return new TextBoxContext( *this, mrShapeModel.createTextBox(mrShape.getTypeModel()), rAttribs,
628
5.80k
                mrShape.getDrawing().getFilter().getGraphicHelper());
629
0
        }
630
4.77k
        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.77k
            dynamic_cast<SimpleShape&>(mrShape).setService(u"com.sun.star.text.TextFrame"_ustr);
634
4.77k
            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
3
        case O_TOKEN( signatureline ):
643
3
            mrShapeModel.mbIsSignatureLine = true;
644
3
            mrShapeModel.maSignatureId = rAttribs.getStringDefaulted(XML_id);
645
3
            mrShapeModel.maSignatureLineSuggestedSignerName
646
3
                = rAttribs.getStringDefaulted(O_TOKEN(suggestedsigner));
647
3
            mrShapeModel.maSignatureLineSuggestedSignerTitle
648
3
                = rAttribs.getStringDefaulted(O_TOKEN(suggestedsigner2));
649
3
            mrShapeModel.maSignatureLineSuggestedSignerEmail
650
3
                = rAttribs.getStringDefaulted(O_TOKEN(suggestedsigneremail));
651
3
            mrShapeModel.maSignatureLineSigningInstructions
652
3
                = 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
3
            if (mrShapeModel.maSignatureLineSigningInstructions.isEmpty())
656
3
                mrShapeModel.maSignatureLineSigningInstructions
657
3
                    = rAttribs.getStringDefaulted(O_TOKEN(signinginstructions));
658
3
            mrShapeModel.mbSignatureLineShowSignDate = ConversionHelper::decodeBool(
659
3
                rAttribs.getString(XML_showsigndate, u"t"_ustr)); // default is true
660
3
            mrShapeModel.mbSignatureLineCanAddComment = ConversionHelper::decodeBool(
661
3
                rAttribs.getString(XML_allowcomments, u"f"_ustr)); // default is false
662
3
            break;
663
30
        case O_TOKEN( lock ):
664
            // TODO
665
30
            break;
666
28.1k
    }
667
    // handle remaining stuff in base class
668
17.5k
    return ShapeTypeContext::onCreateContext( nElement, rAttribs );
669
28.1k
}
670
void ShapeContext::setWriterShape()
671
5
{
672
5
    mrShape.setTextBox(true);
673
5
}
674
675
void ShapeContext::setPoints(std::u16string_view rPoints)
676
7.22k
{
677
7.22k
    mrShapeModel.maPoints.clear();
678
7.22k
    sal_Int32 nIndex = 0;
679
680
14.4k
    while (nIndex >= 0)
681
7.22k
    {
682
7.22k
        sal_Int32 nX = ConversionHelper::decodeMeasureToTwip(
683
7.22k
            mrShape.getDrawing().getFilter().getGraphicHelper(), o3tl::getToken(rPoints, 0, ',', nIndex),
684
7.22k
            0, true, true);
685
7.22k
        sal_Int32 nY = ConversionHelper::decodeMeasureToTwip(
686
7.22k
            mrShape.getDrawing().getFilter().getGraphicHelper(), o3tl::getToken(rPoints, 0, ',', nIndex),
687
7.22k
            0, false, true);
688
7.22k
        mrShapeModel.maPoints.emplace_back(nX, nY);
689
7.22k
    }
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.22k
    if (!mrShape.getTypeModel().maWidth.isEmpty() || !mrShape.getTypeModel().maHeight.isEmpty())
693
6.92k
        return;
694
695
305
    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
305
    else if (mrShapeModel.maPoints.size())
705
305
    {
706
305
        double fMinX = mrShapeModel.maPoints[0].X;
707
305
        double fMaxX = mrShapeModel.maPoints[0].X;
708
305
        double fMinY = mrShapeModel.maPoints[0].Y;
709
305
        double fMaxY = mrShapeModel.maPoints[0].Y;
710
305
        for (const auto& rPoint : mrShapeModel.maPoints)
711
305
        {
712
305
            if (rPoint.X < fMinX)
713
0
                fMinX = rPoint.X;
714
305
            else if (rPoint.X > fMaxX)
715
0
                fMaxX = rPoint.X;
716
305
            if (rPoint.Y < fMinY)
717
0
                fMinY = rPoint.Y;
718
305
            else if (rPoint.Y > fMaxY)
719
0
                fMaxY = rPoint.Y;
720
305
        }
721
305
        mrShape.getTypeModel().maWidth
722
305
            = OUString::number(
723
305
                  o3tl::convert(fMaxX - fMinX, o3tl::Length::twip, o3tl::Length::pt))
724
305
              + "pt";
725
305
        mrShape.getTypeModel().maHeight
726
305
            = OUString::number(
727
305
                  o3tl::convert(fMaxY - fMinY, o3tl::Length::twip, o3tl::Length::pt))
728
305
              + "pt";
729
        // Set moCoordSize, otherwise default (1000,1000) is used.
730
305
        mrShape.getTypeModel().moCoordSize =
731
305
            Int32Pair(basegfx::fround(fMaxX - fMinX), basegfx::fround(fMaxY - fMinY));
732
305
    }
733
305
}
734
735
void ShapeContext::setFrom( const OUString& rPoints )
736
7.22k
{
737
7.22k
    if (!rPoints.isEmpty())
738
271
        mrShapeModel.maFrom = rPoints;
739
7.22k
}
740
741
void ShapeContext::setTo( const OUString& rPoints )
742
7.22k
{
743
7.22k
    if (!rPoints.isEmpty())
744
271
        mrShapeModel.maTo = rPoints;
745
7.22k
}
746
747
void ShapeContext::setControl1( const OUString& rPoints )
748
7.22k
{
749
7.22k
    if (!rPoints.isEmpty())
750
0
        mrShapeModel.maControl1 = rPoints;
751
7.22k
}
752
753
void ShapeContext::setControl2( const OUString& rPoints )
754
7.22k
{
755
7.22k
    if (!rPoints.isEmpty())
756
0
        mrShapeModel.maControl2 = rPoints;
757
7.22k
}
758
void ShapeContext::setVmlPath( const OUString& rPath )
759
7.22k
{
760
7.22k
    if (!rPath.isEmpty())
761
462
        mrShapeModel.maVmlPath = rPath;
762
7.22k
}
763
764
void ShapeContext::setHyperlink( const OUString& rHyperlink )
765
7.22k
{
766
7.22k
    if (!rHyperlink.isEmpty())
767
5
        mrShapeModel.maHyperlink = rHyperlink;
768
7.22k
}
769
770
GroupShapeContext::GroupShapeContext(ContextHandler2Helper const& rParent,
771
                                     const std::shared_ptr<GroupShape>& pShape,
772
                                     const AttributeList& rAttribs)
773
110
    : ShapeContext(rParent, pShape, rAttribs)
774
110
    , mrShapes(pShape->getChildren())
775
110
{
776
110
}
777
778
ContextHandlerRef GroupShapeContext::onCreateContext( sal_Int32 nElement, const AttributeList& rAttribs )
779
690
{
780
    // try to create a context of an embedded shape
781
690
    ContextHandlerRef xContext = createShapeContext( *this, mrShapes, nElement, rAttribs );
782
    // handle remaining stuff of this shape in base class
783
690
    return xContext ? xContext : ShapeContext::onCreateContext( nElement, rAttribs );
784
690
}
785
786
RectangleShapeContext::RectangleShapeContext(ContextHandler2Helper const& rParent,
787
                                             const AttributeList& rAttribs,
788
                                             const std::shared_ptr<RectangleShape>& pShape)
789
562
    : ShapeContext(rParent, pShape, rAttribs)
790
562
{
791
562
}
792
793
ContextHandlerRef RectangleShapeContext::onCreateContext( sal_Int32 nElement, const AttributeList& rAttribs )
794
673
{
795
    // The parent class's context is fine
796
673
    return ShapeContext::onCreateContext( nElement, rAttribs );
797
673
}
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: */