让你的PDF合成后不再失真

前言

现在的前端要处理越来越多的业务需求,如果你的业务中有涉及PDF的处理,你肯定会遇到这么一种情况, 在一个原始的pdf文件上合成进一张图片,或者一段文字。

之前的解决方案基本上是把图片扔给后端,让后端处理,处理好之后,前端再调用接口拿到。

如果非要前端来做,也不是不可以。

网上搜了一圈,主流的方案是,用canvas画布将pdf画出来,再将图片合成进canvans。 这里也提供一下这个方案的代码

js 复制代码
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 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的文字选择,不再是图片了

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

以下是代码,请查收

js 复制代码
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

这里的传参要注意,

  • pdfBase64 是base64位的格式

  • imagesList 数组对象格式为

    js 复制代码
    [
      {
        src:'base64',
        x:'',
        yL'',
        width:'',
        height:'',
        pageIndex:''
      }
    ]

最后也附上 vue文件中如何使用的代码

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>
相关推荐
y***54882 分钟前
TypeScript在React项目中的状态管理
javascript·react.js·typescript
全马必破三2 小时前
CSS 和 JS 如何阻塞浏览器渲染 DOM
javascript
c***V3233 小时前
Vue优化
前端·javascript·vue.js
努力往上爬de蜗牛4 小时前
react native真机调试
javascript·react native·react.js
李@十一₂⁰5 小时前
HTML 特殊字体符号
前端·html
y***86695 小时前
TypeScript在Electron应用中的使用
javascript·typescript·electron
小奶包他干奶奶7 小时前
Webpack学习——Loader(文件转换器)
前端·学习·webpack
zy happy7 小时前
若依 vue3 报错:找不到模块“@/api/xxxx/xxxxx”或其相应的类型声明。。Vue 3 can not find mod
前端·javascript·vue.js
潘小安8 小时前
Git Worktree + Claude Code:让你的开发效率翻倍的秘密武器
前端
meichaoWen8 小时前
【Vue3】vue3的全面学习(一)
前端·javascript·学习