Emoji全平台适配方案:使用彩色/图片字体

Android iOS 一个都不能少

作者 Moonshot 日期 2018-07-10
Emoji全平台适配方案:使用彩色/图片字体

那一天和妹纸聊完天后,一切都 索然无味 怅然落失。

因为 我要是一整天没和你说上话,那一天都黯淡无光。 —HIMYM

然后就开始看看咨询和文章,然后发现Google提供Emoji兼容包EmojiCompat Support Library .是通过字体来实现Emoji的兼容。这时候我才发现原来系统Emoji的渲染是通过字体的实现的,原来字体里可以放非纯色的字形。
那么如果可以将国内主流的Emoji样式图片(Apple的样式)打包进字体,或者设计师自己设计的一套Emoji。只需设置字体,不需要通过常见的ImageSpan进行过多的代码上的兼容,那不是很完美吗?

可行性

使用自定义字体方案 最重要的保证除了Emoji之外其他部分能用原系统字体进行渲染,才能保证原有的文本体验。
要验证可行性, 那么首先需要有个彩色字体进行验证。然后发现这样一个谷歌的开源库noto-emoji。里面NotoColorEmoji.ttf这个彩色字体文件。

测试后发现在8.0上正常,但是在一台5.1、4.4的机子是异常的。然后网上说是用旧版本才可以。然后经测试Tag:v2017-05-18-cook-color-fix下的彩色字体在8.0、7.0、5.1、4.4上都是正常的。且普通文本(字体文件未定义的文本)和平时渲染一样、

但是在4.3上是无法生效的。原因是google在4.4的之后的版本才通过noto-font来支持彩色的Emoji表情。4.3上默认是黑白的Emoji,所以只能兼容4.4及之后的版本。

打包彩色字体文件

然后花了十分钟了解了下这个库,打包的关键在于third_party下的python脚本emoji_builder.py

基础准备

  • 安装python2.7的环境
  • 安装依赖的另一个库nototools 。具体步骤详见官方Github
  • 了解普通字体的基本一些基本概念(其实也可以不用)

打包步骤

打包命令如下:

python emoji_builder.py source-input.ttf color-font-outout.ttf ./emoji_image/

因此我们需要一个源字体文件和需要的打包的图片资源的文件夹。在开源库中分别就是NotoEmoji-Regular.ttf]和png/128/。然后代志没有憨人想的那么简单,直接用这两个资源去执行脚本并不能成功打彩色字体文件。(提供程序员门槛,从而避免失业???)然后就需要自己的改脚本了。(见后文)

没兴趣的同学可以直接使用修改后的版本-分支android_emoji_font

  • 准备一个普通的字体文件(可使用NotoEmoji-Regular.ttf使用相应的工具进行修改),内含需要处理的emoji的unicode编码和命名。
  • 要适配的图片资源按规则修改并修改成相应的文件名如1f603.png 。1f603为unicode编码,对应这个步骤中的字体中的编码。
  • 然后执行打包的脚本命令即可

踩坑改脚本

确定图片资源的命名规则

遇到的第一个问题:图片命名导致编码读取失败,脚本终止

img_files = {}
glb = "%s*.png" % img_prefix
print("Looking for images matching '%s'." % glb)
for img_file in glob.glob (glb):
codes = img_file[len (img_prefix):-4]
if "_" in codes:
pieces = codes.split ("_")
cps = [int(code, 16) for code in pieces]
uchars = "".join ([unichr(cp) for cp in cps if not is_vs(cp)])
else:
cp = int(codes, 16)
if is_vs(cp):
print("ignoring unexpected vs input %04x" % cp)
continue
uchars = unichr(cp)

这段代码主要遍历图片文件夹下的图片,获得根据文件名获取对应的unicode编码。问题在于img_file[len (img_prefix):-4]只去除了后缀.png,但是官方提供的文件是emoji_u[xxx].png的的文件所以无法直接使用。(所以直接去掉前缀即可)

修复0-/u00ff (0-255)与/uffff编码之外的打包失败

首先了解下普通字体内部的组成,一个字体文件定义了若干图形。用工具打开ttf文件可以看到类似下图的内容。

一般有三个部分:字形(一般是矢量图)、编码、命名

