| 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 | |