font: add method for drawing atlas to canvas

This commit is contained in:
Mitchell Hashimoto
2022-12-05 15:01:34 -08:00
parent 84f6f37450
commit 5e9dd02eab
5 changed files with 107 additions and 9 deletions

View File

@ -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);
});

View File

@ -8,6 +8,8 @@
<body>
<p>Open your console, we are just debugging here.</p>
<p>The font rendering canvas should show below. This shows a single glyph.</p>
<div id="face-canvas" style="display: inline-block; border: 1px solid red;"></div>
<div><div id="face-canvas" style="display: inline-block; border: 1px solid red;"></div></div>
<p>The current font atlas is rendered below.</p>
<div><div id="atlas-canvas" style="display: inline-block; border: 1px solid green;"></div></div>
</body>
</html>

View File

@ -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" {

View File

@ -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

2
vendor/zig-js vendored

@ -1 +1 @@
Subproject commit 00eb5166ea3a070ac985b2b4409f2e51877865f4
Subproject commit 52eed4daddcf9fa974ad4457691de26ef2351c56