LibreOffice Module basegfx (master) 1
b2dpolypolygoncutter.cxx
Go to the documentation of this file.
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
30#include <sal/log.hxx>
31#include <utility>
32#include <vector>
33#include <algorithm>
34#include <numeric>
35
36namespace basegfx
37{
38 namespace
39 {
40
41 struct StripHelper
42 {
43 B2DRange maRange;
44 sal_Int32 mnDepth;
46 };
47
48 struct PN
49 {
50 public:
51 B2DPoint maPoint;
52 sal_uInt32 mnI;
53 sal_uInt32 mnIP;
54 sal_uInt32 mnIN;
55 };
56
57 struct VN
58 {
59 public:
60 B2DVector maPrev;
61 B2DVector maNext;
62
63 // to have the correct curve segments in the crossover checks,
64 // it is necessary to keep the original next vectors, too. Else,
65 // it may happen to use an already switched next vector which
66 // would interpolate the wrong comparison point
67 B2DVector maOriginalNext;
68 };
69
70 struct SN
71 {
72 public:
74
75 bool operator<(const SN& rComp) const
76 {
77 if(fTools::equal(mpPN->maPoint.getX(), rComp.mpPN->maPoint.getX()))
78 {
79 if(fTools::equal(mpPN->maPoint.getY(), rComp.mpPN->maPoint.getY()))
80 {
81 return (mpPN->mnI < rComp.mpPN->mnI);
82 }
83 else
84 {
85 return fTools::less(mpPN->maPoint.getY(), rComp.mpPN->maPoint.getY());
86 }
87 }
88 else
89 {
90 return fTools::less(mpPN->maPoint.getX(), rComp.mpPN->maPoint.getX());
91 }
92 }
93 };
94
95 typedef std::vector< PN > PNV;
96 typedef std::vector< VN > VNV;
97 typedef std::vector< SN > SNV;
98 typedef std::pair< basegfx::B2DPoint /*orig*/, basegfx::B2DPoint /*repl*/ > CorrectionPair;
99
100 class solver
101 {
102 private:
103 const B2DPolyPolygon maOriginal;
104 PNV maPNV;
105 VNV maVNV;
106 SNV maSNV;
107 std::vector< CorrectionPair >
109
110 bool mbIsCurve : 1;
111 bool mbChanged : 1;
112
113 void impAddPolygon(const sal_uInt32 aPos, const B2DPolygon& rGeometry)
114 {
115 const sal_uInt32 nCount(rGeometry.count());
116 PN aNewPN;
117 VN aNewVN;
118 SN aNewSN;
119
120 for(sal_uInt32 a(0); a < nCount; a++)
121 {
122 const B2DPoint aPoint(rGeometry.getB2DPoint(a));
123 aNewPN.maPoint = aPoint;
124 aNewPN.mnI = aPos + a;
125 aNewPN.mnIP = aPos + ((a != 0) ? a - 1 : nCount - 1);
126 aNewPN.mnIN = aPos + ((a + 1 == nCount) ? 0 : a + 1);
127 maPNV.push_back(aNewPN);
128
129 if(mbIsCurve)
130 {
131 aNewVN.maPrev = rGeometry.getPrevControlPoint(a) - aPoint;
132 aNewVN.maNext = rGeometry.getNextControlPoint(a) - aPoint;
133 aNewVN.maOriginalNext = aNewVN.maNext;
134 maVNV.push_back(aNewVN);
135 }
136
137 aNewSN.mpPN = &maPNV[maPNV.size() - 1];
138 maSNV.push_back(aNewSN);
139 }
140 }
141
142 static bool impLeftOfEdges(const B2DVector& rVecA, const B2DVector& rVecB, const B2DVector& rTest)
143 {
144 // tests if rTest is left of both directed line segments along the line -rVecA, rVecB. Test is
145 // with border.
146 if(rVecA.cross(rVecB) > 0.0)
147 {
148 // b is left turn seen from a, test if Test is left of both and so inside (left is seen as inside)
149 const bool bBoolA(fTools::moreOrEqual(rVecA.cross(rTest), 0.0));
150 const bool bBoolB(fTools::lessOrEqual(rVecB.cross(rTest), 0.0));
151
152 return (bBoolA && bBoolB);
153 }
154 else
155 {
156 // b is right turn seen from a, test if Test is right of both and so outside (left is seen as inside)
157 const bool bBoolA(fTools::lessOrEqual(rVecA.cross(rTest), 0.0));
158 const bool bBoolB(fTools::moreOrEqual(rVecB.cross(rTest), 0.0));
159
160 return (!(bBoolA && bBoolB));
161 }
162 }
163
164 void impSwitchNext(PN& rPNa, PN& rPNb)
165 {
166 std::swap(rPNa.mnIN, rPNb.mnIN);
167
168 if(mbIsCurve)
169 {
170 VN& rVNa = maVNV[rPNa.mnI];
171 VN& rVNb = maVNV[rPNb.mnI];
172
173 std::swap(rVNa.maNext, rVNb.maNext);
174 }
175
176 if(!mbChanged)
177 {
178 mbChanged = true;
179 }
180 }
181
182 B2DCubicBezier createSegment(const PN& rPN, bool bPrev) const
183 {
184 const B2DPoint& rStart(rPN.maPoint);
185 const B2DPoint& rEnd(maPNV[bPrev ? rPN.mnIP : rPN.mnIN].maPoint);
186 const B2DVector& rCPA(bPrev ? maVNV[rPN.mnI].maPrev : maVNV[rPN.mnI].maNext);
187 // Use maOriginalNext, not maNext to create the original (yet unchanged)
188 // curve segment. Otherwise, this segment would NOT ne correct.
189 const B2DVector& rCPB(bPrev ? maVNV[maPNV[rPN.mnIP].mnI].maOriginalNext : maVNV[maPNV[rPN.mnIN].mnI].maPrev);
190
191 return B2DCubicBezier(rStart, rStart + rCPA, rEnd + rCPB, rEnd);
192 }
193
194 void impHandleCommon(PN& rPNa, PN& rPNb)
195 {
196 if(mbIsCurve)
197 {
198 const B2DCubicBezier aNextA(createSegment(rPNa, false));
199 const B2DCubicBezier aPrevA(createSegment(rPNa, true));
200
201 if(aNextA.equal(aPrevA))
202 {
203 // deadend on A (identical edge)
204 return;
205 }
206
207 const B2DCubicBezier aNextB(createSegment(rPNb, false));
208 const B2DCubicBezier aPrevB(createSegment(rPNb, true));
209
210 if(aNextB.equal(aPrevB))
211 {
212 // deadend on B (identical edge)
213 return;
214 }
215
216 if(aPrevA.equal(aPrevB))
217 {
218 // common edge in same direction
219 return;
220 }
221 else if(aPrevA.equal(aNextB))
222 {
223 // common edge in opposite direction
224 if(aNextA.equal(aPrevB))
225 {
226 // common edge in opposite direction continues
227 return;
228 }
229 else
230 {
231 // common edge in opposite direction leave
232 impSwitchNext(rPNa, rPNb);
233 }
234 }
235 else if(aNextA.equal(aNextB))
236 {
237 // common edge in same direction enter
238 // search leave edge
239 PN* pPNa2 = &maPNV[rPNa.mnIN];
240 PN* pPNb2 = &maPNV[rPNb.mnIN];
241 bool bOnEdge(true);
242
243 do
244 {
245 const B2DCubicBezier aNextA2(createSegment(*pPNa2, false));
246 const B2DCubicBezier aNextB2(createSegment(*pPNb2, false));
247
248 if(aNextA2.equal(aNextB2))
249 {
250 pPNa2 = &maPNV[pPNa2->mnIN];
251 pPNb2 = &maPNV[pPNb2->mnIN];
252 }
253 else
254 {
255 bOnEdge = false;
256 }
257 }
258 while(bOnEdge && pPNa2 != &rPNa && pPNb2 != &rPNb);
259
260 if(bOnEdge)
261 {
262 // loop over two identical polygon paths
263 return;
264 }
265 else
266 {
267 // enter at rPNa, rPNb; leave at pPNa2, pPNb2. No common edges
268 // at enter/leave. Check for crossover.
269 const B2DVector aPrevCA(aPrevA.interpolatePoint(0.5) - aPrevA.getStartPoint());
270 const B2DVector aNextCA(aNextA.interpolatePoint(0.5) - aNextA.getStartPoint());
271 const B2DVector aPrevCB(aPrevB.interpolatePoint(0.5) - aPrevB.getStartPoint());
272 const bool bEnter(impLeftOfEdges(aPrevCA, aNextCA, aPrevCB));
273
274 const B2DCubicBezier aNextA2(createSegment(*pPNa2, false));
275 const B2DCubicBezier aPrevA2(createSegment(*pPNa2, true));
276 const B2DCubicBezier aNextB2(createSegment(*pPNb2, false));
277 const B2DVector aPrevCA2(aPrevA2.interpolatePoint(0.5) - aPrevA2.getStartPoint());
278 const B2DVector aNextCA2(aNextA2.interpolatePoint(0.5) - aNextA2.getStartPoint());
279 const B2DVector aNextCB2(aNextB2.interpolatePoint(0.5) - aNextB2.getStartPoint());
280 const bool bLeave(impLeftOfEdges(aPrevCA2, aNextCA2, aNextCB2));
281
282 if(bEnter != bLeave)
283 {
284 // crossover
285 impSwitchNext(rPNa, rPNb);
286 }
287 }
288 }
289 else if(aNextA.equal(aPrevB))
290 {
291 // common edge in opposite direction enter
292 impSwitchNext(rPNa, rPNb);
293 }
294 else
295 {
296 // no common edges, check for crossover
297 const B2DVector aPrevCA(aPrevA.interpolatePoint(0.5) - aPrevA.getStartPoint());
298 const B2DVector aNextCA(aNextA.interpolatePoint(0.5) - aNextA.getStartPoint());
299 const B2DVector aPrevCB(aPrevB.interpolatePoint(0.5) - aPrevB.getStartPoint());
300 const B2DVector aNextCB(aNextB.interpolatePoint(0.5) - aNextB.getStartPoint());
301
302 const bool bEnter(impLeftOfEdges(aPrevCA, aNextCA, aPrevCB));
303 const bool bLeave(impLeftOfEdges(aPrevCA, aNextCA, aNextCB));
304
305 if(bEnter != bLeave)
306 {
307 // crossover
308 impSwitchNext(rPNa, rPNb);
309 }
310 }
311 }
312 else
313 {
314 const B2DPoint& rNextA(maPNV[rPNa.mnIN].maPoint);
315 const B2DPoint& rPrevA(maPNV[rPNa.mnIP].maPoint);
316
317 if(rNextA.equal(rPrevA))
318 {
319 // deadend on A
320 return;
321 }
322
323 const B2DPoint& rNextB(maPNV[rPNb.mnIN].maPoint);
324 const B2DPoint& rPrevB(maPNV[rPNb.mnIP].maPoint);
325
326 if(rNextB.equal(rPrevB))
327 {
328 // deadend on B
329 return;
330 }
331
332 if(rPrevA.equal(rPrevB))
333 {
334 // common edge in same direction
335 return;
336 }
337 else if(rPrevA.equal(rNextB))
338 {
339 // common edge in opposite direction
340 if(rNextA.equal(rPrevB))
341 {
342 // common edge in opposite direction continues
343 return;
344 }
345 else
346 {
347 // common edge in opposite direction leave
348 impSwitchNext(rPNa, rPNb);
349 }
350 }
351 else if(rNextA.equal(rNextB))
352 {
353 // common edge in same direction enter
354 // search leave edge
355 PN* pPNa2 = &maPNV[rPNa.mnIN];
356 PN* pPNb2 = &maPNV[rPNb.mnIN];
357 bool bOnEdge(true);
358
359 do
360 {
361 const B2DPoint& rNextA2(maPNV[pPNa2->mnIN].maPoint);
362 const B2DPoint& rNextB2(maPNV[pPNb2->mnIN].maPoint);
363
364 if(rNextA2.equal(rNextB2))
365 {
366 pPNa2 = &maPNV[pPNa2->mnIN];
367 pPNb2 = &maPNV[pPNb2->mnIN];
368 }
369 else
370 {
371 bOnEdge = false;
372 }
373 }
374 while(bOnEdge && pPNa2 != &rPNa && pPNb2 != &rPNb);
375
376 if(bOnEdge)
377 {
378 // loop over two identical polygon paths
379 return;
380 }
381 else
382 {
383 // enter at rPNa, rPNb; leave at pPNa2, pPNb2. No common edges
384 // at enter/leave. Check for crossover.
385 const B2DPoint& aPointE(rPNa.maPoint);
386 const B2DVector aPrevAE(rPrevA - aPointE);
387 const B2DVector aNextAE(rNextA - aPointE);
388 const B2DVector aPrevBE(rPrevB - aPointE);
389
390 const B2DPoint& aPointL(pPNa2->maPoint);
391 const B2DVector aPrevAL(maPNV[pPNa2->mnIP].maPoint - aPointL);
392 const B2DVector aNextAL(maPNV[pPNa2->mnIN].maPoint - aPointL);
393 const B2DVector aNextBL(maPNV[pPNb2->mnIN].maPoint - aPointL);
394
395 const bool bEnter(impLeftOfEdges(aPrevAE, aNextAE, aPrevBE));
396 const bool bLeave(impLeftOfEdges(aPrevAL, aNextAL, aNextBL));
397
398 if(bEnter != bLeave)
399 {
400 // crossover; switch start or end
401 impSwitchNext(rPNa, rPNb);
402 }
403 }
404 }
405 else if(rNextA.equal(rPrevB))
406 {
407 // common edge in opposite direction enter
408 impSwitchNext(rPNa, rPNb);
409 }
410 else
411 {
412 // no common edges, check for crossover
413 const B2DPoint& aPoint(rPNa.maPoint);
414 const B2DVector aPrevA(rPrevA - aPoint);
415 const B2DVector aNextA(rNextA - aPoint);
416 const B2DVector aPrevB(rPrevB - aPoint);
417 const B2DVector aNextB(rNextB - aPoint);
418
419 const bool bEnter(impLeftOfEdges(aPrevA, aNextA, aPrevB));
420 const bool bLeave(impLeftOfEdges(aPrevA, aNextA, aNextB));
421
422 if(bEnter != bLeave)
423 {
424 // crossover
425 impSwitchNext(rPNa, rPNb);
426 }
427 }
428 }
429 }
430
431 void impSolve()
432 {
433 // sort by point to identify common nodes easier
434 std::sort(maSNV.begin(), maSNV.end());
435
436 // handle common nodes
437 const sal_uInt32 nNodeCount(maSNV.size());
438 sal_uInt32 a(0);
439
440 // snap unsharp-equal points
441 if(nNodeCount)
442 {
443 basegfx::B2DPoint* pLast(&maSNV[0].mpPN->maPoint);
444
445 for(const auto& rSN : maSNV)
446 {
447 basegfx::B2DPoint* pCurrent(&rSN.mpPN->maPoint);
448
449 if(pLast->equal(*pCurrent) && (pLast->getX() != pCurrent->getX() || pLast->getY() != pCurrent->getY()))
450 {
451 const basegfx::B2DPoint aMiddle((*pLast + *pCurrent) * 0.5);
452
453 if(pLast->getX() != aMiddle.getX() || pLast->getY() != aMiddle.getY())
454 {
455 maCorrectionTable.emplace_back(*pLast, aMiddle);
456 *pLast = aMiddle;
457 }
458
459 if(pCurrent->getX() != aMiddle.getX() || pCurrent->getY() != aMiddle.getY())
460 {
461 maCorrectionTable.emplace_back(*pCurrent, aMiddle);
462 *pCurrent = aMiddle;
463 }
464 }
465
466 pLast = pCurrent;
467 }
468 }
469
470 for(a = 0; a < nNodeCount - 1; a++)
471 {
472 // test a before using it, not after. Also use nPointCount instead of aSortNodes.size()
473 PN& rPNb = *(maSNV[a].mpPN);
474
475 for(sal_uInt32 b(a + 1); b < nNodeCount && rPNb.maPoint.equal(maSNV[b].mpPN->maPoint); b++)
476 {
477 impHandleCommon(rPNb, *maSNV[b].mpPN);
478 }
479 }
480 }
481
482 public:
483 explicit solver(const B2DPolygon& rOriginal)
484 : maOriginal(B2DPolyPolygon(rOriginal)),
485 mbIsCurve(false),
486 mbChanged(false)
487 {
488 const sal_uInt32 nOriginalCount(rOriginal.count());
489
490 if(!nOriginalCount)
491 return;
492
493 B2DPolygon aGeometry(utils::addPointsAtCutsAndTouches(rOriginal));
494 aGeometry.removeDoublePoints();
495 aGeometry = utils::simplifyCurveSegments(aGeometry);
496 mbIsCurve = aGeometry.areControlPointsUsed();
497
498 const sal_uInt32 nPointCount(aGeometry.count());
499
500 // If it's not a bezier polygon, at least four points are needed to create
501 // a self-intersection. If it's a bezier polygon, the minimum point number
502 // is two, since with a single point You get a curve, but no self-intersection
503 if(!(nPointCount > 3 || (nPointCount > 1 && mbIsCurve)))
504 return;
505
506 // reserve space in point, control and sort vector.
507 maSNV.reserve(nPointCount);
508 maPNV.reserve(nPointCount);
509 maVNV.reserve(mbIsCurve ? nPointCount : 0);
510
511 // fill data
512 impAddPolygon(0, aGeometry);
513
514 // solve common nodes
515 impSolve();
516 }
517
518 explicit solver(B2DPolyPolygon aOriginal, size_t* pPointLimit = nullptr)
519 : maOriginal(std::move(aOriginal)),
520 mbIsCurve(false),
521 mbChanged(false)
522 {
523 sal_uInt32 nOriginalCount(maOriginal.count());
524
525 if(!nOriginalCount)
526 return;
527
528 B2DPolyPolygon aGeometry(utils::addPointsAtCutsAndTouches(maOriginal, pPointLimit));
529 aGeometry.removeDoublePoints();
530 aGeometry = utils::simplifyCurveSegments(aGeometry);
531 mbIsCurve = aGeometry.areControlPointsUsed();
532 nOriginalCount = aGeometry.count();
533
534 if(!nOriginalCount)
535 return;
536
537 // If it's not a bezier curve, at least three points would be needed to have a
538 // topological relevant (not empty) polygon. Since it's not known here if trivial
539 // edges (dead ends) will be kept or sorted out, add non-bezier polygons with
540 // more than one point.
541 // For bezier curves, the minimum for defining an area is also one.
542 sal_uInt32 nPointCount = std::accumulate( aGeometry.begin(), aGeometry.end(), sal_uInt32(0),
543 [](sal_uInt32 a, const basegfx::B2DPolygon& b){return a + b.count();});
544
545 if(!nPointCount)
546 return;
547
548 // reserve space in point, control and sort vector.
549 maSNV.reserve(nPointCount);
550 maPNV.reserve(nPointCount);
551 maVNV.reserve(mbIsCurve ? nPointCount : 0);
552
553 // fill data
554 sal_uInt32 nInsertIndex(0);
555
556 for(const auto& rCandidate : aGeometry )
557 {
558 const sal_uInt32 nCandCount(rCandidate.count());
559
560 // use same condition as above, the data vector is
561 // pre-allocated
562 if(nCandCount)
563 {
564 impAddPolygon(nInsertIndex, rCandidate);
565 nInsertIndex += nCandCount;
566 }
567 }
568
569 // solve common nodes
570 impSolve();
571 }
572
573 B2DPolyPolygon getB2DPolyPolygon()
574 {
575 if(mbChanged)
576 {
577 B2DPolyPolygon aRetval;
578 const sal_uInt32 nCount(maPNV.size());
579 sal_uInt32 nCountdown(nCount);
580
581 for(sal_uInt32 a(0); nCountdown && a < nCount; a++)
582 {
583 PN& rPN = maPNV[a];
584
585 if(rPN.mnI != SAL_MAX_UINT32)
586 {
587 // unused node, start new part polygon
588 B2DPolygon aNewPart;
589 PN* pPNCurr = &rPN;
590
591 do
592 {
593 const B2DPoint& rPoint = pPNCurr->maPoint;
594 aNewPart.append(rPoint);
595
596 if(mbIsCurve)
597 {
598 const VN& rVNCurr = maVNV[pPNCurr->mnI];
599
600 if(!rVNCurr.maPrev.equalZero())
601 {
602 aNewPart.setPrevControlPoint(aNewPart.count() - 1, rPoint + rVNCurr.maPrev);
603 }
604
605 if(!rVNCurr.maNext.equalZero())
606 {
607 aNewPart.setNextControlPoint(aNewPart.count() - 1, rPoint + rVNCurr.maNext);
608 }
609 }
610
611 pPNCurr->mnI = SAL_MAX_UINT32;
612 nCountdown--;
613 pPNCurr = &(maPNV[pPNCurr->mnIN]);
614 }
615 while(pPNCurr != &rPN && pPNCurr->mnI != SAL_MAX_UINT32);
616
617 // close and add
618 aNewPart.setClosed(true);
619 aRetval.append(aNewPart);
620 }
621 }
622
623 return aRetval;
624 }
625 else
626 {
627 const sal_uInt32 nCorrectionSize(maCorrectionTable.size());
628
629 // no change, return original
630 if(!nCorrectionSize)
631 {
632 return maOriginal;
633 }
634
635 // apply coordinate corrections to ensure inside/outside correctness after solving
636 const sal_uInt32 nPolygonCount(maOriginal.count());
638
639 for(sal_uInt32 a(0); a < nPolygonCount; a++)
640 {
641 basegfx::B2DPolygon aTemp(aRetval.getB2DPolygon(a));
642 const sal_uInt32 nPointCount(aTemp.count());
643 bool bChanged(false);
644
645 for(sal_uInt32 b(0); b < nPointCount; b++)
646 {
647 const basegfx::B2DPoint aCandidate(aTemp.getB2DPoint(b));
648
649 for(sal_uInt32 c(0); c < nCorrectionSize; c++)
650 {
651 if(maCorrectionTable[c].first.getX() == aCandidate.getX() && maCorrectionTable[c].first.getY() == aCandidate.getY())
652 {
653 aTemp.setB2DPoint(b, maCorrectionTable[c].second);
654 bChanged = true;
655 }
656 }
657 }
658
659 if(bChanged)
660 {
661 aRetval.setB2DPolygon(a, aTemp);
662 }
663 }
664
665 return aRetval;
666 }
667 }
668 };
669
670 } // end of anonymous namespace
671} // end of namespace basegfx
672
673namespace basegfx::utils
674{
675
676 B2DPolyPolygon solveCrossovers(const B2DPolyPolygon& rCandidate, size_t* pPointLimit)
677 {
678 if(rCandidate.count() > 0)
679 {
680 solver aSolver(rCandidate, pPointLimit);
681 return aSolver.getB2DPolyPolygon();
682 }
683 else
684 {
685 return rCandidate;
686 }
687 }
688
690 {
691 solver aSolver(rCandidate);
692 return aSolver.getB2DPolyPolygon();
693 }
694
696 {
697 B2DPolyPolygon aRetval;
698
699 for(const auto& rPolygon : rCandidate)
700 {
702 {
703 aRetval.append(rPolygon);
704 }
705 }
706
707 return aRetval;
708 }
709
711 {
712 if (rCandidate.count() > 1000)
713 {
714 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");
715 return rCandidate;
716 }
717
718 B2DPolyPolygon aCandidate;
719
720 // remove all self-intersections and intersections
721 if(rCandidate.count() == 1)
722 {
723 aCandidate = basegfx::utils::solveCrossovers(rCandidate.getB2DPolygon(0));
724 }
725 else
726 {
727 aCandidate = basegfx::utils::solveCrossovers(rCandidate);
728 }
729
730 // cleanup evtl. neutral polygons
731 aCandidate = basegfx::utils::stripNeutralPolygons(aCandidate);
732
733 // remove all polygons which have the same orientation as the polygon they are directly contained in
734 const sal_uInt32 nCount(aCandidate.count());
735
736 if(nCount > 1)
737 {
738 sal_uInt32 a, b;
739 std::vector< StripHelper > aHelpers;
740 aHelpers.resize(nCount);
741
742 for(a = 0; a < nCount; a++)
743 {
744 const B2DPolygon& aCand(aCandidate.getB2DPolygon(a));
745 StripHelper* pNewHelper = &(aHelpers[a]);
746 pNewHelper->maRange = utils::getRange(aCand);
747 pNewHelper->meOrinetation = utils::getOrientation(aCand);
748
749 // initialize with own orientation
750 pNewHelper->mnDepth = (pNewHelper->meOrinetation == B2VectorOrientation::Negative ? -1 : 1);
751 }
752
753 for(a = 0; a < nCount - 1; a++)
754 {
755 const B2DPolygon& aCandA(aCandidate.getB2DPolygon(a));
756 StripHelper& rHelperA = aHelpers[a];
757
758 for(b = a + 1; b < nCount; b++)
759 {
760 const B2DPolygon& aCandB(aCandidate.getB2DPolygon(b));
761 StripHelper& rHelperB = aHelpers[b];
762 const bool bAInB(rHelperB.maRange.isInside(rHelperA.maRange) && utils::isInside(aCandB, aCandA, true));
763
764 if(bAInB)
765 {
766 // A is inside B, add orientation of B to A
767 rHelperA.mnDepth += (rHelperB.meOrinetation == B2VectorOrientation::Negative ? -1 : 1);
768 }
769
770 const bool bBInA(rHelperA.maRange.isInside(rHelperB.maRange) && utils::isInside(aCandA, aCandB, true));
771
772 if(bBInA)
773 {
774 // B is inside A, add orientation of A to B
775 rHelperB.mnDepth += (rHelperA.meOrinetation == B2VectorOrientation::Negative ? -1 : 1);
776 }
777 }
778 }
779
780 const B2DPolyPolygon aSource(aCandidate);
781 aCandidate.clear();
782
783 for(a = 0; a < nCount; a++)
784 {
785 const StripHelper& rHelper = aHelpers[a];
786 // for contained unequal oriented polygons sum will be 0
787 // for contained equal it will be >=2 or <=-2
788 // for free polygons (not contained) it will be 1 or -1
789 // -> accept all which are >=-1 && <= 1
790 bool bAcceptEntry(rHelper.mnDepth >= -1 && rHelper.mnDepth <= 1);
791
792 if(bAcceptEntry)
793 {
794 aCandidate.append(aSource.getB2DPolygon(a));
795 }
796 }
797 }
798
799 return aCandidate;
800 }
801
802 B2DPolyPolygon stripDispensablePolygons(const B2DPolyPolygon& rCandidate, bool bKeepAboveZero)
803 {
804 const sal_uInt32 nCount(rCandidate.count());
805 B2DPolyPolygon aRetval;
806
807 if(nCount)
808 {
809 if(nCount == 1)
810 {
811 if(!bKeepAboveZero && utils::getOrientation(rCandidate.getB2DPolygon(0)) == B2VectorOrientation::Positive)
812 {
813 aRetval = rCandidate;
814 }
815 }
816 else
817 {
818 sal_uInt32 a, b;
819 std::vector< StripHelper > aHelpers;
820 aHelpers.resize(nCount);
821
822 for(a = 0; a < nCount; a++)
823 {
824 const B2DPolygon& aCandidate(rCandidate.getB2DPolygon(a));
825 StripHelper* pNewHelper = &(aHelpers[a]);
826 pNewHelper->maRange = utils::getRange(aCandidate);
827 pNewHelper->meOrinetation = utils::getOrientation(aCandidate);
828 pNewHelper->mnDepth = (pNewHelper->meOrinetation == B2VectorOrientation::Negative ? -1 : 0);
829 }
830
831 for(a = 0; a < nCount - 1; a++)
832 {
833 const B2DPolygon& aCandA(rCandidate.getB2DPolygon(a));
834 StripHelper& rHelperA = aHelpers[a];
835
836 for(b = a + 1; b < nCount; b++)
837 {
838 const B2DPolygon& aCandB(rCandidate.getB2DPolygon(b));
839 StripHelper& rHelperB = aHelpers[b];
840 const bool bAInB(rHelperB.maRange.isInside(rHelperA.maRange) && utils::isInside(aCandB, aCandA, true));
841 const bool bBInA(rHelperA.maRange.isInside(rHelperB.maRange) && utils::isInside(aCandA, aCandB, true));
842
843 if(bAInB && bBInA)
844 {
845 // congruent
846 if(rHelperA.meOrinetation == rHelperB.meOrinetation)
847 {
848 // two polys or two holes. Lower one of them to get one of them out of the way.
849 // Since each will be contained in the other one, both will be increased, too.
850 // So, for lowering, increase only one of them
851 rHelperA.mnDepth++;
852 }
853 else
854 {
855 // poly and hole. They neutralize, so get rid of both. Move securely below zero.
856 rHelperA.mnDepth = - static_cast<sal_Int32>(nCount);
857 rHelperB.mnDepth = - static_cast<sal_Int32>(nCount);
858 }
859 }
860 else
861 {
862 if(bAInB)
863 {
864 if(rHelperB.meOrinetation == B2VectorOrientation::Negative)
865 {
866 rHelperA.mnDepth--;
867 }
868 else
869 {
870 rHelperA.mnDepth++;
871 }
872 }
873 else if(bBInA)
874 {
875 if(rHelperA.meOrinetation == B2VectorOrientation::Negative)
876 {
877 rHelperB.mnDepth--;
878 }
879 else
880 {
881 rHelperB.mnDepth++;
882 }
883 }
884 }
885 }
886 }
887
888 for(a = 0; a < nCount; a++)
889 {
890 const StripHelper& rHelper = aHelpers[a];
891 bool bAcceptEntry(bKeepAboveZero ? 1 <= rHelper.mnDepth : rHelper.mnDepth == 0);
892
893 if(bAcceptEntry)
894 {
895 aRetval.append(rCandidate.getB2DPolygon(a));
896 }
897 }
898 }
899 }
900
901 return aRetval;
902 }
903
905 {
906 solver aSolver(rCandidate);
907 B2DPolyPolygon aRetval(stripNeutralPolygons(aSolver.getB2DPolyPolygon()));
908
909 return correctOrientations(aRetval);
910 }
911
913 {
914 solver aSolver(rCandidate);
915 B2DPolyPolygon aRetval(stripNeutralPolygons(aSolver.getB2DPolyPolygon()));
916
917 return correctOrientations(aRetval);
918 }
919
921 {
922 if(!rCandidateA.count())
923 {
924 return rCandidateB;
925 }
926 else if(!rCandidateB.count())
927 {
928 return rCandidateA;
929 }
930 else
931 {
932 // concatenate polygons, solve crossovers and throw away all sub-polygons
933 // which have a depth other than 0.
934 B2DPolyPolygon aRetval(rCandidateA);
935
936 aRetval.append(rCandidateB);
937 aRetval = solveCrossovers(aRetval);
938 aRetval = stripNeutralPolygons(aRetval);
939
940 return stripDispensablePolygons(aRetval);
941 }
942 }
943
945 {
946 if(!rCandidateA.count())
947 {
948 return rCandidateB;
949 }
950 else if(!rCandidateB.count())
951 {
952 return rCandidateA;
953 }
954 else
955 {
956 // XOR is pretty simple: By definition it is the simple concatenation of
957 // the single polygons since we imply XOR fill rule. Make it intersection-free
958 // and correct orientations
959 B2DPolyPolygon aRetval(rCandidateA);
960
961 aRetval.append(rCandidateB);
962 aRetval = solveCrossovers(aRetval);
963 aRetval = stripNeutralPolygons(aRetval);
964
965 return correctOrientations(aRetval);
966 }
967 }
968
970 {
971 if(!rCandidateA.count())
972 {
973 return B2DPolyPolygon();
974 }
975 else if(!rCandidateB.count())
976 {
977 return B2DPolyPolygon();
978 }
979 else
980 {
981 // tdf#130150 shortcut & precision: If both are simple ranges,
982 // solve based on ranges
983 if(basegfx::utils::isRectangle(rCandidateA) && basegfx::utils::isRectangle(rCandidateB))
984 {
985 // *if* both are ranges, AND always can be solved
986 const basegfx::B2DRange aRangeA(rCandidateA.getB2DRange());
987 const basegfx::B2DRange aRangeB(rCandidateB.getB2DRange());
988
989 if(aRangeA.isInside(aRangeB))
990 {
991 // 2nd completely inside 1st -> 2nd is result of AND
992 return rCandidateB;
993 }
994
995 if(aRangeB.isInside(aRangeA))
996 {
997 // 2nd completely inside 1st -> 2nd is result of AND
998 return rCandidateA;
999 }
1000
1001 // solve by intersection
1002 basegfx::B2DRange aIntersect(aRangeA);
1003 aIntersect.intersect(aRangeB);
1004
1005 if(aIntersect.isEmpty())
1006 {
1007 // no overlap -> empty polygon as result of AND
1008 return B2DPolyPolygon();
1009 }
1010
1011 // create polygon result
1012 return B2DPolyPolygon(
1014 aIntersect));
1015 }
1016
1017 // concatenate polygons, solve crossovers and throw away all sub-polygons
1018 // with a depth of < 1. This means to keep all polygons where at least two
1019 // polygons do overlap.
1020 B2DPolyPolygon aRetval(rCandidateA);
1021
1022 aRetval.append(rCandidateB);
1023 aRetval = solveCrossovers(aRetval);
1024 aRetval = stripNeutralPolygons(aRetval);
1025
1026 return stripDispensablePolygons(aRetval, true);
1027 }
1028 }
1029
1031 {
1032 if(!rCandidateA.count())
1033 {
1034 return B2DPolyPolygon();
1035 }
1036 else if(!rCandidateB.count())
1037 {
1038 return rCandidateA;
1039 }
1040 else
1041 {
1042 // Make B topologically to holes and append to A
1043 B2DPolyPolygon aRetval(rCandidateB);
1044
1045 aRetval.flip();
1046 aRetval.append(rCandidateA);
1047
1048 // solve crossovers and throw away all sub-polygons which have a
1049 // depth other than 0.
1050 aRetval = basegfx::utils::solveCrossovers(aRetval);
1051 aRetval = basegfx::utils::stripNeutralPolygons(aRetval);
1052
1054 }
1055 }
1056
1058 {
1059 if(rInput.empty())
1060 return B2DPolyPolygon();
1061
1062 // first step: prepareForPolygonOperation and simple merge of non-overlapping
1063 // PolyPolygons for speedup; this is possible for the wanted OR-operation
1064 B2DPolyPolygonVector aResult;
1065 aResult.reserve(rInput.size());
1066
1067 for(const basegfx::B2DPolyPolygon & a : rInput)
1068 {
1070
1071 if(!aResult.empty())
1072 {
1073 const B2DRange aCandidateRange(aCandidate.getB2DRange());
1074 bool bCouldMergeSimple(false);
1075
1076 for(auto & b: aResult)
1077 {
1078 basegfx::B2DPolyPolygon aTarget(b);
1079 const B2DRange aTargetRange(aTarget.getB2DRange());
1080
1081 if(!aCandidateRange.overlaps(aTargetRange))
1082 {
1083 aTarget.append(aCandidate);
1084 b = aTarget;
1085 bCouldMergeSimple = true;
1086 break;
1087 }
1088 }
1089
1090 if(!bCouldMergeSimple)
1091 {
1092 aResult.push_back(aCandidate);
1093 }
1094 }
1095 else
1096 {
1097 aResult.push_back(aCandidate);
1098 }
1099 }
1100
1101 // second step: melt pairwise to a single PolyPolygon
1102 while(aResult.size() > 1)
1103 {
1104 B2DPolyPolygonVector aResult2;
1105 aResult2.reserve((aResult.size() / 2) + 1);
1106
1107 for(size_t a(0); a < aResult.size(); a += 2)
1108 {
1109 if(a + 1 < aResult.size())
1110 {
1111 // a pair for processing
1112 aResult2.push_back(solvePolygonOperationOr(aResult[a], aResult[a + 1]));
1113 }
1114 else
1115 {
1116 // last single PolyPolygon; copy to target to not lose it
1117 aResult2.push_back(aResult[a]);
1118 }
1119 }
1120
1121 aResult = aResult2;
1122 }
1123
1124 // third step: get result
1125 if(aResult.size() == 1)
1126 {
1127 return aResult[0];
1128 }
1129
1130 return B2DPolyPolygon();
1131 }
1132
1133} // end of namespace
1134
1135/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
PN * mpPN
B2VectorOrientation meOrinetation
sal_uInt32 mnIN
B2DVector maNext
sal_uInt32 mnI
sal_uInt32 mnIP
B2DPoint maPoint
bool mbChanged
B2DRange maRange
sal_Int32 mnDepth
std::vector< CorrectionPair > maCorrectionTable
B2DVector maOriginalNext
const B2DPolyPolygon maOriginal
bool mbIsCurve
B2DVector maPrev
Base Point class with two double values.
Definition: b2dpoint.hxx:42
B2DPolygon const & getB2DPolygon(sal_uInt32 nIndex) const
void append(const B2DPolygon &rPolygon, sal_uInt32 nCount=1)
B2DRange getB2DRange() const
Get the B2DRange (Rectangle dimensions) of this B2DPolyPolygon.
sal_uInt32 count() const
void reserve(sal_uInt32 nCount)
sal_uInt32 count() const
member count
A two-dimensional interval over doubles.
Definition: b2drange.hxx:54
void intersect(const Range2D &rRange)
calc set intersection
Definition: Range2D.hxx:156
bool isInside(const Tuple2D< TYPE > &rTuple) const
yields true if given point is contained in set
Definition: Range2D.hxx:118
bool isEmpty() const
Check if the interval set is empty.
Definition: Range2D.hxx:69
bool overlaps(const Range2D &rRange) const
yields true if rRange at least partly inside set
Definition: Range2D.hxx:130
int nCount
uno_Any a
#define SAL_WARN(area, stream)
bool lessOrEqual(const T &rfValA, const T &rfValB)
Definition: ftools.hxx:188
bool equal(T const &rfValA, T const &rfValB)
Definition: ftools.hxx:169
bool less(const T &rfValA, const T &rfValB)
Definition: ftools.hxx:182
bool moreOrEqual(const T &rfValA, const T &rfValB)
Definition: ftools.hxx:200
B2DPolyPolygon createNonzeroConform(const B2DPolyPolygon &rCandidate)
Emulate nonzero winding rule filling.
B2DPolyPolygon prepareForPolygonOperation(const B2DPolygon &rCandidate)
prep for ops - solve self-intersections and intersections, remove neutral parts and check orientation...
B2DPolygon createPolygonFromRect(const B2DRectangle &rRect, double fRadiusX, double fRadiusY)
Create a polygon from a rectangle.
B2VectorOrientation getOrientation(const B2DPolygon &rCandidate)
B2DPolyPolygon solvePolygonOperationOr(const B2DPolyPolygon &rCandidateA, const B2DPolyPolygon &rCandidateB)
OR: Return all areas where CandidateA or CandidateB exist.
B2DPolyPolygon solvePolygonOperationAnd(const B2DPolyPolygon &rCandidateA, const B2DPolyPolygon &rCandidateB)
AND: Return all areas where CandidateA and CandidateB exist.
bool isInside(const B2DPolygon &rCandidate, const B2DPoint &rPoint, bool bWithBorder)
B2DPolyPolygon correctOrientations(const B2DPolyPolygon &rCandidate)
B2DPolyPolygon solvePolygonOperationXor(const B2DPolyPolygon &rCandidateA, const B2DPolyPolygon &rCandidateB)
XOR: Return all areas where CandidateA or CandidateB exist, but not both.
B2DPolyPolygon stripNeutralPolygons(const B2DPolyPolygon &rCandidate)
Strip neutral polygons from PolyPolygon.
bool isRectangle(const B2DPolygon &rPoly)
Predicate whether a given polygon is a rectangle.
B2DPolygon addPointsAtCutsAndTouches(const B2DPolygon &rCandidate, size_t *pPointLimit)
B2DPolyPolygon solveCrossovers(const B2DPolyPolygon &rCandidate, size_t *pPointLimit)
Solve all crossovers (aka self-intersections) in a polyPolygon.
B2DPolyPolygon mergeToSinglePolyPolygon(const B2DPolyPolygonVector &rInput)
merge all single PolyPolygons to a single, OR-ed PolyPolygon
B2DPolygon simplifyCurveSegments(const B2DPolygon &rCandidate)
B2DPolyPolygon stripDispensablePolygons(const B2DPolyPolygon &rCandidate, bool bKeepAboveZero)
Remove unnecessary/non-displayed polygons.
B2DPolyPolygon solvePolygonOperationDiff(const B2DPolyPolygon &rCandidateA, const B2DPolyPolygon &rCandidateB)
DIFF: Return all areas where CandidateA is not covered by CandidateB (cut B out of A)
B2DRange getRange(const B2DPolygon &rCandidate)
Get the range of a polygon.
B2VectorOrientation
Descriptor for the mathematical orientations of two 2D Vectors.
Definition: b2enums.hxx:27
@ Positive
mathematically positive oriented
@ Neutral
mathematically neutral, thus parallel
@ Negative
mathematically negative oriented
::std::vector< B2DPolyPolygon > B2DPolyPolygonVector
constexpr OUStringLiteral first
#define SAL_MAX_UINT32
bool operator<(const wwFont &r1, const wwFont &r2)