flatten pdf

This commit is contained in:
Anthony Stirling 2023-05-04 23:19:05 +01:00
parent a342def43f
commit acda1e4dd8
4 changed files with 243 additions and 97 deletions

View file

@ -0,0 +1,47 @@
async function downloadFilesWithCallback(processFileCallback) {
const fileInput = document.querySelector('input[type="file"]');
const files = fileInput.files;
const zipThreshold = 4;
const zipFiles = files.length > zipThreshold;
let jszip = null;
if (zipFiles) {
jszip = new JSZip();
}
const promises = Array.from(files).map(async file => {
const { processedData, fileName } = await processFileCallback(file);
if (zipFiles) {
jszip.file(fileName, processedData);
} else {
const url = URL.createObjectURL(processedData);
const downloadOption = localStorage.getItem('downloadOption');
if (downloadOption === 'sameWindow') {
window.location.href = url;
} else if (downloadOption === 'newWindow') {
window.open(url, '_blank');
} else {
const downloadLink = document.createElement('a');
downloadLink.href = url;
downloadLink.download = fileName;
downloadLink.click();
}
}
});
await Promise.all(promises);
if (zipFiles) {
const content = await jszip.generateAsync({ type: "blob" });
const url = URL.createObjectURL(content);
const a = document.createElement('a');
a.href = url;
a.download = "files.zip";
document.body.appendChild(a);
a.click();
a.remove();
}
}

View file

@ -205,7 +205,7 @@ document.addEventListener("DOMContentLoaded", function () {
</dialog> </dialog>
</th:block> </th:block>
<th:block th:fragment="fileSelector(name, multiple)" th:with="accept=${accept} ?: '*/*', inputText=${inputText} ?: #{pdfPrompt}"> <th:block th:fragment="fileSelector(name, multiple)" th:with="accept=${accept} ?: '*/*', inputText=${inputText} ?: #{pdfPrompt}, remoteCall=${remoteCall} ?: 'true'">
<script> <script>
$(document).ready(function() { $(document).ready(function() {
$('form').submit(async function(event) { $('form').submit(async function(event) {
@ -227,6 +227,8 @@ document.addEventListener("DOMContentLoaded", function () {
var multiple = [[${multiple}]] || false; var multiple = [[${multiple}]] || false;
var override = $('#override').val() || ''; var override = $('#override').val() || '';
console.log("override=" + override) console.log("override=" + override)
if([[${remoteCall}]] === true) {
if (override === 'multi' || (!multiple && files.length > 1) && override !== 'single' ) { if (override === 'multi' || (!multiple && files.length > 1) && override !== 'single' ) {
console.log("multi parallel download") console.log("multi parallel download")
await submitMultiPdfForm(event,url); await submitMultiPdfForm(event,url);
@ -325,6 +327,10 @@ document.addEventListener("DOMContentLoaded", function () {
}; };
} }
} else {
//Offline do nothing and let other scripts handle everything
}
$('#submitBtn').text(submitButtonText); $('#submitBtn').text(submitButtonText);
}); });
}); });
@ -467,7 +473,7 @@ document.addEventListener("DOMContentLoaded", function () {
<div class="custom-file-chooser"> <div class="custom-file-chooser">
<div class="custom-file"> <div class="custom-file">
<input type="file" class="custom-file-input" th:name="${name}" th:id="${name}+'-input'" th:accept="${accept}" multiple> <input type="file" class="custom-file-input" th:name="${name}" th:id="${name}+'-input'" th:accept="${accept}" multiple required>
<label class="custom-file-label" th:for="${name}+'-input'" th:text="${inputText}"></label> <label class="custom-file-label" th:for="${name}+'-input'" th:text="${inputText}"></label>
</div> </div>
<div class="selected-files"></div> <div class="selected-files"></div>

View file

@ -0,0 +1,57 @@
<!DOCTYPE html>
<html th:lang="${#locale.toString()}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org">
<th:block th:insert="~{fragments/common :: head(title=#{flatten.title})}"></th:block>
<body>
<div id="page-container">
<div id="content-wrap">
<div th:insert="~{fragments/navbar.html :: navbar}"></div>
<br> <br>
<div class="container">
<div class="row justify-content-center">
<div class="col-md-6">
<h2 th:text="#{flatten.header}"></h2>
<form id="pdfForm" class="mb-3">
<div class="custom-file">
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf', remoteCall='false')}"></div>
</div>
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{flatten.submit}"></button>
<script src="js/local-pdf-input-download.js"></script>
<script>
document.getElementById('pdfForm').addEventListener('submit', async (e) => {
e.preventDefault();
const { PDFDocument } = PDFLib;
const processFile = async (file) => {
const origFileUrl = URL.createObjectURL(file);
const formPdfBytes = await fetch(origFileUrl).then(res => res.arrayBuffer());
const pdfDoc = await PDFDocument.load(formPdfBytes);
const form = pdfDoc.getForm();
form.flatten();
const pdfBytes = await pdfDoc.save();
const pdfBlob = new Blob([pdfBytes], { type: 'application/pdf' });
const fileName = (file.name ? file.name.replace('.pdf', '') : 'pdf') + '_flattened.pdf';
return { processedData: pdfBlob, fileName };
};
await downloadFilesWithCallback(processFile);
});
</script>
</form>
</div>
</div>
</div>
</div>
<div th:insert="~{fragments/footer.html :: footer}"></div>
</div>
</body>
</html>

View file

@ -60,8 +60,11 @@
<div class = "btn-group"> <div class = "btn-group">
<input type="radio" class="btn-check" name="signature-type" id="draw-signature" autocomplete="off" checked> <input type="radio" class="btn-check" name="signature-type" id="draw-signature" autocomplete="off" checked>
<label class="btn btn-outline-secondary" for="draw-signature">Draw signature</label> <label class="btn btn-outline-secondary" for="draw-signature">Draw signature</label>
<input type="radio" class="btn-check" name="signature-type" id="generate-signature" autocomplete="off">
<label class="btn btn-outline-secondary" for="generate-signature">Generate Signature</label>
<input type="radio" class="btn-check" name="signature-type" id="import-image" autocomplete="off"> <input type="radio" class="btn-check" name="signature-type" id="import-image" autocomplete="off">
<label class="btn btn-outline-secondary" for="import-image">Import image</label> <label class="btn btn-outline-secondary" for="import-image">Import image</label>
</div> </div>
<div th:replace="~{fragments/common :: fileSelector(name='signature-upload', multiple=false, accept='image/*', inputText=#{imgPrompt})}"></div> <div th:replace="~{fragments/common :: fileSelector(name='signature-upload', multiple=false, accept='image/*', inputText=#{imgPrompt})}"></div>
@ -72,6 +75,10 @@
<button id="clear-signature" class="btn btn-outline-danger mt-2">Clear</button> <button id="clear-signature" class="btn btn-outline-danger mt-2">Clear</button>
<button id="save-signature" class="btn btn-outline-success mt-2">Save</button> <button id="save-signature" class="btn btn-outline-success mt-2">Save</button>
</div> </div>
<div id="signature-text-input-container" style="display: none;">
<input type="text" id="signature-text-input" class="form-control mt-2" placeholder="Type your signature here...">
</div>
<div id="pdf-container"> <div id="pdf-container">
<canvas id="pdf-canvas"></canvas> <canvas id="pdf-canvas"></canvas>
@ -89,6 +96,7 @@
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
const pdfUpload = document.querySelector('input[name=pdf-upload]'); const pdfUpload = document.querySelector('input[name=pdf-upload]');
const signatureUpload = document.querySelector('input[name=signature-upload]'); const signatureUpload = document.querySelector('input[name=signature-upload]');
const signatureTextInput = document.querySelector('input[name=signatureTextInput]');
const pdfCanvas = document.getElementById('pdf-canvas'); const pdfCanvas = document.getElementById('pdf-canvas');
const signatureCanvas = document.getElementById('signature-canvas'); const signatureCanvas = document.getElementById('signature-canvas');
const downloadPdfBtn = document.getElementById('download-pdf'); const downloadPdfBtn = document.getElementById('download-pdf');
@ -124,18 +132,45 @@
signaturePad.clear(); signaturePad.clear();
}); });
$("input[name=signature-type]").change(function() { $("input[name=signature-type]").change(function () {
const drawSignatureInput = document.getElementById('draw-signature'); const drawSignatureInput = document.getElementById("draw-signature");
signaturePadContainer.style.display = drawSignatureInput.checked ? 'block' : 'none'; const generateSignatureInput = document.getElementById("generate-signature");
document.querySelector('input[name=signature-upload]').closest(".custom-file-chooser").style.display = drawSignatureInput.checked ? 'none' : 'block'; const importImageInputContainer = document.querySelector("input[name=signature-upload]").closest(".custom-file-chooser");
signaturePadContainer.style.display = drawSignatureInput.checked ? "block" : "none";
importImageInputContainer.style.display = drawSignatureInput.checked ? "none" : (generateSignatureInput.checked ? "none" : "block");
document.getElementById("signature-text-input-container").style.display = generateSignatureInput.checked ? "block" : "none";
if (drawSignatureInput.checked) { if (drawSignatureInput.checked) {
populateSignatureFromPad(); populateSignatureFromPad();
} else if (generateSignatureInput.checked) {
populateSignatureFromText();
} else { } else {
populateSignatureFromFileUpload(); populateSignatureFromFileUpload();
} }
}); });
function populateSignatureFromText() {
const signatureText = document.getElementById("signature-text-input").value;
if (!signatureText) return;
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
ctx.font = "32px Arial";
const textMetrics = ctx.measureText(signatureText);
canvas.width = textMetrics.width;
canvas.height = parseInt(ctx.font, 10);
ctx.fillStyle = "black";
ctx.font = "32px Arial";
ctx.fillText(signatureText, 0, canvas.height * 0.8);
const dataURL = canvas.toDataURL();
populateSignature(dataURL);
}
function populateSignature(imgUrl) { function populateSignature(imgUrl) {
const img = new Image(); const img = new Image();
img.onload = () => { img.onload = () => {
@ -184,6 +219,7 @@
populateSignature(dataURL); populateSignature(dataURL);
} }
signatureUpload.addEventListener('change', populateSignatureFromFileUpload); signatureUpload.addEventListener('change', populateSignatureFromFileUpload);
signatureTextInput.addEventListener('change', populateSignatureFromText);
saveSignatureBtn.addEventListener('click', populateSignatureFromPad); saveSignatureBtn.addEventListener('click', populateSignatureFromPad);