Skip to content

前端生成pdf

两种生成 pdf 方案

html2canvas + jspdf

因为 jspdf 不支持中文,所以只能通过 html2canvas 将页面样式转换成图片,再把图片插入 jspdf。

html2canvas + jspdf 的方案的优点:

  • 能使用熟悉得 html + css 方式来编写。开发效率高
  • html2canvas 支持网络图片

但是由于其是通过图片生成 pdf 的方式,所以会有如下缺点:

  • 想要高质量的情况下,那么图片清晰度必须高,从而导致文件过大。
  • 生成的 pdf 不能复制里面的文字,因为本质是一张图片
  • 放大和缩小的时候,某些比例情况下会显示比较模糊。
  • 不能自动进行内容截断
  • 由于是插入图片,所以页头和页尾也要自己解决

pdfmake

相比于 jsPdf,pdfMake拥有支持中文、 画质更高(因为 jsPdf 不支持中文,只能用图片生成),支持自动截断等优点。

由于网上对于 html2canvas + jspdf 方案有很多的讲解,所以本文只介绍 pdfmake 方案

如何让 pdfMake 支持中文

pdfMake 默认是不支持中文的,但是该库提供了自定义字体功能。也就是我们只要有相应字体的 ttf 文件,就能让 pdfMake 支持各种各样的字体。

  1. 首先在你的项目下安装 pdfMake 包
npm install pdfmake
  1. 找到包的路径 /node_modules/pdfmake/
  2. 创建 examples/fonts 两个文件夹,此时完整路径为 /node_modules/pdfmake/examples/fonts
  3. 把你的字体文件放到 fonts 里面
  4. 回到 pdfmake 根目录,执行指令
node build-vfs.js "./examples/fonts"
  1. 成功后,你能在 /node_modules/pdfmake/build 下看到 vfs_fonts.js 文件

TIP

相应字体可以到 github 上找 https://github.com/search?q=Source+Han+Serif

  1. 在代码中引入新字体
js
import pdfMake from 'pdfmake/build/pdfmake'
import pdfFonts from 'pdfmake/build/vfs_fonts'

pdfMake.vfs = pdfFonts.pdfMake.vfs

pdfMake.fonts = {
    // webfont是字体名,可以自定义,下面需要用这个名字配置字体
    simsun: {
    // simhei.ttf 这个文件已经在 我们生成的 vfs_font.js 文件中,且已经引入,所以可以直接使用
    // 这里的字符串字体名称实际上映射的是node_modules里examples/fonts里对应的文件名
        normal: 'SourceHanSerifCN-xxx.ttf',
        bold: 'SourceHanSerifCN-xxx.ttf',
        italics: 'SourceHanSerifCN-xxx.ttf',
        bolditalics: 'SourceHanSerifCN-xxx.ttf'
    }
}

const docDefinition = {
    defaultStyle: {
    // 设置我们定义的字体为默认字体
        font: 'simsun',
        bold: false
    },
}

常用语法

文字

  1. 直接写入
js
const docDefinition = {
    content: [
        '这里是文字'
    ]
}
  1. 通过对象写入
js
const docDefinition = {
    content: [
        { text: '这里是文字' }
    ]
}

特有属性

  • preserveLeadingSpaces: boolean 是否支持保存开头空格
  • leadingIndent: number 相当于text-indent

样式

pdfMake 支持的样式属性:

  • font: string-设置字体
  • fontSize: number-设置字体大小(单位pt)
  • fontFeatures: array-设置字体序列
  • lineHeight: number-行高
  • bold: boolean-是否加粗
  • italics: boolean-是否倾斜
  • alignment: string-字体位置(left center right justify)
  • characterSpacing: number-字体间隙(单位pt)
  • color: string-字体颜色
  • background: string -背景颜色
  • markerColor: markerColor-列表的符号颜色
  • decoration: string-设置字体装饰(underline lineThrough overline)
  • decorationStyle: string-辅助decoration样式(dashed dotted double wavy)
  • decorationColor: string-辅助decoration颜色

编写方式

  1. 内嵌方式
js
const docDefinition = {
    content: [
        { text: '这里是文字', color: 'red' }
    ]
}
  1. 类名方式
js
const docDefinition = {
    content: [
        { text: '这里是文字', style: 'title' }
    ],
    styles: {
        title: {
            color: '#006fc0'
        }
    }
}

表格

使用表格

js
const docDefinition = {
  content: [
    {
      layout: 'lightHorizontalLines', // 使用的表格类型
      table: {
        headerRows: 1, // 定义哪一行为表头
        widths: [ '*', 'auto', 100, '*' ],
        body: [
          [ 'First', 'Second', 'Third', 'The last one' ],
          [ 'Value 1', 'Value 2', 'Value 3', 'Value 4' ],
          [ { text: 'Bold value', bold: true }, 'Val 2', 'Val 3', 'Val 4' ]
        ]
      }
    }
  ]
};

