PDF.js 生态中如何处理“添加注释\添加批注”以及 annotations.contents 属性

我们来详细解释一下在 PDF.js 生态中如何处理"添加注释"以及 annotations.contents 属性。

核心要点:PDF.js 本身主要是阅读器,不是编辑器

首先,最重要的一点是:PDF.js 的核心库 (pdfjs-dist) 主要设计用于解析和渲染(显示)PDF 文件,它本身并不提供直接修改 PDF 文件内容(包括添加、删除或修改注释并将其永久保存回原始 PDF 文件)的内置功能。

当你看到 PDF.js 的演示查看器(Viewer)允许你添加高亮、文本注释、绘图等时,这些操作通常是在浏览器层面实现的:

  1. 交互式添加:用户通过 UI 工具在 PDF 的渲染层之上进行绘制或输入。
  2. 临时存储 :这些新创建的注释信息(类型、位置、颜色、文本内容等)通常被存储在浏览器中(例如,使用浏览器的 localStorage 或一个专门的 AnnotationStorage 对象),或者只是存在于当前的会话内存中。
  3. 渲染叠加:查看器将这些存储的注释信息在相应的页面上渲染出来,看起来就像它们是 PDF 的一部分。
  4. 保存(可选,通常需要额外实现) :要将这些注释永久 保存到 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')框中显示的文本。
    • 某些其他注释类型可能用它来存储描述性文本。
    javascript 复制代码
    // (假设你已经获取了 page 对象)
    const annotations = await page.getAnnotations();
    annotations.forEach(anno => {
      if (anno.subtype === 'Text' || anno.subtype === 'FreeText') {
        // 读取已存在注释的 contents
        console.log(`注释类型: ${anno.subtype}, 内容: ${anno.contents}`);
      }
      // 其他类型的注释可能没有 'contents' 或其含义不同
    });
  • 添加时(在 Viewer 或通过外部库) : 当你想要添加 一个新的文本类注释时,你需要设置这个 contents 属性为你希望注释包含的文本内容。

    • 在 PDF.js Viewer 中 :当你使用文本工具添加注释并输入文字时,查看器内部的逻辑会将你输入的文字赋值给它正在创建或管理的注释对象的 contents 属性(以及其他必要的属性如 rect, subtype, color 等)。
    • 使用外部库(如 pdf-lib)添加时 :你需要手动构建一个符合 PDF 规范的注释字典对象,并在其中包含 /Contents 键(在 JavaScript 对象中通常是 contents 属性),然后将这个字典添加到页面的 /Annots 数组中。

示例:使用 pdf-lib 在浏览器或 Node.js 中添加带 contents 的文本注释(概念性)

这个例子不是使用 PDF.js ,而是展示了如何用一个能够修改 PDF 的库 (pdf-lib) 来完成这个任务,这通常是实现"永久添加注释"所需要的方法。

javascript 复制代码
// 需要先安装 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] (从左下角算起)
      );

      // 让用户下载修改后的 PDF
      const 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 键)。
相关推荐
Dovis(誓平步青云)24 分钟前
《QT学习第四篇:常见事件与UDP、TCP、文件系统、(锁、信号量、条件变量》
c语言·开发语言·汇编·qt
小陈同学呦8 小时前
前端如何处理订单状态导航的数据竞态问题
前端·javascript
开发者每周简报8 小时前
网海三部曲·无名宗师传
javascript·人工智能
isyangli_blog9 小时前
OpenDayLight (Carbon 版本) 启动与组件安装
开发语言·php
vb2008119 小时前
FastAPI APIRouter
开发语言·python
Benszen9 小时前
KVM虚拟化解决方案
开发语言·perl
会编程的土豆9 小时前
Go 语言反射(Reflection)详解
开发语言·后端·golang
東雪木9 小时前
多线程与并发编程 专属复习笔记
java·开发语言·笔记·java面试
杨充10 小时前
1.3 浮点型数据设计灵魂
开发语言·python·算法
噜噜噜阿鲁~10 小时前
python学习笔记 | 11.3、面向对象高级编程-多重继承
java·开发语言