diff --git a/example/app.ts b/example/app.ts index 65ba6450f..9113faa5d 100644 --- a/example/app.ts +++ b/example/app.ts @@ -30,6 +30,7 @@ fetch(url.href).then(response => face_debug_canvas, atlas_new, atlas_free, + atlas_debug_canvas, } = results.instance.exports; // Give us access to the zjs value for debugging. globalThis.zjs = zjs; @@ -47,14 +48,21 @@ fetch(url.href).then(response => new Uint8Array(memory.buffer, font_ptr).set(font); // Call whatever example you want: - const face = face_new(font_ptr, font.byteLength, 144); + const face = face_new(font_ptr, font.byteLength, 72); free(font_ptr); // Render a glyph - face_render_glyph(face, atlas, "A".codePointAt(0)); + for (let i = 33; i <= 126; i++) { + face_render_glyph(face, atlas, i); + } + // face_render_glyph(face, atlas, "A".codePointAt(0)); // Debug our canvas face_debug_canvas(face); + // Debug our atlas canvas + const id = atlas_debug_canvas(atlas); + document.getElementById("atlas-canvas").append(zjs.deleteValue(id)); + //face_free(face); }); diff --git a/example/index.html b/example/index.html index 12b4fb1dd..a9bd81547 100644 --- a/example/index.html +++ b/example/index.html @@ -8,6 +8,8 @@

Open your console, we are just debugging here.

The font rendering canvas should show below. This shows a single glyph.

-
+
+

The current font atlas is rendered below.

+
diff --git a/src/font/Atlas.zig b/src/font/Atlas.zig index a48edb966..bcfc6f9c5 100644 --- a/src/font/Atlas.zig +++ b/src/font/Atlas.zig @@ -21,6 +21,8 @@ const Allocator = std.mem.Allocator; const testing = std.testing; const fastmem = @import("../fastmem.zig"); +const log = std.log.scoped(.atlas); + /// Data is the raw texture data. data: []u8, @@ -309,6 +311,7 @@ pub const Wasm = struct { // just replace this with the allocator you want to use. const wasm = @import("../os/wasm.zig"); const alloc = wasm.alloc; + const js = @import("zig-js"); export fn atlas_new(size: u32, format: u8) ?*Atlas { const atlas = init( @@ -321,6 +324,13 @@ pub const Wasm = struct { return result; } + export fn atlas_free(ptr: ?*Atlas) void { + if (ptr) |v| { + v.deinit(alloc); + alloc.destroy(v); + } + } + /// The return value for this should be freed by the caller with "free". export fn atlas_reserve(self: *Atlas, width: u32, height: u32) ?*Region { return atlas_reserve_(self, width, height) catch return null; @@ -348,11 +358,89 @@ pub const Wasm = struct { self.clear(); } - export fn atlas_free(ptr: ?*Atlas) void { - if (ptr) |v| { - v.deinit(alloc); - alloc.destroy(v); + /// This creates a Canvas element identified by the id returned that + /// the caller can draw into the DOM to visualize the atlas. The returned + /// ID must be freed from the JS runtime by calling "zigjs.deleteValue". + export fn atlas_debug_canvas(self: *Atlas) u32 { + return atlas_debug_canvas_(self) catch |err| { + log.warn("error dumping atlas canvas err={}", .{err}); + return 0; + }; + } + + fn atlas_debug_canvas_(self: *Atlas) !u32 { + // Create our canvas + const doc = try js.global.get(js.Object, "document"); + defer doc.deinit(); + const canvas = try doc.call(js.Object, "createElement", .{js.string("canvas")}); + errdefer canvas.deinit(); + + // Setup our canvas size + { + try canvas.set("width", self.size); + try canvas.set("height", self.size); + + const width_str = try std.fmt.allocPrint(alloc, "{d}px", .{self.size}); + defer alloc.free(width_str); + + const style = try canvas.get(js.Object, "style"); + defer style.deinit(); + try style.set("width", js.string(width_str)); + try style.set("height", js.string(width_str)); } + + // This will return the same context on subsequent calls so it + // is important to reset it. + const ctx = try canvas.call(js.Object, "getContext", .{js.string("2d")}); + errdefer ctx.deinit(); + + // We need to draw pixels so this is format dependent. + var buf: []u8 = switch (self.format) { + // RGBA is the native ImageData format + .rgba => self.data, + + .greyscale => buf: { + // Convert from A8 to RGBA so every 4th byte is set to a value. + var buf: []u8 = try alloc.alloc(u8, self.data.len * 4); + errdefer alloc.free(buf); + std.mem.set(u8, buf, 0); + for (self.data) |value, i| { + buf[(i * 4) + 3] = value; + } + break :buf buf; + }, + + else => return error.UnsupportedAtlasFormat, + }; + defer if (buf.ptr != self.data.ptr) alloc.free(buf); + + // Create an ImageData from our buffer and then write it to the canvas + const image_data: js.Object = data: { + // Get our runtime memory + const mem = try js.runtime.get(js.Object, "memory"); + defer mem.deinit(); + const mem_buf = try mem.get(js.Object, "buffer"); + defer mem_buf.deinit(); + + // Create an array that points to our buffer + const Uint8ClampedArray = try js.global.get(js.Object, "Uint8ClampedArray"); + defer Uint8ClampedArray.deinit(); + const arr = try Uint8ClampedArray.new(.{ mem_buf, buf.ptr, buf.len }); + + // Create the image data from our array + const ImageData = try js.global.get(js.Object, "ImageData"); + defer ImageData.deinit(); + const data = try ImageData.new(.{ arr, self.size, self.size }); + errdefer data.deinit(); + + break :data data; + }; + + // Draw it + try ctx.call(void, "putImageData", .{ image_data, 0, 0 }); + + const id = @bitCast(js.Ref, @enumToInt(canvas.value)).id; + return id; } test "happy path" { diff --git a/src/font/face/web_canvas.zig b/src/font/face/web_canvas.zig index 304ebb79d..25dca9a19 100644 --- a/src/font/face/web_canvas.zig +++ b/src/font/face/web_canvas.zig @@ -215,7 +215,7 @@ pub const Face = struct { defer alloc.free(bitmap_a8); var i: usize = 0; while (i < bitmap_a8.len) : (i += 1) { - bitmap_a8[i] = bitmap[i * 4]; + bitmap_a8[i] = bitmap[(i * 4) + 3]; } // Put it in our atlas diff --git a/vendor/zig-js b/vendor/zig-js index 00eb5166e..52eed4dad 160000 --- a/vendor/zig-js +++ b/vendor/zig-js @@ -1 +1 @@ -Subproject commit 00eb5166ea3a070ac985b2b4409f2e51877865f4 +Subproject commit 52eed4daddcf9fa974ad4457691de26ef2351c56