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 .
20 #include <basegfx/numeric/ftools.hxx>
21 #include <basegfx/polygon/b2dpolygontools.hxx>
22 #include <osl/diagnose.h>
23 #include <rtl/math.hxx>
24 #include <rtl/instance.hxx>
25 #include <sal/log.hxx>
26 #include <basegfx/polygon/b2dpolygon.hxx>
27 #include <basegfx/polygon/b2dpolypolygon.hxx>
28 #include <basegfx/range/b2drange.hxx>
29 #include <basegfx/curve/b2dcubicbezier.hxx>
30 #include <basegfx/polygon/b2dpolypolygoncutter.hxx>
31 #include <basegfx/point/b3dpoint.hxx>
32 #include <basegfx/matrix/b3dhommatrix.hxx>
33 #include <basegfx/matrix/b2dhommatrix.hxx>
34 #include <basegfx/curve/b2dbeziertools.hxx>
35 #include <basegfx/matrix/b2dhommatrixtools.hxx>
41 #define ANGLE_BOUND_START_VALUE (2.25)
42 #define ANGLE_BOUND_MINIMUM_VALUE (0.1)
44 static double fAngleBoundStartValue
= ANGLE_BOUND_START_VALUE
;
46 #define STEPSPERQUARTER (3)
52 void openWithGeometryChange(B2DPolygon
& rCandidate
)
54 if(rCandidate
.isClosed())
56 if(rCandidate
.count())
58 rCandidate
.append(rCandidate
.getB2DPoint(0));
60 if(rCandidate
.areControlPointsUsed() && rCandidate
.isPrevControlPointUsed(0))
62 rCandidate
.setPrevControlPoint(rCandidate
.count() - 1, rCandidate
.getPrevControlPoint(0));
63 rCandidate
.resetPrevControlPoint(0);
67 rCandidate
.setClosed(false);
71 void closeWithGeometryChange(B2DPolygon
& rCandidate
)
73 if(!rCandidate
.isClosed())
75 while(rCandidate
.count() > 1 && rCandidate
.getB2DPoint(0) == rCandidate
.getB2DPoint(rCandidate
.count() - 1))
77 if(rCandidate
.areControlPointsUsed() && rCandidate
.isPrevControlPointUsed(rCandidate
.count() - 1))
79 rCandidate
.setPrevControlPoint(0, rCandidate
.getPrevControlPoint(rCandidate
.count() - 1));
82 rCandidate
.remove(rCandidate
.count() - 1);
85 rCandidate
.setClosed(true);
89 void checkClosed(B2DPolygon
& rCandidate
)
91 // #i80172# Removed unnecessary assertion
92 // OSL_ENSURE(!rCandidate.isClosed(), "checkClosed: already closed (!)");
94 if(rCandidate
.count() > 1 && rCandidate
.getB2DPoint(0) == rCandidate
.getB2DPoint(rCandidate
.count() - 1))
96 closeWithGeometryChange(rCandidate
);
100 // Get successor and predecessor indices. Returning the same index means there
101 // is none. Same for successor.
102 sal_uInt32
getIndexOfPredecessor(sal_uInt32 nIndex
, const B2DPolygon
& rCandidate
)
104 OSL_ENSURE(nIndex
< rCandidate
.count(), "getIndexOfPredecessor: Access to polygon out of range (!)");
110 else if(rCandidate
.count())
112 return rCandidate
.count() - 1;
120 sal_uInt32
getIndexOfSuccessor(sal_uInt32 nIndex
, const B2DPolygon
& rCandidate
)
122 OSL_ENSURE(nIndex
< rCandidate
.count(), "getIndexOfPredecessor: Access to polygon out of range (!)");
124 if(nIndex
+ 1 < rCandidate
.count())
128 else if(nIndex
+ 1 == rCandidate
.count())
138 B2VectorOrientation
getOrientation(const B2DPolygon
& rCandidate
)
140 B2VectorOrientation
eRetval(B2VectorOrientation::Neutral
);
142 if(rCandidate
.count() > 2 || rCandidate
.areControlPointsUsed())
144 const double fSignedArea(getSignedArea(rCandidate
));
146 if(fTools::equalZero(fSignedArea
))
148 // B2VectorOrientation::Neutral, already set
150 if(fSignedArea
> 0.0)
152 eRetval
= B2VectorOrientation::Positive
;
154 else if(fSignedArea
< 0.0)
156 eRetval
= B2VectorOrientation::Negative
;
163 B2VectorContinuity
getContinuityInPoint(const B2DPolygon
& rCandidate
, sal_uInt32 nIndex
)
165 return rCandidate
.getContinuityInPoint(nIndex
);
168 B2DPolygon
adaptiveSubdivideByDistance(const B2DPolygon
& rCandidate
, double fDistanceBound
)
170 if(rCandidate
.areControlPointsUsed())
172 const sal_uInt32
nPointCount(rCandidate
.count());
177 // prepare edge-oriented loop
178 const sal_uInt32
nEdgeCount(rCandidate
.isClosed() ? nPointCount
: nPointCount
- 1);
179 B2DCubicBezier aBezier
;
180 aBezier
.setStartPoint(rCandidate
.getB2DPoint(0));
182 // perf: try to avoid too many realloctions by guessing the result's pointcount
183 aRetval
.reserve(nPointCount
*4);
185 // add start point (always)
186 aRetval
.append(aBezier
.getStartPoint());
188 for(sal_uInt32
a(0); a
< nEdgeCount
; a
++)
190 // get next and control points
191 const sal_uInt32
nNextIndex((a
+ 1) % nPointCount
);
192 aBezier
.setEndPoint(rCandidate
.getB2DPoint(nNextIndex
));
193 aBezier
.setControlPointA(rCandidate
.getNextControlPoint(a
));
194 aBezier
.setControlPointB(rCandidate
.getPrevControlPoint(nNextIndex
));
195 aBezier
.testAndSolveTrivialBezier();
197 if(aBezier
.isBezier())
199 // add curved edge and generate DistanceBound
202 if(fDistanceBound
== 0.0)
204 // If not set, use B2DCubicBezier functionality to guess a rough value
205 const double fRoughLength((aBezier
.getEdgeLength() + aBezier
.getControlPolygonLength()) / 2.0);
207 // take 1/100th of the rough curve length
208 fBound
= fRoughLength
* 0.01;
212 // use given bound value
213 fBound
= fDistanceBound
;
216 // make sure bound value is not too small. The base units are 1/100th mm, thus
217 // just make sure it's not smaller then 1/100th of that
223 // call adaptive subdivide which adds edges to aRetval accordingly
224 aBezier
.adaptiveSubdivideByDistance(aRetval
, fBound
);
228 // add non-curved edge
229 aRetval
.append(aBezier
.getEndPoint());
233 aBezier
.setStartPoint(aBezier
.getEndPoint());
236 if(rCandidate
.isClosed())
238 // set closed flag and correct last point (which is added double now).
239 closeWithGeometryChange(aRetval
);
251 B2DPolygon
adaptiveSubdivideByAngle(const B2DPolygon
& rCandidate
, double fAngleBound
)
253 if(rCandidate
.areControlPointsUsed())
255 const sal_uInt32
nPointCount(rCandidate
.count());
260 // prepare edge-oriented loop
261 const sal_uInt32
nEdgeCount(rCandidate
.isClosed() ? nPointCount
: nPointCount
- 1);
262 B2DCubicBezier aBezier
;
263 aBezier
.setStartPoint(rCandidate
.getB2DPoint(0));
265 // perf: try to avoid too many realloctions by guessing the result's pointcount
266 aRetval
.reserve(nPointCount
*4);
268 // add start point (always)
269 aRetval
.append(aBezier
.getStartPoint());
271 // #i37443# prepare convenient AngleBound if none was given
272 if(fAngleBound
== 0.0)
275 fAngleBound
= fAngleBoundStartValue
;
277 fAngleBound
= ANGLE_BOUND_START_VALUE
;
280 else if(fTools::less(fAngleBound
, ANGLE_BOUND_MINIMUM_VALUE
))
285 for(sal_uInt32
a(0); a
< nEdgeCount
; a
++)
287 // get next and control points
288 const sal_uInt32
nNextIndex((a
+ 1) % nPointCount
);
289 aBezier
.setEndPoint(rCandidate
.getB2DPoint(nNextIndex
));
290 aBezier
.setControlPointA(rCandidate
.getNextControlPoint(a
));
291 aBezier
.setControlPointB(rCandidate
.getPrevControlPoint(nNextIndex
));
292 aBezier
.testAndSolveTrivialBezier();
294 if(aBezier
.isBezier())
296 // call adaptive subdivide
297 aBezier
.adaptiveSubdivideByAngle(aRetval
, fAngleBound
);
301 // add non-curved edge
302 aRetval
.append(aBezier
.getEndPoint());
306 aBezier
.setStartPoint(aBezier
.getEndPoint());
309 if(rCandidate
.isClosed())
311 // set closed flag and correct last point (which is added double now).
312 closeWithGeometryChange(aRetval
);
324 bool isInside(const B2DPolygon
& rCandidate
, const B2DPoint
& rPoint
, bool bWithBorder
)
326 const B2DPolygon
aCandidate(rCandidate
.areControlPointsUsed() ? rCandidate
.getDefaultAdaptiveSubdivision() : rCandidate
);
328 if(bWithBorder
&& isPointOnPolygon(aCandidate
, rPoint
))
335 const sal_uInt32
nPointCount(aCandidate
.count());
339 B2DPoint
aCurrentPoint(aCandidate
.getB2DPoint(nPointCount
- 1));
341 for(sal_uInt32
a(0); a
< nPointCount
; a
++)
343 const B2DPoint
aPreviousPoint(aCurrentPoint
);
344 aCurrentPoint
= aCandidate
.getB2DPoint(a
);
347 const bool bCompYA(fTools::more(aPreviousPoint
.getY(), rPoint
.getY()));
348 const bool bCompYB(fTools::more(aCurrentPoint
.getY(), rPoint
.getY()));
350 if(bCompYA
!= bCompYB
)
353 const bool bCompXA(fTools::more(aPreviousPoint
.getX(), rPoint
.getX()));
354 const bool bCompXB(fTools::more(aCurrentPoint
.getX(), rPoint
.getX()));
356 if(bCompXA
== bCompXB
)
365 const double fCompare(
366 aCurrentPoint
.getX() - (aCurrentPoint
.getY() - rPoint
.getY()) *
367 (aPreviousPoint
.getX() - aCurrentPoint
.getX()) /
368 (aPreviousPoint
.getY() - aCurrentPoint
.getY()));
370 if(fTools::more(fCompare
, rPoint
.getX()))
383 bool isInside(const B2DPolygon
& rCandidate
, const B2DPolygon
& rPolygon
, bool bWithBorder
)
385 const B2DPolygon
aCandidate(rCandidate
.areControlPointsUsed() ? rCandidate
.getDefaultAdaptiveSubdivision() : rCandidate
);
386 const B2DPolygon
aPolygon(rPolygon
.areControlPointsUsed() ? rPolygon
.getDefaultAdaptiveSubdivision() : rPolygon
);
387 const sal_uInt32
nPointCount(aPolygon
.count());
389 for(sal_uInt32
a(0); a
< nPointCount
; a
++)
391 const B2DPoint
aTestPoint(aPolygon
.getB2DPoint(a
));
393 if(!isInside(aCandidate
, aTestPoint
, bWithBorder
))
402 B2DRange
getRange(const B2DPolygon
& rCandidate
)
404 // changed to use internally buffered version at B2DPolygon
405 return rCandidate
.getB2DRange();
408 double getSignedArea(const B2DPolygon
& rCandidate
)
410 const B2DPolygon
aCandidate(rCandidate
.areControlPointsUsed() ? rCandidate
.getDefaultAdaptiveSubdivision() : rCandidate
);
412 const sal_uInt32
nPointCount(aCandidate
.count());
416 for(sal_uInt32
a(0); a
< nPointCount
; a
++)
418 const B2DPoint
aPreviousPoint(aCandidate
.getB2DPoint((!a
) ? nPointCount
- 1 : a
- 1));
419 const B2DPoint
aCurrentPoint(aCandidate
.getB2DPoint(a
));
421 fRetval
+= aPreviousPoint
.getX() * aCurrentPoint
.getY();
422 fRetval
-= aPreviousPoint
.getY() * aCurrentPoint
.getX();
425 // correct to zero if small enough. Also test the quadratic
426 // of the result since the precision is near quadratic due to
428 if(fTools::equalZero(fRetval
) || fTools::equalZero(fRetval
* fRetval
))
437 double getArea(const B2DPolygon
& rCandidate
)
441 if(rCandidate
.count() > 2 || rCandidate
.areControlPointsUsed())
443 fRetval
= getSignedArea(rCandidate
);
444 const double fZero(0.0);
446 if(fTools::less(fRetval
, fZero
))
455 double getEdgeLength(const B2DPolygon
& rCandidate
, sal_uInt32 nIndex
)
457 const sal_uInt32
nPointCount(rCandidate
.count());
458 OSL_ENSURE(nIndex
< nPointCount
, "getEdgeLength: Access to polygon out of range (!)");
463 const sal_uInt32
nNextIndex((nIndex
+ 1) % nPointCount
);
465 if(rCandidate
.areControlPointsUsed())
467 B2DCubicBezier aEdge
;
469 aEdge
.setStartPoint(rCandidate
.getB2DPoint(nIndex
));
470 aEdge
.setControlPointA(rCandidate
.getNextControlPoint(nIndex
));
471 aEdge
.setControlPointB(rCandidate
.getPrevControlPoint(nNextIndex
));
472 aEdge
.setEndPoint(rCandidate
.getB2DPoint(nNextIndex
));
474 fRetval
= aEdge
.getLength();
478 const B2DPoint
aCurrent(rCandidate
.getB2DPoint(nIndex
));
479 const B2DPoint
aNext(rCandidate
.getB2DPoint(nNextIndex
));
481 fRetval
= B2DVector(aNext
- aCurrent
).getLength();
488 double getLength(const B2DPolygon
& rCandidate
)
491 const sal_uInt32
nPointCount(rCandidate
.count());
495 const sal_uInt32
nEdgeCount(rCandidate
.isClosed() ? nPointCount
: nPointCount
- 1);
497 if(rCandidate
.areControlPointsUsed())
499 B2DCubicBezier aEdge
;
500 aEdge
.setStartPoint(rCandidate
.getB2DPoint(0));
502 for(sal_uInt32
a(0); a
< nEdgeCount
; a
++)
504 const sal_uInt32
nNextIndex((a
+ 1) % nPointCount
);
505 aEdge
.setControlPointA(rCandidate
.getNextControlPoint(a
));
506 aEdge
.setControlPointB(rCandidate
.getPrevControlPoint(nNextIndex
));
507 aEdge
.setEndPoint(rCandidate
.getB2DPoint(nNextIndex
));
509 fRetval
+= aEdge
.getLength();
510 aEdge
.setStartPoint(aEdge
.getEndPoint());
515 B2DPoint
aCurrent(rCandidate
.getB2DPoint(0));
517 for(sal_uInt32
a(0); a
< nEdgeCount
; a
++)
519 const sal_uInt32
nNextIndex((a
+ 1) % nPointCount
);
520 const B2DPoint
aNext(rCandidate
.getB2DPoint(nNextIndex
));
522 fRetval
+= B2DVector(aNext
- aCurrent
).getLength();
531 B2DPoint
getPositionAbsolute(const B2DPolygon
& rCandidate
, double fDistance
, double fLength
)
534 const sal_uInt32
nPointCount(rCandidate
.count());
536 if( nPointCount
== 1 )
538 // only one point (i.e. no edge) - simply take that point
539 aRetval
= rCandidate
.getB2DPoint(0);
541 else if(nPointCount
> 1)
543 const sal_uInt32
nEdgeCount(rCandidate
.isClosed() ? nPointCount
: nPointCount
- 1);
544 sal_uInt32
nIndex(0);
545 bool bIndexDone(false);
547 // get length if not given
548 if(fTools::equalZero(fLength
))
550 fLength
= getLength(rCandidate
);
553 if(fTools::less(fDistance
, 0.0))
555 // handle fDistance < 0.0
556 if(rCandidate
.isClosed())
558 // if fDistance < 0.0 increment with multiple of fLength
559 sal_uInt32
nCount(sal_uInt32(-fDistance
/ fLength
));
560 fDistance
+= double(nCount
+ 1) * fLength
;
564 // crop to polygon start
569 else if(fTools::moreOrEqual(fDistance
, fLength
))
571 // handle fDistance >= fLength
572 if(rCandidate
.isClosed())
574 // if fDistance >= fLength decrement with multiple of fLength
575 sal_uInt32
nCount(sal_uInt32(fDistance
/ fLength
));
576 fDistance
-= static_cast<double>(nCount
) * fLength
;
580 // crop to polygon end
587 // look for correct index. fDistance is now [0.0 .. fLength[
588 double fEdgeLength(getEdgeLength(rCandidate
, nIndex
));
592 // edge found must be on the half-open range
594 // Note that in theory, we cannot move beyond
595 // the last polygon point, since fDistance>=fLength
596 // is checked above. Unfortunately, with floating-
597 // point calculations, this case might happen.
598 // Handled by nIndex check below
599 if (nIndex
+1 < nEdgeCount
&& fTools::moreOrEqual(fDistance
, fEdgeLength
))
602 fDistance
-= fEdgeLength
;
603 fEdgeLength
= getEdgeLength(rCandidate
, ++nIndex
);
607 // it's on this edge, stop
612 // get the point using nIndex
613 aRetval
= rCandidate
.getB2DPoint(nIndex
);
615 // if fDistance != 0.0, move that length on the edge. The edge
616 // length is in fEdgeLength.
617 if(!fTools::equalZero(fDistance
))
619 if(fTools::moreOrEqual(fDistance
, fEdgeLength
))
621 // end point of chosen edge
622 const sal_uInt32
nNextIndex((nIndex
+ 1) % nPointCount
);
623 aRetval
= rCandidate
.getB2DPoint(nNextIndex
);
625 else if(fTools::equalZero(fDistance
))
627 // start point of chosen edge
632 const sal_uInt32
nNextIndex((nIndex
+ 1) % nPointCount
);
633 const B2DPoint
aNextPoint(rCandidate
.getB2DPoint(nNextIndex
));
636 // add calculated average value to the return value
637 if(rCandidate
.areControlPointsUsed())
639 // get as bezier segment
640 const B2DCubicBezier
aBezierSegment(
641 aRetval
, rCandidate
.getNextControlPoint(nIndex
),
642 rCandidate
.getPrevControlPoint(nNextIndex
), aNextPoint
);
644 if(aBezierSegment
.isBezier())
646 // use B2DCubicBezierHelper to bridge the non-linear gap between
647 // length and bezier distances
648 const B2DCubicBezierHelper
aBezierSegmentHelper(aBezierSegment
);
649 const double fBezierDistance(aBezierSegmentHelper
.distanceToRelative(fDistance
));
651 aRetval
= aBezierSegment
.interpolatePoint(fBezierDistance
);
658 const double fRelativeInEdge(fDistance
/ fEdgeLength
);
659 aRetval
= interpolate(aRetval
, aNextPoint
, fRelativeInEdge
);
668 B2DPoint
getPositionRelative(const B2DPolygon
& rCandidate
, double fDistance
, double fLength
)
670 // get length if not given
671 if(fTools::equalZero(fLength
))
673 fLength
= getLength(rCandidate
);
676 // multiply fDistance with real length to get absolute position and
677 // use getPositionAbsolute
678 return getPositionAbsolute(rCandidate
, fDistance
* fLength
, fLength
);
681 B2DPolygon
getSnippetAbsolute(const B2DPolygon
& rCandidate
, double fFrom
, double fTo
, double fLength
)
683 const sal_uInt32
nPointCount(rCandidate
.count());
687 // get length if not given
688 if(fTools::equalZero(fLength
))
690 fLength
= getLength(rCandidate
);
693 // test and correct fFrom
694 if(fTools::less(fFrom
, 0.0))
699 // test and correct fTo
700 if(fTools::more(fTo
, fLength
))
705 // test and correct relationship of fFrom, fTo
706 if(fTools::more(fFrom
, fTo
))
708 fFrom
= fTo
= (fFrom
+ fTo
) / 2.0;
711 if(fTools::equalZero(fFrom
) && fTools::equal(fTo
, fLength
))
713 // no change, result is the whole polygon
719 const sal_uInt32
nEdgeCount(rCandidate
.isClosed() ? nPointCount
: nPointCount
- 1);
720 double fPositionOfStart(0.0);
721 bool bStartDone(false);
722 bool bEndDone(false);
724 for(sal_uInt32
a(0); !(bStartDone
&& bEndDone
) && a
< nEdgeCount
; a
++)
726 const double fEdgeLength(getEdgeLength(rCandidate
, a
));
730 if(fTools::equalZero(fFrom
))
732 aRetval
.append(rCandidate
.getB2DPoint(a
));
734 if(rCandidate
.areControlPointsUsed())
736 aRetval
.setNextControlPoint(aRetval
.count() - 1, rCandidate
.getNextControlPoint(a
));
741 else if(fTools::moreOrEqual(fFrom
, fPositionOfStart
) && fTools::less(fFrom
, fPositionOfStart
+ fEdgeLength
))
743 // calculate and add start point
744 if(fTools::equalZero(fEdgeLength
))
746 aRetval
.append(rCandidate
.getB2DPoint(a
));
748 if(rCandidate
.areControlPointsUsed())
750 aRetval
.setNextControlPoint(aRetval
.count() - 1, rCandidate
.getNextControlPoint(a
));
755 const sal_uInt32
nNextIndex((a
+ 1) % nPointCount
);
756 const B2DPoint
aStart(rCandidate
.getB2DPoint(a
));
757 const B2DPoint
aEnd(rCandidate
.getB2DPoint(nNextIndex
));
760 if(rCandidate
.areControlPointsUsed())
762 const B2DCubicBezier
aBezierSegment(
763 aStart
, rCandidate
.getNextControlPoint(a
),
764 rCandidate
.getPrevControlPoint(nNextIndex
), aEnd
);
766 if(aBezierSegment
.isBezier())
768 // use B2DCubicBezierHelper to bridge the non-linear gap between
769 // length and bezier distances
770 const B2DCubicBezierHelper
aBezierSegmentHelper(aBezierSegment
);
771 const double fBezierDistance(aBezierSegmentHelper
.distanceToRelative(fFrom
- fPositionOfStart
));
772 B2DCubicBezier aRight
;
774 aBezierSegment
.split(fBezierDistance
, nullptr, &aRight
);
775 aRetval
.append(aRight
.getStartPoint());
776 aRetval
.setNextControlPoint(aRetval
.count() - 1, aRight
.getControlPointA());
783 const double fRelValue((fFrom
- fPositionOfStart
) / fEdgeLength
);
784 aRetval
.append(interpolate(aStart
, aEnd
, fRelValue
));
790 // if same point, end is done, too.
791 if(rtl::math::approxEqual(fFrom
, fTo
))
798 if(!bEndDone
&& fTools::moreOrEqual(fTo
, fPositionOfStart
) && fTools::less(fTo
, fPositionOfStart
+ fEdgeLength
))
800 // calculate and add end point
801 if(fTools::equalZero(fEdgeLength
))
803 const sal_uInt32
nNextIndex((a
+ 1) % nPointCount
);
804 aRetval
.append(rCandidate
.getB2DPoint(nNextIndex
));
806 if(rCandidate
.areControlPointsUsed())
808 aRetval
.setPrevControlPoint(aRetval
.count() - 1, rCandidate
.getPrevControlPoint(nNextIndex
));
813 const sal_uInt32
nNextIndex((a
+ 1) % nPointCount
);
814 const B2DPoint
aStart(rCandidate
.getB2DPoint(a
));
815 const B2DPoint
aEnd(rCandidate
.getB2DPoint(nNextIndex
));
818 if(rCandidate
.areControlPointsUsed())
820 const B2DCubicBezier
aBezierSegment(
821 aStart
, rCandidate
.getNextControlPoint(a
),
822 rCandidate
.getPrevControlPoint(nNextIndex
), aEnd
);
824 if(aBezierSegment
.isBezier())
826 // use B2DCubicBezierHelper to bridge the non-linear gap between
827 // length and bezier distances
828 const B2DCubicBezierHelper
aBezierSegmentHelper(aBezierSegment
);
829 const double fBezierDistance(aBezierSegmentHelper
.distanceToRelative(fTo
- fPositionOfStart
));
830 B2DCubicBezier aLeft
;
832 aBezierSegment
.split(fBezierDistance
, &aLeft
, nullptr);
833 aRetval
.append(aLeft
.getEndPoint());
834 aRetval
.setPrevControlPoint(aRetval
.count() - 1, aLeft
.getControlPointB());
841 const double fRelValue((fTo
- fPositionOfStart
) / fEdgeLength
);
842 aRetval
.append(interpolate(aStart
, aEnd
, fRelValue
));
853 // add segments end point
854 const sal_uInt32
nNextIndex((a
+ 1) % nPointCount
);
855 aRetval
.append(rCandidate
.getB2DPoint(nNextIndex
));
857 if(rCandidate
.areControlPointsUsed())
859 aRetval
.setPrevControlPoint(aRetval
.count() - 1, rCandidate
.getPrevControlPoint(nNextIndex
));
860 aRetval
.setNextControlPoint(aRetval
.count() - 1, rCandidate
.getNextControlPoint(nNextIndex
));
864 // increment fPositionOfStart
865 fPositionOfStart
+= fEdgeLength
;
877 CutFlagValue
findCut(
878 const B2DPoint
& rEdge1Start
, const B2DVector
& rEdge1Delta
,
879 const B2DPoint
& rEdge2Start
, const B2DVector
& rEdge2Delta
,
880 CutFlagValue aCutFlags
,
881 double* pCut1
, double* pCut2
)
883 CutFlagValue
aRetval(CutFlagValue::NONE
);
886 bool bFinished(!static_cast<bool>(aCutFlags
& CutFlagValue::ALL
));
888 // test for same points?
890 && (aCutFlags
& (CutFlagValue::START1
|CutFlagValue::END1
))
891 && (aCutFlags
& (CutFlagValue::START2
|CutFlagValue::END2
)))
894 if((aCutFlags
& (CutFlagValue::START1
|CutFlagValue::START2
)) == (CutFlagValue::START1
|CutFlagValue::START2
))
896 if(rEdge1Start
.equal(rEdge2Start
))
899 aRetval
= (CutFlagValue::START1
|CutFlagValue::START2
);
904 if(!bFinished
&& (aCutFlags
& (CutFlagValue::END1
|CutFlagValue::END2
)) == (CutFlagValue::END1
|CutFlagValue::END2
))
906 const B2DPoint
aEnd1(rEdge1Start
+ rEdge1Delta
);
907 const B2DPoint
aEnd2(rEdge2Start
+ rEdge2Delta
);
909 if(aEnd1
.equal(aEnd2
))
912 aRetval
= (CutFlagValue::END1
|CutFlagValue::END2
);
917 // startpoint1 == endpoint2?
918 if(!bFinished
&& (aCutFlags
& (CutFlagValue::START1
|CutFlagValue::END2
)) == (CutFlagValue::START1
|CutFlagValue::END2
))
920 const B2DPoint
aEnd2(rEdge2Start
+ rEdge2Delta
);
922 if(rEdge1Start
.equal(aEnd2
))
925 aRetval
= (CutFlagValue::START1
|CutFlagValue::END2
);
931 // startpoint2 == endpoint1?
932 if(!bFinished
&& (aCutFlags
& (CutFlagValue::START2
|CutFlagValue::END1
)) == (CutFlagValue::START2
|CutFlagValue::END1
))
934 const B2DPoint
aEnd1(rEdge1Start
+ rEdge1Delta
);
936 if(rEdge2Start
.equal(aEnd1
))
939 aRetval
= (CutFlagValue::START2
|CutFlagValue::END1
);
946 if(!bFinished
&& (aCutFlags
& CutFlagValue::LINE
))
948 if(aCutFlags
& CutFlagValue::START1
)
950 // start1 on line 2 ?
951 if(isPointOnEdge(rEdge1Start
, rEdge2Start
, rEdge2Delta
, &fCut2
))
954 aRetval
= (CutFlagValue::LINE
|CutFlagValue::START1
);
958 if(!bFinished
&& (aCutFlags
& CutFlagValue::START2
))
960 // start2 on line 1 ?
961 if(isPointOnEdge(rEdge2Start
, rEdge1Start
, rEdge1Delta
, &fCut1
))
964 aRetval
= (CutFlagValue::LINE
|CutFlagValue::START2
);
968 if(!bFinished
&& (aCutFlags
& CutFlagValue::END1
))
971 const B2DPoint
aEnd1(rEdge1Start
+ rEdge1Delta
);
973 if(isPointOnEdge(aEnd1
, rEdge2Start
, rEdge2Delta
, &fCut2
))
976 aRetval
= (CutFlagValue::LINE
|CutFlagValue::END1
);
980 if(!bFinished
&& (aCutFlags
& CutFlagValue::END2
))
983 const B2DPoint
aEnd2(rEdge2Start
+ rEdge2Delta
);
985 if(isPointOnEdge(aEnd2
, rEdge1Start
, rEdge1Delta
, &fCut1
))
988 aRetval
= (CutFlagValue::LINE
|CutFlagValue::END2
);
994 // cut in line1, line2 ?
995 fCut1
= (rEdge1Delta
.getX() * rEdge2Delta
.getY()) - (rEdge1Delta
.getY() * rEdge2Delta
.getX());
997 if(!fTools::equalZero(fCut1
))
999 fCut1
= (rEdge2Delta
.getY() * (rEdge2Start
.getX() - rEdge1Start
.getX())
1000 + rEdge2Delta
.getX() * (rEdge1Start
.getY() - rEdge2Start
.getY())) / fCut1
;
1002 const double fZero(0.0);
1003 const double fOne(1.0);
1005 // inside parameter range edge1 AND fCut2 is calculable
1006 if(fTools::more(fCut1
, fZero
) && fTools::less(fCut1
, fOne
)
1007 && (!fTools::equalZero(rEdge2Delta
.getX()) || !fTools::equalZero(rEdge2Delta
.getY())))
1009 // take the more precise calculation of the two possible
1010 if(fabs(rEdge2Delta
.getX()) > fabs(rEdge2Delta
.getY()))
1012 fCut2
= (rEdge1Start
.getX() + fCut1
1013 * rEdge1Delta
.getX() - rEdge2Start
.getX()) / rEdge2Delta
.getX();
1017 fCut2
= (rEdge1Start
.getY() + fCut1
1018 * rEdge1Delta
.getY() - rEdge2Start
.getY()) / rEdge2Delta
.getY();
1021 // inside parameter range edge2, too
1022 if(fTools::more(fCut2
, fZero
) && fTools::less(fCut2
, fOne
))
1024 aRetval
= CutFlagValue::LINE
;
1031 // copy values if wanted
1046 const B2DPoint
& rPoint
,
1047 const B2DPoint
& rEdgeStart
,
1048 const B2DVector
& rEdgeDelta
,
1051 bool bDeltaXIsZero(fTools::equalZero(rEdgeDelta
.getX()));
1052 bool bDeltaYIsZero(fTools::equalZero(rEdgeDelta
.getY()));
1053 const double fZero(0.0);
1054 const double fOne(1.0);
1056 if(bDeltaXIsZero
&& bDeltaYIsZero
)
1058 // no line, just a point
1061 else if(bDeltaXIsZero
)
1064 if(fTools::equal(rPoint
.getX(), rEdgeStart
.getX()))
1066 double fValue
= (rPoint
.getY() - rEdgeStart
.getY()) / rEdgeDelta
.getY();
1068 if(fTools::more(fValue
, fZero
) && fTools::less(fValue
, fOne
))
1079 else if(bDeltaYIsZero
)
1082 if(fTools::equal(rPoint
.getY(), rEdgeStart
.getY()))
1084 double fValue
= (rPoint
.getX() - rEdgeStart
.getX()) / rEdgeDelta
.getX();
1086 if(fTools::more(fValue
, fZero
) && fTools::less(fValue
, fOne
))
1100 double fTOne
= (rPoint
.getX() - rEdgeStart
.getX()) / rEdgeDelta
.getX();
1101 double fTTwo
= (rPoint
.getY() - rEdgeStart
.getY()) / rEdgeDelta
.getY();
1103 if(fTools::equal(fTOne
, fTTwo
))
1105 // same parameter representation, point is on line. Take
1106 // middle value for better results
1107 double fValue
= (fTOne
+ fTTwo
) / 2.0;
1109 if(fTools::more(fValue
, fZero
) && fTools::less(fValue
, fOne
))
1111 // point is inside line bounds, too
1125 void applyLineDashing(const B2DPolygon
& rCandidate
, const std::vector
<double>& rDotDashArray
, B2DPolyPolygon
* pLineTarget
, B2DPolyPolygon
* pGapTarget
, double fDotDashLength
)
1127 const sal_uInt32
nPointCount(rCandidate
.count());
1128 const sal_uInt32
nDotDashCount(rDotDashArray
.size());
1130 if(fTools::lessOrEqual(fDotDashLength
, 0.0))
1132 fDotDashLength
= std::accumulate(rDotDashArray
.begin(), rDotDashArray
.end(), 0.0);
1135 if(fTools::more(fDotDashLength
, 0.0) && (pLineTarget
|| pGapTarget
) && nPointCount
)
1140 pLineTarget
->clear();
1145 pGapTarget
->clear();
1148 // prepare current edge's start
1149 B2DCubicBezier aCurrentEdge
;
1150 const sal_uInt32
nEdgeCount(rCandidate
.isClosed() ? nPointCount
: nPointCount
- 1);
1151 aCurrentEdge
.setStartPoint(rCandidate
.getB2DPoint(0));
1153 // prepare DotDashArray iteration and the line/gap switching bool
1154 sal_uInt32
nDotDashIndex(0);
1156 double fDotDashMovingLength(rDotDashArray
[0]);
1157 B2DPolygon aSnippet
;
1159 // iterate over all edges
1160 for(sal_uInt32
a(0); a
< nEdgeCount
; a
++)
1162 // update current edge (fill in C1, C2 and end point)
1163 double fLastDotDashMovingLength(0.0);
1164 const sal_uInt32
nNextIndex((a
+ 1) % nPointCount
);
1165 aCurrentEdge
.setControlPointA(rCandidate
.getNextControlPoint(a
));
1166 aCurrentEdge
.setControlPointB(rCandidate
.getPrevControlPoint(nNextIndex
));
1167 aCurrentEdge
.setEndPoint(rCandidate
.getB2DPoint(nNextIndex
));
1169 // check if we have a trivial bezier segment -> possible fallback to edge
1170 aCurrentEdge
.testAndSolveTrivialBezier();
1172 if(aCurrentEdge
.isBezier())
1175 const B2DCubicBezierHelper
aCubicBezierHelper(aCurrentEdge
);
1176 const double fEdgeLength(aCubicBezierHelper
.getLength());
1178 if(!fTools::equalZero(fEdgeLength
))
1180 while(fTools::less(fDotDashMovingLength
, fEdgeLength
))
1182 // new split is inside edge, create and append snippet [fLastDotDashMovingLength, fDotDashMovingLength]
1183 const bool bHandleLine(bIsLine
&& pLineTarget
);
1184 const bool bHandleGap(!bIsLine
&& pGapTarget
);
1186 if(bHandleLine
|| bHandleGap
)
1188 const double fBezierSplitStart(aCubicBezierHelper
.distanceToRelative(fLastDotDashMovingLength
));
1189 const double fBezierSplitEnd(aCubicBezierHelper
.distanceToRelative(fDotDashMovingLength
));
1190 B2DCubicBezier
aBezierSnippet(aCurrentEdge
.snippet(fBezierSplitStart
, fBezierSplitEnd
));
1192 if(!aSnippet
.count())
1194 aSnippet
.append(aBezierSnippet
.getStartPoint());
1197 aSnippet
.appendBezierSegment(aBezierSnippet
.getControlPointA(), aBezierSnippet
.getControlPointB(), aBezierSnippet
.getEndPoint());
1201 pLineTarget
->append(aSnippet
);
1205 pGapTarget
->append(aSnippet
);
1211 // prepare next DotDashArray step and flip line/gap flag
1212 fLastDotDashMovingLength
= fDotDashMovingLength
;
1213 fDotDashMovingLength
+= rDotDashArray
[(++nDotDashIndex
) % nDotDashCount
];
1217 // append closing snippet [fLastDotDashMovingLength, fEdgeLength]
1218 const bool bHandleLine(bIsLine
&& pLineTarget
);
1219 const bool bHandleGap(!bIsLine
&& pGapTarget
);
1221 if(bHandleLine
|| bHandleGap
)
1223 B2DCubicBezier aRight
;
1224 const double fBezierSplit(aCubicBezierHelper
.distanceToRelative(fLastDotDashMovingLength
));
1226 aCurrentEdge
.split(fBezierSplit
, nullptr, &aRight
);
1228 if(!aSnippet
.count())
1230 aSnippet
.append(aRight
.getStartPoint());
1233 aSnippet
.appendBezierSegment(aRight
.getControlPointA(), aRight
.getControlPointB(), aRight
.getEndPoint());
1236 // prepare move to next edge
1237 fDotDashMovingLength
-= fEdgeLength
;
1243 const double fEdgeLength(aCurrentEdge
.getEdgeLength());
1245 if(!fTools::equalZero(fEdgeLength
))
1247 while(fTools::less(fDotDashMovingLength
, fEdgeLength
))
1249 // new split is inside edge, create and append snippet [fLastDotDashMovingLength, fDotDashMovingLength]
1250 const bool bHandleLine(bIsLine
&& pLineTarget
);
1251 const bool bHandleGap(!bIsLine
&& pGapTarget
);
1253 if(bHandleLine
|| bHandleGap
)
1255 if(!aSnippet
.count())
1257 aSnippet
.append(interpolate(aCurrentEdge
.getStartPoint(), aCurrentEdge
.getEndPoint(), fLastDotDashMovingLength
/ fEdgeLength
));
1260 aSnippet
.append(interpolate(aCurrentEdge
.getStartPoint(), aCurrentEdge
.getEndPoint(), fDotDashMovingLength
/ fEdgeLength
));
1264 pLineTarget
->append(aSnippet
);
1268 pGapTarget
->append(aSnippet
);
1274 // prepare next DotDashArray step and flip line/gap flag
1275 fLastDotDashMovingLength
= fDotDashMovingLength
;
1276 fDotDashMovingLength
+= rDotDashArray
[(++nDotDashIndex
) % nDotDashCount
];
1280 // append snippet [fLastDotDashMovingLength, fEdgeLength]
1281 const bool bHandleLine(bIsLine
&& pLineTarget
);
1282 const bool bHandleGap(!bIsLine
&& pGapTarget
);
1284 if(bHandleLine
|| bHandleGap
)
1286 if(!aSnippet
.count())
1288 aSnippet
.append(interpolate(aCurrentEdge
.getStartPoint(), aCurrentEdge
.getEndPoint(), fLastDotDashMovingLength
/ fEdgeLength
));
1291 aSnippet
.append(aCurrentEdge
.getEndPoint());
1294 // prepare move to next edge
1295 fDotDashMovingLength
-= fEdgeLength
;
1299 // prepare next edge step (end point gets new start point)
1300 aCurrentEdge
.setStartPoint(aCurrentEdge
.getEndPoint());
1303 // append last intermediate results (if exists)
1304 if(aSnippet
.count())
1306 if(bIsLine
&& pLineTarget
)
1308 pLineTarget
->append(aSnippet
);
1310 else if(!bIsLine
&& pGapTarget
)
1312 pGapTarget
->append(aSnippet
);
1316 // check if start and end polygon may be merged
1319 const sal_uInt32
nCount(pLineTarget
->count());
1323 // these polygons were created above, there exists none with less than two points,
1324 // thus dircet point access below is allowed
1325 const B2DPolygon
aFirst(pLineTarget
->getB2DPolygon(0));
1326 B2DPolygon
aLast(pLineTarget
->getB2DPolygon(nCount
- 1));
1328 if(aFirst
.getB2DPoint(0).equal(aLast
.getB2DPoint(aLast
.count() - 1)))
1330 // start of first and end of last are the same -> merge them
1331 aLast
.append(aFirst
);
1332 aLast
.removeDoublePoints();
1333 pLineTarget
->setB2DPolygon(0, aLast
);
1334 pLineTarget
->remove(nCount
- 1);
1341 const sal_uInt32
nCount(pGapTarget
->count());
1345 // these polygons were created above, there exists none with less than two points,
1346 // thus dircet point access below is allowed
1347 const B2DPolygon
aFirst(pGapTarget
->getB2DPolygon(0));
1348 B2DPolygon
aLast(pGapTarget
->getB2DPolygon(nCount
- 1));
1350 if(aFirst
.getB2DPoint(0).equal(aLast
.getB2DPoint(aLast
.count() - 1)))
1352 // start of first and end of last are the same -> merge them
1353 aLast
.append(aFirst
);
1354 aLast
.removeDoublePoints();
1355 pGapTarget
->setB2DPolygon(0, aLast
);
1356 pGapTarget
->remove(nCount
- 1);
1363 // parameters make no sense, just add source to targets
1366 pLineTarget
->append(rCandidate
);
1371 pGapTarget
->append(rCandidate
);
1376 // test if point is inside epsilon-range around an edge defined
1377 // by the two given points. Can be used for HitTesting. The epsilon-range
1378 // is defined to be the rectangle centered to the given edge, using height
1379 // 2 x fDistance, and the circle around both points with radius fDistance.
1380 bool isInEpsilonRange(const B2DPoint
& rEdgeStart
, const B2DPoint
& rEdgeEnd
, const B2DPoint
& rTestPosition
, double fDistance
)
1382 // build edge vector
1383 const B2DVector
aEdge(rEdgeEnd
- rEdgeStart
);
1384 bool bDoDistanceTestStart(false);
1385 bool bDoDistanceTestEnd(false);
1387 if(aEdge
.equalZero())
1389 // no edge, just a point. Do one of the distance tests.
1390 bDoDistanceTestStart
= true;
1394 // edge has a length. Create perpendicular vector.
1395 const B2DVector
aPerpend(getPerpendicular(aEdge
));
1397 (aPerpend
.getY() * (rTestPosition
.getX() - rEdgeStart
.getX())
1398 + aPerpend
.getX() * (rEdgeStart
.getY() - rTestPosition
.getY())) /
1399 (aEdge
.getX() * aEdge
.getX() + aEdge
.getY() * aEdge
.getY()));
1400 const double fZero(0.0);
1401 const double fOne(1.0);
1403 if(fTools::less(fCut
, fZero
))
1405 // left of rEdgeStart
1406 bDoDistanceTestStart
= true;
1408 else if(fTools::more(fCut
, fOne
))
1410 // right of rEdgeEnd
1411 bDoDistanceTestEnd
= true;
1415 // inside line [0.0 .. 1.0]
1416 const B2DPoint
aCutPoint(interpolate(rEdgeStart
, rEdgeEnd
, fCut
));
1417 const B2DVector
aDelta(rTestPosition
- aCutPoint
);
1418 const double fDistanceSquare(aDelta
.scalar(aDelta
));
1420 return fDistanceSquare
<= fDistance
* fDistance
;
1424 if(bDoDistanceTestStart
)
1426 const B2DVector
aDelta(rTestPosition
- rEdgeStart
);
1427 const double fDistanceSquare(aDelta
.scalar(aDelta
));
1429 if(fDistanceSquare
<= fDistance
* fDistance
)
1434 else if(bDoDistanceTestEnd
)
1436 const B2DVector
aDelta(rTestPosition
- rEdgeEnd
);
1437 const double fDistanceSquare(aDelta
.scalar(aDelta
));
1439 if(fDistanceSquare
<= fDistance
* fDistance
)
1448 // test if point is inside epsilon-range around the given Polygon. Can be used
1449 // for HitTesting. The epsilon-range is defined to be the tube around the polygon
1450 // with distance fDistance and rounded edges (start and end point).
1451 bool isInEpsilonRange(const B2DPolygon
& rCandidate
, const B2DPoint
& rTestPosition
, double fDistance
)
1453 // force to non-bezier polygon
1454 const B2DPolygon
aCandidate(rCandidate
.getDefaultAdaptiveSubdivision());
1455 const sal_uInt32
nPointCount(aCandidate
.count());
1459 const sal_uInt32
nEdgeCount(aCandidate
.isClosed() ? nPointCount
: nPointCount
- 1);
1460 B2DPoint
aCurrent(aCandidate
.getB2DPoint(0));
1465 for(sal_uInt32
a(0); a
< nEdgeCount
; a
++)
1467 const sal_uInt32
nNextIndex((a
+ 1) % nPointCount
);
1468 const B2DPoint
aNext(aCandidate
.getB2DPoint(nNextIndex
));
1470 if(isInEpsilonRange(aCurrent
, aNext
, rTestPosition
, fDistance
))
1475 // prepare next step
1481 // no edges, but points -> not closed. Check single point. Just
1482 // use isInEpsilonRange with twice the same point, it handles those well
1483 if(isInEpsilonRange(aCurrent
, aCurrent
, rTestPosition
, fDistance
))
1493 B2DPolygon
createPolygonFromRect( const B2DRectangle
& rRect
, double fRadiusX
, double fRadiusY
)
1495 const double fZero(0.0);
1496 const double fOne(1.0);
1498 // crop to useful values
1499 if(fTools::less(fRadiusX
, fZero
))
1503 else if(fTools::more(fRadiusX
, fOne
))
1508 if(fTools::less(fRadiusY
, fZero
))
1512 else if(fTools::more(fRadiusY
, fOne
))
1517 if(rtl::math::approxEqual(fZero
, fRadiusX
) || rtl::math::approxEqual(fZero
, fRadiusY
))
1519 // at least in one direction no radius, use rectangle.
1520 // Do not use createPolygonFromRect() here since original
1521 // creator (historical reasons) still creates a start point at the
1522 // bottom center, so do the same here to get the same line patterns.
1523 // Due to this the order of points is different, too.
1524 const B2DPoint
aBottomCenter(rRect
.getCenter().getX(), rRect
.getMaxY());
1525 B2DPolygon aPolygon
{
1527 { rRect
.getMinX(), rRect
.getMaxY() },
1528 { rRect
.getMinX(), rRect
.getMinY() },
1529 { rRect
.getMaxX(), rRect
.getMinY() },
1530 { rRect
.getMaxX(), rRect
.getMaxY() }
1534 aPolygon
.setClosed( true );
1538 else if(rtl::math::approxEqual(fOne
, fRadiusX
) && rtl::math::approxEqual(fOne
, fRadiusY
))
1540 // in both directions full radius, use ellipse
1541 const B2DPoint
aCenter(rRect
.getCenter());
1542 const double fRectRadiusX(rRect
.getWidth() / 2.0);
1543 const double fRectRadiusY(rRect
.getHeight() / 2.0);
1545 return createPolygonFromEllipse( aCenter
, fRectRadiusX
, fRectRadiusY
);
1550 const double fBowX((rRect
.getWidth() / 2.0) * fRadiusX
);
1551 const double fBowY((rRect
.getHeight() / 2.0) * fRadiusY
);
1552 const double fKappa((M_SQRT2
- 1.0) * 4.0 / 3.0);
1554 // create start point at bottom center
1555 if(!rtl::math::approxEqual(fOne
, fRadiusX
))
1557 const B2DPoint
aBottomCenter(rRect
.getCenter().getX(), rRect
.getMaxY());
1558 aRetval
.append(aBottomCenter
);
1563 const B2DPoint
aBottomRight(rRect
.getMaxX(), rRect
.getMaxY());
1564 const B2DPoint
aStart(aBottomRight
+ B2DPoint(-fBowX
, 0.0));
1565 const B2DPoint
aStop(aBottomRight
+ B2DPoint(0.0, -fBowY
));
1566 aRetval
.append(aStart
);
1567 aRetval
.appendBezierSegment(interpolate(aStart
, aBottomRight
, fKappa
), interpolate(aStop
, aBottomRight
, fKappa
), aStop
);
1570 // create second bow
1572 const B2DPoint
aTopRight(rRect
.getMaxX(), rRect
.getMinY());
1573 const B2DPoint
aStart(aTopRight
+ B2DPoint(0.0, fBowY
));
1574 const B2DPoint
aStop(aTopRight
+ B2DPoint(-fBowX
, 0.0));
1575 aRetval
.append(aStart
);
1576 aRetval
.appendBezierSegment(interpolate(aStart
, aTopRight
, fKappa
), interpolate(aStop
, aTopRight
, fKappa
), aStop
);
1581 const B2DPoint
aTopLeft(rRect
.getMinX(), rRect
.getMinY());
1582 const B2DPoint
aStart(aTopLeft
+ B2DPoint(fBowX
, 0.0));
1583 const B2DPoint
aStop(aTopLeft
+ B2DPoint(0.0, fBowY
));
1584 aRetval
.append(aStart
);
1585 aRetval
.appendBezierSegment(interpolate(aStart
, aTopLeft
, fKappa
), interpolate(aStop
, aTopLeft
, fKappa
), aStop
);
1590 const B2DPoint
aBottomLeft(rRect
.getMinX(), rRect
.getMaxY());
1591 const B2DPoint
aStart(aBottomLeft
+ B2DPoint(0.0, -fBowY
));
1592 const B2DPoint
aStop(aBottomLeft
+ B2DPoint(fBowX
, 0.0));
1593 aRetval
.append(aStart
);
1594 aRetval
.appendBezierSegment(interpolate(aStart
, aBottomLeft
, fKappa
), interpolate(aStop
, aBottomLeft
, fKappa
), aStop
);
1598 aRetval
.setClosed( true );
1600 // remove double created points if there are extreme radii involved
1601 if(rtl::math::approxEqual(fOne
, fRadiusX
) || rtl::math::approxEqual(fOne
, fRadiusY
))
1603 aRetval
.removeDoublePoints();
1610 B2DPolygon
createPolygonFromRect( const B2DRectangle
& rRect
)
1612 B2DPolygon aPolygon
{
1613 { rRect
.getMinX(), rRect
.getMinY() },
1614 { rRect
.getMaxX(), rRect
.getMinY() },
1615 { rRect
.getMaxX(), rRect
.getMaxY() },
1616 { rRect
.getMinX(), rRect
.getMaxY() }
1620 aPolygon
.setClosed( true );
1627 struct theUnitPolygon
:
1628 public rtl::StaticWithInit
<B2DPolygon
, theUnitPolygon
>
1630 B2DPolygon
operator () ()
1632 B2DPolygon aPolygon
{
1640 aPolygon
.setClosed( true );
1647 B2DPolygon
const & createUnitPolygon()
1649 return theUnitPolygon::get();
1652 B2DPolygon
createPolygonFromCircle( const B2DPoint
& rCenter
, double fRadius
)
1654 return createPolygonFromEllipse( rCenter
, fRadius
, fRadius
);
1657 B2DPolygon
impCreateUnitCircle(sal_uInt32 nStartQuadrant
)
1659 B2DPolygon aUnitCircle
;
1660 const double fKappa((M_SQRT2
- 1.0) * 4.0 / 3.0);
1661 const double fScaledKappa(fKappa
* (1.0 / STEPSPERQUARTER
));
1662 const B2DHomMatrix
aRotateMatrix(createRotateB2DHomMatrix(F_PI2
/ STEPSPERQUARTER
));
1664 B2DPoint
aPoint(1.0, 0.0);
1665 B2DPoint
aForward(1.0, fScaledKappa
);
1666 B2DPoint
aBackward(1.0, -fScaledKappa
);
1668 if(nStartQuadrant
!= 0)
1670 const B2DHomMatrix
aQuadrantMatrix(createRotateB2DHomMatrix(F_PI2
* (nStartQuadrant
% 4)));
1671 aPoint
*= aQuadrantMatrix
;
1672 aBackward
*= aQuadrantMatrix
;
1673 aForward
*= aQuadrantMatrix
;
1676 aUnitCircle
.append(aPoint
);
1678 for(sal_uInt32
a(0); a
< STEPSPERQUARTER
* 4; a
++)
1680 aPoint
*= aRotateMatrix
;
1681 aBackward
*= aRotateMatrix
;
1682 aUnitCircle
.appendBezierSegment(aForward
, aBackward
, aPoint
);
1683 aForward
*= aRotateMatrix
;
1686 aUnitCircle
.setClosed(true);
1687 aUnitCircle
.removeDoublePoints();
1694 struct theUnitHalfCircle
:
1695 public rtl::StaticWithInit
<B2DPolygon
, theUnitHalfCircle
>
1697 B2DPolygon
operator()()
1699 B2DPolygon aUnitHalfCircle
;
1700 const double fKappa((M_SQRT2
- 1.0) * 4.0 / 3.0);
1701 const double fScaledKappa(fKappa
* (1.0 / STEPSPERQUARTER
));
1702 const B2DHomMatrix
aRotateMatrix(createRotateB2DHomMatrix(F_PI2
/ STEPSPERQUARTER
));
1703 B2DPoint
aPoint(1.0, 0.0);
1704 B2DPoint
aForward(1.0, fScaledKappa
);
1705 B2DPoint
aBackward(1.0, -fScaledKappa
);
1707 aUnitHalfCircle
.append(aPoint
);
1709 for(sal_uInt32
a(0); a
< STEPSPERQUARTER
* 2; a
++)
1711 aPoint
*= aRotateMatrix
;
1712 aBackward
*= aRotateMatrix
;
1713 aUnitHalfCircle
.appendBezierSegment(aForward
, aBackward
, aPoint
);
1714 aForward
*= aRotateMatrix
;
1716 return aUnitHalfCircle
;
1721 B2DPolygon
const & createHalfUnitCircle()
1723 return theUnitHalfCircle::get();
1728 struct theUnitCircleStartQuadrantOne
:
1729 public rtl::StaticWithInit
<B2DPolygon
, theUnitCircleStartQuadrantOne
>
1731 B2DPolygon
operator()() { return impCreateUnitCircle(1); }
1734 struct theUnitCircleStartQuadrantTwo
:
1735 public rtl::StaticWithInit
<B2DPolygon
, theUnitCircleStartQuadrantTwo
>
1737 B2DPolygon
operator()() { return impCreateUnitCircle(2); }
1740 struct theUnitCircleStartQuadrantThree
:
1741 public rtl::StaticWithInit
<B2DPolygon
, theUnitCircleStartQuadrantThree
>
1743 B2DPolygon
operator()() { return impCreateUnitCircle(3); }
1746 struct theUnitCircleStartQuadrantZero
:
1747 public rtl::StaticWithInit
<B2DPolygon
, theUnitCircleStartQuadrantZero
>
1749 B2DPolygon
operator()() { return impCreateUnitCircle(0); }
1753 B2DPolygon
const & createPolygonFromUnitCircle(sal_uInt32 nStartQuadrant
)
1755 switch(nStartQuadrant
% 4)
1758 return theUnitCircleStartQuadrantOne::get();
1761 return theUnitCircleStartQuadrantTwo::get();
1764 return theUnitCircleStartQuadrantThree::get();
1766 default : // case 0 :
1767 return theUnitCircleStartQuadrantZero::get();
1771 B2DPolygon
createPolygonFromEllipse( const B2DPoint
& rCenter
, double fRadiusX
, double fRadiusY
)
1773 B2DPolygon
aRetval(createPolygonFromUnitCircle());
1774 const B2DHomMatrix
aMatrix(createScaleTranslateB2DHomMatrix(fRadiusX
, fRadiusY
, rCenter
.getX(), rCenter
.getY()));
1776 aRetval
.transform(aMatrix
);
1781 B2DPolygon
createPolygonFromUnitEllipseSegment( double fStart
, double fEnd
)
1785 // truncate fStart, fEnd to a range of [0.0 .. F_2PI[ where F_2PI
1786 // falls back to 0.0 to ensure a unique definition
1787 if(fTools::less(fStart
, 0.0))
1792 if(fTools::moreOrEqual(fStart
, F_2PI
))
1797 if(fTools::less(fEnd
, 0.0))
1802 if(fTools::moreOrEqual(fEnd
, F_2PI
))
1807 if(fTools::equal(fStart
, fEnd
))
1809 // same start and end angle, add single point
1810 aRetval
.append(B2DPoint(cos(fStart
), sin(fStart
)));
1814 const sal_uInt32
nSegments(STEPSPERQUARTER
* 4);
1815 const double fAnglePerSegment(F_PI2
/ STEPSPERQUARTER
);
1816 const sal_uInt32
nStartSegment(sal_uInt32(fStart
/ fAnglePerSegment
) % nSegments
);
1817 const sal_uInt32
nEndSegment(sal_uInt32(fEnd
/ fAnglePerSegment
) % nSegments
);
1818 const double fKappa((M_SQRT2
- 1.0) * 4.0 / 3.0);
1819 const double fScaledKappa(fKappa
* (1.0 / STEPSPERQUARTER
));
1821 B2DPoint
aSegStart(cos(fStart
), sin(fStart
));
1822 aRetval
.append(aSegStart
);
1824 if(nStartSegment
== nEndSegment
&& fTools::more(fEnd
, fStart
))
1826 // start and end in one sector and in the right order, create in one segment
1827 const B2DPoint
aSegEnd(cos(fEnd
), sin(fEnd
));
1828 const double fFactor(fScaledKappa
* ((fEnd
- fStart
) / fAnglePerSegment
));
1830 aRetval
.appendBezierSegment(
1831 aSegStart
+ (B2DPoint(-aSegStart
.getY(), aSegStart
.getX()) * fFactor
),
1832 aSegEnd
- (B2DPoint(-aSegEnd
.getY(), aSegEnd
.getX()) * fFactor
),
1837 double fSegEndRad((nStartSegment
+ 1) * fAnglePerSegment
);
1838 double fFactor(fScaledKappa
* ((fSegEndRad
- fStart
) / fAnglePerSegment
));
1839 B2DPoint
aSegEnd(cos(fSegEndRad
), sin(fSegEndRad
));
1841 aRetval
.appendBezierSegment(
1842 aSegStart
+ (B2DPoint(-aSegStart
.getY(), aSegStart
.getX()) * fFactor
),
1843 aSegEnd
- (B2DPoint(-aSegEnd
.getY(), aSegEnd
.getX()) * fFactor
),
1846 sal_uInt32
nSegment((nStartSegment
+ 1) % nSegments
);
1847 aSegStart
= aSegEnd
;
1849 while(nSegment
!= nEndSegment
)
1851 // No end in this sector, add full sector.
1852 fSegEndRad
= (nSegment
+ 1) * fAnglePerSegment
;
1853 aSegEnd
= B2DPoint(cos(fSegEndRad
), sin(fSegEndRad
));
1855 aRetval
.appendBezierSegment(
1856 aSegStart
+ (B2DPoint(-aSegStart
.getY(), aSegStart
.getX()) * fScaledKappa
),
1857 aSegEnd
- (B2DPoint(-aSegEnd
.getY(), aSegEnd
.getX()) * fScaledKappa
),
1860 nSegment
= (nSegment
+ 1) % nSegments
;
1861 aSegStart
= aSegEnd
;
1864 // End in this sector
1865 const double fSegStartRad(nSegment
* fAnglePerSegment
);
1866 fFactor
= fScaledKappa
* ((fEnd
- fSegStartRad
) / fAnglePerSegment
);
1867 aSegEnd
= B2DPoint(cos(fEnd
), sin(fEnd
));
1869 aRetval
.appendBezierSegment(
1870 aSegStart
+ (B2DPoint(-aSegStart
.getY(), aSegStart
.getX()) * fFactor
),
1871 aSegEnd
- (B2DPoint(-aSegEnd
.getY(), aSegEnd
.getX()) * fFactor
),
1876 // remove double points between segments created by segmented creation
1877 aRetval
.removeDoublePoints();
1882 B2DPolygon
createPolygonFromEllipseSegment( const B2DPoint
& rCenter
, double fRadiusX
, double fRadiusY
, double fStart
, double fEnd
)
1884 B2DPolygon
aRetval(createPolygonFromUnitEllipseSegment(fStart
, fEnd
));
1885 const B2DHomMatrix
aMatrix(createScaleTranslateB2DHomMatrix(fRadiusX
, fRadiusY
, rCenter
.getX(), rCenter
.getY()));
1887 aRetval
.transform(aMatrix
);
1892 bool hasNeutralPoints(const B2DPolygon
& rCandidate
)
1894 OSL_ENSURE(!rCandidate
.areControlPointsUsed(), "hasNeutralPoints: ATM works not for curves (!)");
1895 const sal_uInt32
nPointCount(rCandidate
.count());
1899 B2DPoint
aPrevPoint(rCandidate
.getB2DPoint(nPointCount
- 1));
1900 B2DPoint
aCurrPoint(rCandidate
.getB2DPoint(0));
1902 for(sal_uInt32
a(0); a
< nPointCount
; a
++)
1904 const B2DPoint
aNextPoint(rCandidate
.getB2DPoint((a
+ 1) % nPointCount
));
1905 const B2DVector
aPrevVec(aPrevPoint
- aCurrPoint
);
1906 const B2DVector
aNextVec(aNextPoint
- aCurrPoint
);
1907 const B2VectorOrientation
aOrientation(getOrientation(aNextVec
, aPrevVec
));
1909 if(aOrientation
== B2VectorOrientation::Neutral
)
1911 // current has neutral orientation
1917 aPrevPoint
= aCurrPoint
;
1918 aCurrPoint
= aNextPoint
;
1926 B2DPolygon
removeNeutralPoints(const B2DPolygon
& rCandidate
)
1928 if(hasNeutralPoints(rCandidate
))
1930 const sal_uInt32
nPointCount(rCandidate
.count());
1932 B2DPoint
aPrevPoint(rCandidate
.getB2DPoint(nPointCount
- 1));
1933 B2DPoint
aCurrPoint(rCandidate
.getB2DPoint(0));
1935 for(sal_uInt32
a(0); a
< nPointCount
; a
++)
1937 const B2DPoint
aNextPoint(rCandidate
.getB2DPoint((a
+ 1) % nPointCount
));
1938 const B2DVector
aPrevVec(aPrevPoint
- aCurrPoint
);
1939 const B2DVector
aNextVec(aNextPoint
- aCurrPoint
);
1940 const B2VectorOrientation
aOrientation(getOrientation(aNextVec
, aPrevVec
));
1942 if(aOrientation
== B2VectorOrientation::Neutral
)
1944 // current has neutral orientation, leave it out and prepare next
1945 aCurrPoint
= aNextPoint
;
1949 // add current point
1950 aRetval
.append(aCurrPoint
);
1953 aPrevPoint
= aCurrPoint
;
1954 aCurrPoint
= aNextPoint
;
1958 while(aRetval
.count() && getOrientationForIndex(aRetval
, 0) == B2VectorOrientation::Neutral
)
1963 // copy closed state
1964 aRetval
.setClosed(rCandidate
.isClosed());
1974 bool isConvex(const B2DPolygon
& rCandidate
)
1976 OSL_ENSURE(!rCandidate
.areControlPointsUsed(), "isConvex: ATM works not for curves (!)");
1977 const sal_uInt32
nPointCount(rCandidate
.count());
1981 const B2DPoint
aPrevPoint(rCandidate
.getB2DPoint(nPointCount
- 1));
1982 B2DPoint
aCurrPoint(rCandidate
.getB2DPoint(0));
1983 B2DVector
aCurrVec(aPrevPoint
- aCurrPoint
);
1984 B2VectorOrientation
aOrientation(B2VectorOrientation::Neutral
);
1986 for(sal_uInt32
a(0); a
< nPointCount
; a
++)
1988 const B2DPoint
aNextPoint(rCandidate
.getB2DPoint((a
+ 1) % nPointCount
));
1989 const B2DVector
aNextVec(aNextPoint
- aCurrPoint
);
1990 const B2VectorOrientation
aCurrentOrientation(getOrientation(aNextVec
, aCurrVec
));
1992 if(aOrientation
== B2VectorOrientation::Neutral
)
1994 // set start value, maybe neutral again
1995 aOrientation
= aCurrentOrientation
;
1999 if(aCurrentOrientation
!= B2VectorOrientation::Neutral
&& aCurrentOrientation
!= aOrientation
)
2001 // different orientations found, that's it
2007 aCurrPoint
= aNextPoint
;
2008 aCurrVec
= -aNextVec
;
2015 B2VectorOrientation
getOrientationForIndex(const B2DPolygon
& rCandidate
, sal_uInt32 nIndex
)
2017 OSL_ENSURE(nIndex
< rCandidate
.count(), "getOrientationForIndex: index out of range (!)");
2018 const B2DPoint
aPrev(rCandidate
.getB2DPoint(getIndexOfPredecessor(nIndex
, rCandidate
)));
2019 const B2DPoint
aCurr(rCandidate
.getB2DPoint(nIndex
));
2020 const B2DPoint
aNext(rCandidate
.getB2DPoint(getIndexOfSuccessor(nIndex
, rCandidate
)));
2021 const B2DVector
aBack(aPrev
- aCurr
);
2022 const B2DVector
aForw(aNext
- aCurr
);
2024 return getOrientation(aForw
, aBack
);
2027 bool isPointOnLine(const B2DPoint
& rStart
, const B2DPoint
& rEnd
, const B2DPoint
& rCandidate
, bool bWithPoints
)
2029 if(rCandidate
.equal(rStart
) || rCandidate
.equal(rEnd
))
2031 // candidate is in epsilon around start or end -> inside
2034 else if(rStart
.equal(rEnd
))
2036 // start and end are equal, but candidate is outside their epsilon -> outside
2041 const B2DVector
aEdgeVector(rEnd
- rStart
);
2042 const B2DVector
aTestVector(rCandidate
- rStart
);
2044 if(areParallel(aEdgeVector
, aTestVector
))
2046 const double fZero(0.0);
2047 const double fOne(1.0);
2048 const double fParamTestOnCurr(fabs(aEdgeVector
.getX()) > fabs(aEdgeVector
.getY())
2049 ? aTestVector
.getX() / aEdgeVector
.getX()
2050 : aTestVector
.getY() / aEdgeVector
.getY());
2052 if(fTools::more(fParamTestOnCurr
, fZero
) && fTools::less(fParamTestOnCurr
, fOne
))
2062 bool isPointOnPolygon(const B2DPolygon
& rCandidate
, const B2DPoint
& rPoint
, bool bWithPoints
)
2064 const B2DPolygon
aCandidate(rCandidate
.areControlPointsUsed() ? rCandidate
.getDefaultAdaptiveSubdivision() : rCandidate
);
2065 const sal_uInt32
nPointCount(aCandidate
.count());
2069 const sal_uInt32
nLoopCount(aCandidate
.isClosed() ? nPointCount
: nPointCount
- 1);
2070 B2DPoint
aCurrentPoint(aCandidate
.getB2DPoint(0));
2072 for(sal_uInt32
a(0); a
< nLoopCount
; a
++)
2074 const B2DPoint
aNextPoint(aCandidate
.getB2DPoint((a
+ 1) % nPointCount
));
2076 if(isPointOnLine(aCurrentPoint
, aNextPoint
, rPoint
, bWithPoints
))
2081 aCurrentPoint
= aNextPoint
;
2084 else if(nPointCount
&& bWithPoints
)
2086 return rPoint
.equal(aCandidate
.getB2DPoint(0));
2092 bool isPointInTriangle(const B2DPoint
& rA
, const B2DPoint
& rB
, const B2DPoint
& rC
, const B2DPoint
& rCandidate
, bool bWithBorder
)
2094 if(arePointsOnSameSideOfLine(rA
, rB
, rC
, rCandidate
, bWithBorder
))
2096 if(arePointsOnSameSideOfLine(rB
, rC
, rA
, rCandidate
, bWithBorder
))
2098 if(arePointsOnSameSideOfLine(rC
, rA
, rB
, rCandidate
, bWithBorder
))
2108 bool arePointsOnSameSideOfLine(const B2DPoint
& rStart
, const B2DPoint
& rEnd
, const B2DPoint
& rCandidateA
, const B2DPoint
& rCandidateB
, bool bWithLine
)
2110 const B2DVector
aLineVector(rEnd
- rStart
);
2111 const B2DVector
aVectorToA(rEnd
- rCandidateA
);
2112 const double fCrossA(aLineVector
.cross(aVectorToA
));
2114 // tdf#88352 increase numerical correctness and use rtl::math::approxEqual
2115 // instead of fTools::equalZero which compares with a fixed small value
2118 // one point on the line
2122 const B2DVector
aVectorToB(rEnd
- rCandidateB
);
2123 const double fCrossB(aLineVector
.cross(aVectorToB
));
2125 // increase numerical correctness
2128 // one point on the line
2132 // return true if they both have the same sign
2133 return ((fCrossA
> 0.0) == (fCrossB
> 0.0));
2136 void addTriangleFan(const B2DPolygon
& rCandidate
, B2DPolygon
& rTarget
)
2138 const sal_uInt32
nCount(rCandidate
.count());
2142 const B2DPoint
aStart(rCandidate
.getB2DPoint(0));
2143 B2DPoint
aLast(rCandidate
.getB2DPoint(1));
2145 for(sal_uInt32
a(2); a
< nCount
; a
++)
2147 const B2DPoint
aCurrent(rCandidate
.getB2DPoint(a
));
2148 rTarget
.append(aStart
);
2149 rTarget
.append(aLast
);
2150 rTarget
.append(aCurrent
);
2160 /// return 0 for input of 0, -1 for negative and 1 for positive input
2161 inline int lcl_sgn( const double n
)
2163 return n
== 0.0 ? 0 : 1 - 2*int(rtl::math::isSignBitSet(n
));
2167 bool isRectangle( const B2DPolygon
& rPoly
)
2169 // polygon must be closed to resemble a rect, and contain
2170 // at least four points.
2171 if( !rPoly
.isClosed() ||
2172 rPoly
.count() < 4 ||
2173 rPoly
.areControlPointsUsed() )
2178 // number of 90 degree turns the polygon has taken
2181 int nVerticalEdgeType
=0;
2182 int nHorizontalEdgeType
=0;
2183 bool bNullVertex(true);
2184 bool bCWPolygon(false); // when true, polygon is CW
2185 // oriented, when false, CCW
2186 bool bOrientationSet(false); // when false, polygon
2187 // orientation has not yet
2190 // scan all _edges_ (which involves coming back to point 0
2191 // for the last edge - thus the modulo operation below)
2192 const sal_Int32
nCount( rPoly
.count() );
2193 for( sal_Int32 i
=0; i
<nCount
; ++i
)
2195 const B2DPoint
& rPoint0( rPoly
.getB2DPoint(i
% nCount
) );
2196 const B2DPoint
& rPoint1( rPoly
.getB2DPoint((i
+1) % nCount
) );
2198 // is 0 for zero direction vector, 1 for south edge and -1
2199 // for north edge (standard screen coordinate system)
2200 int nCurrVerticalEdgeType( lcl_sgn( rPoint1
.getY() - rPoint0
.getY() ) );
2202 // is 0 for zero direction vector, 1 for east edge and -1
2203 // for west edge (standard screen coordinate system)
2204 int nCurrHorizontalEdgeType( lcl_sgn(rPoint1
.getX() - rPoint0
.getX()) );
2206 if( nCurrVerticalEdgeType
&& nCurrHorizontalEdgeType
)
2207 return false; // oblique edge - for sure no rect
2209 const bool bCurrNullVertex( !nCurrVerticalEdgeType
&& !nCurrHorizontalEdgeType
);
2211 // current vertex is equal to previous - just skip,
2212 // until we have a real edge
2213 if( bCurrNullVertex
)
2216 // if previous edge has two identical points, because
2217 // no previous edge direction was available, simply
2218 // take this first non-null edge as the start
2219 // direction. That's what will happen here, if
2220 // bNullVertex is false
2223 // 2D cross product - is 1 for CW and -1 for CCW turns
2224 const int nCrossProduct( nHorizontalEdgeType
*nCurrVerticalEdgeType
-
2225 nVerticalEdgeType
*nCurrHorizontalEdgeType
);
2227 if( !nCrossProduct
)
2228 continue; // no change in orientation -
2229 // collinear edges - just go on
2231 // if polygon orientation is not set, we'll
2233 if( !bOrientationSet
)
2235 bCWPolygon
= nCrossProduct
== 1;
2236 bOrientationSet
= true;
2240 // if current turn orientation is not equal
2241 // initial orientation, this is not a
2242 // rectangle (as rectangles have consistent
2244 if( (nCrossProduct
== 1) != bCWPolygon
)
2250 // More than four 90 degree turns are an
2251 // indication that this must not be a rectangle.
2256 // store current state for the next turn
2257 nVerticalEdgeType
= nCurrVerticalEdgeType
;
2258 nHorizontalEdgeType
= nCurrHorizontalEdgeType
;
2259 bNullVertex
= false; // won't reach this line,
2260 // if bCurrNullVertex is
2267 B3DPolygon
createB3DPolygonFromB2DPolygon(const B2DPolygon
& rCandidate
, double fZCoordinate
)
2269 if(rCandidate
.areControlPointsUsed())
2271 // call myself recursively with subdivided input
2272 const B2DPolygon
aCandidate(adaptiveSubdivideByAngle(rCandidate
));
2273 return createB3DPolygonFromB2DPolygon(aCandidate
, fZCoordinate
);
2279 for(sal_uInt32
a(0); a
< rCandidate
.count(); a
++)
2281 B2DPoint
aPoint(rCandidate
.getB2DPoint(a
));
2282 aRetval
.append(B3DPoint(aPoint
.getX(), aPoint
.getY(), fZCoordinate
));
2285 // copy closed state
2286 aRetval
.setClosed(rCandidate
.isClosed());
2292 B2DPolygon
createB2DPolygonFromB3DPolygon(const B3DPolygon
& rCandidate
, const B3DHomMatrix
& rMat
)
2295 const sal_uInt32
nCount(rCandidate
.count());
2296 const bool bIsIdentity(rMat
.isIdentity());
2298 for(sal_uInt32
a(0); a
< nCount
; a
++)
2300 B3DPoint
aCandidate(rCandidate
.getB3DPoint(a
));
2307 aRetval
.append(B2DPoint(aCandidate
.getX(), aCandidate
.getY()));
2310 // copy closed state
2311 aRetval
.setClosed(rCandidate
.isClosed());
2316 double getSmallestDistancePointToEdge(const B2DPoint
& rPointA
, const B2DPoint
& rPointB
, const B2DPoint
& rTestPoint
, double& rCut
)
2318 if(rPointA
.equal(rPointB
))
2321 const B2DVector
aVector(rTestPoint
- rPointA
);
2322 return aVector
.getLength();
2326 // get the relative cut value on line vector (Vector1) for cut with perpendicular through TestPoint
2327 const B2DVector
aVector1(rPointB
- rPointA
);
2328 const B2DVector
aVector2(rTestPoint
- rPointA
);
2329 const double fDividend((aVector2
.getX() * aVector1
.getX()) + (aVector2
.getY() * aVector1
.getY()));
2330 const double fDivisor((aVector1
.getX() * aVector1
.getX()) + (aVector1
.getY() * aVector1
.getY()));
2331 const double fCut(fDividend
/ fDivisor
);
2335 // not in line range, get distance to PointA
2337 return aVector2
.getLength();
2341 // not in line range, get distance to PointB
2343 const B2DVector
aVector(rTestPoint
- rPointB
);
2344 return aVector
.getLength();
2349 const B2DPoint
aCutPoint(rPointA
+ fCut
* aVector1
);
2350 const B2DVector
aVector(rTestPoint
- aCutPoint
);
2352 return aVector
.getLength();
2357 double getSmallestDistancePointToPolygon(const B2DPolygon
& rCandidate
, const B2DPoint
& rTestPoint
, sal_uInt32
& rEdgeIndex
, double& rCut
)
2359 double fRetval(DBL_MAX
);
2360 const sal_uInt32
nPointCount(rCandidate
.count());
2364 const double fZero(0.0);
2365 const sal_uInt32
nEdgeCount(rCandidate
.isClosed() ? nPointCount
: nPointCount
- 1);
2366 B2DCubicBezier aBezier
;
2367 aBezier
.setStartPoint(rCandidate
.getB2DPoint(0));
2369 for(sal_uInt32
a(0); a
< nEdgeCount
; a
++)
2371 const sal_uInt32
nNextIndex((a
+ 1) % nPointCount
);
2372 aBezier
.setEndPoint(rCandidate
.getB2DPoint(nNextIndex
));
2374 double fNewCut(0.0);
2375 bool bEdgeIsCurve(false);
2377 if(rCandidate
.areControlPointsUsed())
2379 aBezier
.setControlPointA(rCandidate
.getNextControlPoint(a
));
2380 aBezier
.setControlPointB(rCandidate
.getPrevControlPoint(nNextIndex
));
2381 aBezier
.testAndSolveTrivialBezier();
2382 bEdgeIsCurve
= aBezier
.isBezier();
2387 fEdgeDist
= aBezier
.getSmallestDistancePointToBezierSegment(rTestPoint
, fNewCut
);
2391 fEdgeDist
= getSmallestDistancePointToEdge(aBezier
.getStartPoint(), aBezier
.getEndPoint(), rTestPoint
, fNewCut
);
2394 if(fRetval
== DBL_MAX
|| fEdgeDist
< fRetval
)
2396 fRetval
= fEdgeDist
;
2400 if(fTools::equal(fRetval
, fZero
))
2402 // already found zero distance, cannot get better. Ensure numerical zero value and end loop.
2408 // prepare next step
2409 aBezier
.setStartPoint(aBezier
.getEndPoint());
2412 if(rtl::math::approxEqual(1.0, rCut
))
2414 // correct rEdgeIndex when not last point
2415 if(rCandidate
.isClosed())
2417 rEdgeIndex
= getIndexOfSuccessor(rEdgeIndex
, rCandidate
);
2422 if(rEdgeIndex
!= nEdgeCount
- 1)
2434 B2DPoint
distort(const B2DPoint
& rCandidate
, const B2DRange
& rOriginal
, const B2DPoint
& rTopLeft
, const B2DPoint
& rTopRight
, const B2DPoint
& rBottomLeft
, const B2DPoint
& rBottomRight
)
2436 if(fTools::equalZero(rOriginal
.getWidth()) || fTools::equalZero(rOriginal
.getHeight()))
2442 const double fRelativeX((rCandidate
.getX() - rOriginal
.getMinX()) / rOriginal
.getWidth());
2443 const double fRelativeY((rCandidate
.getY() - rOriginal
.getMinY()) / rOriginal
.getHeight());
2444 const double fOneMinusRelativeX(1.0 - fRelativeX
);
2445 const double fOneMinusRelativeY(1.0 - fRelativeY
);
2446 const double fNewX(fOneMinusRelativeY
* (fOneMinusRelativeX
* rTopLeft
.getX() + fRelativeX
* rTopRight
.getX()) +
2447 fRelativeY
* (fOneMinusRelativeX
* rBottomLeft
.getX() + fRelativeX
* rBottomRight
.getX()));
2448 const double fNewY(fOneMinusRelativeX
* (fOneMinusRelativeY
* rTopLeft
.getY() + fRelativeY
* rBottomLeft
.getY()) +
2449 fRelativeX
* (fOneMinusRelativeY
* rTopRight
.getY() + fRelativeY
* rBottomRight
.getY()));
2451 return B2DPoint(fNewX
, fNewY
);
2455 B2DPolygon
distort(const B2DPolygon
& rCandidate
, const B2DRange
& rOriginal
, const B2DPoint
& rTopLeft
, const B2DPoint
& rTopRight
, const B2DPoint
& rBottomLeft
, const B2DPoint
& rBottomRight
)
2457 const sal_uInt32
nPointCount(rCandidate
.count());
2459 if(nPointCount
&& rOriginal
.getWidth() != 0.0 && rOriginal
.getHeight() != 0.0)
2463 for(sal_uInt32
a(0); a
< nPointCount
; a
++)
2465 aRetval
.append(distort(rCandidate
.getB2DPoint(a
), rOriginal
, rTopLeft
, rTopRight
, rBottomLeft
, rBottomRight
));
2467 if(rCandidate
.areControlPointsUsed())
2469 if(!rCandidate
.getPrevControlPoint(a
).equalZero())
2471 aRetval
.setPrevControlPoint(a
, distort(rCandidate
.getPrevControlPoint(a
), rOriginal
, rTopLeft
, rTopRight
, rBottomLeft
, rBottomRight
));
2474 if(!rCandidate
.getNextControlPoint(a
).equalZero())
2476 aRetval
.setNextControlPoint(a
, distort(rCandidate
.getNextControlPoint(a
), rOriginal
, rTopLeft
, rTopRight
, rBottomLeft
, rBottomRight
));
2481 aRetval
.setClosed(rCandidate
.isClosed());
2490 B2DPolygon
expandToCurve(const B2DPolygon
& rCandidate
)
2492 B2DPolygon
aRetval(rCandidate
);
2494 for(sal_uInt32
a(0); a
< rCandidate
.count(); a
++)
2496 expandToCurveInPoint(aRetval
, a
);
2502 bool expandToCurveInPoint(B2DPolygon
& rCandidate
, sal_uInt32 nIndex
)
2504 OSL_ENSURE(nIndex
< rCandidate
.count(), "expandToCurveInPoint: Access to polygon out of range (!)");
2505 bool bRetval(false);
2506 const sal_uInt32
nPointCount(rCandidate
.count());
2511 if(!rCandidate
.isPrevControlPointUsed(nIndex
))
2513 if(!rCandidate
.isClosed() && nIndex
== 0)
2515 // do not create previous vector for start point of open polygon
2519 const sal_uInt32
nPrevIndex((nIndex
+ (nPointCount
- 1)) % nPointCount
);
2520 rCandidate
.setPrevControlPoint(nIndex
, interpolate(rCandidate
.getB2DPoint(nIndex
), rCandidate
.getB2DPoint(nPrevIndex
), 1.0 / 3.0));
2526 if(!rCandidate
.isNextControlPointUsed(nIndex
))
2528 if(!rCandidate
.isClosed() && nIndex
+ 1 == nPointCount
)
2530 // do not create next vector for end point of open polygon
2534 const sal_uInt32
nNextIndex((nIndex
+ 1) % nPointCount
);
2535 rCandidate
.setNextControlPoint(nIndex
, interpolate(rCandidate
.getB2DPoint(nIndex
), rCandidate
.getB2DPoint(nNextIndex
), 1.0 / 3.0));
2544 bool setContinuityInPoint(B2DPolygon
& rCandidate
, sal_uInt32 nIndex
, B2VectorContinuity eContinuity
)
2546 OSL_ENSURE(nIndex
< rCandidate
.count(), "setContinuityInPoint: Access to polygon out of range (!)");
2547 bool bRetval(false);
2548 const sal_uInt32
nPointCount(rCandidate
.count());
2552 const B2DPoint
aCurrentPoint(rCandidate
.getB2DPoint(nIndex
));
2556 case B2VectorContinuity::NONE
:
2558 if(rCandidate
.isPrevControlPointUsed(nIndex
))
2560 if(!rCandidate
.isClosed() && nIndex
== 0)
2562 // remove existing previous vector for start point of open polygon
2563 rCandidate
.resetPrevControlPoint(nIndex
);
2567 const sal_uInt32
nPrevIndex((nIndex
+ (nPointCount
- 1)) % nPointCount
);
2568 rCandidate
.setPrevControlPoint(nIndex
, interpolate(aCurrentPoint
, rCandidate
.getB2DPoint(nPrevIndex
), 1.0 / 3.0));
2574 if(rCandidate
.isNextControlPointUsed(nIndex
))
2576 if(!rCandidate
.isClosed() && nIndex
== nPointCount
+ 1)
2578 // remove next vector for end point of open polygon
2579 rCandidate
.resetNextControlPoint(nIndex
);
2583 const sal_uInt32
nNextIndex((nIndex
+ 1) % nPointCount
);
2584 rCandidate
.setNextControlPoint(nIndex
, interpolate(aCurrentPoint
, rCandidate
.getB2DPoint(nNextIndex
), 1.0 / 3.0));
2592 case B2VectorContinuity::C1
:
2594 if(rCandidate
.isPrevControlPointUsed(nIndex
) && rCandidate
.isNextControlPointUsed(nIndex
))
2596 // lengths both exist since both are used
2597 B2DVector
aVectorPrev(rCandidate
.getPrevControlPoint(nIndex
) - aCurrentPoint
);
2598 B2DVector
aVectorNext(rCandidate
.getNextControlPoint(nIndex
) - aCurrentPoint
);
2599 const double fLenPrev(aVectorPrev
.getLength());
2600 const double fLenNext(aVectorNext
.getLength());
2601 aVectorPrev
.normalize();
2602 aVectorNext
.normalize();
2603 const B2VectorOrientation
aOrientation(getOrientation(aVectorPrev
, aVectorNext
));
2605 if(aOrientation
== B2VectorOrientation::Neutral
&& aVectorPrev
.scalar(aVectorNext
) < 0.0)
2607 // parallel and opposite direction; check length
2608 if(fTools::equal(fLenPrev
, fLenNext
))
2610 // this would be even C2, but we want C1. Use the lengths of the corresponding edges.
2611 const sal_uInt32
nPrevIndex((nIndex
+ (nPointCount
- 1)) % nPointCount
);
2612 const sal_uInt32
nNextIndex((nIndex
+ 1) % nPointCount
);
2613 const double fLenPrevEdge(B2DVector(rCandidate
.getB2DPoint(nPrevIndex
) - aCurrentPoint
).getLength() * (1.0 / 3.0));
2614 const double fLenNextEdge(B2DVector(rCandidate
.getB2DPoint(nNextIndex
) - aCurrentPoint
).getLength() * (1.0 / 3.0));
2616 rCandidate
.setControlPoints(nIndex
,
2617 aCurrentPoint
+ (aVectorPrev
* fLenPrevEdge
),
2618 aCurrentPoint
+ (aVectorNext
* fLenNextEdge
));
2624 // not parallel or same direction, set vectors and length
2625 const B2DVector
aNormalizedPerpendicular(getNormalizedPerpendicular(aVectorPrev
+ aVectorNext
));
2627 if(aOrientation
== B2VectorOrientation::Positive
)
2629 rCandidate
.setControlPoints(nIndex
,
2630 aCurrentPoint
- (aNormalizedPerpendicular
* fLenPrev
),
2631 aCurrentPoint
+ (aNormalizedPerpendicular
* fLenNext
));
2635 rCandidate
.setControlPoints(nIndex
,
2636 aCurrentPoint
+ (aNormalizedPerpendicular
* fLenPrev
),
2637 aCurrentPoint
- (aNormalizedPerpendicular
* fLenNext
));
2645 case B2VectorContinuity::C2
:
2647 if(rCandidate
.isPrevControlPointUsed(nIndex
) && rCandidate
.isNextControlPointUsed(nIndex
))
2649 // lengths both exist since both are used
2650 B2DVector
aVectorPrev(rCandidate
.getPrevControlPoint(nIndex
) - aCurrentPoint
);
2651 B2DVector
aVectorNext(rCandidate
.getNextControlPoint(nIndex
) - aCurrentPoint
);
2652 const double fCommonLength((aVectorPrev
.getLength() + aVectorNext
.getLength()) / 2.0);
2653 aVectorPrev
.normalize();
2654 aVectorNext
.normalize();
2655 const B2VectorOrientation
aOrientation(getOrientation(aVectorPrev
, aVectorNext
));
2657 if(aOrientation
== B2VectorOrientation::Neutral
&& aVectorPrev
.scalar(aVectorNext
) < 0.0)
2659 // parallel and opposite direction; set length. Use one direction for better numerical correctness
2660 const B2DVector
aScaledDirection(aVectorPrev
* fCommonLength
);
2662 rCandidate
.setControlPoints(nIndex
,
2663 aCurrentPoint
+ aScaledDirection
,
2664 aCurrentPoint
- aScaledDirection
);
2668 // not parallel or same direction, set vectors and length
2669 const B2DVector
aNormalizedPerpendicular(getNormalizedPerpendicular(aVectorPrev
+ aVectorNext
));
2670 const B2DVector
aPerpendicular(aNormalizedPerpendicular
* fCommonLength
);
2672 if(aOrientation
== B2VectorOrientation::Positive
)
2674 rCandidate
.setControlPoints(nIndex
,
2675 aCurrentPoint
- aPerpendicular
,
2676 aCurrentPoint
+ aPerpendicular
);
2680 rCandidate
.setControlPoints(nIndex
,
2681 aCurrentPoint
+ aPerpendicular
,
2682 aCurrentPoint
- aPerpendicular
);
2696 B2DPolygon
growInNormalDirection(const B2DPolygon
& rCandidate
, double fValue
)
2700 if(rCandidate
.areControlPointsUsed())
2702 // call myself recursively with subdivided input
2703 const B2DPolygon
aCandidate(adaptiveSubdivideByAngle(rCandidate
));
2704 return growInNormalDirection(aCandidate
, fValue
);
2709 const sal_uInt32
nPointCount(rCandidate
.count());
2713 B2DPoint
aPrev(rCandidate
.getB2DPoint(nPointCount
- 1));
2714 B2DPoint
aCurrent(rCandidate
.getB2DPoint(0));
2716 for(sal_uInt32
a(0); a
< nPointCount
; a
++)
2718 const B2DPoint
aNext(rCandidate
.getB2DPoint(a
+ 1 == nPointCount
? 0 : a
+ 1));
2719 const B2DVector
aBack(aPrev
- aCurrent
);
2720 const B2DVector
aForw(aNext
- aCurrent
);
2721 const B2DVector
aPerpBack(getNormalizedPerpendicular(aBack
));
2722 const B2DVector
aPerpForw(getNormalizedPerpendicular(aForw
));
2723 B2DVector
aDirection(aPerpBack
- aPerpForw
);
2724 aDirection
.normalize();
2725 aDirection
*= fValue
;
2726 aRetval
.append(aCurrent
+ aDirection
);
2728 // prepare next step
2734 // copy closed state
2735 aRetval
.setClosed(rCandidate
.isClosed());
2746 B2DPolygon
reSegmentPolygon(const B2DPolygon
& rCandidate
, sal_uInt32 nSegments
)
2749 const sal_uInt32
nPointCount(rCandidate
.count());
2751 if(nPointCount
&& nSegments
)
2753 // get current segment count
2754 const sal_uInt32
nSegmentCount(rCandidate
.isClosed() ? nPointCount
: nPointCount
- 1);
2756 if(nSegmentCount
== nSegments
)
2758 aRetval
= rCandidate
;
2762 const double fLength(getLength(rCandidate
));
2763 const sal_uInt32
nLoopCount(rCandidate
.isClosed() ? nSegments
: nSegments
+ 1);
2765 for(sal_uInt32
a(0); a
< nLoopCount
; a
++)
2767 const double fRelativePos(static_cast<double>(a
) / static_cast<double>(nSegments
)); // 0.0 .. 1.0
2768 const B2DPoint
aNewPoint(getPositionRelative(rCandidate
, fRelativePos
, fLength
));
2769 aRetval
.append(aNewPoint
);
2773 aRetval
.setClosed(rCandidate
.isClosed());
2780 B2DPolygon
interpolate(const B2DPolygon
& rOld1
, const B2DPolygon
& rOld2
, double t
)
2782 OSL_ENSURE(rOld1
.count() == rOld2
.count(), "B2DPolygon interpolate: Different geometry (!)");
2784 if(fTools::lessOrEqual(t
, 0.0) || rOld1
== rOld2
)
2788 else if(fTools::moreOrEqual(t
, 1.0))
2795 const bool bInterpolateVectors(rOld1
.areControlPointsUsed() || rOld2
.areControlPointsUsed());
2796 aRetval
.setClosed(rOld1
.isClosed() && rOld2
.isClosed());
2798 for(sal_uInt32
a(0); a
< rOld1
.count(); a
++)
2800 aRetval
.append(interpolate(rOld1
.getB2DPoint(a
), rOld2
.getB2DPoint(a
), t
));
2802 if(bInterpolateVectors
)
2804 aRetval
.setPrevControlPoint(a
, interpolate(rOld1
.getPrevControlPoint(a
), rOld2
.getPrevControlPoint(a
), t
));
2805 aRetval
.setNextControlPoint(a
, interpolate(rOld1
.getNextControlPoint(a
), rOld2
.getNextControlPoint(a
), t
));
2814 B2DPolygon
simplifyCurveSegments(const B2DPolygon
& rCandidate
)
2816 const sal_uInt32
nPointCount(rCandidate
.count());
2818 if(nPointCount
&& rCandidate
.areControlPointsUsed())
2821 const sal_uInt32
nEdgeCount(rCandidate
.isClosed() ? nPointCount
: nPointCount
- 1);
2823 B2DCubicBezier aBezier
;
2824 aBezier
.setStartPoint(rCandidate
.getB2DPoint(0));
2826 // try to avoid costly reallocations
2827 aRetval
.reserve( nEdgeCount
+1);
2830 aRetval
.append(aBezier
.getStartPoint());
2832 for(sal_uInt32
a(0); a
< nEdgeCount
; a
++)
2834 // get values for edge
2835 const sal_uInt32
nNextIndex((a
+ 1) % nPointCount
);
2836 aBezier
.setEndPoint(rCandidate
.getB2DPoint(nNextIndex
));
2837 aBezier
.setControlPointA(rCandidate
.getNextControlPoint(a
));
2838 aBezier
.setControlPointB(rCandidate
.getPrevControlPoint(nNextIndex
));
2839 aBezier
.testAndSolveTrivialBezier();
2842 if(aBezier
.isBezier())
2844 // add edge with control vectors
2845 aRetval
.appendBezierSegment(aBezier
.getControlPointA(), aBezier
.getControlPointB(), aBezier
.getEndPoint());
2850 aRetval
.append(aBezier
.getEndPoint());
2854 aBezier
.setStartPoint(aBezier
.getEndPoint());
2857 if(rCandidate
.isClosed())
2859 // set closed flag, rescue control point and correct last double point
2860 closeWithGeometryChange(aRetval
);
2871 // makes the given indexed point the new polygon start point. To do that, the points in the
2872 // polygon will be rotated. This is only valid for closed polygons, for non-closed ones
2873 // an assertion will be triggered
2874 B2DPolygon
makeStartPoint(const B2DPolygon
& rCandidate
, sal_uInt32 nIndexOfNewStatPoint
)
2876 const sal_uInt32
nPointCount(rCandidate
.count());
2878 if(nPointCount
> 2 && nIndexOfNewStatPoint
!= 0 && nIndexOfNewStatPoint
< nPointCount
)
2880 OSL_ENSURE(rCandidate
.isClosed(), "makeStartPoint: only valid for closed polygons (!)");
2883 for(sal_uInt32
a(0); a
< nPointCount
; a
++)
2885 const sal_uInt32
nSourceIndex((a
+ nIndexOfNewStatPoint
) % nPointCount
);
2886 aRetval
.append(rCandidate
.getB2DPoint(nSourceIndex
));
2888 if(rCandidate
.areControlPointsUsed())
2890 aRetval
.setPrevControlPoint(a
, rCandidate
.getPrevControlPoint(nSourceIndex
));
2891 aRetval
.setNextControlPoint(a
, rCandidate
.getNextControlPoint(nSourceIndex
));
2901 B2DPolygon
createEdgesOfGivenLength(const B2DPolygon
& rCandidate
, double fLength
, double fStart
, double fEnd
)
2910 if(!fTools::equalZero(fLength
))
2927 // iterate and consume pieces with fLength. First subdivide to reduce input to line segments
2928 const B2DPolygon
aCandidate(rCandidate
.areControlPointsUsed() ? rCandidate
.getDefaultAdaptiveSubdivision() : rCandidate
);
2929 const sal_uInt32
nPointCount(aCandidate
.count());
2933 const bool bEndActive(!fTools::equalZero(fEnd
));
2934 const sal_uInt32
nEdgeCount(aCandidate
.isClosed() ? nPointCount
: nPointCount
- 1);
2935 B2DPoint
aCurrent(aCandidate
.getB2DPoint(0));
2936 double fPositionInEdge(fStart
);
2937 double fAbsolutePosition(fStart
);
2939 for(sal_uInt32
a(0); a
< nEdgeCount
; a
++)
2941 const sal_uInt32
nNextIndex((a
+ 1) % nPointCount
);
2942 const B2DPoint
aNext(aCandidate
.getB2DPoint(nNextIndex
));
2943 const B2DVector
aEdge(aNext
- aCurrent
);
2944 double fEdgeLength(aEdge
.getLength());
2946 if(!fTools::equalZero(fEdgeLength
))
2948 while(fTools::less(fPositionInEdge
, fEdgeLength
))
2950 // move position on edge forward as long as on edge
2951 const double fScalar(fPositionInEdge
/ fEdgeLength
);
2952 aRetval
.append(aCurrent
+ (aEdge
* fScalar
));
2953 fPositionInEdge
+= fLength
;
2957 fAbsolutePosition
+= fLength
;
2959 if(fTools::more(fAbsolutePosition
, fEnd
))
2966 // subtract length of current edge
2967 fPositionInEdge
-= fEdgeLength
;
2970 if(bEndActive
&& fTools::more(fAbsolutePosition
, fEnd
))
2975 // prepare next step
2979 // keep closed state
2980 aRetval
.setClosed(aCandidate
.isClosed());
2984 // source polygon has only one point, return unchanged
2985 aRetval
= aCandidate
;
2992 B2DPolygon
createWaveline(const B2DPolygon
& rCandidate
, double fWaveWidth
, double fWaveHeight
)
2996 if(fWaveWidth
< 0.0)
3001 if(fWaveHeight
< 0.0)
3006 const bool bHasWidth(!fTools::equalZero(fWaveWidth
));
3010 const bool bHasHeight(!fTools::equalZero(fWaveHeight
));
3013 // width and height, create waveline. First subdivide to reduce input to line segments
3014 // of WaveWidth. Last segment may be missing. If this turns out to be a problem, it
3015 // may be added here again using the original last point from rCandidate. It may
3016 // also be the case that rCandidate was closed. To simplify things it is handled here
3017 // as if it was opened.
3018 // Result from createEdgesOfGivenLength contains no curved segments, handle as straight
3020 const B2DPolygon
aEqualLenghEdges(createEdgesOfGivenLength(rCandidate
, fWaveWidth
));
3021 const sal_uInt32
nPointCount(aEqualLenghEdges
.count());
3025 // iterate over straight edges, add start point
3026 B2DPoint
aCurrent(aEqualLenghEdges
.getB2DPoint(0));
3027 aRetval
.append(aCurrent
);
3029 for(sal_uInt32
a(0); a
< nPointCount
- 1; a
++)
3031 const sal_uInt32
nNextIndex((a
+ 1) % nPointCount
);
3032 const B2DPoint
aNext(aEqualLenghEdges
.getB2DPoint(nNextIndex
));
3033 const B2DVector
aEdge(aNext
- aCurrent
);
3034 const B2DVector
aPerpendicular(getNormalizedPerpendicular(aEdge
));
3035 const B2DVector
aControlOffset((aEdge
* 0.467308) - (aPerpendicular
* fWaveHeight
));
3037 // add curve segment
3038 aRetval
.appendBezierSegment(
3039 aCurrent
+ aControlOffset
,
3040 aNext
- aControlOffset
,
3043 // prepare next step
3050 // width but no height -> return original polygon
3051 aRetval
= rCandidate
;
3056 // no width -> no waveline, stay empty and return
3062 // snap points of horizontal or vertical edges to discrete values
3063 B2DPolygon
snapPointsOfHorizontalOrVerticalEdges(const B2DPolygon
& rCandidate
)
3065 const sal_uInt32
nPointCount(rCandidate
.count());
3069 // Start by copying the source polygon to get a writeable copy. The closed state is
3070 // copied by aRetval's initialisation, too, so no need to copy it in this method
3071 B2DPolygon
aRetval(rCandidate
);
3073 // prepare geometry data. Get rounded from original
3074 B2ITuple
aPrevTuple(basegfx::fround(rCandidate
.getB2DPoint(nPointCount
- 1)));
3075 B2DPoint
aCurrPoint(rCandidate
.getB2DPoint(0));
3076 B2ITuple
aCurrTuple(basegfx::fround(aCurrPoint
));
3078 // loop over all points. This will also snap the implicit closing edge
3079 // even when not closed, but that's no problem here
3080 for(sal_uInt32
a(0); a
< nPointCount
; a
++)
3082 // get next point. Get rounded from original
3083 const bool bLastRun(a
+ 1 == nPointCount
);
3084 const sal_uInt32
nNextIndex(bLastRun
? 0 : a
+ 1);
3085 const B2DPoint
aNextPoint(rCandidate
.getB2DPoint(nNextIndex
));
3086 const B2ITuple
aNextTuple(basegfx::fround(aNextPoint
));
3089 const bool bPrevVertical(aPrevTuple
.getX() == aCurrTuple
.getX());
3090 const bool bNextVertical(aNextTuple
.getX() == aCurrTuple
.getX());
3091 const bool bPrevHorizontal(aPrevTuple
.getY() == aCurrTuple
.getY());
3092 const bool bNextHorizontal(aNextTuple
.getY() == aCurrTuple
.getY());
3093 const bool bSnapX(bPrevVertical
|| bNextVertical
);
3094 const bool bSnapY(bPrevHorizontal
|| bNextHorizontal
);
3096 if(bSnapX
|| bSnapY
)
3098 const B2DPoint
aSnappedPoint(
3099 bSnapX
? aCurrTuple
.getX() : aCurrPoint
.getX(),
3100 bSnapY
? aCurrTuple
.getY() : aCurrPoint
.getY());
3102 aRetval
.setB2DPoint(a
, aSnappedPoint
);
3105 // prepare next point
3108 aPrevTuple
= aCurrTuple
;
3109 aCurrPoint
= aNextPoint
;
3110 aCurrTuple
= aNextTuple
;
3122 B2DVector
getTangentEnteringPoint(const B2DPolygon
& rCandidate
, sal_uInt32 nIndex
)
3124 B2DVector
aRetval(0.0, 0.0);
3125 const sal_uInt32
nCount(rCandidate
.count());
3127 if(nIndex
>= nCount
)
3133 // start immediately at prev point compared to nIndex
3134 const bool bClosed(rCandidate
.isClosed());
3135 sal_uInt32
nPrev(bClosed
? (nIndex
+ nCount
- 1) % nCount
: nIndex
? nIndex
- 1 : nIndex
);
3139 // no previous, done
3143 B2DCubicBezier aSegment
;
3145 // go backward in the polygon; if closed, maximal back to start index (nIndex); if not closed,
3146 // until zero. Use nIndex as stop criteria
3147 while(nPrev
!= nIndex
)
3149 // get BezierSegment and tangent at the *end* of segment
3150 rCandidate
.getBezierSegment(nPrev
, aSegment
);
3151 aRetval
= aSegment
.getTangent(1.0);
3153 if(!aRetval
.equalZero())
3155 // if we have a tangent, return it
3159 // prepare index before checked one
3160 nPrev
= bClosed
? (nPrev
+ nCount
- 1) % nCount
: nPrev
? nPrev
- 1 : nIndex
;
3166 B2DVector
getTangentLeavingPoint(const B2DPolygon
& rCandidate
, sal_uInt32 nIndex
)
3168 B2DVector
aRetval(0.0, 0.0);
3169 const sal_uInt32
nCount(rCandidate
.count());
3171 if(nIndex
>= nCount
)
3178 const bool bClosed(rCandidate
.isClosed());
3179 sal_uInt32
nCurrent(nIndex
);
3180 B2DCubicBezier aSegment
;
3182 // go forward; if closed, do this until once around and back at start index (nIndex); if not
3183 // closed, until last point (nCount - 1). Use nIndex as stop criteria
3186 // get BezierSegment and tangent at the *beginning* of segment
3187 rCandidate
.getBezierSegment(nCurrent
, aSegment
);
3188 aRetval
= aSegment
.getTangent(0.0);
3190 if(!aRetval
.equalZero())
3192 // if we have a tangent, return it
3196 // prepare next index
3197 nCurrent
= bClosed
? (nCurrent
+ 1) % nCount
: nCurrent
+ 1 < nCount
? nCurrent
+ 1 : nIndex
;
3199 while(nCurrent
!= nIndex
);
3204 // converters for css::drawing::PointSequence
3206 B2DPolygon
UnoPointSequenceToB2DPolygon(
3207 const css::drawing::PointSequence
& rPointSequenceSource
)
3210 const sal_uInt32
nLength(rPointSequenceSource
.getLength());
3214 aRetval
.reserve(nLength
);
3215 const css::awt::Point
* pArray
= rPointSequenceSource
.getConstArray();
3216 const css::awt::Point
* pArrayEnd
= pArray
+ rPointSequenceSource
.getLength();
3218 for(;pArray
!= pArrayEnd
; pArray
++)
3220 aRetval
.append(B2DPoint(pArray
->X
, pArray
->Y
));
3223 // check for closed state flag
3224 utils::checkClosed(aRetval
);
3230 void B2DPolygonToUnoPointSequence(
3231 const B2DPolygon
& rPolygon
,
3232 css::drawing::PointSequence
& rPointSequenceRetval
)
3234 B2DPolygon
aPolygon(rPolygon
);
3236 if(aPolygon
.areControlPointsUsed())
3238 OSL_ENSURE(false, "B2DPolygonToUnoPointSequence: Source contains bezier segments, wrong UNO API data type may be used (!)");
3239 aPolygon
= aPolygon
.getDefaultAdaptiveSubdivision();
3242 const sal_uInt32
nPointCount(aPolygon
.count());
3246 // Take closed state into account, the API polygon still uses the old closed definition
3247 // with last/first point are identical (cannot hold information about open polygons with identical
3248 // first and last point, though)
3249 const bool bIsClosed(aPolygon
.isClosed());
3251 rPointSequenceRetval
.realloc(bIsClosed
? nPointCount
+ 1 : nPointCount
);
3252 css::awt::Point
* pSequence
= rPointSequenceRetval
.getArray();
3254 for(sal_uInt32
b(0); b
< nPointCount
; b
++)
3256 const B2DPoint
aPoint(aPolygon
.getB2DPoint(b
));
3257 const css::awt::Point
aAPIPoint(fround(aPoint
.getX()), fround(aPoint
.getY()));
3259 *pSequence
= aAPIPoint
;
3263 // copy first point if closed
3266 *pSequence
= *rPointSequenceRetval
.getArray();
3271 rPointSequenceRetval
.realloc(0);
3275 // converters for css::drawing::PointSequence and
3276 // css::drawing::FlagSequence to B2DPolygon (curved polygons)
3278 B2DPolygon
UnoPolygonBezierCoordsToB2DPolygon(
3279 const css::drawing::PointSequence
& rPointSequenceSource
,
3280 const css::drawing::FlagSequence
& rFlagSequenceSource
)
3282 const sal_uInt32
nCount(static_cast<sal_uInt32
>(rPointSequenceSource
.getLength()));
3283 OSL_ENSURE(nCount
== static_cast<sal_uInt32
>(rFlagSequenceSource
.getLength()),
3284 "UnoPolygonBezierCoordsToB2DPolygon: Unequal count of Points and Flags (!)");
3286 // prepare new polygon
3288 const css::awt::Point
* pPointSequence
= rPointSequenceSource
.getConstArray();
3289 const css::drawing::PolygonFlags
* pFlagSequence
= rFlagSequenceSource
.getConstArray();
3291 // get first point and flag
3292 B2DPoint
aNewCoordinatePair(pPointSequence
->X
, pPointSequence
->Y
); pPointSequence
++;
3293 css::drawing::PolygonFlags
ePolygonFlag(*pFlagSequence
); pFlagSequence
++;
3297 // first point is not allowed to be a control point
3298 OSL_ENSURE(ePolygonFlag
!= css::drawing::PolygonFlags_CONTROL
,
3299 "UnoPolygonBezierCoordsToB2DPolygon: Start point is a control point, illegal input polygon (!)");
3301 // add first point as start point
3302 aRetval
.append(aNewCoordinatePair
);
3304 for(sal_uInt32
b(1); b
< nCount
;)
3307 bool bControlA(false);
3308 bool bControlB(false);
3310 // get next point and flag
3311 aNewCoordinatePair
= B2DPoint(pPointSequence
->X
, pPointSequence
->Y
);
3312 ePolygonFlag
= *pFlagSequence
;
3313 pPointSequence
++; pFlagSequence
++; b
++;
3315 if(b
< nCount
&& ePolygonFlag
== css::drawing::PolygonFlags_CONTROL
)
3317 aControlA
= aNewCoordinatePair
;
3320 // get next point and flag
3321 aNewCoordinatePair
= B2DPoint(pPointSequence
->X
, pPointSequence
->Y
);
3322 ePolygonFlag
= *pFlagSequence
;
3323 pPointSequence
++; pFlagSequence
++; b
++;
3326 if(b
< nCount
&& ePolygonFlag
== css::drawing::PolygonFlags_CONTROL
)
3328 aControlB
= aNewCoordinatePair
;
3331 // get next point and flag
3332 aNewCoordinatePair
= B2DPoint(pPointSequence
->X
, pPointSequence
->Y
);
3333 ePolygonFlag
= *pFlagSequence
;
3334 pPointSequence
++; pFlagSequence
++; b
++;
3337 // two or no control points are consumed, another one would be an error.
3338 // It's also an error if only one control point was read
3339 SAL_WARN_IF(ePolygonFlag
== css::drawing::PolygonFlags_CONTROL
|| bControlA
!= bControlB
,
3340 "basegfx", "UnoPolygonBezierCoordsToB2DPolygon: Illegal source polygon (!)");
3342 // the previous writes used the B2DPolyPoygon -> utils::PolyPolygon converter
3343 // which did not create minimal PolyPolygons, but created all control points
3344 // as null vectors (identical points). Because of the former P(CA)(CB)-norm of
3345 // B2DPolygon and it's unused sign of being the zero-vector and CA and CB being
3346 // relative to P, an empty edge was exported as P == CA == CB. Luckily, the new
3347 // export format can be read without errors by the old OOo-versions, so we need only
3348 // to correct here at read and do not need to export a wrong but compatible version
3351 && aControlA
.equal(aControlB
)
3352 && aControlA
.equal(aRetval
.getB2DPoint(aRetval
.count() - 1)))
3360 aRetval
.appendBezierSegment(aControlA
, aControlB
, aNewCoordinatePair
);
3365 aRetval
.append(aNewCoordinatePair
);
3369 // #i72807# API import uses old line start/end-equal definition for closed,
3370 // so we need to correct this to closed state here
3371 checkClosed(aRetval
);
3376 void B2DPolygonToUnoPolygonBezierCoords(
3377 const B2DPolygon
& rPolygon
,
3378 css::drawing::PointSequence
& rPointSequenceRetval
,
3379 css::drawing::FlagSequence
& rFlagSequenceRetval
)
3381 const sal_uInt32
nPointCount(rPolygon
.count());
3385 const bool bCurve(rPolygon
.areControlPointsUsed());
3386 const bool bClosed(rPolygon
.isClosed());
3390 // calculate target point count
3391 const sal_uInt32
nLoopCount(bClosed
? nPointCount
: nPointCount
- 1);
3395 // prepare target data. The real needed number of target points (and flags)
3396 // could only be calculated by using two loops, so use dynamic memory
3397 std::vector
< css::awt::Point
> aCollectPoints
;
3398 std::vector
< css::drawing::PolygonFlags
> aCollectFlags
;
3400 // reserve maximum creatable points
3401 const sal_uInt32
nMaxTargetCount((nLoopCount
* 3) + 1);
3402 aCollectPoints
.reserve(nMaxTargetCount
);
3403 aCollectFlags
.reserve(nMaxTargetCount
);
3405 // prepare current bezier segment by setting start point
3406 B2DCubicBezier aBezierSegment
;
3407 aBezierSegment
.setStartPoint(rPolygon
.getB2DPoint(0));
3409 for(sal_uInt32
a(0); a
< nLoopCount
; a
++)
3411 // add current point (always) and remember StartPointIndex for evtl. later corrections
3412 const sal_uInt32
nStartPointIndex(aCollectPoints
.size());
3413 aCollectPoints
.emplace_back(
3414 fround(aBezierSegment
.getStartPoint().getX()),
3415 fround(aBezierSegment
.getStartPoint().getY()));
3416 aCollectFlags
.push_back(css::drawing::PolygonFlags_NORMAL
);
3418 // prepare next segment
3419 const sal_uInt32
nNextIndex((a
+ 1) % nPointCount
);
3420 aBezierSegment
.setEndPoint(rPolygon
.getB2DPoint(nNextIndex
));
3421 aBezierSegment
.setControlPointA(rPolygon
.getNextControlPoint(a
));
3422 aBezierSegment
.setControlPointB(rPolygon
.getPrevControlPoint(nNextIndex
));
3424 if(aBezierSegment
.isBezier())
3426 // if bezier is used, add always two control points due to the old schema
3427 aCollectPoints
.emplace_back(
3428 fround(aBezierSegment
.getControlPointA().getX()),
3429 fround(aBezierSegment
.getControlPointA().getY()));
3430 aCollectFlags
.push_back(css::drawing::PolygonFlags_CONTROL
);
3432 aCollectPoints
.emplace_back(
3433 fround(aBezierSegment
.getControlPointB().getX()),
3434 fround(aBezierSegment
.getControlPointB().getY()));
3435 aCollectFlags
.push_back(css::drawing::PolygonFlags_CONTROL
);
3438 // test continuity with previous control point to set flag value
3439 if(aBezierSegment
.getControlPointA() != aBezierSegment
.getStartPoint() && (bClosed
|| a
))
3441 const B2VectorContinuity
eCont(rPolygon
.getContinuityInPoint(a
));
3443 if(eCont
== B2VectorContinuity::C1
)
3445 aCollectFlags
[nStartPointIndex
] = css::drawing::PolygonFlags_SMOOTH
;
3447 else if(eCont
== B2VectorContinuity::C2
)
3449 aCollectFlags
[nStartPointIndex
] = css::drawing::PolygonFlags_SYMMETRIC
;
3453 // prepare next loop
3454 aBezierSegment
.setStartPoint(aBezierSegment
.getEndPoint());
3459 // add first point again as closing point due to old definition
3460 aCollectPoints
.push_back(aCollectPoints
[0]);
3461 aCollectFlags
.push_back(css::drawing::PolygonFlags_NORMAL
);
3465 // add last point as closing point
3466 const B2DPoint
aClosingPoint(rPolygon
.getB2DPoint(nPointCount
- 1));
3467 aCollectPoints
.emplace_back(
3468 fround(aClosingPoint
.getX()),
3469 fround(aClosingPoint
.getY()));
3470 aCollectFlags
.push_back(css::drawing::PolygonFlags_NORMAL
);
3473 // copy collected data to target arrays
3474 const sal_uInt32
nTargetCount(aCollectPoints
.size());
3475 OSL_ENSURE(nTargetCount
== aCollectFlags
.size(), "Unequal Point and Flag count (!)");
3477 rPointSequenceRetval
.realloc(static_cast<sal_Int32
>(nTargetCount
));
3478 rFlagSequenceRetval
.realloc(static_cast<sal_Int32
>(nTargetCount
));
3479 css::awt::Point
* pPointSequence
= rPointSequenceRetval
.getArray();
3480 css::drawing::PolygonFlags
* pFlagSequence
= rFlagSequenceRetval
.getArray();
3482 for(sal_uInt32
a(0); a
< nTargetCount
; a
++)
3484 *pPointSequence
= aCollectPoints
[a
];
3485 *pFlagSequence
= aCollectFlags
[a
];
3493 // straightforward point list creation
3494 const sal_uInt32
nTargetCount(nPointCount
+ (bClosed
? 1 : 0));
3496 rPointSequenceRetval
.realloc(static_cast<sal_Int32
>(nTargetCount
));
3497 rFlagSequenceRetval
.realloc(static_cast<sal_Int32
>(nTargetCount
));
3499 css::awt::Point
* pPointSequence
= rPointSequenceRetval
.getArray();
3500 css::drawing::PolygonFlags
* pFlagSequence
= rFlagSequenceRetval
.getArray();
3502 for(sal_uInt32
a(0); a
< nPointCount
; a
++)
3504 const B2DPoint
aB2DPoint(rPolygon
.getB2DPoint(a
));
3505 const css::awt::Point
aAPIPoint(
3506 fround(aB2DPoint
.getX()),
3507 fround(aB2DPoint
.getY()));
3509 *pPointSequence
= aAPIPoint
;
3510 *pFlagSequence
= css::drawing::PolygonFlags_NORMAL
;
3517 // add first point as closing point
3518 *pPointSequence
= *rPointSequenceRetval
.getConstArray();
3519 *pFlagSequence
= css::drawing::PolygonFlags_NORMAL
;
3525 rPointSequenceRetval
.realloc(0);
3526 rFlagSequenceRetval
.realloc(0);
3530 } // end of namespace utils
3531 } // end of namespace basegfx
3533 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */