前言
现在的前端要处理越来越多的业务需求,如果你的业务中有涉及PDF的处理,你肯定会遇到这么一种情况, 在一个原始的pdf文件上合成进一张图片,或者一段文字。
之前的解决方案基本上是把图片扔给后端,让后端处理,处理好之后,前端再调用接口拿到。
如果非要前端来做,也不是不可以。
canvas
网上搜了一圈,主流的方案是,用canvas
画布将pdf画出来,再将图片合成进canvans
。
这里也提供一下这个方案的代码
const renderPDF = async (pdfData, index, pages, newPdf, images = []) => {
await pdfData.getPage(index).then(async (pdfPage) => {
const viewport = pdfPage.getViewport({ scale: 3, rotation: 0 })
const canvas = document.createElement("canvas")
const context = canvas.getContext("2d")
canvas.width = 600 * 3
canvas.height = 800 * 3
// PDF渲染到canvas
const renderTask = pdfPage.render({
canvasContext: context,
viewport: viewport,
})
await renderTask.promise.then(() => {
if (index > 1) {
newPdf.addPage()
}
newPdf.addImage(canvas, "JPEG", 0, 0, 600, 800, undefined, "FAST")
images.forEach((item) => {
let width = item.width
let height = item.height
if (index == pages) {
item.src !== "" &&
newPdf.addImage(
item.src,
"PNG",
item.x,
item.y,
width,
height,
undefined,
"FAST"
)
}
})
})
})
}
但是!
这样会有一个很严重的问题,那就是pdf失真,显得很模糊,当然也有解决方案,那就是canvas的缩放比例增加,
但是,缩放比例的增加却带来了pdf文件大小的倍数及增加,前端渲染的压力很大,只有7张的pdf,已经渲染出了8M大小常常见到loading等待。
所以有没有更好的方法解决呢?
有的。
pdf-lib
那就是今天所推荐的库 pdf-lib
github地址
他在github上的star 有5.6k,算的上是成熟,顶级的开源项目
在任何JavaScript环境中创建和修改PDF文档。
好,今天就只介绍如何将图片合成进pdf的功能 ,抛砖引玉。
其余的功能由您自己探索。
合成的思路是这样的:
- 1、我们的原始pdf,转换成pdf-lib 可识别的格式
- 2、同时将我们的图片合成进 pdf-lib里
- 3、pdf-lib 导出合成后的pdf
由于他只是一个工具,没有办法展示pdf 最后找一个pdf预览工具显示在页面即可 我找的是
vue-pdf-embed
这样,使用pdf-lib 方案,就不再是canvas画布画出来的。 我们可以看到,生成后的pdf文件体积增加不大,
而且能够保留原始pdf的文字选择,不再是图片了
同样,页面的缩放不会出现模糊失真的情况(因为不是图片,还是保持文字的矢量)。
代码
以下是代码,请查收
import { PDFDocument } from "pdf-lib"
const getNewPdf = async (pdfBase64, imagesList = []) => {
// 创建新的pdf
const pdfDoc = await PDFDocument.create()
let page = ""
// 传入的pdf进行格式转换
const usConstitutionPdf = await PDFDocument.load(pdfBase64)
// 获取转换后的每一页数据
const userPdf = usConstitutionPdf.getPages()
// 将每一个数据 导入到我们新建的pdf每一页上
for (let index = 0; index < userPdf.length; index++) {
// 修复,不使用 drawPage的方法添加pdf,而是使用copyPages的方法
// page = pdfDoc.addPage()
// const element = userPdf[index]
// const firstPageOfConstitution = await pdfDoc.embedPage(element)
// page.drawPage(firstPageOfConstitution)
const [existingPage] = await pdfDoc.copyPages(usConstitutionPdf,[index])
page = pdfDoc.addPage(existingPage)
// 如果有传入图片,则遍历信息,并将他合成到对应的页码上
const imageSel = imagesList.filter((i) => i.pageIndex === index)
if (imageSel.length > 0) {
for (let idx = 0; idx < imageSel.length; idx++) {
const el = imageSel[idx]
const pngImage = await pdfDoc.embedPng(el.src)
page.drawImage(pngImage, {
x: +el.x,
y: +el.y,
width: +el.width,
height: +el.height,
})
}
}
}
// 保存pdf
const pdfBytes = await pdfDoc.save()
// 将arrayButter 转换成 base64 格式
function ArrayBufferToBase64(buffer) {
//第一步,将ArrayBuffer转为二进制字符串
var binary = ""
var bytes = new Uint8Array(buffer)
for (var len = bytes.byteLength, i = 0; i < len; i++) {
binary += String.fromCharCode(bytes[i])
}
//将二进制字符串转为base64字符串
return window.btoa(binary)
}
// console.log("data:application/pdf;base64," + ArrayBufferToBase64(pdfBytes))
// 最后将合成的pdf返回
return "data:application/pdf;base64," + ArrayBufferToBase64(pdfBytes)
}
export default getNewPdf
这里的传参要注意,
- pdfBase64 是base64位的格式
- imagesList 数组对象格式为
[ { src:'base64', x:'', yL'', width:'', height:'', pageIndex:'' } ]
最后也附上vue
文件中如何使用的代码
<template>
<div>
<el-button @click="pdfComposite">生成新的pdf</el-button>
<div class="pdf-content">
<vue-pdf-embed
:source="url"
/>
</div>
</div>
</template>
<script>
import VuePdfEmbed from "vue-pdf-embed/dist/vue2-pdf-embed"
import getNewPdf from "./utils"
import { pngText, pdfbase64 } from "../data"
export default {
name: "PdfPreview",
components: {
VuePdfEmbed,
},
data() {
return {
url: pdfbase64,// 原始的base64位 pdf
}
},
methods: {
pdfComposite() {
// getNewPdf 返回的是promise 对象
getNewPdf(this.url, pngText).then(res =>{
this.url = res
})
},
},
}
</script>
<style >
.pdf-content {
width: 400px;
min-height: 600px;
}
</style>