pdf转canvas实现信息填写和手写签名再转pdf合并下载并保存

pdf转canvas实现填写信息和在线手写签名并保存为pdf文件本地下载

  • 安装依赖
  • pdf转canvas
  • 实现手写签名
  • canvas填写信息与覆盖
  • canvas转pdf
  • pdf合并
  • 效果

安装依赖

npm i pdfjs-dist
npm i html2canvas
npm i signature_pad
npm i jspdf
npm i html2canvas
npm i axios
npm i vant

pdf转canvas

这里需要用到pdfjs-dist依赖,首先将pdf文件放到文件目录中(这里我放的是vue的public目录中)

<template>
  <div class="pdf">
    <van-popup :close-on-click-overlay="false" v-model="loadingShow"><van-loading type="spinner" size="24px" color="#fff">文件生成中...van-loading>van-popup>
    <div class="box">
        <canvas
            v-for="page in pdf_pages" :id="'render_canvas'+page" :key="page" ref="pdfFiles" class="pdfCls">
        canvas>
        <div class="oprate">
            <van-field v-model="userName" label="乙方(姓名)" placeholder="请输入姓名" />
            <van-field v-model="userPhone" label="乙方(手机号码)" placeholder="请输入手机号码" error-message="" type="tel" maxlength="11" />
            <van-field v-model="userCode" label="乙方(身份证)" placeholder="请输入姓身份证" />
            <van-field v-model="userAddress" label="乙方(合同地址)" placeholder="请输入合同地址" />
            <div class="write">请签名:div>
            <canvas style="box-shadow: 0 5px 5px 0 gray;margin-left: 1rem;" className='canvas' id="canvasWrite" ref='canvasDom' width="340" height="150" />
            <div class="btn">
                <van-button type="info" @click="yes">确定van-button>
                <van-button type="info" @click="reset(1)">重置van-button>
            div>
        div>
    div>
  div>
template>
<style lang="scss" scoped>
.pdf{
    display: flex;
    flex-direction: column;
    align-items: center;
    padding-bottom: 1rem;
    overflow: hidden;
    font-family: Helvetica, Tahoma, Arial, 'PingFang SC', 'Hiragino Sans GB', 'Heiti SC', STXihei, 'Microsoft YaHei', SimHei;
    .van-popup--center{
        background-color: transparent;
        overflow: hidden;
        height: 2rem;
    }
    .btn{
        display: flex;
        padding-top: 1rem;
        .van-button{
            width: 10rem;
            margin-left: .5rem;
        }
    }
    .box{
        height: 100vh;
        overflow: scroll;
        .oprate{
            width: 50%;
            margin-left:4rem;
            .write{
                margin-left: 1rem;
            }
            .btn{
                .van-button{
                    margin-left: 3rem;
                }
            }
        }
    }
}
style>
<style lang="scss">
.pdf {
	.van-field__label{
    	width: 7rem;
    }
    .van-field{
        width: 20rem;
    }
}
style>
import PDFJS from 'pdfjs-dist'

data(){
    return{
        pdf_pages:0,
        pdf_div_width:'100vw',
        beginX:0,
        beginY:0,
        userName:'',
        userPhone:'',
        userCode:'',
        userAddress:'',
        count:0,//pdf接口提交次数
        pdf_url:[],//pdf阿里路径
        loadingShow:false,//加载弹出层
        pdfObj:[],//pdf文件对象
        imgData:[],//图片宽高数据

        s_y:'2023',//开始年
        s_m:'01',//开始月
        s_d:'11',//开始日
        e_y:'2024',//结束年
        e_m:'01',//结束月
        e_d:'11',//结束日

        pageNum_all:0,
        PromiseArr:[],
        PagesAll:0,
        PagesAllCope:0,
        callbackNum:0,
        myid:0,
        PromiseRes:[],
        upFile:null,//上传的文件
        sp:null,//签名对象
        yiSp:'',//乙方签名
        computerYN:'',//电脑or手机
    }
}

mounted(){
   const systemName = navigator.platform
   this.computerYN = systemName.includes("Win") || systemName.includes("Mac")//判断是电脑还是手机
   this.loadTestCanvas()
}
//加载pdf
loadCanvas() {
   const url = '/pdf/demo.pdf' //pdf地址
   let loadingTask = PDFJS.getDocument(url) //url的方式,下面介绍base64方式
   loadingTask.promise
       .then((pdf) => {
         this.pdfDoc = pdf
         this.pdf_pages = this.pdfDoc.numPages
         this.$nextTick(() => {
           //这边pdf就一页,默认为1页
           this.renderPage(1)
         })
       })
 },
