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