Coverage Report

Created: 2026-02-14 09:37

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/svx/source/xoutdev/_xpoly.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 <algorithm>
23
24
#include <tools/debug.hxx>
25
#include <tools/poly.hxx>
26
#include <tools/helpers.hxx>
27
#include <tools/gen.hxx>
28
29
#include <svx/xpoly.hxx>
30
#include <xpolyimp.hxx>
31
#include <basegfx/polygon/b2dpolygon.hxx>
32
#include <basegfx/polygon/b2dpolygontools.hxx>
33
#include <basegfx/range/b2drange.hxx>
34
35
36
ImpXPolygon::ImpXPolygon(sal_uInt16 nInitSize, sal_uInt16 _nResize)
37
112k
    : pOldPointAry(nullptr)
38
112k
    , bDeleteOldPoints(false)
39
112k
    , nSize(0)
40
112k
    , nResize(_nResize)
41
112k
    , nPoints(0)
42
112k
{
43
112k
    Resize(nInitSize);
44
112k
}
45
46
ImpXPolygon::ImpXPolygon( const ImpXPolygon& rImpXPoly )
47
112k
    : pOldPointAry(nullptr)
48
112k
    , bDeleteOldPoints(false)
49
112k
    , nSize(0)
50
112k
    , nResize(rImpXPoly.nResize)
51
112k
    , nPoints(0)
52
112k
{
53
112k
    rImpXPoly.CheckPointDelete();
54
55
112k
    Resize( rImpXPoly.nSize );
56
57
    // copy
58
112k
    nPoints = rImpXPoly.nPoints;
59
112k
    memcpy( pPointAry.get(), rImpXPoly.pPointAry.get(), nSize*sizeof( Point ) );
60
112k
    memcpy( pFlagAry.get(), rImpXPoly.pFlagAry.get(), nSize );
61
112k
}
62
63
ImpXPolygon::~ImpXPolygon()
64
225k
{
65
225k
    pPointAry.reset();
66
225k
    if ( bDeleteOldPoints )
67
0
    {
68
0
        delete[] pOldPointAry;
69
0
        pOldPointAry = nullptr;
70
0
    }
71
225k
}
72
73
bool ImpXPolygon::operator==(const ImpXPolygon& rImpXPoly) const
74
0
{
75
0
    return nPoints==rImpXPoly.nPoints &&
76
0
           (nPoints==0 ||
77
0
            (memcmp(pPointAry.get(), rImpXPoly.pPointAry.get(), nPoints*sizeof(Point))==0 &&
78
0
             memcmp(pFlagAry.get(), rImpXPoly.pFlagAry.get(), nPoints)==0));
79
0
}
80
81
/** Change polygon size
82
 *
83
 * @param nNewSize      the new size of the polygon
84
 * @param bDeletePoints if FALSE, do not delete the point array directly but
85
 *                      wait for the next call before doing so. This prevents
86
 *                      errors with XPoly[n] = XPoly[0] where a resize might
87
 *                      destroy the right side point array too early.
88
 */
89
void ImpXPolygon::Resize( sal_uInt16 nNewSize, bool bDeletePoints )
90
225k
{
91
225k
    if( nNewSize == nSize )
92
0
        return;
93
94
225k
    PolyFlags*  pOldFlagAry  = pFlagAry.release();
95
225k
    sal_uInt16  nOldSize     = nSize;
96
97
225k
    CheckPointDelete();
98
225k
    pOldPointAry = pPointAry.release();
99
100
    // Round the new size to a multiple of nResize, if
101
    // the object was not newly created (nSize != 0)
102
225k
    if ( nSize != 0 && nNewSize > nSize )
103
0
    {
104
0
        DBG_ASSERT(nResize, "Trying to resize but nResize = 0 !");
105
0
        nNewSize = nSize + ((nNewSize-nSize-1) / nResize + 1) * nResize;
106
0
    }
107
    // create point array
108
225k
    nSize     = nNewSize;
109
225k
    pPointAry.reset( new Point[ nSize ] );
110
111
    // create flag array
112
225k
    pFlagAry.reset( new PolyFlags[ nSize ] );
113
225k
    memset( pFlagAry.get(), 0, nSize );
114
115
    // copy if needed
116
225k
    if (nOldSize)
117
0
    {
118
0
        if( nOldSize < nSize )
119
0
        {
120
0
            memcpy( pPointAry.get(), pOldPointAry, nOldSize*sizeof( Point ) );
121
0
            memcpy( pFlagAry.get(),  pOldFlagAry, nOldSize );
122
0
        }
123
0
        else
124
0
        {
125
0
            memcpy( pPointAry.get(), pOldPointAry, nSize*sizeof( Point ) );
126
0
            memcpy( pFlagAry.get(), pOldFlagAry, nSize );
127
128
            // adjust number of valid points
129
0
            if( nPoints > nSize )
130
0
                nPoints = nSize;
131
0
        }
132
0
    }
133
225k
    if ( bDeletePoints )
134
225k
    {
135
225k
        delete[] pOldPointAry;
136
225k
        pOldPointAry = nullptr;
137
225k
    }
138
0
    else
139
0
        bDeleteOldPoints = true;
140
225k
    delete[] pOldFlagAry;
141
225k
}
142
143
void ImpXPolygon::InsertSpace( sal_uInt16 nPos, sal_uInt16 nCount )
144
197k
{
145
197k
    CheckPointDelete();
146
147
197k
    if ( nPos > nPoints )
148
0
        nPos = nPoints;
149
150
    // if the polygon is too small then enlarge it
151
197k
    if( (nPoints + nCount) > nSize )
152
0
        Resize( nPoints + nCount );
153
154
    // If the insert is not at the last position, move everything after backwards
155
197k
    if( nPos < nPoints )
156
0
    {
157
0
        sal_uInt16 nMove = nPoints - nPos;
158
0
        memmove( &pPointAry[nPos+nCount], &pPointAry[nPos],
159
0
                 nMove * sizeof(Point) );
160
0
        memmove( &pFlagAry[nPos+nCount], &pFlagAry[nPos], nMove );
161
0
    }
162
197k
    std::fill(pPointAry.get() + nPos, pPointAry.get() + nPos + nCount, Point());
163
197k
    memset( &pFlagAry [nPos], 0, nCount );
164
165
197k
    nPoints = nPoints + nCount;
166
197k
}
167
168
void ImpXPolygon::Remove( sal_uInt16 nPos, sal_uInt16 nCount )
169
33.8k
{
170
33.8k
    CheckPointDelete();
171
172
33.8k
    if( (nPos + nCount) > nPoints )
173
0
        return;
174
175
33.8k
    sal_uInt16 nMove = nPoints - nPos - nCount;
176
177
33.8k
    if( nMove )
178
12
    {
179
12
        memmove( &pPointAry[nPos], &pPointAry[nPos+nCount],
180
12
                 nMove * sizeof(Point) );
181
12
        memmove( &pFlagAry[nPos], &pFlagAry[nPos+nCount], nMove );
182
12
    }
183
33.8k
    std::fill(pPointAry.get() + (nPoints - nCount), pPointAry.get() + nPoints, Point());
184
33.8k
    memset( &pFlagAry [nPoints - nCount], 0, nCount );
185
33.8k
    nPoints = nPoints - nCount;
186
33.8k
}
187
188
void ImpXPolygon::CheckPointDelete() const
189
2.42M
{
190
2.42M
    if ( bDeleteOldPoints )
191
0
    {
192
0
        delete[] pOldPointAry;
193
0
        const_cast< ImpXPolygon* >(this)->pOldPointAry = nullptr;
194
0
        const_cast< ImpXPolygon* >(this)->bDeleteOldPoints = false;
195
0
    }
196
2.42M
}
197
198
XPolygon::XPolygon( sal_uInt16 nSize )
199
104k
    : m_pImpXPolygon( ImpXPolygon( nSize, 16 ) )
