From f40eb3663ac6c1c9aac22a7142f30c7b14e1da46 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 6 Sep 2022 13:56:20 -0700 Subject: [PATCH] kind of handle VS15/16 better, its not blank anymore, but its wrong --- src/font/GroupCache.zig | 2 +- src/font/Shaper.zig | 124 ++++++++++++++++++++++++++++------------ 2 files changed, 87 insertions(+), 39 deletions(-) diff --git a/src/font/GroupCache.zig b/src/font/GroupCache.zig index f990011fe..06e7fc9de 100644 --- a/src/font/GroupCache.zig +++ b/src/font/GroupCache.zig @@ -158,7 +158,7 @@ pub fn indexForCodepoint( if (gop.found_existing) return gop.value_ptr.*; // Load a value and cache it. This even caches negative matches. - const value = self.group.indexForCodepoint(cp, style, null); + const value = self.group.indexForCodepoint(cp, style, p); gop.value_ptr.* = value; return value; } diff --git a/src/font/Shaper.zig b/src/font/Shaper.zig index 5a82e3112..2ad7aec95 100644 --- a/src/font/Shaper.zig +++ b/src/font/Shaper.zig @@ -11,6 +11,7 @@ const Group = @import("main.zig").Group; const GroupCache = @import("main.zig").GroupCache; const Library = @import("main.zig").Library; const Style = @import("main.zig").Style; +const Presentation = @import("main.zig").Presentation; const terminal = @import("../terminal/main.zig"); const log = std.log.scoped(.font_shaper); @@ -85,12 +86,12 @@ pub fn shape(self: *Shaper, run: TextRun) ![]Cell { // we're the last cell, this is remaining otherwise we use cluster numbers // to detect since we set the cluster number to the column it // originated. - const cp_width = if (i == info.len - 1) + const cp_width = @maximum(1, if (i == info.len - 1) (run.max_cluster - v.cluster) + 1 // + 1 because we're zero indexed else width: { const next_cluster = info[i + 1].cluster; break :width next_cluster - v.cluster; - }; + }); self.cell_buf[i] = .{ .x = x, @@ -174,6 +175,17 @@ pub const RunIterator = struct { else .regular; + // Determine the presentation format for this glyph. + const presentation: ?Presentation = if (cell.attrs.grapheme) p: { + var it = self.row.codepointIterator(j); + while (it.next()) |cp| { + if (cp == 0xFE0E) break :p Presentation.text; + if (cp == 0xFE0F) break :p Presentation.emoji; + } + + break :p null; + } else null; + // Determine the font for this cell. We'll use fallbacks // manually here to try replacement chars and then a space // for unknown glyphs. @@ -181,14 +193,14 @@ pub const RunIterator = struct { alloc, cell.char, style, - null, + presentation, )) orelse (try self.shaper.group.indexForCodepoint( alloc, 0xFFFD, style, - null, + .text, )) orelse - try self.shaper.group.indexForCodepoint(alloc, ' ', style, null); + try self.shaper.group.indexForCodepoint(alloc, ' ', style, .text); const font_idx = font_idx_opt.?; //log.warn("char={x} idx={}", .{ cell.char, font_idx }); if (j == self.i) current_font = font_idx; @@ -380,39 +392,73 @@ test "shape emoji width" { } } -// test "shape variation selector VS15" { -// const testing = std.testing; -// const alloc = testing.allocator; -// -// var testdata = try testShaper(alloc); -// defer testdata.deinit(); -// -// var buf: [32]u8 = undefined; -// var buf_idx: usize = 0; -// buf_idx += try std.unicode.utf8Encode(0x263A, buf[buf_idx..]); // White smiling face (text) -// buf_idx += try std.unicode.utf8Encode(0xFE0F, buf[buf_idx..]); // ZWJ to force color -// -// // Make a screen with some data -// var screen = try terminal.Screen.init(alloc, 3, 10, 0); -// defer screen.deinit(); -// try screen.testWriteString(buf[0..buf_idx]); -// -// // Get our run iterator -// var shaper = testdata.shaper; -// var it = shaper.runIterator(screen.getRow(.{ .screen = 0 })); -// var count: usize = 0; -// while (try it.next(alloc)) |run| { -// count += 1; -// //try testing.expectEqual(@as(u32, 2), shaper.hb_buf.getLength()); -// -// const cells = try shaper.shape(run); -// try testing.expectEqual(@as(usize, 2), cells.len); -// log.warn("WHAT={}", .{cells[0]}); -// log.warn("WHAT={}", .{cells[1]}); -// try testing.expectEqual(@as(u8, 2), cells[0].width); -// } -// try testing.expectEqual(@as(usize, 1), count); -// } +test "shape variation selector VS15" { + const testing = std.testing; + const alloc = testing.allocator; + + var testdata = try testShaper(alloc); + defer testdata.deinit(); + + var buf: [32]u8 = undefined; + var buf_idx: usize = 0; + buf_idx += try std.unicode.utf8Encode(0x270C, buf[buf_idx..]); // Victory sign (default text) + buf_idx += try std.unicode.utf8Encode(0xFE0E, buf[buf_idx..]); // ZWJ to force text + + // Make a screen with some data + var screen = try terminal.Screen.init(alloc, 3, 10, 0); + defer screen.deinit(); + try screen.testWriteString(buf[0..buf_idx]); + + // Get our run iterator + var shaper = testdata.shaper; + var it = shaper.runIterator(screen.getRow(.{ .screen = 0 })); + var count: usize = 0; + while (try it.next(alloc)) |run| { + count += 1; + try testing.expectEqual(@as(u32, 2), shaper.hb_buf.getLength()); + + const cells = try shaper.shape(run); + try testing.expectEqual(@as(usize, 2), cells.len); + try testing.expectEqual(@as(u8, 1), cells[0].width); + try testing.expectEqual(@as(u8, 1), cells[1].width); + } + try testing.expectEqual(@as(usize, 1), count); +} + +test "shape variation selector VS16" { + const testing = std.testing; + const alloc = testing.allocator; + + var testdata = try testShaper(alloc); + defer testdata.deinit(); + + var buf: [32]u8 = undefined; + var buf_idx: usize = 0; + buf_idx += try std.unicode.utf8Encode(0x270C, buf[buf_idx..]); // Victory sign (default text) + buf_idx += try std.unicode.utf8Encode(0xFE0F, buf[buf_idx..]); // ZWJ to force color + + // Make a screen with some data + var screen = try terminal.Screen.init(alloc, 3, 10, 0); + defer screen.deinit(); + try screen.testWriteString(buf[0..buf_idx]); + + // Get our run iterator + var shaper = testdata.shaper; + var it = shaper.runIterator(screen.getRow(.{ .screen = 0 })); + var count: usize = 0; + while (try it.next(alloc)) |run| { + count += 1; + try testing.expectEqual(@as(u32, 2), shaper.hb_buf.getLength()); + + const cells = try shaper.shape(run); + try testing.expectEqual(@as(usize, 1), cells.len); + + // TODO: this should pass, victory sign is width one but + // after forcing color it is width 2 + //try testing.expectEqual(@as(u8, 2), cells[0].width); + } + try testing.expectEqual(@as(usize, 1), count); +} const TestShaper = struct { alloc: Allocator, @@ -434,6 +480,7 @@ const TestShaper = struct { fn testShaper(alloc: Allocator) !TestShaper { const testFont = @import("test.zig").fontRegular; const testEmoji = @import("test.zig").fontEmoji; + const testEmojiText = @import("test.zig").fontEmojiText; var lib = try Library.init(); errdefer lib.deinit(); @@ -446,6 +493,7 @@ fn testShaper(alloc: Allocator) !TestShaper { // Setup group try cache_ptr.group.addFace(alloc, .regular, try Face.init(lib, testFont, .{ .points = 12 })); try cache_ptr.group.addFace(alloc, .regular, try Face.init(lib, testEmoji, .{ .points = 12 })); + try cache_ptr.group.addFace(alloc, .regular, try Face.init(lib, testEmojiText, .{ .points = 12 })); var cell_buf = try alloc.alloc(Cell, 80); errdefer alloc.free(cell_buf);