注册
web

让你的PDF合成后不再失真

前言


现在的前端要处理越来越多的业务需求,如果你的业务中有涉及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的缩放比例增加,


image.png
但是,缩放比例的增加却带来了pdf文件大小的倍数及增加,前端渲染的压力很大,只有7张的pdf,已经渲染出了8M大小常常见到loading等待。


所以有没有更好的方法解决呢?


暴漫g


有的。


pdf-lib


那就是今天所推荐的库 pdf-lib
github地址
他在github上的star 有5.6k,算的上是成熟,顶级的开源项目


image.png


在任何JavaScript环境中创建和修改PDF文档。


好,今天就只介绍如何将图片合成进pdf的功能 ,抛砖引玉。


熊猫头抛砖头 .gif


其余的功能由您自己探索。


合成的思路是这样的:



  • 1、我们的原始pdf,转换成pdf-lib 可识别的格式
  • 2、同时将我们的图片合成进 pdf-lib里
  • 3、pdf-lib 导出合成后的pdf


由于他只是一个工具,没有办法展示pdf
最后找一个pdf预览工具显示在页面即可
我找的是 vue-pdf-embed



这样,使用pdf-lib 方案,就不再是canvas画布画出来的。
我们可以看到,生成后的pdf文件体积增加不大,


image.png


而且能够保留原始pdf的文字选择,不再是图片了


image.png


同样,页面的缩放不会出现模糊失真的情况(因为不是图片,还是保持文字的矢量)。


代码


以下是代码,请查收


感谢 给你磕头 GIF .gif


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++) {
page = pdfDoc.addPage()
const element = userPdf[index]
const firstPageOfConstitution = await pdfDoc.embedPage(element)
page.drawPage(firstPageOfConstitution)
// 如果有传入图片,则遍历信息,并将他合成到对应的页码上
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


这里的传参要注意,


可爱小男生拿喇叭注意啦_爱给网_aigei_com.gif



  • 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>


作者:前端代码王
来源:juejin.cn/post/7293175592163049506

0 个评论

要回复文章请先登录注册