PDF 文档可以携带附件(Attachment),把图片、表格、补充说明等文件随文档一起分发,这种“文档包”形式在合同、报价单、报告等场景中很常见。PDF 中的附件有两种存在形式:一种是文档附件,挂在整份文档上,在阅读器的“附件”面板里统一列出;另一种是注释附件,作为页面上的回形针图标(Paperclip)出现,双击即可打开所附文件。当我们拿到一份带附件的 PDF 时,往往需要把附件取出来单独使用,而这两种附件的读取方式并不相同,需要用不同的接口分别处理。
Spire.PDF for JavaScript 基于 WebAssembly 在浏览器端直接处理 PDF 文档,通过虚拟文件系统(VFS)管理输入输出文件,无需后端服务。两类附件的读取入口不同,但取到后都通过 FileName 与 Data 拿到附件的名称与内容:文档附件用 PdfDocument.Attachments 配 PdfEmbeddedFileSpecification;注释附件需逐页访问 PdfPage.Annotations,按类型筛出 PdfAttachmentAnnotationWidget。
本文介绍两个核心功能点:
有关安装和项目配置,请参考 React 项目中集成 Spire.PDF for JavaScript。以下示例默认已安装 Spire.PDF 并完成 WebAssembly 模块初始化。
相关知识
PDF 文件中的附件分为两类:文档级附件和注释级附件。下表说明了两类附件之间的差异,以及它们在 Spire.PDF for JavaScript 中的表示方式。
| 附件类型 | 表示方式 | 定义 |
|---|---|---|
| 文档附件 | PdfDocument.Attachments,经 PdfEmbeddedFileSpecification 读取 |
以文档级添加的 PDF 附件不会显示在 PDF 页面上,但可以在 PDF 阅读器的“附件”面板中查看。 |
| 注释附件 | PdfAttachmentAnnotationWidget |
作为注释附加的文件可以在页面上或“附件”面板中找到。注释附件在页面上显示为一个纸夹图标;阅读文档时可以双击该图标打开文件。 |
提取 PDF 文档中的附件
PdfDocument.Attachments 返回文档级的全部附件。遍历该集合,用 new PdfEmbeddedFileSpecification(attachment.H) 包装每个附件后,即可通过其 FileName 与 Data 把附件内容写入 VFS;全部写出后,再借助 JSZip 将它们打包成一个 zip 文件下载。这种方式适合一次性留存或迁移文档中的全部附件。
import JSZip from 'jszip';
function App() {
const extractDocumentAttachments = 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);
// 获取文档中的附件集合
let collection = doc.Attachments;
// 在 VFS 中创建临时目录,用于存放解压出的附件
const outputDirectoryName = 'attachmentFiles/';
window.dotnetRuntime.Module.FS.mkdirTree(outputDirectoryName);
// 逐个取出附件,按附件自身的文件名写入临时目录
for (let i = 0; i < collection.Count; i++) {
let attachment = collection.get_Item(i);
// 通过底层句柄 H 构造 PdfEmbeddedFileSpecification,读取附件内容
let embeddedFileSpecification = new pdfModule.PdfEmbeddedFileSpecification(attachment.H);
window.dotnetRuntime.Module.FS.writeFile(
outputDirectoryName + embeddedFileSpecification.FileName,
embeddedFileSpecification.Data
);
}
// 释放文档资源
doc.Close();
// 将临时目录中的全部附件打包为一个 zip 文件
const zip = new JSZip();
let items = await window.dotnetRuntime.Module.FS.readdir(outputDirectoryName);
items = items.filter((item) => item !== '.' && item !== '..');
for (const item of items) {
const fileData = window.dotnetRuntime.Module.FS.readFile(outputDirectoryName + item);
zip.file(item, fileData);
}
const zipBlob = await zip.generateAsync({ type: 'blob' });
// 触发下载
const outputFileName = '文档附件.zip';
const url = URL.createObjectURL(zipBlob);
const a = document.createElement('a');
a.href = url;
a.download = outputFileName;
a.click();
URL.revokeObjectURL(url);
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>提取 PDF 文档中的附件</h1>
<button onClick={extractDocumentAttachments}>
开始提取
</button>
</div>
);
}
export default App;
文档级附件批量导出后打包成的 zip 文件

