之前也寫過兩篇預(yù)覽pdf的,但是沒有加水印,這是鏈接:Vue使用vue-pdf實現(xiàn)PDF文件預(yù)覽,使用pdfobject預(yù)覽pdf。這次項目中又要預(yù)覽pdf了,要求還要加水印,做的時候又發(fā)現(xiàn)了一種預(yù)覽pdf的方式,這種方式我覺的更好一些,并且還有個要求就是添加水印,當然水印后端也是可以加的,但是后端說了一堆...反正就是要讓前端做,在我看來就是借口、不想做,最近也不忙,那我就給他搞出來好了。下面來介紹一下?
首先預(yù)覽pdf就很簡單了,我們只需要通過window.URL.createObjectURL(new Blob(file))轉(zhuǎn)為一個路徑fileSrc后,再通過window.open(fileSrc)就可以了,window.open方法第二個參數(shù)默認就是打開一個新頁簽,這樣就可以直接預(yù)覽了,很方便!就是下面這樣子:
并且右上角自動給我們提供了下載、打印等功能。?
但是要加上水印的話,可能會稍微復(fù)雜一點點,我也百度找了好多,發(fā)現(xiàn)好多都是在項目里直接預(yù)覽的,也就是在當前頁面或者一個div有個容器用來專門預(yù)覽pdf的,然后水印的話也是appendChild到容器div中進行的。這不是我想要的,并且也跟我現(xiàn)在預(yù)覽的方式不一樣,所以我的思路就是如何給文件的那個二進制blob流上加上水印,這樣預(yù)覽的時候也是用這個文件流,以后不想預(yù)覽了、直接下載也要水印也是很方便的。找來找去找到了pdf-lib庫,然后就去https://www.npmjs.com/package/pdf-lib這里去看了下使用示例,看了兩個例子,發(fā)現(xiàn)好像這個很合適哦,終于一波操作拿下了,這就是我想要的。
我這里添加水印共三種方式,第一種就是可以直接傳入文本,將文本添加進去作為水印?;第二種是將圖片的ArrayBuffer傳遞進去,將圖片作為水印;因為第一種方式直接傳文本只能傳英文,我傳入漢字就報錯了,npm官網(wǎng)好像也有寫,這是不可避免的,所以才有了第三種方式,就是也是傳入文本,不過我們通過canvas畫出來,然后通過toDataURL轉(zhuǎn)為base64路徑,然后再通過XHR去加載該圖片拿到圖片的Blob,再調(diào)用Blob的arrayBuffer方法拿到buffer傳遞進去作為水印,其實第三種和第二種都是圖片的形式,第三種會更靈活一些。下面上代碼
1. 安裝?
npm i pdf-lib
2. 引入?
//我的需求里只用到這么多就夠了,其他的按需引入
import { degrees, PDFDocument, rgb, StandardFonts } from 'pdf-lib';
3. 添加水印使用?
?3.1 添加文本水印
import { degrees, PDFDocument, rgb, StandardFonts } from 'pdf-lib';
// This should be a Uint8Array or ArrayBuffer
// This data can be obtained in a number of different ways
// If your running in a Node environment, you could use fs.readFile()
// In the browser, you could make a fetch() call and use res.arrayBuffer()
const existingPdfBytes = ...
// Load a PDFDocument from the existing PDF bytes
const pdfDoc = await PDFDocument.load(existingPdfBytes)
// Embed the Helvetica font
const helveticaFont = await pdfDoc.embedFont(StandardFonts.Helvetica)
// Get the first page of the document
const pages = pdfDoc.getPages()
const firstPage = pages[0]
// Get the width and height of the first page
const { width, height } = firstPage.getSize()
// Draw a string of text diagonally across the first page
firstPage.drawText('This text was added with JavaScript!', {
x: 5,
y: height / 2 + 300,
size: 50,
font: helveticaFont,
color: rgb(0.95, 0.1, 0.1),
rotate: degrees(-45),
})
// Serialize the PDFDocument to bytes (a Uint8Array)
const pdfBytes = await pdfDoc.save()
// For example, `pdfBytes` can be:
// ? Written to a file in Node
// ? Downloaded from the browser
// ? Rendered in an <iframe>
3.2 添加圖片文本?
import { PDFDocument } from 'pdf-lib'
// These should be Uint8Arrays or ArrayBuffers
// This data can be obtained in a number of different ways
// If your running in a Node environment, you could use fs.readFile()
// In the browser, you could make a fetch() call and use res.arrayBuffer()
const jpgImageBytes = ...
const pngImageBytes = ...
// Create a new PDFDocument
const pdfDoc = await PDFDocument.create()
// Embed the JPG image bytes and PNG image bytes
const jpgImage = await pdfDoc.embedJpg(jpgImageBytes)
const pngImage = await pdfDoc.embedPng(pngImageBytes)
// Get the width/height of the JPG image scaled down to 25% of its original size
const jpgDims = jpgImage.scale(0.25)
// Get the width/height of the PNG image scaled down to 50% of its original size
const pngDims = pngImage.scale(0.5)
// Add a blank page to the document
const page = pdfDoc.addPage()
// Draw the JPG image in the center of the page
page.drawImage(jpgImage, {
x: page.getWidth() / 2 - jpgDims.width / 2,
y: page.getHeight() / 2 - jpgDims.height / 2,
width: jpgDims.width,
height: jpgDims.height,
})
// Draw the PNG image near the lower right corner of the JPG image
page.drawImage(pngImage, {
x: page.getWidth() / 2 - pngDims.width / 2 + 75,
y: page.getHeight() / 2 - pngDims.height,
width: pngDims.width,
height: pngDims.height,
})
// Serialize the PDFDocument to bytes (a Uint8Array)
const pdfBytes = await pdfDoc.save()
// For example, `pdfBytes` can be:
// ? Written to a file in Node
// ? Downloaded from the browser
// ? Rendered in an <iframe>
canvas那個也是用的這個這個通過圖片添加水印?
上面這些都是官網(wǎng)上給的一些示例,我當時看到上面這兩個例子,靈感瞬間就來了,然后測試,測試成功沒問題,就開始整理代碼,封裝。結(jié)合自己的業(yè)務(wù)需求和可以復(fù)用通用的思想進行封裝。下面貼一下最終的成功
3.3 封裝previewPdf.js
import { degrees, PDFDocument, rgb, StandardFonts } from 'pdf-lib';
/**
* 瀏覽器打開新頁簽預(yù)覽pdf
* blob(必選): pdf文件信息(Blob對象)【Blob】
* docTitle(可選): 瀏覽器打開新頁簽的title 【String】
* isAddWatermark(可選,默認為false): 是否需要添加水印 【Boolean】
* watermark(必選):水印信息 【Object: { type: string, text: string, image:{ bytes: ArrayBuffer, imageType: string } }】
* watermark.type(可選):類型 可選值:text、image、canvas
* watermark.text(watermark.type為image時不填,否則必填):水印文本。注意:如果watermark.type值為text,text取值僅支持拉丁字母中的218個字符。詳見:https://www.npmjs.com/package/pdf-lib
* watermark.image(watermark.type為image時必填,否則不填):水印圖片
* watermark.image.bytes:圖片ArrayBuffer
* watermark.image.imageType:圖片類型??蛇x值:png、jpg
* Edit By WFT
*/
export default class PreviewPdf {
constructor({ blob, docTitle, isAddWatermark = false, watermark: { type = 'text', text = 'WFT', image } }) {
const _self = this
if(!blob) {
return console.error('[PDF Blob Is a required parameter]')
}
if(!isAddWatermark) { // 不添加水印
_self.preView(blob, docTitle)
} else {
let bytes,imageType
if(type == 'image') {
if(!image) {
return console.error('["image" Is a required parameter]')
}
bytes = image.bytes
imageType = image.imageType
}
const map = {
'text': _self.addTextWatermark.bind(_self),
'image': _self.addImageWatermark.bind(_self),
'canvas': _self.addCanvasWatermark.bind(_self)
}
blob.arrayBuffer().then(async buffer => {
const existingPdfBytes = buffer
const pdfDoc = await PDFDocument.load(existingPdfBytes)
let params
if(type == 'text') params = { pdfDoc, text, docTitle }
if(type == 'image') params = { pdfDoc, bytes, imageType, docTitle }
if(type == 'canvas') params = { pdfDoc, text, docTitle }
map[type](params)
}).catch(e => console.error('[Preview Pdf Error]:', e))
}
}
// 添加 Text 水印
async addTextWatermark({ pdfDoc, text, docTitle }) {
// console.log(StandardFonts, 'StandardFonts-->>') // 字體
const helveticaFont = await pdfDoc.embedFont(StandardFonts.Helvetica)
const pages = pdfDoc.getPages()
for(let i = 0; i < pages.length; i++) {
let page = pages[i]
let { width, height } = page.getSize()
for(let i = 0; i < 6; i++) {
for(let j = 0; j < 6; j++) {
page.drawText(text, {
x: j * 100,
y: height / 5 + i * 100,
size: 30,
font: helveticaFont,
color: rgb(0.95, 0.1, 0.1),
opacity: 0.2,
rotate: degrees(-35),
})
}
}
}
// 序列化為字節(jié)
const pdfBytes = await pdfDoc.save()
this.preView(pdfBytes, docTitle)
}
// 添加 image 水印
async addImageWatermark({ pdfDoc, bytes, imageType, docTitle }) {
// 嵌入JPG圖像字節(jié)和PNG圖像字節(jié)
let image
const maps = {
'jpg': pdfDoc.embedJpg.bind(pdfDoc),
'png': pdfDoc.embedPng.bind(pdfDoc)
}
image = await maps[imageType](bytes)
// 將JPG圖像的寬度/高度縮小到原始大小的50%
const dims = image.scale(0.5)
const pages = pdfDoc.getPages()
for(let i = 0; i < pages.length; i++) {
let page = pages[i]
let { width, height } = page.getSize()
for(let i = 0; i < 6; i++) {
for(let j = 0; j < 6; j++) {
page.drawImage(image, {
x: width / 5 - dims.width / 2 + j * 100,
y: height / 5 - dims.height / 2 + i * 100,
width: dims.width,
height: dims.height,
rotate: degrees(-35)
})
}
}
}
// 序列化為字節(jié)
const pdfBytes = await pdfDoc.save()
this.preView(pdfBytes, docTitle)
}
// 添加 canvas 水印
addCanvasWatermark({ pdfDoc, text, docTitle }) {
// 旋轉(zhuǎn)角度大小
const rotateAngle = Math.PI / 6;
// labels是要顯示的水印文字,垂直排列
let labels = new Array();
labels.push(text);
const pages = pdfDoc.getPages()
const size = pages[0].getSize()
let pageWidth = size.width
let pageHeight = size.height
let canvas = document.createElement('canvas');
let canvasWidth = canvas.width = pageWidth;
let canvasHeight = canvas.height = pageHeight;
const context = canvas.getContext('2d');
context.font = "15px Arial";
// 先平移到畫布中心
context.translate(pageWidth / 2, pageHeight / 2 - 250);
// 在繞畫布逆方向旋轉(zhuǎn)30度
context.rotate(-rotateAngle);
// 在還原畫布的坐標中心
context.translate(-pageWidth / 2, -pageHeight / 2);
// 獲取文本的最大長度
let textWidth = Math.max(...labels.map(item => context.measureText(item).width));
let lineHeight = 15, fontHeight = 12, positionY, i
i = 0, positionY = 0
while (positionY <= pageHeight) {
positionY = positionY + lineHeight * 5
i++
}
canvasWidth += Math.sin(rotateAngle) * (positionY + i * fontHeight) // 給canvas加上畫布向左偏移的最大距離
canvasHeight = 2 * canvasHeight
for (positionY = 0, i = 0; positionY <= canvasHeight; positionY = positionY + lineHeight * 5) {
// 進行畫布偏移是為了讓畫布旋轉(zhuǎn)之后水印能夠左對齊;
context.translate(-(Math.sin(rotateAngle) * (positionY + i * fontHeight)), 0);
for (let positionX = 0; positionX < canvasWidth; positionX += 2 * textWidth) {
let spacing = 0;
labels.forEach(item => {
context.fillText(item, positionX, positionY + spacing);
context.fillStyle = 'rgba(187, 187, 187, .8)'; // 字體顏色
spacing = spacing + lineHeight;
})
}
context.translate(Math.sin(rotateAngle) * (positionY + i * fontHeight), 0);
context.restore();
i++
}
// 圖片的base64編碼路徑
let dataUrl = canvas.toDataURL('image/png');
// 使用Xhr請求獲取圖片Blob
let xhr = new XMLHttpRequest();
xhr.open("get", dataUrl, true);
xhr.responseType = "blob";
xhr.onload = res => {
const imgBlob = res.target.response
// 獲取Blob圖片Buffer
imgBlob.arrayBuffer().then(async buffer => {
const pngImage = await pdfDoc.embedPng(buffer)
for(let i = 0; i < pages.length; i++) {
pages[i].drawImage(pngImage)
}
// 序列化為字節(jié)
const pdfBytes = await pdfDoc.save()
this.preView(pdfBytes, docTitle)
})
}
xhr.send();
}
// 預(yù)覽
preView(stream, docTitle) {
const URL = window.URL || window.webkitURL;
const href = URL.createObjectURL(new Blob([stream], { type: 'application/pdf;charset=utf-8' }))
const wo = window.open(href)
// 設(shè)置新打開的頁簽 document title
let timer = setInterval(() => {
if(wo.closed) {
clearInterval(timer)
} else {
wo.document.title = docTitle
}
}, 500)
}
}
3.4 調(diào)用使用?
我這里將上面文件放在src/utils下?
3.4.1? 預(yù)覽(添加文本水?。?/h4>
代碼:?
// 引入
import PreviewPdf from '@/utils/previewPdf'
// script
// 實例化進行添加水印 并預(yù)覽
// file.raw 是要預(yù)覽的pdf文件流 Blob
new PreviewPdf({
blob: file.raw,
docTitle: 'window.open docTitle',
isAddWatermark: true, // 是否需要添加水印
watermark: { // watermark必填 里面可以不填
type: 'text',
text: 'WFT'
}
})
效果:
3.4.2 預(yù)覽(添加圖片水?。?
?代碼:
// 引入
import PreviewPdf from '@/utils/previewPdf'
// script
const watermarkImage = require('@/assets/img/watermark.png') // 水印圖片
let xhr = new XMLHttpRequest();
xhr.open("get", watermarkImage, true);
xhr.responseType = "blob";
xhr.onload = function (res) {
const imgBlob = res.target.response // 水印圖片的Blob流
imgBlob.arrayBuffer().then(buffer => { //get arraybuffer
// 添加水印 預(yù)覽
new PreviewPdf({
blob: file.raw,
docTitle: file.name,
isAddWatermark: true,
watermark: {
type: 'image',
image: {
bytes: buffer,
imageType: 'png'
}
}
})
})
}
xhr.send();
效果:
3.4.3 預(yù)覽(添加文本canvas繪制水印)?
?代碼:
// 引入
import PreviewPdf from '@/utils/previewPdf'
// script
new PreviewPdf({
blob: file.raw,
docTitle: file.name,
isAddWatermark: true,
watermark: {
type: 'canvas',
text: 'WFT-CANVAS'
}
})
效果:?
因為有些樣式調(diào)的不太好,就我目前寫的我更偏向使用canvas這個,當然都是可以使用的,樣式都是可以調(diào)整的。?
注意:里面的屬性?isAddWatermark 設(shè)置為false或者不傳該字段將不添加水印,還有watermark這個字段是必須的,穿個空對象也行像watermark:{}這樣,因為我上面類中構(gòu)造方法將參數(shù)結(jié)構(gòu)了,可以按需調(diào)整。
整體的封裝使用就是上面這樣子了,?希望可以幫到有需要的伙伴~~~
再給大家一個直接往某個dom元素里面添加水印的方法?文章來源:http://www.zghlxwxcb.cn/news/detail-434104.html
不傳參數(shù)默認為整個body添加水印?文章來源地址http://www.zghlxwxcb.cn/news/detail-434104.html
function waterMark(text = 'WFT', dom = document.body) {
if (document.getElementById('waterMark')) return
// 旋轉(zhuǎn)角度大小
var rotateAngle = Math.PI / 6;
// labels是要顯示的水印文字,垂直排列
var labels = new Array();
labels.push(text);
let pageWidth = dom.clientWidth
let pageHeight = dom.clientHeight
let canvas = document.createElement('canvas');
let canvasWidth = canvas.width = pageWidth;
let canvasHeight = canvas.height = pageHeight;
var context = canvas.getContext('2d');
context.font = "15px Arial";
// 先平移到畫布中心
context.translate(pageWidth / 2, pageHeight / 2 - 250);
// 在繞畫布逆方向旋轉(zhuǎn)30度
context.rotate(-rotateAngle);
// 在還原畫布的坐標中心
context.translate(-pageWidth / 2, -pageHeight / 2);
// 獲取文本的最大長度
let textWidth = Math.max(...labels.map(item => context.measureText(item).width));
let lineHeight = 15, fontHeight = 12, positionY, i
i = 0, positionY = 0
while (positionY <= pageHeight) {
positionY = positionY + lineHeight * 5
i++
}
canvasWidth += Math.sin(rotateAngle) * (positionY + i * fontHeight) // 給canvas加上畫布向左偏移的最大距離
canvasHeight = 2 * canvasHeight
for (positionY = 0, i = 0; positionY <= canvasHeight; positionY = positionY + lineHeight * 5) {
// 進行畫布偏移是為了讓畫布旋轉(zhuǎn)之后水印能夠左對齊;
context.translate(-(Math.sin(rotateAngle) * (positionY + i * fontHeight)), 0);
for (let positionX = 0; positionX < canvasWidth; positionX += 2 * textWidth) {
let spacing = 0;
labels.forEach(item => {
context.fillText(item, positionX, positionY + spacing);
spacing = spacing + lineHeight;
})
}
context.translate(Math.sin(rotateAngle) * (positionY + i * fontHeight), 0);
context.restore();
i++
}
let dataUrl = canvas.toDataURL('image/png');
let waterMarkPage = document.createElement('div');
waterMarkPage.id = "waterMark"
let style = waterMarkPage.style;
style.position = 'fixed';
style.overflow = "hidden";
style.left = 0;
style.top = 0;
style.opacity = '0.4';
style.background = "url(" + dataUrl + ")";
style.zIndex = 999;
style.pointerEvents = "none";
style.width = '100%';
style.height = '100vh';
dom.appendChild(waterMarkPage);
}
到了這里,關(guān)于Vue使用pdf-lib為文件流添加水印并預(yù)覽的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!