• 售前

  • 售后

热门帖子
入门百科

Vue 集成 PDF.js 实现 PDF 预览和添加水印的步调

[复制链接]
天亮再走己 显示全部楼层 发表于 2021-10-25 19:30:50 |阅读模式 打印 上一主题 下一主题
目次


  • 实现效果
  • 可用插件先容
  • 根据需求举行插件选型
  • 安装和引入插件

    • 安装
    • 引入

  • 初始化插件

    • 用于渲染内容的 canvas 节点
    • 用于接收 PDFJS 实例的对象
    • 监听链接变化并初始化实例

  • 渲染 PDF 内容

    • 获取当前页面比率,用于盘算内容的现实宽高
    • 渲染当前页面

  • 实现页面跳转

    • 预备渲染队列,防止渲染次序混乱
    • 在渲染页面时改变队列状态
    • 实现翻页函数

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

    • 绘制作为水印的 canvas
    • 将水印平铺到渲染内容的 canvas 中
    • 页面内容渲染完成后,再次触发水印渲染


实现效果



可用插件先容


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 来完玉成部功能

安装和引入插件



安装
  1. yarn add pdfjs-dist
复制代码
引入


必须手动指定 workerSrc ,否则会抛出 Setting up fake worker failed 的错误。
虽然当地目次 node_modules/pdfjs-dist/build/pdf.worker.js 存在该文件,但现实引入时仍然会报错,以是只能利用 CDN 地址下的 pdf.worker.js 。可以通过传入 PDFJS.version 来提高引入的机动性。
  1. import * as PDFJS from 'pdfjs-dist'
  2. PDFJS.GlobalWorkerOptions.workerSrc = `https://cdnjs.cloudflare.com/ajax/libs/pdf.js/${PDFJS.version}/pdf.worker.js`
复制代码
初始化插件



用于渲染内容的 canvas 节点
  1. <canvas id="pdfCanvas"></canvas>
复制代码
用于接收 PDFJS 实例的对象
  1. props: {
  2. // PDF 文件的实际链接
  3. url: {
  4. type: String
  5. }
  6. },
  7. data () {
  8. return {
  9. totalPage: 1,
  10. // PDFJS 实例
  11. pdfDoc: null
  12. }
  13. },
  14. methods: {
  15. _initPdf () {
  16. PDFJS.getDocument(this.url).promise.then(pdf => {
  17. // 文档对象
  18. this.pdfDoc = pdf
  19. // 总页数
  20. this.totalPage = pdf.numPages
  21. // 渲染页面
  22. this.$nextTick(() => {
  23. this._renderPage()
  24. })
  25. })
  26. }
  27. }
复制代码
监听链接变化并初始化实例


当外部传入的 url 有效时,就可以触发 PDF 检察器的初始化函数
  1. watch: {
  2. 'url' (val) {
  3. if (!val) {
  4. return
  5. }
  6. this._initPdf()
  7. }
  8. },
复制代码
渲染 PDF 内容



获取当前页面比率,用于盘算内容的现实宽高
  1. methods: {
  2. _getRatio (ctx) {
  3. let dpr = window.devicePixelRatio || 1
  4. let bsr =
  5. ctx.webkitBackingStorePixelRatio ||
  6. ctx.mozBackingStorePixelRatio ||
  7. ctx.msBackingStorePixelRatio ||
  8. ctx.oBackingStorePixelRatio ||
  9. ctx.backingStorePixelRatio ||
  10. 1
  11. return dpr / bsr
  12. }
  13. }
复制代码
渲染当前页面


page.getViewport({ scale }) 中的 scale 非常关键,直接关系到渲染出来的内容能不能撑满整个父容器,以是这里分别获取了父容器和页面本身的宽度,父容器宽度 / 页面宽度 后得出的比率就是现实页面必要放大多少的比率。
page.view 是一个数组,内里有四个值,分别是 x轴偏移量、y轴偏移量、宽度、高度。 要获取真实的宽度,还必要思量当前页面比率,以是利用 page.view[2] * ratio 盘算得出现实宽度。
  1. data () {
  2. return {
  3. currentPage: 1,
  4. totalPage: 1,
  5. width: 0,
  6. height: 0,
  7. pdfDoc: null
  8. }
  9. },
  10. methods: {
  11. _renderPage () {
  12. this.pdfDoc.getPage(this.currentPage).then(page => {
  13. let canvas = document.querySelector('#pdfCanvas')
  14. let ctx = canvas.getContext('2d')
  15. // 获取页面比率
  16. let ratio = this._getRatio(ctx)
  17. // 根据页面宽度和视口宽度的比率就是内容区的放大比率
  18. let dialogWidth = this.$refs['pdfDialog'].$el.querySelector('.el-dialog').clientWidth - 40
  19. let pageWidth = page.view[2] * ratio
  20. let scale = dialogWidth / pageWidth
  21. let viewport = page.getViewport({ scale })
  22. // 记录内容区宽高,后期添加水印时需要
  23. this.width = viewport.width * ratio
  24. this.height = viewport.height * ratio
  25. canvas.width = this.width
  26. canvas.height = this.height
  27. // 缩放比率
  28. ctx.setTransform(ratio, 0, 0, ratio, 0, 0)
  29. page.render({
  30. canvasContext: ctx,
  31. viewport
  32. }).promise.then(() => {})
  33. })
  34. }
  35. }
