From d3463f009bc5362b1de133033ea262604e1189dc Mon Sep 17 00:00:00 2001 From: Juergen Kunz Date: Sat, 20 Sep 2025 21:46:52 +0000 Subject: [PATCH] feat(dees-pdf-preview): enhance A4 format detection and improve canvas rendering quality --- ts_web/elements/dees-pdf-preview/component.ts | 96 ++++++++++++++++--- ts_web/elements/dees-pdf-preview/styles.ts | 66 +++++++------ 2 files changed, 119 insertions(+), 43 deletions(-) diff --git a/ts_web/elements/dees-pdf-preview/component.ts b/ts_web/elements/dees-pdf-preview/component.ts index 464091f..3c4b65e 100644 --- a/ts_web/elements/dees-pdf-preview/component.ts +++ b/ts_web/elements/dees-pdf-preview/component.ts @@ -41,9 +41,11 @@ export class DeesPdfPreview extends DeesElement { @property({ type: Boolean }) private isHovering: boolean = false; + @property({ type: Boolean }) + private isA4Format: boolean = true; + private renderPagesTask: Promise | null = null; private renderPagesQueued: boolean = false; - private hoverPageNumber: number = 1; private observer: IntersectionObserver; private pdfDocument: any; @@ -81,7 +83,7 @@ export class DeesPdfPreview extends DeesElement { ` : ''} ${!this.loading && !this.error ? html` -
+
requestAnimationFrame(resolve)); + const canvas = this.shadowRoot?.querySelector('.preview-canvas') as HTMLCanvasElement; - if (!canvas) return; + if (!canvas) { + console.warn('Preview canvas not found in DOM'); + return; + } // Release old canvases this.clearCanvases(); @@ -260,17 +271,47 @@ export class DeesPdfPreview extends DeesElement { const page = await this.pdfDocument.getPage(pageNum); // Calculate scale to fit within available area while keeping aspect ratio + // Use higher scale for sharper rendering const initialViewport = page.getViewport({ scale: 1 }); - const scaleX = availableWidth > 0 ? availableWidth / initialViewport.width : 0; - const scaleY = availableHeight > 0 ? availableHeight / initialViewport.height : 0; - const scale = Math.min(scaleX || 0.5, scaleY || scaleX || 0.5, 1.0); - if (!Number.isFinite(scale) || scale <= 0) { + // Check if this is standard paper format (A4 or US Letter) + const aspectRatio = initialViewport.height / initialViewport.width; + + // Common paper format ratios + const a4PortraitRatio = 1.414; // 297mm / 210mm + const a4LandscapeRatio = 0.707; // 210mm / 297mm + const letterPortraitRatio = 1.294; // 11" / 8.5" + const letterLandscapeRatio = 0.773; // 8.5" / 11" + + // Check for standard formats with 5% tolerance + const tolerance = 0.05; + const isA4Portrait = Math.abs(aspectRatio - a4PortraitRatio) < (a4PortraitRatio * tolerance); + const isA4Landscape = Math.abs(aspectRatio - a4LandscapeRatio) < (a4LandscapeRatio * tolerance); + const isLetterPortrait = Math.abs(aspectRatio - letterPortraitRatio) < (letterPortraitRatio * tolerance); + const isLetterLandscape = Math.abs(aspectRatio - letterLandscapeRatio) < (letterLandscapeRatio * tolerance); + + // Consider it standard format if it matches A4 or US Letter + this.isA4Format = isA4Portrait || isA4Landscape || isLetterPortrait || isLetterLandscape; + + // Debug logging + console.log(`PDF aspect ratio: ${aspectRatio.toFixed(3)}, standard format: ${this.isA4Format}`) + + // Adjust available size for non-A4 documents (account for padding) + const adjustedWidth = this.isA4Format ? availableWidth : availableWidth - 24; + const adjustedHeight = this.isA4Format ? availableHeight : availableHeight - 24; + + const scaleX = adjustedWidth > 0 ? adjustedWidth / initialViewport.width : 0; + const scaleY = adjustedHeight > 0 ? adjustedHeight / initialViewport.height : 0; + // Increase scale by 2x for sharper rendering, but limit to 3.0 max + const baseScale = Math.min(scaleX || 0.5, scaleY || scaleX || 0.5); + const renderScale = Math.min(baseScale * 2, 3.0); + + if (!Number.isFinite(renderScale) || renderScale <= 0) { page.cleanup?.(); return; } - const viewport = page.getViewport({ scale }); + const viewport = page.getViewport({ scale: renderScale }); // Acquire canvas from pool const pooledCanvas = CanvasPool.acquire(viewport.width, viewport.height); @@ -285,13 +326,31 @@ export class DeesPdfPreview extends DeesElement { await page.render(renderContext).promise; // Transfer to display canvas + // Set actual canvas resolution for sharpness canvas.width = viewport.width; canvas.height = viewport.height; - canvas.style.width = `${viewport.width}px`; - canvas.style.height = `${viewport.height}px`; + + // Scale down display size to fit the container while keeping high resolution + // For A4, fill the container; for non-A4, respect padding + const displayWidth = adjustedWidth; + const displayHeight = (viewport.height / viewport.width) * adjustedWidth; + + // If it fits height-wise better, scale by height instead + if (displayHeight > adjustedHeight) { + const altDisplayHeight = adjustedHeight; + const altDisplayWidth = (viewport.width / viewport.height) * adjustedHeight; + canvas.style.width = `${altDisplayWidth}px`; + canvas.style.height = `${altDisplayHeight}px`; + } else { + canvas.style.width = `${displayWidth}px`; + canvas.style.height = `${displayHeight}px`; + } const ctx = canvas.getContext('2d'); if (ctx) { + // Enable image smoothing for better quality + ctx.imageSmoothingEnabled = true; + ctx.imageSmoothingQuality = 'high'; ctx.drawImage(pooledCanvas.canvas, 0, 0); } @@ -323,6 +382,7 @@ export class DeesPdfPreview extends DeesElement { this.pageCount = 0; this.currentPreviewPage = 1; this.isHovering = false; + this.isA4Format = true; this.previewContainer = null; this.stackElement = null; this.loadedPdfUrl = null; @@ -456,15 +516,21 @@ export class DeesPdfPreview extends DeesElement { private getAvailableSize() { if (!this.stackElement) { + // Try to get the stack element if it's not cached + this.stackElement = this.shadowRoot?.querySelector('.preview-stack') as HTMLElement; + } + + if (!this.stackElement) { + // Fallback to default size if element not found return { - availableWidth: 0, - availableHeight: 0, + availableWidth: 200, // Full container width + availableHeight: 260, // Full container height }; } const rect = this.stackElement.getBoundingClientRect(); - const availableWidth = Math.max(rect.width, 0); - const availableHeight = Math.max(rect.height, 0); + const availableWidth = Math.max(rect.width, 0) || 200; + const availableHeight = Math.max(rect.height, 0) || 260; return { availableWidth, availableHeight }; } diff --git a/ts_web/elements/dees-pdf-preview/styles.ts b/ts_web/elements/dees-pdf-preview/styles.ts index d27f168..31817cb 100644 --- a/ts_web/elements/dees-pdf-preview/styles.ts +++ b/ts_web/elements/dees-pdf-preview/styles.ts @@ -12,11 +12,11 @@ export const previewStyles = [ position: relative; width: 200px; height: 260px; - background: ${cssManager.bdTheme('hsl(0 0% 100%)', 'hsl(215 20% 16%)')}; - border: 1px solid ${cssManager.bdTheme('hsl(214 31% 91%)', 'hsl(217 25% 26%)')}; - border-radius: 12px; + background: ${cssManager.bdTheme('hsl(0 0% 98%)', 'hsl(215 20% 14%)')}; + border-radius: 4px; overflow: hidden; transition: transform 0.2s ease, box-shadow 0.2s ease; + box-shadow: 0 1px 3px ${cssManager.bdTheme('rgba(0, 0, 0, 0.12)', 'rgba(0, 0, 0, 0.24)')}; } .preview-container.clickable { @@ -39,49 +39,59 @@ export const previewStyles = [ display: flex; align-items: center; justify-content: center; - padding: 16px; box-sizing: border-box; + overflow: hidden; + } + + .preview-stack.non-a4 { + padding: 12px; } .preview-canvas { position: relative; background: white; - border: 1px solid ${cssManager.bdTheme('hsl(214 31% 88%)', 'hsl(217 25% 30%)')}; - border-radius: 4px; display: block; max-width: 100%; max-height: 100%; + width: auto; + height: auto; object-fit: contain; - image-rendering: -webkit-optimize-contrast; - image-rendering: crisp-edges; - box-shadow: 0 2px 8px ${cssManager.bdTheme('rgba(0, 0, 0, 0.08)', 'rgba(0, 0, 0, 0.2)')}; + image-rendering: auto; + -webkit-font-smoothing: antialiased; + box-shadow: 0 1px 3px ${cssManager.bdTheme('rgba(0, 0, 0, 0.1)', 'rgba(0, 0, 0, 0.3)')}; + } + + .non-a4 .preview-canvas { + border: 1px solid ${cssManager.bdTheme('hsl(214 31% 92%)', 'hsl(217 25% 24%)')}; + border-radius: 4px; } .preview-info { position: absolute; - bottom: 12px; - left: 12px; - right: 12px; - padding: 8px 12px; - background: ${cssManager.bdTheme('hsl(0 0% 100% / 0.95)', 'hsl(215 20% 12% / 0.95)')}; - border: 1px solid ${cssManager.bdTheme('hsl(214 31% 91%)', 'hsl(217 25% 26%)')}; - border-radius: 8px; + bottom: 8px; + left: 8px; + right: 8px; + padding: 6px 10px; + background: ${cssManager.bdTheme('hsl(0 0% 100% / 0.92)', 'hsl(215 20% 12% / 0.92)')}; + border-radius: 6px; display: flex; align-items: center; - gap: 8px; - font-size: 13px; + gap: 6px; + font-size: 12px; color: ${cssManager.bdTheme('hsl(215 16% 45%)', 'hsl(215 16% 75%)')}; - backdrop-filter: blur(8px); + backdrop-filter: blur(12px); + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); z-index: 10; } .preview-info dees-icon { - font-size: 14px; + font-size: 13px; color: ${cssManager.bdTheme('hsl(217 91% 60%)', 'hsl(213 93% 68%)')}; } .preview-pages { font-weight: 500; + font-size: 11px; } .preview-overlay { @@ -162,17 +172,17 @@ export const previewStyles = [ .preview-page-indicator { position: absolute; - top: 12px; - left: 12px; - right: 12px; - padding: 6px 10px; - background: ${cssManager.bdTheme('hsl(0 0% 0% / 0.75)', 'hsl(0 0% 100% / 0.85)')}; + top: 8px; + left: 8px; + right: 8px; + padding: 5px 8px; + background: ${cssManager.bdTheme('hsl(0 0% 0% / 0.7)', 'hsl(0 0% 100% / 0.9)')}; color: ${cssManager.bdTheme('white', 'hsl(215 20% 12%)')}; - border-radius: 6px; - font-size: 12px; + border-radius: 4px; + font-size: 11px; font-weight: 600; text-align: center; - backdrop-filter: blur(8px); + backdrop-filter: blur(12px); z-index: 15; pointer-events: none; animation: fadeIn 0.2s ease;