font/shaper: more tests passing

This commit is contained in:
Mitchell Hashimoto
2024-03-08 09:40:18 -08:00
parent e3230cf1e6
commit 34200a3e83
2 changed files with 350 additions and 252 deletions

View File

@ -84,6 +84,7 @@ pub const Shaper = struct {
pub fn runIterator( pub fn runIterator(
self: *Shaper, self: *Shaper,
group: *GroupCache, group: *GroupCache,
screen: *const terminal.Screen,
row: terminal.Pin, row: terminal.Pin,
selection: ?terminal.Selection, selection: ?terminal.Selection,
cursor_x: ?usize, cursor_x: ?usize,
@ -91,6 +92,7 @@ pub const Shaper = struct {
return .{ return .{
.hooks = .{ .shaper = self }, .hooks = .{ .shaper = self },
.group = group, .group = group,
.screen = screen,
.row = row, .row = row,
.selection = selection, .selection = selection,
.cursor_x = cursor_x, .cursor_x = cursor_x,
@ -250,6 +252,7 @@ test "run iterator" {
var shaper = &testdata.shaper; var shaper = &testdata.shaper;
var it = shaper.runIterator( var it = shaper.runIterator(
testdata.cache, testdata.cache,
&screen,
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?, screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
null, null,
null, null,
@ -268,6 +271,7 @@ test "run iterator" {
var shaper = &testdata.shaper; var shaper = &testdata.shaper;
var it = shaper.runIterator( var it = shaper.runIterator(
testdata.cache, testdata.cache,
&screen,
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?, screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
null, null,
null, null,
@ -287,6 +291,7 @@ test "run iterator" {
var shaper = &testdata.shaper; var shaper = &testdata.shaper;
var it = shaper.runIterator( var it = shaper.runIterator(
testdata.cache, testdata.cache,
&screen,
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?, screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
null, null,
null, null,
@ -337,36 +342,42 @@ test "run iterator" {
// try testing.expectEqual(@as(usize, 1), count); // try testing.expectEqual(@as(usize, 1), count);
// } // }
// } // }
//
// test "shape" { test "shape" {
// const testing = std.testing; const testing = std.testing;
// const alloc = testing.allocator; const alloc = testing.allocator;
//
// var testdata = try testShaper(alloc); var testdata = try testShaper(alloc);
// defer testdata.deinit(); defer testdata.deinit();
//
// var buf: [32]u8 = undefined; var buf: [32]u8 = undefined;
// var buf_idx: usize = 0; var buf_idx: usize = 0;
// buf_idx += try std.unicode.utf8Encode(0x1F44D, buf[buf_idx..]); // Thumbs up plain buf_idx += try std.unicode.utf8Encode(0x1F44D, buf[buf_idx..]); // Thumbs up plain
// buf_idx += try std.unicode.utf8Encode(0x1F44D, buf[buf_idx..]); // Thumbs up plain buf_idx += try std.unicode.utf8Encode(0x1F44D, buf[buf_idx..]); // Thumbs up plain
// buf_idx += try std.unicode.utf8Encode(0x1F3FD, buf[buf_idx..]); // Medium skin tone buf_idx += try std.unicode.utf8Encode(0x1F3FD, buf[buf_idx..]); // Medium skin tone
//
// // Make a screen with some data // Make a screen with some data
// var screen = try terminal.Screen.init(alloc, 3, 10, 0); var screen = try terminal.Screen.init(alloc, 10, 3, 0);
// defer screen.deinit(); defer screen.deinit();
// 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(
// var count: usize = 0; testdata.cache,
// while (try it.next(alloc)) |run| { &screen,
// count += 1; screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
// try testing.expectEqual(@as(u32, 3), shaper.hb_buf.getLength()); null,
// _ = try shaper.shape(run); null,
// } );
// try testing.expectEqual(@as(usize, 1), count); var count: usize = 0;
// } while (try it.next(alloc)) |run| {
count += 1;
try testing.expectEqual(@as(u32, 3), shaper.hb_buf.getLength());
_ = try shaper.shape(run);
}
try testing.expectEqual(@as(usize, 1), count);
}
test "shape inconsolata ligs" { test "shape inconsolata ligs" {
const testing = std.testing; const testing = std.testing;
@ -383,6 +394,7 @@ test "shape inconsolata ligs" {
var shaper = &testdata.shaper; var shaper = &testdata.shaper;
var it = shaper.runIterator( var it = shaper.runIterator(
testdata.cache, testdata.cache,
&screen,
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?, screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
null, null,
null, null,
@ -407,6 +419,7 @@ test "shape inconsolata ligs" {
var shaper = &testdata.shaper; var shaper = &testdata.shaper;
var it = shaper.runIterator( var it = shaper.runIterator(
testdata.cache, testdata.cache,
&screen,
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?, screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
null, null,
null, null,
@ -440,6 +453,7 @@ test "shape monaspace ligs" {
var shaper = &testdata.shaper; var shaper = &testdata.shaper;
var it = shaper.runIterator( var it = shaper.runIterator(
testdata.cache, testdata.cache,
&screen,
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?, screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
null, null,
null, null,
@ -473,6 +487,7 @@ test "shape emoji width" {
var shaper = &testdata.shaper; var shaper = &testdata.shaper;
var it = shaper.runIterator( var it = shaper.runIterator(
testdata.cache, testdata.cache,
&screen,
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?, screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
null, null,
null, null,
@ -512,6 +527,7 @@ test "shape emoji width long" {
var shaper = &testdata.shaper; var shaper = &testdata.shaper;
var it = shaper.runIterator( var it = shaper.runIterator(
testdata.cache, testdata.cache,
&screen,
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?, screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
null, null,
null, null,
@ -550,6 +566,7 @@ test "shape variation selector VS15" {
var shaper = &testdata.shaper; var shaper = &testdata.shaper;
var it = shaper.runIterator( var it = shaper.runIterator(
testdata.cache, testdata.cache,
&screen,
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?, screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
null, null,
null, null,
@ -586,6 +603,7 @@ test "shape variation selector VS16" {
var shaper = &testdata.shaper; var shaper = &testdata.shaper;
var it = shaper.runIterator( var it = shaper.runIterator(
testdata.cache, testdata.cache,
&screen,
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?, screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
null, null,
null, null,
@ -619,6 +637,7 @@ test "shape with empty cells in between" {
var shaper = &testdata.shaper; var shaper = &testdata.shaper;
var it = shaper.runIterator( var it = shaper.runIterator(
testdata.cache, testdata.cache,
&screen,
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?, screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
null, null,
null, null,
@ -656,6 +675,7 @@ test "shape Chinese characters" {
var shaper = &testdata.shaper; var shaper = &testdata.shaper;
var it = shaper.runIterator( var it = shaper.runIterator(
testdata.cache, testdata.cache,
&screen,
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?, screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
null, null,
null, null,
@ -702,6 +722,7 @@ test "shape box glyphs" {
var shaper = &testdata.shaper; var shaper = &testdata.shaper;
var it = shaper.runIterator( var it = shaper.runIterator(
testdata.cache, testdata.cache,
&screen,
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?, screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
null, null,
null, null,
@ -720,214 +741,291 @@ test "shape box glyphs" {
try testing.expectEqual(@as(usize, 1), count); try testing.expectEqual(@as(usize, 1), count);
} }
// test "shape selection boundary" { test "shape selection boundary" {
// const testing = std.testing; const testing = std.testing;
// const alloc = testing.allocator; const alloc = testing.allocator;
//
// var testdata = try testShaper(alloc); var testdata = try testShaper(alloc);
// defer testdata.deinit(); defer testdata.deinit();
//
// // Make a screen with some data // Make a screen with some data
// var screen = try terminal.Screen.init(alloc, 3, 10, 0); var screen = try terminal.Screen.init(alloc, 10, 3, 0);
// defer screen.deinit(); defer screen.deinit();
// try screen.testWriteString("a1b2c3d4e5"); try screen.testWriteString("a1b2c3d4e5");
//
// // 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(
// .start = .{ .x = 0, .y = 0 }, testdata.cache,
// .end = .{ .x = screen.cols - 1, .y = 0 }, &screen,
// }, null); screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
// var count: usize = 0; terminal.Selection.init(
// while (try it.next(alloc)) |run| { screen.pages.pin(.{ .active = .{ .x = 0, .y = 0 } }).?,
// count += 1; screen.pages.pin(.{ .active = .{ .x = screen.pages.cols - 1, .y = 0 } }).?,
// _ = try shaper.shape(run); false,
// } ),
// try testing.expectEqual(@as(usize, 1), count); null,
// } );
// var count: usize = 0;
// // Offset x, goes to end of line selection while (try it.next(alloc)) |run| {
// { count += 1;
// // Get our run iterator _ = try shaper.shape(run);
// var shaper = &testdata.shaper; }
// var it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }), .{ try testing.expectEqual(@as(usize, 1), count);
// .start = .{ .x = 2, .y = 0 }, }
// .end = .{ .x = screen.cols - 1, .y = 0 },
// }, null); // Offset x, goes to end of line selection
// var count: usize = 0; {
// while (try it.next(alloc)) |run| { // Get our run iterator
// count += 1; var shaper = &testdata.shaper;
// _ = try shaper.shape(run); var it = shaper.runIterator(
// } testdata.cache,
// try testing.expectEqual(@as(usize, 2), count); &screen,
// } screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
// terminal.Selection.init(
// // Offset x, starts at beginning of line screen.pages.pin(.{ .active = .{ .x = 2, .y = 0 } }).?,
// { screen.pages.pin(.{ .active = .{ .x = screen.pages.cols - 1, .y = 0 } }).?,
// // Get our run iterator false,
// var shaper = &testdata.shaper; ),
// var it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }), .{ null,
// .start = .{ .x = 0, .y = 0 }, );
// .end = .{ .x = 3, .y = 0 }, var count: usize = 0;
// }, null); while (try it.next(alloc)) |run| {
// var count: usize = 0; count += 1;
// while (try it.next(alloc)) |run| { _ = try shaper.shape(run);
// count += 1; }
// _ = try shaper.shape(run); try testing.expectEqual(@as(usize, 2), count);
// } }
// try testing.expectEqual(@as(usize, 2), count);
// } // Offset x, starts at beginning of line
// {
// // Selection only subset of line // Get our run iterator
// { var shaper = &testdata.shaper;
// // Get our run iterator var it = shaper.runIterator(
// var shaper = &testdata.shaper; testdata.cache,
// var it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }), .{ &screen,
// .start = .{ .x = 1, .y = 0 }, screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
// .end = .{ .x = 3, .y = 0 }, terminal.Selection.init(
// }, null); screen.pages.pin(.{ .active = .{ .x = 0, .y = 0 } }).?,
// var count: usize = 0; screen.pages.pin(.{ .active = .{ .x = 3, .y = 0 } }).?,
// while (try it.next(alloc)) |run| { false,
// count += 1; ),
// _ = try shaper.shape(run); null,
// } );
// try testing.expectEqual(@as(usize, 3), count); var count: usize = 0;
// } while (try it.next(alloc)) |run| {
// count += 1;
// // Selection only one character _ = try shaper.shape(run);
// { }
// // Get our run iterator try testing.expectEqual(@as(usize, 2), count);
// var shaper = &testdata.shaper; }
// var it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }), .{
// .start = .{ .x = 1, .y = 0 }, // Selection only subset of line
// .end = .{ .x = 1, .y = 0 }, {
// }, null); // Get our run iterator
// var count: usize = 0; var shaper = &testdata.shaper;
// while (try it.next(alloc)) |run| { var it = shaper.runIterator(
// count += 1; testdata.cache,
// _ = try shaper.shape(run); &screen,
// } screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
// try testing.expectEqual(@as(usize, 3), count); terminal.Selection.init(
// } screen.pages.pin(.{ .active = .{ .x = 1, .y = 0 } }).?,
// } screen.pages.pin(.{ .active = .{ .x = 3, .y = 0 } }).?,
// false,
// test "shape cursor boundary" { ),
// const testing = std.testing; null,
// const alloc = testing.allocator; );
// var count: usize = 0;
// var testdata = try testShaper(alloc); while (try it.next(alloc)) |run| {
// defer testdata.deinit(); count += 1;
// _ = try shaper.shape(run);
// // Make a screen with some data }
// var screen = try terminal.Screen.init(alloc, 3, 10, 0); try testing.expectEqual(@as(usize, 3), count);
// defer screen.deinit(); }
// try screen.testWriteString("a1b2c3d4e5");
// // Selection only one character
// // 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(
// var it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }), null, null); testdata.cache,
// var count: usize = 0; &screen,
// while (try it.next(alloc)) |run| { screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
// count += 1; terminal.Selection.init(
// _ = try shaper.shape(run); screen.pages.pin(.{ .active = .{ .x = 1, .y = 0 } }).?,
// } screen.pages.pin(.{ .active = .{ .x = 1, .y = 0 } }).?,
// try testing.expectEqual(@as(usize, 1), count); false,
// } ),
// null,
// // Cursor at index 0 is two runs );
// { var count: usize = 0;
// // Get our run iterator while (try it.next(alloc)) |run| {
// var shaper = &testdata.shaper; count += 1;
// var it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }), null, 0); _ = try shaper.shape(run);
// var count: usize = 0; }
// while (try it.next(alloc)) |run| { try testing.expectEqual(@as(usize, 3), count);
// count += 1; }
// _ = try shaper.shape(run); }
// }
// try testing.expectEqual(@as(usize, 2), count); test "shape cursor boundary" {
// } const testing = std.testing;
// const alloc = testing.allocator;
// // Cursor at index 1 is three runs
// { var testdata = try testShaper(alloc);
// // Get our run iterator defer testdata.deinit();
// var shaper = &testdata.shaper;
// var it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }), null, 1); // Make a screen with some data
// var count: usize = 0; var screen = try terminal.Screen.init(alloc, 10, 3, 0);
// while (try it.next(alloc)) |run| { defer screen.deinit();
// count += 1; try screen.testWriteString("a1b2c3d4e5");
// _ = try shaper.shape(run);
// } // No cursor is full line
// try testing.expectEqual(@as(usize, 3), count); {
// } // Get our run iterator
// var shaper = &testdata.shaper;
// // Cursor at last col is two runs var it = shaper.runIterator(
// { testdata.cache,
// // Get our run iterator &screen,
// var shaper = &testdata.shaper; screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
// var it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }), null, 9); null,
// var count: usize = 0; null,
// while (try it.next(alloc)) |run| { );
// count += 1; var count: usize = 0;
// _ = try shaper.shape(run); while (try it.next(alloc)) |run| {
// } count += 1;
// try testing.expectEqual(@as(usize, 2), count); _ = try shaper.shape(run);
// } }
// } try testing.expectEqual(@as(usize, 1), count);
// }
// test "shape cursor boundary and colored emoji" {
// const testing = std.testing; // Cursor at index 0 is two runs
// const alloc = testing.allocator; {
// // Get our run iterator
// var testdata = try testShaper(alloc); var shaper = &testdata.shaper;
// defer testdata.deinit(); var it = shaper.runIterator(
// testdata.cache,
// // Make a screen with some data &screen,
// var screen = try terminal.Screen.init(alloc, 3, 10, 0); screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
// defer screen.deinit(); null,
// try screen.testWriteString("👍🏼"); 0,
// );
// // No cursor is full line var count: usize = 0;
// { while (try it.next(alloc)) |run| {
// // Get our run iterator count += 1;
// var shaper = &testdata.shaper; _ = try shaper.shape(run);
// var it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }), null, null); }
// var count: usize = 0; try testing.expectEqual(@as(usize, 2), count);
// while (try it.next(alloc)) |run| { }
// count += 1;
// _ = try shaper.shape(run); // Cursor at index 1 is three runs
// } {
// try testing.expectEqual(@as(usize, 1), count); // Get our run iterator
// } var shaper = &testdata.shaper;
// var it = shaper.runIterator(
// // Cursor on emoji does not split it testdata.cache,
// { &screen,
// // Get our run iterator screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
// var shaper = &testdata.shaper; null,
// var it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }), null, 0); 1,
// var count: usize = 0; );
// while (try it.next(alloc)) |run| { var count: usize = 0;
// count += 1; while (try it.next(alloc)) |run| {
// _ = try shaper.shape(run); count += 1;
// } _ = try shaper.shape(run);
// try testing.expectEqual(@as(usize, 1), count); }
// } try testing.expectEqual(@as(usize, 3), count);
// { }
// // Get our run iterator
// var shaper = &testdata.shaper; // Cursor at last col is two runs
// var it = shaper.runIterator(testdata.cache, screen.getRow(.{ .screen = 0 }), null, 1); {
// var count: usize = 0; // Get our run iterator
// while (try it.next(alloc)) |run| { var shaper = &testdata.shaper;
// count += 1; var it = shaper.runIterator(
// _ = try shaper.shape(run); testdata.cache,
// } &screen,
// try testing.expectEqual(@as(usize, 1), count); screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
// } null,
// } 9,
// );
var count: usize = 0;
while (try it.next(alloc)) |run| {
count += 1;
_ = try shaper.shape(run);
}
try testing.expectEqual(@as(usize, 2), count);
}
}
test "shape cursor boundary and colored emoji" {
const testing = std.testing;
const alloc = testing.allocator;
var testdata = try testShaper(alloc);
defer testdata.deinit();
// Make a screen with some data
var screen = try terminal.Screen.init(alloc, 3, 10, 0);
defer screen.deinit();
try screen.testWriteString("👍🏼");
// No cursor is full line
{
// Get our run iterator
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);
}
// Cursor on emoji does not split it
{
// Get our run iterator
var shaper = &testdata.shaper;
var it = shaper.runIterator(
testdata.cache,
&screen,
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
null,
0,
);
var count: usize = 0;
while (try it.next(alloc)) |run| {
count += 1;
_ = try shaper.shape(run);
}
try testing.expectEqual(@as(usize, 1), count);
}
{
// Get our run iterator
var shaper = &testdata.shaper;
var it = shaper.runIterator(
testdata.cache,
&screen,
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
null,
1,
);
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" { // test "shape cell attribute change" {
// const testing = std.testing; // const testing = std.testing;
// const alloc = testing.allocator; // const alloc = testing.allocator;

View File

@ -26,6 +26,7 @@ pub const TextRun = struct {
pub const RunIterator = struct { pub const RunIterator = struct {
hooks: font.Shaper.RunIteratorHook, hooks: font.Shaper.RunIteratorHook,
group: *font.GroupCache, group: *font.GroupCache,
screen: *const terminal.Screen,
row: terminal.Pin, row: terminal.Pin,
selection: ?terminal.Selection = null, selection: ?terminal.Selection = null,
cursor_x: ?usize = null, cursor_x: ?usize = null,
@ -61,20 +62,19 @@ pub const RunIterator = struct {
// If we have a selection and we're at a boundary point, then // If we have a selection and we're at a boundary point, then
// we break the run here. // we break the run here.
// TODO(paged-terminal) if (self.selection) |unordered_sel| {
// if (self.selection) |unordered_sel| { if (j > self.i) {
// if (j > self.i) { const sel = unordered_sel.ordered(self.screen, .forward);
// const sel = unordered_sel.ordered(.forward); const start_x = sel.start().x;
// const end_x = sel.end().x;
// if (sel.start.x > 0 and
// j == sel.start.x and if (start_x > 0 and
// self.row.graphemeBreak(sel.start.x)) break; j == start_x) break;
//
// if (sel.end.x > 0 and if (end_x > 0 and
// j == sel.end.x + 1 and j == end_x + 1) break;
// self.row.graphemeBreak(sel.end.x)) break; }
// } }
// }
// If we're a spacer, then we ignore it // If we're a spacer, then we ignore it
switch (cell.wide) { switch (cell.wide) {