From f5c141626f2bf032e668ec25da178780242cee5b Mon Sep 17 00:00:00 2001 From: marwin Date: Sun, 5 Apr 2026 16:14:24 +0200 Subject: [PATCH] Fix blurry PDF rendering when zoomed in Chrome PWA Zoom is now baked into the canvas render scale instead of being applied via CSS zoom. CSS zoom only scales pixels already on the canvas, causing blurriness at high DPR (e.g. PWA on mobile). Now the canvas is rendered at the correct resolution for the chosen zoom level. Also: stop nulling currentPdfDoc on re-render so PDF.js page cache is preserved, making zoom re-renders significantly faster. Co-Authored-By: Claude Sonnet 4.6 --- static/css/app.css | 1 + static/js/app.js | 23 ++++++----------------- 2 files changed, 7 insertions(+), 17 deletions(-) diff --git a/static/css/app.css b/static/css/app.css index 4625ee2..7429252 100644 --- a/static/css/app.css +++ b/static/css/app.css @@ -1554,6 +1554,7 @@ body.dnd-mode .timer-display { .reader-content { flex: 1; overflow-y: scroll; + overflow-x: auto; padding: 24px 16px; line-height: 1.8; font-family: Georgia, 'Times New Roman', serif; diff --git a/static/js/app.js b/static/js/app.js index b4474f5..dcdee81 100644 --- a/static/js/app.js +++ b/static/js/app.js @@ -2996,7 +2996,6 @@ async function renderPdf(arrayBuffer, contentEl, scaleOverride) { // Viewport wrapper: CSS zoom controls display scale without re-rendering const pdfVp = document.createElement('div'); pdfVp.id = 'pdf-viewport'; - if (scaleOverride == null) pdfVp.style.zoom = readerSettings.pdfZoom / 100; contentEl.appendChild(pdfVp); const containerWidth = readerSettings.pdfSpread @@ -3010,8 +3009,9 @@ async function renderPdf(arrayBuffer, contentEl, scaleOverride) { const page = await pdf.getPage(pageNum); const naturalVp = page.getViewport({scale: 1}); const pageWidth = readerSettings.pdfSpread ? (containerWidth - 8) / 2 : containerWidth; + // Zoom baked into canvas resolution — no CSS zoom, stays sharp at any DPR const scale = scaleOverride != null ? scaleOverride - : Math.max(0.5, pageWidth / naturalVp.width); + : Math.max(0.5, pageWidth / naturalVp.width) * (readerSettings.pdfZoom / 100); const viewport = page.getViewport({scale}); const wrapper = document.createElement('div'); @@ -3654,7 +3654,7 @@ function toggleSettingsPanel() { } } -function applyPdfZoom(newZoom) { +async function applyPdfZoom(newZoom) { const contentEl2 = $('reader-content'); const fraction = contentEl2 ? contentEl2.scrollTop / (contentEl2.scrollHeight - contentEl2.clientHeight || 1) @@ -3668,12 +3668,9 @@ function applyPdfZoom(newZoom) { if (readerSettings.pdfPaginated) { pdfSmartZoomPage(pdfCurrentPage); } else { - const vp = document.getElementById('pdf-viewport'); - if (vp) vp.style.zoom = readerSettings.pdfZoom / 100; + await reRenderPdf(); if (contentEl2 && fraction > 0) { - requestAnimationFrame(() => { - contentEl2.scrollTop = fraction * (contentEl2.scrollHeight - contentEl2.clientHeight); - }); + contentEl2.scrollTop = fraction * (contentEl2.scrollHeight - contentEl2.clientHeight); } } } @@ -3682,7 +3679,6 @@ async function reRenderPdf() { if (!currentPdfBuffer) return; const contentEl = $('reader-content'); if (!contentEl) return; - currentPdfDoc = null; // force re-parse with same buffer await renderPdf(currentPdfBuffer, contentEl); if (readerSettings.pdfPaginated) enterPdfPaginatedMode(); } @@ -3716,14 +3712,7 @@ function exitPdfPaginatedMode() { contentEl.classList.remove('pdf-paginated'); contentEl.style.overflow = ''; contentEl.removeEventListener('click', _pdfPaginatedClick); - const wrappers = contentEl.querySelectorAll('.pdf-page-wrapper'); - wrappers.forEach(w => { - w.style.display = ''; - const canvas = w.querySelector('canvas'); - if (canvas) canvas.style.transform = ''; - }); - const pdfVp = document.getElementById('pdf-viewport'); - if (pdfVp) pdfVp.style.zoom = readerSettings.pdfZoom / 100; + reRenderPdf(); } function _pdfPaginatedClick(e) {