NestJS中使用pdfMake生成PDF时遇Roboto-Medium.ttf字体缺失错误
解决NestJS中pdfMake的字体缺失与Promise未处理问题
一、修复"虚拟文件系统中未找到Roboto-Medium.ttf"错误
pdfMake默认依赖Roboto字体,但Node.js环境(如NestJS)需手动配置字体路径:
确认依赖安装
确保已安装核心包与字体包,未安装则执行:npm install pdfmake npm install @pdfmake/font-roboto注册默认字体
在代码开头导入并注册字体到pdfMake虚拟文件系统:import * as pdfMake from 'pdfmake/build/pdfmake'; import * as pdfFonts from 'pdfmake/build/vfs_fonts'; // 绑定默认字体资源 pdfMake.vfs = pdfFonts.pdfMake.vfs;自定义字体备选方案
若默认字体包仍失效,可手动下载Roboto-Medium.ttf文件放入项目assets/fonts目录,然后自定义注册:import * as fs from 'fs'; import * as path from 'path'; import * as pdfMake from 'pdfmake/build/pdfmake'; // 读取本地字体文件 const robotoMedium = fs.readFileSync(path.join(__dirname, '../assets/fonts/Roboto-Medium.ttf'), 'base64'); // 注册字体 pdfMake.addFonts({ 'Roboto-Medium': { normal: robotoMedium, bold: robotoMedium, italics: robotoMedium, bolditalics: robotoMedium } }); // 在文档定义中指定默认字体 const documentDefinition: any = { defaultStyle: { font: 'Roboto-Medium' }, // 原header、content、styles配置... };
二、修复UnhandledPromiseRejection错误
原代码未正确处理Promise的resolve/reject逻辑,且异步回调未捕获错误,修改如下:
正确封装pdf.getBase64为Promise
替换原空Promise逻辑,同时添加错误捕获:try { const pdfBase64 = await new Promise<string>((resolve, reject) => { pdf.getBase64(resolve, reject); }); const newPlan = await this.planRepository.save({ ...payload, buffer64: pdfBase64, }); return newPlan; } catch (err) { this.logger.error('PDF生成或保存失败', err); throw err; // 抛出错误供上层处理,或返回自定义错误响应 }全局错误兜底
调用该生成方法时,需确保外层有try/catch或.catch()处理,避免未捕获的Promise拒绝。
完整修改后代码片段
import * as pdfMake from 'pdfmake/build/pdfmake'; import * as pdfFonts from 'pdfmake/build/vfs_fonts'; // 注册默认字体资源 pdfMake.vfs = pdfFonts.pdfMake.vfs; async function generateAndSavePlan(payload: any) { const documentDefinition: any = { defaultStyle: { font: 'Roboto-Medium' }, header: { text: payload.plan_name, alignment: 'center', style: 'header' }, content: [], styles: { header: { fontSize: 18, bold: true, margin: [10, 10, 10, 10] }, subheader: { fontSize: 16, bold: true, margin: [0, 10, 0, 5] }, tableExample: { margin: [5, 5, 0, 15], padding: [2, 2, 2, 2], }, tableHeader: { bold: true, fontSize: 13, color: 'black' } }, }; payload.all_exercises.forEach((item: any) => { const { day, exercises } = item; const bodyData: any = [ [{ text: 'Name', color: 'white' }, { text: 'Sets', color: 'white' }, { text: 'Reps', color: 'white' }, { text: 'Rest', color: 'white' }], ]; exercises.map((exercise: any) => { const { exercise_name, sets, reps, rest = '1min', video_link } = exercise; bodyData.push( [{ text: exercise_name, link: video_link, color: 'blue' }, sets, reps, rest] ) }) documentDefinition.content.push( { text: day, style: 'header' }, { style: 'tableExample', table: { widths: [200, 70, 70, 70], body: bodyData, }, layout: layout, }) }); const pdf = pdfMake.createPdf(documentDefinition); this.logger.log('Initialized pdfMake'); try { const pdfBase64 = await new Promise<string>((resolve, reject) => { pdf.getBase64(resolve, reject); }); const newPlan = await this.planRepository.save({ ...payload, buffer64: pdfBase64, }); return newPlan; } catch (err) { this.logger.error('PDF生成或保存失败', err); throw err; } }
内容的提问来源于stack exchange,提问作者Chris
相关产品推荐
相关产品推荐

