Revision control
- // Copyright (C) 2025 The Qt Company Ltd.
- // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
- // Qt-Security score:significant reason:default
-
-
- #include "qsvgutils_p.h"
- #include <cmath>
- #include <QtCore/qpoint.h>
- #include <QtCore/qvarlengtharray.h>
- #include <QtGui/private/qmath_p.h>
-
- QT_BEGIN_NAMESPACE
-
- namespace {
-
- 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(xAxisRotation * (Q_PI / 180.0));
- cosTh = qCos(xAxisRotation * (Q_PI / 180.0));
-
- 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);
- }
-
- // the arc handling code underneath is from XSVG (BSD license)
- /*
- * Copyright 2002 USC/Information Sciences Institute
- *
- * Permission to use, copy, modify, distribute, and sell this software
- * and its documentation for any purpose is hereby granted without
- * fee, provided that the above copyright notice appear in all copies
- * and that both that copyright notice and this permission notice
- * appear in supporting documentation, and that the name of
- * Information Sciences Institute not be used in advertising or
- * publicity pertaining to distribution of the software without
- * specific, written prior permission. Information Sciences Institute
- * makes no representations about the suitability of this software for
- * any purpose. It is provided "as is" without express or implied
- * warranty.
- *
- * INFORMATION SCIENCES INSTITUTE DISCLAIMS ALL WARRANTIES WITH REGARD
- * TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL INFORMATION SCIENCES
- * INSTITUTE BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL
- * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA
- * OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
- * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
- * PERFORMANCE OF THIS SOFTWARE.
- *
- */
- static void 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)
- {
- const qreal Pr1 = rx * rx;
- const qreal Pr2 = ry * ry;
-
- if (!Pr1 || !Pr2)
- return;
-
- 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, Px, Py, check;
-
- rx = qAbs(rx);
- ry = qAbs(ry);
-
- sin_th = qSin(x_axis_rotation * (Q_PI / 180.0));
- cos_th = qCos(x_axis_rotation * (Q_PI / 180.0));
-
- dx = (curx - x) / 2.0;
- dy = (cury - y) / 2.0;
- dx1 = cos_th * dx + sin_th * dy;
- dy1 = -sin_th * dx + cos_th * dy;
- 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);
- if (!d)
- return;
- 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 * Q_PI;
- else if (th_arc > 0 && !sweep_flag)
- th_arc -= 2 * Q_PI;
-
- n_segs = qCeil(qAbs(th_arc / (Q_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);
- }
- }
-
- }
- namespace QSvgUtils {
-
- // '0' is 0x30 and '9' is 0x39
- bool isDigit(ushort ch)
- {
- static quint16 magic = 0x3ff;
- return ((ch >> 4) == 3) && (magic >> (ch & 15));
- }
-
- qreal toDouble(QStringView *str)
- {
- const int maxLen = 255;//technically doubles can go til 308+ but whatever
- char temp[maxLen+1];
- int pos = 0;
-
- if (str->startsWith(QLatin1Char('-'))) {
- temp[pos++] = '-';
- str->slice(1);
- } else if (str->startsWith(QLatin1Char('+'))) {
- str->slice(1);
- }
- while (!str->isEmpty() && isDigit(str->first().unicode()) && pos < maxLen) {
- temp[pos++] = str->first().toLatin1();
- str->slice(1);
- }
- if (str->startsWith(QLatin1Char('.')) && pos < maxLen) {
- temp[pos++] = '.';
- str->slice(1);
- }
- while (!str->isEmpty() && isDigit(str->first().unicode()) && pos < maxLen) {
- temp[pos++] = str->first().toLatin1();
- str->slice(1);
- }
- bool exponent = false;
- if ((str->startsWith(QLatin1Char('e')) || str->startsWith(QLatin1Char('E'))) && pos < maxLen) {
- exponent = true;
- temp[pos++] = 'e';
- str->slice(1);
- if ((str->startsWith(QLatin1Char('-')) || str->startsWith(QLatin1Char('+')))
- && pos < maxLen) {
- temp[pos++] = str->first().toLatin1();
- str->slice(1);
- }
- while (!str->isEmpty() && isDigit(str->first().unicode()) && pos < maxLen) {
- temp[pos++] = str->first().toLatin1();
- str->slice(1);
- }
- }
-
- 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 {
- val = QByteArray::fromRawData(temp, pos).toDouble();
- // Do not tolerate values too wild to be represented normally by floats
- if (qFpClassify(float(val)) != FP_NORMAL)
- val = 0;
- }
- return val;
-
- }
-
- qreal toDouble(QStringView str, bool *ok)
- {
- const qreal res{ toDouble(&str) };
- if (ok)
- *ok = str.isEmpty();
- return res;
- }
-
- qreal parseLength(QStringView str, LengthType *type, bool *ok)
- {
- QStringView numStr = str.trimmed();
-
- if (numStr.isEmpty()) {
- if (ok)
- *ok = false;
- *type = LengthType::LT_OTHER;
- return false;
- }
- if (numStr.endsWith(QLatin1Char('%'))) {
- numStr.chop(1);
- *type = LengthType::LT_PERCENT;
- } else if (numStr.endsWith(QLatin1String("px"))) {
- numStr.chop(2);
- *type = LengthType::LT_PX;
- } else if (numStr.endsWith(QLatin1String("pc"))) {
- numStr.chop(2);
- *type = LengthType::LT_PC;
- } else if (numStr.endsWith(QLatin1String("pt"))) {
- numStr.chop(2);
- *type = LengthType::LT_PT;
- } else if (numStr.endsWith(QLatin1String("mm"))) {
- numStr.chop(2);
- *type = LengthType::LT_MM;
- } else if (numStr.endsWith(QLatin1String("cm"))) {
- numStr.chop(2);
- *type = LengthType::LT_CM;
- } else if (numStr.endsWith(QLatin1String("in"))) {
- numStr.chop(2);
- *type = LengthType::LT_IN;
- } else {
- // default coordinate system
- *type = LengthType::LT_PX;
- }
- qreal len = toDouble(numStr, ok);
- return len;
- }
-
- // this should really be called convertToDefaultCoordinateSystem
- // and convert when type != QSvgHandler::defaultCoordinateSystem
- qreal convertToPixels(qreal len, bool , LengthType type)
- {
-
- switch (type) {
- case LengthType::LT_PERCENT:
- break;
- case LengthType::LT_PX:
- break;
- case LengthType::LT_PC:
- break;
- case LengthType::LT_PT:
- return len * 1.25;
- break;
- case LengthType::LT_MM:
- return len * 3.543307;
- break;
- case LengthType::LT_CM:
- return len * 35.43307;
- break;
- case LengthType::LT_IN:
- return len * 90;
- break;
- case LengthType::LT_OTHER:
- break;
- default:
- break;
- }
- return len;
- }
-
- // Parses the angle from a string and convert it to degrees.
- // In CSS, units are not optional so if a non-zero value is defined,
- // without a unit it should be treated as invalid.
- std::optional<qreal> parseAngle(QStringView str)
- {
- QStringView numStr = str.trimmed();
-
- if (numStr.isEmpty())
- return std::nullopt;
-
- qreal unitFactor = 1.0;
- if (numStr.endsWith(QLatin1String("deg"))) {
- numStr.chop(3);
- unitFactor = 1.0;
- } else if (numStr.endsWith(QLatin1String("grad"))) {
- numStr.chop(4);
- // deg = grad * 0.9;
- unitFactor = 0.9;
- } else if (numStr.endsWith(QLatin1String("rad"))) {
- numStr.chop(3);
- unitFactor = 180.0 / Q_PI;
- } else if (numStr.endsWith(QLatin1String("turn"))) {
- numStr.chop(4);
- // one circle = one turn
- unitFactor = 360.0;
- } else {
- return std::nullopt;
- }
-
- bool ok;
- qreal angle = numStr.toDouble(&ok);
- if (!ok)
- return std::nullopt;
-
- return angle * unitFactor;
- }
-
- void parseNumbersArray(QStringView *str, QVarLengthArray<qreal, 8> &points, const char *pattern)
- {
- const size_t patternLen = qstrlen(pattern);
- while (!str->isEmpty() && str->first().isSpace())
- str->slice(1);
- while ((!str->isEmpty() && QSvgUtils::isDigit(str->first().unicode()))
- || str->startsWith(QLatin1Char('-')) || str->startsWith(QLatin1Char('+'))
- || str->startsWith(QLatin1Char('.'))) {
-
- if (patternLen && pattern[points.size() % patternLen] == 'f') {
- // flag expected, may only be 0 or 1
- if (!str->startsWith(QLatin1Char('0')) && !str->startsWith(QLatin1Char('1')))
- return;
- points.append(str->startsWith(QLatin1Char('0')) ? 0.0 : 1.0);
- str->slice(1);
- } else {
- points.append(QSvgUtils::toDouble(str));
- }
-
- while (!str->isEmpty() && str->first().isSpace())
- str->slice(1);
- if (str->startsWith(QLatin1Char(',')))
- str->slice(1);
-
- //eat the rest of space
- while (!str->isEmpty() && str->first().isSpace())
- str->slice(1);
- }
- }
-
- std::optional<QPainterPath> parsePathDataFast(QStringView dataStr, bool limitLength)
- {
- if (dataStr.isEmpty())
- return std::nullopt;
-
- const int maxElementCount = 0x7fff; // Assume file corruption if more path elements than this
- qreal x0 = 0, y0 = 0; // starting point
- qreal x = 0, y = 0; // current point
- char lastMode = 0;
- QPointF ctrlPt;
-
- QPainterPath path;
- while (!dataStr.isEmpty()) {
- while (dataStr.first().isSpace() && dataStr.length() > 1)
- dataStr.slice(1);
- QChar pathElem = dataStr.first();
- dataStr.slice(1);
- const char *pattern = nullptr;
- if (pathElem == QLatin1Char('a') || pathElem == QLatin1Char('A'))
- pattern = "rrrffrr";
- QVarLengthArray<qreal, 8> arg;
- parseNumbersArray(&dataStr, arg, pattern);
- if (pathElem == QLatin1Char('z') || pathElem == QLatin1Char('Z'))
- arg.append(0);//dummy
- const qreal *num = arg.constData();
- int count = arg.size();
- while (count > 0) {
- qreal offsetX = x; // correction offsets
- qreal offsetY = y; // for relative commands
- switch (pathElem.unicode()) {
- case 'm': {
- if (count < 2)
- return std::nullopt;
- 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)
- return std::nullopt;
-
- 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)
- return std::nullopt;
-
- x = num[0] + offsetX;
- y = num[1] + offsetY;
- num += 2;
- count -= 2;
- path.lineTo(x, y);
-
- }
- break;
- case 'L': {
- if (count < 2)
- return std::nullopt;
-
- 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)
- return std::nullopt;
-
- 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)
- return std::nullopt;
-
- 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)
- return std::nullopt;
-
- 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)
- return std::nullopt;
-
- 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)
- return std::nullopt;
-
- 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)
- return std::nullopt;
-
- 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)
- return std::nullopt;
-
- 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)
- return std::nullopt;
-
- 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)
- return std::nullopt;
-
- 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)
- return std::nullopt;
-
- 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 std::nullopt;
- }
- lastMode = pathElem.toLatin1();
- if (limitLength && path.elementCount() > maxElementCount)
- return std::nullopt;
- }
- }
-
- return path;
- }
-
- }
-
- QT_END_NAMESPACE
-