diff --git a/src/Grid.zig b/src/Grid.zig index 0022a9b5d..89fe26a42 100644 --- a/src/Grid.zig +++ b/src/Grid.zig @@ -43,9 +43,10 @@ vbo: gl.Buffer, texture: gl.Texture, texture_color: gl.Texture, -/// The font atlas. +/// The font structures. font_lib: font.Library, font_group: font.GroupCache, +font_shaper: font.Shaper, /// Whether the cursor is visible or not. This is used to control cursor /// blinking. @@ -176,6 +177,12 @@ pub fn init( }); errdefer font_group.deinit(alloc); + // Create the initial font shaper + var shape_buf = try alloc.alloc(font.Shaper.Cell, 1); + errdefer alloc.free(shape_buf); + var shaper = try font.Shaper.init(shape_buf); + errdefer shaper.deinit(); + // Load all visible ASCII characters and build our cell width based on // the widest character that we see. const metrics = try font_group.metrics(alloc); @@ -306,6 +313,7 @@ pub fn init( .texture_color = tex_color, .font_lib = font_lib, .font_group = font_group, + .font_shaper = shaper, .cursor_visible = true, .cursor_style = .box, .background = .{ .r = 0, .g = 0, .b = 0 }, @@ -314,6 +322,8 @@ pub fn init( } pub fn deinit(self: *Grid) void { + self.font_shaper.deinit(); + self.alloc.free(self.font_shaper.cell_buf); self.font_group.deinit(self.alloc); self.font_lib.deinit(); @@ -353,12 +363,6 @@ pub fn rebuildCells(self: *Grid, term: *Terminal) !void { // We've written no data to the GPU, refresh it all self.gl_cells_written = 0; - // Create a text shaper we'll use for the screen - var shape_buf = try self.alloc.alloc(font.Shaper.Cell, term.screen.cols * 2); - defer self.alloc.free(shape_buf); - var shaper = try font.Shaper.init(&self.font_group, shape_buf); - defer shaper.deinit(); - // Build each cell var rowIter = term.screen.rowIterator(.viewport); var y: usize = 0; @@ -366,9 +370,9 @@ pub fn rebuildCells(self: *Grid, term: *Terminal) !void { defer y += 1; // Split our row into runs and shape each one. - var iter = shaper.runIterator(row); + var iter = self.font_shaper.runIterator(&self.font_group, row); while (try iter.next(self.alloc)) |run| { - for (try shaper.shape(run)) |shaper_cell| { + for (try self.font_shaper.shape(run)) |shaper_cell| { assert(try self.updateCell( term, row.getCell(shaper_cell.x), @@ -621,6 +625,12 @@ pub fn setScreenSize(self: *Grid, dim: ScreenSize) !void { // Recalculate the rows/columns. self.size.update(dim, self.cell_size); + // Update our shaper + var shape_buf = try self.alloc.alloc(font.Shaper.Cell, self.size.columns * 2); + errdefer self.alloc.free(shape_buf); + self.alloc.free(self.font_shaper.cell_buf); + self.font_shaper.cell_buf = shape_buf; + log.debug("screen size screen={} grid={}, cell={}", .{ dim, self.size, self.cell_size }); } diff --git a/src/font/Shaper.zig b/src/font/Shaper.zig index 0467bc3b6..1e41f5c69 100644 --- a/src/font/Shaper.zig +++ b/src/font/Shaper.zig @@ -17,9 +17,6 @@ const terminal = @import("../terminal/main.zig"); const log = std.log.scoped(.font_shaper); -/// The font group to use under the covers -group: *GroupCache, - /// The buffer used for text shaping. We reuse it across multiple shaping /// calls to prevent allocations. hb_buf: harfbuzz.Buffer, @@ -29,9 +26,8 @@ cell_buf: []Cell, /// The cell_buf argument is the buffer to use for storing shaped results. /// This should be at least the number of columns in the terminal. -pub fn init(group: *GroupCache, cell_buf: []Cell) !Shaper { +pub fn init(cell_buf: []Cell) !Shaper { return Shaper{ - .group = group, .hb_buf = try harfbuzz.Buffer.create(), .cell_buf = cell_buf, }; @@ -44,8 +40,8 @@ pub fn deinit(self: *Shaper) void { /// Returns an iterator that returns one text run at a time for the /// given terminal row. Note that text runs are are only valid one at a time /// for a Shaper struct since they share state. -pub fn runIterator(self: *Shaper, row: terminal.Screen.Row) RunIterator { - return .{ .shaper = self, .row = row }; +pub fn runIterator(self: *Shaper, group: *GroupCache, row: terminal.Screen.Row) RunIterator { + return .{ .shaper = self, .group = group, .row = row }; } /// Shape the given text run. The text run must be the immediately previous @@ -65,7 +61,7 @@ pub fn shape(self: *Shaper, run: TextRun) ![]Cell { harfbuzz.Feature.fromString("liga").?, }; - const face = self.group.group.faceFromIndex(run.font_index); + const face = run.group.group.faceFromIndex(run.font_index); harfbuzz.shape(face.hb_font, self.hb_buf, hb_feats); // If our buffer is empty, we short-circuit the rest of the work @@ -114,12 +110,16 @@ pub const TextRun = struct { /// The total number of cells produced by this run. cells: u16, + /// The font group that built this run. + group: *GroupCache, + /// The font index to use for the glyphs of this run. font_index: Group.FontIndex, }; pub const RunIterator = struct { shaper: *Shaper, + group: *GroupCache, row: terminal.Screen.Row, i: usize = 0, @@ -174,18 +174,18 @@ pub const RunIterator = struct { // Determine the font for this cell. We'll use fallbacks // manually here to try replacement chars and then a space // for unknown glyphs. - const font_idx_opt = (try self.shaper.group.indexForCodepoint( + const font_idx_opt = (try self.group.indexForCodepoint( alloc, if (cell.empty()) ' ' else cell.char, style, presentation, - )) orelse (try self.shaper.group.indexForCodepoint( + )) orelse (try self.group.indexForCodepoint( alloc, 0xFFFD, style, .text, )) orelse - try self.shaper.group.indexForCodepoint(alloc, ' ', style, .text); + try self.group.indexForCodepoint(alloc, ' ', style, .text); const font_idx = font_idx_opt.?; //log.warn("char={x} idx={}", .{ cell.char, font_idx }); if (j == self.i) current_font = font_idx; @@ -216,6 +216,7 @@ pub const RunIterator = struct { return TextRun{ .offset = @intCast(u16, self.i), .cells = @intCast(u16, j - self.i), + .group = self.group, .font_index = current_font, }; } @@ -236,7 +237,7 @@ test "run iterator" { // Get our run iterator var shaper = testdata.shaper; - var it = shaper.runIterator(screen.getRow(.{ .screen = 0 })); + var it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 })); var count: usize = 0; while (try it.next(alloc)) |_| count += 1; try testing.expectEqual(@as(usize, 1), count); @@ -249,7 +250,7 @@ test "run iterator" { try screen.testWriteString("ABCD EFG"); var shaper = testdata.shaper; - var it = shaper.runIterator(screen.getRow(.{ .screen = 0 })); + var it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 })); var count: usize = 0; while (try it.next(alloc)) |_| count += 1; try testing.expectEqual(@as(usize, 1), count); @@ -263,7 +264,7 @@ test "run iterator" { // Get our run iterator var shaper = testdata.shaper; - var it = shaper.runIterator(screen.getRow(.{ .screen = 0 })); + var it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 })); var count: usize = 0; while (try it.next(alloc)) |_| { count += 1; @@ -295,7 +296,7 @@ test "shape" { // Get our run iterator var shaper = testdata.shaper; - var it = shaper.runIterator(screen.getRow(.{ .screen = 0 })); + var it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 })); var count: usize = 0; while (try it.next(alloc)) |run| { count += 1; @@ -318,7 +319,7 @@ test "shape inconsolata ligs" { try screen.testWriteString(">="); var shaper = testdata.shaper; - var it = shaper.runIterator(screen.getRow(.{ .screen = 0 })); + var it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 })); var count: usize = 0; while (try it.next(alloc)) |run| { count += 1; @@ -335,7 +336,7 @@ test "shape inconsolata ligs" { try screen.testWriteString("==="); var shaper = testdata.shaper; - var it = shaper.runIterator(screen.getRow(.{ .screen = 0 })); + var it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 })); var count: usize = 0; while (try it.next(alloc)) |run| { count += 1; @@ -360,7 +361,7 @@ test "shape emoji width" { try screen.testWriteString("👍"); var shaper = testdata.shaper; - var it = shaper.runIterator(screen.getRow(.{ .screen = 0 })); + var it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 })); var count: usize = 0; while (try it.next(alloc)) |run| { count += 1; @@ -394,7 +395,7 @@ test "shape emoji width long" { // Get our run iterator var shaper = testdata.shaper; - var it = shaper.runIterator(screen.getRow(.{ .screen = 0 })); + var it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 })); var count: usize = 0; while (try it.next(alloc)) |run| { count += 1; @@ -425,7 +426,7 @@ test "shape variation selector VS15" { // Get our run iterator var shaper = testdata.shaper; - var it = shaper.runIterator(screen.getRow(.{ .screen = 0 })); + var it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 })); var count: usize = 0; while (try it.next(alloc)) |run| { count += 1; @@ -456,7 +457,7 @@ test "shape variation selector VS16" { // Get our run iterator var shaper = testdata.shaper; - var it = shaper.runIterator(screen.getRow(.{ .screen = 0 })); + var it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 })); var count: usize = 0; while (try it.next(alloc)) |run| { count += 1; @@ -484,7 +485,7 @@ test "shape with empty cells in between" { // Get our run iterator var shaper = testdata.shaper; - var it = shaper.runIterator(screen.getRow(.{ .screen = 0 })); + var it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 })); var count: usize = 0; while (try it.next(alloc)) |run| { count += 1; @@ -516,7 +517,7 @@ test "shape Chinese characters" { // Get our run iterator var shaper = testdata.shaper; - var it = shaper.runIterator(screen.getRow(.{ .screen = 0 })); + var it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 })); var count: usize = 0; while (try it.next(alloc)) |run| { count += 1; @@ -569,7 +570,7 @@ fn testShaper(alloc: Allocator) !TestShaper { var cell_buf = try alloc.alloc(Cell, 80); errdefer alloc.free(cell_buf); - var shaper = try init(cache_ptr, cell_buf); + var shaper = try init(cell_buf); errdefer shaper.deinit(); return TestShaper{