pdf.js 中的 OpenJPEG 解码器:JPEG 2000 WASM 构建流程、源码调用链与 BSD 许可解析
【免费下载链接】pdf.jsPDF Reader in JavaScript项目地址: https://gitcode.com/gh_mirrors/pd/pdf.js
本指南聚焦 pdf.js 仓库中external/openjpeg/目录的角色与构建方式:它承载了 JPEG 2000(JPX)图像解码所需的 OpenJPEG WASM 模块,是 PDF 渲染管线中处理 JPXDecode 流的核心依赖。读完本文,你将掌握openjpeg.js的重新生成流程(Docker + Node 编译)、其在渲染过程中的完整调用链(JpxStream → JpxImage → OpenJPEG WASM)、无 WASM 环境下的回退机制,以及其 BSD 2-clause 许可的继承关系。
一、OpenJPEG 在 pdf.js 中的定位:JPX 图像的专用解码器
JPEG 2000 是 PDF 规范支持的图像压缩格式之一(Filter 为JPXDecode)。由于 JS 生态没有开箱即用的 JPEG 2000 解码实现,pdf.js 选择将 OpenJPEG 这个 C 语言开源库编译为 WebAssembly,随渲染流程按需加载。
这一设计在源码中有清晰体现。核心解码入口 src/core/jpx.js 中的JpxImage类继承自WasmImage基类,并声明了两个关键文件名:
class JpxImage extends WasmImage { _filename = "openjpeg.wasm"; _noWasmFilename = "openjpeg_nowasm_fallback.js"; ... }即:优先加载external/openjpeg/openjpeg.wasm(WASM 二进制),失败时回退到纯 JS 版external/openjpeg/openjpeg_nowasm_fallback.js。这两个文件连同openjpeg.js加载器均由本 README 描述的构建流程生成。
external/openjpeg/ 目录组成
仓库中external/openjpeg/目录下包含以下与构建和许可相关的文件:
| 文件 | 作用 |
|---|---|
openjpeg.js | Emscripten 生成的模块加载器(异步工厂函数OpenJPEG(moduleArg)),负责 WASM 实例化 |
openjpeg.wasm | OpenJPEG 编译产物,JPEG 2000 解码逻辑的二进制本体 |
openjpeg_nowasm_fallback.js | 无 WASM 环境下的纯 JS 回退实现,文件头注明THIS FILE IS GENERATED - DO NOT EDIT |
LICENSE_OPENJPEG | OpenJPEG 上游的 BSD 2-clause 许可原文 |
LICENSE_PDFJS_OPENJPEG | pdf.js.openjpeg 项目的 BSD 2-clause 许可原文 |
README.md | 本文所讲解的构建与许可说明 |
二、构建 openjpeg.js:从上游仓库到 WASM 产物
本 README 的核心内容即openjpeg.js的生成方式。生成脚本并不在 pdf.js 仓库内,而是位于独立的 mozilla/pdf.js.openjpeg 仓库中。完整步骤如下:
1. 克隆构建源仓库
git clone https://github.com/mozilla/pdf.js.openjpeg/该仓库封装了 OpenJPEG 的编译配置与build.js构建脚本。
2. 准备 Docker 环境
构建过程依赖 Docker 提供 Emscripten 编译环境(即 OpenJPEG 的 C 源码通过 Emscripten 工具链交叉编译为 WASM),因此需要先确保本机已安装并启动 Docker。
3. 构建 Docker 镜像
在克隆下来的pdf.js.openjpeg仓库根目录执行:
node build.js -C-C参数表示构建(Create)编译所需的 Docker 镜像。这一步只需执行一次,后续编译可直接复用该镜像。
4. 编译解码器并输出到 pdf.js 仓库
node build.js -co /pdf.js/external/openjpeg/-c表示执行编译(Compile),-o指定输出目录,即把生成的openjpeg.js、openjpeg.wasm、openjpeg_nowasm_fallback.js等产物写入 pdf.js 仓库的external/openjpeg/目录。请根据实际检出路径替换/pdf.js/external/openjpeg/。
注意:该输出目录下的生成文件带有「GENERATED - DO NOT EDIT」标记(如
openjpeg_nowasm_fallback.js文件头所示),不应手工修改;若需调整解码能力,应在上游pdf.js.openjpeg仓库修改后重新编译。
三、构建产物如何进入 pdf.js 的发布包
生成的 WASM 产物不是直接由浏览器加载的,而是由 pdf.js 的构建系统统一打包。在 gulpfile.mjs 中,dev-wasm任务负责将所有图像解码器的 WASM 资源汇总到web/wasm/目录:
gulp.task("dev-wasm", function () { const VIEWER_WASM_OUTPUT = "web/wasm/"; fs.rmSync(VIEWER_WASM_OUTPUT, { recursive: true, force: true }); fs.mkdirSync(VIEWER_WASM_OUTPUT, { recursive: true }); return createWasmBundle().pipe(gulp.dest(VIEWER_WASM_OUTPUT)); });其中createWasmBundle的资源清单(gulpfile.mjs)明确包含:
"external/openjpeg/*.wasm", "external/openjpeg/openjpeg_nowasm_fallback.js", "external/openjpeg/LICENSE_*",同样被纳入打包的还有external/qcms/(色彩管理)与external/jbig2/(JBIG2 图像)的 WASM 产物(gulpfile.mjs),说明 OpenJPEG 是 pdf.js「WASM 图像解码器家族」的一员。此外,gulpfile.mjs 还会将external/openjpeg/*.js以openjpeg/为基准目录做进一步处理,例如生成带版本信息的构建副本。
四、源码级调用链:从 PDF 流到像素数据
理解了构建方式后,再看 OpenJPEG 在渲染管线中的实际使用路径,会更有全局感:
1. JpxStream:解码请求的发起者
JPEG 2000 图像数据在 PDF 中表现为一种流。 src/core/jpx_stream.js 中的JpxStream extends DecodeStream将其标记为异步解码器,并在decodeImage中把原始字节交给单例解码器:
get isAsyncDecoder() { return true; } async decodeImage(bytes, _length, decoderOptions) { ... this.buffer = await JpxImage.instance.decode(bytes, decoderOptions); this.bufferLength = this.buffer.length; this.eof = true; return this.buffer; }2. JpxImage.decode:与 WASM 模块的内存交互
src/core/jpx.js 中JpxImage.decode()展示了与 OpenJPEG WASM 模块交互的典型模式:
const module = await this._getModule(OpenJPEG); if (!module) { throw new JpxError("OpenJPEG failed to initialize"); } ... ptr = module._malloc(size); module.writeArrayToMemory(bytes, ptr); const ret = module._jp2_decode( ptr, size, numComponents > 0 ? numComponents : 0, !!isIndexedColormap, !!smaskInData, reducePower ); ... const { imageData } = module;即:通过_malloc在 WASM 线性内存中分配缓冲区 →writeArrayToMemory写入 JPEG 2000 字节流 → 调用_jp2_decode完成解码 → 从module.imageData取回像素结果,最后在finally中_free释放内存。该方法还接受numComponents、isIndexedColormap、smaskInData、reducePower等可选参数,用于适配 PDF 中索引颜色、软掩膜(soft mask)等复杂情况。
3. parseImageProperties:绕过解码器直接读尺寸
值得注意的是,获取 JPX 图像的基础属性(宽高、分量数)时并不需要启动 WASM。src/core/jpx.js 的parseImageProperties直接在字节流中扫描 SIZ 标记段(0xff51),解析出width、height、componentsCount,如源码注释所述:“No need to use OpenJPEG here since we're only getting very basic information which are located in the first bytes of the file.”
4. 基类 WasmImage:模块加载与回退策略
src/core/wasm_image.js 的WasmImage.setOptions提供全局开关:
static setOptions({ handler, useWasm, useWorkerFetch, wasmUrl }) { WasmImage.#useWasm = useWasm; WasmImage.#useWorkerFetch = useWorkerFetch; WasmImage.#wasmUrl = wasmUrl; ... }_getModule(src/core/wasm_image.js)的逻辑为:当useWasm为真时,通过ImageDecoder({ instantiateWasm, ... })实例化 WASM;若实例化失败,#instantiateWasm的 catch 分支会调用#getJsModule动态加载openjpeg_nowasm_fallback.js作为兜底。这一“WASM 优先、纯 JS 回退”的架构保证了在不支持 WebAssembly 或 WASM 文件缺失时,JPX 图像依然可以渲染。
5. 单元测试
解码逻辑的测试位于 test/unit/pdf.image_decoders_spec.js,其中导入了JpxError与JpxImage,与 OpenJPEG 相关的解码行为通过该模块统一验证。
五、许可说明:BSD 2-clause 的三层继承
本 README 的另一半内容用于澄清许可归属。综合 LICENSE_OPENJPEG 与 LICENSE_PDFJS_OPENJPEG 两个文件,许可链条如下:
- OpenJPEG 上游:以 BSD 2-clause "Simplified" License 发布,版权归属于 UCL(Université catholique de Louvain)等原始作者(详见
LICENSE_OPENJPEG文件头部的版权声明); - pdf.js.openjpeg 封装项目:同样以 BSD 2-clause 发布(
LICENSE_PDFJS_OPENJPEG,Copyright (c) 2024, Mozilla Foundation); - 编译产物 openjpeg.js:作为上述两者的产物,同样适用 BSD 2-clause 许可。
因此,无论是直接使用external/openjpeg/下的生成文件,还是将其集成进自有构建流程,都需保留两份 LICENSE 文件及其版权声明。BSD 2-clause 是宽松许可,允许源码与二进制形式的再分发与修改,但要求保留版权声明、条件清单与免责声明(详见仓库内 LICENSE_OPENJPEG 的原文)。
六、小结
- 想要重新生成
external/openjpeg/下的解码器文件,核心命令是node build.js -C(构建 Docker 镜像)与node build.js -co /pdf.js/external/openjpeg/(编译输出),前置条件是 Docker 环境; - 想要理解运行机制,可循
JpxStream → JpxImage.decode → _jp2_decode这条调用链深入 src/core/jpx.js 与 src/core/wasm_image.js; - 想要集成发布,关注 gulpfile.mjs 中
createWasmBundle的资源清单与dev-wasm任务; - 想要合规分发,务必保留
LICENSE_OPENJPEG与LICENSE_PDFJS_OPENJPEG两份 BSD 2-clause 许可文本。
【免费下载链接】pdf.jsPDF Reader in JavaScript项目地址: https://gitcode.com/gh_mirrors/pd/pdf.js
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考