Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

454 lines
15 KiB
JavaScript
Raw Normal View History

2023-07-22 13:17:24 +01:00
class PdfContainer {
fileName;
pagesContainer;
pagesContainerWrapper;
pdfAdapters;
downloadLink;
constructor(id, wrapperId, pdfAdapters) {
this.pagesContainer = document.getElementById(id);
this.pagesContainerWrapper = document.getElementById(wrapperId);
this.downloadLink = null;
this.movePageTo = this.movePageTo.bind(this);
this.addFiles = this.addFiles.bind(this);
this.addFilesFromFiles = this.addFilesFromFiles.bind(this);
this.rotateElement = this.rotateElement.bind(this);
this.rotateAll = this.rotateAll.bind(this);
this.exportPdf = this.exportPdf.bind(this);
this.updateFilename = this.updateFilename.bind(this);
this.setDownloadAttribute = this.setDownloadAttribute.bind(this);
this.preventIllegalChars = this.preventIllegalChars.bind(this);
this.addImageFile = this.addImageFile.bind(this);
this.nameAndArchiveFiles = this.nameAndArchiveFiles.bind(this);
this.splitPDF = this.splitPDF.bind(this);
this.splitAll = this.splitAll.bind(this);
this.pdfAdapters = pdfAdapters;
this.pdfAdapters.forEach((adapter) => {
adapter.setActions({
movePageTo: this.movePageTo,
addFiles: this.addFiles,
rotateElement: this.rotateElement,
updateFilename: this.updateFilename,
});
});
window.addFiles = this.addFiles;
window.exportPdf = this.exportPdf;
window.rotateAll = this.rotateAll;
window.splitAll = this.splitAll;
const filenameInput = document.getElementById("filename-input");
const downloadBtn = document.getElementById("export-button");
filenameInput.onkeyup = this.updateFilename;
filenameInput.onkeydown = this.preventIllegalChars;
filenameInput.disabled = false;
filenameInput.innerText = "";
downloadBtn.disabled = true;
}
movePageTo(startElement, endElement, scrollTo = false) {
const childArray = Array.from(this.pagesContainer.childNodes);
const startIndex = childArray.indexOf(startElement);
const endIndex = childArray.indexOf(endElement);
this.pagesContainer.removeChild(startElement);
if (!endElement) {
this.pagesContainer.append(startElement);
} else {
this.pagesContainer.insertBefore(startElement, endElement);
2023-07-22 13:17:24 +01:00
}
2024-02-11 11:47:00 -05:00
if (scrollTo) {
const { width } = startElement.getBoundingClientRect();
const vector = endIndex !== -1 && startIndex > endIndex ? 0 - width : width;
2024-02-11 11:47:00 -05:00
this.pagesContainerWrapper.scroll({
left: this.pagesContainerWrapper.scrollLeft + vector,
});
2023-07-22 13:17:24 +01:00
}
}
addFiles(nextSiblingElement) {
var input = document.createElement("input");
input.type = "file";
input.multiple = true;
input.setAttribute("accept", "application/pdf,image/*");
input.onchange = async (e) => {
const files = e.target.files;
this.addFilesFromFiles(files, nextSiblingElement);
this.updateFilename(files ? files[0].name : "");
};
input.click();
}
async addFilesFromFiles(files, nextSiblingElement) {
this.fileName = files[0].name;
for (var i = 0; i < files.length; i++) {
const file = files[i];
if (file.type === "application/pdf") {
await this.addPdfFile(file, nextSiblingElement);
} else if (file.type.startsWith("image/")) {
await this.addImageFile(file, nextSiblingElement);
}
}
2024-02-11 11:47:00 -05:00
document.querySelectorAll(".enable-on-file").forEach((element) => {
element.disabled = false;
});
}
2024-02-11 11:47:00 -05:00
rotateElement(element, deg) {
var lastTransform = element.style.rotate;
if (!lastTransform) {
lastTransform = "0";
2023-08-05 17:36:05 +02:00
}
const lastAngle = parseInt(lastTransform.replace(/[^\d-]/g, ""));
const newAngle = lastAngle + deg;
element.style.rotate = newAngle + "deg";
}
async addPdfFile(file, nextSiblingElement) {
const { renderer, pdfDocument } = await this.loadFile(file);
for (var i = 0; i < renderer.pageCount; i++) {
const div = document.createElement("div");
div.classList.add("page-container");
var img = document.createElement("img");
img.classList.add("page-image");
const imageSrc = await renderer.renderPage(i);
img.src = imageSrc;
img.pageIdx = i;
img.rend = renderer;
img.doc = pdfDocument;
div.appendChild(img);
this.pdfAdapters.forEach((adapter) => {
adapter.adapt?.(div);
});
if (nextSiblingElement) {
this.pagesContainer.insertBefore(div, nextSiblingElement);
} else {
this.pagesContainer.appendChild(div);
}
2023-07-22 13:17:24 +01:00
}
}
async addImageFile(file, nextSiblingElement) {
const div = document.createElement("div");
div.classList.add("page-container");
var img = document.createElement("img");
img.classList.add("page-image");
img.src = URL.createObjectURL(file);
div.appendChild(img);
this.pdfAdapters.forEach((adapter) => {
adapter.adapt?.(div);
});
if (nextSiblingElement) {
this.pagesContainer.insertBefore(div, nextSiblingElement);
} else {
this.pagesContainer.appendChild(div);
}
}
async loadFile(file) {
var objectUrl = URL.createObjectURL(file);
var pdfDocument = await this.toPdfLib(objectUrl);
var renderer = await this.toRenderer(objectUrl);
return { renderer, pdfDocument };
}
async toRenderer(objectUrl) {
pdfjsLib.GlobalWorkerOptions.workerSrc = "./pdfjs-legacy/pdf.worker.mjs";
const pdf = await pdfjsLib.getDocument(objectUrl).promise;
return {
document: pdf,
pageCount: pdf.numPages,
renderPage: async function (pageIdx) {
const page = await this.document.getPage(pageIdx + 1);
const canvas = document.createElement("canvas");
// set the canvas size to the size of the page
if (page.rotate == 90 || page.rotate == 270) {
canvas.width = page.view[3];
canvas.height = page.view[2];
} else {
canvas.width = page.view[2];
canvas.height = page.view[3];
2023-07-22 13:17:24 +01:00
}
2024-02-11 11:47:00 -05:00
// render the page onto the canvas
var renderContext = {
canvasContext: canvas.getContext("2d"),
viewport: page.getViewport({ scale: 1 }),
2023-07-22 13:17:24 +01:00
};
2024-02-11 11:47:00 -05:00
await page.render(renderContext).promise;
return canvas.toDataURL();
},
};
}
async toPdfLib(objectUrl) {
const existingPdfBytes = await fetch(objectUrl).then((res) => res.arrayBuffer());
const pdfDoc = await PDFLib.PDFDocument.load(existingPdfBytes, {
ignoreEncryption: true,
});
return pdfDoc;
}
rotateAll(deg) {
for (var i = 0; i < this.pagesContainer.childNodes.length; i++) {
const child = this.pagesContainer.children[i];
if (!child) continue;
const img = child.querySelector("img");
if (!img) continue;
this.rotateElement(img, deg);
2023-07-22 13:17:24 +01:00
}
}
splitAll() {
const allPages = this.pagesContainer.querySelectorAll(".page-container");
if (this.pagesContainer.querySelectorAll(".split-before").length > 0) {
allPages.forEach(page => {
page.classList.remove("split-before");
});
} else {
allPages.forEach(page => {
page.classList.add("split-before");
});
}
}
async splitPDF(baseDocBytes, splitters) {
const baseDocument = await PDFLib.PDFDocument.load(baseDocBytes);
const pageNum = baseDocument.getPages().length;
splitters.sort((a, b) => a - b);; // We'll sort the separator indexes just in case querySelectorAll does something funny.
splitters.push(pageNum); // We'll also add a faux separator at the end in order to get the pages after the last separator.
const splitDocuments = [];
for (const splitterPosition of splitters) {
const subDocument = await PDFLib.PDFDocument.create();
const splitterIndex = splitters.indexOf(splitterPosition);
let firstPage = splitterIndex === 0 ? 0 : splitters[splitterIndex - 1];
const pageIndices = Array.from({ length: splitterPosition - firstPage }, (value, key) => firstPage + key);
const copiedPages = await subDocument.copyPages(baseDocument, pageIndices);
copiedPages.forEach(copiedPage => {
subDocument.addPage(copiedPage);
});
const subDocumentBytes = await subDocument.save();
splitDocuments.push(subDocumentBytes);
};
return splitDocuments;
}
async nameAndArchiveFiles(pdfBytesArray, baseNameString) {
const zip = new JSZip();
for (let i = 0; i < pdfBytesArray.length; i++) {
const documentBlob = new Blob([pdfBytesArray[i]], { type: "application/pdf" });
zip.file(baseNameString + "-" + (i + 1) + ".pdf", documentBlob);
}
return zip;
}
async exportPdf() {
const pdfDoc = await PDFLib.PDFDocument.create();
const pageContainers = this.pagesContainer.querySelectorAll(".page-container"); // Select all .page-container elements
for (var i = 0; i < pageContainers.length; i++) {
const img = pageContainers[i].querySelector("img"); // Find the img element within each .page-container
if (!img) continue;
let page;
if (img.doc) {
const pages = await pdfDoc.copyPages(img.doc, [img.pageIdx]);
page = pages[0];
pdfDoc.addPage(page);
} else {
page = pdfDoc.addPage([img.naturalWidth, img.naturalHeight]);
const imageBytes = await fetch(img.src).then((res) => res.arrayBuffer());
const uint8Array = new Uint8Array(imageBytes);
const imageType = detectImageType(uint8Array);
let image;
switch (imageType) {
case 'PNG':
image = await pdfDoc.embedPng(imageBytes);
break;
case 'JPEG':
image = await pdfDoc.embedJpg(imageBytes);
break;
case 'TIFF':
image = await pdfDoc.embedTiff(imageBytes);
break;
case 'GIF':
console.warn(`Unsupported image type: ${imageType}`);
continue; // Skip this image
default:
console.warn(`Unsupported image type: ${imageType}`);
continue; // Skip this image
}
page.drawImage(image, {
x: 0,
y: 0,
width: img.naturalWidth,
height: img.naturalHeight,
});
}
const rotation = img.style.rotate;
if (rotation) {
const rotationAngle = parseInt(rotation.replace(/[^\d-]/g, ""));
page.setRotation(PDFLib.degrees(page.getRotation().angle + rotationAngle));
}
2023-07-22 13:17:24 +01:00
}
const pdfBytes = await pdfDoc.save();
const pdfBlob = new Blob([pdfBytes], { type: "application/pdf" });
2024-02-11 11:47:00 -05:00
const filenameInput = document.getElementById("filename-input");
2024-02-11 11:47:00 -05:00
let inputArr = filenameInput.value.split(".");
2024-02-11 11:47:00 -05:00
if (inputArr !== null && inputArr !== undefined && inputArr.length > 0) {
inputArr = inputArr.filter((n) => n); // remove all empty strings, nulls or undefined
2024-02-11 11:47:00 -05:00
if (inputArr.length > 1) {
inputArr.pop(); // remove right part after last dot
}
2024-02-11 11:47:00 -05:00
filenameInput.value = inputArr.join("");
this.fileName = filenameInput.value;
}
2024-02-11 11:47:00 -05:00
const separators = this.pagesContainer.querySelectorAll(".split-before");
if (separators.length !== 0) { // Split the pdf if there are separators.
const baseName = this.fileName ? this.fileName : "managed";
2024-02-11 11:47:00 -05:00
const pagesArray = Array.from(this.pagesContainer.children);
const splitters = [];
separators.forEach(page => {
const pageIndex = pagesArray.indexOf(page);
if (pageIndex !== 0) {
splitters.push(pageIndex);
}
});
const splitDocuments = await this.splitPDF(pdfBytes, splitters);
const archivedDocuments = await this.nameAndArchiveFiles(splitDocuments, baseName);
const self = this;
archivedDocuments.generateAsync({ type: "base64" }).then(function (base64) {
const url = "data:application/zip;base64," + base64;
self.downloadLink = document.createElement("a");
self.downloadLink.href = url;
self.downloadLink.setAttribute("download", baseName + ".zip");
self.downloadLink.setAttribute("target", "_blank");
self.downloadLink.click();
});
} else { // Continue normally if there are no separators
const url = URL.createObjectURL(pdfBlob);
const downloadOption = localStorage.getItem("downloadOption");
if (!filenameInput.value.includes(".pdf")) {
filenameInput.value = filenameInput.value + ".pdf";
this.fileName = filenameInput.value;
}
if (downloadOption === "sameWindow") {
// Open the file in the same window
window.location.href = url;
} else if (downloadOption === "newWindow") {
// Open the file in a new window
window.open(url, "_blank");
} else {
// Download the file
this.downloadLink = document.createElement("a");
this.downloadLink.id = "download-link";
this.downloadLink.href = url;
// downloadLink.download = this.fileName ? this.fileName : 'managed.pdf';
// downloadLink.download = this.fileName;
this.downloadLink.setAttribute("download", this.fileName ? this.fileName : "managed.pdf");
this.downloadLink.setAttribute("target", "_blank");
this.downloadLink.onclick = this.setDownloadAttribute;
this.downloadLink.click();
}
2023-10-08 19:57:19 +03:00
}
}
2024-02-11 11:47:00 -05:00
setDownloadAttribute() {
this.downloadLink.setAttribute("download", this.fileName ? this.fileName : "managed.pdf");
}
2024-02-11 11:47:00 -05:00
updateFilename(fileName = "") {
const filenameInput = document.getElementById("filename-input");
const pagesContainer = document.getElementById("pages-container");
const downloadBtn = document.getElementById("export-button");
2024-02-11 11:47:00 -05:00
downloadBtn.disabled = pagesContainer.childElementCount === 0;
2024-02-11 11:47:00 -05:00
if (!this.fileName) {
this.fileName = fileName;
}
2024-02-11 11:47:00 -05:00
if (!filenameInput.value) {
filenameInput.value = this.fileName;
}
}
preventIllegalChars(e) {
// const filenameInput = document.getElementById('filename-input');
//
// filenameInput.value = filenameInput.value.replace('.pdf', '');
//
// // prevent .
// if (filenameInput.value.includes('.')) {
// filenameInput.value.replace('.','');
// }
}
2023-07-22 13:17:24 +01:00
}
function detectImageType(uint8Array) {
// Check for PNG signature
if (uint8Array[0] === 137 && uint8Array[1] === 80 && uint8Array[2] === 78 && uint8Array[3] === 71) {
return 'PNG';
}
// Check for JPEG signature
if (uint8Array[0] === 255 && uint8Array[1] === 216 && uint8Array[2] === 255) {
return 'JPEG';
}
2024-02-11 11:47:00 -05:00
// Check for TIFF signature (little-endian and big-endian)
if ((uint8Array[0] === 73 && uint8Array[1] === 73 && uint8Array[2] === 42 && uint8Array[3] === 0) ||
(uint8Array[0] === 77 && uint8Array[1] === 77 && uint8Array[2] === 0 && uint8Array[3] === 42)) {
return 'TIFF';
}
// Check for GIF signature
if (uint8Array[0] === 71 && uint8Array[1] === 73 && uint8Array[2] === 70) {
return 'GIF';
}
return 'UNKNOWN';
}
2023-07-22 13:17:24 +01:00
export default PdfContainer;