Coverage Report

Created: 2026-02-14 09:37

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/basegfx/source/polygon/b2dsvgpolypolygon.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 <basegfx/polygon/b2dpolygontools.hxx>
21
#include <basegfx/polygon/b2dpolypolygontools.hxx>
22
#include <basegfx/polygon/b2dpolypolygon.hxx>
23
#include <basegfx/matrix/b2dhommatrix.hxx>
24
#include <basegfx/matrix/b2dhommatrixtools.hxx>
25
#include <basegfx/vector/b2enums.hxx>
26
27
#include <rtl/ustring.hxx>
28
#include <sal/log.hxx>
29
#include <rtl/math.hxx>
30
#include <rtl/character.hxx>
31
#include <stringconversiontools.hxx>
32
33
namespace
34
{
35
36
void putCommandChar(OUStringBuffer& rBuffer,sal_Unicode& rLastSVGCommand, sal_Unicode aChar, bool bToLower,bool bVerbose)
37
68
{
38
68
    const sal_Unicode aCommand = bToLower ? rtl::toAsciiLowerCase(aChar) : aChar;
39
40
68
    if (bVerbose && rBuffer.getLength())
41
0
        rBuffer.append(' ');
42
43
68
    if (bVerbose || rLastSVGCommand != aCommand)
44
68
    {
45
68
        rBuffer.append(aCommand);
46
68
        rLastSVGCommand = aCommand;
47
68
    }
48
68
}
49
50
void putNumberChar(OUStringBuffer& rStr,double fValue, double fOldValue, bool bUseRelativeCoordinates,bool bVerbose)
51
85
{
52
85
    if (bUseRelativeCoordinates)
53
51
        fValue -= fOldValue;
54
55
85
    const sal_Int32 aLen(rStr.getLength());
56
85
    if (bVerbose || (aLen && basegfx::internal::isOnNumberChar(rStr[aLen - 1], false) && fValue >= 0.0))
57
34
        rStr.append(' ');
58
59
85
    rStr.append(fValue);
60
85
}
61
62
}
63
64
namespace basegfx::utils
65
{
66
        bool PointIndex::operator<(const PointIndex& rComp) const
67
0
        {
68
0
            if(rComp.getPolygonIndex() == getPolygonIndex())
69
0
            {
70
0
                return rComp.getPointIndex() < getPointIndex();
71
0
            }
72
73
0
            return rComp.getPolygonIndex() < getPolygonIndex();
74
0
        }
75
76
        bool importFromSvgD(
77
            B2DPolyPolygon& o_rPolyPolygon,
78
            std::u16string_view rSvgDStatement,
79
            bool bHandleRelativeNextPointCompatible,
80
            PointIndexSet* pHelpPointIndexSet)
81
18.9k
        {
82
18.9k
            o_rPolyPolygon.clear();
83
18.9k
            const sal_Int32 nLen(rSvgDStatement.size());
84
18.9k
            sal_Int32 nPos(0);
85
18.9k
            double nLastX( 0.0 );
86
18.9k
            double nLastY( 0.0 );
87
18.9k
            B2DPolygon aCurrPoly;
88
89
            // skip initial whitespace
90
18.9k
            basegfx::internal::skipSpaces(nPos, rSvgDStatement, nLen);
91
92
462k
            while(nPos < nLen)
93
444k
            {
94
444k
                bool bRelative(false);
95
444k
                const sal_Unicode aCurrChar(rSvgDStatement[nPos]);
96
97
444k
                if(o_rPolyPolygon.count() && !aCurrPoly.count() && aCurrChar != 'm' && aCurrChar != 'M')
98
10.6k
                {
99
                    // we have a new sub-polygon starting, but without a 'moveto' command.
100
                    // this requires to add the current point as start point to the polygon
101
                    // (see SVG1.1 8.3.3 The "closepath" command)
102
10.6k
                    aCurrPoly.append(B2DPoint(nLastX, nLastY));
103
10.6k
                }
104
105
444k
                switch(aCurrChar)
106
444k
                {
107
18.0k
                    case 'z' :
108
18.2k
                    case 'Z' :
109
18.2k
                    {
110
                        // consume CurrChar and whitespace
111
18.2k
                        nPos++;
112
18.2k
                        basegfx::internal::skipSpaces(nPos, rSvgDStatement, nLen);
113
114
                        // create closed polygon and reset import values
115
18.2k
                        if(aCurrPoly.count())
116
18.0k
                        {
117
18.0k
                            if(!bHandleRelativeNextPointCompatible)
118
17.9k
                            {
119
                                // SVG defines that "the next subpath starts at the
120
                                // same initial point as the current subpath", so set the
121
                                // current point if we do not need to be compatible
122
17.9k
                                nLastX = aCurrPoly.getB2DPoint(0).getX();
123
17.9k
                                nLastY = aCurrPoly.getB2DPoint(0).getY();
124
17.9k
                            }
125
126
18.0k
                            aCurrPoly.setClosed(true);
127
18.0k
                            o_rPolyPolygon.append(aCurrPoly);
128
18.0k
                            aCurrPoly.clear();
129
18.0k
                        }
130
131
18.2k
                        break;
132
18.0k
                    }
133
134
29.1k
                    case 'm' :
135
32.0k
                    case 'M' :
136
32.0k
                    {
137
                        // create non-closed polygon and reset import values
138
32.0k
                        if(aCurrPoly.count())
139
10.5k
                        {
140
10.5k
                            o_rPolyPolygon.append(aCurrPoly);
141
10.5k
                            aCurrPoly.clear();
142
10.5k
                        }
143
32.0k
                        [[fallthrough]]; // to add coordinate data as 1st point of new polygon
144
32.0k
                    }
145
35.9k
                    case 'l' :
146
39.2k
                    case 'L' :
147
39.2k
                    {
148
39.2k
                        if(aCurrChar == 'm' || aCurrChar == 'l')
149
33.0k
                        {
150
33.0k
                            bRelative = true;
151
33.0k
                        }
152
153
                        // consume CurrChar and whitespace
154
39.2k
                        nPos++;
155
39.2k
                        basegfx::internal::skipSpaces(nPos, rSvgDStatement, nLen);
156
157
69.6k
                        while(nPos < nLen && basegfx::internal::isOnNumberChar(rSvgDStatement, nPos))
158
30.7k
                        {
159
30.7k
                            double nX, nY;
160
161
30.7k
                            if(!basegfx::internal::importDoubleAndSpaces(nX, nPos, rSvgDStatement, nLen)) return false;
162
30.7k
                            if(!basegfx::internal::importDoubleAndSpaces(nY, nPos, rSvgDStatement, nLen)) return false;
163
164
30.3k
                            if(bRelative)
165
30.2k
                            {
166
30.2k
                                nX += nLastX;
167
30.2k
                                nY += nLastY;
168
30.2k
                            }
169
170
                            // set last position
171
30.3k
                            nLastX = nX;
172
30.3k
                            nLastY = nY;
173
174
                            // add point
175
30.3k
                            aCurrPoly.append(B2DPoint(nX, nY));
176
30.3k
                        }
177
38.8k
                        break;
178
39.2k
                    }
179
180
38.8k
                    case 'h' :
181
17.6k
                    {
182
17.6k
                        bRelative = true;
183
17.6k
                        [[fallthrough]];
184
17.6k
                    }
185
17.8k
                    case 'H' :
186
17.8k
                    {
187
17.8k
                        nPos++;
188
17.8k
                        basegfx::internal::skipSpaces(nPos, rSvgDStatement, nLen);
189
190
28.6k
                        while(nPos < nLen && basegfx::internal::isOnNumberChar(rSvgDStatement, nPos))
191
10.7k
                        {
192
10.7k
                            double nX, nY(nLastY);
193
194
10.7k
                            if(!basegfx::internal::importDoubleAndSpaces(nX, nPos, rSvgDStatement, nLen)) return false;
195
196
10.7k
                            if(bRelative)
197
10.7k
                            {
198
10.7k
                                nX += nLastX;
199
10.7k
                            }
200
201
                            // set last position
202
10.7k
                            nLastX = nX;
203
204
                            // add point
205
10.7k
                            aCurrPoly.append(B2DPoint(nX, nY));
206
10.7k
                        }
207
17.8k
                        break;
208
17.8k
                    }
209
210
17.8k
                    case 'v' :
211
52
                    {
212
52
                        bRelative = true;
213
52
                        [[fallthrough]];
214
52
                    }
215
64
                    case 'V' :
216
64
                    {
217
64
                        nPos++;
218
64
                        basegfx::internal::skipSpaces(nPos, rSvgDStatement, nLen);
219
220
103
                        while(nPos < nLen && basegfx::internal::isOnNumberChar(rSvgDStatement, nPos))
221
39
                        {
222
39
                            double nX(nLastX), nY;
223
224
39
                            if(!basegfx::internal::importDoubleAndSpaces(nY, nPos, rSvgDStatement, nLen)) return false;
225
226
39
                            if(bRelative)
227
34
                            {
228
34
                                nY += nLastY;
229
34
                            }
230
231
                            // set last position
232
39
                            nLastY = nY;
233
234
                            // add point
235
39
                            aCurrPoly.append(B2DPoint(nX, nY));
236
39
                        }
237
64
                        break;
238
64
                    }
239
240
10.7k
                    case 's' :
241
10.7k
                    {
242
10.7k
                        bRelative = true;
243
10.7k
                        [[fallthrough]];
244
10.7k
                    }
245
10.8k
                    case 'S' :
246
10.8k
                    {
247
10.8k
                        nPos++;
248
10.8k
                        basegfx::internal::skipSpaces(nPos, rSvgDStatement, nLen);
249
250
10.8k
                        while(nPos < nLen && basegfx::internal::isOnNumberChar(rSvgDStatement, nPos))
251
167
                        {
252
167
                            double nX, nY;
253
167
                            double nX2, nY2;
254
255
167
                            if(!basegfx::internal::importDoubleAndSpaces(nX2, nPos, rSvgDStatement, nLen)) return false;
256
167
                            if(!basegfx::internal::importDoubleAndSpaces(nY2, nPos, rSvgDStatement, nLen)) return false;
257
139
                            if(!basegfx::internal::importDoubleAndSpaces(nX, nPos, rSvgDStatement, nLen)) return false;
258
5
                            if(!basegfx::internal::importDoubleAndSpaces(nY, nPos, rSvgDStatement, nLen)) return false;
259
260
3
                            if(bRelative)
261
2
                            {
262
2
                                nX2 += nLastX;
263
2
                                nY2 += nLastY;
264
2
                                nX += nLastX;
265
2
                                nY += nLastY;
266
2
                            }
267
268
                            // ensure existence of start point
269
3
                            sal_uInt32 nCurrPolyCount = aCurrPoly.count();
270
3
                            if (nCurrPolyCount == 0)
271
2
                            {
272
2
                                aCurrPoly.append(B2DPoint(nLastX, nLastY));
273
2
                                nCurrPolyCount = 1;
274
2
                            }
275
3
                            assert(nCurrPolyCount > 0 && "coverity 2023.12.2");
276
277
                            // get first control point. It's the reflection of the PrevControlPoint
278
                            // of the last point. If not existent, use current point (see SVG)
279
3
                            B2DPoint aPrevControl(nLastX, nLastY);
280
3
                            const sal_uInt32 nIndex(nCurrPolyCount - 1);
281
282
3
                            if(aCurrPoly.areControlPointsUsed() && aCurrPoly.isPrevControlPointUsed(nIndex))
283
0
                            {
284
0
                                const B2DPoint aPrevPoint(aCurrPoly.getB2DPoint(nIndex));
285
0
                                const B2DPoint aPrevControlPoint(aCurrPoly.getPrevControlPoint(nIndex));
286
287
                                // use mirrored previous control point
288
0
                                aPrevControl.setX((2.0 * aPrevPoint.getX()) - aPrevControlPoint.getX());
289
0
                                aPrevControl.setY((2.0 * aPrevPoint.getY()) - aPrevControlPoint.getY());
290
0
                            }
291
292
                            // append curved edge
293
3
                            aCurrPoly.appendBezierSegment(aPrevControl, B2DPoint(nX2, nY2), B2DPoint(nX, nY));
294
295
                            // set last position
296
3
                            nLastX = nX;
297
3
                            nLastY = nY;
298
3
                        }
299
10.7k
                        break;
300
10.8k
                    }
301
302
10.7k
                    case 'c' :
303
10.7k
                    {
304
10.7k
                        bRelative = true;
305
10.7k
                        [[fallthrough]];
306
10.7k
                    }
307
10.8k
                    case 'C' :
308
10.8k
                    {
309
10.8k
                        nPos++;
310
10.8k
                        basegfx::internal::skipSpaces(nPos, rSvgDStatement, nLen);
311
312
10.8k
                        while(nPos < nLen && basegfx::internal::isOnNumberChar(rSvgDStatement, nPos))
313
50
                        {
314
50
                            double nX, nY;
315
50
                            double nX1, nY1;
316
50
                            double nX2, nY2;
317
318
50
                            if(!basegfx::internal::importDoubleAndSpaces(nX1, nPos, rSvgDStatement, nLen)) return false;
319
50
                            if(!basegfx::internal::importDoubleAndSpaces(nY1, nPos, rSvgDStatement, nLen)) return false;
320
2
                            if(!basegfx::internal::importDoubleAndSpaces(nX2, nPos, rSvgDStatement, nLen)) return false;
321
1
                            if(!basegfx::internal::importDoubleAndSpaces(nY2, nPos, rSvgDStatement, nLen)) return false;
322
1
                            if(!basegfx::internal::importDoubleAndSpaces(nX, nPos, rSvgDStatement, nLen)) return false;
323
0
                            if(!basegfx::internal::importDoubleAndSpaces(nY, nPos, rSvgDStatement, nLen)) return false;
324
325
0
                            if(bRelative)
326
0
                            {
327
0
                                nX1 += nLastX;
328
0
                                nY1 += nLastY;
329
0
                                nX2 += nLastX;
330
0
                                nY2 += nLastY;
331
0
                                nX += nLastX;
332
0
                                nY += nLastY;
333
0
                            }
334
335
                            // ensure existence of start point
336
0
                            if(!aCurrPoly.count())
337
0
                            {
338
0
                                aCurrPoly.append(B2DPoint(nLastX, nLastY));
339
0
                            }
340
341
                            // append curved edge
342
0
                            aCurrPoly.appendBezierSegment(B2DPoint(nX1, nY1), B2DPoint(nX2, nY2), B2DPoint(nX, nY));
343
344
                            // set last position
345
0
                            nLastX = nX;
346
0
                            nLastY = nY;
347
0
                        }
348
10.7k
                        break;
349
10.8k
                    }
350
351
                    // #100617# quadratic beziers are imported as cubic ones
352
10.7k
                    case 'q' :
353
3.48k
                    {
354
3.48k
                        bRelative = true;
355
3.48k
                        [[fallthrough]];
356
3.48k
                    }
357
3.48k
                    case 'Q' :
358
3.48k
                    {
359
3.48k
                        nPos++;
360
3.48k
                        basegfx::internal::skipSpaces(nPos, rSvgDStatement, nLen);
361
362
6.82k
                        while(nPos < nLen && basegfx::internal::isOnNumberChar(rSvgDStatement, nPos))
363
3.57k
                        {
364
3.57k
                            double nX, nY;
365
3.57k
                            double nX1, nY1;
366
367
3.57k
                            if(!basegfx::internal::importDoubleAndSpaces(nX1, nPos, rSvgDStatement, nLen)) return false;
368
3.57k
                            if(!basegfx::internal::importDoubleAndSpaces(nY1, nPos, rSvgDStatement, nLen)) return false;
369
3.40k
                            if(!basegfx::internal::importDoubleAndSpaces(nX, nPos, rSvgDStatement, nLen)) return false;
370
3.37k
                            if(!basegfx::internal::importDoubleAndSpaces(nY, nPos, rSvgDStatement, nLen)) return false;
371
372
3.33k
                            if(bRelative)
373
3.33k
                            {
374
3.33k
                                nX1 += nLastX;
375
3.33k
                                nY1 += nLastY;
376
3.33k
                                nX += nLastX;
377
3.33k
                                nY += nLastY;
378
3.33k
                            }
379
380
                            // ensure existence of start point
381
3.33k
                            if(!aCurrPoly.count())
382
3.31k
                            {
383
3.31k
                                aCurrPoly.append(B2DPoint(nLastX, nLastY));
384
3.31k
                            }
385
386
                            // append curved edge
387
3.33k
                            aCurrPoly.appendQuadraticBezierSegment(B2DPoint(nX1, nY1), B2DPoint(nX, nY));
388
389
                            // set last position
390
3.33k
                            nLastX = nX;
391
3.33k
                            nLastY = nY;
392
3.33k
                        }
393
3.24k
                        break;
394
3.48k
                    }
395
396
                    // #100617# relative quadratic beziers are imported as cubic
397
7.17k
                    case 't' :
398
7.17k
                    {
399
7.17k
                        bRelative = true;
400
7.17k
                        [[fallthrough]];
401
7.17k
                    }
402
14.2k
                    case 'T' :
403
14.2k
                    {
404
14.2k
                        nPos++;
405
14.2k
                        basegfx::internal::skipSpaces(nPos, rSvgDStatement, nLen);
406
407
14.3k
                        while(nPos < nLen && basegfx::internal::isOnNumberChar(rSvgDStatement, nPos))
408
83
                        {
409
83
                            double nX, nY;
410
411
83
                            if(!basegfx::internal::importDoubleAndSpaces(nX, nPos, rSvgDStatement, nLen)) return false;
412
83
                            if(!basegfx::internal::importDoubleAndSpaces(nY, nPos, rSvgDStatement, nLen)) return false;
413
414
18
                            if(bRelative)
415
18
                            {
416
18
                                nX += nLastX;
417
18
                                nY += nLastY;
418
18
                            }
419
420
                            // ensure existence of start point
421
18
                            sal_uInt32 nCurrPolyCount = aCurrPoly.count();
422
18
                            if (nCurrPolyCount == 0)
423
1
                            {
424
1
                                aCurrPoly.append(B2DPoint(nLastX, nLastY));
425
1
                                nCurrPolyCount = 1;
426
1
                            }
427
18
                            assert(nCurrPolyCount > 0 && "coverity 2023.12.2");
428
429
                            // get first control point. It's the reflection of the PrevControlPoint
430
                            // of the last point. If not existent, use current point (see SVG)
431
18
                            B2DPoint aPrevControl(nLastX, nLastY);
432
18
                            const sal_uInt32 nIndex(nCurrPolyCount - 1);
433
18
                            const B2DPoint aPrevPoint(aCurrPoly.getB2DPoint(nIndex));
434
435
18
                            if(aCurrPoly.areControlPointsUsed() && aCurrPoly.isPrevControlPointUsed(nIndex))
436
0
                            {
437
0
                                const B2DPoint aPrevControlPoint(aCurrPoly.getPrevControlPoint(nIndex));
438
439
                                // use mirrored previous control point
440
0
                                aPrevControl.setX((2.0 * aPrevPoint.getX()) - aPrevControlPoint.getX());
441
0
                                aPrevControl.setY((2.0 * aPrevPoint.getY()) - aPrevControlPoint.getY());
442
0
                            }
443
444
18
                            if(!aPrevControl.equal(aPrevPoint))
445
0
                            {
446
                                // there is a prev control point, and we have the already mirrored one
447
                                // in aPrevControl. We also need the quadratic control point for this
448
                                // new quadratic segment to calculate the 2nd cubic control point
449
0
                                const B2DPoint aQuadControlPoint(
450
0
                                    ((3.0 * aPrevControl.getX()) - aPrevPoint.getX()) / 2.0,
451
0
                                    ((3.0 * aPrevControl.getY()) - aPrevPoint.getY()) / 2.0);
452
453
                                // calculate the cubic bezier coefficients from the quadratic ones.
454
0
                                const double nX2Prime((aQuadControlPoint.getX() * 2.0 + nX) / 3.0);
455
0
                                const double nY2Prime((aQuadControlPoint.getY() * 2.0 + nY) / 3.0);
456
457
                                // append curved edge, use mirrored cubic control point directly
458
0
                                aCurrPoly.appendBezierSegment(aPrevControl, B2DPoint(nX2Prime, nY2Prime), B2DPoint(nX, nY));
459
0
                            }
460
18
                            else
461
18
                            {
462
                                // when no previous control, SVG says to use current point -> straight line.
463
                                // Just add end point
464
18
                                aCurrPoly.append(B2DPoint(nX, nY));
465
18
                            }
466
467
                            // set last position
468
18
                            nLastX = nX;
469
18
                            nLastY = nY;
470
18
                        }
471
14.2k
                        break;
472
14.2k
                    }
473
474
14.2k
                    case 'a' :
475
3.90k
                    {
476
3.90k
                        bRelative = true;
477
3.90k
                        [[fallthrough]];
478
3.90k
                    }
479
3.92k
                    case 'A' :
480
3.92k
                    {
481
3.92k
                        nPos++;
482
3.92k
                        basegfx::internal::skipSpaces(nPos, rSvgDStatement, nLen);
483
484
3.92k
                        while(nPos < nLen && basegfx::internal::isOnNumberChar(rSvgDStatement, nPos))
485
5
                        {
486
5
                            double nX, nY;
487
5
                            double fRX, fRY, fPhi;
488
5
                            sal_Int32 bLargeArcFlag, bSweepFlag;
489
490
5
                            if(!basegfx::internal::importDoubleAndSpaces(fRX, nPos, rSvgDStatement, nLen)) return false;
491
5
                            if(!basegfx::internal::importDoubleAndSpaces(fRY, nPos, rSvgDStatement, nLen)) return false;
492
1
                            if(!basegfx::internal::importDoubleAndSpaces(fPhi, nPos, rSvgDStatement, nLen)) return false;
493
1
                            if(!basegfx::internal::importFlagAndSpaces(bLargeArcFlag, nPos, rSvgDStatement, nLen)) return false;
494
0
                            if(!basegfx::internal::importFlagAndSpaces(bSweepFlag, nPos, rSvgDStatement, nLen)) return false;
495
0
                            if(!basegfx::internal::importDoubleAndSpaces(nX, nPos, rSvgDStatement, nLen)) return false;
496
0
                            if(!basegfx::internal::importDoubleAndSpaces(nY, nPos, rSvgDStatement, nLen)) return false;
497
498
0
                            if(bRelative)
499
0
                            {
500
0
                                nX += nLastX;
501
0
                                nY += nLastY;
502
0
                            }
503
504
0
                            if( rtl::math::approxEqual(nX, nLastX) && rtl::math::approxEqual(nY, nLastY) )
505
0
                                continue; // start==end -> skip according to SVG spec
506
507
0
                            if( fRX == 0.0 || fRY == 0.0 )
508
0
                            {
509
                                // straight line segment according to SVG spec
510
0
                                aCurrPoly.append(B2DPoint(nX, nY));
511
0
                            }
512
0
                            else
513
0
                            {
514
                                // normalize according to SVG spec
515
0
                                fRX=fabs(fRX); fRY=fabs(fRY);
516
517
                                // from the SVG spec, appendix F.6.4
518
519
                                // |x1'|   |cos phi   sin phi|  |(x1 - x2)/2|
520
                                // |y1'| = |-sin phi  cos phi|  |(y1 - y2)/2|
521
0
                                const B2DPoint p1(nLastX, nLastY);
522
0
                                const B2DPoint p2(nX, nY);
523
0
                                B2DHomMatrix aTransform(basegfx::utils::createRotateB2DHomMatrix(
524
0
                                    -deg2rad(fPhi)));
525
526
0
                                const B2DPoint p1_prime( aTransform * B2DPoint(((p1-p2)/2.0)) );
527
528
                                //           ______________________________________       rx y1'
529
                                // |cx'|  + /  rx^2 ry^2 - rx^2 y1'^2 - ry^2 x1^2           ry
530
                                // |cy'| =-/       rx^2y1'^2 + ry^2 x1'^2               - ry x1'
531
                                //                                                          rx
532
                                // chose + if f_A != f_S
533
                                // chose - if f_A  = f_S
534
0
                                B2DPoint aCenter_prime;
535
0
                                const double fRadicant(
536
0
                                    (fRX*fRX*fRY*fRY - fRX*fRX*p1_prime.getY()*p1_prime.getY() - fRY*fRY*p1_prime.getX()*p1_prime.getX())/
537
0
                                    (fRX*fRX*p1_prime.getY()*p1_prime.getY() + fRY*fRY*p1_prime.getX()*p1_prime.getX()));
538
0
                                if( fRadicant < 0.0 )
539
0
                                {
540
                                    // no solution - according to SVG
541
                                    // spec, scale up ellipse
542
                                    // uniformly such that it passes
543
                                    // through end points (denominator
544
                                    // of radicant solved for fRY,
545
                                    // with s=fRX/fRY)
546
0
                                    const double fRatio(fRX/fRY);
547
0
                                    fRY=std::hypot(p1_prime.getY(), p1_prime.getX()/fRatio);
548
0
                                    fRX=fRatio*fRY;
549
550
                                    // keep center_prime forced to (0,0)
551
0
                                }
552
0
                                else
553
0
                                {
554
0
                                    const double fFactor(
555
0
                                        (bLargeArcFlag==bSweepFlag ? -1.0 : 1.0) *
556
0
                                        sqrt(fRadicant));
557
558
                                    // actually calculate center_prime
559
0
                                    aCenter_prime = B2DPoint(
560
0
                                        fFactor*fRX*p1_prime.getY()/fRY,
561
0
                                        -fFactor*fRY*p1_prime.getX()/fRX);
562
0
                                }
563
564
                                //              +           u - v
565
                                // angle(u,v) =  arccos( ------------ )     (take the sign of (ux vy - uy vx))
566
                                //              -        ||u|| ||v||
567
568
                                //                  1    | (x1' - cx')/rx |
569
                                // theta1 = angle((   ), |                | )
570
                                //                  0    | (y1' - cy')/ry |
571
0
                                const B2DPoint aRadii(fRX,fRY);
572
0
                                double fTheta1(
573
0
                                    B2DVector(1.0,0.0).angle(
574
0
                                        (p1_prime-aCenter_prime)/aRadii));
575
576
                                //                 |1|    |  (-x1' - cx')/rx |
577
                                // theta2 = angle( | | ,  |                  | )
578
                                //                 |0|    |  (-y1' - cy')/ry |
579
0
                                double fTheta2(
580
0
                                    B2DVector(1.0,0.0).angle(
581
0
                                        (-p1_prime-aCenter_prime)/aRadii));
582
583
                                // map both angles to [0,2pi)
584
0
                                fTheta1 = fmod(2*M_PI+fTheta1,2*M_PI);
585
0
                                fTheta2 = fmod(2*M_PI+fTheta2,2*M_PI);
586
587
                                // make sure the large arc is taken
588
                                // (since
589
                                // createPolygonFromEllipseSegment()
590
                                // normalizes to e.g. cw arc)
591
0
                                if( !bSweepFlag )
592
0
                                    std::swap(fTheta1,fTheta2);
593
594
                                // finally, create bezier polygon from this
595
0
                                B2DPolygon aSegment(
596
0
                                    utils::createPolygonFromUnitEllipseSegment(
597
0
                                        fTheta1, fTheta2 ));
598
599
                                // transform ellipse by rotation & move to final center
600
0
                                aTransform = basegfx::utils::createScaleB2DHomMatrix(fRX, fRY);
601
0
                                aTransform.translate(aCenter_prime.getX(),
602
0
                                                     aCenter_prime.getY());
603
0
                                aTransform.rotate(deg2rad(fPhi));
604
0
                                const B2DPoint aOffset((p1+p2)/2.0);
605
0
                                aTransform.translate(aOffset.getX(),
606
0
                                                     aOffset.getY());
607
0
                                aSegment.transform(aTransform);
608
609
                                // createPolygonFromEllipseSegment()
610
                                // always creates arcs that are
611
                                // positively oriented - flip polygon
612
                                // if we swapped angles above
613
0
                                if( !bSweepFlag )
614
0
                                    aSegment.flip();
615
616
                                // remember PointIndex of evtl. added pure helper points
617
0
                                sal_uInt32 nPointIndex(aCurrPoly.count() + 1);
618
0
                                aCurrPoly.append(aSegment);
619
620
                                // if asked for, mark pure helper points by adding them to the index list of
621
                                // helper points
622
0
                                if(pHelpPointIndexSet && aCurrPoly.count() > 1)
623
0
                                {
624
0
                                    const sal_uInt32 nPolyIndex(o_rPolyPolygon.count());
625
626
0
                                    for(;nPointIndex + 1 < aCurrPoly.count(); nPointIndex++)
627
0
                                    {
628
0
                                        pHelpPointIndexSet->insert(PointIndex(nPolyIndex, nPointIndex));
629
0
                                    }
630
0
                                }
631
0
                            }
632
633
                            // set last position
634
0
                            nLastX = nX;
635
0
                            nLastY = nY;
636
0
                        }
637
3.91k
                        break;
638
3.92k
                    }
639
640
326k
                    default:
641
326k
                    {
642
326k
                        SAL_WARN("basegfx", "importFromSvgD(): skipping tags in svg:d element (unknown: \""
643
326k
                                << OUString(aCurrChar)
644
326k
                                << "\")!");
645
326k
                        ++nPos;
646
326k
                        break;
647
326k
                    }
648
444k
                }
649
444k
            }
650
651
            // if there is polygon data, create non-closed polygon
652
18.0k
            if(aCurrPoly.count())
653
72
            {
654
72
                o_rPolyPolygon.append(aCurrPoly);
655
72
            }
656
657
18.0k
            return true;
658
18.9k
        }
659
660
        bool importFromSvgPoints( B2DPolygon&            o_rPoly,
661
                                  std::u16string_view rSvgPointsAttribute )
662
108
        {
663
108
            o_rPoly.clear();
664
108
            const sal_Int32 nLen(rSvgPointsAttribute.size());
665
108
            sal_Int32 nPos(0);
666
108
            double nX, nY;
667
668
            // skip initial whitespace
669
108
            basegfx::internal::skipSpaces(nPos, rSvgPointsAttribute, nLen);
670
671
429
            while(nPos < nLen)
672
322
            {
673
322
                if(!basegfx::internal::importDoubleAndSpaces(nX, nPos, rSvgPointsAttribute, nLen)) return false;
674
322
                if(!basegfx::internal::importDoubleAndSpaces(nY, nPos, rSvgPointsAttribute, nLen)) return false;
675
676
                // add point
677
321
                o_rPoly.append(B2DPoint(nX, nY));
678
679
                // skip to next number, or finish
680
321
                basegfx::internal::skipSpaces(nPos, rSvgPointsAttribute, nLen);
681
321
            }
682
683
107
            return true;
684
108
        }
685
686
        OUString exportToSvgPoints( const B2DPolygon& rPoly )
687
277
        {
688
277
            SAL_WARN_IF(rPoly.areControlPointsUsed(), "basegfx", "exportToSvgPoints: Only non-bezier polygons allowed (!)");
689
277
            const sal_uInt32 nPointCount(rPoly.count());
690
277
            OUStringBuffer aResult;
691
692
833
            for(sal_uInt32 a(0); a < nPointCount; a++)
693
556
            {
694
556
                const basegfx::B2DPoint aPoint(rPoly.getB2DPoint(a));
695
696
556
                if(a)
697
279
                {
698
279
                    aResult.append(' ');
699
279
                }
700
701
556
                aResult.append(OUString::number(aPoint.getX())
702
556
                        + ","
703
556
                        + OUString::number(aPoint.getY()));
704
556
            }
705
706
277
            return aResult.makeStringAndClear();
707
277
        }
708
709
        OUString exportToSvgD(
710
            const B2DPolyPolygon& rPolyPolygon,
711
            bool bUseRelativeCoordinates,
712
            bool bDetectQuadraticBeziers,
713
            bool bHandleRelativeNextPointCompatible,
714
            bool bOOXMLMotionPath)
715
17
        {
716
17
            const sal_uInt32 nCount(rPolyPolygon.count());
717
17
            sal_uInt32 nCombinedPointCount = 0;
718
34
            for(sal_uInt32 i(0); i < nCount; i++)
719
17
            {
720
17
                const B2DPolygon& aPolygon(rPolyPolygon.getB2DPolygon(i));
721
17
                nCombinedPointCount += aPolygon.count();
722
17
            }
723
724
17
            OUStringBuffer aResult(std::max<int>(nCombinedPointCount * 32,512));
725
17
            B2DPoint aCurrentSVGPosition(0.0, 0.0); // SVG assumes (0,0) as the initial current point
726
727
34
            for(sal_uInt32 i(0); i < nCount; i++)
728
17
            {
729
17
                const B2DPolygon& aPolygon(rPolyPolygon.getB2DPolygon(i));
730
17
                const sal_uInt32 nPointCount(aPolygon.count());
731
732
17
                if(nPointCount)
733
17
                {
734
17
                    const bool bPolyUsesControlPoints(aPolygon.areControlPointsUsed());
735
17
                    const sal_uInt32 nEdgeCount(aPolygon.isClosed() ? nPointCount : nPointCount - 1);
736
17
                    sal_Unicode aLastSVGCommand(' '); // last SVG command char
737
17
                    B2DPoint aLeft, aRight; // for quadratic bezier test
738
739
                    // handle polygon start point
740
17
                    B2DPoint aEdgeStart(aPolygon.getB2DPoint(0));
741
17
                    bool bUseRelativeCoordinatesForFirstPoint(bUseRelativeCoordinates);
742
743
17
                    if(bHandleRelativeNextPointCompatible)
744
17
                    {
745
                        // To get around the error that the start point for the next polygon is the
746
                        // start point of the current one (and not the last as it was handled up to now)
747
                        // do force to write an absolute 'M' command as start for the next polygon
748
17
                        bUseRelativeCoordinatesForFirstPoint = false;
749
17
                    }
750
751
                    // Write 'moveto' and the 1st coordinates, set aLastSVGCommand to 'lineto'
752
17
                    putCommandChar(aResult, aLastSVGCommand, 'M', bUseRelativeCoordinatesForFirstPoint, bOOXMLMotionPath);
753
17
                    putNumberChar(aResult, aEdgeStart.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinatesForFirstPoint, bOOXMLMotionPath);
754
17
                    putNumberChar(aResult, aEdgeStart.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinatesForFirstPoint, bOOXMLMotionPath);
755
17
                    aLastSVGCommand =  bUseRelativeCoordinatesForFirstPoint ? 'l' : 'L';
756
17
                    aCurrentSVGPosition = aEdgeStart;
757
758
68
                    for(sal_uInt32 nIndex(0); nIndex < nEdgeCount; nIndex++)
759
51
                    {
760
                        // prepare access to next point
761
51
                        const sal_uInt32 nNextIndex((nIndex + 1) % nPointCount);
762
51
                        const B2DPoint aEdgeEnd(aPolygon.getB2DPoint(nNextIndex));
763
764
                        // handle edge from (aEdgeStart, aEdgeEnd) using indices (nIndex, nNextIndex)
765
51
                        const bool bEdgeIsBezier(bPolyUsesControlPoints
766
0
                            && (aPolygon.isNextControlPointUsed(nIndex) || aPolygon.isPrevControlPointUsed(nNextIndex)));
767
768
51
                        if(bEdgeIsBezier)
769
0
                        {
770
                            // handle bezier edge
771
0
                            const B2DPoint aControlEdgeStart(aPolygon.getNextControlPoint(nIndex));
772
0
                            const B2DPoint aControlEdgeEnd(aPolygon.getPrevControlPoint(nNextIndex));
773
0
                            bool bIsQuadraticBezier(false);
774
775
                            // check continuity at current edge's start point. For SVG, do NOT use an
776
                            // existing continuity since no 'S' or 's' statement should be written. At
777
                            // import, that 'previous' control vector is not available. SVG documentation
778
                            // says for interpretation:
779
780
                            // "(If there is no previous command or if the previous command was
781
                            // not a C, c, S or s, assume the first control point is coincident
782
                            // with the current point.)"
783
784
                            // That's what is done from our import, so avoid exporting it as first statement
785
                            // is necessary.
786
0
                            const bool bSymmetricAtEdgeStart(
787
0
                                !bOOXMLMotionPath && nIndex != 0
788
0
                                && aPolygon.getContinuityInPoint(nIndex) == B2VectorContinuity::C2);
789
790
0
                            if(bDetectQuadraticBeziers)
791
0
                            {
792
                                // check for quadratic beziers - that's
793
                                // the case if both control points are in
794
                                // the same place when they are prolonged
795
                                // to the common quadratic control point
796
797
                                // Left: P = (3P1 - P0) / 2
798
                                // Right: P = (3P2 - P3) / 2
799
0
                                aLeft = B2DPoint((3.0 * aControlEdgeStart - aEdgeStart) / 2.0);
800
0
                                aRight= B2DPoint((3.0 * aControlEdgeEnd - aEdgeEnd) / 2.0);
801
0
                                bIsQuadraticBezier = aLeft.equal(aRight);
802
0
                            }
803
804
0
                            if(bIsQuadraticBezier)
805
0
                            {
806
                                // approximately equal, export as quadratic bezier
807
0
                                if(bSymmetricAtEdgeStart)
808
0
                                {
809
0
                                    putCommandChar(aResult, aLastSVGCommand, 'T', bUseRelativeCoordinates, bOOXMLMotionPath);
810
811
0
                                    putNumberChar(aResult, aEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates, bOOXMLMotionPath);
812
0
                                    putNumberChar(aResult, aEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates, bOOXMLMotionPath);
813
0
                                    aCurrentSVGPosition = aEdgeEnd;
814
0
                                }
815
0
                                else
816
0
                                {
817
0
                                    putCommandChar(aResult, aLastSVGCommand, 'Q', bUseRelativeCoordinates, bOOXMLMotionPath);
818
819
0
                                    putNumberChar(aResult, aLeft.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates, bOOXMLMotionPath);
820
0
                                    putNumberChar(aResult, aLeft.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates, bOOXMLMotionPath);
821
0
                                    putNumberChar(aResult, aEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates, bOOXMLMotionPath);
822
0
                                    putNumberChar(aResult, aEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates, bOOXMLMotionPath);
823
0
                                    aCurrentSVGPosition = aEdgeEnd;
824
0
                                }
825
0
                            }
826
0
                            else
827
0
                            {
828
                                // export as cubic bezier
829
0
                                if(bSymmetricAtEdgeStart)
830
0
                                {
831
0
                                    putCommandChar(aResult, aLastSVGCommand, 'S', bUseRelativeCoordinates, bOOXMLMotionPath);
832
833
0
                                    putNumberChar(aResult, aControlEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates, bOOXMLMotionPath);
834
0
                                    putNumberChar(aResult, aControlEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates, bOOXMLMotionPath);
835
0
                                    putNumberChar(aResult, aEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates, bOOXMLMotionPath);
836
0
                                    putNumberChar(aResult, aEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates, bOOXMLMotionPath);
837
0
                                    aCurrentSVGPosition = aEdgeEnd;
838
0
                                }
839
0
                                else
840
0
                                {
841
0
                                    putCommandChar(aResult, aLastSVGCommand, 'C', bUseRelativeCoordinates, bOOXMLMotionPath);
842
843
0
                                    putNumberChar(aResult, aControlEdgeStart.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates, bOOXMLMotionPath);
844
0
                                    putNumberChar(aResult, aControlEdgeStart.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates, bOOXMLMotionPath);
845
0
                                    putNumberChar(aResult, aControlEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates, bOOXMLMotionPath);
846
0
                                    putNumberChar(aResult, aControlEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates, bOOXMLMotionPath);
847
0
                                    putNumberChar(aResult, aEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates, bOOXMLMotionPath);
848
0
                                    putNumberChar(aResult, aEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates, bOOXMLMotionPath);
849
0
                                    aCurrentSVGPosition = aEdgeEnd;
850
0
                                }
851
0
                            }
852
0
                        }
853
51
                        else
854
51
                        {
855
                            // straight edge
856
51
                            if(nNextIndex == 0)
857
17
                            {
858
                                // it's a closed polygon's last edge and it's not a bezier edge, so there is
859
                                // no need to write it
860
17
                            }
861
34
                            else
862
34
                            {
863
34
                                const bool bXEqual(rtl::math::approxEqual(aEdgeStart.getX(), aEdgeEnd.getX()));
864
34
                                const bool bYEqual(rtl::math::approxEqual(aEdgeStart.getY(), aEdgeEnd.getY()));
865
866
34
                                if(bXEqual && bYEqual)
867
0
                                {
868
                                    // point is a double point; do not export at all
869
0
                                }
870
34
                                else if(bXEqual && !bOOXMLMotionPath)
871
0
                                {
872
                                    // export as vertical line
873
0
                                    putCommandChar(aResult, aLastSVGCommand, 'V', bUseRelativeCoordinates, bOOXMLMotionPath);
874
875
0
                                    putNumberChar(aResult, aEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates, bOOXMLMotionPath);
876
0
                                    aCurrentSVGPosition = aEdgeEnd;
877
0
                                }
878
34
                                else if(bYEqual && !bOOXMLMotionPath)
879
17
                                {
880
                                    // export as horizontal line
881
17
                                    putCommandChar(aResult, aLastSVGCommand, 'H', bUseRelativeCoordinates, bOOXMLMotionPath);
882
883
17
                                    putNumberChar(aResult, aEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates, bOOXMLMotionPath);
884
17
                                    aCurrentSVGPosition = aEdgeEnd;
885
17
                                }
886
17
                                else
887
17
                                {
888
                                    // export as line
889
17
                                    putCommandChar(aResult, aLastSVGCommand, 'L', bUseRelativeCoordinates, bOOXMLMotionPath);
890
891
17
                                    putNumberChar(aResult, aEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates, bOOXMLMotionPath);
892
17
                                    putNumberChar(aResult, aEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates, bOOXMLMotionPath);
893
17
                                    aCurrentSVGPosition = aEdgeEnd;
894
17
                                }
895
34
                            }
896
51
                        }
897
898
                        // prepare edge start for next loop step
899
51
                        aEdgeStart = aEdgeEnd;
900
51
                    }
901
902
                    // close path if closed poly (Z and z are equivalent here, but looks nicer when case is matched)
903
17
                    if(aPolygon.isClosed())
904
17
                    {
905
17
                        putCommandChar(aResult, aLastSVGCommand, 'Z', bUseRelativeCoordinates, bOOXMLMotionPath);
906
17
                    }
907
0
                    else if (bOOXMLMotionPath)
908
0
                    {
909
0
                        putCommandChar(aResult, aLastSVGCommand, 'E', bUseRelativeCoordinates, bOOXMLMotionPath);
910
0
                    }
911
912
17
                    if(!bHandleRelativeNextPointCompatible)
913
0
                    {
914
                        // SVG defines that "the next subpath starts at the same initial point as the current subpath",
915
                        // so set aCurrentSVGPosition to the 1st point of the current, now ended and written path
916
0
                        aCurrentSVGPosition = aPolygon.getB2DPoint(0);
917
0
                    }
918
17
                }
919
17
            }
920
921
17
            return aResult.makeStringAndClear();
922
17
        }
923
}
924
925
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */