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 追加回退字体:主回退未覆盖的语种按注入顺序补位(如泰文/泰米尔)
setStyledFallbackFont (style, bytes) → void 按风格注入转换模块回退字体(0.12.0):style ∈ "song"|"serif"|"hei"|"sans"|"kai";非嵌入字体转换时按 PDF 字体名选风格匹配者做替代子集(详见 convert 字体保真)
initLicense (token) → void 闭环构建激活授权(默认无授权校验构建为 no-op)
licenseStatus () → LicenseStatus 授权状态明细(enforced/active/env/typ/lic/hosts/scope/module/deploymentId/didBaked/did/expiresAt/reason);scope 为功能档位(0.14.0 起 preview/convert/full)、module 为本 wasm 模块标识(core/convert/tool)
resetLicense () → void 本地失活(联网复核确认服务端吊销/到期后调用)
wasmVersion () → string 内核版本号(与发布版本一致,可用于核对部署产物)

授权的时间边界(集成前必读):过期判定取「客户端时钟」与「单调流逝」两者的较大值—— 单调流逝可挡住系统级时钟回拨(把系统时间改回去不会让已过期授权"复活"), 但页面脚本仍可重定义 Date.now / performance.now,所以 客户端时钟不是权威。 在线授权请在 Web 集成层定时向授权服务端复核(licenseStatus().expiresAt 只是本地判断), 服务端确认吊销/到期后调用 resetLicense() 立即失活;离线授权的有效期以签发时的 ttl 为准,需要长期可用时用较短的 ttl 配合在线复核。

功能档位(0.14.0 起):token 携带 scope 档位(preview 仅基础预览 / convert 预览+转换 / full 全功能,对应价格页三档),内核按模块身份强制——档位不足时 initLicense 与对应模块的功能调用抛错(原因码 scope_denied,licenseStatus().reason 可查)。 预览版部署上调用转换/工具函数即得到明确中文报错;packages/ofd 的聚合 licenseStatus() 对 scope_denied 不拉低整体 active(未购模块≠授权失效)。

功能级档位(0.16.0 起):注释与附件读写、签名与签章校验(verifySignatures / signOfd / annotations / attachments / attachmentBytes,均在本页 core 模块章节) 划入全功能版——预览版 / 转换版授权调用即抛错(原因码同 scope_denied),各接口小节 已用提示块标注。

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

interface DocInfo {
  docId?: string | null; title?: string | null; author?: string | null; subject?: string | null;
  abstractText?: string | null; creationDate?: string | null; modDate?: string | null;
  keywords?: string | null; creator?: string | null; producer?: string | null;
  /** [键, 值] 数组,保持包内声明顺序 */
  customDatas: [string, string][];
  /** 文档用途(DocInfo/DocUsage,如 "E-government";null = 未声明;v0.18.0) */
  docUsage?: string | null;
  /** 封面图像路径(DocInfo/Cover,包根相对);null = 未声明;v0.18.0 */
  cover?: string | null;
  /** 文档权限声明(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;
}

键名为 camelCase(v0.22.0 起文档与运行时口径对齐——此前本页与 .d.ts 手写为 snake_case 系文档陈旧,运行时序列化一直是 camelCase,消费方不受影响)。日期字段格式 YYYY-MM-DDThh:mm:ss;customDatas 可承载 Dublin Core / DA/T 46 档案等扩展元数据。permissions 为文档制作方声明的权限(本库仅导出、不强制拦截,阅读器可据此提示或限制)。编辑元数据用工具模块的 editOfdMeta。

outline(): OutlineElem[](v0.18.0 多文档聚合)

大纲/书签树(无大纲时为空数组;多 DocBody 归档包聚合全部文档,条目带 docIndex):

interface OutlineElem { title: string; dest: string; docIndex: number; children: OutlineElem[] }

配套 docPageCounts(): number[] 返回逐文档页数(单文档等价 [pageCount]), 供把 search 命中的 docIndex+pageIndex(文档内)换算成全局页号。

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()

图层面板三件套:枚举页面图层(含声明隐藏的层)、运行时切换显隐、恢复文档声明状态。

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 条目):

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 取字节自建播放器实现(astro-site 已内置该形态)。

customTags(): CustomTagEntry[]

文档语义标引条目(GB/T 33190 第 16 章,如数科发票字段标引)。file 指向厂商自定义 XML,其格式由调用方解析——本接口提供的是「标引存在与位置」的数据入口。0.17.0 起支持国标 TypeID 属性与 <FileLoc> 子元素写法,Doc_0/Tags/CustomTags.xml 约定路径(Document.xml 未引用)自动发现。

customTagBytes(index): Uint8Array | null

取第 index 个标引条目的内容文件字节(0.17.0)。外部 URL 引用或下标越界返回 null;路径基准按「包内实际存在」择优(CustomTags.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。

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),超限报错。

const longPng = doc.exportLongImage([0, 1, 2], "png", 150); // 前 3 页长图

文本与检索

pageText(page): string

提取整页文本(按阅读顺序)。无文本页返回空串。闭环构建下逐次复验授权:失效时抛出授权错误(而非静默返回空串)。

pageTextLayout(page, includeHidden?): TextSpan[]

页面文本布局(span 包围盒,页面 mm 坐标、左上原点,与 Canvas 坐标一致;前端自行乘 scale)。旋转/斜切文本以近似盒包含(45° 水印等斜排内容也有 span,包围盒为外接矩形)。includeHidden 为 true 时一并返回 Visible="false" 的隐藏文本层——扫描件/检测报告把可检索文字放在隐藏层,阅读器据此实现"文字不可见但可拖选复制"(PDF.js textLayer 同款形态)。前端拖选/检索高亮/文本选择均以此实现,参考 astro-site。

extractFields(page?): InvoiceFields(v0.7.0 新增)

电子发票字段提取(基于文本层布局的模板化提取:行聚类 + 标签列对齐 + 正则兜底)。发票字体普遍未嵌入,调用前须先 addFallbackFont 注入回退字体,否则文本层无法定位、结果恒为空。非发票文档不报错:各字段为 null 且 matched=false,调用方据此走全文降级。

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。无填写区的文档返回 []。

interface FormField { name: string; value: string; boundary: [number, number, number, number] }

search(query, caseInsensitive?): SearchHit[]

全文检索(同步)。匹配忽略空白——页文本在文本对象之间插换行(逐字成对象的文档即"字字换行"),多字词组仍能跨行命中;start/end 指向原始 pageText 的字符偏移(区间含其间换行):

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"):

interface LinkRegion { x: number; y: number; w: number; h: number; event: string; kind: "goto" | "uri"; target: string }

attachments(): AttachmentEntry[] 与 attachmentBytes(index, docIndex?)

授权档位

全功能版能力(v0.16.0 起):预览版 / 转换版授权调用时抛错(v0.18.0 起 attachmentBytes 与 attachments 同口径抛错,此前静默返回 null)。

附件列表与附件字节(多 DocBody 聚合、条目带 docIndex;index 为所属文档内序号, 取字节用 attachmentBytes(index, docIndex);非内嵌/越界返回 null):

interface AttachmentEntry { index: number; docIndex: number; id: number | null; name: string | null; format: string | null; size: number | null; embedded: boolean; loc: string | null }

annotations(): AnnotInfo[]

授权档位

全功能版能力(v0.16.0 起):预览版 / 转换版授权调用时抛错。

注释概要列表(type 为注释文件中的原始类型标签):

interface AnnotInfo { id: number | null; docIndex: number; type: string; pageIndex: number | null; pageRef: number | null; author: string | null; creatTime: string | null; remark: string | null }

(v0.18.0 起多 DocBody 聚合全部文档,条目带 docIndex,pageIndex 为所属文档内序号。)

增删注释请用下方注释操作自由函数。

验签

verifySignatures(trustedKeyHex?, anchors?, crls?): SigReport[]

授权档位

全功能版能力(v0.16.0 起):预览版 / 转换版授权调用时抛错。

校验文档中的数字签名(同步),报告字段见签名验证报告。文档没有签名时返回 []。

  • trustedKeyHex:信任公钥(SM2 点 04||X||Y 的 hex)——纯数字签名(SignedValue 为裸 DER(r,s)、无内嵌证书)时必需。v0.18.0 语义:显式传入时签名值只按它判定(usedEmbeddedCert=false);签名者内嵌证书自验通过但与传入公钥不一致时在报告 warnings 中告警——以「签名值有效」作可信判据的调用方由此可识别自签证书冒充(未传时维持内嵌证书优先)
  • anchors:信任锚——PEM 文本(可含多张证书)或 base64 编码的证书 DER;给出后逐个签名做证书链校验
  • crls(v0.18.0):CRL(证书吊销列表)——-----BEGIN X509 CRL----- PEM 块(可多块)或 base64(DER)。给出后对签名者证书做离线吊销查询:CRL 须由签发者证书(信任锚/签名包证书集内)验签后才采信;命中吊销 → chainStatus="revoked"。CLI 侧为 verify --crl <文件>

v0.18.0 起 CMS/PKCS#7 SignedData 真验签(此前该形态按「格式不支持」区分,无法给出结论):SM3withSM2 / SM2-1(GB/T 35275 国密变体)与 SHA 族 RSA(sha256WithRSA / sha1WithRSA)——真实政务签章(topsec、云南 CA 等)直接给出 signatureValid 结论;CMS unsignedAttrs 携带 RFC 3161 时间戳时验证 messageImprint 与 TSA 签名,报告见 timestamp 字段(present/verified/genTime)。

转换(自由函数 · 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)
editOfdMeta (ofd, patch) → Promise<EditOfdMetaResult> 编辑文档元数据 DocInfo(v0.22.0,见下)

editOfdMeta(ofd, patch)(v0.22.0 新增)

编辑首个文档的元数据(GB/T 33190 DocInfo 全字段),返回 { ofd, report }。全功能版能力(工具模块即全功能档)。加密件拒绝。

interface DocInfoPatch {
  // 每个标准字段三态:缺 key 不动 / null 清除 / 字符串设值(trim 后空串视为清除)
  title?: string | null; author?: string | null; subject?: string | null;
  abstract?: string | null; keywords?: string | null; creator?: string | null;
  producer?: string | null; docUsage?: string | null; cover?: string | null;
  creationDate?: string | null; modDate?: string | null;   // YYYY-MM-DDThh:mm:ss
  customDatas?: [string, string][];                        // 整体替换([] = 清空)
  autoModDate?: boolean;                                   // 缺省 true:自动刷 ModDate
}
interface MetaEditReport {
  docCount: number;          // >1 = 多文档包,仅编辑了首个
  changed: string[];         // 变更的国标字段名(Title/Author/…/ModDate/CustomDatas)
  customAdded: number; customRemoved: number;
  createdDocInfo: boolean;   // 原 DocInfo 缺失、本次新建
  hadSignature: boolean;     // 含签名:改写使既有签名摘要失配,应提示重新签名
}
const res = await editOfdMeta(ofdBytes, {
  title: "归档著录标题",            // 设值
  author: null,                    // 清除
  customDatas: [["档号", "A-2026-001"]],  // 整体替换自定义键值
});
if (res.report.hadSignature) { /* 提示重新签名 */ }

