mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-14 15:56:13 +03:00
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:
@ -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
|
||||||
|
@ -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,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -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),
|
||||||
|
@ -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 };
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user