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/b2dpolypolygoncutter.cxx
Line
Count
Source
1
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
/*
3
 * This file is part of the LibreOffice project.
4
 *
5
 * This Source Code Form is subject to the terms of the Mozilla Public
6
 * License, v. 2.0. If a copy of the MPL was not distributed with this
7
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8
 *
9
 * This file incorporates work covered by the following license notice:
10
 *
11
 *   Licensed to the Apache Software Foundation (ASF) under one or more
12
 *   contributor license agreements. See the NOTICE file distributed
13
 *   with this work for additional information regarding copyright
14
 *   ownership. The ASF licenses this file to you under the Apache
15
 *   License, Version 2.0 (the "License"); you may not use this file
16
 *   except in compliance with the License. You may obtain a copy of
17
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
18
 */
19
20
#include <basegfx/polygon/b2dpolypolygoncutter.hxx>
21
#include <basegfx/point/b2dpoint.hxx>
22
#include <basegfx/vector/b2dvector.hxx>
23
#include <basegfx/vector/b2enums.hxx>
24
#include <basegfx/polygon/b2dpolygon.hxx>
25
#include <basegfx/polygon/b2dpolygontools.hxx>
26
#include <basegfx/polygon/b2dpolygoncutandtouch.hxx>
27
#include <basegfx/range/b2drange.hxx>
28
#include <basegfx/polygon/b2dpolypolygontools.hxx>
29
#include <basegfx/curve/b2dcubicbezier.hxx>
30
#include <sal/log.hxx>
31
#include <utility>
32
#include <vector>
33
#include <algorithm>
34
#include <map>
35
#include <numeric>
36
#include <tuple>
37
38
namespace basegfx
39
{
40
    namespace
41
    {
42
43
        struct StripHelper
44
        {
45
            B2DRange                                maRange;
46
            sal_Int32                               mnDepth;
47
            B2VectorOrientation                     meOrinetation;
48
        };
49
50
        struct PN
51
        {
52
        public:
53
            B2DPoint                maPoint;
54
            sal_uInt32              mnI;
55
            sal_uInt32              mnIP;
56
            sal_uInt32              mnIN;
57
        };
58
59
        struct VN
60
        {
61
        public:
62
            B2DVector               maPrev;
63
            B2DVector               maNext;
64
65
            // to have the correct curve segments in the crossover checks,
66
            // it is necessary to keep the original next vectors, too. Else,
67
            // it may happen to use an already switched next vector which
68
            // would interpolate the wrong comparison point
69
            B2DVector               maOriginalNext;
70
        };
71
72
        struct SN
73
        {
74
        public:
75
            PN*                     mpPN;
76
77
            // For this to be a strict weak ordering, the assumption is that none of the involved
78
            // maPoint coordinates are NaN:
79
            bool operator<(const SN& rComp) const
80
48.4M
            {
81
48.4M
                return std::tie(mpPN->maPoint, mpPN->mnI)
82
48.4M
                    < std::tie(rComp.mpPN->maPoint, rComp.mpPN->mnI);
83
48.4M
            }
84
        };
85
86
        typedef std::vector< PN > PNV;
87
        typedef std::vector< VN > VNV;
88
        typedef std::vector< SN > SNV;
89
        typedef std::pair< basegfx::B2DPoint /*orig*/, basegfx::B2DPoint /*repl*/ > CorrectionPair;
90
91
        class solver
92
        {
93
        private:
94
            const B2DPolyPolygon    maOriginal;
95
            PNV                     maPNV;
96
            VNV                     maVNV;
97
            SNV                     maSNV;
98
            std::vector< CorrectionPair >
99
                                    maCorrectionTable;
100
101
            bool                    mbIsCurve : 1;
102
            bool                    mbChanged : 1;
103
104
            void impAddPolygon(const sal_uInt32 aPos, const B2DPolygon& rGeometry)
105
396k
            {
106
396k
                const sal_uInt32 nCount(rGeometry.count());
107
396k
                PN aNewPN;
108
396k
                VN aNewVN;
109
396k
                SN aNewSN;
110
111
5.02M
                for(sal_uInt32 a(0); a < nCount; a++)
112
4.62M
                {
113
4.62M
                    const B2DPoint aPoint(rGeometry.getB2DPoint(a));
114
4.62M
                    aNewPN.maPoint = aPoint;
115
4.62M
                    aNewPN.mnI = aPos + a;
116
4.62M
                    aNewPN.mnIP = aPos + ((a != 0) ? a - 1 : nCount - 1);
117
4.62M
                    aNewPN.mnIN = aPos + ((a + 1 == nCount) ? 0 : a + 1);
118
4.62M
                    maPNV.push_back(aNewPN);
119
120
4.62M
                    if(mbIsCurve)
121
0
                    {
122
0
                        aNewVN.maPrev = rGeometry.getPrevControlPoint(a) - aPoint;
123
0
                        aNewVN.maNext = rGeometry.getNextControlPoint(a) - aPoint;
124
0
                        aNewVN.maOriginalNext = aNewVN.maNext;
125
0
                        maVNV.push_back(aNewVN);
126
0
                    }
127
128
4.62M
                    aNewSN.mpPN = &maPNV[maPNV.size() - 1];
129
4.62M
                    maSNV.push_back(aNewSN);
130
4.62M
                }
131
396k
            }
132
133
            static bool impLeftOfEdges(const B2DVector& rVecA, const B2DVector& rVecB, const B2DVector& rTest)
134
6.21M
            {
135
                // tests if rTest is left of both directed line segments along the line -rVecA, rVecB. Test is
136
                // with border.
137
6.21M
                if(rVecA.cross(rVecB) > 0.0)
138
1.73M
                {
139
                    // b is left turn seen from a, test if Test is left of both and so inside (left is seen as inside)
140
1.73M
                    const bool bBoolA(rVecA.cross(rTest) >= 0.0);
141
1.73M
                    const bool bBoolB(rVecB.cross(rTest) <= 0.0);
142
143
1.73M
                    return (bBoolA && bBoolB);
144
1.73M
                }
145
4.47M
                else
146
4.47M
                {
147
                    // b is right turn seen from a, test if Test is right of both and so outside (left is seen as inside)
148
4.47M
                    const bool bBoolA(rVecA.cross(rTest) <= 0.0);
149
4.47M
                    const bool bBoolB(rVecB.cross(rTest) >= 0.0);
150
151
4.47M
                    return (!(bBoolA && bBoolB));
152
4.47M
                }
153
6.21M
            }
154
155
            void impSwitchNext(PN& rPNa, PN& rPNb)
156
1.33M
            {
157
1.33M
                std::swap(rPNa.mnIN, rPNb.mnIN);
158
159
1.33M
                if(mbIsCurve)
160
0
                {
161
0
                    VN& rVNa = maVNV[rPNa.mnI];
162
0
                    VN& rVNb = maVNV[rPNb.mnI];
163
164
0
                    std::swap(rVNa.maNext, rVNb.maNext);
165
0
                }
166
167
1.33M
                if(!mbChanged)
168
1.73k
                {
169
1.73k
                    mbChanged = true;
170
1.73k
                }
171
1.33M
            }
172
173
            B2DCubicBezier createSegment(const PN& rPN, bool bPrev) const
174
0
            {
175
0
                const B2DPoint& rStart(rPN.maPoint);
176
0
                const B2DPoint& rEnd(maPNV[bPrev ? rPN.mnIP : rPN.mnIN].maPoint);
177
0
                const B2DVector& rCPA(bPrev ? maVNV[rPN.mnI].maPrev : maVNV[rPN.mnI].maNext);
178
                // Use maOriginalNext, not maNext to create the original (yet unchanged)
179
                // curve segment. Otherwise, this segment would NOT ne correct.
180
0
                const B2DVector& rCPB(bPrev ? maVNV[maPNV[rPN.mnIP].mnI].maOriginalNext : maVNV[maPNV[rPN.mnIN].mnI].maPrev);
181
182
0
                return B2DCubicBezier(rStart, rStart + rCPA, rEnd + rCPB, rEnd);
183
0
            }
184
185
            void impHandleCommon(PN& rPNa, PN& rPNb)
186
56.7M
            {
187
56.7M
                if(mbIsCurve)
188
0
                {
189
0
                    const B2DCubicBezier aNextA(createSegment(rPNa, false));
190
0
                    const B2DCubicBezier aPrevA(createSegment(rPNa, true));
191
192
0
                    if(aNextA.equal(aPrevA))
193
0
                    {
194
                        // deadend on A (identical edge)
195
0
                        return;
196
0
                    }
197
198
0
                    const B2DCubicBezier aNextB(createSegment(rPNb, false));
199
0
                    const B2DCubicBezier aPrevB(createSegment(rPNb, true));
200
201
0
                    if(aNextB.equal(aPrevB))
202
0
                    {
203
                        // deadend on B (identical edge)
204
0
                        return;
205
0
                    }
206
207
0
                    if(aPrevA.equal(aPrevB))
208
0
                    {
209
                        // common edge in same direction
210
0
                        return;
211
0
                    }
212
0
                    else if(aPrevA.equal(aNextB))
213
0
                    {
214
                        // common edge in opposite direction
215
0
                        if(aNextA.equal(aPrevB))
216
0
                        {
217
                            // common edge in opposite direction continues
218
0
                            return;
219
0
                        }
220
0
                        else
221
0
                        {
222
                            // common edge in opposite direction leave
223
0
                            impSwitchNext(rPNa, rPNb);
224
0
                        }
225
0
                    }
226
0
                    else if(aNextA.equal(aNextB))
227
0
                    {
228
                        // common edge in same direction enter
229
                        // search leave edge
230
0
                        PN* pPNa2 = &maPNV[rPNa.mnIN];
231
0
                        PN* pPNb2 = &maPNV[rPNb.mnIN];
232
0
                        bool bOnEdge(true);
233
234
0
                        do
235
0
                        {
236
0
                            const B2DCubicBezier aNextA2(createSegment(*pPNa2, false));
237
0
                            const B2DCubicBezier aNextB2(createSegment(*pPNb2, false));
238
239
0
                            if(aNextA2.equal(aNextB2))
240
0
                            {
241
0
                                pPNa2 = &maPNV[pPNa2->mnIN];
242
0
                                pPNb2 = &maPNV[pPNb2->mnIN];
243
0
                            }
244
0
                            else
245
0
                            {
246
0
                                bOnEdge = false;
247
0
                            }
248
0
                        }
249
0
                        while(bOnEdge && pPNa2 != &rPNa && pPNb2 != &rPNb);
250
251
0
                        if(bOnEdge)
252
0
                        {
253
                            // loop over two identical polygon paths
254
0
                            return;
255
0
                        }
256
0
                        else
257
0
                        {
258
                            // enter at rPNa, rPNb; leave at pPNa2, pPNb2. No common edges
259
                            // at enter/leave. Check for crossover.
260
0
                            const B2DVector aPrevCA(aPrevA.interpolatePoint(0.5) - aPrevA.getStartPoint());
261
0
                            const B2DVector aNextCA(aNextA.interpolatePoint(0.5) - aNextA.getStartPoint());
262
0
                            const B2DVector aPrevCB(aPrevB.interpolatePoint(0.5) - aPrevB.getStartPoint());
263
0
                            const bool bEnter(impLeftOfEdges(aPrevCA, aNextCA, aPrevCB));
264
265
0
                            const B2DCubicBezier aNextA2(createSegment(*pPNa2, false));
266
0
                            const B2DCubicBezier aPrevA2(createSegment(*pPNa2, true));
267
0
                            const B2DCubicBezier aNextB2(createSegment(*pPNb2, false));
268
0
                            const B2DVector aPrevCA2(aPrevA2.interpolatePoint(0.5) - aPrevA2.getStartPoint());
269
0
                            const B2DVector aNextCA2(aNextA2.interpolatePoint(0.5) - aNextA2.getStartPoint());
270
0
                            const B2DVector aNextCB2(aNextB2.interpolatePoint(0.5) - aNextB2.getStartPoint());
271
0
                            const bool bLeave(impLeftOfEdges(aPrevCA2, aNextCA2, aNextCB2));
272
273
0
                            if(bEnter != bLeave)
274
0
                            {
275
                                // crossover
276
0
                                impSwitchNext(rPNa, rPNb);
277
0
                            }
278
0
                        }
279
0
                    }
280
0
                    else if(aNextA.equal(aPrevB))
281
0
                    {
282
                        // common edge in opposite direction enter
283
0
                        impSwitchNext(rPNa, rPNb);
284
0
                    }
285
0
                    else
286
0
                    {
287
                        // no common edges, check for crossover
288
0
                        const B2DVector aPrevCA(aPrevA.interpolatePoint(0.5) - aPrevA.getStartPoint());
289
0
                        const B2DVector aNextCA(aNextA.interpolatePoint(0.5) - aNextA.getStartPoint());
290
0
                        const B2DVector aPrevCB(aPrevB.interpolatePoint(0.5) - aPrevB.getStartPoint());
291
0
                        const B2DVector aNextCB(aNextB.interpolatePoint(0.5) - aNextB.getStartPoint());
292
293
0
                        const bool bEnter(impLeftOfEdges(aPrevCA, aNextCA, aPrevCB));
294
0
                        const bool bLeave(impLeftOfEdges(aPrevCA, aNextCA, aNextCB));
295
296
0
                        if(bEnter != bLeave)
297
0
                        {
298
                            // crossover
299
0
                            impSwitchNext(rPNa, rPNb);
300
0
                        }
301
0
                    }
302
0
                }
303
56.7M
                else
304
56.7M
                {
305
56.7M
                    const B2DPoint& rNextA(maPNV[rPNa.mnIN].maPoint);
306
56.7M
                    const B2DPoint& rPrevA(maPNV[rPNa.mnIP].maPoint);
307
308
56.7M
                    if(rNextA.equal(rPrevA))
309
32.0M
                    {
310
                        // deadend on A
311
32.0M
                        return;
312
32.0M
                    }
313
314
24.7M
                    const B2DPoint& rNextB(maPNV[rPNb.mnIN].maPoint);
315
24.7M
                    const B2DPoint& rPrevB(maPNV[rPNb.mnIP].maPoint);
316
317
24.7M
                    if(rNextB.equal(rPrevB))
318
5.68M
                    {
319
                        // deadend on B
320
5.68M
                        return;
321
5.68M
                    }
322
323
19.0M
                    if(rPrevA.equal(rPrevB))
324
8.31M
                    {
325
                        // common edge in same direction
326
8.31M
                        return;
327
8.31M
                    }
328
10.7M
                    else if(rPrevA.equal(rNextB))
329
7.30M
                    {
330
                        // common edge in opposite direction
331
7.30M
                        if(rNextA.equal(rPrevB))
332
7.04M
                        {
333
                            // common edge in opposite direction continues
334
7.04M
                            return;
335
7.04M
                        }
336
252k
                        else
337
252k
                        {
338
                            // common edge in opposite direction leave
339
252k
                            impSwitchNext(rPNa, rPNb);
340
252k
                        }
341
7.30M
                    }
342
3.47M
                    else if(rNextA.equal(rNextB))
343
646k
                    {
344
                        // common edge in same direction enter
345
                        // search leave edge
346
646k
                        PN* pPNa2 = &maPNV[rPNa.mnIN];
347
646k
                        PN* pPNb2 = &maPNV[rPNb.mnIN];
348
646k
                        bool bOnEdge(true);
349
350
646k
                        do
351
6.43M
                        {
352
6.43M
                            const B2DPoint& rNextA2(maPNV[pPNa2->mnIN].maPoint);
353
6.43M
                            const B2DPoint& rNextB2(maPNV[pPNb2->mnIN].maPoint);
354
355
6.43M
                            if(rNextA2.equal(rNextB2))
356
5.81M
                            {
357
5.81M
                                pPNa2 = &maPNV[pPNa2->mnIN];
358
5.81M
                                pPNb2 = &maPNV[pPNb2->mnIN];
359
5.81M
                            }
360
616k
                            else
361
616k
                            {
362
616k
                                bOnEdge = false;
363
616k
                            }
364
6.43M
                        }
365
6.43M
                        while(bOnEdge && pPNa2 != &rPNa && pPNb2 != &rPNb);
366
367
646k
                        if(bOnEdge)
368
30.1k
                        {
369
                            // loop over two identical polygon paths
370
30.1k
                            return;
371
30.1k
                        }
372
616k
                        else
373
616k
                        {
374
                            // enter at rPNa, rPNb; leave at pPNa2, pPNb2. No common edges
375
                            // at enter/leave. Check for crossover.
376
616k
                            const B2DPoint& aPointE(rPNa.maPoint);
377
616k
                            const B2DVector aPrevAE(rPrevA - aPointE);
378
616k
                            const B2DVector aNextAE(rNextA - aPointE);
379
616k
                            const B2DVector aPrevBE(rPrevB - aPointE);
380
381
616k
                            const B2DPoint& aPointL(pPNa2->maPoint);
382
616k
                            const B2DVector aPrevAL(maPNV[pPNa2->mnIP].maPoint - aPointL);
383
616k
                            const B2DVector aNextAL(maPNV[pPNa2->mnIN].maPoint - aPointL);
384
616k
                            const B2DVector aNextBL(maPNV[pPNb2->mnIN].maPoint - aPointL);
385
386
616k
                            const bool bEnter(impLeftOfEdges(aPrevAE, aNextAE, aPrevBE));
387
616k
                            const bool bLeave(impLeftOfEdges(aPrevAL, aNextAL, aNextBL));
388
389
616k
                            if(bEnter != bLeave)
390
164k
                            {
391
                                // crossover; switch start or end
392
164k
                                impSwitchNext(rPNa, rPNb);
393
164k
                            }
394
616k
                        }
395
646k
                    }
396
2.82M
                    else if(rNextA.equal(rPrevB))
397
334k
                    {
398
                        // common edge in opposite direction enter
399
334k
                        impSwitchNext(rPNa, rPNb);
400
334k
                    }
401
2.49M
                    else
402
2.49M
                    {
403
                        // no common edges, check for crossover
404
2.49M
                        const B2DPoint& aPoint(rPNa.maPoint);
405
2.49M
                        const B2DVector aPrevA(rPrevA - aPoint);
406
2.49M
                        const B2DVector aNextA(rNextA - aPoint);
407
2.49M
                        const B2DVector aPrevB(rPrevB - aPoint);
408
2.49M
                        const B2DVector aNextB(rNextB - aPoint);
409
410
2.49M
                        const bool bEnter(impLeftOfEdges(aPrevA, aNextA, aPrevB));
411
2.49M
                        const bool bLeave(impLeftOfEdges(aPrevA, aNextA, aNextB));
412
413
2.49M
                        if(bEnter != bLeave)
414
578k
                        {
415
                            // crossover
416
578k
                            impSwitchNext(rPNa, rPNb);
417
578k
                        }
418
2.49M
                    }
419
19.0M
                }
420
56.7M
            }
421
422
            void impSolve()
423
331k
            {
424
                // sort by point to identify common nodes easier
425
331k
                std::sort(maSNV.begin(), maSNV.end());
426
427
                // handle common nodes
428
331k
                const sal_uInt32 nNodeCount(maSNV.size());
429
430
                // snap unsharp-equal points
431
331k
                if(nNodeCount)
432
331k
                {
433
331k
                    basegfx::B2DPoint* pLast(&maSNV[0].mpPN->maPoint);
434
435
331k
                    for(const auto& rSN : maSNV)
436
4.62M
                    {
437
4.62M
                        basegfx::B2DPoint* pCurrent(&rSN.mpPN->maPoint);
438
439
4.62M
                        if(pLast->equal(*pCurrent) && (pLast->getX() != pCurrent->getX() || pLast->getY() != pCurrent->getY()))
440
125k
                        {
441
125k
                            const basegfx::B2DPoint aMiddle((*pLast + *pCurrent) * 0.5);
442
443
125k
                            if(pLast->getX() != aMiddle.getX() || pLast->getY() != aMiddle.getY())
444
78.7k
                            {
445
78.7k
                                maCorrectionTable.emplace_back(*pLast, aMiddle);
446
78.7k
                                *pLast = aMiddle;
447
78.7k
                            }
448
449
125k
                            if(pCurrent->getX() != aMiddle.getX() || pCurrent->getY() != aMiddle.getY())
450
102k
                            {
451
102k
                                maCorrectionTable.emplace_back(*pCurrent, aMiddle);
452
102k
                                *pCurrent = aMiddle;
453
102k
                            }
454
125k
                        }
455
456
4.62M
                        pLast = pCurrent;
457
4.62M
                    }
458
459
4.62M
                    for (sal_uInt32 a = 0; a < nNodeCount - 1; a++)
460
4.29M
                    {
461
                        // test a before using it, not after. Also use nPointCount instead of aSortNodes.size()
462
4.29M
                        PN& rPNb = *(maSNV[a].mpPN);
463
464
61.0M
                        for(sal_uInt32 b(a + 1); b < nNodeCount && rPNb.maPoint.equal(maSNV[b].mpPN->maPoint); b++)
465
56.7M
                        {
466
56.7M
                            impHandleCommon(rPNb, *maSNV[b].mpPN);
467
56.7M
                        }
468
4.29M
                    }
469
331k
                }
470
331k
            }
471
472
        public:
473
            explicit solver(const B2DPolygon& rOriginal)
474
0
            :   maOriginal(B2DPolyPolygon(rOriginal)),
475
0
                mbIsCurve(false),
476
0
                mbChanged(false)
477
0
            {
478
0
                const sal_uInt32 nOriginalCount(rOriginal.count());
479
480
0
                if(!nOriginalCount)
481
0
                    return;
482
483
0
                B2DPolygon aGeometry(utils::addPointsAtCutsAndTouches(rOriginal));
484
0
                aGeometry.removeDoublePoints();
485
0
                aGeometry = utils::simplifyCurveSegments(aGeometry);
486
0
                mbIsCurve = aGeometry.areControlPointsUsed();
487
488
0
                const sal_uInt32 nPointCount(aGeometry.count());
489
490
                // If it's not a bezier polygon, at least four points are needed to create
491
                // a self-intersection. If it's a bezier polygon, the minimum point number
492
                // is two, since with a single point You get a curve, but no self-intersection
493
0
                if(!(nPointCount > 3 || (nPointCount > 1 && mbIsCurve)))
494
0
                    return;
495
496
                // reserve space in point, control and sort vector.
497
0
                maSNV.reserve(nPointCount);
498
0
                maPNV.reserve(nPointCount);
499
0
                maVNV.reserve(mbIsCurve ? nPointCount : 0);
500
501
                // fill data
502
0
                impAddPolygon(0, aGeometry);
503
504
                // solve common nodes
505
0
                impSolve();
506
0
            }
507
508
            explicit solver(B2DPolyPolygon aOriginal, size_t* pPointLimit = nullptr)
509
331k
            :   maOriginal(std::move(aOriginal)),
510
331k
                mbIsCurve(false),
511
331k
                mbChanged(false)
512
331k
            {
513
331k
                sal_uInt32 nOriginalCount(maOriginal.count());
514
515
331k
                if(!nOriginalCount)
516
0
                    return;
517
518
331k
                B2DPolyPolygon aGeometry(utils::addPointsAtCutsAndTouches(maOriginal, pPointLimit));
519
331k
                aGeometry.removeDoublePoints();
520
331k
                aGeometry = utils::simplifyCurveSegments(aGeometry);
521
331k
                mbIsCurve = aGeometry.areControlPointsUsed();
522
331k
                nOriginalCount = aGeometry.count();
523
524
331k
                if(!nOriginalCount)
525
0
                    return;
526
527
                // If it's not a bezier curve, at least three points would be needed to have a
528
                // topological relevant (not empty) polygon. Since it's not known here if trivial
529
                // edges (dead ends) will be kept or sorted out, add non-bezier polygons with
530
                // more than one point.
531
                // For bezier curves, the minimum for defining an area is also one.
532
331k
                sal_uInt32 nPointCount = std::accumulate( aGeometry.begin(), aGeometry.end(), sal_uInt32(0),
533
396k
                    [](sal_uInt32 a, const basegfx::B2DPolygon& b){return a + b.count();});
534
535
331k
                if(!nPointCount)
536
0
                    return;
537
538
                // reserve space in point, control and sort vector.
539
331k
                maSNV.reserve(nPointCount);
540
331k
                maPNV.reserve(nPointCount);
541
331k
                maVNV.reserve(mbIsCurve ? nPointCount : 0);
542
543
                // fill data
544
331k
                sal_uInt32 nInsertIndex(0);
545
546
331k
                for(const auto& rCandidate : aGeometry )
547
396k
                {
548
396k
                    const sal_uInt32 nCandCount(rCandidate.count());
549
550
                    // use same condition as above, the data vector is
551
                    // pre-allocated
552
396k
                    if(nCandCount)
553
396k
                    {
554
396k
                        impAddPolygon(nInsertIndex, rCandidate);
555
396k
                        nInsertIndex += nCandCount;
556
396k
                    }
557
396k
                }
558
559
                // solve common nodes
560
331k
                impSolve();
561
331k
            }
562
563
            B2DPolyPolygon getB2DPolyPolygon()
564
331k
            {
565
331k
                if(mbChanged)
566
1.73k
                {
567
1.73k
                    B2DPolyPolygon aRetval;
568
1.73k
                    const sal_uInt32 nCount(maPNV.size());
569
1.73k
                    sal_uInt32 nCountdown(nCount);
570
571
3.26M
                    for(sal_uInt32 a(0); nCountdown && a < nCount; a++)
572
3.26M
                    {
573
3.26M
                        PN& rPN = maPNV[a];
574
575
3.26M
                        if(rPN.mnI != SAL_MAX_UINT32)
576
260k
                        {
577
                            // unused node, start new part polygon
578
260k
                            B2DPolygon aNewPart;
579
260k
                            PN* pPNCurr = &rPN;
580
581
260k
                            do
582
3.30M
                            {
583
3.30M
                                const B2DPoint& rPoint = pPNCurr->maPoint;
584
3.30M
                                aNewPart.append(rPoint);
585
586
3.30M
                                if(mbIsCurve)
587
0
                                {
588
0
                                    const VN& rVNCurr = maVNV[pPNCurr->mnI];
589
590
0
                                    if(!rVNCurr.maPrev.equalZero())
591
0
                                    {
592
0
                                        aNewPart.setPrevControlPoint(aNewPart.count() - 1, rPoint + rVNCurr.maPrev);
593
0
                                    }
594
595
0
                                    if(!rVNCurr.maNext.equalZero())
596
0
                                    {
597
0
                                        aNewPart.setNextControlPoint(aNewPart.count() - 1, rPoint + rVNCurr.maNext);
598
0
                                    }
599
0
                                }
600
601
3.30M
                                pPNCurr->mnI = SAL_MAX_UINT32;
602
3.30M
                                nCountdown--;
603
3.30M
                                pPNCurr = &(maPNV[pPNCurr->mnIN]);
604
3.30M
                            }
605
3.30M
                            while(pPNCurr != &rPN && pPNCurr->mnI != SAL_MAX_UINT32);
606
607
                            // close and add
608
260k
                            aNewPart.setClosed(true);
609
260k
                            aRetval.append(aNewPart);
610
260k
                        }
611
3.26M
                    }
612
613
1.73k
                    return aRetval;
614
1.73k
                }
615
330k
                else
616
330k
                {
617
330k
                    const sal_uInt32 nCorrectionSize(maCorrectionTable.size());
618
619
                    // no change, return original
620
330k
                    if(!nCorrectionSize)
621
330k
                    {
622
330k
                        return maOriginal;
623
330k
                    }
624
625
                    // apply coordinate corrections to ensure inside/outside correctness after solving
626
6
                    const sal_uInt32 nPolygonCount(maOriginal.count());
627
6
                    basegfx::B2DPolyPolygon aRetval(maOriginal);
628
629
166
                    for(sal_uInt32 a(0); a < nPolygonCount; a++)
630
160
                    {
631
160
                        basegfx::B2DPolygon aTemp(aRetval.getB2DPolygon(a));
632
160
                        const sal_uInt32 nPointCount(aTemp.count());
633
160
                        bool bChanged(false);
634
635
1.68k
                        for(sal_uInt32 b(0); b < nPointCount; b++)
636
1.52k
                        {
637
1.52k
                            const basegfx::B2DPoint aCandidate(aTemp.getB2DPoint(b));
638
639
26.6k
                            for(sal_uInt32 c(0); c < nCorrectionSize; c++)
640
25.1k
                            {
641
25.1k
                                if(maCorrectionTable[c].first.getX() == aCandidate.getX() && maCorrectionTable[c].first.getY() == aCandidate.getY())
642
69
                                {
643
69
                                    aTemp.setB2DPoint(b, maCorrectionTable[c].second);
644
69
                                    bChanged = true;
645
69
                                }
646
25.1k
                            }
647
1.52k
                        }
648
649
160
                        if(bChanged)
650
12
                        {
651
12
                            aRetval.setB2DPolygon(a, aTemp);
652
12
                        }
653
160
                    }
654
655
6
                    return aRetval;
656
330k
                }
657
331k
            }
658
        };
659
660
    } // end of anonymous namespace
661
} // end of namespace basegfx
662
663
namespace basegfx::utils
664
{
665
666
        B2DPolyPolygon solveCrossovers(const B2DPolyPolygon& rCandidate, size_t* pPointLimit)
667
3.05k
        {
668
3.05k
            if(rCandidate.count() > 0)
669
3.02k
            {
670
3.02k
                solver aSolver(rCandidate, pPointLimit);
671
3.02k
                return aSolver.getB2DPolyPolygon();
672
3.02k
            }
673
31
            else
674
31
            {
675
31
                return rCandidate;
676
31
            }
677
3.05k
        }
678
679
        B2DPolyPolygon solveCrossovers(const B2DPolygon& rCandidate)
680
0
        {
681
0
            solver aSolver(rCandidate);
682
0
            return aSolver.getB2DPolyPolygon();
683
0
        }
684
685
        B2DPolyPolygon stripNeutralPolygons(const B2DPolyPolygon& rCandidate)
686
331k
        {
687
331k
            B2DPolyPolygon aRetval;
688
689
331k
            for(const auto& rPolygon : rCandidate)
690
582k
            {
691
582k
                if(utils::getOrientation(rPolygon) != B2VectorOrientation::Neutral)
692
458k
                {
693
458k
                    aRetval.append(rPolygon);
694
458k
                }
695
582k
            }
696
697
331k
            return aRetval;
698
331k
        }
699
700
        B2DPolyPolygon createNonzeroConform(const B2DPolyPolygon& rCandidate)
701
0
        {
702
0
            if (rCandidate.count() > 1000)
703
0
            {
704
0
                SAL_WARN("basegfx", "this poly is too large, " << rCandidate.count() << " elements, to be able to process timeously, falling back to ignoring the winding rule, which is likely to cause rendering artifacts");
705
0
                return rCandidate;
706
0
            }
707
708
0
            B2DPolyPolygon aCandidate;
709
710
            // remove all self-intersections and intersections
711
0
            if(rCandidate.count() == 1)
712
0
            {
713
0
                aCandidate = basegfx::utils::solveCrossovers(rCandidate.getB2DPolygon(0));
714
0
            }
715
0
            else
716
0
            {
717
0
                aCandidate = basegfx::utils::solveCrossovers(rCandidate);
718
0
            }
719
720
            // cleanup evtl. neutral polygons
721
0
            aCandidate = basegfx::utils::stripNeutralPolygons(aCandidate);
722
723
            // remove all polygons which have the same orientation as the polygon they are directly contained in
724
0
            const sal_uInt32 nCount(aCandidate.count());
725
726
0
            if(nCount > 1)
727
0
            {
728
0
                sal_uInt32 a, b;
729
0
                std::vector< StripHelper > aHelpers;
730
0
                aHelpers.resize(nCount);
731
732
0
                for(a = 0; a < nCount; a++)
733
0
                {
734
0
                    const B2DPolygon& aCand(aCandidate.getB2DPolygon(a));
735
0
                    StripHelper* pNewHelper = &(aHelpers[a]);
736
0
                    pNewHelper->maRange = aCand.getB2DRange();
737
0
                    pNewHelper->meOrinetation = utils::getOrientation(aCand);
738
739
                    // initialize with own orientation
740
0
                    pNewHelper->mnDepth = (pNewHelper->meOrinetation == B2VectorOrientation::Negative ? -1 : 1);
741
0
                }
742
743
0
                for(a = 0; a < nCount - 1; a++)
744
0
                {
745
0
                    const B2DPolygon& aCandA(aCandidate.getB2DPolygon(a));
746
0
                    StripHelper& rHelperA = aHelpers[a];
747
748
0
                    for(b = a + 1; b < nCount; b++)
749
0
                    {
750
0
                        const B2DPolygon& aCandB(aCandidate.getB2DPolygon(b));
751
0
                        StripHelper& rHelperB = aHelpers[b];
752
0
                        const bool bAInB(rHelperB.maRange.isInside(rHelperA.maRange) && utils::isInside(aCandB, aCandA, true));
753
754
0
                        if(bAInB)
755
0
                        {
756
                            // A is inside B, add orientation of B to A
757
0
                            rHelperA.mnDepth += (rHelperB.meOrinetation == B2VectorOrientation::Negative ? -1 : 1);
758
0
                        }
759
760
0
                        const bool bBInA(rHelperA.maRange.isInside(rHelperB.maRange) && utils::isInside(aCandA, aCandB, true));
761
762
0
                        if(bBInA)
763
0
                        {
764
                            // B is inside A, add orientation of A to B
765
0
                            rHelperB.mnDepth += (rHelperA.meOrinetation == B2VectorOrientation::Negative ? -1 : 1);
766
0
                        }
767
0
                    }
768
0
                }
769
770
0
                const B2DPolyPolygon aSource(aCandidate);
771
0
                aCandidate.clear();
772
773
0
                for(a = 0; a < nCount; a++)
774
0
                {
775
0
                    const StripHelper& rHelper = aHelpers[a];
776
                    // for contained unequal oriented polygons sum will be 0
777
                    // for contained equal it will be >=2 or <=-2
778
                    // for free polygons (not contained) it will be 1 or -1
779
                    // -> accept all which are >=-1 && <= 1
780
0
                    bool bAcceptEntry(rHelper.mnDepth >= -1 && rHelper.mnDepth <= 1);
781
782
0
                    if(bAcceptEntry)
783
0
                    {
784
0
                        aCandidate.append(aSource.getB2DPolygon(a));
785
0
                    }
786
0
                }
787
0
            }
788
789
0
            return aCandidate;
790
0
        }
791
792
        B2DPolyPolygon stripDispensablePolygons(const B2DPolyPolygon& rCandidate, bool bKeepAboveZero)
793
985
        {
794
985
            const sal_uInt32 nCount(rCandidate.count());
795
985
            B2DPolyPolygon aRetval;
796
797
985
            if(nCount)
798
954
            {
799
954
                if(nCount == 1)
800
139
                {
801
139
                    if(!bKeepAboveZero && utils::getOrientation(rCandidate.getB2DPolygon(0)) == B2VectorOrientation::Positive)
802
0
                    {
803
0
                        aRetval = rCandidate;
804
0
                    }
805
139
                }
806
815
                else
807
815
                {
808
815
                    sal_uInt32 a, b;
809
815
                    std::vector< StripHelper > aHelpers;
810
815
                    aHelpers.resize(nCount);
811
812
67.1k
                    for(a = 0; a < nCount; a++)
813
66.2k
                    {
814
66.2k
                        const B2DPolygon& aCandidate(rCandidate.getB2DPolygon(a));
815
66.2k
                        StripHelper* pNewHelper = &(aHelpers[a]);
816
66.2k
                        pNewHelper->maRange = aCandidate.getB2DRange();
817
66.2k
                        pNewHelper->meOrinetation = utils::getOrientation(aCandidate);
818
66.2k
                        pNewHelper->mnDepth = (pNewHelper->meOrinetation == B2VectorOrientation::Negative ? -1 : 0);
819
66.2k
                    }
820
821
66.2k
                    for(a = 0; a < nCount - 1; a++)
822
65.4k
                    {
823
65.4k
                        const B2DPolygon& aCandA(rCandidate.getB2DPolygon(a));
824
65.4k
                        StripHelper& rHelperA = aHelpers[a];
825
826
6.57M
                        for(b = a + 1; b < nCount; b++)
827
6.50M
                        {
828
6.50M
                            const B2DPolygon& aCandB(rCandidate.getB2DPolygon(b));
829
6.50M
                            StripHelper& rHelperB = aHelpers[b];
830
6.50M
                            const bool bAInB(rHelperB.maRange.isInside(rHelperA.maRange) && utils::isInside(aCandB, aCandA, true));
831
6.50M
                            const bool bBInA(rHelperA.maRange.isInside(rHelperB.maRange) && utils::isInside(aCandA, aCandB, true));
832
833
6.50M
                            if(bAInB && bBInA)
834
8.29k
                            {
835
                                // congruent
836
8.29k
                                if(rHelperA.meOrinetation == rHelperB.meOrinetation)
837
8.28k
                                {
838
                                    // two polys or two holes. Lower one of them to get one of them out of the way.
839
                                    // Since each will be contained in the other one, both will be increased, too.
840
                                    // So, for lowering, increase only one of them
841
8.28k
                                    rHelperA.mnDepth++;
842
8.28k
                                }
843
8
                                else
844
8
                                {
845
                                    // poly and hole. They neutralize, so get rid of both. Move securely below zero.
846
8
                                    rHelperA.mnDepth = - static_cast<sal_Int32>(nCount);
847
8
                                    rHelperB.mnDepth = - static_cast<sal_Int32>(nCount);
848
8
                                }
849
8.29k
                            }
850
6.49M
                            else
851
6.49M
                            {
852
6.49M
                                if(bAInB)
853
2.27k
                                {
854
2.27k
                                    if(rHelperB.meOrinetation == B2VectorOrientation::Negative)
855
638
                                    {
856
638
                                        rHelperA.mnDepth--;
857
638
                                    }
858
1.63k
                                    else
859
1.63k
                                    {
860
1.63k
                                        rHelperA.mnDepth++;
861
1.63k
                                    }
862
2.27k
                                }
863
6.49M
                                else if(bBInA)
864
89.5k
                                {
865
89.5k
                                    if(rHelperA.meOrinetation == B2VectorOrientation::Negative)
866
16.6k
                                    {
867
16.6k
                                        rHelperB.mnDepth--;
868
16.6k
                                    }
869
72.8k
                                    else
870
72.8k
                                    {
871
72.8k
                                        rHelperB.mnDepth++;
872
72.8k
                                    }
873
89.5k
                                }
874
6.49M
                            }
875
6.50M
                        }
876
65.4k
                    }
877
878
67.1k
                    for(a = 0; a < nCount; a++)
879
66.2k
                    {
880
66.2k
                        const StripHelper& rHelper = aHelpers[a];
881
66.2k
                        bool bAcceptEntry(bKeepAboveZero ? 1 <= rHelper.mnDepth : rHelper.mnDepth == 0);
882
883
66.2k
                        if(bAcceptEntry)
884
19.6k
                        {
885
19.6k
                            aRetval.append(rCandidate.getB2DPolygon(a));
886
19.6k
                        }
887
66.2k
                    }
888
815
                }
889
954
            }
890
891
985
            return aRetval;
892
985
        }
893
894
        B2DPolyPolygon prepareForPolygonOperation(const B2DPolygon& rCandidate)
895
0
        {
896
0
            solver aSolver(rCandidate);
897
0
            B2DPolyPolygon aRetval(stripNeutralPolygons(aSolver.getB2DPolyPolygon()));
898
899
0
            return correctOrientations(aRetval);
900
0
        }
901
902
        B2DPolyPolygon prepareForPolygonOperation(const B2DPolyPolygon& rCandidate)
903
328k
        {
904
328k
            solver aSolver(rCandidate);
905
328k
            B2DPolyPolygon aRetval(stripNeutralPolygons(aSolver.getB2DPolyPolygon()));
906
907
328k
            return correctOrientations(aRetval);
908
328k
        }
909
910
        B2DPolyPolygon solvePolygonOperationOr(const B2DPolyPolygon& rCandidateA, const B2DPolyPolygon& rCandidateB)
911
0
        {
912
0
            if(!rCandidateA.count())
913
0
            {
914
0
                return rCandidateB;
915
0
            }
916
0
            else if(!rCandidateB.count())
917
0
            {
918
0
                return rCandidateA;
919
0
            }
920
0
            else
921
0
            {
922
                // concatenate polygons, solve crossovers and throw away all sub-polygons
923
                // which have a depth other than 0.
924
0
                B2DPolyPolygon aRetval(rCandidateA);
925
926
0
                aRetval.append(rCandidateB);
927
0
                aRetval = solveCrossovers(aRetval);
928
0
                aRetval = stripNeutralPolygons(aRetval);
929
930
0
                return stripDispensablePolygons(aRetval);
931
0
            }
932
0
        }
933
934
        B2DPolyPolygon solvePolygonOperationXor(const B2DPolyPolygon& rCandidateA, const B2DPolyPolygon& rCandidateB)
935
0
        {
936
0
            if(!rCandidateA.count())
937
0
            {
938
0
                return rCandidateB;
939
0
            }
940
0
            else if(!rCandidateB.count())
941
0
            {
942
0
                return rCandidateA;
943
0
            }
944
0
            else
945
0
            {
946
                // XOR is pretty simple: By definition it is the simple concatenation of
947
                // the single polygons since we imply XOR fill rule. Make it intersection-free
948
                // and correct orientations
949
0
                B2DPolyPolygon aRetval(rCandidateA);
950
951
0
                aRetval.append(rCandidateB);
952
0
                aRetval = solveCrossovers(aRetval);
953
0
                aRetval = stripNeutralPolygons(aRetval);
954
955
0
                return correctOrientations(aRetval);
956
0
            }
957
0
        }
958
959
        B2DPolyPolygon solvePolygonOperationAnd(const B2DPolyPolygon& rCandidateA, const B2DPolyPolygon& rCandidateB)
960
164k
        {
961
164k
            if(!rCandidateA.count())
962
0
            {
963
0
                return B2DPolyPolygon();
964
0
            }
965
164k
            else if(!rCandidateB.count())
966
0
            {
967
0
                return B2DPolyPolygon();
968
0
            }
969
164k
            else
970
164k
            {
971
                // tdf#130150 shortcut & precision: If both are simple ranges,
972
                // solve based on ranges
973
164k
                if(basegfx::utils::isRectangle(rCandidateA) && basegfx::utils::isRectangle(rCandidateB))
974
164k
                {
975
                    // *if* both are ranges, AND always can be solved
976
164k
                    const basegfx::B2DRange aRangeA(rCandidateA.getB2DRange());
977
164k
                    const basegfx::B2DRange aRangeB(rCandidateB.getB2DRange());
978
979
164k
                    if(aRangeA.isInside(aRangeB))
980
318
                    {
981
                        // 2nd completely inside 1st -> 2nd is result of AND
982
318
                        return rCandidateB;
983
318
                    }
984
985
164k
                    if(aRangeB.isInside(aRangeA))
986
163k
                    {
987
                        // 2nd completely inside 1st -> 2nd is result of AND
988
163k
                        return rCandidateA;
989
163k
                    }
990
991
                    // solve by intersection
992
323
                    basegfx::B2DRange aIntersect(aRangeA);
993
323
                    aIntersect.intersect(aRangeB);
994
995
323
                    if(aIntersect.isEmpty())
996
0
                    {
997
                        // no overlap -> empty polygon as result of AND
998
0
                        return B2DPolyPolygon();
999
0
                    }
1000
1001
                    // create polygon result
1002
323
                    return B2DPolyPolygon(
1003
323
                        basegfx::utils::createPolygonFromRect(
1004
323
                            aIntersect));
1005
323
                }
1006
1007
                // concatenate polygons, solve crossovers and throw away all sub-polygons
1008
                // with a depth of < 1. This means to keep all polygons where at least two
1009
                // polygons do overlap.
1010
0
                B2DPolyPolygon aRetval(rCandidateA);
1011
1012
0
                aRetval.append(rCandidateB);
1013
0
                aRetval = solveCrossovers(aRetval);
1014
0
                aRetval = stripNeutralPolygons(aRetval);
1015
1016
0
                return stripDispensablePolygons(aRetval, true);
1017
164k
            }
1018
164k
        }
1019
1020
        B2DPolyPolygon solvePolygonOperationDiff(const B2DPolyPolygon& rCandidateA, const B2DPolyPolygon& rCandidateB)
1021
0
        {
1022
0
            if(!rCandidateA.count())
1023
0
            {
1024
0
                return B2DPolyPolygon();
1025
0
            }
1026
0
            else if(!rCandidateB.count())
1027
0
            {
1028
0
                return rCandidateA;
1029
0
            }
1030
0
            else
1031
0
            {
1032
                // Make B topologically to holes and append to A
1033
0
                B2DPolyPolygon aRetval(rCandidateB);
1034
1035
0
                aRetval.flip();
1036
0
                aRetval.append(rCandidateA);
1037
1038
                // solve crossovers and throw away all sub-polygons which have a
1039
                // depth other than 0.
1040
0
                aRetval = basegfx::utils::solveCrossovers(aRetval);
1041
0
                aRetval = basegfx::utils::stripNeutralPolygons(aRetval);
1042
1043
0
                return basegfx::utils::stripDispensablePolygons(aRetval);
1044
0
            }
1045
0
        }
1046
1047
        B2DPolyPolygon mergeToSinglePolyPolygon(const B2DPolyPolygonVector& rInput)
1048
0
        {
1049
0
            if(rInput.empty())
1050
0
                return B2DPolyPolygon();
1051
1052
            // first step: prepareForPolygonOperation and simple merge of non-overlapping
1053
            // PolyPolygons for speedup; this is possible for the wanted OR-operation
1054
0
            B2DPolyPolygonVector aResult;
1055
0
            aResult.reserve(rInput.size());
1056
1057
0
            for(const basegfx::B2DPolyPolygon & a : rInput)
1058
0
            {
1059
0
                const basegfx::B2DPolyPolygon aCandidate(prepareForPolygonOperation(a));
1060
1061
0
                if(!aResult.empty())
1062
0
                {
1063
0
                    const B2DRange aCandidateRange(aCandidate.getB2DRange());
1064
0
                    bool bCouldMergeSimple(false);
1065
1066
0
                    for(auto & b: aResult)
1067
0
                    {
1068
0
                        basegfx::B2DPolyPolygon aTarget(b);
1069
0
                        const B2DRange aTargetRange(aTarget.getB2DRange());
1070
1071
0
                        if(!aCandidateRange.overlaps(aTargetRange))
1072
0
                        {
1073
0
                            aTarget.append(aCandidate);
1074
0
                            b = std::move(aTarget);
1075
0
                            bCouldMergeSimple = true;
1076
0
                            break;
1077
0
                        }
1078
0
                    }
1079
1080
0
                    if(!bCouldMergeSimple)
1081
0
                    {
1082
0
                        aResult.push_back(aCandidate);
1083
0
                    }
1084
0
                }
1085
0
                else
1086
0
                {
1087
0
                    aResult.push_back(aCandidate);
1088
0
                }
1089
0
            }
1090
1091
            // second step: melt pairwise to a single PolyPolygon
1092
0
            while(aResult.size() > 1)
1093
0
            {
1094
0
                B2DPolyPolygonVector aResult2;
1095
0
                aResult2.reserve((aResult.size() / 2) + 1);
1096
1097
0
                for(size_t a(0); a < aResult.size(); a += 2)
1098
0
                {
1099
0
                    if(a + 1 < aResult.size())
1100
0
                    {
1101
                        // a pair for processing
1102
0
                        aResult2.push_back(solvePolygonOperationOr(aResult[a], aResult[a + 1]));
1103
0
                    }
1104
0
                    else
1105
0
                    {
1106
                        // last single PolyPolygon; copy to target to not lose it
1107
0
                        aResult2.push_back(aResult[a]);
1108
0
                    }
1109
0
                }
1110
1111
0
                aResult = std::move(aResult2);
1112
0
            }
1113
1114
            // third step: get result
1115
0
            if(aResult.size() == 1)
1116
0
            {
1117
0
                return aResult[0];
1118
0
            }
1119
1120
0
            return B2DPolyPolygon();
1121
0
        }
1122
1123
        namespace
1124
        {
1125
        struct B2DPointCompare
1126
        {
1127
            bool operator()(const basegfx::B2DPoint& lhs, const basegfx::B2DPoint& rhs) const
1128
0
            {
1129
0
                return std::make_pair(lhs.getX(), lhs.getY()) < std::make_pair(rhs.getX(), rhs.getY());
1130
0
            }
1131
        };
1132
1133
        struct B2DPointCompareYThenX
1134
        {
1135
            bool operator()(const basegfx::B2DPoint& lhs, const basegfx::B2DPoint& rhs) const
1136
0
            {
1137
0
                return std::make_pair(lhs.getY(), lhs.getX()) < std::make_pair(rhs.getY(), rhs.getX());
1138
0
            }
1139
        };
1140
1141
        }
1142
1143
        // Combine rectangles geometrically to a single OR'ed polygon.
1144
        // Algorithm is from
1145
        //     "Uniqueness of orthogonal connect-the-dots" Joseph O'Rourke 1988
1146
        // The basic algorithm is:
1147
        //   Sort points by lowest x, lowest y
1148
        //   Go through each column and create edges between the vertices 2i and 2i + 1 in that column
1149
        //   Sort points by lowest y, lowest x
1150
        //   Go through each row and create edges between the vertices 2i and 2i + 1 in that row.
1151
        //
1152
        basegfx::B2DPolyPolygon combineRectanglesToPolyPolygon(const std::vector< basegfx::B2DRange >& rRectangles)
1153
0
        {
1154
0
            o3tl::sorted_vector<basegfx::B2DPoint, B2DPointCompare> sort_x;
1155
0
            for (auto const & rRect : rRectangles)
1156
0
            {
1157
0
                auto checkPoint = [&sort_x](double x, double y)
1158
0
                {
1159
0
                    basegfx::B2DPoint pt(x, y);
1160
0
                    auto it = sort_x.find(pt);
1161
0
                    if (it != sort_x.end()) // Shared vertice, remove it.
1162
0
                        sort_x.erase(it);
1163
0
                    else
1164
0
                        sort_x.insert(pt);
1165
0
                };
1166
0
                checkPoint(rRect.getMinX(), rRect.getMinY());
1167
0
                checkPoint(rRect.getMinX(), rRect.getMaxY());
1168
0
                checkPoint(rRect.getMaxX(), rRect.getMinY());
1169
0
                checkPoint(rRect.getMaxX(), rRect.getMaxY());
1170
0
            }
1171
1172
0
            o3tl::sorted_vector<basegfx::B2DPoint, B2DPointCompareYThenX> sort_y;
1173
0
            for (auto const & i : sort_x)
1174
0
                sort_y.insert(i);
1175
1176
0
            std::map<basegfx::B2DPoint, basegfx::B2DPoint, B2DPointCompare> edges_h;
1177
0
            std::map<basegfx::B2DPoint, basegfx::B2DPoint, B2DPointCompare> edges_v;
1178
1179
0
            size_t i = 0;
1180
0
            while (i < sort_x.size())
1181
0
            {
1182
0
                auto curr_y = sort_y[i].getY();
1183
0
                while (i < sort_x.size() && sort_y[i].getY() == curr_y)
1184
0
                {
1185
0
                    edges_h[sort_y[i]] = sort_y[i + 1];
1186
0
                    edges_h[sort_y[i + 1]] = sort_y[i];
1187
0
                    i += 2;
1188
0
                }
1189
0
            }
1190
0
            i = 0;
1191
0
            while (i < sort_x.size())
1192
0
            {
1193
0
                auto curr_x = sort_x[i].getX();
1194
0
                while (i < sort_x.size() && sort_x[i].getX() == curr_x)
1195
0
                {
1196
0
                    edges_v[sort_x[i]] = sort_x[i + 1];
1197
0
                    edges_v[sort_x[i + 1]] = sort_x[i];
1198
0
                    i += 2;
1199
0
                }
1200
0
            }
1201
1202
           // Get all the polygons.
1203
0
            basegfx::B2DPolyPolygon aPolyPolygon;
1204
0
            std::vector<std::tuple<basegfx::B2DPoint, bool>> tmpPolygon;
1205
0
            while (!edges_h.empty())
1206
0
            {
1207
0
                tmpPolygon.clear();
1208
                // We can start with any point.
1209
0
                basegfx::B2DPoint pt = edges_h.begin()->first;
1210
0
                edges_h.erase(edges_h.begin());
1211
0
                tmpPolygon.push_back({pt, false});
1212
0
                for (;;)
1213
0
                {
1214
0
                    auto [curr, e] = tmpPolygon.back();
1215
0
                    if (!e)
1216
0
                    {
1217
0
                        auto it = edges_v.find(curr);
1218
0
                        auto next_vertex = it->second;
1219
0
                        edges_v.erase(it);
1220
0
                        tmpPolygon.push_back({next_vertex, true});
1221
0
                    }
1222
0
                    else
1223
0
                    {
1224
0
                        auto it = edges_h.find(curr);
1225
0
                        auto next_vertex = it->second;
1226
0
                        edges_h.erase(it);
1227
0
                        tmpPolygon.push_back({next_vertex, false});
1228
0
                    }
1229
0
                    if (tmpPolygon.back() == tmpPolygon.front())
1230
0
                    {
1231
                        // Closed polygon
1232
0
                        break;
1233
0
                    }
1234
0
                }
1235
0
                for (auto const & pair : tmpPolygon)
1236
0
                {
1237
0
                    auto const & vertex = std::get<0>(pair);
1238
0
                    edges_h.erase(vertex);
1239
0
                    edges_v.erase(vertex);
1240
0
                }
1241
0
                basegfx::B2DPolygon aPoly;
1242
0
                aPoly.reserve(tmpPolygon.size());
1243
0
                for (auto const & pair : tmpPolygon)
1244
0
                    aPoly.append(std::get<0>(pair));
1245
0
                aPolyPolygon.append(std::move(aPoly));
1246
0
            }
1247
1248
0
            return aPolyPolygon;
1249
0
        }
1250
1251
} // end of namespace
1252
1253
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */