前端生成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 支持各种各样的字体。
- 首先在你的项目下安装 pdfMake 包
npm install pdfmake
- 找到包的路径 /node_modules/pdfmake/
- 创建 examples/fonts 两个文件夹,此时完整路径为 /node_modules/pdfmake/examples/fonts
- 把你的字体文件放到 fonts 里面
- 回到 pdfmake 根目录,执行指令
node build-vfs.js "./examples/fonts"
- 成功后,你能在 /node_modules/pdfmake/build 下看到 vfs_fonts.js 文件
TIP
相应字体可以到 github 上找 https://github.com/search?q=Source+Han+Serif
- 在代码中引入新字体
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
},
}
常用语法
文字
- 直接写入
js
const docDefinition = {
content: [
'这里是文字'
]
}
- 通过对象写入
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颜色
编写方式
- 内嵌方式
js
const docDefinition = {
content: [
{ text: '这里是文字', color: 'red' }
]
}
- 类名方式
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作为服务器才可以。
使用方式
- 直接使用
js
const docDefinition = {
content: [
{
image: 'data:image/jpeg;base64,...encodedContent...'
},
]
}
- 通过全局变量使用
js
const docDefinition = {
content: [
{
image: imagesName
},
],
images: {
imagesName: 'data:image/jpeg;base64,...encodedContent...'
}
}
特有属性
- width:number
- height:number
- 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 里面的元素修改样式
总结
优点
- 支持自动截断分页
- 支持自定义字体,所以能支持中文
- 相比 jsPdf 生成速度更快、画质更高、文件更小。
缺点
- 相比 jsPdf,不能使用熟悉得 html + css 方式来编写。
- 表格内容不支持垂直居中
- 不支持本地图片,只能 base64 或 网络图片
- 相比 jsPdf 直接用图片生成 pdf 的方案,会有一些样式特别难实现或实现不了等问题
- 需要引入比较大的字体包。
字体包优化
中文字体并不像英文一样,仅靠 26 个字母就能组成所有的单词,只能一个字一个字的录入。所以中文字体包通常都会非常大。
解决方法也很简单,就是把常用的字体从字体包里面提取出来,从而大大的减少字体体积。
缩小字体包
- 提取工具使用 font-spider 这个插件
全局安装
npm install font-spider -g
- 新建一个 index.html 文件,并且定义使用字体
css
@font-face {
font-family: "songti";
src: url("./font/songti.ttf");
font-weight: normal;
font-style: normal;
}
div {
font-family: "songti";
}
- 将常用的字体写进页面
html
<div>超 人 常 ...</div>
- 执行指令分离字体
font-spider ./index.html
- 成功分离后,你会发现字体包大大的减少了
使用 ES6 的动态加载
在点击生成 pdf 时,才导入生成 pdf 所需包和字体。这样配合生成时间的 loading 效果,能大大的增加用户体验。
js
const font = await import("./font.js");