mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-16 16:56:09 +03:00
font: web canvas tracks glyph indexes for grapheme clusters
This commit is contained in:
@ -9,6 +9,10 @@ const font = @import("../main.zig");
|
|||||||
const log = std.log.scoped(.font_face);
|
const log = std.log.scoped(.font_face);
|
||||||
|
|
||||||
pub const Face = struct {
|
pub const Face = struct {
|
||||||
|
/// See graphemes field for more details.
|
||||||
|
const grapheme_start: u32 = 0x10FFFF + 1;
|
||||||
|
const grapheme_end: u32 = std.math.maxInt(u32);
|
||||||
|
|
||||||
/// The web canvas face makes use of an allocator when interacting
|
/// The web canvas face makes use of an allocator when interacting
|
||||||
/// with the JS environment.
|
/// with the JS environment.
|
||||||
alloc: Allocator,
|
alloc: Allocator,
|
||||||
@ -28,6 +32,13 @@ pub const Face = struct {
|
|||||||
/// The canvas element that we will reuse to render glyphs
|
/// The canvas element that we will reuse to render glyphs
|
||||||
canvas: js.Object,
|
canvas: js.Object,
|
||||||
|
|
||||||
|
/// The map to store multi-codepoint grapheme clusters that are rendered.
|
||||||
|
/// We use 1 above the maximum unicode codepoint up to the max 32-bit
|
||||||
|
/// unsigned integer to store the "glyph index" for graphemes.
|
||||||
|
grapheme_to_glyph: std.StringHashMapUnmanaged(u32) = .{},
|
||||||
|
glyph_to_grapheme: std.AutoHashMapUnmanaged(u32, []u8) = .{},
|
||||||
|
grapheme_next: u32 = grapheme_start,
|
||||||
|
|
||||||
/// Initialize a web canvas font with a "raw" value. The "raw" value can
|
/// 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
|
/// be any valid value for a CSS "font" property EXCLUDING the size. The
|
||||||
/// size is always added via the `size` parameter.
|
/// size is always added via the `size` parameter.
|
||||||
@ -72,6 +83,12 @@ pub const Face = struct {
|
|||||||
|
|
||||||
pub fn deinit(self: *Face) void {
|
pub fn deinit(self: *Face) void {
|
||||||
self.alloc.free(self.font_str);
|
self.alloc.free(self.font_str);
|
||||||
|
self.grapheme_to_glyph.deinit(self.alloc);
|
||||||
|
{
|
||||||
|
var it = self.glyph_to_grapheme.valueIterator();
|
||||||
|
while (it.next()) |value| self.alloc.free(value.*);
|
||||||
|
self.glyph_to_grapheme.deinit(self.alloc);
|
||||||
|
}
|
||||||
self.canvas.deinit();
|
self.canvas.deinit();
|
||||||
self.* = undefined;
|
self.* = undefined;
|
||||||
}
|
}
|
||||||
@ -132,6 +149,34 @@ pub const Face = struct {
|
|||||||
return .text;
|
return .text;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the glyph index for the given grapheme cluster. The same
|
||||||
|
/// cluster will always map to the same glyph index. This does not render
|
||||||
|
/// the grapheme at this time, only reserves the index.
|
||||||
|
pub fn graphemeGlyphIndex(self: *Face, cluster: []const u8) error{OutOfMemory}!u32 {
|
||||||
|
// If we already have this stored then return it
|
||||||
|
const gop = try self.grapheme_to_glyph.getOrPut(self.alloc, cluster);
|
||||||
|
if (gop.found_existing) return gop.value_ptr.*;
|
||||||
|
errdefer _ = self.grapheme_to_glyph.remove(cluster);
|
||||||
|
|
||||||
|
// We don't have it stored. Ensure we have space to store. The
|
||||||
|
// next will be "0" if we're out of space due to unsigned int wrapping.
|
||||||
|
if (self.grapheme_next == 0) return error.OutOfMemory;
|
||||||
|
|
||||||
|
// Copy the cluster for our reverse mapping
|
||||||
|
const copy = try self.alloc.dupe(u8, cluster);
|
||||||
|
errdefer self.alloc.free(copy);
|
||||||
|
|
||||||
|
// Grow space for the reverse mapping
|
||||||
|
try self.glyph_to_grapheme.ensureUnusedCapacity(self.alloc, 1);
|
||||||
|
|
||||||
|
// Store it
|
||||||
|
gop.value_ptr.* = self.grapheme_next;
|
||||||
|
self.glyph_to_grapheme.putAssumeCapacity(self.grapheme_next, copy);
|
||||||
|
|
||||||
|
self.grapheme_next +%= 1;
|
||||||
|
return gop.value_ptr.*;
|
||||||
|
}
|
||||||
|
|
||||||
/// Render a glyph using the glyph index. The rendered glyph is stored
|
/// Render a glyph using the glyph index. The rendered glyph is stored
|
||||||
/// in the given texture atlas.
|
/// in the given texture atlas.
|
||||||
pub fn renderGlyph(
|
pub fn renderGlyph(
|
||||||
|
@ -71,7 +71,6 @@ pub const Shaper = struct {
|
|||||||
/// returned.
|
/// returned.
|
||||||
pub fn shape(self: *Shaper, run: font.shape.TextRun) ![]font.shape.Cell {
|
pub fn shape(self: *Shaper, run: font.shape.TextRun) ![]font.shape.Cell {
|
||||||
// TODO: memory check that cell_buf can fit results
|
// TODO: memory check that cell_buf can fit results
|
||||||
_ = run;
|
|
||||||
|
|
||||||
const codepoints = self.run_buf.items(.codepoint);
|
const codepoints = self.run_buf.items(.codepoint);
|
||||||
const clusters = self.run_buf.items(.cluster);
|
const clusters = self.run_buf.items(.cluster);
|
||||||
@ -143,9 +142,47 @@ pub const Shaper = struct {
|
|||||||
.glyph_index = codepoints[start],
|
.glyph_index = codepoints[start],
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// We must have multiple codepoints (see assert above). In
|
||||||
|
// this case we UTF-8 encode the codepoints and send them
|
||||||
|
// to the face to reserve a private glyph index.
|
||||||
else => {
|
else => {
|
||||||
unreachable;
|
// UTF-8 encode the codepoints in this cluster.
|
||||||
// TODO;
|
const cluster = cluster: {
|
||||||
|
const cluster_points = codepoints[start..i];
|
||||||
|
assert(cluster_points.len == len);
|
||||||
|
|
||||||
|
const buf_len = buf_len: {
|
||||||
|
var acc: usize = 0;
|
||||||
|
for (cluster_points) |cp| {
|
||||||
|
acc += try std.unicode.utf8CodepointSequenceLength(
|
||||||
|
@intCast(u21, cp),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
break :buf_len acc;
|
||||||
|
};
|
||||||
|
|
||||||
|
var buf = try self.alloc.alloc(u8, buf_len);
|
||||||
|
errdefer self.alloc.free(buf);
|
||||||
|
var buf_i: usize = 0;
|
||||||
|
for (cluster_points) |cp| {
|
||||||
|
buf_i += try std.unicode.utf8Encode(
|
||||||
|
@intCast(u21, cp),
|
||||||
|
buf[buf_i..],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
break :cluster buf;
|
||||||
|
};
|
||||||
|
defer self.alloc.free(cluster);
|
||||||
|
|
||||||
|
var face = try run.group.group.faceFromIndex(run.font_index);
|
||||||
|
const index = try face.graphemeGlyphIndex(cluster);
|
||||||
|
|
||||||
|
self.cell_buf[cur] = .{
|
||||||
|
.x = @intCast(u16, clusters[start]),
|
||||||
|
.glyph_index = index,
|
||||||
|
};
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user