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’)框中显示的文本。
    • 某些其他注释类型可能用它来存储描述性文本。
    // (假设你已经获取了 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-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 键)。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/79672.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

当HTTP遇到SQL注入:Java开发者的攻防实战手册

一、从HTTP请求到数据库查询:漏洞如何产生? 危险的参数拼接:Servlet中的经典错误 漏洞代码重现: public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {String category = request.getParameter("…

【grafana原生告警中心配置飞书机器人告警】

在grafana中的connect point中使用webhook的方式推送到飞书&#xff0c;始终无法触发告警&#xff0c;原因是grafana推送的格式飞书不识别&#xff0c;现有两种方式 1.使用中转服务 使用flask搭建一个服务&#xff0c;grafana告警先通过webhook发送到web服务中&#xff0c;格…

kafka集群认证

1、安装Kerberos(10.10.10.168) yum install krb5-server krb5-workstation krb5-libs -y ​ 查看版本 klist -V ​ Kerberos 5 version 1.20.1 ​ 编辑/etc/hosts 10.10.10.168 ms1 10.10.10.150 ms2 10.10.10.110 ms3 vim /etc/krb5.conf # Configuration snippets ma…

前端工程化之自动化测试

自动化测试 自动化测试为什么需要测试&#xff1f;什么时候需要考虑测试测试类型前端测试框架单元测试Jest 重点掌握项目示例package.jsonsrc/utils/math.tssrc/utils/math.test.ts进行测试jest.config.js覆盖率直观看覆盖率coverage/lcov-report/index.html src/main.test.tst…

分布式系统核心原理

CAP定理与权衡实践 CAP定理 一致性&#xff08;Consistency&#xff09; 强一致性&#xff1a;所有读写操作均基于最新数据&#xff08;如银行转账&#xff09;。 最终一致性&#xff1a;数据副本经过一段时间后达到一致&#xff08;如社交媒体的点赞数&#xff09;。 技术实现…

Step文件无法编辑怎么办?

Step文件无法编辑怎么办&#xff1f; 这里介绍两种方法&#xff0c; 1、 直接导入 准备step文件&#xff0c;solidworks导入后是这样&#xff0c;不能在上面直接编辑 图 1 点击右键&#xff0c;选择解除特征&#xff08;不同版本的可能不太一样&#xff0c;这里是solidworks2…

TIM_ITConfig() 和 TIM_Cmd()

在STM32的定时器中断配置中&#xff0c;TIM_ITConfig() 和 TIM_Cmd() 是两个关键函数&#xff0c;它们分别控制中断使能和定时器计数器的启停&#xff0c;作用层级不同。以下是详细解释&#xff1a; 1. TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE) 作用 启用定时器的特定中断…

TensorFlow 实现 Mixture Density Network (MDN) 的完整说明

本文档详细解释了一段使用 TensorFlow 构建和训练混合密度网络&#xff08;Mixture Density Network, MDN&#xff09;的代码&#xff0c;涵盖数据生成、模型构建、自定义损失函数与预测可视化等各个环节。 1. 导入库与设置超参数 import numpy as np import tensorflow as t…

数据结构实验7.2:二叉树的基本运算

文章目录 一&#xff0c;实验目的二&#xff0c;问题描述三&#xff0c;基本要求四&#xff0c;实验操作五&#xff0c;示例代码六&#xff0c;运行效果 一&#xff0c;实验目的 深入理解树与二叉树的基本概念&#xff0c;包括节点、度、层次、深度等&#xff0c;清晰区分二叉…

直线轴承常规分类知多少?

直线轴承的分类方式多样&#xff0c;以下是从材质、结构形状和常规系列三个维度进行的具体分类&#xff1a; 按主要材质分类 外壳材质&#xff1a;常见的有不锈钢&#xff0c;具有良好的耐腐蚀性&#xff0c;适用于一些对环境要求较高、易受腐蚀的工作场景&#xff1b;轴承…

websocket和SSE学习记录

websocket学习记录 websocket使用场景 即时聊天在线文档协同编辑实施地图位置 从开发角度来学习websocket开发 即使通信项目 通过node建立简单的后端接口,利用fs&#xff0c; path&#xff0c; express app.get(*, (req, res) > {const assetsType req.url.split(/)[…

CUDA编程中影响性能的小细节总结

一、内存访问优化 合并内存访问&#xff1a;确保相邻线程访问连续内存地址&#xff08;全局内存对齐访问&#xff09;。优先使用共享内存&#xff08;Shared Memory&#xff09;减少全局内存访问。避免共享内存的Bank Conflict&#xff08;例如&#xff0c;使用padding或调整访…

【双指针】对撞指针 快慢指针 移动零

文章目录 双指针介绍对撞指针快慢指针283. 移动零解题思路算法思路算法流程双指针介绍 ​ 算法中的双指针,并不一定是指我们平常在 c/c++ 使用的指针类型,更多时候其实是数组的下标等,因为它们也是有标识某个元素的功能,通常我们也就顺其自然地称其为 “指针” ! ​ 常见…

数据结构0基础学习堆

文章目录 简介公式建立堆函数解释 堆排序O(n logn)topk问题 简介 堆是一种重要的数据结构&#xff0c;是一种完全二叉树&#xff0c;&#xff08;二叉树的内容后面会出&#xff09;&#xff0c; 堆分为大小堆&#xff0c;大堆&#xff0c;左右结点都小于根节点&#xff0c;&am…

4.17--4.19刷题记录(贪心)

第一部分&#xff1a;准备工作 代码随想录中解释为&#xff1a;贪心的本质是选择每一阶段的局部最优&#xff0c;从而达到全局最优。 而我的理解为&#xff1a;贪心实质上是具有最优子结构的一种算法。所有的解都能由当前最优的解组成。 第二部分&#xff1a;开始刷题 &…

学习笔记十七——Rust 支持面向对象编程吗?

&#x1f9e0; Rust 支持面向对象编程吗&#xff1f; Rust 是一门多范式语言&#xff0c;主要以 安全、并发、函数式、系统级编程为核心目标&#xff0c;但它同时也支持面向对象的一些关键特性&#xff0c;比如&#xff1a; 特性传统 OOP&#xff08;如 Java/C&#xff09;Ru…

【Linux】43.网络基础(2.5)

文章目录 2.4 TCP/UDP对比2.4.1 用UDP实现可靠传输(经典面试题) 2.5 TCP 相关实验2.5.1 理解 listen 的第二个参数 2.4 TCP/UDP对比 我们说了TCP是可靠连接, 那么是不是TCP一定就优于UDP呢? TCP和UDP之间的优点和缺点, 不能简单, 绝对的进行比较TCP用于可靠传输的情况, 应用于…

three.js与webgl在buffer上的对应关系

一、three.js的类名 最近开始接触three.js 看到three.js中的一些类名和webgl的很相似 不自觉的就想对比一下 二、three.js中绘制4个点 // 创建点的几何体 const vertices new Float32Array([0.0, 0.0, 0.0, // 点11.0, 0.0, 0.0, // 点20.0, 1.0, 0.0, // 点30.…

DataWhale AI春训营 问题汇总

1.没用下载训练集导致出错&#xff0c;爆错如下。 这个时候需要去比赛官网下载对应的初赛训练集 unzip -d /mnt/workspace/sais_third_new_energy_baseline/data /mnt/workspace/sais_third_new_energy_baseline/初赛训练集.zip 在命令行执行这个命令解压 2.没定义测试集 te…

CANFD技术在新能源汽车通信网络中的应用与可靠性分析

一、引言 新能源汽车产业正处于快速发展阶段&#xff0c;其电子系统复杂度不断攀升&#xff0c;涵盖众多传感器、控制器与执行器。高效通信网络成为确保新能源汽车安全运行与智能功能实现的核心要素。传统CAN总线因带宽限制&#xff0c;难以满足高级驾驶辅助系统&#xff08;A…