pdf实现电子手写签名的踩坑及解决

1,272 阅读1分钟

需求

有一个pdf模板文档,需要根据客户输入生成协议,并手写签字

项目使用框架

vue-cli, vue:^2.5.10

实现签名使用框架

"pdfjs-dist": "^2.5.2076" // 用于将pdf渲染成canvas
"pdf-lib": "^1.17.1" // 用于给pdf打水印,手写签名后将签名想水印一样印在pdf上

实现

pdf渲染成canvas

<div v-for="(item, i) in imgFiles" :key="i" class="pdf-page">
  <div class="canvas-page">
    <canvas :id="`pdf_canvas_${item}`" style="border: 1px solid #eeeeee;"></canvas>
    <canvas class="sign_canva" :id="`sign_canva_${item}`" style="border: 1px solid #eeeeee;" @mousedown="e => mousedown(e, i)"
            @mousemove="e => mousemove(e, i)"
    @mouseup="mouseup"></canvas>
  </div>
</div>
import * as pdfjs from 'pdfjs-dist'
import * as pdfjsWorker from 'pdfjs-dist/build/pdf.worker.entry'
pdfjs.GlobalWorkerOptions.workerSrc = pdfjsWorker
//...
data () {
  return {
    basePdf: 'https://xxxx.pdf' //pdf模版
  }
},
methods: {
   // 请求pdf并转换成base64
    init() {
      const self = this
      const request = new XMLHttpRequest();
      request.open('GET', this.basePdf, true);
      request.responseType = 'arraybuffer';
      request.onreadystatechange = async function () {
        if (request.readyState === 4 && request.status === 200) {
          // 转换成base64
          self.base64 = self.translateArrayBufferToBase64(request.response);
          // base64渲染
          await self.render(self.base64)
        }
      };
      request.send()
    },
    
    // arraybuffer转base64
    translateArrayBufferToBase64(buffer){
      let binaryStr = "";
      const bytes = new Uint8Array(buffer);
      for(let i=0,len = bytes.byteLength;i<len;i++){
        binaryStr +=String.fromCharCode(bytes [i]);
      }
      // 注意
      return 'data:application/pdf;base64,' + window.btoa(binaryStr );
    },
    // 渲染pdf
    render(base64) {
      let that = this;
      const loadingTask = pdfjs.getDocument(base64)
      loadingTask.promise.then((pdf) => {
        var pageNum = pdf.numPages;

        //准备图片
        for (let i = 1; i <= pageNum; i++) {
          let one = i;
          that.imgFiles.push(one);
        }
        //处理
        for (let i = 1; i <= pageNum; i++) {
          pdf.getPage(i).then((page) => {
            const viewport = page.getViewport({ scale: 4 })
            // 初始化渲染pdf的canvas
            const canvas = that.initCanvas('pdf_canvas_' + i, viewport);
            // 初始化用于签名的canvas
            that.signCanvass.push(that.initCanvas('sign_canva_' + i, viewport));
            const ctx = canvas.getContext('2d')
            page.render({
              canvasContext: ctx,
              viewport,
            })
          })
        }
      })
    },
    // 初始化canvas
    initCanvas(idName, viewport) {
      const canvas = document.getElementById(idName);
      canvas.height = viewport.height;
      canvas.width = viewport.width;
      this.scale = viewport.width / 1000;
      const destWidth = 1000;
      canvas.style.width = destWidth + 'px';
      canvas.style.height = destWidth * (viewport.height / viewport.width) + 'px';
      return canvas
    },
}
  • 先请求pdf,转成base64
  • 将base64渲染在canvas上,同时在pdf的canvas上覆盖一个一样大小canvas用于签名

将签名印到pdf上(pdf-lib)

import { PDFDocument } from 'pdf-lib';
async savePDF(){
  const self = this
  const pdfDoc = await PDFDocument.load(this.base64); // pdf的base64
  const pages = pdfDoc.getPages();
  for (let i = 0; i < pages.length; i++) {
    //对应下标的 签名画布中的内容生成 png图片
    const eleImgCover = await pdfDoc.embedPng(this.signCanvass[i].toDataURL('image/PNG'));
    //页面中添加水印
    pages[i].drawImage(eleImgCover, {
      x: 0,
      y: 0,
      width: Math.round(1092.82 / 1.79), 
      height: Math.round(1414.24 / 1.79), //这里要进行缩放,因为之前的画布我们是放大过的
    });
  }
  // 生成blob流
  const pdfBytes = await pdfDoc.save();
  // 这里将blob流转换成file文件,上传oss.也可以将blob转ArrayBuffer,再转buffer传oss
  const file = this.BlobToFile(new Blob([pdfBytes]))
},
// blob流转file,
BlobToFile(blob) {
  let filename = '协议.pdf'
  var file = new File([blob], filename, {
    type: 'application/pdf',
    lastModified: Date.now()
  });
  return file;
},
  • 签名好后,将签名的canvas转成png,然后覆在pdf上
  • pdfDoc.save()将打好水印的pdf生成流
  • 最后将流转成文件,可以上传oss或者其他地方,得到签好名的pdf

遇到的坑

  1. 在国外镜像下一切正常,部署到服务器之后编译出错,

image.png 大概意思是pdfjs-dist里用了高级语法,编译的时候没有转成es5

解决:用 babel-loader将高级语法转成es5

vue.config.js里面添加代码

chainWebpack: (config) => {
  config.module
    .rule('pdfjs-dist')
    .test({
      test: /.js$/,
      include: path.join(__dirname, 'node_modules/pdfjs-dist')
    })
    .use('babel-loader')
    .loader('babel-loader')
},
  1. 可以运行但是页面报错(..).split is not xxx

解决:降级pdfjs-dist版本为2.14.305