提取 PDF 注释中的附件
注释附件的读取方式与文档附件不同:它属于页面上的注释,无法通过 doc.Attachments 取得。需要先遍历 doc.Pages 逐页访问 PdfPage.Annotations,用 instanceof 判断每个注释是否为 PdfAttachmentAnnotationWidget(附件注释),命中的注释其 FileName 与 Data 就是所附文件的名称与内容。由于附件注释可能分布在不同页面,外层必须完整遍历所有页面,才能把文档中的注释附件全部取出。
import JSZip from 'jszip';
function App() {
const extractAnnotationAttachments = 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);
// 在 VFS 中创建临时目录,用于存放解压出的附件
const outputDirectoryName = 'annotationFiles/';
window.dotnetRuntime.Module.FS.mkdirTree(outputDirectoryName);
// 逐页遍历,从注释中提取附件
for (let p = 0; p < doc.Pages.Count; p++) {
let page = doc.Pages.get_Item(p);
// 获取当前页的注释集合
let annotations = page.Annotations;
for (let i = 0; i < annotations.Count; i++) {
let annotation = annotations.get_Item(i);
// 仅处理附件注释,其余注释(文本、链接等)跳过
if (annotation instanceof pdfModule.PdfAttachmentAnnotationWidget) {
// FileName 为附件文件名,Data 为附件的二进制内容
window.dotnetRuntime.Module.FS.writeFile(
outputDirectoryName + annotation.FileName,
annotation.Data
);
}
}
}
// 释放文档资源
doc.Close();
// 将临时目录中的全部附件打包为一个 zip 文件
const zip = new JSZip();
let items = await window.dotnetRuntime.Module.FS.readdir(outputDirectoryName);
items = items.filter((item) => item !== '.' && item !== '..');
for (const item of items) {
const fileData = window.dotnetRuntime.Module.FS.readFile(outputDirectoryName + item);
zip.file(item, fileData);
}
const zipBlob = await zip.generateAsync({ type: 'blob' });
// 触发下载
const outputFileName = '注释附件.zip';
const url = URL.createObjectURL(zipBlob);
const a = document.createElement('a');
a.href = url;
a.download = outputFileName;
a.click();
URL.revokeObjectURL(url);
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>提取 PDF 注释中的附件</h1>
<button onClick={extractAnnotationAttachments}>
开始提取
</button>
</div>
);
}
export default App;
从页面注释中提取出的附件打包成的 zip 文件

常见问题
为什么 doc.Attachments 里没有页面上看到的回形针附件
原因:PDF 中的附件分为文档级与注释级两种。PdfDocument.Attachments 只返回文档级附件(在阅读器“附件”面板中列出),而页面上的回形针图标属于注释级附件,它是页面注释的一部分,不会出现在 Attachments 集合中。
解决:提取注释附件要逐页访问页面注释集合,并按类型筛出附件注释:
for (let p = 0; p < doc.Pages.Count; p++) {
let annotations = doc.Pages.get_Item(p).Annotations;
for (let i = 0; i < annotations.Count; i++) {
if (annotations.get_Item(i) instanceof pdfModule.PdfAttachmentAnnotationWidget) {
// 处理附件注释
}
}
}
为什么遍历 page.Annotations 时必须先做类型判断
原因:页面上可以并存多种注释,例如文本注释、链接注释、图章注释等,它们的属性各不相同。只有附件注释 PdfAttachmentAnnotationWidget 才提供 FileName 与 Data,直接对任意注释读取这两个属性并不可靠。
解决:用 instanceof PdfAttachmentAnnotationWidget 先判断类型,再读取属性:
let annotation = annotations.get_Item(i);
if (annotation instanceof pdfModule.PdfAttachmentAnnotationWidget) {
let fileName = annotation.FileName;
let data = annotation.Data;
}
为什么只提取到了部分注释附件
原因:注释附件是挂在具体页面上的,不同页都可能有。如果只访问 doc.Pages.get_Item(0) 这一页,其余页面上的附件注释就会被漏掉。
解决:外层遍历 doc.Pages,把每一页的注释集合都检查一遍:
for (let p = 0; p < doc.Pages.Count; p++) {
let annotations = doc.Pages.get_Item(p).Annotations;
// 逐个检查该页的注释
}
获取免费许可证
如果您希望删除结果文档中的评估消息,或者摆脱功能限制,请该Email地址已收到反垃圾邮件插件保护。要显示它您需要在浏览器中启用JavaScript。获取有效期 30 天的临时许可证。







