Officia.Words — Word 转 PDF 与模板填充
能力摘要(AI 友好):
OfficiaWords把 Word/DOCX 转 PDF(toPdf),并支持 docx 模板占位符填充与邮件合并(fillTemplate*)。门面静态方法,byte[]进byte[]出,纯 JDK 实现。
将 Word 文档(DOCX,及部分 DOC)转换为 PDF,或以 docx 为模板做数据填充与邮件合并。中文/CJK 通过 CID 子集嵌入正确显示且可复制。
引入
<dependency>
<groupId>plus.ruoyi</groupId>
<artifactId>officia-all</artifactId>
<version>1.0.0</version>
</dependency>这是唯一需要引的依赖:Maven Central 上只发布
officia-all这一个构件, 它是含全部能力模块与授权客户端的单一 jar,且无任何第三方传递依赖。
Word → PDF
最小示例:
import plus.ruoyi.officia.words.OfficiaWords;
byte[] pdf = OfficiaWords.toPdf(docxBytes);来源重载 —— 字节 / 输入流 / 文件都能直接调:
OfficiaWords.toPdf(byte[] docx);
OfficiaWords.toPdf(InputStream in);
OfficiaWords.toPdf(File file);需要页数等元信息时用 convert 拿富结果:
ConvertResult r = OfficiaWords.convert(docxBytes, ConvertOptions.defaults());
byte[] pdf = r.getData();
int pages = r.getPageCount();转换选项
ConvertOptions 链式配置,传给任意带 options 的重载:
import plus.ruoyi.officia.engine.api.ConvertOptions;
import plus.ruoyi.officia.engine.api.enums.PageSize;
ConvertOptions opts = ConvertOptions.defaults()
.setDefaultPageSize(PageSize.A4)
.setFontDirectory("/usr/share/fonts")
.setEmbedFonts(true)
.setMaxImageDpi(150)
.setTimeoutMillis(30_000)
.setReportFontSubstitutions(false);
byte[] pdf = OfficiaWords.toPdf(docxBytes, opts);| 选项 | 默认 | 说明 |
|---|---|---|
defaultPageSize | PageSize.A4 | 仅作默认;文档自身声明了纸张时以文档为准 |
fontDirectory | null | 追加字体扫描目录,中文场景建议指定 |
embedFonts | true | 嵌入字体子集,保证换机器显示一致 |
maxImageDpi | 150 | 位图按其在页面上占的面积降采样;设 0 保留源图原始像素 |
timeoutMillis | 0(不限) | 超时保护,处理不可信来源文档时建议设 |
reportFontSubstitutions | false | 记录字体替换/回退,供排查跨平台版式差异,见下 |
字体替换诊断
同一份文档在本机正常、部署到 Linux 后版式变了时,用它查清哪个字体被换掉、换成了什么:
ConvertResult r = OfficiaWords.convert(docxBytes,
ConvertOptions.defaults().setReportFontSubstitutions(true));
for (FontSubstitution s : r.getFontSubstitutions()) {
System.out.println(s.describe());
// 字体「宋体」字体不存在,实际使用「noto sans sc」
}FontSubstitution 提供 requestedFamily(文档要什么)、bold / italic(哪个字面)、 resolvedFamily(实际用了什么)与 reason。reason 分两种,处置方式不同:
reason | 含义 | 处置 |
|---|---|---|
FAMILY_NOT_FOUND | 整个字体在运行环境中不存在 | 安装字体或配置 fontDirectory |
GLYPH_NOT_IN_FAMILY | 字体存在,但不含该字形(如拉丁字体遇到汉字) | 正常的字形级回退,一般无需处理 |
记录按「族名 + 字重 + 倾斜」去重,数万字文档也只有数条。默认关闭(收集有开销), 排查时再开启;开启不会改变转换结果。
关于 maxImageDpi
文档里的图片像素数往往远超它在页面上占的面积,多出来的部分对观感没有贡献,只增加体积。 默认 150 dpi 在屏幕阅读与一般打印下看不出差别,却能显著压缩产物。只有高精度印刷或产物 需要二次编辑时才设 0。JPEG 源图始终按原字节直通嵌入,不受该选项影响。
Word → 图片(一页一张)
用于文档在线预览:图片在任何浏览器与移动端 webview 里都能直接 <img> 显示, 不依赖 PDF 阅读器。返回顺序即页码顺序。
List<byte[]> pages = OfficiaWords.toImages(docx); // 默认 96 DPI PNG
// 指定 DPI 与格式
List<byte[]> jpgs = OfficiaWords.toImages(docx, null,
ImageRenderOptions.defaults().dpi(150).format("jpg"));ImageRenderOptions(plus.ruoyi.officia.render.image):
| 方法 | 默认 | 说明 |
|---|---|---|
dpi(int) | 96 | 取值 36–600,越界抛异常而非静默钳位 |
format(String) | png | png / jpg / jpeg |
backgroundRgb(int) | 白 | 页面底色 |
antiAlias(boolean) | true | 抗锯齿 |
watermark(...) | 无 | 自定义水印,见下 |
水印
水印烧进像素,读者另存图片后仍然带着。
// 最简:对角灰字
OfficiaWords.toImages(docx, null,
ImageRenderOptions.defaults().watermark("机密 · 仅供内部"));
// 平铺:防止截图裁掉水印
OfficiaWords.toImages(docx, null, ImageRenderOptions.defaults()
.watermark(WatermarkOptions.text("张三 · 2026-08-28 · 禁止外传")
.tile(true)
.fontSizePt(13)
.opacity(0.13f)));| 方法 | 默认 | 说明 |
|---|---|---|
text(String) | 必填 | 空字符串抛异常 |
fontSizePt(float) | 0 = 自动 | 自动时单个按页宽 1/14、平铺按 1/40;上限 400 |
colorRgb(int) | 灰 0x808080 | 0xRRGGBB |
opacity(float) | 0.15 | 0–1 |
rotationDegrees(float) | -38 | 逆时针为正,0 为水平 |
tile(boolean) | false | 整页平铺,用于防截图裁剪 |
fontFamily(String) | 自动 | 不指定时按水印文字自动挑覆盖该字符集的字体 |
中文水印无需自备字体
水印由渲染器在出图时画,用的是库内部的字体栈(含内置中文兜底), 所以中文水印不会变方框,你也不用传字体文件。
要给非 Officia 产出的图片加水印才用 OfficiaImaging.textWatermark。
什么时候该用图片、什么时候该用 PDF
桌面浏览器(Chrome / Edge / Firefox / Safari)都内置 PDF 阅读器、不需要插件, 直接给 PDF 更省体积、文字还能选中搜索。
图片的优势场景是:移动端 webview(微信 / 钉钉 / 企业微信内置浏览器对 PDF 支持差)、 需要防止读者直接拿到可编辑原件、或要把水印烧进像素。
体积远大于 PDF
实测 A4 三页合同(含表格与中英混排):96dpi 约 109 KB/页、150dpi 约 198 KB/页, 而同内容 PDF 全文仅 145 KB。在线预览请考虑懒加载。
当前尚未覆盖的图元
图案填充的真实平铺(按等效纯色近似)、下划线 / 删除线 / 高亮、行内图片、 图片旋转裁剪变换、水平分隔线。文本、纯色填充、表格边框、内嵌图片、定位文本框已覆盖。
水印方面:只支持文字水印,不支持图片水印;一份水印设置作用于全部页面, 不支持逐页不同的动态水印。
Markdown → PDF / docx
Markdown 是 Officia.Words 的一种输入格式(与 docx / doc 并列),无需引入额外模块。
// Markdown 文本 → PDF(版式由排版引擎定版)
byte[] pdf = OfficiaWords.markdownToPdf("# 标题\n\n正文**加粗**");
// Markdown 文本 → 可编辑 docx
byte[] docx = OfficiaWords.markdownToDocx("# 标题\n\n正文**加粗**");
// 字节 / 文件 / 输入流重载(编码自动判定:BOM → 严格 UTF-8 → GBK 回退)
byte[] pdf2 = OfficiaWords.markdownToPdf(new File("readme.md"));
// 流式:边生成边写盘,返回页数
int pages = OfficiaWords.markdownToPdf(new File("in.md"), new File("out.pdf"));支持 CommonMark 常用子集 + GFM 表格:标题(带大纲级别,转 PDF 时成为书签)、段落(中文软换行不插空格)、 任意层级嵌套列表、围栏代码块、引用块、GFM 表格三种对齐、分隔线、YAML front matter → 文档属性, 以及加粗 / 斜体 / 删除线 / 行内代码 / 链接 / 自动链接 / 反斜杠转义。
当前边界
转 PDF 的段落缩进、表格边框、代码块外框均已生效;转 docx 目前只写字符级样式 (文字、加粗斜体、字号、等宽字体与底纹完整),段落缩进、边框与表格结构尚未写出,表格会被平铺为段落—— docx 写侧补齐后自动生效,调用方代码无需改动。
列表编号为展平文本前缀(非 Word 自动编号);图片 ![]() 降级为「[图片] 替代文字」+ 指向原地址的链接, 不会下载外链图;缩进代码块(4 空格)请改用围栏写法。
模板填充
以 docx 为模板,用占位符 + 数据生成填充后的 docx 或直接出 PDF。在 OOXML DOM 上就地替换、保留原样式。
// 单份填充 → docx
byte[] docx = OfficiaWords.fillTemplate(templateBytes, data);
// 单份填充 → 直接出 PDF
byte[] pdf = OfficiaWords.fillTemplateToPdf(templateBytes, data);邮件合并(一模板多数据)
// 每条数据各出一份,返回多份 docx
List<byte[]> docs = OfficiaWords.fillTemplateEach(templateBytes, dataList);
// 每条各出一份并转 PDF
List<byte[]> pdfs = OfficiaWords.fillTemplateEachToPdf(templateBytes, dataList);
// 多条数据合并进一份文档(连续多页)
byte[] mergedDocx = OfficiaWords.fillTemplateMerged(templateBytes, dataList);
byte[] mergedPdf = OfficiaWords.fillTemplateMergedToPdf(templateBytes, dataList);方法一览
| 方法 | 说明 | 返回 |
|---|---|---|
toPdf(docx) | Word → PDF(字节 / 流 / 文件重载) | byte[] |
toImages(docx) | Word → 图片,一页一张(字节 / 流 / 文件重载) | List<byte[]> |
convert(docx, options) | 转换并返回富结果(含页数) | ConvertResult |
markdownToPdf(md) | Markdown → PDF(字节 / 流 / 文件重载,含流式) | byte[] |
markdownToDocx(md) | Markdown → 可编辑 docx(同上重载) | byte[] (docx) |
convertMarkdown(md, options) | Markdown → PDF 并返回富结果 | ConvertResult |
fillTemplate(tpl, data) | 单份模板填充 | byte[] (docx) |
fillTemplateToPdf(tpl, data) | 单份填充并转 PDF | byte[] (pdf) |
fillTemplateEach(tpl, list) | 每条数据各出一份 | List<byte[]> (docx) |
fillTemplateEachToPdf(tpl, list) | 每条各出一份并转 PDF | List<byte[]> (pdf) |
fillTemplateMerged(tpl, list) | 多条合并进一份 | byte[] (docx) |
fillTemplateMergedToPdf(tpl, list) | 多条合并并转 PDF | byte[] (pdf) |
常见问题
- 中文变方块 / 不可复制:Officia 通过 CID Identity-H 子集嵌入字体,正常不会出现;若自定义字体缺字,确认字体覆盖了所需字符集。
- 未授权水印:评估态输出带水印并限页,加载
officia.lic后消失(见 授权与加载)。 - 异常:入参为空或格式非法抛
OfficiaException(非受检),不会向外抛底层IOException。