font-struct

0-255的编码根本不能满足我们的需求,且占用了基本的字符,而一般emoji的编码范围是在/uFFFF之外,显然现状是不行的。

那么问题出在哪?主要有两个地方 1.编码的获取 2.对应的命名/字形名的获取

命名的获取

codes = img_file[len (img_prefix):-4]
if "_" in codes:
pieces = codes.split ("_")
cps = [int(code, 16) for code in pieces]
uchars = "".join ([unichr(cp) for cp in cps if not is_vs(cp)])
else:
cp = int(codes, 16)
if is_vs(cp):
print("ignoring unexpected vs input %04x" % cp)
continue
uchars = unichr(cp)

字形名的获取

当len (uchars)为1时(/u00ff/uffff 这种长度为2)通过Cmap)表获取编码对应的命名

超过时根据提供的其他函数获取命名

if len (uchars) == 1:
try:
glyph_name = unicode_cmap.cmap[ord (uchars)]
except:
print("no cmap entry for %x" % ord(uchars))
raise ValueError("%x" % ord(uchars))
else:
glyph_name = get_glyph_name_from_gsub (uchars, font, unicode_cmap.cmap)
glyph_id = font.getGlyphID (glyph_name)
glyph_imgs[glyph_id] = img_file

其中255内正常,在命名超过00ff.png时uchars = unichr(cp)会报错。这个函数是获取相应的的unicode的字符串(其实知道为什么错误)然后通过直接获取对应unicode编码的16进制数字。

uchars = int(img_file[len(img_prefix):-4], 16)

相应ord (uchars)直接替换成uchars后则直接成功

然后获取字形名直接使用: 这样则可以保证在/uffff之外的编码正常

glyph_name = unicode_cmap.cmap[uchars]

其他注意事项

  • 图片命名先需要严格限定为[unicode].png 例如1f609.png
  • 原始的普通字体需要只放置需要的打包的字形的信息,其他无关的内容要删除,否则会导致Texiview无法显示该字形

性能分析与对比

Android上的表情主流方案是使用ImageSpan去处理。这里直接使用Github上一个库进行测试Emojicon它使用的图片资源的规格也是45*45px.

对比时间:Activity onCreate 到TextView onDraw结束

Emoji编码数量 EmojiCon三次耗时 ms 彩色字体三次耗时
40 390+208+222 119+184+128
200 441+305+306 216+118+121
800 588+599+457 208+131+124

由于ImageSpan的方案需要做更多的编码匹配,加载图片设置Span等操作,可以预见性能上会差于使用彩色字体的方案。当然也少些了很多代码。

在iOS使用彩色字体

opentype
先上一张图,其实各个平台对彩色字体都有支持,只是可是所扩展的规则不太一样。所以理论上只有打出合适的字体文件,在IOS也是可以使用的。就可以达到设计师设计一套独特的emoji表情在多平台上使用。

然后一不做二不休,花了21天(滑稽)学习了下iOS。

  • 打iOS的彩色字体文件使用Emoji-Tools。这个工具可以打iOS的字体文件也可以打Android的字体文件(所以上文也不是很有必要,心酸)

  • 在iOS使用自定义字体

    UIFont *font = [UIFont fontWithName:@"AppleCustomEmoji" size:24];

    然后其实iOS使用自定义字体最麻烦的就是确认这个AppleCustomEmoji这个字体名,实际上Emoji-Tools打包的出来的字体名是Apple Color Emoji和系统默认是一样的名字,所以是不能直接使用。

  • 然后最终找到一个工具TTFEDIT,使用它修改掉字体名,然后重新添加在项目中即可使用

最终效果:
Android7.0的果冻人是真的好看(辣鸡谷歌8.0变得难看得一匹)

在Web上使用彩色字体

TODO

总结

  • 彩色字体来适配或者实现独立一套Emoji表情适用各个平台

  • 在Android上只能兼容4.4及以上的系统,但性能较好

  • 字体包体积实测略大于图片资源文件总大小,在接受范围内。(图片太大可走在线现在)

  • 缺点:不支持打包gif、webp格式的图片(其实没试过,应该是不行)

参考文章

  1. 彩色字体生成小记
  2. 探索在Android中使用Emoji Font的方法