复制代码
实现页面跳转



预备渲染队列,防止渲染次序混乱


当触发页面跳转时,会调用 _renderQueue() 函数,而不是直接调用 _renderPage() 函数,因为是否开始渲染,要取决于当前是否没有正在被渲染的页面。
  1. data () {
  2. return {
  3. // 是否位于队列中
  4. rendering: false
  5. }
  6. },
  7. methods: {
  8. _renderQueue () {
  9. if (this.rendering) {
  10. return
  11. }
  12. this._renderPage()
  13. }
  14. }
复制代码
在渲染页面时改变队列状态
  1. methods: {
  2. _renderPage () {
  3. // 队列开始
  4. this.rendering = true
  5. this.pdfDoc.getPage(this.currentPage).then(page => {
  6. // ... 省略实现代码
  7. page.render({
  8. canvasContext: ctx,
  9. viewport
  10. }).promise.then(() => {
  11. // 队列结束
  12. this.rendering = false
  13. })
  14. })
  15. }
  16. }
复制代码
实现翻页函数
  1. data () {
  2. return {
  3. currentPage: 1,
  4. totalPage: 1
  5. }
  6. },
  7. computed: {
  8. // 是否首页
  9. firstPage () {
  10. return this.currentPage <= 1
  11. },
  12. // 是否尾页
  13. lastPage () {
  14. return this.currentPage >= this.totalPage
  15. },
  16. },
  17. methods: {
  18. // 跳转到首页
  19. firstPageHandler () {
  20. if (this.firstPage) {
  21. return
  22. }
  23. this.currentPage = 1
  24. this._renderQueue()
  25. },
  26. // 跳转到尾页
  27. lastPageHandler () {
  28. if (this.lastPage) {
  29. return
  30. }
  31. this.currentPage = this.totalPage
  32. this._renderQueue()
  33. },
  34. // 上一页
  35. previousPage () {
  36. if (this.firstPage) {
  37. return
  38. }
  39. this.currentPage--
  40. this._renderQueue()
  41. },
  42. // 下一页
  43. nextPage () {
  44. if (this.lastPage) {
  45. return
  46. }
  47. this.currentPage++
  48. this._renderQueue()
  49. }
  50. }
复制代码
在页面内容中添加平铺的文字水印


前端添加水印的方式毋庸置疑都是利用 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轴偏移量。
  1. props: {
  2. watermark: {
  3. type: String,
  4. default: 'asing1elife'
  5. }
  6. },
  7. methods: {
  8. _initWatermark () {
  9. let canvas = document.createElement('canvas');
  10. canvas.width = 200
  11. canvas.height = 200
  12. let ctx = canvas.getContext('2d')
  13. ctx.rotate(-18 * Math.PI / 180)
  14. ctx.font = '14px Vedana'
  15. ctx.fillStyle = 'rgba(200, 200, 200, .3)'
  16. ctx.textAlign = 'left'
  17. ctx.textBaseline = 'middle'
  18. ctx.fillText(this.watermark, 50, 50)
  19. return canvas
  20. }
  21. }
复制代码
将水印平铺到渲染内容的 canvas 中


该方法参考自 HTML5 canvas 平铺的几种方法 ,ctx.rect(0, 0, this.width, this.height) 中的 width 和 height 就是在 _renderPage() 函数中记载的页面内容区的现实宽高。只要将现实宽高传入,canvas 就会主动根据水印图片的大小和内容区的大小主动实现 x轴和 y轴的重复次数。
  1. methods: {
  2. _renderWatermark () {
  3. let canvas = document.querySelector('#pdfCanvas')
  4. let ctx = canvas.getContext('2d')
  5. // 平铺水印
  6. let pattern = ctx.createPattern(this._initWatermark(), 'repeat')
  7. ctx.rect(0, 0, this.width, this.height)
  8. ctx.fillStyle = pattern
  9. ctx.fill()
  10. }
  11. }
复制代码
页面内容渲染完成后,再次触发水印渲染
  1. methods: {
  2. // 渲染页面
  3. _renderPage () {
  4. this.pdfDoc.getPage(this.currentPage).then(page => {
  5. // ... 省略实现代码
  6. page.render({
  7. canvasContext: ctx,
  8. viewport
  9. }).promise.then(() => {
  10. // 渲染水印
  11. this._renderWatermark()
  12. })
  13. })
  14. }
  15. }
复制代码
以上就是Vue 集成 PDF.js 实现 PDF 预览和添加水印的的详细内容,更多关于vue 实现 PDF 预览和添加水印的资料请关注草根技术分享别的相关文章!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x

帖子地址: 

回复

使用道具 举报

分享
推广
火星云矿 | 预约S19Pro,享500抵1000!
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

草根技术分享(草根吧)是全球知名中文IT技术交流平台,创建于2021年,包含原创博客、精品问答、职业培训、技术社区、资源下载等产品服务,提供原创、优质、完整内容的专业IT技术开发社区。
  • 官方手机版

  • 微信公众号

  • 商务合作