在 Vue 中集成 Mozilla/PDF.js ,实现自定义的 PDF 预览器,以及给被预览的 PDF 添加水印

实现效果

可用插件介绍

Mozilla 提供了 PDF.js 和 pdfjs-dist ,两者的区别如下:

  • PDF.js ,一个完整的 PDF 查看器,可以直接使用其提供的 viewer.html 查看 PDF 内容,包含完整样式和相关功能。优点是快速集成,不需要自己实现查看器的功能和样式。缺点是如果要自定义样式和功能,反而会很麻烦。
  • pdfjs-dist ,PDF.js 的预购建版本,只包含 PDF 内容的渲染功能,需要自己实现查看器的样式和相关功能。

Vue 官方插件库 Awesome Vue.js 推荐的 vue-pdf 就是对 pdfjs-dist 进行了封装实现,一般情况下使用 vue-pdf 即可快速实现 PDF 的预览效果。

根据需求进行插件选型

我们的需求是在现有页面中实现 PDF 预览的同时,在 PDF 内容上添加水印。

PDF.js 这种完整版的查看器显得过于臃肿,而 vue-pdf 虽然可以快速实现预览效果,但在添加水印时需要对显示 PDF 的 canvas 进行二次渲染,经过尝试后发现会抛出 Failed to execute 'drawImage' on 'CanvasRenderingContext2D': Overload resolution failed. 的错误。

所以最后选择直接集成 pdfjs-dist 来完成全部功能,主要实现方式参考自 vue项目,npm install方式使用pdfjs

安装和引入插件

安装

yarn add pdfjs-dist

引入

必须手动指定 workerSrc ,不然会抛出 Setting up fake worker failed 的错误。

虽然本地目录 node_modules/pdfjs-dist/build/pdf.worker.js 存在该文件,但实际引入时依旧会报错,所以只能使用 CDN 地址下的 pdf.worker.js 。可以通过传入 PDFJS.version 来提高引入的灵活性。

import * as PDFJS from 'pdfjs-dist'PDFJS.GlobalWorkerOptions.workerSrc = `https://cdnjs.cloudflare.com/ajax/libs/pdf.js/${PDFJS.version}/pdf.worker.js`

初始化插件

用于渲染内容的 canvas 节点

<canvas id="pdfCanvas"></canvas>

用于接收 PDFJS 实例的对象

