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 | |
46 | QT_BEGIN_NAMESPACE |
47 | |
48 | //copied from Qt SVG (qsvghandler.cpp). |
49 | Q_CORE_EXPORT double qstrtod(const char *s00, char const **se, bool *ok); |
50 | // '0' is 0x30 and '9' is 0x39 |
51 | static inline bool isDigit(ushort ch) |
52 | { |
53 | static quint16 magic = 0x3ff; |
54 | return ((ch >> 4) == 3) && (magic >> (ch & 15)); |
55 | } |
56 | |
57 | static 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(ch: 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(ch: 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(ch: 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(s00: temp, se: nullptr, ok: &ok); |
130 | } |
131 | return val; |
132 | |
133 | } |
134 | static inline void parseNumbersArray(const QChar *&str, QVarLengthArray<qreal, 8> &points) |
135 | { |
136 | while (str->isSpace()) |
137 | ++str; |
138 | while (isDigit(ch: str->unicode()) || |
139 | *str == QLatin1Char('-') || *str == QLatin1Char('+') || |
140 | *str == QLatin1Char('.')) { |
141 | |
142 | points.append(t: 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 | |
155 | static 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(v: qDegreesToRadians(degrees: xAxisRotation)); |
167 | cosTh = qCos(v: qDegreesToRadians(degrees: 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(v: thHalf * 0.5) * qSin(v: thHalf * 0.5) / qSin(v: thHalf); |
176 | x1 = xc + qCos(v: th0) - t * qSin(v: th0); |
177 | y1 = yc + qSin(v: th0) + t * qCos(v: th0); |
178 | x3 = xc + qCos(v: th1); |
179 | y3 = yc + qSin(v: th1); |
180 | x2 = x3 + t * qSin(v: th1); |
181 | y2 = y3 - t * qCos(v: th1); |
182 | |
183 | path.cubicTo(ctrlPt1x: a00 * x1 + a01 * y1, ctrlPt1y: a10 * x1 + a11 * y1, |
184 | ctrlPt2x: a00 * x2 + a01 * y2, ctrlPt2y: a10 * x2 + a11 * y2, |
185 | endPtx: a00 * x3 + a01 * y3, endPty: a10 * x3 + a11 * y3); |
186 | } |
187 | |
188 | void 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(t: rx); |
207 | ry = qAbs(t: ry); |
208 | |
209 | sin_th = qSin(v: qDegreesToRadians(degrees: x_axis_rotation)); |
210 | cos_th = qCos(v: qDegreesToRadians(degrees: 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(v: check); |
224 | ry = ry * qSqrt(v: 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(v: 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(y: y0 - yc, x: x0 - xc); |
250 | th1 = qAtan2(y: y1 - yc, x: 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(v: qAbs(t: th_arc / (M_PI * 0.5 + 0.001))); |
259 | |
260 | for (i = 0; i < n_segs; i++) { |
261 | pathArcSegment(path, xc, yc, |
262 | th0: th0 + i * th_arc / n_segs, |
263 | th1: th0 + (i + 1) * th_arc / n_segs, |
264 | rx, ry, xAxisRotation: x_axis_rotation); |
265 | } |
266 | } |
267 | |
268 | |
269 | bool 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, points&: arg); |
285 | if (pathElem == QLatin1Char('z') || pathElem == QLatin1Char('Z')) |
286 | arg.append(t: 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(x: x0, y: 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(x: x0, y: 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(ctrlPt1: c1, ctrlPt2: c2, endPt: 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(ctrlPt1: c1, ctrlPt2: c2, endPt: 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(ctrlPt1: c1, ctrlPt2: c2, endPt: 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(ctrlPt1: c1, ctrlPt2: c2, endPt: 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(ctrlPt: c, endPt: 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(ctrlPt: c, endPt: 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(ctrlPt: c, endPt: 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(ctrlPt: c, endPt: 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, x_axis_rotation: xAxisRotation, large_arc_flag: int(largeArcFlag), |
562 | sweep_flag: int(sweepFlag), x: ex, y: 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, x_axis_rotation: xAxisRotation, large_arc_flag: int(largeArcFlag), |
585 | sweep_flag: int(sweepFlag), x: ex, y: 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 | |
600 | QT_END_NAMESPACE |
601 | |