200
104k
{
201
104k
}
202
203
58
XPolygon::XPolygon( const XPolygon& ) = default;
204
205
9.69k
XPolygon::XPolygon( XPolygon&& ) = default;
206
207
/// create a XPolygon out of a standard polygon
208
XPolygon::XPolygon( const tools::Polygon& rPoly )
209
0
    : m_pImpXPolygon( rPoly.GetSize() )
210
0
{
211
0
    sal_uInt16 nSize = rPoly.GetSize();
212
0
    m_pImpXPolygon->nPoints = nSize;
213
214
0
    for( sal_uInt16 i = 0; i < nSize;  i++ )
215
0
    {
216
0
        m_pImpXPolygon->pPointAry[i] = rPoly[i];
217
0
        m_pImpXPolygon->pFlagAry[i] = rPoly.GetFlags( i );
218
0
    }
219
0
}
220
221
/// create a rectangle (also with rounded corners) as a Bézier polygon
222
XPolygon::XPolygon(const tools::Rectangle& rRect, tools::Long nRx, tools::Long nRy)
223
15
    : m_pImpXPolygon( 17 )
224
15
{
225
15
    tools::Long nWh = (rRect.GetWidth()  - 1) / 2;
226
15
    tools::Long nHh = (rRect.GetHeight() - 1) / 2;
227
228
15
    if ( nRx > nWh )    nRx = nWh;
229
15
    if ( nRy > nHh )    nRy = nHh;
230
231
    // negate Rx => circle clockwise
232
15
    nRx = -nRx;
233
234
    // factor for control points of the Bézier curve: 8/3 * (sin(45g) - 0.5)
235
15
    tools::Long    nXHdl = static_cast<tools::Long>(0.552284749 * nRx);
236
15
    tools::Long    nYHdl = static_cast<tools::Long>(0.552284749 * nRy);
237
15
    sal_uInt16  nPos = 0;
238
239
15
    if ( nRx && nRy )
240
0
    {
241
0
        Point aCenter;
242
243
0
        for (sal_uInt16 nQuad = 0; nQuad < 4; nQuad++)
244
0
        {
245
0
            switch ( nQuad )
246
0
            {
247
0
                case 0: aCenter = rRect.TopLeft();
248
0
                        aCenter.AdjustX( -nRx );
249
0
                        aCenter.AdjustY(nRy );
250
0
                        break;
251
0
                case 1: aCenter = rRect.TopRight();
252
0
                        aCenter.AdjustX(nRx );
253
0
                        aCenter.AdjustY(nRy );
254
0
                        break;
255
0
                case 2: aCenter = rRect.BottomRight();
256
0
                        aCenter.AdjustX(nRx );
257
0
                        aCenter.AdjustY( -nRy );
258
0
                        break;
259
0
                case 3: aCenter = rRect.BottomLeft();
260
0
                        aCenter.AdjustX( -nRx );
261
0
                        aCenter.AdjustY( -nRy );
262
0
                        break;
263
0
            }
264
0
            GenBezArc(aCenter, nRx, nRy, nXHdl, nYHdl, 0_deg100, 9000_deg100, nQuad, nPos);
265
0
            m_pImpXPolygon->pFlagAry[nPos  ] = PolyFlags::Smooth;
266
0
            m_pImpXPolygon->pFlagAry[nPos+3] = PolyFlags::Smooth;
267
0
            nPos += 4;
268
0
        }
269
0
    }
270
15
    else
271
15
    {
272
15
        m_pImpXPolygon->pPointAry[nPos++] = rRect.TopLeft();
273
15
        m_pImpXPolygon->pPointAry[nPos++] = rRect.TopRight();
274
15
        m_pImpXPolygon->pPointAry[nPos++] = rRect.BottomRight();
275
15
        m_pImpXPolygon->pPointAry[nPos++] = rRect.BottomLeft();
276
15
    }
277
15
    m_pImpXPolygon->pPointAry[nPos] = m_pImpXPolygon->pPointAry[0];
278
15
    m_pImpXPolygon->nPoints = nPos + 1;
279
15
}
280
281
/// create an ellipse (curve) as Bézier polygon
282
XPolygon::XPolygon(const Point& rCenter, tools::Long nRx, tools::Long nRy,
283
                   Degree100 nStartAngle, Degree100 nEndAngle, bool bClose)
