在使用 Flying Saucer(ITextRenderer) 将 HTML 模板转换为 PDF 的过程中,遇到了一个非常典型、也非常折磨人的问题:


🔍 一、问题现象

  • 使用 HTML + CSS + Freemarker 模板生成 PDF:
  • HTML 在浏览器中显示完全正常
  • 背景图、图片、二维码都正常
  • 但生成的 PDF 中:

    • 中文不显示
    • 或显示为方块
    • 或全部回退成默认字体

CSS 看起来也没问题:

.title {
    font-family: 仿宋;
}

.content {
    font-family: 黑体;
}

Java 里也注册了字体:

renderer.getFontResolver().addFont("simfang.ttf", BaseFont.IDENTITY_H, BaseFont.EMBEDDED);

但就是不生效。


🧠 二、第一次误区:以为「字体文件名 = 字体名」

很多人(包括我自己)一开始都会认为:

simfang.ttf  →  font-family: simfang

或者:

font-family: 仿宋;
font-family: 仿宋_GB2312;

这是完全错误的。
在 Flying Saucer / iText 中:


🧪 三、关键转折:如何发现字体真正的名称?

1️⃣ 打印字体信息
iText 在加载字体时,其实已经解析了字体内部信息。
在调试过程中,可以发现类似输出(或通过代码打印),就在你 addFont 之后,加上👇👇👇:

BaseFont bf1 = BaseFont.createFont(
        basePath + "simfang.ttf",
        BaseFont.IDENTITY_H,
        BaseFont.EMBEDDED
);

System.out.println("FontName      = " + bf1.getPostscriptFontName());
System.out.println("FamilyNames   = " + Arrays.toString(bf1.getFamilyFontName()));
System.out.println("FullNames     = " + Arrays.toString(bf1.getFullFontName()));
FontName    = FangSong
FamilyNames = [...]
FullNames   = [...]

这一步非常关键:


而不是你看到的中文名,也不是文件名。


2️⃣ 常见中文字体的真实名称
通过实际加载与验证,总结出几个最常见的映射关系:
字体文件 CSS 中应使用

字体文件CSS 中应使用
simfang.ttfFangSong
simkai.ttfKaiTi
simhei.ttfSimHei

⚠️ 注意大小写,建议保持一致。


3️⃣ 验证方式(非常重要)

把 CSS 改成:

.title {
    font-family: FangSong;
}

.award {
    font-family: KaiTi;
}

.content {
    font-family: SimHei;
}

然后重新生成 PDF,如果字体生效,说明:

  • 字体文件加载成功
  • FontName 使用正确
  • 编码和嵌入方式没问题

🛠️ 四、Java 端的正确字体注册方式

字体注册必须满足三个条件:

renderer.getFontResolver().addFont(
    "simfang.ttf",
    BaseFont.IDENTITY_H,
    BaseFont.EMBEDDED
);

为什么必须这样?

  • IDENTITY_H:支持 Unicode(中文)
  • EMBEDDED:字体必须嵌入 PDF,否则中文丢失
  • Flying Saucer 不支持系统字体 fallback

任何一个条件不满足,中文都可能直接消失。


⚠️ 五、CSS 层面必须遵守的规则

❌ 不要使用的方式

@font-face { ... }   /* 不支持 */
font-family: 仿宋;   /* 无效 */
font-family: 仿宋_GB2312; /* 无效 */

✅ 正确写法

font-family: FangSong;
font-family: KaiTi;
font-family: SimHei;

只写 PostScript 名称。


🔔 六、顺带踩到的一个布局大坑

在调整证书版式时,还发现 Flying Saucer 对 CSS 的支持非常有限:

❌ 不支持 / 不稳定的属性

  • flex / justify-content
  • margin-right: -xxx
  • transform: translateX()

✅ 稳定可用的替代方案

  • 整体位移:padding-left 或 margin-left
  • 精准定位:position: relative + absolute
  • 行内偏移:padding-left

例如:

.footer {
    padding-left: 200px;
}

比负 margin 稳定得多。


🙆 七、最终可复用的经验总结

  1. 只要记住下面这 7 点,Flying Saucer 的中文字体基本不会再踩坑:
  2. 字体必须是 .ttf
  3. 字体必须手动注册
  4. 使用 BaseFont.IDENTITY_H
  5. 使用 BaseFont.EMBEDDED
  6. 不要依赖系统字体
  7. CSS 中只写 PostScript FontName
  8. 不要相信浏览器里的显示结果
最后修改:2025 年 12 月 16 日
如果觉得我的文章对你有用,请点赞