From 75e10efcbda1f794cd461a4bf7412e5d60d35649 Mon Sep 17 00:00:00 2001 From: Anthony Stirling <77850077+Frooodle@users.noreply.github.com.> Date: Sun, 7 Jul 2024 22:49:21 +0100 Subject: [PATCH 01/10] auto decrypt, update discord, fix multi file support for some inputs --- .github/ISSUE_TEMPLATE/config.yml | 2 +- README.md | 2 +- .../api/security/PasswordController.java | 2 +- src/main/resources/static/js/downloader.js | 116 ++++++++++++++++-- .../convert/pdf-to-presentation.html | 2 +- .../templates/convert/pdf-to-word.html | 2 +- src/main/resources/templates/error.html | 2 +- .../templates/fragments/errorBanner.html | 2 +- .../fragments/errorBannerPerPage.html | 2 +- .../resources/templates/fragments/navbar.html | 2 +- src/main/resources/templates/home.html | 4 +- src/main/resources/templates/pipeline.html | 2 +- .../resources/templates/remove-pages.html | 2 +- .../templates/security/add-watermark.html | 4 +- .../templates/security/auto-redact.html | 6 +- 15 files changed, 125 insertions(+), 27 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 0f3580d5a..2725e5f5a 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,5 +1,5 @@ blank_issues_enabled: true contact_links: - name: 💬 Discord Server - url: https://discord.gg/Cn8pWhQRxZ + url: https://discord.gg/HYmhKj45pU about: You can join our Discord server for real time discussion and support diff --git a/README.md b/README.md index 4d5eae28b..dbaab9822 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@

Stirling-PDF

[![Docker Pulls](https://img.shields.io/docker/pulls/frooodle/s-pdf)](https://hub.docker.com/r/frooodle/s-pdf) -[![Discord](https://img.shields.io/discord/1068636748814483718?label=Discord)](https://discord.gg/Cn8pWhQRxZ) +[![Discord](https://img.shields.io/discord/1068636748814483718?label=Discord)](https://discord.gg/HYmhKj45pU) [![Docker Image Version (tag latest semver)](https://img.shields.io/docker/v/frooodle/s-pdf/latest)](https://github.com/Stirling-Tools/Stirling-PDF/) [![GitHub Repo stars](https://img.shields.io/github/stars/stirling-tools/stirling-pdf?style=social)](https://github.com/Stirling-Tools/stirling-pdf) [![Paypal Donate](https://img.shields.io/badge/Paypal%20Donate-yellow?style=flat&logo=paypal)](https://www.paypal.com/donate/?hosted_button_id=MN7JPG5G6G3JL) diff --git a/src/main/java/stirling/software/SPDF/controller/api/security/PasswordController.java b/src/main/java/stirling/software/SPDF/controller/api/security/PasswordController.java index 84c449331..b8e36e0e1 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/security/PasswordController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/security/PasswordController.java @@ -68,7 +68,7 @@ public class PasswordController { boolean canModifyAnnotations = request.isCanModifyAnnotations(); boolean canPrint = request.isCanPrint(); boolean canPrintFaithful = request.isCanPrintFaithful(); - + System.out.println(fileInput.getOriginalFilename()); PDDocument document = Loader.loadPDF(fileInput.getBytes()); AccessPermission ap = new AccessPermission(); ap.setCanAssembleDocument(!canAssembleDocument); diff --git a/src/main/resources/static/js/downloader.js b/src/main/resources/static/js/downloader.js index c955bb1bb..07f7c5b68 100644 --- a/src/main/resources/static/js/downloader.js +++ b/src/main/resources/static/js/downloader.js @@ -12,7 +12,7 @@ $(document).ready(function () { event.preventDefault(); firstErrorOccurred = false; const url = this.action; - const files = $("#fileInput-input")[0].files; + var files = $("#fileInput-input")[0].files; const formData = new FormData(this); // Remove empty file entries @@ -36,6 +36,17 @@ $(document).ready(function () { }, 5000); try { + + if (!url.includes("remove-password")) { + // Check if any PDF files are encrypted and handle decryption if necessary + const decryptedFiles = await checkAndDecryptFiles(url ,files); + files = decryptedFiles + // Append decrypted files to formData + decryptedFiles.forEach((file, index) => { + formData.set(`fileInput`, file); + }); + } + if (remoteCall === true) { if (override === "multi" || (!multiple && files.length > 1 && override !== "single")) { await submitMultiPdfForm(url, files); @@ -45,24 +56,24 @@ $(document).ready(function () { } clearTimeout(timeoutId); $("#submitBtn").text(originalButtonText); - + // After process finishes, check for boredWaiting and gameDialog open status const boredWaiting = localStorage.getItem("boredWaiting") || "disabled"; const gameDialog = document.getElementById('game-container-wrapper'); if (boredWaiting === "enabled" && gameDialog && gameDialog.open) { // Display a green banner at the bottom of the screen saying "Download complete" let downloadCompleteText = "Download Complete"; - if(window.downloadCompleteText){ + if (window.downloadCompleteText) { downloadCompleteText = window.downloadCompleteText; } - $("body").append('
'+ downloadCompleteText + '
'); - setTimeout(function() { - $("#download-complete-banner").fadeOut("slow", function() { + $("body").append('
' + downloadCompleteText + '
'); + setTimeout(function () { + $("#download-complete-banner").fadeOut("slow", function () { $(this).remove(); // Remove the banner after fading out }); }, 5000); // Banner will fade out after 5 seconds } - + } catch (error) { clearTimeout(timeoutId); handleDownloadError(error); @@ -72,6 +83,97 @@ $(document).ready(function () { }); }); +async function checkAndDecryptFiles(url, files) { + const decryptedFiles = []; + pdfjsLib.GlobalWorkerOptions.workerSrc = './pdfjs-legacy/pdf.worker.mjs'; + +// Extract the base URL + const baseUrl = new URL(url); + let removePasswordUrl = `${baseUrl.origin}`; + + // Check if there's a path before /api/ + const apiIndex = baseUrl.pathname.indexOf('/api/'); + if (apiIndex > 0) { + removePasswordUrl += baseUrl.pathname.substring(0, apiIndex); + } + + // Append the new endpoint + removePasswordUrl += '/api/v1/security/remove-password'; + + console.log(`Remove password URL: ${removePasswordUrl}`); + + + for (const file of files) { + console.log(`Processing file: ${file.name}`); + if (file.type !== 'application/pdf') { + console.log(`Skipping non-PDF file: ${file.name}`); + decryptedFiles.push(file); + continue; + } + try { + const arrayBuffer = await file.arrayBuffer(); + const loadingTask = pdfjsLib.getDocument({ data: arrayBuffer }); + + console.log(`Attempting to load PDF: ${file.name}`); + const pdf = await loadingTask.promise; + console.log(`File is not encrypted: ${file.name}`); + decryptedFiles.push(file); // If no error, file is not encrypted + } catch (error) { + if (error.name === 'PasswordException' && error.code === 1) { + console.log(`PDF requires password: ${file.name}`, error); + console.log(`Attempting to remove password from PDF: ${file.name} with password.`); + const password = prompt(`This PDF (${file.name}) is encrypted. Please enter the password:`); + + if (!password) { + console.error(`No password provided for encrypted PDF: ${file.name}`); + showErrorBanner(`No password provided for encrypted PDF: ${file.name}`, 'Please enter a valid password.'); + throw error; + } + + try { + // Prepare FormData for the decryption request + const formData = new FormData(); + formData.append('fileInput', file); + formData.append('password', password); + + // Use handleSingleDownload to send the request + const decryptionResult = await fetch(removePasswordUrl, { method: "POST", body: formData }); + + if (decryptionResult && decryptionResult.blob) { + const decryptedBlob = await decryptionResult.blob(); + const decryptedFile = new File([decryptedBlob], file.name, { type: 'application/pdf' }); + + /* // Create a link element to download the file + const link = document.createElement('a'); + link.href = URL.createObjectURL(decryptedBlob); + link.download = 'test.pdf'; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); +*/ + decryptedFiles.push(decryptedFile); + console.log(`Successfully decrypted PDF: ${file.name}`); + } else { + throw new Error('Decryption failed: No valid response from server'); + } + } catch (decryptError) { + console.error(`Failed to decrypt PDF: ${file.name}`, decryptError); + showErrorBanner(`Failed to decrypt PDF: ${file.name}`, 'Incorrect password or unsupported encryption.'); + throw decryptError; + } + } else { + console.log(`Error loading PDF: ${file.name}`, error); + throw error; + } + } + } + return decryptedFiles; +} + + + + + async function handleSingleDownload(url, formData, isMulti = false, isZip = false) { try { const response = await fetch(url, { method: "POST", body: formData }); diff --git a/src/main/resources/templates/convert/pdf-to-presentation.html b/src/main/resources/templates/convert/pdf-to-presentation.html index 794317609..ce1f455dd 100644 --- a/src/main/resources/templates/convert/pdf-to-presentation.html +++ b/src/main/resources/templates/convert/pdf-to-presentation.html @@ -22,8 +22,8 @@
diff --git a/src/main/resources/templates/convert/pdf-to-word.html b/src/main/resources/templates/convert/pdf-to-word.html index 7e5f96c03..f91b6a60e 100644 --- a/src/main/resources/templates/convert/pdf-to-word.html +++ b/src/main/resources/templates/convert/pdf-to-word.html @@ -22,8 +22,8 @@
diff --git a/src/main/resources/templates/error.html b/src/main/resources/templates/error.html index 314d28b39..00f22c27b 100644 --- a/src/main/resources/templates/error.html +++ b/src/main/resources/templates/error.html @@ -19,7 +19,7 @@

- +
diff --git a/src/main/resources/templates/fragments/errorBanner.html b/src/main/resources/templates/fragments/errorBanner.html index 32fb9019c..d682dcb92 100644 --- a/src/main/resources/templates/fragments/errorBanner.html +++ b/src/main/resources/templates/fragments/errorBanner.html @@ -20,7 +20,7 @@ - + diff --git a/src/main/resources/templates/pipeline.html b/src/main/resources/templates/pipeline.html index 0a1d879f2..1f5f1c7cb 100644 --- a/src/main/resources/templates/pipeline.html +++ b/src/main/resources/templates/pipeline.html @@ -16,7 +16,7 @@ /> diff --git a/src/main/resources/templates/remove-pages.html b/src/main/resources/templates/remove-pages.html index 58dc18c78..84471ef29 100644 --- a/src/main/resources/templates/remove-pages.html +++ b/src/main/resources/templates/remove-pages.html @@ -21,7 +21,7 @@
- +
diff --git a/src/main/resources/templates/security/add-watermark.html b/src/main/resources/templates/security/add-watermark.html index 12e706e41..71c323746 100644 --- a/src/main/resources/templates/security/add-watermark.html +++ b/src/main/resources/templates/security/add-watermark.html @@ -20,9 +20,7 @@
-
- -
+
diff --git a/src/main/resources/templates/security/auto-redact.html b/src/main/resources/templates/security/auto-redact.html index 7709af87a..5be0f0dd6 100644 --- a/src/main/resources/templates/security/auto-redact.html +++ b/src/main/resources/templates/security/auto-redact.html @@ -17,10 +17,8 @@
-
- -
- +
+
From f4082e3f965d037bb85f99f2508f08180cbb251b Mon Sep 17 00:00:00 2001 From: Anthony Stirling <77850077+Frooodle@users.noreply.github.com> Date: Sun, 7 Jul 2024 22:51:59 +0100 Subject: [PATCH 02/10] Update PasswordController.java --- .../SPDF/controller/api/security/PasswordController.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/stirling/software/SPDF/controller/api/security/PasswordController.java b/src/main/java/stirling/software/SPDF/controller/api/security/PasswordController.java index b8e36e0e1..8a947c8e0 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/security/PasswordController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/security/PasswordController.java @@ -68,7 +68,6 @@ public class PasswordController { boolean canModifyAnnotations = request.isCanModifyAnnotations(); boolean canPrint = request.isCanPrint(); boolean canPrintFaithful = request.isCanPrintFaithful(); - System.out.println(fileInput.getOriginalFilename()); PDDocument document = Loader.loadPDF(fileInput.getBytes()); AccessPermission ap = new AccessPermission(); ap.setCanAssembleDocument(!canAssembleDocument); From 4d017610b80a5c5ff763a97dbd9e5d7472b8bc37 Mon Sep 17 00:00:00 2001 From: Reece Browne Date: Fri, 6 Dec 2024 19:08:18 +0000 Subject: [PATCH 03/10] PDF decryption --- .../static/js/multitool/DecryptFiles.js | 90 +++++++++++++++++++ .../static/js/multitool/PdfContainer.js | 27 ++++-- 2 files changed, 110 insertions(+), 7 deletions(-) create mode 100644 src/main/resources/static/js/multitool/DecryptFiles.js diff --git a/src/main/resources/static/js/multitool/DecryptFiles.js b/src/main/resources/static/js/multitool/DecryptFiles.js new file mode 100644 index 000000000..bd7e0073a --- /dev/null +++ b/src/main/resources/static/js/multitool/DecryptFiles.js @@ -0,0 +1,90 @@ +export class DecryptFile { + async decryptFile(file) { + try { + const password = prompt('This file is password-protected. Please enter the password:'); + + if (password === null) { + // User cancelled + console.error(`Password prompt cancelled for PDF: ${file.name}`); + this.showErrorBanner(`Operation cancelled for PDF: ${file.name}`, 'You cancelled the decryption process.'); + return null; // No file to return + } + + if (!password) { + // No password provided + console.error(`No password provided for encrypted PDF: ${file.name}`); + this.showErrorBanner(`No password provided for encrypted PDF: ${file.name}`, 'Please enter a valid password.'); + return null; // No file to return + } + + const formData = new FormData(); + formData.append('fileInput', file); + formData.append('password', password); + + // Send decryption request + const response = await fetch('/api/v1/security/remove-password', { + method: 'POST', + body: formData, + }); + + if (response.ok) { + const decryptedBlob = await response.blob(); + this.removeErrorBanner(); + return new File([decryptedBlob], file.name, {type: 'application/pdf'}); + } else { + const errorText = await response.text(); + console.error(`Server error while decrypting: ${errorText}`); + this.showErrorBanner( + 'Please try again with the correct password.', + errorText, + `Incorrect password for PDF: ${file.name}` + ); + return null; // No file to return + } + } catch (error) { + // Handle network or unexpected errors + console.error(`Failed to decrypt PDF: ${file.name}`, error); + this.showErrorBanner( + `Decryption error for PDF: ${file.name}`, + error.message || 'Unexpected error occurred.', + 'There was an error processing the file. Please try again.' + ); + return null; // No file to return + } + } + + async checkFileEncrypted(file) { + try { + pdfjsLib.GlobalWorkerOptions.workerSrc = './pdfjs-legacy/pdf.worker.mjs'; + const arrayBuffer = await file.arrayBuffer(); // Convert file to ArrayBuffer + await pdfjsLib.getDocument({ + data: arrayBuffer, + password: '', + }).promise; + + return false; // File is not encrypted + } catch (error) { + if (error.name === 'PasswordException') { + return true; // File is encrypted + } + console.error('Error checking encryption:', error); + throw new Error('Failed to determine if the file is encrypted.'); + } + } + + showErrorBanner(message, stackTrace, error) { + const errorContainer = document.getElementById('errorContainer'); + errorContainer.style.display = 'block'; // Display the banner + errorContainer.querySelector('.alert-heading').textContent = error; + errorContainer.querySelector('p').textContent = message; + document.querySelector('#traceContent').textContent = stackTrace; + } + + removeErrorBanner() { + const errorContainer = document.getElementById('errorContainer'); + errorContainer.style.display = 'none'; // Hide the banner + errorContainer.querySelector('.alert-heading').textContent = ''; + errorContainer.querySelector('p').textContent = ''; + document.querySelector('#traceContent').textContent = ''; + } +} diff --git a/src/main/resources/static/js/multitool/PdfContainer.js b/src/main/resources/static/js/multitool/PdfContainer.js index 4eaf43f14..f7fa25366 100644 --- a/src/main/resources/static/js/multitool/PdfContainer.js +++ b/src/main/resources/static/js/multitool/PdfContainer.js @@ -5,6 +5,7 @@ import {SplitAllCommand} from './commands/split.js'; import {UndoManager} from './UndoManager.js'; import {PageBreakCommand} from './commands/page-break.js'; import {AddFilesCommand} from './commands/add-page.js'; +import {DecryptFile} from './DecryptFiles.js'; class PdfContainer { fileName; @@ -40,6 +41,8 @@ class PdfContainer { this.removeAllElements = this.removeAllElements.bind(this); this.resetPages = this.resetPages.bind(this); + this.decryptFile = new DecryptFile(); + this.undoManager = undoManager || new UndoManager(); this.pdfAdapters = pdfAdapters; @@ -165,7 +168,6 @@ class PdfContainer { input.click(); }); } - async addFilesFromFiles(files, nextSiblingElement, pages) { this.fileName = files[0].name; for (var i = 0; i < files.length; i++) { @@ -173,17 +175,27 @@ class PdfContainer { let processingTime, errorMessage = null, pageCount = 0; + try { - const file = files[i]; - if (file.type === 'application/pdf') { - const {renderer, pdfDocument} = await this.loadFile(file); + let decryptedFile = files[i]; + + if (decryptedFile.type === 'application/pdf' && (await this.decryptFile.checkFileEncrypted(decryptedFile))) { + decryptedFile = await this.decryptFile.decryptFile(decryptedFile); + if (!decryptedFile) { + throw new Error('File decryption failed.'); + } + } + + if (decryptedFile.type === 'application/pdf') { + const {renderer, pdfDocument} = await this.loadFile(decryptedFile); pageCount = renderer.pageCount || 0; pages = await this.addPdfFile(renderer, pdfDocument, nextSiblingElement, pages); - } else if (file.type.startsWith('image/')) { - pages = await this.addImageFile(file, nextSiblingElement, pages); + } else if (decryptedFile.type.startsWith('image/')) { + pages = await this.addImageFile(decryptedFile, nextSiblingElement, pages); } + processingTime = Date.now() - startTime; - this.captureFileProcessingEvent(true, file, processingTime, null, pageCount); + this.captureFileProcessingEvent(true, decryptedFile, processingTime, null, pageCount); } catch (error) { processingTime = Date.now() - startTime; errorMessage = error.message || 'Unknown error'; @@ -194,6 +206,7 @@ class PdfContainer { document.querySelectorAll('.enable-on-file').forEach((element) => { element.disabled = false; }); + return pages; } From 58278c07ff368fe565a6e301664408015c2827e7 Mon Sep 17 00:00:00 2001 From: Reece Browne Date: Fri, 6 Dec 2024 20:46:04 +0000 Subject: [PATCH 04/10] Translations for errors --- src/main/resources/messages_en_GB.properties | 10 ++++++++ .../static/js/multitool/DecryptFiles.js | 24 ++++++++++++------- src/main/resources/templates/multi-tool.html | 10 +++++++- 3 files changed, 35 insertions(+), 9 deletions(-) diff --git a/src/main/resources/messages_en_GB.properties b/src/main/resources/messages_en_GB.properties index b8c443216..c5e42bff5 100644 --- a/src/main/resources/messages_en_GB.properties +++ b/src/main/resources/messages_en_GB.properties @@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) Selected multiTool.undo=Undo multiTool.redo=Redo +#multiTool-decrypt +multiTool.decrypt.passwordPrompt=This file is password-protected. Please enter the password: +multiTool.decrypt.cancelled=Operation cancelled for PDF: {0} +multiTool.decrypt.noPassword=No password provided for encrypted PDF: {0} +multiTool.decrypt.invalidPassword=Please try again with the correct password. +multiTool.decrypt.invalidPasswordHeader=Incorrect password for PDF: {0} +multiTool.decrypt.unexpectedError=There was an error processing the file. Please try again. +multiTool.decrypt.serverError=Server error while decrypting: {0} +multiTool.decrypt.success=File decrypted successfully. + #multiTool-advert multiTool-advert.message=This feature is also available in our multi-tool page. Check it out for enhanced page-by-page UI and additional features! diff --git a/src/main/resources/static/js/multitool/DecryptFiles.js b/src/main/resources/static/js/multitool/DecryptFiles.js index bd7e0073a..a9acbccc6 100644 --- a/src/main/resources/static/js/multitool/DecryptFiles.js +++ b/src/main/resources/static/js/multitool/DecryptFiles.js @@ -6,14 +6,22 @@ export class DecryptFile { if (password === null) { // User cancelled console.error(`Password prompt cancelled for PDF: ${file.name}`); - this.showErrorBanner(`Operation cancelled for PDF: ${file.name}`, 'You cancelled the decryption process.'); + this.showErrorBanner( + `${window.translations.cancelled.replace('{0}', file.name)}`, + '', + `${window.translations.unexpectedError}` + ); return null; // No file to return } if (!password) { // No password provided console.error(`No password provided for encrypted PDF: ${file.name}`); - this.showErrorBanner(`No password provided for encrypted PDF: ${file.name}`, 'Please enter a valid password.'); + this.showErrorBanner( + `${window.translations.noPassword.replace('{0}', file.name)}`, + '', + `${window.translations.unexpectedError}` + ); return null; // No file to return } @@ -33,11 +41,11 @@ export class DecryptFile { return new File([decryptedBlob], file.name, {type: 'application/pdf'}); } else { const errorText = await response.text(); - console.error(`Server error while decrypting: ${errorText}`); + console.error(`${window.translations.invalidPassword} ${errorText}`); this.showErrorBanner( - 'Please try again with the correct password.', + `${window.translations.invalidPassword}`, errorText, - `Incorrect password for PDF: ${file.name}` + `${window.translations.invalidPasswordHeader.replace('{0}', file.name)}` ); return null; // No file to return } @@ -45,9 +53,9 @@ export class DecryptFile { // Handle network or unexpected errors console.error(`Failed to decrypt PDF: ${file.name}`, error); this.showErrorBanner( - `Decryption error for PDF: ${file.name}`, - error.message || 'Unexpected error occurred.', - 'There was an error processing the file. Please try again.' + `${window.translations.unexpectedError.replace('{0}', file.name)}`, + `${error.message || window.translations.unexpectedError}`, + error ); return null; // No file to return } diff --git a/src/main/resources/templates/multi-tool.html b/src/main/resources/templates/multi-tool.html index 14e5a85ec..532ca5f81 100644 --- a/src/main/resources/templates/multi-tool.html +++ b/src/main/resources/templates/multi-tool.html @@ -162,7 +162,15 @@ insertPageBreak:'[[#{multiTool.insertPageBreak}]]', dragDropMessage:'[[#{multiTool.dragDropMessage}]]', undo: '[[#{multiTool.undo}]]', - redo: '[[#{multiTool.redo}]]' + redo: '[[#{multiTool.redo}]]', + passwordPrompt: '[[#{multiTool.decrypt.passwordPrompt}]]', + cancelled: '[[#{multiTool.decrypt.cancelled}]]', + noPassword: '[[#{multiTool.decrypt.noPassword}]]', + invalidPassword: '[[#{multiTool.decrypt.invalidPassword}]]', + invalidPasswordHeader: '[[#{multiTool.decrypt.invalidPasswordHeader}]]', + unexpectedError: '[[#{multiTool.decrypt.unexpectedError}]]', + serverError: '[[#{multiTool.decrypt.serverError}]]', + success: '[[#{multiTool.decrypt.success}]]', }; From f2c9549ba1be59b210794fd94b1f059058bb29ef Mon Sep 17 00:00:00 2001 From: Reece Browne Date: Fri, 6 Dec 2024 20:53:16 +0000 Subject: [PATCH 05/10] Password prompt translations --- src/main/resources/static/js/multitool/DecryptFiles.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/static/js/multitool/DecryptFiles.js b/src/main/resources/static/js/multitool/DecryptFiles.js index a9acbccc6..f050aec1e 100644 --- a/src/main/resources/static/js/multitool/DecryptFiles.js +++ b/src/main/resources/static/js/multitool/DecryptFiles.js @@ -1,7 +1,7 @@ export class DecryptFile { async decryptFile(file) { try { - const password = prompt('This file is password-protected. Please enter the password:'); + const password = prompt(`${window.translations.passwordPrompt}`); if (password === null) { // User cancelled From 6ee6254f5a9750bbdf696f7ed902cdefb343f094 Mon Sep 17 00:00:00 2001 From: Reece Browne Date: Fri, 6 Dec 2024 21:26:28 +0000 Subject: [PATCH 06/10] Additional decryption translations --- src/main/resources/messages_en_GB.properties | 18 +++++++++--------- src/main/resources/static/js/downloader.js | 18 ++++++++++++------ .../resources/templates/fragments/common.html | 14 +++++++++++++- src/main/resources/templates/multi-tool.html | 16 ++++++++-------- 4 files changed, 42 insertions(+), 24 deletions(-) diff --git a/src/main/resources/messages_en_GB.properties b/src/main/resources/messages_en_GB.properties index c5e42bff5..13accc475 100644 --- a/src/main/resources/messages_en_GB.properties +++ b/src/main/resources/messages_en_GB.properties @@ -965,15 +965,15 @@ multiTool.dragDropMessage=Page(s) Selected multiTool.undo=Undo multiTool.redo=Redo -#multiTool-decrypt -multiTool.decrypt.passwordPrompt=This file is password-protected. Please enter the password: -multiTool.decrypt.cancelled=Operation cancelled for PDF: {0} -multiTool.decrypt.noPassword=No password provided for encrypted PDF: {0} -multiTool.decrypt.invalidPassword=Please try again with the correct password. -multiTool.decrypt.invalidPasswordHeader=Incorrect password for PDF: {0} -multiTool.decrypt.unexpectedError=There was an error processing the file. Please try again. -multiTool.decrypt.serverError=Server error while decrypting: {0} -multiTool.decrypt.success=File decrypted successfully. +#decrypt +decrypt.passwordPrompt=This file is password-protected. Please enter the password: +decrypt.cancelled=Operation cancelled for PDF: {0} +decrypt.noPassword=No password provided for encrypted PDF: {0} +decrypt.invalidPassword=Please try again with the correct password. +decrypt.invalidPasswordHeader=Incorrect password or unsupported encryption for PDF: {0} +decrypt.unexpectedError=There was an error processing the file. Please try again. +decrypt.serverError=Server error while decrypting: {0} +decrypt.success=File decrypted successfully. #multiTool-advert multiTool-advert.message=This feature is also available in our multi-tool page. Check it out for enhanced page-by-page UI and additional features! diff --git a/src/main/resources/static/js/downloader.js b/src/main/resources/static/js/downloader.js index c651620e9..f89c28ec3 100644 --- a/src/main/resources/static/js/downloader.js +++ b/src/main/resources/static/js/downloader.js @@ -71,9 +71,6 @@ }, 5000); try { - submitButton.textContent = 'Processing...'; - submitButton.disabled = true; - if (!url.includes('remove-password')) { // Check if any PDF files are encrypted and handle decryption if necessary const decryptedFiles = await checkAndDecryptFiles(url, files); @@ -84,6 +81,9 @@ }); } + submitButton.textContent = 'Processing...'; + submitButton.disabled = true; + if (remoteCall === true) { if (override === 'multi' || (!multipleInputsForSingleRequest && files.length > 1 && override !== 'single')) { await submitMultiPdfForm(url, files); @@ -181,11 +181,14 @@ if (error.name === 'PasswordException' && error.code === 1) { console.log(`PDF requires password: ${file.name}`, error); console.log(`Attempting to remove password from PDF: ${file.name} with password.`); - const password = prompt(`This PDF (${file.name}) is encrypted. Please enter the password:`); + const password = prompt(`${window.translations.decrypt.passwordPrompt}`); if (!password) { console.error(`No password provided for encrypted PDF: ${file.name}`); - showErrorBanner(`No password provided for encrypted PDF: ${file.name}`, 'Please enter a valid password.'); + showErrorBanner( + `${window.translations.decrypt.noPassword.replace('{0}', file.name)}`, + `${window.translations.decrypt.unexpectedError}` + ); throw error; } @@ -217,7 +220,10 @@ } } catch (decryptError) { console.error(`Failed to decrypt PDF: ${file.name}`, decryptError); - showErrorBanner(`Failed to decrypt PDF: ${file.name}`, 'Incorrect password or unsupported encryption.'); + showErrorBanner( + `${window.translations.invalidPasswordHeader.replace('{0}', file.name)}`, + `${window.translations.invalidPassword}` + ); throw decryptError; } } else { diff --git a/src/main/resources/templates/fragments/common.html b/src/main/resources/templates/fragments/common.html index f372d1dd5..04a91a8dd 100644 --- a/src/main/resources/templates/fragments/common.html +++ b/src/main/resources/templates/fragments/common.html @@ -203,7 +203,19 @@ - +
diff --git a/src/main/resources/templates/multi-tool.html b/src/main/resources/templates/multi-tool.html index 260ad74cd..17eafedf3 100644 --- a/src/main/resources/templates/multi-tool.html +++ b/src/main/resources/templates/multi-tool.html @@ -163,17 +163,18 @@ dragDropMessage:'[[#{multiTool.dragDropMessage}]]', undo: '[[#{multiTool.undo}]]', redo: '[[#{multiTool.redo}]]', - passwordPrompt: '[[#{decrypt.passwordPrompt}]]', - cancelled: '[[#{decrypt.cancelled}]]', - noPassword: '[[#{decrypt.noPassword}]]', - invalidPassword: '[[#{decrypt.invalidPassword}]]', - invalidPasswordHeader: '[[#{decrypt.invalidPasswordHeader}]]', - unexpectedError: '[[#{decrypt.unexpectedError}]]', - serverError: '[[#{decrypt.serverError}]]', - success: '[[#{decrypt.success}]]', }; - + window.decrypt = { + passwordPrompt: '[[#{decrypt.passwordPrompt}]]', + cancelled: '[[#{decrypt.cancelled}]]', + noPassword: '[[#{decrypt.noPassword}]]', + invalidPassword: '[[#{decrypt.invalidPassword}]]', + invalidPasswordHeader: '[[#{decrypt.invalidPasswordHeader}]]', + unexpectedError: '[[#{decrypt.unexpectedError}]]', + serverError: '[[#{decrypt.serverError}]]', + success: '[[#{decrypt.success}]]', + } const csvInput = document.getElementById("csv-input"); csvInput.addEventListener("keydown", function (event) { diff --git a/src/main/resources/templates/sign.html b/src/main/resources/templates/sign.html index 31c855dac..a0fe2f107 100644 --- a/src/main/resources/templates/sign.html +++ b/src/main/resources/templates/sign.html @@ -19,9 +19,10 @@ } - + + @@ -42,75 +43,6 @@ th:replace="~{fragments/common :: fileSelector(name='pdf-upload', multipleInputsForSingleRequest=false, disableMultipleFiles=true, accept='application/pdf')}">
-
- - - - -
@@ -410,35 +225,11 @@
-
- - - -
@@ -447,6 +238,7 @@ + \ No newline at end of file From 64dfa4b8416aba48524137516c908f4f00674f26 Mon Sep 17 00:00:00 2001 From: Reece Browne Date: Tue, 10 Dec 2024 22:21:00 +0000 Subject: [PATCH 09/10] Tweak additional files to integrate decryption and clean up js --- src/main/resources/static/js/fileInput.js | 2 +- .../resources/static/js/pages/add-image.js | 47 ++++++ .../static/js/pages/change-metadata.js | 150 +++++++++++++++++ src/main/resources/static/js/pages/crop.js | 159 ++++++++++++++++++ .../resources/static/js/pages/pdf-to-csv.js | 138 +++++++++++++++ src/main/resources/static/js/pages/sign.js | 2 - .../templates/convert/pdf-to-csv.html | 136 +-------------- src/main/resources/templates/crop.html | 158 +---------------- .../resources/templates/misc/add-image.html | 50 +----- .../templates/misc/change-metadata.html | 135 +-------------- src/main/resources/templates/overlay-pdf.html | 1 + 11 files changed, 501 insertions(+), 477 deletions(-) create mode 100644 src/main/resources/static/js/pages/add-image.js create mode 100644 src/main/resources/static/js/pages/change-metadata.js create mode 100644 src/main/resources/static/js/pages/crop.js create mode 100644 src/main/resources/static/js/pages/pdf-to-csv.js diff --git a/src/main/resources/static/js/fileInput.js b/src/main/resources/static/js/fileInput.js index 57010b6c6..63485c606 100644 --- a/src/main/resources/static/js/fileInput.js +++ b/src/main/resources/static/js/fileInput.js @@ -2,7 +2,6 @@ import FileIconFactory from './file-icon-factory.js'; import FileUtils from './file-utils.js'; import UUID from './uuid.js'; import {DecryptFile} from './DecryptFiles.js'; -const decryptFile = new DecryptFile(); let isScriptExecuted = false; if (!isScriptExecuted) { isScriptExecuted = true; @@ -123,6 +122,7 @@ function setupFileInput(chooser) { return decryptedFile; } catch (error) { console.error(`Error decrypting file: ${file.name}`, error); + if (!file.uniqueId) file.uniqueId = UUID.uuidv4(); return file; } }) diff --git a/src/main/resources/static/js/pages/add-image.js b/src/main/resources/static/js/pages/add-image.js new file mode 100644 index 000000000..5899b53f9 --- /dev/null +++ b/src/main/resources/static/js/pages/add-image.js @@ -0,0 +1,47 @@ +document.getElementById('download-pdf').addEventListener('click', async () => { + const modifiedPdf = await DraggableUtils.getOverlayedPdfDocument(); + const modifiedPdfBytes = await modifiedPdf.save(); + const blob = new Blob([modifiedPdfBytes], {type: 'application/pdf'}); + const link = document.createElement('a'); + link.href = URL.createObjectURL(blob); + link.download = originalFileName + '_addedImage.pdf'; + link.click(); +}); +let originalFileName = ''; +document.querySelector('input[name=pdf-upload]').addEventListener('change', async (event) => { + const fileInput = event.target; + fileInput.addEventListener('file-input-change', async (e) => { + const {allFiles} = e.detail; + if (allFiles && allFiles.length > 0) { + const file = allFiles[0]; + originalFileName = file.name.replace(/\.[^/.]+$/, ''); + const pdfData = await file.arrayBuffer(); + pdfjsLib.GlobalWorkerOptions.workerSrc = './pdfjs-legacy/pdf.worker.mjs'; + const pdfDoc = await pdfjsLib.getDocument({data: pdfData}).promise; + await DraggableUtils.renderPage(pdfDoc, 0); + + document.querySelectorAll('.show-on-file-selected').forEach((el) => { + el.style.cssText = ''; + }); + } + }); +}); +document.addEventListener('DOMContentLoaded', () => { + document.querySelectorAll('.show-on-file-selected').forEach((el) => { + el.style.cssText = 'display:none !important'; + }); +}); + +const imageUpload = document.querySelector('input[name=image-upload]'); +imageUpload.addEventListener('change', (e) => { + if (!e.target.files) { + return; + } + for (const imageFile of e.target.files) { + var reader = new FileReader(); + reader.readAsDataURL(imageFile); + reader.onloadend = function (e) { + DraggableUtils.createDraggableCanvasFromUrl(e.target.result); + }; + } +}); diff --git a/src/main/resources/static/js/pages/change-metadata.js b/src/main/resources/static/js/pages/change-metadata.js new file mode 100644 index 000000000..bdc5426b7 --- /dev/null +++ b/src/main/resources/static/js/pages/change-metadata.js @@ -0,0 +1,150 @@ +const deleteAllCheckbox = document.querySelector('#deleteAll'); +let inputs = document.querySelectorAll('input'); +const customMetadataDiv = document.getElementById('customMetadata'); +const otherMetadataEntriesDiv = document.getElementById('otherMetadataEntries'); + +deleteAllCheckbox.addEventListener('change', function (event) { + inputs.forEach((input) => { + // If it's the deleteAllCheckbox or any file input, skip + if (input === deleteAllCheckbox || input.type === 'file') { + return; + } + // Disable or enable based on the checkbox state + input.disabled = deleteAllCheckbox.checked; + }); +}); + +const customModeCheckbox = document.getElementById('customModeCheckbox'); +const addMetadataBtn = document.getElementById('addMetadataBtn'); +const customMetadataFormContainer = document.getElementById('customMetadataEntries'); +var count = 1; +const fileInput = document.querySelector('#fileInput-input'); +const authorInput = document.querySelector('#author'); +const creationDateInput = document.querySelector('#creationDate'); +const creatorInput = document.querySelector('#creator'); +const keywordsInput = document.querySelector('#keywords'); +const modificationDateInput = document.querySelector('#modificationDate'); +const producerInput = document.querySelector('#producer'); +const subjectInput = document.querySelector('#subject'); +const titleInput = document.querySelector('#title'); +const trappedInput = document.querySelector('#trapped'); +var lastPDFFileMeta = null; +var lastPDFFile = null; + +fileInput.addEventListener('change', async function () { + fileInput.addEventListener('file-input-change', async (e) => { + const {allFiles} = e.detail; + if (allFiles && allFiles.length > 0) { + const file = allFiles[0]; + while (otherMetadataEntriesDiv.firstChild) { + otherMetadataEntriesDiv.removeChild(otherMetadataEntriesDiv.firstChild); + } + while (customMetadataFormContainer.firstChild) { + customMetadataFormContainer.removeChild(customMetadataFormContainer.firstChild); + } + var url = URL.createObjectURL(file); + pdfjsLib.GlobalWorkerOptions.workerSrc = './pdfjs-legacy/pdf.worker.mjs'; + const pdf = await pdfjsLib.getDocument(url).promise; + const pdfMetadata = await pdf.getMetadata(); + lastPDFFile = pdfMetadata?.info; + console.log(pdfMetadata); + if (!pdfMetadata?.info?.Custom || pdfMetadata?.info?.Custom.size == 0) { + customModeCheckbox.disabled = true; + customModeCheckbox.checked = false; + } else { + customModeCheckbox.disabled = false; + } + authorInput.value = pdfMetadata?.info?.Author; + creationDateInput.value = convertDateFormat(pdfMetadata?.info?.CreationDate); + creatorInput.value = pdfMetadata?.info?.Creator; + keywordsInput.value = pdfMetadata?.info?.Keywords; + modificationDateInput.value = convertDateFormat(pdfMetadata?.info?.ModDate); + producerInput.value = pdfMetadata?.info?.Producer; + subjectInput.value = pdfMetadata?.info?.Subject; + titleInput.value = pdfMetadata?.info?.Title; + console.log(pdfMetadata?.info); + const trappedValue = pdfMetadata?.info?.Trapped; + // Get all options in the select element + const options = trappedInput.options; + // Loop through all options to find the one with a matching value + for (let i = 0; i < options.length; i++) { + if (options[i].value === trappedValue) { + options[i].selected = true; + break; + } + } + addExtra(); + } + }); +}); + +addMetadataBtn.addEventListener('click', () => { + const keyInput = document.createElement('input'); + keyInput.type = 'text'; + keyInput.placeholder = 'Key'; + keyInput.className = 'form-control'; + keyInput.name = `allRequestParams[customKey${count}]`; + + const valueInput = document.createElement('input'); + valueInput.type = 'text'; + valueInput.placeholder = 'Value'; + valueInput.className = 'form-control'; + valueInput.name = `allRequestParams[customValue${count}]`; + count = count + 1; + + const formGroup = document.createElement('div'); + formGroup.className = 'mb-3'; + formGroup.appendChild(keyInput); + formGroup.appendChild(valueInput); + + customMetadataFormContainer.appendChild(formGroup); +}); +function convertDateFormat(dateTimeString) { + if (!dateTimeString || dateTimeString.length < 17) { + return dateTimeString; + } + + const year = dateTimeString.substring(2, 6); + const month = dateTimeString.substring(6, 8); + const day = dateTimeString.substring(8, 10); + const hour = dateTimeString.substring(10, 12); + const minute = dateTimeString.substring(12, 14); + const second = dateTimeString.substring(14, 16); + + return year + '/' + month + '/' + day + ' ' + hour + ':' + minute + ':' + second; +} + +function addExtra() { + const event = document.getElementById('customModeCheckbox'); + if (event.checked && lastPDFFile.Custom != null) { + customMetadataDiv.style.display = 'block'; + for (const [key, value] of Object.entries(lastPDFFile.Custom)) { + if ( + key === 'Author' || + key === 'CreationDate' || + key === 'Creator' || + key === 'Keywords' || + key === 'ModDate' || + key === 'Producer' || + key === 'Subject' || + key === 'Title' || + key === 'Trapped' + ) { + continue; + } + const entryDiv = document.createElement('div'); + entryDiv.className = 'mb-3'; + entryDiv.innerHTML = `
`; + otherMetadataEntriesDiv.appendChild(entryDiv); + } + } else { + customMetadataDiv.style.display = 'none'; + while (otherMetadataEntriesDiv.firstChild) { + otherMetadataEntriesDiv.removeChild(otherMetadataEntriesDiv.firstChild); + } + } +} + +customModeCheckbox.addEventListener('change', (event) => { + addExtra(); +}); diff --git a/src/main/resources/static/js/pages/crop.js b/src/main/resources/static/js/pages/crop.js new file mode 100644 index 000000000..1854023a0 --- /dev/null +++ b/src/main/resources/static/js/pages/crop.js @@ -0,0 +1,159 @@ +let pdfCanvas = document.getElementById('cropPdfCanvas'); +let overlayCanvas = document.getElementById('overlayCanvas'); +let canvasesContainer = document.getElementById('canvasesContainer'); +canvasesContainer.style.display = 'none'; +let containerRect = canvasesContainer.getBoundingClientRect(); + +let context = pdfCanvas.getContext('2d'); +let overlayContext = overlayCanvas.getContext('2d'); + +overlayCanvas.width = pdfCanvas.width; +overlayCanvas.height = pdfCanvas.height; + +let isDrawing = false; // New flag to check if drawing is ongoing + +let cropForm = document.getElementById('cropForm'); +let fileInput = document.getElementById('fileInput-input'); +let xInput = document.getElementById('x'); +let yInput = document.getElementById('y'); +let widthInput = document.getElementById('width'); +let heightInput = document.getElementById('height'); + +let pdfDoc = null; +let currentPage = 1; +let totalPages = 0; + +let startX = 0; +let startY = 0; +let rectWidth = 0; +let rectHeight = 0; + +let pageScale = 1; // The scale which the pdf page renders +let timeId = null; // timeout id for resizing canvases event + +function renderPageFromFile(file) { + if (file.type === 'application/pdf') { + let reader = new FileReader(); + reader.onload = function (ev) { + let typedArray = new Uint8Array(reader.result); + pdfjsLib.GlobalWorkerOptions.workerSrc = './pdfjs-legacy/pdf.worker.mjs'; + pdfjsLib.getDocument(typedArray).promise.then(function (pdf) { + pdfDoc = pdf; + totalPages = pdf.numPages; + renderPage(currentPage); + }); + }; + reader.readAsArrayBuffer(file); + } +} + +window.addEventListener('resize', function () { + clearTimeout(timeId); + + timeId = setTimeout(function () { + if (fileInput.files.length == 0) return; + let canvasesContainer = document.getElementById('canvasesContainer'); + let containerRect = canvasesContainer.getBoundingClientRect(); + + context.clearRect(0, 0, pdfCanvas.width, pdfCanvas.height); + + overlayContext.clearRect(0, 0, overlayCanvas.width, overlayCanvas.height); + + pdfCanvas.width = containerRect.width; + pdfCanvas.height = containerRect.height; + + overlayCanvas.width = containerRect.width; + overlayCanvas.height = containerRect.height; + + let file = fileInput.files[0]; + renderPageFromFile(file); + }, 1000); +}); + +fileInput.addEventListener('change', function (e) { + fileInput.addEventListener('file-input-change', async (e) => { + const {allFiles} = e.detail; + if (allFiles && allFiles.length > 0) { + canvasesContainer.style.display = 'block'; // set for visual purposes + let file = allFiles[0]; + renderPageFromFile(file); + } + }); +}); + +cropForm.addEventListener('submit', function (e) { + if (xInput.value == '' && yInput.value == '' && widthInput.value == '' && heightInput.value == '') { + // Ορίστε συντεταγμένες για ολόκληρη την επιφάνεια του PDF + xInput.value = 0; + yInput.value = 0; + widthInput.value = containerRect.width; + heightInput.value = containerRect.height; + } +}); + +overlayCanvas.addEventListener('mousedown', function (e) { + // Clear previously drawn rectangle on the main canvas + context.clearRect(0, 0, pdfCanvas.width, pdfCanvas.height); + renderPage(currentPage); // Re-render the PDF + + // Clear the overlay canvas to ensure old drawings are removed + overlayContext.clearRect(0, 0, overlayCanvas.width, overlayCanvas.height); + + startX = e.offsetX; + startY = e.offsetY; + isDrawing = true; +}); + +overlayCanvas.addEventListener('mousemove', function (e) { + if (!isDrawing) return; + overlayContext.clearRect(0, 0, overlayCanvas.width, overlayCanvas.height); // Clear previous rectangle + + rectWidth = e.offsetX - startX; + rectHeight = e.offsetY - startY; + overlayContext.strokeStyle = 'red'; + overlayContext.strokeRect(startX, startY, rectWidth, rectHeight); +}); + +overlayCanvas.addEventListener('mouseup', function (e) { + isDrawing = false; + + rectWidth = e.offsetX - startX; + rectHeight = e.offsetY - startY; + + let flippedY = pdfCanvas.height - e.offsetY; + + xInput.value = startX / pageScale; + yInput.value = flippedY / pageScale; + widthInput.value = rectWidth / pageScale; + heightInput.value = rectHeight / pageScale; + + // Draw the final rectangle on the main canvas + context.strokeStyle = 'red'; + context.strokeRect(startX, startY, rectWidth, rectHeight); + + overlayContext.clearRect(0, 0, overlayCanvas.width, overlayCanvas.height); // Clear the overlay +}); + +function renderPage(pageNumber) { + pdfDoc.getPage(pageNumber).then(function (page) { + let canvasesContainer = document.getElementById('canvasesContainer'); + let containerRect = canvasesContainer.getBoundingClientRect(); + + pageScale = containerRect.width / page.getViewport({scale: 1}).width; // The new scale + + let viewport = page.getViewport({scale: containerRect.width / page.getViewport({scale: 1}).width}); + + canvasesContainer.width = viewport.width; + canvasesContainer.height = viewport.height; + + pdfCanvas.width = viewport.width; + pdfCanvas.height = viewport.height; + + overlayCanvas.width = viewport.width; // Match overlay canvas size with PDF canvas + overlayCanvas.height = viewport.height; + + let renderContext = {canvasContext: context, viewport: viewport}; + page.render(renderContext); + pdfCanvas.classList.add('shadow-canvas'); + }); +} diff --git a/src/main/resources/static/js/pages/pdf-to-csv.js b/src/main/resources/static/js/pages/pdf-to-csv.js new file mode 100644 index 000000000..6be3c2ed6 --- /dev/null +++ b/src/main/resources/static/js/pages/pdf-to-csv.js @@ -0,0 +1,138 @@ +let pdfCanvas = document.getElementById('cropPdfCanvas'); +let overlayCanvas = document.getElementById('overlayCanvas'); +let canvasesContainer = document.getElementById('canvasesContainer'); +canvasesContainer.style.display = 'none'; +// let paginationBtnContainer = ; + +let context = pdfCanvas.getContext('2d'); +let overlayContext = overlayCanvas.getContext('2d'); + +let btn1Object = document.getElementById('previous-page-btn'); +let btn2Object = document.getElementById('next-page-btn'); +overlayCanvas.width = pdfCanvas.width; +overlayCanvas.height = pdfCanvas.height; + +let fileInput = document.getElementById('fileInput-input'); + +let file; + +let pdfDoc = null; +let pageId = document.getElementById('pageId'); +let currentPage = 1; +let totalPages = 0; + +let startX = 0; +let startY = 0; +let rectWidth = 0; +let rectHeight = 0; + +let timeId = null; // timeout id for resizing canvases event + +btn1Object.addEventListener('click', function (e) { + if (currentPage !== 1) { + currentPage = currentPage - 1; + pageId.value = currentPage; + + if (file.type === 'application/pdf') { + let reader = new FileReader(); + reader.onload = function (ev) { + let typedArray = new Uint8Array(reader.result); + pdfjsLib.GlobalWorkerOptions.workerSrc = './pdfjs-legacy/pdf.worker.mjs'; + pdfjsLib.getDocument(typedArray).promise.then(function (pdf) { + pdfDoc = pdf; + totalPages = pdf.numPages; + renderPage(currentPage); + }); + }; + reader.readAsArrayBuffer(file); + } + } +}); + +btn2Object.addEventListener('click', function (e) { + if (currentPage !== totalPages) { + currentPage = currentPage + 1; + pageId.value = currentPage; + + if (file.type === 'application/pdf') { + let reader = new FileReader(); + reader.onload = function (ev) { + let typedArray = new Uint8Array(reader.result); + pdfjsLib.GlobalWorkerOptions.workerSrc = './pdfjs-legacy/pdf.worker.mjs'; + pdfjsLib.getDocument(typedArray).promise.then(function (pdf) { + pdfDoc = pdf; + totalPages = pdf.numPages; + renderPage(currentPage); + }); + }; + reader.readAsArrayBuffer(file); + } + } +}); + +function renderPageFromFile(file) { + if (file.type === 'application/pdf') { + let reader = new FileReader(); + reader.onload = function (ev) { + let typedArray = new Uint8Array(reader.result); + pdfjsLib.GlobalWorkerOptions.workerSrc = './pdfjs-legacy/pdf.worker.mjs'; + pdfjsLib.getDocument(typedArray).promise.then(function (pdf) { + pdfDoc = pdf; + totalPages = pdf.numPages; + renderPage(currentPage); + }); + pageId.value = currentPage; + }; + reader.readAsArrayBuffer(file); + document.getElementById('pagination-button-container').style.display = 'flex'; + document.getElementById('instruction-text').style.display = 'block'; + } +} + +window.addEventListener('resize', function () { + clearTimeout(timeId); + timeId = setTimeout(function () { + if (fileInput.files.length == 0) return; + let canvasesContainer = document.getElementById('canvasesContainer'); + let containerRect = canvasesContainer.getBoundingClientRect(); + + context.clearRect(0, 0, pdfCanvas.width, pdfCanvas.height); + + overlayContext.clearRect(0, 0, overlayCanvas.width, overlayCanvas.height); + + pdfCanvas.width = containerRect.width; + pdfCanvas.height = containerRect.height; + + overlayCanvas.width = containerRect.width; + overlayCanvas.height = containerRect.height; + + let file = fileInput.files[0]; + renderPageFromFile(file); + }, 1000); +}); + +fileInput.addEventListener('change', function (e) { + fileInput.addEventListener('file-input-change', async (e) => { + const {allFiles} = e.detail; + if (allFiles && allFiles.length > 0) { + canvasesContainer.style.display = 'block'; // set for visual purposes + file = e.target.files[0]; + renderPageFromFile(file); + } + }); +}); + +function renderPage(pageNumber) { + pdfDoc.getPage(pageNumber).then(function (page) { + let viewport = page.getViewport({scale: 1.0}); + pdfCanvas.width = viewport.width; + pdfCanvas.height = viewport.height; + + overlayCanvas.width = viewport.width; // Match overlay canvas size with PDF canvas + overlayCanvas.height = viewport.height; + + let renderContext = {canvasContext: context, viewport: viewport}; + page.render(renderContext); + pdfCanvas.classList.add('shadow-canvas'); + }); +} diff --git a/src/main/resources/static/js/pages/sign.js b/src/main/resources/static/js/pages/sign.js index 411fabf49..8d45c9697 100644 --- a/src/main/resources/static/js/pages/sign.js +++ b/src/main/resources/static/js/pages/sign.js @@ -52,8 +52,6 @@ function addSignatureFromPreview() { let originalFileName = ''; document.querySelector('input[name=pdf-upload]').addEventListener('change', async (event) => { const fileInput = event.target; - - // Wait for the second function to complete fileInput.addEventListener('file-input-change', async (e) => { const {allFiles} = e.detail; if (allFiles && allFiles.length > 0) { diff --git a/src/main/resources/templates/convert/pdf-to-csv.html b/src/main/resources/templates/convert/pdf-to-csv.html index b3dfc09cd..976837340 100644 --- a/src/main/resources/templates/convert/pdf-to-csv.html +++ b/src/main/resources/templates/convert/pdf-to-csv.html @@ -34,140 +34,8 @@ - + diff --git a/src/main/resources/templates/crop.html b/src/main/resources/templates/crop.html index e606fded5..5262f0f59 100644 --- a/src/main/resources/templates/crop.html +++ b/src/main/resources/templates/crop.html @@ -32,163 +32,7 @@ - + diff --git a/src/main/resources/templates/misc/add-image.html b/src/main/resources/templates/misc/add-image.html index 947185a73..6bca8ee7a 100644 --- a/src/main/resources/templates/misc/add-image.html +++ b/src/main/resources/templates/misc/add-image.html @@ -22,46 +22,9 @@
- - +
-
@@ -93,17 +56,6 @@
- diff --git a/src/main/resources/templates/misc/change-metadata.html b/src/main/resources/templates/misc/change-metadata.html index 50ba318bf..2aaa47e9a 100644 --- a/src/main/resources/templates/misc/change-metadata.html +++ b/src/main/resources/templates/misc/change-metadata.html @@ -85,140 +85,7 @@
- diff --git a/src/main/resources/templates/overlay-pdf.html b/src/main/resources/templates/overlay-pdf.html index e92e4cb08..616e3343a 100644 --- a/src/main/resources/templates/overlay-pdf.html +++ b/src/main/resources/templates/overlay-pdf.html @@ -2,6 +2,7 @@ + From bb3f076e6dbb58e82f28d21d580450295e185912 Mon Sep 17 00:00:00 2001 From: Reece Browne Date: Wed, 11 Dec 2024 16:55:27 +0000 Subject: [PATCH 10/10] Final pages fixed for decryption --- src/main/resources/templates/misc/extract-images.html | 1 + src/main/resources/templates/misc/replace-color.html | 1 + src/main/resources/templates/pipeline.html | 1 + 3 files changed, 3 insertions(+) diff --git a/src/main/resources/templates/misc/extract-images.html b/src/main/resources/templates/misc/extract-images.html index d47a72d7c..1e6268feb 100644 --- a/src/main/resources/templates/misc/extract-images.html +++ b/src/main/resources/templates/misc/extract-images.html @@ -2,6 +2,7 @@ + diff --git a/src/main/resources/templates/misc/replace-color.html b/src/main/resources/templates/misc/replace-color.html index 4defcff72..4ab5460a9 100644 --- a/src/main/resources/templates/misc/replace-color.html +++ b/src/main/resources/templates/misc/replace-color.html @@ -4,6 +4,7 @@ + diff --git a/src/main/resources/templates/pipeline.html b/src/main/resources/templates/pipeline.html index eaa874639..dae05df2b 100644 --- a/src/main/resources/templates/pipeline.html +++ b/src/main/resources/templates/pipeline.html @@ -14,6 +14,7 @@ th:href="@{'/css/pipeline.css'}" th:if="${currentPage == 'pipeline'}" /> +