font: shaper dynamically allocates cell buffer

Pathlogical grapheme clusters can use a LOT of memory, so we need to be
able to grow.
This commit is contained in:
Mitchell Hashimoto
2023-09-26 17:57:50 -07:00
parent 0c9799ba61
commit 5c1fbd09cd
4 changed files with 54 additions and 99 deletions

View File

@ -33,10 +33,6 @@ pub const Cell = struct {
/// Options for shapers. /// Options for shapers.
pub const Options = struct { 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 /// Font features to use when shaping. These can be in the following
/// formats: "-feat" "+feat" "feat". A "-"-prefix is used to disable /// formats: "-feat" "+feat" "feat". A "-"-prefix is used to disable
/// a feature and the others are used to enable a feature. If a feature /// a feature and the others are used to enable a feature. If a feature

View File

@ -17,17 +17,21 @@ const log = std.log.scoped(.font_shaper);
/// Shaper that uses Harfbuzz. /// Shaper that uses Harfbuzz.
pub const Shaper = struct { 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 /// The buffer used for text shaping. We reuse it across multiple shaping
/// calls to prevent allocations. /// calls to prevent allocations.
hb_buf: harfbuzz.Buffer, hb_buf: harfbuzz.Buffer,
/// The shared memory used for shaping results. /// The shared memory used for shaping results.
cell_buf: []font.shape.Cell, cell_buf: CellBuf,
/// The features to use for shaping. /// The features to use for shaping.
hb_feats: FeatureList, 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 // These features are hardcoded to always be on by default. Users
// can turn them off by setting the features to "-liga" for example. // 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 // Parse all the features we want to use. We use
var hb_feats = hb_feats: { var hb_feats = hb_feats: {
var list = try FeatureList.initCapacity(alloc, opts.features.len + hardcoded_features.len); var list = try FeatureList.initCapacity(alloc, opts.features.len + hardcoded_features.len);
errdefer list.deinit(); errdefer list.deinit(alloc);
for (hardcoded_features) |name| { for (hardcoded_features) |name| {
if (harfbuzz.Feature.fromString(name)) |feat| { 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}); } else log.warn("failed to parse font feature: {s}", .{name});
} }
for (opts.features) |name| { for (opts.features) |name| {
if (harfbuzz.Feature.fromString(name)) |feat| { 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}); } else log.warn("failed to parse font feature: {s}", .{name});
} }
break :hb_feats list; break :hb_feats list;
}; };
errdefer hb_feats.deinit(); errdefer hb_feats.deinit(alloc);
return Shaper{ return Shaper{
.alloc = alloc,
.hb_buf = try harfbuzz.Buffer.create(), .hb_buf = try harfbuzz.Buffer.create(),
.cell_buf = opts.cell_buf, .cell_buf = .{},
.hb_feats = hb_feats, .hb_feats = hb_feats,
}; };
} }
pub fn deinit(self: *Shaper) void { pub fn deinit(self: *Shaper) void {
self.hb_buf.destroy(); 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 /// 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. /// 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. /// 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()); const tracy = trace(@src());
defer tracy.end(); defer tracy.end();
@ -119,7 +125,7 @@ pub const Shaper = struct {
// If our buffer is empty, we short-circuit the rest of the work // If our buffer is empty, we short-circuit the rest of the work
// return nothing. // 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 info = self.hb_buf.getGlyphInfos();
const pos = self.hb_buf.getGlyphPositions() orelse return error.HarfbuzzFailed; const pos = self.hb_buf.getGlyphPositions() orelse return error.HarfbuzzFailed;
@ -128,19 +134,18 @@ pub const Shaper = struct {
assert(info.len == pos.len); assert(info.len == pos.len);
// Convert all our info/pos to cells and set it. // Convert all our info/pos to cells and set it.
if (info.len > self.cell_buf.len) return error.OutOfMemory; self.cell_buf.clearRetainingCapacity();
//log.warn("info={} pos={} run={}", .{ info.len, pos.len, run }); try self.cell_buf.ensureTotalCapacity(self.alloc, info.len);
for (info) |v| {
for (info, 0..) |v, i| { self.cell_buf.appendAssumeCapacity(.{
self.cell_buf[i] = .{
.x = @intCast(v.cluster), .x = @intCast(v.cluster),
.glyph_index = v.codepoint, .glyph_index = v.codepoint,
}; });
// log.warn("i={} info={} pos={} cell={}", .{ i, v, pos[i], self.cell_buf[i] }); // 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. /// The hooks for RunIterator.
@ -178,7 +183,7 @@ test "run iterator" {
try screen.testWriteString("ABCD"); try screen.testWriteString("ABCD");
// Get our run iterator // 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 it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }), null, null);
var count: usize = 0; var count: usize = 0;
while (try it.next(alloc)) |_| count += 1; while (try it.next(alloc)) |_| count += 1;
@ -191,7 +196,7 @@ test "run iterator" {
defer screen.deinit(); defer screen.deinit();
try screen.testWriteString("ABCD EFG"); 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 it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }), null, null);
var count: usize = 0; var count: usize = 0;
while (try it.next(alloc)) |_| count += 1; while (try it.next(alloc)) |_| count += 1;
@ -205,7 +210,7 @@ test "run iterator" {
try screen.testWriteString("A😃D"); try screen.testWriteString("A😃D");
// Get our run iterator // 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 it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }), null, null);
var count: usize = 0; var count: usize = 0;
while (try it.next(alloc)) |_| { while (try it.next(alloc)) |_| {
@ -239,7 +244,7 @@ test "run iterator: empty cells with background set" {
row.getCellPtr(2).* = screen.cursor.pen; row.getCellPtr(2).* = screen.cursor.pen;
// Get our run iterator // 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 it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }), null, null);
var count: usize = 0; var count: usize = 0;
while (try it.next(alloc)) |run| { while (try it.next(alloc)) |run| {
@ -274,7 +279,7 @@ test "shape" {
try screen.testWriteString(buf[0..buf_idx]); try screen.testWriteString(buf[0..buf_idx]);
// Get our run iterator // 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 it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }), null, null);
var count: usize = 0; var count: usize = 0;
while (try it.next(alloc)) |run| { while (try it.next(alloc)) |run| {
@ -297,7 +302,7 @@ test "shape inconsolata ligs" {
defer screen.deinit(); defer screen.deinit();
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 it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }), null, null);
var count: usize = 0; var count: usize = 0;
while (try it.next(alloc)) |run| { while (try it.next(alloc)) |run| {
@ -314,7 +319,7 @@ test "shape inconsolata ligs" {
defer screen.deinit(); defer screen.deinit();
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 it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }), null, null);
var count: usize = 0; var count: usize = 0;
while (try it.next(alloc)) |run| { while (try it.next(alloc)) |run| {
@ -339,7 +344,7 @@ test "shape emoji width" {
defer screen.deinit(); defer screen.deinit();
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 it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }), null, null);
var count: usize = 0; var count: usize = 0;
while (try it.next(alloc)) |run| { while (try it.next(alloc)) |run| {
@ -373,7 +378,7 @@ test "shape emoji width long" {
try screen.testWriteString(buf[0..buf_idx]); try screen.testWriteString(buf[0..buf_idx]);
// Get our run iterator // 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 it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }), null, null);
var count: usize = 0; var count: usize = 0;
while (try it.next(alloc)) |run| { while (try it.next(alloc)) |run| {
@ -404,7 +409,7 @@ test "shape variation selector VS15" {
try screen.testWriteString(buf[0..buf_idx]); try screen.testWriteString(buf[0..buf_idx]);
// Get our run iterator // 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 it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }), null, null);
var count: usize = 0; var count: usize = 0;
while (try it.next(alloc)) |run| { while (try it.next(alloc)) |run| {
@ -435,7 +440,7 @@ test "shape variation selector VS16" {
try screen.testWriteString(buf[0..buf_idx]); try screen.testWriteString(buf[0..buf_idx]);
// Get our run iterator // 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 it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }), null, null);
var count: usize = 0; var count: usize = 0;
while (try it.next(alloc)) |run| { while (try it.next(alloc)) |run| {
@ -463,7 +468,7 @@ test "shape with empty cells in between" {
try screen.testWriteString("B"); try screen.testWriteString("B");
// Get our run iterator // 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 it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }), null, null);
var count: usize = 0; var count: usize = 0;
while (try it.next(alloc)) |run| { while (try it.next(alloc)) |run| {
@ -495,7 +500,7 @@ test "shape Chinese characters" {
try screen.testWriteString(buf[0..buf_idx]); try screen.testWriteString(buf[0..buf_idx]);
// Get our run iterator // 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 it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }), null, null);
var count: usize = 0; var count: usize = 0;
while (try it.next(alloc)) |run| { while (try it.next(alloc)) |run| {
@ -536,7 +541,7 @@ test "shape box glyphs" {
try screen.testWriteString(buf[0..buf_idx]); try screen.testWriteString(buf[0..buf_idx]);
// Get our run iterator // 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 it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }), null, null);
var count: usize = 0; var count: usize = 0;
while (try it.next(alloc)) |run| { while (try it.next(alloc)) |run| {
@ -567,7 +572,7 @@ test "shape selection boundary" {
// Full line selection // Full line selection
{ {
// Get our run iterator // Get our run iterator
var shaper = testdata.shaper; var shaper = &testdata.shaper;
var it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }), .{ var it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }), .{
.start = .{ .x = 0, .y = 0 }, .start = .{ .x = 0, .y = 0 },
.end = .{ .x = screen.cols - 1, .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 // Offset x, goes to end of line selection
{ {
// Get our run iterator // Get our run iterator
var shaper = testdata.shaper; var shaper = &testdata.shaper;
var it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }), .{ var it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }), .{
.start = .{ .x = 2, .y = 0 }, .start = .{ .x = 2, .y = 0 },
.end = .{ .x = screen.cols - 1, .y = 0 }, .end = .{ .x = screen.cols - 1, .y = 0 },
@ -599,7 +604,7 @@ test "shape selection boundary" {
// Offset x, starts at beginning of line // Offset x, starts at beginning of line
{ {
// Get our run iterator // Get our run iterator
var shaper = testdata.shaper; var shaper = &testdata.shaper;
var it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }), .{ var it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }), .{
.start = .{ .x = 0, .y = 0 }, .start = .{ .x = 0, .y = 0 },
.end = .{ .x = 3, .y = 0 }, .end = .{ .x = 3, .y = 0 },
@ -615,7 +620,7 @@ test "shape selection boundary" {
// Selection only subset of line // Selection only subset of line
{ {
// Get our run iterator // Get our run iterator
var shaper = testdata.shaper; var shaper = &testdata.shaper;
var it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }), .{ var it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }), .{
.start = .{ .x = 1, .y = 0 }, .start = .{ .x = 1, .y = 0 },
.end = .{ .x = 3, .y = 0 }, .end = .{ .x = 3, .y = 0 },
@ -631,7 +636,7 @@ test "shape selection boundary" {
// Selection only one character // Selection only one character
{ {
// Get our run iterator // Get our run iterator
var shaper = testdata.shaper; var shaper = &testdata.shaper;
var it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }), .{ var it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }), .{
.start = .{ .x = 1, .y = 0 }, .start = .{ .x = 1, .y = 0 },
.end = .{ .x = 1, .y = 0 }, .end = .{ .x = 1, .y = 0 },
@ -660,7 +665,7 @@ test "shape cursor boundary" {
// No cursor is full line // No cursor is full line
{ {
// Get our run iterator // 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 it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }), null, null);
var count: usize = 0; var count: usize = 0;
while (try it.next(alloc)) |run| { while (try it.next(alloc)) |run| {
@ -673,7 +678,7 @@ test "shape cursor boundary" {
// Cursor at index 0 is two runs // Cursor at index 0 is two runs
{ {
// Get our run iterator // 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 it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }), null, 0);
var count: usize = 0; var count: usize = 0;
while (try it.next(alloc)) |run| { while (try it.next(alloc)) |run| {
@ -686,7 +691,7 @@ test "shape cursor boundary" {
// Cursor at index 1 is three runs // Cursor at index 1 is three runs
{ {
// Get our run iterator // 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 it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }), null, 1);
var count: usize = 0; var count: usize = 0;
while (try it.next(alloc)) |run| { while (try it.next(alloc)) |run| {
@ -699,7 +704,7 @@ test "shape cursor boundary" {
// Cursor at last col is two runs // Cursor at last col is two runs
{ {
// Get our run iterator // 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 it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }), null, 9);
var count: usize = 0; var count: usize = 0;
while (try it.next(alloc)) |run| { while (try it.next(alloc)) |run| {
@ -725,7 +730,7 @@ test "shape cursor boundary and colored emoji" {
// No cursor is full line // No cursor is full line
{ {
// Get our run iterator // 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 it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }), null, null);
var count: usize = 0; var count: usize = 0;
while (try it.next(alloc)) |run| { while (try it.next(alloc)) |run| {
@ -738,7 +743,7 @@ test "shape cursor boundary and colored emoji" {
// Cursor on emoji does not split it // Cursor on emoji does not split it
{ {
// Get our run iterator // 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 it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }), null, 0);
var count: usize = 0; var count: usize = 0;
while (try it.next(alloc)) |run| { while (try it.next(alloc)) |run| {
@ -749,7 +754,7 @@ test "shape cursor boundary and colored emoji" {
} }
{ {
// Get our run iterator // 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 it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }), null, 1);
var count: usize = 0; var count: usize = 0;
while (try it.next(alloc)) |run| { while (try it.next(alloc)) |run| {
@ -773,7 +778,7 @@ test "shape cell attribute change" {
defer screen.deinit(); defer screen.deinit();
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 it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }), null, null);
var count: usize = 0; var count: usize = 0;
while (try it.next(alloc)) |run| { while (try it.next(alloc)) |run| {
@ -791,7 +796,7 @@ test "shape cell attribute change" {
screen.cursor.pen.attrs.bold = true; screen.cursor.pen.attrs.bold = true;
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 it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }), null, null);
var count: usize = 0; var count: usize = 0;
while (try it.next(alloc)) |run| { 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 }; screen.cursor.pen.fg = .{ .r = 3, .g = 2, .b = 1 };
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 it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }), null, null);
var count: usize = 0; var count: usize = 0;
while (try it.next(alloc)) |run| { 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 }; screen.cursor.pen.bg = .{ .r = 3, .g = 2, .b = 1 };
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 it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }), null, null);
var count: usize = 0; var count: usize = 0;
while (try it.next(alloc)) |run| { while (try it.next(alloc)) |run| {
@ -850,7 +855,7 @@ test "shape cell attribute change" {
try screen.testWriteString(">"); try screen.testWriteString(">");
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 it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }), null, null);
var count: usize = 0; var count: usize = 0;
while (try it.next(alloc)) |run| { while (try it.next(alloc)) |run| {
@ -866,13 +871,11 @@ const TestShaper = struct {
shaper: Shaper, shaper: Shaper,
cache: *GroupCache, cache: *GroupCache,
lib: Library, lib: Library,
cell_buf: []font.shape.Cell,
pub fn deinit(self: *TestShaper) void { pub fn deinit(self: *TestShaper) void {
self.shaper.deinit(); self.shaper.deinit();
self.cache.deinit(self.alloc); self.cache.deinit(self.alloc);
self.alloc.destroy(self.cache); self.alloc.destroy(self.cache);
self.alloc.free(self.cell_buf);
self.lib.deinit(); 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 }) }); _ = try cache_ptr.group.addFace(.regular, .{ .loaded = try Face.init(lib, testEmojiText, .{ .points = 12 }) });
var cell_buf = try alloc.alloc(font.shape.Cell, 80); var shaper = try Shaper.init(alloc, .{});
errdefer alloc.free(cell_buf);
var shaper = try Shaper.init(alloc, .{ .cell_buf = cell_buf });
errdefer shaper.deinit(); errdefer shaper.deinit();
return TestShaper{ return TestShaper{
@ -928,6 +928,5 @@ fn testShaper(alloc: Allocator) !TestShaper {
.shaper = shaper, .shaper = shaper,
.cache = cache_ptr, .cache = cache_ptr,
.lib = lib, .lib = lib,
.cell_buf = cell_buf,
}; };
} }

View File

@ -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 // 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 // a width of 160 which is a common width for modern screens to help
// avoid allocations later. // 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, .{ var font_shaper = try font.Shaper.init(alloc, .{
.cell_buf = shape_buf,
.features = options.config.font_features.items, .features = options.config.font_features.items,
}); });
errdefer font_shaper.deinit(); errdefer font_shaper.deinit();
@ -288,7 +285,6 @@ pub fn deinit(self: *Metal) void {
self.cells_bg.deinit(self.alloc); self.cells_bg.deinit(self.alloc);
self.font_shaper.deinit(); self.font_shaper.deinit();
self.alloc.free(self.font_shaper.cell_buf);
self.config.deinit(); 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; if (std.meta.eql(self.cell_size, new_cell_size)) return;
self.cell_size = new_cell_size; 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 // Set the sprite font up
self.font_group.group.sprite = font.sprite.Face{ self.font_group.group.sprite = font.sprite.Face{
.width = self.cell_size.width, .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. // easier and rare enough to not cause performance issues.
{ {
var font_shaper = try font.Shaper.init(self.alloc, .{ var font_shaper = try font.Shaper.init(self.alloc, .{
.cell_buf = self.font_shaper.cell_buf,
.features = config.font_features.items, .features = config.font_features.items,
}); });
errdefer font_shaper.deinit(); errdefer font_shaper.deinit();
@ -1033,13 +1020,6 @@ pub fn setScreenSize(
.{}); .{});
const padded_dim = dim.subPadding(padding); 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 // Set the size of the drawable surface to the bounds
self.swapchain.setProperty("drawableSize", macos.graphics.Size{ self.swapchain.setProperty("drawableSize", macos.graphics.Size{
.width = @floatFromInt(dim.width), .width = @floatFromInt(dim.width),

View File

@ -280,10 +280,7 @@ pub const DerivedConfig = struct {
pub fn init(alloc: Allocator, options: renderer.Options) !OpenGL { pub fn init(alloc: Allocator, options: renderer.Options) !OpenGL {
// Create the initial font shaper // 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, .{ var shaper = try font.Shaper.init(alloc, .{
.cell_buf = shape_buf,
.features = options.config.font_features.items, .features = options.config.font_features.items,
}); });
errdefer shaper.deinit(); errdefer shaper.deinit();
@ -318,7 +315,6 @@ pub fn init(alloc: Allocator, options: renderer.Options) !OpenGL {
pub fn deinit(self: *OpenGL) void { pub fn deinit(self: *OpenGL) void {
self.font_shaper.deinit(); self.font_shaper.deinit();
self.alloc.free(self.font_shaper.cell_buf);
if (self.gl_state) |*v| v.deinit(); 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; if (std.meta.eql(self.cell_size, new_cell_size)) return;
self.cell_size = new_cell_size; 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. // Notify the window that the cell size changed.
_ = self.surface_mailbox.push(.{ _ = self.surface_mailbox.push(.{
.cell_size = new_cell_size, .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. // easier and rare enough to not cause performance issues.
{ {
var font_shaper = try font.Shaper.init(self.alloc, .{ var font_shaper = try font.Shaper.init(self.alloc, .{
.cell_buf = self.font_shaper.cell_buf,
.features = config.font_features.items, .features = config.font_features.items,
}); });
errdefer font_shaper.deinit(); errdefer font_shaper.deinit();
@ -1264,12 +1250,6 @@ pub fn setScreenSize(
self.padding.explicit, 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 // Defer our OpenGL updates
self.deferred_screen_size = .{ .size = dim }; self.deferred_screen_size = .{ .size = dim };
} }