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