Start scaffolding web_canvas backend

This commit is contained in:
Mitchell Hashimoto
2022-11-30 15:05:09 -08:00
parent 6b101c2293
commit b858aea124
7 changed files with 117 additions and 35 deletions

View File

@ -13,21 +13,24 @@
} }
}; };
fetch('ghostty-term.wasm').then(response => fetch('ghostty-wasm.wasm').then(response =>
response.arrayBuffer() response.arrayBuffer()
).then(bytes => ).then(bytes =>
WebAssembly.instantiate(bytes, importObject) WebAssembly.instantiate(bytes, importObject)
).then(results => { ).then(results => {
const { const {
terminal_new, atlas_new,
terminal_free, atlas_free,
terminal_print, atlas_reserve,
free,
memory,
} = results.instance.exports; } = results.instance.exports;
const term = terminal_new(80, 80); const atlas = atlas_new(512, 0);
console.log(term); const reg = atlas_reserve(atlas, 10, 10);
terminal_free(term); console.log(reg);
terminal_print('a'); free(reg);
atlas_free(atlas);
}); });
</script> </script>
</body> </body>

View File

@ -49,10 +49,10 @@ modified: bool = false,
/// updated in-place. /// updated in-place.
resized: bool = false, resized: bool = false,
pub const Format = enum { pub const Format = enum(u8) {
greyscale, greyscale = 0,
rgb, rgb = 1,
rgba, rgba = 2,
pub fn depth(self: Format) u8 { pub fn depth(self: Format) u8 {
return switch (self) { return switch (self) {
@ -310,33 +310,33 @@ pub const Wasm = struct {
const wasm = @import("../wasm.zig"); const wasm = @import("../wasm.zig");
const alloc = wasm.alloc; const alloc = wasm.alloc;
const FormatInt = @typeInfo(Format).Enum.tag_type;
export const ATLAS_FORMAT_GREYSCALE: u8 = @enumToInt(Format.greyscale);
export const ATLAS_FORMAT_RGB: u8 = @enumToInt(Format.rgb);
export const ATLAS_FORMAT_RGBA: u8 = @enumToInt(Format.rgba);
export fn atlas_new(size: u32, format: u8) ?*Atlas { export fn atlas_new(size: u32, format: u8) ?*Atlas {
const atlas = init( const atlas = init(
alloc, alloc,
size, size,
@intToEnum(Format, @intCast(FormatInt, format)), @intToEnum(Format, format),
) catch return null; ) catch return null;
const result = alloc.create(Atlas) catch return null; const result = alloc.create(Atlas) catch return null;
result.* = atlas; result.* = atlas;
return result; return result;
} }
export fn atlas_reserve(self: *Atlas, width: u32, height: u32) Region { /// The return value for this should be freed by the caller with "free".
return self.reserve(alloc, width, height) catch .{ export fn atlas_reserve(self: *Atlas, width: u32, height: u32) ?*Region {
.x = 0, return atlas_reserve_(self, width, height) catch return null;
.y = 0,
.width = 0,
.height = 0,
};
} }
export fn atlas_set(self: *Atlas, reg: Region, data: [*]const u8, len: usize) void { fn atlas_reserve_(self: *Atlas, width: u32, height: u32) !*Region {
self.set(reg, data[0..len]); const reg = try self.reserve(alloc, width, height);
const result = try alloc.create(Region);
errdefer alloc.destroy(result);
_ = try wasm.toHostOwned(result);
result.* = reg;
return result;
}
export fn atlas_set(self: *Atlas, reg: *Region, data: [*]const u8, len: usize) void {
self.set(reg.*, data[0..len]);
} }
export fn atlas_grow(self: *Atlas, size_new: u32) bool { export fn atlas_grow(self: *Atlas, size_new: u32) bool {
@ -354,6 +354,22 @@ pub const Wasm = struct {
alloc.destroy(v); alloc.destroy(v);
} }
} }
test "happy path" {
var atlas = atlas_new(512, @enumToInt(Format.greyscale)).?;
defer atlas_free(atlas);
const reg = atlas_reserve(atlas, 2, 2).?;
defer alloc.destroy(reg);
try testing.expect(wasm.isHostOwned(reg));
defer wasm.toModuleOwned(reg);
try testing.expect(reg.width > 0);
const data = &[_]u8{ 1, 2, 3, 4 };
try testing.expect(!atlas.modified);
atlas_set(atlas, reg, data, data.len);
try testing.expect(atlas.modified);
}
}; };
test "exact fit" { test "exact fit" {

View File

@ -68,6 +68,8 @@ pub fn deinit(self: *DeferredFace) void {
.fontconfig_freetype => if (self.fc) |*fc| fc.deinit(), .fontconfig_freetype => if (self.fc) |*fc| fc.deinit(),
.coretext => if (self.ct) |*ct| ct.deinit(), .coretext => if (self.ct) |*ct| ct.deinit(),
.freetype => {}, .freetype => {},
// TODO
.web_canvas => unreachable,
} }
self.* = undefined; self.* = undefined;
} }
@ -90,6 +92,9 @@ pub fn name(self: DeferredFace) ![:0]const u8 {
}, },
.freetype => {}, .freetype => {},
// TODO
.web_canvas => unreachable,
} }
return "TODO: built-in font names"; return "TODO: built-in font names";
@ -122,6 +127,9 @@ pub fn load(
return; return;
}, },
// TODO
.web_canvas => unreachable,
// Unreachable because we must be already loaded or have the // Unreachable because we must be already loaded or have the
// proper configuration for one of the other deferred mechanisms. // proper configuration for one of the other deferred mechanisms.
.freetype => unreachable, .freetype => unreachable,
@ -245,6 +253,9 @@ pub fn hasCodepoint(self: DeferredFace, cp: u32, p: ?Presentation) bool {
} }
}, },
// TODO
.web_canvas => unreachable,
.freetype => {}, .freetype => {},
} }

View File

@ -2,12 +2,14 @@ const builtin = @import("builtin");
const options = @import("main.zig").options; const options = @import("main.zig").options;
const freetype = @import("face/freetype.zig"); const freetype = @import("face/freetype.zig");
const coretext = @import("face/coretext.zig"); const coretext = @import("face/coretext.zig");
const web_canvas = @import("face/web_canvas.zig");
/// Face implementation for the compile options. /// Face implementation for the compile options.
pub const Face = switch (options.backend) { pub const Face = switch (options.backend) {
.fontconfig_freetype => freetype.Face, .fontconfig_freetype => freetype.Face,
.coretext => freetype.Face, .coretext => freetype.Face,
//.coretext => coretext.Face, //.coretext => coretext.Face,
.web_canvas => web_canvas.Face,
else => unreachable, else => unreachable,
}; };

View File

@ -0,0 +1,22 @@
const std = @import("std");
const builtin = @import("builtin");
const freetype = @import("freetype");
const assert = std.debug.assert;
const testing = std.testing;
const Allocator = std.mem.Allocator;
const font = @import("../main.zig");
const Glyph = font.Glyph;
const Library = font.Library;
const Presentation = font.Presentation;
const convert = @import("freetype_convert.zig");
const log = std.log.scoped(.font_face);
pub const Face = struct {
/// 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: Presentation,
/// Metrics for this font face. These are useful for renderers.
metrics: font.face.Metrics,
};

View File

@ -25,12 +25,7 @@ pub usingnamespace if (builtin.target.isWasm()) struct {
pub const options: struct { pub const options: struct {
backend: Backend, backend: Backend,
} = .{ } = .{
.backend = if (build_options.coretext) .backend = Backend.default(),
.coretext
else if (build_options.fontconfig)
.fontconfig_freetype
else
.freetype,
}; };
pub const Backend = enum { pub const Backend = enum {
@ -43,14 +38,37 @@ pub const Backend = enum {
/// CoreText for both font discovery and rendering (macOS). /// CoreText for both font discovery and rendering (macOS).
coretext, coretext,
/// Use the browser font system and the Canvas API (wasm). This limits
/// the available fonts to browser fonts (anything Canvas natively
/// supports).
web_canvas,
/// Returns the default backend for a build environment. This is
/// meant to be called at comptime.
pub fn default() Backend {
// Wasm only supports browser at the moment.
if (builtin.target.isWasm()) return .web_canvas;
return if (build_options.coretext)
.coretext
else if (build_options.fontconfig)
.fontconfig_freetype
else
.freetype;
}
/// Helper that just returns true if we should be using freetype. This /// Helper that just returns true if we should be using freetype. This
/// is used for tests. /// is used for tests.
pub fn freetype(self: Backend) bool { pub fn freetype(self: Backend) bool {
return switch (self) { return switch (self) {
.freetype, .fontconfig_freetype => true, .freetype, .fontconfig_freetype => true,
.coretext => false, .coretext, .web_canvas => false,
}; };
} }
test "default can run at comptime" {
_ = comptime default();
}
}; };
/// The styles that a family can take. /// The styles that a family can take.
@ -71,5 +89,11 @@ pub const Presentation = enum(u1) {
pub const sprite_index = Group.FontIndex.initSpecial(.sprite); pub const sprite_index = Group.FontIndex.initSpecial(.sprite);
test { test {
@import("std").testing.refAllDecls(@This()); // For non-wasm we want to test everything we can
if (!comptime builtin.target.isWasm()) {
@import("std").testing.refAllDecls(@This());
return;
}
_ = Atlas;
} }

View File

@ -1,4 +1,8 @@
// This is the main file for the WASM module. The WASM module has to // This is the main file for the WASM module. The WASM module has to
// export a C ABI compatible API. // export a C ABI compatible API.
pub usingnamespace @import("wasm.zig");
pub usingnamespace @import("font/main.zig"); pub usingnamespace @import("font/main.zig");
// TODO: temporary while we dev this
pub usingnamespace @import("font/face/web_canvas.zig");