Introduce `font-shaping-break` config option
parent
2592286988
commit
beb961fb80
|
|
@ -20,6 +20,7 @@ pub const ConfirmCloseSurface = Config.ConfirmCloseSurface;
|
||||||
pub const CopyOnSelect = Config.CopyOnSelect;
|
pub const CopyOnSelect = Config.CopyOnSelect;
|
||||||
pub const CustomShaderAnimation = Config.CustomShaderAnimation;
|
pub const CustomShaderAnimation = Config.CustomShaderAnimation;
|
||||||
pub const FontSyntheticStyle = Config.FontSyntheticStyle;
|
pub const FontSyntheticStyle = Config.FontSyntheticStyle;
|
||||||
|
pub const FontShapingBreak = Config.FontShapingBreak;
|
||||||
pub const FontStyle = Config.FontStyle;
|
pub const FontStyle = Config.FontStyle;
|
||||||
pub const FreetypeLoadFlags = Config.FreetypeLoadFlags;
|
pub const FreetypeLoadFlags = Config.FreetypeLoadFlags;
|
||||||
pub const Keybinds = Config.Keybinds;
|
pub const Keybinds = Config.Keybinds;
|
||||||
|
|
|
||||||
|
|
@ -270,6 +270,32 @@ pub const compatibility = std.StaticStringMap(
|
||||||
/// This is currently only supported on macOS.
|
/// This is currently only supported on macOS.
|
||||||
@"font-thicken-strength": u8 = 255,
|
@"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.
|
/// What color space to use when performing alpha blending.
|
||||||
///
|
///
|
||||||
/// This affects the appearance of text and of any images with transparency.
|
/// 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,
|
@"bold-italic": bool = true,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// See "font-shaping-break" for documentation
|
||||||
|
pub const FontShapingBreak = packed struct {
|
||||||
|
cursor: bool = true,
|
||||||
|
};
|
||||||
|
|
||||||
/// See "link" for documentation.
|
/// See "link" for documentation.
|
||||||
pub const RepeatableLink = struct {
|
pub const RepeatableLink = struct {
|
||||||
const Self = @This();
|
const Self = @This();
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ const trace = @import("tracy").trace;
|
||||||
const font = @import("../main.zig");
|
const font = @import("../main.zig");
|
||||||
const os = @import("../../os/main.zig");
|
const os = @import("../../os/main.zig");
|
||||||
const terminal = @import("../../terminal/main.zig");
|
const terminal = @import("../../terminal/main.zig");
|
||||||
|
const config = @import("../../config.zig");
|
||||||
const Feature = font.shape.Feature;
|
const Feature = font.shape.Feature;
|
||||||
const FeatureList = font.shape.FeatureList;
|
const FeatureList = font.shape.FeatureList;
|
||||||
const default_features = font.shape.default_features;
|
const default_features = font.shape.default_features;
|
||||||
|
|
@ -293,6 +294,7 @@ pub const Shaper = struct {
|
||||||
row: terminal.Pin,
|
row: terminal.Pin,
|
||||||
selection: ?terminal.Selection,
|
selection: ?terminal.Selection,
|
||||||
cursor_x: ?usize,
|
cursor_x: ?usize,
|
||||||
|
break_config: config.FontShapingBreak,
|
||||||
) font.shape.RunIterator {
|
) font.shape.RunIterator {
|
||||||
return .{
|
return .{
|
||||||
.hooks = .{ .shaper = self },
|
.hooks = .{ .shaper = self },
|
||||||
|
|
@ -301,6 +303,7 @@ pub const Shaper = struct {
|
||||||
.row = row,
|
.row = row,
|
||||||
.selection = selection,
|
.selection = selection,
|
||||||
.cursor_x = cursor_x,
|
.cursor_x = cursor_x,
|
||||||
|
.break_config = break_config,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -600,6 +603,7 @@ test "run iterator" {
|
||||||
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
|
.{},
|
||||||
);
|
);
|
||||||
var count: usize = 0;
|
var count: usize = 0;
|
||||||
while (try it.next(alloc)) |_| count += 1;
|
while (try it.next(alloc)) |_| count += 1;
|
||||||
|
|
@ -619,6 +623,7 @@ test "run iterator" {
|
||||||
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
|
.{},
|
||||||
);
|
);
|
||||||
var count: usize = 0;
|
var count: usize = 0;
|
||||||
while (try it.next(alloc)) |_| count += 1;
|
while (try it.next(alloc)) |_| count += 1;
|
||||||
|
|
@ -639,6 +644,7 @@ test "run iterator" {
|
||||||
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
|
.{},
|
||||||
);
|
);
|
||||||
var count: usize = 0;
|
var count: usize = 0;
|
||||||
while (try it.next(alloc)) |_| count += 1;
|
while (try it.next(alloc)) |_| count += 1;
|
||||||
|
|
@ -660,6 +666,7 @@ test "run iterator" {
|
||||||
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
|
.{},
|
||||||
);
|
);
|
||||||
var count: usize = 0;
|
var count: usize = 0;
|
||||||
while (try it.next(alloc)) |_| count += 1;
|
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 } }).?,
|
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
|
.{},
|
||||||
);
|
);
|
||||||
{
|
{
|
||||||
const run = (try it.next(alloc)).?;
|
const run = (try it.next(alloc)).?;
|
||||||
|
|
@ -743,6 +751,7 @@ test "shape" {
|
||||||
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
|
.{},
|
||||||
);
|
);
|
||||||
var count: usize = 0;
|
var count: usize = 0;
|
||||||
while (try it.next(alloc)) |run| {
|
while (try it.next(alloc)) |run| {
|
||||||
|
|
@ -778,6 +787,7 @@ test "shape nerd fonts" {
|
||||||
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
|
.{},
|
||||||
);
|
);
|
||||||
var count: usize = 0;
|
var count: usize = 0;
|
||||||
while (try it.next(alloc)) |run| {
|
while (try it.next(alloc)) |run| {
|
||||||
|
|
@ -806,6 +816,7 @@ test "shape inconsolata ligs" {
|
||||||
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
|
.{},
|
||||||
);
|
);
|
||||||
var count: usize = 0;
|
var count: usize = 0;
|
||||||
while (try it.next(alloc)) |run| {
|
while (try it.next(alloc)) |run| {
|
||||||
|
|
@ -831,6 +842,7 @@ test "shape inconsolata ligs" {
|
||||||
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
|
.{},
|
||||||
);
|
);
|
||||||
var count: usize = 0;
|
var count: usize = 0;
|
||||||
while (try it.next(alloc)) |run| {
|
while (try it.next(alloc)) |run| {
|
||||||
|
|
@ -864,6 +876,7 @@ test "shape monaspace ligs" {
|
||||||
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
|
.{},
|
||||||
);
|
);
|
||||||
var count: usize = 0;
|
var count: usize = 0;
|
||||||
while (try it.next(alloc)) |run| {
|
while (try it.next(alloc)) |run| {
|
||||||
|
|
@ -898,6 +911,7 @@ test "shape left-replaced lig in last run" {
|
||||||
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
|
.{},
|
||||||
);
|
);
|
||||||
var count: usize = 0;
|
var count: usize = 0;
|
||||||
while (try it.next(alloc)) |run| {
|
while (try it.next(alloc)) |run| {
|
||||||
|
|
@ -932,6 +946,7 @@ test "shape left-replaced lig in early run" {
|
||||||
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
|
.{},
|
||||||
);
|
);
|
||||||
|
|
||||||
const run = (try it.next(alloc)).?;
|
const run = (try it.next(alloc)).?;
|
||||||
|
|
@ -963,6 +978,7 @@ test "shape U+3C9 with JB Mono" {
|
||||||
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
|
.{},
|
||||||
);
|
);
|
||||||
|
|
||||||
var run_count: usize = 0;
|
var run_count: usize = 0;
|
||||||
|
|
@ -996,6 +1012,7 @@ test "shape emoji width" {
|
||||||
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
|
.{},
|
||||||
);
|
);
|
||||||
var count: usize = 0;
|
var count: usize = 0;
|
||||||
while (try it.next(alloc)) |run| {
|
while (try it.next(alloc)) |run| {
|
||||||
|
|
@ -1046,6 +1063,7 @@ test "shape emoji width long" {
|
||||||
screen.pages.pin(.{ .screen = .{ .y = 1 } }).?,
|
screen.pages.pin(.{ .screen = .{ .y = 1 } }).?,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
|
.{},
|
||||||
);
|
);
|
||||||
var count: usize = 0;
|
var count: usize = 0;
|
||||||
while (try it.next(alloc)) |run| {
|
while (try it.next(alloc)) |run| {
|
||||||
|
|
@ -1082,6 +1100,7 @@ test "shape variation selector VS15" {
|
||||||
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
|
.{},
|
||||||
);
|
);
|
||||||
var count: usize = 0;
|
var count: usize = 0;
|
||||||
while (try it.next(alloc)) |run| {
|
while (try it.next(alloc)) |run| {
|
||||||
|
|
@ -1117,6 +1136,7 @@ test "shape variation selector VS16" {
|
||||||
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
|
.{},
|
||||||
);
|
);
|
||||||
var count: usize = 0;
|
var count: usize = 0;
|
||||||
while (try it.next(alloc)) |run| {
|
while (try it.next(alloc)) |run| {
|
||||||
|
|
@ -1149,6 +1169,7 @@ test "shape with empty cells in between" {
|
||||||
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
|
.{},
|
||||||
);
|
);
|
||||||
var count: usize = 0;
|
var count: usize = 0;
|
||||||
while (try it.next(alloc)) |run| {
|
while (try it.next(alloc)) |run| {
|
||||||
|
|
@ -1187,6 +1208,7 @@ test "shape Chinese characters" {
|
||||||
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
|
.{},
|
||||||
);
|
);
|
||||||
var count: usize = 0;
|
var count: usize = 0;
|
||||||
while (try it.next(alloc)) |run| {
|
while (try it.next(alloc)) |run| {
|
||||||
|
|
@ -1227,6 +1249,7 @@ test "shape box glyphs" {
|
||||||
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
|
.{},
|
||||||
);
|
);
|
||||||
var count: usize = 0;
|
var count: usize = 0;
|
||||||
while (try it.next(alloc)) |run| {
|
while (try it.next(alloc)) |run| {
|
||||||
|
|
@ -1267,6 +1290,7 @@ test "shape selection boundary" {
|
||||||
false,
|
false,
|
||||||
),
|
),
|
||||||
null,
|
null,
|
||||||
|
.{},
|
||||||
);
|
);
|
||||||
var count: usize = 0;
|
var count: usize = 0;
|
||||||
while (try it.next(alloc)) |run| {
|
while (try it.next(alloc)) |run| {
|
||||||
|
|
@ -1290,6 +1314,7 @@ test "shape selection boundary" {
|
||||||
false,
|
false,
|
||||||
),
|
),
|
||||||
null,
|
null,
|
||||||
|
.{},
|
||||||
);
|
);
|
||||||
var count: usize = 0;
|
var count: usize = 0;
|
||||||
while (try it.next(alloc)) |run| {
|
while (try it.next(alloc)) |run| {
|
||||||
|
|
@ -1313,6 +1338,7 @@ test "shape selection boundary" {
|
||||||
false,
|
false,
|
||||||
),
|
),
|
||||||
null,
|
null,
|
||||||
|
.{},
|
||||||
);
|
);
|
||||||
var count: usize = 0;
|
var count: usize = 0;
|
||||||
while (try it.next(alloc)) |run| {
|
while (try it.next(alloc)) |run| {
|
||||||
|
|
@ -1336,6 +1362,7 @@ test "shape selection boundary" {
|
||||||
false,
|
false,
|
||||||
),
|
),
|
||||||
null,
|
null,
|
||||||
|
.{},
|
||||||
);
|
);
|
||||||
var count: usize = 0;
|
var count: usize = 0;
|
||||||
while (try it.next(alloc)) |run| {
|
while (try it.next(alloc)) |run| {
|
||||||
|
|
@ -1359,6 +1386,7 @@ test "shape selection boundary" {
|
||||||
false,
|
false,
|
||||||
),
|
),
|
||||||
null,
|
null,
|
||||||
|
.{},
|
||||||
);
|
);
|
||||||
var count: usize = 0;
|
var count: usize = 0;
|
||||||
while (try it.next(alloc)) |run| {
|
while (try it.next(alloc)) |run| {
|
||||||
|
|
@ -1391,6 +1419,7 @@ test "shape cursor boundary" {
|
||||||
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
|
.{},
|
||||||
);
|
);
|
||||||
var count: usize = 0;
|
var count: usize = 0;
|
||||||
while (try it.next(alloc)) |run| {
|
while (try it.next(alloc)) |run| {
|
||||||
|
|
@ -1400,61 +1429,126 @@ test "shape cursor boundary" {
|
||||||
try testing.expectEqual(@as(usize, 1), count);
|
try testing.expectEqual(@as(usize, 1), count);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cursor at index 0 is two runs
|
|
||||||
{
|
{
|
||||||
// Get our run iterator
|
// Cursor at index 0 is two runs
|
||||||
var shaper = &testdata.shaper;
|
{
|
||||||
var it = shaper.runIterator(
|
// Get our run iterator
|
||||||
testdata.grid,
|
var shaper = &testdata.shaper;
|
||||||
&screen,
|
var it = shaper.runIterator(
|
||||||
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
testdata.grid,
|
||||||
null,
|
&screen,
|
||||||
0,
|
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
||||||
);
|
null,
|
||||||
var count: usize = 0;
|
0,
|
||||||
while (try it.next(alloc)) |run| {
|
.{ .cursor = true },
|
||||||
count += 1;
|
);
|
||||||
_ = try shaper.shape(run);
|
var count: usize = 0;
|
||||||
|
while (try it.next(alloc)) |run| {
|
||||||
|
count += 1;
|
||||||
|
_ = try shaper.shape(run);
|
||||||
|
}
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
try testing.expectEqual(@as(usize, 2), count);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cursor at index 1 is three runs
|
|
||||||
{
|
{
|
||||||
// Get our run iterator
|
// Cursor at index 1 is three runs
|
||||||
var shaper = &testdata.shaper;
|
{
|
||||||
var it = shaper.runIterator(
|
// Get our run iterator
|
||||||
testdata.grid,
|
var shaper = &testdata.shaper;
|
||||||
&screen,
|
var it = shaper.runIterator(
|
||||||
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
testdata.grid,
|
||||||
null,
|
&screen,
|
||||||
1,
|
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
||||||
);
|
null,
|
||||||
var count: usize = 0;
|
1,
|
||||||
while (try it.next(alloc)) |run| {
|
.{ .cursor = true },
|
||||||
count += 1;
|
);
|
||||||
_ = try shaper.shape(run);
|
var count: usize = 0;
|
||||||
|
while (try it.next(alloc)) |run| {
|
||||||
|
count += 1;
|
||||||
|
_ = try shaper.shape(run);
|
||||||
|
}
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
try testing.expectEqual(@as(usize, 3), count);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cursor at last col is two runs
|
|
||||||
{
|
{
|
||||||
// Get our run iterator
|
// Cursor at last col is two runs
|
||||||
var shaper = &testdata.shaper;
|
{
|
||||||
var it = shaper.runIterator(
|
// Get our run iterator
|
||||||
testdata.grid,
|
var shaper = &testdata.shaper;
|
||||||
&screen,
|
var it = shaper.runIterator(
|
||||||
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
testdata.grid,
|
||||||
null,
|
&screen,
|
||||||
9,
|
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
||||||
);
|
null,
|
||||||
var count: usize = 0;
|
9,
|
||||||
while (try it.next(alloc)) |run| {
|
.{ .cursor = true },
|
||||||
count += 1;
|
);
|
||||||
_ = try shaper.shape(run);
|
var count: usize = 0;
|
||||||
|
while (try it.next(alloc)) |run| {
|
||||||
|
count += 1;
|
||||||
|
_ = try shaper.shape(run);
|
||||||
|
}
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
try testing.expectEqual(@as(usize, 2), count);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1480,6 +1574,7 @@ test "shape cursor boundary and colored emoji" {
|
||||||
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
|
.{},
|
||||||
);
|
);
|
||||||
var count: usize = 0;
|
var count: usize = 0;
|
||||||
while (try it.next(alloc)) |run| {
|
while (try it.next(alloc)) |run| {
|
||||||
|
|
@ -1499,6 +1594,25 @@ test "shape cursor boundary and colored emoji" {
|
||||||
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
||||||
null,
|
null,
|
||||||
0,
|
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;
|
var count: usize = 0;
|
||||||
while (try it.next(alloc)) |run| {
|
while (try it.next(alloc)) |run| {
|
||||||
|
|
@ -1516,6 +1630,25 @@ test "shape cursor boundary and colored emoji" {
|
||||||
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
||||||
null,
|
null,
|
||||||
1,
|
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;
|
var count: usize = 0;
|
||||||
while (try it.next(alloc)) |run| {
|
while (try it.next(alloc)) |run| {
|
||||||
|
|
@ -1546,6 +1679,7 @@ test "shape cell attribute change" {
|
||||||
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
|
.{},
|
||||||
);
|
);
|
||||||
var count: usize = 0;
|
var count: usize = 0;
|
||||||
while (try it.next(alloc)) |run| {
|
while (try it.next(alloc)) |run| {
|
||||||
|
|
@ -1570,6 +1704,7 @@ test "shape cell attribute change" {
|
||||||
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
|
.{},
|
||||||
);
|
);
|
||||||
var count: usize = 0;
|
var count: usize = 0;
|
||||||
while (try it.next(alloc)) |run| {
|
while (try it.next(alloc)) |run| {
|
||||||
|
|
@ -1595,6 +1730,7 @@ test "shape cell attribute change" {
|
||||||
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
|
.{},
|
||||||
);
|
);
|
||||||
var count: usize = 0;
|
var count: usize = 0;
|
||||||
while (try it.next(alloc)) |run| {
|
while (try it.next(alloc)) |run| {
|
||||||
|
|
@ -1620,6 +1756,7 @@ test "shape cell attribute change" {
|
||||||
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
|
.{},
|
||||||
);
|
);
|
||||||
var count: usize = 0;
|
var count: usize = 0;
|
||||||
while (try it.next(alloc)) |run| {
|
while (try it.next(alloc)) |run| {
|
||||||
|
|
@ -1644,6 +1781,7 @@ test "shape cell attribute change" {
|
||||||
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
|
.{},
|
||||||
);
|
);
|
||||||
var count: usize = 0;
|
var count: usize = 0;
|
||||||
while (try it.next(alloc)) |run| {
|
while (try it.next(alloc)) |run| {
|
||||||
|
|
@ -1684,6 +1822,7 @@ test "shape high plane sprite font codepoint" {
|
||||||
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
|
.{},
|
||||||
);
|
);
|
||||||
// We should get one run
|
// We should get one run
|
||||||
const run = (try it.next(alloc)).?;
|
const run = (try it.next(alloc)).?;
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ const Allocator = std.mem.Allocator;
|
||||||
const harfbuzz = @import("harfbuzz");
|
const harfbuzz = @import("harfbuzz");
|
||||||
const font = @import("../main.zig");
|
const font = @import("../main.zig");
|
||||||
const terminal = @import("../../terminal/main.zig");
|
const terminal = @import("../../terminal/main.zig");
|
||||||
|
const config = @import("../../config.zig");
|
||||||
const Feature = font.shape.Feature;
|
const Feature = font.shape.Feature;
|
||||||
const FeatureList = font.shape.FeatureList;
|
const FeatureList = font.shape.FeatureList;
|
||||||
const default_features = font.shape.default_features;
|
const default_features = font.shape.default_features;
|
||||||
|
|
@ -94,6 +95,7 @@ pub const Shaper = struct {
|
||||||
row: terminal.Pin,
|
row: terminal.Pin,
|
||||||
selection: ?terminal.Selection,
|
selection: ?terminal.Selection,
|
||||||
cursor_x: ?usize,
|
cursor_x: ?usize,
|
||||||
|
break_config: config.FontShapingBreak,
|
||||||
) font.shape.RunIterator {
|
) font.shape.RunIterator {
|
||||||
return .{
|
return .{
|
||||||
.hooks = .{ .shaper = self },
|
.hooks = .{ .shaper = self },
|
||||||
|
|
@ -102,6 +104,7 @@ pub const Shaper = struct {
|
||||||
.row = row,
|
.row = row,
|
||||||
.selection = selection,
|
.selection = selection,
|
||||||
.cursor_x = cursor_x,
|
.cursor_x = cursor_x,
|
||||||
|
.break_config = break_config,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -231,6 +234,7 @@ test "run iterator" {
|
||||||
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
|
.{},
|
||||||
);
|
);
|
||||||
var count: usize = 0;
|
var count: usize = 0;
|
||||||
while (try it.next(alloc)) |_| count += 1;
|
while (try it.next(alloc)) |_| count += 1;
|
||||||
|
|
@ -250,6 +254,7 @@ test "run iterator" {
|
||||||
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
|
.{},
|
||||||
);
|
);
|
||||||
var count: usize = 0;
|
var count: usize = 0;
|
||||||
while (try it.next(alloc)) |_| count += 1;
|
while (try it.next(alloc)) |_| count += 1;
|
||||||
|
|
@ -270,6 +275,7 @@ test "run iterator" {
|
||||||
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
|
.{},
|
||||||
);
|
);
|
||||||
var count: usize = 0;
|
var count: usize = 0;
|
||||||
while (try it.next(alloc)) |_| {
|
while (try it.next(alloc)) |_| {
|
||||||
|
|
@ -322,6 +328,7 @@ test "run iterator: empty cells with background set" {
|
||||||
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
|
.{},
|
||||||
);
|
);
|
||||||
{
|
{
|
||||||
const run = (try it.next(alloc)).?;
|
const run = (try it.next(alloc)).?;
|
||||||
|
|
@ -359,6 +366,7 @@ test "shape" {
|
||||||
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
|
.{},
|
||||||
);
|
);
|
||||||
var count: usize = 0;
|
var count: usize = 0;
|
||||||
while (try it.next(alloc)) |run| {
|
while (try it.next(alloc)) |run| {
|
||||||
|
|
@ -388,6 +396,7 @@ test "shape inconsolata ligs" {
|
||||||
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
|
.{},
|
||||||
);
|
);
|
||||||
var count: usize = 0;
|
var count: usize = 0;
|
||||||
while (try it.next(alloc)) |run| {
|
while (try it.next(alloc)) |run| {
|
||||||
|
|
@ -413,6 +422,7 @@ test "shape inconsolata ligs" {
|
||||||
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
|
.{},
|
||||||
);
|
);
|
||||||
var count: usize = 0;
|
var count: usize = 0;
|
||||||
while (try it.next(alloc)) |run| {
|
while (try it.next(alloc)) |run| {
|
||||||
|
|
@ -446,6 +456,7 @@ test "shape monaspace ligs" {
|
||||||
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
|
.{},
|
||||||
);
|
);
|
||||||
var count: usize = 0;
|
var count: usize = 0;
|
||||||
while (try it.next(alloc)) |run| {
|
while (try it.next(alloc)) |run| {
|
||||||
|
|
@ -482,6 +493,7 @@ test "shape arabic forced LTR" {
|
||||||
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
|
.{},
|
||||||
);
|
);
|
||||||
var count: usize = 0;
|
var count: usize = 0;
|
||||||
while (try it.next(alloc)) |run| {
|
while (try it.next(alloc)) |run| {
|
||||||
|
|
@ -519,6 +531,7 @@ test "shape emoji width" {
|
||||||
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
|
.{},
|
||||||
);
|
);
|
||||||
var count: usize = 0;
|
var count: usize = 0;
|
||||||
while (try it.next(alloc)) |run| {
|
while (try it.next(alloc)) |run| {
|
||||||
|
|
@ -571,6 +584,7 @@ test "shape emoji width long" {
|
||||||
screen.pages.pin(.{ .screen = .{ .y = 1 } }).?,
|
screen.pages.pin(.{ .screen = .{ .y = 1 } }).?,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
|
.{},
|
||||||
);
|
);
|
||||||
var count: usize = 0;
|
var count: usize = 0;
|
||||||
while (try it.next(alloc)) |run| {
|
while (try it.next(alloc)) |run| {
|
||||||
|
|
@ -609,6 +623,7 @@ test "shape variation selector VS15" {
|
||||||
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
|
.{},
|
||||||
);
|
);
|
||||||
var count: usize = 0;
|
var count: usize = 0;
|
||||||
while (try it.next(alloc)) |run| {
|
while (try it.next(alloc)) |run| {
|
||||||
|
|
@ -646,6 +661,7 @@ test "shape variation selector VS16" {
|
||||||
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
|
.{},
|
||||||
);
|
);
|
||||||
var count: usize = 0;
|
var count: usize = 0;
|
||||||
while (try it.next(alloc)) |run| {
|
while (try it.next(alloc)) |run| {
|
||||||
|
|
@ -680,6 +696,7 @@ test "shape with empty cells in between" {
|
||||||
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
|
.{},
|
||||||
);
|
);
|
||||||
var count: usize = 0;
|
var count: usize = 0;
|
||||||
while (try it.next(alloc)) |run| {
|
while (try it.next(alloc)) |run| {
|
||||||
|
|
@ -718,6 +735,7 @@ test "shape Chinese characters" {
|
||||||
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
|
.{},
|
||||||
);
|
);
|
||||||
var count: usize = 0;
|
var count: usize = 0;
|
||||||
while (try it.next(alloc)) |run| {
|
while (try it.next(alloc)) |run| {
|
||||||
|
|
@ -758,6 +776,7 @@ test "shape box glyphs" {
|
||||||
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
|
.{},
|
||||||
);
|
);
|
||||||
var count: usize = 0;
|
var count: usize = 0;
|
||||||
while (try it.next(alloc)) |run| {
|
while (try it.next(alloc)) |run| {
|
||||||
|
|
@ -799,6 +818,7 @@ test "shape selection boundary" {
|
||||||
false,
|
false,
|
||||||
),
|
),
|
||||||
null,
|
null,
|
||||||
|
.{},
|
||||||
);
|
);
|
||||||
var count: usize = 0;
|
var count: usize = 0;
|
||||||
while (try it.next(alloc)) |run| {
|
while (try it.next(alloc)) |run| {
|
||||||
|
|
@ -822,6 +842,7 @@ test "shape selection boundary" {
|
||||||
false,
|
false,
|
||||||
),
|
),
|
||||||
null,
|
null,
|
||||||
|
.{},
|
||||||
);
|
);
|
||||||
var count: usize = 0;
|
var count: usize = 0;
|
||||||
while (try it.next(alloc)) |run| {
|
while (try it.next(alloc)) |run| {
|
||||||
|
|
@ -845,6 +866,7 @@ test "shape selection boundary" {
|
||||||
false,
|
false,
|
||||||
),
|
),
|
||||||
null,
|
null,
|
||||||
|
.{},
|
||||||
);
|
);
|
||||||
var count: usize = 0;
|
var count: usize = 0;
|
||||||
while (try it.next(alloc)) |run| {
|
while (try it.next(alloc)) |run| {
|
||||||
|
|
@ -868,6 +890,7 @@ test "shape selection boundary" {
|
||||||
false,
|
false,
|
||||||
),
|
),
|
||||||
null,
|
null,
|
||||||
|
.{},
|
||||||
);
|
);
|
||||||
var count: usize = 0;
|
var count: usize = 0;
|
||||||
while (try it.next(alloc)) |run| {
|
while (try it.next(alloc)) |run| {
|
||||||
|
|
@ -891,6 +914,7 @@ test "shape selection boundary" {
|
||||||
false,
|
false,
|
||||||
),
|
),
|
||||||
null,
|
null,
|
||||||
|
.{},
|
||||||
);
|
);
|
||||||
var count: usize = 0;
|
var count: usize = 0;
|
||||||
while (try it.next(alloc)) |run| {
|
while (try it.next(alloc)) |run| {
|
||||||
|
|
@ -923,6 +947,7 @@ test "shape cursor boundary" {
|
||||||
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
|
.{},
|
||||||
);
|
);
|
||||||
var count: usize = 0;
|
var count: usize = 0;
|
||||||
while (try it.next(alloc)) |run| {
|
while (try it.next(alloc)) |run| {
|
||||||
|
|
@ -932,61 +957,126 @@ test "shape cursor boundary" {
|
||||||
try testing.expectEqual(@as(usize, 1), count);
|
try testing.expectEqual(@as(usize, 1), count);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cursor at index 0 is two runs
|
|
||||||
{
|
{
|
||||||
// Get our run iterator
|
// Cursor at index 0 is two runs
|
||||||
var shaper = &testdata.shaper;
|
{
|
||||||
var it = shaper.runIterator(
|
// Get our run iterator
|
||||||
testdata.grid,
|
var shaper = &testdata.shaper;
|
||||||
&screen,
|
var it = shaper.runIterator(
|
||||||
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
testdata.grid,
|
||||||
null,
|
&screen,
|
||||||
0,
|
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
||||||
);
|
null,
|
||||||
var count: usize = 0;
|
0,
|
||||||
while (try it.next(alloc)) |run| {
|
.{ .cursor = true },
|
||||||
count += 1;
|
);
|
||||||
_ = try shaper.shape(run);
|
var count: usize = 0;
|
||||||
|
while (try it.next(alloc)) |run| {
|
||||||
|
count += 1;
|
||||||
|
_ = try shaper.shape(run);
|
||||||
|
}
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
try testing.expectEqual(@as(usize, 2), count);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cursor at index 1 is three runs
|
|
||||||
{
|
{
|
||||||
// Get our run iterator
|
// Cursor at index 1 is three runs
|
||||||
var shaper = &testdata.shaper;
|
{
|
||||||
var it = shaper.runIterator(
|
// Get our run iterator
|
||||||
testdata.grid,
|
var shaper = &testdata.shaper;
|
||||||
&screen,
|
var it = shaper.runIterator(
|
||||||
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
testdata.grid,
|
||||||
null,
|
&screen,
|
||||||
1,
|
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
||||||
);
|
null,
|
||||||
var count: usize = 0;
|
1,
|
||||||
while (try it.next(alloc)) |run| {
|
.{ .cursor = true },
|
||||||
count += 1;
|
);
|
||||||
_ = try shaper.shape(run);
|
var count: usize = 0;
|
||||||
|
while (try it.next(alloc)) |run| {
|
||||||
|
count += 1;
|
||||||
|
_ = try shaper.shape(run);
|
||||||
|
}
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
try testing.expectEqual(@as(usize, 3), count);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cursor at last col is two runs
|
|
||||||
{
|
{
|
||||||
// Get our run iterator
|
// Cursor at last col is two runs
|
||||||
var shaper = &testdata.shaper;
|
{
|
||||||
var it = shaper.runIterator(
|
// Get our run iterator
|
||||||
testdata.grid,
|
var shaper = &testdata.shaper;
|
||||||
&screen,
|
var it = shaper.runIterator(
|
||||||
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
testdata.grid,
|
||||||
null,
|
&screen,
|
||||||
9,
|
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
||||||
);
|
null,
|
||||||
var count: usize = 0;
|
9,
|
||||||
while (try it.next(alloc)) |run| {
|
.{ .cursor = true },
|
||||||
count += 1;
|
);
|
||||||
_ = try shaper.shape(run);
|
var count: usize = 0;
|
||||||
|
while (try it.next(alloc)) |run| {
|
||||||
|
count += 1;
|
||||||
|
_ = try shaper.shape(run);
|
||||||
|
}
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
try testing.expectEqual(@as(usize, 2), count);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1012,6 +1102,7 @@ test "shape cursor boundary and colored emoji" {
|
||||||
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
|
.{},
|
||||||
);
|
);
|
||||||
var count: usize = 0;
|
var count: usize = 0;
|
||||||
while (try it.next(alloc)) |run| {
|
while (try it.next(alloc)) |run| {
|
||||||
|
|
@ -1031,6 +1122,25 @@ test "shape cursor boundary and colored emoji" {
|
||||||
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
||||||
null,
|
null,
|
||||||
0,
|
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;
|
var count: usize = 0;
|
||||||
while (try it.next(alloc)) |run| {
|
while (try it.next(alloc)) |run| {
|
||||||
|
|
@ -1048,6 +1158,25 @@ test "shape cursor boundary and colored emoji" {
|
||||||
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
||||||
null,
|
null,
|
||||||
1,
|
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;
|
var count: usize = 0;
|
||||||
while (try it.next(alloc)) |run| {
|
while (try it.next(alloc)) |run| {
|
||||||
|
|
@ -1078,6 +1207,7 @@ test "shape cell attribute change" {
|
||||||
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
|
.{},
|
||||||
);
|
);
|
||||||
var count: usize = 0;
|
var count: usize = 0;
|
||||||
while (try it.next(alloc)) |run| {
|
while (try it.next(alloc)) |run| {
|
||||||
|
|
@ -1102,6 +1232,7 @@ test "shape cell attribute change" {
|
||||||
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
|
.{},
|
||||||
);
|
);
|
||||||
var count: usize = 0;
|
var count: usize = 0;
|
||||||
while (try it.next(alloc)) |run| {
|
while (try it.next(alloc)) |run| {
|
||||||
|
|
@ -1127,6 +1258,7 @@ test "shape cell attribute change" {
|
||||||
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
|
.{},
|
||||||
);
|
);
|
||||||
var count: usize = 0;
|
var count: usize = 0;
|
||||||
while (try it.next(alloc)) |run| {
|
while (try it.next(alloc)) |run| {
|
||||||
|
|
@ -1152,6 +1284,7 @@ test "shape cell attribute change" {
|
||||||
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
|
.{},
|
||||||
);
|
);
|
||||||
var count: usize = 0;
|
var count: usize = 0;
|
||||||
while (try it.next(alloc)) |run| {
|
while (try it.next(alloc)) |run| {
|
||||||
|
|
@ -1176,6 +1309,7 @@ test "shape cell attribute change" {
|
||||||
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
|
.{},
|
||||||
);
|
);
|
||||||
var count: usize = 0;
|
var count: usize = 0;
|
||||||
while (try it.next(alloc)) |run| {
|
while (try it.next(alloc)) |run| {
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ const assert = std.debug.assert;
|
||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
const trace = @import("tracy").trace;
|
const trace = @import("tracy").trace;
|
||||||
const font = @import("../main.zig");
|
const font = @import("../main.zig");
|
||||||
|
const config = @import("../../config.zig");
|
||||||
const Face = font.Face;
|
const Face = font.Face;
|
||||||
const Collection = font.Collection;
|
const Collection = font.Collection;
|
||||||
const DeferredFace = font.DeferredFace;
|
const DeferredFace = font.DeferredFace;
|
||||||
|
|
@ -75,6 +76,7 @@ pub const Shaper = struct {
|
||||||
row: terminal.Pin,
|
row: terminal.Pin,
|
||||||
selection: ?terminal.Selection,
|
selection: ?terminal.Selection,
|
||||||
cursor_x: ?usize,
|
cursor_x: ?usize,
|
||||||
|
break_config: config.FontShapingBreak,
|
||||||
) font.shape.RunIterator {
|
) font.shape.RunIterator {
|
||||||
return .{
|
return .{
|
||||||
.hooks = .{ .shaper = self },
|
.hooks = .{ .shaper = self },
|
||||||
|
|
@ -83,6 +85,7 @@ pub const Shaper = struct {
|
||||||
.row = row,
|
.row = row,
|
||||||
.selection = selection,
|
.selection = selection,
|
||||||
.cursor_x = cursor_x,
|
.cursor_x = cursor_x,
|
||||||
|
.break_config = break_config,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,8 @@ const shape = @import("../shape.zig");
|
||||||
const terminal = @import("../../terminal/main.zig");
|
const terminal = @import("../../terminal/main.zig");
|
||||||
const autoHash = std.hash.autoHash;
|
const autoHash = std.hash.autoHash;
|
||||||
const Hasher = std.hash.Wyhash;
|
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
|
/// 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
|
/// until the next run is created. A text run never goes across multiple
|
||||||
|
|
@ -40,6 +42,7 @@ pub const RunIterator = struct {
|
||||||
row: terminal.Pin,
|
row: terminal.Pin,
|
||||||
selection: ?terminal.Selection = null,
|
selection: ?terminal.Selection = null,
|
||||||
cursor_x: ?usize = null,
|
cursor_x: ?usize = null,
|
||||||
|
break_config: configpkg.FontShapingBreak,
|
||||||
i: usize = 0,
|
i: usize = 0,
|
||||||
|
|
||||||
pub fn next(self: *RunIterator, alloc: Allocator) !?TextRun {
|
pub fn next(self: *RunIterator, alloc: Allocator) !?TextRun {
|
||||||
|
|
@ -175,36 +178,38 @@ pub const RunIterator = struct {
|
||||||
break :emoji null;
|
break :emoji null;
|
||||||
};
|
};
|
||||||
|
|
||||||
// If our cursor is on this line then we break the run around the
|
if (self.break_config.cursor) {
|
||||||
// cursor. This means that any row with a cursor has at least
|
// If our cursor is on this line then we break the run around the
|
||||||
// three breaks: before, exactly the cursor, and after.
|
// 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
|
// We do not break a cell that is exactly the grapheme. If there
|
||||||
// break. This creates an effect where hovering over an emoji
|
// are cells following that contain joiners, we allow those to
|
||||||
// such as a skin-tone emoji is fine, but hovering over the
|
// break. This creates an effect where hovering over an emoji
|
||||||
// joiners will show the joiners allowing you to modify the
|
// such as a skin-tone emoji is fine, but hovering over the
|
||||||
// emoji.
|
// joiners will show the joiners allowing you to modify the
|
||||||
if (!cell.hasGrapheme()) {
|
// emoji.
|
||||||
if (self.cursor_x) |cursor_x| {
|
if (!cell.hasGrapheme()) {
|
||||||
// Exactly: self.i is the cursor and we iterated once. This
|
if (self.cursor_x) |cursor_x| {
|
||||||
// means that we started exactly at the cursor and did at
|
// Exactly: self.i is the cursor and we iterated once. This
|
||||||
// exactly one iteration. Why exactly one? Because we may
|
// means that we started exactly at the cursor and did at
|
||||||
// start at our cursor but do many if our cursor is exactly
|
// exactly one iteration. Why exactly one? Because we may
|
||||||
// on an emoji.
|
// start at our cursor but do many if our cursor is exactly
|
||||||
if (self.i == cursor_x and j == self.i + 1) break;
|
// on an emoji.
|
||||||
|
if (self.i == cursor_x and j == self.i + 1) break;
|
||||||
|
|
||||||
// Before: up to and not including the cursor. This means
|
// Before: up to and not including the cursor. This means
|
||||||
// that we started before the cursor (self.i < cursor_x)
|
// that we started before the cursor (self.i < cursor_x)
|
||||||
// and j is now at the cursor meaning we haven't yet processed
|
// and j is now at the cursor meaning we haven't yet processed
|
||||||
// the cursor.
|
// the cursor.
|
||||||
if (self.i < cursor_x and j == cursor_x) {
|
if (self.i < cursor_x and j == cursor_x) {
|
||||||
assert(j > 0);
|
assert(j > 0);
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// After: after the cursor. We don't need to do anything
|
||||||
|
// special, we just let the run complete.
|
||||||
}
|
}
|
||||||
|
|
||||||
// After: after the cursor. We don't need to do anything
|
|
||||||
// special, we just let the run complete.
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ const Allocator = std.mem.Allocator;
|
||||||
const ziglyph = @import("ziglyph");
|
const ziglyph = @import("ziglyph");
|
||||||
const font = @import("../main.zig");
|
const font = @import("../main.zig");
|
||||||
const terminal = @import("../../terminal/main.zig");
|
const terminal = @import("../../terminal/main.zig");
|
||||||
|
const config = @import("../../config.zig");
|
||||||
|
|
||||||
const log = std.log.scoped(.font_shaper);
|
const log = std.log.scoped(.font_shaper);
|
||||||
|
|
||||||
|
|
@ -65,6 +66,7 @@ pub const Shaper = struct {
|
||||||
row: terminal.Screen.Row,
|
row: terminal.Screen.Row,
|
||||||
selection: ?terminal.Selection,
|
selection: ?terminal.Selection,
|
||||||
cursor_x: ?usize,
|
cursor_x: ?usize,
|
||||||
|
break_config: config.FontShapingBreak,
|
||||||
) font.shape.RunIterator {
|
) font.shape.RunIterator {
|
||||||
return .{
|
return .{
|
||||||
.hooks = .{ .shaper = self },
|
.hooks = .{ .shaper = self },
|
||||||
|
|
@ -72,6 +74,7 @@ pub const Shaper = struct {
|
||||||
.row = row,
|
.row = row,
|
||||||
.selection = selection,
|
.selection = selection,
|
||||||
.cursor_x = cursor_x,
|
.cursor_x = cursor_x,
|
||||||
|
.break_config = break_config,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue