From 05470bb36a11f5cbaefb141fbaa5f67a37dcb1bd Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 8 Mar 2024 09:58:19 -0800 Subject: [PATCH] font/shaper: new API --- src/font/shaper/harfbuzz.zig | 224 ++++++++++++++++++++--------------- src/font/shaper/run.zig | 24 ++-- src/terminal2/PageList.zig | 9 ++ 3 files changed, 149 insertions(+), 108 deletions(-) diff --git a/src/font/shaper/harfbuzz.zig b/src/font/shaper/harfbuzz.zig index 17a09c3ad..29a7e315f 100644 --- a/src/font/shaper/harfbuzz.zig +++ b/src/font/shaper/harfbuzz.zig @@ -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, diff --git a/src/font/shaper/run.zig b/src/font/shaper/run.zig index b98f0fd49..7a6c4e554 100644 --- a/src/font/shaper/run.zig +++ b/src/font/shaper/run.zig @@ -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 = ' ' }; diff --git a/src/terminal2/PageList.zig b/src/terminal2/PageList.zig index dc72bb881..fb4005991 100644 --- a/src/terminal2/PageList.zig +++ b/src/terminal2/PageList.zig @@ -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.