8

终端模拟器下使用双倍宽度多色Emoji字体

 3 years ago
source link: http://maskray.me/blog/2016-03-13-terminal-emulator-fullwidth-color-emoji
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

终端模拟器下使用双倍宽度多色Emoji字体

2017年8月更新。

多色Emoji字体

Cairo支持

较新的FreeType支持多色,但cairo-1.14.6没有默认开启支持。hexchain指出修改cairo源码一行代码即可:

--- a/src/cairo-1.14.6/src/cairo-ft-font.c 2016-03-13 09:36:42.618325503 +0800
+++ b/src/cairo-1.14.6/src/cairo-ft-font.c 2016-03-13 09:38:24.194288159 +0800
@@ -2258,7 +2258,7 @@
* Moreover, none of our backends and compositors currently support
* color glyphs. As such, this is currently disabled.
- /* load_flags |= FT_LOAD_COLOR; */
+ load_flags |= FT_LOAD_COLOR;
#endif
error = FT_Load_Glyph (face,

然后编译安装cairo。

2017年8月更新,cairo-1.14.10依然需要这个patch……

Arch Linux普通用户

可以安装aur/cairo-coloredemoji

Arch Linux infinality-bundle用户

huiyiqun指出,infinality-bundle用户应该:

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-cachesudo fc-cache更新。

可以用fc-match -s monospacefc-match -s sansfc-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,不全:

--- a/src/vte.cc 2016-03-12 23:44:04.157720973 +0800
+++ b/src/vte.cc 2016-03-13 00:20:45.592623311 +0800
@@ -206,6 +206,10 @@
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
+ 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.gzWIDTHEND 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:

[Trigger]
Operation = Upgrade
Type = File
Target = usr/share/i18n/charmaps/UTF-8.gz
[Action]
When = PostTransaction
Exec = /etc/pacman.d/hooks/update-charmaps-UTF-8.py

/etc/pacman.d/hooks/update-charmaps-UTF-8.py:

https://gist.github.com/MaskRay/86b71b50d30cfffbca7a下载

echo 🚂🚊🚉🚞🚆🚄🚅🚈🚇🚝🚋🚃🚟
trains.jpg

为了这张笑脸真是一把辛酸泪……

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看上去是无辜的,所以谁坏掉了呢?

Share Comments


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK