Apache FOP 是基于 Java 的 PDF 生成引擎,需先将 XML 用 XSLT 转为合规 XSL-FO(含 fo:root、正确命名空间等),再交由 FOP 渲染;不支持直接 XML+XSLT 一步生成,且对 XSL-FO 1.0/1.1 子集支持较好,不支持 2.0 新特性。

Apache FOP 是一个开源的、基于 Java 的 PDF 生成引擎,它把符合 XSL-FO 规范的 XML 文档(即 .fo 文件)渲染成 PDF、PostScript、AFP 等格式。它不直接处理任意 XML —— 你必须先用 XSLT 把原始 XML 转成 XSL-FO,再交给 FOP 渲染。
为什么不能直接用 XML + XSLT 生成 PDF?
FOP 本身不执行 XSLT;它只消费已转换好的 XSL-FO。常见错误是以为 fop -xml data.xml -xsl style.xsl -pdf out.pdf 能一步到位,但实际中:如果 style.xsl 输出不是严格合规的 XSL-FO(比如漏了 fo:root、用了非标准命名空间、或没声明 xmlns:fo="http://www.w3.org/1999/XSL/Format"),FOP 会报类似 org.apache.fop.fo.ValidationException: Missing root element 'fo:root' 的错误。
- 确保 XSLT 输出以
开头 - 所有 FO 元素必须带
fo:前缀,且在正确命名空间下 - FOP 对 XSL-FO 1.0 和 1.1 子集支持较好,但不支持 XSL-FO 2.0 新特性(如
fo:table-column的column-width自适应算法)
命令行快速生成 PDF 的典型流程
分两步最可控:先 XSLT 转 FO,再用 FOP 渲染。推荐用 saxon 或 xsltproc 做第一步,避免 FOP 内置 XSLT 引擎(默认使用 Apache Xalan)的兼容性问题。
- 用 Saxon-HE(免费)转 FO:
saxon -s:data.xml -xsl:transform.xsl -o:output.fo - 再用 FOP 渲染:
fop -fo output.fo -pdf result.pdf - 若要跳过中间文件,可用管道(Linux/macOS):
saxon -s:data.xml -xsl:transform.xsl | fop -fo - -pdf result.pdf - FOP 默认查找字体依赖系统或配置的
userconfig.xml;中文乱码 通常是因为没配好font metrics或未启用embed-fonts
Java 代码中集成 FOP 的关键点
直接调用 FOP API 比命令行更灵活,但也更容易踩内存和线程坑。核心是 FopFactory 和 Fop 实例的复用策略。
-
FopFactory是重量级对象,应全局单例或池化,不要每次渲染都新建 -
Fop实例是轻量级、线程不安全的,每次转换需新建 - 务必关闭
OutputStream和Fop关联的流,否则 PDF 可能截断或损坏 - 示例片段(注意异常处理与资源释放):
FopFactory fopFactory = FopFactory.newInstance(new File("fop.xconf")); FOUserAgent userAgent = fopFactory.newFOUserAgent(); OutputStream out = new FileOutputStream("out.pdf"); try (Fop fop = fopFactory.newFop(MimeConstants.MIME_PDF, userAgent, out)) {TransformerFactory factory = TransformerFactory.newInstance(); Transformer transformer = factory.newTransformer(new StreamSource("transform.xsl")); Source src = new StreamSource("data.xml"); Result res = new SAXResult(fop.getDefaultHandler()); transformer.transform(src, res); }
真正难的不是语法,而是 XSL-FO 的盒模型细节:页边距继承规则、fo:block-container 的 绝对定位 限制、表格跨页断裂行为、以及中文字体嵌入时的 metrics-url 路径解析失败——这些不会报编译错,但会让 PDF 在某一页突然空白或文字堆叠。调试时优先用 fop -fo input.fo -pdf - 输出到 stdout 查看是否生成有效 PDF 流,再逐段注释 FO 片段定位问题。