ofd-lib
v0.1.0
Published
JavaScript/TypeScript 生态中 OFD(开放版式文档)格式的通用操作库,API 设计对标 pdf-lib。
Maintainers
Readme
ofd-lib
JavaScript / TypeScript 生态中 OFD(Open Fixed-layout Document,开放版式文档) 格式的通用操作库,对标 pdf-lib 在 PDF 领域的地位。
- 跨环境:Node.js / 浏览器 / Deno(全部 I/O 与解析返回
Promise) - 创建与修改并重:从零创建 OFD,或加载并修改现有 OFD
- API 简洁:对标 pdf-lib 的
OFDDocument/OFDPage/rgb()/degrees() - TypeScript 优先:完整类型定义
- 扩展能力:标准中文(宋体/黑体等)、表单、数字签名(国密 SM2/SM3)、印章、模板
遵循 GB/T 33190-2016《电子文件存储与交换格式 版式文档》。
安装
npm install ofd-lib国密 SM2/SM3 为内置纯 TypeScript 实现,零额外依赖。
快速开始
创建文档
import { OFDDocument, StandardFonts, rgb } from 'ofd-lib';
const doc = await OFDDocument.create();
const font = await doc.embedFont(StandardFonts.SimSun);
const page = doc.addPage([210, 297]); // A4(mm)
page.drawText('你好,OFD!', {
x: 50, y: 200, size: 24, font, color: rgb(0, 0, 0),
});
page.drawRectangle({
x: 50, y: 50, width: 100, height: 50,
borderColor: rgb(1, 0, 0), borderWidth: 1,
});
const ofdBytes = await doc.save(); // Uint8Array(ZIP)加载与修改
const doc = await OFDDocument.load(existingBytes);
const pages = doc.getPages();
doc.insertPage(1, doc.addPage([210, 297]));
doc.removePage(0);
doc.movePage(2, 0); // 页面重排
const copied = await destDoc.copyPages(srcDoc, [0, 1]);
copied.forEach((p) => destDoc.addPage(p));
doc.setTitle('我的文档');
doc.setAuthor('作者');
const text = doc.extractText(); // 提取全文文本(可传页索引)表单
const form = doc.getForm();
form.addTextField('name', { x: 50, y: 100, size: 14 });
form.getField('name').setText('张三');数字签名 / 印章(国密 SM2/SM3,内置零依赖实现)
import { generateKeyPair } from 'ofd-lib';
// 生成密钥对(或使用已有 SM2 私钥十六进制串)
const { privateKey, publicKey } = generateKeyPair();
// 签名(可同时加盖印章图片);不传 privateKey 时自动生成临时密钥
await doc.sign({ signer: '张三', reason: '审批通过', privateKey, stampImage: stampPngBytes });
const bytes = await doc.save(); // 签名持久化到 Doc_0/Signs/
// 加载后验签(默认使用随签名保存的公钥,可显式传入外部公钥)
const loaded = await OFDDocument.load(bytes);
const results = await loaded.verifySignatures();
// [{ valid: true, message: '签名有效', signer: '张三', time: '...' }]
// 内容被篡改时 valid 为 false(SM3 摘要不匹配)也可直接使用底层国密算法:
import { sm3, sm2Sign, sm2Verify, generateKeyPair } from 'ofd-lib';
sm3('abc'); // '66c7f0f4...'(GB/T 32905 标准向量)
const kp = generateKeyPair();
const sig = sm2Sign('消息', kp.privateKey);
sm2Verify('消息', sig, kp.publicKey); // true附件
doc.attach(fileBytes, 'contract.pdf'); // 添加附件
const atts = doc.getAttachments(); // 加载后提取:[{ name, bytes, format }]
doc.removeAttachment('contract.pdf'); // 按名称删除附件页面模板
const tpl = doc.addTemplate([210, 297]); // 创建模板页(页眉/边框/水印等)
tpl.drawText('机密文件', { x: 30, y: 10, size: 10 });
const page = doc.addPage([210, 297]);
page.setTemplate(tpl); // 页面引用模板,渲染时模板层在下OFD/A 归档校验
import { OFDArchiveValidator } from 'ofd-lib';
const result = OFDArchiveValidator.validate(doc);
// { valid, profile: 'OFD' | 'OFD/A', issues, summary }
if (!result.valid) {
// 常见错误:FONT_NOT_EMBEDDED(字体未嵌入)、IMAGE_NOT_EMBEDDED(图像未嵌入)、
// MISSING_SIGNATURE(缺少数字签名)、SIGNATURE_EMPTY(签名值为空)
console.error(result.issues);
}
// OFD/A 要求:所有字体/图像必须内嵌、必须带数字签名、不得含外部引用。字体子集化(减小嵌入体积)
// 自动收集文档实际用到的字符,仅保留对应字形并重建字体表
const results = doc.subsetFonts();
// 或保存时一次性完成:
const bytes = await doc.save({ subsetFonts: true });
// 仅支持 TrueType 轮廓字体;CFF/OTF 会自动跳过且不影响保存。大文档流式读写
// 流式保存:逐条目压缩并产出字节块,避免一次性占用全部未压缩内存
for await (const chunk of doc.saveToStream()) {
await writable.write(chunk); // 写入磁盘 / 响应流
}
// 从可读流加载(无需事先把整个文件读入内存)
// 浏览器:fetch 响应体 / File(Blob) 流都实现了 AsyncIterable<Uint8Array>
const res = await fetch('big.ofd');
const doc = await OFDDocument.loadFromStream(res.body!);
// 或来自用户选择的文件
const doc2 = await OFDDocument.loadFromStream(file.stream());
// 完整文件可直接:
const doc3 = await OFDDocument.load(await (await fetch('doc.ofd')).arrayBuffer());渲染
import { renderPageToCanvas, renderPageToSVG, getPageDimensions } from 'ofd-lib';
const svg = renderPageToSVG(doc, 0, { scale: 1.5 });
const { width, height } = getPageDimensions(doc, 0); // mm
await renderPageToCanvas(doc, 0, canvasEl, { scale: 1.5 });API 对照(pdf-lib → ofd-lib)
| pdf-lib | ofd-lib | 说明 |
| --- | --- | --- |
| PDFDocument | OFDDocument | 文档主类 |
| PDFPage | OFDPage | 页面类 |
| StandardFonts | StandardFonts | 扩展中文字体 |
| rgb() / cmyk() | rgb() / cmyk() | 完全兼容 |
| degrees() | degrees() | 完全兼容 |
| PDFForm | OFDForm | 表单 |
| — | OFDSignature | 数字签名(国密) |
| — | OFDStamp | 印章 |
目录结构
src/
├── index.ts # 公开 API
├── ofd/ # 文档/页面/字体/图像/表单/签名/印章
├── parsing/ # OFD.xml / Document.xml / Page.xml / 资源 解析
├── rendering/ # Canvas2D 与 SVG 渲染器
├── objects/ # Text/Image/Path/Composite 内容对象
├── fonts/ # 字体嵌入与度量
├── crypto/ # 国密签名验证
└── utils/ # ZIP / XML / 坐标 / 颜色 工具开发路线图
- [x] Phase 1:基础解析与渲染(MVP)
- [x] Phase 2:文档创建与修改、绘制、字体嵌入、保存
- [x] Phase 3:跨文档复制、表单、元数据、SVG 渲染
- [x] Phase 4:国密签名(内置 SM2/SM3)与验签、印章、附件、页面模板
- [x] Phase 5:OFD/A 归档校验、字体子集化(TTF)、大文档流式读写
构建与测试
npm run build # tsup 构建 ESM + CJS
npm run typecheck # tsc 类型检查
npm test # vitestLicense
MIT
