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