news 2026/10/14 0:41:50

MuPDF PDFPage 类型完全解析:JavaScript API 中的页面加载、批注、编辑、去标识化与渲染实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
MuPDF PDFPage 类型完全解析:JavaScript API 中的页面加载、批注、编辑、去标识化与渲染实战
  • 图形学
  • 图像处理

【免费下载链接】mupdf

mupdf mirror

项目地址:https://gitcode.com/gh_mirrors/mu/mupdf
点击查看免费下载

本文基于 MuPDF 官方参考文档 PDFPage.rst 展开,系统讲解 MuPDF JavaScript API 中PDFPage类型的全部静态常量与实例方法,包括页面加载、批注增删改、页面盒子设置、toPixmap页面渲染、Redaction(去标识化/涂黑)两阶段流程、页面内容流处理与坐标变换等。读完后,你可以在mutool run的 JS 环境中独立完成页面级读取、编辑、渲染与敏感信息移除等完整工作流,并能对照 source/tools/murun.c 中的 FFI 绑定源码验证每个方法的底层行为。

一、PDFPage 在 MuPDF JS API 中的定位

PDFPage是 MuPDF JavaScript 绑定中代表 PDF 文档中单个页面的对象类型。根据 PDFPage.rst 的说明:

  • PDFPage继承自通用Page类型(参见 Page.rst),因此所有Page接口(如getSize、toDisplayList、run等)同样可用,PDFPage在其上叠加了 PDF 专属能力;
  • 该类型没有可直接调用的构造函数。PDFPage实例只能通过调用PDFDocument的Document.prototype.loadPage方法获得,且文档中特别注明这一点针对PDFDocument;
  • 在 C 源码层面,所有PDFPage方法都通过 FFI 形式注册到 mujs 解释器中。可以在 source/tools/murun.c 看到完整的注册表:
js_getregistry(J, "fz_page"); js_newobjectx(J); { jsB_propfun(J, "PDFPage.getObject", ffi_PDFPage_getObject, 0); jsB_propfun(J, "PDFPage.getWidgets", ffi_PDFPage_getWidgets, 0); jsB_propfun(J, "PDFPage.getAnnotations", ffi_PDFPage_getAnnotations, 0); jsB_propfun(J, "PDFPage.createAnnotation", ffi_PDFPage_createAnnotation, 1); jsB_propfun(J, "PDFPage.deleteAnnotation", ffi_PDFPage_deleteAnnotation, 1); jsB_propfun(J, "PDFPage.createSignature", ffi_PDFPage_createSignature, 0); jsB_propfun(J, "PDFPage.update", ffi_PDFPage_update, 0); jsB_propfun(J, "PDFPage.applyRedactions", ffi_PDFPage_applyRedactions, 4); jsB_propfun(J, "PDFPage.process", ffi_PDFPage_process, 1); jsB_propfun(J, "PDFPage.toPixmap", ffi_PDFPage_toPixmap, 6); jsB_propfun(J, "PDFPage.getTransform", ffi_PDFPage_getTransform, 0); jsB_propfun(J, "PDFPage.setPageBox", ffi_PDFPage_setPageBox, 2); jsB_propfun(J, "PDFPage.countAssociatedFiles", ffi_PDFPage_countAssociatedFiles, 0); jsB_propfun(J, "PDFPage.associatedFile", ffi_PDFPage_associatedFile, 1); jsB_propfun(J, "PDFPage.clip", ffi_PDFPage_clip, 1); } js_setregistry(J, "pdf_page");

这个注册表同时说明了各方法的实参个数,是理解"哪些参数可省略"的可靠依据。加载页面的入口实现在 source/tools/murun.c 的ffi_Document_loadPage中,内部调用fz_load_page,页码从 0 开始。一个典型的获取实例的写法是:

var doc = mupdf.Document.openDocument("document.pdf", null) var page = doc.loadPage(0) // 返回 PDFPage 实例

二、读取页面结构与对象:getObject、getAnnotations、getWidgets

getObject

