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(const QChar *&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 == QLatin1Char('-')) {
178 temp[pos++] = '-';
179 ++str;
180 } else if (*str == QLatin1Char('+')) {
181 ++str;
182 }
183 while (isDigit(str->unicode()) && pos < maxLen) {
184 temp[pos++] = str->toLatin1();
185 ++str;
186 }
187 if (*str == QLatin1Char('.') && pos < maxLen) {
188 temp[pos++] = '.';
189 ++str;
190 }
191 while (isDigit(str->unicode()) && pos < maxLen) {
192 temp[pos++] = str->toLatin1();
193 ++str;
194 }
195 bool exponent = false;
196 if ((*str == QLatin1Char('e') || *str == QLatin1Char('E')) && pos < maxLen) {
197 exponent = true;
198 temp[pos++] = 'e';
199 ++str;
200 if ((*str == QLatin1Char('-') || *str == QLatin1Char('+')) && pos < maxLen) {
201 temp[pos++] = str->toLatin1();
202 ++str;
203 }
204 while (isDigit(str->unicode()) && pos < maxLen) {
205 temp[pos++] = str->toLatin1();
206 ++str;
207 }
208 }
209
210 temp[pos] = '\0';
211
212 qreal val;
213 if (!exponent && pos < 10) {
214 int ival = 0;
215 const char *t = temp;
216 bool neg = false;
217 if (*t == '-') {
218 neg = true;
219 ++t;
220 }
221 while (*t && *t != '.') {
222 ival *= 10;
223 ival += (*t) - '0';
224 ++t;
225 }
226 if (*t == '.') {
227 ++t;
228 int div = 1;
229 while (*t) {
230 ival *= 10;
231 ival += (*t) - '0';
232 div *= 10;
233 ++t;
234 }
235 val = ((qreal)ival)/((qreal)div);
236 } else {
237 val = ival;
238 }
239 if (neg)
240 val = -val;
241 } else {
242 val = QByteArray::fromRawData(temp, pos).toDouble();
243 // Do not tolerate values too wild to be represented normally by floats
244 if (qFpClassify(float(val)) != FP_NORMAL)
245 val = 0;
246 }
247 return val;
248
249}
250
251qreal toDouble(QStringView str, bool *ok)
252{
253 const QChar *c = str.constData();
254 qreal res = (c == nullptr ? qreal{} : toDouble(c));
255 if (ok)
256 *ok = (c == (str.constData() + str.size()));
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(const QChar *&str, QVarLengthArray<qreal, 8> &points,
369 const char *pattern)
370{
371 const size_t patternLen = qstrlen(pattern);
372 while (str->isSpace())
373 ++str;
374 while (QSvgUtils::isDigit(str->unicode()) ||
375 *str == QLatin1Char('-') || *str == QLatin1Char('+') ||
376 *str == QLatin1Char('.')) {
377
378 if (patternLen && pattern[points.size() % patternLen] == 'f') {
379 // flag expected, may only be 0 or 1
380 if (*str != QLatin1Char('0') && *str != QLatin1Char('1'))
381 return;
382 points.append(*str == QLatin1Char('0') ? 0.0 : 1.0);
383 ++str;
384 } else {
385 points.append(QSvgUtils::toDouble(str));
386 }
387
388 while (str->isSpace())
389 ++str;
390 if (*str == QLatin1Char(','))
391 ++str;
392
393 //eat the rest of space
394 while (str->isSpace())
395 ++str;
396 }
397}
398
399std::optional<QPainterPath> parsePathDataFast(QStringView dataStr, bool limitLength)
400{
401 if (dataStr.isEmpty())
402 return std::nullopt;
403
404 const int maxElementCount = 0x7fff; // Assume file corruption if more path elements than this
405 qreal x0 = 0, y0 = 0; // starting point
406 qreal x = 0, y = 0; // current point
407 char lastMode = 0;
408 QPointF ctrlPt;
409 const QChar *str = dataStr.constData();
410 const QChar *end = str + dataStr.size();
411
412 QPainterPath path;
413 while (str != end) {
414 while (str->isSpace() && (str + 1) != end)
415 ++str;
416 QChar pathElem = *str;
417 ++str;
418 QChar endc = *end;
419 *const_cast<QChar *>(end) = u'\0'; // parseNumbersArray requires 0-termination that QStringView cannot guarantee
420 const char *pattern = nullptr;
421 if (pathElem == QLatin1Char('a') || pathElem == QLatin1Char('A'))
422 pattern = "rrrffrr";
423 QVarLengthArray<qreal, 8> arg;
424 parseNumbersArray(str, arg, pattern);
425 *const_cast<QChar *>(end) = endc;
426 if (pathElem == QLatin1Char('z') || pathElem == QLatin1Char('Z'))
427 arg.append(0);//dummy
428 const qreal *num = arg.constData();
429 int count = arg.size();
430 while (count > 0) {
431 qreal offsetX = x; // correction offsets
432 qreal offsetY = y; // for relative commands
433 switch (pathElem.unicode()) {
434 case 'm': {
435 if (count < 2)
436 return std::nullopt;
437 x = x0 = num[0] + offsetX;
438 y = y0 = num[1] + offsetY;
439 num += 2;
440 count -= 2;
441 path.moveTo(x0, y0);
442
443 // As per 1.2 spec 8.3.2 The "moveto" commands
444 // If a 'moveto' is followed by multiple pairs of coordinates without explicit commands,
445 // the subsequent pairs shall be treated as implicit 'lineto' commands.
446 pathElem = QLatin1Char('l');
447 }
448 break;
449 case 'M': {
450 if (count < 2)
451 return std::nullopt;
452
453 x = x0 = num[0];
454 y = y0 = num[1];
455 num += 2;
456 count -= 2;
457 path.moveTo(x0, y0);
458
459 // As per 1.2 spec 8.3.2 The "moveto" commands
460 // If a 'moveto' is followed by multiple pairs of coordinates without explicit commands,
461 // the subsequent pairs shall be treated as implicit 'lineto' commands.
462 pathElem = QLatin1Char('L');
463 }
464 break;
465 case 'z':
466 case 'Z': {
467 x = x0;
468 y = y0;
469 count--; // skip dummy
470 num++;
471 path.closeSubpath();
472 }
473 break;
474 case 'l': {
475 if (count < 2)
476 return std::nullopt;
477
478 x = num[0] + offsetX;
479 y = num[1] + offsetY;
480 num += 2;
481 count -= 2;
482 path.lineTo(x, y);
483
484 }
485 break;
486 case 'L': {
487 if (count < 2)
488 return std::nullopt;
489
490 x = num[0];
491 y = num[1];
492 num += 2;
493 count -= 2;
494 path.lineTo(x, y);
495 }
496 break;
497 case 'h': {
498 x = num[0] + offsetX;
499 num++;
500 count--;
501 path.lineTo(x, y);
502 }
503 break;
504 case 'H': {
505 x = num[0];
506 num++;
507 count--;
508 path.lineTo(x, y);
509 }
510 break;
511 case 'v': {
512 y = num[0] + offsetY;
513 num++;
514 count--;
515 path.lineTo(x, y);
516 }
517 break;
518 case 'V': {
519 y = num[0];
520 num++;
521 count--;
522 path.lineTo(x, y);
523 }
524 break;
525 case 'c': {
526 if (count < 6)
527 return std::nullopt;
528
529 QPointF c1(num[0] + offsetX, num[1] + offsetY);
530 QPointF c2(num[2] + offsetX, num[3] + offsetY);
531 QPointF e(num[4] + offsetX, num[5] + offsetY);
532 num += 6;
533 count -= 6;
534 path.cubicTo(c1, c2, e);
535 ctrlPt = c2;
536 x = e.x();
537 y = e.y();
538 break;
539 }
540 case 'C': {
541 if (count < 6)
542 return std::nullopt;
543
544 QPointF c1(num[0], num[1]);
545 QPointF c2(num[2], num[3]);
546 QPointF e(num[4], num[5]);
547 num += 6;
548 count -= 6;
549 path.cubicTo(c1, c2, e);
550 ctrlPt = c2;
551 x = e.x();
552 y = e.y();
553 break;
554 }
555 case 's': {
556 if (count < 4)
557 return std::nullopt;
558
559 QPointF c1;
560 if (lastMode == 'c' || lastMode == 'C' ||
561 lastMode == 's' || lastMode == 'S')
562 c1 = QPointF(2*x-ctrlPt.x(), 2*y-ctrlPt.y());
563 else
564 c1 = QPointF(x, y);
565 QPointF c2(num[0] + offsetX, num[1] + offsetY);
566 QPointF e(num[2] + offsetX, num[3] + offsetY);
567 num += 4;
568 count -= 4;
569 path.cubicTo(c1, c2, e);
570 ctrlPt = c2;
571 x = e.x();
572 y = e.y();
573 break;
574 }
575 case 'S': {
576 if (count < 4)
577 return std::nullopt;
578
579 QPointF c1;
580 if (lastMode == 'c' || lastMode == 'C' ||
581 lastMode == 's' || lastMode == 'S')
582 c1 = QPointF(2*x-ctrlPt.x(), 2*y-ctrlPt.y());
583 else
584 c1 = QPointF(x, y);
585 QPointF c2(num[0], num[1]);
586 QPointF e(num[2], num[3]);
587 num += 4;
588 count -= 4;
589 path.cubicTo(c1, c2, e);
590 ctrlPt = c2;
591 x = e.x();
592 y = e.y();
593 break;
594 }
595 case 'q': {
596 if (count < 4)
597 return std::nullopt;
598
599 QPointF c(num[0] + offsetX, num[1] + offsetY);
600 QPointF e(num[2] + offsetX, num[3] + offsetY);
601 num += 4;
602 count -= 4;
603 path.quadTo(c, e);
604 ctrlPt = c;
605 x = e.x();
606 y = e.y();
607 break;
608 }
609 case 'Q': {
610 if (count < 4)
611 return std::nullopt;
612
613 QPointF c(num[0], num[1]);
614 QPointF e(num[2], num[3]);
615 num += 4;
616 count -= 4;
617 path.quadTo(c, e);
618 ctrlPt = c;
619 x = e.x();
620 y = e.y();
621 break;
622 }
623 case 't': {
624 if (count < 2)
625 return std::nullopt;
626
627 QPointF e(num[0] + offsetX, num[1] + offsetY);
628 num += 2;
629 count -= 2;
630 QPointF c;
631 if (lastMode == 'q' || lastMode == 'Q' ||
632 lastMode == 't' || lastMode == 'T')
633 c = QPointF(2*x-ctrlPt.x(), 2*y-ctrlPt.y());
634 else
635 c = QPointF(x, y);
636 path.quadTo(c, e);
637 ctrlPt = c;
638 x = e.x();
639 y = e.y();
640 break;
641 }
642 case 'T': {
643 if (count < 2)
644 return std::nullopt;
645
646 QPointF e(num[0], num[1]);
647 num += 2;
648 count -= 2;
649 QPointF c;
650 if (lastMode == 'q' || lastMode == 'Q' ||
651 lastMode == 't' || lastMode == 'T')
652 c = QPointF(2*x-ctrlPt.x(), 2*y-ctrlPt.y());
653 else
654 c = QPointF(x, y);
655 path.quadTo(c, e);
656 ctrlPt = c;
657 x = e.x();
658 y = e.y();
659 break;
660 }
661 case 'a': {
662 if (count < 7)
663 return std::nullopt;
664
665 qreal rx = (*num++);
666 qreal ry = (*num++);
667 qreal xAxisRotation = (*num++);
668 qreal largeArcFlag = (*num++);
669 qreal sweepFlag = (*num++);
670 qreal ex = (*num++) + offsetX;
671 qreal ey = (*num++) + offsetY;
672 count -= 7;
673 qreal curx = x;
674 qreal cury = y;
675 pathArc(path, rx, ry, xAxisRotation, int(largeArcFlag),
676 int(sweepFlag), ex, ey, curx, cury);
677
678 x = ex;
679 y = ey;
680 }
681 break;
682 case 'A': {
683 if (count < 7)
684 return std::nullopt;
685
686 qreal rx = (*num++);
687 qreal ry = (*num++);
688 qreal xAxisRotation = (*num++);
689 qreal largeArcFlag = (*num++);
690 qreal sweepFlag = (*num++);
691 qreal ex = (*num++);
692 qreal ey = (*num++);
693 count -= 7;
694 qreal curx = x;
695 qreal cury = y;
696 pathArc(path, rx, ry, xAxisRotation, int(largeArcFlag),
697 int(sweepFlag), ex, ey, curx, cury);
698
699 x = ex;
700 y = ey;
701 }
702 break;
703 default:
704 return std::nullopt;
705 }
706 lastMode = pathElem.toLatin1();
707 if (limitLength && path.elementCount() > maxElementCount)
708 return std::nullopt;
709 }
710 }
711
712 return path;
713}
714
715}
716
717QT_END_NAMESPACE
bool isDigit(ushort ch)
qreal toDouble(const QChar *&str)
void parseNumbersArray(const QChar *&str, QVarLengthArray< qreal, 8 > &points, const char *pattern)
std::optional< qreal > parseAngle(QStringView str)
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)
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