最近做了一个在线生成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 打开的)。
>>