mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2025-05-25 03:12:02 +00:00
Merge pull request #2412 from reecebrowne/feature/1856/decrypt
Feature/1856/decrypt
This commit is contained in:
commit
93e190fdeb
2
.github/ISSUE_TEMPLATE/config.yml
vendored
2
.github/ISSUE_TEMPLATE/config.yml
vendored
@ -1,5 +1,5 @@
|
|||||||
blank_issues_enabled: true
|
blank_issues_enabled: true
|
||||||
contact_links:
|
contact_links:
|
||||||
- name: 💬 Discord Server
|
- 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
|
about: You can join our Discord server for real time discussion and support
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<h1 align="center">Stirling-PDF</h1>
|
<h1 align="center">Stirling-PDF</h1>
|
||||||
|
|
||||||
[](https://hub.docker.com/r/frooodle/s-pdf)
|
[](https://hub.docker.com/r/frooodle/s-pdf)
|
||||||
[](https://discord.gg/Cn8pWhQRxZ)
|
[](https://discord.gg/HYmhKj45pU)
|
||||||
[](https://github.com/Stirling-Tools/Stirling-PDF/)
|
[](https://github.com/Stirling-Tools/Stirling-PDF/)
|
||||||
[](https://github.com/Stirling-Tools/stirling-pdf)
|
[](https://github.com/Stirling-Tools/stirling-pdf)
|
||||||
|
|
||||||
|
@ -39,10 +39,7 @@ public class PasswordController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/remove-password")
|
@PostMapping(consumes = "multipart/form-data", value = "/remove-password")
|
||||||
@Operation(
|
@Operation(summary = "Remove password from a PDF file", description = "This endpoint removes the password from a protected PDF file. Users need to provide the existing password. Input:PDF Output:PDF Type:SISO")
|
||||||
summary = "Remove password from a PDF file",
|
|
||||||
description =
|
|
||||||
"This endpoint removes the password from a protected PDF file. Users need to provide the existing password. Input:PDF Output:PDF Type:SISO")
|
|
||||||
public ResponseEntity<byte[]> removePassword(@ModelAttribute PDFPasswordRequest request)
|
public ResponseEntity<byte[]> removePassword(@ModelAttribute PDFPasswordRequest request)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
MultipartFile fileInput = request.getFileInput();
|
MultipartFile fileInput = request.getFileInput();
|
||||||
@ -57,10 +54,7 @@ public class PasswordController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/add-password")
|
@PostMapping(consumes = "multipart/form-data", value = "/add-password")
|
||||||
@Operation(
|
@Operation(summary = "Add password to a PDF file", description = "This endpoint adds password protection to a PDF file. Users can specify a set of permissions that should be applied to the file. Input:PDF Output:PDF")
|
||||||
summary = "Add password to a PDF file",
|
|
||||||
description =
|
|
||||||
"This endpoint adds password protection to a PDF file. Users can specify a set of permissions that should be applied to the file. Input:PDF Output:PDF")
|
|
||||||
public ResponseEntity<byte[]> addPassword(@ModelAttribute AddPasswordRequest request)
|
public ResponseEntity<byte[]> addPassword(@ModelAttribute AddPasswordRequest request)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
MultipartFile fileInput = request.getFileInput();
|
MultipartFile fileInput = request.getFileInput();
|
||||||
|
@ -965,6 +965,16 @@ multiTool.dragDropMessage=Page(s) Selected
|
|||||||
multiTool.undo=Undo
|
multiTool.undo=Undo
|
||||||
multiTool.redo=Redo
|
multiTool.redo=Redo
|
||||||
|
|
||||||
|
#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
|
||||||
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
multiTool-advert.message=This feature is also available in our <a href="{0}">multi-tool page</a>. Check it out for enhanced page-by-page UI and additional features!
|
||||||
|
|
||||||
|
116
src/main/resources/static/js/DecryptFiles.js
Normal file
116
src/main/resources/static/js/DecryptFiles.js
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
export class DecryptFile {
|
||||||
|
async decryptFile(file, requiresPassword) {
|
||||||
|
try {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('fileInput', file);
|
||||||
|
if (requiresPassword) {
|
||||||
|
const password = prompt(`${window.decrypt.passwordPrompt}`);
|
||||||
|
|
||||||
|
if (password === null) {
|
||||||
|
// User cancelled
|
||||||
|
console.error(`Password prompt cancelled for PDF: ${file.name}`);
|
||||||
|
return null; // No file to return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!password) {
|
||||||
|
// No password provided
|
||||||
|
console.error(`No password provided for encrypted PDF: ${file.name}`);
|
||||||
|
this.showErrorBanner(
|
||||||
|
`${window.decrypt.noPassword.replace('{0}', file.name)}`,
|
||||||
|
'',
|
||||||
|
`${window.decrypt.unexpectedError}`
|
||||||
|
);
|
||||||
|
return null; // No file to return
|
||||||
|
}
|
||||||
|
|
||||||
|
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(`${window.decrypt.invalidPassword} ${errorText}`);
|
||||||
|
this.showErrorBanner(
|
||||||
|
`${window.decrypt.invalidPassword}`,
|
||||||
|
errorText,
|
||||||
|
`${window.decrypt.invalidPasswordHeader.replace('{0}', 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(
|
||||||
|
`${window.decrypt.unexpectedError.replace('{0}', file.name)}`,
|
||||||
|
`${error.message || window.decrypt.unexpectedError}`,
|
||||||
|
error
|
||||||
|
);
|
||||||
|
return null; // No file to return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async checkFileEncrypted(file) {
|
||||||
|
try {
|
||||||
|
if (file.type !== 'application/pdf') {
|
||||||
|
return {isEncrypted: false, requiresPassword: false};
|
||||||
|
}
|
||||||
|
|
||||||
|
pdfjsLib.GlobalWorkerOptions.workerSrc = './pdfjs-legacy/pdf.worker.mjs';
|
||||||
|
const arrayBuffer = await file.arrayBuffer();
|
||||||
|
const arrayBufferForPdfLib = arrayBuffer.slice(0);
|
||||||
|
|
||||||
|
const loadingTask = pdfjsLib.getDocument({
|
||||||
|
data: arrayBuffer,
|
||||||
|
});
|
||||||
|
|
||||||
|
await loadingTask.promise;
|
||||||
|
|
||||||
|
try {
|
||||||
|
//Uses PDFLib.PDFDocument to check if unpassworded but encrypted
|
||||||
|
const pdfDoc = await PDFLib.PDFDocument.load(arrayBufferForPdfLib);
|
||||||
|
return {isEncrypted: false, requiresPassword: false};
|
||||||
|
} catch (error) {
|
||||||
|
if (error.message.includes('Input document to `PDFDocument.load` is encrypted')) {
|
||||||
|
return {isEncrypted: true, requiresPassword: false};
|
||||||
|
}
|
||||||
|
console.error('Error checking encryption:', error);
|
||||||
|
throw new Error('Failed to determine if the file is encrypted.');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
if (error.name === 'PasswordException') {
|
||||||
|
if (error.code === pdfjsLib.PasswordResponses.NEED_PASSWORD) {
|
||||||
|
return {isEncrypted: true, requiresPassword: true};
|
||||||
|
} else if (error.code === pdfjsLib.PasswordResponses.INCORRECT_PASSWORD) {
|
||||||
|
return {isEncrypted: true, requiresPassword: false};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = '';
|
||||||
|
}
|
||||||
|
}
|
27
src/main/resources/static/js/download.js
Normal file
27
src/main/resources/static/js/download.js
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
document.getElementById('download-pdf').addEventListener('click', async () => {
|
||||||
|
const modifiedPdf = await DraggableUtils.getOverlayedPdfDocument();
|
||||||
|
let decryptedFile = modifiedPdf;
|
||||||
|
let isEncrypted = false;
|
||||||
|
let requiresPassword = false;
|
||||||
|
await this.decryptFile
|
||||||
|
.checkFileEncrypted(decryptedFile)
|
||||||
|
.then((result) => {
|
||||||
|
isEncrypted = result.isEncrypted;
|
||||||
|
requiresPassword = result.requiresPassword;
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error(error);
|
||||||
|
});
|
||||||
|
if (decryptedFile.type === 'application/pdf' && isEncrypted) {
|
||||||
|
decryptedFile = await this.decryptFile.decryptFile(decryptedFile, requiresPassword);
|
||||||
|
if (!decryptedFile) {
|
||||||
|
throw new Error('File decryption failed.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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 + '_signed.pdf';
|
||||||
|
link.click();
|
||||||
|
});
|
@ -42,7 +42,7 @@
|
|||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
firstErrorOccurred = false;
|
firstErrorOccurred = false;
|
||||||
const url = this.action;
|
const url = this.action;
|
||||||
const files = $('#fileInput-input')[0].files;
|
let files = $('#fileInput-input')[0].files;
|
||||||
const formData = new FormData(this);
|
const formData = new FormData(this);
|
||||||
const submitButton = document.getElementById('submitBtn');
|
const submitButton = document.getElementById('submitBtn');
|
||||||
const showGameBtn = document.getElementById('show-game-btn');
|
const showGameBtn = document.getElementById('show-game-btn');
|
||||||
@ -71,6 +71,16 @@
|
|||||||
}, 5000);
|
}, 5000);
|
||||||
|
|
||||||
try {
|
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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
submitButton.textContent = 'Processing...';
|
submitButton.textContent = 'Processing...';
|
||||||
submitButton.disabled = true;
|
submitButton.disabled = true;
|
||||||
|
|
||||||
@ -133,6 +143,98 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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(`${window.translations.decrypt.passwordPrompt}`);
|
||||||
|
|
||||||
|
if (!password) {
|
||||||
|
console.error(`No password provided for encrypted PDF: ${file.name}`);
|
||||||
|
showErrorBanner(
|
||||||
|
`${window.translations.decrypt.noPassword.replace('{0}', file.name)}`,
|
||||||
|
`${window.translations.decrypt.unexpectedError}`
|
||||||
|
);
|
||||||
|
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(
|
||||||
|
`${window.translations.invalidPasswordHeader.replace('{0}', file.name)}`,
|
||||||
|
`${window.translations.invalidPassword}`
|
||||||
|
);
|
||||||
|
throw decryptError;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log(`Error loading PDF: ${file.name}`, error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return decryptedFiles;
|
||||||
|
}
|
||||||
|
|
||||||
async function handleSingleDownload(url, formData, isMulti = false, isZip = false) {
|
async function handleSingleDownload(url, formData, isMulti = false, isZip = false) {
|
||||||
const startTime = performance.now();
|
const startTime = performance.now();
|
||||||
const file = formData.get('fileInput');
|
const file = formData.get('fileInput');
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
const DraggableUtils = {
|
const DraggableUtils = {
|
||||||
boxDragContainer: document.getElementById("box-drag-container"),
|
boxDragContainer: document.getElementById('box-drag-container'),
|
||||||
pdfCanvas: document.getElementById("pdf-canvas"),
|
pdfCanvas: document.getElementById('pdf-canvas'),
|
||||||
nextId: 0,
|
nextId: 0,
|
||||||
pdfDoc: null,
|
pdfDoc: null,
|
||||||
pageIndex: 0,
|
pageIndex: 0,
|
||||||
@ -9,19 +9,17 @@ const DraggableUtils = {
|
|||||||
lastInteracted: null,
|
lastInteracted: null,
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
interact(".draggable-canvas")
|
interact('.draggable-canvas')
|
||||||
.draggable({
|
.draggable({
|
||||||
listeners: {
|
listeners: {
|
||||||
move: (event) => {
|
move: (event) => {
|
||||||
const target = event.target;
|
const target = event.target;
|
||||||
const x = (parseFloat(target.getAttribute("data-bs-x")) || 0)
|
const x = (parseFloat(target.getAttribute('data-bs-x')) || 0) + event.dx;
|
||||||
+ event.dx;
|
const y = (parseFloat(target.getAttribute('data-bs-y')) || 0) + event.dy;
|
||||||
const y = (parseFloat(target.getAttribute("data-bs-y")) || 0)
|
|
||||||
+ event.dy;
|
|
||||||
|
|
||||||
target.style.transform = `translate(${x}px, ${y}px)`;
|
target.style.transform = `translate(${x}px, ${y}px)`;
|
||||||
target.setAttribute("data-bs-x", x);
|
target.setAttribute('data-bs-x', x);
|
||||||
target.setAttribute("data-bs-y", y);
|
target.setAttribute('data-bs-y', y);
|
||||||
|
|
||||||
this.onInteraction(target);
|
this.onInteraction(target);
|
||||||
//update the last interacted element
|
//update the last interacted element
|
||||||
@ -30,12 +28,12 @@ const DraggableUtils = {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
.resizable({
|
.resizable({
|
||||||
edges: { left: true, right: true, bottom: true, top: true },
|
edges: {left: true, right: true, bottom: true, top: true},
|
||||||
listeners: {
|
listeners: {
|
||||||
move: (event) => {
|
move: (event) => {
|
||||||
var target = event.target;
|
var target = event.target;
|
||||||
var x = parseFloat(target.getAttribute("data-bs-x")) || 0;
|
var x = parseFloat(target.getAttribute('data-bs-x')) || 0;
|
||||||
var y = parseFloat(target.getAttribute("data-bs-y")) || 0;
|
var y = parseFloat(target.getAttribute('data-bs-y')) || 0;
|
||||||
|
|
||||||
// check if control key is pressed
|
// check if control key is pressed
|
||||||
if (event.ctrlKey) {
|
if (event.ctrlKey) {
|
||||||
@ -44,8 +42,7 @@ const DraggableUtils = {
|
|||||||
let width = event.rect.width;
|
let width = event.rect.width;
|
||||||
let height = event.rect.height;
|
let height = event.rect.height;
|
||||||
|
|
||||||
if (Math.abs(event.deltaRect.width) >= Math.abs(
|
if (Math.abs(event.deltaRect.width) >= Math.abs(event.deltaRect.height)) {
|
||||||
event.deltaRect.height)) {
|
|
||||||
height = width / aspectRatio;
|
height = width / aspectRatio;
|
||||||
} else {
|
} else {
|
||||||
width = height * aspectRatio;
|
width = height * aspectRatio;
|
||||||
@ -55,19 +52,18 @@ const DraggableUtils = {
|
|||||||
event.rect.height = height;
|
event.rect.height = height;
|
||||||
}
|
}
|
||||||
|
|
||||||
target.style.width = event.rect.width + "px";
|
target.style.width = event.rect.width + 'px';
|
||||||
target.style.height = event.rect.height + "px";
|
target.style.height = event.rect.height + 'px';
|
||||||
|
|
||||||
// translate when resizing from top or left edges
|
// translate when resizing from top or left edges
|
||||||
x += event.deltaRect.left;
|
x += event.deltaRect.left;
|
||||||
y += event.deltaRect.top;
|
y += event.deltaRect.top;
|
||||||
|
|
||||||
target.style.transform = "translate(" + x + "px," + y + "px)";
|
target.style.transform = 'translate(' + x + 'px,' + y + 'px)';
|
||||||
|
|
||||||
target.setAttribute("data-bs-x", x);
|
target.setAttribute('data-bs-x', x);
|
||||||
target.setAttribute("data-bs-y", y);
|
target.setAttribute('data-bs-y', y);
|
||||||
target.textContent = Math.round(event.rect.width) + "\u00D7"
|
target.textContent = Math.round(event.rect.width) + '\u00D7' + Math.round(event.rect.height);
|
||||||
+ Math.round(event.rect.height);
|
|
||||||
|
|
||||||
this.onInteraction(target);
|
this.onInteraction(target);
|
||||||
},
|
},
|
||||||
@ -75,7 +71,7 @@ const DraggableUtils = {
|
|||||||
|
|
||||||
modifiers: [
|
modifiers: [
|
||||||
interact.modifiers.restrictSize({
|
interact.modifiers.restrictSize({
|
||||||
min: { width: 5, height: 5 },
|
min: {width: 5, height: 5},
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
inertia: true,
|
inertia: true,
|
||||||
@ -95,8 +91,8 @@ const DraggableUtils = {
|
|||||||
const stepY = target.offsetHeight * 0.05;
|
const stepY = target.offsetHeight * 0.05;
|
||||||
|
|
||||||
// Get the current x and y coordinates
|
// Get the current x and y coordinates
|
||||||
let x = (parseFloat(target.getAttribute('data-bs-x')) || 0);
|
let x = parseFloat(target.getAttribute('data-bs-x')) || 0;
|
||||||
let y = (parseFloat(target.getAttribute('data-bs-y')) || 0);
|
let y = parseFloat(target.getAttribute('data-bs-y')) || 0;
|
||||||
|
|
||||||
// Check which key was pressed and update the coordinates accordingly
|
// Check which key was pressed and update the coordinates accordingly
|
||||||
switch (event.key) {
|
switch (event.key) {
|
||||||
@ -135,15 +131,15 @@ const DraggableUtils = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
createDraggableCanvas() {
|
createDraggableCanvas() {
|
||||||
const createdCanvas = document.createElement("canvas");
|
const createdCanvas = document.createElement('canvas');
|
||||||
createdCanvas.id = `draggable-canvas-${this.nextId++}`;
|
createdCanvas.id = `draggable-canvas-${this.nextId++}`;
|
||||||
createdCanvas.classList.add("draggable-canvas");
|
createdCanvas.classList.add('draggable-canvas');
|
||||||
|
|
||||||
const x = 0;
|
const x = 0;
|
||||||
const y = 20;
|
const y = 20;
|
||||||
createdCanvas.style.transform = `translate(${x}px, ${y}px)`;
|
createdCanvas.style.transform = `translate(${x}px, ${y}px)`;
|
||||||
createdCanvas.setAttribute("data-bs-x", x);
|
createdCanvas.setAttribute('data-bs-x', x);
|
||||||
createdCanvas.setAttribute("data-bs-y", y);
|
createdCanvas.setAttribute('data-bs-y', y);
|
||||||
|
|
||||||
//Click element in order to enable arrow keys
|
//Click element in order to enable arrow keys
|
||||||
createdCanvas.addEventListener('click', () => {
|
createdCanvas.addEventListener('click', () => {
|
||||||
@ -186,29 +182,29 @@ const DraggableUtils = {
|
|||||||
newHeight = newHeight * scaleMultiplier;
|
newHeight = newHeight * scaleMultiplier;
|
||||||
}
|
}
|
||||||
|
|
||||||
createdCanvas.style.width = newWidth + "px";
|
createdCanvas.style.width = newWidth + 'px';
|
||||||
createdCanvas.style.height = newHeight + "px";
|
createdCanvas.style.height = newHeight + 'px';
|
||||||
|
|
||||||
var myContext = createdCanvas.getContext("2d");
|
var myContext = createdCanvas.getContext('2d');
|
||||||
myContext.drawImage(myImage, 0, 0);
|
myContext.drawImage(myImage, 0, 0);
|
||||||
resolve(createdCanvas);
|
resolve(createdCanvas);
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
deleteAllDraggableCanvases() {
|
deleteAllDraggableCanvases() {
|
||||||
this.boxDragContainer.querySelectorAll(".draggable-canvas").forEach((el) => el.remove());
|
this.boxDragContainer.querySelectorAll('.draggable-canvas').forEach((el) => el.remove());
|
||||||
},
|
},
|
||||||
async addAllPagesDraggableCanvas(element) {
|
async addAllPagesDraggableCanvas(element) {
|
||||||
if (element) {
|
if (element) {
|
||||||
let currentPage = this.pageIndex
|
let currentPage = this.pageIndex;
|
||||||
if (!this.elementAllPages.includes(element)) {
|
if (!this.elementAllPages.includes(element)) {
|
||||||
this.elementAllPages.push(element)
|
this.elementAllPages.push(element);
|
||||||
element.style.filter = 'sepia(1) hue-rotate(90deg) brightness(1.2)';
|
element.style.filter = 'sepia(1) hue-rotate(90deg) brightness(1.2)';
|
||||||
let newElement = {
|
let newElement = {
|
||||||
"element": element,
|
element: element,
|
||||||
"offsetWidth": element.width,
|
offsetWidth: element.width,
|
||||||
"offsetHeight": element.height
|
offsetHeight: element.height,
|
||||||
}
|
};
|
||||||
|
|
||||||
let pagesMap = this.documentsMap.get(this.pdfDoc);
|
let pagesMap = this.documentsMap.get(this.pdfDoc);
|
||||||
|
|
||||||
@ -216,21 +212,20 @@ const DraggableUtils = {
|
|||||||
pagesMap = {};
|
pagesMap = {};
|
||||||
this.documentsMap.set(this.pdfDoc, pagesMap);
|
this.documentsMap.set(this.pdfDoc, pagesMap);
|
||||||
}
|
}
|
||||||
let page = this.pageIndex
|
let page = this.pageIndex;
|
||||||
|
|
||||||
for (let pageIndex = 0; pageIndex < this.pdfDoc.numPages; pageIndex++) {
|
for (let pageIndex = 0; pageIndex < this.pdfDoc.numPages; pageIndex++) {
|
||||||
|
|
||||||
if (pagesMap[`${pageIndex}-offsetWidth`]) {
|
if (pagesMap[`${pageIndex}-offsetWidth`]) {
|
||||||
if (!pagesMap[pageIndex].includes(newElement)) {
|
if (!pagesMap[pageIndex].includes(newElement)) {
|
||||||
pagesMap[pageIndex].push(newElement);
|
pagesMap[pageIndex].push(newElement);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
pagesMap[pageIndex] = []
|
pagesMap[pageIndex] = [];
|
||||||
pagesMap[pageIndex].push(newElement)
|
pagesMap[pageIndex].push(newElement);
|
||||||
pagesMap[`${pageIndex}-offsetWidth`] = pagesMap[`${page}-offsetWidth`];
|
pagesMap[`${pageIndex}-offsetWidth`] = pagesMap[`${page}-offsetWidth`];
|
||||||
pagesMap[`${pageIndex}-offsetHeight`] = pagesMap[`${page}-offsetHeight`];
|
pagesMap[`${pageIndex}-offsetHeight`] = pagesMap[`${page}-offsetHeight`];
|
||||||
}
|
}
|
||||||
await this.goToPage(pageIndex)
|
await this.goToPage(pageIndex);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const index = this.elementAllPages.indexOf(element);
|
const index = this.elementAllPages.indexOf(element);
|
||||||
@ -247,17 +242,17 @@ const DraggableUtils = {
|
|||||||
for (let pageIndex = 0; pageIndex < this.pdfDoc.numPages; pageIndex++) {
|
for (let pageIndex = 0; pageIndex < this.pdfDoc.numPages; pageIndex++) {
|
||||||
if (pagesMap[`${pageIndex}-offsetWidth`] && pageIndex != currentPage) {
|
if (pagesMap[`${pageIndex}-offsetWidth`] && pageIndex != currentPage) {
|
||||||
const pageElements = pagesMap[pageIndex];
|
const pageElements = pagesMap[pageIndex];
|
||||||
pageElements.forEach(elementPage => {
|
pageElements.forEach((elementPage) => {
|
||||||
const elementIndex = pageElements.findIndex(elementPage => elementPage['element'].id === element.id);
|
const elementIndex = pageElements.findIndex((elementPage) => elementPage['element'].id === element.id);
|
||||||
if (elementIndex !== -1) {
|
if (elementIndex !== -1) {
|
||||||
pageElements.splice(elementIndex, 1);
|
pageElements.splice(elementIndex, 1);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
await this.goToPage(pageIndex)
|
await this.goToPage(pageIndex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await this.goToPage(currentPage)
|
await this.goToPage(currentPage);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
deleteDraggableCanvas(element) {
|
deleteDraggableCanvas(element) {
|
||||||
@ -271,7 +266,7 @@ const DraggableUtils = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
getLastInteracted() {
|
getLastInteracted() {
|
||||||
return this.boxDragContainer.querySelector(".draggable-canvas:last-of-type");
|
return this.boxDragContainer.querySelector('.draggable-canvas:last-of-type');
|
||||||
},
|
},
|
||||||
|
|
||||||
storePageContents() {
|
storePageContents() {
|
||||||
@ -280,7 +275,7 @@ const DraggableUtils = {
|
|||||||
pagesMap = {};
|
pagesMap = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
const elements = [...this.boxDragContainer.querySelectorAll(".draggable-canvas")];
|
const elements = [...this.boxDragContainer.querySelectorAll('.draggable-canvas')];
|
||||||
const draggablesData = elements.map((el) => {
|
const draggablesData = elements.map((el) => {
|
||||||
return {
|
return {
|
||||||
element: el,
|
element: el,
|
||||||
@ -291,8 +286,8 @@ const DraggableUtils = {
|
|||||||
elements.forEach((el) => this.boxDragContainer.removeChild(el));
|
elements.forEach((el) => this.boxDragContainer.removeChild(el));
|
||||||
|
|
||||||
pagesMap[this.pageIndex] = draggablesData;
|
pagesMap[this.pageIndex] = draggablesData;
|
||||||
pagesMap[this.pageIndex + "-offsetWidth"] = this.pdfCanvas.offsetWidth;
|
pagesMap[this.pageIndex + '-offsetWidth'] = this.pdfCanvas.offsetWidth;
|
||||||
pagesMap[this.pageIndex + "-offsetHeight"] = this.pdfCanvas.offsetHeight;
|
pagesMap[this.pageIndex + '-offsetHeight'] = this.pdfCanvas.offsetHeight;
|
||||||
|
|
||||||
this.documentsMap.set(this.pdfDoc, pagesMap);
|
this.documentsMap.set(this.pdfDoc, pagesMap);
|
||||||
},
|
},
|
||||||
@ -329,8 +324,8 @@ const DraggableUtils = {
|
|||||||
|
|
||||||
// render the page onto the canvas
|
// render the page onto the canvas
|
||||||
var renderContext = {
|
var renderContext = {
|
||||||
canvasContext: this.pdfCanvas.getContext("2d"),
|
canvasContext: this.pdfCanvas.getContext('2d'),
|
||||||
viewport: page.getViewport({ scale: 1 }),
|
viewport: page.getViewport({scale: 1}),
|
||||||
};
|
};
|
||||||
await page.render(renderContext).promise;
|
await page.render(renderContext).promise;
|
||||||
|
|
||||||
@ -358,7 +353,7 @@ const DraggableUtils = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
parseTransform(element) { },
|
parseTransform(element) {},
|
||||||
async getOverlayedPdfDocument() {
|
async getOverlayedPdfDocument() {
|
||||||
const pdfBytes = await this.pdfDoc.getData();
|
const pdfBytes = await this.pdfDoc.getData();
|
||||||
const pdfDocModified = await PDFLib.PDFDocument.load(pdfBytes, {
|
const pdfDocModified = await PDFLib.PDFDocument.load(pdfBytes, {
|
||||||
@ -369,7 +364,7 @@ const DraggableUtils = {
|
|||||||
const pagesMap = this.documentsMap.get(this.pdfDoc);
|
const pagesMap = this.documentsMap.get(this.pdfDoc);
|
||||||
|
|
||||||
for (let pageIdx in pagesMap) {
|
for (let pageIdx in pagesMap) {
|
||||||
if (pageIdx.includes("offset")) {
|
if (pageIdx.includes('offset')) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
console.log(typeof pageIdx);
|
console.log(typeof pageIdx);
|
||||||
@ -377,9 +372,8 @@ const DraggableUtils = {
|
|||||||
const page = pdfDocModified.getPage(parseInt(pageIdx));
|
const page = pdfDocModified.getPage(parseInt(pageIdx));
|
||||||
let draggablesData = pagesMap[pageIdx];
|
let draggablesData = pagesMap[pageIdx];
|
||||||
|
|
||||||
const offsetWidth = pagesMap[pageIdx + "-offsetWidth"];
|
const offsetWidth = pagesMap[pageIdx + '-offsetWidth'];
|
||||||
const offsetHeight = pagesMap[pageIdx + "-offsetHeight"];
|
const offsetHeight = pagesMap[pageIdx + '-offsetHeight'];
|
||||||
|
|
||||||
|
|
||||||
for (const draggableData of draggablesData) {
|
for (const draggableData of draggablesData) {
|
||||||
// embed the draggable canvas
|
// embed the draggable canvas
|
||||||
@ -389,8 +383,8 @@ const DraggableUtils = {
|
|||||||
const pdfImageObject = await pdfDocModified.embedPng(draggableImgBytes);
|
const pdfImageObject = await pdfDocModified.embedPng(draggableImgBytes);
|
||||||
|
|
||||||
// calculate the position in the pdf document
|
// calculate the position in the pdf document
|
||||||
const tansform = draggableElement.style.transform.replace(/[^.,-\d]/g, "");
|
const tansform = draggableElement.style.transform.replace(/[^.,-\d]/g, '');
|
||||||
const transformComponents = tansform.split(",");
|
const transformComponents = tansform.split(',');
|
||||||
const draggablePositionPixels = {
|
const draggablePositionPixels = {
|
||||||
x: parseFloat(transformComponents[0]),
|
x: parseFloat(transformComponents[0]),
|
||||||
y: parseFloat(transformComponents[1]),
|
y: parseFloat(transformComponents[1]),
|
||||||
@ -429,9 +423,8 @@ const DraggableUtils = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
//Defining the image if the page has a 0-degree angle
|
//Defining the image if the page has a 0-degree angle
|
||||||
let x = draggablePositionPdf.x
|
let x = draggablePositionPdf.x;
|
||||||
let y = heightAdjusted - draggablePositionPdf.y - draggablePositionPdf.height
|
let y = heightAdjusted - draggablePositionPdf.y - draggablePositionPdf.height;
|
||||||
|
|
||||||
|
|
||||||
//Defining the image position if it is at other angles
|
//Defining the image position if it is at other angles
|
||||||
if (normalizedAngle === 90) {
|
if (normalizedAngle === 90) {
|
||||||
@ -451,7 +444,7 @@ const DraggableUtils = {
|
|||||||
y: y,
|
y: y,
|
||||||
width: draggablePositionPdf.width,
|
width: draggablePositionPdf.width,
|
||||||
height: draggablePositionPdf.height,
|
height: draggablePositionPdf.height,
|
||||||
rotate: rotation
|
rotate: rotation,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -460,6 +453,6 @@ const DraggableUtils = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
DraggableUtils.init();
|
DraggableUtils.init();
|
||||||
});
|
});
|
||||||
|
@ -1,20 +1,19 @@
|
|||||||
import FileIconFactory from "./file-icon-factory.js";
|
import FileIconFactory from './file-icon-factory.js';
|
||||||
import FileUtils from "./file-utils.js";
|
import FileUtils from './file-utils.js';
|
||||||
import UUID from './uuid.js';
|
import UUID from './uuid.js';
|
||||||
|
import {DecryptFile} from './DecryptFiles.js';
|
||||||
let isScriptExecuted = false;
|
let isScriptExecuted = false;
|
||||||
if (!isScriptExecuted) {
|
if (!isScriptExecuted) {
|
||||||
isScriptExecuted = true;
|
isScriptExecuted = true;
|
||||||
document.addEventListener("DOMContentLoaded", function () {
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
document.querySelectorAll(".custom-file-chooser").forEach(setupFileInput);
|
document.querySelectorAll('.custom-file-chooser').forEach(setupFileInput);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function setupFileInput(chooser) {
|
function setupFileInput(chooser) {
|
||||||
const elementId = chooser.getAttribute("data-bs-element-id");
|
const elementId = chooser.getAttribute('data-bs-element-id');
|
||||||
const filesSelected = chooser.getAttribute("data-bs-files-selected");
|
const filesSelected = chooser.getAttribute('data-bs-files-selected');
|
||||||
const pdfPrompt = chooser.getAttribute("data-bs-pdf-prompt");
|
const pdfPrompt = chooser.getAttribute('data-bs-pdf-prompt');
|
||||||
const inputContainerId = chooser.getAttribute('data-bs-element-container-id');
|
const inputContainerId = chooser.getAttribute('data-bs-element-container-id');
|
||||||
|
|
||||||
let inputContainer = document.getElementById(inputContainerId);
|
let inputContainer = document.getElementById(inputContainerId);
|
||||||
@ -26,7 +25,7 @@ function setupFileInput(chooser) {
|
|||||||
inputContainer.addEventListener('click', (e) => {
|
inputContainer.addEventListener('click', (e) => {
|
||||||
let inputBtn = document.getElementById(elementId);
|
let inputBtn = document.getElementById(elementId);
|
||||||
inputBtn.click();
|
inputBtn.click();
|
||||||
})
|
});
|
||||||
|
|
||||||
const dragenterListener = function () {
|
const dragenterListener = function () {
|
||||||
dragCounter++;
|
dragCounter++;
|
||||||
@ -63,7 +62,7 @@ function setupFileInput(chooser) {
|
|||||||
const files = dt.files;
|
const files = dt.files;
|
||||||
|
|
||||||
const fileInput = document.getElementById(elementId);
|
const fileInput = document.getElementById(elementId);
|
||||||
if (fileInput?.hasAttribute("multiple")) {
|
if (fileInput?.hasAttribute('multiple')) {
|
||||||
pushFileListTo(files, allFiles);
|
pushFileListTo(files, allFiles);
|
||||||
} else if (fileInput) {
|
} else if (fileInput) {
|
||||||
allFiles = [files[0]];
|
allFiles = [files[0]];
|
||||||
@ -78,7 +77,7 @@ function setupFileInput(chooser) {
|
|||||||
|
|
||||||
dragCounter = 0;
|
dragCounter = 0;
|
||||||
|
|
||||||
fileInput.dispatchEvent(new CustomEvent("change", { bubbles: true, detail: {source: 'drag-drop'} }));
|
fileInput.dispatchEvent(new CustomEvent('change', {bubbles: true, detail: {source: 'drag-drop'}}));
|
||||||
};
|
};
|
||||||
|
|
||||||
function pushFileListTo(fileList, container) {
|
function pushFileListTo(fileList, container) {
|
||||||
@ -87,7 +86,7 @@ function setupFileInput(chooser) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
["dragenter", "dragover", "dragleave", "drop"].forEach((eventName) => {
|
['dragenter', 'dragover', 'dragleave', 'drop'].forEach((eventName) => {
|
||||||
document.body.addEventListener(eventName, preventDefaults, false);
|
document.body.addEventListener(eventName, preventDefaults, false);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -96,37 +95,50 @@ function setupFileInput(chooser) {
|
|||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
}
|
}
|
||||||
|
|
||||||
document.body.addEventListener("dragenter", dragenterListener);
|
document.body.addEventListener('dragenter', dragenterListener);
|
||||||
document.body.addEventListener("dragleave", dragleaveListener);
|
document.body.addEventListener('dragleave', dragleaveListener);
|
||||||
document.body.addEventListener("drop", dropListener);
|
document.body.addEventListener('drop', dropListener);
|
||||||
|
|
||||||
$("#" + elementId).on("change", function (e) {
|
$('#' + elementId).on('change', async function (e) {
|
||||||
let element = e.target;
|
let element = e.target;
|
||||||
const isDragAndDrop = e.detail?.source == 'drag-drop';
|
const isDragAndDrop = e.detail?.source == 'drag-drop';
|
||||||
|
|
||||||
if (element instanceof HTMLInputElement && element.hasAttribute("multiple")) {
|
if (element instanceof HTMLInputElement && element.hasAttribute('multiple')) {
|
||||||
allFiles = isDragAndDrop ? allFiles : [... allFiles, ... element.files];
|
allFiles = isDragAndDrop ? allFiles : [...allFiles, ...element.files];
|
||||||
} else {
|
} else {
|
||||||
allFiles = Array.from(isDragAndDrop ? allFiles : [element.files[0]]);
|
allFiles = Array.from(isDragAndDrop ? allFiles : [element.files[0]]);
|
||||||
}
|
}
|
||||||
|
allFiles = await Promise.all(
|
||||||
allFiles = allFiles.map(file => {
|
allFiles.map(async (file) => {
|
||||||
|
let decryptedFile = file;
|
||||||
|
try {
|
||||||
|
const decryptFile = new DecryptFile();
|
||||||
|
const {isEncrypted, requiresPassword} = await decryptFile.checkFileEncrypted(file);
|
||||||
|
if (file.type === 'application/pdf' && isEncrypted) {
|
||||||
|
decryptedFile = await decryptFile.decryptFile(file, requiresPassword);
|
||||||
|
if (!decryptedFile) throw new Error('File decryption failed.');
|
||||||
|
}
|
||||||
|
decryptedFile.uniqueId = UUID.uuidv4();
|
||||||
|
return decryptedFile;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error decrypting file: ${file.name}`, error);
|
||||||
if (!file.uniqueId) file.uniqueId = UUID.uuidv4();
|
if (!file.uniqueId) file.uniqueId = UUID.uuidv4();
|
||||||
return file;
|
return file;
|
||||||
});
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
if (!isDragAndDrop) {
|
if (!isDragAndDrop) {
|
||||||
let dataTransfer = toDataTransfer(allFiles);
|
let dataTransfer = toDataTransfer(allFiles);
|
||||||
element.files = dataTransfer.files;
|
element.files = dataTransfer.files;
|
||||||
}
|
}
|
||||||
|
|
||||||
handleFileInputChange(this);
|
handleFileInputChange(this);
|
||||||
this.dispatchEvent(new CustomEvent("file-input-change", { bubbles: true }));
|
this.dispatchEvent(new CustomEvent('file-input-change', {bubbles: true, detail: {elementId, allFiles}}));
|
||||||
});
|
});
|
||||||
|
|
||||||
function toDataTransfer(files) {
|
function toDataTransfer(files) {
|
||||||
let dataTransfer = new DataTransfer();
|
let dataTransfer = new DataTransfer();
|
||||||
files.forEach(file => dataTransfer.items.add(file));
|
files.forEach((file) => dataTransfer.items.add(file));
|
||||||
return dataTransfer;
|
return dataTransfer;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -136,7 +148,7 @@ function setupFileInput(chooser) {
|
|||||||
|
|
||||||
const filesInfo = files.map((f) => ({name: f.name, size: f.size, uniqueId: f.uniqueId}));
|
const filesInfo = files.map((f) => ({name: f.name, size: f.size, uniqueId: f.uniqueId}));
|
||||||
|
|
||||||
const selectedFilesContainer = $(inputContainer).siblings(".selected-files");
|
const selectedFilesContainer = $(inputContainer).siblings('.selected-files');
|
||||||
selectedFilesContainer.empty();
|
selectedFilesContainer.empty();
|
||||||
filesInfo.forEach((info) => {
|
filesInfo.forEach((info) => {
|
||||||
let fileContainerClasses = 'small-file-container d-flex flex-column justify-content-center align-items-center';
|
let fileContainerClasses = 'small-file-container d-flex flex-column justify-content-center align-items-center';
|
||||||
@ -167,28 +179,26 @@ function setupFileInput(chooser) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function showOrHideSelectedFilesContainer(files) {
|
function showOrHideSelectedFilesContainer(files) {
|
||||||
if (files && files.length > 0)
|
if (files && files.length > 0) chooser.style.setProperty('--selected-files-display', 'flex');
|
||||||
chooser.style.setProperty('--selected-files-display', 'flex');
|
else chooser.style.setProperty('--selected-files-display', 'none');
|
||||||
else
|
|
||||||
chooser.style.setProperty('--selected-files-display', 'none');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeFileListener(e) {
|
function removeFileListener(e) {
|
||||||
const fileId = (e.target).getAttribute('data-file-id');
|
const fileId = e.target.getAttribute('data-file-id');
|
||||||
|
|
||||||
let inputElement = document.getElementById(elementId);
|
let inputElement = document.getElementById(elementId);
|
||||||
removeFileById(fileId, inputElement);
|
removeFileById(fileId, inputElement);
|
||||||
|
|
||||||
showOrHideSelectedFilesContainer(allFiles);
|
showOrHideSelectedFilesContainer(allFiles);
|
||||||
|
|
||||||
inputElement.dispatchEvent(new CustomEvent("file-input-change", { bubbles: true }));
|
inputElement.dispatchEvent(new CustomEvent('file-input-change', {bubbles: true}));
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeFileById(fileId, inputElement) {
|
function removeFileById(fileId, inputElement) {
|
||||||
let fileContainer = document.getElementById(fileId);
|
let fileContainer = document.getElementById(fileId);
|
||||||
fileContainer.remove();
|
fileContainer.remove();
|
||||||
|
|
||||||
allFiles = allFiles.filter(v => v.uniqueId != fileId);
|
allFiles = allFiles.filter((v) => v.uniqueId != fileId);
|
||||||
let dataTransfer = toDataTransfer(allFiles);
|
let dataTransfer = toDataTransfer(allFiles);
|
||||||
|
|
||||||
if (inputElement) inputElement.files = dataTransfer.files;
|
if (inputElement) inputElement.files = dataTransfer.files;
|
||||||
@ -207,23 +217,19 @@ function setupFileInput(chooser) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function createFileInfoContainer(info) {
|
function createFileInfoContainer(info) {
|
||||||
let fileInfoContainer = document.createElement("div");
|
let fileInfoContainer = document.createElement('div');
|
||||||
let fileInfoContainerClasses = 'file-info d-flex flex-column align-items-center justify-content-center';
|
let fileInfoContainerClasses = 'file-info d-flex flex-column align-items-center justify-content-center';
|
||||||
|
|
||||||
$(fileInfoContainer).addClass(fileInfoContainerClasses);
|
$(fileInfoContainer).addClass(fileInfoContainerClasses);
|
||||||
|
|
||||||
$(fileInfoContainer).append(
|
$(fileInfoContainer).append(`<div title="${info.name}">${info.name}</div>`);
|
||||||
`<div title="${info.name}">${info.name}</div>`
|
|
||||||
);
|
|
||||||
let fileSizeWithUnits = FileUtils.transformFileSize(info.size);
|
let fileSizeWithUnits = FileUtils.transformFileSize(info.size);
|
||||||
$(fileInfoContainer).append(
|
$(fileInfoContainer).append(`<div title="${info.size}">${fileSizeWithUnits}</div>`);
|
||||||
`<div title="${info.size}">${fileSizeWithUnits}</div>`
|
|
||||||
);
|
|
||||||
return fileInfoContainer;
|
return fileInfoContainer;
|
||||||
}
|
}
|
||||||
|
|
||||||
//Listen for event of file being removed and the filter it out of the allFiles array
|
//Listen for event of file being removed and the filter it out of the allFiles array
|
||||||
document.addEventListener("fileRemoved", function (e) {
|
document.addEventListener('fileRemoved', function (e) {
|
||||||
const fileId = e.detail;
|
const fileId = e.detail;
|
||||||
let inputElement = document.getElementById(elementId);
|
let inputElement = document.getElementById(elementId);
|
||||||
removeFileById(fileId, inputElement);
|
removeFileById(fileId, inputElement);
|
||||||
|
@ -5,6 +5,7 @@ import {SplitAllCommand} from './commands/split.js';
|
|||||||
import {UndoManager} from './UndoManager.js';
|
import {UndoManager} from './UndoManager.js';
|
||||||
import {PageBreakCommand} from './commands/page-break.js';
|
import {PageBreakCommand} from './commands/page-break.js';
|
||||||
import {AddFilesCommand} from './commands/add-page.js';
|
import {AddFilesCommand} from './commands/add-page.js';
|
||||||
|
import {DecryptFile} from '../DecryptFiles.js';
|
||||||
|
|
||||||
class PdfContainer {
|
class PdfContainer {
|
||||||
fileName;
|
fileName;
|
||||||
@ -40,6 +41,8 @@ class PdfContainer {
|
|||||||
this.removeAllElements = this.removeAllElements.bind(this);
|
this.removeAllElements = this.removeAllElements.bind(this);
|
||||||
this.resetPages = this.resetPages.bind(this);
|
this.resetPages = this.resetPages.bind(this);
|
||||||
|
|
||||||
|
this.decryptFile = new DecryptFile();
|
||||||
|
|
||||||
this.undoManager = undoManager || new UndoManager();
|
this.undoManager = undoManager || new UndoManager();
|
||||||
|
|
||||||
this.pdfAdapters = pdfAdapters;
|
this.pdfAdapters = pdfAdapters;
|
||||||
@ -165,7 +168,6 @@ class PdfContainer {
|
|||||||
input.click();
|
input.click();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async addFilesFromFiles(files, nextSiblingElement, pages) {
|
async addFilesFromFiles(files, nextSiblingElement, pages) {
|
||||||
this.fileName = files[0].name;
|
this.fileName = files[0].name;
|
||||||
for (var i = 0; i < files.length; i++) {
|
for (var i = 0; i < files.length; i++) {
|
||||||
@ -173,17 +175,37 @@ class PdfContainer {
|
|||||||
let processingTime,
|
let processingTime,
|
||||||
errorMessage = null,
|
errorMessage = null,
|
||||||
pageCount = 0;
|
pageCount = 0;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const file = files[i];
|
let decryptedFile = files[i];
|
||||||
if (file.type === 'application/pdf') {
|
let isEncrypted = false;
|
||||||
const {renderer, pdfDocument} = await this.loadFile(file);
|
let requiresPassword = false;
|
||||||
|
await this.decryptFile
|
||||||
|
.checkFileEncrypted(decryptedFile)
|
||||||
|
.then((result) => {
|
||||||
|
isEncrypted = result.isEncrypted;
|
||||||
|
requiresPassword = result.requiresPassword;
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error(error);
|
||||||
|
});
|
||||||
|
if (decryptedFile.type === 'application/pdf' && isEncrypted) {
|
||||||
|
decryptedFile = await this.decryptFile.decryptFile(decryptedFile, requiresPassword);
|
||||||
|
if (!decryptedFile) {
|
||||||
|
throw new Error('File decryption failed.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (decryptedFile.type === 'application/pdf') {
|
||||||
|
const {renderer, pdfDocument} = await this.loadFile(decryptedFile);
|
||||||
pageCount = renderer.pageCount || 0;
|
pageCount = renderer.pageCount || 0;
|
||||||
pages = await this.addPdfFile(renderer, pdfDocument, nextSiblingElement, pages);
|
pages = await this.addPdfFile(renderer, pdfDocument, nextSiblingElement, pages);
|
||||||
} else if (file.type.startsWith('image/')) {
|
} else if (decryptedFile.type.startsWith('image/')) {
|
||||||
pages = await this.addImageFile(file, nextSiblingElement, pages);
|
pages = await this.addImageFile(decryptedFile, nextSiblingElement, pages);
|
||||||
}
|
}
|
||||||
|
|
||||||
processingTime = Date.now() - startTime;
|
processingTime = Date.now() - startTime;
|
||||||
this.captureFileProcessingEvent(true, file, processingTime, null, pageCount);
|
this.captureFileProcessingEvent(true, decryptedFile, processingTime, null, pageCount);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
processingTime = Date.now() - startTime;
|
processingTime = Date.now() - startTime;
|
||||||
errorMessage = error.message || 'Unknown error';
|
errorMessage = error.message || 'Unknown error';
|
||||||
@ -194,6 +216,7 @@ class PdfContainer {
|
|||||||
document.querySelectorAll('.enable-on-file').forEach((element) => {
|
document.querySelectorAll('.enable-on-file').forEach((element) => {
|
||||||
element.disabled = false;
|
element.disabled = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
return pages;
|
return pages;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
47
src/main/resources/static/js/pages/add-image.js
Normal file
47
src/main/resources/static/js/pages/add-image.js
Normal file
@ -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);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
253
src/main/resources/static/js/pages/adjust-contrast.js
Normal file
253
src/main/resources/static/js/pages/adjust-contrast.js
Normal file
@ -0,0 +1,253 @@
|
|||||||
|
var canvas = document.getElementById('contrast-pdf-canvas');
|
||||||
|
var context = canvas.getContext('2d');
|
||||||
|
var originalImageData = null;
|
||||||
|
var allPages = [];
|
||||||
|
var pdfDoc = null;
|
||||||
|
var pdf = null; // This is the current PDF document
|
||||||
|
|
||||||
|
async function renderPDFAndSaveOriginalImageData(file) {
|
||||||
|
var fileReader = new FileReader();
|
||||||
|
fileReader.onload = async function () {
|
||||||
|
var data = new Uint8Array(this.result);
|
||||||
|
pdfjsLib.GlobalWorkerOptions.workerSrc = './pdfjs-legacy/pdf.worker.mjs';
|
||||||
|
pdf = await pdfjsLib.getDocument({data: data}).promise;
|
||||||
|
|
||||||
|
// Get the number of pages in the PDF
|
||||||
|
var numPages = pdf.numPages;
|
||||||
|
allPages = Array.from({length: numPages}, (_, i) => i + 1);
|
||||||
|
|
||||||
|
// Create a new PDF document
|
||||||
|
pdfDoc = await PDFLib.PDFDocument.create();
|
||||||
|
// Render the first page in the viewer
|
||||||
|
await renderPageAndAdjustImageProperties(1);
|
||||||
|
document.getElementById('sliders-container').style.display = 'block';
|
||||||
|
};
|
||||||
|
fileReader.readAsArrayBuffer(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
// This function is now async and returns a promise
|
||||||
|
function renderPageAndAdjustImageProperties(pageNum) {
|
||||||
|
return new Promise(async function (resolve, reject) {
|
||||||
|
var page = await pdf.getPage(pageNum);
|
||||||
|
var scale = 1.5;
|
||||||
|
var viewport = page.getViewport({scale: scale});
|
||||||
|
|
||||||
|
canvas.height = viewport.height;
|
||||||
|
canvas.width = viewport.width;
|
||||||
|
|
||||||
|
var renderContext = {
|
||||||
|
canvasContext: context,
|
||||||
|
viewport: viewport,
|
||||||
|
};
|
||||||
|
|
||||||
|
var renderTask = page.render(renderContext);
|
||||||
|
renderTask.promise.then(function () {
|
||||||
|
originalImageData = context.getImageData(0, 0, canvas.width, canvas.height);
|
||||||
|
adjustImageProperties();
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
canvas.classList.add('fixed-shadow-canvas');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function adjustImageProperties() {
|
||||||
|
var contrast = parseFloat(document.getElementById('contrast-slider').value);
|
||||||
|
var brightness = parseFloat(document.getElementById('brightness-slider').value);
|
||||||
|
var saturation = parseFloat(document.getElementById('saturation-slider').value);
|
||||||
|
|
||||||
|
contrast /= 100; // normalize to range [0, 2]
|
||||||
|
brightness /= 100; // normalize to range [0, 2]
|
||||||
|
saturation /= 100; // normalize to range [0, 2]
|
||||||
|
|
||||||
|
if (originalImageData) {
|
||||||
|
var newImageData = context.createImageData(originalImageData.width, originalImageData.height);
|
||||||
|
newImageData.data.set(originalImageData.data);
|
||||||
|
|
||||||
|
for (var i = 0; i < newImageData.data.length; i += 4) {
|
||||||
|
var r = newImageData.data[i];
|
||||||
|
var g = newImageData.data[i + 1];
|
||||||
|
var b = newImageData.data[i + 2];
|
||||||
|
// Adjust contrast
|
||||||
|
r = adjustContrastForPixel(r, contrast);
|
||||||
|
g = adjustContrastForPixel(g, contrast);
|
||||||
|
b = adjustContrastForPixel(b, contrast);
|
||||||
|
// Adjust brightness
|
||||||
|
r = adjustBrightnessForPixel(r, brightness);
|
||||||
|
g = adjustBrightnessForPixel(g, brightness);
|
||||||
|
b = adjustBrightnessForPixel(b, brightness);
|
||||||
|
// Adjust saturation
|
||||||
|
var rgb = adjustSaturationForPixel(r, g, b, saturation);
|
||||||
|
newImageData.data[i] = rgb[0];
|
||||||
|
newImageData.data[i + 1] = rgb[1];
|
||||||
|
newImageData.data[i + 2] = rgb[2];
|
||||||
|
}
|
||||||
|
context.putImageData(newImageData, 0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function rgbToHsl(r, g, b) {
|
||||||
|
(r /= 255), (g /= 255), (b /= 255);
|
||||||
|
|
||||||
|
var max = Math.max(r, g, b),
|
||||||
|
min = Math.min(r, g, b);
|
||||||
|
var h,
|
||||||
|
s,
|
||||||
|
l = (max + min) / 2;
|
||||||
|
|
||||||
|
if (max === min) {
|
||||||
|
h = s = 0; // achromatic
|
||||||
|
} else {
|
||||||
|
var d = max - min;
|
||||||
|
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
||||||
|
|
||||||
|
switch (max) {
|
||||||
|
case r:
|
||||||
|
h = (g - b) / d + (g < b ? 6 : 0);
|
||||||
|
break;
|
||||||
|
case g:
|
||||||
|
h = (b - r) / d + 2;
|
||||||
|
break;
|
||||||
|
case b:
|
||||||
|
h = (r - g) / d + 4;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
h /= 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [h, s, l];
|
||||||
|
}
|
||||||
|
|
||||||
|
function hslToRgb(h, s, l) {
|
||||||
|
var r, g, b;
|
||||||
|
|
||||||
|
if (s === 0) {
|
||||||
|
r = g = b = l; // achromatic
|
||||||
|
} else {
|
||||||
|
var hue2rgb = function hue2rgb(p, q, t) {
|
||||||
|
if (t < 0) t += 1;
|
||||||
|
if (t > 1) t -= 1;
|
||||||
|
if (t < 1 / 6) return p + (q - p) * 6 * t;
|
||||||
|
if (t < 1 / 2) return q;
|
||||||
|
if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
|
||||||
|
return p;
|
||||||
|
};
|
||||||
|
|
||||||
|
var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
|
||||||
|
var p = 2 * l - q;
|
||||||
|
|
||||||
|
r = hue2rgb(p, q, h + 1 / 3);
|
||||||
|
g = hue2rgb(p, q, h);
|
||||||
|
b = hue2rgb(p, q, h - 1 / 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [r * 255, g * 255, b * 255];
|
||||||
|
}
|
||||||
|
|
||||||
|
function adjustContrastForPixel(pixel, contrast) {
|
||||||
|
// Normalize to range [-0.5, 0.5]
|
||||||
|
var normalized = pixel / 255 - 0.5;
|
||||||
|
|
||||||
|
// Apply contrast
|
||||||
|
normalized *= contrast;
|
||||||
|
|
||||||
|
// Denormalize back to [0, 255]
|
||||||
|
return (normalized + 0.5) * 255;
|
||||||
|
}
|
||||||
|
|
||||||
|
function clamp(value, min, max) {
|
||||||
|
return Math.min(Math.max(value, min), max);
|
||||||
|
}
|
||||||
|
|
||||||
|
function adjustSaturationForPixel(r, g, b, saturation) {
|
||||||
|
var hsl = rgbToHsl(r, g, b);
|
||||||
|
|
||||||
|
// Adjust saturation
|
||||||
|
hsl[1] = clamp(hsl[1] * saturation, 0, 1);
|
||||||
|
|
||||||
|
// Convert back to RGB
|
||||||
|
var rgb = hslToRgb(hsl[0], hsl[1], hsl[2]);
|
||||||
|
|
||||||
|
// Return adjusted RGB values
|
||||||
|
return rgb;
|
||||||
|
}
|
||||||
|
|
||||||
|
function adjustBrightnessForPixel(pixel, brightness) {
|
||||||
|
return Math.max(0, Math.min(255, pixel * brightness));
|
||||||
|
}
|
||||||
|
let inputFileName = '';
|
||||||
|
async function downloadPDF() {
|
||||||
|
for (var i = 0; i < allPages.length; i++) {
|
||||||
|
await renderPageAndAdjustImageProperties(allPages[i]);
|
||||||
|
const pngImageBytes = canvas.toDataURL('image/png');
|
||||||
|
const pngImage = await pdfDoc.embedPng(pngImageBytes);
|
||||||
|
const pngDims = pngImage.scale(1);
|
||||||
|
|
||||||
|
// Create a blank page matching the dimensions of the image
|
||||||
|
const page = pdfDoc.addPage([pngDims.width, pngDims.height]);
|
||||||
|
|
||||||
|
// Draw the PNG image
|
||||||
|
page.drawImage(pngImage, {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
width: pngDims.width,
|
||||||
|
height: pngDims.height,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serialize the PDFDocument to bytes (a Uint8Array)
|
||||||
|
const pdfBytes = await pdfDoc.save();
|
||||||
|
|
||||||
|
// Create a Blob
|
||||||
|
const blob = new Blob([pdfBytes.buffer], {type: 'application/pdf'});
|
||||||
|
|
||||||
|
// Create download link
|
||||||
|
const downloadLink = document.createElement('a');
|
||||||
|
downloadLink.href = URL.createObjectURL(blob);
|
||||||
|
let newFileName = inputFileName ? inputFileName.replace('.pdf', '') : 'download';
|
||||||
|
newFileName += '_adjusted_color.pdf';
|
||||||
|
|
||||||
|
downloadLink.download = newFileName;
|
||||||
|
downloadLink.click();
|
||||||
|
|
||||||
|
// After download, reset the viewer and clear stored data
|
||||||
|
allPages = []; // Clear the pages
|
||||||
|
originalImageData = null; // Clear the image data
|
||||||
|
|
||||||
|
// Go back to page 1 and render it in the viewer
|
||||||
|
if (pdf !== null) {
|
||||||
|
renderPageAndAdjustImageProperties(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Event listeners
|
||||||
|
document.getElementById('fileInput-input').addEventListener('change', function (e) {
|
||||||
|
const fileInput = event.target;
|
||||||
|
fileInput.addEventListener('file-input-change', async (e) => {
|
||||||
|
const {allFiles} = e.detail;
|
||||||
|
if (allFiles && allFiles.length > 0) {
|
||||||
|
const file = allFiles[0];
|
||||||
|
inputFileName = file.name;
|
||||||
|
renderPDFAndSaveOriginalImageData(file);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('contrast-slider').addEventListener('input', function () {
|
||||||
|
document.getElementById('contrast-val').textContent = this.value;
|
||||||
|
adjustImageProperties();
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('brightness-slider').addEventListener('input', function () {
|
||||||
|
document.getElementById('brightness-val').textContent = this.value;
|
||||||
|
adjustImageProperties();
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('saturation-slider').addEventListener('input', function () {
|
||||||
|
document.getElementById('saturation-val').textContent = this.value;
|
||||||
|
adjustImageProperties();
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('download-button').addEventListener('click', function () {
|
||||||
|
downloadPDF();
|
||||||
|
});
|
150
src/main/resources/static/js/pages/change-metadata.js
Normal file
150
src/main/resources/static/js/pages/change-metadata.js
Normal file
@ -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 = `<div class="mb-3"><label class="form-check-label" for="${key}">${key}:</label><input name="${key}" value="${value}" type="text" class="form-control" id="${key}"></div>`;
|
||||||
|
otherMetadataEntriesDiv.appendChild(entryDiv);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
customMetadataDiv.style.display = 'none';
|
||||||
|
while (otherMetadataEntriesDiv.firstChild) {
|
||||||
|
otherMetadataEntriesDiv.removeChild(otherMetadataEntriesDiv.firstChild);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
customModeCheckbox.addEventListener('change', (event) => {
|
||||||
|
addExtra();
|
||||||
|
});
|
159
src/main/resources/static/js/pages/crop.js
Normal file
159
src/main/resources/static/js/pages/crop.js
Normal file
@ -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');
|
||||||
|
});
|
||||||
|
}
|
138
src/main/resources/static/js/pages/pdf-to-csv.js
Normal file
138
src/main/resources/static/js/pages/pdf-to-csv.js
Normal file
@ -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');
|
||||||
|
});
|
||||||
|
}
|
213
src/main/resources/static/js/pages/sign.js
Normal file
213
src/main/resources/static/js/pages/sign.js
Normal file
@ -0,0 +1,213 @@
|
|||||||
|
window.toggleSignatureView = toggleSignatureView;
|
||||||
|
window.previewSignature = previewSignature;
|
||||||
|
window.addSignatureFromPreview = addSignatureFromPreview;
|
||||||
|
window.addDraggableFromPad = addDraggableFromPad;
|
||||||
|
window.addDraggableFromText = addDraggableFromText;
|
||||||
|
window.goToFirstOrLastPage = goToFirstOrLastPage;
|
||||||
|
|
||||||
|
let currentPreviewSrc = null;
|
||||||
|
|
||||||
|
function toggleSignatureView() {
|
||||||
|
const gridView = document.getElementById('gridView');
|
||||||
|
const listView = document.getElementById('listView');
|
||||||
|
const gridText = document.querySelector('.grid-view-text');
|
||||||
|
const listText = document.querySelector('.list-view-text');
|
||||||
|
|
||||||
|
if (gridView.style.display !== 'none') {
|
||||||
|
gridView.style.display = 'none';
|
||||||
|
listView.style.display = 'block';
|
||||||
|
gridText.style.display = 'none';
|
||||||
|
listText.style.display = 'inline';
|
||||||
|
} else {
|
||||||
|
gridView.style.display = 'block';
|
||||||
|
listView.style.display = 'none';
|
||||||
|
gridText.style.display = 'inline';
|
||||||
|
listText.style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function previewSignature(element) {
|
||||||
|
const src = element.dataset.src;
|
||||||
|
currentPreviewSrc = src;
|
||||||
|
|
||||||
|
const filename = element.querySelector('.signature-list-name').textContent;
|
||||||
|
|
||||||
|
const previewImage = document.getElementById('previewImage');
|
||||||
|
const previewFileName = document.getElementById('previewFileName');
|
||||||
|
|
||||||
|
previewImage.src = src;
|
||||||
|
previewFileName.textContent = filename;
|
||||||
|
|
||||||
|
const modal = new bootstrap.Modal(document.getElementById('signaturePreview'));
|
||||||
|
modal.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
function addSignatureFromPreview() {
|
||||||
|
if (currentPreviewSrc) {
|
||||||
|
DraggableUtils.createDraggableCanvasFromUrl(currentPreviewSrc);
|
||||||
|
bootstrap.Modal.getInstance(document.getElementById('signaturePreview')).hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const signaturePadCanvas = document.getElementById('drawing-pad-canvas');
|
||||||
|
const signaturePad = new SignaturePad(signaturePadCanvas, {
|
||||||
|
minWidth: 1,
|
||||||
|
maxWidth: 2,
|
||||||
|
penColor: 'black',
|
||||||
|
});
|
||||||
|
|
||||||
|
function addDraggableFromPad() {
|
||||||
|
if (signaturePad.isEmpty()) return;
|
||||||
|
const startTime = Date.now();
|
||||||
|
const croppedDataUrl = getCroppedCanvasDataUrl(signaturePadCanvas);
|
||||||
|
console.log(Date.now() - startTime);
|
||||||
|
DraggableUtils.createDraggableCanvasFromUrl(croppedDataUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCroppedCanvasDataUrl(canvas) {
|
||||||
|
let originalCtx = canvas.getContext('2d');
|
||||||
|
let originalWidth = canvas.width;
|
||||||
|
let originalHeight = canvas.height;
|
||||||
|
let imageData = originalCtx.getImageData(0, 0, originalWidth, originalHeight);
|
||||||
|
|
||||||
|
let minX = originalWidth + 1,
|
||||||
|
maxX = -1,
|
||||||
|
minY = originalHeight + 1,
|
||||||
|
maxY = -1,
|
||||||
|
x = 0,
|
||||||
|
y = 0,
|
||||||
|
currentPixelColorValueIndex;
|
||||||
|
|
||||||
|
for (y = 0; y < originalHeight; y++) {
|
||||||
|
for (x = 0; x < originalWidth; x++) {
|
||||||
|
currentPixelColorValueIndex = (y * originalWidth + x) * 4;
|
||||||
|
let currentPixelAlphaValue = imageData.data[currentPixelColorValueIndex + 3];
|
||||||
|
if (currentPixelAlphaValue > 0) {
|
||||||
|
if (minX > x) minX = x;
|
||||||
|
if (maxX < x) maxX = x;
|
||||||
|
if (minY > y) minY = y;
|
||||||
|
if (maxY < y) maxY = y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let croppedWidth = maxX - minX;
|
||||||
|
let croppedHeight = maxY - minY;
|
||||||
|
if (croppedWidth < 0 || croppedHeight < 0) return null;
|
||||||
|
let cuttedImageData = originalCtx.getImageData(minX, minY, croppedWidth, croppedHeight);
|
||||||
|
|
||||||
|
let croppedCanvas = document.createElement('canvas'),
|
||||||
|
croppedCtx = croppedCanvas.getContext('2d');
|
||||||
|
|
||||||
|
croppedCanvas.width = croppedWidth;
|
||||||
|
croppedCanvas.height = croppedHeight;
|
||||||
|
croppedCtx.putImageData(cuttedImageData, 0, 0);
|
||||||
|
|
||||||
|
return croppedCanvas.toDataURL();
|
||||||
|
}
|
||||||
|
|
||||||
|
function resizeCanvas() {
|
||||||
|
var ratio = Math.max(window.devicePixelRatio || 1, 1);
|
||||||
|
var additionalFactor = 10;
|
||||||
|
|
||||||
|
signaturePadCanvas.width = signaturePadCanvas.offsetWidth * ratio * additionalFactor;
|
||||||
|
signaturePadCanvas.height = signaturePadCanvas.offsetHeight * ratio * additionalFactor;
|
||||||
|
signaturePadCanvas.getContext('2d').scale(ratio * additionalFactor, ratio * additionalFactor);
|
||||||
|
|
||||||
|
signaturePad.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
new IntersectionObserver((entries, observer) => {
|
||||||
|
if (entries.some((entry) => entry.intersectionRatio > 0)) {
|
||||||
|
resizeCanvas();
|
||||||
|
}
|
||||||
|
}).observe(signaturePadCanvas);
|
||||||
|
|
||||||
|
new ResizeObserver(resizeCanvas).observe(signaturePadCanvas);
|
||||||
|
|
||||||
|
function addDraggableFromText() {
|
||||||
|
const sigText = document.getElementById('sigText').value;
|
||||||
|
const font = document.querySelector('select[name=font]').value;
|
||||||
|
const fontSize = 100;
|
||||||
|
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
ctx.font = `${fontSize}px ${font}`;
|
||||||
|
const textWidth = ctx.measureText(sigText).width;
|
||||||
|
const textHeight = fontSize;
|
||||||
|
|
||||||
|
let paragraphs = sigText.split(/\r?\n/);
|
||||||
|
|
||||||
|
canvas.width = textWidth;
|
||||||
|
canvas.height = paragraphs.length * textHeight * 1.35; // for tails
|
||||||
|
ctx.font = `${fontSize}px ${font}`;
|
||||||
|
|
||||||
|
ctx.textBaseline = 'top';
|
||||||
|
|
||||||
|
let y = 0;
|
||||||
|
|
||||||
|
paragraphs.forEach((paragraph) => {
|
||||||
|
ctx.fillText(paragraph, 0, y);
|
||||||
|
y += fontSize;
|
||||||
|
});
|
||||||
|
|
||||||
|
const dataURL = canvas.toDataURL();
|
||||||
|
DraggableUtils.createDraggableCanvasFromUrl(dataURL);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function goToFirstOrLastPage(page) {
|
||||||
|
if (page) {
|
||||||
|
const lastPage = DraggableUtils.pdfDoc.numPages;
|
||||||
|
await DraggableUtils.goToPage(lastPage - 1);
|
||||||
|
} else {
|
||||||
|
await DraggableUtils.goToPage(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 + '_signed.pdf';
|
||||||
|
link.click();
|
||||||
|
});
|
@ -34,140 +34,8 @@
|
|||||||
<canvas id="overlayCanvas" style="position: absolute; top: 0; left: 0; z-index: 2; width: 100%"></canvas>
|
<canvas id="overlayCanvas" style="position: absolute; top: 0; left: 0; z-index: 2; width: 100%"></canvas>
|
||||||
</div>
|
</div>
|
||||||
<script type="module" th:src="@{'/pdfjs-legacy/pdf.mjs'}"></script>
|
<script type="module" th:src="@{'/pdfjs-legacy/pdf.mjs'}"></script>
|
||||||
<script>
|
<script th:src="@{'/js/thirdParty/pdf-lib.min.js'}"></script>
|
||||||
let pdfCanvas = document.getElementById('cropPdfCanvas');
|
<script type="module" th:src="@{'/js/pages/pdf-to-csv.js'}">
|
||||||
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) {
|
|
||||||
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");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -32,163 +32,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script type="module" th:src="@{'/pdfjs-legacy/pdf.mjs'}"></script>
|
<script type="module" th:src="@{'/pdfjs-legacy/pdf.mjs'}"></script>
|
||||||
<script>
|
<script type="module" th:src="@{'/js/pages/crop.js'}"></script>
|
||||||
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) {
|
|
||||||
canvasesContainer.style.display = "block"; // set for visual purposes
|
|
||||||
let file = e.target.files[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");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
|
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
<p th:text="#{error.contactTip}"></p>
|
<p th:text="#{error.contactTip}"></p>
|
||||||
<div id="button-group">
|
<div id="button-group">
|
||||||
<a href="https://github.com/Stirling-Tools/Stirling-PDF/issues" id="github-button" class="btn btn-primary" target="_blank" th:text="#{error.github}"></a>
|
<a href="https://github.com/Stirling-Tools/Stirling-PDF/issues" id="github-button" class="btn btn-primary" target="_blank" th:text="#{error.github}"></a>
|
||||||
<a href="https://discord.gg/Cn8pWhQRxZ" id="discord-button" class="btn btn-primary" target="_blank" th:text="#{joinDiscord}"></a>
|
<a href="https://discord.gg/HYmhKj45pU" id="discord-button" class="btn btn-primary" target="_blank" th:text="#{joinDiscord}"></a>
|
||||||
</div>
|
</div>
|
||||||
<a th:href="@{'/'}" id="home-button" class="home-button btn btn-primary" th:text="#{goHomepage}"></a>
|
<a th:href="@{'/'}" id="home-button" class="home-button btn btn-primary" th:text="#{goHomepage}"></a>
|
||||||
</div>
|
</div>
|
||||||
|
@ -203,7 +203,17 @@
|
|||||||
</script>
|
</script>
|
||||||
<script type="module" th:src="@{'/pdfjs-legacy/pdf.mjs'}"></script>
|
<script type="module" th:src="@{'/pdfjs-legacy/pdf.mjs'}"></script>
|
||||||
<script th:src="@{'/js/downloader.js'}"></script>
|
<script th:src="@{'/js/downloader.js'}"></script>
|
||||||
|
<script>
|
||||||
|
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}]]',
|
||||||
|
};</script>
|
||||||
<div class="custom-file-chooser mb-3" th:attr="data-bs-unique-id=${name}, data-bs-element-id=${name+'-input'}, data-bs-element-container-id=${name+'-input-container'}, data-bs-files-selected=#{filesSelected}, data-bs-pdf-prompt=#{pdfPrompt}">
|
<div class="custom-file-chooser mb-3" th:attr="data-bs-unique-id=${name}, data-bs-element-id=${name+'-input'}, data-bs-element-container-id=${name+'-input-container'}, data-bs-files-selected=#{filesSelected}, data-bs-pdf-prompt=#{pdfPrompt}">
|
||||||
<div class="mb-3 d-flex flex-row justify-content-center align-items-center flex-wrap input-container" th:name="${name}+'-input'" th:id="${name}+'-input-container'" th:data-text="#{fileChooser.hoveredDragAndDrop}">
|
<div class="mb-3 d-flex flex-row justify-content-center align-items-center flex-wrap input-container" th:name="${name}+'-input'" th:id="${name}+'-input-container'" th:data-text="#{fileChooser.hoveredDragAndDrop}">
|
||||||
<label class="file-input-btn d-none">
|
<label class="file-input-btn d-none">
|
||||||
|
@ -20,7 +20,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<!-- Buttons to submit a ticket on GitHub and join Discord server -->
|
<!-- Buttons to submit a ticket on GitHub and join Discord server -->
|
||||||
<a href="https://github.com/Stirling-Tools/Stirling-PDF/issues" id="github-button" target="_blank" th:text="#{error.github}"></a>
|
<a href="https://github.com/Stirling-Tools/Stirling-PDF/issues" id="github-button" target="_blank" th:text="#{error.github}"></a>
|
||||||
<a href="https://discord.gg/Cn8pWhQRxZ" id="discord-button" target="_blank" th:text="#{joinDiscord}"></a>
|
<a href="https://discord.gg/HYmhKj45pU" id="discord-button" target="_blank" th:text="#{joinDiscord}"></a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script>
|
<script>
|
||||||
|
@ -39,7 +39,7 @@
|
|||||||
<p th:text="#{error.contactTip}"></p>
|
<p th:text="#{error.contactTip}"></p>
|
||||||
<div id="button-group">
|
<div id="button-group">
|
||||||
<a href="https://github.com/Stirling-Tools/Stirling-PDF/issues" id="github-button" target="_blank" th:text="#{error.githubSubmit}"></a>
|
<a href="https://github.com/Stirling-Tools/Stirling-PDF/issues" id="github-button" target="_blank" th:text="#{error.githubSubmit}"></a>
|
||||||
<a href="https://discord.gg/Cn8pWhQRxZ" id="discord-button" target="_blank" th:text="#{error.discordSubmit}"></a>
|
<a href="https://discord.gg/HYmhKj45pU" id="discord-button" target="_blank" th:text="#{error.discordSubmit}"></a>
|
||||||
</div>
|
</div>
|
||||||
<a th:href="@{'/'}" class="home-button" th:text="#{goHomepage}"></a>
|
<a th:href="@{'/'}" class="home-button" th:text="#{goHomepage}"></a>
|
||||||
<a data-bs-dismiss="modal" class="home-button" th:text="#{close}"></a>
|
<a data-bs-dismiss="modal" class="home-button" th:text="#{close}"></a>
|
||||||
|
@ -482,7 +482,11 @@ document.addEventListener("DOMContentLoaded", function() {
|
|||||||
localStorage.setItem('surveyVersion', surveyVersion);
|
localStorage.setItem('surveyVersion', surveyVersion);
|
||||||
modal.hide();
|
modal.hide();
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
if (localStorage.getItem('dontShowSurvey')) {
|
||||||
|
modal.hide();
|
||||||
|
}
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
@ -22,46 +22,9 @@
|
|||||||
<!-- pdf selector -->
|
<!-- pdf selector -->
|
||||||
<div th:replace="~{fragments/common :: fileSelector(name='pdf-upload', disableMultipleFiles=true, multipleInputsForSingleRequest=false, accept='application/pdf')}"></div>
|
<div th:replace="~{fragments/common :: fileSelector(name='pdf-upload', disableMultipleFiles=true, multipleInputsForSingleRequest=false, accept='application/pdf')}"></div>
|
||||||
<script type="module" th:src="@{'/pdfjs-legacy/pdf.mjs'}"></script>
|
<script type="module" th:src="@{'/pdfjs-legacy/pdf.mjs'}"></script>
|
||||||
<script>
|
<script type="module" th:src="@{'/js/pages/add-image.js'}"></script>
|
||||||
let originalFileName = '';
|
|
||||||
document.querySelector('input[name=pdf-upload]').addEventListener('change', async (event) => {
|
|
||||||
const file = event.target.files[0];
|
|
||||||
if (file) {
|
|
||||||
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";
|
|
||||||
})
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="tab-group show-on-file-selected">
|
<div class="tab-group show-on-file-selected">
|
||||||
<div th:replace="~{fragments/common :: fileSelector(name='image-upload', disableMultipleFiles=true, multipleInputsForSingleRequest=true, accept='image/*', inputText=#{imgPrompt})}"></div>
|
<div th:replace="~{fragments/common :: fileSelector(name='image-upload', disableMultipleFiles=true, multipleInputsForSingleRequest=true, accept='image/*', inputText=#{imgPrompt})}"></div>
|
||||||
<script>
|
|
||||||
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);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- draggables box -->
|
<!-- draggables box -->
|
||||||
@ -93,17 +56,6 @@
|
|||||||
<div class="margin-auto-parent">
|
<div class="margin-auto-parent">
|
||||||
<button id="download-pdf" class="btn btn-primary mb-2 show-on-file-selected margin-center" th:text="#{downloadPdf}"></button>
|
<button id="download-pdf" class="btn btn-primary mb-2 show-on-file-selected margin-center" th:text="#{downloadPdf}"></button>
|
||||||
</div>
|
</div>
|
||||||
<script>
|
|
||||||
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();
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -55,247 +55,7 @@
|
|||||||
</form>
|
</form>
|
||||||
<script type="module" th:src="@{'/pdfjs-legacy/pdf.mjs'}"></script>
|
<script type="module" th:src="@{'/pdfjs-legacy/pdf.mjs'}"></script>
|
||||||
<script th:src="@{'/js/thirdParty/pdf-lib.min.js'}"></script>
|
<script th:src="@{'/js/thirdParty/pdf-lib.min.js'}"></script>
|
||||||
<script>
|
<script type="module" th:src="@{'/js/pages/adjust-contrast.js'}"></script>
|
||||||
var canvas = document.getElementById('contrast-pdf-canvas');
|
|
||||||
var context = canvas.getContext('2d');
|
|
||||||
var originalImageData = null;
|
|
||||||
var allPages = [];
|
|
||||||
var pdfDoc = null;
|
|
||||||
var pdf = null; // This is the current PDF document
|
|
||||||
|
|
||||||
async function renderPDFAndSaveOriginalImageData(file) {
|
|
||||||
var fileReader = new FileReader();
|
|
||||||
fileReader.onload = async function() {
|
|
||||||
var data = new Uint8Array(this.result);
|
|
||||||
pdfjsLib.GlobalWorkerOptions.workerSrc = './pdfjs-legacy/pdf.worker.mjs'
|
|
||||||
pdf = await pdfjsLib.getDocument({data: data}).promise;
|
|
||||||
|
|
||||||
// Get the number of pages in the PDF
|
|
||||||
var numPages = pdf.numPages;
|
|
||||||
allPages = Array.from({length: numPages}, (_, i) => i + 1);
|
|
||||||
|
|
||||||
// Create a new PDF document
|
|
||||||
pdfDoc = await PDFLib.PDFDocument.create();
|
|
||||||
// Render the first page in the viewer
|
|
||||||
await renderPageAndAdjustImageProperties(1);
|
|
||||||
document.getElementById("sliders-container").style.display = "block";
|
|
||||||
};
|
|
||||||
fileReader.readAsArrayBuffer(file);
|
|
||||||
}
|
|
||||||
|
|
||||||
// This function is now async and returns a promise
|
|
||||||
function renderPageAndAdjustImageProperties(pageNum) {
|
|
||||||
return new Promise(async function(resolve, reject) {
|
|
||||||
var page = await pdf.getPage(pageNum);
|
|
||||||
var scale = 1.5;
|
|
||||||
var viewport = page.getViewport({ scale: scale });
|
|
||||||
|
|
||||||
canvas.height = viewport.height;
|
|
||||||
canvas.width = viewport.width;
|
|
||||||
|
|
||||||
var renderContext = {
|
|
||||||
canvasContext: context,
|
|
||||||
viewport: viewport
|
|
||||||
};
|
|
||||||
|
|
||||||
var renderTask = page.render(renderContext);
|
|
||||||
renderTask.promise.then(function () {
|
|
||||||
originalImageData = context.getImageData(0, 0, canvas.width, canvas.height);
|
|
||||||
adjustImageProperties();
|
|
||||||
resolve();
|
|
||||||
});
|
|
||||||
canvas.classList.add("fixed-shadow-canvas");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function adjustImageProperties() {
|
|
||||||
var contrast = parseFloat(document.getElementById('contrast-slider').value);
|
|
||||||
var brightness = parseFloat(document.getElementById('brightness-slider').value);
|
|
||||||
var saturation = parseFloat(document.getElementById('saturation-slider').value);
|
|
||||||
|
|
||||||
contrast /= 100; // normalize to range [0, 2]
|
|
||||||
brightness /= 100; // normalize to range [0, 2]
|
|
||||||
saturation /= 100; // normalize to range [0, 2]
|
|
||||||
|
|
||||||
if (originalImageData) {
|
|
||||||
var newImageData = context.createImageData(originalImageData.width, originalImageData.height);
|
|
||||||
newImageData.data.set(originalImageData.data);
|
|
||||||
|
|
||||||
for(var i=0; i<newImageData.data.length; i+=4) {
|
|
||||||
var r = newImageData.data[i];
|
|
||||||
var g = newImageData.data[i+1];
|
|
||||||
var b = newImageData.data[i+2];
|
|
||||||
// Adjust contrast
|
|
||||||
r = adjustContrastForPixel(r, contrast);
|
|
||||||
g = adjustContrastForPixel(g, contrast);
|
|
||||||
b = adjustContrastForPixel(b, contrast);
|
|
||||||
// Adjust brightness
|
|
||||||
r = adjustBrightnessForPixel(r, brightness);
|
|
||||||
g = adjustBrightnessForPixel(g, brightness);
|
|
||||||
b = adjustBrightnessForPixel(b, brightness);
|
|
||||||
// Adjust saturation
|
|
||||||
var rgb = adjustSaturationForPixel(r, g, b, saturation);
|
|
||||||
newImageData.data[i] = rgb[0];
|
|
||||||
newImageData.data[i+1] = rgb[1];
|
|
||||||
newImageData.data[i+2] = rgb[2];
|
|
||||||
}
|
|
||||||
context.putImageData(newImageData, 0, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function rgbToHsl(r, g, b) {
|
|
||||||
r /= 255, g /= 255, b /= 255;
|
|
||||||
|
|
||||||
var max = Math.max(r, g, b), min = Math.min(r, g, b);
|
|
||||||
var h, s, l = (max + min) / 2;
|
|
||||||
|
|
||||||
if (max === min) {
|
|
||||||
h = s = 0; // achromatic
|
|
||||||
} else {
|
|
||||||
var d = max - min;
|
|
||||||
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
|
||||||
|
|
||||||
switch (max) {
|
|
||||||
case r: h = (g - b) / d + (g < b ? 6 : 0); break;
|
|
||||||
case g: h = (b - r) / d + 2; break;
|
|
||||||
case b: h = (r - g) / d + 4; break;
|
|
||||||
}
|
|
||||||
|
|
||||||
h /= 6;
|
|
||||||
}
|
|
||||||
|
|
||||||
return [h, s, l];
|
|
||||||
}
|
|
||||||
|
|
||||||
function hslToRgb(h, s, l) {
|
|
||||||
var r, g, b;
|
|
||||||
|
|
||||||
if (s === 0) {
|
|
||||||
r = g = b = l; // achromatic
|
|
||||||
} else {
|
|
||||||
var hue2rgb = function hue2rgb(p, q, t) {
|
|
||||||
if (t < 0) t += 1;
|
|
||||||
if (t > 1) t -= 1;
|
|
||||||
if (t < 1 / 6) return p + (q - p) * 6 * t;
|
|
||||||
if (t < 1 / 2) return q;
|
|
||||||
if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
|
|
||||||
return p;
|
|
||||||
};
|
|
||||||
|
|
||||||
var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
|
|
||||||
var p = 2 * l - q;
|
|
||||||
|
|
||||||
r = hue2rgb(p, q, h + 1 / 3);
|
|
||||||
g = hue2rgb(p, q, h);
|
|
||||||
b = hue2rgb(p, q, h - 1 / 3);
|
|
||||||
}
|
|
||||||
|
|
||||||
return [r * 255, g * 255, b * 255];
|
|
||||||
}
|
|
||||||
|
|
||||||
function adjustContrastForPixel(pixel, contrast) {
|
|
||||||
// Normalize to range [-0.5, 0.5]
|
|
||||||
var normalized = pixel / 255 - 0.5;
|
|
||||||
|
|
||||||
// Apply contrast
|
|
||||||
normalized *= contrast;
|
|
||||||
|
|
||||||
// Denormalize back to [0, 255]
|
|
||||||
return (normalized + 0.5) * 255;
|
|
||||||
}
|
|
||||||
|
|
||||||
function clamp(value, min, max) {
|
|
||||||
return Math.min(Math.max(value, min), max);
|
|
||||||
}
|
|
||||||
|
|
||||||
function adjustSaturationForPixel(r, g, b, saturation) {
|
|
||||||
var hsl = rgbToHsl(r, g, b);
|
|
||||||
|
|
||||||
// Adjust saturation
|
|
||||||
hsl[1] = clamp(hsl[1] * saturation, 0, 1);
|
|
||||||
|
|
||||||
// Convert back to RGB
|
|
||||||
var rgb = hslToRgb(hsl[0], hsl[1], hsl[2]);
|
|
||||||
|
|
||||||
// Return adjusted RGB values
|
|
||||||
return rgb;
|
|
||||||
}
|
|
||||||
|
|
||||||
function adjustBrightnessForPixel(pixel, brightness) {
|
|
||||||
return Math.max(0, Math.min(255, pixel * brightness));
|
|
||||||
}
|
|
||||||
let inputFileName = '';
|
|
||||||
async function downloadPDF() {
|
|
||||||
for (var i = 0; i < allPages.length; i++) {
|
|
||||||
await renderPageAndAdjustImageProperties(allPages[i]);
|
|
||||||
const pngImageBytes = canvas.toDataURL('image/png');
|
|
||||||
const pngImage = await pdfDoc.embedPng(pngImageBytes);
|
|
||||||
const pngDims = pngImage.scale(1);
|
|
||||||
|
|
||||||
// Create a blank page matching the dimensions of the image
|
|
||||||
const page = pdfDoc.addPage([pngDims.width, pngDims.height]);
|
|
||||||
|
|
||||||
// Draw the PNG image
|
|
||||||
page.drawImage(pngImage, {
|
|
||||||
x: 0,
|
|
||||||
y: 0,
|
|
||||||
width: pngDims.width,
|
|
||||||
height: pngDims.height
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Serialize the PDFDocument to bytes (a Uint8Array)
|
|
||||||
const pdfBytes = await pdfDoc.save();
|
|
||||||
|
|
||||||
// Create a Blob
|
|
||||||
const blob = new Blob([pdfBytes.buffer], {type: "application/pdf"});
|
|
||||||
|
|
||||||
// Create download link
|
|
||||||
const downloadLink = document.createElement('a');
|
|
||||||
downloadLink.href = URL.createObjectURL(blob);
|
|
||||||
let newFileName = inputFileName ? inputFileName.replace('.pdf', '') : 'download';
|
|
||||||
newFileName += '_adjusted_color.pdf';
|
|
||||||
|
|
||||||
downloadLink.download = newFileName;
|
|
||||||
downloadLink.click();
|
|
||||||
|
|
||||||
// After download, reset the viewer and clear stored data
|
|
||||||
allPages = []; // Clear the pages
|
|
||||||
originalImageData = null; // Clear the image data
|
|
||||||
|
|
||||||
// Go back to page 1 and render it in the viewer
|
|
||||||
if (pdf !== null) {
|
|
||||||
renderPageAndAdjustImageProperties(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Event listeners
|
|
||||||
document.getElementById('fileInput-input').addEventListener('change', function(e) {
|
|
||||||
if (e.target.files.length > 0) {
|
|
||||||
inputFileName = e.target.files[0].name;
|
|
||||||
renderPDFAndSaveOriginalImageData(e.target.files[0]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
document.getElementById('contrast-slider').addEventListener('input', function() {
|
|
||||||
document.getElementById('contrast-val').textContent = this.value;
|
|
||||||
adjustImageProperties();
|
|
||||||
});
|
|
||||||
|
|
||||||
document.getElementById('brightness-slider').addEventListener('input', function() {
|
|
||||||
document.getElementById('brightness-val').textContent = this.value;
|
|
||||||
adjustImageProperties();
|
|
||||||
});
|
|
||||||
|
|
||||||
document.getElementById('saturation-slider').addEventListener('input', function() {
|
|
||||||
document.getElementById('saturation-val').textContent = this.value;
|
|
||||||
adjustImageProperties();
|
|
||||||
});
|
|
||||||
|
|
||||||
document.getElementById('download-button').addEventListener('click', function() {
|
|
||||||
downloadPDF();
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -85,140 +85,7 @@
|
|||||||
<br>
|
<br>
|
||||||
<button class="btn btn-primary" type="submit" id="submitBtn" th:text="#{changeMetadata.submit}"></button>
|
<button class="btn btn-primary" type="submit" id="submitBtn" th:text="#{changeMetadata.submit}"></button>
|
||||||
<script type="module" th:src="@{'/pdfjs-legacy/pdf.mjs'}"></script>
|
<script type="module" th:src="@{'/pdfjs-legacy/pdf.mjs'}"></script>
|
||||||
<script>
|
<script type="module" th:src="@{'/js/pages/change-metadata.js'}">
|
||||||
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;
|
|
||||||
fileInput.addEventListener("change", async function() {
|
|
||||||
while (otherMetadataEntriesDiv.firstChild) {
|
|
||||||
otherMetadataEntriesDiv.removeChild(otherMetadataEntriesDiv.firstChild);
|
|
||||||
}
|
|
||||||
while (customMetadataFormContainer.firstChild) {
|
|
||||||
customMetadataFormContainer.removeChild(customMetadataFormContainer.firstChild);
|
|
||||||
}
|
|
||||||
const file = this.files[0];
|
|
||||||
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 = `<div class="mb-3"><label class="form-check-label" for="${key}">${key}:</label><input name="${key}" value="${value}" type="text" class="form-control" id="${key}"></div>`;
|
|
||||||
otherMetadataEntriesDiv.appendChild(entryDiv);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
customMetadataDiv.style.display = 'none';
|
|
||||||
while (otherMetadataEntriesDiv.firstChild) {
|
|
||||||
otherMetadataEntriesDiv.removeChild(otherMetadataEntriesDiv.firstChild);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
customModeCheckbox.addEventListener('change', (event) => {
|
|
||||||
addExtra();
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
<html th:lang="${#locale.language}" th:dir="#{language.direction}" th:data-language="${#locale.toString()}" xmlns:th="https://www.thymeleaf.org">
|
<html th:lang="${#locale.language}" th:dir="#{language.direction}" th:data-language="${#locale.toString()}" xmlns:th="https://www.thymeleaf.org">
|
||||||
<head>
|
<head>
|
||||||
<th:block th:insert="~{fragments/common :: head(title=#{extractImages.title}, header=#{extractImages.header})}"></th:block>
|
<th:block th:insert="~{fragments/common :: head(title=#{extractImages.title}, header=#{extractImages.header})}"></th:block>
|
||||||
|
<script th:src="@{'/js/thirdParty/pdf-lib.min.js'}"></script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
<head>
|
<head>
|
||||||
<th:block th:insert="~{fragments/common :: head(title=#{replace-color.title}, header=#{replace-color.header})}"></th:block>
|
<th:block th:insert="~{fragments/common :: head(title=#{replace-color.title}, header=#{replace-color.header})}"></th:block>
|
||||||
|
<script th:src="@{'/js/thirdParty/pdf-lib.min.js'}"></script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
@ -162,10 +162,19 @@
|
|||||||
insertPageBreak:'[[#{multiTool.insertPageBreak}]]',
|
insertPageBreak:'[[#{multiTool.insertPageBreak}]]',
|
||||||
dragDropMessage:'[[#{multiTool.dragDropMessage}]]',
|
dragDropMessage:'[[#{multiTool.dragDropMessage}]]',
|
||||||
undo: '[[#{multiTool.undo}]]',
|
undo: '[[#{multiTool.undo}]]',
|
||||||
redo: '[[#{multiTool.redo}]]'
|
redo: '[[#{multiTool.redo}]]',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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");
|
const csvInput = document.getElementById("csv-input");
|
||||||
csvInput.addEventListener("keydown", function (event) {
|
csvInput.addEventListener("keydown", function (event) {
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
<html th:lang="${#locale.language}" th:dir="#{language.direction}" th:data-language="${#locale.toString()}" xmlns:th="https://www.thymeleaf.org">
|
<html th:lang="${#locale.language}" th:dir="#{language.direction}" th:data-language="${#locale.toString()}" xmlns:th="https://www.thymeleaf.org">
|
||||||
<head>
|
<head>
|
||||||
<th:block th:insert="~{fragments/common :: head(title=#{split-by-size-or-count.title}, header=#{split-by-size-or-count.header})}"></th:block>
|
<th:block th:insert="~{fragments/common :: head(title=#{split-by-size-or-count.title}, header=#{split-by-size-or-count.header})}"></th:block>
|
||||||
|
<script th:src="@{'/js/thirdParty/pdf-lib.min.js'}"></script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
th:href="@{'/css/pipeline.css'}"
|
th:href="@{'/css/pipeline.css'}"
|
||||||
th:if="${currentPage == 'pipeline'}"
|
th:if="${currentPage == 'pipeline'}"
|
||||||
/>
|
/>
|
||||||
|
<script th:src="@{'/js/thirdParty/pdf-lib.min.js'}"></script>
|
||||||
<script th:inline="javascript">
|
<script th:inline="javascript">
|
||||||
const saveSettings = /*[[#{pipelineOptions.saveSettings}]]*/ "";
|
const saveSettings = /*[[#{pipelineOptions.saveSettings}]]*/ "";
|
||||||
const deletePipelineText = /*[[#{pipeline.deletePrompt}]]*/ "Are you sure you want to delete pipeline";
|
const deletePipelineText = /*[[#{pipeline.deletePrompt}]]*/ "Are you sure you want to delete pipeline";
|
||||||
|
@ -19,9 +19,10 @@
|
|||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</th:block>
|
</th:block>
|
||||||
|
|
||||||
<script th:src="@{'/js/thirdParty/signature_pad.umd.min.js'}"></script>
|
<script th:src="@{'/js/thirdParty/signature_pad.umd.min.js'}"></script>
|
||||||
<script th:src="@{'/js/thirdParty/interact.min.js'}"></script>
|
<script th:src="@{'/js/thirdParty/interact.min.js'}"></script>
|
||||||
|
<script type="module" th:src="@{'/js/pages/sign.js'}"></script>
|
||||||
|
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
@ -42,75 +43,6 @@
|
|||||||
th:replace="~{fragments/common :: fileSelector(name='pdf-upload', multipleInputsForSingleRequest=false, disableMultipleFiles=true, accept='application/pdf')}">
|
th:replace="~{fragments/common :: fileSelector(name='pdf-upload', multipleInputsForSingleRequest=false, disableMultipleFiles=true, accept='application/pdf')}">
|
||||||
</div>
|
</div>
|
||||||
<script type="module" th:src="@{'/pdfjs-legacy/pdf.mjs'}"></script>
|
<script type="module" th:src="@{'/pdfjs-legacy/pdf.mjs'}"></script>
|
||||||
<script>
|
|
||||||
let currentPreviewSrc = null;
|
|
||||||
|
|
||||||
function toggleSignatureView() {
|
|
||||||
const gridView = document.getElementById('gridView');
|
|
||||||
const listView = document.getElementById('listView');
|
|
||||||
const gridText = document.querySelector('.grid-view-text');
|
|
||||||
const listText = document.querySelector('.list-view-text');
|
|
||||||
|
|
||||||
if (gridView.style.display !== 'none') {
|
|
||||||
gridView.style.display = 'none';
|
|
||||||
listView.style.display = 'block';
|
|
||||||
gridText.style.display = 'none';
|
|
||||||
listText.style.display = 'inline';
|
|
||||||
} else {
|
|
||||||
gridView.style.display = 'block';
|
|
||||||
listView.style.display = 'none';
|
|
||||||
gridText.style.display = 'inline';
|
|
||||||
listText.style.display = 'none';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function previewSignature(element) {
|
|
||||||
const src = element.dataset.src;
|
|
||||||
currentPreviewSrc = src;
|
|
||||||
|
|
||||||
// Extract filename from the data source path
|
|
||||||
const filename = element.querySelector('.signature-list-name').textContent;
|
|
||||||
|
|
||||||
// Update preview modal
|
|
||||||
const previewImage = document.getElementById('previewImage');
|
|
||||||
const previewFileName = document.getElementById('previewFileName');
|
|
||||||
|
|
||||||
previewImage.src = src;
|
|
||||||
previewFileName.textContent = filename;
|
|
||||||
|
|
||||||
const modal = new bootstrap.Modal(document.getElementById('signaturePreview'));
|
|
||||||
modal.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
function addSignatureFromPreview() {
|
|
||||||
if (currentPreviewSrc) {
|
|
||||||
DraggableUtils.createDraggableCanvasFromUrl(currentPreviewSrc);
|
|
||||||
bootstrap.Modal.getInstance(document.getElementById('signaturePreview')).hide();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
let originalFileName = '';
|
|
||||||
document.querySelector('input[name=pdf-upload]').addEventListener('change', async (event) => {
|
|
||||||
const file = event.target.files[0];
|
|
||||||
if (file) {
|
|
||||||
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";
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
<div class="tab-group show-on-file-selected">
|
<div class="tab-group show-on-file-selected">
|
||||||
<div class="tab-container" th:title="#{sign.upload}">
|
<div class="tab-container" th:title="#{sign.upload}">
|
||||||
<div
|
<div
|
||||||
@ -237,123 +169,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
|
||||||
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);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
<script>
|
|
||||||
const signaturePadCanvas = document.getElementById('drawing-pad-canvas');
|
|
||||||
const signaturePad = new SignaturePad(signaturePadCanvas, {
|
|
||||||
minWidth: 1,
|
|
||||||
maxWidth: 2,
|
|
||||||
penColor: 'black',
|
|
||||||
});
|
|
||||||
|
|
||||||
function addDraggableFromPad() {
|
|
||||||
if (signaturePad.isEmpty()) return;
|
|
||||||
const startTime = Date.now();
|
|
||||||
const croppedDataUrl = getCroppedCanvasDataUrl(signaturePadCanvas);
|
|
||||||
console.log(Date.now() - startTime);
|
|
||||||
DraggableUtils.createDraggableCanvasFromUrl(croppedDataUrl);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getCroppedCanvasDataUrl(canvas) {
|
|
||||||
let originalCtx = canvas.getContext('2d');
|
|
||||||
let originalWidth = canvas.width;
|
|
||||||
let originalHeight = canvas.height;
|
|
||||||
let imageData = originalCtx.getImageData(0, 0, originalWidth, originalHeight);
|
|
||||||
|
|
||||||
let minX = originalWidth + 1, maxX = -1, minY = originalHeight + 1, maxY = -1, x = 0, y = 0, currentPixelColorValueIndex;
|
|
||||||
|
|
||||||
for (y = 0; y < originalHeight; y++) {
|
|
||||||
for (x = 0; x < originalWidth; x++) {
|
|
||||||
currentPixelColorValueIndex = (y * originalWidth + x) * 4;
|
|
||||||
let currentPixelAlphaValue = imageData.data[currentPixelColorValueIndex + 3];
|
|
||||||
if (currentPixelAlphaValue > 0) {
|
|
||||||
if (minX > x) minX = x;
|
|
||||||
if (maxX < x) maxX = x;
|
|
||||||
if (minY > y) minY = y;
|
|
||||||
if (maxY < y) maxY = y;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let croppedWidth = maxX - minX;
|
|
||||||
let croppedHeight = maxY - minY;
|
|
||||||
if (croppedWidth < 0 || croppedHeight < 0) return null;
|
|
||||||
let cuttedImageData = originalCtx.getImageData(minX, minY, croppedWidth, croppedHeight);
|
|
||||||
|
|
||||||
let croppedCanvas = document.createElement('canvas'),
|
|
||||||
croppedCtx = croppedCanvas.getContext('2d');
|
|
||||||
|
|
||||||
croppedCanvas.width = croppedWidth;
|
|
||||||
croppedCanvas.height = croppedHeight;
|
|
||||||
croppedCtx.putImageData(cuttedImageData, 0, 0);
|
|
||||||
|
|
||||||
return croppedCanvas.toDataURL();
|
|
||||||
}
|
|
||||||
|
|
||||||
function resizeCanvas() {
|
|
||||||
var ratio = Math.max(window.devicePixelRatio || 1, 1);
|
|
||||||
var additionalFactor = 10;
|
|
||||||
|
|
||||||
signaturePadCanvas.width = signaturePadCanvas.offsetWidth * ratio * additionalFactor;
|
|
||||||
signaturePadCanvas.height = signaturePadCanvas.offsetHeight * ratio * additionalFactor;
|
|
||||||
signaturePadCanvas.getContext("2d").scale(ratio * additionalFactor, ratio * additionalFactor);
|
|
||||||
|
|
||||||
signaturePad.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
new IntersectionObserver((entries, observer) => {
|
|
||||||
if (entries.some(entry => entry.intersectionRatio > 0)) {
|
|
||||||
resizeCanvas();
|
|
||||||
}
|
|
||||||
}).observe(signaturePadCanvas);
|
|
||||||
|
|
||||||
new ResizeObserver(resizeCanvas).observe(signaturePadCanvas);
|
|
||||||
</script>
|
|
||||||
<script>
|
|
||||||
function addDraggableFromText() {
|
|
||||||
const sigText = document.getElementById('sigText').value;
|
|
||||||
const font = document.querySelector('select[name=font]').value;
|
|
||||||
const fontSize = 100;
|
|
||||||
|
|
||||||
const canvas = document.createElement('canvas');
|
|
||||||
const ctx = canvas.getContext('2d');
|
|
||||||
ctx.font = `${fontSize}px ${font}`;
|
|
||||||
const textWidth = ctx.measureText(sigText).width;
|
|
||||||
const textHeight = fontSize;
|
|
||||||
|
|
||||||
let paragraphs = sigText.split(/\r?\n/);
|
|
||||||
|
|
||||||
canvas.width = textWidth;
|
|
||||||
canvas.height = paragraphs.length * textHeight * 1.35; // for tails
|
|
||||||
ctx.font = `${fontSize}px ${font}`;
|
|
||||||
|
|
||||||
ctx.textBaseline = 'top';
|
|
||||||
|
|
||||||
let y = 0;
|
|
||||||
|
|
||||||
paragraphs.forEach(paragraph => {
|
|
||||||
ctx.fillText(paragraph, 0, y);
|
|
||||||
y += fontSize;
|
|
||||||
});
|
|
||||||
|
|
||||||
const dataURL = canvas.toDataURL();
|
|
||||||
DraggableUtils.createDraggableCanvasFromUrl(dataURL);
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<!-- draggables box -->
|
<!-- draggables box -->
|
||||||
<div id="box-drag-container" class="show-on-file-selected">
|
<div id="box-drag-container" class="show-on-file-selected">
|
||||||
<canvas id="pdf-canvas"></canvas>
|
<canvas id="pdf-canvas"></canvas>
|
||||||
@ -410,35 +225,11 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- download button -->
|
<!-- download button -->
|
||||||
<div class="margin-auto-parent">
|
<div class="margin-auto-parent">
|
||||||
<button id="download-pdf" class="btn btn-primary mb-2 show-on-file-selected margin-center"
|
<button id="download-pdf" class="btn btn-primary mb-2 show-on-file-selected margin-center"
|
||||||
th:text="#{downloadPdf}"></button>
|
th:text="#{downloadPdf}"></button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
|
||||||
async function goToFirstOrLastPage(page) {
|
|
||||||
if (page) {
|
|
||||||
const lastPage = DraggableUtils.pdfDoc.numPages
|
|
||||||
await DraggableUtils.goToPage(lastPage - 1)
|
|
||||||
} else {
|
|
||||||
await DraggableUtils.goToPage(0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
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 + '_signed.pdf';
|
|
||||||
link.click();
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -447,6 +238,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<!-- Link the draggable.js file -->
|
<!-- Link the draggable.js file -->
|
||||||
<script th:src="@{'/js/draggable.js'}"></script>
|
<script th:src="@{'/js/draggable.js'}"></script>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
Loading…
x
Reference in New Issue
Block a user