jsPDF:前端或node端生成pdf方案

2,017 阅读5分钟

背景

最近项目需要实现前端生成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默认不支持中文,其自带的以下字体,均不包含中文字符。

image.png

如果生成的pdf包含中文,会显示为乱码,要解决该问题,需要手动引入中文字体。

引入字体至项目中最好考虑一下字体版权,是否可免费商用的问题。以下以引入思源黑体字体为例。

  1. 该字体是开源免费的,分别找到并下载其normal(常规)、bold(粗体)、italic(斜体)、bolditalic(粗斜体)对应的ttf文件(不要用otf文件,不生效)。 image.png
  2. 将下载的字体文件名改为全小写

image.png

  1. 打开转换工具github.com/parallax/js…

image.png 选择对应的字体文件和对应的fontStyle进行转换(转化前统一命名为XXX.ttf,转化后会生成对应的XXX-normal.js,XXX-bold.js文件)

image.png

image.png

  1. 将相应的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()查看字体是否添加成功

image.png

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体积
});

绘制

  1. 文本 doc.text(text, x, y, options, transform)
  2. 直线 doc.line(x1, y1, x2, y2, style)
  3. 矩形 doc.rect(x, y, w, h, style)
  4. 图片 doc.addImage(imageData, format, x, y, width, height, alias, compression, rotation)
  5. ...

详情可以参考官方文档

绘制表格

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,可以获得表格最后绘制的一点的纵坐标,即右下角的位置,可以用于在表格后绘制内容,或将表格后的内容下移等操作。

常用操作

  1. doc.setFont('sourcehansans', 'normal') // 设置字体
  2. doc.setFontSize(20) // 设置字体大小
  3. doc.setDrawColor('#000000') // 设置颜色
  4. doc.setLineDash([1, 1]) // 设置虚线
  5. doc.setLineWidth(0.200025) // 设置线宽
  6. doc.setCharSpace(1) // 设置字间距
  7. doc.setFillColor('#000000') // 设置填充颜色
  8. doc.setGState(doc.GState({ opacity: 0.1 })) // 设置透明度
  9. doc.addPage() // 添加页
  10. doc.setPage(2) //设置接下来在第几页上画
  11. doc.deletePage(2) // 删除第几页
  12. doc.output('datauristring') // 输出pdf为制定格式
  13. doc.save('"name.pdf"') // 保存pdf文件至本地
  14. doc.getPageHeight() // 获取页高
  15. doc.getPageWidth() // 获取页宽

小技巧

  1. doc.splitTextToSize(text, size, options)可以用于文本换行,将给定的文本根据指定宽度拆分为数组,而doc.text(text, x, y, options, transform)第一个参数传入数组时绘制文本数组中每个元素会独占一行(文本之间有间距就不准确了,需要特殊处理)
  2. 绘制水印等操作时可以利用doc.saveGraphicsState()保存图形状态,绘制完成后,再通过doc.restoreGraphicsState()清除图形状态,防止影响其他内容
  3. 生成条形码,可以参考JsBarcode这个开源库

问题

  1. 在浏览器端,doc.addImage()的第一个参数可以是图片的url字符串,而在node服务端不行,需要为base64、Uint8Array等其他格式。若转换url为base64格式,处理需要一定时间,需要异步等待表格的绘制,否则output或save的结果中可能不包含图片。

其他

如果对清晰度要求没那么高,可以采用html转换成canvas后生成图片导出pdf等其他简单的方式,就无需一个个绘制pdf中的内容。

由于引入的字体文件比较大,并且复杂场景下绘制pdf内元素包含的细节很多,某些具体的绘制需求jspdf实现起来比较麻烦等原因,前端生成pdf本身就不是一个好的方案,如果可以,最好是由后端来生成。