emis-frontend/src/utils/pdfUtils.js
2024-06-01 19:43:18 +08:00

499 lines
16 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, PDFDocument, rgb, StandardFonts } from 'pdf-lib';
import axios from "axios";
import Buffer from 'vue-buffer'
/**
* 浏览器打开新页签预览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() {
// { 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))
// }
}
/**
* 通过模版生成A4纸面单
* @param {*} templatePath
* @param {*} outputPath
* @param {*} data
*/
// const { PDFDocument, StandardFonts } = require('pdf-lib');
// const fs = require('fs');
// const fontkit = require('@pdf-lib/fontkit')
// let pdfDoc
// let font
// 使用示例
// const templatePath = 'template.pdf';
// const outputPath = 'output.pdf';
// const data = {
// 'key': 'value支持中文参数',
// };
// fillPdfForm(templatePath, outputPath, data)
// .then(() => {
// console.log('PDF生成成功');
// })
// .catch((error) => {
// console.log('PDF生成失败:', error);
// });
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);
}
// 添加 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)
}
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);
}
}
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)
}
fileTobase64(file) {
let reader = new FileReader();
reader.readAsDataURL(file)
}
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)
}
}