1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <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>
38 B2DPolyPolygon
createAreaGeometryForLineStartEnd(
39 const B2DPolygon
& rCandidate
,
40 const B2DPolyPolygon
& rArrow
,
43 double fCandidateLength
,
44 double fDockingPosition
, // 0->top, 1->bottom
45 double* pConsumedLength
,
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] (!)");
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
122 *pConsumedLength
= fConsumedLength
;
128 } // end of namespace utils
129 } // end of namespace basegfx
133 // anonymous namespace for local helpers
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
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
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
169 if(fTools::lessOrEqual(fScalarAE
* fScalarAE
, fScalarA
* fScalarE
* fMaxCosQuad
))
171 // angle between TangentA and Edge is bigger or equal angle defined by fMaxCos
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
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
194 if(fTools::lessOrEqual(fScalarBE
* fScalarBE
, fScalarB
* fScalarE
* fMaxCosQuad
))
196 // angle between TangentB and Edge is bigger or equal defined by fMaxCos
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());
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);
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();
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());
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();
275 impSubdivideToSimple(aLeft
, aRetval
, fMaxCosQuad
, fMaxPartOfEdgeQuad
, 6);
279 aRetval
.append(aLeft
.getEndPoint());
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
300 impSubdivideToSimple(aEdge
, aRetval
, fMaxCosQuad
, fMaxPartOfEdgeQuad
, 6);
304 aRetval
.append(aEdge
.getEndPoint());
309 impSubdivideToSimple(aEdge
, aRetval
, fMaxCosQuad
, fMaxPartOfEdgeQuad
, 6);
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();
334 B2DPolygon
createAreaGeometryForEdge(
335 const B2DCubicBezier
& rEdge
,
336 double fHalfLineWidth
,
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
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
);
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
);
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
);
384 if(bStartRound
|| bStartSquare
)
388 basegfx::B2DPolygon
aStartPolygon(utils::createHalfUnitCircle());
390 aStartPolygon
.transform(
391 utils::createScaleShearXRotateTranslateB2DHomMatrix(
392 fHalfLineWidth
, fHalfLineWidth
,
394 atan2(aTangentA
.getY(), aTangentA
.getX()) + F_PI2
,
395 rEdge
.getStartPoint().getX(), rEdge
.getStartPoint().getY()));
396 aBezierPolygon
.append(aStartPolygon
);
400 const basegfx::B2DPoint
aStart(rEdge
.getStartPoint() - (aTangentA
* fHalfLineWidth
));
404 aBezierPolygon
.append(rEdge
.getStartPoint() + aPerpendStartB
);
407 aBezierPolygon
.append(aStart
+ aPerpendStartB
);
408 aBezierPolygon
.append(aStart
+ aPerpendStartA
);
412 aBezierPolygon
.append(rEdge
.getStartPoint() + aPerpendStartA
);
418 // append original in-between point
419 aBezierPolygon
.append(rEdge
.getStartPoint());
422 // create upper edge.
426 // calculate cut point and add
427 aCutPoint
= rEdge
.getStartPoint() + (aPerpendStartA
* fCutA
);
428 aBezierPolygon
.append(aCutPoint
);
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
);
447 if(bEndRound
|| bEndSquare
)
451 basegfx::B2DPolygon
aEndPolygon(utils::createHalfUnitCircle());
453 aEndPolygon
.transform(
454 utils::createScaleShearXRotateTranslateB2DHomMatrix(
455 fHalfLineWidth
, fHalfLineWidth
,
457 atan2(aTangentB
.getY(), aTangentB
.getX()) - F_PI2
,
458 rEdge
.getEndPoint().getX(), rEdge
.getEndPoint().getY()));
459 aBezierPolygon
.append(aEndPolygon
);
463 const basegfx::B2DPoint
aEnd(rEdge
.getEndPoint() + (aTangentB
* fHalfLineWidth
));
467 aBezierPolygon
.append(rEdge
.getEndPoint() + aPerpendEndA
);
470 aBezierPolygon
.append(aEnd
+ aPerpendEndA
);
471 aBezierPolygon
.append(aEnd
+ aPerpendEndB
);
475 aBezierPolygon
.append(rEdge
.getEndPoint() + aPerpendEndB
);
481 // append original in-between point
482 aBezierPolygon
.append(rEdge
.getEndPoint());
485 // create lower edge.
489 // calculate cut point and add
490 aCutPoint
= rEdge
.getEndPoint() + (aPerpendEndB
* fCutB
);
491 aBezierPolygon
.append(aCutPoint
);
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
);
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
526 const B2DPolyPolygon
aTemp(utils::solveCrossovers(aBezierPolygon
));
527 const sal_uInt32
nTempCount(aTemp
.count());
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();
550 if(aBezierPolygon
.count())
556 OSL_ENSURE(aBezierPolygon
.count(), "Error in line geometry creation, could not solve self-intersection (!)");
560 // none found, use result
561 aBezierPolygon
= aTemp
.getB2DPolygon(0);
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(
575 pTriangles
->insert(pTriangles
->end(), aResult
.begin(), aResult
.end());
576 aBezierPolygon
.clear();
580 return aBezierPolygon
;
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
;
595 // buffered perpendicular
597 bool bPerpend(false);
599 // create left vertical
602 aEdgePolygon
= utils::createHalfUnitCircle();
603 fAngle
= atan2(aTangent
.getY(), aTangent
.getX());
605 aEdgePolygon
.transform(
606 utils::createScaleShearXRotateTranslateB2DHomMatrix(
607 fHalfLineWidth
, fHalfLineWidth
,
610 rEdge
.getStartPoint().getX(), rEdge
.getStartPoint().getY()));
614 aPerpend
.setX(-aTangent
.getY());
615 aPerpend
.setY(aTangent
.getX());
620 const basegfx::B2DPoint
aStart(rEdge
.getStartPoint() - aTangent
);
622 aEdgePolygon
.append(aStart
+ aPerpend
);
623 aEdgePolygon
.append(aStart
- aPerpend
);
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
636 basegfx::B2DPolygon
aEndPolygon(utils::createHalfUnitCircle());
640 fAngle
= atan2(aTangent
.getY(), aTangent
.getX());
643 aEndPolygon
.transform(
644 utils::createScaleShearXRotateTranslateB2DHomMatrix(
645 fHalfLineWidth
, fHalfLineWidth
,
648 rEdge
.getEndPoint().getX(), rEdge
.getEndPoint().getY()));
649 aEdgePolygon
.append(aEndPolygon
);
655 aPerpend
.setX(-aTangent
.getY());
656 aPerpend
.setY(aTangent
.getX());
661 const basegfx::B2DPoint
aEnd(rEdge
.getEndPoint() + aTangent
);
663 aEdgePolygon
.append(aEnd
- aPerpend
);
664 aEdgePolygon
.append(aEnd
+ aPerpend
);
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
);
675 aEdgePolygon
.setClosed(true);
677 if(nullptr != pTriangles
)
679 const basegfx::triangulator::B2DTriangleVector
aResult(
680 basegfx::triangulator::triangulate(
682 pTriangles
->insert(pTriangles
->end(), aResult
.begin(), aResult
.end());
683 aEdgePolygon
.clear();
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
,
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
)
718 eJoin
= B2DLineJoin::Bevel
;
724 case B2DLineJoin::Miter
:
726 if(nullptr != pTriangles
)
728 pTriangles
->emplace_back(
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.
746 utils::findCut(aStartPoint
, rTangentPrev
, aEndPoint
, rTangentEdge
, CutFlagValue::ALL
, &fCutPos
);
750 const B2DPoint
aCutPoint(aStartPoint
+ (rTangentPrev
* fCutPos
));
752 if(nullptr != pTriangles
)
754 pTriangles
->emplace_back(
761 aEdgePolygon
.append(aCutPoint
);
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
;
784 const B2DPolygon
aBow(utils::createPolygonFromEllipseSegment(rPoint
, fHalfLineWidth
, fHalfLineWidth
, fAngleStart
, fAngleEnd
));
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
),
795 aBow
.count() - 1 == a
+ 1 ? aEndPoint
: aBow
.getB2DPoint(a
+ 1));
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)
805 aEdgePolygon
.setB2DPoint(0, aStartPoint
);
806 aEdgePolygon
.setB2DPoint(aEdgePolygon
.count() - 1, aEndPoint
);
807 aEdgePolygon
.append(rPoint
);
814 [[fallthrough
]]; // wanted fall-through to default
817 default: // B2DLineJoin::Bevel
819 if(nullptr != pTriangles
)
821 pTriangles
->emplace_back(
828 aEdgePolygon
.append(aEndPoint
);
829 aEdgePolygon
.append(rPoint
);
830 aEdgePolygon
.append(aStartPoint
);
837 // create last polygon part for edge
838 aEdgePolygon
.setClosed(true);
842 } // end of anonymous namespace
846 B2DPolyPolygon
createAreaGeometry(
847 const B2DPolygon
& rCandidate
,
848 double fHalfLineWidth
,
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());
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
);
900 B2DCubicBezier aEdge
;
901 B2DCubicBezier aPrev
;
903 const bool bEventuallyCreateLineJoin(eJoin
!= B2DLineJoin::NONE
);
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
++)
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
944 aOrientation
= B2VectorOrientation::Positive
;
949 if(aOrientation
== B2VectorOrientation::Positive
)
951 const B2DVector
aPerpendPrev(getPerpendicular(aTangentPrev
) * -fHalfLineWidth
);
952 const B2DVector
aPerpendEdge(getPerpendicular(aTangentEdge
) * -fHalfLineWidth
);
955 createAreaGeometryForJoin(
960 aEdge
.getStartPoint(),
966 else if(aOrientation
== B2VectorOrientation::Negative
)
968 const B2DVector
aPerpendPrev(getPerpendicular(aTangentPrev
) * fHalfLineWidth
);
969 const B2DVector
aPerpendEdge(getPerpendicular(aTangentEdge
) * fHalfLineWidth
);
972 createAreaGeometryForJoin(
977 aEdge
.getStartPoint(),
985 // create geometry for edge
986 const bool bLast(a
+ 1 == nEdgeCount
);
990 const bool bFirst(!a
);
993 createAreaGeometryForEdge(
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
,
1005 createAreaGeometryForEdge(
1015 // prepare next step
1018 if(bEventuallyCreateLineJoin
)
1023 aEdge
.setStartPoint(aEdge
.getEndPoint());
1029 // point count, but no edge count -> single point
1030 const basegfx::B2DPolygon
aCircle(
1031 createPolygonFromCircle(
1032 aCandidate
.getB2DPoint(0),
1035 if(nullptr != pTriangles
)
1037 const basegfx::triangulator::B2DTriangleVector
aResult(
1038 basegfx::triangulator::triangulate(
1040 pTriangles
->insert(pTriangles
->end(), aResult
.begin(), aResult
.end());
1044 aRetval
.append(aCircle
);
1052 return B2DPolyPolygon(rCandidate
);
1055 } // end of namespace utils
1056 } // end of namespace basegfx
1058 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */