Add Decrypt to all relevant pages

This commit is contained in:
Reece Browne 2024-12-10 16:39:06 +00:00
parent 1d6511b043
commit ef8231de3a
11 changed files with 633 additions and 584 deletions

View File

@ -4,7 +4,7 @@ export class DecryptFile {
const formData = new FormData();
formData.append('fileInput', file);
if (requiresPassword) {
const password = prompt(`${window.translations.passwordPrompt}`);
const password = prompt(`${window.decrypt.passwordPrompt}`);
if (password === null) {
// User cancelled
@ -16,9 +16,9 @@ export class DecryptFile {
// No password provided
console.error(`No password provided for encrypted PDF: ${file.name}`);
this.showErrorBanner(
`${window.translations.noPassword.replace('{0}', file.name)}`,
`${window.decrypt.noPassword.replace('{0}', file.name)}`,
'',
`${window.translations.unexpectedError}`
`${window.decrypt.unexpectedError}`
);
return null; // No file to return
}
@ -37,11 +37,11 @@ export class DecryptFile {
return new File([decryptedBlob], file.name, {type: 'application/pdf'});
} else {
const errorText = await response.text();
console.error(`${window.translations.invalidPassword} ${errorText}`);
console.error(`${window.decrypt.invalidPassword} ${errorText}`);
this.showErrorBanner(
`${window.translations.invalidPassword}`,
`${window.decrypt.invalidPassword}`,
errorText,
`${window.translations.invalidPasswordHeader.replace('{0}', file.name)}`
`${window.decrypt.invalidPasswordHeader.replace('{0}', file.name)}`
);
return null; // No file to return
}
@ -49,8 +49,8 @@ export class DecryptFile {
// Handle network or unexpected errors
console.error(`Failed to decrypt PDF: ${file.name}`, error);
this.showErrorBanner(
`${window.translations.unexpectedError.replace('{0}', file.name)}`,
`${error.message || window.translations.unexpectedError}`,
`${window.decrypt.unexpectedError.replace('{0}', file.name)}`,
`${error.message || window.decrypt.unexpectedError}`,
error
);
return null; // No file to return
@ -59,6 +59,10 @@ export class DecryptFile {
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);

View 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();
});

View File

@ -1,6 +1,6 @@
const DraggableUtils = {
boxDragContainer: document.getElementById("box-drag-container"),
pdfCanvas: document.getElementById("pdf-canvas"),
boxDragContainer: document.getElementById('box-drag-container'),
pdfCanvas: document.getElementById('pdf-canvas'),
nextId: 0,
pdfDoc: null,
pageIndex: 0,
@ -9,19 +9,17 @@ const DraggableUtils = {
lastInteracted: null,
init() {
interact(".draggable-canvas")
interact('.draggable-canvas')
.draggable({
listeners: {
move: (event) => {
const target = event.target;
const x = (parseFloat(target.getAttribute("data-bs-x")) || 0)
+ event.dx;
const y = (parseFloat(target.getAttribute("data-bs-y")) || 0)
+ event.dy;
const x = (parseFloat(target.getAttribute('data-bs-x')) || 0) + event.dx;
const y = (parseFloat(target.getAttribute('data-bs-y')) || 0) + event.dy;
target.style.transform = `translate(${x}px, ${y}px)`;
target.setAttribute("data-bs-x", x);
target.setAttribute("data-bs-y", y);
target.setAttribute('data-bs-x', x);
target.setAttribute('data-bs-y', y);
this.onInteraction(target);
//update the last interacted element
@ -30,12 +28,12 @@ const DraggableUtils = {
},
})
.resizable({
edges: { left: true, right: true, bottom: true, top: true },
edges: {left: true, right: true, bottom: true, top: true},
listeners: {
move: (event) => {
var target = event.target;
var x = parseFloat(target.getAttribute("data-bs-x")) || 0;
var y = parseFloat(target.getAttribute("data-bs-y")) || 0;
var x = parseFloat(target.getAttribute('data-bs-x')) || 0;
var y = parseFloat(target.getAttribute('data-bs-y')) || 0;
// check if control key is pressed
if (event.ctrlKey) {
@ -44,8 +42,7 @@ const DraggableUtils = {
let width = event.rect.width;
let height = event.rect.height;
if (Math.abs(event.deltaRect.width) >= Math.abs(
event.deltaRect.height)) {
if (Math.abs(event.deltaRect.width) >= Math.abs(event.deltaRect.height)) {
height = width / aspectRatio;
} else {
width = height * aspectRatio;
@ -55,19 +52,18 @@ const DraggableUtils = {
event.rect.height = height;
}
target.style.width = event.rect.width + "px";
target.style.height = event.rect.height + "px";
target.style.width = event.rect.width + 'px';
target.style.height = event.rect.height + 'px';
// translate when resizing from top or left edges
x += event.deltaRect.left;
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-y", y);
target.textContent = Math.round(event.rect.width) + "\u00D7"
+ Math.round(event.rect.height);
target.setAttribute('data-bs-x', x);
target.setAttribute('data-bs-y', y);
target.textContent = Math.round(event.rect.width) + '\u00D7' + Math.round(event.rect.height);
this.onInteraction(target);
},
@ -75,7 +71,7 @@ const DraggableUtils = {
modifiers: [
interact.modifiers.restrictSize({
min: { width: 5, height: 5 },
min: {width: 5, height: 5},
}),
],
inertia: true,
@ -95,8 +91,8 @@ const DraggableUtils = {
const stepY = target.offsetHeight * 0.05;
// Get the current x and y coordinates
let x = (parseFloat(target.getAttribute('data-bs-x')) || 0);
let y = (parseFloat(target.getAttribute('data-bs-y')) || 0);
let x = parseFloat(target.getAttribute('data-bs-x')) || 0;
let y = parseFloat(target.getAttribute('data-bs-y')) || 0;
// Check which key was pressed and update the coordinates accordingly
switch (event.key) {
@ -135,15 +131,15 @@ const DraggableUtils = {
},
createDraggableCanvas() {
const createdCanvas = document.createElement("canvas");
const createdCanvas = document.createElement('canvas');
createdCanvas.id = `draggable-canvas-${this.nextId++}`;
createdCanvas.classList.add("draggable-canvas");
createdCanvas.classList.add('draggable-canvas');
const x = 0;
const y = 20;
createdCanvas.style.transform = `translate(${x}px, ${y}px)`;
createdCanvas.setAttribute("data-bs-x", x);
createdCanvas.setAttribute("data-bs-y", y);
createdCanvas.setAttribute('data-bs-x', x);
createdCanvas.setAttribute('data-bs-y', y);
//Click element in order to enable arrow keys
createdCanvas.addEventListener('click', () => {
@ -186,29 +182,29 @@ const DraggableUtils = {
newHeight = newHeight * scaleMultiplier;
}
createdCanvas.style.width = newWidth + "px";
createdCanvas.style.height = newHeight + "px";
createdCanvas.style.width = newWidth + 'px';
createdCanvas.style.height = newHeight + 'px';
var myContext = createdCanvas.getContext("2d");
var myContext = createdCanvas.getContext('2d');
myContext.drawImage(myImage, 0, 0);
resolve(createdCanvas);
};
});
},
deleteAllDraggableCanvases() {
this.boxDragContainer.querySelectorAll(".draggable-canvas").forEach((el) => el.remove());
this.boxDragContainer.querySelectorAll('.draggable-canvas').forEach((el) => el.remove());
},
async addAllPagesDraggableCanvas(element) {
if (element) {
let currentPage = this.pageIndex
let currentPage = this.pageIndex;
if (!this.elementAllPages.includes(element)) {
this.elementAllPages.push(element)
this.elementAllPages.push(element);
element.style.filter = 'sepia(1) hue-rotate(90deg) brightness(1.2)';
let newElement = {
"element": element,
"offsetWidth": element.width,
"offsetHeight": element.height
}
element: element,
offsetWidth: element.width,
offsetHeight: element.height,
};
let pagesMap = this.documentsMap.get(this.pdfDoc);
@ -216,21 +212,20 @@ const DraggableUtils = {
pagesMap = {};
this.documentsMap.set(this.pdfDoc, pagesMap);
}
let page = this.pageIndex
let page = this.pageIndex;
for (let pageIndex = 0; pageIndex < this.pdfDoc.numPages; pageIndex++) {
if (pagesMap[`${pageIndex}-offsetWidth`]) {
if (!pagesMap[pageIndex].includes(newElement)) {
pagesMap[pageIndex].push(newElement);
}
} else {
pagesMap[pageIndex] = []
pagesMap[pageIndex].push(newElement)
pagesMap[pageIndex] = [];
pagesMap[pageIndex].push(newElement);
pagesMap[`${pageIndex}-offsetWidth`] = pagesMap[`${page}-offsetWidth`];
pagesMap[`${pageIndex}-offsetHeight`] = pagesMap[`${page}-offsetHeight`];
}
await this.goToPage(pageIndex)
await this.goToPage(pageIndex);
}
} else {
const index = this.elementAllPages.indexOf(element);
@ -247,17 +242,17 @@ const DraggableUtils = {
for (let pageIndex = 0; pageIndex < this.pdfDoc.numPages; pageIndex++) {
if (pagesMap[`${pageIndex}-offsetWidth`] && pageIndex != currentPage) {
const pageElements = pagesMap[pageIndex];
pageElements.forEach(elementPage => {
const elementIndex = pageElements.findIndex(elementPage => elementPage['element'].id === element.id);
pageElements.forEach((elementPage) => {
const elementIndex = pageElements.findIndex((elementPage) => elementPage['element'].id === element.id);
if (elementIndex !== -1) {
pageElements.splice(elementIndex, 1);
}
});
}
await this.goToPage(pageIndex)
await this.goToPage(pageIndex);
}
}
await this.goToPage(currentPage)
await this.goToPage(currentPage);
}
},
deleteDraggableCanvas(element) {
@ -271,7 +266,7 @@ const DraggableUtils = {
}
},
getLastInteracted() {
return this.boxDragContainer.querySelector(".draggable-canvas:last-of-type");
return this.boxDragContainer.querySelector('.draggable-canvas:last-of-type');
},
storePageContents() {
@ -280,7 +275,7 @@ const DraggableUtils = {
pagesMap = {};
}
const elements = [...this.boxDragContainer.querySelectorAll(".draggable-canvas")];
const elements = [...this.boxDragContainer.querySelectorAll('.draggable-canvas')];
const draggablesData = elements.map((el) => {
return {
element: el,
@ -291,8 +286,8 @@ const DraggableUtils = {
elements.forEach((el) => this.boxDragContainer.removeChild(el));
pagesMap[this.pageIndex] = draggablesData;
pagesMap[this.pageIndex + "-offsetWidth"] = this.pdfCanvas.offsetWidth;
pagesMap[this.pageIndex + "-offsetHeight"] = this.pdfCanvas.offsetHeight;
pagesMap[this.pageIndex + '-offsetWidth'] = this.pdfCanvas.offsetWidth;
pagesMap[this.pageIndex + '-offsetHeight'] = this.pdfCanvas.offsetHeight;
this.documentsMap.set(this.pdfDoc, pagesMap);
},
@ -329,8 +324,8 @@ const DraggableUtils = {
// render the page onto the canvas
var renderContext = {
canvasContext: this.pdfCanvas.getContext("2d"),
viewport: page.getViewport({ scale: 1 }),
canvasContext: this.pdfCanvas.getContext('2d'),
viewport: page.getViewport({scale: 1}),
};
await page.render(renderContext).promise;
@ -358,7 +353,7 @@ const DraggableUtils = {
}
},
parseTransform(element) { },
parseTransform(element) {},
async getOverlayedPdfDocument() {
const pdfBytes = await this.pdfDoc.getData();
const pdfDocModified = await PDFLib.PDFDocument.load(pdfBytes, {
@ -369,7 +364,7 @@ const DraggableUtils = {
const pagesMap = this.documentsMap.get(this.pdfDoc);
for (let pageIdx in pagesMap) {
if (pageIdx.includes("offset")) {
if (pageIdx.includes('offset')) {
continue;
}
console.log(typeof pageIdx);
@ -377,9 +372,8 @@ const DraggableUtils = {
const page = pdfDocModified.getPage(parseInt(pageIdx));
let draggablesData = pagesMap[pageIdx];
const offsetWidth = pagesMap[pageIdx + "-offsetWidth"];
const offsetHeight = pagesMap[pageIdx + "-offsetHeight"];
const offsetWidth = pagesMap[pageIdx + '-offsetWidth'];
const offsetHeight = pagesMap[pageIdx + '-offsetHeight'];
for (const draggableData of draggablesData) {
// embed the draggable canvas
@ -389,8 +383,8 @@ const DraggableUtils = {
const pdfImageObject = await pdfDocModified.embedPng(draggableImgBytes);
// calculate the position in the pdf document
const tansform = draggableElement.style.transform.replace(/[^.,-\d]/g, "");
const transformComponents = tansform.split(",");
const tansform = draggableElement.style.transform.replace(/[^.,-\d]/g, '');
const transformComponents = tansform.split(',');
const draggablePositionPixels = {
x: parseFloat(transformComponents[0]),
y: parseFloat(transformComponents[1]),
@ -429,9 +423,8 @@ const DraggableUtils = {
};
//Defining the image if the page has a 0-degree angle
let x = draggablePositionPdf.x
let y = heightAdjusted - draggablePositionPdf.y - draggablePositionPdf.height
let x = draggablePositionPdf.x;
let y = heightAdjusted - draggablePositionPdf.y - draggablePositionPdf.height;
//Defining the image position if it is at other angles
if (normalizedAngle === 90) {
@ -451,7 +444,7 @@ const DraggableUtils = {
y: y,
width: draggablePositionPdf.width,
height: draggablePositionPdf.height,
rotate: rotation
rotate: rotation,
});
}
}
@ -460,6 +453,6 @@ const DraggableUtils = {
},
};
document.addEventListener("DOMContentLoaded", () => {
document.addEventListener('DOMContentLoaded', () => {
DraggableUtils.init();
});

View File

@ -1,20 +1,20 @@
import FileIconFactory from "./file-icon-factory.js";
import FileUtils from "./file-utils.js";
import FileIconFactory from './file-icon-factory.js';
import FileUtils from './file-utils.js';
import UUID from './uuid.js';
import {DecryptFile} from './DecryptFiles.js';
const decryptFile = new DecryptFile();
let isScriptExecuted = false;
if (!isScriptExecuted) {
isScriptExecuted = true;
document.addEventListener("DOMContentLoaded", function () {
document.querySelectorAll(".custom-file-chooser").forEach(setupFileInput);
document.addEventListener('DOMContentLoaded', function () {
document.querySelectorAll('.custom-file-chooser').forEach(setupFileInput);
});
}
function setupFileInput(chooser) {
const elementId = chooser.getAttribute("data-bs-element-id");
const filesSelected = chooser.getAttribute("data-bs-files-selected");
const pdfPrompt = chooser.getAttribute("data-bs-pdf-prompt");
const elementId = chooser.getAttribute('data-bs-element-id');
const filesSelected = chooser.getAttribute('data-bs-files-selected');
const pdfPrompt = chooser.getAttribute('data-bs-pdf-prompt');
const inputContainerId = chooser.getAttribute('data-bs-element-container-id');
let inputContainer = document.getElementById(inputContainerId);
@ -26,7 +26,7 @@ function setupFileInput(chooser) {
inputContainer.addEventListener('click', (e) => {
let inputBtn = document.getElementById(elementId);
inputBtn.click();
})
});
const dragenterListener = function () {
dragCounter++;
@ -63,7 +63,7 @@ function setupFileInput(chooser) {
const files = dt.files;
const fileInput = document.getElementById(elementId);
if (fileInput?.hasAttribute("multiple")) {
if (fileInput?.hasAttribute('multiple')) {
pushFileListTo(files, allFiles);
} else if (fileInput) {
allFiles = [files[0]];
@ -78,7 +78,7 @@ function setupFileInput(chooser) {
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) {
@ -87,7 +87,7 @@ function setupFileInput(chooser) {
}
}
["dragenter", "dragover", "dragleave", "drop"].forEach((eventName) => {
['dragenter', 'dragover', 'dragleave', 'drop'].forEach((eventName) => {
document.body.addEventListener(eventName, preventDefaults, false);
});
@ -96,37 +96,49 @@ function setupFileInput(chooser) {
e.stopPropagation();
}
document.body.addEventListener("dragenter", dragenterListener);
document.body.addEventListener("dragleave", dragleaveListener);
document.body.addEventListener("drop", dropListener);
document.body.addEventListener('dragenter', dragenterListener);
document.body.addEventListener('dragleave', dragleaveListener);
document.body.addEventListener('drop', dropListener);
$("#" + elementId).on("change", function (e) {
$('#' + elementId).on('change', async function (e) {
let element = e.target;
const isDragAndDrop = e.detail?.source == 'drag-drop';
if (element instanceof HTMLInputElement && element.hasAttribute("multiple")) {
allFiles = isDragAndDrop ? allFiles : [... allFiles, ... element.files];
if (element instanceof HTMLInputElement && element.hasAttribute('multiple')) {
allFiles = isDragAndDrop ? allFiles : [...allFiles, ...element.files];
} else {
allFiles = Array.from(isDragAndDrop ? allFiles : [element.files[0]]);
}
allFiles = allFiles.map(file => {
if (!file.uniqueId) file.uniqueId = UUID.uuidv4();
return file;
});
allFiles = await Promise.all(
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);
return file;
}
})
);
if (!isDragAndDrop) {
let dataTransfer = toDataTransfer(allFiles);
element.files = dataTransfer.files;
let dataTransfer = toDataTransfer(allFiles);
element.files = dataTransfer.files;
}
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) {
let dataTransfer = new DataTransfer();
files.forEach(file => dataTransfer.items.add(file));
files.forEach((file) => dataTransfer.items.add(file));
return dataTransfer;
}
@ -136,7 +148,7 @@ function setupFileInput(chooser) {
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();
filesInfo.forEach((info) => {
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) {
if (files && files.length > 0)
chooser.style.setProperty('--selected-files-display', 'flex');
else
chooser.style.setProperty('--selected-files-display', 'none');
if (files && files.length > 0) chooser.style.setProperty('--selected-files-display', 'flex');
else chooser.style.setProperty('--selected-files-display', 'none');
}
function removeFileListener(e) {
const fileId = (e.target).getAttribute('data-file-id');
const fileId = e.target.getAttribute('data-file-id');
let inputElement = document.getElementById(elementId);
removeFileById(fileId, inputElement);
showOrHideSelectedFilesContainer(allFiles);
inputElement.dispatchEvent(new CustomEvent("file-input-change", { bubbles: true }));
inputElement.dispatchEvent(new CustomEvent('file-input-change', {bubbles: true}));
}
function removeFileById(fileId, inputElement) {
let fileContainer = document.getElementById(fileId);
fileContainer.remove();
allFiles = allFiles.filter(v => v.uniqueId != fileId);
allFiles = allFiles.filter((v) => v.uniqueId != fileId);
let dataTransfer = toDataTransfer(allFiles);
if (inputElement) inputElement.files = dataTransfer.files;
@ -207,23 +217,19 @@ function setupFileInput(chooser) {
}
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';
$(fileInfoContainer).addClass(fileInfoContainerClasses);
$(fileInfoContainer).append(
`<div title="${info.name}">${info.name}</div>`
);
$(fileInfoContainer).append(`<div title="${info.name}">${info.name}</div>`);
let fileSizeWithUnits = FileUtils.transformFileSize(info.size);
$(fileInfoContainer).append(
`<div title="${info.size}">${fileSizeWithUnits}</div>`
);
$(fileInfoContainer).append(`<div title="${info.size}">${fileSizeWithUnits}</div>`);
return fileInfoContainer;
}
//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;
let inputElement = document.getElementById(elementId);
removeFileById(fileId, inputElement);

View File

@ -5,7 +5,7 @@ import {SplitAllCommand} from './commands/split.js';
import {UndoManager} from './UndoManager.js';
import {PageBreakCommand} from './commands/page-break.js';
import {AddFilesCommand} from './commands/add-page.js';
import {DecryptFile} from './DecryptFiles.js';
import {DecryptFile} from '../DecryptFiles.js';
class PdfContainer {
fileName;

View 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();
});

View File

@ -0,0 +1,215 @@
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;
// Wait for the second function to complete
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();
});

View File

@ -204,8 +204,7 @@
<script type="module" th:src="@{'/pdfjs-legacy/pdf.mjs'}"></script>
<script th:src="@{'/js/downloader.js'}"></script>
<script>
window.translations = {
decrypt: {
window.decrypt = {
passwordPrompt: '[[#{decrypt.passwordPrompt}]]',
cancelled: '[[#{decrypt.cancelled}]]',
noPassword: '[[#{decrypt.noPassword}]]',
@ -214,7 +213,6 @@
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="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}">

View File

@ -55,247 +55,7 @@
</form>
<script type="module" th:src="@{'/pdfjs-legacy/pdf.mjs'}"></script>
<script th:src="@{'/js/thirdParty/pdf-lib.min.js'}"></script>
<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>
<script type="module" th:src="@{'/js/pages/adjust-contrast.js'}"></script>
</div>
</div>
</div>

View File

@ -163,17 +163,18 @@
dragDropMessage:'[[#{multiTool.dragDropMessage}]]',
undo: '[[#{multiTool.undo}]]',
redo: '[[#{multiTool.redo}]]',
passwordPrompt: '[[#{decrypt.passwordPrompt}]]',
cancelled: '[[#{decrypt.cancelled}]]',
noPassword: '[[#{decrypt.noPassword}]]',
invalidPassword: '[[#{decrypt.invalidPassword}]]',
invalidPasswordHeader: '[[#{decrypt.invalidPasswordHeader}]]',
unexpectedError: '[[#{decrypt.unexpectedError}]]',
serverError: '[[#{decrypt.serverError}]]',
success: '[[#{decrypt.success}]]',
};
window.decrypt = {
passwordPrompt: '[[#{decrypt.passwordPrompt}]]',
cancelled: '[[#{decrypt.cancelled}]]',
noPassword: '[[#{decrypt.noPassword}]]',
invalidPassword: '[[#{decrypt.invalidPassword}]]',
invalidPasswordHeader: '[[#{decrypt.invalidPasswordHeader}]]',
unexpectedError: '[[#{decrypt.unexpectedError}]]',
serverError: '[[#{decrypt.serverError}]]',
success: '[[#{decrypt.success}]]',
}
const csvInput = document.getElementById("csv-input");
csvInput.addEventListener("keydown", function (event) {

View File

@ -19,9 +19,10 @@
}
</style>
</th:block>
<script th:src="@{'/js/thirdParty/signature_pad.umd.min.js'}"></script>
<script th:src="@{'/js/thirdParty/interact.min.js'}"></script>
<script type="module" th:src="@{'/js/pages/sign.js'}"></script>
</head>
<body>
@ -42,75 +43,6 @@
th:replace="~{fragments/common :: fileSelector(name='pdf-upload', multipleInputsForSingleRequest=false, disableMultipleFiles=true, accept='application/pdf')}">
</div>
<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-container" th:title="#{sign.upload}">
<div
@ -237,123 +169,6 @@
</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 -->
<div id="box-drag-container" class="show-on-file-selected">
<canvas id="pdf-canvas"></canvas>
@ -410,35 +225,11 @@
</button>
</div>
</div>
<!-- download button -->
<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>
</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>
@ -447,6 +238,7 @@
</div>
<!-- Link the draggable.js file -->
<script th:src="@{'/js/draggable.js'}"></script>
</body>
</html>