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 | |
12 | QT_BEGIN_NAMESPACE |
13 | |
14 | // '0' is 0x30 and '9' is 0x39 |
15 | static inline bool isDigit(ushort ch) |
16 | { |
17 | static quint16 magic = 0x3ff; |
18 | return ((ch >> 4) == 3) && (magic >> (ch & 15)); |
19 | } |
20 | |
21 | static 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 | } |
98 | static 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 | |
119 | static 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 | |
152 | void 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 | |
233 | bool 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 | |
564 | QT_END_NAMESPACE |
565 | |