PDF 到了互审环节,意见往往不是写在正文里,而是以批注的形式贴在页边:某处要改、某处已确认、某处存疑。要把这些批注汇总成一份清单,靠人工在阅读器里逐条点开、抄录,文档一多就难以为继。
本文介绍用 Spire.PDF for JavaScript 提取 PDF 文档中指定的注释与全部注释。它基于 WebAssembly 在浏览器端直接加载与读取 PDF 文档,全过程在本地完成,通过虚拟文件系统(VFS)读写文件,无需后端配合。
本文介绍两个核心功能点:
有关安装和项目配置,请参考 React 项目中集成 Spire.PDF for JavaScript。以下示例默认已安装 Spire.PDF 并完成 WebAssembly 模块初始化。
提取指定的注释
Spire.PDF for JavaScript 提供 PdfPage.Annotations 集合,用于读取页面上已有的注释。按索引取出单条注释、用 instanceof 判断它的类型后,就能读到这条注释的内容、作者、名称与修改时间。
function App() {
const extractSpecificAnnotation = 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 文档
const doc = new pdfModule.PdfDocument();
doc.LoadFromFile(inputFileName);
// 取第一页的注释集合
const page = doc.Pages.get_Item(0);
const annotations = page.Annotations;
// 指定要读取的注释,这里取集合中的第一条
const index = 0;
const target = annotations.get_Item(index);
let content = `读取第 ${index + 1} 条注释\r\n`;
// 判断注释类型:只有文本注释才带作者与名称
if (target instanceof pdfModule.PdfTextAnnotationWidget) {
const styled = new pdfModule.PdfStyledAnnotationWidget(target.H);
content += `内容:${styled.Text}\r\n`;
const annot = new pdfModule.PdfAnnotation(target.H);
content += `修改时间:${annot.ModifiedDate.toString()}\r\n`;
content += `名称:${annot.Name}\r\n`;
const markup = new pdfModule.PdfMarkUpAnnotationWidget(target.H);
content += `作者:${markup.Author}\r\n`;
} else {
content += '该注释不是文本注释。\r\n';
}
// 定义输出文件名并释放文档
const outputFileName = '指定注释信息.txt';
doc.Close();
// 将内容写入 VFS 并触发下载
window.dotnetRuntime.Module.FS.writeFile(outputFileName, content);
const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
const blob = new Blob([fileArray], { type: 'text/plain' });
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={extractSpecificAnnotation}>
开始提取
</button>
</div>
);
}
export default App;
提取单条注释得到的内容、作者、名称与修改时间:

提取文档中所有的注释
Spire.PDF for JavaScript 还提供按序遍历注释集合的方式,用于一次导出整页的注释。文本注释会附带一条弹出式(popup)子注释,内容存放在文本注释上,遍历时按类型跳过弹出式,同一处批注就不会被记录两次。
function App() {
const extractAllAnnotations = 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 文档
const doc = new pdfModule.PdfDocument();
doc.LoadFromFile(inputFileName);
let content = '';
// 逐页遍历文档中的注释
for (let p = 0; p < doc.Pages.Count; p++) {
const annotations = doc.Pages.get_Item(p).Annotations;
for (let i = 0; i < annotations.Count; i++) {
const item = annotations.get_Item(i);
// 文本注释会派生一条弹出式子注释,内容在文本注释上,跳过弹出式避免重复
if (item instanceof pdfModule.PdfPopupAnnotationWidget) {
continue;
}
const annot = new pdfModule.PdfAnnotation(item.H);
content += `内容:${annot.Text}\r\n`;
content += `修改时间:${annot.ModifiedDate.toString()}\r\n\r\n`;
}
}
// 定义输出文件名并释放文档
const outputFileName = '全部注释信息.txt';
doc.Close();
// 将内容写入 VFS 并触发下载
window.dotnetRuntime.Module.FS.writeFile(outputFileName, content);
const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
const blob = new Blob([fileArray], { type: 'text/plain' });
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={extractAllAnnotations}>
开始提取
</button>
</div>
);
}
export default App;
遍历整份文档后逐条汇总的注释内容与修改时间(弹出式子注释已跳过)

常见问题
为什么遍历出的注释条数比页面上看到的多
原因:每条文本注释(便签)在 PDF 里由两个对象组成——注释本身和一条弹出式(popup)子注释,两者是父子关系,内容只存在父对象上。Annotations 集合会把两者都列出来,于是同一个批注被数了两次。
解决:遍历时判断类型并跳过弹出式注释:
// 弹出式子注释的内容为空,跳过它即可避免重复
if (item instanceof pdfModule.PdfPopupAnnotationWidget) {
continue;
}
按索引取到的注释为什么读不到内容和作者
原因:集合里的顺序不一定和你在阅读器里看到的一致,索引 0 可能指向一条弹出式子注释,或者这条注释本来就不是文本注释(比如链接、图章),自然取不到 Author、Name 这些字段。
解决:先用 instanceof 确认类型再取值;要定位某一条批注,建议遍历集合、按内容或作者筛选,而不是写死索引:
if (target instanceof pdfModule.PdfTextAnnotationWidget) {
const markup = new pdfModule.PdfMarkUpAnnotationWidget(target.H);
console.log(markup.Author);
}
输入文档没有任何注释时结果为空
原因:文档本身没有注释时 Annotations.Count 为 0,遍历一次都不会进入循环,导出的 txt 自然是空的——这不是读取失败,而是文档里确实没有可提取的内容。
解决:取值前先判断集合是否为空,给调用方一个明确的提示:
const annotations = doc.Pages.get_Item(0).Annotations;
if (annotations.Count === 0) {
alert('该文档没有注释');
return;
}
获取免费许可证
如果您希望删除结果文档中的评估消息,或者摆脱功能限制,请该Email地址已收到反垃圾邮件插件保护。要显示它您需要在浏览器中启用JavaScript。获取有效期 30 天的临时许可证。









