起因
最近在开发的时候有一个需求:因为空间原因比较小等原因某些位置展示不下,需要进行截断展示,发现国旗图标(🇸🇪)按照这个格式解析不出来,具体排查发现这个国旗图标对应的UTF-8二进制格式是:
111110000
210011111
310000111
410111000
511110000
610011111
710000111
810101010
发现并不满足标准的UTF-8格式,而是两个UTF-8字符编码拼接起来的。
初步排查
通常来讲提到的UTF-8都是基础的基础格式:https://www.rfc-editor.org/rfc/rfc3629#page-5
根据查阅了相关资料及文章后发现国旗图标是按照一定规则由2个字符组合出来的,大致代码如下:
1OFFSET = 0x1F1A5
2def get_country_flag(code):
3 return chr(OFFSET + ord(str.upper(code[0]))) + chr(OFFSET + ord(str.upper(code[1])))
其参数为国家代码,如CN,US等,返回值为对应国家国旗的emoji图标,就是说国旗编码是由两个Unicode字符组合而成的,那么解决办法就是如果当前字符是在 0x1F1E6
到 0x1F1FF
之间,判断下一个字符是否也在这个范围之间,如果在的话,那么就认为这两个共同组成一个显示的文字。
新的问题
处理完国旗问题以后,同事又发了几个新的字符:👩🏻👩🏼👩🏽👩🏾👩🏿,拆解这几个字符会发现和国旗一样是由两个Unicode编码组成的,查阅相关资料后发现emoji实际上是有"皮肤"的概念的,即正常图标+颜色修饰,修饰字符的范围为 0x1F3FB
-0x1F3FF
。
紧接着同事又发了下面这个东西:
1
2
3Z͑ͫ̓ͪ̂ͫ̽͏̴̙̤̞͉͚̯̞̠͍A̴̵̜̰͔ͫ͗͢L̠ͨͧͩ͘G̴̻͈͍͔̹̑͗̎̅͛́Ǫ̵̹̻̝̳͂̌̌͘!͖̬̰̙̗̿̋ͥͥ̂ͣ̐́́͜͞
4
5
这个东西实际上是对 ZALGO!
的这五个字符的修饰,就是每个字母后面都跟着大量的用于修饰前面字符的编码。
查阅相关资料后发现这些组合字符的范围分为几段:0x0300
- 0x036F
、0x1DC0
- 0x1DFF
、0x20D0
- 0x20FF
、0xFE20
- 0xFE2F
,这些编码一定是依赖于前一个字符存在而存在的。
后来找到了这样两个网址:
https://www.unicode.org/Public/emoji/14.0/emoji-sequences.txt
https://www.unicode.org/Public/emoji/14.0/emoji-zwj-sequences.txt
这里面是Unicode14.0的基础emoji和组合emoji图标,在这里面又发现了几个之前没注意到的编码:0x200D
和 0xFE0F
。
0x200D
是用于将前面一个编码和后面一个编码“粘起来”的编码,0x200D
前后共通组成一个用于显示的字符。
0xFE0F
是用于修饰前面一个编码的(根据相关文档介绍, 0xFE00
-0xFE0F
均是,但是常见的只有 0xFE0E
和 0xFE0F
),具体用处还没太搞清楚。
此外,还有除了常规两个编码拼接起来的国旗以外,还有三个Unicode 5.0加入的由七个编码组成的特殊国旗:🏴、🏴、🏴,这三个还需要特殊处理。
在查找问题的过程中还发现了这样一个网站:https://www.compart.com/en/unicode/,可以很方便的查询单个Unicode编码的用处。
总结
目前很多程序使用的都是UTF-8或者UTF-16编码,需要先拿到每个字符的Unicode编号,这个在大部分语言中都是内置提供的,不需要特殊处理,直接使用即可。
- 从前往后读取的过程中,需要判断当前字符是否是国旗编码所在范围内,如果在的话需要判断是否能与下一个(普通国旗)或多个字符(🏴🏴🏴)组成国旗,如果能的话需要和前一个合并处理。
- 判断下一个字符是否是修饰编码或者组合编码(如
0xFE0F
或0x0300
等),如果是的话那么也需要合并到前一个处理。 - 上述判断完成后还需要判断下一个字符是否是
0x200D
,如果是的话,那么需要再向下读取前至少1个,具体数量重复上述1-2步骤进行判断。
综合以上信息可以解决目前遇到的大部分的Unicode字符截断展示的问题,但这里面有两个问题:
- 目前接触到的类型还比较少,可能还有尚没有接触到的编码,遇到了还需要进行补充。
- Unicode版本是一直在更新的,如果新版本发布了可能还需要跟进相关的适配。