检测 emoji 支持:字体里到底有没有这个字形
RGI 那页结尾说过:一段 emoji 合不合法(\p{RGI_Emoji} 说了算)和它渲不渲染得出来是两件事——后者取决于当前设备的字体里有没有对应字形,和系统版本、地区强绑定。
常见做法是嗅探 User-Agent 猜操作系统厂商、再假定它的 emoji 字体,但 UA 可随意伪装,也不反映字体实际装到了哪一代 Unicode。可靠的做法是直接测量渲染结果。
本页给出三种互补的检查手段,合到一个 lab 里:勾选启用哪几种,对任意 emoji 得出一个综合结论。
基线:缺字形的码点都回退成同一个「豆腐块」
一个码点没有字形时会回退到末位字体的 .notdef(豆腐块 ▯),而所有缺字形的码点都回退到同一个 .notdef——渲染出来完全一样。判断一个字符有没有专属字形,就看它渲染出来是否等于豆腐块:一致 = 没字形 = 未支持。
注意用渲染像素比、而不是只比 measureText 的宽度:某些系统的 .notdef 步进宽度恰好等于 emoji 宽度,只比宽度会把能显示的字(如黑白的 🥷)误判成不支持。宽度留给下一节的连字塌缩用,那里比的是相对宽度,不受此坑影响。
三种检查手段:各管一类 emoji
没有单一指标能覆盖所有 emoji——单码点、ZWJ 序列、国旗各有各的「未支持」表现。三种手段分别对准一类:
.notdef 宽度恰好等于 emoji,只比宽度会把能显示的字(如黑白的 🥷)误判为不支持。只要求「有独立字形」,不要求彩色。TW 为例:在中国大陆地区的系统上,🇹🇼 不渲染成旗子,而是退成一个方块、里面是两个字母。这个方块也是一个字形、宽度也塌窄了,所以 B(宽度塌缩)会把它误判为「支持」。
真旗和字母方块的区别在颜色:真旗有大片彩色,字母方块是灰度——所以国旗要用 C(彩色像素)来判。下面的 lab 里把 C 的勾去掉,就能复现这个误判。
合到一个 lab:勾选检查手段,得出综合结论
综合规则用「不支持优先」:在勾选启用且适用于当前输入的检查里,只要有一个判「不支持」,综合结论就是不支持;都判支持才是支持;若勾选的检查都不适用,则无法判定。下面就是本页实际运行的检测:
体检:对照组(任何设备可复现)+ 版本阶梯(反映本机)
两组都跑三种检查全开的综合判定。对照组不依赖设备新旧——未分配码点(豆腐块)、无效地区码(字母块)在任何机器上都判不支持,总能演示三种检查的区别。版本阶梯是这台设备的实时探测:越靠下版本越新,某个版本起开始出现 ✗ 就是这台设备 emoji 字体的更新截止线。
对照组 · 不依赖设备新旧,总能看出三种检查的区别
版本阶梯 · 反映这台设备 emoji 字体的新旧(越靠下越新)
\p{RGI_Emoji} 是正交的两问:属性那页的 \p{RGI_Emoji} 只判「这串码点在 Unicode 里是不是合法 emoji」(在哪台机器上都一样);本页判「当前字体渲不渲染得出来」(每台机器、每个系统版本、每个地区都可能不同)。
上线一个 emoji 前,合法性用正则查、渲染支持度用本页的三种检查探——两者都过,才是真的「能用」。
measureText,不读像素、跨机稳定、不受反指纹噪声影响;A / C 需要 getImageData 读像素(A 比「渲染是否 = 豆腐块」,C 数彩色像素占比)。因此在开启了 canvas 指纹保护的浏览器里,A / C 的像素读数可能被掺入噪声、结论不稳——这时 A 可退回只比宽度(measureText),但要接受 .notdef 宽度与 emoji 撞车导致的误判。