表单填完之后下一步通常是归档。可 PDF 表单域偏偏在这里留下口子:文件看着已经填好,控件却还在,收件人随手就能改掉金额、日期或者签字栏,有的阅读器打开时还会重新校验一遍。要把这份文件变成谁都改不动的成品,就得把控件连同值一起压进页面内容里。
本文用 Spire.PDF for JavaScript 来实现 PDF 表单域的扁平化。它基于 WebAssembly 在浏览器端加载、修改与保存文档,通过虚拟文件系统(VFS)读写文件,无需后端配合。
本文介绍两个核心功能点:
有关安装和项目配置,请参考 React 项目中集成 Spire.PDF for JavaScript。以下示例默认已安装 Spire.PDF 并完成 WebAssembly 模块初始化。
扁平化整个表单
Spire.PDF for JavaScript 提供 PdfForm.IsFlatten 属性,用于把整份表单一次性扁平化。置为 true 后,文档里所有字段连同当前值一起转成静态页面内容,保存出来的 PDF 不再有可交互的控件;文字仍留在文本层,照样可以选中、复制和检索。
function App() {
const flattenWholeForm = async () => {
// 获取 Spire.PDF WASM 模块
const pdfModule = window.wasmModule?.spirepdf;
// 检查模块是否就绪
if (!pdfModule) {
alert('Spire.PDF is not ready yet');
return;
}
// 将待处理的 PDF 文件载入 VFS
const inputFileName = '员工登记表.pdf';
await window.spire.FetchFileToVFS(inputFileName, "", `${process.env.PUBLIC_URL}/data/`);
// 创建 PdfDocument 对象并加载 PDF 文档
let doc = new pdfModule.PdfDocument();
doc.LoadFromFile(inputFileName);
// 一次性扁平化整份表单
doc.Form.IsFlatten = true;
const outputFileName = '扁平化表单.pdf';
doc.SaveToFile(outputFileName);
doc.Close();
// 从 VFS 读取生成的文件,触发下载
const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
const blob = new Blob([fileArray], { type: 'application/pdf' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = outputFileName;
a.click();
URL.revokeObjectURL(url);
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>扁平化整个表单</h1>
<button onClick={flattenWholeForm}>
开始扁平化
</button>
</div>
);
}
export default App;
所有字段的输入框都消失,值留在页面上成为普通文本:

扁平化指定字段
Spire.PDF for JavaScript 还提供 PdfField.Flatten 属性,用于按字段粒度扁平化。字段实例要从 PdfFormWidget 的 FieldsWidget 集合里按 Name 取,只有挑中的那个字段被固化,其余保持可编辑——比如把已经核对过的邮箱冻结,同时留出日期栏给收件人填。
function App() {
const flattenSelectedField = async () => {
// 获取 Spire.PDF WASM 模块
const pdfModule = window.wasmModule?.spirepdf;
// 检查模块是否就绪
if (!pdfModule) {
alert('Spire.PDF is not ready yet');
return;
}
// 将待处理的 PDF 文件载入 VFS
const inputFileName = '员工登记表.pdf';
await window.spire.FetchFileToVFS(inputFileName, "", `${process.env.PUBLIC_URL}/data/`);
// 创建 PdfDocument 对象并加载 PDF 文档
let doc = new pdfModule.PdfDocument();
doc.LoadFromFile(inputFileName);
// 从表单拿到 Widget 集合
let formWidget = new pdfModule.PdfFormWidget(doc.Form.H);
// 按名称挑出目标字段,只扁平化它
for (let i = 0; i < formWidget.FieldsWidget.Count; i++) {
let field = formWidget.FieldsWidget.get_Item({ index: i });
if (field.Name === 'email') {
field.Flatten = true;
}
}
const outputFileName = '扁平化指定字段.pdf';
doc.SaveToFile(outputFileName);
doc.Close();
// 从 VFS 读取生成的文件,触发下载
const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
const blob = new Blob([fileArray], { type: 'application/pdf' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = outputFileName;
a.click();
URL.revokeObjectURL(url);
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>扁平化指定字段</h1>
<button onClick={flattenSelectedField}>
开始扁平化
</button>
</div>
);
}
export default App;
只有邮箱的输入框消失,其余字段仍是可编辑的控件:

常见问题
设置了 IsFlatten,字段在阅读器里还是能点
原因:代码里混用了两个字段集合。doc.Form.Fields 对由其它工具生成的 AcroForm 常常一个字段都读不到,本文样例文档的 Count 读回就是 0,对它调 get_Item() 会直接抛 ArgumentOutOfRange_IndexMustBeLess;就算能读到,那里返回的 PdfField 也没有 Text、Checked 这类控件属性,改它不会影响页面。
解决:整表扁平化只用 doc.Form.IsFlatten,与集合无关;只要涉及逐个字段——按名挑选、写值、单独固化——一律改走 PdfFormWidget:
let formWidget = new pdfModule.PdfFormWidget(doc.Form.H);
for (let i = 0; i < formWidget.FieldsWidget.Count; i++) {
console.log(formWidget.FieldsWidget.get_Item({ index: i }).Name);
}
怎么判断一份 PDF 是否已经扁平化
原因:PdfForm.IsFlatten 是写入指令,不是状态位。把已经扁平化的产物重新载入,doc.Form.IsFlatten 照旧读回 false——实测此时文档里的字段数已经是 0。
解决:按字段数量判断,FieldsWidget.Count 为 0 就是已经没有可交互的控件了:
let formWidget = new pdfModule.PdfFormWidget(doc.Form.H);
const hasFormFields = formWidget.FieldsWidget.Count > 0;
只扁平化了指定字段,值却没有跟着固化
原因:Name 是逐字符比较,区分大小写,也保留首尾空格。写成 company_name 而文档里实际是 company_name (末尾带空格),循环里一次都不会命中,而且不报错、文件原样输出。
解决:先把所有字段名打印一遍再照着复制,判断与赋值都必须落在 FieldsWidget 给出的 *FieldWidget 实例上:
for (let i = 0; i < formWidget.FieldsWidget.Count; i++) {
console.log(formWidget.FieldsWidget.get_Item({ index: i }).Name);
}
获取免费许可证
如果您希望删除结果文档中的评估消息,或者摆脱功能限制,请该Email地址已收到反垃圾邮件插件保护。要显示它您需要在浏览器中启用JavaScript。获取有效期 30 天的临时许可证。









