一开始使用Linux系统的时候,并没有多少自由开源的中文字体。那时候几乎所有人的选择都是文泉驿正黑。我就一直用啊用了好多年,直到后来截图时被网友说该换字体了,我才知道原来文泉驿项目已经停止很久了,网站上的新闻截止于2008年——都快20年啦。
文泉驿正黑的字形比较「旧时代」——以屏幕清晰度为优先,由于当时的屏幕普遍dpi低,笔画迁就像素风格,所以比较丑。另外也有些bug,比如「撨䑾詺㘃㞈㟯㫥」这几个字会有一片漆黑的区域。
文泉驿正黑看起来是这样的。注意截图中只有除标题外的中文部分用的是文泉驿正黑字体。
2014年,Google联合Adobe发布了思源黑体和Noto Sans CJK字体——这两款字体的汉字部分是相同的,区别只在于思源黑体会根据文本的区域设置来自动选择字形(通常不管使用哪个语言的字族名来指定),而Noto Sans CJK字族名只有英文版本,并且不同的字族名后缀会选择不同的地区字形。
我在2024年终于决定切换到思源黑体试试。它长这样:
图中的日文部分也是使用的思源黑体,只不过是日文字形。标题则使用的是思源宋体。读者可以在图片上点「右键」然后新建标签页打开图像,然后来回切换着对比。
可以看出,思源黑体是比文泉驿正黑好看多啦。而且思源字体有粗体版本,文泉驿正黑是没有粗体的,只有合成出来的所谓「伪粗体」。
但是,你有没有发现有什么地方不对劲?注意看大标题下方那行字,「条目」和「阅读」下边的装饰线与该行下方的分隔线有一段距离,而「大陆简体」和「工具」两处字偏低。事实上,思源黑体的问题远不止这些。思源黑体文字上方空出来的空间比下方多不少,造成行高太高、终端里文字不居中等各种问题。
(这个图片是使用命令pango-view --dpi=1024 --font=思源黑体 --annotate=glyph,layout,baselines -t A测试中文Test -o out.png生成的。)
因此我用了几天就换回文泉驿正黑了,但是把思源黑体作为部分网页字体在用(主要用于非简体中文内容,以及通过stylus指定的特定几个网站)。
最近,oldherl说更纱黑体修了行高的问题。于是我又试了几天这个基于思源黑体的字体。它的效果是这样的:
行高的问题确实解决了!但是——又要「但是」了——它的字怎么矮胖矮胖的?是我没看习惯的原因吗?于是我使用了几天,最后并没有习惯,反而是和思源黑体一对照,发现确实是许多字都变矮了一点。
于是我只好又换回已经用习惯了的文泉驿正黑。
诶等等!既然更纱黑体能修行高,我为什么不行?因为我不会,可是,今非昔比了呀——Gemini,给我来个脚本!
于是就有了这么个脚本——当然这个脚本是我改过的最终版本了。
#!/usr/bin/python3
from io import BytesIO
from fontTools.ttLib import TTCollection, TTFont
def adjust_font(font):
target_ascent = 1025
target_descent = -265
# 修改 hhea 表 (macOS/Pango 渲染常用)
font['hhea'].ascent = target_ascent
font['hhea'].descent = target_descent
font['hhea'].lineGap = 92
# 修改 OS/2 表 (Windows/Linux 合规性)
font['OS/2'].sTypoAscender = target_ascent
font['OS/2'].sTypoDescender = target_descent
font['OS/2'].sTypoLineGap = 92
# usWin 参数决定了红线(剪切区域),设为相同值可消除额外间距
font['OS/2'].usWinAscent = target_ascent
font['OS/2'].usWinDescent = abs(target_descent)
# 2. 修改 Font Family 名称
name_table = font['name']
for record in name_table.names:
name_str = record.toUnicode()
new_record_str = name_str.replace('Source Han', 'Lily Han') \
.replace('思源', '百合') \
.replace('源ノ角ゴシック', '百合ノ角ゴシック') \
.replace('본고딕', '백합고딕') \
.replace('SourceHan', 'LilyHan')
# 针对不同 ID 进行替换
# ID 1: Family Name, ID 4: Full Name, ID 6: PostScript Name 等
if name_str != new_record_str:
name_table.setName(new_record_str, record.nameID, record.platformID, record.platEncID, record.langID)
# Medium as Semibold
if font['OS/2'].usWeightClass == 500:
# save then read to copy the font without referencing existing data structures
buf = BytesIO()
font.save(buf)
buf.seek(0)
sb_font = TTFont(buf)
sb_font['OS/2'].usWeightClass = 600
for record in sb_font['name'].names:
name_str = record.toUnicode()
if "Medium" in name_str:
new_name = name_str.replace("Medium", "Semibold")
record.string = new_name.encode(record.getEncoding())
cff = sb_font['CFF '].cff
for i in range(len(cff.fontNames)):
if "Medium" in cff.fontNames[i]:
cff.fontNames[i] = cff.fontNames[i].replace("Medium", "Semibold")
# 修改 TopDict 内部的名称
for topDict in cff.topDictIndex:
for attr in ['FullName', 'FamilyName', 'Weight']:
if val := getattr(topDict, attr):
if isinstance(val, str) and "Medium" in val:
setattr(topDict, attr, val.replace("Medium", "Semibold"))
return sb_font
def main(input_ttc, output_ttc):
ttc = TTCollection(input_ttc)
new_fonts = list(ttc.fonts)
for font in ttc.fonts:
newfont = adjust_font(font)
if newfont:
new_fonts.append(newfont)
ttc.fonts = new_fonts
ttc.save(output_ttc)
print(f"成功保存至: {output_ttc}")
if __name__ == '__main__':
import nicelogger
nicelogger.enable_pretty_logging('DEBUG')
input_ttc = "SourceHanSans.ttc"
output_ttc = "LilyHanSans.otc"
main(input_ttc, output_ttc)
脚本使用fonttools这个Python库,把ascent、descent和lineGap这三个参数改成和文泉驿正黑一样的了。之所以要照着文泉驿正黑来改,是因为我的终端最大化之后,使用13pt字号差不多刚好填满35行(只空出来两行像素)。而使用别的参数,我调整字号好久,都会空出来小半到大半甚至接近一行文字的高度。
另外,这个脚本运行起来非常耗资源:它保存的时候会重新计算每一个字形,持续占用一个CPU核心长达六分多钟,内存只分配不释放,最终用掉接近20GiB。倒是生成的文件差异不大,用rsync很快就能同步回来。
最终效果图:
可惜的是思源黑体只有粗体和Medium,不像更纱黑体那样有半粗(Semibold)版本。思源黑体的粗体挺粗的。
由于我在使用思源黑体时遇到的问题都是在UI部分,而这里又不需要用到宋体,所以我没有去改思源宋体,其它地区的字形也还是用的原本的思源系列。
如果有人想试试我生成的这个字体的话,在这里下载(112 MiB)。
2026年05月13日更新:更新了脚本和字体文件。现在把Medium复制一份然后改叫Semibold了,因此有了半粗字重,在用到的地方会更接近设计者的意图,也看着更舒服。



















