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()
).then(bytes =>
WebAssembly.instantiate(bytes, importObject)
).then(results => {
const {
terminal_new,
terminal_free,
terminal_print,
atlas_new,
atlas_free,
atlas_reserve,
free,
memory,
} = results.instance.exports;
const term = terminal_new(80, 80);
console.log(term);
terminal_free(term);
terminal_print('a');
const atlas = atlas_new(512, 0);
const reg = atlas_reserve(atlas, 10, 10);
console.log(reg);
free(reg);
atlas_free(atlas);
});
</script>
</body>

View File

@ -49,10 +49,10 @@ modified: bool = false,
/// updated in-place.
resized: bool = false,
pub const Format = enum {
greyscale,
rgb,
rgba,
pub const Format = enum(u8) {
greyscale = 0,
rgb = 1,
rgba = 2,
pub fn depth(self: Format) u8 {
return switch (self) {
@ -310,33 +310,33 @@ pub const Wasm = struct {
const wasm = @import("../wasm.zig");
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 {
const atlas = init(
alloc,
size,
@intToEnum(Format, @intCast(FormatInt, format)),
@intToEnum(Format, format),
) catch return null;
const result = alloc.create(Atlas) catch return null;
result.* = atlas;
return result;
}
export fn atlas_reserve(self: *Atlas, width: u32, height: u32) Region {
return self.reserve(alloc, width, height) catch .{
.x = 0,
.y = 0,
.width = 0,
.height = 0,
};
/// 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;
}
export fn atlas_set(self: *Atlas, reg: Region, data: [*]const u8, len: usize) void {
self.set(reg, data[0..len]);
fn atlas_reserve_(self: *Atlas, width: u32, height: u32) !*Region {
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 {
@ -354,6 +354,22 @@ pub const Wasm = struct {
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" {

View File

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

View File

@ -2,12 +2,14 @@ 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");
/// Face implementation for the compile options.
pub const Face = switch (options.backend) {
.fontconfig_freetype => freetype.Face,
.coretext => freetype.Face,
//.coretext => coretext.Face,
.web_canvas => web_canvas.Face,
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 {
backend: Backend,
} = .{
.backend = if (build_options.coretext)
.coretext
else if (build_options.fontconfig)
.fontconfig_freetype
else
.freetype,
.backend = Backend.default(),
};
pub const Backend = enum {
@ -43,14 +38,37 @@ pub const Backend = enum {
/// CoreText for both font discovery and rendering (macOS).
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
/// is used for tests.
pub fn freetype(self: Backend) bool {
return switch (self) {
.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.
@ -71,5 +89,11 @@ pub const Presentation = enum(u1) {
pub const sprite_index = Group.FontIndex.initSpecial(.sprite);
test {
// 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
// export a C ABI compatible API.
pub usingnamespace @import("wasm.zig");
pub usingnamespace @import("font/main.zig");
// TODO: temporary while we dev this
pub usingnamespace @import("font/face/web_canvas.zig");