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