emis-frontend/src/utils/pdfUtils.js

706 lines
22 KiB
JavaScript
Raw Normal View History

2024-06-04 02:02:17 +00:00
import { degrees,PageSizes, PDFDocument, rgb, StandardFonts } from 'pdf-lib';
2024-06-06 04:49:35 +00:00
import JsBarcode from 'jsbarcode'
2024-05-19 16:31:57 +00:00
import axios from "axios";
import Buffer from 'vue-buffer'
2024-06-03 00:29:12 +00:00
import * as pdfjs from 'pdfjs-dist';
import * as pdfjsWorker from 'pdfjs-dist/build/pdf.worker.entry';
2024-05-19 16:31:57 +00:00
/**
* 浏览器打开新页签预览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
}
2024-06-01 11:43:18 +00:00
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);
}
2024-06-05 01:02:17 +00:00
async getPdfByTpl(tplStr, params) {
const tplBase64Str = 'data:application/pdf;base64,' + tplStr
pdfDoc = await PDFDocument.load(tplBase64Str);
2024-06-04 23:16:31 +00:00
pdfDoc.registerFontkit(fontkit);
font = await pdfDoc.embedFont(fontBytes);
const form = pdfDoc.getForm();
2024-06-05 01:02:17 +00:00
for (const field in params) {
const field = form.getTextField(field);
2024-06-04 23:16:31 +00:00
if (field) {
2024-06-05 01:02:17 +00:00
field.setText(data[field]);
2024-06-04 23:16:31 +00:00
field.updateAppearances(font)
}
}
2024-06-05 01:02:17 +00:00
const uint8Array = await newPdf.save();
let mergeBuffer = Buffer.from(uint8Array);
2024-06-04 23:16:31 +00:00
2024-06-05 01:02:17 +00:00
var blob=new Blob([mergeBuffer]);
2024-06-04 23:16:31 +00:00
}
2024-05-19 16:31:57 +00:00
// 添加 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)
}
2024-06-03 00:29:12 +00:00
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 - 文件名称
*/
2024-06-04 02:02:17 +00:00
savePdfImage(newimgList, x, y, fileName) {
2024-06-03 00:29:12 +00:00
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);
}
});
}
});
});
}
2024-05-31 10:12:59 +00:00
2024-05-31 23:31:01 +00:00
getBlobUrl(base64Str, fileName,isNeedDown=false) {
let blob = this.base64ToBlobNoHeader(base64Str);
// new Blob([base64Str],{type: 'application/pdf'});
2024-05-31 11:59:20 +00:00
let pdfurl = window.URL.createObjectURL(blob);
2024-05-31 23:31:01 +00:00
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类型
2024-05-31 10:12:59 +00:00
}
2024-05-31 11:59:20 +00:00
2024-05-24 01:48:56 +00:00
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",
});
2024-05-31 10:12:59 +00:00
return URL.createObjectURL(mergedPdfBlob);
2024-05-24 01:48:56 +00:00
}
}
2024-06-04 02:02:17 +00:00
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;
2024-06-04 04:50:10 +00:00
let spaceWidth=2;
2024-06-04 02:02:17 +00:00
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
2024-06-04 04:50:10 +00:00
// let rateDif=pageDims.width/pageDims.height;
2024-06-04 02:02:17 +00:00
// width = drawHeight*rateDif
2024-06-04 04:50:10 +00:00
width = pageDims.width;
height = pageDims.height;
2024-06-04 02:02:17 +00:00
if(i==0){
2024-06-04 04:50:10 +00:00
x=spaceWidth;
2024-06-04 02:02:17 +00:00
y=pageA4.getHeight()/2;
}
else if(i==1){
2024-06-04 04:50:10 +00:00
x=spaceWidth+pageA4.getWidth()/2;
2024-06-04 02:02:17 +00:00
y=pageA4.getHeight()/2;
}
else if(i==2){
2024-06-04 04:50:10 +00:00
x=spaceWidth;
y=spaceWidth;
2024-06-04 02:02:17 +00:00
}
else if(i==3){
2024-06-04 04:50:10 +00:00
x=spaceWidth+pageA4.getWidth()/2;
y=spaceWidth;
2024-06-04 02:02:17 +00:00
}
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);
}
}
2024-05-24 01:48:56 +00:00
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)
}
2024-06-04 02:02:17 +00:00
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);
});
}
2024-05-24 01:48:56 +00:00
fileTobase64(file) {
let reader = new FileReader();
reader.readAsDataURL(file)
}
2024-06-04 02:02:17 +00:00
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) })
2024-05-24 01:48:56 +00:00
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
});
}
2024-05-19 16:31:57 +00:00
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)
}
}