import { degrees,PageSizes, PDFDocument, rgb, StandardFonts } from 'pdf-lib'; // import JsBarcode from 'jsbarcode' import axios from "axios"; import Buffer from 'vue-buffer' // import * as pdfjs from 'pdfjs-dist'; /** * 浏览器打开新页签预览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 PdfUtils { constructor() { const _self = this } async fillPdfForm(templatePath, outputPath, data) { const templateBytes = await fs.promises.readFile(templatePath); const fontBytes = await fs.promises.readFile('xxx.ttf'); pdfDoc = await PDFDocument.load(templateBytes); pdfDoc.registerFontkit(fontkit); font = await pdfDoc.embedFont(fontBytes); const form = pdfDoc.getForm(); for (const fieldKey in data) { const field = form.getTextField(fieldKey); if (field) { field.setText(data[fieldKey]); field.updateAppearances(font) } } const modifiedPdfBytes = await pdfDoc.save(); await fs.promises.writeFile(outputPath, modifiedPdfBytes); } async getPdfByTpl(tplStr, params) { const tplBase64Str = 'data:application/pdf;base64,' + tplStr pdfDoc = await PDFDocument.load(tplBase64Str); pdfDoc.registerFontkit(fontkit); font = await pdfDoc.embedFont(fontBytes); const form = pdfDoc.getForm(); for (const field in params) { const field = form.getTextField(field); if (field) { field.setText(data[field]); field.updateAppearances(font) } } const uint8Array = await newPdf.save(); let mergeBuffer = Buffer.from(uint8Array); var blob=new Blob([mergeBuffer]); } // 添加 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) } // 合并文件 async mergePdf2(urlList, fileName,isNeedDown=false) { let startTime = +new Date() // console.log(startTime) // eslint-disable-next-line let promises = []; urlList.map((url) => { // eslint-disable-next-line no-async-promise-executor let tmp = new Promise(async (resolve) => { const response = await axios({ method: "get", url, responseType: "arraybuffer", }); resolve(Buffer.from(response.data)); }); promises.push(tmp); }); const pdfBuffers = await Promise.all(promises); const newPdf = await PDFDocument.create(); for (let buffer of pdfBuffers) { const pdfDocument = await PDFDocument.load(buffer); const contentPages = await newPdf.copyPages( pdfDocument, pdfDocument.getPageIndices() ); for (let page of contentPages) { newPdf.addPage(page); } } const uint8Array = await newPdf.save(); let mergeBuffer = Buffer.from(uint8Array); var blob=new Blob([mergeBuffer]); if(isNeedDown){ const url = window.URL.createObjectURL(blob); const link = document.createElement("a"); link.href = url; link.setAttribute("download", fileName); if (typeof link.download === 'undefined') { link.setAttribute('target', '_blank') } // document.body.appendChild(link); // link.click(); document.body.appendChild(link) link.click() document.body.removeChild(link) }else{ const mergedPdfBlob = new Blob([mergeBuffer], { type: "application/pdf", }); return URL.createObjectURL(mergedPdfBlob);; } // console.log(+new Date() - startTime) } // getPdfnum(url) { // return new Promise((resolve, reject) => { // const imgFiles = []; // //文件读取成功完成时触发 // const loadingTask = pdfjs.getDocument(url); // loadingTask.promise.then(async (pdf) => { // const pageNum = pdf.numPages; // if (pageNum > 10) { // reject('此pdf页数过多,请线下合并'); // } // //准备图片 // for (let i = 1; i <= pageNum; i++) { // imgFiles.push(i); // } // resolve(imgFiles); // }); // }); // } /** * pdf保存图片 * @param {Array} newimgList - canvas列表 * @param {Number} x - 页面展示宽 * @param {Number} y - 页面展示高 * @param {String}fileName - 文件名称 */ savePdfImage(newimgList, x, y, fileName) { return new Promise((resolve, reject) => { // 创建canvas var canvas = document.createElement("canvas"); canvas.width = 100; canvas.height = 100; canvas.id='d_canvas0'; document.body.appendChild(canvas) let allcanvas = document.getElementById('d_canvas0'); let allctx = allcanvas.getContext('2d'); allcanvas.style.width = x; allcanvas.style.height = y * newimgList.length; const subCanvasWidth = newimgList[0].width; const subCanvasHeight = newimgList[0].height; allcanvas.width = subCanvasWidth; allcanvas.height = subCanvasHeight * newimgList.length; newimgList.forEach((subCanvas, index) => { // 计算当前图片在Canvas中的垂直位置 const yPosition = index * subCanvasHeight; // 绘制图片 // 使用drawImage的四个参数版本来指定绘制的源图像区域和目标区域 allctx.drawImage(subCanvas, 0, yPosition, subCanvasWidth, subCanvasHeight); if (index === newimgList.length - 1) { let pngData = allcanvas.toDataURL('image/png'); let obj = { url: pngData, name: fileName, imgWidth:subCanvasWidth, imgHeight:subCanvasHeight }; document.body.removeChild(canvas) resolve(obj); } }); }); } pdfToImg(pdfUrl) { return new Promise((resolve, reject) => { const newimgList = []; const fileName = new Date().getTime()+""; const loadingTask = pdfjs.getDocument(pdfUrl); loadingTask.promise.then(async (pdf) => { let pageNum = pdf.numPages; console.log(pageNum); // 处理 for (let i = 1; i <= pageNum; i++) { // 创建canvas var canvas = document.createElement("canvas"); canvas.width = 100; canvas.height = 100; canvas.id='vmpdf_canvas_' + i; document.body.appendChild(canvas) let canvasItem = ''; pdf.getPage(i).then(async (page) => { const canvas = document.getElementById('vmpdf_canvas_' + i); const ctx = canvas.getContext('2d'); const viewport = page.getViewport({ scale: 4 }); canvas.height = viewport.height; canvas.width = viewport.width; const destWidth = 298; const destheight = destWidth * (viewport.height / viewport.width); canvas.style.width = destWidth + 'px'; canvas.style.height = destWidth * (viewport.height / viewport.width) + 'px'; newimgList.push(canvas); await page.render({ canvasContext: ctx, viewport }); // 使用file对象进行后续操作 if (i === pageNum) { document.body.removeChild(canvas) setTimeout(async () => { const res = await this.savePdfImage(newimgList, destWidth, destheight, fileName); resolve(res); }, 500); } }); } }); }); } getBlobUrl(base64Str, fileName,isNeedDown=false) { let blob = this.base64ToBlobNoHeader(base64Str); // new Blob([base64Str],{type: 'application/pdf'}); let pdfurl = window.URL.createObjectURL(blob); if(isNeedDown){ const link = document.createElement("a"); link.href = pdfurl; link.setAttribute("download", fileName); if (typeof link.download === 'undefined') { link.setAttribute('target', '_blank') } document.body.appendChild(link) link.click() document.body.removeChild(link) }else{ return pdfurl; } } base64ToBlobNoHeader(code) { //Base64一行不能超过76字符,超过则添加回车换行符。因此需要把base64字段中的换行符,回车符给去掉,有时候因为存在需要把加号空格之类的换回来,取决于base64存取时的规则。 code = code.replace(/[\n\r]/g, ''); var raw = window.atob(code); let rawLength = raw.length; //转换成pdf.js能直接解析的Uint8Array类型 let uInt8Array = new Uint8Array(rawLength); for (let i = 0; i < rawLength; ++i) { uInt8Array[i] = raw.charCodeAt(i); } return new Blob([uInt8Array], {type: 'application/pdf'});//转成pdf类型 } async mergePdf3(pdfFileList, fileName,isNeedDown=false) { let pdfBuffers = []; pdfFileList.map((item) => { let dateArray=this.base64ToBuffer(item); pdfBuffers.push(Buffer.from(dateArray)); }); const newPdf = await PDFDocument.create(); for (let buffer of pdfBuffers) { const pdfDocument = await PDFDocument.load(buffer); const contentPages = await newPdf.copyPages( pdfDocument, pdfDocument.getPageIndices() ); for (let page of contentPages) { newPdf.addPage(page); } } const uint8Array = await newPdf.save(); let mergeBuffer = Buffer.from(uint8Array); var blob=new Blob([mergeBuffer]); if(isNeedDown){ const url = window.URL.createObjectURL(blob); const link = document.createElement("a"); link.href = url; link.setAttribute("download", fileName); if (typeof link.download === 'undefined') { link.setAttribute('target', '_blank') } document.body.appendChild(link) link.click() document.body.removeChild(link) }else{ const mergedPdfBlob = new Blob([mergeBuffer], { type: "application/pdf", }); return URL.createObjectURL(mergedPdfBlob); } } async mergePdfToA4(pdfFileList, fileName,isNeedDown=false) { let pdfBuffers = []; pdfFileList.map((item) => { let dateArray=this.base64ToBuffer(item); pdfBuffers.push(Buffer.from(dateArray)); }); const pdfDoc = await PDFDocument.create(); const pageA4 = pdfDoc.addPage(PageSizes.A4); const drawHeight = pageA4.getHeight()/2; const drawWidth = pageA4.getWidth()/2; let i=0; let x=0; let y=0; let width=0; let height=0; let spaceWidth=2; for (let buffer of pdfBuffers) { const [page] = await pdfDoc.embedPdf(buffer); const pageDims = page.scale(1.0); // ...pageDims, // rotate: degrees(30), // opacity: 0.75, // Optional blendMode // Optional height // Optional opacity // Optional rotate // Optional width // Optional x // Optional xScale // Optional xSkew // Optional y // Optional yScale // Optional ySkew // let rateDif=pageDims.width/pageDims.height; // width = drawHeight*rateDif width = pageDims.width; height = pageDims.height; if(i==0){ x=spaceWidth; y=pageA4.getHeight()/2; } else if(i==1){ x=spaceWidth+pageA4.getWidth()/2; y=pageA4.getHeight()/2; } else if(i==2){ x=spaceWidth; y=spaceWidth; } else if(i==3){ x=spaceWidth+pageA4.getWidth()/2; y=spaceWidth; } pageA4.drawPage(page, { x: x, y: y, width:width, height:height }); i++; } const uint8Array = await pdfDoc.save(); let mergeBuffer = Buffer.from(uint8Array); var blob=new Blob([mergeBuffer]); if(isNeedDown){ const url = window.URL.createObjectURL(blob); const link = document.createElement("a"); link.href = url; link.setAttribute("download", fileName); if (typeof link.download === 'undefined') { link.setAttribute('target', '_blank') } document.body.appendChild(link) link.click() document.body.removeChild(link) }else{ const mergedPdfBlob = new Blob([mergeBuffer], { type: "application/pdf", }); return URL.createObjectURL(mergedPdfBlob); } } downloadBase64(base64Str,fileName) { let result = base64Str.replace(/[\r\n]/g, ""); let pdfBase64 = `data:application/pdf;base64,${result}`; let url = URL.createObjectURL(this.base64ToBlob(pdfBase64)); const link = document.createElement("a"); link.href = url; link.setAttribute("download", fileName); if (typeof link.download === 'undefined') { link.setAttribute('target', '_blank') } document.body.appendChild(link) link.click() document.body.removeChild(link) } async urlToBlob(url) { const response = await fetch(url); const arrayBuffer = await response.arrayBuffer(); const byteArray = new Uint8Array(arrayBuffer); return new Blob([byteArray], {type: response.headers.get('content-type')}); } blobToBase64(blob) { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.onload = () => resolve(reader.result); reader.onerror = reject; reader.readAsDataURL(blob); }); } fileTobase64(file) { let reader = new FileReader(); reader.readAsDataURL(file) } blobToArrayBuffer(blob, callback) { let reader = new FileReader(); reader.readAsArrayBuffer(blob); reader.onload = function () { return callback(this.result); } } // let blob = new Blob([1, 2, 3, 4, 5]); // blobToArrayBuffer(blob, (arrayBuffer) => { console.log(arrayBuffer) }) base64ToFile(base64Data, filename) { // 64转file if (typeof base64Data != 'string') { // this.$toast("urlData不是字符串") return; } var arr = base64Data.split(',') var type = arr[0].match(/:(.*?);/)[1] var fileExt = type.split('/')[1] var bstr = atob(arr[1]) var n = bstr.length var u8arr = new Uint8Array(n) while (n--) { u8arr[n] = bstr.charCodeAt(n); } return new File([u8arr], filename +'.' + fileExt, { type: type }); } base64ToBlob(base64Data) { let arr = base64Data.split(','), fileType = arr[0].match(/:(.*?);/)[1], bstr = atob(arr[1]), l = bstr.length, u8Arr = new Uint8Array(l); while (l--) { u8Arr[l] = bstr.charCodeAt(l); } return new Blob([u8Arr], { type: fileType }); } base64ToBuffer(base64Str){ const padding = '='.repeat((4 - base64Str.length % 4) % 4); const base64 = (base64Str + padding).replace(/\-/g, '+').replace(/_/g, '/'); const rawData = window.atob(base64); const outputArray = new Uint8Array(rawData.length); for (let i = 0; i < rawData.length; ++i) { outputArray[i] = rawData.charCodeAt(i); } return outputArray; } // base64ToArrayBuffer(base64Str) { // let arr = base64Data.split(','), // fileType = arr[0].match(/:(.*?);/)[1], // bstr = atob(result), // l = bstr.length, // u8Arr = new Uint8Array(l); // while (l--) { // u8Arr[l] = bstr.charCodeAt(l); // } // let blob = new Blob([u8Arr], { // type: fileType // }); // let reader = new FileReader(); // reader.onload = function(result) { // console.log(result); // } // reader.readAsArrayBuffer(blob); // } base64File(base64Str,fileName){ var fileExt=".pdf"; var bstr = atob(base64Str) var n = bstr.length var u8arr = new Uint8Array(n) while (n--) { u8arr[n] = bstr.charCodeAt(n); } return new File([u8arr], fileName +'.' + fileExt, { type: type }); } getObjectURL(file) { let url = null; if (window.createObjectURL != undefined) { // basic url = window.createObjectURL(file); } else if (window.webkitURL != undefined) { // webkit or chrome url = window.webkitURL.createObjectURL(file); } else if (window.URL != undefined) { // mozilla(firefox) url = window.URL.createObjectURL(file); } return url; } // 添加 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) } }