前端用 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
相关推荐
美狐美颜sdk5 小时前
跨平台直播美颜sdk集成攻略:Android、iOS与Web的统一方案
android·前端·ios
Airser6 小时前
npm启动Taro框架报错
前端·npm·taro
Anlici6 小时前
连载小说大学生课设 需求&架构
前端·javascript·后端
2501_938769997 小时前
React Server Components 进阶:数据预取与缓存
前端·react.js·缓存
蒜香拿铁8 小时前
Angular【基础语法】
前端·javascript·angular.js
xiaoxiao无脸男8 小时前
纯css:一个好玩的按钮边框动态动画
前端·css·css3
rookie_fly8 小时前
基于Vue的数字输入框指令
前端·vue.js·设计模式
元直数字电路验证9 小时前
ASP.NET Core Web APP(MVC)开发中无法全局配置 NuGet 包,该怎么解?
前端·javascript·ui·docker·asp.net·.net
rexling19 小时前
【Spring Boot】Spring Boot解决循环依赖
java·前端·spring boot
我有一棵树9 小时前
Vue 项目中全局样式的正确写法:不要把字体和主题写在 #app 上
前端·javascript·vue.js