#1 - 2021-7-31 20:49
NekoNull
最近遇到了一个和 Emoji 相关的奇妙问题,在此分享一下自己的探索过程,以及期望能得到一些指点。

起源是发现 ©️ 这个 Emoji 在不同浏览器中的显示不一样。在 Chrome 系下就是一个单纯的文本字符,但是在 Firefox 中就会显示为一个 Emoji (圆圈内有白色底色)。进一步探索,发现 Emoji 实际上有两种 presentation:text 和 emoji (也称作 pictographic)(Link)。Unicode 中规定了 VS15 和 VS16 (VS=Variation Selector),实际上是两个特殊的 Unicode 码点( U+FE0E 和 U+FE0F),追加在 Emoji 后可以强制指定要显示哪种 presentaion。如果不在 Emoji 字符后追加 VS,那么默认的 presentation 是系统/环境特定的。比较新的 emoji 基本上只有 emoji presentation,但是老的 emoji(包括部分从 wingding 继承过来的),就同时有 text 和 emoji presentation。
所有 emoji 的 Presentation 展示,网页较大,请用 Firefox 打开,可能导致 Chrome 崩溃。)

然而追加 VS16(即要求使用 emoji presentation),只能解决部分 Emoji 在 Chrome 下调用 text presentation 的问题,有的 Emoji 依然不会调用 emoji presentation。
(前者没有加 VS16,后者加了 VS16)
可以要求调用 emoji: ⛰ ⛰️
不能要求调用 emoji:© ©️

预期:在任何浏览器上,第二个 emoji 都应该以 emoji 图像的形式显示
实际:Firefox 正常,Chrome 系 Copyright 加了 VS16 也不会显示为 emoji。
(工具:Unicode Inspector

进一步调查,发现可能和浏览器的字体回退机制有关。(Reddit 讨论)具体而言,在 Chrome 上,如果 CSS 里写的是 SYSTEM-UI 的话,Windows 10 下会被解析到 Segoe UI,但这个字体对 Emoji 的支持是不完整的,即便追加了 VS16 也无法解析到 Copyright 这个 emoji 的 emoji presentation,最后回落到了 text presentation。但是在 Firefox 上,对 emoji 有特定处理,emoji 会被解析到 Segoe UI Emoji,从而能正常显示。

豆知识:如果在网页里用了 Emoji,为了确保 Windows 10 + Chrome 用户能正常显示,请在 CSS 中把 Segoe UI Emoji 放在 SYSTEM-UI 或类似的字体家族之前。 不要这样做,不然文字显示也会受到影响。Segoe UI Emoji 里不仅有 Emoji,也有正常字符。)

到这里逻辑链条似乎都很正常,但是似乎继续深究下去就出现了奇怪的问题。以阴阳(☯,☯️,U+262F)这个 emoji 为例子,如果在 notepad 中直接复制,显示的 glyph (字形) 是一个带黑框,阴阳两点在纵轴的 text presentation。用 FontForge 打开各个字体文件检查,可以发现这个glyph 来自于 Segoe UI Emoji。notepad 默认字体是微软雅黑,但是检查 NT 系统的字体回退机制(Link),可以发现 Microsoft YaHei 这个字体的回退项并不包括 Segoe UI Emoji。所以这个字形到底是怎么被选中的?

希望各位能提供一些思路。
#2 - 2021-7-31 22:27
(宅男会虚构虚构老婆倒贴给虚构自己这件事究竟有什么错)
https://blog.csdn.net/weixin_39753397/article/details/111551193

你可能把 fallback 和 font link 搞混了。Windows 里,颜文字在没有特别声明的情况下默认回退到 Segoe UI Emoji,跟 font link(比如 KaiTi_GB2312 在 win10 中 link 到 KaiTi)是两码事。

不过我搜到一个有趣的事情:2013 年 Chromium 的  bug 列表里,有人提到 Chromium 没有支持 OpenType 的 SVG 特性(事实上可以说是 emoji 或者严格意义上彩色颜文字的根基)。维护者以该特性可能影响到程序性能为由而表示不会改动。