//渲染pdf页
renderPage(num) {  
   this.pdfDoc.getPage(num)
     .then((page) => {
     let canvas = document.getElementById('render_canvas' + num)
     let ctx = canvas.getContext('2d')
     let dpr = window.devicePixelRatio || 1
     let bsr = ctx.webkitBackingStorePixelRatio ||
         ctx.mozBackingStorePixelRatio ||
         ctx.msBackingStorePixelRatio ||
         ctx.oBackingStorePixelRatio ||
         ctx.backingStorePixelRatio || 1
     let ratio = dpr / bsr
     //判断是电脑还是手机,缩放比例:电脑2,手机(ios浏览器最好<=1倍缩放,不然会出现白屏情况)
     let viewport = page.getViewport({scale: this.computerYN?2:.8})

     canvas.width = viewport.width * ratio
     canvas.height = viewport.height * ratio

     canvas.style.width = viewport.width + 'px'

     this.pdf_div_width = viewport.width + 'px'

     canvas.style.height = viewport.height + 'px'

     ctx.setTransform(ratio, 0, 0, ratio, 0, 0)
     let renderContext = {
         canvasContext: ctx,
         viewport: viewport
     }
     page.render(renderContext)
     if (this.pdf_pages > num) {
         this.renderPage(num + 1)
     }
  })
 },

实现手写签名

这里可以使用signaturePad插件初始化canvas

init(){
   const canvas = document.querySelector("canvas");
   this.sp = new signaturePad(canvas);
},

canvas填写信息与覆盖

canvas1.fillText('内容',700,470)后面两个值为当前canvas x,y轴的定位
ctx1.clearRect(132,458,44,12)前面两个值为,xy轴位置,后面两个值为从当前位置开始被覆盖的宽高

//放置签名生成的图片到指定位置
let image = new Image()
image.src = this.sp.toDataURL('image/png')
image.onload =()=>{
  this.computerYN?canvas3.drawImage(image,360,765,100,40):canvas3.drawImage(image,144,303,40,16)
}

canvas转pdf

这里会用到html2Canvas进行转化,但是会将转化的对象放进数组,最后调用方法统一合并,这是我目前能想到的方法

//打印
printPdf(dom,ind){
    html2Canvas(dom, {
        allowTaint: true,
        width: dom.offsetWidth, //设置获取到的canvas宽度
        height: dom.offsetHeight, //设置获取到的canvas高度
        x: 0, //页面在水平方向滚动的距离
        y: 0, //页面在垂直方向滚动的距离
    }).then((canvas) => {
    let canvasWidth = canvas.width;
    let canvasHeight = canvas.height;
    let pageHeight = (canvasWidth / 592.28) * 841.89; // 一页A4 pdf能显示的canvas高度
    let imgWidth = 595.28; // 设置图片宽度和A4纸宽度相等
    let imgHeight = (592.28 / canvasWidth) * canvasHeight;//等比例换算成A4纸的高度

    let totalHeight = imgHeight; // 需要打印的图片总高度,初始状态和图片高度相等
    let pageData = canvas.toDataURL('image/png', 1.0);
    this.imgData.push({
        h:imgHeight,//图片高
        w:imgWidth,//图片宽
        img:pageData,//图片数据
    })
    let PDF = new JsPDF('p', 'pt', 'a4', true);
    if (totalHeight < pageHeight) { 
        PDF.addImage(pageData, 'JPEG', 0, 0, imgWidth, imgHeight); // 从顶部开始打印
    } else {
        let top = 0;   // 打印初始区域
        while (totalHeight > 0) {
            PDF.addImage(pageData, 'JPEG', 0, top, imgWidth, imgHeight);  // 从图片顶部往下top位置开始打印
            totalHeight -= pageHeight;
            top -= 841.89;
            if (totalHeight > 0) {
                PDF.addPage();
            }
        }
    }
    this.pdfObj.push(PDF)
    this.count += 1;
    if(this.count >= 3){//pdf打印的张数
            this.createPDF()//合并
        }
    });
},
//重置
reset(type){
            let ctx1 = this.$refs.pdfFiles[0].getContext('2d')
            let ctx3 = this.$refs.pdfFiles[2].getContext('2d')
            
            type === 1?this.sp.clear():''
            if(this.computerYN){
            //以下代码中的坐标都是可以更改的,这只是我的文件的坐标,请自行更改
                ctx3.clearRect(790,855,250,30)
                ctx1.clearRect(250,398,250,30)
                ctx1.clearRect(290,444,250,30)
                ctx1.clearRect(700,444,250,30)

                ctx1.clearRect(330,1144,110,30)
                ctx1.clearRect(460,1144,70,30)
                ctx1.clearRect(550,1144,60,30)
                ctx1.clearRect(655,1144,70,30)
                ctx1.clearRect(750,1144,60,30)
                ctx1.clearRect(830,1144,120,30)
                ctx3.clearRect(350,755,240,50)
                return
            }
            // ctx3.clearRect(316,342,100,12)覆盖图片(手机端)
            ctx1.clearRect(100,159,100,12)
            ctx1.clearRect(116,177,100,12)
            ctx1.clearRect(280,177,100,12)

            ctx1.clearRect(132,458,44,12)
            ctx1.clearRect(184,458,28,12)
            ctx1.clearRect(220,458,24,12)
            ctx1.clearRect(262,458,28,12)
            ctx1.clearRect(300,458,24,12)
            ctx1.clearRect(332,458,48,12)

            ctx3.clearRect(315,335,96,20)//签署日期
            ctx3.clearRect(138,300,100,30)//覆盖签名
        },
        //确定
         yes(){
            if(!(this.userName&&this.userCode&&this.userPhone&&this.userAddress)){
                return this.$toast('请将信息填写完整')
            }
            this.reset()//重置

            const canvas3 = document.getElementById("render_canvas3").getContext('2d');
            let image = new Image()
            image.src = this.sp.toDataURL('image/png')
            image.onload =()=>{
                this.computerYN?canvas3.drawImage(image,360,765,100,40):canvas3.drawImage(image,144,303,40,16)
            canvas3.font = this.computerYN?'18px serif':'10px serif'
            let nowDates = new Date().getTime()
            this.computerYN?canvas3.fillText(this.format(nowDates),800,875):canvas3.fillText(this.format(nowDates),315,351)//姓名(签字)
            
            const canvas1 = document.getElementById("the-canvas1").getContext('2d');
            canvas1.font = this.computerYN?'22px serif':'10px serif'
            if(this.computerYN){
                canvas1.fillText(this.userName,250,425)
                canvas1.fillText(this.userCode,290,470)
                canvas1.fillText(this.userPhone,700,470)
            }else{
                canvas1.fillText(this.userName,100,170)
                canvas1.fillText(this.userCode,116,188)
                canvas1.fillText(this.userPhone,280,188)
            }

            let nowDate = new Date().getTime() // 合同开始时间
            let yestDate = nowDate +  31536000000 // 合同结束时间
            this.format(nowDate,0)
            this.format(yestDate,1)
            if(this.computerYN){
            //以下代码中的坐标都是可以更改的,这只是我的文件的坐标,请自行更改
                canvas1.fillText(this.s_y,330,1170)
                canvas1.fillText(this.s_m,500,1170)
                canvas1.fillText(this.s_d,570,1170)
                canvas1.fillText(this.e_y,660,1170)
                canvas1.fillText(this.e_m,760,1170)
                canvas1.fillText(this.e_d,900,1170)
            }else{
                canvas1.fillText(this.s_y,135,468)
                canvas1.fillText(this.s_m,195,468)
                canvas1.fillText(this.s_d,228,468)
                canvas1.fillText(this.e_y,265,468)
                canvas1.fillText(this.e_m,305,468)
                canvas1.fillText(this.e_d,360,468)
            }
            this.loadingShow = true
            
            this.$refs.pdfFiles.forEach((val,index)=>{
                this.printPdf(val,index)
            })
        }
        },
        add0(m){
            return m<10?'0'+m:m
        },
        //type:0开始时间 1结束时间
        format(date,type) {
            if(!date) return;
            //时间戳是整数,否则要parseInt转换
            var time = new Date(date);
            var y = time.getFullYear();
            var m = time.getMonth()+1;
            var d = time.getDate();
            // var h = time.getHours();
            // var mm = time.getMinutes();
            // var s = time.getSeconds();
            if(type === 0){
                this.s_y = y
                this.s_m = this.add0(m)
                this.s_d = this.add0(d)
            }else if(type === 1){
                this.e_y = y
                this.e_m = this.add0(m)
                this.e_d = this.add0(d)
            }else{
                return y+'年'+this.add0(m)+'月'+this.add0(d)+'日';
            }
        },

