diff --git a/src/font/shape.zig b/src/font/shape.zig index 086e5d8f6..2aa3bf442 100644 --- a/src/font/shape.zig +++ b/src/font/shape.zig @@ -33,10 +33,6 @@ pub const Cell = struct { /// Options for shapers. pub const Options = struct { - /// 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. - cell_buf: []Cell, - /// Font features to use when shaping. These can be in the following /// formats: "-feat" "+feat" "feat". A "-"-prefix is used to disable /// a feature and the others are used to enable a feature. If a feature diff --git a/src/font/shaper/harfbuzz.zig b/src/font/shaper/harfbuzz.zig index cceb7d8af..0b5293d70 100644 --- a/src/font/shaper/harfbuzz.zig +++ b/src/font/shaper/harfbuzz.zig @@ -17,17 +17,21 @@ const log = std.log.scoped(.font_shaper); /// Shaper that uses Harfbuzz. pub const Shaper = struct { + /// The allocated used for the feature list and cell buf. + alloc: Allocator, + /// The buffer used for text shaping. We reuse it across multiple shaping /// calls to prevent allocations. hb_buf: harfbuzz.Buffer, /// The shared memory used for shaping results. - cell_buf: []font.shape.Cell, + cell_buf: CellBuf, /// The features to use for shaping. hb_feats: FeatureList, - const FeatureList = std.ArrayList(harfbuzz.Feature); + const CellBuf = std.ArrayListUnmanaged(font.shape.Cell); + const FeatureList = std.ArrayListUnmanaged(harfbuzz.Feature); // These features are hardcoded to always be on by default. Users // can turn them off by setting the features to "-liga" for example. @@ -39,34 +43,36 @@ pub const Shaper = struct { // Parse all the features we want to use. We use var hb_feats = hb_feats: { var list = try FeatureList.initCapacity(alloc, opts.features.len + hardcoded_features.len); - errdefer list.deinit(); + errdefer list.deinit(alloc); for (hardcoded_features) |name| { if (harfbuzz.Feature.fromString(name)) |feat| { - try list.append(feat); + try list.append(alloc, feat); } else log.warn("failed to parse font feature: {s}", .{name}); } for (opts.features) |name| { if (harfbuzz.Feature.fromString(name)) |feat| { - try list.append(feat); + try list.append(alloc, feat); } else log.warn("failed to parse font feature: {s}", .{name}); } break :hb_feats list; }; - errdefer hb_feats.deinit(); + errdefer hb_feats.deinit(alloc); return Shaper{ + .alloc = alloc, .hb_buf = try harfbuzz.Buffer.create(), - .cell_buf = opts.cell_buf, + .cell_buf = .{}, .hb_feats = hb_feats, }; } pub fn deinit(self: *Shaper) void { self.hb_buf.destroy(); - self.hb_feats.deinit(); + self.cell_buf.deinit(self.alloc); + self.hb_feats.deinit(self.alloc); } /// Returns an iterator that returns one text run at a time for the @@ -99,7 +105,7 @@ pub const Shaper = struct { /// The return value is only valid until the next shape call is called. /// /// If there is not enough space in the cell buffer, an error is returned. - pub fn shape(self: *Shaper, run: font.shape.TextRun) ![]font.shape.Cell { + pub fn shape(self: *Shaper, run: font.shape.TextRun) ![]const font.shape.Cell { const tracy = trace(@src()); defer tracy.end(); @@ -119,7 +125,7 @@ pub const Shaper = struct { // If our buffer is empty, we short-circuit the rest of the work // return nothing. - if (self.hb_buf.getLength() == 0) return self.cell_buf[0..0]; + if (self.hb_buf.getLength() == 0) return self.cell_buf.items[0..0]; const info = self.hb_buf.getGlyphInfos(); const pos = self.hb_buf.getGlyphPositions() orelse return error.HarfbuzzFailed; @@ -128,19 +134,18 @@ pub const Shaper = struct { assert(info.len == pos.len); // Convert all our info/pos to cells and set it. - if (info.len > self.cell_buf.len) return error.OutOfMemory; - //log.warn("info={} pos={} run={}", .{ info.len, pos.len, run }); - - for (info, 0..) |v, i| { - self.cell_buf[i] = .{ + self.cell_buf.clearRetainingCapacity(); + try self.cell_buf.ensureTotalCapacity(self.alloc, info.len); + for (info) |v| { + self.cell_buf.appendAssumeCapacity(.{ .x = @intCast(v.cluster), .glyph_index = v.codepoint, - }; + }); // log.warn("i={} info={} pos={} cell={}", .{ i, v, pos[i], self.cell_buf[i] }); } - return self.cell_buf[0..info.len]; + return self.cell_buf.items; } /// The hooks for RunIterator. @@ -178,7 +183,7 @@ test "run iterator" { try screen.testWriteString("ABCD"); // Get our run iterator - var shaper = testdata.shaper; + var shaper = &testdata.shaper; var it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }), null, null); var count: usize = 0; while (try it.next(alloc)) |_| count += 1; @@ -191,7 +196,7 @@ test "run iterator" { defer screen.deinit(); try screen.testWriteString("ABCD EFG"); - var shaper = testdata.shaper; + var shaper = &testdata.shaper; var it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }), null, null); var count: usize = 0; while (try it.next(alloc)) |_| count += 1; @@ -205,7 +210,7 @@ test "run iterator" { try screen.testWriteString("A😃D"); // Get our run iterator - var shaper = testdata.shaper; + var shaper = &testdata.shaper; var it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }), null, null); var count: usize = 0; while (try it.next(alloc)) |_| { @@ -239,7 +244,7 @@ test "run iterator: empty cells with background set" { row.getCellPtr(2).* = screen.cursor.pen; // Get our run iterator - var shaper = testdata.shaper; + var shaper = &testdata.shaper; var it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }), null, null); var count: usize = 0; while (try it.next(alloc)) |run| { @@ -274,7 +279,7 @@ test "shape" { try screen.testWriteString(buf[0..buf_idx]); // Get our run iterator - var shaper = testdata.shaper; + var shaper = &testdata.shaper; var it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }), null, null); var count: usize = 0; while (try it.next(alloc)) |run| { @@ -297,7 +302,7 @@ test "shape inconsolata ligs" { defer screen.deinit(); try screen.testWriteString(">="); - var shaper = testdata.shaper; + var shaper = &testdata.shaper; var it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }), null, null); var count: usize = 0; while (try it.next(alloc)) |run| { @@ -314,7 +319,7 @@ test "shape inconsolata ligs" { defer screen.deinit(); try screen.testWriteString("==="); - var shaper = testdata.shaper; + var shaper = &testdata.shaper; var it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }), null, null); var count: usize = 0; while (try it.next(alloc)) |run| { @@ -339,7 +344,7 @@ test "shape emoji width" { defer screen.deinit(); try screen.testWriteString("👍"); - var shaper = testdata.shaper; + var shaper = &testdata.shaper; var it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }), null, null); var count: usize = 0; while (try it.next(alloc)) |run| { @@ -373,7 +378,7 @@ test "shape emoji width long" { try screen.testWriteString(buf[0..buf_idx]); // Get our run iterator - var shaper = testdata.shaper; + var shaper = &testdata.shaper; var it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }), null, null); var count: usize = 0; while (try it.next(alloc)) |run| { @@ -404,7 +409,7 @@ test "shape variation selector VS15" { try screen.testWriteString(buf[0..buf_idx]); // Get our run iterator - var shaper = testdata.shaper; + var shaper = &testdata.shaper; var it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }), null, null); var count: usize = 0; while (try it.next(alloc)) |run| { @@ -435,7 +440,7 @@ test "shape variation selector VS16" { try screen.testWriteString(buf[0..buf_idx]); // Get our run iterator - var shaper = testdata.shaper; + var shaper = &testdata.shaper; var it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }), null, null); var count: usize = 0; while (try it.next(alloc)) |run| { @@ -463,7 +468,7 @@ test "shape with empty cells in between" { try screen.testWriteString("B"); // Get our run iterator - var shaper = testdata.shaper; + var shaper = &testdata.shaper; var it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }), null, null); var count: usize = 0; while (try it.next(alloc)) |run| { @@ -495,7 +500,7 @@ test "shape Chinese characters" { try screen.testWriteString(buf[0..buf_idx]); // Get our run iterator - var shaper = testdata.shaper; + var shaper = &testdata.shaper; var it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }), null, null); var count: usize = 0; while (try it.next(alloc)) |run| { @@ -536,7 +541,7 @@ test "shape box glyphs" { try screen.testWriteString(buf[0..buf_idx]); // Get our run iterator - var shaper = testdata.shaper; + var shaper = &testdata.shaper; var it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }), null, null); var count: usize = 0; while (try it.next(alloc)) |run| { @@ -567,7 +572,7 @@ test "shape selection boundary" { // Full line selection { // Get our run iterator - var shaper = testdata.shaper; + var shaper = &testdata.shaper; var it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }), .{ .start = .{ .x = 0, .y = 0 }, .end = .{ .x = screen.cols - 1, .y = 0 }, @@ -583,7 +588,7 @@ test "shape selection boundary" { // Offset x, goes to end of line selection { // Get our run iterator - var shaper = testdata.shaper; + var shaper = &testdata.shaper; var it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }), .{ .start = .{ .x = 2, .y = 0 }, .end = .{ .x = screen.cols - 1, .y = 0 }, @@ -599,7 +604,7 @@ test "shape selection boundary" { // Offset x, starts at beginning of line { // Get our run iterator - var shaper = testdata.shaper; + var shaper = &testdata.shaper; var it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }), .{ .start = .{ .x = 0, .y = 0 }, .end = .{ .x = 3, .y = 0 }, @@ -615,7 +620,7 @@ test "shape selection boundary" { // Selection only subset of line { // Get our run iterator - var shaper = testdata.shaper; + var shaper = &testdata.shaper; var it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }), .{ .start = .{ .x = 1, .y = 0 }, .end = .{ .x = 3, .y = 0 }, @@ -631,7 +636,7 @@ test "shape selection boundary" { // Selection only one character { // Get our run iterator - var shaper = testdata.shaper; + var shaper = &testdata.shaper; var it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }), .{ .start = .{ .x = 1, .y = 0 }, .end = .{ .x = 1, .y = 0 }, @@ -660,7 +665,7 @@ test "shape cursor boundary" { // No cursor is full line { // Get our run iterator - var shaper = testdata.shaper; + var shaper = &testdata.shaper; var it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }), null, null); var count: usize = 0; while (try it.next(alloc)) |run| { @@ -673,7 +678,7 @@ test "shape cursor boundary" { // Cursor at index 0 is two runs { // Get our run iterator - var shaper = testdata.shaper; + var shaper = &testdata.shaper; var it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }), null, 0); var count: usize = 0; while (try it.next(alloc)) |run| { @@ -686,7 +691,7 @@ test "shape cursor boundary" { // Cursor at index 1 is three runs { // Get our run iterator - var shaper = testdata.shaper; + var shaper = &testdata.shaper; var it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }), null, 1); var count: usize = 0; while (try it.next(alloc)) |run| { @@ -699,7 +704,7 @@ test "shape cursor boundary" { // Cursor at last col is two runs { // Get our run iterator - var shaper = testdata.shaper; + var shaper = &testdata.shaper; var it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }), null, 9); var count: usize = 0; while (try it.next(alloc)) |run| { @@ -725,7 +730,7 @@ test "shape cursor boundary and colored emoji" { // No cursor is full line { // Get our run iterator - var shaper = testdata.shaper; + var shaper = &testdata.shaper; var it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }), null, null); var count: usize = 0; while (try it.next(alloc)) |run| { @@ -738,7 +743,7 @@ test "shape cursor boundary and colored emoji" { // Cursor on emoji does not split it { // Get our run iterator - var shaper = testdata.shaper; + var shaper = &testdata.shaper; var it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }), null, 0); var count: usize = 0; while (try it.next(alloc)) |run| { @@ -749,7 +754,7 @@ test "shape cursor boundary and colored emoji" { } { // Get our run iterator - var shaper = testdata.shaper; + var shaper = &testdata.shaper; var it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }), null, 1); var count: usize = 0; while (try it.next(alloc)) |run| { @@ -773,7 +778,7 @@ test "shape cell attribute change" { defer screen.deinit(); try screen.testWriteString(">="); - var shaper = testdata.shaper; + var shaper = &testdata.shaper; var it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }), null, null); var count: usize = 0; while (try it.next(alloc)) |run| { @@ -791,7 +796,7 @@ test "shape cell attribute change" { screen.cursor.pen.attrs.bold = true; try screen.testWriteString("="); - var shaper = testdata.shaper; + var shaper = &testdata.shaper; var it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }), null, null); var count: usize = 0; while (try it.next(alloc)) |run| { @@ -811,7 +816,7 @@ test "shape cell attribute change" { screen.cursor.pen.fg = .{ .r = 3, .g = 2, .b = 1 }; try screen.testWriteString("="); - var shaper = testdata.shaper; + var shaper = &testdata.shaper; var it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }), null, null); var count: usize = 0; while (try it.next(alloc)) |run| { @@ -831,7 +836,7 @@ test "shape cell attribute change" { screen.cursor.pen.bg = .{ .r = 3, .g = 2, .b = 1 }; try screen.testWriteString("="); - var shaper = testdata.shaper; + var shaper = &testdata.shaper; var it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }), null, null); var count: usize = 0; while (try it.next(alloc)) |run| { @@ -850,7 +855,7 @@ test "shape cell attribute change" { try screen.testWriteString(">"); try screen.testWriteString("="); - var shaper = testdata.shaper; + var shaper = &testdata.shaper; var it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }), null, null); var count: usize = 0; while (try it.next(alloc)) |run| { @@ -866,13 +871,11 @@ const TestShaper = struct { shaper: Shaper, cache: *GroupCache, lib: Library, - cell_buf: []font.shape.Cell, pub fn deinit(self: *TestShaper) void { self.shaper.deinit(); self.cache.deinit(self.alloc); self.alloc.destroy(self.cache); - self.alloc.free(self.cell_buf); self.lib.deinit(); } }; @@ -917,10 +920,7 @@ fn testShaper(alloc: Allocator) !TestShaper { } _ = try cache_ptr.group.addFace(.regular, .{ .loaded = try Face.init(lib, testEmojiText, .{ .points = 12 }) }); - var cell_buf = try alloc.alloc(font.shape.Cell, 80); - errdefer alloc.free(cell_buf); - - var shaper = try Shaper.init(alloc, .{ .cell_buf = cell_buf }); + var shaper = try Shaper.init(alloc, .{}); errdefer shaper.deinit(); return TestShaper{ @@ -928,6 +928,5 @@ fn testShaper(alloc: Allocator) !TestShaper { .shaper = shaper, .cache = cache_ptr, .lib = lib, - .cell_buf = cell_buf, }; } diff --git a/src/renderer/Metal.zig b/src/renderer/Metal.zig index 7e9051d37..f33553f6b 100644 --- a/src/renderer/Metal.zig +++ b/src/renderer/Metal.zig @@ -218,10 +218,7 @@ pub fn init(alloc: Allocator, options: renderer.Options) !Metal { // Create the font shaper. We initially create a shaper that can support // a width of 160 which is a common width for modern screens to help // avoid allocations later. - var shape_buf = try alloc.alloc(font.shape.Cell, 160); - errdefer alloc.free(shape_buf); var font_shaper = try font.Shaper.init(alloc, .{ - .cell_buf = shape_buf, .features = options.config.font_features.items, }); errdefer font_shaper.deinit(); @@ -288,7 +285,6 @@ pub fn deinit(self: *Metal) void { self.cells_bg.deinit(self.alloc); self.font_shaper.deinit(); - self.alloc.free(self.font_shaper.cell_buf); self.config.deinit(); @@ -417,14 +413,6 @@ pub fn setFontSize(self: *Metal, size: font.face.DesiredSize) !void { if (std.meta.eql(self.cell_size, new_cell_size)) return; self.cell_size = new_cell_size; - // Resize our font shaping buffer to fit the new width. - if (self.gridSize()) |grid_size| { - var shape_buf = try self.alloc.alloc(font.shape.Cell, grid_size.columns * 2); - errdefer self.alloc.free(shape_buf); - self.alloc.free(self.font_shaper.cell_buf); - self.font_shaper.cell_buf = shape_buf; - } - // Set the sprite font up self.font_group.group.sprite = font.sprite.Face{ .width = self.cell_size.width, @@ -998,7 +986,6 @@ pub fn changeConfig(self: *Metal, config: *DerivedConfig) !void { // easier and rare enough to not cause performance issues. { var font_shaper = try font.Shaper.init(self.alloc, .{ - .cell_buf = self.font_shaper.cell_buf, .features = config.font_features.items, }); errdefer font_shaper.deinit(); @@ -1033,13 +1020,6 @@ pub fn setScreenSize( .{}); const padded_dim = dim.subPadding(padding); - // Update our shaper - // TODO: don't reallocate if it is close enough (but bigger) - var shape_buf = try self.alloc.alloc(font.shape.Cell, grid_size.columns * 2); - errdefer self.alloc.free(shape_buf); - self.alloc.free(self.font_shaper.cell_buf); - self.font_shaper.cell_buf = shape_buf; - // Set the size of the drawable surface to the bounds self.swapchain.setProperty("drawableSize", macos.graphics.Size{ .width = @floatFromInt(dim.width), diff --git a/src/renderer/OpenGL.zig b/src/renderer/OpenGL.zig index 44e52b4a6..96b3de5e1 100644 --- a/src/renderer/OpenGL.zig +++ b/src/renderer/OpenGL.zig @@ -280,10 +280,7 @@ pub const DerivedConfig = struct { pub fn init(alloc: Allocator, options: renderer.Options) !OpenGL { // Create the initial font shaper - var shape_buf = try alloc.alloc(font.shape.Cell, 1); - errdefer alloc.free(shape_buf); var shaper = try font.Shaper.init(alloc, .{ - .cell_buf = shape_buf, .features = options.config.font_features.items, }); errdefer shaper.deinit(); @@ -318,7 +315,6 @@ pub fn init(alloc: Allocator, options: renderer.Options) !OpenGL { pub fn deinit(self: *OpenGL) void { self.font_shaper.deinit(); - self.alloc.free(self.font_shaper.cell_buf); if (self.gl_state) |*v| v.deinit(); @@ -525,15 +521,6 @@ pub fn setFontSize(self: *OpenGL, size: font.face.DesiredSize) !void { if (std.meta.eql(self.cell_size, new_cell_size)) return; self.cell_size = new_cell_size; - // Resize our font shaping buffer to fit the new width. - if (self.screen_size) |dim| { - const grid_size = self.gridSize(dim); - var shape_buf = try self.alloc.alloc(font.shape.Cell, grid_size.columns * 2); - errdefer self.alloc.free(shape_buf); - self.alloc.free(self.font_shaper.cell_buf); - self.font_shaper.cell_buf = shape_buf; - } - // Notify the window that the cell size changed. _ = self.surface_mailbox.push(.{ .cell_size = new_cell_size, @@ -1228,7 +1215,6 @@ pub fn changeConfig(self: *OpenGL, config: *DerivedConfig) !void { // easier and rare enough to not cause performance issues. { var font_shaper = try font.Shaper.init(self.alloc, .{ - .cell_buf = self.font_shaper.cell_buf, .features = config.font_features.items, }); errdefer font_shaper.deinit(); @@ -1264,12 +1250,6 @@ pub fn setScreenSize( self.padding.explicit, }); - // Update our shaper - var shape_buf = try self.alloc.alloc(font.shape.Cell, grid_size.columns * 2); - errdefer self.alloc.free(shape_buf); - self.alloc.free(self.font_shaper.cell_buf); - self.font_shaper.cell_buf = shape_buf; - // Defer our OpenGL updates self.deferred_screen_size = .{ .size = dim }; }