别再为jspdf中文乱码发愁了!手把手教你用自定义字体搞定text和autotable

张开发
2026/5/18 13:13:45 15 分钟阅读
别再为jspdf中文乱码发愁了!手把手教你用自定义字体搞定text和autotable
彻底解决jsPDF中文乱码从字体配置到autotable实战指南每次看到导出PDF时那些变成□□□的中文字符是不是感觉血压瞬间飙升作为前端开发者我们经常需要将数据导出为PDF格式而jsPDF无疑是这个领域最受欢迎的库之一。但当遇到中文内容时原生支持的字体往往会导致乱码问题特别是结合autotable插件生成复杂表格时问题会更加棘手。1. 为什么jsPDF会出现中文乱码jsPDF默认只包含几种基本拉丁字体如Courier、Helvetica等这些字体根本不包含中文字形。当尝试渲染中文时系统找不到对应的字形就会显示为乱码或方框。要解决这个问题我们必须引入支持中文的自定义字体并正确配置到jsPDF中。常见误区警示单纯调用setFont()而不先添加字体文件是无效的即使添加了字体如果autotable插件未单独配置字体样式表格内容仍可能乱码TypeScript项目需要特别注意文件后缀和模块导入方式2. 获取并转换中文字体2.1 选择合适的中文字体首先需要获取一个支持中文的TTF字体文件。推荐使用以下几种开源字体思源黑体Source Han Sans阿里巴巴普惠体站酷酷圆体这些字体都可以免费商用避免版权风险。Windows系统自带的字体如微软雅黑虽然也可以使用但需要注意版权限制。2.2 使用fontconverter转换字体jsPDF提供了一个在线字体转换工具可以将TTF转换为它能够识别的JS格式访问jsPDF仓库中的fontconverter.html文件上传你选择的TTF字体文件设置字体名称建议使用全小写避免兼容性问题点击Create按钮生成JS文件常见问题排查如果遇到权限错误尝试将字体文件复制到其他目录再上传生成的JS文件大约有几MB大小过小可能意味着转换失败确保字体名称不包含特殊字符或空格// 生成的字体文件示例结构 import { jsPDF } from jspdf; var font AAEAAAAVA...; // 压缩后的字体数据 var callAddFont function() { this.addFileToVFS(myfont-normal.ttf, font); this.addFont(myfont-normal.ttf, myfont, normal); }; jsPDF.API.events.push([addFonts, callAddFont]);3. 在项目中集成自定义字体3.1 基本集成方法将生成的字体JS文件放入你的项目目录中通常在src/assets/fonts/然后在需要使用的地方导入import ./assets/fonts/myfont-normal; // 路径根据实际项目调整对于TypeScript项目需要将文件后缀改为.ts否则可能报404错误// 修改前myfont-normal.js // 修改后myfont-normal.ts3.2 替代方案直接嵌入字体数据如果不想单独维护字体文件也可以直接将字体数据嵌入代码中const fontData AAEAAAAVA...; // 从生成的JS文件中复制 const doc new jsPDF(); doc.addFileToVFS(myfont.ttf, fontData); doc.addFont(myfont.ttf, myfont, normal); doc.setFont(myfont);这种方法适合小型项目但会使代码文件变大不利于维护。4. 配置autotable插件支持中文即使设置了全局字体autotable插件也需要单独配置才能正确显示中文import { jsPDF } from jspdf; import jspdf-autotable; function exportPDF(data: any[], filename: string) { const doc new jsPDF() as any; // 设置全局字体 doc.setFont(myfont); // 生成表格 doc.autoTable({ body: data, columns: [ { header: 姓名, dataKey: name }, { header: 年龄, dataKey: age } ], styles: { font: myfont, // 必须单独指定表格字体 textColor: [0, 0, 0] // 黑色文字 }, headStyles: { fillColor: [51, 51, 51], // 表头背景色 textColor: [255, 255, 255] // 白色文字 } }); doc.save(${filename}.pdf); }关键配置项styles.font: 设置表格内容字体headStyles.font: 单独设置表头字体如果不设置会继承stylescolumnStyles: 可以为特定列设置不同的字体样式5. 高级技巧与性能优化5.1 多字体支持如果你的应用需要支持多种字体如中英文混排可以注册多个字体// 注册中文字体 doc.addFont(myfont.ttf, myfont, normal); // 注册英文字体 doc.addFont(helvetica.ttf, helvetica, normal); // 使用时根据内容切换字体 doc.setFont(helvetica); doc.text(English text, 10, 10); doc.setFont(myfont); doc.text(中文内容, 10, 20);5.2 字体子集化完整的中文字体通常很大几MB可以通过子集化只包含实际用到的字符使用fonttools等工具分析你的应用中实际使用的中文字符生成只包含这些字符的字体子集转换子集字体供jsPDF使用这可以显著减小最终PDF的体积。5.3 服务端字体缓存对于频繁生成PDF的应用可以考虑在服务端缓存转换后的字体文件// 服务端代码示例Node.js const fs require(fs); const { jsPDF } require(jspdf); let cachedFont null; function getFont() { if (!cachedFont) { cachedFont fs.readFileSync(./assets/myfont-normal.js, utf8); } return cachedFont; } // 在API响应中返回字体数据 app.get(/api/font, (req, res) { res.send(getFont()); });6. 常见问题解决方案6.1 字体未生效的可能原因字体名称不匹配检查setFont()调用是否与addFont()时指定的名称一致未正确导入字体文件确保字体JS文件被正确导入且路径正确autotable未单独配置记住表格需要单独设置字体样式TypeScript文件后缀问题确保.ts后缀已被正确处理6.2 表格样式被覆盖autotable的样式优先级如下columnStyles列样式headStyles/bodyStyles表头/表体样式styles全局样式如果发现样式不符合预期检查是否有更高优先级的样式覆盖了你的设置。6.3 性能优化建议对于大量数据考虑分页生成使用web worker在后台生成PDF避免阻塞UI对于相同模板的PDF可以预先生成并缓存部分内容// 使用web worker生成PDF的示例 const pdfWorker new Worker(./pdf.worker.js); pdfWorker.postMessage({ type: generate, data: tableData, config: { font: myfont, styles: { /* ... */ } } }); pdfWorker.onmessage (event) { if (event.data.type done) { const blob new Blob([event.data.pdf], { type: application/pdf }); saveAs(blob, report.pdf); } };7. 完整实战示例下面是一个结合React和TypeScript的完整示例// PDFGenerator.tsx import React from react; import { jsPDF } from jspdf; import jspdf-autotable; import ./fonts/sourcehansans-normal.ts; interface UserData { id: number; name: string; email: string; department: string; } interface PDFGeneratorProps { data: UserData[]; filename?: string; } const PDFGenerator: React.FCPDFGeneratorProps ({ data, filename export }) { const exportToPDF () { const doc new jsPDF() as any; // 设置中文字体 doc.setFont(sourcehansans); // 添加标题 doc.setFontSize(18); doc.text(员工信息表, 105, 15, { align: center }); // 准备表格数据 const tableData data.map(user [ user.id, user.name, user.email, user.department ]); // 生成表格 doc.autoTable({ startY: 25, head: [[ID, 姓名, 邮箱, 部门]], body: tableData, styles: { font: sourcehansans, fontSize: 10, cellPadding: 3, halign: center }, headStyles: { fillColor: [51, 122, 183], textColor: [255, 255, 255], fontStyle: bold }, columnStyles: { 0: { cellWidth: 15 }, 1: { cellWidth: 25 }, 2: { cellWidth: 45 }, 3: { cellWidth: 30 } } }); doc.save(${filename}.pdf); }; return ( button onClick{exportToPDF} classNamepdf-export-button 导出PDF /button ); }; export default PDFGenerator;关键点说明将PDF生成逻辑封装成可复用的React组件支持自定义文件名和数据类型详细配置了表格样式和列宽添加了中文标题并居中显示8. 跨框架解决方案虽然上面的示例基于React但解决方案同样适用于其他框架8.1 Vue 3实现// PdfExportButton.vue script setup import { jsPDF } from jspdf; import jspdf-autotable; import ./fonts/sourcehansans-normal; const props defineProps({ data: Array, filename: { type: String, default: export } }); const exportPDF () { const doc new jsPDF(); doc.setFont(sourcehansans); // ...其余逻辑与React示例类似 }; /script template button clickexportPDF classpdf-button 导出PDF /button /template8.2 Angular实现// pdf.service.ts import { Injectable } from angular/core; import { jsPDF } from jspdf; import jspdf-autotable; import ./sourcehansans-normal; Injectable({ providedIn: root }) export class PdfService { exportTable(data: any[], headers: string[], filename: string export) { const doc new jsPDF() as any; doc.setFont(sourcehansans); doc.autoTable({ head: [headers], body: data, styles: { font: sourcehansans } }); doc.save(${filename}.pdf); } }9. 自动化构建集成对于大型项目可以考虑将字体处理集成到构建流程中在package.json中添加字体转换脚本{ scripts: { convert-font: node scripts/convertFont.js } }创建convertFont.js脚本// scripts/convertFont.js const fs require(fs); const puppeteer require(puppeteer); async function convertFont(ttfPath, outputPath) { const browser await puppeteer.launch(); const page await browser.newPage(); await page.goto(file:///path/to/fontconverter.html); // 模拟文件上传和转换操作 // ... const fontJs await page.evaluate(() { return document.getElementById(output).value; }); fs.writeFileSync(outputPath, fontJs); await browser.close(); } convertFont(./fonts/SourceHanSans.ttf, ./src/assets/fonts/sourcehansans-normal.ts);在CI/CD流程中自动运行字体转换# .github/workflows/build.yml jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkoutv2 - run: npm install - run: npm run convert-font - run: npm run build10. 测试与验证策略确保PDF生成功能稳定可靠需要建立完善的测试方案10.1 单元测试使用Jest测试PDF生成逻辑// pdfGenerator.test.ts import { jsPDF } from jspdf; import { exportToPDF } from ./pdfGenerator; test(generates PDF with Chinese characters, () { const mockData [ { id: 1, name: 张三, email: zhangsanexample.com } ]; exportToPDF(mockData); const doc new jsPDF(); const fontList doc.getFontList(); expect(fontList).toHaveProperty(sourcehansans); // 更多断言... });10.2 视觉回归测试使用类似jest-image-snapshot的工具比较生成的PDFtest(PDF visual snapshot, async () { const pdfBuffer await generateTestPDF(); const images await pdfToImages(pdfBuffer); // 将PDF转换为图片 expect(images[0]).toMatchImageSnapshot(); });10.3 端到端测试使用Cypress测试完整用户流程// cypress/integration/pdfExport.spec.js describe(PDF Export, () { it(successfully exports Chinese PDF, () { cy.visit(/report); cy.get(.export-button).click(); // 验证下载的文件 cy.readFile(cypress/downloads/report.pdf).should(exist); // 更多验证... }); });11. 替代方案评估虽然本文聚焦jsPDF但了解其他选择也很重要方案优点缺点中文支持jsPDFautotable纯前端方案轻量级复杂布局支持有限需自定义字体PDFKit强大灵活支持流式生成学习曲线较陡需自定义字体Puppeteer完美还原HTML样式需要服务端支持原生支持html2pdf.js基于html转换简单易用体积较大原生支持服务端生成不依赖客户端性能增加服务器负载依赖服务端环境对于简单需求jsPDFautotable仍然是最轻量便捷的方案。如果你的内容非常复杂或者需要精确还原HTML样式可以考虑Puppeteer等方案。12. 移动端适配注意事项在移动设备上生成PDF时还需要考虑性能问题移动设备CPU较弱大数据量PDF可能导致页面卡顿内存限制某些低端设备可能无法处理大字体文件用户交互移动端下载体验与桌面不同可能需要特殊处理优化建议显示生成进度提示考虑分块生成PDF对于超大PDF提示用户连接WiFi使用Web Worker避免阻塞UI线程// 移动端进度提示示例 function exportPDF(data, updateProgress) { return new Promise((resolve) { const doc new jsPDF(); const chunkSize 50; const chunks Math.ceil(data.length / chunkSize); let currentChunk 0; function processNextChunk() { const start currentChunk * chunkSize; const end start chunkSize; const chunkData data.slice(start, end); // 添加当前分块到PDF doc.autoTable({ body: chunkData, startY: currentChunk 0 ? 20 : doc.lastAutoTable.finalY 10 }); currentChunk; updateProgress(Math.min(100, (currentChunk / chunks) * 100)); if (currentChunk chunks) { setTimeout(processNextChunk, 0); // 让UI有机会更新 } else { resolve(doc); } } processNextChunk(); }); } // 使用示例 exportPDF(largeData, (progress) { document.getElementById(progress).textContent ${progress}%; }).then((doc) { doc.save(large-report.pdf); });13. 安全与合规考量使用自定义字体时务必注意字体授权确保你拥有所用字体的合法授权数据安全字体文件可能包含敏感元数据必要时进行清理GDPR合规如果处理用户数据确保PDF生成过程符合隐私法规内容审核动态生成的PDF内容应经过适当过滤对于企业级应用建议建立内部字体库统一管理授权字体对用户上传的字体文件进行安全检查记录PDF生成日志以便审计14. 调试技巧与工具遇到问题时这些工具和技术可能帮到你检查注册的字体列表const doc new jsPDF(); console.log(doc.getFontList());查看PDF内部结构使用文本编辑器打开PDF搜索字体名称使用pdfinfo等工具分析PDF元数据调试autotable布局doc.autoTable({ // ...其他配置 didDrawPage: (data) { console.log(Page drawn, data); }, willDrawCell: (data) { console.log(Drawing cell, data); } });使用浏览器PDF查看器Chrome内置的PDF查看器可以显示详细的渲染信息比较不同查看器的渲染结果排查兼容性问题15. 未来技术演进随着浏览器技术的进步PDF生成方案也在不断发展CSS Paged Media通过CSS直接控制打印样式有望实现更简单的PDF生成WebAssembly使用wasm加速字体处理和PDF生成新的字体格式可变字体和WOFF2等格式可能带来更好的压缩效果浏览器原生API未来可能会有更强大的原生PDF生成API作为开发者保持对这些趋势的关注但现阶段jsPDF自定义字体仍然是最可靠的跨平台解决方案。

更多文章