背景
最近项目需要实现前端生成pdf(用于打印),经过一番技术调研,采用了jsPDF这一开源库
🔗
jsPDF的GitHub地址:github.com/parallax/js…
npm地址:www.npmjs.com/package/jsp…
官方文档:raw.githack.com/MrRio/jsPDF… (可能很难打开,需要科学,或者拉取项目代码在本地npm run generate-docs生成文档)
安装引入
npm install jspdf --save
or
yarn add jspdf
或cdn引入
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>
<script src="https://unpkg.com/jspdf@latest/dist/jspdf.umd.min.js"></script>
(最新版)
使用
用例
import { jsPDF } from "jspdf";
// Default export is a4 paper, portrait, using millimeters for units
const doc = new jsPDF();
doc.text("Hello world!", 10, 10);
doc.save("a4.pdf");
引入字体(中文乱码问题)
jspdf默认不支持中文,其自带的以下字体,均不包含中文字符。
如果生成的pdf包含中文,会显示为乱码,要解决该问题,需要手动引入中文字体。
引入字体至项目中最好考虑一下字体版权,是否可免费商用的问题。以下以引入思源黑体字体为例。
- 该字体是开源免费的,分别找到并下载其normal(常规)、bold(粗体)、italic(斜体)、bolditalic(粗斜体)对应的
ttf
文件(不要用otf文件,不生效)。 - 将下载的字体文件名改为全小写
- 打开转换工具github.com/parallax/js…
选择对应的字体文件和对应的fontStyle进行转换(转化前统一命名为XXX.ttf,转化后会生成对应的XXX-normal.js,XXX-bold.js文件)
- 将相应的js文件引入到项目中
(由于字体文件体积较大,放在前端项目中不太合适,笔者搭建了一个node服务来开发绘制生成pdf这一模块)
# fonts/syht/index.js
require('./sourcehansans-normal');
require('./sourcehansans-bold');
require('./sourcehansans-italic');
require('./sourcehansans-bolditalic');
# app/service/drawPdf.ts
import '../../fonts/syht/index';
可以通过doc.getFontList()
查看字体是否添加成功
5.使用时设置字体
doc.setFont('sourcehansans', 'normal');
doc.setFont('sourcehansans', 'bold');
doc.setFont('sourcehansans', 'italic');
doc.setFont('sourcehansans', 'bold-italic');
创建实例
import { jsPDF } from 'jspdf';
const doc = new jsPDF({
orientation: 'portrait', // 纸张方向, "portrait" | "landscape" | "p" | "l" (portrait/p - 纵向, landscape/l - 横向, 默认为portrait纵向)
unit: 'mm', // 单位, "mm" | "cm" | "in" | "px" | "pc" | "em" | "ex" (默认为mm)
format: 'a4', // 纸张尺寸, a0-a10 | b0-b10 | c0-c10 | 数组等等,自定义尺寸可以传入数组,如[595.28, 841.89]
putOnlyUsedFonts: true, // 默认为false,设置为true后可以只将pdf用到的字体引入,减小生成的pdf体积
compress: true, // 默认为false,设置为true后可以压缩生成的pdf体积
});
绘制
- 文本
doc.text(text, x, y, options, transform)
- 直线
doc.line(x1, y1, x2, y2, style)
- 矩形
doc.rect(x, y, w, h, style)
- 图片
doc.addImage(imageData, format, x, y, width, height, alias, compression, rotation)
- ...
详情可以参考官方文档
绘制表格
jspdf内置的表格API并不好用,建议采用插件jspdf-autotable
npm i jspdf-autotable
import 'jspdf-autotable' // import autoTable from 'jspdf-autotable'
doc.autoTable({...}) // autoTable(doc, {...})
jspdf-autoTable npm 链接: www.npmjs.com/package/jsp…
demo:simonbengtsson.github.io/jsPDF-AutoT…
doc.autoTable({
theme: 'plain', // 主题
startY: 100, // 竖直位置
margin: { // 边距,配合getPageWidth()可用于控制表格宽度
left: 20,
right: 20,
...
},
head: [...], // 表头数据
body: [...], // 表行数据
styles: {
font: 'sourcehansans', // 设置字体
textColor: [0, 0, 0], // 字体颜色 RGB
minCellWidth: 20, // 单元格最小宽度
...
},
headStyles: {
lineWidth: 0.2,
lineColor: [0, 0, 0],
...
},
bodyStyles: {
lineWidth: 0.2,
lineColor: [0, 0, 0],
...
},
...
didParseCell: (HookData) => {}, // 插件完成单元格内容解析时调用。
willDrawCell: (HookData) => {}, // 在绘制单元格之前调用。
didDrawCell: (HookData) => {}, // 将单元格添加到页面后调用。可用于绘制附加单元格内容,例如绘制图片doc.addImage等。
didDrawPage: (HookData) => {}, // 在插件绘制完页面上的所有内容后调用。
})
钩子函数的HookData可以拿到很多表格信息,具体可以输出或断点查看。
doc.autoTable.previous中也有许多表格信息,常用的有doc.autoTable.previous.finalY,可以获得表格最后绘制的一点的纵坐标,即右下角的位置,可以用于在表格后绘制内容,或将表格后的内容下移等操作。
常用操作
doc.setFont('sourcehansans', 'normal')
// 设置字体doc.setFontSize(20)
// 设置字体大小doc.setDrawColor('#000000')
// 设置颜色doc.setLineDash([1, 1])
// 设置虚线doc.setLineWidth(0.200025)
// 设置线宽doc.setCharSpace(1)
// 设置字间距doc.setFillColor('#000000')
// 设置填充颜色doc.setGState(doc.GState({ opacity: 0.1 }))
// 设置透明度doc.addPage()
// 添加页doc.setPage(2)
//设置接下来在第几页上画doc.deletePage(2)
// 删除第几页doc.output('datauristring')
// 输出pdf为制定格式doc.save('"name.pdf"')
// 保存pdf文件至本地doc.getPageHeight()
// 获取页高doc.getPageWidth()
// 获取页宽
小技巧
doc.splitTextToSize(text, size, options)
可以用于文本换行,将给定的文本根据指定宽度拆分为数组,而doc.text(text, x, y, options, transform)
第一个参数传入数组时绘制文本数组中每个元素会独占一行(文本之间有间距就不准确了,需要特殊处理)- 绘制水印等操作时可以利用
doc.saveGraphicsState()
保存图形状态,绘制完成后,再通过doc.restoreGraphicsState()
清除图形状态,防止影响其他内容 - 生成条形码,可以参考
JsBarcode
这个开源库
问题
- 在浏览器端,doc.addImage()的第一个参数可以是图片的url字符串,而在node服务端不行,需要为base64、Uint8Array等其他格式。若转换url为base64格式,处理需要一定时间,需要异步等待表格的绘制,否则output或save的结果中可能不包含图片。
其他
如果对清晰度要求没那么高,可以采用html转换成canvas后生成图片导出pdf等其他简单的方式,就无需一个个绘制pdf中的内容。
由于引入的字体文件比较大,并且复杂场景下绘制pdf内元素包含的细节很多,某些具体的绘制需求jspdf实现起来比较麻烦等原因,前端生成pdf本身就不是一个好的方案,如果可以,最好是由后端来生成。