Coverage Report

Created: 2025-07-07 10:01

/src/libreoffice/oox/source/drawingml/transform2dcontext.cxx
Line
Count
Source (jump to first uncovered line)
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 <cmath>
21
22
#include <drawingml/transform2dcontext.hxx>
23
24
#include <basegfx/matrix/b2dhommatrixtools.hxx>
25
#include <basegfx/numeric/ftools.hxx>
26
#include <basegfx/point/b2dpoint.hxx>
27
#include <drawingml/customshapeproperties.hxx>
28
#include <drawingml/textbody.hxx>
29
#include <oox/drawingml/shape.hxx>
30
#include <oox/helper/attributelist.hxx>
31
#include <oox/token/namespaces.hxx>
32
33
#include <com/sun/star/awt/Rectangle.hpp>
34
35
using namespace ::com::sun::star;
36
using ::oox::core::ContextHandlerRef;
37
38
namespace oox::drawingml {
39
40
/** context to import a CT_Transform2D */
41
Transform2DContext::Transform2DContext( ContextHandler2Helper const & rParent, const AttributeList& rAttribs, Shape& rShape, bool btxXfrm )
42
133k
: ContextHandler2( rParent )
43
133k
, mrShape( rShape )
44
133k
, mbtxXfrm ( btxXfrm )
45
133k
{
46
133k
    if( !btxXfrm )
47
133k
    {
48
133k
        mrShape.setRotation( rAttribs.getInteger( XML_rot, 0 ) ); // 60000ths of a degree Positive angles are clockwise; negative angles are counter-clockwise
49
133k
        mrShape.setFlip( rAttribs.getBool( XML_flipH, false ), rAttribs.getBool( XML_flipV, false ) );
50
133k
    }
51
294
    else
52
294
    {
53
294
        if (rAttribs.hasAttribute(XML_rot) && mrShape.getTextBody())
54
65
        {
55
65
            mno_txXfrmRot = rAttribs.getInteger(XML_rot, 0);
56
65
            sal_Int32 nTextAreaRot = mrShape.getTextBody()->getTextProperties().moTextAreaRotation.value_or(0);
57
65
            mrShape.getTextBody()->getTextProperties().moTextAreaRotation = mno_txXfrmRot.value() + nTextAreaRot;
58
65
        }
59
294
    }
60
133k
}
61
62
namespace
63
{
64
bool ConstructPresetTextRectangle(Shape& rShape, awt::Rectangle& rRect)
65
588
{
66
    // When we are here, we have neither xShape nor a SdrObject. So need to manually calc the text
67
    // area rectangle defined in the preset in OOXML standard, but only for those types of shapes
68
    // where we know, that MS Office SmartArt presets do not use the default text area rectangle.
69
588
    const sal_Int32 nType = rShape.getCustomShapeProperties()->getShapePresetType();
70
588
    switch (nType)
71
588
    {
72
60
        case XML_ellipse:
73
            // The preset text rectangle touches the perimeter of the ellipse at 45deg.
74
60
            rRect.X = rShape.getPosition().X + rShape.getSize().Width * ((1.0 - M_SQRT1_2) / 2.0);
75
60
            rRect.Y = rShape.getPosition().Y + rShape.getSize().Height * ((1.0 - M_SQRT1_2) / 2.0);
76
60
            rRect.Width = rShape.getSize().Width * M_SQRT1_2;
77
60
            rRect.Height = rShape.getSize().Height * M_SQRT1_2;
78
60
            return true;
79
126
        case XML_roundRect:
80
126
        case XML_round2SameRect:
81
126
        {
82
            // Second handle of round2SameRect used in preset diagrams has value 0.
83
126
            const auto& aAdjGdList = rShape.getCustomShapeProperties()->getAdjustmentGuideList();
84
126
            double fAdj = aAdjGdList.empty() ? 16667 : aAdjGdList[0].maFormula.toDouble();
85
126
            sal_Int32 nWidth = rShape.getSize().Width;
86
126
            sal_Int32 nHeight = rShape.getSize().Height;
87
126
            if (nWidth == 0 || nHeight == 0)
88
0
                return false;
89
126
            double fMaxAdj = 50000.0 * nWidth / std::min(nWidth, nHeight);
90
126
            fAdj = std::clamp<double>(fAdj, 0, fMaxAdj);
91
126
            sal_Int32 nTextLeft = std::min(nWidth, nHeight) * fAdj / 100000.0 * 0.29289;
92
126
            sal_Int32 nTextTop = nTextLeft;
93
126
            rRect.X = rShape.getPosition().X + nTextLeft;
94
126
            rRect.Y = rShape.getPosition().Y + nTextTop;
95
126
            rRect.Width = nWidth - 2 * nTextLeft;
96
126
            rRect.Height = nHeight - (nType == XML_roundRect ? 2 : 1) * nTextTop;
97
126
            return true;
98
126
        }
99
0
        case XML_trapezoid:
100
0
        {
101
0
            const auto& aAdjGdList = rShape.getCustomShapeProperties()->getAdjustmentGuideList();
102
0
            double fAdj = aAdjGdList.empty() ? 25000 : aAdjGdList[0].maFormula.toDouble();
103
0
            sal_Int32 nWidth = rShape.getSize().Width;
104
0
            sal_Int32 nHeight = rShape.getSize().Height;
105
0
            if (nWidth == 0 || nHeight == 0)
106
0
                return false;
107
0
            double fMaxAdj = 50000.0 * nWidth / std::min(nWidth, nHeight);
108
0
            fAdj = std::clamp<double>(fAdj, 0, fMaxAdj);
109
0
            sal_Int32 nTextLeft = nWidth / 3.0 * fAdj / fMaxAdj;
110
0
            sal_Int32 nTextTop = nHeight / 3.0 * fAdj / fMaxAdj;
111
0
            rRect.X = rShape.getPosition().X + nTextLeft;
112
0
            rRect.Y = rShape.getPosition().Y + nTextTop;
113
0
            rRect.Width = nWidth - 2 * nTextLeft;
114
0
            rRect.Height = nHeight - 2 * nTextTop;
115
0
            return true;
116
0
        }
117
0
        case XML_flowChartManualOperation:
118
0
        {
119
0
            sal_Int32 nWidth = rShape.getSize().Width;
120
0
            sal_Int32 nTextLeft = nWidth / 5;
121
0
            rRect.X = rShape.getPosition().X + nTextLeft;
122
0
            rRect.Y = rShape.getPosition().Y;
123
0
            rRect.Width = nWidth - 2 * nTextLeft;
124
0
            rRect.Height = rShape.getSize().Height;
125
0
            return true;
126
0
        }
127
12
        case XML_pie:
128
232
        case XML_rect:
129
232
        case XML_wedgeRectCallout:
130
232
        {
131
            // When tdf#149918 is fixed, pie will need its own case
132
232
            rRect.X = rShape.getPosition().X;
133
232
            rRect.Y = rShape.getPosition().Y;
134
232
            rRect.Width = rShape.getSize().Width;
135
232
            rRect.Height = rShape.getSize().Height;
136
232
            return true;
137
232
        }
138
0
        case XML_upArrowCallout:
139
0
        case XML_downArrowCallout:
140
0
        {
141
            // The identifiers here reflect the guides name value in presetShapeDefinitions.xml
142
0
            sal_Int32 nWidth = rShape.getSize().Width;
143
0
            sal_Int32 nHeight = rShape.getSize().Height;
144
0
            if (nWidth == 0 || nHeight == 0)
145
0
                return false;
146
            // double adj1 = 25000.0;
147
            // double adj2 = 25000.0;
148
0
            double adj3 = 25000.0; // height of arrow head
149
0
            double adj4 = 64977.0; // height of arrow shaft
150
0
            const auto& aAdjGdList = rShape.getCustomShapeProperties()->getAdjustmentGuideList();
151
0
            if (aAdjGdList.size() == 4)
152
0
            {
153
                // adj1 = aAdjGdList[0].maFormula.toDouble();
154
                // adj2 = aAdjGdList[1].maFormula.toDouble();
155
0
                adj3 = aAdjGdList[2].maFormula.toDouble();
156
0
                adj4 = aAdjGdList[3].maFormula.toDouble();
157
0
            }
158
159
0
            double maxAdj3 = 100000.0 * nHeight / std::min(nWidth, nHeight);
160
0
            adj3 = std::clamp<double>(adj3, 0, maxAdj3);
161
0
            double q2 = adj3 * std::min(nWidth, nHeight) / nHeight;
162
0
            double maxAdj4 = 100000.0 - q2;
163
0
            adj4 = std::clamp<double>(adj4, 0, maxAdj4);
164
165
0
            rRect.X = rShape.getPosition().X;
166
0
            rRect.Y = rShape.getPosition().Y;
167
0
            rRect.Width = rShape.getSize().Width;
168
0
            rRect.Height = nHeight * adj4 / 100000.0;
169
0
            return true;
170
0
        }
171
0
        case XML_gear6:
172
0
        {
173
            // The identifiers here reflect the guides name value in presetShapeDefinitions.xml
174
0
            double w = rShape.getSize().Width;
175
0
            double h = rShape.getSize().Height;
176
0
            if (w <= 0 || h <= 0)
177
0
                return false;
178
0
            double a1(15000.0);
179
0
            double a2(3526.0);
180
0
            const auto& aAdjGdList = rShape.getCustomShapeProperties()->getAdjustmentGuideList();
181
0
            if (aAdjGdList.size() == 2)
182
0
            {
183
0
                a1 = aAdjGdList[0].maFormula.toDouble();
184
0
                a2 = aAdjGdList[1].maFormula.toDouble();
185
0
                a1 = std::clamp<double>(a1, 0, 20000);
186
0
                a2 = std::clamp<double>(a2, 0, 5358);
187
0
            }
188
0
            double th = std::min(w, h) * a1 / 100000.0;
189
0
            double l2 = std::min(w, h) * a2 / 100000.0 / 2.0;
190
0
            double l3 = th / 2.0 + l2;
191
192
0
            double rh = h / 2.0 - th;
193
0
            double rw = w / 2.0 - th;
194
195
0
            double maxr = std::min(rw, rh);
196
0
            double ha = atan2(l3, maxr);
197
198
0
            double aA1 = basegfx::deg2rad(330) - ha;
199
0
            double ta11 = rw * cos(aA1);
200
0
            double ta12 = rh * sin(aA1);
201
0
            double bA1 = atan2(ta12, ta11);
202
0
            double cta1 = rh * cos(bA1);
203
0
            double sta1 = rw * sin(bA1);
204
0
            double ma1 = std::hypot(cta1, sta1);
205
0
            double na1 = rw * rh / ma1;
206
0
            double dxa1 = na1 * cos(bA1);
207
0
            double dya1 = na1 * sin(bA1);
208
209
0
            double xA1 = w / 2.0 + dxa1; // r
210
0
            double yA1 = h / 2.0 + dya1; // t
211
0
            double yD2 = h - yA1; // b
212
0
            double xD5 = w - xA1; // l
213
214
0
            rRect.X = rShape.getPosition().X + xD5;
215
0
            rRect.Y = rShape.getPosition().Y + yA1;
216
0
            rRect.Width = xA1 - xD5;
217
0
            rRect.Height = yD2 - yA1;
218
0
            return true;
219
0
        }
220
0
        case XML_hexagon:
221
0
        {
222
0
            const auto& aAdjGdList = rShape.getCustomShapeProperties()->getAdjustmentGuideList();
223
0
            double fAdj = aAdjGdList.empty() ? 25000 : aAdjGdList[0].maFormula.toDouble();
224
0
            sal_Int32 nWidth = rShape.getSize().Width;
225
0
            sal_Int32 nHeight = rShape.getSize().Height;
226
0
            if (nWidth == 0 || nHeight == 0)
227
0
                return false;
228
0
            double fMaxAdj = 50000.0 * nWidth / std::min(nWidth, nHeight);
229
0
            fAdj = std::clamp<double>(fAdj, 0, fMaxAdj);
230
0
            double fFactor = fAdj / fMaxAdj / 6.0 + 1.0 / 12.0;
231
0
            sal_Int32 nTextLeft = nWidth * fFactor;
232
0
            sal_Int32 nTextTop = nHeight * fFactor;
233
0
            rRect.X = rShape.getPosition().X + nTextLeft;
234
0
            rRect.Y = rShape.getPosition().Y + nTextTop;
235
0
            rRect.Width = nWidth - 2 * nTextLeft;
236
0
            rRect.Height = nHeight - 2 * nTextTop;
237
0
            return true;
238
0
        }
239
0
        case XML_round1Rect:
240
0
        {
241
0
            sal_Int32 nWidth = rShape.getSize().Width;
242
0
            sal_Int32 nHeight = rShape.getSize().Height;
243
0
            if (nWidth == 0 || nHeight == 0)
244
0
                return false;
245
0
            const auto& aAdjGdList = rShape.getCustomShapeProperties()->getAdjustmentGuideList();
246
0
            double fAdj = aAdjGdList.empty() ? 16667.0 : aAdjGdList[0].maFormula.toDouble();
247
0
            fAdj = std::clamp<double>(fAdj, 0.0, 50000.0);
248
0
            double fDx = std::min(nWidth, nHeight) * fAdj / 100000.0 * 0.29289;
249
0
            rRect.X = rShape.getPosition().X;
250
0
            rRect.Y = rShape.getPosition().Y;
251
0
            rRect.Width = nWidth - fDx;
252
0
            rRect.Height = nHeight;
253
0
            return true;
254
0
        }
255
28
        case XML_rightArrow:
256
28
        {
257
            // The identifiers here reflect the guides name value in presetShapeDefinitions.xml
258
28
            sal_Int32 nWidth = rShape.getSize().Width;
259
28
            sal_Int32 nHeight = rShape.getSize().Height;
260
28
            if (nWidth == 0 || nHeight == 0)
261
0
                return false;
262
28
            double a1(50000.0);
263
28
            double a2(50000.0);
264
28
            const auto& aAdjGdList = rShape.getCustomShapeProperties()->getAdjustmentGuideList();
265
28
            if (aAdjGdList.size() == 2)
266
28
            {
267
28
                a1 = aAdjGdList[0].maFormula.toDouble();
268
28
                a2 = aAdjGdList[1].maFormula.toDouble();
269
28
                a1 = std::clamp<double>(a1, 0, 100000);
270
28
            }
271
28
            double maxAdj2 = 100000.0 * nWidth / std::min(nWidth, nHeight);
272
28
            a2 = std::clamp<double>(a2, 0, maxAdj2);
273
28
            double dx1 = std::min(nWidth, nHeight) * a2 / 100000.0;
274
28
            double x1 = nWidth - dx1;
275
28
            double dy1 = nHeight * a1 / 200000.0;
276
28
            double y1 = nHeight / 2.0 - dy1; // top
277
28
            double y2 = nHeight / 2.0 + dy1; // bottom
278
28
            double dx2 = y1 * dx1 / (nHeight / 2.0);
279
28
            double x2 = x1 + dx2; // right
280
28
            rRect.X = rShape.getPosition().X; // left = 0
281
28
            rRect.Y = rShape.getPosition().Y + y1;
282
28
            rRect.Width = x2;
283
28
            rRect.Height = y2 - y1;
284
28
            return true;
285
28
        }
286
142
        default:
287
142
            return false;
288
588
    }
289
588
}
290
291
basegfx::B2DPoint getCenter(const awt::Rectangle& rRect)
292
8
{
293
8
    return basegfx::B2DPoint(rRect.X + rRect.Width / 2.0, rRect.Y + rRect.Height / 2.0);
294
8
}
295
} // end namespace
296
297
ContextHandlerRef Transform2DContext::onCreateContext( sal_Int32 aElementToken, const AttributeList& rAttribs )
298
324k
{
299
324k
    if (mbtxXfrm)
300
588
    {
301
        // The child elements <a:off> and <a:ext> of a <dsp:txXfrm> element describe the position and
302
        // size of the text area rectangle. We cannot change the text area rectangle directly, because
303
        // currently we depend on the geometry definition of the preset. As workaround we change the
304
        // indents to move and scale the text block. The needed shifts are calculated here as moTextOff
305
        // and used in TextBodyProperties::pushTextDistances().
306
588
        awt::Rectangle aPresetTextRectangle;
307
588
        if (!ConstructPresetTextRectangle(mrShape, aPresetTextRectangle))
308
142
            return nullptr; // faulty shape or text area calculation not implemented
309
310
446
        switch (aElementToken)
311
446
        {
312
223
            case A_TOKEN(off):
313
223
            {
314
                // need <a:ext> too, so only save values here.
315
223
                const OUString sXValue = rAttribs.getStringDefaulted(XML_x);
316
223
                const OUString sYValue = rAttribs.getStringDefaulted(XML_y);
317
223
                if (!sXValue.isEmpty() && !sYValue.isEmpty())
318
223
                {
319
223
                    mno_txXfrmOffX = sXValue.toInt32();
320
223
                    mno_txXfrmOffY = sYValue.toInt32();
321
223
                }
322
223
            }
323
223
            break;
324
223
            case A_TOKEN(ext):
325
223
            {
326
                // Build text frame from txXfrm element
327
223
                awt::Rectangle aUnrotatedTxXfrm = aPresetTextRectangle; // dummy initialize
328
223
                const OUString sCXValue = rAttribs.getStringDefaulted(XML_cx);
329
223
                const OUString sCYValue = rAttribs.getStringDefaulted(XML_cy);
330
223
                if (!sCXValue.isEmpty() && !sCYValue.isEmpty())
331
223
                {
332
223
                    aUnrotatedTxXfrm.Width = sCXValue.toInt32();
333
223
                    aUnrotatedTxXfrm.Height = sCYValue.toInt32();
334
223
                }
335
223
                if (mno_txXfrmOffX.has_value() && mno_txXfrmOffY.has_value())
336
223
                {
337
223
                    aUnrotatedTxXfrm.X = mno_txXfrmOffX.value();
338
223
                    aUnrotatedTxXfrm.Y = mno_txXfrmOffY.value();
339
223
                }
340
341
                // Has the txXfrm an own rotation beyond compensation of the shape rotation?
342
                // Happens e.g. in diagram type 'Detailed Process'.
343
223
                sal_Int32 nAngleDiff
344
223
                    = (mrShape.getRotation() + mno_txXfrmRot.value_or(0)) % 21600000;
345
223
                if (nAngleDiff != 0)
346
4
                {
347
                    // Rectangle aUnrotatedTxXfrm rotates around its center not around text area
348
                    // center from preset. We shift aUnrotatedTxXfrm so that it is at the original
349
                    // position after rotation of text area rectangle from preset.
350
4
                    basegfx::B2DPoint aXfrmCenter(getCenter(aUnrotatedTxXfrm));
351
4
                    basegfx::B2DPoint aPresetCenter(getCenter(aPresetTextRectangle));
352
353
4
                    if (!aXfrmCenter.equal(aPresetCenter))
354
4
                    {
355
4
                        double fAngleRad = basegfx::deg2rad(nAngleDiff / 60000.0);
356
4
                        basegfx::B2DHomMatrix aRotMatrix(
357
4
                            basegfx::utils::createRotateAroundPoint(aPresetCenter, -fAngleRad));
358
4
                        basegfx::B2DPoint aNewCenter(aRotMatrix * aXfrmCenter);
359
4
                        aUnrotatedTxXfrm.X += aNewCenter.getX() - aXfrmCenter.getX();
360
4
                        aUnrotatedTxXfrm.Y += aNewCenter.getY() - aXfrmCenter.getY();
361
4
                    }
362
4
                }
363
364
223
                if(mrShape.getTextBody())
365
223
                {
366
                    // Calculate indent offsets
367
223
                    sal_Int32 nOffsetLeft = aUnrotatedTxXfrm.X - aPresetTextRectangle.X;
368
223
                    sal_Int32 nOffsetTop = aUnrotatedTxXfrm.Y - aPresetTextRectangle.Y;
369
223
                    sal_Int32 nOffsetRight
370
223
                        = aPresetTextRectangle.Width - aUnrotatedTxXfrm.Width - nOffsetLeft;
371
223
                    sal_Int32 nOffsetBottom
372
223
                        = aPresetTextRectangle.Height - aUnrotatedTxXfrm.Height - nOffsetTop;
373
374
223
                    if (nOffsetLeft)
375
103
                        mrShape.getTextBody()->getTextProperties().moTextOffLeft
376
103
                            = GetCoordinate(nOffsetLeft);
377
223
                    if (nOffsetTop)
378
113
                        mrShape.getTextBody()->getTextProperties().moTextOffUpper
379
113
                            = GetCoordinate(nOffsetTop);
380
223
                    if (nOffsetRight)
381
109
                        mrShape.getTextBody()->getTextProperties().moTextOffRight
382
109
                            = GetCoordinate(nOffsetRight);
383
223
                    if (nOffsetBottom)
384
113
                        mrShape.getTextBody()->getTextProperties().moTextOffLower
385
113
                            = GetCoordinate(nOffsetBottom);
386
223
                }
387
223
            }
388
223
            break;
389
446
        }
390
446
        return nullptr;
391
446
    } // end of case mbtxXfrm
392
393
323k
    switch( aElementToken )
394
323k
    {
395
133k
    case A_TOKEN( off ):        // horz/vert translation
396
133k
        mrShape.setPosition( awt::Point( rAttribs.getInteger( XML_x, 0 ), rAttribs.getInteger( XML_y, 0 ) ) );
397
133k
        break;
398
133k
    case A_TOKEN( ext ):        // horz/vert size
399
133k
        mrShape.setSize( awt::Size( rAttribs.getInteger( XML_cx, 0 ), rAttribs.getInteger( XML_cy, 0 ) ) );
400
133k
        break;
401
28.4k
    case A_TOKEN( chOff ):  // horz/vert translation of children
402
28.4k
        mrShape.setChildPosition( awt::Point( rAttribs.getInteger( XML_x, 0 ), rAttribs.getInteger( XML_y, 0 ) ) );
403
28.4k
        break;
404
28.4k
    case A_TOKEN( chExt ):  // horz/vert size of children
405
28.4k
        {
406
28.4k
            sal_Int32 nChExtCx = rAttribs.getInteger(XML_cx, 0);
407
408
28.4k
            if(nChExtCx == 0)
409
28.2k
                nChExtCx = mrShape.getSize().Width;
410
411
28.4k
            sal_Int32 nChExtCy = rAttribs.getInteger(XML_cy, 0);
412
413
28.4k
            if(nChExtCy == 0)
414
28.2k
                nChExtCy = mrShape.getSize().Height;
415
416
28.4k
            mrShape.setChildSize(awt::Size(nChExtCx, nChExtCy));
417
28.4k
        }
418
28.4k
        break;
419
323k
    }
420
421
323k
    return nullptr;
422
323k
}
423
424
} // namespace oox::drawingml
425
426
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */