mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-15 16:26:08 +03:00
font/shaper: new API
This commit is contained in:
@ -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,
|
||||
|
@ -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 = ' ' };
|
||||
|
||||
|
@ -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.
|
||||
|
Reference in New Issue
Block a user