// Copyright (C) 2016 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

#include "qquickcontext2d_p.h"
#include "qquickcontext2dcommandbuffer_p.h"
#include "qquickcanvasitem_p.h"
#include <private/qtquickglobal_p.h>
#include <private/qquickcontext2dtexture_p.h>
#include <private/qquickitem_p.h>
#if QT_CONFIG(quick_shadereffect)
#include <QtQuick/private/qquickshadereffectsource_p.h>
#endif
#include <qsgrendererinterface.h>

#include <QtQuick/private/qsgcontext_p.h>
#include <private/qquicksvgparser_p.h>
#if QT_CONFIG(quick_path)
#include <private/qquickpath_p.h>
#endif
#include <private/qquickimage_p_p.h>

#include <qqmlinfo.h>

#include <qqmlengine.h>
#include <private/qv4domerrors_p.h>
#include <private/qv4engine_p.h>
#include <private/qv4object_p.h>
#include <private/qv4qobjectwrapper_p.h>
#include <private/qquickwindow_p.h>

#include <private/qv4value_p.h>
#include <private/qv4functionobject_p.h>
#include <private/qv4objectproto_p.h>
#include <private/qv4scopedvalue_p.h>
#include <private/qlocale_tools_p.h>

#include <QtCore/qmath.h>
#include <QtCore/qvector.h>
#include <QtCore/private/qnumeric_p.h>
#include <QtCore/QRunnable>
#include <QtGui/qguiapplication.h>
#include <private/qguiapplication_p.h>
#include <qpa/qplatformintegration.h>

#include <private/qsgdefaultrendercontext_p.h>

#include <QtCore/qpointer.h>

#include <cmath>
#if defined(Q_OS_QNX) || defined(Q_OS_ANDROID)
#include <ctype.h>
#endif

QT_BEGIN_NAMESPACE
/*!
    \qmltype Context2D
    \nativetype QQuickContext2D
    \inqmlmodule QtQuick
    \ingroup qtquick-canvas
    \since 5.0
    \brief Provides 2D context for shapes on a Canvas item.

    The Context2D object can be created by \c Canvas item's \c getContext()
    method:
    \code
    Canvas {
      id:canvas
      onPaint:{
         var ctx = canvas.getContext('2d');
         //...
      }
    }
    \endcode
    The Context2D API implements the same \l
    {http://www.w3.org/TR/2dcontext}{W3C Canvas 2D Context API standard} with
    some enhanced features.

    The Context2D API provides the rendering \b{context} which defines the
    methods and attributes needed to draw on the \c Canvas item. The following
    assigns the canvas rendering context to a \c{context} variable:
    \code
    var context = mycanvas.getContext("2d")
    \endcode

    The Context2D API renders the canvas as a coordinate system whose origin
    (0,0) is at the top left corner, as shown in the figure below. Coordinates
    increase along the \c{x} axis from left to right and along the \c{y} axis
    from top to bottom of the canvas.
    \image qml-item-canvas-context.gif
*/



#define CHECK_CONTEXT(r)     if (!r || !r->d()->context() || !r->d()->context()->bufferValid()) \
                                THROW_GENERIC_ERROR("Not a Context2D object");

#define CHECK_CONTEXT_SETTER(r)     if (!r || !r->d()->context() || !r->d()->context()->bufferValid()) \
                                       THROW_GENERIC_ERROR("Not a Context2D object");
#define qClamp(val, min, max) qMin(qMax(val, min), max)
#define CHECK_RGBA(c) (c == '-' || c == '.' || (c >=0 && c <= 9))
Q_QUICK_EXPORT QColor qt_color_from_string(const QV4::Value &name)
{
    QByteArray str = name.toQString().toUtf8();

    char *p = str.data();
    int len = str.size();
    //rgb/hsl color string has at least 7 characters
    if (!p || len > 255 || len <= 7)
        return QColor::fromString(p);
    else {
        bool isRgb(false), isHsl(false), hasAlpha(false);
        Q_UNUSED(isHsl);

        while (isspace(*p)) p++;
        if (strncmp(p, "rgb", 3) == 0)
            isRgb = true;
        else if (strncmp(p, "hsl", 3) == 0)
            isHsl = true;
        else
            return QColor::fromString(p);

        p+=3; //skip "rgb" or "hsl"
        hasAlpha = (*p == 'a') ? true : false;

        ++p; //skip "("

        if (hasAlpha) ++p; //skip "a"

        int rh, gs, bl, alpha = 255;

        //red
        while (isspace(*p)) p++;
        rh = strtol(p, &p, 10);
        if (*p == '%') {
            rh = qRound(rh/100.0 * 255);
            ++p;
        }
        if (*p++ != ',') return QColor();

        //green
        while (isspace(*p)) p++;
        gs = strtol(p, &p, 10);
        if (*p == '%') {
            gs = qRound(gs/100.0 * 255);
            ++p;
        }
        if (*p++ != ',') return QColor();

        //blue
        while (isspace(*p)) p++;
        bl = strtol(p, &p, 10);
        if (*p == '%') {
            bl = qRound(bl/100.0 * 255);
            ++p;
        }

        if (hasAlpha) {
            if (*p++!= ',') return QColor();
            while (isspace(*p)) p++;
            bool ok = false;
            alpha = qRound(qstrtod(p, const_cast<const char **>(&p), &ok) * 255);
        }

        if (*p != ')') return QColor();
        if (isRgb)
            return QColor::fromRgba(qRgba(qClamp(rh, 0, 255), qClamp(gs, 0, 255), qClamp(bl, 0, 255), qClamp(alpha, 0, 255)));
        else if (isHsl)
            return QColor::fromHsl(qClamp(rh, 0, 359), qClamp(gs, 0, 255), qClamp(bl, 0, 255), qClamp(alpha, 0, 255));
    }
    return QColor();
}

static int qParseFontSizeFromToken(QStringView fontSizeToken, bool &ok)
{
    ok = false;
    float size = fontSizeToken.trimmed().toFloat(&ok);
    if (ok) {
        return int(size);
    }
    qWarning().nospace() << "Context2D: A font size of " << fontSizeToken << " is invalid.";
    return 0;
}

/*
    Attempts to set the font size of \a font to \a fontSizeToken, returning
    \c true if successful. If the font size is invalid, \c false is returned
    and a warning is printed.
*/
static bool qSetFontSizeFromToken(QFont &font, QStringView fontSizeToken)
{
    const QStringView trimmedToken = fontSizeToken.trimmed();
    const QStringView unitStr = trimmedToken.right(2);
    const QStringView value = trimmedToken.left(trimmedToken.size() - 2);
    bool ok = false;
    int size = 0;
    if (unitStr == QLatin1String("px")) {
        size = qParseFontSizeFromToken(value, ok);
        if (ok) {
            font.setPixelSize(size);
            return true;
        }
    } else if (unitStr == QLatin1String("pt")) {
        size = qParseFontSizeFromToken(value, ok);
        if (ok) {
            font.setPointSize(size);
            return true;
        }
    } else {
        qWarning().nospace() << "Context2D: Invalid font size unit in font string.";
    }
    return false;
}

/*
    Returns a list of all of the families in \a fontFamiliesString, where
    each family is separated by spaces. Families with spaces in their name
    must be quoted.
*/
static QStringList qExtractFontFamiliesFromString(QStringView fontFamiliesString)
{
    QStringList extractedFamilies;
    int quoteIndex = -1;
    QString currentFamily;
    for (int index = 0; index < fontFamiliesString.size(); ++index) {
        const QChar ch = fontFamiliesString.at(index);
        if (ch == u'"' || ch == u'\'') {
            if (quoteIndex == -1) {
                quoteIndex = index;
            } else {
                if (ch == fontFamiliesString.at(quoteIndex)) {
                    // Found the matching quote. +1/-1 because we don't want the quote as part of the name.
                    const QString family = fontFamiliesString.mid(quoteIndex + 1, index - quoteIndex - 1).toString();
                    extractedFamilies.push_back(family);
                    currentFamily.clear();
                    quoteIndex = -1;
                } else {
                    qWarning().nospace() << "Context2D: Mismatched quote in font string.";
                    return QStringList();
                }
            }
        } else if (ch == u' ' && quoteIndex == -1) {
            // This is a space that's not within quotes...
            if (!currentFamily.isEmpty()) {
                // and there is a current family; consider it the end of the current family.
                extractedFamilies.push_back(currentFamily);
                currentFamily.clear();
            } // else: ignore the space
        } else {
            currentFamily.push_back(ch);
        }
    }
    if (!currentFamily.isEmpty()) {
        if (quoteIndex == -1) {
            // This is the end of the string, so add this family to our list.
            extractedFamilies.push_back(currentFamily);
        } else {
            qWarning().nospace() << "Context2D: Unclosed quote in font string.";
            return QStringList();
        }
    }
    if (extractedFamilies.isEmpty()) {
        qWarning().nospace() << "Context2D: Missing or misplaced font family in font string"
            << " (it must come after the font size).";
    }
    return extractedFamilies;
}

/*
    Tries to set a family on \a font using the families provided in \a fontFamilyTokens.

    The list is ordered by preference, with the first family having the highest preference.
    If the first family is invalid, the next family in the list is evaluated.
    This process is repeated until a valid font is found (at which point the function
    will return \c true and the family set on \a font) or there are no more
    families left, at which point a warning is printed and \c false is returned.
*/
static bool qSetFontFamilyFromTokens(QFont &font, const QStringList &fontFamilyTokens)
{
    for (const QString &fontFamilyToken : fontFamilyTokens) {
        if (QFontDatabase::hasFamily(fontFamilyToken)) {
            font.setFamily(fontFamilyToken);
            return true;
        } else {
            // Can't find a family matching this name; if it's a generic family,
            // try searching for the default family for it by using style hints.
            int styleHint = -1;
            if (fontFamilyToken.compare(QLatin1String("serif")) == 0) {
                styleHint = QFont::Serif;
            } else if (fontFamilyToken.compare(QLatin1String("sans-serif")) == 0) {
                styleHint = QFont::SansSerif;
            } else if (fontFamilyToken.compare(QLatin1String("cursive")) == 0) {
                styleHint = QFont::Cursive;
            } else if (fontFamilyToken.compare(QLatin1String("monospace")) == 0) {
                styleHint = QFont::Monospace;
            } else if (fontFamilyToken.compare(QLatin1String("fantasy")) == 0) {
                styleHint = QFont::Fantasy;
            }
            if (styleHint != -1) {
                QFont tmp;
                tmp.setStyleHint(static_cast<QFont::StyleHint>(styleHint));
                font.setFamily(tmp.defaultFamily());
                return true;
            }
        }
    }
    qWarning("Context2D: The font families specified are invalid: %s", qPrintable(fontFamilyTokens.join(QString()).trimmed()));
    return false;
}

enum FontToken
{
    NoTokens = 0x00,
    FontStyle = 0x01,
    FontVariant = 0x02,
    FontWeight = 0x04
};

#define Q_TRY_SET_TOKEN(token, value, setStatement) \
if (!(usedTokens & token)) { \
    usedTokens |= token; \
    setStatement; \
} else { \
    qWarning().nospace() << "Context2D: Duplicate token " << QLatin1String(value) << " found in font string."; \
    return currentFont; \
}

/*
    Parses a font string based on the CSS shorthand font property.

    See: http://www.w3.org/TR/css3-fonts/#font-prop
*/
static QFont qt_font_from_string(const QString& fontString, const QFont &currentFont) {
    if (fontString.isEmpty()) {
        qWarning().nospace() << "Context2D: Font string is empty.";
        return currentFont;
    }

    // We know that font-size must be specified and it must be before font-family
    // (which could potentially have "px" or "pt" in its name), so extract it now.
    int fontSizeEnd = fontString.indexOf(QLatin1String("px"));
    if (fontSizeEnd == -1)
        fontSizeEnd = fontString.indexOf(QLatin1String("pt"));
    if (fontSizeEnd == -1) {
        qWarning().nospace() << "Context2D: Invalid font size unit in font string.";
        return currentFont;
    }

    int fontSizeStart = fontString.lastIndexOf(u' ', fontSizeEnd);
    if (fontSizeStart == -1) {
        // The font size might be the first token in the font string, which is OK.
        // Regardless, we'll find out if the font is invalid with qSetFontSizeFromToken().
        fontSizeStart = 0;
    } else {
        // Don't want to take the leading space.
        ++fontSizeStart;
    }

    // + 2 for the unit, +1 for the space that we require.
    fontSizeEnd += 3;

    QFont newFont;
    if (!qSetFontSizeFromToken(newFont, QStringView{fontString}.mid(fontSizeStart, fontSizeEnd - fontSizeStart)))
        return currentFont;

    // We don't want to parse the size twice, so remove it now.
    QString remainingFontString = fontString;
    remainingFontString.remove(fontSizeStart, fontSizeEnd - fontSizeStart);
    QStringView remainingFontStringRef(remainingFontString);

    // Next, we have to take any font families out, as QString::split() will ruin quoted family names.
    const QStringView fontFamiliesString = remainingFontStringRef.mid(fontSizeStart);
    remainingFontStringRef.truncate(fontSizeStart);
    QStringList fontFamilies = qExtractFontFamiliesFromString(fontFamiliesString);
    if (fontFamilies.isEmpty()) {
        return currentFont;
    }
    if (!qSetFontFamilyFromTokens(newFont, fontFamilies))
        return currentFont;

    // Now that we've removed the messy parts, we can split the font string on spaces.
    const QStringView trimmedTokensStr = remainingFontStringRef.trimmed();
    if (trimmedTokensStr.isEmpty()) {
        // No optional properties.
        return newFont;
    }
    const auto tokens = trimmedTokensStr.split(QLatin1Char(' '));

    int usedTokens = NoTokens;
    // Optional properties can be in any order, but font-size and font-family must be last.
    for (const QStringView &token : tokens) {
        if (token.compare(QLatin1String("normal")) == 0) {
            if (!(usedTokens & FontStyle) || !(usedTokens & FontVariant) || !(usedTokens & FontWeight)) {
                // Could be font-style, font-variant or font-weight.
                if (!(usedTokens & FontStyle)) {
                    // QFont::StyleNormal is the default for QFont::style.
                    usedTokens = usedTokens | FontStyle;
                } else if (!(usedTokens & FontVariant)) {
                    // QFont::MixedCase is the default for QFont::capitalization.
                    usedTokens |= FontVariant;
                } else if (!(usedTokens & FontWeight)) {
                    // QFont::Normal is the default for QFont::weight.
                    usedTokens |= FontWeight;
                }
            } else {
                qWarning().nospace() << "Context2D: Duplicate token \"normal\" found in font string.";
                return currentFont;
            }
        } else if (token.compare(QLatin1String("bold")) == 0) {
            Q_TRY_SET_TOKEN(FontWeight, "bold", newFont.setBold(true))
        } else if (token.compare(QLatin1String("italic")) == 0) {
            Q_TRY_SET_TOKEN(FontStyle, "italic", newFont.setStyle(QFont::StyleItalic))
        } else if (token.compare(QLatin1String("oblique")) == 0) {
            Q_TRY_SET_TOKEN(FontStyle, "oblique", newFont.setStyle(QFont::StyleOblique))
        } else if (token.compare(QLatin1String("small-caps")) == 0) {
            Q_TRY_SET_TOKEN(FontVariant, "small-caps", newFont.setCapitalization(QFont::SmallCaps))
        } else {
            bool conversionOk = false;
            int weight = token.toInt(&conversionOk);
            if (conversionOk) {
                Q_TRY_SET_TOKEN(FontWeight, "<font-weight>",
                                newFont.setWeight(QFont::Weight(weight)))
            } else {
                // The token is invalid or in the wrong place/order in the font string.
                qWarning().nospace() << "Context2D: Invalid or misplaced token " << token
                                     << " found in font string.";
                return currentFont;
            }
        }
    }
    return newFont;
}

class QQuickContext2DEngineData : public QV4::ExecutionEngine::Deletable
{
public:
    QQuickContext2DEngineData(QV4::ExecutionEngine *engine);
    ~QQuickContext2DEngineData();

    QV4::PersistentValue contextPrototype;
    QV4::PersistentValue gradientProto;
    QV4::PersistentValue pixelArrayProto;
};

V4_DEFINE_EXTENSION(QQuickContext2DEngineData, engineData)

namespace QV4 {
namespace Heap {

struct QQuickJSContext2D : Object {
    void init()
    {
        Object::init();
        m_context = nullptr;
    }

    void destroy()
    {
        delete m_context;
        Object::destroy();
    }

    QQuickContext2D *context() { return m_context ? *m_context : nullptr; }
    void setContext(QQuickContext2D *context)
    {
        if (m_context)
            *m_context = context;
        else
            m_context = new QPointer<QQuickContext2D>(context);
    }

private:
    QPointer<QQuickContext2D>* m_context;
};

struct QQuickJSContext2DPrototype : Object {
    void init() { Object::init(); }
};

struct QQuickContext2DStyle : Object {
    void init()
    {
        brush = new QBrush;
        patternRepeatX = false;
        patternRepeatY = false;
    }
    void destroy() {
        delete brush;
        Object::destroy();
    }

    QBrush *brush;
    bool patternRepeatX:1;
    bool patternRepeatY:1;
};

struct QQuickJSContext2DPixelData : Object {
    void init();
    void destroy() {
        delete image;
        Object::destroy();
    }

    QImage *image;
};

struct QQuickJSContext2DImageData : Object {
    void init();

    static void markObjects(QV4::Heap::Base *that, QV4::MarkStack *markStack) {
        static_cast<QQuickJSContext2DImageData *>(that)->pixelData.mark(markStack);
        Object::markObjects(that, markStack);
    }

    QV4::Value pixelData;
};

}
}

struct QQuickJSContext2D : public QV4::Object
{
    V4_OBJECT2(QQuickJSContext2D, QV4::Object)
    V4_NEEDS_DESTROY