284
336
    : m_pImpXPolygon( 17 )
285
336
{
286
336
    nStartAngle %= 36000_deg100;
287
336
    if ( nEndAngle > 36000_deg100 ) nEndAngle %= 36000_deg100;
288
336
    bool bFull = (nStartAngle == 0_deg100 && nEndAngle == 36000_deg100);
289
290
    // factor for control points of the Bézier curve: 8/3 * (sin(45g) - 0.5)
291
336
    tools::Long    nXHdl = static_cast<tools::Long>(0.552284749 * nRx);
292
336
    tools::Long    nYHdl = static_cast<tools::Long>(0.552284749 * nRy);
293
336
    sal_uInt16  nPos = 0;
294
336
    bool    bLoopEnd = false;
295
296
336
    do
297
1.34k
    {
298
1.34k
        Degree100 nA1, nA2;
299
1.34k
        sal_uInt16 nQuad = nStartAngle.get() / 9000;
300
1.34k
        if ( nQuad == 4 ) nQuad = 0;
301
1.34k
        bLoopEnd = CheckAngles(nStartAngle, nEndAngle, nA1, nA2);
302
1.34k
        GenBezArc(rCenter, nRx, nRy, nXHdl, nYHdl, nA1, nA2, nQuad, nPos);
303
1.34k
        nPos += 3;
304
1.34k
        if ( !bLoopEnd )
305
1.00k
            m_pImpXPolygon->pFlagAry[nPos] = PolyFlags::Smooth;
306
307
1.34k
    } while ( !bLoopEnd );
308
309
    // if not a full circle then connect edges with center point if necessary
310
336
    if ( !bFull && bClose )
311
0
        m_pImpXPolygon->pPointAry[++nPos] = rCenter;
312
313
336
    if ( bFull )
314
336
    {
315
336
        m_pImpXPolygon->pFlagAry[0   ] = PolyFlags::Smooth;
316
336
        m_pImpXPolygon->pFlagAry[nPos] = PolyFlags::Smooth;
317
336
    }
318
336
    m_pImpXPolygon->nPoints = nPos + 1;
319
336
}
320
321
122k
XPolygon::~XPolygon() = default;
322
323
void XPolygon::SetPointCount( sal_uInt16 nPoints )
324
0
{
325
0
    std::as_const(m_pImpXPolygon)->CheckPointDelete();
326
327
0
    if( m_pImpXPolygon->nSize < nPoints )
328
0
        m_pImpXPolygon->Resize( nPoints );
329
330
0
    if ( nPoints < m_pImpXPolygon->nPoints )
331
0
    {
332
0
        sal_uInt16 nSize = m_pImpXPolygon->nPoints - nPoints;
333
0
        std::fill(
334
0
            m_pImpXPolygon->pPointAry.get() + nPoints, m_pImpXPolygon->pPointAry.get() + nPoints + nSize, Point());
335
0
        memset( &m_pImpXPolygon->pFlagAry [nPoints], 0, nSize );
336
0
    }
337
0
    m_pImpXPolygon->nPoints = nPoints;
338
0
}
339
340
sal_uInt16 XPolygon::GetSize() const
341
0
{
342
0
    m_pImpXPolygon->CheckPointDelete();
343
0
    return m_pImpXPolygon->nSize;
344
0
}
345
346
sal_uInt16 XPolygon::GetPointCount() const
347
193k
{
348
193k
    m_pImpXPolygon->CheckPointDelete();
349
193k
    return m_pImpXPolygon->nPoints;
350
193k
}
351
352
void XPolygon::Insert( sal_uInt16 nPos, const Point& rPt, PolyFlags eFlags )
353
197k
{
354
197k
    if (nPos>m_pImpXPolygon->nPoints) nPos=m_pImpXPolygon->nPoints;
355
197k
    m_pImpXPolygon->InsertSpace( nPos, 1 );
356
197k
    m_pImpXPolygon->pPointAry[nPos] = rPt;
357
197k
    m_pImpXPolygon->pFlagAry[nPos]  = eFlags;
358
197k
}
359
360
void XPolygon::Insert( sal_uInt16 nPos, const XPolygon& rXPoly )
361
0
{
362
0
    if (nPos>m_pImpXPolygon->nPoints) nPos=m_pImpXPolygon->nPoints;
363
364
0
    sal_uInt16 nPoints = rXPoly.GetPointCount();
365
366
0
    m_pImpXPolygon->InsertSpace( nPos, nPoints );
367
368
0
    memcpy( &(m_pImpXPolygon->pPointAry[nPos]),
369
0
            rXPoly.m_pImpXPolygon->pPointAry.get(),
370
0
            nPoints*sizeof( Point ) );
371
0
    memcpy( &(m_pImpXPolygon->pFlagAry[nPos]),
372
0
            rXPoly.m_pImpXPolygon->pFlagAry.get(),
373
0
            nPoints );
374
0
}
375
376
void XPolygon::Remove( sal_uInt16 nPos, sal_uInt16 nCount )
377
33.8k
{
378
33.8k
    m_pImpXPolygon->Remove( nPos, nCount );
379
33.8k
}
380
381
void XPolygon::Move( tools::Long nHorzMove, tools::Long nVertMove )
382
2.18k
{
383
2.18k
    if ( !nHorzMove && !nVertMove )
384
1.08k
        return;
385
386
    // move points
387
1.10k
    sal_uInt16 nCount = m_pImpXPolygon->nPoints;
388
904k
    for ( sal_uInt16 i = 0; i < nCount; i++ )
389
903k
    {
390
903k
        Point* pPt = &(m_pImpXPolygon->pPointAry[i]);
391
903k
        pPt->AdjustX( nHorzMove );
392
903k
        pPt->AdjustY( nVertMove );
393
903k
    }
394
1.10k
}
395
396
tools::Rectangle XPolygon::GetBoundRect() const
397
13.2k
{
398
13.2k
    m_pImpXPolygon->CheckPointDelete();
399
13.2k
    tools::Rectangle aRetval;
400
401
13.2k
    if(m_pImpXPolygon->nPoints)
402
11.7k
    {
403
        // #i37709#
404
        // For historical reasons the control points are not part of the
405
        // BoundRect. This makes it necessary to subdivide the polygon to
406
        // get a relatively correct BoundRect. Numerically, this is not
407
        // correct and never was.
408
409
11.7k
        const basegfx::B2DRange aPolygonRange(getB2DPolygon().getB2DRange());
410
11.7k
        aRetval = tools::Rectangle(basegfx::fround<tools::Long>(aPolygonRange.getMinX()),
411
11.7k
                                   basegfx::fround<tools::Long>(aPolygonRange.getMinY()),
412
11.7k
                                   basegfx::fround<tools::Long>(aPolygonRange.getMaxX()),
413
11.7k
                                   basegfx::fround<tools::Long>(aPolygonRange.getMaxY()));
414
11.7k
    }
415
416
13.2k
    return aRetval;
417
13.2k
}
418
419
const Point& XPolygon::operator[]( sal_uInt16 nPos ) const
420
7.41k
{
421
7.41k
    DBG_ASSERT(nPos < m_pImpXPolygon->nPoints, "Invalid index at const array access to XPolygon");
422
423
7.41k
    m_pImpXPolygon->CheckPointDelete();
424
7.41k
    return m_pImpXPolygon->pPointAry[nPos];
425
7.41k
}
426
427
Point& XPolygon::operator[]( sal_uInt16 nPos )
428
1.63M
{
429
1.63M
    std::as_const(m_pImpXPolygon)->CheckPointDelete();
430
431
1.63M
    if( nPos >= m_pImpXPolygon->nSize )
432
0
    {
433
0
        DBG_ASSERT(m_pImpXPolygon->nResize, "Invalid index at array access to XPolygon");
434
0
        m_pImpXPolygon->Resize(nPos + 1, false);
435
0
    }
436
1.63M
    if( nPos >= m_pImpXPolygon->nPoints )
437
59.2k
        m_pImpXPolygon->nPoints = nPos + 1;
438
439
1.63M
    return m_pImpXPolygon->pPointAry[nPos];
440
1.63M
}
441
442
0
XPolygon& XPolygon::operator=( const XPolygon& ) = default;
443
444
16.1k
XPolygon& XPolygon::operator=( XPolygon&& ) = default;
445
446
bool XPolygon::operator==( const XPolygon& rXPoly ) const
447
0
{
448
0
    m_pImpXPolygon->CheckPointDelete();
449
0
    return rXPoly.m_pImpXPolygon == m_pImpXPolygon;
450
0
}
451
452
/// get the flags for the point at the given position
453
PolyFlags XPolygon::GetFlags( sal_uInt16 nPos ) const
454
60
{
455
60
    m_pImpXPolygon->CheckPointDelete();
456
60
    return m_pImpXPolygon->pFlagAry[nPos];
457
60
}
458
459
/// set the flags for the point at the given position
460
void XPolygon::SetFlags( sal_uInt16 nPos, PolyFlags eFlags )
461
2.74k
{
462
2.74k
    std::as_const(m_pImpXPolygon)->CheckPointDelete();
463
2.74k
    m_pImpXPolygon->pFlagAry[nPos] = eFlags;
464
2.74k
}
465
466
/// short path to read the CONTROL flag directly (TODO: better explain what the sense behind this flag is!)
467
bool XPolygon::IsControl(sal_uInt16 nPos) const
468
0
{
469
0
    return m_pImpXPolygon->pFlagAry[nPos] == PolyFlags::Control;
470
0
}
471
472
/// short path to read the SMOOTH and SYMMTR flag directly (TODO: better explain what the sense behind these flags is!)
473
bool XPolygon::IsSmooth(sal_uInt16 nPos) const
474
0
{
475
0
    PolyFlags eFlag = m_pImpXPolygon->pFlagAry[nPos];
476
0
    return ( eFlag == PolyFlags::Smooth || eFlag == PolyFlags::Symmetric );
477
0
}
478
479
/** calculate the euclidean distance between two points
480
 *
481
 * @param nP1 The first point
482
 * @param nP2 The second point
483
 */
484
double XPolygon::CalcDistance(sal_uInt16 nP1, sal_uInt16 nP2)
485
0
{
486
0
    const Point& rP1 = m_pImpXPolygon->pPointAry[nP1];
487
0
    const Point& rP2 = m_pImpXPolygon->pPointAry[nP2];
488
0
    double fDx = rP2.X() - rP1.X();
489
0
    double fDy = rP2.Y() - rP1.Y();
490
0
    return std::hypot(fDx, fDy);
491
0
}
492
493
void XPolygon::SubdivideBezier(sal_uInt16 nPos, bool bCalcFirst, double fT)
494
0
{
495
0
    Point*  pPoints = m_pImpXPolygon->pPointAry.get();
496
0
    double  fT2 = fT * fT;
497
0
    double  fT3 = fT * fT2;
498
0
    double  fU = 1.0 - fT;
499
0
    double  fU2 = fU * fU;
500
0
    double  fU3 = fU * fU2;
501
0
    sal_uInt16  nIdx = nPos;
502
0
    short   nPosInc, nIdxInc;
503
504
0
    if ( bCalcFirst )
505
0
    {
506
0
        nPos += 3;
507
0
        nPosInc = -1;
508
0
        nIdxInc = 0;
509
0
    }
510
0
    else
511
0
    {
512
0
        nPosInc = 1;
513
0
        nIdxInc = 1;
514
0
    }
515
0
    pPoints[nPos].setX( static_cast<tools::Long>(fU3 *       pPoints[nIdx  ].X() +
516
0
                                fT  * fU2 * pPoints[nIdx+1].X() * 3 +
517
0
                                fT2 * fU  * pPoints[nIdx+2].X() * 3 +
518
0
                                fT3 *       pPoints[nIdx+3].X()) );
519
0
    pPoints[nPos].setY( static_cast<tools::Long>(fU3 *       pPoints[nIdx  ].Y() +
520
0
                                fT  * fU2 * pPoints[nIdx+1].Y() * 3 +
521
0
                                fT2 * fU  * pPoints[nIdx+2].Y() * 3 +
522
0
                                fT3 *       pPoints[nIdx+3].Y()) );
523
0
    nPos = nPos + nPosInc;
524
0
    nIdx = nIdx + nIdxInc;
525
0
    pPoints[nPos].setX( static_cast<tools::Long>(fU2 *       pPoints[nIdx  ].X() +
526
0
                                fT  * fU *  pPoints[nIdx+1].X() * 2 +
527
0
                                fT2 *       pPoints[nIdx+2].X()) );
528
0
    pPoints[nPos].setY( static_cast<tools::Long>(fU2 *       pPoints[nIdx  ].Y() +
529
0
                                fT  * fU *  pPoints[nIdx+1].Y() * 2 +
530
0
                                fT2 *       pPoints[nIdx+2].Y()) );
531
0
    nPos = nPos + nPosInc;
532
0
    nIdx = nIdx + nIdxInc;
533
0
    pPoints[nPos].setX( static_cast<tools::Long>(fU * pPoints[nIdx  ].X() +
534
0
                                fT * pPoints[nIdx+1].X()) );
535
0
    pPoints[nPos].setY( static_cast<tools::Long>(fU * pPoints[nIdx  ].Y() +
536
0
                                fT * pPoints[nIdx+1].Y()) );
537
0
}
538
539
/// Generate a Bézier arc
540
void XPolygon::GenBezArc(const Point& rCenter, tools::Long nRx, tools::Long nRy,
541
                         tools::Long nXHdl, tools::Long nYHdl, Degree100 nStart, Degree100 nEnd,
542
                         sal_uInt16 nQuad, sal_uInt16 nFirst)
