From 940828ed974b1497dc5cdb411ef175f4c6dba74b Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 3 Dec 2022 22:11:01 -0800 Subject: [PATCH] font: web canvas creates a canvas element --- build.zig | 1 + src/font/face.zig | 6 +-- src/font/face/web_canvas.zig | 90 +++++++++++++++++++++++++++++++++++- src/font/main.zig | 2 + 4 files changed, 95 insertions(+), 4 deletions(-) diff --git a/build.zig b/build.zig index 207acfa06..25df881bf 100644 --- a/build.zig +++ b/build.zig @@ -122,6 +122,7 @@ pub fn build(b: *std.build.Builder) !void { wasm.setOutputDir("zig-out"); // Wasm-specific deps + wasm.addPackage(js.pkg); wasm.addPackage(tracylib.pkg); wasm.addPackage(utf8proc.pkg); _ = try utf8proc.link(b, wasm); diff --git a/src/font/face.zig b/src/font/face.zig index 3986d7aa9..22fec7eeb 100644 --- a/src/font/face.zig +++ b/src/font/face.zig @@ -1,8 +1,8 @@ const builtin = @import("builtin"); const options = @import("main.zig").options; -const freetype = @import("face/freetype.zig"); -const coretext = @import("face/coretext.zig"); -const web_canvas = @import("face/web_canvas.zig"); +pub const freetype = @import("face/freetype.zig"); +pub const coretext = @import("face/coretext.zig"); +pub const web_canvas = @import("face/web_canvas.zig"); /// Face implementation for the compile options. pub const Face = switch (options.backend) { diff --git a/src/font/face/web_canvas.zig b/src/font/face/web_canvas.zig index a2969ef68..9710c810e 100644 --- a/src/font/face/web_canvas.zig +++ b/src/font/face/web_canvas.zig @@ -1,11 +1,99 @@ const std = @import("std"); const builtin = @import("builtin"); const assert = std.debug.assert; +const testing = std.testing; const Allocator = std.mem.Allocator; +const js = @import("zig-js"); const font = @import("../main.zig"); const log = std.log.scoped(.font_face); pub const Face = struct { - // TODO + /// The web canvas face makes use of an allocator when interacting + /// with the JS environment. + alloc: Allocator, + + /// The CSS "font" attribute, excluding size. + font_str: []const u8, + + /// The size we currently have set. + size: font.face.DesiredSize, + + /// The presentation for this font. This is a heuristic since fonts don't have + /// a way to declare this. We just assume a font with color is an emoji font. + presentation: font.Presentation, + + /// Metrics for this font face. These are useful for renderers. + metrics: font.face.Metrics, + + /// The canvas element that we will reuse to render glyphs + canvas: js.Object, + + /// Initialize a web canvas font with a "raw" value. The "raw" value can + /// be any valid value for a CSS "font" property EXCLUDING the size. The + /// size is always added via the `size` parameter. + /// + /// The raw value is copied so the caller can free it after it is gone. + pub fn initNamed( + alloc: Allocator, + raw: []const u8, + size: font.face.DesiredSize, + ) !Face { + // Copy our font string because we're going to have to reuse it. + const font_str = try alloc.dupe(u8, raw); + errdefer alloc.free(font_str); + + // Create our canvas that we're going to continue to reuse. + 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(); + + return Face{ + .alloc = alloc, + .font_str = font_str, + .size = size, + + .canvas = canvas, + + // TODO: real metrics + .metrics = undefined, + + // TODO: figure out how we're going to do emoji with web canvas + .presentation = .text, + }; + } + + pub fn deinit(self: *Face) void { + self.alloc.free(self.font_str); + self.canvas.deinit(); + self.* = undefined; + } +}; + +/// The wasm-compatible API. +pub const Wasm = struct { + const wasm = @import("../../wasm.zig"); + const alloc = wasm.alloc; + + export fn face_new(ptr: [*]const u8, len: usize, pts: u16) ?*Face { + return face_new_(ptr, len, pts) catch null; + } + + fn face_new_(ptr: [*]const u8, len: usize, pts: u16) !*Face { + var face = try Face.initNamed(alloc, ptr[0..len], .{ .points = pts }); + errdefer face.deinit(); + + var result = try alloc.create(Face); + errdefer alloc.destroy(result); + result.* = face; + return result; + } + + export fn face_free(ptr: ?*Face) void { + if (ptr) |v| { + v.deinit(); + alloc.destroy(v); + } + } }; diff --git a/src/font/main.zig b/src/font/main.zig index 4226f732c..10c63d49f 100644 --- a/src/font/main.zig +++ b/src/font/main.zig @@ -17,8 +17,10 @@ pub const Sprite = sprite.Sprite; pub const Descriptor = discovery.Descriptor; pub const Discover = discovery.Discover; +/// If we're targeting wasm then we export some wasm APIs. pub usingnamespace if (builtin.target.isWasm()) struct { pub usingnamespace Atlas.Wasm; + pub usingnamespace face.web_canvas.Wasm; } else struct {}; /// Build options