PDF ↔ OFD 双向转换
两个方向均为 async 自由函数(首次调用自动懒加载转换模块):
import { convertPdfToOfd, convertPdfDetailed, convertOfdToPdf, pdfPageCount } from "rust-ofd";
// PDF → OFD:只要字节
const ofdBytes = await convertPdfToOfd(pdfBytes, "auto");
// PDF → OFD:要报告
const { ofd, report } = await convertPdfDetailed(pdfBytes, "embed", "1-3,5", "password?");
// OFD → PDF:矢量 + 真实可检索文本
const pdfBytes = await convertOfdToPdf(ofdBytes, "real");
// 转换前探测 PDF 页数
const pages = await pdfPageCount(pdfBytes);
参数
| 参数 | 类型 | 说明 |
|---|---|---|
pdf / ofd |
Uint8Array |
源文件字节 |
textMode |
PDF→OFD:"auto" | "embed" | "outline";OFD→PDF:"real" | "outline" |
文本策略(下表) |
pages |
string?("1-3,5") |
页码范围;省略转全部 |
password |
string?(仅 PDF→OFD) | 加密 PDF 口令 |
PDF → OFD 文本双模式
| 模式 | 行为 | 适用 |
|---|---|---|
auto |
先尝试嵌入字体子集(Tier 1),无法保真时退化为文字轮廓(Tier 2) | 默认推荐 |
embed |
只做字体嵌入;字体无法解析时报警告 | 需要可检索文本 |
outline |
文字一律转矢量轮廓 | 目标环境无字体渲染能力 |
GB/T 42133-2022(档案指南)要求字体保真——auto 模式在国标原文 PDF(138 个裸 CFF 字体)上达成零退化嵌入。旋转/非均匀缩放文本走轮廓退化时会并行输出不可见 TextCode,保检索。
OFD → PDF
textMode: "real"(默认)输出真实可检索文本(内嵌字体按原字节透传,非嵌入字体按回退字体输出);"outline" 输出矢量轮廓。报告含嵌入/回退字体数、大纲与附件迁移统计:
interface OfdToPdfReport {
pages: number; // 转换页数
fonts_embedded: number; // 原字节透传的嵌入字体数
fonts_fallback: number; // 走回退字体的字体数
images: number; // 转换图像数
outline_items: number; // 迁移的书签数
attachments: number; // 迁移的附件数
warnings: string[];
}
ConvertReport(PDF → OFD)
interface ConvertReport {
pages: number; // 转换页数
fonts_embedded: number; // 嵌入字体数(Tier 1)
fonts_outlined: number; // 退化为轮廓的字体数(Tier 2)
images: number; // 转换图像数
warnings: { kind: string; page?: number; message: string }[];
// 0.12.0 新增:字体诊断
font_details: { name: string; action: "embedded" | "substituted" | "outlined" | "skipped";
source?: "system" | "fallback" }[]; // 逐字体处理明细
substitute_missing: [string, number][]; // 替代子集源缺失字符(字体名 → 缺字数)
}
warnings 记录每一处降级(如 JPX 图像占位、字体无法嵌入),上线前务必展示给用户,不要静默吞掉。font_details 是「预览字体和 PDF 不一样」的第一排查入口:哪个字体被替代、替代来源一目了然;substitute_missing 非 0 表示该字体有部分字符在替代字库中无字形(预览时会缺字)。
字体保真(0.12.0)
非嵌入字体(PDF 只带字体名,中文发票/公文极常见)的处理:
- 本机 / 服务端(CLI):按字体名匹配系统同族字库做替代子集——宋体/仿宋/楷体/黑体/微软雅黑/等线及西文 Base14(Times/Helvetica/Courier)均映射到 Windows/macOS/Linux 的同名系统字体,转换产物内嵌正确风格的字体子集,任何阅读器预览即与 PDF 一致;
- 网页端(WASM,无系统字体):按风格槽注入回退字体,转换时按 PDF 字体名选风格匹配者:
import { setStyledFallbackFont } from "rust-ofd";
// init/initConvert 完成后;同风格重复注入覆盖,槽缺省落 setFallbackFont 的字体
setStyledFallbackFont("hei", heiBytes); // 黑体类(如思源黑体子集)
setStyledFallbackFont("kai", kaiBytes); // 楷体类(如霞鹜文楷子集)
// "song"/"serif"(宋/衬线)同理;不注入则该风格文档回退 setFallbackFont 字体
风格字体须为 TrueType 风味(glyf),推荐 OFL 开源字体子集(黑体类:思源黑体 / Noto Sans SC;楷体类:霞鹜文楷 LXGW WenKai),用 ofd-cli subset-font <输入> <输出> 生成 GB2312+ASCII 子集(约 2-3 MB/套)后放到站点静态目录按需 fetch。
嵌入字体的个别字形缺失(解码失败/字形 ID 越界)时,该字符自动从回退字体轮廓补画,不再显示空白;检索语义经 ToUnicode/编码恢复链保留。
转换质量评测(0.6.0)
内核以 1018 份公开 PDF 语料(corpus/,覆盖中文正文/嵌入子集字体/扫描件/表单/注释/加密/JPX 图像等矩阵)做自动化评测(scripts/pdf2ofd-eval.py,指标为转换成功率 / SSIM 视觉还原 / 文本层保留)。全量口径(2026-10-12,100 DPI、5 页/份、-cropbox):转换 rc=0 1002/1018 = 98.4%,SSIM≥0.95 557 份、文本相似度≥0.9 437 份、丢文本层 29 份、平均分 59.19、panic 0。其中 4 份产出空文档(0 页、不可渲染),故同时给出「非空产物」口径 998/1018 = 98.0%——convert_ok 只看退出码,空产物会被计成成功,评测脚本现单独统计 empty_output 与 ref_render_failed(poppler 参考渲染失败约 1%,属口径噪声)。对照上一版基线(999/1018、非空 996、SSIM 542、文本 431、平均分 57.61):非空产物 +2、SSIM +15、文本 +6、平均分 +1.58,无新增转换失败。达标线与逐份对账、以及伴随的字体编码工作线对 13 份样例文本层的影响见 docs/business/pdf2ofd-商业化达标线.md。另以 scripts/pdf2ofd-page-verify.py 做单文件全页逐页严格对拍(1018 份/2036 页,2026-10-08 口径:bad 476 份、panic 0),两套口径互补定位"整页分数不敏感但逐页可辨"的渲染不一致。
回退字体
无嵌入字体的 PDF(扫描件、小众字体)转换后需要回退字体才能显示文字:
// init 完成后注入;用 TTF(CFF/OTF 会使 OFD→PDF 真实文本退化为轮廓)
setFallbackFont(new Uint8Array(await (await fetch("/fallback-font.ttf")).arrayBuffer()));
渲染与转换共用同一份注入字体。
实测数据
| 样本 | 页数 | 耗时 | 产物 | 字体 |
|---|---|---|---|---|
| GB/T 42133-2022 国标原文 PDF | 14 | 0.47s | 1533.9 KB | 138 嵌入 / 0 退化 |
| w3.org 示例 PDF | — | <0.3s | — | 保真嵌入 |
(单机实测值,尚未做系统基准测试;浏览器 WASM 端耗时约为原生的 1.5–3 倍。)