543
1.34k
{
544
1.34k
    Point* pPoints = m_pImpXPolygon->pPointAry.get();
545
1.34k
    pPoints[nFirst  ] = rCenter;
546
1.34k
    pPoints[nFirst+3] = rCenter;
547
548
1.34k
    if ( nQuad == 1 || nQuad == 2 )
549
672
    {
550
672
        nRx   = -nRx; nXHdl = -nXHdl;
551
672
    }
552
1.34k
    if ( nQuad == 0 || nQuad == 1 )
553
672
    {
554
672
        nRy   = -nRy; nYHdl = -nYHdl;
555
672
    }
556
557
1.34k
    if ( nQuad == 0 || nQuad == 2 )
558
672
    {
559
672
        pPoints[nFirst].AdjustX( nRx );
560
672
        pPoints[nFirst+3].AdjustY( nRy );
561
672
    }
562
672
    else
563
672
    {
564
672
        pPoints[nFirst].AdjustY( nRy );
565
672
        pPoints[nFirst+3].AdjustX( nRx );
566
672
    }
567
1.34k
    pPoints[nFirst+1] = pPoints[nFirst];
568
1.34k
    pPoints[nFirst+2] = pPoints[nFirst+3];
569
570
1.34k
    if ( nQuad == 0 || nQuad == 2 )
571
672
    {
572
672
        pPoints[nFirst+1].AdjustY( nYHdl );
573
672
        pPoints[nFirst+2].AdjustX( nXHdl );
574
672
    }
575
672
    else
576
672
    {
577
672
        pPoints[nFirst+1].AdjustX( nXHdl );
578
672
        pPoints[nFirst+2].AdjustY( nYHdl );
579
672
    }
580
1.34k
    if ( nStart > 0_deg100 )
581
0
        SubdivideBezier(nFirst, false, static_cast<double>(nStart.get()) / 9000);
582
1.34k
    if ( nEnd < 9000_deg100 )
583
0
        SubdivideBezier(nFirst, true, static_cast<double>((nEnd-nStart).get()) / (9000_deg100-nStart).get());
584
1.34k
    SetFlags(nFirst+1, PolyFlags::Control);
585
1.34k
    SetFlags(nFirst+2, PolyFlags::Control);
586
1.34k
}
587
588
bool XPolygon::CheckAngles(Degree100& nStart, Degree100 nEnd, Degree100& nA1, Degree100& nA2)
589
1.34k
{
590
1.34k
    if ( nStart == 36000_deg100 ) nStart = 0_deg100;
591
1.34k
    if ( nEnd == 0_deg100 ) nEnd = 36000_deg100;
592
1.34k
    Degree100 nStPrev = nStart;
593
1.34k
    Degree100 nMax((nStart.get() / 9000 + 1) * 9000);
594
1.34k
    Degree100 nMin = nMax - 9000_deg100;
595
596
1.34k
    if ( nEnd >= nMax || nEnd <= nStart )   nA2 = 9000_deg100;
597
0
    else                                    nA2 = nEnd - nMin;
598
1.34k
    nA1 = nStart - nMin;
599
1.34k
    nStart = nMax;
600
601
    // returns true when the last segment was calculated
602
1.34k
    return (nStPrev < nEnd && nStart >= nEnd);
603
1.34k
}
604
605
/** Calculate a smooth transition to connect two Bézier curves
606
 *
607
 * This is done by projecting the corresponding point onto a line between
608
 * two other points.
609
 *
610
 * @param nCenter The point at the end or beginning of the curve.
611
 *                If nCenter is at the end of the polygon the point is moved
612
 *                to the opposite side.
613
 * @param nDrag The moved point that specifies the relocation.
614
 * @param nPnt The point to modify.
615
 */
616
void XPolygon::CalcSmoothJoin(sal_uInt16 nCenter, sal_uInt16 nDrag, sal_uInt16 nPnt)
617
0
{
618
    // If nPoint is no control point, i.e. cannot be moved, then
619
    // move nDrag instead on the line between nCenter and nPnt
620
0
    if ( !IsControl(nPnt) )
621
0
        std::swap( nDrag, nPnt );
622
0
    Point*  pPoints = m_pImpXPolygon->pPointAry.get();
623
0
    Point   aDiff   = pPoints[nDrag] - pPoints[nCenter];
624
0
    double  fDiv    = CalcDistance(nCenter, nDrag);
625
626
0
    if ( fDiv )
627
0
    {
628
0
        double fRatio = CalcDistance(nCenter, nPnt) / fDiv;
629
        // keep the length if SMOOTH
630
0
        if ( GetFlags(nCenter) == PolyFlags::Smooth || !IsControl(nDrag) )
631
0
        {
632
0
            aDiff.setX( static_cast<tools::Long>(fRatio * aDiff.X()) );
633
0
            aDiff.setY( static_cast<tools::Long>(fRatio * aDiff.Y()) );
634
0
        }
635
0
        pPoints[nPnt] = pPoints[nCenter] - aDiff;
636
0
    }
637
0
}
638
639
/** Calculate tangent between two Bézier curves
640
 *
641
 * @param nCenter start or end point of the curves
642
 * @param nPrev previous reference point
643
 * @param nNext next reference point
644
 */
645
void XPolygon::CalcTangent(sal_uInt16 nCenter, sal_uInt16 nPrev, sal_uInt16 nNext)
646
0
{
647
0
    double fAbsLen = CalcDistance(nNext, nPrev);
648
649
0
    if ( !fAbsLen )
650
0
        return;
651
652
0
    const Point& rCenter = m_pImpXPolygon->pPointAry[nCenter];
653
0
    Point&  rNext = m_pImpXPolygon->pPointAry[nNext];
654
0
    Point&  rPrev = m_pImpXPolygon->pPointAry[nPrev];
655
0
    Point   aDiff = rNext - rPrev;
656
0
    double  fNextLen = CalcDistance(nCenter, nNext) / fAbsLen;
657
0
    double  fPrevLen = CalcDistance(nCenter, nPrev) / fAbsLen;
658
659
    // same length for both sides if SYMMTR
660
0
    if ( GetFlags(nCenter) == PolyFlags::Symmetric )
661
0
    {
662
0
        fPrevLen = (fNextLen + fPrevLen) / 2;
663
0
        fNextLen = fPrevLen;
664
0
    }
665
0
    rNext.setX( rCenter.X() + static_cast<tools::Long>(fNextLen * aDiff.X()) );
666
0
    rNext.setY( rCenter.Y() + static_cast<tools::Long>(fNextLen * aDiff.Y()) );
667
0
    rPrev.setX( rCenter.X() - static_cast<tools::Long>(fPrevLen * aDiff.X()) );
668
0
    rPrev.setY( rCenter.Y() - static_cast<tools::Long>(fPrevLen * aDiff.Y()) );
669
0
}
670
671
/// convert four polygon points into a Bézier curve
672
void XPolygon::PointsToBezier(sal_uInt16 nFirst)
673
0
{
674
0
    double  nFullLength, nPart1Length, nPart2Length;
675
0
    double  fX0, fY0, fX1, fY1, fX2, fY2, fX3, fY3;
676
0
    double  fTx1, fTx2, fTy1, fTy2;
677
0
    double  fT1, fU1, fT2, fU2, fV;
678
0
    Point*  pPoints = m_pImpXPolygon->pPointAry.get();
679
680
0
    if ( nFirst > m_pImpXPolygon->nPoints - 4 || IsControl(nFirst) ||
681
0
         IsControl(nFirst+1) || IsControl(nFirst+2) || IsControl(nFirst+3) )
682
0
        return;
683
684
0
    fTx1 = pPoints[nFirst+1].X();
685
0
    fTy1 = pPoints[nFirst+1].Y();
686
0
    fTx2 = pPoints[nFirst+2].X();
687
0
    fTy2 = pPoints[nFirst+2].Y();
688
0
    fX0  = pPoints[nFirst  ].X();
689
0
    fY0  = pPoints[nFirst  ].Y();
690
0
    fX3  = pPoints[nFirst+3].X();
691
0
    fY3  = pPoints[nFirst+3].Y();
692
693
0
    nPart1Length = CalcDistance(nFirst, nFirst+1);
694
0
    nPart2Length = nPart1Length + CalcDistance(nFirst+1, nFirst+2);
695
0
    nFullLength  = nPart2Length + CalcDistance(nFirst+2, nFirst+3);
696
0
    if ( nFullLength < 20 )
697
0
        return;
698
699
0
    if ( nPart2Length == nFullLength )
700
0
        nPart2Length -= 1;
701
0
    if ( nPart1Length == nFullLength )
702
0
        nPart1Length = nPart2Length - 1;
703
0
    if ( nPart1Length <= 0 )
704
0
        nPart1Length = 1;
705
0
    if ( nPart2Length <= 0 || nPart2Length == nPart1Length )
706
0
        nPart2Length = nPart1Length + 1;
707
708
0
    fT1 = nPart1Length / nFullLength;
709
0
    fU1 = 1.0 - fT1;
710
0
    fT2 = nPart2Length / nFullLength;
711
0
    fU2 = 1.0 - fT2;
712
0
    fV = 3 * (1.0 - (fT1 * fU2) / (fT2 * fU1));
713
714
0
    fX1 = fTx1 / (fT1 * fU1 * fU1) - fTx2 * fT1 / (fT2 * fT2 * fU1 * fU2);
715
0
    fX1 /= fV;
716
0
    fX1 -= fX0 * ( fU1 / fT1 + fU2 / fT2) / 3;
717
0
    fX1 += fX3 * ( fT1 * fT2 / (fU1 * fU2)) / 3;
718
719
0
    fY1 = fTy1 / (fT1 * fU1 * fU1) - fTy2 * fT1 / (fT2 * fT2 * fU1 * fU2);
720
0
    fY1 /= fV;
721
0
    fY1 -= fY0 * ( fU1 / fT1 + fU2 / fT2) / 3;
722
0
    fY1 += fY3 * ( fT1 * fT2 / (fU1 * fU2)) / 3;
723
724
0
    fX2 = fTx2 / (fT2 * fT2 * fU2 * 3) - fX0 * fU2 * fU2 / ( fT2 * fT2 * 3);
725
0
    fX2 -= fX1 * fU2 / fT2;
726
0
    fX2 -= fX3 * fT2 / (fU2 * 3);
727
728
0
    fY2 = fTy2 / (fT2 * fT2 * fU2 * 3) - fY0 * fU2 * fU2 / ( fT2 * fT2 * 3);
729
0
    fY2 -= fY1 * fU2 / fT2;
730
0
    fY2 -= fY3 * fT2 / (fU2 * 3);
731
732
0
    Point aControlPoint1(static_cast<tools::Long>(fX1), static_cast<tools::Long>(fY1));
733
0
    Point aControlPoint2(static_cast<tools::Long>(fX2), static_cast<tools::Long>(fY2));
734
735
0
    auto fPointOffset1 = std::hypot(aControlPoint1.X() - pPoints[nFirst + 1].X(), aControlPoint1.Y() - pPoints[nFirst + 1].Y());
736
0
    auto fPointOffset2 = std::hypot(aControlPoint2.X() - pPoints[nFirst + 2].X(), aControlPoint2.Y() - pPoints[nFirst + 2].Y());
737
738
    // To prevent the curve from overshooting due to sharp direction changes in the given sequence of points,
739
    // compare the control point offsets against the full segment length.
740
    // Apply the calculated ControlPoints only if their offsets are within a reasonable range.
741
0
    if( fPointOffset1 < nFullLength && fPointOffset2 < nFullLength )
742
0
    {
743
0
        pPoints[nFirst + 1] = aControlPoint1;
744
0
        pPoints[nFirst + 2] = aControlPoint2;
745
0
    }
746
747
0
    SetFlags(nFirst+1, PolyFlags::Control);
748
0
    SetFlags(nFirst+2, PolyFlags::Control);
749
0
}
750
751
/// scale in X- and/or Y-direction
752
void XPolygon::Scale(double fSx, double fSy)
753
0
{
754
0
    std::as_const(m_pImpXPolygon)->CheckPointDelete();
755
756
0
    sal_uInt16 nPntCnt = m_pImpXPolygon->nPoints;
757
758
0
    for (sal_uInt16 i = 0; i < nPntCnt; i++)
759
0
    {
760
0
        Point& rPnt = m_pImpXPolygon->pPointAry[i];
761
0
        rPnt.setX( static_cast<tools::Long>(fSx * rPnt.X()) );
762
0
        rPnt.setY( static_cast<tools::Long>(fSy * rPnt.Y()) );
763
0
    }
764
0
}
765
766
/**
767
 * Distort a polygon by scaling its coordinates relative to a reference
768
 * rectangle into an arbitrary rectangle.
769
 *
770
 * Mapping between polygon corners and reference rectangle:
771
 *     0: top left     0----1
772
 *     1: top right    |    |
773
 *     2: bottom right 3----2
774
 *     3: bottom left
775
 */