PDFPage.prototype.getObject()返回该页面对应的底层PDFObject(即 PDF 中 Page 字典对象),是页面级"逃生舱口"——拿到它之后即可通过PDFObject字典接口直接读写任意键值,参见 PDFObject.rst。

var obj = page.getObject()

源码实现见 source/tools/murun.c:ffi_PDFPage_getObject直接取出pdf_page结构体中的page->obj并以pdf_keep_obj保留引用,因此返回的对象与内部页面保持同一生命周期。

getAnnotations

返回页面上所有批注组成的数组(元素类型为PDFAnnotation)。实现(source/tools/murun.c)通过pdf_first_annot/pdf_next_annot遍历页面对象的/Annots链表,逐个构造PDFAnnotationuserdata 压入 JS 数组:

var annots = page.getAnnotations()

getWidgets

与上类似,但只返回表单控件(widget 是批注的一个子类)。实现(source/tools/murun.c)调用pdf_first_widget/pdf_next_widget,返回的数组元素类型为PDFWidget(参见 PDFWidget.rst):

var widgets = page.getWidgets()

批注与控件的完整属性、坐标、内容、矩形等操作,可继续查阅 PDFAnnotation.rst。

三、批注的创建、删除与更新

createAnnotation(type)

创建指定类型的空白批注,type为 PDF 规范的批注类型字符串(如"Text"、"Highlight"、"Redaction"等),返回PDFAnnotation:

var annot = page.createAnnotation("Text")

从源码看(source/tools/murun.c),实现先用pdf_annot_type_from_string把字符串解析为枚举(解析失败时得到PDF_ANNOT_UNKNOWN),再调用 C 层的pdf_create_annot(ctx, page, type)。也就是说,创建批注这一步同时完成了"构造批注对象 + 挂到页面 /Annots"两件事。

deleteAnnotation(annot)

从页面删除一个PDFAnnotation,内部调用pdf_delete_annot(source/tools/murun.c):

var annots = page.getAnnotations() page.deleteAnnotation(annots[0])

注意原参考文档示例中写作page.delete(annots[0]),这是笔误,实际注册的方法名是deleteAnnotation(见上文注册表)。

update()

遍历页面上所有批注并执行更新,返回值是 boolean:只要有任何一个批注因事件、JavaScript 动作或人工编辑而发生了变化,就返回true,提示宿主程序需要重新渲染该页。底层对应pdf_update_page(source/tools/murun.c):

var updated = page.update()

在交互式编辑器中,这通常与PDFAnnotation的edit/事件回调配合使用:编辑结束 -> 调page.update()-> 根据返回值决定是否触发重绘。

四、设置页面盒子:setPageBox(box, rect)

PDFPage.prototype.setPageBox(box, rect)用于修改页面某一种 box 的尺寸。box是页面盒子类型名,rect是新的期望尺寸(Rect):

page.setPageBox("ArtBox", [10, 10, 585, 832])

合法的 box 名称取值。从实现看(source/tools/murun.c 调用fz_box_type_from_string),名称解析在 source/fitz/document.c 中完成,大小写不敏感,仅支持以下五种:

box 名称含义
MediaBox页面实际介质(纸张/画布)大小
CropBox显示、打印、绘图所用的默认可见区域
BleedBox印刷出血区域
TrimBox成品裁切线区域
ArtBox艺术内容区域(如封面设计区)

源码中若名称无法识别会得到FZ_UNKNOWN_BOX,FFI 层随即抛出"invalid page box name"错误,因此传入非法名称不会静默成功。确认合法后再写入 C 层pdf_set_page_box。

五、渲染页面为像素图:toPixmap(matrix, colorspace, alpha, renderExtra, usage, box)

toPixmap将页面渲染成Pixmap位图,是 JS 侧生成缩略图、做预处理(deskew、OCR 前处理等)的核心入口:

