我們來詳細解釋一下在 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
鍵)。