主题
API 参考
npm 包 rust-ofd 的完整 API 面:核心模块的 OfdDoc(解析/渲染/检索/验签)与转换、工具、注释、OFD/A、签名等自由函数。核心 OfdDoc 的方法同步执行(WASM 单线程);转换/工具类自由函数均为 async(首次调用自动懒加载对应模块,可用 initConvert/initTool 显式预载)。bytes 入参均为 Uint8Array。
初始化与全局函数
| 函数 | 签名 | 说明 |
|---|---|---|
init | (module_or_path?) → Promise | 加载核心 WASM;Node 下必须显式传字节(见快速开始) |
initConvert / initTool | (opts?) → Promise | 显式预载转换 / 工具模块(可选;不预载则相关函数首次调用时自动加载) |
setFallbackFont | (bytes) → void | 全局回退字体(TTF/OTF/TTC 字节);须在 await init() 完成后调用(wasm-bindgen 导出,初始化前调用会抛错) |
addFallbackFont | (bytes) → void | 追加回退字体:主回退未覆盖的语种按注入顺序补位(如泰文/泰米尔) |
initLicense | (token) → void | 闭环构建激活授权(默认无授权校验构建为 no-op) |
licenseStatus | () → LicenseStatus | 授权状态明细(enforced/active/env/typ/lic/hosts/deploymentId/didBaked/did/expiresAt/reason) |
resetLicense | () → void | 本地失活(联网复核确认服务端吊销/到期后调用) |
wasmVersion | () → string | 内核版本号(与发布版本一致,可用于核对部署产物) |
OfdDoc(核心模块)
构造与属性
| 成员 | 签名 | 说明 |
|---|---|---|
| 构造 | new OfdDoc(bytes: Uint8Array) | 从 OFD 文件字节解析;失败抛 Error(中文错误信息) |
pageCount | number(getter) | 总页数 |
docType | string(getter) | "OFD" 或 "OFD-A" |
signatureCount | number(getter) | 签名数量(全部 DocBody 合计) |
free() | void | 释放 wasm 侧内存(见文末错误处理与内存管理) |
docInfo(): DocInfo
ts
interface DocInfo {
doc_id?: string; title?: string; author?: string; subject?: string;
abstract_text?: string; creation_date?: string; mod_date?: string;
keywords?: string; creator?: string; producer?: string;
/** [键, 值] 数组,保持包内声明顺序 */
custom_datas: [string, string][];
/** 文档权限声明(Document.xml/Permissions;null = 未声明;v0.11.0) */
permissions?: { print?: boolean; export?: boolean; edit?: boolean; annot?: boolean;
signature?: boolean; watermark?: boolean; fullScreen?: boolean; copy?: boolean } | null;
/** 视图首选项(Document.xml/VPreferences;null = 未声明;v0.11.0) */
vPreferences?: { hideToolbar?: boolean; hideMenubar?: boolean;
hideWindowUI?: boolean; pageMode?: string } | null;
}键名按 OFD 包内声明原样返回(snake_case),与 OFD XML 的元素名一一对应。permissions 为文档制作方声明的权限(本库仅导出、不强制拦截,阅读器可据此提示或限制)。
outline(): OutlineElem[]
大纲/书签树(无大纲时为空数组):
ts
interface OutlineElem { title: string; dest: string; children: OutlineElem[] }pageSize(page): Float64Array
页面物理尺寸 [宽mm, 高mm](页级 Area 优先 → 文档级 CommonData → A4 兜底)。用于按真实纸张呈现:A4 在 96DPI 下即 210/25.4*96 ≈ 794px 宽。
pageImages(page): string[]
页面引用的全部图像,按出现顺序返回 dataURL(按魔数判 PNG/JPEG/BMP/TIFF),可直接作为 <img src>。闭环构建下逐次复验授权:失效时抛出授权错误(而非静默返回空数组)。
图层、多媒体与语义标引(v0.11.0 新增)
pageLayers(page): LayerEntry[] 与 setLayerVisible(id, visible) / resetLayerVisibility()
图层面板三件套:枚举页面图层(含声明隐藏的层)、运行时切换显隐、恢复文档声明状态。
ts
interface LayerEntry { id: number | null; type: "body" | "background" | "fore"; userId?: string | null; visible: boolean }
const layers = doc.pageLayers(0);
const target = layers.find((l) => l.id != null);
doc.setLayerVisible(target.id, false); // 隐藏;true 可强制显示声明隐藏的层
const png = doc.renderPng(0, 3.78); // 之后的全部渲染出口都按覆盖值绘制
doc.resetLayerVisibility(); // 回到文档声明状态无显式层的旧式文档 pageLayers 返回 [];id 为 null 的层无法运行时覆盖。
medias(): MediaEntry[] 与 mediaBytes(id): Uint8Array | null
多媒体资源清单(图像/视频/音频;同 ID 时 DocumentRes 优先)与按资源 ID 取文件字节(任意类型通用,视频封面是独立的 Image 条目):
ts
interface MediaEntry { id: number | null; type: "Image" | "Video" | "Audio";
format?: string | null; duration?: number | null; autoStart?: boolean | null; hasFile: boolean }
const video = doc.medias().find((m) => m.type === "Video" && m.hasFile);
const bytes = doc.mediaBytes(video.id); // → Blob → <video src>pageVideos(page): VideoRegion[]
页内视频对象区域(矩形为左上原点 mm)。静态渲染呈现 PosterID 封面图(无封面画占位框);播放由前端在区域上放播放按钮、经 mediaBytes 取字节自建播放器实现(vue-demo 已内置该形态)。
customTags(): CustomTagEntry[]
文档语义标引条目(GB/T 33190 第 16 章,如数科发票字段标引)。file 指向厂商自定义 XML,其格式由调用方解析——本接口提供的是「标引存在与位置」的数据入口。
渲染
renderPng(page, scale): Uint8Array
渲染为 PNG 字节。scale 为像素/毫米:96DPI 传 96/25.4 ≈ 3.78;2.0 即 2 倍 A4 精度。
renderCanvas(page, scale): CanvasScript
渲染为 Canvas 指令流(指令集见下一页),浏览器配合 playCanvasScript 回放。images 按普通对象(Record<id, dataURL>)序列化,便于 Object.entries 遍历。
exportImage(page, format?, dpi?, quality?): Uint8Array(v0.7.0 新增)
导出单页为图片字节(归档出图/缩略图场景)。format:"png"(默认)| "jpeg";dpi:输出分辨率(默认 96,上限 2540,与渲染守卫同口径);quality:JPEG 质量 1–100(默认 90,PNG 忽略)。JPEG 单边上限 65535 像素,超限报错提示降低 DPI。
ts
const jpg = doc.exportImage(0, "jpeg", 300, 90); // 300dpi 首页
const blob = new Blob([jpg], { type: "image/jpeg" });exportLongImage(pages?, format?, dpi?, quality?): Uint8Array(v0.7.0 新增)
多页竖向拼接为一张长图(白底、水平居中;档案"单图交付"场景)。pages 为 0 起页索引数组,缺省 = 全部页。总画布沿用渲染守卫(边长 ≤16384px、总像素 ≤64M),超限报错。
ts
const longPng = doc.exportLongImage([0, 1, 2], "png", 150); // 前 3 页长图文本与检索
pageText(page): string
提取整页文本(按阅读顺序)。无文本页返回空串。闭环构建下逐次复验授权:失效时抛出授权错误(而非静默返回空串)。
pageTextLayout(page, includeHidden?): TextSpan[]
页面文本布局(逐字符盒,页面 mm 坐标、左上原点,与 Canvas 坐标一致;前端自行乘 scale)。旋转/斜切文本不包含。includeHidden 为 true 时一并返回 Visible="false" 的隐藏文本层——扫描件/检测报告把可检索文字放在隐藏层,阅读器据此实现"文字不可见但可拖选复制"(PDF.js textLayer 同款形态)。前端拖选/检索高亮/文本选择均以此实现,参考 vue-demo。
extractFields(page?): InvoiceFields(v0.7.0 新增)
电子发票字段提取(基于文本层布局的模板化提取:行聚类 + 标签列对齐 + 正则兜底)。发票字体普遍未嵌入,调用前须先 addFallbackFont 注入回退字体,否则文本层无法定位、结果恒为空。非发票文档不报错:各字段为 null 且 matched=false,调用方据此走全文降级。
ts
interface InvoiceFields {
invoiceType?: string; // 票种标题(如「北京增值税电子普通发票」)
invoiceNumber?: string; // 发票号码(旧版 8 位 / 全电票 20 位)
invoiceCode?: string; // 发票代码(旧版 10–12 位;全电票无)
issueDate?: string; // 开票日期(原文格式)
checkCode?: string; // 校验码
machineNumber?: string; // 机器编号
buyerName?: string; // 购买方名称(版式第一个「名称」标签块)
buyerTaxId?: string; // 购买方纳税人识别号
sellerName?: string;
sellerTaxId?: string;
amountExcludingTax?: string; // 合计金额(不含税)
taxAmount?: string; // 合计税额
totalAmount?: string; // 价税合计(小写)
totalAmountCn?: string; // 价税合计(大写中文)
matched: boolean; // 关键字段(号码/日期/购销方)命中 ≥2
}百望增值税电子发票实测:号码/代码/日期/校验码/购销方名称与税号/价税合计(大小写)全部提取正确(见 ofd-render/src/invoice.rs 测试与 tests/data/invoice-baiwang-vat*.ofd 样本)。
formFields(page?, includeHidden?): FormField[](v0.8.0 新增)
表单字段(数科 AreaHolder 命名填写区 ∩ 文本层)。OFD 国标没有表单域——生态事实标准是数科 AreaHolderBlocks.xml 命名填写区(区域名+边界),内核按约定发现式加载;字段值 = 区域内命中的文本层 span 拼接(pdf2ofd 生成的表单值文本即此形态)。与 extractFields 同因:值文本未嵌入时须先 addFallbackFont。无填写区的文档返回 []。
ts
interface FormField { name: string; value: string; boundary: [number, number, number, number] }search(query, caseInsensitive?): SearchHit[]
全文检索(同步)。匹配忽略空白——页文本在文本对象之间插换行(逐字成对象的文档即"字字换行"),多字词组仍能跨行命中;start/end 指向原始 pageText 的字符偏移(区间含其间换行):
ts
interface SearchHit { docIndex: number; pageIndex: number; pageId: number | null; start: number; end: number; snippet: string }docIndex > 0 表示命中来自文档内引用的附属文档(如 OFD/A 附件),主视图不可直接跳转。
交互区域与附件
pageIds(): (number | null)[]
全部页面对象 ID(下标即页序号;未分配 ID 的页为 null),供解析 Goto Dest。
pageLinks(page): LinkRegion[]
页面可交互区域(矩形为左上原点 mm;kind: "goto" | "uri"):
ts
interface LinkRegion { x: number; y: number; w: number; h: number; event: string; kind: "goto" | "uri"; target: string }attachments(): AttachmentEntry[] 与 attachmentBytes(index)
附件列表与附件字节(非内嵌/越界返回 null):
ts
interface AttachmentEntry { index: number; id: number | null; name: string | null; format: string | null; size: number | null; embedded: boolean; loc: string | null }annotations(): AnnotInfo[]
注释概要列表(type 为注释文件中的原始类型标签):
ts
interface AnnotInfo { id: number | null; type: string; pageIndex: number | null; pageRef: number | null; author: string | null; creatTime: string | null; remark: string | null }增删注释请用下方注释操作自由函数。
验签
verifySignatures(trustedKeyHex?, anchors?): SigReport[]
校验文档中的数字签名(同步),报告字段见签名验证报告。文档没有签名时返回 []。
trustedKeyHex:信任公钥(SM2 点04||X||Y的 hex)——纯数字签名(SignedValue 为裸 DER(r,s)、无内嵌证书)时必需anchors:信任锚——PEM 文本(可含多张证书)或 base64 编码的证书 DER;给出后逐个签名做证书链校验
转换(自由函数 · async)
| 函数 | 签名 | 说明 |
|---|---|---|
pdfPageCount | (pdf) → Promise<number> | 探测 PDF 页数 |
convertPdfToOfd | (pdf, textMode?, pages?, password?) → Promise<Uint8Array> | 返回 OFD 包字节 |
convertPdfDetailed | 同参 → Promise<{ ofd, report: ConvertReport }> | 附转换报告(详见转换页) |
convertOfdToPdf | (ofd, textMode?, pages?) → Promise<Uint8Array> | OFD → PDF 矢量转换 |
convertOfdToPdfDetailed | 同参 → Promise<{ pdf, report: OfdToPdfReport }> | 附 OFD→PDF 报告 |
textMode(PDF→OFD):"auto" | "embed" | "outline";(OFD→PDF):"real" | "outline"(real 为真实可检索文本,默认);pages: "1-3,5";password: 加密 PDF 口令。
文档工具(自由函数 · async,工具模块)
| 函数 | 签名 | 说明 |
|---|---|---|
extractPages | (ofd, pages) → Promise<Uint8Array> | 抽取页面("1,3-5",1 起) |
deletePages | (ofd, pages) → Promise<Uint8Array> | 删除页面 |
rotatePages | (ofd, pages, deg) → Promise<Uint8Array> | 旋转页面(pages 空=全部;deg 90|180|270 顺时针) |
mergeOfd | (files, mergeOutline?) → Promise<Uint8Array> | 合并多个 OFD(首个为目标文档) |
splitOfd | (ofd, each) → Promise<Uint8Array[]> | 拆分文档(每 each 页一份) |
addTextWatermark | (ofd, text, size?, alpha?, tile?, rotation?) → Promise<Uint8Array> | 文字水印(垫底层不遮挡内容) |
addImageWatermark | (ofd, image, format?, alpha?, widthMm?, heightMm?, tile?, rotation?) → Promise<Uint8Array> | 图像水印(tile=false 居中) |
createOfd | (spec, fonts?, images?) → Promise<Uint8Array> | 结构化生成 OFD(spec JSON:texts/rects/lines/images,详见 index.d.ts 的 CreateOfdSpec) |
注释操作(自由函数 · async,工具模块)
| 函数 | 签名 | 说明 |
|---|---|---|
listAnnotations | (ofd) → Promise<AnnotationInfo[]> | 列出文档注释 |
addAnnotation | (ofd, spec, image?) → Promise<Uint8Array> | 添加注释,返回新 OFD 字节 |
removeAnnotation | (ofd, annotId) → Promise<Uint8Array> | 删除指定 ID 注释 |
fillFormFields | (ofd, fills) → Promise<Uint8Array> | 表单填写(v0.8.0,见下) |
AnnotationSpec 按 type 取相关字段(完整定义见 index.d.ts):
type:"highlite" | "path" | "ink" | "link" | "watermark" | "stamp"(兼容"highlight");坐标 mm、页 0 起- 通用:
pageIndex、boundary: [x, y, w, h](w/h 为 0 时由内容推导)、author、remark、color: "r,g,b" highlite:rects(矩形组);path:points(折线顶点)+lineWidth;link:uri或gotoPagewatermark:text、fontSize、angle、alpha;stamp:印章image(PNG/JPEG 字节)+ 显示尺寸imageW/imageHink(手写签批,v0.8.0):strokes: [{ points: [[x, y], …], width }]多笔一次落成一条标准Path注释(GB/T 33190 第 15 章原生表达,数科/ofdrw 均可渲染)——每笔独立 PathObject 与线宽(压感由前端烘进width),lineWidth为缺省笔宽;单点笔自动扩为微线段。阅读器示例「签批」按钮即此形态(触屏/手写板压感采集)
表单填写(自由函数 · async,工具模块,v0.8.0)
fillFormFields(ofd, fills): Promise<Uint8Array>
把值文本写入数科 AreaHolder 命名填写区,返回新 OFD 字节。fills: [{ name, value }, …];未命中字段名抛错并列出文档全部填写区名单。值以 TextObject 写入所属页正文层(数科语义,跨阅读器可见、可检索);填写后用 formFields 可取回值(区域∩文本层闭环)。注意:填写会修改页内容文件——已签名文档的既有签名摘要将失配(等同修改正文),应在填写后重新签名。
加密与解密(自由函数 · async,工具模块,v0.11.0)
SM4-CBC 内容加密 + SM2 数字信封(每接收者一条),与 CLI encrypt/decrypt 子命令同底座、字节级一致:
js
import { isEncryptedOfd, encryptOfd, decryptOfd } from "rust-ofd";
const locked = await encryptOfd(ofdBytes, [recipientPublicKeyHex]); // 04||X||Y hex
const needKey = await isEncryptedOfd(locked); // true(普通文档 false)
const plain = await decryptOfd(locked, recipientPrivateKeyHex); // 32 字节大端 hexisEncryptedOfd 不消耗授权(阅读器可用它把「打开失败」分流为密钥解锁流程);私钥与任何信封不匹配时报「私钥与任何信封不匹配」。
OFD/A 归档合规(自由函数 · async,工具模块)
GB/T 42133-2022 检测与修复(九条规则),详见 changelog v0.4.3 与 vue-demo「归档」页:
| 函数 | 签名 | 说明 |
|---|---|---|
ofdaCheck | (ofd) → Promise<OfdaCheckReport> | 九条规则逐项检查;missingFonts 给出未嵌入字型清单 |
ofdaRepair | (ofd, fonts?) → Promise<OfdaRepairResult> | 自动修复另存(子集化嵌入字体/图像转码白名单/去技术化),报告含逐条动作与修复前后复检 |
collectSystemFonts | (names) → Promise<{ found, missing }> | 读取系统字体字节(Chromium queryLocalFonts,需 https/localhost 与用户授权);自动剥 BAAAAA+ 子集前缀回退基名 |
本地签名(自由函数 · 同步)
signOfd(ofd, opts?): Uint8Array
对文档本地签名:即时生成 SM2 密钥对与自签证书(CN=signer),SES v4 + 印章,返回签名后的新 OFD 字节。
js
import { signOfd } from "rust-ofd";
const signed = signOfd(ofdBytes, {
signer: "张三", // 签署人显示名(自签证书 CN)
seal: sealPngBytes, // 印章图片 PNG/JPG(缺省内置占位章)
page: 0, // 盖章页(0 起;null 不盖章做纯数字签名)
x: 140, y: 240, w: 40, h: 40, // 印章 Boundary(mm,页面左上原点)
basis: { reason: "同意", location: "北京", provider: "rust-ofd" },
});注意:签名针对当前内容——签名后若再做旋转/合并等修改,摘要会失效(验签显示"文档已被修改")。选项完整定义见 SignOfdOptions(index.d.ts)。
错误处理与内存管理
所有可失败方法抛 Error,message 为中文(如 "未知文本模式: x(可选 auto|embed|outline)")。
内存管理:wasm 对象不走浏览器 GC。换文档/卸载页面前应显式调用 doc.free() 释放(之后实例不可再用);长会话连续打开多份文档时不释放会持续累积。类实例亦支持 using(Symbol.dispose)语法自动释放。