Coverage Report

Created: 2025-12-31 10:39

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/oox/source/drawingml/transform2dcontext.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 <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
121k
: ContextHandler2( rParent )
43
121k
, mrShape( rShape )
44
121k
, mbtxXfrm ( btxXfrm )
45
121k
{
46
121k
    if( !btxXfrm )
47
121k
    {
48
121k
        mrShape.setRotation( rAttribs.getInteger( XML_rot, 0 ) ); // 60000ths of a degree Positive angles are clockwise; negative angles are counter-clockwise
49
121k
        mrShape.setFlip( rAttribs.getBool( XML_flipH, false ), rAttribs.getBool( XML_flipV, false ) );
50
121k
    }
51
279
    else
52
279
    {
53
279
        if (rAttribs.hasAttribute(XML_rot) && mrShape.getTextBody())
54
66
        {
55
66
            mno_txXfrmRot = rAttribs.getInteger(XML_rot, 0);
56
66
            sal_Int32 nTextAreaRot = mrShape.getTextBody()->getTextProperties().moTextAreaRotation.value_or(0);
57
66
            mrShape.getTextBody()->getTextProperties().moTextAreaRotation = mno_txXfrmRot.value() + nTextAreaRot;
58
66
        }
59
279
    }
60
121k
}
61
62
namespace
63
{
64
bool ConstructPresetTextRectangle(Shape& rShape, awt::Rectangle& rRect)
65
558
{
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
558
    const sal_Int32 nType = rShape.getCustomShapeProperties()->getShapePresetType();
70
558
    switch (nType)
71
558
    {
72
80
        case XML_ellipse:
73
            // The preset text rectangle touches the perimeter of the ellipse at 45deg.
74
80
            rRect.X = rShape.getPosition().X + rShape.getSize().Width * ((1.0 - M_SQRT1_2) / 2.0);
75
80
            rRect.Y = rShape.getPosition().Y + rShape.getSize().Height * ((1.0 - M_SQRT1_2) / 2.0);
76
80
            rRect.Width = rShape.getSize().Width * M_SQRT1_2;
77
80
            rRect.Height = rShape.getSize().Height * M_SQRT1_2;
78
80
            return true;
79
140
        case XML_roundRect:
80
140
        case XML_round2SameRect:
81
140
        {
82
            // Second handle of round2SameRect used in preset diagrams has value 0.
83
140
            const auto& aAdjGdList = rShape.getCustomShapeProperties()->getAdjustmentGuideList();
84
140
            double fAdj = aAdjGdList.empty() ? 16667 : aAdjGdList[0].maFormula.toDouble();
85
140
            sal_Int32 nWidth = rShape.getSize().Width;
86
140
            sal_Int32 nHeight = rShape.getSize().Height;
87
140
            if (nWidth == 0 || nHeight == 0)
88
0
                return false;
89
140
            double fMaxAdj = 50000.0 * nWidth / std::min(nWidth, nHeight);
90
140
            fAdj = std::clamp<double>(fAdj, 0, fMaxAdj);
91
140
            sal_Int32 nTextLeft = std::min(nWidth, nHeight) * fAdj / 100000.0 * 0.29289;
92
140
            sal_Int32 nTextTop = nTextLeft;
93
140
            rRect.X = rShape.getPosition().X + nTextLeft;
94
140
            rRect.Y = rShape.getPosition().Y + nTextTop;
95
140
            rRect.Width = nWidth - 2 * nTextLeft;
96
140
            rRect.Height = nHeight - (nType == XML_roundRect ? 2 : 1) * nTextTop;
97
140
            return true;
98
140
        }
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
142
        case XML_rect:
129
142
        case XML_wedgeRectCallout:
130
142
        {
131
            // When tdf#149918 is fixed, pie will need its own case
132
142
            rRect.X = rShape.getPosition().X;
133
142
            rRect.Y = rShape.getPosition().Y;
134
142
            rRect.Width = rShape.getSize().Width;
135
142
            rRect.Height = rShape.getSize().Height;
136
142
            return true;
137
142
        }
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
36
        case XML_rightArrow:
256
36
        {
257
            // The identifiers here reflect the guides name value in presetShapeDefinitions.xml
258
36
            sal_Int32 nWidth = rShape.getSize().Width;
259
36
            sal_Int32 nHeight = rShape.getSize().Height;
260
36
            if (nWidth == 0 || nHeight == 0)
261
0
                return false;
262
36
            double a1(50000.0);
263
36
            double a2(50000.0);
264
36
            const auto& aAdjGdList = rShape.getCustomShapeProperties()->getAdjustmentGuideList();
265
36
            if (aAdjGdList.size() == 2)
266
36
            {
267
36
                a1 = aAdjGdList[0].maFormula.toDouble();
268
36
                a2 = aAdjGdList[1].maFormula.toDouble();
269
36
                a1 = std::clamp<double>(a1, 0, 100000);
270
36
            }
271
36
            double maxAdj2 = 100000.0 * nWidth / std::min(nWidth, nHeight);
272
36
            a2 = std::clamp<double>(a2, 0, maxAdj2);
273
36
            double dx1 = std::min(nWidth, nHeight) * a2 / 100000.0;
274
36
            double x1 = nWidth - dx1;
275
36
            double dy1 = nHeight * a1 / 200000.0;
276
36
            double y1 = nHeight / 2.0 - dy1; // top
277
36
            double y2 = nHeight / 2.0 + dy1; // bottom
278
36
            double dx2 = y1 * dx1 / (nHeight / 2.0);
279
36
            double x2 = x1 + dx2; // right
280
36
            rRect.X = rShape.getPosition().X; // left = 0
281
36
            rRect.Y = rShape.getPosition().Y + y1;
282
36
            rRect.Width = x2;
283
36
            rRect.Height = y2 - y1;
284
36
            return true;
285
36
        }
286
160
        default:
287
160
            return false;
288
558
    }
289
558
}
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
294k
{
299
294k
    if (mbtxXfrm)
300
558
    {
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
558
        awt::Rectangle aPresetTextRectangle;
307
558
        if (!ConstructPresetTextRectangle(mrShape, aPresetTextRectangle))
308
160
            return nullptr; // faulty shape or text area calculation not implemented
309
310
398
        switch (aElementToken)
311
398
        {
312
199
            case A_TOKEN(off):
313
199
            {
314
                // need <a:ext> too, so only save values here.
315
199
                const OUString sXValue = rAttribs.getStringDefaulted(XML_x);
316
199
                const OUString sYValue = rAttribs.getStringDefaulted(XML_y);
317
199
                if (!sXValue.isEmpty() && !sYValue.isEmpty())
318
199
                {
319
199
                    mno_txXfrmOffX = sXValue.toInt32();
320
199
                    mno_txXfrmOffY = sYValue.toInt32();
321
199
                }
322
199
            }
323
199
            break;
324
199
            case A_TOKEN(ext):
325
199
            {
326
                // Build text frame from txXfrm element
327
199
                awt::Rectangle aUnrotatedTxXfrm = aPresetTextRectangle; // dummy initialize
328
199
                const OUString sCXValue = rAttribs.getStringDefaulted(XML_cx);
329
199
                const OUString sCYValue = rAttribs.getStringDefaulted(XML_cy);
330
199
                if (!sCXValue.isEmpty() && !sCYValue.isEmpty())
331
199
                {
332
199
                    aUnrotatedTxXfrm.Width = sCXValue.toInt32();
333
199
                    aUnrotatedTxXfrm.Height = sCYValue.toInt32();
334
199
                }
335
199
                if (mno_txXfrmOffX.has_value() && mno_txXfrmOffY.has_value())
336
199
                {
337
199
                    aUnrotatedTxXfrm.X = mno_txXfrmOffX.value();
338
199
                    aUnrotatedTxXfrm.Y = mno_txXfrmOffY.value();
339
199
                }
340
341
                // Has the txXfrm an own rotation beyond compensation of the shape rotation?
342
                // Happens e.g. in diagram type 'Detailed Process'.
343
199
                sal_Int32 nAngleDiff
344
199
                    = (mrShape.getRotation() + mno_txXfrmRot.value_or(0)) % 21600000;
345
199
                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
199
                if(mrShape.getTextBody())
365
199
                {
366
                    // Calculate indent offsets
367
199
                    sal_Int32 nOffsetLeft = aUnrotatedTxXfrm.X - aPresetTextRectangle.X;
368
199
                    sal_Int32 nOffsetTop = aUnrotatedTxXfrm.Y - aPresetTextRectangle.Y;
369
199
                    sal_Int32 nOffsetRight
370
199
                        = aPresetTextRectangle.Width - aUnrotatedTxXfrm.Width - nOffsetLeft;
371
199
                    sal_Int32 nOffsetBottom
372
199
                        = aPresetTextRectangle.Height - aUnrotatedTxXfrm.Height - nOffsetTop;
373
374
199
                    if (nOffsetLeft)
375
120
                        mrShape.getTextBody()->getTextProperties().moTextOffLeft
376
120
                            = GetCoordinate(nOffsetLeft);
377
199
                    if (nOffsetTop)
378
134
                        mrShape.getTextBody()->getTextProperties().moTextOffUpper
379
134
                            = GetCoordinate(nOffsetTop);
380
199
                    if (nOffsetRight)
381
130
                        mrShape.getTextBody()->getTextProperties().moTextOffRight
382
130
                            = GetCoordinate(nOffsetRight);
383
199
                    if (nOffsetBottom)
384
134
                        mrShape.getTextBody()->getTextProperties().moTextOffLower
385
134
                            = GetCoordinate(nOffsetBottom);
386
199
                }
387
199
            }
388
199
            break;
389
398
        }
390
398
        return nullptr;
391
398
    } // end of case mbtxXfrm
392
393
293k
    switch( aElementToken )
394
293k
    {
395
121k
    case A_TOKEN( off ):        // horz/vert translation
396
121k
        mrShape.setPosition( awt::Point( rAttribs.getInteger( XML_x, 0 ), rAttribs.getInteger( XML_y, 0 ) ) );
397
121k
        break;
398
121k
    case A_TOKEN( ext ):        // horz/vert size
399
121k
        mrShape.setSize( awt::Size( rAttribs.getInteger( XML_cx, 0 ), rAttribs.getInteger( XML_cy, 0 ) ) );
400
121k
        break;
401
25.2k
    case A_TOKEN( chOff ):  // horz/vert translation of children
402
25.2k
        mrShape.setChildPosition( awt::Point( rAttribs.getInteger( XML_x, 0 ), rAttribs.getInteger( XML_y, 0 ) ) );
403
25.2k
        break;
404
25.2k
    case A_TOKEN( chExt ):  // horz/vert size of children
405
25.2k
        {
406
25.2k
            sal_Int32 nChExtCx = rAttribs.getInteger(XML_cx, 0);
407
408
25.2k
            if(nChExtCx == 0)
409
25.1k
                nChExtCx = mrShape.getSize().Width;
410
411
25.2k
            sal_Int32 nChExtCy = rAttribs.getInteger(XML_cy, 0);
412
413
25.2k
            if(nChExtCy == 0)
414
25.1k
                nChExtCy = mrShape.getSize().Height;
415
416
25.2k
            mrShape.setChildSize(awt::Size(nChExtCx, nChExtCy));
417
25.2k
        }
418
25.2k
        break;
419
293k
    }
420
421
293k
    return nullptr;
422
293k
}
423
424
} // namespace oox::drawingml
425
426
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */