PDF.js 生态中如何处理“添加注释\添加批注”以及 annotations.contents 属性
我们来详细解释一下在 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键)。
相关文章:
PDF.js 生态中如何处理“添加注释\添加批注”以及 annotations.contents 属性
我们来详细解释一下在 PDF.js 生态中如何处理“添加注释”以及 annotations.contents 属性。 核心要点:PDF.js 本身主要是阅读器,不是编辑器 首先,最重要的一点是:PDF.js 的核心库 (pdfjs-dist) 主要设计用于解析和渲染…...
MySQL性能调优(三):MySQL中的系统库(简介、performance_schema)
文章目录 MySQL性能调优数据库设计优化查询优化配置参数调整硬件优化 1.MySQL中的系统库1.1.系统库简介1.2.performance_schema1.2.1.什么是performance_schema1.2.2.performance_schema使用1.2.3.检查当前数据库版本是否支持1.2.4.performance_schema表的分类1.2.5.performanc…...
【Python语言基础】22、异常处理
文章目录 1. 异常1.1 简介1.2 为什么需要异常处理 2. 基本语法2.1 各部分详解 3. 异常处理流程3.1 执行try代码块3.2 异常发生检查3.3 异常捕获与匹配3.4 执行匹配的 except 代码块3.5 执行 else 代码块(可选)3.6 执行 finally 代码块(可选&a…...
印度zj游戏出海代投本土网盟广告核心优势
印度游戏出海代投本土网盟广告的核心优势包括: 本土化广告策略:针对印度市场的特点,定制本土化的广告策略,吸引更多印度用户的关注和参与。 深度了解印度市场:对印度文化、消费习惯、网络使用习惯等有深入了解&#x…...
NO.97十六届蓝桥杯备战|数论板块-最大公约数和最小公倍数|欧几里得算法|秦九韶算法|小红的gcd(C++)
约数和倍数 如果a 除以b 没有余数,那么a 就是b 的倍数,b 就是a 的约数,记作b ∣ a 。 约数,也称因数。 最⼤公约数和最⼩公倍数 最⼤公约数Greatest Common Divisor,常缩写为gcd。 ⼀组整数的公约数,是…...
《软件设计师》复习笔记(11.6)——系统转换、系统维护、系统评价
目录 一、遗留系统(Legacy System) 定义: 特点: 演化策略(基于价值与技术评估): 高水平 - 低价值: 高水平 - 高价值: 低水平 - 低价值: 低水平 - 高价…...
ROS机器人一般用哪些传感器?
以下是ROS机器人常用传感器的分层详解及思维导图总结,涵盖传感器分类、核心参数、ROS支持及典型应用: 一、环境感知传感器 1. 视觉传感器 类型 原理 ROS支持 数据类型 典型型号/驱动 优缺点及应用场景 单目摄像头 单镜头成像,通过透视变换获取2D图像,依赖算法推断深度 驱…...
嵌入式linux架构理解(宏观理解)6ull学习心得---从架构理解到自写程序运行及自写程序开机自启动
一、linux系统的三个组成部分 U-Boot、Linux kernel 和 rootfs 这三者一起构成了一个完整的 Linux 系 统,一个可以正常使用、功能完善的 Linux 系统。 1.在移植 Linux之前我们需要先移植一个 bootloader 代码,这个 bootloader 代码用于启动 Linux 内核,bootloader有很多,常…...
人像面部关键点检测
此工作为本人近期做人脸情绪识别,CBAM模块前是否能加人脸关键点检测而做的尝试。由于创新点不是在于检测点的标注,而是CBAM的改进,因此,只是借用了现成库Dilb与cv2进行。 首先,下载人脸关键点预测模型:Index of /file…...
面试算法高频08-动态规划-02
动态规划练习题 题目描述 给定两个字符串 text1 和 text2,要求返回这两个字符串的最长公共子序列。例如对于字符串 “ABAZDC” 和 “BACBAD”,需找出它们最长的公共子序列。子序列是指在不改变其余字符相对位置的情况下,从原始字符串中删除…...
PyTorch逻辑回归总结
目录 PyTorch逻辑回归总结神经网络基础基本结构学习路径 线性回归简单线性回归多元线性回归 逻辑回归核心原理损失函数 梯度下降法基本思想关键公式学习率影响 PyTorch实现数据准备模型构建代码优化 核心概念对比 PyTorch逻辑回归总结 神经网络基础 基本结构 输入节点隐藏节…...
使用 Vue 开发登录页面的完整指南
一、项目搭建与基础配置 环境准备 使用 Vue CLI 或 Vite 创建项目,推荐组合:Vue3 Element Plus Vue Router npm create vuelatest npm install element-plus element-plus/icons-vue vue-router 全局配置(main.js) import { c…...
EDID结构
EDID DDC通讯中传输显示设备数据 VGA , DVI 的EDID由128字节组成,hdmi的EDID增加扩展块128字节。扩展快的内容主要是和音频属性相关的,DVI和vga没有音频,hdmi自带音频,扩展快数据规范按照cea-861x标准。 Edid为了让pc或其他的图像…...
文件包含(详解)
文件包含漏洞是一种常见的Web安全漏洞,其核心在于应用程序未对用户控制的文件路径或文件名进行严格过滤,导致攻击者能够包含并执行任意文件(包括本地或远程恶意文件)。 1. 文件包含原理 动态文件包含机制 开发者使用动态包含函数…...
《SpringBoot中@Scheduled和Quartz的区别是什么?分布式定时任务框架选型实战》
🌟 大家好,我是摘星! 🌟 今天为大家带来的是Scheduled和Quartz对比分析: 新手常见困惑: 刚学SpringBoot时,我发现用Scheduled写定时任务特别简单。但当我看到同事在项目里用Quartz时&…...
安装fvm可以让电脑同时管理多个版本的flutter、flutter常用命令、vscode连接模拟器
打开 PowerShellfvm安装 dart pub global activate fvm安装完成后,如果显示FVM无法识别,那么需要去添加环境变量path添加这个:C:\Users\Administrator\AppData\Local\Pub\Cache\bin 常用命令 fvm releases 查看用户可以装的flutter版本fvm l…...
UNION和UNION ALL的主要区别
UNION和UNION ALL的主要区别在于处理重复数据和排序的方式。 UNION和UNION ALL都是SQL语言中用于合并两个或多个SELECT语句结果集的关键字。它们的主要区别如下: 1、对重复结果的处理:UNION在进行表链接后会筛选掉重复的记录,而UNION ALL不会…...
Kafka系列之:计算kafka集群topic占的存储大小
Kafka系列之:计算kafka集群topic占的存储大小 topic存储数据格式统计topic存储大小定时统计topic存储大小topic存储数据格式 单位是字节大小 size_bytes{directory="/data/datum/kafka/optics-all" } 782336计算topic存储大小脚本逻辑是: 计算指定目录或文件的大小…...
[密码学实战]Java使用Bouncy Castle实现Base64编码解码:完整指南
Java使用Bouncy Castle实现Base64编码解码:完整指南 摘要 本文将深入讲解如何通过Bouncy Castle(BC)加密库实现Base64编码解码,包含核心API使用、流式处理、与加密算法集成三大实战场景,提供5种代码实现方案和3种性能优化技巧。 一、Base64基础原理 1.1 编码机制 Bas…...
智谱AI大模型免费开放:开启AI创作新时代
文章摘要:近日,国内领先的人工智能公司智谱AI宣布旗下多款大模型服务免费开放,这一举措标志着大模型技术正式迈入普惠阶段。本文将详细介绍智谱AI此次开放的GLM-4 等大模型,涵盖其主要功能、技术特点、使用步骤以及应用场景&#…...
为什么要给单片机植入操作系统
给单片机植入操作系统(通常是实时操作系统,RTOS)主要是为了在资源有限的环境中实现更高效、更可靠的多任务管理和系统调度。以下是主要原因和优势: 1. 多任务并行处理 背景:单片机通常需要同时处理多个任务࿰…...
T1结构像+RS-fMRI影像处理过程记录(数据下载+Matlab工具箱+数据处理)
最近需要仿真研究T1结构像RS-fMRI影像融合处理输出目标坐标的路线可行性。就此机会记录下来。 为了完成验证目标处理,首先需要有数据,然后需要准备对应的处理平台和工具箱,进行一系列。那么开始记录~ 前言: 为了基于种子点的功能连…...
【前端基础】--- HTML
个人主页 : 9ilk 专栏 : 前端基础 文章目录 🏠 初识HTML🏠 HTML结构认识HTML标签HTML文件基本结构标签层次结构快速生成代码框架 🏠 HTML常见标签注释标签标题标签 h1-h6段落标签 p换行标签 br格式化标签图片标签 img超链接标签…...
黑马V11版 最新Java高级软件工程师课程-JavaEE精英进阶课
课程大小:60.2G 课程下载:https://download.csdn.net/download/m0_66047725/90615581 更多资源下载:关注我 阶段一 中台战略与组件化开发专题课程 阶段二 【物流行业】品达物流TMS 阶段三 智牛股 阶段四 千亿级电商秒杀解决方案专题 …...
C#插件与可扩展性
外接程序为主机应用程序提供了扩展功能或服务。.net framework提供了一个编程模型,开发人员可以使用该模型来开发加载项并在其主机应用程序中激活它们。该模型通过在主机和外接程序之间构建通信管道来实现此目的。该模型是使用: System.AddIn, System.AddIn.Hosting, System.…...
CVPR‘25 | 高文字渲染精度的商品图文海报生成
本文分享阿里妈妈智能创作与AI应用团队在图文广告创意方向上提出的商品图文海报生成模型,通过构建字符级视觉表征作为控制信号,可以实现精准的图上中文逐像素生成。基于该项工作总结的论文已被 CVPR 2025录用,并在阿里妈妈业务场景落地&#…...
Golang|抽奖相关
文章目录 抽奖核心算法生成抽奖大转盘抽奖接口实现 抽奖核心算法 我们可以根据 单商品库存量/总商品库存量 得到每个商品被抽中的概率,可以想象这样一条 0-1 的数轴,数轴上的每一段相当于一种商品,概率之和为1。 抽奖时,我们会生…...
RT-Thread开发文档合集
瑞萨VisionBoard开发实践指南 RT-Thread 文档中心 RT-Thread-【RA8D1-Vision Board】 RA8D1 Vision Board上的USB实践RT-Thread问答社区 - RT-Thread 【开发板】环境篇:05烧录工具介绍_哔哩哔哩_bilibili 【RA8D1-Vision Board】基于OpenMV 实现图像分类_哔哩哔哩_…...
rulego-server是一个开源程序,是一个轻量级、无依赖性的工作流自动化平台。支持 iPaaS、流式计算和 AI 能力。
一、软件介绍 文末提供程序和源码下载学习 RuleGo-Server 是一个基于 RuleGo 构建的轻量级、高性能、模块化和集成友好的自动化工作流程平台。可用于自动化编排、iPaaS(集成平台即服务)、API 编排、应用编排、AI 编排、数据处理、IoT 规则引擎、AI 助手…...
鸿蒙系统开发状态更新字段区别对比
在鸿蒙系统开发中,状态管理是构建响应式UI的核心机制,主要通过装饰器(Decorators)实现字段的状态观测与更新。根据鸿蒙的版本(V1稳定版和V2试用版),支持的装饰器及其特性有所不同。以下是主要状…...
