emis-frontend/src/utils/pdfUtils.js
2024-06-08 15:20:21 +08:00

705 lines
22 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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)
}
}