js生成PDF的几种方法

34,658 阅读4分钟

前言

最近因为要求前端生成pdf,就此研究了一阵子,发现就 jspdf 稍微好用一些,为了能导出pdf我试了好几种办法,下面给大家分享一下

无论使用哪一种办法,图片都必须转成base64,否则显示不出来

方法一:html2canvas + jsPDF 生成pdf

这个方法就是把html内容转成canvas,然后生成图片,把图片添加到jspdf里,保存导出pdf

  • 优点:生成的pdf清晰度良好,且支持中文字体
  • 缺点:生成的pdf内容大小不可控与模板样式有关,并且html模板必须要展示,display:none或者visibility:hidden, 甚至设置定位,top:-1000%,也不行,会打印出空白的 下面贴出代码
// 批量导出多个页面数据在一张pdf里
// html生成Canvas图片,添加到pdf里
downloadPDF () {
    // 判断所有图片是否都已经转成base64了,如果没有继续等待
    // 我在模板的所有图片的onload里在转base64,每转成一个就调取下载方法,
    // 下载方法就判断是否和要生成的图片总数一致
	let imgload = this.judgeimg()
	if (!imgload) {
		return false
	}
	const pdf = new window.jsPDF('', 'pt', 'a4')
	let pdfList = document.getElementsByClassName('pdfItem')
	for (let i = 0, len = pdfList.length; i < len; i++) {
            let target = pdfList[i]
            target.style.background = '#FFFFFF'
	    window.html2canvas(target, {
                dpi: 144, // 设置dpi,会使图片高清一些
		onrendered: function (canvas) {
			const contentWidth = canvas.width
			const contentHeight = canvas.height

			//一页pdf显示html页面生成的canvas高度;
			const pageHeight = (contentWidth / 595.28) * 841.89
			//未生成pdf的html页面高度
			let leftHeight = contentHeight
			//页面偏移
			let position = 0
			//a4纸的尺寸[595.28,841.89],html页面生成的canvas在pdf中图片的宽高
			const imgWidth = 595.28
			const imgHeight = (592.28 / contentWidth) * contentHeight

			const pageData = canvas.toDataURL('image/jpeg', 1.0)
			if (leftHeight < pageHeight) {
			    pdf.addImage(pageData, 'JPEG', 0, 0, imgWidth, imgHeight)
			    // 为下一条数据添加空白页
			    if (i < len - 1) {
			        pdf.addPage()
				}
			} else {
			    while (leftHeight > 0) {
				    pdf.addImage(pageData, 'JPEG', 0, position, imgWidth, imgHeight)
				    leftHeight -= pageHeight
				    position -= 841.89
				    //避免添加空白页
				    if (leftHeight > 0) {
				        pdf.addPage()
				    }
			    }
			}
			if (i === len - 1) {
			    pdf.save('PDF存档.pdf')
			}
		    }   
	})
    }
}

方法二:jsPDF的addHTML生成pdf

jsPDF有直接提供html生成PDF的方法,也是对生成的html进行截图生成图片

  • 优点:代码简单,且支持中文字体
  • 缺点:同样html模板必须要展示,display:none或者visibility:hidden, 甚至设置定位,top:-1000%,也不行,会打印出空白的(或者整块黑色的);清晰度一般,不是很高,要求不高的可以使用这个方法

模板页面的样式一定要设置background:#ffffff; ,否则会打印出的背景默认黑色

下面贴出代码

// html直接生成pdf截图
downloadPDF () {
    let imgload = this.judgeimg()
    if (!imgload) {
        return false
    }
    const pdf = new window.jsPDF('', 'pt', 'a4')
    let orderList = document.getElementsByClassName('pdfOrder')
    for (let i = 0, len = orderList.length; i < len; i++) {
        let target = orderList[i]
        target.style.background = '#FFFFFF'
        // 经调试,55%的时候内容显示效果比较好,与模板内容样式也有关系,自己调节一下比较好
        target.style.width = '55%'
        pdf.addHTML(target, function () {
            if (i < len - 1) {
                pdf.addPage()
            }
            if (i === len - 1) {
                pdf.save('PDF存档.pdf')
            }
        })
    }
}

方法三:jsPDF手写内容生成pdf

按照jspdf的api逐行编写排版

  • 优点:生成的pdf清晰度高,不需要html模板,不需要显示在浏览器内,可静默导出
  • 缺点:默认字体不支持中文字体,自己排版比较麻烦

如何支持中文字体呢?

既然写出来第三种办法就说明肯定是可以解决中文字体不支持情况的, 我查看了jspdf官方网站,没找到api文档。但是搜到了一些文章,然后开始了自己的测试。

首先支持设置中文字体的版本,目前找到的方法里,只有1.4.0版本
<script src="https://cdn.bootcss.com/jspdf/1.4.0/jspdf.debug.js"></script> 然后在网上下载一套中文字体,ttf格式的就好。 感谢大佬的生成字体的演示 生成字体代码

// addFileToVFS方法添加字体文件
doc.addFileToVFS(fileName, Base64content);
// 添加字体
doc.addFont(fileName, fontName, fontStyle);
// 使用字体
doc.setFont(fontName)

这里有有个坑,我当时设置了字体样式,然后就乱码了,说明在字体样式上的支持还不是很友好,但是字体大小还是ok的

<button @click="download2PDF" >导出pdf</button>
// 手写pdf内容
download2PDF () {
    this.pdfloading = true
    const pdf = new window.jsPDF()
    //添加并设置字体
    pdf.addFont('华文仿宋.ttf', 'custom', 'normal');
    pdf.setFont('custom');
    console.log(this.pdfData.length)
    for (let i = 0, len = this.pdfData.length; i < len; i++) {
		const item = this.pdfData[i]
		try {
		const promises = [item.imgurl1, item.imgurl2].map(imgurl => {
		    return imgGetAction(imgurl)
		})
		    Promise.all(promises).then(values => {
		        console.log(values)
		        item.pFrontImg = 'data:image/jpeg;base64,' + btoa(String.fromCharCode(...new Uint8Array(values[0])))
		        item.pBackImg = 'data:image/jpeg;base64,' + btoa(String.fromCharCode(...new Uint8Array(values[1])))
		        this.getPdfTemplate(pdf, item)
		        if (i < len - 1) {
		            pdf.addPage()
		        }
		        if (i === len - 1) {
		            pdf.save('PDF存档.pdf')
		            this.pdfloading = false
		        }
		    })
		} catch (error) {
			console.log(error)
			this.$message.error('PDF导出失败')
			this.pdfloading = false
			return false
		}
	}
},
// pdf模板信息
getPdfTemplate (pdf, data) {
    let linePos = 14
	pdf.text(60, linePos, '订单号 ' + data.id)
	pdf.setFontType('normal')
	
	pdf.setFontSize(10.5)
	linePos += 8
	//// 信息
	pdf.text(15, linePos, '信息')
	linePos += 5
	pdf.text(15, linePos, '姓名:' + data.name)
	pdf.text(75, linePos, '申请日期:' + data.applyDate)
	pdf.text(135.5, linePos, '审核日期:' + data.auditDate)

	linePos += 5
	// 第四行 40 照片
	pdf.text(15, linePos, '正面照片:')
	pdf.text(75, linePos, '反面照片:')
	linePos += 3
	pdf.addImage(pFrontImg, 'JPEG', 15, linePos, 50, 60)
	pdf.addImage(pBackImg, 'JPEG', 75, linePos, 50, 60)
	//// 收取信息 215
	linePos += 6
	pdf.text(15, linePos, '收取信息')
	// 第一行 220
	linePos += 5
	pdf.text(15, linePos, '收取方式:' + this.wayName[data.receiveWayId])
	pdf.text(75, linePos, '收取人姓名:' + data.destName)
	pdf.text(135.5, linePos, '收取人联系方式:' + data.destPhone)
	// 第二行 225
	linePos += 5
	let addrStr = (data.destProvince + data.destCity + data.destDistrict + data.destAddress) || '--'
	pdf.text(15, linePos, '收货地址:' + addrStr)
	//// 结尾footer 230
	pdf.text(15, 240, '打印时间:' + formatDate(new Date().valueOf(), 'yyyy-MM-dd hh:mm:ss'))
	pdf.text(135.5, 240, '经办人签字:')
}
	

参考内容