    static QV4::ReturnedValue method_get_globalAlpha(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
    static QV4::ReturnedValue method_set_globalAlpha(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
    static QV4::ReturnedValue method_get_globalCompositeOperation(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
    static QV4::ReturnedValue method_set_globalCompositeOperation(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
    static QV4::ReturnedValue method_get_fillStyle(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
    static QV4::ReturnedValue method_set_fillStyle(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
    static QV4::ReturnedValue method_get_fillRule(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
    static QV4::ReturnedValue method_set_fillRule(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
    static QV4::ReturnedValue method_get_strokeStyle(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
    static QV4::ReturnedValue method_set_strokeStyle(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);

    static QV4::ReturnedValue method_get_lineCap(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
    static QV4::ReturnedValue method_set_lineCap(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
    static QV4::ReturnedValue method_get_lineJoin(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
    static QV4::ReturnedValue method_set_lineJoin(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
    static QV4::ReturnedValue method_get_lineWidth(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
    static QV4::ReturnedValue method_set_lineWidth(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
    static QV4::ReturnedValue method_get_miterLimit(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
    static QV4::ReturnedValue method_set_miterLimit(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
    static QV4::ReturnedValue method_set_lineDashOffset(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
    static QV4::ReturnedValue method_get_lineDashOffset(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);

    static QV4::ReturnedValue method_get_shadowBlur(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
    static QV4::ReturnedValue method_set_shadowBlur(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
    static QV4::ReturnedValue method_get_shadowColor(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
    static QV4::ReturnedValue method_set_shadowColor(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
    static QV4::ReturnedValue method_get_shadowOffsetX(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
    static QV4::ReturnedValue method_set_shadowOffsetX(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
    static QV4::ReturnedValue method_get_shadowOffsetY(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
    static QV4::ReturnedValue method_set_shadowOffsetY(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);

    // should these two be on the proto?
#if QT_CONFIG(quick_path)
    static QV4::ReturnedValue method_get_path(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
    static QV4::ReturnedValue method_set_path(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
#endif
    static QV4::ReturnedValue method_get_font(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
    static QV4::ReturnedValue method_set_font(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
    static QV4::ReturnedValue method_get_textAlign(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
    static QV4::ReturnedValue method_set_textAlign(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
    static QV4::ReturnedValue method_get_textBaseline(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
    static QV4::ReturnedValue method_set_textBaseline(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
};

DEFINE_OBJECT_VTABLE(QQuickJSContext2D);


struct QQuickJSContext2DPrototype : public QV4::Object
{
    V4_OBJECT2(QQuickJSContext2DPrototype, QV4::Object)
public:
    static QV4::Heap::QQuickJSContext2DPrototype *create(QV4::ExecutionEngine *engine)
    {
        QV4::Scope scope(engine);
        QV4::Scoped<QQuickJSContext2DPrototype> o(scope, engine->memoryManager->allocate<QQuickJSContext2DPrototype>());

        o->defineDefaultProperty(QStringLiteral("quadraticCurveTo"), method_quadraticCurveTo, 0);
        o->defineDefaultProperty(QStringLiteral("restore"), method_restore, 0);
        o->defineDefaultProperty(QStringLiteral("moveTo"), method_moveTo, 0);
        o->defineDefaultProperty(QStringLiteral("lineTo"), method_lineTo, 0);
        o->defineDefaultProperty(QStringLiteral("caretBlinkRate"), method_caretBlinkRate, 0);
        o->defineDefaultProperty(QStringLiteral("clip"), method_clip, 0);
        o->defineDefaultProperty(QStringLiteral("setTransform"), method_setTransform, 0);
        o->defineDefaultProperty(QStringLiteral("text"), method_text, 0);
        o->defineDefaultProperty(QStringLiteral("roundedRect"), method_roundedRect, 0);
        o->defineDefaultProperty(QStringLiteral("createPattern"), method_createPattern, 0);
        o->defineDefaultProperty(QStringLiteral("stroke"), method_stroke, 0);
        o->defineDefaultProperty(QStringLiteral("arc"), method_arc, 0);
        o->defineDefaultProperty(QStringLiteral("createImageData"), method_createImageData, 0);
        o->defineDefaultProperty(QStringLiteral("measureText"), method_measureText, 0);
        o->defineDefaultProperty(QStringLiteral("ellipse"), method_ellipse, 0);
        o->defineDefaultProperty(QStringLiteral("fill"), method_fill, 0);
        o->defineDefaultProperty(QStringLiteral("save"), method_save, 0);
        o->defineDefaultProperty(QStringLiteral("scale"), method_scale, 0);
        o->defineDefaultProperty(QStringLiteral("drawImage"), method_drawImage, 0);
        o->defineDefaultProperty(QStringLiteral("transform"), method_transform, 0);
        o->defineDefaultProperty(QStringLiteral("fillText"), method_fillText, 0);
        o->defineDefaultProperty(QStringLiteral("strokeText"), method_strokeText, 0);
        o->defineDefaultProperty(QStringLiteral("translate"), method_translate, 0);
        o->defineDefaultProperty(QStringLiteral("createRadialGradient"), method_createRadialGradient, 0);
        o->defineDefaultProperty(QStringLiteral("shear"), method_shear, 0);
        o->defineDefaultProperty(QStringLiteral("isPointInPath"), method_isPointInPath, 0);
        o->defineDefaultProperty(QStringLiteral("bezierCurveTo"), method_bezierCurveTo, 0);
        o->defineDefaultProperty(QStringLiteral("resetTransform"), method_resetTransform, 0);
        o->defineDefaultProperty(QStringLiteral("arcTo"), method_arcTo, 0);
        o->defineDefaultProperty(QStringLiteral("fillRect"), method_fillRect, 0);
        o->defineDefaultProperty(QStringLiteral("createConicalGradient"), method_createConicalGradient, 0);
        o->defineDefaultProperty(QStringLiteral("drawFocusRing"), method_drawFocusRing, 0);
        o->defineDefaultProperty(QStringLiteral("beginPath"), method_beginPath, 0);
        o->defineDefaultProperty(QStringLiteral("clearRect"), method_clearRect, 0);
        o->defineDefaultProperty(QStringLiteral("rect"), method_rect, 0);
        o->defineDefaultProperty(QStringLiteral("reset"), method_reset, 0);
        o->defineDefaultProperty(QStringLiteral("rotate"), method_rotate, 0);
        o->defineDefaultProperty(QStringLiteral("setCaretSelectionRect"), method_setCaretSelectionRect, 0);
        o->defineDefaultProperty(QStringLiteral("putImageData"), method_putImageData, 0);
        o->defineDefaultProperty(QStringLiteral("getImageData"), method_getImageData, 0);
        o->defineDefaultProperty(QStringLiteral("createLinearGradient"), method_createLinearGradient, 0);
        o->defineDefaultProperty(QStringLiteral("strokeRect"), method_strokeRect, 0);
        o->defineDefaultProperty(QStringLiteral("closePath"), method_closePath, 0);
        o->defineDefaultProperty(QStringLiteral("setLineDash"), method_setLineDash, 0);
        o->defineDefaultProperty(QStringLiteral("getLineDash"), method_getLineDash, 0);
        o->defineAccessorProperty(QStringLiteral("canvas"), QQuickJSContext2DPrototype::method_get_canvas, nullptr);

        return o->d();
    }

    static QV4::ReturnedValue method_get_canvas(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
    static QV4::ReturnedValue method_restore(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
    static QV4::ReturnedValue method_reset(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
    static QV4::ReturnedValue method_save(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
    static QV4::ReturnedValue method_rotate(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
    static QV4::ReturnedValue method_scale(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
    static QV4::ReturnedValue method_translate(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
    static QV4::ReturnedValue method_setTransform(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
    static QV4::ReturnedValue method_transform(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
    static QV4::ReturnedValue method_resetTransform(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
    static QV4::ReturnedValue method_shear(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
    static QV4::ReturnedValue method_createLinearGradient(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
    static QV4::ReturnedValue method_createRadialGradient(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
    static QV4::ReturnedValue method_createConicalGradient(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
    static QV4::ReturnedValue method_createPattern(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
    static QV4::ReturnedValue method_clearRect(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
    static QV4::ReturnedValue method_fillRect(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
    static QV4::ReturnedValue method_strokeRect(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
    static QV4::ReturnedValue method_arc(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
    static QV4::ReturnedValue method_arcTo(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
    static QV4::ReturnedValue method_beginPath(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
    static QV4::ReturnedValue method_bezierCurveTo(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
    static QV4::ReturnedValue method_clip(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
    static QV4::ReturnedValue method_closePath(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
    static QV4::ReturnedValue method_fill(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
    static QV4::ReturnedValue method_lineTo(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
    static QV4::ReturnedValue method_moveTo(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
    static QV4::ReturnedValue method_quadraticCurveTo(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
    static QV4::ReturnedValue method_rect(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
    static QV4::ReturnedValue method_roundedRect(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
    static QV4::ReturnedValue method_ellipse(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
    static QV4::ReturnedValue method_text(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
    static QV4::ReturnedValue method_stroke(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
    static QV4::ReturnedValue method_isPointInPath(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
    static QV4::ReturnedValue method_drawFocusRing(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
    static QV4::ReturnedValue method_setCaretSelectionRect(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
    static QV4::ReturnedValue method_caretBlinkRate(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
    static QV4::ReturnedValue method_fillText(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
    static QV4::ReturnedValue method_strokeText(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
    static QV4::ReturnedValue method_measureText(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
    static QV4::ReturnedValue method_drawImage(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
    static QV4::ReturnedValue method_createImageData(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
    static QV4::ReturnedValue method_getImageData(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
    static QV4::ReturnedValue method_putImageData(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
    static QV4::ReturnedValue method_setLineDash(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
    static QV4::ReturnedValue method_getLineDash(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);

};

DEFINE_OBJECT_VTABLE(QQuickJSContext2DPrototype);


struct QQuickContext2DStyle : public QV4::Object
{
    V4_OBJECT2(QQuickContext2DStyle, QV4::Object)
    V4_NEEDS_DESTROY

    static QV4::ReturnedValue gradient_proto_addColorStop(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
};



DEFINE_OBJECT_VTABLE(QQuickContext2DStyle);

QImage qt_image_convolute_filter(const QImage& src, const QVector<qreal>& weights, int radius = 0)
{
    // weights 3x3 => delta 1
    int delta = radius ? radius : qFloor(qSqrt(weights.size()) / qreal(2));
    int filterDim = 2 * delta + 1;

    QImage dst = QImage(src.size(), src.format());

    int w = src.width();
    int h = src.height();

    const QRgb *sr = (const QRgb *)(src.constBits());
    int srcStride = src.bytesPerLine() / 4;

    QRgb *dr = (QRgb*)dst.bits();
    int dstStride = dst.bytesPerLine() / 4;

    for (int y = 0; y < h; ++y) {
        for (int x = 0; x < w; ++x) {
            int red = 0;
            int green = 0;
            int blue = 0;
            int alpha = 0;

            qreal redF = 0;
            qreal greenF = 0;
            qreal blueF = 0;
            qreal alphaF = 0;

            int sy = y;
            int sx = x;

            for (int cy = 0; cy < filterDim; ++cy) {
                int scy = sy + cy - delta;

                if (scy < 0 || scy >= h)
                    continue;

                const QRgb *sry = sr + scy * srcStride;

                for (int cx = 0; cx < filterDim; ++cx) {
                    int scx = sx + cx - delta;

                    if (scx < 0 || scx >= w)
                        continue;

                    const QRgb col = sry[scx];

                    if (radius) {
                        red += qRed(col);
                        green += qGreen(col);
                        blue += qBlue(col);
                        alpha += qAlpha(col);
                    } else {
                        qreal wt = weights[cy * filterDim + cx];

                        redF += qRed(col) * wt;
                        greenF += qGreen(col) * wt;
                        blueF += qBlue(col) * wt;
                        alphaF += qAlpha(col) * wt;
                    }
                }
            }

            if (radius)
                dr[x] = qRgba(qRound(red * weights[0]), qRound(green * weights[0]), qRound(blue * weights[0]), qRound(alpha * weights[0]));
            else
                dr[x] = qRgba(qRound(redF), qRound(greenF), qRound(blueF), qRound(alphaF));
        }

        dr += dstStride;
    }

    return dst;
}

void qt_image_boxblur(QImage& image, int radius, bool quality)
{
    int passes = quality? 3: 1;
    int filterSize = 2 * radius + 1;
    for (int i = 0; i < passes; ++i)
        image = qt_image_convolute_filter(image, QVector<qreal>() << 1.0 / (filterSize * filterSize), radius);
}

static QPainter::CompositionMode qt_composite_mode_from_string(const QString &compositeOperator)
{
    if (compositeOperator == QLatin1String("source-over")) {
        return QPainter::CompositionMode_SourceOver;
    } else if (compositeOperator == QLatin1String("source-out")) {
        return QPainter::CompositionMode_SourceOut;
    } else if (compositeOperator == QLatin1String("source-in")) {
        return QPainter::CompositionMode_SourceIn;
    } else if (compositeOperator == QLatin1String("source-atop")) {
        return QPainter::CompositionMode_SourceAtop;
    } else if (compositeOperator == QLatin1String("destination-atop")) {
        return QPainter::CompositionMode_DestinationAtop;
    } else if (compositeOperator == QLatin1String("destination-in")) {
        return QPainter::CompositionMode_DestinationIn;
    } else if (compositeOperator == QLatin1String("destination-out")) {
        return QPainter::CompositionMode_DestinationOut;
    } else if (compositeOperator == QLatin1String("destination-over")) {
        return QPainter::CompositionMode_DestinationOver;
    } else if (compositeOperator == QLatin1String("lighter")) {
        return QPainter::CompositionMode_Plus;
    } else if (compositeOperator == QLatin1String("copy")) {
        return QPainter::CompositionMode_Source;
    } else if (compositeOperator == QLatin1String("xor")) {
        return QPainter::CompositionMode_Xor;
    } else if (compositeOperator == QLatin1String("qt-clear")) {
        return QPainter::CompositionMode_Clear;
    } else if (compositeOperator == QLatin1String("qt-destination")) {
        return QPainter::CompositionMode_Destination;
    } else if (compositeOperator == QLatin1String("qt-multiply")) {
        return QPainter::CompositionMode_Multiply;
    } else if (compositeOperator == QLatin1String("qt-screen")) {
        return QPainter::CompositionMode_Screen;
    } else if (compositeOperator == QLatin1String("qt-overlay")) {
        return QPainter::CompositionMode_Overlay;
    } else if (compositeOperator == QLatin1String("qt-darken")) {
        return QPainter::CompositionMode_Darken;
    } else if (compositeOperator == QLatin1String("qt-lighten")) {
        return QPainter::CompositionMode_Lighten;
    } else if (compositeOperator == QLatin1String("qt-color-dodge")) {
        return QPainter::CompositionMode_ColorDodge;
    } else if (compositeOperator == QLatin1String("qt-color-burn")) {
        return QPainter::CompositionMode_ColorBurn;
    } else if (compositeOperator == QLatin1String("qt-hard-light")) {
        return QPainter::CompositionMode_HardLight;
    } else if (compositeOperator == QLatin1String("qt-soft-light")) {
        return QPainter::CompositionMode_SoftLight;
    } else if (compositeOperator == QLatin1String("qt-difference")) {
        return QPainter::CompositionMode_Difference;
    } else if (compositeOperator == QLatin1String("qt-exclusion")) {
        return QPainter::CompositionMode_Exclusion;
    }
    return QPainter::CompositionMode_SourceOver;
}

static QString qt_composite_mode_to_string(QPainter::CompositionMode op)
{
    switch (op) {
    case QPainter::CompositionMode_SourceOver:
        return QStringLiteral("source-over");
    case QPainter::CompositionMode_DestinationOver:
        return QStringLiteral("destination-over");
    case QPainter::CompositionMode_Clear:
        return QStringLiteral("qt-clear");
    case QPainter::CompositionMode_Source:
        return QStringLiteral("copy");
    case QPainter::CompositionMode_Destination:
        return QStringLiteral("qt-destination");
    case QPainter::CompositionMode_SourceIn:
        return QStringLiteral("source-in");
    case QPainter::CompositionMode_DestinationIn:
        return QStringLiteral("destination-in");
    case QPainter::CompositionMode_SourceOut:
        return QStringLiteral("source-out");
    case QPainter::CompositionMode_DestinationOut:
        return QStringLiteral("destination-out");
    case QPainter::CompositionMode_SourceAtop:
        return QStringLiteral("source-atop");
    case QPainter::CompositionMode_DestinationAtop:
        return QStringLiteral("destination-atop");
    case QPainter::CompositionMode_Xor:
        return QStringLiteral("xor");
    case QPainter::CompositionMode_Plus:
        return QStringLiteral("lighter");
    case QPainter::CompositionMode_Multiply:
        return QStringLiteral("qt-multiply");
    case QPainter::CompositionMode_Screen:
        return QStringLiteral("qt-screen");
    case QPainter::CompositionMode_Overlay:
        return QStringLiteral("qt-overlay");
    case QPainter::CompositionMode_Darken:
        return QStringLiteral("qt-darken");
    case QPainter::CompositionMode_Lighten:
        return QStringLiteral("lighter");
    case QPainter::CompositionMode_ColorDodge:
        return QStringLiteral("qt-color-dodge");
    case QPainter::CompositionMode_ColorBurn:
        return QStringLiteral("qt-color-burn");
    case QPainter::CompositionMode_HardLight:
        return QStringLiteral("qt-hard-light");
    case QPainter::CompositionMode_SoftLight:
        return QStringLiteral("qt-soft-light");
    case QPainter::CompositionMode_Difference:
        return QStringLiteral("qt-difference");
    case QPainter::CompositionMode_Exclusion:
        return QStringLiteral("qt-exclusion");
    default:
        break;
    }
    return QString();
}

struct QQuickJSContext2DPixelData : public QV4::Object
{
    V4_OBJECT2(QQuickJSContext2DPixelData, QV4::Object)
    V4_NEEDS_DESTROY

    static QV4::ReturnedValue virtualGet(const QV4::Managed *m, QV4::PropertyKey id, const QV4::Value *receiver, bool *hasProperty);
    static bool virtualPut(QV4::Managed *m, QV4::PropertyKey  id, const QV4::Value &value, Value *receiver);

    static QV4::ReturnedValue proto_get_length(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
};

void QV4::Heap::QQuickJSContext2DPixelData::init()
{
    Object::init();
    image = new QImage;
    QV4::Scope scope(internalClass->engine);
    QV4::ScopedObject o(scope, this);
    o->setArrayType(QV4::Heap::ArrayData::Custom);
}

DEFINE_OBJECT_VTABLE(QQuickJSContext2DPixelData);

struct QQuickJSContext2DImageData : public QV4::Object
{
    V4_OBJECT2(QQuickJSContext2DImageData, QV4::Object)

    static QV4::ReturnedValue method_get_width(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
    static QV4::ReturnedValue method_get_height(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);
    static QV4::ReturnedValue method_get_data(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc);

};

void QV4::Heap::QQuickJSContext2DImageData::init()
{
    Object::init();
    pixelData = QV4::Value::undefinedValue();

    QV4::Scope scope(internalClass->engine);
    QV4::ScopedObject o(scope, this);

    o->defineAccessorProperty(QStringLiteral("width"), ::QQuickJSContext2DImageData::method_get_width, nullptr);
    o->defineAccessorProperty(QStringLiteral("height"), ::QQuickJSContext2DImageData::method_get_height, nullptr);
    o->defineAccessorProperty(QStringLiteral("data"), ::QQuickJSContext2DImageData::method_get_data, nullptr);
}

DEFINE_OBJECT_VTABLE(QQuickJSContext2DImageData);

static QV4::ReturnedValue qt_create_image_data(qreal w, qreal h, QV4::ExecutionEngine *v4, QImage&& image)
{
    QV4::Scope scope(v4);
    QQuickContext2DEngineData *ed = engineData(scope.engine);
    QV4::Scoped<QQuickJSContext2DPixelData> pixelData(scope, scope.engine->memoryManager->allocate<QQuickJSContext2DPixelData>());
    QV4::ScopedObject p(scope, ed->pixelArrayProto.value());
    pixelData->setPrototypeOf(p);

    if (image.isNull()) {
        *pixelData->d()->image = QImage(qRound(w), qRound(h), QImage::Format_ARGB32);
        pixelData->d()->image->fill(0x00000000);
    } else {
        // After qtbase 88e56d0932a3615231adf40d5ae033e742d72c33, the image size can be off by one.
        Q_ASSERT(qAbs(image.width() - qRound(w * image.devicePixelRatio())) <= 1 && qAbs(image.height() - qRound(h * image.devicePixelRatio())) <= 1);
        *pixelData->d()->image = image.format() == QImage::Format_ARGB32 ? std::move(image) : std::move(image).convertToFormat(QImage::Format_ARGB32);
    }

    QV4::Scoped<QQuickJSContext2DImageData> imageData(scope, scope.engine->memoryManager->allocate<QQuickJSContext2DImageData>());
    imageData->d()->pixelData = pixelData.asReturnedValue();
    return imageData.asReturnedValue();
}

//static script functions

/*!
    \qmlproperty QtQuick::Canvas QtQuick::Context2D::canvas
     Holds the canvas item that the context paints on.

     This property is read only.
*/
QV4::ReturnedValue QQuickJSContext2DPrototype::method_get_canvas(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int)
{
    QV4::Scope scope(b);
    QV4::Scoped<QQuickJSContext2D> r(scope, thisObject->as<QQuickJSContext2D>());
    CHECK_CONTEXT(r)

    RETURN_RESULT(QV4::QObjectWrapper::wrap(scope.engine, r->d()->context()->canvas()));
}

/*!
    \qmlmethod object QtQuick::Context2D::restore()
    Pops the top state on the stack, restoring the context to that state.

    \sa save()
*/
QV4::ReturnedValue QQuickJSContext2DPrototype::method_restore(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int)
{
    QV4::Scope scope(b);
    QV4::Scoped<QQuickJSContext2D> r(scope, thisObject->as<QQuickJSContext2D>());
    CHECK_CONTEXT(r)

    r->d()->context()->popState();
    RETURN_RESULT(thisObject->asReturnedValue());
}

/*!
    \qmlmethod object QtQuick::Context2D::reset()
    Resets the context state and properties to the default values.
*/
QV4::ReturnedValue QQuickJSContext2DPrototype::method_reset(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int)
{
    QV4::Scope scope(b);
    QV4::Scoped<QQuickJSContext2D> r(scope, thisObject->as<QQuickJSContext2D>());
    CHECK_CONTEXT(r)

    r->d()->context()->reset();

    RETURN_RESULT(thisObject->asReturnedValue());
}

/*!
    \qmlmethod object QtQuick::Context2D::save()
    Pushes the current state onto the state stack.

    Before changing any state attributes, you should save the current state
    for future reference. The context maintains a stack of drawing states.
    Each state consists of the current transformation matrix, clipping region,
    and values of the following attributes:
    \list
    \li strokeStyle
    \li fillStyle
    \li fillRule
    \li globalAlpha
    \li lineWidth
    \li lineCap
    \li lineJoin
    \li miterLimit
    \li shadowOffsetX
    \li shadowOffsetY
    \li shadowBlur
    \li shadowColor
    \li globalCompositeOperation
    \li \l font
    \li textAlign
    \li textBaseline
    \endlist

    The current path is NOT part of the drawing state. The path can be reset by
    invoking the beginPath() method.
*/
QV4::ReturnedValue QQuickJSContext2DPrototype::method_save(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int)
{
    QV4::Scope scope(b);
    QV4::Scoped<QQuickJSContext2D> r(scope, thisObject->as<QQuickJSContext2D>());
    CHECK_CONTEXT(r)

    r->d()->context()->pushState();

    RETURN_RESULT(*thisObject);
}

// transformations
/*!
    \qmlmethod object QtQuick::Context2D::rotate(real angle)
    Rotate the canvas around the current origin by \a angle in radians and clockwise direction.

    \code
    ctx.rotate(Math.PI/2);
    \endcode

    \image qml-item-canvas-rotate.png

    The rotation transformation matrix is as follows:

    \image qml-item-canvas-math-rotate.png

    where the \a angle of rotation is in radians.

*/
QV4::ReturnedValue QQuickJSContext2DPrototype::method_rotate(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc)
{
    QV4::Scope scope(b);
    QV4::Scoped<QQuickJSContext2D> r(scope, thisObject->as<QQuickJSContext2D>());
    CHECK_CONTEXT(r)

    if (argc >= 1)
        r->d()->context()->rotate(argv[0].toNumber());
    RETURN_RESULT(*thisObject);
}

/*!
    \qmlmethod object QtQuick::Context2D::scale(real x, real y)

    Increases or decreases the size of each unit in the canvas grid by multiplying the scale factors
    to the current tranform matrix.
    \a x is the scale factor in the horizontal direction and \a y is the scale factor in the
    vertical direction.

    The following code doubles the horizontal size of an object drawn on the canvas and halves its
    vertical size:

    \code
    ctx.scale(2.0, 0.5);
    \endcode

    \image qml-item-canvas-scale.png
*/
QV4::ReturnedValue QQuickJSContext2DPrototype::method_scale(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc)
{
    QV4::Scope scope(b);
    QV4::Scoped<QQuickJSContext2D> r(scope, thisObject->as<QQuickJSContext2D>());
    CHECK_CONTEXT(r)


    if (argc >= 2)
        r->d()->context()->scale(argv[0].toNumber(), argv[1].toNumber());
    RETURN_RESULT(*thisObject);

}

/*!
    \qmlmethod object QtQuick::Context2D::setTransform(real a, real b, real c, real d, real e, real f)

    Changes the transformation matrix to the matrix given by the arguments as described below.

    Modifying the transformation matrix directly enables you to perform scaling,
    rotating, and translating transformations in a single step.

    Each point on the canvas is multiplied by the matrix before anything is
    drawn. The \l{http://www.w3.org/TR/2dcontext/#transformations}{HTML Canvas 2D Context specification}
    defines the transformation matrix as:

    \image qml-item-canvas-math.png
    where:
    \list
    \li \a{a} is the scale factor in the horizontal (x) direction
    \image qml-item-canvas-scalex.png
    \li \a{c} is the skew factor in the x direction
    \image qml-item-canvas-skewx.png
    \li \a{e} is the translation in the x direction
    \image qml-item-canvas-translate.png
    \li \a{b} is the skew factor in the y (vertical) direction
    \image qml-item-canvas-skewy.png
    \li \a{d} is the scale factor in the y direction
    \image qml-item-canvas-scaley.png
    \li \a{f} is the translation in the y direction
    \image qml-item-canvas-translatey.png
    \li the last row remains constant
    \endlist

    The scale factors and skew factors are multiples; \a{e} and \a{f} are
    coordinate space units, just like the units in the translate(x,y)
    method.

    \sa transform()
*/
QV4::ReturnedValue QQuickJSContext2DPrototype::method_setTransform(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc)
{
    QV4::Scope scope(b);
    QV4::Scoped<QQuickJSContext2D> r(scope, thisObject->as<QQuickJSContext2D>());
    CHECK_CONTEXT(r)


    if (argc >= 6)
        r->d()->context()->setTransform( argv[0].toNumber()
                                                        , argv[1].toNumber()
                                                        , argv[2].toNumber()
                                                        , argv[3].toNumber()
                                                        , argv[4].toNumber()
                                                        , argv[5].toNumber());

    RETURN_RESULT(*thisObject);

}

/*!
    \qmlmethod object QtQuick::Context2D::transform(real a, real b, real c, real d, real e, real f)

    This method is very similar to setTransform(), but instead of replacing
    the old transform matrix, this method applies the given tranform matrix
    to the current matrix by multiplying to it.

    The setTransform(\a a, \a b, \a c, \a d, \a e, \a f) method actually
    resets the current transform to the identity matrix, and then invokes
    the transform(\a a, \a b, \a c, \a d, \a e, \a f) method with the same
    arguments.

    \sa setTransform()
*/
QV4::ReturnedValue QQuickJSContext2DPrototype::method_transform(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc)
{
    QV4::Scope scope(b);
    QV4::Scoped<QQuickJSContext2D> r(scope, thisObject->as<QQuickJSContext2D>());
    CHECK_CONTEXT(r)

    if (argc >= 6)
        r->d()->context()->transform( argv[0].toNumber()
                                                  , argv[1].toNumber()
                                                  , argv[2].toNumber()
                                                  , argv[3].toNumber()
                                                  , argv[4].toNumber()
                                                  , argv[5].toNumber());

    RETURN_RESULT(*thisObject);

}

/*!
    \qmlmethod object QtQuick::Context2D::translate(real x, real y)

    Translates the origin of the canvas by a horizontal distance of \a x,
    and a vertical distance of \a y, in coordinate space units.

    Translating the origin enables you to draw patterns of different objects on the canvas
    without having to measure the coordinates manually for each shape.
*/
QV4::ReturnedValue QQuickJSContext2DPrototype::method_translate(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc)
{
    QV4::Scope scope(b);
    QV4::Scoped<QQuickJSContext2D> r(scope, thisObject->as<QQuickJSContext2D>());
    CHECK_CONTEXT(r)

    if (argc >= 2)
            r->d()->context()->translate(argv[0].toNumber(), argv[1].toNumber());
    RETURN_RESULT(*thisObject);

}


/*!
    \qmlmethod object QtQuick::Context2D::resetTransform()

    Reset the transformation matrix to the default value (equivalent to calling
    setTransform(\c 1, \c 0, \c 0, \c 1, \c 0, \c 0)).

    \sa transform(), setTransform(), reset()
*/
QV4::ReturnedValue QQuickJSContext2DPrototype::method_resetTransform(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int)
{
    QV4::Scope scope(b);
    QV4::Scoped<QQuickJSContext2D> r(scope, thisObject->as<QQuickJSContext2D>());
    CHECK_CONTEXT(r)

    r->d()->context()->setTransform(1, 0, 0, 1, 0, 0);

    RETURN_RESULT(*thisObject);

}


/*!
    \qmlmethod object QtQuick::Context2D::shear(real sh, real sv)

    Shears the transformation matrix by \a sh in the horizontal direction and
    \a sv in the vertical direction.
*/
QV4::ReturnedValue QQuickJSContext2DPrototype::method_shear(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc)
{
    QV4::Scope scope(b);
    QV4::Scoped<QQuickJSContext2D> r(scope, thisObject->as<QQuickJSContext2D>());
    CHECK_CONTEXT(r)

    if (argc >= 2)
            r->d()->context()->shear(argv[0].toNumber(), argv[1].toNumber());

    RETURN_RESULT(*thisObject);

}
// compositing

/*!
    \qmlproperty real QtQuick::Context2D::globalAlpha

     Holds the current alpha value applied to rendering operations.
     The value must be in the range from \c 0.0 (fully transparent) to \c 1.0 (fully opaque).
     The default value is \c 1.0.
*/
QV4::ReturnedValue QQuickJSContext2D::method_get_globalAlpha(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int)
{
    QV4::Scope scope(b);
    QV4::Scoped<QQuickJSContext2D> r(scope, thisObject->as<QQuickJSContext2D>());
    CHECK_CONTEXT(r)

    RETURN_RESULT(QV4::Encode(r->d()->context()->state.globalAlpha));
}

QV4::ReturnedValue QQuickJSContext2D::method_set_globalAlpha(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc)
{
    QV4::Scope scope(b);
    QV4::Scoped<QQuickJSContext2D> r(scope, thisObject->as<QQuickJSContext2D>());
    CHECK_CONTEXT_SETTER(r)

    double globalAlpha = argc ? argv[0].toNumber() : qt_qnan();


    if (!qt_is_finite(globalAlpha))
        RETURN_UNDEFINED();

    if (globalAlpha >= 0.0 && globalAlpha <= 1.0 && r->d()->context()->state.globalAlpha != globalAlpha) {
        r->d()->context()->state.globalAlpha = globalAlpha;
        r->d()->context()->buffer()->setGlobalAlpha(r->d()->context()->state.globalAlpha);
    }
    RETURN_UNDEFINED();
}

/*!
    \qmlproperty string QtQuick::Context2D::globalCompositeOperation
    Holds the current the current composition operation. Allowed operations are:

    \value "source-atop"
        QPainter::CompositionMode_SourceAtop
        A atop B. Display the source image wherever both images are opaque.
        Display the destination image wherever the destination image is opaque
        but the source image is transparent. Display transparency elsewhere.
    \value "source-in"
        QPainter::CompositionMode_SourceIn
        A in B. Display the source image wherever both the source image and
        destination image are opaque. Display transparency elsewhere.
    \value "source-out"
        QPainter::CompositionMode_SourceOut
        A out B. Display the source image wherever the source image is opaque
        and the destination image is transparent. Display transparency elsewhere.
    \value "source-over"
        QPainter::CompositionMode_SourceOver (default)
        A over B. Display the source image wherever the source image is opaque.
        Display the destination image elsewhere.
    \value "destination-atop"
        QPainter::CompositionMode_DestinationAtop
        B atop A. Same as \c source-atop but using the destination image instead
        of the source image and vice versa.
    \value "destination-in"
        QPainter::CompositionMode_DestinationIn
        B in A. Same as \c source-in but using the destination image instead of
        the source image and vice versa.
    \value "destination-out"
        QPainter::CompositionMode_DestinationOut
        B out A. Same as \c source-out but using the destination image instead
        of the source image and vice versa.
    \value "destination-over"
        QPainter::CompositionMode_DestinationOver
        B over A. Same as \c source-over but using the destination image
        instead of the source image and vice versa.
    \value "lighter"
        QPainter::CompositionMode_Plus
        A plus B. Display the sum of the source image and destination image,
        with color values approaching \c 255 (100%) as a limit.
    \value "copy"
        QPainter::CompositionMode_Source
        A (B is ignored). Display the source image instead of the destination image.
    \value "xor"
        QPainter::CompositionMode_Xor
        A xor B. Exclusive OR of the source image and destination image.
    \value "qt-clear"
        QPainter::CompositionMode_Clear
    \value "qt-destination"
        QPainter::CompositionMode_Destination
    \value "qt-multiply"
        QPainter::CompositionMode_Multiply
    \value "qt-screen"
        QPainter::CompositionMode_Screen
    \value "qt-overlay"
        QPainter::CompositionMode_Overlay
    \value "qt-darken"
        QPainter::CompositionMode_Darken
    \value "qt-lighten"
        QPainter::CompositionMode_Lighten
    \value "qt-color-dodge"
        QPainter::CompositionMode_ColorDodge
    \value "qt-color-burn"
        QPainter::CompositionMode_ColorBurn
    \value "qt-hard-light"
        QPainter::CompositionMode_HardLight
    \value "qt-soft-light"
        QPainter::CompositionMode_SoftLight
    \value "qt-difference"
        QPainter::CompositionMode_Difference
    \value "qt-exclusion"
        QPainter::CompositionMode_Exclusion

    In compliance with the W3C standard, the extended composition modes beyond
    the required modes are provided as "vendorName-operationName" syntax, for
    example: QPainter::CompositionMode_Exclusion is provided as "qt-exclusion".
*/
QV4::ReturnedValue QQuickJSContext2D::method_get_globalCompositeOperation(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int)
{
    QV4::Scope scope(b);
    QV4::Scoped<QQuickJSContext2D> r(scope, thisObject->as<QQuickJSContext2D>());
    CHECK_CONTEXT(r)

    RETURN_RESULT(scope.engine->newString(qt_composite_mode_to_string(r->d()->context()->state.globalCompositeOperation)));
}

QV4::ReturnedValue QQuickJSContext2D::method_set_globalCompositeOperation(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc)
{
    QV4::Scope scope(b);
    QV4::Scoped<QQuickJSContext2D> r(scope, thisObject->as<QQuickJSContext2D>());
    CHECK_CONTEXT_SETTER(r)

    if (!argc)
        THROW_TYPE_ERROR();

    QString mode = argv[0].toQString();
    QPainter::CompositionMode cm = qt_composite_mode_from_string(mode);
    if (cm == QPainter::CompositionMode_SourceOver && mode != QLatin1String("source-over"))
        RETURN_UNDEFINED();

    if (cm != r->d()->context()->state.globalCompositeOperation) {
        r->d()->context()->state.globalCompositeOperation = cm;
        r->d()->context()->buffer()->setGlobalCompositeOperation(cm);
    }

    RETURN_UNDEFINED();
}

// colors and styles
/*!
    \qmlproperty variant QtQuick::Context2D::fillStyle
     Holds the current style used for filling shapes.
     The style can be either a string containing a CSS color, a CanvasGradient or CanvasPattern object. Invalid values are ignored.
     This property accepts several color syntaxes:
     \list
     \li 'rgb(red, green, blue)' - for example: 'rgb(255, 100, 55)' or 'rgb(100%, 70%, 30%)'
     \li 'rgba(red, green, blue, alpha)' - for example: 'rgb(255, 100, 55, 1.0)' or 'rgb(100%, 70%, 30%, 0.5)'
     \li 'hsl(hue, saturation, lightness)'
     \li 'hsla(hue, saturation, lightness, alpha)'
     \li '#RRGGBB' - for example: '#00FFCC'
     \li Qt.rgba(red, green, blue, alpha) - for example: Qt.rgba(0.3, 0.7, 1, 1.0)
     \endlist
     If the \c fillStyle or \l strokeStyle is assigned many times in a loop, the last Qt.rgba() syntax should be chosen, as it has the
     best performance, because it's already a valid QColor value, does not need to be parsed everytime.

     The default value is  '#000000'.
     \sa createLinearGradient()
     \sa createRadialGradient()
     \sa createPattern()
     \sa strokeStyle
 */
QV4::ReturnedValue QQuickJSContext2D::method_get_fillStyle(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int)
{
    QV4::Scope scope(b);
    QV4::Scoped<QQuickJSContext2D> r(scope, thisObject->as<QQuickJSContext2D>());
    CHECK_CONTEXT(r)

    const QColor color = r->d()->context()->state.fillStyle.color().toRgb();
    if (color.isValid()) {
        if (color.alpha() == 255)
            RETURN_RESULT(scope.engine->newString(color.name()));
        QString alphaString = QString::number(color.alphaF(), 'f');
        while (alphaString.endsWith(QLatin1Char('0')))
            alphaString.chop(1);
        if (alphaString.endsWith(QLatin1Char('.')))
            alphaString += QLatin1Char('0');
        QString str = QString::fromLatin1("rgba(%1, %2, %3, %4)").arg(color.red()).arg(color.green()).arg(color.blue()).arg(alphaString);
        RETURN_RESULT(scope.engine->newString(str));
    }
    RETURN_RESULT(r->d()->context()->m_fillStyle.value());
}

QV4::ReturnedValue QQuickJSContext2D::method_set_fillStyle(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc)
{
    QV4::Scope scope(b);
    QV4::Scoped<QQuickJSContext2D> r(scope, thisObject->as<QQuickJSContext2D>());
    CHECK_CONTEXT_SETTER(r)

    QV4::ScopedValue value(scope, argc ? argv[0] : QV4::Value::undefinedValue());

   if (value->as<Object>()) {
       QColor color = QV4::ExecutionEngine::toVariant(value, QMetaType::fromType<QColor>()).value<QColor>();
       if (color.isValid()) {
           r->d()->context()->state.fillStyle = color;
           r->d()->context()->buffer()->setFillStyle(color);
           r->d()->context()->m_fillStyle.set(scope.engine, value);
       } else {
           QV4::Scoped<QQuickContext2DStyle> style(scope, value->as<QQuickContext2DStyle>());
           if (style && *style->d()->brush != r->d()->context()->state.fillStyle) {
               r->d()->context()->state.fillStyle = *style->d()->brush;
               r->d()->context()->buffer()->setFillStyle(*style->d()->brush, style->d()->patternRepeatX, style->d()->patternRepeatY);
               r->d()->context()->m_fillStyle.set(scope.engine, value);
               r->d()->context()->state.fillPatternRepeatX = style->d()->patternRepeatX;
               r->d()->context()->state.fillPatternRepeatY = style->d()->patternRepeatY;
           }
       }
   } else if (value->isString()) {
       QColor color = qt_color_from_string(value);
       if (color.isValid() && r->d()->context()->state.fillStyle != QBrush(color)) {
            r->d()->context()->state.fillStyle = QBrush(color);
            r->d()->context()->buffer()->setFillStyle(r->d()->context()->state.fillStyle);
            r->d()->context()->m_fillStyle.set(scope.engine, value);
       }
   }
   RETURN_UNDEFINED();
}

/*!
    \qmlproperty enumeration QtQuick::Context2D::fillRule
    Holds the current fill rule used for filling shapes. The following fill rules are supported:

    \value Qt.OddEvenFill  Qt::OddEvenFill
    \value Qt.WindingFill  (default) Qt::WindingFill

    \note Unlike QPainterPath, the Canvas API uses the winding fill as the default fill rule.
    The fillRule property is part of the context rendering state.

    \sa fillStyle
*/
QV4::ReturnedValue QQuickJSContext2D::method_get_fillRule(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int)
{
    QV4::Scope scope(b);
    QV4::Scoped<QQuickJSContext2D> r(scope, thisObject->as<QQuickJSContext2D>());
    CHECK_CONTEXT(r)

    RETURN_RESULT(scope.engine->fromVariant(r->d()->context()->state.fillRule));
}

QV4::ReturnedValue QQuickJSContext2D::method_set_fillRule(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc)
{
    QV4::Scope scope(b);
    QV4::Scoped<QQuickJSContext2D> r(scope, thisObject->as<QQuickJSContext2D>());
    CHECK_CONTEXT_SETTER(r)

    QV4::ScopedValue value(scope, argc ? argv[0] : QV4::Value::undefinedValue());

    if ((value->isString() && value->toQString() == QLatin1String("WindingFill"))
        || (value->isInt32() && value->integerValue() == Qt::WindingFill)) {
        r->d()->context()->state.fillRule = Qt::WindingFill;
    } else if ((value->isString() && value->toQStringNoThrow() == QLatin1String("OddEvenFill"))
               || (value->isInt32() && value->integerValue() == Qt::OddEvenFill)) {
        r->d()->context()->state.fillRule = Qt::OddEvenFill;
    } else {
        //error
    }
    r->d()->context()->m_path.setFillRule(r->d()->context()->state.fillRule);
    RETURN_UNDEFINED();
}
/*!
    \qmlproperty variant QtQuick::Context2D::strokeStyle
     Holds the current color or style to use for the lines around shapes,
     The style can be either a string containing a CSS color, a CanvasGradient or CanvasPattern object.
     Invalid values are ignored.

     The default value is  '#000000'.

     \sa createLinearGradient()
     \sa createRadialGradient()
     \sa createPattern()
     \sa fillStyle
 */
QV4::ReturnedValue QQuickJSContext2D::method_get_strokeStyle(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int)
{
    QV4::Scope scope(b);
    QV4::Scoped<QQuickJSContext2D> r(scope, thisObject->as<QQuickJSContext2D>());
    CHECK_CONTEXT(r)

    const QColor color = r->d()->context()->state.strokeStyle.color().toRgb();
    if (color.isValid()) {
        if (color.alpha() == 255)
            RETURN_RESULT(scope.engine->newString(color.name()));
        QString alphaString = QString::number(color.alphaF(), 'f');
        while (alphaString.endsWith(QLatin1Char('0')))
            alphaString.chop(1);
        if (alphaString.endsWith(QLatin1Char('.')))
            alphaString += QLatin1Char('0');
        QString str = QString::fromLatin1("rgba(%1, %2, %3, %4)").arg(color.red()).arg(color.green()).arg(color.blue()).arg(alphaString);
        RETURN_RESULT(scope.engine->newString(str));
    }
    RETURN_RESULT(r->d()->context()->m_strokeStyle.value());
}

QV4::ReturnedValue QQuickJSContext2D::method_set_strokeStyle(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc)
{
    QV4::Scope scope(b);
    QV4::Scoped<QQuickJSContext2D> r(scope, thisObject->as<QQuickJSContext2D>());
    CHECK_CONTEXT_SETTER(r)

    QV4::ScopedValue value(scope, argc ? argv[0] : QV4::Value::undefinedValue());

    if (value->as<Object>()) {
        QColor color = QV4::ExecutionEngine::toVariant(value, QMetaType::fromType<QColor>()).value<QColor>();
        if (color.isValid()) {
            r->d()->context()->state.strokeStyle = color;
            r->d()->context()->buffer()->setStrokeStyle(color);
            r->d()->context()->m_strokeStyle.set(scope.engine, value);
        } else {
            QV4::Scoped<QQuickContext2DStyle> style(scope, value->as<QQuickContext2DStyle>());
            if (style && *style->d()->brush != r->d()->context()->state.strokeStyle) {
                r->d()->context()->state.strokeStyle = *style->d()->brush;
                r->d()->context()->buffer()->setStrokeStyle(*style->d()->brush, style->d()->patternRepeatX, style->d()->patternRepeatY);
                r->d()->context()->m_strokeStyle.set(scope.engine, value);
                r->d()->context()->state.strokePatternRepeatX = style->d()->patternRepeatX;
                r->d()->context()->state.strokePatternRepeatY = style->d()->patternRepeatY;
            } else if (!style && r->d()->context()->state.strokeStyle != QBrush(QColor())) {
                // If there is no style object, then ensure that the strokeStyle is at least
                // QColor in case it was previously set
                r->d()->context()->state.strokeStyle = QBrush(QColor());
                r->d()->context()->buffer()->setStrokeStyle(r->d()->context()->state.strokeStyle);
                r->d()->context()->m_strokeStyle.set(scope.engine, value);
            }
        }
    } else if (value->isString()) {
        QColor color = qt_color_from_string(value);
        if (color.isValid() && r->d()->context()->state.strokeStyle != QBrush(color)) {
             r->d()->context()->state.strokeStyle = QBrush(color);
             r->d()->context()->buffer()->setStrokeStyle(r->d()->context()->state.strokeStyle);
             r->d()->context()->m_strokeStyle.set(scope.engine, value);
        }
    }
    RETURN_UNDEFINED();
}

/*!
  \qmlmethod object QtQuick::Context2D::createLinearGradient(real x0, real y0, real x1, real y1)
   Returns a CanvasGradient object that represents a linear gradient that transitions the color along a line between
   the start point (\a x0, \a y0) and the end point (\a x1, \a y1).

   A gradient is a smooth transition between colors. There are two types of gradients: linear and radial.
   Gradients must have two or more color stops, representing color shifts positioned from 0 to 1 between
   to the gradient's starting and end points or circles.

    \sa CanvasGradient::addColorStop()
    \sa createRadialGradient()
    \sa createConicalGradient()
    \sa createPattern()
    \sa fillStyle
    \sa strokeStyle
  */

QV4::ReturnedValue QQuickJSContext2DPrototype::method_createLinearGradient(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc)
{
    QV4::Scope scope(b);
    QV4::Scoped<QQuickJSContext2D> r(scope, thisObject->as<QQuickJSContext2D>());
    CHECK_CONTEXT(r)

    if (argc >= 4) {
        qreal x0 = argv[0].toNumber();
        qreal y0 = argv[1].toNumber();
        qreal x1 = argv[2].toNumber();
        qreal y1 = argv[3].toNumber();

        if (!qt_is_finite(x0)
         || !qt_is_finite(y0)
         || !qt_is_finite(x1)
         || !qt_is_finite(y1)) {
            THROW_DOM(DOMEXCEPTION_NOT_SUPPORTED_ERR, "createLinearGradient(): Incorrect arguments")
        }
        QQuickContext2DEngineData *ed = engineData(scope.engine);

        QV4::Scoped<QQuickContext2DStyle> gradient(scope, scope.engine->memoryManager->allocate<QQuickContext2DStyle>());
        QV4::ScopedObject p(scope, ed->gradientProto.value());
        gradient->setPrototypeOf(p);
        *gradient->d()->brush = QLinearGradient(x0, y0, x1, y1);
        RETURN_RESULT(*gradient);
    }

    RETURN_RESULT(*thisObject);

}

/*!
    \qmlmethod object QtQuick::Context2D::createRadialGradient(real x0, real y0, real r0, real x1, real y1, real r1)

    Returns a CanvasGradient object that represents a radial gradient that
    paints along the cone given by the start circle with origin (\a x0, \a y0)
    and radius \a r0, and the end circle with origin (\a x1, \a y1) and radius
    \a r1.

    \sa CanvasGradient::addColorStop()
    \sa createLinearGradient()
    \sa createConicalGradient()
    \sa createPattern()
    \sa fillStyle
    \sa strokeStyle
  */

QV4::ReturnedValue QQuickJSContext2DPrototype::method_createRadialGradient(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc)
{
    QV4::Scope scope(b);
    QV4::Scoped<QQuickJSContext2D> r(scope, thisObject->as<QQuickJSContext2D>());
    CHECK_CONTEXT(r)

    if (argc >= 6) {
        qreal x0 = argv[0].toNumber();
        qreal y0 = argv[1].toNumber();
        qreal r0 = argv[2].toNumber();
        qreal x1 = argv[3].toNumber();
        qreal y1 = argv[4].toNumber();
        qreal r1 = argv[5].toNumber();

        if (!qt_is_finite(x0)
         || !qt_is_finite(y0)
         || !qt_is_finite(x1)
         || !qt_is_finite(r0)
         || !qt_is_finite(r1)
         || !qt_is_finite(y1)) {
            THROW_DOM(DOMEXCEPTION_NOT_SUPPORTED_ERR, "createRadialGradient(): Incorrect arguments")
        }

        if (r0 < 0 || r1 < 0)
            THROW_DOM(DOMEXCEPTION_INDEX_SIZE_ERR, "createRadialGradient(): Incorrect arguments")

        QQuickContext2DEngineData *ed = engineData(scope.engine);

        QV4::Scoped<QQuickContext2DStyle> gradient(scope, scope.engine->memoryManager->allocate<QQuickContext2DStyle>());
        QV4::ScopedObject p(scope, ed->gradientProto.value());
        gradient->setPrototypeOf(p);
        *gradient->d()->brush = QRadialGradient(QPointF(x1, y1), r1, QPointF(x0, y0), r0);
        RETURN_RESULT(*gradient);
    }

    RETURN_RESULT(*thisObject);

}

/*!
  \qmlmethod object QtQuick::Context2D::createConicalGradient(real x, real y, real angle)

   Returns a CanvasGradient object that represents a conical gradient that
   interpolates colors counter-clockwise around a center point (\a x, \a y)
   with a start angle \a angle in units of radians.

    \sa CanvasGradient::addColorStop()
    \sa createLinearGradient()
    \sa createRadialGradient()
    \sa createPattern()
    \sa fillStyle
    \sa strokeStyle
  */

QV4::ReturnedValue QQuickJSContext2DPrototype::method_createConicalGradient(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc)
{
    QV4::Scope scope(b);
    QV4::Scoped<QQuickJSContext2D> r(scope, thisObject->as<QQuickJSContext2D>());
    CHECK_CONTEXT(r)

    if (argc >= 3) {
        qreal x = argv[0].toNumber();
        qreal y = argv[1].toNumber();
        qreal angle = qRadiansToDegrees(argv[2].toNumber());
        if (!qt_is_finite(x) || !qt_is_finite(y)) {
            THROW_DOM(DOMEXCEPTION_NOT_SUPPORTED_ERR, "createConicalGradient(): Incorrect arguments");
        }

        if (!qt_is_finite(angle)) {
            THROW_DOM(DOMEXCEPTION_INDEX_SIZE_ERR, "createConicalGradient(): Incorrect arguments");
        }

        QQuickContext2DEngineData *ed = engineData(scope.engine);

        QV4::Scoped<QQuickContext2DStyle> gradient(scope, scope.engine->memoryManager->allocate<QQuickContext2DStyle>());
        QV4::ScopedObject p(scope, ed->gradientProto.value());
        gradient->setPrototypeOf(p);
        *gradient->d()->brush = QConicalGradient(x, y, angle);
        RETURN_RESULT(*gradient);
    }

    RETURN_RESULT(*thisObject);

}
/*!
    \qmlmethod variant QtQuick::Context2D::createPattern(color color, enumeration patternMode)
    This is an overloaded function.
    Returns a CanvasPattern object that uses the given \a color and \a patternMode.
    The valid pattern modes are:

    \value Qt.SolidPattern      Qt::SolidPattern
    \value Qt.Dense1Pattern     Qt::Dense1Pattern
    \value Qt.Dense2Pattern     Qt::Dense2Pattern
    \value Qt.Dense3Pattern     Qt::Dense3Pattern
    \value Qt.Dense4Pattern     Qt::Dense4Pattern
    \value Qt.Dense5Pattern     Qt::Dense5Pattern
    \value Qt.Dense6Pattern     Qt::Dense6Pattern
    \value Qt.Dense7Pattern     Qt::Dense7Pattern
    \value Qt.HorPattern        Qt::HorPattern
    \value Qt.VerPattern        Qt::VerPattern
    \value Qt.CrossPattern      Qt::CrossPattern
    \value Qt.BDiagPattern      Qt::BDiagPattern
    \value Qt.FDiagPattern      Qt::FDiagPattern
    \value Qt.DiagCrossPattern  Qt::DiagCrossPattern

    \sa Qt::BrushStyle
*/
/*!
    \qmlmethod variant QtQuick::Context2D::createPattern(Image image, string repetition)
    Returns a CanvasPattern object that uses the given image and repeats in the
    direction(s) given by the repetition argument.

    The \a image parameter must be a valid Image item, a valid CanvasImageData
    object or loaded image url. If there is no image data, thus function throws an
    INVALID_STATE_ERR exception.

    The allowed values for \a repetition are:

    \value "repeat"    both directions
    \value "repeat-x   horizontal only
    \value "repeat-y"  vertical only
    \value "no-repeat" neither

    If the repetition argument is empty or null, the value "repeat" is used.

    \sa strokeStyle
    \sa fillStyle
*/
QV4::ReturnedValue QQuickJSContext2DPrototype::method_createPattern(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc)
{
    QV4::Scope scope(b);
    QV4::Scoped<QQuickJSContext2D> r(scope, *thisObject);
    CHECK_CONTEXT(r)

    if (argc >= 2) {
        QV4::Scoped<QQuickContext2DStyle> pattern(scope, scope.engine->memoryManager->allocate<QQuickContext2DStyle>());

        QColor color = QV4::ExecutionEngine::toVariant(
                    argv[0], QMetaType::fromType<QColor>()).value<QColor>();
        if (color.isValid()) {
            int patternMode = argv[1].toInt32();
            Qt::BrushStyle style = Qt::SolidPattern;
            if (patternMode >= 0 && patternMode < Qt::LinearGradientPattern) {
                style = static_cast<Qt::BrushStyle>(patternMode);
            }
            *pattern->d()->brush = QBrush(color, style);
        } else {
            QImage patternTexture;

            if (const QV4::Object *o = argv[0].as<Object>()) {
                QV4::ScopedString s(scope, scope.engine->newString(QStringLiteral("data")));
                QV4::Scoped<QQuickJSContext2DPixelData> pixelData(scope, o->get(s));
                if (!!pixelData) {
                    patternTexture = *pixelData->d()->image;
                }
            } else {
                patternTexture = r->d()->context()->createPixmap(QUrl(argv[0].toQStringNoThrow()))->image();
            }

            if (!patternTexture.isNull()) {
                pattern->d()->brush->setTextureImage(patternTexture);

                QString repetition = argv[1].toQStringNoThrow();
                if (repetition == QLatin1String("repeat") || repetition.isEmpty()) {
                    pattern->d()->patternRepeatX = true;
                    pattern->d()->patternRepeatY = true;
                } else if (repetition == QLatin1String("repeat-x")) {
                    pattern->d()->patternRepeatX = true;
                    pattern->d()->patternRepeatY = false;
                } else if (repetition == QLatin1String("repeat-y")) {
                    pattern->d()->patternRepeatX = false;
                    pattern->d()->patternRepeatY = true;
                } else if (repetition == QLatin1String("no-repeat")) {
                    pattern->d()->patternRepeatX = false;
                    pattern->d()->patternRepeatY = false;
                } else {
                    //TODO: exception: SYNTAX_ERR
                }

            }
        }

        RETURN_RESULT(*pattern);

    }
    RETURN_UNDEFINED();
}

// line styles
/*!
    \qmlproperty string QtQuick::Context2D::lineCap
    Holds the current line cap style.
    The possible line cap styles are:

    \value "butt"
        (default) Qt::FlatCap the end of each line has a flat edge
        perpendicular to the direction of the line.
    \value "round"
        Qt::RoundCap a semi-circle with the diameter equal to the width of the
        line is added on to the end of the line.
    \value "square"
        Qt::SquareCap a rectangle with the length of the line width and the
        width of half the line width, placed flat against the edge
        perpendicular to the direction of the line.

    Other values are ignored.
*/
QV4::ReturnedValue QQuickJSContext2D::method_get_lineCap(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int)
{
    QV4::Scope scope(b);
    QV4::Scoped<QQuickJSContext2D> r(scope, *thisObject);
    CHECK_CONTEXT(r)

    switch (r->d()->context()->state.lineCap) {
    case Qt::RoundCap:
        RETURN_RESULT(scope.engine->newString(QStringLiteral("round")));
    case Qt::SquareCap:
        RETURN_RESULT(scope.engine->newString(QStringLiteral("square")));
    case Qt::FlatCap:
    default:
        break;
    }
    RETURN_RESULT(scope.engine->newString(QStringLiteral("butt")));
}

QV4::ReturnedValue QQuickJSContext2D::method_set_lineCap(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc)
{
    if (!argc)
        return QV4::Encode::undefined();

    QV4::Scope scope(b);
    QV4::Scoped<QQuickJSContext2D> r(scope, *thisObject);
    CHECK_CONTEXT_SETTER(r)

    QString lineCap = argv[0].toQString();
    Qt::PenCapStyle cap;
    if (lineCap == QLatin1String("round"))
        cap = Qt::RoundCap;
    else if (lineCap == QLatin1String("butt"))
        cap = Qt::FlatCap;
    else if (lineCap == QLatin1String("square"))
        cap = Qt::SquareCap;
    else
        RETURN_UNDEFINED();

    if (cap != r->d()->context()->state.lineCap) {
        r->d()->context()->state.lineCap = cap;
        r->d()->context()->buffer()->setLineCap(cap);
    }
    RETURN_UNDEFINED();
}

/*!
    \qmlproperty string QtQuick::Context2D::lineJoin
    Holds the current line join style. A join exists at any point in a subpath
    shared by two consecutive lines. When a subpath is closed, then a join also
    exists at its first point (equivalent to its last point) connecting the
    first and last lines in the subpath.

    The possible line join styles are:

    \value "bevel"  Qt::BevelJoin The triangular notch between the two lines is filled.
    \value "round"  Qt::RoundJoin A circular arc between the two lines is filled.
    \value "miter"  (default) Qt::MiterJoin The outer edges of the lines are extended to
                    meet at an angle, and this area is filled.

    Other values are ignored.
*/
QV4::ReturnedValue QQuickJSContext2D::method_get_lineJoin(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int)
{
    QV4::Scope scope(b);
    QV4::Scoped<QQuickJSContext2D> r(scope, *thisObject);
    CHECK_CONTEXT(r)

    switch (r->d()->context()->state.lineJoin) {
    case Qt::RoundJoin:
        RETURN_RESULT(scope.engine->newString(QStringLiteral("round")));
    case Qt::BevelJoin:
        RETURN_RESULT(scope.engine->newString(QStringLiteral("bevel")));
    case Qt::MiterJoin:
    default:
        break;
    }
    RETURN_RESULT(scope.engine->newString(QStringLiteral("miter")));
}

QV4::ReturnedValue QQuickJSContext2D::method_set_lineJoin(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc)
{
    QV4::Scope scope(b);
    QV4::Scoped<QQuickJSContext2D> r(scope, *thisObject);
    CHECK_CONTEXT_SETTER(r)

    if (!argc)
        THROW_TYPE_ERROR();

    QString lineJoin = argv[0].toQString();
    Qt::PenJoinStyle join;
    if (lineJoin == QLatin1String("round"))
        join = Qt::RoundJoin;
    else if (lineJoin == QLatin1String("bevel"))
        join = Qt::BevelJoin;
    else if (lineJoin == QLatin1String("miter"))
        join = Qt::SvgMiterJoin;
    else
        RETURN_UNDEFINED();

    if (join != r->d()->context()->state.lineJoin) {
        r->d()->context()->state.lineJoin = join;
        r->d()->context()->buffer()->setLineJoin(join);
    }
    RETURN_UNDEFINED();
}

/*!
    \qmlproperty real QtQuick::Context2D::lineWidth
     Holds the current line width. Values that are not finite values greater than zero are ignored.
 */
QV4::ReturnedValue QQuickJSContext2D::method_get_lineWidth(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int)
{
    QV4::Scope scope(b);
    QV4::Scoped<QQuickJSContext2D> r(scope, *thisObject);
    CHECK_CONTEXT(r)

    RETURN_RESULT(QV4::Encode(r->d()->context()->state.lineWidth));
}

QV4::ReturnedValue QQuickJSContext2D::method_set_lineWidth(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc)
{
    QV4::Scope scope(b);
    QV4::Scoped<QQuickJSContext2D> r(scope, *thisObject);
    CHECK_CONTEXT_SETTER(r)

    qreal w = argc ? argv[0].toNumber() : -1;

    if (w > 0 && qt_is_finite(w) && w != r->d()->context()->state.lineWidth) {
        r->d()->context()->state.lineWidth = w;
        r->d()->context()->buffer()->setLineWidth(w);
    }
    RETURN_UNDEFINED();
}

/*!
    \qmlproperty real QtQuick::Context2D::miterLimit
     Holds the current miter limit ratio.
     The default miter limit value is 10.0.
 */
QV4::ReturnedValue QQuickJSContext2D::method_get_miterLimit(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int)
{
    QV4::Scope scope(b);
    QV4::Scoped<QQuickJSContext2D> r(scope, *thisObject);
    CHECK_CONTEXT(r)

    RETURN_RESULT(QV4::Encode(r->d()->context()->state.miterLimit));
}

QV4::ReturnedValue QQuickJSContext2D::method_set_miterLimit(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc)
{
    QV4::Scope scope(b);
    QV4::Scoped<QQuickJSContext2D> r(scope, *thisObject);
    CHECK_CONTEXT_SETTER(r)

    qreal ml = argc ? argv[0].toNumber() : -1;

    if (ml > 0 && qt_is_finite(ml) && ml != r->d()->context()->state.miterLimit) {
        r->d()->context()->state.miterLimit = ml;
        r->d()->context()->buffer()->setMiterLimit(ml);
    }
    RETURN_UNDEFINED();
}

/*!
    \qmlmethod array QtQuick::Context2D::getLineDash()
    \since QtQuick 2.11
    Returns an array of qreals representing the dash pattern of the line.

    \sa setLineDash(), lineDashOffset
  */
QV4::ReturnedValue QQuickJSContext2DPrototype::method_getLineDash(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int)
{
    QV4::Scope scope(b);
    QV4::Scoped<QQuickJSContext2D> r(scope, *thisObject);
    CHECK_CONTEXT(r)

    const QVector<qreal> pattern = r->d()->context()->state.lineDash;
    QV4::ScopedArrayObject array(scope, scope.engine->newArrayObject(pattern.size()));
    array->arrayReserve(pattern.size());
    for (int i = 0; i < pattern.size(); i++)
        array->put(i, QV4::Value::fromDouble(pattern[i]));

    array->setArrayLengthUnchecked(pattern.size());

    RETURN_RESULT(*array);
}

/*!
    \qmlmethod QtQuick::Context2D::setLineDash(array pattern)
    \since QtQuick 2.11
    Sets the dash pattern to the given pattern.

    \a pattern a list of numbers that specifies distances to alternately draw a line and a gap.

    If the number of elements in the array is odd, the elements of the array get copied
    and concatenated. For example, [5, 15, 25] will become [5, 15, 25, 5, 15, 25].

    \table 100%
    \row
    \li \inlineimage qml-item-canvas-lineDash.png
    \li
    \code
    var space = 4
    ctx.setLineDash([1, space, 3, space, 9, space, 27, space, 9, space])
    ...
    ctx.stroke();
    \endcode
    \endtable

    \sa getLineDash(), lineDashOffset
  */
QV4::ReturnedValue QQuickJSContext2DPrototype::method_setLineDash(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc)
{
    QV4::Scope scope(b);
    QV4::Scoped<QQuickJSContext2D> r(scope, *thisObject);
    CHECK_CONTEXT_SETTER(r)

    if (!argc)
        RETURN_UNDEFINED();

    QV4::ScopedArrayObject array(scope, argv[0]);
    if (!array)
        RETURN_UNDEFINED();

    QV4::ScopedValue v(scope);
    const uint arrayLength = array->getLength();
    QVector<qreal> dashes;
    dashes.reserve(arrayLength);
    for (uint i = 0; i < arrayLength; ++i) {
        v = array->get(i);
        const double number = v->toNumber();

        if (!qt_is_finite(number) || (number < 0))
                RETURN_UNDEFINED();

        dashes.append(v->toNumber());
    }
    if (dashes.size() % 2 != 0) {
        dashes += dashes;
    }

    r->d()->context()->state.lineDash = dashes;
    r->d()->context()->buffer()->setLineDash(dashes);

    RETURN_UNDEFINED();
}

/*!
    \qmlproperty real QtQuick::Context2D::lineDashOffset
    \since QtQuick 2.11

     Holds the current line dash offset.
     The default line dash offset value is \c 0.

     \sa getLineDash(), setLineDash()
 */
QV4::ReturnedValue QQuickJSContext2D::method_get_lineDashOffset(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int)
{
    QV4::Scope scope(b);
    QV4::Scoped<QQuickJSContext2D> r(scope, *thisObject);
    CHECK_CONTEXT(r)

    RETURN_RESULT(QV4::Encode(r->d()->context()->state.lineDashOffset));
}

QV4::ReturnedValue QQuickJSContext2D::method_set_lineDashOffset(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc)
{
    QV4::Scope scope(b);
    QV4::Scoped<QQuickJSContext2D> r(scope, *thisObject);
    CHECK_CONTEXT_SETTER(r)

    const qreal offset = argc ? argv[0].toNumber() : -1;

    if (qt_is_finite(offset) && offset != r->d()->context()->state.lineDashOffset) {
        r->d()->context()->state.lineDashOffset = offset;
        r->d()->context()->buffer()->setLineDashOffset(offset);
    }
    RETURN_UNDEFINED();
}


// shadows
/*!
    \qmlproperty real QtQuick::Context2D::shadowBlur
     Holds the current level of blur applied to shadows
 */
QV4::ReturnedValue QQuickJSContext2D::method_get_shadowBlur(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int)
{
    QV4::Scope scope(b);
    QV4::Scoped<QQuickJSContext2D> r(scope, *thisObject);
    CHECK_CONTEXT(r)

    RETURN_RESULT(QV4::Encode(r->d()->context()->state.shadowBlur));
}

QV4::ReturnedValue QQuickJSContext2D::method_set_shadowBlur(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc)
{
    QV4::Scope scope(b);
    QV4::Scoped<QQuickJSContext2D> r(scope, *thisObject);
    CHECK_CONTEXT_SETTER(r)

    qreal blur = argc ? argv[0].toNumber() : -1;

    if (blur > 0 && qt_is_finite(blur) && blur != r->d()->context()->state.shadowBlur) {
        r->d()->context()->state.shadowBlur = blur;
        r->d()->context()->buffer()->setShadowBlur(blur);
    }
    RETURN_UNDEFINED();
}

/*!
    \qmlproperty string QtQuick::Context2D::shadowColor
     Holds the current shadow color.
 */
QV4::ReturnedValue QQuickJSContext2D::method_get_shadowColor(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int)
{
    QV4::Scope scope(b);
    QV4::Scoped<QQuickJSContext2D> r(scope, *thisObject);
    CHECK_CONTEXT(r)

    RETURN_RESULT(scope.engine->newString(r->d()->context()->state.shadowColor.name()));
}

QV4::ReturnedValue QQuickJSContext2D::method_set_shadowColor(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc)
{
    QV4::Scope scope(b);
    QV4::Scoped<QQuickJSContext2D> r(scope, *thisObject);
    CHECK_CONTEXT_SETTER(r)

    QColor color;
    if (argc)
        color = qt_color_from_string(argv[0]);

    if (color.isValid() && color != r->d()->context()->state.shadowColor) {
        r->d()->context()->state.shadowColor = color;
        r->d()->context()->buffer()->setShadowColor(color);
    }
    RETURN_UNDEFINED();
}


/*!
    \qmlproperty real QtQuick::Context2D::shadowOffsetX
     Holds the current shadow offset in the positive horizontal distance.

     \sa shadowOffsetY
 */
QV4::ReturnedValue QQuickJSContext2D::method_get_shadowOffsetX(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int)
{
    QV4::Scope scope(b);
    QV4::Scoped<QQuickJSContext2D> r(scope, *thisObject);
    CHECK_CONTEXT(r)

    RETURN_RESULT(QV4::Encode(r->d()->context()->state.shadowOffsetX));
}

QV4::ReturnedValue QQuickJSContext2D::method_set_shadowOffsetX(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc)
{
    QV4::Scope scope(b);
    QV4::Scoped<QQuickJSContext2D> r(scope, *thisObject);
    CHECK_CONTEXT_SETTER(r)

    qreal offsetX = argc ? argv[0].toNumber() : qt_qnan();
    if (qt_is_finite(offsetX) && offsetX != r->d()->context()->state.shadowOffsetX) {
        r->d()->context()->state.shadowOffsetX = offsetX;
        r->d()->context()->buffer()->setShadowOffsetX(offsetX);
    }
    RETURN_UNDEFINED();
}
/*!
    \qmlproperty real QtQuick::Context2D::shadowOffsetY
     Holds the current shadow offset in the positive vertical distance.

     \sa shadowOffsetX
 */
QV4::ReturnedValue QQuickJSContext2D::method_get_shadowOffsetY(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int)
{
    QV4::Scope scope(b);
    QV4::Scoped<QQuickJSContext2D> r(scope, *thisObject);
    CHECK_CONTEXT(r)

    RETURN_RESULT(QV4::Encode(r->d()->context()->state.shadowOffsetY));
}

QV4::ReturnedValue QQuickJSContext2D::method_set_shadowOffsetY(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc)
{
    QV4::Scope scope(b);
    QV4::Scoped<QQuickJSContext2D> r(scope, *thisObject);
    CHECK_CONTEXT_SETTER(r)

    qreal offsetY = argc ? argv[0].toNumber() : qt_qnan();
    if (qt_is_finite(offsetY) && offsetY != r->d()->context()->state.shadowOffsetY) {
        r->d()->context()->state.shadowOffsetY = offsetY;
        r->d()->context()->buffer()->setShadowOffsetY(offsetY);
    }
    RETURN_UNDEFINED();
}

#if QT_CONFIG(quick_path)
QV4::ReturnedValue QQuickJSContext2D::method_get_path(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int)
{
    QV4::Scope scope(b);
    QV4::Scoped<QQuickJSContext2D> r(scope, *thisObject);
    CHECK_CONTEXT(r)

    RETURN_RESULT(r->d()->context()->m_v4path.value());
}

QV4::ReturnedValue QQuickJSContext2D::method_set_path(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc)
{
    QV4::Scope scope(b);
    QV4::Scoped<QQuickJSContext2D> r(scope, *thisObject);
    CHECK_CONTEXT_SETTER(r)

    QV4::ScopedValue value(scope, argc ? argv[0] : QV4::Value::undefinedValue());
    r->d()->context()->beginPath();
    QV4::Scoped<QV4::QObjectWrapper> qobjectWrapper(scope, value);
    if (!!qobjectWrapper) {
        if (QQuickPath *path = qobject_cast<QQuickPath*>(qobjectWrapper->object()))
            r->d()->context()->m_path = path->path();
    } else {
        QString path =value->toQStringNoThrow();
        QQuickSvgParser::parsePathDataFast(path, r->d()->context()->m_path);
    }
    r->d()->context()->m_v4path.set(scope.engine, value);
    RETURN_UNDEFINED();
}
#endif // QT_CONFIG(quick_path)

//rects
/*!
    \qmlmethod object QtQuick::Context2D::clearRect(real x, real y, real w, real h)

    Clears all pixels on the canvas in the rectangle specified by
    (\a x, \a y, \a w, \a h) to transparent black.
  */
QV4::ReturnedValue QQuickJSContext2DPrototype::method_clearRect(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc)
{
    QV4::Scope scope(b);
    QV4::Scoped<QQuickJSContext2D> r(scope, *thisObject);
    CHECK_CONTEXT(r)


    if (argc >= 4)
        r->d()->context()->clearRect(argv[0].toNumber(),
                              argv[1].toNumber(),
                              argv[2].toNumber(),
                              argv[3].toNumber());

    RETURN_RESULT(*thisObject);

}
/*!
    \qmlmethod object QtQuick::Context2D::fillRect(real x, real y, real w, real h)

    Paints a rectangular area specified by (\a x, \a y, \a w, \a h) using fillStyle.

   \sa fillStyle
  */
QV4::ReturnedValue QQuickJSContext2DPrototype::method_fillRect(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc)
{
    QV4::Scope scope(b);
    QV4::Scoped<QQuickJSContext2D> r(scope, *thisObject);
    CHECK_CONTEXT(r)

    if (argc >= 4)
        r->d()->context()->fillRect(argv[0].toNumber(), argv[1].toNumber(), argv[2].toNumber(), argv[3].toNumber());
    RETURN_RESULT(*thisObject);

}

/*!
    \qmlmethod object QtQuick::Context2D::strokeRect(real x, real y, real w, real h)

    Strokes the path of the rectangle specified by (\a x, \a y, \a w, \a h) using
    strokeStyle, lineWidth, lineJoin, and (if appropriate) miterLimit attributes.

   \sa strokeStyle, lineWidth, lineJoin, miterLimit
  */
QV4::ReturnedValue QQuickJSContext2DPrototype::method_strokeRect(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc)
{
    QV4::Scope scope(b);
    QV4::Scoped<QQuickJSContext2D> r(scope, *thisObject);
    CHECK_CONTEXT(r)

    if (argc >= 4)
        r->d()->context()->strokeRect(argv[0].toNumber(), argv[1].toNumber(), argv[2].toNumber(), argv[3].toNumber());

    RETURN_RESULT(*thisObject);

}

// Complex shapes (paths) API
/*!
    \qmlmethod object QtQuick::Context2D::arc(real x, real y, real radius,
        real startAngle, real endAngle, bool anticlockwise)

    Adds an arc to the current subpath that lies on the circumference of the
    circle whose center is at the point (\a x, \a y) and whose radius is
    \a radius.

    Both \a startAngle and \a endAngle are measured from the x-axis in radians.

    \image qml-item-canvas-arc.png

    \image qml-item-canvas-startAngle.png

    The \a anticlockwise parameter is \c false for each arc in the figure above
    because they are all drawn in the clockwise direction.

    \sa arcTo, {http://www.w3.org/TR/2dcontext/#dom-context-2d-arc}{W3C's 2D
    Context Standard for arc()}
*/
QV4::ReturnedValue QQuickJSContext2DPrototype::method_arc(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc)
{
    QV4::Scope scope(b);
    QV4::Scoped<QQuickJSContext2D> r(scope, *thisObject);
    CHECK_CONTEXT(r)

    if (argc >= 5) {
        bool antiClockwise = false;

        if (argc == 6)
            antiClockwise = argv[5].toBoolean();

        qreal radius = argv[2].toNumber();

        if (qt_is_finite(radius) && radius < 0)
           THROW_DOM(DOMEXCEPTION_INDEX_SIZE_ERR, "Incorrect argument radius");

        r->d()->context()->arc(argv[0].toNumber(),
                        argv[1].toNumber(),
                        radius,
                        argv[3].toNumber(),
                        argv[4].toNumber(),
                        antiClockwise);
    }

    RETURN_RESULT(*thisObject);

}

/*!
    \qmlmethod object QtQuick::Context2D::arcTo(real x1, real y1, real x2,
        real y2, real radius)

    Adds an arc with the given control points and radius to the current subpath,
    connected to the previous point by a straight line. To draw an arc, you
    begin with the same steps you followed to create a line:

    \list
    \li Call the beginPath() method to set a new path.
    \li Call the moveTo(\c x, \c y) method to set your starting position on the
        canvas at the point (\c x, \c y).
    \li To draw an arc or circle, call the arcTo(\a x1, \a y1, \a x2, \a y2,
        \a radius) method. This adds an arc with starting point (\a x1, \a y1),
        ending point (\a x2, \a y2), and \a radius to the current subpath and
        connects it to the previous subpath by a straight line.
    \endlist

    \image qml-item-canvas-arcTo.png

    \sa arc, {http://www.w3.org/TR/2dcontext/#dom-context-2d-arcto}{W3C's 2D
    Context Standard for arcTo()}
*/
QV4::ReturnedValue QQuickJSContext2DPrototype::method_arcTo(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc)
{
    QV4::Scope scope(b);
    QV4::Scoped<QQuickJSContext2D> r(scope, *thisObject);
    CHECK_CONTEXT(r)

    if (argc >= 5) {
        qreal radius = argv[4].toNumber();

        if (qt_is_finite(radius) && radius < 0)
           THROW_DOM(DOMEXCEPTION_INDEX_SIZE_ERR, "Incorrect argument radius");

        r->d()->context()->arcTo(argv[0].toNumber(),
                          argv[1].toNumber(),
                          argv[2].toNumber(),
                          argv[3].toNumber(),
                          radius);
    }

    RETURN_RESULT(*thisObject);

}

/*!
  \qmlmethod object QtQuick::Context2D::beginPath()

   Resets the current path to a new path.
  */
QV4::ReturnedValue QQuickJSContext2DPrototype::method_beginPath(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int)
{
    QV4::Scope scope(b);
    QV4::Scoped<QQuickJSContext2D> r(scope, *thisObject);
    CHECK_CONTEXT(r)

    r->d()->context()->beginPath();

    RETURN_RESULT(*thisObject);

}

/*!
  \qmlmethod object QtQuick::Context2D::bezierCurveTo(real cp1x, real cp1y, real cp2x, real cp2y, real x, real y)

  Adds a cubic bezier curve between the current position and the given endPoint using the control points specified by (\a {cp1x}, \a {cp1y}),
  and (\a {cp2x}, \a {cp2y}).
  After the curve is added, the current position is updated to be at the end point (\a {x}, \a {y}) of the curve.
  The following code produces the path shown below:

  \code
  ctx.strokeStyle = Qt.rgba(0, 0, 0, 1);
  ctx.lineWidth = 1;
  ctx.beginPath();
  ctx.moveTo(20, 0);//start point
  ctx.bezierCurveTo(-10, 90, 210, 90, 180, 0);
  ctx.stroke();
  \endcode

   \image qml-item-canvas-bezierCurveTo.png

  \sa {http://www.w3.org/TR/2dcontext/#dom-context-2d-beziercurveto}{W3C 2d context standard for bezierCurveTo}
  \sa {https://web.archive.org/web/20130505222636if_/http://www.openrise.com/lab/FlowerPower/}{The beautiful flower demo by using bezierCurveTo}
  */
QV4::ReturnedValue QQuickJSContext2DPrototype::method_bezierCurveTo(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc)
{
    QV4::Scope scope(b);
    QV4::Scoped<QQuickJSContext2D> r(scope, *thisObject);
    CHECK_CONTEXT(r)

    if (argc >= 6) {
        qreal cp1x = argv[0].toNumber();
        qreal cp1y = argv[1].toNumber();
        qreal cp2x = argv[2].toNumber();
        qreal cp2y = argv[3].toNumber();
        qreal x = argv[4].toNumber();
        qreal y = argv[5].toNumber();

        if (!qt_is_finite(cp1x) || !qt_is_finite(cp1y) || !qt_is_finite(cp2x) || !qt_is_finite(cp2y) || !qt_is_finite(x) || !qt_is_finite(y))
            RETURN_UNDEFINED();

        r->d()->context()->bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y);
    }
    RETURN_RESULT(*thisObject);
}

/*!
  \qmlmethod object QtQuick::Context2D::clip()

   Creates the clipping region from the current path.
   Any parts of the shape outside the clipping path are not displayed.
   To create a complex shape using the \c clip() method:

    \list 1
    \li Call the \c{context.beginPath()} method to set the clipping path.
    \li Define the clipping path by calling any combination of the \c{lineTo},
    \c{arcTo}, \c{arc}, \c{moveTo}, etc and \c{closePath} methods.
    \li Call the \c{context.clip()} method.
    \endlist

    The new shape displays.  The following shows how a clipping path can
    modify how an image displays:

    \image qml-item-canvas-clip-complex.png
    \sa beginPath()
    \sa closePath()
    \sa stroke()
    \sa fill()
   \sa {http://www.w3.org/TR/2dcontext/#dom-context-2d-clip}{W3C 2d context standard for clip}
  */
QV4::ReturnedValue QQuickJSContext2DPrototype::method_clip(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int)
{
    QV4::Scope scope(b);
    QV4::Scoped<QQuickJSContext2D> r(scope, *thisObject);
    CHECK_CONTEXT(r)

    r->d()->context()->clip();
    RETURN_RESULT(*thisObject);
}

/*!
  \qmlmethod object QtQuick::Context2D::closePath()
   Closes the current subpath by drawing a line to the beginning of the subpath, automatically starting a new path.
   The current point of the new path is the previous subpath's first point.

   \sa {http://www.w3.org/TR/2dcontext/#dom-context-2d-closepath}{W3C 2d context standard for closePath}
  */
QV4::ReturnedValue QQuickJSContext2DPrototype::method_closePath(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int)
{
    QV4::Scope scope(b);
    QV4::Scoped<QQuickJSContext2D> r(scope, *thisObject);
    CHECK_CONTEXT(r)

    r->d()->context()->closePath();

    RETURN_RESULT(*thisObject);
}

/*!
  \qmlmethod object QtQuick::Context2D::fill()

   Fills the subpaths with the current fill style.

   \sa {http://www.w3.org/TR/2dcontext/#dom-context-2d-fill}{W3C 2d context standard for fill}

   \sa fillStyle
  */
QV4::ReturnedValue QQuickJSContext2DPrototype::method_fill(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int)
{
    QV4::Scope scope(b);
    QV4::Scoped<QQuickJSContext2D> r(scope, *thisObject);
    CHECK_CONTEXT(r);
    r->d()->context()->fill();
    RETURN_RESULT(*thisObject);
}

/*!
  \qmlmethod object QtQuick::Context2D::lineTo(real x, real y)

   Draws a line from the current position to the point at (\a x, \a y).
 */
QV4::ReturnedValue QQuickJSContext2DPrototype::method_lineTo(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc)
{
    QV4::Scope scope(b);
    QV4::Scoped<QQuickJSContext2D> r(scope, *thisObject);
    CHECK_CONTEXT(r)

    if (argc >= 2) {
        qreal x = argv[0].toNumber();
        qreal y = argv[1].toNumber();

        if (!qt_is_finite(x) || !qt_is_finite(y))
            RETURN_UNDEFINED();

        r->d()->context()->lineTo(x, y);
    }

    RETURN_RESULT(*thisObject);
}

/*!
  \qmlmethod object QtQuick::Context2D::moveTo(real x, real y)

   Creates a new subpath with a point at (\a x, \a y).
 */
QV4::ReturnedValue QQuickJSContext2DPrototype::method_moveTo(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc)
{
    QV4::Scope scope(b);
    QV4::Scoped<QQuickJSContext2D> r(scope, *thisObject);
    CHECK_CONTEXT(r)

    if (argc >= 2) {
        qreal x = argv[0].toNumber();
        qreal y = argv[1].toNumber();

        if (!qt_is_finite(x) || !qt_is_finite(y))
            RETURN_UNDEFINED();
        r->d()->context()->moveTo(x, y);
    }

    RETURN_RESULT(*thisObject);
}

/*!
    \qmlmethod object QtQuick::Context2D::quadraticCurveTo(real cpx, real cpy, real x, real y)

    Adds a quadratic bezier curve between the current point and the endpoint
    (\a x, \a y) with the control point specified by (\a cpx, \a cpy).

    \sa {http://www.w3.org/TR/2dcontext/#dom-context-2d-quadraticcurveto}{W3C 2d context standard for quadraticCurveTo}
 */
QV4::ReturnedValue QQuickJSContext2DPrototype::method_quadraticCurveTo(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc)
{
    QV4::Scope scope(b);
    QV4::Scoped<QQuickJSContext2D> r(scope, *thisObject);
    CHECK_CONTEXT(r)

    if (argc >= 4) {
        qreal cpx = argv[0].toNumber();
        qreal cpy = argv[1].toNumber();
        qreal x = argv[2].toNumber();
        qreal y = argv[3].toNumber();

        if (!qt_is_finite(cpx) || !qt_is_finite(cpy) || !qt_is_finite(x) || !qt_is_finite(y))
            RETURN_UNDEFINED();

        r->d()->context()->quadraticCurveTo(cpx, cpy, x, y);
    }

    RETURN_RESULT(*thisObject);
}

/*!
    \qmlmethod object QtQuick::Context2D::rect(real x, real y, real w, real h)

    Adds a rectangle at position (\a x, \a y), with the given width \a w and
    height \a h, as a closed subpath.
 */
QV4::ReturnedValue QQuickJSContext2DPrototype::method_rect(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc)
{
    QV4::Scope scope(b);
    QV4::Scoped<QQuickJSContext2D> r(scope, *thisObject);
    CHECK_CONTEXT(r)

    if (argc >= 4)
        r->d()->context()->rect(argv[0].toNumber(), argv[1].toNumber(), argv[2].toNumber(), argv[3].toNumber());
    RETURN_RESULT(*thisObject);

}

/*!
    \qmlmethod object QtQuick::Context2D::roundedRect(real x, real y, real w, real h, real xRadius, real yRadius)

    Adds a rounded-corner rectangle, specified by (\a x, \a y, \a w, \a h), to the path.
    The \a xRadius and \a yRadius arguments specify the radius of the
    ellipses defining the corners of the rounded rectangle.
 */
QV4::ReturnedValue QQuickJSContext2DPrototype::method_roundedRect(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc)
{
    QV4::Scope scope(b);
    QV4::Scoped<QQuickJSContext2D> r(scope, *thisObject);
    CHECK_CONTEXT(r)

    if (argc >= 6)
        r->d()->context()->roundedRect(argv[0].toNumber()
                              , argv[1].toNumber()
                              , argv[2].toNumber()
                              , argv[3].toNumber()
                              , argv[4].toNumber()
                              , argv[5].toNumber());
    RETURN_RESULT(*thisObject);

}

/*!
    \qmlmethod object QtQuick::Context2D::ellipse(real x, real y, real w, real h)

    Creates an ellipse within the bounding rectangle defined by its top-left
    corner at (\a x, \a y), width \a w and height \a h, and adds it to the
    path as a closed subpath.

    The ellipse is composed of a clockwise curve, starting and finishing at
    zero degrees (the 3 o'clock position).
 */
QV4::ReturnedValue QQuickJSContext2DPrototype::method_ellipse(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc)
{
    QV4::Scope scope(b);
    QV4::Scoped<QQuickJSContext2D> r(scope, *thisObject);
    CHECK_CONTEXT(r)

    if (argc >= 4)
        r->d()->context()->ellipse(argv[0].toNumber(), argv[1].toNumber(), argv[2].toNumber(), argv[3].toNumber());

    RETURN_RESULT(*thisObject);

}

/*!
  \qmlmethod object QtQuick::Context2D::text(string text, real x, real y)

  Adds the given \a text to the path as a set of closed subpaths created
  from the current context font supplied.

  The subpaths are positioned so that the left end of the text's baseline
  lies at the point specified by (\a x, \a y).
 */
QV4::ReturnedValue QQuickJSContext2DPrototype::method_text(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc)
{
    QV4::Scope scope(b);
    QV4::Scoped<QQuickJSContext2D> r(scope, *thisObject);
    CHECK_CONTEXT(r)

    if (argc >= 3) {
        qreal x = argv[1].toNumber();
        qreal y = argv[2].toNumber();

        if (!qt_is_finite(x) || !qt_is_finite(y))
            RETURN_UNDEFINED();
        r->d()->context()->text(argv[0].toQStringNoThrow(), x, y);
    }

    RETURN_RESULT(*thisObject);
}

/*!
  \qmlmethod object QtQuick::Context2D::stroke()

   Strokes the subpaths with the current stroke style.

   \sa strokeStyle, {http://www.w3.org/TR/2dcontext/#dom-context-2d-stroke}{W3C 2d context standard for stroke}
  */
QV4::ReturnedValue QQuickJSContext2DPrototype::method_stroke(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int)
{
    QV4::Scope scope(b);
    QV4::Scoped<QQuickJSContext2D> r(scope, *thisObject);
    CHECK_CONTEXT(r)

    r->d()->context()->stroke();
    RETURN_RESULT(*thisObject);

}

/*!
  \qmlmethod object QtQuick::Context2D::isPointInPath(real x, real y)

   Returns \c true if the point (\a x, \a y) is in the current path.

   \sa {http://www.w3.org/TR/2dcontext/#dom-context-2d-ispointinpath}{W3C 2d context standard for isPointInPath}
  */
QV4::ReturnedValue QQuickJSContext2DPrototype::method_isPointInPath(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc)
{
    QV4::Scope scope(b);
    QV4::Scoped<QQuickJSContext2D> r(scope, *thisObject);
    CHECK_CONTEXT(r)

    bool pointInPath = false;
    if (argc >= 2)
        pointInPath = r->d()->context()->isPointInPath(argv[0].toNumber(), argv[1].toNumber());
    RETURN_RESULT(QV4::Value::fromBoolean(pointInPath).asReturnedValue());
}

QV4::ReturnedValue QQuickJSContext2DPrototype::method_drawFocusRing(const QV4::FunctionObject *b, const QV4::Value *, const QV4::Value *, int)
{
    QV4::Scope scope(b);
    THROW_DOM(DOMEXCEPTION_NOT_SUPPORTED_ERR, "Context2D::drawFocusRing is not supported");
}

QV4::ReturnedValue QQuickJSContext2DPrototype::method_setCaretSelectionRect(const QV4::FunctionObject *b, const QV4::Value *, const QV4::Value *, int)
{
    QV4::Scope scope(b);
    THROW_DOM(DOMEXCEPTION_NOT_SUPPORTED_ERR, "Context2D::setCaretSelectionRect is not supported");
}

QV4::ReturnedValue QQuickJSContext2DPrototype::method_caretBlinkRate(const QV4::FunctionObject *b, const QV4::Value *, const QV4::Value *, int)
{
    QV4::Scope scope(b);
    THROW_DOM(DOMEXCEPTION_NOT_SUPPORTED_ERR, "Context2D::caretBlinkRate is not supported");
}

/*!
    \qmlproperty string QtQuick::Context2D::font
    Holds the current font settings.

    A subset of the
    \l {http://www.w3.org/TR/2dcontext/#dom-context-2d-font}{w3C 2d context standard for font}
    is supported:

    \list
        \li font-style (optional):
        normal | italic | oblique
        \li font-variant (optional): normal | small-caps
        \li font-weight (optional): normal | bold | 1 ... 1000
        \li font-size: Npx | Npt (where N is a positive number)
        \li font-family: See \l {http://www.w3.org/TR/CSS2/fonts.html#propdef-font-family}
    \endlist

    \note The font-size and font-family properties are mandatory and must be in
    the order they are shown in above. In addition, a font family with spaces in
    its name must be quoted.

    The default font value is "10px sans-serif".
  */
QV4::ReturnedValue QQuickJSContext2D::method_get_font(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int)
{
    QV4::Scope scope(b);
    QV4::Scoped<QQuickJSContext2D> r(scope, *thisObject);
    CHECK_CONTEXT(r)

    RETURN_RESULT(scope.engine->newString(r->d()->context()->state.font.toString()));
}

QV4::ReturnedValue QQuickJSContext2D::method_set_font(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc)
{
    QV4::Scope scope(b);
    QV4::Scoped<QQuickJSContext2D> r(scope, *thisObject);
    CHECK_CONTEXT_SETTER(r)

    QV4::ScopedString s(scope, argc ? argv[0] : QV4::Value::undefinedValue(), QV4::ScopedString::Convert);
    if (scope.hasException())
        RETURN_UNDEFINED();
    QFont font = qt_font_from_string(s->toQString(), r->d()->context()->state.font);
    if (font != r->d()->context()->state.font) {
        r->d()->context()->state.font = font;
    }
    RETURN_UNDEFINED();
}

/*!
    \qmlproperty string QtQuick::Context2D::textAlign

    Holds the current text alignment settings. The possible values are:

    \value "start"  (default) Align to the start edge of the text (left side in
                    left-to-right text, right side in right-to-left text).
    \value "end"    Align to the end edge of the text (right side in left-to-right
                    text, left side in right-to-left text).
    \value "left"   Qt::AlignLeft
    \value "right"  Qt::AlignRight
    \value "center" Qt::AlignHCenter

    Other values are ignored.
*/
QV4::ReturnedValue QQuickJSContext2D::method_get_textAlign(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int)
{
    QV4::Scope scope(b);
    QV4::Scoped<QQuickJSContext2D> r(scope, *thisObject);
    CHECK_CONTEXT(r)

    switch (r->d()->context()->state.textAlign) {
    case QQuickContext2D::End:
        RETURN_RESULT(scope.engine->newString(QStringLiteral("end")));
    case QQuickContext2D::Left:
        RETURN_RESULT(scope.engine->newString(QStringLiteral("left")));
    case QQuickContext2D::Right:
        RETURN_RESULT(scope.engine->newString(QStringLiteral("right")));
    case QQuickContext2D::Center:
        RETURN_RESULT(scope.engine->newString(QStringLiteral("center")));
    case QQuickContext2D::Start:
    default:
        break;
    }
    RETURN_RESULT(scope.engine->newString(QStringLiteral("start")));
}

QV4::ReturnedValue QQuickJSContext2D::method_set_textAlign(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc)
{
    QV4::Scope scope(b);
    QV4::Scoped<QQuickJSContext2D> r(scope, *thisObject);
    CHECK_CONTEXT_SETTER(r)

    QV4::ScopedString s(scope, argc ? argv[0] : QV4::Value::undefinedValue(), QV4::ScopedString::Convert);
    if (scope.hasException())
        RETURN_UNDEFINED();
    QString textAlign = s->toQString();

    QQuickContext2D::TextAlignType ta;
    if (textAlign == QLatin1String("start"))
        ta = QQuickContext2D::Start;
    else if (textAlign == QLatin1String("end"))
        ta = QQuickContext2D::End;
    else if (textAlign == QLatin1String("left"))
        ta = QQuickContext2D::Left;
    else if (textAlign == QLatin1String("right"))
        ta = QQuickContext2D::Right;
    else if (textAlign == QLatin1String("center"))
        ta = QQuickContext2D::Center;
    else
        RETURN_UNDEFINED();

    if (ta != r->d()->context()->state.textAlign)
        r->d()->context()->state.textAlign = ta;

    RETURN_UNDEFINED();
}

/*!
    \qmlproperty string QtQuick::Context2D::textBaseline

    Holds the current baseline alignment settings. The possible values are:

    \value "top"            The top of the em square
    \value "hanging"        The hanging baseline
    \value "middle"         The middle of the em square
    \value "alphabetic"     (default) The alphabetic baseline
    \value "ideographic"    The ideographic-under baseline
    \value "bottom"         The bottom of the em square

    Other values are ignored. The default value is "alphabetic".
*/
QV4::ReturnedValue QQuickJSContext2D::method_get_textBaseline(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int)
{
    QV4::Scope scope(b);
    QV4::Scoped<QQuickJSContext2D> r(scope, *thisObject);
    CHECK_CONTEXT(r)

    switch (r->d()->context()->state.textBaseline) {
    case QQuickContext2D::Hanging:
        RETURN_RESULT(scope.engine->newString(QStringLiteral("hanging")));
    case QQuickContext2D::Top:
        RETURN_RESULT(scope.engine->newString(QStringLiteral("top")));
    case QQuickContext2D::Bottom:
        RETURN_RESULT(scope.engine->newString(QStringLiteral("bottom")));
    case QQuickContext2D::Middle:
        RETURN_RESULT(scope.engine->newString(QStringLiteral("middle")));
    case QQuickContext2D::Alphabetic:
    default:
        break;
    }
    RETURN_RESULT(scope.engine->newString(QStringLiteral("alphabetic")));
}

QV4::ReturnedValue QQuickJSContext2D::method_set_textBaseline(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc)
{
    QV4::Scope scope(b);
    QV4::Scoped<QQuickJSContext2D> r(scope, *thisObject);
    CHECK_CONTEXT_SETTER(r)
    QV4::ScopedString s(scope, argc ? argv[0] : QV4::Value::undefinedValue(), QV4::ScopedString::Convert);
    if (scope.hasException())
        RETURN_UNDEFINED();
    QString textBaseline = s->toQString();

    QQuickContext2D::TextBaseLineType tb;
    if (textBaseline == QLatin1String("alphabetic"))
        tb = QQuickContext2D::Alphabetic;
    else if (textBaseline == QLatin1String("hanging"))
        tb = QQuickContext2D::Hanging;
    else if (textBaseline == QLatin1String("top"))
        tb = QQuickContext2D::Top;
    else if (textBaseline == QLatin1String("bottom"))
        tb = QQuickContext2D::Bottom;
    else if (textBaseline == QLatin1String("middle"))
        tb = QQuickContext2D::Middle;
    else
        RETURN_UNDEFINED();

    if (tb != r->d()->context()->state.textBaseline)
        r->d()->context()->state.textBaseline = tb;

    RETURN_UNDEFINED();
}

/*!
  \qmlmethod object QtQuick::Context2D::fillText(text, x, y)

  Fills the specified \a text at the given position (\a x, \a y).

  \sa font
  \sa textAlign
  \sa textBaseline
  \sa strokeText
  */
QV4::ReturnedValue QQuickJSContext2DPrototype::method_fillText(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc)
{
    QV4::Scope scope(b);
    QV4::Scoped<QQuickJSContext2D> r(scope, *thisObject);
    CHECK_CONTEXT(r)

    if (argc >= 3) {
        qreal x = argv[1].toNumber();
        qreal y = argv[2].toNumber();
        if (!qt_is_finite(x) || !qt_is_finite(y))
            RETURN_UNDEFINED();
        QPainterPath textPath = r->d()->context()->createTextGlyphs(x, y, argv[0].toQStringNoThrow());
        r->d()->context()->buffer()->fill(textPath);
    }

    RETURN_RESULT(*thisObject);
}
/*!
    \qmlmethod object QtQuick::Context2D::strokeText(text, x, y)

    Strokes the given \a text at a position specified by (\a x, \a y).

    \sa font
    \sa textAlign
    \sa textBaseline
    \sa fillText
*/
QV4::ReturnedValue QQuickJSContext2DPrototype::method_strokeText(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc)
{
    QV4::Scope scope(b);
    QV4::Scoped<QQuickJSContext2D> r(scope, *thisObject);
    CHECK_CONTEXT(r)

    if (argc >= 3)
        r->d()->context()->drawText(argv[0].toQStringNoThrow(), argv[1].toNumber(), argv[2].toNumber(), false);

    RETURN_RESULT(*thisObject);
}

/*!
  \qmlmethod object QtQuick::Context2D::measureText(text)

  Returns an object with a \c width property, whose value is equivalent to
  calling QFontMetrics::horizontalAdvance() with the given \a text in the
  current font.
  */
QV4::ReturnedValue QQuickJSContext2DPrototype::method_measureText(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc)
{
    QV4::Scope scope(b);
    QV4::Scoped<QQuickJSContext2D> r(scope, *thisObject);
    CHECK_CONTEXT(r)

    if (argc >= 1) {
        QFontMetrics fm(r->d()->context()->state.font);
        uint width = fm.horizontalAdvance(argv[0].toQStringNoThrow());
        QV4::ScopedObject tm(scope, scope.engine->newObject());
        tm->put(QV4::ScopedString(scope, scope.engine->newIdentifier(QStringLiteral("width"))).getPointer(),
                QV4::ScopedValue(scope, QV4::Value::fromDouble(width)));
        RETURN_RESULT(*tm);
    }
    RETURN_UNDEFINED();
}

// drawing images
/*!
  \qmlmethod QtQuick::Context2D::drawImage(variant image, real dx, real dy)
  Draws the given \a image on the canvas at position (\a dx, \a dy).
  Note:
  The \a image type can be an Image item, an image url or a CanvasImageData object.
  When given as Image item, if the image isn't fully loaded, this method draws nothing.
  When given as url string, the image should be loaded by calling Canvas item's Canvas::loadImage() method first.
  This image been drawing is subject to the current context clip path, even the given \c image is a CanvasImageData object.

  \sa CanvasImageData
  \sa Image
  \sa Canvas::loadImage
  \sa Canvas::isImageLoaded
  \sa Canvas::imageLoaded

  \sa {http://www.w3.org/TR/2dcontext/#dom-context-2d-drawimage}{W3C 2d context standard for drawImage}
  */
/*!
  \qmlmethod QtQuick::Context2D::drawImage(variant image, real dx, real dy, real dw, real dh)
  This is an overloaded function.
  Draws the given item as \a image onto the canvas at point (\a dx, \a dy) and with width \a dw,
  height \a dh.

  Note:
  The \a image type can be an Image item, an image url or a CanvasImageData object.
  When given as Image item, if the image isn't fully loaded, this method draws nothing.
  When given as url string, the image should be loaded by calling Canvas item's Canvas::loadImage() method first.
  This image been drawing is subject to the current context clip path, even the given \c image is a CanvasImageData object.

  \sa CanvasImageData
  \sa Image
  \sa Canvas::loadImage()
  \sa Canvas::isImageLoaded
  \sa Canvas::imageLoaded

  \sa {http://www.w3.org/TR/2dcontext/#dom-context-2d-drawimage}{W3C 2d context standard for drawImage}
  */
/*!
  \qmlmethod QtQuick::Context2D::drawImage(variant image, real sx, real sy, real sw, real sh, real dx, real dy, real dw, real dh)
  This is an overloaded function.
  Draws the given item as \a image from source point (\a sx, \a sy) and source width \a sw, source height \a sh
  onto the canvas at point (\a dx, \a dy) and with width \a dw, height \a dh.


  Note:
  The \a image type can be an Image or Canvas item, an image url or a CanvasImageData object.
  When given as Image item, if the image isn't fully loaded, this method draws nothing.
  When given as url string, the image should be loaded by calling Canvas item's Canvas::loadImage() method first.
  This image been drawing is subject to the current context clip path, even the given \c image is a CanvasImageData object.

  \sa CanvasImageData
  \sa Image
  \sa Canvas::loadImage()
  \sa Canvas::isImageLoaded
  \sa Canvas::imageLoaded

  \sa {http://www.w3.org/TR/2dcontext/#dom-context-2d-drawimage}{W3C 2d context standard for drawImage}
*/
QV4::ReturnedValue QQuickJSContext2DPrototype::method_drawImage(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc)
{
    QV4::Scope scope(b);
    QV4::Scoped<QQuickJSContext2D> r(scope, *thisObject);
    CHECK_CONTEXT(r)

    qreal sx, sy, sw, sh, dx, dy, dw, dh;

    if (!argc)
        RETURN_UNDEFINED();

    //FIXME:This function should be moved to QQuickContext2D::drawImage(...)
    if (!r->d()->context()->state.invertibleCTM)
        RETURN_UNDEFINED();

    QQmlRefPointer<QQuickCanvasPixmap> pixmap;

    QV4::ScopedValue arg(scope, argv[0]);
    if (arg->isString()) {
        QUrl url(arg->toQString());
        if (!url.isValid())
            THROW_DOM(DOMEXCEPTION_TYPE_MISMATCH_ERR, "drawImage(), type mismatch");

        pixmap = r->d()->context()->createPixmap(url);
    } else if (arg->isObject()) {
        QV4::Scoped<QV4::QObjectWrapper> qobjectWrapper(scope, arg);
        if (!!qobjectWrapper) {
            if (QQuickImage *imageItem = qobject_cast<QQuickImage*>(qobjectWrapper->object())) {
                pixmap = r->d()->context()->createPixmap(imageItem->source());
            } else if (QQuickCanvasItem *canvas = qobject_cast<QQuickCanvasItem*>(qobjectWrapper->object())) {
                QImage img = canvas->toImage();
                if (!img.isNull())
                    pixmap.adopt(new QQuickCanvasPixmap(img));
            } else {
                THROW_DOM(DOMEXCEPTION_TYPE_MISMATCH_ERR, "drawImage(), type mismatch");
            }
        } else {
            QV4::Scoped<QQuickJSContext2DImageData> imageData(scope, arg);
            if (!!imageData) {
                QV4::Scoped<QQuickJSContext2DPixelData> pix(scope, imageData->d()->pixelData.as<QQuickJSContext2DPixelData>());
                if (pix && !pix->d()->image->isNull()) {
                    pixmap.adopt(new QQuickCanvasPixmap(*pix->d()->image));
                } else {
                    THROW_DOM(DOMEXCEPTION_TYPE_MISMATCH_ERR, "drawImage(), type mismatch");
                }
            } else {
                QUrl url(arg->toQStringNoThrow());
                if (url.isValid())
                    pixmap = r->d()->context()->createPixmap(url);
                else
                    THROW_DOM(DOMEXCEPTION_TYPE_MISMATCH_ERR, "drawImage(), type mismatch");
            }
        }
    } else {
        THROW_DOM(DOMEXCEPTION_TYPE_MISMATCH_ERR, "drawImage(), type mismatch");
    }

    if (pixmap.isNull() || !pixmap->isValid())
        RETURN_UNDEFINED();

    if (argc >= 9) {
        sx = argv[1].toNumber();
        sy = argv[2].toNumber();
        sw = argv[3].toNumber();
        sh = argv[4].toNumber();
        dx = argv[5].toNumber();
        dy = argv[6].toNumber();
        dw = argv[7].toNumber();
        dh = argv[8].toNumber();
    } else if (argc >= 5) {
        sx = 0;
        sy = 0;
        sw = pixmap->width();
        sh = pixmap->height();
        dx = argv[1].toNumber();
        dy = argv[2].toNumber();
        dw = argv[3].toNumber();
        dh = argv[4].toNumber();
    } else if (argc >= 3) {
        dx = argv[1].toNumber();
        dy = argv[2].toNumber();
        sx = 0;
        sy = 0;
        sw = pixmap->width();
        sh = pixmap->height();
        dw = sw;
        dh = sh;
    } else {
        RETURN_UNDEFINED();
    }

    if (!qt_is_finite(sx)
     || !qt_is_finite(sy)
     || !qt_is_finite(sw)
     || !qt_is_finite(sh)
     || !qt_is_finite(dx)
     || !qt_is_finite(dy)
     || !qt_is_finite(dw)
     || !qt_is_finite(dh))
        RETURN_UNDEFINED();

    if (sx < 0
    || sy < 0
    || sw == 0
    || sh == 0
    || sx + sw > pixmap->width()
    || sy + sh > pixmap->height()
    || sx + sw < 0 || sy + sh < 0) {
            THROW_DOM(DOMEXCEPTION_INDEX_SIZE_ERR, "drawImage(), index size error");
    }

    r->d()->context()->buffer()->drawPixmap(pixmap, QRectF(sx, sy, sw, sh), QRectF(dx, dy, dw, dh));

    RETURN_RESULT(*thisObject);
}

// pixel manipulation
/*!
    \qmltype CanvasImageData
    \inqmlmodule QtQuick
    \ingroup qtquick-canvas
    \brief Contains image pixel data in RGBA order.

     The CanvasImageData object holds the image pixel data.

     The CanvasImageData object has the actual dimensions of the data stored in
     this object and holds the one-dimensional array containing the data in RGBA order,
     as integers in the range 0 to 255.

     \sa width
     \sa height
     \sa data
     \sa Context2D::createImageData()
     \sa Context2D::getImageData()
     \sa Context2D::putImageData()
  */
/*!
  \qmlproperty int QtQuick::CanvasImageData::width
  Holds the actual width dimension of the data in the ImageData object, in device pixels.
 */
QV4::ReturnedValue QQuickJSContext2DImageData::method_get_width(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int)
{
    QV4::Scope scope(b);
    QV4::Scoped<QQuickJSContext2DImageData> imageData(scope, *thisObject);
    if (!imageData)
        THROW_TYPE_ERROR();
    QV4::Scoped<QQuickJSContext2DPixelData> r(scope, imageData->d()->pixelData.as<QQuickJSContext2DPixelData>());
    int width = r ? r->d()->image->width() : 0;
    RETURN_RESULT(QV4::Encode(width));
}

/*!
  \qmlproperty int QtQuick::CanvasImageData::height
  Holds the actual height dimension of the data in the ImageData object, in device pixels.
  */
QV4::ReturnedValue QQuickJSContext2DImageData::method_get_height(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int)
{
    QV4::Scope scope(b);
    QV4::Scoped<QQuickJSContext2DImageData> imageData(scope, *thisObject);
    if (!imageData)
        THROW_TYPE_ERROR();
    QV4::Scoped<QQuickJSContext2DPixelData> r(scope, imageData->d()->pixelData.as<QQuickJSContext2DPixelData>());
    int height = r ? r->d()->image->height() : 0;
    RETURN_RESULT(QV4::Encode(height));
}

/*!
  \qmlproperty object QtQuick::CanvasImageData::data
  Holds the one-dimensional array containing the data in RGBA order, as integers in the range 0 to 255.
 */
QV4::ReturnedValue QQuickJSContext2DImageData::method_get_data(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int)
{
    QV4::Scope scope(b);
    QV4::Scoped<QQuickJSContext2DImageData> imageData(scope, *thisObject);
    if (!imageData)
        THROW_TYPE_ERROR();
    RETURN_RESULT(imageData->d()->pixelData);
}

/*!
    \qmltype CanvasPixelArray
    \inqmlmodule QtQuick
    \ingroup qtquick-canvas
    \brief Provides ordered and indexed access to the components of each pixel in image data.

  The CanvasPixelArray object provides ordered, indexed access to the color components of each pixel of the image data.
  The CanvasPixelArray can be accessed as normal Javascript array.
    \sa CanvasImageData
    \sa {http://www.w3.org/TR/2dcontext/#canvaspixelarray}{W3C 2d context standard for PixelArray}
  */

/*!
  \qmlproperty int QtQuick::CanvasPixelArray::length
  The CanvasPixelArray object represents h×w×4 integers which w and h comes from CanvasImageData.
  The length attribute of a CanvasPixelArray object must return this h×w×4 number value.
  This property is read only.
*/
QV4::ReturnedValue QQuickJSContext2DPixelData::proto_get_length(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *, int)
{
    QV4::Scope scope(b);
    QV4::Scoped<QQuickJSContext2DPixelData> r(scope, thisObject->as<QQuickJSContext2DPixelData>());
    if (!r || r->d()->image->isNull())
        RETURN_UNDEFINED();

    RETURN_RESULT(QV4::Encode(r->d()->image->width() * r->d()->image->height() * 4));
}

QV4::ReturnedValue QQuickJSContext2DPixelData::virtualGet(const QV4::Managed *m, QV4::PropertyKey id, const QV4::Value *receiver, bool *hasProperty)
{
    if (!id.isArrayIndex())
        return QV4::Object::virtualGet(m, id, receiver, hasProperty);

    uint index = id.asArrayIndex();
    Q_ASSERT(m->as<QQuickJSContext2DPixelData>());
    QV4::ExecutionEngine *v4 = static_cast<const QQuickJSContext2DPixelData *>(m)->engine();
    QV4::Scope scope(v4);
    QV4::Scoped<QQuickJSContext2DPixelData> r(scope, static_cast<const QQuickJSContext2DPixelData *>(m));

    if (index < static_cast<quint32>(r->d()->image->width() * r->d()->image->height() * 4)) {
        if (hasProperty)
            *hasProperty = true;
        const quint32 w = r->d()->image->width();
        const quint32 row = (index / 4) / w;
        const quint32 col = (index / 4) % w;
        const QRgb* pixel = reinterpret_cast<const QRgb*>(r->d()->image->constScanLine(row));
        pixel += col;
        switch (index % 4) {
        case 0:
            return QV4::Encode(qRed(*pixel));
        case 1:
            return QV4::Encode(qGreen(*pixel));
        case 2:
            return QV4::Encode(qBlue(*pixel));
        case 3:
            return QV4::Encode(qAlpha(*pixel));
        }
    }

    if (hasProperty)
        *hasProperty = false;
    return QV4::Encode::undefined();
}

bool QQuickJSContext2DPixelData::virtualPut(QV4::Managed *m, QV4::PropertyKey id, const QV4::Value &value, QV4::Value *receiver)
{
    if (!id.isArrayIndex())
        return Object::virtualPut(m, id, value, receiver);

    Q_ASSERT(m->as<QQuickJSContext2DPixelData>());
    QV4::ExecutionEngine *v4 = static_cast<QQuickJSContext2DPixelData *>(m)->engine();
    QV4::Scope scope(v4);
    if (scope.hasException())
        return false;

    uint index = id.asArrayIndex();
    QV4::Scoped<QQuickJSContext2DPixelData> r(scope, static_cast<QQuickJSContext2DPixelData *>(m));

    const int v = value.toInt32();
    if (r && index < static_cast<quint32>(r->d()->image->width() * r->d()->image->height() * 4) && v >= 0 && v <= 255) {
        const quint32 w = r->d()->image->width();
        const quint32 row = (index / 4) / w;
        const quint32 col = (index / 4) % w;

        QRgb* pixel = reinterpret_cast<QRgb*>(r->d()->image->scanLine(row));
        pixel += col;
        switch (index % 4) {
        case 0:
            *pixel = qRgba(v, qGreen(*pixel), qBlue(*pixel), qAlpha(*pixel));
            break;
        case 1:
            *pixel = qRgba(qRed(*pixel), v, qBlue(*pixel), qAlpha(*pixel));
            break;
        case 2:
            *pixel = qRgba(qRed(*pixel), qGreen(*pixel), v, qAlpha(*pixel));
            break;
        case 3:
            *pixel = qRgba(qRed(*pixel), qGreen(*pixel), qBlue(*pixel), v);
            break;
        }
        return true;
    }

    return false;
}
/*!
    \qmlmethod CanvasImageData QtQuick::Context2D::createImageData(real sw, real sh)

    Creates a CanvasImageData object with the given dimensions(\a sw, \a sh).
*/
/*!
    \qmlmethod CanvasImageData QtQuick::Context2D::createImageData(CanvasImageData imageData)

    Creates a CanvasImageData object with the same dimensions as the \a imageData argument.
*/
/*!
    \qmlmethod CanvasImageData QtQuick::Context2D::createImageData(Url imageUrl)

    Creates a CanvasImageData object with the given image loaded from \a imageUrl.

    \note The \a imageUrl must be already loaded before this function call,
    otherwise an empty CanvasImageData obect will be returned.

    \sa Canvas::loadImage(), QtQuick::Canvas::unloadImage(),
        QtQuick::Canvas::isImageLoaded
  */
QV4::ReturnedValue QQuickJSContext2DPrototype::method_createImageData(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc)
{
    QV4::Scope scope(b);
    QV4::Scoped<QQuickJSContext2D> r(scope, thisObject->as<QQuickJSContext2D>());
    CHECK_CONTEXT(r)

    if (argc == 1) {
        QV4::ScopedValue arg0(scope, argv[0]);
        QV4::Scoped<QQuickJSContext2DImageData> imgData(scope, arg0);
        if (!!imgData) {
            QV4::Scoped<QQuickJSContext2DPixelData> pa(scope, imgData->d()->pixelData.as<QQuickJSContext2DPixelData>());
            if (pa) {
                qreal w = pa->d()->image->width();
                qreal h = pa->d()->image->height();
                RETURN_RESULT(qt_create_image_data(w, h, scope.engine, QImage()));
            }
        } else if (arg0->isString()) {
            QImage image = r->d()->context()->createPixmap(QUrl(arg0->toQStringNoThrow()))->image();
            RETURN_RESULT(qt_create_image_data(image.width(), image.height(), scope.engine, std::move(image)));
        }
    } else if (argc == 2) {
        qreal w = argv[0].toNumber();
        qreal h = argv[1].toNumber();

        if (!qt_is_finite(w) || !qt_is_finite(h))
            THROW_DOM(DOMEXCEPTION_NOT_SUPPORTED_ERR, "createImageData(): invalid arguments");

        if (w > 0 && h > 0)
            RETURN_RESULT(qt_create_image_data(w, h, scope.engine, QImage()));
        else
            THROW_DOM(DOMEXCEPTION_INDEX_SIZE_ERR, "createImageData(): invalid arguments");
    }
    RETURN_UNDEFINED();
}

/*!
    \qmlmethod CanvasImageData QtQuick::Context2D::getImageData(real x, real y, real w, real h)

    Returns an CanvasImageData object containing the image data for the canvas
    rectangle specified by (\a x, \a y, \a w, \a h).
  */
QV4::ReturnedValue QQuickJSContext2DPrototype::method_getImageData(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc)
{
    QV4::Scope scope(b);
    QV4::Scoped<QQuickJSContext2D> r(scope, thisObject->as<QQuickJSContext2D>());
    CHECK_CONTEXT(r)

    if (argc >= 4) {
        qreal x = argv[0].toNumber();
        qreal y = argv[1].toNumber();
        qreal w = argv[2].toNumber();
        qreal h = argv[3].toNumber();
        if (!qt_is_finite(x) || !qt_is_finite(y) || !qt_is_finite(w) || !qt_is_finite(h))
            THROW_DOM(DOMEXCEPTION_NOT_SUPPORTED_ERR, "getImageData(): Invalid arguments");

        if (w <= 0 || h <= 0)
            THROW_DOM(DOMEXCEPTION_INDEX_SIZE_ERR, "getImageData(): Invalid arguments");

        QImage image = r->d()->context()->canvas()->toImage(QRectF(x, y, w, h));
        RETURN_RESULT(qt_create_image_data(w, h, scope.engine, std::move(image)));
    }
    RETURN_RESULT(QV4::Encode::null());
}

/*!
    \qmlmethod object QtQuick::Context2D::putImageData(CanvasImageData imageData, real dx, real dy, real dirtyX, real dirtyY, real dirtyWidth, real dirtyHeight)

    Paints the data from the given \a imageData object onto the canvas at
    (\a dx, \a dy).

    If a dirty rectangle (\a dirtyX, \a dirtyY, \a dirtyWidth, \a dirtyHeight)
    is provided, only the pixels from that rectangle are painted.
  */
QV4::ReturnedValue QQuickJSContext2DPrototype::method_putImageData(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc)
{
    QV4::Scope scope(b);
    QV4::Scoped<QQuickJSContext2D> r(scope, thisObject->as<QQuickJSContext2D>());
    CHECK_CONTEXT(r)
    if (argc < 7)
        RETURN_UNDEFINED();

    QV4::ScopedValue arg0(scope, argv[0]);
    if (!arg0->isObject())
        THROW_DOM(DOMEXCEPTION_TYPE_MISMATCH_ERR, "Context2D::putImageData, the image data type mismatch");

    qreal dx = argv[1].toNumber();
    qreal dy = argv[2].toNumber();
    qreal w, h, dirtyX, dirtyY, dirtyWidth, dirtyHeight;

    if (!qt_is_finite(dx) || !qt_is_finite(dy))
        THROW_DOM(DOMEXCEPTION_NOT_SUPPORTED_ERR, "putImageData() : Invalid arguments");

    QV4::Scoped<QQuickJSContext2DImageData> imageData(scope, arg0);
    if (!imageData)
        RETURN_UNDEFINED();

    QV4::Scoped<QQuickJSContext2DPixelData> pixelArray(scope, imageData->d()->pixelData.as<QQuickJSContext2DPixelData>());
    if (pixelArray) {
        w = pixelArray->d()->image->width();
        h = pixelArray->d()->image->height();

        if (argc == 7) {
            dirtyX = argv[3].toNumber();
            dirtyY = argv[4].toNumber();
            dirtyWidth = argv[5].toNumber();
            dirtyHeight = argv[6].toNumber();

            if (!qt_is_finite(dirtyX) || !qt_is_finite(dirtyY) || !qt_is_finite(dirtyWidth) || !qt_is_finite(dirtyHeight))
                THROW_DOM(DOMEXCEPTION_NOT_SUPPORTED_ERR, "putImageData() : Invalid arguments");


            if (dirtyWidth < 0) {
                dirtyX = dirtyX+dirtyWidth;
                dirtyWidth = -dirtyWidth;
            }

            if (dirtyHeight < 0) {
                dirtyY = dirtyY+dirtyHeight;
                dirtyHeight = -dirtyHeight;
            }

            if (dirtyX < 0) {
                dirtyWidth = dirtyWidth+dirtyX;
                dirtyX = 0;
            }

            if (dirtyY < 0) {
                dirtyHeight = dirtyHeight+dirtyY;
                dirtyY = 0;
            }

            if (dirtyX+dirtyWidth > w) {
                dirtyWidth = w - dirtyX;
            }

            if (dirtyY+dirtyHeight > h) {
                dirtyHeight = h - dirtyY;
            }

            if (dirtyWidth <=0 || dirtyHeight <= 0)
                RETURN_UNDEFINED();
        } else {
            dirtyX = 0;
            dirtyY = 0;
            dirtyWidth = w;
            dirtyHeight = h;
        }

        QImage image = pixelArray->d()->image->copy(dirtyX, dirtyY, dirtyWidth, dirtyHeight);
        r->d()->context()->buffer()->drawImage(image, QRectF(dirtyX, dirtyY, dirtyWidth, dirtyHeight), QRectF(dx, dy, dirtyWidth, dirtyHeight));
    }

    RETURN_RESULT(*thisObject);
}

/*!
    \qmltype CanvasGradient
    \inqmlmodule QtQuick
    \since 5.0
    \ingroup qtquick-canvas
    \brief Provides an opaque CanvasGradient interface.
  */

/*!
    \qmlmethod CanvasGradient QtQuick::CanvasGradient::addColorStop(real offset, string color)

    Adds a color stop with the given \a color to the gradient at the given \a offset.
    0.0 is the offset at one end of the gradient, 1.0 is the offset at the other end.

    For example:

    \code
    var gradient = ctx.createLinearGradient(0, 0, 100, 100);
    gradient.addColorStop(0.3, Qt.rgba(1, 0, 0, 1));
    gradient.addColorStop(0.7, 'rgba(0, 255, 255, 1)');
    \endcode
  */
QV4::ReturnedValue QQuickContext2DStyle::gradient_proto_addColorStop(const QV4::FunctionObject *b, const QV4::Value *thisObject, const QV4::Value *argv, int argc)
{
    QV4::Scope scope(b);
    QV4::Scoped<QQuickContext2DStyle> style(scope, thisObject->as<QQuickContext2DStyle>());
    if (!style)
        THROW_GENERIC_ERROR("Not a CanvasGradient object");

    if (argc == 2) {

        if (!style->d()->brush->gradient())
            THROW_GENERIC_ERROR("Not a valid CanvasGradient object, can't get the gradient information");
        QGradient gradient = *(style->d()->brush->gradient());
        qreal pos = argv[0].toNumber();
        QColor color;

        if (argv[1].as<Object>()) {
            color = QV4::ExecutionEngine::toVariant(
                        argv[1], QMetaType::fromType<QColor>()).value<QColor>();
        } else {
            color = qt_color_from_string(argv[1]);
        }
        if (pos < 0.0 || pos > 1.0 || !qt_is_finite(pos)) {
            THROW_DOM(DOMEXCEPTION_INDEX_SIZE_ERR, "CanvasGradient: parameter offset out of range");
        }

        if (color.isValid()) {
            gradient.setColorAt(pos, color);
        } else {
            THROW_DOM(DOMEXCEPTION_SYNTAX_ERR, "CanvasGradient: parameter color is not a valid color string");
        }
        *style->d()->brush = gradient;
    }

    return thisObject->asReturnedValue();
}

void QQuickContext2D::scale(qreal x,  qreal y)
{
    if (!state.invertibleCTM)
        return;

    if (!qt_is_finite(x) || !qt_is_finite(y))
        return;

    QTransform newTransform = state.matrix;
    newTransform.scale(x, y);

    if (!newTransform.isInvertible()) {
        state.invertibleCTM = false;
        return;
    }

    state.matrix = newTransform;
    buffer()->updateMatrix(state.matrix);
    m_path = QTransform().scale(1.0 / x, 1.0 / y).map(m_path);
}

void QQuickContext2D::rotate(qreal angle)
{
    if (!state.invertibleCTM)
        return;

    if (!qt_is_finite(angle))
        return;

    QTransform newTransform =state.matrix;
    newTransform.rotate(qRadiansToDegrees(angle));

    if (!newTransform.isInvertible()) {
        state.invertibleCTM = false;
        return;
    }

    state.matrix = newTransform;
    buffer()->updateMatrix(state.matrix);
    m_path = QTransform().rotate(-qRadiansToDegrees(angle)).map(m_path);
}

void QQuickContext2D::shear(qreal h, qreal v)
{
    if (!state.invertibleCTM)
        return;

    if (!qt_is_finite(h) || !qt_is_finite(v))
        return ;

    QTransform newTransform = state.matrix;
    newTransform.shear(h, v);

    if (!newTransform.isInvertible()) {
        state.invertibleCTM = false;
        return;
    }

    state.matrix = newTransform;
    buffer()->updateMatrix(state.matrix);
    m_path = QTransform().shear(-h, -v).map(m_path);
}

void QQuickContext2D::translate(qreal x, qreal y)
{
    if (!state.invertibleCTM)
        return;

    if (!qt_is_finite(x) || !qt_is_finite(y))
        return ;

    QTransform newTransform = state.matrix;
    newTransform.translate(x, y);

    if (!newTransform.isInvertible()) {
        state.invertibleCTM = false;
        return;
    }

    state.matrix = newTransform;
    buffer()->updateMatrix(state.matrix);
    m_path = QTransform().translate(-x, -y).map(m_path);
}

void QQuickContext2D::transform(qreal a, qreal b, qreal c, qreal d, qreal e, qreal f)
{
    if (!state.invertibleCTM)
        return;

    if (!qt_is_finite(a) || !qt_is_finite(b) || !qt_is_finite(c) || !qt_is_finite(d) || !qt_is_finite(e) || !qt_is_finite(f))
        return;

    QTransform transform(a, b, c, d, e, f);
    QTransform newTransform = state.matrix * transform;

    if (!newTransform.isInvertible()) {
        state.invertibleCTM = false;
        return;
    }
    state.matrix = newTransform;
    buffer()->updateMatrix(state.matrix);
    m_path = transform.inverted().map(m_path);
}

void QQuickContext2D::setTransform(qreal a, qreal b, qreal c, qreal d, qreal e, qreal f)
{
    if (!qt_is_finite(a) || !qt_is_finite(b) || !qt_is_finite(c) || !qt_is_finite(d) || !qt_is_finite(e) || !qt_is_finite(f))
        return;

    QTransform ctm = state.matrix;
    if (!ctm.isInvertible())
        return;

    state.matrix = ctm.inverted() * state.matrix;
    m_path = ctm.map(m_path);
    state.invertibleCTM = true;
    transform(a, b, c, d, e, f);
}

void QQuickContext2D::fill()
{
    if (!state.invertibleCTM)
        return;

    if (!m_path.elementCount())
        return;

    m_path.setFillRule(state.fillRule);
    buffer()->fill(m_path);
}

void QQuickContext2D::clip()
{
    if (!state.invertibleCTM)
        return;

    QPainterPath clipPath = m_path;
    clipPath.closeSubpath();
    if (state.clip) {
        state.clipPath = clipPath.intersected(state.clipPath);
    } else {
        state.clip = true;
        state.clipPath = clipPath;
    }
    buffer()->clip(state.clip, state.clipPath);
}

void QQuickContext2D::stroke()
{
    if (!state.invertibleCTM)
        return;

    if (!m_path.elementCount())
        return;

    buffer()->stroke(m_path);
}

void QQuickContext2D::fillRect(qreal x, qreal y, qreal w, qreal h)
{
    if (!state.invertibleCTM)
        return;

    if (!qt_is_finite(x) || !qt_is_finite(y) || !qt_is_finite(w) || !qt_is_finite(h))
        return;

    buffer()->fillRect(QRectF(x, y, w, h));
}

void QQuickContext2D::strokeRect(qreal x, qreal y, qreal w, qreal h)
{
    if (!state.invertibleCTM)
        return;

    if (!qt_is_finite(x) || !qt_is_finite(y) || !qt_is_finite(w) || !qt_is_finite(h))
        return;

    buffer()->strokeRect(QRectF(x, y, w, h));
}

void QQuickContext2D::clearRect(qreal x, qreal y, qreal w, qreal h)
{
    if (!state.invertibleCTM)
        return;

    if (!qt_is_finite(x) || !qt_is_finite(y) || !qt_is_finite(w) || !qt_is_finite(h))
        return;

    buffer()->clearRect(QRectF(x, y, w, h));
}

void QQuickContext2D::drawText(const QString& text, qreal x, qreal y, bool fill)
{
    if (!state.invertibleCTM)
        return;

    if (!qt_is_finite(x) || !qt_is_finite(y))
        return;

    QPainterPath textPath = createTextGlyphs(x, y, text);
    if (fill)
        buffer()->fill(textPath);
    else
        buffer()->stroke(textPath);
}


void QQuickContext2D::beginPath()
{
    if (!m_path.elementCount())
        return;
    m_path = QPainterPath();
}

void QQuickContext2D::closePath()
{
    if (!m_path.elementCount())
        return;

    QRectF boundRect = m_path.boundingRect();
    if (boundRect.width() || boundRect.height())
        m_path.closeSubpath();
    //FIXME:QPainterPath set the current point to (0,0) after close subpath
    //should be the first point of the previous subpath
}

void QQuickContext2D::moveTo( qreal x, qreal y)
{
    if (!state.invertibleCTM)
        return;

    //FIXME: moveTo should not close the previous subpath
    m_path.moveTo(QPointF(x, y));
}

void QQuickContext2D::lineTo( qreal x, qreal y)
{
    if (!state.invertibleCTM)
        return;

    QPointF pt(x, y);

    if (!m_path.elementCount())
        m_path.moveTo(pt);
    else if (m_path.currentPosition() != pt)
        m_path.lineTo(pt);
}

void QQuickContext2D::quadraticCurveTo(qreal cpx, qreal cpy,
                                           qreal x, qreal y)
{
    if (!state.invertibleCTM)
        return;

    if (!m_path.elementCount())
        m_path.moveTo(QPointF(cpx, cpy));

    QPointF pt(x, y);
    if (m_path.currentPosition() != pt)
        m_path.quadTo(QPointF(cpx, cpy), pt);
}

void QQuickContext2D::bezierCurveTo(qreal cp1x, qreal cp1y,
                                        qreal cp2x, qreal cp2y,
                                        qreal x, qreal y)
{
    if (!state.invertibleCTM)
        return;

    if (!m_path.elementCount())
        m_path.moveTo(QPointF(cp1x, cp1y));

    QPointF pt(x, y);
    if (m_path.currentPosition() != pt)
        m_path.cubicTo(QPointF(cp1x, cp1y), QPointF(cp2x, cp2y),  pt);
}

void QQuickContext2D::addArcTo(const QPointF& p1, const QPointF& p2, qreal radius)
{
    QPointF p0(m_path.currentPosition());

    QPointF p1p0((p0.x() - p1.x()), (p0.y() - p1.y()));
    QPointF p1p2((p2.x() - p1.x()), (p2.y() - p1.y()));
    qreal p1p0_length = std::hypot(p1p0.x(), p1p0.y());
    qreal p1p2_length = std::hypot(p1p2.x(), p1p2.y());

    qreal cos_phi = QPointF::dotProduct(p1p0, p1p2) / (p1p0_length * p1p2_length);

    // The points p0, p1, and p2 are on the same straight line (HTML5, 4.8.11.1.8)
    // We could have used areCollinear() here, but since we're reusing
    // the variables computed above later on we keep this logic.
    if (qFuzzyCompare(std::abs(cos_phi), 1.0)) {
        m_path.lineTo(p1);
        return;
    }

    qreal tangent = radius / std::tan(std::acos(cos_phi) / 2);
    qreal factor_p1p0 = tangent / p1p0_length;
    QPointF t_p1p0((p1.x() + factor_p1p0 * p1p0.x()), (p1.y() + factor_p1p0 * p1p0.y()));

    QPointF orth_p1p0(p1p0.y(), -p1p0.x());
    qreal orth_p1p0_length = std::hypot(orth_p1p0.x(), orth_p1p0.y());
    qreal factor_ra = radius / orth_p1p0_length;

    // angle between orth_p1p0 and p1p2 to get the right vector orthographic to p1p0
    qreal cos_alpha = QPointF::dotProduct(orth_p1p0, p1p2) / (orth_p1p0_length * p1p2_length);
    if (cos_alpha < 0.f)
        orth_p1p0 = QPointF(-orth_p1p0.x(), -orth_p1p0.y());

    QPointF p((t_p1p0.x() + factor_ra * orth_p1p0.x()), (t_p1p0.y() + factor_ra * orth_p1p0.y()));

    // calculate angles for addArc
    orth_p1p0 = QPointF(-orth_p1p0.x(), -orth_p1p0.y());
    qreal sa = std::atan2(orth_p1p0.y(), orth_p1p0.x());

    // anticlockwise logic
    bool anticlockwise = false;

    qreal factor_p1p2 = tangent / p1p2_length;
    QPointF t_p1p2((p1.x() + factor_p1p2 * p1p2.x()), (p1.y() + factor_p1p2 * p1p2.y()));
    QPointF orth_p1p2((t_p1p2.x() - p.x()), (t_p1p2.y() - p.y()));
    qreal ea = std::atan2(orth_p1p2.y(), orth_p1p2.x());
    if ((sa > ea) && ((sa - ea) < M_PI))
        anticlockwise = true;
    if ((sa < ea) && ((ea - sa) > M_PI))
        anticlockwise = true;

    arc(p.x(), p.y(), radius, sa, ea, anticlockwise);
}

void QQuickContext2D::arcTo(qreal x1, qreal y1,
                                qreal x2, qreal y2,
                                qreal radius)
{
    if (!state.invertibleCTM)
        return;

    if (!qt_is_finite(x1) || !qt_is_finite(y1) || !qt_is_finite(x2) || !qt_is_finite(y2) || !qt_is_finite(radius))
        return;

    QPointF st(x1, y1);
    QPointF end(x2, y2);

    if (!m_path.elementCount())
        m_path.moveTo(st);
    else if (st == m_path.currentPosition() || st == end || !radius)
        lineTo(x1, y1);
    else
        addArcTo(st, end, radius);
 }

void QQuickContext2D::rect(qreal x, qreal y, qreal w, qreal h)
{
    if (!state.invertibleCTM)
        return;
    if (!qt_is_finite(x) || !qt_is_finite(y) || !qt_is_finite(w) || !qt_is_finite(h))
        return;

    if (!w && !h) {
        m_path.moveTo(x, y);
        return;
    }
    m_path.addRect(x, y, w, h);
}

void QQuickContext2D::roundedRect(qreal x, qreal y,
                               qreal w, qreal h,
                               qreal xr, qreal yr)
{
    if (!state.invertibleCTM)
        return;

    if (!qt_is_finite(x) || !qt_is_finite(y) || !qt_is_finite(w) || !qt_is_finite(h) || !qt_is_finite(xr) || !qt_is_finite(yr))
        return;

    if (!w && !h) {
        m_path.moveTo(x, y);
        return;
    }
    m_path.addRoundedRect(QRectF(x, y, w, h), xr, yr, Qt::AbsoluteSize);
}

void QQuickContext2D::ellipse(qreal x, qreal y,
                           qreal w, qreal h)
{
    if (!state.invertibleCTM)
        return;

    if (!qt_is_finite(x) || !qt_is_finite(y) || !qt_is_finite(w) || !qt_is_finite(h))
        return;

    if (!w && !h) {
        m_path.moveTo(x, y);
        return;
    }

    m_path.addEllipse(x, y, w, h);
}

void QQuickContext2D::text(const QString& str, qreal x, qreal y)
{
    if (!state.invertibleCTM)
        return;

    QPainterPath path;
    path.addText(x, y, state.font, str);
    m_path.addPath(path);
}

void QQuickContext2D::arc(qreal xc, qreal yc, qreal radius, qreal sar, qreal ear, bool antiClockWise)
{
    if (!state.invertibleCTM)
        return;

    if (!qt_is_finite(xc) || !qt_is_finite(yc) || !qt_is_finite(sar) || !qt_is_finite(ear) || !qt_is_finite(radius))
        return;

    if (sar == ear)
        return;


    //### HACK

    // In Qt we don't switch the coordinate system for degrees
    // and still use the 0,0 as bottom left for degrees so we need
    // to switch
    sar = -sar;
    ear = -ear;
    antiClockWise = !antiClockWise;
    //end hack

    float sa = qRadiansToDegrees(sar);
    float ea = qRadiansToDegrees(ear);

    double span = 0;

    double xs     = xc - radius;
    double ys     = yc - radius;
    double width  = radius*2;
    double height = radius*2;
    if ((!antiClockWise && (ea - sa >= 360)) || (antiClockWise && (sa - ea >= 360)))
        // If the anticlockwise argument is false and endAngle-startAngle is equal to or greater than 2*PI, or, if the
        // anticlockwise argument is true and startAngle-endAngle is equal to or greater than 2*PI, then the arc is the whole
        // circumference of this circle.
        span = 360;
    else {
        if (!antiClockWise && (ea < sa)) {
            span += 360;
        } else if (antiClockWise && (sa < ea)) {
            span -= 360;
        }
        //### this is also due to switched coordinate system
        // we would end up with a 0 span instead of 360
        if (!(qFuzzyCompare(span + (ea - sa) + 1, 1) &&
              qFuzzyCompare(qAbs(span), 360))) {
            span   += ea - sa;
        }
    }

    // If the path is empty, move to where the arc will start to avoid painting a line from (0,0)
    if (!m_path.elementCount())
        m_path.arcMoveTo(xs, ys, width, height, sa);
    else if (!radius) {
        m_path.lineTo(xc, yc);
        return;
    }

    m_path.arcTo(xs, ys, width, height, sa, span);
}

int baseLineOffset(QQuickContext2D::TextBaseLineType value, const QFontMetrics &metrics)
{
    int offset = 0;
    switch (value) {
    case QQuickContext2D::Top:
    case QQuickContext2D::Hanging:
        break;
    case QQuickContext2D::Middle:
        offset = (metrics.ascent() >> 1) + metrics.height() - metrics.ascent();
        break;
    case QQuickContext2D::Alphabetic:
        offset = metrics.ascent();
        break;
    case QQuickContext2D::Bottom:
        offset = metrics.height();
       break;
    }
    return offset;
}

static int textAlignOffset(QQuickContext2D::TextAlignType value, const QFontMetrics &metrics, const QString &text)
{
    int offset = 0;
    if (value == QQuickContext2D::Start)
        value = QGuiApplication::layoutDirection() == Qt::LeftToRight ? QQuickContext2D::Left : QQuickContext2D::Right;
    else if (value == QQuickContext2D::End)
        value = QGuiApplication::layoutDirection() == Qt::LeftToRight ? QQuickContext2D::Right: QQuickContext2D::Left;
    switch (value) {
    case QQuickContext2D::Center:
        offset = metrics.horizontalAdvance(text) / 2;
        break;
    case QQuickContext2D::Right:
        offset = metrics.horizontalAdvance(text);
        break;
    case QQuickContext2D::Left:
    default:
        break;
    }
    return offset;
}

void QQuickContext2D::setGrabbedImage(const QImage& grab)
{
    m_grabbedImage = grab;
    m_grabbed = true;
}

QQmlRefPointer<QQuickCanvasPixmap> QQuickContext2D::createPixmap(const QUrl& url, QSizeF sourceSize)
{
    return m_canvas->loadedPixmap(url, sourceSize);
}

QPainterPath QQuickContext2D::createTextGlyphs(qreal x, qreal y, const QString& text)
{
    const QFontMetrics metrics(state.font);
    int yoffset = baseLineOffset(static_cast<QQuickContext2D::TextBaseLineType>(state.textBaseline), metrics);
    int xoffset = textAlignOffset(static_cast<QQuickContext2D::TextAlignType>(state.textAlign), metrics, text);

    QPainterPath textPath;

    textPath.addText(x - xoffset, y - yoffset+metrics.ascent(), state.font, text);
    return textPath;
}


static inline bool areCollinear(const QPointF& a, const QPointF& b, const QPointF& c)
{
    // Solved from comparing the slopes of a to b and b to c: (ay-by)/(ax-bx) == (cy-by)/(cx-bx)
    return qFuzzyCompare((c.y() - b.y()) * (a.x() - b.x()), (a.y() - b.y()) * (c.x() - b.x()));
}

static inline bool withinRange(qreal p, qreal a, qreal b)
{
    return (p >= a && p <= b) || (p >= b && p <= a);
}

bool QQuickContext2D::isPointInPath(qreal x, qreal y) const
{
    if (!state.invertibleCTM)
        return false;

    if (!m_path.elementCount())
        return false;

    if (!qt_is_finite(x) || !qt_is_finite(y))
        return false;

    QPointF point(x, y);
    QTransform ctm = state.matrix;
    QPointF p = ctm.inverted().map(point);
    if (!qt_is_finite(p.x()) || !qt_is_finite(p.y()))
        return false;

    const_cast<QQuickContext2D *>(this)->m_path.setFillRule(state.fillRule);

    bool contains = m_path.contains(p);

    if (!contains) {
        // check whether the point is on the border
        QPolygonF border = m_path.toFillPolygon();

        QPointF p1 = border.at(0);
        QPointF p2;

        for (int i = 1; i < border.size(); ++i) {
            p2 = border.at(i);
            if (areCollinear(p, p1, p2)
                    // Once we know that the points are collinear we
                    // only need to check one of the coordinates
                    && (qAbs(p2.x() - p1.x()) > qAbs(p2.y() - p1.y()) ?
                        withinRange(p.x(), p1.x(), p2.x()) :
                        withinRange(p.y(), p1.y(), p2.y()))) {
                return true;
            }
            p1 = p2;
        }
    }
    return contains;
}

QMutex QQuickContext2D::mutex;

QQuickContext2D::QQuickContext2D(QObject *parent)
    : QQuickCanvasContext(parent)
    , m_buffer(new QQuickContext2DCommandBuffer)
    , m_v4engine(nullptr)
    , m_surface(nullptr)
    , m_thread(nullptr)
    , m_grabbed(false)
{
}

QQuickContext2D::~QQuickContext2D()
{
    mutex.lock();
    m_texture->setItem(nullptr);
    delete m_buffer;
    m_texture->deleteLater();

    mutex.unlock();
}

QV4::ReturnedValue QQuickContext2D::v4value() const
{
    return m_v4value.value();
}

QStringList QQuickContext2D::contextNames() const
{
    return QStringList() << QStringLiteral("2d");
}

void QQuickContext2D::init(QQuickCanvasItem *canvasItem, const QVariantMap &args)
{
    Q_UNUSED(args);

    m_canvas = canvasItem;
    m_renderTarget = canvasItem->renderTarget();
    m_renderStrategy = canvasItem->renderStrategy();

    // Disable threaded background rendering if the platform has issues with it
    if (m_renderTarget == QQuickCanvasItem::FramebufferObject
            && m_renderStrategy == QQuickCanvasItem::Threaded
            && !QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::ThreadedOpenGL)) {
        m_renderTarget = QQuickCanvasItem::Image;
    }

    // Disable framebuffer object based rendering always in Qt 6. It
    // is not implemented in the new RHI-based graphics stack, but the
    // enum value is still present. Switch to Image instead.
    if (m_renderTarget == QQuickCanvasItem::FramebufferObject)
        m_renderTarget = QQuickCanvasItem::Image;

    m_texture = new QQuickContext2DImageTexture;

    m_texture->setItem(canvasItem);
    m_texture->setCanvasWindow(canvasItem->canvasWindow().toRect());
    m_texture->setTileSize(canvasItem->tileSize());
    m_texture->setCanvasSize(canvasItem->canvasSize().toSize());
    m_texture->setSmooth(canvasItem->smooth());
    m_texture->setAntialiasing(canvasItem->antialiasing());
    m_texture->setOnCustomThread(m_renderStrategy == QQuickCanvasItem::Threaded);
    m_thread = QThread::currentThread();

    QThread *renderThread = m_thread;
    if (m_renderStrategy == QQuickCanvasItem::Threaded)
        renderThread = QQuickContext2DRenderThread::instance(qmlEngine(canvasItem));
    if (renderThread && renderThread != QThread::currentThread())
        m_texture->moveToThread(renderThread);
    connect(m_texture, SIGNAL(textureChanged()), SIGNAL(textureChanged()));

    reset();
}

void QQuickContext2D::prepare(const QSize& canvasSize, const QSize& tileSize, const QRect& canvasWindow, const QRect& dirtyRect, bool smooth, bool antialiasing)
{
    if (m_texture->thread() == QThread::currentThread()) {
        m_texture->canvasChanged(canvasSize, tileSize, canvasWindow, dirtyRect, smooth, antialiasing);
    } else {
        QEvent *e =  new QQuickContext2DTexture::CanvasChangeEvent(canvasSize,
                                                                   tileSize,
                                                                   canvasWindow,
                                                                   dirtyRect,
                                                                   smooth,
                                                                   antialiasing);
        QCoreApplication::postEvent(m_texture, e);
    }
}

void QQuickContext2D::flush()
{
    if (m_buffer) {
        if (m_texture->thread() == QThread::currentThread())
            m_texture->paint(m_buffer);
        else
            QCoreApplication::postEvent(m_texture, new QQuickContext2DTexture::PaintEvent(m_buffer));
    }
    m_buffer = new QQuickContext2DCommandBuffer();
}

QQuickContext2DTexture *QQuickContext2D::texture() const
{
    return m_texture;
}

QImage QQuickContext2D::toImage(const QRectF& bounds)
{
    if (m_texture->thread() == QThread::currentThread()) {
            flush();
            m_texture->grabImage(bounds);
    } else if (m_renderStrategy == QQuickCanvasItem::Cooperative) {
        qWarning() << "Pixel readback is not supported in Cooperative mode, please try Threaded or Immediate mode";
        return QImage();
    } else {
        flush();
        QCoreApplication::postEvent(m_texture, new QEvent(QEvent::Type(QEvent::User + 10)));
        QMetaObject::invokeMethod(m_texture,
                                  "grabImage",
                                  Qt::BlockingQueuedConnection,
                                  Q_ARG(QRectF, bounds));
    }
    QImage img = m_grabbedImage;
    m_grabbedImage = QImage();
    m_grabbed = false;
    return img;
}


QQuickContext2DEngineData::QQuickContext2DEngineData(QV4::ExecutionEngine *v4)
{
    QV4::Scope scope(v4);

    QV4::ScopedObject proto(scope, QQuickJSContext2DPrototype::create(v4));
    proto->defineAccessorProperty(QStringLiteral("strokeStyle"), QQuickJSContext2D::method_get_strokeStyle, QQuickJSContext2D::method_set_strokeStyle);
    proto->defineAccessorProperty(QStringLiteral("font"), QQuickJSContext2D::method_get_font, QQuickJSContext2D::method_set_font);
    proto->defineAccessorProperty(QStringLiteral("fillRule"), QQuickJSContext2D::method_get_fillRule, QQuickJSContext2D::method_set_fillRule);
    proto->defineAccessorProperty(QStringLiteral("globalAlpha"), QQuickJSContext2D::method_get_globalAlpha, QQuickJSContext2D::method_set_globalAlpha);
    proto->defineAccessorProperty(QStringLiteral("lineCap"), QQuickJSContext2D::method_get_lineCap, QQuickJSContext2D::method_set_lineCap);
    proto->defineAccessorProperty(QStringLiteral("shadowOffsetX"), QQuickJSContext2D::method_get_shadowOffsetX, QQuickJSContext2D::method_set_shadowOffsetX);
    proto->defineAccessorProperty(QStringLiteral("shadowOffsetY"), QQuickJSContext2D::method_get_shadowOffsetY, QQuickJSContext2D::method_set_shadowOffsetY);
    proto->defineAccessorProperty(QStringLiteral("globalCompositeOperation"), QQuickJSContext2D::method_get_globalCompositeOperation, QQuickJSContext2D::method_set_globalCompositeOperation);
    proto->defineAccessorProperty(QStringLiteral("miterLimit"), QQuickJSContext2D::method_get_miterLimit, QQuickJSContext2D::method_set_miterLimit);
    proto->defineAccessorProperty(QStringLiteral("fillStyle"), QQuickJSContext2D::method_get_fillStyle, QQuickJSContext2D::method_set_fillStyle);
    proto->defineAccessorProperty(QStringLiteral("shadowColor"), QQuickJSContext2D::method_get_shadowColor, QQuickJSContext2D::method_set_shadowColor);
    proto->defineAccessorProperty(QStringLiteral("textBaseline"), QQuickJSContext2D::method_get_textBaseline, QQuickJSContext2D::method_set_textBaseline);
#if QT_CONFIG(quick_path)
    proto->defineAccessorProperty(QStringLiteral("path"), QQuickJSContext2D::method_get_path, QQuickJSContext2D::method_set_path);
#endif
    proto->defineAccessorProperty(QStringLiteral("lineJoin"), QQuickJSContext2D::method_get_lineJoin, QQuickJSContext2D::method_set_lineJoin);
    proto->defineAccessorProperty(QStringLiteral("lineWidth"), QQuickJSContext2D::method_get_lineWidth, QQuickJSContext2D::method_set_lineWidth);
    proto->defineAccessorProperty(QStringLiteral("textAlign"), QQuickJSContext2D::method_get_textAlign, QQuickJSContext2D::method_set_textAlign);
    proto->defineAccessorProperty(QStringLiteral("shadowBlur"), QQuickJSContext2D::method_get_shadowBlur, QQuickJSContext2D::method_set_shadowBlur);
    proto->defineAccessorProperty(QStringLiteral("lineDashOffset"), QQuickJSContext2D::method_get_lineDashOffset, QQuickJSContext2D::method_set_lineDashOffset);
    contextPrototype = proto;

    proto = scope.engine->newObject();
    proto->defineDefaultProperty(QStringLiteral("addColorStop"), QQuickContext2DStyle::gradient_proto_addColorStop, 0);
    gradientProto = proto;

    proto = scope.engine->newObject();
    proto->defineAccessorProperty(scope.engine->id_length(), QQuickJSContext2DPixelData::proto_get_length, nullptr);
    pixelArrayProto = proto;
}

QQuickContext2DEngineData::~QQuickContext2DEngineData()
{
}

void QQuickContext2D::popState()
{
    if (m_stateStack.isEmpty())
        return;

    QQuickContext2D::State newState = m_stateStack.pop();

    if (state.matrix != newState.matrix)
        buffer()->updateMatrix(newState.matrix);

    if (newState.globalAlpha != state.globalAlpha)
        buffer()->setGlobalAlpha(newState.globalAlpha);

    if (newState.globalCompositeOperation != state.globalCompositeOperation)
        buffer()->setGlobalCompositeOperation(newState.globalCompositeOperation);

    if (newState.fillStyle != state.fillStyle)
        buffer()->setFillStyle(newState.fillStyle);

    if (newState.strokeStyle != state.strokeStyle)
        buffer()->setStrokeStyle(newState.strokeStyle);

    if (newState.lineWidth != state.lineWidth)
        buffer()->setLineWidth(newState.lineWidth);

    if (newState.lineCap != state.lineCap)
        buffer()->setLineCap(newState.lineCap);

    if (newState.lineJoin != state.lineJoin)
        buffer()->setLineJoin(newState.lineJoin);

    if (newState.miterLimit != state.miterLimit)
        buffer()->setMiterLimit(newState.miterLimit);

    if (newState.clip != state.clip || newState.clipPath != state.clipPath)
        buffer()->clip(newState.clip, newState.clipPath);

    if (newState.shadowBlur != state.shadowBlur)
        buffer()->setShadowBlur(newState.shadowBlur);

    if (newState.shadowColor != state.shadowColor)
        buffer()->setShadowColor(newState.shadowColor);

    if (newState.shadowOffsetX != state.shadowOffsetX)
        buffer()->setShadowOffsetX(newState.shadowOffsetX);

    if (newState.shadowOffsetY != state.shadowOffsetY)
        buffer()->setShadowOffsetY(newState.shadowOffsetY);

    if (newState.lineDash != state.lineDash)
        buffer()->setLineDash(newState.lineDash);

    m_path = state.matrix.map(m_path);
    state = newState;
    m_path = state.matrix.inverted().map(m_path);
}
void QQuickContext2D::pushState()
{
    m_stateStack.push(state);
}

void QQuickContext2D::reset()
{
    QQuickContext2D::State newState;

    m_path = QPainterPath();

    newState.clipPath.setFillRule(Qt::WindingFill);

    m_stateStack.clear();
    m_stateStack.push(newState);
    popState();
    m_buffer->clearRect(QRectF(0, 0, m_canvas->width(), m_canvas->height()));
}

QV4::ExecutionEngine *QQuickContext2D::v4Engine() const
{
    return m_v4engine;
}

void QQuickContext2D::setV4Engine(QV4::ExecutionEngine *engine)
{
    if (m_v4engine != engine) {
        m_v4engine = engine;

        if (m_v4engine == nullptr)
            return;

        QQuickContext2DEngineData *ed = engineData(engine);
        QV4::Scope scope(engine);
        QV4::Scoped<QQuickJSContext2D> wrapper(scope, engine->memoryManager->allocate<QQuickJSContext2D>());
        QV4::ScopedObject p(scope, ed->contextPrototype.value());
        wrapper->setPrototypeOf(p);
        wrapper->d()->setContext(this);
        m_v4value = wrapper;
    }
}

QT_END_NAMESPACE

#include "moc_qquickcontext2d_p.cpp"
