简介这份资源面向需要在Web前端或Node环境中导出中文PDF的开发者尤其是使用pdfmake时遇到中文乱码、字体缺失问题的技术人员。资源包提供了一套可直接运行的示例方案帮助读者快速验证pdfmake加载中文字体并生成无乱码PDF的完整流程。压缩包共4个文件约2.36MB以JavaScript脚本和HTML页面为主其中脚本文件承担pdfmake核心库与中文字体映射的职责HTML页面则作为浏览器端的演示入口另含系统隐藏文件不影响使用。目前已有1313人学习下载说明该方案在实际开发中具有较高的参考价值。读者拿到资源后可直接在浏览器中打开演示页面点击下载即可观察中文PDF的导出效果并以此为基础替换字体或调整文档结构将方案迁移到自己的项目中省去反复排查字体配置与编码问题的时间适合作为pdfmake中文导出功能的入门与排错参考。1. 前端导出中文 PDF 的最后一公里pdfmake 到底能不能扛住做过管理后台的都知道导出 PDF 这个需求看着简单真上手十有八九要翻车。用浏览器原生window.print()吧样式全丢、分页靠玄学用后端生成吧服务器得装字体、装渲染引擎部署环境一换就崩。于是很多人把目光投向 pdfmake——一个纯前端的 PDF 生成库声明式文档定义不依赖后端浏览器里直接出文件。但真正让一线开发者头疼的从来不是“怎么画表格”而是“中文怎么不乱码”。默认的 Roboto 字体压根没有中文字形你写进去的汉字要么变成空白方块要么直接消失。这篇笔记就围绕 pdfmake 导出中文 PDF 无乱码文件这条主线把字体注册、vfs 打包、构建配置、常见翻车点一次讲透。适合正在做前端导出、被中文乱码卡住的同学也适合想搞清楚 pdfmake 字体机制、避免重复踩坑的熟手。2. pdfmake 的中文字体机制为什么默认导出全是方块2.1 默认字体 Roboto 的局限与乱码根因pdfmake 内部维护了一个虚拟文件系统vfs默认只打包了 Roboto 系列字体。PDF 本质上是一个自包含的文档格式它不会去读你系统里装了什么字体而是要求你把字体文件嵌入到 PDF 里。所以当你的文档里出现中文而 pdfmake 的 vfs 里没有对应中文字形时渲染引擎找不到字形就只能输出空白或者方块。很多人第一反应是“我在 CSS 里设了中文字体啊”但 pdfmake 不吃 CSS 那一套。它的字体配置是独立的一套对象必须在pdfMake.fonts里显式声明并且字体文件必须能被 pdfmake 访问到——要么通过 vfs 内嵌要么通过 URL 加载。这就是为什么你明明页面显示正常导出 PDF 却全是乱码。理解这一点很关键pdfmake 的字体是“打包进 PDF”的不是“引用系统字体”。所以中文字体文件动辄几 MB 到十几 MB全量打包会让生成的 JS 体积暴涨这也是后面要讲的按需裁剪的原因。2.2 字体注册的三种方式与选型对比实际项目里把中文字体交给 pdfmake 常见有三种做法各有适用场景。第一种是vfs内嵌。把字体文件转成 base64塞进 pdfmake 的虚拟文件系统。优点是纯前端、无网络依赖、离线可用缺点是体积大一个完整中文字体 base64 后可能十几 MB首屏加载压力明显。第二种是URL远程加载。在pdfMake.fonts里直接写字体文件的 URLpdfmake 在生成时去请求。优点是主包体积小缺点是有网络依赖跨域没配好会直接失败而且首次生成有等待。第三种是服务端预生成字体子集。把项目里实际用到的字符提取出来用工具生成一个只包含这些字形的子集字体再走 vfs 内嵌。优点是体积能压到几百 KB缺点是多了一步构建流程字符集变了要重新生成。方式体积网络依赖适用场景vfs 内嵌全量字体大10MB无离线工具、内网系统URL 远程加载小有公网后台、字体可 CDN 化子集字体内嵌小几百 KB无字符集固定的报表导出我一般会优先推荐子集方案尤其是导出内容字段相对固定的后台报表。如果字符集不可控比如用户能输入任意文字那就退回全量 vfs 内嵌用体积换稳定。2.3 字体文件格式的选择TTF 与 OTF 的差异pdfmake 底层依赖 pdfkit对字体格式有要求。实践中 TTF 的兼容性最好OTF尤其是 CFF 轮廓的 OTF在部分版本上会出现解析异常或者字形缺失。所以选字体时优先找 TTF 版本。另外要注意字体的授权。很多商业中文字体不允许嵌入分发用在项目里要确认许可。开源可商用的中文字体里思源系列、文泉驿系列是常见选择。字体文件本身不要用来源不明的版本否则可能出现部分汉字缺字这种问题排查起来非常费劲——因为不是全乱码而是个别字变方块很容易被忽略。3. 从零跑通一个中文 PDF 导出vfs 打包与字体注册实操3.1 环境准备与依赖安装先建一个最小可复现的工程。用 npm 装 pdfmake注意版本差异0.1.x 和 0.2.x 在 API 和构建产物上有区别0.2.x 对模块化更友好。这里以常见的 0.2.x 为例。# 初始化一个最小工程 mkdir pdfmake-cn-demo cd pdfmake-cn-demo npm init -y # 安装 pdfmake 和构建工具 npm install pdfmake npm install --save-dev vite装完之后node_modules/pdfmake/build/下会有pdfmake.min.js和vfs_fonts.js。默认的vfs_fonts.js只含 Roboto中文场景要替换成自己的字体 vfs。3.2 用脚本把 TTF 转成 pdfmake 可用的 vfspdfmake 官方提供了一个pdfmake-font-generator之类的工具思路但更通用的做法是自己写个小脚本把 TTF 读成 base64拼成 vfs 对象。下面这个脚本把字体文件转成vfs_fonts_cn.js。// build-vfs.js const fs require(fs); const path require(path); // 字体文件名放在项目根目录的 fonts 下 const fontFile SourceHanSansCN-Regular.ttf; const fontPath path.join(__dirname, fonts, fontFile); // 读取字体并转 base64 const base64 fs.readFileSync(fontPath).toString(base64); // pdfmake 的 vfs 结构文件名 - base64 内容 const vfs { [fontFile]: base64, }; // 输出成可直接引入的 JS 文件 const output window.pdfMake window.pdfMake || {}; window.pdfMake.vfs ${JSON.stringify(vfs)};; fs.writeFileSync(path.join(__dirname, vfs_fonts_cn.js), output); console.log(vfs 生成完成大小约, (output.length / 1024 / 1024).toFixed(2), MB);这段脚本的逻辑很直白读字体文件、转 base64、按 pdfmake 要求的文件名 - base64结构组装、挂到window.pdfMake.vfs上。参数上唯一要改的是fontFile换成你实际用的 TTF 文件名。生成后控制台会打印体积如果超过 10MB就该考虑子集方案了。提示base64 会让体积膨胀约 33%所以一个 8MB 的 TTF 转完接近 11MB这是正常的不是脚本写错了。3.3 注册字体并生成第一份中文 PDFvfs 有了接下来在业务代码里注册字体并调用。关键点是pdfMake.fonts里的normal、bold、italics、bolditalics四个键至少要提供normal否则默认样式找不到字体。// main.js import pdfMake from pdfmake/build/pdfmake; import pdfmake/build/vfs_fonts; // 默认 vfs会被下面覆盖 import ./vfs_fonts_cn.js; // 我们生成的中文字体 vfs // 把自定义 vfs 合并进 pdfMake pdfMake.vfs { ...pdfMake.vfs, ...window.pdfMake.vfs, }; // 注册中文字体 pdfMake.fonts { SourceHanSans: { normal: SourceHanSansCN-Regular.ttf, bold: SourceHanSansCN-Regular.ttf, // 没有粗体文件时先复用 italics: SourceHanSansCN-Regular.ttf, bolditalics: SourceHanSansCN-Regular.ttf, }, }; // 文档定义 const docDefinition { defaultStyle: { font: SourceHanSans, // 全局指定中文字体 fontSize: 12, }, content: [ { text: 中文导出测试这是一段包含标点、数字 123 和英文 ABC 的文本。, }, { table: { body: [ [字段, 值], [姓名, 张三], [备注, 无乱码测试通过], ], }, }, ], }; // 生成并下载 pdfMake.createPdf(docDefinition).download(中文导出测试.pdf);逻辑说明先引入 pdfmake 主包和默认 vfs再引入自定义 vfs 并合并避免覆盖掉 Roboto 导致英文也出问题。pdfMake.fonts里把四个样式键都指向同一个中文字体文件是因为很多中文字体没有独立的粗体、斜体文件先复用保证不报错。defaultStyle.font设为中文字体名这样所有内容默认走中文字体。参数上fontSize按需调table的body是二维数组第一行默认作为表头。生成后如果中文正常显示说明整条链路通了。3.4 在构建工具里处理字体体积上面是同步引入字体 base64 会直接进主包。用 Vite 或 Webpack 时更合理的做法是把 vfs 文件做成动态导入只在用户点击导出时加载。// 按需加载 vfs避免首屏体积膨胀 async function exportPdf() { const pdfMakeModule await import(pdfmake/build/pdfmake); const pdfMake pdfMakeModule.default; // 动态加载中文字体 vfs await import(./vfs_fonts_cn.js); pdfMake.vfs { ...pdfMake.vfs, ...window.pdfMake.vfs }; pdfMake.fonts { SourceHanSans: { normal: SourceHanSansCN-Regular.ttf, bold: SourceHanSansCN-Regular.ttf, italics: SourceHanSansCN-Regular.ttf, bolditalics: SourceHanSansCN-Regular.ttf, }, }; pdfMake.createPdf({ defaultStyle: { font: SourceHanSans, fontSize: 12 }, content: [{ text: 按需加载导出的中文内容 }], }).download(按需导出.pdf); }这样首屏不背字体体积只有真正导出时才拉取。代价是首次导出有加载等待可以在按钮上加个 loading 状态。如果项目对首屏和导出体验都敏感就回到子集方案把体积压到几百 KB 再同步引入。4. 中文乱码排查清单五个真实翻车现场4.1 现象中文全部变方块英文正常原因defaultStyle.font没设成中文字体或者pdfMake.fonts里注册的字体名和defaultStyle.font对不上。英文正常说明 Roboto 还在生效中文没有对应字形。解决检查pdfMake.fonts的键名和defaultStyle.font的值是否完全一致大小写敏感。再确认 vfs 里确实有那个字体文件可以用Object.keys(pdfMake.vfs)打印出来核对。4.2 现象部分汉字缺字个别字变空白原因字体文件本身不含这些字形常见于子集字体生成时字符集没覆盖全或者用了裁剪过的字体版本。解决换用完整版字体或者重新生成子集时把字符集范围放宽。排查时可以把出问题的字单独拎出来用字体查看工具确认是否在字形表里。4.3 现象控制台报 “File not found” 或字体加载失败原因vfs 里的文件名和pdfMake.fonts里写的文件名不一致或者 vfs 根本没合并进去。解决统一文件名建议用英文名避免编码问题。确认pdfMake.vfs合并顺序正确自定义 vfs 要在默认 vfs 之后合并。4.4 现象本地正常打包上线后乱码原因构建工具把 vfs 文件 tree-shaking 掉了或者动态导入路径在生产环境不对。解决检查构建产物里 vfs 是否真的被打进去动态导入的路径用相对路径并确认打包配置。必要时把 vfs 文件放到 public 目录用绝对路径引入。4.5 现象导出大文档时页面卡死原因全量中文字体 base64 解析 大量内容渲染主线程压力大。解决改用子集字体或者把导出放到 Web Worker 里。pdfmake 支持在 worker 中运行把字体和文档定义传进去主线程只负责触发和接收结果。5. 进阶字体子集裁剪与导出稳定性验证子集裁剪是中文 PDF 导出里性价比最高的一步。思路很简单把项目里实际会用到的字符收集起来生成一个只含这些字形的字体文件。常见做法是用fonttools的pyftsubset命令。# 安装 fonttools pip install fonttools # 从完整字体裁剪出子集 # --text 指定要保留的字符--output-file 指定输出 pyftsubset fonts/SourceHanSansCN-Regular.ttf \ --text中文导出测试字段姓名备注无乱码通过0123456789ABCabc。、 \ --output-filefonts/subset.ttf \ --flavorwoff2参数说明--text是要保留的字符集合实际项目里可以从后端接口拉取所有可能的字段值去重后拼成字符串--output-file是输出路径--flavor指定输出格式pdfmake 场景建议输出 TTF所以可以去掉--flavor或者改成--flavorttf不适用时直接不写。裁剪完再用第 3 章的脚本转 vfs体积通常能从十几 MB 降到几百 KB。验证稳定性时我习惯做三件事。第一准备一份“字符覆盖测试文档”把项目里所有可能出现的字符类型都塞进去——中文、英文、数字、标点、生僻字、emoji如果业务需要导出后逐页检查。第二在 Chrome、Firefox、Safari 三个浏览器各跑一遍pdfmake 在不同浏览器下的字体解析偶有差异。第三模拟弱网和离线确认 vfs 内嵌方案不依赖网络。还有一个容易被忽略的点PDF 里的字体嵌入状态。用 Adobe Acrobat 或类似工具打开生成的 PDF查看字体属性确认中文字体是“嵌入”而不是“引用”。如果是引用换台机器打开就可能乱码。pdfmake 默认会嵌入 vfs 里的字体但如果你用了 URL 加载方式要确认字体确实被嵌进去了。从那以后我每次做中文导出都会先跑一遍字符覆盖测试再确认字体嵌入状态最后才交付。这个习惯帮我挡掉过好几次“本地好好的客户那边全是方块”的尴尬。希望帮到你。本文还有配套的精品资源点击获取