较新的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。Arch Linux可以 yaourt -G cairo
下载cairo的 PKGBUILD
后 makepkg
。
安装Noto Color Emoji字体,Arch Linux可以装 extra/noto-fonts-emoji
。
在 ~/.config/fontconfig/fonts.conf
(更准确点应用 $XDG_CONFIG_HOME
)中添加下面两个 <match>
标签(hexchain提供)。
<fontconfig>
...
<match target="scan">
<test name="family">
<string>Noto Color Emoji</string>
</test>
<edit name="scalable" mode="assign"><bool>true</bool></edit>
</match>
<match target="pattern">
<edit name="family" mode="prepend">
<string>Noto Color Emoji</string>
</edit>
</match>
</fontconfig>
可以用 fc-match -s monospace
、 fc-match -s sans
、 fc-match -s sans-serif
确定字体选择顺序。我这里Noto Color Emoji只能排到第二,不明原因。
经过以上两步配置,理应可以使用Noto Color Emoji字体了。但对于Monospace字体,emoji字符显示宽度为1,会与其后的字符重叠。
在终端模拟器中,字符是按列显示的,因此有宽度的概念。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
+ 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)))
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
计算出来的宽度确实变更了。
echo :steam_locomotive::tram::station::mountain_railway::train2::bullettrain_side::bullettrain_front::light_rail::metro::monorail::train::railway_car::suspension_railway:
为了这张笑脸真是一把辛酸泪……
要画出好看的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
看上去是无辜的,所以谁坏掉了呢?