Version 6.4.0.0.beta1, tag libreoffice-6.4.0.0.beta1
[LibreOffice.git] / basegfx / source / polygon / b2dlinegeometry.cxx
blobee55a081214114235a170830c5e76ba5ea7dbd53
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <osl/diagnose.h>
21 #include <basegfx/polygon/b2dlinegeometry.hxx>
22 #include <basegfx/point/b2dpoint.hxx>
23 #include <basegfx/vector/b2dvector.hxx>
24 #include <basegfx/polygon/b2dpolygontools.hxx>
25 #include <basegfx/polygon/b2dpolypolygontools.hxx>
26 #include <basegfx/range/b2drange.hxx>
27 #include <basegfx/matrix/b2dhommatrix.hxx>
28 #include <basegfx/curve/b2dcubicbezier.hxx>
29 #include <basegfx/matrix/b2dhommatrixtools.hxx>
30 #include <com/sun/star/drawing/LineCap.hpp>
31 #include <basegfx/polygon/b2dpolypolygoncutter.hxx>
32 #include <basegfx/polygon/b2dpolygontriangulator.hxx>
34 namespace basegfx
36 namespace utils
38 B2DPolyPolygon createAreaGeometryForLineStartEnd(
39 const B2DPolygon& rCandidate,
40 const B2DPolyPolygon& rArrow,
41 bool bStart,
42 double fWidth,
43 double fCandidateLength,
44 double fDockingPosition, // 0->top, 1->bottom
45 double* pConsumedLength,
46 double fShift)
48 B2DPolyPolygon aRetval;
49 OSL_ENSURE(rCandidate.count() > 1, "createAreaGeometryForLineStartEnd: Line polygon has too less points (!)");
50 OSL_ENSURE(rArrow.count() > 0, "createAreaGeometryForLineStartEnd: Empty arrow utils::PolyPolygon (!)");
51 OSL_ENSURE(fWidth > 0.0, "createAreaGeometryForLineStartEnd: Width too small (!)");
52 OSL_ENSURE(fDockingPosition >= 0.0 && fDockingPosition <= 1.0,
53 "createAreaGeometryForLineStartEnd: fDockingPosition out of range [0.0 .. 1.0] (!)");
55 if(fWidth < 0.0)
57 fWidth = -fWidth;
60 if(rCandidate.count() > 1 && rArrow.count() && !fTools::equalZero(fWidth))
62 if(fDockingPosition < 0.0)
64 fDockingPosition = 0.0;
66 else if(fDockingPosition > 1.0)
68 fDockingPosition = 1.0;
71 // init return value from arrow
72 aRetval.append(rArrow);
74 // get size of the arrow
75 const B2DRange aArrowSize(getRange(rArrow));
77 // build ArrowTransform; center in X, align with axis in Y
78 B2DHomMatrix aArrowTransform(basegfx::utils::createTranslateB2DHomMatrix(
79 -aArrowSize.getCenter().getX(), -aArrowSize.getMinimum().getY()));
81 // scale to target size
82 const double fArrowScale(fWidth / (aArrowSize.getWidth()));
83 aArrowTransform.scale(fArrowScale, fArrowScale);
85 // get arrow size in Y
86 B2DPoint aUpperCenter(aArrowSize.getCenter().getX(), aArrowSize.getMaximum().getY());
87 aUpperCenter *= aArrowTransform;
88 const double fArrowYLength(B2DVector(aUpperCenter).getLength());
90 // move arrow to have docking position centered
91 aArrowTransform.translate(0.0, -fArrowYLength * fDockingPosition + fShift);
93 // prepare polygon length
94 if(fTools::equalZero(fCandidateLength))
96 fCandidateLength = getLength(rCandidate);
99 // get the polygon vector we want to plant this arrow on
100 const double fConsumedLength(fArrowYLength * (1.0 - fDockingPosition) - fShift);
101 const B2DVector aHead(rCandidate.getB2DPoint(bStart ? 0 : rCandidate.count() - 1));
102 const B2DVector aTail(getPositionAbsolute(rCandidate,
103 bStart ? fConsumedLength : fCandidateLength - fConsumedLength, fCandidateLength));
105 // from that vector, take the needed rotation and add rotate for arrow to transformation
106 const B2DVector aTargetDirection(aHead - aTail);
107 const double fRotation(atan2(aTargetDirection.getY(), aTargetDirection.getX()) + F_PI2);
109 // rotate around docking position
110 aArrowTransform.rotate(fRotation);
112 // move arrow docking position to polygon head
113 aArrowTransform.translate(aHead.getX(), aHead.getY());
115 // transform retval and close
116 aRetval.transform(aArrowTransform);
117 aRetval.setClosed(true);
119 // if pConsumedLength is asked for, fill it
120 if(pConsumedLength)
122 *pConsumedLength = fConsumedLength;
126 return aRetval;
128 } // end of namespace utils
129 } // end of namespace basegfx
131 namespace basegfx
133 // anonymous namespace for local helpers
134 namespace
136 bool impIsSimpleEdge(const B2DCubicBezier& rCandidate, double fMaxCosQuad, double fMaxPartOfEdgeQuad)
138 // isBezier() is true, already tested by caller
139 const B2DVector aEdge(rCandidate.getEndPoint() - rCandidate.getStartPoint());
141 if(aEdge.equalZero())
143 // start and end point the same, but control vectors used -> balloon curve loop
144 // is not a simple edge
145 return false;
148 // get tangentA and scalar with edge
149 const B2DVector aTangentA(rCandidate.getTangent(0.0));
150 const double fScalarAE(aEdge.scalar(aTangentA));
152 if(fTools::lessOrEqual(fScalarAE, 0.0))
154 // angle between TangentA and Edge is bigger or equal 90 degrees
155 return false;
158 // get self-scalars for E and A
159 const double fScalarE(aEdge.scalar(aEdge));
160 const double fScalarA(aTangentA.scalar(aTangentA));
161 const double fLengthCompareE(fScalarE * fMaxPartOfEdgeQuad);
163 if(fTools::moreOrEqual(fScalarA, fLengthCompareE))
165 // length of TangentA is more than fMaxPartOfEdge of length of edge
166 return false;
169 if(fTools::lessOrEqual(fScalarAE * fScalarAE, fScalarA * fScalarE * fMaxCosQuad))
171 // angle between TangentA and Edge is bigger or equal angle defined by fMaxCos
172 return false;
175 // get tangentB and scalar with edge
176 const B2DVector aTangentB(rCandidate.getTangent(1.0));
177 const double fScalarBE(aEdge.scalar(aTangentB));
179 if(fTools::lessOrEqual(fScalarBE, 0.0))
181 // angle between TangentB and Edge is bigger or equal 90 degrees
182 return false;
185 // get self-scalar for B
186 const double fScalarB(aTangentB.scalar(aTangentB));
188 if(fTools::moreOrEqual(fScalarB, fLengthCompareE))
190 // length of TangentB is more than fMaxPartOfEdge of length of edge
191 return false;
194 if(fTools::lessOrEqual(fScalarBE * fScalarBE, fScalarB * fScalarE * fMaxCosQuad))
196 // angle between TangentB and Edge is bigger or equal defined by fMaxCos
197 return false;
200 return true;
203 void impSubdivideToSimple(const B2DCubicBezier& rCandidate, B2DPolygon& rTarget, double fMaxCosQuad, double fMaxPartOfEdgeQuad, sal_uInt32 nMaxRecursionDepth)
205 if(!nMaxRecursionDepth || impIsSimpleEdge(rCandidate, fMaxCosQuad, fMaxPartOfEdgeQuad))
207 rTarget.appendBezierSegment(rCandidate.getControlPointA(), rCandidate.getControlPointB(), rCandidate.getEndPoint());
209 else
211 B2DCubicBezier aLeft, aRight;
212 rCandidate.split(0.5, &aLeft, &aRight);
214 impSubdivideToSimple(aLeft, rTarget, fMaxCosQuad, fMaxPartOfEdgeQuad, nMaxRecursionDepth - 1);
215 impSubdivideToSimple(aRight, rTarget, fMaxCosQuad, fMaxPartOfEdgeQuad, nMaxRecursionDepth - 1);
219 B2DPolygon subdivideToSimple(const B2DPolygon& rCandidate, double fMaxCosQuad, double fMaxPartOfEdgeQuad)
221 const sal_uInt32 nPointCount(rCandidate.count());
223 if(rCandidate.areControlPointsUsed() && nPointCount)
225 const sal_uInt32 nEdgeCount(rCandidate.isClosed() ? nPointCount : nPointCount - 1);
226 B2DPolygon aRetval;
227 B2DCubicBezier aEdge;
229 // prepare edge for loop
230 aEdge.setStartPoint(rCandidate.getB2DPoint(0));
231 aRetval.append(aEdge.getStartPoint());
233 for(sal_uInt32 a(0); a < nEdgeCount; a++)
235 // fill B2DCubicBezier
236 const sal_uInt32 nNextIndex((a + 1) % nPointCount);
237 aEdge.setControlPointA(rCandidate.getNextControlPoint(a));
238 aEdge.setControlPointB(rCandidate.getPrevControlPoint(nNextIndex));
239 aEdge.setEndPoint(rCandidate.getB2DPoint(nNextIndex));
241 // get rid of unnecessary bezier segments
242 aEdge.testAndSolveTrivialBezier();
244 if(aEdge.isBezier())
246 // before splitting recursively with internal simple criteria, use
247 // ExtremumPosFinder to remove those
248 std::vector< double > aExtremumPositions;
250 aExtremumPositions.reserve(4);
251 aEdge.getAllExtremumPositions(aExtremumPositions);
253 const sal_uInt32 nCount(aExtremumPositions.size());
255 if(nCount)
257 if(nCount > 1)
259 // create order from left to right
260 std::sort(aExtremumPositions.begin(), aExtremumPositions.end());
263 for(sal_uInt32 b(0); b < nCount;)
265 // split aEdge at next split pos
266 B2DCubicBezier aLeft;
267 const double fSplitPos(aExtremumPositions[b++]);
269 aEdge.split(fSplitPos, &aLeft, &aEdge);
270 aLeft.testAndSolveTrivialBezier();
272 // consume left part
273 if(aLeft.isBezier())
275 impSubdivideToSimple(aLeft, aRetval, fMaxCosQuad, fMaxPartOfEdgeQuad, 6);
277 else
279 aRetval.append(aLeft.getEndPoint());
282 if(b < nCount)
284 // correct the remaining split positions to fit to shortened aEdge
285 const double fScaleFactor(1.0 / (1.0 - fSplitPos));
287 for(sal_uInt32 c(b); c < nCount; c++)
289 aExtremumPositions[c] = (aExtremumPositions[c] - fSplitPos) * fScaleFactor;
294 // test the shortened rest of aEdge
295 aEdge.testAndSolveTrivialBezier();
297 // consume right part
298 if(aEdge.isBezier())
300 impSubdivideToSimple(aEdge, aRetval, fMaxCosQuad, fMaxPartOfEdgeQuad, 6);
302 else
304 aRetval.append(aEdge.getEndPoint());
307 else
309 impSubdivideToSimple(aEdge, aRetval, fMaxCosQuad, fMaxPartOfEdgeQuad, 6);
312 else
314 // straight edge, add point
315 aRetval.append(aEdge.getEndPoint());
318 // prepare edge for next step
319 aEdge.setStartPoint(aEdge.getEndPoint());
322 // copy closed flag and check for double points
323 aRetval.setClosed(rCandidate.isClosed());
324 aRetval.removeDoublePoints();
326 return aRetval;
328 else
330 return rCandidate;
334 B2DPolygon createAreaGeometryForEdge(
335 const B2DCubicBezier& rEdge,
336 double fHalfLineWidth,
337 bool bStartRound,
338 bool bEndRound,
339 bool bStartSquare,
340 bool bEndSquare,
341 basegfx::triangulator::B2DTriangleVector* pTriangles)
343 // create polygon for edge
344 // Unfortunately, while it would be geometrically correct to not add
345 // the in-between points EdgeEnd and EdgeStart, it leads to rounding
346 // errors when converting to integer polygon coordinates for painting
347 if(rEdge.isBezier())
349 // prepare target and data common for upper and lower
350 B2DPolygon aBezierPolygon;
351 const B2DVector aPureEdgeVector(rEdge.getEndPoint() - rEdge.getStartPoint());
352 const double fEdgeLength(aPureEdgeVector.getLength());
353 const bool bIsEdgeLengthZero(fTools::equalZero(fEdgeLength));
354 B2DVector aTangentA(rEdge.getTangent(0.0)); aTangentA.normalize();
355 B2DVector aTangentB(rEdge.getTangent(1.0)); aTangentB.normalize();
356 const B2DVector aNormalizedPerpendicularA(getPerpendicular(aTangentA));
357 const B2DVector aNormalizedPerpendicularB(getPerpendicular(aTangentB));
359 // create upper displacement vectors and check if they cut
360 const B2DVector aPerpendStartA(aNormalizedPerpendicularA * -fHalfLineWidth);
361 const B2DVector aPerpendEndA(aNormalizedPerpendicularB * -fHalfLineWidth);
362 double fCutA(0.0);
363 const CutFlagValue aCutA(utils::findCut(
364 rEdge.getStartPoint(), aPerpendStartA,
365 rEdge.getEndPoint(), aPerpendEndA,
366 CutFlagValue::ALL, &fCutA));
367 const bool bCutA(aCutA != CutFlagValue::NONE);
369 // create lower displacement vectors and check if they cut
370 const B2DVector aPerpendStartB(aNormalizedPerpendicularA * fHalfLineWidth);
371 const B2DVector aPerpendEndB(aNormalizedPerpendicularB * fHalfLineWidth);
372 double fCutB(0.0);
373 const CutFlagValue aCutB(utils::findCut(
374 rEdge.getEndPoint(), aPerpendEndB,
375 rEdge.getStartPoint(), aPerpendStartB,
376 CutFlagValue::ALL, &fCutB));
377 const bool bCutB(aCutB != CutFlagValue::NONE);
379 // check if cut happens
380 const bool bCut(bCutA || bCutB);
381 B2DPoint aCutPoint;
383 // create left edge
384 if(bStartRound || bStartSquare)
386 if(bStartRound)
388 basegfx::B2DPolygon aStartPolygon(utils::createHalfUnitCircle());
390 aStartPolygon.transform(
391 utils::createScaleShearXRotateTranslateB2DHomMatrix(
392 fHalfLineWidth, fHalfLineWidth,
393 0.0,
394 atan2(aTangentA.getY(), aTangentA.getX()) + F_PI2,
395 rEdge.getStartPoint().getX(), rEdge.getStartPoint().getY()));
396 aBezierPolygon.append(aStartPolygon);
398 else // bStartSquare
400 const basegfx::B2DPoint aStart(rEdge.getStartPoint() - (aTangentA * fHalfLineWidth));
402 if(bCutB)
404 aBezierPolygon.append(rEdge.getStartPoint() + aPerpendStartB);
407 aBezierPolygon.append(aStart + aPerpendStartB);
408 aBezierPolygon.append(aStart + aPerpendStartA);
410 if(bCutA)
412 aBezierPolygon.append(rEdge.getStartPoint() + aPerpendStartA);
416 else
418 // append original in-between point
419 aBezierPolygon.append(rEdge.getStartPoint());
422 // create upper edge.
424 if(bCutA)
426 // calculate cut point and add
427 aCutPoint = rEdge.getStartPoint() + (aPerpendStartA * fCutA);
428 aBezierPolygon.append(aCutPoint);
430 else
432 // create scaled bezier segment
433 const B2DPoint aStart(rEdge.getStartPoint() + aPerpendStartA);
434 const B2DPoint aEnd(rEdge.getEndPoint() + aPerpendEndA);
435 const B2DVector aEdge(aEnd - aStart);
436 const double fLength(aEdge.getLength());
437 const double fScale(bIsEdgeLengthZero ? 1.0 : fLength / fEdgeLength);
438 const B2DVector fRelNext(rEdge.getControlPointA() - rEdge.getStartPoint());
439 const B2DVector fRelPrev(rEdge.getControlPointB() - rEdge.getEndPoint());
441 aBezierPolygon.append(aStart);
442 aBezierPolygon.appendBezierSegment(aStart + (fRelNext * fScale), aEnd + (fRelPrev * fScale), aEnd);
446 // create right edge
447 if(bEndRound || bEndSquare)
449 if(bEndRound)
451 basegfx::B2DPolygon aEndPolygon(utils::createHalfUnitCircle());
453 aEndPolygon.transform(
454 utils::createScaleShearXRotateTranslateB2DHomMatrix(
455 fHalfLineWidth, fHalfLineWidth,
456 0.0,
457 atan2(aTangentB.getY(), aTangentB.getX()) - F_PI2,
458 rEdge.getEndPoint().getX(), rEdge.getEndPoint().getY()));
459 aBezierPolygon.append(aEndPolygon);
461 else // bEndSquare
463 const basegfx::B2DPoint aEnd(rEdge.getEndPoint() + (aTangentB * fHalfLineWidth));
465 if(bCutA)
467 aBezierPolygon.append(rEdge.getEndPoint() + aPerpendEndA);
470 aBezierPolygon.append(aEnd + aPerpendEndA);
471 aBezierPolygon.append(aEnd + aPerpendEndB);
473 if(bCutB)
475 aBezierPolygon.append(rEdge.getEndPoint() + aPerpendEndB);
479 else
481 // append original in-between point
482 aBezierPolygon.append(rEdge.getEndPoint());
485 // create lower edge.
487 if(bCutB)
489 // calculate cut point and add
490 aCutPoint = rEdge.getEndPoint() + (aPerpendEndB * fCutB);
491 aBezierPolygon.append(aCutPoint);
493 else
495 // create scaled bezier segment
496 const B2DPoint aStart(rEdge.getEndPoint() + aPerpendEndB);
497 const B2DPoint aEnd(rEdge.getStartPoint() + aPerpendStartB);
498 const B2DVector aEdge(aEnd - aStart);
499 const double fLength(aEdge.getLength());
500 const double fScale(bIsEdgeLengthZero ? 1.0 : fLength / fEdgeLength);
501 const B2DVector fRelNext(rEdge.getControlPointB() - rEdge.getEndPoint());
502 const B2DVector fRelPrev(rEdge.getControlPointA() - rEdge.getStartPoint());
504 aBezierPolygon.append(aStart);
505 aBezierPolygon.appendBezierSegment(aStart + (fRelNext * fScale), aEnd + (fRelPrev * fScale), aEnd);
509 // close
510 aBezierPolygon.setClosed(true);
512 if(bStartRound || bEndRound)
514 // double points possible when round caps are used at start or end
515 aBezierPolygon.removeDoublePoints();
518 if(bCut && ((bStartRound || bStartSquare) && (bEndRound || bEndSquare)))
520 // When cut exists and both ends are extended with caps, a self-intersecting polygon
521 // is created; one cut point is known, but there is a 2nd one in the caps geometry.
522 // Solve by using tooling.
523 // Remark: This nearly never happens due to curve preparations to extreme points
524 // and maximum angle turning, but I constructed a test case and checked that it is
525 // working properly.
526 const B2DPolyPolygon aTemp(utils::solveCrossovers(aBezierPolygon));
527 const sal_uInt32 nTempCount(aTemp.count());
529 if(nTempCount)
531 if(nTempCount > 1)
533 // as expected, multiple polygons (with same orientation). Remove
534 // the one which contains aCutPoint, or better take the one without
535 for (sal_uInt32 a(0); a < nTempCount; a++)
537 aBezierPolygon = aTemp.getB2DPolygon(a);
539 const sal_uInt32 nCandCount(aBezierPolygon.count());
541 for(sal_uInt32 b(0); b < nCandCount; b++)
543 if(aCutPoint.equal(aBezierPolygon.getB2DPoint(b)))
545 aBezierPolygon.clear();
546 break;
550 if(aBezierPolygon.count())
552 break;
556 OSL_ENSURE(aBezierPolygon.count(), "Error in line geometry creation, could not solve self-intersection (!)");
558 else
560 // none found, use result
561 aBezierPolygon = aTemp.getB2DPolygon(0);
564 else
566 OSL_ENSURE(false, "Error in line geometry creation, could not solve self-intersection (!)");
570 if(nullptr != pTriangles)
572 const basegfx::triangulator::B2DTriangleVector aResult(
573 basegfx::triangulator::triangulate(
574 aBezierPolygon));
575 pTriangles->insert(pTriangles->end(), aResult.begin(), aResult.end());
576 aBezierPolygon.clear();
579 // return
580 return aBezierPolygon;
582 else
584 // Get start and end point, create tangent and set to needed length
585 B2DVector aTangent(rEdge.getEndPoint() - rEdge.getStartPoint());
586 aTangent.setLength(fHalfLineWidth);
588 // prepare return value
589 B2DPolygon aEdgePolygon;
591 // buffered angle
592 double fAngle(0.0);
593 bool bAngle(false);
595 // buffered perpendicular
596 B2DVector aPerpend;
597 bool bPerpend(false);
599 // create left vertical
600 if(bStartRound)
602 aEdgePolygon = utils::createHalfUnitCircle();
603 fAngle = atan2(aTangent.getY(), aTangent.getX());
604 bAngle = true;
605 aEdgePolygon.transform(
606 utils::createScaleShearXRotateTranslateB2DHomMatrix(
607 fHalfLineWidth, fHalfLineWidth,
608 0.0,
609 fAngle + F_PI2,
610 rEdge.getStartPoint().getX(), rEdge.getStartPoint().getY()));
612 else
614 aPerpend.setX(-aTangent.getY());
615 aPerpend.setY(aTangent.getX());
616 bPerpend = true;
618 if(bStartSquare)
620 const basegfx::B2DPoint aStart(rEdge.getStartPoint() - aTangent);
622 aEdgePolygon.append(aStart + aPerpend);
623 aEdgePolygon.append(aStart - aPerpend);
625 else
627 aEdgePolygon.append(rEdge.getStartPoint() + aPerpend);
628 aEdgePolygon.append(rEdge.getStartPoint()); // keep the in-between point for numerical reasons
629 aEdgePolygon.append(rEdge.getStartPoint() - aPerpend);
633 // create right vertical
634 if(bEndRound)
636 basegfx::B2DPolygon aEndPolygon(utils::createHalfUnitCircle());
638 if(!bAngle)
640 fAngle = atan2(aTangent.getY(), aTangent.getX());
643 aEndPolygon.transform(
644 utils::createScaleShearXRotateTranslateB2DHomMatrix(
645 fHalfLineWidth, fHalfLineWidth,
646 0.0,
647 fAngle - F_PI2,
648 rEdge.getEndPoint().getX(), rEdge.getEndPoint().getY()));
649 aEdgePolygon.append(aEndPolygon);
651 else
653 if(!bPerpend)
655 aPerpend.setX(-aTangent.getY());
656 aPerpend.setY(aTangent.getX());
659 if(bEndSquare)
661 const basegfx::B2DPoint aEnd(rEdge.getEndPoint() + aTangent);
663 aEdgePolygon.append(aEnd - aPerpend);
664 aEdgePolygon.append(aEnd + aPerpend);
666 else
668 aEdgePolygon.append(rEdge.getEndPoint() - aPerpend);
669 aEdgePolygon.append(rEdge.getEndPoint()); // keep the in-between point for numerical reasons
670 aEdgePolygon.append(rEdge.getEndPoint() + aPerpend);
674 // close and return
675 aEdgePolygon.setClosed(true);
677 if(nullptr != pTriangles)
679 const basegfx::triangulator::B2DTriangleVector aResult(
680 basegfx::triangulator::triangulate(
681 aEdgePolygon));
682 pTriangles->insert(pTriangles->end(), aResult.begin(), aResult.end());
683 aEdgePolygon.clear();
686 return aEdgePolygon;
690 B2DPolygon createAreaGeometryForJoin(
691 const B2DVector& rTangentPrev,
692 const B2DVector& rTangentEdge,
693 const B2DVector& rPerpendPrev,
694 const B2DVector& rPerpendEdge,
695 const B2DPoint& rPoint,
696 double fHalfLineWidth,
697 B2DLineJoin eJoin,
698 double fMiterMinimumAngle,
699 basegfx::triangulator::B2DTriangleVector* pTriangles)
701 OSL_ENSURE(fHalfLineWidth > 0.0, "createAreaGeometryForJoin: LineWidth too small (!)");
702 OSL_ENSURE(eJoin != B2DLineJoin::NONE, "createAreaGeometryForJoin: B2DLineJoin::NONE not allowed (!)");
704 // LineJoin from tangent rPerpendPrev to tangent rPerpendEdge in rPoint
705 B2DPolygon aEdgePolygon;
706 const B2DPoint aStartPoint(rPoint + rPerpendPrev);
707 const B2DPoint aEndPoint(rPoint + rPerpendEdge);
709 // test if for Miter, the angle is too small and the fallback
710 // to bevel needs to be used
711 if(eJoin == B2DLineJoin::Miter)
713 const double fAngle(fabs(rPerpendPrev.angle(rPerpendEdge)));
715 if((F_PI - fAngle) < fMiterMinimumAngle)
717 // fallback to bevel
718 eJoin = B2DLineJoin::Bevel;
722 switch(eJoin)
724 case B2DLineJoin::Miter :
726 if(nullptr != pTriangles)
728 pTriangles->emplace_back(
729 aEndPoint,
730 rPoint,
731 aStartPoint);
733 else
735 aEdgePolygon.append(aEndPoint);
736 aEdgePolygon.append(rPoint);
737 aEdgePolygon.append(aStartPoint);
740 // Look for the cut point between start point along rTangentPrev and
741 // end point along rTangentEdge. -rTangentEdge should be used, but since
742 // the cut value is used for interpolating along the first edge, the negation
743 // is not needed since the same fCut will be found on the first edge.
744 // If it exists, insert it to complete the mitered fill polygon.
745 double fCutPos(0.0);
746 utils::findCut(aStartPoint, rTangentPrev, aEndPoint, rTangentEdge, CutFlagValue::ALL, &fCutPos);
748 if(fCutPos != 0.0)
750 const B2DPoint aCutPoint(aStartPoint + (rTangentPrev * fCutPos));
752 if(nullptr != pTriangles)
754 pTriangles->emplace_back(
755 aStartPoint,
756 aCutPoint,
757 aEndPoint);
759 else
761 aEdgePolygon.append(aCutPoint);
765 break;
767 case B2DLineJoin::Round :
769 // use tooling to add needed EllipseSegment
770 double fAngleStart(atan2(rPerpendPrev.getY(), rPerpendPrev.getX()));
771 double fAngleEnd(atan2(rPerpendEdge.getY(), rPerpendEdge.getX()));
773 // atan2 results are [-PI .. PI], consolidate to [0.0 .. 2PI]
774 if(fAngleStart < 0.0)
776 fAngleStart += F_2PI;
779 if(fAngleEnd < 0.0)
781 fAngleEnd += F_2PI;
784 const B2DPolygon aBow(utils::createPolygonFromEllipseSegment(rPoint, fHalfLineWidth, fHalfLineWidth, fAngleStart, fAngleEnd));
786 if(aBow.count() > 1)
788 if(nullptr != pTriangles)
790 for(sal_uInt32 a(0); a < aBow.count() - 1; a++)
792 pTriangles->emplace_back(
793 0 == a ? aStartPoint : aBow.getB2DPoint(a),
794 rPoint,
795 aBow.count() - 1 == a + 1 ? aEndPoint : aBow.getB2DPoint(a + 1));
798 else
800 // #i101491#
801 // use the original start/end positions; the ones from bow creation may be numerically
802 // different due to their different creation. To guarantee good merging quality with edges
803 // and edge roundings (and to reduce point count)
804 aEdgePolygon = aBow;
805 aEdgePolygon.setB2DPoint(0, aStartPoint);
806 aEdgePolygon.setB2DPoint(aEdgePolygon.count() - 1, aEndPoint);
807 aEdgePolygon.append(rPoint);
810 break;
812 else
814 [[fallthrough]]; // wanted fall-through to default
817 default: // B2DLineJoin::Bevel
819 if(nullptr != pTriangles)
821 pTriangles->emplace_back(
822 aEndPoint,
823 rPoint,
824 aStartPoint);
826 else
828 aEdgePolygon.append(aEndPoint);
829 aEdgePolygon.append(rPoint);
830 aEdgePolygon.append(aStartPoint);
833 break;
837 // create last polygon part for edge
838 aEdgePolygon.setClosed(true);
840 return aEdgePolygon;
842 } // end of anonymous namespace
844 namespace utils
846 B2DPolyPolygon createAreaGeometry(
847 const B2DPolygon& rCandidate,
848 double fHalfLineWidth,
849 B2DLineJoin eJoin,
850 css::drawing::LineCap eCap,
851 double fMaxAllowedAngle,
852 double fMaxPartOfEdge,
853 double fMiterMinimumAngle,
854 basegfx::triangulator::B2DTriangleVector* pTriangles)
856 if(fMaxAllowedAngle > F_PI2)
858 fMaxAllowedAngle = F_PI2;
860 else if(fMaxAllowedAngle < 0.01 * F_PI2)
862 fMaxAllowedAngle = 0.01 * F_PI2;
865 if(fMaxPartOfEdge > 1.0)
867 fMaxPartOfEdge = 1.0;
869 else if(fMaxPartOfEdge < 0.01)
871 fMaxPartOfEdge = 0.01;
874 if(fMiterMinimumAngle > F_PI)
876 fMiterMinimumAngle = F_PI;
878 else if(fMiterMinimumAngle < 0.01 * F_PI)
880 fMiterMinimumAngle = 0.01 * F_PI;
883 B2DPolygon aCandidate(rCandidate);
884 const double fMaxCos(cos(fMaxAllowedAngle));
886 aCandidate.removeDoublePoints();
887 aCandidate = subdivideToSimple(aCandidate, fMaxCos * fMaxCos, fMaxPartOfEdge * fMaxPartOfEdge);
889 const sal_uInt32 nPointCount(aCandidate.count());
891 if(nPointCount)
893 B2DPolyPolygon aRetval;
894 const bool bIsClosed(aCandidate.isClosed());
895 const sal_uInt32 nEdgeCount(bIsClosed ? nPointCount : nPointCount - 1);
896 const bool bLineCap(!bIsClosed && eCap != css::drawing::LineCap_BUTT);
898 if(nEdgeCount)
900 B2DCubicBezier aEdge;
901 B2DCubicBezier aPrev;
903 const bool bEventuallyCreateLineJoin(eJoin != B2DLineJoin::NONE);
904 // prepare edge
905 aEdge.setStartPoint(aCandidate.getB2DPoint(0));
907 if(bIsClosed && bEventuallyCreateLineJoin)
909 // prepare previous edge
910 const sal_uInt32 nPrevIndex(nPointCount - 1);
911 aPrev.setStartPoint(aCandidate.getB2DPoint(nPrevIndex));
912 aPrev.setControlPointA(aCandidate.getNextControlPoint(nPrevIndex));
913 aPrev.setControlPointB(aCandidate.getPrevControlPoint(0));
914 aPrev.setEndPoint(aEdge.getStartPoint());
917 for(sal_uInt32 a(0); a < nEdgeCount; a++)
919 // fill current Edge
920 const sal_uInt32 nNextIndex((a + 1) % nPointCount);
921 aEdge.setControlPointA(aCandidate.getNextControlPoint(a));
922 aEdge.setControlPointB(aCandidate.getPrevControlPoint(nNextIndex));
923 aEdge.setEndPoint(aCandidate.getB2DPoint(nNextIndex));
925 // check and create linejoin
926 if(bEventuallyCreateLineJoin && (bIsClosed || a != 0))
928 B2DVector aTangentPrev(aPrev.getTangent(1.0)); aTangentPrev.normalize();
929 B2DVector aTangentEdge(aEdge.getTangent(0.0)); aTangentEdge.normalize();
930 B2VectorOrientation aOrientation(getOrientation(aTangentPrev, aTangentEdge));
932 if(aOrientation == B2VectorOrientation::Neutral)
934 // they are parallel or empty; if they are both not zero and point
935 // in opposite direction, a half-circle is needed
936 if(!aTangentPrev.equalZero() && !aTangentEdge.equalZero())
938 const double fAngle(fabs(aTangentPrev.angle(aTangentEdge)));
940 if(fTools::equal(fAngle, F_PI))
942 // for half-circle production, fallback to positive
943 // orientation
944 aOrientation = B2VectorOrientation::Positive;
949 if(aOrientation == B2VectorOrientation::Positive)
951 const B2DVector aPerpendPrev(getPerpendicular(aTangentPrev) * -fHalfLineWidth);
952 const B2DVector aPerpendEdge(getPerpendicular(aTangentEdge) * -fHalfLineWidth);
954 aRetval.append(
955 createAreaGeometryForJoin(
956 aTangentPrev,
957 aTangentEdge,
958 aPerpendPrev,
959 aPerpendEdge,
960 aEdge.getStartPoint(),
961 fHalfLineWidth,
962 eJoin,
963 fMiterMinimumAngle,
964 pTriangles));
966 else if(aOrientation == B2VectorOrientation::Negative)
968 const B2DVector aPerpendPrev(getPerpendicular(aTangentPrev) * fHalfLineWidth);
969 const B2DVector aPerpendEdge(getPerpendicular(aTangentEdge) * fHalfLineWidth);
971 aRetval.append(
972 createAreaGeometryForJoin(
973 aTangentEdge,
974 aTangentPrev,
975 aPerpendEdge,
976 aPerpendPrev,
977 aEdge.getStartPoint(),
978 fHalfLineWidth,
979 eJoin,
980 fMiterMinimumAngle,
981 pTriangles));
985 // create geometry for edge
986 const bool bLast(a + 1 == nEdgeCount);
988 if(bLineCap)
990 const bool bFirst(!a);
992 aRetval.append(
993 createAreaGeometryForEdge(
994 aEdge,
995 fHalfLineWidth,
996 bFirst && eCap == css::drawing::LineCap_ROUND,
997 bLast && eCap == css::drawing::LineCap_ROUND,
998 bFirst && eCap == css::drawing::LineCap_SQUARE,
999 bLast && eCap == css::drawing::LineCap_SQUARE,
1000 pTriangles));
1002 else
1004 aRetval.append(
1005 createAreaGeometryForEdge(
1006 aEdge,
1007 fHalfLineWidth,
1008 false,
1009 false,
1010 false,
1011 false,
1012 pTriangles));
1015 // prepare next step
1016 if(!bLast)
1018 if(bEventuallyCreateLineJoin)
1020 aPrev = aEdge;
1023 aEdge.setStartPoint(aEdge.getEndPoint());
1027 else
1029 // point count, but no edge count -> single point
1030 const basegfx::B2DPolygon aCircle(
1031 createPolygonFromCircle(
1032 aCandidate.getB2DPoint(0),
1033 fHalfLineWidth));
1035 if(nullptr != pTriangles)
1037 const basegfx::triangulator::B2DTriangleVector aResult(
1038 basegfx::triangulator::triangulate(
1039 aCircle));
1040 pTriangles->insert(pTriangles->end(), aResult.begin(), aResult.end());
1042 else
1044 aRetval.append(aCircle);
1048 return aRetval;
1050 else
1052 return B2DPolyPolygon(rCandidate);
1055 } // end of namespace utils
1056 } // end of namespace basegfx
1058 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */