Skip to content

Commit f008052

Browse files
committed
fix(font/shape): don't require emoji presentation for grapheme parts
Also update shaper test that fails because the run iterator can't apply that logic since `testWriteString` doesn't do proper grpaheme clustering so the parts are actually split across multiple cells. Several other tests are technically incorrect for the same reason but still pass, so I've decided not to fix them here.
1 parent 6f84a5d commit f008052

File tree

3 files changed

+49
-24
lines changed

3 files changed

+49
-24
lines changed

src/font/shaper/coretext.zig

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1015,25 +1015,35 @@ test "shape emoji width long" {
10151015
var testdata = try testShaper(alloc);
10161016
defer testdata.deinit();
10171017

1018-
var buf: [32]u8 = undefined;
1019-
var buf_idx: usize = 0;
1020-
buf_idx += try std.unicode.utf8Encode(0x1F9D4, buf[buf_idx..]); // man: beard
1021-
buf_idx += try std.unicode.utf8Encode(0x1F3FB, buf[buf_idx..]); // light skin tone (Fitz 1-2)
1022-
buf_idx += try std.unicode.utf8Encode(0x200D, buf[buf_idx..]); // ZWJ
1023-
buf_idx += try std.unicode.utf8Encode(0x2642, buf[buf_idx..]); // male sign
1024-
buf_idx += try std.unicode.utf8Encode(0xFE0F, buf[buf_idx..]); // emoji representation
1025-
1026-
// Make a screen with some data
1018+
// Make a screen and add a long emoji sequence to it.
10271019
var screen = try terminal.Screen.init(alloc, 30, 3, 0);
10281020
defer screen.deinit();
1029-
try screen.testWriteString(buf[0..buf_idx]);
1021+
1022+
var page = screen.pages.pages.first.?.data;
1023+
var row = page.getRow(1);
1024+
const cell = &row.cells.ptr(page.memory)[0];
1025+
cell.* = .{
1026+
.content_tag = .codepoint,
1027+
.content = .{ .codepoint = 0x1F9D4 }, // Person with beard
1028+
};
1029+
var graphemes = [_]u21{
1030+
0x1F3FB, // Light skin tone (Fitz 1-2)
1031+
0x200D, // ZWJ
1032+
0x2642, // Male sign
1033+
0xFE0F, // Emoji presentation selector
1034+
};
1035+
try page.setGraphemes(
1036+
row,
1037+
cell,
1038+
graphemes[0..],
1039+
);
10301040

10311041
// Get our run iterator
10321042
var shaper = &testdata.shaper;
10331043
var it = shaper.runIterator(
10341044
testdata.grid,
10351045
&screen,
1036-
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
1046+
screen.pages.pin(.{ .screen = .{ .y = 1 } }).?,
10371047
null,
10381048
null,
10391049
);

src/font/shaper/harfbuzz.zig

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -540,25 +540,35 @@ test "shape emoji width long" {
540540
var testdata = try testShaper(alloc);
541541
defer testdata.deinit();
542542

543-
var buf: [32]u8 = undefined;
544-
var buf_idx: usize = 0;
545-
buf_idx += try std.unicode.utf8Encode(0x1F9D4, buf[buf_idx..]); // man: beard
546-
buf_idx += try std.unicode.utf8Encode(0x1F3FB, buf[buf_idx..]); // light skin tone (Fitz 1-2)
547-
buf_idx += try std.unicode.utf8Encode(0x200D, buf[buf_idx..]); // ZWJ
548-
buf_idx += try std.unicode.utf8Encode(0x2642, buf[buf_idx..]); // male sign
549-
buf_idx += try std.unicode.utf8Encode(0xFE0F, buf[buf_idx..]); // emoji representation
550-
551-
// Make a screen with some data
543+
// Make a screen and add a long emoji sequence to it.
552544
var screen = try terminal.Screen.init(alloc, 30, 3, 0);
553545
defer screen.deinit();
554-
try screen.testWriteString(buf[0..buf_idx]);
546+
547+
var page = screen.pages.pages.first.?.data;
548+
var row = page.getRow(1);
549+
const cell = &row.cells.ptr(page.memory)[0];
550+
cell.* = .{
551+
.content_tag = .codepoint,
552+
.content = .{ .codepoint = 0x1F9D4 }, // Person with beard
553+
};
554+
var graphemes = [_]u21{
555+
0x1F3FB, // Light skin tone (Fitz 1-2)
556+
0x200D, // ZWJ
557+
0x2642, // Male sign
558+
0xFE0F, // Emoji presentation selector
559+
};
560+
try page.setGraphemes(
561+
row,
562+
cell,
563+
graphemes[0..],
564+
);
555565

556566
// Get our run iterator
557567
var shaper = &testdata.shaper;
558568
var it = shaper.runIterator(
559569
testdata.grid,
560570
&screen,
561-
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
571+
screen.pages.pin(.{ .screen = .{ .y = 1 } }).?,
562572
null,
563573
null,
564574
);

src/font/shaper/run.zig

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -360,11 +360,16 @@ pub const RunIterator = struct {
360360

361361
// Find a font that supports this codepoint. If none support this
362362
// then the whole grapheme can't be rendered so we return null.
363+
//
364+
// We explicitly do not require the additional grapheme components
365+
// to support the base presentation, since it is common for emoji
366+
// fonts to support the base emoji with emoji presentation but not
367+
// certain ZWJ-combined characters like the male and female signs.
363368
const idx = try self.grid.getIndex(
364369
alloc,
365370
cp,
366371
style,
367-
presentation,
372+
null,
368373
) orelse return null;
369374
candidates.appendAssumeCapacity(idx);
370375
}
@@ -375,7 +380,7 @@ pub const RunIterator = struct {
375380
for (cps) |cp| {
376381
// Ignore Emoji ZWJs
377382
if (cp == 0xFE0E or cp == 0xFE0F or cp == 0x200D) continue;
378-
if (!self.grid.hasCodepoint(idx, cp, presentation)) break;
383+
if (!self.grid.hasCodepoint(idx, cp, null)) break;
379384
} else {
380385
// If the while completed, then we have a candidate that
381386
// supports all of our codepoints.

0 commit comments

Comments
 (0)