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
qguisvg.cpp
Go to the documentation of this file.
1// Copyright (C) 2026 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:critical reason:data-parser
4
5
6#include "qguisvg_p.h"
7#include <cmath>
8#include <QtCore/qpoint.h>
9#include <QtCore/qregularexpression.h>
10#include <QtCore/qvarlengtharray.h>
11#include <QtGui/private/qmath_p.h>
12#include <array>
13
14QT_BEGIN_NAMESPACE
15
16using namespace Qt::Literals::StringLiterals;
17
18namespace {
19
20using namespace Qt::StringLiterals;
21
22static void pathArcSegment(QPainterPath &path,
23 qreal xc, qreal yc,
24 qreal th0, qreal th1,
25 qreal rx, qreal ry, qreal xAxisRotation)
26{
27 qreal sinTh, cosTh;
28 qreal a00, a01, a10, a11;
29 qreal x1, y1, x2, y2, x3, y3;
30 qreal t;
31 qreal thHalf;
32
33 sinTh = qSin(xAxisRotation * (Q_PI / 180.0));
34 cosTh = qCos(xAxisRotation * (Q_PI / 180.0));
35
36 a00 = cosTh * rx;
37 a01 = -sinTh * ry;
38 a10 = sinTh * rx;
39 a11 = cosTh * ry;
40
41 thHalf = 0.5 * (th1 - th0);
42 t = (8.0 / 3.0) * qSin(thHalf * 0.5) * qSin(thHalf * 0.5) / qSin(thHalf);
43 x1 = xc + qCos(th0) - t * qSin(th0);
44 y1 = yc + qSin(th0) + t * qCos(th0);
45 x3 = xc + qCos(th1);
46 y3 = yc + qSin(th1);
47 x2 = x3 + t * qSin(th1);
48 y2 = y3 - t * qCos(th1);
49
50 path.cubicTo(a00 * x1 + a01 * y1, a10 * x1 + a11 * y1,
51 a00 * x2 + a01 * y2, a10 * x2 + a11 * y2,
52 a00 * x3 + a01 * y3, a10 * x3 + a11 * y3);
53}
54
55}
56namespace QGuiSvg {
57
58// '0' is 0x30 and '9' is 0x39
59bool isDigit(ushort ch)
60{
61 static quint16 magic = 0x3ff;
62 return ((ch >> 4) == 3) && (magic >> (ch & 15));
63}
64
66{
67 const int maxLen = 255;//technically doubles can go til 308+ but whatever
68 char temp[maxLen+1];
69 int pos = 0;
70
71 if (str->startsWith(QLatin1Char('-'))) {
72 temp[pos++] = '-';
73 str->slice(1);
74 } else if (str->startsWith(QLatin1Char('+'))) {
75 str->slice(1);
76 }
77 while (!str->isEmpty() && isDigit(str->first().unicode()) && pos < maxLen) {
78 temp[pos++] = str->first().toLatin1();
79 str->slice(1);
80 }
81 if (str->startsWith(QLatin1Char('.')) && pos < maxLen) {
82 temp[pos++] = '.';
83 str->slice(1);
84 }
85 while (!str->isEmpty() && isDigit(str->first().unicode()) && pos < maxLen) {
86 temp[pos++] = str->first().toLatin1();
87 str->slice(1);
88 }
89 bool exponent = false;
90 if ((str->startsWith(QLatin1Char('e')) || str->startsWith(QLatin1Char('E'))) && pos < maxLen) {
91 exponent = true;
92 temp[pos++] = 'e';
93 str->slice(1);
94 if ((str->startsWith(QLatin1Char('-')) || str->startsWith(QLatin1Char('+')))
95 && pos < maxLen) {
96 temp[pos++] = str->first().toLatin1();
97 str->slice(1);
98 }
99 while (!str->isEmpty() && isDigit(str->first().unicode()) && pos < maxLen) {
100 temp[pos++] = str->first().toLatin1();
101 str->slice(1);
102 }
103 }
104
105 temp[pos] = '\0';
106
107 qreal val;
108 if (!exponent && pos < 10) {
109 int ival = 0;
110 const char *t = temp;
111 bool neg = false;
112 if (*t == '-') {
113 neg = true;
114 ++t;
115 }
116 while (*t && *t != '.') {
117 ival *= 10;
118 ival += (*t) - '0';
119 ++t;
120 }
121 if (*t == '.') {
122 ++t;
123 int div = 1;
124 while (*t) {
125 ival *= 10;
126 ival += (*t) - '0';
127 div *= 10;
128 ++t;
129 }
130 val = ((qreal)ival)/((qreal)div);
131 } else {
132 val = ival;
133 }
134 if (neg)
135 val = -val;
136 } else {
138 // Do not tolerate values too wild to be represented normally by floats
139 if (qFpClassify(float(val)) != FP_NORMAL)
140 val = 0;
141 }
142 return val;
143
144}
145
146qreal toDouble(QStringView str, bool *ok)
147{
148 const qreal res{ toDouble(&str) };
149 if (ok)
150 *ok = str.isEmpty();
151 return res;
152}
153
154qreal parseLength(QStringView str, LengthType *type, bool *ok, bool tiny12FeaturesOnly)
155{
156 QStringView numStr = QGuiSvg::trimmed(str, tiny12FeaturesOnly);
157
158 if (numStr.isEmpty()) {
159 if (ok)
160 *ok = false;
161 *type = LengthType::LT_OTHER;
162 return false;
163 }
164 if (numStr.endsWith(QLatin1Char('%'))) {
165 numStr.chop(1);
167 } else if (numStr.endsWith(QLatin1String("px"))) {
168 numStr.chop(2);
169 *type = LengthType::LT_PX;
170 } else if (numStr.endsWith(QLatin1String("pc"))) {
171 numStr.chop(2);
172 *type = LengthType::LT_PC;
173 } else if (numStr.endsWith(QLatin1String("pt"))) {
174 numStr.chop(2);
175 *type = LengthType::LT_PT;
176 } else if (numStr.endsWith(QLatin1String("mm"))) {
177 numStr.chop(2);
178 *type = LengthType::LT_MM;
179 } else if (numStr.endsWith(QLatin1String("cm"))) {
180 numStr.chop(2);
181 *type = LengthType::LT_CM;
182 } else if (numStr.endsWith(QLatin1String("in"))) {
183 numStr.chop(2);
184 *type = LengthType::LT_IN;
185 } else {
186 // default coordinate system
187 *type = LengthType::LT_PX;
188 }
189 qreal len = toDouble(numStr, ok);
190 return len;
191}
192
193// this should really be called convertToDefaultCoordinateSystem
194// and convert when type != QSvgHandler::defaultCoordinateSystem
195qreal convertToPixels(qreal len, bool , LengthType type)
196{
197
198 switch (type) {
200 break;
202 break;
204 break;
206 return len * 1.25;
207 break;
209 return len * 3.543307;
210 break;
212 return len * 35.43307;
213 break;
215 return len * 90;
216 break;
218 break;
219 default:
220 break;
221 }
222 return len;
223}
224
225// Parses the angle from a string and convert it to degrees.
226// In CSS, units are not optional so if a non-zero value is defined,
227// without a unit it should be treated as invalid.
228std::optional<qreal> parseAngle(QStringView str, bool tiny12FeaturesOnly)
229{
230 QStringView numStr = QGuiSvg::trimmed(str, tiny12FeaturesOnly);
231
232 if (numStr.isEmpty())
233 return std::nullopt;
234
235 qreal unitFactor = 1.0;
236 if (numStr.endsWith(QLatin1String("deg"))) {
237 numStr.chop(3);
238 unitFactor = 1.0;
239 } else if (numStr.endsWith(QLatin1String("grad"))) {
240 numStr.chop(4);
241 // deg = grad * 0.9;
242 unitFactor = 0.9;
243 } else if (numStr.endsWith(QLatin1String("rad"))) {
244 numStr.chop(3);
245 unitFactor = 180.0 / Q_PI;
246 } else if (numStr.endsWith(QLatin1String("turn"))) {
247 numStr.chop(4);
248 // one circle = one turn
249 unitFactor = 360.0;
250 } else {
251 return std::nullopt;
252 }
253
254 bool ok;
255 qreal angle = numStr.toDouble(&ok);
256 if (!ok)
257 return std::nullopt;
258
259 return angle * unitFactor;
260}
261
263{
265 while (!str->isEmpty() && str->first().isSpace())
266 str->slice(1);
267 while ((!str->isEmpty() && QGuiSvg::isDigit(str->first().unicode()))
269 || str->startsWith(QLatin1Char('.'))) {
270
271 if (patternLen && pattern[points.size() % patternLen] == 'f') {
272 // flag expected, may only be 0 or 1
273 if (!str->startsWith(QLatin1Char('0')) && !str->startsWith(QLatin1Char('1')))
274 return;
275 points.append(str->startsWith(QLatin1Char('0')) ? 0.0 : 1.0);
276 str->slice(1);
277 } else {
279 }
280
281 while (!str->isEmpty() && str->first().isSpace())
282 str->slice(1);
283 if (str->startsWith(QLatin1Char(',')))
284 str->slice(1);
285
286 //eat the rest of space
287 while (!str->isEmpty() && str->first().isSpace())
288 str->slice(1);
289 }
290}
291
292std::optional<QPainterPath> parsePath(QStringView dataStr, bool limitLength)
293{
294 if (dataStr.isEmpty())
295 return std::nullopt;
296
297 // Max QPainterPath element count when limitLength is set to true.
298 constexpr int maxElementCount = 0x7fff;
299
300 // Return parameters count for the command or -1 for invalid commands.
301 constexpr auto paramCountFor = [](char16_t c) {
302 switch (c) {
303 case 'm': case 'l': case 't': return 2;
304 case 'z': return 0;
305 case 'h': case 'v': return 1;
306 case 's': case 'q': return 4;
307 case 'c': return 6;
308 case 'a': return 7;
309 }
310 return -1;
311 };
312
313 QPointF startPoint;
314 QPointF currPoint;
315 QPointF ctrlPoint;
316 char16_t lastMode = 0;
317
318 QPainterPath path;
319 while (!dataStr.isEmpty()) {
320 while (dataStr.first().isSpace() && dataStr.length() > 1)
321 dataStr.slice(1);
322
323 const char16_t rawCmd = dataStr.first().unicode();
324 char16_t cmd = rawCmd | 0x20; // ASCII-lowercase
325
326 int paramCount = paramCountFor(cmd);
327 if (paramCount == -1)
328 break;
329 // If the raw command is lowercase -> it is relative.
330 const bool isRelative = rawCmd >= 'a' && rawCmd <= 'z';
331
332 dataStr.slice(1);
333 const char *pattern = nullptr;
334 if (cmd == 'a')
335 pattern = "rrrffrr";
336 QVarLengthArray<qreal, 8> arg;
337 parseNumbersArray(&dataStr, arg, pattern);
338 const qreal *num = arg.constData();
339 int count = arg.size();
340
341 do {
342 if (count < paramCount)
343 return path;
344
345 QPointF offset = isRelative ? currPoint : QPointF {0., 0.};
346 switch (cmd) {
347 case 'm': {
348 currPoint = startPoint = QPointF {num[0], num[1]} + offset;
349 path.moveTo(startPoint);
350
351 // As per 1.2 spec 8.3.2 The "moveto" commands
352 // If a 'moveto' is followed by multiple pairs of coordinates without explicit commands,
353 // the subsequent pairs shall be treated as implicit 'lineto' commands.
354 cmd = 'l';
355 }
356 break;
357 case 'z': {
358 currPoint = startPoint;
359 path.closeSubpath();
360 }
361 break;
362 case 'l': {
363 currPoint = QPointF {num[0], num[1]} + offset;
364 path.lineTo(currPoint);
365 }
366 break;
367 case 'h': {
368 currPoint.setX(num[0] + offset.x());
369 path.lineTo(currPoint);
370 }
371 break;
372 case 'v': {
373 currPoint.setY(num[0] + offset.y());
374 path.lineTo(currPoint);
375 }
376 break;
377 case 'c': {
378 QPointF c1 = QPointF {num[0], num[1]} + offset;
379 QPointF c2 = QPointF {num[2], num[3]} + offset;
380 QPointF e = QPointF {num[4], num[5]} + offset;
381 path.cubicTo(c1, c2, e);
382 ctrlPoint = c2;
383 currPoint = e;
384 break;
385 }
386 case 's': {
387 QPointF c1;
388 if (lastMode == 'c' || lastMode == 's')
389 c1 = 2 * currPoint - ctrlPoint;
390 else
391 c1 = currPoint;
392 QPointF c2 = QPointF {num[0], num[1]} + offset;
393 QPointF e = QPointF {num[2], num[3]} + offset;
394
395 path.cubicTo(c1, c2, e);
396 ctrlPoint = c2;
397 currPoint = e;
398 break;
399 }
400 case 'q': {
401 QPointF c = QPointF {num[0], num[1]} + offset;
402 QPointF e = QPointF {num[2], num[3]} + offset;
403 path.quadTo(c, e);
404 ctrlPoint = c;
405 currPoint = e;
406 break;
407 }
408 case 't': {
409 QPointF e = QPointF {num[0], num[1]} + offset;
410 QPointF c;
411 if (lastMode == 'q' || lastMode == 't')
412 c = 2 * currPoint - ctrlPoint;
413 else
414 c = currPoint;
415 path.quadTo(c, e);
416 ctrlPoint = c;
417 currPoint = e;
418 break;
419 }
420 case 'a': {
421 qreal rx = num[0];
422 qreal ry = num[1];
423 qreal xAxisRotation = num[2];
424 qreal largeArcFlag = num[3];
425 qreal sweepFlag = num[4];
426 qreal ex = num[5] + offset.x();
427 qreal ey = num[6] + offset.y();
428 qreal curx = currPoint.x();
429 qreal cury = currPoint.y();
430 pathArc(path, rx, ry, xAxisRotation, int(largeArcFlag),
431 int(sweepFlag), ex, ey, curx, cury);
432
433 currPoint = QPointF {ex, ey};
434 }
435 break;
436 default:
437 Q_ASSERT_X(false, "parsePath", "command table out of sync with switch");
438 return std::nullopt;
439 }
440
441 lastMode = cmd;
442 if (limitLength && path.elementCount() > maxElementCount)
443 return std::nullopt;
444
445 // the z/Z command should have no arguments. Otherwise,
446 // the remaining part of the path is invalid.
447 if (cmd == 'z' && count != 0)
448 return path;
449
450 count -= paramCount;
451 num += paramCount;
452 } while (count > 0);
453 }
454
455 return path;
456}
457
458// the arc handling code underneath is from XSVG (BSD license)
459/*
460 * Copyright 2002 USC/Information Sciences Institute
461 *
462 * Permission to use, copy, modify, distribute, and sell this software
463 * and its documentation for any purpose is hereby granted without
464 * fee, provided that the above copyright notice appear in all copies
465 * and that both that copyright notice and this permission notice
466 * appear in supporting documentation, and that the name of
467 * Information Sciences Institute not be used in advertising or
468 * publicity pertaining to distribution of the software without
469 * specific, written prior permission. Information Sciences Institute
470 * makes no representations about the suitability of this software for
471 * any purpose. It is provided "as is" without express or implied
472 * warranty.
473 *
474 * INFORMATION SCIENCES INSTITUTE DISCLAIMS ALL WARRANTIES WITH REGARD
475 * TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF
476 * MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL INFORMATION SCIENCES
477 * INSTITUTE BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL
478 * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA
479 * OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
480 * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
481 * PERFORMANCE OF THIS SOFTWARE.
482 *
483 */
484void pathArc(QPainterPath &path, qreal rx, qreal ry,
485 qreal x_axis_rotation, int large_arc_flag,
486 int sweep_flag, qreal x, qreal y,
487 qreal curx, qreal cury)
488{
489 // Check if the start point is equal to the end point.
490 if (QPointF(curx, cury) == QPointF(x, y))
491 return;
492
493 const qreal Pr1 = rx * rx;
494 const qreal Pr2 = ry * ry;
495
496 // Avoid nans and division by zero.
497 if (qFuzzyIsNull(Pr1) || qFuzzyIsNull(Pr2)) {
498 // https://www.w3.org/TR/SVG/paths.html#ArcOutOfRangeParameters says:
499 // "If either rx or ry is 0, then this arc is treated as a straight line
500 // segment (a "lineto") joining the endpoints."
501 path.lineTo(x, y);
502 return;
503 }
504
505 qreal sin_th, cos_th;
506 qreal a00, a01, a10, a11;
507 qreal x0, y0, x1, y1, xc, yc;
508 qreal d, sfactor, sfactor_sq;
509 qreal th0, th1, th_arc;
510 int i, n_segs;
511 qreal dx, dy, dx1, dy1, Px, Py, check;
512
513 rx = qAbs(rx);
514 ry = qAbs(ry);
515
516 sin_th = qSin(x_axis_rotation * (Q_PI / 180.0));
517 cos_th = qCos(x_axis_rotation * (Q_PI / 180.0));
518
519 dx = (curx - x) / 2.0;
520 dy = (cury - y) / 2.0;
521 dx1 = cos_th * dx + sin_th * dy;
522 dy1 = -sin_th * dx + cos_th * dy;
523 Px = dx1 * dx1;
524 Py = dy1 * dy1;
525 /* Spec : check if radii are large enough */
526 check = Px / Pr1 + Py / Pr2;
527 if (check > 1) {
528 rx = rx * qSqrt(check);
529 ry = ry * qSqrt(check);
530 }
531
532 a00 = cos_th / rx;
533 a01 = sin_th / rx;
534 a10 = -sin_th / ry;
535 a11 = cos_th / ry;
536 x0 = a00 * curx + a01 * cury;
537 y0 = a10 * curx + a11 * cury;
538 x1 = a00 * x + a01 * y;
539 y1 = a10 * x + a11 * y;
540 /* (x0, y0) is current point in transformed coordinate space.
541 (x1, y1) is new point in transformed coordinate space.
542
543 The arc fits a unit-radius circle in this space.
544 */
545 d = (x1 - x0) * (x1 - x0) + (y1 - y0) * (y1 - y0);
546 if (!d)
547 return;
548 sfactor_sq = 1.0 / d - 0.25;
549 if (sfactor_sq < 0) sfactor_sq = 0;
550 sfactor = qSqrt(sfactor_sq);
551 if (sweep_flag == large_arc_flag) sfactor = -sfactor;
552 xc = 0.5 * (x0 + x1) - sfactor * (y1 - y0);
553 yc = 0.5 * (y0 + y1) + sfactor * (x1 - x0);
554 /* (xc, yc) is center of the circle. */
555
556 th0 = qAtan2(y0 - yc, x0 - xc);
557 th1 = qAtan2(y1 - yc, x1 - xc);
558
559 th_arc = th1 - th0;
560 if (th_arc < 0 && sweep_flag)
561 th_arc += 2 * Q_PI;
562 else if (th_arc > 0 && !sweep_flag)
563 th_arc -= 2 * Q_PI;
564
565 n_segs = qCeil(qAbs(th_arc / (Q_PI * 0.5 + 0.001)));
566
567 for (i = 0; i < n_segs; i++) {
568 pathArcSegment(path, xc, yc,
569 th0 + i * th_arc / n_segs,
570 th0 + (i + 1) * th_arc / n_segs,
571 rx, ry, x_axis_rotation);
572 }
573}
574
575namespace {
576namespace svgChars {
577constexpr auto spaces = " \\t\\n\\r\\f"_L1;
578constexpr auto spacesTiny = " \\t\\n\\r"_L1;
579} // namespace svgChars
580} // namespace
581
582static const QRegularExpression &reNoSpace(bool tiny12FeaturesOnly)
583{
584 if (tiny12FeaturesOnly) {
585 static QRegularExpression reTiny("[^"_L1 + svgChars::spacesTiny + "]"_L1);
586 return reTiny;
587 } else {
588 static QRegularExpression re("[^"_L1 + svgChars::spaces + "]"_L1);
589 return re;
590 }
591}
592
593QStringView trimmed(QStringView sv, bool tiny12FeaturesOnly)
594{
595 const QRegularExpression &re = reNoSpace(tiny12FeaturesOnly);
596 const qsizetype first = sv.indexOf(re);
597 if (first == -1) // implies last
598 return sv.first(0);
599 else
600 return sv.sliced(first, sv.lastIndexOf(re) - first + 1);
601}
602}
603
604QT_END_NAMESPACE
@ LT_PERCENT
Definition qguisvg_p.h:30
@ LT_OTHER
Definition qguisvg_p.h:37
std::optional< QPainterPath > parsePath(QStringView dataStr, bool limitLength)
Definition qguisvg.cpp:292
QStringView trimmed(QStringView sv, bool tiny12FeaturesOnly)
Definition qguisvg.cpp:593
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 qguisvg.cpp:484
void parseNumbersArray(QStringView *str, QVarLengthArray< qreal, 8 > &points, const char *pattern)
Definition qguisvg.cpp:262
std::optional< qreal > parseAngle(QStringView str, bool tiny12FeaturesOnly)
Definition qguisvg.cpp:228
bool isDigit(ushort ch)
Definition qguisvg.cpp:59
qreal convertToPixels(qreal len, bool, LengthType type)
Definition qguisvg.cpp:195
static const QRegularExpression & reNoSpace(bool tiny12FeaturesOnly)
Definition qguisvg.cpp:582
qreal toDouble(QStringView str, bool *ok)
Definition qguisvg.cpp:146
qreal parseLength(QStringView str, LengthType *type, bool *ok, bool tiny12FeaturesOnly)
Definition qguisvg.cpp:154
qreal toDouble(QStringView *str)
Definition qguisvg.cpp:65