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