2017年8月更新。
多色Emoji字体
Cairo支持
较新的FreeType支持多色,但cairo-1.14.6没有默认开启支持。hexchain指出修改cairo源码一行代码即可:
1 | --- a/src/cairo-1.14.6/src/cairo-ft-font.c 2016-03-13 09:36:42.618325503 +0800 |
然后编译安装cairo。
2017年8月更新,cairo-1.14.10依然需要这个patch……
Arch Linux普通用户
可以安装aur/cairo-coloredemoji
。
Arch Linux infinality-bundle用户
huiyiqun指出,infinality-bundle用户应该:
- 安装https://aur.archlinux.org/packages/cairo-infinality-ultimate-with-colored-emoji/
删除。按https://gist.github.com/huiyiqun/9f20f177655946263a48170ee662cea9配置fontconfig。/etc/fonts/conf.d/82-no-embedded-bitmaps.conf
Fontconfig配置
安装Noto Color Emoji字体,Arch
Linux可以装extra/noto-fonts-emoji
。
推荐使用hexchain的配置:https://gist.github.com/hexchain/47f550472e79d0805060。把Noto
Color
Emoji的配置放到~/.config/fontconfig/conf.d/
或全局的/etc/fonts/conf.d/
,使用fc-cache
或sudo fc-cache
更新。
可以用fc-match -s monospace
、fc-match -s sans
、fc-match -s sans-serif
确定字体选择顺序。我这里Noto
Color Emoji只能排到第二,不明原因。
DejaVu Serif/Sans包含部分黑白emoji glyphs,如果fc-match顺序在Noto Color Emoji前面,就看不到后者的相应字形。可以考虑安装aur/ttf-dejavu-emojiless等移除emoji glyphs的包。
Emoji字符宽度
经过以上两步配置,理应可以使用Noto Color Emoji字体了。但对于Monospace字体,emoji字符显示宽度为1,会与其后的字符重叠。
vte中设置emoji字符宽度
2017年8月更新,较新的vte3-ng或依赖的glib终于修好了宽度计算,不需要这么patch了。
在终端模拟器中,字符是按列显示的,因此有宽度的概念。vte把emoji字符当作1,因此显示时会与之后的字符重叠。
以终端模拟器termite使用的community/vte3-ng
为例。修改源码src/vte.cc:_vte_unichar_width
,让该函数对于某些Noto
Color Emoji提供的Unicode block返回2。此处我硬编码了几个用到的Unicode
block,不全: 1
2
3
4
5
6
7
8
9
10
11
12
13
14--- a/src/vte.cc 2016-03-12 23:44:04.157720973 +0800
+++ b/src/vte.cc 2016-03-13 00:20:45.592623311 +0800
return 0;
if (G_UNLIKELY (g_unichar_iswide (c)))
return 2;
+ if (G_UNLIKELY(0x25a0 <= c && c < 0x27c0 || // Geometric Shapes, Miscellaneous Symbols, Dingbats
+ 0x2b00 <= c && c < 0x2c00 || // Miscellaneous Symbols and Arrows
+ 0x1f300 <= c && c < 0x1f700 || // Miscellaneous Symbols and Pictographs ... Geometric Shapes Extended
+ 0))
+ return 2;
if (G_LIKELY (utf8_ambiguous_width == 1))
return 1;
if (G_UNLIKELY (g_unichar_iswide_cjk (c)))
Arch
Linux可以装aur/vte3-ng-fullwidth-emoji
。该patch另外修复了canonical
mode下退格宽字符,终端模拟器只移动一格光标的问题(其实应该是kernel的bug,不妨头痛医脚)。
设置wcwidth
宽度
对于ncurses应用,会用wcwidth
计算待擦除字符的宽度。仅经过上面的配置,wcwidth
仍认为emoji字符宽度为1,擦除时宽度计算不对,可能导致一些字符残余在屏幕上。
wcwidth
对字符宽度的计算由locale决定,比如对于常用的en_US.UTF-8
等,glibc提供的/usr/share/i18n/charmaps/UTF-8.gz
中WIDTH
、END WIDTH
区块给出了字符宽度信息。但其中没有列出Emoji字符,因此宽度将用缺省值1。
我用https://gist.github.com/MaskRay/86b71b50d30cfffbca7a重新生成一个UTF-8
,gzip压缩后覆盖/usr/share/i18n/charmaps/UTF-8.gz
,然后执行locale-gen
。修改后,可以用https://gist.github.com/MaskRay/8042e39dc822a57c217f确定wcwidth
计算出来的宽度确实变更了。
glibc 2.26支持Unicode 10.0.0的character encoding, character type info, transliteration tables,很多emoji字符的宽度应该正确了。
Emoji ligature
很多emoji是由几个codepoints拼凑出来的,比如Regional Indicator Symbol Letter C(U+1F1E8)和Regional Indicator Symbol Letter n(U+1F1F3)组合得到中国国旗(🇨🇳),skin tone modifier可以切换肤色。这类emoji宽度在终端里就很难弄对。
Arch Linux用户
/etc/pacman.d/hooks/update-charmaps-UTF-8.hook
:
1 | [Trigger] |
/etc/pacman.d/hooks/update-charmaps-UTF-8.py
:
从https://gist.github.com/MaskRay/86b71b50d30cfffbca7a下载
效果
1 | echo 🚂🚊🚉🚞🚆🚄🚅🚈🚇🚝🚋🚃🚟 |
为了这张笑脸真是一把辛酸泪……
感想
Monospace实现很困难。要画出好看的Emoji,势必要用fullwidth,这个属性如果能由字体提供是再好不过的。对于底层的glibc、glib
,它们并不能知道字体,但又不得不规定字符宽度,一不小心即与字体的实际宽度产生冲突。翻了翻http://www.unicode.org/Public/UCD/latest/ucd/等,好多地方分散地提到了fullwidth,比如Halfwidth
and Fullwidth Forms、East Asian
Width等,不同实现对这些概念的理解会有偏差……
在neovim里写这篇文章时又发现neovim对字符宽度也有问题,src/nvim/mbyte.c:utf_char2cells
看上去是无辜的,所以谁坏掉了呢?