https://pixelambacht.nl/2014/multicolor-fonts/
https://www.colorfonts.wtf/

顺着这条线我查到用来支持彩色 OpenType 的方案有四种:CBDT/CBLC(谷歌),COLR/CPAL(微软),SBIX(苹果),SVG(Mozilla & 奥多比)。那么 Chrome 究竟是因为对 OpenType 特性实现不全导致无法显示 ©,还是仅仅因为 Arial 里一样有 copyright 这个字符而不选择向下回退?我觉得只是因为 Chromuim 不想显示 emoji 而已。
#2-1 - 2021-7-31 22:34
板砖加身
删除了回复
#2-2 - 2021-7-31 23:35
NekoNull
多谢说明!我的确弄混了 fallback 和 linking。那看来是我的思维模型有问题了。我之前的理解中,应用层面的优先级应该是比系统高的,不过现在看来似乎不是这样。
#2-3 - 2021-7-31 23:47
NekoNull
所以是否可以认为,在网页中显示某个 emoji 的时候,如果 font link 在 base font 里没找到对应的 glyph,但是在 link 链中的某个字体找到了,浏览器会认为这个 glyph 就存在于这个 base font 中,最终结果就是 font link 比 font fallback 的优先级更高?
或者说,font link 是否可以理解为生成了一个 virtual font,其中的 glyph 相当于 base font 并上(putIfAbsent)所有存在于 link 列表里的 font?
#2-4 - 2021-8-1 10:21
板砖加身
NekoNull 说: 所以是否可以认为,在网页中显示某个 emoji 的时候,如果 font link 在 base font 里没找到对应的 glyph,但是在 link 链中的某个字体找到了,浏览器会认为这个 glyp...
应用程序里 font link 的匹配优先于 fallback。
Font link 列表里好像没有一对多的情况吧,把对应的字体理解为相同字体就可以了。至于字形自然以 link 到的字体为准。
#3 - 2021-7-31 22:53
(宅男会虚构虚构老婆倒贴给虚构自己这件事究竟有什么错)
如果是 Windows 的 System UI font 的话,浏览器里可能默认第一位是 Arial,包含 copyright 这个字符,于是 Chromium 放弃思考了(或者说 Chromium 本身倾向于文本模式)。

Firefox 后来为了避免 fallback 到 Segoe UI Emoji 时仍有符号无法显示,又内置了一套 Twitter 的颜文字字体来作为备用的回退方案。

事实上对 Color Font 支持最好的浏览器是旧版 Edge。

冷知识:记事本其实是微软在为 Windows 添加新功能前的试验田。记事本其实挺牛逼的。
#3-1 - 2021-7-31 23:53
NekoNull
我在 Windows 下的新 Edge 里试了一下,直接修改这个帖子的 CSS。
sai 老板对帖子正文(class="topic_content")原始的 font-family 设定是
'SF Pro SC','SF Pro Display','PingFang SC','Lucida Grande','Helvetica Neue',Helvetica,Arial,Verdana,sans-serif,"Hiragino Sans GB";

