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

source code of qtdeclarative/src/quick/util/qquicksvgparser.cpp