Introduce `font-shaping-break` config option

pull/5374/head
Daniel Patterson 2025-01-26 00:30:18 +00:00 committed by Mitchell Hashimoto
parent 2592286988
commit beb961fb80
7 changed files with 436 additions and 120 deletions

View File

@ -20,6 +20,7 @@ pub const ConfirmCloseSurface = Config.ConfirmCloseSurface;
pub const CopyOnSelect = Config.CopyOnSelect;
pub const CustomShaderAnimation = Config.CustomShaderAnimation;
pub const FontSyntheticStyle = Config.FontSyntheticStyle;
pub const FontShapingBreak = Config.FontShapingBreak;
pub const FontStyle = Config.FontStyle;
pub const FreetypeLoadFlags = Config.FreetypeLoadFlags;
pub const Keybinds = Config.Keybinds;

View File

@ -270,6 +270,32 @@ pub const compatibility = std.StaticStringMap(
/// This is currently only supported on macOS.
@"font-thicken-strength": u8 = 255,
/// Locations to break font shaping into multiple runs.
///
/// A "run" is a contiguous segment of text that is shaped together. "Shaping"
/// is the process of converting text (codepoints) into glyphs (renderable
/// characters). This is how ligatures are formed, among other things.
/// For example, if a coding font turns "!=" into a single glyph, then it
/// must see "!" and "=" next to each other in a single run. When a run
/// is broken, the text is shaped separately. To continue our example, if
/// "!" is at the end of one run and "=" is at the start of the next run,
/// then the ligature will not be formed.
///
/// Ghostty breaks runs at certain points to improve readability or usability.
/// For example, Ghostty by default will break runs under the cursor so that
/// text editing can see the individual characters rather than a ligature.
/// This configuration lets you configure this behavior.
///
/// Combine values with a comma to set multiple options. Prefix an
/// option with "no-" to disable it. Enabling and disabling options
/// can be done at the same time.
///
/// Available options:
///
/// * `cursor` - Break runs under the cursor.
///
@"font-shaping-break": FontShapingBreak = .{},
/// What color space to use when performing alpha blending.
///
/// This affects the appearance of text and of any images with transparency.
@ -6214,6 +6240,11 @@ pub const FontSyntheticStyle = packed struct {
@"bold-italic": bool = true,
};
/// See "font-shaping-break" for documentation
pub const FontShapingBreak = packed struct {
cursor: bool = true,
};
/// See "link" for documentation.
pub const RepeatableLink = struct {
const Self = @This();

View File

@ -7,6 +7,7 @@ const trace = @import("tracy").trace;
const font = @import("../main.zig");
const os = @import("../../os/main.zig");
const terminal = @import("../../terminal/main.zig");
const config = @import("../../config.zig");
const Feature = font.shape.Feature;
const FeatureList = font.shape.FeatureList;
const default_features = font.shape.default_features;
@ -293,6 +294,7 @@ pub const Shaper = struct {
row: terminal.Pin,
selection: ?terminal.Selection,
cursor_x: ?usize,
break_config: config.FontShapingBreak,
) font.shape.RunIterator {
return .{
.hooks = .{ .shaper = self },
@ -301,6 +303,7 @@ pub const Shaper = struct {
.row = row,
.selection = selection,
.cursor_x = cursor_x,
.break_config = break_config,
};
}
@ -600,6 +603,7 @@ test "run iterator" {
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
null,
null,
.{},
);
var count: usize = 0;
while (try it.next(alloc)) |_| count += 1;
@ -619,6 +623,7 @@ test "run iterator" {
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
null,
null,
.{},
);
var count: usize = 0;
while (try it.next(alloc)) |_| count += 1;
@ -639,6 +644,7 @@ test "run iterator" {
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
null,
null,
.{},
);
var count: usize = 0;
while (try it.next(alloc)) |_| count += 1;
@ -660,6 +666,7 @@ test "run iterator" {
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
null,
null,
.{},
);
var count: usize = 0;
while (try it.next(alloc)) |_| count += 1;
@ -707,6 +714,7 @@ test "run iterator: empty cells with background set" {
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
null,
null,
.{},
);
{
const run = (try it.next(alloc)).?;
@ -743,6 +751,7 @@ test "shape" {
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
null,
null,
.{},
);
var count: usize = 0;
while (try it.next(alloc)) |run| {
@ -778,6 +787,7 @@ test "shape nerd fonts" {
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
null,
null,
.{},
);
var count: usize = 0;
while (try it.next(alloc)) |run| {
@ -806,6 +816,7 @@ test "shape inconsolata ligs" {
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
null,
null,
.{},
);
var count: usize = 0;
while (try it.next(alloc)) |run| {
@ -831,6 +842,7 @@ test "shape inconsolata ligs" {
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
null,
null,
.{},
);
var count: usize = 0;
while (try it.next(alloc)) |run| {
@ -864,6 +876,7 @@ test "shape monaspace ligs" {
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
null,
null,
.{},
);
var count: usize = 0;
while (try it.next(alloc)) |run| {
@ -898,6 +911,7 @@ test "shape left-replaced lig in last run" {
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
null,
null,
.{},
);
var count: usize = 0;
while (try it.next(alloc)) |run| {
@ -932,6 +946,7 @@ test "shape left-replaced lig in early run" {
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
null,
null,
.{},
);
const run = (try it.next(alloc)).?;
@ -963,6 +978,7 @@ test "shape U+3C9 with JB Mono" {
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
null,
null,
.{},
);
var run_count: usize = 0;
@ -996,6 +1012,7 @@ test "shape emoji width" {
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
null,
null,
.{},
);
var count: usize = 0;
while (try it.next(alloc)) |run| {
@ -1046,6 +1063,7 @@ test "shape emoji width long" {
screen.pages.pin(.{ .screen = .{ .y = 1 } }).?,
null,
null,
.{},
);
var count: usize = 0;
while (try it.next(alloc)) |run| {
@ -1082,6 +1100,7 @@ test "shape variation selector VS15" {
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
null,
null,
.{},
);
var count: usize = 0;
while (try it.next(alloc)) |run| {
@ -1117,6 +1136,7 @@ test "shape variation selector VS16" {
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
null,
null,
.{},
);
var count: usize = 0;
while (try it.next(alloc)) |run| {
@ -1149,6 +1169,7 @@ test "shape with empty cells in between" {
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
null,
null,
.{},
);
var count: usize = 0;
while (try it.next(alloc)) |run| {
@ -1187,6 +1208,7 @@ test "shape Chinese characters" {
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
null,
null,
.{},
);
var count: usize = 0;
while (try it.next(alloc)) |run| {
@ -1227,6 +1249,7 @@ test "shape box glyphs" {
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
null,
null,
.{},
);
var count: usize = 0;
while (try it.next(alloc)) |run| {
@ -1267,6 +1290,7 @@ test "shape selection boundary" {
false,
),
null,
.{},
);
var count: usize = 0;
while (try it.next(alloc)) |run| {
@ -1290,6 +1314,7 @@ test "shape selection boundary" {
false,
),
null,
.{},
);
var count: usize = 0;
while (try it.next(alloc)) |run| {
@ -1313,6 +1338,7 @@ test "shape selection boundary" {
false,
),
null,
.{},
);
var count: usize = 0;
while (try it.next(alloc)) |run| {
@ -1336,6 +1362,7 @@ test "shape selection boundary" {
false,
),
null,
.{},
);
var count: usize = 0;
while (try it.next(alloc)) |run| {
@ -1359,6 +1386,7 @@ test "shape selection boundary" {
false,
),
null,
.{},
);
var count: usize = 0;
while (try it.next(alloc)) |run| {
@ -1391,6 +1419,7 @@ test "shape cursor boundary" {
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
null,
null,
.{},
);
var count: usize = 0;
while (try it.next(alloc)) |run| {
@ -1400,6 +1429,7 @@ test "shape cursor boundary" {
try testing.expectEqual(@as(usize, 1), count);
}
{
// Cursor at index 0 is two runs
{
// Get our run iterator
@ -1410,6 +1440,7 @@ test "shape cursor boundary" {
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
null,
0,
.{ .cursor = true },
);
var count: usize = 0;
while (try it.next(alloc)) |run| {
@ -1418,7 +1449,28 @@ test "shape cursor boundary" {
}
try testing.expectEqual(@as(usize, 2), count);
}
// And without cursor splitting remains one
{
// Get our run iterator
var shaper = &testdata.shaper;
var it = shaper.runIterator(
testdata.grid,
&screen,
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
null,
0,
.{ .cursor = false },
);
var count: usize = 0;
while (try it.next(alloc)) |run| {
count += 1;
_ = try shaper.shape(run);
}
try testing.expectEqual(@as(usize, 1), count);
}
}
{
// Cursor at index 1 is three runs
{
// Get our run iterator
@ -1429,6 +1481,7 @@ test "shape cursor boundary" {
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
null,
1,
.{ .cursor = true },
);
var count: usize = 0;
while (try it.next(alloc)) |run| {
@ -1437,7 +1490,27 @@ test "shape cursor boundary" {
}
try testing.expectEqual(@as(usize, 3), count);
}
// And without cursor splitting remains one
{
// Get our run iterator
var shaper = &testdata.shaper;
var it = shaper.runIterator(
testdata.grid,
&screen,
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
null,
1,
.{ .cursor = false },
);
var count: usize = 0;
while (try it.next(alloc)) |run| {
count += 1;
_ = try shaper.shape(run);
}
try testing.expectEqual(@as(usize, 1), count);
}
}
{
// Cursor at last col is two runs
{
// Get our run iterator
@ -1448,6 +1521,7 @@ test "shape cursor boundary" {
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
null,
9,
.{ .cursor = true },
);
var count: usize = 0;
while (try it.next(alloc)) |run| {
@ -1456,6 +1530,26 @@ test "shape cursor boundary" {
}
try testing.expectEqual(@as(usize, 2), count);
}
// And without cursor splitting remains one
{
// Get our run iterator
var shaper = &testdata.shaper;
var it = shaper.runIterator(
testdata.grid,
&screen,
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
null,
9,
.{ .cursor = false },
);
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 cursor boundary and colored emoji" {
@ -1480,6 +1574,7 @@ test "shape cursor boundary and colored emoji" {
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
null,
null,
.{},
);
var count: usize = 0;
while (try it.next(alloc)) |run| {
@ -1499,6 +1594,25 @@ test "shape cursor boundary and colored emoji" {
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.grid,
&screen,
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
null,
0,
.{ .cursor = false },
);
var count: usize = 0;
while (try it.next(alloc)) |run| {
@ -1516,6 +1630,25 @@ test "shape cursor boundary and colored emoji" {
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);
}
{
// Get our run iterator
var shaper = &testdata.shaper;
var it = shaper.runIterator(
testdata.grid,
&screen,
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
null,
1,
.{ .cursor = false },
);
var count: usize = 0;
while (try it.next(alloc)) |run| {
@ -1546,6 +1679,7 @@ test "shape cell attribute change" {
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
null,
null,
.{},
);
var count: usize = 0;
while (try it.next(alloc)) |run| {
@ -1570,6 +1704,7 @@ test "shape cell attribute change" {
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
null,
null,
.{},
);
var count: usize = 0;
while (try it.next(alloc)) |run| {
@ -1595,6 +1730,7 @@ test "shape cell attribute change" {
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
null,
null,
.{},
);
var count: usize = 0;
while (try it.next(alloc)) |run| {
@ -1620,6 +1756,7 @@ test "shape cell attribute change" {
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
null,
null,
.{},
);
var count: usize = 0;
while (try it.next(alloc)) |run| {
@ -1644,6 +1781,7 @@ test "shape cell attribute change" {
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
null,
null,
.{},
);
var count: usize = 0;
while (try it.next(alloc)) |run| {
@ -1684,6 +1822,7 @@ test "shape high plane sprite font codepoint" {
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
null,
null,
.{},
);
// We should get one run
const run = (try it.next(alloc)).?;

View File

@ -4,6 +4,7 @@ const Allocator = std.mem.Allocator;
const harfbuzz = @import("harfbuzz");
const font = @import("../main.zig");
const terminal = @import("../../terminal/main.zig");
const config = @import("../../config.zig");
const Feature = font.shape.Feature;
const FeatureList = font.shape.FeatureList;
const default_features = font.shape.default_features;
@ -94,6 +95,7 @@ pub const Shaper = struct {
row: terminal.Pin,
selection: ?terminal.Selection,
cursor_x: ?usize,
break_config: config.FontShapingBreak,
) font.shape.RunIterator {
return .{
.hooks = .{ .shaper = self },
@ -102,6 +104,7 @@ pub const Shaper = struct {
.row = row,
.selection = selection,
.cursor_x = cursor_x,
.break_config = break_config,
};
}
@ -231,6 +234,7 @@ test "run iterator" {
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
null,
null,
.{},
);
var count: usize = 0;
while (try it.next(alloc)) |_| count += 1;
@ -250,6 +254,7 @@ test "run iterator" {
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
null,
null,
.{},
);
var count: usize = 0;
while (try it.next(alloc)) |_| count += 1;
@ -270,6 +275,7 @@ test "run iterator" {
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
null,
null,
.{},
);
var count: usize = 0;
while (try it.next(alloc)) |_| {
@ -322,6 +328,7 @@ test "run iterator: empty cells with background set" {
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
null,
null,
.{},
);
{
const run = (try it.next(alloc)).?;
@ -359,6 +366,7 @@ test "shape" {
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
null,
null,
.{},
);
var count: usize = 0;
while (try it.next(alloc)) |run| {
@ -388,6 +396,7 @@ test "shape inconsolata ligs" {
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
null,
null,
.{},
);
var count: usize = 0;
while (try it.next(alloc)) |run| {
@ -413,6 +422,7 @@ test "shape inconsolata ligs" {
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
null,
null,
.{},
);
var count: usize = 0;
while (try it.next(alloc)) |run| {
@ -446,6 +456,7 @@ test "shape monaspace ligs" {
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
null,
null,
.{},
);
var count: usize = 0;
while (try it.next(alloc)) |run| {
@ -482,6 +493,7 @@ test "shape arabic forced LTR" {
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
null,
null,
.{},
);
var count: usize = 0;
while (try it.next(alloc)) |run| {
@ -519,6 +531,7 @@ test "shape emoji width" {
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
null,
null,
.{},
);
var count: usize = 0;
while (try it.next(alloc)) |run| {
@ -571,6 +584,7 @@ test "shape emoji width long" {
screen.pages.pin(.{ .screen = .{ .y = 1 } }).?,
null,
null,
.{},
);
var count: usize = 0;
while (try it.next(alloc)) |run| {
@ -609,6 +623,7 @@ test "shape variation selector VS15" {
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
null,
null,
.{},
);
var count: usize = 0;
while (try it.next(alloc)) |run| {
@ -646,6 +661,7 @@ test "shape variation selector VS16" {
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
null,
null,
.{},
);
var count: usize = 0;
while (try it.next(alloc)) |run| {
@ -680,6 +696,7 @@ test "shape with empty cells in between" {
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
null,
null,
.{},
);
var count: usize = 0;
while (try it.next(alloc)) |run| {
@ -718,6 +735,7 @@ test "shape Chinese characters" {
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
null,
null,
.{},
);
var count: usize = 0;
while (try it.next(alloc)) |run| {
@ -758,6 +776,7 @@ test "shape box glyphs" {
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
null,
null,
.{},
);
var count: usize = 0;
while (try it.next(alloc)) |run| {
@ -799,6 +818,7 @@ test "shape selection boundary" {
false,
),
null,
.{},
);
var count: usize = 0;
while (try it.next(alloc)) |run| {
@ -822,6 +842,7 @@ test "shape selection boundary" {
false,
),
null,
.{},
);
var count: usize = 0;
while (try it.next(alloc)) |run| {
@ -845,6 +866,7 @@ test "shape selection boundary" {
false,
),
null,
.{},
);
var count: usize = 0;
while (try it.next(alloc)) |run| {
@ -868,6 +890,7 @@ test "shape selection boundary" {
false,
),
null,
.{},
);
var count: usize = 0;
while (try it.next(alloc)) |run| {
@ -891,6 +914,7 @@ test "shape selection boundary" {
false,
),
null,
.{},
);
var count: usize = 0;
while (try it.next(alloc)) |run| {
@ -923,6 +947,7 @@ test "shape cursor boundary" {
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
null,
null,
.{},
);
var count: usize = 0;
while (try it.next(alloc)) |run| {
@ -932,6 +957,7 @@ test "shape cursor boundary" {
try testing.expectEqual(@as(usize, 1), count);
}
{
// Cursor at index 0 is two runs
{
// Get our run iterator
@ -942,6 +968,7 @@ test "shape cursor boundary" {
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
null,
0,
.{ .cursor = true },
);
var count: usize = 0;
while (try it.next(alloc)) |run| {
@ -950,7 +977,28 @@ test "shape cursor boundary" {
}
try testing.expectEqual(@as(usize, 2), count);
}
// And without cursor splitting remains one
{
// Get our run iterator
var shaper = &testdata.shaper;
var it = shaper.runIterator(
testdata.grid,
&screen,
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
null,
0,
.{ .cursor = false },
);
var count: usize = 0;
while (try it.next(alloc)) |run| {
count += 1;
_ = try shaper.shape(run);
}
try testing.expectEqual(@as(usize, 1), count);
}
}
{
// Cursor at index 1 is three runs
{
// Get our run iterator
@ -961,6 +1009,7 @@ test "shape cursor boundary" {
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
null,
1,
.{ .cursor = true },
);
var count: usize = 0;
while (try it.next(alloc)) |run| {
@ -969,7 +1018,27 @@ test "shape cursor boundary" {
}
try testing.expectEqual(@as(usize, 3), count);
}
// And without cursor splitting remains one
{
// Get our run iterator
var shaper = &testdata.shaper;
var it = shaper.runIterator(
testdata.grid,
&screen,
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
null,
1,
.{ .cursor = false },
);
var count: usize = 0;
while (try it.next(alloc)) |run| {
count += 1;
_ = try shaper.shape(run);
}
try testing.expectEqual(@as(usize, 1), count);
}
}
{
// Cursor at last col is two runs
{
// Get our run iterator
@ -980,6 +1049,7 @@ test "shape cursor boundary" {
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
null,
9,
.{ .cursor = true },
);
var count: usize = 0;
while (try it.next(alloc)) |run| {
@ -988,6 +1058,26 @@ test "shape cursor boundary" {
}
try testing.expectEqual(@as(usize, 2), count);
}
// And without cursor splitting remains one
{
// Get our run iterator
var shaper = &testdata.shaper;
var it = shaper.runIterator(
testdata.grid,
&screen,
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
null,
9,
.{ .cursor = false },
);
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 cursor boundary and colored emoji" {
@ -1012,6 +1102,7 @@ test "shape cursor boundary and colored emoji" {
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
null,
null,
.{},
);
var count: usize = 0;
while (try it.next(alloc)) |run| {
@ -1031,6 +1122,25 @@ test "shape cursor boundary and colored emoji" {
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
null,
0,
.{ .cursor = true },
);
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.grid,
&screen,
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
null,
0,
.{ .cursor = false },
);
var count: usize = 0;
while (try it.next(alloc)) |run| {
@ -1048,6 +1158,25 @@ test "shape cursor boundary and colored emoji" {
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
null,
1,
.{ .cursor = true },
);
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.grid,
&screen,
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
null,
1,
.{ .cursor = false },
);
var count: usize = 0;
while (try it.next(alloc)) |run| {
@ -1078,6 +1207,7 @@ test "shape cell attribute change" {
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
null,
null,
.{},
);
var count: usize = 0;
while (try it.next(alloc)) |run| {
@ -1102,6 +1232,7 @@ test "shape cell attribute change" {
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
null,
null,
.{},
);
var count: usize = 0;
while (try it.next(alloc)) |run| {
@ -1127,6 +1258,7 @@ test "shape cell attribute change" {
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
null,
null,
.{},
);
var count: usize = 0;
while (try it.next(alloc)) |run| {
@ -1152,6 +1284,7 @@ test "shape cell attribute change" {
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
null,
null,
.{},
);
var count: usize = 0;
while (try it.next(alloc)) |run| {
@ -1176,6 +1309,7 @@ test "shape cell attribute change" {
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
null,
null,
.{},
);
var count: usize = 0;
while (try it.next(alloc)) |run| {

View File

@ -3,6 +3,7 @@ const assert = std.debug.assert;
const Allocator = std.mem.Allocator;
const trace = @import("tracy").trace;
const font = @import("../main.zig");
const config = @import("../../config.zig");
const Face = font.Face;
const Collection = font.Collection;
const DeferredFace = font.DeferredFace;
@ -75,6 +76,7 @@ pub const Shaper = struct {
row: terminal.Pin,
selection: ?terminal.Selection,
cursor_x: ?usize,
break_config: config.FontShapingBreak,
) font.shape.RunIterator {
return .{
.hooks = .{ .shaper = self },
@ -83,6 +85,7 @@ pub const Shaper = struct {
.row = row,
.selection = selection,
.cursor_x = cursor_x,
.break_config = break_config,
};
}

View File

@ -6,6 +6,8 @@ const shape = @import("../shape.zig");
const terminal = @import("../../terminal/main.zig");
const autoHash = std.hash.autoHash;
const Hasher = std.hash.Wyhash;
const configpkg = @import("../../config.zig");
const Config = configpkg.Config;
/// A single text run. A text run is only valid for one Shaper instance and
/// until the next run is created. A text run never goes across multiple
@ -40,6 +42,7 @@ pub const RunIterator = struct {
row: terminal.Pin,
selection: ?terminal.Selection = null,
cursor_x: ?usize = null,
break_config: configpkg.FontShapingBreak,
i: usize = 0,
pub fn next(self: *RunIterator, alloc: Allocator) !?TextRun {
@ -175,6 +178,7 @@ pub const RunIterator = struct {
break :emoji null;
};
if (self.break_config.cursor) {
// 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.
@ -207,6 +211,7 @@ pub const RunIterator = struct {
// 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

View File

@ -4,6 +4,7 @@ const Allocator = std.mem.Allocator;
const ziglyph = @import("ziglyph");
const font = @import("../main.zig");
const terminal = @import("../../terminal/main.zig");
const config = @import("../../config.zig");
const log = std.log.scoped(.font_shaper);
@ -65,6 +66,7 @@ pub const Shaper = struct {
row: terminal.Screen.Row,
selection: ?terminal.Selection,
cursor_x: ?usize,
break_config: config.FontShapingBreak,
) font.shape.RunIterator {
return .{
.hooks = .{ .shaper = self },
@ -72,6 +74,7 @@ pub const Shaper = struct {
.row = row,
.selection = selection,
.cursor_x = cursor_x,
.break_config = break_config,
};
}