如果在 Helvetica 前面手动加上 "Segoe UI Emoji",那么就能让 Copyright 正常以 Emoji 形式显示。但是如果在 Helvetica 和 Arial 中间加,就不行。但是我的系统内是没有安装 Helvetica 的,所以不太确定为什么会有这样的行为。
#3-2 - 2021-8-1 10:08
板砖加身
NekoNull 说: 我在 Windows 下的新 Edge 里试了一下,直接修改这个帖子的 CSS。 sai 老板对帖子正文(class="topic_content")原始的 font-family 设定是 'SF P...
Windows 里 Helvetica 是被 link 到 Arial 的。
#4 - 2021-8-1 00:14
(V1046-R MAHORO)
怎么判定一个字符是否是emoji,因而应该有“emoji presentation”?我在你贴的 https://character.construction/emoji-vs-text 里搜索,没有© (U+00A9) 这个字符。如果没有标准,就没法判断谁是对的。
#4-1 - 2021-8-1 00:38
己注销
U+00A9 这里的高位0可以省略吧,应该要写成 U+A9 才是。(学艺不精,也没有找到相关资料,但后者可以搜索到,盲猜是这样)
#4-2 - 2021-8-1 03:17
NekoNull
在我给的列表里搜索 「COPYRIGHT SIGN 」或者「U+A9」是可以搜索到的。
如果需要看完整 emoji 列表,可以用这个连接:https://unicode.org/emoji/charts/full-emoji-list.html
关于 presentation,我之前在 emojipedia 上读到的是所有的 emoji 都有 emoji presentation,部分 emoji 有 text presentation。
#4-3 - 2021-8-1 09:09
烈之斩
onebyten 说: U+00A9 这里的高位0可以省略吧,应该要写成 U+A9 才是。(学艺不精,也没有找到相关资料,但后者可以搜索到,盲猜是这样)
啊,搜到了,其实我都搜了,没想到他写的是U+ A9(中间有个空格),所以没搜到 (bgm38)
#5 - 2021-8-6 09:38
(請注意UID)
©️符號在Android的Vivaldi即使加了VS16,也是非emoji的representation呀,是Chromium/Webkit方面的bug嗎?(Firefox Android能正常反映)
#6 - 2021-10-1 04:51
尝试挖一铲 (bgm38)

如果想要让 Emoji 正常显示(例如 Windows 下强制调用 Segoe UI Emoji),那么就要给 Emoji 设定对应的字体,否则较早的 Emoji 可能在其他字体中已经存在了对应 codepoint 上的字形,就没法调用系统的 Emoji 字体了,表现出来的结果就是例如上面 Copyright 符号,就算用 Emoji Presentation Selector 强制指定了要求 Emoji 形式,Chrome 下还是渲染不出来。

正如之前的讨论中"板砖加身"所说,Firefox 有一个自己的设置项(font.name-list.emoji),单独给 Emoji 设定了字体。Chrome 没有这样的机制。作为前端开发者,那就只剩下用 CSS 指定字体了。可惜 CSS 中并没有现有的属性,能够直接选中所有的 Emoji。反之,如果直接把系统的 Emoji 字体置前,那么又会影响正文字体(例如 Segoe UI Emoji 中是含有基本拉丁字符的)。考虑到 CSS 中存在一个 unicode-range 属性,可以给特定 Unicode codepoint 的字符指定字体。

剩下的就很简单了,写一个小脚本,从 Unicode 官网提取当前的所有 Unicode codepoint,转换成符合 CSS 要求的 unicode-range,这样就能在浏览器没有支持的情况下指定 Emoji 使用的字体,且避免正文字体受到影响了。

