Version 6.4.0.0.beta1, tag libreoffice-6.4.0.0.beta1
[LibreOffice.git] / basegfx / source / polygon / b2dpolygontools.cxx
blob4ac68958eaa2a3eeab791fb7a46c1a4e2d8277b3
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
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/.
9 * This file incorporates work covered by the following license notice:
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 .
20 #include <basegfx/numeric/ftools.hxx>
21 #include <basegfx/polygon/b2dpolygontools.hxx>
22 #include <osl/diagnose.h>
23 #include <rtl/math.hxx>
24 #include <sal/log.hxx>
25 #include <basegfx/polygon/b2dpolygon.hxx>
26 #include <basegfx/polygon/b2dpolypolygon.hxx>
27 #include <basegfx/range/b2drange.hxx>
28 #include <basegfx/curve/b2dcubicbezier.hxx>
29 #include <basegfx/point/b3dpoint.hxx>
30 #include <basegfx/matrix/b3dhommatrix.hxx>
31 #include <basegfx/matrix/b2dhommatrix.hxx>
32 #include <basegfx/curve/b2dbeziertools.hxx>
33 #include <basegfx/matrix/b2dhommatrixtools.hxx>
35 #include <numeric>
37 // #i37443#
38 #define ANGLE_BOUND_START_VALUE (2.25)
39 #define ANGLE_BOUND_MINIMUM_VALUE (0.1)
40 #define STEPSPERQUARTER (3)
42 namespace basegfx
44 namespace utils
46 void openWithGeometryChange(B2DPolygon& rCandidate)
48 if(rCandidate.isClosed())
50 if(rCandidate.count())
52 rCandidate.append(rCandidate.getB2DPoint(0));
54 if(rCandidate.areControlPointsUsed() && rCandidate.isPrevControlPointUsed(0))
56 rCandidate.setPrevControlPoint(rCandidate.count() - 1, rCandidate.getPrevControlPoint(0));
57 rCandidate.resetPrevControlPoint(0);
61 rCandidate.setClosed(false);
65 void closeWithGeometryChange(B2DPolygon& rCandidate)
67 if(!rCandidate.isClosed())
69 while(rCandidate.count() > 1 && rCandidate.getB2DPoint(0) == rCandidate.getB2DPoint(rCandidate.count() - 1))
71 if(rCandidate.areControlPointsUsed() && rCandidate.isPrevControlPointUsed(rCandidate.count() - 1))
73 rCandidate.setPrevControlPoint(0, rCandidate.getPrevControlPoint(rCandidate.count() - 1));
76 rCandidate.remove(rCandidate.count() - 1);
79 rCandidate.setClosed(true);
83 void checkClosed(B2DPolygon& rCandidate)
85 // #i80172# Removed unnecessary assertion
86 // OSL_ENSURE(!rCandidate.isClosed(), "checkClosed: already closed (!)");
88 if(rCandidate.count() > 1 && rCandidate.getB2DPoint(0) == rCandidate.getB2DPoint(rCandidate.count() - 1))
90 closeWithGeometryChange(rCandidate);
94 // Get successor and predecessor indices. Returning the same index means there
95 // is none. Same for successor.
96 sal_uInt32 getIndexOfPredecessor(sal_uInt32 nIndex, const B2DPolygon& rCandidate)
98 OSL_ENSURE(nIndex < rCandidate.count(), "getIndexOfPredecessor: Access to polygon out of range (!)");
100 if(nIndex)
102 return nIndex - 1;
104 else if(rCandidate.count())
106 return rCandidate.count() - 1;
108 else
110 return nIndex;
114 sal_uInt32 getIndexOfSuccessor(sal_uInt32 nIndex, const B2DPolygon& rCandidate)
116 OSL_ENSURE(nIndex < rCandidate.count(), "getIndexOfPredecessor: Access to polygon out of range (!)");
118 if(nIndex + 1 < rCandidate.count())
120 return nIndex + 1;
122 else if(nIndex + 1 == rCandidate.count())
124 return 0;
126 else
128 return nIndex;
132 B2VectorOrientation getOrientation(const B2DPolygon& rCandidate)
134 B2VectorOrientation eRetval(B2VectorOrientation::Neutral);
136 if(rCandidate.count() > 2 || rCandidate.areControlPointsUsed())
138 const double fSignedArea(getSignedArea(rCandidate));
140 if(fTools::equalZero(fSignedArea))
142 // B2VectorOrientation::Neutral, already set
144 if(fSignedArea > 0.0)
146 eRetval = B2VectorOrientation::Positive;
148 else if(fSignedArea < 0.0)
150 eRetval = B2VectorOrientation::Negative;
154 return eRetval;
157 B2VectorContinuity getContinuityInPoint(const B2DPolygon& rCandidate, sal_uInt32 nIndex)
159 return rCandidate.getContinuityInPoint(nIndex);
162 B2DPolygon adaptiveSubdivideByDistance(const B2DPolygon& rCandidate, double fDistanceBound)
164 if(rCandidate.areControlPointsUsed())
166 const sal_uInt32 nPointCount(rCandidate.count());
167 B2DPolygon aRetval;
169 if(nPointCount)
171 // prepare edge-oriented loop
172 const sal_uInt32 nEdgeCount(rCandidate.isClosed() ? nPointCount : nPointCount - 1);
173 B2DCubicBezier aBezier;
174 aBezier.setStartPoint(rCandidate.getB2DPoint(0));
176 // perf: try to avoid too many realloctions by guessing the result's pointcount
177 aRetval.reserve(nPointCount*4);
179 // add start point (always)
180 aRetval.append(aBezier.getStartPoint());
182 for(sal_uInt32 a(0); a < nEdgeCount; a++)
184 // get next and control points
185 const sal_uInt32 nNextIndex((a + 1) % nPointCount);
186 aBezier.setEndPoint(rCandidate.getB2DPoint(nNextIndex));
187 aBezier.setControlPointA(rCandidate.getNextControlPoint(a));
188 aBezier.setControlPointB(rCandidate.getPrevControlPoint(nNextIndex));
189 aBezier.testAndSolveTrivialBezier();
191 if(aBezier.isBezier())
193 // add curved edge and generate DistanceBound
194 double fBound(0.0);
196 if(fDistanceBound == 0.0)
198 // If not set, use B2DCubicBezier functionality to guess a rough value
199 const double fRoughLength((aBezier.getEdgeLength() + aBezier.getControlPolygonLength()) / 2.0);
201 // take 1/100th of the rough curve length
202 fBound = fRoughLength * 0.01;
204 else
206 // use given bound value
207 fBound = fDistanceBound;
210 // make sure bound value is not too small. The base units are 1/100th mm, thus
211 // just make sure it's not smaller then 1/100th of that
212 if(fBound < 0.01)
214 fBound = 0.01;
217 // call adaptive subdivide which adds edges to aRetval accordingly
218 aBezier.adaptiveSubdivideByDistance(aRetval, fBound);
220 else
222 // add non-curved edge
223 aRetval.append(aBezier.getEndPoint());
226 // prepare next step
227 aBezier.setStartPoint(aBezier.getEndPoint());
230 if(rCandidate.isClosed())
232 // set closed flag and correct last point (which is added double now).
233 closeWithGeometryChange(aRetval);
237 return aRetval;
239 else
241 return rCandidate;
245 B2DPolygon adaptiveSubdivideByAngle(const B2DPolygon& rCandidate, double fAngleBound)
247 if(rCandidate.areControlPointsUsed())
249 const sal_uInt32 nPointCount(rCandidate.count());
250 B2DPolygon aRetval;
252 if(nPointCount)
254 // prepare edge-oriented loop
255 const sal_uInt32 nEdgeCount(rCandidate.isClosed() ? nPointCount : nPointCount - 1);
256 B2DCubicBezier aBezier;
257 aBezier.setStartPoint(rCandidate.getB2DPoint(0));
259 // perf: try to avoid too many realloctions by guessing the result's pointcount
260 aRetval.reserve(nPointCount*4);
262 // add start point (always)
263 aRetval.append(aBezier.getStartPoint());
265 // #i37443# prepare convenient AngleBound if none was given
266 if(fAngleBound == 0.0)
268 fAngleBound = ANGLE_BOUND_START_VALUE;
270 else if(fTools::less(fAngleBound, ANGLE_BOUND_MINIMUM_VALUE))
272 fAngleBound = 0.1;
275 for(sal_uInt32 a(0); a < nEdgeCount; a++)
277 // get next and control points
278 const sal_uInt32 nNextIndex((a + 1) % nPointCount);
279 aBezier.setEndPoint(rCandidate.getB2DPoint(nNextIndex));
280 aBezier.setControlPointA(rCandidate.getNextControlPoint(a));
281 aBezier.setControlPointB(rCandidate.getPrevControlPoint(nNextIndex));
282 aBezier.testAndSolveTrivialBezier();
284 if(aBezier.isBezier())
286 // call adaptive subdivide
287 aBezier.adaptiveSubdivideByAngle(aRetval, fAngleBound);
289 else
291 // add non-curved edge
292 aRetval.append(aBezier.getEndPoint());
295 // prepare next step
296 aBezier.setStartPoint(aBezier.getEndPoint());
299 if(rCandidate.isClosed())
301 // set closed flag and correct last point (which is added double now).
302 closeWithGeometryChange(aRetval);
306 return aRetval;
308 else
310 return rCandidate;
314 bool isInside(const B2DPolygon& rCandidate, const B2DPoint& rPoint, bool bWithBorder)
316 const B2DPolygon aCandidate(rCandidate.areControlPointsUsed() ? rCandidate.getDefaultAdaptiveSubdivision() : rCandidate);
318 if(bWithBorder && isPointOnPolygon(aCandidate, rPoint))
320 return true;
322 else
324 bool bRetval(false);
325 const sal_uInt32 nPointCount(aCandidate.count());
327 if(nPointCount)
329 B2DPoint aCurrentPoint(aCandidate.getB2DPoint(nPointCount - 1));
331 for(sal_uInt32 a(0); a < nPointCount; a++)
333 const B2DPoint aPreviousPoint(aCurrentPoint);
334 aCurrentPoint = aCandidate.getB2DPoint(a);
336 // cross-over in Y?
337 const bool bCompYA(fTools::more(aPreviousPoint.getY(), rPoint.getY()));
338 const bool bCompYB(fTools::more(aCurrentPoint.getY(), rPoint.getY()));
340 if(bCompYA != bCompYB)
342 // cross-over in X?
343 const bool bCompXA(fTools::more(aPreviousPoint.getX(), rPoint.getX()));
344 const bool bCompXB(fTools::more(aCurrentPoint.getX(), rPoint.getX()));
346 if(bCompXA == bCompXB)
348 if(bCompXA)
350 bRetval = !bRetval;
353 else
355 const double fCompare(
356 aCurrentPoint.getX() - (aCurrentPoint.getY() - rPoint.getY()) *
357 (aPreviousPoint.getX() - aCurrentPoint.getX()) /
358 (aPreviousPoint.getY() - aCurrentPoint.getY()));
360 if(fTools::more(fCompare, rPoint.getX()))
362 bRetval = !bRetval;
369 return bRetval;
373 bool isInside(const B2DPolygon& rCandidate, const B2DPolygon& rPolygon, bool bWithBorder)
375 const B2DPolygon aCandidate(rCandidate.areControlPointsUsed() ? rCandidate.getDefaultAdaptiveSubdivision() : rCandidate);
376 const B2DPolygon aPolygon(rPolygon.areControlPointsUsed() ? rPolygon.getDefaultAdaptiveSubdivision() : rPolygon);
377 const sal_uInt32 nPointCount(aPolygon.count());
379 for(sal_uInt32 a(0); a < nPointCount; a++)
381 const B2DPoint aTestPoint(aPolygon.getB2DPoint(a));
383 if(!isInside(aCandidate, aTestPoint, bWithBorder))
385 return false;
389 return true;
392 B2DRange getRange(const B2DPolygon& rCandidate)
394 // changed to use internally buffered version at B2DPolygon
395 return rCandidate.getB2DRange();
398 double getSignedArea(const B2DPolygon& rCandidate)
400 const B2DPolygon aCandidate(rCandidate.areControlPointsUsed() ? rCandidate.getDefaultAdaptiveSubdivision() : rCandidate);
401 double fRetval(0.0);
402 const sal_uInt32 nPointCount(aCandidate.count());
404 if(nPointCount > 2)
406 for(sal_uInt32 a(0); a < nPointCount; a++)
408 const B2DPoint aPreviousPoint(aCandidate.getB2DPoint((!a) ? nPointCount - 1 : a - 1));
409 const B2DPoint aCurrentPoint(aCandidate.getB2DPoint(a));
411 fRetval += aPreviousPoint.getX() * aCurrentPoint.getY();
412 fRetval -= aPreviousPoint.getY() * aCurrentPoint.getX();
415 // correct to zero if small enough. Also test the quadratic
416 // of the result since the precision is near quadratic due to
417 // the algorithm
418 if(fTools::equalZero(fRetval) || fTools::equalZero(fRetval * fRetval))
420 fRetval = 0.0;
424 return fRetval;
427 double getArea(const B2DPolygon& rCandidate)
429 double fRetval(0.0);
431 if(rCandidate.count() > 2 || rCandidate.areControlPointsUsed())
433 fRetval = getSignedArea(rCandidate);
434 const double fZero(0.0);
436 if(fTools::less(fRetval, fZero))
438 fRetval = -fRetval;
442 return fRetval;
445 double getEdgeLength(const B2DPolygon& rCandidate, sal_uInt32 nIndex)
447 const sal_uInt32 nPointCount(rCandidate.count());
448 OSL_ENSURE(nIndex < nPointCount, "getEdgeLength: Access to polygon out of range (!)");
449 double fRetval(0.0);
451 if(nPointCount)
453 const sal_uInt32 nNextIndex((nIndex + 1) % nPointCount);
455 if(rCandidate.areControlPointsUsed())
457 B2DCubicBezier aEdge;
459 aEdge.setStartPoint(rCandidate.getB2DPoint(nIndex));
460 aEdge.setControlPointA(rCandidate.getNextControlPoint(nIndex));
461 aEdge.setControlPointB(rCandidate.getPrevControlPoint(nNextIndex));
462 aEdge.setEndPoint(rCandidate.getB2DPoint(nNextIndex));
464 fRetval = aEdge.getLength();
466 else
468 const B2DPoint aCurrent(rCandidate.getB2DPoint(nIndex));
469 const B2DPoint aNext(rCandidate.getB2DPoint(nNextIndex));
471 fRetval = B2DVector(aNext - aCurrent).getLength();
475 return fRetval;
478 double getLength(const B2DPolygon& rCandidate)
480 double fRetval(0.0);
481 const sal_uInt32 nPointCount(rCandidate.count());
483 if(nPointCount)
485 const sal_uInt32 nEdgeCount(rCandidate.isClosed() ? nPointCount : nPointCount - 1);
487 if(rCandidate.areControlPointsUsed())
489 B2DCubicBezier aEdge;
490 aEdge.setStartPoint(rCandidate.getB2DPoint(0));
492 for(sal_uInt32 a(0); a < nEdgeCount; a++)
494 const sal_uInt32 nNextIndex((a + 1) % nPointCount);
495 aEdge.setControlPointA(rCandidate.getNextControlPoint(a));
496 aEdge.setControlPointB(rCandidate.getPrevControlPoint(nNextIndex));
497 aEdge.setEndPoint(rCandidate.getB2DPoint(nNextIndex));
499 fRetval += aEdge.getLength();
500 aEdge.setStartPoint(aEdge.getEndPoint());
503 else
505 B2DPoint aCurrent(rCandidate.getB2DPoint(0));
507 for(sal_uInt32 a(0); a < nEdgeCount; a++)
509 const sal_uInt32 nNextIndex((a + 1) % nPointCount);
510 const B2DPoint aNext(rCandidate.getB2DPoint(nNextIndex));
512 fRetval += B2DVector(aNext - aCurrent).getLength();
513 aCurrent = aNext;
518 return fRetval;
521 B2DPoint getPositionAbsolute(const B2DPolygon& rCandidate, double fDistance, double fLength)
523 B2DPoint aRetval;
524 const sal_uInt32 nPointCount(rCandidate.count());
526 if( nPointCount == 1 )
528 // only one point (i.e. no edge) - simply take that point
529 aRetval = rCandidate.getB2DPoint(0);
531 else if(nPointCount > 1)
533 const sal_uInt32 nEdgeCount(rCandidate.isClosed() ? nPointCount : nPointCount - 1);
534 sal_uInt32 nIndex(0);
535 bool bIndexDone(false);
537 // get length if not given
538 if(fTools::equalZero(fLength))
540 fLength = getLength(rCandidate);
543 if(fTools::less(fDistance, 0.0))
545 // handle fDistance < 0.0
546 if(rCandidate.isClosed())
548 // if fDistance < 0.0 increment with multiple of fLength
549 sal_uInt32 nCount(sal_uInt32(-fDistance / fLength));
550 fDistance += double(nCount + 1) * fLength;
552 else
554 // crop to polygon start
555 fDistance = 0.0;
556 bIndexDone = true;
559 else if(fTools::moreOrEqual(fDistance, fLength))
561 // handle fDistance >= fLength
562 if(rCandidate.isClosed())
564 // if fDistance >= fLength decrement with multiple of fLength
565 sal_uInt32 nCount(sal_uInt32(fDistance / fLength));
566 fDistance -= static_cast<double>(nCount) * fLength;
568 else
570 // crop to polygon end
571 fDistance = 0.0;
572 nIndex = nEdgeCount;
573 bIndexDone = true;
577 // look for correct index. fDistance is now [0.0 .. fLength[
578 double fEdgeLength(getEdgeLength(rCandidate, nIndex));
580 while(!bIndexDone)
582 // edge found must be on the half-open range
583 // [0,fEdgeLength).
584 // Note that in theory, we cannot move beyond
585 // the last polygon point, since fDistance>=fLength
586 // is checked above. Unfortunately, with floating-
587 // point calculations, this case might happen.
588 // Handled by nIndex check below
589 if (nIndex+1 < nEdgeCount && fTools::moreOrEqual(fDistance, fEdgeLength))
591 // go to next edge
592 fDistance -= fEdgeLength;
593 fEdgeLength = getEdgeLength(rCandidate, ++nIndex);
595 else
597 // it's on this edge, stop
598 bIndexDone = true;
602 // get the point using nIndex
603 aRetval = rCandidate.getB2DPoint(nIndex);
605 // if fDistance != 0.0, move that length on the edge. The edge
606 // length is in fEdgeLength.
607 if(!fTools::equalZero(fDistance))
609 if(fTools::moreOrEqual(fDistance, fEdgeLength))
611 // end point of chosen edge
612 const sal_uInt32 nNextIndex((nIndex + 1) % nPointCount);
613 aRetval = rCandidate.getB2DPoint(nNextIndex);
615 else if(fTools::equalZero(fDistance))
617 // start point of chosen edge
619 else
621 // inside edge
622 const sal_uInt32 nNextIndex((nIndex + 1) % nPointCount);
623 const B2DPoint aNextPoint(rCandidate.getB2DPoint(nNextIndex));
624 bool bDone(false);
626 // add calculated average value to the return value
627 if(rCandidate.areControlPointsUsed())
629 // get as bezier segment
630 const B2DCubicBezier aBezierSegment(
631 aRetval, rCandidate.getNextControlPoint(nIndex),
632 rCandidate.getPrevControlPoint(nNextIndex), aNextPoint);
634 if(aBezierSegment.isBezier())
636 // use B2DCubicBezierHelper to bridge the non-linear gap between
637 // length and bezier distances
638 const B2DCubicBezierHelper aBezierSegmentHelper(aBezierSegment);
639 const double fBezierDistance(aBezierSegmentHelper.distanceToRelative(fDistance));
641 aRetval = aBezierSegment.interpolatePoint(fBezierDistance);
642 bDone = true;
646 if(!bDone)
648 const double fRelativeInEdge(fDistance / fEdgeLength);
649 aRetval = interpolate(aRetval, aNextPoint, fRelativeInEdge);
655 return aRetval;
658 B2DPoint getPositionRelative(const B2DPolygon& rCandidate, double fDistance, double fLength)
660 // get length if not given
661 if(fTools::equalZero(fLength))
663 fLength = getLength(rCandidate);
666 // multiply fDistance with real length to get absolute position and
667 // use getPositionAbsolute
668 return getPositionAbsolute(rCandidate, fDistance * fLength, fLength);
671 B2DPolygon getSnippetAbsolute(const B2DPolygon& rCandidate, double fFrom, double fTo, double fLength)
673 const sal_uInt32 nPointCount(rCandidate.count());
675 if(nPointCount)
677 // get length if not given
678 if(fTools::equalZero(fLength))
680 fLength = getLength(rCandidate);
683 // test and correct fFrom
684 if(fTools::less(fFrom, 0.0))
686 fFrom = 0.0;
689 // test and correct fTo
690 if(fTools::more(fTo, fLength))
692 fTo = fLength;
695 // test and correct relationship of fFrom, fTo
696 if(fTools::more(fFrom, fTo))
698 fFrom = fTo = (fFrom + fTo) / 2.0;
701 if(fTools::equalZero(fFrom) && fTools::equal(fTo, fLength))
703 // no change, result is the whole polygon
704 return rCandidate;
706 else
708 B2DPolygon aRetval;
709 const sal_uInt32 nEdgeCount(rCandidate.isClosed() ? nPointCount : nPointCount - 1);
710 double fPositionOfStart(0.0);
711 bool bStartDone(false);
712 bool bEndDone(false);
714 for(sal_uInt32 a(0); !(bStartDone && bEndDone) && a < nEdgeCount; a++)
716 const double fEdgeLength(getEdgeLength(rCandidate, a));
718 if(!bStartDone)
720 if(fTools::equalZero(fFrom))
722 aRetval.append(rCandidate.getB2DPoint(a));
724 if(rCandidate.areControlPointsUsed())
726 aRetval.setNextControlPoint(aRetval.count() - 1, rCandidate.getNextControlPoint(a));
729 bStartDone = true;
731 else if(fTools::moreOrEqual(fFrom, fPositionOfStart) && fTools::less(fFrom, fPositionOfStart + fEdgeLength))
733 // calculate and add start point
734 if(fTools::equalZero(fEdgeLength))
736 aRetval.append(rCandidate.getB2DPoint(a));
738 if(rCandidate.areControlPointsUsed())
740 aRetval.setNextControlPoint(aRetval.count() - 1, rCandidate.getNextControlPoint(a));
743 else
745 const sal_uInt32 nNextIndex((a + 1) % nPointCount);
746 const B2DPoint aStart(rCandidate.getB2DPoint(a));
747 const B2DPoint aEnd(rCandidate.getB2DPoint(nNextIndex));
748 bool bDone(false);
750 if(rCandidate.areControlPointsUsed())
752 const B2DCubicBezier aBezierSegment(
753 aStart, rCandidate.getNextControlPoint(a),
754 rCandidate.getPrevControlPoint(nNextIndex), aEnd);
756 if(aBezierSegment.isBezier())
758 // use B2DCubicBezierHelper to bridge the non-linear gap between
759 // length and bezier distances
760 const B2DCubicBezierHelper aBezierSegmentHelper(aBezierSegment);
761 const double fBezierDistance(aBezierSegmentHelper.distanceToRelative(fFrom - fPositionOfStart));
762 B2DCubicBezier aRight;
764 aBezierSegment.split(fBezierDistance, nullptr, &aRight);
765 aRetval.append(aRight.getStartPoint());
766 aRetval.setNextControlPoint(aRetval.count() - 1, aRight.getControlPointA());
767 bDone = true;
771 if(!bDone)
773 const double fRelValue((fFrom - fPositionOfStart) / fEdgeLength);
774 aRetval.append(interpolate(aStart, aEnd, fRelValue));
778 bStartDone = true;
780 // if same point, end is done, too.
781 if(rtl::math::approxEqual(fFrom, fTo))
783 bEndDone = true;
788 if(!bEndDone && fTools::moreOrEqual(fTo, fPositionOfStart) && fTools::less(fTo, fPositionOfStart + fEdgeLength))
790 // calculate and add end point
791 if(fTools::equalZero(fEdgeLength))
793 const sal_uInt32 nNextIndex((a + 1) % nPointCount);
794 aRetval.append(rCandidate.getB2DPoint(nNextIndex));
796 if(rCandidate.areControlPointsUsed())
798 aRetval.setPrevControlPoint(aRetval.count() - 1, rCandidate.getPrevControlPoint(nNextIndex));
801 else
803 const sal_uInt32 nNextIndex((a + 1) % nPointCount);
804 const B2DPoint aStart(rCandidate.getB2DPoint(a));
805 const B2DPoint aEnd(rCandidate.getB2DPoint(nNextIndex));
806 bool bDone(false);
808 if(rCandidate.areControlPointsUsed())
810 const B2DCubicBezier aBezierSegment(
811 aStart, rCandidate.getNextControlPoint(a),
812 rCandidate.getPrevControlPoint(nNextIndex), aEnd);
814 if(aBezierSegment.isBezier())
816 // use B2DCubicBezierHelper to bridge the non-linear gap between
817 // length and bezier distances
818 const B2DCubicBezierHelper aBezierSegmentHelper(aBezierSegment);
819 const double fBezierDistance(aBezierSegmentHelper.distanceToRelative(fTo - fPositionOfStart));
820 B2DCubicBezier aLeft;
822 aBezierSegment.split(fBezierDistance, &aLeft, nullptr);
823 aRetval.append(aLeft.getEndPoint());
824 aRetval.setPrevControlPoint(aRetval.count() - 1, aLeft.getControlPointB());
825 bDone = true;
829 if(!bDone)
831 const double fRelValue((fTo - fPositionOfStart) / fEdgeLength);
832 aRetval.append(interpolate(aStart, aEnd, fRelValue));
836 bEndDone = true;
839 if(!bEndDone)
841 if(bStartDone)
843 // add segments end point
844 const sal_uInt32 nNextIndex((a + 1) % nPointCount);
845 aRetval.append(rCandidate.getB2DPoint(nNextIndex));
847 if(rCandidate.areControlPointsUsed())
849 aRetval.setPrevControlPoint(aRetval.count() - 1, rCandidate.getPrevControlPoint(nNextIndex));
850 aRetval.setNextControlPoint(aRetval.count() - 1, rCandidate.getNextControlPoint(nNextIndex));
854 // increment fPositionOfStart
855 fPositionOfStart += fEdgeLength;
858 return aRetval;
861 else
863 return rCandidate;
867 CutFlagValue findCut(
868 const B2DPoint& rEdge1Start, const B2DVector& rEdge1Delta,
869 const B2DPoint& rEdge2Start, const B2DVector& rEdge2Delta,
870 CutFlagValue aCutFlags,
871 double* pCut1, double* pCut2)
873 CutFlagValue aRetval(CutFlagValue::NONE);
874 double fCut1(0.0);
875 double fCut2(0.0);
876 bool bFinished(!static_cast<bool>(aCutFlags & CutFlagValue::ALL));
878 // test for same points?
879 if(!bFinished
880 && (aCutFlags & (CutFlagValue::START1|CutFlagValue::END1))
881 && (aCutFlags & (CutFlagValue::START2|CutFlagValue::END2)))
883 // same startpoint?
884 if((aCutFlags & (CutFlagValue::START1|CutFlagValue::START2)) == (CutFlagValue::START1|CutFlagValue::START2))
886 if(rEdge1Start.equal(rEdge2Start))
888 bFinished = true;
889 aRetval = (CutFlagValue::START1|CutFlagValue::START2);
893 // same endpoint?
894 if(!bFinished && (aCutFlags & (CutFlagValue::END1|CutFlagValue::END2)) == (CutFlagValue::END1|CutFlagValue::END2))
896 const B2DPoint aEnd1(rEdge1Start + rEdge1Delta);
897 const B2DPoint aEnd2(rEdge2Start + rEdge2Delta);
899 if(aEnd1.equal(aEnd2))
901 bFinished = true;
902 aRetval = (CutFlagValue::END1|CutFlagValue::END2);
903 fCut1 = fCut2 = 1.0;
907 // startpoint1 == endpoint2?
908 if(!bFinished && (aCutFlags & (CutFlagValue::START1|CutFlagValue::END2)) == (CutFlagValue::START1|CutFlagValue::END2))
910 const B2DPoint aEnd2(rEdge2Start + rEdge2Delta);
912 if(rEdge1Start.equal(aEnd2))
914 bFinished = true;
915 aRetval = (CutFlagValue::START1|CutFlagValue::END2);
916 fCut1 = 0.0;
917 fCut2 = 1.0;
921 // startpoint2 == endpoint1?
922 if(!bFinished&& (aCutFlags & (CutFlagValue::START2|CutFlagValue::END1)) == (CutFlagValue::START2|CutFlagValue::END1))
924 const B2DPoint aEnd1(rEdge1Start + rEdge1Delta);
926 if(rEdge2Start.equal(aEnd1))
928 bFinished = true;
929 aRetval = (CutFlagValue::START2|CutFlagValue::END1);
930 fCut1 = 1.0;
931 fCut2 = 0.0;
936 if(!bFinished && (aCutFlags & CutFlagValue::LINE))
938 if(aCutFlags & CutFlagValue::START1)
940 // start1 on line 2 ?
941 if(isPointOnEdge(rEdge1Start, rEdge2Start, rEdge2Delta, &fCut2))
943 bFinished = true;
944 aRetval = (CutFlagValue::LINE|CutFlagValue::START1);
948 if(!bFinished && (aCutFlags & CutFlagValue::START2))
950 // start2 on line 1 ?
951 if(isPointOnEdge(rEdge2Start, rEdge1Start, rEdge1Delta, &fCut1))
953 bFinished = true;
954 aRetval = (CutFlagValue::LINE|CutFlagValue::START2);
958 if(!bFinished && (aCutFlags & CutFlagValue::END1))
960 // end1 on line 2 ?
961 const B2DPoint aEnd1(rEdge1Start + rEdge1Delta);
963 if(isPointOnEdge(aEnd1, rEdge2Start, rEdge2Delta, &fCut2))
965 bFinished = true;
966 aRetval = (CutFlagValue::LINE|CutFlagValue::END1);
970 if(!bFinished && (aCutFlags & CutFlagValue::END2))
972 // end2 on line 1 ?
973 const B2DPoint aEnd2(rEdge2Start + rEdge2Delta);
975 if(isPointOnEdge(aEnd2, rEdge1Start, rEdge1Delta, &fCut1))
977 bFinished = true;
978 aRetval = (CutFlagValue::LINE|CutFlagValue::END2);
982 if(!bFinished)
984 // cut in line1, line2 ?
985 fCut1 = (rEdge1Delta.getX() * rEdge2Delta.getY()) - (rEdge1Delta.getY() * rEdge2Delta.getX());
987 if(!fTools::equalZero(fCut1))
989 fCut1 = (rEdge2Delta.getY() * (rEdge2Start.getX() - rEdge1Start.getX())
990 + rEdge2Delta.getX() * (rEdge1Start.getY() - rEdge2Start.getY())) / fCut1;
992 const double fZero(0.0);
993 const double fOne(1.0);
995 // inside parameter range edge1 AND fCut2 is calculable
996 if(fTools::more(fCut1, fZero) && fTools::less(fCut1, fOne)
997 && (!fTools::equalZero(rEdge2Delta.getX()) || !fTools::equalZero(rEdge2Delta.getY())))
999 // take the more precise calculation of the two possible
1000 if(fabs(rEdge2Delta.getX()) > fabs(rEdge2Delta.getY()))
1002 fCut2 = (rEdge1Start.getX() + fCut1
1003 * rEdge1Delta.getX() - rEdge2Start.getX()) / rEdge2Delta.getX();
1005 else
1007 fCut2 = (rEdge1Start.getY() + fCut1
1008 * rEdge1Delta.getY() - rEdge2Start.getY()) / rEdge2Delta.getY();
1011 // inside parameter range edge2, too
1012 if(fTools::more(fCut2, fZero) && fTools::less(fCut2, fOne))
1014 aRetval = CutFlagValue::LINE;
1021 // copy values if wanted
1022 if(pCut1)
1024 *pCut1 = fCut1;
1027 if(pCut2)
1029 *pCut2 = fCut2;
1032 return aRetval;
1035 bool isPointOnEdge(
1036 const B2DPoint& rPoint,
1037 const B2DPoint& rEdgeStart,
1038 const B2DVector& rEdgeDelta,
1039 double* pCut)
1041 bool bDeltaXIsZero(fTools::equalZero(rEdgeDelta.getX()));
1042 bool bDeltaYIsZero(fTools::equalZero(rEdgeDelta.getY()));
1043 const double fZero(0.0);
1044 const double fOne(1.0);
1046 if(bDeltaXIsZero && bDeltaYIsZero)
1048 // no line, just a point
1049 return false;
1051 else if(bDeltaXIsZero)
1053 // vertical line
1054 if(fTools::equal(rPoint.getX(), rEdgeStart.getX()))
1056 double fValue = (rPoint.getY() - rEdgeStart.getY()) / rEdgeDelta.getY();
1058 if(fTools::more(fValue, fZero) && fTools::less(fValue, fOne))
1060 if(pCut)
1062 *pCut = fValue;
1065 return true;
1069 else if(bDeltaYIsZero)
1071 // horizontal line
1072 if(fTools::equal(rPoint.getY(), rEdgeStart.getY()))
1074 double fValue = (rPoint.getX() - rEdgeStart.getX()) / rEdgeDelta.getX();
1076 if(fTools::more(fValue, fZero) && fTools::less(fValue, fOne))
1078 if(pCut)
1080 *pCut = fValue;
1083 return true;
1087 else
1089 // any angle line
1090 double fTOne = (rPoint.getX() - rEdgeStart.getX()) / rEdgeDelta.getX();
1091 double fTTwo = (rPoint.getY() - rEdgeStart.getY()) / rEdgeDelta.getY();
1093 if(fTools::equal(fTOne, fTTwo))
1095 // same parameter representation, point is on line. Take
1096 // middle value for better results
1097 double fValue = (fTOne + fTTwo) / 2.0;
1099 if(fTools::more(fValue, fZero) && fTools::less(fValue, fOne))
1101 // point is inside line bounds, too
1102 if(pCut)
1104 *pCut = fValue;
1107 return true;
1112 return false;
1115 void applyLineDashing(const B2DPolygon& rCandidate, const std::vector<double>& rDotDashArray, B2DPolyPolygon* pLineTarget, B2DPolyPolygon* pGapTarget, double fDotDashLength)
1117 const sal_uInt32 nPointCount(rCandidate.count());
1118 const sal_uInt32 nDotDashCount(rDotDashArray.size());
1120 if(fTools::lessOrEqual(fDotDashLength, 0.0))
1122 fDotDashLength = std::accumulate(rDotDashArray.begin(), rDotDashArray.end(), 0.0);
1125 if(fTools::more(fDotDashLength, 0.0) && (pLineTarget || pGapTarget) && nPointCount)
1127 // clear targets
1128 if(pLineTarget)
1130 pLineTarget->clear();
1133 if(pGapTarget)
1135 pGapTarget->clear();
1138 // prepare current edge's start
1139 B2DCubicBezier aCurrentEdge;
1140 const sal_uInt32 nEdgeCount(rCandidate.isClosed() ? nPointCount : nPointCount - 1);
1141 aCurrentEdge.setStartPoint(rCandidate.getB2DPoint(0));
1143 // prepare DotDashArray iteration and the line/gap switching bool
1144 sal_uInt32 nDotDashIndex(0);
1145 bool bIsLine(true);
1146 double fDotDashMovingLength(rDotDashArray[0]);
1147 B2DPolygon aSnippet;
1149 // iterate over all edges
1150 for(sal_uInt32 a(0); a < nEdgeCount; a++)
1152 // update current edge (fill in C1, C2 and end point)
1153 double fLastDotDashMovingLength(0.0);
1154 const sal_uInt32 nNextIndex((a + 1) % nPointCount);
1155 aCurrentEdge.setControlPointA(rCandidate.getNextControlPoint(a));
1156 aCurrentEdge.setControlPointB(rCandidate.getPrevControlPoint(nNextIndex));
1157 aCurrentEdge.setEndPoint(rCandidate.getB2DPoint(nNextIndex));
1159 // check if we have a trivial bezier segment -> possible fallback to edge
1160 aCurrentEdge.testAndSolveTrivialBezier();
1162 if(aCurrentEdge.isBezier())
1164 // bezier segment
1165 const B2DCubicBezierHelper aCubicBezierHelper(aCurrentEdge);
1166 const double fEdgeLength(aCubicBezierHelper.getLength());
1168 if(!fTools::equalZero(fEdgeLength))
1170 while(fTools::less(fDotDashMovingLength, fEdgeLength))
1172 // new split is inside edge, create and append snippet [fLastDotDashMovingLength, fDotDashMovingLength]
1173 const bool bHandleLine(bIsLine && pLineTarget);
1174 const bool bHandleGap(!bIsLine && pGapTarget);
1176 if(bHandleLine || bHandleGap)
1178 const double fBezierSplitStart(aCubicBezierHelper.distanceToRelative(fLastDotDashMovingLength));
1179 const double fBezierSplitEnd(aCubicBezierHelper.distanceToRelative(fDotDashMovingLength));
1180 B2DCubicBezier aBezierSnippet(aCurrentEdge.snippet(fBezierSplitStart, fBezierSplitEnd));
1182 if(!aSnippet.count())
1184 aSnippet.append(aBezierSnippet.getStartPoint());
1187 aSnippet.appendBezierSegment(aBezierSnippet.getControlPointA(), aBezierSnippet.getControlPointB(), aBezierSnippet.getEndPoint());
1189 if(bHandleLine)
1191 pLineTarget->append(aSnippet);
1193 else
1195 pGapTarget->append(aSnippet);
1198 aSnippet.clear();
1201 // prepare next DotDashArray step and flip line/gap flag
1202 fLastDotDashMovingLength = fDotDashMovingLength;
1203 fDotDashMovingLength += rDotDashArray[(++nDotDashIndex) % nDotDashCount];
1204 bIsLine = !bIsLine;
1207 // append closing snippet [fLastDotDashMovingLength, fEdgeLength]
1208 const bool bHandleLine(bIsLine && pLineTarget);
1209 const bool bHandleGap(!bIsLine && pGapTarget);
1211 if(bHandleLine || bHandleGap)
1213 B2DCubicBezier aRight;
1214 const double fBezierSplit(aCubicBezierHelper.distanceToRelative(fLastDotDashMovingLength));
1216 aCurrentEdge.split(fBezierSplit, nullptr, &aRight);
1218 if(!aSnippet.count())
1220 aSnippet.append(aRight.getStartPoint());
1223 aSnippet.appendBezierSegment(aRight.getControlPointA(), aRight.getControlPointB(), aRight.getEndPoint());
1226 // prepare move to next edge
1227 fDotDashMovingLength -= fEdgeLength;
1230 else
1232 // simple edge
1233 const double fEdgeLength(aCurrentEdge.getEdgeLength());
1235 if(!fTools::equalZero(fEdgeLength))
1237 while(fTools::less(fDotDashMovingLength, fEdgeLength))
1239 // new split is inside edge, create and append snippet [fLastDotDashMovingLength, fDotDashMovingLength]
1240 const bool bHandleLine(bIsLine && pLineTarget);
1241 const bool bHandleGap(!bIsLine && pGapTarget);
1243 if(bHandleLine || bHandleGap)
1245 if(!aSnippet.count())
1247 aSnippet.append(interpolate(aCurrentEdge.getStartPoint(), aCurrentEdge.getEndPoint(), fLastDotDashMovingLength / fEdgeLength));
1250 aSnippet.append(interpolate(aCurrentEdge.getStartPoint(), aCurrentEdge.getEndPoint(), fDotDashMovingLength / fEdgeLength));
1252 if(bHandleLine)
1254 pLineTarget->append(aSnippet);
1256 else
1258 pGapTarget->append(aSnippet);
1261 aSnippet.clear();
1264 // prepare next DotDashArray step and flip line/gap flag
1265 fLastDotDashMovingLength = fDotDashMovingLength;
1266 fDotDashMovingLength += rDotDashArray[(++nDotDashIndex) % nDotDashCount];
1267 bIsLine = !bIsLine;
1270 // append snippet [fLastDotDashMovingLength, fEdgeLength]
1271 const bool bHandleLine(bIsLine && pLineTarget);
1272 const bool bHandleGap(!bIsLine && pGapTarget);
1274 if(bHandleLine || bHandleGap)
1276 if(!aSnippet.count())
1278 aSnippet.append(interpolate(aCurrentEdge.getStartPoint(), aCurrentEdge.getEndPoint(), fLastDotDashMovingLength / fEdgeLength));
1281 aSnippet.append(aCurrentEdge.getEndPoint());
1284 // prepare move to next edge
1285 fDotDashMovingLength -= fEdgeLength;
1289 // prepare next edge step (end point gets new start point)
1290 aCurrentEdge.setStartPoint(aCurrentEdge.getEndPoint());
1293 // append last intermediate results (if exists)
1294 if(aSnippet.count())
1296 if(bIsLine && pLineTarget)
1298 pLineTarget->append(aSnippet);
1300 else if(!bIsLine && pGapTarget)
1302 pGapTarget->append(aSnippet);
1306 // check if start and end polygon may be merged
1307 if(pLineTarget)
1309 const sal_uInt32 nCount(pLineTarget->count());
1311 if(nCount > 1)
1313 // these polygons were created above, there exists none with less than two points,
1314 // thus direct point access below is allowed
1315 const B2DPolygon aFirst(pLineTarget->getB2DPolygon(0));
1316 B2DPolygon aLast(pLineTarget->getB2DPolygon(nCount - 1));
1318 if(aFirst.getB2DPoint(0).equal(aLast.getB2DPoint(aLast.count() - 1)))
1320 // start of first and end of last are the same -> merge them
1321 aLast.append(aFirst);
1322 aLast.removeDoublePoints();
1323 pLineTarget->setB2DPolygon(0, aLast);
1324 pLineTarget->remove(nCount - 1);
1329 if(pGapTarget)
1331 const sal_uInt32 nCount(pGapTarget->count());
1333 if(nCount > 1)
1335 // these polygons were created above, there exists none with less than two points,
1336 // thus direct point access below is allowed
1337 const B2DPolygon aFirst(pGapTarget->getB2DPolygon(0));
1338 B2DPolygon aLast(pGapTarget->getB2DPolygon(nCount - 1));
1340 if(aFirst.getB2DPoint(0).equal(aLast.getB2DPoint(aLast.count() - 1)))
1342 // start of first and end of last are the same -> merge them
1343 aLast.append(aFirst);
1344 aLast.removeDoublePoints();
1345 pGapTarget->setB2DPolygon(0, aLast);
1346 pGapTarget->remove(nCount - 1);
1351 else
1353 // parameters make no sense, just add source to targets
1354 if(pLineTarget)
1356 pLineTarget->append(rCandidate);
1359 if(pGapTarget)
1361 pGapTarget->append(rCandidate);
1366 // test if point is inside epsilon-range around an edge defined
1367 // by the two given points. Can be used for HitTesting. The epsilon-range
1368 // is defined to be the rectangle centered to the given edge, using height
1369 // 2 x fDistance, and the circle around both points with radius fDistance.
1370 bool isInEpsilonRange(const B2DPoint& rEdgeStart, const B2DPoint& rEdgeEnd, const B2DPoint& rTestPosition, double fDistance)
1372 // build edge vector
1373 const B2DVector aEdge(rEdgeEnd - rEdgeStart);
1374 bool bDoDistanceTestStart(false);
1375 bool bDoDistanceTestEnd(false);
1377 if(aEdge.equalZero())
1379 // no edge, just a point. Do one of the distance tests.
1380 bDoDistanceTestStart = true;
1382 else
1384 // edge has a length. Create perpendicular vector.
1385 const B2DVector aPerpend(getPerpendicular(aEdge));
1386 double fCut(
1387 (aPerpend.getY() * (rTestPosition.getX() - rEdgeStart.getX())
1388 + aPerpend.getX() * (rEdgeStart.getY() - rTestPosition.getY())) /
1389 (aEdge.getX() * aEdge.getX() + aEdge.getY() * aEdge.getY()));
1390 const double fZero(0.0);
1391 const double fOne(1.0);
1393 if(fTools::less(fCut, fZero))
1395 // left of rEdgeStart
1396 bDoDistanceTestStart = true;
1398 else if(fTools::more(fCut, fOne))
1400 // right of rEdgeEnd
1401 bDoDistanceTestEnd = true;
1403 else
1405 // inside line [0.0 .. 1.0]
1406 const B2DPoint aCutPoint(interpolate(rEdgeStart, rEdgeEnd, fCut));
1407 const B2DVector aDelta(rTestPosition - aCutPoint);
1408 const double fDistanceSquare(aDelta.scalar(aDelta));
1410 return fDistanceSquare <= fDistance * fDistance;
1414 if(bDoDistanceTestStart)
1416 const B2DVector aDelta(rTestPosition - rEdgeStart);
1417 const double fDistanceSquare(aDelta.scalar(aDelta));
1419 if(fDistanceSquare <= fDistance * fDistance)
1421 return true;
1424 else if(bDoDistanceTestEnd)
1426 const B2DVector aDelta(rTestPosition - rEdgeEnd);
1427 const double fDistanceSquare(aDelta.scalar(aDelta));
1429 if(fDistanceSquare <= fDistance * fDistance)
1431 return true;
1435 return false;
1438 // test if point is inside epsilon-range around the given Polygon. Can be used
1439 // for HitTesting. The epsilon-range is defined to be the tube around the polygon
1440 // with distance fDistance and rounded edges (start and end point).
1441 bool isInEpsilonRange(const B2DPolygon& rCandidate, const B2DPoint& rTestPosition, double fDistance)
1443 // force to non-bezier polygon
1444 const B2DPolygon& aCandidate(rCandidate.getDefaultAdaptiveSubdivision());
1445 const sal_uInt32 nPointCount(aCandidate.count());
1447 if(nPointCount)
1449 const sal_uInt32 nEdgeCount(aCandidate.isClosed() ? nPointCount : nPointCount - 1);
1450 B2DPoint aCurrent(aCandidate.getB2DPoint(0));
1452 if(nEdgeCount)
1454 // edges
1455 for(sal_uInt32 a(0); a < nEdgeCount; a++)
1457 const sal_uInt32 nNextIndex((a + 1) % nPointCount);
1458 const B2DPoint aNext(aCandidate.getB2DPoint(nNextIndex));
1460 if(isInEpsilonRange(aCurrent, aNext, rTestPosition, fDistance))
1462 return true;
1465 // prepare next step
1466 aCurrent = aNext;
1469 else
1471 // no edges, but points -> not closed. Check single point. Just
1472 // use isInEpsilonRange with twice the same point, it handles those well
1473 if(isInEpsilonRange(aCurrent, aCurrent, rTestPosition, fDistance))
1475 return true;
1480 return false;
1483 // Calculates distance of curve point to its control point for a Bézier curve, that
1484 // approximates a unit circle arc. fAngle is the center angle of the circle arc. The
1485 // constrain 0<=fAngle<=pi/2 must not be violated to give a useful accuracy. For details
1486 // and alternatives read document "ApproxCircleInfo.odt", attachment of bug tdf#121425.
1487 static double impDistanceBezierPointToControl(double fAngle)
1489 SAL_WARN_IF(fAngle < 0 || fAngle > F_PI2,"basegfx","angle not suitable for approximate circle");
1490 if (0 <= fAngle && fAngle <= F_PI2)
1492 return 4.0/3.0 * ( tan(fAngle/4.0));
1494 else
1495 return 0;
1498 B2DPolygon createPolygonFromRect( const B2DRectangle& rRect, double fRadiusX, double fRadiusY )
1500 const double fZero(0.0);
1501 const double fOne(1.0);
1503 fRadiusX = std::clamp(fRadiusX, 0.0, 1.0);
1504 fRadiusY = std::clamp(fRadiusY, 0.0, 1.0);
1506 if(rtl::math::approxEqual(fZero, fRadiusX) || rtl::math::approxEqual(fZero, fRadiusY))
1508 // at least in one direction no radius, use rectangle.
1509 // Do not use createPolygonFromRect() here since original
1510 // creator (historical reasons) still creates a start point at the
1511 // bottom center, so do the same here to get the same line patterns.
1512 // Due to this the order of points is different, too.
1513 const B2DPoint aBottomCenter(rRect.getCenter().getX(), rRect.getMaxY());
1514 B2DPolygon aPolygon {
1515 aBottomCenter,
1516 { rRect.getMinX(), rRect.getMaxY() },
1517 { rRect.getMinX(), rRect.getMinY() },
1518 { rRect.getMaxX(), rRect.getMinY() },
1519 { rRect.getMaxX(), rRect.getMaxY() }
1522 // close
1523 aPolygon.setClosed( true );
1525 return aPolygon;
1527 else if(rtl::math::approxEqual(fOne, fRadiusX) && rtl::math::approxEqual(fOne, fRadiusY))
1529 // in both directions full radius, use ellipse
1530 const B2DPoint aCenter(rRect.getCenter());
1531 const double fRectRadiusX(rRect.getWidth() / 2.0);
1532 const double fRectRadiusY(rRect.getHeight() / 2.0);
1534 return createPolygonFromEllipse( aCenter, fRectRadiusX, fRectRadiusY );
1536 else
1538 B2DPolygon aRetval;
1539 const double fBowX((rRect.getWidth() / 2.0) * fRadiusX);
1540 const double fBowY((rRect.getHeight() / 2.0) * fRadiusY);
1541 const double fKappa(impDistanceBezierPointToControl(F_PI2));
1543 // create start point at bottom center
1544 if(!rtl::math::approxEqual(fOne, fRadiusX))
1546 const B2DPoint aBottomCenter(rRect.getCenter().getX(), rRect.getMaxY());
1547 aRetval.append(aBottomCenter);
1550 // create first bow
1552 const B2DPoint aBottomRight(rRect.getMaxX(), rRect.getMaxY());
1553 const B2DPoint aStart(aBottomRight + B2DPoint(-fBowX, 0.0));
1554 const B2DPoint aStop(aBottomRight + B2DPoint(0.0, -fBowY));
1555 aRetval.append(aStart);
1556 aRetval.appendBezierSegment(interpolate(aStart, aBottomRight, fKappa), interpolate(aStop, aBottomRight, fKappa), aStop);
1559 // create second bow
1561 const B2DPoint aTopRight(rRect.getMaxX(), rRect.getMinY());
1562 const B2DPoint aStart(aTopRight + B2DPoint(0.0, fBowY));
1563 const B2DPoint aStop(aTopRight + B2DPoint(-fBowX, 0.0));
1564 aRetval.append(aStart);
1565 aRetval.appendBezierSegment(interpolate(aStart, aTopRight, fKappa), interpolate(aStop, aTopRight, fKappa), aStop);
1568 // create third bow
1570 const B2DPoint aTopLeft(rRect.getMinX(), rRect.getMinY());
1571 const B2DPoint aStart(aTopLeft + B2DPoint(fBowX, 0.0));
1572 const B2DPoint aStop(aTopLeft + B2DPoint(0.0, fBowY));
1573 aRetval.append(aStart);
1574 aRetval.appendBezierSegment(interpolate(aStart, aTopLeft, fKappa), interpolate(aStop, aTopLeft, fKappa), aStop);
1577 // create forth bow
1579 const B2DPoint aBottomLeft(rRect.getMinX(), rRect.getMaxY());
1580 const B2DPoint aStart(aBottomLeft + B2DPoint(0.0, -fBowY));
1581 const B2DPoint aStop(aBottomLeft + B2DPoint(fBowX, 0.0));
1582 aRetval.append(aStart);
1583 aRetval.appendBezierSegment(interpolate(aStart, aBottomLeft, fKappa), interpolate(aStop, aBottomLeft, fKappa), aStop);
1586 // close
1587 aRetval.setClosed( true );
1589 // remove double created points if there are extreme radii involved
1590 if(rtl::math::approxEqual(fOne, fRadiusX) || rtl::math::approxEqual(fOne, fRadiusY))
1592 aRetval.removeDoublePoints();
1595 return aRetval;
1599 B2DPolygon createPolygonFromRect( const B2DRectangle& rRect )
1601 B2DPolygon aPolygon {
1602 { rRect.getMinX(), rRect.getMinY() },
1603 { rRect.getMaxX(), rRect.getMinY() },
1604 { rRect.getMaxX(), rRect.getMaxY() },
1605 { rRect.getMinX(), rRect.getMaxY() }
1608 // close
1609 aPolygon.setClosed( true );
1611 return aPolygon;
1614 B2DPolygon const & createUnitPolygon()
1616 static auto const singleton = [] {
1617 B2DPolygon aPolygon {
1618 { 0.0, 0.0 },
1619 { 1.0, 0.0 },
1620 { 1.0, 1.0 },
1621 { 0.0, 1.0 }
1624 // close
1625 aPolygon.setClosed( true );
1627 return aPolygon;
1628 }();
1629 return singleton;
1632 B2DPolygon createPolygonFromCircle( const B2DPoint& rCenter, double fRadius )
1634 return createPolygonFromEllipse( rCenter, fRadius, fRadius );
1637 static B2DPolygon impCreateUnitCircle(sal_uInt32 nStartQuadrant)
1639 B2DPolygon aUnitCircle;
1640 const double fSegmentKappa = impDistanceBezierPointToControl(F_PI2 / STEPSPERQUARTER);
1641 const B2DHomMatrix aRotateMatrix(createRotateB2DHomMatrix(F_PI2 / STEPSPERQUARTER));
1643 B2DPoint aPoint(1.0, 0.0);
1644 B2DPoint aForward(1.0, fSegmentKappa);
1645 B2DPoint aBackward(1.0, -fSegmentKappa);
1647 if(nStartQuadrant != 0)
1649 const B2DHomMatrix aQuadrantMatrix(createRotateB2DHomMatrix(F_PI2 * (nStartQuadrant % 4)));
1650 aPoint *= aQuadrantMatrix;
1651 aBackward *= aQuadrantMatrix;
1652 aForward *= aQuadrantMatrix;
1655 aUnitCircle.append(aPoint);
1657 for(sal_uInt32 a(0); a < STEPSPERQUARTER * 4; a++)
1659 aPoint *= aRotateMatrix;
1660 aBackward *= aRotateMatrix;
1661 aUnitCircle.appendBezierSegment(aForward, aBackward, aPoint);
1662 aForward *= aRotateMatrix;
1665 aUnitCircle.setClosed(true);
1666 aUnitCircle.removeDoublePoints();
1668 return aUnitCircle;
1671 B2DPolygon const & createHalfUnitCircle()
1673 static auto const singleton = [] {
1674 B2DPolygon aUnitHalfCircle;
1675 const double fSegmentKappa(impDistanceBezierPointToControl(F_PI2 / STEPSPERQUARTER));
1676 const B2DHomMatrix aRotateMatrix(createRotateB2DHomMatrix(F_PI2 / STEPSPERQUARTER));
1677 B2DPoint aPoint(1.0, 0.0);
1678 B2DPoint aForward(1.0, fSegmentKappa);
1679 B2DPoint aBackward(1.0, -fSegmentKappa);
1681 aUnitHalfCircle.append(aPoint);
1683 for(sal_uInt32 a(0); a < STEPSPERQUARTER * 2; a++)
1685 aPoint *= aRotateMatrix;
1686 aBackward *= aRotateMatrix;
1687 aUnitHalfCircle.appendBezierSegment(aForward, aBackward, aPoint);
1688 aForward *= aRotateMatrix;
1690 return aUnitHalfCircle;
1691 }();
1692 return singleton;
1695 B2DPolygon const & createPolygonFromUnitCircle(sal_uInt32 nStartQuadrant)
1697 switch(nStartQuadrant % 4)
1699 case 1 :
1701 static auto const singleton = impCreateUnitCircle(1);
1702 return singleton;
1705 case 2 :
1707 static auto const singleton = impCreateUnitCircle(2);
1708 return singleton;
1711 case 3 :
1713 static auto const singleton = impCreateUnitCircle(3);
1714 return singleton;
1717 default : // case 0 :
1719 static auto const singleton = impCreateUnitCircle(0);
1720 return singleton;
1725 B2DPolygon createPolygonFromEllipse( const B2DPoint& rCenter, double fRadiusX, double fRadiusY, sal_uInt32 nStartQuadrant)
1727 B2DPolygon aRetval(createPolygonFromUnitCircle(nStartQuadrant));
1728 const B2DHomMatrix aMatrix(createScaleTranslateB2DHomMatrix(fRadiusX, fRadiusY, rCenter.getX(), rCenter.getY()));
1730 aRetval.transform(aMatrix);
1732 return aRetval;
1735 B2DPolygon createPolygonFromUnitEllipseSegment( double fStart, double fEnd )
1737 B2DPolygon aRetval;
1739 // truncate fStart, fEnd to a range of [0.0 .. F_2PI[ where F_2PI
1740 // falls back to 0.0 to ensure a unique definition
1741 if(fTools::less(fStart, 0.0))
1743 fStart = 0.0;
1746 if(fTools::moreOrEqual(fStart, F_2PI))
1748 fStart = 0.0;
1751 if(fTools::less(fEnd, 0.0))
1753 fEnd = 0.0;
1756 if(fTools::moreOrEqual(fEnd, F_2PI))
1758 fEnd = 0.0;
1761 if(fTools::equal(fStart, fEnd))
1763 // same start and end angle, add single point
1764 aRetval.append(B2DPoint(cos(fStart), sin(fStart)));
1766 else
1768 const sal_uInt32 nSegments(STEPSPERQUARTER * 4);
1769 const double fAnglePerSegment(F_PI2 / STEPSPERQUARTER);
1770 const sal_uInt32 nStartSegment(sal_uInt32(fStart / fAnglePerSegment) % nSegments);
1771 const sal_uInt32 nEndSegment(sal_uInt32(fEnd / fAnglePerSegment) % nSegments);
1772 const double fSegmentKappa(impDistanceBezierPointToControl(fAnglePerSegment));
1774 B2DPoint aSegStart(cos(fStart), sin(fStart));
1775 aRetval.append(aSegStart);
1777 if(nStartSegment == nEndSegment && fTools::more(fEnd, fStart))
1779 // start and end in one sector and in the right order, create in one segment
1780 const B2DPoint aSegEnd(cos(fEnd), sin(fEnd));
1781 const double fFactor(impDistanceBezierPointToControl(fEnd - fStart));
1783 aRetval.appendBezierSegment(
1784 aSegStart + (B2DPoint(-aSegStart.getY(), aSegStart.getX()) * fFactor),
1785 aSegEnd - (B2DPoint(-aSegEnd.getY(), aSegEnd.getX()) * fFactor),
1786 aSegEnd);
1788 else
1790 double fSegEndRad((nStartSegment + 1) * fAnglePerSegment);
1791 double fFactor(impDistanceBezierPointToControl(fSegEndRad - fStart));
1792 B2DPoint aSegEnd(cos(fSegEndRad), sin(fSegEndRad));
1794 aRetval.appendBezierSegment(
1795 aSegStart + (B2DPoint(-aSegStart.getY(), aSegStart.getX()) * fFactor),
1796 aSegEnd - (B2DPoint(-aSegEnd.getY(), aSegEnd.getX()) * fFactor),
1797 aSegEnd);
1799 sal_uInt32 nSegment((nStartSegment + 1) % nSegments);
1800 aSegStart = aSegEnd;
1802 while(nSegment != nEndSegment)
1804 // No end in this sector, add full sector.
1805 fSegEndRad = (nSegment + 1) * fAnglePerSegment;
1806 aSegEnd = B2DPoint(cos(fSegEndRad), sin(fSegEndRad));
1808 aRetval.appendBezierSegment(
1809 aSegStart + (B2DPoint(-aSegStart.getY(), aSegStart.getX()) * fSegmentKappa),
1810 aSegEnd - (B2DPoint(-aSegEnd.getY(), aSegEnd.getX()) * fSegmentKappa),
1811 aSegEnd);
1813 nSegment = (nSegment + 1) % nSegments;
1814 aSegStart = aSegEnd;
1817 // End in this sector
1818 const double fSegStartRad(nSegment * fAnglePerSegment);
1819 fFactor= impDistanceBezierPointToControl(fEnd - fSegStartRad);
1820 aSegEnd = B2DPoint(cos(fEnd), sin(fEnd));
1822 aRetval.appendBezierSegment(
1823 aSegStart + (B2DPoint(-aSegStart.getY(), aSegStart.getX()) * fFactor),
1824 aSegEnd - (B2DPoint(-aSegEnd.getY(), aSegEnd.getX()) * fFactor),
1825 aSegEnd);
1829 // remove double points between segments created by segmented creation
1830 aRetval.removeDoublePoints();
1832 return aRetval;
1835 B2DPolygon createPolygonFromEllipseSegment( const B2DPoint& rCenter, double fRadiusX, double fRadiusY, double fStart, double fEnd )
1837 B2DPolygon aRetval(createPolygonFromUnitEllipseSegment(fStart, fEnd));
1838 const B2DHomMatrix aMatrix(createScaleTranslateB2DHomMatrix(fRadiusX, fRadiusY, rCenter.getX(), rCenter.getY()));
1840 aRetval.transform(aMatrix);
1842 return aRetval;
1845 bool hasNeutralPoints(const B2DPolygon& rCandidate)
1847 OSL_ENSURE(!rCandidate.areControlPointsUsed(), "hasNeutralPoints: ATM works not for curves (!)");
1848 const sal_uInt32 nPointCount(rCandidate.count());
1850 if(nPointCount > 2)
1852 B2DPoint aPrevPoint(rCandidate.getB2DPoint(nPointCount - 1));
1853 B2DPoint aCurrPoint(rCandidate.getB2DPoint(0));
1855 for(sal_uInt32 a(0); a < nPointCount; a++)
1857 const B2DPoint aNextPoint(rCandidate.getB2DPoint((a + 1) % nPointCount));
1858 const B2DVector aPrevVec(aPrevPoint - aCurrPoint);
1859 const B2DVector aNextVec(aNextPoint - aCurrPoint);
1860 const B2VectorOrientation aOrientation(getOrientation(aNextVec, aPrevVec));
1862 if(aOrientation == B2VectorOrientation::Neutral)
1864 // current has neutral orientation
1865 return true;
1867 else
1869 // prepare next
1870 aPrevPoint = aCurrPoint;
1871 aCurrPoint = aNextPoint;
1876 return false;
1879 B2DPolygon removeNeutralPoints(const B2DPolygon& rCandidate)
1881 if(hasNeutralPoints(rCandidate))
1883 const sal_uInt32 nPointCount(rCandidate.count());
1884 B2DPolygon aRetval;
1885 B2DPoint aPrevPoint(rCandidate.getB2DPoint(nPointCount - 1));
1886 B2DPoint aCurrPoint(rCandidate.getB2DPoint(0));
1888 for(sal_uInt32 a(0); a < nPointCount; a++)
1890 const B2DPoint aNextPoint(rCandidate.getB2DPoint((a + 1) % nPointCount));
1891 const B2DVector aPrevVec(aPrevPoint - aCurrPoint);
1892 const B2DVector aNextVec(aNextPoint - aCurrPoint);
1893 const B2VectorOrientation aOrientation(getOrientation(aNextVec, aPrevVec));
1895 if(aOrientation == B2VectorOrientation::Neutral)
1897 // current has neutral orientation, leave it out and prepare next
1898 aCurrPoint = aNextPoint;
1900 else
1902 // add current point
1903 aRetval.append(aCurrPoint);
1905 // prepare next
1906 aPrevPoint = aCurrPoint;
1907 aCurrPoint = aNextPoint;
1911 while(aRetval.count() && getOrientationForIndex(aRetval, 0) == B2VectorOrientation::Neutral)
1913 aRetval.remove(0);
1916 // copy closed state
1917 aRetval.setClosed(rCandidate.isClosed());
1919 return aRetval;
1921 else
1923 return rCandidate;
1927 bool isConvex(const B2DPolygon& rCandidate)
1929 OSL_ENSURE(!rCandidate.areControlPointsUsed(), "isConvex: ATM works not for curves (!)");
1930 const sal_uInt32 nPointCount(rCandidate.count());
1932 if(nPointCount > 2)
1934 const B2DPoint aPrevPoint(rCandidate.getB2DPoint(nPointCount - 1));
1935 B2DPoint aCurrPoint(rCandidate.getB2DPoint(0));
1936 B2DVector aCurrVec(aPrevPoint - aCurrPoint);
1937 B2VectorOrientation aOrientation(B2VectorOrientation::Neutral);
1939 for(sal_uInt32 a(0); a < nPointCount; a++)
1941 const B2DPoint aNextPoint(rCandidate.getB2DPoint((a + 1) % nPointCount));
1942 const B2DVector aNextVec(aNextPoint - aCurrPoint);
1943 const B2VectorOrientation aCurrentOrientation(getOrientation(aNextVec, aCurrVec));
1945 if(aOrientation == B2VectorOrientation::Neutral)
1947 // set start value, maybe neutral again
1948 aOrientation = aCurrentOrientation;
1950 else
1952 if(aCurrentOrientation != B2VectorOrientation::Neutral && aCurrentOrientation != aOrientation)
1954 // different orientations found, that's it
1955 return false;
1959 // prepare next
1960 aCurrPoint = aNextPoint;
1961 aCurrVec = -aNextVec;
1965 return true;
1968 B2VectorOrientation getOrientationForIndex(const B2DPolygon& rCandidate, sal_uInt32 nIndex)
1970 OSL_ENSURE(nIndex < rCandidate.count(), "getOrientationForIndex: index out of range (!)");
1971 const B2DPoint aPrev(rCandidate.getB2DPoint(getIndexOfPredecessor(nIndex, rCandidate)));
1972 const B2DPoint aCurr(rCandidate.getB2DPoint(nIndex));
1973 const B2DPoint aNext(rCandidate.getB2DPoint(getIndexOfSuccessor(nIndex, rCandidate)));
1974 const B2DVector aBack(aPrev - aCurr);
1975 const B2DVector aForw(aNext - aCurr);
1977 return getOrientation(aForw, aBack);
1980 bool isPointOnLine(const B2DPoint& rStart, const B2DPoint& rEnd, const B2DPoint& rCandidate, bool bWithPoints)
1982 if(rCandidate.equal(rStart) || rCandidate.equal(rEnd))
1984 // candidate is in epsilon around start or end -> inside
1985 return bWithPoints;
1987 else if(rStart.equal(rEnd))
1989 // start and end are equal, but candidate is outside their epsilon -> outside
1990 return false;
1992 else
1994 const B2DVector aEdgeVector(rEnd - rStart);
1995 const B2DVector aTestVector(rCandidate - rStart);
1997 if(areParallel(aEdgeVector, aTestVector))
1999 const double fZero(0.0);
2000 const double fOne(1.0);
2001 const double fParamTestOnCurr(fabs(aEdgeVector.getX()) > fabs(aEdgeVector.getY())
2002 ? aTestVector.getX() / aEdgeVector.getX()
2003 : aTestVector.getY() / aEdgeVector.getY());
2005 if(fTools::more(fParamTestOnCurr, fZero) && fTools::less(fParamTestOnCurr, fOne))
2007 return true;
2011 return false;
2015 bool isPointOnPolygon(const B2DPolygon& rCandidate, const B2DPoint& rPoint, bool bWithPoints)
2017 const B2DPolygon aCandidate(rCandidate.areControlPointsUsed() ? rCandidate.getDefaultAdaptiveSubdivision() : rCandidate);
2018 const sal_uInt32 nPointCount(aCandidate.count());
2020 if(nPointCount > 1)
2022 const sal_uInt32 nLoopCount(aCandidate.isClosed() ? nPointCount : nPointCount - 1);
2023 B2DPoint aCurrentPoint(aCandidate.getB2DPoint(0));
2025 for(sal_uInt32 a(0); a < nLoopCount; a++)
2027 const B2DPoint aNextPoint(aCandidate.getB2DPoint((a + 1) % nPointCount));
2029 if(isPointOnLine(aCurrentPoint, aNextPoint, rPoint, bWithPoints))
2031 return true;
2034 aCurrentPoint = aNextPoint;
2037 else if(nPointCount && bWithPoints)
2039 return rPoint.equal(aCandidate.getB2DPoint(0));
2042 return false;
2045 bool isPointInTriangle(const B2DPoint& rA, const B2DPoint& rB, const B2DPoint& rC, const B2DPoint& rCandidate, bool bWithBorder)
2047 if(arePointsOnSameSideOfLine(rA, rB, rC, rCandidate, bWithBorder))
2049 if(arePointsOnSameSideOfLine(rB, rC, rA, rCandidate, bWithBorder))
2051 if(arePointsOnSameSideOfLine(rC, rA, rB, rCandidate, bWithBorder))
2053 return true;
2058 return false;
2061 bool arePointsOnSameSideOfLine(const B2DPoint& rStart, const B2DPoint& rEnd, const B2DPoint& rCandidateA, const B2DPoint& rCandidateB, bool bWithLine)
2063 const B2DVector aLineVector(rEnd - rStart);
2064 const B2DVector aVectorToA(rEnd - rCandidateA);
2065 const double fCrossA(aLineVector.cross(aVectorToA));
2067 // tdf#88352 increase numerical correctness and use rtl::math::approxEqual
2068 // instead of fTools::equalZero which compares with a fixed small value
2069 if(fCrossA == 0.0)
2071 // one point on the line
2072 return bWithLine;
2075 const B2DVector aVectorToB(rEnd - rCandidateB);
2076 const double fCrossB(aLineVector.cross(aVectorToB));
2078 // increase numerical correctness
2079 if(fCrossB == 0.0)
2081 // one point on the line
2082 return bWithLine;
2085 // return true if they both have the same sign
2086 return ((fCrossA > 0.0) == (fCrossB > 0.0));
2089 void addTriangleFan(
2090 const B2DPolygon& rCandidate,
2091 triangulator::B2DTriangleVector& rTarget)
2093 const sal_uInt32 nCount(rCandidate.count());
2095 if(nCount > 2)
2097 const B2DPoint aStart(rCandidate.getB2DPoint(0));
2098 B2DPoint aLast(rCandidate.getB2DPoint(1));
2100 for(sal_uInt32 a(2); a < nCount; a++)
2102 const B2DPoint aCurrent(rCandidate.getB2DPoint(a));
2103 rTarget.emplace_back(
2104 aStart,
2105 aLast,
2106 aCurrent);
2108 // prepare next
2109 aLast = aCurrent;
2114 namespace
2116 /// return 0 for input of 0, -1 for negative and 1 for positive input
2117 int lcl_sgn( const double n )
2119 return n == 0.0 ? 0 : 1 - 2*int(rtl::math::isSignBitSet(n));
2123 bool isRectangle( const B2DPolygon& rPoly )
2125 // polygon must be closed to resemble a rect, and contain
2126 // at least four points.
2127 if( !rPoly.isClosed() ||
2128 rPoly.count() < 4 ||
2129 rPoly.areControlPointsUsed() )
2131 return false;
2134 // number of 90 degree turns the polygon has taken
2135 int nNumTurns(0);
2137 int nVerticalEdgeType=0;
2138 int nHorizontalEdgeType=0;
2139 bool bNullVertex(true);
2140 bool bCWPolygon(false); // when true, polygon is CW
2141 // oriented, when false, CCW
2142 bool bOrientationSet(false); // when false, polygon
2143 // orientation has not yet
2144 // been determined.
2146 // scan all _edges_ (which involves coming back to point 0
2147 // for the last edge - thus the modulo operation below)
2148 const sal_Int32 nCount( rPoly.count() );
2149 for( sal_Int32 i=0; i<nCount; ++i )
2151 const B2DPoint& rPoint0( rPoly.getB2DPoint(i % nCount) );
2152 const B2DPoint& rPoint1( rPoly.getB2DPoint((i+1) % nCount) );
2154 // is 0 for zero direction vector, 1 for south edge and -1
2155 // for north edge (standard screen coordinate system)
2156 int nCurrVerticalEdgeType( lcl_sgn( rPoint1.getY() - rPoint0.getY() ) );
2158 // is 0 for zero direction vector, 1 for east edge and -1
2159 // for west edge (standard screen coordinate system)
2160 int nCurrHorizontalEdgeType( lcl_sgn(rPoint1.getX() - rPoint0.getX()) );
2162 if( nCurrVerticalEdgeType && nCurrHorizontalEdgeType )
2163 return false; // oblique edge - for sure no rect
2165 const bool bCurrNullVertex( !nCurrVerticalEdgeType && !nCurrHorizontalEdgeType );
2167 // current vertex is equal to previous - just skip,
2168 // until we have a real edge
2169 if( bCurrNullVertex )
2170 continue;
2172 // if previous edge has two identical points, because
2173 // no previous edge direction was available, simply
2174 // take this first non-null edge as the start
2175 // direction. That's what will happen here, if
2176 // bNullVertex is false
2177 if( !bNullVertex )
2179 // 2D cross product - is 1 for CW and -1 for CCW turns
2180 const int nCrossProduct( nHorizontalEdgeType*nCurrVerticalEdgeType -
2181 nVerticalEdgeType*nCurrHorizontalEdgeType );
2183 if( !nCrossProduct )
2184 continue; // no change in orientation -
2185 // collinear edges - just go on
2187 // if polygon orientation is not set, we'll
2188 // determine it now
2189 if( !bOrientationSet )
2191 bCWPolygon = nCrossProduct == 1;
2192 bOrientationSet = true;
2194 else
2196 // if current turn orientation is not equal
2197 // initial orientation, this is not a
2198 // rectangle (as rectangles have consistent
2199 // orientation).
2200 if( (nCrossProduct == 1) != bCWPolygon )
2201 return false;
2204 ++nNumTurns;
2206 // More than four 90 degree turns are an
2207 // indication that this must not be a rectangle.
2208 if( nNumTurns > 4 )
2209 return false;
2212 // store current state for the next turn
2213 nVerticalEdgeType = nCurrVerticalEdgeType;
2214 nHorizontalEdgeType = nCurrHorizontalEdgeType;
2215 bNullVertex = false; // won't reach this line,
2216 // if bCurrNullVertex is
2217 // true - see above
2220 return true;
2223 B3DPolygon createB3DPolygonFromB2DPolygon(const B2DPolygon& rCandidate, double fZCoordinate)
2225 if(rCandidate.areControlPointsUsed())
2227 // call myself recursively with subdivided input
2228 const B2DPolygon aCandidate(adaptiveSubdivideByAngle(rCandidate));
2229 return createB3DPolygonFromB2DPolygon(aCandidate, fZCoordinate);
2231 else
2233 B3DPolygon aRetval;
2235 for(sal_uInt32 a(0); a < rCandidate.count(); a++)
2237 B2DPoint aPoint(rCandidate.getB2DPoint(a));
2238 aRetval.append(B3DPoint(aPoint.getX(), aPoint.getY(), fZCoordinate));
2241 // copy closed state
2242 aRetval.setClosed(rCandidate.isClosed());
2244 return aRetval;
2248 B2DPolygon createB2DPolygonFromB3DPolygon(const B3DPolygon& rCandidate, const B3DHomMatrix& rMat)
2250 B2DPolygon aRetval;
2251 const sal_uInt32 nCount(rCandidate.count());
2252 const bool bIsIdentity(rMat.isIdentity());
2254 for(sal_uInt32 a(0); a < nCount; a++)
2256 B3DPoint aCandidate(rCandidate.getB3DPoint(a));
2258 if(!bIsIdentity)
2260 aCandidate *= rMat;
2263 aRetval.append(B2DPoint(aCandidate.getX(), aCandidate.getY()));
2266 // copy closed state
2267 aRetval.setClosed(rCandidate.isClosed());
2269 return aRetval;
2272 double getSmallestDistancePointToEdge(const B2DPoint& rPointA, const B2DPoint& rPointB, const B2DPoint& rTestPoint, double& rCut)
2274 if(rPointA.equal(rPointB))
2276 rCut = 0.0;
2277 const B2DVector aVector(rTestPoint - rPointA);
2278 return aVector.getLength();
2280 else
2282 // get the relative cut value on line vector (Vector1) for cut with perpendicular through TestPoint
2283 const B2DVector aVector1(rPointB - rPointA);
2284 const B2DVector aVector2(rTestPoint - rPointA);
2285 const double fDividend((aVector2.getX() * aVector1.getX()) + (aVector2.getY() * aVector1.getY()));
2286 const double fDivisor((aVector1.getX() * aVector1.getX()) + (aVector1.getY() * aVector1.getY()));
2287 const double fCut(fDividend / fDivisor);
2289 if(fCut < 0.0)
2291 // not in line range, get distance to PointA
2292 rCut = 0.0;
2293 return aVector2.getLength();
2295 else if(fCut > 1.0)
2297 // not in line range, get distance to PointB
2298 rCut = 1.0;
2299 const B2DVector aVector(rTestPoint - rPointB);
2300 return aVector.getLength();
2302 else
2304 // in line range
2305 const B2DPoint aCutPoint(rPointA + fCut * aVector1);
2306 const B2DVector aVector(rTestPoint - aCutPoint);
2307 rCut = fCut;
2308 return aVector.getLength();
2313 double getSmallestDistancePointToPolygon(const B2DPolygon& rCandidate, const B2DPoint& rTestPoint, sal_uInt32& rEdgeIndex, double& rCut)
2315 double fRetval(DBL_MAX);
2316 const sal_uInt32 nPointCount(rCandidate.count());
2318 if(nPointCount > 1)
2320 const double fZero(0.0);
2321 const sal_uInt32 nEdgeCount(rCandidate.isClosed() ? nPointCount : nPointCount - 1);
2322 B2DCubicBezier aBezier;
2323 aBezier.setStartPoint(rCandidate.getB2DPoint(0));
2325 for(sal_uInt32 a(0); a < nEdgeCount; a++)
2327 const sal_uInt32 nNextIndex((a + 1) % nPointCount);
2328 aBezier.setEndPoint(rCandidate.getB2DPoint(nNextIndex));
2329 double fEdgeDist;
2330 double fNewCut(0.0);
2331 bool bEdgeIsCurve(false);
2333 if(rCandidate.areControlPointsUsed())
2335 aBezier.setControlPointA(rCandidate.getNextControlPoint(a));
2336 aBezier.setControlPointB(rCandidate.getPrevControlPoint(nNextIndex));
2337 aBezier.testAndSolveTrivialBezier();
2338 bEdgeIsCurve = aBezier.isBezier();
2341 if(bEdgeIsCurve)
2343 fEdgeDist = aBezier.getSmallestDistancePointToBezierSegment(rTestPoint, fNewCut);
2345 else
2347 fEdgeDist = getSmallestDistancePointToEdge(aBezier.getStartPoint(), aBezier.getEndPoint(), rTestPoint, fNewCut);
2350 if(fRetval == DBL_MAX || fEdgeDist < fRetval)
2352 fRetval = fEdgeDist;
2353 rEdgeIndex = a;
2354 rCut = fNewCut;
2356 if(fTools::equal(fRetval, fZero))
2358 // already found zero distance, cannot get better. Ensure numerical zero value and end loop.
2359 fRetval = 0.0;
2360 break;
2364 // prepare next step
2365 aBezier.setStartPoint(aBezier.getEndPoint());
2368 if(rtl::math::approxEqual(1.0, rCut))
2370 // correct rEdgeIndex when not last point
2371 if(rCandidate.isClosed())
2373 rEdgeIndex = getIndexOfSuccessor(rEdgeIndex, rCandidate);
2374 rCut = 0.0;
2376 else
2378 if(rEdgeIndex != nEdgeCount - 1)
2380 rEdgeIndex++;
2381 rCut = 0.0;
2387 return fRetval;
2390 B2DPoint distort(const B2DPoint& rCandidate, const B2DRange& rOriginal, const B2DPoint& rTopLeft, const B2DPoint& rTopRight, const B2DPoint& rBottomLeft, const B2DPoint& rBottomRight)
2392 if(fTools::equalZero(rOriginal.getWidth()) || fTools::equalZero(rOriginal.getHeight()))
2394 return rCandidate;
2396 else
2398 const double fRelativeX((rCandidate.getX() - rOriginal.getMinX()) / rOriginal.getWidth());
2399 const double fRelativeY((rCandidate.getY() - rOriginal.getMinY()) / rOriginal.getHeight());
2400 const double fOneMinusRelativeX(1.0 - fRelativeX);
2401 const double fOneMinusRelativeY(1.0 - fRelativeY);
2402 const double fNewX(fOneMinusRelativeY * (fOneMinusRelativeX * rTopLeft.getX() + fRelativeX * rTopRight.getX()) +
2403 fRelativeY * (fOneMinusRelativeX * rBottomLeft.getX() + fRelativeX * rBottomRight.getX()));
2404 const double fNewY(fOneMinusRelativeX * (fOneMinusRelativeY * rTopLeft.getY() + fRelativeY * rBottomLeft.getY()) +
2405 fRelativeX * (fOneMinusRelativeY * rTopRight.getY() + fRelativeY * rBottomRight.getY()));
2407 return B2DPoint(fNewX, fNewY);
2411 B2DPolygon distort(const B2DPolygon& rCandidate, const B2DRange& rOriginal, const B2DPoint& rTopLeft, const B2DPoint& rTopRight, const B2DPoint& rBottomLeft, const B2DPoint& rBottomRight)
2413 const sal_uInt32 nPointCount(rCandidate.count());
2415 if(nPointCount && rOriginal.getWidth() != 0.0 && rOriginal.getHeight() != 0.0)
2417 B2DPolygon aRetval;
2419 for(sal_uInt32 a(0); a < nPointCount; a++)
2421 aRetval.append(distort(rCandidate.getB2DPoint(a), rOriginal, rTopLeft, rTopRight, rBottomLeft, rBottomRight));
2423 if(rCandidate.areControlPointsUsed())
2425 if(!rCandidate.getPrevControlPoint(a).equalZero())
2427 aRetval.setPrevControlPoint(a, distort(rCandidate.getPrevControlPoint(a), rOriginal, rTopLeft, rTopRight, rBottomLeft, rBottomRight));
2430 if(!rCandidate.getNextControlPoint(a).equalZero())
2432 aRetval.setNextControlPoint(a, distort(rCandidate.getNextControlPoint(a), rOriginal, rTopLeft, rTopRight, rBottomLeft, rBottomRight));
2437 aRetval.setClosed(rCandidate.isClosed());
2438 return aRetval;
2440 else
2442 return rCandidate;
2446 B2DPolygon expandToCurve(const B2DPolygon& rCandidate)
2448 B2DPolygon aRetval(rCandidate);
2450 for(sal_uInt32 a(0); a < rCandidate.count(); a++)
2452 expandToCurveInPoint(aRetval, a);
2455 return aRetval;
2458 bool expandToCurveInPoint(B2DPolygon& rCandidate, sal_uInt32 nIndex)
2460 OSL_ENSURE(nIndex < rCandidate.count(), "expandToCurveInPoint: Access to polygon out of range (!)");
2461 bool bRetval(false);
2462 const sal_uInt32 nPointCount(rCandidate.count());
2464 if(nPointCount)
2466 // predecessor
2467 if(!rCandidate.isPrevControlPointUsed(nIndex))
2469 if(!rCandidate.isClosed() && nIndex == 0)
2471 // do not create previous vector for start point of open polygon
2473 else
2475 const sal_uInt32 nPrevIndex((nIndex + (nPointCount - 1)) % nPointCount);
2476 rCandidate.setPrevControlPoint(nIndex, interpolate(rCandidate.getB2DPoint(nIndex), rCandidate.getB2DPoint(nPrevIndex), 1.0 / 3.0));
2477 bRetval = true;
2481 // successor
2482 if(!rCandidate.isNextControlPointUsed(nIndex))
2484 if(!rCandidate.isClosed() && nIndex + 1 == nPointCount)
2486 // do not create next vector for end point of open polygon
2488 else
2490 const sal_uInt32 nNextIndex((nIndex + 1) % nPointCount);
2491 rCandidate.setNextControlPoint(nIndex, interpolate(rCandidate.getB2DPoint(nIndex), rCandidate.getB2DPoint(nNextIndex), 1.0 / 3.0));
2492 bRetval = true;
2497 return bRetval;
2500 bool setContinuityInPoint(B2DPolygon& rCandidate, sal_uInt32 nIndex, B2VectorContinuity eContinuity)
2502 OSL_ENSURE(nIndex < rCandidate.count(), "setContinuityInPoint: Access to polygon out of range (!)");
2503 bool bRetval(false);
2504 const sal_uInt32 nPointCount(rCandidate.count());
2506 if(nPointCount)
2508 const B2DPoint aCurrentPoint(rCandidate.getB2DPoint(nIndex));
2510 switch(eContinuity)
2512 case B2VectorContinuity::NONE :
2514 if(rCandidate.isPrevControlPointUsed(nIndex))
2516 if(!rCandidate.isClosed() && nIndex == 0)
2518 // remove existing previous vector for start point of open polygon
2519 rCandidate.resetPrevControlPoint(nIndex);
2521 else
2523 const sal_uInt32 nPrevIndex((nIndex + (nPointCount - 1)) % nPointCount);
2524 rCandidate.setPrevControlPoint(nIndex, interpolate(aCurrentPoint, rCandidate.getB2DPoint(nPrevIndex), 1.0 / 3.0));
2527 bRetval = true;
2530 if(rCandidate.isNextControlPointUsed(nIndex))
2532 if(!rCandidate.isClosed() && nIndex == nPointCount + 1)
2534 // remove next vector for end point of open polygon
2535 rCandidate.resetNextControlPoint(nIndex);
2537 else
2539 const sal_uInt32 nNextIndex((nIndex + 1) % nPointCount);
2540 rCandidate.setNextControlPoint(nIndex, interpolate(aCurrentPoint, rCandidate.getB2DPoint(nNextIndex), 1.0 / 3.0));
2543 bRetval = true;
2546 break;
2548 case B2VectorContinuity::C1 :
2550 if(rCandidate.isPrevControlPointUsed(nIndex) && rCandidate.isNextControlPointUsed(nIndex))
2552 // lengths both exist since both are used
2553 B2DVector aVectorPrev(rCandidate.getPrevControlPoint(nIndex) - aCurrentPoint);
2554 B2DVector aVectorNext(rCandidate.getNextControlPoint(nIndex) - aCurrentPoint);
2555 const double fLenPrev(aVectorPrev.getLength());
2556 const double fLenNext(aVectorNext.getLength());
2557 aVectorPrev.normalize();
2558 aVectorNext.normalize();
2559 const B2VectorOrientation aOrientation(getOrientation(aVectorPrev, aVectorNext));
2561 if(aOrientation == B2VectorOrientation::Neutral && aVectorPrev.scalar(aVectorNext) < 0.0)
2563 // parallel and opposite direction; check length
2564 if(fTools::equal(fLenPrev, fLenNext))
2566 // this would be even C2, but we want C1. Use the lengths of the corresponding edges.
2567 const sal_uInt32 nPrevIndex((nIndex + (nPointCount - 1)) % nPointCount);
2568 const sal_uInt32 nNextIndex((nIndex + 1) % nPointCount);
2569 const double fLenPrevEdge(B2DVector(rCandidate.getB2DPoint(nPrevIndex) - aCurrentPoint).getLength() * (1.0 / 3.0));
2570 const double fLenNextEdge(B2DVector(rCandidate.getB2DPoint(nNextIndex) - aCurrentPoint).getLength() * (1.0 / 3.0));
2572 rCandidate.setControlPoints(nIndex,
2573 aCurrentPoint + (aVectorPrev * fLenPrevEdge),
2574 aCurrentPoint + (aVectorNext * fLenNextEdge));
2575 bRetval = true;
2578 else
2580 // not parallel or same direction, set vectors and length
2581 const B2DVector aNormalizedPerpendicular(getNormalizedPerpendicular(aVectorPrev + aVectorNext));
2583 if(aOrientation == B2VectorOrientation::Positive)
2585 rCandidate.setControlPoints(nIndex,
2586 aCurrentPoint - (aNormalizedPerpendicular * fLenPrev),
2587 aCurrentPoint + (aNormalizedPerpendicular * fLenNext));
2589 else
2591 rCandidate.setControlPoints(nIndex,
2592 aCurrentPoint + (aNormalizedPerpendicular * fLenPrev),
2593 aCurrentPoint - (aNormalizedPerpendicular * fLenNext));
2596 bRetval = true;
2599 break;
2601 case B2VectorContinuity::C2 :
2603 if(rCandidate.isPrevControlPointUsed(nIndex) && rCandidate.isNextControlPointUsed(nIndex))
2605 // lengths both exist since both are used
2606 B2DVector aVectorPrev(rCandidate.getPrevControlPoint(nIndex) - aCurrentPoint);
2607 B2DVector aVectorNext(rCandidate.getNextControlPoint(nIndex) - aCurrentPoint);
2608 const double fCommonLength((aVectorPrev.getLength() + aVectorNext.getLength()) / 2.0);
2609 aVectorPrev.normalize();
2610 aVectorNext.normalize();
2611 const B2VectorOrientation aOrientation(getOrientation(aVectorPrev, aVectorNext));
2613 if(aOrientation == B2VectorOrientation::Neutral && aVectorPrev.scalar(aVectorNext) < 0.0)
2615 // parallel and opposite direction; set length. Use one direction for better numerical correctness
2616 const B2DVector aScaledDirection(aVectorPrev * fCommonLength);
2618 rCandidate.setControlPoints(nIndex,
2619 aCurrentPoint + aScaledDirection,
2620 aCurrentPoint - aScaledDirection);
2622 else
2624 // not parallel or same direction, set vectors and length
2625 const B2DVector aNormalizedPerpendicular(getNormalizedPerpendicular(aVectorPrev + aVectorNext));
2626 const B2DVector aPerpendicular(aNormalizedPerpendicular * fCommonLength);
2628 if(aOrientation == B2VectorOrientation::Positive)
2630 rCandidate.setControlPoints(nIndex,
2631 aCurrentPoint - aPerpendicular,
2632 aCurrentPoint + aPerpendicular);
2634 else
2636 rCandidate.setControlPoints(nIndex,
2637 aCurrentPoint + aPerpendicular,
2638 aCurrentPoint - aPerpendicular);
2642 bRetval = true;
2644 break;
2649 return bRetval;
2652 B2DPolygon growInNormalDirection(const B2DPolygon& rCandidate, double fValue)
2654 if(fValue != 0.0)
2656 if(rCandidate.areControlPointsUsed())
2658 // call myself recursively with subdivided input
2659 const B2DPolygon aCandidate(adaptiveSubdivideByAngle(rCandidate));
2660 return growInNormalDirection(aCandidate, fValue);
2662 else
2664 B2DPolygon aRetval;
2665 const sal_uInt32 nPointCount(rCandidate.count());
2667 if(nPointCount)
2669 B2DPoint aPrev(rCandidate.getB2DPoint(nPointCount - 1));
2670 B2DPoint aCurrent(rCandidate.getB2DPoint(0));
2672 for(sal_uInt32 a(0); a < nPointCount; a++)
2674 const B2DPoint aNext(rCandidate.getB2DPoint(a + 1 == nPointCount ? 0 : a + 1));
2675 const B2DVector aBack(aPrev - aCurrent);
2676 const B2DVector aForw(aNext - aCurrent);
2677 const B2DVector aPerpBack(getNormalizedPerpendicular(aBack));
2678 const B2DVector aPerpForw(getNormalizedPerpendicular(aForw));
2679 B2DVector aDirection(aPerpBack - aPerpForw);
2680 aDirection.normalize();
2681 aDirection *= fValue;
2682 aRetval.append(aCurrent + aDirection);
2684 // prepare next step
2685 aPrev = aCurrent;
2686 aCurrent = aNext;
2690 // copy closed state
2691 aRetval.setClosed(rCandidate.isClosed());
2693 return aRetval;
2696 else
2698 return rCandidate;
2702 B2DPolygon reSegmentPolygon(const B2DPolygon& rCandidate, sal_uInt32 nSegments)
2704 B2DPolygon aRetval;
2705 const sal_uInt32 nPointCount(rCandidate.count());
2707 if(nPointCount && nSegments)
2709 // get current segment count
2710 const sal_uInt32 nSegmentCount(rCandidate.isClosed() ? nPointCount : nPointCount - 1);
2712 if(nSegmentCount == nSegments)
2714 aRetval = rCandidate;
2716 else
2718 const double fLength(getLength(rCandidate));
2719 const sal_uInt32 nLoopCount(rCandidate.isClosed() ? nSegments : nSegments + 1);
2721 for(sal_uInt32 a(0); a < nLoopCount; a++)
2723 const double fRelativePos(static_cast<double>(a) / static_cast<double>(nSegments)); // 0.0 .. 1.0
2724 const B2DPoint aNewPoint(getPositionRelative(rCandidate, fRelativePos, fLength));
2725 aRetval.append(aNewPoint);
2728 // copy closed flag
2729 aRetval.setClosed(rCandidate.isClosed());
2733 return aRetval;
2736 B2DPolygon interpolate(const B2DPolygon& rOld1, const B2DPolygon& rOld2, double t)
2738 OSL_ENSURE(rOld1.count() == rOld2.count(), "B2DPolygon interpolate: Different geometry (!)");
2740 if(fTools::lessOrEqual(t, 0.0) || rOld1 == rOld2)
2742 return rOld1;
2744 else if(fTools::moreOrEqual(t, 1.0))
2746 return rOld2;
2748 else
2750 B2DPolygon aRetval;
2751 const bool bInterpolateVectors(rOld1.areControlPointsUsed() || rOld2.areControlPointsUsed());
2752 aRetval.setClosed(rOld1.isClosed() && rOld2.isClosed());
2754 for(sal_uInt32 a(0); a < rOld1.count(); a++)
2756 aRetval.append(interpolate(rOld1.getB2DPoint(a), rOld2.getB2DPoint(a), t));
2758 if(bInterpolateVectors)
2760 aRetval.setPrevControlPoint(a, interpolate(rOld1.getPrevControlPoint(a), rOld2.getPrevControlPoint(a), t));
2761 aRetval.setNextControlPoint(a, interpolate(rOld1.getNextControlPoint(a), rOld2.getNextControlPoint(a), t));
2765 return aRetval;
2769 // #i76891#
2770 B2DPolygon simplifyCurveSegments(const B2DPolygon& rCandidate)
2772 const sal_uInt32 nPointCount(rCandidate.count());
2774 if(nPointCount && rCandidate.areControlPointsUsed())
2776 // prepare loop
2777 const sal_uInt32 nEdgeCount(rCandidate.isClosed() ? nPointCount : nPointCount - 1);
2778 B2DPolygon aRetval;
2779 B2DCubicBezier aBezier;
2780 aBezier.setStartPoint(rCandidate.getB2DPoint(0));
2782 // try to avoid costly reallocations
2783 aRetval.reserve( nEdgeCount+1);
2785 // add start point
2786 aRetval.append(aBezier.getStartPoint());
2788 for(sal_uInt32 a(0); a < nEdgeCount; a++)
2790 // get values for edge
2791 const sal_uInt32 nNextIndex((a + 1) % nPointCount);
2792 aBezier.setEndPoint(rCandidate.getB2DPoint(nNextIndex));
2793 aBezier.setControlPointA(rCandidate.getNextControlPoint(a));
2794 aBezier.setControlPointB(rCandidate.getPrevControlPoint(nNextIndex));
2795 aBezier.testAndSolveTrivialBezier();
2797 // still bezier?
2798 if(aBezier.isBezier())
2800 // add edge with control vectors
2801 aRetval.appendBezierSegment(aBezier.getControlPointA(), aBezier.getControlPointB(), aBezier.getEndPoint());
2803 else
2805 // add edge
2806 aRetval.append(aBezier.getEndPoint());
2809 // next point
2810 aBezier.setStartPoint(aBezier.getEndPoint());
2813 if(rCandidate.isClosed())
2815 // set closed flag, rescue control point and correct last double point
2816 closeWithGeometryChange(aRetval);
2819 return aRetval;
2821 else
2823 return rCandidate;
2827 // makes the given indexed point the new polygon start point. To do that, the points in the
2828 // polygon will be rotated. This is only valid for closed polygons, for non-closed ones
2829 // an assertion will be triggered
2830 B2DPolygon makeStartPoint(const B2DPolygon& rCandidate, sal_uInt32 nIndexOfNewStatPoint)
2832 const sal_uInt32 nPointCount(rCandidate.count());
2834 if(nPointCount > 2 && nIndexOfNewStatPoint != 0 && nIndexOfNewStatPoint < nPointCount)
2836 OSL_ENSURE(rCandidate.isClosed(), "makeStartPoint: only valid for closed polygons (!)");
2837 B2DPolygon aRetval;
2839 for(sal_uInt32 a(0); a < nPointCount; a++)
2841 const sal_uInt32 nSourceIndex((a + nIndexOfNewStatPoint) % nPointCount);
2842 aRetval.append(rCandidate.getB2DPoint(nSourceIndex));
2844 if(rCandidate.areControlPointsUsed())
2846 aRetval.setPrevControlPoint(a, rCandidate.getPrevControlPoint(nSourceIndex));
2847 aRetval.setNextControlPoint(a, rCandidate.getNextControlPoint(nSourceIndex));
2851 return aRetval;
2854 return rCandidate;
2857 B2DPolygon createEdgesOfGivenLength(const B2DPolygon& rCandidate, double fLength, double fStart, double fEnd)
2859 B2DPolygon aRetval;
2861 if(fLength < 0.0)
2863 fLength = 0.0;
2866 if(!fTools::equalZero(fLength))
2868 if(fStart < 0.0)
2870 fStart = 0.0;
2873 if(fEnd < 0.0)
2875 fEnd = 0.0;
2878 if(fEnd < fStart)
2880 fEnd = fStart;
2883 // iterate and consume pieces with fLength. First subdivide to reduce input to line segments
2884 const B2DPolygon aCandidate(rCandidate.areControlPointsUsed() ? rCandidate.getDefaultAdaptiveSubdivision() : rCandidate);
2885 const sal_uInt32 nPointCount(aCandidate.count());
2887 if(nPointCount > 1)
2889 const bool bEndActive(!fTools::equalZero(fEnd));
2890 const sal_uInt32 nEdgeCount(aCandidate.isClosed() ? nPointCount : nPointCount - 1);
2891 B2DPoint aCurrent(aCandidate.getB2DPoint(0));
2892 double fPositionInEdge(fStart);
2893 double fAbsolutePosition(fStart);
2895 for(sal_uInt32 a(0); a < nEdgeCount; a++)
2897 const sal_uInt32 nNextIndex((a + 1) % nPointCount);
2898 const B2DPoint aNext(aCandidate.getB2DPoint(nNextIndex));
2899 const B2DVector aEdge(aNext - aCurrent);
2900 double fEdgeLength(aEdge.getLength());
2902 if(!fTools::equalZero(fEdgeLength))
2904 while(fTools::less(fPositionInEdge, fEdgeLength))
2906 // move position on edge forward as long as on edge
2907 const double fScalar(fPositionInEdge / fEdgeLength);
2908 aRetval.append(aCurrent + (aEdge * fScalar));
2909 fPositionInEdge += fLength;
2911 if(bEndActive)
2913 fAbsolutePosition += fLength;
2915 if(fTools::more(fAbsolutePosition, fEnd))
2917 break;
2922 // subtract length of current edge
2923 fPositionInEdge -= fEdgeLength;
2926 if(bEndActive && fTools::more(fAbsolutePosition, fEnd))
2928 break;
2931 // prepare next step
2932 aCurrent = aNext;
2935 // keep closed state
2936 aRetval.setClosed(aCandidate.isClosed());
2938 else
2940 // source polygon has only one point, return unchanged
2941 aRetval = aCandidate;
2945 return aRetval;
2948 B2DPolygon createWaveline(const B2DPolygon& rCandidate, double fWaveWidth, double fWaveHeight)
2950 B2DPolygon aRetval;
2952 if(fWaveWidth < 0.0)
2954 fWaveWidth = 0.0;
2957 if(fWaveHeight < 0.0)
2959 fWaveHeight = 0.0;
2962 const bool bHasWidth(!fTools::equalZero(fWaveWidth));
2964 if(bHasWidth)
2966 const bool bHasHeight(!fTools::equalZero(fWaveHeight));
2967 if(bHasHeight)
2969 // width and height, create waveline. First subdivide to reduce input to line segments
2970 // of WaveWidth. Last segment may be missing. If this turns out to be a problem, it
2971 // may be added here again using the original last point from rCandidate. It may
2972 // also be the case that rCandidate was closed. To simplify things it is handled here
2973 // as if it was opened.
2974 // Result from createEdgesOfGivenLength contains no curved segments, handle as straight
2975 // edges.
2976 const B2DPolygon aEqualLenghEdges(createEdgesOfGivenLength(rCandidate, fWaveWidth));
2977 const sal_uInt32 nPointCount(aEqualLenghEdges.count());
2979 if(nPointCount > 1)
2981 // iterate over straight edges, add start point
2982 B2DPoint aCurrent(aEqualLenghEdges.getB2DPoint(0));
2983 aRetval.append(aCurrent);
2985 for(sal_uInt32 a(0); a < nPointCount - 1; a++)
2987 const sal_uInt32 nNextIndex((a + 1) % nPointCount);
2988 const B2DPoint aNext(aEqualLenghEdges.getB2DPoint(nNextIndex));
2989 const B2DVector aEdge(aNext - aCurrent);
2990 const B2DVector aPerpendicular(getNormalizedPerpendicular(aEdge));
2991 const B2DVector aControlOffset((aEdge * 0.467308) - (aPerpendicular * fWaveHeight));
2993 // add curve segment
2994 aRetval.appendBezierSegment(
2995 aCurrent + aControlOffset,
2996 aNext - aControlOffset,
2997 aNext);
2999 // prepare next step
3000 aCurrent = aNext;
3004 else
3006 // width but no height -> return original polygon
3007 aRetval = rCandidate;
3010 else
3012 // no width -> no waveline, stay empty and return
3015 return aRetval;
3018 // snap points of horizontal or vertical edges to discrete values
3019 B2DPolygon snapPointsOfHorizontalOrVerticalEdges(const B2DPolygon& rCandidate)
3021 const sal_uInt32 nPointCount(rCandidate.count());
3023 if(nPointCount > 1)
3025 // Start by copying the source polygon to get a writeable copy. The closed state is
3026 // copied by aRetval's initialisation, too, so no need to copy it in this method
3027 B2DPolygon aRetval(rCandidate);
3029 // prepare geometry data. Get rounded from original
3030 B2ITuple aPrevTuple(basegfx::fround(rCandidate.getB2DPoint(nPointCount - 1)));
3031 B2DPoint aCurrPoint(rCandidate.getB2DPoint(0));
3032 B2ITuple aCurrTuple(basegfx::fround(aCurrPoint));
3034 // loop over all points. This will also snap the implicit closing edge
3035 // even when not closed, but that's no problem here
3036 for(sal_uInt32 a(0); a < nPointCount; a++)
3038 // get next point. Get rounded from original
3039 const bool bLastRun(a + 1 == nPointCount);
3040 const sal_uInt32 nNextIndex(bLastRun ? 0 : a + 1);
3041 const B2DPoint aNextPoint(rCandidate.getB2DPoint(nNextIndex));
3042 const B2ITuple aNextTuple(basegfx::fround(aNextPoint));
3044 // get the states
3045 const bool bPrevVertical(aPrevTuple.getX() == aCurrTuple.getX());
3046 const bool bNextVertical(aNextTuple.getX() == aCurrTuple.getX());
3047 const bool bPrevHorizontal(aPrevTuple.getY() == aCurrTuple.getY());
3048 const bool bNextHorizontal(aNextTuple.getY() == aCurrTuple.getY());
3049 const bool bSnapX(bPrevVertical || bNextVertical);
3050 const bool bSnapY(bPrevHorizontal || bNextHorizontal);
3052 if(bSnapX || bSnapY)
3054 const B2DPoint aSnappedPoint(
3055 bSnapX ? aCurrTuple.getX() : aCurrPoint.getX(),
3056 bSnapY ? aCurrTuple.getY() : aCurrPoint.getY());
3058 aRetval.setB2DPoint(a, aSnappedPoint);
3061 // prepare next point
3062 if(!bLastRun)
3064 aPrevTuple = aCurrTuple;
3065 aCurrPoint = aNextPoint;
3066 aCurrTuple = aNextTuple;
3070 return aRetval;
3072 else
3074 return rCandidate;
3078 B2DVector getTangentEnteringPoint(const B2DPolygon& rCandidate, sal_uInt32 nIndex)
3080 B2DVector aRetval(0.0, 0.0);
3081 const sal_uInt32 nCount(rCandidate.count());
3083 if(nIndex >= nCount)
3085 // out of range
3086 return aRetval;
3089 // start immediately at prev point compared to nIndex
3090 const bool bClosed(rCandidate.isClosed());
3091 sal_uInt32 nPrev(bClosed ? (nIndex + nCount - 1) % nCount : nIndex ? nIndex - 1 : nIndex);
3093 if(nPrev == nIndex)
3095 // no previous, done
3096 return aRetval;
3099 B2DCubicBezier aSegment;
3101 // go backward in the polygon; if closed, maximal back to start index (nIndex); if not closed,
3102 // until zero. Use nIndex as stop criteria
3103 while(nPrev != nIndex)
3105 // get BezierSegment and tangent at the *end* of segment
3106 rCandidate.getBezierSegment(nPrev, aSegment);
3107 aRetval = aSegment.getTangent(1.0);
3109 if(!aRetval.equalZero())
3111 // if we have a tangent, return it
3112 return aRetval;
3115 // prepare index before checked one
3116 nPrev = bClosed ? (nPrev + nCount - 1) % nCount : nPrev ? nPrev - 1 : nIndex;
3119 return aRetval;
3122 B2DVector getTangentLeavingPoint(const B2DPolygon& rCandidate, sal_uInt32 nIndex)
3124 B2DVector aRetval(0.0, 0.0);
3125 const sal_uInt32 nCount(rCandidate.count());
3127 if(nIndex >= nCount)
3129 // out of range
3130 return aRetval;
3133 // start at nIndex
3134 const bool bClosed(rCandidate.isClosed());
3135 sal_uInt32 nCurrent(nIndex);
3136 B2DCubicBezier aSegment;
3138 // go forward; if closed, do this until once around and back at start index (nIndex); if not
3139 // closed, until last point (nCount - 1). Use nIndex as stop criteria
3142 // get BezierSegment and tangent at the *beginning* of segment
3143 rCandidate.getBezierSegment(nCurrent, aSegment);
3144 aRetval = aSegment.getTangent(0.0);
3146 if(!aRetval.equalZero())
3148 // if we have a tangent, return it
3149 return aRetval;
3152 // prepare next index
3153 nCurrent = bClosed ? (nCurrent + 1) % nCount : nCurrent + 1 < nCount ? nCurrent + 1 : nIndex;
3155 while(nCurrent != nIndex);
3157 return aRetval;
3160 // converters for css::drawing::PointSequence
3162 B2DPolygon UnoPointSequenceToB2DPolygon(
3163 const css::drawing::PointSequence& rPointSequenceSource)
3165 B2DPolygon aRetval;
3166 const sal_uInt32 nLength(rPointSequenceSource.getLength());
3168 if(nLength)
3170 aRetval.reserve(nLength);
3171 const css::awt::Point* pArray = rPointSequenceSource.getConstArray();
3172 const css::awt::Point* pArrayEnd = pArray + rPointSequenceSource.getLength();
3174 for(;pArray != pArrayEnd; pArray++)
3176 aRetval.append(B2DPoint(pArray->X, pArray->Y));
3179 // check for closed state flag
3180 utils::checkClosed(aRetval);
3183 return aRetval;
3186 void B2DPolygonToUnoPointSequence(
3187 const B2DPolygon& rPolygon,
3188 css::drawing::PointSequence& rPointSequenceRetval)
3190 B2DPolygon aPolygon(rPolygon);
3192 if(aPolygon.areControlPointsUsed())
3194 OSL_ENSURE(false, "B2DPolygonToUnoPointSequence: Source contains bezier segments, wrong UNO API data type may be used (!)");
3195 aPolygon = aPolygon.getDefaultAdaptiveSubdivision();
3198 const sal_uInt32 nPointCount(aPolygon.count());
3200 if(nPointCount)
3202 // Take closed state into account, the API polygon still uses the old closed definition
3203 // with last/first point are identical (cannot hold information about open polygons with identical
3204 // first and last point, though)
3205 const bool bIsClosed(aPolygon.isClosed());
3207 rPointSequenceRetval.realloc(bIsClosed ? nPointCount + 1 : nPointCount);
3208 css::awt::Point* pSequence = rPointSequenceRetval.getArray();
3210 for(sal_uInt32 b(0); b < nPointCount; b++)
3212 const B2DPoint aPoint(aPolygon.getB2DPoint(b));
3213 const css::awt::Point aAPIPoint(fround(aPoint.getX()), fround(aPoint.getY()));
3215 *pSequence = aAPIPoint;
3216 pSequence++;
3219 // copy first point if closed
3220 if(bIsClosed)
3222 *pSequence = *rPointSequenceRetval.getArray();
3225 else
3227 rPointSequenceRetval.realloc(0);
3231 // converters for css::drawing::PointSequence and
3232 // css::drawing::FlagSequence to B2DPolygon (curved polygons)
3234 B2DPolygon UnoPolygonBezierCoordsToB2DPolygon(
3235 const css::drawing::PointSequence& rPointSequenceSource,
3236 const css::drawing::FlagSequence& rFlagSequenceSource)
3238 const sal_uInt32 nCount(static_cast<sal_uInt32>(rPointSequenceSource.getLength()));
3239 OSL_ENSURE(nCount == static_cast<sal_uInt32>(rFlagSequenceSource.getLength()),
3240 "UnoPolygonBezierCoordsToB2DPolygon: Unequal count of Points and Flags (!)");
3242 // prepare new polygon
3243 B2DPolygon aRetval;
3245 if(0 != nCount)
3247 const css::awt::Point* pPointSequence = rPointSequenceSource.getConstArray();
3248 const css::drawing::PolygonFlags* pFlagSequence = rFlagSequenceSource.getConstArray();
3250 // get first point and flag
3251 B2DPoint aNewCoordinatePair(pPointSequence->X, pPointSequence->Y); pPointSequence++;
3252 css::drawing::PolygonFlags ePolygonFlag(*pFlagSequence); pFlagSequence++;
3253 B2DPoint aControlA;
3254 B2DPoint aControlB;
3256 // first point is not allowed to be a control point
3257 OSL_ENSURE(ePolygonFlag != css::drawing::PolygonFlags_CONTROL,
3258 "UnoPolygonBezierCoordsToB2DPolygon: Start point is a control point, illegal input polygon (!)");
3260 // add first point as start point
3261 aRetval.append(aNewCoordinatePair);
3263 for(sal_uInt32 b(1); b < nCount;)
3265 // prepare loop
3266 bool bControlA(false);
3267 bool bControlB(false);
3269 // get next point and flag
3270 aNewCoordinatePair = B2DPoint(pPointSequence->X, pPointSequence->Y);
3271 ePolygonFlag = *pFlagSequence;
3272 pPointSequence++; pFlagSequence++; b++;
3274 if(b < nCount && ePolygonFlag == css::drawing::PolygonFlags_CONTROL)
3276 aControlA = aNewCoordinatePair;
3277 bControlA = true;
3279 // get next point and flag
3280 aNewCoordinatePair = B2DPoint(pPointSequence->X, pPointSequence->Y);
3281 ePolygonFlag = *pFlagSequence;
3282 pPointSequence++; pFlagSequence++; b++;
3285 if(b < nCount && ePolygonFlag == css::drawing::PolygonFlags_CONTROL)
3287 aControlB = aNewCoordinatePair;
3288 bControlB = true;
3290 // get next point and flag
3291 aNewCoordinatePair = B2DPoint(pPointSequence->X, pPointSequence->Y);
3292 ePolygonFlag = *pFlagSequence;
3293 pPointSequence++; pFlagSequence++; b++;
3296 // two or no control points are consumed, another one would be an error.
3297 // It's also an error if only one control point was read
3298 SAL_WARN_IF(ePolygonFlag == css::drawing::PolygonFlags_CONTROL || bControlA != bControlB,
3299 "basegfx", "UnoPolygonBezierCoordsToB2DPolygon: Illegal source polygon (!)");
3301 // the previous writes used the B2DPolyPoygon -> utils::PolyPolygon converter
3302 // which did not create minimal PolyPolygons, but created all control points
3303 // as null vectors (identical points). Because of the former P(CA)(CB)-norm of
3304 // B2DPolygon and it's unused sign of being the zero-vector and CA and CB being
3305 // relative to P, an empty edge was exported as P == CA == CB. Luckily, the new
3306 // export format can be read without errors by the old OOo-versions, so we need only
3307 // to correct here at read and do not need to export a wrong but compatible version
3308 // for the future.
3309 if(bControlA
3310 && aControlA.equal(aControlB)
3311 && aControlA.equal(aRetval.getB2DPoint(aRetval.count() - 1)))
3313 bControlA = false;
3316 if(bControlA)
3318 // add bezier edge
3319 aRetval.appendBezierSegment(aControlA, aControlB, aNewCoordinatePair);
3321 else
3323 // add edge
3324 aRetval.append(aNewCoordinatePair);
3328 // #i72807# API import uses old line start/end-equal definition for closed,
3329 // so we need to correct this to closed state here
3330 checkClosed(aRetval);
3333 return aRetval;
3336 void B2DPolygonToUnoPolygonBezierCoords(
3337 const B2DPolygon& rPolygon,
3338 css::drawing::PointSequence& rPointSequenceRetval,
3339 css::drawing::FlagSequence& rFlagSequenceRetval)
3341 const sal_uInt32 nPointCount(rPolygon.count());
3343 if(nPointCount)
3345 const bool bCurve(rPolygon.areControlPointsUsed());
3346 const bool bClosed(rPolygon.isClosed());
3348 if(bCurve)
3350 // calculate target point count
3351 const sal_uInt32 nLoopCount(bClosed ? nPointCount : nPointCount - 1);
3353 if(nLoopCount)
3355 // prepare target data. The real needed number of target points (and flags)
3356 // could only be calculated by using two loops, so use dynamic memory
3357 std::vector< css::awt::Point > aCollectPoints;
3358 std::vector< css::drawing::PolygonFlags > aCollectFlags;
3360 // reserve maximum creatable points
3361 const sal_uInt32 nMaxTargetCount((nLoopCount * 3) + 1);
3362 aCollectPoints.reserve(nMaxTargetCount);
3363 aCollectFlags.reserve(nMaxTargetCount);
3365 // prepare current bezier segment by setting start point
3366 B2DCubicBezier aBezierSegment;
3367 aBezierSegment.setStartPoint(rPolygon.getB2DPoint(0));
3369 for(sal_uInt32 a(0); a < nLoopCount; a++)
3371 // add current point (always) and remember StartPointIndex for evtl. later corrections
3372 const sal_uInt32 nStartPointIndex(aCollectPoints.size());
3373 aCollectPoints.emplace_back(
3374 fround(aBezierSegment.getStartPoint().getX()),
3375 fround(aBezierSegment.getStartPoint().getY()));
3376 aCollectFlags.push_back(css::drawing::PolygonFlags_NORMAL);
3378 // prepare next segment
3379 const sal_uInt32 nNextIndex((a + 1) % nPointCount);
3380 aBezierSegment.setEndPoint(rPolygon.getB2DPoint(nNextIndex));
3381 aBezierSegment.setControlPointA(rPolygon.getNextControlPoint(a));
3382 aBezierSegment.setControlPointB(rPolygon.getPrevControlPoint(nNextIndex));
3384 if(aBezierSegment.isBezier())
3386 // if bezier is used, add always two control points due to the old schema
3387 aCollectPoints.emplace_back(
3388 fround(aBezierSegment.getControlPointA().getX()),
3389 fround(aBezierSegment.getControlPointA().getY()));
3390 aCollectFlags.push_back(css::drawing::PolygonFlags_CONTROL);
3392 aCollectPoints.emplace_back(
3393 fround(aBezierSegment.getControlPointB().getX()),
3394 fround(aBezierSegment.getControlPointB().getY()));
3395 aCollectFlags.push_back(css::drawing::PolygonFlags_CONTROL);
3398 // test continuity with previous control point to set flag value
3399 if(aBezierSegment.getControlPointA() != aBezierSegment.getStartPoint() && (bClosed || a))
3401 const B2VectorContinuity eCont(rPolygon.getContinuityInPoint(a));
3403 if(eCont == B2VectorContinuity::C1)
3405 aCollectFlags[nStartPointIndex] = css::drawing::PolygonFlags_SMOOTH;
3407 else if(eCont == B2VectorContinuity::C2)
3409 aCollectFlags[nStartPointIndex] = css::drawing::PolygonFlags_SYMMETRIC;
3413 // prepare next loop
3414 aBezierSegment.setStartPoint(aBezierSegment.getEndPoint());
3417 if(bClosed)
3419 // add first point again as closing point due to old definition
3420 aCollectPoints.push_back(aCollectPoints[0]);
3421 aCollectFlags.push_back(css::drawing::PolygonFlags_NORMAL);
3423 else
3425 // add last point as closing point
3426 const B2DPoint aClosingPoint(rPolygon.getB2DPoint(nPointCount - 1));
3427 aCollectPoints.emplace_back(
3428 fround(aClosingPoint.getX()),
3429 fround(aClosingPoint.getY()));
3430 aCollectFlags.push_back(css::drawing::PolygonFlags_NORMAL);
3433 // copy collected data to target arrays
3434 const sal_uInt32 nTargetCount(aCollectPoints.size());
3435 OSL_ENSURE(nTargetCount == aCollectFlags.size(), "Unequal Point and Flag count (!)");
3437 rPointSequenceRetval.realloc(static_cast<sal_Int32>(nTargetCount));
3438 rFlagSequenceRetval.realloc(static_cast<sal_Int32>(nTargetCount));
3439 css::awt::Point* pPointSequence = rPointSequenceRetval.getArray();
3440 css::drawing::PolygonFlags* pFlagSequence = rFlagSequenceRetval.getArray();
3442 for(sal_uInt32 a(0); a < nTargetCount; a++)
3444 *pPointSequence = aCollectPoints[a];
3445 *pFlagSequence = aCollectFlags[a];
3446 pPointSequence++;
3447 pFlagSequence++;
3451 else
3453 // straightforward point list creation
3454 const sal_uInt32 nTargetCount(nPointCount + (bClosed ? 1 : 0));
3456 rPointSequenceRetval.realloc(static_cast<sal_Int32>(nTargetCount));
3457 rFlagSequenceRetval.realloc(static_cast<sal_Int32>(nTargetCount));
3459 css::awt::Point* pPointSequence = rPointSequenceRetval.getArray();
3460 css::drawing::PolygonFlags* pFlagSequence = rFlagSequenceRetval.getArray();
3462 for(sal_uInt32 a(0); a < nPointCount; a++)
3464 const B2DPoint aB2DPoint(rPolygon.getB2DPoint(a));
3465 const css::awt::Point aAPIPoint(
3466 fround(aB2DPoint.getX()),
3467 fround(aB2DPoint.getY()));
3469 *pPointSequence = aAPIPoint;
3470 *pFlagSequence = css::drawing::PolygonFlags_NORMAL;
3471 pPointSequence++;
3472 pFlagSequence++;
3475 if(bClosed)
3477 // add first point as closing point
3478 *pPointSequence = *rPointSequenceRetval.getConstArray();
3479 *pFlagSequence = css::drawing::PolygonFlags_NORMAL;
3483 else
3485 rPointSequenceRetval.realloc(0);
3486 rFlagSequenceRetval.realloc(0);
3490 } // end of namespace utils
3491 } // end of namespace basegfx
3493 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */