Coverage Report

Created: 2026-03-31 11:00

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/oox/source/drawingml/customshapeproperties.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 <drawingml/customshapeproperties.hxx>
21
#include <oox/helper/propertymap.hxx>
22
#include <oox/helper/propertyset.hxx>
23
#include <oox/token/properties.hxx>
24
#include <oox/token/tokenmap.hxx>
25
#include <com/sun/star/awt/Rectangle.hpp>
26
#include <com/sun/star/awt/Size.hpp>
27
#include <com/sun/star/beans/PropertyValues.hpp>
28
#include <com/sun/star/beans/XPropertySet.hpp>
29
#include <com/sun/star/drawing/EnhancedCustomShapeAdjustmentValue.hpp>
30
#include <com/sun/star/drawing/EnhancedCustomShapeTextFrame.hpp>
31
#include <com/sun/star/drawing/XEnhancedCustomShapeDefaulter.hpp>
32
#include <com/sun/star/drawing/XShape.hpp>
33
#include <comphelper/sequence.hxx>
34
#include <o3tl/string_view.hxx>
35
#include <sal/log.hxx>
36
37
#include <algorithm>
38
39
using namespace ::com::sun::star;
40
using namespace ::com::sun::star::uno;
41
using namespace ::com::sun::star::beans;
42
using namespace ::com::sun::star::drawing;
43
44
namespace oox::drawingml {
45
46
void CustomShapeGuideContainer::ActualizeLookupMap() const
47
4.46k
{
48
4.46k
    if ( mbLookupMapStale )
49
833
    {
50
        // maGuideListLookupMap maps guide name to index in maGuideList
51
        // guides were added since last actualization, need to update map based on those
52
        // guide names can be reused, and current is the latest one
53
        // (see a1 guide in gear6 custom shape preset as example):
54
        //  go backwards and update if index is higher than previously
55
1.91k
        for ( sal_Int32 nIndex = static_cast<sal_Int32>( maGuideList.size() ) - 1; nIndex >= mnPreviousActSize; --nIndex )
56
1.07k
        {
57
1.07k
            const auto it = maGuideListLookupMap.find( maGuideList[ nIndex ].maName );
58
1.07k
            if ( it != maGuideListLookupMap.end() )
59
0
            {
60
0
                if ( nIndex > it->second )
61
0
                    it->second = nIndex;
62
0
            }
63
1.07k
            else
64
1.07k
                maGuideListLookupMap[ maGuideList[ nIndex ].maName ] = nIndex;
65
1.07k
        }
66
833
        mbLookupMapStale = false;
67
833
        mnPreviousActSize = static_cast<sal_Int32>( maGuideList.size() );
68
833
    }
69
4.46k
}
70
71
void CustomShapeGuideContainer::push_back( const CustomShapeGuide& rGuide )
72
1.40k
{
73
1.40k
    if ( !mbLookupMapStale )
74
1.06k
    {
75
1.06k
        mbLookupMapStale = true;
76
1.06k
        mnPreviousActSize = static_cast<sal_Int32>( maGuideList.size() );
77
1.06k
    }
78
1.40k
    maGuideList.push_back( rGuide );
79
1.40k
}
80
81
// returns the index into the guidelist for a given formula name,
82
// if the return value is < 0 then the guide value could not be found
83
sal_Int32 CustomShapeGuideContainer::GetCustomShapeGuideValue( const OUString &rFormulaName ) const
84
3.79k
{
85
3.79k
    ActualizeLookupMap();
86
3.79k
    const auto it = maGuideListLookupMap.find( rFormulaName );
87
3.79k
    if ( it != maGuideListLookupMap.end() )
88
1.89k
        return it->second;
89
90
1.89k
    return -1;
91
3.79k
}
92
93
sal_Int32 CustomShapeGuideContainer::SetCustomShapeGuideValue( const CustomShapeGuide& rGuide )
94
670
{
95
670
    ActualizeLookupMap();
96
    // change from previous SetCustomShapeGuideValue behavior: searching using cache traverses backwards
97
670
    const auto it = maGuideListLookupMap.find( rGuide.maName );
98
670
    if ( it != maGuideListLookupMap.end() )
99
40
        return it->second;
100
101
630
    maGuideList.push_back( rGuide );
102
630
    maGuideListLookupMap[ rGuide.maName ] = mnPreviousActSize;
103
630
    mnPreviousActSize++;
104
630
    return mnPreviousActSize - 1;
105
670
}
106
107
CustomShapeProperties::CustomShapeProperties()
108
251k
: mnShapePresetType ( -1 )
109
251k
, mbShapeTypeOverride(false)
110
251k
, mbMirroredX   ( false )
111
251k
, mbMirroredY   ( false )
112
251k
, mnTextPreRotateAngle ( 0 )
113
251k
, mnTextCameraZRotateAngle ( 0 )
114
251k
, mnArcNum ( 0 )
115
251k
{
116
251k
}
117
118
OUString CustomShapeProperties::getShapePresetTypeName() const
119
2.01k
{
120
2.01k
    return TokenMap::getUnicodeTokenName(mnShapePresetType);
121
2.01k
}
122
123
bool CustomShapeProperties::representsDefaultShape() const
124
15.8k
{
125
15.8k
    return !((getShapePresetType() >= 0 || maPath2DList.size() > 0) &&
126
14.6k
             getShapePresetType() != XML_Rect &&
127
14.6k
             getShapePresetType() != XML_rect);
128
15.8k
}
129
130
CustomShapeProperties::PresetDataMap CustomShapeProperties::maPresetDataMap;
131
132
void CustomShapeProperties::pushToPropSet(
133
    const Reference < XPropertySet >& xPropSet, const awt::Size &aSize )
134
19.5k
{
135
19.5k
    if ( mnShapePresetType >= 0 )
136
19.2k
    {
137
19.2k
        SAL_INFO("oox.drawingml", "preset: " << mnShapePresetType);
138
139
19.2k
        if (maPresetDataMap.empty())
140
3
            initializePresetDataMap();
141
142
19.2k
        PropertyMap aPropertyMap;
143
19.2k
        PropertySet aPropSet( xPropSet );
144
145
19.2k
        if (maPresetDataMap.contains(mnShapePresetType))
146
0
        {
147
0
            SAL_INFO(
148
0
                "oox.drawingml",
149
0
                "found property map for preset: " << mnShapePresetType);
150
151
0
            aPropertyMap = maPresetDataMap[mnShapePresetType];
152
#if OSL_DEBUG_LEVEL >= 2
153
            aPropertyMap.dumpCode( aPropertyMap.makePropertySet() );
154
#endif
155
0
        }
156
157
19.2k
        aPropertyMap.setProperty( PROP_MirroredX, mbMirroredX );
158
19.2k
        aPropertyMap.setProperty( PROP_MirroredY, mbMirroredY );
159
19.2k
        aPropertyMap.setProperty( PROP_TextPreRotateAngle, mnTextPreRotateAngle );
160
19.2k
        aPropertyMap.setProperty( PROP_TextCameraZRotateAngle, mnTextCameraZRotateAngle );
161
19.2k
        if (moTextAreaRotateAngle.has_value())
162
22
            aPropertyMap.setProperty(PROP_TextRotateAngle, moTextAreaRotateAngle.value());
163
19.2k
        if (!maExtrusionPropertyMap.empty())
164
3
        {
165
3
            Sequence< PropertyValue > aExtrusionSequence = maExtrusionPropertyMap.makePropertyValueSequence();
166
3
            aPropertyMap.setAnyProperty( PROP_Extrusion, css::uno::Any(aExtrusionSequence));
167
3
        }
168
19.2k
        Sequence< PropertyValue > aSeq = aPropertyMap.makePropertyValueSequence();
169
19.2k
        aPropSet.setProperty( PROP_CustomShapeGeometry, aSeq );
170
171
19.2k
        if ( !maAdjustmentGuideList.empty() )
172
218
        {
173
218
            static constexpr OUString sCustomShapeGeometry(u"CustomShapeGeometry"_ustr);
174
218
            static constexpr OUStringLiteral sAdjustmentValues(u"AdjustmentValues");
175
218
            uno::Any aGeoPropSet = xPropSet->getPropertyValue( sCustomShapeGeometry );
176
218
            uno::Sequence< beans::PropertyValue > aGeoPropSeq;
177
218
            if ( aGeoPropSet >>= aGeoPropSeq )
178
218
            {
179
218
                for ( auto& rGeoProp : asNonConstRange(aGeoPropSeq) )
180
1.09k
                {
181
1.09k
                    if ( rGeoProp.Name == sAdjustmentValues )
182
218
                    {
183
218
                        uno::Sequence< css::drawing::EnhancedCustomShapeAdjustmentValue > aAdjustmentSeq;
184
218
                        if ( rGeoProp.Value >>= aAdjustmentSeq )
185
218
                        {
186
218
                            auto aAdjustmentSeqRange = asNonConstRange(aAdjustmentSeq);
187
218
                            int nIndex=0;
188
218
                            for (auto const& adjustmentGuide : maAdjustmentGuideList)
189
304
                            {
190
304
                                if ( adjustmentGuide.maName.getLength() > 3 )
191
166
                                {
192
166
                                    sal_Int32 nAdjustmentIndex = o3tl::toInt32(adjustmentGuide.maName.subView( 3 )) - 1;
193
166
                                    if ( ( nAdjustmentIndex >= 0 ) && ( nAdjustmentIndex < aAdjustmentSeq.getLength() ) )
194
0
                                    {
195
0
                                        EnhancedCustomShapeAdjustmentValue aAdjustmentVal;
196
0
                                        aAdjustmentVal.Value <<= adjustmentGuide.maFormula.toInt32();
197
0
                                        aAdjustmentVal.State = PropertyState_DIRECT_VALUE;
198
0
                                        aAdjustmentVal.Name = adjustmentGuide.maName;
199
0
                                        aAdjustmentSeqRange[ nAdjustmentIndex ] = std::move(aAdjustmentVal);
200
0
                                    }
201
166
                                } else if ( aAdjustmentSeq.hasElements() ) {
202
0
                                    EnhancedCustomShapeAdjustmentValue aAdjustmentVal;
203
0
                                    aAdjustmentVal.Value <<= adjustmentGuide.maFormula.toInt32();
204
0
                                    aAdjustmentVal.State = PropertyState_DIRECT_VALUE;
205
0
                                    aAdjustmentVal.Name = adjustmentGuide.maName;
206
0
                                    if (nIndex < aAdjustmentSeq.getLength())
207
0
                                    {
208
0
                                        aAdjustmentSeqRange[nIndex] = std::move(aAdjustmentVal);
209
0
                                        ++nIndex;
210
0
                                    }
211
0
                                }
212
304
                            }
213
218
                            rGeoProp.Value <<= aAdjustmentSeq;
214
218
                            xPropSet->setPropertyValue( sCustomShapeGeometry, Any( aGeoPropSeq ) );
215
218
                            break;
216
218
                        }
217
218
                    }
218
1.09k
                }
219
218
            }
220
218
        }
221
19.2k
    }
222
331
    else
223
331
    {
224
331
        PropertyMap aPropertyMap;
225
331
        aPropertyMap.setProperty( PROP_Type, u"ooxml-non-primitive"_ustr);
226
331
        aPropertyMap.setProperty( PROP_MirroredX, mbMirroredX );
227
331
        aPropertyMap.setProperty( PROP_MirroredY, mbMirroredY );
228
331
        if( mnTextPreRotateAngle )
229
0
            aPropertyMap.setProperty( PROP_TextPreRotateAngle, mnTextPreRotateAngle );
230
331
        if (mnTextCameraZRotateAngle)
231
0
            aPropertyMap.setProperty(PROP_TextCameraZRotateAngle, mnTextCameraZRotateAngle);
232
331
        if (moTextAreaRotateAngle.has_value())
233
30
            aPropertyMap.setProperty(PROP_TextRotateAngle, moTextAreaRotateAngle.value());
234
        // Note 1: If Equations are defined - they are processed using internal div by 360 coordinates
235
        // while if they are not, standard ooxml coordinates are used.
236
        // This size specifically affects scaling.
237
        // Note 2: Width and Height are set to 0 to force scaling to 1.
238
331
        awt::Rectangle aViewBox( 0, 0, aSize.Width, aSize.Height );
239
        // tdf#113187 Each ArcTo introduces two additional equations for converting start and swing
240
        // angles. They do not count for test for use of standard ooxml coordinates
241
331
        if (maGuideList.size() - 2 * countArcTo() > 0)
242
235
            aViewBox = awt::Rectangle( 0, 0, 0, 0 );
243
331
        aPropertyMap.setProperty( PROP_ViewBox, aViewBox);
244
245
331
        Sequence< EnhancedCustomShapeAdjustmentValue > aAdjustmentValues( maAdjustmentGuideList.size() );
246
331
        auto aAdjustmentValuesRange = asNonConstRange(aAdjustmentValues);
247
331
        for ( std::vector<CustomShapeGuide>::size_type i = 0; i < maAdjustmentGuideList.size(); i++ )
248
0
        {
249
0
            EnhancedCustomShapeAdjustmentValue aAdjustmentVal;
250
0
            aAdjustmentVal.Value <<= maAdjustmentGuideList[ i ].maFormula.toInt32();
251
0
            aAdjustmentVal.State = PropertyState_DIRECT_VALUE;
252
0
            aAdjustmentVal.Name = maAdjustmentGuideList[ i ].maName;
253
0
            aAdjustmentValuesRange[i] = std::move(aAdjustmentVal);
254
0
        }
255
331
        aPropertyMap.setProperty( PROP_AdjustmentValues, aAdjustmentValues);
256
257
331
        PropertyMap aPath;
258
259
331
        aPath.setProperty( PROP_Segments, comphelper::containerToSequence(maSegments) );
260
261
331
        if ( maTextRect.has_value() ) {
262
319
            Sequence< EnhancedCustomShapeTextFrame > aTextFrames{
263
319
                { /* tl */ { maTextRect.value().l, maTextRect.value().t },
264
319
                  /* br */ { maTextRect.value().r, maTextRect.value().b } }
265
319
            };
266
319
            aPath.setProperty( PROP_TextFrames, aTextFrames);
267
319
        }
268
269
331
        if (!maConnectionSiteList.empty())
270
135
        {
271
135
            css::uno::Sequence<EnhancedCustomShapeParameterPair> seqGluePoints;
272
135
            seqGluePoints.realloc(maConnectionSiteList.size());
273
135
            sal_Int32 nId = 0;
274
135
            for (auto& rGluePoint : asNonConstRange(seqGluePoints))
275
687
            {
276
687
                rGluePoint.First.Value = maConnectionSiteList[nId].pos.First.Value;
277
687
                rGluePoint.First.Type = maConnectionSiteList[nId].pos.First.Type;
278
687
                rGluePoint.Second.Value = maConnectionSiteList[nId].pos.Second.Value;
279
687
                rGluePoint.Second.Type = maConnectionSiteList[nId].pos.Second.Type;
280
687
                nId++;
281
687
            }
282
135
            aPath.setProperty(PROP_GluePoints, seqGluePoints);
283
135
        }
284
285
331
        sal_uInt32 nParameterPairs = 0;
286
331
        for ( auto const & i: maPath2DList )
287
325
            nParameterPairs += i.parameter.size();
288
289
331
        Sequence< EnhancedCustomShapeParameterPair > aParameterPairs( nParameterPairs );
290
331
        auto aParameterPairsRange = asNonConstRange(aParameterPairs);
291
331
        sal_uInt32 k = 0;
292
331
        for ( auto const & i: maPath2DList )
293
325
            for ( auto const & j: i.parameter )
294
1.25k
                aParameterPairsRange[ k++ ] = j;
295
331
        aPath.setProperty( PROP_Coordinates, aParameterPairs);
296
297
331
        if ( !maPath2DList.empty() )
298
325
        {
299
325
            bool bAllZero = true;
300
325
            for ( auto const & i: maPath2DList )
301
325
            {
302
325
                if ( i.w || i.h ) {
303
241
                    bAllZero = false;
304
241
                    break;
305
241
                }
306
325
            }
307
308
325
            if ( !bAllZero ) {
309
241
                Sequence< awt::Size > aSubViewSize( maPath2DList.size() );
310
241
                std::transform(maPath2DList.begin(), maPath2DList.end(), aSubViewSize.getArray(),
311
241
                               [](const auto& p2d)
312
241
                               {
313
241
                                   SAL_INFO("oox.cscode",
314
241
                                            "set subpath; size: " << p2d.w << " x " << p2d.h);
315
241
                                   return awt::Size(p2d.w, p2d.h);
316
241
                               });
317
241
                aPath.setProperty( PROP_SubViewSize, aSubViewSize);
318
241
            }
319
325
        }
320
321
331
        Sequence< PropertyValue > aPathSequence = aPath.makePropertyValueSequence();
322
331
        aPropertyMap.setProperty( PROP_Path, aPathSequence);
323
324
331
        Sequence< OUString > aEquations( maGuideList.size() );
325
331
        std::transform(maGuideList.begin(), maGuideList.end(), aEquations.getArray(),
326
1.70k
                       [](const auto& g) { return g.maFormula; });
327
331
        aPropertyMap.setProperty( PROP_Equations, aEquations);
328
329
331
        Sequence< PropertyValues > aHandles( maAdjustHandleList.size() );
330
331
        auto aHandlesRange = asNonConstRange(aHandles);
331
331
        for ( std::vector<AdjustHandle>::size_type i = 0; i < maAdjustHandleList.size(); i++ )
332
0
        {
333
0
            PropertyMap aHandle;
334
            // maAdjustmentHandle[ i ].gdRef1 ... maAdjustmentHandle[ i ].gdRef2 ... :(
335
            // gdRef1 && gdRef2 -> we do not offer such reference, so it is difficult
336
            // to determine the correct adjustment handle that should be updated with the adjustment
337
            // position. here is the solution: the adjustment value that is used within the position
338
            // has to be updated, in case the position is a formula the first usage of a
339
            // adjustment value is decisive
340
0
            if ( maAdjustHandleList[ i ].polar )
341
0
            {
342
                // Polar handles in DrawingML
343
                // 1. don't have reference center, so PROP_Polar isn't needed.
344
                // 2. position always use planar coordinates.
345
                // 3. use RefAngle and RefR to specify adjustment value to be updated.
346
                // 4. The unit of angular adjustment values are 6000th degree.
347
348
0
                aHandle.setProperty( PROP_Position, maAdjustHandleList[ i ].pos);
349
0
                if ( maAdjustHandleList[ i ].gdRef1.has_value() )
350
0
                {
351
0
                    sal_Int32 nIndex = maAdjustmentGuideList.GetCustomShapeGuideValue( maAdjustHandleList[ i ].gdRef1.value() );
352
0
                    if ( nIndex >= 0 )
353
0
                        aHandle.setProperty( PROP_RefR, nIndex);
354
0
                }
355
0
                if ( maAdjustHandleList[ i ].gdRef2.has_value() )
356
0
                {
357
0
                    sal_Int32 nIndex = maAdjustmentGuideList.GetCustomShapeGuideValue( maAdjustHandleList[ i ].gdRef2.value() );
358
0
                    if ( nIndex >= 0 )
359
0
                        aHandle.setProperty( PROP_RefAngle, nIndex);
360
0
                }
361
0
                if ( maAdjustHandleList[ i ].min1.has_value() )
362
0
                    aHandle.setProperty( PROP_RadiusRangeMinimum, maAdjustHandleList[ i ].min1.value());
363
0
                if ( maAdjustHandleList[ i ].max1.has_value() )
364
0
                    aHandle.setProperty( PROP_RadiusRangeMaximum, maAdjustHandleList[ i ].max1.value());
365
366
                /* TODO: AngleMin & AngleMax
367
                if ( maAdjustHandleList[ i ].min2.has() )
368
                    aHandle.setProperty( PROP_ ] = maAdjustHandleList[ i ].min2.get());
369
                if ( maAdjustHandleList[ i ].max2.has() )
370
                    aHandle.setProperty( PROP_ ] = maAdjustHandleList[ i ].max2.get());
371
                */
372
0
            }
373
0
            else
374
0
            {
375
0
                aHandle.setProperty( PROP_Position, maAdjustHandleList[ i ].pos);
376
0
                if ( maAdjustHandleList[ i ].gdRef1.has_value() )
377
0
                {
378
                    // TODO: PROP_RefX and PROP_RefY are not yet part of our file format,
379
                    // so the handles will not work after save/reload
380
0
                    sal_Int32 nIndex = maAdjustmentGuideList.GetCustomShapeGuideValue( maAdjustHandleList[ i ].gdRef1.value() );
381
0
                    if ( nIndex >= 0 )
382
0
                        aHandle.setProperty( PROP_RefX, nIndex);
383
0
                }
384
0
                if ( maAdjustHandleList[ i ].gdRef2.has_value() )
385
0
                {
386
0
                    sal_Int32 nIndex = maAdjustmentGuideList.GetCustomShapeGuideValue( maAdjustHandleList[ i ].gdRef2.value() );
387
0
                    if ( nIndex >= 0 )
388
0
                        aHandle.setProperty( PROP_RefY, nIndex);
389
0
                }
390
0
                if ( maAdjustHandleList[ i ].min1.has_value() )
391
0
                    aHandle.setProperty( PROP_RangeXMinimum, maAdjustHandleList[ i ].min1.value());
392
0
                if ( maAdjustHandleList[ i ].max1.has_value() )
393
0
                    aHandle.setProperty( PROP_RangeXMaximum, maAdjustHandleList[ i ].max1.value());
394
0
                if ( maAdjustHandleList[ i ].min2.has_value() )
395
0
                    aHandle.setProperty( PROP_RangeYMinimum, maAdjustHandleList[ i ].min2.value());
396
0
                if ( maAdjustHandleList[ i ].max2.has_value() )
397
0
                    aHandle.setProperty( PROP_RangeYMaximum, maAdjustHandleList[ i ].max2.value());
398
0
            }
399
0
            aHandlesRange[ i ] = aHandle.makePropertyValueSequence();
400
0
        }
401
331
        aPropertyMap.setProperty( PROP_Handles, aHandles);
402
331
        if (!maExtrusionPropertyMap.empty())
403
0
        {
404
0
            Sequence< PropertyValue > aExtrusionSequence = maExtrusionPropertyMap.makePropertyValueSequence();
405
0
            aPropertyMap.setProperty( PROP_Extrusion, aExtrusionSequence);
406
0
        }
407
408
#if OSL_DEBUG_LEVEL >= 2
409
        // Note that the script oox/source/drawingml/customshapes/generatePresetsData.pl looks
410
        // for these ==cscode== and ==csdata== markers, so don't "clean up" these SAL_INFOs.
411
        SAL_INFO("oox.cscode", "==cscode== begin");
412
        aPropertyMap.dumpCode( aPropertyMap.makePropertySet() );
413
        SAL_INFO("oox.cscode", "==cscode== end");
414
        SAL_INFO("oox.csdata", "==csdata== begin");
415
        aPropertyMap.dumpData( aPropertyMap.makePropertySet() );
416
        SAL_INFO("oox.csdata", "==csdata== end");
417
#endif
418
        // converting the vector to a sequence
419
331
        Sequence< PropertyValue > aSeq = aPropertyMap.makePropertyValueSequence();
420
331
        PropertySet aPropSet( xPropSet );
421
331
        aPropSet.setProperty( PROP_CustomShapeGeometry, aSeq );
422
331
    }
423
19.5k
}
424
425
}
426
427
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */