// // CanvasCommand.cpp // libraries/script-engine/src // // Created by Ada on 2025-02-27 // Copyright 2025 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // SPDX-License-Identifier: Apache-2.0 // #include "CanvasCommand.h" #include #include "ScriptEngine.h" #include "ScriptEngineCast.h" #include "ScriptValue.h" #include "ScriptManager.h" #include "ScriptValueUtils.h" #include "v8/FastScriptValueUtils.h" STATIC_SCRIPT_TYPES_INITIALIZER((+[](ScriptManager* manager){ auto scriptEngine = manager->engine().get(); scriptRegisterMetaType(scriptEngine, "CanvasCommand"); scriptRegisterMetaType(scriptEngine, "CanvasImage"); scriptRegisterMetaType(scriptEngine, "QPainterPath"); scriptRegisterMetaType, qVectorCanvasCommandToScriptValue, qVectorCanvasCommandFromScriptValue>(scriptEngine); scriptRegisterMetaType(scriptEngine, "CanvasPathElement"); })); const QString CMD_TYPE_PROP_NAME = "type"; const QString IMG_WIDTH_PROP_NAME = "width"; const QString IMG_HEIGHT_PROP_NAME = "height"; const QString IMG_DATA_PROP_NAME = "data"; ScriptValue canvasCommandToScriptValue(ScriptEngine* engine, const CanvasCommand& cmd) { using Variant = CanvasCommand::Variant; ScriptValue obj = engine->newObject(); switch (cmd.kind) { case Variant::SetStrokeWidth: { obj.setProperty(CMD_TYPE_PROP_NAME, "strokeWidth"); obj.setProperty("width", cmd._float[0]); return obj; } case Variant::SetColor: { obj.setProperty(CMD_TYPE_PROP_NAME, "color"); auto color = engine->newArray(4); color.setProperty(0, cmd._color.red()); color.setProperty(1, cmd._color.green()); color.setProperty(2, cmd._color.blue()); color.setProperty(3, cmd._color.alpha()); obj.setProperty("color", color); return obj; } case Variant::SetHints: { obj.setProperty(CMD_TYPE_PROP_NAME, "hints"); obj.setProperty("hints", cmd._int[0]); return obj; } case Variant::SetBlendMode: { obj.setProperty(CMD_TYPE_PROP_NAME, "blendMode"); obj.setProperty("mode", cmd._int[0]); return obj; } case Variant::SetFont: { obj.setProperty(CMD_TYPE_PROP_NAME, "font"); obj.setProperty("family", cmd._text); obj.setProperty("size", cmd._int[0]); obj.setProperty("weight", cmd._int[1]); obj.setProperty("italic", cmd._int[2]); return obj; } case Variant::ClearRect: { obj.setProperty(CMD_TYPE_PROP_NAME, "clearRect"); obj.setProperty("x", cmd._rect.x()); obj.setProperty("y", cmd._rect.y()); obj.setProperty("w", cmd._rect.width()); obj.setProperty("h", cmd._rect.height()); return obj; } case Variant::FillPath: { obj.setProperty(CMD_TYPE_PROP_NAME, "fillPath"); obj.setProperty("path", qPainterPathToScriptValue(engine, cmd._paintPath)); return obj; } case Variant::FillRect: { obj.setProperty(CMD_TYPE_PROP_NAME, "fillRect"); obj.setProperty("x", cmd._rect.x()); obj.setProperty("y", cmd._rect.y()); obj.setProperty("w", cmd._rect.width()); obj.setProperty("h", cmd._rect.height()); return obj; } case Variant::FillEllipse: { obj.setProperty(CMD_TYPE_PROP_NAME, "fillEllipse"); obj.setProperty("x", cmd._rect.x()); obj.setProperty("y", cmd._rect.y()); obj.setProperty("w", cmd._rect.width()); obj.setProperty("h", cmd._rect.height()); return obj; } case Variant::FillText: { obj.setProperty(CMD_TYPE_PROP_NAME, "fillText"); obj.setProperty("x", cmd._rect.x()); obj.setProperty("y", cmd._rect.y()); obj.setProperty("w", cmd._rect.width()); obj.setProperty("h", cmd._rect.height()); obj.setProperty("text", cmd._text); obj.setProperty("flag", cmd._int[0]); return obj; } case Variant::StrokePath: { obj.setProperty(CMD_TYPE_PROP_NAME, "strokePath"); obj.setProperty("path", qPainterPathToScriptValue(engine, cmd._paintPath)); return obj; } case Variant::StrokeRect: { obj.setProperty(CMD_TYPE_PROP_NAME, "strokeRect"); obj.setProperty("x", cmd._rect.x()); obj.setProperty("y", cmd._rect.y()); obj.setProperty("w", cmd._rect.width()); obj.setProperty("h", cmd._rect.height()); return obj; } case Variant::StrokeArc: { obj.setProperty(CMD_TYPE_PROP_NAME, "strokeArc"); obj.setProperty("x", cmd._rect.x()); obj.setProperty("y", cmd._rect.y()); obj.setProperty("w", cmd._rect.width()); obj.setProperty("h", cmd._rect.height()); obj.setProperty("startAngle", cmd._float[0]); obj.setProperty("spanAngle", cmd._float[1]); return obj; } case Variant::StrokeEllipse: { obj.setProperty(CMD_TYPE_PROP_NAME, "strokeEllipse"); obj.setProperty("x", cmd._rect.x()); obj.setProperty("y", cmd._rect.y()); obj.setProperty("w", cmd._rect.width()); obj.setProperty("h", cmd._rect.height()); return obj; } case Variant::Point: { obj.setProperty(CMD_TYPE_PROP_NAME, "point"); obj.setProperty("x", cmd._point.x()); obj.setProperty("y", cmd._point.y()); return obj; } case Variant::Line: { obj.setProperty(CMD_TYPE_PROP_NAME, "line"); obj.setProperty("x1", cmd._line.x1()); obj.setProperty("y1", cmd._line.y1()); obj.setProperty("x2", cmd._line.x2()); obj.setProperty("y2", cmd._line.y2()); return obj; } case Variant::ImageCopy: { obj.setProperty(CMD_TYPE_PROP_NAME, "imageCopy"); obj.setProperty("srcX", cmd._rect.x()); obj.setProperty("srcY", cmd._rect.y()); obj.setProperty("srcW", cmd._rect.width()); obj.setProperty("srcH", cmd._rect.height()); obj.setProperty("destX", cmd._rect2.x()); obj.setProperty("destY", cmd._rect2.y()); obj.setProperty("destW", cmd._rect2.width()); obj.setProperty("destH", cmd._rect2.height()); obj.setProperty("image", canvasImageToScriptValue(engine, cmd._image)); return obj; } case Variant::Invalid: break; } return obj; } bool canvasCommandFromScriptValue(const ScriptValue& object, CanvasCommand& cmd) { QString type = object.property(CMD_TYPE_PROP_NAME).toString(); if (type == "strokeWidth") { cmd = CanvasCommand::setStrokeWidth(object.property("width").toNumber()); } else if (type == "color") { QColor c; if (!qColorFromScriptValue(object.property("color"), c)) { return false; } cmd = CanvasCommand::setColor(c); } else if (type == "hints") { cmd = CanvasCommand::setHints(object.property("hints").toInt32()); } else if (type == "blendMode") { cmd = CanvasCommand::setBlendMode(object.property("mode").toInt32()); } else if (type == "font") { cmd = CanvasCommand::setFont( object.property("family").toString(), object.property("size").toInt32(), object.property("weight").toInt32(), object.property("italic").toBool() ); } else if (type == "clearRect") { cmd = CanvasCommand::clearRect( object.property("x").toInt32(), object.property("y").toInt32(), object.property("w").toInt32(), object.property("h").toInt32() ); } else if (type == "fillPath") { cmd = CanvasCommand::fillPath(qPainterPathFromScriptValue(object.property("path"))); } else if (type == "fillRect") { cmd = CanvasCommand::fillRect(QRectF( object.property("x").toNumber(), object.property("y").toNumber(), object.property("w").toNumber(), object.property("h").toNumber() )); } else if (type == "fillEllipse") { cmd = CanvasCommand::fillEllipse(QRectF( object.property("x").toNumber(), object.property("y").toNumber(), object.property("w").toNumber(), object.property("h").toNumber() )); } else if (type == "fillText") { cmd = CanvasCommand::fillText( object.property("text").toString(), QRectF( object.property("x").toNumber(), object.property("y").toNumber(), object.property("w").toNumber(), object.property("h").toNumber() ), object.property("flag").toInt32() ); } else if (type == "strokePath") { cmd = CanvasCommand::strokePath(qPainterPathFromScriptValue(object.property("path"))); } else if (type == "strokeRect") { cmd = CanvasCommand::strokeRect(QRectF( object.property("x").toNumber(), object.property("y").toNumber(), object.property("w").toNumber(), object.property("h").toNumber() )); } else if (type == "strokeArc") { cmd = CanvasCommand::strokeArc( QRectF( object.property("x").toNumber(), object.property("y").toNumber(), object.property("w").toNumber(), object.property("h").toNumber() ), object.property("startAngle").toNumber(), object.property("spanAngle").toNumber() ); } else if (type == "strokeEllipse") { cmd = CanvasCommand::strokeEllipse(QRectF( object.property("x").toNumber(), object.property("y").toNumber(), object.property("w").toNumber(), object.property("h").toNumber() )); } else if (type == "point") { cmd = CanvasCommand::point( object.property("x").toNumber(), object.property("y").toNumber() ); } else if (type == "line") { cmd = CanvasCommand::line( object.property("x1").toNumber(), object.property("y1").toNumber(), object.property("x2").toNumber(), object.property("y2").toNumber() ); } else if (type == "imageCopy") { cmd = CanvasCommand::imageCopy( canvasImageFromScriptValue(object.property("image")), QRectF( object.property("srcX").toNumber(), object.property("srcY").toNumber(), object.property("srcW").toNumber(), object.property("srcH").toNumber() ), QRectF( object.property("destX").toNumber(), object.property("destY").toNumber(), object.property("destW").toNumber(), object.property("destH").toNumber() ) ); } else { cmd = CanvasCommand {}; } return true; } CanvasCommand canvasCommandFromScriptValue(const ScriptValue& object) { CanvasCommand cmd; canvasCommandFromScriptValue(object, cmd); return cmd; } QVector qVectorCanvasCommandFromScriptValue(const ScriptValue& object) { QVector list; qVectorCanvasCommandFromScriptValue(object, list); return list; } bool qVectorCanvasCommandFromScriptValue(const ScriptValue& array, QVector& list) { int length = array.property("length").toInteger(); for (int i = 0; i < length; i++) { list << canvasCommandFromScriptValue(array.property(i)); } return true; } ScriptValue qVectorCanvasCommandToScriptValue(ScriptEngine* engine, const QVector& list) { auto array = engine->newArray(list.length()); for (int i = 0; i < list.length(); i++) { array.setProperty(i, canvasCommandToScriptValue(engine, list.at(i))); } return array; } ScriptValue canvasImageToScriptValue(ScriptEngine* engine, const CanvasImage& img) { ScriptValue obj = engine->newObject(); obj.setProperty(IMG_WIDTH_PROP_NAME, img.width); obj.setProperty(IMG_HEIGHT_PROP_NAME, img.height); obj.setProperty(IMG_DATA_PROP_NAME, qBytearrayToScriptValue(engine, img.buffer)); return obj; } bool canvasImageFromScriptValue(const ScriptValue& object, CanvasImage& img) { img.width = object.property(IMG_WIDTH_PROP_NAME).toInt32(); img.height = object.property(IMG_HEIGHT_PROP_NAME).toInt32(); return qBytearrayFromScriptValue(object.property(IMG_DATA_PROP_NAME), img.buffer); } CanvasImage canvasImageFromScriptValue(const ScriptValue& object) { CanvasImage img = {}; canvasImageFromScriptValue(object, img); return img; } ScriptValue qPainterPathToScriptValue(ScriptEngine* engine, const QPainterPath& path) { ScriptValue array = engine->newArray(path.elementCount()); for (int i = 0; i < path.elementCount(); i++) { ScriptValue obj = engine->newObject(); auto elem = path.elementAt(i); // curves have another two points if (elem.type == QPainterPath::CurveToElement) { obj.setProperty("type", static_cast(elem.type)); obj.setProperty("c1x", static_cast(elem.x)); obj.setProperty("c1y", static_cast(elem.y)); obj.setProperty("c2x", static_cast(path.elementAt(i + 1).x)); obj.setProperty("c2y", static_cast(path.elementAt(i + 1).y)); obj.setProperty("x", static_cast(path.elementAt(i + 2).x)); obj.setProperty("y", static_cast(path.elementAt(i + 2).y)); i += 2; } else { obj.setProperty("type", static_cast(elem.type)); obj.setProperty("x", static_cast(elem.x)); obj.setProperty("y", static_cast(elem.y)); } } return array; } bool qPainterPathFromScriptValue(const ScriptValue& array, QPainterPath& path) { int length = array.property("length").toInteger(); path.reserve(length); for (int i = 0; i < length; i++) { ScriptValue obj = array.property(i); uint type = obj.property("type").toUInt32(); auto x = obj.property("x").toNumber(); auto y = obj.property("y").toNumber(); if (type == QPainterPath::CurveToElement) { auto c1x = obj.property("c1x").toNumber(); auto c1y = obj.property("c1y").toNumber(); auto c2x = obj.property("c2x").toNumber(); auto c2y = obj.property("c2y").toNumber(); path.cubicTo(c1x, c1y, c2x, c2y, x, y); } else if (type == QPainterPath::LineToElement) { path.lineTo(x, y); } else { path.moveTo(x, y); } } return true; } QPainterPath qPainterPathFromScriptValue(const ScriptValue& object) { QPainterPath p; qPainterPathFromScriptValue(object, p); return p; } ScriptValue canvasPathElementToScriptValue(ScriptEngine* engine, const CanvasPathElement& elem) { auto obj = engine->newObject(); obj.setProperty("type", elem.type); obj.setProperty("x", elem.x); obj.setProperty("y", elem.y); if (elem.type == QPainterPath::CurveToElement) { obj.setProperty("c1x", elem.c1x); obj.setProperty("c1y", elem.c1y); obj.setProperty("c2x", elem.c2x); obj.setProperty("c2y", elem.c2y); } return obj; } bool canvasPathElementFromScriptValue(const ScriptValue& obj, CanvasPathElement& elem) { int type = obj.property("type").toInt32(); elem.type = type; elem.x = obj.property("x").toNumber(); elem.y = obj.property("y").toNumber(); if (type == QPainterPath::CurveToElement) { elem.c1x = obj.property("c1x").toNumber(); elem.c1y = obj.property("c1y").toNumber(); elem.c2x = obj.property("c2x").toNumber(); elem.c2y = obj.property("c2y").toNumber(); } return true; } CanvasPathElement canvasPathElementFromScriptValue(const ScriptValue& object) { CanvasPathElement e; canvasPathElementFromScriptValue(object, e); return e; }