前端用 pdf.js 将 PDF 渲染到 Canvas 再转图片,文字消失的坑

一、背景与业务场景

  • 业务流程:上传 PDF → 使用 pdf.js 渲染页面为 canvas → canvas.toDataURL() → 上传为图片 → 页面展示图片。
  • 现象对比:
    • WPS 创建的 PDF:渲染为图片后,文字正常显示。
    • "网页转 PDF"的文件:渲染为图片后,文字不显示(图片里空白或只有图形/线条,没有文字)。

二、问题现象

  • 代码位置(节选):
    • 使用 pdfjsLib.getDocument 加载 PDF 数据后,在 renderPdfToImages 中调用 page.render 渲染到 canvas,之后 canvas.toDataURL() 上传。
  • 异常点:
    • 相同的流程下,只有"网页转 PDF"的文档渲染后的图片出现文字缺失。

三、排查线索

  • 之前未为 pdf.js 显式配置 CMaps、标准字体路径等参数。
  • "网页转 PDF"常见特点:使用 CID/子集字体,依赖 CMap 资源映射字形;若运行时找不到对应资源,pdf.js 会出现文字无法正确绘制到 canvas 的情况。
  • WPS 导出的 PDF 通常内嵌常用字体,即使不配置 CMaps 也能绘制出字形,不容易暴露问题。

四、根因分析

  • pdf.js 在渲染涉及 CJK(中文/日/韩)与 CID 字体的 PDF 时,需要 CMap 和标准字体资源辅助解析。未配置 cMapUrl / cMapPacked (以及在部分场景下的 standardFontDataUrl )会导致文本无法正确绘制。
  • 个别环境中直接将 ArrayBuffer 传入 getDocument 可能存在解析不稳定的问题,转为 Uint8Array 更稳妥。
  • 渲染 intent 使用默认值时,在某些 PDF 文本/矢量渲染质量欠佳;使用 intent: 'print' 可提高文本渲染稳定性和清晰度。

五、解决方案(分步骤)

1)准备静态资源(CMaps 与标准字体)

  • 将 node_modules 中的标准字体资源复制到 public (public/lib/pdfjs-dist/web/cmaps/ ) 这样运行时就可以通过 URL 加载这些静态资源,不会被打包工具"吃掉"。

2)为 getDocument 显式配置 CMaps/(可选)标准字体,并把 ArrayBuffer 转为 Uint8Array

  • 要点:
    • data :使用 Uint8Array (由 ArrayBuffer 转),提升兼容性。
    • cMapUrl :指向 public 下的 cmaps/ 目录。
    • cMapPacked: true :使用打包的 .bcmap 文件。
    • standardFontDataUrl :可选。如果极个别 PDF 仍有文字缺失,可打开这一项。
js 复制代码
// 将 ArrayBuffer 转成 Uint8Array,并配置 CMaps/标准字体资源(可选)
// 这样可解决"网页转 PDF"使用 CID/中文字体时文字不显示的问题
const pdfBinary = pdfData instanceof ArrayBuffer ? new Uint8Array(pdfData) : pdfData

// 显式配置 pdf.js 的 CMaps 路径与打包模式(.bcmap)
const loadingTask = pdfjsLib.getDocument({
 data: pdfBinary,
 cMapUrl:'/lib/pdfjs-dist/web/cmaps/',
 cMapPacked: true,
})

const pdf = await loadingTask.promise

3)渲染时增加 intent: 'print'

  • 要点:提升文字/矢量渲染清晰度与稳定性,减少文字丢失或模糊。
js 复制代码
// 设置渲染意图为 'print',可提升文字/矢量渲染清晰度与稳定性
await page.render({
  canvasContext: context,
  viewport: viewport,
  intent: 'print' // 关键:使用"打印渲染"路径
}).promise

总结

  • "WPS PDF 正常、网页转 PDF 异常"的根因多在字体/CMap 资源缺失。
  • 落地方案核心:
    • 给 pdf.js 配置 cMapUrl / cMapPacked (必要时 standardFontDataUrl );
    • 渲染时使用 intent: 'print' ;
    • 将 ArrayBuffer 转为 Uint8Array 。
相关推荐
拖孩4 小时前
AI 帮我写的小程序「厨菜记」上线满月了,挣了两百块!!!
前端·后端·微信小程序
用户360055579004 小时前
Kestrel_VLLM - 13 · 为什么排除 MoE FFN Tiling 与 Tensor Core
前端
Hilaku4 小时前
AI 流式输出都用 SSE,什么时候必须换 WebSocket?
前端·javascript·程序员
奇牙coding4 小时前
Codex CLI 升级后多文件编辑静默失效:config.yaml 枚举变更排查与修复指南
java·前端·javascript·ai
小羊没烦恼!4 小时前
Ajax与JSON的一些总结
android·java·前端·数据库·mysql·算法·okhttp
程序员老赵4 小时前
Docker 部署 Filestash:轻松搭建浏览器文件管理平台
前端·docker·开源
夏天要喝冰可乐5 小时前
给 23 万字修仙小说造个阅读站:浏览器内打包 EPUB 分页阅读 + 三主题实战
前端·产品·全栈
茶底世界之下5 小时前
林间步道要不要加暗角?用 Awaking 把强度收在20%
前端·ios·图片资源
执子念的飞鱼5 小时前
纯前端预览 275 种文件格式是怎么做到的
前端·vue.js·react.js
行者全栈架构师5 小时前
【鸿蒙心迹】互动卡片全链路——把核心功能放到桌面(FormKit 实战)(HarmonyOS 7.x)
前端·算法·架构