让你的PDF合成后不再失真

1,853 阅读4分钟

前言

现在的前端要处理越来越多的业务需求,如果你的业务中有涉及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++) {
    // 修复,不使用 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

这里的传参要注意,

可爱小男生拿喇叭注意啦_爱给网_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>

后续

让你的PDF合成后不再失真2-文字合成