| /**************************************************************************** |
| ** |
| ** Copyright (C) 2016 The Qt Company Ltd. |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** This file is part of the Qt Quick module of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:LGPL$ |
| ** Commercial License Usage |
| ** Licensees holding valid commercial Qt licenses may use this file in |
| ** accordance with the commercial license agreement provided with the |
| ** Software or, alternatively, in accordance with the terms contained in |
| ** a written agreement between you and The Qt Company. For licensing terms |
| ** and conditions see https://www.qt.io/terms-conditions. For further |
| ** information use the contact form at https://www.qt.io/contact-us. |
| ** |
| ** GNU Lesser General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU Lesser |
| ** General Public License version 3 as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
| ** packaging of this file. Please review the following information to |
| ** ensure the GNU Lesser General Public License version 3 requirements |
| ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU |
| ** General Public License version 2.0 or (at your option) the GNU General |
| ** Public license version 3 or any later version approved by the KDE Free |
| ** Qt Foundation. The licenses are as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
| ** included in the packaging of this file. Please review the following |
| ** information to ensure the GNU General Public License requirements will |
| ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
| ** https://www.gnu.org/licenses/gpl-3.0.html. |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| #include "qquicksvgparser_p.h" |
| |
| #include <QtCore/qmath.h> |
| #include <QtCore/qvarlengtharray.h> |
| #include <QtCore/qstring.h> |
| |
| QT_BEGIN_NAMESPACE |
| |
| //copied from Qt SVG (qsvghandler.cpp). |
| Q_CORE_EXPORT double qstrtod(const char *s00, char const **se, bool *ok); |
| // '0' is 0x30 and '9' is 0x39 |
| static inline bool isDigit(ushort ch) |
| { |
| static quint16 magic = 0x3ff; |
| return ((ch >> 4) == 3) && (magic >> (ch & 15)); |
| } |
| |
| static qreal toDouble(const QChar *&str) |
| { |
| const int maxLen = 255;//technically doubles can go til 308+ but whatever |
| char temp[maxLen+1]; |
| int pos = 0; |
| |
| if (*str == QLatin1Char('-')) { |
| temp[pos++] = '-'; |
| ++str; |
| } else if (*str == QLatin1Char('+')) { |
| ++str; |
| } |
| while (isDigit(str->unicode()) && pos < maxLen) { |
| temp[pos++] = str->toLatin1(); |
| ++str; |
| } |
| if (*str == QLatin1Char('.') && pos < maxLen) { |
| temp[pos++] = '.'; |
| ++str; |
| } |
| while (isDigit(str->unicode()) && pos < maxLen) { |
| temp[pos++] = str->toLatin1(); |
| ++str; |
| } |
| bool exponent = false; |
| if ((*str == QLatin1Char('e') || *str == QLatin1Char('E')) && pos < maxLen) { |
| exponent = true; |
| temp[pos++] = 'e'; |
| ++str; |
| if ((*str == QLatin1Char('-') || *str == QLatin1Char('+')) && pos < maxLen) { |
| temp[pos++] = str->toLatin1(); |
| ++str; |
| } |
| while (isDigit(str->unicode()) && pos < maxLen) { |
| temp[pos++] = str->toLatin1(); |
| ++str; |
| } |
| } |
| |
| temp[pos] = '\0'; |
| |
| qreal val; |
| if (!exponent && pos < 10) { |
| int ival = 0; |
| const char *t = temp; |
| bool neg = false; |
| if(*t == '-') { |
| neg = true; |
| ++t; |
| } |
| while(*t && *t != '.') { |
| ival *= 10; |
| ival += (*t) - '0'; |
| ++t; |
| } |
| if(*t == '.') { |
| ++t; |
| int div = 1; |
| while(*t) { |
| ival *= 10; |
| ival += (*t) - '0'; |
| div *= 10; |
| ++t; |
| } |
| val = ((qreal)ival)/((qreal)div); |
| } else { |
| val = ival; |
| } |
| if (neg) |
| val = -val; |
| } else { |
| bool ok = false; |
| val = qstrtod(temp, nullptr, &ok); |
| } |
| return val; |
| |
| } |
| static inline void parseNumbersArray(const QChar *&str, QVarLengthArray<qreal, 8> &points) |
| { |
| while (str->isSpace()) |
| ++str; |
| while (isDigit(str->unicode()) || |
| *str == QLatin1Char('-') || *str == QLatin1Char('+') || |
| *str == QLatin1Char('.')) { |
| |
| points.append(toDouble(str)); |
| |
| while (str->isSpace()) |
| ++str; |
| if (*str == QLatin1Char(',')) |
| ++str; |
| |
| //eat the rest of space |
| while (str->isSpace()) |
| ++str; |
| } |
| } |
| |
| static void pathArcSegment(QPainterPath &path, |
| qreal xc, qreal yc, |
| qreal th0, qreal th1, |
| qreal rx, qreal ry, qreal xAxisRotation) |
| { |
| qreal sinTh, cosTh; |
| qreal a00, a01, a10, a11; |
| qreal x1, y1, x2, y2, x3, y3; |
| qreal t; |
| qreal thHalf; |
| |
| sinTh = qSin(qDegreesToRadians(xAxisRotation)); |
| cosTh = qCos(qDegreesToRadians(xAxisRotation)); |
| |
| a00 = cosTh * rx; |
| a01 = -sinTh * ry; |
| a10 = sinTh * rx; |
| a11 = cosTh * ry; |
| |
| thHalf = 0.5 * (th1 - th0); |
| t = (8.0 / 3.0) * qSin(thHalf * 0.5) * qSin(thHalf * 0.5) / qSin(thHalf); |
| x1 = xc + qCos(th0) - t * qSin(th0); |
| y1 = yc + qSin(th0) + t * qCos(th0); |
| x3 = xc + qCos(th1); |
| y3 = yc + qSin(th1); |
| x2 = x3 + t * qSin(th1); |
| y2 = y3 - t * qCos(th1); |
| |
| path.cubicTo(a00 * x1 + a01 * y1, a10 * x1 + a11 * y1, |
| a00 * x2 + a01 * y2, a10 * x2 + a11 * y2, |
| a00 * x3 + a01 * y3, a10 * x3 + a11 * y3); |
| } |
| |
| void QQuickSvgParser::pathArc(QPainterPath &path, |
| qreal rx, |
| qreal ry, |
| qreal x_axis_rotation, |
| int large_arc_flag, |
| int sweep_flag, |
| qreal x, |
| qreal y, |
| qreal curx, qreal cury) |
| { |
| qreal sin_th, cos_th; |
| qreal a00, a01, a10, a11; |
| qreal x0, y0, x1, y1, xc, yc; |
| qreal d, sfactor, sfactor_sq; |
| qreal th0, th1, th_arc; |
| int i, n_segs; |
| qreal dx, dy, dx1, dy1, Pr1, Pr2, Px, Py, check; |
| |
| rx = qAbs(rx); |
| ry = qAbs(ry); |
| |
| sin_th = qSin(qDegreesToRadians(x_axis_rotation)); |
| cos_th = qCos(qDegreesToRadians(x_axis_rotation)); |
| |
| dx = (curx - x) / 2.0; |
| dy = (cury - y) / 2.0; |
| dx1 = cos_th * dx + sin_th * dy; |
| dy1 = -sin_th * dx + cos_th * dy; |
| Pr1 = rx * rx; |
| Pr2 = ry * ry; |
| Px = dx1 * dx1; |
| Py = dy1 * dy1; |
| /* Spec : check if radii are large enough */ |
| check = Px / Pr1 + Py / Pr2; |
| if (check > 1) { |
| rx = rx * qSqrt(check); |
| ry = ry * qSqrt(check); |
| } |
| |
| a00 = cos_th / rx; |
| a01 = sin_th / rx; |
| a10 = -sin_th / ry; |
| a11 = cos_th / ry; |
| x0 = a00 * curx + a01 * cury; |
| y0 = a10 * curx + a11 * cury; |
| x1 = a00 * x + a01 * y; |
| y1 = a10 * x + a11 * y; |
| /* (x0, y0) is current point in transformed coordinate space. |
| (x1, y1) is new point in transformed coordinate space. |
| |
| The arc fits a unit-radius circle in this space. |
| */ |
| d = (x1 - x0) * (x1 - x0) + (y1 - y0) * (y1 - y0); |
| sfactor_sq = 1.0 / d - 0.25; |
| if (sfactor_sq < 0) sfactor_sq = 0; |
| sfactor = qSqrt(sfactor_sq); |
| if (sweep_flag == large_arc_flag) sfactor = -sfactor; |
| xc = 0.5 * (x0 + x1) - sfactor * (y1 - y0); |
| yc = 0.5 * (y0 + y1) + sfactor * (x1 - x0); |
| /* (xc, yc) is center of the circle. */ |
| |
| th0 = qAtan2(y0 - yc, x0 - xc); |
| th1 = qAtan2(y1 - yc, x1 - xc); |
| |
| th_arc = th1 - th0; |
| if (th_arc < 0 && sweep_flag) |
| th_arc += 2 * M_PI; |
| else if (th_arc > 0 && !sweep_flag) |
| th_arc -= 2 * M_PI; |
| |
| n_segs = qCeil(qAbs(th_arc / (M_PI * 0.5 + 0.001))); |
| |
| for (i = 0; i < n_segs; i++) { |
| pathArcSegment(path, xc, yc, |
| th0 + i * th_arc / n_segs, |
| th0 + (i + 1) * th_arc / n_segs, |
| rx, ry, x_axis_rotation); |
| } |
| } |
| |
| |
| bool QQuickSvgParser::parsePathDataFast(const QString &dataStr, QPainterPath &path) |
| { |
| qreal x0 = 0, y0 = 0; // starting point |
| qreal x = 0, y = 0; // current point |
| char lastMode = 0; |
| QPointF ctrlPt; |
| const QChar *str = dataStr.constData(); |
| const QChar *end = str + dataStr.size(); |
| |
| while (str != end) { |
| while (str->isSpace()) |
| ++str; |
| QChar pathElem = *str; |
| ++str; |
| QVarLengthArray<qreal, 8> arg; |
| parseNumbersArray(str, arg); |
| if (pathElem == QLatin1Char('z') || pathElem == QLatin1Char('Z')) |
| arg.append(0);//dummy |
| const qreal *num = arg.constData(); |
| int count = arg.count(); |
| while (count > 0) { |
| qreal offsetX = x; // correction offsets |
| qreal offsetY = y; // for relative commands |
| switch (pathElem.unicode()) { |
| case 'm': { |
| if (count < 2) { |
| num++; |
| count--; |
| break; |
| } |
| x = x0 = num[0] + offsetX; |
| y = y0 = num[1] + offsetY; |
| num += 2; |
| count -= 2; |
| path.moveTo(x0, y0); |
| |
| // As per 1.2 spec 8.3.2 The "moveto" commands |
| // If a 'moveto' is followed by multiple pairs of coordinates without explicit commands, |
| // the subsequent pairs shall be treated as implicit 'lineto' commands. |
| pathElem = QLatin1Char('l'); |
| } |
| break; |
| case 'M': { |
| if (count < 2) { |
| num++; |
| count--; |
| break; |
| } |
| x = x0 = num[0]; |
| y = y0 = num[1]; |
| num += 2; |
| count -= 2; |
| path.moveTo(x0, y0); |
| |
| // As per 1.2 spec 8.3.2 The "moveto" commands |
| // If a 'moveto' is followed by multiple pairs of coordinates without explicit commands, |
| // the subsequent pairs shall be treated as implicit 'lineto' commands. |
| pathElem = QLatin1Char('L'); |
| } |
| break; |
| case 'z': |
| case 'Z': { |
| x = x0; |
| y = y0; |
| count--; // skip dummy |
| num++; |
| path.closeSubpath(); |
| } |
| break; |
| case 'l': { |
| if (count < 2) { |
| num++; |
| count--; |
| break; |
| } |
| x = num[0] + offsetX; |
| y = num[1] + offsetY; |
| num += 2; |
| count -= 2; |
| path.lineTo(x, y); |
| |
| } |
| break; |
| case 'L': { |
| if (count < 2) { |
| num++; |
| count--; |
| break; |
| } |
| x = num[0]; |
| y = num[1]; |
| num += 2; |
| count -= 2; |
| path.lineTo(x, y); |
| } |
| break; |
| case 'h': { |
| x = num[0] + offsetX; |
| num++; |
| count--; |
| path.lineTo(x, y); |
| } |
| break; |
| case 'H': { |
| x = num[0]; |
| num++; |
| count--; |
| path.lineTo(x, y); |
| } |
| break; |
| case 'v': { |
| y = num[0] + offsetY; |
| num++; |
| count--; |
| path.lineTo(x, y); |
| } |
| break; |
| case 'V': { |
| y = num[0]; |
| num++; |
| count--; |
| path.lineTo(x, y); |
| } |
| break; |
| case 'c': { |
| if (count < 6) { |
| num += count; |
| count = 0; |
| break; |
| } |
| QPointF c1(num[0] + offsetX, num[1] + offsetY); |
| QPointF c2(num[2] + offsetX, num[3] + offsetY); |
| QPointF e(num[4] + offsetX, num[5] + offsetY); |
| num += 6; |
| count -= 6; |
| path.cubicTo(c1, c2, e); |
| ctrlPt = c2; |
| x = e.x(); |
| y = e.y(); |
| break; |
| } |
| case 'C': { |
| if (count < 6) { |
| num += count; |
| count = 0; |
| break; |
| } |
| QPointF c1(num[0], num[1]); |
| QPointF c2(num[2], num[3]); |
| QPointF e(num[4], num[5]); |
| num += 6; |
| count -= 6; |
| path.cubicTo(c1, c2, e); |
| ctrlPt = c2; |
| x = e.x(); |
| y = e.y(); |
| break; |
| } |
| case 's': { |
| if (count < 4) { |
| num += count; |
| count = 0; |
| break; |
| } |
| QPointF c1; |
| if (lastMode == 'c' || lastMode == 'C' || |
| lastMode == 's' || lastMode == 'S') |
| c1 = QPointF(2*x-ctrlPt.x(), 2*y-ctrlPt.y()); |
| else |
| c1 = QPointF(x, y); |
| QPointF c2(num[0] + offsetX, num[1] + offsetY); |
| QPointF e(num[2] + offsetX, num[3] + offsetY); |
| num += 4; |
| count -= 4; |
| path.cubicTo(c1, c2, e); |
| ctrlPt = c2; |
| x = e.x(); |
| y = e.y(); |
| break; |
| } |
| case 'S': { |
| if (count < 4) { |
| num += count; |
| count = 0; |
| break; |
| } |
| QPointF c1; |
| if (lastMode == 'c' || lastMode == 'C' || |
| lastMode == 's' || lastMode == 'S') |
| c1 = QPointF(2*x-ctrlPt.x(), 2*y-ctrlPt.y()); |
| else |
| c1 = QPointF(x, y); |
| QPointF c2(num[0], num[1]); |
| QPointF e(num[2], num[3]); |
| num += 4; |
| count -= 4; |
| path.cubicTo(c1, c2, e); |
| ctrlPt = c2; |
| x = e.x(); |
| y = e.y(); |
| break; |
| } |
| case 'q': { |
| if (count < 4) { |
| num += count; |
| count = 0; |
| break; |
| } |
| QPointF c(num[0] + offsetX, num[1] + offsetY); |
| QPointF e(num[2] + offsetX, num[3] + offsetY); |
| num += 4; |
| count -= 4; |
| path.quadTo(c, e); |
| ctrlPt = c; |
| x = e.x(); |
| y = e.y(); |
| break; |
| } |
| case 'Q': { |
| if (count < 4) { |
| num += count; |
| count = 0; |
| break; |
| } |
| QPointF c(num[0], num[1]); |
| QPointF e(num[2], num[3]); |
| num += 4; |
| count -= 4; |
| path.quadTo(c, e); |
| ctrlPt = c; |
| x = e.x(); |
| y = e.y(); |
| break; |
| } |
| case 't': { |
| if (count < 2) { |
| num += count; |
| count = 0; |
| break; |
| } |
| QPointF e(num[0] + offsetX, num[1] + offsetY); |
| num += 2; |
| count -= 2; |
| QPointF c; |
| if (lastMode == 'q' || lastMode == 'Q' || |
| lastMode == 't' || lastMode == 'T') |
| c = QPointF(2*x-ctrlPt.x(), 2*y-ctrlPt.y()); |
| else |
| c = QPointF(x, y); |
| path.quadTo(c, e); |
| ctrlPt = c; |
| x = e.x(); |
| y = e.y(); |
| break; |
| } |
| case 'T': { |
| if (count < 2) { |
| num += count; |
| count = 0; |
| break; |
| } |
| QPointF e(num[0], num[1]); |
| num += 2; |
| count -= 2; |
| QPointF c; |
| if (lastMode == 'q' || lastMode == 'Q' || |
| lastMode == 't' || lastMode == 'T') |
| c = QPointF(2*x-ctrlPt.x(), 2*y-ctrlPt.y()); |
| else |
| c = QPointF(x, y); |
| path.quadTo(c, e); |
| ctrlPt = c; |
| x = e.x(); |
| y = e.y(); |
| break; |
| } |
| case 'a': { |
| if (count < 7) { |
| num += count; |
| count = 0; |
| break; |
| } |
| qreal rx = (*num++); |
| qreal ry = (*num++); |
| qreal xAxisRotation = (*num++); |
| qreal largeArcFlag = (*num++); |
| qreal sweepFlag = (*num++); |
| qreal ex = (*num++) + offsetX; |
| qreal ey = (*num++) + offsetY; |
| count -= 7; |
| qreal curx = x; |
| qreal cury = y; |
| pathArc(path, rx, ry, xAxisRotation, int(largeArcFlag), |
| int(sweepFlag), ex, ey, curx, cury); |
| |
| x = ex; |
| y = ey; |
| } |
| break; |
| case 'A': { |
| if (count < 7) { |
| num += count; |
| count = 0; |
| break; |
| } |
| qreal rx = (*num++); |
| qreal ry = (*num++); |
| qreal xAxisRotation = (*num++); |
| qreal largeArcFlag = (*num++); |
| qreal sweepFlag = (*num++); |
| qreal ex = (*num++); |
| qreal ey = (*num++); |
| count -= 7; |
| qreal curx = x; |
| qreal cury = y; |
| pathArc(path, rx, ry, xAxisRotation, int(largeArcFlag), |
| int(sweepFlag), ex, ey, curx, cury); |
| |
| x = ex; |
| y = ey; |
| } |
| break; |
| default: |
| return false; |
| } |
| lastMode = pathElem.toLatin1(); |
| } |
| } |
| return true; |
| } |
| |
| QT_END_NAMESPACE |