1 /*************************************************************************
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
5 * Copyright 2008 by Sun Microsystems, Inc.
7 * OpenOffice.org - a multi-platform office productivity suite
9 * $RCSfile: b2dlinegeometry.cxx,v $
12 * This file is part of OpenOffice.org.
14 * OpenOffice.org is free software: you can redistribute it and/or modify
15 * it under the terms of the GNU Lesser General Public License version 3
16 * only, as published by the Free Software Foundation.
18 * OpenOffice.org is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU Lesser General Public License version 3 for more details
22 * (a copy is included in the LICENSE file that accompanied this code).
24 * You should have received a copy of the GNU Lesser General Public License
25 * version 3 along with OpenOffice.org. If not, see
26 * <http://www.openoffice.org/license.html>
27 * for a copy of the LGPLv3 License.
29 ************************************************************************/
31 // MARKER(update_precomp.py): autogen include statement, do not remove
32 #include "precompiled_basegfx.hxx"
34 #include <osl/diagnose.h>
35 #include <basegfx/polygon/b2dlinegeometry.hxx>
36 #include <basegfx/point/b2dpoint.hxx>
37 #include <basegfx/vector/b2dvector.hxx>
38 #include <basegfx/polygon/b2dpolygontools.hxx>
39 #include <basegfx/polygon/b2dpolypolygontools.hxx>
40 #include <basegfx/range/b2drange.hxx>
41 #include <basegfx/matrix/b2dhommatrix.hxx>
42 #include <basegfx/curve/b2dcubicbezier.hxx>
44 //////////////////////////////////////////////////////////////////////////////
50 B2DPolyPolygon
createAreaGeometryForLineStartEnd(
51 const B2DPolygon
& rCandidate
,
52 const B2DPolyPolygon
& rArrow
,
55 double fCandidateLength
,
56 double fDockingPosition
, // 0->top, 1->bottom
57 double* pConsumedLength
)
59 B2DPolyPolygon aRetval
;
60 OSL_ENSURE(rCandidate
.count() > 1L, "createAreaGeometryForLineStartEnd: Line polygon has too less points (!)");
61 OSL_ENSURE(rArrow
.count() > 0L, "createAreaGeometryForLineStartEnd: Empty arrow PolyPolygon (!)");
62 OSL_ENSURE(fWidth
> 0.0, "createAreaGeometryForLineStartEnd: Width too small (!)");
63 OSL_ENSURE(fDockingPosition
>= 0.0 && fDockingPosition
<= 1.0,
64 "createAreaGeometryForLineStartEnd: fDockingPosition out of range [0.0 .. 1.0] (!)");
71 if(rCandidate
.count() > 1 && rArrow
.count() && !fTools::equalZero(fWidth
))
73 if(fDockingPosition
< 0.0)
75 fDockingPosition
= 0.0;
77 else if(fDockingPosition
> 1.0)
79 fDockingPosition
= 1.0;
82 // init return value from arrow
83 aRetval
.append(rArrow
);
85 // get size of the arrow
86 const B2DRange
aArrowSize(getRange(rArrow
));
88 // build ArrowTransform
89 B2DHomMatrix aArrowTransform
;
91 // center in X, align with axis in Y
92 aArrowTransform
.translate(-aArrowSize
.getCenter().getX(), -aArrowSize
.getMinimum().getY());
94 // scale to target size
95 const double fArrowScale(fWidth
/ (aArrowSize
.getRange().getX()));
96 aArrowTransform
.scale(fArrowScale
, fArrowScale
);
98 // get arrow size in Y
99 B2DPoint
aUpperCenter(aArrowSize
.getCenter().getX(), aArrowSize
.getMaximum().getY());
100 aUpperCenter
*= aArrowTransform
;
101 const double fArrowYLength(B2DVector(aUpperCenter
).getLength());
103 // move arrow to have docking position centered
104 aArrowTransform
.translate(0.0, -fArrowYLength
* fDockingPosition
);
106 // prepare polygon length
107 if(fTools::equalZero(fCandidateLength
))
109 fCandidateLength
= getLength(rCandidate
);
112 // get the polygon vector we want to plant this arrow on
113 const double fConsumedLength(fArrowYLength
* (1.0 - fDockingPosition
));
114 const B2DVector
aHead(rCandidate
.getB2DPoint((bStart
) ? 0L : rCandidate
.count() - 1L));
115 const B2DVector
aTail(getPositionAbsolute(rCandidate
,
116 (bStart
) ? fConsumedLength
: fCandidateLength
- fConsumedLength
, fCandidateLength
));
118 // from that vector, take the needed rotation and add rotate for arrow to transformation
119 const B2DVector
aTargetDirection(aHead
- aTail
);
120 const double fRotation(atan2(aTargetDirection
.getY(), aTargetDirection
.getX()) + (90.0 * F_PI180
));
122 // rotate around docking position
123 aArrowTransform
.rotate(fRotation
);
125 // move arrow docking position to polygon head
126 aArrowTransform
.translate(aHead
.getX(), aHead
.getY());
128 // transform retval and close
129 aRetval
.transform(aArrowTransform
);
130 aRetval
.setClosed(true);
132 // if pConsumedLength is asked for, fill it
135 *pConsumedLength
= fConsumedLength
;
141 } // end of namespace tools
142 } // end of namespace basegfx
144 //////////////////////////////////////////////////////////////////////////////
148 // anonymus namespace for local helpers
151 bool impIsSimpleEdge(const B2DCubicBezier
& rCandidate
, double fMaxCosQuad
, double fMaxPartOfEdgeQuad
)
153 // isBezier() is true, already tested by caller
154 const B2DVector
aEdge(rCandidate
.getEndPoint() - rCandidate
.getStartPoint());
156 if(aEdge
.equalZero())
158 // start and end point the same, but control vectors used -> baloon curve loop
159 // is not a simple edge
163 // get tangentA and scalar with edge
164 const B2DVector
aTangentA(rCandidate
.getTangent(0.0));
165 const double fScalarAE(aEdge
.scalar(aTangentA
));
167 if(fTools::lessOrEqual(fScalarAE
, 0.0))
169 // angle between TangentA and Edge is bigger or equal 90 degrees
173 // get self-scalars for E and A
174 const double fScalarE(aEdge
.scalar(aEdge
));
175 const double fScalarA(aTangentA
.scalar(aTangentA
));
176 const double fLengthCompareE(fScalarE
* fMaxPartOfEdgeQuad
);
178 if(fTools::moreOrEqual(fScalarA
, fLengthCompareE
))
180 // length of TangentA is more than fMaxPartOfEdge of length of edge
184 if(fTools::lessOrEqual(fScalarAE
* fScalarAE
, fScalarA
* fScalarE
* fMaxCosQuad
))
186 // angle between TangentA and Edge is bigger or equal angle defined by fMaxCos
190 // get tangentB and scalar with edge
191 const B2DVector
aTangentB(rCandidate
.getTangent(1.0));
192 const double fScalarBE(aEdge
.scalar(aTangentB
));
194 if(fTools::lessOrEqual(fScalarBE
, 0.0))
196 // angle between TangentB and Edge is bigger or equal 90 degrees
200 // get self-scalar for B
201 const double fScalarB(aTangentB
.scalar(aTangentB
));
203 if(fTools::moreOrEqual(fScalarB
, fLengthCompareE
))
205 // length of TangentB is more than fMaxPartOfEdge of length of edge
209 if(fTools::lessOrEqual(fScalarBE
* fScalarBE
, fScalarB
* fScalarE
* fMaxCosQuad
))
211 // angle between TangentB and Edge is bigger or equal defined by fMaxCos
218 void impSubdivideToSimple(const B2DCubicBezier
& rCandidate
, B2DPolygon
& rTarget
, double fMaxCosQuad
, double fMaxPartOfEdgeQuad
, sal_uInt32 nMaxRecursionDepth
)
220 if(!nMaxRecursionDepth
|| impIsSimpleEdge(rCandidate
, fMaxCosQuad
, fMaxPartOfEdgeQuad
))
222 rTarget
.appendBezierSegment(rCandidate
.getControlPointA(), rCandidate
.getControlPointB(), rCandidate
.getEndPoint());
226 B2DCubicBezier aLeft
, aRight
;
227 rCandidate
.split(0.5, &aLeft
, &aRight
);
229 impSubdivideToSimple(aLeft
, rTarget
, fMaxCosQuad
, fMaxPartOfEdgeQuad
, nMaxRecursionDepth
- 1);
230 impSubdivideToSimple(aRight
, rTarget
, fMaxCosQuad
, fMaxPartOfEdgeQuad
, nMaxRecursionDepth
- 1);
234 B2DPolygon
subdivideToSimple(const B2DPolygon
& rCandidate
, double fMaxCosQuad
, double fMaxPartOfEdgeQuad
)
236 const sal_uInt32
nPointCount(rCandidate
.count());
238 if(rCandidate
.areControlPointsUsed() && nPointCount
)
240 const sal_uInt32
nEdgeCount(rCandidate
.isClosed() ? nPointCount
: nPointCount
- 1);
242 B2DCubicBezier aEdge
;
244 // prepare edge for loop
245 aEdge
.setStartPoint(rCandidate
.getB2DPoint(0));
246 aRetval
.append(aEdge
.getStartPoint());
248 for(sal_uInt32
a(0); a
< nEdgeCount
; a
++)
250 // fill B2DCubicBezier
251 const sal_uInt32
nNextIndex((a
+ 1) % nPointCount
);
252 aEdge
.setControlPointA(rCandidate
.getNextControlPoint(a
));
253 aEdge
.setControlPointB(rCandidate
.getPrevControlPoint(nNextIndex
));
254 aEdge
.setEndPoint(rCandidate
.getB2DPoint(nNextIndex
));
256 // get rid of unnecessary bezier segments
257 aEdge
.testAndSolveTrivialBezier();
261 // before splitting recursively with internal simple criteria, use
262 // ExtremumPosFinder to remove those
263 ::std::vector
< double > aExtremumPositions
;
265 aExtremumPositions
.reserve(4);
266 aEdge
.getAllExtremumPositions(aExtremumPositions
);
268 const sal_uInt32
nCount(aExtremumPositions
.size());
274 // create order from left to right
275 ::std::sort(aExtremumPositions
.begin(), aExtremumPositions
.end());
278 for(sal_uInt32
b(0); b
< nCount
;)
280 // split aEdge at next split pos
281 B2DCubicBezier aLeft
;
282 const double fSplitPos(aExtremumPositions
[b
++]);
284 aEdge
.split(fSplitPos
, &aLeft
, &aEdge
);
285 aLeft
.testAndSolveTrivialBezier();
290 impSubdivideToSimple(aLeft
, aRetval
, fMaxCosQuad
, fMaxPartOfEdgeQuad
, 6);
294 aRetval
.append(aLeft
.getEndPoint());
299 // correct the remaining split positions to fit to shortened aEdge
300 const double fScaleFactor(1.0 / (1.0 - fSplitPos
));
302 for(sal_uInt32
c(b
); c
< nCount
; c
++)
304 aExtremumPositions
[c
] = (aExtremumPositions
[c
] - fSplitPos
) * fScaleFactor
;
309 // test the shortened rest of aEdge
310 aEdge
.testAndSolveTrivialBezier();
312 // consume right part
315 impSubdivideToSimple(aEdge
, aRetval
, fMaxCosQuad
, fMaxPartOfEdgeQuad
, 6);
319 aRetval
.append(aEdge
.getEndPoint());
324 impSubdivideToSimple(aEdge
, aRetval
, fMaxCosQuad
, fMaxPartOfEdgeQuad
, 6);
329 // straight edge, add point
330 aRetval
.append(aEdge
.getEndPoint());
333 // prepare edge for next step
334 aEdge
.setStartPoint(aEdge
.getEndPoint());
337 // copy closed flag and check for double points
338 aRetval
.setClosed(rCandidate
.isClosed());
339 aRetval
.removeDoublePoints();
349 B2DPolygon
createAreaGeometryForEdge(const B2DCubicBezier
& rEdge
, double fHalfLineWidth
)
351 // create polygon for edge
352 // Unfortunately, while it would be geometrically correct to not add
353 // the in-between points EdgeEnd and EdgeStart, it leads to rounding
354 // errors when converting to integer polygon coordinates for painting
357 // prepare target and data common for upper and lower
358 B2DPolygon aBezierPolygon
;
359 const B2DVector
aPureEdgeVector(rEdge
.getEndPoint() - rEdge
.getStartPoint());
360 const double fEdgeLength(aPureEdgeVector
.getLength());
361 const bool bIsEdgeLengthZero(fTools::equalZero(fEdgeLength
));
362 const B2DVector
aTangentA(rEdge
.getTangent(0.0));
363 const B2DVector
aTangentB(rEdge
.getTangent(1.0));
365 // create upper edge.
367 // create displacement vectors and check if they cut
368 const B2DVector
aPerpendStart(getNormalizedPerpendicular(aTangentA
) * -fHalfLineWidth
);
369 const B2DVector
aPerpendEnd(getNormalizedPerpendicular(aTangentB
) * -fHalfLineWidth
);
371 const tools::CutFlagValue
aCut(tools::findCut(
372 rEdge
.getStartPoint(), aPerpendStart
,
373 rEdge
.getEndPoint(), aPerpendEnd
,
374 CUTFLAG_ALL
, &fCut
));
376 if(CUTFLAG_NONE
!= aCut
)
378 // calculate cut point and add
379 const B2DPoint
aCutPoint(rEdge
.getStartPoint() + (aPerpendStart
* fCut
));
380 aBezierPolygon
.append(aCutPoint
);
384 // create scaled bezier segment
385 const B2DPoint
aStart(rEdge
.getStartPoint() + aPerpendStart
);
386 const B2DPoint
aEnd(rEdge
.getEndPoint() + aPerpendEnd
);
387 const B2DVector
aEdge(aEnd
- aStart
);
388 const double fLength(aEdge
.getLength());
389 const double fScale(bIsEdgeLengthZero
? 1.0 : fLength
/ fEdgeLength
);
390 const B2DVector
fRelNext(rEdge
.getControlPointA() - rEdge
.getStartPoint());
391 const B2DVector
fRelPrev(rEdge
.getControlPointB() - rEdge
.getEndPoint());
393 aBezierPolygon
.append(aStart
);
394 aBezierPolygon
.appendBezierSegment(aStart
+ (fRelNext
* fScale
), aEnd
+ (fRelPrev
* fScale
), aEnd
);
398 // append original in-between point
399 aBezierPolygon
.append(rEdge
.getEndPoint());
401 // create lower edge.
403 // create displacement vectors and check if they cut
404 const B2DVector
aPerpendStart(getNormalizedPerpendicular(aTangentA
) * fHalfLineWidth
);
405 const B2DVector
aPerpendEnd(getNormalizedPerpendicular(aTangentB
) * fHalfLineWidth
);
407 const tools::CutFlagValue
aCut(tools::findCut(
408 rEdge
.getEndPoint(), aPerpendEnd
,
409 rEdge
.getStartPoint(), aPerpendStart
,
410 CUTFLAG_ALL
, &fCut
));
412 if(CUTFLAG_NONE
!= aCut
)
414 // calculate cut point and add
415 const B2DPoint
aCutPoint(rEdge
.getEndPoint() + (aPerpendEnd
* fCut
));
416 aBezierPolygon
.append(aCutPoint
);
420 // create scaled bezier segment
421 const B2DPoint
aStart(rEdge
.getEndPoint() + aPerpendEnd
);
422 const B2DPoint
aEnd(rEdge
.getStartPoint() + aPerpendStart
);
423 const B2DVector
aEdge(aEnd
- aStart
);
424 const double fLength(aEdge
.getLength());
425 const double fScale(bIsEdgeLengthZero
? 1.0 : fLength
/ fEdgeLength
);
426 const B2DVector
fRelNext(rEdge
.getControlPointB() - rEdge
.getEndPoint());
427 const B2DVector
fRelPrev(rEdge
.getControlPointA() - rEdge
.getStartPoint());
429 aBezierPolygon
.append(aStart
);
430 aBezierPolygon
.appendBezierSegment(aStart
+ (fRelNext
* fScale
), aEnd
+ (fRelPrev
* fScale
), aEnd
);
434 // append original in-between point
435 aBezierPolygon
.append(rEdge
.getStartPoint());
438 aBezierPolygon
.setClosed(true);
439 return aBezierPolygon
;
443 // #i101491# emulate rEdge.getTangent call which applies a factor of 0.3 to the
444 // full-length edge vector to have numerically exactly the same results as in the
445 // createAreaGeometryForJoin implementation
446 const B2DVector
aEdgeTangent((rEdge
.getEndPoint() - rEdge
.getStartPoint()) * 0.3);
447 const B2DVector
aPerpendEdgeVector(getNormalizedPerpendicular(aEdgeTangent
) * fHalfLineWidth
);
448 B2DPolygon aEdgePolygon
;
451 aEdgePolygon
.append(rEdge
.getStartPoint() - aPerpendEdgeVector
);
452 aEdgePolygon
.append(rEdge
.getEndPoint() - aPerpendEdgeVector
);
454 // append original in-between point
455 aEdgePolygon
.append(rEdge
.getEndPoint());
458 aEdgePolygon
.append(rEdge
.getEndPoint() + aPerpendEdgeVector
);
459 aEdgePolygon
.append(rEdge
.getStartPoint() + aPerpendEdgeVector
);
461 // append original in-between point
462 aEdgePolygon
.append(rEdge
.getStartPoint());
465 aEdgePolygon
.setClosed(true);
470 B2DPolygon
createAreaGeometryForJoin(
471 const B2DVector
& rTangentPrev
,
472 const B2DVector
& rTangentEdge
,
473 const B2DVector
& rPerpendPrev
,
474 const B2DVector
& rPerpendEdge
,
475 const B2DPoint
& rPoint
,
476 double fHalfLineWidth
,
478 double fMiterMinimumAngle
)
480 OSL_ENSURE(fHalfLineWidth
> 0.0, "createAreaGeometryForJoin: LineWidth too small (!)");
481 OSL_ENSURE(B2DLINEJOIN_NONE
!= eJoin
, "createAreaGeometryForJoin: B2DLINEJOIN_NONE not allowed (!)");
483 // LineJoin from tangent rPerpendPrev to tangent rPerpendEdge in rPoint
484 B2DPolygon aEdgePolygon
;
485 const B2DPoint
aStartPoint(rPoint
+ rPerpendPrev
);
486 const B2DPoint
aEndPoint(rPoint
+ rPerpendEdge
);
488 // test if for Miter, the angle is too small and the fallback
489 // to bevel needs to be used
490 if(B2DLINEJOIN_MITER
== eJoin
)
492 const double fAngle(fabs(rPerpendPrev
.angle(rPerpendEdge
)));
494 if((F_PI
- fAngle
) < fMiterMinimumAngle
)
497 eJoin
= B2DLINEJOIN_BEVEL
;
503 case B2DLINEJOIN_MITER
:
505 aEdgePolygon
.append(aEndPoint
);
506 aEdgePolygon
.append(rPoint
);
507 aEdgePolygon
.append(aStartPoint
);
509 // Look for the cut point between start point along rTangentPrev and
510 // end point along rTangentEdge. -rTangentEdge should be used, but since
511 // the cut value is used for interpolating along the first edge, the negation
512 // is not needed since the same fCut will be found on the first edge.
513 // If it exists, insert it to complete the mitered fill polygon.
515 tools::findCut(aStartPoint
, rTangentPrev
, aEndPoint
, rTangentEdge
, CUTFLAG_ALL
, &fCutPos
);
519 const B2DPoint
aCutPoint(interpolate(aStartPoint
, aStartPoint
+ rTangentPrev
, fCutPos
));
520 aEdgePolygon
.append(aCutPoint
);
525 case B2DLINEJOIN_ROUND
:
527 // use tooling to add needed EllipseSegment
528 double fAngleStart(atan2(rPerpendPrev
.getY(), rPerpendPrev
.getX()));
529 double fAngleEnd(atan2(rPerpendEdge
.getY(), rPerpendEdge
.getX()));
531 // atan2 results are [-PI .. PI], consolidate to [0.0 .. 2PI]
532 if(fAngleStart
< 0.0)
534 fAngleStart
+= F_2PI
;
542 const B2DPolygon
aBow(tools::createPolygonFromEllipseSegment(rPoint
, fHalfLineWidth
, fHalfLineWidth
, fAngleStart
, fAngleEnd
));
547 // use the original start/end positions; the ones from bow creation may be numerically
548 // different due to their different creation. To guarantee good merging quality with edges
549 // and edge roundings (and to reduce point count)
551 aEdgePolygon
.setB2DPoint(0, aStartPoint
);
552 aEdgePolygon
.setB2DPoint(aEdgePolygon
.count() - 1, aEndPoint
);
553 aEdgePolygon
.append(rPoint
);
559 // wanted fall-through to default
562 default: // B2DLINEJOIN_BEVEL
564 aEdgePolygon
.append(aEndPoint
);
565 aEdgePolygon
.append(rPoint
);
566 aEdgePolygon
.append(aStartPoint
);
572 // create last polygon part for edge
573 aEdgePolygon
.setClosed(true);
577 } // end of anonymus namespace
581 B2DPolyPolygon
createAreaGeometry(
582 const B2DPolygon
& rCandidate
,
583 double fHalfLineWidth
,
585 double fMaxAllowedAngle
,
586 double fMaxPartOfEdge
,
587 double fMiterMinimumAngle
)
589 if(fMaxAllowedAngle
> F_PI2
)
591 fMaxAllowedAngle
= F_PI2
;
593 else if(fMaxAllowedAngle
< 0.01 * F_PI2
)
595 fMaxAllowedAngle
= 0.01 * F_PI2
;
598 if(fMaxPartOfEdge
> 1.0)
600 fMaxPartOfEdge
= 1.0;
602 else if(fMaxPartOfEdge
< 0.01)
604 fMaxPartOfEdge
= 0.01;
607 if(fMiterMinimumAngle
> F_PI
)
609 fMiterMinimumAngle
= F_PI
;
611 else if(fMiterMinimumAngle
< 0.01 * F_PI
)
613 fMiterMinimumAngle
= 0.01 * F_PI
;
616 B2DPolygon
aCandidate(rCandidate
);
617 const double fMaxCos(cos(fMaxAllowedAngle
));
619 aCandidate
.removeDoublePoints();
620 aCandidate
= subdivideToSimple(aCandidate
, fMaxCos
* fMaxCos
, fMaxPartOfEdge
* fMaxPartOfEdge
);
622 const sal_uInt32
nPointCount(aCandidate
.count());
626 B2DPolyPolygon aRetval
;
627 const bool bEventuallyCreateLineJoin(B2DLINEJOIN_NONE
!= eJoin
);
628 const bool bIsClosed(aCandidate
.isClosed());
629 const sal_uInt32
nEdgeCount(bIsClosed
? nPointCount
: nPointCount
- 1);
633 B2DCubicBezier aEdge
;
634 B2DCubicBezier aPrev
;
637 aEdge
.setStartPoint(aCandidate
.getB2DPoint(0));
639 if(bIsClosed
&& bEventuallyCreateLineJoin
)
641 // prepare previous edge
642 const sal_uInt32
nPrevIndex(nPointCount
- 1);
643 aPrev
.setStartPoint(aCandidate
.getB2DPoint(nPrevIndex
));
644 aPrev
.setControlPointA(aCandidate
.getNextControlPoint(nPrevIndex
));
645 aPrev
.setControlPointB(aCandidate
.getPrevControlPoint(0));
646 aPrev
.setEndPoint(aEdge
.getStartPoint());
649 for(sal_uInt32
a(0); a
< nEdgeCount
; a
++)
652 const sal_uInt32
nNextIndex((a
+ 1) % nPointCount
);
653 aEdge
.setControlPointA(aCandidate
.getNextControlPoint(a
));
654 aEdge
.setControlPointB(aCandidate
.getPrevControlPoint(nNextIndex
));
655 aEdge
.setEndPoint(aCandidate
.getB2DPoint(nNextIndex
));
657 // check and create linejoin
658 if(bEventuallyCreateLineJoin
&& (bIsClosed
|| 0 != a
))
660 const B2DVector
aTangentPrev(aPrev
.getTangent(1.0));
661 const B2DVector
aTangentEdge(aEdge
.getTangent(0.0));
662 B2VectorOrientation
aOrientation(getOrientation(aTangentPrev
, aTangentEdge
));
664 if(ORIENTATION_NEUTRAL
== aOrientation
)
666 // they are parallell or empty; if they are both not zero and point
667 // in opposite direction, a half-circle is needed
668 if(!aTangentPrev
.equalZero() && !aTangentEdge
.equalZero())
670 const double fAngle(fabs(aTangentPrev
.angle(aTangentEdge
)));
672 if(fTools::equal(fAngle
, F_PI
))
674 // for half-circle production, fallback to positive
676 aOrientation
= ORIENTATION_POSITIVE
;
681 if(ORIENTATION_POSITIVE
== aOrientation
)
683 const B2DVector
aPerpendPrev(getNormalizedPerpendicular(aTangentPrev
) * -fHalfLineWidth
);
684 const B2DVector
aPerpendEdge(getNormalizedPerpendicular(aTangentEdge
) * -fHalfLineWidth
);
686 aRetval
.append(createAreaGeometryForJoin(
687 aTangentPrev
, aTangentEdge
,
688 aPerpendPrev
, aPerpendEdge
,
689 aEdge
.getStartPoint(), fHalfLineWidth
,
690 eJoin
, fMiterMinimumAngle
));
692 else if(ORIENTATION_NEGATIVE
== aOrientation
)
694 const B2DVector
aPerpendPrev(getNormalizedPerpendicular(aTangentPrev
) * fHalfLineWidth
);
695 const B2DVector
aPerpendEdge(getNormalizedPerpendicular(aTangentEdge
) * fHalfLineWidth
);
697 aRetval
.append(createAreaGeometryForJoin(
698 aTangentEdge
, aTangentPrev
,
699 aPerpendEdge
, aPerpendPrev
,
700 aEdge
.getStartPoint(), fHalfLineWidth
,
701 eJoin
, fMiterMinimumAngle
));
705 // create geometry for edge
706 aRetval
.append(createAreaGeometryForEdge(aEdge
, fHalfLineWidth
));
709 if(bEventuallyCreateLineJoin
)
714 aEdge
.setStartPoint(aEdge
.getEndPoint());
722 return B2DPolyPolygon(rCandidate
);
725 } // end of namespace tools
726 } // end of namespace basegfx
728 //////////////////////////////////////////////////////////////////////////////