mirror of
				https://github.com/Frooodle/Stirling-PDF.git
				synced 2025-10-25 11:17:28 +02:00 
			
		
		
		
	sign v2
This commit is contained in:
		
							parent
							
								
									9af537c985
								
							
						
					
					
						commit
						fd246aac93
					
				| @ -66,4 +66,15 @@ public class GeneralWebController { | |||||||
|         model.addAttribute("currentPage", "split-pdfs"); |         model.addAttribute("currentPage", "split-pdfs"); | ||||||
|         return "split-pdfs"; |         return "split-pdfs"; | ||||||
|     } |     } | ||||||
|  |      | ||||||
|  |      | ||||||
|  |      | ||||||
|  |      | ||||||
|  |     @GetMapping("/sign") | ||||||
|  |     @Hidden | ||||||
|  |     public String signForm(Model model) { | ||||||
|  |         model.addAttribute("currentPage", "sign"); | ||||||
|  |         return "sign"; | ||||||
|  |     } | ||||||
|  |      | ||||||
| } | } | ||||||
|  | |||||||
							
								
								
									
										282
									
								
								src/main/resources/templates/sign.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										282
									
								
								src/main/resources/templates/sign.html
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,282 @@ | |||||||
|  | <!DOCTYPE html> | ||||||
|  | <html th:lang="${#locale.language}" th:lang-direction="#{language.direction}" xmlns:th="http://www.thymeleaf.org"> | ||||||
|  | 
 | ||||||
|  | <head> | ||||||
|  |     <th:block th:insert="~{fragments/common :: head(title=#{extractImages.title})}"></th:block> | ||||||
|  |     <meta name="viewport" content="width=device-width, initial-scale=1.0"> | ||||||
|  |     <title>PDF Signature App</title> | ||||||
|  |     <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.0/dist/css/bootstrap.min.css" rel="stylesheet"> | ||||||
|  |     <script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.12.313/pdf.min.js"></script> | ||||||
|  |     <script src="https://unpkg.com/pdf-lib@1.18.0/dist/umd/pdf-lib.min.js"></script> | ||||||
|  |     <script src="https://cdn.jsdelivr.net/npm/signature_pad@4.1.5/dist/signature_pad.umd.min.js"></script> | ||||||
|  |     <script src="https://cdnjs.cloudflare.com/ajax/libs/interact.js/1.10.11/interact.min.js"></script> | ||||||
|  |     <style> | ||||||
|  |        #pdf-container { | ||||||
|  |     position: relative; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #pdf-canvas { | ||||||
|  |     border: 1px solid black; | ||||||
|  |     margin-top: 10px; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #signature-canvas { | ||||||
|  |     border: 1px solid red; | ||||||
|  |     position: absolute; | ||||||
|  |     touch-action: none; | ||||||
|  |     top: 10px; /* Make sure this value matches the margin-top of #pdf-canvas */ | ||||||
|  |     left: 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .signature-pad-container { | ||||||
|  |     border: 1px solid black; | ||||||
|  |     display: none; | ||||||
|  |     margin-top: 10px; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  |     </style> | ||||||
|  | </head> | ||||||
|  | 
 | ||||||
|  | <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="#{extractImages.header}"></h2> | ||||||
|  |                         <input type="file" id="pdf-upload" accept=".pdf" class="btn btn-outline-primary mb-2" /> | ||||||
|  |                         <input type="file" id="signature-upload" accept="image/*" class="btn btn-outline-primary mb-2" /> | ||||||
|  |                         <button id="toggle-draw-signature" class="btn btn-outline-primary mb-2">Draw signature</button> | ||||||
|  |                         <button id="download-pdf" class="btn btn-outline-primary mb-2">Download PDF</button> | ||||||
|  |                         <div id="pdf-container"> | ||||||
|  |                             <canvas id="pdf-canvas"></canvas> | ||||||
|  |                             <canvas id="signature-canvas" hidden style="position: absolute;" data-scale="1"></canvas> | ||||||
|  |                         </div> | ||||||
|  |                         <!-- Signature Pad --> | ||||||
|  |                         <div class="signature-pad-container"> | ||||||
|  |                             <canvas id="signature-pad-canvas"></canvas> | ||||||
|  |                             <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> | ||||||
|  |                         </div> | ||||||
|  |                     </div> | ||||||
|  |                 </div> | ||||||
|  |             </div> | ||||||
|  |         </div> | ||||||
|  |         <div th:insert="~{fragments/footer.html :: footer}"></div> | ||||||
|  |     </div> | ||||||
|  |     <script> | ||||||
|  |     document.addEventListener('DOMContentLoaded', () => { | ||||||
|  |         const pdfUpload = document.getElementById('pdf-upload'); | ||||||
|  |         const signatureUpload = document.getElementById('signature-upload'); | ||||||
|  |         const pdfCanvas = document.getElementById('pdf-canvas'); | ||||||
|  |         const signatureCanvas = document.getElementById('signature-canvas'); | ||||||
|  |         const downloadPdfBtn = document.getElementById('download-pdf'); | ||||||
|  |         const toggleDrawSignatureBtn = document.getElementById('toggle-draw-signature'); | ||||||
|  |         const signaturePadCanvas = document.getElementById('signature-pad-canvas'); | ||||||
|  |         const clearSignatureBtn = document.getElementById('clear-signature'); | ||||||
|  |         const saveSignatureBtn = document.getElementById('save-signature'); | ||||||
|  |          | ||||||
|  |         const signaturePad = new SignaturePad(signaturePadCanvas, { | ||||||
|  |             minWidth: 1, | ||||||
|  |             maxWidth: 2, | ||||||
|  |             penColor: 'black', | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |          | ||||||
|  |         const pdfCtx = pdfCanvas.getContext('2d'); | ||||||
|  |         let pdfDoc = null; | ||||||
|  | 
 | ||||||
|  |         pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.12.313/pdf.worker.min.js'; | ||||||
|  | 
 | ||||||
|  |         async function loadPdf(pdfData) { | ||||||
|  |             pdfDoc = await pdfjsLib.getDocument({ data: pdfData }).promise; | ||||||
|  |             renderPage(1); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         pdfUpload.addEventListener('change', async (event) => { | ||||||
|  |             const file = event.target.files[0]; | ||||||
|  |             if (file) { | ||||||
|  |                 const pdfData = await file.arrayBuffer(); | ||||||
|  |                 loadPdf(pdfData); | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |         signatureUpload.addEventListener('change', async (event) => { | ||||||
|  |             const file = event.target.files[0]; | ||||||
|  |             if (file) { | ||||||
|  |                 const reader = new FileReader(); | ||||||
|  |                 reader.onload = (e) => { | ||||||
|  |                     const img = new Image(); | ||||||
|  |                     img.src = e.target.result; | ||||||
|  |                     img.onload = () => { | ||||||
|  |                         const ctx = signatureCanvas.getContext('2d'); | ||||||
|  |                         ctx.drawImage(img, 0, 0, img.width, img.height); | ||||||
|  |                         signatureCanvas.width = img.width; | ||||||
|  |                         signatureCanvas.height = img.height; | ||||||
|  |                         signatureCanvas.hidden = false; | ||||||
|  |                         setTimeout(() => { | ||||||
|  |                             const x = 0; | ||||||
|  |                             const y = 0; | ||||||
|  |                             signatureCanvas.style.transform = `translate(${x}px, ${y}px)`; | ||||||
|  |                             signatureCanvas.setAttribute('data-x', x); | ||||||
|  |                             signatureCanvas.setAttribute('data-y', y); | ||||||
|  |                         }, 0); | ||||||
|  |                     }; | ||||||
|  |                 }; | ||||||
|  |                 reader.readAsDataURL(file); | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |         let usingSignaturePad = false; | ||||||
|  | 
 | ||||||
|  |         toggleDrawSignatureBtn.addEventListener('click', () => { | ||||||
|  |             usingSignaturePad = !usingSignaturePad; | ||||||
|  |             if (usingSignaturePad) { | ||||||
|  |                 signatureCanvas.width = 0; | ||||||
|  |                 signatureCanvas.height = 0; | ||||||
|  |             } | ||||||
|  |             document.querySelector('.signature-pad-container').style.display = usingSignaturePad ? 'block' : 'none'; | ||||||
|  |             toggleDrawSignatureBtn.textContent = usingSignaturePad | ||||||
|  |                 ? 'Import signature image' | ||||||
|  |                 : 'Draw signature'; | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |         clearSignatureBtn.addEventListener('click', () => { | ||||||
|  |             signaturePad.clear(); | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |         saveSignatureBtn.addEventListener('click', async () => { | ||||||
|  |             if (!signaturePad.isEmpty()) { | ||||||
|  |                 const dataURL = signaturePad.toDataURL(); | ||||||
|  |                 const img = new Image(); | ||||||
|  |                 img.src = dataURL; | ||||||
|  |                 img.onload = () => { | ||||||
|  |                     const ctx = signatureCanvas.getContext('2d'); | ||||||
|  |                     ctx.clearRect(0, 0, signatureCanvas.width, signatureCanvas.height); | ||||||
|  |                     signatureCanvas.width = img.width; | ||||||
|  |                     signatureCanvas.height = img.height; | ||||||
|  |                     ctx.drawImage(img, 0, 0, img.width, img.height); | ||||||
|  |                     signatureCanvas.hidden = false; | ||||||
|  |                     setTimeout(() => { | ||||||
|  |                         const x = 0; | ||||||
|  |                         const y = 0; | ||||||
|  |                         signatureCanvas.style.transform = `translate(${x}px, ${y}px)`; | ||||||
|  |                         signatureCanvas.setAttribute('data-x', x); | ||||||
|  |                         signatureCanvas.setAttribute('data-y', y); | ||||||
|  |                     }, 0); | ||||||
|  |                 }; | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |         function renderPage(pageNum) { | ||||||
|  |             pdfDoc.getPage(pageNum).then((page) => { | ||||||
|  |                 const viewport = page.getViewport({ scale: 1 }); | ||||||
|  |                 pdfCanvas.width = viewport.width; | ||||||
|  |                 pdfCanvas.height = viewport.height; | ||||||
|  | 
 | ||||||
|  |                 const renderCtx = { | ||||||
|  |                     canvasContext: pdfCtx, | ||||||
|  |                     viewport: viewport, | ||||||
|  |                 }; | ||||||
|  | 
 | ||||||
|  |                 page.render(renderCtx); | ||||||
|  |             }); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         interact('#signature-canvas') | ||||||
|  |         .draggable({ | ||||||
|  |             listeners: { | ||||||
|  |                 move: (event) => { | ||||||
|  |                     const target = event.target; | ||||||
|  |                     const x = (parseFloat(target.getAttribute('data-x')) || 0) + event.dx; | ||||||
|  |                     const y = (parseFloat(target.getAttribute('data-y')) || 0) + event.dy; | ||||||
|  | 
 | ||||||
|  |                     target.style.transform = `translate(${x}px, ${y}px)`; | ||||||
|  |                     target.setAttribute('data-x', x); | ||||||
|  |                     target.setAttribute('data-y', y); | ||||||
|  |                 }, | ||||||
|  |             }, | ||||||
|  |         }) | ||||||
|  |         .resizable({ | ||||||
|  |             edges: { left: true, right: true, bottom: true, top: true }, | ||||||
|  |             listeners: { | ||||||
|  |                 move: (event) => { | ||||||
|  |                     const target = event.target; | ||||||
|  |                     const x = (parseFloat(target.getAttribute('data-x')) || 0); | ||||||
|  |                     const y = (parseFloat(target.getAttribute('data-y')) || 0); | ||||||
|  | 
 | ||||||
|  |                     const newWidth = event.rect.width; | ||||||
|  |                     const newHeight = event.rect.height; | ||||||
|  |                     const scale = newWidth / target.width; | ||||||
|  | 
 | ||||||
|  |                     target.style.width = newWidth + 'px'; | ||||||
|  |                     target.style.height = newHeight + 'px'; | ||||||
|  |                     target.setAttribute('data-scale', scale); | ||||||
|  | 
 | ||||||
|  |                     target.style.transform = `translate(${x}px, ${y}px)`; | ||||||
|  |                 }, | ||||||
|  |             }, | ||||||
|  |             modifiers: [ | ||||||
|  |                 interact.modifiers.restrictSize({ | ||||||
|  |                     min: { width: 50, height: 50 }, | ||||||
|  |                 }), | ||||||
|  |             ], | ||||||
|  |             inertia: true, | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |         async function getSignatureImage() { | ||||||
|  |             const dataURL = signatureCanvas.toDataURL(); | ||||||
|  |             return dataURLToArrayBuffer(dataURL); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         downloadPdfBtn.addEventListener('click', async () => { | ||||||
|  |             if (pdfDoc) { | ||||||
|  |                 const pdfBytes = await pdfDoc.getData(); | ||||||
|  |                 const pdfDocModified = await PDFLib.PDFDocument.load(pdfBytes); | ||||||
|  | 
 | ||||||
|  |                 if (signatureCanvas) { | ||||||
|  |                     const signatureBytes = await getSignatureImage(); | ||||||
|  |                     const signatureImageObject = await pdfDocModified.embedPng(signatureBytes); | ||||||
|  | 
 | ||||||
|  |                     const pageIndex = 0; // Choose the page index where the signature should be added (0 is the first page) | ||||||
|  |                     const page = pdfDocModified.getPages()[pageIndex]; | ||||||
|  | 
 | ||||||
|  |                     const targetElement = signatureCanvas; | ||||||
|  |                     const x = parseFloat(targetElement.getAttribute('data-x')) || 0; | ||||||
|  |                     const y = parseFloat(targetElement.getAttribute('data-y')) || 0; | ||||||
|  |                     const scale = parseFloat(targetElement.getAttribute('data-scale')) || 1; | ||||||
|  | 
 | ||||||
|  |                     page.drawImage(signatureImageObject, { | ||||||
|  |                         x: x, | ||||||
|  |                         y: page.getHeight() - y - (targetElement.height * scale), | ||||||
|  |                         width: targetElement.width * scale, | ||||||
|  |                         height: targetElement.height * scale, | ||||||
|  |                     }); | ||||||
|  | 
 | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 const modifiedPdfBytes = await pdfDocModified.save(); | ||||||
|  |                 const blob = new Blob([modifiedPdfBytes], { type: 'application/pdf' }); | ||||||
|  |                 const link = document.createElement('a'); | ||||||
|  |                 link.href = URL.createObjectURL(blob); | ||||||
|  |                 link.download = 'signed-document.pdf'; | ||||||
|  |                 link.click(); | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |          | ||||||
|  |         async function dataURLToArrayBuffer(dataURL) { | ||||||
|  |             const response = await fetch(dataURL); | ||||||
|  |             return response.arrayBuffer(); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |          | ||||||
|  |     }); | ||||||
|  | </script> | ||||||
		Loading…
	
		Reference in New Issue
	
	Block a user