浅谈 Emoji 中的 Unicode 编码
要知道 Emoji 中的编码,我们就得先知道「Unicode」这个玩意,在官方网站中有这么一句话:「Unicode 为所有的字符都提供了一个独一无二的数字,不论在哪个平台,不论在什么项目,不论是怎么语言。」
Unicode provides a unique number for every character, no matter what the platform, no matter what the program, no matter what the language. -- Unicode Information
什么是 Unicode?
最简单的来说,他就是使用英文+数字+"+"来表示我们日常的字符的,譬如说这个文章我们就可以使用 Unicode 全部写出来,当然就不是给人看的了,为什么?
Unicode 标准定义了一个 code space,一组从 0 到 10FFFF[^1] 范围的数值被称为「代码点」, 并且会表示为 U+0000 到 U+10FFFF
「U+」后面跟的十六进制的代码点值,前面有前导零[^2],代码点值至少四位数
比如说
U+00F7
表示÷
,代码点 U+0041 是十六进制数 0041(等于十进制数 65)代表 Unicode 标准中的字符“A”,而 U+13254 (不是 U+013254,没有前导零)表示的是某一个埃及象形文字
但是在众多代码点中,从 U+D800 到 U+DFFF 的代码点,是不允许编码有效字符的,它将被 Unicode 保留。
U+D800–U+DBFF(1024 个代码点)范围内的代码点称为高代理代码点,而 U+DC00–U+DFFF(1024 个代码点)范围内的代码点称为低代理代码点码点。1高1低在 UTF-16 中形成一个代理对,以此来表示大于 U+FFFF 的代码点。
在排除代理项和非字符后,有 1,111,998 个代码点可以使用。
UTF-X 都是些什么?
UTF-8 是一种变长的 Unicode 编码方式,它可以使用 1 到 4 个字节表示一个符号,根据不同的符号而变化字节长度。
UTF-8 的编码规则很简单,只有二条:
- 对于单字节的符号,字节的第一位设为0,后面7位为这个符号的 Unicode 码。因此对于英语字母,UTF-8 编码和 ASCII 码是相同的。
- 对于 n 字节的符号(n > 1),第一个字节的前 n 位都设为1,第 n + 1 位设为0,后面字节的前两位一律设为10。剩下的没有提及的二进制位,全部为这个符号的 Unicode 码。
UTF-8 编码的一个例子:
Unicode 码点 | UTF-8 编码(二进制) | UTF-8 编码(十六进制) |
---|---|---|
U+0000 | 00000000 | 00 |
U+0007 | 00000111 | 07 |
U+0041 | 01000001 | 41 |
UTF-16 是一种 Unicode 的编码方式,它使用 2 或 4 个字节来表示一个符号(字符)。UTF-16实现方案则介于UTF-8和UTF-32之间。对最常用的基本平面中字符的存储空间进行了压缩,使得汉字只需要两个字节就可以存储。
但是UTF-16又是怎么解决字符存储的时不同字符的边界问题的?这就需要用到代理对的概念了。
前面提到代理对的概念。UTF-16 将代理对的第一个代码点的值减去 0xD800,将第二个代码点的值减去 0xDC00,然后将两个结果合并,得到一个 20 位的二进制数,加上 0x10000,就得到了原来的代码点的值。
UTF-16 编码的一个例子:
Unicode 码点 | UTF-16 编码(二进制) | UTF-16 编码(十六进制) |
---|---|---|
U+0000 | 0000000000000000 | 0000 |
U+0007 | 0000000000000111 | 0007 |
U+0041 | 0000000001000001 | 0041 |
这样,UTF-16 就可以使用 2 个字节来表示 U+0000 到 U+FFFF 之间的字符,而对于 U+10000 到 U+10FFFF 之间的字符,就需要使用 4 个字节来表示了。这样就可以保证所有的字符都可以被编码,而且编码后的字节长度也是固定的。
当然,除 Unicode 以外,还有很多不同的编码标准(如 GBK 等)但本文不详细说他们了。
讲讲 Emoji 的编码
Emoji 我们日常都会使用,比如 😊 它们都是 Unicode UTF-16 编码的,在 UTF-8 编码中,它们会被编码成 8 个字节,这样的编码方式就会导致 Emoji 的存储空间变大。
从U+1F300开始,存放的这些小表情就是emoji表情。
但需要注意的是,Unicode 本身是不支持 Emoji 的,Emoji 是由 Unicode 和 Emoji 两部分组成的,Unicode 只是规定了 Emoji 的编码,而 Emoji 是由 Apple、Google 等公司自己定义的。
如果用户的系统没有实现某个emoji表情的渲染,就会显示成一个空方框。
一个码位 | 三个码位 | 四个码位 |
我们看到,一个 emoji 表情是变长存储的,而且一个表情可占用多个码位组成。那么这些表情的渲染又有什么规则呢?
在 Unicode 官方文档中,有提到相关的内容 我简单给大家总结一下:
- 一个 emoji 表情的渲染是由多个码位组成的,这些码位的值是连续的,且值的范围是在 U+1F300 到 U+1F5FF 之间的。
- emoji 标签由两部分组成,第一部分是 emoji 的类型,第二部分是 emoji 的子类型,比如 😊 的 emoji 标签是
Smileys & Emotion
,Face Smiling
。 emoji 标签的具体内容可以参考这里。
有一个很有趣的现象,就是 emoji 表情的渲染是由多个码位组成的,但是这些码位的值是连续的,且值的范围是在 U+1F300 到 U+1F5FF 之间的。这样的设计,就导致了 emoji 表情的渲染是有规律的,比如我们可以通过下面的代码来生成 emoji 表情:
function generateEmoji() {
const start = 0x1f300;
const end = 0x1f5ff;
const code = Math.floor(Math.random() * (end - start + 1)) + start;
return String.fromCodePoint(code);
}
甚至,你可以用 js 的 replace 方法来替换 emoji 表情,假设我们要替换一下 👨👩👧👧 中的孩子,要把它变 👨👩👧👧 ,我们可以这样做:
'👨👩👧👧'.replace('👦', '👧'); // 👨👩👧👧
可以看到,这个白发男人,是由三个码位组成: U+1F468 U+200D U+1F9B3,而这个白发男人的 emoji 标签是 People & Body
,Person
。这表明,U+1F468 表示的是人,U+200D 表示的是连接符,U+1F9B3 表示的是白发男人。