Render PDF from current page outward on zoom/re-render
renderPdf now takes an optional pivotPage. Pass 1 builds the full DOM and sizes all canvases (instant). Then scrolls to the pivot page immediately so the user stays in place. Pass 2 renders pixels from pivot→end, then pivot-1→start. reRenderPdf detects the current visible page before re-rendering and passes it as pivot, so zoom no longer resets scroll position. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
20a1b9a889
commit
1cf3f730ea
1 changed files with 38 additions and 22 deletions
|
|
@ -3010,7 +3010,7 @@ async function _parsePdfOutline(pdf, items, depth) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function renderPdf(arrayBuffer, contentEl, scaleOverride) {
|
async function renderPdf(arrayBuffer, contentEl, scaleOverride, pivotPage) {
|
||||||
const myGen = ++_pdfRenderGen;
|
const myGen = ++_pdfRenderGen;
|
||||||
const pdf = currentPdfDoc || await pdfjsLib.getDocument({data: new Uint8Array(arrayBuffer.slice(0))}).promise;
|
const pdf = currentPdfDoc || await pdfjsLib.getDocument({data: new Uint8Array(arrayBuffer.slice(0))}).promise;
|
||||||
if (_pdfRenderGen !== myGen) return null;
|
if (_pdfRenderGen !== myGen) return null;
|
||||||
|
|
@ -3031,7 +3031,6 @@ async function renderPdf(arrayBuffer, contentEl, scaleOverride) {
|
||||||
|
|
||||||
contentEl.innerHTML = '';
|
contentEl.innerHTML = '';
|
||||||
|
|
||||||
// Viewport wrapper: CSS zoom controls display scale without re-rendering
|
|
||||||
const pdfVp = document.createElement('div');
|
const pdfVp = document.createElement('div');
|
||||||
pdfVp.id = 'pdf-viewport';
|
pdfVp.id = 'pdf-viewport';
|
||||||
contentEl.appendChild(pdfVp);
|
contentEl.appendChild(pdfVp);
|
||||||
|
|
@ -3039,15 +3038,17 @@ async function renderPdf(arrayBuffer, contentEl, scaleOverride) {
|
||||||
const containerWidth = readerSettings.pdfSpread
|
const containerWidth = readerSettings.pdfSpread
|
||||||
? contentEl.clientWidth - 32
|
? contentEl.clientWidth - 32
|
||||||
: Math.min(contentEl.clientWidth - 32, 900);
|
: Math.min(contentEl.clientWidth - 32, 900);
|
||||||
|
const pageWidth = readerSettings.pdfSpread ? (containerWidth - 8) / 2 : containerWidth;
|
||||||
|
const dpr = window.devicePixelRatio || 1;
|
||||||
|
|
||||||
|
// Pass 1: build DOM structure and size all canvases (no pixel rendering yet)
|
||||||
|
const pageEntries = [];
|
||||||
let spreadContainer = null;
|
let spreadContainer = null;
|
||||||
|
|
||||||
for (let pageNum = 1; pageNum <= pdf.numPages; pageNum++) {
|
for (let pageNum = 1; pageNum <= pdf.numPages; pageNum++) {
|
||||||
if (_pdfRenderGen !== myGen) { contentEl.innerHTML = ''; return null; }
|
if (_pdfRenderGen !== myGen) { contentEl.innerHTML = ''; return null; }
|
||||||
const page = await pdf.getPage(pageNum);
|
const page = await pdf.getPage(pageNum);
|
||||||
const naturalVp = page.getViewport({scale: 1});
|
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
|
const scale = scaleOverride != null ? scaleOverride
|
||||||
: Math.max(0.5, pageWidth / naturalVp.width) * (readerSettings.pdfZoom / 100);
|
: Math.max(0.5, pageWidth / naturalVp.width) * (readerSettings.pdfZoom / 100);
|
||||||
const viewport = page.getViewport({scale});
|
const viewport = page.getViewport({scale});
|
||||||
|
|
@ -3056,12 +3057,9 @@ async function renderPdf(arrayBuffer, contentEl, scaleOverride) {
|
||||||
wrapper.className = 'pdf-page-wrapper';
|
wrapper.className = 'pdf-page-wrapper';
|
||||||
wrapper.id = `pdf-page-${pageNum}`;
|
wrapper.id = `pdf-page-${pageNum}`;
|
||||||
|
|
||||||
// Inner container gives canvas + text layer a shared position:relative origin,
|
|
||||||
// independent of the outer flex wrapper's centering.
|
|
||||||
const inner = document.createElement('div');
|
const inner = document.createElement('div');
|
||||||
inner.className = 'pdf-page-inner';
|
inner.className = 'pdf-page-inner';
|
||||||
|
|
||||||
const dpr = window.devicePixelRatio || 1;
|
|
||||||
const canvas = document.createElement('canvas');
|
const canvas = document.createElement('canvas');
|
||||||
canvas.className = 'pdf-page';
|
canvas.className = 'pdf-page';
|
||||||
canvas.width = Math.round(viewport.width * dpr);
|
canvas.width = Math.round(viewport.width * dpr);
|
||||||
|
|
@ -3087,14 +3085,29 @@ async function renderPdf(arrayBuffer, contentEl, scaleOverride) {
|
||||||
else pdfVp.appendChild(wrapper);
|
else pdfVp.appendChild(wrapper);
|
||||||
}
|
}
|
||||||
|
|
||||||
const ctx = canvas.getContext('2d');
|
pageEntries.push({page, canvas, viewport});
|
||||||
ctx.scale(dpr, dpr);
|
|
||||||
await page.render({canvasContext: ctx, viewport}).promise;
|
|
||||||
|
|
||||||
// Text layer disabled — re-enable once overlay rendering is resolved
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pdfTotalPages = pdf.numPages;
|
pdfTotalPages = pdf.numPages;
|
||||||
|
|
||||||
|
// Scroll to pivot page immediately — DOM and canvas sizes are set
|
||||||
|
const pivot = Math.max(1, Math.min(pdf.numPages, pivotPage || 1));
|
||||||
|
const pivotEl = document.getElementById(`pdf-page-${pivot}`);
|
||||||
|
if (pivotEl && pivot > 1) pivotEl.scrollIntoView({block: 'start'});
|
||||||
|
|
||||||
|
// Pass 2: render pixels from pivot outward (pivot→end, then pivot-1→start)
|
||||||
|
const order = [];
|
||||||
|
for (let i = pivot - 1; i < pdf.numPages; i++) order.push(i);
|
||||||
|
for (let i = pivot - 2; i >= 0; i--) order.push(i);
|
||||||
|
|
||||||
|
for (const idx of order) {
|
||||||
|
if (_pdfRenderGen !== myGen) { contentEl.innerHTML = ''; return null; }
|
||||||
|
const {page, canvas, viewport} = pageEntries[idx];
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
ctx.scale(dpr, dpr);
|
||||||
|
await page.render({canvasContext: ctx, viewport}).promise;
|
||||||
|
}
|
||||||
|
|
||||||
return {title: pdfTitle, author: pdfAuthor, toc, numPages: pdf.numPages};
|
return {title: pdfTitle, author: pdfAuthor, toc, numPages: pdf.numPages};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3698,10 +3711,6 @@ function toggleSettingsPanel() {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function applyPdfZoom(newZoom) {
|
async function applyPdfZoom(newZoom) {
|
||||||
const contentEl2 = $('reader-content');
|
|
||||||
const fraction = contentEl2
|
|
||||||
? contentEl2.scrollTop / (contentEl2.scrollHeight - contentEl2.clientHeight || 1)
|
|
||||||
: 0;
|
|
||||||
readerSettings.pdfZoom = newZoom;
|
readerSettings.pdfZoom = newZoom;
|
||||||
const zoomRange = document.getElementById('rs-zoom');
|
const zoomRange = document.getElementById('rs-zoom');
|
||||||
const zoomVal = document.getElementById('rs-zoom-val');
|
const zoomVal = document.getElementById('rs-zoom-val');
|
||||||
|
|
@ -3712,25 +3721,32 @@ async function applyPdfZoom(newZoom) {
|
||||||
pdfSmartZoomPage(pdfCurrentPage);
|
pdfSmartZoomPage(pdfCurrentPage);
|
||||||
} else {
|
} else {
|
||||||
await reRenderPdf();
|
await reRenderPdf();
|
||||||
// Wait for two animation frames so the browser finishes layout
|
}
|
||||||
// before reading scrollHeight for position restoration
|
}
|
||||||
await new Promise(r => requestAnimationFrame(() => requestAnimationFrame(r)));
|
|
||||||
if (contentEl2 && fraction > 0) {
|
function _currentScrollPage(contentEl) {
|
||||||
contentEl2.scrollTop = fraction * (contentEl2.scrollHeight - contentEl2.clientHeight);
|
const wrappers = contentEl.querySelectorAll('.pdf-page-wrapper');
|
||||||
|
const viewTop = contentEl.scrollTop;
|
||||||
|
for (const w of wrappers) {
|
||||||
|
if (w.offsetTop + w.offsetHeight > viewTop) {
|
||||||
|
const n = parseInt(w.id.replace('pdf-page-', ''), 10);
|
||||||
|
if (n) return n;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return pdfCurrentPage || 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function reRenderPdf() {
|
async function reRenderPdf() {
|
||||||
if (!currentPdfBuffer) return;
|
if (!currentPdfBuffer) return;
|
||||||
const contentEl = $('reader-content');
|
const contentEl = $('reader-content');
|
||||||
if (!contentEl) return;
|
if (!contentEl) return;
|
||||||
|
const pivot = readerSettings.pdfPaginated ? pdfCurrentPage : _currentScrollPage(contentEl);
|
||||||
const overlay = $('reader-overlay');
|
const overlay = $('reader-overlay');
|
||||||
const loadingEl = document.createElement('div');
|
const loadingEl = document.createElement('div');
|
||||||
loadingEl.className = 'pdf-loading-overlay';
|
loadingEl.className = 'pdf-loading-overlay';
|
||||||
loadingEl.innerHTML = '<span class="pdf-loading-spinner"></span>';
|
loadingEl.innerHTML = '<span class="pdf-loading-spinner"></span>';
|
||||||
if (overlay) overlay.appendChild(loadingEl);
|
if (overlay) overlay.appendChild(loadingEl);
|
||||||
await renderPdf(currentPdfBuffer, contentEl);
|
await renderPdf(currentPdfBuffer, contentEl, undefined, pivot);
|
||||||
loadingEl.remove();
|
loadingEl.remove();
|
||||||
if (readerSettings.pdfPaginated) enterPdfPaginatedMode();
|
if (readerSettings.pdfPaginated) enterPdfPaginatedMode();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue