让你的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>
相关推荐
一斤代码6 分钟前
vue3 下载图片(标签内容可转图)
前端·javascript·vue
中微子8 分钟前
React Router 源码深度剖析解决面试中的深层次问题
前端·react.js
光影少年10 分钟前
从前端转go开发的学习路线
前端·学习·golang
中微子40 分钟前
React Router 面试指南:从基础到实战
前端·react.js·前端框架
3Katrina43 分钟前
深入理解 useLayoutEffect:解决 UI "闪烁"问题的利器
前端·javascript·面试
前端_学习之路2 小时前
React--Fiber 架构
前端·react.js·架构
coderlin_2 小时前
BI布局拖拽 (1) 深入react-gird-layout源码
android·javascript·react.js
伍哥的传说2 小时前
React 实现五子棋人机对战小游戏
前端·javascript·react.js·前端框架·node.js·ecmascript·js
qq_424409192 小时前
uniapp的app项目,某个页面长时间无操作,返回首页
前端·vue.js·uni-app
我在北京coding2 小时前
element el-table渲染二维对象数组
前端·javascript·vue.js