每一个 emoji,就是一个 Unicode 字符。全世界的 emoji 都由统一码联盟(The Unicode Consortium)来投票选拔和公布,世界各地的人们可以向联盟提交 emoji 提案。而统一码联盟的 emoji 规范,只是定义了某个字符的语义,再由 Emojipedia 这个网站对 emoji 进行描述表达,最后允许大家按照对描述的理解,自由地去设计图案。
目前 iOS 15.4 和 Android 12L 所使用的最新版 Emoji 的正式名称其实叫 Emoji 14.0,它是在 2021 年 9 月通过了 Unicode(统一码)联盟的批准发布,然后才被 Google 和苹果应用在系统中。
😀
Emoji名称:嘿嘿
英文名称:grinning face
unicode版本: 6.1
unicode编码:U+1F600
一个 emoji 的版权细节可能设计多方的利益:
emoji 的官方名字版权属于 Unicode 规范,即统一码联盟;
emoji 的描述文字版权,属于网站 emojipedia.org;
emoji 对应的图形设计版权,属于它的创作者或公司。
Android不同系统版本之间emoji互通问题
在 Android 7.0 Nougat 和更早版本上无法正确通过 Emoji 表示肤色。用户发送了一个表示深肤色手臂的 Emoji,但对方收到的却是一个手臂和深色方块的分解版本。
在 Android 8.0 Oreo 和更早版本上无法正确显示代表中性的 Emoji。
在 Android 9 Pie 和更早版本上不支持显示多人多肤色的 Emoji。
emoji 编码规则
emoji modifier sequence (修饰序列)
👨🏾是一个Emoji修饰符序列,它由两个Emoji组合而成,分别是:👨(Emoji修饰符基础)和🏾(Emoji修饰符)。肤色Emoji修饰符共有5种,分别是:🏻, 🏼, 🏽, 🏾, 🏿。👨可以与这些肤色Emoji修饰符结合形成新的Emoji序列,下面是组合的例子:
👨(\ud83e\uddd1)👨🏾(\ud83e\uddd1\uD83C\uDFFE)
emoji zwj sequence (零宽连接序列, zwj=zero width joint)
👨👩👧👧(1F468 200D 1F469 200D 1F467 200D 1F467)是一个零宽连接符Emoji序列,它由3个ZWJ零宽度连接符和4个单独Emoji组合在一起形成。这些单独的Emoji分别是:👨 (男人), 👩 (女人), 👧 (女孩), 👧 (女孩)。组成的新Emoji在一些兼容性好的平台中显示为一个单独的Emoji:👨👩👧👧,但在一些兼容性不好的平台也可能显示为多个放在一起的Emoji:👨👩👧👧
Emoji 渲染原理
Emoji 属于一种图形字符,是字符串的一部分。它就像字母 "I" 一样,只是绘制方式和从属的字体文件不同而已。但是对于计算机来说,它并不会特意关心什么是 Emoji 或字母 "I",一个字符串本质上就只是一组码点,其中的数字通过 Unicode 进行分配,代表着计算机上会出现的每一个字符。
现在 Unicode 并不仅仅只是一种格式了,它还代表了制定该标准的委员会,委员会会决定一些事情,比如数字 7 代表字母 "I" (实际上 7 并非真正代表字母 I 的码点,此处仅仅是举个例子)。那么当您试图在 Android 上渲染上述表示字符串的码点时会发生什么呢?
首先,Android 会根据码点和应用要求使用的字体样式为每个字符找出最佳字体。当前 Android 上非斜体且正常粗细 "V" 的默认字体是 roboto-regular.ttf,Android 会对字符串进行遍历,检查每个字符并查找最佳字体。它会检查码点和样式,您可以对字符串进行样式的定制操作,比如对一些字符进行加粗等等。对于上述简单的字符串来说,它就只是会选择 roboto-regular.ttf 字体。
但是,当遇到 Emoji 字符时,您可能会觉得它会进行完全不同的渲染方式,毕竟它看起来不像任何其他的字母。但实际上,Emoji 就是个文本,由码点表示,同字母 "I" 和 "I" 一样没什么区别,绘制它的方式就存储到了字体中。Android 会首先尝试在字体中查找无斜体且正常粗细的 "融化脸",但这一次发现在 roboto-regular 中并没有想要的结果,便会去 NotoColorEmoji 中进行查找,这是 AOSP 上预装的 Emoji 字体,它包含了每个 Emoji 的图像,在 Android 平台上通过这种字体绘制 Emoji 和绘制字母 "I" 的方式完全相同,都是查找字体文件后在屏幕上绘制出来。
在 Android 12 及以上版本中,平台可以确保 Emoji 会正常显示,因为可更新系统字体会将新版 Emoji 添加到字体文件中。但对于 Android 12 之前的版本,我们没有任何方法可以更新字体,这意味着 Android 不知道用什么字体来绘制 "融化脸",此时它会改为绘制一个称为豆腐块的备用字形。这里就是 Emoji2 开始大展身手的地方了。
在将字符串发送到 Android 系统之前,在字符串上会调用 EmojiCompat.process 方法,此调用将遍历并查找那些系统不知道如何绘制的 Emoji,并为每个 Emoji 添加一个 EmojiSpan,这是一个替换 Span,这意味着它将只替换该段字符串中对应的内容。系统会直接使用 roboto-regular.ttf 正常绘制,但当找到 EmojiSpan 时它会将绘制权转交给 Span。
在该 Span 中 Android 使用了两个方法,首先,它会获取字符尺寸并告诉 Android 要在文本布局中为此 Span 保留多少空间,然后,当需要绘制字符串时,它将调用 EmojiSpan 上的 draw 而非自行绘制。在 EmojiSpan 中,它知道 Compat 版的 Emoji 字体位置,并能直接从中绘制出 "融化脸"。再返回到渲染阶段,平台将调用 EmojiSpan.draw,整个区域将由 EmojiSpan 进行绘制,而非平台。实际上,从平台的角度来看 EmojiSpan 只是在字符串中间绘制了一张图片,并没有别的特殊操作。