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 #include <com/sun/star/drawing/LineCap.hpp>
38 
39 //////////////////////////////////////////////////////////////////////////////
40 
41 namespace basegfx
42 {
43 	namespace tools
44 	{
45 		B2DPolyPolygon createAreaGeometryForLineStartEnd(
46 			const B2DPolygon& rCandidate,
47 			const B2DPolyPolygon& rArrow,
48 			bool bStart,
49 			double fWidth,
50 			double fCandidateLength,
51 			double fDockingPosition, // 0->top, 1->bottom
52 			double* pConsumedLength)
53 		{
54 			B2DPolyPolygon aRetval;
55 			OSL_ENSURE(rCandidate.count() > 1L, "createAreaGeometryForLineStartEnd: Line polygon has too less points (!)");
56 			OSL_ENSURE(rArrow.count() > 0L, "createAreaGeometryForLineStartEnd: Empty arrow PolyPolygon (!)");
57 			OSL_ENSURE(fWidth > 0.0, "createAreaGeometryForLineStartEnd: Width too small (!)");
58 			OSL_ENSURE(fDockingPosition >= 0.0 && fDockingPosition <= 1.0,
59 				"createAreaGeometryForLineStartEnd: fDockingPosition out of range [0.0 .. 1.0] (!)");
60 
61             if(fWidth < 0.0)
62             {
63                 fWidth = -fWidth;
64             }
65 
66             if(rCandidate.count() > 1 && rArrow.count() && !fTools::equalZero(fWidth))
67             {
68                 if(fDockingPosition < 0.0)
69                 {
70                     fDockingPosition = 0.0;
71                 }
72                 else if(fDockingPosition > 1.0)
73                 {
74                     fDockingPosition = 1.0;
75                 }
76 
77 			    // init return value from arrow
78 			    aRetval.append(rArrow);
79 
80 			    // get size of the arrow
81 			    const B2DRange aArrowSize(getRange(rArrow));
82 
83 			    // build ArrowTransform; center in X, align with axis in Y
84                 B2DHomMatrix aArrowTransform(basegfx::tools::createTranslateB2DHomMatrix(
85                     -aArrowSize.getCenter().getX(), -aArrowSize.getMinimum().getY()));
86 
87 			    // scale to target size
88 			    const double fArrowScale(fWidth / (aArrowSize.getRange().getX()));
89 			    aArrowTransform.scale(fArrowScale, fArrowScale);
90 
91 			    // get arrow size in Y
92 			    B2DPoint aUpperCenter(aArrowSize.getCenter().getX(), aArrowSize.getMaximum().getY());
93 			    aUpperCenter *= aArrowTransform;
94 			    const double fArrowYLength(B2DVector(aUpperCenter).getLength());
95 
96 			    // move arrow to have docking position centered
97 			    aArrowTransform.translate(0.0, -fArrowYLength * fDockingPosition);
98 
99 			    // prepare polygon length
100 			    if(fTools::equalZero(fCandidateLength))
101 			    {
102 				    fCandidateLength = getLength(rCandidate);
103 			    }
104 
105 			    // get the polygon vector we want to plant this arrow on
106 			    const double fConsumedLength(fArrowYLength * (1.0 - fDockingPosition));
107 			    const B2DVector aHead(rCandidate.getB2DPoint((bStart) ? 0L : rCandidate.count() - 1L));
108 			    const B2DVector aTail(getPositionAbsolute(rCandidate,
109 				    (bStart) ? fConsumedLength : fCandidateLength - fConsumedLength, fCandidateLength));
110 
111 			    // from that vector, take the needed rotation and add rotate for arrow to transformation
112 			    const B2DVector aTargetDirection(aHead - aTail);
113 			    const double fRotation(atan2(aTargetDirection.getY(), aTargetDirection.getX()) + (90.0 * F_PI180));
114 
115 			    // rotate around docking position
116 			    aArrowTransform.rotate(fRotation);
117 
118 			    // move arrow docking position to polygon head
119 			    aArrowTransform.translate(aHead.getX(), aHead.getY());
120 
121 			    // transform retval and close
122 			    aRetval.transform(aArrowTransform);
123 			    aRetval.setClosed(true);
124 
125 			    // if pConsumedLength is asked for, fill it
126 			    if(pConsumedLength)
127 			    {
128 				    *pConsumedLength = fConsumedLength;
129 			    }
130             }
131 
132 			return aRetval;
133 		}
134 	} // end of namespace tools
135 } // end of namespace basegfx
136 
137 //////////////////////////////////////////////////////////////////////////////
138 
139 namespace basegfx
140 {
141 	// anonymus namespace for local helpers
142 	namespace
143 	{
144         bool impIsSimpleEdge(const B2DCubicBezier& rCandidate, double fMaxCosQuad, double fMaxPartOfEdgeQuad)
145         {
146             // isBezier() is true, already tested by caller
147             const B2DVector aEdge(rCandidate.getEndPoint() - rCandidate.getStartPoint());
148 
149             if(aEdge.equalZero())
150             {
151                 // start and end point the same, but control vectors used -> baloon curve loop
152                 // is not a simple edge
153                 return false;
154             }
155 
156             // get tangentA and scalar with edge
157             const B2DVector aTangentA(rCandidate.getTangent(0.0));
158     		const double fScalarAE(aEdge.scalar(aTangentA));
159 
160             if(fTools::lessOrEqual(fScalarAE, 0.0))
161             {
162                 // angle between TangentA and Edge is bigger or equal 90 degrees
163                 return false;
164             }
165 
166             // get self-scalars for E and A
167     		const double fScalarE(aEdge.scalar(aEdge));
168     		const double fScalarA(aTangentA.scalar(aTangentA));
169 			const double fLengthCompareE(fScalarE * fMaxPartOfEdgeQuad);
170 
171 			if(fTools::moreOrEqual(fScalarA, fLengthCompareE))
172 			{
173 				// length of TangentA is more than fMaxPartOfEdge of length of edge
174 				return false;
175 			}
176 
177             if(fTools::lessOrEqual(fScalarAE * fScalarAE, fScalarA * fScalarE * fMaxCosQuad))
178             {
179                 // angle between TangentA and Edge is bigger or equal angle defined by fMaxCos
180                 return false;
181             }
182 
183             // get tangentB and scalar with edge
184             const B2DVector aTangentB(rCandidate.getTangent(1.0));
185     		const double fScalarBE(aEdge.scalar(aTangentB));
186 
187             if(fTools::lessOrEqual(fScalarBE, 0.0))
188             {
189                 // angle between TangentB and Edge is bigger or equal 90 degrees
190                 return false;
191             }
192 
193             // get self-scalar for B
194     		const double fScalarB(aTangentB.scalar(aTangentB));
195 
196 			if(fTools::moreOrEqual(fScalarB, fLengthCompareE))
197 			{
198 				// length of TangentB is more than fMaxPartOfEdge of length of edge
199 				return false;
200 			}
201 
202 			if(fTools::lessOrEqual(fScalarBE * fScalarBE, fScalarB * fScalarE * fMaxCosQuad))
203             {
204                 // angle between TangentB and Edge is bigger or equal defined by fMaxCos
205                 return false;
206             }
207 
208             return true;
209         }
210 
211         void impSubdivideToSimple(const B2DCubicBezier& rCandidate, B2DPolygon& rTarget, double fMaxCosQuad, double fMaxPartOfEdgeQuad, sal_uInt32 nMaxRecursionDepth)
212         {
213             if(!nMaxRecursionDepth || impIsSimpleEdge(rCandidate, fMaxCosQuad, fMaxPartOfEdgeQuad))
214             {
215                 rTarget.appendBezierSegment(rCandidate.getControlPointA(), rCandidate.getControlPointB(), rCandidate.getEndPoint());
216             }
217             else
218             {
219                 B2DCubicBezier aLeft, aRight;
220                 rCandidate.split(0.5, &aLeft, &aRight);
221 
222                 impSubdivideToSimple(aLeft, rTarget, fMaxCosQuad, fMaxPartOfEdgeQuad, nMaxRecursionDepth - 1);
223                 impSubdivideToSimple(aRight, rTarget, fMaxCosQuad, fMaxPartOfEdgeQuad, nMaxRecursionDepth - 1);
224             }
225         }
226 
227         B2DPolygon subdivideToSimple(const B2DPolygon& rCandidate, double fMaxCosQuad, double fMaxPartOfEdgeQuad)
228         {
229             const sal_uInt32 nPointCount(rCandidate.count());
230 
231             if(rCandidate.areControlPointsUsed() && nPointCount)
232             {
233                 const sal_uInt32 nEdgeCount(rCandidate.isClosed() ? nPointCount : nPointCount - 1);
234                 B2DPolygon aRetval;
235 				B2DCubicBezier aEdge;
236 
237 				// prepare edge for loop
238 				aEdge.setStartPoint(rCandidate.getB2DPoint(0));
239 				aRetval.append(aEdge.getStartPoint());
240 
241                 for(sal_uInt32 a(0); a < nEdgeCount; a++)
242                 {
243                     // fill B2DCubicBezier
244                     const sal_uInt32 nNextIndex((a + 1) % nPointCount);
245                     aEdge.setControlPointA(rCandidate.getNextControlPoint(a));
246                     aEdge.setControlPointB(rCandidate.getPrevControlPoint(nNextIndex));
247                     aEdge.setEndPoint(rCandidate.getB2DPoint(nNextIndex));
248 
249                     // get rid of unnecessary bezier segments
250                     aEdge.testAndSolveTrivialBezier();
251 
252                     if(aEdge.isBezier())
253                     {
254 						// before splitting recursively with internal simple criteria, use
255 						// ExtremumPosFinder to remove those
256                 		::std::vector< double > aExtremumPositions;
257 
258                         aExtremumPositions.reserve(4);
259 		                aEdge.getAllExtremumPositions(aExtremumPositions);
260 
261                         const sal_uInt32 nCount(aExtremumPositions.size());
262 
263                         if(nCount)
264                         {
265                             if(nCount > 1)
266                             {
267                                 // create order from left to right
268                                 ::std::sort(aExtremumPositions.begin(), aExtremumPositions.end());
269                             }
270 
271                             for(sal_uInt32 b(0); b < nCount;)
272                             {
273                                 // split aEdge at next split pos
274                                 B2DCubicBezier aLeft;
275                                 const double fSplitPos(aExtremumPositions[b++]);
276 
277                                 aEdge.split(fSplitPos, &aLeft, &aEdge);
278                                 aLeft.testAndSolveTrivialBezier();
279 
280                                 // consume left part
281                                 if(aLeft.isBezier())
282 						        {
283 	                                impSubdivideToSimple(aLeft, aRetval, fMaxCosQuad, fMaxPartOfEdgeQuad, 6);
284 						        }
285 						        else
286 						        {
287 	                                aRetval.append(aLeft.getEndPoint());
288 						        }
289 
290                                 if(b < nCount)
291                                 {
292                                     // correct the remaining split positions to fit to shortened aEdge
293                                     const double fScaleFactor(1.0 / (1.0 - fSplitPos));
294 
295                                     for(sal_uInt32 c(b); c < nCount; c++)
296                                     {
297                                         aExtremumPositions[c] = (aExtremumPositions[c] - fSplitPos) * fScaleFactor;
298                                     }
299                                 }
300                             }
301 
302                             // test the shortened rest of aEdge
303                             aEdge.testAndSolveTrivialBezier();
304 
305                             // consume right part
306                             if(aEdge.isBezier())
307 					        {
308                                 impSubdivideToSimple(aEdge, aRetval, fMaxCosQuad, fMaxPartOfEdgeQuad, 6);
309 					        }
310 					        else
311 					        {
312                                 aRetval.append(aEdge.getEndPoint());
313 					        }
314                         }
315                         else
316                         {
317 	                        impSubdivideToSimple(aEdge, aRetval, fMaxCosQuad, fMaxPartOfEdgeQuad, 6);
318                         }
319                     }
320                     else
321                     {
322                         // straight edge, add point
323                         aRetval.append(aEdge.getEndPoint());
324                     }
325 
326 					// prepare edge for next step
327 					aEdge.setStartPoint(aEdge.getEndPoint());
328                 }
329 
330 				// copy closed flag and check for double points
331                 aRetval.setClosed(rCandidate.isClosed());
332                 aRetval.removeDoublePoints();
333 
334                 return aRetval;
335             }
336             else
337             {
338                 return rCandidate;
339             }
340         }
341 
342         B2DPolyPolygon createAreaGeometryForEdge(
343             const B2DCubicBezier& rEdge,
344             double fHalfLineWidth,
345             bool bStartRound,
346             bool bEndRound,
347             bool bStartSquare,
348             bool bEndSquare)
349         {
350             // create polygon for edge
351             // Unfortunately, while it would be geometrically correct to not add
352             // the in-between points EdgeEnd and EdgeStart, it leads to rounding
353             // errors when converting to integer polygon coordinates for painting
354             if(rEdge.isBezier())
355             {
356                 // prepare target and data common for upper and lower
357                 B2DPolyPolygon aRetval;
358                 B2DPolygon aBezierPolygon;
359                 const B2DVector aPureEdgeVector(rEdge.getEndPoint() - rEdge.getStartPoint());
360                 const double fEdgeLength(aPureEdgeVector.getLength());
361                 const bool bIsEdgeLengthZero(fTools::equalZero(fEdgeLength));
362                 B2DVector aTangentA(rEdge.getTangent(0.0)); aTangentA.normalize();
363                 B2DVector aTangentB(rEdge.getTangent(1.0)); aTangentB.normalize();
364                 const B2DVector aNormalizedPerpendicularA(getPerpendicular(aTangentA));
365                 const B2DVector aNormalizedPerpendicularB(getPerpendicular(aTangentB));
366 
367                 // create upper displacement vectors and check if they cut
368                 const B2DVector aPerpendStartA(aNormalizedPerpendicularA * -fHalfLineWidth);
369                 const B2DVector aPerpendEndA(aNormalizedPerpendicularB * -fHalfLineWidth);
370                 double fCutA(0.0);
371                 const tools::CutFlagValue aCutA(tools::findCut(
372                     rEdge.getStartPoint(), aPerpendStartA,
373                     rEdge.getEndPoint(), aPerpendEndA,
374                     CUTFLAG_ALL, &fCutA));
375                 const bool bCutA(CUTFLAG_NONE != aCutA);
376 
377                 // create lower displacement vectors and check if they cut
378                 const B2DVector aPerpendStartB(aNormalizedPerpendicularA * fHalfLineWidth);
379                 const B2DVector aPerpendEndB(aNormalizedPerpendicularB * fHalfLineWidth);
380                 double fCutB(0.0);
381                 const tools::CutFlagValue aCutB(tools::findCut(
382                     rEdge.getEndPoint(), aPerpendEndB,
383                     rEdge.getStartPoint(), aPerpendStartB,
384                     CUTFLAG_ALL, &fCutB));
385                 const bool bCutB(CUTFLAG_NONE != aCutB);
386 
387                 // check if cut happens
388                 const bool bCut(bCutA || bCutB);
389 
390                 // create left edge
391                 if(bStartRound || bStartSquare)
392                 {
393                     basegfx::B2DPolygon aStartPolygon;
394 
395                     if(bStartRound)
396                     {
397                         aStartPolygon = tools::createHalfUnitCircle();
398                         aStartPolygon.transform(
399                             tools::createScaleShearXRotateTranslateB2DHomMatrix(
400                                 fHalfLineWidth, fHalfLineWidth,
401                                 0.0,
402                                 atan2(aTangentA.getY(), aTangentA.getX()) + F_PI2,
403                                 rEdge.getStartPoint().getX(), rEdge.getStartPoint().getY()));
404                     }
405                     else // bStartSquare
406                     {
407                         const basegfx::B2DPoint aStart(rEdge.getStartPoint() - (aTangentA * fHalfLineWidth));
408 
409                         if(bCut)
410                         {
411                             aStartPolygon.append(rEdge.getStartPoint() + aPerpendStartB);
412                         }
413 
414                         aStartPolygon.append(aStart + aPerpendStartB);
415                         aStartPolygon.append(aStart + aPerpendStartA);
416 
417                         if(bCut)
418                         {
419                             aStartPolygon.append(rEdge.getStartPoint() + aPerpendStartA);
420                         }
421                     }
422 
423                     if(bCut)
424                     {
425                         aStartPolygon.append(rEdge.getStartPoint());
426                         aStartPolygon.setClosed(true);
427                         aRetval.append(aStartPolygon);
428                     }
429                     else
430                     {
431                         aBezierPolygon.append(aStartPolygon);
432                     }
433                 }
434                 else
435                 {
436                     // append original in-between point
437                     aBezierPolygon.append(rEdge.getStartPoint());
438                 }
439 
440                 // create upper edge.
441                 {
442                     if(bCutA)
443                     {
444                         // calculate cut point and add
445                         const B2DPoint aCutPoint(rEdge.getStartPoint() + (aPerpendStartA * fCutA));
446                         aBezierPolygon.append(aCutPoint);
447                     }
448                     else
449                     {
450                         // create scaled bezier segment
451                         const B2DPoint aStart(rEdge.getStartPoint() + aPerpendStartA);
452                         const B2DPoint aEnd(rEdge.getEndPoint() + aPerpendEndA);
453                         const B2DVector aEdge(aEnd - aStart);
454                         const double fLength(aEdge.getLength());
455                         const double fScale(bIsEdgeLengthZero ? 1.0 : fLength / fEdgeLength);
456                         const B2DVector fRelNext(rEdge.getControlPointA() - rEdge.getStartPoint());
457                         const B2DVector fRelPrev(rEdge.getControlPointB() - rEdge.getEndPoint());
458 
459                         aBezierPolygon.append(aStart);
460                         aBezierPolygon.appendBezierSegment(aStart + (fRelNext * fScale), aEnd + (fRelPrev * fScale), aEnd);
461                     }
462                 }
463 
464                 // create right edge
465                 if(bEndRound || bEndSquare)
466                 {
467                     basegfx::B2DPolygon aEndPolygon;
468 
469                     if(bEndRound)
470                     {
471                         aEndPolygon = tools::createHalfUnitCircle();
472                         aEndPolygon.transform(
473                             tools::createScaleShearXRotateTranslateB2DHomMatrix(
474                                 fHalfLineWidth, fHalfLineWidth,
475                                 0.0,
476                                 atan2(aTangentB.getY(), aTangentB.getX()) - F_PI2,
477                                 rEdge.getEndPoint().getX(), rEdge.getEndPoint().getY()));
478                     }
479                     else // bEndSquare
480                     {
481                         const basegfx::B2DPoint aEnd(rEdge.getEndPoint() + (aTangentB * fHalfLineWidth));
482 
483                         if(bCut)
484                         {
485                             aEndPolygon.append(rEdge.getEndPoint() + aPerpendEndA);
486                         }
487 
488                         aEndPolygon.append(aEnd + aPerpendEndA);
489                         aEndPolygon.append(aEnd + aPerpendEndB);
490 
491                         if(bCut)
492                         {
493                             aEndPolygon.append(rEdge.getEndPoint() + aPerpendEndB);
494                         }
495                     }
496 
497                     if(bCut)
498                     {
499                         aEndPolygon.append(rEdge.getEndPoint());
500                         aEndPolygon.setClosed(true);
501                         aRetval.append(aEndPolygon);
502                     }
503                     else
504                     {
505                         aBezierPolygon.append(aEndPolygon);
506                     }
507                 }
508                 else
509                 {
510                     // append original in-between point
511                     aBezierPolygon.append(rEdge.getEndPoint());
512                 }
513 
514                 // create lower edge.
515                 {
516                     if(bCutB)
517                     {
518                         // calculate cut point and add
519                         const B2DPoint aCutPoint(rEdge.getEndPoint() + (aPerpendEndB * fCutB));
520                         aBezierPolygon.append(aCutPoint);
521                     }
522                     else
523                     {
524                         // create scaled bezier segment
525                         const B2DPoint aStart(rEdge.getEndPoint() + aPerpendEndB);
526                         const B2DPoint aEnd(rEdge.getStartPoint() + aPerpendStartB);
527                         const B2DVector aEdge(aEnd - aStart);
528                         const double fLength(aEdge.getLength());
529                         const double fScale(bIsEdgeLengthZero ? 1.0 : fLength / fEdgeLength);
530                         const B2DVector fRelNext(rEdge.getControlPointB() - rEdge.getEndPoint());
531                         const B2DVector fRelPrev(rEdge.getControlPointA() - rEdge.getStartPoint());
532 
533                         aBezierPolygon.append(aStart);
534                         aBezierPolygon.appendBezierSegment(aStart + (fRelNext * fScale), aEnd + (fRelPrev * fScale), aEnd);
535                     }
536                 }
537 
538                 // close and return
539                 aBezierPolygon.setClosed(true);
540                 aRetval.append(aBezierPolygon);
541 
542                 return aRetval;
543             }
544             else
545             {
546                 // Get start and  end point, create tangent and set to needed length
547                 B2DVector aTangent(rEdge.getEndPoint() - rEdge.getStartPoint());
548                 aTangent.setLength(fHalfLineWidth);
549 
550                 // prepare return value
551                 B2DPolygon aEdgePolygon;
552 
553                 // buffered angle
554                 double fAngle(0.0);
555                 bool bAngle(false);
556 
557                 // buffered perpendicular
558                 B2DVector aPerpend;
559                 bool bPerpend(false);
560 
561                 // create left vertical
562                 if(bStartRound)
563                 {
564                     aEdgePolygon = tools::createHalfUnitCircle();
565                     fAngle = atan2(aTangent.getY(), aTangent.getX());
566                     bAngle = true;
567                     aEdgePolygon.transform(
568                         tools::createScaleShearXRotateTranslateB2DHomMatrix(
569                             fHalfLineWidth, fHalfLineWidth,
570                             0.0,
571                             fAngle + F_PI2,
572                             rEdge.getStartPoint().getX(), rEdge.getStartPoint().getY()));
573                 }
574                 else
575                 {
576                     aPerpend.setX(-aTangent.getY());
577                     aPerpend.setY(aTangent.getX());
578                     bPerpend = true;
579 
580                     if(bStartSquare)
581                     {
582                         const basegfx::B2DPoint aStart(rEdge.getStartPoint() - aTangent);
583 
584                         aEdgePolygon.append(aStart + aPerpend);
585                         aEdgePolygon.append(aStart - aPerpend);
586                     }
587                     else
588                     {
589                         aEdgePolygon.append(rEdge.getStartPoint() + aPerpend);
590                         aEdgePolygon.append(rEdge.getStartPoint()); // keep the in-between point for numerical reasons
591                         aEdgePolygon.append(rEdge.getStartPoint() - aPerpend);
592                     }
593                 }
594 
595                 // create right vertical
596                 if(bEndRound)
597                 {
598                     basegfx::B2DPolygon aEndPolygon(tools::createHalfUnitCircle());
599 
600                     if(!bAngle)
601                     {
602                         fAngle = atan2(aTangent.getY(), aTangent.getX());
603                     }
604 
605                     aEndPolygon.transform(
606                         tools::createScaleShearXRotateTranslateB2DHomMatrix(
607                             fHalfLineWidth, fHalfLineWidth,
608                             0.0,
609                             fAngle - F_PI2,
610                             rEdge.getEndPoint().getX(), rEdge.getEndPoint().getY()));
611                     aEdgePolygon.append(aEndPolygon);
612                 }
613                 else
614                 {
615                     if(!bPerpend)
616                     {
617                         aPerpend.setX(-aTangent.getY());
618                         aPerpend.setY(aTangent.getX());
619                     }
620 
621                     if(bEndSquare)
622                     {
623                         const basegfx::B2DPoint aEnd(rEdge.getEndPoint() + aTangent);
624 
625                         aEdgePolygon.append(aEnd - aPerpend);
626                         aEdgePolygon.append(aEnd + aPerpend);
627                     }
628                     else
629                     {
630                         aEdgePolygon.append(rEdge.getEndPoint() - aPerpend);
631                         aEdgePolygon.append(rEdge.getEndPoint()); // keep the in-between point for numerical reasons
632                         aEdgePolygon.append(rEdge.getEndPoint() + aPerpend);
633                     }
634                 }
635 
636                 // close and return
637                 aEdgePolygon.setClosed(true);
638 
639                 return B2DPolyPolygon(aEdgePolygon);
640             }
641         }
642 
643         B2DPolygon createAreaGeometryForJoin(
644             const B2DVector& rTangentPrev,
645             const B2DVector& rTangentEdge,
646             const B2DVector& rPerpendPrev,
647             const B2DVector& rPerpendEdge,
648             const B2DPoint& rPoint,
649             double fHalfLineWidth,
650             B2DLineJoin eJoin,
651             double fMiterMinimumAngle)
652 		{
653 			OSL_ENSURE(fHalfLineWidth > 0.0, "createAreaGeometryForJoin: LineWidth too small (!)");
654 			OSL_ENSURE(B2DLINEJOIN_NONE != eJoin, "createAreaGeometryForJoin: B2DLINEJOIN_NONE not allowed (!)");
655 
656             // LineJoin from tangent rPerpendPrev to tangent rPerpendEdge in rPoint
657             B2DPolygon aEdgePolygon;
658 			const B2DPoint aStartPoint(rPoint + rPerpendPrev);
659 			const B2DPoint aEndPoint(rPoint + rPerpendEdge);
660 
661 			// test if for Miter, the angle is too small and the fallback
662 			// to bevel needs to be used
663 			if(B2DLINEJOIN_MITER == eJoin)
664 			{
665 				const double fAngle(fabs(rPerpendPrev.angle(rPerpendEdge)));
666 
667 				if((F_PI - fAngle) < fMiterMinimumAngle)
668 				{
669 					// fallback to bevel
670 					eJoin = B2DLINEJOIN_BEVEL;
671 				}
672 			}
673 
674 			switch(eJoin)
675 			{
676 				case B2DLINEJOIN_MITER :
677 				{
678 					aEdgePolygon.append(aEndPoint);
679 					aEdgePolygon.append(rPoint);
680 					aEdgePolygon.append(aStartPoint);
681 
682 					// Look for the cut point between start point along rTangentPrev and
683 					// end point along rTangentEdge. -rTangentEdge should be used, but since
684 					// the cut value is used for interpolating along the first edge, the negation
685 					// is not needed since the same fCut will be found on the first edge.
686 					// If it exists, insert it to complete the mitered fill polygon.
687     				double fCutPos(0.0);
688 					tools::findCut(aStartPoint, rTangentPrev, aEndPoint, rTangentEdge, CUTFLAG_ALL, &fCutPos);
689 
690 					if(0.0 != fCutPos)
691 					{
692 						const B2DPoint aCutPoint(interpolate(aStartPoint, aStartPoint + rTangentPrev, fCutPos));
693 						aEdgePolygon.append(aCutPoint);
694 					}
695 
696 					break;
697 				}
698 				case B2DLINEJOIN_ROUND :
699 				{
700 					// use tooling to add needed EllipseSegment
701 					double fAngleStart(atan2(rPerpendPrev.getY(), rPerpendPrev.getX()));
702 					double fAngleEnd(atan2(rPerpendEdge.getY(), rPerpendEdge.getX()));
703 
704 					// atan2 results are [-PI .. PI], consolidate to [0.0 .. 2PI]
705 					if(fAngleStart < 0.0)
706 					{
707 						fAngleStart += F_2PI;
708 					}
709 
710 					if(fAngleEnd < 0.0)
711 					{
712 						fAngleEnd += F_2PI;
713 					}
714 
715 					const B2DPolygon aBow(tools::createPolygonFromEllipseSegment(rPoint, fHalfLineWidth, fHalfLineWidth, fAngleStart, fAngleEnd));
716 
717 					if(aBow.count() > 1)
718 					{
719 						// #i101491#
720 						// use the original start/end positions; the ones from bow creation may be numerically
721 						// different due to their different creation. To guarantee good merging quality with edges
722 						// and edge roundings (and to reduce point count)
723 						aEdgePolygon = aBow;
724 						aEdgePolygon.setB2DPoint(0, aStartPoint);
725 						aEdgePolygon.setB2DPoint(aEdgePolygon.count() - 1, aEndPoint);
726 						aEdgePolygon.append(rPoint);
727 
728 						break;
729 					}
730 					else
731 					{
732 						// wanted fall-through to default
733 					}
734 				}
735 				default: // B2DLINEJOIN_BEVEL
736 				{
737 					aEdgePolygon.append(aEndPoint);
738 					aEdgePolygon.append(rPoint);
739 					aEdgePolygon.append(aStartPoint);
740 
741 					break;
742 				}
743 			}
744 
745             // create last polygon part for edge
746             aEdgePolygon.setClosed(true);
747 
748             return aEdgePolygon;
749         }
750     } // end of anonymus namespace
751 
752 	namespace tools
753 	{
754         B2DPolyPolygon createAreaGeometry(
755             const B2DPolygon& rCandidate,
756             double fHalfLineWidth,
757             B2DLineJoin eJoin,
758             com::sun::star::drawing::LineCap eCap,
759             double fMaxAllowedAngle,
760 			double fMaxPartOfEdge,
761             double fMiterMinimumAngle)
762 		{
763             if(fMaxAllowedAngle > F_PI2)
764             {
765                 fMaxAllowedAngle = F_PI2;
766             }
767             else if(fMaxAllowedAngle < 0.01 * F_PI2)
768             {
769                 fMaxAllowedAngle = 0.01 * F_PI2;
770             }
771 
772             if(fMaxPartOfEdge > 1.0)
773             {
774                 fMaxPartOfEdge = 1.0;
775             }
776             else if(fMaxPartOfEdge < 0.01)
777             {
778                 fMaxPartOfEdge = 0.01;
779             }
780 
781             if(fMiterMinimumAngle > F_PI)
782             {
783                 fMiterMinimumAngle = F_PI;
784             }
785             else if(fMiterMinimumAngle < 0.01 * F_PI)
786             {
787                 fMiterMinimumAngle = 0.01 * F_PI;
788             }
789 
790             B2DPolygon aCandidate(rCandidate);
791             const double fMaxCos(cos(fMaxAllowedAngle));
792 
793             aCandidate.removeDoublePoints();
794             aCandidate = subdivideToSimple(aCandidate, fMaxCos * fMaxCos, fMaxPartOfEdge * fMaxPartOfEdge);
795 
796             const sal_uInt32 nPointCount(aCandidate.count());
797 
798 			if(nPointCount)
799 			{
800                 B2DPolyPolygon aRetval;
801 				const bool bEventuallyCreateLineJoin(B2DLINEJOIN_NONE != eJoin);
802                 const bool bIsClosed(aCandidate.isClosed());
803                 const sal_uInt32 nEdgeCount(bIsClosed ? nPointCount : nPointCount - 1);
804                 const bool bLineCap(!bIsClosed && com::sun::star::drawing::LineCap_BUTT != eCap);
805 
806                 if(nEdgeCount)
807                 {
808                     B2DCubicBezier aEdge;
809                     B2DCubicBezier aPrev;
810 
811                     // prepare edge
812                     aEdge.setStartPoint(aCandidate.getB2DPoint(0));
813 
814                     if(bIsClosed && bEventuallyCreateLineJoin)
815                     {
816                         // prepare previous edge
817                         const sal_uInt32 nPrevIndex(nPointCount - 1);
818                         aPrev.setStartPoint(aCandidate.getB2DPoint(nPrevIndex));
819                         aPrev.setControlPointA(aCandidate.getNextControlPoint(nPrevIndex));
820                         aPrev.setControlPointB(aCandidate.getPrevControlPoint(0));
821                         aPrev.setEndPoint(aEdge.getStartPoint());
822                     }
823 
824                     for(sal_uInt32 a(0); a < nEdgeCount; a++)
825                     {
826                         // fill current Edge
827                         const sal_uInt32 nNextIndex((a + 1) % nPointCount);
828                         aEdge.setControlPointA(aCandidate.getNextControlPoint(a));
829                         aEdge.setControlPointB(aCandidate.getPrevControlPoint(nNextIndex));
830                         aEdge.setEndPoint(aCandidate.getB2DPoint(nNextIndex));
831 
832                         // check and create linejoin
833                         if(bEventuallyCreateLineJoin && (bIsClosed || 0 != a))
834                         {
835                             B2DVector aTangentPrev(aPrev.getTangent(1.0)); aTangentPrev.normalize();
836                             B2DVector aTangentEdge(aEdge.getTangent(0.0)); aTangentEdge.normalize();
837                             B2VectorOrientation aOrientation(getOrientation(aTangentPrev, aTangentEdge));
838 
839                             if(ORIENTATION_NEUTRAL == aOrientation)
840                             {
841                                    // they are parallell or empty; if they are both not zero and point
842                                    // in opposite direction, a half-circle is needed
843                                    if(!aTangentPrev.equalZero() && !aTangentEdge.equalZero())
844                                    {
845                                     const double fAngle(fabs(aTangentPrev.angle(aTangentEdge)));
846 
847                                     if(fTools::equal(fAngle, F_PI))
848                                     {
849                                         // for half-circle production, fallback to positive
850                                         // orientation
851                                         aOrientation = ORIENTATION_POSITIVE;
852                                     }
853                                 }
854                             }
855 
856                             if(ORIENTATION_POSITIVE == aOrientation)
857                             {
858                                 const B2DVector aPerpendPrev(getPerpendicular(aTangentPrev) * -fHalfLineWidth);
859                                 const B2DVector aPerpendEdge(getPerpendicular(aTangentEdge) * -fHalfLineWidth);
860 
861                                 aRetval.append(
862                                     createAreaGeometryForJoin(
863                                         aTangentPrev,
864                                         aTangentEdge,
865                                         aPerpendPrev,
866                                         aPerpendEdge,
867                                         aEdge.getStartPoint(),
868                                         fHalfLineWidth,
869                                         eJoin,
870                                         fMiterMinimumAngle));
871                             }
872                             else if(ORIENTATION_NEGATIVE == aOrientation)
873                             {
874                                 const B2DVector aPerpendPrev(getPerpendicular(aTangentPrev) * fHalfLineWidth);
875                                 const B2DVector aPerpendEdge(getPerpendicular(aTangentEdge) * fHalfLineWidth);
876 
877                                 aRetval.append(
878                                     createAreaGeometryForJoin(
879                                         aTangentEdge,
880                                         aTangentPrev,
881                                         aPerpendEdge,
882                                         aPerpendPrev,
883                                         aEdge.getStartPoint(),
884                                         fHalfLineWidth,
885                                         eJoin,
886                                         fMiterMinimumAngle));
887                             }
888                         }
889 
890                         // create geometry for edge
891                         const bool bLast(a + 1 == nEdgeCount);
892 
893                         if(bLineCap)
894                         {
895                             const bool bFirst(!a);
896 
897                             aRetval.append(
898                                 createAreaGeometryForEdge(
899                                     aEdge,
900                                     fHalfLineWidth,
901                                     bFirst && com::sun::star::drawing::LineCap_ROUND == eCap,
902                                     bLast && com::sun::star::drawing::LineCap_ROUND == eCap,
903                                     bFirst && com::sun::star::drawing::LineCap_SQUARE == eCap,
904                                     bLast && com::sun::star::drawing::LineCap_SQUARE == eCap));
905                         }
906                         else
907                         {
908                             aRetval.append(
909                                 createAreaGeometryForEdge(
910                                     aEdge,
911                                     fHalfLineWidth,
912                                     false,
913                                     false,
914                                     false,
915                                     false));
916                         }
917 
918                         // prepare next step
919                         if(!bLast)
920                         {
921                             if(bEventuallyCreateLineJoin)
922                             {
923                                 aPrev = aEdge;
924                             }
925 
926                             aEdge.setStartPoint(aEdge.getEndPoint());
927                         }
928                     }
929                 }
930 
931                 return aRetval;
932             }
933             else
934             {
935                 return B2DPolyPolygon(rCandidate);
936             }
937         }
938     } // end of namespace tools
939 } // end of namespace basegfx
940 
941 //////////////////////////////////////////////////////////////////////////////
942 // eof
943