pdf转图片并下载

1,865 阅读4分钟

一、实现效果

选择本地 pdf 文件上传,会生成 pdf 文件的预览,点击保存功能。

预览图1

预览图2

预览图3


二、所用插件

  1. pdf 文件相关的文件处理插件:pdfjs-dist@2.3.200
  2. zip 压缩文件相关的包: jszip@3.5.0
  3. 文件保存下载相关的包:file-saver@2.0.2

三、相关代码

vue 中测试


<!--
 * @Date: 2020-10-21 10:44:54
 * @information: pdf 转图片并下载
-->
<template>
  <div id="page09">

    <div class="info-box">
      <div class="input">
        <input id="input" type="file" accept="application/pdf" @change="convertFile()"/>
      </div>
      <div class="cell">
        <div>名称:{{fileName || '-'}}</div>
        <div>大小:{{Number(fileSize).toFixed(2)}}M</div>
        <div>页数:{{filePage}}</div>
        <button @click="onExportImg">保存图片</button>
      </div>
    </div>

    <div id="container"></div>

  </div>
</template>

<script>
import PDFJS from 'pdfjs-dist'
import JSZIP from 'jszip'
import FileSaver from 'file-saver'

export default {
  data() {
    return {
      // pdf地址
      pdfPath2: `https://mozilla.github.io/pdf.js/web/compressed.tracemonkey-pldi-09.pdf`,
      // arrayBuffer
      arrayBuffer: null,
      // 文件名称
      fileName: null,
      // 文件大小
      fileSize: 0,
      // 文件页数
      filePage: 0,
    }
  },
  methods: {

    /**
     * 读取文件内容
     */
    convertFile() {
      let file = document.getElementById('input').files
      if(!file.length) return;
      let {name, size} = file[0]
      Object.assign(this, {fileName: name, fileSize: size/1024/1024})
      // 使用FileReader对象,web应用程序可以异步的读取存储在用户计算机上的文件(或者原始数据缓冲)内容,可以使用File对象或者Blob对象来指定所要处理的文件或数据
      let fileReader = new FileReader()
      // 异步按字节读取文件内容,结果用ArrayBuffer对象表示
      fileReader.readAsArrayBuffer(file[0])
      fileReader.onload = (e) => {
        let arrayBuffer = this.arrayBuffer = e.target.result
        // 创建canvas节点
        this.createCanvas(arrayBuffer)
      }
    },

    /**
     * 创建canvas
     */
    createCanvas(val) {
      // 清空节点下数据
      document.getElementById('container').innerHTML = ''
      // 使用getTextContent获取pdf内容
      PDFJS.getDocument(val).promise.then(el => {
        let filePage = this.filePage = el.numPages
        for(let i = 1; i <= filePage; i ++) {
          let canvas = document.createElement('canvas')
          canvas.id = `pageNum-${i}`
          let context = canvas.getContext('2d')
          document.getElementById('container').append(canvas)
          // 渲染canvas
          this.openPage(el, i, context)
        }
      })
    },

    /**
     * 渲染canvas
     */
    openPage(pdfFile, pageNumber, context) {
      // 获取PDF文档中的各个页面
      pdfFile.getPage(pageNumber).then(page => {
        // 设置展示比例
        let scale = 3
        // 获取pdf尺寸
        let viewport = page.getViewport(scale)
        let canvas = context.canvas
        canvas.width = viewport.width
        canvas.height = viewport.height
        canvas.style.width = "100%"
        canvas.style.height = "100%"

        let model = {
          canvasContext: context,
          viewport: viewport,
        }
        // 渲染PDF
        page.render(model)
      })
    },

    /**
     * 保存图片
     */
    onExportImg() {
      if(!this.arrayBuffer) {
        alert(`请上传pdf文件`)
        return;
      }

      let jszip = new JSZIP()
      // 解压缩后的文件夹名称
      let images = jszip.folder("images")
      let eleList = document.querySelectorAll('canvas')
      // 遍历所有canvas节点
      for(let i = 0; i < eleList.length; i ++) {
        let canvas = document.getElementById(`pageNum-${i+1}`)
        // 向此文件夹中加入文件
        // toDataURL() 方法返回一个包含图片展示的 data URI 。可以使用 type 参数其类型,默认为 PNG 格式。图片的分辨率为96dpi
        images.file(`image-${i+1}.png`, this.dataURLtoBlob(canvas.toDataURL("image/png", 1.0)), {
          base64: true
        })
      }

      // 生成一个zip文件
      jszip.generateAsync({
        type: "blob"
      }).then((content) => {
        // 使用 FileSaver 保存下载 zip 文件
        FileSaver.saveAs(content, "pdfToImages.zip");
      })
    },

    /**
     * dataURL 转成 Blob
     */
    dataURLtoBlob(val) {
      let arr = val.split(','),
          mime = arr[0].match(/:(.*?);/)[1],
          bstr = atob(arr[1]),
          n = bstr.length,
          u8arr = new Uint8Array(n);
      while(n --) {
        // charCodeAt() 方法可返回指定位置的字符的 Unicode 编码。这个返回值是 0 - 65535 之间的整数
        u8arr[n] = bstr.charCodeAt(n)
      }
      // new Blob(blobParts, [options]) 参数说明:
      // 1. blobParts:数组类型,数组中的每一项连接起来构成Blob对象的数据,数组中的每项元素可以是ArrayBuffer, ArrayBufferView, Blob, DOMString
      // 2. options:可选项,字典格式类型,可以指定如下两个属性:
      //    (1) type,默认值为 "",它代表了将会被放入到blob中的数组内容的MIME类型。
      //    (2) endings,默认值为"transparent",用于指定包含行结束符\n的字符串如何被写入。 它是以下两个值中的一个: "native",表示行结束符会被更改为适合宿主操作系统文件系统的换行符; "transparent",表示会保持blob中保存的结束符不变

      return new Blob([u8arr], { type: mime })
    },





  },
  created() {

  },
  mounted() {

  }
}
</script>

<style lang="scss">
#page09 {
  width: 1000px;
  margin: 0 auto;

  .info-box {
    position: relative;

    .input {
      margin: 15px 0;

      #input {
        width: 100%;
        height: 100%;
        cursor: pointer;
      }
    }

    .cell {
      margin: 15px 0;
      display: flex;
      justify-content: space-around;

      div {
        margin-right: 20px;
      }
    }
  }

  #container {
    width: 100%;
    min-height: 850px;
    margin:  0 auto;
    border: 1px solid #eee;
    border-radius: 10px;

    canvas {
      margin-bottom: 10px;
      border: 1px solid #ff6700;
      border-radius: 10px;
    }
  }


}
</style>

四、相关知识点

1. 使用 FileReader 进行文件读取

[HTML5] FileReader 对象

(1)创建实例

let fileReader = new FileReader()

(2)方法

方法定义描述
abort():void终止文件读取操作
readAsArrayBuffer(file):void异步按字节读取文件内容,结果用 ArrayBuffer 对象表示
readAsBinaryString(file):void异步按字节读取文件内容,结果为文件的二进制串
readAsDataURL(file):void异步读取文件内容,结果用 data:url 的字符串形式表示
readAsText(file,encoding):void异步按字符读取文件内容,结果用字符串形式表示

(3)事件: FileReader 通过异步的方式读取文件内容,结果均是通过事件回调获取。

方法定义描述
onabort当读取操作被中止时调用
onerror当读取操作发生错误时调用
onload当读取操作成功完成时调用
onloadend当读取操作完成时调用,不管是成功还是失败
onloadstart当读取操作将要开始之前调用
onprogress在读取数据过程中周期性调用

2. 使用 canvas 的 toDataURL() 方法返回 包含 data URI 的 DOMString

let canvas = document.getElementById(`canvas`)

