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:
@@ -628,8 +628,8 @@ const int MaxThumbnailWidth = 400;
|
|||||||
_currentZoom = (int)(scale * 100);
|
_currentZoom = (int)(scale * 100);
|
||||||
await InvokeAsync(StateHasChanged);
|
await InvokeAsync(StateHasChanged);
|
||||||
|
|
||||||
// Re-render signature buttons when zoom changes
|
// Small delay for canvas render to complete (reduced from 100ms to 10ms)
|
||||||
await Task.Delay(100);
|
await Task.Delay(10);
|
||||||
await RenderSignatureButtonsAsync();
|
await RenderSignatureButtonsAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -652,6 +652,9 @@ const int MaxThumbnailWidth = 400;
|
|||||||
await JSRuntime.InvokeVoidAsync("pdfViewer.zoomIn");
|
await JSRuntime.InvokeVoidAsync("pdfViewer.zoomIn");
|
||||||
var scale = await JSRuntime.InvokeAsync<double>("pdfViewer.getScale");
|
var scale = await JSRuntime.InvokeAsync<double>("pdfViewer.getScale");
|
||||||
_currentZoom = (int)(scale * 100);
|
_currentZoom = (int)(scale * 100);
|
||||||
|
|
||||||
|
// Update signature overlay positions after zoom
|
||||||
|
await RenderSignatureButtonsAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
async Task ZoomOut() {
|
async Task ZoomOut() {
|
||||||
@@ -659,6 +662,9 @@ const int MaxThumbnailWidth = 400;
|
|||||||
await JSRuntime.InvokeVoidAsync("pdfViewer.zoomOut");
|
await JSRuntime.InvokeVoidAsync("pdfViewer.zoomOut");
|
||||||
var scale = await JSRuntime.InvokeAsync<double>("pdfViewer.getScale");
|
var scale = await JSRuntime.InvokeAsync<double>("pdfViewer.getScale");
|
||||||
_currentZoom = (int)(scale * 100);
|
_currentZoom = (int)(scale * 100);
|
||||||
|
|
||||||
|
// Update signature overlay positions after zoom
|
||||||
|
await RenderSignatureButtonsAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
async Task SetZoom(int percentage) {
|
async Task SetZoom(int percentage) {
|
||||||
@@ -670,6 +676,9 @@ const int MaxThumbnailWidth = 400;
|
|||||||
async Task OnZoomSliderChanged(ChangeEventArgs e) {
|
async Task OnZoomSliderChanged(ChangeEventArgs e) {
|
||||||
if (int.TryParse(e.Value?.ToString(), out var zoom)) {
|
if (int.TryParse(e.Value?.ToString(), out var zoom)) {
|
||||||
await SetZoom(zoom);
|
await SetZoom(zoom);
|
||||||
|
|
||||||
|
// Update signature overlay positions after zoom
|
||||||
|
await RenderSignatureButtonsAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -309,16 +309,16 @@ window.pdfViewer = {
|
|||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
|
|
||||||
zoomIn() {
|
async zoomIn() {
|
||||||
const step = this.qualityOptions.zoomStepPercentage / 100;
|
const step = this.qualityOptions.zoomStepPercentage / 100;
|
||||||
this.scale = Math.min(this.scale + step, 3.0);
|
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;
|
const step = this.qualityOptions.zoomStepPercentage / 100;
|
||||||
this.scale = Math.max(this.scale - step, 0.5);
|
this.scale = Math.max(this.scale - step, 0.5);
|
||||||
this.queueRenderPage(this.pageNum);
|
await this.renderPage(this.pageNum);
|
||||||
},
|
},
|
||||||
|
|
||||||
setScale(scale) {
|
setScale(scale) {
|
||||||
@@ -480,7 +480,8 @@ window.pdfViewer = {
|
|||||||
|
|
||||||
// Signature button functionality
|
// Signature button functionality
|
||||||
signatureButtons: [],
|
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
|
_lastViewedSignatureId: null, // Track last viewed signature for navigation
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -765,12 +766,28 @@ window.pdfViewer = {
|
|||||||
signatureLayer.style.width = `${viewport.width / dpr}px`;
|
signatureLayer.style.width = `${viewport.width / dpr}px`;
|
||||||
signatureLayer.style.height = `${viewport.height / 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
|
// Create button for each UNSIGNED signature
|
||||||
pageSignatures.forEach(sig => {
|
pageSignatures.forEach(sig => {
|
||||||
// Coordinates are in PDF POINTS - convert to display pixels
|
// Coordinates are in PDF POINTS - convert to display pixels
|
||||||
const xPx = (sig.x * this.scale);
|
const xPx = (sig.x * this.scale);
|
||||||
const yPx = (sig.y * 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
|
// Create button element
|
||||||
const button = document.createElement('button');
|
const button = document.createElement('button');
|
||||||
button.className = 'signature-button';
|
button.className = 'signature-button';
|
||||||
@@ -780,8 +797,8 @@ window.pdfViewer = {
|
|||||||
button.style.position = 'absolute';
|
button.style.position = 'absolute';
|
||||||
button.style.left = `${xPx}px`;
|
button.style.left = `${xPx}px`;
|
||||||
button.style.top = `${yPx}px`;
|
button.style.top = `${yPx}px`;
|
||||||
button.style.width = '150px';
|
button.style.width = `${scaledWidth}px`; // ✅ Scaled
|
||||||
button.style.height = '60px';
|
button.style.height = `${scaledHeight}px`; // ✅ Scaled
|
||||||
button.style.backgroundColor = '#4F46E5';
|
button.style.backgroundColor = '#4F46E5';
|
||||||
button.style.color = 'white';
|
button.style.color = 'white';
|
||||||
button.style.border = 'none';
|
button.style.border = 'none';
|
||||||
@@ -801,14 +818,14 @@ window.pdfViewer = {
|
|||||||
// Add text
|
// Add text
|
||||||
const textDiv = document.createElement('div');
|
const textDiv = document.createElement('div');
|
||||||
textDiv.textContent = 'Unterschreiben';
|
textDiv.textContent = 'Unterschreiben';
|
||||||
textDiv.style.fontSize = '18px';
|
textDiv.style.fontSize = `${scaledFontSize}px`; // ✅ Scaled
|
||||||
textDiv.style.fontWeight = '700';
|
textDiv.style.fontWeight = '700';
|
||||||
|
|
||||||
// Add SVG icon
|
// Add SVG icon
|
||||||
const svgNS = 'http://www.w3.org/2000/svg';
|
const svgNS = 'http://www.w3.org/2000/svg';
|
||||||
const svg = document.createElementNS(svgNS, 'svg');
|
const svg = document.createElementNS(svgNS, 'svg');
|
||||||
svg.setAttribute('width', '24');
|
svg.setAttribute('width', scaledIconSize); // ✅ Scaled
|
||||||
svg.setAttribute('height', '24');
|
svg.setAttribute('height', scaledIconSize); // ✅ Scaled
|
||||||
svg.setAttribute('viewBox', '0 8 32 36');
|
svg.setAttribute('viewBox', '0 8 32 36');
|
||||||
svg.setAttribute('fill', 'none');
|
svg.setAttribute('fill', 'none');
|
||||||
svg.style.filter = 'drop-shadow(0 1px 2px rgba(0,0,0,0.2))';
|
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.
|
* Clears all signature buttons from the canvas.
|
||||||
* Also hides applied signatures that don't belong to current page.
|
* Also hides applied signatures that don't belong to current page.
|
||||||
@@ -864,24 +956,25 @@ window.pdfViewer = {
|
|||||||
});
|
});
|
||||||
this.signatureButtons = [];
|
this.signatureButtons = [];
|
||||||
|
|
||||||
// Hide/show applied signatures based on current page
|
// ✅ FIXED: Update applied signatures (position + scaling)
|
||||||
const signatureLayer = document.getElementById('pdf-signature-layer');
|
this.appliedSignatureElements.forEach(container => {
|
||||||
if (signatureLayer) {
|
|
||||||
const appliedContainers = signatureLayer.querySelectorAll('.applied-signature');
|
|
||||||
appliedContainers.forEach(container => {
|
|
||||||
const signatureId = parseInt(container.getAttribute('data-signature-id'));
|
const signatureId = parseInt(container.getAttribute('data-signature-id'));
|
||||||
const signature = this._allSignatures?.find(s => s.id === signatureId);
|
const signature = this._allSignatures?.find(s => s.id === signatureId);
|
||||||
|
|
||||||
if (signature) {
|
if (signature) {
|
||||||
// Show only if on current page, hide otherwise
|
// Update position
|
||||||
if (signature.page === this.pageNum) {
|
const xPx = signature.x * this.scale;
|
||||||
container.style.display = ''; // Show
|
const yPx = signature.y * this.scale;
|
||||||
} else {
|
container.style.left = `${xPx}px`;
|
||||||
container.style.display = 'none'; // Hide
|
container.style.top = `${yPx}px`;
|
||||||
}
|
|
||||||
|
// 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');
|
const signatureContainer = document.createElement('div');
|
||||||
signatureContainer.className = 'applied-signature';
|
signatureContainer.className = 'applied-signature';
|
||||||
signatureContainer.setAttribute('data-signature-id', signatureId);
|
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.position = 'absolute';
|
||||||
signatureContainer.style.left = left;
|
signatureContainer.style.left = left;
|
||||||
signatureContainer.style.top = top;
|
signatureContainer.style.top = top;
|
||||||
@@ -969,6 +1070,8 @@ window.pdfViewer = {
|
|||||||
|
|
||||||
// Text information container
|
// Text information container
|
||||||
const infoContainer = document.createElement('div');
|
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.fontSize = '9px';
|
||||||
infoContainer.style.lineHeight = '1.4';
|
infoContainer.style.lineHeight = '1.4';
|
||||||
infoContainer.style.color = '#495057';
|
infoContainer.style.color = '#495057';
|
||||||
@@ -1002,6 +1105,12 @@ window.pdfViewer = {
|
|||||||
// Add to signature layer
|
// Add to signature layer
|
||||||
signatureLayer.appendChild(signatureContainer);
|
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`);
|
console.log(`Signature #${signatureId} applied successfully`);
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
Reference in New Issue
Block a user