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" ---
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();
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;
}
}
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 });
},
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 ] }); },
}
};