算法与数据结构 / regex · Unicode 字符模型与一个自研引擎 / Basic_Emoji:变体选择符与 ZWJ 待审核 11 / 36
Basic_Emoji · VS · Join_Control

Basic_Emoji:变体选择符与 ZWJ

emoji 的复杂度是逐级加上去的,最底下一档叫 Basic_Emoji:要么是一个单独的 code point(😀),要么是一个 code point 紧跟变体选择符 U+FE0F,不牵扯连接符、区域码与标签序列。国旗、肤色、家庭这些多码点序列都在这一档之上,属于 RGI 序列 一页的内容。本页拆开最底这一档,并认全两个零宽的「胶水」码点——它们是上层所有序列的零件。

1 · 变体选择符

U+2764)、U+2600)、U+270C)在 Unicode 1.1(1993)就已收录,那时它们是黑白的排版符号。emoji 化之后同一个码点有了两副面孔,于是需要一个显式开关:紧跟其后的 U+FE0F 要求彩色 emoji 字形,U+FE0E 要求单色文本字形,二者合称变体选择符。开关本身比它要修饰的符号晚了九年(U+FE0F 属 Unicode 3.2,2002)。

\p{Basic_Emoji} 收的正是「emoji 脸」那一形态。逐码点实测(Unicode 17.0):单码点形态 1193 个,恰是 Emoji_Presentation 的 1219 个减去 26 个区域指示符——后者默认就走彩色展示,却因为只是国旗序列的零件而被排除;另有 207 个「码点 + U+FE0F」的两码点形态。合计 1400 条,与上游 emoji-sequences.txtBasic_Emoji 展开后的条目数一致。现代新造的 emoji(😀🚀🧩)默认就是彩色形态,VS16 只用来唤醒那些历史上是文本符号的老码点。

图 1-1 · 一个历史符号的三种形态:裸码点、加 VS15、加 VS16,各自逐一实测是否属于 Basic_Emoji。可换基础符号。

警示 · keycap 底座(#*09)同样带 Emoji=Yes,但 1U+FE0F 并不属于 Basic_Emoji:按键 emoji 1️⃣ 还需要第三个码点 U+20E3(combining enclosing keycap)才成立,Unicode 把这一族单列为 Emoji_Keycap_Sequence,共 12 条。属性带 Emoji 不等于这个码点自己就能当 emoji 用。

2 · Join_Control 的两个成员

\p{Join_Control} 是全 Unicode 里最小的属性之一:逐码点扫过 U+0000U+10FFFF,命中的只有 U+200CU+200D 两个。前者是 ZWNJ,阻止相邻字符连写;后者是 ZWJ,要求把前后粘成一体。两个码点都不占宽度、屏幕上看不见,却决定相邻字符渲染成两个字形还是一个。它们的本行是波斯语、印地语这类连写文字的断连控制,与 同属 Unicode 1.1 的老码点;emoji 只是把 ZWJ 借去当粘合剂。

图 2-1 · Join_Control 的两个成员,及其对相邻字符连写的作用;右侧为逐码点全域扫描得出的基数。

3 · ZWJ 序列

把若干 emoji 用 ZWJ 串起来,支持的平台会合成一个字形:👨 + ZWJ + 👩 + ZWJ + 👧 渲染成一家三口,五个 code point、八个 UTF-16 码元,屏幕上只占一个字符的位置。这种拼法叫 ZWJ 序列,已超出 Basic_Emoji 的两种形态。彩虹旗 🏳️‍🌈 则把两类零件用在一处:🏳 + U+FE0F + ZWJ + 🌈,先用变体选择符把白旗唤成 emoji 脸,再用 ZWJ 粘上彩虹。

图 3-1 · 任意 emoji 的 code point 拆解,ZWJ 与变体选择符单独标色,并逐条实测 Basic_Emoji、Join_Control 与 RGI_Emoji 三个属性。

注 · 合法与渲染得出来是两回事。\p{RGI_Emoji} 判的是「这串码点在 Unicode 里是否构成一个完整 emoji」,各机器答案一致;屏幕上能否画出来则取决于本机字体,见 emoji 渲染检测

4 · 参考文献

  1. Unicode. UTS #51: Unicode Emoji. emoji 的规范出处:Basic_Emoji 等序列集合的定义(ED-20 至 ED-25,其并集 RGI emoji set 是 ED-27)、六个码点级 emoji 属性、变体选择符与 ZWJ 序列的构造规则。unicode.org
  2. Unicode. emoji-sequences.txt(Emoji 17.0). 序列的全量数据文件,按 Basic_Emoji / Emoji_Keycap_Sequence / RGI_Emoji_Flag_Sequence 等类型分列。本页的 1193 + 207 = 1400 与 keycap 12 条即照它统计。unicode.org
  3. Unicode. UAX #44: Unicode Character Database. Join_Control 等二元属性的定义与 DerivedAge.txt 的格式;本页各码点的收录版本据后者查得。unicode.org
  4. MDN. Unicode character class escape. JS 里 \p{…} 的可用属性清单,以及码点属性与字符串属性对 u / v 标志的不同要求。developer.mozilla.org