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