首页IT科技vue-pdf(Vue使用pdf-lib为文件流添加水印并预览)

vue-pdf(Vue使用pdf-lib为文件流添加水印并预览)

时间2025-05-05 03:23:03分类IT科技浏览5291
导读:之前也写过两篇预览pdf的,但是没有加水印,这是链接:Vue使用vue-pdf实现PDF文件预览,使用pdfobject预览pdf。这次项目中又要预览pdf了,要求还要加水印,做的时候又发现了一种预览pdf的方式,这种方式我觉的更好一些,并且还有个要求就是添加水印,当然水印后端也是可以加的,但是后端说了一堆...反正就是要让前端...

之前也写过两篇预览pdf的             ,但是没有加水印                   ,这是链接:Vue使用vue-pdf实现PDF文件预览      ,使用pdfobject预览pdf             。这次项目中又要预览pdf了       ,要求还要加水印                   ,做的时候又发现了一种预览pdf的方式            ,这种方式我觉的更好一些       ,并且还有个要求就是添加水印                    ,当然水印后端也是可以加的            ,但是后端说了一堆...反正就是要让前端做,在我看来就是借口             、不想做                    ,最近也不忙                   ,那我就给他搞出来好了                   。下面来介绍一下 

首先预览pdf就很简单了,我们只需要通过window.URL.createObjectURL(new Blob(file))转为一个路径fileSrc后             ,再通过window.open(fileSrc)就可以了                   ,window.open方法第二个参数默认就是打开一个新页签      ,这样就可以直接预览了             ,很方便!就是下面这样子:

并且右上角自动给我们提供了下载                   、打印等功能      。 

但是要加上水印的话                   ,可能会稍微复杂一点点      ,我也百度找了好多       ,发现好多都是在项目里直接预览的                   ,也就是在当前页面或者一个div有个容器用来专门预览pdf的            ,然后水印的话也是appendChild到容器div中进行的       。这不是我想要的       ,并且也跟我现在预览的方式不一样                    ,所以我的思路就是如何给文件的那个二进制blob流上加上水印            ,这样预览的时候也是用这个文件流,以后不想预览了      、直接下载也要水印也是很方便的                   。找来找去找到了pdf-lib库                    ,然后就去https://www.npmjs.com/package/pdf-lib这里去看了下使用示例                   ,看了两个例子,发现好像这个很合适哦             ,终于一波操作拿下了                   ,这就是我想要的            。

我这里添加水印共三种方式      ,第一种就是可以直接传入文本             ,将文本添加进去作为水印 ;第二种是将图片的ArrayBuffer传递进去                   ,将图片作为水印;因为第一种方式直接传文本只能传英文      ,我传入汉字就报错了       ,npm官网好像也有写                   ,这是不可避免的            ,所以才有了第三种方式       ,就是也是传入文本                    ,不过我们通过canvas画出来            ,然后通过toDataURL转为base64路径,然后再通过XHR去加载该图片拿到图片的Blob                    ,再调用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那个也是用的这个这个通过图片添加水印 

上面这些都是官网上给的一些示例                   ,我当时看到上面这两个例子      ,灵感瞬间就来了             ,然后测试                   ,测试成功没问题      ,就开始整理代码       ,封装                    。结合自己的业务需求和可以复用通用的思想进行封装            。下面贴一下最终的成功

3.3 封装previewPdf.js

import { degrees, PDFDocument, rgb, StandardFonts } from pdf-lib; /** * 浏览器打开新页签预览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:图片类型                   。可选值: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), }) } } } // 序列化为字节 const pdfBytes = await pdfDoc.save() this.preView(pdfBytes, docTitle) } // 添加 image 水印 async addImageWatermark({ pdfDoc, bytes, imageType, docTitle }) { // 嵌入JPG图像字节和PNG图像字节 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) }) } } } // 序列化为字节 const pdfBytes = await pdfDoc.save() this.preView(pdfBytes, docTitle) } // 添加 canvas 水印 addCanvasWatermark({ pdfDoc, text, docTitle }) { // 旋转角度大小 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); // 在绕画布逆方向旋转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) { // 进行画布偏移是为了让画布旋转之后水印能够左对齐; 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) } // 序列化为字节 const pdfBytes = await pdfDoc.save() this.preView(pdfBytes, docTitle) }) } xhr.send(); } // 预览 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) // 设置新打开的页签 document title let timer = setInterval(() => { if(wo.closed) { clearInterval(timer) } else { wo.document.title = docTitle } }, 500) } }

3.4 调用使用 

我这里将上面文件放在src/utils下 

3.4.1  预览(添加文本水印)

代码: 

// 引入 import PreviewPdf from @/utils/previewPdf // script // 实例化进行添加水印 并预览 // file.raw 是要预览的pdf文件流 Blob new PreviewPdf({ blob: file.raw, docTitle: window.open docTitle, isAddWatermark: true, // 是否需要添加水印 watermark: { // watermark必填 里面可以不填 type: text, text: WFT } })

效果:

3.4.2 预览(添加图片水印) 

 代码:

// 引入 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 // 添加水印 预览 new PreviewPdf({ blob: file.raw, docTitle: file.name, isAddWatermark: true, watermark: { type: image, image: { bytes: buffer, imageType: png } } }) }) } xhr.send();

效果:

3.4.3 预览(添加文本canvas绘制水印) 

 代码:

// 引入 import PreviewPdf from @/utils/previewPdf // script new PreviewPdf({ blob: file.raw, docTitle: file.name, isAddWatermark: true, watermark: { type: canvas, text: WFT-CANVAS } })

效果: 

因为有些样式调的不太好,就我目前写的我更偏向使用canvas这个                    ,当然都是可以使用的                   ,样式都是可以调整的。 

注意:里面的属性 isAddWatermark 设置为false或者不传该字段将不添加水印,还有watermark这个字段是必须的             ,穿个空对象也行像watermark:{}这样                   ,因为我上面类中构造方法将参数结构了      ,可以按需调整             。

整体的封装使用就是上面这样子了             , 希望可以帮到有需要的伙伴~~~

再给大家一个直接往某个dom元素里面添加水印的方法 

不传参数默认为整个body添加水印 

function waterMark(text = WFT, dom = document.body) { if (document.getElementById(waterMark)) return // 旋转角度大小 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); // 在绕画布逆方向旋转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) { // 进行画布偏移是为了让画布旋转之后水印能够左对齐; 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); }

创心域SEO版权声明:以上内容作者已申请原创保护,未经允许不得转载,侵权必究!授权事宜、对本内容有异议或投诉,敬请联系网站管理员,我们将尽快回复您,谢谢合作!

展开全文READ MORE
更新SEO日常工作指南(提高网站排名,吸引更多访客)