summaryrefslogtreecommitdiff
path: root/content/handlers/javascript/duktape/CanvasRenderingContext2D.bnd
diff options
context:
space:
mode:
Diffstat (limited to 'content/handlers/javascript/duktape/CanvasRenderingContext2D.bnd')
-rw-r--r--content/handlers/javascript/duktape/CanvasRenderingContext2D.bnd603
1 files changed, 603 insertions, 0 deletions
diff --git a/content/handlers/javascript/duktape/CanvasRenderingContext2D.bnd b/content/handlers/javascript/duktape/CanvasRenderingContext2D.bnd
new file mode 100644
index 000000000..2fe73f4e1
--- /dev/null
+++ b/content/handlers/javascript/duktape/CanvasRenderingContext2D.bnd
@@ -0,0 +1,603 @@
+/* HTML canvas element rendering context binding using duktape and libdom
+ *
+ * Copyright 2020 Daniel Silverstone <dsilvers@netsurf-browser.org>
+ *
+ * This file is part of NetSurf, http://www.netsurf-browser.org/
+ *
+ * Released under the terms of the MIT License,
+ * http://www.opensource.org/licenses/mit-license
+ */
+
+class CanvasRenderingContext2D {
+ private struct dom_html_canvas_element *canvas;
+ private struct bitmap *bitmap;
+ private int width;
+ private int height;
+ private size_t stride;
+ private dom_event_listener *listener;
+ prologue %{
+/* prologue */
+#include "desktop/gui_internal.h"
+#include "desktop/gui_table.h"
+#include "netsurf/bitmap.h"
+#include "utils/corestrings.h"
+/* It's a smidge naughty of us to read
+ * this particular header, but we're needing
+ * to redraw the node we represent
+ */
+#include "content/handlers/html/private.h"
+
+static void redraw_node(dom_node *node)
+{
+ struct box *box = NULL;
+ html_content *htmlc = NULL;
+ dom_exception exc;
+ dom_document *doc;
+
+ exc = dom_node_get_user_data(node,
+ corestring_dom___ns_key_box_node_data,
+ &box);
+ if (exc != DOM_NO_ERR || box == NULL) {
+ return;
+ }
+
+ exc = dom_node_get_owner_document(node, &doc);
+ if (exc != DOM_NO_ERR || doc == NULL) {
+ return;
+ }
+
+ exc = dom_node_get_user_data(doc,
+ corestring_dom___ns_key_html_content_data,
+ &htmlc);
+ if (exc != DOM_NO_ERR || htmlc == NULL) {
+ dom_node_unref(doc);
+ return;
+ }
+
+ html__redraw_a_box(htmlc, box);
+
+ dom_node_unref(doc);
+}
+
+/**
+ * deal with events from the DOM for canvas node user data
+ *
+ * \param operation The DOM operation happening
+ * \param key The user data key
+ * \param data The user data (our bitmap)
+ * \param src The DOM node emitting the event (our <canvas>)
+ * \param dst The target DOM node if applicable
+ */
+static void
+canvas2d_user_data_handler(dom_node_operation operation,
+ dom_string *key,
+ void *data,
+ struct dom_node *src,
+ struct dom_node *dst)
+{
+ struct bitmap *newbitmap, *bitmap = (struct bitmap*)data, *oldbitmap = NULL;
+ int width, height;
+ size_t stride;
+
+ if (dom_string_isequal(key,corestring_dom___ns_key_canvas_node_data) == false || data == NULL) {
+ /* Not for us */
+ return;
+ }
+
+ switch (operation) {
+ case DOM_NODE_CLONED:
+ width = guit->bitmap->get_width(bitmap);
+ height = guit->bitmap->get_height(bitmap);
+ stride = guit->bitmap->get_rowstride(bitmap);
+ newbitmap = guit->bitmap->create(width, height,
+ BITMAP_NONE);
+ if (newbitmap != NULL) {
+ if (guit->bitmap->get_rowstride(newbitmap) == stride) {
+ // Compatible bitmap, bung the data over
+ memcpy(guit->bitmap->get_buffer(newbitmap),
+ guit->bitmap->get_buffer(bitmap),
+ stride * height);
+ guit->bitmap->modified(newbitmap);
+ }
+ }
+ if (dom_node_set_user_data(dst,
+ corestring_dom___ns_key_canvas_node_data,
+ newbitmap, canvas2d_user_data_handler,
+ &oldbitmap) == DOM_NO_ERR) {
+ if (oldbitmap != NULL)
+ guit->bitmap->destroy(oldbitmap);
+ }
+ break;
+
+ case DOM_NODE_RENAMED:
+ case DOM_NODE_IMPORTED:
+ case DOM_NODE_ADOPTED:
+ break;
+
+ case DOM_NODE_DELETED:
+ guit->bitmap->destroy(bitmap);
+ break;
+ default:
+ NSLOG(netsurf, INFO, "User data operation not handled.");
+ assert(0);
+ }
+}
+
+/**
+ * Give the canvas element an appropriately sized bitmap
+ *
+ * \param node The DOM node being inserted
+ * \param[out] bitmap_out The bitmap created
+ * \return NSERROR_OK on success else appropriate error code
+ */
+static nserror canvas2d_create_bitmap(dom_node *node, struct bitmap **bitmap_out)
+{
+ dom_exception exc;
+ dom_string *width_s = NULL, *height_s = NULL;
+ unsigned long width = 300, height = 150;
+ struct bitmap *bitmap, *oldbitmap = NULL;
+
+ exc = dom_element_get_attribute(node,
+ corestring_dom_width,
+ &width_s);
+ if (exc == DOM_NO_ERR && width_s != NULL) {
+ const char *ptr = (const char *)dom_string_data(width_s);
+ const char *endptr = ptr + dom_string_length(width_s);
+ char * ended;
+ unsigned long width_n = strtoul(ptr, &ended, 10);
+
+ if (ended == endptr || strcasecmp(ended, "px") == 0) {
+ /* parsed it all */
+ width = width_n;
+ }
+
+ dom_string_unref(width_s);
+ }
+
+ exc = dom_element_get_attribute(node,
+ corestring_dom_height,
+ &height_s);
+ if (exc == DOM_NO_ERR && height_s != NULL) {
+ const char *ptr = (const char *)dom_string_data(height_s);
+ const char *endptr = ptr + dom_string_length(height_s);
+ char * ended;
+ unsigned long height_n = strtoul(ptr, &ended, 10);
+
+ if (ended == endptr || strcasecmp(ended, "px") == 0) {
+ /* parsed it all */
+ height = height_n;
+ }
+
+ dom_string_unref(height_s);
+ }
+
+ bitmap = guit->bitmap->create(
+ (int)width, (int)height,
+ BITMAP_NONE);
+
+ if (bitmap == NULL) {
+ return NSERROR_NOMEM;
+ }
+
+ memset(guit->bitmap->get_buffer(bitmap),
+ 0, /* Transparent black */
+ height * guit->bitmap->get_rowstride(bitmap));
+ guit->bitmap->modified(bitmap);
+
+ exc = dom_node_set_user_data(node,
+ corestring_dom___ns_key_canvas_node_data,
+ bitmap,
+ canvas2d_user_data_handler,
+ &oldbitmap);
+
+ if (exc != DOM_NO_ERR) {
+ guit->bitmap->destroy(bitmap);
+ return NSERROR_DOM;
+ }
+
+ assert(oldbitmap == NULL);
+
+ if (bitmap_out != NULL)
+ *bitmap_out = bitmap;
+
+ return NSERROR_OK;
+}
+
+/**
+ * Handle subtree modified events for our canvas node
+ *
+ * If width or height has changed relative to our priv, then
+ * we need to recreate the bitmap and reset our cached width
+ * and height values in order to be safe. Plus redraw ourselves.
+ *
+ * \param evt The event which occurred
+ * \param pw The private pointer for our canvas object
+ */
+static void
+canvas2d__handle_dom_event(dom_event *evt, void *pw)
+{
+ canvas_rendering_context2d_private_t *priv = pw;
+ dom_ulong width;
+ dom_ulong height;
+ dom_exception exc;
+ struct bitmap *newbitmap, *oldbitmap = NULL;
+ size_t stride;
+ dom_event_flow_phase phase;
+
+ exc = dom_event_get_event_phase(evt, &phase);
+ assert(exc == DOM_NO_ERR);
+ /* If we're not being hit right now, we're not up for it */
+ if (phase != DOM_AT_TARGET) return;
+
+ /* Rather than being complex about things, let's just work out
+ * what the width and height are and hope nothing else matters
+ */
+
+ exc = dom_html_canvas_element_get_width(priv->canvas, &width);
+ if (exc != DOM_NO_ERR) return;
+ exc = dom_html_canvas_element_get_height(priv->canvas, &height);
+ if (exc != DOM_NO_ERR) return;
+
+ if ((int)height == priv->height && (int)width == priv->width) return;
+
+ /* Okay, we need to reallocate our bitmap and re-cache values */
+
+ newbitmap = guit->bitmap->create(width, height, BITMAP_NONE);
+ stride = guit->bitmap->get_rowstride(newbitmap);
+
+ if (newbitmap != NULL) {
+ memset(guit->bitmap->get_buffer(newbitmap),
+ 0,
+ stride * height);
+ guit->bitmap->modified(newbitmap);
+ }
+
+ if (dom_node_set_user_data(priv->canvas,
+ corestring_dom___ns_key_canvas_node_data,
+ newbitmap, canvas2d_user_data_handler,
+ &oldbitmap) == DOM_NO_ERR) {
+ if (oldbitmap != NULL)
+ guit->bitmap->destroy(oldbitmap);
+ } else {
+ guit->bitmap->destroy(newbitmap);
+ /* We'll stick with the old, odd though that might be */
+ return;
+ }
+
+ /* Cache the new values */
+ priv->width = (int)width;
+ priv->height = (int)height;
+ priv->stride = stride;
+ priv->bitmap = newbitmap;
+}
+
+typedef struct {
+ uint8_t *ptr;
+ size_t stride;
+ ssize_t width;
+ ssize_t height;
+} raw_bitmap;
+
+typedef struct {
+ raw_bitmap src;
+ raw_bitmap dst;
+ /* These are relative to the destination top/left */
+ ssize_t dst_x;
+ ssize_t dst_y;
+ /* These are relative to the source top/left */
+ ssize_t x1;
+ ssize_t y1;
+ /* And these are +1, so a 1x1 copy will have x2==x1+1 etc */
+ ssize_t x2;
+ ssize_t y2;
+} copy_operation;
+
+/**
+ * Copy from src to dst
+ *
+ * Note, this is destructive to its copy_operation input
+ *
+ * \param op The copy operation to perform
+ * \return Whether the destination bitmap was altered
+ */
+static bool
+canvas2d__copy_bitmap_to_bitmap(copy_operation *op)
+{
+ /* Constrain src rectangle to src bitmap size */
+ if (op->x1 < 0) op->x1 = 0;
+ if (op->y1 < 0) op->y1 = 0;
+ if (op->x2 > op->src.width) op->x2 = op->src.width;
+ if (op->y2 > op->src.height) op->y2 = op->src.height;
+ /* Offset the rectangle into dst coordinates */
+ op->x1 += op->dst_x;
+ op->x2 += op->dst_x;
+ op->y1 += op->dst_y;
+ op->y2 += op->dst_y;
+ /* Constrain dst rectangle to dst bitmap */
+ if (op->x1 < 0) op->x1 = 0;
+ if (op->y1 < 0) op->y1 = 0;
+ if (op->x2 > op->dst.width) op->x2 = op->dst.width;
+ if (op->y2 > op->dst.height) op->y2 = op->dst.height;
+ /* If we have nothing to copy, stop now */
+ if ((op->x2 - op->x1) < 1 ||
+ (op->y2 - op->y1) < 1)
+ return false;
+ /* Okay, stuff to copy, so let's begin */
+ op->src.ptr +=
+ (op->src.stride * (op->y1 - op->dst_y)) + /* move down y1 rows */
+ (op->x1 - op->dst_x) * 4; /* and across x1 pixels */
+ op->dst.ptr +=
+ (op->dst.stride * op->y1) + /* down down y1 rows */
+ (op->x1 * 4); /* and across x1 pixels */
+ for (ssize_t rowctr = op->y2 - op->y1; rowctr > 0; --rowctr) {
+ memcpy(op->dst.ptr, op->src.ptr, (op->x2 - op->x1) * 4);
+ op->src.ptr += op->src.stride;
+ op->dst.ptr += op->dst.stride;
+ }
+ return true;
+}
+
+/* prologue ends */
+%};
+};
+
+init CanvasRenderingContext2D(struct dom_html_canvas_element *canvas)
+%{
+ struct bitmap *bitmap;
+ dom_exception exc;
+
+ assert(canvas != NULL);
+
+ priv->canvas = canvas;
+ dom_node_ref(canvas);
+
+ exc = dom_event_listener_create(canvas2d__handle_dom_event,
+ priv,
+ &priv->listener);
+ assert(exc == DOM_NO_ERR);
+
+ exc = dom_event_target_add_event_listener(
+ canvas,
+ corestring_dom_DOMSubtreeModified,
+ priv->listener,
+ false);
+ assert(exc == DOM_NO_ERR);
+
+ exc = dom_node_get_user_data(canvas,
+ corestring_dom___ns_key_canvas_node_data,
+ &bitmap);
+ assert(exc == DOM_NO_ERR);
+
+ if (bitmap == NULL) {
+ if (canvas2d_create_bitmap((dom_node *)canvas,
+ &bitmap) != NSERROR_OK) {
+ priv->bitmap = NULL;
+ priv->width = -1;
+ priv->height = -1;
+ priv->stride = 0;
+ return;
+ }
+ }
+
+ assert(bitmap != NULL);
+
+ priv->bitmap = bitmap;
+ priv->width = guit->bitmap->get_width(bitmap);
+ priv->height = guit->bitmap->get_height(bitmap);
+ priv->stride = guit->bitmap->get_rowstride(bitmap);
+%}
+
+fini CanvasRenderingContext2D()
+%{
+ dom_exception exc;
+ exc = dom_event_target_remove_event_listener(
+ priv->canvas,
+ corestring_dom_DOMSubtreeModified,
+ priv->listener,
+ false);
+ assert(exc == DOM_NO_ERR);
+ dom_event_listener_unref(priv->listener);
+ dom_node_unref(priv->canvas);
+%}
+
+getter CanvasRenderingContext2D::canvas()
+%{
+ dukky_push_node(ctx, (dom_node *)priv->canvas);
+ return 1;
+%}
+
+getter CanvasRenderingContext2D::width()
+%{
+ dom_exception exc;
+ dom_ulong width;
+
+ exc = dom_html_canvas_element_get_width(priv->canvas, &width);
+ if (exc != DOM_NO_ERR) return 0;
+
+ duk_push_number(ctx, (duk_double_t)width);
+ return 1;
+%}
+
+setter CanvasRenderingContext2D::width()
+%{
+ dom_exception exc;
+ dom_ulong width = duk_get_uint(ctx, 0);
+
+ exc = dom_html_canvas_element_set_width(priv->canvas, width);
+ if (exc != DOM_NO_ERR) return 0;
+
+ return 1;
+%}
+
+getter CanvasRenderingContext2D::height()
+%{
+ dom_exception exc;
+ dom_ulong height;
+
+ exc = dom_html_canvas_element_get_height(priv->canvas, &height);
+ if (exc != DOM_NO_ERR) return 0;
+
+ duk_push_number(ctx, (duk_double_t)height);
+ return 1;
+%}
+
+setter CanvasRenderingContext2D::height()
+%{
+ dom_exception exc;
+ dom_ulong height = duk_get_uint(ctx, 0);
+
+ exc = dom_html_canvas_element_set_height(priv->canvas, height);
+ if (exc != DOM_NO_ERR) return 0;
+
+ return 1;
+%}
+
+method CanvasRenderingContext2D::createImageData()
+%{
+ /* Can be called either with width and height, or with a reference
+ * imagedata object
+ */
+ image_data_private_t *idpriv;
+ int width, height;
+
+ if (duk_get_top(ctx) == 2) {
+ width = duk_to_int(ctx, 0);
+ height = duk_to_int(ctx, 1);
+ } else if (dukky_instanceof(ctx, 0, PROTO_NAME(IMAGEDATA))) {
+ duk_get_prop_string(ctx, 0, dukky_magic_string_private);
+ idpriv = duk_get_pointer(ctx, -1);
+ width = idpriv->width;
+ height = idpriv->height;
+ duk_pop(ctx);
+ } else {
+ duk_push_null(ctx);
+ return 1;
+ }
+
+ duk_push_int(ctx, width);
+ duk_push_int(ctx, height);
+ if (dukky_create_object(ctx,
+ PROTO_NAME(IMAGEDATA),
+ 2) != DUK_EXEC_SUCCESS) {
+ return duk_error(ctx,
+ DUK_ERR_ERROR,
+ "Unable to create ImageData");
+ }
+ return 1;
+%}
+
+method CanvasRenderingContext2D::getImageData()
+%{
+ /* called with x, y, width, height */
+ int x = duk_get_int(ctx, 0);
+ int y = duk_get_int(ctx, 1);
+ int width = duk_get_int(ctx, 2);
+ int height = duk_get_int(ctx, 3);
+ image_data_private_t *idpriv;
+ copy_operation copyop;
+
+ if (priv->bitmap == NULL)
+ return duk_generic_error(ctx, "Canvas in bad state, sorry");
+
+ duk_push_int(ctx, width);
+ duk_push_int(ctx, height);
+ if (dukky_create_object(ctx,
+ PROTO_NAME(IMAGEDATA),
+ 2) != DUK_EXEC_SUCCESS) {
+ return duk_error(ctx,
+ DUK_ERR_ERROR,
+ "Unable to create ImageData");
+ }
+
+ /* ... imgdata */
+ duk_get_prop_string(ctx, -1, dukky_magic_string_private);
+ idpriv = duk_get_pointer(ctx, -1);
+ duk_pop(ctx);
+
+ /* We now have access to the imagedata private, so we need to copy
+ * the pixel range out of ourselves
+ */
+ copyop.src.ptr = guit->bitmap->get_buffer(priv->bitmap);
+ copyop.src.stride = priv->stride;
+ copyop.src.width = priv->width;
+ copyop.src.height = priv->height;
+
+ copyop.dst.ptr = idpriv->data;
+ copyop.dst.stride = idpriv->width * 4;
+ copyop.dst.width = idpriv->width;
+ copyop.dst.height = idpriv->height;
+
+ /* Copying to top/left of our new bitmap */
+ copyop.dst_x = 0;
+ copyop.dst_y = 0;
+
+ /* Copying from x,y for width,height */
+ copyop.x1 = x;
+ copyop.x2 = x + width;
+ copyop.y1 = y;
+ copyop.y2 = y + height;
+
+ /* We don't care if the copy operation wrote or not because
+ * we don't need to invalidate ImageData objects
+ */
+ (void)canvas2d__copy_bitmap_to_bitmap(&copyop);
+ return 1;
+%}
+
+method CanvasRenderingContext2D::putImageData()
+%{
+ /* imgdata, x, y[, clipx, clipy, clipw, cliph] */
+ /* If provided, the clip coordinates are within the input image data */
+ /* We pretend the image is placed at x,y within ourselves, and then we
+ * copy the clip rectangle (defaults to whole image)
+ */
+ image_data_private_t *idpriv;
+ copy_operation copyop;
+
+ if (!dukky_instanceof(ctx, 0, PROTO_NAME(IMAGEDATA))) {
+ return duk_generic_error(ctx, "Expected ImageData as first argument");
+ }
+
+ if (priv->bitmap == NULL)
+ return duk_generic_error(ctx, "Canvas in bad state, sorry");
+
+ duk_get_prop_string(ctx, 0, dukky_magic_string_private);
+ idpriv = duk_get_pointer(ctx, -1);
+ duk_pop(ctx);
+
+ /* Copying from the input ImageData object */
+ copyop.src.ptr = idpriv->data;
+ copyop.src.stride = idpriv->width * 4;
+ copyop.src.width = idpriv->width;
+ copyop.src.height = idpriv->height;
+
+ /* Copying to ourselves */
+ copyop.dst.ptr = guit->bitmap->get_buffer(priv->bitmap);
+ copyop.dst.stride = priv->stride;
+ copyop.dst.width = priv->width;
+ copyop.dst.height = priv->height;
+
+ /* X Y target coordinates */
+ copyop.dst_x = duk_to_int(ctx, 1);
+ copyop.dst_y = duk_to_int(ctx, 2);
+
+ if (duk_get_top(ctx) < 7) {
+ /* Clipping data not provided */
+ copyop.x1 = 0;
+ copyop.y1 = 0;
+ copyop.x2 = idpriv->width;
+ copyop.y2 = idpriv->height;
+ } else {
+ copyop.x1 = duk_to_int(ctx, 3);
+ copyop.y1 = duk_to_int(ctx, 4);
+ copyop.x2 = copyop.x1 + duk_to_int(ctx, 5);
+ copyop.y2 = copyop.y1 + duk_to_int(ctx, 6);
+ }
+
+ if (canvas2d__copy_bitmap_to_bitmap(&copyop)) {
+ guit->bitmap->modified(priv->bitmap);
+ redraw_node((dom_node *)(priv->canvas));
+ }
+
+ return 0;
+%}