added almost all of the canvas functionality
authorBrendan Hansen <brendan.f.hansen@gmail.com>
Tue, 7 Sep 2021 14:11:43 +0000 (09:11 -0500)
committerBrendan Hansen <brendan.f.hansen@gmail.com>
Tue, 7 Sep 2021 14:11:43 +0000 (09:11 -0500)
canvas.onyx
static/src/canvas.js
static/src/worker.js

index 07e830ec8f38166b09d664a47cb66024be32c427..dbde2690f76aa74e40823e02c60c49db5718f800 100644 (file)
@@ -1,13 +1,52 @@
 package canvas
 
-init           :: () -> void #foreign "canvas" "init" ---
-clear          :: () -> void #foreign "canvas" "clear" ---
-set_fill_style :: (style: str) -> void #foreign "canvas" "set_fill_style" ---
-fill_rect      :: (x: i32, y: i32, w: i32, h: i32) -> void #foreign "canvas" "fill_rect" ---
-
-get_size_internal :: (out_width: ^i32, out_height: ^i32) -> void #foreign "canvas" "get_size" ---
-get_size :: () -> (i32, i32) {
-    sx, sy: i32;
+FillRule  :: enum { NonZero :: 1; EvenOdd :: 2; }
+TextAlign :: enum { Left :: 1; Right; Center; Start; End; }
+LineJoin  :: enum { Bevel :: 1; Round; Miter; }
+
+init  :: () -> void #foreign "canvas" "init" ---
+clear :: () -> void #foreign "canvas" "clear" ---
+sync  :: () -> void #foreign "canvas" "sync" ---
+
+get_size_internal :: (out_width: ^f32, out_height: ^f32) -> void #foreign "canvas" "get_size" ---
+get_size :: () -> (f32, f32) {
+    sx, sy: f32;
     get_size_internal(^sx, ^sy);
     return sx, sy;
 }
+
+// Styling settings
+fill_style              :: (style: str) -> void #foreign "canvas" "fill_style" ---
+font                    :: (fontname: str) -> void #foreign "canvas" "font" ---
+image_smoothing_enabled :: (enabled: bool) -> void #foreign "canvas" "image_smoothing_enabled" ---
+line_join               :: (join: LineJoin) -> void #foreign "canvas" "line_join" ---
+line_width              :: (width: f32) -> void #foreign "canvas" "line_width" ---
+stroke_style            :: (style: str) -> void #foreign "canvas" "stroke_style" ---
+text_align              :: (align: TextAlign) -> void #foreign "canvas" "text_align" ---
+
+// Drawing
+arc                :: (x: f32,  y:  f32,  r: f32, start_angle: f32, end_angle: f32, counter_clockwise := false) -> void #foreign "canvas" "arc" ---
+arc_to             :: (x1: f32, y1: f32, x2: f32, y2: f32, radius: f32) -> void #foreign "canvas" "arc_to" ---
+begin_path         :: () -> void #foreign "canvas" "begin_path" ---
+bezier_curve_to    :: (cp1x: f32, cp1y: f32, cp2x: f32, cp2y: f32, x: f32, y: f32) -> void #foreign "canvas" "bezier_curve_to" ---
+clear_rect         :: (x: f32, y: f32, w: f32, h: f32) -> void #foreign "canvas" "clear_rect" ---
+clip               :: (clip_rule := FillRule.NonZero) -> void #foreign "canvas" "clip" ---
+close_path         :: () -> void #foreign "canvas" "close_path" ---
+ellipse            :: (x: f32, y: f32, rx: f32, ry: f32, rotation: f32, start_angle: f32, end_angle: f32, counter_clockwise := false) -> void #foreign "canvas" "ellipse" ---
+fill               :: (fill_rule := FillRule.NonZero) -> void #foreign "canvas" "fill" ---
+fill_rect          :: (x: f32, y: f32, w: f32, h: f32) -> void #foreign "canvas" "fill_rect" ---
+fill_text          :: (text: str, x: f32, y: f32, max_width := -1.0f) -> void #foreign "canvas" "fill_text" ---
+line_to            :: (x: f32, y: f32) -> void #foreign "canvas" "line_to" ---
+move_to            :: (x: f32, y: f32) -> void #foreign "canvas" "move_to" ---
+quadratic_curve_to :: (cpx: f32, cpy: f32, x: f32, y: f32) -> void #foreign "canvas" "quadratic_curve_to" ---
+rect               :: (x: f32, y: f32, w: f32, h: f32) -> void #foreign "canvas" "rect" ---
+restore            :: () -> void #foreign "canvas" "restore" ---
+rotate             :: (angle: f32) -> void #foreign "canvas" "rotate" ---
+save               :: () -> void #foreign "canvas" "save" ---
+scale              :: (x: f32, y: f32) -> void #foreign "canvas" "scale" ---
+set_transform      :: (a: f32, b: f32, c: f32, d: f32, e: f32, f: f32) -> void #foreign "canvas" "set_transform" ---
+stroke             :: () -> void #foreign "canvas" "stroke" ---
+stroke_rect        :: (x: f32, y: f32, w: f32, h: f32) -> void #foreign "canvas" "stroke_rect" ---
+stroke_text        :: (text: str, x: f32, y: f32, max_width := -1.0f) -> void #foreign "canvas" "stroke_text" ---
+transform          :: (a: f32, b: f32, c: f32, d: f32, e: f32, f: f32) -> void #foreign "canvas" "transform" ---
+translate          :: (x: f32, y: f32) -> void #foreign "canvas" "translate" ---
index fa9c98fbb366d350a0af627b473fda39f69f1e83..a880bfb3535032c994c1135b439b4fa646b7187a 100644 (file)
@@ -1,7 +1,7 @@
 let canvas_element = "";
 let ctx = null;
 
-function update_canvas_size() {
+function update_canvas_data() {
     let canvas_data = new Int32Array(canvas_shared_buffer);
     let box = canvas_element.getBoundingClientRect();
 
@@ -19,28 +19,50 @@ function canvas_handler(instr) {
         case 'init': {
             canvas_element = document.getElementById("render-target");
             ctx = canvas_element.getContext('2d');
-            update_canvas_size();
+            update_canvas_data();
             break;
         }
 
-        case 'set_fill_style': {
-            ctx.fillStyle = instr[1];
+        case 'sync': {
+            window.requestAnimationFrame(() => {
+                let canvas_data = new Int32Array(canvas_shared_buffer);
+                Atomics.store(canvas_data, 2, 1);
+                Atomics.notify(canvas_data, 2);
+            });
             break;
         }
 
-        case 'fill_rect': {
-            ctx.fillRect(instr[1], instr[2], instr[3], instr[4]);
-            let canvas_data = new Int32Array(canvas_shared_buffer);
+        case 'fill_style': ctx.fillStyle = instr[1]; break;
+        case 'font': ctx.font = instr[1]; break;
+        case 'image_smoothing_enabled': ctx.imageSmoothingEnabled = instr[1]; break;
+        case 'line_join': ctx.lineJoin = instr[1]; break;
+        case 'line_width': ctx.lineWidth = instr[1]; break;
+        case 'stroke_style': ctx.strokeStyle = instr[1]; break;
+        case 'text_align': ctx.textAlign = instr[1]; break;
 
-            // TODO: Do I need to synchonize the worker thread and the master thread?
-            Atomics.store(canvas_data, 2, 1);
-            Atomics.notify(canvas_data, 2);
-            break;
-        }
-
-        case 'clear': {
-            ctx.clearRect(0, 0, canvas_element.width, canvas_element.height);
-            break;
-        }
+        case 'arc': ctx.arc(instr[1], instr[2], instr[3], instr[4], instr[5], instr[6]); break;
+        case 'arc_to': ctx.arcTo(instr[1], instr[2], instr[3], instr[4], instr[5]); break;
+        case 'begin_path': ctx.beginPath(); break;
+        case 'clear': ctx.clearRect(0, 0, canvas_element.width, canvas_element.height); break;
+        case 'clear_rect': ctx.clearRect(instr[1], instr[2], instr[3], instr[4]); break;
+        case 'clip': ctx.clip(instr[1]); break;
+        case 'close_path': ctx.closePath(); break;
+        case 'ellipse': ctx.ellipse(instr[1], instr[2], instr[3], instr[4], instr[5], instr[6], instr[7], instr[8]); break;
+        case 'fill': ctx.fill(instr[1]); break;
+        case 'fill_rect': ctx.fillRect(instr[1], instr[2], instr[3], instr[4]); break;
+        case 'fill_text': ctx.fillText(instr[1], instr[2], instr[3], instr[4]); break;
+        case 'line_to': ctx.lineTo(instr[1], instr[2]); break;
+        case 'move_to': ctx.moveTo(instr[1], instr[2]); break;
+        case 'quadratic_curve_to': ctx.quadraticCurveTo(instr[1], instr[2], instr[3], instr[4]); break;
+        case 'rect': ctx.rect(instr[1], instr[2], instr[3], instr[4]); break;
+        case 'rotate': ctx.rotate(instr[1]); break;
+        case 'save': ctx.save(); break;
+        case 'scale': ctx.scale(instr[1], instr[2]); break;
+        case 'set_transform': ctx.setTransform(instr[1], instr[2], instr[3], instr[4], instr[5], instr[6]); break;
+        case 'stroke': ctx.stroke(); break;
+        case 'stroke_rect': ctx.strokeRect(instr[1], instr[2], instr[3], instr[4]); break;
+        case 'stroke_text': ctx.strokeText(instr[1], instr[2], instr[3], instr[4]); break;
+        case 'transform': ctx.transform(instr[1], instr[2], instr[3], instr[4], instr[5], instr[6]); break;
+        case 'translate': ctx.translate(instr[1], instr[2]); break;
     }
 }
index 70bd4e343af2013855e46ddbd337a7533ce4f795..46c2a9db6d9e8e1a3f6341375dccd6be868c40a0 100644 (file)
@@ -2,11 +2,15 @@ let input_shared_buffer = null;
 let canvas_data_buffer = null;
 let wasm_memory = null;
 
+function S(strptr, strlen) {
+    let strdata = new Uint8Array(wasm_memory.buffer, strptr, strlen);
+    return new TextDecoder().decode(strdata);
+}
+
 let import_obj = {
     host: {
         print_str(strptr, strlen) {
-            let strdata = new Uint8Array(wasm_memory.buffer, strptr, strlen);
-            let str = new TextDecoder().decode(strdata);
+            let str = S(strptr, strlen);
             self.postMessage({ type: 'print_str', data: str });
         },
 
@@ -45,37 +49,110 @@ let import_obj = {
         init() {
             self.postMessage({ type: 'canvas', data: [ 'init' ] });
 
+            // Issue a synchronization because the data needs to be correct
+            // before control should be handed back to the running program.
             let canvas_data = new Int32Array(canvas_data_buffer);
             Atomics.wait(canvas_data, 2, 0);
             Atomics.store(canvas_data, 2, 0);
         },
 
-        clear() {
-            self.postMessage({ type: 'canvas', data: [ 'clear' ] });
+        sync() {
+            self.postMessage({ type: 'canvas', data: [ 'sync'] });
+
+            let canvas_data = new Int32Array(canvas_data_buffer);
+            Atomics.wait(canvas_data, 2, 0);
+            Atomics.store(canvas_data, 2, 0);
         },
         
         get_size(outwidth, outheight) {
             let data = new DataView(wasm_memory.buffer);
             let canvas_data = new Int32Array(canvas_data_buffer);
 
-            data.setInt32(outwidth, Atomics.load(canvas_data, 0), true);
-            data.setInt32(outheight, Atomics.load(canvas_data, 1), true);
+            data.setFloat32(outwidth, Atomics.load(canvas_data, 0), true);
+            data.setFloat32(outheight, Atomics.load(canvas_data, 1), true);
         },
 
-        set_fill_style(styleptr, stylelen) {
-            let strdata = new Uint8Array(wasm_memory.buffer, styleptr, stylelen);
-            let str = new TextDecoder().decode(strdata);
+        // Settings
+        fill_style(styleptr, stylelen) { self.postMessage({ type: 'canvas', data: [ 'fill_style', S(styleptr, stylelen) ] }); },
+        font(p, l) { self.postMessage({ type: 'canvas', data: [ 'font', S(p, l) ] }); },
+        image_smoothing_enabled(e) { self.postMessage({ type: 'canvas', data: [ 'image_smoothing_enabled', e != 0 ] }); },
+        line_join(join) {
+            let method = "";
+            switch (join) {
+                case 1: method = "bevel"; break;
+                case 2: method = "round"; break;
+                case 3: method = "miter"; break;
+            }
 
-            self.postMessage({ type: 'canvas', data: [ 'set_fill_style', str ] });
+            if (method != "") {
+                self.postMessage({ type: 'canvas', data: [ 'line_join' , method ] });
+            }
         },
+        line_width(width) { self.postMessage({ type: 'canvas', data: [ 'line_width', width ] }); },
+        stroke_style(s, l) { self.postMessage({ type: 'canvas', data: [ 'stroke_style', S(s, l) ] }); },
+        text_align(align) {
+            let method = "";
+            switch (align) {
+                case 1: method = "left"; break;
+                case 2: method = "right"; break;
+                case 3: method = "center"; break;
+                case 4: method = "start"; break;
+                case 5: method = "end"; break;
+            }
 
-        fill_rect(x, y, w, h) {
-            self.postMessage({ type: 'canvas', data: [ 'fill_rect', x, y, w, h ] });
+            if (method != "") {
+                self.postMessage({ type: 'canvas', data: [ 'text_align' , method ] });
+            }
+        },
 
-            let canvas_data = new Int32Array(canvas_data_buffer);
-            Atomics.wait(canvas_data, 2, 0);
-            Atomics.store(canvas_data, 2, 0);
-        }
+        // Drawing
+        arc(x, y, r, sa, ea, cc) { self.postMessage({ type: 'canvas', data: [ 'arc', x, y, r, sa, ea, cc != 0 ] }); },
+        arc_to(x1, y1, x2, y2, r) { self.postMessage({ type: 'canvas', data: [ 'arc_to', x1, y1, x2, y2, r ] }); },
+        begin_path() { self.postMessage({ type: 'canvas', data: [ 'begin_path' ] }); },
+        clear() { self.postMessage({ type: 'canvas', data: [ 'clear' ] }); },
+        clear_rect(x, y, w, h) { self.postMessage({ type: 'canvas', data: [ 'clear_rect', x, y, w, h ] }); },
+        clip(cliprule) {
+            let method = "";
+            switch (cliprule) {
+                case 1: method = "nonzero"; break;
+                case 2: method = "evenodd"; break;
+            }
+
+            if (method != "") {
+                self.postMessage({ type: 'canvas', data: [ 'clip', method ] });
+            }
+        },
+        close_path() { self.postMessage({ type: 'canvas', data: [ 'close_path' ] }); },
+        ellipse(x, y, rx, ry, r, sa, ea, cc) { self.postMessage({ type: 'canvas', data: [ 'ellipse', x, y, rx, ry, r, sa, ea, cc != 0 ] }); },
+        fill(fillrule) {
+            let method = "";
+            switch (fillrule) {
+                case 1: method = "nonzero"; break;
+                case 2: method = "evenodd"; break;
+            }
+
+            if (method != "") {
+                self.postMessage({ type: 'canvas', data: [ 'fill', method ] });
+            }
+        },
+        fill_rect(x, y, w, h) { self.postMessage({ type: 'canvas', data: [ 'fill_rect', x, y, w, h ] }); },
+        fill_text(textptr, textlen, x, y, max_width) {
+            self.postMessage({ type: 'canvas', data: [ 'fill_text', S(textptr, textlen), x, y, max_width > 0 ? max_width : null ] });
+        },
+        line_to(x, y) { self.postMessage({ type: 'canvas', data: [ 'line_to', x, y ] }); },
+        move_to(x, y) { self.postMessage({ type: 'canvas', data: [ 'move_to', x, y ] }); },
+        quadratic_curve_to(cpx, cpy, x, y) { self.postMessage({ type: 'canvas', data: [ 'quadratic_curve_to', cpx, cpy, x, y ] }); },
+        rect(x, y, w, h) { self.postMessage({ type: 'canvas', data: [ 'rect', x, y, w, h ] }); },
+        restore() { self.postMessage({ type: 'canvas', data: [ 'restore' ] }); },
+        rotate(angle) { self.postMessage({ type: 'canvas', data: [ 'rotate', angle ] }); },
+        save() { self.postMessage({ type: 'canvas', data: [ 'save' ] }); },
+        scale(x, y) { self.postMessage({ type: 'canvas', data: [ 'scale', x, y ] }); },
+        set_transform(a, b, c, d, e, f) { self.postMessage({ type: 'canvas', data: [ 'set_transform', a, b, c, d, e, f ] }); },
+        stroke() { self.postMessage({ type: 'canvas', data: [ 'stroke' ] }); },
+        stroke_rect(x, y, w, h) { self.postMessage({ type: 'canvas', data: [ 'stroke_rect', x, y, w, h ] }); },
+        stroke_text(textptr, textlen, x, y, max_width) { self.postMessage({ type: 'canvas', data: [ 'stroke_text', S(textptr, textlen), x, y, max_width > 0 ? max_width : null ] }); },
+        transform(a, b, c, d, e, f) { self.postMessage({ type: 'canvas', data: [ 'transform', a, b, c, d, e, f ] }); },
+        translate(x, y) { self.postMessage({ type: 'canvas', data: [ 'translate', x, y ] }); },
     }
 };