緣起
最近因為仰慕org-mode,從vim遷移到了Emacs。偶然發現org-mode中調出的calendar第一行居然沒有對齊,排查一下發現是字體的問題。剛好也想改改Emacs的字體,於是我就開始了一段查找資料的過程。
背景
純原始Emacs,init.el中僅有Customize的theme信息,以前一直使用的是默認字體。筆者剛開始使用Emacs三天,因假期在即得以偷閑有時間查資料。
淺探
現象:Org mode使用C-c .調出的Calendar第一行日期沒有對齊,看起來非常別扭。
過程:再橫排出現問題但是豎排沒有,Google之,是字體不等寬的問題。於是乎,最簡單的方式就是設置一個等寬字體,Google 一下“Emacs設置等寬字體”得到函數set-face-attribute,抄之,寫作
(set-face-attribute 'default nil :font "Fira Code Retina")
其中,Fira Code是我很喜歡的一個等寬字體。
結果:Calendar顯示正常。
追究
技術就是解決問題,當問題被解決后繼續投入精力就是浪費精力。
然而字體問題遠不止這么簡單,現在我設置了一個等寬字體,如果我想再換一個中文字體呢?再使用一遍set-face-attribute顯然是行不通的。使用微軟雅黑等寬這樣的特制字體自然是很方便,但是這樣就限制了選擇字體的自由。
路線
明確一下,我的目標是能夠為不同語言定制字體,最好保持等寬,並且能夠適應放大縮小。
很顯然,問題在於emac在背后是如何選擇一個字體(font)的。從頭捋一遍,思路如下:
- Emacs內部使用的編碼集是什么?是否支持Unicode?
- Emacs對編碼方式的支持如何?
- 對特定的character,Emacs如何選擇字體?
set-face-attribute是如何起作用的?應該用什么方式實現定制?
前兩個問題使用Google可以得到,Emacs在最近(實際上也是很久以前了)的版本中使用Unicode重新實現了一遍,我的Emacs是24,支持Unicode;Emacs支持多種編碼方式,至少utf-8、gb2312、gbk這樣的編碼方式是支持的。
face
對於第三個問題,GNU Emacs Manual的font slection一節中指出:
在Emacs將一個字符繪制到圖形顯示設備之前,它必須為這個字符選擇一個字體。正常來說,Emacs會自動根據該font被賦予的那個face的屬性選擇字體——具體而言,就是face屬性
:family,:weight,:slant, and:width。這個選擇過程也依賴於被顯示的那個字符——某些字體只能顯示有限的字符。如果沒有精確符合條件的字體,Emacs就會尋找匹配程度最高的字體。
那么,什么是face呢?繼續查閱Emacs手冊,在Display Faces一節中,有:
當Emacs顯示給定的文本片段時,文本的視覺外觀可以由從不同來源指定的face確定。如果這些來源同時對某個特定的字符指定了超過一個的face,那么Emacs將會把這些faces的屬性合並起來。
無論是Wiki還是能找到的資料,對於Face的定義都是”關於要顯示出來的東西的外在屬性的定義“,包括font的屬性(family,width,slant等等),還有顏色、下划線等等等等(Emacs wiki上甚至說“我們需要一個明確的定義”)。話句話說,face指定了我們會看到什么東西。
同一節指出了合並face的屬性的優先級。其中最低的優先級是default face,也就是我一開始查到的命令所設置的東西,使用 M-h f set-face-attribute可以得到
(set-face-attribute FACE FRAME &rest ARGS)
Set attributes of FACE on FRAME from ARGS.
This function overrides the face attributes specified by FACE's
face spec. It is mostly intended for internal use only.If FRAME is nil, set the attributes for all existing frames, as
well as the default for new frames. If FRAME is t, change the
default for new frames only.
因為設置了默認的face,並且init.el和別的插件(org)也沒有更改face,所以對於能夠用Fira Code顯示的character,Emacs自動選擇了Fira Code。
那么,Emacs又是如何尋找”匹配程度最高“的字體的呢?這就不得不說到另外一個概念了:fontset
fontset
Emacs Wiki上對fontset有一個基本的描述,總結起來要點如下:
- fontset是能夠確定某個font能表示哪些character。它使用<CHARSET or CHAR RANGE> - <FONT NAME>的二元組的表來實現這一點。
- fontset可以被修改,因而如果想使用某種特殊的font來繪制某些字符,使用標准fontset並修改它是最好的選擇。
使用M-x describe-fontset <RET> <RET>可以查詢到當前fontset的詳細信息(運行比較慢),我的顯示如下(經過了修改):
Fontset: -outline-Fira Code Retina-normal-normal-normal-mono-17----c--fontset-auto1
CHAR RANGE (CODE RANGE)
FONT NAME (REQUESTED and [OPENED])
C-@ .. (#x43 .. #x9F)
-------------iso8859-1
(#xA0)
--微軟雅黑------------*
¡ .. « (#xA1 .. #xAB)
-------------iso8859-1
¬ (#xAC)
--微軟雅黑------------*
.. ¯ (#xAD .. #xAF)
-------------iso8859-1
° .. ± (#xB0 .. #xB1)
--微軟雅黑------------*
...
CHAR RANGE打頭的第二行以及第三行是表頭,下面每兩行是一組,每組第一行格式是
<范圍開始處符號> .. <范圍結束處符號> (<范圍開始處符號碼值> .. <范圍結束處符號碼值>)
第二行即是XLFD格式的font描述,這是X window system的字體標准。
當Emacs發現指定的face中的font(我的是Fira Code)無法顯示這個字符時,它就會按照字符集到fontset中找到能夠顯示這個字符的字體,並且使用之。
fontset可以使用set-fontset-font來進行修改。我設置中文字符的代碼如下,其中script的順序來自這篇博客:
(dolist (charset '(kana han symbol cjk-misc bopomofo))
(set-fontset-font (frame-parameter nil 'font)
charset (font-spec :family "微軟雅黑"))
set-fontset-font的使用非常易於理解,
(set-fontset-font NAME TARGET FONT-SPEC &optional FRAME ADD)
Modify fontset NAME to use FONT-SPEC for TARGET characters.
以上代碼其實就是從frame-parameter中取出當前frame的fontset,然后向這個fontset插入某些字符的字體。TARGET可以是字符范圍的起始和結束的cons;可以是script的名字(我的就是script的名字),也可以是一個charset。FONT-SPEC可以用font-spec來確定字體,不用手寫XLFD了。
使用按鍵組合C-u C-x =可以查看point下的那個character的信息,比如筆者在”你“字上按下之后顯示如此:
position: 192 of 192 (99%), column: 0 character: 你 (displayed as 你) (codepoint 20320, #o47540, #x4f60) preferred charset: chinese-gbk (GBK Chinese simplified.) code point in charset: 0xC4E3 script: han syntax: w which means: word category: .:Base, C:2-byte han, L:Left-to-right (strong), c:Chinese, j:Japanese, |:line breakable to input: type "C-x 8 RET HEX-CODEPOINT" or "C-x 8 RET NAME" buffer code: #xE4 #xBD #xA0 file code: #xC4 #xE3 (encoded by coding system chinese-gbk-dos) display: by this font (glyph code) uniscribe:-outline-微軟雅黑-normal-normal-normal-sans-20-*-*-*-p-*-iso8859-1 (#x482)
Character code properties: customize what to show
name: CJK IDEOGRAPH-4F60
general-category: Lo (Letter, Other)
decomposition: (20320) ('你')
中英混排等寬
關於emacs的中英文混排下的等寬以及放縮兼容,這篇博客狠狠地折騰了一把Emacs中文字體進行了一系列探索,改進了把中文字體和英文字體各自設置一個固定的值的方法,轉為某種字體設置放縮系數,最終得到了一個不錯的結果。
但是字體之間的寬度並不是一個固定的比例,對於每種不同的中文——英文字體組合,使用者都需要找不同的參數,還是比較麻煩的。雖然字體並不是一個常換的東西(也許。從這個角度講,或許直接換一個中英文兼有的等寬字體才是正道。
github上也有個項目cnfonts,能夠解決中英混排等寬的問題,作者自述原理是”讓中文字體和英文字體使用不同的字號,從而實現中英文對齊“,效果非常不錯,安裝也很方便,推薦大家試試。
結論
魚和熊掌不可得兼,選擇了選取字體的自由后,就勢必犧牲了適配的便捷性。
- 可以set-face-attribute設置一個默認的中英文等寬字體
- 通過修改fontset來修改特定的字體
- 可以使用第三方包來解決問題。
另外,找完之后才發現,我上一秒還在看Org-mode學習timestamp的用法,回過神來就已經開了十幾個網頁學習Emacs的font了。這種time-killer的折騰還是需要謹慎。
