PDF.js 从 demo 深入浅出 2: getDocument 方法全貌
2026年10月27日 · StreamPDF Team

前言
在前一篇文章中, 传入了一个参数, 是一个对象参数, 属性为
- src: PDF 文件的 URL. 值为
https://assets.streampdf.com/PDF32000_2008.pdf
getDocument 调用
依据display/api.js 中 getDocument 的定义, 代码执行流程如下
第 1-2 行, 初始化 loading 任务
实例化 PDFDocumentLoadingTask 类: 点击查看调用, 并将返回值赋给 task 变量
从 task 中取出 docId 变量, 此时 docId 为 d1
第 3-15 行, 参数校验&处理
这一段代码把用户传入的 src 对象展开成 35+ 个局部变量: 传入的做校验和类型转换, 未传入的取默认值. demo 只传了 url, 因此绝大多数参数走默认分支.
唯一传入的 url, 经 getUrlProp 转换为 URL 对象:
const url = URL.parse('https://assets.streampdf.com/PDF32000_2008.pdf', window.location);
// const url = src.url ? getUrlProp(src.url) : null;其余参数全部未传入. 按源码顺序, 计算结果如下:
| 局部变量 | 计算结果 | 取值逻辑 |
|---|---|---|
data | null | 传入则经 getDataProp 统一转为 Uint8Array |
httpHeaders | null | 原样透传, 未传为 null |
withCredentials | false | 仅接受 === true |
password | null | ?? null |
rangeTransport | null | 仅接受 PDFDataRangeTransport 实例 |
rangeChunkSize | 2 ** 16 (64KB) | 正整数才接受传入值 |
worker | null | 仅接受 PDFWorker 实例 |
verbosity | undefined | 原样取 src.verbosity |
docBaseUrl | null | 仅接受非 data: 协议的 string |
cMapUrl | null | getFactoryUrlProp: 非 string 为 null, 且必须以 / 结尾 |
cMapPacked | true | !== false |
iccUrl | null | 同 cMapUrl |
standardFontDataUrl | null | 同 cMapUrl |
wasmUrl | null | 同 cMapUrl |
ignoreErrors | true | src.stopAtErrors !== true, 见下文 |
maxImageSize | -1 (不限制) | 整数且 > -1 才接受 |
isOffscreenCanvasSupported | true | 未传则取 !isNodeJS |
isImageDecoderSupported | true | 未传则取 !isNodeJS |
canvasMaxAreaInBytes | -1 (不限制) | 整数才接受 |
disableFontFace | false | 未传则取 isNodeJS |
fontExtraProperties | false | 仅接受 === true |
enableXfa | false | 仅接受 === true |
ownerDocument | globalThis.document | 未传则取全局 document |
disableRange | false | 仅接受 === true |
disableStream | false | 仅接受 === true |
disableAutoFetch | false | 仅接受 === true |
pdfBug | false | 仅接受 === true |
CanvasFactory | DOMCanvasFactory | 环境分支, 见下文 |
FilterFactory | DOMFilterFactory | 同上 |
BinaryDataFactory | DOMBinaryDataFactory | 同上 |
enableHWA | false | 仅接受 === true |
enableWebGPU | false | 仅接受 === true |
gpuPromise | Promise.resolve(false) | 未开 WebGPU, 不启动 GPU 初始化 |
useWasm | true | !== false |
pagesMapper | new PagesMapper() | 未传则新建 |
useSystemFonts | true | 级联默认, 见下文 |
useWorkerFetch | false | 复合判断, 见下文 Q&A |
styleElement | null | 仅 TESTING 构建保留传入值, 生产恒为 null |
以上就是这一段代码产生的全部局部变量. 其中大部分判断是平凡的「未传取默认」, 但有几个值得展开.
ignoreErrors: 语义取反的内部变量
用户面向的参数叫 stopAtErrors, 内部变量却叫 ignoreErrors, 取值逻辑是 src.stopAtErrors !== true. 也就是说默认行为是忽略错误: 解析出错时尽量恢复现场, 而不是 reject getOperatorList / getTextContent / RenderTask 等 promise. 看源码时这里极易把名字和值抄反.
rangeChunkSize: 64KB 的伏笔
rangeChunkSize 默认为 64KB. 由于 disableRange = false, 后文创建 transport 时会尝试对 PDF 发起 range 请求, 每次拉取的块大小就是这个值. 先记住它.
docBaseUrl: 为什么排除 data: URL
源码注释给出了原因: data: URL 无法用来还原绝对路径, 而且它内含整个 PDF 文档, 长度没有上限, 因此不将它发往 worker 线程.
gpuPromise: 与 worker 启动并行的设计
const gpuPromise = Promise.resolve(false); // enableWebGPU ? initGPU() : Promise.resolve(false);即使不启用 WebGPU, 这里也立即返回一个 resolved promise, 而不是把判断推迟到使用时. 注释说明了意图: 让 GPU 初始化与 worker 启动并行执行, 解析出的布尔值随后转发给 worker.
三个 Factory: 环境分支
const CanvasFactory = DOMCanvasFactory;
// src.CanvasFactory || (GENERIC 构建 && isNodeJS ? NodeCanvasFactory : DOMCanvasFactory);FilterFactory, BinaryDataFactory 同理. 判断分三层: 用户自定义 > Node 环境实现 > DOM 实现. demo 运行在浏览器且未自定义, 因此三个工厂都是 DOM 版本. 三者分工:
CanvasFactory: 创建渲染用的 canvasFilterFactory: 创建图像滤镜 (如 SVG 滤镜)BinaryDataFactory: 下载内置静态资源, 与下文的useWorkerFetch联动
useSystemFonts: 默认值依赖其他参数
const useSystemFonts = true;
// typeof src.useSystemFonts === "boolean" ? src.useSystemFonts : !isNodeJS && !disableFontFace;它的默认值依赖前面刚解析出的 disableFontFace. 为什么? PDF 中未嵌入的字体会被转成 FontFace 加载; 如果 disableFontFace 为 true, 这条加载路径被禁用, 此时再用系统字体兜底会导致字形完全错乱. 因此该场景下默认值索性也为 false, 避免渲染出彻底坏掉的字体.
useWorkerFetch: 复合判断
const useWorkerFetch = false;
// typeof src.useWorkerFetch === "boolean"
// ? src.useWorkerFetch
// : MOZCENTRAL ||
// !!(
// BinaryDataFactory === DOMBinaryDataFactory &&
// cMapUrl && cMapPacked && standardFontDataUrl && wasmUrl &&
// isValidFetchUrl(cMapUrl, document.baseURI) &&
// isValidFetchUrl(standardFontDataUrl, document.baseURI) &&
// isValidFetchUrl(wasmUrl, document.baseURI)
// );Q: 这里的判断在做什么? A: 在决定「内置静态资源」(CMap 文件, 标准字体文件, wasm 文件) 由谁去下载: worker 线程自己用 Fetch API 下载 (
true), 还是主线程下载后转发给 worker (false).如果用户显式传了 boolean, 直接用用户的值. 否则计算默认值: MOZCENTRAL (Firefox 内置 viewer) 恒为
true; 普通构建必须同时满足三个条件才为true:
- 没有自定义
BinaryDataFactory—— 因为useWorkerFetch为true时该参数会被直接忽略 (后面transportFactory里binaryDataFactory会被置为null), 用户自定义的工厂必须被尊重, 所以不能默认true;cMapUrl/standardFontDataUrl/wasmUrl都已提供, 且cMapPacked为true—— 没有 URL 就无从 fetch;- 这三个 URL 相对
document.baseURI解析后是 http/https —— Fetch API 只支持 http/https, 不支持 file/ftp.本文的 demo 只传了
url,cMapUrl为null, 条件 2 不满足, 因此useWorkerFetch = false: 走主线程路径, 由DOMBinaryDataFactory在主线程下载, worker 需要时通过FetchBinaryData消息向主线程索取.
第 16 行, 设置主线程日志级别
setVerbosityLevel(verbosity); // verbosity 为 undefinedverbosity 为 undefined, 主线程日志级别保持默认. 注意这只影响主线程; worker 线程的日志级别会在创建 worker 时单独传入.
第 17-25 行, 实例化三个工厂
const transportFactory = {
canvasFactory: new DOMCanvasFactory({ ownerDocument: document, enableHWA: false }),
filterFactory: new DOMFilterFactory({ docId: 'd1', ownerDocument: document }),
binaryDataFactory: new DOMBinaryDataFactory({
cMapUrl: null,
standardFontDataUrl: null,
wasmUrl: null,
}),
};
// binaryDataFactory: MOZCENTRAL 或 useWorkerFetch 为 true 时为 null将前面选出的三个 Factory 类实例化, 打包为 transportFactory, 随后交给 transport 使用. 由于 useWorkerFetch = false, binaryDataFactory 不为 null —— 与上一节的 Q&A 呼应: 内置静态资源走主线程下载.
三个工厂实例内部的能力, 另文展开.
第 26-33 行, 创建 Worker
worker = PDFWorker.create({ verbosity: undefined, port: undefined });
// port 来自 GlobalWorkerOptions.workerPort, demo 未设置
task._worker = worker;demo 未传入 worker, 因此 PDF.js 自己创建并拥有一个. PDFWorker.create 同步返回一个 PDFWorker 实例, 其构造函数会读取 GlobalWorkerOptions.workerSrc (即第一篇中配置的 worker 文件地址), 创建 Worker 并通过 ready / test 消息完成启动握手 —— 这是重点, 已在第 8 篇详细展开. 这里只需记住: 返回的 worker 对象上有一个 promise, worker 就绪后 resolve.
第 34-63 行, 组装两份参数包
第一份 docParams, 随后会跨线程发往 worker:
const docParams = {
docId: 'd1',
apiVersion: '6.3.289', // 构建版本号
data: null,
password: null,
disableAutoFetch: false,
rangeChunkSize: 65536,
docBaseUrl: null,
enableXfa: false,
evaluatorOptions: {
maxImageSize: -1,
disableFontFace: false,
ignoreErrors: true,
isOffscreenCanvasSupported: true,
isImageDecoderSupported: true,
canvasMaxAreaInBytes: -1,
fontExtraProperties: false,
useSystemFonts: true,
useWasm: true,
useWorkerFetch: false,
cMapUrl: null,
cMapPacked: true,
iccUrl: null,
standardFontDataUrl: null,
wasmUrl: null,
hasGPU: false, // 占位, 稍后回填
},
};第二份 transportParams, 只留在主线程:
const transportParams = {
ownerDocument: document,
pdfBug: false,
styleElement: null,
enableHWA: false,
loadingParams: { disableAutoFetch: false, enableXfa: false },
};一句话区分: docParams 是发给 worker 的「工作指令」, transportParams 是主线程自留的「本地配置」.
第 64 行起, worker 就绪后的握手
Promise.all([worker.promise, gpuPromise]).then(...)worker 就绪与 GPU 探测 (resolve 为 false) 都完成后, 依次发生:
- 将
hasGPU = false回填进evaluatorOptions; - 向 worker 发送
GetDocRequest消息, 载荷为docParams.data为null, 因此没有需要转移所有权的 buffer; - 选择网络流:
data为null且rangeTransport为null, 走url分支, 实例化网络流 (优先 fetch 实现, 不支持则回退 XHR; 内部如何发 range 请求, 见第 9 篇):
networkStream = new NetworkStream({
url,
httpHeaders: null,
withCredentials: false,
rangeChunkSize: 65536,
disableRange: false,
disableStream: false,
});- 收到 worker 对
GetDocRequest的回执 (workerId) 后, 创建MessageHandler与WorkerTransport, 并挂到task._transport上 —— 两个核心类分别在第 6、7 篇展开; - 向 worker 发送
Ready消息, 主线程侧宣告就绪; worker 的Ready处理函数随后调用setupDoc(docParams).
返回值: task
以上 .then 链全部在后台异步推进, getDocument 本身同步返回 task. 链路中任何一步出错都会 reject task 内部的 capability, 也就是 demo 中 await loadingTask.promise 所等待的那个 promise.
至此 getDocument 执行完毕.
总结
本节跟随 demo 走完了 getDocument 的完整调用:
- 创建
PDFDocumentLoadingTask, 取出docId = 'd1'; - 将
src展开为 35+ 个局部变量, demo 只传了url, 其余全部落到默认值; - 实例化三个 DOM 工厂, 打包为
transportFactory; PDFWorker.create创建 worker 并启动握手;- 组装
docParams(发往 worker) 与transportParams(留在主线程); - worker 就绪后发送
GetDocRequest, 建立网络流, 创建MessageHandler与WorkerTransport, 最后发送Ready; - 同步返回
task, demo 通过await loadingTask.promise等待文档就绪.
过程中刻意略过了四个「重角色」: PDFWorker 的创建与握手, MessageHandler 的消息机制, WorkerTransport 的初始化, 以及网络流的 range 请求策略. 它们各自独立成文, 从下一篇开始逐个深入.
相关文章
- PDF.js 从 demo 深入浅出 10: CanvasGraphics 与 operatorList 绘制2026年10月27日
- PDF.js 从 demo 深入浅出 9: 网络流与 Range 请求2026年10月27日
- PDF.js 从 demo 深入浅出 8: PDFWorker.create 与真假 Worker 握手2026年10月27日
- PDF.js 从 demo 深入浅出 7: WorkerTransport 初始化2026年10月27日
- PDF.js 从 demo 深入浅出 6: MessageHandler 跨线程通讯2026年10月27日
- PDF.js 从 demo 深入浅出 5: render 方法全貌2026年10月27日
- PDF.js 从 demo 深入浅出 4: getViewport 方法全貌2026年10月27日
- PDF.js 从 demo 深入浅出 3: getPage 方法全貌2026年10月27日
- PDF.js 源码解读: TextLayer2026年10月27日
- PDF.js 源码解读: font_loader2026年10月27日
- PDF.js 源码解读: obj_bin_transform_display2026年10月27日
- PDF.js 源码解读: PDFObjects2026年10月27日
- PDF.js 源码解读: 网络流2026年10月27日
- PDF.js 源码解读: internal_evt.js2026年10月27日
- PDF.js 源码解读: math_clamp.js2026年10月27日
- PDF.js 源码解读: css_utils.js2026年10月27日
- PDF.js 源码解读: scripting_utils2026年10月27日
- PDF.js 源码解读: MurmurHash3_642026年10月27日
- PDF.js 源码解读: obj_bin_transform_utils2026年10月27日
- PDF.js 源码解读: image_utils.js2026年10月27日
- PDF.js 源码解读: BasePDFStream2026年10月27日
- PDF.js 源码解读: 共享工具层2026年10月27日
- PDF.js 源码解读: MessageHandler2026年10月27日
- PDF.js 源码解读: api2026年10月27日
- PDF.js 源码解读: fonts2026年10月27日
- PDF.js 源码解读: glyphlist2026年10月27日
- PDF.js 源码解读: binary_cmap2026年10月27日
- PDF.js 源码解读: cmap2026年10月27日
- PDF.js 源码解读: encodings2026年10月27日
- PDF.js 源码解读: metrics2026年10月27日
- PDF.js 源码解读: standard_fonts2026年10月27日
- PDF.js 源码解读: fonts_utils2026年10月27日
- PDF.js 源码解读: default_appearance2026年10月27日
- PDF.js 源码解读: metadata_parser2026年10月27日
- PDF.js 源码解读: name_number_tree2026年10月27日
- PDF.js 源码解读: struct_tree2026年10月27日
- PDF.js 源码解读: catalog2026年10月27日
- PDF.js 源码解读: pdf_manager2026年10月27日
- PDF.js 源码解读: object_loader2026年10月27日
- PDF.js 源码解读: obj_bin_transform_core2026年10月27日
- PDF.js 源码解读: crypto2026年10月27日
- PDF.js 源码解读: core_utils2026年10月27日
- PDF.js 源码解读: primitives2026年10月27日
- PDF.js 源码解读: worker_stream2026年10月27日
- PDF.js 源码解读: ccitt_stream2026年10月27日
- PDF.js 源码解读: jpx_stream2026年10月27日
- PDF.js 源码解读: jbig2_stream2026年10月27日
- PDF.js 源码解读: jpeg_stream2026年10月27日
- PDF.js 源码解读: decrypt_stream2026年10月27日
- PDF.js 源码解读: brotli_stream2026年10月27日
- PDF.js 源码解读: run_length_stream2026年10月27日
- PDF.js 源码解读: ascii_hex_stream2026年10月27日
- PDF.js 源码解读: ascii_85_stream2026年10月27日
- PDF.js 源码解读: lzw_stream2026年10月27日
- PDF.js 源码解读: predictor_stream2026年10月27日
- PDF.js 源码解读: flate_stream2026年10月27日
- PDF.js 源码解读: decode_stream2026年10月27日
- PDF.js 源码解读: chunked_stream2026年10月27日
- PDF.js 源码解读: stream2026年10月27日
- PDF.js 源码解读: base_stream2026年10月27日
- PDF.js 深入浅出 11: Parser2026年10月26日
- PDF.js 深入浅出 10: XRef2026年10月26日
- PDF.js 深入浅出 9: CanvasGraphics2026年10月26日
- PDF.js 深入浅出 8: Document 与 Page2026年10月26日
- AI 时代, 我们到底应该做什么才能活下去(StreamPDF 创立有感)2026年10月26日
- PDF.js 深入浅出 7: PartialEvaluator2026年10月25日
- PDF.js 深入浅出 6: PDFManager.initDocument2026年10月25日
- PDF.js 深入浅出 5: PDFFetchStream 和 PDFWorkerStream2026年10月25日
- PDF.js 深入浅出 4: MessageHandler2026年10月22日
- PDF.js 深入浅出 3: getViewport & render2026年10月22日
- PDF.js 从 demo 深入浅出 1: 自定义 viewer 渲染 PDF2026年10月19日
- PDF.js 深入浅出 2: getDocument & getPage2026年10月19日
- PDF.js 深入浅出 1: PDF.js 的架构设计2026年10月16日