diff --git a/src/font/embedded.zig b/src/font/embedded.zig index 2f496f86a..098aa3eb4 100644 --- a/src/font/embedded.zig +++ b/src/font/embedded.zig @@ -14,6 +14,7 @@ pub const emoji = @embedFile("res/NotoColorEmoji.ttf"); pub const emoji_text = @embedFile("res/NotoEmoji-Regular.ttf"); /// Fonts with general properties +pub const arabic = @embedFile("res/KawkabMono-Regular.ttf"); pub const variable = @embedFile("res/Lilex-VF.ttf"); /// Font with nerd fonts embedded. diff --git a/src/font/res/KawkabMono-Regular.ttf b/src/font/res/KawkabMono-Regular.ttf new file mode 100644 index 000000000..4841678de Binary files /dev/null and b/src/font/res/KawkabMono-Regular.ttf differ diff --git a/src/font/shaper/harfbuzz.zig b/src/font/shaper/harfbuzz.zig index b3c8400b3..ccb422f20 100644 --- a/src/font/shaper/harfbuzz.zig +++ b/src/font/shaper/harfbuzz.zig @@ -190,6 +190,11 @@ pub const Shaper = struct { // Reset the buffer for our current run self.shaper.hb_buf.reset(); self.shaper.hb_buf.setContentType(.unicode); + + // We don't support RTL text because RTL in terminals is messy. + // Its something we want to improve. For now, we force LTR because + // our renderers assume a strictly increasing X value. + self.shaper.hb_buf.setDirection(.ltr); } pub fn addCodepoint(self: RunIteratorHook, cp: u32, cluster: u32) !void { @@ -453,6 +458,46 @@ test "shape monaspace ligs" { } } +// Ghostty doesn't currently support RTL and our renderers assume +// that cells are in strict LTR order. This means that we need to +// force RTL text to be LTR for rendering. This test ensures that +// we are correctly forcing RTL text to be LTR. +test "shape arabic forced LTR" { + const testing = std.testing; + const alloc = testing.allocator; + + var testdata = try testShaperWithFont(alloc, .arabic); + defer testdata.deinit(); + + var screen = try terminal.Screen.init(alloc, 120, 30, 0); + defer screen.deinit(); + try screen.testWriteString(@embedFile("testdata/arabic.txt")); + + var shaper = &testdata.shaper; + var it = shaper.runIterator( + testdata.grid, + &screen, + screen.pages.pin(.{ .screen = .{ .y = 0 } }).?, + null, + null, + ); + var count: usize = 0; + while (try it.next(alloc)) |run| { + count += 1; + try testing.expectEqual(@as(usize, 25), run.cells); + + const cells = try shaper.shape(run); + try testing.expectEqual(@as(usize, 25), cells.len); + + var x: u16 = cells[0].x; + for (cells[1..]) |cell| { + try testing.expectEqual(x + 1, cell.x); + x = cell.x; + } + } + try testing.expectEqual(@as(usize, 1), count); +} + test "shape emoji width" { const testing = std.testing; const alloc = testing.allocator; @@ -1146,6 +1191,7 @@ const TestShaper = struct { const TestFont = enum { inconsolata, monaspace_neon, + arabic, }; /// Helper to return a fully initialized shaper. @@ -1159,6 +1205,7 @@ fn testShaperWithFont(alloc: Allocator, font_req: TestFont) !TestShaper { const testFont = switch (font_req) { .inconsolata => font.embedded.inconsolata, .monaspace_neon => font.embedded.monaspace_neon, + .arabic => font.embedded.arabic, }; var lib = try Library.init(); diff --git a/src/font/shaper/testdata/arabic.txt b/src/font/shaper/testdata/arabic.txt new file mode 100644 index 000000000..d450c7623 --- /dev/null +++ b/src/font/shaper/testdata/arabic.txt @@ -0,0 +1,3 @@ +غريبه لاني عربي أبا عن جد +واتكلم الانجليزية بطلاقة اكثر من ٢٥ سنه +ومع هذا اجد العربيه افضل لان فيها الكثير من المفردات الاكثر دقه بالوصف