LibreOffice Module basegfx (master) 1
b2dpolygontools.cxx
Go to the documentation of this file.
1/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2/*
3 * This file is part of the LibreOffice project.
4 *
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 *
9 * This file incorporates work covered by the following license notice:
10 *
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
18 */
19#include <numeric>
20#include <algorithm>
21
24#include <osl/diagnose.h>
25#include <rtl/math.hxx>
26#include <sal/log.hxx>
36
37// #i37443#
38#define ANGLE_BOUND_START_VALUE (2.25)
39#define ANGLE_BOUND_MINIMUM_VALUE (0.1)
40#define STEPSPERQUARTER (3)
41
42namespace basegfx::utils
43{
45 {
46 if(!rCandidate.isClosed())
47 return;
48
49 if(rCandidate.count())
50 {
51 rCandidate.append(rCandidate.getB2DPoint(0));
52
53 if(rCandidate.areControlPointsUsed() && rCandidate.isPrevControlPointUsed(0))
54 {
55 rCandidate.setPrevControlPoint(rCandidate.count() - 1, rCandidate.getPrevControlPoint(0));
56 rCandidate.resetPrevControlPoint(0);
57 }
58 }
59
60 rCandidate.setClosed(false);
61 }
62
64 {
65 if(rCandidate.isClosed())
66 return;
67
68 while(rCandidate.count() > 1 && rCandidate.getB2DPoint(0) == rCandidate.getB2DPoint(rCandidate.count() - 1))
69 {
70 if(rCandidate.areControlPointsUsed() && rCandidate.isPrevControlPointUsed(rCandidate.count() - 1))
71 {
72 rCandidate.setPrevControlPoint(0, rCandidate.getPrevControlPoint(rCandidate.count() - 1));
73 }
74
75 rCandidate.remove(rCandidate.count() - 1);
76 }
77
78 rCandidate.setClosed(true);
79 }
80
81 void checkClosed(B2DPolygon& rCandidate)
82 {
83 // #i80172# Removed unnecessary assertion
84 // OSL_ENSURE(!rCandidate.isClosed(), "checkClosed: already closed (!)");
85
86 if(rCandidate.count() > 1 && rCandidate.getB2DPoint(0) == rCandidate.getB2DPoint(rCandidate.count() - 1))
87 {
88 closeWithGeometryChange(rCandidate);
89 }
90 }
91
92 // Get successor and predecessor indices. Returning the same index means there
93 // is none. Same for successor.
94 sal_uInt32 getIndexOfPredecessor(sal_uInt32 nIndex, const B2DPolygon& rCandidate)
95 {
96 OSL_ENSURE(nIndex < rCandidate.count(), "getIndexOfPredecessor: Access to polygon out of range (!)");
97
98 if(nIndex)
99 {
100 return nIndex - 1;
101 }
102 else if(rCandidate.count())
103 {
104 return rCandidate.count() - 1;
105 }
106 else
107 {
108 return nIndex;
109 }
110 }
111
112 sal_uInt32 getIndexOfSuccessor(sal_uInt32 nIndex, const B2DPolygon& rCandidate)
113 {
114 OSL_ENSURE(nIndex < rCandidate.count(), "getIndexOfPredecessor: Access to polygon out of range (!)");
115
116 if(nIndex + 1 < rCandidate.count())
117 {
118 return nIndex + 1;
119 }
120 else if(nIndex + 1 == rCandidate.count())
121 {
122 return 0;
123 }
124 else
125 {
126 return nIndex;
127 }
128 }
129
131 {
133
134 if(rCandidate.count() > 2 || rCandidate.areControlPointsUsed())
135 {
136 const double fSignedArea(getSignedArea(rCandidate));
137
138 if(fTools::equalZero(fSignedArea))
139 {
140 // B2VectorOrientation::Neutral, already set
141 }
142 if(fSignedArea > 0.0)
143 {
145 }
146 else if(fSignedArea < 0.0)
147 {
149 }
150 }
151
152 return eRetval;
153 }
154
155 B2VectorContinuity getContinuityInPoint(const B2DPolygon& rCandidate, sal_uInt32 nIndex)
156 {
157 return rCandidate.getContinuityInPoint(nIndex);
158 }
159
160 B2DPolygon adaptiveSubdivideByDistance(const B2DPolygon& rCandidate, double fDistanceBound, int nRecurseLimit)
161 {
162 if(rCandidate.areControlPointsUsed())
163 {
164 const sal_uInt32 nPointCount(rCandidate.count());
165 B2DPolygon aRetval;
166
167 if(nPointCount)
168 {
169 // prepare edge-oriented loop
170 const sal_uInt32 nEdgeCount(rCandidate.isClosed() ? nPointCount : nPointCount - 1);
171 B2DCubicBezier aBezier;
172 aBezier.setStartPoint(rCandidate.getB2DPoint(0));
173
174 // perf: try to avoid too many reallocations by guessing the result's pointcount
175 aRetval.reserve(nPointCount*4);
176
177 // add start point (always)
178 aRetval.append(aBezier.getStartPoint());
179
180 for(sal_uInt32 a(0); a < nEdgeCount; a++)
181 {
182 // get next and control points
183 const sal_uInt32 nNextIndex((a + 1) % nPointCount);
184 aBezier.setEndPoint(rCandidate.getB2DPoint(nNextIndex));
185 aBezier.setControlPointA(rCandidate.getNextControlPoint(a));
186 aBezier.setControlPointB(rCandidate.getPrevControlPoint(nNextIndex));
188
189 if(aBezier.isBezier())
190 {
191 // add curved edge and generate DistanceBound
192 double fBound(0.0);
193
194 if(fDistanceBound == 0.0)
195 {
196 // If not set, use B2DCubicBezier functionality to guess a rough value
197 const double fRoughLength((aBezier.getEdgeLength() + aBezier.getControlPolygonLength()) / 2.0);
198
199 // take 1/100th of the rough curve length
200 fBound = fRoughLength * 0.01;
201 }
202 else
203 {
204 // use given bound value
205 fBound = fDistanceBound;
206 }
207
208 // make sure bound value is not too small. The base units are 1/100th mm, thus
209 // just make sure it's not smaller then 1/100th of that
210 if(fBound < 0.01)
211 {
212 fBound = 0.01;
213 }
214
215 // call adaptive subdivide which adds edges to aRetval accordingly
216 aBezier.adaptiveSubdivideByDistance(aRetval, fBound, nRecurseLimit);
217 }
218 else
219 {
220 // add non-curved edge
221 aRetval.append(aBezier.getEndPoint());
222 }
223
224 // prepare next step
225 aBezier.setStartPoint(aBezier.getEndPoint());
226 }
227
228 if(rCandidate.isClosed())
229 {
230 // set closed flag and correct last point (which is added double now).
232 }
233 }
234
235 return aRetval;
236 }
237 else
238 {
239 return rCandidate;
240 }
241 }
242
243 B2DPolygon adaptiveSubdivideByAngle(const B2DPolygon& rCandidate, double fAngleBound)
244 {
245 if(rCandidate.areControlPointsUsed())
246 {
247 const sal_uInt32 nPointCount(rCandidate.count());
248 B2DPolygon aRetval;
249
250 if(nPointCount)
251 {
252 // prepare edge-oriented loop
253 const sal_uInt32 nEdgeCount(rCandidate.isClosed() ? nPointCount : nPointCount - 1);
254 B2DCubicBezier aBezier;
255 aBezier.setStartPoint(rCandidate.getB2DPoint(0));
256
257 // perf: try to avoid too many reallocations by guessing the result's pointcount
258 aRetval.reserve(nPointCount*4);
259
260 // add start point (always)
261 aRetval.append(aBezier.getStartPoint());
262
263 // #i37443# prepare convenient AngleBound if none was given
264 if(fAngleBound == 0.0)
265 {
266 fAngleBound = ANGLE_BOUND_START_VALUE;
267 }
268 else if(fTools::less(fAngleBound, ANGLE_BOUND_MINIMUM_VALUE))
269 {
270 fAngleBound = 0.1;
271 }
272
273 for(sal_uInt32 a(0); a < nEdgeCount; a++)
274 {
275 // get next and control points
276 const sal_uInt32 nNextIndex((a + 1) % nPointCount);
277 aBezier.setEndPoint(rCandidate.getB2DPoint(nNextIndex));
278 aBezier.setControlPointA(rCandidate.getNextControlPoint(a));
279 aBezier.setControlPointB(rCandidate.getPrevControlPoint(nNextIndex));
281
282 if(aBezier.isBezier())
283 {
284 // call adaptive subdivide
285 aBezier.adaptiveSubdivideByAngle(aRetval, fAngleBound);
286 }
287 else
288 {
289 // add non-curved edge
290 aRetval.append(aBezier.getEndPoint());
291 }
292
293 // prepare next step
294 aBezier.setStartPoint(aBezier.getEndPoint());
295 }
296
297 if(rCandidate.isClosed())
298 {
299 // set closed flag and correct last point (which is added double now).
301 }
302 }
303
304 return aRetval;
305 }
306 else
307 {
308 return rCandidate;
309 }
310 }
311
312 bool isInside(const B2DPolygon& rCandidate, const B2DPoint& rPoint, bool bWithBorder)
313 {
314 const B2DPolygon aCandidate(rCandidate.areControlPointsUsed() ? rCandidate.getDefaultAdaptiveSubdivision() : rCandidate);
315
316 if(bWithBorder && isPointOnPolygon(aCandidate, rPoint))
317 {
318 return true;
319 }
320 else
321 {
322 bool bRetval(false);
323 const sal_uInt32 nPointCount(aCandidate.count());
324
325 if(nPointCount)
326 {
327 B2DPoint aCurrentPoint(aCandidate.getB2DPoint(nPointCount - 1));
328
329 for(sal_uInt32 a(0); a < nPointCount; a++)
330 {
331 const B2DPoint aPreviousPoint(aCurrentPoint);
332 aCurrentPoint = aCandidate.getB2DPoint(a);
333
334 // cross-over in Y? tdf#130150 use full precision, no need for epsilon
335 const bool bCompYA(aPreviousPoint.getY() > rPoint.getY());
336 const bool bCompYB(aCurrentPoint.getY() > rPoint.getY());
337
338 if(bCompYA != bCompYB)
339 {
340 // cross-over in X? tdf#130150 use full precision, no need for epsilon
341 const bool bCompXA(aPreviousPoint.getX() > rPoint.getX());
342 const bool bCompXB(aCurrentPoint.getX() > rPoint.getX());
343
344 if(bCompXA == bCompXB)
345 {
346 if(bCompXA)
347 {
348 bRetval = !bRetval;
349 }
350 }
351 else
352 {
353 const double fCompare(
354 aCurrentPoint.getX() - (aCurrentPoint.getY() - rPoint.getY()) *
355 (aPreviousPoint.getX() - aCurrentPoint.getX()) /
356 (aPreviousPoint.getY() - aCurrentPoint.getY()));
357
358 // tdf#130150 use full precision, no need for epsilon
359 if(fCompare > rPoint.getX())
360 {
361 bRetval = !bRetval;
362 }
363 }
364 }
365 }
366 }
367
368 return bRetval;
369 }
370 }
371
372 bool isInside(const B2DPolygon& rCandidate, const B2DPolygon& rPolygon, bool bWithBorder)
373 {
374 const B2DPolygon aCandidate(rCandidate.areControlPointsUsed() ? rCandidate.getDefaultAdaptiveSubdivision() : rCandidate);
375 const B2DPolygon aPolygon(rPolygon.areControlPointsUsed() ? rPolygon.getDefaultAdaptiveSubdivision() : rPolygon);
376 const sal_uInt32 nPointCount(aPolygon.count());
377
378 for(sal_uInt32 a(0); a < nPointCount; a++)
379 {
380 const B2DPoint aTestPoint(aPolygon.getB2DPoint(a));
381
382 if(!isInside(aCandidate, aTestPoint, bWithBorder))
383 {
384 return false;
385 }
386 }
387
388 return true;
389 }
390
391 B2DRange getRange(const B2DPolygon& rCandidate)
392 {
393 // changed to use internally buffered version at B2DPolygon
394 return rCandidate.getB2DRange();
395 }
396
397 double getSignedArea(const B2DPolygon& rCandidate)
398 {
399 const B2DPolygon aCandidate(rCandidate.areControlPointsUsed() ? rCandidate.getDefaultAdaptiveSubdivision() : rCandidate);
400 double fRetval(0.0);
401 const sal_uInt32 nPointCount(aCandidate.count());
402
403 if(nPointCount > 2)
404 {
405 for(sal_uInt32 a(0); a < nPointCount; a++)
406 {
407 const B2DPoint aPreviousPoint(aCandidate.getB2DPoint((!a) ? nPointCount - 1 : a - 1));
408 const B2DPoint aCurrentPoint(aCandidate.getB2DPoint(a));
409
410 fRetval += aPreviousPoint.getX() * aCurrentPoint.getY();
411 fRetval -= aPreviousPoint.getY() * aCurrentPoint.getX();
412 }
413
414 // correct to zero if small enough. Also test the quadratic
415 // of the result since the precision is near quadratic due to
416 // the algorithm
417 if(fTools::equalZero(fRetval) || fTools::equalZero(fRetval * fRetval))
418 {
419 fRetval = 0.0;
420 }
421 }
422
423 return fRetval;
424 }
425
426 double getArea(const B2DPolygon& rCandidate)
427 {
428 double fRetval(0.0);
429
430 if(rCandidate.count() > 2 || rCandidate.areControlPointsUsed())
431 {
432 fRetval = getSignedArea(rCandidate);
433 const double fZero(0.0);
434
435 if(fTools::less(fRetval, fZero))
436 {
437 fRetval = -fRetval;
438 }
439 }
440
441 return fRetval;
442 }
443
444 double getEdgeLength(const B2DPolygon& rCandidate, sal_uInt32 nIndex)
445 {
446 const sal_uInt32 nPointCount(rCandidate.count());
447 OSL_ENSURE(nIndex < nPointCount, "getEdgeLength: Access to polygon out of range (!)");
448 double fRetval(0.0);
449
450 if(nPointCount)
451 {
452 const sal_uInt32 nNextIndex((nIndex + 1) % nPointCount);
453
454 if(rCandidate.areControlPointsUsed())
455 {
456 B2DCubicBezier aEdge;
457
458 aEdge.setStartPoint(rCandidate.getB2DPoint(nIndex));
460 aEdge.setControlPointB(rCandidate.getPrevControlPoint(nNextIndex));
461 aEdge.setEndPoint(rCandidate.getB2DPoint(nNextIndex));
462
463 fRetval = aEdge.getLength();
464 }
465 else
466 {
467 const B2DPoint aCurrent(rCandidate.getB2DPoint(nIndex));
468 const B2DPoint aNext(rCandidate.getB2DPoint(nNextIndex));
469
470 fRetval = B2DVector(aNext - aCurrent).getLength();
471 }
472 }
473
474 return fRetval;
475 }
476
477 double getLength(const B2DPolygon& rCandidate)
478 {
479 double fRetval(0.0);
480 const sal_uInt32 nPointCount(rCandidate.count());
481
482 if(nPointCount)
483 {
484 const sal_uInt32 nEdgeCount(rCandidate.isClosed() ? nPointCount : nPointCount - 1);
485
486 if(rCandidate.areControlPointsUsed())
487 {
488 B2DCubicBezier aEdge;
489 aEdge.setStartPoint(rCandidate.getB2DPoint(0));
490
491 for(sal_uInt32 a(0); a < nEdgeCount; a++)
492 {
493 const sal_uInt32 nNextIndex((a + 1) % nPointCount);
494 aEdge.setControlPointA(rCandidate.getNextControlPoint(a));
495 aEdge.setControlPointB(rCandidate.getPrevControlPoint(nNextIndex));
496 aEdge.setEndPoint(rCandidate.getB2DPoint(nNextIndex));
497
498 fRetval += aEdge.getLength();
499 aEdge.setStartPoint(aEdge.getEndPoint());
500 }
501 }
502 else
503 {
504 B2DPoint aCurrent(rCandidate.getB2DPoint(0));
505
506 for(sal_uInt32 a(0); a < nEdgeCount; a++)
507 {
508 const sal_uInt32 nNextIndex((a + 1) % nPointCount);
509 const B2DPoint aNext(rCandidate.getB2DPoint(nNextIndex));
510
511 fRetval += B2DVector(aNext - aCurrent).getLength();
512 aCurrent = aNext;
513 }
514 }
515 }
516
517 return fRetval;
518 }
519
520 B2DPoint getPositionAbsolute(const B2DPolygon& rCandidate, double fDistance, double fLength)
521 {
522 B2DPoint aRetval;
523 const sal_uInt32 nPointCount(rCandidate.count());
524
525 if( nPointCount == 1 )
526 {
527 // only one point (i.e. no edge) - simply take that point
528 aRetval = rCandidate.getB2DPoint(0);
529 }
530 else if(nPointCount > 1)
531 {
532 const sal_uInt32 nEdgeCount(rCandidate.isClosed() ? nPointCount : nPointCount - 1);
533 sal_uInt32 nIndex(0);
534 bool bIndexDone(false);
535
536 // get length if not given
537 if(fTools::equalZero(fLength))
538 {
539 fLength = getLength(rCandidate);
540 }
541
542 if(fTools::less(fDistance, 0.0))
543 {
544 // handle fDistance < 0.0
545 if(rCandidate.isClosed())
546 {
547 // if fDistance < 0.0 increment with multiple of fLength
548 sal_uInt32 nCount(sal_uInt32(-fDistance / fLength));
549 fDistance += double(nCount + 1) * fLength;
550 }
551 else
552 {
553 // crop to polygon start
554 fDistance = 0.0;
555 bIndexDone = true;
556 }
557 }
558 else if(fTools::moreOrEqual(fDistance, fLength))
559 {
560 // handle fDistance >= fLength
561 if(rCandidate.isClosed())
562 {
563 // if fDistance >= fLength decrement with multiple of fLength
564 sal_uInt32 nCount(sal_uInt32(fDistance / fLength));
565 fDistance -= static_cast<double>(nCount) * fLength;
566 }
567 else
568 {
569 // crop to polygon end
570 fDistance = 0.0;
571 nIndex = nEdgeCount;
572 bIndexDone = true;
573 }
574 }
575
576 // look for correct index. fDistance is now [0.0 .. fLength[
577 double fEdgeLength(getEdgeLength(rCandidate, nIndex));
578
579 while(!bIndexDone)
580 {
581 // edge found must be on the half-open range
582 // [0,fEdgeLength).
583 // Note that in theory, we cannot move beyond
584 // the last polygon point, since fDistance>=fLength
585 // is checked above. Unfortunately, with floating-
586 // point calculations, this case might happen.
587 // Handled by nIndex check below
588 if (nIndex+1 < nEdgeCount && fTools::moreOrEqual(fDistance, fEdgeLength))
589 {
590 // go to next edge
591 fDistance -= fEdgeLength;
592 fEdgeLength = getEdgeLength(rCandidate, ++nIndex);
593 }
594 else
595 {
596 // it's on this edge, stop
597 bIndexDone = true;
598 }
599 }
600
601 // get the point using nIndex
602 aRetval = rCandidate.getB2DPoint(nIndex);
603
604 // if fDistance != 0.0, move that length on the edge. The edge
605 // length is in fEdgeLength.
606 if(!fTools::equalZero(fDistance))
607 {
608 if(fTools::moreOrEqual(fDistance, fEdgeLength))
609 {
610 // end point of chosen edge
611 const sal_uInt32 nNextIndex((nIndex + 1) % nPointCount);
612 aRetval = rCandidate.getB2DPoint(nNextIndex);
613 }
614 else if(fTools::equalZero(fDistance))
615 {
616 // start point of chosen edge
617 }
618 else
619 {
620 // inside edge
621 const sal_uInt32 nNextIndex((nIndex + 1) % nPointCount);
622 const B2DPoint aNextPoint(rCandidate.getB2DPoint(nNextIndex));
623 bool bDone(false);
624
625 // add calculated average value to the return value
626 if(rCandidate.areControlPointsUsed())
627 {
628 // get as bezier segment
629 const B2DCubicBezier aBezierSegment(
630 aRetval, rCandidate.getNextControlPoint(nIndex),
631 rCandidate.getPrevControlPoint(nNextIndex), aNextPoint);
632
633 if(aBezierSegment.isBezier())
634 {
635 // use B2DCubicBezierHelper to bridge the non-linear gap between
636 // length and bezier distances
637 const B2DCubicBezierHelper aBezierSegmentHelper(aBezierSegment);
638 const double fBezierDistance(aBezierSegmentHelper.distanceToRelative(fDistance));
639
640 aRetval = aBezierSegment.interpolatePoint(fBezierDistance);
641 bDone = true;
642 }
643 }
644
645 if(!bDone)
646 {
647 const double fRelativeInEdge(fDistance / fEdgeLength);
648 aRetval = interpolate(aRetval, aNextPoint, fRelativeInEdge);
649 }
650 }
651 }
652 }
653
654 return aRetval;
655 }
656
657 B2DPoint getPositionRelative(const B2DPolygon& rCandidate, double fDistance, double fLength)
658 {
659 // get length if not given
660 if(fTools::equalZero(fLength))
661 {
662 fLength = getLength(rCandidate);
663 }
664
665 // multiply fDistance with real length to get absolute position and
666 // use getPositionAbsolute
667 return getPositionAbsolute(rCandidate, fDistance * fLength, fLength);
668 }
669
670 B2DPolygon getSnippetAbsolute(const B2DPolygon& rCandidate, double fFrom, double fTo, double fLength)
671 {
672 const sal_uInt32 nPointCount(rCandidate.count());
673
674 if(nPointCount)
675 {
676 // get length if not given
677 if(fTools::equalZero(fLength))
678 {
679 fLength = getLength(rCandidate);
680 }
681
682 // test and correct fFrom
683 if(fTools::less(fFrom, 0.0))
684 {
685 fFrom = 0.0;
686 }
687
688 // test and correct fTo
689 if(fTools::more(fTo, fLength))
690 {
691 fTo = fLength;
692 }
693
694 // test and correct relationship of fFrom, fTo
695 if(fTools::more(fFrom, fTo))
696 {
697 fFrom = fTo = (fFrom + fTo) / 2.0;
698 }
699
700 if(fTools::equalZero(fFrom) && fTools::equal(fTo, fLength))
701 {
702 // no change, result is the whole polygon
703 return rCandidate;
704 }
705 else
706 {
707 B2DPolygon aRetval;
708 const sal_uInt32 nEdgeCount(rCandidate.isClosed() ? nPointCount : nPointCount - 1);
709 double fPositionOfStart(0.0);
710 bool bStartDone(false);
711 bool bEndDone(false);
712
713 for(sal_uInt32 a(0); !(bStartDone && bEndDone) && a < nEdgeCount; a++)
714 {
715 const double fEdgeLength(getEdgeLength(rCandidate, a));
716
717 if(!bStartDone)
718 {
719 if(fTools::equalZero(fFrom))
720 {
721 aRetval.append(rCandidate.getB2DPoint(a));
722
723 if(rCandidate.areControlPointsUsed())
724 {
725 aRetval.setNextControlPoint(aRetval.count() - 1, rCandidate.getNextControlPoint(a));
726 }
727
728 bStartDone = true;
729 }
730 else if(fTools::moreOrEqual(fFrom, fPositionOfStart) && fTools::less(fFrom, fPositionOfStart + fEdgeLength))
731 {
732 // calculate and add start point
733 if(fTools::equalZero(fEdgeLength))
734 {
735 aRetval.append(rCandidate.getB2DPoint(a));
736
737 if(rCandidate.areControlPointsUsed())
738 {
739 aRetval.setNextControlPoint(aRetval.count() - 1, rCandidate.getNextControlPoint(a));
740 }
741 }
742 else
743 {
744 const sal_uInt32 nNextIndex((a + 1) % nPointCount);
745 const B2DPoint aStart(rCandidate.getB2DPoint(a));
746 const B2DPoint aEnd(rCandidate.getB2DPoint(nNextIndex));
747 bool bDone(false);
748
749 if(rCandidate.areControlPointsUsed())
750 {
751 const B2DCubicBezier aBezierSegment(
752 aStart, rCandidate.getNextControlPoint(a),
753 rCandidate.getPrevControlPoint(nNextIndex), aEnd);
754
755 if(aBezierSegment.isBezier())
756 {
757 // use B2DCubicBezierHelper to bridge the non-linear gap between
758 // length and bezier distances
759 const B2DCubicBezierHelper aBezierSegmentHelper(aBezierSegment);
760 const double fBezierDistance(aBezierSegmentHelper.distanceToRelative(fFrom - fPositionOfStart));
761 B2DCubicBezier aRight;
762
763 aBezierSegment.split(fBezierDistance, nullptr, &aRight);
764 aRetval.append(aRight.getStartPoint());
765 aRetval.setNextControlPoint(aRetval.count() - 1, aRight.getControlPointA());
766 bDone = true;
767 }
768 }
769
770 if(!bDone)
771 {
772 const double fRelValue((fFrom - fPositionOfStart) / fEdgeLength);
773 aRetval.append(interpolate(aStart, aEnd, fRelValue));
774 }
775 }
776
777 bStartDone = true;
778
779 // if same point, end is done, too.
780 if(rtl::math::approxEqual(fFrom, fTo))
781 {
782 bEndDone = true;
783 }
784 }
785 }
786
787 if(!bEndDone && fTools::moreOrEqual(fTo, fPositionOfStart) && fTools::less(fTo, fPositionOfStart + fEdgeLength))
788 {
789 // calculate and add end point
790 if(fTools::equalZero(fEdgeLength))
791 {
792 const sal_uInt32 nNextIndex((a + 1) % nPointCount);
793 aRetval.append(rCandidate.getB2DPoint(nNextIndex));
794
795 if(rCandidate.areControlPointsUsed())
796 {
797 aRetval.setPrevControlPoint(aRetval.count() - 1, rCandidate.getPrevControlPoint(nNextIndex));
798 }
799 }
800 else
801 {
802 const sal_uInt32 nNextIndex((a + 1) % nPointCount);
803 const B2DPoint aStart(rCandidate.getB2DPoint(a));
804 const B2DPoint aEnd(rCandidate.getB2DPoint(nNextIndex));
805 bool bDone(false);
806
807 if(rCandidate.areControlPointsUsed())
808 {
809 const B2DCubicBezier aBezierSegment(
810 aStart, rCandidate.getNextControlPoint(a),
811 rCandidate.getPrevControlPoint(nNextIndex), aEnd);
812
813 if(aBezierSegment.isBezier())
814 {
815 // use B2DCubicBezierHelper to bridge the non-linear gap between
816 // length and bezier distances
817 const B2DCubicBezierHelper aBezierSegmentHelper(aBezierSegment);
818 const double fBezierDistance(aBezierSegmentHelper.distanceToRelative(fTo - fPositionOfStart));
819 B2DCubicBezier aLeft;
820
821 aBezierSegment.split(fBezierDistance, &aLeft, nullptr);
822 aRetval.append(aLeft.getEndPoint());
823 aRetval.setPrevControlPoint(aRetval.count() - 1, aLeft.getControlPointB());
824 bDone = true;
825 }
826 }
827
828 if(!bDone)
829 {
830 const double fRelValue((fTo - fPositionOfStart) / fEdgeLength);
831 aRetval.append(interpolate(aStart, aEnd, fRelValue));
832 }
833 }
834
835 bEndDone = true;
836 }
837
838 if(!bEndDone)
839 {
840 if(bStartDone)
841 {
842 // add segments end point
843 const sal_uInt32 nNextIndex((a + 1) % nPointCount);
844 aRetval.append(rCandidate.getB2DPoint(nNextIndex));
845
846 if(rCandidate.areControlPointsUsed())
847 {
848 aRetval.setPrevControlPoint(aRetval.count() - 1, rCandidate.getPrevControlPoint(nNextIndex));
849 aRetval.setNextControlPoint(aRetval.count() - 1, rCandidate.getNextControlPoint(nNextIndex));
850 }
851 }
852
853 // increment fPositionOfStart
854 fPositionOfStart += fEdgeLength;
855 }
856 }
857 return aRetval;
858 }
859 }
860 else
861 {
862 return rCandidate;
863 }
864 }
865
867 const B2DPoint& rEdge1Start, const B2DVector& rEdge1Delta,
868 const B2DPoint& rEdge2Start, const B2DVector& rEdge2Delta,
869 CutFlagValue aCutFlags,
870 double* pCut1, double* pCut2)
871 {
873 double fCut1(0.0);
874 double fCut2(0.0);
875 bool bFinished(!static_cast<bool>(aCutFlags & CutFlagValue::ALL));
876
877 // test for same points?
878 if(!bFinished
880 && (aCutFlags & (CutFlagValue::START2|CutFlagValue::END2)))
881 {
882 // same startpoint?
884 {
885 if(rEdge1Start.equal(rEdge2Start))
886 {
887 bFinished = true;
889 }
890 }
891
892 // same endpoint?
894 {
895 const B2DPoint aEnd1(rEdge1Start + rEdge1Delta);
896 const B2DPoint aEnd2(rEdge2Start + rEdge2Delta);
897
898 if(aEnd1.equal(aEnd2))
899 {
900 bFinished = true;
902 fCut1 = fCut2 = 1.0;
903 }
904 }
905
906 // startpoint1 == endpoint2?
908 {
909 const B2DPoint aEnd2(rEdge2Start + rEdge2Delta);
910
911 if(rEdge1Start.equal(aEnd2))
912 {
913 bFinished = true;
915 fCut1 = 0.0;
916 fCut2 = 1.0;
917 }
918 }
919
920 // startpoint2 == endpoint1?
922 {
923 const B2DPoint aEnd1(rEdge1Start + rEdge1Delta);
924
925 if(rEdge2Start.equal(aEnd1))
926 {
927 bFinished = true;
929 fCut1 = 1.0;
930 fCut2 = 0.0;
931 }
932 }
933 }
934
935 if(!bFinished && (aCutFlags & CutFlagValue::LINE))
936 {
937 if(aCutFlags & CutFlagValue::START1)
938 {
939 // start1 on line 2 ?
940 if(isPointOnEdge(rEdge1Start, rEdge2Start, rEdge2Delta, &fCut2))
941 {
942 bFinished = true;
944 }
945 }
946
947 if(!bFinished && (aCutFlags & CutFlagValue::START2))
948 {
949 // start2 on line 1 ?
950 if(isPointOnEdge(rEdge2Start, rEdge1Start, rEdge1Delta, &fCut1))
951 {
952 bFinished = true;
954 }
955 }
956
957 if(!bFinished && (aCutFlags & CutFlagValue::END1))
958 {
959 // end1 on line 2 ?
960 const B2DPoint aEnd1(rEdge1Start + rEdge1Delta);
961
962 if(isPointOnEdge(aEnd1, rEdge2Start, rEdge2Delta, &fCut2))
963 {
964 bFinished = true;
966 }
967 }
968
969 if(!bFinished && (aCutFlags & CutFlagValue::END2))
970 {
971 // end2 on line 1 ?
972 const B2DPoint aEnd2(rEdge2Start + rEdge2Delta);
973
974 if(isPointOnEdge(aEnd2, rEdge1Start, rEdge1Delta, &fCut1))
975 {
976 bFinished = true;
978 }
979 }
980
981 if(!bFinished)
982 {
983 // cut in line1, line2 ?
984 fCut1 = (rEdge1Delta.getX() * rEdge2Delta.getY()) - (rEdge1Delta.getY() * rEdge2Delta.getX());
985
986 if(!fTools::equalZero(fCut1))
987 {
988 fCut1 = (rEdge2Delta.getY() * (rEdge2Start.getX() - rEdge1Start.getX())
989 + rEdge2Delta.getX() * (rEdge1Start.getY() - rEdge2Start.getY())) / fCut1;
990
991 const double fZero(0.0);
992 const double fOne(1.0);
993
994 // inside parameter range edge1 AND fCut2 is calculable
995 if(fTools::more(fCut1, fZero) && fTools::less(fCut1, fOne)
996 && (!fTools::equalZero(rEdge2Delta.getX()) || !fTools::equalZero(rEdge2Delta.getY())))
997 {
998 // take the more precise calculation of the two possible
999 if(fabs(rEdge2Delta.getX()) > fabs(rEdge2Delta.getY()))
1000 {
1001 fCut2 = (rEdge1Start.getX() + fCut1
1002 * rEdge1Delta.getX() - rEdge2Start.getX()) / rEdge2Delta.getX();
1003 }
1004 else
1005 {
1006 fCut2 = (rEdge1Start.getY() + fCut1
1007 * rEdge1Delta.getY() - rEdge2Start.getY()) / rEdge2Delta.getY();
1008 }
1009
1010 // inside parameter range edge2, too
1011 if(fTools::more(fCut2, fZero) && fTools::less(fCut2, fOne))
1012 {
1013 aRetval = CutFlagValue::LINE;
1014 }
1015 }
1016 }
1017 }
1018 }
1019
1020 // copy values if wanted
1021 if(pCut1)
1022 {
1023 *pCut1 = fCut1;
1024 }
1025
1026 if(pCut2)
1027 {
1028 *pCut2 = fCut2;
1029 }
1030
1031 return aRetval;
1032 }
1033
1035 const B2DPoint& rPoint,
1036 const B2DPoint& rEdgeStart,
1037 const B2DVector& rEdgeDelta,
1038 double* pCut)
1039 {
1040 bool bDeltaXIsZero(fTools::equalZero(rEdgeDelta.getX()));
1041 bool bDeltaYIsZero(fTools::equalZero(rEdgeDelta.getY()));
1042 const double fZero(0.0);
1043 const double fOne(1.0);
1044
1045 if(bDeltaXIsZero && bDeltaYIsZero)
1046 {
1047 // no line, just a point
1048 return false;
1049 }
1050 else if(bDeltaXIsZero)
1051 {
1052 // vertical line
1053 if(fTools::equal(rPoint.getX(), rEdgeStart.getX()))
1054 {
1055 double fValue = (rPoint.getY() - rEdgeStart.getY()) / rEdgeDelta.getY();
1056
1057 if(fTools::more(fValue, fZero) && fTools::less(fValue, fOne))
1058 {
1059 if(pCut)
1060 {
1061 *pCut = fValue;
1062 }
1063
1064 return true;
1065 }
1066 }
1067 }
1068 else if(bDeltaYIsZero)
1069 {
1070 // horizontal line
1071 if(fTools::equal(rPoint.getY(), rEdgeStart.getY()))
1072 {
1073 double fValue = (rPoint.getX() - rEdgeStart.getX()) / rEdgeDelta.getX();
1074
1075 if(fTools::more(fValue, fZero) && fTools::less(fValue, fOne))
1076 {
1077 if(pCut)
1078 {
1079 *pCut = fValue;
1080 }
1081
1082 return true;
1083 }
1084 }
1085 }
1086 else
1087 {
1088 // any angle line
1089 double fTOne = (rPoint.getX() - rEdgeStart.getX()) / rEdgeDelta.getX();
1090 double fTTwo = (rPoint.getY() - rEdgeStart.getY()) / rEdgeDelta.getY();
1091
1092 if(fTools::equal(fTOne, fTTwo))
1093 {
1094 // same parameter representation, point is on line. Take
1095 // middle value for better results
1096 double fValue = (fTOne + fTTwo) / 2.0;
1097
1098 if(fTools::more(fValue, fZero) && fTools::less(fValue, fOne))
1099 {
1100 // point is inside line bounds, too
1101 if(pCut)
1102 {
1103 *pCut = fValue;
1104 }
1105
1106 return true;
1107 }
1108 }
1109 }
1110
1111 return false;
1112 }
1113
1115 const B2DPolygon& rCandidate,
1116 const std::vector<double>& rDotDashArray,
1117 B2DPolyPolygon* pLineTarget,
1118 B2DPolyPolygon* pGapTarget,
1119 double fDotDashLength)
1120 {
1121 // clear targets in any case
1122 if(pLineTarget)
1123 {
1124 pLineTarget->clear();
1125 }
1126
1127 if(pGapTarget)
1128 {
1129 pGapTarget->clear();
1130 }
1131
1132 // provide callbacks as lambdas
1133 auto aLineCallback(
1134 nullptr == pLineTarget
1135 ? std::function<void(const basegfx::B2DPolygon&)>()
1136 : [&pLineTarget](const basegfx::B2DPolygon& rSnippet){ pLineTarget->append(rSnippet); });
1137 auto aGapCallback(
1138 nullptr == pGapTarget
1139 ? std::function<void(const basegfx::B2DPolygon&)>()
1140 : [&pGapTarget](const basegfx::B2DPolygon& rSnippet){ pGapTarget->append(rSnippet); });
1141
1142 // call version that uses callbacks
1144 rCandidate,
1145 rDotDashArray,
1146 aLineCallback,
1147 aGapCallback,
1148 fDotDashLength);
1149 }
1150
1152 const B2DPolygon& rSnippet,
1153 const std::function<void(const basegfx::B2DPolygon& rSnippet)>& rTargetCallback,
1154 B2DPolygon& rFirst,
1155 B2DPolygon& rLast)
1156 {
1157 if(rSnippet.isClosed())
1158 {
1159 if(!rFirst.count())
1160 {
1161 rFirst = rSnippet;
1162 }
1163 else
1164 {
1165 if(rLast.count())
1166 {
1167 rTargetCallback(rLast);
1168 }
1169
1170 rLast = rSnippet;
1171 }
1172 }
1173 else
1174 {
1175 rTargetCallback(rSnippet);
1176 }
1177 }
1178
1180 const std::function<void(const basegfx::B2DPolygon& rSnippet)>& rTargetCallback,
1181 B2DPolygon& rFirst,
1182 B2DPolygon& rLast)
1183 {
1184 if(rFirst.count() && rLast.count()
1185 && rFirst.getB2DPoint(0).equal(rLast.getB2DPoint(rLast.count() - 1)))
1186 {
1187 // start of first and end of last are the same -> merge them
1188 rLast.append(rFirst);
1189 rLast.removeDoublePoints();
1190 rFirst.clear();
1191 }
1192
1193 if(rLast.count())
1194 {
1195 rTargetCallback(rLast);
1196 }
1197
1198 if(rFirst.count())
1199 {
1200 rTargetCallback(rFirst);
1201 }
1202 }
1203
1205 const B2DPolygon& rCandidate,
1206 const std::vector<double>& rDotDashArray,
1207 std::function<void(const basegfx::B2DPolygon& rSnippet)> aLineTargetCallback,
1208 std::function<void(const basegfx::B2DPolygon& rSnippet)> aGapTargetCallback,
1209 double fDotDashLength)
1210 {
1211 const sal_uInt32 nPointCount(rCandidate.count());
1212 const sal_uInt32 nDotDashCount(rDotDashArray.size());
1213
1214 if(fTools::lessOrEqual(fDotDashLength, 0.0))
1215 {
1216 fDotDashLength = std::accumulate(rDotDashArray.begin(), rDotDashArray.end(), 0.0);
1217 }
1218
1219 if(fTools::lessOrEqual(fDotDashLength, 0.0) || (!aLineTargetCallback && !aGapTargetCallback) || !nPointCount)
1220 {
1221 // parameters make no sense, just add source to targets
1222 if(aLineTargetCallback)
1223 {
1224 aLineTargetCallback(rCandidate);
1225 }
1226
1227 if(aGapTargetCallback)
1228 {
1229 aGapTargetCallback(rCandidate);
1230 }
1231
1232 return;
1233 }
1234
1235 // precalculate maximal acceptable length of candidate polygon assuming
1236 // we want to create a maximum of fNumberOfAllowedSnippets. For
1237 // fNumberOfAllowedSnippets use ca. 65536, double due to line & gap.
1238 static const double fNumberOfAllowedSnippets(65535.0 * 2.0);
1239 const double fAllowedLength((fNumberOfAllowedSnippets * fDotDashLength) / double(rDotDashArray.size()));
1240 const double fCandidateLength(basegfx::utils::getLength(rCandidate));
1241 std::vector<double> aDotDashArray(rDotDashArray);
1242
1243 if(fCandidateLength > fAllowedLength)
1244 {
1245 // we would produce more than fNumberOfAllowedSnippets, so
1246 // adapt aDotDashArray to exactly produce assumed number. Also
1247 // assert this to let the caller know about it.
1248 // If this asserts: Please think about checking your DotDashArray
1249 // before calling this function or evtl. use the callback version
1250 // to *not* produce that much of data. Even then, you may still
1251 // think about producing too much runtime (!)
1252 assert(true && "applyLineDashing: potentially too expensive to do the requested dismantle - please consider stretched LineDash pattern (!)");
1253
1254 // calculate correcting factor, apply to aDotDashArray and fDotDashLength
1255 // to enlarge these as needed
1256 const double fFactor(fCandidateLength / fAllowedLength);
1257 std::for_each(aDotDashArray.begin(), aDotDashArray.end(), [&fFactor](double &f){ f *= fFactor; });
1258 }
1259
1260 // prepare current edge's start
1261 B2DCubicBezier aCurrentEdge;
1262 const bool bIsClosed(rCandidate.isClosed());
1263 const sal_uInt32 nEdgeCount(bIsClosed ? nPointCount : nPointCount - 1);
1264 aCurrentEdge.setStartPoint(rCandidate.getB2DPoint(0));
1265
1266 // prepare DotDashArray iteration and the line/gap switching bool
1267 sal_uInt32 nDotDashIndex(0);
1268 bool bIsLine(true);
1269 double fDotDashMovingLength(aDotDashArray[0]);
1270 B2DPolygon aSnippet;
1271
1272 // remember 1st and last snippets to try to merge after execution
1273 // is complete and hand to callback
1274 B2DPolygon aFirstLine, aLastLine;
1275 B2DPolygon aFirstGap, aLastGap;
1276
1277 // iterate over all edges
1278 for(sal_uInt32 a(0); a < nEdgeCount; a++)
1279 {
1280 // update current edge (fill in C1, C2 and end point)
1281 double fLastDotDashMovingLength(0.0);
1282 const sal_uInt32 nNextIndex((a + 1) % nPointCount);
1283 aCurrentEdge.setControlPointA(rCandidate.getNextControlPoint(a));
1284 aCurrentEdge.setControlPointB(rCandidate.getPrevControlPoint(nNextIndex));
1285 aCurrentEdge.setEndPoint(rCandidate.getB2DPoint(nNextIndex));
1286
1287 // check if we have a trivial bezier segment -> possible fallback to edge
1288 aCurrentEdge.testAndSolveTrivialBezier();
1289
1290 if(aCurrentEdge.isBezier())
1291 {
1292 // bezier segment
1293 const B2DCubicBezierHelper aCubicBezierHelper(aCurrentEdge);
1294 const double fEdgeLength(aCubicBezierHelper.getLength());
1295
1296 if(!fTools::equalZero(fEdgeLength))
1297 {
1298 while(fTools::less(fDotDashMovingLength, fEdgeLength))
1299 {
1300 // new split is inside edge, create and append snippet [fLastDotDashMovingLength, fDotDashMovingLength]
1301 const bool bHandleLine(bIsLine && aLineTargetCallback);
1302 const bool bHandleGap(!bIsLine && aGapTargetCallback);
1303
1304 if(bHandleLine || bHandleGap)
1305 {
1306 const double fBezierSplitStart(aCubicBezierHelper.distanceToRelative(fLastDotDashMovingLength));
1307 const double fBezierSplitEnd(aCubicBezierHelper.distanceToRelative(fDotDashMovingLength));
1308 B2DCubicBezier aBezierSnippet(aCurrentEdge.snippet(fBezierSplitStart, fBezierSplitEnd));
1309
1310 if(!aSnippet.count())
1311 {
1312 aSnippet.append(aBezierSnippet.getStartPoint());
1313 }
1314
1315 aSnippet.appendBezierSegment(aBezierSnippet.getControlPointA(), aBezierSnippet.getControlPointB(), aBezierSnippet.getEndPoint());
1316
1317 if(bHandleLine)
1318 {
1319 implHandleSnippet(aSnippet, aLineTargetCallback, aFirstLine, aLastLine);
1320 }
1321
1322 if(bHandleGap)
1323 {
1324 implHandleSnippet(aSnippet, aGapTargetCallback, aFirstGap, aLastGap);
1325 }
1326
1327 aSnippet.clear();
1328 }
1329
1330 // prepare next DotDashArray step and flip line/gap flag
1331 fLastDotDashMovingLength = fDotDashMovingLength;
1332 fDotDashMovingLength += aDotDashArray[(++nDotDashIndex) % nDotDashCount];
1333 bIsLine = !bIsLine;
1334 }
1335
1336 // append closing snippet [fLastDotDashMovingLength, fEdgeLength]
1337 const bool bHandleLine(bIsLine && aLineTargetCallback);
1338 const bool bHandleGap(!bIsLine && aGapTargetCallback);
1339
1340 if(bHandleLine || bHandleGap)
1341 {
1342 B2DCubicBezier aRight;
1343 const double fBezierSplit(aCubicBezierHelper.distanceToRelative(fLastDotDashMovingLength));
1344
1345 aCurrentEdge.split(fBezierSplit, nullptr, &aRight);
1346
1347 if(!aSnippet.count())
1348 {
1349 aSnippet.append(aRight.getStartPoint());
1350 }
1351
1352 aSnippet.appendBezierSegment(aRight.getControlPointA(), aRight.getControlPointB(), aRight.getEndPoint());
1353 }
1354
1355 // prepare move to next edge
1356 fDotDashMovingLength -= fEdgeLength;
1357 }
1358 }
1359 else
1360 {
1361 // simple edge
1362 const double fEdgeLength(aCurrentEdge.getEdgeLength());
1363
1364 if(!fTools::equalZero(fEdgeLength))
1365 {
1366 while(fTools::less(fDotDashMovingLength, fEdgeLength))
1367 {
1368 // new split is inside edge, create and append snippet [fLastDotDashMovingLength, fDotDashMovingLength]
1369 const bool bHandleLine(bIsLine && aLineTargetCallback);
1370 const bool bHandleGap(!bIsLine && aGapTargetCallback);
1371
1372 if(bHandleLine || bHandleGap)
1373 {
1374 if(!aSnippet.count())
1375 {
1376 aSnippet.append(interpolate(aCurrentEdge.getStartPoint(), aCurrentEdge.getEndPoint(), fLastDotDashMovingLength / fEdgeLength));
1377 }
1378
1379 aSnippet.append(interpolate(aCurrentEdge.getStartPoint(), aCurrentEdge.getEndPoint(), fDotDashMovingLength / fEdgeLength));
1380
1381 if(bHandleLine)
1382 {
1383 implHandleSnippet(aSnippet, aLineTargetCallback, aFirstLine, aLastLine);
1384 }
1385
1386 if(bHandleGap)
1387 {
1388 implHandleSnippet(aSnippet, aGapTargetCallback, aFirstGap, aLastGap);
1389 }
1390
1391 aSnippet.clear();
1392 }
1393
1394 // prepare next DotDashArray step and flip line/gap flag
1395 fLastDotDashMovingLength = fDotDashMovingLength;
1396 fDotDashMovingLength += aDotDashArray[(++nDotDashIndex) % nDotDashCount];
1397 bIsLine = !bIsLine;
1398 }
1399
1400 // append snippet [fLastDotDashMovingLength, fEdgeLength]
1401 const bool bHandleLine(bIsLine && aLineTargetCallback);
1402 const bool bHandleGap(!bIsLine && aGapTargetCallback);
1403
1404 if(bHandleLine || bHandleGap)
1405 {
1406 if(!aSnippet.count())
1407 {
1408 aSnippet.append(interpolate(aCurrentEdge.getStartPoint(), aCurrentEdge.getEndPoint(), fLastDotDashMovingLength / fEdgeLength));
1409 }
1410
1411 aSnippet.append(aCurrentEdge.getEndPoint());
1412 }
1413
1414 // prepare move to next edge
1415 fDotDashMovingLength -= fEdgeLength;
1416 }
1417 }
1418
1419 // prepare next edge step (end point gets new start point)
1420 aCurrentEdge.setStartPoint(aCurrentEdge.getEndPoint());
1421 }
1422
1423 // append last intermediate results (if exists)
1424 if(aSnippet.count())
1425 {
1426 const bool bHandleLine(bIsLine && aLineTargetCallback);
1427 const bool bHandleGap(!bIsLine && aGapTargetCallback);
1428
1429 if(bHandleLine)
1430 {
1431 implHandleSnippet(aSnippet, aLineTargetCallback, aFirstLine, aLastLine);
1432 }
1433
1434 if(bHandleGap)
1435 {
1436 implHandleSnippet(aSnippet, aGapTargetCallback, aFirstGap, aLastGap);
1437 }
1438 }
1439
1440 if(bIsClosed && aLineTargetCallback)
1441 {
1442 implHandleFirstLast(aLineTargetCallback, aFirstLine, aLastLine);
1443 }
1444
1445 if(bIsClosed && aGapTargetCallback)
1446 {
1447 implHandleFirstLast(aGapTargetCallback, aFirstGap, aLastGap);
1448 }
1449 }
1450
1451 // test if point is inside epsilon-range around an edge defined
1452 // by the two given points. Can be used for HitTesting. The epsilon-range
1453 // is defined to be the rectangle centered to the given edge, using height
1454 // 2 x fDistance, and the circle around both points with radius fDistance.
1455 bool isInEpsilonRange(const B2DPoint& rEdgeStart, const B2DPoint& rEdgeEnd, const B2DPoint& rTestPosition, double fDistance)
1456 {
1457 // build edge vector
1458 const B2DVector aEdge(rEdgeEnd - rEdgeStart);
1459 bool bDoDistanceTestStart(false);
1460 bool bDoDistanceTestEnd(false);
1461
1462 if(aEdge.equalZero())
1463 {
1464 // no edge, just a point. Do one of the distance tests.
1465 bDoDistanceTestStart = true;
1466 }
1467 else
1468 {
1469 // edge has a length. Create perpendicular vector.
1470 const B2DVector aPerpend(getPerpendicular(aEdge));
1471 double fCut(
1472 (aPerpend.getY() * (rTestPosition.getX() - rEdgeStart.getX())
1473 + aPerpend.getX() * (rEdgeStart.getY() - rTestPosition.getY())) /
1474 (aEdge.getX() * aEdge.getX() + aEdge.getY() * aEdge.getY()));
1475 const double fZero(0.0);
1476 const double fOne(1.0);
1477
1478 if(fTools::less(fCut, fZero))
1479 {
1480 // left of rEdgeStart
1481 bDoDistanceTestStart = true;
1482 }
1483 else if(fTools::more(fCut, fOne))
1484 {
1485 // right of rEdgeEnd
1486 bDoDistanceTestEnd = true;
1487 }
1488 else
1489 {
1490 // inside line [0.0 .. 1.0]
1491 const B2DPoint aCutPoint(interpolate(rEdgeStart, rEdgeEnd, fCut));
1492 const B2DVector aDelta(rTestPosition - aCutPoint);
1493 const double fDistanceSquare(aDelta.scalar(aDelta));
1494
1495 return fDistanceSquare <= fDistance * fDistance;
1496 }
1497 }
1498
1499 if(bDoDistanceTestStart)
1500 {
1501 const B2DVector aDelta(rTestPosition - rEdgeStart);
1502 const double fDistanceSquare(aDelta.scalar(aDelta));
1503
1504 if(fDistanceSquare <= fDistance * fDistance)
1505 {
1506 return true;
1507 }
1508 }
1509 else if(bDoDistanceTestEnd)
1510 {
1511 const B2DVector aDelta(rTestPosition - rEdgeEnd);
1512 const double fDistanceSquare(aDelta.scalar(aDelta));
1513
1514 if(fDistanceSquare <= fDistance * fDistance)
1515 {
1516 return true;
1517 }
1518 }
1519
1520 return false;
1521 }
1522
1523 // test if point is inside epsilon-range around the given Polygon. Can be used
1524 // for HitTesting. The epsilon-range is defined to be the tube around the polygon
1525 // with distance fDistance and rounded edges (start and end point).
1526 bool isInEpsilonRange(const B2DPolygon& rCandidate, const B2DPoint& rTestPosition, double fDistance)
1527 {
1528 // force to non-bezier polygon
1529 const B2DPolygon& aCandidate(rCandidate.getDefaultAdaptiveSubdivision());
1530 const sal_uInt32 nPointCount(aCandidate.count());
1531
1532 if(!nPointCount)
1533 return false;
1534
1535 const sal_uInt32 nEdgeCount(aCandidate.isClosed() ? nPointCount : nPointCount - 1);
1536 B2DPoint aCurrent(aCandidate.getB2DPoint(0));
1537
1538 if(nEdgeCount)
1539 {
1540 // edges
1541 for(sal_uInt32 a(0); a < nEdgeCount; a++)
1542 {
1543 const sal_uInt32 nNextIndex((a + 1) % nPointCount);
1544 const B2DPoint aNext(aCandidate.getB2DPoint(nNextIndex));
1545
1546 if(isInEpsilonRange(aCurrent, aNext, rTestPosition, fDistance))
1547 {
1548 return true;
1549 }
1550
1551 // prepare next step
1552 aCurrent = aNext;
1553 }
1554 }
1555 else
1556 {
1557 // no edges, but points -> not closed. Check single point. Just
1558 // use isInEpsilonRange with twice the same point, it handles those well
1559 if(isInEpsilonRange(aCurrent, aCurrent, rTestPosition, fDistance))
1560 {
1561 return true;
1562 }
1563 }
1564
1565 return false;
1566 }
1567
1568 // Calculates distance of curve point to its control point for a Bézier curve, that
1569 // approximates a unit circle arc. fAngle is the center angle of the circle arc. The
1570 // constrain 0<=fAngle<=pi/2 must not be violated to give a useful accuracy. For details
1571 // and alternatives read document "ApproxCircleInfo.odt", attachment of bug tdf#121425.
1572 static double impDistanceBezierPointToControl(double fAngle)
1573 {
1574 SAL_WARN_IF(fAngle < 0 || fAngle > M_PI_2,"basegfx","angle not suitable for approximate circle");
1575 if (0 <= fAngle && fAngle <= M_PI_2)
1576 {
1577 return 4.0/3.0 * ( tan(fAngle/4.0));
1578 }
1579 else
1580 return 0;
1581 }
1582
1583 B2DPolygon createPolygonFromRect( const B2DRectangle& rRect, double fRadiusX, double fRadiusY )
1584 {
1585 const double fZero(0.0);
1586 const double fOne(1.0);
1587
1588 fRadiusX = std::clamp(fRadiusX, 0.0, 1.0);
1589 fRadiusY = std::clamp(fRadiusY, 0.0, 1.0);
1590
1591 if(rtl::math::approxEqual(fZero, fRadiusX) || rtl::math::approxEqual(fZero, fRadiusY))
1592 {
1593 // at least in one direction no radius, use rectangle.
1594 // Do not use createPolygonFromRect() here since original
1595 // creator (historical reasons) still creates a start point at the
1596 // bottom center, so do the same here to get the same line patterns.
1597 // Due to this the order of points is different, too.
1598 const B2DPoint aBottomCenter(rRect.getCenter().getX(), rRect.getMaxY());
1599 B2DPolygon aPolygon {
1600 aBottomCenter,
1601 { rRect.getMinX(), rRect.getMaxY() },
1602 { rRect.getMinX(), rRect.getMinY() },
1603 { rRect.getMaxX(), rRect.getMinY() },
1604 { rRect.getMaxX(), rRect.getMaxY() }
1605 };
1606
1607 // close
1608 aPolygon.setClosed( true );
1609
1610 return aPolygon;
1611 }
1612 else if(rtl::math::approxEqual(fOne, fRadiusX) && rtl::math::approxEqual(fOne, fRadiusY))
1613 {
1614 // in both directions full radius, use ellipse
1615 const B2DPoint aCenter(rRect.getCenter());
1616 const double fRectRadiusX(rRect.getWidth() / 2.0);
1617 const double fRectRadiusY(rRect.getHeight() / 2.0);
1618
1619 return createPolygonFromEllipse( aCenter, fRectRadiusX, fRectRadiusY );
1620 }
1621 else
1622 {
1623 B2DPolygon aRetval;
1624 const double fBowX((rRect.getWidth() / 2.0) * fRadiusX);
1625 const double fBowY((rRect.getHeight() / 2.0) * fRadiusY);
1626 const double fKappa(impDistanceBezierPointToControl(M_PI_2));
1627
1628 // create start point at bottom center
1629 if(!rtl::math::approxEqual(fOne, fRadiusX))
1630 {
1631 const B2DPoint aBottomCenter(rRect.getCenter().getX(), rRect.getMaxY());
1632 aRetval.append(aBottomCenter);
1633 }
1634
1635 // create first bow
1636 {
1637 const B2DPoint aBottomRight(rRect.getMaxX(), rRect.getMaxY());
1638 const B2DPoint aStart(aBottomRight + B2DPoint(-fBowX, 0.0));
1639 const B2DPoint aStop(aBottomRight + B2DPoint(0.0, -fBowY));
1640 aRetval.append(aStart);
1641 aRetval.appendBezierSegment(interpolate(aStart, aBottomRight, fKappa), interpolate(aStop, aBottomRight, fKappa), aStop);
1642 }
1643
1644 // create second bow
1645 {
1646 const B2DPoint aTopRight(rRect.getMaxX(), rRect.getMinY());
1647 const B2DPoint aStart(aTopRight + B2DPoint(0.0, fBowY));
1648 const B2DPoint aStop(aTopRight + B2DPoint(-fBowX, 0.0));
1649 aRetval.append(aStart);
1650 aRetval.appendBezierSegment(interpolate(aStart, aTopRight, fKappa), interpolate(aStop, aTopRight, fKappa), aStop);
1651 }
1652
1653 // create third bow
1654 {
1655 const B2DPoint aTopLeft(rRect.getMinX(), rRect.getMinY());
1656 const B2DPoint aStart(aTopLeft + B2DPoint(fBowX, 0.0));
1657 const B2DPoint aStop(aTopLeft + B2DPoint(0.0, fBowY));
1658 aRetval.append(aStart);
1659 aRetval.appendBezierSegment(interpolate(aStart, aTopLeft, fKappa), interpolate(aStop, aTopLeft, fKappa), aStop);
1660 }
1661
1662 // create forth bow
1663 {
1664 const B2DPoint aBottomLeft(rRect.getMinX(), rRect.getMaxY());
1665 const B2DPoint aStart(aBottomLeft + B2DPoint(0.0, -fBowY));
1666 const B2DPoint aStop(aBottomLeft + B2DPoint(fBowX, 0.0));
1667 aRetval.append(aStart);
1668 aRetval.appendBezierSegment(interpolate(aStart, aBottomLeft, fKappa), interpolate(aStop, aBottomLeft, fKappa), aStop);
1669 }
1670
1671 // close
1672 aRetval.setClosed( true );
1673
1674 // remove double created points if there are extreme radii involved
1675 if(rtl::math::approxEqual(fOne, fRadiusX) || rtl::math::approxEqual(fOne, fRadiusY))
1676 {
1677 aRetval.removeDoublePoints();
1678 }
1679
1680 return aRetval;
1681 }
1682 }
1683
1685 {
1686 B2DPolygon aPolygon {
1687 { rRect.getMinX(), rRect.getMinY() },
1688 { rRect.getMaxX(), rRect.getMinY() },
1689 { rRect.getMaxX(), rRect.getMaxY() },
1690 { rRect.getMinX(), rRect.getMaxY() }
1691 };
1692
1693 // close
1694 aPolygon.setClosed( true );
1695
1696 return aPolygon;
1697 }
1698
1700 {
1701 static auto const singleton = [] {
1702 B2DPolygon aPolygon {
1703 { 0.0, 0.0 },
1704 { 1.0, 0.0 },
1705 { 1.0, 1.0 },
1706 { 0.0, 1.0 }
1707 };
1708
1709 // close
1710 aPolygon.setClosed( true );
1711
1712 return aPolygon;
1713 }();
1714 return singleton;
1715 }
1716
1717 B2DPolygon createPolygonFromCircle( const B2DPoint& rCenter, double fRadius )
1718 {
1719 return createPolygonFromEllipse( rCenter, fRadius, fRadius );
1720 }
1721
1722 static B2DPolygon impCreateUnitCircle(sal_uInt32 nStartQuadrant)
1723 {
1724 B2DPolygon aUnitCircle;
1725 const double fSegmentKappa = impDistanceBezierPointToControl(M_PI_2 / STEPSPERQUARTER);
1726 const B2DHomMatrix aRotateMatrix(createRotateB2DHomMatrix(M_PI_2 / STEPSPERQUARTER));
1727
1728 B2DPoint aPoint(1.0, 0.0);
1729 B2DPoint aForward(1.0, fSegmentKappa);
1730 B2DPoint aBackward(1.0, -fSegmentKappa);
1731
1732 if(nStartQuadrant != 0)
1733 {
1734 const B2DHomMatrix aQuadrantMatrix(createRotateB2DHomMatrix(M_PI_2 * (nStartQuadrant % 4)));
1735 aPoint *= aQuadrantMatrix;
1736 aBackward *= aQuadrantMatrix;
1737 aForward *= aQuadrantMatrix;
1738 }
1739
1740 aUnitCircle.append(aPoint);
1741
1742 for(sal_uInt32 a(0); a < STEPSPERQUARTER * 4; a++)
1743 {
1744 aPoint *= aRotateMatrix;
1745 aBackward *= aRotateMatrix;
1746 aUnitCircle.appendBezierSegment(aForward, aBackward, aPoint);
1747 aForward *= aRotateMatrix;
1748 }
1749
1750 aUnitCircle.setClosed(true);
1751 aUnitCircle.removeDoublePoints();
1752
1753 return aUnitCircle;
1754 }
1755
1757 {
1758 static auto const singleton = [] {
1759 B2DPolygon aUnitHalfCircle;
1760 const double fSegmentKappa(impDistanceBezierPointToControl(M_PI_2 / STEPSPERQUARTER));
1761 const B2DHomMatrix aRotateMatrix(createRotateB2DHomMatrix(M_PI_2 / STEPSPERQUARTER));
1762 B2DPoint aPoint(1.0, 0.0);
1763 B2DPoint aForward(1.0, fSegmentKappa);
1764 B2DPoint aBackward(1.0, -fSegmentKappa);
1765
1766 aUnitHalfCircle.append(aPoint);
1767
1768 for(sal_uInt32 a(0); a < STEPSPERQUARTER * 2; a++)
1769 {
1770 aPoint *= aRotateMatrix;
1771 aBackward *= aRotateMatrix;
1772 aUnitHalfCircle.appendBezierSegment(aForward, aBackward, aPoint);
1773 aForward *= aRotateMatrix;
1774 }
1775 return aUnitHalfCircle;
1776 }();
1777 return singleton;
1778 }
1779
1780 B2DPolygon const & createPolygonFromUnitCircle(sal_uInt32 nStartQuadrant)
1781 {
1782 switch(nStartQuadrant % 4)
1783 {
1784 case 1 :
1785 {
1786 static auto const singleton = impCreateUnitCircle(1);
1787 return singleton;
1788 }
1789
1790 case 2 :
1791 {
1792 static auto const singleton = impCreateUnitCircle(2);
1793 return singleton;
1794 }
1795
1796 case 3 :
1797 {
1798 static auto const singleton = impCreateUnitCircle(3);
1799 return singleton;
1800 }
1801
1802 default : // case 0 :
1803 {
1804 static auto const singleton = impCreateUnitCircle(0);
1805 return singleton;
1806 }
1807 }
1808 }
1809
1810 B2DPolygon createPolygonFromEllipse( const B2DPoint& rCenter, double fRadiusX, double fRadiusY, sal_uInt32 nStartQuadrant)
1811 {
1812 B2DPolygon aRetval(createPolygonFromUnitCircle(nStartQuadrant));
1813 const B2DHomMatrix aMatrix(createScaleTranslateB2DHomMatrix(fRadiusX, fRadiusY, rCenter.getX(), rCenter.getY()));
1814
1815 aRetval.transform(aMatrix);
1816
1817 return aRetval;
1818 }
1819
1821 {
1822 B2DPolygon aRetval;
1823
1824 // truncate fStart, fEnd to a range of [0.0 .. 2PI[ where 2PI
1825 // falls back to 0.0 to ensure a unique definition
1826 if(fTools::less(fStart, 0.0))
1827 {
1828 fStart = 0.0;
1829 }
1830
1831 if(fTools::moreOrEqual(fStart, 2 * M_PI))
1832 {
1833 fStart = 0.0;
1834 }
1835
1836 if(fTools::less(fEnd, 0.0))
1837 {
1838 fEnd = 0.0;
1839 }
1840
1841 if(fTools::moreOrEqual(fEnd, 2 * M_PI))
1842 {
1843 fEnd = 0.0;
1844 }
1845
1846 if(fTools::equal(fStart, fEnd))
1847 {
1848 // same start and end angle, add single point
1849 aRetval.append(B2DPoint(cos(fStart), sin(fStart)));
1850 }
1851 else
1852 {
1853 const sal_uInt32 nSegments(STEPSPERQUARTER * 4);
1854 const double fAnglePerSegment(M_PI_2 / STEPSPERQUARTER);
1855 const sal_uInt32 nStartSegment(sal_uInt32(fStart / fAnglePerSegment) % nSegments);
1856 const sal_uInt32 nEndSegment(sal_uInt32(fEnd / fAnglePerSegment) % nSegments);
1857 const double fSegmentKappa(impDistanceBezierPointToControl(fAnglePerSegment));
1858
1859 B2DPoint aSegStart(cos(fStart), sin(fStart));
1860 aRetval.append(aSegStart);
1861
1862 if(nStartSegment == nEndSegment && fTools::more(fEnd, fStart))
1863 {
1864 // start and end in one sector and in the right order, create in one segment
1865 const B2DPoint aSegEnd(cos(fEnd), sin(fEnd));
1866 const double fFactor(impDistanceBezierPointToControl(fEnd - fStart));
1867
1868 aRetval.appendBezierSegment(
1869 aSegStart + (B2DPoint(-aSegStart.getY(), aSegStart.getX()) * fFactor),
1870 aSegEnd - (B2DPoint(-aSegEnd.getY(), aSegEnd.getX()) * fFactor),
1871 aSegEnd);
1872 }
1873 else
1874 {
1875 double fSegEndRad((nStartSegment + 1) * fAnglePerSegment);
1876 double fFactor(impDistanceBezierPointToControl(fSegEndRad - fStart));
1877 B2DPoint aSegEnd(cos(fSegEndRad), sin(fSegEndRad));
1878
1879 aRetval.appendBezierSegment(
1880 aSegStart + (B2DPoint(-aSegStart.getY(), aSegStart.getX()) * fFactor),
1881 aSegEnd - (B2DPoint(-aSegEnd.getY(), aSegEnd.getX()) * fFactor),
1882 aSegEnd);
1883
1884 sal_uInt32 nSegment((nStartSegment + 1) % nSegments);
1885 aSegStart = aSegEnd;
1886
1887 while(nSegment != nEndSegment)
1888 {
1889 // No end in this sector, add full sector.
1890 fSegEndRad = (nSegment + 1) * fAnglePerSegment;
1891 aSegEnd = B2DPoint(cos(fSegEndRad), sin(fSegEndRad));
1892
1893 aRetval.appendBezierSegment(
1894 aSegStart + (B2DPoint(-aSegStart.getY(), aSegStart.getX()) * fSegmentKappa),
1895 aSegEnd - (B2DPoint(-aSegEnd.getY(), aSegEnd.getX()) * fSegmentKappa),
1896 aSegEnd);
1897
1898 nSegment = (nSegment + 1) % nSegments;
1899 aSegStart = aSegEnd;
1900 }
1901
1902 // End in this sector
1903 const double fSegStartRad(nSegment * fAnglePerSegment);
1904 fFactor= impDistanceBezierPointToControl(fEnd - fSegStartRad);
1905 aSegEnd = B2DPoint(cos(fEnd), sin(fEnd));
1906
1907 aRetval.appendBezierSegment(
1908 aSegStart + (B2DPoint(-aSegStart.getY(), aSegStart.getX()) * fFactor),
1909 aSegEnd - (B2DPoint(-aSegEnd.getY(), aSegEnd.getX()) * fFactor),
1910 aSegEnd);
1911 }
1912 }
1913
1914 // remove double points between segments created by segmented creation
1915 aRetval.removeDoublePoints();
1916
1917 return aRetval;
1918 }
1919
1920 B2DPolygon createPolygonFromEllipseSegment( const B2DPoint& rCenter, double fRadiusX, double fRadiusY, double fStart, double fEnd )
1921 {
1922 B2DPolygon aRetval(createPolygonFromUnitEllipseSegment(fStart, fEnd));
1923 const B2DHomMatrix aMatrix(createScaleTranslateB2DHomMatrix(fRadiusX, fRadiusY, rCenter.getX(), rCenter.getY()));
1924
1925 aRetval.transform(aMatrix);
1926
1927 return aRetval;
1928 }
1929
1930 bool hasNeutralPoints(const B2DPolygon& rCandidate)
1931 {
1932 OSL_ENSURE(!rCandidate.areControlPointsUsed(), "hasNeutralPoints: ATM works not for curves (!)");
1933 const sal_uInt32 nPointCount(rCandidate.count());
1934
1935 if(nPointCount <= 2)
1936 return false;
1937
1938 B2DPoint aPrevPoint(rCandidate.getB2DPoint(nPointCount - 1));
1939 B2DPoint aCurrPoint(rCandidate.getB2DPoint(0));
1940
1941 for(sal_uInt32 a(0); a < nPointCount; a++)
1942 {
1943 const B2DPoint aNextPoint(rCandidate.getB2DPoint((a + 1) % nPointCount));
1944 const B2DVector aPrevVec(aPrevPoint - aCurrPoint);
1945 const B2DVector aNextVec(aNextPoint - aCurrPoint);
1946 const B2VectorOrientation aOrientation(getOrientation(aNextVec, aPrevVec));
1947
1948 if(aOrientation == B2VectorOrientation::Neutral)
1949 {
1950 // current has neutral orientation
1951 return true;
1952 }
1953 else
1954 {
1955 // prepare next
1956 aPrevPoint = aCurrPoint;
1957 aCurrPoint = aNextPoint;
1958 }
1959 }
1960
1961 return false;
1962 }
1963
1965 {
1966 if(hasNeutralPoints(rCandidate))
1967 {
1968 const sal_uInt32 nPointCount(rCandidate.count());
1969 B2DPolygon aRetval;
1970 B2DPoint aPrevPoint(rCandidate.getB2DPoint(nPointCount - 1));
1971 B2DPoint aCurrPoint(rCandidate.getB2DPoint(0));
1972
1973 for(sal_uInt32 a(0); a < nPointCount; a++)
1974 {
1975 const B2DPoint aNextPoint(rCandidate.getB2DPoint((a + 1) % nPointCount));
1976 const B2DVector aPrevVec(aPrevPoint - aCurrPoint);
1977 const B2DVector aNextVec(aNextPoint - aCurrPoint);
1978 const B2VectorOrientation aOrientation(getOrientation(aNextVec, aPrevVec));
1979
1980 if(aOrientation == B2VectorOrientation::Neutral)
1981 {
1982 // current has neutral orientation, leave it out and prepare next
1983 aCurrPoint = aNextPoint;
1984 }
1985 else
1986 {
1987 // add current point
1988 aRetval.append(aCurrPoint);
1989
1990 // prepare next
1991 aPrevPoint = aCurrPoint;
1992 aCurrPoint = aNextPoint;
1993 }
1994 }
1995
1996 while(aRetval.count() && getOrientationForIndex(aRetval, 0) == B2VectorOrientation::Neutral)
1997 {
1998 aRetval.remove(0);
1999 }
2000
2001 // copy closed state
2002 aRetval.setClosed(rCandidate.isClosed());
2003
2004 return aRetval;
2005 }
2006 else
2007 {
2008 return rCandidate;
2009 }
2010 }
2011
2012 bool isConvex(const B2DPolygon& rCandidate)
2013 {
2014 OSL_ENSURE(!rCandidate.areControlPointsUsed(), "isConvex: ATM works not for curves (!)");
2015 const sal_uInt32 nPointCount(rCandidate.count());
2016
2017 if(nPointCount <= 2)
2018 return true;
2019
2020 const B2DPoint aPrevPoint(rCandidate.getB2DPoint(nPointCount - 1));
2021 B2DPoint aCurrPoint(rCandidate.getB2DPoint(0));
2022 B2DVector aCurrVec(aPrevPoint - aCurrPoint);
2024
2025 for(sal_uInt32 a(0); a < nPointCount; a++)
2026 {
2027 const B2DPoint aNextPoint(rCandidate.getB2DPoint((a + 1) % nPointCount));
2028 const B2DVector aNextVec(aNextPoint - aCurrPoint);
2029 const B2VectorOrientation aCurrentOrientation(getOrientation(aNextVec, aCurrVec));
2030
2031 if(aOrientation == B2VectorOrientation::Neutral)
2032 {
2033 // set start value, maybe neutral again
2034 aOrientation = aCurrentOrientation;
2035 }
2036 else
2037 {
2038 if(aCurrentOrientation != B2VectorOrientation::Neutral && aCurrentOrientation != aOrientation)
2039 {
2040 // different orientations found, that's it
2041 return false;
2042 }
2043 }
2044
2045 // prepare next
2046 aCurrPoint = aNextPoint;
2047 aCurrVec = -aNextVec;
2048 }
2049
2050 return true;
2051 }
2052
2053 B2VectorOrientation getOrientationForIndex(const B2DPolygon& rCandidate, sal_uInt32 nIndex)
2054 {
2055 OSL_ENSURE(nIndex < rCandidate.count(), "getOrientationForIndex: index out of range (!)");
2056 const B2DPoint aPrev(rCandidate.getB2DPoint(getIndexOfPredecessor(nIndex, rCandidate)));
2057 const B2DPoint aCurr(rCandidate.getB2DPoint(nIndex));
2058 const B2DPoint aNext(rCandidate.getB2DPoint(getIndexOfSuccessor(nIndex, rCandidate)));
2059 const B2DVector aBack(aPrev - aCurr);
2060 const B2DVector aForw(aNext - aCurr);
2061
2062 return getOrientation(aForw, aBack);
2063 }
2064
2065 bool isPointOnLine(const B2DPoint& rStart, const B2DPoint& rEnd, const B2DPoint& rCandidate, bool bWithPoints)
2066 {
2067 if(rCandidate.equal(rStart) || rCandidate.equal(rEnd))
2068 {
2069 // candidate is in epsilon around start or end -> inside
2070 return bWithPoints;
2071 }
2072 else if(rStart.equal(rEnd))
2073 {
2074 // start and end are equal, but candidate is outside their epsilon -> outside
2075 return false;
2076 }
2077 else
2078 {
2079 const B2DVector aEdgeVector(rEnd - rStart);
2080 const B2DVector aTestVector(rCandidate - rStart);
2081
2082 if(areParallel(aEdgeVector, aTestVector))
2083 {
2084 const double fZero(0.0);
2085 const double fOne(1.0);
2086 const double fParamTestOnCurr(fabs(aEdgeVector.getX()) > fabs(aEdgeVector.getY())
2087 ? aTestVector.getX() / aEdgeVector.getX()
2088 : aTestVector.getY() / aEdgeVector.getY());
2089
2090 if(fTools::more(fParamTestOnCurr, fZero) && fTools::less(fParamTestOnCurr, fOne))
2091 {
2092 return true;
2093 }
2094 }
2095
2096 return false;
2097 }
2098 }
2099
2100 bool isPointOnPolygon(const B2DPolygon& rCandidate, const B2DPoint& rPoint, bool bWithPoints)
2101 {
2102 const B2DPolygon aCandidate(rCandidate.areControlPointsUsed() ? rCandidate.getDefaultAdaptiveSubdivision() : rCandidate);
2103 const sal_uInt32 nPointCount(aCandidate.count());
2104
2105 if(nPointCount > 1)
2106 {
2107 const sal_uInt32 nLoopCount(aCandidate.isClosed() ? nPointCount : nPointCount - 1);
2108 B2DPoint aCurrentPoint(aCandidate.getB2DPoint(0));
2109
2110 for(sal_uInt32 a(0); a < nLoopCount; a++)
2111 {
2112 const B2DPoint aNextPoint(aCandidate.getB2DPoint((a + 1) % nPointCount));
2113
2114 if(isPointOnLine(aCurrentPoint, aNextPoint, rPoint, bWithPoints))
2115 {
2116 return true;
2117 }
2118
2119 aCurrentPoint = aNextPoint;
2120 }
2121 }
2122 else if(nPointCount && bWithPoints)
2123 {
2124 return rPoint.equal(aCandidate.getB2DPoint(0));
2125 }
2126
2127 return false;
2128 }
2129
2130 bool isPointInTriangle(const B2DPoint& rA, const B2DPoint& rB, const B2DPoint& rC, const B2DPoint& rCandidate, bool bWithBorder)
2131 {
2132 if(arePointsOnSameSideOfLine(rA, rB, rC, rCandidate, bWithBorder))
2133 {
2134 if(arePointsOnSameSideOfLine(rB, rC, rA, rCandidate, bWithBorder))
2135 {
2136 if(arePointsOnSameSideOfLine(rC, rA, rB, rCandidate, bWithBorder))
2137 {
2138 return true;
2139 }
2140 }
2141 }
2142
2143 return false;
2144 }
2145
2146 bool arePointsOnSameSideOfLine(const B2DPoint& rStart, const B2DPoint& rEnd, const B2DPoint& rCandidateA, const B2DPoint& rCandidateB, bool bWithLine)
2147 {
2148 const B2DVector aLineVector(rEnd - rStart);
2149 const B2DVector aVectorToA(rEnd - rCandidateA);
2150 const double fCrossA(aLineVector.cross(aVectorToA));
2151
2152 // tdf#88352 increase numerical correctness and use rtl::math::approxEqual
2153 // instead of fTools::equalZero which compares with a fixed small value
2154 if(fCrossA == 0.0)
2155 {
2156 // one point on the line
2157 return bWithLine;
2158 }
2159
2160 const B2DVector aVectorToB(rEnd - rCandidateB);
2161 const double fCrossB(aLineVector.cross(aVectorToB));
2162
2163 // increase numerical correctness
2164 if(fCrossB == 0.0)
2165 {
2166 // one point on the line
2167 return bWithLine;
2168 }
2169
2170 // return true if they both have the same sign
2171 return ((fCrossA > 0.0) == (fCrossB > 0.0));
2172 }
2173
2175 const B2DPolygon& rCandidate,
2177 {
2178 const sal_uInt32 nCount(rCandidate.count());
2179
2180 if(nCount <= 2)
2181 return;
2182
2183 const B2DPoint aStart(rCandidate.getB2DPoint(0));
2184 B2DPoint aLast(rCandidate.getB2DPoint(1));
2185
2186 for(sal_uInt32 a(2); a < nCount; a++)
2187 {
2188 const B2DPoint aCurrent(rCandidate.getB2DPoint(a));
2189 rTarget.emplace_back(
2190 aStart,
2191 aLast,
2192 aCurrent);
2193
2194 // prepare next
2195 aLast = aCurrent;
2196 }
2197 }
2198
2199 namespace
2200 {
2202 int lcl_sgn( const double n )
2203 {
2204 return n == 0.0 ? 0 : 1 - 2*int(std::signbit(n));
2205 }
2206 }
2207
2208 bool isRectangle( const B2DPolygon& rPoly )
2209 {
2210 // polygon must be closed to resemble a rect, and contain
2211 // at least four points.
2212 if( !rPoly.isClosed() ||
2213 rPoly.count() < 4 ||
2214 rPoly.areControlPointsUsed() )
2215 {
2216 return false;
2217 }
2218
2219 // number of 90 degree turns the polygon has taken
2220 int nNumTurns(0);
2221
2222 int nVerticalEdgeType=0;
2223 int nHorizontalEdgeType=0;
2224 bool bNullVertex(true);
2225 bool bCWPolygon(false); // when true, polygon is CW
2226 // oriented, when false, CCW
2227 bool bOrientationSet(false); // when false, polygon
2228 // orientation has not yet
2229 // been determined.
2230
2231 // scan all _edges_ (which involves coming back to point 0
2232 // for the last edge - thus the modulo operation below)
2233 const sal_Int32 nCount( rPoly.count() );
2234 for( sal_Int32 i=0; i<nCount; ++i )
2235 {
2236 const B2DPoint& rPoint0( rPoly.getB2DPoint(i % nCount) );
2237 const B2DPoint& rPoint1( rPoly.getB2DPoint((i+1) % nCount) );
2238
2239 // is 0 for zero direction vector, 1 for south edge and -1
2240 // for north edge (standard screen coordinate system)
2241 int nCurrVerticalEdgeType( lcl_sgn( rPoint1.getY() - rPoint0.getY() ) );
2242
2243 // is 0 for zero direction vector, 1 for east edge and -1
2244 // for west edge (standard screen coordinate system)
2245 int nCurrHorizontalEdgeType( lcl_sgn(rPoint1.getX() - rPoint0.getX()) );
2246
2247 if( nCurrVerticalEdgeType && nCurrHorizontalEdgeType )
2248 return false; // oblique edge - for sure no rect
2249
2250 const bool bCurrNullVertex( !nCurrVerticalEdgeType && !nCurrHorizontalEdgeType );
2251
2252 // current vertex is equal to previous - just skip,
2253 // until we have a real edge
2254 if( bCurrNullVertex )
2255 continue;
2256
2257 // if previous edge has two identical points, because
2258 // no previous edge direction was available, simply
2259 // take this first non-null edge as the start
2260 // direction. That's what will happen here, if
2261 // bNullVertex is false
2262 if( !bNullVertex )
2263 {
2264 // 2D cross product - is 1 for CW and -1 for CCW turns
2265 const int nCrossProduct( nHorizontalEdgeType*nCurrVerticalEdgeType -
2266 nVerticalEdgeType*nCurrHorizontalEdgeType );
2267
2268 if( !nCrossProduct )
2269 continue; // no change in orientation -
2270 // collinear edges - just go on
2271
2272 // if polygon orientation is not set, we'll
2273 // determine it now
2274 if( !bOrientationSet )
2275 {
2276 bCWPolygon = nCrossProduct == 1;
2277 bOrientationSet = true;
2278 }
2279 else
2280 {
2281 // if current turn orientation is not equal
2282 // initial orientation, this is not a
2283 // rectangle (as rectangles have consistent
2284 // orientation).
2285 if( (nCrossProduct == 1) != bCWPolygon )
2286 return false;
2287 }
2288
2289 ++nNumTurns;
2290
2291 // More than four 90 degree turns are an
2292 // indication that this must not be a rectangle.
2293 if( nNumTurns > 4 )
2294 return false;
2295 }
2296
2297 // store current state for the next turn
2298 nVerticalEdgeType = nCurrVerticalEdgeType;
2299 nHorizontalEdgeType = nCurrHorizontalEdgeType;
2300 bNullVertex = false; // won't reach this line,
2301 // if bCurrNullVertex is
2302 // true - see above
2303 }
2304
2305 return true;
2306 }
2307
2308 B3DPolygon createB3DPolygonFromB2DPolygon(const B2DPolygon& rCandidate, double fZCoordinate)
2309 {
2310 if(rCandidate.areControlPointsUsed())
2311 {
2312 // call myself recursively with subdivided input
2313 const B2DPolygon aCandidate(adaptiveSubdivideByAngle(rCandidate));
2314 return createB3DPolygonFromB2DPolygon(aCandidate, fZCoordinate);
2315 }
2316 else
2317 {
2318 B3DPolygon aRetval;
2319
2320 for(sal_uInt32 a(0); a < rCandidate.count(); a++)
2321 {
2322 B2DPoint aPoint(rCandidate.getB2DPoint(a));
2323 aRetval.append(B3DPoint(aPoint.getX(), aPoint.getY(), fZCoordinate));
2324 }
2325
2326 // copy closed state
2327 aRetval.setClosed(rCandidate.isClosed());
2328
2329 return aRetval;
2330 }
2331 }
2332
2334 {
2335 B2DPolygon aRetval;
2336 const sal_uInt32 nCount(rCandidate.count());
2337 const bool bIsIdentity(rMat.isIdentity());
2338
2339 for(sal_uInt32 a(0); a < nCount; a++)
2340 {
2341 B3DPoint aCandidate(rCandidate.getB3DPoint(a));
2342
2343 if(!bIsIdentity)
2344 {
2345 aCandidate *= rMat;
2346 }
2347
2348 aRetval.append(B2DPoint(aCandidate.getX(), aCandidate.getY()));
2349 }
2350
2351 // copy closed state
2352 aRetval.setClosed(rCandidate.isClosed());
2353
2354 return aRetval;
2355 }
2356
2357 double getSmallestDistancePointToEdge(const B2DPoint& rPointA, const B2DPoint& rPointB, const B2DPoint& rTestPoint, double& rCut)
2358 {
2359 if(rPointA.equal(rPointB))
2360 {
2361 rCut = 0.0;
2362 const B2DVector aVector(rTestPoint - rPointA);
2363 return aVector.getLength();
2364 }
2365 else
2366 {
2367 // get the relative cut value on line vector (Vector1) for cut with perpendicular through TestPoint
2368 const B2DVector aVector1(rPointB - rPointA);
2369 const B2DVector aVector2(rTestPoint - rPointA);
2370 const double fDividend((aVector2.getX() * aVector1.getX()) + (aVector2.getY() * aVector1.getY()));
2371 const double fDivisor((aVector1.getX() * aVector1.getX()) + (aVector1.getY() * aVector1.getY()));
2372 const double fCut(fDividend / fDivisor);
2373
2374 if(fCut < 0.0)
2375 {
2376 // not in line range, get distance to PointA
2377 rCut = 0.0;
2378 return aVector2.getLength();
2379 }
2380 else if(fCut > 1.0)
2381 {
2382 // not in line range, get distance to PointB
2383 rCut = 1.0;
2384 const B2DVector aVector(rTestPoint - rPointB);
2385 return aVector.getLength();
2386 }
2387 else
2388 {
2389 // in line range
2390 const B2DPoint aCutPoint(rPointA + fCut * aVector1);
2391 const B2DVector aVector(rTestPoint - aCutPoint);
2392 rCut = fCut;
2393 return aVector.getLength();
2394 }
2395 }
2396 }
2397
2398 double getSmallestDistancePointToPolygon(const B2DPolygon& rCandidate, const B2DPoint& rTestPoint, sal_uInt32& rEdgeIndex, double& rCut)
2399 {
2400 double fRetval(DBL_MAX);
2401 const sal_uInt32 nPointCount(rCandidate.count());
2402
2403 if(nPointCount > 1)
2404 {
2405 const double fZero(0.0);
2406 const sal_uInt32 nEdgeCount(rCandidate.isClosed() ? nPointCount : nPointCount - 1);
2407 B2DCubicBezier aBezier;
2408 aBezier.setStartPoint(rCandidate.getB2DPoint(0));
2409
2410 for(sal_uInt32 a(0); a < nEdgeCount; a++)
2411 {
2412 const sal_uInt32 nNextIndex((a + 1) % nPointCount);
2413 aBezier.setEndPoint(rCandidate.getB2DPoint(nNextIndex));
2414 double fEdgeDist;
2415 double fNewCut(0.0);
2416 bool bEdgeIsCurve(false);
2417
2418 if(rCandidate.areControlPointsUsed())
2419 {
2420 aBezier.setControlPointA(rCandidate.getNextControlPoint(a));
2421 aBezier.setControlPointB(rCandidate.getPrevControlPoint(nNextIndex));
2422 aBezier.testAndSolveTrivialBezier();
2423 bEdgeIsCurve = aBezier.isBezier();
2424 }
2425
2426 if(bEdgeIsCurve)
2427 {
2428 fEdgeDist = aBezier.getSmallestDistancePointToBezierSegment(rTestPoint, fNewCut);
2429 }
2430 else
2431 {
2432 fEdgeDist = getSmallestDistancePointToEdge(aBezier.getStartPoint(), aBezier.getEndPoint(), rTestPoint, fNewCut);
2433 }
2434
2435 if(fRetval == DBL_MAX || fEdgeDist < fRetval)
2436 {
2437 fRetval = fEdgeDist;
2438 rEdgeIndex = a;
2439 rCut = fNewCut;
2440
2441 if(fTools::equal(fRetval, fZero))
2442 {
2443 // already found zero distance, cannot get better. Ensure numerical zero value and end loop.
2444 fRetval = 0.0;
2445 break;
2446 }
2447 }
2448
2449 // prepare next step
2450 aBezier.setStartPoint(aBezier.getEndPoint());
2451 }
2452
2453 if(rtl::math::approxEqual(1.0, rCut))
2454 {
2455 // correct rEdgeIndex when not last point
2456 if(rCandidate.isClosed())
2457 {
2458 rEdgeIndex = getIndexOfSuccessor(rEdgeIndex, rCandidate);
2459 rCut = 0.0;
2460 }
2461 else
2462 {
2463 if(rEdgeIndex != nEdgeCount - 1)
2464 {
2465 rEdgeIndex++;
2466 rCut = 0.0;
2467 }
2468 }
2469 }
2470 }
2471
2472 return fRetval;
2473 }
2474
2475 B2DPoint distort(const B2DPoint& rCandidate, const B2DRange& rOriginal, const B2DPoint& rTopLeft, const B2DPoint& rTopRight, const B2DPoint& rBottomLeft, const B2DPoint& rBottomRight)
2476 {
2477 if(fTools::equalZero(rOriginal.getWidth()) || fTools::equalZero(rOriginal.getHeight()))
2478 {
2479 return rCandidate;
2480 }
2481 else
2482 {
2483 const double fRelativeX((rCandidate.getX() - rOriginal.getMinX()) / rOriginal.getWidth());
2484 const double fRelativeY((rCandidate.getY() - rOriginal.getMinY()) / rOriginal.getHeight());
2485 const double fOneMinusRelativeX(1.0 - fRelativeX);
2486 const double fOneMinusRelativeY(1.0 - fRelativeY);
2487 const double fNewX(fOneMinusRelativeY * (fOneMinusRelativeX * rTopLeft.getX() + fRelativeX * rTopRight.getX()) +
2488 fRelativeY * (fOneMinusRelativeX * rBottomLeft.getX() + fRelativeX * rBottomRight.getX()));
2489 const double fNewY(fOneMinusRelativeX * (fOneMinusRelativeY * rTopLeft.getY() + fRelativeY * rBottomLeft.getY()) +
2490 fRelativeX * (fOneMinusRelativeY * rTopRight.getY() + fRelativeY * rBottomRight.getY()));
2491
2492 return B2DPoint(fNewX, fNewY);
2493 }
2494 }
2495
2496 B2DPolygon distort(const B2DPolygon& rCandidate, const B2DRange& rOriginal, const B2DPoint& rTopLeft, const B2DPoint& rTopRight, const B2DPoint& rBottomLeft, const B2DPoint& rBottomRight)
2497 {
2498 const sal_uInt32 nPointCount(rCandidate.count());
2499
2500 if(nPointCount && rOriginal.getWidth() != 0.0 && rOriginal.getHeight() != 0.0)
2501 {
2502 B2DPolygon aRetval;
2503
2504 for(sal_uInt32 a(0); a < nPointCount; a++)
2505 {
2506 aRetval.append(distort(rCandidate.getB2DPoint(a), rOriginal, rTopLeft, rTopRight, rBottomLeft, rBottomRight));
2507
2508 if(rCandidate.areControlPointsUsed())
2509 {
2510 if(!rCandidate.getPrevControlPoint(a).equalZero())
2511 {
2512 aRetval.setPrevControlPoint(a, distort(rCandidate.getPrevControlPoint(a), rOriginal, rTopLeft, rTopRight, rBottomLeft, rBottomRight));
2513 }
2514
2515 if(!rCandidate.getNextControlPoint(a).equalZero())
2516 {
2517 aRetval.setNextControlPoint(a, distort(rCandidate.getNextControlPoint(a), rOriginal, rTopLeft, rTopRight, rBottomLeft, rBottomRight));
2518 }
2519 }
2520 }
2521
2522 aRetval.setClosed(rCandidate.isClosed());
2523 return aRetval;
2524 }
2525 else
2526 {
2527 return rCandidate;
2528 }
2529 }
2530
2532 {
2533 B2DPolygon aRetval(rCandidate);
2534
2535 for(sal_uInt32 a(0); a < rCandidate.count(); a++)
2536 {
2537 expandToCurveInPoint(aRetval, a);
2538 }
2539
2540 return aRetval;
2541 }
2542
2543 bool expandToCurveInPoint(B2DPolygon& rCandidate, sal_uInt32 nIndex)
2544 {
2545 OSL_ENSURE(nIndex < rCandidate.count(), "expandToCurveInPoint: Access to polygon out of range (!)");
2546 bool bRetval(false);
2547 const sal_uInt32 nPointCount(rCandidate.count());
2548
2549 if(nPointCount)
2550 {
2551 // predecessor
2552 if(!rCandidate.isPrevControlPointUsed(nIndex))
2553 {
2554 if(!rCandidate.isClosed() && nIndex == 0)
2555 {
2556 // do not create previous vector for start point of open polygon
2557 }
2558 else
2559 {
2560 const sal_uInt32 nPrevIndex((nIndex + (nPointCount - 1)) % nPointCount);
2561 rCandidate.setPrevControlPoint(nIndex, interpolate(rCandidate.getB2DPoint(nIndex), rCandidate.getB2DPoint(nPrevIndex), 1.0 / 3.0));
2562 bRetval = true;
2563 }
2564 }
2565
2566 // successor
2567 if(!rCandidate.isNextControlPointUsed(nIndex))
2568 {
2569 if(!rCandidate.isClosed() && nIndex + 1 == nPointCount)
2570 {
2571 // do not create next vector for end point of open polygon
2572 }
2573 else
2574 {
2575 const sal_uInt32 nNextIndex((nIndex + 1) % nPointCount);
2576 rCandidate.setNextControlPoint(nIndex, interpolate(rCandidate.getB2DPoint(nIndex), rCandidate.getB2DPoint(nNextIndex), 1.0 / 3.0));
2577 bRetval = true;
2578 }
2579 }
2580 }
2581
2582 return bRetval;
2583 }
2584
2585 bool setContinuityInPoint(B2DPolygon& rCandidate, sal_uInt32 nIndex, B2VectorContinuity eContinuity)
2586 {
2587 OSL_ENSURE(nIndex < rCandidate.count(), "setContinuityInPoint: Access to polygon out of range (!)");
2588 bool bRetval(false);
2589 const sal_uInt32 nPointCount(rCandidate.count());
2590
2591 if(nPointCount)
2592 {
2593 const B2DPoint aCurrentPoint(rCandidate.getB2DPoint(nIndex));
2594
2595 switch(eContinuity)
2596 {
2598 {
2599 if(rCandidate.isPrevControlPointUsed(nIndex))
2600 {
2601 if(!rCandidate.isClosed() && nIndex == 0)
2602 {
2603 // remove existing previous vector for start point of open polygon
2604 rCandidate.resetPrevControlPoint(nIndex);
2605 }
2606 else
2607 {
2608 const sal_uInt32 nPrevIndex((nIndex + (nPointCount - 1)) % nPointCount);
2609 rCandidate.setPrevControlPoint(nIndex, interpolate(aCurrentPoint, rCandidate.getB2DPoint(nPrevIndex), 1.0 / 3.0));
2610 }
2611
2612 bRetval = true;
2613 }
2614
2615 if(rCandidate.isNextControlPointUsed(nIndex))
2616 {
2617 if(!rCandidate.isClosed() && nIndex == nPointCount + 1)
2618 {
2619 // remove next vector for end point of open polygon
2620 rCandidate.resetNextControlPoint(nIndex);
2621 }
2622 else
2623 {
2624 const sal_uInt32 nNextIndex((nIndex + 1) % nPointCount);
2625 rCandidate.setNextControlPoint(nIndex, interpolate(aCurrentPoint, rCandidate.getB2DPoint(nNextIndex), 1.0 / 3.0));
2626 }
2627
2628 bRetval = true;
2629 }
2630
2631 break;
2632 }
2634 {
2635 if(rCandidate.isPrevControlPointUsed(nIndex) && rCandidate.isNextControlPointUsed(nIndex))
2636 {
2637 // lengths both exist since both are used
2638 B2DVector aVectorPrev(rCandidate.getPrevControlPoint(nIndex) - aCurrentPoint);
2639 B2DVector aVectorNext(rCandidate.getNextControlPoint(nIndex) - aCurrentPoint);
2640 const double fLenPrev(aVectorPrev.getLength());
2641 const double fLenNext(aVectorNext.getLength());
2642 aVectorPrev.normalize();
2643 aVectorNext.normalize();
2644 const B2VectorOrientation aOrientation(getOrientation(aVectorPrev, aVectorNext));
2645
2646 if(aOrientation == B2VectorOrientation::Neutral && aVectorPrev.scalar(aVectorNext) < 0.0)
2647 {
2648 // parallel and opposite direction; check length
2649 if(fTools::equal(fLenPrev, fLenNext))
2650 {
2651 // this would be even C2, but we want C1. Use the lengths of the corresponding edges.
2652 const sal_uInt32 nPrevIndex((nIndex + (nPointCount - 1)) % nPointCount);
2653 const sal_uInt32 nNextIndex((nIndex + 1) % nPointCount);
2654 const double fLenPrevEdge(B2DVector(rCandidate.getB2DPoint(nPrevIndex) - aCurrentPoint).getLength() * (1.0 / 3.0));
2655 const double fLenNextEdge(B2DVector(rCandidate.getB2DPoint(nNextIndex) - aCurrentPoint).getLength() * (1.0 / 3.0));
2656
2657 rCandidate.setControlPoints(nIndex,
2658 aCurrentPoint + (aVectorPrev * fLenPrevEdge),
2659 aCurrentPoint + (aVectorNext * fLenNextEdge));
2660 bRetval = true;
2661 }
2662 }
2663 else
2664 {
2665 // not parallel or same direction, set vectors and length
2666 const B2DVector aNormalizedPerpendicular(getNormalizedPerpendicular(aVectorPrev + aVectorNext));
2667
2668 if(aOrientation == B2VectorOrientation::Positive)
2669 {
2670 rCandidate.setControlPoints(nIndex,
2671 aCurrentPoint - (aNormalizedPerpendicular * fLenPrev),
2672 aCurrentPoint + (aNormalizedPerpendicular * fLenNext));
2673 }
2674 else
2675 {
2676 rCandidate.setControlPoints(nIndex,
2677 aCurrentPoint + (aNormalizedPerpendicular * fLenPrev),
2678 aCurrentPoint - (aNormalizedPerpendicular * fLenNext));
2679 }
2680
2681 bRetval = true;
2682 }
2683 }
2684 break;
2685 }
2687 {
2688 if(rCandidate.isPrevControlPointUsed(nIndex) && rCandidate.isNextControlPointUsed(nIndex))
2689 {
2690 // lengths both exist since both are used
2691 B2DVector aVectorPrev(rCandidate.getPrevControlPoint(nIndex) - aCurrentPoint);
2692 B2DVector aVectorNext(rCandidate.getNextControlPoint(nIndex) - aCurrentPoint);
2693 const double fCommonLength((aVectorPrev.getLength() + aVectorNext.getLength()) / 2.0);
2694 aVectorPrev.normalize();
2695 aVectorNext.normalize();
2696 const B2VectorOrientation aOrientation(getOrientation(aVectorPrev, aVectorNext));
2697
2698 if(aOrientation == B2VectorOrientation::Neutral && aVectorPrev.scalar(aVectorNext) < 0.0)
2699 {
2700 // parallel and opposite direction; set length. Use one direction for better numerical correctness
2701 const B2DVector aScaledDirection(aVectorPrev * fCommonLength);
2702
2703 rCandidate.setControlPoints(nIndex,
2704 aCurrentPoint + aScaledDirection,
2705 aCurrentPoint - aScaledDirection);
2706 }
2707 else
2708 {
2709 // not parallel or same direction, set vectors and length
2710 const B2DVector aNormalizedPerpendicular(getNormalizedPerpendicular(aVectorPrev + aVectorNext));
2711 const B2DVector aPerpendicular(aNormalizedPerpendicular * fCommonLength);
2712
2713 if(aOrientation == B2VectorOrientation::Positive)
2714 {
2715 rCandidate.setControlPoints(nIndex,
2716 aCurrentPoint - aPerpendicular,
2717 aCurrentPoint + aPerpendicular);
2718 }
2719 else
2720 {
2721 rCandidate.setControlPoints(nIndex,
2722 aCurrentPoint + aPerpendicular,
2723 aCurrentPoint - aPerpendicular);
2724 }
2725 }
2726
2727 bRetval = true;
2728 }
2729 break;
2730 }
2731 }
2732 }
2733
2734 return bRetval;
2735 }
2736
2737 B2DPolygon growInNormalDirection(const B2DPolygon& rCandidate, double fValue)
2738 {
2739 if(fValue != 0.0)
2740 {
2741 if(rCandidate.areControlPointsUsed())
2742 {
2743 // call myself recursively with subdivided input
2744 const B2DPolygon aCandidate(adaptiveSubdivideByAngle(rCandidate));
2745 return growInNormalDirection(aCandidate, fValue);
2746 }
2747 else
2748 {
2749 B2DPolygon aRetval;
2750 const sal_uInt32 nPointCount(rCandidate.count());
2751
2752 if(nPointCount)
2753 {
2754 B2DPoint aPrev(rCandidate.getB2DPoint(nPointCount - 1));
2755 B2DPoint aCurrent(rCandidate.getB2DPoint(0));
2756
2757 for(sal_uInt32 a(0); a < nPointCount; a++)
2758 {
2759 const B2DPoint aNext(rCandidate.getB2DPoint(a + 1 == nPointCount ? 0 : a + 1));
2760 const B2DVector aBack(aPrev - aCurrent);
2761 const B2DVector aForw(aNext - aCurrent);
2762 const B2DVector aPerpBack(getNormalizedPerpendicular(aBack));
2763 const B2DVector aPerpForw(getNormalizedPerpendicular(aForw));
2764 B2DVector aDirection(aPerpBack - aPerpForw);
2765 aDirection.normalize();
2766 aDirection *= fValue;
2767 aRetval.append(aCurrent + aDirection);
2768
2769 // prepare next step
2770 aPrev = aCurrent;
2771 aCurrent = aNext;
2772 }
2773 }
2774
2775 // copy closed state
2776 aRetval.setClosed(rCandidate.isClosed());
2777
2778 return aRetval;
2779 }
2780 }
2781 else
2782 {
2783 return rCandidate;
2784 }
2785 }
2786
2787 B2DPolygon reSegmentPolygon(const B2DPolygon& rCandidate, sal_uInt32 nSegments)
2788 {
2789 B2DPolygon aRetval;
2790 const sal_uInt32 nPointCount(rCandidate.count());
2791
2792 if(nPointCount && nSegments)
2793 {
2794 // get current segment count
2795 const sal_uInt32 nSegmentCount(rCandidate.isClosed() ? nPointCount : nPointCount - 1);
2796
2797 if(nSegmentCount == nSegments)
2798 {
2799 aRetval = rCandidate;
2800 }
2801 else
2802 {
2803 const double fLength(getLength(rCandidate));
2804 const sal_uInt32 nLoopCount(rCandidate.isClosed() ? nSegments : nSegments + 1);
2805
2806 for(sal_uInt32 a(0); a < nLoopCount; a++)
2807 {
2808 const double fRelativePos(static_cast<double>(a) / static_cast<double>(nSegments)); // 0.0 .. 1.0
2809 const B2DPoint aNewPoint(getPositionRelative(rCandidate, fRelativePos, fLength));
2810 aRetval.append(aNewPoint);
2811 }
2812
2813 // copy closed flag
2814 aRetval.setClosed(rCandidate.isClosed());
2815 }
2816 }
2817
2818 return aRetval;
2819 }
2820
2821 B2DPolygon interpolate(const B2DPolygon& rOld1, const B2DPolygon& rOld2, double t)
2822 {
2823 OSL_ENSURE(rOld1.count() == rOld2.count(), "B2DPolygon interpolate: Different geometry (!)");
2824
2825 if(fTools::lessOrEqual(t, 0.0) || rOld1 == rOld2)
2826 {
2827 return rOld1;
2828 }
2829 else if(fTools::moreOrEqual(t, 1.0))
2830 {
2831 return rOld2;
2832 }
2833 else
2834 {
2835 B2DPolygon aRetval;
2836 const bool bInterpolateVectors(rOld1.areControlPointsUsed() || rOld2.areControlPointsUsed());
2837 aRetval.setClosed(rOld1.isClosed() && rOld2.isClosed());
2838
2839 for(sal_uInt32 a(0); a < rOld1.count(); a++)
2840 {
2841 aRetval.append(interpolate(rOld1.getB2DPoint(a), rOld2.getB2DPoint(a), t));
2842
2843 if(bInterpolateVectors)
2844 {
2847 }
2848 }
2849
2850 return aRetval;
2851 }
2852 }
2853
2854 // #i76891#
2856 {
2857 const sal_uInt32 nPointCount(rCandidate.count());
2858
2859 if(nPointCount && rCandidate.areControlPointsUsed())
2860 {
2861 // prepare loop
2862 const sal_uInt32 nEdgeCount(rCandidate.isClosed() ? nPointCount : nPointCount - 1);
2863 B2DPolygon aRetval;
2864 B2DCubicBezier aBezier;
2865 aBezier.setStartPoint(rCandidate.getB2DPoint(0));
2866
2867 // try to avoid costly reallocations
2868 aRetval.reserve( nEdgeCount+1);
2869
2870 // add start point
2871 aRetval.append(aBezier.getStartPoint());
2872
2873 for(sal_uInt32 a(0); a < nEdgeCount; a++)
2874 {
2875 // get values for edge
2876 const sal_uInt32 nNextIndex((a + 1) % nPointCount);
2877 aBezier.setEndPoint(rCandidate.getB2DPoint(nNextIndex));
2878 aBezier.setControlPointA(rCandidate.getNextControlPoint(a));
2879 aBezier.setControlPointB(rCandidate.getPrevControlPoint(nNextIndex));
2880 aBezier.testAndSolveTrivialBezier();
2881
2882 // still bezier?
2883 if(aBezier.isBezier())
2884 {
2885 // add edge with control vectors
2886 aRetval.appendBezierSegment(aBezier.getControlPointA(), aBezier.getControlPointB(), aBezier.getEndPoint());
2887 }
2888 else
2889 {
2890 // add edge
2891 aRetval.append(aBezier.getEndPoint());
2892 }
2893
2894 // next point
2895 aBezier.setStartPoint(aBezier.getEndPoint());
2896 }
2897
2898 if(rCandidate.isClosed())
2899 {
2900 // set closed flag, rescue control point and correct last double point
2901 closeWithGeometryChange(aRetval);
2902 }
2903
2904 return aRetval;
2905 }
2906 else
2907 {
2908 return rCandidate;
2909 }
2910 }
2911
2912 // makes the given indexed point the new polygon start point. To do that, the points in the
2913 // polygon will be rotated. This is only valid for closed polygons, for non-closed ones
2914 // an assertion will be triggered
2915 B2DPolygon makeStartPoint(const B2DPolygon& rCandidate, sal_uInt32 nIndexOfNewStatPoint)
2916 {
2917 const sal_uInt32 nPointCount(rCandidate.count());
2918
2919 if(nPointCount > 2 && nIndexOfNewStatPoint != 0 && nIndexOfNewStatPoint < nPointCount)
2920 {
2921 OSL_ENSURE(rCandidate.isClosed(), "makeStartPoint: only valid for closed polygons (!)");
2922 B2DPolygon aRetval;
2923
2924 for(sal_uInt32 a(0); a < nPointCount; a++)
2925 {
2926 const sal_uInt32 nSourceIndex((a + nIndexOfNewStatPoint) % nPointCount);
2927 aRetval.append(rCandidate.getB2DPoint(nSourceIndex));
2928
2929 if(rCandidate.areControlPointsUsed())
2930 {
2931 aRetval.setPrevControlPoint(a, rCandidate.getPrevControlPoint(nSourceIndex));
2932 aRetval.setNextControlPoint(a, rCandidate.getNextControlPoint(nSourceIndex));
2933 }
2934 }
2935
2936 return aRetval;
2937 }
2938
2939 return rCandidate;
2940 }
2941
2942 B2DPolygon createEdgesOfGivenLength(const B2DPolygon& rCandidate, double fLength, double fStart, double fEnd)
2943 {
2944 B2DPolygon aRetval;
2945
2946 if(fLength < 0.0)
2947 {
2948 fLength = 0.0;
2949 }
2950
2951 if(!fTools::equalZero(fLength))
2952 {
2953 if(fStart < 0.0)
2954 {
2955 fStart = 0.0;
2956 }
2957
2958 if(fEnd < 0.0)
2959 {
2960 fEnd = 0.0;
2961 }
2962
2963 if(fEnd < fStart)
2964 {
2965 fEnd = fStart;
2966 }
2967
2968 // iterate and consume pieces with fLength. First subdivide to reduce input to line segments
2969 const B2DPolygon aCandidate(rCandidate.areControlPointsUsed() ? rCandidate.getDefaultAdaptiveSubdivision() : rCandidate);
2970 const sal_uInt32 nPointCount(aCandidate.count());
2971
2972 if(nPointCount > 1)
2973 {
2974 const bool bEndActive(!fTools::equalZero(fEnd));
2975 const sal_uInt32 nEdgeCount(aCandidate.isClosed() ? nPointCount : nPointCount - 1);
2976 B2DPoint aCurrent(aCandidate.getB2DPoint(0));
2977 double fPositionInEdge(fStart);
2978 double fAbsolutePosition(fStart);
2979
2980 for(sal_uInt32 a(0); a < nEdgeCount; a++)
2981 {
2982 const sal_uInt32 nNextIndex((a + 1) % nPointCount);
2983 const B2DPoint aNext(aCandidate.getB2DPoint(nNextIndex));
2984 const B2DVector aEdge(aNext - aCurrent);
2985 double fEdgeLength(aEdge.getLength());
2986
2987 if(!fTools::equalZero(fEdgeLength))
2988 {
2989 while(fTools::less(fPositionInEdge, fEdgeLength))
2990 {
2991 // move position on edge forward as long as on edge
2992 const double fScalar(fPositionInEdge / fEdgeLength);
2993 aRetval.append(aCurrent + (aEdge * fScalar));
2994 fPositionInEdge += fLength;
2995
2996 if(bEndActive)
2997 {
2998 fAbsolutePosition += fLength;
2999
3000 if(fTools::more(fAbsolutePosition, fEnd))
3001 {
3002 break;
3003 }
3004 }
3005 }
3006
3007 // subtract length of current edge
3008 fPositionInEdge -= fEdgeLength;
3009 }
3010
3011 if(bEndActive && fTools::more(fAbsolutePosition, fEnd))
3012 {
3013 break;
3014 }
3015
3016 // prepare next step
3017 aCurrent = aNext;
3018 }
3019
3020 // keep closed state
3021 aRetval.setClosed(aCandidate.isClosed());
3022 }
3023 else
3024 {
3025 // source polygon has only one point, return unchanged
3026 aRetval = aCandidate;
3027 }
3028 }
3029
3030 return aRetval;
3031 }
3032
3033 B2DPolygon createWaveline(const B2DPolygon& rCandidate, double fWaveWidth, double fWaveHeight)
3034 {
3035 B2DPolygon aRetval;
3036
3037 if(fWaveWidth < 0.0)
3038 {
3039 fWaveWidth = 0.0;
3040 }
3041
3042 if(fWaveHeight < 0.0)
3043 {
3044 fWaveHeight = 0.0;
3045 }
3046
3047 const bool bHasWidth(!fTools::equalZero(fWaveWidth));
3048
3049 if(bHasWidth)
3050 {
3051 const bool bHasHeight(!fTools::equalZero(fWaveHeight));
3052 if(bHasHeight)
3053 {
3054 // width and height, create waveline. First subdivide to reduce input to line segments
3055 // of WaveWidth. Last segment may be missing. If this turns out to be a problem, it
3056 // may be added here again using the original last point from rCandidate. It may
3057 // also be the case that rCandidate was closed. To simplify things it is handled here
3058 // as if it was opened.
3059 // Result from createEdgesOfGivenLength contains no curved segments, handle as straight
3060 // edges.
3061 const B2DPolygon aEqualLenghEdges(createEdgesOfGivenLength(rCandidate, fWaveWidth));
3062 const sal_uInt32 nPointCount(aEqualLenghEdges.count());
3063
3064 if(nPointCount > 1)
3065 {
3066 // iterate over straight edges, add start point
3067 B2DPoint aCurrent(aEqualLenghEdges.getB2DPoint(0));
3068 aRetval.append(aCurrent);
3069
3070 for(sal_uInt32 a(0); a < nPointCount - 1; a++)
3071 {
3072 const sal_uInt32 nNextIndex((a + 1) % nPointCount);
3073 const B2DPoint aNext(aEqualLenghEdges.getB2DPoint(nNextIndex));
3074 const B2DVector aEdge(aNext - aCurrent);
3075 const B2DVector aPerpendicular(getNormalizedPerpendicular(aEdge));
3076 const B2DVector aControlOffset((aEdge * 0.467308) - (aPerpendicular * fWaveHeight));
3077
3078 // add curve segment
3079 aRetval.appendBezierSegment(
3080 aCurrent + aControlOffset,
3081 aNext - aControlOffset,
3082 aNext);
3083
3084 // prepare next step
3085 aCurrent = aNext;
3086 }
3087 }
3088 }
3089 else
3090 {
3091 // width but no height -> return original polygon
3092 aRetval = rCandidate;
3093 }
3094 }
3095 else
3096 {
3097 // no width -> no waveline, stay empty and return
3098 }
3099
3100 return aRetval;
3101 }
3102
3103 // snap points of horizontal or vertical edges to discrete values
3105 {
3106 const sal_uInt32 nPointCount(rCandidate.count());
3107
3108 if(nPointCount > 1)
3109 {
3110 // Start by copying the source polygon to get a writeable copy. The closed state is
3111 // copied by aRetval's initialisation, too, so no need to copy it in this method
3112 B2DPolygon aRetval(rCandidate);
3113
3114 // prepare geometry data. Get rounded from original
3115 B2ITuple aPrevTuple(basegfx::fround(rCandidate.getB2DPoint(nPointCount - 1)));
3116 B2DPoint aCurrPoint(rCandidate.getB2DPoint(0));
3117 B2ITuple aCurrTuple(basegfx::fround(aCurrPoint));
3118
3119 // loop over all points. This will also snap the implicit closing edge
3120 // even when not closed, but that's no problem here
3121 for(sal_uInt32 a(0); a < nPointCount; a++)
3122 {
3123 // get next point. Get rounded from original
3124 const bool bLastRun(a + 1 == nPointCount);
3125 const sal_uInt32 nNextIndex(bLastRun ? 0 : a + 1);
3126 const B2DPoint aNextPoint(rCandidate.getB2DPoint(nNextIndex));
3127 const B2ITuple aNextTuple(basegfx::fround(aNextPoint));
3128
3129 // get the states
3130 const bool bPrevVertical(aPrevTuple.getX() == aCurrTuple.getX());
3131 const bool bNextVertical(aNextTuple.getX() == aCurrTuple.getX());
3132 const bool bPrevHorizontal(aPrevTuple.getY() == aCurrTuple.getY());
3133 const bool bNextHorizontal(aNextTuple.getY() == aCurrTuple.getY());
3134 const bool bSnapX(bPrevVertical || bNextVertical);
3135 const bool bSnapY(bPrevHorizontal || bNextHorizontal);
3136
3137 if(bSnapX || bSnapY)
3138 {
3139 const B2DPoint aSnappedPoint(
3140 bSnapX ? aCurrTuple.getX() : aCurrPoint.getX(),
3141 bSnapY ? aCurrTuple.getY() : aCurrPoint.getY());
3142
3143 aRetval.setB2DPoint(a, aSnappedPoint);
3144 }
3145
3146 // prepare next point
3147 if(!bLastRun)
3148 {
3149 aPrevTuple = aCurrTuple;
3150 aCurrPoint = aNextPoint;
3151 aCurrTuple = aNextTuple;
3152 }
3153 }
3154
3155 return aRetval;
3156 }
3157 else
3158 {
3159 return rCandidate;
3160 }
3161 }
3162
3163 B2DVector getTangentEnteringPoint(const B2DPolygon& rCandidate, sal_uInt32 nIndex)
3164 {
3165 B2DVector aRetval(0.0, 0.0);
3166 const sal_uInt32 nCount(rCandidate.count());
3167
3168 if(nIndex >= nCount)
3169 {
3170 // out of range
3171 return aRetval;
3172 }
3173
3174 // start immediately at prev point compared to nIndex
3175 const bool bClosed(rCandidate.isClosed());
3176 sal_uInt32 nPrev(bClosed ? (nIndex + nCount - 1) % nCount : nIndex ? nIndex - 1 : nIndex);
3177
3178 if(nPrev == nIndex)
3179 {
3180 // no previous, done
3181 return aRetval;
3182 }
3183
3184 B2DCubicBezier aSegment;
3185
3186 // go backward in the polygon; if closed, maximal back to start index (nIndex); if not closed,
3187 // until zero. Use nIndex as stop criteria
3188 while(nPrev != nIndex)
3189 {
3190 // get BezierSegment and tangent at the *end* of segment
3191 rCandidate.getBezierSegment(nPrev, aSegment);
3192 aRetval = aSegment.getTangent(1.0);
3193
3194 if(!aRetval.equalZero())
3195 {
3196 // if we have a tangent, return it
3197 return aRetval;
3198 }
3199
3200 // prepare index before checked one
3201 nPrev = bClosed ? (nPrev + nCount - 1) % nCount : nPrev ? nPrev - 1 : nIndex;
3202 }
3203
3204 return aRetval;
3205 }
3206
3207 B2DVector getTangentLeavingPoint(const B2DPolygon& rCandidate, sal_uInt32 nIndex)
3208 {
3209 B2DVector aRetval(0.0, 0.0);
3210 const sal_uInt32 nCount(rCandidate.count());
3211
3212 if(nIndex >= nCount)
3213 {
3214 // out of range
3215 return aRetval;
3216 }
3217
3218 // start at nIndex
3219 const bool bClosed(rCandidate.isClosed());
3220 sal_uInt32 nCurrent(nIndex);
3221 B2DCubicBezier aSegment;
3222
3223 // go forward; if closed, do this until once around and back at start index (nIndex); if not
3224 // closed, until last point (nCount - 1). Use nIndex as stop criteria
3225 do
3226 {
3227 // get BezierSegment and tangent at the *beginning* of segment
3228 rCandidate.getBezierSegment(nCurrent, aSegment);
3229 aRetval = aSegment.getTangent(0.0);
3230
3231 if(!aRetval.equalZero())
3232 {
3233 // if we have a tangent, return it
3234 return aRetval;
3235 }
3236
3237 // prepare next index
3238 nCurrent = bClosed ? (nCurrent + 1) % nCount : nCurrent + 1 < nCount ? nCurrent + 1 : nIndex;
3239 }
3240 while(nCurrent != nIndex);
3241
3242 return aRetval;
3243 }
3244
3245 // converters for css::drawing::PointSequence
3246
3248 const css::drawing::PointSequence& rPointSequenceSource)
3249 {
3250 B2DPolygon aRetval;
3251 const sal_uInt32 nLength(rPointSequenceSource.getLength());
3252
3253 if(nLength)
3254 {
3255 aRetval.reserve(nLength);
3256 const css::awt::Point* pArray = rPointSequenceSource.getConstArray();
3257 const css::awt::Point* pArrayEnd = pArray + rPointSequenceSource.getLength();
3258
3259 for(;pArray != pArrayEnd; pArray++)
3260 {
3261 aRetval.append(B2DPoint(pArray->X, pArray->Y));
3262 }
3263
3264 // check for closed state flag
3265 utils::checkClosed(aRetval);
3266 }
3267
3268 return aRetval;
3269 }
3270
3272 const B2DPolygon& rPolygon,
3273 css::drawing::PointSequence& rPointSequenceRetval)
3274 {
3275 B2DPolygon aPolygon(rPolygon);
3276
3277 if(aPolygon.areControlPointsUsed())
3278 {
3279 OSL_ENSURE(false, "B2DPolygonToUnoPointSequence: Source contains bezier segments, wrong UNO API data type may be used (!)");
3280 aPolygon = aPolygon.getDefaultAdaptiveSubdivision();
3281 }
3282
3283 const sal_uInt32 nPointCount(aPolygon.count());
3284
3285 if(nPointCount)
3286 {
3287 // Take closed state into account, the API polygon still uses the old closed definition
3288 // with last/first point are identical (cannot hold information about open polygons with identical
3289 // first and last point, though)
3290 const bool bIsClosed(aPolygon.isClosed());
3291
3292 rPointSequenceRetval.realloc(bIsClosed ? nPointCount + 1 : nPointCount);
3293 css::awt::Point* pSequence = rPointSequenceRetval.getArray();
3294
3295 for(sal_uInt32 b(0); b < nPointCount; b++)
3296 {
3297 const B2DPoint aPoint(aPolygon.getB2DPoint(b));
3298 const css::awt::Point aAPIPoint(fround(aPoint.getX()), fround(aPoint.getY()));
3299
3300 *pSequence = aAPIPoint;
3301 pSequence++;
3302 }
3303
3304 // copy first point if closed
3305 if(bIsClosed)
3306 {
3307 *pSequence = *rPointSequenceRetval.getConstArray();
3308 }
3309 }
3310 else
3311 {
3312 rPointSequenceRetval.realloc(0);
3313 }
3314 }
3315
3316 // converters for css::drawing::PointSequence and
3317 // css::drawing::FlagSequence to B2DPolygon (curved polygons)
3318
3320 const css::drawing::PointSequence& rPointSequenceSource,
3321 const css::drawing::FlagSequence& rFlagSequenceSource)
3322 {
3323 const sal_uInt32 nCount(static_cast<sal_uInt32>(rPointSequenceSource.getLength()));
3324 OSL_ENSURE(nCount == static_cast<sal_uInt32>(rFlagSequenceSource.getLength()),
3325 "UnoPolygonBezierCoordsToB2DPolygon: Unequal count of Points and Flags (!)");
3326
3327 // prepare new polygon
3328 B2DPolygon aRetval;
3329
3330 if(0 != nCount)
3331 {
3332 aRetval.reserve(nCount);
3333
3334 const css::awt::Point* pPointSequence = rPointSequenceSource.getConstArray();
3335 const css::drawing::PolygonFlags* pFlagSequence = rFlagSequenceSource.getConstArray();
3336
3337 // get first point and flag
3338 B2DPoint aNewCoordinatePair(pPointSequence->X, pPointSequence->Y); pPointSequence++;
3339 css::drawing::PolygonFlags ePolygonFlag(*pFlagSequence); pFlagSequence++;
3340 B2DPoint aControlA;
3341 B2DPoint aControlB;
3342
3343 // first point is not allowed to be a control point
3344 OSL_ENSURE(ePolygonFlag != css::drawing::PolygonFlags_CONTROL,
3345 "UnoPolygonBezierCoordsToB2DPolygon: Start point is a control point, illegal input polygon (!)");
3346
3347 // add first point as start point
3348 aRetval.append(aNewCoordinatePair);
3349
3350 for(sal_uInt32 b(1); b < nCount;)
3351 {
3352 // prepare loop
3353 bool bControlA(false);
3354 bool bControlB(false);
3355
3356 // get next point and flag
3357 aNewCoordinatePair = B2DPoint(pPointSequence->X, pPointSequence->Y);
3358 ePolygonFlag = *pFlagSequence;
3359 pPointSequence++; pFlagSequence++; b++;
3360
3361 if(b < nCount && ePolygonFlag == css::drawing::PolygonFlags_CONTROL)
3362 {
3363 aControlA = aNewCoordinatePair;
3364 bControlA = true;
3365
3366 // get next point and flag
3367 aNewCoordinatePair = B2DPoint(pPointSequence->X, pPointSequence->Y);
3368 ePolygonFlag = *pFlagSequence;
3369 pPointSequence++; pFlagSequence++; b++;
3370 }
3371
3372 if(b < nCount && ePolygonFlag == css::drawing::PolygonFlags_CONTROL)
3373 {
3374 aControlB = aNewCoordinatePair;
3375 bControlB = true;
3376
3377 // get next point and flag
3378 aNewCoordinatePair = B2DPoint(pPointSequence->X, pPointSequence->Y);
3379 ePolygonFlag = *pFlagSequence;
3380 pPointSequence++; pFlagSequence++; b++;
3381 }
3382
3383 // two or no control points are consumed, another one would be an error.
3384 // It's also an error if only one control point was read
3385 SAL_WARN_IF(ePolygonFlag == css::drawing::PolygonFlags_CONTROL || bControlA != bControlB,
3386 "basegfx", "UnoPolygonBezierCoordsToB2DPolygon: Illegal source polygon (!)");
3387
3388 // the previous writes used the B2DPolyPolygon -> utils::PolyPolygon converter
3389 // which did not create minimal PolyPolygons, but created all control points
3390 // as null vectors (identical points). Because of the former P(CA)(CB)-norm of
3391 // B2DPolygon and it's unused sign of being the zero-vector and CA and CB being
3392 // relative to P, an empty edge was exported as P == CA == CB. Luckily, the new
3393 // export format can be read without errors by the old OOo-versions, so we need only
3394 // to correct here at read and do not need to export a wrong but compatible version
3395 // for the future.
3396 if(bControlA
3397 && aControlA.equal(aControlB)
3398 && aControlA.equal(aRetval.getB2DPoint(aRetval.count() - 1)))
3399 {
3400 bControlA = false;
3401 }
3402
3403 if(bControlA)
3404 {
3405 // add bezier edge
3406 aRetval.appendBezierSegment(aControlA, aControlB, aNewCoordinatePair);
3407 }
3408 else
3409 {
3410 // add edge
3411 aRetval.append(aNewCoordinatePair);
3412 }
3413 }
3414
3415 // #i72807# API import uses old line start/end-equal definition for closed,
3416 // so we need to correct this to closed state here
3417 checkClosed(aRetval);
3418 }
3419
3420 return aRetval;
3421 }
3422
3424 const B2DPolygon& rPolygon,
3425 css::drawing::PointSequence& rPointSequenceRetval,
3426 css::drawing::FlagSequence& rFlagSequenceRetval)
3427 {
3428 const sal_uInt32 nPointCount(rPolygon.count());
3429
3430 if(nPointCount)
3431 {
3432 const bool bCurve(rPolygon.areControlPointsUsed());
3433 const bool bClosed(rPolygon.isClosed());
3434
3435 if(bCurve)
3436 {
3437 // calculate target point count
3438 const sal_uInt32 nLoopCount(bClosed ? nPointCount : nPointCount - 1);
3439
3440 if(nLoopCount)
3441 {
3442 // prepare target data. The real needed number of target points (and flags)
3443 // could only be calculated by using two loops, so use dynamic memory
3444 std::vector< css::awt::Point > aCollectPoints;
3445 std::vector< css::drawing::PolygonFlags > aCollectFlags;
3446
3447 // reserve maximum creatable points
3448 const sal_uInt32 nMaxTargetCount((nLoopCount * 3) + 1);
3449 aCollectPoints.reserve(nMaxTargetCount);
3450 aCollectFlags.reserve(nMaxTargetCount);
3451
3452 // prepare current bezier segment by setting start point
3453 B2DCubicBezier aBezierSegment;
3454 aBezierSegment.setStartPoint(rPolygon.getB2DPoint(0));
3455
3456 for(sal_uInt32 a(0); a < nLoopCount; a++)
3457 {
3458 // add current point (always) and remember StartPointIndex for evtl. later corrections
3459 const sal_uInt32 nStartPointIndex(aCollectPoints.size());
3460 aCollectPoints.emplace_back(
3461 fround(aBezierSegment.getStartPoint().getX()),
3462 fround(aBezierSegment.getStartPoint().getY()));
3463 aCollectFlags.push_back(css::drawing::PolygonFlags_NORMAL);
3464
3465 // prepare next segment
3466 const sal_uInt32 nNextIndex((a + 1) % nPointCount);
3467 aBezierSegment.setEndPoint(rPolygon.getB2DPoint(nNextIndex));
3468 aBezierSegment.setControlPointA(rPolygon.getNextControlPoint(a));
3469 aBezierSegment.setControlPointB(rPolygon.getPrevControlPoint(nNextIndex));
3470
3471 if(aBezierSegment.isBezier())
3472 {
3473 // if bezier is used, add always two control points due to the old schema
3474 aCollectPoints.emplace_back(
3475 fround(aBezierSegment.getControlPointA().getX()),
3476 fround(aBezierSegment.getControlPointA().getY()));
3477 aCollectFlags.push_back(css::drawing::PolygonFlags_CONTROL);
3478
3479 aCollectPoints.emplace_back(
3480 fround(aBezierSegment.getControlPointB().getX()),
3481 fround(aBezierSegment.getControlPointB().getY()));
3482 aCollectFlags.push_back(css::drawing::PolygonFlags_CONTROL);
3483 }
3484
3485 // test continuity with previous control point to set flag value
3486 if(aBezierSegment.getControlPointA() != aBezierSegment.getStartPoint() && (bClosed || a))
3487 {
3488 const B2VectorContinuity eCont(rPolygon.getContinuityInPoint(a));
3489
3490 if(eCont == B2VectorContinuity::C1)
3491 {
3492 aCollectFlags[nStartPointIndex] = css::drawing::PolygonFlags_SMOOTH;
3493 }
3494 else if(eCont == B2VectorContinuity::C2)
3495 {
3496 aCollectFlags[nStartPointIndex] = css::drawing::PolygonFlags_SYMMETRIC;
3497 }
3498 }
3499
3500 // prepare next loop
3501 aBezierSegment.setStartPoint(aBezierSegment.getEndPoint());
3502 }
3503
3504 if(bClosed)
3505 {
3506 // add first point again as closing point due to old definition
3507 aCollectPoints.push_back(aCollectPoints[0]);
3508 aCollectFlags.push_back(css::drawing::PolygonFlags_NORMAL);
3509 }
3510 else
3511 {
3512 // add last point as closing point
3513 const B2DPoint aClosingPoint(rPolygon.getB2DPoint(nPointCount - 1));
3514 aCollectPoints.emplace_back(
3515 fround(aClosingPoint.getX()),
3516 fround(aClosingPoint.getY()));
3517 aCollectFlags.push_back(css::drawing::PolygonFlags_NORMAL);
3518 }
3519
3520 // copy collected data to target arrays
3521 const sal_uInt32 nTargetCount(aCollectPoints.size());
3522 OSL_ENSURE(nTargetCount == aCollectFlags.size(), "Unequal Point and Flag count (!)");
3523
3524 rPointSequenceRetval.realloc(static_cast<sal_Int32>(nTargetCount));
3525 rFlagSequenceRetval.realloc(static_cast<sal_Int32>(nTargetCount));
3526 css::awt::Point* pPointSequence = rPointSequenceRetval.getArray();
3527 css::drawing::PolygonFlags* pFlagSequence = rFlagSequenceRetval.getArray();
3528
3529 for(sal_uInt32 a(0); a < nTargetCount; a++)
3530 {
3531 *pPointSequence = aCollectPoints[a];
3532 *pFlagSequence = aCollectFlags[a];
3533 pPointSequence++;
3534 pFlagSequence++;
3535 }
3536 }
3537 }
3538 else
3539 {
3540 // straightforward point list creation
3541 const sal_uInt32 nTargetCount(nPointCount + (bClosed ? 1 : 0));
3542
3543 rPointSequenceRetval.realloc(static_cast<sal_Int32>(nTargetCount));
3544 rFlagSequenceRetval.realloc(static_cast<sal_Int32>(nTargetCount));
3545
3546 css::awt::Point* pPointSequence = rPointSequenceRetval.getArray();
3547 css::drawing::PolygonFlags* pFlagSequence = rFlagSequenceRetval.getArray();
3548
3549 for(sal_uInt32 a(0); a < nPointCount; a++)
3550 {
3551 const B2DPoint aB2DPoint(rPolygon.getB2DPoint(a));
3552 const css::awt::Point aAPIPoint(
3553 fround(aB2DPoint.getX()),
3554 fround(aB2DPoint.getY()));
3555
3556 *pPointSequence = aAPIPoint;
3557 *pFlagSequence = css::drawing::PolygonFlags_NORMAL;
3558 pPointSequence++;
3559 pFlagSequence++;
3560 }
3561
3562 if(bClosed)
3563 {
3564 // add first point as closing point
3565 *pPointSequence = *rPointSequenceRetval.getConstArray();
3566 *pFlagSequence = css::drawing::PolygonFlags_NORMAL;
3567 }
3568 }
3569 }
3570 else
3571 {
3572 rPointSequenceRetval.realloc(0);
3573 rFlagSequenceRetval.realloc(0);
3574 }
3575 }
3576
3577} // end of namespace
3578
3579/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
XPropertyListType t
#define ANGLE_BOUND_MINIMUM_VALUE
#define ANGLE_BOUND_START_VALUE
#define STEPSPERQUARTER
CutFlagValue
double distanceToRelative(double fDistance) const
void setStartPoint(const B2DPoint &rValue)
const B2DPoint & getStartPoint() const
SAL_DLLPRIVATE B2DCubicBezier snippet(double fStart, double fEnd) const
const B2DPoint & getControlPointB() const
SAL_DLLPRIVATE void adaptiveSubdivideByAngle(B2DPolygon &rTarget, double fAngleBound) const
adaptive subdivide by angle criteria no start point is added, but all necessary created edges and the...
double getLength(double fDeviation=0.01) const
get length of edge
SAL_DLLPRIVATE double getSmallestDistancePointToBezierSegment(const B2DPoint &rTestPoint, double &rCut) const
void setControlPointA(const B2DPoint &rValue)
void setEndPoint(const B2DPoint &rValue)
void setControlPointB(const B2DPoint &rValue)
B2DPoint interpolatePoint(double t) const
void split(double t, B2DCubicBezier *pBezierA, B2DCubicBezier *pBezierB) const
const B2DPoint & getEndPoint() const
const B2DPoint & getControlPointA() const
SAL_DLLPRIVATE double getControlPolygonLength() const
B2DVector getTangent(double t) const
get the tangent in point t
SAL_DLLPRIVATE double getEdgeLength() const
void adaptiveSubdivideByDistance(B2DPolygon &rTarget, double fDistanceBound, int nRecurseLimit=30) const
Subdivide cubic bezier segment.
Base Point class with two double values.
Definition: b2dpoint.hxx:42
void append(const B2DPolygon &rPolygon, sal_uInt32 nCount=1)
bool isPrevControlPointUsed(sal_uInt32 nIndex) const
void setB2DPoint(sal_uInt32 nIndex, const basegfx::B2DPoint &rValue)
bool isNextControlPointUsed(sal_uInt32 nIndex) const
void clear()
clear all points
void resetNextControlPoint(sal_uInt32 nIndex)
void setPrevControlPoint(sal_uInt32 nIndex, const basegfx::B2DPoint &rValue)
bool isClosed() const
closed state interface
basegfx::B2DPoint const & getB2DPoint(sal_uInt32 nIndex) const
Coordinate interface.
void transform(const basegfx::B2DHomMatrix &rMatrix)
apply transformation given in matrix form
void getBezierSegment(sal_uInt32 nIndex, B2DCubicBezier &rTarget) const
bezier segment access
void setNextControlPoint(sal_uInt32 nIndex, const basegfx::B2DPoint &rValue)
basegfx::B2DPoint getPrevControlPoint(sal_uInt32 nIndex) const
Basic ControlPoint interface.
void setControlPoints(sal_uInt32 nIndex, const basegfx::B2DPoint &rPrev, const basegfx::B2DPoint &rNext)
bool areControlPointsUsed() const
ControlPoint checks.
B2VectorContinuity getContinuityInPoint(sal_uInt32 nIndex) const
void append(const basegfx::B2DPoint &rPoint, sal_uInt32 nCount)
void reserve(sal_uInt32 nCount)
B2DRange const & getB2DRange() const
Get the B2DRange (Rectangle dimensions) of this B2DPolygon.
void removeDoublePoints()
remove double points, at the begin/end and follow-ups, too
sal_uInt32 count() const
member count
void setClosed(bool bNew)
B2DPolygon const & getDefaultAdaptiveSubdivision() const
Default adaptive subdivision access.
void resetPrevControlPoint(sal_uInt32 nIndex)
ControlPoint resets.
void remove(sal_uInt32 nIndex, sal_uInt32 nCount=1)
remove points
basegfx::B2DPoint getNextControlPoint(sal_uInt32 nIndex) const
void appendBezierSegment(const basegfx::B2DPoint &rNextControlPoint, const basegfx::B2DPoint &rPrevControlPoint, const basegfx::B2DPoint &rPoint)
Bezier segment append with control points. The current last polygon point is implicitly taken as star...
A two-dimensional interval over doubles.
Definition: b2drange.hxx:54
B2DPoint getCenter() const
return center point of set. returns (0,0) for empty sets.
Definition: b2drange.hxx:107
Base Point class with two double values.
Definition: b2dvector.hxx:40
B2DVector & normalize()
Normalize this 2D Vector.
Definition: b2dvector.cxx:26
double scalar(const B2DVector &rVec) const
Calculate the Scalar with another 2D Vector.
Definition: b2dvector.hxx:134
double cross(const B2DVector &rVec) const
Calculate the length of the cross product with another 2D Vector.
Definition: b2dvector.hxx:151
double getLength() const
Calculate the length of this 2D Vector.
Definition: b2dvector.cxx:54
Base class for all Points/Vectors with two sal_Int32 values.
Definition: b2ituple.hxx:37
bool isIdentity() const
Base Point class with three double values.
Definition: b3dpoint.hxx:38
bool isClosed() const
void append(const B3DPoint &rPoint, sal_uInt32 nCount=1)
B3DPoint const & getB3DPoint(sal_uInt32 nIndex) const
sal_uInt32 count() const
void setClosed(bool bNew)
TYPE getMaxX() const
get upper bound of the set. returns arbitrary values for empty sets.
Definition: Range2D.hxx:100
TYPE getWidth() const
return difference between upper and lower X value. returns 0 for empty sets.
Definition: Range2D.hxx:106
TYPE getMinX() const
get lower bound of the set. returns arbitrary values for empty sets.
Definition: Range2D.hxx:94
TYPE getMinY() const
get lower bound of the set. returns arbitrary values for empty sets.
Definition: Range2D.hxx:97
TYPE getMaxY() const
get upper bound of the set. returns arbitrary values for empty sets.
Definition: Range2D.hxx:103
TYPE getHeight() const
return difference between upper and lower Y value. returns 0 for empty sets.
Definition: Range2D.hxx:109
bool equalZero() const
Definition: Tuple2D.hxx:95
bool equal(const Tuple2D< TYPE > &rTup) const
Definition: Tuple2D.hxx:83
TYPE getX() const
Get X-Coordinate of 2D Tuple.
Definition: Tuple2D.hxx:63
TYPE getY() const
Get Y-Coordinate of 2D Tuple.
Definition: Tuple2D.hxx:66
TYPE getX() const
Get X-Coordinate of 3D Tuple.
Definition: Tuple3D.hxx:57
TYPE getY() const
Get Y-Coordinate of 3D Tuple.
Definition: Tuple3D.hxx:60
int nCount
FilterGroup & rTarget
sal_Int32 nIndex
sal_Int64 n
uno_Any a
#define SAL_WARN_IF(condition, area, stream)
bool lessOrEqual(const T &rfValA, const T &rfValB)
Definition: ftools.hxx:188
bool more(const T &rfValA, const T &rfValB)
Definition: ftools.hxx:194
bool equalZero(const T &rfVal)
Compare against small value.
Definition: ftools.hxx:156
bool equal(T const &rfValA, T const &rfValB)
Definition: ftools.hxx:169
bool less(const T &rfValA, const T &rfValB)
Definition: ftools.hxx:182
bool moreOrEqual(const T &rfValA, const T &rfValB)
Definition: ftools.hxx:200
::std::vector< B2DTriangle > B2DTriangleVector
B2DPolygon removeNeutralPoints(const B2DPolygon &rCandidate)
double getArea(const B2DPolygon &rCandidate)
B2VectorContinuity getContinuityInPoint(const B2DPolygon &rCandidate, sal_uInt32 nIndex)
bool isPointOnLine(const B2DPoint &rStart, const B2DPoint &rEnd, const B2DPoint &rCandidate, bool bWithPoints)
B2DPolygon const & createHalfUnitCircle()
create half circle centered on (0,0) from [0 .. M_PI]
static void implHandleSnippet(const B2DPolygon &rSnippet, const std::function< void(const basegfx::B2DPolygon &rSnippet)> &rTargetCallback, B2DPolygon &rFirst, B2DPolygon &rLast)
CutFlagValue findCut(const B2DPoint &rEdge1Start, const B2DVector &rEdge1Delta, const B2DPoint &rEdge2Start, const B2DVector &rEdge2Delta, CutFlagValue aCutFlags, double *pCut1, double *pCut2)
void addTriangleFan(const B2DPolygon &rCandidate, triangulator::B2DTriangleVector &rTarget)
double getLength(const B2DPolygon &rCandidate)
get length of polygon
bool arePointsOnSameSideOfLine(const B2DPoint &rStart, const B2DPoint &rEnd, const B2DPoint &rCandidateA, const B2DPoint &rCandidateB, bool bWithLine)
B2DPolygon reSegmentPolygon(const B2DPolygon &rCandidate, sal_uInt32 nSegments)
B2DHomMatrix createScaleTranslateB2DHomMatrix(double fScaleX, double fScaleY, double fTranslateX, double fTranslateY)
B2DPolygon createEdgesOfGivenLength(const B2DPolygon &rCandidate, double fLength, double fStart, double fEnd)
create edges of given length along given B2DPolygon
B2DPolygon createPolygonFromRect(const B2DRectangle &rRect, double fRadiusX, double fRadiusY)
Create a polygon from a rectangle.
B2DPolygon UnoPointSequenceToB2DPolygon(const css::drawing::PointSequence &rPointSequenceSource)
converters for css::drawing::PointSequence
B2DPolygon interpolate(const B2DPolygon &rOld1, const B2DPolygon &rOld2, double t)
B2DPolygon expandToCurve(const B2DPolygon &rCandidate)
B2DPolygon growInNormalDirection(const B2DPolygon &rCandidate, double fValue)
double getSmallestDistancePointToEdge(const B2DPoint &rPointA, const B2DPoint &rPointB, const B2DPoint &rTestPoint, double &rCut)
B2DPoint getPositionAbsolute(const B2DPolygon &rCandidate, double fDistance, double fLength)
B2DPolygon adaptiveSubdivideByDistance(const B2DPolygon &rCandidate, double fDistanceBound, int nRecurseLimit)
void applyLineDashing(const B2DPolygon &rCandidate, const std::vector< double > &rDotDashArray, B2DPolyPolygon *pLineTarget, B2DPolyPolygon *pGapTarget, double fDotDashLength)
sal_uInt32 getIndexOfSuccessor(sal_uInt32 nIndex, const B2DPolygon &rCandidate)
B2VectorOrientation getOrientationForIndex(const B2DPolygon &rCandidate, sal_uInt32 nIndex)
B2VectorOrientation getOrientation(const B2DPolygon &rCandidate)
B2DPolygon const & createPolygonFromUnitCircle(sal_uInt32 nStartQuadrant)
create a polygon which describes the unit circle and close it
double getSignedArea(const B2DPolygon &rCandidate)
bool isInside(const B2DPolygon &rCandidate, const B2DPoint &rPoint, bool bWithBorder)
B2DPoint distort(const B2DPoint &rCandidate, const B2DRange &rOriginal, const B2DPoint &rTopLeft, const B2DPoint &rTopRight, const B2DPoint &rBottomLeft, const B2DPoint &rBottomRight)
B2DPolygon adaptiveSubdivideByAngle(const B2DPolygon &rCandidate, double fAngleBound)
B3DPolygon createB3DPolygonFromB2DPolygon(const B2DPolygon &rCandidate, double fZCoordinate)
B2DPolygon createWaveline(const B2DPolygon &rCandidate, double fWaveWidth, double fWaveHeight)
Create Waveline along given polygon The implementation is based on createEdgesOfGivenLength and creat...
void B2DPolygonToUnoPolygonBezierCoords(const B2DPolygon &rPolygon, css::drawing::PointSequence &rPointSequenceRetval, css::drawing::FlagSequence &rFlagSequenceRetval)
B2DPolygon snapPointsOfHorizontalOrVerticalEdges(const B2DPolygon &rCandidate)
snap some polygon coordinates to discrete coordinates
B2DPolygon createPolygonFromCircle(const B2DPoint &rCenter, double fRadius)
Create a circle polygon with given radius.
void closeWithGeometryChange(B2DPolygon &rCandidate)
bool isPointOnPolygon(const B2DPolygon &rCandidate, const B2DPoint &rPoint, bool bWithPoints)
bool expandToCurveInPoint(B2DPolygon &rCandidate, sal_uInt32 nIndex)
bool isInEpsilonRange(const B2DPoint &rEdgeStart, const B2DPoint &rEdgeEnd, const B2DPoint &rTestPosition, double fDistance)
bool isPointOnEdge(const B2DPoint &rPoint, const B2DPoint &rEdgeStart, const B2DVector &rEdgeDelta, double *pCut)
B2DPolygon createB2DPolygonFromB3DPolygon(const B3DPolygon &rCandidate, const B3DHomMatrix &rMat)
bool isRectangle(const B2DPolygon &rPoly)
Predicate whether a given polygon is a rectangle.
static B2DPolygon impCreateUnitCircle(sal_uInt32 nStartQuadrant)
void openWithGeometryChange(B2DPolygon &rCandidate)
sal_uInt32 getIndexOfPredecessor(sal_uInt32 nIndex, const B2DPolygon &rCandidate)
B2DVector getTangentLeavingPoint(const B2DPolygon &rCandidate, sal_uInt32 nIndex)
get the tangent with which the given point is left seen from the following polygon path data.
B2DPolygon getSnippetAbsolute(const B2DPolygon &rCandidate, double fFrom, double fTo, double fLength)
static double impDistanceBezierPointToControl(double fAngle)
B2DPolygon createPolygonFromEllipseSegment(const B2DPoint &rCenter, double fRadiusX, double fRadiusY, double fStart, double fEnd)
Create a unit ellipse polygon with the given angles, from start to end.
bool hasNeutralPoints(const B2DPolygon &rCandidate)
B2DPoint getPositionRelative(const B2DPolygon &rCandidate, double fDistance, double fLength)
void B2DPolygonToUnoPointSequence(const B2DPolygon &rPolygon, css::drawing::PointSequence &rPointSequenceRetval)
B2DVector getTangentEnteringPoint(const B2DPolygon &rCandidate, sal_uInt32 nIndex)
get the tangent with which the given point is entered seen from the previous polygon path data.
double getEdgeLength(const B2DPolygon &rCandidate, sal_uInt32 nIndex)
get length of polygon edge from point nIndex to nIndex + 1
B2DHomMatrix createRotateB2DHomMatrix(double fRadiant)
double getSmallestDistancePointToPolygon(const B2DPolygon &rCandidate, const B2DPoint &rTestPoint, sal_uInt32 &rEdgeIndex, double &rCut)
bool setContinuityInPoint(B2DPolygon &rCandidate, sal_uInt32 nIndex, B2VectorContinuity eContinuity)
B2DPolygon simplifyCurveSegments(const B2DPolygon &rCandidate)
B2DPolygon UnoPolygonBezierCoordsToB2DPolygon(const css::drawing::PointSequence &rPointSequenceSource, const css::drawing::FlagSequence &rFlagSequenceSource)
static void implHandleFirstLast(const std::function< void(const basegfx::B2DPolygon &rSnippet)> &rTargetCallback, B2DPolygon &rFirst, B2DPolygon &rLast)
bool isConvex(const B2DPolygon &rCandidate)
B2DPolygon const & createUnitPolygon()
Create the unit polygon.
B2DPolygon createPolygonFromEllipse(const B2DPoint &rCenter, double fRadiusX, double fRadiusY, sal_uInt32 nStartQuadrant)
Create an ellipse polygon with given radii.
void checkClosed(B2DPolygon &rCandidate)
Check if given polygon is closed.
B2DPolygon createPolygonFromUnitEllipseSegment(double fStart, double fEnd)
B2DPolygon makeStartPoint(const B2DPolygon &rCandidate, sal_uInt32 nIndexOfNewStatPoint)
bool isPointInTriangle(const B2DPoint &rA, const B2DPoint &rB, const B2DPoint &rC, const B2DPoint &rCandidate, bool bWithBorder)
B2DRange getRange(const B2DPolygon &rCandidate)
Get the range of a polygon.
B2VectorContinuity
Descriptor for the mathematical continuity of two 2D Vectors.
Definition: b2enums.hxx:41
@ C1
mathematically negative oriented
@ C2
mathematically neutral, thus parallel
bool areParallel(const B2DVector &rVecA, const B2DVector &rVecB)
Test two vectors which need not to be normalized for parallelism.
Definition: b2dvector.cxx:111
B2VectorOrientation
Descriptor for the mathematical orientations of two 2D Vectors.
Definition: b2enums.hxx:27
@ Positive
mathematically positive oriented
@ Neutral
mathematically neutral, thus parallel
@ Negative
mathematically negative oriented
B2DVector getNormalizedPerpendicular(const B2DVector &rVec)
Calculate a perpendicular 2D Vector to the given one, normalize the given one as preparation.
Definition: b2dvector.cxx:144
B2DVector getPerpendicular(const B2DVector &rNormalizedVec)
Calculate a perpendicular 2D Vector to the given one.
Definition: b2dvector.cxx:138
B2IRange fround(const B2DRange &rRange)
Round double to nearest integer for 2D range.
Definition: b2drange.cxx:64
int i
const wchar_t *typedef int(__stdcall *DllNativeUnregProc)(int
css::drawing::Direction3D aDirection
sal_Int32 nLength