Coverage Report

Created: 2026-03-31 11:00

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/basegfx/source/polygon/b2dpolygontools.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
#include <numeric>
20
#include <algorithm>
21
#include <stack>
22
23
#include <basegfx/numeric/ftools.hxx>
24
#include <basegfx/polygon/b2dpolygontools.hxx>
25
#include <osl/diagnose.h>
26
#include <rtl/math.hxx>
27
#include <sal/log.hxx>
28
#include <basegfx/polygon/b2dpolygon.hxx>
29
#include <basegfx/polygon/b2dpolypolygon.hxx>
30
#include <basegfx/polygon/b3dpolygon.hxx>
31
#include <basegfx/range/b2drange.hxx>
32
#include <basegfx/curve/b2dcubicbezier.hxx>
33
#include <basegfx/point/b3dpoint.hxx>
34
#include <basegfx/matrix/b3dhommatrix.hxx>
35
#include <basegfx/matrix/b2dhommatrix.hxx>
36
#include <basegfx/curve/b2dbeziertools.hxx>
37
#include <basegfx/matrix/b2dhommatrixtools.hxx>
38
#include <basegfx/vector/b2enums.hxx>
39
40
// #i37443#
41
constexpr double ANGLE_BOUND_START_VALUE = 2.25;
42
constexpr double ANGLE_BOUND_MINIMUM_VALUE = 0.1;
43
constexpr sal_uInt32 STEPSPERQUARTER = 3;
44
45
namespace basegfx::utils
46
{
47
        void openWithGeometryChange(B2DPolygon& rCandidate)
48
3.04k
        {
49
3.04k
            if(!rCandidate.isClosed())
50
0
                return;
51
52
3.04k
            if(rCandidate.count())
53
3.04k
            {
54
3.04k
                rCandidate.append(rCandidate.getB2DPoint(0));
55
56
3.04k
                if(rCandidate.areControlPointsUsed() && rCandidate.isPrevControlPointUsed(0))
57
104
                {
58
104
                    rCandidate.setPrevControlPoint(rCandidate.count() - 1, rCandidate.getPrevControlPoint(0));
59
104
                    rCandidate.resetPrevControlPoint(0);
60
104
                }
61
3.04k
            }
62
63
3.04k
            rCandidate.setClosed(false);
64
3.04k
        }
65
66
        void closeWithGeometryChange(B2DPolygon& rCandidate)
67
3.25M
        {
68
3.25M
            if(rCandidate.isClosed())
69
1.17k
                return;
70
71
6.69M
            while(rCandidate.count() > 1 && rCandidate.getB2DPoint(0) == rCandidate.getB2DPoint(rCandidate.count() - 1))
72
3.44M
            {
73
3.44M
                if(rCandidate.areControlPointsUsed() && rCandidate.isPrevControlPointUsed(rCandidate.count() - 1))
74
2.86M
                {
75
2.86M
                    rCandidate.setPrevControlPoint(0, rCandidate.getPrevControlPoint(rCandidate.count() - 1));
76
2.86M
                }
77
78
3.44M
                rCandidate.remove(rCandidate.count() - 1);
79
3.44M
            }
80
81
3.25M
            rCandidate.setClosed(true);
82
3.25M
        }
83
84
        void checkClosed(B2DPolygon& rCandidate)
85
3.36M
        {
86
            // #i80172# Removed unnecessary assertion
87
            // OSL_ENSURE(!rCandidate.isClosed(), "checkClosed: already closed (!)");
88
89
3.36M
            if(rCandidate.count() > 1 && rCandidate.getB2DPoint(0) == rCandidate.getB2DPoint(rCandidate.count() - 1))
90
3.23M
            {
91
3.23M
                closeWithGeometryChange(rCandidate);
92
3.23M
            }
93
3.36M
        }
94
95
        // Get successor and predecessor indices. Returning the same index means there
96
        // is none. Same for successor.
97
        sal_uInt32 getIndexOfPredecessor(sal_uInt32 nIndex, const B2DPolygon& rCandidate)
98
0
        {
99
0
            OSL_ENSURE(nIndex < rCandidate.count(), "getIndexOfPredecessor: Access to polygon out of range (!)");
100
101
0
            if(nIndex)
102
0
            {
103
0
                return nIndex - 1;
104
0
            }
105
0
            else if(rCandidate.count())
106
0
            {
107
0
                return rCandidate.count() - 1;
108
0
            }
109
0
            else
110
0
            {
111
0
                return nIndex;
112
0
            }
113
0
        }
114
115
        sal_uInt32 getIndexOfSuccessor(sal_uInt32 nIndex, const B2DPolygon& rCandidate)
116
0
        {
117
0
            OSL_ENSURE(nIndex < rCandidate.count(), "getIndexOfPredecessor: Access to polygon out of range (!)");
118
119
0
            if(nIndex + 1 < rCandidate.count())
120
0
            {
121
0
                return nIndex + 1;
122
0
            }
123
0
            else if(nIndex + 1 == rCandidate.count())
124
0
            {
125
0
                return 0;
126
0
            }
127
0
            else
128
0
            {
129
0
                return nIndex;
130
0
            }
131
0
        }
132
133
        B2VectorOrientation getOrientation(const B2DPolygon& rCandidate)
134
1.04M
        {
135
1.04M
            B2VectorOrientation eRetval(B2VectorOrientation::Neutral);
136
137
1.04M
            if(rCandidate.count() > 2 || rCandidate.areControlPointsUsed())
138
960k
            {
139
960k
                const double fSignedArea(getSignedArea(rCandidate));
140
141
960k
                if(fTools::equalZero(fSignedArea))
142
43.4k
                {
143
                    // B2VectorOrientation::Neutral, already set
144
43.4k
                }
145
960k
                if(fSignedArea > 0.0)
146
786k
                {
147
786k
                    eRetval = B2VectorOrientation::Positive;
148
786k
                }
149
173k
                else if(fSignedArea < 0.0)
150
129k
                {
151
129k
                    eRetval = B2VectorOrientation::Negative;
152
129k
                }
153
960k
            }
154
155
1.04M
            return eRetval;
156
1.04M
        }
157
158
        B2VectorContinuity getContinuityInPoint(const B2DPolygon& rCandidate, sal_uInt32 nIndex)
159
0
        {
160
0
            return rCandidate.getContinuityInPoint(nIndex);
161
0
        }
162
163
        B2DPolygon adaptiveSubdivideByDistance(const B2DPolygon& rCandidate, double fDistanceBound, int nRecurseLimit)
164
18
        {
165
18
            if(rCandidate.areControlPointsUsed())
166
18
            {
167
18
                const sal_uInt32 nPointCount(rCandidate.count());
168
18
                B2DPolygon aRetval;
169
170
18
                if(nPointCount)
171
18
                {
172
                    // prepare edge-oriented loop
173
18
                    const sal_uInt32 nEdgeCount(rCandidate.isClosed() ? nPointCount : nPointCount - 1);
174
18
                    B2DCubicBezier aBezier;
175
18
                    aBezier.setStartPoint(rCandidate.getB2DPoint(0));
176
177
                    // perf: try to avoid too many reallocations by guessing the result's pointcount
178
18
                    aRetval.reserve(nPointCount*4);
179
180
                    // add start point (always)
181
18
                    aRetval.append(aBezier.getStartPoint());
182
183
4.31k
                    for(sal_uInt32 a(0); a < nEdgeCount; a++)
184
4.29k
                    {
185
                        // get next and control points
186
4.29k
                        const sal_uInt32 nNextIndex((a + 1) % nPointCount);
187
4.29k
                        aBezier.setEndPoint(rCandidate.getB2DPoint(nNextIndex));
188
4.29k
                        aBezier.setControlPointA(rCandidate.getNextControlPoint(a));
189
4.29k
                        aBezier.setControlPointB(rCandidate.getPrevControlPoint(nNextIndex));
190
4.29k
                        aBezier.testAndSolveTrivialBezier();
191
192
4.29k
                        if(aBezier.isBezier())
193
350
                        {
194
                            // add curved edge and generate DistanceBound
195
350
                            double fBound(0.0);
196
197
350
                            if(fDistanceBound == 0.0)
198
0
                            {
199
                                // If not set, use B2DCubicBezier functionality to guess a rough value
200
0
                                const double fRoughLength((aBezier.getEdgeLength() + aBezier.getControlPolygonLength()) / 2.0);
201
202
                                // take 1/100th of the rough curve length
203
0
                                fBound = fRoughLength * 0.01;
204
0
                            }
205
350
                            else
206
350
                            {
207
                                // use given bound value
208
350
                                fBound = fDistanceBound;
209
350
                            }
210
211
                            // make sure bound value is not too small. The base units are 1/100th mm, thus
212
                            // just make sure it's not smaller than 1/100th of that
213
350
                            if(fBound < 0.01)
214
0
                            {
215
0
                                fBound = 0.01;
216
0
                            }
217
218
                            // call adaptive subdivide which adds edges to aRetval accordingly
219
350
                            aBezier.adaptiveSubdivideByDistance(aRetval, fBound, nRecurseLimit);
220
350
                        }
221
3.94k
                        else
222
3.94k
                        {
223
                            // add non-curved edge
224
3.94k
                            aRetval.append(aBezier.getEndPoint());
225
3.94k
                        }
226
227
                        // prepare next step
228
4.29k
                        aBezier.setStartPoint(aBezier.getEndPoint());
229
4.29k
                    }
230
231
18
                    if(rCandidate.isClosed())
232
0
                    {
233
                        // set closed flag and correct last point (which is added double now).
234
0
                        closeWithGeometryChange(aRetval);
235
0
                    }
236
18
                }
237
238
18
                return aRetval;
239
18
            }
240
0
            else
241
0
            {
242
0
                return rCandidate;
243
0
            }
244
18
        }
245
246
        B2DPolygon adaptiveSubdivideByAngle(const B2DPolygon& rCandidate, double fAngleBound)
247
8.78k
        {
248
8.78k
            if(rCandidate.areControlPointsUsed())
249
8.78k
            {
250
8.78k
                const sal_uInt32 nPointCount(rCandidate.count());
251
8.78k
                B2DPolygon aRetval;
252
253
8.78k
                if(nPointCount)
254
8.78k
                {
255
                    // prepare edge-oriented loop
256
8.78k
                    const sal_uInt32 nEdgeCount(rCandidate.isClosed() ? nPointCount : nPointCount - 1);
257
8.78k
                    B2DCubicBezier aBezier;
258
8.78k
                    aBezier.setStartPoint(rCandidate.getB2DPoint(0));
259
260
                    // perf: try to avoid too many reallocations by guessing the result's pointcount
261
8.78k
                    aRetval.reserve(nPointCount*4);
262
263
                    // add start point (always)
264
8.78k
                    aRetval.append(aBezier.getStartPoint());
265
266
                    // #i37443# prepare convenient AngleBound if none was given
267
8.78k
                    if(fAngleBound == 0.0)
268
8.78k
                    {
269
8.78k
                        fAngleBound = ANGLE_BOUND_START_VALUE;
270
8.78k
                    }
271
0
                    else if(fTools::less(fAngleBound, ANGLE_BOUND_MINIMUM_VALUE))
272
0
                    {
273
0
                        fAngleBound = 0.1;
274
0
                    }
275
276
2.52M
                    for(sal_uInt32 a(0); a < nEdgeCount; a++)
277
2.52M
                    {
278
                        // get next and control points
279
2.52M
                        const sal_uInt32 nNextIndex((a + 1) % nPointCount);
280
2.52M
                        aBezier.setEndPoint(rCandidate.getB2DPoint(nNextIndex));
281
2.52M
                        aBezier.setControlPointA(rCandidate.getNextControlPoint(a));
282
2.52M
                        aBezier.setControlPointB(rCandidate.getPrevControlPoint(nNextIndex));
283
2.52M
                        aBezier.testAndSolveTrivialBezier();
284
285
2.52M
                        if(aBezier.isBezier())
286
1.98M
                        {
287
                            // call adaptive subdivide
288
1.98M
                            aBezier.adaptiveSubdivideByAngle(aRetval, fAngleBound);
289
1.98M
                        }
290
533k
                        else
291
533k
                        {
292
                            // add non-curved edge
293
533k
                            aRetval.append(aBezier.getEndPoint());
294
533k
                        }
295
296
                        // prepare next step
297
2.52M
                        aBezier.setStartPoint(aBezier.getEndPoint());
298
2.52M
                    }
299
300
8.78k
                    if(rCandidate.isClosed())
301
1.55k
                    {
302
                        // set closed flag and correct last point (which is added double now).
303
1.55k
                        closeWithGeometryChange(aRetval);
304
1.55k
                    }
305
8.78k
                }
306
307
8.78k
                return aRetval;
308
8.78k
            }
309
0
            else
310
0
            {
311
0
                return rCandidate;
312
0
            }
313
8.78k
        }
314
315
        bool isInside(const B2DPolygon& rCandidate, const B2DPoint& rPoint, bool bWithBorder)
316
13.7M
        {
317
13.7M
            const B2DPolygon aCandidate(rCandidate.areControlPointsUsed() ? rCandidate.getDefaultAdaptiveSubdivision() : rCandidate);
318
319
13.7M
            if(bWithBorder && isPointOnPolygon(aCandidate, rPoint))
320
883k
            {
321
883k
                return true;
322
883k
            }
323
12.8M
            else
324
12.8M
            {
325
12.8M
                bool bRetval(false);
326
12.8M
                const sal_uInt32 nPointCount(aCandidate.count());
327
328
12.8M
                if(nPointCount)
329
12.8M
                {
330
12.8M
                    B2DPoint aCurrentPoint(aCandidate.getB2DPoint(nPointCount - 1));
331
332
887M
                    for(sal_uInt32 a(0); a < nPointCount; a++)
333
874M
                    {
334
874M
                        const B2DPoint aPreviousPoint(aCurrentPoint);
335
874M
                        aCurrentPoint = aCandidate.getB2DPoint(a);
336
337
                        // cross-over in Y? tdf#130150 use full precision, no need for epsilon
338
874M
                        const bool bCompYA(aPreviousPoint.getY() > rPoint.getY());
339
874M
                        const bool bCompYB(aCurrentPoint.getY() > rPoint.getY());
340
341
874M
                        if(bCompYA != bCompYB)
342
13.6M
                        {
343
                            // cross-over in X? tdf#130150 use full precision, no need for epsilon
344
13.6M
                            const bool bCompXA(aPreviousPoint.getX() > rPoint.getX());
345
13.6M
                            const bool bCompXB(aCurrentPoint.getX() > rPoint.getX());
346
347
13.6M
                            if(bCompXA == bCompXB)
348
13.5M
                            {
349
13.5M
                                if(bCompXA)
350
5.54M
                                {
351
5.54M
                                    bRetval = !bRetval;
352
5.54M
                                }
353
13.5M
                            }
354
135k
                            else
355
135k
                            {
356
135k
                                const double fCompare(
357
135k
                                    aCurrentPoint.getX() - (aCurrentPoint.getY() - rPoint.getY()) *
358
135k
                                    (aPreviousPoint.getX() - aCurrentPoint.getX()) /
359
135k
                                    (aPreviousPoint.getY() - aCurrentPoint.getY()));
360
361
                                // tdf#130150 use full precision, no need for epsilon
362
135k
                                if(fCompare > rPoint.getX())
363
77.4k
                                {
364
77.4k
                                    bRetval = !bRetval;
365
77.4k
                                }
366
135k
                            }
367
13.6M
                        }
368
874M
                    }
369
12.8M
                }
370
371
12.8M
                return bRetval;
372
12.8M
            }
373
13.7M
        }
374
375
        bool isInside(const B2DPolygon& rCandidate, const B2DPolygon& rPolygon, bool bWithBorder)
376
11.9M
        {
377
11.9M
            const B2DPolygon aCandidate(rCandidate.areControlPointsUsed() ? rCandidate.getDefaultAdaptiveSubdivision() : rCandidate);
378
11.9M
            const B2DPolygon aPolygon(rPolygon.areControlPointsUsed() ? rPolygon.getDefaultAdaptiveSubdivision() : rPolygon);
379
11.9M
            const sal_uInt32 nPointCount(aPolygon.count());
380
381
13.9M
            for(sal_uInt32 a(0); a < nPointCount; a++)
382
13.7M
            {
383
13.7M
                const B2DPoint aTestPoint(aPolygon.getB2DPoint(a));
384
385
13.7M
                if(!isInside(aCandidate, aTestPoint, bWithBorder))
386
11.7M
                {
387
11.7M
                    return false;
388
11.7M
                }
389
13.7M
            }
390
391
210k
            return true;
392
11.9M
        }
393
394
        double getSignedArea(const B2DPolygon& rCandidate)
395
960k
        {
396
960k
            const B2DPolygon aCandidate(rCandidate.areControlPointsUsed() ? rCandidate.getDefaultAdaptiveSubdivision() : rCandidate);
397
960k
            double fRetval(0.0);
398
960k
            const sal_uInt32 nPointCount(aCandidate.count());
399
400
960k
            if(nPointCount > 2)
401
960k
            {
402
9.38M
                for(sal_uInt32 a(0); a < nPointCount; a++)
403
8.42M
                {
404
8.42M
                    const B2DPoint aPreviousPoint(aCandidate.getB2DPoint((!a) ? nPointCount - 1 : a - 1));
405
8.42M
                    const B2DPoint aCurrentPoint(aCandidate.getB2DPoint(a));
406
407
8.42M
                    fRetval += aPreviousPoint.getX() * aCurrentPoint.getY();
408
8.42M
                    fRetval -= aPreviousPoint.getY() * aCurrentPoint.getX();
409
8.42M
                }
410
411
                // correct to zero if small enough. Also test the quadratic
412
                // of the result since the precision is near quadratic due to
413
                // the algorithm
414
960k
                if(fTools::equalZero(fRetval) || fTools::equalZero(fRetval * fRetval))
415
43.4k
                {
416
43.4k
                    fRetval = 0.0;
417
43.4k
                }
418
960k
            }
419
420
960k
            return fRetval;
421
960k
        }
422
423
        double getArea(const B2DPolygon& rCandidate)
424
0
        {
425
0
            double fRetval(0.0);
426
427
0
            if(rCandidate.count() > 2 || rCandidate.areControlPointsUsed())
428
0
            {
429
0
                fRetval = getSignedArea(rCandidate);
430
0
                const double fZero(0.0);
431
432
0
                if(fTools::less(fRetval, fZero))
433
0
                {
434
0
                    fRetval = -fRetval;
435
0
                }
436
0
            }
437
438
0
            return fRetval;
439
0
        }
440
441
        double getEdgeLength(const B2DPolygon& rCandidate, sal_uInt32 nIndex)
442
0
        {
443
0
            const sal_uInt32 nPointCount(rCandidate.count());
444
0
            OSL_ENSURE(nIndex < nPointCount, "getEdgeLength: Access to polygon out of range (!)");
445
0
            double fRetval(0.0);
446
447
0
            if(nPointCount)
448
0
            {
449
0
                const sal_uInt32 nNextIndex((nIndex + 1) % nPointCount);
450
451
0
                if(rCandidate.areControlPointsUsed())
452
0
                {
453
0
                    B2DCubicBezier aEdge;
454
455
0
                    aEdge.setStartPoint(rCandidate.getB2DPoint(nIndex));
456
0
                    aEdge.setControlPointA(rCandidate.getNextControlPoint(nIndex));
457
0
                    aEdge.setControlPointB(rCandidate.getPrevControlPoint(nNextIndex));
458
0
                    aEdge.setEndPoint(rCandidate.getB2DPoint(nNextIndex));
459
460
0
                    fRetval = aEdge.getLength();
461
0
                }
462
0
                else
463
0
                {
464
0
                    const B2DPoint aCurrent(rCandidate.getB2DPoint(nIndex));
465
0
                    const B2DPoint aNext(rCandidate.getB2DPoint(nNextIndex));
466
467
0
                    fRetval = B2DVector(aNext - aCurrent).getLength();
468
0
                }
469
0
            }
470
471
0
            return fRetval;
472
0
        }
473
474
        double getLength(const B2DPolygon& rCandidate)
475
0
        {
476
0
            double fRetval(0.0);
477
0
            const sal_uInt32 nPointCount(rCandidate.count());
478
479
0
            if(nPointCount)
480
0
            {
481
0
                const sal_uInt32 nEdgeCount(rCandidate.isClosed() ? nPointCount : nPointCount - 1);
482
483
0
                if(rCandidate.areControlPointsUsed())
484
0
                {
485
0
                    B2DCubicBezier aEdge;
486
0
                    aEdge.setStartPoint(rCandidate.getB2DPoint(0));
487
488
0
                    for(sal_uInt32 a(0); a < nEdgeCount; a++)
489
0
                    {
490
0
                        const sal_uInt32 nNextIndex((a + 1) % nPointCount);
491
0
                        aEdge.setControlPointA(rCandidate.getNextControlPoint(a));
492
0
                        aEdge.setControlPointB(rCandidate.getPrevControlPoint(nNextIndex));
493
0
                        aEdge.setEndPoint(rCandidate.getB2DPoint(nNextIndex));
494
495
0
                        fRetval += aEdge.getLength();
496
0
                        aEdge.setStartPoint(aEdge.getEndPoint());
497
0
                    }
498
0
                }
499
0
                else
500
0
                {
501
0
                    B2DPoint aCurrent(rCandidate.getB2DPoint(0));
502
503
0
                    for(sal_uInt32 a(0); a < nEdgeCount; a++)
504
0
                    {
505
0
                        const sal_uInt32 nNextIndex((a + 1) % nPointCount);
506
0
                        const B2DPoint aNext(rCandidate.getB2DPoint(nNextIndex));
507
508
0
                        fRetval += B2DVector(aNext - aCurrent).getLength();
509
0
                        aCurrent = aNext;
510
0
                    }
511
0
                }
512
0
            }
513
514
0
            return fRetval;
515
0
        }
516
517
        B2DPoint getPositionAbsolute(const B2DPolygon& rCandidate, double fDistance, double fLength)
518
0
        {
519
0
            B2DPoint aRetval;
520
0
            const sal_uInt32 nPointCount(rCandidate.count());
521
522
0
            if( nPointCount == 1 )
523
0
            {
524
                // only one point (i.e. no edge) - simply take that point
525
0
                aRetval = rCandidate.getB2DPoint(0);
526
0
            }
527
0
            else if(nPointCount > 1)
528
0
            {
529
0
                const sal_uInt32 nEdgeCount(rCandidate.isClosed() ? nPointCount : nPointCount - 1);
530
0
                sal_uInt32 nIndex(0);
531
0
                bool bIndexDone(false);
532
533
0
                if (fDistance < 0.0)
534
0
                {
535
                    // handle fDistance < 0.0
536
0
                    if(rCandidate.isClosed())
537
0
                    {
538
0
                        if (fLength != 0)
539
0
                        {
540
                            // if fDistance < 0.0 increment with multiple of fLength
541
0
                            sal_uInt32 nCount(sal_uInt32(-fDistance / fLength));
542
0
                            fDistance += double(nCount + 1) * fLength;
543
0
                        }
544
0
                    }
545
0
                    else
546
0
                    {
547
                        // crop to polygon start
548
0
                        fDistance = 0.0;
549
0
                        bIndexDone = true;
550
0
                    }
551
0
                }
552
0
                else if(fTools::moreOrEqual(fDistance, fLength))
553
0
                {
554
                    // handle fDistance >= fLength
555
0
                    if(rCandidate.isClosed())
556
0
                    {
557
0
                        if (fLength != 0)
558
0
                        {
559
                            // if fDistance >= fLength decrement with multiple of fLength
560
0
                            sal_uInt32 nCount(sal_uInt32(fDistance / fLength));
561
0
                            fDistance -= static_cast<double>(nCount) * fLength;
562
0
                        }
563
0
                    }
564
0
                    else
565
0
                    {
566
                        // crop to polygon end
567
0
                        fDistance = 0.0;
568
0
                        nIndex = nEdgeCount;
569
0
                        bIndexDone = true;
570
0
                    }
571
0
                }
572
573
                // look for correct index. fDistance is now [0.0 .. fLength[
574
0
                double fEdgeLength(getEdgeLength(rCandidate, nIndex));
575
576
0
                while(!bIndexDone)
577
0
                {
578
                    // edge found must be on the half-open range
579
                    // [0,fEdgeLength).
580
                    // Note that in theory, we cannot move beyond
581
                    // the last polygon point, since fDistance>=fLength
582
                    // is checked above. Unfortunately, with floating-
583
                    // point calculations, this case might happen.
584
                    // Handled by nIndex check below
585
0
                    if (nIndex+1 < nEdgeCount && fTools::moreOrEqual(fDistance, fEdgeLength))
586
0
                    {
587
                        // go to next edge
588
0
                        fDistance -= fEdgeLength;
589
0
                        fEdgeLength = getEdgeLength(rCandidate, ++nIndex);
590
0
                    }
591
0
                    else
592
0
                    {
593
                        // it's on this edge, stop
594
0
                        bIndexDone = true;
595
0
                    }
596
0
                }
597
598
                // get the point using nIndex
599
0
                aRetval = rCandidate.getB2DPoint(nIndex);
600
601
                // if fDistance != 0.0, move that length on the edge. The edge
602
                // length is in fEdgeLength.
603
0
                if(!fTools::equalZero(fDistance))
604
0
                {
605
0
                    if(fTools::moreOrEqual(fDistance, fEdgeLength))
606
0
                    {
607
                        // end point of chosen edge
608
0
                        const sal_uInt32 nNextIndex((nIndex + 1) % nPointCount);
609
0
                        aRetval = rCandidate.getB2DPoint(nNextIndex);
610
0
                    }
611
0
                    else if(fTools::equalZero(fDistance))
612
0
                    {
613
                        // start point of chosen edge
614
0
                    }
615
0
                    else
616
0
                    {
617
                        // inside edge
618
0
                        const sal_uInt32 nNextIndex((nIndex + 1) % nPointCount);
619
0
                        const B2DPoint aNextPoint(rCandidate.getB2DPoint(nNextIndex));
620
0
                        bool bDone(false);
621
622
                        // add calculated average value to the return value
623
0
                        if(rCandidate.areControlPointsUsed())
624
0
                        {
625
                            // get as bezier segment
626
0
                            const B2DCubicBezier aBezierSegment(
627
0
                                aRetval, rCandidate.getNextControlPoint(nIndex),
628
0
                                rCandidate.getPrevControlPoint(nNextIndex), aNextPoint);
629
630
0
                            if(aBezierSegment.isBezier())
631
0
                            {
632
                                // use B2DCubicBezierHelper to bridge the non-linear gap between
633
                                // length and bezier distances
634
0
                                const B2DCubicBezierHelper aBezierSegmentHelper(aBezierSegment);
635
0
                                const double fBezierDistance(aBezierSegmentHelper.distanceToRelative(fDistance));
636
637
0
                                aRetval = aBezierSegment.interpolatePoint(fBezierDistance);
638
0
                                bDone = true;
639
0
                            }
640
0
                        }
641
642
0
                        if(!bDone && fEdgeLength != 0)
643
0
                        {
644
0
                            const double fRelativeInEdge(fDistance / fEdgeLength);
645
0
                            aRetval = interpolate(aRetval, aNextPoint, fRelativeInEdge);
646
0
                        }
647
0
                    }
648
0
                }
649
0
            }
650
651
0
            return aRetval;
652
0
        }
653
654
        B2DPoint getPositionRelative(const B2DPolygon& rCandidate, double fDistance, double fLength)
655
0
        {
656
            // multiply fDistance with real length to get absolute position and
657
            // use getPositionAbsolute
658
0
            return getPositionAbsolute(rCandidate, fDistance * fLength, fLength);
659
0
        }
660
661
        B2DPolygon getSnippetAbsolute(const B2DPolygon& rCandidate, double fFrom, double fTo, double fLength)
662
0
        {
663
0
            const sal_uInt32 nPointCount(rCandidate.count());
664
665
0
            if(nPointCount)
666
0
            {
667
                // test and correct fFrom
668
0
                if (fFrom < 0.0)
669
0
                {
670
0
                    fFrom = 0.0;
671
0
                }
672
673
                // test and correct fTo
674
0
                if(fTools::more(fTo, fLength))
675
0
                {
676
0
                    fTo = fLength;
677
0
                }
678
679
                // test and correct relationship of fFrom, fTo
680
0
                if(fTools::more(fFrom, fTo))
681
0
                {
682
0
                    fFrom = fTo = (fFrom + fTo) / 2.0;
683
0
                }
684
685
0
                if(fTools::equalZero(fFrom) && fTools::equal(fTo, fLength))
686
0
                {
687
                    // no change, result is the whole polygon
688
0
                    return rCandidate;
689
0
                }
690
0
                else
691
0
                {
692
0
                    B2DPolygon aRetval;
693
0
                    const sal_uInt32 nEdgeCount(rCandidate.isClosed() ? nPointCount : nPointCount - 1);
694
0
                    double fPositionOfStart(0.0);
695
0
                    bool bStartDone(false);
696
0
                    bool bEndDone(false);
697
698
0
                    for(sal_uInt32 a(0); !(bStartDone && bEndDone) && a < nEdgeCount; a++)
699
0
                    {
700
0
                        const double fEdgeLength(getEdgeLength(rCandidate, a));
701
702
0
                        if(!bStartDone)
703
0
                        {
704
0
                            if(fTools::equalZero(fFrom))
705
0
                            {
706
0
                                aRetval.append(rCandidate.getB2DPoint(a));
707
708
0
                                if(rCandidate.areControlPointsUsed())
709
0
                                {
710
0
                                    aRetval.setNextControlPoint(aRetval.count() - 1, rCandidate.getNextControlPoint(a));
711
0
                                }
712
713
0
                                bStartDone = true;
714
0
                            }
715
0
                            else if(fTools::moreOrEqual(fFrom, fPositionOfStart) && fTools::less(fFrom, fPositionOfStart + fEdgeLength))
716
0
                            {
717
                                // calculate and add start point
718
0
                                if(fTools::equalZero(fEdgeLength))
719
0
                                {
720
0
                                    aRetval.append(rCandidate.getB2DPoint(a));
721
722
0
                                    if(rCandidate.areControlPointsUsed())
723
0
                                    {
724
0
                                        aRetval.setNextControlPoint(aRetval.count() - 1, rCandidate.getNextControlPoint(a));
725
0
                                    }
726
0
                                }
727
0
                                else
728
0
                                {
729
0
                                    const sal_uInt32 nNextIndex((a + 1) % nPointCount);
730
0
                                    const B2DPoint aStart(rCandidate.getB2DPoint(a));
731
0
                                    const B2DPoint aEnd(rCandidate.getB2DPoint(nNextIndex));
732
0
                                    bool bDone(false);
733
734
0
                                    if(rCandidate.areControlPointsUsed())
735
0
                                    {
736
0
                                        const B2DCubicBezier aBezierSegment(
737
0
                                            aStart, rCandidate.getNextControlPoint(a),
738
0
                                            rCandidate.getPrevControlPoint(nNextIndex), aEnd);
739
740
0
                                        if(aBezierSegment.isBezier())
741
0
                                        {
742
                                            // use B2DCubicBezierHelper to bridge the non-linear gap between
743
                                            // length and bezier distances
744
0
                                            const B2DCubicBezierHelper aBezierSegmentHelper(aBezierSegment);
745
0
                                            const double fBezierDistance(aBezierSegmentHelper.distanceToRelative(fFrom - fPositionOfStart));
746
0
                                            B2DCubicBezier aRight;
747
748
0
                                            aBezierSegment.split(fBezierDistance, nullptr, &aRight);
749
0
                                            aRetval.append(aRight.getStartPoint());
750
0
                                            aRetval.setNextControlPoint(aRetval.count() - 1, aRight.getControlPointA());
751
0
                                            bDone = true;
752
0
                                        }
753
0
                                    }
754
755
0
                                    if(!bDone)
756
0
                                    {
757
0
                                        const double fRelValue((fFrom - fPositionOfStart) / fEdgeLength);
758
0
                                        aRetval.append(interpolate(aStart, aEnd, fRelValue));
759
0
                                    }
760
0
                                }
761
762
0
                                bStartDone = true;
763
764
                                // if same point, end is done, too.
765
0
                                if(rtl::math::approxEqual(fFrom, fTo))
766
0
                                {
767
0
                                    bEndDone = true;
768
0
                                }
769
0
                            }
770
0
                        }
771
772
0
                        if(!bEndDone && fTools::moreOrEqual(fTo, fPositionOfStart) && fTools::less(fTo, fPositionOfStart + fEdgeLength))
773
0
                        {
774
                            // calculate and add end point
775
0
                            if(fTools::equalZero(fEdgeLength))
776
0
                            {
777
0
                                const sal_uInt32 nNextIndex((a + 1) % nPointCount);
778
0
                                aRetval.append(rCandidate.getB2DPoint(nNextIndex));
779
780
0
                                if(rCandidate.areControlPointsUsed())
781
0
                                {
782
0
                                    aRetval.setPrevControlPoint(aRetval.count() - 1, rCandidate.getPrevControlPoint(nNextIndex));
783
0
                                }
784
0
                            }
785
0
                            else
786
0
                            {
787
0
                                const sal_uInt32 nNextIndex((a + 1) % nPointCount);
788
0
                                const B2DPoint aStart(rCandidate.getB2DPoint(a));
789
0
                                const B2DPoint aEnd(rCandidate.getB2DPoint(nNextIndex));
790
0
                                bool bDone(false);
791
792
0
                                if(rCandidate.areControlPointsUsed())
793
0
                                {
794
0
                                    const B2DCubicBezier aBezierSegment(
795
0
                                        aStart, rCandidate.getNextControlPoint(a),
796
0
                                        rCandidate.getPrevControlPoint(nNextIndex), aEnd);
797
798
0
                                    if(aBezierSegment.isBezier())
799
0
                                    {
800
                                        // use B2DCubicBezierHelper to bridge the non-linear gap between
801
                                        // length and bezier distances
802
0
                                        const B2DCubicBezierHelper aBezierSegmentHelper(aBezierSegment);
803
0
                                        const double fBezierDistance(aBezierSegmentHelper.distanceToRelative(fTo - fPositionOfStart));
804
0
                                        B2DCubicBezier aLeft;
805
806
0
                                        aBezierSegment.split(fBezierDistance, &aLeft, nullptr);
807
0
                                        aRetval.append(aLeft.getEndPoint());
808
0
                                        aRetval.setPrevControlPoint(aRetval.count() - 1, aLeft.getControlPointB());
809
0
                                        bDone = true;
810
0
                                    }
811
0
                                }
812
813
0
                                if(!bDone)
814
0
                                {
815
0
                                    const double fRelValue((fTo - fPositionOfStart) / fEdgeLength);
816
0
                                    aRetval.append(interpolate(aStart, aEnd, fRelValue));
817
0
                                }
818
0
                            }
819
820
0
                            bEndDone = true;
821
0
                        }
822
823
0
                        if(!bEndDone)
824
0
                        {
825
0
                            if(bStartDone)
826
0
                            {
827
                                // add segments end point
828
0
                                const sal_uInt32 nNextIndex((a + 1) % nPointCount);
829
0
                                aRetval.append(rCandidate.getB2DPoint(nNextIndex));
830
831
0
                                if(rCandidate.areControlPointsUsed())
832
0
                                {
833
0
                                    aRetval.setPrevControlPoint(aRetval.count() - 1, rCandidate.getPrevControlPoint(nNextIndex));
834
0
                                    aRetval.setNextControlPoint(aRetval.count() - 1, rCandidate.getNextControlPoint(nNextIndex));
835
0
                                }
836
0
                            }
837
838
                            // increment fPositionOfStart
839
0
                            fPositionOfStart += fEdgeLength;
840
0
                        }
841
0
                    }
842
0
                    return aRetval;
843
0
                }
844
0
            }
845
0
            else
846
0
            {
847
0
                return rCandidate;
848
0
            }
849
0
        }
850
851
        CutFlagValue findCut(
852
            const B2DPoint& rEdge1Start, const B2DVector& rEdge1Delta,
853
            const B2DPoint& rEdge2Start, const B2DVector& rEdge2Delta,
854
            CutFlagValue aCutFlags,
855
            double* pCut1, double* pCut2)
856
402k
        {
857
402k
            CutFlagValue aRetval(CutFlagValue::NONE);
858
402k
            double fCut1(0.0);
859
402k
            double fCut2(0.0);
860
402k
            bool bFinished(!static_cast<bool>(aCutFlags & CutFlagValue::ALL));
861
862
            // test for same points?
863
402k
            if(!bFinished
864
402k
                && (aCutFlags & (CutFlagValue::START1|CutFlagValue::END1))
865
348k
                && (aCutFlags & (CutFlagValue::START2|CutFlagValue::END2)))
866
348k
            {
867
                // same startpoint?
868
348k
                if((aCutFlags & (CutFlagValue::START1|CutFlagValue::START2)) == (CutFlagValue::START1|CutFlagValue::START2))
869
348k
                {
870
348k
                    if(rEdge1Start.equal(rEdge2Start))
871
646
                    {
872
646
                        bFinished = true;
873
646
                        aRetval = (CutFlagValue::START1|CutFlagValue::START2);
874
646
                    }
875
348k
                }
876
877
                // same endpoint?
878
348k
                if(!bFinished && (aCutFlags & (CutFlagValue::END1|CutFlagValue::END2)) == (CutFlagValue::END1|CutFlagValue::END2))
879
348k
                {
880
348k
                    const B2DPoint aEnd1(rEdge1Start + rEdge1Delta);
881
348k
                    const B2DPoint aEnd2(rEdge2Start + rEdge2Delta);
882
883
348k
                    if(aEnd1.equal(aEnd2))
884
10
                    {
885
10
                        bFinished = true;
886
10
                        aRetval = (CutFlagValue::END1|CutFlagValue::END2);
887
10
                        fCut1 = fCut2 = 1.0;
888
10
                    }
889
348k
                }
890
891
                // startpoint1 == endpoint2?
892
348k
                if(!bFinished && (aCutFlags & (CutFlagValue::START1|CutFlagValue::END2)) == (CutFlagValue::START1|CutFlagValue::END2))
893
348k
                {
894
348k
                    const B2DPoint aEnd2(rEdge2Start + rEdge2Delta);
895
896
348k
                    if(rEdge1Start.equal(aEnd2))
897
0
                    {
898
0
                        bFinished = true;
899
0
                        aRetval = (CutFlagValue::START1|CutFlagValue::END2);
900
0
                        fCut1 = 0.0;
901
0
                        fCut2 = 1.0;
902
0
                    }
903
348k
                }
904
905
                // startpoint2 == endpoint1?
906
348k
                if(!bFinished&& (aCutFlags & (CutFlagValue::START2|CutFlagValue::END1)) == (CutFlagValue::START2|CutFlagValue::END1))
907
348k
                {
908
348k
                    const B2DPoint aEnd1(rEdge1Start + rEdge1Delta);
909
910
348k
                    if(rEdge2Start.equal(aEnd1))
911
0
                    {
912
0
                        bFinished = true;
913
0
                        aRetval = (CutFlagValue::START2|CutFlagValue::END1);
914
0
                        fCut1 = 1.0;
915
0
                        fCut2 = 0.0;
916
0
                    }
917
348k
                }
918
348k
            }
919
920
402k
            if(!bFinished && (aCutFlags & CutFlagValue::LINE))
921
401k
            {
922
401k
                if(aCutFlags & CutFlagValue::START1)
923
348k
                {
924
                    // start1 on line 2 ?
925
348k
                    if(isPointOnEdge(rEdge1Start, rEdge2Start, rEdge2Delta, &fCut2))
926
32
                    {
927
32
                        bFinished = true;
928
32
                        aRetval = (CutFlagValue::LINE|CutFlagValue::START1);
929
32
                    }
930
348k
                }
931
932
401k
                if(!bFinished && (aCutFlags & CutFlagValue::START2))
933
348k
                {
934
                    // start2 on line 1 ?
935
348k
                    if(isPointOnEdge(rEdge2Start, rEdge1Start, rEdge1Delta, &fCut1))
936
24
                    {
937
24
                        bFinished = true;
938
24
                        aRetval = (CutFlagValue::LINE|CutFlagValue::START2);
939
24
                    }
940
348k
                }
941
942
401k
                if(!bFinished && (aCutFlags & CutFlagValue::END1))
943
348k
                {
944
                    // end1 on line 2 ?
945
348k
                    const B2DPoint aEnd1(rEdge1Start + rEdge1Delta);
946
947
348k
                    if(isPointOnEdge(aEnd1, rEdge2Start, rEdge2Delta, &fCut2))
948
7
                    {
949
7
                        bFinished = true;
950
7
                        aRetval = (CutFlagValue::LINE|CutFlagValue::END1);
951
7
                    }
952
348k
                }
953
954
401k
                if(!bFinished && (aCutFlags & CutFlagValue::END2))
955
348k
                {
956
                    // end2 on line 1 ?
957
348k
                    const B2DPoint aEnd2(rEdge2Start + rEdge2Delta);
958
959
348k
                    if(isPointOnEdge(aEnd2, rEdge1Start, rEdge1Delta, &fCut1))
960
10
                    {
961
10
                        bFinished = true;
962
10
                        aRetval = (CutFlagValue::LINE|CutFlagValue::END2);
963
10
                    }
964
348k
                }
965
966
401k
                if(!bFinished)
967
401k
                {
968
                    // cut in line1, line2 ?
969
401k
                    fCut1 = (rEdge1Delta.getX() * rEdge2Delta.getY()) - (rEdge1Delta.getY() * rEdge2Delta.getX());
970
971
401k
                    if(!fTools::equalZero(fCut1))
972
394k
                    {
973
394k
                        fCut1 = (rEdge2Delta.getY() * (rEdge2Start.getX() - rEdge1Start.getX())
974
394k
                            + rEdge2Delta.getX() * (rEdge1Start.getY() - rEdge2Start.getY())) / fCut1;
975
976
394k
                        const double fZero(0.0);
977
394k
                        const double fOne(1.0);
978
979
                        // inside parameter range edge1 AND fCut2 is calculable
980
394k
                        if(fTools::more(fCut1, fZero) && fTools::less(fCut1, fOne)
981
67.5k
                            && (!fTools::equalZero(rEdge2Delta.getX()) || !fTools::equalZero(rEdge2Delta.getY())))
982
67.5k
                        {
983
                            // take the more precise calculation of the two possible
984
67.5k
                            if(fabs(rEdge2Delta.getX()) > fabs(rEdge2Delta.getY()))
985
33.0k
                            {
986
33.0k
                                fCut2 = (rEdge1Start.getX() + fCut1
987
33.0k
                                    * rEdge1Delta.getX() - rEdge2Start.getX()) / rEdge2Delta.getX();
988
33.0k
                            }
989
34.5k
                            else
990
34.5k
                            {
991
34.5k
                                fCut2 = (rEdge1Start.getY() + fCut1
992
34.5k
                                    * rEdge1Delta.getY() - rEdge2Start.getY()) / rEdge2Delta.getY();
993
34.5k
                            }
994
995
                            // inside parameter range edge2, too
996
67.5k
                            if(fTools::more(fCut2, fZero) && fTools::less(fCut2, fOne))
997
53.1k
                            {
998
53.1k
                                aRetval = CutFlagValue::LINE;
999
53.1k
                            }
1000
67.5k
                        }
1001
394k
                    }
1002
401k
                }
1003
401k
            }
1004
1005
            // copy values if wanted
1006
402k
            if(pCut1)
1007
402k
            {
1008
402k
                *pCut1 = fCut1;
1009
402k
            }
1010
1011
402k
            if(pCut2)
1012
0
            {
1013
0
                *pCut2 = fCut2;
1014
0
            }
1015
1016
402k
            return aRetval;
1017
402k
        }
1018
1019
        bool isPointOnEdge(
1020
            const B2DPoint& rPoint,
1021
            const B2DPoint& rEdgeStart,
1022
            const B2DVector& rEdgeDelta,
1023
            double* pCut)
1024
1.39M
        {
1025
1.39M
            bool bDeltaXIsZero(fTools::equalZero(rEdgeDelta.getX()));
1026
1.39M
            bool bDeltaYIsZero(fTools::equalZero(rEdgeDelta.getY()));
1027
1.39M
            const double fZero(0.0);
1028
1.39M
            const double fOne(1.0);
1029
1030
1.39M
            if(bDeltaXIsZero && bDeltaYIsZero)
1031
28
            {
1032
                // no line, just a point
1033
28
                return false;
1034
28
            }
1035
1.39M
            else if(bDeltaXIsZero)
1036
59.1k
            {
1037
                // vertical line
1038
59.1k
                if(fTools::equal(rPoint.getX(), rEdgeStart.getX()))
1039
80
                {
1040
80
                    double fValue = (rPoint.getY() - rEdgeStart.getY()) / rEdgeDelta.getY();
1041
1042
80
                    if(fTools::more(fValue, fZero) && fTools::less(fValue, fOne))
1043
40
                    {
1044
40
                        if(pCut)
1045
40
                        {
1046
40
                            *pCut = fValue;
1047
40
                        }
1048
1049
40
                        return true;
1050
40
                    }
1051
80
                }
1052
59.1k
            }
1053
1.33M
            else if(bDeltaYIsZero)
1054
58.7k
            {
1055
                // horizontal line
1056
58.7k
                if(fTools::equal(rPoint.getY(), rEdgeStart.getY()))
1057
75
                {
1058
75
                    double fValue = (rPoint.getX() - rEdgeStart.getX()) / rEdgeDelta.getX();
1059
1060
75
                    if(fTools::more(fValue, fZero) && fTools::less(fValue, fOne))
1061
33
                    {
1062
33
                        if(pCut)
1063
33
                        {
1064
33
                            *pCut = fValue;
1065
33
                        }
1066
1067
33
                        return true;
1068
33
                    }
1069
75
                }
1070
58.7k
            }
1071
1.27M
            else
1072
1.27M
            {
1073
                // any angle line
1074
1.27M
                double fTOne = (rPoint.getX() - rEdgeStart.getX()) / rEdgeDelta.getX();
1075
1.27M
                double fTTwo = (rPoint.getY() - rEdgeStart.getY()) / rEdgeDelta.getY();
1076
1077
1.27M
                if(fTools::equal(fTOne, fTTwo))
1078
0
                {
1079
                    // same parameter representation, point is on line. Take
1080
                    // middle value for better results
1081
0
                    double fValue = (fTOne + fTTwo) / 2.0;
1082
1083
0
                    if(fTools::more(fValue, fZero) && fTools::less(fValue, fOne))
1084
0
                    {
1085
                        // point is inside line bounds, too
1086
0
                        if(pCut)
1087
0
                        {
1088
0
                            *pCut = fValue;
1089
0
                        }
1090
1091
0
                        return true;
1092
0
                    }
1093
0
                }
1094
1.27M
            }
1095
1096
1.39M
            return false;
1097
1.39M
        }
1098
1099
        void applyLineDashing(
1100
            const B2DPolygon& rCandidate,
1101
            const std::vector<double>& rDotDashArray,
1102
            B2DPolyPolygon* pLineTarget,
1103
            B2DPolyPolygon* pGapTarget,
1104
            double fDotDashLength)
1105
0
        {
1106
            // clear targets in any case
1107
0
            if(pLineTarget)
1108
0
            {
1109
0
                pLineTarget->clear();
1110
0
            }
1111
1112
0
            if(pGapTarget)
1113
0
            {
1114
0
                pGapTarget->clear();
1115
0
            }
1116
1117
            // call version that uses callbacks
1118
0
            applyLineDashing(
1119
0
                rCandidate,
1120
0
                rDotDashArray,
1121
                // provide callbacks as lambdas
1122
0
                (!pLineTarget
1123
0
                    ? std::function<void(const basegfx::B2DPolygon&)>()
1124
0
                    : [&pLineTarget](const basegfx::B2DPolygon& rSnippet){ pLineTarget->append(rSnippet); }),
1125
0
                (!pGapTarget
1126
0
                    ? std::function<void(const basegfx::B2DPolygon&)>()
1127
0
                    : [&pGapTarget](const basegfx::B2DPolygon& rSnippet){ pGapTarget->append(rSnippet); }),
1128
0
                fDotDashLength);
1129
0
        }
1130
1131
        static void implHandleSnippet(
1132
            const B2DPolygon& rSnippet,
1133
            const std::function<void(const basegfx::B2DPolygon& rSnippet)>& rTargetCallback,
1134
            B2DPolygon& rFirst,
1135
            B2DPolygon& rLast)
1136
0
        {
1137
0
            if(rSnippet.isClosed())
1138
0
            {
1139
0
                if(!rFirst.count())
1140
0
                {
1141
0
                    rFirst = rSnippet;
1142
0
                }
1143
0
                else
1144
0
                {
1145
0
                    if(rLast.count())
1146
0
                    {
1147
0
                        rTargetCallback(rLast);
1148
0
                    }
1149
1150
0
                    rLast = rSnippet;
1151
0
                }
1152
0
            }
1153
0
            else
1154
0
            {
1155
0
                rTargetCallback(rSnippet);
1156
0
            }
1157
0
        }
1158
1159
        static void implHandleFirstLast(
1160
            const std::function<void(const basegfx::B2DPolygon& rSnippet)>& rTargetCallback,
1161
            B2DPolygon& rFirst,
1162
            B2DPolygon& rLast)
1163
0
        {
1164
0
            if(rFirst.count() && rLast.count()
1165
0
                && rFirst.getB2DPoint(0).equal(rLast.getB2DPoint(rLast.count() - 1)))
1166
0
            {
1167
                // start of first and end of last are the same -> merge them
1168
0
                rLast.append(rFirst);
1169
0
                rLast.removeDoublePoints();
1170
0
                rFirst.clear();
1171
0
            }
1172
1173
0
            if(rLast.count())
1174
0
            {
1175
0
                rTargetCallback(rLast);
1176
0
            }
1177
1178
0
            if(rFirst.count())
1179
0
            {
1180
0
                rTargetCallback(rFirst);
1181
0
            }
1182
0
        }
1183
1184
        void applyLineDashing(
1185
            const B2DPolygon& rCandidate,
1186
            const std::vector<double>& rDotDashArray,
1187
            const std::function<void(const basegfx::B2DPolygon& rSnippet)>& rLineTargetCallback,
1188
            const std::function<void(const basegfx::B2DPolygon& rSnippet)>& rGapTargetCallback,
1189
            double fDotDashLength)
1190
0
        {
1191
0
            const sal_uInt32 nPointCount(rCandidate.count());
1192
0
            const sal_uInt32 nDotDashCount(rDotDashArray.size());
1193
1194
0
            if(fDotDashLength <= 0.0)
1195
0
            {
1196
0
                fDotDashLength = std::accumulate(rDotDashArray.begin(), rDotDashArray.end(), 0.0);
1197
0
            }
1198
1199
0
            if(fDotDashLength <= 0.0 || (!rLineTargetCallback && !rGapTargetCallback) || !nPointCount)
1200
0
            {
1201
                // parameters make no sense, just add source to targets
1202
0
                if (rLineTargetCallback)
1203
0
                    rLineTargetCallback(rCandidate);
1204
1205
0
                if (rGapTargetCallback)
1206
0
                    rGapTargetCallback(rCandidate);
1207
1208
0
                return;
1209
0
            }
1210
1211
            // precalculate maximal acceptable length of candidate polygon assuming
1212
            // we want to create a maximum of fNumberOfAllowedSnippets. For
1213
            // fNumberOfAllowedSnippets use ca. 65536, double due to line & gap.
1214
0
            static const double fNumberOfAllowedSnippets(65535.0 * 2.0);
1215
0
            const double fAllowedLength((fNumberOfAllowedSnippets * fDotDashLength) / double(rDotDashArray.size()));
1216
0
            const double fCandidateLength(basegfx::utils::getLength(rCandidate));
1217
0
            std::vector<double> aDotDashArray(rDotDashArray);
1218
1219
0
            if(fCandidateLength > fAllowedLength)
1220
0
            {
1221
                // we would produce more than fNumberOfAllowedSnippets, so
1222
                // adapt aDotDashArray to exactly produce assumed number. Also
1223
                // assert this to let the caller know about it.
1224
                // If this asserts: Please think about checking your DotDashArray
1225
                // before calling this function or evtl. use the callback version
1226
                // to *not* produce that much of data. Even then, you may still
1227
                // think about producing too much runtime (!)
1228
0
                assert(true && "applyLineDashing: potentially too expensive to do the requested dismantle - please consider stretched LineDash pattern (!)");
1229
1230
                // calculate correcting factor, apply to aDotDashArray and fDotDashLength
1231
                // to enlarge these as needed
1232
0
                const double fFactor(fCandidateLength / fAllowedLength);
1233
0
                std::for_each(aDotDashArray.begin(), aDotDashArray.end(), [&fFactor](double &f){ f *= fFactor; });
1234
0
            }
1235
1236
            // prepare current edge's start
1237
0
            B2DCubicBezier aCurrentEdge;
1238
0
            const bool bIsClosed(rCandidate.isClosed());
1239
0
            const sal_uInt32 nEdgeCount(bIsClosed ? nPointCount : nPointCount - 1);
1240
0
            aCurrentEdge.setStartPoint(rCandidate.getB2DPoint(0));
1241
1242
            // prepare DotDashArray iteration and the line/gap switching bool
1243
0
            sal_uInt32 nDotDashIndex(0);
1244
0
            bool bIsLine(true);
1245
0
            double fDotDashMovingLength(aDotDashArray[0]);
1246
0
            B2DPolygon aSnippet;
1247
1248
            // remember 1st and last snippets to try to merge after execution
1249
            // is complete and hand to callback
1250
0
            B2DPolygon aFirstLine, aLastLine;
1251
0
            B2DPolygon aFirstGap, aLastGap;
1252
1253
            // iterate over all edges
1254
0
            for(sal_uInt32 a(0); a < nEdgeCount; a++)
1255
0
            {
1256
                // update current edge (fill in C1, C2 and end point)
1257
0
                double fLastDotDashMovingLength(0.0);
1258
0
                const sal_uInt32 nNextIndex((a + 1) % nPointCount);
1259
0
                aCurrentEdge.setControlPointA(rCandidate.getNextControlPoint(a));
1260
0
                aCurrentEdge.setControlPointB(rCandidate.getPrevControlPoint(nNextIndex));
1261
0
                aCurrentEdge.setEndPoint(rCandidate.getB2DPoint(nNextIndex));
1262
1263
                // check if we have a trivial bezier segment -> possible fallback to edge
1264
0
                aCurrentEdge.testAndSolveTrivialBezier();
1265
1266
0
                if(aCurrentEdge.isBezier())
1267
0
                {
1268
                    // bezier segment
1269
0
                    const B2DCubicBezierHelper aCubicBezierHelper(aCurrentEdge);
1270
0
                    const double fEdgeLength(aCubicBezierHelper.getLength());
1271
1272
0
                    if(!fTools::equalZero(fEdgeLength))
1273
0
                    {
1274
0
                        while(fTools::less(fDotDashMovingLength, fEdgeLength))
1275
0
                        {
1276
                            // new split is inside edge, create and append snippet [fLastDotDashMovingLength, fDotDashMovingLength]
1277
0
                            const bool bHandleLine(bIsLine && rLineTargetCallback);
1278
0
                            const bool bHandleGap(!bIsLine && rGapTargetCallback);
1279
1280
0
                            if(bHandleLine || bHandleGap)
1281
0
                            {
1282
0
                                const double fBezierSplitStart(aCubicBezierHelper.distanceToRelative(fLastDotDashMovingLength));
1283
0
                                const double fBezierSplitEnd(aCubicBezierHelper.distanceToRelative(fDotDashMovingLength));
1284
0
                                B2DCubicBezier aBezierSnippet(aCurrentEdge.snippet(fBezierSplitStart, fBezierSplitEnd));
1285
1286
0
                                if(!aSnippet.count())
1287
0
                                {
1288
0
                                    aSnippet.append(aBezierSnippet.getStartPoint());
1289
0
                                }
1290
1291
0
                                aSnippet.appendBezierSegment(aBezierSnippet.getControlPointA(), aBezierSnippet.getControlPointB(), aBezierSnippet.getEndPoint());
1292
1293
0
                                if(bHandleLine)
1294
0
                                {
1295
0
                                    implHandleSnippet(aSnippet, rLineTargetCallback, aFirstLine, aLastLine);
1296
0
                                }
1297
1298
0
                                if(bHandleGap)
1299
0
                                {
1300
0
                                    implHandleSnippet(aSnippet, rGapTargetCallback, aFirstGap, aLastGap);
1301
0
                                }
1302
1303
0
                                aSnippet.clear();
1304
0
                            }
1305
1306
                            // prepare next DotDashArray step and flip line/gap flag
1307
0
                            fLastDotDashMovingLength = fDotDashMovingLength;
1308
0
                            fDotDashMovingLength += aDotDashArray[(++nDotDashIndex) % nDotDashCount];
1309
0
                            bIsLine = !bIsLine;
1310
0
                        }
1311
1312
                        // append closing snippet [fLastDotDashMovingLength, fEdgeLength]
1313
0
                        const bool bHandleLine(bIsLine && rLineTargetCallback);
1314
0
                        const bool bHandleGap(!bIsLine && rGapTargetCallback);
1315
1316
0
                        if(bHandleLine || bHandleGap)
1317
0
                        {
1318
0
                            B2DCubicBezier aRight;
1319
0
                            const double fBezierSplit(aCubicBezierHelper.distanceToRelative(fLastDotDashMovingLength));
1320
1321
0
                            aCurrentEdge.split(fBezierSplit, nullptr, &aRight);
1322
1323
0
                            if(!aSnippet.count())
1324
0
                            {
1325
0
                                aSnippet.append(aRight.getStartPoint());
1326
0
                            }
1327
1328
0
                            aSnippet.appendBezierSegment(aRight.getControlPointA(), aRight.getControlPointB(), aRight.getEndPoint());
1329
0
                        }
1330
1331
                        // prepare move to next edge
1332
0
                        fDotDashMovingLength -= fEdgeLength;
1333
0
                    }
1334
0
                }
1335
0
                else
1336
0
                {
1337
                    // simple edge
1338
0
                    const double fEdgeLength(aCurrentEdge.getEdgeLength());
1339
1340
0
                    if(!fTools::equalZero(fEdgeLength))
1341
0
                    {
1342
0
                        while(fTools::less(fDotDashMovingLength, fEdgeLength))
1343
0
                        {
1344
                            // new split is inside edge, create and append snippet [fLastDotDashMovingLength, fDotDashMovingLength]
1345
0
                            const bool bHandleLine(bIsLine && rLineTargetCallback);
1346
0
                            const bool bHandleGap(!bIsLine && rGapTargetCallback);
1347
1348
0
                            if(bHandleLine || bHandleGap)
1349
0
                            {
1350
0
                                if(!aSnippet.count())
1351
0
                                {
1352
0
                                    aSnippet.append(interpolate(aCurrentEdge.getStartPoint(), aCurrentEdge.getEndPoint(), fLastDotDashMovingLength / fEdgeLength));
1353
0
                                }
1354
1355
0
                                aSnippet.append(interpolate(aCurrentEdge.getStartPoint(), aCurrentEdge.getEndPoint(), fDotDashMovingLength / fEdgeLength));
1356
1357
0
                                if(bHandleLine)
1358
0
                                {
1359
0
                                    implHandleSnippet(aSnippet, rLineTargetCallback, aFirstLine, aLastLine);
1360
0
                                }
1361
1362
0
                                if(bHandleGap)
1363
0
                                {
1364
0
                                    implHandleSnippet(aSnippet, rGapTargetCallback, aFirstGap, aLastGap);
1365
0
                                }
1366
1367
0
                                aSnippet.clear();
1368
0
                            }
1369
1370
                            // prepare next DotDashArray step and flip line/gap flag
1371
0
                            fLastDotDashMovingLength = fDotDashMovingLength;
1372
0
                            fDotDashMovingLength += aDotDashArray[(++nDotDashIndex) % nDotDashCount];
1373
0
                            bIsLine = !bIsLine;
1374
0
                        }
1375
1376
                        // append snippet [fLastDotDashMovingLength, fEdgeLength]
1377
0
                        const bool bHandleLine(bIsLine && rLineTargetCallback);
1378
0
                        const bool bHandleGap(!bIsLine && rGapTargetCallback);
1379
1380
0
                        if(bHandleLine || bHandleGap)
1381
0
                        {
1382
0
                            if(!aSnippet.count())
1383
0
                            {
1384
0
                                aSnippet.append(interpolate(aCurrentEdge.getStartPoint(), aCurrentEdge.getEndPoint(), fLastDotDashMovingLength / fEdgeLength));
1385
0
                            }
1386
1387
0
                            aSnippet.append(aCurrentEdge.getEndPoint());
1388
0
                        }
1389
1390
                        // prepare move to next edge
1391
0
                        fDotDashMovingLength -= fEdgeLength;
1392
0
                    }
1393
0
                }
1394
1395
                // prepare next edge step (end point gets new start point)
1396
0
                aCurrentEdge.setStartPoint(aCurrentEdge.getEndPoint());
1397
0
            }
1398
1399
            // append last intermediate results (if exists)
1400
0
            if(aSnippet.count())
1401
0
            {
1402
0
                const bool bHandleLine(bIsLine && rLineTargetCallback);
1403
0
                const bool bHandleGap(!bIsLine && rGapTargetCallback);
1404
1405
0
                if(bHandleLine)
1406
0
                {
1407
0
                    implHandleSnippet(aSnippet, rLineTargetCallback, aFirstLine, aLastLine);
1408
0
                }
1409
1410
0
                if(bHandleGap)
1411
0
                {
1412
0
                    implHandleSnippet(aSnippet, rGapTargetCallback, aFirstGap, aLastGap);
1413
0
                }
1414
0
            }
1415
1416
0
            if(bIsClosed && rLineTargetCallback)
1417
0
            {
1418
0
                implHandleFirstLast(rLineTargetCallback, aFirstLine, aLastLine);
1419
0
            }
1420
1421
0
            if(bIsClosed && rGapTargetCallback)
1422
0
            {
1423
0
                implHandleFirstLast(rGapTargetCallback, aFirstGap, aLastGap);
1424
0
            }
1425
0
        }
1426
1427
        // test if point is inside epsilon-range around an edge defined
1428
        // by the two given points. Can be used for HitTesting. The epsilon-range
1429
        // is defined to be the rectangle centered to the given edge, using height
1430
        // 2 x fDistance, and the circle around both points with radius fDistance.
1431
        bool isInEpsilonRange(const B2DPoint& rEdgeStart, const B2DPoint& rEdgeEnd, const B2DPoint& rTestPosition, double fDistance)
1432
0
        {
1433
            // build edge vector
1434
0
            const B2DVector aEdge(rEdgeEnd - rEdgeStart);
1435
0
            bool bDoDistanceTestStart(false);
1436
0
            bool bDoDistanceTestEnd(false);
1437
1438
0
            if(aEdge.equalZero())
1439
0
            {
1440
                // no edge, just a point. Do one of the distance tests.
1441
0
                bDoDistanceTestStart = true;
1442
0
            }
1443
0
            else
1444
0
            {
1445
                // edge has a length. Create perpendicular vector.
1446
0
                const B2DVector aPerpend(getPerpendicular(aEdge));
1447
0
                double fCut(
1448
0
                    (aPerpend.getY() * (rTestPosition.getX() - rEdgeStart.getX())
1449
0
                    + aPerpend.getX() * (rEdgeStart.getY() - rTestPosition.getY())) /
1450
0
                    (aEdge.getX() * aEdge.getX() + aEdge.getY() * aEdge.getY()));
1451
0
                const double fZero(0.0);
1452
0
                const double fOne(1.0);
1453
1454
0
                if(fTools::less(fCut, fZero))
1455
0
                {
1456
                    // left of rEdgeStart
1457
0
                    bDoDistanceTestStart = true;
1458
0
                }
1459
0
                else if(fTools::more(fCut, fOne))
1460
0
                {
1461
                    // right of rEdgeEnd
1462
0
                    bDoDistanceTestEnd = true;
1463
0
                }
1464
0
                else
1465
0
                {
1466
                    // inside line [0.0 .. 1.0]
1467
0
                    const B2DPoint aCutPoint(interpolate(rEdgeStart, rEdgeEnd, fCut));
1468
0
                    const B2DVector aDelta(rTestPosition - aCutPoint);
1469
0
                    const double fDistanceSquare(aDelta.scalar(aDelta));
1470
1471
0
                    return fDistanceSquare <= fDistance * fDistance;
1472
0
                }
1473
0
            }
1474
1475
0
            if(bDoDistanceTestStart)
1476
0
            {
1477
0
                const B2DVector aDelta(rTestPosition - rEdgeStart);
1478
0
                const double fDistanceSquare(aDelta.scalar(aDelta));
1479
1480
0
                if(fDistanceSquare <= fDistance * fDistance)
1481
0
                {
1482
0
                    return true;
1483
0
                }
1484
0
            }
1485
0
            else if(bDoDistanceTestEnd)
1486
0
            {
1487
0
                const B2DVector aDelta(rTestPosition - rEdgeEnd);
1488
0
                const double fDistanceSquare(aDelta.scalar(aDelta));
1489
1490
0
                if(fDistanceSquare <= fDistance * fDistance)
1491
0
                {
1492
0
                    return true;
1493
0
                }
1494
0
            }
1495
1496
0
            return false;
1497
0
        }
1498
1499
        // test if point is inside epsilon-range around the given Polygon. Can be used
1500
        // for HitTesting. The epsilon-range is defined to be the tube around the polygon
1501
        // with distance fDistance and rounded edges (start and end point).
1502
        bool isInEpsilonRange(const B2DPolygon& rCandidate, const B2DPoint& rTestPosition, double fDistance)
1503
0
        {
1504
            // force to non-bezier polygon
1505
0
            const B2DPolygon& aCandidate(rCandidate.getDefaultAdaptiveSubdivision());
1506
0
            const sal_uInt32 nPointCount(aCandidate.count());
1507
1508
0
            if(!nPointCount)
1509
0
                return false;
1510
1511
0
            const sal_uInt32 nEdgeCount(aCandidate.isClosed() ? nPointCount : nPointCount - 1);
1512
0
            B2DPoint aCurrent(aCandidate.getB2DPoint(0));
1513
1514
0
            if(nEdgeCount)
1515
0
            {
1516
                // edges
1517
0
                for(sal_uInt32 a(0); a < nEdgeCount; a++)
1518
0
                {
1519
0
                    const sal_uInt32 nNextIndex((a + 1) % nPointCount);
1520
0
                    const B2DPoint aNext(aCandidate.getB2DPoint(nNextIndex));
1521
1522
0
                    if(isInEpsilonRange(aCurrent, aNext, rTestPosition, fDistance))
1523
0
                    {
1524
0
                        return true;
1525
0
                    }
1526
1527
                    // prepare next step
1528
0
                    aCurrent = aNext;
1529
0
                }
1530
0
            }
1531
0
            else
1532
0
            {
1533
                // no edges, but points -> not closed. Check single point. Just
1534
                // use isInEpsilonRange with twice the same point, it handles those well
1535
0
                if(isInEpsilonRange(aCurrent, aCurrent, rTestPosition, fDistance))
1536
0
                {
1537
0
                    return true;
1538
0
                }
1539
0
            }
1540
1541
0
            return false;
1542
0
        }
1543
1544
        // Calculates distance of curve point to its control point for a Bézier curve, that
1545
        // approximates a unit circle arc. fAngle is the center angle of the circle arc. The
1546
        // constrain 0<=fAngle<=pi/2 must not be violated to give a useful accuracy. For details
1547
        // and alternatives read document "ApproxCircleInfo.odt", attachment of bug tdf#121425.
1548
        static double impDistanceBezierPointToControl(double fAngle)
1549
1.50M
        {
1550
1.50M
            SAL_WARN_IF(fAngle < 0 || fAngle > M_PI_2,"basegfx","angle not suitable for approximate circle");
1551
1.50M
            if (0 <= fAngle && fAngle <= M_PI_2)
1552
1.50M
            {
1553
1.50M
                return 4.0/3.0 * ( tan(fAngle/4.0));
1554
1.50M
            }
1555
1
            else
1556
1
                return 0;
1557
1.50M
        }
1558
1559
        B2DPolygon createPolygonFromRect( const B2DRectangle& rRect, double fRadiusX, double fRadiusY )
1560
293
        {
1561
293
            const double fZero(0.0);
1562
293
            const double fOne(1.0);
1563
1564
293
            fRadiusX = std::clamp(fRadiusX, 0.0, 1.0);
1565
293
            fRadiusY = std::clamp(fRadiusY, 0.0, 1.0);
1566
1567
293
            if(rtl::math::approxEqual(fZero, fRadiusX) || rtl::math::approxEqual(fZero, fRadiusY))
1568
293
            {
1569
                // at least in one direction no radius, use rectangle.
1570
                // Do not use createPolygonFromRect() here since original
1571
                // creator (historical reasons) still creates a start point at the
1572
                // bottom center, so do the same here to get the same line patterns.
1573
                // Due to this the order of points is different, too.
1574
293
                const B2DPoint aBottomCenter(rRect.getCenter().getX(), rRect.getMaxY());
1575
293
                B2DPolygon aPolygon {
1576
293
                    aBottomCenter,
1577
293
                    { rRect.getMinX(), rRect.getMaxY() },
1578
293
                    { rRect.getMinX(), rRect.getMinY() },
1579
293
                    { rRect.getMaxX(), rRect.getMinY() },
1580
293
                    { rRect.getMaxX(), rRect.getMaxY() }
1581
293
                };
1582
1583
                // close
1584
293
                aPolygon.setClosed( true );
1585
1586
293
                return aPolygon;
1587
293
            }
1588
0
            else if(rtl::math::approxEqual(fOne, fRadiusX) && rtl::math::approxEqual(fOne, fRadiusY))
1589
0
            {
1590
                // in both directions full radius, use ellipse
1591
0
                const B2DPoint aCenter(rRect.getCenter());
1592
0
                const double fRectRadiusX(rRect.getWidth() / 2.0);
1593
0
                const double fRectRadiusY(rRect.getHeight() / 2.0);
1594
1595
0
                return createPolygonFromEllipse( aCenter, fRectRadiusX, fRectRadiusY );
1596
0
            }
1597
0
            else
1598
0
            {
1599
0
                B2DPolygon aRetval;
1600
0
                const double fBowX((rRect.getWidth() / 2.0) * fRadiusX);
1601
0
                const double fBowY((rRect.getHeight() / 2.0) * fRadiusY);
1602
0
                const double fKappa(impDistanceBezierPointToControl(M_PI_2));
1603
1604
                // create start point at bottom center
1605
0
                if(!rtl::math::approxEqual(fOne, fRadiusX))
1606
0
                {
1607
0
                    const B2DPoint aBottomCenter(rRect.getCenter().getX(), rRect.getMaxY());
1608
0
                    aRetval.append(aBottomCenter);
1609
0
                }
1610
1611
                // create first bow
1612
0
                {
1613
0
                    const B2DPoint aBottomRight(rRect.getMaxX(), rRect.getMaxY());
1614
0
                    const B2DPoint aStart(aBottomRight + B2DPoint(-fBowX, 0.0));
1615
0
                    const B2DPoint aStop(aBottomRight + B2DPoint(0.0, -fBowY));
1616
0
                    aRetval.append(aStart);
1617
0
                    aRetval.appendBezierSegment(interpolate(aStart, aBottomRight, fKappa), interpolate(aStop, aBottomRight, fKappa), aStop);
1618
0
                }
1619
1620
                // create second bow
1621
0
                {
1622
0
                    const B2DPoint aTopRight(rRect.getMaxX(), rRect.getMinY());
1623
0
                    const B2DPoint aStart(aTopRight + B2DPoint(0.0, fBowY));
1624
0
                    const B2DPoint aStop(aTopRight + B2DPoint(-fBowX, 0.0));
1625
0
                    aRetval.append(aStart);
1626
0
                    aRetval.appendBezierSegment(interpolate(aStart, aTopRight, fKappa), interpolate(aStop, aTopRight, fKappa), aStop);
1627
0
                }
1628
1629
                // create third bow
1630
0
                {
1631
0
                    const B2DPoint aTopLeft(rRect.getMinX(), rRect.getMinY());
1632
0
                    const B2DPoint aStart(aTopLeft + B2DPoint(fBowX, 0.0));
1633
0
                    const B2DPoint aStop(aTopLeft + B2DPoint(0.0, fBowY));
1634
0
                    aRetval.append(aStart);
1635
0
                    aRetval.appendBezierSegment(interpolate(aStart, aTopLeft, fKappa), interpolate(aStop, aTopLeft, fKappa), aStop);
1636
0
                }
1637
1638
                // create forth bow
1639
0
                {
1640
0
                    const B2DPoint aBottomLeft(rRect.getMinX(), rRect.getMaxY());
1641
0
                    const B2DPoint aStart(aBottomLeft + B2DPoint(0.0, -fBowY));
1642
0
                    const B2DPoint aStop(aBottomLeft + B2DPoint(fBowX, 0.0));
1643
0
                    aRetval.append(aStart);
1644
0
                    aRetval.appendBezierSegment(interpolate(aStart, aBottomLeft, fKappa), interpolate(aStop, aBottomLeft, fKappa), aStop);
1645
0
                }
1646
1647
                // close
1648
0
                aRetval.setClosed( true );
1649
1650
                // remove double created points if there are extreme radii involved
1651
0
                if(rtl::math::approxEqual(fOne, fRadiusX) || rtl::math::approxEqual(fOne, fRadiusY))
1652
0
                {
1653
0
                    aRetval.removeDoublePoints();
1654
0
                }
1655
1656
0
                return aRetval;
1657
0
            }
1658
293
        }
1659
1660
        B2DPolygon createPolygonFromRect( const B2DRectangle& rRect )
1661
65.1k
        {
1662
65.1k
            B2DPolygon aPolygon {
1663
65.1k
                { rRect.getMinX(), rRect.getMinY() },
1664
65.1k
                { rRect.getMaxX(), rRect.getMinY() },
1665
65.1k
                { rRect.getMaxX(), rRect.getMaxY() },
1666
65.1k
                { rRect.getMinX(), rRect.getMaxY() }
1667
65.1k
            };
1668
1669
            // close
1670
65.1k
            aPolygon.setClosed( true );
1671
1672
65.1k
            return aPolygon;
1673
65.1k
        }
1674
1675
        B2DPolygon const & createUnitPolygon()
1676
643
        {
1677
643
            static auto const singleton = [] {
1678
2
                    B2DPolygon aPolygon {
1679
2
                        { 0.0, 0.0 },
1680
2
                        { 1.0, 0.0 },
1681
2
                        { 1.0, 1.0 },
1682
2
                        { 0.0, 1.0 }
1683
2
                    };
1684
1685
                    // close
1686
2
                    aPolygon.setClosed( true );
1687
1688
2
                    return aPolygon;
1689
2
            }();
1690
643
            return singleton;
1691
643
        }
1692
1693
        B2DPolygon createPolygonFromCircle( const B2DPoint& rCenter, double fRadius )
1694
8
        {
1695
8
            return createPolygonFromEllipse( rCenter, fRadius, fRadius );
1696
8
        }
1697
1698
        static B2DPolygon impCreateUnitCircle(sal_uInt32 nStartQuadrant)
1699
8
        {
1700
8
            B2DPolygon aUnitCircle;
1701
8
            const double fSegmentKappa = impDistanceBezierPointToControl(M_PI_2 / STEPSPERQUARTER);
1702
8
            const B2DHomMatrix aRotateMatrix(createRotateB2DHomMatrix(M_PI_2 / STEPSPERQUARTER));
1703
1704
8
            B2DPoint aPoint(1.0, 0.0);
1705
8
            B2DPoint aForward(1.0, fSegmentKappa);
1706
8
            B2DPoint aBackward(1.0, -fSegmentKappa);
1707
1708
8
            if(nStartQuadrant != 0)
1709
3
            {
1710
3
                const B2DHomMatrix aQuadrantMatrix(createRotateB2DHomMatrix(M_PI_2 * (nStartQuadrant % 4)));
1711
3
                aPoint *= aQuadrantMatrix;
1712
3
                aBackward *= aQuadrantMatrix;
1713
3
                aForward *= aQuadrantMatrix;
1714
3
            }
1715
1716
8
            aUnitCircle.append(aPoint);
1717
1718
104
            for(sal_uInt32 a(0); a < STEPSPERQUARTER * 4; a++)
1719
96
            {
1720
96
                aPoint *= aRotateMatrix;
1721
96
                aBackward *= aRotateMatrix;
1722
96
                aUnitCircle.appendBezierSegment(aForward, aBackward, aPoint);
1723
96
                aForward *= aRotateMatrix;
1724
96
            }
1725
1726
8
            aUnitCircle.setClosed(true);
1727
8
            aUnitCircle.removeDoublePoints();
1728
1729
8
            return aUnitCircle;
1730
8
        }
1731
1732
        B2DPolygon const & createHalfUnitCircle()
1733
12
        {
1734
12
            static auto const singleton = [] {
1735
1
                    B2DPolygon aUnitHalfCircle;
1736
1
                    const double fSegmentKappa(impDistanceBezierPointToControl(M_PI_2 / STEPSPERQUARTER));
1737
1
                    const B2DHomMatrix aRotateMatrix(createRotateB2DHomMatrix(M_PI_2 / STEPSPERQUARTER));
1738
1
                    B2DPoint aPoint(1.0, 0.0);
1739
1
                    B2DPoint aForward(1.0, fSegmentKappa);
1740
1
                    B2DPoint aBackward(1.0, -fSegmentKappa);
1741
1742
1
                    aUnitHalfCircle.append(aPoint);
1743
1744
7
                    for(sal_uInt32 a(0); a < STEPSPERQUARTER * 2; a++)
1745
6
                    {
1746
6
                        aPoint *= aRotateMatrix;
1747
6
                        aBackward *= aRotateMatrix;
1748
6
                        aUnitHalfCircle.appendBezierSegment(aForward, aBackward, aPoint);
1749
6
                        aForward *= aRotateMatrix;
1750
6
                    }
1751
1
                    return aUnitHalfCircle;
1752
1
                }();
1753
12
            return singleton;
1754
12
        }
1755
1756
        B2DPolygon const & createPolygonFromUnitCircle(sal_uInt32 nStartQuadrant)
1757
2.54k
        {
1758
2.54k
            switch(nStartQuadrant % 4)
1759
2.54k
            {
1760
1.95k
                case 1 :
1761
1.95k
                    {
1762
1.95k
                        static auto const singleton = impCreateUnitCircle(1);
1763
1.95k
                        return singleton;
1764
0
                    }
1765
1766
0
                case 2 :
1767
0
                    {
1768
0
                        static auto const singleton = impCreateUnitCircle(2);
1769
0
                        return singleton;
1770
0
                    }
1771
1772
0
                case 3 :
1773
0
                    {
1774
0
                        static auto const singleton = impCreateUnitCircle(3);
1775
0
                        return singleton;
1776
0
                    }
1777
1778
585
                default : // case 0 :
1779
585
                    {
1780
585
                        static auto const singleton = impCreateUnitCircle(0);
1781
585
                        return singleton;
1782
0
                    }
1783
2.54k
            }
1784
2.54k
        }
1785
1786
        B2DPolygon createPolygonFromEllipse( const B2DPoint& rCenter, double fRadiusX, double fRadiusY, sal_uInt32 nStartQuadrant)
1787
585
        {
1788
585
            B2DPolygon aRetval(createPolygonFromUnitCircle(nStartQuadrant));
1789
585
            const B2DHomMatrix aMatrix(createScaleTranslateB2DHomMatrix(fRadiusX, fRadiusY, rCenter.getX(), rCenter.getY()));
1790
1791
585
            aRetval.transform(aMatrix);
1792
1793
585
            return aRetval;
1794
585
        }
1795
1796
        B2DPolygon createPolygonFromUnitEllipseSegment( double fStart, double fEnd )
1797
519k
        {
1798
519k
            B2DPolygon aRetval;
1799
1800
            // truncate fStart, fEnd to a range of [0.0 .. 2PI[ where 2PI
1801
            // falls back to 0.0 to ensure a unique definition
1802
519k
            if(fStart < 0.0)
1803
0
            {
1804
0
                fStart = 0.0;
1805
0
            }
1806
1807
519k
            if(fTools::moreOrEqual(fStart, 2 * M_PI))
1808
4
            {
1809
4
                fStart = 0.0;
1810
4
            }
1811
1812
519k
            if(fEnd < 0.0)
1813
0
            {
1814
0
                fEnd = 0.0;
1815
0
            }
1816
1817
519k
            if(fTools::moreOrEqual(fEnd, 2 * M_PI))
1818
8.94k
            {
1819
8.94k
                fEnd = 0.0;
1820
8.94k
            }
1821
1822
519k
            if(fTools::equal(fStart, fEnd))
1823
2.91k
            {
1824
                // same start and end angle, add single point
1825
2.91k
                aRetval.append(B2DPoint(cos(fStart), sin(fStart)));
1826
2.91k
            }
1827
517k
            else
1828
517k
            {
1829
517k
                const sal_uInt32 nSegments(STEPSPERQUARTER * 4);
1830
517k
                const double fAnglePerSegment(M_PI_2 / STEPSPERQUARTER);
1831
517k
                const sal_uInt32 nStartSegment(sal_uInt32(fStart / fAnglePerSegment) % nSegments);
1832
517k
                const sal_uInt32 nEndSegment(sal_uInt32(fEnd / fAnglePerSegment) % nSegments);
1833
517k
                const double fSegmentKappa(impDistanceBezierPointToControl(fAnglePerSegment));
1834
1835
517k
                B2DPoint aSegStart(cos(fStart), sin(fStart));
1836
517k
                aRetval.append(aSegStart);
1837
1838
517k
                if(nStartSegment == nEndSegment && fTools::more(fEnd, fStart))
1839
45.6k
                {
1840
                    // start and end in one sector and in the right order, create in one segment
1841
45.6k
                    const B2DPoint aSegEnd(cos(fEnd), sin(fEnd));
1842
45.6k
                    const double fFactor(impDistanceBezierPointToControl(fEnd - fStart));
1843
1844
45.6k
                    aRetval.appendBezierSegment(
1845
45.6k
                        aSegStart + (B2DPoint(-aSegStart.getY(), aSegStart.getX()) * fFactor),
1846
45.6k
                        aSegEnd - (B2DPoint(-aSegEnd.getY(), aSegEnd.getX()) * fFactor),
1847
45.6k
                        aSegEnd);
1848
45.6k
                }
1849
471k
                else
1850
471k
                {
1851
471k
                    double fSegEndRad((nStartSegment + 1) * fAnglePerSegment);
1852
471k
                    double fFactor(impDistanceBezierPointToControl(fSegEndRad - fStart));
1853
471k
                    B2DPoint aSegEnd(cos(fSegEndRad), sin(fSegEndRad));
1854
1855
471k
                    aRetval.appendBezierSegment(
1856
471k
                        aSegStart + (B2DPoint(-aSegStart.getY(), aSegStart.getX()) * fFactor),
1857
471k
                        aSegEnd - (B2DPoint(-aSegEnd.getY(), aSegEnd.getX()) * fFactor),
1858
471k
                        aSegEnd);
1859
1860
471k
                    sal_uInt32 nSegment((nStartSegment + 1) % nSegments);
1861
471k
                    aSegStart = aSegEnd;
1862
1863
2.60M
                    while(nSegment != nEndSegment)
1864
2.12M
                    {
1865
                        // No end in this sector, add full sector.
1866
2.12M
                        fSegEndRad = (nSegment + 1) * fAnglePerSegment;
1867
2.12M
                        aSegEnd = B2DPoint(cos(fSegEndRad), sin(fSegEndRad));
1868
1869
2.12M
                        aRetval.appendBezierSegment(
1870
2.12M
                            aSegStart + (B2DPoint(-aSegStart.getY(), aSegStart.getX()) * fSegmentKappa),
1871
2.12M
                            aSegEnd - (B2DPoint(-aSegEnd.getY(), aSegEnd.getX()) * fSegmentKappa),
1872
2.12M
                            aSegEnd);
1873
1874
2.12M
                        nSegment = (nSegment + 1) % nSegments;
1875
2.12M
                        aSegStart = aSegEnd;
1876
2.12M
                    }
1877
1878
                    // End in this sector
1879
471k
                    const double fSegStartRad(nSegment * fAnglePerSegment);
1880
471k
                    fFactor= impDistanceBezierPointToControl(fEnd - fSegStartRad);
1881
471k
                    aSegEnd = B2DPoint(cos(fEnd), sin(fEnd));
1882
1883
471k
                    aRetval.appendBezierSegment(
1884
471k
                        aSegStart + (B2DPoint(-aSegStart.getY(), aSegStart.getX()) * fFactor),
1885
471k
                        aSegEnd - (B2DPoint(-aSegEnd.getY(), aSegEnd.getX()) * fFactor),
1886
471k
                        aSegEnd);
1887
471k
                }
1888
517k
            }
1889
1890
            // remove double points between segments created by segmented creation
1891
519k
            aRetval.removeDoublePoints();
1892
1893
519k
            return aRetval;
1894
519k
        }
1895
1896
        B2DPolygon createPolygonFromEllipseSegment( const B2DPoint& rCenter, double fRadiusX, double fRadiusY, double fStart, double fEnd )
1897
519k
        {
1898
519k
            B2DPolygon aRetval(createPolygonFromUnitEllipseSegment(fStart, fEnd));
1899
519k
            const B2DHomMatrix aMatrix(createScaleTranslateB2DHomMatrix(fRadiusX, fRadiusY, rCenter.getX(), rCenter.getY()));
1900
1901
519k
            aRetval.transform(aMatrix);
1902
1903
519k
            return aRetval;
1904
519k
        }
1905
1906
        bool hasNeutralPoints(const B2DPolygon& rCandidate)
1907
0
        {
1908
0
            OSL_ENSURE(!rCandidate.areControlPointsUsed(), "hasNeutralPoints: ATM works not for curves (!)");
1909
0
            const sal_uInt32 nPointCount(rCandidate.count());
1910
1911
0
            if(nPointCount <= 2)
1912
0
                return false;
1913
1914
0
            B2DPoint aPrevPoint(rCandidate.getB2DPoint(nPointCount - 1));
1915
0
            B2DPoint aCurrPoint(rCandidate.getB2DPoint(0));
1916
1917
0
            for(sal_uInt32 a(0); a < nPointCount; a++)
1918
0
            {
1919
0
                const B2DPoint aNextPoint(rCandidate.getB2DPoint((a + 1) % nPointCount));
1920
0
                const B2DVector aPrevVec(aPrevPoint - aCurrPoint);
1921
0
                const B2DVector aNextVec(aNextPoint - aCurrPoint);
1922
0
                const B2VectorOrientation aOrientation(getOrientation(aNextVec, aPrevVec));
1923
1924
0
                if(aOrientation == B2VectorOrientation::Neutral)
1925
0
                {
1926
                    // current has neutral orientation
1927
0
                    return true;
1928
0
                }
1929
0
                else
1930
0
                {
1931
                    // prepare next
1932
0
                    aPrevPoint = aCurrPoint;
1933
0
                    aCurrPoint = aNextPoint;
1934
0
                }
1935
0
            }
1936
1937
0
            return false;
1938
0
        }
1939
1940
        B2DPolygon removeNeutralPoints(const B2DPolygon& rCandidate)
1941
0
        {
1942
0
            if(hasNeutralPoints(rCandidate))
1943
0
            {
1944
0
                const sal_uInt32 nPointCount(rCandidate.count());
1945
0
                B2DPolygon aRetval;
1946
0
                B2DPoint aPrevPoint(rCandidate.getB2DPoint(nPointCount - 1));
1947
0
                B2DPoint aCurrPoint(rCandidate.getB2DPoint(0));
1948
1949
0
                for(sal_uInt32 a(0); a < nPointCount; a++)
1950
0
                {
1951
0
                    const B2DPoint aNextPoint(rCandidate.getB2DPoint((a + 1) % nPointCount));
1952
0
                    const B2DVector aPrevVec(aPrevPoint - aCurrPoint);
1953
0
                    const B2DVector aNextVec(aNextPoint - aCurrPoint);
1954
0
                    const B2VectorOrientation aOrientation(getOrientation(aNextVec, aPrevVec));
1955
1956
0
                    if(aOrientation == B2VectorOrientation::Neutral)
1957
0
                    {
1958
                        // current has neutral orientation, leave it out and prepare next
1959
0
                        aCurrPoint = aNextPoint;
1960
0
                    }
1961
0
                    else
1962
0
                    {
1963
                        // add current point
1964
0
                        aRetval.append(aCurrPoint);
1965
1966
                        // prepare next
1967
0
                        aPrevPoint = aCurrPoint;
1968
0
                        aCurrPoint = aNextPoint;
1969
0
                    }
1970
0
                }
1971
1972
0
                while(aRetval.count() && getOrientationForIndex(aRetval, 0) == B2VectorOrientation::Neutral)
1973
0
                {
1974
0
                    aRetval.remove(0);
1975
0
                }
1976
1977
                // copy closed state
1978
0
                aRetval.setClosed(rCandidate.isClosed());
1979
1980
0
                return aRetval;
1981
0
            }
1982
0
            else
1983
0
            {
1984
0
                return rCandidate;
1985
0
            }
1986
0
        }
1987
1988
        bool isConvex(const B2DPolygon& rCandidate)
1989
0
        {
1990
0
            OSL_ENSURE(!rCandidate.areControlPointsUsed(), "isConvex: ATM works not for curves (!)");
1991
0
            const sal_uInt32 nPointCount(rCandidate.count());
1992
1993
0
            if(nPointCount <= 2)
1994
0
                return true;
1995
1996
0
            const B2DPoint aPrevPoint(rCandidate.getB2DPoint(nPointCount - 1));
1997
0
            B2DPoint aCurrPoint(rCandidate.getB2DPoint(0));
1998
0
            B2DVector aCurrVec(aPrevPoint - aCurrPoint);
1999
0
            B2VectorOrientation aOrientation(B2VectorOrientation::Neutral);
2000
2001
0
            for(sal_uInt32 a(0); a < nPointCount; a++)
2002
0
            {
2003
0
                const B2DPoint aNextPoint(rCandidate.getB2DPoint((a + 1) % nPointCount));
2004
0
                const B2DVector aNextVec(aNextPoint - aCurrPoint);
2005
0
                const B2VectorOrientation aCurrentOrientation(getOrientation(aNextVec, aCurrVec));
2006
2007
0
                if(aOrientation == B2VectorOrientation::Neutral)
2008
0
                {
2009
                    // set start value, maybe neutral again
2010
0
                    aOrientation = aCurrentOrientation;
2011
0
                }
2012
0
                else
2013
0
                {
2014
0
                    if(aCurrentOrientation != B2VectorOrientation::Neutral && aCurrentOrientation != aOrientation)
2015
0
                    {
2016
                        // different orientations found, that's it
2017
0
                        return false;
2018
0
                    }
2019
0
                }
2020
2021
                // prepare next
2022
0
                aCurrPoint = aNextPoint;
2023
0
                aCurrVec = -aNextVec;
2024
0
            }
2025
2026
0
            return true;
2027
0
        }
2028
2029
        B2VectorOrientation getOrientationForIndex(const B2DPolygon& rCandidate, sal_uInt32 nIndex)
2030
0
        {
2031
0
            OSL_ENSURE(nIndex < rCandidate.count(), "getOrientationForIndex: index out of range (!)");
2032
0
            const B2DPoint aPrev(rCandidate.getB2DPoint(getIndexOfPredecessor(nIndex, rCandidate)));
2033
0
            const B2DPoint aCurr(rCandidate.getB2DPoint(nIndex));
2034
0
            const B2DPoint aNext(rCandidate.getB2DPoint(getIndexOfSuccessor(nIndex, rCandidate)));
2035
0
            const B2DVector aBack(aPrev - aCurr);
2036
0
            const B2DVector aForw(aNext - aCurr);
2037
2038
0
            return getOrientation(aForw, aBack);
2039
0
        }
2040
2041
        bool isPointOnLine(const B2DPoint& rStart, const B2DPoint& rEnd, const B2DPoint& rCandidate, bool bWithPoints)
2042
1.11G
        {
2043
1.11G
            if(rCandidate.equal(rStart) || rCandidate.equal(rEnd))
2044
833k
            {
2045
                // candidate is in epsilon around start or end -> inside
2046
833k
                return bWithPoints;
2047
833k
            }
2048
1.11G
            else if(rStart.equal(rEnd))
2049
1.74M
            {
2050
                // start and end are equal, but candidate is outside their epsilon -> outside
2051
1.74M
                return false;
2052
1.74M
            }
2053
1.11G
            else
2054
1.11G
            {
2055
1.11G
                const B2DVector aEdgeVector(rEnd - rStart);
2056
1.11G
                const B2DVector aTestVector(rCandidate - rStart);
2057
2058
1.11G
                if(areParallel(aEdgeVector, aTestVector))
2059
52.2M
                {
2060
52.2M
                    const double fZero(0.0);
2061
52.2M
                    const double fOne(1.0);
2062
52.2M
                    const double fParamTestOnCurr(fabs(aEdgeVector.getX()) > fabs(aEdgeVector.getY())
2063
52.2M
                        ? aTestVector.getX() / aEdgeVector.getX()
2064
52.2M
                        : aTestVector.getY() / aEdgeVector.getY());
2065
2066
52.2M
                    if(fTools::more(fParamTestOnCurr, fZero) && fTools::less(fParamTestOnCurr, fOne))
2067
50.0k
                    {
2068
50.0k
                        return true;
2069
50.0k
                    }
2070
52.2M
                }
2071
2072
1.11G
                return false;
2073
1.11G
            }
2074
1.11G
        }
2075
2076
        bool isPointOnPolygon(const B2DPolygon& rCandidate, const B2DPoint& rPoint, bool bWithPoints)
2077
13.7M
        {
2078
13.7M
            const B2DPolygon aCandidate(rCandidate.areControlPointsUsed() ? rCandidate.getDefaultAdaptiveSubdivision() : rCandidate);
2079
13.7M
            const sal_uInt32 nPointCount(aCandidate.count());
2080
2081
13.7M
            if(nPointCount > 1)
2082
13.7M
            {
2083
13.7M
                const sal_uInt32 nLoopCount(aCandidate.isClosed() ? nPointCount : nPointCount - 1);
2084
13.7M
                B2DPoint aCurrentPoint(aCandidate.getB2DPoint(0));
2085
2086
1.12G
                for(sal_uInt32 a(0); a < nLoopCount; a++)
2087
1.11G
                {
2088
1.11G
                    const B2DPoint aNextPoint(aCandidate.getB2DPoint((a + 1) % nPointCount));
2089
2090
1.11G
                    if(isPointOnLine(aCurrentPoint, aNextPoint, rPoint, bWithPoints))
2091
883k
                    {
2092
883k
                        return true;
2093
883k
                    }
2094
2095
1.11G
                    aCurrentPoint = aNextPoint;
2096
1.11G
                }
2097
13.7M
            }
2098
0
            else if(nPointCount && bWithPoints)
2099
0
            {
2100
0
                return rPoint.equal(aCandidate.getB2DPoint(0));
2101
0
            }
2102
2103
12.8M
            return false;
2104
13.7M
        }
2105
2106
        bool isPointInTriangle(const B2DPoint& rA, const B2DPoint& rB, const B2DPoint& rC, const B2DPoint& rCandidate, bool bWithBorder)
2107
0
        {
2108
0
            if(arePointsOnSameSideOfLine(rA, rB, rC, rCandidate, bWithBorder))
2109
0
            {
2110
0
                if(arePointsOnSameSideOfLine(rB, rC, rA, rCandidate, bWithBorder))
2111
0
                {
2112
0
                    if(arePointsOnSameSideOfLine(rC, rA, rB, rCandidate, bWithBorder))
2113
0
                    {
2114
0
                        return true;
2115
0
                    }
2116
0
                }
2117
0
            }
2118
2119
0
            return false;
2120
0
        }
2121
2122
        bool arePointsOnSameSideOfLine(const B2DPoint& rStart, const B2DPoint& rEnd, const B2DPoint& rCandidateA, const B2DPoint& rCandidateB, bool bWithLine)
2123
0
        {
2124
0
            const B2DVector aLineVector(rEnd - rStart);
2125
0
            const B2DVector aVectorToA(rEnd - rCandidateA);
2126
0
            const double fCrossA(aLineVector.cross(aVectorToA));
2127
2128
            // tdf#88352 increase numerical correctness and use rtl::math::approxEqual
2129
            // instead of fTools::equalZero which compares with a fixed small value
2130
0
            if(fCrossA == 0.0)
2131
0
            {
2132
                // one point on the line
2133
0
                return bWithLine;
2134
0
            }
2135
2136
0
            const B2DVector aVectorToB(rEnd - rCandidateB);
2137
0
            const double fCrossB(aLineVector.cross(aVectorToB));
2138
2139
            // increase numerical correctness
2140
0
            if(fCrossB == 0.0)
2141
0
            {
2142
                // one point on the line
2143
0
                return bWithLine;
2144
0
            }
2145
2146
            // return true if they both have the same sign
2147
0
            return ((fCrossA > 0.0) == (fCrossB > 0.0));
2148
0
        }
2149
2150
        void addTriangleFan(
2151
            const B2DPolygon& rCandidate,
2152
            triangulator::B2DTriangleVector& rTarget)
2153
0
        {
2154
0
            const sal_uInt32 nCount(rCandidate.count());
2155
2156
0
            if(nCount <= 2)
2157
0
                return;
2158
2159
0
            const B2DPoint aStart(rCandidate.getB2DPoint(0));
2160
0
            B2DPoint aLast(rCandidate.getB2DPoint(1));
2161
2162
0
            for(sal_uInt32 a(2); a < nCount; a++)
2163
0
            {
2164
0
                const B2DPoint aCurrent(rCandidate.getB2DPoint(a));
2165
0
                rTarget.emplace_back(
2166
0
                    aStart,
2167
0
                    aLast,
2168
0
                    aCurrent);
2169
2170
                // prepare next
2171
0
                aLast = aCurrent;
2172
0
            }
2173
0
        }
2174
2175
        namespace
2176
        {
2177
            /// return 0 for input of 0, -1 for negative and 1 for positive input
2178
            int lcl_sgn( const double n )
2179
3.07M
            {
2180
3.07M
                return n == 0.0 ? 0 : 1 - 2*int(std::signbit(n));
2181
3.07M
            }
2182
        }
2183
2184
        bool isRectangle( const B2DPolygon& rPoly )
2185
385k
        {
2186
            // polygon must be closed to resemble a rect, and contain
2187
            // at least four points.
2188
385k
            if( !rPoly.isClosed() ||
2189
385k
                rPoly.count() < 4 ||
2190
384k
                rPoly.areControlPointsUsed() )
2191
1.08k
            {
2192
1.08k
                return false;
2193
1.08k
            }
2194
2195
            // number of 90 degree turns the polygon has taken
2196
384k
            int nNumTurns(0);
2197
2198
384k
            int  nVerticalEdgeType=0;
2199
384k
            int  nHorizontalEdgeType=0;
2200
384k
            bool bNullVertex(true);
2201
384k
            bool bCWPolygon(false);  // when true, polygon is CW
2202
                                     // oriented, when false, CCW
2203
384k
            bool bOrientationSet(false); // when false, polygon
2204
                                         // orientation has not yet
2205
                                         // been determined.
2206
2207
            // scan all _edges_ (which involves coming back to point 0
2208
            // for the last edge - thus the modulo operation below)
2209
384k
            const sal_Int32 nCount( rPoly.count() );
2210
1.92M
            for( sal_Int32 i=0; i<nCount; ++i )
2211
1.53M
            {
2212
1.53M
                const B2DPoint& rPoint0( rPoly.getB2DPoint(i % nCount) );
2213
1.53M
                const B2DPoint& rPoint1( rPoly.getB2DPoint((i+1) % nCount) );
2214
2215
                // is 0 for zero direction vector, 1 for south edge and -1
2216
                // for north edge (standard screen coordinate system)
2217
1.53M
                int nCurrVerticalEdgeType( lcl_sgn( rPoint1.getY() - rPoint0.getY() ) );
2218
2219
                // is 0 for zero direction vector, 1 for east edge and -1
2220
                // for west edge (standard screen coordinate system)
2221
1.53M
                int nCurrHorizontalEdgeType( lcl_sgn(rPoint1.getX() - rPoint0.getX()) );
2222
2223
1.53M
                if( nCurrVerticalEdgeType && nCurrHorizontalEdgeType )
2224
71
                    return false; // oblique edge - for sure no rect
2225
2226
1.53M
                const bool bCurrNullVertex( !nCurrVerticalEdgeType && !nCurrHorizontalEdgeType );
2227
2228
                // current vertex is equal to previous - just skip,
2229
                // until we have a real edge
2230
1.53M
                if( bCurrNullVertex )
2231
791
                    continue;
2232
2233
                // if previous edge has two identical points, because
2234
                // no previous edge direction was available, simply
2235
                // take this first non-null edge as the start
2236
                // direction. That's what will happen here, if
2237
                // bNullVertex is false
2238
1.53M
                if( !bNullVertex )
2239
1.15M
                {
2240
                    // 2D cross product - is 1 for CW and -1 for CCW turns
2241
1.15M
                    const int nCrossProduct( nHorizontalEdgeType*nCurrVerticalEdgeType -
2242
1.15M
                                             nVerticalEdgeType*nCurrHorizontalEdgeType );
2243
2244
1.15M
                    if( !nCrossProduct )
2245
1.26k
                        continue; // no change in orientation -
2246
                                  // collinear edges - just go on
2247
2248
                    // if polygon orientation is not set, we'll
2249
                    // determine it now
2250
1.15M
                    if( !bOrientationSet )
2251
384k
                    {
2252
384k
                        bCWPolygon = nCrossProduct == 1;
2253
384k
                        bOrientationSet = true;
2254
384k
                    }
2255
767k
                    else
2256
767k
                    {
2257
                        // if current turn orientation is not equal
2258
                        // initial orientation, this is not a
2259
                        // rectangle (as rectangles have consistent
2260
                        // orientation).
2261
767k
                        if( (nCrossProduct == 1) != bCWPolygon )
2262
1
                            return false;
2263
767k
                    }
2264
2265
1.15M
                    ++nNumTurns;
2266
2267
                    // More than four 90 degree turns are an
2268
                    // indication that this must not be a rectangle.
2269
1.15M
                    if( nNumTurns > 4 )
2270
0
                        return false;
2271
1.15M
                }
2272
2273
                // store current state for the next turn
2274
1.53M
                nVerticalEdgeType   = nCurrVerticalEdgeType;
2275
1.53M
                nHorizontalEdgeType = nCurrHorizontalEdgeType;
2276
1.53M
                bNullVertex         = false; // won't reach this line,
2277
                                             // if bCurrNullVertex is
2278
                                             // true - see above
2279
1.53M
            }
2280
2281
384k
            return true;
2282
384k
        }
2283
2284
        B3DPolygon createB3DPolygonFromB2DPolygon(const B2DPolygon& rCandidate, double fZCoordinate)
2285
0
        {
2286
0
            if(rCandidate.areControlPointsUsed())
2287
0
            {
2288
                // call myself recursively with subdivided input
2289
0
                const B2DPolygon aCandidate(adaptiveSubdivideByAngle(rCandidate));
2290
0
                return createB3DPolygonFromB2DPolygon(aCandidate, fZCoordinate);
2291
0
            }
2292
0
            else
2293
0
            {
2294
0
                B3DPolygon aRetval;
2295
2296
0
                for(sal_uInt32 a(0); a < rCandidate.count(); a++)
2297
0
                {
2298
0
                    B2DPoint aPoint(rCandidate.getB2DPoint(a));
2299
0
                    aRetval.append(B3DPoint(aPoint.getX(), aPoint.getY(), fZCoordinate));
2300
0
                }
2301
2302
                // copy closed state
2303
0
                aRetval.setClosed(rCandidate.isClosed());
2304
2305
0
                return aRetval;
2306
0
            }
2307
0
        }
2308
2309
        B2DPolygon createB2DPolygonFromB3DPolygon(const B3DPolygon& rCandidate, const B3DHomMatrix& rMat)
2310
0
        {
2311
0
            B2DPolygon aRetval;
2312
0
            const sal_uInt32 nCount(rCandidate.count());
2313
0
            const bool bIsIdentity(rMat.isIdentity());
2314
2315
0
            for(sal_uInt32 a(0); a < nCount; a++)
2316
0
            {
2317
0
                B3DPoint aCandidate(rCandidate.getB3DPoint(a));
2318
2319
0
                if(!bIsIdentity)
2320
0
                {
2321
0
                    aCandidate *= rMat;
2322
0
                }
2323
2324
0
                aRetval.append(B2DPoint(aCandidate.getX(), aCandidate.getY()));
2325
0
            }
2326
2327
            // copy closed state
2328
0
            aRetval.setClosed(rCandidate.isClosed());
2329
2330
0
            return aRetval;
2331
0
        }
2332
2333
        double getSmallestDistancePointToEdge(const B2DPoint& rPointA, const B2DPoint& rPointB, const B2DPoint& rTestPoint, double& rCut)
2334
0
        {
2335
0
            if(rPointA.equal(rPointB))
2336
0
            {
2337
0
                rCut = 0.0;
2338
0
                const B2DVector aVector(rTestPoint - rPointA);
2339
0
                return aVector.getLength();
2340
0
            }
2341
0
            else
2342
0
            {
2343
                // get the relative cut value on line vector (Vector1) for cut with perpendicular through TestPoint
2344
0
                const B2DVector aVector1(rPointB - rPointA);
2345
0
                const B2DVector aVector2(rTestPoint - rPointA);
2346
0
                const double fDividend((aVector2.getX() * aVector1.getX()) + (aVector2.getY() * aVector1.getY()));
2347
0
                const double fDivisor((aVector1.getX() * aVector1.getX()) + (aVector1.getY() * aVector1.getY()));
2348
0
                const double fCut(fDividend / fDivisor);
2349
2350
0
                if(fCut < 0.0)
2351
0
                {
2352
                    // not in line range, get distance to PointA
2353
0
                    rCut = 0.0;
2354
0
                    return aVector2.getLength();
2355
0
                }
2356
0
                else if(fCut > 1.0)
2357
0
                {
2358
                    // not in line range, get distance to PointB
2359
0
                    rCut = 1.0;
2360
0
                    const B2DVector aVector(rTestPoint - rPointB);
2361
0
                    return aVector.getLength();
2362
0
                }
2363
0
                else
2364
0
                {
2365
                    // in line range
2366
0
                    const B2DPoint aCutPoint(rPointA + fCut * aVector1);
2367
0
                    const B2DVector aVector(rTestPoint - aCutPoint);
2368
0
                    rCut = fCut;
2369
0
                    return aVector.getLength();
2370
0
                }
2371
0
            }
2372
0
        }
2373
2374
        double getSmallestDistancePointToPolygon(const B2DPolygon& rCandidate, const B2DPoint& rTestPoint, sal_uInt32& rEdgeIndex, double& rCut)
2375
0
        {
2376
0
            double fRetval(DBL_MAX);
2377
0
            const sal_uInt32 nPointCount(rCandidate.count());
2378
2379
0
            if(nPointCount > 1)
2380
0
            {
2381
0
                const double fZero(0.0);
2382
0
                const sal_uInt32 nEdgeCount(rCandidate.isClosed() ? nPointCount : nPointCount - 1);
2383
0
                B2DCubicBezier aBezier;
2384
0
                aBezier.setStartPoint(rCandidate.getB2DPoint(0));
2385
2386
0
                for(sal_uInt32 a(0); a < nEdgeCount; a++)
2387
0
                {
2388
0
                    const sal_uInt32 nNextIndex((a + 1) % nPointCount);
2389
0
                    aBezier.setEndPoint(rCandidate.getB2DPoint(nNextIndex));
2390
0
                    double fEdgeDist;
2391
0
                    double fNewCut(0.0);
2392
0
                    bool bEdgeIsCurve(false);
2393
2394
0
                    if(rCandidate.areControlPointsUsed())
2395
0
                    {
2396
0
                        aBezier.setControlPointA(rCandidate.getNextControlPoint(a));
2397
0
                        aBezier.setControlPointB(rCandidate.getPrevControlPoint(nNextIndex));
2398
0
                        aBezier.testAndSolveTrivialBezier();
2399
0
                        bEdgeIsCurve = aBezier.isBezier();
2400
0
                    }
2401
2402
0
                    if(bEdgeIsCurve)
2403
0
                    {
2404
0
                        fEdgeDist = aBezier.getSmallestDistancePointToBezierSegment(rTestPoint, fNewCut);
2405
0
                    }
2406
0
                    else
2407
0
                    {
2408
0
                        fEdgeDist = getSmallestDistancePointToEdge(aBezier.getStartPoint(), aBezier.getEndPoint(), rTestPoint, fNewCut);
2409
0
                    }
2410
2411
0
                    if(fRetval == DBL_MAX || fEdgeDist < fRetval)
2412
0
                    {
2413
0
                        fRetval = fEdgeDist;
2414
0
                        rEdgeIndex = a;
2415
0
                        rCut = fNewCut;
2416
2417
0
                        if(fTools::equal(fRetval, fZero))
2418
0
                        {
2419
                            // already found zero distance, cannot get better. Ensure numerical zero value and end loop.
2420
0
                            fRetval = 0.0;
2421
0
                            break;
2422
0
                        }
2423
0
                    }
2424
2425
                    // prepare next step
2426
0
                    aBezier.setStartPoint(aBezier.getEndPoint());
2427
0
                }
2428
2429
0
                if(rtl::math::approxEqual(1.0, rCut))
2430
0
                {
2431
                    // correct rEdgeIndex when not last point
2432
0
                    if(rCandidate.isClosed())
2433
0
                    {
2434
0
                        rEdgeIndex = getIndexOfSuccessor(rEdgeIndex, rCandidate);
2435
0
                        rCut = 0.0;
2436
0
                    }
2437
0
                    else
2438
0
                    {
2439
0
                        if(rEdgeIndex != nEdgeCount - 1)
2440
0
                        {
2441
0
                            rEdgeIndex++;
2442
0
                            rCut = 0.0;
2443
0
                        }
2444
0
                    }
2445
0
                }
2446
0
            }
2447
2448
0
            return fRetval;
2449
0
        }
2450
2451
        B2DPoint distort(const B2DPoint& rCandidate, const B2DRange& rOriginal, const B2DPoint& rTopLeft, const B2DPoint& rTopRight, const B2DPoint& rBottomLeft, const B2DPoint& rBottomRight)
2452
0
        {
2453
0
            if(fTools::equalZero(rOriginal.getWidth()) || fTools::equalZero(rOriginal.getHeight()))
2454
0
            {
2455
0
                return rCandidate;
2456
0
            }
2457
0
            else
2458
0
            {
2459
0
                const double fRelativeX((rCandidate.getX() - rOriginal.getMinX()) / rOriginal.getWidth());
2460
0
                const double fRelativeY((rCandidate.getY() - rOriginal.getMinY()) / rOriginal.getHeight());
2461
0
                const double fOneMinusRelativeX(1.0 - fRelativeX);
2462
0
                const double fOneMinusRelativeY(1.0 - fRelativeY);
2463
0
                const double fNewX(fOneMinusRelativeY * (fOneMinusRelativeX * rTopLeft.getX() + fRelativeX * rTopRight.getX()) +
2464
0
                    fRelativeY * (fOneMinusRelativeX * rBottomLeft.getX() + fRelativeX * rBottomRight.getX()));
2465
0
                const double fNewY(fOneMinusRelativeX * (fOneMinusRelativeY * rTopLeft.getY() + fRelativeY * rBottomLeft.getY()) +
2466
0
                    fRelativeX * (fOneMinusRelativeY * rTopRight.getY() + fRelativeY * rBottomRight.getY()));
2467
2468
0
                return B2DPoint(fNewX, fNewY);
2469
0
            }
2470
0
        }
2471
2472
        B2DPolygon distort(const B2DPolygon& rCandidate, const B2DRange& rOriginal, const B2DPoint& rTopLeft, const B2DPoint& rTopRight, const B2DPoint& rBottomLeft, const B2DPoint& rBottomRight)
2473
0
        {
2474
0
            const sal_uInt32 nPointCount(rCandidate.count());
2475
2476
0
            if(nPointCount && rOriginal.getWidth() != 0.0 && rOriginal.getHeight() != 0.0)
2477
0
            {
2478
0
                B2DPolygon aRetval;
2479
2480
0
                for(sal_uInt32 a(0); a < nPointCount; a++)
2481
0
                {
2482
0
                    aRetval.append(distort(rCandidate.getB2DPoint(a), rOriginal, rTopLeft, rTopRight, rBottomLeft, rBottomRight));
2483
2484
0
                    if(rCandidate.areControlPointsUsed())
2485
0
                    {
2486
0
                        if(!rCandidate.getPrevControlPoint(a).equalZero())
2487
0
                        {
2488
0
                            aRetval.setPrevControlPoint(a, distort(rCandidate.getPrevControlPoint(a), rOriginal, rTopLeft, rTopRight, rBottomLeft, rBottomRight));
2489
0
                        }
2490
2491
0
                        if(!rCandidate.getNextControlPoint(a).equalZero())
2492
0
                        {
2493
0
                            aRetval.setNextControlPoint(a, distort(rCandidate.getNextControlPoint(a), rOriginal, rTopLeft, rTopRight, rBottomLeft, rBottomRight));
2494
0
                        }
2495
0
                    }
2496
0
                }
2497
2498
0
                aRetval.setClosed(rCandidate.isClosed());
2499
0
                return aRetval;
2500
0
            }
2501
0
            else
2502
0
            {
2503
0
                return rCandidate;
2504
0
            }
2505
0
        }
2506
2507
        B2DPolygon expandToCurve(const B2DPolygon& rCandidate)
2508
0
        {
2509
0
            B2DPolygon aRetval(rCandidate);
2510
2511
0
            for(sal_uInt32 a(0); a < rCandidate.count(); a++)
2512
0
            {
2513
0
                expandToCurveInPoint(aRetval, a);
2514
0
            }
2515
2516
0
            return aRetval;
2517
0
        }
2518
2519
        bool expandToCurveInPoint(B2DPolygon& rCandidate, sal_uInt32 nIndex)
2520
0
        {
2521
0
            OSL_ENSURE(nIndex < rCandidate.count(), "expandToCurveInPoint: Access to polygon out of range (!)");
2522
0
            bool bRetval(false);
2523
0
            const sal_uInt32 nPointCount(rCandidate.count());
2524
2525
0
            if(nPointCount)
2526
0
            {
2527
                // predecessor
2528
0
                if(!rCandidate.isPrevControlPointUsed(nIndex))
2529
0
                {
2530
0
                    if(!rCandidate.isClosed() && nIndex == 0)
2531
0
                    {
2532
                        // do not create previous vector for start point of open polygon
2533
0
                    }
2534
0
                    else
2535
0
                    {
2536
0
                        const sal_uInt32 nPrevIndex((nIndex + (nPointCount - 1)) % nPointCount);
2537
0
                        rCandidate.setPrevControlPoint(nIndex, interpolate(rCandidate.getB2DPoint(nIndex), rCandidate.getB2DPoint(nPrevIndex), 1.0 / 3.0));
2538
0
                        bRetval = true;
2539
0
                    }
2540
0
                }
2541
2542
                // successor
2543
0
                if(!rCandidate.isNextControlPointUsed(nIndex))
2544
0
                {
2545
0
                    if(!rCandidate.isClosed() && nIndex + 1 == nPointCount)
2546
0
                    {
2547
                        // do not create next vector for end point of open polygon
2548
0
                    }
2549
0
                    else
2550
0
                    {
2551
0
                        const sal_uInt32 nNextIndex((nIndex + 1) % nPointCount);
2552
0
                        rCandidate.setNextControlPoint(nIndex, interpolate(rCandidate.getB2DPoint(nIndex), rCandidate.getB2DPoint(nNextIndex), 1.0 / 3.0));
2553
0
                        bRetval = true;
2554
0
                    }
2555
0
                }
2556
0
            }
2557
2558
0
            return bRetval;
2559
0
        }
2560
2561
        bool setContinuityInPoint(B2DPolygon& rCandidate, sal_uInt32 nIndex, B2VectorContinuity eContinuity)
2562
0
        {
2563
0
            OSL_ENSURE(nIndex < rCandidate.count(), "setContinuityInPoint: Access to polygon out of range (!)");
2564
0
            bool bRetval(false);
2565
0
            const sal_uInt32 nPointCount(rCandidate.count());
2566
2567
0
            if(nPointCount)
2568
0
            {
2569
0
                const B2DPoint aCurrentPoint(rCandidate.getB2DPoint(nIndex));
2570
2571
0
                switch(eContinuity)
2572
0
                {
2573
0
                    case B2VectorContinuity::NONE :
2574
0
                    {
2575
0
                        if(rCandidate.isPrevControlPointUsed(nIndex))
2576
0
                        {
2577
0
                            if(!rCandidate.isClosed() && nIndex == 0)
2578
0
                            {
2579
                                // remove existing previous vector for start point of open polygon
2580
0
                                rCandidate.resetPrevControlPoint(nIndex);
2581
0
                            }
2582
0
                            else
2583
0
                            {
2584
0
                                const sal_uInt32 nPrevIndex((nIndex + (nPointCount - 1)) % nPointCount);
2585
0
                                rCandidate.setPrevControlPoint(nIndex, interpolate(aCurrentPoint, rCandidate.getB2DPoint(nPrevIndex), 1.0 / 3.0));
2586
0
                            }
2587
2588
0
                            bRetval = true;
2589
0
                        }
2590
2591
0
                        if(rCandidate.isNextControlPointUsed(nIndex))
2592
0
                        {
2593
0
                            if(!rCandidate.isClosed() && nIndex == nPointCount + 1)
2594
0
                            {
2595
                                // remove next vector for end point of open polygon
2596
0
                                rCandidate.resetNextControlPoint(nIndex);
2597
0
                            }
2598
0
                            else
2599
0
                            {
2600
0
                                const sal_uInt32 nNextIndex((nIndex + 1) % nPointCount);
2601
0
                                rCandidate.setNextControlPoint(nIndex, interpolate(aCurrentPoint, rCandidate.getB2DPoint(nNextIndex), 1.0 / 3.0));
2602
0
                            }
2603
2604
0
                            bRetval = true;
2605
0
                        }
2606
2607
0
                        break;
2608
0
                    }
2609
0
                    case B2VectorContinuity::C1 :
2610
0
                    {
2611
0
                        if(rCandidate.isPrevControlPointUsed(nIndex) && rCandidate.isNextControlPointUsed(nIndex))
2612
0
                        {
2613
                            // lengths both exist since both are used
2614
0
                            B2DVector aVectorPrev(rCandidate.getPrevControlPoint(nIndex) - aCurrentPoint);
2615
0
                            B2DVector aVectorNext(rCandidate.getNextControlPoint(nIndex) - aCurrentPoint);
2616
0
                            const double fLenPrev(aVectorPrev.getLength());
2617
0
                            const double fLenNext(aVectorNext.getLength());
2618
0
                            aVectorPrev.normalize();
2619
0
                            aVectorNext.normalize();
2620
0
                            const B2VectorOrientation aOrientation(getOrientation(aVectorPrev, aVectorNext));
2621
2622
0
                            if(aOrientation == B2VectorOrientation::Neutral && aVectorPrev.scalar(aVectorNext) < 0.0)
2623
0
                            {
2624
                                // parallel and opposite direction; check length
2625
0
                                if(fTools::equal(fLenPrev, fLenNext))
2626
0
                                {
2627
                                    // this would be even C2, but we want C1. Use the lengths of the corresponding edges.
2628
0
                                    const sal_uInt32 nPrevIndex((nIndex + (nPointCount - 1)) % nPointCount);
2629
0
                                    const sal_uInt32 nNextIndex((nIndex + 1) % nPointCount);
2630
0
                                    const double fLenPrevEdge(B2DVector(rCandidate.getB2DPoint(nPrevIndex) - aCurrentPoint).getLength() * (1.0 / 3.0));
2631
0
                                    const double fLenNextEdge(B2DVector(rCandidate.getB2DPoint(nNextIndex) - aCurrentPoint).getLength() * (1.0 / 3.0));
2632
2633
0
                                    rCandidate.setControlPoints(nIndex,
2634
0
                                        aCurrentPoint + (aVectorPrev * fLenPrevEdge),
2635
0
                                        aCurrentPoint + (aVectorNext * fLenNextEdge));
2636
0
                                    bRetval = true;
2637
0
                                }
2638
0
                            }
2639
0
                            else
2640
0
                            {
2641
                                // not parallel or same direction, set vectors and length
2642
0
                                const B2DVector aNormalizedPerpendicular(getNormalizedPerpendicular(aVectorPrev + aVectorNext));
2643
2644
0
                                if(aOrientation == B2VectorOrientation::Positive)
2645
0
                                {
2646
0
                                    rCandidate.setControlPoints(nIndex,
2647
0
                                        aCurrentPoint - (aNormalizedPerpendicular * fLenPrev),
2648
0
                                        aCurrentPoint + (aNormalizedPerpendicular * fLenNext));
2649
0
                                }
2650
0
                                else
2651
0
                                {
2652
0
                                    rCandidate.setControlPoints(nIndex,
2653
0
                                        aCurrentPoint + (aNormalizedPerpendicular * fLenPrev),
2654
0
                                        aCurrentPoint - (aNormalizedPerpendicular * fLenNext));
2655
0
                                }
2656
2657
0
                                bRetval = true;
2658
0
                            }
2659
0
                        }
2660
0
                        break;
2661
0
                    }
2662
0
                    case B2VectorContinuity::C2 :
2663
0
                    {
2664
0
                        if(rCandidate.isPrevControlPointUsed(nIndex) && rCandidate.isNextControlPointUsed(nIndex))
2665
0
                        {
2666
                            // lengths both exist since both are used
2667
0
                            B2DVector aVectorPrev(rCandidate.getPrevControlPoint(nIndex) - aCurrentPoint);
2668
0
                            B2DVector aVectorNext(rCandidate.getNextControlPoint(nIndex) - aCurrentPoint);
2669
0
                            const double fCommonLength((aVectorPrev.getLength() + aVectorNext.getLength()) / 2.0);
2670
0
                            aVectorPrev.normalize();
2671
0
                            aVectorNext.normalize();
2672
0
                            const B2VectorOrientation aOrientation(getOrientation(aVectorPrev, aVectorNext));
2673
2674
0
                            if(aOrientation == B2VectorOrientation::Neutral && aVectorPrev.scalar(aVectorNext) < 0.0)
2675
0
                            {
2676
                                // parallel and opposite direction; set length. Use one direction for better numerical correctness
2677
0
                                const B2DVector aScaledDirection(aVectorPrev * fCommonLength);
2678
2679
0
                                rCandidate.setControlPoints(nIndex,
2680
0
                                    aCurrentPoint + aScaledDirection,
2681
0
                                    aCurrentPoint - aScaledDirection);
2682
0
                            }
2683
0
                            else
2684
0
                            {
2685
                                // not parallel or same direction, set vectors and length
2686
0
                                const B2DVector aNormalizedPerpendicular(getNormalizedPerpendicular(aVectorPrev + aVectorNext));
2687
0
                                const B2DVector aPerpendicular(aNormalizedPerpendicular * fCommonLength);
2688
2689
0
                                if(aOrientation == B2VectorOrientation::Positive)
2690
0
                                {
2691
0
                                    rCandidate.setControlPoints(nIndex,
2692
0
                                        aCurrentPoint - aPerpendicular,
2693
0
                                        aCurrentPoint + aPerpendicular);
2694
0
                                }
2695
0
                                else
2696
0
                                {
2697
0
                                    rCandidate.setControlPoints(nIndex,
2698
0
                                        aCurrentPoint + aPerpendicular,
2699
0
                                        aCurrentPoint - aPerpendicular);
2700
0
                                }
2701
0
                            }
2702
2703
0
                            bRetval = true;
2704
0
                        }
2705
0
                        break;
2706
0
                    }
2707
0
                }
2708
0
            }
2709
2710
0
            return bRetval;
2711
0
        }
2712
2713
        B2DPolygon growInNormalDirection(const B2DPolygon& rCandidate, double fValue)
2714
0
        {
2715
0
            if(fValue != 0.0)
2716
0
            {
2717
0
                if(rCandidate.areControlPointsUsed())
2718
0
                {
2719
                    // call myself recursively with subdivided input
2720
0
                    const B2DPolygon aCandidate(adaptiveSubdivideByAngle(rCandidate));
2721
0
                    return growInNormalDirection(aCandidate, fValue);
2722
0
                }
2723
0
                else
2724
0
                {
2725
0
                    B2DPolygon aRetval;
2726
0
                    const sal_uInt32 nPointCount(rCandidate.count());
2727
2728
0
                    if(nPointCount)
2729
0
                    {
2730
0
                        B2DPoint aPrev(rCandidate.getB2DPoint(nPointCount - 1));
2731
0
                        B2DPoint aCurrent(rCandidate.getB2DPoint(0));
2732
2733
0
                        for(sal_uInt32 a(0); a < nPointCount; a++)
2734
0
                        {
2735
0
                            const B2DPoint aNext(rCandidate.getB2DPoint(a + 1 == nPointCount ? 0 : a + 1));
2736
0
                            const B2DVector aBack(aPrev - aCurrent);
2737
0
                            const B2DVector aForw(aNext - aCurrent);
2738
0
                            const B2DVector aPerpBack(getNormalizedPerpendicular(aBack));
2739
0
                            const B2DVector aPerpForw(getNormalizedPerpendicular(aForw));
2740
0
                            B2DVector aDirection(aPerpBack - aPerpForw);
2741
0
                            aDirection.normalize();
2742
0
                            aDirection *= fValue;
2743
0
                            aRetval.append(aCurrent + aDirection);
2744
2745
                            // prepare next step
2746
0
                            aPrev = aCurrent;
2747
0
                            aCurrent = aNext;
2748
0
                        }
2749
0
                    }
2750
2751
                    // copy closed state
2752
0
                    aRetval.setClosed(rCandidate.isClosed());
2753
2754
0
                    return aRetval;
2755
0
                }
2756
0
            }
2757
0
            else
2758
0
            {
2759
0
                return rCandidate;
2760
0
            }
2761
0
        }
2762
2763
        B2DPolygon reSegmentPolygon(const B2DPolygon& rCandidate, sal_uInt32 nSegments)
2764
0
        {
2765
0
            B2DPolygon aRetval;
2766
0
            const sal_uInt32 nPointCount(rCandidate.count());
2767
2768
0
            if(nPointCount && nSegments)
2769
0
            {
2770
                // get current segment count
2771
0
                const sal_uInt32 nSegmentCount(rCandidate.isClosed() ? nPointCount : nPointCount - 1);
2772
2773
0
                if(nSegmentCount == nSegments)
2774
0
                {
2775
0
                    aRetval = rCandidate;
2776
0
                }
2777
0
                else
2778
0
                {
2779
0
                    const double fLength(getLength(rCandidate));
2780
0
                    const sal_uInt32 nLoopCount(rCandidate.isClosed() ? nSegments : nSegments + 1);
2781
2782
0
                    for(sal_uInt32 a(0); a < nLoopCount; a++)
2783
0
                    {
2784
0
                        const double fRelativePos(static_cast<double>(a) / static_cast<double>(nSegments)); // 0.0 .. 1.0
2785
0
                        const B2DPoint aNewPoint(getPositionRelative(rCandidate, fRelativePos, fLength));
2786
0
                        aRetval.append(aNewPoint);
2787
0
                    }
2788
2789
                    // copy closed flag
2790
0
                    aRetval.setClosed(rCandidate.isClosed());
2791
0
                }
2792
0
            }
2793
2794
0
            return aRetval;
2795
0
        }
2796
2797
        B2DPolygon interpolate(const B2DPolygon& rOld1, const B2DPolygon& rOld2, double t)
2798
0
        {
2799
0
            OSL_ENSURE(rOld1.count() == rOld2.count(), "B2DPolygon interpolate: Different geometry (!)");
2800
2801
0
            if(t <= 0.0 || rOld1 == rOld2)
2802
0
            {
2803
0
                return rOld1;
2804
0
            }
2805
0
            else if(fTools::moreOrEqual(t, 1.0))
2806
0
            {
2807
0
                return rOld2;
2808
0
            }
2809
0
            else
2810
0
            {
2811
0
                B2DPolygon aRetval;
2812
0
                const bool bInterpolateVectors(rOld1.areControlPointsUsed() || rOld2.areControlPointsUsed());
2813
0
                aRetval.setClosed(rOld1.isClosed() && rOld2.isClosed());
2814
2815
0
                for(sal_uInt32 a(0); a < rOld1.count(); a++)
2816
0
                {
2817
0
                    aRetval.append(interpolate(rOld1.getB2DPoint(a), rOld2.getB2DPoint(a), t));
2818
2819
0
                    if(bInterpolateVectors)
2820
0
                    {
2821
0
                        aRetval.setPrevControlPoint(a, interpolate(rOld1.getPrevControlPoint(a), rOld2.getPrevControlPoint(a), t));
2822
0
                        aRetval.setNextControlPoint(a, interpolate(rOld1.getNextControlPoint(a), rOld2.getNextControlPoint(a), t));
2823
0
                    }
2824
0
                }
2825
2826
0
                return aRetval;
2827
0
            }
2828
0
        }
2829
2830
        // #i76891#
2831
        B2DPolygon simplifyCurveSegments(const B2DPolygon& rCandidate)
2832
11.6k
        {
2833
11.6k
            const sal_uInt32 nPointCount(rCandidate.count());
2834
2835
11.6k
            if(nPointCount && rCandidate.areControlPointsUsed())
2836
25
            {
2837
                // prepare loop
2838
25
                const sal_uInt32 nEdgeCount(rCandidate.isClosed() ? nPointCount : nPointCount - 1);
2839
25
                B2DPolygon aRetval;
2840
25
                B2DCubicBezier aBezier;
2841
25
                aBezier.setStartPoint(rCandidate.getB2DPoint(0));
2842
2843
                // try to avoid costly reallocations
2844
25
                aRetval.reserve( nEdgeCount+1);
2845
2846
                // add start point
2847
25
                aRetval.append(aBezier.getStartPoint());
2848
2849
110
                for(sal_uInt32 a(0); a < nEdgeCount; a++)
2850
85
                {
2851
                    // get values for edge
2852
85
                    const sal_uInt32 nNextIndex((a + 1) % nPointCount);
2853
85
                    aBezier.setEndPoint(rCandidate.getB2DPoint(nNextIndex));
2854
85
                    aBezier.setControlPointA(rCandidate.getNextControlPoint(a));
2855
85
                    aBezier.setControlPointB(rCandidate.getPrevControlPoint(nNextIndex));
2856
85
                    aBezier.testAndSolveTrivialBezier();
2857
2858
                    // still bezier?
2859
85
                    if(aBezier.isBezier())
2860
60
                    {
2861
                        // add edge with control vectors
2862
60
                        aRetval.appendBezierSegment(aBezier.getControlPointA(), aBezier.getControlPointB(), aBezier.getEndPoint());
2863
60
                    }
2864
25
                    else
2865
25
                    {
2866
                        // add edge
2867
25
                        aRetval.append(aBezier.getEndPoint());
2868
25
                    }
2869
2870
                    // next point
2871
85
                    aBezier.setStartPoint(aBezier.getEndPoint());
2872
85
                }
2873
2874
25
                if(rCandidate.isClosed())
2875
0
                {
2876
                    // set closed flag, rescue control point and correct last double point
2877
0
                    closeWithGeometryChange(aRetval);
2878
0
                }
2879
2880
25
                return aRetval;
2881
25
            }
2882
11.6k
            else
2883
11.6k
            {
2884
11.6k
                return rCandidate;
2885
11.6k
            }
2886
11.6k
        }
2887
2888
        // makes the given indexed point the new polygon start point. To do that, the points in the
2889
        // polygon will be rotated. This is only valid for closed polygons, for non-closed ones
2890
        // an assertion will be triggered
2891
        B2DPolygon makeStartPoint(const B2DPolygon& rCandidate, sal_uInt32 nIndexOfNewStatPoint)
2892
0
        {
2893
0
            const sal_uInt32 nPointCount(rCandidate.count());
2894
2895
0
            if(nPointCount > 2 && nIndexOfNewStatPoint != 0 && nIndexOfNewStatPoint < nPointCount)
2896
0
            {
2897
0
                OSL_ENSURE(rCandidate.isClosed(), "makeStartPoint: only valid for closed polygons (!)");
2898
0
                B2DPolygon aRetval;
2899
2900
0
                for(sal_uInt32 a(0); a < nPointCount; a++)
2901
0
                {
2902
0
                    const sal_uInt32 nSourceIndex((a + nIndexOfNewStatPoint) % nPointCount);
2903
0
                    aRetval.append(rCandidate.getB2DPoint(nSourceIndex));
2904
2905
0
                    if(rCandidate.areControlPointsUsed())
2906
0
                    {
2907
0
                        aRetval.setPrevControlPoint(a, rCandidate.getPrevControlPoint(nSourceIndex));
2908
0
                        aRetval.setNextControlPoint(a, rCandidate.getNextControlPoint(nSourceIndex));
2909
0
                    }
2910
0
                }
2911
2912
0
                return aRetval;
2913
0
            }
2914
2915
0
            return rCandidate;
2916
0
        }
2917
2918
        B2DPolygon createEdgesOfGivenLength(const B2DPolygon& rCandidate, double fLength, double fStart, double fEnd)
2919
0
        {
2920
0
            B2DPolygon aRetval;
2921
2922
0
            if(fLength < 0.0)
2923
0
            {
2924
0
                fLength = 0.0;
2925
0
            }
2926
2927
0
            if(!fTools::equalZero(fLength))
2928
0
            {
2929
0
                if(fStart < 0.0)
2930
0
                {
2931
0
                    fStart = 0.0;
2932
0
                }
2933
2934
0
                if(fEnd < 0.0)
2935
0
                {
2936
0
                    fEnd = 0.0;
2937
0
                }
2938
2939
0
                if(fEnd < fStart)
2940
0
                {
2941
0
                    fEnd = fStart;
2942
0
                }
2943
2944
                // iterate and consume pieces with fLength. First subdivide to reduce input to line segments
2945
0
                const B2DPolygon aCandidate(rCandidate.areControlPointsUsed() ? rCandidate.getDefaultAdaptiveSubdivision() : rCandidate);
2946
0
                const sal_uInt32 nPointCount(aCandidate.count());
2947
2948
0
                if(nPointCount > 1)
2949
0
                {
2950
0
                    const bool bEndActive(!fTools::equalZero(fEnd));
2951
0
                    const sal_uInt32 nEdgeCount(aCandidate.isClosed() ? nPointCount : nPointCount - 1);
2952
0
                    B2DPoint aCurrent(aCandidate.getB2DPoint(0));
2953
0
                    double fPositionInEdge(fStart);
2954
0
                    double fAbsolutePosition(fStart);
2955
2956
0
                    for(sal_uInt32 a(0); a < nEdgeCount; a++)
2957
0
                    {
2958
0
                        const sal_uInt32 nNextIndex((a + 1) % nPointCount);
2959
0
                        const B2DPoint aNext(aCandidate.getB2DPoint(nNextIndex));
2960
0
                        const B2DVector aEdge(aNext - aCurrent);
2961
0
                        double fEdgeLength(aEdge.getLength());
2962
2963
0
                        if(!fTools::equalZero(fEdgeLength))
2964
0
                        {
2965
0
                            while(fTools::less(fPositionInEdge, fEdgeLength))
2966
0
                            {
2967
                                // move position on edge forward as long as on edge
2968
0
                                const double fScalar(fPositionInEdge / fEdgeLength);
2969
0
                                aRetval.append(aCurrent + (aEdge * fScalar));
2970
0
                                fPositionInEdge += fLength;
2971
2972
0
                                if(bEndActive)
2973
0
                                {
2974
0
                                    fAbsolutePosition += fLength;
2975
2976
0
                                    if(fTools::more(fAbsolutePosition, fEnd))
2977
0
                                    {
2978
0
                                        break;
2979
0
                                    }
2980
0
                                }
2981
0
                            }
2982
2983
                            // subtract length of current edge
2984
0
                            fPositionInEdge -= fEdgeLength;
2985
0
                        }
2986
2987
0
                        if(bEndActive && fTools::more(fAbsolutePosition, fEnd))
2988
0
                        {
2989
0
                            break;
2990
0
                        }
2991
2992
                        // prepare next step
2993
0
                        aCurrent = aNext;
2994
0
                    }
2995
2996
                    // keep closed state
2997
0
                    aRetval.setClosed(aCandidate.isClosed());
2998
0
                }
2999
0
                else
3000
0
                {
3001
                    // source polygon has only one point, return unchanged
3002
0
                    aRetval = aCandidate;
3003
0
                }
3004
0
            }
3005
3006
0
            return aRetval;
3007
0
        }
3008
3009
        B2DPolygon createWaveline(const B2DPolygon& rCandidate, double fWaveWidth, double fWaveHeight)
3010
0
        {
3011
0
            B2DPolygon aRetval;
3012
3013
0
            if(fWaveWidth < 0.0)
3014
0
            {
3015
0
                fWaveWidth = 0.0;
3016
0
            }
3017
3018
0
            if(fWaveHeight < 0.0)
3019
0
            {
3020
0
                fWaveHeight = 0.0;
3021
0
            }
3022
3023
0
            const bool bHasWidth(!fTools::equalZero(fWaveWidth));
3024
3025
0
            if(bHasWidth)
3026
0
            {
3027
0
                const bool bHasHeight(!fTools::equalZero(fWaveHeight));
3028
0
                if(bHasHeight)
3029
0
                {
3030
                    // width and height, create waveline. First subdivide to reduce input to line segments
3031
                    // of WaveWidth. Last segment may be missing. If this turns out to be a problem, it
3032
                    // may be added here again using the original last point from rCandidate. It may
3033
                    // also be the case that rCandidate was closed. To simplify things it is handled here
3034
                    // as if it was opened.
3035
                    // Result from createEdgesOfGivenLength contains no curved segments, handle as straight
3036
                    // edges.
3037
0
                    const B2DPolygon aEqualLenghEdges(createEdgesOfGivenLength(rCandidate, fWaveWidth));
3038
0
                    const sal_uInt32 nPointCount(aEqualLenghEdges.count());
3039
3040
0
                    if(nPointCount > 1)
3041
0
                    {
3042
                        // iterate over straight edges, add start point
3043
0
                        B2DPoint aCurrent(aEqualLenghEdges.getB2DPoint(0));
3044
0
                        aRetval.append(aCurrent);
3045
3046
0
                        for(sal_uInt32 a(0); a < nPointCount - 1; a++)
3047
0
                        {
3048
0
                            const sal_uInt32 nNextIndex((a + 1) % nPointCount);
3049
0
                            const B2DPoint aNext(aEqualLenghEdges.getB2DPoint(nNextIndex));
3050
0
                            const B2DVector aEdge(aNext - aCurrent);
3051
0
                            const B2DVector aPerpendicular(getNormalizedPerpendicular(aEdge));
3052
0
                            const B2DVector aControlOffset((aEdge * 0.467308) - (aPerpendicular * fWaveHeight));
3053
3054
                            // add curve segment
3055
0
                            aRetval.appendBezierSegment(
3056
0
                                aCurrent + aControlOffset,
3057
0
                                aNext - aControlOffset,
3058
0
                                aNext);
3059
3060
                            // prepare next step
3061
0
                            aCurrent = aNext;
3062
0
                        }
3063
0
                    }
3064
0
                }
3065
0
                else
3066
0
                {
3067
                    // width but no height -> return original polygon
3068
0
                    aRetval = rCandidate;
3069
0
                }
3070
0
            }
3071
0
            else
3072
0
            {
3073
                // no width -> no waveline, stay empty and return
3074
0
            }
3075
3076
0
            return aRetval;
3077
0
        }
3078
3079
        // snap points of horizontal or vertical edges to discrete values
3080
        B2DPolygon snapPointsOfHorizontalOrVerticalEdges(const B2DPolygon& rCandidate)
3081
0
        {
3082
0
            const sal_uInt32 nPointCount(rCandidate.count());
3083
3084
0
            if(nPointCount > 1)
3085
0
            {
3086
                // Start by copying the source polygon to get a writeable copy. The closed state is
3087
                // copied by aRetval's initialisation, too, so no need to copy it in this method
3088
0
                B2DPolygon aRetval(rCandidate);
3089
3090
                // prepare geometry data. Get rounded from original
3091
0
                B2ITuple aPrevTuple(basegfx::fround(rCandidate.getB2DPoint(nPointCount - 1)));
3092
0
                B2DPoint aCurrPoint(rCandidate.getB2DPoint(0));
3093
0
                B2ITuple aCurrTuple(basegfx::fround(aCurrPoint));
3094
3095
                // loop over all points. This will also snap the implicit closing edge
3096
                // even when not closed, but that's no problem here
3097
0
                for(sal_uInt32 a(0); a < nPointCount; a++)
3098
0
                {
3099
                    // get next point. Get rounded from original
3100
0
                    const bool bLastRun(a + 1 == nPointCount);
3101
0
                    const sal_uInt32 nNextIndex(bLastRun ? 0 : a + 1);
3102
0
                    const B2DPoint aNextPoint(rCandidate.getB2DPoint(nNextIndex));
3103
0
                    const B2ITuple aNextTuple(basegfx::fround(aNextPoint));
3104
3105
                    // get the states
3106
0
                    const bool bPrevVertical(aPrevTuple.getX() == aCurrTuple.getX());
3107
0
                    const bool bNextVertical(aNextTuple.getX() == aCurrTuple.getX());
3108
0
                    const bool bPrevHorizontal(aPrevTuple.getY() == aCurrTuple.getY());
3109
0
                    const bool bNextHorizontal(aNextTuple.getY() == aCurrTuple.getY());
3110
0
                    const bool bSnapX(bPrevVertical || bNextVertical);
3111
0
                    const bool bSnapY(bPrevHorizontal || bNextHorizontal);
3112
3113
0
                    if(bSnapX || bSnapY)
3114
0
                    {
3115
0
                        const B2DPoint aSnappedPoint(
3116
0
                            bSnapX ? aCurrTuple.getX() : aCurrPoint.getX(),
3117
0
                            bSnapY ? aCurrTuple.getY() : aCurrPoint.getY());
3118
3119
0
                        aRetval.setB2DPoint(a, aSnappedPoint);
3120
0
                    }
3121
3122
                    // prepare next point
3123
0
                    if(!bLastRun)
3124
0
                    {
3125
0
                        aPrevTuple = aCurrTuple;
3126
0
                        aCurrPoint = aNextPoint;
3127
0
                        aCurrTuple = aNextTuple;
3128
0
                    }
3129
0
                }
3130
3131
0
                return aRetval;
3132
0
            }
3133
0
            else
3134
0
            {
3135
0
                return rCandidate;
3136
0
            }
3137
0
        }
3138
3139
        B2DVector getTangentEnteringPoint(const B2DPolygon& rCandidate, sal_uInt32 nIndex)
3140
0
        {
3141
0
            B2DVector aRetval(0.0, 0.0);
3142
0
            const sal_uInt32 nCount(rCandidate.count());
3143
3144
0
            if(nIndex >= nCount)
3145
0
            {
3146
                // out of range
3147
0
                return aRetval;
3148
0
            }
3149
3150
            // start immediately at prev point compared to nIndex
3151
0
            const bool bClosed(rCandidate.isClosed());
3152
0
            sal_uInt32 nPrev(bClosed ? (nIndex + nCount - 1) % nCount : nIndex ? nIndex - 1 : nIndex);
3153
3154
0
            if(nPrev == nIndex)
3155
0
            {
3156
                // no previous, done
3157
0
                return aRetval;
3158
0
            }
3159
3160
0
            B2DCubicBezier aSegment;
3161
3162
            // go backward in the polygon; if closed, maximal back to start index (nIndex); if not closed,
3163
            // until zero. Use nIndex as stop criteria
3164
0
            while(nPrev != nIndex)
3165
0
            {
3166
                // get BezierSegment and tangent at the *end* of segment
3167
0
                rCandidate.getBezierSegment(nPrev, aSegment);
3168
0
                aRetval = aSegment.getTangent(1.0);
3169
3170
0
                if(!aRetval.equalZero())
3171
0
                {
3172
                    // if we have a tangent, return it
3173
0
                    return aRetval;
3174
0
                }
3175
3176
                // prepare index before checked one
3177
0
                nPrev = bClosed ? (nPrev + nCount - 1) % nCount : nPrev ? nPrev - 1 : nIndex;
3178
0
            }
3179
3180
0
            return aRetval;
3181
0
        }
3182
3183
        B2DVector getTangentLeavingPoint(const B2DPolygon& rCandidate, sal_uInt32 nIndex)
3184
0
        {
3185
0
            B2DVector aRetval(0.0, 0.0);
3186
0
            const sal_uInt32 nCount(rCandidate.count());
3187
3188
0
            if(nIndex >= nCount)
3189
0
            {
3190
                // out of range
3191
0
                return aRetval;
3192
0
            }
3193
3194
            // start at nIndex
3195
0
            const bool bClosed(rCandidate.isClosed());
3196
0
            sal_uInt32 nCurrent(nIndex);
3197
0
            B2DCubicBezier aSegment;
3198
3199
            // go forward; if closed, do this until once around and back at start index (nIndex); if not
3200
            // closed, until last point (nCount - 1). Use nIndex as stop criteria
3201
0
            do
3202
0
            {
3203
                // get BezierSegment and tangent at the *beginning* of segment
3204
0
                rCandidate.getBezierSegment(nCurrent, aSegment);
3205
0
                aRetval = aSegment.getTangent(0.0);
3206
3207
0
                if(!aRetval.equalZero())
3208
0
                {
3209
                    // if we have a tangent, return it
3210
0
                    return aRetval;
3211
0
                }
3212
3213
                // prepare next index
3214
0
                nCurrent = bClosed ? (nCurrent + 1) % nCount : nCurrent + 1 < nCount ? nCurrent + 1 : nIndex;
3215
0
            }
3216
0
            while(nCurrent != nIndex);
3217
3218
0
            return aRetval;
3219
0
        }
3220
3221
        // converters for css::drawing::PointSequence
3222
3223
        B2DPolygon UnoPointSequenceToB2DPolygon(
3224
            const css::drawing::PointSequence& rPointSequenceSource)
3225
1.73k
        {
3226
1.73k
            B2DPolygon aRetval;
3227
1.73k
            const sal_uInt32 nLength(rPointSequenceSource.getLength());
3228
3229
1.73k
            if(nLength)
3230
1.73k
            {
3231
1.73k
                aRetval.reserve(nLength);
3232
3233
1.73k
                for(auto& point : rPointSequenceSource)
3234
3.73k
                {
3235
3.73k
                    aRetval.append(B2DPoint(point.X, point.Y));
3236
3.73k
                }
3237
3238
                // check for closed state flag
3239
1.73k
                utils::checkClosed(aRetval);
3240
1.73k
            }
3241
3242
1.73k
            return aRetval;
3243
1.73k
        }
3244
3245
        void B2DPolygonToUnoPointSequence(
3246
            const B2DPolygon& rPolygon,
3247
            css::drawing::PointSequence& rPointSequenceRetval)
3248
254
        {
3249
254
            B2DPolygon aPolygon(rPolygon);
3250
3251
254
            if(aPolygon.areControlPointsUsed())
3252
0
            {
3253
0
                OSL_ENSURE(false, "B2DPolygonToUnoPointSequence: Source contains bezier segments, wrong UNO API data type may be used (!)");
3254
0
                aPolygon = aPolygon.getDefaultAdaptiveSubdivision();
3255
0
            }
3256
3257
254
            const sal_uInt32 nPointCount(aPolygon.count());
3258
3259
254
            if(nPointCount)
3260
254
            {
3261
                // Take closed state into account, the API polygon still uses the old closed definition
3262
                // with last/first point are identical (cannot hold information about open polygons with identical
3263
                // first and last point, though)
3264
254
                const bool bIsClosed(aPolygon.isClosed());
3265
3266
254
                rPointSequenceRetval.realloc(bIsClosed ? nPointCount + 1 : nPointCount);
3267
254
                css::awt::Point* pSequence = rPointSequenceRetval.getArray();
3268
3269
996
                for(sal_uInt32 b(0); b < nPointCount; b++)
3270
742
                {
3271
742
                    const B2DPoint aPoint(aPolygon.getB2DPoint(b));
3272
742
                    const css::awt::Point aAPIPoint(fround(aPoint.getX()), fround(aPoint.getY()));
3273
3274
742
                    *pSequence = aAPIPoint;
3275
742
                    pSequence++;
3276
742
                }
3277
3278
                // copy first point if closed
3279
254
                if(bIsClosed)
3280
17
                {
3281
17
                    *pSequence = rPointSequenceRetval[0];
3282
17
                }
3283
254
            }
3284
0
            else
3285
0
            {
3286
0
                rPointSequenceRetval.realloc(0);
3287
0
            }
3288
254
        }
3289
3290
        // converters for css::drawing::PointSequence and
3291
        // css::drawing::FlagSequence to B2DPolygon (curved polygons)
3292
3293
        B2DPolygon UnoPolygonBezierCoordsToB2DPolygon(
3294
            const css::drawing::PointSequence& rPointSequenceSource,
3295
            const css::drawing::FlagSequence& rFlagSequenceSource)
3296
56.9k
        {
3297
56.9k
            const sal_Int32 nCount(rPointSequenceSource.getLength());
3298
56.9k
            OSL_ENSURE(nCount == rFlagSequenceSource.getLength(),
3299
56.9k
                "UnoPolygonBezierCoordsToB2DPolygon: Unequal count of Points and Flags (!)");
3300
3301
            // prepare new polygon
3302
56.9k
            B2DPolygon aRetval;
3303
3304
56.9k
            if(0 != nCount)
3305
56.9k
            {
3306
56.9k
                aRetval.reserve(nCount);
3307
3308
                // get first point and flag
3309
56.9k
                B2DPoint aNewCoordinatePair(rPointSequenceSource[0].X, rPointSequenceSource[0].Y);
3310
56.9k
                B2DPoint aControlA;
3311
56.9k
                B2DPoint aControlB;
3312
3313
                // first point is not allowed to be a control point
3314
56.9k
                OSL_ENSURE(rFlagSequenceSource[0] != css::drawing::PolygonFlags_CONTROL,
3315
56.9k
                    "UnoPolygonBezierCoordsToB2DPolygon: Start point is a control point, illegal input polygon (!)");
3316
3317
                // add first point as start point
3318
56.9k
                aRetval.append(aNewCoordinatePair);
3319
3320
148k
                for(sal_Int32 b(1); b < nCount;)
3321
91.9k
                {
3322
                    // prepare loop
3323
91.9k
                    bool bControlA(false);
3324
91.9k
                    bool bControlB(false);
3325
3326
                    // get next point and flag
3327
91.9k
                    aNewCoordinatePair = B2DPoint(rPointSequenceSource[b].X, rPointSequenceSource[b].Y);
3328
91.9k
                    css::drawing::PolygonFlags ePolygonFlag = rFlagSequenceSource[b];
3329
91.9k
                    b++;
3330
3331
91.9k
                    if(b < nCount && ePolygonFlag == css::drawing::PolygonFlags_CONTROL)
3332
4.63k
                    {
3333
4.63k
                        aControlA = aNewCoordinatePair;
3334
4.63k
                        bControlA = true;
3335
3336
                        // get next point and flag
3337
4.63k
                        aNewCoordinatePair = B2DPoint(rPointSequenceSource[b].X, rPointSequenceSource[b].Y);
3338
4.63k
                        ePolygonFlag = rFlagSequenceSource[b];
3339
4.63k
                        b++;
3340
4.63k
                    }
3341
3342
91.9k
                    if(b < nCount && ePolygonFlag == css::drawing::PolygonFlags_CONTROL)
3343
4.63k
                    {
3344
4.63k
                        aControlB = aNewCoordinatePair;
3345
4.63k
                        bControlB = true;
3346
3347
                        // get next point and flag
3348
4.63k
                        aNewCoordinatePair = B2DPoint(rPointSequenceSource[b].X, rPointSequenceSource[b].Y);
3349
4.63k
                        ePolygonFlag = rFlagSequenceSource[b];
3350
4.63k
                        b++;
3351
4.63k
                    }
3352
3353
                    // two or no control points are consumed, another one would be an error.
3354
                    // It's also an error if only one control point was read
3355
91.9k
                    SAL_WARN_IF(ePolygonFlag == css::drawing::PolygonFlags_CONTROL || bControlA != bControlB,
3356
91.9k
                        "basegfx", "UnoPolygonBezierCoordsToB2DPolygon: Illegal source polygon (!)");
3357
3358
                    // the previous writes used the B2DPolyPolygon -> utils::PolyPolygon converter
3359
                    // which did not create minimal PolyPolygons, but created all control points
3360
                    // as null vectors (identical points). Because of the former P(CA)(CB)-norm of
3361
                    // B2DPolygon and it's unused sign of being the zero-vector and CA and CB being
3362
                    // relative to P, an empty edge was exported as P == CA == CB. Luckily, the new
3363
                    // export format can be read without errors by the old OOo-versions, so we need only
3364
                    // to correct here at read and do not need to export a wrong but compatible version
3365
                    // for the future.
3366
91.9k
                    if(bControlA
3367
4.63k
                        && aControlA.equal(aControlB)
3368
2
                        && aControlA.equal(aRetval.getB2DPoint(aRetval.count() - 1)))
3369
0
                    {
3370
0
                        bControlA = false;
3371
0
                    }
3372
3373
91.9k
                    if(bControlA)
3374
4.63k
                    {
3375
                        // add bezier edge
3376
4.63k
                        aRetval.appendBezierSegment(aControlA, aControlB, aNewCoordinatePair);
3377
4.63k
                    }
3378
87.3k
                    else
3379
87.3k
                    {
3380
                        // add edge
3381
87.3k
                        aRetval.append(aNewCoordinatePair);
3382
87.3k
                    }
3383
91.9k
                }
3384
3385
                // #i72807# API import uses old line start/end-equal definition for closed,
3386
                // so we need to correct this to closed state here
3387
56.9k
                checkClosed(aRetval);
3388
56.9k
            }
3389
3390
56.9k
            return aRetval;
3391
56.9k
        }
3392
3393
        void B2DPolygonToUnoPolygonBezierCoords(
3394
            const B2DPolygon& rPolygon,
3395
            css::drawing::PointSequence& rPointSequenceRetval,
3396
            css::drawing::FlagSequence& rFlagSequenceRetval)
3397
27.3k
        {
3398
27.3k
            const sal_uInt32 nPointCount(rPolygon.count());
3399
3400
27.3k
            if(nPointCount)
3401
27.3k
            {
3402
27.3k
                const bool bCurve(rPolygon.areControlPointsUsed());
3403
27.3k
                const bool bClosed(rPolygon.isClosed());
3404
3405
27.3k
                if(bCurve)
3406
2.25k
                {
3407
                    // calculate target point count
3408
2.25k
                    const sal_uInt32 nLoopCount(bClosed ? nPointCount : nPointCount - 1);
3409
3410
2.25k
                    if(nLoopCount)
3411
2.25k
                    {
3412
                        // prepare target data. The real needed number of target points (and flags)
3413
                        // could only be calculated by using two loops, so use dynamic memory
3414
2.25k
                        std::vector< css::awt::Point > aCollectPoints;
3415
2.25k
                        std::vector< css::drawing::PolygonFlags > aCollectFlags;
3416
3417
                        // reserve maximum creatable points
3418
2.25k
                        const sal_uInt32 nMaxTargetCount((nLoopCount * 3) + 1);
3419
2.25k
                        aCollectPoints.reserve(nMaxTargetCount);
3420
2.25k
                        aCollectFlags.reserve(nMaxTargetCount);
3421
3422
                        // prepare current bezier segment by setting start point
3423
2.25k
                        B2DCubicBezier aBezierSegment;
3424
2.25k
                        aBezierSegment.setStartPoint(rPolygon.getB2DPoint(0));
3425
3426
8.95k
                        for(sal_uInt32 a(0); a < nLoopCount; a++)
3427
6.70k
                        {
3428
                            // add current point (always) and remember StartPointIndex for evtl. later corrections
3429
6.70k
                            const sal_uInt32 nStartPointIndex(aCollectPoints.size());
3430
6.70k
                            aCollectPoints.emplace_back(
3431
6.70k
                                    fround(aBezierSegment.getStartPoint().getX()),
3432
6.70k
                                    fround(aBezierSegment.getStartPoint().getY()));
3433
6.70k
                            aCollectFlags.push_back(css::drawing::PolygonFlags_NORMAL);
3434
3435
                            // prepare next segment
3436
6.70k
                            const sal_uInt32 nNextIndex((a + 1) % nPointCount);
3437
6.70k
                            aBezierSegment.setEndPoint(rPolygon.getB2DPoint(nNextIndex));
3438
6.70k
                            aBezierSegment.setControlPointA(rPolygon.getNextControlPoint(a));
3439
6.70k
                            aBezierSegment.setControlPointB(rPolygon.getPrevControlPoint(nNextIndex));
3440
3441
6.70k
                            if(aBezierSegment.isBezier())
3442
2.27k
                            {
3443
                                // if bezier is used, add always two control points due to the old schema
3444
2.27k
                                aCollectPoints.emplace_back(
3445
2.27k
                                        fround(aBezierSegment.getControlPointA().getX()),
3446
2.27k
                                        fround(aBezierSegment.getControlPointA().getY()));
3447
2.27k
                                aCollectFlags.push_back(css::drawing::PolygonFlags_CONTROL);
3448
3449
2.27k
                                aCollectPoints.emplace_back(
3450
2.27k
                                        fround(aBezierSegment.getControlPointB().getX()),
3451
2.27k
                                        fround(aBezierSegment.getControlPointB().getY()));
3452
2.27k
                                aCollectFlags.push_back(css::drawing::PolygonFlags_CONTROL);
3453
2.27k
                            }
3454
3455
                            // test continuity with previous control point to set flag value
3456
6.70k
                            if(aBezierSegment.getControlPointA() != aBezierSegment.getStartPoint() && (bClosed || a))
3457
106
                            {
3458
106
                                const B2VectorContinuity eCont(rPolygon.getContinuityInPoint(a));
3459
3460
106
                                if(eCont == B2VectorContinuity::C1)
3461
0
                                {
3462
0
                                    aCollectFlags[nStartPointIndex] = css::drawing::PolygonFlags_SMOOTH;
3463
0
                                }
3464
106
                                else if(eCont == B2VectorContinuity::C2)
3465
21
                                {
3466
21
                                    aCollectFlags[nStartPointIndex] = css::drawing::PolygonFlags_SYMMETRIC;
3467
21
                                }
3468
106
                            }
3469
3470
                            // prepare next loop
3471
6.70k
                            aBezierSegment.setStartPoint(aBezierSegment.getEndPoint());
3472
6.70k
                        }
3473
3474
2.25k
                        if(bClosed)
3475
2.21k
                        {
3476
                            // add first point again as closing point due to old definition
3477
2.21k
                            aCollectPoints.push_back(aCollectPoints[0]);
3478
2.21k
                            aCollectFlags.push_back(css::drawing::PolygonFlags_NORMAL);
3479
2.21k
                        }
3480
36
                        else
3481
36
                        {
3482
                            // add last point as closing point
3483
36
                            const B2DPoint aClosingPoint(rPolygon.getB2DPoint(nPointCount - 1));
3484
36
                            aCollectPoints.emplace_back(
3485
36
                                    fround(aClosingPoint.getX()),
3486
36
                                    fround(aClosingPoint.getY()));
3487
36
                            aCollectFlags.push_back(css::drawing::PolygonFlags_NORMAL);
3488
36
                        }
3489
3490
                        // copy collected data to target arrays
3491
2.25k
                        const sal_uInt32 nTargetCount(aCollectPoints.size());
3492
2.25k
                        OSL_ENSURE(nTargetCount == aCollectFlags.size(), "Unequal Point and Flag count (!)");
3493
3494
2.25k
                        rPointSequenceRetval.realloc(static_cast<sal_Int32>(nTargetCount));
3495
2.25k
                        rFlagSequenceRetval.realloc(static_cast<sal_Int32>(nTargetCount));
3496
2.25k
                        css::awt::Point* pPointSequence = rPointSequenceRetval.getArray();
3497
2.25k
                        css::drawing::PolygonFlags* pFlagSequence = rFlagSequenceRetval.getArray();
3498
3499
15.7k
                        for(sal_uInt32 a(0); a < nTargetCount; a++)
3500
13.5k
                        {
3501
13.5k
                            *pPointSequence = aCollectPoints[a];
3502
13.5k
                            *pFlagSequence = aCollectFlags[a];
3503
13.5k
                            pPointSequence++;
3504
13.5k
                            pFlagSequence++;
3505
13.5k
                        }
3506
2.25k
                    }
3507
2.25k
                }
3508
25.0k
                else
3509
25.0k
                {
3510
                    // straightforward point list creation
3511
25.0k
                    const sal_uInt32 nTargetCount(nPointCount + (bClosed ? 1 : 0));
3512
3513
25.0k
                    rPointSequenceRetval.realloc(static_cast<sal_Int32>(nTargetCount));
3514
25.0k
                    rFlagSequenceRetval.realloc(static_cast<sal_Int32>(nTargetCount));
3515
3516
25.0k
                    css::awt::Point* pPointSequence = rPointSequenceRetval.getArray();
3517
25.0k
                    css::drawing::PolygonFlags* pFlagSequence = rFlagSequenceRetval.getArray();
3518
3519
71.5k
                    for(sal_uInt32 a(0); a < nPointCount; a++)
3520
46.4k
                    {
3521
46.4k
                        const B2DPoint aB2DPoint(rPolygon.getB2DPoint(a));
3522
46.4k
                        const css::awt::Point aAPIPoint(
3523
46.4k
                            fround(aB2DPoint.getX()),
3524
46.4k
                            fround(aB2DPoint.getY()));
3525
3526
46.4k
                        *pPointSequence = aAPIPoint;
3527
46.4k
                        *pFlagSequence = css::drawing::PolygonFlags_NORMAL;
3528
46.4k
                        pPointSequence++;
3529
46.4k
                        pFlagSequence++;
3530
46.4k
                    }
3531
3532
25.0k
                    if(bClosed)
3533
14.3k
                    {
3534
                        // add first point as closing point
3535
14.3k
                        *pPointSequence = rPointSequenceRetval[0];
3536
14.3k
                        *pFlagSequence = css::drawing::PolygonFlags_NORMAL;
3537
14.3k
                    }
3538
25.0k
                }
3539
27.3k
            }
3540
0
            else
3541
0
            {
3542
0
                rPointSequenceRetval.realloc(0);
3543
0
                rFlagSequenceRetval.realloc(0);
3544
0
            }
3545
27.3k
        }
3546
3547
B2DPolygon createSimplifiedPolygon(const B2DPolygon& rCandidate, const double fTolerance)
3548
0
{
3549
0
    const sal_uInt32 nPointCount(rCandidate.count());
3550
0
    if (nPointCount < 3 || rCandidate.areControlPointsUsed())
3551
0
        return rCandidate;
3552
3553
    // The solution does not use recursion directly, since this could lead to a stack overflow if
3554
    // nPointCount is very large. Instead, an own stack is used. This does not contain points, but
3555
    // pairs of low and high index of a range in rCandidate. A parallel boolean vector is used to note
3556
    // whether a point of rCandidate belongs to the output polygon or not.
3557
0
    std::vector<bool> bIsKeptVec(nPointCount, false);
3558
0
    bIsKeptVec[0] = true;
3559
0
    bIsKeptVec[nPointCount - 1] = true;
3560
0
    sal_uInt32 nKept = 2;
3561
0
    std::stack<std::pair<sal_uInt32, sal_uInt32>> aUnfinishedRangesStack;
3562
3563
    // The RDP algorithm draws a straight line from the first point to the last point of a range.
3564
    // Then, from the inner points of the range, the point that has the largest distance to the line
3565
    // is determined. If the distance is greater than the tolerance, this point is kept and the lower
3566
    // and upper sub-segments of the range are treated in the same way. If the distance is smaller
3567
    // than the tolerance, none of the inner points will be kept.
3568
0
    sal_uInt32 nLowIndex = 0;
3569
0
    sal_uInt32 nHighIndex = nPointCount - 1;
3570
0
    bool bContinue = true;
3571
0
    do
3572
0
    {
3573
0
        bContinue = false;
3574
0
        if (nHighIndex - nLowIndex < 2) // maximal two points, range is finished.
3575
0
        {
3576
            // continue with sibling upper range if any
3577
0
            if (!aUnfinishedRangesStack.empty())
3578
0
            {
3579
0
                std::pair<sal_uInt32, sal_uInt32> aIndexPair = aUnfinishedRangesStack.top();
3580
0
                aUnfinishedRangesStack.pop();
3581
0
                nLowIndex = aIndexPair.first;
3582
0
                nHighIndex = aIndexPair.second;
3583
0
                bContinue = true;
3584
0
            }
3585
0
        }
3586
0
        else // the range needs examine the max distance
3587
0
        {
3588
            // Get maximal distance of inner points of the range to the straight line from start to
3589
            // end point of the range.
3590
            // For calculation of the distance we use the Hesse normal form of the straight line.
3591
0
            B2DPoint aLowPoint = rCandidate.getB2DPoint(nLowIndex);
3592
0
            B2DPoint aHighPoint = rCandidate.getB2DPoint(nHighIndex);
3593
0
            B2DVector aNormalVec
3594
0
                = basegfx::getNormalizedPerpendicular(B2DVector(aHighPoint) - B2DVector(aLowPoint));
3595
0
            double fLineOriginDistance = aNormalVec.scalar(B2DVector(aLowPoint));
3596
0
            double fMaxDist = 0;
3597
0
            sal_uInt32 nMaxPointIndex = nLowIndex;
3598
0
            for (sal_uInt32 i = nLowIndex + 1; i < nHighIndex; i++)
3599
0
            {
3600
0
                double fDistance
3601
0
                    = aNormalVec.scalar(B2DVector(rCandidate.getB2DPoint(i))) - fLineOriginDistance;
3602
0
                if (std::fabs(fDistance) > fMaxDist)
3603
0
                {
3604
0
                    fMaxDist = std::fabs(fDistance);
3605
0
                    nMaxPointIndex = i;
3606
0
                }
3607
0
            }
3608
3609
0
            if (fMaxDist >= fTolerance)
3610
0
            {
3611
                // We need to divide the current range into two sub ranges.
3612
0
                bIsKeptVec[nMaxPointIndex] = true;
3613
0
                nKept++;
3614
                // continue with lower sub range and push upper sub range to stack
3615
0
                aUnfinishedRangesStack.push(std::make_pair(nMaxPointIndex, nHighIndex));
3616
0
                nHighIndex = nMaxPointIndex;
3617
0
                bContinue = true;
3618
0
            }
3619
0
            else
3620
0
            {
3621
                // We do not keep any of the inner points of the current range.
3622
                // continue with sibling upper range if any
3623
0
                if (!aUnfinishedRangesStack.empty())
3624
0
                {
3625
0
                    std::pair<sal_uInt32, sal_uInt32> aIndexPair = aUnfinishedRangesStack.top();
3626
0
                    aUnfinishedRangesStack.pop();
3627
0
                    nLowIndex = aIndexPair.first;
3628
0
                    nHighIndex = aIndexPair.second;
3629
0
                    bContinue = true;
3630
0
                }
3631
0
            }
3632
0
        }
3633
0
    } while (bContinue);
3634
3635
    // Put all points which are marked as "keep" into the result polygon
3636
0
    B2DPolygon aResultPolygon;
3637
0
    aResultPolygon.reserve(nKept);
3638
0
    for (sal_uInt32 i = 0; i < nPointCount; i++)
3639
0
    {
3640
0
        if (bIsKeptVec[i])
3641
0
            aResultPolygon.append(rCandidate.getB2DPoint(i));
3642
0
    }
3643
0
    return aResultPolygon;
3644
0
}
3645
3646
} // end of namespace
3647
3648
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */