Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qsvgutils.cpp
Go to the documentation of this file.
1// Copyright (C) 2025 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
5
6#include "qsvgutils_p.h"
7#include <cmath>
8#include <QtCore/qpoint.h>
9#include <QtCore/qvarlengtharray.h>
10#include <QtGui/private/qmath_p.h>
11
12QT_BEGIN_NAMESPACE
13
14namespace {
15
16static void pathArcSegment(QPainterPath &path,
17 qreal xc, qreal yc,
18 qreal th0, qreal th1,
19 qreal rx, qreal ry, qreal xAxisRotation)
20{
21 qreal sinTh, cosTh;
22 qreal a00, a01, a10, a11;
23 qreal x1, y1, x2, y2, x3, y3;
24 qreal t;
25 qreal thHalf;
26
27 sinTh = qSin(xAxisRotation * (Q_PI / 180.0));
28 cosTh = qCos(xAxisRotation * (Q_PI / 180.0));
29
30 a00 = cosTh * rx;
31 a01 = -sinTh * ry;
32 a10 = sinTh * rx;
33 a11 = cosTh * ry;
34
35 thHalf = 0.5 * (th1 - th0);
36 t = (8.0 / 3.0) * qSin(thHalf * 0.5) * qSin(thHalf * 0.5) / qSin(thHalf);
37 x1 = xc + qCos(th0) - t * qSin(th0);
38 y1 = yc + qSin(th0) + t * qCos(th0);
39 x3 = xc + qCos(th1);
40 y3 = yc + qSin(th1);
41 x2 = x3 + t * qSin(th1);
42 y2 = y3 - t * qCos(th1);
43
44 path.cubicTo(a00 * x1 + a01 * y1, a10 * x1 + a11 * y1,
45 a00 * x2 + a01 * y2, a10 * x2 + a11 * y2,
46 a00 * x3 + a01 * y3, a10 * x3 + a11 * y3);
47}
48
49// the arc handling code underneath is from XSVG (BSD license)
50/*
51 * Copyright 2002 USC/Information Sciences Institute
52 *
53 * Permission to use, copy, modify, distribute, and sell this software
54 * and its documentation for any purpose is hereby granted without
55 * fee, provided that the above copyright notice appear in all copies
56 * and that both that copyright notice and this permission notice
57 * appear in supporting documentation, and that the name of
58 * Information Sciences Institute not be used in advertising or
59 * publicity pertaining to distribution of the software without
60 * specific, written prior permission. Information Sciences Institute
61 * makes no representations about the suitability of this software for
62 * any purpose. It is provided "as is" without express or implied
63 * warranty.
64 *
65 * INFORMATION SCIENCES INSTITUTE DISCLAIMS ALL WARRANTIES WITH REGARD
66 * TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF
67 * MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL INFORMATION SCIENCES
68 * INSTITUTE BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL
69 * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA
70 * OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
71 * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
72 * PERFORMANCE OF THIS SOFTWARE.
73 *
74 */
75static void pathArc(QPainterPath &path,
76 qreal rx,
77 qreal ry,
78 qreal x_axis_rotation,
79 int large_arc_flag,
80 int sweep_flag,
81 qreal x,
82 qreal y,
83 qreal curx, qreal cury)
84{
85 const qreal Pr1 = rx * rx;
86 const qreal Pr2 = ry * ry;
87
88 if (!Pr1 || !Pr2)
89 return;
90
91 qreal sin_th, cos_th;
92 qreal a00, a01, a10, a11;
93 qreal x0, y0, x1, y1, xc, yc;
94 qreal d, sfactor, sfactor_sq;
95 qreal th0, th1, th_arc;
96 int i, n_segs;
97 qreal dx, dy, dx1, dy1, Px, Py, check;
98
99 rx = qAbs(rx);
100 ry = qAbs(ry);
101
102 sin_th = qSin(x_axis_rotation * (Q_PI / 180.0));
103 cos_th = qCos(x_axis_rotation * (Q_PI / 180.0));
104
105 dx = (curx - x) / 2.0;
106 dy = (cury - y) / 2.0;
107 dx1 = cos_th * dx + sin_th * dy;
108 dy1 = -sin_th * dx + cos_th * dy;
109 Px = dx1 * dx1;
110 Py = dy1 * dy1;
111 /* Spec : check if radii are large enough */
112 check = Px / Pr1 + Py / Pr2;
113 if (check > 1) {
114 rx = rx * qSqrt(check);
115 ry = ry * qSqrt(check);
116 }
117
118 a00 = cos_th / rx;
119 a01 = sin_th / rx;
120 a10 = -sin_th / ry;
121 a11 = cos_th / ry;
122 x0 = a00 * curx + a01 * cury;
123 y0 = a10 * curx + a11 * cury;
124 x1 = a00 * x + a01 * y;
125 y1 = a10 * x + a11 * y;
126 /* (x0, y0) is current point in transformed coordinate space.
127 (x1, y1) is new point in transformed coordinate space.
128
129 The arc fits a unit-radius circle in this space.
130 */
131 d = (x1 - x0) * (x1 - x0) + (y1 - y0) * (y1 - y0);
132 if (!d)
133 return;
134 sfactor_sq = 1.0 / d - 0.25;
135 if (sfactor_sq < 0) sfactor_sq = 0;
136 sfactor = qSqrt(sfactor_sq);
137 if (sweep_flag == large_arc_flag) sfactor = -sfactor;
138 xc = 0.5 * (x0 + x1) - sfactor * (y1 - y0);
139 yc = 0.5 * (y0 + y1) + sfactor * (x1 - x0);
140 /* (xc, yc) is center of the circle. */
141
142 th0 = qAtan2(y0 - yc, x0 - xc);
143 th1 = qAtan2(y1 - yc, x1 - xc);
144
145 th_arc = th1 - th0;
146 if (th_arc < 0 && sweep_flag)
147 th_arc += 2 * Q_PI;
148 else if (th_arc > 0 && !sweep_flag)
149 th_arc -= 2 * Q_PI;
150
151 n_segs = qCeil(qAbs(th_arc / (Q_PI * 0.5 + 0.001)));
152
153 for (i = 0; i < n_segs; i++) {
154 pathArcSegment(path, xc, yc,
155 th0 + i * th_arc / n_segs,
156 th0 + (i + 1) * th_arc / n_segs,
157 rx, ry, x_axis_rotation);
158 }
159}
160
161}
162namespace QSvgUtils {
163
164// '0' is 0x30 and '9' is 0x39
165bool isDigit(ushort ch)
166{
167 static quint16 magic = 0x3ff;
168 return ((ch >> 4) == 3) && (magic >> (ch & 15));
169}
170
171qreal toDouble(QStringView *str)
172{
173 const int maxLen = 255;//technically doubles can go til 308+ but whatever
174 char temp[maxLen+1];
175 int pos = 0;
176
177 if (str->startsWith(QLatin1Char('-'))) {
178 temp[pos++] = '-';
179 str->slice(1);
180 } else if (str->startsWith(QLatin1Char('+'))) {
181 str->slice(1);
182 }
183 while (!str->isEmpty() && isDigit(str->first().unicode()) && pos < maxLen) {
184 temp[pos++] = str->first().toLatin1();
185 str->slice(1);
186 }
187 if (str->startsWith(QLatin1Char('.')) && pos < maxLen) {
188 temp[pos++] = '.';
189 str->slice(1);
190 }
191 while (!str->isEmpty() && isDigit(str->first().unicode()) && pos < maxLen) {
192 temp[pos++] = str->first().toLatin1();
193 str->slice(1);
194 }
195 bool exponent = false;
196 if ((str->startsWith(QLatin1Char('e')) || str->startsWith(QLatin1Char('E'))) && pos < maxLen) {
197 exponent = true;
198 temp[pos++] = 'e';
199 str->slice(1);
200 if ((str->startsWith(QLatin1Char('-')) || str->startsWith(QLatin1Char('+')))
201 && pos < maxLen) {
202 temp[pos++] = str->first().toLatin1();
203 str->slice(1);
204 }
205 while (!str->isEmpty() && isDigit(str->first().unicode()) && pos < maxLen) {
206 temp[pos++] = str->first().toLatin1();
207 str->slice(1);
208 }
209 }
210
211 temp[pos] = '\0';
212
213 qreal val;
214 if (!exponent && pos < 10) {
215 int ival = 0;
216 const char *t = temp;
217 bool neg = false;
218 if (*t == '-') {
219 neg = true;
220 ++t;
221 }
222 while (*t && *t != '.') {
223 ival *= 10;
224 ival += (*t) - '0';
225 ++t;
226 }
227 if (*t == '.') {
228 ++t;
229 int div = 1;
230 while (*t) {
231 ival *= 10;
232 ival += (*t) - '0';
233 div *= 10;
234 ++t;
235 }
236 val = ((qreal)ival)/((qreal)div);
237 } else {
238 val = ival;
239 }
240 if (neg)
241 val = -val;
242 } else {
243 val = QByteArray::fromRawData(temp, pos).toDouble();
244 // Do not tolerate values too wild to be represented normally by floats
245 if (qFpClassify(float(val)) != FP_NORMAL)
246 val = 0;
247 }
248 return val;
249
250}
251
252qreal toDouble(QStringView str, bool *ok)
253{
254 const qreal res{ toDouble(&str) };
255 if (ok)
256 *ok = str.isEmpty();
257 return res;
258}
259
260qreal parseLength(QStringView str, LengthType *type, bool *ok)
261{
262 QStringView numStr = str.trimmed();
263
264 if (numStr.isEmpty()) {
265 if (ok)
266 *ok = false;
267 *type = LengthType::LT_OTHER;
268 return false;
269 }
270 if (numStr.endsWith(QLatin1Char('%'))) {
271 numStr.chop(1);
273 } else if (numStr.endsWith(QLatin1String("px"))) {
274 numStr.chop(2);
275 *type = LengthType::LT_PX;
276 } else if (numStr.endsWith(QLatin1String("pc"))) {
277 numStr.chop(2);
278 *type = LengthType::LT_PC;
279 } else if (numStr.endsWith(QLatin1String("pt"))) {
280 numStr.chop(2);
281 *type = LengthType::LT_PT;
282 } else if (numStr.endsWith(QLatin1String("mm"))) {
283 numStr.chop(2);
284 *type = LengthType::LT_MM;
285 } else if (numStr.endsWith(QLatin1String("cm"))) {
286 numStr.chop(2);
287 *type = LengthType::LT_CM;
288 } else if (numStr.endsWith(QLatin1String("in"))) {
289 numStr.chop(2);
290 *type = LengthType::LT_IN;
291 } else {
292 // default coordinate system
293 *type = LengthType::LT_PX;
294 }
295 qreal len = toDouble(numStr, ok);
296 return len;
297}
298
299// this should really be called convertToDefaultCoordinateSystem
300// and convert when type != QSvgHandler::defaultCoordinateSystem
301qreal convertToPixels(qreal len, bool , LengthType type)
302{
303
304 switch (type) {
306 break;
308 break;
310 break;
312 return len * 1.25;
313 break;
315 return len * 3.543307;
316 break;
318 return len * 35.43307;
319 break;
321 return len * 90;
322 break;
324 break;
325 default:
326 break;
327 }
328 return len;
329}
330
331// Parses the angle from a string and convert it to degrees.
332// In CSS, units are not optional so if a non-zero value is defined,
333// without a unit it should be treated as invalid.
334std::optional<qreal> parseAngle(QStringView str)
335{
336 QStringView numStr = str.trimmed();
337
338 if (numStr.isEmpty())
339 return std::nullopt;
340
341 qreal unitFactor = 1.0;
342 if (numStr.endsWith(QLatin1String("deg"))) {
343 numStr.chop(3);
344 unitFactor = 1.0;
345 } else if (numStr.endsWith(QLatin1String("grad"))) {
346 numStr.chop(4);
347 // deg = grad * 0.9;
348 unitFactor = 0.9;
349 } else if (numStr.endsWith(QLatin1String("rad"))) {
350 numStr.chop(3);
351 unitFactor = 180.0 / Q_PI;
352 } else if (numStr.endsWith(QLatin1String("turn"))) {
353 numStr.chop(4);
354 // one circle = one turn
355 unitFactor = 360.0;
356 } else {
357 return std::nullopt;
358 }
359
360 bool ok;
361 qreal angle = numStr.toDouble(&ok);
362 if (!ok)
363 return std::nullopt;
364
365 return angle * unitFactor;
366}
367
368void parseNumbersArray(QStringView *str, QVarLengthArray<qreal, 8> &points, const char *pattern)
369{
370 const size_t patternLen = qstrlen(pattern);
371 while (!str->isEmpty() && str->first().isSpace())
372 str->slice(1);
373 while ((!str->isEmpty() && QSvgUtils::isDigit(str->first().unicode()))
374 || str->startsWith(QLatin1Char('-')) || str->startsWith(QLatin1Char('+'))
375 || str->startsWith(QLatin1Char('.'))) {
376
377 if (patternLen && pattern[points.size() % patternLen] == 'f') {
378 // flag expected, may only be 0 or 1
379 if (!str->startsWith(QLatin1Char('0')) && !str->startsWith(QLatin1Char('1')))
380 return;
381 points.append(str->startsWith(QLatin1Char('0')) ? 0.0 : 1.0);
382 str->slice(1);
383 } else {
384 points.append(QSvgUtils::toDouble(str));
385 }
386
387 while (!str->isEmpty() && str->first().isSpace())
388 str->slice(1);
389 if (str->startsWith(QLatin1Char(',')))
390 str->slice(1);
391
392 //eat the rest of space
393 while (!str->isEmpty() && str->first().isSpace())
394 str->slice(1);
395 }
396}
397
398std::optional<QPainterPath> parsePathDataFast(QStringView dataStr, bool limitLength)
399{
400 if (dataStr.isEmpty())
401 return std::nullopt;
402
403 const int maxElementCount = 0x7fff; // Assume file corruption if more path elements than this
404 qreal x0 = 0, y0 = 0; // starting point
405 qreal x = 0, y = 0; // current point
406 char lastMode = 0;
407 QPointF ctrlPt;
408
409 QPainterPath path;
410 while (!dataStr.isEmpty()) {
411 while (dataStr.first().isSpace() && dataStr.length() > 1)
412 dataStr.slice(1);
413 QChar pathElem = dataStr.first();
414 dataStr.slice(1);
415 const char *pattern = nullptr;
416 if (pathElem == QLatin1Char('a') || pathElem == QLatin1Char('A'))
417 pattern = "rrrffrr";
418 QVarLengthArray<qreal, 8> arg;
419 parseNumbersArray(&dataStr, arg, pattern);
420 if (pathElem == QLatin1Char('z') || pathElem == QLatin1Char('Z'))
421 arg.append(0);//dummy
422 const qreal *num = arg.constData();
423 int count = arg.size();
424 while (count > 0) {
425 qreal offsetX = x; // correction offsets
426 qreal offsetY = y; // for relative commands
427 switch (pathElem.unicode()) {
428 case 'm': {
429 if (count < 2)
430 return std::nullopt;
431 x = x0 = num[0] + offsetX;
432 y = y0 = num[1] + offsetY;
433 num += 2;
434 count -= 2;
435 path.moveTo(x0, y0);
436
437 // As per 1.2 spec 8.3.2 The "moveto" commands
438 // If a 'moveto' is followed by multiple pairs of coordinates without explicit commands,
439 // the subsequent pairs shall be treated as implicit 'lineto' commands.
440 pathElem = QLatin1Char('l');
441 }
442 break;
443 case 'M': {
444 if (count < 2)
445 return std::nullopt;
446
447 x = x0 = num[0];
448 y = y0 = num[1];
449 num += 2;
450 count -= 2;
451 path.moveTo(x0, y0);
452
453 // As per 1.2 spec 8.3.2 The "moveto" commands
454 // If a 'moveto' is followed by multiple pairs of coordinates without explicit commands,
455 // the subsequent pairs shall be treated as implicit 'lineto' commands.
456 pathElem = QLatin1Char('L');
457 }
458 break;
459 case 'z':
460 case 'Z': {
461 x = x0;
462 y = y0;
463 count--; // skip dummy
464 num++;
465 path.closeSubpath();
466 }
467 break;
468 case 'l': {
469 if (count < 2)
470 return std::nullopt;
471
472 x = num[0] + offsetX;
473 y = num[1] + offsetY;
474 num += 2;
475 count -= 2;
476 path.lineTo(x, y);
477
478 }
479 break;
480 case 'L': {
481 if (count < 2)
482 return std::nullopt;
483
484 x = num[0];
485 y = num[1];
486 num += 2;
487 count -= 2;
488 path.lineTo(x, y);
489 }
490 break;
491 case 'h': {
492 x = num[0] + offsetX;
493 num++;
494 count--;
495 path.lineTo(x, y);
496 }
497 break;
498 case 'H': {
499 x = num[0];
500 num++;
501 count--;
502 path.lineTo(x, y);
503 }
504 break;
505 case 'v': {
506 y = num[0] + offsetY;
507 num++;
508 count--;
509 path.lineTo(x, y);
510 }
511 break;
512 case 'V': {
513 y = num[0];
514 num++;
515 count--;
516 path.lineTo(x, y);
517 }
518 break;
519 case 'c': {
520 if (count < 6)
521 return std::nullopt;
522
523 QPointF c1(num[0] + offsetX, num[1] + offsetY);
524 QPointF c2(num[2] + offsetX, num[3] + offsetY);
525 QPointF e(num[4] + offsetX, num[5] + offsetY);
526 num += 6;
527 count -= 6;
528 path.cubicTo(c1, c2, e);
529 ctrlPt = c2;
530 x = e.x();
531 y = e.y();
532 break;
533 }
534 case 'C': {
535 if (count < 6)
536 return std::nullopt;
537
538 QPointF c1(num[0], num[1]);
539 QPointF c2(num[2], num[3]);
540 QPointF e(num[4], num[5]);
541 num += 6;
542 count -= 6;
543 path.cubicTo(c1, c2, e);
544 ctrlPt = c2;
545 x = e.x();
546 y = e.y();
547 break;
548 }
549 case 's': {
550 if (count < 4)
551 return std::nullopt;
552
553 QPointF c1;
554 if (lastMode == 'c' || lastMode == 'C' ||
555 lastMode == 's' || lastMode == 'S')
556 c1 = QPointF(2*x-ctrlPt.x(), 2*y-ctrlPt.y());
557 else
558 c1 = QPointF(x, y);
559 QPointF c2(num[0] + offsetX, num[1] + offsetY);
560 QPointF e(num[2] + offsetX, num[3] + offsetY);
561 num += 4;
562 count -= 4;
563 path.cubicTo(c1, c2, e);
564 ctrlPt = c2;
565 x = e.x();
566 y = e.y();
567 break;
568 }
569 case 'S': {
570 if (count < 4)
571 return std::nullopt;
572
573 QPointF c1;
574 if (lastMode == 'c' || lastMode == 'C' ||
575 lastMode == 's' || lastMode == 'S')
576 c1 = QPointF(2*x-ctrlPt.x(), 2*y-ctrlPt.y());
577 else
578 c1 = QPointF(x, y);
579 QPointF c2(num[0], num[1]);
580 QPointF e(num[2], num[3]);
581 num += 4;
582 count -= 4;
583 path.cubicTo(c1, c2, e);
584 ctrlPt = c2;
585 x = e.x();
586 y = e.y();
587 break;
588 }
589 case 'q': {
590 if (count < 4)
591 return std::nullopt;
592
593 QPointF c(num[0] + offsetX, num[1] + offsetY);
594 QPointF e(num[2] + offsetX, num[3] + offsetY);
595 num += 4;
596 count -= 4;
597 path.quadTo(c, e);
598 ctrlPt = c;
599 x = e.x();
600 y = e.y();
601 break;
602 }
603 case 'Q': {
604 if (count < 4)
605 return std::nullopt;
606
607 QPointF c(num[0], num[1]);
608 QPointF e(num[2], num[3]);
609 num += 4;
610 count -= 4;
611 path.quadTo(c, e);
612 ctrlPt = c;
613 x = e.x();
614 y = e.y();
615 break;
616 }
617 case 't': {
618 if (count < 2)
619 return std::nullopt;
620
621 QPointF e(num[0] + offsetX, num[1] + offsetY);
622 num += 2;
623 count -= 2;
624 QPointF c;
625 if (lastMode == 'q' || lastMode == 'Q' ||
626 lastMode == 't' || lastMode == 'T')
627 c = QPointF(2*x-ctrlPt.x(), 2*y-ctrlPt.y());
628 else
629 c = QPointF(x, y);
630 path.quadTo(c, e);
631 ctrlPt = c;
632 x = e.x();
633 y = e.y();
634 break;
635 }
636 case 'T': {
637 if (count < 2)
638 return std::nullopt;
639
640 QPointF e(num[0], num[1]);
641 num += 2;
642 count -= 2;
643 QPointF c;
644 if (lastMode == 'q' || lastMode == 'Q' ||
645 lastMode == 't' || lastMode == 'T')
646 c = QPointF(2*x-ctrlPt.x(), 2*y-ctrlPt.y());
647 else
648 c = QPointF(x, y);
649 path.quadTo(c, e);
650 ctrlPt = c;
651 x = e.x();
652 y = e.y();
653 break;
654 }
655 case 'a': {
656 if (count < 7)
657 return std::nullopt;
658
659 qreal rx = (*num++);
660 qreal ry = (*num++);
661 qreal xAxisRotation = (*num++);
662 qreal largeArcFlag = (*num++);
663 qreal sweepFlag = (*num++);
664 qreal ex = (*num++) + offsetX;
665 qreal ey = (*num++) + offsetY;
666 count -= 7;
667 qreal curx = x;
668 qreal cury = y;
669 pathArc(path, rx, ry, xAxisRotation, int(largeArcFlag),
670 int(sweepFlag), ex, ey, curx, cury);
671
672 x = ex;
673 y = ey;
674 }
675 break;
676 case 'A': {
677 if (count < 7)
678 return std::nullopt;
679
680 qreal rx = (*num++);
681 qreal ry = (*num++);
682 qreal xAxisRotation = (*num++);
683 qreal largeArcFlag = (*num++);
684 qreal sweepFlag = (*num++);
685 qreal ex = (*num++);
686 qreal ey = (*num++);
687 count -= 7;
688 qreal curx = x;
689 qreal cury = y;
690 pathArc(path, rx, ry, xAxisRotation, int(largeArcFlag),
691 int(sweepFlag), ex, ey, curx, cury);
692
693 x = ex;
694 y = ey;
695 }
696 break;
697 default:
698 return std::nullopt;
699 }
700 lastMode = pathElem.toLatin1();
701 if (limitLength && path.elementCount() > maxElementCount)
702 return std::nullopt;
703 }
704 }
705
706 return path;
707}
708
709}
710
711QT_END_NAMESPACE
bool isDigit(ushort ch)
std::optional< qreal > parseAngle(QStringView str)
void parseNumbersArray(QStringView *str, QVarLengthArray< qreal, 8 > &points, const char *pattern)
qreal convertToPixels(qreal len, bool, LengthType type)
qreal toDouble(QStringView str, bool *ok)
qreal parseLength(QStringView str, LengthType *type, bool *ok)
std::optional< QPainterPath > parsePathDataFast(QStringView dataStr, bool limitLength)
qreal toDouble(QStringView *str)
static void pathArcSegment(QPainterPath &path, qreal xc, qreal yc, qreal th0, qreal th1, qreal rx, qreal ry, qreal xAxisRotation)
Definition qsvgutils.cpp:16
static void pathArc(QPainterPath &path, qreal rx, qreal ry, qreal x_axis_rotation, int large_arc_flag, int sweep_flag, qreal x, qreal y, qreal curx, qreal cury)
Definition qsvgutils.cpp:75