字体子集化优化在线动态渲染PDF

By heiry on 2026-02-01 [ in 生活 ]

最近做了一个在线生成PDF,适时预览并可在线打印的系统。使用了pdf-lib.js(https://pdf-lib.js.org),作为前端创建PDF的框架,虽然很方便,但遇到一个棘手的问题:如何解决中文显示。

PDF文件不同于web文件(HTML+CSS),它的字体是嵌入文件的,而由于安全性和PDF文件本身机制的问题,并不能读取本地字体文件。而国际PDF阅读器规范中的 “Standard 14 Fonts”(14 种标准字体)中并没有中文字体,这就导致了在不嵌入中文字体的情况下,出现中文就会无法渲染。

Standard 14 Fonts

内置的 14 种标准字体 这 14 种字体全部属于 拉丁字母(英文) 系列,分为四大类:
1.无衬线 (Sans Serif)字体: Helvetica, Helvetica-Bold, Helvetica-Oblique, Helvetica-BoldOblique
2.衬线 (Serif/Roman) 字体:Times-Roman, Times-Bold, Times-Italic, Times-BoldItalic
3.等宽 (Monospace)字体: Courier, Courier-Bold, Courier-Oblique, Courier-BoldOblique
4.符号 (Symbol) :Symbol, ZapfDingbats

PDF 的设计初衷是 “所见即所得” (WYSIWYG)。它的目标是:不管你把这个文件发给谁,在手机上、打印机上还是 20 年后的电脑上,看到的排版必须一模一样。

封闭性:PDF 不信任阅读者的系统。它认为:“万一对方电脑里没有微软雅黑,我的排版不就乱了吗?”

机制:为了保证绝对一致,pdf-lib 必须执行 “字体嵌入 (Embedding)”。它需要把字体文件的每一个二进制数据“缝进” PDF 文件里。

结果pdf-lib 作为一个运行在浏览器沙箱里的 JavaScript 库,它没有权限直接扫描并读取你电脑硬盘上的 .ttf 文件。它必须通过网络请求(字体链接)拿到数据,然后再加工进 PDF。

面临的难点

这个系统需要兼容正体中文(繁体)和简体中文,而这种字体体量大得惊人。兼容性最好的Adobe全字符体——Super OTC 体积高达100M+,师出同门的Google Noto也差不多这个体量,如放到Web项目,简直是灾难,浏览器瞬间会被卡死。

于是只能放弃使用这种大字符集的字体,选择性地支持正体或简体。

但是单独的简体字体,体积也很大,思源Source Han Serif,单独一种字重的字体,也高达20M+,Noto也要10M-20M。如果要加载这么大的字体,配置一般的电脑尤其是内存小的电脑,会频繁出现假死的情况。

于是,只能想办法减少字体的体积。

通过万能的Gemini,终于有了解决办法:字体子集化。

说白了,就是从原字体中提取一部分字符,只要能覆盖自己常用的字符就行了。

方案:

1. 用集成工具做子集化。这是最常用的方式,工具例如,Fontmin(https://ecomfe.github.io/fontmin/)。

这种方式就是要准备“字体白名单”,也就是你要把那些你需要的字符一一列出来,让它把你需要的字符从庞大字库中“抠”出来。

可以到GitHub上下载常用中文字符3500字等整理好的字符集,导入Fontmin中。也可以自己整理用到的文字再加进去。

2.用 Python 做字体子集化。

#安装fonttools库
pip install fonttools brotli

#命令执行子集化
pyftsubset original.ttf --text-file=chars.txt --output-file=sub.woff2 --flavor=woff2
'''
其中
original.ttf:原始字体,如思源宋体
--text-file:字符白名单文件
--output-file输出字体
'''

#不指定字符白名单,只按照某个标准来子集化(如变成GB2312标准字符集)
pyftsubset NotoSerifSC-Medium.ttf --unicodes="U+0020-007E,U+00A0-00FF,U+2000-206F,U+3000-303F,U+4E00-9FA5" --output-file=label_gb2312.ttf --layout-features='*'

PDF相关开发小知识:

Chrome的PDF渲染核心核心是PDFium(Google 和福昕合作开发的C / C++开源项目),Firefox使用的则是自家的PDF.js(纯 JavaScript 实现的 PDF 渲染器),开发环境下它们有一些使用上的差异:

在 PDF 官方规范中,FitH 是针对“宽度自适应”的,但在 Firefox 的 pdf.js 源代码里,他们对参数的映射是这样的:

view=FitH: 对应的内部指令是 FitH(垂直滚动模式下对齐宽度)。

zoom=page-fit: 对应的内部指令是 page-fit(强制将整个页面缩放到当前容器可视窗口内)。

Firefox中可以通过“about:config”——“pdfjs.defaultZoomValue”,将值设置为page-fit/page-width,可实现自适应窗口展示,不过这种方式只适用于用浏览器直接打开pdf文件的情况下适用(这种情况是用 Firefox 原生 PDF.js 打开的)。

 >>



© 2009-2026 MOSANG.NET DESIGNED BY HEIRY