From cd8e2fa0b7ca51e1198a1a800f285d0591af9973 Mon Sep 17 00:00:00 2001
From: Ada <ada+github@thingvellir.net>
Date: Fri, 28 Feb 2025 23:47:25 +1000
Subject: [PATCH] CanvasCommand better type safety and accessible from script,
 segfault on usage

---
 .../entities/src/CanvasEntityItem.cpp.in      |  35 +-
 libraries/script-engine/src/CanvasCommand.cpp | 291 +++++++-----
 libraries/script-engine/src/CanvasCommand.h   | 443 ++++++++++--------
 libraries/script-engine/src/ScriptManager.cpp |   4 +
 libraries/script-engine/src/ScriptManager.h   |   2 +
 5 files changed, 448 insertions(+), 327 deletions(-)

diff --git a/libraries/entities/src/CanvasEntityItem.cpp.in b/libraries/entities/src/CanvasEntityItem.cpp.in
index 9c8bcbe6c5..e09cfbbf93 100644
--- a/libraries/entities/src/CanvasEntityItem.cpp.in
+++ b/libraries/entities/src/CanvasEntityItem.cpp.in
@@ -108,6 +108,7 @@ void CanvasEntityItem::setImageData(const CanvasImage& image) {
 }
 
 void CanvasEntityItem::paintCommands(const QVector<CanvasCommand>& queue) {
+    using namespace canvas_cmd;
     using V = CanvasCommand::Variant;
     using Hint = CanvasCommand::RenderHint;
 
@@ -126,14 +127,14 @@ void CanvasEntityItem::paintCommands(const QVector<CanvasCommand>& queue) {
                 break;
 
             case V::SetStrokeWidth: {
-                auto props = cmd._setStrokeWidth;
+                auto props = dynamic_cast<const SetStrokeWidth&>(cmd);
                 pen.setWidthF(props.width);
                 p.setPen(pen);
                 break;
             }
 
             case V::SetColor: {
-                auto props = cmd._setColor;
+                auto props = dynamic_cast<const SetColor&>(cmd);
                 auto color = QColor(props.color[0], props.color[1], props.color[2], 255);
                 pen.setColor(color);
                 brush.setColor(color);
@@ -143,7 +144,7 @@ void CanvasEntityItem::paintCommands(const QVector<CanvasCommand>& queue) {
             }
 
             case V::SetHints: {
-                auto props = cmd._setHints;
+                auto props = dynamic_cast<const SetHints&>(cmd);
                 p.setRenderHint(QPainter::Antialiasing, (props.hints & Hint::PrimitiveAntialiasing) != 0);
                 p.setRenderHint(QPainter::TextAntialiasing, (props.hints & Hint::TextAntialiasing) != 0);
                 p.setRenderHint(QPainter::SmoothPixmapTransform, (props.hints & Hint::BilinearImageScaling) != 0);
@@ -151,37 +152,37 @@ void CanvasEntityItem::paintCommands(const QVector<CanvasCommand>& queue) {
             }
 
             case V::SetBlendMode: {
-                auto props = cmd._setBlendMode;
+                auto props = dynamic_cast<const SetBlendMode&>(cmd);
                 p.setCompositionMode(props.mode);
                 break;
             }
 
             case V::SetFont: {
-                auto props = cmd._setFont;
+                auto props = dynamic_cast<const SetFont&>(cmd);
                 p.setFont(QFont(props.family, props.size, props.weight, props.italic));
                 break;
             }
 
             case V::ClearRect: {
-                auto props = cmd._clearRect;
+                auto props = dynamic_cast<const ClearRect&>(cmd);
                 p.eraseRect(props.rect);
                 break;
             }
 
             case V::FillPath: {
-                auto props = cmd._fillPath;
+                auto props = dynamic_cast<const FillPath&>(cmd);
                 p.fillPath(props.path, brush);
                 break;
             }
 
             case V::FillRect: {
-                auto props = cmd._fillRect;
+                auto props = dynamic_cast<const FillRect&>(cmd);
                 p.fillRect(props.rect, brush);
                 break;
             }
 
             case V::FillEllipse: {
-                auto props = cmd._fillEllipse;
+                auto props = dynamic_cast<const FillEllipse&>(cmd);
                 auto pathHack = QPainterPath();
                 pathHack.addEllipse(props.rect);
                 p.fillPath(pathHack, brush);
@@ -189,7 +190,7 @@ void CanvasEntityItem::paintCommands(const QVector<CanvasCommand>& queue) {
             }
 
             case V::FillText: {
-                auto props = cmd._fillText;
+                auto props = dynamic_cast<const FillText&>(cmd);
 
                 // unbounded text
                 if (props.rect.width() == 0.0 && props.rect.height() == 0.0) {
@@ -201,43 +202,43 @@ void CanvasEntityItem::paintCommands(const QVector<CanvasCommand>& queue) {
             }
 
             case V::StrokePath: {
-                auto props = cmd._strokePath;
+                auto props = dynamic_cast<const StrokePath&>(cmd);
                 p.strokePath(props.path, pen);
                 break;
             }
 
             case V::StrokeRect: {
-                auto props = cmd._strokeRect;
+                auto props = dynamic_cast<const StrokeRect&>(cmd);
                 p.drawRect(props.rect);
                 break;
             }
 
             case V::StrokeArc: {
-                auto props = cmd._strokeArc;
+                auto props = dynamic_cast<const StrokeArc&>(cmd);
                 p.drawArc(props.rect, props.startAngle * 16, props.spanAngle * 16);
                 break;
             }
 
             case V::StrokeEllipse: {
-                auto props = cmd._strokeEllipse;
+                auto props = dynamic_cast<const StrokeEllipse&>(cmd);
                 p.drawEllipse(props.rect);
                 break;
             }
 
             case V::Point: {
-                auto props = cmd._point;
+                auto props = dynamic_cast<const Point&>(cmd);
                 p.drawPoint(props.point);
                 break;
             }
 
             case V::Line: {
-                auto props = cmd._line;
+                auto props = dynamic_cast<const Line&>(cmd);
                 p.drawLine(props.line);
                 break;
             }
 
             case V::ImageCopy: {
-                auto props = cmd._imageCopy;
+                auto props = dynamic_cast<const ImageCopy&>(cmd);
                 if (props.image.buffer.length() != static_cast<int>(4 * props.image.width * props.image.height)) {
                     qCCritical(entities) << "CanvasEntityItem::paintCommands: Source CanvasImage buffer has incorrect size, expected " << (4 * props.image.width * props.image.height) << ", got " << props.image.buffer.length();
                     break;
diff --git a/libraries/script-engine/src/CanvasCommand.cpp b/libraries/script-engine/src/CanvasCommand.cpp
index aece5fd9ae..47db820684 100644
--- a/libraries/script-engine/src/CanvasCommand.cpp
+++ b/libraries/script-engine/src/CanvasCommand.cpp
@@ -38,6 +38,7 @@ const QString IMG_BUFFER_PROP_NAME = "buffer";
 
 ScriptValue canvasCommandToScriptValue(ScriptEngine* engine, const CanvasCommand& cmd) {
     using Variant = CanvasCommand::Variant;
+    using namespace canvas_cmd;
 
     ScriptValue obj = engine->newObject();
 
@@ -45,31 +46,31 @@ ScriptValue canvasCommandToScriptValue(ScriptEngine* engine, const CanvasCommand
 
     switch (cmd.kind()) {
         case Variant::SetStrokeWidth: {
-            auto props = cmd._setStrokeWidth;
+            auto props = dynamic_cast<const SetStrokeWidth&>(cmd);
             obj.setProperty("width", props.width);
             return obj;
         }
 
         case Variant::SetColor: {
-            auto props = cmd._setColor;
+            auto props = dynamic_cast<const SetColor&>(cmd);
             obj.setProperty("color", u8vec3ColorToScriptValue(engine, props.color));
             return obj;
         }
 
         case Variant::SetHints: {
-            auto props = cmd._setHints;
+            auto props = dynamic_cast<const SetHints&>(cmd);
             obj.setProperty("hints", props.hints);
             return obj;
         }
 
         case Variant::SetBlendMode: {
-            auto props = cmd._setBlendMode;
+            auto props = dynamic_cast<const SetBlendMode&>(cmd);
             obj.setProperty("mode", props.mode);
             return obj;
         }
 
         case Variant::SetFont: {
-            auto props = cmd._setFont;
+            auto props = dynamic_cast<const SetFont&>(cmd);
             obj.setProperty("family", props.family);
             obj.setProperty("size", props.size);
             obj.setProperty("weight", props.weight);
@@ -77,14 +78,23 @@ ScriptValue canvasCommandToScriptValue(ScriptEngine* engine, const CanvasCommand
             return obj;
         }
 
+        case Variant::ClearRect: {
+            auto props = dynamic_cast<const ClearRect&>(cmd);
+            obj.setProperty("x", props.rect.x());
+            obj.setProperty("y", props.rect.y());
+            obj.setProperty("w", props.rect.width());
+            obj.setProperty("h", props.rect.height());
+            return obj;
+        }
+
         case Variant::FillPath: {
-            auto props = cmd._fillPath;
+            auto props = dynamic_cast<const FillPath&>(cmd);
             obj.setProperty("path", qPainterPathToScriptValue(engine, props.path));
             return obj;
         }
 
         case Variant::FillRect: {
-            auto props = cmd._fillRect;
+            auto props = dynamic_cast<const FillRect&>(cmd);
             obj.setProperty("x", props.rect.x());
             obj.setProperty("y", props.rect.y());
             obj.setProperty("w", props.rect.width());
@@ -93,7 +103,7 @@ ScriptValue canvasCommandToScriptValue(ScriptEngine* engine, const CanvasCommand
         }
 
         case Variant::FillEllipse: {
-            auto props = cmd._fillEllipse;
+            auto props = dynamic_cast<const FillEllipse&>(cmd);
             obj.setProperty("x", props.rect.x());
             obj.setProperty("y", props.rect.y());
             obj.setProperty("w", props.rect.width());
@@ -102,7 +112,7 @@ ScriptValue canvasCommandToScriptValue(ScriptEngine* engine, const CanvasCommand
         }
 
         case Variant::FillText: {
-            auto props = cmd._fillText;
+            auto props = dynamic_cast<const FillText&>(cmd);
             obj.setProperty("x", props.rect.x());
             obj.setProperty("y", props.rect.y());
             obj.setProperty("w", props.rect.width());
@@ -113,13 +123,13 @@ ScriptValue canvasCommandToScriptValue(ScriptEngine* engine, const CanvasCommand
         }
 
         case Variant::StrokePath: {
-            auto props = cmd._strokePath;
+            auto props = dynamic_cast<const StrokePath&>(cmd);
             obj.setProperty("path", qPainterPathToScriptValue(engine, props.path));
             return obj;
         }
 
         case Variant::StrokeRect: {
-            auto props = cmd._strokeRect;
+            auto props = dynamic_cast<const StrokeRect&>(cmd);
             obj.setProperty("x", props.rect.x());
             obj.setProperty("y", props.rect.y());
             obj.setProperty("w", props.rect.width());
@@ -128,7 +138,7 @@ ScriptValue canvasCommandToScriptValue(ScriptEngine* engine, const CanvasCommand
         }
 
         case Variant::StrokeArc: {
-            auto props = cmd._strokeArc;
+            auto props = dynamic_cast<const StrokeArc&>(cmd);
             obj.setProperty("x", props.rect.x());
             obj.setProperty("y", props.rect.y());
             obj.setProperty("w", props.rect.width());
@@ -139,7 +149,7 @@ ScriptValue canvasCommandToScriptValue(ScriptEngine* engine, const CanvasCommand
         }
 
         case Variant::StrokeEllipse: {
-            auto props = cmd._strokeEllipse;
+            auto props = dynamic_cast<const StrokeEllipse&>(cmd);
             obj.setProperty("x", props.rect.x());
             obj.setProperty("y", props.rect.y());
             obj.setProperty("w", props.rect.width());
@@ -148,14 +158,14 @@ ScriptValue canvasCommandToScriptValue(ScriptEngine* engine, const CanvasCommand
         }
 
         case Variant::Point: {
-            auto props = cmd._point;
+            auto props = dynamic_cast<const Point&>(cmd);
             obj.setProperty("x", props.point.x());
             obj.setProperty("y", props.point.y());
             return obj;
         }
 
         case Variant::Line: {
-            auto props = cmd._line;
+            auto props = dynamic_cast<const Line&>(cmd);
             obj.setProperty("x1", props.line.x1());
             obj.setProperty("y1", props.line.y1());
             obj.setProperty("x2", props.line.x2());
@@ -164,7 +174,7 @@ ScriptValue canvasCommandToScriptValue(ScriptEngine* engine, const CanvasCommand
         }
 
         case Variant::ImageCopy: {
-            auto props = cmd._imageCopy;
+            auto props = dynamic_cast<const ImageCopy&>(cmd);
             obj.setProperty("srcX", props.src.x());
             obj.setProperty("srcY", props.src.y());
             obj.setProperty("srcW", props.src.width());
@@ -185,65 +195,54 @@ ScriptValue canvasCommandToScriptValue(ScriptEngine* engine, const CanvasCommand
 
 bool canvasCommandFromScriptValue(const ScriptValue& object, CanvasCommand& cmd) {
     using Variant = CanvasCommand::Variant;
+    using namespace canvas_cmd;
 
-    uint type = object.property(CMD_TYPE_PROP_NAME).toVariant().toUInt();
+    uint type = object.property(CMD_TYPE_PROP_NAME).toVariant().toInt();
 
-    if (type == static_cast<uint>(Variant::SetStrokeWidth)) {
-        cmd.set(CanvasCommand::SetStrokeWidth { object.property("width").toNumber() });
-    } else if (type == static_cast<uint>(Variant::SetColor)) {
+    if (type == Variant::SetStrokeWidth) {
+        cmd = SetStrokeWidth { object.property("width").toNumber() };
+    } else if (type == Variant::SetColor) {
         glm::u8vec3 c;
         if (!u8vec3FromScriptValue(object.property("color"), c)) { return false; }
 
         // FIXME: we have a script RGB color type but not an RGBA one
-        cmd.set(CanvasCommand::SetColor { c });
-    } else if (type == static_cast<uint>(Variant::SetHints)) {
-        cmd.set(CanvasCommand::SetHints {
-            static_cast<CanvasCommand::RenderHint>(object.property("hints").toVariant().toUInt())
-        });
-    } else if (type == static_cast<uint>(Variant::SetBlendMode)) {
-        cmd.set(CanvasCommand::SetBlendMode {
-            static_cast<QPainter::CompositionMode>(object.property("mode").toVariant().toUInt())
-        });
-    } else if (type == static_cast<uint>(Variant::SetFont)) {
-        cmd.set(CanvasCommand::SetFont {
+        cmd = SetColor(c);
+    } else if (type == Variant::SetHints) {
+        cmd = SetHints(object.property("hints").toVariant().toUInt());
+    } else if (type == Variant::SetBlendMode) {
+        cmd = SetBlendMode(object.property("mode").toVariant().toUInt());
+    } else if (type == Variant::SetFont) {
+        cmd = SetFont(
             object.property("family").toString(),
             object.property("size").toVariant().toInt(),
             object.property("weight").toVariant().toInt(),
-            object.property("italic").toBool(),
-        });
-    } else if (type == static_cast<uint>(Variant::ClearRect)) {
-        cmd.set(CanvasCommand::ClearRect {
-            QRect(
-                object.property("x").toVariant().toInt(),
-                object.property("y").toVariant().toInt(),
-                object.property("w").toVariant().toInt(),
-                object.property("h").toVariant().toInt()
-            )
-        });
-    } else if (type == static_cast<uint>(Variant::FillPath)) {
-        cmd.set(CanvasCommand::FillPath {
-            qPainterPathFromScriptValue(object.property("path"))
-        });
-    } else if (type == static_cast<uint>(Variant::FillRect)) {
-        cmd.set(CanvasCommand::FillRect {
-            QRectF(
-                object.property("x").toNumber(),
-                object.property("y").toNumber(),
-                object.property("w").toNumber(),
-                object.property("h").toNumber()
-            )
-        });
-    } else if (type == static_cast<uint>(Variant::FillEllipse)) {
-        cmd.set(CanvasCommand::FillEllipse {
-            QRectF(
-                object.property("x").toNumber(),
-                object.property("y").toNumber(),
-                object.property("w").toNumber(),
-                object.property("h").toNumber()
-            )
-        });
-    } else if (type == static_cast<uint>(Variant::FillText)) {
-        cmd.set(CanvasCommand::FillText {
+            object.property("italic").toBool()
+        );
+    } else if (type == Variant::ClearRect) {
+        cmd = ClearRect(QRect(
+            object.property("x").toVariant().toInt(),
+            object.property("y").toVariant().toInt(),
+            object.property("w").toVariant().toInt(),
+            object.property("h").toVariant().toInt()
+        ));
+    } else if (type == Variant::FillPath) {
+        cmd = FillPath(qPainterPathFromScriptValue(object.property("path")));
+    } else if (type == Variant::FillRect) {
+        cmd = FillRect(QRectF(
+            object.property("x").toNumber(),
+            object.property("y").toNumber(),
+            object.property("w").toNumber(),
+            object.property("h").toNumber()
+        ));
+    } else if (type == Variant::FillEllipse) {
+        cmd = FillEllipse(QRectF(
+            object.property("x").toNumber(),
+            object.property("y").toNumber(),
+            object.property("w").toNumber(),
+            object.property("h").toNumber()
+        ));
+    } else if (type == Variant::FillText) {
+        cmd = FillText(
             QRectF(
                 object.property("x").toNumber(),
                 object.property("y").toNumber(),
@@ -251,23 +250,19 @@ bool canvasCommandFromScriptValue(const ScriptValue& object, CanvasCommand& cmd)
                 object.property("h").toNumber()
             ),
             object.property("text").toString(),
-            static_cast<Qt::AlignmentFlag>(object.property("flag").toVariant().toUInt()),
-        });
-    } else if (type == static_cast<uint>(Variant::StrokePath)) {
-        cmd.set(CanvasCommand::StrokePath {
-            qPainterPathFromScriptValue(object.property("path"))
-        });
-    } else if (type == static_cast<uint>(Variant::StrokeRect)) {
-        cmd.set(CanvasCommand::StrokeRect {
-            QRectF(
-                object.property("x").toNumber(),
-                object.property("y").toNumber(),
-                object.property("w").toNumber(),
-                object.property("h").toNumber()
-            )
-        });
-    } else if (type == static_cast<uint>(Variant::StrokeArc)) {
-        cmd.set(CanvasCommand::StrokeArc {
+            object.property("flag").toVariant().toInt()
+        );
+    } else if (type == Variant::StrokePath) {
+        cmd = StrokePath(qPainterPathFromScriptValue(object.property("path")));
+    } else if (type == Variant::StrokeRect) {
+        cmd = StrokeRect(QRectF(
+            object.property("x").toNumber(),
+            object.property("y").toNumber(),
+            object.property("w").toNumber(),
+            object.property("h").toNumber()
+        ));
+    } else if (type == Variant::StrokeArc) {
+        cmd = StrokeArc(
             QRectF(
                 object.property("x").toNumber(),
                 object.property("y").toNumber(),
@@ -275,35 +270,30 @@ bool canvasCommandFromScriptValue(const ScriptValue& object, CanvasCommand& cmd)
                 object.property("h").toNumber()
             ),
             object.property("startAngle").toNumber(),
-            object.property("spanAngle").toNumber(),
-        });
-    } else if (type == static_cast<uint>(Variant::StrokeEllipse)) {
-        cmd.set(CanvasCommand::StrokeEllipse {
-            QRectF(
-                object.property("x").toNumber(),
-                object.property("y").toNumber(),
-                object.property("w").toNumber(),
-                object.property("h").toNumber()
-            )
-        });
-    } else if (type == static_cast<uint>(Variant::Point)) {
-        cmd.set(CanvasCommand::Point {
-            QPointF(
-                object.property("x").toNumber(),
-                object.property("y").toNumber()
-            )
-        });
-    } else if (type == static_cast<uint>(Variant::Line)) {
-        cmd.set(CanvasCommand::Line {
-            QLineF(
-                object.property("x1").toNumber(),
-                object.property("y1").toNumber(),
-                object.property("x2").toNumber(),
-                object.property("y2").toNumber()
-            )
-        });
-    } else if (type == static_cast<uint>(Variant::ImageCopy)) {
-        cmd.set(CanvasCommand::ImageCopy {
+            object.property("spanAngle").toNumber()
+        );
+    } else if (type == Variant::StrokeEllipse) {
+        cmd = StrokeEllipse(QRectF(
+            object.property("x").toNumber(),
+            object.property("y").toNumber(),
+            object.property("w").toNumber(),
+            object.property("h").toNumber()
+        ));
+    } else if (type == Variant::Point) {
+        cmd = Point(
+            object.property("x").toNumber(),
+            object.property("y").toNumber()
+        );
+    } else if (type == Variant::Line) {
+        cmd = Line(
+            object.property("x1").toNumber(),
+            object.property("y1").toNumber(),
+            object.property("x2").toNumber(),
+            object.property("y2").toNumber()
+        );
+    } else if (type == Variant::ImageCopy) {
+        cmd = ImageCopy(
+            canvasImageFromScriptValue(object.property("image")),
             QRectF(
                 object.property("srcX").toNumber(),
                 object.property("srcY").toNumber(),
@@ -315,11 +305,10 @@ bool canvasCommandFromScriptValue(const ScriptValue& object, CanvasCommand& cmd)
                 object.property("destY").toNumber(),
                 object.property("destW").toNumber(),
                 object.property("destH").toNumber()
-            ),
-            canvasImageFromScriptValue(object.property("image")),
-        });
+            )
+        );
     } else {
-        cmd.set(CanvasCommand::Invalid());
+        cmd = Invalid();
     }
 
     return true;
@@ -437,3 +426,73 @@ QPainterPath qPainterPathFromScriptValue(const ScriptValue& object) {
     qPainterPathFromScriptValue(object, p);
     return p;
 }
+
+using namespace canvas_cmd;
+
+CanvasCommand CanvasCommandInterface::setStrokeWidth(qreal width) const {
+    return SetStrokeWidth(width);
+}
+
+CanvasCommand CanvasCommandInterface::setColor(const glm::u8vec3& color) const {
+    return SetColor(color);
+}
+
+CanvasCommand CanvasCommandInterface::setHints(int hints) const {
+    return SetHints(hints);
+}
+
+CanvasCommand CanvasCommandInterface::setBlendMode(int mode) const {
+    return SetBlendMode(mode);
+}
+
+CanvasCommand CanvasCommandInterface::setFont(const QString& family, int size, int weight, bool italic) const {
+    return SetFont(family, size, weight, italic);
+}
+
+CanvasCommand CanvasCommandInterface::clearRect(const QRect& rect) const {
+    return ClearRect(rect);
+}
+
+CanvasCommand CanvasCommandInterface::fillPath(const QPainterPath& path) const {
+    return FillPath(path);
+}
+
+CanvasCommand CanvasCommandInterface::fillRect(const QRectF& rect) const {
+    return FillRect(rect);
+}
+
+CanvasCommand CanvasCommandInterface::fillEllipse(const QRectF& rect) const {
+    return FillEllipse(rect);
+}
+
+CanvasCommand CanvasCommandInterface::fillText(const QString& text, const QRectF& rect, int flag) const {
+    return FillText(rect, text, flag);
+}
+
+CanvasCommand CanvasCommandInterface::strokePath(const QPainterPath& path) const {
+    return StrokePath(path);
+}
+
+CanvasCommand CanvasCommandInterface::strokeRect(const QRectF& rect) const {
+    return StrokeRect(rect);
+}
+
+CanvasCommand CanvasCommandInterface::strokeArc(const QRectF& rect, qreal startAngle, qreal spanAngle) const {
+    return StrokeArc(rect, startAngle, spanAngle);
+}
+
+CanvasCommand CanvasCommandInterface::strokeEllipse(const QRectF& rect) const {
+    return StrokeEllipse(rect);
+}
+
+CanvasCommand CanvasCommandInterface::point(qreal x, qreal y) const {
+    return Point(x, y);
+}
+
+CanvasCommand CanvasCommandInterface::line(qreal x1, qreal y1, qreal x2, qreal y2) const {
+    return Line(x1, y1, x2, y2);
+}
+
+CanvasCommand CanvasCommandInterface::imageCopy(const CanvasImage& image, const QRectF& src, const QRectF& dest) const {
+    return ImageCopy(image, src, dest);
+}
diff --git a/libraries/script-engine/src/CanvasCommand.h b/libraries/script-engine/src/CanvasCommand.h
index 47c01a6a4b..8ffd321ae2 100644
--- a/libraries/script-engine/src/CanvasCommand.h
+++ b/libraries/script-engine/src/CanvasCommand.h
@@ -18,6 +18,7 @@
 
 #include "ScriptValue.h"
 #include "ScriptValueUtils.h"
+#include "Scriptable.h"
 
 #include <QColor>
 #include <QPainter>
@@ -33,9 +34,8 @@ struct CanvasImage {
     CanvasImage(QByteArray buffer, int width, int height) : buffer(buffer), width(width), height(height) {}
 };
 
-class CanvasCommand {
-public:
-    enum class Variant: uint8_t {
+struct CanvasCommand {
+    enum Variant {
         Invalid,
         SetStrokeWidth,
         SetColor,
@@ -56,208 +56,263 @@ public:
         ImageCopy,
     };
 
-    enum RenderHint: uint8_t {
+    enum RenderHint {
         PrimitiveAntialiasing = (1 << 0),
         TextAntialiasing = (1 << 1),
         BilinearImageScaling = (1 << 2),
     };
 
-    struct Invalid {};
-    struct SetStrokeWidth { qreal width; };
-    struct SetColor { glm::u8vec3 color; };
-    struct SetHints { RenderHint hints; };
-    struct SetBlendMode { QPainter::CompositionMode mode; };
-    struct SetFont { QString family; int size; int weight; bool italic; };
-    struct ClearRect { QRect rect; };
-    struct FillPath { QPainterPath path; };
-    struct FillRect { QRectF rect; };
-    struct FillEllipse { QRectF rect; };
-    struct FillText { QRectF rect; QString text; Qt::AlignmentFlag flag; };
-    struct StrokePath { QPainterPath path; };
-    struct StrokeRect { QRectF rect; };
-    struct StrokeArc { QRectF rect; qreal startAngle, spanAngle; };
-    struct StrokeEllipse { QRectF rect; };
-    struct Point { QPointF point; };
-    struct Line { QLineF line; };
-    struct ImageCopy { QRectF src; QRectF dst; CanvasImage image; };
-
-    CanvasCommand() noexcept: _invalid(), _tag(Variant::Invalid) {}
-    CanvasCommand(Invalid cmd) noexcept: _invalid(), _tag(Variant::Invalid) {}
-    CanvasCommand(SetStrokeWidth cmd) noexcept: _setStrokeWidth(cmd), _tag(Variant::SetStrokeWidth) {}
-    CanvasCommand(SetColor cmd) noexcept: _setColor(cmd), _tag(Variant::SetColor) {}
-    CanvasCommand(SetHints cmd) noexcept: _setHints(cmd), _tag(Variant::SetHints) {}
-    CanvasCommand(SetBlendMode cmd) noexcept: _setBlendMode(cmd), _tag(Variant::SetBlendMode) {}
-    CanvasCommand(SetFont cmd) noexcept: _setFont(cmd), _tag(Variant::SetFont) {}
-    CanvasCommand(ClearRect cmd) noexcept: _clearRect(cmd), _tag(Variant::ClearRect) {}
-    CanvasCommand(FillPath cmd) noexcept: _fillPath(cmd), _tag(Variant::FillPath) {}
-    CanvasCommand(FillRect cmd) noexcept: _fillRect(cmd), _tag(Variant::FillRect) {}
-    CanvasCommand(FillEllipse cmd) noexcept: _fillEllipse(cmd), _tag(Variant::FillEllipse) {}
-    CanvasCommand(FillText cmd) noexcept: _fillText(cmd), _tag(Variant::FillText) {}
-    CanvasCommand(StrokePath cmd) noexcept: _strokePath(cmd), _tag(Variant::StrokePath) {}
-    CanvasCommand(StrokeRect cmd) noexcept: _strokeRect(cmd), _tag(Variant::StrokeRect) {}
-    CanvasCommand(StrokeArc cmd) noexcept: _strokeArc(cmd), _tag(Variant::StrokeArc) {}
-    CanvasCommand(StrokeEllipse cmd) noexcept: _strokeEllipse(cmd), _tag(Variant::StrokeArc) {}
-    CanvasCommand(Point cmd) noexcept: _point(cmd), _tag(Variant::Point) {}
-    CanvasCommand(Line cmd) noexcept: _line(cmd), _tag(Variant::Line) {}
-    CanvasCommand(ImageCopy cmd) noexcept: _imageCopy(cmd), _tag(Variant::ImageCopy) {}
-
-    ~CanvasCommand() noexcept {
-        switch (_tag) {
-            case Variant::Invalid: _invalid.~Invalid(); break;
-            case Variant::SetStrokeWidth: _setStrokeWidth.~SetStrokeWidth(); break;
-            case Variant::SetColor: _setColor.~SetColor(); break;
-            case Variant::SetHints: _setHints.~SetHints(); break;
-            case Variant::SetBlendMode: _setBlendMode.~SetBlendMode(); break;
-            case Variant::SetFont: _setFont.~SetFont(); break;
-            case Variant::ClearRect: _clearRect.~ClearRect(); break;
-            case Variant::FillPath: _fillPath.~FillPath(); break;
-            case Variant::FillRect: _fillRect.~FillRect(); break;
-            case Variant::FillEllipse: _fillEllipse.~FillEllipse(); break;
-            case Variant::FillText: _fillText.~FillText(); break;
-            case Variant::StrokePath: _strokePath.~StrokePath(); break;
-            case Variant::StrokeRect: _strokeRect.~StrokeRect(); break;
-            case Variant::StrokeArc: _strokeArc.~StrokeArc(); break;
-            case Variant::StrokeEllipse: _strokeEllipse.~StrokeEllipse(); break;
-            case Variant::Point: _point.~Point(); break;
-            case Variant::Line: _line.~Line(); break;
-            case Variant::ImageCopy: _imageCopy.~ImageCopy(); break;
-        }
-    }
-
-    CanvasCommand(const CanvasCommand& other) noexcept {
-        _tag = other._tag;
-        switch (other._tag) {
-            case Variant::Invalid: _invalid = other._invalid; break;
-            case Variant::SetStrokeWidth: _setStrokeWidth = _setStrokeWidth; break;
-            case Variant::SetColor: _setColor = other._setColor; break;
-            case Variant::SetHints: _setHints = other._setHints; break;
-            case Variant::SetBlendMode: _setBlendMode = other._setBlendMode; break;
-            case Variant::SetFont: _setFont = other._setFont; break;
-            case Variant::ClearRect: _clearRect = other._clearRect; break;
-            case Variant::FillPath: _fillPath = other._fillPath; break;
-            case Variant::FillRect: _fillRect = other._fillRect; break;
-            case Variant::FillEllipse: _fillEllipse = other._fillEllipse; break;
-            case Variant::FillText: _fillText = other._fillText; break;
-            case Variant::StrokePath: _strokePath = other._strokePath; break;
-            case Variant::StrokeRect: _strokeRect = other._strokeRect; break;
-            case Variant::StrokeArc: _strokeArc = other._strokeArc; break;
-            case Variant::StrokeEllipse: _strokeEllipse = other._strokeEllipse; break;
-            case Variant::Point: _point = other._point; break;
-            case Variant::Line: _line = other._line; break;
-            case Variant::ImageCopy: _imageCopy = other._imageCopy; break;
-        }
-    }
-
-    CanvasCommand(CanvasCommand&& other) noexcept {
-        _tag = other._tag;
-        switch (other._tag) {
-            case Variant::Invalid: _invalid = other._invalid; break;
-            case Variant::SetStrokeWidth: _setStrokeWidth = _setStrokeWidth; break;
-            case Variant::SetColor: _setColor = other._setColor; break;
-            case Variant::SetHints: _setHints = other._setHints; break;
-            case Variant::SetBlendMode: _setBlendMode = other._setBlendMode; break;
-            case Variant::SetFont: _setFont = other._setFont; break;
-            case Variant::ClearRect: _clearRect = other._clearRect; break;
-            case Variant::FillPath: _fillPath = other._fillPath; break;
-            case Variant::FillRect: _fillRect = other._fillRect; break;
-            case Variant::FillEllipse: _fillEllipse = other._fillEllipse; break;
-            case Variant::FillText: _fillText = other._fillText; break;
-            case Variant::StrokePath: _strokePath = other._strokePath; break;
-            case Variant::StrokeRect: _strokeRect = other._strokeRect; break;
-            case Variant::StrokeArc: _strokeArc = other._strokeArc; break;
-            case Variant::StrokeEllipse: _strokeEllipse = other._strokeEllipse; break;
-            case Variant::Point: _point = other._point; break;
-            case Variant::Line: _line = other._line; break;
-            case Variant::ImageCopy: _imageCopy = other._imageCopy; break;
-        }
-    }
-
-    CanvasCommand& operator=(const CanvasCommand& other) noexcept {
-        _tag = other._tag;
-        switch (other._tag) {
-            case Variant::Invalid: _invalid = other._invalid; break;
-            case Variant::SetStrokeWidth: _setStrokeWidth = _setStrokeWidth; break;
-            case Variant::SetColor: _setColor = other._setColor; break;
-            case Variant::SetHints: _setHints = other._setHints; break;
-            case Variant::SetBlendMode: _setBlendMode = other._setBlendMode; break;
-            case Variant::SetFont: _setFont = other._setFont; break;
-            case Variant::ClearRect: _clearRect = other._clearRect; break;
-            case Variant::FillPath: _fillPath = other._fillPath; break;
-            case Variant::FillRect: _fillRect = other._fillRect; break;
-            case Variant::FillEllipse: _fillEllipse = other._fillEllipse; break;
-            case Variant::FillText: _fillText = other._fillText; break;
-            case Variant::StrokePath: _strokePath = other._strokePath; break;
-            case Variant::StrokeRect: _strokeRect = other._strokeRect; break;
-            case Variant::StrokeArc: _strokeArc = other._strokeArc; break;
-            case Variant::StrokeEllipse: _strokeEllipse = other._strokeEllipse; break;
-            case Variant::Point: _point = other._point; break;
-            case Variant::Line: _line = other._line; break;
-            case Variant::ImageCopy: _imageCopy = other._imageCopy; break;
-        }
-        return *this;
-    }
-
-    void set(Invalid&& cmd) { _tag = Variant::Invalid; _invalid = cmd; }
-    void set(SetStrokeWidth&& cmd) { _tag = Variant::SetStrokeWidth; _setStrokeWidth = cmd; }
-    void set(SetColor&& cmd) { _tag = Variant::SetColor; _setColor = cmd; }
-    void set(SetHints&& cmd) { _tag = Variant::SetHints; _setHints = cmd; }
-    void set(SetBlendMode&& cmd) { _tag = Variant::SetBlendMode; _setBlendMode = cmd; }
-    void set(SetFont&& cmd) { _tag = Variant::SetFont; _setFont = cmd; }
-    void set(ClearRect&& cmd) { _tag = Variant::ClearRect; _clearRect = cmd; }
-    void set(FillPath&& cmd) { _tag = Variant::FillPath; _fillPath = cmd; }
-    void set(FillRect&& cmd) { _tag = Variant::FillRect; _fillRect = cmd; }
-    void set(FillEllipse&& cmd) { _tag = Variant::FillEllipse; _fillEllipse = cmd; }
-    void set(FillText&& cmd) { _tag = Variant::FillText; _fillText = cmd; }
-    void set(StrokePath&& cmd) { _tag = Variant::StrokePath; _strokePath = cmd; }
-    void set(StrokeRect&& cmd) { _tag = Variant::StrokeRect; _strokeRect = cmd; }
-    void set(StrokeArc&& cmd) { _tag = Variant::StrokeArc; _strokeArc = cmd; }
-    void set(StrokeEllipse&& cmd) { _tag = Variant::StrokeEllipse; _strokeEllipse = cmd; }
-    void set(Point&& cmd) { _tag = Variant::Point; _point = cmd; }
-    void set(Line&& cmd) { _tag = Variant::Line; _line = cmd; }
-    void set(ImageCopy&& cmd) { _tag = Variant::ImageCopy; _imageCopy = cmd; }
-
-    Variant kind() const { return _tag; }
-
-    union {
-        Invalid _invalid;
-        SetStrokeWidth _setStrokeWidth;
-        SetColor _setColor;
-        SetHints _setHints;
-        SetBlendMode _setBlendMode;
-        SetFont _setFont;
-        ClearRect _clearRect;
-        FillPath _fillPath;
-        FillRect _fillRect;
-        FillText _fillText;
-        FillEllipse _fillEllipse;
-        StrokePath _strokePath;
-        StrokeRect _strokeRect;
-        StrokeArc _strokeArc;
-        StrokeEllipse _strokeEllipse;
-        Point _point;
-        Line _line;
-        ImageCopy _imageCopy;
-    };
-
-private:
-    Variant _tag;
+    virtual Variant kind() const { return Variant::Invalid; }
 };
 
-class CanvasCommandFactory : public QObject {
+namespace canvas_cmd {
+struct Invalid : public CanvasCommand {
+    virtual Variant kind() const override { return Variant::Invalid; }
+};
+
+struct SetStrokeWidth : public CanvasCommand {
+    virtual Variant kind() const override { return Variant::SetStrokeWidth; }
+
+    SetStrokeWidth(qreal width) : width(width) {}
+
+    qreal width;
+};
+
+struct SetColor : public CanvasCommand {
+    virtual Variant kind() const override { return Variant::SetColor; }
+
+    SetColor(const glm::u8vec3& color) : color(color) {}
+
+    glm::u8vec3 color;
+};
+
+struct SetHints : public CanvasCommand {
+    virtual Variant kind() const override { return Variant::SetHints; }
+
+    SetHints(int hints) : hints(static_cast<CanvasCommand::RenderHint>(hints)) {}
+
+    CanvasCommand::RenderHint hints;
+};
+
+struct SetBlendMode : public CanvasCommand {
+    virtual Variant kind() const override { return Variant::SetBlendMode; }
+
+    SetBlendMode(int mode) : mode(static_cast<QPainter::CompositionMode>(mode)) {}
+
+    QPainter::CompositionMode mode;
+};
+
+struct SetFont : public CanvasCommand {
+    virtual Variant kind() const override { return Variant::SetFont; }
+
+    SetFont(const QString& family, int size = 12, int weight = 400, bool italic = false) : family(family), size(size), weight(weight), italic(italic) {}
+
+    QString family;
+    int size;
+    int weight;
+    bool italic;
+};
+
+struct ClearRect : public CanvasCommand {
+    virtual Variant kind() const override { return Variant::ClearRect; }
+
+    ClearRect(const QRect& rect) : rect(rect) {}
+
+    QRect rect;
+};
+
+struct FillPath : public CanvasCommand {
+    virtual Variant kind() const override { return Variant::FillPath; }
+
+    FillPath(const QPainterPath& path) : path(path) {}
+
+    QPainterPath path;
+};
+
+struct FillRect : public CanvasCommand {
+    virtual Variant kind() const override { return Variant::FillRect; }
+
+    FillRect(const QRectF& rect) : rect(rect) {}
+
+    QRectF rect;
+};
+
+struct FillEllipse : public CanvasCommand {
+    virtual Variant kind() const override { return Variant::FillEllipse; }
+
+    FillEllipse(const QRectF& rect) : rect(rect) {}
+
+    QRectF rect;
+};
+
+struct FillText : public CanvasCommand {
+    virtual Variant kind() const override { return Variant::FillText; }
+
+    FillText(const QRectF& rect, const QString& text, int flag = 0) : rect(rect), text(text), flag(static_cast<Qt::AlignmentFlag>(flag)) {}
+
+    QRectF rect;
+    QString text;
+    Qt::AlignmentFlag flag;
+};
+
+struct StrokePath : public CanvasCommand {
+    virtual Variant kind() const override { return Variant::StrokePath; }
+
+    StrokePath(const QPainterPath& path) : path(path) {}
+
+    QPainterPath path;
+};
+
+struct StrokeRect : public CanvasCommand {
+    virtual Variant kind() const override { return Variant::StrokeRect; }
+
+    StrokeRect(const QRectF& rect) : rect(rect) {}
+
+    QRectF rect;
+};
+
+struct StrokeArc : public CanvasCommand {
+    virtual Variant kind() const override { return Variant::StrokeArc; }
+
+    StrokeArc(const QRectF& rect, qreal startAngle, qreal spanAngle) : rect(rect), startAngle(startAngle), spanAngle(spanAngle) {}
+
+    QRectF rect;
+    qreal startAngle, spanAngle;
+};
+
+struct StrokeEllipse : public CanvasCommand {
+    virtual Variant kind() const override { return Variant::StrokeEllipse; }
+
+    StrokeEllipse(const QRectF& rect) : rect(rect) {}
+
+    QRectF rect;
+};
+
+struct Point : public CanvasCommand {
+    virtual Variant kind() const override { return Variant::Point; }
+
+    Point(qreal x, qreal y) : point(QPointF(x, y)) {}
+
+    QPointF point;
+};
+
+struct Line : public CanvasCommand {
+    virtual Variant kind() const override { return Variant::Line; }
+
+    Line(qreal x1, qreal y1, qreal x2, qreal y2) : line(QLineF(x1, y1, x2, y2)) {}
+
+    QLineF line;
+};
+
+struct ImageCopy : public CanvasCommand {
+    virtual Variant kind() const override { return Variant::ImageCopy; }
+
+    ImageCopy(const CanvasImage& image, const QRectF& src, const QRectF& dst) : src(src), dst(dst), image(image) {}
+
+    QRectF src;
+    QRectF dst;
+    CanvasImage image;
+};
+}
+
+class CanvasCommandInterface : public QObject, protected Scriptable {
     Q_OBJECT
 
-public:
-    static CanvasCommand setStrokeWidth(qreal width) {
-        return CanvasCommand(CanvasCommand::SetStrokeWidth { width });
-    }
+    Q_PROPERTY(int TEXT_ALIGN_LEFT READ TEXT_ALIGN_LEFT CONSTANT)
+    Q_PROPERTY(int TEXT_ALIGN_RIGHT READ TEXT_ALIGN_RIGHT CONSTANT)
+    Q_PROPERTY(int TEXT_ALIGN_HCENTER READ TEXT_ALIGN_HCENTER CONSTANT)
+    Q_PROPERTY(int TEXT_ALIGN_JUSTIFY READ TEXT_ALIGN_JUSTIFY CONSTANT)
+    Q_PROPERTY(int TEXT_ALIGN_TOP READ TEXT_ALIGN_TOP CONSTANT)
+    Q_PROPERTY(int TEXT_ALIGN_BOTTOM READ TEXT_ALIGN_BOTTOM CONSTANT)
+    Q_PROPERTY(int TEXT_ALIGN_VCENTER READ TEXT_ALIGN_VCENTER CONSTANT)
+    Q_PROPERTY(int TEXT_ALIGN_BASELINE READ TEXT_ALIGN_BASELINE CONSTANT)
 
-    static CanvasCommand setColor(glm::u8vec3 color) {
-        return CanvasCommand(CanvasCommand::SetColor { color });
-    }
+    Q_PROPERTY(int HINT_ANTIALIASING READ HINT_ANTIALIASING CONSTANT)
+    Q_PROPERTY(int HINT_TEXT_ANTIALIASING READ HINT_TEXT_ANTIALIASING CONSTANT)
+    Q_PROPERTY(int HINT_BILINEAR_SCALING READ HINT_BILINEAR_SCALING CONSTANT)
 
-    static CanvasCommand setHints(uint hints) {
-        return CanvasCommand(CanvasCommand::SetHints { static_cast<CanvasCommand::RenderHint>(hints) });
-    }
+    Q_PROPERTY(int BLEND_SOURCEOVER READ BLEND_SOURCEOVER CONSTANT)
+    Q_PROPERTY(int BLEND_DESTINATIONOVER READ BLEND_DESTINATIONOVER CONSTANT)
+    Q_PROPERTY(int BLEND_CLEAR READ BLEND_CLEAR CONSTANT)
+    Q_PROPERTY(int BLEND_SOURCE READ BLEND_SOURCE CONSTANT)
+    Q_PROPERTY(int BLEND_DESTINATION READ BLEND_DESTINATION CONSTANT)
+    Q_PROPERTY(int BLEND_SOURCEIN READ BLEND_SOURCEIN CONSTANT)
+    Q_PROPERTY(int BLEND_DESTINATIONIN READ BLEND_DESTINATIONIN CONSTANT)
+    Q_PROPERTY(int BLEND_SOURCEOUT READ BLEND_SOURCEOUT CONSTANT)
+    Q_PROPERTY(int BLEND_DESTINATIONOUT READ BLEND_DESTINATIONOUT CONSTANT)
+    Q_PROPERTY(int BLEND_SOURCEATOP READ BLEND_SOURCEATOP CONSTANT)
+    Q_PROPERTY(int BLEND_DESTINATIONATOP READ BLEND_DESTINATIONATOP CONSTANT)
+    Q_PROPERTY(int BLEND_XOR READ BLEND_XOR CONSTANT)
+    Q_PROPERTY(int BLEND_PLUS READ BLEND_PLUS CONSTANT)
+    Q_PROPERTY(int BLEND_MULTIPLY READ BLEND_MULTIPLY CONSTANT)
+    Q_PROPERTY(int BLEND_SCREEN READ BLEND_SCREEN CONSTANT)
+    Q_PROPERTY(int BLEND_OVERLAY READ BLEND_OVERLAY CONSTANT)
+    Q_PROPERTY(int BLEND_DARKEN READ BLEND_DARKEN CONSTANT)
+    Q_PROPERTY(int BLEND_LIGHTEN READ BLEND_LIGHTEN CONSTANT)
+    Q_PROPERTY(int BLEND_COLORDODGE READ BLEND_COLORDODGE CONSTANT)
+    Q_PROPERTY(int BLEND_COLORBURN READ BLEND_COLORBURN CONSTANT)
+    Q_PROPERTY(int BLEND_HARDLIGHT READ BLEND_HARDLIGHT CONSTANT)
+    Q_PROPERTY(int BLEND_SOFTLIGHT READ BLEND_SOFTLIGHT CONSTANT)
+    Q_PROPERTY(int BLEND_DIFFERENCE READ BLEND_DIFFERENCE CONSTANT)
+    Q_PROPERTY(int BLEND_EXCLUSION READ BLEND_EXCLUSION CONSTANT)
+
+public slots:
+    CanvasCommand setStrokeWidth(qreal width) const;
+    CanvasCommand setColor(const glm::u8vec3& color) const;
+    CanvasCommand setHints(int hints) const;
+    CanvasCommand setBlendMode(int mode) const;
+    CanvasCommand setFont(const QString& family, int size = 12, int weight = QFont::Normal, bool italic = false) const;
+    CanvasCommand clearRect(const QRect& rect) const;
+    CanvasCommand fillPath(const QPainterPath& path) const;
+    CanvasCommand fillRect(const QRectF& rect) const;
+    CanvasCommand fillEllipse(const QRectF& rect) const;
+    CanvasCommand fillText(const QString& text, const QRectF& rect, int flag = 0) const;
+    CanvasCommand strokePath(const QPainterPath& path) const;
+    CanvasCommand strokeRect(const QRectF& rect) const;
+    CanvasCommand strokeArc(const QRectF& rect, qreal startAngle, qreal spanAngle) const;
+    CanvasCommand strokeEllipse(const QRectF& rect) const;
+    CanvasCommand point(qreal x, qreal y) const;
+    CanvasCommand line(qreal x1, qreal y1, qreal x2, qreal y2) const;
+    CanvasCommand imageCopy(const CanvasImage& image, const QRectF& src, const QRectF& dest) const;
+
+private:
+    int TEXT_ALIGN_LEFT() const { return Qt::AlignLeft; }
+    int TEXT_ALIGN_RIGHT() const { return Qt::AlignRight; }
+    int TEXT_ALIGN_HCENTER() const { return Qt::AlignHCenter; }
+    int TEXT_ALIGN_JUSTIFY() const { return Qt::AlignJustify; }
+    int TEXT_ALIGN_TOP() const { return Qt::AlignTop; }
+    int TEXT_ALIGN_BOTTOM() const { return Qt::AlignBottom; }
+    int TEXT_ALIGN_VCENTER() const { return Qt::AlignVCenter; }
+    int TEXT_ALIGN_BASELINE() const { return Qt::AlignBaseline; }
+
+    int HINT_ANTIALIASING() const { return CanvasCommand::RenderHint::PrimitiveAntialiasing; }
+    int HINT_TEXT_ANTIALIASING() const { return CanvasCommand::RenderHint::PrimitiveAntialiasing; }
+    int HINT_BILINEAR_SCALING() const { return CanvasCommand::RenderHint::BilinearImageScaling; }
+
+    int BLEND_SOURCEOVER() const { return QPainter::CompositionMode_SourceOver; }
+    int BLEND_DESTINATIONOVER() const { return QPainter::CompositionMode_DestinationOver; }
+    int BLEND_CLEAR() const { return QPainter::CompositionMode_Clear; }
+    int BLEND_SOURCE() const { return QPainter::CompositionMode_Source; }
+    int BLEND_DESTINATION() const { return QPainter::CompositionMode_Destination; }
+    int BLEND_SOURCEIN() const { return QPainter::CompositionMode_SourceIn; }
+    int BLEND_DESTINATIONIN() const { return QPainter::CompositionMode_DestinationIn; }
+    int BLEND_SOURCEOUT() const { return QPainter::CompositionMode_SourceOut; }
+    int BLEND_DESTINATIONOUT() const { return QPainter::CompositionMode_DestinationOut; }
+    int BLEND_SOURCEATOP() const { return QPainter::CompositionMode_SourceAtop; }
+    int BLEND_DESTINATIONATOP() const { return QPainter::CompositionMode_DestinationAtop; }
+    int BLEND_XOR() const { return QPainter::CompositionMode_Xor; }
+    int BLEND_PLUS() const { return QPainter::CompositionMode_Plus; }
+    int BLEND_MULTIPLY() const { return QPainter::CompositionMode_Multiply; }
+    int BLEND_SCREEN() const { return QPainter::CompositionMode_Screen; }
+    int BLEND_OVERLAY() const { return QPainter::CompositionMode_Overlay; }
+    int BLEND_DARKEN() const { return QPainter::CompositionMode_Darken; }
+    int BLEND_LIGHTEN() const { return QPainter::CompositionMode_Lighten; }
+    int BLEND_COLORDODGE() const { return QPainter::CompositionMode_ColorDodge; }
+    int BLEND_COLORBURN() const { return QPainter::CompositionMode_ColorBurn; }
+    int BLEND_HARDLIGHT() const { return QPainter::CompositionMode_HardLight; }
+    int BLEND_SOFTLIGHT() const { return QPainter::CompositionMode_SoftLight; }
+    int BLEND_DIFFERENCE() const { return QPainter::CompositionMode_Difference; }
+    int BLEND_EXCLUSION() const { return QPainter::CompositionMode_Exclusion; }
 };
 
 void registerCanvasMetaTypes(ScriptEngine *engine);
diff --git a/libraries/script-engine/src/ScriptManager.cpp b/libraries/script-engine/src/ScriptManager.cpp
index 58fee1f06d..54c0a59d38 100644
--- a/libraries/script-engine/src/ScriptManager.cpp
+++ b/libraries/script-engine/src/ScriptManager.cpp
@@ -847,6 +847,10 @@ void ScriptManager::init() {
     scriptEngine->registerGlobalObject("StackTest", new StackTestScriptingInterface(this));
 #endif
 
+    if (_context == CLIENT_SCRIPT) {
+        scriptEngine->registerGlobalObject("CanvasCommand", &_canvasCommandLibrary);
+    }
+
     qCDebug(scriptengine) << "Engine initialized";
 }
 
diff --git a/libraries/script-engine/src/ScriptManager.h b/libraries/script-engine/src/ScriptManager.h
index 7185d3e615..88bbd7c357 100644
--- a/libraries/script-engine/src/ScriptManager.h
+++ b/libraries/script-engine/src/ScriptManager.h
@@ -48,6 +48,7 @@
 #include "ScriptValue.h"
 #include "ScriptException.h"
 #include "Vec3.h"
+#include "CanvasCommand.h"
 
 static const QString NO_SCRIPT("");
 
@@ -1636,6 +1637,7 @@ protected:
     Mat4 _mat4Library;
     ScriptUUID _uuidLibrary;
     ConsoleScriptingInterface _consoleScriptingInterface;
+    CanvasCommandInterface _canvasCommandLibrary;
     std::atomic<bool> _isUserLoaded { false };
     bool _isReloading { false };