1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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 .
23 #include <basegfx/numeric/ftools.hxx>
24 #include <basegfx/polygon/b2dpolygontools.hxx>
25 #include <osl/diagnose.h>
26 #include <rtl/math.hxx>
27 #include <sal/log.hxx>
28 #include <basegfx/polygon/b2dpolygon.hxx>
29 #include <basegfx/polygon/b2dpolypolygon.hxx>
30 #include <basegfx/range/b2drange.hxx>
31 #include <basegfx/curve/b2dcubicbezier.hxx>
32 #include <basegfx/point/b3dpoint.hxx>
33 #include <basegfx/matrix/b3dhommatrix.hxx>
34 #include <basegfx/matrix/b2dhommatrix.hxx>
35 #include <basegfx/curve/b2dbeziertools.hxx>
36 #include <basegfx/matrix/b2dhommatrixtools.hxx>
39 #define ANGLE_BOUND_START_VALUE (2.25)
40 #define ANGLE_BOUND_MINIMUM_VALUE (0.1)
41 #define STEPSPERQUARTER (3)
43 namespace basegfx::utils
45 void openWithGeometryChange(B2DPolygon
& rCandidate
)
47 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);
64 void closeWithGeometryChange(B2DPolygon
& rCandidate
)
66 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);
82 void checkClosed(B2DPolygon
& rCandidate
)
84 // #i80172# Removed unnecessary assertion
85 // OSL_ENSURE(!rCandidate.isClosed(), "checkClosed: already closed (!)");
87 if(rCandidate
.count() > 1 && rCandidate
.getB2DPoint(0) == rCandidate
.getB2DPoint(rCandidate
.count() - 1))
89 closeWithGeometryChange(rCandidate
);
93 // Get successor and predecessor indices. Returning the same index means there
94 // is none. Same for successor.
95 sal_uInt32
getIndexOfPredecessor(sal_uInt32 nIndex
, const B2DPolygon
& rCandidate
)
97 OSL_ENSURE(nIndex
< rCandidate
.count(), "getIndexOfPredecessor: Access to polygon out of range (!)");
103 else if(rCandidate
.count())
105 return rCandidate
.count() - 1;
113 sal_uInt32
getIndexOfSuccessor(sal_uInt32 nIndex
, const B2DPolygon
& rCandidate
)
115 OSL_ENSURE(nIndex
< rCandidate
.count(), "getIndexOfPredecessor: Access to polygon out of range (!)");
117 if(nIndex
+ 1 < rCandidate
.count())
121 else if(nIndex
+ 1 == rCandidate
.count())
131 B2VectorOrientation
getOrientation(const B2DPolygon
& rCandidate
)
133 B2VectorOrientation
eRetval(B2VectorOrientation::Neutral
);
135 if(rCandidate
.count() > 2 || rCandidate
.areControlPointsUsed())
137 const double fSignedArea(getSignedArea(rCandidate
));
139 if(fTools::equalZero(fSignedArea
))
141 // B2VectorOrientation::Neutral, already set
143 if(fSignedArea
> 0.0)
145 eRetval
= B2VectorOrientation::Positive
;
147 else if(fSignedArea
< 0.0)
149 eRetval
= B2VectorOrientation::Negative
;
156 B2VectorContinuity
getContinuityInPoint(const B2DPolygon
& rCandidate
, sal_uInt32 nIndex
)
158 return rCandidate
.getContinuityInPoint(nIndex
);
161 B2DPolygon
adaptiveSubdivideByDistance(const B2DPolygon
& rCandidate
, double fDistanceBound
, int nRecurseLimit
)
163 if(rCandidate
.areControlPointsUsed())
165 const sal_uInt32
nPointCount(rCandidate
.count());
170 // prepare edge-oriented loop
171 const sal_uInt32
nEdgeCount(rCandidate
.isClosed() ? nPointCount
: nPointCount
- 1);
172 B2DCubicBezier aBezier
;
173 aBezier
.setStartPoint(rCandidate
.getB2DPoint(0));
175 // perf: try to avoid too many reallocations by guessing the result's pointcount
176 aRetval
.reserve(nPointCount
*4);
178 // add start point (always)
179 aRetval
.append(aBezier
.getStartPoint());
181 for(sal_uInt32
a(0); a
< nEdgeCount
; a
++)
183 // get next and control points
184 const sal_uInt32
nNextIndex((a
+ 1) % nPointCount
);
185 aBezier
.setEndPoint(rCandidate
.getB2DPoint(nNextIndex
));
186 aBezier
.setControlPointA(rCandidate
.getNextControlPoint(a
));
187 aBezier
.setControlPointB(rCandidate
.getPrevControlPoint(nNextIndex
));
188 aBezier
.testAndSolveTrivialBezier();
190 if(aBezier
.isBezier())
192 // add curved edge and generate DistanceBound
195 if(fDistanceBound
== 0.0)
197 // If not set, use B2DCubicBezier functionality to guess a rough value
198 const double fRoughLength((aBezier
.getEdgeLength() + aBezier
.getControlPolygonLength()) / 2.0);
200 // take 1/100th of the rough curve length
201 fBound
= fRoughLength
* 0.01;
205 // use given bound value
206 fBound
= fDistanceBound
;
209 // make sure bound value is not too small. The base units are 1/100th mm, thus
210 // just make sure it's not smaller then 1/100th of that
216 // call adaptive subdivide which adds edges to aRetval accordingly
217 aBezier
.adaptiveSubdivideByDistance(aRetval
, fBound
, nRecurseLimit
);
221 // add non-curved edge
222 aRetval
.append(aBezier
.getEndPoint());
226 aBezier
.setStartPoint(aBezier
.getEndPoint());
229 if(rCandidate
.isClosed())
231 // set closed flag and correct last point (which is added double now).
232 closeWithGeometryChange(aRetval
);
244 B2DPolygon
adaptiveSubdivideByAngle(const B2DPolygon
& rCandidate
, double fAngleBound
)
246 if(rCandidate
.areControlPointsUsed())
248 const sal_uInt32
nPointCount(rCandidate
.count());
253 // prepare edge-oriented loop
254 const sal_uInt32
nEdgeCount(rCandidate
.isClosed() ? nPointCount
: nPointCount
- 1);
255 B2DCubicBezier aBezier
;
256 aBezier
.setStartPoint(rCandidate
.getB2DPoint(0));
258 // perf: try to avoid too many reallocations by guessing the result's pointcount
259 aRetval
.reserve(nPointCount
*4);
261 // add start point (always)
262 aRetval
.append(aBezier
.getStartPoint());
264 // #i37443# prepare convenient AngleBound if none was given
265 if(fAngleBound
== 0.0)
267 fAngleBound
= ANGLE_BOUND_START_VALUE
;
269 else if(fTools::less(fAngleBound
, ANGLE_BOUND_MINIMUM_VALUE
))
274 for(sal_uInt32
a(0); a
< nEdgeCount
; a
++)
276 // get next and control points
277 const sal_uInt32
nNextIndex((a
+ 1) % nPointCount
);
278 aBezier
.setEndPoint(rCandidate
.getB2DPoint(nNextIndex
));
279 aBezier
.setControlPointA(rCandidate
.getNextControlPoint(a
));
280 aBezier
.setControlPointB(rCandidate
.getPrevControlPoint(nNextIndex
));
281 aBezier
.testAndSolveTrivialBezier();
283 if(aBezier
.isBezier())
285 // call adaptive subdivide
286 aBezier
.adaptiveSubdivideByAngle(aRetval
, fAngleBound
);
290 // add non-curved edge
291 aRetval
.append(aBezier
.getEndPoint());
295 aBezier
.setStartPoint(aBezier
.getEndPoint());
298 if(rCandidate
.isClosed())
300 // set closed flag and correct last point (which is added double now).
301 closeWithGeometryChange(aRetval
);
313 bool isInside(const B2DPolygon
& rCandidate
, const B2DPoint
& rPoint
, bool bWithBorder
)
315 const B2DPolygon
aCandidate(rCandidate
.areControlPointsUsed() ? rCandidate
.getDefaultAdaptiveSubdivision() : rCandidate
);
317 if(bWithBorder
&& isPointOnPolygon(aCandidate
, rPoint
))
324 const sal_uInt32
nPointCount(aCandidate
.count());
328 B2DPoint
aCurrentPoint(aCandidate
.getB2DPoint(nPointCount
- 1));
330 for(sal_uInt32
a(0); a
< nPointCount
; a
++)
332 const B2DPoint
aPreviousPoint(aCurrentPoint
);
333 aCurrentPoint
= aCandidate
.getB2DPoint(a
);
335 // cross-over in Y? tdf#130150 use full precision, no need for epsilon
336 const bool bCompYA(aPreviousPoint
.getY() > rPoint
.getY());
337 const bool bCompYB(aCurrentPoint
.getY() > rPoint
.getY());
339 if(bCompYA
!= bCompYB
)
341 // cross-over in X? tdf#130150 use full precision, no need for epsilon
342 const bool bCompXA(aPreviousPoint
.getX() > rPoint
.getX());
343 const bool bCompXB(aCurrentPoint
.getX() > rPoint
.getX());
345 if(bCompXA
== bCompXB
)
354 const double fCompare(
355 aCurrentPoint
.getX() - (aCurrentPoint
.getY() - rPoint
.getY()) *
356 (aPreviousPoint
.getX() - aCurrentPoint
.getX()) /
357 (aPreviousPoint
.getY() - aCurrentPoint
.getY()));
359 // tdf#130150 use full precision, no need for epsilon
360 if(fCompare
> rPoint
.getX())
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
))
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
);
402 const sal_uInt32
nPointCount(aCandidate
.count());
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
418 if(fTools::equalZero(fRetval
) || fTools::equalZero(fRetval
* fRetval
))
427 double getArea(const B2DPolygon
& rCandidate
)
431 if(rCandidate
.count() > 2 || rCandidate
.areControlPointsUsed())
433 fRetval
= getSignedArea(rCandidate
);
434 const double fZero(0.0);
436 if(fTools::less(fRetval
, fZero
))
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 (!)");
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();
468 const B2DPoint
aCurrent(rCandidate
.getB2DPoint(nIndex
));
469 const B2DPoint
aNext(rCandidate
.getB2DPoint(nNextIndex
));
471 fRetval
= B2DVector(aNext
- aCurrent
).getLength();
478 double getLength(const B2DPolygon
& rCandidate
)
481 const sal_uInt32
nPointCount(rCandidate
.count());
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());
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();
521 B2DPoint
getPositionAbsolute(const B2DPolygon
& rCandidate
, double fDistance
, double fLength
)
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
);
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
;
554 // crop to polygon start
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
;
570 // crop to polygon end
577 // look for correct index. fDistance is now [0.0 .. fLength[
578 double fEdgeLength(getEdgeLength(rCandidate
, nIndex
));
582 // edge found must be on the half-open range
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
))
592 fDistance
-= fEdgeLength
;
593 fEdgeLength
= getEdgeLength(rCandidate
, ++nIndex
);
597 // it's on this edge, stop
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
622 const sal_uInt32
nNextIndex((nIndex
+ 1) % nPointCount
);
623 const B2DPoint
aNextPoint(rCandidate
.getB2DPoint(nNextIndex
));
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
);
648 const double fRelativeInEdge(fDistance
/ fEdgeLength
);
649 aRetval
= interpolate(aRetval
, aNextPoint
, fRelativeInEdge
);
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());
677 // get length if not given
678 if(fTools::equalZero(fLength
))
680 fLength
= getLength(rCandidate
);
683 // test and correct fFrom
689 // test and correct fTo
690 if(fTools::more(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
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
));
720 if(fTools::equalZero(fFrom
))
722 aRetval
.append(rCandidate
.getB2DPoint(a
));
724 if(rCandidate
.areControlPointsUsed())
726 aRetval
.setNextControlPoint(aRetval
.count() - 1, rCandidate
.getNextControlPoint(a
));
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
));
745 const sal_uInt32
nNextIndex((a
+ 1) % nPointCount
);
746 const B2DPoint
aStart(rCandidate
.getB2DPoint(a
));
747 const B2DPoint
aEnd(rCandidate
.getB2DPoint(nNextIndex
));
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());
773 const double fRelValue((fFrom
- fPositionOfStart
) / fEdgeLength
);
774 aRetval
.append(interpolate(aStart
, aEnd
, fRelValue
));
780 // if same point, end is done, too.
781 if(rtl::math::approxEqual(fFrom
, fTo
))
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
));
803 const sal_uInt32
nNextIndex((a
+ 1) % nPointCount
);
804 const B2DPoint
aStart(rCandidate
.getB2DPoint(a
));
805 const B2DPoint
aEnd(rCandidate
.getB2DPoint(nNextIndex
));
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());
831 const double fRelValue((fTo
- fPositionOfStart
) / fEdgeLength
);
832 aRetval
.append(interpolate(aStart
, aEnd
, fRelValue
));
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
;
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
);
876 bool bFinished(!static_cast<bool>(aCutFlags
& CutFlagValue::ALL
));
878 // test for same points?
880 && (aCutFlags
& (CutFlagValue::START1
|CutFlagValue::END1
))
881 && (aCutFlags
& (CutFlagValue::START2
|CutFlagValue::END2
)))
884 if((aCutFlags
& (CutFlagValue::START1
|CutFlagValue::START2
)) == (CutFlagValue::START1
|CutFlagValue::START2
))
886 if(rEdge1Start
.equal(rEdge2Start
))
889 aRetval
= (CutFlagValue::START1
|CutFlagValue::START2
);
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
))
902 aRetval
= (CutFlagValue::END1
|CutFlagValue::END2
);
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
))
915 aRetval
= (CutFlagValue::START1
|CutFlagValue::END2
);
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
))
929 aRetval
= (CutFlagValue::START2
|CutFlagValue::END1
);
936 if(!bFinished
&& (aCutFlags
& CutFlagValue::LINE
))
938 if(aCutFlags
& CutFlagValue::START1
)
940 // start1 on line 2 ?
941 if(isPointOnEdge(rEdge1Start
, rEdge2Start
, rEdge2Delta
, &fCut2
))
944 aRetval
= (CutFlagValue::LINE
|CutFlagValue::START1
);
948 if(!bFinished
&& (aCutFlags
& CutFlagValue::START2
))
950 // start2 on line 1 ?
951 if(isPointOnEdge(rEdge2Start
, rEdge1Start
, rEdge1Delta
, &fCut1
))
954 aRetval
= (CutFlagValue::LINE
|CutFlagValue::START2
);
958 if(!bFinished
&& (aCutFlags
& CutFlagValue::END1
))
961 const B2DPoint
aEnd1(rEdge1Start
+ rEdge1Delta
);
963 if(isPointOnEdge(aEnd1
, rEdge2Start
, rEdge2Delta
, &fCut2
))
966 aRetval
= (CutFlagValue::LINE
|CutFlagValue::END1
);
970 if(!bFinished
&& (aCutFlags
& CutFlagValue::END2
))
973 const B2DPoint
aEnd2(rEdge2Start
+ rEdge2Delta
);
975 if(isPointOnEdge(aEnd2
, rEdge1Start
, rEdge1Delta
, &fCut1
))
978 aRetval
= (CutFlagValue::LINE
|CutFlagValue::END2
);
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();
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
1036 const B2DPoint
& rPoint
,
1037 const B2DPoint
& rEdgeStart
,
1038 const B2DVector
& rEdgeDelta
,
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
1051 else if(bDeltaXIsZero
)
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
))
1069 else if(bDeltaYIsZero
)
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
))
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
1115 void applyLineDashing(
1116 const B2DPolygon
& rCandidate
,
1117 const std::vector
<double>& rDotDashArray
,
1118 B2DPolyPolygon
* pLineTarget
,
1119 B2DPolyPolygon
* pGapTarget
,
1120 double fDotDashLength
)
1122 // clear targets in any case
1125 pLineTarget
->clear();
1130 pGapTarget
->clear();
1133 // call version that uses callbacks
1137 // provide callbacks as lambdas
1139 ? std::function
<void(const basegfx::B2DPolygon
&)>()
1140 : [&pLineTarget
](const basegfx::B2DPolygon
& rSnippet
){ pLineTarget
->append(rSnippet
); }),
1142 ? std::function
<void(const basegfx::B2DPolygon
&)>()
1143 : [&pGapTarget
](const basegfx::B2DPolygon
& rSnippet
){ pGapTarget
->append(rSnippet
); }),
1147 static void implHandleSnippet(
1148 const B2DPolygon
& rSnippet
,
1149 const std::function
<void(const basegfx::B2DPolygon
& rSnippet
)>& rTargetCallback
,
1153 if(rSnippet
.isClosed())
1163 rTargetCallback(rLast
);
1171 rTargetCallback(rSnippet
);
1175 static void implHandleFirstLast(
1176 const std::function
<void(const basegfx::B2DPolygon
& rSnippet
)>& rTargetCallback
,
1180 if(rFirst
.count() && rLast
.count()
1181 && rFirst
.getB2DPoint(0).equal(rLast
.getB2DPoint(rLast
.count() - 1)))
1183 // start of first and end of last are the same -> merge them
1184 rLast
.append(rFirst
);
1185 rLast
.removeDoublePoints();
1191 rTargetCallback(rLast
);
1196 rTargetCallback(rFirst
);
1200 void applyLineDashing(
1201 const B2DPolygon
& rCandidate
,
1202 const std::vector
<double>& rDotDashArray
,
1203 const std::function
<void(const basegfx::B2DPolygon
& rSnippet
)>& rLineTargetCallback
,
1204 const std::function
<void(const basegfx::B2DPolygon
& rSnippet
)>& rGapTargetCallback
,
1205 double fDotDashLength
)
1207 const sal_uInt32
nPointCount(rCandidate
.count());
1208 const sal_uInt32
nDotDashCount(rDotDashArray
.size());
1210 if(fDotDashLength
<= 0.0)
1212 fDotDashLength
= std::accumulate(rDotDashArray
.begin(), rDotDashArray
.end(), 0.0);
1215 if(fDotDashLength
<= 0.0 || (!rLineTargetCallback
&& !rGapTargetCallback
) || !nPointCount
)
1217 // parameters make no sense, just add source to targets
1218 if (rLineTargetCallback
)
1219 rLineTargetCallback(rCandidate
);
1221 if (rGapTargetCallback
)
1222 rGapTargetCallback(rCandidate
);
1227 // precalculate maximal acceptable length of candidate polygon assuming
1228 // we want to create a maximum of fNumberOfAllowedSnippets. For
1229 // fNumberOfAllowedSnippets use ca. 65536, double due to line & gap.
1230 static const double fNumberOfAllowedSnippets(65535.0 * 2.0);
1231 const double fAllowedLength((fNumberOfAllowedSnippets
* fDotDashLength
) / double(rDotDashArray
.size()));
1232 const double fCandidateLength(basegfx::utils::getLength(rCandidate
));
1233 std::vector
<double> aDotDashArray(rDotDashArray
);
1235 if(fCandidateLength
> fAllowedLength
)
1237 // we would produce more than fNumberOfAllowedSnippets, so
1238 // adapt aDotDashArray to exactly produce assumed number. Also
1239 // assert this to let the caller know about it.
1240 // If this asserts: Please think about checking your DotDashArray
1241 // before calling this function or evtl. use the callback version
1242 // to *not* produce that much of data. Even then, you may still
1243 // think about producing too much runtime (!)
1244 assert(true && "applyLineDashing: potentially too expensive to do the requested dismantle - please consider stretched LineDash pattern (!)");
1246 // calculate correcting factor, apply to aDotDashArray and fDotDashLength
1247 // to enlarge these as needed
1248 const double fFactor(fCandidateLength
/ fAllowedLength
);
1249 std::for_each(aDotDashArray
.begin(), aDotDashArray
.end(), [&fFactor
](double &f
){ f
*= fFactor
; });
1252 // prepare current edge's start
1253 B2DCubicBezier aCurrentEdge
;
1254 const bool bIsClosed(rCandidate
.isClosed());
1255 const sal_uInt32
nEdgeCount(bIsClosed
? nPointCount
: nPointCount
- 1);
1256 aCurrentEdge
.setStartPoint(rCandidate
.getB2DPoint(0));
1258 // prepare DotDashArray iteration and the line/gap switching bool
1259 sal_uInt32
nDotDashIndex(0);
1261 double fDotDashMovingLength(aDotDashArray
[0]);
1262 B2DPolygon aSnippet
;
1264 // remember 1st and last snippets to try to merge after execution
1265 // is complete and hand to callback
1266 B2DPolygon aFirstLine
, aLastLine
;
1267 B2DPolygon aFirstGap
, aLastGap
;
1269 // iterate over all edges
1270 for(sal_uInt32
a(0); a
< nEdgeCount
; a
++)
1272 // update current edge (fill in C1, C2 and end point)
1273 double fLastDotDashMovingLength(0.0);
1274 const sal_uInt32
nNextIndex((a
+ 1) % nPointCount
);
1275 aCurrentEdge
.setControlPointA(rCandidate
.getNextControlPoint(a
));
1276 aCurrentEdge
.setControlPointB(rCandidate
.getPrevControlPoint(nNextIndex
));
1277 aCurrentEdge
.setEndPoint(rCandidate
.getB2DPoint(nNextIndex
));
1279 // check if we have a trivial bezier segment -> possible fallback to edge
1280 aCurrentEdge
.testAndSolveTrivialBezier();
1282 if(aCurrentEdge
.isBezier())
1285 const B2DCubicBezierHelper
aCubicBezierHelper(aCurrentEdge
);
1286 const double fEdgeLength(aCubicBezierHelper
.getLength());
1288 if(!fTools::equalZero(fEdgeLength
))
1290 while(fTools::less(fDotDashMovingLength
, fEdgeLength
))
1292 // new split is inside edge, create and append snippet [fLastDotDashMovingLength, fDotDashMovingLength]
1293 const bool bHandleLine(bIsLine
&& rLineTargetCallback
);
1294 const bool bHandleGap(!bIsLine
&& rGapTargetCallback
);
1296 if(bHandleLine
|| bHandleGap
)
1298 const double fBezierSplitStart(aCubicBezierHelper
.distanceToRelative(fLastDotDashMovingLength
));
1299 const double fBezierSplitEnd(aCubicBezierHelper
.distanceToRelative(fDotDashMovingLength
));
1300 B2DCubicBezier
aBezierSnippet(aCurrentEdge
.snippet(fBezierSplitStart
, fBezierSplitEnd
));
1302 if(!aSnippet
.count())
1304 aSnippet
.append(aBezierSnippet
.getStartPoint());
1307 aSnippet
.appendBezierSegment(aBezierSnippet
.getControlPointA(), aBezierSnippet
.getControlPointB(), aBezierSnippet
.getEndPoint());
1311 implHandleSnippet(aSnippet
, rLineTargetCallback
, aFirstLine
, aLastLine
);
1316 implHandleSnippet(aSnippet
, rGapTargetCallback
, aFirstGap
, aLastGap
);
1322 // prepare next DotDashArray step and flip line/gap flag
1323 fLastDotDashMovingLength
= fDotDashMovingLength
;
1324 fDotDashMovingLength
+= aDotDashArray
[(++nDotDashIndex
) % nDotDashCount
];
1328 // append closing snippet [fLastDotDashMovingLength, fEdgeLength]
1329 const bool bHandleLine(bIsLine
&& rLineTargetCallback
);
1330 const bool bHandleGap(!bIsLine
&& rGapTargetCallback
);
1332 if(bHandleLine
|| bHandleGap
)
1334 B2DCubicBezier aRight
;
1335 const double fBezierSplit(aCubicBezierHelper
.distanceToRelative(fLastDotDashMovingLength
));
1337 aCurrentEdge
.split(fBezierSplit
, nullptr, &aRight
);
1339 if(!aSnippet
.count())
1341 aSnippet
.append(aRight
.getStartPoint());
1344 aSnippet
.appendBezierSegment(aRight
.getControlPointA(), aRight
.getControlPointB(), aRight
.getEndPoint());
1347 // prepare move to next edge
1348 fDotDashMovingLength
-= fEdgeLength
;
1354 const double fEdgeLength(aCurrentEdge
.getEdgeLength());
1356 if(!fTools::equalZero(fEdgeLength
))
1358 while(fTools::less(fDotDashMovingLength
, fEdgeLength
))
1360 // new split is inside edge, create and append snippet [fLastDotDashMovingLength, fDotDashMovingLength]
1361 const bool bHandleLine(bIsLine
&& rLineTargetCallback
);
1362 const bool bHandleGap(!bIsLine
&& rGapTargetCallback
);
1364 if(bHandleLine
|| bHandleGap
)
1366 if(!aSnippet
.count())
1368 aSnippet
.append(interpolate(aCurrentEdge
.getStartPoint(), aCurrentEdge
.getEndPoint(), fLastDotDashMovingLength
/ fEdgeLength
));
1371 aSnippet
.append(interpolate(aCurrentEdge
.getStartPoint(), aCurrentEdge
.getEndPoint(), fDotDashMovingLength
/ fEdgeLength
));
1375 implHandleSnippet(aSnippet
, rLineTargetCallback
, aFirstLine
, aLastLine
);
1380 implHandleSnippet(aSnippet
, rGapTargetCallback
, aFirstGap
, aLastGap
);
1386 // prepare next DotDashArray step and flip line/gap flag
1387 fLastDotDashMovingLength
= fDotDashMovingLength
;
1388 fDotDashMovingLength
+= aDotDashArray
[(++nDotDashIndex
) % nDotDashCount
];
1392 // append snippet [fLastDotDashMovingLength, fEdgeLength]
1393 const bool bHandleLine(bIsLine
&& rLineTargetCallback
);
1394 const bool bHandleGap(!bIsLine
&& rGapTargetCallback
);
1396 if(bHandleLine
|| bHandleGap
)
1398 if(!aSnippet
.count())
1400 aSnippet
.append(interpolate(aCurrentEdge
.getStartPoint(), aCurrentEdge
.getEndPoint(), fLastDotDashMovingLength
/ fEdgeLength
));
1403 aSnippet
.append(aCurrentEdge
.getEndPoint());
1406 // prepare move to next edge
1407 fDotDashMovingLength
-= fEdgeLength
;
1411 // prepare next edge step (end point gets new start point)
1412 aCurrentEdge
.setStartPoint(aCurrentEdge
.getEndPoint());
1415 // append last intermediate results (if exists)
1416 if(aSnippet
.count())
1418 const bool bHandleLine(bIsLine
&& rLineTargetCallback
);
1419 const bool bHandleGap(!bIsLine
&& rGapTargetCallback
);
1423 implHandleSnippet(aSnippet
, rLineTargetCallback
, aFirstLine
, aLastLine
);
1428 implHandleSnippet(aSnippet
, rGapTargetCallback
, aFirstGap
, aLastGap
);
1432 if(bIsClosed
&& rLineTargetCallback
)
1434 implHandleFirstLast(rLineTargetCallback
, aFirstLine
, aLastLine
);
1437 if(bIsClosed
&& rGapTargetCallback
)
1439 implHandleFirstLast(rGapTargetCallback
, aFirstGap
, aLastGap
);
1443 // test if point is inside epsilon-range around an edge defined
1444 // by the two given points. Can be used for HitTesting. The epsilon-range
1445 // is defined to be the rectangle centered to the given edge, using height
1446 // 2 x fDistance, and the circle around both points with radius fDistance.
1447 bool isInEpsilonRange(const B2DPoint
& rEdgeStart
, const B2DPoint
& rEdgeEnd
, const B2DPoint
& rTestPosition
, double fDistance
)
1449 // build edge vector
1450 const B2DVector
aEdge(rEdgeEnd
- rEdgeStart
);
1451 bool bDoDistanceTestStart(false);
1452 bool bDoDistanceTestEnd(false);
1454 if(aEdge
.equalZero())
1456 // no edge, just a point. Do one of the distance tests.
1457 bDoDistanceTestStart
= true;
1461 // edge has a length. Create perpendicular vector.
1462 const B2DVector
aPerpend(getPerpendicular(aEdge
));
1464 (aPerpend
.getY() * (rTestPosition
.getX() - rEdgeStart
.getX())
1465 + aPerpend
.getX() * (rEdgeStart
.getY() - rTestPosition
.getY())) /
1466 (aEdge
.getX() * aEdge
.getX() + aEdge
.getY() * aEdge
.getY()));
1467 const double fZero(0.0);
1468 const double fOne(1.0);
1470 if(fTools::less(fCut
, fZero
))
1472 // left of rEdgeStart
1473 bDoDistanceTestStart
= true;
1475 else if(fTools::more(fCut
, fOne
))
1477 // right of rEdgeEnd
1478 bDoDistanceTestEnd
= true;
1482 // inside line [0.0 .. 1.0]
1483 const B2DPoint
aCutPoint(interpolate(rEdgeStart
, rEdgeEnd
, fCut
));
1484 const B2DVector
aDelta(rTestPosition
- aCutPoint
);
1485 const double fDistanceSquare(aDelta
.scalar(aDelta
));
1487 return fDistanceSquare
<= fDistance
* fDistance
;
1491 if(bDoDistanceTestStart
)
1493 const B2DVector
aDelta(rTestPosition
- rEdgeStart
);
1494 const double fDistanceSquare(aDelta
.scalar(aDelta
));
1496 if(fDistanceSquare
<= fDistance
* fDistance
)
1501 else if(bDoDistanceTestEnd
)
1503 const B2DVector
aDelta(rTestPosition
- rEdgeEnd
);
1504 const double fDistanceSquare(aDelta
.scalar(aDelta
));
1506 if(fDistanceSquare
<= fDistance
* fDistance
)
1515 // test if point is inside epsilon-range around the given Polygon. Can be used
1516 // for HitTesting. The epsilon-range is defined to be the tube around the polygon
1517 // with distance fDistance and rounded edges (start and end point).
1518 bool isInEpsilonRange(const B2DPolygon
& rCandidate
, const B2DPoint
& rTestPosition
, double fDistance
)
1520 // force to non-bezier polygon
1521 const B2DPolygon
& aCandidate(rCandidate
.getDefaultAdaptiveSubdivision());
1522 const sal_uInt32
nPointCount(aCandidate
.count());
1527 const sal_uInt32
nEdgeCount(aCandidate
.isClosed() ? nPointCount
: nPointCount
- 1);
1528 B2DPoint
aCurrent(aCandidate
.getB2DPoint(0));
1533 for(sal_uInt32
a(0); a
< nEdgeCount
; a
++)
1535 const sal_uInt32
nNextIndex((a
+ 1) % nPointCount
);
1536 const B2DPoint
aNext(aCandidate
.getB2DPoint(nNextIndex
));
1538 if(isInEpsilonRange(aCurrent
, aNext
, rTestPosition
, fDistance
))
1543 // prepare next step
1549 // no edges, but points -> not closed. Check single point. Just
1550 // use isInEpsilonRange with twice the same point, it handles those well
1551 if(isInEpsilonRange(aCurrent
, aCurrent
, rTestPosition
, fDistance
))
1560 // Calculates distance of curve point to its control point for a Bézier curve, that
1561 // approximates a unit circle arc. fAngle is the center angle of the circle arc. The
1562 // constrain 0<=fAngle<=pi/2 must not be violated to give a useful accuracy. For details
1563 // and alternatives read document "ApproxCircleInfo.odt", attachment of bug tdf#121425.
1564 static double impDistanceBezierPointToControl(double fAngle
)
1566 SAL_WARN_IF(fAngle
< 0 || fAngle
> M_PI_2
,"basegfx","angle not suitable for approximate circle");
1567 if (0 <= fAngle
&& fAngle
<= M_PI_2
)
1569 return 4.0/3.0 * ( tan(fAngle
/4.0));
1575 B2DPolygon
createPolygonFromRect( const B2DRectangle
& rRect
, double fRadiusX
, double fRadiusY
)
1577 const double fZero(0.0);
1578 const double fOne(1.0);
1580 fRadiusX
= std::clamp(fRadiusX
, 0.0, 1.0);
1581 fRadiusY
= std::clamp(fRadiusY
, 0.0, 1.0);
1583 if(rtl::math::approxEqual(fZero
, fRadiusX
) || rtl::math::approxEqual(fZero
, fRadiusY
))
1585 // at least in one direction no radius, use rectangle.
1586 // Do not use createPolygonFromRect() here since original
1587 // creator (historical reasons) still creates a start point at the
1588 // bottom center, so do the same here to get the same line patterns.
1589 // Due to this the order of points is different, too.
1590 const B2DPoint
aBottomCenter(rRect
.getCenter().getX(), rRect
.getMaxY());
1591 B2DPolygon aPolygon
{
1593 { rRect
.getMinX(), rRect
.getMaxY() },
1594 { rRect
.getMinX(), rRect
.getMinY() },
1595 { rRect
.getMaxX(), rRect
.getMinY() },
1596 { rRect
.getMaxX(), rRect
.getMaxY() }
1600 aPolygon
.setClosed( true );
1604 else if(rtl::math::approxEqual(fOne
, fRadiusX
) && rtl::math::approxEqual(fOne
, fRadiusY
))
1606 // in both directions full radius, use ellipse
1607 const B2DPoint
aCenter(rRect
.getCenter());
1608 const double fRectRadiusX(rRect
.getWidth() / 2.0);
1609 const double fRectRadiusY(rRect
.getHeight() / 2.0);
1611 return createPolygonFromEllipse( aCenter
, fRectRadiusX
, fRectRadiusY
);
1616 const double fBowX((rRect
.getWidth() / 2.0) * fRadiusX
);
1617 const double fBowY((rRect
.getHeight() / 2.0) * fRadiusY
);
1618 const double fKappa(impDistanceBezierPointToControl(M_PI_2
));
1620 // create start point at bottom center
1621 if(!rtl::math::approxEqual(fOne
, fRadiusX
))
1623 const B2DPoint
aBottomCenter(rRect
.getCenter().getX(), rRect
.getMaxY());
1624 aRetval
.append(aBottomCenter
);
1629 const B2DPoint
aBottomRight(rRect
.getMaxX(), rRect
.getMaxY());
1630 const B2DPoint
aStart(aBottomRight
+ B2DPoint(-fBowX
, 0.0));
1631 const B2DPoint
aStop(aBottomRight
+ B2DPoint(0.0, -fBowY
));
1632 aRetval
.append(aStart
);
1633 aRetval
.appendBezierSegment(interpolate(aStart
, aBottomRight
, fKappa
), interpolate(aStop
, aBottomRight
, fKappa
), aStop
);
1636 // create second bow
1638 const B2DPoint
aTopRight(rRect
.getMaxX(), rRect
.getMinY());
1639 const B2DPoint
aStart(aTopRight
+ B2DPoint(0.0, fBowY
));
1640 const B2DPoint
aStop(aTopRight
+ B2DPoint(-fBowX
, 0.0));
1641 aRetval
.append(aStart
);
1642 aRetval
.appendBezierSegment(interpolate(aStart
, aTopRight
, fKappa
), interpolate(aStop
, aTopRight
, fKappa
), aStop
);
1647 const B2DPoint
aTopLeft(rRect
.getMinX(), rRect
.getMinY());
1648 const B2DPoint
aStart(aTopLeft
+ B2DPoint(fBowX
, 0.0));
1649 const B2DPoint
aStop(aTopLeft
+ B2DPoint(0.0, fBowY
));
1650 aRetval
.append(aStart
);
1651 aRetval
.appendBezierSegment(interpolate(aStart
, aTopLeft
, fKappa
), interpolate(aStop
, aTopLeft
, fKappa
), aStop
);
1656 const B2DPoint
aBottomLeft(rRect
.getMinX(), rRect
.getMaxY());
1657 const B2DPoint
aStart(aBottomLeft
+ B2DPoint(0.0, -fBowY
));
1658 const B2DPoint
aStop(aBottomLeft
+ B2DPoint(fBowX
, 0.0));
1659 aRetval
.append(aStart
);
1660 aRetval
.appendBezierSegment(interpolate(aStart
, aBottomLeft
, fKappa
), interpolate(aStop
, aBottomLeft
, fKappa
), aStop
);
1664 aRetval
.setClosed( true );
1666 // remove double created points if there are extreme radii involved
1667 if(rtl::math::approxEqual(fOne
, fRadiusX
) || rtl::math::approxEqual(fOne
, fRadiusY
))
1669 aRetval
.removeDoublePoints();
1676 B2DPolygon
createPolygonFromRect( const B2DRectangle
& rRect
)
1678 B2DPolygon aPolygon
{
1679 { rRect
.getMinX(), rRect
.getMinY() },
1680 { rRect
.getMaxX(), rRect
.getMinY() },
1681 { rRect
.getMaxX(), rRect
.getMaxY() },
1682 { rRect
.getMinX(), rRect
.getMaxY() }
1686 aPolygon
.setClosed( true );
1691 B2DPolygon
const & createUnitPolygon()
1693 static auto const singleton
= [] {
1694 B2DPolygon aPolygon
{
1702 aPolygon
.setClosed( true );
1709 B2DPolygon
createPolygonFromCircle( const B2DPoint
& rCenter
, double fRadius
)
1711 return createPolygonFromEllipse( rCenter
, fRadius
, fRadius
);
1714 static B2DPolygon
impCreateUnitCircle(sal_uInt32 nStartQuadrant
)
1716 B2DPolygon aUnitCircle
;
1717 const double fSegmentKappa
= impDistanceBezierPointToControl(M_PI_2
/ STEPSPERQUARTER
);
1718 const B2DHomMatrix
aRotateMatrix(createRotateB2DHomMatrix(M_PI_2
/ STEPSPERQUARTER
));
1720 B2DPoint
aPoint(1.0, 0.0);
1721 B2DPoint
aForward(1.0, fSegmentKappa
);
1722 B2DPoint
aBackward(1.0, -fSegmentKappa
);
1724 if(nStartQuadrant
!= 0)
1726 const B2DHomMatrix
aQuadrantMatrix(createRotateB2DHomMatrix(M_PI_2
* (nStartQuadrant
% 4)));
1727 aPoint
*= aQuadrantMatrix
;
1728 aBackward
*= aQuadrantMatrix
;
1729 aForward
*= aQuadrantMatrix
;
1732 aUnitCircle
.append(aPoint
);
1734 for(sal_uInt32
a(0); a
< STEPSPERQUARTER
* 4; a
++)
1736 aPoint
*= aRotateMatrix
;
1737 aBackward
*= aRotateMatrix
;
1738 aUnitCircle
.appendBezierSegment(aForward
, aBackward
, aPoint
);
1739 aForward
*= aRotateMatrix
;
1742 aUnitCircle
.setClosed(true);
1743 aUnitCircle
.removeDoublePoints();
1748 B2DPolygon
const & createHalfUnitCircle()
1750 static auto const singleton
= [] {
1751 B2DPolygon aUnitHalfCircle
;
1752 const double fSegmentKappa(impDistanceBezierPointToControl(M_PI_2
/ STEPSPERQUARTER
));
1753 const B2DHomMatrix
aRotateMatrix(createRotateB2DHomMatrix(M_PI_2
/ STEPSPERQUARTER
));
1754 B2DPoint
aPoint(1.0, 0.0);
1755 B2DPoint
aForward(1.0, fSegmentKappa
);
1756 B2DPoint
aBackward(1.0, -fSegmentKappa
);
1758 aUnitHalfCircle
.append(aPoint
);
1760 for(sal_uInt32
a(0); a
< STEPSPERQUARTER
* 2; a
++)
1762 aPoint
*= aRotateMatrix
;
1763 aBackward
*= aRotateMatrix
;
1764 aUnitHalfCircle
.appendBezierSegment(aForward
, aBackward
, aPoint
);
1765 aForward
*= aRotateMatrix
;
1767 return aUnitHalfCircle
;
1772 B2DPolygon
const & createPolygonFromUnitCircle(sal_uInt32 nStartQuadrant
)
1774 switch(nStartQuadrant
% 4)
1778 static auto const singleton
= impCreateUnitCircle(1);
1784 static auto const singleton
= impCreateUnitCircle(2);
1790 static auto const singleton
= impCreateUnitCircle(3);
1794 default : // case 0 :
1796 static auto const singleton
= impCreateUnitCircle(0);
1802 B2DPolygon
createPolygonFromEllipse( const B2DPoint
& rCenter
, double fRadiusX
, double fRadiusY
, sal_uInt32 nStartQuadrant
)
1804 B2DPolygon
aRetval(createPolygonFromUnitCircle(nStartQuadrant
));
1805 const B2DHomMatrix
aMatrix(createScaleTranslateB2DHomMatrix(fRadiusX
, fRadiusY
, rCenter
.getX(), rCenter
.getY()));
1807 aRetval
.transform(aMatrix
);
1812 B2DPolygon
createPolygonFromUnitEllipseSegment( double fStart
, double fEnd
)
1816 // truncate fStart, fEnd to a range of [0.0 .. 2PI[ where 2PI
1817 // falls back to 0.0 to ensure a unique definition
1823 if(fTools::moreOrEqual(fStart
, 2 * M_PI
))
1833 if(fTools::moreOrEqual(fEnd
, 2 * M_PI
))
1838 if(fTools::equal(fStart
, fEnd
))
1840 // same start and end angle, add single point
1841 aRetval
.append(B2DPoint(cos(fStart
), sin(fStart
)));
1845 const sal_uInt32
nSegments(STEPSPERQUARTER
* 4);
1846 const double fAnglePerSegment(M_PI_2
/ STEPSPERQUARTER
);
1847 const sal_uInt32
nStartSegment(sal_uInt32(fStart
/ fAnglePerSegment
) % nSegments
);
1848 const sal_uInt32
nEndSegment(sal_uInt32(fEnd
/ fAnglePerSegment
) % nSegments
);
1849 const double fSegmentKappa(impDistanceBezierPointToControl(fAnglePerSegment
));
1851 B2DPoint
aSegStart(cos(fStart
), sin(fStart
));
1852 aRetval
.append(aSegStart
);
1854 if(nStartSegment
== nEndSegment
&& fTools::more(fEnd
, fStart
))
1856 // start and end in one sector and in the right order, create in one segment
1857 const B2DPoint
aSegEnd(cos(fEnd
), sin(fEnd
));
1858 const double fFactor(impDistanceBezierPointToControl(fEnd
- fStart
));
1860 aRetval
.appendBezierSegment(
1861 aSegStart
+ (B2DPoint(-aSegStart
.getY(), aSegStart
.getX()) * fFactor
),
1862 aSegEnd
- (B2DPoint(-aSegEnd
.getY(), aSegEnd
.getX()) * fFactor
),
1867 double fSegEndRad((nStartSegment
+ 1) * fAnglePerSegment
);
1868 double fFactor(impDistanceBezierPointToControl(fSegEndRad
- fStart
));
1869 B2DPoint
aSegEnd(cos(fSegEndRad
), sin(fSegEndRad
));
1871 aRetval
.appendBezierSegment(
1872 aSegStart
+ (B2DPoint(-aSegStart
.getY(), aSegStart
.getX()) * fFactor
),
1873 aSegEnd
- (B2DPoint(-aSegEnd
.getY(), aSegEnd
.getX()) * fFactor
),
1876 sal_uInt32
nSegment((nStartSegment
+ 1) % nSegments
);
1877 aSegStart
= aSegEnd
;
1879 while(nSegment
!= nEndSegment
)
1881 // No end in this sector, add full sector.
1882 fSegEndRad
= (nSegment
+ 1) * fAnglePerSegment
;
1883 aSegEnd
= B2DPoint(cos(fSegEndRad
), sin(fSegEndRad
));
1885 aRetval
.appendBezierSegment(
1886 aSegStart
+ (B2DPoint(-aSegStart
.getY(), aSegStart
.getX()) * fSegmentKappa
),
1887 aSegEnd
- (B2DPoint(-aSegEnd
.getY(), aSegEnd
.getX()) * fSegmentKappa
),
1890 nSegment
= (nSegment
+ 1) % nSegments
;
1891 aSegStart
= aSegEnd
;
1894 // End in this sector
1895 const double fSegStartRad(nSegment
* fAnglePerSegment
);
1896 fFactor
= impDistanceBezierPointToControl(fEnd
- fSegStartRad
);
1897 aSegEnd
= B2DPoint(cos(fEnd
), sin(fEnd
));
1899 aRetval
.appendBezierSegment(
1900 aSegStart
+ (B2DPoint(-aSegStart
.getY(), aSegStart
.getX()) * fFactor
),
1901 aSegEnd
- (B2DPoint(-aSegEnd
.getY(), aSegEnd
.getX()) * fFactor
),
1906 // remove double points between segments created by segmented creation
1907 aRetval
.removeDoublePoints();
1912 B2DPolygon
createPolygonFromEllipseSegment( const B2DPoint
& rCenter
, double fRadiusX
, double fRadiusY
, double fStart
, double fEnd
)
1914 B2DPolygon
aRetval(createPolygonFromUnitEllipseSegment(fStart
, fEnd
));
1915 const B2DHomMatrix
aMatrix(createScaleTranslateB2DHomMatrix(fRadiusX
, fRadiusY
, rCenter
.getX(), rCenter
.getY()));
1917 aRetval
.transform(aMatrix
);
1922 bool hasNeutralPoints(const B2DPolygon
& rCandidate
)
1924 OSL_ENSURE(!rCandidate
.areControlPointsUsed(), "hasNeutralPoints: ATM works not for curves (!)");
1925 const sal_uInt32
nPointCount(rCandidate
.count());
1927 if(nPointCount
<= 2)
1930 B2DPoint
aPrevPoint(rCandidate
.getB2DPoint(nPointCount
- 1));
1931 B2DPoint
aCurrPoint(rCandidate
.getB2DPoint(0));
1933 for(sal_uInt32
a(0); a
< nPointCount
; a
++)
1935 const B2DPoint
aNextPoint(rCandidate
.getB2DPoint((a
+ 1) % nPointCount
));
1936 const B2DVector
aPrevVec(aPrevPoint
- aCurrPoint
);
1937 const B2DVector
aNextVec(aNextPoint
- aCurrPoint
);
1938 const B2VectorOrientation
aOrientation(getOrientation(aNextVec
, aPrevVec
));
1940 if(aOrientation
== B2VectorOrientation::Neutral
)
1942 // current has neutral orientation
1948 aPrevPoint
= aCurrPoint
;
1949 aCurrPoint
= aNextPoint
;
1956 B2DPolygon
removeNeutralPoints(const B2DPolygon
& rCandidate
)
1958 if(hasNeutralPoints(rCandidate
))
1960 const sal_uInt32
nPointCount(rCandidate
.count());
1962 B2DPoint
aPrevPoint(rCandidate
.getB2DPoint(nPointCount
- 1));
1963 B2DPoint
aCurrPoint(rCandidate
.getB2DPoint(0));
1965 for(sal_uInt32
a(0); a
< nPointCount
; a
++)
1967 const B2DPoint
aNextPoint(rCandidate
.getB2DPoint((a
+ 1) % nPointCount
));
1968 const B2DVector
aPrevVec(aPrevPoint
- aCurrPoint
);
1969 const B2DVector
aNextVec(aNextPoint
- aCurrPoint
);
1970 const B2VectorOrientation
aOrientation(getOrientation(aNextVec
, aPrevVec
));
1972 if(aOrientation
== B2VectorOrientation::Neutral
)
1974 // current has neutral orientation, leave it out and prepare next
1975 aCurrPoint
= aNextPoint
;
1979 // add current point
1980 aRetval
.append(aCurrPoint
);
1983 aPrevPoint
= aCurrPoint
;
1984 aCurrPoint
= aNextPoint
;
1988 while(aRetval
.count() && getOrientationForIndex(aRetval
, 0) == B2VectorOrientation::Neutral
)
1993 // copy closed state
1994 aRetval
.setClosed(rCandidate
.isClosed());
2004 bool isConvex(const B2DPolygon
& rCandidate
)
2006 OSL_ENSURE(!rCandidate
.areControlPointsUsed(), "isConvex: ATM works not for curves (!)");
2007 const sal_uInt32
nPointCount(rCandidate
.count());
2009 if(nPointCount
<= 2)
2012 const B2DPoint
aPrevPoint(rCandidate
.getB2DPoint(nPointCount
- 1));
2013 B2DPoint
aCurrPoint(rCandidate
.getB2DPoint(0));
2014 B2DVector
aCurrVec(aPrevPoint
- aCurrPoint
);
2015 B2VectorOrientation
aOrientation(B2VectorOrientation::Neutral
);
2017 for(sal_uInt32
a(0); a
< nPointCount
; a
++)
2019 const B2DPoint
aNextPoint(rCandidate
.getB2DPoint((a
+ 1) % nPointCount
));
2020 const B2DVector
aNextVec(aNextPoint
- aCurrPoint
);
2021 const B2VectorOrientation
aCurrentOrientation(getOrientation(aNextVec
, aCurrVec
));
2023 if(aOrientation
== B2VectorOrientation::Neutral
)
2025 // set start value, maybe neutral again
2026 aOrientation
= aCurrentOrientation
;
2030 if(aCurrentOrientation
!= B2VectorOrientation::Neutral
&& aCurrentOrientation
!= aOrientation
)
2032 // different orientations found, that's it
2038 aCurrPoint
= aNextPoint
;
2039 aCurrVec
= -aNextVec
;
2045 B2VectorOrientation
getOrientationForIndex(const B2DPolygon
& rCandidate
, sal_uInt32 nIndex
)
2047 OSL_ENSURE(nIndex
< rCandidate
.count(), "getOrientationForIndex: index out of range (!)");
2048 const B2DPoint
aPrev(rCandidate
.getB2DPoint(getIndexOfPredecessor(nIndex
, rCandidate
)));
2049 const B2DPoint
aCurr(rCandidate
.getB2DPoint(nIndex
));
2050 const B2DPoint
aNext(rCandidate
.getB2DPoint(getIndexOfSuccessor(nIndex
, rCandidate
)));
2051 const B2DVector
aBack(aPrev
- aCurr
);
2052 const B2DVector
aForw(aNext
- aCurr
);
2054 return getOrientation(aForw
, aBack
);
2057 bool isPointOnLine(const B2DPoint
& rStart
, const B2DPoint
& rEnd
, const B2DPoint
& rCandidate
, bool bWithPoints
)
2059 if(rCandidate
.equal(rStart
) || rCandidate
.equal(rEnd
))
2061 // candidate is in epsilon around start or end -> inside
2064 else if(rStart
.equal(rEnd
))
2066 // start and end are equal, but candidate is outside their epsilon -> outside
2071 const B2DVector
aEdgeVector(rEnd
- rStart
);
2072 const B2DVector
aTestVector(rCandidate
- rStart
);
2074 if(areParallel(aEdgeVector
, aTestVector
))
2076 const double fZero(0.0);
2077 const double fOne(1.0);
2078 const double fParamTestOnCurr(fabs(aEdgeVector
.getX()) > fabs(aEdgeVector
.getY())
2079 ? aTestVector
.getX() / aEdgeVector
.getX()
2080 : aTestVector
.getY() / aEdgeVector
.getY());
2082 if(fTools::more(fParamTestOnCurr
, fZero
) && fTools::less(fParamTestOnCurr
, fOne
))
2092 bool isPointOnPolygon(const B2DPolygon
& rCandidate
, const B2DPoint
& rPoint
, bool bWithPoints
)
2094 const B2DPolygon
aCandidate(rCandidate
.areControlPointsUsed() ? rCandidate
.getDefaultAdaptiveSubdivision() : rCandidate
);
2095 const sal_uInt32
nPointCount(aCandidate
.count());
2099 const sal_uInt32
nLoopCount(aCandidate
.isClosed() ? nPointCount
: nPointCount
- 1);
2100 B2DPoint
aCurrentPoint(aCandidate
.getB2DPoint(0));
2102 for(sal_uInt32
a(0); a
< nLoopCount
; a
++)
2104 const B2DPoint
aNextPoint(aCandidate
.getB2DPoint((a
+ 1) % nPointCount
));
2106 if(isPointOnLine(aCurrentPoint
, aNextPoint
, rPoint
, bWithPoints
))
2111 aCurrentPoint
= aNextPoint
;
2114 else if(nPointCount
&& bWithPoints
)
2116 return rPoint
.equal(aCandidate
.getB2DPoint(0));
2122 bool isPointInTriangle(const B2DPoint
& rA
, const B2DPoint
& rB
, const B2DPoint
& rC
, const B2DPoint
& rCandidate
, bool bWithBorder
)
2124 if(arePointsOnSameSideOfLine(rA
, rB
, rC
, rCandidate
, bWithBorder
))
2126 if(arePointsOnSameSideOfLine(rB
, rC
, rA
, rCandidate
, bWithBorder
))
2128 if(arePointsOnSameSideOfLine(rC
, rA
, rB
, rCandidate
, bWithBorder
))
2138 bool arePointsOnSameSideOfLine(const B2DPoint
& rStart
, const B2DPoint
& rEnd
, const B2DPoint
& rCandidateA
, const B2DPoint
& rCandidateB
, bool bWithLine
)
2140 const B2DVector
aLineVector(rEnd
- rStart
);
2141 const B2DVector
aVectorToA(rEnd
- rCandidateA
);
2142 const double fCrossA(aLineVector
.cross(aVectorToA
));
2144 // tdf#88352 increase numerical correctness and use rtl::math::approxEqual
2145 // instead of fTools::equalZero which compares with a fixed small value
2148 // one point on the line
2152 const B2DVector
aVectorToB(rEnd
- rCandidateB
);
2153 const double fCrossB(aLineVector
.cross(aVectorToB
));
2155 // increase numerical correctness
2158 // one point on the line
2162 // return true if they both have the same sign
2163 return ((fCrossA
> 0.0) == (fCrossB
> 0.0));
2166 void addTriangleFan(
2167 const B2DPolygon
& rCandidate
,
2168 triangulator::B2DTriangleVector
& rTarget
)
2170 const sal_uInt32
nCount(rCandidate
.count());
2175 const B2DPoint
aStart(rCandidate
.getB2DPoint(0));
2176 B2DPoint
aLast(rCandidate
.getB2DPoint(1));
2178 for(sal_uInt32
a(2); a
< nCount
; a
++)
2180 const B2DPoint
aCurrent(rCandidate
.getB2DPoint(a
));
2181 rTarget
.emplace_back(
2193 /// return 0 for input of 0, -1 for negative and 1 for positive input
2194 int lcl_sgn( const double n
)
2196 return n
== 0.0 ? 0 : 1 - 2*int(std::signbit(n
));
2200 bool isRectangle( const B2DPolygon
& rPoly
)
2202 // polygon must be closed to resemble a rect, and contain
2203 // at least four points.
2204 if( !rPoly
.isClosed() ||
2205 rPoly
.count() < 4 ||
2206 rPoly
.areControlPointsUsed() )
2211 // number of 90 degree turns the polygon has taken
2214 int nVerticalEdgeType
=0;
2215 int nHorizontalEdgeType
=0;
2216 bool bNullVertex(true);
2217 bool bCWPolygon(false); // when true, polygon is CW
2218 // oriented, when false, CCW
2219 bool bOrientationSet(false); // when false, polygon
2220 // orientation has not yet
2223 // scan all _edges_ (which involves coming back to point 0
2224 // for the last edge - thus the modulo operation below)
2225 const sal_Int32
nCount( rPoly
.count() );
2226 for( sal_Int32 i
=0; i
<nCount
; ++i
)
2228 const B2DPoint
& rPoint0( rPoly
.getB2DPoint(i
% nCount
) );
2229 const B2DPoint
& rPoint1( rPoly
.getB2DPoint((i
+1) % nCount
) );
2231 // is 0 for zero direction vector, 1 for south edge and -1
2232 // for north edge (standard screen coordinate system)
2233 int nCurrVerticalEdgeType( lcl_sgn( rPoint1
.getY() - rPoint0
.getY() ) );
2235 // is 0 for zero direction vector, 1 for east edge and -1
2236 // for west edge (standard screen coordinate system)
2237 int nCurrHorizontalEdgeType( lcl_sgn(rPoint1
.getX() - rPoint0
.getX()) );
2239 if( nCurrVerticalEdgeType
&& nCurrHorizontalEdgeType
)
2240 return false; // oblique edge - for sure no rect
2242 const bool bCurrNullVertex( !nCurrVerticalEdgeType
&& !nCurrHorizontalEdgeType
);
2244 // current vertex is equal to previous - just skip,
2245 // until we have a real edge
2246 if( bCurrNullVertex
)
2249 // if previous edge has two identical points, because
2250 // no previous edge direction was available, simply
2251 // take this first non-null edge as the start
2252 // direction. That's what will happen here, if
2253 // bNullVertex is false
2256 // 2D cross product - is 1 for CW and -1 for CCW turns
2257 const int nCrossProduct( nHorizontalEdgeType
*nCurrVerticalEdgeType
-
2258 nVerticalEdgeType
*nCurrHorizontalEdgeType
);
2260 if( !nCrossProduct
)
2261 continue; // no change in orientation -
2262 // collinear edges - just go on
2264 // if polygon orientation is not set, we'll
2266 if( !bOrientationSet
)
2268 bCWPolygon
= nCrossProduct
== 1;
2269 bOrientationSet
= true;
2273 // if current turn orientation is not equal
2274 // initial orientation, this is not a
2275 // rectangle (as rectangles have consistent
2277 if( (nCrossProduct
== 1) != bCWPolygon
)
2283 // More than four 90 degree turns are an
2284 // indication that this must not be a rectangle.
2289 // store current state for the next turn
2290 nVerticalEdgeType
= nCurrVerticalEdgeType
;
2291 nHorizontalEdgeType
= nCurrHorizontalEdgeType
;
2292 bNullVertex
= false; // won't reach this line,
2293 // if bCurrNullVertex is
2300 B3DPolygon
createB3DPolygonFromB2DPolygon(const B2DPolygon
& rCandidate
, double fZCoordinate
)
2302 if(rCandidate
.areControlPointsUsed())
2304 // call myself recursively with subdivided input
2305 const B2DPolygon
aCandidate(adaptiveSubdivideByAngle(rCandidate
));
2306 return createB3DPolygonFromB2DPolygon(aCandidate
, fZCoordinate
);
2312 for(sal_uInt32
a(0); a
< rCandidate
.count(); a
++)
2314 B2DPoint
aPoint(rCandidate
.getB2DPoint(a
));
2315 aRetval
.append(B3DPoint(aPoint
.getX(), aPoint
.getY(), fZCoordinate
));
2318 // copy closed state
2319 aRetval
.setClosed(rCandidate
.isClosed());
2325 B2DPolygon
createB2DPolygonFromB3DPolygon(const B3DPolygon
& rCandidate
, const B3DHomMatrix
& rMat
)
2328 const sal_uInt32
nCount(rCandidate
.count());
2329 const bool bIsIdentity(rMat
.isIdentity());
2331 for(sal_uInt32
a(0); a
< nCount
; a
++)
2333 B3DPoint
aCandidate(rCandidate
.getB3DPoint(a
));
2340 aRetval
.append(B2DPoint(aCandidate
.getX(), aCandidate
.getY()));
2343 // copy closed state
2344 aRetval
.setClosed(rCandidate
.isClosed());
2349 double getSmallestDistancePointToEdge(const B2DPoint
& rPointA
, const B2DPoint
& rPointB
, const B2DPoint
& rTestPoint
, double& rCut
)
2351 if(rPointA
.equal(rPointB
))
2354 const B2DVector
aVector(rTestPoint
- rPointA
);
2355 return aVector
.getLength();
2359 // get the relative cut value on line vector (Vector1) for cut with perpendicular through TestPoint
2360 const B2DVector
aVector1(rPointB
- rPointA
);
2361 const B2DVector
aVector2(rTestPoint
- rPointA
);
2362 const double fDividend((aVector2
.getX() * aVector1
.getX()) + (aVector2
.getY() * aVector1
.getY()));
2363 const double fDivisor((aVector1
.getX() * aVector1
.getX()) + (aVector1
.getY() * aVector1
.getY()));
2364 const double fCut(fDividend
/ fDivisor
);
2368 // not in line range, get distance to PointA
2370 return aVector2
.getLength();
2374 // not in line range, get distance to PointB
2376 const B2DVector
aVector(rTestPoint
- rPointB
);
2377 return aVector
.getLength();
2382 const B2DPoint
aCutPoint(rPointA
+ fCut
* aVector1
);
2383 const B2DVector
aVector(rTestPoint
- aCutPoint
);
2385 return aVector
.getLength();
2390 double getSmallestDistancePointToPolygon(const B2DPolygon
& rCandidate
, const B2DPoint
& rTestPoint
, sal_uInt32
& rEdgeIndex
, double& rCut
)
2392 double fRetval(DBL_MAX
);
2393 const sal_uInt32
nPointCount(rCandidate
.count());
2397 const double fZero(0.0);
2398 const sal_uInt32
nEdgeCount(rCandidate
.isClosed() ? nPointCount
: nPointCount
- 1);
2399 B2DCubicBezier aBezier
;
2400 aBezier
.setStartPoint(rCandidate
.getB2DPoint(0));
2402 for(sal_uInt32
a(0); a
< nEdgeCount
; a
++)
2404 const sal_uInt32
nNextIndex((a
+ 1) % nPointCount
);
2405 aBezier
.setEndPoint(rCandidate
.getB2DPoint(nNextIndex
));
2407 double fNewCut(0.0);
2408 bool bEdgeIsCurve(false);
2410 if(rCandidate
.areControlPointsUsed())
2412 aBezier
.setControlPointA(rCandidate
.getNextControlPoint(a
));
2413 aBezier
.setControlPointB(rCandidate
.getPrevControlPoint(nNextIndex
));
2414 aBezier
.testAndSolveTrivialBezier();
2415 bEdgeIsCurve
= aBezier
.isBezier();
2420 fEdgeDist
= aBezier
.getSmallestDistancePointToBezierSegment(rTestPoint
, fNewCut
);
2424 fEdgeDist
= getSmallestDistancePointToEdge(aBezier
.getStartPoint(), aBezier
.getEndPoint(), rTestPoint
, fNewCut
);
2427 if(fRetval
== DBL_MAX
|| fEdgeDist
< fRetval
)
2429 fRetval
= fEdgeDist
;
2433 if(fTools::equal(fRetval
, fZero
))
2435 // already found zero distance, cannot get better. Ensure numerical zero value and end loop.
2441 // prepare next step
2442 aBezier
.setStartPoint(aBezier
.getEndPoint());
2445 if(rtl::math::approxEqual(1.0, rCut
))
2447 // correct rEdgeIndex when not last point
2448 if(rCandidate
.isClosed())
2450 rEdgeIndex
= getIndexOfSuccessor(rEdgeIndex
, rCandidate
);
2455 if(rEdgeIndex
!= nEdgeCount
- 1)
2467 B2DPoint
distort(const B2DPoint
& rCandidate
, const B2DRange
& rOriginal
, const B2DPoint
& rTopLeft
, const B2DPoint
& rTopRight
, const B2DPoint
& rBottomLeft
, const B2DPoint
& rBottomRight
)
2469 if(fTools::equalZero(rOriginal
.getWidth()) || fTools::equalZero(rOriginal
.getHeight()))
2475 const double fRelativeX((rCandidate
.getX() - rOriginal
.getMinX()) / rOriginal
.getWidth());
2476 const double fRelativeY((rCandidate
.getY() - rOriginal
.getMinY()) / rOriginal
.getHeight());
2477 const double fOneMinusRelativeX(1.0 - fRelativeX
);
2478 const double fOneMinusRelativeY(1.0 - fRelativeY
);
2479 const double fNewX(fOneMinusRelativeY
* (fOneMinusRelativeX
* rTopLeft
.getX() + fRelativeX
* rTopRight
.getX()) +
2480 fRelativeY
* (fOneMinusRelativeX
* rBottomLeft
.getX() + fRelativeX
* rBottomRight
.getX()));
2481 const double fNewY(fOneMinusRelativeX
* (fOneMinusRelativeY
* rTopLeft
.getY() + fRelativeY
* rBottomLeft
.getY()) +
2482 fRelativeX
* (fOneMinusRelativeY
* rTopRight
.getY() + fRelativeY
* rBottomRight
.getY()));
2484 return B2DPoint(fNewX
, fNewY
);
2488 B2DPolygon
distort(const B2DPolygon
& rCandidate
, const B2DRange
& rOriginal
, const B2DPoint
& rTopLeft
, const B2DPoint
& rTopRight
, const B2DPoint
& rBottomLeft
, const B2DPoint
& rBottomRight
)
2490 const sal_uInt32
nPointCount(rCandidate
.count());
2492 if(nPointCount
&& rOriginal
.getWidth() != 0.0 && rOriginal
.getHeight() != 0.0)
2496 for(sal_uInt32
a(0); a
< nPointCount
; a
++)
2498 aRetval
.append(distort(rCandidate
.getB2DPoint(a
), rOriginal
, rTopLeft
, rTopRight
, rBottomLeft
, rBottomRight
));
2500 if(rCandidate
.areControlPointsUsed())
2502 if(!rCandidate
.getPrevControlPoint(a
).equalZero())
2504 aRetval
.setPrevControlPoint(a
, distort(rCandidate
.getPrevControlPoint(a
), rOriginal
, rTopLeft
, rTopRight
, rBottomLeft
, rBottomRight
));
2507 if(!rCandidate
.getNextControlPoint(a
).equalZero())
2509 aRetval
.setNextControlPoint(a
, distort(rCandidate
.getNextControlPoint(a
), rOriginal
, rTopLeft
, rTopRight
, rBottomLeft
, rBottomRight
));
2514 aRetval
.setClosed(rCandidate
.isClosed());
2523 B2DPolygon
expandToCurve(const B2DPolygon
& rCandidate
)
2525 B2DPolygon
aRetval(rCandidate
);
2527 for(sal_uInt32
a(0); a
< rCandidate
.count(); a
++)
2529 expandToCurveInPoint(aRetval
, a
);
2535 bool expandToCurveInPoint(B2DPolygon
& rCandidate
, sal_uInt32 nIndex
)
2537 OSL_ENSURE(nIndex
< rCandidate
.count(), "expandToCurveInPoint: Access to polygon out of range (!)");
2538 bool bRetval(false);
2539 const sal_uInt32
nPointCount(rCandidate
.count());
2544 if(!rCandidate
.isPrevControlPointUsed(nIndex
))
2546 if(!rCandidate
.isClosed() && nIndex
== 0)
2548 // do not create previous vector for start point of open polygon
2552 const sal_uInt32
nPrevIndex((nIndex
+ (nPointCount
- 1)) % nPointCount
);
2553 rCandidate
.setPrevControlPoint(nIndex
, interpolate(rCandidate
.getB2DPoint(nIndex
), rCandidate
.getB2DPoint(nPrevIndex
), 1.0 / 3.0));
2559 if(!rCandidate
.isNextControlPointUsed(nIndex
))
2561 if(!rCandidate
.isClosed() && nIndex
+ 1 == nPointCount
)
2563 // do not create next vector for end point of open polygon
2567 const sal_uInt32
nNextIndex((nIndex
+ 1) % nPointCount
);
2568 rCandidate
.setNextControlPoint(nIndex
, interpolate(rCandidate
.getB2DPoint(nIndex
), rCandidate
.getB2DPoint(nNextIndex
), 1.0 / 3.0));
2577 bool setContinuityInPoint(B2DPolygon
& rCandidate
, sal_uInt32 nIndex
, B2VectorContinuity eContinuity
)
2579 OSL_ENSURE(nIndex
< rCandidate
.count(), "setContinuityInPoint: Access to polygon out of range (!)");
2580 bool bRetval(false);
2581 const sal_uInt32
nPointCount(rCandidate
.count());
2585 const B2DPoint
aCurrentPoint(rCandidate
.getB2DPoint(nIndex
));
2589 case B2VectorContinuity::NONE
:
2591 if(rCandidate
.isPrevControlPointUsed(nIndex
))
2593 if(!rCandidate
.isClosed() && nIndex
== 0)
2595 // remove existing previous vector for start point of open polygon
2596 rCandidate
.resetPrevControlPoint(nIndex
);
2600 const sal_uInt32
nPrevIndex((nIndex
+ (nPointCount
- 1)) % nPointCount
);
2601 rCandidate
.setPrevControlPoint(nIndex
, interpolate(aCurrentPoint
, rCandidate
.getB2DPoint(nPrevIndex
), 1.0 / 3.0));
2607 if(rCandidate
.isNextControlPointUsed(nIndex
))
2609 if(!rCandidate
.isClosed() && nIndex
== nPointCount
+ 1)
2611 // remove next vector for end point of open polygon
2612 rCandidate
.resetNextControlPoint(nIndex
);
2616 const sal_uInt32
nNextIndex((nIndex
+ 1) % nPointCount
);
2617 rCandidate
.setNextControlPoint(nIndex
, interpolate(aCurrentPoint
, rCandidate
.getB2DPoint(nNextIndex
), 1.0 / 3.0));
2625 case B2VectorContinuity::C1
:
2627 if(rCandidate
.isPrevControlPointUsed(nIndex
) && rCandidate
.isNextControlPointUsed(nIndex
))
2629 // lengths both exist since both are used
2630 B2DVector
aVectorPrev(rCandidate
.getPrevControlPoint(nIndex
) - aCurrentPoint
);
2631 B2DVector
aVectorNext(rCandidate
.getNextControlPoint(nIndex
) - aCurrentPoint
);
2632 const double fLenPrev(aVectorPrev
.getLength());
2633 const double fLenNext(aVectorNext
.getLength());
2634 aVectorPrev
.normalize();
2635 aVectorNext
.normalize();
2636 const B2VectorOrientation
aOrientation(getOrientation(aVectorPrev
, aVectorNext
));
2638 if(aOrientation
== B2VectorOrientation::Neutral
&& aVectorPrev
.scalar(aVectorNext
) < 0.0)
2640 // parallel and opposite direction; check length
2641 if(fTools::equal(fLenPrev
, fLenNext
))
2643 // this would be even C2, but we want C1. Use the lengths of the corresponding edges.
2644 const sal_uInt32
nPrevIndex((nIndex
+ (nPointCount
- 1)) % nPointCount
);
2645 const sal_uInt32
nNextIndex((nIndex
+ 1) % nPointCount
);
2646 const double fLenPrevEdge(B2DVector(rCandidate
.getB2DPoint(nPrevIndex
) - aCurrentPoint
).getLength() * (1.0 / 3.0));
2647 const double fLenNextEdge(B2DVector(rCandidate
.getB2DPoint(nNextIndex
) - aCurrentPoint
).getLength() * (1.0 / 3.0));
2649 rCandidate
.setControlPoints(nIndex
,
2650 aCurrentPoint
+ (aVectorPrev
* fLenPrevEdge
),
2651 aCurrentPoint
+ (aVectorNext
* fLenNextEdge
));
2657 // not parallel or same direction, set vectors and length
2658 const B2DVector
aNormalizedPerpendicular(getNormalizedPerpendicular(aVectorPrev
+ aVectorNext
));
2660 if(aOrientation
== B2VectorOrientation::Positive
)
2662 rCandidate
.setControlPoints(nIndex
,
2663 aCurrentPoint
- (aNormalizedPerpendicular
* fLenPrev
),
2664 aCurrentPoint
+ (aNormalizedPerpendicular
* fLenNext
));
2668 rCandidate
.setControlPoints(nIndex
,
2669 aCurrentPoint
+ (aNormalizedPerpendicular
* fLenPrev
),
2670 aCurrentPoint
- (aNormalizedPerpendicular
* fLenNext
));
2678 case B2VectorContinuity::C2
:
2680 if(rCandidate
.isPrevControlPointUsed(nIndex
) && rCandidate
.isNextControlPointUsed(nIndex
))
2682 // lengths both exist since both are used
2683 B2DVector
aVectorPrev(rCandidate
.getPrevControlPoint(nIndex
) - aCurrentPoint
);
2684 B2DVector
aVectorNext(rCandidate
.getNextControlPoint(nIndex
) - aCurrentPoint
);
2685 const double fCommonLength((aVectorPrev
.getLength() + aVectorNext
.getLength()) / 2.0);
2686 aVectorPrev
.normalize();
2687 aVectorNext
.normalize();
2688 const B2VectorOrientation
aOrientation(getOrientation(aVectorPrev
, aVectorNext
));
2690 if(aOrientation
== B2VectorOrientation::Neutral
&& aVectorPrev
.scalar(aVectorNext
) < 0.0)
2692 // parallel and opposite direction; set length. Use one direction for better numerical correctness
2693 const B2DVector
aScaledDirection(aVectorPrev
* fCommonLength
);
2695 rCandidate
.setControlPoints(nIndex
,
2696 aCurrentPoint
+ aScaledDirection
,
2697 aCurrentPoint
- aScaledDirection
);
2701 // not parallel or same direction, set vectors and length
2702 const B2DVector
aNormalizedPerpendicular(getNormalizedPerpendicular(aVectorPrev
+ aVectorNext
));
2703 const B2DVector
aPerpendicular(aNormalizedPerpendicular
* fCommonLength
);
2705 if(aOrientation
== B2VectorOrientation::Positive
)
2707 rCandidate
.setControlPoints(nIndex
,
2708 aCurrentPoint
- aPerpendicular
,
2709 aCurrentPoint
+ aPerpendicular
);
2713 rCandidate
.setControlPoints(nIndex
,
2714 aCurrentPoint
+ aPerpendicular
,
2715 aCurrentPoint
- aPerpendicular
);
2729 B2DPolygon
growInNormalDirection(const B2DPolygon
& rCandidate
, double fValue
)
2733 if(rCandidate
.areControlPointsUsed())
2735 // call myself recursively with subdivided input
2736 const B2DPolygon
aCandidate(adaptiveSubdivideByAngle(rCandidate
));
2737 return growInNormalDirection(aCandidate
, fValue
);
2742 const sal_uInt32
nPointCount(rCandidate
.count());
2746 B2DPoint
aPrev(rCandidate
.getB2DPoint(nPointCount
- 1));
2747 B2DPoint
aCurrent(rCandidate
.getB2DPoint(0));
2749 for(sal_uInt32
a(0); a
< nPointCount
; a
++)
2751 const B2DPoint
aNext(rCandidate
.getB2DPoint(a
+ 1 == nPointCount
? 0 : a
+ 1));
2752 const B2DVector
aBack(aPrev
- aCurrent
);
2753 const B2DVector
aForw(aNext
- aCurrent
);
2754 const B2DVector
aPerpBack(getNormalizedPerpendicular(aBack
));
2755 const B2DVector
aPerpForw(getNormalizedPerpendicular(aForw
));
2756 B2DVector
aDirection(aPerpBack
- aPerpForw
);
2757 aDirection
.normalize();
2758 aDirection
*= fValue
;
2759 aRetval
.append(aCurrent
+ aDirection
);
2761 // prepare next step
2767 // copy closed state
2768 aRetval
.setClosed(rCandidate
.isClosed());
2779 B2DPolygon
reSegmentPolygon(const B2DPolygon
& rCandidate
, sal_uInt32 nSegments
)
2782 const sal_uInt32
nPointCount(rCandidate
.count());
2784 if(nPointCount
&& nSegments
)
2786 // get current segment count
2787 const sal_uInt32
nSegmentCount(rCandidate
.isClosed() ? nPointCount
: nPointCount
- 1);
2789 if(nSegmentCount
== nSegments
)
2791 aRetval
= rCandidate
;
2795 const double fLength(getLength(rCandidate
));
2796 const sal_uInt32
nLoopCount(rCandidate
.isClosed() ? nSegments
: nSegments
+ 1);
2798 for(sal_uInt32
a(0); a
< nLoopCount
; a
++)
2800 const double fRelativePos(static_cast<double>(a
) / static_cast<double>(nSegments
)); // 0.0 .. 1.0
2801 const B2DPoint
aNewPoint(getPositionRelative(rCandidate
, fRelativePos
, fLength
));
2802 aRetval
.append(aNewPoint
);
2806 aRetval
.setClosed(rCandidate
.isClosed());
2813 B2DPolygon
interpolate(const B2DPolygon
& rOld1
, const B2DPolygon
& rOld2
, double t
)
2815 OSL_ENSURE(rOld1
.count() == rOld2
.count(), "B2DPolygon interpolate: Different geometry (!)");
2817 if(t
<= 0.0 || rOld1
== rOld2
)
2821 else if(fTools::moreOrEqual(t
, 1.0))
2828 const bool bInterpolateVectors(rOld1
.areControlPointsUsed() || rOld2
.areControlPointsUsed());
2829 aRetval
.setClosed(rOld1
.isClosed() && rOld2
.isClosed());
2831 for(sal_uInt32
a(0); a
< rOld1
.count(); a
++)
2833 aRetval
.append(interpolate(rOld1
.getB2DPoint(a
), rOld2
.getB2DPoint(a
), t
));
2835 if(bInterpolateVectors
)
2837 aRetval
.setPrevControlPoint(a
, interpolate(rOld1
.getPrevControlPoint(a
), rOld2
.getPrevControlPoint(a
), t
));
2838 aRetval
.setNextControlPoint(a
, interpolate(rOld1
.getNextControlPoint(a
), rOld2
.getNextControlPoint(a
), t
));
2847 B2DPolygon
simplifyCurveSegments(const B2DPolygon
& rCandidate
)
2849 const sal_uInt32
nPointCount(rCandidate
.count());
2851 if(nPointCount
&& rCandidate
.areControlPointsUsed())
2854 const sal_uInt32
nEdgeCount(rCandidate
.isClosed() ? nPointCount
: nPointCount
- 1);
2856 B2DCubicBezier aBezier
;
2857 aBezier
.setStartPoint(rCandidate
.getB2DPoint(0));
2859 // try to avoid costly reallocations
2860 aRetval
.reserve( nEdgeCount
+1);
2863 aRetval
.append(aBezier
.getStartPoint());
2865 for(sal_uInt32
a(0); a
< nEdgeCount
; a
++)
2867 // get values for edge
2868 const sal_uInt32
nNextIndex((a
+ 1) % nPointCount
);
2869 aBezier
.setEndPoint(rCandidate
.getB2DPoint(nNextIndex
));
2870 aBezier
.setControlPointA(rCandidate
.getNextControlPoint(a
));
2871 aBezier
.setControlPointB(rCandidate
.getPrevControlPoint(nNextIndex
));
2872 aBezier
.testAndSolveTrivialBezier();
2875 if(aBezier
.isBezier())
2877 // add edge with control vectors
2878 aRetval
.appendBezierSegment(aBezier
.getControlPointA(), aBezier
.getControlPointB(), aBezier
.getEndPoint());
2883 aRetval
.append(aBezier
.getEndPoint());
2887 aBezier
.setStartPoint(aBezier
.getEndPoint());
2890 if(rCandidate
.isClosed())
2892 // set closed flag, rescue control point and correct last double point
2893 closeWithGeometryChange(aRetval
);
2904 // makes the given indexed point the new polygon start point. To do that, the points in the
2905 // polygon will be rotated. This is only valid for closed polygons, for non-closed ones
2906 // an assertion will be triggered
2907 B2DPolygon
makeStartPoint(const B2DPolygon
& rCandidate
, sal_uInt32 nIndexOfNewStatPoint
)
2909 const sal_uInt32
nPointCount(rCandidate
.count());
2911 if(nPointCount
> 2 && nIndexOfNewStatPoint
!= 0 && nIndexOfNewStatPoint
< nPointCount
)
2913 OSL_ENSURE(rCandidate
.isClosed(), "makeStartPoint: only valid for closed polygons (!)");
2916 for(sal_uInt32
a(0); a
< nPointCount
; a
++)
2918 const sal_uInt32
nSourceIndex((a
+ nIndexOfNewStatPoint
) % nPointCount
);
2919 aRetval
.append(rCandidate
.getB2DPoint(nSourceIndex
));
2921 if(rCandidate
.areControlPointsUsed())
2923 aRetval
.setPrevControlPoint(a
, rCandidate
.getPrevControlPoint(nSourceIndex
));
2924 aRetval
.setNextControlPoint(a
, rCandidate
.getNextControlPoint(nSourceIndex
));
2934 B2DPolygon
createEdgesOfGivenLength(const B2DPolygon
& rCandidate
, double fLength
, double fStart
, double fEnd
)
2943 if(!fTools::equalZero(fLength
))
2960 // iterate and consume pieces with fLength. First subdivide to reduce input to line segments
2961 const B2DPolygon
aCandidate(rCandidate
.areControlPointsUsed() ? rCandidate
.getDefaultAdaptiveSubdivision() : rCandidate
);
2962 const sal_uInt32
nPointCount(aCandidate
.count());
2966 const bool bEndActive(!fTools::equalZero(fEnd
));
2967 const sal_uInt32
nEdgeCount(aCandidate
.isClosed() ? nPointCount
: nPointCount
- 1);
2968 B2DPoint
aCurrent(aCandidate
.getB2DPoint(0));
2969 double fPositionInEdge(fStart
);
2970 double fAbsolutePosition(fStart
);
2972 for(sal_uInt32
a(0); a
< nEdgeCount
; a
++)
2974 const sal_uInt32
nNextIndex((a
+ 1) % nPointCount
);
2975 const B2DPoint
aNext(aCandidate
.getB2DPoint(nNextIndex
));
2976 const B2DVector
aEdge(aNext
- aCurrent
);
2977 double fEdgeLength(aEdge
.getLength());
2979 if(!fTools::equalZero(fEdgeLength
))
2981 while(fTools::less(fPositionInEdge
, fEdgeLength
))
2983 // move position on edge forward as long as on edge
2984 const double fScalar(fPositionInEdge
/ fEdgeLength
);
2985 aRetval
.append(aCurrent
+ (aEdge
* fScalar
));
2986 fPositionInEdge
+= fLength
;
2990 fAbsolutePosition
+= fLength
;
2992 if(fTools::more(fAbsolutePosition
, fEnd
))
2999 // subtract length of current edge
3000 fPositionInEdge
-= fEdgeLength
;
3003 if(bEndActive
&& fTools::more(fAbsolutePosition
, fEnd
))
3008 // prepare next step
3012 // keep closed state
3013 aRetval
.setClosed(aCandidate
.isClosed());
3017 // source polygon has only one point, return unchanged
3018 aRetval
= aCandidate
;
3025 B2DPolygon
createWaveline(const B2DPolygon
& rCandidate
, double fWaveWidth
, double fWaveHeight
)
3029 if(fWaveWidth
< 0.0)
3034 if(fWaveHeight
< 0.0)
3039 const bool bHasWidth(!fTools::equalZero(fWaveWidth
));
3043 const bool bHasHeight(!fTools::equalZero(fWaveHeight
));
3046 // width and height, create waveline. First subdivide to reduce input to line segments
3047 // of WaveWidth. Last segment may be missing. If this turns out to be a problem, it
3048 // may be added here again using the original last point from rCandidate. It may
3049 // also be the case that rCandidate was closed. To simplify things it is handled here
3050 // as if it was opened.
3051 // Result from createEdgesOfGivenLength contains no curved segments, handle as straight
3053 const B2DPolygon
aEqualLenghEdges(createEdgesOfGivenLength(rCandidate
, fWaveWidth
));
3054 const sal_uInt32
nPointCount(aEqualLenghEdges
.count());
3058 // iterate over straight edges, add start point
3059 B2DPoint
aCurrent(aEqualLenghEdges
.getB2DPoint(0));
3060 aRetval
.append(aCurrent
);
3062 for(sal_uInt32
a(0); a
< nPointCount
- 1; a
++)
3064 const sal_uInt32
nNextIndex((a
+ 1) % nPointCount
);
3065 const B2DPoint
aNext(aEqualLenghEdges
.getB2DPoint(nNextIndex
));
3066 const B2DVector
aEdge(aNext
- aCurrent
);
3067 const B2DVector
aPerpendicular(getNormalizedPerpendicular(aEdge
));
3068 const B2DVector
aControlOffset((aEdge
* 0.467308) - (aPerpendicular
* fWaveHeight
));
3070 // add curve segment
3071 aRetval
.appendBezierSegment(
3072 aCurrent
+ aControlOffset
,
3073 aNext
- aControlOffset
,
3076 // prepare next step
3083 // width but no height -> return original polygon
3084 aRetval
= rCandidate
;
3089 // no width -> no waveline, stay empty and return
3095 // snap points of horizontal or vertical edges to discrete values
3096 B2DPolygon
snapPointsOfHorizontalOrVerticalEdges(const B2DPolygon
& rCandidate
)
3098 const sal_uInt32
nPointCount(rCandidate
.count());
3102 // Start by copying the source polygon to get a writeable copy. The closed state is
3103 // copied by aRetval's initialisation, too, so no need to copy it in this method
3104 B2DPolygon
aRetval(rCandidate
);
3106 // prepare geometry data. Get rounded from original
3107 B2ITuple
aPrevTuple(basegfx::fround(rCandidate
.getB2DPoint(nPointCount
- 1)));
3108 B2DPoint
aCurrPoint(rCandidate
.getB2DPoint(0));
3109 B2ITuple
aCurrTuple(basegfx::fround(aCurrPoint
));
3111 // loop over all points. This will also snap the implicit closing edge
3112 // even when not closed, but that's no problem here
3113 for(sal_uInt32
a(0); a
< nPointCount
; a
++)
3115 // get next point. Get rounded from original
3116 const bool bLastRun(a
+ 1 == nPointCount
);
3117 const sal_uInt32
nNextIndex(bLastRun
? 0 : a
+ 1);
3118 const B2DPoint
aNextPoint(rCandidate
.getB2DPoint(nNextIndex
));
3119 const B2ITuple
aNextTuple(basegfx::fround(aNextPoint
));
3122 const bool bPrevVertical(aPrevTuple
.getX() == aCurrTuple
.getX());
3123 const bool bNextVertical(aNextTuple
.getX() == aCurrTuple
.getX());
3124 const bool bPrevHorizontal(aPrevTuple
.getY() == aCurrTuple
.getY());
3125 const bool bNextHorizontal(aNextTuple
.getY() == aCurrTuple
.getY());
3126 const bool bSnapX(bPrevVertical
|| bNextVertical
);
3127 const bool bSnapY(bPrevHorizontal
|| bNextHorizontal
);
3129 if(bSnapX
|| bSnapY
)
3131 const B2DPoint
aSnappedPoint(
3132 bSnapX
? aCurrTuple
.getX() : aCurrPoint
.getX(),
3133 bSnapY
? aCurrTuple
.getY() : aCurrPoint
.getY());
3135 aRetval
.setB2DPoint(a
, aSnappedPoint
);
3138 // prepare next point
3141 aPrevTuple
= aCurrTuple
;
3142 aCurrPoint
= aNextPoint
;
3143 aCurrTuple
= aNextTuple
;
3155 B2DVector
getTangentEnteringPoint(const B2DPolygon
& rCandidate
, sal_uInt32 nIndex
)
3157 B2DVector
aRetval(0.0, 0.0);
3158 const sal_uInt32
nCount(rCandidate
.count());
3160 if(nIndex
>= nCount
)
3166 // start immediately at prev point compared to nIndex
3167 const bool bClosed(rCandidate
.isClosed());
3168 sal_uInt32
nPrev(bClosed
? (nIndex
+ nCount
- 1) % nCount
: nIndex
? nIndex
- 1 : nIndex
);
3172 // no previous, done
3176 B2DCubicBezier aSegment
;
3178 // go backward in the polygon; if closed, maximal back to start index (nIndex); if not closed,
3179 // until zero. Use nIndex as stop criteria
3180 while(nPrev
!= nIndex
)
3182 // get BezierSegment and tangent at the *end* of segment
3183 rCandidate
.getBezierSegment(nPrev
, aSegment
);
3184 aRetval
= aSegment
.getTangent(1.0);
3186 if(!aRetval
.equalZero())
3188 // if we have a tangent, return it
3192 // prepare index before checked one
3193 nPrev
= bClosed
? (nPrev
+ nCount
- 1) % nCount
: nPrev
? nPrev
- 1 : nIndex
;
3199 B2DVector
getTangentLeavingPoint(const B2DPolygon
& rCandidate
, sal_uInt32 nIndex
)
3201 B2DVector
aRetval(0.0, 0.0);
3202 const sal_uInt32
nCount(rCandidate
.count());
3204 if(nIndex
>= nCount
)
3211 const bool bClosed(rCandidate
.isClosed());
3212 sal_uInt32
nCurrent(nIndex
);
3213 B2DCubicBezier aSegment
;
3215 // go forward; if closed, do this until once around and back at start index (nIndex); if not
3216 // closed, until last point (nCount - 1). Use nIndex as stop criteria
3219 // get BezierSegment and tangent at the *beginning* of segment
3220 rCandidate
.getBezierSegment(nCurrent
, aSegment
);
3221 aRetval
= aSegment
.getTangent(0.0);
3223 if(!aRetval
.equalZero())
3225 // if we have a tangent, return it
3229 // prepare next index
3230 nCurrent
= bClosed
? (nCurrent
+ 1) % nCount
: nCurrent
+ 1 < nCount
? nCurrent
+ 1 : nIndex
;
3232 while(nCurrent
!= nIndex
);
3237 // converters for css::drawing::PointSequence
3239 B2DPolygon
UnoPointSequenceToB2DPolygon(
3240 const css::drawing::PointSequence
& rPointSequenceSource
)
3243 const sal_uInt32
nLength(rPointSequenceSource
.getLength());
3247 aRetval
.reserve(nLength
);
3249 for(auto& point
: rPointSequenceSource
)
3251 aRetval
.append(B2DPoint(point
.X
, point
.Y
));
3254 // check for closed state flag
3255 utils::checkClosed(aRetval
);
3261 void B2DPolygonToUnoPointSequence(
3262 const B2DPolygon
& rPolygon
,
3263 css::drawing::PointSequence
& rPointSequenceRetval
)
3265 B2DPolygon
aPolygon(rPolygon
);
3267 if(aPolygon
.areControlPointsUsed())
3269 OSL_ENSURE(false, "B2DPolygonToUnoPointSequence: Source contains bezier segments, wrong UNO API data type may be used (!)");
3270 aPolygon
= aPolygon
.getDefaultAdaptiveSubdivision();
3273 const sal_uInt32
nPointCount(aPolygon
.count());
3277 // Take closed state into account, the API polygon still uses the old closed definition
3278 // with last/first point are identical (cannot hold information about open polygons with identical
3279 // first and last point, though)
3280 const bool bIsClosed(aPolygon
.isClosed());
3282 rPointSequenceRetval
.realloc(bIsClosed
? nPointCount
+ 1 : nPointCount
);
3283 css::awt::Point
* pSequence
= rPointSequenceRetval
.getArray();
3285 for(sal_uInt32
b(0); b
< nPointCount
; b
++)
3287 const B2DPoint
aPoint(aPolygon
.getB2DPoint(b
));
3288 const css::awt::Point
aAPIPoint(fround(aPoint
.getX()), fround(aPoint
.getY()));
3290 *pSequence
= aAPIPoint
;
3294 // copy first point if closed
3297 *pSequence
= rPointSequenceRetval
[0];
3302 rPointSequenceRetval
.realloc(0);
3306 // converters for css::drawing::PointSequence and
3307 // css::drawing::FlagSequence to B2DPolygon (curved polygons)
3309 B2DPolygon
UnoPolygonBezierCoordsToB2DPolygon(
3310 const css::drawing::PointSequence
& rPointSequenceSource
,
3311 const css::drawing::FlagSequence
& rFlagSequenceSource
)
3313 const sal_Int32
nCount(rPointSequenceSource
.getLength());
3314 OSL_ENSURE(nCount
== rFlagSequenceSource
.getLength(),
3315 "UnoPolygonBezierCoordsToB2DPolygon: Unequal count of Points and Flags (!)");
3317 // prepare new polygon
3322 aRetval
.reserve(nCount
);
3324 // get first point and flag
3325 B2DPoint
aNewCoordinatePair(rPointSequenceSource
[0].X
, rPointSequenceSource
[0].Y
);
3329 // first point is not allowed to be a control point
3330 OSL_ENSURE(rFlagSequenceSource
[0] != css::drawing::PolygonFlags_CONTROL
,
3331 "UnoPolygonBezierCoordsToB2DPolygon: Start point is a control point, illegal input polygon (!)");
3333 // add first point as start point
3334 aRetval
.append(aNewCoordinatePair
);
3336 for(sal_Int32
b(1); b
< nCount
;)
3339 bool bControlA(false);
3340 bool bControlB(false);
3342 // get next point and flag
3343 aNewCoordinatePair
= B2DPoint(rPointSequenceSource
[b
].X
, rPointSequenceSource
[b
].Y
);
3344 css::drawing::PolygonFlags ePolygonFlag
= rFlagSequenceSource
[b
];
3347 if(b
< nCount
&& ePolygonFlag
== css::drawing::PolygonFlags_CONTROL
)
3349 aControlA
= aNewCoordinatePair
;
3352 // get next point and flag
3353 aNewCoordinatePair
= B2DPoint(rPointSequenceSource
[b
].X
, rPointSequenceSource
[b
].Y
);
3354 ePolygonFlag
= rFlagSequenceSource
[b
];
3358 if(b
< nCount
&& ePolygonFlag
== css::drawing::PolygonFlags_CONTROL
)
3360 aControlB
= aNewCoordinatePair
;
3363 // get next point and flag
3364 aNewCoordinatePair
= B2DPoint(rPointSequenceSource
[b
].X
, rPointSequenceSource
[b
].Y
);
3365 ePolygonFlag
= rFlagSequenceSource
[b
];
3369 // two or no control points are consumed, another one would be an error.
3370 // It's also an error if only one control point was read
3371 SAL_WARN_IF(ePolygonFlag
== css::drawing::PolygonFlags_CONTROL
|| bControlA
!= bControlB
,
3372 "basegfx", "UnoPolygonBezierCoordsToB2DPolygon: Illegal source polygon (!)");
3374 // the previous writes used the B2DPolyPolygon -> utils::PolyPolygon converter
3375 // which did not create minimal PolyPolygons, but created all control points
3376 // as null vectors (identical points). Because of the former P(CA)(CB)-norm of
3377 // B2DPolygon and it's unused sign of being the zero-vector and CA and CB being
3378 // relative to P, an empty edge was exported as P == CA == CB. Luckily, the new
3379 // export format can be read without errors by the old OOo-versions, so we need only
3380 // to correct here at read and do not need to export a wrong but compatible version
3383 && aControlA
.equal(aControlB
)
3384 && aControlA
.equal(aRetval
.getB2DPoint(aRetval
.count() - 1)))
3392 aRetval
.appendBezierSegment(aControlA
, aControlB
, aNewCoordinatePair
);
3397 aRetval
.append(aNewCoordinatePair
);
3401 // #i72807# API import uses old line start/end-equal definition for closed,
3402 // so we need to correct this to closed state here
3403 checkClosed(aRetval
);
3409 void B2DPolygonToUnoPolygonBezierCoords(
3410 const B2DPolygon
& rPolygon
,
3411 css::drawing::PointSequence
& rPointSequenceRetval
,
3412 css::drawing::FlagSequence
& rFlagSequenceRetval
)
3414 const sal_uInt32
nPointCount(rPolygon
.count());
3418 const bool bCurve(rPolygon
.areControlPointsUsed());
3419 const bool bClosed(rPolygon
.isClosed());
3423 // calculate target point count
3424 const sal_uInt32
nLoopCount(bClosed
? nPointCount
: nPointCount
- 1);
3428 // prepare target data. The real needed number of target points (and flags)
3429 // could only be calculated by using two loops, so use dynamic memory
3430 std::vector
< css::awt::Point
> aCollectPoints
;
3431 std::vector
< css::drawing::PolygonFlags
> aCollectFlags
;
3433 // reserve maximum creatable points
3434 const sal_uInt32
nMaxTargetCount((nLoopCount
* 3) + 1);
3435 aCollectPoints
.reserve(nMaxTargetCount
);
3436 aCollectFlags
.reserve(nMaxTargetCount
);
3438 // prepare current bezier segment by setting start point
3439 B2DCubicBezier aBezierSegment
;
3440 aBezierSegment
.setStartPoint(rPolygon
.getB2DPoint(0));
3442 for(sal_uInt32
a(0); a
< nLoopCount
; a
++)
3444 // add current point (always) and remember StartPointIndex for evtl. later corrections
3445 const sal_uInt32
nStartPointIndex(aCollectPoints
.size());
3446 aCollectPoints
.emplace_back(
3447 fround(aBezierSegment
.getStartPoint().getX()),
3448 fround(aBezierSegment
.getStartPoint().getY()));
3449 aCollectFlags
.push_back(css::drawing::PolygonFlags_NORMAL
);
3451 // prepare next segment
3452 const sal_uInt32
nNextIndex((a
+ 1) % nPointCount
);
3453 aBezierSegment
.setEndPoint(rPolygon
.getB2DPoint(nNextIndex
));
3454 aBezierSegment
.setControlPointA(rPolygon
.getNextControlPoint(a
));
3455 aBezierSegment
.setControlPointB(rPolygon
.getPrevControlPoint(nNextIndex
));
3457 if(aBezierSegment
.isBezier())
3459 // if bezier is used, add always two control points due to the old schema
3460 aCollectPoints
.emplace_back(
3461 fround(aBezierSegment
.getControlPointA().getX()),
3462 fround(aBezierSegment
.getControlPointA().getY()));
3463 aCollectFlags
.push_back(css::drawing::PolygonFlags_CONTROL
);
3465 aCollectPoints
.emplace_back(
3466 fround(aBezierSegment
.getControlPointB().getX()),
3467 fround(aBezierSegment
.getControlPointB().getY()));
3468 aCollectFlags
.push_back(css::drawing::PolygonFlags_CONTROL
);
3471 // test continuity with previous control point to set flag value
3472 if(aBezierSegment
.getControlPointA() != aBezierSegment
.getStartPoint() && (bClosed
|| a
))
3474 const B2VectorContinuity
eCont(rPolygon
.getContinuityInPoint(a
));
3476 if(eCont
== B2VectorContinuity::C1
)
3478 aCollectFlags
[nStartPointIndex
] = css::drawing::PolygonFlags_SMOOTH
;
3480 else if(eCont
== B2VectorContinuity::C2
)
3482 aCollectFlags
[nStartPointIndex
] = css::drawing::PolygonFlags_SYMMETRIC
;
3486 // prepare next loop
3487 aBezierSegment
.setStartPoint(aBezierSegment
.getEndPoint());
3492 // add first point again as closing point due to old definition
3493 aCollectPoints
.push_back(aCollectPoints
[0]);
3494 aCollectFlags
.push_back(css::drawing::PolygonFlags_NORMAL
);
3498 // add last point as closing point
3499 const B2DPoint
aClosingPoint(rPolygon
.getB2DPoint(nPointCount
- 1));
3500 aCollectPoints
.emplace_back(
3501 fround(aClosingPoint
.getX()),
3502 fround(aClosingPoint
.getY()));
3503 aCollectFlags
.push_back(css::drawing::PolygonFlags_NORMAL
);
3506 // copy collected data to target arrays
3507 const sal_uInt32
nTargetCount(aCollectPoints
.size());
3508 OSL_ENSURE(nTargetCount
== aCollectFlags
.size(), "Unequal Point and Flag count (!)");
3510 rPointSequenceRetval
.realloc(static_cast<sal_Int32
>(nTargetCount
));
3511 rFlagSequenceRetval
.realloc(static_cast<sal_Int32
>(nTargetCount
));
3512 css::awt::Point
* pPointSequence
= rPointSequenceRetval
.getArray();
3513 css::drawing::PolygonFlags
* pFlagSequence
= rFlagSequenceRetval
.getArray();
3515 for(sal_uInt32
a(0); a
< nTargetCount
; a
++)
3517 *pPointSequence
= aCollectPoints
[a
];
3518 *pFlagSequence
= aCollectFlags
[a
];
3526 // straightforward point list creation
3527 const sal_uInt32
nTargetCount(nPointCount
+ (bClosed
? 1 : 0));
3529 rPointSequenceRetval
.realloc(static_cast<sal_Int32
>(nTargetCount
));
3530 rFlagSequenceRetval
.realloc(static_cast<sal_Int32
>(nTargetCount
));
3532 css::awt::Point
* pPointSequence
= rPointSequenceRetval
.getArray();
3533 css::drawing::PolygonFlags
* pFlagSequence
= rFlagSequenceRetval
.getArray();
3535 for(sal_uInt32
a(0); a
< nPointCount
; a
++)
3537 const B2DPoint
aB2DPoint(rPolygon
.getB2DPoint(a
));
3538 const css::awt::Point
aAPIPoint(
3539 fround(aB2DPoint
.getX()),
3540 fround(aB2DPoint
.getY()));
3542 *pPointSequence
= aAPIPoint
;
3543 *pFlagSequence
= css::drawing::PolygonFlags_NORMAL
;
3550 // add first point as closing point
3551 *pPointSequence
= rPointSequenceRetval
[0];
3552 *pFlagSequence
= css::drawing::PolygonFlags_NORMAL
;
3558 rPointSequenceRetval
.realloc(0);
3559 rFlagSequenceRetval
.realloc(0);
3563 B2DPolygon
createSimplifiedPolygon(const B2DPolygon
& rCandidate
, const double fTolerance
)
3565 const sal_uInt32
nPointCount(rCandidate
.count());
3566 if (nPointCount
< 3 || rCandidate
.areControlPointsUsed())
3569 // The solution does not use recursion directly, since this could lead to a stack overflow if
3570 // nPointCount is very large. Instead, an own stack is used. This does not contain points, but
3571 // pairs of low and high index of a range in rCandidate. A parallel boolean vector is used to note
3572 // whether a point of rCandidate belongs to the output polygon or not.
3573 std::vector
<bool> bIsKeptVec(nPointCount
, false);
3574 bIsKeptVec
[0] = true;
3575 bIsKeptVec
[nPointCount
- 1] = true;
3576 sal_uInt32 nKept
= 2;
3577 std::stack
<std::pair
<sal_uInt32
, sal_uInt32
>> aUnfinishedRangesStack
;
3579 // The RDP algorithm draws a straight line from the first point to the last point of a range.
3580 // Then, from the inner points of the range, the point that has the largest distance to the line
3581 // is determined. If the distance is greater than the tolerance, this point is kept and the lower
3582 // and upper sub-segments of the range are treated in the same way. If the distance is smaller
3583 // than the tolerance, none of the inner points will be kept.
3584 sal_uInt32 nLowIndex
= 0;
3585 sal_uInt32 nHighIndex
= nPointCount
- 1;
3586 bool bContinue
= true;
3590 if (nHighIndex
- nLowIndex
< 2) // maximal two points, range is finished.
3592 // continue with sibling upper range if any
3593 if (!aUnfinishedRangesStack
.empty())
3595 std::pair
<sal_uInt32
, sal_uInt32
> aIndexPair
= aUnfinishedRangesStack
.top();
3596 aUnfinishedRangesStack
.pop();
3597 nLowIndex
= aIndexPair
.first
;
3598 nHighIndex
= aIndexPair
.second
;
3602 else // the range needs examine the max distance
3604 // Get maximal distance of inner points of the range to the straight line from start to
3605 // end point of the range.
3606 // For calculation of the distance we use the Hesse normal form of the straight line.
3607 B2DPoint aLowPoint
= rCandidate
.getB2DPoint(nLowIndex
);
3608 B2DPoint aHighPoint
= rCandidate
.getB2DPoint(nHighIndex
);
3609 B2DVector aNormalVec
3610 = basegfx::getNormalizedPerpendicular(B2DVector(aHighPoint
) - B2DVector(aLowPoint
));
3611 double fLineOriginDistance
= aNormalVec
.scalar(B2DVector(aLowPoint
));
3612 double fMaxDist
= 0;
3613 sal_uInt32 nMaxPointIndex
= nLowIndex
;
3614 for (sal_uInt32 i
= nLowIndex
+ 1; i
< nHighIndex
; i
++)
3617 = aNormalVec
.scalar(B2DVector(rCandidate
.getB2DPoint(i
))) - fLineOriginDistance
;
3618 if (std::fabs(fDistance
) > fMaxDist
)
3620 fMaxDist
= std::fabs(fDistance
);
3625 if (fMaxDist
>= fTolerance
)
3627 // We need to divide the current range into two sub ranges.
3628 bIsKeptVec
[nMaxPointIndex
] = true;
3630 // continue with lower sub range and push upper sub range to stack
3631 aUnfinishedRangesStack
.push(std::make_pair(nMaxPointIndex
, nHighIndex
));
3632 nHighIndex
= nMaxPointIndex
;
3637 // We do not keep any of the inner points of the current range.
3638 // continue with sibling upper range if any
3639 if (!aUnfinishedRangesStack
.empty())
3641 std::pair
<sal_uInt32
, sal_uInt32
> aIndexPair
= aUnfinishedRangesStack
.top();
3642 aUnfinishedRangesStack
.pop();
3643 nLowIndex
= aIndexPair
.first
;
3644 nHighIndex
= aIndexPair
.second
;
3649 } while (bContinue
);
3651 // Put all points which are marked as "keep" into the result polygon
3652 B2DPolygon aResultPolygon
;
3653 aResultPolygon
.reserve(nKept
);
3654 for (sal_uInt32 i
= 0; i
< nPointCount
; i
++)
3657 aResultPolygon
.append(rCandidate
.getB2DPoint(i
));
3659 return aResultPolygon
;
3662 } // end of namespace
3664 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */