记录jsPdf相关使用
相关文档:
一:我的需求
浏览器接口:window.print(),可以直接调用打印,并存储为PDF文档
用工具库:html2canvas+jsPDF实现以上(类似的)PDF
二:我的实现
2.1:基础参数
首先定义一个标准数据,以Windows下的A4打印为基础:
// 基础参数
const printParams = {
a4: {
dpi: 96, // windows 下默认 dpi
width: 210, // A4 标准宽度 mm,含边距
height: 297, // A4 标准高度 mm, 含边距
marginX: 10, // 默认打印左右边距,约为 10mm
marginY: 10, // 默认打印上下边距,约为 10mm
},
// 以下是设置值/初始值
dpi: 288, // 设置的打印密度,数值越大越清晰,PDF文件越大
format: 'a4', // 打印类型,这里只有 A4
orientation: 'portrait', // portrait | landscape
// 以下为计算出的值,方便调用
ratio: 1, // dpi 比例
width: 0, // PDF 文档宽度,不含边距
widthFull: 0, // PDF 文档完整宽度
height: 0, // PDF 文档高度,不含边距
heightFull: 0, // PDF 文档完整高度
marginX: 0, // PDF 左右边距
marginY: 0, // PDF 上下边距
}
数据初始化:
// 根据设置的 dpi,format,orientation 数据初始化
const { dpi: setDpi, orientation, format } = printParams;
const { dpi, width, height, marginX, marginY } = printParams[format];
printParams.ratio = setDpi/dpi;
let setWidth;
switch (orientation) {
case 'landscape':
printParams.width = height - 2 * marginY; // 不含边距的内容宽度
printParams.height = width - 2 * marginX; // 不含边距的内容高度
printParams.widthFull = height; // 整页PDF宽度
printParams.heightFull = width; // 整页PDF高度
printParams.marginX = marginY; // 左右单边边距
printParams.marginY = marginX; // 上下单边边距
setWidth = mm2px(height, dpi); // 页面宽度像素值
break;
case 'portrait':
default:
printParams.width = width - 2 * marginX;
printParams.height = height - 2 * marginY;
printParams.widthFull = width;
printParams.heightFull = height;
printParams.marginX = marginX;
printParams.marginY = marginY;
setWidth = mm2px(width, dpi);
}
// 设置body宽度,给 window.print 一个“最适合”的宽度
window.document.body.style.width = `${setWidth}px`;
这里有用到一个mm/px的转换,把以前写过的方法拿过来:
/**
* 工具类:mm转px
* */
function mm2px(mm, dpi, radio) {
if (mm == null) return 0;
dpi = dpi || printParams.dpi;
const ret = parseFloat(`${(mm * dpi * 10) / 254}`);
if (radio == null || radio <= 0) radio = 4;
radio = 10 ** radio;
return Math.floor(ret * radio) / radio;
}
/**
* 工具类:px转mm
* */
function px2mm(px, dpi, radio) {
if (px == null) return 0;
dpi = dpi || printParams.dpi;
const ret = parseFloat(`${(px * 254) / (dpi * 10)}`);
if (radio == null || radio <= 0) radio = 4;
radio = 10 ** radio;
return Math.floor(ret * radio) / radio;
}
2.2:生成图片
// 需要用到的数据
const $pdfDom = document.getElementById('pdf');
const {
ratio, orientation, format,
width, height, widthFull, marginX, marginY
} = printParams;
html2canvas($pdfDom, {
scale: window.devicePixelRatio * Math.ceil(ratio), // 缩放
allowTaint: true,
useCORS: true,
}).then((canvas) => {
const canvasWidth = this.px2mm(canvas.width);
const canvasHeight = this.px2mm(canvas.height);
// ··· 以下是 pdf 生成
// ···
});
此处用到html2canvas实现绘制页面生成图片,主要影响清晰度的是参数scale:
| scale | window.devicePixelRatio | The scale to use for rendering. Defaults to the browsers device pixel ratio. |
默认值为window.devicePixelRatio,主要用于防止页面缩放导致的生成图片不清晰问题
为了实现更好地缩放,最好乘以一个整数值,以保证像素值的对应,就把它当作windows下的高分屏缩放理解就行
这里用px2mm转换成mm主要方便用于后面的PDF编辑
2.3:生成PDF
// 得到图片数据
const pageData = canvas.toDataURL('image/jpeg', 1.0);
// 创建PDF文档对象
const PDF = new JsPDF({ orientation, format });
// 标准宽度下,图片的相对高度
const imageHeight = (width / canvasWidth) * canvasHeight;
// 考虑可能有多页的情况,用 leftHeight 循环判断
let leftHeight = imageHeight;
// 每次绘制图片的位置垂直偏移量
let position = marginY;
while (leftHeight > 0) {
// 1:图形绘制到PDF
PDF.addImage(pageData, 'JPEG', marginX, position, width, imageHeight);
leftHeight -= height;
position -= height;
// 2:页头页脚添加,用 rect 遮罩
PDF.setFillColor(255, 255, 255); // 填充白色,每次填充前都要定义
PDF.rect(0, 0, widthFull, marginY, 'F');
PDF.rect(0, height + marginY, widthFull, marginY, 'F');
// 添加空白页
if (leftHeight > 0) {
PDF.addPage();
}
}
PDF.save(`${title}.pdf`);
这里有几个点,需要留意:
- 默认PDF为单页,需要判断“图片”相对高度,超出标准高度时添加新页面
- 每次添加新页面,
PDF绘制即“当前页”,相对位置都是相对“当前页”而言 - 绘制遵循页面标准的层级,后绘制内容会覆盖已绘制内容
- 垂直偏移
position是图片相对“当前页”的偏移,向上偏移一个内容页的距离后,当前页绘制的就是下一页该显示的图片内容 - 页眉页脚的模拟绘制,每次都要设置填充颜色(默认为黑色),因为每次都是“不同页”,参考第
2点
三:实现效果
目前只需要模拟A4下的打印,在对比使用window.print()生成的PDF和手动生成的PDF之后,还是比较满意的,以后再有自定义编辑页眉页脚,到时候再说


要说有什么不满意的地方,那肯定是哪里不满意再改哪里,哪里不购加哪里
不过呢,有个东西肯定只会越改(加)越糟的,那就是文件大小:

Holy shit,大了整整20倍·····
这和生成图片时设置的缩放比(scale)有关,目前我测试时改值为4.5(1.5*288/96),既使变成1/5也还有400KB,大约4倍的差距,看来文件大小的劣势还是挺明显的
不过呢,现在谁还在乎这么点大小呢···谁还在乎呢···谁···
