From 5234c9daa603065a4c509dd4e590e0856f5b2cae Mon Sep 17 00:00:00 2001 From: Reece Date: Fri, 14 Nov 2025 19:48:38 +0000 Subject: [PATCH 1/3] zoom fix --- .../core/components/viewer/LocalEmbedPDF.tsx | 3 - .../core/components/viewer/ZoomAPIBridge.tsx | 20 ++---- frontend/src/core/utils/viewerZoom.ts | 61 ++++++++++--------- 3 files changed, 35 insertions(+), 49 deletions(-) diff --git a/frontend/src/core/components/viewer/LocalEmbedPDF.tsx b/frontend/src/core/components/viewer/LocalEmbedPDF.tsx index 62d60e14f..f20b68b9a 100644 --- a/frontend/src/core/components/viewer/LocalEmbedPDF.tsx +++ b/frontend/src/core/components/viewer/LocalEmbedPDF.tsx @@ -191,7 +191,6 @@ export function LocalEmbedPDF({ file, url, enableAnnotations = false, onSignatur height: '100%', width: '100%', position: 'relative', - overflow: 'hidden', flex: 1, minHeight: 0, minWidth: 0, @@ -287,8 +286,6 @@ export function LocalEmbedPDF({ file, url, enableAnnotations = false, onSignatur minHeight: 0, minWidth: 0, contain: 'strict', - display: 'flex', - justifyContent: 'center', }} > cancelled, - }); - if (cancelled) { - return; - } - const decision = determineAutoZoom({ viewportWidth, viewportHeight, fitWidthZoom, pagesPerSpread, - pageRect: pageRect - ? { width: pageRect.width, height: pageRect.height } - : undefined, + pageRect: undefined, metadataAspectRatio: metadataAspectRatio ?? null, visibilityThreshold: DEFAULT_VISIBILITY_THRESHOLD, fallbackZoom: DEFAULT_FALLBACK_ZOOM, diff --git a/frontend/src/core/utils/viewerZoom.ts b/frontend/src/core/utils/viewerZoom.ts index 1fa1fb492..80775cfbe 100644 --- a/frontend/src/core/utils/viewerZoom.ts +++ b/frontend/src/core/utils/viewerZoom.ts @@ -1,6 +1,6 @@ import { useEffect, useRef } from 'react'; -export const DEFAULT_VISIBILITY_THRESHOLD = 80; // Require at least 80% of the page height to be visible +export const DEFAULT_VISIBILITY_THRESHOLD = 70; // Require at least 70% of the page height to be visible export const DEFAULT_FALLBACK_ZOOM = 1.44; // 144% fallback when no reliable metadata is present export interface ZoomViewport { @@ -36,47 +36,48 @@ export function determineAutoZoom({ visibilityThreshold = DEFAULT_VISIBILITY_THRESHOLD, fallbackZoom = DEFAULT_FALLBACK_ZOOM, }: AutoZoomParams): AutoZoomDecision { + // Get aspect ratio from pageRect or metadata const rectWidth = pageRect?.width ?? 0; const rectHeight = pageRect?.height ?? 0; - const aspectRatio: number | null = rectWidth > 0 ? rectHeight / rectWidth : metadataAspectRatio ?? null; - let renderedHeight: number | null = rectHeight > 0 ? rectHeight : null; - - if (!renderedHeight || renderedHeight <= 0) { - if (aspectRatio == null || aspectRatio <= 0) { - return { type: 'fallback', zoom: Math.min(fitWidthZoom, fallbackZoom) }; - } - - const pageWidth = viewportWidth / (fitWidthZoom * pagesPerSpread); - const pageHeight = pageWidth * aspectRatio; - renderedHeight = pageHeight * fitWidthZoom; + // Need aspect ratio to proceed + if (!aspectRatio || aspectRatio <= 0) { + return { type: 'fallback', zoom: Math.min(fitWidthZoom, fallbackZoom) }; } - if (!renderedHeight || renderedHeight <= 0) { - return { type: 'fitWidth' }; - } - - const isLandscape = aspectRatio !== null && aspectRatio < 1; + // Landscape pages need 100% visibility, portrait need 80% + const isLandscape = aspectRatio < 1; const targetVisibility = isLandscape ? 100 : visibilityThreshold; - const visiblePercent = (viewportHeight / renderedHeight) * 100; + // Step 1: Calculate what zoom level shows targetVisibility% (80%) of page height + // + // At fitWidth, page dimensions are: + // pageHeightAtFitWidth = (viewportWidth / pagesPerSpread) * aspectRatio + // + // For 80% of page to be visible: + // viewportHeight / pageHeightAtZoom = targetVisibility / 100 + // viewportHeight / (pageHeightAtFitWidth * zoomRatio) = targetVisibility / 100 + // + // Where zoomRatio = heightBasedZoom / fitWidthZoom + // + // Solving: + // heightBasedZoom = fitWidthZoom * viewportHeight / (pageHeightAtFitWidth * targetVisibility / 100) + // heightBasedZoom = fitWidthZoom * (viewportHeight / pageHeightAtFitWidth) * (100 / targetVisibility) - if (visiblePercent >= targetVisibility) { + const pageHeightAtFitWidth = (viewportWidth / pagesPerSpread) * aspectRatio; + const heightBasedZoom = fitWidthZoom * (viewportHeight / pageHeightAtFitWidth) / (targetVisibility / 100); + + // Step 2: Compare with fitWidth + // Use whichever is smaller (more zoomed out) to ensure both constraints are met + if (heightBasedZoom < fitWidthZoom) { + // Need to zoom out from fitWidth to show enough height + return { type: 'adjust', zoom: heightBasedZoom }; + } else { + // fitWidth already shows enough return { type: 'fitWidth' }; } - - const allowableHeightRatio = targetVisibility / 100; - const zoomScale = - viewportHeight / (allowableHeightRatio * renderedHeight); - const targetZoom = Math.min(fitWidthZoom, fitWidthZoom * zoomScale); - - if (Math.abs(targetZoom - fitWidthZoom) < 0.001) { - return { type: 'fitWidth' }; - } - - return { type: 'adjust', zoom: targetZoom }; } export interface MeasurePageRectOptions { From 568cda1288402b51c7147ee61c899b760ead3c47 Mon Sep 17 00:00:00 2001 From: Reece Date: Fri, 14 Nov 2025 20:42:14 +0000 Subject: [PATCH 2/3] lint --- .../core/components/viewer/ZoomAPIBridge.tsx | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/frontend/src/core/components/viewer/ZoomAPIBridge.tsx b/frontend/src/core/components/viewer/ZoomAPIBridge.tsx index e951e76ad..b603480d8 100644 --- a/frontend/src/core/components/viewer/ZoomAPIBridge.tsx +++ b/frontend/src/core/components/viewer/ZoomAPIBridge.tsx @@ -7,9 +7,7 @@ import { determineAutoZoom, DEFAULT_FALLBACK_ZOOM, DEFAULT_VISIBILITY_THRESHOLD, - measureRenderedPageRect, useFitWidthResize, - ZoomViewport, } from '@app/utils/viewerZoom'; import { getFirstPageAspectRatioFromStub } from '@app/utils/pageMetadata'; @@ -73,18 +71,6 @@ export function ZoomAPIBridge() { } }, [spreadMode, zoomState?.zoomLevel, scheduleAutoZoom, requestFitWidth]); - const getViewportSnapshot = useCallback((): ZoomViewport | null => { - if (!zoomState || typeof zoomState !== 'object') { - return null; - } - - if ('viewport' in zoomState) { - const candidate = (zoomState as { viewport?: ZoomViewport | null }).viewport; - return candidate ?? null; - } - - return null; - }, [zoomState]); const isManagedZoom = !!zoom && @@ -137,8 +123,6 @@ export function ZoomAPIBridge() { const pagesPerSpread = currentSpreadMode !== SpreadMode.None ? 2 : 1; const metadataAspectRatio = getFirstPageAspectRatioFromStub(firstFileStub); - const viewport = getViewportSnapshot(); - if (cancelled) { return; } @@ -185,7 +169,6 @@ export function ZoomAPIBridge() { firstFileId, firstFileStub, requestFitWidth, - getViewportSnapshot, autoZoomTick, spreadMode, triggerImmediateZoomUpdate, From 4093ded6286cde74711e009ca8b85729296d29dc Mon Sep 17 00:00:00 2001 From: Reece Date: Fri, 14 Nov 2025 20:43:44 +0000 Subject: [PATCH 3/3] simplify comments --- frontend/src/core/utils/viewerZoom.ts | 21 +++------------------ 1 file changed, 3 insertions(+), 18 deletions(-) diff --git a/frontend/src/core/utils/viewerZoom.ts b/frontend/src/core/utils/viewerZoom.ts index 80775cfbe..78799e8b2 100644 --- a/frontend/src/core/utils/viewerZoom.ts +++ b/frontend/src/core/utils/viewerZoom.ts @@ -47,30 +47,15 @@ export function determineAutoZoom({ return { type: 'fallback', zoom: Math.min(fitWidthZoom, fallbackZoom) }; } - // Landscape pages need 100% visibility, portrait need 80% + // Landscape pages need 100% visibility, portrait need the specified threshold const isLandscape = aspectRatio < 1; const targetVisibility = isLandscape ? 100 : visibilityThreshold; - // Step 1: Calculate what zoom level shows targetVisibility% (80%) of page height - // - // At fitWidth, page dimensions are: - // pageHeightAtFitWidth = (viewportWidth / pagesPerSpread) * aspectRatio - // - // For 80% of page to be visible: - // viewportHeight / pageHeightAtZoom = targetVisibility / 100 - // viewportHeight / (pageHeightAtFitWidth * zoomRatio) = targetVisibility / 100 - // - // Where zoomRatio = heightBasedZoom / fitWidthZoom - // - // Solving: - // heightBasedZoom = fitWidthZoom * viewportHeight / (pageHeightAtFitWidth * targetVisibility / 100) - // heightBasedZoom = fitWidthZoom * (viewportHeight / pageHeightAtFitWidth) * (100 / targetVisibility) - + // Calculate zoom level that shows targetVisibility% of page height const pageHeightAtFitWidth = (viewportWidth / pagesPerSpread) * aspectRatio; const heightBasedZoom = fitWidthZoom * (viewportHeight / pageHeightAtFitWidth) / (targetVisibility / 100); - // Step 2: Compare with fitWidth - // Use whichever is smaller (more zoomed out) to ensure both constraints are met + // Use whichever zoom is smaller (more zoomed out) to satisfy both width and height constraints if (heightBasedZoom < fitWidthZoom) { // Need to zoom out from fitWidth to show enough height return { type: 'adjust', zoom: heightBasedZoom };