问题背景的产生:iOS - NSString 与 Emoji
在解决这个问题的过程中,发现一个 Emoji 字符的 UTF-16 编码长度经常会有超过 2 的情况,而 Unicode 一个码点用两个 UTF-16 编码长度已经足够表示了。而一个字符编码长度超过 2 的情况就涉及到了 Unicode 的组合字符。
组合字符
Unicode 中有一类字符称为组合字符,它可以附加在前一个非组合字符上,从而使整体看起来像是一个字符。组合字符原来目的是为了解决一些地区语言、文字特殊的需要,比如说泰文声调符号与母音符号。
通过组合字符可以实现一些比较有趣的效果,比如:
利用这两个字符和中文字符 ”啊“ 结合,可以得到 ”啊̮̮̮̮̮̮̮̮̮̮̮̑̑̑̑̑̑̑̑̑̑̑“ 这个神奇的字符,这个字符的编码用 OC 表示为:
@"啊\u0311\u032E\u0311\u032E\u0311\u032E\u0311\u032E\u0311\u032E\u0311\u032E\u0311\u032E\u0311\u032E\u0311\u032E\u0311\u032E\u0311\u032E"
只是在 "啊" 这个字符后面不断的重复这两个组合字符。而 U+0311 和 U+032E 这两个字符也可以单独显示为:
Emoji 与组合字符
其实在 Emoji 中,也可以看到组合字符的大量应用,比如:
+ ✋ = ✋ (由于编辑器原因皮肤写在最前面才能正常显示)
- U+1F3FB
✋ - U+270B
✋ - U+270B U+1F3FB
作为组合字符和上一个字符组合 ✋ 形成了新的字符 ✋。在 Emoji 中,组合字符被用作调整 Emoji 颜色,使 Emoji 可以表示不同的人种和肤色。
零宽字符
Unicode 中除了组合字符之外,还有一种特殊的字符可以实现组合字的效果,这种字符不可见,不可打印,主要作用于调整字符的显示格式,我们将其称为零宽字符。
零宽字符主要有以下几类:
- 零宽度空格符 (zero-width space) U+200B : 用于较长单词的换行分隔
- 零宽度非断空格符 (zero width no-break space) U+FEFF : 用于阻止特定位置的换行分隔
- 零宽度连字符 (zero-width joiner) U+200D : 用于阿拉伯文与印度语系等文字中,使不会发生连字的字符间产生连字效果
- 零宽度断字符 (zero-width non-joiner) U+200C : 用于阿拉伯文,德文,印度语系等文字中,阻止会发生连字的字符间的连字效果
- 左至右符 (left-to-right mark) U+200E : 用于在混合文字方向的多种语言文本中(例:混合左至右书写的英语与右至左书写的希伯来语),规定排版文字书写方向为左至右
- 右至左符 (right-to-left mark) U+200F : 用于在混合文字方向的多种语言文本中,规定排版文字书写方向为右至左
零宽字符在实际应用中也有很多有趣的用法,在本文中我们只探究 Emoji 中的零宽字符
Emoji 与零宽字符
在 Emoji 中除了组合字符之外,零宽字符对于 Emoji 也是非常重要的。比如对于 Emoji 字符:
它由四个不同的码点组合而成:
- U+1F468 - 基础字符
- U+1F3FB - 组合字符,表示肤色
- U+200D - 零宽度连字符,表示上下相连
- U+1F9B3 - 基础字符,表示发型
如果去掉其中的连字符,则会得到两个分离的字符:
♀ - U+1F468 U+1F3FB U+1F9B3
可以发现,连字符在这个 Emoji 中的作用是使用 的发型修饰上一个字符 。
除了发型,连字符的作用还包括了,修饰性别:
它由四个不同的码点组合而成:
- U+1F468 - 基础字符
- U+1F3FB - 组合字符,表示肤色
- U+200D - 零宽度连字符,表示上下相连
♀ - U+2640 - 基础字符,表示性别
组合图形:
它由五个不同的码点组合而成:
- U+1F469 - 基础字符
- U+200D - 零宽度连字符,表示上下相连
❤ - U+2764 - 基础字符
- U+200D - 零宽度连字符,表示上下相连
- U+1F468 - 基础字符
以及更多的组合图形:
它由七个不同的码点组合而成:
- U+1F469 - 基础字符
- U+200D - 零宽度连字符,表示上下相连
- U+1F469 - 基础字符
- U+200D - 零宽度连字符,表示上下相连
- U+1F466 - 基础字符
- U+200D - 零宽度连字符,表示上下相连
- U+1F466 - 基础字符
可能还有其他的情景没有提及的,有兴趣的同学可以自己探究一下