Bug Summary

File:home/maarten/src/libreoffice/core/basegfx/source/polygon/b2dpolygontools.cxx
Warning:line 1258, column 17
Value stored to 'fDotDashLength' is never read

Annotated Source Code

Press '?' to see keyboard shortcuts

clang -cc1 -cc1 -triple x86_64-unknown-linux-gnu -analyze -disable-free -disable-llvm-verifier -discard-value-names -main-file-name b2dpolygontools.cxx -analyzer-store=region -analyzer-opt-analyze-nested-blocks -analyzer-checker=core -analyzer-checker=apiModeling -analyzer-checker=unix -analyzer-checker=deadcode -analyzer-checker=cplusplus -analyzer-checker=security.insecureAPI.UncheckedReturn -analyzer-checker=security.insecureAPI.getpw -analyzer-checker=security.insecureAPI.gets -analyzer-checker=security.insecureAPI.mktemp -analyzer-checker=security.insecureAPI.mkstemp -analyzer-checker=security.insecureAPI.vfork -analyzer-checker=nullability.NullPassedToNonnull -analyzer-checker=nullability.NullReturnedFromNonnull -analyzer-output plist -w -setup-static-analyzer -mrelocation-model pic -pic-level 2 -mframe-pointer=all -fmath-errno -fno-rounding-math -mconstructor-aliases -munwind-tables -target-cpu x86-64 -fno-split-dwarf-inlining -debugger-tuning=gdb -resource-dir /usr/lib64/clang/11.0.0 -D BOOST_ERROR_CODE_HEADER_ONLY -D BOOST_SYSTEM_NO_DEPRECATED -D CPPU_ENV=gcc3 -D LINUX -D OSL_DEBUG_LEVEL=1 -D SAL_LOG_INFO -D SAL_LOG_WARN -D UNIX -D UNX -D X86_64 -D _PTHREADS -D _REENTRANT -D BASEGFX_DLLIMPLEMENTATION -D EXCEPTIONS_ON -D LIBO_INTERNAL_ONLY -I /home/maarten/src/libreoffice/core/basegfx/source/inc -I /home/maarten/src/libreoffice/core/external/boost/include -I /home/maarten/src/libreoffice/core/workdir/UnpackedTarball/boost -I /home/maarten/src/libreoffice/core/include -I /usr/lib/jvm/java-11-openjdk-11.0.9.10-0.0.ea.fc33.x86_64/include -I /usr/lib/jvm/java-11-openjdk-11.0.9.10-0.0.ea.fc33.x86_64/include/linux -I /home/maarten/src/libreoffice/core/config_host -I /home/maarten/src/libreoffice/core/workdir/UnoApiHeadersTarget/udkapi/normal -I /home/maarten/src/libreoffice/core/workdir/UnoApiHeadersTarget/offapi/normal -internal-isystem /usr/bin/../lib/gcc/x86_64-redhat-linux/10/../../../../include/c++/10 -internal-isystem /usr/bin/../lib/gcc/x86_64-redhat-linux/10/../../../../include/c++/10/x86_64-redhat-linux -internal-isystem /usr/bin/../lib/gcc/x86_64-redhat-linux/10/../../../../include/c++/10/backward -internal-isystem /usr/local/include -internal-isystem /usr/lib64/clang/11.0.0/include -internal-externc-isystem /include -internal-externc-isystem /usr/include -O0 -Wno-missing-braces -std=c++17 -fdeprecated-macro -fdebug-compilation-dir /home/maarten/src/libreoffice/core -ferror-limit 19 -fvisibility hidden -fvisibility-inlines-hidden -stack-protector 2 -fgnuc-version=4.2.1 -fcxx-exceptions -fexceptions -debug-info-kind=constructor -analyzer-output=html -faddrsig -o /home/maarten/tmp/wis/scan-build-libreoffice/output/report/2020-10-07-141433-9725-1 -x c++ /home/maarten/src/libreoffice/core/basegfx/source/polygon/b2dpolygontools.cxx
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
22#include <basegfx/numeric/ftools.hxx>
23#include <basegfx/polygon/b2dpolygontools.hxx>
24#include <osl/diagnose.h>
25#include <rtl/math.hxx>
26#include <sal/log.hxx>
27#include <basegfx/polygon/b2dpolygon.hxx>
28#include <basegfx/polygon/b2dpolypolygon.hxx>
29#include <basegfx/range/b2drange.hxx>
30#include <basegfx/curve/b2dcubicbezier.hxx>
31#include <basegfx/point/b3dpoint.hxx>
32#include <basegfx/matrix/b3dhommatrix.hxx>
33#include <basegfx/matrix/b2dhommatrix.hxx>
34#include <basegfx/curve/b2dbeziertools.hxx>
35#include <basegfx/matrix/b2dhommatrixtools.hxx>
36
37// #i37443#
38#define ANGLE_BOUND_START_VALUE(2.25) (2.25)
39#define ANGLE_BOUND_MINIMUM_VALUE(0.1) (0.1)
40#define STEPSPERQUARTER(3) (3)
41
42namespace basegfx::utils
43{
44 void openWithGeometryChange(B2DPolygon& rCandidate)
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
63 void closeWithGeometryChange(B2DPolygon& rCandidate)
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 (!)")do { if (true && (!(nIndex < rCandidate.count())))
{ sal_detail_logFormat((SAL_DETAIL_LOG_LEVEL_WARN), ("legacy.osl"
), ("/home/maarten/src/libreoffice/core/basegfx/source/polygon/b2dpolygontools.cxx"
":" "96" ": "), "%s", "getIndexOfPredecessor: Access to polygon out of range (!)"
); } } while (false)
;
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 (!)")do { if (true && (!(nIndex < rCandidate.count())))
{ sal_detail_logFormat((SAL_DETAIL_LOG_LEVEL_WARN), ("legacy.osl"
), ("/home/maarten/src/libreoffice/core/basegfx/source/polygon/b2dpolygontools.cxx"
":" "114" ": "), "%s", "getIndexOfPredecessor: Access to polygon out of range (!)"
); } } while (false)
;
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
130 B2VectorOrientation getOrientation(const B2DPolygon& rCandidate)
131 {
132 B2VectorOrientation eRetval(B2VectorOrientation::Neutral);
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 {
144 eRetval = B2VectorOrientation::Positive;
145 }
146 else if(fSignedArea < 0.0)
147 {
148 eRetval = B2VectorOrientation::Negative;
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)
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));
187 aBezier.testAndSolveTrivialBezier();
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);
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).
231 closeWithGeometryChange(aRetval);
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(2.25);
267 }
268 else if(fTools::less(fAngleBound, ANGLE_BOUND_MINIMUM_VALUE(0.1)))
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));
280 aBezier.testAndSolveTrivialBezier();
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).
300 closeWithGeometryChange(aRetval);
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 (!)")do { if (true && (!(nIndex < nPointCount))) { sal_detail_logFormat
((SAL_DETAIL_LOG_LEVEL_WARN), ("legacy.osl"), ("/home/maarten/src/libreoffice/core/basegfx/source/polygon/b2dpolygontools.cxx"
":" "447" ": "), "%s", "getEdgeLength: Access to polygon out of range (!)"
); } } while (false)
;
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));
459 aEdge.setControlPointA(rCandidate.getNextControlPoint(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
866 CutFlagValue findCut(
867 const B2DPoint& rEdge1Start, const B2DVector& rEdge1Delta,
868 const B2DPoint& rEdge2Start, const B2DVector& rEdge2Delta,
869 CutFlagValue aCutFlags,
870 double* pCut1, double* pCut2)
871 {
872 CutFlagValue aRetval(CutFlagValue::NONE);
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
879 && (aCutFlags & (CutFlagValue::START1|CutFlagValue::END1))
880 && (aCutFlags & (CutFlagValue::START2|CutFlagValue::END2)))
881 {
882 // same startpoint?
883 if((aCutFlags & (CutFlagValue::START1|CutFlagValue::START2)) == (CutFlagValue::START1|CutFlagValue::START2))
884 {
885 if(rEdge1Start.equal(rEdge2Start))
886 {
887 bFinished = true;
888 aRetval = (CutFlagValue::START1|CutFlagValue::START2);
889 }
890 }
891
892 // same endpoint?
893 if(!bFinished && (aCutFlags & (CutFlagValue::END1|CutFlagValue::END2)) == (CutFlagValue::END1|CutFlagValue::END2))
894 {
895 const B2DPoint aEnd1(rEdge1Start + rEdge1Delta);
896 const B2DPoint aEnd2(rEdge2Start + rEdge2Delta);
897
898 if(aEnd1.equal(aEnd2))
899 {
900 bFinished = true;
901 aRetval = (CutFlagValue::END1|CutFlagValue::END2);
902 fCut1 = fCut2 = 1.0;
903 }
904 }
905
906 // startpoint1 == endpoint2?
907 if(!bFinished && (aCutFlags & (CutFlagValue::START1|CutFlagValue::END2)) == (CutFlagValue::START1|CutFlagValue::END2))
908 {
909 const B2DPoint aEnd2(rEdge2Start + rEdge2Delta);
910
911 if(rEdge1Start.equal(aEnd2))
912 {
913 bFinished = true;
914 aRetval = (CutFlagValue::START1|CutFlagValue::END2);
915 fCut1 = 0.0;
916 fCut2 = 1.0;
917 }
918 }
919
920 // startpoint2 == endpoint1?
921 if(!bFinished&& (aCutFlags & (CutFlagValue::START2|CutFlagValue::END1)) == (CutFlagValue::START2|CutFlagValue::END1))
922 {
923 const B2DPoint aEnd1(rEdge1Start + rEdge1Delta);
924
925 if(rEdge2Start.equal(aEnd1))
926 {
927 bFinished = true;
928 aRetval = (CutFlagValue::START2|CutFlagValue::END1);
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;
943 aRetval = (CutFlagValue::LINE|CutFlagValue::START1);
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;
953 aRetval = (CutFlagValue::LINE|CutFlagValue::START2);
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;
965 aRetval = (CutFlagValue::LINE|CutFlagValue::END1);
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;
977 aRetval = (CutFlagValue::LINE|CutFlagValue::END2);
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
1034 bool isPointOnEdge(
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
1114 void applyLineDashing(
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
1143 applyLineDashing(
1144 rCandidate,
1145 rDotDashArray,
1146 aLineCallback,
1147 aGapCallback,
1148 fDotDashLength);
1149 }
1150
1151 static void implHandleSnippet(
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
1179 static void implHandleFirstLast(
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
1204 void applyLineDashing(
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 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 (!)")(static_cast <bool> (true && "applyLineDashing: potentially too expensive to do the requested dismantle - please consider stretched LineDash pattern (!)"
) ? void (0) : __assert_fail ("true && \"applyLineDashing: potentially too expensive to do the requested dismantle - please consider stretched LineDash pattern (!)\""
, "/home/maarten/src/libreoffice/core/basegfx/source/polygon/b2dpolygontools.cxx"
, 1252, __extension__ __PRETTY_FUNCTION__))
;
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 fDotDashLength *= fFactor;
Value stored to 'fDotDashLength' is never read
1259 }
1260
1261 // prepare current edge's start
1262 B2DCubicBezier aCurrentEdge;
1263 const bool bIsClosed(rCandidate.isClosed());
1264 const sal_uInt32 nEdgeCount(bIsClosed ? nPointCount : nPointCount - 1);
1265 aCurrentEdge.setStartPoint(rCandidate.getB2DPoint(0));
1266
1267 // prepare DotDashArray iteration and the line/gap switching bool
1268 sal_uInt32 nDotDashIndex(0);
1269 bool bIsLine(true);
1270 double fDotDashMovingLength(aDotDashArray[0]);
1271 B2DPolygon aSnippet;
1272
1273 // remember 1st and last snippets to try to merge after execution
1274 // is complete and hand to callback
1275 B2DPolygon aFirstLine, aLastLine;
1276 B2DPolygon aFirstGap, aLastGap;
1277
1278 // iterate over all edges
1279 for(sal_uInt32 a(0); a < nEdgeCount; a++)
1280 {
1281 // update current edge (fill in C1, C2 and end point)
1282 double fLastDotDashMovingLength(0.0);
1283 const sal_uInt32 nNextIndex((a + 1) % nPointCount);
1284 aCurrentEdge.setControlPointA(rCandidate.getNextControlPoint(a));
1285 aCurrentEdge.setControlPointB(rCandidate.getPrevControlPoint(nNextIndex));
1286 aCurrentEdge.setEndPoint(rCandidate.getB2DPoint(nNextIndex));
1287
1288 // check if we have a trivial bezier segment -> possible fallback to edge
1289 aCurrentEdge.testAndSolveTrivialBezier();
1290
1291 if(aCurrentEdge.isBezier())
1292 {
1293 // bezier segment
1294 const B2DCubicBezierHelper aCubicBezierHelper(aCurrentEdge);
1295 const double fEdgeLength(aCubicBezierHelper.getLength());
1296
1297 if(!fTools::equalZero(fEdgeLength))
1298 {
1299 while(fTools::less(fDotDashMovingLength, fEdgeLength))
1300 {
1301 // new split is inside edge, create and append snippet [fLastDotDashMovingLength, fDotDashMovingLength]
1302 const bool bHandleLine(bIsLine && aLineTargetCallback);
1303 const bool bHandleGap(!bIsLine && aGapTargetCallback);
1304
1305 if(bHandleLine || bHandleGap)
1306 {
1307 const double fBezierSplitStart(aCubicBezierHelper.distanceToRelative(fLastDotDashMovingLength));
1308 const double fBezierSplitEnd(aCubicBezierHelper.distanceToRelative(fDotDashMovingLength));
1309 B2DCubicBezier aBezierSnippet(aCurrentEdge.snippet(fBezierSplitStart, fBezierSplitEnd));
1310
1311 if(!aSnippet.count())
1312 {
1313 aSnippet.append(aBezierSnippet.getStartPoint());
1314 }
1315
1316 aSnippet.appendBezierSegment(aBezierSnippet.getControlPointA(), aBezierSnippet.getControlPointB(), aBezierSnippet.getEndPoint());
1317
1318 if(bHandleLine)
1319 {
1320 implHandleSnippet(aSnippet, aLineTargetCallback, aFirstLine, aLastLine);
1321 }
1322
1323 if(bHandleGap)
1324 {
1325 implHandleSnippet(aSnippet, aGapTargetCallback, aFirstGap, aLastGap);
1326 }
1327
1328 aSnippet.clear();
1329 }
1330
1331 // prepare next DotDashArray step and flip line/gap flag
1332 fLastDotDashMovingLength = fDotDashMovingLength;
1333 fDotDashMovingLength += aDotDashArray[(++nDotDashIndex) % nDotDashCount];
1334 bIsLine = !bIsLine;
1335 }
1336
1337 // append closing snippet [fLastDotDashMovingLength, fEdgeLength]
1338 const bool bHandleLine(bIsLine && aLineTargetCallback);
1339 const bool bHandleGap(!bIsLine && aGapTargetCallback);
1340
1341 if(bHandleLine || bHandleGap)
1342 {
1343 B2DCubicBezier aRight;
1344 const double fBezierSplit(aCubicBezierHelper.distanceToRelative(fLastDotDashMovingLength));
1345
1346 aCurrentEdge.split(fBezierSplit, nullptr, &aRight);
1347
1348 if(!aSnippet.count())
1349 {
1350 aSnippet.append(aRight.getStartPoint());
1351 }
1352
1353 aSnippet.appendBezierSegment(aRight.getControlPointA(), aRight.getControlPointB(), aRight.getEndPoint());
1354 }
1355
1356 // prepare move to next edge
1357 fDotDashMovingLength -= fEdgeLength;
1358 }
1359 }
1360 else
1361 {
1362 // simple edge
1363 const double fEdgeLength(aCurrentEdge.getEdgeLength());
1364
1365 if(!fTools::equalZero(fEdgeLength))
1366 {
1367 while(fTools::less(fDotDashMovingLength, fEdgeLength))
1368 {
1369 // new split is inside edge, create and append snippet [fLastDotDashMovingLength, fDotDashMovingLength]
1370 const bool bHandleLine(bIsLine && aLineTargetCallback);
1371 const bool bHandleGap(!bIsLine && aGapTargetCallback);
1372
1373 if(bHandleLine || bHandleGap)
1374 {
1375 if(!aSnippet.count())
1376 {
1377 aSnippet.append(interpolate(aCurrentEdge.getStartPoint(), aCurrentEdge.getEndPoint(), fLastDotDashMovingLength / fEdgeLength));
1378 }
1379
1380 aSnippet.append(interpolate(aCurrentEdge.getStartPoint(), aCurrentEdge.getEndPoint(), fDotDashMovingLength / fEdgeLength));
1381
1382 if(bHandleLine)
1383 {
1384 implHandleSnippet(aSnippet, aLineTargetCallback, aFirstLine, aLastLine);
1385 }
1386
1387 if(bHandleGap)
1388 {
1389 implHandleSnippet(aSnippet, aGapTargetCallback, aFirstGap, aLastGap);
1390 }
1391
1392 aSnippet.clear();
1393 }
1394
1395 // prepare next DotDashArray step and flip line/gap flag
1396 fLastDotDashMovingLength = fDotDashMovingLength;
1397 fDotDashMovingLength += aDotDashArray[(++nDotDashIndex) % nDotDashCount];
1398 bIsLine = !bIsLine;
1399 }
1400
1401 // append snippet [fLastDotDashMovingLength, fEdgeLength]
1402 const bool bHandleLine(bIsLine && aLineTargetCallback);
1403 const bool bHandleGap(!bIsLine && aGapTargetCallback);
1404
1405 if(bHandleLine || bHandleGap)
1406 {
1407 if(!aSnippet.count())
1408 {
1409 aSnippet.append(interpolate(aCurrentEdge.getStartPoint(), aCurrentEdge.getEndPoint(), fLastDotDashMovingLength / fEdgeLength));
1410 }
1411
1412 aSnippet.append(aCurrentEdge.getEndPoint());
1413 }
1414
1415 // prepare move to next edge
1416 fDotDashMovingLength -= fEdgeLength;
1417 }
1418 }
1419
1420 // prepare next edge step (end point gets new start point)
1421 aCurrentEdge.setStartPoint(aCurrentEdge.getEndPoint());
1422 }
1423
1424 // append last intermediate results (if exists)
1425 if(aSnippet.count())
1426 {
1427 const bool bHandleLine(bIsLine && aLineTargetCallback);
1428 const bool bHandleGap(!bIsLine && aGapTargetCallback);
1429
1430 if(bHandleLine)
1431 {
1432 implHandleSnippet(aSnippet, aLineTargetCallback, aFirstLine, aLastLine);
1433 }
1434
1435 if(bHandleGap)
1436 {
1437 implHandleSnippet(aSnippet, aGapTargetCallback, aFirstGap, aLastGap);
1438 }
1439 }
1440
1441 if(bIsClosed && aLineTargetCallback)
1442 {
1443 implHandleFirstLast(aLineTargetCallback, aFirstLine, aLastLine);
1444 }
1445
1446 if(bIsClosed && aGapTargetCallback)
1447 {
1448 implHandleFirstLast(aGapTargetCallback, aFirstGap, aLastGap);
1449 }
1450 }
1451
1452 // test if point is inside epsilon-range around an edge defined
1453 // by the two given points. Can be used for HitTesting. The epsilon-range
1454 // is defined to be the rectangle centered to the given edge, using height
1455 // 2 x fDistance, and the circle around both points with radius fDistance.
1456 bool isInEpsilonRange(const B2DPoint& rEdgeStart, const B2DPoint& rEdgeEnd, const B2DPoint& rTestPosition, double fDistance)
1457 {
1458 // build edge vector
1459 const B2DVector aEdge(rEdgeEnd - rEdgeStart);
1460 bool bDoDistanceTestStart(false);
1461 bool bDoDistanceTestEnd(false);
1462
1463 if(aEdge.equalZero())
1464 {
1465 // no edge, just a point. Do one of the distance tests.
1466 bDoDistanceTestStart = true;
1467 }
1468 else
1469 {
1470 // edge has a length. Create perpendicular vector.
1471 const B2DVector aPerpend(getPerpendicular(aEdge));
1472 double fCut(
1473 (aPerpend.getY() * (rTestPosition.getX() - rEdgeStart.getX())
1474 + aPerpend.getX() * (rEdgeStart.getY() - rTestPosition.getY())) /
1475 (aEdge.getX() * aEdge.getX() + aEdge.getY() * aEdge.getY()));
1476 const double fZero(0.0);
1477 const double fOne(1.0);
1478
1479 if(fTools::less(fCut, fZero))
1480 {
1481 // left of rEdgeStart
1482 bDoDistanceTestStart = true;
1483 }
1484 else if(fTools::more(fCut, fOne))
1485 {
1486 // right of rEdgeEnd
1487 bDoDistanceTestEnd = true;
1488 }
1489 else
1490 {
1491 // inside line [0.0 .. 1.0]
1492 const B2DPoint aCutPoint(interpolate(rEdgeStart, rEdgeEnd, fCut));
1493 const B2DVector aDelta(rTestPosition - aCutPoint);
1494 const double fDistanceSquare(aDelta.scalar(aDelta));
1495
1496 return fDistanceSquare <= fDistance * fDistance;
1497 }
1498 }
1499
1500 if(bDoDistanceTestStart)
1501 {
1502 const B2DVector aDelta(rTestPosition - rEdgeStart);
1503 const double fDistanceSquare(aDelta.scalar(aDelta));
1504
1505 if(fDistanceSquare <= fDistance * fDistance)
1506 {
1507 return true;
1508 }
1509 }
1510 else if(bDoDistanceTestEnd)
1511 {
1512 const B2DVector aDelta(rTestPosition - rEdgeEnd);
1513 const double fDistanceSquare(aDelta.scalar(aDelta));
1514
1515 if(fDistanceSquare <= fDistance * fDistance)
1516 {
1517 return true;
1518 }
1519 }
1520
1521 return false;
1522 }
1523
1524 // test if point is inside epsilon-range around the given Polygon. Can be used
1525 // for HitTesting. The epsilon-range is defined to be the tube around the polygon
1526 // with distance fDistance and rounded edges (start and end point).
1527 bool isInEpsilonRange(const B2DPolygon& rCandidate, const B2DPoint& rTestPosition, double fDistance)
1528 {
1529 // force to non-bezier polygon
1530 const B2DPolygon& aCandidate(rCandidate.getDefaultAdaptiveSubdivision());
1531 const sal_uInt32 nPointCount(aCandidate.count());
1532
1533 if(nPointCount)
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
1566 return false;
1567 }
1568
1569 // Calculates distance of curve point to its control point for a Bézier curve, that
1570 // approximates a unit circle arc. fAngle is the center angle of the circle arc. The
1571 // constrain 0<=fAngle<=pi/2 must not be violated to give a useful accuracy. For details
1572 // and alternatives read document "ApproxCircleInfo.odt", attachment of bug tdf#121425.
1573 static double impDistanceBezierPointToControl(double fAngle)
1574 {
1575 SAL_WARN_IF(fAngle < 0 || fAngle > F_PI2,"basegfx","angle not suitable for approximate circle")do { if (true && (fAngle < 0 || fAngle > 1.57079632679489661923
)) { switch (sal_detail_log_report(::SAL_DETAIL_LOG_LEVEL_WARN
, "basegfx")) { case SAL_DETAIL_LOG_ACTION_IGNORE: break; case
SAL_DETAIL_LOG_ACTION_LOG: if (sizeof ::sal::detail::getResult
( ::sal::detail::StreamStart() << "angle not suitable for approximate circle"
) == 1) { ::sal_detail_log( (::SAL_DETAIL_LOG_LEVEL_WARN), ("basegfx"
), ("/home/maarten/src/libreoffice/core/basegfx/source/polygon/b2dpolygontools.cxx"
":" "1575" ": "), ::sal::detail::unwrapStream( ::sal::detail
::StreamStart() << "angle not suitable for approximate circle"
), 0); } else { ::std::ostringstream sal_detail_stream; sal_detail_stream
<< "angle not suitable for approximate circle"; ::sal::
detail::log( (::SAL_DETAIL_LOG_LEVEL_WARN), ("basegfx"), ("/home/maarten/src/libreoffice/core/basegfx/source/polygon/b2dpolygontools.cxx"
":" "1575" ": "), sal_detail_stream, 0); }; break; case SAL_DETAIL_LOG_ACTION_FATAL
: if (sizeof ::sal::detail::getResult( ::sal::detail::StreamStart
() << "angle not suitable for approximate circle") == 1
) { ::sal_detail_log( (::SAL_DETAIL_LOG_LEVEL_WARN), ("basegfx"
), ("/home/maarten/src/libreoffice/core/basegfx/source/polygon/b2dpolygontools.cxx"
":" "1575" ": "), ::sal::detail::unwrapStream( ::sal::detail
::StreamStart() << "angle not suitable for approximate circle"
), 0); } else { ::std::ostringstream sal_detail_stream; sal_detail_stream
<< "angle not suitable for approximate circle"; ::sal::
detail::log( (::SAL_DETAIL_LOG_LEVEL_WARN), ("basegfx"), ("/home/maarten/src/libreoffice/core/basegfx/source/polygon/b2dpolygontools.cxx"
":" "1575" ": "), sal_detail_stream, 0); }; std::abort(); break
; } } } while (false)
;
1576 if (0 <= fAngle && fAngle <= F_PI21.57079632679489661923)
1577 {
1578 return 4.0/3.0 * ( tan(fAngle/4.0));
1579 }
1580 else
1581 return 0;
1582 }
1583
1584 B2DPolygon createPolygonFromRect( const B2DRectangle& rRect, double fRadiusX, double fRadiusY )
1585 {
1586 const double fZero(0.0);
1587 const double fOne(1.0);
1588
1589 fRadiusX = std::clamp(fRadiusX, 0.0, 1.0);
1590 fRadiusY = std::clamp(fRadiusY, 0.0, 1.0);
1591
1592 if(rtl::math::approxEqual(fZero, fRadiusX) || rtl::math::approxEqual(fZero, fRadiusY))
1593 {
1594 // at least in one direction no radius, use rectangle.
1595 // Do not use createPolygonFromRect() here since original
1596 // creator (historical reasons) still creates a start point at the
1597 // bottom center, so do the same here to get the same line patterns.
1598 // Due to this the order of points is different, too.
1599 const B2DPoint aBottomCenter(rRect.getCenter().getX(), rRect.getMaxY());
1600 B2DPolygon aPolygon {
1601 aBottomCenter,
1602 { rRect.getMinX(), rRect.getMaxY() },
1603 { rRect.getMinX(), rRect.getMinY() },
1604 { rRect.getMaxX(), rRect.getMinY() },
1605 { rRect.getMaxX(), rRect.getMaxY() }
1606 };
1607
1608 // close
1609 aPolygon.setClosed( true );
1610
1611 return aPolygon;
1612 }
1613 else if(rtl::math::approxEqual(fOne, fRadiusX) && rtl::math::approxEqual(fOne, fRadiusY))
1614 {
1615 // in both directions full radius, use ellipse
1616 const B2DPoint aCenter(rRect.getCenter());
1617 const double fRectRadiusX(rRect.getWidth() / 2.0);
1618 const double fRectRadiusY(rRect.getHeight() / 2.0);
1619
1620 return createPolygonFromEllipse( aCenter, fRectRadiusX, fRectRadiusY );
1621 }
1622 else
1623 {
1624 B2DPolygon aRetval;
1625 const double fBowX((rRect.getWidth() / 2.0) * fRadiusX);
1626 const double fBowY((rRect.getHeight() / 2.0) * fRadiusY);
1627 const double fKappa(impDistanceBezierPointToControl(F_PI21.57079632679489661923));
1628
1629 // create start point at bottom center
1630 if(!rtl::math::approxEqual(fOne, fRadiusX))
1631 {
1632 const B2DPoint aBottomCenter(rRect.getCenter().getX(), rRect.getMaxY());
1633 aRetval.append(aBottomCenter);
1634 }
1635
1636 // create first bow
1637 {
1638 const B2DPoint aBottomRight(rRect.getMaxX(), rRect.getMaxY());
1639 const B2DPoint aStart(aBottomRight + B2DPoint(-fBowX, 0.0));
1640 const B2DPoint aStop(aBottomRight + B2DPoint(0.0, -fBowY));
1641 aRetval.append(aStart);
1642 aRetval.appendBezierSegment(interpolate(aStart, aBottomRight, fKappa), interpolate(aStop, aBottomRight, fKappa), aStop);
1643 }
1644
1645 // create second bow
1646 {
1647 const B2DPoint aTopRight(rRect.getMaxX(), rRect.getMinY());
1648 const B2DPoint aStart(aTopRight + B2DPoint(0.0, fBowY));
1649 const B2DPoint aStop(aTopRight + B2DPoint(-fBowX, 0.0));
1650 aRetval.append(aStart);
1651 aRetval.appendBezierSegment(interpolate(aStart, aTopRight, fKappa), interpolate(aStop, aTopRight, fKappa), aStop);
1652 }
1653
1654 // create third bow
1655 {
1656 const B2DPoint aTopLeft(rRect.getMinX(), rRect.getMinY());
1657 const B2DPoint aStart(aTopLeft + B2DPoint(fBowX, 0.0));
1658 const B2DPoint aStop(aTopLeft + B2DPoint(0.0, fBowY));
1659 aRetval.append(aStart);
1660 aRetval.appendBezierSegment(interpolate(aStart, aTopLeft, fKappa), interpolate(aStop, aTopLeft, fKappa), aStop);
1661 }
1662
1663 // create forth bow
1664 {
1665 const B2DPoint aBottomLeft(rRect.getMinX(), rRect.getMaxY());
1666 const B2DPoint aStart(aBottomLeft + B2DPoint(0.0, -fBowY));
1667 const B2DPoint aStop(aBottomLeft + B2DPoint(fBowX, 0.0));
1668 aRetval.append(aStart);
1669 aRetval.appendBezierSegment(interpolate(aStart, aBottomLeft, fKappa), interpolate(aStop, aBottomLeft, fKappa), aStop);
1670 }
1671
1672 // close
1673 aRetval.setClosed( true );
1674
1675 // remove double created points if there are extreme radii involved
1676 if(rtl::math::approxEqual(fOne, fRadiusX) || rtl::math::approxEqual(fOne, fRadiusY))
1677 {
1678 aRetval.removeDoublePoints();
1679 }
1680
1681 return aRetval;
1682 }
1683 }
1684
1685 B2DPolygon createPolygonFromRect( const B2DRectangle& rRect )
1686 {
1687 B2DPolygon aPolygon {
1688 { rRect.getMinX(), rRect.getMinY() },
1689 { rRect.getMaxX(), rRect.getMinY() },
1690 { rRect.getMaxX(), rRect.getMaxY() },
1691 { rRect.getMinX(), rRect.getMaxY() }
1692 };
1693
1694 // close
1695 aPolygon.setClosed( true );
1696
1697 return aPolygon;
1698 }
1699
1700 B2DPolygon const & createUnitPolygon()
1701 {
1702 static auto const singleton = [] {
1703 B2DPolygon aPolygon {
1704 { 0.0, 0.0 },
1705 { 1.0, 0.0 },
1706 { 1.0, 1.0 },
1707 { 0.0, 1.0 }
1708 };
1709
1710 // close
1711 aPolygon.setClosed( true );
1712
1713 return aPolygon;
1714 }();
1715 return singleton;
1716 }
1717
1718 B2DPolygon createPolygonFromCircle( const B2DPoint& rCenter, double fRadius )
1719 {
1720 return createPolygonFromEllipse( rCenter, fRadius, fRadius );
1721 }
1722
1723 static B2DPolygon impCreateUnitCircle(sal_uInt32 nStartQuadrant)
1724 {
1725 B2DPolygon aUnitCircle;
1726 const double fSegmentKappa = impDistanceBezierPointToControl(F_PI21.57079632679489661923 / STEPSPERQUARTER(3));
1727 const B2DHomMatrix aRotateMatrix(createRotateB2DHomMatrix(F_PI21.57079632679489661923 / STEPSPERQUARTER(3)));
1728
1729 B2DPoint aPoint(1.0, 0.0);
1730 B2DPoint aForward(1.0, fSegmentKappa);
1731 B2DPoint aBackward(1.0, -fSegmentKappa);
1732
1733 if(nStartQuadrant != 0)
1734 {
1735 const B2DHomMatrix aQuadrantMatrix(createRotateB2DHomMatrix(F_PI21.57079632679489661923 * (nStartQuadrant % 4)));
1736 aPoint *= aQuadrantMatrix;
1737 aBackward *= aQuadrantMatrix;
1738 aForward *= aQuadrantMatrix;
1739 }
1740
1741 aUnitCircle.append(aPoint);
1742
1743 for(sal_uInt32 a(0); a < STEPSPERQUARTER(3) * 4; a++)
1744 {
1745 aPoint *= aRotateMatrix;
1746 aBackward *= aRotateMatrix;
1747 aUnitCircle.appendBezierSegment(aForward, aBackward, aPoint);
1748 aForward *= aRotateMatrix;
1749 }
1750
1751 aUnitCircle.setClosed(true);
1752 aUnitCircle.removeDoublePoints();
1753
1754 return aUnitCircle;
1755 }
1756
1757 B2DPolygon const & createHalfUnitCircle()
1758 {
1759 static auto const singleton = [] {
1760 B2DPolygon aUnitHalfCircle;
1761 const double fSegmentKappa(impDistanceBezierPointToControl(F_PI21.57079632679489661923 / STEPSPERQUARTER(3)));
1762 const B2DHomMatrix aRotateMatrix(createRotateB2DHomMatrix(F_PI21.57079632679489661923 / STEPSPERQUARTER(3)));
1763 B2DPoint aPoint(1.0, 0.0);
1764 B2DPoint aForward(1.0, fSegmentKappa);
1765 B2DPoint aBackward(1.0, -fSegmentKappa);
1766
1767 aUnitHalfCircle.append(aPoint);
1768
1769 for(sal_uInt32 a(0); a < STEPSPERQUARTER(3) * 2; a++)
1770 {
1771 aPoint *= aRotateMatrix;
1772 aBackward *= aRotateMatrix;
1773 aUnitHalfCircle.appendBezierSegment(aForward, aBackward, aPoint);
1774 aForward *= aRotateMatrix;
1775 }
1776 return aUnitHalfCircle;
1777 }();
1778 return singleton;
1779 }
1780
1781 B2DPolygon const & createPolygonFromUnitCircle(sal_uInt32 nStartQuadrant)
1782 {
1783 switch(nStartQuadrant % 4)
1784 {
1785 case 1 :
1786 {
1787 static auto const singleton = impCreateUnitCircle(1);
1788 return singleton;
1789 }
1790
1791 case 2 :
1792 {
1793 static auto const singleton = impCreateUnitCircle(2);
1794 return singleton;
1795 }
1796
1797 case 3 :
1798 {
1799 static auto const singleton = impCreateUnitCircle(3);
1800 return singleton;
1801 }
1802
1803 default : // case 0 :
1804 {
1805 static auto const singleton = impCreateUnitCircle(0);
1806 return singleton;
1807 }
1808 }
1809 }
1810
1811 B2DPolygon createPolygonFromEllipse( const B2DPoint& rCenter, double fRadiusX, double fRadiusY, sal_uInt32 nStartQuadrant)
1812 {
1813 B2DPolygon aRetval(createPolygonFromUnitCircle(nStartQuadrant));
1814 const B2DHomMatrix aMatrix(createScaleTranslateB2DHomMatrix(fRadiusX, fRadiusY, rCenter.getX(), rCenter.getY()));
1815
1816 aRetval.transform(aMatrix);
1817
1818 return aRetval;
1819 }
1820
1821 B2DPolygon createPolygonFromUnitEllipseSegment( double fStart, double fEnd )
1822 {
1823 B2DPolygon aRetval;
1824
1825 // truncate fStart, fEnd to a range of [0.0 .. F_2PI[ where F_2PI
1826 // falls back to 0.0 to ensure a unique definition
1827 if(fTools::less(fStart, 0.0))
1828 {
1829 fStart = 0.0;
1830 }
1831
1832 if(fTools::moreOrEqual(fStart, F_2PI(2.0*3.14159265358979323846)))
1833 {
1834 fStart = 0.0;
1835 }
1836
1837 if(fTools::less(fEnd, 0.0))
1838 {
1839 fEnd = 0.0;
1840 }
1841
1842 if(fTools::moreOrEqual(fEnd, F_2PI(2.0*3.14159265358979323846)))
1843 {
1844 fEnd = 0.0;
1845 }
1846
1847 if(fTools::equal(fStart, fEnd))
1848 {
1849 // same start and end angle, add single point
1850 aRetval.append(B2DPoint(cos(fStart), sin(fStart)));
1851 }
1852 else
1853 {
1854 const sal_uInt32 nSegments(STEPSPERQUARTER(3) * 4);
1855 const double fAnglePerSegment(F_PI21.57079632679489661923 / STEPSPERQUARTER(3));
1856 const sal_uInt32 nStartSegment(sal_uInt32(fStart / fAnglePerSegment) % nSegments);
1857 const sal_uInt32 nEndSegment(sal_uInt32(fEnd / fAnglePerSegment) % nSegments);
1858 const double fSegmentKappa(impDistanceBezierPointToControl(fAnglePerSegment));
1859
1860 B2DPoint aSegStart(cos(fStart), sin(fStart));
1861 aRetval.append(aSegStart);
1862
1863 if(nStartSegment == nEndSegment && fTools::more(fEnd, fStart))
1864 {
1865 // start and end in one sector and in the right order, create in one segment
1866 const B2DPoint aSegEnd(cos(fEnd), sin(fEnd));
1867 const double fFactor(impDistanceBezierPointToControl(fEnd - fStart));
1868
1869 aRetval.appendBezierSegment(
1870 aSegStart + (B2DPoint(-aSegStart.getY(), aSegStart.getX()) * fFactor),
1871 aSegEnd - (B2DPoint(-aSegEnd.getY(), aSegEnd.getX()) * fFactor),
1872 aSegEnd);
1873 }
1874 else
1875 {
1876 double fSegEndRad((nStartSegment + 1) * fAnglePerSegment);
1877 double fFactor(impDistanceBezierPointToControl(fSegEndRad - fStart));
1878 B2DPoint aSegEnd(cos(fSegEndRad), sin(fSegEndRad));
1879
1880 aRetval.appendBezierSegment(
1881 aSegStart + (B2DPoint(-aSegStart.getY(), aSegStart.getX()) * fFactor),
1882 aSegEnd - (B2DPoint(-aSegEnd.getY(), aSegEnd.getX()) * fFactor),
1883 aSegEnd);
1884
1885 sal_uInt32 nSegment((nStartSegment + 1) % nSegments);
1886 aSegStart = aSegEnd;
1887
1888 while(nSegment != nEndSegment)
1889 {
1890 // No end in this sector, add full sector.
1891 fSegEndRad = (nSegment + 1) * fAnglePerSegment;
1892 aSegEnd = B2DPoint(cos(fSegEndRad), sin(fSegEndRad));
1893
1894 aRetval.appendBezierSegment(
1895 aSegStart + (B2DPoint(-aSegStart.getY(), aSegStart.getX()) * fSegmentKappa),
1896 aSegEnd - (B2DPoint(-aSegEnd.getY(), aSegEnd.getX()) * fSegmentKappa),
1897 aSegEnd);
1898
1899 nSegment = (nSegment + 1) % nSegments;
1900 aSegStart = aSegEnd;
1901 }
1902
1903 // End in this sector
1904 const double fSegStartRad(nSegment * fAnglePerSegment);
1905 fFactor= impDistanceBezierPointToControl(fEnd - fSegStartRad);
1906 aSegEnd = B2DPoint(cos(fEnd), sin(fEnd));
1907
1908 aRetval.appendBezierSegment(
1909 aSegStart + (B2DPoint(-aSegStart.getY(), aSegStart.getX()) * fFactor),
1910 aSegEnd - (B2DPoint(-aSegEnd.getY(), aSegEnd.getX()) * fFactor),
1911 aSegEnd);
1912 }
1913 }
1914
1915 // remove double points between segments created by segmented creation
1916 aRetval.removeDoublePoints();
1917
1918 return aRetval;
1919 }
1920
1921 B2DPolygon createPolygonFromEllipseSegment( const B2DPoint& rCenter, double fRadiusX, double fRadiusY, double fStart, double fEnd )
1922 {
1923 B2DPolygon aRetval(createPolygonFromUnitEllipseSegment(fStart, fEnd));
1924 const B2DHomMatrix aMatrix(createScaleTranslateB2DHomMatrix(fRadiusX, fRadiusY, rCenter.getX(), rCenter.getY()));
1925
1926 aRetval.transform(aMatrix);
1927
1928 return aRetval;
1929 }
1930
1931 bool hasNeutralPoints(const B2DPolygon& rCandidate)
1932 {
1933 OSL_ENSURE(!rCandidate.areControlPointsUsed(), "hasNeutralPoints: ATM works not for curves (!)")do { if (true && (!(!rCandidate.areControlPointsUsed(
)))) { sal_detail_logFormat((SAL_DETAIL_LOG_LEVEL_WARN), ("legacy.osl"
), ("/home/maarten/src/libreoffice/core/basegfx/source/polygon/b2dpolygontools.cxx"
":" "1933" ": "), "%s", "hasNeutralPoints: ATM works not for curves (!)"
); } } while (false)
;
1934 const sal_uInt32 nPointCount(rCandidate.count());
1935
1936 if(nPointCount > 2)
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
1962 return false;
1963 }
1964
1965 B2DPolygon removeNeutralPoints(const B2DPolygon& rCandidate)
1966 {
1967 if(hasNeutralPoints(rCandidate))
1968 {
1969 const sal_uInt32 nPointCount(rCandidate.count());
1970 B2DPolygon aRetval;
1971 B2DPoint aPrevPoint(rCandidate.getB2DPoint(nPointCount - 1));
1972 B2DPoint aCurrPoint(rCandidate.getB2DPoint(0));
1973
1974 for(sal_uInt32 a(0); a < nPointCount; a++)
1975 {
1976 const B2DPoint aNextPoint(rCandidate.getB2DPoint((a + 1) % nPointCount));
1977 const B2DVector aPrevVec(aPrevPoint - aCurrPoint);
1978 const B2DVector aNextVec(aNextPoint - aCurrPoint);
1979 const B2VectorOrientation aOrientation(getOrientation(aNextVec, aPrevVec));
1980
1981 if(aOrientation == B2VectorOrientation::Neutral)
1982 {
1983 // current has neutral orientation, leave it out and prepare next
1984 aCurrPoint = aNextPoint;
1985 }
1986 else
1987 {
1988 // add current point
1989 aRetval.append(aCurrPoint);
1990
1991 // prepare next
1992 aPrevPoint = aCurrPoint;
1993 aCurrPoint = aNextPoint;
1994 }
1995 }
1996
1997 while(aRetval.count() && getOrientationForIndex(aRetval, 0) == B2VectorOrientation::Neutral)
1998 {
1999 aRetval.remove(0);
2000 }
2001
2002 // copy closed state
2003 aRetval.setClosed(rCandidate.isClosed());
2004
2005 return aRetval;
2006 }
2007 else
2008 {
2009 return rCandidate;
2010 }
2011 }
2012
2013 bool isConvex(const B2DPolygon& rCandidate)
2014 {
2015 OSL_ENSURE(!rCandidate.areControlPointsUsed(), "isConvex: ATM works not for curves (!)")do { if (true && (!(!rCandidate.areControlPointsUsed(
)))) { sal_detail_logFormat((SAL_DETAIL_LOG_LEVEL_WARN), ("legacy.osl"
), ("/home/maarten/src/libreoffice/core/basegfx/source/polygon/b2dpolygontools.cxx"
":" "2015" ": "), "%s", "isConvex: ATM works not for curves (!)"
); } } while (false)
;
2016 const sal_uInt32 nPointCount(rCandidate.count());
2017
2018 if(nPointCount > 2)
2019 {
2020 const B2DPoint aPrevPoint(rCandidate.getB2DPoint(nPointCount - 1));
2021 B2DPoint aCurrPoint(rCandidate.getB2DPoint(0));
2022 B2DVector aCurrVec(aPrevPoint - aCurrPoint);
2023 B2VectorOrientation aOrientation(B2VectorOrientation::Neutral);
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
2051 return true;
2052 }
2053
2054 B2VectorOrientation getOrientationForIndex(const B2DPolygon& rCandidate, sal_uInt32 nIndex)
2055 {
2056 OSL_ENSURE(nIndex < rCandidate.count(), "getOrientationForIndex: index out of range (!)")do { if (true && (!(nIndex < rCandidate.count())))
{ sal_detail_logFormat((SAL_DETAIL_LOG_LEVEL_WARN), ("legacy.osl"
), ("/home/maarten/src/libreoffice/core/basegfx/source/polygon/b2dpolygontools.cxx"
":" "2056" ": "), "%s", "getOrientationForIndex: index out of range (!)"
); } } while (false)
;
2057 const B2DPoint aPrev(rCandidate.getB2DPoint(getIndexOfPredecessor(nIndex, rCandidate)));
2058 const B2DPoint aCurr(rCandidate.getB2DPoint(nIndex));
2059 const B2DPoint aNext(rCandidate.getB2DPoint(getIndexOfSuccessor(nIndex, rCandidate)));
2060 const B2DVector aBack(aPrev - aCurr);
2061 const B2DVector aForw(aNext - aCurr);
2062
2063 return getOrientation(aForw, aBack);
2064 }
2065
2066 bool isPointOnLine(const B2DPoint& rStart, const B2DPoint& rEnd, const B2DPoint& rCandidate, bool bWithPoints)
2067 {
2068 if(rCandidate.equal(rStart) || rCandidate.equal(rEnd))
2069 {
2070 // candidate is in epsilon around start or end -> inside
2071 return bWithPoints;
2072 }
2073 else if(rStart.equal(rEnd))
2074 {
2075 // start and end are equal, but candidate is outside their epsilon -> outside
2076 return false;
2077 }
2078 else
2079 {
2080 const B2DVector aEdgeVector(rEnd - rStart);
2081 const B2DVector aTestVector(rCandidate - rStart);
2082
2083 if(areParallel(aEdgeVector, aTestVector))
2084 {
2085 const double fZero(0.0);
2086 const double fOne(1.0);
2087 const double fParamTestOnCurr(fabs(aEdgeVector.getX()) > fabs(aEdgeVector.getY())
2088 ? aTestVector.getX() / aEdgeVector.getX()
2089 : aTestVector.getY() / aEdgeVector.getY());
2090
2091 if(fTools::more(fParamTestOnCurr, fZero) && fTools::less(fParamTestOnCurr, fOne))
2092 {
2093 return true;
2094 }
2095 }
2096
2097 return false;
2098 }
2099 }
2100
2101 bool isPointOnPolygon(const B2DPolygon& rCandidate, const B2DPoint& rPoint, bool bWithPoints)
2102 {
2103 const B2DPolygon aCandidate(rCandidate.areControlPointsUsed() ? rCandidate.getDefaultAdaptiveSubdivision() : rCandidate);
2104 const sal_uInt32 nPointCount(aCandidate.count());
2105
2106 if(nPointCount > 1)
2107 {
2108 const sal_uInt32 nLoopCount(aCandidate.isClosed() ? nPointCount : nPointCount - 1);
2109 B2DPoint aCurrentPoint(aCandidate.getB2DPoint(0));
2110
2111 for(sal_uInt32 a(0); a < nLoopCount; a++)
2112 {
2113 const B2DPoint aNextPoint(aCandidate.getB2DPoint((a + 1) % nPointCount));
2114
2115 if(isPointOnLine(aCurrentPoint, aNextPoint, rPoint, bWithPoints))
2116 {
2117 return true;
2118 }
2119
2120 aCurrentPoint = aNextPoint;
2121 }
2122 }
2123 else if(nPointCount && bWithPoints)
2124 {
2125 return rPoint.equal(aCandidate.getB2DPoint(0));
2126 }
2127
2128 return false;
2129 }
2130
2131 bool isPointInTriangle(const B2DPoint& rA, const B2DPoint& rB, const B2DPoint& rC, const B2DPoint& rCandidate, bool bWithBorder)
2132 {
2133 if(arePointsOnSameSideOfLine(rA, rB, rC, rCandidate, bWithBorder))
2134 {
2135 if(arePointsOnSameSideOfLine(rB, rC, rA, rCandidate, bWithBorder))
2136 {
2137 if(arePointsOnSameSideOfLine(rC, rA, rB, rCandidate, bWithBorder))
2138 {
2139 return true;
2140 }
2141 }
2142 }
2143
2144 return false;
2145 }
2146
2147 bool arePointsOnSameSideOfLine(const B2DPoint& rStart, const B2DPoint& rEnd, const B2DPoint& rCandidateA, const B2DPoint& rCandidateB, bool bWithLine)
2148 {
2149 const B2DVector aLineVector(rEnd - rStart);
2150 const B2DVector aVectorToA(rEnd - rCandidateA);
2151 const double fCrossA(aLineVector.cross(aVectorToA));
2152
2153 // tdf#88352 increase numerical correctness and use rtl::math::approxEqual
2154 // instead of fTools::equalZero which compares with a fixed small value
2155 if(fCrossA == 0.0)
2156 {
2157 // one point on the line
2158 return bWithLine;
2159 }
2160
2161 const B2DVector aVectorToB(rEnd - rCandidateB);
2162 const double fCrossB(aLineVector.cross(aVectorToB));
2163
2164 // increase numerical correctness
2165 if(fCrossB == 0.0)
2166 {
2167 // one point on the line
2168 return bWithLine;
2169 }
2170
2171 // return true if they both have the same sign
2172 return ((fCrossA > 0.0) == (fCrossB > 0.0));
2173 }
2174
2175 void addTriangleFan(
2176 const B2DPolygon& rCandidate,
2177 triangulator::B2DTriangleVector& rTarget)
2178 {
2179 const sal_uInt32 nCount(rCandidate.count());
2180
2181 if(nCount <= 2)
2182 return;
2183
2184 const B2DPoint aStart(rCandidate.getB2DPoint(0));
2185 B2DPoint aLast(rCandidate.getB2DPoint(1));
2186
2187 for(sal_uInt32 a(2); a < nCount; a++)
2188 {
2189 const B2DPoint aCurrent(rCandidate.getB2DPoint(a));
2190 rTarget.emplace_back(
2191 aStart,
2192 aLast,
2193 aCurrent);
2194
2195 // prepare next
2196 aLast = aCurrent;
2197 }
2198 }
2199
2200 namespace
2201 {
2202 /// return 0 for input of 0, -1 for negative and 1 for positive input
2203 int lcl_sgn( const double n )
2204 {
2205 return n == 0.0 ? 0 : 1 - 2*int(std::signbit(n));
2206 }
2207 }
2208
2209 bool isRectangle( const B2DPolygon& rPoly )
2210 {
2211 // polygon must be closed to resemble a rect, and contain
2212 // at least four points.
2213 if( !rPoly.isClosed() ||
2214 rPoly.count() < 4 ||
2215 rPoly.areControlPointsUsed() )
2216 {
2217 return false;
2218 }
2219
2220 // number of 90 degree turns the polygon has taken
2221 int nNumTurns(0);
2222
2223 int nVerticalEdgeType=0;
2224 int nHorizontalEdgeType=0;
2225 bool bNullVertex(true);
2226 bool bCWPolygon(false); // when true, polygon is CW
2227 // oriented, when false, CCW
2228 bool bOrientationSet(false); // when false, polygon
2229 // orientation has not yet
2230 // been determined.
2231
2232 // scan all _edges_ (which involves coming back to point 0
2233 // for the last edge - thus the modulo operation below)
2234 const sal_Int32 nCount( rPoly.count() );
2235 for( sal_Int32 i=0; i<nCount; ++i )
2236 {
2237 const B2DPoint& rPoint0( rPoly.getB2DPoint(i % nCount) );
2238 const B2DPoint& rPoint1( rPoly.getB2DPoint((i+1) % nCount) );
2239
2240 // is 0 for zero direction vector, 1 for south edge and -1
2241 // for north edge (standard screen coordinate system)
2242 int nCurrVerticalEdgeType( lcl_sgn( rPoint1.getY() - rPoint0.getY() ) );
2243
2244 // is 0 for zero direction vector, 1 for east edge and -1
2245 // for west edge (standard screen coordinate system)
2246 int nCurrHorizontalEdgeType( lcl_sgn(rPoint1.getX() - rPoint0.getX()) );
2247
2248 if( nCurrVerticalEdgeType && nCurrHorizontalEdgeType )
2249 return false; // oblique edge - for sure no rect
2250
2251 const bool bCurrNullVertex( !nCurrVerticalEdgeType && !nCurrHorizontalEdgeType );
2252
2253 // current vertex is equal to previous - just skip,
2254 // until we have a real edge
2255 if( bCurrNullVertex )
2256 continue;
2257
2258 // if previous edge has two identical points, because
2259 // no previous edge direction was available, simply
2260 // take this first non-null edge as the start
2261 // direction. That's what will happen here, if
2262 // bNullVertex is false
2263 if( !bNullVertex )
2264 {
2265 // 2D cross product - is 1 for CW and -1 for CCW turns
2266 const int nCrossProduct( nHorizontalEdgeType*nCurrVerticalEdgeType -
2267 nVerticalEdgeType*nCurrHorizontalEdgeType );
2268
2269 if( !nCrossProduct )
2270 continue; // no change in orientation -
2271 // collinear edges - just go on
2272
2273 // if polygon orientation is not set, we'll
2274 // determine it now
2275 if( !bOrientationSet )
2276 {
2277 bCWPolygon = nCrossProduct == 1;
2278 bOrientationSet = true;
2279 }
2280 else
2281 {
2282 // if current turn orientation is not equal
2283 // initial orientation, this is not a
2284 // rectangle (as rectangles have consistent
2285 // orientation).
2286 if( (nCrossProduct == 1) != bCWPolygon )
2287 return false;
2288 }
2289
2290 ++nNumTurns;
2291
2292 // More than four 90 degree turns are an
2293 // indication that this must not be a rectangle.
2294 if( nNumTurns > 4 )
2295 return false;
2296 }
2297
2298 // store current state for the next turn
2299 nVerticalEdgeType = nCurrVerticalEdgeType;
2300 nHorizontalEdgeType = nCurrHorizontalEdgeType;
2301 bNullVertex = false; // won't reach this line,
2302 // if bCurrNullVertex is
2303 // true - see above
2304 }
2305
2306 return true;
2307 }
2308
2309 B3DPolygon createB3DPolygonFromB2DPolygon(const B2DPolygon& rCandidate, double fZCoordinate)
2310 {
2311 if(rCandidate.areControlPointsUsed())
2312 {
2313 // call myself recursively with subdivided input
2314 const B2DPolygon aCandidate(adaptiveSubdivideByAngle(rCandidate));
2315 return createB3DPolygonFromB2DPolygon(aCandidate, fZCoordinate);
2316 }
2317 else
2318 {
2319 B3DPolygon aRetval;
2320
2321 for(sal_uInt32 a(0); a < rCandidate.count(); a++)
2322 {
2323 B2DPoint aPoint(rCandidate.getB2DPoint(a));
2324 aRetval.append(B3DPoint(aPoint.getX(), aPoint.getY(), fZCoordinate));
2325 }
2326
2327 // copy closed state
2328 aRetval.setClosed(rCandidate.isClosed());
2329
2330 return aRetval;
2331 }
2332 }
2333
2334 B2DPolygon createB2DPolygonFromB3DPolygon(const B3DPolygon& rCandidate, const B3DHomMatrix& rMat)
2335 {
2336 B2DPolygon aRetval;
2337 const sal_uInt32 nCount(rCandidate.count());
2338 const bool bIsIdentity(rMat.isIdentity());
2339
2340 for(sal_uInt32 a(0); a < nCount; a++)
2341 {
2342 B3DPoint aCandidate(rCandidate.getB3DPoint(a));
2343
2344 if(!bIsIdentity)
2345 {
2346 aCandidate *= rMat;
2347 }
2348
2349 aRetval.append(B2DPoint(aCandidate.getX(), aCandidate.getY()));
2350 }
2351
2352 // copy closed state
2353 aRetval.setClosed(rCandidate.isClosed());
2354
2355 return aRetval;
2356 }
2357
2358 double getSmallestDistancePointToEdge(const B2DPoint& rPointA, const B2DPoint& rPointB, const B2DPoint& rTestPoint, double& rCut)
2359 {
2360 if(rPointA.equal(rPointB))
2361 {
2362 rCut = 0.0;
2363 const B2DVector aVector(rTestPoint - rPointA);
2364 return aVector.getLength();
2365 }
2366 else
2367 {
2368 // get the relative cut value on line vector (Vector1) for cut with perpendicular through TestPoint
2369 const B2DVector aVector1(rPointB - rPointA);
2370 const B2DVector aVector2(rTestPoint - rPointA);
2371 const double fDividend((aVector2.getX() * aVector1.getX()) + (aVector2.getY() * aVector1.getY()));
2372 const double fDivisor((aVector1.getX() * aVector1.getX()) + (aVector1.getY() * aVector1.getY()));
2373 const double fCut(fDividend / fDivisor);
2374
2375 if(fCut < 0.0)
2376 {
2377 // not in line range, get distance to PointA
2378 rCut = 0.0;
2379 return aVector2.getLength();
2380 }
2381 else if(fCut > 1.0)
2382 {
2383 // not in line range, get distance to PointB
2384 rCut = 1.0;
2385 const B2DVector aVector(rTestPoint - rPointB);
2386 return aVector.getLength();
2387 }
2388 else
2389 {
2390 // in line range
2391 const B2DPoint aCutPoint(rPointA + fCut * aVector1);
2392 const B2DVector aVector(rTestPoint - aCutPoint);
2393 rCut = fCut;
2394 return aVector.getLength();
2395 }
2396 }
2397 }
2398
2399 double getSmallestDistancePointToPolygon(const B2DPolygon& rCandidate, const B2DPoint& rTestPoint, sal_uInt32& rEdgeIndex, double& rCut)
2400 {
2401 double fRetval(DBL_MAX1.7976931348623157e+308);
2402 const sal_uInt32 nPointCount(rCandidate.count());
2403
2404 if(nPointCount > 1)
2405 {
2406 const double fZero(0.0);
2407 const sal_uInt32 nEdgeCount(rCandidate.isClosed() ? nPointCount : nPointCount - 1);
2408 B2DCubicBezier aBezier;
2409 aBezier.setStartPoint(rCandidate.getB2DPoint(0));
2410
2411 for(sal_uInt32 a(0); a < nEdgeCount; a++)
2412 {
2413 const sal_uInt32 nNextIndex((a + 1) % nPointCount);
2414 aBezier.setEndPoint(rCandidate.getB2DPoint(nNextIndex));
2415 double fEdgeDist;
2416 double fNewCut(0.0);
2417 bool bEdgeIsCurve(false);
2418
2419 if(rCandidate.areControlPointsUsed())
2420 {
2421 aBezier.setControlPointA(rCandidate.getNextControlPoint(a));
2422 aBezier.setControlPointB(rCandidate.getPrevControlPoint(nNextIndex));
2423 aBezier.testAndSolveTrivialBezier();
2424 bEdgeIsCurve = aBezier.isBezier();
2425 }
2426
2427 if(bEdgeIsCurve)
2428 {
2429 fEdgeDist = aBezier.getSmallestDistancePointToBezierSegment(rTestPoint, fNewCut);
2430 }
2431 else
2432 {
2433 fEdgeDist = getSmallestDistancePointToEdge(aBezier.getStartPoint(), aBezier.getEndPoint(), rTestPoint, fNewCut);
2434 }
2435
2436 if(fRetval == DBL_MAX1.7976931348623157e+308 || fEdgeDist < fRetval)
2437 {
2438 fRetval = fEdgeDist;
2439 rEdgeIndex = a;
2440 rCut = fNewCut;
2441
2442 if(fTools::equal(fRetval, fZero))
2443 {
2444 // already found zero distance, cannot get better. Ensure numerical zero value and end loop.
2445 fRetval = 0.0;
2446 break;
2447 }
2448 }
2449
2450 // prepare next step
2451 aBezier.setStartPoint(aBezier.getEndPoint());
2452 }
2453
2454 if(rtl::math::approxEqual(1.0, rCut))
2455 {
2456 // correct rEdgeIndex when not last point
2457 if(rCandidate.isClosed())
2458 {
2459 rEdgeIndex = getIndexOfSuccessor(rEdgeIndex, rCandidate);
2460 rCut = 0.0;
2461 }
2462 else
2463 {
2464 if(rEdgeIndex != nEdgeCount - 1)
2465 {
2466 rEdgeIndex++;
2467 rCut = 0.0;
2468 }
2469 }
2470 }
2471 }
2472
2473 return fRetval;
2474 }
2475
2476 B2DPoint distort(const B2DPoint& rCandidate, const B2DRange& rOriginal, const B2DPoint& rTopLeft, const B2DPoint& rTopRight, const B2DPoint& rBottomLeft, const B2DPoint& rBottomRight)
2477 {
2478 if(fTools::equalZero(rOriginal.getWidth()) || fTools::equalZero(rOriginal.getHeight()))
2479 {
2480 return rCandidate;
2481 }
2482 else
2483 {
2484 const double fRelativeX((rCandidate.getX() - rOriginal.getMinX()) / rOriginal.getWidth());
2485 const double fRelativeY((rCandidate.getY() - rOriginal.getMinY()) / rOriginal.getHeight());
2486 const double fOneMinusRelativeX(1.0 - fRelativeX);
2487 const double fOneMinusRelativeY(1.0 - fRelativeY);
2488 const double fNewX(fOneMinusRelativeY * (fOneMinusRelativeX * rTopLeft.getX() + fRelativeX * rTopRight.getX()) +
2489 fRelativeY * (fOneMinusRelativeX * rBottomLeft.getX() + fRelativeX * rBottomRight.getX()));
2490 const double fNewY(fOneMinusRelativeX * (fOneMinusRelativeY * rTopLeft.getY() + fRelativeY * rBottomLeft.getY()) +
2491 fRelativeX * (fOneMinusRelativeY * rTopRight.getY() + fRelativeY * rBottomRight.getY()));
2492
2493 return B2DPoint(fNewX, fNewY);
2494 }
2495 }
2496
2497 B2DPolygon distort(const B2DPolygon& rCandidate, const B2DRange& rOriginal, const B2DPoint& rTopLeft, const B2DPoint& rTopRight, const B2DPoint& rBottomLeft, const B2DPoint& rBottomRight)
2498 {
2499 const sal_uInt32 nPointCount(rCandidate.count());
2500
2501 if(nPointCount && rOriginal.getWidth() != 0.0 && rOriginal.getHeight() != 0.0)
2502 {
2503 B2DPolygon aRetval;
2504
2505 for(sal_uInt32 a(0); a < nPointCount; a++)
2506 {
2507 aRetval.append(distort(rCandidate.getB2DPoint(a), rOriginal, rTopLeft, rTopRight, rBottomLeft, rBottomRight));
2508
2509 if(rCandidate.areControlPointsUsed())
2510 {
2511 if(!rCandidate.getPrevControlPoint(a).equalZero())
2512 {
2513 aRetval.setPrevControlPoint(a, distort(rCandidate.getPrevControlPoint(a), rOriginal, rTopLeft, rTopRight, rBottomLeft, rBottomRight));
2514 }
2515
2516 if(!rCandidate.getNextControlPoint(a).equalZero())
2517 {
2518 aRetval.setNextControlPoint(a, distort(rCandidate.getNextControlPoint(a), rOriginal, rTopLeft, rTopRight, rBottomLeft, rBottomRight));
2519 }
2520 }
2521 }
2522
2523 aRetval.setClosed(rCandidate.isClosed());
2524 return aRetval;
2525 }
2526 else
2527 {
2528 return rCandidate;
2529 }
2530 }
2531
2532 B2DPolygon expandToCurve(const B2DPolygon& rCandidate)
2533 {
2534 B2DPolygon aRetval(rCandidate);
2535
2536 for(sal_uInt32 a(0); a < rCandidate.count(); a++)
2537 {
2538 expandToCurveInPoint(aRetval, a);
2539 }
2540
2541 return aRetval;
2542 }
2543
2544 bool expandToCurveInPoint(B2DPolygon& rCandidate, sal_uInt32 nIndex)
2545 {
2546 OSL_ENSURE(nIndex < rCandidate.count(), "expandToCurveInPoint: Access to polygon out of range (!)")do { if (true && (!(nIndex < rCandidate.count())))
{ sal_detail_logFormat((SAL_DETAIL_LOG_LEVEL_WARN), ("legacy.osl"
), ("/home/maarten/src/libreoffice/core/basegfx/source/polygon/b2dpolygontools.cxx"
":" "2546" ": "), "%s", "expandToCurveInPoint: Access to polygon out of range (!)"
); } } while (false)
;
2547 bool bRetval(false);
2548 const sal_uInt32 nPointCount(rCandidate.count());
2549
2550 if(nPointCount)
2551 {
2552 // predecessor
2553 if(!rCandidate.isPrevControlPointUsed(nIndex))
2554 {
2555 if(!rCandidate.isClosed() && nIndex == 0)
2556 {
2557 // do not create previous vector for start point of open polygon
2558 }
2559 else
2560 {
2561 const sal_uInt32 nPrevIndex((nIndex + (nPointCount - 1)) % nPointCount);
2562 rCandidate.setPrevControlPoint(nIndex, interpolate(rCandidate.getB2DPoint(nIndex), rCandidate.getB2DPoint(nPrevIndex), 1.0 / 3.0));
2563 bRetval = true;
2564 }
2565 }
2566
2567 // successor
2568 if(!rCandidate.isNextControlPointUsed(nIndex))
2569 {
2570 if(!rCandidate.isClosed() && nIndex + 1 == nPointCount)
2571 {
2572 // do not create next vector for end point of open polygon
2573 }
2574 else
2575 {
2576 const sal_uInt32 nNextIndex((nIndex + 1) % nPointCount);
2577 rCandidate.setNextControlPoint(nIndex, interpolate(rCandidate.getB2DPoint(nIndex), rCandidate.getB2DPoint(nNextIndex), 1.0 / 3.0));
2578 bRetval = true;
2579 }
2580 }
2581 }
2582
2583 return bRetval;
2584 }
2585
2586 bool setContinuityInPoint(B2DPolygon& rCandidate, sal_uInt32 nIndex, B2VectorContinuity eContinuity)
2587 {
2588 OSL_ENSURE(nIndex < rCandidate.count(), "setContinuityInPoint: Access to polygon out of range (!)")do { if (true && (!(nIndex < rCandidate.count())))
{ sal_detail_logFormat((SAL_DETAIL_LOG_LEVEL_WARN), ("legacy.osl"
), ("/home/maarten/src/libreoffice/core/basegfx/source/polygon/b2dpolygontools.cxx"
":" "2588" ": "), "%s", "setContinuityInPoint: Access to polygon out of range (!)"
); } } while (false)
;
2589 bool bRetval(false);
2590 const sal_uInt32 nPointCount(rCandidate.count());
2591
2592 if(nPointCount)
2593 {
2594 const B2DPoint aCurrentPoint(rCandidate.getB2DPoint(nIndex));
2595
2596 switch(eContinuity)
2597 {
2598 case B2VectorContinuity::NONE :
2599 {
2600 if(rCandidate.isPrevControlPointUsed(nIndex))
2601 {
2602 if(!rCandidate.isClosed() && nIndex == 0)
2603 {
2604 // remove existing previous vector for start point of open polygon
2605 rCandidate.resetPrevControlPoint(nIndex);
2606 }
2607 else
2608 {
2609 const sal_uInt32 nPrevIndex((nIndex + (nPointCount - 1)) % nPointCount);
2610 rCandidate.setPrevControlPoint(nIndex, interpolate(aCurrentPoint, rCandidate.getB2DPoint(nPrevIndex), 1.0 / 3.0));
2611 }
2612
2613 bRetval = true;
2614 }
2615
2616 if(rCandidate.isNextControlPointUsed(nIndex))
2617 {
2618 if(!rCandidate.isClosed() && nIndex == nPointCount + 1)
2619 {
2620 // remove next vector for end point of open polygon
2621 rCandidate.resetNextControlPoint(nIndex);
2622 }
2623 else
2624 {
2625 const sal_uInt32 nNextIndex((nIndex + 1) % nPointCount);
2626 rCandidate.setNextControlPoint(nIndex, interpolate(aCurrentPoint, rCandidate.getB2DPoint(nNextIndex), 1.0 / 3.0));
2627 }
2628
2629 bRetval = true;
2630 }
2631
2632 break;
2633 }
2634 case B2VectorContinuity::C1 :
2635 {
2636 if(rCandidate.isPrevControlPointUsed(nIndex) && rCandidate.isNextControlPointUsed(nIndex))
2637 {
2638 // lengths both exist since both are used
2639 B2DVector aVectorPrev(rCandidate.getPrevControlPoint(nIndex) - aCurrentPoint);
2640 B2DVector aVectorNext(rCandidate.getNextControlPoint(nIndex) - aCurrentPoint);
2641 const double fLenPrev(aVectorPrev.getLength());
2642 const double fLenNext(aVectorNext.getLength());
2643 aVectorPrev.normalize();
2644 aVectorNext.normalize();
2645 const B2VectorOrientation aOrientation(getOrientation(aVectorPrev, aVectorNext));
2646
2647 if(aOrientation == B2VectorOrientation::Neutral && aVectorPrev.scalar(aVectorNext) < 0.0)
2648 {
2649 // parallel and opposite direction; check length
2650 if(fTools::equal(fLenPrev, fLenNext))
2651 {
2652 // this would be even C2, but we want C1. Use the lengths of the corresponding edges.
2653 const sal_uInt32 nPrevIndex((nIndex + (nPointCount - 1)) % nPointCount);
2654 const sal_uInt32 nNextIndex((nIndex + 1) % nPointCount);
2655 const double fLenPrevEdge(B2DVector(rCandidate.getB2DPoint(nPrevIndex) - aCurrentPoint).getLength() * (1.0 / 3.0));
2656 const double fLenNextEdge(B2DVector(rCandidate.getB2DPoint(nNextIndex) - aCurrentPoint).getLength() * (1.0 / 3.0));
2657
2658 rCandidate.setControlPoints(nIndex,
2659 aCurrentPoint + (aVectorPrev * fLenPrevEdge),
2660 aCurrentPoint + (aVectorNext * fLenNextEdge));
2661 bRetval = true;
2662 }
2663 }
2664 else
2665 {
2666 // not parallel or same direction, set vectors and length
2667 const B2DVector aNormalizedPerpendicular(getNormalizedPerpendicular(aVectorPrev + aVectorNext));
2668
2669 if(aOrientation == B2VectorOrientation::Positive)
2670 {
2671 rCandidate.setControlPoints(nIndex,
2672 aCurrentPoint - (aNormalizedPerpendicular * fLenPrev),
2673 aCurrentPoint + (aNormalizedPerpendicular * fLenNext));
2674 }
2675 else
2676 {
2677 rCandidate.setControlPoints(nIndex,
2678 aCurrentPoint + (aNormalizedPerpendicular * fLenPrev),
2679 aCurrentPoint - (aNormalizedPerpendicular * fLenNext));
2680 }
2681
2682 bRetval = true;
2683 }
2684 }
2685 break;
2686 }
2687 case B2VectorContinuity::C2 :
2688 {
2689 if(rCandidate.isPrevControlPointUsed(nIndex) && rCandidate.isNextControlPointUsed(nIndex))
2690 {
2691 // lengths both exist since both are used
2692 B2DVector aVectorPrev(rCandidate.getPrevControlPoint(nIndex) - aCurrentPoint);
2693 B2DVector aVectorNext(rCandidate.getNextControlPoint(nIndex) - aCurrentPoint);
2694 const double fCommonLength((aVectorPrev.getLength() + aVectorNext.getLength()) / 2.0);
2695 aVectorPrev.normalize();
2696 aVectorNext.normalize();
2697 const B2VectorOrientation aOrientation(getOrientation(aVectorPrev, aVectorNext));
2698
2699 if(aOrientation == B2VectorOrientation::Neutral && aVectorPrev.scalar(aVectorNext) < 0.0)
2700 {
2701 // parallel and opposite direction; set length. Use one direction for better numerical correctness
2702 const B2DVector aScaledDirection(aVectorPrev * fCommonLength);
2703
2704 rCandidate.setControlPoints(nIndex,
2705 aCurrentPoint + aScaledDirection,
2706 aCurrentPoint - aScaledDirection);
2707 }
2708 else
2709 {
2710 // not parallel or same direction, set vectors and length
2711 const B2DVector aNormalizedPerpendicular(getNormalizedPerpendicular(aVectorPrev + aVectorNext));
2712 const B2DVector aPerpendicular(aNormalizedPerpendicular * fCommonLength);
2713
2714 if(aOrientation == B2VectorOrientation::Positive)
2715 {
2716 rCandidate.setControlPoints(nIndex,
2717 aCurrentPoint - aPerpendicular,
2718 aCurrentPoint + aPerpendicular);
2719 }
2720 else
2721 {
2722 rCandidate.setControlPoints(nIndex,
2723 aCurrentPoint + aPerpendicular,
2724 aCurrentPoint - aPerpendicular);
2725 }
2726 }
2727
2728 bRetval = true;
2729 }
2730 break;
2731 }
2732 }
2733 }
2734
2735 return bRetval;
2736 }
2737
2738 B2DPolygon growInNormalDirection(const B2DPolygon& rCandidate, double fValue)
2739 {
2740 if(fValue != 0.0)
2741 {
2742 if(rCandidate.areControlPointsUsed())
2743 {
2744 // call myself recursively with subdivided input
2745 const B2DPolygon aCandidate(adaptiveSubdivideByAngle(rCandidate));
2746 return growInNormalDirection(aCandidate, fValue);
2747 }
2748 else
2749 {
2750 B2DPolygon aRetval;
2751 const sal_uInt32 nPointCount(rCandidate.count());
2752
2753 if(nPointCount)
2754 {
2755 B2DPoint aPrev(rCandidate.getB2DPoint(nPointCount - 1));
2756 B2DPoint aCurrent(rCandidate.getB2DPoint(0));
2757
2758 for(sal_uInt32 a(0); a < nPointCount; a++)
2759 {
2760 const B2DPoint aNext(rCandidate.getB2DPoint(a + 1 == nPointCount ? 0 : a + 1));
2761 const B2DVector aBack(aPrev - aCurrent);
2762 const B2DVector aForw(aNext - aCurrent);
2763 const B2DVector aPerpBack(getNormalizedPerpendicular(aBack));
2764 const B2DVector aPerpForw(getNormalizedPerpendicular(aForw));
2765 B2DVector aDirection(aPerpBack - aPerpForw);
2766 aDirection.normalize();
2767 aDirection *= fValue;
2768 aRetval.append(aCurrent + aDirection);
2769
2770 // prepare next step
2771 aPrev = aCurrent;
2772 aCurrent = aNext;
2773 }
2774 }
2775
2776 // copy closed state
2777 aRetval.setClosed(rCandidate.isClosed());
2778
2779 return aRetval;
2780 }
2781 }
2782 else
2783 {
2784 return rCandidate;
2785 }
2786 }
2787
2788 B2DPolygon reSegmentPolygon(const B2DPolygon& rCandidate, sal_uInt32 nSegments)
2789 {
2790 B2DPolygon aRetval;
2791 const sal_uInt32 nPointCount(rCandidate.count());
2792
2793 if(nPointCount && nSegments)
2794 {
2795 // get current segment count
2796 const sal_uInt32 nSegmentCount(rCandidate.isClosed() ? nPointCount : nPointCount - 1);
2797
2798 if(nSegmentCount == nSegments)
2799 {
2800 aRetval = rCandidate;
2801 }
2802 else
2803 {
2804 const double fLength(getLength(rCandidate));
2805 const sal_uInt32 nLoopCount(rCandidate.isClosed() ? nSegments : nSegments + 1);
2806
2807 for(sal_uInt32 a(0); a < nLoopCount; a++)
2808 {
2809 const double fRelativePos(static_cast<double>(a) / static_cast<double>(nSegments)); // 0.0 .. 1.0
2810 const B2DPoint aNewPoint(getPositionRelative(rCandidate, fRelativePos, fLength));
2811 aRetval.append(aNewPoint);
2812 }
2813
2814 // copy closed flag
2815 aRetval.setClosed(rCandidate.isClosed());
2816 }
2817 }
2818
2819 return aRetval;
2820 }
2821
2822 B2DPolygon interpolate(const B2DPolygon& rOld1, const B2DPolygon& rOld2, double t)
2823 {
2824 OSL_ENSURE(rOld1.count() == rOld2.count(), "B2DPolygon interpolate: Different geometry (!)")do { if (true && (!(rOld1.count() == rOld2.count())))
{ sal_detail_logFormat((SAL_DETAIL_LOG_LEVEL_WARN), ("legacy.osl"
), ("/home/maarten/src/libreoffice/core/basegfx/source/polygon/b2dpolygontools.cxx"
":" "2824" ": "), "%s", "B2DPolygon interpolate: Different geometry (!)"
); } } while (false)
;
2825
2826 if(fTools::lessOrEqual(t, 0.0) || rOld1 == rOld2)
2827 {
2828 return rOld1;
2829 }
2830 else if(fTools::moreOrEqual(t, 1.0))
2831 {
2832 return rOld2;
2833 }
2834 else
2835 {
2836 B2DPolygon aRetval;
2837 const bool bInterpolateVectors(rOld1.areControlPointsUsed() || rOld2.areControlPointsUsed());
2838 aRetval.setClosed(rOld1.isClosed() && rOld2.isClosed());
2839
2840 for(sal_uInt32 a(0); a < rOld1.count(); a++)
2841 {
2842 aRetval.append(interpolate(rOld1.getB2DPoint(a), rOld2.getB2DPoint(a), t));
2843
2844 if(bInterpolateVectors)
2845 {
2846 aRetval.setPrevControlPoint(a, interpolate(rOld1.getPrevControlPoint(a), rOld2.getPrevControlPoint(a), t));
2847 aRetval.setNextControlPoint(a, interpolate(rOld1.getNextControlPoint(a), rOld2.getNextControlPoint(a), t));
2848 }
2849 }
2850
2851 return aRetval;
2852 }
2853 }
2854
2855 // #i76891#
2856 B2DPolygon simplifyCurveSegments(const B2DPolygon& rCandidate)
2857 {
2858 const sal_uInt32 nPointCount(rCandidate.count());
2859
2860 if(nPointCount && rCandidate.areControlPointsUsed())
2861 {
2862 // prepare loop
2863 const sal_uInt32 nEdgeCount(rCandidate.isClosed() ? nPointCount : nPointCount - 1);
2864 B2DPolygon aRetval;
2865 B2DCubicBezier aBezier;
2866 aBezier.setStartPoint(rCandidate.getB2DPoint(0));
2867
2868 // try to avoid costly reallocations
2869 aRetval.reserve( nEdgeCount+1);
2870
2871 // add start point
2872 aRetval.append(aBezier.getStartPoint());
2873
2874 for(sal_uInt32 a(0); a < nEdgeCount; a++)
2875 {
2876 // get values for edge
2877 const sal_uInt32 nNextIndex((a + 1) % nPointCount);
2878 aBezier.setEndPoint(rCandidate.getB2DPoint(nNextIndex));
2879 aBezier.setControlPointA(rCandidate.getNextControlPoint(a));
2880 aBezier.setControlPointB(rCandidate.getPrevControlPoint(nNextIndex));
2881 aBezier.testAndSolveTrivialBezier();
2882
2883 // still bezier?
2884 if(aBezier.isBezier())
2885 {
2886 // add edge with control vectors
2887 aRetval.appendBezierSegment(aBezier.getControlPointA(), aBezier.getControlPointB(), aBezier.getEndPoint());
2888 }
2889 else
2890 {
2891 // add edge
2892 aRetval.append(aBezier.getEndPoint());
2893 }
2894
2895 // next point
2896 aBezier.setStartPoint(aBezier.getEndPoint());
2897 }
2898
2899 if(rCandidate.isClosed())
2900 {
2901 // set closed flag, rescue control point and correct last double point
2902 closeWithGeometryChange(aRetval);
2903 }
2904
2905 return aRetval;
2906 }
2907 else
2908 {
2909 return rCandidate;
2910 }
2911 }
2912
2913 // makes the given indexed point the new polygon start point. To do that, the points in the
2914 // polygon will be rotated. This is only valid for closed polygons, for non-closed ones
2915 // an assertion will be triggered
2916 B2DPolygon makeStartPoint(const B2DPolygon& rCandidate, sal_uInt32 nIndexOfNewStatPoint)
2917 {
2918 const sal_uInt32 nPointCount(rCandidate.count());
2919
2920 if(nPointCount > 2 && nIndexOfNewStatPoint != 0 && nIndexOfNewStatPoint < nPointCount)
2921 {
2922 OSL_ENSURE(rCandidate.isClosed(), "makeStartPoint: only valid for closed polygons (!)")do { if (true && (!(rCandidate.isClosed()))) { sal_detail_logFormat
((SAL_DETAIL_LOG_LEVEL_WARN), ("legacy.osl"), ("/home/maarten/src/libreoffice/core/basegfx/source/polygon/b2dpolygontools.cxx"
":" "2922" ": "), "%s", "makeStartPoint: only valid for closed polygons (!)"
); } } while (false)
;
2923 B2DPolygon aRetval;
2924
2925 for(sal_uInt32 a(0); a < nPointCount; a++)
2926 {
2927 const sal_uInt32 nSourceIndex((a + nIndexOfNewStatPoint) % nPointCount);
2928 aRetval.append(rCandidate.getB2DPoint(nSourceIndex));
2929
2930 if(rCandidate.areControlPointsUsed())
2931 {
2932 aRetval.setPrevControlPoint(a, rCandidate.getPrevControlPoint(nSourceIndex));
2933 aRetval.setNextControlPoint(a, rCandidate.getNextControlPoint(nSourceIndex));
2934 }
2935 }
2936
2937 return aRetval;
2938 }
2939
2940 return rCandidate;
2941 }
2942
2943 B2DPolygon createEdgesOfGivenLength(const B2DPolygon& rCandidate, double fLength, double fStart, double fEnd)
2944 {
2945 B2DPolygon aRetval;
2946
2947 if(fLength < 0.0)
2948 {
2949 fLength = 0.0;
2950 }
2951
2952 if(!fTools::equalZero(fLength))
2953 {
2954 if(fStart < 0.0)
2955 {
2956 fStart = 0.0;
2957 }
2958
2959 if(fEnd < 0.0)
2960 {
2961 fEnd = 0.0;
2962 }
2963
2964 if(fEnd < fStart)
2965 {
2966 fEnd = fStart;
2967 }
2968
2969 // iterate and consume pieces with fLength. First subdivide to reduce input to line segments
2970 const B2DPolygon aCandidate(rCandidate.areControlPointsUsed() ? rCandidate.getDefaultAdaptiveSubdivision() : rCandidate);
2971 const sal_uInt32 nPointCount(aCandidate.count());
2972
2973 if(nPointCount > 1)
2974 {
2975 const bool bEndActive(!fTools::equalZero(fEnd));
2976 const sal_uInt32 nEdgeCount(aCandidate.isClosed() ? nPointCount : nPointCount - 1);
2977 B2DPoint aCurrent(aCandidate.getB2DPoint(0));
2978 double fPositionInEdge(fStart);
2979 double fAbsolutePosition(fStart);
2980
2981 for(sal_uInt32 a(0); a < nEdgeCount; a++)
2982 {
2983 const sal_uInt32 nNextIndex((a + 1) % nPointCount);
2984 const B2DPoint aNext(aCandidate.getB2DPoint(nNextIndex));
2985 const B2DVector aEdge(aNext - aCurrent);
2986 double fEdgeLength(aEdge.getLength());
2987
2988 if(!fTools::equalZero(fEdgeLength))
2989 {
2990 while(fTools::less(fPositionInEdge, fEdgeLength))
2991 {
2992 // move position on edge forward as long as on edge
2993 const double fScalar(fPositionInEdge / fEdgeLength);
2994 aRetval.append(aCurrent + (aEdge * fScalar));
2995 fPositionInEdge += fLength;
2996
2997 if(bEndActive)
2998 {
2999 fAbsolutePosition += fLength;
3000
3001 if(fTools::more(fAbsolutePosition, fEnd))
3002 {
3003 break;
3004 }
3005 }
3006 }
3007
3008 // subtract length of current edge
3009 fPositionInEdge -= fEdgeLength;
3010 }
3011
3012 if(bEndActive && fTools::more(fAbsolutePosition, fEnd))
3013 {
3014 break;
3015 }
3016
3017 // prepare next step
3018 aCurrent = aNext;
3019 }
3020
3021 // keep closed state
3022 aRetval.setClosed(aCandidate.isClosed());
3023 }
3024 else
3025 {
3026 // source polygon has only one point, return unchanged
3027 aRetval = aCandidate;
3028 }
3029 }
3030
3031 return aRetval;
3032 }
3033
3034 B2DPolygon createWaveline(const B2DPolygon& rCandidate, double fWaveWidth, double fWaveHeight)
3035 {
3036 B2DPolygon aRetval;
3037
3038 if(fWaveWidth < 0.0)
3039 {
3040 fWaveWidth = 0.0;
3041 }
3042
3043 if(fWaveHeight < 0.0)
3044 {
3045 fWaveHeight = 0.0;
3046 }
3047
3048 const bool bHasWidth(!fTools::equalZero(fWaveWidth));
3049
3050 if(bHasWidth)
3051 {
3052 const bool bHasHeight(!fTools::equalZero(fWaveHeight));
3053 if(bHasHeight)
3054 {
3055 // width and height, create waveline. First subdivide to reduce input to line segments
3056 // of WaveWidth. Last segment may be missing. If this turns out to be a problem, it
3057 // may be added here again using the original last point from rCandidate. It may
3058 // also be the case that rCandidate was closed. To simplify things it is handled here
3059 // as if it was opened.
3060 // Result from createEdgesOfGivenLength contains no curved segments, handle as straight
3061 // edges.
3062 const B2DPolygon aEqualLenghEdges(createEdgesOfGivenLength(rCandidate, fWaveWidth));
3063 const sal_uInt32 nPointCount(aEqualLenghEdges.count());
3064
3065 if(nPointCount > 1)
3066 {
3067 // iterate over straight edges, add start point
3068 B2DPoint aCurrent(aEqualLenghEdges.getB2DPoint(0));
3069 aRetval.append(aCurrent);
3070
3071 for(sal_uInt32 a(0); a < nPointCount - 1; a++)
3072 {
3073 const sal_uInt32 nNextIndex((a + 1) % nPointCount);
3074 const B2DPoint aNext(aEqualLenghEdges.getB2DPoint(nNextIndex));
3075 const B2DVector aEdge(aNext - aCurrent);
3076 const B2DVector aPerpendicular(getNormalizedPerpendicular(aEdge));
3077 const B2DVector aControlOffset((aEdge * 0.467308) - (aPerpendicular * fWaveHeight));
3078
3079 // add curve segment
3080 aRetval.appendBezierSegment(
3081 aCurrent + aControlOffset,
3082 aNext - aControlOffset,
3083 aNext);
3084
3085 // prepare next step
3086 aCurrent = aNext;
3087 }
3088 }
3089 }
3090 else
3091 {
3092 // width but no height -> return original polygon
3093 aRetval = rCandidate;
3094 }
3095 }
3096 else
3097 {
3098 // no width -> no waveline, stay empty and return
3099 }
3100
3101 return aRetval;
3102 }
3103
3104 // snap points of horizontal or vertical edges to discrete values
3105 B2DPolygon snapPointsOfHorizontalOrVerticalEdges(const B2DPolygon& rCandidate)
3106 {
3107 const sal_uInt32 nPointCount(rCandidate.count());
3108
3109 if(nPointCount > 1)
3110 {
3111 // Start by copying the source polygon to get a writeable copy. The closed state is
3112 // copied by aRetval's initialisation, too, so no need to copy it in this method
3113 B2DPolygon aRetval(rCandidate);
3114
3115 // prepare geometry data. Get rounded from original
3116 B2ITuple aPrevTuple(basegfx::fround(rCandidate.getB2DPoint(nPointCount - 1)));
3117 B2DPoint aCurrPoint(rCandidate.getB2DPoint(0));
3118 B2ITuple aCurrTuple(basegfx::fround(aCurrPoint));
3119
3120 // loop over all points. This will also snap the implicit closing edge
3121 // even when not closed, but that's no problem here
3122 for(sal_uInt32 a(0); a < nPointCount; a++)
3123 {
3124 // get next point. Get rounded from original
3125 const bool bLastRun(a + 1 == nPointCount);
3126 const sal_uInt32 nNextIndex(bLastRun ? 0 : a + 1);
3127 const B2DPoint aNextPoint(rCandidate.getB2DPoint(nNextIndex));
3128 const B2ITuple aNextTuple(basegfx::fround(aNextPoint));
3129
3130 // get the states
3131 const bool bPrevVertical(aPrevTuple.getX() == aCurrTuple.getX());
3132 const bool bNextVertical(aNextTuple.getX() == aCurrTuple.getX());
3133 const bool bPrevHorizontal(aPrevTuple.getY() == aCurrTuple.getY());
3134 const bool bNextHorizontal(aNextTuple.getY() == aCurrTuple.getY());
3135 const bool bSnapX(bPrevVertical || bNextVertical);
3136 const bool bSnapY(bPrevHorizontal || bNextHorizontal);
3137
3138 if(bSnapX || bSnapY)
3139 {
3140 const B2DPoint aSnappedPoint(
3141 bSnapX ? aCurrTuple.getX() : aCurrPoint.getX(),
3142 bSnapY ? aCurrTuple.getY() : aCurrPoint.getY());
3143
3144 aRetval.setB2DPoint(a, aSnappedPoint);
3145 }
3146
3147 // prepare next point
3148 if(!bLastRun)
3149 {
3150 aPrevTuple = aCurrTuple;
3151 aCurrPoint = aNextPoint;
3152 aCurrTuple = aNextTuple;
3153 }
3154 }
3155
3156 return aRetval;
3157 }
3158 else
3159 {
3160 return rCandidate;
3161 }
3162 }
3163
3164 B2DVector getTangentEnteringPoint(const B2DPolygon& rCandidate, sal_uInt32 nIndex)
3165 {
3166 B2DVector aRetval(0.0, 0.0);
3167 const sal_uInt32 nCount(rCandidate.count());
3168
3169 if(nIndex >= nCount)
3170 {
3171 // out of range
3172 return aRetval;
3173 }
3174
3175 // start immediately at prev point compared to nIndex
3176 const bool bClosed(rCandidate.isClosed());
3177 sal_uInt32 nPrev(bClosed ? (nIndex + nCount - 1) % nCount : nIndex ? nIndex - 1 : nIndex);
3178
3179 if(nPrev == nIndex)
3180 {
3181 // no previous, done
3182 return aRetval;
3183 }
3184
3185 B2DCubicBezier aSegment;
3186
3187 // go backward in the polygon; if closed, maximal back to start index (nIndex); if not closed,
3188 // until zero. Use nIndex as stop criteria
3189 while(nPrev != nIndex)
3190 {
3191 // get BezierSegment and tangent at the *end* of segment
3192 rCandidate.getBezierSegment(nPrev, aSegment);
3193 aRetval = aSegment.getTangent(1.0);
3194
3195 if(!aRetval.equalZero())
3196 {
3197 // if we have a tangent, return it
3198 return aRetval;
3199 }
3200
3201 // prepare index before checked one
3202 nPrev = bClosed ? (nPrev + nCount - 1) % nCount : nPrev ? nPrev - 1 : nIndex;
3203 }
3204
3205 return aRetval;
3206 }
3207
3208 B2DVector getTangentLeavingPoint(const B2DPolygon& rCandidate, sal_uInt32 nIndex)
3209 {
3210 B2DVector aRetval(0.0, 0.0);
3211 const sal_uInt32 nCount(rCandidate.count());
3212
3213 if(nIndex >= nCount)
3214 {
3215 // out of range
3216 return aRetval;
3217 }
3218
3219 // start at nIndex
3220 const bool bClosed(rCandidate.isClosed());
3221 sal_uInt32 nCurrent(nIndex);
3222 B2DCubicBezier aSegment;
3223
3224 // go forward; if closed, do this until once around and back at start index (nIndex); if not
3225 // closed, until last point (nCount - 1). Use nIndex as stop criteria
3226 do
3227 {
3228 // get BezierSegment and tangent at the *beginning* of segment
3229 rCandidate.getBezierSegment(nCurrent, aSegment);
3230 aRetval = aSegment.getTangent(0.0);
3231
3232 if(!aRetval.equalZero())
3233 {
3234 // if we have a tangent, return it
3235 return aRetval;
3236 }
3237
3238 // prepare next index
3239 nCurrent = bClosed ? (nCurrent + 1) % nCount : nCurrent + 1 < nCount ? nCurrent + 1 : nIndex;
3240 }
3241 while(nCurrent != nIndex);
3242
3243 return aRetval;
3244 }
3245
3246 // converters for css::drawing::PointSequence
3247
3248 B2DPolygon UnoPointSequenceToB2DPolygon(
3249 const css::drawing::PointSequence& rPointSequenceSource)
3250 {
3251 B2DPolygon aRetval;
3252 const sal_uInt32 nLength(rPointSequenceSource.getLength());
3253
3254 if(nLength)
3255 {
3256 aRetval.reserve(nLength);
3257 const css::awt::Point* pArray = rPointSequenceSource.getConstArray();
3258 const css::awt::Point* pArrayEnd = pArray + rPointSequenceSource.getLength();
3259
3260 for(;pArray != pArrayEnd; pArray++)
3261 {
3262 aRetval.append(B2DPoint(pArray->X, pArray->Y));
3263 }
3264
3265 // check for closed state flag
3266 utils::checkClosed(aRetval);
3267 }
3268
3269 return aRetval;
3270 }
3271
3272 void B2DPolygonToUnoPointSequence(
3273 const B2DPolygon& rPolygon,
3274 css::drawing::PointSequence& rPointSequenceRetval)
3275 {
3276 B2DPolygon aPolygon(rPolygon);
3277
3278 if(aPolygon.areControlPointsUsed())
3279 {
3280 OSL_ENSURE(false, "B2DPolygonToUnoPointSequence: Source contains bezier segments, wrong UNO API data type may be used (!)")do { if (true && (!(false))) { sal_detail_logFormat((
SAL_DETAIL_LOG_LEVEL_WARN), ("legacy.osl"), ("/home/maarten/src/libreoffice/core/basegfx/source/polygon/b2dpolygontools.cxx"
":" "3280" ": "), "%s", "B2DPolygonToUnoPointSequence: Source contains bezier segments, wrong UNO API data type may be used (!)"
); } } while (false)
;
3281 aPolygon = aPolygon.getDefaultAdaptiveSubdivision();
3282 }
3283
3284 const sal_uInt32 nPointCount(aPolygon.count());
3285
3286 if(nPointCount)
3287 {
3288 // Take closed state into account, the API polygon still uses the old closed definition
3289 // with last/first point are identical (cannot hold information about open polygons with identical
3290 // first and last point, though)
3291 const bool bIsClosed(aPolygon.isClosed());
3292
3293 rPointSequenceRetval.realloc(bIsClosed ? nPointCount + 1 : nPointCount);
3294 css::awt::Point* pSequence = rPointSequenceRetval.getArray();
3295
3296 for(sal_uInt32 b(0); b < nPointCount; b++)
3297 {
3298 const B2DPoint aPoint(aPolygon.getB2DPoint(b));
3299 const css::awt::Point aAPIPoint(fround(aPoint.getX()), fround(aPoint.getY()));
3300
3301 *pSequence = aAPIPoint;
3302 pSequence++;
3303 }
3304
3305 // copy first point if closed
3306 if(bIsClosed)
3307 {
3308 *pSequence = *rPointSequenceRetval.getArray();
3309 }
3310 }
3311 else
3312 {
3313 rPointSequenceRetval.realloc(0);
3314 }
3315 }
3316
3317 // converters for css::drawing::PointSequence and
3318 // css::drawing::FlagSequence to B2DPolygon (curved polygons)
3319
3320 B2DPolygon UnoPolygonBezierCoordsToB2DPolygon(
3321 const css::drawing::PointSequence& rPointSequenceSource,
3322 const css::drawing::FlagSequence& rFlagSequenceSource)
3323 {
3324 const sal_uInt32 nCount(static_cast<sal_uInt32>(rPointSequenceSource.getLength()));
3325 OSL_ENSURE(nCount == static_cast<sal_uInt32>(rFlagSequenceSource.getLength()),do { if (true && (!(nCount == static_cast<sal_uInt32
>(rFlagSequenceSource.getLength())))) { sal_detail_logFormat
((SAL_DETAIL_LOG_LEVEL_WARN), ("legacy.osl"), ("/home/maarten/src/libreoffice/core/basegfx/source/polygon/b2dpolygontools.cxx"
":" "3326" ": "), "%s", "UnoPolygonBezierCoordsToB2DPolygon: Unequal count of Points and Flags (!)"
); } } while (false)
3326 "UnoPolygonBezierCoordsToB2DPolygon: Unequal count of Points and Flags (!)")do { if (true && (!(nCount == static_cast<sal_uInt32
>(rFlagSequenceSource.getLength())))) { sal_detail_logFormat
((SAL_DETAIL_LOG_LEVEL_WARN), ("legacy.osl"), ("/home/maarten/src/libreoffice/core/basegfx/source/polygon/b2dpolygontools.cxx"
":" "3326" ": "), "%s", "UnoPolygonBezierCoordsToB2DPolygon: Unequal count of Points and Flags (!)"
); } } while (false)
;
3327
3328 // prepare new polygon
3329 B2DPolygon aRetval;
3330
3331 if(0 != nCount)
3332 {
3333 const css::awt::Point* pPointSequence = rPointSequenceSource.getConstArray();
3334 const css::drawing::PolygonFlags* pFlagSequence = rFlagSequenceSource.getConstArray();
3335
3336 // get first point and flag
3337 B2DPoint aNewCoordinatePair(pPointSequence->X, pPointSequence->Y); pPointSequence++;
3338 css::drawing::PolygonFlags ePolygonFlag(*pFlagSequence); pFlagSequence++;
3339 B2DPoint aControlA;
3340 B2DPoint aControlB;
3341
3342 // first point is not allowed to be a control point
3343 OSL_ENSURE(ePolygonFlag != css::drawing::PolygonFlags_CONTROL,do { if (true && (!(ePolygonFlag != css::drawing::PolygonFlags_CONTROL
))) { sal_detail_logFormat((SAL_DETAIL_LOG_LEVEL_WARN), ("legacy.osl"
), ("/home/maarten/src/libreoffice/core/basegfx/source/polygon/b2dpolygontools.cxx"
":" "3344" ": "), "%s", "UnoPolygonBezierCoordsToB2DPolygon: Start point is a control point, illegal input polygon (!)"
); } } while (false)
3344 "UnoPolygonBezierCoordsToB2DPolygon: Start point is a control point, illegal input polygon (!)")do { if (true && (!(ePolygonFlag != css::drawing::PolygonFlags_CONTROL
))) { sal_detail_logFormat((SAL_DETAIL_LOG_LEVEL_WARN), ("legacy.osl"
), ("/home/maarten/src/libreoffice/core/basegfx/source/polygon/b2dpolygontools.cxx"
":" "3344" ": "), "%s", "UnoPolygonBezierCoordsToB2DPolygon: Start point is a control point, illegal input polygon (!)"
); } } while (false)
;
3345
3346 // add first point as start point
3347 aRetval.append(aNewCoordinatePair);
3348
3349 for(sal_uInt32 b(1); b < nCount;)
3350 {
3351 // prepare loop
3352 bool bControlA(false);
3353 bool bControlB(false);
3354
3355 // get next point and flag
3356 aNewCoordinatePair = B2DPoint(pPointSequence->X, pPointSequence->Y);
3357 ePolygonFlag = *pFlagSequence;
3358 pPointSequence++; pFlagSequence++; b++;
3359
3360 if(b < nCount && ePolygonFlag == css::drawing::PolygonFlags_CONTROL)
3361 {
3362 aControlA = aNewCoordinatePair;
3363 bControlA = true;
3364
3365 // get next point and flag
3366 aNewCoordinatePair = B2DPoint(pPointSequence->X, pPointSequence->Y);
3367 ePolygonFlag = *pFlagSequence;
3368 pPointSequence++; pFlagSequence++; b++;
3369 }
3370
3371 if(b < nCount && ePolygonFlag == css::drawing::PolygonFlags_CONTROL)
3372 {
3373 aControlB = aNewCoordinatePair;
3374 bControlB = true;
3375
3376 // get next point and flag
3377 aNewCoordinatePair = B2DPoint(pPointSequence->X, pPointSequence->Y);
3378 ePolygonFlag = *pFlagSequence;
3379 pPointSequence++; pFlagSequence++; b++;
3380 }
3381
3382 // two or no control points are consumed, another one would be an error.
3383 // It's also an error if only one control point was read
3384 SAL_WARN_IF(ePolygonFlag == css::drawing::PolygonFlags_CONTROL || bControlA != bControlB,do { if (true && (ePolygonFlag == css::drawing::PolygonFlags_CONTROL
|| bControlA != bControlB)) { switch (sal_detail_log_report(
::SAL_DETAIL_LOG_LEVEL_WARN, "basegfx")) { case SAL_DETAIL_LOG_ACTION_IGNORE
: break; case SAL_DETAIL_LOG_ACTION_LOG: if (sizeof ::sal::detail
::getResult( ::sal::detail::StreamStart() << "UnoPolygonBezierCoordsToB2DPolygon: Illegal source polygon (!)"
) == 1) { ::sal_detail_log( (::SAL_DETAIL_LOG_LEVEL_WARN), ("basegfx"
), ("/home/maarten/src/libreoffice/core/basegfx/source/polygon/b2dpolygontools.cxx"
":" "3385" ": "), ::sal::detail::unwrapStream( ::sal::detail
::StreamStart() << "UnoPolygonBezierCoordsToB2DPolygon: Illegal source polygon (!)"
), 0); } else { ::std::ostringstream sal_detail_stream; sal_detail_stream
<< "UnoPolygonBezierCoordsToB2DPolygon: Illegal source polygon (!)"
; ::sal::detail::log( (::SAL_DETAIL_LOG_LEVEL_WARN), ("basegfx"
), ("/home/maarten/src/libreoffice/core/basegfx/source/polygon/b2dpolygontools.cxx"
":" "3385" ": "), sal_detail_stream, 0); }; break; case SAL_DETAIL_LOG_ACTION_FATAL
: if (sizeof ::sal::detail::getResult( ::sal::detail::StreamStart
() << "UnoPolygonBezierCoordsToB2DPolygon: Illegal source polygon (!)"
) == 1) { ::sal_detail_log( (::SAL_DETAIL_LOG_LEVEL_WARN), ("basegfx"
), ("/home/maarten/src/libreoffice/core/basegfx/source/polygon/b2dpolygontools.cxx"
":" "3385" ": "), ::sal::detail::unwrapStream( ::sal::detail
::StreamStart() << "UnoPolygonBezierCoordsToB2DPolygon: Illegal source polygon (!)"
), 0); } else { ::std::ostringstream sal_detail_stream; sal_detail_stream
<< "UnoPolygonBezierCoordsToB2DPolygon: Illegal source polygon (!)"
; ::sal::detail::log( (::SAL_DETAIL_LOG_LEVEL_WARN), ("basegfx"
), ("/home/maarten/src/libreoffice/core/basegfx/source/polygon/b2dpolygontools.cxx"
":" "3385" ": "), sal_detail_stream, 0); }; std::abort(); break
; } } } while (false)
3385 "basegfx", "UnoPolygonBezierCoordsToB2DPolygon: Illegal source polygon (!)")do { if (true && (ePolygonFlag == css::drawing::PolygonFlags_CONTROL
|| bControlA != bControlB)) { switch (sal_detail_log_report(
::SAL_DETAIL_LOG_LEVEL_WARN, "basegfx")) { case SAL_DETAIL_LOG_ACTION_IGNORE
: break; case SAL_DETAIL_LOG_ACTION_LOG: if (sizeof ::sal::detail
::getResult( ::sal::detail::StreamStart() << "UnoPolygonBezierCoordsToB2DPolygon: Illegal source polygon (!)"
) == 1) { ::sal_detail_log( (::SAL_DETAIL_LOG_LEVEL_WARN), ("basegfx"
), ("/home/maarten/src/libreoffice/core/basegfx/source/polygon/b2dpolygontools.cxx"
":" "3385" ": "), ::sal::detail::unwrapStream( ::sal::detail
::StreamStart() << "UnoPolygonBezierCoordsToB2DPolygon: Illegal source polygon (!)"
), 0); } else { ::std::ostringstream sal_detail_stream; sal_detail_stream
<< "UnoPolygonBezierCoordsToB2DPolygon: Illegal source polygon (!)"
; ::sal::detail::log( (::SAL_DETAIL_LOG_LEVEL_WARN), ("basegfx"
), ("/home/maarten/src/libreoffice/core/basegfx/source/polygon/b2dpolygontools.cxx"
":" "3385" ": "), sal_detail_stream, 0); }; break; case SAL_DETAIL_LOG_ACTION_FATAL
: if (sizeof ::sal::detail::getResult( ::sal::detail::StreamStart
() << "UnoPolygonBezierCoordsToB2DPolygon: Illegal source polygon (!)"
) == 1) { ::sal_detail_log( (::SAL_DETAIL_LOG_LEVEL_WARN), ("basegfx"
), ("/home/maarten/src/libreoffice/core/basegfx/source/polygon/b2dpolygontools.cxx"
":" "3385" ": "), ::sal::detail::unwrapStream( ::sal::detail
::StreamStart() << "UnoPolygonBezierCoordsToB2DPolygon: Illegal source polygon (!)"
), 0); } else { ::std::ostringstream sal_detail_stream; sal_detail_stream
<< "UnoPolygonBezierCoordsToB2DPolygon: Illegal source polygon (!)"
; ::sal::detail::log( (::SAL_DETAIL_LOG_LEVEL_WARN), ("basegfx"
), ("/home/maarten/src/libreoffice/core/basegfx/source/polygon/b2dpolygontools.cxx"
":" "3385" ": "), sal_detail_stream, 0); }; std::abort(); break
; } } } while (false)
;
3386
3387 // the previous writes used the B2DPolyPolygon -> utils::PolyPolygon converter
3388 // which did not create minimal PolyPolygons, but created all control points
3389 // as null vectors (identical points). Because of the former P(CA)(CB)-norm of
3390 // B2DPolygon and it's unused sign of being the zero-vector and CA and CB being
3391 // relative to P, an empty edge was exported as P == CA == CB. Luckily, the new
3392 // export format can be read without errors by the old OOo-versions, so we need only
3393 // to correct here at read and do not need to export a wrong but compatible version
3394 // for the future.
3395 if(bControlA
3396 && aControlA.equal(aControlB)
3397 && aControlA.equal(aRetval.getB2DPoint(aRetval.count() - 1)))
3398 {
3399 bControlA = false;
3400 }
3401
3402 if(bControlA)
3403 {
3404 // add bezier edge
3405 aRetval.appendBezierSegment(aControlA, aControlB, aNewCoordinatePair);
3406 }
3407 else
3408 {
3409 // add edge
3410 aRetval.append(aNewCoordinatePair);
3411 }
3412 }
3413
3414 // #i72807# API import uses old line start/end-equal definition for closed,
3415 // so we need to correct this to closed state here
3416 checkClosed(aRetval);
3417 }
3418
3419 return aRetval;
3420 }
3421
3422 void B2DPolygonToUnoPolygonBezierCoords(
3423 const B2DPolygon& rPolygon,
3424 css::drawing::PointSequence& rPointSequenceRetval,
3425 css::drawing::FlagSequence& rFlagSequenceRetval)
3426 {
3427 const sal_uInt32 nPointCount(rPolygon.count());
3428
3429 if(nPointCount)
3430 {
3431 const bool bCurve(rPolygon.areControlPointsUsed());
3432 const bool bClosed(rPolygon.isClosed());
3433
3434 if(bCurve)
3435 {
3436 // calculate target point count
3437 const sal_uInt32 nLoopCount(bClosed ? nPointCount : nPointCount - 1);
3438
3439 if(nLoopCount)
3440 {
3441 // prepare target data. The real needed number of target points (and flags)
3442 // could only be calculated by using two loops, so use dynamic memory
3443 std::vector< css::awt::Point > aCollectPoints;
3444 std::vector< css::drawing::PolygonFlags > aCollectFlags;
3445
3446 // reserve maximum creatable points
3447 const sal_uInt32 nMaxTargetCount((nLoopCount * 3) + 1);
3448 aCollectPoints.reserve(nMaxTargetCount);
3449 aCollectFlags.reserve(nMaxTargetCount);
3450
3451 // prepare current bezier segment by setting start point
3452 B2DCubicBezier aBezierSegment;
3453 aBezierSegment.setStartPoint(rPolygon.getB2DPoint(0));
3454
3455 for(sal_uInt32 a(0); a < nLoopCount; a++)
3456 {
3457 // add current point (always) and remember StartPointIndex for evtl. later corrections
3458 const sal_uInt32 nStartPointIndex(aCollectPoints.size());
3459 aCollectPoints.emplace_back(
3460 fround(aBezierSegment.getStartPoint().getX()),
3461 fround(aBezierSegment.getStartPoint().getY()));
3462 aCollectFlags.push_back(css::drawing::PolygonFlags_NORMAL);
3463
3464 // prepare next segment
3465 const sal_uInt32 nNextIndex((a + 1) % nPointCount);
3466 aBezierSegment.setEndPoint(rPolygon.getB2DPoint(nNextIndex));
3467 aBezierSegment.setControlPointA(rPolygon.getNextControlPoint(a));
3468 aBezierSegment.setControlPointB(rPolygon.getPrevControlPoint(nNextIndex));
3469
3470 if(aBezierSegment.isBezier())
3471 {
3472 // if bezier is used, add always two control points due to the old schema
3473 aCollectPoints.emplace_back(
3474 fround(aBezierSegment.getControlPointA().getX()),
3475 fround(aBezierSegment.getControlPointA().getY()));
3476 aCollectFlags.push_back(css::drawing::PolygonFlags_CONTROL);
3477
3478 aCollectPoints.emplace_back(
3479 fround(aBezierSegment.getControlPointB().getX()),
3480 fround(aBezierSegment.getControlPointB().getY()));
3481 aCollectFlags.push_back(css::drawing::PolygonFlags_CONTROL);
3482 }
3483
3484 // test continuity with previous control point to set flag value
3485 if(aBezierSegment.getControlPointA() != aBezierSegment.getStartPoint() && (bClosed || a))
3486 {
3487 const B2VectorContinuity eCont(rPolygon.getContinuityInPoint(a));
3488
3489 if(eCont == B2VectorContinuity::C1)
3490 {
3491 aCollectFlags[nStartPointIndex] = css::drawing::PolygonFlags_SMOOTH;
3492 }
3493 else if(eCont == B2VectorContinuity::C2)
3494 {
3495 aCollectFlags[nStartPointIndex] = css::drawing::PolygonFlags_SYMMETRIC;
3496 }
3497 }
3498
3499 // prepare next loop
3500 aBezierSegment.setStartPoint(aBezierSegment.getEndPoint());
3501 }
3502
3503 if(bClosed)
3504 {
3505 // add first point again as closing point due to old definition
3506 aCollectPoints.push_back(aCollectPoints[0]);
3507 aCollectFlags.push_back(css::drawing::PolygonFlags_NORMAL);
3508 }
3509 else
3510 {
3511 // add last point as closing point
3512 const B2DPoint aClosingPoint(rPolygon.getB2DPoint(nPointCount - 1));
3513 aCollectPoints.emplace_back(
3514 fround(aClosingPoint.getX()),
3515 fround(aClosingPoint.getY()));
3516 aCollectFlags.push_back(css::drawing::PolygonFlags_NORMAL);
3517 }
3518
3519 // copy collected data to target arrays
3520 const sal_uInt32 nTargetCount(aCollectPoints.size());
3521 OSL_ENSURE(nTargetCount == aCollectFlags.size(), "Unequal Point and Flag count (!)")do { if (true && (!(nTargetCount == aCollectFlags.size
()))) { sal_detail_logFormat((SAL_DETAIL_LOG_LEVEL_WARN), ("legacy.osl"
), ("/home/maarten/src/libreoffice/core/basegfx/source/polygon/b2dpolygontools.cxx"
":" "3521" ": "), "%s", "Unequal Point and Flag count (!)");
} } while (false)
;
3522
3523 rPointSequenceRetval.realloc(static_cast<sal_Int32>(nTargetCount));
3524 rFlagSequenceRetval.realloc(static_cast<sal_Int32>(nTargetCount));
3525 css::awt::Point* pPointSequence = rPointSequenceRetval.getArray();
3526 css::drawing::PolygonFlags* pFlagSequence = rFlagSequenceRetval.getArray();
3527
3528 for(sal_uInt32 a(0); a < nTargetCount; a++)
3529 {
3530 *pPointSequence = aCollectPoints[a];
3531 *pFlagSequence = aCollectFlags[a];
3532 pPointSequence++;
3533 pFlagSequence++;
3534 }
3535 }
3536 }
3537 else
3538 {
3539 // straightforward point list creation
3540 const sal_uInt32 nTargetCount(nPointCount + (bClosed ? 1 : 0));
3541
3542 rPointSequenceRetval.realloc(static_cast<sal_Int32>(nTargetCount));
3543 rFlagSequenceRetval.realloc(static_cast<sal_Int32>(nTargetCount));
3544
3545 css::awt::Point* pPointSequence = rPointSequenceRetval.getArray();
3546 css::drawing::PolygonFlags* pFlagSequence = rFlagSequenceRetval.getArray();
3547
3548 for(sal_uInt32 a(0); a < nPointCount; a++)
3549 {
3550 const B2DPoint aB2DPoint(rPolygon.getB2DPoint(a));
3551 const css::awt::Point aAPIPoint(
3552 fround(aB2DPoint.getX()),
3553 fround(aB2DPoint.getY()));
3554
3555 *pPointSequence = aAPIPoint;
3556 *pFlagSequence = css::drawing::PolygonFlags_NORMAL;
3557 pPointSequence++;
3558 pFlagSequence++;
3559 }
3560
3561 if(bClosed)
3562 {
3563 // add first point as closing point
3564 *pPointSequence = *rPointSequenceRetval.getConstArray();
3565 *pFlagSequence = css::drawing::PolygonFlags_NORMAL;
3566 }
3567 }
3568 }
3569 else
3570 {
3571 rPointSequenceRetval.realloc(0);
3572 rFlagSequenceRetval.realloc(0);
3573 }
3574 }
3575
3576} // end of namespace
3577
3578/* vim:set shiftwidth=4 softtabstop=4 expandtab: */