# ofd-lib **Repository Path**: 9658065/ofd-lib ## Basic Information - **Project Name**: ofd-lib - **Description**: No description available - **Primary Language**: Unknown - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-07-28 - **Last Updated**: 2026-07-31 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # ofd-lib JavaScript / TypeScript 生态中 **OFD(Open Fixed-layout Document,开放版式文档)** 格式的通用操作库,对标 [pdf-lib](https://pdf-lib.js.org/) 在 PDF 领域的地位。 - 跨环境:Node.js / 浏览器 / Deno(全部 I/O 与解析返回 `Promise`) - 创建与修改并重:从零创建 OFD,或加载并修改现有 OFD - API 简洁:对标 pdf-lib 的 `OFDDocument` / `OFDPage` / `rgb()` / `degrees()` - TypeScript 优先:完整类型定义 - 扩展能力:标准中文(宋体/黑体等)、表单、数字签名(国密 SM2/SM3)、印章、模板 > 遵循 GB/T 33190-2016《电子文件存储与交换格式 版式文档》。 ## 安装 ```bash npm install ofd-lib ``` > 国密 SM2/SM3 为内置纯 TypeScript 实现,零额外依赖。 ## 快速开始 ### 创建文档 ```ts 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) ``` ### 加载与修改 ```ts 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(); // 提取全文文本(可传页索引) ``` ### 表单 ```ts const form = doc.getForm(); form.addTextField('name', { x: 50, y: 100, size: 14 }); form.getField('name').setText('张三'); ``` ### 数字签名 / 印章(国密 SM2/SM3,内置零依赖实现) ```ts 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 摘要不匹配) ``` 也可直接使用底层国密算法: ```ts 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 ``` ### 附件 ```ts doc.attach(fileBytes, 'contract.pdf'); // 添加附件 const atts = doc.getAttachments(); // 加载后提取:[{ name, bytes, format }] doc.removeAttachment('contract.pdf'); // 按名称删除附件 ``` ### 页面模板 ```ts 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 归档校验 ```ts 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 要求:所有字体/图像必须内嵌、必须带数字签名、不得含外部引用。 ``` ### 字体子集化(减小嵌入体积) ```ts // 自动收集文档实际用到的字符,仅保留对应字形并重建字体表 const results = doc.subsetFonts(); // 或保存时一次性完成: const bytes = await doc.save({ subsetFonts: true }); // 仅支持 TrueType 轮廓字体;CFF/OTF 会自动跳过且不影响保存。 ``` ### 大文档流式读写 ```ts // 流式保存:逐条目压缩并产出字节块,避免一次性占用全部未压缩内存 for await (const chunk of doc.saveToStream()) { await writable.write(chunk); // 写入磁盘 / 响应流 } // 从可读流加载(无需事先把整个文件读入内存) // 浏览器:fetch 响应体 / File(Blob) 流都实现了 AsyncIterable 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()); ``` ### 渲染 渲染展示层基于成熟的前端 OFD 渲染库 **[EasyOFD](https://github.com/11627685/EasyOFD)**(Apache-2.0), 由它负责解析与绘制,正确支持 OFD 文字单位(mm→px)、`TextCode`/`DeltaX` 逐字定位、`CTM`、印章图层等。 > 前置:渲染为**浏览器专用**。EasyOFD 依赖 DOM 及 `jszip`/`x2js`/`jb2`/`opentype.js`, > 安装 `ofd-lib` 时会一并安装这些依赖。 ```ts import { renderPageToCanvas, getPageDimensions } from 'ofd-lib'; // 输入既可以是本库解析对象,也可以是原始 OFD 字节(Uint8Array/ArrayBuffer/Blob/File) const ofdBuf = new Uint8Array(await (await fetch('doc.ofd')).arrayBuffer()); // Canvas 栅格化(位置与字号准确) await renderPageToCanvas(ofdBuf, 0, canvasEl, { scale: 1.5 }); const { width, height } = getPageDimensions(doc, 0); // mm ``` 说明: - `renderPageToCanvas(docOrBytes, pageIndex, canvas, options)`:异步,栅格化到 ``。 - 已知局限:Canvas 栅格化为位图,文本在脱离原文档 `@font-face` 时可能回退为系统字体 (位置与字号仍准确);导出请使用 PNG(`canvas.toBlob`)。 - 本库不提供 SVG 矢量导出(EasyOFD 仅输出 Canvas)。 ## 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 渲染器 ├── objects/ # Text/Image/Path/Composite 内容对象 ├── fonts/ # 字体嵌入与度量 ├── crypto/ # 国密签名验证 └── utils/ # ZIP / XML / 坐标 / 颜色 工具 ``` ## 开发路线图 - [x] Phase 1:基础解析与渲染(MVP) - [x] Phase 2:文档创建与修改、绘制、字体嵌入、保存 - [x] Phase 3:跨文档复制、表单、元数据、页面渲染(Canvas) - [x] Phase 4:国密签名(内置 SM2/SM3)与验签、印章、附件、页面模板 - [x] Phase 5:OFD/A 归档校验、字体子集化(TTF)、大文档流式读写 ## 构建与测试 ```bash npm run build # tsup 构建 ESM + CJS npm run typecheck # tsc 类型检查 npm test # vitest ``` ## License MIT