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
qquicksvgparser.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 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
6
7#include <QtCore/qmath.h>
8#include <QtCore/qvarlengtharray.h>
9#include <QtCore/qstring.h>
10
11#include <private/qlocale_tools_p.h>
12
14
15// '0' is 0x30 and '9' is 0x39
16static inline bool isDigit(ushort ch)
17{
18 static quint16 magic = 0x3ff;
19 return ((ch >> 4) == 3) && (magic >> (ch & 15));
20}
21
22static qreal toDouble(const QChar *&str)
23{
24 const int maxLen = 255;//technically doubles can go til 308+ but whatever
25 char temp[maxLen+1];
26 int pos = 0;
27
28 if (*str == QLatin1Char('-')) {
29 temp[pos++] = '-';
30 ++str;
31 } else if (*str == QLatin1Char('+')) {
32 ++str;
33 }
34 while (isDigit(str->unicode()) && pos < maxLen) {
35 temp[pos++] = str->toLatin1();
36 ++str;
37 }
38 if (*str == QLatin1Char('.') && pos < maxLen) {
39 temp[pos++] = '.';
40 ++str;
41 }
42 while (isDigit(str->unicode()) && pos < maxLen) {
43 temp[pos++] = str->toLatin1();
44 ++str;
45 }
46 bool exponent = false;
47 if ((*str == QLatin1Char('e') || *str == QLatin1Char('E')) && pos < maxLen) {
48 exponent = true;
49 temp[pos++] = 'e';
50 ++str;
51 if ((*str == QLatin1Char('-') || *str == QLatin1Char('+')) && pos < maxLen) {
52 temp[pos++] = str->toLatin1();
53 ++str;
54 }
55 while (isDigit(str->unicode()) && pos < maxLen) {
56 temp[pos++] = str->toLatin1();
57 ++str;
58 }
59 }
60
61 temp[pos] = '\0';
62
63 qreal val;
64 if (!exponent && pos < 10) {
65 int ival = 0;
66 const char *t = temp;
67 bool neg = false;
68 if(*t == '-') {
69 neg = true;
70 ++t;
71 }
72 while(*t && *t != '.') {
73 ival *= 10;
74 ival += (*t) - '0';
75 ++t;
76 }
77 if(*t == '.') {
78 ++t;
79 int div = 1;
80 while(*t) {
81 ival *= 10;
82 ival += (*t) - '0';
83 div *= 10;
84 ++t;
85 }
86 val = ((qreal)ival)/((qreal)div);
87 } else {
88 val = ival;
89 }
90 if (neg)
91 val = -val;
92 } else {
93 bool ok = false;
94 val = qstrtod(temp, nullptr, &ok);
95 }
96 return val;
97
98}
99static inline void parseNumbersArray(const QChar *&str, QVarLengthArray<qreal, 8> &points)
100{
101 while (str->isSpace())
102 ++str;
103 while (isDigit(str->unicode()) ||
104 *str == QLatin1Char('-') || *str == QLatin1Char('+') ||
105 *str == QLatin1Char('.')) {
106
107 points.append(toDouble(str));
108
109 while (str->isSpace())
110 ++str;
111 if (*str == QLatin1Char(','))
112 ++str;
113
114 //eat the rest of space
115 while (str->isSpace())
116 ++str;
117 }
118}
119
120static void pathArcSegment(QPainterPath &path,
121 qreal xc, qreal yc,
122 qreal th0, qreal th1,
123 qreal rx, qreal ry, qreal xAxisRotation)
124{
125 qreal sinTh, cosTh;
126 qreal a00, a01, a10, a11;
127 qreal x1, y1, x2, y2, x3, y3;
128 qreal t;
129 qreal thHalf;
130
131 sinTh = qSin(qDegreesToRadians(xAxisRotation));
132 cosTh = qCos(qDegreesToRadians(xAxisRotation));
133
134 a00 = cosTh * rx;
135 a01 = -sinTh * ry;
136 a10 = sinTh * rx;
137 a11 = cosTh * ry;
138
139 thHalf = 0.5 * (th1 - th0);
140 t = (8.0 / 3.0) * qSin(thHalf * 0.5) * qSin(thHalf * 0.5) / qSin(thHalf);
141 x1 = xc + qCos(th0) - t * qSin(th0);
142 y1 = yc + qSin(th0) + t * qCos(th0);
143 x3 = xc + qCos(th1);
144 y3 = yc + qSin(th1);
145 x2 = x3 + t * qSin(th1);
146 y2 = y3 - t * qCos(th1);
147
148 path.cubicTo(a00 * x1 + a01 * y1, a10 * x1 + a11 * y1,
149 a00 * x2 + a01 * y2, a10 * x2 + a11 * y2,
150 a00 * x3 + a01 * y3, a10 * x3 + a11 * y3);
151}
152
153void QQuickSvgParser::pathArc(QPainterPath &path,
154 qreal rx,
155 qreal ry,
156 qreal x_axis_rotation,
157 int large_arc_flag,
158 int sweep_flag,
159 qreal x,
160 qreal y,
161 qreal curx, qreal cury)
162{
163 // Check if the start point is equal to the end point.
164 if (QPointF(curx, cury) == QPointF(x, y))
165 return;
166
167 qreal sin_th, cos_th;
168 qreal a00, a01, a10, a11;
169 qreal x0, y0, x1, y1, xc, yc;
170 qreal d, sfactor, sfactor_sq;
171 qreal th0, th1, th_arc;
172 int i, n_segs;
173 qreal dx, dy, dx1, dy1, Pr1, Pr2, Px, Py, check;
174
175 rx = qAbs(rx);
176 ry = qAbs(ry);
177 // Avoid nans and division by zero.
178 if (qFuzzyIsNull(rx) || qFuzzyIsNull(ry)) {
179 // https://www.w3.org/TR/SVG/paths.html#ArcOutOfRangeParameters says:
180 // "If either rx or ry is 0, then this arc is treated as a straight line
181 // segment (a "lineto") joining the endpoints."
182 path.lineTo(x, y);
183 return;
184 }
185
186 sin_th = qSin(qDegreesToRadians(x_axis_rotation));
187 cos_th = qCos(qDegreesToRadians(x_axis_rotation));
188
189 dx = (curx - x) / 2.0;
190 dy = (cury - y) / 2.0;
191 dx1 = cos_th * dx + sin_th * dy;
192 dy1 = -sin_th * dx + cos_th * dy;
193 Pr1 = rx * rx;
194 Pr2 = ry * ry;
195 Px = dx1 * dx1;
196 Py = dy1 * dy1;
197 /* Spec : check if radii are large enough */
198 check = Px / Pr1 + Py / Pr2;
199 if (check > 1) {
200 rx = rx * qSqrt(check);
201 ry = ry * qSqrt(check);
202 }
203
204 a00 = cos_th / rx;
205 a01 = sin_th / rx;
206 a10 = -sin_th / ry;
207 a11 = cos_th / ry;
208 x0 = a00 * curx + a01 * cury;
209 y0 = a10 * curx + a11 * cury;
210 x1 = a00 * x + a01 * y;
211 y1 = a10 * x + a11 * y;
212 /* (x0, y0) is current point in transformed coordinate space.
213 (x1, y1) is new point in transformed coordinate space.
214
215 The arc fits a unit-radius circle in this space.
216 */
217 d = (x1 - x0) * (x1 - x0) + (y1 - y0) * (y1 - y0);
218 sfactor_sq = 1.0 / d - 0.25;
219 if (sfactor_sq < 0) sfactor_sq = 0;
220 sfactor = qSqrt(sfactor_sq);
221 if (sweep_flag == large_arc_flag) sfactor = -sfactor;
222 xc = 0.5 * (x0 + x1) - sfactor * (y1 - y0);
223 yc = 0.5 * (y0 + y1) + sfactor * (x1 - x0);
224 /* (xc, yc) is center of the circle. */
225
226 th0 = qAtan2(y0 - yc, x0 - xc);
227 th1 = qAtan2(y1 - yc, x1 - xc);
228
229 th_arc = th1 - th0;
230 if (th_arc < 0 && sweep_flag)
231 th_arc += 2 * M_PI;
232 else if (th_arc > 0 && !sweep_flag)
233 th_arc -= 2 * M_PI;
234
235 n_segs = qCeil(qAbs(th_arc / (M_PI * 0.5 + 0.001)));
236
237 for (i = 0; i < n_segs; i++) {
238 pathArcSegment(path, xc, yc,
239 th0 + i * th_arc / n_segs,
240 th0 + (i + 1) * th_arc / n_segs,
241 rx, ry, x_axis_rotation);
242 }
243}
244
245
246bool QQuickSvgParser::parsePathDataFast(const QString &dataStr, QPainterPath &path)
247{
248 qreal x0 = 0, y0 = 0; // starting point
249 qreal x = 0, y = 0; // current point
250 char lastMode = 0;
251 QPointF ctrlPt;
252 const QChar *str = dataStr.constData();
253 const QChar *end = str + dataStr.size();
254
255 while (str != end) {
256 while (str->isSpace())
257 ++str;
258 QChar pathElem = *str;
259 ++str;
260 QVarLengthArray<qreal, 8> arg;
261 parseNumbersArray(str, arg);
262 if (pathElem == QLatin1Char('z') || pathElem == QLatin1Char('Z'))
263 arg.append(0);//dummy
264 const qreal *num = arg.constData();
265 int count = arg.size();
266 while (count > 0) {
267 qreal offsetX = x; // correction offsets
268 qreal offsetY = y; // for relative commands
269 switch (pathElem.unicode()) {
270 case 'm': {
271 if (count < 2) {
272 num++;
273 count--;
274 break;
275 }
276 x = x0 = num[0] + offsetX;
277 y = y0 = num[1] + offsetY;
278 num += 2;
279 count -= 2;
280 path.moveTo(x0, y0);
281
282 // As per 1.2 spec 8.3.2 The "moveto" commands
283 // If a 'moveto' is followed by multiple pairs of coordinates without explicit commands,
284 // the subsequent pairs shall be treated as implicit 'lineto' commands.
285 pathElem = QLatin1Char('l');
286 }
287 break;
288 case 'M': {
289 if (count < 2) {
290 num++;
291 count--;
292 break;
293 }
294 x = x0 = num[0];
295 y = y0 = num[1];
296 num += 2;
297 count -= 2;
298 path.moveTo(x0, y0);
299
300 // As per 1.2 spec 8.3.2 The "moveto" commands
301 // If a 'moveto' is followed by multiple pairs of coordinates without explicit commands,
302 // the subsequent pairs shall be treated as implicit 'lineto' commands.
303 pathElem = QLatin1Char('L');
304 }
305 break;
306 case 'z':
307 case 'Z': {
308 x = x0;
309 y = y0;
310 count--; // skip dummy
311 num++;
312 path.closeSubpath();
313 }
314 break;
315 case 'l': {
316 if (count < 2) {
317 num++;
318 count--;
319 break;
320 }
321 x = num[0] + offsetX;
322 y = num[1] + offsetY;
323 num += 2;
324 count -= 2;
325 path.lineTo(x, y);
326
327 }
328 break;
329 case 'L': {
330 if (count < 2) {
331 num++;
332 count--;
333 break;
334 }
335 x = num[0];
336 y = num[1];
337 num += 2;
338 count -= 2;
339 path.lineTo(x, y);
340 }
341 break;
342 case 'h': {
343 x = num[0] + offsetX;
344 num++;
345 count--;
346 path.lineTo(x, y);
347 }
348 break;
349 case 'H': {
350 x = num[0];
351 num++;
352 count--;
353 path.lineTo(x, y);
354 }
355 break;
356 case 'v': {
357 y = num[0] + offsetY;
358 num++;
359 count--;
360 path.lineTo(x, y);
361 }
362 break;
363 case 'V': {
364 y = num[0];
365 num++;
366 count--;
367 path.lineTo(x, y);
368 }
369 break;
370 case 'c': {
371 if (count < 6) {
372 num += count;
373 count = 0;
374 break;
375 }
376 QPointF c1(num[0] + offsetX, num[1] + offsetY);
377 QPointF c2(num[2] + offsetX, num[3] + offsetY);
378 QPointF e(num[4] + offsetX, num[5] + offsetY);
379 num += 6;
380 count -= 6;
381 path.cubicTo(c1, c2, e);
382 ctrlPt = c2;
383 x = e.x();
384 y = e.y();
385 break;
386 }
387 case 'C': {
388 if (count < 6) {
389 num += count;
390 count = 0;
391 break;
392 }
393 QPointF c1(num[0], num[1]);
394 QPointF c2(num[2], num[3]);
395 QPointF e(num[4], num[5]);
396 num += 6;
397 count -= 6;
398 path.cubicTo(c1, c2, e);
399 ctrlPt = c2;
400 x = e.x();
401 y = e.y();
402 break;
403 }
404 case 's': {
405 if (count < 4) {
406 num += count;
407 count = 0;
408 break;
409 }
410 QPointF c1;
411 if (lastMode == 'c' || lastMode == 'C' ||
412 lastMode == 's' || lastMode == 'S')
413 c1 = QPointF(2*x-ctrlPt.x(), 2*y-ctrlPt.y());
414 else
415 c1 = QPointF(x, y);
416 QPointF c2(num[0] + offsetX, num[1] + offsetY);
417 QPointF e(num[2] + offsetX, num[3] + offsetY);
418 num += 4;
419 count -= 4;
420 path.cubicTo(c1, c2, e);
421 ctrlPt = c2;
422 x = e.x();
423 y = e.y();
424 break;
425 }
426 case 'S': {
427 if (count < 4) {
428 num += count;
429 count = 0;
430 break;
431 }
432 QPointF c1;
433 if (lastMode == 'c' || lastMode == 'C' ||
434 lastMode == 's' || lastMode == 'S')
435 c1 = QPointF(2*x-ctrlPt.x(), 2*y-ctrlPt.y());
436 else
437 c1 = QPointF(x, y);
438 QPointF c2(num[0], num[1]);
439 QPointF e(num[2], num[3]);
440 num += 4;
441 count -= 4;
442 path.cubicTo(c1, c2, e);
443 ctrlPt = c2;
444 x = e.x();
445 y = e.y();
446 break;
447 }
448 case 'q': {
449 if (count < 4) {
450 num += count;
451 count = 0;
452 break;
453 }
454 QPointF c(num[0] + offsetX, num[1] + offsetY);
455 QPointF e(num[2] + offsetX, num[3] + offsetY);
456 num += 4;
457 count -= 4;
458 path.quadTo(c, e);
459 ctrlPt = c;
460 x = e.x();
461 y = e.y();
462 break;
463 }
464 case 'Q': {
465 if (count < 4) {
466 num += count;
467 count = 0;
468 break;
469 }
470 QPointF c(num[0], num[1]);
471 QPointF e(num[2], num[3]);
472 num += 4;
473 count -= 4;
474 path.quadTo(c, e);
475 ctrlPt = c;
476 x = e.x();
477 y = e.y();
478 break;
479 }
480 case 't': {
481 if (count < 2) {
482 num += count;
483 count = 0;
484 break;
485 }
486 QPointF e(num[0] + offsetX, num[1] + offsetY);
487 num += 2;
488 count -= 2;
489 QPointF c;
490 if (lastMode == 'q' || lastMode == 'Q' ||
491 lastMode == 't' || lastMode == 'T')
492 c = QPointF(2*x-ctrlPt.x(), 2*y-ctrlPt.y());
493 else
494 c = QPointF(x, y);
495 path.quadTo(c, e);
496 ctrlPt = c;
497 x = e.x();
498 y = e.y();
499 break;
500 }
501 case 'T': {
502 if (count < 2) {
503 num += count;
504 count = 0;
505 break;
506 }
507 QPointF e(num[0], num[1]);
508 num += 2;
509 count -= 2;
510 QPointF c;
511 if (lastMode == 'q' || lastMode == 'Q' ||
512 lastMode == 't' || lastMode == 'T')
513 c = QPointF(2*x-ctrlPt.x(), 2*y-ctrlPt.y());
514 else
515 c = QPointF(x, y);
516 path.quadTo(c, e);
517 ctrlPt = c;
518 x = e.x();
519 y = e.y();
520 break;
521 }
522 case 'a': {
523 if (count < 7) {
524 num += count;
525 count = 0;
526 break;
527 }
528 qreal rx = (*num++);
529 qreal ry = (*num++);
530 qreal xAxisRotation = (*num++);
531 qreal largeArcFlag = (*num++);
532 qreal sweepFlag = (*num++);
533 qreal ex = (*num++) + offsetX;
534 qreal ey = (*num++) + offsetY;
535 count -= 7;
536 qreal curx = x;
537 qreal cury = y;
538 pathArc(path, rx, ry, xAxisRotation, int(largeArcFlag),
539 int(sweepFlag), ex, ey, curx, cury);
540
541 x = ex;
542 y = ey;
543 }
544 break;
545 case 'A': {
546 if (count < 7) {
547 num += count;
548 count = 0;
549 break;
550 }
551 qreal rx = (*num++);
552 qreal ry = (*num++);
553 qreal xAxisRotation = (*num++);
554 qreal largeArcFlag = (*num++);
555 qreal sweepFlag = (*num++);
556 qreal ex = (*num++);
557 qreal ey = (*num++);
558 count -= 7;
559 qreal curx = x;
560 qreal cury = y;
561 pathArc(path, rx, ry, xAxisRotation, int(largeArcFlag),
562 int(sweepFlag), ex, ey, curx, cury);
563
564 x = ex;
565 y = ey;
566 }
567 break;
568 default:
569 return false;
570 }
571 lastMode = pathElem.toLatin1();
572 }
573 }
574 return true;
575}
576
577QT_END_NAMESPACE
Q_QUICK_EXPORT 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)
bool parsePathDataFast(const QString &dataStr, QPainterPath &path)
static QT_BEGIN_NAMESPACE bool isDigit(ushort ch)
static qreal toDouble(const QChar *&str)
static void pathArcSegment(QPainterPath &path, qreal xc, qreal yc, qreal th0, qreal th1, qreal rx, qreal ry, qreal xAxisRotation)
static void parseNumbersArray(const QChar *&str, QVarLengthArray< qreal, 8 > &points)