diff --git a/src/font/shaper/coretext.zig b/src/font/shaper/coretext.zig index e084a68c9..ec64fe6eb 100644 --- a/src/font/shaper/coretext.zig +++ b/src/font/shaper/coretext.zig @@ -1015,25 +1015,35 @@ test "shape emoji width long" { var testdata = try testShaper(alloc); defer testdata.deinit(); - var buf: [32]u8 = undefined; - var buf_idx: usize = 0; - buf_idx += try std.unicode.utf8Encode(0x1F9D4, buf[buf_idx..]); // man: beard - buf_idx += try std.unicode.utf8Encode(0x1F3FB, buf[buf_idx..]); // light skin tone (Fitz 1-2) - buf_idx += try std.unicode.utf8Encode(0x200D, buf[buf_idx..]); // ZWJ - buf_idx += try std.unicode.utf8Encode(0x2642, buf[buf_idx..]); // male sign - buf_idx += try std.unicode.utf8Encode(0xFE0F, buf[buf_idx..]); // emoji representation - - // Make a screen with some data + // Make a screen and add a long emoji sequence to it. var screen = try terminal.Screen.init(alloc, 30, 3, 0); defer screen.deinit(); - try screen.testWriteString(buf[0..buf_idx]); + + var page = screen.pages.pages.first.?.data; + var row = page.getRow(1); + const cell = &row.cells.ptr(page.memory)[0]; + cell.* = .{ + .content_tag = .codepoint, + .content = .{ .codepoint = 0x1F9D4 }, // Person with beard + }; + var graphemes = [_]u21{ + 0x1F3FB, // Light skin tone (Fitz 1-2) + 0x200D, // ZWJ + 0x2642, // Male sign + 0xFE0F, // Emoji presentation selector + }; + try page.setGraphemes( + row, + cell, + graphemes[0..], + ); // Get our run iterator var shaper = &testdata.shaper; var it = shaper.runIterator( testdata.grid, &screen, - screen.pages.pin(.{ .screen = .{ .y = 0 } }).?, + screen.pages.pin(.{ .screen = .{ .y = 1 } }).?, null, null, ); diff --git a/src/font/shaper/harfbuzz.zig b/src/font/shaper/harfbuzz.zig index 97292b9b0..b284dc140 100644 --- a/src/font/shaper/harfbuzz.zig +++ b/src/font/shaper/harfbuzz.zig @@ -540,25 +540,35 @@ test "shape emoji width long" { var testdata = try testShaper(alloc); defer testdata.deinit(); - var buf: [32]u8 = undefined; - var buf_idx: usize = 0; - buf_idx += try std.unicode.utf8Encode(0x1F9D4, buf[buf_idx..]); // man: beard - buf_idx += try std.unicode.utf8Encode(0x1F3FB, buf[buf_idx..]); // light skin tone (Fitz 1-2) - buf_idx += try std.unicode.utf8Encode(0x200D, buf[buf_idx..]); // ZWJ - buf_idx += try std.unicode.utf8Encode(0x2642, buf[buf_idx..]); // male sign - buf_idx += try std.unicode.utf8Encode(0xFE0F, buf[buf_idx..]); // emoji representation - - // Make a screen with some data + // Make a screen and add a long emoji sequence to it. var screen = try terminal.Screen.init(alloc, 30, 3, 0); defer screen.deinit(); - try screen.testWriteString(buf[0..buf_idx]); + + var page = screen.pages.pages.first.?.data; + var row = page.getRow(1); + const cell = &row.cells.ptr(page.memory)[0]; + cell.* = .{ + .content_tag = .codepoint, + .content = .{ .codepoint = 0x1F9D4 }, // Person with beard + }; + var graphemes = [_]u21{ + 0x1F3FB, // Light skin tone (Fitz 1-2) + 0x200D, // ZWJ + 0x2642, // Male sign + 0xFE0F, // Emoji presentation selector + }; + try page.setGraphemes( + row, + cell, + graphemes[0..], + ); // Get our run iterator var shaper = &testdata.shaper; var it = shaper.runIterator( testdata.grid, &screen, - screen.pages.pin(.{ .screen = .{ .y = 0 } }).?, + screen.pages.pin(.{ .screen = .{ .y = 1 } }).?, null, null, ); diff --git a/src/font/shaper/run.zig b/src/font/shaper/run.zig index 22d19979e..18ddd4b56 100644 --- a/src/font/shaper/run.zig +++ b/src/font/shaper/run.zig @@ -360,11 +360,16 @@ pub const RunIterator = struct { // 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.grid.getIndex( alloc, cp, style, - presentation, + null, ) orelse return null; candidates.appendAssumeCapacity(idx); } @@ -375,7 +380,7 @@ pub const RunIterator = struct { for (cps) |cp| { // Ignore Emoji ZWJs if (cp == 0xFE0E or cp == 0xFE0F or cp == 0x200D) continue; - if (!self.grid.hasCodepoint(idx, cp, presentation)) break; + if (!self.grid.hasCodepoint(idx, cp, null)) break; } else { // If the while completed, then we have a candidate that // supports all of our codepoints.