pdf合并

//合并pdf
createPDF() {
	let doc = new JsPDF('p', 'pt', 'a4', true);
	this.$refs.pdfFiles.forEach((canvas,index) => {
	if (index > 0) doc.addPage() //添加页
	//a4纸的尺寸[595.28,841.89],html页面生成的canvas在pdf中图片的宽高
	doc.addImage(this.imgData[index].img, 'JPEG', 0, 0, this.imgData[index].w, this.imgData[index].h)
	})
     this.upFile = this.base64ConvertFile(doc.output("dataurlstring"),'测试文件')
     // console.log(this.upFile);
     let fom = new FormData()
     fom.append('file',this.upFile,this.upFile.name)
     fom.append('mobile',this.userPhone)
     //发送文件给oss
     Axios.post('你的文件上传的地址',fom,{
         headers:{'Content-Type':'multipart/form-data'}
     }).then(result =>{
         // console.log(result);
         doc.save('合同.pdf')
         this.$toast('生成成功')
         this.count = 0
         this.loadingShow = false
     }).catch(err=>{this.loadingShow = false})
},
// base64转file
base64ConvertFile (urlData, filename) { 
     var arr = urlData.split(',');
     var type = arr[0].match(/:(.*?);/)[1];
     var fileExt = type.split('/')[1];
     var bstr = atob(arr[1]);
     var n = bstr.length;
     var u8arr = new Uint8Array(n);
     while (n--) {
         u8arr[n] = bstr.charCodeAt(n);
     }
     return new File([u8arr], filename + "." + fileExt, {
         type: type
     });
 },

效果

pdf转canvas实现信息填写和手写签名再转pdf合并下载并保存_第1张图片

到这里基本就完成了,要是小伙伴们有更好的解决方案或者其它问题可在下方留言,一起探讨技术问题。

你可能感兴趣的:(pdf,前端,javascript,vue,html)