PDF.js 从 demo 深入浅出 1: 自定义 viewer 渲染 PDF
2026年10月19日 · StreamPDF Team

前言
在之前的系列文章中, 我们探索了 PDF.js 的架构, 以及渲染层和消息通讯等技术点, 但是在写文章的过程中, 总结了一些问题:
- 文章之间无法串联, 无法形成一个完整的知识体系
- 无法对应 PDF.js 的代码, 因此跟随文章的过程中心之负担较重
因此, 我们决定起一个新系列, 从 demo 代码出发, 慢慢的跟随代码的调用链组件深入 PDF.js 的内部. 并且会从两个方面来诠释代码原理:
- demo 解读: 实例化解读, 由浅入深链接起来, 深度探索
- 代码仓库解读: 通用化解读, 可分块阅读, 独立成文
来吧, 试着从头出发.
自定义 viewer 渲染 PDF
我们来尝试自定义一个极简的 viewer 来渲染 PDF, 然后跟随每一行代码, 进入 PDF.js 的内部. 在跟随的过程中, 我们会将变量值带入代码中, 以便于理解代码的执行流程.
import * as pdfjsLib from 'https://cdn.jsdelivr.net/npm/pdfjs-dist@6.3.289/build/pdf.mjs';
pdfjsLib.GlobalWorkerOptions.workerSrc =
'https://cdn.jsdelivr.net/npm/pdfjs-dist@6.3.289/build/pdf.worker.mjs';
const loadingTask = pdfjsLib.getDocument({
url: 'https://assets.streampdf.com/PDF32000_2008.pdf',
});
const pdf = await loadingTask.promise;
// 获取第一页的渲染数据
const page = await pdf.getPage(1);
// 获取第一页的视口数据
const viewport = page.getViewport({ scale: 0.5 });
// 拿到 canvas, 我们将渲染到这个 canvas 上面
const canvas = document.getElementById('canvas-id');
const context = canvas.getContext('2d');
// 将 canvas 的尺寸匹配上PDF 的视口大小
canvas.width = Math.floor(viewport.width);
canvas.height = Math.floor(viewport.height);
canvas.style.width = Math.floor(viewport.width) + 'px';
canvas.style.height = Math.floor(viewport.height) + 'px';
// 准备渲染参数
const renderContext = {
canvasContext: context,
viewport,
};
// 调用 render 方法进行实际的渲染,await 等待渲染完成
await page.render(renderContext);demo: https://streampdf.com/blog/demos/a-simple-pdf-render-demo.html
第 1 行-第 3 行, 配置 Worker 文件路径
引入 PDF.js 库的入口文件, 以及设置 Worker 文件的路径.
这意味着在使用 PDF.js 的时候, 我们甚至可以完全替换成自己的 Worker, 只要遵循接口规范即可.
在这里我们设置为默认的 Worker 文件路径:
GlobalWorkerOptions.workerSrc = https://cdn.jsdelivr.net/npm/pdfjs-dist@6.3.289/build/pdf.worker.mjs
第 4 行-第 7 行, 加载 PDF 文档
调用 getDocument 方法加载 PDF 文档. 传入 object 参数:
- src: PDF 文档的 URL. 值为
https://assets.streampdf.com/PDF32000_2008.pdf
getDocument 方法返回一个 Promise, 当 PDF 文档加载完成后, 会 resolve 一个 PDF 文档对象.
const loadingTask = pdfjsLib.getDocument(...) 为同步调用: 点击查看调用
loadingTask.promise 为一个 getter, 返回 Promise.withResolvers() 方法创建的 Promise.
在这里我们 await loadingTask.promise, 等待 PDF 文档加载完成, 并将其赋值为 pdf 变量.
第 8 行-第 10 行, 获取 PDF 文档的第一页
在文档初始化完成之后, 我们可以调用 getPage 方法获取第一页的渲染数据.
getPage 方法返回一个 Promise, 当页面数据加载完成后, 会 resolve 一个页面对象.
const page = await pdf.getPage(1); 为异步调用: 点击查看调用
等待第一页数据加载完成后, 并将其赋值为 page 变量.
第 11 行-第 14 行, 获取第一页的视口数据
在 page 数据加载完成后, 我们可以调用 getViewport 方法获取第一页的视口数据.
const viewport = page.getViewport({ scale: 0.5 }); 为同步调用: 点击查看调用
获取第一页的视口数据. 传入 object 参数:
- scale: 缩放比例. 值为
0.5
返回的 viewport 是一个对象, 包含了视口的宽度和高度. 值为:
{
width: 297.5,
height: 421,
// others, 此处没有用到, 先不列出...
}将其赋值给 viewport 变量.
第 15 行-第 18 行, 拿到 canvas, 我们将渲染到这个 canvas 上面
拿到 viewport 变量之后, 将 canvas 的 width 和 height 匹配上 PDF 的视口大小.
canvas.width = 297; // Math.floor(viewport.width);
canvas.height = 421; // Math.floor(viewport.height);将 canvas 的 css 宽度和高度也设置上 PDF 的视口大小.
canvas.style.width = 297 + 'px'; // Math.floor(viewport.width) + 'px';
canvas.style.height = 421 + 'px'; // Math.floor(viewport.height) + 'px';为什么需要设置 canvas 的 css 宽度和高度? canvas 的 width, height 属性控制的是 canvas 的像素尺寸, 而 css 宽度和高度是 canvas 显示在页面上的尺寸. 一般情况下, 这两者是相同的. 但是在高分辨率屏幕上, 这两者是不同的. 比如 window.devicePixelRatio 为 2 时, canvas 的像素尺寸应设为 594, 842, 而 css 宽度和高度保持 297, 421, 以防止渲染模糊.
第 19 行-第 22 行, 渲染第一页
canvas 准备好之后, 我们可以调用 render 方法渲染第一页.
将 canvas 上下文, 视口数据, 渲染参数等准备好, 以便于调用 render 方法.
await page.render(renderContext); 为异步调用: 点击查看调用
至此, 代码执行结束.
总结
本章我们跟随一个自定义的 viewer 来渲染了一个 PDF 文件的第一页.
和其他文章最大的不同是, 将变量的值带入到解析代码的链路中, 以便确定不同的执行路径. 由不同的文件和分析文章来进入不同的分支路径, 读文章的过程中就好像是在 debug 代码一样.
希望能够给你带来不一样的源码阅读体验.
happy coding!
相关文章
- 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 从 demo 深入浅出 2: getDocument 方法全貌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 深入浅出 2: getDocument & getPage2026年10月19日
- PDF.js 深入浅出 1: PDF.js 的架构设计2026年10月16日