From ce43ace3c23c60461852f9f7b20c29a8238ddb81 Mon Sep 17 00:00:00 2001 From: TekH Date: Sun, 7 Jun 2026 13:47:34 +0200 Subject: [PATCH] Refactor signature handling and add PDF signature support Refactored `OnSignatureButtonClick` in `EnvelopeViewer.razor`: - Converted to async and added null-check for `_capturedSignature`. - Integrated `pdfViewer.applySignature` to apply signatures to PDFs. Added `applySignature` method to `pdf-viewer.js`: - Handles rendering of signatures with image, metadata, and styling. - Follows German standards for signature formatting. - Includes error handling for missing elements. Introduced `escapeHtml` helper in `pdf-viewer.js` to prevent XSS. Updated `MaxThumbnailWidth` in `EnvelopeViewer.razor` to 400. Enhanced logging for better debugging during signature application. --- .../Pages/EnvelopeViewer.razor | 16 ++- .../wwwroot/js/pdf-viewer.js | 126 ++++++++++++++++++ 2 files changed, 139 insertions(+), 3 deletions(-) diff --git a/EnvelopeGenerator.ReceiverUI/Pages/EnvelopeViewer.razor b/EnvelopeGenerator.ReceiverUI/Pages/EnvelopeViewer.razor index e4390286..75e6acbe 100644 --- a/EnvelopeGenerator.ReceiverUI/Pages/EnvelopeViewer.razor +++ b/EnvelopeGenerator.ReceiverUI/Pages/EnvelopeViewer.razor @@ -566,9 +566,19 @@ const int MaxThumbnailWidth = 400; } [JSInvokable] - public void OnSignatureButtonClick(int signatureId) { - Console.WriteLine($"Signature #{signatureId} clicked"); - OpenSignaturePopup(); + public async Task OnSignatureButtonClick(int signatureId) { + if (_capturedSignature == null) { + // No signature captured yet - should not happen as popup is shown on page load + return; + } + + // Apply signature to PDF canvas + await JSRuntime.InvokeVoidAsync("pdfViewer.applySignature", + signatureId, + _capturedSignature.DataUrl, + _capturedSignature.FullName, + _capturedSignature.Position, + _capturedSignature.Place); } // Signature popup methods diff --git a/EnvelopeGenerator.ReceiverUI/wwwroot/js/pdf-viewer.js b/EnvelopeGenerator.ReceiverUI/wwwroot/js/pdf-viewer.js index 5c7fc556..e78ede29 100644 --- a/EnvelopeGenerator.ReceiverUI/wwwroot/js/pdf-viewer.js +++ b/EnvelopeGenerator.ReceiverUI/wwwroot/js/pdf-viewer.js @@ -594,7 +594,133 @@ window.pdfViewer = { } }); this.signatureButtons = []; + }, + + /** + * Applies a signature to a specific signature field, removing the button and rendering the signature. + * German standard: Signature image + Name, Position, Place, Date + * @param {number} signatureId - ID of the signature field + * @param {string} signatureDataUrl - Base64 PNG data URL of signature + * @param {string} fullName - Signer's full name + * @param {string} position - Signer's position (optional, can be empty) + * @param {string} place - Signing place + */ + async applySignature(signatureId, signatureDataUrl, fullName, position, place) { + try { + // Find and remove the button + const buttonIndex = this.signatureButtons.findIndex(btn => { + return btn.getAttribute('data-signature-id') == signatureId; + }); + + if (buttonIndex === -1) { + console.warn(`Signature button #${signatureId} not found`); + return; + } + + const button = this.signatureButtons[buttonIndex]; + const signatureLayer = document.getElementById('pdf-signature-layer'); + + if (!signatureLayer) { + console.error('Signature layer not found'); + return; + } + + // Get button position before removing it + const left = button.style.left; + const top = button.style.top; + + // Remove button + if (button.parentNode) { + button.parentNode.removeChild(button); + } + this.signatureButtons.splice(buttonIndex, 1); + + // Create signature container + const signatureContainer = document.createElement('div'); + signatureContainer.className = 'applied-signature'; + signatureContainer.setAttribute('data-signature-id', signatureId); + signatureContainer.style.position = 'absolute'; + signatureContainer.style.left = left; + signatureContainer.style.top = top; + signatureContainer.style.width = '230px'; + signatureContainer.style.backgroundColor = '#f8f9fa'; + signatureContainer.style.border = '1px solid #dee2e6'; + signatureContainer.style.borderRadius = '6px'; + signatureContainer.style.padding = '12px'; + signatureContainer.style.boxShadow = '0 2px 8px rgba(0,0,0,0.1)'; + signatureContainer.style.fontFamily = "'Open Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif"; + + // Signature image + const signatureImg = document.createElement('img'); + signatureImg.src = signatureDataUrl; + signatureImg.alt = 'Unterschrift'; + signatureImg.style.width = '100%'; + signatureImg.style.height = 'auto'; + signatureImg.style.maxHeight = '70px'; + signatureImg.style.display = 'block'; + signatureImg.style.objectFit = 'contain'; + signatureImg.style.marginBottom = '6px'; + + // Separator line (German standard) + const separator = document.createElement('div'); + separator.style.width = '100%'; + separator.style.height = '1px'; + separator.style.backgroundColor = '#495057'; + separator.style.marginBottom = '8px'; + + // Text information container + const infoContainer = document.createElement('div'); + infoContainer.style.fontSize = '9px'; + infoContainer.style.lineHeight = '1.4'; + infoContainer.style.color = '#495057'; + infoContainer.style.fontWeight = '400'; + + // Format date (German style: dd.MM.yyyy) + const today = new Date(); + const dateStr = today.toLocaleDateString('de-DE', { + day: '2-digit', + month: '2-digit', + year: 'numeric' + }); + + // Build text lines (German standard format) + const lines = []; + lines.push(`${this.escapeHtml(fullName)}`); + + if (position && position.trim() !== '') { + lines.push(`${this.escapeHtml(position)}`); + } + + lines.push(`${this.escapeHtml(place)}, ${dateStr}`); + + infoContainer.innerHTML = lines.join('
'); + + // Assemble container + signatureContainer.appendChild(signatureImg); + signatureContainer.appendChild(separator); + signatureContainer.appendChild(infoContainer); + + // Add to signature layer + signatureLayer.appendChild(signatureContainer); + + console.log(`Signature #${signatureId} applied successfully`); + + } catch (error) { + console.error('Error applying signature:', error); + } + }, + + /** + * Escapes HTML to prevent XSS attacks + */ + escapeHtml(text) { + const div = document.createElement('div'); + div.textContent = text; + return div.innerHTML; } }; + + +