776
void XPolygon::Distort(const tools::Rectangle& rRefRect,
777
                       const XPolygon& rDistortedRect)
778
0
{
779
0
    std::as_const(m_pImpXPolygon)->CheckPointDelete();
780
781
0
    tools::Long    Xr, Wr;
782
0
    tools::Long    Yr, Hr;
783
784
0
    Xr = rRefRect.Left();
785
0
    Yr = rRefRect.Top();
786
0
    Wr = rRefRect.GetWidth();
787
0
    Hr = rRefRect.GetHeight();
788
789
0
    if ( !Wr || !Hr )
790
0
        return;
791
792
0
    tools::Long    X1, X2, X3, X4;
793
0
    tools::Long    Y1, Y2, Y3, Y4;
794
0
    DBG_ASSERT(rDistortedRect.m_pImpXPolygon->nPoints >= 4,
795
0
               "Distort: rectangle too small");
796
797
0
    X1 = rDistortedRect[0].X();
798
0
    Y1 = rDistortedRect[0].Y();
799
0
    X2 = rDistortedRect[1].X();
800
0
    Y2 = rDistortedRect[1].Y();
801
0
    X3 = rDistortedRect[3].X();
802
0
    Y3 = rDistortedRect[3].Y();
803
0
    X4 = rDistortedRect[2].X();
804
0
    Y4 = rDistortedRect[2].Y();
805
806
0
    sal_uInt16 nPntCnt = m_pImpXPolygon->nPoints;
807
808
0
    for (sal_uInt16 i = 0; i < nPntCnt; i++)
809
0
    {
810
0
        double  fTx, fTy, fUx, fUy;
811
0
        Point& rPnt = m_pImpXPolygon->pPointAry[i];
812
813
0
        fTx = static_cast<double>(rPnt.X() - Xr) / Wr;
814
0
        fTy = static_cast<double>(rPnt.Y() - Yr) / Hr;
815
0
        fUx = 1.0 - fTx;
816
0
        fUy = 1.0 - fTy;
817
818
0
        rPnt.setX( static_cast<tools::Long>( fUy * (fUx * X1 + fTx * X2) +
819
0
                            fTy * (fUx * X3 + fTx * X4) ) );
820
0
        rPnt.setY( static_cast<tools::Long>( fUx * (fUy * Y1 + fTy * Y3) +
821
0
                            fTx * (fUy * Y2 + fTy * Y4) ) );
822
0
    }
823
0
}
824
825
basegfx::B2DPolygon XPolygon::getB2DPolygon() const
826
12.1k
{
827
    // #i74631# use tools Polygon class for conversion to not have the code doubled
828
    // here. This needs one more conversion but avoids different converters in
829
    // the long run
830
12.1k
    const tools::Polygon aSource(GetPointCount(), m_pImpXPolygon->pPointAry.get(), m_pImpXPolygon->pFlagAry.get());
831
832
12.1k
    return aSource.getB2DPolygon();
833
12.1k
}
834
835
XPolygon::XPolygon(const basegfx::B2DPolygon& rPolygon)
836
8.18k
    : m_pImpXPolygon( tools::Polygon( rPolygon ).GetSize() )
