☰
MuPDF PDFProcessor 参考:用 JavaScript 回调遍历 PDF 页面操作符
2026/10/14 15:31:09 网站建设 项目流程
  • 图形学
  • 图像处理

【免费下载链接】mupdf

mupdf mirror

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

本文围绕 MuPDF 官方文档中的PDFProcessor类型参考展开,完整讲解这一 JavaScript 回调接口的构造方式、全部操作符回调清单(图形状态、路径、文本、颜色、XObject、标记内容等),并结合仓库中的示例脚本 pdf-trace.js 与mutool run的实现源码,说明如何基于它实现自定义的 PDF 内容流解析器。读完本文,你可以直接在mutool run中编写脚本,对任意 PDF 页面或注释外观流执行逐操作符级别的遍历、检查与定制处理。

PDFProcessor 是什么

根据类型参考文档 PDFProcessor.rst 的定义:

A PDF processor object provides callbacks that will be called for each PDF operator when it is passed toPDFAnnotation.prototype.processandPDFPage.prototype.process. The callbacks correspond to the equivalent PDF operator.

也就是说,PDFProcessor是一个由用户提供的回调对象,当它被传给PDFAnnotation.prototype.process或PDFPage.prototype.process时,MuPDF 会解析并执行目标内容流(页面对象或注释的外观流)中的每一个 PDF 操作符,并在遇到每个操作符时回调对象上与之同名的方法。回调方法名直接对应 PDF 规范中的 graphic operators(如w、cm、BT、Do等),参数语义也与 PDF 规范一致——因此熟悉 PDF 32000 规范中 graphic operators 一节的读者可以直接对照理解每个回调的含义。

它的典型应用场景包括:

  • 逐操作符“追踪/打印”页面内容流,用于调试与逆向分析(仓库示例 pdf-trace.js 正是干这件事的);
  • 自定义内容检查器,例如统计页面使用了哪些字体、颜色空间或 XObject;
  • 对注释外观流做细粒度分析,定位注释绘图中具体的绘制操作。

如何驱动 PDFProcessor

PDFProcessor本身只是回调容器,真正驱动它的是两个process方法。

PDFPage.prototype.process(processor)

类型文档 PDFPage.rst 中的说明:

Run through the page contents stream and call methods on the suppliedPDFProcessor.

pdfPage.process(processor)

即“遍历该页的内容流,对传入的PDFProcessor调用相应方法”。

PDFAnnotation.prototype.process(processor)

类型文档 PDFAnnotation.rst 中的说明:

Run through the annotation appearance stream and call methods on the suppliedPDFProcessor.

annotation.process(processor)

即“遍历该注释的外观流(AP 流),对传入的PDFProcessor调用相应方法”。两者入口不同(页面内容流 vs 注释外观流),但使用的回调接口完全相同,一个PDFProcessor实现可以同时用于两者。

运行环境

process方法属于 MuPDF 的 JavaScript 绑定(mutool run)。根据 mutool-run.md,脚本以 ECMAScript 5 严格模式执行,可通过全局scriptArgs数组访问命令行参数,并使用print(...)等 shell 函数输出结果:

mutool run script.js [ arguments ... ]

回调方法完整清单

以下是 PDFProcessor.rst 定义的全部回调,按文档分组完整继承。除构造函数与资源跟踪外,每组均对应 PDF 规范中的一类操作符。

构造函数

PDFProcessor(callbacks)

interface_type接口类型:传入一个携带各op_*回调函数的对象即可。

特殊资源跟踪(Special resource tracking)

这些方法严格来说不是操作符,而是在“当前使用的资源字典发生变化”时被调用,例如执行 XObject 表单时:

  • push_resources(resources)— 压入新的当前资源字典
  • pop_resources()— 弹出资源字典

通用图形状态(General graphics state)

  • op_w(lineWidth: number)
  • op_j(lineJoin: number)
  • op_J(lineCap: number)
  • op_M(miterLimit: number)
  • op_d(dashPattern: Array of number, phase: number)
  • op_ri(intent: string)
  • op_i(flatness: number)
  • op_gs(name: string, extGState: PDFObject)

特殊图形状态(Special graphics state)

  • op_q()
  • op_Q()
  • op_cm(a: number, b: number, c: number, d: number, e: number, f: number)

路径构造(Path construction)

  • op_m(x: number, y: number)
  • op_l(x: number, y: number)
  • op_c(x1: number, y1: number, x2: number, y2: number, x3: number, y3: number)
  • op_v(x2: number, y2: number, x3: number, y3: number)
  • op_y(x1: number, y1: number, x3: number, y3: number)
  • op_h()
  • op_re(x: number, y: number, w: number, h: number)

