Coverage Report

Created: 2025-12-31 10:39

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