mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-16 16:56:09 +03:00
Merge pull request #62 from mitchellh/web-emoji
wasm: web canvas font rasterizer can render emoji (no ZWJ yet)
This commit is contained in:
@ -63,17 +63,18 @@ fetch(url.href).then(response =>
|
||||
new Uint8Array(memory.buffer, font_ptr).set(font);
|
||||
|
||||
// Initialize our deferred face
|
||||
const df = deferred_face_new(font_ptr, font.byteLength);
|
||||
// const df = deferred_face_new(font_ptr, font.byteLength, 0 /* text */);
|
||||
//deferred_face_load(df, 72 /* size */);
|
||||
//const face = deferred_face_face(df);
|
||||
|
||||
// Initialize our font face
|
||||
//const face = face_new(font_ptr, font.byteLength, 72 /* size in px */);
|
||||
free(font_ptr);
|
||||
//free(font_ptr);
|
||||
|
||||
// Create our group
|
||||
const group = group_new(72 /* size */);
|
||||
group_add_face(group, 0, df);
|
||||
group_add_face(group, 0 /* regular */, deferred_face_new(font_ptr, font.byteLength, 0 /* text */));
|
||||
group_add_face(group, 0 /* regular */, deferred_face_new(font_ptr, font.byteLength, 1 /* emoji */));
|
||||
|
||||
// Create our group cache
|
||||
const group_cache = group_cache_new(group);
|
||||
@ -84,6 +85,14 @@ fetch(url.href).then(response =>
|
||||
group_cache_render_glyph(group_cache, font_idx, i, 0);
|
||||
//face_render_glyph(face, atlas, i);
|
||||
}
|
||||
|
||||
const emoji = ["🐏","🌞","🌚","🍱","💿","🐈","📃","📀","🕡","🙃"];
|
||||
for (let i = 0; i < emoji.length; i++) {
|
||||
const cp = emoji[i].codePointAt(0);
|
||||
const font_idx = group_cache_index_for_codepoint(group_cache, cp, 0, 1 /* emoji */);
|
||||
group_cache_render_glyph(group_cache, font_idx, cp, 0);
|
||||
}
|
||||
|
||||
//face_render_glyph(face, atlas, "橋".codePointAt(0));
|
||||
//face_render_glyph(face, atlas, "p".codePointAt(0));
|
||||
|
||||
|
@ -71,6 +71,9 @@ pub const WebCanvas = struct {
|
||||
/// The string to use for the "font" attribute for the canvas
|
||||
font_str: [:0]const u8,
|
||||
|
||||
/// The presentation for this font.
|
||||
presentation: Presentation,
|
||||
|
||||
pub fn deinit(self: *WebCanvas) void {
|
||||
self.alloc.free(self.font_str);
|
||||
self.* = undefined;
|
||||
@ -227,7 +230,7 @@ fn loadWebCanvas(
|
||||
) !void {
|
||||
assert(self.face == null);
|
||||
const wc = self.wc.?;
|
||||
self.face = try Face.initNamed(wc.alloc, wc.font_str, size);
|
||||
self.face = try Face.initNamed(wc.alloc, wc.font_str, size, wc.presentation);
|
||||
}
|
||||
|
||||
/// Returns true if this face can satisfy the given codepoint and
|
||||
@ -283,7 +286,13 @@ pub fn hasCodepoint(self: DeferredFace, cp: u32, p: ?Presentation) bool {
|
||||
|
||||
// Canvas always has the codepoint because we have no way of
|
||||
// really checking and we let the browser handle it.
|
||||
.web_canvas => return true,
|
||||
.web_canvas => {
|
||||
if (self.wc) |wc| {
|
||||
if (p) |desired| if (wc.presentation != desired) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
.freetype => {},
|
||||
}
|
||||
@ -298,14 +307,14 @@ pub const Wasm = struct {
|
||||
const wasm = @import("../os/wasm.zig");
|
||||
const alloc = wasm.alloc;
|
||||
|
||||
export fn deferred_face_new(ptr: [*]const u8, len: usize) ?*DeferredFace {
|
||||
return deferred_face_new_(ptr, len) catch |err| {
|
||||
export fn deferred_face_new(ptr: [*]const u8, len: usize, presentation: u16) ?*DeferredFace {
|
||||
return deferred_face_new_(ptr, len, presentation) catch |err| {
|
||||
log.warn("error creating deferred face err={}", .{err});
|
||||
return null;
|
||||
};
|
||||
}
|
||||
|
||||
fn deferred_face_new_(ptr: [*]const u8, len: usize) !*DeferredFace {
|
||||
fn deferred_face_new_(ptr: [*]const u8, len: usize, presentation: u16) !*DeferredFace {
|
||||
var font_str = try alloc.dupeZ(u8, ptr[0..len]);
|
||||
errdefer alloc.free(font_str);
|
||||
|
||||
@ -313,6 +322,7 @@ pub const Wasm = struct {
|
||||
.wc = .{
|
||||
.alloc = alloc,
|
||||
.font_str = font_str,
|
||||
.presentation = @intToEnum(font.Presentation, presentation),
|
||||
},
|
||||
};
|
||||
errdefer face.deinit();
|
||||
|
@ -19,8 +19,7 @@ pub const Face = struct {
|
||||
/// 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.
|
||||
/// The presentation for this font.
|
||||
presentation: font.Presentation,
|
||||
|
||||
/// Metrics for this font face. These are useful for renderers.
|
||||
@ -34,10 +33,15 @@ pub const Face = struct {
|
||||
/// size is always added via the `size` parameter.
|
||||
///
|
||||
/// The raw value is copied so the caller can free it after it is gone.
|
||||
///
|
||||
/// The presentation is given here directly because the browser gives
|
||||
/// us no easy way to determine the presentation we want for this font.
|
||||
/// Callers should just tell us what to expect.
|
||||
pub fn initNamed(
|
||||
alloc: Allocator,
|
||||
raw: []const u8,
|
||||
size: font.face.DesiredSize,
|
||||
presentation: font.Presentation,
|
||||
) !Face {
|
||||
// Copy our font string because we're going to have to reuse it.
|
||||
const font_str = try alloc.dupe(u8, raw);
|
||||
@ -53,8 +57,7 @@ pub const Face = struct {
|
||||
.alloc = alloc,
|
||||
.font_str = font_str,
|
||||
.size = size,
|
||||
// TODO: figure out how we're going to do emoji with web canvas
|
||||
.presentation = .text,
|
||||
.presentation = presentation,
|
||||
|
||||
.canvas = canvas,
|
||||
|
||||
@ -123,11 +126,17 @@ pub const Face = struct {
|
||||
break :width try metrics.get(f32, "width");
|
||||
})) + 1;
|
||||
|
||||
// Height is our ascender + descender for this char
|
||||
const left = try metrics.get(f32, "actualBoundingBoxLeft");
|
||||
const asc = try metrics.get(f32, "actualBoundingBoxAscent");
|
||||
const desc = try metrics.get(f32, "actualBoundingBoxDescent");
|
||||
const left = try metrics.get(f32, "actualBoundingBoxLeft");
|
||||
const height = @floatToInt(u32, @ceil(asc + desc)) + 1;
|
||||
|
||||
// On Firefox on Linux, the bounding box is broken in some cases for
|
||||
// ideographic glyphs (such as emoji). We detect this and behave
|
||||
// differently.
|
||||
const broken_bbox = asc + desc < 0.001;
|
||||
|
||||
// Height is our ascender + descender for this char
|
||||
const height = if (!broken_bbox) @floatToInt(u32, @ceil(asc + desc)) + 1 else width;
|
||||
|
||||
// Note: width and height both get "+ 1" added to them above. This
|
||||
// is important so that there is a 1px border around the glyph to avoid
|
||||
@ -153,6 +162,12 @@ pub const Face = struct {
|
||||
const ctx = try self.context();
|
||||
defer ctx.deinit();
|
||||
|
||||
// For the broken bounding box case we render ideographic baselines
|
||||
// so that we just render the glyph fully in the box with no offsets.
|
||||
if (broken_bbox) {
|
||||
try ctx.set("textBaseline", js.string("ideographic"));
|
||||
}
|
||||
|
||||
// Draw background
|
||||
try ctx.set("fillStyle", js.string("transparent"));
|
||||
try ctx.call(void, "fillRect", .{
|
||||
@ -167,7 +182,7 @@ pub const Face = struct {
|
||||
try ctx.call(void, "fillText", .{
|
||||
glyph_str,
|
||||
left + 1,
|
||||
asc + 1,
|
||||
if (!broken_bbox) asc + 1 else @intToFloat(f32, height),
|
||||
});
|
||||
|
||||
// Read the image data and get it into a []u8 on our side
|
||||
@ -209,18 +224,31 @@ pub const Face = struct {
|
||||
};
|
||||
defer alloc.free(bitmap);
|
||||
|
||||
// The bitmap is in RGBA format and we just want alpha8.
|
||||
// Convert the format of the bitmap if necessary
|
||||
const bitmap_formatted: []u8 = switch (atlas.format) {
|
||||
// Bitmap is already in RGBA
|
||||
.rgba => bitmap,
|
||||
|
||||
// Convert down to A8
|
||||
.greyscale => a8: {
|
||||
assert(@mod(bitmap.len, 4) == 0);
|
||||
var bitmap_a8 = try alloc.alloc(u8, bitmap.len / 4);
|
||||
defer alloc.free(bitmap_a8);
|
||||
errdefer alloc.free(bitmap_a8);
|
||||
var i: usize = 0;
|
||||
while (i < bitmap_a8.len) : (i += 1) {
|
||||
bitmap_a8[i] = bitmap[(i * 4) + 3];
|
||||
}
|
||||
|
||||
break :a8 bitmap_a8;
|
||||
},
|
||||
|
||||
else => return error.UnsupportedAtlasFormat,
|
||||
};
|
||||
defer if (bitmap_formatted.ptr != bitmap.ptr) alloc.free(bitmap_formatted);
|
||||
|
||||
// Put it in our atlas
|
||||
const region = try atlas.reserve(alloc, width, height);
|
||||
if (region.width > 0 and region.height > 0) atlas.set(region, bitmap_a8);
|
||||
if (region.width > 0 and region.height > 0) atlas.set(region, bitmap_formatted);
|
||||
|
||||
return font.Glyph{
|
||||
.width = width,
|
||||
@ -325,12 +353,17 @@ pub const Wasm = struct {
|
||||
const wasm = @import("../../os/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;
|
||||
export fn face_new(ptr: [*]const u8, len: usize, pts: u16, p: u16) ?*Face {
|
||||
return face_new_(ptr, len, pts, p) catch null;
|
||||
}
|
||||
|
||||
fn face_new_(ptr: [*]const u8, len: usize, pts: u16) !*Face {
|
||||
var face = try Face.initNamed(alloc, ptr[0..len], .{ .points = pts });
|
||||
fn face_new_(ptr: [*]const u8, len: usize, pts: u16, presentation: u16) !*Face {
|
||||
var face = try Face.initNamed(
|
||||
alloc,
|
||||
ptr[0..len],
|
||||
.{ .points = pts },
|
||||
@intToEnum(font.Presentation, presentation),
|
||||
);
|
||||
errdefer face.deinit();
|
||||
|
||||
var result = try alloc.create(Face);
|
||||
|
Reference in New Issue
Block a user