我们来详细解释一下在 PDF.js 生态中如何处理“添加注释”以及 annotations.contents
属性。
核心要点:PDF.js 本身主要是阅读器,不是编辑器
首先,最重要的一点是:PDF.js 的核心库 (pdfjs-dist
) 主要设计用于解析和渲染(显示)PDF 文件,它本身并不提供直接修改 PDF 文件内容(包括添加、删除或修改注释并将其永久保存回原始 PDF 文件)的内置功能。
当你看到 PDF.js 的演示查看器(Viewer)允许你添加高亮、文本注释、绘图等时,这些操作通常是在浏览器层面实现的:
- 交互式添加:用户通过 UI 工具在 PDF 的渲染层之上进行绘制或输入。
- 临时存储:这些新创建的注释信息(类型、位置、颜色、文本内容等)通常被存储在浏览器中(例如,使用浏览器的
localStorage
或一个专门的AnnotationStorage
对象),或者只是存在于当前的会话内存中。 - 渲染叠加:查看器将这些存储的注释信息在相应的页面上渲染出来,看起来就像它们是 PDF 的一部分。
- 保存(可选,通常需要额外实现):要将这些注释永久保存到 PDF 文件中,需要一个额外的步骤,通常涉及:
- 将注释数据发送到服务器。
- 服务器使用一个能够修改 PDF 文件的库(例如 Node.js 的
pdf-lib
,Java 的 iText/PDFBox,Python 的 PyPDF2/ReportLab 等)来解析原始 PDF,将新的注释对象按照 PDF 规范添加到相应的页面字典中,然后生成一个新的、包含注释的 PDF 文件。 - 或者,在客户端使用像
pdf-lib
这样的库直接在浏览器中修改 PDF(这可能对性能要求较高,且需要用户下载修改后的新文件)。
关于 annotations.contents
-
读取时: 当你使用
page.getAnnotations()
获取已存在于 PDF 文件中的注释时,contents
属性是 PDF 规范中定义的注释字典(Annotation Dictionary)里的/Contents
键对应的值。这通常用于存储:- 文本注释(Sticky Note,类型为 ‘Text’)的弹出窗口中显示的文本。
- 自由文本注释(Free Text,类型为 ‘FreeText’)框中显示的文本。
- 某些其他注释类型可能用它来存储描述性文本。
// (假设你已经获取了 page 对象) const annotations = await page.getAnnotations(); annotations.forEach(anno => {if (anno.subtype === 'Text' || anno.subtype === 'FreeText') {// 读取已存在注释的 contentsconsole.log(`注释类型: ${anno.subtype}, 内容: ${anno.contents}`);}// 其他类型的注释可能没有 'contents' 或其含义不同 });
-
添加时(在 Viewer 或通过外部库): 当你想要添加一个新的文本类注释时,你需要设置这个
contents
属性为你希望注释包含的文本内容。- 在 PDF.js Viewer 中:当你使用文本工具添加注释并输入文字时,查看器内部的逻辑会将你输入的文字赋值给它正在创建或管理的注释对象的
contents
属性(以及其他必要的属性如rect
,subtype
,color
等)。 - 使用外部库(如
pdf-lib
)添加时:你需要手动构建一个符合 PDF 规范的注释字典对象,并在其中包含/Contents
键(在 JavaScript 对象中通常是contents
属性),然后将这个字典添加到页面的/Annots
数组中。
- 在 PDF.js Viewer 中:当你使用文本工具添加注释并输入文字时,查看器内部的逻辑会将你输入的文字赋值给它正在创建或管理的注释对象的
示例:使用 pdf-lib
在浏览器或 Node.js 中添加带 contents
的文本注释(概念性)
这个例子不是使用 PDF.js,而是展示了如何用一个能够修改 PDF 的库 (pdf-lib
) 来完成这个任务,这通常是实现“永久添加注释”所需要的方法。
// 需要先安装 pdf-lib: npm install pdf-lib
import { PDFDocument, rgb, StandardFonts } from 'pdf-lib';async function addTextAnnotation(inputPdfBytes, pageIndex, textContent, rect) {// 加载 PDF 文档const pdfDoc = await PDFDocument.load(inputPdfBytes);const pages = pdfDoc.getPages();const targetPage = pages[pageIndex]; // 获取要添加注释的页面 (0-based index)// 获取页面的尺寸,用于可能的坐标计算const { width, height } = targetPage.getSize();// 准备字体 (对于 FreeText 可能需要)const helveticaFont = await pdfDoc.embedFont(StandardFonts.Helvetica);// 创建一个文本注释 (Sticky Note / Popup)// 注意:添加注释通常涉及创建注释本身和可能的弹出窗口 (Popup)// 这里简化,仅展示核心概念 - pdf-lib 可能有更高级的 API// 需要查阅 pdf-lib 文档以获取创建特定注释类型的准确方法// 假设我们要创建一个 'Text' (Sticky Note) 注释// 1. 定义注释的外观字典 (Appearance Dictionary - /AP) - 这部分复杂,pdf-lib 可能简化了它// 2. 创建注释字典const textAnnotationDict = pdfDoc.context.obj({Type: 'Annot', // PDF 对象类型Subtype: 'Text', // 注释子类型:文本注释 (Sticky Note)Rect: rect, // 注释在页面上的位置 [lowerLeftX, lowerLeftY, upperRightX, upperRightY]Contents: textContent, // <--- 设置注释的文本内容C: [1, 1, 0], // 颜色 (RGB, e.g., Yellow)T: '作者名称', // 标题 (可选)M: new Date().toISOString(), // 修改日期 (可选)Name: 'Comment', // 图标名称 (e.g., 'Comment', 'Note')Open: false, // 初始状态是否打开 Popup (通常 false)// 可能还需要 /P (页面引用), /Popup (关联的弹出窗口对象) 等});// 将注释字典添加到页面的 /Annots 数组// pdf-lib 提供了更方便的方法来添加注释,而不是直接操作字典:// (请查阅 pdf-lib 文档,以下为示意)targetPage.node.addAnnot(textAnnotationDict); // 这行是示意,具体 API 可能不同// 另存为新的 PDF 文件const pdfBytes = await pdfDoc.save();// 返回修改后的 PDF 文件字节流 (Uint8Array)return pdfBytes;
}// --- 使用示例 (假设在浏览器中) ---
async function handleFileSelect(event) {const file = event.target.files[0];if (!file) return;const reader = new FileReader();reader.onload = async (e) => {const inputPdfBytes = new Uint8Array(e.target.result);try {const newPdfBytes = await addTextAnnotation(inputPdfBytes,0, // 添加到第一页 (index 0)'这是我用 pdf-lib 添加的注释内容!', // 设置 contents[50, 700, 200, 750] // 注释的位置 [x1, y1, x2, y2] (从左下角算起));// 让用户下载修改后的 PDFconst blob = new Blob([newPdfBytes], { type: 'application/pdf' });const link = document.createElement('a');link.href = URL.createObjectURL(blob);link.download = 'annotated_document.pdf';link.click();URL.revokeObjectURL(link.href);} catch (error) {console.error("添加注释失败:", error);}};reader.readAsArrayBuffer(file);
}
总结
- 使用 PDF.js 的
page.getAnnotations()
可以读取 PDF 文件中已有注释的contents
属性。 - PDF.js 的核心库不能直接用于添加注释并永久保存到 PDF 文件中。
- PDF.js 的查看器可以在界面上创建和显示注释,但这些注释默认是临时存储的(如
localStorage
),需要额外的工作才能将其永久保存到 PDF 文件中。 - 要永久添加注释(包括设置
contents
),你需要:- 要么将注释数据发送到服务器,使用服务器端的 PDF 修改库来处理。
- 要么在客户端使用像
pdf-lib
这样的 JavaScript PDF 修改库来直接操作 PDF 文件字节流,然后生成一个新的、包含注释的 PDF 文件供用户下载。
- 当你使用这些修改库添加注释时,你需要按照 PDF 规范构建注释对象,并将所需的文本内容赋值给
contents
属性(或 PDF 字典中的/Contents
键)。