前言
标点挤压示意(图片来自 east_asian_spacing 项目)
使用 fontconfig 调整 Linux 字体只能解决字体替换的问题,不能直接实现标点挤压等与单字相关的问题。目前,除了专业的排版软件和 Chrome 及 Chromium 内核的软件(在 M123 开始默认启用软件实现的标点挤压)外,没有什么软件默认支持它。(但是假如大部分软件高级文本排版的基础⸺HarfBuzz 的这个 Issue 被解决,每个软件就不必要自己适配了。)但我们也可以通过自己给字体加上 chws
相关的 OpenType 特性,并在 fontconfig 中给支持的字体开启,来在完好支持 fontconfig 的软件中开启标点挤压(然而 Qt 程序会忽略该设置,参见 QTBUG-78645)。
这篇文章没有深入讲解,所以很短(
前置知识
本文不会解释 fontconfig 的有关操作(可以看我的另一篇文章)。
若使用 Noto CJK
由于我使用的是 Noto CJK 字体,所以我给字体加上字体特性后打包成了两个新字体文件(Sans 和 Serif):noto-cjk-chws 和 noto-cjk-chws-patch。其中 noto-cjk-chws
用于直接替换原有的字体,但字体非常大;而 noto-cjk-chws-patch
只保留了其中更改的标点部分(因此在 fontconfig 中需要排在原字体之前),且可以通过在 patch 和原 Noto CJK 字体之间夹一个英文字体来更改英文字体并不影响引号、撇号与间隔号(“·”)的显示。如果使用 Arch Linux,可以分别通过 AUR 包 noto-fonts-cjk-chws
(直接替换 noto-fonts-cjk
包)与 noto-fonts-cjk-chws-patch
(需手动配置 fontconfig)直接安装。使用说明我的存储库里有,这里不再赘述。
使用其他字体
如果字体本身支持 chws
和 vchw
特性(极少),直接改 fontconfig 在字体 fontfeatures
属性中加入就好。
示例(~/.config/fontconfig/fonts.conf
,<fontconfig>
元素中):
<match target="font">
<test name="family">
<string>*你的字体名称*</string>
</test>
<edit binding="strong" name="fontfeatures">
<string>chws</string>
<string>vchw</string>
</edit>
</match>
如果字体不支持这些特性,需要利用 chws_tool(基于 east_asian_spacing)手动加入。如果是 Google Fonts 中的字体,安装后直接执行 add-chws
+字体路径就行,如果不是,则在 src/chws_tool/config.py
的 _get_factory_by_name
函数加入自己字体的 config
(其实一般不用加直接用 default
就行)。
另外如果需要子集化字体的话可以使用 fontTools
子集化的同时更改字体名称。我的 subsetter.py
的代码如下:
#!/usr/bin/env python3
from fontTools.ttLib import TTFont, TTCollection
from fontTools.subset import Subsetter
import sys
subsetter = Subsetter()
subsetter.options.name_IDs = "*" # 保留所有 nameID
# 只有保留所有 nameID(默认只保留 nameID 1~6)才能使 fontconfig 正确识别子集化后的字体,因为 Noto CJK 在 nameID=16/17(排版字族名/样式名)存储正确的字族与样式(如 Black、DemiLight、Light 等),nameID=1/2(基本的字族名/样式名)只能存储基本的 Regular、Bold 变体名。(见 https://learn.microsoft.com/en-us/typography/opentype/spec/name#name-ids。)
subsetter.options.name_languages = "*" # 保留所有语言
# 保留所有语言的记录(默认只保留英文),但实际上名称都是英文的,主要是想让 fontconfig 正确识别字体的语言⸺实际上 fontconfig 还是会识别成英文,但还是先留着比较好(
subsetter.populate(text="‘“〈《「『【〔〖〘〚〝([{⦅([·‧・;:’”〉》」』】〕〗〙〛〞〟)]}⦆、。,.!?)]—…")
tran = {
"Noto Sans CJK": "Noto Sans CJK CHWS Patch",
"Noto Sans Mono CJK": "Noto Sans Mono CJK CHWS Patch",
"Noto Serif CJK": "Noto Serif CJK CHWS Patch",
"Noto Sans": "Noto Sans CHWS Patch",
"Noto Sans Mono": "Noto Sans Mono CHWS Patch",
"Noto Serif": "Noto Serif CHWS Patch",
"NotoSansCJK": "NotoSansCJKChwsPatch",
"NotoSansMonoCJK": "NotoSansMonoCJKChwsPatch",
"NotoSerifCJK": "NotoSerifCJKChwsPatch",
"NotoSans": "NotoSansChwsPatch",
"NotoSansMono": "NotoSansMonoChwsPatch",
"NotoSerif": "NotoSerifChwsPatch"
}
def namer(arg):
if type(arg) == bytes:
return namer(arg.decode("utf-16-be")).encode("utf-16-be")
if type(arg) == str:
for before in tran:
if tran[before] in arg:
return arg
elif before in arg:
return arg.replace(before, tran[before])
return arg
def list_namer(li):
for i in range(len(li)):
li[i] = namer(li[i])
def dict_namer(di):
for k in di:
di[k] = namer(di[k])
def modify(font):
subsetter.subset(font)
for record in font['name'].names:
record.string = namer(record.string)
if "CFF " in font.keys():
cff = font["CFF "].cff
list_namer(cff.strings.strings)
list_namer(cff.fontNames)
for dic in cff:
dict_namer(dic.rawDict)
path = sys.argv[1]
if path.endswith("ttc"):
ttc = TTCollection(path)
for font in ttc:
modify(font)
ttc.save(namer(path))
else:
font = TTFont(path)
modify(font)
font.save(namer(path))
局限性
用这种方式可以完美地解决字与字之间的标点挤压,但其实行首和行尾的标点也应该实现标点挤压,但这些挤压(至少目前)不能够从字体层面解决,只能通过软件或者字体渲染库的适配(安卓端的微信实现了,但只是在发送和接收的消息中)。
与现行国标(GB/T 25834—2011)不符
行首、行尾的标点
见#局限性。
叠用的问号、叹号
5.1.2 问号、叹号均置于相应文字之后,占一个字位置,居左,不出现在一行之首。两个问号(或叹号)叠用时,占一个字位置;三个问号(或叹号)叠用时,占两个字位置;问号与叹号连用时,占一个字位置。
对于加粗部分(使用全角符号直接叠用),在添上 chws
特性后,均显示为 1.5 个汉字位置。[这不比国标的规定好看?但国标就是这么规定的,虽然一个新的国标(计划号 20240034-T-360)正在起草。]
间隔号(“·”)
5.1.7 间隔号标在需要隔开的项目之间,占半个字位置,上下居中,不出现在一行之首。
(这其实只与字体有关。)
根据 W3C 的中文排版需求,间隔号为 U+00B7 MIDDLE DOT
[·]。而日本使用来自日文 JIS 编码的 U+30FB KATAKANA MIDDLE DOT
[・],需占全宽,U+00B7 MIDDLE DOT
[·]在日文排版中用于英文或数字中,应与英文相适应。
这里国标要求半个汉字宽,台湾、香港要求占全宽。例如在 Noto Sans CJK SC 中,该间隔号占一个汉字位置(与 TC、HK 变体相同,KR、JP 变体约占半个汉字位置);Noto Serif CJK SC 中,该间隔号约占半个汉字位置(其他变体也如此,与台湾、香港的需求不符)。但一个字宽的间隔号可能更加适合中文(毕竟大部分中文字体都这么做的)。
另请参阅
版权声明
本作品的原始版本及截至 2024 年 8 月 1 日 0:00 UTC 之前的所有版本均遵循 CC BY 4.0 许可证。从 2024 年 8 月 1 日 0:00 UTC 开始的所有更新版本遵循 CC BY-SA 4.0 许可证。文章开头仅标注创作开始日期。