var pixmap = page.toPixmap( mupdf.Matrix.identity, // 变换矩阵 mupdf.ColorSpace.DeviceRGB, // 目标颜色空间 true, // 是否带 alpha 通道 false, // 是否渲染批注与控件 "View", // 用途:View 或 Print "CropBox" // 使用的页面盒子 )

参数完整说明(含默认值):

参数类型说明默认值
matrixMatrix从页面坐标到输出像素的变换矩阵无
colorspaceColorSpace输出像素的颜色空间,如DeviceRGB、DeviceCMYK无
alphaboolean输出的 Pixmap 是否包含 alpha 通道无
renderExtraboolean是否渲染批注与表单控件true
usagestring输出目标场景,"View"或"Print""View"
boxstring使用的页面盒子名"CropBox"

从源码实现(source/tools/murun.c)可以进一步看清两个细节:

  1. 默认值:renderExtra未传时按1(true)处理,usage未传时按"View",box未传时按FZ_CROP_BOX——与文档声明一致;
  2. 渲染路径分流:renderExtra为true时调用pdf_new_pixmap_from_page_with_usage(页面内容 + 批注 + 控件一起渲染);为false时改走pdf_new_pixmap_from_page_contents_with_usage(只渲染页面内容流,跳过批注层)。这两个 C 函数声明于 include/mupdf/pdf/page.h。

usage参数对应 PDF 的 Usage 特性:设为"Print"时,仅当批注带有Print标志时才会出现在输出中,这与mudraw等工具的--print行为一致。

六、敏感信息移除:applyRedactions 两阶段流程

静态常量

PDFPage带有一组用于 Redaction(去标识化,俗称"涂黑")的静态常量(文档标注为 MuPDF.js 平台可用,WASM 绑定 platform/wasm/lib/mupdf.ts 中也定义了同样的取值)。各常量与 C 层枚举一一对应,枚举定义及权威注释在 include/mupdf/pdf/page.h:

常量值语义
PDFPage.REDACT_IMAGE_NONE0完全不处理图片
PDFPage.REDACT_IMAGE_REMOVE1图片与涂黑区域相交(即使被裁剪)则整张移除
PDFPage.REDACT_IMAGE_PIXELS2仅把侵入涂黑区域的像素替换为黑色
PDFPage.REDACT_IMAGE_UNLESS_INVISIBLE3仅在图片可见部分侵入涂黑区域时整张移除;源码注释提示该方式在去标识化阶段可能"泄漏"不可见信息,需谨慎使用
PDFPage.REDACT_LINE_ART_NONE0不处理线条图形(line art)
PDFPage.REDACT_LINE_ART_REMOVE_IF_COVERED1线条被涂黑区域覆盖时移除
PDFPage.REDACT_LINE_ART_REMOVE_IF_TOUCHED2线条只要与涂黑区域相交即移除
PDFPage.REDACT_TEXT_REMOVE0移除与涂黑区域相交的文字(默认,安全行为)
PDFPage.REDACT_TEXT_NONE1不移除任何文字——源码明确警告这样做是不安全的

此外 C 枚举中还定义了PDF_REDACT_TEXT_REMOVE_INVISIBLE(移除重叠区域中的不可见文字层,常用于清理 OCR 残留层),但 JS 文档当前只列出上述 9 个常量,使用时以文档列出的为准。

applyRedactions(blackBoxes, imageMethod, lineArtMethod, textMethod)

方法签名与默认值:

参数类型说明默认值
blackBoxesboolean是否在涂黑位置保留黑底框—
imageMethodnumber图片处理方式REDACT_IMAGE_PIXELS
lineArtMethodnumber线条处理方式REDACT_LINE_ART_REMOVE_IF_COVERED
textMethodnumber文字处理方式REDACT_TEXT_REMOVE

Redaction 是一种特殊批注,用于永久删除PDF 内容。整个流程是不可逆的两步操作:先创建Redaction批注圈定范围,再调用applyRedactions真正执行删除:

var annot1 = page.createAnnotation("Redaction") annot1.setRect([0, 0, 500, 100]) var annot2 = page.createAnnotation("Redaction") annot2.setRect([0, 600, 500, 700]) page.applyRedactions(true, mupdf.PDFPage.REDACT_IMAGE_NONE)

上例中只显式传了两个参数,其余按默认值生效。FFI 实现(source/tools/murun.c)构造pdf_redact_options结构体(定义见 include/mupdf/pdf/page.h),对未定义的参数保留 C 侧默认值{ 1, PDF_REDACT_IMAGE_PIXELS, 0, 0 },最终调用pdf_redact_page执行全页去标识化。由于applyRedactions会重写页面内容流与资源,执行后应保存文档(PDFDocument.save/Document.saveIncremental)并重新加载验证,原始内容无法从文件中恢复。

七、坐标体系与内容流处理

getTransform()

返回从 MuPDF 页面空间到 PDF 用户空间的变换。两种坐标系的关键区别(可对照 coordinate-system.md 深入理解):

  • MuPDF 页面空间:原点位于页面左上角,y 轴向下增长,单位固定为 72 dpi 下的点;
  • PDF 用户空间:原点位置任意(由页面盒子决定),y 轴向上增长,单位受UserUnit影响。
var transform = pdfPage.getTransform()

需要说明的一点:参考文档中该方法的返回类型标注为Rect,但从源码看(source/tools/murun.c),实现是调用pdf_page_transform得到fz_matrix后通过ffi_pushmatrix压栈,实际返回的是一个Matrix对象。在编写坐标换算逻辑时请以矩阵形式处理为准。

process(processor)

|only_mutool|标注表示该方法仅在mutool run环境可用。它遍历页面内容流(contents stream),把遇到的每个操作符回调给传入的PDFProcessor对象,可用于审计、转换或统计页面绘制指令:

pdfPage.process(processor)

从实现看(source/tools/murun.c),FFI 层先取pdf_page_resources与pdf_page_contents,再用pdf_process_contents驱动 C 层解释器逐条执行页面指令,PDFProcessor的回调对象(new_js_processor创建的包装)在每个操作符上被调用。PDFProcessor接口见 PDFProcessor.rst。

八、仅 mutool 可用的其他页面能力

以下三个方法在参考文档中带有|only_mutool|标记,表示只在mutool run的完整 JS 环境里注册可用:

createSignature(name)

创建一个以name作为字段标签(field label)的新签名控件,返回PDFWidget。底层调用pdf_create_signature_widget(source/tools/murun.c):

var signatureWidget = pdfPage.createSignature("test")

countAssociatedFiles() / associatedFile(n)

用于处理 PDF 的 Associated Files(关联文件,常见于 Office 导出的文档中嵌入的原始部件)。注意文档特别强调:countAssociatedFiles()返回的是本页关联的文件数,而不是整份文档的总数(文档级计数请使用PDFDocument.countAssociatedFiles,参见 PDFDocument.rst)。

var count = pdfPage.countAssociatedFiles() var obj = pdfPage.associatedFile(0) // 0 <= n < count,越界返回 null

实现分别对应 C 层的pdf_count_page_associated_files与pdf_page_associated_file(source/tools/murun.c),后者返回表示第n个关联文件的 file specification 对象,越界时返回null。

clip(rect)

把页面裁剪到给定矩形。注意clip是永久性的页面编辑,不是渲染时的一次性裁剪,执行后需要保存文档才能持久生效:

pdfPage.clip([0, 0, 200, 300])

实现调用 C 层pdf_clip_page(source/tools/murun.c)。

九、方法速查表与源码对照

下表汇总 PDFPage.rst 定义的全部实例方法及其实现在murun.c中的位置,方便按图索骥:

方法作用FFI 实现位置
getObject()取底层PDFObjectmurun.c#L9630
getAnnotations()全部批注数组murun.c#L9663
getWidgets()全部表单控件数组murun.c#L9637
setPageBox(box, rect)修改页面盒子murun.c#L9813
createAnnotation(type)创建空白批注murun.c#L9689
deleteAnnotation(annot)删除批注murun.c#L9704
update()更新全部批注,返回是否变化murun.c#L9730
toPixmap(matrix, cs, alpha, extra, usage, box)渲染页面为Pixmapmurun.c#L9775
applyRedactions(...)执行去标识化(不可逆)murun.c#L9742
process(processor)遍历内容流回调PDFProcessormurun.c#L9757
getTransform()页面空间到用户空间的变换murun.c#L9799
createSignature(name)创建签名控件(仅 mutool)murun.c#L9715
countAssociatedFiles()本页关联文件数(仅 mutool)murun.c#L9829
associatedFile(n)取第 n 个关联文件对象(仅 mutool)murun.c#L9841
clip(rect)永久裁剪页面(仅 mutool)murun.c#L9854

十、实践建议与注意事项

  1. 获取实例的唯一入口:不要尝试new PDFPage(),始终通过pdfDocument.loadPage(n)获取;页码从 0 起算。
  2. 修改后必须保存:setPageBox、createAnnotation、deleteAnnotation、clip、applyRedactions修改的都是文档对象树中的状态,执行Document.save(或增量保存)后变更才会落盘。
  3. Redaction 的不可逆性:applyRedactions会真正从内容流中删除文字、改写图片与线条,测试时应先在副本文件上执行;若需要"看起来涂黑但保留原文"的效果,请改用 Black 填充的批注而不是 Redaction。
  4. 平台差异:标注|only_mutool|的方法在mutool run中可用;Redaction 常量文档标注为 MuPDF.js 专属;在 Java/WASM 等其他绑定中对应的 API 面可能不同(例如 Java 侧见 platform/java/src/com/artifex/mupdf/fitz/PDFPage.java)。
  5. 坐标系换算:凡涉及批注矩形、裁剪矩形与页面像素的换算,先用getTransform()拿到页面空间到用户空间的矩阵(按Matrix处理),再结合toPixmap的matrix参数统一坐标,避免混用两种坐标系。
  • 图形学
  • 图像处理

【免费下载链接】mupdf

mupdf mirror

项目地址:https://gitcode.com/gh_mirrors/mu/mupdf
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/14 0:40:37

【揭秘】淘宝AI购物技术:AI替用户下单,付款链路怎么测?

摘要&#xff1a;千问与淘宝打通商品发现、比较、下单和售后后&#xff0c;AI购物不再只是“推荐商品”。本文依据阿里巴巴与支付宝公开资料&#xff0c;把一句自然语言购物需求拆成商品、报价、确认、订单、支付和异步结果六段证据&#xff0c;并给出可运行的行为断言、异常测…

作者头像 李华
网站建设 2026/10/14 0:38:15

基于SVM、随机森林与DNN的恶意网站检测系统实战

简介&#xff1a;这是一套面向高校计算机、人工智能与电子信息等专业学生的机器学习实践资源&#xff0c;围绕恶意网站检测这一网络安全场景&#xff0c;提供从特征提取到多算法验证的完整实现方案。压缩包共11个文件&#xff0c;约3.32MB&#xff0c;以Python脚本为主体&#…

作者头像 李华
网站建设 2026/10/14 0:37:53

atlthunk.dll丢失怎么办?官方修复流程与单文件下载风险详解

在使用电脑系统时经常会出现丢失找不到某些文件的情况&#xff0c;由于很多常用软件都是采用 Microsoft Visual Studio 编写的&#xff0c;所以这类软件的运行需要依赖微软Visual C运行库&#xff0c;比如像 QQ、迅雷、Adobe 软件等等&#xff0c;如果没有安装VC运行库或者安装…

作者头像 李华
网站建设 2026/10/14 0:37:47

audiodev.dll丢失怎么办?官方修复流程与单文件下载风险详解

在使用电脑系统时经常会出现丢失找不到某些文件的情况&#xff0c;由于很多常用软件都是采用 Microsoft Visual Studio 编写的&#xff0c;所以这类软件的运行需要依赖微软Visual C运行库&#xff0c;比如像 QQ、迅雷、Adobe 软件等等&#xff0c;如果没有安装VC运行库或者安装…

作者头像 李华