日期字段格式非法时整体报错且产物不变;DocID 只读(文档身份标识,OFD-A 合规依赖,缺失由 ofdaRepair 补全)。参考实现:astro-site 阅读器「信息」面板与 /meta 独立工作台。

注释操作(自由函数 · 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 子命令同底座、字节级一致:

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

安全语义(选型前必读):

  1. 本形态提供机密性(SM4-CBC 内容加密 + SM2 数字信封),不提供完整性 / 抗篡改;
  2. 完整性校验依赖包内 Encryption.xml 的 Checksum,而该元数据与密文同处一个包、不受密钥保护——能改写包的攻击者可以同时改写两者。因此**「解密成功」只说明拿到了明文,不等于内容可信**;需要抗篡改请叠加数字签名(本页下文的 signOfd / verifySignatures 两节)并校验签名结论;
  3. 旧版包(Entry 无独立 IV)回退使用顶层单一 IV,同一 CEK 下多文件复用 IV——为互操作性仍支持解密此类包,但不要用它加密新数据。

OFD/A 归档合规(自由函数 · async,工具模块)

GB/T 42133-2022 检测与修复(v0.21.0 起 14 个检查项:格式规则 + 四性文件级要求——引用完整性、DocID 元数据、扩展信息残留、签名有效性),详见 changelog 与 astro-site 阅读器「归档」面板。原生端等价命令为 ofd-cli ofda-check / ofda-repair / ofda-batch(用法见仓库 docs/site/bindings/cli.md,该目录不随线上文档站发布):

函数 签名 说明
ofdaCheck (ofd) → Promise<OfdaCheckReport> 14 项逐项检查;missingFonts 给出未嵌入字型清单,signatures 给出逐签名验签状态(签名值+文件摘要,不判证书链信任)
ofdaRepair (ofd, fonts?, options?) → Promise<OfdaRepairResult> 自动修复另存(子集化嵌入字体/图像白名单/去技术化/悬空引用清理/大纲治理/附件治理/DocID 补全/修复留痕),报告含逐条动作、修复前后复检与 warnings;options 见下方
ofdaConvert (ofd) → Promise<Uint8Array> 仅转存 OFD/A 基础形态(去技术化,不含字体嵌入/图像修复);加密/多文档/已签件拒绝;需要完整合规请用 ofdaRepair
collectSystemFonts (names) → Promise<{ found, missing }> Chromium queryLocalFonts(需 https/localhost 与用户授权)读取系统字体;按名称归一化匹配(子集前缀/空白/样式后缀/中文-英文别名),未授权时 found 为空

已签名件处置(v0.21.0,GB/T 42133 6.13):修复重写受保护文件会使验签必败,因此 ofdaRepair 对含签名件默认拒绝(错误消息说明处置方式);验签合规的签章件可免技术化带签名归档(检测报告 signatures 全部有效即属此类);确需修复时显式传 { stripSignature: true } 剥离——SES 内嵌印章图片转为页面图像保留视觉(矢量/外置印章记 stamp-visual-lost 警告),剥离动作与印章去向全部记入报告。

浏览器端合规修复流程(工具模块需全功能档):

import { ofdaCheck, ofdaRepair, collectSystemFonts } from "rust-ofd";

const before = await ofdaCheck(bytes);
const names = before.missingFonts.map((f) => f.name);
const { found, missing } = await collectSystemFonts(names);
// missing 非空时:提示用户安装字体,或用业务允许的替代字体补入 found
const { ofd, report } = await ofdaRepair(bytes, found);
const after = await ofdaCheck(ofd);   // 复检
// 已签件:ofdaRepair(bytes, found) 报错时按需显式剥离
// const { ofd, report } = await ofdaRepair(bytes, found, { stripSignature: true });
  • 修复报告 report.warnings 说明被替代/未嵌入的字体;after.missingFonts 仍不空时不得宣称合规。
  • 修复留痕(DA/T 70 处理过程记入元数据):默认写入 DocInfo/CustomDatas 三键 OFDA-Repair-Time/Tool/Summary,重复修复覆盖同键;传 { noTrace: true } 关闭。
  • 原生批量修复推荐 ofd-cli ofda-repair/ofda-batch:可扫描系统字体目录、显式 --fallback-font 替代、--strip-signature 剥离签名并输出 JSON/CSV 报告。
  • OFD/A single-doc 规则要求单文档:ofdaRepair 对多 DocBody 输入拒绝(属不可无损修复),需先归档拆分为单文档。

本地签名(自由函数 · 同步)

signOfd(ofd, opts?): Uint8Array

授权档位

全功能版能力(v0.16.0 起):预览版 / 转换版授权调用时抛错。

对文档本地签名:即时生成 SM2 密钥对与自签证书(CN=signer),SES v4 + 印章,返回签名后的新 OFD 字节。

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