table属性

  • headerRows: number 设置由哪一行数据为表头
  • dontBreakRows: boolean 当某一行位置不足时,是否全部数据放到下一页

cell属性

  • colSpan 合并行
js
{ text: 'Bold value', bold: true, colSpan: 2 }
  • rowSpan 合并列
js
{ text: 'Bold value', bold: true, rowSpan: 2 }
  • border
js
{ text: 'Bold value', bold: true, border: [ true, true, true, true] }

TIP

在使用 colSpan 和 rowSpan 时,被合并的行或列依然需要赋空值( '' 或 {} )

自定义表格

js
pdfMake.tableLayouts = {
  // key 为自定义表格名称
  exampleLayout: {
    hLineWidth: function (i, node) {
      if (i === 0 || i === node.table.body.length) {
        return 0;
      }
      return (i === node.table.headerRows) ? 2 : 1;
    },
    vLineWidth: function (i) {
      return 0;
    },
    hLineColor: function (i) {
      return i === 1 ? 'black' : '#aaa';
    },
    paddingLeft: function (i) {
      return i === 0 ? 0 : 8;
    },
    paddingRight: function (i, node) {
      return (i === node.table.widths.length - 1) ? 0 : 8;
    }
  }
}

const docDefinition = {
  content: [
    {
      layout: 'exampleLayout' // 指定使用 exampleLayout 表格
    }
  ]
}

图片

在插入图片时,pdfmake 要求现将图片转换为Data URL*(base64 和 http)才可以,而 pdfmake 允许直接指定路径,但是却是有条件的,必须在node.js作为服务器才可以。

使用方式

  1. 直接使用
js
const docDefinition = {
  content: [
    {
      image: 'data:image/jpeg;base64,...encodedContent...'
    },
  ]
}
  1. 通过全局变量使用
js
const docDefinition = {
  content: [
    {
      image: imagesName
    },
  ],
  images: {
    imagesName: 'data:image/jpeg;base64,...encodedContent...'
  }
}

特有属性

  1. width:number
  2. height:number
  3. fit: array [100, 100] 将图像放在一个矩形里面

margin

支持使用在各个标签里

margin: [left, top, right, bottom]
margin: [ 5, 2, 10, 20 ]

margin: [horizontal, vertical]
margin: [ 5, 2]

margin: [left = 5, top = 5, right = 5, bottom = 5]
margin: [ 5 ]

列布局 columns

js
var docDefinition = {
  content: [
    {
      columns: [
        {
          width: 'auto',
          text: 'First column'
        },
        {
          width: '25%',
          text: 'First column'
        }
      ]
    }
  ]
}

特有属性

  • columnGap: number 列间隙

stack

相当于 html 的 div

js
var docDefinition = {
  content: [
    {
      stack: [
        'paragraph A',
        'paragraph B',
        'these paragraphs will be rendered one below another inside the column'
      ],
      fontSize: 15
    }
  ]
};

特点:能统一给 stack 里面的元素修改样式

总结

优点

  1. 支持自动截断分页
  2. 支持自定义字体,所以能支持中文
  3. 相比 jsPdf 生成速度更快、画质更高、文件更小。

缺点

  1. 相比 jsPdf,不能使用熟悉得 html + css 方式来编写。
  2. 表格内容不支持垂直居中
  3. 不支持本地图片,只能 base64 或 网络图片
  4. 相比 jsPdf 直接用图片生成 pdf 的方案,会有一些样式特别难实现或实现不了等问题
  5. 需要引入比较大的字体包。

字体包优化

中文字体并不像英文一样,仅靠 26 个字母就能组成所有的单词,只能一个字一个字的录入。所以中文字体包通常都会非常大。

解决方法也很简单,就是把常用的字体从字体包里面提取出来,从而大大的减少字体体积。

缩小字体包

  1. 提取工具使用 font-spider 这个插件
    全局安装
npm install font-spider -g
  1. 新建一个 index.html 文件,并且定义使用字体
css
@font-face {
    font-family: "songti";
    src: url("./font/songti.ttf");
    font-weight: normal;
    font-style: normal;
}

div {
    font-family: "songti";
}
  1. 将常用的字体写进页面
html
<div>超 人 常 ...</div>
  1. 执行指令分离字体
font-spider ./index.html
  1. 成功分离后,你会发现字体包大大的减少了

使用 ES6 的动态加载

在点击生成 pdf 时,才导入生成 pdf 所需包和字体。这样配合生成时间的 loading 效果,能大大的增加用户体验。

js
const font = await import("./font.js");