add svg style path string support for context2d api
authorCharles Yin <charles.yin@nokia.com>
Thu, 23 Jun 2011 04:15:01 +0000 (14:15 +1000)
committerCharles Yin <charles.yin@nokia.com>
Thu, 23 Jun 2011 04:15:01 +0000 (14:15 +1000)
Change-Id: I6b815f58c1d96e88fe9b592515bf62b749011b8e

src/declarative/items/qsgcontext2d.cpp
src/declarative/items/qsgcontext2d_p.h

index f8d3b90..ff7b5c3 100644 (file)
@@ -55,6 +55,7 @@
 #include "qdeclarativepixmapcache_p.h"
 
 #include <private/qv8engine_p.h>
+#include "qvarlengtharray.h"
 
 QT_BEGIN_NAMESPACE
 
@@ -2444,6 +2445,593 @@ bool QSGContext2D::isPointInPath(qreal x, qreal y) const
     return d->path.contains(QPointF(x, y));
 }
 
+//copied from QtSvg (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 {
+#if defined(Q_WS_QWS) && !defined(Q_OS_VXWORKS)
+        if(sizeof(qreal) == sizeof(float))
+            val = strtof(temp, 0);
+        else
+#endif
+        {
+            bool ok = false;
+            val = qstrtod(temp, 0, &ok);
+        }
+    }
+    return val;
+
+}
+static qreal toDouble(const QString &str, bool *ok = NULL)
+{
+    const QChar *c = str.constData();
+    qreal res = toDouble(c);
+    if (ok) {
+        *ok = ((*c) == QLatin1Char('\0'));
+    }
+    return res;
+}
+
+static qreal toDouble(const QStringRef &str, bool *ok = NULL)
+{
+    const QChar *c = str.constData();
+    qreal res = toDouble(c);
+    if (ok) {
+        *ok = (c == (str.constData() + str.length()));
+    }
+    return res;
+}
+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(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);
+}
+
+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)
+{
+    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(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;
+    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 * 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);
+    }
+}
+
+
+static bool 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;
+        QChar endc = *end;
+        *const_cast<QChar *>(end) = 0; // parseNumbersArray requires 0-termination that QStringRef cannot guarantee
+        QVarLengthArray<qreal, 8> arg;
+        parseNumbersArray(str, arg);
+        *const_cast<QChar *>(end) = endc;
+        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;
+}
+
+void QSGContext2D::setPathString(const QString& path)
+{
+    Q_D(QSGContext2D);
+    d->path = QPainterPath();
+    parsePathDataFast(path, d->path);
+}
 
 QList<int> QSGContext2D::getImageData(qreal sx, qreal sy, qreal sw, qreal sh)
 {
index 128219f..4de9fc1 100644 (file)
@@ -291,6 +291,10 @@ public slots:
     void clip();
     bool isPointInPath(qreal x, qreal y) const;
 
+    //path string parser
+    //implement the W3C SVG path spec:
+    //http://www.w3.org/TR/SVG/paths.html
+    void setPathString(const QString& path);
 
     QSGImage *createImage(const QString &url);