1. 程式人生 > 程式設計 >Vue 整合 PDF.js 實現 PDF 預覽和新增水印的步驟

Vue 整合 PDF.js 實現 PDF 預覽和新增水印的步驟

實現效果

Vue 整合 PDF.js 實現 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 來完成全部功能

安裝和引入外掛

安裝

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 || 1
 let bsr =
 ctx.webkitBackingStorePixelRatio ||
 ctx.mozBackingStorePixelRatio ||
 ctx.msBackingStorePixelRatio ||
 ctx.oBackingStorePixelRatio ||
 ctx.backingStorePixelRatio ||
 1

 return 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 - 40
 let pageWidth = page.view[2] * ratio
 let scale = dialogWidth / pageWidth

 let viewport = page.getViewport({ scale })

 // 記錄內容區寬高,後期新增水印時需要
 this.width = viewport.width * ratio
 this.height = viewport.height * ratio

 canvas.width = this.width
 canvas.height = this.height

 // 縮放比率
 ctx.setTransform(ratio,ratio,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 = true

 this.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 = 1
 this._renderQueue()
 },// 跳轉到尾頁
 lastPageHandler () {
 if (this.lastPage) {
 return
 }

 this.currentPage = this.totalPage
 this._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 = 200
 canvas.height = 200

 let ctx = canvas.getContext('2d')
 ctx.rotate(-18 * Math.PI / 180)
 ctx.font = '14px Vedana'
 ctx.fillStyle = 'rgba(200,200,.3)'
 ctx.textAlign = 'left'
 ctx.textBaseline = 'middle'
 ctx.fillText(this.watermark,50)

 return canvas
 }
}

將水印平鋪到渲染內容的 canvas 中

該方法參考自 HTML5 canvas 平鋪的幾種方法 ,ctx.rect(0,this.width,this.height) 中的 width 和 height 就是在 _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,this.height)
 ctx.fillStyle = pattern
 ctx.fill()
 }
}

頁面內容渲染完成後,再次觸發水印渲染

methods: {
 // 渲染頁面
 _renderPage () {
 this.pdfDoc.getPage(this.currentPage).then(page => {
 // ... 省略實現程式碼

 page.render({
 canvasContext: ctx,viewport
 }).promise.then(() => {
 // 渲染水印
 this._renderWatermark()
 })
 })
 }
}

以上就是Vue 整合 PDF.js 實現 PDF 預覽和新增水印的的詳細內容,更多關於vue 實現 PDF 預覽和新增水印的資料請關注我們其它相關文章!