转换后的 unicode-range 如下:(基于 Unicode 14.0)
unicode-range: U+A9,U+AE,U+203C,U+2049,U+20E3,U+2122,U+2139,U+2194-2199,U+21A9-21AA,U+231A-231B,U+2328,U+23CF,U+23E9-23F3,U+23F8-23FA,U+24C2,U+25AA-25AB,U+25B6,U+25C0,U+25FB-25FE,U+2600-2604,U+260E,U+2611,U+2614-2615,U+2618,U+261D,U+2620,U+2622-2623,U+2626,U+262A,U+262E-262F,U+2638-263A,U+2640,U+2642,U+2648-2653,U+265F-2660,U+2663,U+2665-2666,U+2668,U+267B,U+267E-267F,U+2692-2697,U+2699,U+269B-269C,U+26A0-26A1,U+26A7,U+26AA-26AB,U+26B0-26B1,U+26BD-26BE,U+26C4-26C5,U+26C8,U+26CE-26CF,U+26D1,U+26D3-26D4,U+26E9-26EA,U+26F0-26F5,U+26F7-26FA,U+26FD,U+2702,U+2705,U+2708-270D,U+270F,U+2712,U+2714,U+2716,U+271D,U+2721,U+2728,U+2733-2734,U+2744,U+2747,U+274C,U+274E,U+2753-2755,U+2757,U+2763-2764,U+2795-2797,U+27A1,U+27B0,U+27BF,U+2934-2935,U+2B05-2B07,U+2B1B-2B1C,U+2B50,U+2B55,U+3030,U+303D,U+3297,U+3299,U+FE0F,U+1F004,U+1F0CF,U+1F170-1F171,U+1F17E-1F17F,U+1F18E,U+1F191-1F19A,U+1F1E6-1F1FF,U+1F201-1F202,U+1F21A,U+1F22F,U+1F232-1F23A,U+1F250-1F251,U+1F300-1F321,U+1F324-1F393,U+1F396-1F397,U+1F399-1F39B,U+1F39E-1F3F0,U+1F3F3-1F3F5,U+1F3F7-1F4FD,U+1F4FF-1F53D,U+1F549-1F54E,U+1F550-1F567,U+1F56F-1F570,U+1F573-1F57A,U+1F587,U+1F58A-1F58D,U+1F590,U+1F595-1F596,U+1F5A4-1F5A5,U+1F5A8,U+1F5B1-1F5B2,U+1F5BC,U+1F5C2-1F5C4,U+1F5D1-1F5D3,U+1F5DC-1F5DE,U+1F5E1,U+1F5E3,U+1F5E8,U+1F5EF,U+1F5F3,U+1F5FA-1F64F,U+1F680-1F6C5,U+1F6CB-1F6D2,U+1F6D5-1F6D7,U+1F6DD-1F6E5,U+1F6E9,U+1F6EB-1F6EC,U+1F6F0,U+1F6F3-1F6FC,U+1F7E0-1F7EB,U+1F7F0,U+1F90C-1F93A,U+1F93C-1F945,U+1F947-1F9FF,U+1FA70-1FA74,U+1FA78-1FA7C,U+1FA80-1FA86,U+1FA90-1FAAC,U+1FAB0-1FABA,U+1FAC0-1FAC5,U+1FAD0-1FAD9,U+1FAE0-1FAE7,U+1FAF0-1FAF6,U+E0062-E0063,U+E0065,U+E0067,U+E006C,U+E006E,U+E0073-E0074,U+E0077;



结果展示:JSFiddle
(场景:正文希望使用 serif 字体,但是对所有的 Emoji 都调用系统 Emoji 字体显示)
图:
#7 - 2021-10-1 05:22
关于阴阳 Emoji 的问题(Notepad 下阴阳两点在纵轴,Chrome 中阴阳两点在横轴),原因大概是正如"板砖加身"所说 Notepad 算是系统应用,会调用 Windows 的默认 Emoji 字体,也就是 Segoe UI Emoji。但是 Chrome 有自己的字体选择逻辑,用 devtools 中 computed (已计算)查看元素,可以发现实际上阴阳调用的是 Segoe UI Symbol。

以下是一个默认的阴阳字符,以便在 devtool 中单独 inspect 这个 div:

