Improve signature scaling and responsiveness in PDF viewer

Reduced delay in `OnZoomChanged` to improve responsiveness when rendering signature buttons. Added calls to `RenderSignatureButtonsAsync` in zoom-related methods to ensure signature overlays update dynamically.

Refactored `pdf-viewer.js` to introduce `appliedSignatureElements` for better management of applied signatures. Added `scaleAppliedSignature` and `updateAppliedSignaturePositions` methods to dynamically scale and position applied signatures based on zoom level and page.

Enhanced signature button rendering by scaling dimensions (width, height, font size, icon size) proportionally with zoom. Added attributes to store base values for applied signature containers to facilitate scaling.

Improved handling of applied signatures to ensure proper scaling, positioning, and visibility during zoom and page navigation. These changes enhance user experience and maintain consistency across zoom levels.
This commit is contained in:
2026-06-08 11:39:17 +02:00
parent 63b47ddbf2
commit 9535c7dd6b
2 changed files with 147 additions and 29 deletions

View File

@@ -628,8 +628,8 @@ const int MaxThumbnailWidth = 400;
_currentZoom = (int)(scale * 100);
await InvokeAsync(StateHasChanged);
// Re-render signature buttons when zoom changes
await Task.Delay(100);
// Small delay for canvas render to complete (reduced from 100ms to 10ms)
await Task.Delay(10);
await RenderSignatureButtonsAsync();
}
@@ -652,6 +652,9 @@ const int MaxThumbnailWidth = 400;
await JSRuntime.InvokeVoidAsync("pdfViewer.zoomIn");
var scale = await JSRuntime.InvokeAsync<double>("pdfViewer.getScale");
_currentZoom = (int)(scale * 100);
// Update signature overlay positions after zoom
await RenderSignatureButtonsAsync();
}
async Task ZoomOut() {
@@ -659,6 +662,9 @@ const int MaxThumbnailWidth = 400;
await JSRuntime.InvokeVoidAsync("pdfViewer.zoomOut");
var scale = await JSRuntime.InvokeAsync<double>("pdfViewer.getScale");
_currentZoom = (int)(scale * 100);
// Update signature overlay positions after zoom
await RenderSignatureButtonsAsync();
}
async Task SetZoom(int percentage) {
@@ -670,6 +676,9 @@ const int MaxThumbnailWidth = 400;
async Task OnZoomSliderChanged(ChangeEventArgs e) {
if (int.TryParse(e.Value?.ToString(), out var zoom)) {
await SetZoom(zoom);
// Update signature overlay positions after zoom
await RenderSignatureButtonsAsync();
}
}

View File

@@ -309,16 +309,16 @@ window.pdfViewer = {
return true;
},
zoomIn() {
async zoomIn() {
const step = this.qualityOptions.zoomStepPercentage / 100;
this.scale = Math.min(this.scale + step, 3.0);
this.queueRenderPage(this.pageNum);
await this.renderPage(this.pageNum);
},
zoomOut() {
async zoomOut() {
const step = this.qualityOptions.zoomStepPercentage / 100;
this.scale = Math.max(this.scale - step, 0.5);
this.queueRenderPage(this.pageNum);
await this.renderPage(this.pageNum);
},
setScale(scale) {
@@ -480,7 +480,8 @@ window.pdfViewer = {
// Signature button functionality
signatureButtons: [],
appliedSignatures: [], // Track which signatures have been applied
appliedSignatures: [], // Track which signatures have been applied (ID list)
appliedSignatureElements: [], // ✅ NEW: Track applied signature DOM elements
_lastViewedSignatureId: null, // Track last viewed signature for navigation
/**
@@ -765,12 +766,28 @@ window.pdfViewer = {
signatureLayer.style.width = `${viewport.width / dpr}px`;
signatureLayer.style.height = `${viewport.height / dpr}px`;
// Update applied signature coordinates for current zoom level
this.updateAppliedSignaturePositions(signatureLayer, currentPageNum);
// Create button for each UNSIGNED signature
pageSignatures.forEach(sig => {
// Coordinates are in PDF POINTS - convert to display pixels
const xPx = (sig.x * this.scale);
const yPx = (sig.y * this.scale);
// ✅ FIXED: Scale button size proportionally with zoom
const baseScale = 1.5; // Reference scale (initial load)
const scaleFactor = this.scale / baseScale;
const baseWidth = 150;
const baseHeight = 60;
const baseFontSize = 18;
const baseIconSize = 24;
const scaledWidth = baseWidth * scaleFactor;
const scaledHeight = baseHeight * scaleFactor;
const scaledFontSize = Math.max(baseFontSize * scaleFactor, 10); // Min 10px
const scaledIconSize = baseIconSize * scaleFactor;
// Create button element
const button = document.createElement('button');
button.className = 'signature-button';
@@ -780,8 +797,8 @@ window.pdfViewer = {
button.style.position = 'absolute';
button.style.left = `${xPx}px`;
button.style.top = `${yPx}px`;
button.style.width = '150px';
button.style.height = '60px';
button.style.width = `${scaledWidth}px`; // ✅ Scaled
button.style.height = `${scaledHeight}px`; // ✅ Scaled
button.style.backgroundColor = '#4F46E5';
button.style.color = 'white';
button.style.border = 'none';
@@ -801,14 +818,14 @@ window.pdfViewer = {
// Add text
const textDiv = document.createElement('div');
textDiv.textContent = 'Unterschreiben';
textDiv.style.fontSize = '18px';
textDiv.style.fontSize = `${scaledFontSize}px`; // ✅ Scaled
textDiv.style.fontWeight = '700';
// Add SVG icon
const svgNS = 'http://www.w3.org/2000/svg';
const svg = document.createElementNS(svgNS, 'svg');
svg.setAttribute('width', '24');
svg.setAttribute('height', '24');
svg.setAttribute('width', scaledIconSize); // ✅ Scaled
svg.setAttribute('height', scaledIconSize); // ✅ Scaled
svg.setAttribute('viewBox', '0 8 32 36');
svg.setAttribute('fill', 'none');
svg.style.filter = 'drop-shadow(0 1px 2px rgba(0,0,0,0.2))';
@@ -851,6 +868,81 @@ window.pdfViewer = {
}
},
/**
* Scales an applied signature container based on current zoom level
* @param {HTMLElement} container - Applied signature container
* @param {number} currentScale - Current PDF zoom scale
*/
scaleAppliedSignature(container, currentScale) {
const baseScale = 1.5; // Reference scale (initial load)
const scaleFactor = currentScale / baseScale;
// Scale width
const baseWidth = parseInt(container.getAttribute('data-base-width') || 230);
container.style.width = `${baseWidth * scaleFactor}px`;
// Scale padding
const basePadding = parseInt(container.getAttribute('data-base-padding') || 12);
container.style.padding = `${basePadding * scaleFactor}px`;
// Scale border radius (subtle detail)
const baseBorderRadius = parseInt(container.getAttribute('data-base-border-radius') || 6);
container.style.borderRadius = `${baseBorderRadius * scaleFactor}px`;
// Scale font size (with min 6px for readability)
const infoContainer = container.querySelector('.signature-info-text');
if (infoContainer) {
const baseFontSize = parseInt(infoContainer.getAttribute('data-base-font-size') || 9);
const scaledFontSize = Math.max(baseFontSize * scaleFactor, 6);
infoContainer.style.fontSize = `${scaledFontSize}px`;
}
// Scale image max height
const baseImgHeight = parseInt(container.getAttribute('data-base-img-height') || 70);
const img = container.querySelector('img');
if (img) {
img.style.maxHeight = `${baseImgHeight * scaleFactor}px`;
}
// Scale separator line margin
const baseSeparatorMargin = 6;
const separators = container.querySelectorAll('div[style*="border-top"]');
separators.forEach(sep => {
sep.style.marginTop = `${baseSeparatorMargin * scaleFactor}px`;
sep.style.marginBottom = `${(baseSeparatorMargin + 2) * scaleFactor}px`;
});
},
/**
* Updates applied signature positions based on current zoom level
* @param {HTMLElement} signatureLayer - Signature layer container
* @param {number} currentPageNum - Current page number
*/
updateAppliedSignaturePositions(signatureLayer, currentPageNum) {
if (!signatureLayer || !this._allSignatures) return;
const appliedContainers = signatureLayer.querySelectorAll('.applied-signature');
appliedContainers.forEach(container => {
const signatureId = parseInt(container.getAttribute('data-signature-id'));
const signature = this._allSignatures.find(s => s.id === signatureId);
if (signature) {
// ✅ Position calculation (same as renderSignatureButtons)
const xPx = signature.x * this.scale;
const yPx = signature.y * this.scale;
container.style.left = `${xPx}px`;
container.style.top = `${yPx}px`;
// ✅ FIXED: Apply comprehensive scaling using helper method
this.scaleAppliedSignature(container, this.scale);
// Show/hide based on current page
container.style.display = (signature.page === currentPageNum) ? '' : 'none';
}
});
},
/**
* Clears all signature buttons from the canvas.
* Also hides applied signatures that don't belong to current page.
@@ -864,24 +956,25 @@ window.pdfViewer = {
});
this.signatureButtons = [];
// Hide/show applied signatures based on current page
const signatureLayer = document.getElementById('pdf-signature-layer');
if (signatureLayer) {
const appliedContainers = signatureLayer.querySelectorAll('.applied-signature');
appliedContainers.forEach(container => {
const signatureId = parseInt(container.getAttribute('data-signature-id'));
const signature = this._allSignatures?.find(s => s.id === signatureId);
// ✅ FIXED: Update applied signatures (position + scaling)
this.appliedSignatureElements.forEach(container => {
const signatureId = parseInt(container.getAttribute('data-signature-id'));
const signature = this._allSignatures?.find(s => s.id === signatureId);
if (signature) {
// Update position
const xPx = signature.x * this.scale;
const yPx = signature.y * this.scale;
container.style.left = `${xPx}px`;
container.style.top = `${yPx}px`;
if (signature) {
// Show only if on current page, hide otherwise
if (signature.page === this.pageNum) {
container.style.display = ''; // Show
} else {
container.style.display = 'none'; // Hide
}
}
});
}
// Update scaling
this.scaleAppliedSignature(container, this.scale);
// Show/hide based on current page
container.style.display = (signature.page === this.pageNum) ? '' : 'none';
}
});
},
/**
@@ -938,6 +1031,14 @@ window.pdfViewer = {
const signatureContainer = document.createElement('div');
signatureContainer.className = 'applied-signature';
signatureContainer.setAttribute('data-signature-id', signatureId);
// ✅ FIXED: Store base values for scaling
signatureContainer.setAttribute('data-base-width', '230');
signatureContainer.setAttribute('data-base-padding', '12');
signatureContainer.setAttribute('data-base-font-size', '9');
signatureContainer.setAttribute('data-base-img-height', '70');
signatureContainer.setAttribute('data-base-border-radius', '6');
signatureContainer.style.position = 'absolute';
signatureContainer.style.left = left;
signatureContainer.style.top = top;
@@ -969,6 +1070,8 @@ window.pdfViewer = {
// Text information container
const infoContainer = document.createElement('div');
infoContainer.className = 'signature-info-text'; // ✅ Class ekle (querySelector için)
infoContainer.setAttribute('data-base-font-size', '9'); // ✅ Base font size sakla
infoContainer.style.fontSize = '9px';
infoContainer.style.lineHeight = '1.4';
infoContainer.style.color = '#495057';
@@ -1001,6 +1104,12 @@ window.pdfViewer = {
// Add to signature layer
signatureLayer.appendChild(signatureContainer);
// ✅ FIXED: Track applied signature element for zoom updates
this.appliedSignatureElements.push(signatureContainer);
// ✅ FIXED: Apply initial scaling based on current zoom
this.scaleAppliedSignature(signatureContainer, this.scale);
console.log(`Signature #${signatureId} applied successfully`);