font/shaper: remove old pre-renderstate logic
parent
c892599385
commit
3d56a3a02b
|
|
@ -77,19 +77,7 @@ pub const RunOptions = struct {
|
|||
cells: std.MultiArrayList(terminal.RenderState.Cell).Slice = .empty,
|
||||
|
||||
/// The x boundaries of the selection in this row.
|
||||
selection2: ?[2]u16 = null,
|
||||
|
||||
/// The terminal screen to shape.
|
||||
screen: *const terminal.Screen,
|
||||
|
||||
/// The row within the screen to shape. This row must exist within
|
||||
/// screen; it is not validated.
|
||||
row: terminal.Pin,
|
||||
|
||||
/// The selection boundaries. This is used to break shaping on
|
||||
/// selection boundaries. This can be disabled by setting this to
|
||||
/// null.
|
||||
selection: ?terminal.Selection = null,
|
||||
selection: ?[2]u16 = null,
|
||||
|
||||
/// The cursor position within this row. This is used to break shaping
|
||||
/// on cursor boundaries. This can be disabled by setting this to
|
||||
|
|
|
|||
|
|
@ -644,8 +644,6 @@ test "run iterator" {
|
|||
var it = shaper.runIterator(.{
|
||||
.grid = testdata.grid,
|
||||
.cells = state.row_data.get(0).cells.slice(),
|
||||
.screen = undefined,
|
||||
.row = undefined,
|
||||
});
|
||||
var count: usize = 0;
|
||||
while (try it.next(alloc)) |_| count += 1;
|
||||
|
|
@ -669,8 +667,6 @@ test "run iterator" {
|
|||
var it = shaper.runIterator(.{
|
||||
.grid = testdata.grid,
|
||||
.cells = state.row_data.get(0).cells.slice(),
|
||||
.screen = undefined,
|
||||
.row = undefined,
|
||||
});
|
||||
var count: usize = 0;
|
||||
while (try it.next(alloc)) |_| count += 1;
|
||||
|
|
@ -695,8 +691,6 @@ test "run iterator" {
|
|||
var it = shaper.runIterator(.{
|
||||
.grid = testdata.grid,
|
||||
.cells = state.row_data.get(0).cells.slice(),
|
||||
.screen = undefined,
|
||||
.row = undefined,
|
||||
});
|
||||
var count: usize = 0;
|
||||
while (try it.next(alloc)) |_| count += 1;
|
||||
|
|
@ -722,8 +716,6 @@ test "run iterator" {
|
|||
var it = shaper.runIterator(.{
|
||||
.grid = testdata.grid,
|
||||
.cells = state.row_data.get(0).cells.slice(),
|
||||
.screen = undefined,
|
||||
.row = undefined,
|
||||
});
|
||||
var count: usize = 0;
|
||||
while (try it.next(alloc)) |_| count += 1;
|
||||
|
|
@ -776,8 +768,6 @@ test "run iterator: empty cells with background set" {
|
|||
var it = shaper.runIterator(.{
|
||||
.grid = testdata.grid,
|
||||
.cells = state.row_data.get(0).cells.slice(),
|
||||
.screen = undefined,
|
||||
.row = undefined,
|
||||
});
|
||||
{
|
||||
const run = (try it.next(alloc)).?;
|
||||
|
|
@ -818,8 +808,6 @@ test "shape" {
|
|||
var it = shaper.runIterator(.{
|
||||
.grid = testdata.grid,
|
||||
.cells = state.row_data.get(0).cells.slice(),
|
||||
.screen = undefined,
|
||||
.row = undefined,
|
||||
});
|
||||
var count: usize = 0;
|
||||
while (try it.next(alloc)) |run| {
|
||||
|
|
@ -859,8 +847,6 @@ test "shape nerd fonts" {
|
|||
var it = shaper.runIterator(.{
|
||||
.grid = testdata.grid,
|
||||
.cells = state.row_data.get(0).cells.slice(),
|
||||
.screen = undefined,
|
||||
.row = undefined,
|
||||
});
|
||||
var count: usize = 0;
|
||||
while (try it.next(alloc)) |run| {
|
||||
|
|
@ -893,8 +879,6 @@ test "shape inconsolata ligs" {
|
|||
var it = shaper.runIterator(.{
|
||||
.grid = testdata.grid,
|
||||
.cells = state.row_data.get(0).cells.slice(),
|
||||
.screen = undefined,
|
||||
.row = undefined,
|
||||
});
|
||||
var count: usize = 0;
|
||||
while (try it.next(alloc)) |run| {
|
||||
|
|
@ -924,8 +908,6 @@ test "shape inconsolata ligs" {
|
|||
var it = shaper.runIterator(.{
|
||||
.grid = testdata.grid,
|
||||
.cells = state.row_data.get(0).cells.slice(),
|
||||
.screen = undefined,
|
||||
.row = undefined,
|
||||
});
|
||||
var count: usize = 0;
|
||||
while (try it.next(alloc)) |run| {
|
||||
|
|
@ -963,8 +945,6 @@ test "shape monaspace ligs" {
|
|||
var it = shaper.runIterator(.{
|
||||
.grid = testdata.grid,
|
||||
.cells = state.row_data.get(0).cells.slice(),
|
||||
.screen = undefined,
|
||||
.row = undefined,
|
||||
});
|
||||
var count: usize = 0;
|
||||
while (try it.next(alloc)) |run| {
|
||||
|
|
@ -1003,8 +983,6 @@ test "shape left-replaced lig in last run" {
|
|||
var it = shaper.runIterator(.{
|
||||
.grid = testdata.grid,
|
||||
.cells = state.row_data.get(0).cells.slice(),
|
||||
.screen = undefined,
|
||||
.row = undefined,
|
||||
});
|
||||
var count: usize = 0;
|
||||
while (try it.next(alloc)) |run| {
|
||||
|
|
@ -1043,8 +1021,6 @@ test "shape left-replaced lig in early run" {
|
|||
var it = shaper.runIterator(.{
|
||||
.grid = testdata.grid,
|
||||
.cells = state.row_data.get(0).cells.slice(),
|
||||
.screen = undefined,
|
||||
.row = undefined,
|
||||
});
|
||||
|
||||
const run = (try it.next(alloc)).?;
|
||||
|
|
@ -1080,8 +1056,6 @@ test "shape U+3C9 with JB Mono" {
|
|||
var it = shaper.runIterator(.{
|
||||
.grid = testdata.grid,
|
||||
.cells = state.row_data.get(0).cells.slice(),
|
||||
.screen = undefined,
|
||||
.row = undefined,
|
||||
});
|
||||
|
||||
var run_count: usize = 0;
|
||||
|
|
@ -1119,8 +1093,6 @@ test "shape emoji width" {
|
|||
var it = shaper.runIterator(.{
|
||||
.grid = testdata.grid,
|
||||
.cells = state.row_data.get(0).cells.slice(),
|
||||
.screen = undefined,
|
||||
.row = undefined,
|
||||
});
|
||||
var count: usize = 0;
|
||||
while (try it.next(alloc)) |run| {
|
||||
|
|
@ -1172,8 +1144,6 @@ test "shape emoji width long" {
|
|||
var it = shaper.runIterator(.{
|
||||
.grid = testdata.grid,
|
||||
.cells = state.row_data.get(1).cells.slice(),
|
||||
.screen = undefined,
|
||||
.row = undefined,
|
||||
});
|
||||
var count: usize = 0;
|
||||
while (try it.next(alloc)) |run| {
|
||||
|
|
@ -1214,8 +1184,6 @@ test "shape variation selector VS15" {
|
|||
var it = shaper.runIterator(.{
|
||||
.grid = testdata.grid,
|
||||
.cells = state.row_data.get(0).cells.slice(),
|
||||
.screen = undefined,
|
||||
.row = undefined,
|
||||
});
|
||||
var count: usize = 0;
|
||||
while (try it.next(alloc)) |run| {
|
||||
|
|
@ -1255,8 +1223,6 @@ test "shape variation selector VS16" {
|
|||
var it = shaper.runIterator(.{
|
||||
.grid = testdata.grid,
|
||||
.cells = state.row_data.get(0).cells.slice(),
|
||||
.screen = undefined,
|
||||
.row = undefined,
|
||||
});
|
||||
var count: usize = 0;
|
||||
while (try it.next(alloc)) |run| {
|
||||
|
|
@ -1293,8 +1259,6 @@ test "shape with empty cells in between" {
|
|||
var it = shaper.runIterator(.{
|
||||
.grid = testdata.grid,
|
||||
.cells = state.row_data.get(0).cells.slice(),
|
||||
.screen = undefined,
|
||||
.row = undefined,
|
||||
});
|
||||
var count: usize = 0;
|
||||
while (try it.next(alloc)) |run| {
|
||||
|
|
@ -1337,8 +1301,6 @@ test "shape Chinese characters" {
|
|||
var it = shaper.runIterator(.{
|
||||
.grid = testdata.grid,
|
||||
.cells = state.row_data.get(0).cells.slice(),
|
||||
.screen = undefined,
|
||||
.row = undefined,
|
||||
});
|
||||
var count: usize = 0;
|
||||
while (try it.next(alloc)) |run| {
|
||||
|
|
@ -1386,8 +1348,6 @@ test "shape Devanagari string" {
|
|||
var it = shaper.runIterator(.{
|
||||
.grid = testdata.grid,
|
||||
.cells = state.row_data.get(0).cells.slice(),
|
||||
.screen = undefined,
|
||||
.row = undefined,
|
||||
});
|
||||
|
||||
const run = try it.next(alloc);
|
||||
|
|
@ -1436,8 +1396,6 @@ test "shape box glyphs" {
|
|||
var it = shaper.runIterator(.{
|
||||
.grid = testdata.grid,
|
||||
.cells = state.row_data.get(0).cells.slice(),
|
||||
.screen = undefined,
|
||||
.row = undefined,
|
||||
});
|
||||
var count: usize = 0;
|
||||
while (try it.next(alloc)) |run| {
|
||||
|
|
@ -1478,9 +1436,7 @@ test "shape selection boundary" {
|
|||
var it = shaper.runIterator(.{
|
||||
.grid = testdata.grid,
|
||||
.cells = state.row_data.get(0).cells.slice(),
|
||||
.screen = undefined,
|
||||
.row = undefined,
|
||||
.selection2 = .{ 0, @intCast(t.cols - 1) },
|
||||
.selection = .{ 0, @intCast(t.cols - 1) },
|
||||
});
|
||||
var count: usize = 0;
|
||||
while (try it.next(alloc)) |run| {
|
||||
|
|
@ -1497,9 +1453,7 @@ test "shape selection boundary" {
|
|||
var it = shaper.runIterator(.{
|
||||
.grid = testdata.grid,
|
||||
.cells = state.row_data.get(0).cells.slice(),
|
||||
.screen = undefined,
|
||||
.row = undefined,
|
||||
.selection2 = .{ 2, @intCast(t.cols - 1) },
|
||||
.selection = .{ 2, @intCast(t.cols - 1) },
|
||||
});
|
||||
var count: usize = 0;
|
||||
while (try it.next(alloc)) |run| {
|
||||
|
|
@ -1516,9 +1470,7 @@ test "shape selection boundary" {
|
|||
var it = shaper.runIterator(.{
|
||||
.grid = testdata.grid,
|
||||
.cells = state.row_data.get(0).cells.slice(),
|
||||
.screen = undefined,
|
||||
.row = undefined,
|
||||
.selection2 = .{ 0, 3 },
|
||||
.selection = .{ 0, 3 },
|
||||
});
|
||||
var count: usize = 0;
|
||||
while (try it.next(alloc)) |run| {
|
||||
|
|
@ -1535,9 +1487,7 @@ test "shape selection boundary" {
|
|||
var it = shaper.runIterator(.{
|
||||
.grid = testdata.grid,
|
||||
.cells = state.row_data.get(0).cells.slice(),
|
||||
.screen = undefined,
|
||||
.row = undefined,
|
||||
.selection2 = .{ 1, 3 },
|
||||
.selection = .{ 1, 3 },
|
||||
});
|
||||
var count: usize = 0;
|
||||
while (try it.next(alloc)) |run| {
|
||||
|
|
@ -1554,9 +1504,7 @@ test "shape selection boundary" {
|
|||
var it = shaper.runIterator(.{
|
||||
.grid = testdata.grid,
|
||||
.cells = state.row_data.get(0).cells.slice(),
|
||||
.screen = undefined,
|
||||
.row = undefined,
|
||||
.selection2 = .{ 1, 1 },
|
||||
.selection = .{ 1, 1 },
|
||||
});
|
||||
var count: usize = 0;
|
||||
while (try it.next(alloc)) |run| {
|
||||
|
|
@ -1593,8 +1541,6 @@ test "shape cursor boundary" {
|
|||
var it = shaper.runIterator(.{
|
||||
.grid = testdata.grid,
|
||||
.cells = state.row_data.get(0).cells.slice(),
|
||||
.screen = undefined,
|
||||
.row = undefined,
|
||||
});
|
||||
var count: usize = 0;
|
||||
while (try it.next(alloc)) |run| {
|
||||
|
|
@ -1612,8 +1558,6 @@ test "shape cursor boundary" {
|
|||
var it = shaper.runIterator(.{
|
||||
.grid = testdata.grid,
|
||||
.cells = state.row_data.get(0).cells.slice(),
|
||||
.screen = undefined,
|
||||
.row = undefined,
|
||||
.cursor_x = 0,
|
||||
});
|
||||
var count: usize = 0;
|
||||
|
|
@ -1630,8 +1574,6 @@ test "shape cursor boundary" {
|
|||
var it = shaper.runIterator(.{
|
||||
.grid = testdata.grid,
|
||||
.cells = state.row_data.get(0).cells.slice(),
|
||||
.screen = undefined,
|
||||
.row = undefined,
|
||||
});
|
||||
var count: usize = 0;
|
||||
while (try it.next(alloc)) |run| {
|
||||
|
|
@ -1650,8 +1592,6 @@ test "shape cursor boundary" {
|
|||
var it = shaper.runIterator(.{
|
||||
.grid = testdata.grid,
|
||||
.cells = state.row_data.get(0).cells.slice(),
|
||||
.screen = undefined,
|
||||
.row = undefined,
|
||||
.cursor_x = 1,
|
||||
});
|
||||
var count: usize = 0;
|
||||
|
|
@ -1668,8 +1608,6 @@ test "shape cursor boundary" {
|
|||
var it = shaper.runIterator(.{
|
||||
.grid = testdata.grid,
|
||||
.cells = state.row_data.get(0).cells.slice(),
|
||||
.screen = undefined,
|
||||
.row = undefined,
|
||||
});
|
||||
var count: usize = 0;
|
||||
while (try it.next(alloc)) |run| {
|
||||
|
|
@ -1687,8 +1625,6 @@ test "shape cursor boundary" {
|
|||
var it = shaper.runIterator(.{
|
||||
.grid = testdata.grid,
|
||||
.cells = state.row_data.get(0).cells.slice(),
|
||||
.screen = undefined,
|
||||
.row = undefined,
|
||||
.cursor_x = 9,
|
||||
});
|
||||
var count: usize = 0;
|
||||
|
|
@ -1705,8 +1641,6 @@ test "shape cursor boundary" {
|
|||
var it = shaper.runIterator(.{
|
||||
.grid = testdata.grid,
|
||||
.cells = state.row_data.get(0).cells.slice(),
|
||||
.screen = undefined,
|
||||
.row = undefined,
|
||||
});
|
||||
var count: usize = 0;
|
||||
while (try it.next(alloc)) |run| {
|
||||
|
|
@ -1744,8 +1678,6 @@ test "shape cursor boundary and colored emoji" {
|
|||
var it = shaper.runIterator(.{
|
||||
.grid = testdata.grid,
|
||||
.cells = state.row_data.get(0).cells.slice(),
|
||||
.screen = undefined,
|
||||
.row = undefined,
|
||||
});
|
||||
var count: usize = 0;
|
||||
while (try it.next(alloc)) |run| {
|
||||
|
|
@ -1762,8 +1694,6 @@ test "shape cursor boundary and colored emoji" {
|
|||
var it = shaper.runIterator(.{
|
||||
.grid = testdata.grid,
|
||||
.cells = state.row_data.get(0).cells.slice(),
|
||||
.screen = undefined,
|
||||
.row = undefined,
|
||||
.cursor_x = 0,
|
||||
});
|
||||
var count: usize = 0;
|
||||
|
|
@ -1779,8 +1709,6 @@ test "shape cursor boundary and colored emoji" {
|
|||
var it = shaper.runIterator(.{
|
||||
.grid = testdata.grid,
|
||||
.cells = state.row_data.get(0).cells.slice(),
|
||||
.screen = undefined,
|
||||
.row = undefined,
|
||||
});
|
||||
var count: usize = 0;
|
||||
while (try it.next(alloc)) |run| {
|
||||
|
|
@ -1795,8 +1723,6 @@ test "shape cursor boundary and colored emoji" {
|
|||
var it = shaper.runIterator(.{
|
||||
.grid = testdata.grid,
|
||||
.cells = state.row_data.get(0).cells.slice(),
|
||||
.screen = undefined,
|
||||
.row = undefined,
|
||||
.cursor_x = 1,
|
||||
});
|
||||
var count: usize = 0;
|
||||
|
|
@ -1812,8 +1738,6 @@ test "shape cursor boundary and colored emoji" {
|
|||
var it = shaper.runIterator(.{
|
||||
.grid = testdata.grid,
|
||||
.cells = state.row_data.get(0).cells.slice(),
|
||||
.screen = undefined,
|
||||
.row = undefined,
|
||||
});
|
||||
var count: usize = 0;
|
||||
while (try it.next(alloc)) |run| {
|
||||
|
|
@ -1848,8 +1772,6 @@ test "shape cell attribute change" {
|
|||
var it = shaper.runIterator(.{
|
||||
.grid = testdata.grid,
|
||||
.cells = state.row_data.get(0).cells.slice(),
|
||||
.screen = undefined,
|
||||
.row = undefined,
|
||||
});
|
||||
var count: usize = 0;
|
||||
while (try it.next(alloc)) |run| {
|
||||
|
|
@ -1878,8 +1800,6 @@ test "shape cell attribute change" {
|
|||
var it = shaper.runIterator(.{
|
||||
.grid = testdata.grid,
|
||||
.cells = state.row_data.get(0).cells.slice(),
|
||||
.screen = undefined,
|
||||
.row = undefined,
|
||||
});
|
||||
var count: usize = 0;
|
||||
while (try it.next(alloc)) |run| {
|
||||
|
|
@ -1911,8 +1831,6 @@ test "shape cell attribute change" {
|
|||
var it = shaper.runIterator(.{
|
||||
.grid = testdata.grid,
|
||||
.cells = state.row_data.get(0).cells.slice(),
|
||||
.screen = undefined,
|
||||
.row = undefined,
|
||||
});
|
||||
var count: usize = 0;
|
||||
while (try it.next(alloc)) |run| {
|
||||
|
|
@ -1944,8 +1862,6 @@ test "shape cell attribute change" {
|
|||
var it = shaper.runIterator(.{
|
||||
.grid = testdata.grid,
|
||||
.cells = state.row_data.get(0).cells.slice(),
|
||||
.screen = undefined,
|
||||
.row = undefined,
|
||||
});
|
||||
var count: usize = 0;
|
||||
while (try it.next(alloc)) |run| {
|
||||
|
|
@ -1975,8 +1891,6 @@ test "shape cell attribute change" {
|
|||
var it = shaper.runIterator(.{
|
||||
.grid = testdata.grid,
|
||||
.cells = state.row_data.get(0).cells.slice(),
|
||||
.screen = undefined,
|
||||
.row = undefined,
|
||||
});
|
||||
var count: usize = 0;
|
||||
while (try it.next(alloc)) |run| {
|
||||
|
|
@ -2020,8 +1934,6 @@ test "shape high plane sprite font codepoint" {
|
|||
var it = shaper.runIterator(.{
|
||||
.grid = testdata.grid,
|
||||
.cells = state.row_data.get(0).cells.slice(),
|
||||
.screen = undefined,
|
||||
.row = undefined,
|
||||
});
|
||||
// We should get one run
|
||||
const run = (try it.next(alloc)).?;
|
||||
|
|
|
|||
|
|
@ -45,273 +45,6 @@ pub const RunIterator = struct {
|
|||
i: usize = 0,
|
||||
|
||||
pub fn next(self: *RunIterator, alloc: Allocator) !?TextRun {
|
||||
if (self.opts.cells.len > 0) return try self.next2(alloc);
|
||||
|
||||
const cells = self.opts.row.cells(.all);
|
||||
|
||||
// Trim the right side of a row that might be empty
|
||||
const max: usize = max: {
|
||||
for (0..cells.len) |i| {
|
||||
const rev_i = cells.len - i - 1;
|
||||
if (!cells[rev_i].isEmpty()) break :max rev_i + 1;
|
||||
}
|
||||
|
||||
break :max 0;
|
||||
};
|
||||
|
||||
// Invisible cells don't have any glyphs rendered,
|
||||
// so we explicitly skip them in the shaping process.
|
||||
while (self.i < max and
|
||||
self.opts.row.style(&cells[self.i]).flags.invisible)
|
||||
{
|
||||
self.i += 1;
|
||||
}
|
||||
|
||||
// We're over at the max
|
||||
if (self.i >= max) return null;
|
||||
|
||||
// Track the font for our current run
|
||||
var current_font: font.Collection.Index = .{};
|
||||
|
||||
// Allow the hook to prepare
|
||||
try self.hooks.prepare();
|
||||
|
||||
// Initialize our hash for this run.
|
||||
var hasher = Hasher.init(0);
|
||||
|
||||
// Let's get our style that we'll expect for the run.
|
||||
const style = self.opts.row.style(&cells[self.i]);
|
||||
|
||||
// Go through cell by cell and accumulate while we build our run.
|
||||
var j: usize = self.i;
|
||||
while (j < max) : (j += 1) {
|
||||
// Use relative cluster positions (offset from run start) to make
|
||||
// the shaping cache position-independent. This ensures that runs
|
||||
// with identical content but different starting positions in the
|
||||
// row produce the same hash, enabling cache reuse.
|
||||
const cluster = j - self.i;
|
||||
const cell = &cells[j];
|
||||
|
||||
// If we have a selection and we're at a boundary point, then
|
||||
// we break the run here.
|
||||
if (self.opts.selection) |unordered_sel| {
|
||||
if (j > self.i) {
|
||||
const sel = unordered_sel.ordered(self.opts.screen, .forward);
|
||||
const start_x = sel.start().x;
|
||||
const end_x = sel.end().x;
|
||||
|
||||
if (start_x > 0 and
|
||||
j == start_x) break;
|
||||
|
||||
if (end_x > 0 and
|
||||
j == end_x + 1) break;
|
||||
}
|
||||
}
|
||||
|
||||
// If we're a spacer, then we ignore it
|
||||
switch (cell.wide) {
|
||||
.narrow, .wide => {},
|
||||
.spacer_head, .spacer_tail => continue,
|
||||
}
|
||||
|
||||
// If our cell attributes are changing, then we split the run.
|
||||
// This prevents a single glyph for ">=" to be rendered with
|
||||
// one color when the two components have different styling.
|
||||
if (j > self.i) style: {
|
||||
const prev_cell = cells[j - 1];
|
||||
|
||||
// If the prev cell and this cell are both plain
|
||||
// codepoints then we check if they are commonly "bad"
|
||||
// ligatures and spit the run if they are.
|
||||
if (prev_cell.content_tag == .codepoint and
|
||||
cell.content_tag == .codepoint)
|
||||
{
|
||||
const prev_cp = prev_cell.codepoint();
|
||||
switch (prev_cp) {
|
||||
// fl, fi
|
||||
'f' => {
|
||||
const cp = cell.codepoint();
|
||||
if (cp == 'l' or cp == 'i') break;
|
||||
},
|
||||
|
||||
// st
|
||||
's' => {
|
||||
const cp = cell.codepoint();
|
||||
if (cp == 't') break;
|
||||
},
|
||||
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
|
||||
// If the style is exactly the change then fast path out.
|
||||
if (prev_cell.style_id == cell.style_id) break :style;
|
||||
|
||||
// The style is different. We allow differing background
|
||||
// styles but any other change results in a new run.
|
||||
const c1 = comparableStyle(style);
|
||||
const c2 = comparableStyle(self.opts.row.style(&cells[j]));
|
||||
if (!c1.eql(c2)) break;
|
||||
}
|
||||
|
||||
// Text runs break when font styles change so we need to get
|
||||
// the proper style.
|
||||
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;
|
||||
};
|
||||
|
||||
// Determine the presentation format for this glyph.
|
||||
const presentation: ?font.Presentation = if (cell.hasGrapheme()) p: {
|
||||
// We only check the FIRST codepoint because I believe the
|
||||
// presentation format must be directly adjacent to the codepoint.
|
||||
const cps = self.opts.row.grapheme(cell) orelse break :p null;
|
||||
assert(cps.len > 0);
|
||||
if (cps[0] == 0xFE0E) break :p .text;
|
||||
if (cps[0] == 0xFE0F) break :p .emoji;
|
||||
break :p null;
|
||||
} else emoji: {
|
||||
// If we're not a grapheme, our individual char could be
|
||||
// an emoji so we want to check if we expect emoji presentation.
|
||||
// The font grid indexForCodepoint we use below will do this
|
||||
// automatically.
|
||||
break :emoji null;
|
||||
};
|
||||
|
||||
// If our cursor is on this line then we break the run around the
|
||||
// cursor. This means that any row with a cursor has at least
|
||||
// three breaks: before, exactly the cursor, and after.
|
||||
//
|
||||
// We do not break a cell that is exactly the grapheme. If there
|
||||
// are cells following that contain joiners, we allow those to
|
||||
// break. This creates an effect where hovering over an emoji
|
||||
// such as a skin-tone emoji is fine, but hovering over the
|
||||
// joiners will show the joiners allowing you to modify the
|
||||
// emoji.
|
||||
if (!cell.hasGrapheme()) {
|
||||
if (self.opts.cursor_x) |cursor_x| {
|
||||
// Exactly: self.i is the cursor and we iterated once. This
|
||||
// means that we started exactly at the cursor and did at
|
||||
// exactly one iteration. Why exactly one? Because we may
|
||||
// start at our cursor but do many if our cursor is exactly
|
||||
// on an emoji.
|
||||
if (self.i == cursor_x and j == self.i + 1) break;
|
||||
|
||||
// Before: up to and not including the cursor. This means
|
||||
// that we started before the cursor (self.i < cursor_x)
|
||||
// and j is now at the cursor meaning we haven't yet processed
|
||||
// the cursor.
|
||||
if (self.i < cursor_x and j == cursor_x) {
|
||||
assert(j > 0);
|
||||
break;
|
||||
}
|
||||
|
||||
// After: after the cursor. We don't need to do anything
|
||||
// special, we just let the run complete.
|
||||
}
|
||||
}
|
||||
|
||||
// We need to find a font that supports this character. If
|
||||
// there are additional zero-width codepoints (to form a single
|
||||
// grapheme, i.e. combining characters), we need to find a font
|
||||
// that supports all of them.
|
||||
const font_info: struct {
|
||||
idx: font.Collection.Index,
|
||||
fallback: ?u32 = null,
|
||||
} = font_info: {
|
||||
// If we find a font that supports this entire grapheme
|
||||
// then we use that.
|
||||
if (try self.indexForCell(
|
||||
alloc,
|
||||
cell,
|
||||
font_style,
|
||||
presentation,
|
||||
)) |idx| break :font_info .{ .idx = idx };
|
||||
|
||||
// Otherwise we need a fallback character. Prefer the
|
||||
// official replacement character.
|
||||
if (try self.opts.grid.getIndex(
|
||||
alloc,
|
||||
0xFFFD, // replacement char
|
||||
font_style,
|
||||
presentation,
|
||||
)) |idx| break :font_info .{ .idx = idx, .fallback = 0xFFFD };
|
||||
|
||||
// Fallback to space
|
||||
if (try self.opts.grid.getIndex(
|
||||
alloc,
|
||||
' ',
|
||||
font_style,
|
||||
presentation,
|
||||
)) |idx| break :font_info .{ .idx = idx, .fallback = ' ' };
|
||||
|
||||
// We can't render at all. This is a bug, we should always
|
||||
// have a font that can render a space.
|
||||
unreachable;
|
||||
};
|
||||
|
||||
//log.warn("char={x} info={}", .{ cell.char, font_info });
|
||||
if (j == self.i) current_font = font_info.idx;
|
||||
|
||||
// If our fonts are not equal, then we're done with our run.
|
||||
if (font_info.idx != current_font) break;
|
||||
|
||||
// If we're a fallback character, add that and continue; we
|
||||
// don't want to add the entire grapheme.
|
||||
if (font_info.fallback) |cp| {
|
||||
try self.addCodepoint(&hasher, cp, @intCast(cluster));
|
||||
continue;
|
||||
}
|
||||
|
||||
// If we're a Kitty unicode placeholder then we add a blank.
|
||||
if (cell.codepoint() == terminal.kitty.graphics.unicode.placeholder) {
|
||||
try self.addCodepoint(&hasher, ' ', @intCast(cluster));
|
||||
continue;
|
||||
}
|
||||
|
||||
// Add all the codepoints for our grapheme
|
||||
try self.addCodepoint(
|
||||
&hasher,
|
||||
if (cell.codepoint() == 0) ' ' else cell.codepoint(),
|
||||
@intCast(cluster),
|
||||
);
|
||||
if (cell.hasGrapheme()) {
|
||||
const cps = self.opts.row.grapheme(cell).?;
|
||||
for (cps) |cp| {
|
||||
// Do not send presentation modifiers
|
||||
if (cp == 0xFE0E or cp == 0xFE0F) continue;
|
||||
try self.addCodepoint(&hasher, cp, @intCast(cluster));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Finalize our buffer
|
||||
try self.hooks.finalize();
|
||||
|
||||
// Add our length to the hash as an additional mechanism to avoid collisions
|
||||
autoHash(&hasher, j - self.i);
|
||||
|
||||
// Add our font index
|
||||
autoHash(&hasher, current_font);
|
||||
|
||||
// Move our cursor. Must defer since we use self.i below.
|
||||
defer self.i = j;
|
||||
|
||||
return TextRun{
|
||||
.hash = hasher.final(),
|
||||
.offset = @intCast(self.i),
|
||||
.cells = @intCast(j - self.i),
|
||||
.grid = self.opts.grid,
|
||||
.font_index = current_font,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn next2(self: *RunIterator, alloc: Allocator) !?TextRun {
|
||||
const slice = &self.opts.cells;
|
||||
const cells: []const terminal.page.Cell = slice.items(.raw);
|
||||
const graphemes: []const []const u21 = slice.items(.grapheme);
|
||||
|
|
@ -360,7 +93,7 @@ pub const RunIterator = struct {
|
|||
|
||||
// If we have a selection and we're at a boundary point, then
|
||||
// we break the run here.
|
||||
if (self.opts.selection2) |bounds| {
|
||||
if (self.opts.selection) |bounds| {
|
||||
if (j > self.i) {
|
||||
if (bounds[0] > 0 and j == bounds[0]) break;
|
||||
if (bounds[1] > 0 and j == bounds[1] + 1) break;
|
||||
|
|
@ -485,7 +218,7 @@ pub const RunIterator = struct {
|
|||
} = font_info: {
|
||||
// If we find a font that supports this entire grapheme
|
||||
// then we use that.
|
||||
if (try self.indexForCell2(
|
||||
if (try self.indexForCell(
|
||||
alloc,
|
||||
cell,
|
||||
graphemes[j],
|
||||
|
|
@ -583,82 +316,6 @@ pub const RunIterator = struct {
|
|||
/// We look for fonts that support each individual codepoint and then
|
||||
/// find the common font amongst all candidates.
|
||||
fn indexForCell(
|
||||
self: *RunIterator,
|
||||
alloc: Allocator,
|
||||
cell: *const terminal.Cell,
|
||||
style: font.Style,
|
||||
presentation: ?font.Presentation,
|
||||
) !?font.Collection.Index {
|
||||
if (cell.isEmpty() or
|
||||
cell.codepoint() == 0 or
|
||||
cell.codepoint() == terminal.kitty.graphics.unicode.placeholder)
|
||||
{
|
||||
return try self.opts.grid.getIndex(
|
||||
alloc,
|
||||
' ',
|
||||
style,
|
||||
presentation,
|
||||
);
|
||||
}
|
||||
|
||||
// Get the font index for the primary codepoint.
|
||||
const primary_cp: u32 = cell.codepoint();
|
||||
const primary = try self.opts.grid.getIndex(
|
||||
alloc,
|
||||
primary_cp,
|
||||
style,
|
||||
presentation,
|
||||
) orelse return null;
|
||||
|
||||
// Easy, and common: we aren't a multi-codepoint grapheme, so
|
||||
// we just return whatever index for the cell codepoint.
|
||||
if (!cell.hasGrapheme()) return primary;
|
||||
|
||||
// If this is a grapheme, we need to find a font that supports
|
||||
// all of the codepoints in the grapheme.
|
||||
const cps = self.opts.row.grapheme(cell) orelse return primary;
|
||||
var candidates: std.ArrayList(font.Collection.Index) = try .initCapacity(alloc, cps.len + 1);
|
||||
defer candidates.deinit(alloc);
|
||||
candidates.appendAssumeCapacity(primary);
|
||||
|
||||
for (cps) |cp| {
|
||||
// Ignore Emoji ZWJs
|
||||
if (cp == 0xFE0E or cp == 0xFE0F or cp == 0x200D) continue;
|
||||
|
||||
// Find a font that supports this codepoint. If none support this
|
||||
// then the whole grapheme can't be rendered so we return null.
|
||||
//
|
||||
// We explicitly do not require the additional grapheme components
|
||||
// to support the base presentation, since it is common for emoji
|
||||
// fonts to support the base emoji with emoji presentation but not
|
||||
// certain ZWJ-combined characters like the male and female signs.
|
||||
const idx = try self.opts.grid.getIndex(
|
||||
alloc,
|
||||
cp,
|
||||
style,
|
||||
null,
|
||||
) orelse return null;
|
||||
candidates.appendAssumeCapacity(idx);
|
||||
}
|
||||
|
||||
// We need to find a candidate that has ALL of our codepoints
|
||||
for (candidates.items) |idx| {
|
||||
if (!self.opts.grid.hasCodepoint(idx, primary_cp, presentation)) continue;
|
||||
for (cps) |cp| {
|
||||
// Ignore Emoji ZWJs
|
||||
if (cp == 0xFE0E or cp == 0xFE0F or cp == 0x200D) continue;
|
||||
if (!self.opts.grid.hasCodepoint(idx, cp, null)) break;
|
||||
} else {
|
||||
// If the while completed, then we have a candidate that
|
||||
// supports all of our codepoints.
|
||||
return idx;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
fn indexForCell2(
|
||||
self: *RunIterator,
|
||||
alloc: Allocator,
|
||||
cell: *const terminal.Cell,
|
||||
|
|
|
|||
|
|
@ -2414,7 +2414,7 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
|
|||
var run_iter_opts: font.shape.RunOptions = .{
|
||||
.grid = self.font_grid,
|
||||
.cells = cells_slice,
|
||||
.selection2 = if (selection) |s| s else null,
|
||||
.selection = if (selection) |s| s else null,
|
||||
|
||||
// We want to do font shaping as long as the cursor is
|
||||
// visible on this viewport.
|
||||
|
|
@ -2423,11 +2423,6 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
|
|||
if (vp.y != y) break :cursor_x null;
|
||||
break :cursor_x vp.x;
|
||||
},
|
||||
|
||||
// Old stuff
|
||||
.screen = undefined,
|
||||
.row = undefined,
|
||||
.selection = null,
|
||||
};
|
||||
run_iter_opts.applyBreakConfig(self.config.font_shaping_break);
|
||||
var run_iter = self.font_shaper.runIterator(run_iter_opts);
|
||||
|
|
|
|||
Loading…
Reference in New Issue