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.
This commit is contained in:
2026-06-07 13:47:34 +02:00
parent 9523766678
commit ce43ace3c2
2 changed files with 139 additions and 3 deletions

View File

@@ -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

View File

@@ -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(`<strong style="font-weight: 600; color: #212529;">${this.escapeHtml(fullName)}</strong>`);
if (position && position.trim() !== '') {
lines.push(`${this.escapeHtml(position)}`);
}
lines.push(`${this.escapeHtml(place)}, ${dateStr}`);
infoContainer.innerHTML = lines.join('<br>');
// 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;
}
};