1、调研
主要需要用到插件jsPDF,jsPDF可以将html下载为pdf格式的文件,但无法支持中文字形,下载带中文的网页会有乱码,经验证可以通过如下3种方案下载中文网页。
2、方案
2.1、方案一(jsPDF + html2canvas):html元素通过html2canvas生成canvas,再将canvas添加到pdf文档上。
优点: 文档清晰度可控制,下载的PDF文件上不会有乱码,html样式还原度100%。
缺点: 分页不好控制,适合不长或者每页内容比较规律(比如table数据)的页面下载。
项目下载jsPDF和html2canvas依赖,引入依赖,下面按不分页与分页的2种情况给出代码示例。
2.1.1 不分页
js源码如下:
import { jsPDF } from 'jspdf';
import html2canvas from 'html2canvas';
let element = document.body; //下载整个页面,也可以下载页面的部分html
let scale = 0.5;
let pageFormat = [element.scrollWidth * scale, element.scrollHeight * scale];
const pdf = new jsPDF({
orientation: 'l',
unit: 'pt', // pdf大小计量单位
format: pageFormat // 自定义第一页pdf页面大小,按html元素的比例设定
});
let opt = {
scale: 4 // 保证pdf文档内容清晰度,放大4倍,网上说这种方案文档清晰度不高,经验证可以这么解决清晰度问题
};
let width = pageFormat[0];
let height = pageFormat[1];
// 将html元素绘制为cancas
html2canvas(element, opt).then(canvas => {
// cancas添加到pdf文档上
pdf.addImage(canvas, 0, 0, width, height);
// 下载
pdf.save('下载' + '.pdf');
});
2.1.2 分页
html:提前规划每月pdf内容
<div v-for="(data, index) in pageData" :key="index" :ref="'page' + index" class="pdf-page">
第{{ index + 1 }} 页
......
</div>
js代码:将每一块的html转化为canvas,再将canvas添加到pdf的一页上
// pageCount: 总共的页数
if (this.pageCount === 0) return;
let elScale = 0.5;
// 将每一块的html转化为canvas,再将canvas添加到pdf的一页上
let canvasPage = (pdf, index) => {
let element = this.$refs['page' + index][0];
let width = element.scrollWidth * elScale;
let height = element.scrollHeight * elScale;
html2canvas(element, { scale: 4 }).then(canvas => {
(index > 0) && pdf.addPage();
pdf.addImage(canvas, 0, 0, width, height);
if ((index + 1) === this.pageCount) {
pdf.save('demp.pdf');
return;
}
index++;
canvasPage(pdf, index);
});
};
const pdf = new jsPDF({orientation: 'p', unit: 'pt', format: 'a4'});
this.$nextTick(() => {
canvasPage(pdf, 0);
});
}
2.2、方案二(jsPDF添加ttf中文字形文件)
优点:pdf文档可以自动分页。
缺点:需要保证带中文的html元素的字体(font-family)和新添加的ttf文件字体一致,不然中文也为乱码,会和你用的插件字体有冲突(比如你用到了iconfont),html中如果有图片会无法下载成功。
步骤如下:
a、下载ttf文件。
b、通过jsPDF提供的网页将ttf文件转为js文件(文件较大),网页地址:https://rawgit.com/MrRio/jsPDF/master/fontconverter/fontconverter.html,转化后的js文件内容如下:
c、将font字符串复制粘贴到json文件(js文件内容很大,最好用记事本打开会快一些),放在前端项目的文件夹下,通过http请求json文件内容(不直接引入js文件,因为字体文件很大,直接引入node会报错)。
d、示例使用的ttf文件和json文件下载地址:https://download.csdn.net/download/buler_sky/15417402。
e、html元素修改字体:style="font-family:chinese"。
f、项目下载jsPDF和axios依赖(或者其他可以发送请求的插件),引入依赖,js源码如下:
import { jsPDF } from 'jspdf';
import axios from 'axios';
axios.get('/jspdf/chinese.json').then(res => {
var callAddFont = function () {
this.addFileToVFS('chinese.ttf', res.data.font);
this.addFont('chinese.ttf', 'chinese', 'normal');
};
jsPDF.API.events.push(['addFonts', callAddFont]);
download(jsPDF);
});
download(jsPDF) {
let element = document.body; //下载整个页面,也可以下载页面的部分html
let pdf = new jsPDF({
orientation: 'l',
unit: 'pt',
format: 'a4',
compress: true
});
pdf.setFont('chinese');
pdf.setFontSize(14);
let pWidth = pdf.internal.pageSize.width; // 595.28 is the width of a4
let srcWidth = element.scrollWidth;
let margin = 18; // narrow margin - 1.27 cm (36);
let scale = (pWidth - margin * 2) / srcWidth;
pdf.html(element, {
x: margin,
y: margin,
html2canvas: {
scale: scale
},
callback: function (doc) {
doc.save();
}
});
}
2.3、 方案三(jsPdf + jsPDF-CustomFonts-support)
优点: 示例为非包管理的项目、和方案二原理差不多,都是替换字体源,引入很简单;
缺点: 对应jsPDF版本比较老、和jsPdf文档的api很多都不一致
a、通过script引入插件:
<script src="/jspdf/jspdf.es.min.js"></script>
<script src="/jspdf/jspdf.customfonts.min.js"></script>
<script src="/jspdf/default_vfs.js"></script>
b、js代码:
let element = document.body; //下载整个页面,也可以下载页面的部分html
const doc = new jsPDF({format: 'a4'});
doc.addFont('NotoSansCJKtc-Regular.ttf', 'NotoSansCJKtc-Regular', 'normal');
doc.setFont('NotoSansCJKtc-Regular');
console.log(doc.getFont());
//最新的jsPdf的api为doc.html()
doc.addHTML(element , {
callback: function (doc) {
doc.save();
},
x: 10,
y: 10,
margin: 10
});
c、示例文件下载地址:https://download.csdn.net/download/buler_sky/15501445。
3、总结
1、寻找方法的过程中淘汰的方法:
方案三和方案二的原理差不多,但是不适合通过包管理的项目,需要通过script引入插件,且对应jsPDF版本比较老。
2、寻找最佳解决方案的过程中遇见很多坑,这里依然没有完美的解决问题(pdf分页不完美和背景图片无法下载),后续有突破会继续更新,大家在实践过程中如果有问题欢迎留言讨论。