路径绘制(Path painting)

  • op_S()— 描边
  • op_s()— 描边并填充
  • op_F()— 非零环绕填充(非兼容操作符)
  • op_f()— 非零环绕填充
  • op_fstar()— 奇偶填充
  • op_B()— 描边填充(非兼容操作符)
  • op_Bstar()
  • op_b()
  • op_bstar()
  • op_n()— 结束路径,不绘制

裁剪路径(Clipping paths)

  • op_W()
  • op_Wstar()

文本对象(Text objects)

  • op_BT()
  • op_ET()

文本状态(Text state)

  • op_Tc(charSpace: number)
  • op_Tw(wordSpace: number)
  • op_Tz(scale: number)
  • op_TL(leading: number)
  • op_Tf(name: string, size: number)
  • op_Tr(render: number)
  • op_Ts(rise: number)

文本定位(Text positioning)

  • op_Td(tx: number, ty: number)
  • op_TD(tx: number, ty: number)
  • op_Tm(a: number, b: number, c: number, d: number, e: number, f: number)
  • op_Tstar()

文本显示(Text showing)

  • op_TJ(textArray: Array of (string | number))
  • op_Tj(stringOrByteArray: string | Array of number)
  • op_squote(stringOrByteArray: string | Array of number)
  • op_dquote(wordSpace: number, charSpace: number, stringOrByteArray: string | Array of number)

注意:文本回调的参数可能是字符串,也可能是字节数组(Array of number),回调实现必须两者都能处理——这正是编码字体与 Unicode 字体在解析层上的差异。

Type 3 字体

  • op_d0(wx: number, wy: number)
  • op_d1(wx: number, wy: number, llx: number, lly: number, urx: number, ury: number)

颜色(Color)

  • op_CS(name: string, colorspace: ColorSpace)— 设置填充颜色空间
  • op_cs(name: string, colorspace: ColorSpace)— 设置描边颜色空间
  • op_SC_color(color: Array of number)/op_sc_color(color: Array of number)— 当前颜色空间下的通用颜色
  • op_SC_pattern(name: string, patternID: number, color: Array of number)/op_sc_pattern(...)— 图案(Pattern)
  • op_SC_shade(name: string, shade: Shade)/op_sc_shade(...)— 着色器(Shading)
  • op_G(gray: number)/op_g(gray: number)
  • op_RG(r: number, g: number, b: number)/op_rg(r: number, g: number, b: number)
  • op_K(c: number, m: number, y: number, k: number)/op_k(c: number, m: number, y: number, k: number)

可以看到颜色回调按 PDF 的三种颜色表示方式拆分为*_color、*_pattern、*_shade以及设备专用(G/g、RG/rg、K/k)几组,回调时会根据实际颜色类型分发到对应方法。

着色(Shadings)

  • op_sh(name: string, shade: Shade)

内联图像(Inline images)

  • op_BI(image: Image, colorspace: ColorSpace)

XObject(图像与表单)

  • op_Do_image(name: string, image: Image)
  • op_Do_form(name: string, xobject: PDFObject, resources: PDFObject)

Do操作符按 XObject 类型拆分为图像与表单两个回调,便于区分处理。

标记内容(Marked content)

  • op_MP(tag: string)
  • op_DP(tag: string, raw: string)
  • op_BMC(tag: string)
  • op_BDC(tag: string, raw: string)
  • op_EMC()

兼容性(Compatibility)

  • op_BX()
  • op_EX()

实战示例:pdf-trace.js 操作符追踪器

仓库中的 pdf-trace.js 是一个完整可用的PDFProcessor实现:它把每个回调都映射为把原始操作符及其参数按 PDF 语法格式打印出来,相当于对页面内容流做一次“可读化回放”。脚本末尾只有三行驱动代码:

var doc = Document.openDocument(scriptArgs[0]); var page = doc.loadPage(scriptArgs[1]-1); page.process(traceProcessor);

其中showString辅助函数值得注意:它对文本参数做了字符串/字节数组的双重处理——字节数组会被格式化为<hex>形式,字符串则还原为带转义的 PDF 字面量( ... ):

function showString(str) { var out, c, i, n; n = str.length; if (Array.isArray(str)) { out = '<'; for (i = 0; i < n; ++i) { c = str[i].toString(16); if (c.length == 1) out += '0'; out += c; } out += '>'; } else { out = '('; for (i = 0; i < n; ++i) { c = str[i]; if (c == '(' || c == ')' || c == '\\') out += '\\'; out += c; } out += ')'; } return out; }

