Vue3 -- PDF展示、添加签名(带笔锋)、导出_smooth-signature(1)

本文介绍了如何在Vue应用中使用pdfJS和SmoothSignature库实现PDF文件的展示与签名功能,并讨论了生成的PDF图片化导致的问题和改进方案,包括使用PDF-Lib处理签名画布并添加到原PDF中,以及确保页面滚动同步。
摘要由CSDN通过智能技术生成

##### 添加引用


这里要注意的是,需要给 **pdfJS** 指定工作路径



import { Button, Input } from ‘ant-design-vue’;
import { defineComponent, ref } from ‘vue’;
import SmoothSignature from ‘smooth-signature’;
import * as pdfJS from ‘pdfjs-dist’;
import * as pdfjsWorker from ‘pdfjs-dist/build/pdf.worker.entry’;
import JsPDF from ‘jspdf’;

pdfJS.GlobalWorkerOptions.workerSrc = pdfjsWorker;


##### 实现代码


代码中添加了主要的注释,可以查看下述代码



export default defineComponent({
components: { Button, Input },
setup() {
const fielinput = ref(null);
const contentDiv = ref(null);

  //签名相关
  const isSign = ref(false); //控制是否允许签名
  const canvass = ref([]); //保存所有画布元素
  const signatures = ref([]); //所有签名对象
  const historys = ref([]); //签名历史 用于回退或者清除,因为是一次性展示多个页面,会存在多个包装好的签名对象,存放历史列表方便操作

  //PDF展示相关
  const pdfData = ref(null); // PDF 内容
  const scale = ref(2); //放大比例 ,有的时候展示可能会比较模糊,可以放大展示

  //上传控件选择事件,加载选中的 PDF 文件
  const uploadFile = (e: Event) => {
    // 断言为HTMLInputElement
    const target = e.target as HTMLInputElement;
    const files = target.files;
    let reader = new FileReader();
    reader.readAsDataURL(files[0]);
    reader.onload = () => {
      let data = atob(reader.result.substring(reader.result.indexOf(',') + 1));
      loadPdfData(data);
    };
  };
  //加载PDF
  function loadPdfData(data) {
    //移除所有旧的 Canvas 画布元素
    removeChild();
    //重置对象状态
    isSign.value = false;
    canvass.value = [];
    signatures.value = [];
    // 引入pdf.js的字体,如果没有引用的话字体可能会不显示
    let CMAP\_URL = 'https://unpkg.com/pdfjs-dist@2.0.943/cmaps/';
    //读取base64的pdf流文件
    pdfData.value = pdfJS.getDocument({
      data: data, // PDF base64编码
      cMapUrl: CMAP\_URL,
      cMapPacked: true,
    });
    //渲染全部页面
    renderAllPages();
  }
  //移除页面上旧的元素
  function removeChild() {
    var content = contentDiv.value;
    var child = content.lastElementChild;
    while (child) {
      content.removeChild(child);
      child = content.lastElementChild;
    }
  }
  //渲染全部页面
  function renderAllPages() {
    pdfData.value.promise.then((pdf) => {
      for (let i = 1; i <= pdf.numPages; i++) {
        pdf.getPage(i).then((page) => {
          let viewport = page.getViewport(scale.value);
          //动态生成 Canvas 画布并设置宽高
          var canvas = document.createElement('canvas');
          canvas.height = viewport.height;
          canvas.width = viewport.width;

          let ctx = canvas.getContext('2d');
          let renderContext = {
            canvasContext: ctx,
            viewport: viewport,
          };
          //将 PDF 页面渲染到 Canvas 上
          page.render(renderContext).then(() => {});
          //将画布包装成 SmoothSignature
          initSignatureCanvas(canvas);
          //将画布元素放入到 div 容器中展示
          canvass.value.push(canvas);
          contentDiv.value.appendChild(canvas);
        });
      }
    });
  }
  //初始化签名对象
  const initSignatureCanvas = (canvas) => {
    const optionSign = {
      width: canvas.width,
      height: canvas.height,
      maxHistoryLength: 100, //最大历史记录
    };

    const signature = new SmoothSignature(canvas, optionSign);
    //初始化时 先移除它内部添加的监听事件,默认不能签名
    signature.removeListener();
    //签名对象 addHistory 方法做一下修改,在原来逻辑的基础上添加这一行
    // historys.value.push(signature); 方便处理历史签名记录
    signature.addHistory = function () {
      if (!signature.maxHistoryLength || !signature.canAddHistory) return;
      signature.canAddHistory = false;
      signature.historyList.push(signature.canvas.toDataURL());
      signature.historyList = signature.historyList.slice(-signature.maxHistoryLength);
      historys.value.push(signature);
    };
    signatures.value.push(signature);
  };
  /\*\*

* 签名预览转换
* 允许签名:调用 signature 对象中的 addListener 方法,添加监听事件
* 不允许签名:调用 signature 对象中的 removeListener 方法,移除监听事件
*/
const handleSign = () => {
isSign.value = !isSign.value;
if (signatures.value && signatures.value.length > 0) {
if (isSign.value) {
for (let i = 0; i < signatures.value.length; i++) {
signatures.value[i].addListener();
}
} else {
for (let i = 0; i < signatures.value.length; i++) {
signatures.value[i].removeListener();
}
}
}
};
/**
* 后退操作
* 调用历史签名记录中的 signature 对象中的 undo 方法会撤回当前对象中的最后一次的画线记录
* 注意:后退后不要忘记将列表中最后一个元素移除
*/
const handleUndo = () => {
if (historys.value && historys.value.length > 0) {
const signatureList = historys.value;
let signature = signatureList.pop();
signature.undo();
historys.value = signatureList;
}
};
// 清除所有 循环把所有签名历史都处理了
const handleClear = async () => {
while (historys.value && historys.value.length > 0) {
handleUndo();
}
};
// 下载PDF
const savePDF = () => {
//生成新的 PDF
let pdf = new JsPDF(‘’, ‘pt’, ‘a4’);
if (canvass.value.length > 0) {
//将 canvas 内容转化成 JPEG
for (let i = 0; i < canvass.value.length; i++) {
const ccccc = canvass.value[i];
let pageData = ccccc.toDataURL(‘image/JPEG’);
if (i > 0) {
pdf.addPage();
}
pdf.addImage(
pageData,
‘JPEG’,
0,
0,
ccccc.width / scale.value,
ccccc.height / scale.value,
);
}
//到处新的PDF
return pdf.save(‘TestPdf.pdf’);
}
};

  return {
    fielinput,
    uploadFile,
    contentDiv,
    isSign,
    handleSign,
    handleUndo,
    handleClear,
    savePDF,
  };
},
mounted() {},

});


##### 效果展示


![在这里插入图片描述](https://img-blog.csdnimg.cn/e4f647169203471d9cbc62eb720006a4.png)  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/d3efb341b2c844eca264cb2f14d516d2.png)


##### 缺点


1、生成的新的PDF每一页都是一个图片,这就表示 PDF 中的内容无法被解析,文字再也无法被选中了。  
 2、因为生成的是图片,所以最终效果可能会变模糊,可以通过放大比例去优化展示效果,但是始终不是一个最优的解决方案。


### 方案二


方案二使用一个新的组件 **pdf-lib** 来处理最后生成的 **PDF**  
 方案二仍旧使用 **pdfjs-dist** 在 **Canvas** 上展示 **PDF**,并使用 **smooth-signature** 使得画布拥有笔锋签名效果。  
 不同的是,这一次签名画布和 PDF 展示画布并不再是同一个画布,而是上下重叠的两个分离的画布  
 这样一来,我们可以将签名画布上的内容生成一个透明背景的 **PNG** 图片,然后以水印的方式添加到原来的 **PDF** 文件中。


#### 修改页面元素


需要两个 Div 容器 ,父容器的滚动条需要同步滚动,否则会出现签名在滚动,但是 **PDF** 页面不动的情况




#### 替换引用



//import JsPDF from ‘jspdf’;
import { PDFDocument } from ‘pdf-lib’;


#### 修改代码


**文章底部附完整代码**




const signCanvass = ref([]); //保存所有签名画布
const base64 = ref(null); //读取的pdf的base64数据


上传文件的方法中添加一行保存PDF base64 ,生成新的 PDF 时使用



const uploadFile = (e: Event) => {

reader.onload = () => {
base64.value = reader.result;

};
};


加载 PDF 时,我们要重置的对象增加了,而且加载完之后我们要让两个父容器滚动同步



function loadPdfData(data) {
removeChild();

signCanvass.value = []; //重置

renderAllPages();

//两个DIV协同滚动
var div1 = document.getElementById(‘parentDiv1’);
var div2 = document.getElementById(‘parentDiv2’);
div1.addEventListener(‘scroll’, function () {
div2.scrollLeft = div1.scrollLeft;
div2.scrollTop = div1.scrollTop;
});
div2.addEventListener(‘scroll’, function () {
div1.scrollLeft = div2.scrollLeft;
div1.scrollTop = div2.scrollTop;
});
}


移除页面元素的时候,我们要将两个 **div** 容器中的元素都移除掉



### 文末

技术是没有终点的,也是学不完的,最重要的是活着、不秃。

零基础入门的时候看书还是看视频,我觉得成年人,何必做选择题呢,两个都要。喜欢看书就看书,喜欢看视频就看视频。

最重要的是在自学的过程中,一定不要眼高手低,要实战,把学到的技术投入到项目当中,解决问题,之后进一步锤炼自己的技术。

自学最怕的就是缺乏自驱力,一定要自律,杜绝“三天打鱼两天晒网”,到最后白忙活一场。

高度自律的同时,要保持耐心,不抛弃不放弃,切勿自怨自艾,每天给自己一点点鼓励,学习的劲头就会很足,不容易犯困。

技术学到手后,找工作的时候一定要好好准备一份简历,不要无头苍蝇一样去海投简历,容易“竹篮打水一场空”。好好的准备一下简历,毕竟是找工作的敲门砖。

拿到面试邀请后,在面试的过程中一定要大大方方,尽力把自己学到的知识舒适地表达出来,不要因为是自学就不够自信,给面试官一个好的印象,面试成功的几率就会大很多,加油吧,骚年!

**[开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】](https://bbs.csdn.net/topics/618166371)**
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值