在 Emoji 后面按一次退格键,它通常就会整个消失,所以我们很自然地把它看作一个字符。但在底层,同一张图可能由一个代码点、两个代码点,甚至更长的序列组成。
Unicode 中有几个容易混淆的层次:
- 代码点是 U+1F52C 这样的编号位置;
- 编码字符是分配给这个位置的抽象对象;
- 字素簇是用户通常会当成一个文字单位的字符序列;
- 字形是字体或 Emoji 图库最终画出来的样子。
它们经常一一对应,到了 Emoji 这里却会明显分开。
一颗爱心可以是文字,也可以是 Emoji
♥ 是 U+2665 BLACK HEART SUIT,❤ 是 U+2764 HEAVY BLACK HEART。这些历史符号在现代彩色 Emoji 出现前就已经存在。
Unicode 使用看不见的变体选择符来请求显示方式:
- U+FE0E,即 Variation Selector-15,请求文字样式;
- U+FE0F,即 Variation Selector-16,请求 Emoji 样式。
因此 ❤︎ 是 U+2764 后跟 U+FE0E,而 ❤️ 是 U+2764 后跟 U+FE0F。选择符不是第二张图片,它更像是对排版系统说:“请把前面的字符按这种方式显示。”
如果应用忽略了选择符,或者没有合适的 Emoji 字体,彩色爱心可能退回黑白符号。这是显示差异,不一定说明文字损坏了。
肤色是一个修饰符
Emoji 肤色使用基于 Fitzpatrick 分型的修饰字符编码。可以接受修饰的人物或身体部位 Emoji 后面,可以跟随一个肤色修饰符。
例如 👋🏽 包含:
- U+1F44B WAVING HAND SIGN;
- U+1F3FD EMOJI MODIFIER FITZPATRICK TYPE-4。
接收系统识别这个组合后,会画成一只手。如果系统不支持,它也可能把基础手势和肤色块分开显示。
看不见的胶水叫 ZWJ
U+200D ZERO WIDTH JOINER 是一个不可见的格式字符。在 Emoji ZWJ 序列中,它请求排版系统把两边的 Emoji 组合成一个受支持的图形。
👩🔬 包含:
👩WOMAN;- U+200D ZERO WIDTH JOINER;
🔬MICROSCOPE。
支持这个推荐序列的系统会画出女科学家;不支持的系统可能只显示一名女性,后面再放一个显微镜。逻辑文字仍然存在,精致的合成字形则取决于字体和平台。
更长的家庭与关系 Emoji 可能连接多个人物、爱心、亲吻、性别符号和肤色修饰符。看起来很紧凑的一枚图标,底层其实像一小句代码点组成的句子。
旗帜又用了另一套办法
大多数国家或地区旗帜,由两个区域指示符组成。例如日本国旗,是由对应 J 和 P 的两个区域指示符形成的。
这些代码点没有编码一张日本国旗图片,而是编码了一个地区序列,支持 Emoji 的系统可以把它显示成旗帜。不支持的系统通常会显示两个带框字母。
部分地区旗帜使用黑旗作为基础,后面跟一串不可见的标签字符和结束标签。按键 Emoji 则会组合数字或符号、可选的显示选择符,以及 U+20E3 COMBINING ENCLOSING KEYCAP。Emoji 不是一文件夹的小图片,而是一整套编码配方。
“长度”取决于你在数什么
程序员可以提出几种都合理的问题:
- 存储了多少个 UTF-16 代码单元?
- 包含多少个 Unicode 代码点?
- 光标应该经过多少个字素簇?
- 排版系统最终画了多少个字形?
答案可能全都不同。随意按代码单元切割,会拆坏代理项对;按代码点切割,也可能把肤色或变体选择符从基础字符上撕下来。好的文本软件会按照 Unicode 字素簇规则处理光标、选区、删除和长度限制。
所以“用户名最多 20 个字符”其实需要进一步定义。按字节、代码单元、代码点和用户感知字符计数,会得到不同结果。
为什么不同平台画出来仍然不一样?
Unicode 标准化的是字符身份和序列行为,不会规定唯一的插画。Apple、Google、Microsoft、Samsung 等厂商会绘制自己的字形,就像不同字体设计师会画出不同版本的 A。
同一序列应该代表同一个编码概念,但姿势、颜色、细节和情绪可能变化。共享代码与厂商绘图之间的空间,既给了 Emoji 活力,也制造了很多误解。
屏幕上的“一枚 Emoji”最好理解成一个字素簇,而不一定是一个代码点。
从Emoji 表情组合里复制家庭、职业、旗帜或爱心到上方显微镜。把隐形部分逐个列出来后,这些组合会容易理解得多。