xref: /trunk/main/basegfx/source/polygon/b2dlinegeometry.cxx (revision cdf0e10c4e3984b49a9502b011690b615761d4a3)
1 /*************************************************************************
2  *
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * Copyright 2000, 2010 Oracle and/or its affiliates.
6  *
7  * OpenOffice.org - a multi-platform office productivity suite
8  *
9  * This file is part of OpenOffice.org.
10  *
11  * OpenOffice.org is free software: you can redistribute it and/or modify
12  * it under the terms of the GNU Lesser General Public License version 3
13  * only, as published by the Free Software Foundation.
14  *
15  * OpenOffice.org is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU Lesser General Public License version 3 for more details
19  * (a copy is included in the LICENSE file that accompanied this code).
20  *
21  * You should have received a copy of the GNU Lesser General Public License
22  * version 3 along with OpenOffice.org.  If not, see
23  * <http://www.openoffice.org/license.html>
24  * for a copy of the LGPLv3 License.
25  *
26  ************************************************************************/
27 
28 // MARKER(update_precomp.py): autogen include statement, do not remove
29 #include "precompiled_basegfx.hxx"
30 #include <cstdio>
31 #include <osl/diagnose.h>
32 #include <basegfx/polygon/b2dlinegeometry.hxx>
33 #include <basegfx/point/b2dpoint.hxx>
34 #include <basegfx/vector/b2dvector.hxx>
35 #include <basegfx/polygon/b2dpolygontools.hxx>
36 #include <basegfx/polygon/b2dpolypolygontools.hxx>
37 #include <basegfx/range/b2drange.hxx>
38 #include <basegfx/matrix/b2dhommatrix.hxx>
39 #include <basegfx/curve/b2dcubicbezier.hxx>
40 #include <basegfx/matrix/b2dhommatrixtools.hxx>
41 
42 //////////////////////////////////////////////////////////////////////////////
43 
44 namespace basegfx
45 {
46     namespace tools
47     {
48         B2DPolyPolygon createAreaGeometryForLineStartEnd(
49             const B2DPolygon& rCandidate,
50             const B2DPolyPolygon& rArrow,
51             bool bStart,
52             double fWidth,
53             double fCandidateLength,
54             double fDockingPosition, // 0->top, 1->bottom
55             double* pConsumedLength)
56         {
57             B2DPolyPolygon aRetval;
58             OSL_ENSURE(rCandidate.count() > 1L, "createAreaGeometryForLineStartEnd: Line polygon has too less points (!)");
59             OSL_ENSURE(rArrow.count() > 0L, "createAreaGeometryForLineStartEnd: Empty arrow PolyPolygon (!)");
60             OSL_ENSURE(fWidth > 0.0, "createAreaGeometryForLineStartEnd: Width too small (!)");
61             OSL_ENSURE(fDockingPosition >= 0.0 && fDockingPosition <= 1.0,
62                 "createAreaGeometryForLineStartEnd: fDockingPosition out of range [0.0 .. 1.0] (!)");
63 
64             if(fWidth < 0.0)
65             {
66                 fWidth = -fWidth;
67             }
68 
69             if(rCandidate.count() > 1 && rArrow.count() && !fTools::equalZero(fWidth))
70             {
71                 if(fDockingPosition < 0.0)
72                 {
73                     fDockingPosition = 0.0;
74                 }
75                 else if(fDockingPosition > 1.0)
76                 {
77                     fDockingPosition = 1.0;
78                 }
79 
80                 // init return value from arrow
81                 aRetval.append(rArrow);
82 
83                 // get size of the arrow
84                 const B2DRange aArrowSize(getRange(rArrow));
85 
86                 // build ArrowTransform; center in X, align with axis in Y
87                 B2DHomMatrix aArrowTransform(basegfx::tools::createTranslateB2DHomMatrix(
88                     -aArrowSize.getCenter().getX(), -aArrowSize.getMinimum().getY()));
89 
90                 // scale to target size
91                 const double fArrowScale(fWidth / (aArrowSize.getRange().getX()));
92                 aArrowTransform.scale(fArrowScale, fArrowScale);
93 
94                 // get arrow size in Y
95                 B2DPoint aUpperCenter(aArrowSize.getCenter().getX(), aArrowSize.getMaximum().getY());
96                 aUpperCenter *= aArrowTransform;
97                 const double fArrowYLength(B2DVector(aUpperCenter).getLength());
98 
99                 // move arrow to have docking position centered
100                 aArrowTransform.translate(0.0, -fArrowYLength * fDockingPosition);
101 
102                 // prepare polygon length
103                 if(fTools::equalZero(fCandidateLength))
104                 {
105                     fCandidateLength = getLength(rCandidate);
106                 }
107 
108                 // get the polygon vector we want to plant this arrow on
109                 const double fConsumedLength(fArrowYLength * (1.0 - fDockingPosition));
110                 const B2DVector aHead(rCandidate.getB2DPoint((bStart) ? 0L : rCandidate.count() - 1L));
111                 const B2DVector aTail(getPositionAbsolute(rCandidate,
112                     (bStart) ? fConsumedLength : fCandidateLength - fConsumedLength, fCandidateLength));
113 
114                 // from that vector, take the needed rotation and add rotate for arrow to transformation
115                 const B2DVector aTargetDirection(aHead - aTail);
116                 const double fRotation(atan2(aTargetDirection.getY(), aTargetDirection.getX()) + (90.0 * F_PI180));
117 
118                 // rotate around docking position
119                 aArrowTransform.rotate(fRotation);
120 
121                 // move arrow docking position to polygon head
122                 aArrowTransform.translate(aHead.getX(), aHead.getY());
123 
124                 // transform retval and close
125                 aRetval.transform(aArrowTransform);
126                 aRetval.setClosed(true);
127 
128                 // if pConsumedLength is asked for, fill it
129                 if(pConsumedLength)
130                 {
131                     *pConsumedLength = fConsumedLength;
132                 }
133             }
134 
135             return aRetval;
136         }
137     } // end of namespace tools
138 } // end of namespace basegfx
139 
140 //////////////////////////////////////////////////////////////////////////////
141 
142 namespace basegfx
143 {
144     // anonymus namespace for local helpers
145     namespace
146     {
147         bool impIsSimpleEdge(const B2DCubicBezier& rCandidate, double fMaxCosQuad, double fMaxPartOfEdgeQuad)
148         {
149             // isBezier() is true, already tested by caller
150             const B2DVector aEdge(rCandidate.getEndPoint() - rCandidate.getStartPoint());
151 
152             if(aEdge.equalZero())
153             {
154                 // start and end point the same, but control vectors used -> baloon curve loop
155                 // is not a simple edge
156                 return false;
157             }
158 
159             // get tangentA and scalar with edge
160             const B2DVector aTangentA(rCandidate.getTangent(0.0));
161             const double fScalarAE(aEdge.scalar(aTangentA));
162 
163             if(fTools::lessOrEqual(fScalarAE, 0.0))
164             {
165                 // angle between TangentA and Edge is bigger or equal 90 degrees
166                 return false;
167             }
168 
169             // get self-scalars for E and A
170             const double fScalarE(aEdge.scalar(aEdge));
171             const double fScalarA(aTangentA.scalar(aTangentA));
172             const double fLengthCompareE(fScalarE * fMaxPartOfEdgeQuad);
173 
174             if(fTools::moreOrEqual(fScalarA, fLengthCompareE))
175             {
176                 // length of TangentA is more than fMaxPartOfEdge of length of edge
177                 return false;
178             }
179 
180             if(fTools::lessOrEqual(fScalarAE * fScalarAE, fScalarA * fScalarE * fMaxCosQuad))
181             {
182                 // angle between TangentA and Edge is bigger or equal angle defined by fMaxCos
183                 return false;
184             }
185 
186             // get tangentB and scalar with edge
187             const B2DVector aTangentB(rCandidate.getTangent(1.0));
188             const double fScalarBE(aEdge.scalar(aTangentB));
189 
190             if(fTools::lessOrEqual(fScalarBE, 0.0))
191             {
192                 // angle between TangentB and Edge is bigger or equal 90 degrees
193                 return false;
194             }
195 
196             // get self-scalar for B
197             const double fScalarB(aTangentB.scalar(aTangentB));
198 
199             if(fTools::moreOrEqual(fScalarB, fLengthCompareE))
200             {
201                 // length of TangentB is more than fMaxPartOfEdge of length of edge
202                 return false;
203             }
204 
205             if(fTools::lessOrEqual(fScalarBE * fScalarBE, fScalarB * fScalarE * fMaxCosQuad))
206             {
207                 // angle between TangentB and Edge is bigger or equal defined by fMaxCos
208                 return false;
209             }
210 
211             return true;
212         }
213 
214         void impSubdivideToSimple(const B2DCubicBezier& rCandidate, B2DPolygon& rTarget, double fMaxCosQuad, double fMaxPartOfEdgeQuad, sal_uInt32 nMaxRecursionDepth)
215         {
216             if(!nMaxRecursionDepth || impIsSimpleEdge(rCandidate, fMaxCosQuad, fMaxPartOfEdgeQuad))
217             {
218                 rTarget.appendBezierSegment(rCandidate.getControlPointA(), rCandidate.getControlPointB(), rCandidate.getEndPoint());
219             }
220             else
221             {
222                 B2DCubicBezier aLeft, aRight;
223                 rCandidate.split(0.5, &aLeft, &aRight);
224 
225                 impSubdivideToSimple(aLeft, rTarget, fMaxCosQuad, fMaxPartOfEdgeQuad, nMaxRecursionDepth - 1);
226                 impSubdivideToSimple(aRight, rTarget, fMaxCosQuad, fMaxPartOfEdgeQuad, nMaxRecursionDepth - 1);
227             }
228         }
229 
230         B2DPolygon subdivideToSimple(const B2DPolygon& rCandidate, double fMaxCosQuad, double fMaxPartOfEdgeQuad)
231         {
232             const sal_uInt32 nPointCount(rCandidate.count());
233 
234             if(rCandidate.areControlPointsUsed() && nPointCount)
235             {
236                 const sal_uInt32 nEdgeCount(rCandidate.isClosed() ? nPointCount : nPointCount - 1);
237                 B2DPolygon aRetval;
238                 B2DCubicBezier aEdge;
239 
240                 // prepare edge for loop
241                 aEdge.setStartPoint(rCandidate.getB2DPoint(0));
242                 aRetval.append(aEdge.getStartPoint());
243 
244                 for(sal_uInt32 a(0); a < nEdgeCount; a++)
245                 {
246                     // fill B2DCubicBezier
247                     const sal_uInt32 nNextIndex((a + 1) % nPointCount);
248                     aEdge.setControlPointA(rCandidate.getNextControlPoint(a));
249                     aEdge.setControlPointB(rCandidate.getPrevControlPoint(nNextIndex));
250                     aEdge.setEndPoint(rCandidate.getB2DPoint(nNextIndex));
251 
252                     // get rid of unnecessary bezier segments
253                     aEdge.testAndSolveTrivialBezier();
254 
255                     if(aEdge.isBezier())
256                     {
257                         // before splitting recursively with internal simple criteria, use
258                         // ExtremumPosFinder to remove those
259                         ::std::vector< double > aExtremumPositions;
260 
261                         aExtremumPositions.reserve(4);
262                         aEdge.getAllExtremumPositions(aExtremumPositions);
263 
264                         const sal_uInt32 nCount(aExtremumPositions.size());
265 
266                         if(nCount)
267                         {
268                             if(nCount > 1)
269                             {
270                                 // create order from left to right
271                                 ::std::sort(aExtremumPositions.begin(), aExtremumPositions.end());
272                             }
273 
274                             for(sal_uInt32 b(0); b < nCount;)
275                             {
276                                 // split aEdge at next split pos
277                                 B2DCubicBezier aLeft;
278                                 const double fSplitPos(aExtremumPositions[b++]);
279 
280                                 aEdge.split(fSplitPos, &aLeft, &aEdge);
281                                 aLeft.testAndSolveTrivialBezier();
282 
283                                 // consume left part
284                                 if(aLeft.isBezier())
285                                 {
286                                     impSubdivideToSimple(aLeft, aRetval, fMaxCosQuad, fMaxPartOfEdgeQuad, 6);
287                                 }
288                                 else
289                                 {
290                                     aRetval.append(aLeft.getEndPoint());
291                                 }
292 
293                                 if(b < nCount)
294                                 {
295                                     // correct the remaining split positions to fit to shortened aEdge
296                                     const double fScaleFactor(1.0 / (1.0 - fSplitPos));
297 
298                                     for(sal_uInt32 c(b); c < nCount; c++)
299                                     {
300                                         aExtremumPositions[c] = (aExtremumPositions[c] - fSplitPos) * fScaleFactor;
301                                     }
302                                 }
303                             }
304 
305                             // test the shortened rest of aEdge
306                             aEdge.testAndSolveTrivialBezier();
307 
308                             // consume right part
309                             if(aEdge.isBezier())
310                             {
311                                 impSubdivideToSimple(aEdge, aRetval, fMaxCosQuad, fMaxPartOfEdgeQuad, 6);
312                             }
313                             else
314                             {
315                                 aRetval.append(aEdge.getEndPoint());
316                             }
317                         }
318                         else
319                         {
320                             impSubdivideToSimple(aEdge, aRetval, fMaxCosQuad, fMaxPartOfEdgeQuad, 6);
321                         }
322                     }
323                     else
324                     {
325                         // straight edge, add point
326                         aRetval.append(aEdge.getEndPoint());
327                     }
328 
329                     // prepare edge for next step
330                     aEdge.setStartPoint(aEdge.getEndPoint());
331                 }
332 
333                 // copy closed flag and check for double points
334                 aRetval.setClosed(rCandidate.isClosed());
335                 aRetval.removeDoublePoints();
336 
337                 return aRetval;
338             }
339             else
340             {
341                 return rCandidate;
342             }
343         }
344 
345         B2DPolygon createAreaGeometryForEdge(const B2DCubicBezier& rEdge, double fHalfLineWidth)
346         {
347             // create polygon for edge
348             // Unfortunately, while it would be geometrically correct to not add
349             // the in-between points EdgeEnd and EdgeStart, it leads to rounding
350             // errors when converting to integer polygon coordinates for painting
351             if(rEdge.isBezier())
352             {
353                 // prepare target and data common for upper and lower
354                 B2DPolygon aBezierPolygon;
355                 const B2DVector aPureEdgeVector(rEdge.getEndPoint() - rEdge.getStartPoint());
356                 const double fEdgeLength(aPureEdgeVector.getLength());
357                 const bool bIsEdgeLengthZero(fTools::equalZero(fEdgeLength));
358                 const B2DVector aTangentA(rEdge.getTangent(0.0));
359                 const B2DVector aTangentB(rEdge.getTangent(1.0));
360 
361                 // create upper edge.
362                 {
363                     // create displacement vectors and check if they cut
364                     const B2DVector aPerpendStart(getNormalizedPerpendicular(aTangentA) * -fHalfLineWidth);
365                     const B2DVector aPerpendEnd(getNormalizedPerpendicular(aTangentB) * -fHalfLineWidth);
366                     double fCut(0.0);
367                     const tools::CutFlagValue aCut(tools::findCut(
368                         rEdge.getStartPoint(), aPerpendStart,
369                         rEdge.getEndPoint(), aPerpendEnd,
370                         CUTFLAG_ALL, &fCut));
371 
372                     if(CUTFLAG_NONE != aCut)
373                     {
374                         // calculate cut point and add
375                         const B2DPoint aCutPoint(rEdge.getStartPoint() + (aPerpendStart * fCut));
376                         aBezierPolygon.append(aCutPoint);
377                     }
378                     else
379                     {
380                         // create scaled bezier segment
381                         const B2DPoint aStart(rEdge.getStartPoint() + aPerpendStart);
382                         const B2DPoint aEnd(rEdge.getEndPoint() + aPerpendEnd);
383                         const B2DVector aEdge(aEnd - aStart);
384                         const double fLength(aEdge.getLength());
385                         const double fScale(bIsEdgeLengthZero ? 1.0 : fLength / fEdgeLength);
386                         const B2DVector fRelNext(rEdge.getControlPointA() - rEdge.getStartPoint());
387                         const B2DVector fRelPrev(rEdge.getControlPointB() - rEdge.getEndPoint());
388 
389                         aBezierPolygon.append(aStart);
390                         aBezierPolygon.appendBezierSegment(aStart + (fRelNext * fScale), aEnd + (fRelPrev * fScale), aEnd);
391                     }
392                 }
393 
394                 // append original in-between point
395                 aBezierPolygon.append(rEdge.getEndPoint());
396 
397                 // create lower edge.
398                 {
399                     // create displacement vectors and check if they cut
400                     const B2DVector aPerpendStart(getNormalizedPerpendicular(aTangentA) * fHalfLineWidth);
401                     const B2DVector aPerpendEnd(getNormalizedPerpendicular(aTangentB) * fHalfLineWidth);
402                     double fCut(0.0);
403                     const tools::CutFlagValue aCut(tools::findCut(
404                         rEdge.getEndPoint(), aPerpendEnd,
405                         rEdge.getStartPoint(), aPerpendStart,
406                         CUTFLAG_ALL, &fCut));
407 
408                     if(CUTFLAG_NONE != aCut)
409                     {
410                         // calculate cut point and add
411                         const B2DPoint aCutPoint(rEdge.getEndPoint() + (aPerpendEnd * fCut));
412                         aBezierPolygon.append(aCutPoint);
413                     }
414                     else
415                     {
416                         // create scaled bezier segment
417                         const B2DPoint aStart(rEdge.getEndPoint() + aPerpendEnd);
418                         const B2DPoint aEnd(rEdge.getStartPoint() + aPerpendStart);
419                         const B2DVector aEdge(aEnd - aStart);
420                         const double fLength(aEdge.getLength());
421                         const double fScale(bIsEdgeLengthZero ? 1.0 : fLength / fEdgeLength);
422                         const B2DVector fRelNext(rEdge.getControlPointB() - rEdge.getEndPoint());
423                         const B2DVector fRelPrev(rEdge.getControlPointA() - rEdge.getStartPoint());
424 
425                         aBezierPolygon.append(aStart);
426                         aBezierPolygon.appendBezierSegment(aStart + (fRelNext * fScale), aEnd + (fRelPrev * fScale), aEnd);
427                     }
428                 }
429 
430                 // append original in-between point
431                 aBezierPolygon.append(rEdge.getStartPoint());
432 
433                 // close and return
434                 aBezierPolygon.setClosed(true);
435                 return aBezierPolygon;
436             }
437             else
438             {
439                 // #i101491# emulate rEdge.getTangent call which applies a factor of 0.3 to the
440                 // full-length edge vector to have numerically exactly the same results as in the
441                 // createAreaGeometryForJoin implementation
442                 const B2DVector aEdgeTangent((rEdge.getEndPoint() - rEdge.getStartPoint()) * 0.3);
443                 const B2DVector aPerpendEdgeVector(getNormalizedPerpendicular(aEdgeTangent) * fHalfLineWidth);
444                 B2DPolygon aEdgePolygon;
445 
446                 // create upper edge
447                 aEdgePolygon.append(rEdge.getStartPoint() - aPerpendEdgeVector);
448                 aEdgePolygon.append(rEdge.getEndPoint() - aPerpendEdgeVector);
449 
450                 // append original in-between point
451                 aEdgePolygon.append(rEdge.getEndPoint());
452 
453                 // create lower edge
454                 aEdgePolygon.append(rEdge.getEndPoint() + aPerpendEdgeVector);
455                 aEdgePolygon.append(rEdge.getStartPoint() + aPerpendEdgeVector);
456 
457                 // append original in-between point
458                 aEdgePolygon.append(rEdge.getStartPoint());
459 
460                 // close and return
461                 aEdgePolygon.setClosed(true);
462                 return aEdgePolygon;
463             }
464         }
465 
466         B2DPolygon createAreaGeometryForJoin(
467             const B2DVector& rTangentPrev,
468             const B2DVector& rTangentEdge,
469             const B2DVector& rPerpendPrev,
470             const B2DVector& rPerpendEdge,
471             const B2DPoint& rPoint,
472             double fHalfLineWidth,
473             B2DLineJoin eJoin,
474             double fMiterMinimumAngle)
475         {
476             OSL_ENSURE(fHalfLineWidth > 0.0, "createAreaGeometryForJoin: LineWidth too small (!)");
477             OSL_ENSURE(B2DLINEJOIN_NONE != eJoin, "createAreaGeometryForJoin: B2DLINEJOIN_NONE not allowed (!)");
478 
479             // LineJoin from tangent rPerpendPrev to tangent rPerpendEdge in rPoint
480             B2DPolygon aEdgePolygon;
481             const B2DPoint aStartPoint(rPoint + rPerpendPrev);
482             const B2DPoint aEndPoint(rPoint + rPerpendEdge);
483 
484             // test if for Miter, the angle is too small and the fallback
485             // to bevel needs to be used
486             if(B2DLINEJOIN_MITER == eJoin)
487             {
488                 const double fAngle(fabs(rPerpendPrev.angle(rPerpendEdge)));
489 
490                 if((F_PI - fAngle) < fMiterMinimumAngle)
491                 {
492                     // fallback to bevel
493                     eJoin = B2DLINEJOIN_BEVEL;
494                 }
495             }
496 
497             switch(eJoin)
498             {
499                 case B2DLINEJOIN_MITER :
500                 {
501                     aEdgePolygon.append(aEndPoint);
502                     aEdgePolygon.append(rPoint);
503                     aEdgePolygon.append(aStartPoint);
504 
505                     // Look for the cut point between start point along rTangentPrev and
506                     // end point along rTangentEdge. -rTangentEdge should be used, but since
507                     // the cut value is used for interpolating along the first edge, the negation
508                     // is not needed since the same fCut will be found on the first edge.
509                     // If it exists, insert it to complete the mitered fill polygon.
510                     double fCutPos(0.0);
511                     tools::findCut(aStartPoint, rTangentPrev, aEndPoint, rTangentEdge, CUTFLAG_ALL, &fCutPos);
512 
513                     if(0.0 != fCutPos)
514                     {
515                         const B2DPoint aCutPoint(interpolate(aStartPoint, aStartPoint + rTangentPrev, fCutPos));
516                         aEdgePolygon.append(aCutPoint);
517                     }
518 
519                     break;
520                 }
521                 case B2DLINEJOIN_ROUND :
522                 {
523                     // use tooling to add needed EllipseSegment
524                     double fAngleStart(atan2(rPerpendPrev.getY(), rPerpendPrev.getX()));
525                     double fAngleEnd(atan2(rPerpendEdge.getY(), rPerpendEdge.getX()));
526 
527                     // atan2 results are [-PI .. PI], consolidate to [0.0 .. 2PI]
528                     if(fAngleStart < 0.0)
529                     {
530                         fAngleStart += F_2PI;
531                     }
532 
533                     if(fAngleEnd < 0.0)
534                     {
535                         fAngleEnd += F_2PI;
536                     }
537 
538                     const B2DPolygon aBow(tools::createPolygonFromEllipseSegment(rPoint, fHalfLineWidth, fHalfLineWidth, fAngleStart, fAngleEnd));
539 
540                     if(aBow.count() > 1)
541                     {
542                         // #i101491#
543                         // use the original start/end positions; the ones from bow creation may be numerically
544                         // different due to their different creation. To guarantee good merging quality with edges
545                         // and edge roundings (and to reduce point count)
546                         aEdgePolygon = aBow;
547                         aEdgePolygon.setB2DPoint(0, aStartPoint);
548                         aEdgePolygon.setB2DPoint(aEdgePolygon.count() - 1, aEndPoint);
549                         aEdgePolygon.append(rPoint);
550 
551                         break;
552                     }
553                     else
554                     {
555                         // wanted fall-through to default
556                     }
557                 }
558                 default: // B2DLINEJOIN_BEVEL
559                 {
560                     aEdgePolygon.append(aEndPoint);
561                     aEdgePolygon.append(rPoint);
562                     aEdgePolygon.append(aStartPoint);
563 
564                     break;
565                 }
566             }
567 
568             // create last polygon part for edge
569             aEdgePolygon.setClosed(true);
570 
571             return aEdgePolygon;
572         }
573     } // end of anonymus namespace
574 
575     namespace tools
576     {
577         B2DPolyPolygon createAreaGeometry(
578             const B2DPolygon& rCandidate,
579             double fHalfLineWidth,
580             B2DLineJoin eJoin,
581             double fMaxAllowedAngle,
582             double fMaxPartOfEdge,
583             double fMiterMinimumAngle)
584         {
585             if(fMaxAllowedAngle > F_PI2)
586             {
587                 fMaxAllowedAngle = F_PI2;
588             }
589             else if(fMaxAllowedAngle < 0.01 * F_PI2)
590             {
591                 fMaxAllowedAngle = 0.01 * F_PI2;
592             }
593 
594             if(fMaxPartOfEdge > 1.0)
595             {
596                 fMaxPartOfEdge = 1.0;
597             }
598             else if(fMaxPartOfEdge < 0.01)
599             {
600                 fMaxPartOfEdge = 0.01;
601             }
602 
603             if(fMiterMinimumAngle > F_PI)
604             {
605                 fMiterMinimumAngle = F_PI;
606             }
607             else if(fMiterMinimumAngle < 0.01 * F_PI)
608             {
609                 fMiterMinimumAngle = 0.01 * F_PI;
610             }
611 
612             B2DPolygon aCandidate(rCandidate);
613             const double fMaxCos(cos(fMaxAllowedAngle));
614 
615             aCandidate.removeDoublePoints();
616             aCandidate = subdivideToSimple(aCandidate, fMaxCos * fMaxCos, fMaxPartOfEdge * fMaxPartOfEdge);
617 
618             const sal_uInt32 nPointCount(aCandidate.count());
619 
620             if(nPointCount)
621             {
622                 B2DPolyPolygon aRetval;
623                 const bool bEventuallyCreateLineJoin(B2DLINEJOIN_NONE != eJoin);
624                 const bool bIsClosed(aCandidate.isClosed());
625                 const sal_uInt32 nEdgeCount(bIsClosed ? nPointCount : nPointCount - 1);
626 
627                 if(nEdgeCount)
628                 {
629                     B2DCubicBezier aEdge;
630                     B2DCubicBezier aPrev;
631 
632                     // prepare edge
633                     aEdge.setStartPoint(aCandidate.getB2DPoint(0));
634 
635                     if(bIsClosed && bEventuallyCreateLineJoin)
636                     {
637                         // prepare previous edge
638                         const sal_uInt32 nPrevIndex(nPointCount - 1);
639                         aPrev.setStartPoint(aCandidate.getB2DPoint(nPrevIndex));
640                         aPrev.setControlPointA(aCandidate.getNextControlPoint(nPrevIndex));
641                         aPrev.setControlPointB(aCandidate.getPrevControlPoint(0));
642                         aPrev.setEndPoint(aEdge.getStartPoint());
643                     }
644 
645                     for(sal_uInt32 a(0); a < nEdgeCount; a++)
646                     {
647                         // fill current Edge
648                         const sal_uInt32 nNextIndex((a + 1) % nPointCount);
649                         aEdge.setControlPointA(aCandidate.getNextControlPoint(a));
650                         aEdge.setControlPointB(aCandidate.getPrevControlPoint(nNextIndex));
651                         aEdge.setEndPoint(aCandidate.getB2DPoint(nNextIndex));
652 
653                         // check and create linejoin
654                         if(bEventuallyCreateLineJoin && (bIsClosed || 0 != a))
655                         {
656                             const B2DVector aTangentPrev(aPrev.getTangent(1.0));
657                             const B2DVector aTangentEdge(aEdge.getTangent(0.0));
658                             B2VectorOrientation aOrientation(getOrientation(aTangentPrev, aTangentEdge));
659 
660                             if(ORIENTATION_NEUTRAL == aOrientation)
661                             {
662                                 // they are parallell or empty; if they are both not zero and point
663                                 // in opposite direction, a half-circle is needed
664                                 if(!aTangentPrev.equalZero() && !aTangentEdge.equalZero())
665                                 {
666                                     const double fAngle(fabs(aTangentPrev.angle(aTangentEdge)));
667 
668                                     if(fTools::equal(fAngle, F_PI))
669                                     {
670                                         // for half-circle production, fallback to positive
671                                         // orientation
672                                         aOrientation = ORIENTATION_POSITIVE;
673                                     }
674                                 }
675                             }
676 
677                             if(ORIENTATION_POSITIVE == aOrientation)
678                             {
679                                 const B2DVector aPerpendPrev(getNormalizedPerpendicular(aTangentPrev) * -fHalfLineWidth);
680                                 const B2DVector aPerpendEdge(getNormalizedPerpendicular(aTangentEdge) * -fHalfLineWidth);
681 
682                                 aRetval.append(createAreaGeometryForJoin(
683                                     aTangentPrev, aTangentEdge,
684                                     aPerpendPrev, aPerpendEdge,
685                                     aEdge.getStartPoint(), fHalfLineWidth,
686                                     eJoin, fMiterMinimumAngle));
687                             }
688                             else if(ORIENTATION_NEGATIVE == aOrientation)
689                             {
690                                 const B2DVector aPerpendPrev(getNormalizedPerpendicular(aTangentPrev) * fHalfLineWidth);
691                                 const B2DVector aPerpendEdge(getNormalizedPerpendicular(aTangentEdge) * fHalfLineWidth);
692 
693                                 aRetval.append(createAreaGeometryForJoin(
694                                     aTangentEdge, aTangentPrev,
695                                     aPerpendEdge, aPerpendPrev,
696                                     aEdge.getStartPoint(), fHalfLineWidth,
697                                     eJoin, fMiterMinimumAngle));
698                             }
699                         }
700 
701                         // create geometry for edge
702                         aRetval.append(createAreaGeometryForEdge(aEdge, fHalfLineWidth));
703 
704                         // prepare next step
705                         if(bEventuallyCreateLineJoin)
706                         {
707                             aPrev = aEdge;
708                         }
709 
710                         aEdge.setStartPoint(aEdge.getEndPoint());
711                     }
712                 }
713 
714                 return aRetval;
715             }
716             else
717             {
718                 return B2DPolyPolygon(rCandidate);
719             }
720         }
721     } // end of namespace tools
722 } // end of namespace basegfx
723 
724 //////////////////////////////////////////////////////////////////////////////
725 // eof
726