缘起
最近因为仰慕 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 https://www.emacswiki.org/emacs/FontSets 上对 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 Windows system 的字体标准.
当 Emacs 发现指定的 face 中的 font(我的是 Fira Code)无法显示这个字符时, 它就会按照字符集到 fontset 中找到能够显示这个字符的字体, 并且使用之.
fontset 可以使用 set-fontset-font 来进行修改. 我设置中文字符的代码如下, 其中 script 的顺序来自这篇博客 http://zhuoqiang.me/torture-emacs.html :
- (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 上也有个项目 https://github.com/tumashu/cnfonts , 能够解决中英混排等宽的问题, 作者自述原理是 "让中文字体和英文字体使用不同的字号, 从而实现中英文对齐", 效果非常不错, 安装也很方便, 推荐大家试试.
结论
鱼和熊掌不可得兼, 选择了选取字体的自由后, 就势必牺牲了适配的便捷性.
可以 set-face-attribute 设置一个默认的中英文等宽字体
通过修改 fontset 来修改特定的字体
可以使用第三方包来解决问题.
另外, 找完之后才发现, 我上一秒还在看 Org-mode 学习 timestamp 的用法, 回过神来就已经开了十几个网页学习 Emacs 的 font 了. 这种 time-killer 的折腾还是需要谨慎.
来源: https://www.cnblogs.com/eadle/p/10658661.html