LibreOffice Module basegfx (master) 1
b2dlinegeometry.cxx
Go to the documentation of this file.
1/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2/*
3 * This file is part of the LibreOffice project.
4 *
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 *
9 * This file incorporates work covered by the following license notice:
10 *
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
18 */
19
20#include <osl/diagnose.h>
21#include <sal/log.hxx>
31#include <com/sun/star/drawing/LineCap.hpp>
34
35namespace basegfx::utils
36{
38 const B2DPolygon& rCandidate,
39 const B2DPolyPolygon& rArrow,
40 bool bStart,
41 double fWidth,
42 double fCandidateLength,
43 double fDockingPosition, // 0->top, 1->bottom
44 double* pConsumedLength,
45 double fShift)
46 {
47 B2DPolyPolygon aRetval;
48 assert((rCandidate.count() > 1) && "createAreaGeometryForLineStartEnd: Line polygon has too few points");
49 assert((rArrow.count() > 0) && "createAreaGeometryForLineStartEnd: Empty arrow utils::PolyPolygon");
50 assert((fWidth > 0.0) && "createAreaGeometryForLineStartEnd: Width too small");
51 assert((fDockingPosition >= 0.0 && fDockingPosition <= 1.0) &&
52 "createAreaGeometryForLineStartEnd: fDockingPosition out of range [0.0 .. 1.0]");
53
54 if(fWidth < 0.0)
55 {
56 fWidth = -fWidth;
57 }
58
59 if(rCandidate.count() > 1 && rArrow.count() && !fTools::equalZero(fWidth))
60 {
61 if(fDockingPosition < 0.0)
62 {
63 fDockingPosition = 0.0;
64 }
65 else if(fDockingPosition > 1.0)
66 {
67 fDockingPosition = 1.0;
68 }
69
70 // init return value from arrow
71 aRetval.append(rArrow);
72
73 // get size of the arrow
74 const B2DRange aArrowSize(getRange(rArrow));
75
76 // build ArrowTransform; center in X, align with axis in Y
78 -aArrowSize.getCenter().getX(), -aArrowSize.getMinimum().getY()));
79
80 // scale to target size
81 const double fArrowScale(fWidth / (aArrowSize.getWidth()));
82 aArrowTransform.scale(fArrowScale, fArrowScale);
83
84 // get arrow size in Y
85 B2DPoint aUpperCenter(aArrowSize.getCenter().getX(), aArrowSize.getMaximum().getY());
86 aUpperCenter *= aArrowTransform;
87 const double fArrowYLength(B2DVector(aUpperCenter).getLength());
88
89 // move arrow to have docking position centered
90 aArrowTransform.translate(0.0, -fArrowYLength * fDockingPosition + fShift);
91
92 // prepare polygon length
93 if(fTools::equalZero(fCandidateLength))
94 {
95 fCandidateLength = getLength(rCandidate);
96 }
97
98 // get the polygon vector we want to plant this arrow on
99 const double fConsumedLength(fArrowYLength * (1.0 - fDockingPosition) - fShift);
100 const B2DVector aHead(rCandidate.getB2DPoint(bStart ? 0 : rCandidate.count() - 1));
101 const B2DVector aTail(getPositionAbsolute(rCandidate,
102 bStart ? fConsumedLength : fCandidateLength - fConsumedLength, fCandidateLength));
103
104 // from that vector, take the needed rotation and add rotate for arrow to transformation
105 const B2DVector aTargetDirection(aHead - aTail);
106 const double fRotation(atan2(aTargetDirection.getY(), aTargetDirection.getX()) + M_PI_2);
107
108 // rotate around docking position
109 aArrowTransform.rotate(fRotation);
110
111 // move arrow docking position to polygon head
112 aArrowTransform.translate(aHead.getX(), aHead.getY());
113
114 // transform retval and close
115 aRetval.transform(aArrowTransform);
116 aRetval.setClosed(true);
117
118 // if pConsumedLength is asked for, fill it
119 if(pConsumedLength)
120 {
121 *pConsumedLength = fConsumedLength;
122 }
123 }
124
125 return aRetval;
126 }
127} // end of namespace
128
129namespace basegfx
130{
131 // anonymous namespace for local helpers
132 namespace
133 {
134 bool impIsSimpleEdge(const B2DCubicBezier& rCandidate, double fMaxCosQuad, double fMaxPartOfEdgeQuad)
135 {
136 // isBezier() is true, already tested by caller
137 const B2DVector aEdge(rCandidate.getEndPoint() - rCandidate.getStartPoint());
138
139 if(aEdge.equalZero())
140 {
141 // start and end point the same, but control vectors used -> balloon curve loop
142 // is not a simple edge
143 return false;
144 }
145
146 // get tangentA and scalar with edge
147 const B2DVector aTangentA(rCandidate.getTangent(0.0));
148 const double fScalarAE(aEdge.scalar(aTangentA));
149
150 if(fTools::lessOrEqual(fScalarAE, 0.0))
151 {
152 // angle between TangentA and Edge is bigger or equal 90 degrees
153 return false;
154 }
155
156 // get self-scalars for E and A
157 const double fScalarE(aEdge.scalar(aEdge));
158 const double fScalarA(aTangentA.scalar(aTangentA));
159 const double fLengthCompareE(fScalarE * fMaxPartOfEdgeQuad);
160
161 if(fTools::moreOrEqual(fScalarA, fLengthCompareE))
162 {
163 // length of TangentA is more than fMaxPartOfEdge of length of edge
164 return false;
165 }
166
167 if(fTools::lessOrEqual(fScalarAE * fScalarAE, fScalarA * fScalarE * fMaxCosQuad))
168 {
169 // angle between TangentA and Edge is bigger or equal angle defined by fMaxCos
170 return false;
171 }
172
173 // get tangentB and scalar with edge
174 const B2DVector aTangentB(rCandidate.getTangent(1.0));
175 const double fScalarBE(aEdge.scalar(aTangentB));
176
177 if(fTools::lessOrEqual(fScalarBE, 0.0))
178 {
179 // angle between TangentB and Edge is bigger or equal 90 degrees
180 return false;
181 }
182
183 // get self-scalar for B
184 const double fScalarB(aTangentB.scalar(aTangentB));
185
186 if(fTools::moreOrEqual(fScalarB, fLengthCompareE))
187 {
188 // length of TangentB is more than fMaxPartOfEdge of length of edge
189 return false;
190 }
191
192 if(fTools::lessOrEqual(fScalarBE * fScalarBE, fScalarB * fScalarE * fMaxCosQuad))
193 {
194 // angle between TangentB and Edge is bigger or equal defined by fMaxCos
195 return false;
196 }
197
198 return true;
199 }
200
201 void impSubdivideToSimple(const B2DCubicBezier& rCandidate, B2DPolygon& rTarget, double fMaxCosQuad, double fMaxPartOfEdgeQuad, sal_uInt32 nMaxRecursionDepth)
202 {
203 if(!nMaxRecursionDepth || impIsSimpleEdge(rCandidate, fMaxCosQuad, fMaxPartOfEdgeQuad))
204 {
205 rTarget.appendBezierSegment(rCandidate.getControlPointA(), rCandidate.getControlPointB(), rCandidate.getEndPoint());
206 }
207 else
208 {
209 B2DCubicBezier aLeft, aRight;
210 rCandidate.split(0.5, &aLeft, &aRight);
211
212 impSubdivideToSimple(aLeft, rTarget, fMaxCosQuad, fMaxPartOfEdgeQuad, nMaxRecursionDepth - 1);
213 impSubdivideToSimple(aRight, rTarget, fMaxCosQuad, fMaxPartOfEdgeQuad, nMaxRecursionDepth - 1);
214 }
215 }
216
217 B2DPolygon subdivideToSimple(const B2DPolygon& rCandidate, double fMaxCosQuad, double fMaxPartOfEdgeQuad)
218 {
219 const sal_uInt32 nPointCount(rCandidate.count());
220
221 if(rCandidate.areControlPointsUsed() && nPointCount)
222 {
223 const sal_uInt32 nEdgeCount(rCandidate.isClosed() ? nPointCount : nPointCount - 1);
224 B2DPolygon aRetval;
225 B2DCubicBezier aEdge;
226
227 // prepare edge for loop
228 aEdge.setStartPoint(rCandidate.getB2DPoint(0));
229 aRetval.append(aEdge.getStartPoint());
230
231 for(sal_uInt32 a(0); a < nEdgeCount; a++)
232 {
233 // fill B2DCubicBezier
234 const sal_uInt32 nNextIndex((a + 1) % nPointCount);
235 aEdge.setControlPointA(rCandidate.getNextControlPoint(a));
236 aEdge.setControlPointB(rCandidate.getPrevControlPoint(nNextIndex));
237 aEdge.setEndPoint(rCandidate.getB2DPoint(nNextIndex));
238
239 // get rid of unnecessary bezier segments
240 aEdge.testAndSolveTrivialBezier();
241
242 if(aEdge.isBezier())
243 {
244 // before splitting recursively with internal simple criteria, use
245 // ExtremumPosFinder to remove those
246 std::vector< double > aExtremumPositions;
247
248 aExtremumPositions.reserve(4);
249 aEdge.getAllExtremumPositions(aExtremumPositions);
250
251 const sal_uInt32 nCount(aExtremumPositions.size());
252
253 if(nCount)
254 {
255 if(nCount > 1)
256 {
257 // create order from left to right
258 std::sort(aExtremumPositions.begin(), aExtremumPositions.end());
259 }
260
261 for(sal_uInt32 b(0); b < nCount;)
262 {
263 // split aEdge at next split pos
264 B2DCubicBezier aLeft;
265 const double fSplitPos(aExtremumPositions[b++]);
266
267 aEdge.split(fSplitPos, &aLeft, &aEdge);
268 aLeft.testAndSolveTrivialBezier();
269
270 // consume left part
271 if(aLeft.isBezier())
272 {
273 impSubdivideToSimple(aLeft, aRetval, fMaxCosQuad, fMaxPartOfEdgeQuad, 6);
274 }
275 else
276 {
277 aRetval.append(aLeft.getEndPoint());
278 }
279
280 if(b < nCount)
281 {
282 // correct the remaining split positions to fit to shortened aEdge
283 const double fScaleFactor(1.0 / (1.0 - fSplitPos));
284
285 for(sal_uInt32 c(b); c < nCount; c++)
286 {
287 aExtremumPositions[c] = (aExtremumPositions[c] - fSplitPos) * fScaleFactor;
288 }
289 }
290 }
291
292 // test the shortened rest of aEdge
293 aEdge.testAndSolveTrivialBezier();
294
295 // consume right part
296 if(aEdge.isBezier())
297 {
298 impSubdivideToSimple(aEdge, aRetval, fMaxCosQuad, fMaxPartOfEdgeQuad, 6);
299 }
300 else
301 {
302 aRetval.append(aEdge.getEndPoint());
303 }
304 }
305 else
306 {
307 impSubdivideToSimple(aEdge, aRetval, fMaxCosQuad, fMaxPartOfEdgeQuad, 6);
308 }
309 }
310 else
311 {
312 // straight edge, add point
313 aRetval.append(aEdge.getEndPoint());
314 }
315
316 // prepare edge for next step
317 aEdge.setStartPoint(aEdge.getEndPoint());
318 }
319
320 // copy closed flag and check for double points
321 aRetval.setClosed(rCandidate.isClosed());
322 aRetval.removeDoublePoints();
323
324 return aRetval;
325 }
326 else
327 {
328 return rCandidate;
329 }
330 }
331
332 B2DPolygon createAreaGeometryForEdge(
333 const B2DCubicBezier& rEdge,
334 double fHalfLineWidth,
335 bool bStartRound,
336 bool bEndRound,
337 bool bStartSquare,
338 bool bEndSquare,
340 {
341 // create polygon for edge
342 // Unfortunately, while it would be geometrically correct to not add
343 // the in-between points EdgeEnd and EdgeStart, it leads to rounding
344 // errors when converting to integer polygon coordinates for painting
345 if(rEdge.isBezier())
346 {
347 // prepare target and data common for upper and lower
348 B2DPolygon aBezierPolygon;
349 const B2DVector aPureEdgeVector(rEdge.getEndPoint() - rEdge.getStartPoint());
350 const double fEdgeLength(aPureEdgeVector.getLength());
351 const bool bIsEdgeLengthZero(fTools::equalZero(fEdgeLength));
352 B2DVector aTangentA(rEdge.getTangent(0.0)); aTangentA.normalize();
353 B2DVector aTangentB(rEdge.getTangent(1.0)); aTangentB.normalize();
354 const B2DVector aNormalizedPerpendicularA(getPerpendicular(aTangentA));
355 const B2DVector aNormalizedPerpendicularB(getPerpendicular(aTangentB));
356
357 // create upper displacement vectors and check if they cut
358 const B2DVector aPerpendStartA(aNormalizedPerpendicularA * -fHalfLineWidth);
359 const B2DVector aPerpendEndA(aNormalizedPerpendicularB * -fHalfLineWidth);
360 double fCutA(0.0);
361 const CutFlagValue aCutA(utils::findCut(
362 rEdge.getStartPoint(), aPerpendStartA,
363 rEdge.getEndPoint(), aPerpendEndA,
364 CutFlagValue::ALL, &fCutA));
365 const bool bCutA(aCutA != CutFlagValue::NONE);
366
367 // create lower displacement vectors and check if they cut
368 const B2DVector aPerpendStartB(aNormalizedPerpendicularA * fHalfLineWidth);
369 const B2DVector aPerpendEndB(aNormalizedPerpendicularB * fHalfLineWidth);
370 double fCutB(0.0);
371 const CutFlagValue aCutB(utils::findCut(
372 rEdge.getEndPoint(), aPerpendEndB,
373 rEdge.getStartPoint(), aPerpendStartB,
374 CutFlagValue::ALL, &fCutB));
375 const bool bCutB(aCutB != CutFlagValue::NONE);
376
377 // check if cut happens
378 const bool bCut(bCutA || bCutB);
379 B2DPoint aCutPoint;
380
381 // create left edge
382 if(bStartRound || bStartSquare)
383 {
384 if(bStartRound)
385 {
387
388 aStartPolygon.transform(
390 fHalfLineWidth, fHalfLineWidth,
391 0.0,
392 atan2(aTangentA.getY(), aTangentA.getX()) + M_PI_2,
393 rEdge.getStartPoint().getX(), rEdge.getStartPoint().getY()));
394 aBezierPolygon.append(aStartPolygon);
395 }
396 else // bStartSquare
397 {
398 const basegfx::B2DPoint aStart(rEdge.getStartPoint() - (aTangentA * fHalfLineWidth));
399
400 if(bCutB)
401 {
402 aBezierPolygon.append(rEdge.getStartPoint() + aPerpendStartB);
403 }
404
405 aBezierPolygon.append(aStart + aPerpendStartB);
406 aBezierPolygon.append(aStart + aPerpendStartA);
407
408 if(bCutA)
409 {
410 aBezierPolygon.append(rEdge.getStartPoint() + aPerpendStartA);
411 }
412 }
413 }
414 else
415 {
416 // append original in-between point
417 aBezierPolygon.append(rEdge.getStartPoint());
418 }
419
420 // create upper edge.
421 {
422 if(bCutA)
423 {
424 // calculate cut point and add
425 aCutPoint = rEdge.getStartPoint() + (aPerpendStartA * fCutA);
426 aBezierPolygon.append(aCutPoint);
427 }
428 else
429 {
430 // create scaled bezier segment
431 const B2DPoint aStart(rEdge.getStartPoint() + aPerpendStartA);
432 const B2DPoint aEnd(rEdge.getEndPoint() + aPerpendEndA);
433 const B2DVector aEdge(aEnd - aStart);
434 const double fLength(aEdge.getLength());
435 const double fScale(bIsEdgeLengthZero ? 1.0 : fLength / fEdgeLength);
436 const B2DVector fRelNext(rEdge.getControlPointA() - rEdge.getStartPoint());
437 const B2DVector fRelPrev(rEdge.getControlPointB() - rEdge.getEndPoint());
438
439 aBezierPolygon.append(aStart);
440 aBezierPolygon.appendBezierSegment(aStart + (fRelNext * fScale), aEnd + (fRelPrev * fScale), aEnd);
441 }
442 }
443
444 // create right edge
445 if(bEndRound || bEndSquare)
446 {
447 if(bEndRound)
448 {
450
451 aEndPolygon.transform(
453 fHalfLineWidth, fHalfLineWidth,
454 0.0,
455 atan2(aTangentB.getY(), aTangentB.getX()) - M_PI_2,
456 rEdge.getEndPoint().getX(), rEdge.getEndPoint().getY()));
457 aBezierPolygon.append(aEndPolygon);
458 }
459 else // bEndSquare
460 {
461 const basegfx::B2DPoint aEnd(rEdge.getEndPoint() + (aTangentB * fHalfLineWidth));
462
463 if(bCutA)
464 {
465 aBezierPolygon.append(rEdge.getEndPoint() + aPerpendEndA);
466 }
467
468 aBezierPolygon.append(aEnd + aPerpendEndA);
469 aBezierPolygon.append(aEnd + aPerpendEndB);
470
471 if(bCutB)
472 {
473 aBezierPolygon.append(rEdge.getEndPoint() + aPerpendEndB);
474 }
475 }
476 }
477 else
478 {
479 // append original in-between point
480 aBezierPolygon.append(rEdge.getEndPoint());
481 }
482
483 // create lower edge.
484 {
485 if(bCutB)
486 {
487 // calculate cut point and add
488 aCutPoint = rEdge.getEndPoint() + (aPerpendEndB * fCutB);
489 aBezierPolygon.append(aCutPoint);
490 }
491 else
492 {
493 // create scaled bezier segment
494 const B2DPoint aStart(rEdge.getEndPoint() + aPerpendEndB);
495 const B2DPoint aEnd(rEdge.getStartPoint() + aPerpendStartB);
496 const B2DVector aEdge(aEnd - aStart);
497 const double fLength(aEdge.getLength());
498 const double fScale(bIsEdgeLengthZero ? 1.0 : fLength / fEdgeLength);
499 const B2DVector fRelNext(rEdge.getControlPointB() - rEdge.getEndPoint());
500 const B2DVector fRelPrev(rEdge.getControlPointA() - rEdge.getStartPoint());
501
502 aBezierPolygon.append(aStart);
503 aBezierPolygon.appendBezierSegment(aStart + (fRelNext * fScale), aEnd + (fRelPrev * fScale), aEnd);
504 }
505 }
506
507 // close
508 aBezierPolygon.setClosed(true);
509
510 if(bStartRound || bEndRound)
511 {
512 // double points possible when round caps are used at start or end
513 aBezierPolygon.removeDoublePoints();
514 }
515
516 if(bCut && ((bStartRound || bStartSquare) && (bEndRound || bEndSquare)))
517 {
518 // When cut exists and both ends are extended with caps, a self-intersecting polygon
519 // is created; one cut point is known, but there is a 2nd one in the caps geometry.
520 // Solve by using tooling.
521 // Remark: This nearly never happens due to curve preparations to extreme points
522 // and maximum angle turning, but I constructed a test case and checked that it is
523 // working properly.
524 const B2DPolyPolygon aTemp(utils::solveCrossovers(aBezierPolygon));
525 const sal_uInt32 nTempCount(aTemp.count());
526
527 if(nTempCount)
528 {
529 if(nTempCount > 1)
530 {
531 // as expected, multiple polygons (with same orientation). Remove
532 // the one which contains aCutPoint, or better take the one without
533 for (sal_uInt32 a(0); a < nTempCount; a++)
534 {
535 aBezierPolygon = aTemp.getB2DPolygon(a);
536
537 const sal_uInt32 nCandCount(aBezierPolygon.count());
538
539 for(sal_uInt32 b(0); b < nCandCount; b++)
540 {
541 if(aCutPoint.equal(aBezierPolygon.getB2DPoint(b)))
542 {
543 aBezierPolygon.clear();
544 break;
545 }
546 }
547
548 if(aBezierPolygon.count())
549 {
550 break;
551 }
552 }
553
554 OSL_ENSURE(aBezierPolygon.count(), "Error in line geometry creation, could not solve self-intersection (!)");
555 }
556 else
557 {
558 // none found, use result
559 aBezierPolygon = aTemp.getB2DPolygon(0);
560 }
561 }
562 else
563 {
564 OSL_ENSURE(false, "Error in line geometry creation, could not solve self-intersection (!)");
565 }
566 }
567
568 if(nullptr != pTriangles)
569 {
572 aBezierPolygon));
573 pTriangles->insert(pTriangles->end(), aResult.begin(), aResult.end());
574 aBezierPolygon.clear();
575 }
576
577 // return
578 return aBezierPolygon;
579 }
580 else
581 {
582 // Get start and end point, create tangent and set to needed length
583 B2DVector aTangent(rEdge.getEndPoint() - rEdge.getStartPoint());
584 aTangent.setLength(fHalfLineWidth);
585
586 // prepare return value
587 B2DPolygon aEdgePolygon;
588
589 // buffered angle
590 double fAngle(0.0);
591 bool bAngle(false);
592
593 // buffered perpendicular
594 B2DVector aPerpend;
595 bool bPerpend(false);
596
597 // create left vertical
598 if(bStartRound)
599 {
600 aEdgePolygon = utils::createHalfUnitCircle();
601 fAngle = atan2(aTangent.getY(), aTangent.getX());
602 bAngle = true;
603 aEdgePolygon.transform(
605 fHalfLineWidth, fHalfLineWidth,
606 0.0,
607 fAngle + M_PI_2,
608 rEdge.getStartPoint().getX(), rEdge.getStartPoint().getY()));
609 }
610 else
611 {
612 aPerpend.setX(-aTangent.getY());
613 aPerpend.setY(aTangent.getX());
614 bPerpend = true;
615
616 if(bStartSquare)
617 {
618 const basegfx::B2DPoint aStart(rEdge.getStartPoint() - aTangent);
619
620 aEdgePolygon.append(aStart + aPerpend);
621 aEdgePolygon.append(aStart - aPerpend);
622 }
623 else
624 {
625 aEdgePolygon.append(rEdge.getStartPoint() + aPerpend);
626 aEdgePolygon.append(rEdge.getStartPoint()); // keep the in-between point for numerical reasons
627 aEdgePolygon.append(rEdge.getStartPoint() - aPerpend);
628 }
629 }
630
631 // create right vertical
632 if(bEndRound)
633 {
635
636 if(!bAngle)
637 {
638 fAngle = atan2(aTangent.getY(), aTangent.getX());
639 }
640
641 aEndPolygon.transform(
643 fHalfLineWidth, fHalfLineWidth,
644 0.0,
645 fAngle - M_PI_2,
646 rEdge.getEndPoint().getX(), rEdge.getEndPoint().getY()));
647 aEdgePolygon.append(aEndPolygon);
648 }
649 else
650 {
651 if(!bPerpend)
652 {
653 aPerpend.setX(-aTangent.getY());
654 aPerpend.setY(aTangent.getX());
655 }
656
657 if(bEndSquare)
658 {
659 const basegfx::B2DPoint aEnd(rEdge.getEndPoint() + aTangent);
660
661 aEdgePolygon.append(aEnd - aPerpend);
662 aEdgePolygon.append(aEnd + aPerpend);
663 }
664 else
665 {
666 aEdgePolygon.append(rEdge.getEndPoint() - aPerpend);
667 aEdgePolygon.append(rEdge.getEndPoint()); // keep the in-between point for numerical reasons
668 aEdgePolygon.append(rEdge.getEndPoint() + aPerpend);
669 }
670 }
671
672 // close and return
673 aEdgePolygon.setClosed(true);
674
675 if(nullptr != pTriangles)
676 {
679 aEdgePolygon));
680 pTriangles->insert(pTriangles->end(), aResult.begin(), aResult.end());
681 aEdgePolygon.clear();
682 }
683
684 return aEdgePolygon;
685 }
686 }
687
688 B2DPolygon createAreaGeometryForJoin(
689 const B2DVector& rTangentPrev,
690 const B2DVector& rTangentEdge,
691 const B2DVector& rPerpendPrev,
692 const B2DVector& rPerpendEdge,
693 const B2DPoint& rPoint,
694 double fHalfLineWidth,
695 B2DLineJoin eJoin,
696 double fMiterMinimumAngle,
698 {
699 SAL_WARN_IF(fHalfLineWidth <= 0.0,"basegfx","createAreaGeometryForJoin: LineWidth too small (!)");
700 assert((eJoin != B2DLineJoin::NONE) && "createAreaGeometryForJoin: B2DLineJoin::NONE not allowed (!)");
701
702 // LineJoin from tangent rPerpendPrev to tangent rPerpendEdge in rPoint
703 B2DPolygon aEdgePolygon;
704 const B2DPoint aStartPoint(rPoint + rPerpendPrev);
705 const B2DPoint aEndPoint(rPoint + rPerpendEdge);
706
707 // test if for Miter, the angle is too small and the fallback
708 // to bevel needs to be used
709 if(eJoin == B2DLineJoin::Miter)
710 {
711 const double fAngle(fabs(rPerpendPrev.angle(rPerpendEdge)));
712
713 if((M_PI - fAngle) < fMiterMinimumAngle)
714 {
715 // fallback to bevel
716 eJoin = B2DLineJoin::Bevel;
717 }
718 }
719
720 switch(eJoin)
721 {
722 case B2DLineJoin::Miter :
723 {
724 if(nullptr != pTriangles)
725 {
726 pTriangles->emplace_back(
727 aEndPoint,
728 rPoint,
729 aStartPoint);
730 }
731 else
732 {
733 aEdgePolygon.append(aEndPoint);
734 aEdgePolygon.append(rPoint);
735 aEdgePolygon.append(aStartPoint);
736 }
737
738 // Look for the cut point between start point along rTangentPrev and
739 // end point along rTangentEdge. -rTangentEdge should be used, but since
740 // the cut value is used for interpolating along the first edge, the negation
741 // is not needed since the same fCut will be found on the first edge.
742 // If it exists, insert it to complete the mitered fill polygon.
743 double fCutPos(0.0);
744 utils::findCut(aStartPoint, rTangentPrev, aEndPoint, rTangentEdge, CutFlagValue::ALL, &fCutPos);
745
746 if(fCutPos != 0.0)
747 {
748 const B2DPoint aCutPoint(aStartPoint + (rTangentPrev * fCutPos));
749
750 if(nullptr != pTriangles)
751 {
752 pTriangles->emplace_back(
753 aStartPoint,
754 aCutPoint,
755 aEndPoint);
756 }
757 else
758 {
759 aEdgePolygon.append(aCutPoint);
760 }
761 }
762
763 break;
764 }
765 case B2DLineJoin::Round :
766 {
767 // use tooling to add needed EllipseSegment
768 double fAngleStart(atan2(rPerpendPrev.getY(), rPerpendPrev.getX()));
769 double fAngleEnd(atan2(rPerpendEdge.getY(), rPerpendEdge.getX()));
770
771 // atan2 results are [-PI .. PI], consolidate to [0.0 .. 2PI]
772 if(fAngleStart < 0.0)
773 {
774 fAngleStart += 2 * M_PI;
775 }
776
777 if(fAngleEnd < 0.0)
778 {
779 fAngleEnd += 2 * M_PI;
780 }
781
782 const B2DPolygon aBow(utils::createPolygonFromEllipseSegment(rPoint, fHalfLineWidth, fHalfLineWidth, fAngleStart, fAngleEnd));
783
784 if(aBow.count() > 1)
785 {
786 if(nullptr != pTriangles)
787 {
788 for(sal_uInt32 a(0); a < aBow.count() - 1; a++)
789 {
790 pTriangles->emplace_back(
791 0 == a ? aStartPoint : aBow.getB2DPoint(a),
792 rPoint,
793 aBow.count() - 1 == a + 1 ? aEndPoint : aBow.getB2DPoint(a + 1));
794 }
795 }
796 else
797 {
798 // #i101491#
799 // use the original start/end positions; the ones from bow creation may be numerically
800 // different due to their different creation. To guarantee good merging quality with edges
801 // and edge roundings (and to reduce point count)
802 aEdgePolygon = aBow;
803 aEdgePolygon.setB2DPoint(0, aStartPoint);
804 aEdgePolygon.setB2DPoint(aEdgePolygon.count() - 1, aEndPoint);
805 aEdgePolygon.append(rPoint);
806 }
807
808 break;
809 }
810 else
811 {
812 [[fallthrough]]; // wanted fall-through to default
813 }
814 }
815 default: // B2DLineJoin::Bevel
816 {
817 if(nullptr != pTriangles)
818 {
819 pTriangles->emplace_back(
820 aEndPoint,
821 rPoint,
822 aStartPoint);
823 }
824 else
825 {
826 aEdgePolygon.append(aEndPoint);
827 aEdgePolygon.append(rPoint);
828 aEdgePolygon.append(aStartPoint);
829 }
830
831 break;
832 }
833 }
834
835 // create last polygon part for edge
836 aEdgePolygon.setClosed(true);
837
838 return aEdgePolygon;
839 }
840 } // end of anonymous namespace
841
842 namespace utils
843 {
845 const B2DPolygon& rCandidate,
846 double fHalfLineWidth,
847 B2DLineJoin eJoin,
848 css::drawing::LineCap eCap,
849 double fMaxAllowedAngle,
850 double fMaxPartOfEdge,
851 double fMiterMinimumAngle)
852 {
853 if(fMaxAllowedAngle > M_PI_2)
854 {
855 fMaxAllowedAngle = M_PI_2;
856 }
857 else if(fMaxAllowedAngle < 0.01 * M_PI_2)
858 {
859 fMaxAllowedAngle = 0.01 * M_PI_2;
860 }
861
862 if(fMaxPartOfEdge > 1.0)
863 {
864 fMaxPartOfEdge = 1.0;
865 }
866 else if(fMaxPartOfEdge < 0.01)
867 {
868 fMaxPartOfEdge = 0.01;
869 }
870
871 if(fMiterMinimumAngle > M_PI)
872 {
873 fMiterMinimumAngle = M_PI;
874 }
875 else if(fMiterMinimumAngle < 0.01 * M_PI)
876 {
877 fMiterMinimumAngle = 0.01 * M_PI;
878 }
879
880 B2DPolygon aCandidate(rCandidate);
881 const double fMaxCos(cos(fMaxAllowedAngle));
882
883 aCandidate.removeDoublePoints();
884 aCandidate = subdivideToSimple(aCandidate, fMaxCos * fMaxCos, fMaxPartOfEdge * fMaxPartOfEdge);
885
886 const sal_uInt32 nPointCount(aCandidate.count());
887
888 if(nPointCount)
889 {
890 B2DPolyPolygon aRetval;
891 const bool bIsClosed(aCandidate.isClosed());
892 const sal_uInt32 nEdgeCount(bIsClosed ? nPointCount : nPointCount - 1);
893 const bool bLineCap(!bIsClosed && eCap != css::drawing::LineCap_BUTT);
894
895 if(nEdgeCount)
896 {
897 B2DCubicBezier aEdge;
898 B2DCubicBezier aPrev;
899
900 const bool bEventuallyCreateLineJoin(eJoin != B2DLineJoin::NONE);
901 // prepare edge
902 aEdge.setStartPoint(aCandidate.getB2DPoint(0));
903
904 if(bIsClosed && bEventuallyCreateLineJoin)
905 {
906 // prepare previous edge
907 const sal_uInt32 nPrevIndex(nPointCount - 1);
908 aPrev.setStartPoint(aCandidate.getB2DPoint(nPrevIndex));
909 aPrev.setControlPointA(aCandidate.getNextControlPoint(nPrevIndex));
910 aPrev.setControlPointB(aCandidate.getPrevControlPoint(0));
911 aPrev.setEndPoint(aEdge.getStartPoint());
912 }
913
914 for(sal_uInt32 a(0); a < nEdgeCount; a++)
915 {
916 // fill current Edge
917 const sal_uInt32 nNextIndex((a + 1) % nPointCount);
918 aEdge.setControlPointA(aCandidate.getNextControlPoint(a));
919 aEdge.setControlPointB(aCandidate.getPrevControlPoint(nNextIndex));
920 aEdge.setEndPoint(aCandidate.getB2DPoint(nNextIndex));
921
922 // check and create linejoin
923 if(bEventuallyCreateLineJoin && (bIsClosed || a != 0))
924 {
925 B2DVector aTangentPrev(aPrev.getTangent(1.0)); aTangentPrev.normalize();
926 B2DVector aTangentEdge(aEdge.getTangent(0.0)); aTangentEdge.normalize();
927 B2VectorOrientation aOrientation(getOrientation(aTangentPrev, aTangentEdge));
928
929 if(aOrientation == B2VectorOrientation::Neutral)
930 {
931 // they are parallel or empty; if they are both not zero and point
932 // in opposite direction, a half-circle is needed
933 if(!aTangentPrev.equalZero() && !aTangentEdge.equalZero())
934 {
935 const double fAngle(fabs(aTangentPrev.angle(aTangentEdge)));
936
937 if(fTools::equal(fAngle, M_PI))
938 {
939 // for half-circle production, fallback to positive
940 // orientation
941 aOrientation = B2VectorOrientation::Positive;
942 }
943 }
944 }
945
946 if(aOrientation == B2VectorOrientation::Positive)
947 {
948 const B2DVector aPerpendPrev(getPerpendicular(aTangentPrev) * -fHalfLineWidth);
949 const B2DVector aPerpendEdge(getPerpendicular(aTangentEdge) * -fHalfLineWidth);
950
951 aRetval.append(
952 createAreaGeometryForJoin(
953 aTangentPrev,
954 aTangentEdge,
955 aPerpendPrev,
956 aPerpendEdge,
957 aEdge.getStartPoint(),
958 fHalfLineWidth,
959 eJoin,
960 fMiterMinimumAngle,
961 nullptr));
962 }
963 else if(aOrientation == B2VectorOrientation::Negative)
964 {
965 const B2DVector aPerpendPrev(getPerpendicular(aTangentPrev) * fHalfLineWidth);
966 const B2DVector aPerpendEdge(getPerpendicular(aTangentEdge) * fHalfLineWidth);
967
968 aRetval.append(
969 createAreaGeometryForJoin(
970 aTangentEdge,
971 aTangentPrev,
972 aPerpendEdge,
973 aPerpendPrev,
974 aEdge.getStartPoint(),
975 fHalfLineWidth,
976 eJoin,
977 fMiterMinimumAngle,
978 nullptr));
979 }
980 }
981
982 // create geometry for edge
983 const bool bLast(a + 1 == nEdgeCount);
984
985 if(bLineCap)
986 {
987 const bool bFirst(!a);
988
989 aRetval.append(
990 createAreaGeometryForEdge(
991 aEdge,
992 fHalfLineWidth,
993 bFirst && eCap == css::drawing::LineCap_ROUND,
994 bLast && eCap == css::drawing::LineCap_ROUND,
995 bFirst && eCap == css::drawing::LineCap_SQUARE,
996 bLast && eCap == css::drawing::LineCap_SQUARE,
997 nullptr));
998 }
999 else
1000 {
1001 aRetval.append(
1002 createAreaGeometryForEdge(
1003 aEdge,
1004 fHalfLineWidth,
1005 false,
1006 false,
1007 false,
1008 false,
1009 nullptr));
1010 }
1011
1012 // prepare next step
1013 if(!bLast)
1014 {
1015 if(bEventuallyCreateLineJoin)
1016 {
1017 aPrev = aEdge;
1018 }
1019
1020 aEdge.setStartPoint(aEdge.getEndPoint());
1021 }
1022 }
1023 }
1024 else
1025 {
1026 // point count, but no edge count -> single point
1027 const basegfx::B2DPolygon aCircle(
1029 aCandidate.getB2DPoint(0),
1030 fHalfLineWidth));
1031
1032 aRetval.append(aCircle);
1033 }
1034
1035 return aRetval;
1036 }
1037 else
1038 {
1039 return B2DPolyPolygon(rCandidate);
1040 }
1041 }
1042 } // end of namespace utils
1043} // end of namespace basegfx
1044
1045/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
CutFlagValue
void setStartPoint(const B2DPoint &rValue)
const B2DPoint & getStartPoint() const
void setControlPointA(const B2DPoint &rValue)
void setEndPoint(const B2DPoint &rValue)
void setControlPointB(const B2DPoint &rValue)
const B2DPoint & getEndPoint() const
B2DVector getTangent(double t) const
get the tangent in point t
void rotate(double fRadiant)
void translate(double fX, double fY)
void scale(double fX, double fY)
Base Point class with two double values.
Definition: b2dpoint.hxx:42
void append(const B2DPolygon &rPolygon, sal_uInt32 nCount=1)
void transform(const basegfx::B2DHomMatrix &rMatrix)
sal_uInt32 count() const
bool isClosed() const
closed state interface
basegfx::B2DPoint const & getB2DPoint(sal_uInt32 nIndex) const
Coordinate interface.
basegfx::B2DPoint getPrevControlPoint(sal_uInt32 nIndex) const
Basic ControlPoint interface.
void removeDoublePoints()
remove double points, at the begin/end and follow-ups, too
sal_uInt32 count() const
member count
basegfx::B2DPoint getNextControlPoint(sal_uInt32 nIndex) const
A two-dimensional interval over doubles.
Definition: b2drange.hxx:54
B2DPoint getMaximum() const
get upper bound of the set. returns arbitrary values for empty sets.
Definition: b2drange.hxx:89
B2DPoint getCenter() const
return center point of set. returns (0,0) for empty sets.
Definition: b2drange.hxx:107
B2DPoint getMinimum() const
get lower bound of the set. returns arbitrary values for empty sets.
Definition: b2drange.hxx:80
Base Point class with two double values.
Definition: b2dvector.hxx:40
double angle(const B2DVector &rVec) const
Calculate the Angle with another 2D Vector.
Definition: b2dvector.cxx:68
B2DVector & normalize()
Normalize this 2D Vector.
Definition: b2dvector.cxx:26
TYPE getWidth() const
return difference between upper and lower X value. returns 0 for empty sets.
Definition: Range2D.hxx:106
bool equalZero() const
Definition: Tuple2D.hxx:95
TYPE getX() const
Get X-Coordinate of 2D Tuple.
Definition: Tuple2D.hxx:63
TYPE getY() const
Get Y-Coordinate of 2D Tuple.
Definition: Tuple2D.hxx:66
int nCount
FilterGroup & rTarget
uno_Any a
#define SAL_WARN_IF(condition, area, stream)
bool lessOrEqual(const T &rfValA, const T &rfValB)
Definition: ftools.hxx:188
bool equalZero(const T &rfVal)
Compare against small value.
Definition: ftools.hxx:156
bool equal(T const &rfValA, T const &rfValB)
Definition: ftools.hxx:169
bool moreOrEqual(const T &rfValA, const T &rfValB)
Definition: ftools.hxx:200
::std::vector< B2DTriangle > B2DTriangleVector
B2DTriangleVector triangulate(const B2DPolygon &rCandidate)
B2DPolygon const & createHalfUnitCircle()
create half circle centered on (0,0) from [0 .. M_PI]
CutFlagValue findCut(const B2DPoint &rEdge1Start, const B2DVector &rEdge1Delta, const B2DPoint &rEdge2Start, const B2DVector &rEdge2Delta, CutFlagValue aCutFlags, double *pCut1, double *pCut2)
double getLength(const B2DPolygon &rCandidate)
get length of polygon
B2DPolyPolygon createAreaGeometry(const B2DPolygon &rCandidate, double fHalfLineWidth, B2DLineJoin eJoin, css::drawing::LineCap eCap, double fMaxAllowedAngle, double fMaxPartOfEdge, double fMiterMinimumAngle)
create filled polygon geometry for lines with a line width
B2DPoint getPositionAbsolute(const B2DPolygon &rCandidate, double fDistance, double fLength)
B2VectorOrientation getOrientation(const B2DPolygon &rCandidate)
B2DPolygon createPolygonFromCircle(const B2DPoint &rCenter, double fRadius)
Create a circle polygon with given radius.
B2DPolygon createPolygonFromEllipseSegment(const B2DPoint &rCenter, double fRadiusX, double fRadiusY, double fStart, double fEnd)
Create a unit ellipse polygon with the given angles, from start to end.
B2DPolyPolygon solveCrossovers(const B2DPolyPolygon &rCandidate, size_t *pPointLimit)
Solve all crossovers (aka self-intersections) in a polyPolygon.
B2DHomMatrix createTranslateB2DHomMatrix(double fTranslateX, double fTranslateY)
B2DHomMatrix createScaleShearXRotateTranslateB2DHomMatrix(double fScaleX, double fScaleY, double fShearX, double fRadiant, double fTranslateX, double fTranslateY)
Tooling methods for faster completely combined matrix creation when scale, shearX,...
B2DRange getRange(const B2DPolygon &rCandidate)
Get the range of a polygon.
B2DPolyPolygon createAreaGeometryForLineStartEnd(const B2DPolygon &rCandidate, const B2DPolyPolygon &rArrow, bool bStart, double fWidth, double fCandidateLength, double fDockingPosition, double *pConsumedLength, double fShift)
Create line start/end geometry element, mostly arrows and things like that.
B2VectorOrientation
Descriptor for the mathematical orientations of two 2D Vectors.
Definition: b2enums.hxx:27
@ Positive
mathematically positive oriented
@ Neutral
mathematically neutral, thus parallel
@ Negative
mathematically negative oriented
B2DVector getPerpendicular(const B2DVector &rNormalizedVec)
Calculate a perpendicular 2D Vector to the given one.
Definition: b2dvector.cxx:138
B2DLineJoin
Descriptor for possible line joins between two line segments.
Definition: b2enums.hxx:58