Coverage Report

Created: 2025-07-07 10:01

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