props: {// PDF 文件的实际链接url: {type: String}
},
data () {return {totalPage: 1,// PDFJS 实例pdfDoc: null}
},
methods: {_initPdf () {PDFJS.getDocument(this.url).promise.then(pdf => {// 文档对象this.pdfDoc = pdf// 总页数this.totalPage = pdf.numPages// 渲染页面this.$nextTick(() => {this._renderPage()})})}
}

监听链接变化并初始化实例

当外部传入的 url 有效时,就可以触发 PDF 查看器的初始化函数

watch: {'url' (val) {if (!val) {return}this._initPdf()}
},

渲染 PDF 内容

获取当前页面比率,用于计算内容的实际宽高

methods: {_getRatio (ctx) {let dpr = window.devicePixelRatio || 1let bsr =ctx.webkitBackingStorePixelRatio ||ctx.mozBackingStorePixelRatio ||ctx.msBackingStorePixelRatio ||ctx.oBackingStorePixelRatio ||ctx.backingStorePixelRatio ||1return dpr / bsr}
}

渲染当前页面

page.getViewport({ scale }) 中的 scale 非常关键,直接关系到渲染出来的内容能不能撑满整个父容器,所以这里分别获取了父容器和页面本身的宽度,父容器宽度 / 页面宽度 后得出的比率就是实际页面需要放大多少的比率。

page.view 是一个数组,里面有四个值,分别是 x轴偏移量、y轴偏移量、宽度、高度。
要获取真实的宽度,还需要考虑当前页面比率,所以使用 page.view[2] * ratio 计算得出实际宽度。

data () {return {currentPage: 1,totalPage: 1,width: 0,height: 0,pdfDoc: null}
},
methods: {_renderPage () {this.pdfDoc.getPage(this.currentPage).then(page => {let canvas = document.querySelector('#pdfCanvas')let ctx = canvas.getContext('2d')// 获取页面比率let ratio = this._getRatio(ctx)// 根据页面宽度和视口宽度的比率就是内容区的放大比率let dialogWidth = this.$refs['pdfDialog'].$el.querySelector('.el-dialog').clientWidth - 40let pageWidth = page.view[2] * ratiolet scale = dialogWidth / pageWidthlet viewport = page.getViewport({ scale })// 记录内容区宽高,后期添加水印时需要this.width = viewport.width * ratiothis.height = viewport.height * ratiocanvas.width = this.widthcanvas.height = this.height// 缩放比率ctx.setTransform(ratio, 0, 0, ratio, 0, 0)page.render({canvasContext: ctx,viewport}).promise.then(() => {})})}
}

实现页面跳转

准备渲染队列,防止渲染顺序混乱

当触发页面跳转时,会调用 _renderQueue() 函数,而不是直接调用 _renderPage() 函数,因为是否开始渲染,要取决于当前是否没有正在被渲染的页面。

data () {return {// 是否位于队列中rendering: false}
},
methods: {_renderQueue () {if (this.rendering) {return}this._renderPage()}
}

在渲染页面时改变队列状态

methods: {_renderPage () {// 队列开始this.rendering = truethis.pdfDoc.getPage(this.currentPage).then(page => {// ... 省略实现代码page.render({canvasContext: ctx,viewport}).promise.then(() => {// 队列结束this.rendering = false})})}
}

实现翻页函数

data () {return {currentPage: 1,totalPage: 1}
},
computed: {// 是否首页firstPage () {return this.currentPage <= 1},// 是否尾页lastPage () {return this.currentPage >= this.totalPage},
},
methods: {// 跳转到首页firstPageHandler () {if (this.firstPage) {return}this.currentPage = 1this._renderQueue()},// 跳转到尾页lastPageHandler () {if (this.lastPage) {return}this.currentPage = this.totalPagethis._renderQueue()},// 上一页previousPage () {if (this.firstPage) {return}this.currentPage--this._renderQueue()},// 下一页nextPage () {if (this.lastPage) {return}this.currentPage++this._renderQueue()}
}

在页面内容中添加平铺的文字水印

前端添加水印的方式毋庸置疑都是使用 canvas 进行绘制。

最开始找到的方案是准备一个 div 作为透明的遮罩层挡在内容区的上层,然后将 canvas 绘制的水印使用 canvas.toDataURL('image/png') 导出成 Base64 格式,作为遮罩层的背景图片进行平铺。
虽然可以实现效果,但这种方式只要简单的打开浏览器控制台,删除这个遮罩层就可以去除水印。

之后在 Canvas 绘制另一个 Canvas 中找到 canvas 其实是可以将一个 canvas 作为图片绘制到自身上的,于是有了接下来的方案。

绘制作为水印的 canvas

因为是组件,所以水印的文字 watermark 由外部传入。

绘制水印的 canvas 不需要添加到页面中,绘制完成后直接将 DOM 元素返回即可,注意,返回的是 DOM 元素 ,而不是使用 getContext(2d) 获取的画布实例。

ctx.fillStyle 表示文字的透明度。
ctx.fillText(this.watermark, 50, 50) 表示文字在画布中的位置,第一个值是文字内容,第二个值是 x轴偏移量,第三个值是 y轴偏移量。

props: {watermark: {type: String,default: 'asing1elife'}
},
methods: {_initWatermark () {let canvas = document.createElement('canvas');canvas.width = 200canvas.height = 200let ctx = canvas.getContext('2d')ctx.rotate(-18 * Math.PI / 180)ctx.font = '14px Vedana'ctx.fillStyle = 'rgba(200, 200, 200, .3)'ctx.textAlign = 'left'ctx.textBaseline = 'middle'ctx.fillText(this.watermark, 50, 50)return canvas}
}

将水印平铺到渲染内容的 canvas 中

该方法参考自 HTML5 canvas 平铺的几种方法 ,ctx.rect(0, 0, this.width, this.height) 中的 widthheight 就是在 _renderPage() 函数中记录的页面内容区的实际宽高。只要将实际宽高传入,canvas 就会自动根据水印图片的大小和内容区的大小自动实现 x轴和 y轴的重复次数。

methods: {_renderWatermark () {let canvas = document.querySelector('#pdfCanvas')let ctx = canvas.getContext('2d')// 平铺水印let pattern = ctx.createPattern(this._initWatermark(), 'repeat')ctx.rect(0, 0, this.width, this.height)ctx.fillStyle = patternctx.fill()}
}

页面内容渲染完成后,再次触发水印渲染

methods: {// 渲染页面_renderPage () {this.pdfDoc.getPage(this.currentPage).then(page => {// ... 省略实现代码page.render({canvasContext: ctx,viewport}).promise.then(() => {// 渲染水印this._renderWatermark()})})}
}

Vue 集成 PDF.js 实现 PDF 预览和添加水印相关推荐

  1. springboot整合pdf.js实现在线预览pdf文件

    今天在项目中实现pdf在线预览的功能的如图, 通过百度,查询合适的方法,最为简单的的是通过pdf.js的插件在前台展示.本以为是挺容易实现的,但都有莫名其妙的错误. 第一步:下载源码https://g ...

  2. 文件被损坏 java_使用PDF.JS实现pdf文件在线预览时,报文件被损坏的错误

    首先大概说明一下问题出现的背景:我用PDF.JS实现文件在线预览,参考网上的办法,在jsp文件中使用 ?file=" width="1024" height=" ...

  3. 使用libreoffice将office文档(word、ppt、excel)转pdf,实现在线预览

    项目需要实现局域网预览office文档的功能,之前做的在线项目,都是将文档上传到cdn,利用cdn自带的转码功能,把文档转换为pdf,然后再用pdf.js实现在线预览. 因为是局域网,没有办法上传到c ...

  4. java集成pdf.js实现pdf文件在线预览

    最近项目需要实现在线预览pdf文件功能,找来找去,发现有个pdf.js的类库挺好用,直接用js实现在线预览. pdf.js是开源项目,github的地址: https://github.com/moz ...

  5. vue里使用pdf.js实现pdf文件的预览功能

    引言:经过上一篇文章的测试,发现了以下问题: 兼容性不好 不适用多语言场景(不能共用一套) 会出现跨域问题 难于监控阅读状态 不能控制下载状态 在基于vue开发的移动项目中,如果预览的pdf数量不是很 ...

  6. 前端vue实现pdf文件的在线预览

    3.前端vue实现pdf文件的在线预览 我是通过 <iframe> 标签就可以满足我工作的 pdf预览需求 如果<iframe> 无法满足需求 , 可以使用pdf.js这个插件 ...

  7. 【iframe结合pdf.js实现pdf的预览/下载及打印功能】

    iframe结合pdf.js实现pdf的预览/下载及打印 1.[下载pdf.js](http://mozilla.github.io/pdf.js/getting_started/#download) ...

  8. 文档在线预览(四)使用js前端实现word、excel、pdf、ppt 在线预览

    文章目录 实现方案 一.docx文件实现前端预览 1.docx-preview 2.Mammoth 二.PDF文件实现前端预览 1.pdf.js 2.pdfobject.js 3.vue-pdf 4. ...

  9. Vue3 + PDF.js 实现 PDF 预览

    文章目录 1 前言 2 PDF 预览测试 2.1 下载 PDF.js 2.2 window.open 直接打开 2.3 弹框形式打开 3 修改配置项 3.1 修改主题色为暗色系 3.2 修改默认语言为 ...

  10. 利用aspose实现ppt,doc,docx,pptx,xlsx,xls,txt,图片类型转pdf以及实现在线预览(可用于window和linux上,无水印)

    声明:对于本文章上的这个aspose相关的jar,切勿用于商业用途,后果我概不负责 1,先准备jar 链接:jar连接 提取码:bccn 下载后选中自己合适的 我使用的jar,而我的环境是jdk1.8 ...

最新文章

  1. python 对象拷贝
  2. 个人--变成星星的孩子
  3. 2021-06-12
  4. ubuntu中安装jdk
  5. /etc/sysconfig/network-script/ifcfg-eth0究竟怎么填!!!
  6. python变量定义大全_详解python变量与数据类型
  7. NetBeans 7.1:创建自定义提示
  8. java连接u盘_虚拟机VM6.0 LINUX redhat 挂接U盘,访问U盘
  9. camera(16)---双摄持续扩散 摄像头供应链阵营变动加剧
  10. 微信Mac版可以发朋友圈了 还能浏览相册
  11. Spring 整合 Disruptor 第一个版本
  12. RHEL 5下配置Hadoop集群:java.net.NoRouteToHostException: No route to host问题的解决
  13. nginx日志统计分析的相关常用命
  14. 设计模式-Builder Pattern
  15. Python实现Matlab绘制散点图
  16. c语言转化音乐格式转换器安卓版,MP3格式转换器APP
  17. 火狐浏览器常用插件的使用
  18. vs官网下载安装太慢的问题解决
  19. Google 创始人
  20. Keystore was tampered with or password was incorrect

热门文章

  1. 超像素分割研究进展+SLIC近几年进展
  2. ubuntu给手机刷机安卓8.0 ROOT+Xposed+JustTrustMe+Kali NetHunter
  3. python从入门到精通资源库_[百度网盘]PYTHON从入门到精通全套资料 - 磁力点点
  4. JavaScript --------WebS APIs学习之网页特效(offset系列)
  5. Java程序的方法设计
  6. 二维数组与指针(详解)
  7. 线上幽灵:世界头号黑客米特尼克自传(体验头号黑客传奇人生,洞悉头号黑客思维模式!启明,绿盟,安天,安全宝,百度,腾讯,阿里……众安全专家一致推荐!)...
  8. 重现Struts1的操纵classLoader漏洞
  9. 公司简介ppt模板如何利用曲线时间轴提升质感
  10. string函数使用---复制子字符串(含substr用法)