- 图形学
- 图像处理
【免费下载链接】mupdf
mupdf mirror
本文围绕 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 to
PDFAnnotation.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 supplied
PDFProcessor.
pdfPage.process(processor)即“遍历该页的内容流,对传入的PDFProcessor调用相应方法”。
PDFAnnotation.prototype.process(processor)
类型文档 PDFAnnotation.rst 中的说明:
Run through the annotation appearance stream and call methods on the supplied
PDFProcessor.
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); }可以推断出以下实现要点:
new_js_processor(murun.c)把传入的 JavaScript 对象包装为 C 层的pdf_processor。其各pdf_processor函数指针在触发时回到 JS 引擎,按名字查找并调用对象上对应的op_*属性,实现“C 回调 → JS 函数”的透明转发;- 处理器拿到的输入是页面对象的 resources 字典与 contents 流(
pdf_page_resources/pdf_page_contents),由pdf_process_contents统一完成操作符解释与回调分发——PDFAnnotation.process复用同一个new_js_processor,只是输入换成注释的外观流; - 全程使用
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
相关推荐
SumatraPDF 内置 MuPDF 的 PDFProcessor 接口:逐操作符解析 PDF 内容流与图形操作
SumatraPDF 内置 MuPDF 的 PDFProcessor 接口:逐操作符解析 PDF 内容流与图形操作 导读 PDFProcessor 是 Suma
桌面应用文档MuPDF JavaScript 绑定 PathWalker 接口详解:用回调遍历路径绘制指令
MuPDF JavaScript 绑定 PathWalker 接口详解:用回调遍历路径绘制指令 PathWalker 是 MuPDF JavaScript 绑定
图形学图像处理MuPDF JavaScript PathWalker 接口深度解析:通过回调遍历 Path 矢量绘图指令
MuPDF JavaScript PathWalker 接口深度解析:通过回调遍历 Path 矢量绘图指令 PathWalker 是 MuPDF JavaScr
桌面应用文档
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考