// 参数说明:
// 1. type(可选):图片格式,默认为 image/png
// 2. encoderOptions(可选):在指定图片格式为 image/jpeg 或 image/webp的情况下,可以从 0 到 1 的区间内选择图片的质量。如果超出取值范围,将会使用默认值 0.92。其他参数会被忽略。
let result = canvas.toDataURL('image/png', 1.0)

MDN: HTMLCanvasElement.toDataURL()


3. ArrayBuffer 二进制数组

ArrayBuffer,二进制数组

(1) JS 中的二进制数据格式,例:ArrayBuffer,Uint8Array,DataView,Blob,File 及其他。

基本的二进制对象是 ArrayBuffer —— 对固定长度的连续内存空间的引用。

(2)如要操作 ArrayBuffer,我们需要使用“视图”对象。 视图对象本身并不存储任何东西。它是一副“眼镜”,透过它来解释存储在 ArrayBuffer 中的字节。例如下:

视图类型说明
Uint8Array将 ArrayBuffer 中的每个字节视为 0 到 255 之间的单个数字(每个字节是 8 位,因此只能容纳那么多)。这称为 “8 位无符号整数”。
Uint16Array将每 2 个字节视为一个 0 到 65535 之间的整数。这称为 “16 位无符号整数”。
Uint32Array将每 4 个字节视为一个 0 到 4294967295 之间的整数。这称为 “32 位无符号整数”。
Float64Array将每 8 个字节视为一个 5.0x10-324 到 1.8x10308 之间的浮点数。

3. Blob

细说 Web API 中的 Blob

(1)概述: Blob 对象表示一个不可变、原始数据的类文件对象。它的数据可以按文本或二进制的格式进行读取,也可以转换成 ReadableStream 来用于数据操作。Blob 表示的不一定是 JavaScript 原生格式的数据。File 接口基于 Blob,继承了 blob 的功能并将其扩展使其支持用户系统上的文件。

(2)创建 Blob 对象

// 参数说明
// 1. blobParts: 数组类型,数组中的每一项连接起来构成Blob对象的数据,数组中的每项元素可以是ArrayBuffer, ArrayBufferView, Blob, DOMString
// 2. options:可选项,字典格式类型,可以指定如下两个属性:

type,默认值为 "",它代表了将会被放入到blob中的数组内容的MIME类型。
endings,默认值为"transparent",用于指定包含行结束符\n的字符串如何被写入。 它是以下两个值中的一个: "native",表示行结束符会被更改为适合宿主操作系统文件系统的换行符; "transparent",表示会保持blob中保存的结束符不变。

let blob = new Blob(blobParts[, options])

let data1 = "a"
let data2 = { "name": "abc" }

let blob1 = new Blob([data1])
let blob2 = new Blob([JSON.stringify(data4)])
let blob3 = new Blob([data4])

console.log(blob1);  // Blob {size: 1, type: ""}
console.log(blob2);  // Blob {size: 14, type: ""}
console.log(blob3);  // Blob {size: 15, type: ""}

size 代表 Blob 对象中所包含数据的字节数。这里要注意,使用字符串和普通对象创建 Blob 时的不同,blob4 使用通过 JSON.stringify 把 data4 对象转换成 json 字符串,blob5 则直接使用 data4 创建,两个对象的 size 分别为 14 和 15。blob4 的 size 等于 14 很容易理解,因为 JSON.stringify(data4)的结果为:"{"name":"abc"}",正好 14 个字节(不包含最外层的引号)。blob5 的 size 等于 15 是如何计算而来的呢?实际上,当使用普通对象创建 Blob 对象时,相当于调用了普通对象的 toString()方法得到字符串数据,然后再创建 Blob 对象。所以,blob5 保存的数据是"[object Object]",是 15 个字节(不包含最外层的引号)。

(3)Blob 方法: slice()

(4)Blob 使用场景

  • 分片上传
  • Blob URL

PDF 转成图片