Version 4.2.0.1, tag libreoffice-4.2.0.1
[LibreOffice.git] / basegfx / source / polygon / b2dlinegeometry.cxx
blob417944dde525036a1d0b3195733630f464cb5d68
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 <cstdio>
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 //////////////////////////////////////////////////////////////////////////////
36 namespace basegfx
38 namespace tools
40 B2DPolyPolygon createAreaGeometryForLineStartEnd(
41 const B2DPolygon& rCandidate,
42 const B2DPolyPolygon& rArrow,
43 bool bStart,
44 double fWidth,
45 double fCandidateLength,
46 double fDockingPosition, // 0->top, 1->bottom
47 double* pConsumedLength,
48 double fShift)
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] (!)");
57 if(fWidth < 0.0)
59 fWidth = -fWidth;
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
122 if(pConsumedLength)
124 *pConsumedLength = fConsumedLength;
128 return aRetval;
130 } // end of namespace tools
131 } // end of namespace basegfx
133 //////////////////////////////////////////////////////////////////////////////
135 namespace basegfx
137 // anonymus namespace for local helpers
138 namespace
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
149 return false;
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
159 return false;
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
170 return false;
173 if(fTools::lessOrEqual(fScalarAE * fScalarAE, fScalarA * fScalarE * fMaxCosQuad))
175 // angle between TangentA and Edge is bigger or equal angle defined by fMaxCos
176 return false;
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
186 return false;
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
195 return false;
198 if(fTools::lessOrEqual(fScalarBE * fScalarBE, fScalarB * fScalarE * fMaxCosQuad))
200 // angle between TangentB and Edge is bigger or equal defined by fMaxCos
201 return false;
204 return true;
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());
213 else
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);
230 B2DPolygon aRetval;
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();
248 if(aEdge.isBezier())
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());
259 if(nCount)
261 if(nCount > 1)
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();
276 // consume left part
277 if(aLeft.isBezier())
279 impSubdivideToSimple(aLeft, aRetval, fMaxCosQuad, fMaxPartOfEdgeQuad, 6);
281 else
283 aRetval.append(aLeft.getEndPoint());
286 if(b < nCount)
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
302 if(aEdge.isBezier())
304 impSubdivideToSimple(aEdge, aRetval, fMaxCosQuad, fMaxPartOfEdgeQuad, 6);
306 else
308 aRetval.append(aEdge.getEndPoint());
311 else
313 impSubdivideToSimple(aEdge, aRetval, fMaxCosQuad, fMaxPartOfEdgeQuad, 6);
316 else
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();
330 return aRetval;
332 else
334 return rCandidate;
338 B2DPolygon createAreaGeometryForEdge(
339 const B2DCubicBezier& rEdge,
340 double fHalfLineWidth,
341 bool bStartRound,
342 bool bEndRound,
343 bool bStartSquare,
344 bool bEndSquare)
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
350 if(rEdge.isBezier())
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);
365 double fCutA(0.0);
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);
375 double fCutB(0.0);
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);
384 B2DPoint aCutPoint;
386 // create left edge
387 if(bStartRound || bStartSquare)
389 if(bStartRound)
391 basegfx::B2DPolygon aStartPolygon(tools::createHalfUnitCircle());
393 aStartPolygon.transform(
394 tools::createScaleShearXRotateTranslateB2DHomMatrix(
395 fHalfLineWidth, fHalfLineWidth,
396 0.0,
397 atan2(aTangentA.getY(), aTangentA.getX()) + F_PI2,
398 rEdge.getStartPoint().getX(), rEdge.getStartPoint().getY()));
399 aBezierPolygon.append(aStartPolygon);
401 else // bStartSquare
403 const basegfx::B2DPoint aStart(rEdge.getStartPoint() - (aTangentA * fHalfLineWidth));
405 if(bCutB)
407 aBezierPolygon.append(rEdge.getStartPoint() + aPerpendStartB);
410 aBezierPolygon.append(aStart + aPerpendStartB);
411 aBezierPolygon.append(aStart + aPerpendStartA);
413 if(bCutA)
415 aBezierPolygon.append(rEdge.getStartPoint() + aPerpendStartA);
419 else
421 // append original in-between point
422 aBezierPolygon.append(rEdge.getStartPoint());
425 // create upper edge.
427 if(bCutA)
429 // calculate cut point and add
430 aCutPoint = rEdge.getStartPoint() + (aPerpendStartA * fCutA);
431 aBezierPolygon.append(aCutPoint);
433 else
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);
449 // create right edge
450 if(bEndRound || bEndSquare)
452 if(bEndRound)
454 basegfx::B2DPolygon aEndPolygon(tools::createHalfUnitCircle());
456 aEndPolygon.transform(
457 tools::createScaleShearXRotateTranslateB2DHomMatrix(
458 fHalfLineWidth, fHalfLineWidth,
459 0.0,
460 atan2(aTangentB.getY(), aTangentB.getX()) - F_PI2,
461 rEdge.getEndPoint().getX(), rEdge.getEndPoint().getY()));
462 aBezierPolygon.append(aEndPolygon);
464 else // bEndSquare
466 const basegfx::B2DPoint aEnd(rEdge.getEndPoint() + (aTangentB * fHalfLineWidth));
468 if(bCutA)
470 aBezierPolygon.append(rEdge.getEndPoint() + aPerpendEndA);
473 aBezierPolygon.append(aEnd + aPerpendEndA);
474 aBezierPolygon.append(aEnd + aPerpendEndB);
476 if(bCutB)
478 aBezierPolygon.append(rEdge.getEndPoint() + aPerpendEndB);
482 else
484 // append original in-between point
485 aBezierPolygon.append(rEdge.getEndPoint());
488 // create lower edge.
490 if(bCutB)
492 // calculate cut point and add
493 aCutPoint = rEdge.getEndPoint() + (aPerpendEndB * fCutB);
494 aBezierPolygon.append(aCutPoint);
496 else
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);
512 // close
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
528 // working propery.
529 const B2DPolyPolygon aTemp(tools::solveCrossovers(aBezierPolygon));
530 const sal_uInt32 nTempCount(aTemp.count());
532 if(nTempCount)
534 if(nTempCount > 1)
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();
549 break;
553 if(aBezierPolygon.count())
555 break;
559 OSL_ENSURE(aBezierPolygon.count(), "Error in line geometry creation, could not solve self-intersection (!)");
561 else
563 // none found, use result
564 aBezierPolygon = aTemp.getB2DPolygon(0);
567 else
569 OSL_ENSURE(false, "Error in line geometry creation, could not solve self-intersection (!)");
573 // return
574 return aBezierPolygon;
576 else
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;
585 // buffered angle
586 double fAngle(0.0);
587 bool bAngle(false);
589 // buffered perpendicular
590 B2DVector aPerpend;
591 bool bPerpend(false);
593 // create left vertical
594 if(bStartRound)
596 aEdgePolygon = tools::createHalfUnitCircle();
597 fAngle = atan2(aTangent.getY(), aTangent.getX());
598 bAngle = true;
599 aEdgePolygon.transform(
600 tools::createScaleShearXRotateTranslateB2DHomMatrix(
601 fHalfLineWidth, fHalfLineWidth,
602 0.0,
603 fAngle + F_PI2,
604 rEdge.getStartPoint().getX(), rEdge.getStartPoint().getY()));
606 else
608 aPerpend.setX(-aTangent.getY());
609 aPerpend.setY(aTangent.getX());
610 bPerpend = true;
612 if(bStartSquare)
614 const basegfx::B2DPoint aStart(rEdge.getStartPoint() - aTangent);
616 aEdgePolygon.append(aStart + aPerpend);
617 aEdgePolygon.append(aStart - aPerpend);
619 else
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
628 if(bEndRound)
630 basegfx::B2DPolygon aEndPolygon(tools::createHalfUnitCircle());
632 if(!bAngle)
634 fAngle = atan2(aTangent.getY(), aTangent.getX());
637 aEndPolygon.transform(
638 tools::createScaleShearXRotateTranslateB2DHomMatrix(
639 fHalfLineWidth, fHalfLineWidth,
640 0.0,
641 fAngle - F_PI2,
642 rEdge.getEndPoint().getX(), rEdge.getEndPoint().getY()));
643 aEdgePolygon.append(aEndPolygon);
645 else
647 if(!bPerpend)
649 aPerpend.setX(-aTangent.getY());
650 aPerpend.setY(aTangent.getX());
653 if(bEndSquare)
655 const basegfx::B2DPoint aEnd(rEdge.getEndPoint() + aTangent);
657 aEdgePolygon.append(aEnd - aPerpend);
658 aEdgePolygon.append(aEnd + aPerpend);
660 else
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);
668 // close and return
669 aEdgePolygon.setClosed(true);
671 return aEdgePolygon;
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,
682 B2DLineJoin eJoin,
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)
701 // fallback to bevel
702 eJoin = B2DLINEJOIN_BEVEL;
706 switch(eJoin)
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.
719 double fCutPos(0.0);
720 tools::findCut(aStartPoint, rTangentPrev, aEndPoint, rTangentEdge, CUTFLAG_ALL, &fCutPos);
722 if(0.0 != fCutPos)
724 const B2DPoint aCutPoint(aStartPoint + (rTangentPrev * fCutPos));
725 aEdgePolygon.append(aCutPoint);
728 break;
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;
742 if(fAngleEnd < 0.0)
744 fAngleEnd += F_2PI;
747 const B2DPolygon aBow(tools::createPolygonFromEllipseSegment(rPoint, fHalfLineWidth, fHalfLineWidth, fAngleStart, fAngleEnd));
749 if(aBow.count() > 1)
751 // #i101491#
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)
755 aEdgePolygon = aBow;
756 aEdgePolygon.setB2DPoint(0, aStartPoint);
757 aEdgePolygon.setB2DPoint(aEdgePolygon.count() - 1, aEndPoint);
758 aEdgePolygon.append(rPoint);
760 break;
762 else
764 // wanted fall-through to default
767 default: // B2DLINEJOIN_BEVEL
769 aEdgePolygon.append(aEndPoint);
770 aEdgePolygon.append(rPoint);
771 aEdgePolygon.append(aStartPoint);
773 break;
777 // create last polygon part for edge
778 aEdgePolygon.setClosed(true);
780 return aEdgePolygon;
782 } // end of anonymus namespace
784 namespace tools
786 B2DPolyPolygon createAreaGeometry(
787 const B2DPolygon& rCandidate,
788 double fHalfLineWidth,
789 B2DLineJoin eJoin,
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());
830 if(nPointCount)
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);
837 if(nEdgeCount)
839 B2DCubicBezier aEdge;
840 B2DCubicBezier aPrev;
842 const bool bEventuallyCreateLineJoin(B2DLINEJOIN_NONE != eJoin);
843 // prepare edge
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++)
858 // fill current Edge
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
882 // orientation
883 aOrientation = ORIENTATION_POSITIVE;
888 if(ORIENTATION_POSITIVE == aOrientation)
890 const B2DVector aPerpendPrev(getPerpendicular(aTangentPrev) * -fHalfLineWidth);
891 const B2DVector aPerpendEdge(getPerpendicular(aTangentEdge) * -fHalfLineWidth);
893 aRetval.append(
894 createAreaGeometryForJoin(
895 aTangentPrev,
896 aTangentEdge,
897 aPerpendPrev,
898 aPerpendEdge,
899 aEdge.getStartPoint(),
900 fHalfLineWidth,
901 eJoin,
902 fMiterMinimumAngle));
904 else if(ORIENTATION_NEGATIVE == aOrientation)
906 const B2DVector aPerpendPrev(getPerpendicular(aTangentPrev) * fHalfLineWidth);
907 const B2DVector aPerpendEdge(getPerpendicular(aTangentEdge) * fHalfLineWidth);
909 aRetval.append(
910 createAreaGeometryForJoin(
911 aTangentEdge,
912 aTangentPrev,
913 aPerpendEdge,
914 aPerpendPrev,
915 aEdge.getStartPoint(),
916 fHalfLineWidth,
917 eJoin,
918 fMiterMinimumAngle));
922 // create geometry for edge
923 const bool bLast(a + 1 == nEdgeCount);
925 if(bLineCap)
927 const bool bFirst(!a);
929 aRetval.append(
930 createAreaGeometryForEdge(
931 aEdge,
932 fHalfLineWidth,
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));
938 else
940 aRetval.append(
941 createAreaGeometryForEdge(
942 aEdge,
943 fHalfLineWidth,
944 false,
945 false,
946 false,
947 false));
950 // prepare next step
951 if(!bLast)
953 if(bEventuallyCreateLineJoin)
955 aPrev = aEdge;
958 aEdge.setStartPoint(aEdge.getEndPoint());
963 return aRetval;
965 else
967 return B2DPolyPolygon(rCandidate);
970 } // end of namespace tools
971 } // end of namespace basegfx
973 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */