PDF.js 如何在 HiDPI 屏幕上把 PDF 页面渲染得清晰devicePixelRatio 与 viewport 缩放【免费下载链接】pdf.jsPDF Reader in JavaScript项目地址: https://gitcode.com/gh_mirrors/pd/pdf.js在浏览器里用 canvas 渲染 PDF 页面时一个常见的问题是页面能显示但放在高分屏Retina、devicePixelRatio 1上会发虚。原因是 canvas 的内部分辨率只按 CSS 像素设置没有按设备的物理像素密度放大。pdf.js 的官方示例给出的做法是用page.getViewport()决定页面的布局尺寸再用window.devicePixelRatio放大 canvas 的像素缓冲区并通过render的transform参数把绘制内容同步放大。本文按 官方示例文档 和 helloworld 示例 把这条操作路径走一遍。viewport 在渲染中起什么作用文档对 viewport 的说明是每个 PDF 页面都有自己的 viewport它定义像素72DPI尺寸和初始旋转默认情况下 viewport 缩放为 PDF 的原始大小也可以通过修改 viewport 改变缩放。创建 viewport 时会同时生成一个初始变换矩阵考虑期望的缩放和旋转并转换坐标系PDF 文档中 (0,0) 在左下角而 canvas 的 (0,0) 在左上角。对应 API 是PDFPageProxy.getViewport()参数为scale、rotation、offsetX、offsetY、dontFlip返回一个包含width、height属性以及渲染所需变换的PageViewport实现在 src/display/api.jsgetViewport({ scale, rotation this.rotate, offsetX 0, offsetY 0, dontFlip false, } {}) { return new PageViewport({ viewBox: this.view, userUnit: this.userUnit, scale, rotation, offsetX, offsetY, dontFlip, }); }所以scale控制的是“页面内容占多大”而 canvas 上的清晰度还取决于另一件事canvas 自身按多少物理像素绘制。这就是 HiDPI 处理要补上的一环。主路径按 helloworld 示例渲染第一页示例文件 examples/learning/helloworld.html 引用的是本地构建产物路径../../node_modules/pdfjs-dist/build/pdf.mjs和pdf.worker.mjs因此先在项目根目录安装依赖再在浏览器中打开该 HTML 文件即可复现。准备部分是三件事加载 pdfjs 模块、指定 worker 脚本、拿到第一页。示例中的注释明确workerSrc必须指定当 worker 脚本与 PDF.js 主文件不在同一目录时pdfjsLib.GlobalWorkerOptions.workerSrc ../../node_modules/pdfjs-dist/build/pdf.worker.mjs; const loadingTask pdfjsLib.getDocument({ url: ./helloworld.pdf }); const pdf await loadingTask.promise; const page await pdf.getPage(1);然后是 HiDPI 渲染的核心代码与 文档中的写法 一致const scale 1.5; const viewport page.getViewport({ scale }); // Support HiDPI-screens. const outputScale window.devicePixelRatio || 1; const canvas document.getElementById(the-canvas); const context canvas.getContext(2d); canvas.width Math.floor(viewport.width * outputScale); canvas.height Math.floor(viewport.height * outputScale); canvas.style.width Math.floor(viewport.width) px; canvas.style.height Math.floor(viewport.height) px; const transform outputScale ! 1 ? [outputScale, 0, 0, outputScale, 0, 0] : null; const renderContext { canvasContext: context, transform, viewport, }; page.render(renderContext);逐行看关键是三处canvas.width/canvas.height按viewport × outputScale设置这是 canvas 的物理像素缓冲区决定实际绘制精度canvas.style.width/canvas.style.height只按viewport设置 CSS 像素页面上的布局尺寸不变outputScale ! 1时给renderContext传入[outputScale, 0, 0, outputScale, 0, 0]这个缩放变换矩阵让渲染内容跟随缓冲区放大outputScale为 1 时transform传null不做额外变换。代码库自身的渲染路径也用同样的兜底策略src/display/display_utils.js 中统一以globalThis.devicePixelRatio || 1获取像素密度即读不到devicePixelRatio时按 1 处理。按示例代码执行后的结果是canvas 的 CSS 尺寸等于viewport.width/height内部分辨率再乘以devicePixelRatio页面内容按transform同步放大绘制——这正是文档注释里 “Support HiDPI-screens” 所指的清晰度做法。可选分支指定 canvas 的 CSS 像素宽度如果你不是按固定scale渲染而是想让 canvas 呈现某个特定宽度文档给出的算法是先用scale: 1的 viewport 求出目标缩放再重新创建 viewport文档示例var desiredWidth 100; var viewport page.getViewport({ scale: 1, }); var scale desiredWidth / viewport.width; var scaledViewport page.getViewport({ scale: scale, });之后把scaledViewport传给渲染HiDPI 处理方式与主路径相同。多页翻页时同一 canvas 不能同时画两页如果页面支持前后翻页还要注意文档指出的一点同一个 canvas 不能同时用于绘制两页必须先等上一次渲染完成。prevnext 示例 展示了完整做法HiDPI 部分代码与主路径相同额外加了一个排队逻辑var renderTask page.render(renderContext); // Wait for rendering to finish renderTask.promise.then(function () { pageRendering false; if (pageNumPending ! null) { // New page rendering is pending renderPage(pageNumPending); pageNumPending null; } });queueRenderPage(num)的逻辑是若正在渲染pageRendering为true把请求记到pageNumPending等renderTask.promise完成后再触发否则直接渲染。这样翻页按钮快速连点时不会让两次render竞争同一个 canvas。限制说明上述代码依赖浏览器环境下的window.devicePixelRatio示例代码和代码库都用|| 1兜底取值失败时退化为普通像素密度渲染。viewport定义的是 72DPI 下的像素尺寸scale改变的是页面在 viewport 中的大小清晰度由 canvas 缓冲区outputScale与transform共同保证两者要配套使用。文档没有对“渲染清晰”给出量化判定标准验证方式就是运行示例页面在devicePixelRatio 1的环境下canvas.width应等于Math.floor(viewport.width * outputScale)而 CSS 尺寸保持viewport.width不变。【免费下载链接】pdf.jsPDF Reader in JavaScript项目地址: https://gitcode.com/gh_mirrors/pd/pdf.js创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考