前端用 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 。
相关推荐
福兮说6 小时前
前端下载大文件,点了按钮半天没反应:fetch + blob 的三个问题和三种替代写法
前端·javascript·性能优化·文件下载
GreenTea6 小时前
我把 Agent 的 while 循环拆掉了:一种你可能没想到的 Agent 架构
前端·后端·架构
科技观察哨7 小时前
六足平台选型与纳米定位系统集成:HEB-640六自由度位移台在半导体光刻对准中的参数边界与国产替代评估
前端·人工智能
niucloud-admin7 小时前
JAVA V6 多商户商城 开发文档——web前端
前端
颜进强7 小时前
16 · NestJS LifecycleEvents 生命周期事件:五个钩子、三条边界,和 rag-server 的优雅停机
前端·后端·ai编程
今年下半年9 小时前
【VUE】整合腾讯地图、自定义区域边界、村委名称及资产统计(放大显示资产点位)
前端·javascript·vue.js
郑州光合科技余经理9 小时前
同城电商系统:库存变更怎么同步到订单
java·开发语言·前端·后端·uni-app·php·ai编程
前端snow11 小时前
ai agent --- DeepAgents 中间件
前端
data analyse 45611 小时前
报表能自定义的国内统计工具有哪些
前端·数据分析