PDFBox 在 Linux 报 “No glyph for U+535A (博)” —— 一次子集化踩坑与完整排查清单
关键词:PDFBox、PDType0Font、子集嵌入(subset embedding)、SimHei、思源黑体、字体回退
1. 背景
-
业务场景
后端使用 Apache PDFBox 填充含 AcroForm 的中文 PDF。
项目把SimHei.ttf
(9.6 MB,官方完整字体)打进resources/fonts
并在运行时嵌入:PDType0Font chineseFont = PDType0Font.load(document,getClass().getResourceAsStream("/fonts/SimHei.ttf") );
-
运行环境
- ✔︎ Windows 11(开发机,一切正常)
- ✖︎ CentOS 7 / JDK 8(生产容器,抛异常)
2. 问题症状
IllegalArgumentException: No glyph for U+535A (博) in font SimHei
- 仅在 Linux 抛出;Windows 本地生成和预览都正常。
- 只对个别汉字(如“博”)报错,其余内容正常。
3. 根因解析:子集化 + 多次写入
链条 | 说明 |
---|---|
PDFBox 默认子集化 | PDType0Font.load 默认 embedSubset = true 。第一次写入字段时只把“当下需要”的 glyph 打进 PDF。 |
后续写入新字符 | 当再次向同一个字体对象写入未在子集里的字符时(如“博”),映射表里找不到对应 glyph ⇒ 报 No glyph… 。 |
Windows“看不出来” | Acrobat/Edge 等查看器在 显示阶段 做字体回退,用系统字体补缺字形;生成阶段的异常被掩盖。Linux 服务端无此回退链,直接崩溃。 |
⚠ 注意:
- 字体文件本身是完整的 9.6 MB SimHei;问题与“精简字体缺字”无关。
- 但“检查字体是否精简”仍是常见排查思路,见 § 4 - Step 1。
4. 完整排查清单
步骤 | 目的 | 指令/思路 |
---|---|---|
Step 1 — 检查字体文件 | 确认不是“瘦身版” | ls -lh simhei.ttf ;若 < 5 MB 高度可疑。 |
Step 2 — 复现路径 | 确认是否因 同一字体对象 被多次写入 | 在本地单元测试里: ① 初始化 PDType0Font ② 先写 “ABC” ③ 再写 “博”——若此时抛错,即为子集化问题。 |
Step 3 — 验证 embedSubset | 定位问题点 | PDType0Font.load(document, is, false) 全量嵌入;若问题消失 ⇒ 子集化导致。 |
Step 4 — 路径大小写/权限 | 排除 Linux 特有问题 | Linux 区分大小写;确保资源路径正确且容器内可读。 |
Step 5 — 备用字体 | 彻底规避字库差异 | 尝试思源黑体 / Noto Sans CJK SC;若仍报错则回到子集化逻辑排查。 |
5. 两种解决路线
5.1 简单粗暴:全量嵌入字体
PDType0Font font = PDType0Font.load(document, fontStream, /*embedSubset*/ false);
- 一次性把完整字体写进 PDF → 后续再写多少字符都不会缺 glyph。
- 缺点:文件体积增大(示例 PDF 从 35 KB ➜ 11 MB)。
5.2 继续使用子集化,但保证完整
策略 | 思路 |
---|---|
初始化时预写“字库字符串” | 先把所有可能出现的汉字拼成一大字符串写入隐藏字段,让 PDFBox 第一次就收集到完整 glyph 集。 |
每次写前重新加载字体 | 不复用同一 PDType0Font ;每次 load 时只有一次写入,确保子集自洽。 |
一次写完再保存 | 避免“写-保存-再写”分阶段生成。 |
6. 代码示例:全量嵌入写字段
try (InputStream is = getClass().getResourceAsStream("/fonts/SimHei.ttf")) {PDType0Font font = PDType0Font.load(document, is, false); // 全量嵌入PDAcroForm form = document.getDocumentCatalog().getAcroForm();PDTextField field = (PDTextField) form.getField("studentName");COSName fname = form.getDefaultResources().add(font);field.setDefaultAppearance("/" + fname.getName() + " 10 Tf 0 g");field.setValue("博学者");
}
7. 结果验证
- Linux 部署重新跑流程,异常消失,PDF 正常生成。
- Windows 打开无字体回退提示。
- 压测千级并发,未再出现
No glyph
类错误。
8. 总结
No glyph for U+XXXX
往往是“子集化 + 后续新增字符”导致,与字体文件大小不一定相关。- 排查顺序:先确认字体完整 → 再检查子集化写入流程。
- 最稳妥方案:在后端生成阶段关闭子集化 (
embedSubset=false
),或保证一次性写入全部需求字符。
一句话:如果你在 PDFBox 里动态写中文,还想持续子集化省体积,就必须让 PDFBox 一次性“看到所有汉字”;否则就全量嵌入,图省心最稳。