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看上去是無辜的,所以誰壞掉了呢?