diff --git a/src/main/resources/static/css/multi-tool.css b/src/main/resources/static/css/multi-tool.css index 0e609652..29565908 100644 --- a/src/main/resources/static/css/multi-tool.css +++ b/src/main/resources/static/css/multi-tool.css @@ -20,7 +20,7 @@ label { display: flex; gap: 10px; align-items: start; - background-color: var(--md-sys-color-surface-5); + background-color: var(--md-sys-color-surface-5); border: none; backdrop-filter: blur(2px); top: 10px; @@ -127,6 +127,19 @@ label { margin-bottom: 16px; } +.page-container.split-before { + border-left: 1px dashed var(--md-sys-color-on-surface); + padding-left: -1px; +} + +.page-container.split-before:first-child { + border-left: none; +} + +.page-container:first-child .pdf-actions_split-file-button { + display: none; +} + /* Pushes the last item to the left */ .page-container:last-child { margin-right: auto; @@ -171,8 +184,9 @@ label { .page-container:last-child:lang(nqo), /* N'Ko */ .page-container:last-child:lang(bqi) + /* Bakhtiari */ -{ + { margin-left: auto !important; margin-right: 0 !important; } @@ -209,4 +223,4 @@ label { .tool-header { margin: 0.5rem 1rem 2rem; -} \ No newline at end of file +} diff --git a/src/main/resources/static/css/pdfActions.css b/src/main/resources/static/css/pdfActions.css index e8a51224..b76e45b1 100644 --- a/src/main/resources/static/css/pdfActions.css +++ b/src/main/resources/static/css/pdfActions.css @@ -116,4 +116,13 @@ html[dir="rtl"] .pdf-actions_container:last-child>.pdf-actions_insert-file-butto translate: 50% -50%; aspect-ratio: 1; border-radius: 100px; -} \ No newline at end of file +} + +.pdf-actions_split-file-button { + position: absolute; + top: 25%; + right: 50%; + translate: 0 -50%; + aspect-ratio: 1; + border-radius: 100px; +} diff --git a/src/main/resources/static/js/multitool/PdfActionsManager.js b/src/main/resources/static/js/multitool/PdfActionsManager.js index a4e313c7..83768dc0 100644 --- a/src/main/resources/static/js/multitool/PdfActionsManager.js +++ b/src/main/resources/static/js/multitool/PdfActionsManager.js @@ -73,7 +73,12 @@ class PdfActionsManager { this.addFiles(imgContainer); } - setActions({ movePageTo, addFiles, rotateElement }) { + splitFileButtonCallback(e) { + var imgContainer = this.getPageContainer(e.target); + imgContainer.classList.toggle("split-before"); + } + + setActions({ movePageTo, addPdfs, rotateElement }) { this.movePageTo = movePageTo; this.addFiles = addFiles; this.rotateElement = rotateElement; @@ -84,6 +89,7 @@ class PdfActionsManager { this.rotateCWButtonCallback = this.rotateCWButtonCallback.bind(this); this.deletePageButtonCallback = this.deletePageButtonCallback.bind(this); this.insertFileButtonCallback = this.insertFileButtonCallback.bind(this); + this.splitFileButtonCallback = this.splitFileButtonCallback.bind(this); } adapt(div) { @@ -140,6 +146,12 @@ class PdfActionsManager { insertFileButton.onclick = this.insertFileButtonCallback; insertFileButtonContainer.appendChild(insertFileButton); + const splitFileButton = document.createElement("button"); + splitFileButton.classList.add("btn", "btn-primary", "pdf-actions_split-file-button"); + splitFileButton.innerHTML = `cut`; + splitFileButton.onclick = this.splitFileButtonCallback; + insertFileButtonContainer.appendChild(splitFileButton); + div.appendChild(insertFileButtonContainer); // add this button to every element, but only show it on the last one :D diff --git a/src/main/resources/static/js/multitool/PdfContainer.js b/src/main/resources/static/js/multitool/PdfContainer.js index 7f2ceebf..b55b5699 100644 --- a/src/main/resources/static/js/multitool/PdfContainer.js +++ b/src/main/resources/static/js/multitool/PdfContainer.js @@ -19,6 +19,9 @@ class PdfContainer { 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; @@ -34,6 +37,7 @@ class PdfContainer { 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"); @@ -212,6 +216,61 @@ class PdfContainer { } } + 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 @@ -262,8 +321,6 @@ class PdfContainer { } const pdfBytes = await pdfDoc.save(); const pdfBlob = new Blob([pdfBytes], { type: "application/pdf" }); - const url = URL.createObjectURL(pdfBlob); - const downloadOption = localStorage.getItem("downloadOption"); const filenameInput = document.getElementById("filename-input"); @@ -280,28 +337,60 @@ class PdfContainer { this.fileName = filenameInput.value; } - if (!filenameInput.value.includes(".pdf")) { - filenameInput.value = filenameInput.value + ".pdf"; - this.fileName = filenameInput.value; - } + 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"; - 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(); + 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(); + } } } @@ -350,7 +439,7 @@ function detectImageType(uint8Array) { // 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)) { + (uint8Array[0] === 77 && uint8Array[1] === 77 && uint8Array[2] === 0 && uint8Array[3] === 42)) { return 'TIFF'; } diff --git a/src/main/resources/templates/multi-tool.html b/src/main/resources/templates/multi-tool.html index 2bbbf086..9f165014 100644 --- a/src/main/resources/templates/multi-tool.html +++ b/src/main/resources/templates/multi-tool.html @@ -42,6 +42,11 @@ rotate_right +