Coverage Report

Created: 2026-04-09 11:41

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/basegfx/source/tools/b2dclipstate.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/utils/b2dclipstate.hxx>
21
22
#include <basegfx/range/b2drange.hxx>
23
#include <basegfx/range/b2dpolyrange.hxx>
24
#include <basegfx/polygon/b2dpolygon.hxx>
25
#include <basegfx/polygon/b2dpolygontools.hxx>
26
#include <basegfx/polygon/b2dpolypolygon.hxx>
27
#include <basegfx/polygon/b2dpolypolygontools.hxx>
28
#include <basegfx/polygon/b2dpolypolygoncutter.hxx>
29
#include <utility>
30
31
32
namespace basegfx::utils
33
{
34
    class ImplB2DClipState
35
    {
36
    public:
37
        enum Operation {UNION, INTERSECT, XOR, SUBTRACT};
38
39
        ImplB2DClipState() :
40
111k
            mePendingOps(UNION)
41
111k
        {}
42
43
        explicit ImplB2DClipState( B2DPolyPolygon aPoly ) :
44
53.6k
            maClipPoly(std::move(aPoly)),
45
53.6k
            mePendingOps(UNION)
46
53.6k
        {}
47
48
        bool isCleared() const
49
13.1k
        {
50
13.1k
            return !maClipPoly.count()
51
13.1k
                && !maPendingPolygons.count()
52
13.1k
                && !maPendingRanges.count();
53
13.1k
        }
54
55
        bool isNullClipPoly() const
56
0
        {
57
0
            return maClipPoly.count() == 1
58
0
                && !maClipPoly.getB2DPolygon(0).count();
59
0
        }
60
61
        bool isNull() const
62
0
        {
63
0
            return !maPendingPolygons.count()
64
0
                && !maPendingRanges.count()
65
0
                && isNullClipPoly();
66
0
        }
67
68
        void makeNull()
69
0
        {
70
0
            maPendingPolygons.clear();
71
0
            maPendingRanges.clear();
72
0
            maClipPoly.clear();
73
0
            maClipPoly.append(B2DPolygon());
74
0
            mePendingOps = UNION;
75
0
        }
76
77
        bool operator==(const ImplB2DClipState& rRHS) const
78
702
        {
79
702
            return maPendingPolygons == rRHS.maPendingPolygons
80
702
                && maPendingRanges == rRHS.maPendingRanges
81
702
                && maClipPoly == rRHS.maClipPoly
82
702
                && mePendingOps == rRHS.mePendingOps;
83
702
        }
84
85
        void addRange(const B2DRange& rRange, Operation eOp)
86
0
        {
87
0
            if( rRange.isEmpty() )
88
0
                return;
89
90
0
            commitPendingPolygons();
91
0
            if( mePendingOps != eOp )
92
0
                commitPendingRanges();
93
94
0
            mePendingOps = eOp;
95
0
            maPendingRanges.appendElement(
96
0
                rRange,
97
0
                B2VectorOrientation::Positive);
98
0
        }
99
100
        void addPolyPolygon(const B2DPolyPolygon& aPoly, Operation eOp)
101
0
        {
102
0
            commitPendingRanges();
103
0
            if( mePendingOps != eOp )
104
0
                commitPendingPolygons();
105
106
0
            mePendingOps = eOp;
107
0
            maPendingPolygons.append(aPoly);
108
0
        }
109
110
        void unionRange(const B2DRange& rRange)
111
0
        {
112
0
            if( isCleared() )
113
0
                return;
114
115
0
            addRange(rRange,UNION);
116
0
        }
117
118
        void unionPolyPolygon(const B2DPolyPolygon& rPolyPoly)
119
0
        {
120
0
            if( isCleared() )
121
0
                return;
122
123
0
            addPolyPolygon(rPolyPoly,UNION);
124
0
        }
125
126
        void intersectRange(const B2DRange& rRange)
127
0
        {
128
0
            if( isNull() )
129
0
                return;
130
131
0
            addRange(rRange,INTERSECT);
132
0
        }
133
134
        void intersectPolyPolygon(const B2DPolyPolygon& rPolyPoly)
135
0
        {
136
0
            if( isNull() )
137
0
                return;
138
139
0
            addPolyPolygon(rPolyPoly,INTERSECT);
140
0
        }
141
142
        void subtractRange(const B2DRange& rRange )
143
0
        {
144
0
            if( isNull() )
145
0
                return;
146
147
0
            addRange(rRange,SUBTRACT);
148
0
        }
149
150
        void subtractPolyPolygon(const B2DPolyPolygon& rPolyPoly)
151
0
        {
152
0
            if( isNull() )
153
0
                return;
154
155
0
            addPolyPolygon(rPolyPoly,SUBTRACT);
156
0
        }
157
158
        void xorRange(const B2DRange& rRange)
159
0
        {
160
0
            addRange(rRange,XOR);
161
0
        }
162
163
        void xorPolyPolygon(const B2DPolyPolygon& rPolyPoly)
164
0
        {
165
0
            addPolyPolygon(rPolyPoly,XOR);
166
0
        }
167
168
        void transform(const basegfx::B2DHomMatrix& rTranslate)
169
0
        {
170
0
            maPendingRanges.transform(rTranslate);
171
0
            maPendingPolygons.transform(rTranslate);
172
0
            maClipPoly.transform(rTranslate);
173
0
        }
174
175
        B2DPolyPolygon const & getClipPoly() const
176
0
        {
177
0
            commitPendingRanges();
178
0
            commitPendingPolygons();
179
180
0
            return maClipPoly;
181
0
        }
182
183
    private:
184
        void commitPendingPolygons() const
185
0
        {
186
0
            if( !maPendingPolygons.count() )
187
0
                return;
188
189
            // assumption: maClipPoly has kept polygons prepared for
190
            // clipping; i.e. no neutral polygons & correct
191
            // orientation
192
0
            maPendingPolygons = utils::prepareForPolygonOperation(maPendingPolygons);
193
0
            const bool bIsEmpty=isNullClipPoly();
194
0
            const bool bIsCleared=!maClipPoly.count();
195
0
            switch(mePendingOps)
196
0
            {
197
0
                case UNION:
198
0
                    assert( !bIsCleared );
199
200
0
                    if( bIsEmpty )
201
0
                        maClipPoly = maPendingPolygons;
202
0
                    else
203
0
                        maClipPoly = utils::solvePolygonOperationOr(
204
0
                            maClipPoly,
205
0
                            maPendingPolygons);
206
0
                    break;
207
0
                case INTERSECT:
208
0
                    assert( !bIsEmpty );
209
210
0
                    if( bIsCleared )
211
0
                        maClipPoly = maPendingPolygons;
212
0
                    else
213
0
                        maClipPoly = utils::solvePolygonOperationAnd(
214
0
                            maClipPoly,
215
0
                            maPendingPolygons);
216
0
                    break;
217
0
                case XOR:
218
0
                    if( bIsEmpty )
219
0
                        maClipPoly = maPendingPolygons;
220
0
                    else if( bIsCleared )
221
0
                    {
222
                        // not representable, strictly speaking,
223
                        // using polygons with the common even/odd
224
                        // or nonzero winding number fill rule. If
225
                        // we'd want to represent it, fill rule
226
                        // would need to be "non-negative winding
227
                        // number" (and we then would return
228
                        // 'holes' here)
229
230
                        // going for an ugly hack meanwhile
231
0
                        maClipPoly = utils::solvePolygonOperationXor(
232
0
                            B2DPolyPolygon(
233
0
                                utils::createPolygonFromRect(B2DRange(-1E20,-1E20,1E20,1E20))),
234
0
                            maPendingPolygons);
235
0
                    }
236
0
                    else
237
0
                        maClipPoly = utils::solvePolygonOperationXor(
238
0
                            maClipPoly,
239
0
                            maPendingPolygons);
240
0
                    break;
241
0
                case SUBTRACT:
242
0
                    assert( !bIsEmpty );
243
244
                    // first union all pending ones, subtract en bloc then
245
0
                    maPendingPolygons = solveCrossovers(maPendingPolygons);
246
0
                    maPendingPolygons = stripNeutralPolygons(maPendingPolygons);
247
0
                    maPendingPolygons = stripDispensablePolygons(maPendingPolygons);
248
249
0
                    if( bIsCleared )
250
0
                    {
251
                        // not representable, strictly speaking,
252
                        // using polygons with the common even/odd
253
                        // or nonzero winding number fill rule. If
254
                        // we'd want to represent it, fill rule
255
                        // would need to be "non-negative winding
256
                        // number" (and we then would return
257
                        // 'holes' here)
258
259
                        // going for an ugly hack meanwhile
260
0
                        maClipPoly = utils::solvePolygonOperationDiff(
261
0
                            B2DPolyPolygon(
262
0
                                utils::createPolygonFromRect(B2DRange(-1E20,-1E20,1E20,1E20))),
263
0
                            maPendingPolygons);
264
0
                    }
265
0
                    else
266
0
                        maClipPoly = utils::solvePolygonOperationDiff(
267
0
                            maClipPoly,
268
0
                            maPendingPolygons);
269
0
                    break;
270
0
            }
271
272
0
            maPendingPolygons.clear();
273
0
            mePendingOps = UNION;
274
0
        }
275
276
        void commitPendingRanges() const
277
0
        {
278
0
            if( !maPendingRanges.count() )
279
0
                return;
280
281
            // use the specialized range clipper for the win
282
0
            B2DPolyPolygon aCollectedRanges;
283
0
            const bool bIsEmpty=isNullClipPoly();
284
0
            const bool bIsCleared=!maClipPoly.count();
285
0
            switch(mePendingOps)
286
0
            {
287
0
                case UNION:
288
0
                    assert( !bIsCleared );
289
290
0
                    aCollectedRanges = maPendingRanges.solveCrossovers();
291
0
                    aCollectedRanges = stripNeutralPolygons(aCollectedRanges);
292
0
                    aCollectedRanges = stripDispensablePolygons(aCollectedRanges);
293
0
                    if( bIsEmpty )
294
0
                        maClipPoly = aCollectedRanges;
295
0
                    else
296
0
                        maClipPoly = utils::solvePolygonOperationOr(
297
0
                            maClipPoly,
298
0
                            aCollectedRanges);
299
0
                    break;
300
0
                case INTERSECT:
301
0
                    assert( !bIsEmpty );
302
303
0
                    aCollectedRanges = maPendingRanges.solveCrossovers();
304
0
                    aCollectedRanges = stripNeutralPolygons(aCollectedRanges);
305
0
                    if( maPendingRanges.count() > 1 )
306
0
                        aCollectedRanges = stripDispensablePolygons(aCollectedRanges, true);
307
308
0
                    if( bIsCleared )
309
0
                        maClipPoly = aCollectedRanges;
310
0
                    else
311
0
                        maClipPoly = utils::solvePolygonOperationAnd(
312
0
                            maClipPoly,
313
0
                            aCollectedRanges);
314
0
                    break;
315
0
                case XOR:
316
0
                    aCollectedRanges = maPendingRanges.solveCrossovers();
317
0
                    aCollectedRanges = stripNeutralPolygons(aCollectedRanges);
318
0
                    aCollectedRanges = correctOrientations(aCollectedRanges);
319
320
0
                    if( bIsEmpty )
321
0
                        maClipPoly = aCollectedRanges;
322
0
                    else if( bIsCleared )
323
0
                    {
324
                        // not representable, strictly speaking,
325
                        // using polygons with the common even/odd
326
                        // or nonzero winding number fill rule. If
327
                        // we'd want to represent it, fill rule
328
                        // would need to be "non-negative winding
329
                        // number" (and we then would return
330
                        // 'holes' here)
331
332
                        // going for an ugly hack meanwhile
333
0
                        maClipPoly = utils::solvePolygonOperationXor(
334
0
                            B2DPolyPolygon(
335
0
                                utils::createPolygonFromRect(B2DRange(-1E20,-1E20,1E20,1E20))),
336
0
                            aCollectedRanges);
337
0
                    }
338
0
                    else
339
0
                        maClipPoly = utils::solvePolygonOperationXor(
340
0
                            maClipPoly,
341
0
                            aCollectedRanges);
342
0
                    break;
343
0
                case SUBTRACT:
344
0
                    assert( !bIsEmpty );
345
346
                    // first union all pending ranges, subtract en bloc then
347
0
                    aCollectedRanges = maPendingRanges.solveCrossovers();
348
0
                    aCollectedRanges = stripNeutralPolygons(aCollectedRanges);
349
0
                    aCollectedRanges = stripDispensablePolygons(aCollectedRanges);
350
351
0
                    if( bIsCleared )
352
0
                    {
353
                        // not representable, strictly speaking,
354
                        // using polygons with the common even/odd
355
                        // or nonzero winding number fill rule. If
356
                        // we'd want to represent it, fill rule
357
                        // would need to be "non-negative winding
358
                        // number" (and we then would return
359
                        // 'holes' here)
360
361
                        // going for an ugly hack meanwhile
362
0
                        maClipPoly = utils::solvePolygonOperationDiff(
363
0
                            B2DPolyPolygon(
364
0
                                utils::createPolygonFromRect(B2DRange(-1E20,-1E20,1E20,1E20))),
365
0
                            aCollectedRanges);
366
0
                    }
367
0
                    else
368
0
                        maClipPoly = utils::solvePolygonOperationDiff(
369
0
                            maClipPoly,
370
0
                            aCollectedRanges);
371
0
                    break;
372
0
            }
373
374
0
            maPendingRanges.clear();
375
0
            mePendingOps = UNION;
376
0
        }
377
378
        mutable B2DPolyPolygon maPendingPolygons;
379
        mutable B2DPolyRange   maPendingRanges;
380
        mutable B2DPolyPolygon maClipPoly;
381
        mutable Operation      mePendingOps;
382
    };
383
384
111k
    B2DClipState::B2DClipState() = default;
385
386
165k
    B2DClipState::~B2DClipState() = default;
387
388
0
    B2DClipState::B2DClipState( const B2DClipState& ) = default;
389
390
0
    B2DClipState::B2DClipState( B2DClipState&& ) = default;
391
392
    B2DClipState::B2DClipState( const B2DPolyPolygon& rPolyPoly ) :
393
53.6k
        mpImpl( ImplB2DClipState(rPolyPoly) )
394
53.6k
    {}
395
396
88.8k
    B2DClipState& B2DClipState::operator=( const B2DClipState& ) = default;
397
398
953
    B2DClipState& B2DClipState::operator=( B2DClipState&& ) = default;
399
400
    void B2DClipState::makeNull()
401
0
    {
402
0
        mpImpl->makeNull();
403
0
    }
404
405
    bool B2DClipState::isCleared() const
406
13.1k
    {
407
13.1k
        return mpImpl->isCleared();
408
13.1k
    }
409
410
    bool B2DClipState::operator==(const B2DClipState& rRHS) const
411
70.5k
    {
412
70.5k
        if(mpImpl.same_object(rRHS.mpImpl))
413
69.8k
            return true;
414
415
702
        return ((*mpImpl) == (*rRHS.mpImpl));
416
70.5k
    }
417
418
    void B2DClipState::unionRange(const B2DRange& rRange)
419
0
    {
420
0
        mpImpl->unionRange(rRange);
421
0
    }
422
423
    void B2DClipState::unionPolyPolygon(const B2DPolyPolygon& rPolyPoly)
424
0
    {
425
0
        mpImpl->unionPolyPolygon(rPolyPoly);
426
0
    }
427
428
    void B2DClipState::intersectRange(const B2DRange& rRange)
429
0
    {
430
0
        mpImpl->intersectRange(rRange);
431
0
    }
432
433
    void B2DClipState::intersectPolyPolygon(const B2DPolyPolygon& rPolyPoly)
434
0
    {
435
0
        mpImpl->intersectPolyPolygon(rPolyPoly);
436
0
    }
437
438
    void B2DClipState::subtractRange(const B2DRange& rRange)
439
0
    {
440
0
        mpImpl->subtractRange(rRange);
441
0
    }
442
443
    void B2DClipState::subtractPolyPolygon(const B2DPolyPolygon& rPolyPoly)
444
0
    {
445
0
        mpImpl->subtractPolyPolygon(rPolyPoly);
446
0
    }
447
448
    void B2DClipState::xorRange(const B2DRange& rRange)
449
0
    {
450
0
        mpImpl->xorRange(rRange);
451
0
    }
452
453
    void B2DClipState::xorPolyPolygon(const B2DPolyPolygon& rPolyPoly)
454
0
    {
455
0
        mpImpl->xorPolyPolygon(rPolyPoly);
456
0
    }
457
458
    B2DPolyPolygon const & B2DClipState::getClipPoly() const
459
0
    {
460
0
        return mpImpl->getClipPoly();
461
0
    }
462
463
    void B2DClipState::transform(const basegfx::B2DHomMatrix& rTranslate)
464
0
    {
465
0
        return mpImpl->transform(rTranslate);
466
0
    }
467
468
469
} // end of namespace
470
471
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */