mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-08-02 14:57:31 +03:00
Start scaffolding web_canvas backend
This commit is contained in:
@ -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>
|
||||
|
@ -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" {
|
||||
|
@ -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 => {},
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
};
|
||||
|
||||
|
22
src/font/face/web_canvas.zig
Normal file
22
src/font/face/web_canvas.zig
Normal 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,
|
||||
};
|
@ -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;
|
||||
}
|
||||
|
@ -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");
|
||||
|
Reference in New Issue
Block a user