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