From 9535c7dd6bebaa412dd8e6c0b2ef5a7b7774d23b Mon Sep 17 00:00:00 2001 From: TekH Date: Mon, 8 Jun 2026 11:39:17 +0200 Subject: [PATCH] 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. --- .../Pages/EnvelopeViewer.razor | 13 +- .../wwwroot/js/pdf-viewer.js | 163 +++++++++++++++--- 2 files changed, 147 insertions(+), 29 deletions(-) diff --git a/EnvelopeGenerator.ReceiverUI/Pages/EnvelopeViewer.razor b/EnvelopeGenerator.ReceiverUI/Pages/EnvelopeViewer.razor index 6ffb64b4..dad7a54e 100644 --- a/EnvelopeGenerator.ReceiverUI/Pages/EnvelopeViewer.razor +++ b/EnvelopeGenerator.ReceiverUI/Pages/EnvelopeViewer.razor @@ -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("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("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(); } } diff --git a/EnvelopeGenerator.ReceiverUI/wwwroot/js/pdf-viewer.js b/EnvelopeGenerator.ReceiverUI/wwwroot/js/pdf-viewer.js index f6284233..4a377dec 100644 --- a/EnvelopeGenerator.ReceiverUI/wwwroot/js/pdf-viewer.js +++ b/EnvelopeGenerator.ReceiverUI/wwwroot/js/pdf-viewer.js @@ -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`);