You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

NestJS中使用pdfMake生成PDF时遇Roboto-Medium.ttf字体缺失错误

解决NestJS中pdfMake的字体缺失与Promise未处理问题

一、修复"虚拟文件系统中未找到Roboto-Medium.ttf"错误

pdfMake默认依赖Roboto字体,但Node.js环境(如NestJS)需手动配置字体路径:

  1. 确认依赖安装
    确保已安装核心包与字体包,未安装则执行:

    npm install pdfmake
    npm install @pdfmake/font-roboto
    
  2. 注册默认字体
    在代码开头导入并注册字体到pdfMake虚拟文件系统:

    import * as pdfMake from 'pdfmake/build/pdfmake';
    import * as pdfFonts from 'pdfmake/build/vfs_fonts';
    
    // 绑定默认字体资源
    pdfMake.vfs = pdfFonts.pdfMake.vfs;
    
  3. 自定义字体备选方案
    若默认字体包仍失效,可手动下载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逻辑,且异步回调未捕获错误,修改如下:

  1. 正确封装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; // 抛出错误供上层处理,或返回自定义错误响应
    }
    
  2. 全局错误兜底
    调用该生成方法时,需确保外层有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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.27 18:23:27