font/shaper: new API

This commit is contained in:
Mitchell Hashimoto
2024-03-08 09:58:19 -08:00
parent efe037bb9f
commit 05470bb36a
3 changed files with 149 additions and 108 deletions

View File

@ -1047,103 +1047,133 @@ test "shape cursor boundary and colored emoji" {
}
}
// test "shape cell attribute change" {
// const testing = std.testing;
// const alloc = testing.allocator;
//
// var testdata = try testShaper(alloc);
// defer testdata.deinit();
//
// // Plain >= should shape into 1 run
// {
// var screen = try terminal.Screen.init(alloc, 3, 10, 0);
// defer screen.deinit();
// try screen.testWriteString(">=");
//
// 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| {
// count += 1;
// _ = try shaper.shape(run);
// }
// try testing.expectEqual(@as(usize, 1), count);
// }
//
// // Bold vs regular should split
// {
// var screen = try terminal.Screen.init(alloc, 3, 10, 0);
// defer screen.deinit();
// try screen.testWriteString(">");
// screen.cursor.pen.attrs.bold = true;
// try screen.testWriteString("=");
//
// 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| {
// count += 1;
// _ = try shaper.shape(run);
// }
// try testing.expectEqual(@as(usize, 2), count);
// }
//
// // Changing fg color should split
// {
// var screen = try terminal.Screen.init(alloc, 3, 10, 0);
// defer screen.deinit();
// screen.cursor.pen.fg = .{ .rgb = .{ .r = 1, .g = 2, .b = 3 } };
// try screen.testWriteString(">");
// screen.cursor.pen.fg = .{ .rgb = .{ .r = 3, .g = 2, .b = 1 } };
// try screen.testWriteString("=");
//
// 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| {
// count += 1;
// _ = try shaper.shape(run);
// }
// try testing.expectEqual(@as(usize, 2), count);
// }
//
// // Changing bg color should split
// {
// var screen = try terminal.Screen.init(alloc, 3, 10, 0);
// defer screen.deinit();
// screen.cursor.pen.bg = .{ .rgb = .{ .r = 1, .g = 2, .b = 3 } };
// try screen.testWriteString(">");
// screen.cursor.pen.bg = .{ .rgb = .{ .r = 3, .g = 2, .b = 1 } };
// try screen.testWriteString("=");
//
// 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| {
// count += 1;
// _ = try shaper.shape(run);
// }
// try testing.expectEqual(@as(usize, 2), count);
// }
//
// // Same bg color should not split
// {
// var screen = try terminal.Screen.init(alloc, 3, 10, 0);
// defer screen.deinit();
// screen.cursor.pen.bg = .{ .rgb = .{ .r = 1, .g = 2, .b = 3 } };
// try screen.testWriteString(">");
// try screen.testWriteString("=");
//
// 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| {
// count += 1;
// _ = try shaper.shape(run);
// }
// try testing.expectEqual(@as(usize, 1), count);
// }
// }
test "shape cell attribute change" {
const testing = std.testing;
const alloc = testing.allocator;
var testdata = try testShaper(alloc);
defer testdata.deinit();
// Plain >= should shape into 1 run
{
var screen = try terminal.Screen.init(alloc, 10, 3, 0);
defer screen.deinit();
try screen.testWriteString(">=");
var shaper = &testdata.shaper;
var it = shaper.runIterator(
testdata.cache,
&screen,
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
null,
null,
);
var count: usize = 0;
while (try it.next(alloc)) |run| {
count += 1;
_ = try shaper.shape(run);
}
try testing.expectEqual(@as(usize, 1), count);
}
// Bold vs regular should split
{
var screen = try terminal.Screen.init(alloc, 3, 10, 0);
defer screen.deinit();
try screen.testWriteString(">");
try screen.setAttribute(.{ .bold = {} });
try screen.testWriteString("=");
var shaper = &testdata.shaper;
var it = shaper.runIterator(
testdata.cache,
&screen,
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
null,
null,
);
var count: usize = 0;
while (try it.next(alloc)) |run| {
count += 1;
_ = try shaper.shape(run);
}
try testing.expectEqual(@as(usize, 2), count);
}
// Changing fg color should split
{
var screen = try terminal.Screen.init(alloc, 3, 10, 0);
defer screen.deinit();
try screen.setAttribute(.{ .direct_color_fg = .{ .r = 1, .g = 2, .b = 3 } });
try screen.testWriteString(">");
try screen.setAttribute(.{ .direct_color_fg = .{ .r = 3, .g = 2, .b = 1 } });
try screen.testWriteString("=");
var shaper = &testdata.shaper;
var it = shaper.runIterator(
testdata.cache,
&screen,
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
null,
null,
);
var count: usize = 0;
while (try it.next(alloc)) |run| {
count += 1;
_ = try shaper.shape(run);
}
try testing.expectEqual(@as(usize, 2), count);
}
// Changing bg color should split
{
var screen = try terminal.Screen.init(alloc, 3, 10, 0);
defer screen.deinit();
try screen.setAttribute(.{ .direct_color_bg = .{ .r = 1, .g = 2, .b = 3 } });
try screen.testWriteString(">");
try screen.setAttribute(.{ .direct_color_bg = .{ .r = 3, .g = 2, .b = 1 } });
try screen.testWriteString("=");
var shaper = &testdata.shaper;
var it = shaper.runIterator(
testdata.cache,
&screen,
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
null,
null,
);
var count: usize = 0;
while (try it.next(alloc)) |run| {
count += 1;
_ = try shaper.shape(run);
}
try testing.expectEqual(@as(usize, 2), count);
}
// Same bg color should not split
{
var screen = try terminal.Screen.init(alloc, 3, 10, 0);
defer screen.deinit();
try screen.setAttribute(.{ .direct_color_bg = .{ .r = 1, .g = 2, .b = 3 } });
try screen.testWriteString(">");
try screen.testWriteString("=");
var shaper = &testdata.shaper;
var it = shaper.runIterator(
testdata.cache,
&screen,
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
null,
null,
);
var count: usize = 0;
while (try it.next(alloc)) |run| {
count += 1;
_ = try shaper.shape(run);
}
try testing.expectEqual(@as(usize, 1), count);
}
}
const TestShaper = struct {
alloc: Allocator,

View File

@ -54,6 +54,9 @@ pub const RunIterator = struct {
// Allow the hook to prepare
try self.hooks.prepare();
// Let's get our style that we'll expect for the run.
const style = self.row.style(&cells[0]);
// Go through cell by cell and accumulate while we build our run.
var j: usize = self.i;
while (j < max) : (j += 1) {
@ -92,14 +95,13 @@ pub const RunIterator = struct {
// Text runs break when font styles change so we need to get
// the proper style.
const style: font.Style = style: {
// TODO(paged-terminal)
// if (cell.attrs.bold) {
// if (cell.attrs.italic) break :style .bold_italic;
// break :style .bold;
// }
//
// if (cell.attrs.italic) break :style .italic;
const font_style: font.Style = style: {
if (style.flags.bold) {
if (style.flags.italic) break :style .bold_italic;
break :style .bold;
}
if (style.flags.italic) break :style .italic;
break :style .regular;
};
@ -166,7 +168,7 @@ pub const RunIterator = struct {
if (try self.indexForCell(
alloc,
cell,
style,
font_style,
presentation,
)) |idx| break :font_info .{ .idx = idx };
@ -175,7 +177,7 @@ pub const RunIterator = struct {
if (try self.group.indexForCodepoint(
alloc,
0xFFFD, // replacement char
style,
font_style,
presentation,
)) |idx| break :font_info .{ .idx = idx, .fallback = 0xFFFD };
@ -183,7 +185,7 @@ pub const RunIterator = struct {
if (try self.group.indexForCodepoint(
alloc,
' ',
style,
font_style,
presentation,
)) |idx| break :font_info .{ .idx = idx, .fallback = ' ' };

View File

@ -2059,6 +2059,15 @@ pub const Pin = struct {
return self.page.data.lookupGrapheme(cell);
}
/// Returns the style for the given cell in this pin.
pub fn style(self: Pin, cell: *pagepkg.Cell) stylepkg.Style {
if (cell.style_id == stylepkg.default_id) return .{};
return self.page.data.styles.lookupId(
self.page.data.memory,
cell.style_id,
).?.*;
}
/// Iterators. These are the same as PageList iterator funcs but operate
/// on pins rather than points. This is MUCH more efficient than calling
/// pointFromPin and building up the iterator from points.