/浸月之海/ 列表

一个标点符号引发的血案

#技术

目录

“中国有句俗语:‘宫廷玉液酒——一百八一杯’……”

“你的引号没有占一整格,破折号中间是断的,省略号还不在一行的中间!你是不是美国派来的间谍?!”

我为什么要在自己的博客里玩标题党……

咳咳,网页地址可能已经暴露了我。这篇文章的真实目的是介绍我刚刚做的小工具——Google Fonts+。但我为什么要做这样一个工具呢?这又得回到上面的小剧场。

起因

众所周知,中英文混排时,通常会用一种字体显示中文,而用另一种字体显示西文。这是因为西文字体的选择范围比中文大,可以选择更合适的字体代替中文字体里的西文字形。然而,这种设计策略在网页上有一个小问题。

网页中,字体的选择是由 CSS 样式表决定的。开发者可以设置一系列字体,当要显示某个字符时,就依次尝试用列表里的每个字体显示这个字符,直到找到第一个能显示这个字符的字体。

由于中文字体一般包含西文字符,而西文字体没有中文字符,所以通常把西文字体放在中文字体的前面。这时问题就来了:假如这个西文字体还包含一些别的字符,比如中文的标点?1这时就会出现前面小剧场里的情况,中文的标点用西文字体显示,虽然不能说错,但是很丑。

解决办法

手动设置西文的字体

也就是说,页面默认为中文字体,只在用到西文的地方专门设置成西文字体。

这当然可行,但是太麻烦了……我的文章是用 Markdown 写的,如果要把文章里的西文都单独设置成西文字体,既麻烦,又会让文档的可读性很差。

可以自动化吗?

自动查找正文中插入的西文字符,并添加使用西文字体的标记并不难,但这就需要添加一道过滤的步骤。如果在浏览器端实现,就会拖慢网页的性能;如果在服务端实现,用我现在使用的 Hugo 生成器的模板语言写程序逻辑实在费劲。总之,我最终没有选择这种方法。

修改 unicode-range

幸好,现代 CSS 为我们提供了 @font-faceunicode-range。可以自定义一个 @font-face,利用 unicode-range 限制西文字体的使用范围,把中文标点排除在外。

这种处理在使用本地字体或者自建服务的 Web 字体时当然可行,但不幸的是我用的是 Google Fonts……Google Fonts 提供字体的方式就是直接提供一个包含了 @font-face 定义的 CSS 文件,根本没法再做修改。而且,Google Fonts 提供的 CSS 是动态生成的,指不定什么时候里面的字体链接就过期了,不能下载下来自己修改。我还希望字体有更新时我网站上的字体能自动更新呢。2

去年年末,我向 Google Fonts 提出请求,希望 Google Fonts 能提供一个自定义 unicode-range 的选项。然而半年多过去了,没有任何回应。

我最终决定自己来实现这个功能。于是,就有了我们今天要介绍的小工具——

Google Fonts+

这个小工具实际上是 Google Fonts 的一个代理,实现为 Vercel 的 Serverless Function,目前还不用花我一分钱。

程序上,这个工具很简单,就是代替用户向 Google Fonts 获取原版 CSS,然后按用户的需求过滤里面的 unicode-range,再返回给用户,一共只用了一百多行代码。加了一层代理,性能肯定不如 Google Fonts,但我启用了缓存,这样除了每天第一次加载比较慢,之后就没区别了。

使用方法

你要问使用方法?首先,这个工具可以当作 Google Fonts 的透明代理。只要把 Google Fonts 的 CSS 地址中的 https://fonts.googleapis.com/css2 换成 https://fonts.paro.one/api 即可。

我加入了什么额外功能呢?我们以这个地址为例(插入了换行以便讲解):

1https://fonts.paro.one/api
2?family=Bitter:ital,wght@0,400;0,700;1,400;1,700
3&family=Noto+Serif+SC:wght@400;700
4&family=Noto+Sans+Symbols
5&display=swap

这个地址会给你三个字体:一个是西文字体 Bitter,一个是中文字体 Noto Serif SC,还有一个是符号字体 Noto Sans Symbols,分别定义在第 2、3、4 行。

现在我需要在 Bitter 里排除掉中文常用的标点,就可以在第二行末尾加上:

1:exclude@U%2BB7,U%2B2002,U%2B2013-2014,U%2B2018-2019,U%2B201C-201D,U%2B2026

也就是 :exclude@,加上要排除掉的 Unicode 范围(要通过 URL Encode)。

如果还要保留没做修改的字体,可以再加上:

1:rename@Bitter+CJK

这样,修改后的字体就会被重命名为 Bitter CJK,而原字体保持不变。

最后,你还可以用 :include@ 加上 Unicode 范围来直接无视原来的 Unicode 范围,重新指定新的范围。

注意事项

另外,有一点要注意:如果原来的字体没有指定属性轴,比如前面的 Noto Sans Symbols,没有类似于 :wght@400;700 的部分,那么在添加额外参数之前要再加一个冒号 :,例如:

1?family=Noto+Sans+Symbols::exclude@U%2BB7,U%2B2013-2014

目前只添加了这三个功能。源代码和更严谨的说明都在 Github 上。那么,这篇文章就到此为止啦。


  1. 其实中文和西文的标点有不少字符是共用的……所以严格来说这些标点也不只是中文的标点。 ↩︎

  2. 虽然在生产环境中使用自动更新的第三方资源不是 best practice,但我喜欢保持最新,特别是在我自己的博客上。 ↩︎