#7-1 - 2021-10-1 05:38
烈之斩
Firefox里好像也是优先使用 Segoe UI Symbol ? 搜到一个issue https://bugzilla.mozilla.org/show_bug.cgi?id=1054780
(他这里举的例子我这里只有闪电使用了Emoji(Symbol有这个字符吗?),其他都是Symbol,所以我怀疑这个“issue”已经不再存在了?不知道为啥还是Open)。
#7-2 - 2021-10-1 06:53
NekoNull
烈之斩 说: Firefox里好像也是优先使用 Segoe UI Symbol ? 搜到一个issue https://bugzilla.mozilla.org/show_bug.cgi?id=1054780
(他...
可以去 about:config 找 font.name-list.emoji 设置项看看,不太确定你用的版本里的值是怎样的。我这里是 Segoe UI Emoji, Twemoji Mozilla。闪电如果指的是 U+26A1,在 Segoe UI Symbol 里是有的。可以用 dp4 Font Viewer 查看字体内的字符覆盖。
#7-3 - 2021-10-1 08:10
烈之斩
NekoNull 说: 可以去 about:config 找 font.name-list.emoji 设置项看看,不太确定你用的版本里的值是怎样的。我这里是 Segoe UI Emoji, Twemoji Mozilla。...
就是默认设置Segoe UI Emoji, Twemoji Mozilla无误

显示效果如下: Fx 93.0 (64-bit)


审查元素前4个都是用了Symbol,只有最后一个用了Emoji

(Chrome也是一样,只不过Chrome里的Emoji如下所述是黑白的。)
#8 - 2021-10-1 05:30
(V1046-R MAHORO)
一个无关问题,为什么Segoe UI Emoji在Win7+Chrome是黑白的(理论上就应该是黑白的,win7好像基本不支持彩色emoji),但是在Firefox就是彩色的

(网上搜了一波,居然搜到了自己4年前问的 (bgm38)
#8-1 - 2021-10-1 06:50
NekoNull
Segoe UI Emoji 似乎有两种渲染模式,如果支持彩色字体就会渲染成彩色(Word),如果不支持就会被渲染成轮廓(记事本)。不太确定 Windows 7 + Chrome 组合黑白的原因(手头没有这样的环境),但是 Firefox 自己随着安装包带了一个彩色 Emoji 字体(Twemoji Mozilla),所以是支持的?
#8-2 - 2021-10-1 08:06
烈之斩
NekoNull 说: Segoe UI Emoji 似乎有两种渲染模式,如果支持彩色字体就会渲染成彩色(Word),如果不支持就会被渲染成轮廓(记事本)。不太确定 Windows 7 + Chrome 组合黑白的原因(手头...
如我在那个ticket里所说,实际调用的是Segoe UI Emoji而不是Twemoji
#8-3 - 2021-10-1 11:03
弥御水Scyiki
正好最近我有在折腾 emoji 的显示问题。首先 win7 原生只支持黑白 emoji,火狐应该是针对 Segoe UI Emoji 这个字体做了处理,渲染成了彩色 emoji 。酷容没做处理,所以在 win7 上是原生的黑白 emoji 。

另外火狐应该也针对其他 emoji 字体做了处理,EmojiOne 的显示效果与 Segoe 的不同。但是在酷容下都是同样的图形。(我还拿 IE 做了实验,虽然也是黑白的,但 EmojiOne 的显示效果也与 Segoe 的不同,这样看来貌似是酷容写死了用于显示 emoji 的字体)。
#9 - 2021-10-1 07:07
顺带一提,在研究这个问题的时候,看到了一个 RegEx,生成可以匹配所有的 Emoji(至 2018 年版本),也给出了相应的测试例子,见此:https://www.regextester.com/106421

看完之后我半信半疑,因为这个 RegEx 太简单了,于是手动转换成了对应的 Unicode codepoint 范围检查了一下,结果发现的确有问题:这个 RegEx 的匹配范围太大了,忽略 Copyright 和 Registered 符号(u+00a9, u+00ae),剩下的区间分别是 [u+2000, u+3300] 和 [u+1f000, u+1fbff]。后者还算合理,查 Wikipedia 上 Unicode 平面映射 link 基本上也就是新增 Emoji 的对应 codepoint;然而前一个区间就太过广泛了,甚至连日文平假名、片假名都会被匹配上。(不过的确覆盖了几乎完全的 Emoji codepoint,虽然有些类似于 Selector 之类的边角没覆盖到)

那么怎么做一个能精确匹配 Emoji 的 RegEx 呢?考虑到 unicode-range 本质上是对 Emoji codepoint 的 compact 描述(部分相邻的 codepoint 合并成一个 range),只需要把 unicode-range 转换成对应的 RegEx 就可以了。然而说起来容易做起来难,RegEx 的视角中,字符是 UTF-16 的(如果要用 \uabcd 的形式的话),因此需要把高于 u+ffff 的 codepoint 用代理对的方式表示。

最后结果如下:
(\u00a9|\u00ae|\u203c|\u2049|\u20e3|\u2122|\u2139|[\u2194-\u2199]|[\u21a9-\u21aa]|[\u231a-\u231b]|\u2328|\u23cf|[\u23e9-\u23f3]|[\u23f8-\u23fa]|\u24c2|[\u25aa-\u25ab]|\u25b6|\u25c0|[\u25fb-\u25fe]|[\u2600-\u2604]|\u260e|\u2611|[\u2614-\u2615]|\u2618|\u261d|\u2620|[\u2622-\u2623]|\u2626|\u262a|[\u262e-\u262f]|[\u2638-\u263a]|\u2640|\u2642|[\u2648-\u2653]|[\u265f-\u2660]|\u2663|[\u2665-\u2666]|\u2668|\u267b|[\u267e-\u267f]|[\u2692-\u2697]|\u2699|[\u269b-\u269c]|[\u26a0-\u26a1]|\u26a7|[\u26aa-\u26ab]|[\u26b0-\u26b1]|[\u26bd-\u26be]|[\u26c4-\u26c5]|\u26c8|[\u26ce-\u26cf]|\u26d1|[\u26d3-\u26d4]|[\u26e9-\u26ea]|[\u26f0-\u26f5]|[\u26f7-\u26fa]|\u26fd|\u2702|\u2705|[\u2708-\u270d]|\u270f|\u2712|\u2714|\u2716|\u271d|\u2721|\u2728|[\u2733-\u2734]|\u2744|\u2747|\u274c|\u274e|[\u2753-\u2755]|\u2757|[\u2763-\u2764]|[\u2795-\u2797]|\u27a1|\u27b0|\u27bf|[\u2934-\u2935]|[\u2b05-\u2b07]|[\u2b1b-\u2b1c]|\u2b50|\u2b55|\u3030|\u303d|\u3297|\u3299)|(\ud83c(\udc04|\udccf|[\udd70-\udd71]|[\udd7e-\udd7f]|\udd8e|[\udd91-\udd9a]|[\udde6-\uddff]|[\ude01-\ude02]|\ude1a|\ude2f|[\ude32-\ude3a]|[\ude50-\ude51]|[\udf00-\udf21]|[\udf24-\udf93]|[\udf96-\udf97]|[\udf99-\udf9b]|[\udf9e-\udff0]|[\udff3-\udff5]))|(\ud83d([\udc00-\udcfd]|[\udcff-\udd3d]|[\udd49-\udd4e]|[\udd50-\udd67]|[\udd6f-\udd70]|[\udd73-\udd7a]|\udd87|[\udd8a-\udd8d]|\udd90|[\udd95-\udd96]|[\udda4-\udda5]|\udda8|[\uddb1-\uddb2]|\uddbc|[\uddc2-\uddc4]|[\uddd1-\uddd3]|[\udddc-\uddde]|\udde1|\udde3|\udde8|\uddef|\uddf3|[\uddfa-\ude4f]|[\ude80-\udec5]|[\udecb-\uded2]|[\uded5-\uded7]|[\udedd-\udee5]|\udee9|[\udeeb-\udeec]|\udef0|[\udef3-\udefc]|[\udfe0-\udfeb]))|(\ud83e([\udd0c-\udd3a]|[\udd3c-\udd45]|[\udd47-\uddff]|[\ude70-\ude74]|[\ude78-\ude7c]|[\ude80-\ude86]|[\ude90-\udeac]|[\udeb0-\udeba]|[\udec0-\udec5]|[\uded0-\uded9]|[\udee0-\udee7]))|(\udb40([\udc62-\udc63]|\udc65|\udc67|\udc6c|\udc6e|[\udc73-\udc74]))