Skip to content

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(中文错误信息)
pageCountnumber(getter)总页数
docTypestring(getter)"OFD" 或 "OFD-A"
signatureCountnumber(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。

页面可交互区域(矩形为左上原点 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 或 gotoPage
  • watermark:text、fontSize、angle、alpha;stamp:印章 image(PNG/JPEG 字节)+ 显示尺寸 imageW/imageH
  • ink(手写签批,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 字节大端 hex

isEncryptedOfd 不消耗授权(阅读器可用它把「打开失败」分流为密钥解锁流程);私钥与任何信封不匹配时报「私钥与任何信封不匹配」。

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)语法自动释放。

专有软件 · 商业授权(闭源)