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