处理器对象本身则以对象字面量声明全部op_*方法,例如路径与文本部分:

var traceProcessor = { // ... 图形状态、路径、颜色等回调 ... op_m: function (x,y) { print(x, y, "m"); }, op_l: function (x,y) { print(x, y, "l"); }, op_re: function (x,y,w,h) { print(x,y,w,h, "re"); }, op_BT: function () { print("BT"); }, op_ET: function () { print("ET"); }, op_Tf: function (name,size) { print("/"+name, size, "Tf"); }, op_TJ: function (text) { text = text.map(function (x) { if (typeof x != 'number') return showString(x); return x; }); print("[" + showArray(text) + "] TJ") }, // XObject 按类型拆分 op_Do_image: function (name,image) { print("/"+name, "Do%image"); }, op_Do_form: function (name,form,page_res) { print("/"+name, "Do%form"); }, };

颜色回调也完整体现了“按颜色类型分发”的设计,处理器同时定义了op_SC_color、op_SC_pattern、op_SC_shade及op_G/op_g、op_RG/op_rg、op_K/op_k等全部变体。

运行方式(在已构建mutool的环境中,对指定文件的第一页执行):

mutool run pdf-trace.js input.pdf 1

注意页码参数在脚本内被当作从 1 开始计数(loadPage(scriptArgs[1]-1))。

源码实现:从 JS 回调到 pdf_processor

从源码结构看,mutool run中 JavaScript 绑定的注册与执行链位于 murun.c:

jsB_propfun(J, "PDFPage.process", ffi_PDFPage_process, 1);

以及PDFAnnotation.process的注册(murun.c)。ffi_PDFPage_process的实现(murun.c)清晰地展示了回调桥接的全过程:

static void ffi_PDFPage_process(js_State *J) { fz_context *ctx = js_getcontext(J); pdf_page *page = js_touserdata(J, 0, "pdf_page"); pdf_processor *proc = new_js_processor(ctx, J); fz_try(ctx) { pdf_obj *resources = pdf_page_resources(ctx, page); pdf_obj *contents = pdf_page_contents(ctx, page); pdf_process_contents(ctx, proc, page->doc, resources, contents, NULL, NULL); pdf_close_processor(ctx, proc); } fz_always(ctx) pdf_drop_processor(ctx, proc); fz_catch(ctx) rethrow(J); }

可以推断出以下实现要点:

  1. new_js_processor(murun.c)把传入的 JavaScript 对象包装为 C 层的pdf_processor。其各pdf_processor函数指针在触发时回到 JS 引擎,按名字查找并调用对象上对应的op_*属性,实现“C 回调 → JS 函数”的透明转发;
  2. 处理器拿到的输入是页面对象的 resources 字典与 contents 流(pdf_page_resources/pdf_page_contents),由pdf_process_contents统一完成操作符解释与回调分发——PDFAnnotation.process复用同一个new_js_processor,只是输入换成注释的外观流;
  3. 全程使用fz_try/fz_always/fz_catch错误处理框架,异常最终通过rethrow(J)抛回 JavaScript 层,因此在脚本里try { page.process(proc); } catch (e) {...}即可捕获解析异常。

Java 绑定中的对应形态

MuPDF 的 Java 绑定提供了同构的抽象类 PDFProcessor.java,将本文清单中的每个回调声明为抽象方法(如op_gs(String name, PDFObject extgstate)、op_fstar()、op_Tf(String name, float size)等),实现方式是把接口对象继承为具名字段/匿名类再传给page.process(proc)——Java 示例见 PDFTrace.java。两个绑定在回调命名与参数含义上保持一致,理解了 JavaScript 版本的清单即可平移到 Java 版本。

小结与使用建议

  • 只要需要“逐操作符级别”访问 PDF 内容,PDFProcessor+process()是 MuPDF JavaScript 绑定的标准入口;页面用PDFPage.process,注释外观用PDFAnnotation.process。
  • 回调参数完全遵循 PDF 规范操作词语义,写自定义解析器前建议对照 PDF 规范 graphic operators 章节确认每个操作符的作用。
  • 文本回调(TJ/Tj等)的参数可能是字符串或字节数组,颜色回调按 color/pattern/shade/设备色分组,XObject 的Do拆为 image/form 两个回调——这三点是实现完整处理器时最容易漏掉的分支,可参考 pdf-trace.js 中全部回调的逐一实现作为模板。
  • 脚本运行环境为mutool run的 ECMAScript 5 严格模式,输出用print(...),命令行参数在全局scriptArgs数组中(参见 mutool-run.md)。
  • 图形学
  • 图像处理

【免费下载链接】mupdf

mupdf mirror

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

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询