837
8.18k
{
838
    // #i74631# use tools Polygon class for conversion to not have the code doubled
839
    // here. This needs one more conversion but avoids different converters in
840
    // the long run
841
842
8.18k
    const tools::Polygon aSource(rPolygon);
843
8.18k
    sal_uInt16 nSize = aSource.GetSize();
844
8.18k
    m_pImpXPolygon->nPoints = nSize;
845
846
982k
    for( sal_uInt16 i = 0; i < nSize;  i++ )
847
974k
    {
848
974k
        m_pImpXPolygon->pPointAry[i] = aSource[i];
849
974k
        m_pImpXPolygon->pFlagAry[i] = aSource.GetFlags( i );
850
974k
    }
851
8.18k
}
852
853
// XPolyPolygon
854
3
XPolyPolygon::XPolyPolygon() = default;
855
856
0
XPolyPolygon::XPolyPolygon( const XPolyPolygon& ) = default;
857
858
0
XPolyPolygon::XPolyPolygon( XPolyPolygon&& ) = default;
859
860
XPolyPolygon::XPolyPolygon(const basegfx::B2DPolyPolygon& rPolyPolygon)
861
0
{
862
0
    for(auto const& rCandidate : rPolyPolygon)
863
0
    {
864
0
        Insert(XPolygon(rCandidate));
865
0
    }
866
0
}
867
868
3
XPolyPolygon::~XPolyPolygon() = default;
869
870
void XPolyPolygon::Insert( XPolygon&& rXPoly )
871
3
{
872
3
    m_pImpXPolyPolygon->aXPolyList.emplace_back( std::move(rXPoly) );
873
3
}
874
875
/// insert all XPolygons of a XPolyPolygon
876
void XPolyPolygon::Insert( const XPolyPolygon& rXPolyPoly )
877
0
{
878
0
    for ( size_t i = 0; i < rXPolyPoly.Count(); i++)
879
0
    {
880
0
        m_pImpXPolyPolygon->aXPolyList.emplace_back( rXPolyPoly[i] );
881
0
    }
882
0
}
883
884
void XPolyPolygon::Remove( sal_uInt16 nPos )
885
0
{
886
0
    m_pImpXPolyPolygon->aXPolyList.erase( m_pImpXPolyPolygon->aXPolyList.begin() + nPos );
887
0
}
888
889
const XPolygon& XPolyPolygon::GetObject( sal_uInt16 nPos ) const
890
3
{
891
3
    return m_pImpXPolyPolygon->aXPolyList[ nPos ];
892
3
}
893
894
void XPolyPolygon::Clear()
895
0
{
896
0
    m_pImpXPolyPolygon->aXPolyList.clear();
897
0
}
898
899
sal_uInt16 XPolyPolygon::Count() const
900
6
{
901
6
    return static_cast<sal_uInt16>(m_pImpXPolyPolygon->aXPolyList.size());
902
6
}
903
904
tools::Rectangle XPolyPolygon::GetBoundRect() const
905
0
{
906
0
    size_t nXPoly = m_pImpXPolyPolygon->aXPolyList.size();
907
0
    tools::Rectangle aRect;
908
909
0
    for ( size_t n = 0; n < nXPoly; n++ )
910
0
    {
911
0
        XPolygon const & rXPoly = m_pImpXPolyPolygon->aXPolyList[ n ];
912
0
        aRect.Union( rXPoly.GetBoundRect() );
913
0
    }
914
915
0
    return aRect;
916
0
}
917
918
XPolygon& XPolyPolygon::operator[]( sal_uInt16 nPos )
919
0
{
920
0
    return m_pImpXPolyPolygon->aXPolyList[ nPos ];
921
0
}
922
923
0
XPolyPolygon& XPolyPolygon::operator=( const XPolyPolygon& ) = default;
924
925
0
XPolyPolygon& XPolyPolygon::operator=( XPolyPolygon&& ) = default;
926
927
/**
928
 * Distort a polygon by scaling its coordinates relative to a reference
929
 * rectangle into an arbitrary rectangle.
930
 *
931
 * Mapping between polygon corners and reference rectangle:
932
 *     0: top left     0----1
933
 *     1: top right    |    |
934
 *     2: bottom right 3----2
935
 *     3: bottom left
936
 */
937
void XPolyPolygon::Distort(const tools::Rectangle& rRefRect,
938
                           const XPolygon& rDistortedRect)
939
0
{
940
0
    for (size_t i = 0; i < Count(); i++)
941
0
        m_pImpXPolyPolygon->aXPolyList[ i ].Distort(rRefRect, rDistortedRect);
942
0
}
943
944
basegfx::B2DPolyPolygon XPolyPolygon::getB2DPolyPolygon() const
945
3
{
946
3
    basegfx::B2DPolyPolygon aRetval;
947
948
6
    for(sal_uInt16 a(0); a < Count(); a++)
949
3
    {
950
3
        const XPolygon& rPoly = (*this)[a];
951
3
        aRetval.append(rPoly.getB2DPolygon());
952
3
    }
953
954
3
    return aRetval;
955
3
}
956
957
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */