vue中如何预览PDF文件,隐藏工具栏,给PDF文件加水印

1,682 阅读2分钟

一、预览文件

1、浏览器新标签页中预览

通过 a 标签预览,只展示文件名称,点击链接以后,在新标签页中预览。

<a href="文件地址" target="_blank">网站备案信息真实性承诺书</a>

image.png

image.png

2、浏览器当前标签页中预览

通过 iframe 标签或 embed 标签进行预览,直接在当前标签页预览。

<iframe src="文件地址" frameborder="0" width="100%" height="100%" />
<embed src="文件地址" width="100%" height="100%" />

image.png

二、隐藏工具栏

只需要在标签的 src 路径后面拼接 #toolbar=0 即可隐藏工具栏

<a href="文件地址#toolbar=0" target="_blank">网站备案信息真实性承诺书</a>
<iframe src="文件地址#toolbar=0" frameborder="0" width="100%" height="100%" />
<embed src="文件地址#toolbar=0" width="100%" height="100%" />

image.png

image.png

三、使用pdf-lib添加水印并预览

1、安装 pdf-lib 

npm install pdf-lib -S

2、引入

import { degrees, PDFDocument, StandardFonts, rgb } from 'pdf-lib'

3、添加水印

3.1、文本水印

将文本添加进去作为水印,但是只支持英文,不支持中文。如果需要使用中文,必须安装自定义字体库,我自己也尝试了一下,自定义字体引入后老是报错没有该字体,不太推荐使用。

// 安装自定义字体库依赖
npm install "@pdf-lib/fontkit" -S
// 引入
import fontkit from "@pdf-lib/fontkit"
// 添加文本水印
    async modifyPdf(title, url) {
      const existingPdfBytes = await fetch(url).then(res => res.arrayBuffer())
      const pdfDoc = await PDFDocument.load(existingPdfBytes)

      const helveticaFont = await pdfDoc.embedFont(StandardFonts.Helvetica) // 只支持英文

      // 自定义字体包路径,放在 public 目录
      // const fonturl = './font/font.ttf'
      // const fontBytes = await fetch(fonturl).then(res => res.arrayBuffer())
      // pdfDoc.registerFontkit(fontkit)
      // const customFont = await pdfDoc.embedFont(fontBytes)

      const pages = pdfDoc.getPages()
      for (let i = 0; i < pages.length; i++) {
        const page = pages[i]
        // width
        const { height } = page.getSize()
        for (let i = 0; i < 6; i++) {
          for (let j = 0; j < 6; j++) {
            page.drawText('SYCEE', {
              x: j * 100,
              y: height / 5 + i * 100,
              size: 30,
              font: helveticaFont,
              // font: customFont, // 自定义字体
              color: rgb(0.95, 0.1, 0.1),
              opacity: 0.2,
              rotate: degrees(-35)
            })
          }
        }
      }

      // 序列化为字节
      const pdfBytes = await pdfDoc.save()
      // 预览
      this.preView(title, pdfBytes)
    },

image.png

3.2、图片水印

将图片的 ArrayBuffer 传递进去,将图片作为水印,支持 png 和 jpg 格式的图片,可参考官网,图片水印位置可以自己调整一下。

// 添加图片水印
    async embedImages(title, url) {
      const existingPdfBytes = await fetch(url).then(res => res.arrayBuffer())
      const pdfDoc = await PDFDocument.load(existingPdfBytes)

      const pngUrl = 'https://pdf-lib.js.org/img/logo3.png'
      const pngImageBytes = await fetch(pngUrl).then(res => res.arrayBuffer())
      const pngImage = await pdfDoc.embedPng(pngImageBytes)
      const pngDims = pngImage.scale(0.5)
      const pages = pdfDoc.getPages()

      for (let i = 0; i < pages.length; i++) {
        const page = pages[i]
        const { width, height } = page.getSize()
        for (let i = 0; i < 6; i++) {
          for (let j = 0; j < 6; j++) {
            page.drawImage(pngImage, {
              x: width / 2 - pngDims.width / 2 + j * 100,
              y: height / 2 - pngDims.height + i * 100,
              width: pngDims.width,
              height: pngDims.height,
              rotate: degrees(-35)
            })
          }
        }
      }

      // 序列化为字节
      const pdfBytes = await pdfDoc.save()
      // 预览
      this.preView(title, pdfBytes)
    },

image.png

3.3、canvas 水印(推荐)

canvas 水印其实也是传入文本,然后通过 canvas 画出来。原理是通过 toDataURL 转为 图片的 base64 路径,然后通过 XHR 去加载该图片拿到图片的 Blob,再调用 Blob 的arrayBuffer 方法拿到 buffer 传递进去作为水印,此方法更灵活。但是通过 XHR 加载图片的方式打包发布以后会报错(查不出原因),所以此部分的代码我采用了添加图片水印的方式进行了优化,代码中会有说明。

// 添加 canvas 水印
    async canvasWatermark(title, url) {
      const existingPdfBytes = await fetch(url).then(res => res.arrayBuffer())
      const pdfDoc = await PDFDocument.load(existingPdfBytes)

      // 旋转角度大小
      const rotateAngle = Math.PI / 6

      // labels是要显示的水印文字,垂直排列
      const labels = []
      labels.push('巨蟹座不吃鱼SYCEE')

      const pages = pdfDoc.getPages()
      const size = pages[0].getSize()

      const pageWidth = size.width
      const pageHeight = size.height

      // 生成 canvas 文字图片
      const canvas = document.createElement('canvas')
      let canvasWidth = (canvas.width = pageWidth)
      let canvasHeight = (canvas.height = pageHeight)

      const context = canvas.getContext('2d')
      context.font = '32px Arial'
      context.shadowColor = 'rgba(187, 187, 187, .8)'

      // 在绕画布逆方向旋转30度
      context.rotate(-rotateAngle)

      // 获取文本的最大长度
      const textWidth = Math.max(...labels.map(item => context.measureText(item).width))

      const lineHeight = 20
      const fontHeight = 40
      let positionY = 0
      let i = 0
      while (positionY <= pageHeight) {
        positionY = positionY + lineHeight * 5
        i++
      }
      canvasWidth += Math.sin(rotateAngle) * (positionY + i * fontHeight) // 给canvas加上画布向左偏移的最大距离
      canvasHeight = 2 * canvasHeight
      for (positionY = 0, i = 0; positionY <= canvasHeight; positionY = positionY + lineHeight * 5) {
        // 进行画布偏移是为了让画布旋转之后水印能够左对齐;
        context.translate(-(Math.sin(rotateAngle) * (positionY + i * fontHeight)) - (i % 2) * 50, 0)
        for (let positionX = 0; positionX < canvasWidth; positionX += 2 * textWidth - 100) {
          let spacing = 0
          labels.forEach(item => {
            context.fillText(item, positionX, positionY + spacing)
            context.fillStyle = 'rgba(187, 187, 187, .8)' // 字体颜色
            spacing = spacing + lineHeight
          })
        }
        context.translate(Math.sin(rotateAngle) * (positionY + i * fontHeight), 0)
        context.restore()
        i++
      }

      // ---------- 打包发布后 res.target 报错 undefined---------------
      // // 图片的base64编码路径
      // const dataUrl = canvas.toDataURL('image/png')
      // // 使用Xhr请求获取图片Blob
      // const xhr = new XMLHttpRequest()
      // xhr.open('get', dataUrl, true)
      // xhr.responseType = 'blob'
      // xhr.onload = res => {
      //   const imgBlob = res.target.response
      //   // 获取Blob图片Buffer
      //   imgBlob.arrayBuffer().then(async buffer => {
      //     const pngImage = await pdfDoc.embedPng(buffer)
      //     for (let i = 0; i < pages.length; i++) {
      //       pages[i].drawImage(pngImage)
      //     }

      //     // 序列化为字节
      //     const pdfBytes = await pdfDoc.save()
      //     // 预览
      //     this.preView(title, pdfBytes)
      //   })
      // }
      // xhr.send()
      // ---------- 打包发布后 res.target 报错 undefined---------------

      // ----------- 优化 --------------------
      // 图片的base64编码路径
      const png = canvas.toDataURL('img/png')
      // 获取图片
      const imagePDF = await pdfDoc.embedPng(png)
      for (let i = 0; i < pages.length; i++) {
        pages[i].drawImage(imagePDF)
      }

      // 序列化为字节
      const pdfBytes = await pdfDoc.save()
      // 预览
      this.preView(title, pdfBytes)
      // ----------- 优化 --------------------
    },

image.png

4、完整代码

<template>
  <div class="app-container">
    <div style="width: 100%; height: 860px">
      <div style="cursor: pointer;" @click="addWatermark">网站备案信息真实性承诺书</div>
    </div>
  </div>
</template>

<script>
import { degrees, PDFDocument, StandardFonts, rgb } from 'pdf-lib'

export default {
  data() {
    return {}
  },
  created() {
    // 根据不同环境获取IP地址
    let baseURL
    if (process.env.NODE_ENV === 'production') {
      baseURL = window.api.apiURL
    } else {
      baseURL = process.env.VUE_APP_BASE_API
    }
    this.base_url = baseURL
  },
  methods: {
    addWatermark() {
      // 为防止跨域问题,文件路径进行了拼接
      var url = this.base_url + '/review/GW11P22-test/CDCP/%E7%BD%91%E7%AB%99%E5%A4%87%E6%A1%88%E4%BF%A1%E6%81%AF%E7%9C%9F%E5%AE%9E%E6%80%A7%E6%89%BF%E8%AF%BA%E4%B9%A6.pdf'
      var title = '网站备案信息真实性承诺书'

      // 添加文本水印
      // this.modifyPdf(title, url)

      // 添加图片水印
      // this.embedImages(title, url)

      // // 添加 canvas 水印
      this.canvasWatermark(title, url)
    },

    // 添加文本水印
    async modifyPdf(title, url) {
      const existingPdfBytes = await fetch(url).then(res => res.arrayBuffer())
      const pdfDoc = await PDFDocument.load(existingPdfBytes)

      const helveticaFont = await pdfDoc.embedFont(StandardFonts.Helvetica) // 只支持英文

      // 自定义字体包路径,放在 public 目录
      // const fonturl = './font/font.ttf'
      // const fontBytes = await fetch(fonturl).then(res => res.arrayBuffer())
      // pdfDoc.registerFontkit(fontkit)
      // const customFont = await pdfDoc.embedFont(fontBytes)

      const pages = pdfDoc.getPages()
      for (let i = 0; i < pages.length; i++) {
        const page = pages[i]
        // width
        const { height } = page.getSize()
        for (let i = 0; i < 6; i++) {
          for (let j = 0; j < 6; j++) {
            page.drawText('SYCEE', {
              x: j * 100,
              y: height / 5 + i * 100,
              size: 30,
              font: helveticaFont,
              // font: customFont, // 自定义字体
              color: rgb(0.95, 0.1, 0.1),
              opacity: 0.2,
              rotate: degrees(-35)
            })
          }
        }
      }

      // 序列化为字节
      const pdfBytes = await pdfDoc.save()
      // 预览
      this.preView(title, pdfBytes)
    },

    // 添加图片水印
    async embedImages(title, url) {
      const existingPdfBytes = await fetch(url).then(res => res.arrayBuffer())
      const pdfDoc = await PDFDocument.load(existingPdfBytes)

      const pngUrl = 'https://pdf-lib.js.org/img/logo3.png'
      const pngImageBytes = await fetch(pngUrl).then(res => res.arrayBuffer())
      const pngImage = await pdfDoc.embedPng(pngImageBytes)
      const pngDims = pngImage.scale(0.5)
      const pages = pdfDoc.getPages()

      for (let i = 0; i < pages.length; i++) {
        const page = pages[i]
        const { width, height } = page.getSize()
        for (let i = 0; i < 6; i++) {
          for (let j = 0; j < 6; j++) {
            page.drawImage(pngImage, {
              x: width / 2 - pngDims.width / 2 + j * 100,
              y: height / 2 - pngDims.height + i * 100,
              width: pngDims.width,
              height: pngDims.height,
              rotate: degrees(-35)
            })
          }
        }
      }

      // 序列化为字节
      const pdfBytes = await pdfDoc.save()
      // 预览
      this.preView(title, pdfBytes)
    },

    // 添加 canvas 水印
    async canvasWatermark(title, url) {
      const existingPdfBytes = await fetch(url).then(res => res.arrayBuffer())
      const pdfDoc = await PDFDocument.load(existingPdfBytes)

      // 旋转角度大小
      const rotateAngle = Math.PI / 6

      // labels是要显示的水印文字,垂直排列
      const labels = []
      labels.push('巨蟹座不吃鱼SYCEE')

      const pages = pdfDoc.getPages()
      const size = pages[0].getSize()

      const pageWidth = size.width
      const pageHeight = size.height

      // 生成 canvas 文字图片
      const canvas = document.createElement('canvas')
      let canvasWidth = (canvas.width = pageWidth)
      let canvasHeight = (canvas.height = pageHeight)

      const context = canvas.getContext('2d')
      context.font = '32px Arial'
      context.shadowColor = 'rgba(187, 187, 187, .8)'

      // 在绕画布逆方向旋转30度
      context.rotate(-rotateAngle)

      // 获取文本的最大长度
      const textWidth = Math.max(...labels.map(item => context.measureText(item).width))

      const lineHeight = 20
      const fontHeight = 40
      let positionY = 0
      let i = 0
      while (positionY <= pageHeight) {
        positionY = positionY + lineHeight * 5
        i++
      }
      canvasWidth += Math.sin(rotateAngle) * (positionY + i * fontHeight) // 给canvas加上画布向左偏移的最大距离
      canvasHeight = 2 * canvasHeight
      for (positionY = 0, i = 0; positionY <= canvasHeight; positionY = positionY + lineHeight * 5) {
        // 进行画布偏移是为了让画布旋转之后水印能够左对齐;
        context.translate(-(Math.sin(rotateAngle) * (positionY + i * fontHeight)) - (i % 2) * 50, 0)
        for (let positionX = 0; positionX < canvasWidth; positionX += 2 * textWidth - 100) {
          let spacing = 0
          labels.forEach(item => {
            context.fillText(item, positionX, positionY + spacing)
            context.fillStyle = 'rgba(187, 187, 187, .8)' // 字体颜色
            spacing = spacing + lineHeight
          })
        }
        context.translate(Math.sin(rotateAngle) * (positionY + i * fontHeight), 0)
        context.restore()
        i++
      }

      // ---------- 打包发布后 res.target 报错 undefined---------------
      // // 图片的base64编码路径
      // const dataUrl = canvas.toDataURL('image/png')
      // // 使用Xhr请求获取图片Blob
      // const xhr = new XMLHttpRequest()
      // xhr.open('get', dataUrl, true)
      // xhr.responseType = 'blob'
      // xhr.onload = res => {
      //   const imgBlob = res.target.response
      //   // 获取Blob图片Buffer
      //   imgBlob.arrayBuffer().then(async buffer => {
      //     const pngImage = await pdfDoc.embedPng(buffer)
      //     for (let i = 0; i < pages.length; i++) {
      //       pages[i].drawImage(pngImage)
      //     }

      //     // 序列化为字节
      //     const pdfBytes = await pdfDoc.save()
      //     // 预览
      //     this.preView(title, pdfBytes)
      //   })
      // }
      // xhr.send()
      // ---------- 打包发布后 res.target 报错 undefined---------------

      // ----------- 优化 --------------------
      // 图片的base64编码路径
      const png = canvas.toDataURL('img/png')
      // 获取图片
      const imagePDF = await pdfDoc.embedPng(png)
      for (let i = 0; i < pages.length; i++) {
        pages[i].drawImage(imagePDF)
      }

      // 序列化为字节
      const pdfBytes = await pdfDoc.save()
      // 预览
      this.preView(title, pdfBytes)
      // ----------- 优化 --------------------
    },

    // 在浏览器新的标签页预览PDF
    preView(docTitle, stream) {
      const URL = window.URL || window.webkitURL
      const href = URL.createObjectURL(new Blob([stream], { type: 'application/pdf;charset=utf-8' }))
      // 由于项目要求,需要隐藏工具栏,所以拼接了 '#toolbar=0',删除拼接即可展示工具栏
      const wo = window.open(href + '#toolbar=0')
      // 设置新打开的页签 document title
      const timer = setInterval(() => {
        if (wo.closed) {
          clearInterval(timer)
        } else {
          wo.document.title = docTitle
        }
      }, 500)
    }
  }
}
</script>