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 .
21 #include <osl/diagnose.h>
22 #include <basegfx/polygon/b2dlinegeometry.hxx>
23 #include <basegfx/point/b2dpoint.hxx>
24 #include <basegfx/vector/b2dvector.hxx>
25 #include <basegfx/polygon/b2dpolygontools.hxx>
26 #include <basegfx/polygon/b2dpolypolygontools.hxx>
27 #include <basegfx/range/b2drange.hxx>
28 #include <basegfx/matrix/b2dhommatrix.hxx>
29 #include <basegfx/curve/b2dcubicbezier.hxx>
30 #include <basegfx/matrix/b2dhommatrixtools.hxx>
31 #include <com/sun/star/drawing/LineCap.hpp>
32 #include <basegfx/polygon/b2dpolypolygoncutter.hxx>
34 //////////////////////////////////////////////////////////////////////////////
40 B2DPolyPolygon
createAreaGeometryForLineStartEnd(
41 const B2DPolygon
& rCandidate
,
42 const B2DPolyPolygon
& rArrow
,
45 double fCandidateLength
,
46 double fDockingPosition
, // 0->top, 1->bottom
47 double* pConsumedLength
,
50 B2DPolyPolygon aRetval
;
51 OSL_ENSURE(rCandidate
.count() > 1L, "createAreaGeometryForLineStartEnd: Line polygon has too less points (!)");
52 OSL_ENSURE(rArrow
.count() > 0L, "createAreaGeometryForLineStartEnd: Empty arrow PolyPolygon (!)");
53 OSL_ENSURE(fWidth
> 0.0, "createAreaGeometryForLineStartEnd: Width too small (!)");
54 OSL_ENSURE(fDockingPosition
>= 0.0 && fDockingPosition
<= 1.0,
55 "createAreaGeometryForLineStartEnd: fDockingPosition out of range [0.0 .. 1.0] (!)");
62 if(rCandidate
.count() > 1 && rArrow
.count() && !fTools::equalZero(fWidth
))
64 if(fDockingPosition
< 0.0)
66 fDockingPosition
= 0.0;
68 else if(fDockingPosition
> 1.0)
70 fDockingPosition
= 1.0;
73 // init return value from arrow
74 aRetval
.append(rArrow
);
76 // get size of the arrow
77 const B2DRange
aArrowSize(getRange(rArrow
));
79 // build ArrowTransform; center in X, align with axis in Y
80 B2DHomMatrix
aArrowTransform(basegfx::tools::createTranslateB2DHomMatrix(
81 -aArrowSize
.getCenter().getX(), -aArrowSize
.getMinimum().getY()));
83 // scale to target size
84 const double fArrowScale(fWidth
/ (aArrowSize
.getRange().getX()));
85 aArrowTransform
.scale(fArrowScale
, fArrowScale
);
87 // get arrow size in Y
88 B2DPoint
aUpperCenter(aArrowSize
.getCenter().getX(), aArrowSize
.getMaximum().getY());
89 aUpperCenter
*= aArrowTransform
;
90 const double fArrowYLength(B2DVector(aUpperCenter
).getLength());
92 // move arrow to have docking position centered
93 aArrowTransform
.translate(0.0, -fArrowYLength
* fDockingPosition
+ fShift
);
95 // prepare polygon length
96 if(fTools::equalZero(fCandidateLength
))
98 fCandidateLength
= getLength(rCandidate
);
101 // get the polygon vector we want to plant this arrow on
102 const double fConsumedLength(fArrowYLength
* (1.0 - fDockingPosition
) - fShift
);
103 const B2DVector
aHead(rCandidate
.getB2DPoint((bStart
) ? 0L : rCandidate
.count() - 1L));
104 const B2DVector
aTail(getPositionAbsolute(rCandidate
,
105 (bStart
) ? fConsumedLength
: fCandidateLength
- fConsumedLength
, fCandidateLength
));
107 // from that vector, take the needed rotation and add rotate for arrow to transformation
108 const B2DVector
aTargetDirection(aHead
- aTail
);
109 const double fRotation(atan2(aTargetDirection
.getY(), aTargetDirection
.getX()) + (90.0 * F_PI180
));
111 // rotate around docking position
112 aArrowTransform
.rotate(fRotation
);
114 // move arrow docking position to polygon head
115 aArrowTransform
.translate(aHead
.getX(), aHead
.getY());
117 // transform retval and close
118 aRetval
.transform(aArrowTransform
);
119 aRetval
.setClosed(true);
121 // if pConsumedLength is asked for, fill it
124 *pConsumedLength
= fConsumedLength
;
130 } // end of namespace tools
131 } // end of namespace basegfx
133 //////////////////////////////////////////////////////////////////////////////
137 // anonymus namespace for local helpers
140 bool impIsSimpleEdge(const B2DCubicBezier
& rCandidate
, double fMaxCosQuad
, double fMaxPartOfEdgeQuad
)
142 // isBezier() is true, already tested by caller
143 const B2DVector
aEdge(rCandidate
.getEndPoint() - rCandidate
.getStartPoint());
145 if(aEdge
.equalZero())
147 // start and end point the same, but control vectors used -> baloon curve loop
148 // is not a simple edge
152 // get tangentA and scalar with edge
153 const B2DVector
aTangentA(rCandidate
.getTangent(0.0));
154 const double fScalarAE(aEdge
.scalar(aTangentA
));
156 if(fTools::lessOrEqual(fScalarAE
, 0.0))
158 // angle between TangentA and Edge is bigger or equal 90 degrees
162 // get self-scalars for E and A
163 const double fScalarE(aEdge
.scalar(aEdge
));
164 const double fScalarA(aTangentA
.scalar(aTangentA
));
165 const double fLengthCompareE(fScalarE
* fMaxPartOfEdgeQuad
);
167 if(fTools::moreOrEqual(fScalarA
, fLengthCompareE
))
169 // length of TangentA is more than fMaxPartOfEdge of length of edge
173 if(fTools::lessOrEqual(fScalarAE
* fScalarAE
, fScalarA
* fScalarE
* fMaxCosQuad
))
175 // angle between TangentA and Edge is bigger or equal angle defined by fMaxCos
179 // get tangentB and scalar with edge
180 const B2DVector
aTangentB(rCandidate
.getTangent(1.0));
181 const double fScalarBE(aEdge
.scalar(aTangentB
));
183 if(fTools::lessOrEqual(fScalarBE
, 0.0))
185 // angle between TangentB and Edge is bigger or equal 90 degrees
189 // get self-scalar for B
190 const double fScalarB(aTangentB
.scalar(aTangentB
));
192 if(fTools::moreOrEqual(fScalarB
, fLengthCompareE
))
194 // length of TangentB is more than fMaxPartOfEdge of length of edge
198 if(fTools::lessOrEqual(fScalarBE
* fScalarBE
, fScalarB
* fScalarE
* fMaxCosQuad
))
200 // angle between TangentB and Edge is bigger or equal defined by fMaxCos
207 void impSubdivideToSimple(const B2DCubicBezier
& rCandidate
, B2DPolygon
& rTarget
, double fMaxCosQuad
, double fMaxPartOfEdgeQuad
, sal_uInt32 nMaxRecursionDepth
)
209 if(!nMaxRecursionDepth
|| impIsSimpleEdge(rCandidate
, fMaxCosQuad
, fMaxPartOfEdgeQuad
))
211 rTarget
.appendBezierSegment(rCandidate
.getControlPointA(), rCandidate
.getControlPointB(), rCandidate
.getEndPoint());
215 B2DCubicBezier aLeft
, aRight
;
216 rCandidate
.split(0.5, &aLeft
, &aRight
);
218 impSubdivideToSimple(aLeft
, rTarget
, fMaxCosQuad
, fMaxPartOfEdgeQuad
, nMaxRecursionDepth
- 1);
219 impSubdivideToSimple(aRight
, rTarget
, fMaxCosQuad
, fMaxPartOfEdgeQuad
, nMaxRecursionDepth
- 1);
223 B2DPolygon
subdivideToSimple(const B2DPolygon
& rCandidate
, double fMaxCosQuad
, double fMaxPartOfEdgeQuad
)
225 const sal_uInt32
nPointCount(rCandidate
.count());
227 if(rCandidate
.areControlPointsUsed() && nPointCount
)
229 const sal_uInt32
nEdgeCount(rCandidate
.isClosed() ? nPointCount
: nPointCount
- 1);
231 B2DCubicBezier aEdge
;
233 // prepare edge for loop
234 aEdge
.setStartPoint(rCandidate
.getB2DPoint(0));
235 aRetval
.append(aEdge
.getStartPoint());
237 for(sal_uInt32
a(0); a
< nEdgeCount
; a
++)
239 // fill B2DCubicBezier
240 const sal_uInt32
nNextIndex((a
+ 1) % nPointCount
);
241 aEdge
.setControlPointA(rCandidate
.getNextControlPoint(a
));
242 aEdge
.setControlPointB(rCandidate
.getPrevControlPoint(nNextIndex
));
243 aEdge
.setEndPoint(rCandidate
.getB2DPoint(nNextIndex
));
245 // get rid of unnecessary bezier segments
246 aEdge
.testAndSolveTrivialBezier();
250 // before splitting recursively with internal simple criteria, use
251 // ExtremumPosFinder to remove those
252 ::std::vector
< double > aExtremumPositions
;
254 aExtremumPositions
.reserve(4);
255 aEdge
.getAllExtremumPositions(aExtremumPositions
);
257 const sal_uInt32
nCount(aExtremumPositions
.size());
263 // create order from left to right
264 ::std::sort(aExtremumPositions
.begin(), aExtremumPositions
.end());
267 for(sal_uInt32
b(0); b
< nCount
;)
269 // split aEdge at next split pos
270 B2DCubicBezier aLeft
;
271 const double fSplitPos(aExtremumPositions
[b
++]);
273 aEdge
.split(fSplitPos
, &aLeft
, &aEdge
);
274 aLeft
.testAndSolveTrivialBezier();
279 impSubdivideToSimple(aLeft
, aRetval
, fMaxCosQuad
, fMaxPartOfEdgeQuad
, 6);
283 aRetval
.append(aLeft
.getEndPoint());
288 // correct the remaining split positions to fit to shortened aEdge
289 const double fScaleFactor(1.0 / (1.0 - fSplitPos
));
291 for(sal_uInt32
c(b
); c
< nCount
; c
++)
293 aExtremumPositions
[c
] = (aExtremumPositions
[c
] - fSplitPos
) * fScaleFactor
;
298 // test the shortened rest of aEdge
299 aEdge
.testAndSolveTrivialBezier();
301 // consume right part
304 impSubdivideToSimple(aEdge
, aRetval
, fMaxCosQuad
, fMaxPartOfEdgeQuad
, 6);
308 aRetval
.append(aEdge
.getEndPoint());
313 impSubdivideToSimple(aEdge
, aRetval
, fMaxCosQuad
, fMaxPartOfEdgeQuad
, 6);
318 // straight edge, add point
319 aRetval
.append(aEdge
.getEndPoint());
322 // prepare edge for next step
323 aEdge
.setStartPoint(aEdge
.getEndPoint());
326 // copy closed flag and check for double points
327 aRetval
.setClosed(rCandidate
.isClosed());
328 aRetval
.removeDoublePoints();
338 B2DPolygon
createAreaGeometryForEdge(
339 const B2DCubicBezier
& rEdge
,
340 double fHalfLineWidth
,
346 // create polygon for edge
347 // Unfortunately, while it would be geometrically correct to not add
348 // the in-between points EdgeEnd and EdgeStart, it leads to rounding
349 // errors when converting to integer polygon coordinates for painting
352 // prepare target and data common for upper and lower
353 B2DPolygon aBezierPolygon
;
354 const B2DVector
aPureEdgeVector(rEdge
.getEndPoint() - rEdge
.getStartPoint());
355 const double fEdgeLength(aPureEdgeVector
.getLength());
356 const bool bIsEdgeLengthZero(fTools::equalZero(fEdgeLength
));
357 B2DVector
aTangentA(rEdge
.getTangent(0.0)); aTangentA
.normalize();
358 B2DVector
aTangentB(rEdge
.getTangent(1.0)); aTangentB
.normalize();
359 const B2DVector
aNormalizedPerpendicularA(getPerpendicular(aTangentA
));
360 const B2DVector
aNormalizedPerpendicularB(getPerpendicular(aTangentB
));
362 // create upper displacement vectors and check if they cut
363 const B2DVector
aPerpendStartA(aNormalizedPerpendicularA
* -fHalfLineWidth
);
364 const B2DVector
aPerpendEndA(aNormalizedPerpendicularB
* -fHalfLineWidth
);
366 const tools::CutFlagValue
aCutA(tools::findCut(
367 rEdge
.getStartPoint(), aPerpendStartA
,
368 rEdge
.getEndPoint(), aPerpendEndA
,
369 CUTFLAG_ALL
, &fCutA
));
370 const bool bCutA(CUTFLAG_NONE
!= aCutA
);
372 // create lower displacement vectors and check if they cut
373 const B2DVector
aPerpendStartB(aNormalizedPerpendicularA
* fHalfLineWidth
);
374 const B2DVector
aPerpendEndB(aNormalizedPerpendicularB
* fHalfLineWidth
);
376 const tools::CutFlagValue
aCutB(tools::findCut(
377 rEdge
.getEndPoint(), aPerpendEndB
,
378 rEdge
.getStartPoint(), aPerpendStartB
,
379 CUTFLAG_ALL
, &fCutB
));
380 const bool bCutB(CUTFLAG_NONE
!= aCutB
);
382 // check if cut happens
383 const bool bCut(bCutA
|| bCutB
);
387 if(bStartRound
|| bStartSquare
)
391 basegfx::B2DPolygon
aStartPolygon(tools::createHalfUnitCircle());
393 aStartPolygon
.transform(
394 tools::createScaleShearXRotateTranslateB2DHomMatrix(
395 fHalfLineWidth
, fHalfLineWidth
,
397 atan2(aTangentA
.getY(), aTangentA
.getX()) + F_PI2
,
398 rEdge
.getStartPoint().getX(), rEdge
.getStartPoint().getY()));
399 aBezierPolygon
.append(aStartPolygon
);
403 const basegfx::B2DPoint
aStart(rEdge
.getStartPoint() - (aTangentA
* fHalfLineWidth
));
407 aBezierPolygon
.append(rEdge
.getStartPoint() + aPerpendStartB
);
410 aBezierPolygon
.append(aStart
+ aPerpendStartB
);
411 aBezierPolygon
.append(aStart
+ aPerpendStartA
);
415 aBezierPolygon
.append(rEdge
.getStartPoint() + aPerpendStartA
);
421 // append original in-between point
422 aBezierPolygon
.append(rEdge
.getStartPoint());
425 // create upper edge.
429 // calculate cut point and add
430 aCutPoint
= rEdge
.getStartPoint() + (aPerpendStartA
* fCutA
);
431 aBezierPolygon
.append(aCutPoint
);
435 // create scaled bezier segment
436 const B2DPoint
aStart(rEdge
.getStartPoint() + aPerpendStartA
);
437 const B2DPoint
aEnd(rEdge
.getEndPoint() + aPerpendEndA
);
438 const B2DVector
aEdge(aEnd
- aStart
);
439 const double fLength(aEdge
.getLength());
440 const double fScale(bIsEdgeLengthZero
? 1.0 : fLength
/ fEdgeLength
);
441 const B2DVector
fRelNext(rEdge
.getControlPointA() - rEdge
.getStartPoint());
442 const B2DVector
fRelPrev(rEdge
.getControlPointB() - rEdge
.getEndPoint());
444 aBezierPolygon
.append(aStart
);
445 aBezierPolygon
.appendBezierSegment(aStart
+ (fRelNext
* fScale
), aEnd
+ (fRelPrev
* fScale
), aEnd
);
450 if(bEndRound
|| bEndSquare
)
454 basegfx::B2DPolygon
aEndPolygon(tools::createHalfUnitCircle());
456 aEndPolygon
.transform(
457 tools::createScaleShearXRotateTranslateB2DHomMatrix(
458 fHalfLineWidth
, fHalfLineWidth
,
460 atan2(aTangentB
.getY(), aTangentB
.getX()) - F_PI2
,
461 rEdge
.getEndPoint().getX(), rEdge
.getEndPoint().getY()));
462 aBezierPolygon
.append(aEndPolygon
);
466 const basegfx::B2DPoint
aEnd(rEdge
.getEndPoint() + (aTangentB
* fHalfLineWidth
));
470 aBezierPolygon
.append(rEdge
.getEndPoint() + aPerpendEndA
);
473 aBezierPolygon
.append(aEnd
+ aPerpendEndA
);
474 aBezierPolygon
.append(aEnd
+ aPerpendEndB
);
478 aBezierPolygon
.append(rEdge
.getEndPoint() + aPerpendEndB
);
484 // append original in-between point
485 aBezierPolygon
.append(rEdge
.getEndPoint());
488 // create lower edge.
492 // calculate cut point and add
493 aCutPoint
= rEdge
.getEndPoint() + (aPerpendEndB
* fCutB
);
494 aBezierPolygon
.append(aCutPoint
);
498 // create scaled bezier segment
499 const B2DPoint
aStart(rEdge
.getEndPoint() + aPerpendEndB
);
500 const B2DPoint
aEnd(rEdge
.getStartPoint() + aPerpendStartB
);
501 const B2DVector
aEdge(aEnd
- aStart
);
502 const double fLength(aEdge
.getLength());
503 const double fScale(bIsEdgeLengthZero
? 1.0 : fLength
/ fEdgeLength
);
504 const B2DVector
fRelNext(rEdge
.getControlPointB() - rEdge
.getEndPoint());
505 const B2DVector
fRelPrev(rEdge
.getControlPointA() - rEdge
.getStartPoint());
507 aBezierPolygon
.append(aStart
);
508 aBezierPolygon
.appendBezierSegment(aStart
+ (fRelNext
* fScale
), aEnd
+ (fRelPrev
* fScale
), aEnd
);
513 aBezierPolygon
.setClosed(true);
515 if(bStartRound
|| bEndRound
)
517 // double points possible when round caps are used at start or end
518 aBezierPolygon
.removeDoublePoints();
521 if(bCut
&& ((bStartRound
|| bStartSquare
) && (bEndRound
|| bEndSquare
)))
523 // When cut exists and both ends are extended with caps, a self-intersecting polygon
524 // is created; one cut point is known, but there is a 2nd one in the caps geometry.
525 // Solve by using tooling.
526 // Remark: This nearly never happens due to curve preparations to extreme points
527 // and maximum angle turning, but I constructed a test case and checkd that it is
529 const B2DPolyPolygon
aTemp(tools::solveCrossovers(aBezierPolygon
));
530 const sal_uInt32
nTempCount(aTemp
.count());
536 // as expected, multiple polygons (with same orientation). Remove
537 // the one which contains aCutPoint, or better take the one without
538 for (sal_uInt32
a(0); a
< nTempCount
; a
++)
540 aBezierPolygon
= aTemp
.getB2DPolygon(a
);
542 const sal_uInt32
nCandCount(aBezierPolygon
.count());
544 for(sal_uInt32
b(0); b
< nCandCount
; b
++)
546 if(aCutPoint
.equal(aBezierPolygon
.getB2DPoint(b
)))
548 aBezierPolygon
.clear();
553 if(aBezierPolygon
.count())
559 OSL_ENSURE(aBezierPolygon
.count(), "Error in line geometry creation, could not solve self-intersection (!)");
563 // none found, use result
564 aBezierPolygon
= aTemp
.getB2DPolygon(0);
569 OSL_ENSURE(false, "Error in line geometry creation, could not solve self-intersection (!)");
574 return aBezierPolygon
;
578 // Get start and end point, create tangent and set to needed length
579 B2DVector
aTangent(rEdge
.getEndPoint() - rEdge
.getStartPoint());
580 aTangent
.setLength(fHalfLineWidth
);
582 // prepare return value
583 B2DPolygon aEdgePolygon
;
589 // buffered perpendicular
591 bool bPerpend(false);
593 // create left vertical
596 aEdgePolygon
= tools::createHalfUnitCircle();
597 fAngle
= atan2(aTangent
.getY(), aTangent
.getX());
599 aEdgePolygon
.transform(
600 tools::createScaleShearXRotateTranslateB2DHomMatrix(
601 fHalfLineWidth
, fHalfLineWidth
,
604 rEdge
.getStartPoint().getX(), rEdge
.getStartPoint().getY()));
608 aPerpend
.setX(-aTangent
.getY());
609 aPerpend
.setY(aTangent
.getX());
614 const basegfx::B2DPoint
aStart(rEdge
.getStartPoint() - aTangent
);
616 aEdgePolygon
.append(aStart
+ aPerpend
);
617 aEdgePolygon
.append(aStart
- aPerpend
);
621 aEdgePolygon
.append(rEdge
.getStartPoint() + aPerpend
);
622 aEdgePolygon
.append(rEdge
.getStartPoint()); // keep the in-between point for numerical reasons
623 aEdgePolygon
.append(rEdge
.getStartPoint() - aPerpend
);
627 // create right vertical
630 basegfx::B2DPolygon
aEndPolygon(tools::createHalfUnitCircle());
634 fAngle
= atan2(aTangent
.getY(), aTangent
.getX());
637 aEndPolygon
.transform(
638 tools::createScaleShearXRotateTranslateB2DHomMatrix(
639 fHalfLineWidth
, fHalfLineWidth
,
642 rEdge
.getEndPoint().getX(), rEdge
.getEndPoint().getY()));
643 aEdgePolygon
.append(aEndPolygon
);
649 aPerpend
.setX(-aTangent
.getY());
650 aPerpend
.setY(aTangent
.getX());
655 const basegfx::B2DPoint
aEnd(rEdge
.getEndPoint() + aTangent
);
657 aEdgePolygon
.append(aEnd
- aPerpend
);
658 aEdgePolygon
.append(aEnd
+ aPerpend
);
662 aEdgePolygon
.append(rEdge
.getEndPoint() - aPerpend
);
663 aEdgePolygon
.append(rEdge
.getEndPoint()); // keep the in-between point for numerical reasons
664 aEdgePolygon
.append(rEdge
.getEndPoint() + aPerpend
);
669 aEdgePolygon
.setClosed(true);
675 B2DPolygon
createAreaGeometryForJoin(
676 const B2DVector
& rTangentPrev
,
677 const B2DVector
& rTangentEdge
,
678 const B2DVector
& rPerpendPrev
,
679 const B2DVector
& rPerpendEdge
,
680 const B2DPoint
& rPoint
,
681 double fHalfLineWidth
,
683 double fMiterMinimumAngle
)
685 OSL_ENSURE(fHalfLineWidth
> 0.0, "createAreaGeometryForJoin: LineWidth too small (!)");
686 OSL_ENSURE(B2DLINEJOIN_NONE
!= eJoin
, "createAreaGeometryForJoin: B2DLINEJOIN_NONE not allowed (!)");
688 // LineJoin from tangent rPerpendPrev to tangent rPerpendEdge in rPoint
689 B2DPolygon aEdgePolygon
;
690 const B2DPoint
aStartPoint(rPoint
+ rPerpendPrev
);
691 const B2DPoint
aEndPoint(rPoint
+ rPerpendEdge
);
693 // test if for Miter, the angle is too small and the fallback
694 // to bevel needs to be used
695 if(B2DLINEJOIN_MITER
== eJoin
)
697 const double fAngle(fabs(rPerpendPrev
.angle(rPerpendEdge
)));
699 if((F_PI
- fAngle
) < fMiterMinimumAngle
)
702 eJoin
= B2DLINEJOIN_BEVEL
;
708 case B2DLINEJOIN_MITER
:
710 aEdgePolygon
.append(aEndPoint
);
711 aEdgePolygon
.append(rPoint
);
712 aEdgePolygon
.append(aStartPoint
);
714 // Look for the cut point between start point along rTangentPrev and
715 // end point along rTangentEdge. -rTangentEdge should be used, but since
716 // the cut value is used for interpolating along the first edge, the negation
717 // is not needed since the same fCut will be found on the first edge.
718 // If it exists, insert it to complete the mitered fill polygon.
720 tools::findCut(aStartPoint
, rTangentPrev
, aEndPoint
, rTangentEdge
, CUTFLAG_ALL
, &fCutPos
);
724 const B2DPoint
aCutPoint(aStartPoint
+ (rTangentPrev
* fCutPos
));
725 aEdgePolygon
.append(aCutPoint
);
730 case B2DLINEJOIN_ROUND
:
732 // use tooling to add needed EllipseSegment
733 double fAngleStart(atan2(rPerpendPrev
.getY(), rPerpendPrev
.getX()));
734 double fAngleEnd(atan2(rPerpendEdge
.getY(), rPerpendEdge
.getX()));
736 // atan2 results are [-PI .. PI], consolidate to [0.0 .. 2PI]
737 if(fAngleStart
< 0.0)
739 fAngleStart
+= F_2PI
;
747 const B2DPolygon
aBow(tools::createPolygonFromEllipseSegment(rPoint
, fHalfLineWidth
, fHalfLineWidth
, fAngleStart
, fAngleEnd
));
752 // use the original start/end positions; the ones from bow creation may be numerically
753 // different due to their different creation. To guarantee good merging quality with edges
754 // and edge roundings (and to reduce point count)
756 aEdgePolygon
.setB2DPoint(0, aStartPoint
);
757 aEdgePolygon
.setB2DPoint(aEdgePolygon
.count() - 1, aEndPoint
);
758 aEdgePolygon
.append(rPoint
);
764 // wanted fall-through to default
767 default: // B2DLINEJOIN_BEVEL
769 aEdgePolygon
.append(aEndPoint
);
770 aEdgePolygon
.append(rPoint
);
771 aEdgePolygon
.append(aStartPoint
);
777 // create last polygon part for edge
778 aEdgePolygon
.setClosed(true);
782 } // end of anonymus namespace
786 B2DPolyPolygon
createAreaGeometry(
787 const B2DPolygon
& rCandidate
,
788 double fHalfLineWidth
,
790 com::sun::star::drawing::LineCap eCap
,
791 double fMaxAllowedAngle
,
792 double fMaxPartOfEdge
,
793 double fMiterMinimumAngle
)
795 if(fMaxAllowedAngle
> F_PI2
)
797 fMaxAllowedAngle
= F_PI2
;
799 else if(fMaxAllowedAngle
< 0.01 * F_PI2
)
801 fMaxAllowedAngle
= 0.01 * F_PI2
;
804 if(fMaxPartOfEdge
> 1.0)
806 fMaxPartOfEdge
= 1.0;
808 else if(fMaxPartOfEdge
< 0.01)
810 fMaxPartOfEdge
= 0.01;
813 if(fMiterMinimumAngle
> F_PI
)
815 fMiterMinimumAngle
= F_PI
;
817 else if(fMiterMinimumAngle
< 0.01 * F_PI
)
819 fMiterMinimumAngle
= 0.01 * F_PI
;
822 B2DPolygon
aCandidate(rCandidate
);
823 const double fMaxCos(cos(fMaxAllowedAngle
));
825 aCandidate
.removeDoublePoints();
826 aCandidate
= subdivideToSimple(aCandidate
, fMaxCos
* fMaxCos
, fMaxPartOfEdge
* fMaxPartOfEdge
);
828 const sal_uInt32
nPointCount(aCandidate
.count());
832 B2DPolyPolygon aRetval
;
833 const bool bIsClosed(aCandidate
.isClosed());
834 const sal_uInt32
nEdgeCount(bIsClosed
? nPointCount
: nPointCount
- 1);
835 const bool bLineCap(!bIsClosed
&& com::sun::star::drawing::LineCap_BUTT
!= eCap
);
839 B2DCubicBezier aEdge
;
840 B2DCubicBezier aPrev
;
842 const bool bEventuallyCreateLineJoin(B2DLINEJOIN_NONE
!= eJoin
);
844 aEdge
.setStartPoint(aCandidate
.getB2DPoint(0));
846 if(bIsClosed
&& bEventuallyCreateLineJoin
)
848 // prepare previous edge
849 const sal_uInt32
nPrevIndex(nPointCount
- 1);
850 aPrev
.setStartPoint(aCandidate
.getB2DPoint(nPrevIndex
));
851 aPrev
.setControlPointA(aCandidate
.getNextControlPoint(nPrevIndex
));
852 aPrev
.setControlPointB(aCandidate
.getPrevControlPoint(0));
853 aPrev
.setEndPoint(aEdge
.getStartPoint());
856 for(sal_uInt32
a(0); a
< nEdgeCount
; a
++)
859 const sal_uInt32
nNextIndex((a
+ 1) % nPointCount
);
860 aEdge
.setControlPointA(aCandidate
.getNextControlPoint(a
));
861 aEdge
.setControlPointB(aCandidate
.getPrevControlPoint(nNextIndex
));
862 aEdge
.setEndPoint(aCandidate
.getB2DPoint(nNextIndex
));
864 // check and create linejoin
865 if(bEventuallyCreateLineJoin
&& (bIsClosed
|| 0 != a
))
867 B2DVector
aTangentPrev(aPrev
.getTangent(1.0)); aTangentPrev
.normalize();
868 B2DVector
aTangentEdge(aEdge
.getTangent(0.0)); aTangentEdge
.normalize();
869 B2VectorOrientation
aOrientation(getOrientation(aTangentPrev
, aTangentEdge
));
871 if(ORIENTATION_NEUTRAL
== aOrientation
)
873 // they are parallell or empty; if they are both not zero and point
874 // in opposite direction, a half-circle is needed
875 if(!aTangentPrev
.equalZero() && !aTangentEdge
.equalZero())
877 const double fAngle(fabs(aTangentPrev
.angle(aTangentEdge
)));
879 if(fTools::equal(fAngle
, F_PI
))
881 // for half-circle production, fallback to positive
883 aOrientation
= ORIENTATION_POSITIVE
;
888 if(ORIENTATION_POSITIVE
== aOrientation
)
890 const B2DVector
aPerpendPrev(getPerpendicular(aTangentPrev
) * -fHalfLineWidth
);
891 const B2DVector
aPerpendEdge(getPerpendicular(aTangentEdge
) * -fHalfLineWidth
);
894 createAreaGeometryForJoin(
899 aEdge
.getStartPoint(),
902 fMiterMinimumAngle
));
904 else if(ORIENTATION_NEGATIVE
== aOrientation
)
906 const B2DVector
aPerpendPrev(getPerpendicular(aTangentPrev
) * fHalfLineWidth
);
907 const B2DVector
aPerpendEdge(getPerpendicular(aTangentEdge
) * fHalfLineWidth
);
910 createAreaGeometryForJoin(
915 aEdge
.getStartPoint(),
918 fMiterMinimumAngle
));
922 // create geometry for edge
923 const bool bLast(a
+ 1 == nEdgeCount
);
927 const bool bFirst(!a
);
930 createAreaGeometryForEdge(
933 bFirst
&& com::sun::star::drawing::LineCap_ROUND
== eCap
,
934 bLast
&& com::sun::star::drawing::LineCap_ROUND
== eCap
,
935 bFirst
&& com::sun::star::drawing::LineCap_SQUARE
== eCap
,
936 bLast
&& com::sun::star::drawing::LineCap_SQUARE
== eCap
));
941 createAreaGeometryForEdge(
953 if(bEventuallyCreateLineJoin
)
958 aEdge
.setStartPoint(aEdge
.getEndPoint());
967 return B2DPolyPolygon(rCandidate
);
970 } // end of namespace tools
971 } // end of namespace basegfx
973 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */