Emoji
ui.textEmoji en couleur, séquences ZWJ et modificateurs de teint.
BEHAVIOR CONTRACT
Les glyphes en couleur restent en couleur ; les séquences ZWJ sont rendues comme un seul graphème.
RECORDED INTERACTION
Parcourt les échantillons d'emoji.
// 每个 emoji 单独一个 test_id 节点,且刻意选中心区域是大面积纯色的:
// 🟥 U+1F7E5 红方块 🟩 U+1F7E9 绿方块 🟦 U+1F7E6 蓝方块
// 方块类 emoji 中心整块同色,不受字号/抗锯齿/中心点偏移影响,是最稳的
// 像素靶。文字颜色特意设成中性灰 —— 若 shader 错误地把 emoji 乘上文字色,
// 结果会退回灰度(三通道相等),此测立刻变红。
pub fn buildEmoji(scope: *ui.Scope, cx: *ui.Cx) anyerror!*ui.Node {
_ = scope;
const a = cx.allocator;
const c = try col(cx, 16);
try c.appendChild(a, try label(cx, "Color emoji (BGRA atlas page + shader color branch)"));
const Case = struct { name: []const u8, test_id: []const u8, glyph: []const u8 };
const cases = [_]Case{
.{ .name = "red square", .test_id = "story.emoji.red", .glyph = "\u{1F7E5}" },
.{ .name = "green square", .test_id = "story.emoji.green", .glyph = "\u{1F7E9}" },
.{ .name = "blue square", .test_id = "story.emoji.blue", .glyph = "\u{1F7E6}" },
};
const tiles = try row(cx, 24);
for (cases) |case| {
const cell = try col(cx, 6);
try cell.appendChild(a, try label(cx, case.name));
// 中性灰文字色:emoji 若被错误地乘上它,会退化成灰度。
const glyph_node = try ui.text(cx, case.glyph, .{
.font_size = 64,
.color = ui.Color.rgb(128, 128, 128),
});
glyph_node.meta.ownership.meta.test_id = case.test_id;
try cell.appendChild(a, glyph_node);
try tiles.appendChild(a, cell);
}
try c.appendChild(a, tiles);
// 对照组:同一个中性灰颜色下的普通文本,必须仍然是灰度(三通道相等)。
// 证明彩色分支没有把灰度路径一起改坏。
try c.appendChild(a, try label(cx, "grayscale control (must stay gray)"));
const control = try ui.text(cx, "AAAA", .{
.font_size = 64,
.color = ui.Color.rgb(128, 128, 128),
});
control.meta.ownership.meta.test_id = "story.emoji.control";
try c.appendChild(a, control);
// 混排:emoji 与文字同一段,验证 shaping/fallback 与彩色分流能共存于一行。
try c.appendChild(a, try label(cx, "mixed run"));
// 显式给 light 文本色:ui.text 的默认 color 是 comptime 求值的
// theme.dark.color.fg_primary,不随 cx.setTheme(&light) 变化,
// 漏写会在 storybook 的浅色底上渲染成几乎看不见的浅色字。
const mixed = try ui.text(cx, "Hello \u{1F600} world \u{1F30D}", .{
.font_size = 28,
.color = light.color.fg_primary,
});
mixed.meta.ownership.meta.test_id = "story.emoji.mixed";
try c.appendChild(a, mixed);
return c;
}Le code source tel quel de la story dans le Storybook (zenit 5f9add5+wip 2026-09-30). W désigne ui.widgets ; col / row / label sont de petits helpers de mise en page du Storybook.