代码字体其实是一类特殊字体,通常情况下来说它可以被认为是带一些特殊需求(例如无衬线、等宽、易混淆字符区别设计等)的拉丁字体。但作为一个编程爱好者,这类字体用得比较多,所以首先介绍代码字体。
Unicode 覆盖范围
用于代码或者说编程的字体的覆盖范围说简单也简单,说复杂也复杂。在 Unicode 之前,计算机代码基本都是以 ASCII 或者其子集中的字符书写的。所以覆盖 ASCII 中 95 个可打印字符,就算是覆盖了绝大多数代码的用字范围。但这仅仅是就一般情况而论,由于以下情况,代码中出现非 ASCII 字符也是有可能的。
程序员母语
从执行的角度来说,计算机代码只需要让编译器看懂就可以了。但是从开发维护的角度来说,计算机代码还要能够让程序员看懂。而程序员的母语千差万别,每个程序员都想用自己最熟悉的文字写代码,因此总存在往程序代码中添加各种文字的需求。经过几十年的实践,这些需求大致会被以两种方式回应:
相对彻底的本地化替换:例如易语言、文言编程语言(wenyan-lang)就属于这种,代码基本上可以用纯汉字来写。理论上也可以有希腊文编程、古埃及文编程、楔形文字编程等等。但它们要么是局限于某些程序员社群使用,要么就干脆是一个实验性作品。互联网虽然允许个性,但也提倡交流。越个性化的编程语言其实越不方便交流。
兼容:大多数广泛使用的编程语言的新版本都采用这种方式。即可以在有效代码中出现非 ASCII 字符,但是不推荐这么做。即便是不允许有限代码中出现非 ASCII 字符的语言一般也允许注释中出现非 ASCII 字符。
语言文字问题一直是没入门的编程学习者的重要借口。但其实在 Python 等语言支持汉字作变量名之前,也完全可以通过写汉语拼音的方式用汉语命名标识符。所以只能说,这个问题存在,但根本没那么严重。不然,为什么易语言出现之后,很多不会编程的还是不会呢?
但从工程实践的角度来说,还是不推荐可编译的有效代码中出现非 ASCII 字符。就算不会给编译器造成麻烦也容易给看代码、改代码的人造成麻烦。不过,如果开发同一个项目的同事们彼此语言相通的话,在注释里写写非 ASCII 字符倒是无伤大雅。
表达能力拓展
一些编程语言,如 Julia 等会为了拓展表达能力,允许使用非 ASCII 字符。例如 Julia 为了让数学公式在代码中显得简洁一些,允许使用希腊字母替代其拉丁转写(而且官方教程还特意演示了这一功能)。
这类拓展确实能让代码表达能力拓展,但是相应的学习成本也上升了。而且最麻烦的是这些拓展尚未形成一个统一的范式。反观仅用 ASCII 的代码,在命名约定清晰有效的情况下,也不见得就表达能力不足。所以个人建议还是尽量从命名风格等角度去处理这些精简公式表达的问题,而不是在可编译执行的部分引入非 ASCII 字符。
当然还是那句话,在注释当中想用希腊字母写写数学公式是没问题的。
数据文件
并不是只有扔到编译器中执行的才叫做代码。程序读写的表格和文本等数据文件也是也可以看作代码。由于这些数据文件不一定是按照可编译执行的代码的要求产生的,所以很自然会有非 ASCII 字符。例如一个中文网页的 HTML 文件包含汉字是再正常不过的了(总不能全写成转移序列吧)。但作为程序员其实免不了用代码编辑器去查看这些含有非 ASCII 字符的文本。
小结
综上所述,虽然在编码工作中主要和 95 个可打印 ASCII 字符打交道,但所查看的文本中出现非 ASCII 字符其实是避免不了的。你可以确保可编译执行的代码文件中不出现非 ASCII 字符,但是对于数据文件作同样要求有点多余且不易操作。
所以一款代码字体最基本要支持 95 个 ASCII 可打印字符。根据使用场景可以再作拓展和补充。
代码字体功能要求
辨识度
在自然语言中, 26 个拉丁字母出现的概率并不是均等的。因此一些字母和数字写得相近一点,不构成太大问题。但在编程语言中, 95 个 ASCII 可打印字符是可以一视同仁的,所有组合排列都是有可能出现的。这就要求它们彼此之前有较高的辨识度不至于混淆。
例如,在一般的字体设计中 1lI, Z2z, 0Oo 等非常容易混淆。而在代码字体中,即便牺牲字体风格的一致性也要为它们作可区分的设计。
等宽
对于汉字来说,这似乎不是一个特殊要求,因为相同段落中字字宽高相等本身就是汉字石刻和印刷的审美追求。但对于拉丁字符来说,每个字母的宽度是不尽相同的,例如“m”比“n”宽,“n”又可能比“i”宽。这样宽窄相济,拼在一起形成了拉丁文的审美风格。
如果使用一般的拉丁字符字体, ASCII 中的拉丁字符一般就是这种非等宽设计。但是有三个理由让程序员们倾向于使用字字等宽的拉丁字体。
其一是早期终端机(或者电传打字机)的复古审美。那时候因为技术限制,作非等宽设计比较困难,所以采用了字字等宽的字体。
其二则是表里一致——虽然手写字体中拉丁字符宽窄不一,但是它们的 ASCII 编码长度是一致的,存储一个字符“i”并不会比“m”用更少的空间。用等宽字体的话,字符在视觉上占空间的比例和在内存中相对一致,更方面直观估计它们的内存空间占用。
其三,程序员不管用不用鼠标,总是得用键盘的。而键盘上的四向方向键在非等宽字体显示的文本上移动光标时会不太均一平滑。同样是按一下按键,跳过一个字符,可能移动很大一段距离,也可能是很小一点。而使用等宽字体则没有这个问题。
等宽字体英文叫 monospaced font (有点地方也用 monospace 或者 mono 简称)。它仅仅表示拉丁字母宽度相等,并不一定同时也做了易混淆字符的区别化设计。非代码领域有时也会用一些等宽字体,来做视觉效果表达。
代码连字
由于 ASCII 字符数量有限,很多编程语言使用一些符号组合来表示原本 ASCII 中没有的字符的功能。例如用 >= 来替换大于等于号“≥”,用 -> 来替代箭头“→”。但是这样的组合在视觉上看起来比较奇怪。一些字体和字体引擎会利用连字功能把这种符号组合显示为更恰当的形式,以美化代码方便阅读。
此外,当符号与不同类型的字符相连时(例如数字、大写字母、小写字母),往往应当呈现出不同的高度或在形状上有所微调,以使得代码整体更为整洁。这些需求也可以通过连字实现。
不过由于连字功能出现的时间较短,很多习惯了不连字字体的程序员和追求字符一个一个清晰分开的程序员并不喜欢用这种功能。所以这个功能虽然试图提升代码阅读体验,但并不算代码字体的必备功能。
字体推荐
Unifont
虽然用 Unifont 作为代码字体的人不一代很多,但值得一提的是 Unifont 本身就是一款基本合格的代码字体。首先它是一款等宽字体,全角和半角之间字宽严格符合 2:1。其二它对易混淆 ASCII 字符做了一些区别化设计(虽然不是很彻底, 1 和 l 的区分算不上特别明显)。这两点让它基本能胜任代码字体的工作。
除此而外,它还有一些别的字体不太容易复制的优点。其一是它的字符集覆盖非常广泛,如果你所查看的代码中涉及多种语言文字一般不需要再切换其他字体。其二,全角字符和长宽相等,对于追求方块字的强迫症来说是一个好事。其三,在保持相当大的字符覆盖范围的前提下,这款字体的体积并不是很大,大概只有十几到二十几 MiB。
不过这个字体也有一些需要注意的地方(不一代算缺点)。首先是它是一款点阵字体,可能有的人并不喜欢这个风格。其二,它的全角字符宽高是 1024 单位,半角字符宽是 512 单位,和一般字体以整百单位为基准不同,因此在字体混用时可能产生一些不整齐的情况(不过一般不太明显)。
最后简单小结一下这款字体:
辨识度:一般。
等宽:全角字宽和所有字符字高 1024,半角字宽 512(FontForge 中显示的数据)。
连字:不支持。
可变:无。
特色:收字特别多,自带汉字。像素风格。
Source Code Pro
地址: https://github.com/adobe-fonts/source-code-pro
Source Code Pro 是 Adobe 出品的一款代码字体,符合基本的代码字体要求,而且个人感觉这款字体线条非常顺畅,看起来很舒服。字符集不仅覆盖 ASCII 字符也包括一些其他欧洲文字和一些特殊符号,甚至包括 Powerline 字符支持,但是不包括汉字,所以纯 ASCII 环境或混有常见欧洲文字字符的环境可用。
小结:
辨识度:优秀。
等宽:仅半角字体,宽度 600。
连字:不支持。
可变:字重。
Noto Sans Mono
地址: https://fonts.google.com/noto/specimen/Noto+Sans+Mono
Noto Sans Mono 是 Google 出品的一款等宽字体,除了不覆盖汉字以外大体的字符覆盖还是比较全的。而且支持字重和字宽两个可变参数。
小结:
辨识度:一般。
等宽:仅半角字体,宽度 600。
连字:不支持。
可变:字重,字宽。
Cascadia Code
地址: https://github.com/microsoft/cascadia-code
Cascadia Code 是 Microsoft 出品的一款代码字体。支持很多新颖的字体特性,包括可变字体和常见代码组合符号的连写替换字形。还有内置的 Powerline 字符支持,可玩性很高。如果不喜欢连写功能,也有不带连写的版本可供选择。
小结:
辨识度:优秀。
等宽:仅半角字体,宽度 600。
连字:支持。
可变:字重。
Ubuntu Mono
地址: https://design.ubuntu.com/font
Ubuntu Mono 是 Ubuntu 设计的一套中规中矩的代码字体。它的特殊之处在于其字宽偏瘦,正好能和大多数汉字字体的字宽成整数比(一般汉字字宽 1000 个单位,且汉字天然等宽)。在多语言代码混排的时候替换汉字字体内置的非等宽 ASCII 字符,能让代码排版整体显得比较整齐。此外,一般的汉字字体中内置的 ASCII 字体不一定做了易混淆字符的区分设计,用 Ubuntu Mono 替换则可以补足这一点。
小结:
辨识度:优秀。
等宽:仅半角字体,宽度 500。
连字:不支持。
可变:无。
Fira Code
地址: https://github.com/tonsky/FiraCode
一款由 tonsky 制作的代码字体,支持代码连字和可变字体。虽然名声似乎不如 Source 或者 Noto 这些系列大,但是在程序员界似乎还挺受欢迎的。它支持连字且内置的各种特殊字符比较多,除了看代码还可以应付一般数学或终端使用。
小结:
辨识度:优秀。
等宽:仅半角字体,宽度 600。
连字:支持。
可变:字重。
Iosevka
地址: https://github.com/be5invis/Iosevka
Iosevka 是一款由 be5invis 制作的参数和选项特别多的字体合集。例如它的默认字宽是 500,但是也有加宽版,有黑体也有台阶化的衬线体(slab-serif),很多字符还有多种替代字形可选。此外它也支持连字和命令行字符。虽然不是可变字体,但是提供多种字重。算是一款比较全能的代码字体。如果想折腾的话还能根据指南自己调整构建。
Iosevka 提供三种字宽选项,
Mono和Term带连字,Fixed不带。Mono的 em dash “—” 长度是一个全角字符宽度,但Term和Fixed是半角宽度。根据定义来说, em dash 应该和字母M一样宽,所以当字体是等宽字体的时候它确实应该是半角字宽。但问题是 en dash 此时也是半角字宽,不太好区分了。 从 em dash 应当比 en dash 长这一点来说,似乎 em dash 采用全角宽度也有道理。就 font family name 而言,
Mono版字体就叫Iosevka,而Term字体叫Iosevka Term,Fixed字体叫Iosevka Fixed。可见作者默认使用Mono模式。没有特殊要求,仅仅看看代码用默认的Mono就可以,如果追求终端中统一且符合定义的效果,再换其他模式。归根到底 em dash 宽度在 Unicode 中是没有明确指定的(或者说 Unicode 指定其宽度为“模糊”)。在中文中当破折号时它应当和中文等宽,但是在英文中当 em dash 时应该和字母“M”等宽。
除了构建时的参数,这款字体还提供非常多的替换字体参数,或者叫风格化子集(stylistic sets)参数。可以通过在浏览器或者代码编辑器中填写参数来启用替换字形。这款字体包含了众多开源字体的字形,所以可以使用其中的替换字形选项来模拟很多开源字体。
替换字形选项用法参考:https://github.com/tonsky/FiraCode/wiki/How-to-enable-stylistic-sets
Iosevka 提供的替换字形参数详见: https://github.com/be5invis/Iosevka/blob/main/doc/stylistic-sets.md
小结:
辨识度:优秀。
等宽:仅半角字体,宽度 500(有其他宽度选项可选)。
连字:支持。
可变:无。
特色:提供非常多替换字形和多种组合的预构建包,如果满足不了需求还可以自行修改构建。
全角字符覆盖问题
之前说过,虽然 ASCII 的 95 个可打印字符是代码字体要覆盖的核心字符集,但这不是说代码中不可能出现非 ASCII 字符。当出现这种情况的时候增补对应的字符集就可以。由于这些非 ASCII 字符很少出现在代码中,大多是以注释或者数据的形式出现,所以对它们做区别化设计的需求不强烈,只要是能用的字体就行,如果风格还能和代码字体比较统一那就更好了。同理,一般也不需要它们和 ASCII 字符有什么代码连字。但是,等宽问题还是要特别注意的。这不仅仅是出于好看的目的,也是为了方便复杂编辑中的光标运动。如果字体宽度参差不齐,那么通过键盘移动光标可能出现偏移。
对于半角字符来说,这个问题很简单,只要找宽度相当的拓展字体加进去即可。很多编码字体甚至自带了不少非 ASCII 半角字符的支持。但对于汉字等全角字符来说,就比较麻烦了。按照习惯,全角字符的宽度应当是半角字符的两倍,但是不同的字体作者对于半角字符多宽的意见是不统一的。有的使用 500 单位,有的使用 600 单位,还有使用其他基准的。所以如果两套字体的半角字宽不统一,那么拼在一起就没法很整齐。由于一般的汉字字宽设计为 1000 单位,知则建议找字宽为 500 单位的代码字体和汉字字体组合使用(Unifont 比较特殊,在 FontForge 中它的半角字宽 512, 全角字宽 1024,按理说应该和其他汉字字体不能完全对齐,但是实际在 VS Code 中测试时我没看出来,可能 VS Code 在显示时做了一些缩放调整)。
虽然 Source Code Pro 之类字宽 600 单位的字体,和汉字字宽不是不是 1:2 关系,但是大体上仍能对齐(5 个拉丁字符和 3 个汉字等长),只不过上下移动光标时会感觉有时候不是在走直线。相对来说, 2:1 的比例关系则不太容易出现这种情况(不是绝对不出现,例如,两行的字符数一样但拉丁字符数一奇一偶,那么上下移动光标还是可能出现不走直线的情况)。如不太在意这一点,其实使用字宽 500 还是 600 的编程字体没有太大差别。而如果拉丁字宽不是特别圆整的数字,混排的时候就会很乱。
另外,在混用不同字体时,虽然行高一致,但由于不同字体实际的高度设计不太一样,来自不同字体的拉丁字符和汉字在视觉上还是可能高度不一致。这就需要逐个尝试体验了。
如果不想自己去尝试组合配对,也有很多由代码字体和汉字字体拼凑成的字体可供使用,不需要自己去组合尝试(如果有需要也可以试试自己拼凑一款字体)。
这里简单列举几个:
Source Han Sans HW SC VF 思源黑体自带的等宽模式(“HW” 意思是 half-width),半角字体等宽且宽度为 500。但是这个字体仅仅能保证等宽,对
0O没做特别明显的区别化设计。Sarasa Gothic 和 Iosevka 作者是同一个人。主要是基于 Iosevka 和思源黑体制作的支持汉字的一款编程字体。同样是一个大合集,包含多个字体家族。不同版本字体名称中的参数意义可以看项目页面说明。知则看代码一般用
Sarasa Term SC。比较遗憾的是这个字体中没有囊括 Iosevka 字体中众多的变体字形,不能通过在浏览器或编辑器中调整字体参数来启用替换字形。LXGW WenKai Mono 霞鹜文楷的等宽版, ASCII 区域的字符有防混淆设计,基本满足代码字体的需求。但其字体风格比较偏向手写楷书,笔画有出锋,不算特别典型的无衬线印刷体。看使用者是否喜欢这个风格了。其 font family name 是
LXGW WenKai Mono。
当然了,这些字体主要覆盖的是汉字,对于非拉丁也非汉字的字符它们也可能覆盖不到。当可以设置多个字体时可以考虑用 Unifont 做补全。
本文源码采用 MIT 协议开放,托管于: https://github.com/ZhiZe-ZG/ZZToolLibrary
如果觉得本文内容对您有用,希望您能在能力和意愿范围内给我一些资助。我不以此为生,但我也是个普通人。