diora-web/static/js/app/58-pdf.js

125 lines
4.3 KiB
JavaScript
Raw Normal View History

'use strict';
async function _parsePdfOutline(pdf, items, depth) {
depth = depth || 0;
const result = [];
for (const item of items) {
let href = '';
if (item.dest) {
try {
const dest = typeof item.dest === 'string' ? await pdf.getDestination(item.dest) : item.dest;
if (dest) {
const pageIndex = await pdf.getPageIndex(dest[0]);
href = `#pdf-page-${pageIndex + 1}`;
}
} catch (e) {}
}
result.push({label: item.title || '(untitled)', href, depth});
if (item.items && item.items.length) {
result.push(...await _parsePdfOutline(pdf, item.items, depth + 1));
}
}
return result;
}
async function renderPdf(arrayBuffer, contentEl, scaleOverride, pivotPage) {
const myGen = ++_pdfRenderGen;
const pdf = currentPdfDoc || await pdfjsLib.getDocument({data: new Uint8Array(arrayBuffer.slice(0))}).promise;
if (_pdfRenderGen !== myGen) return null;
currentPdfDoc = pdf;
let pdfTitle = '', pdfAuthor = '';
try {
const meta = await pdf.getMetadata();
pdfTitle = meta.info?.Title?.trim() || '';
pdfAuthor = meta.info?.Author?.trim() || '';
} catch (e) {}
let toc = [];
try {
const outline = await pdf.getOutline();
if (outline && outline.length) toc = await _parsePdfOutline(pdf, outline);
} catch (e) {}
contentEl.innerHTML = '';
const pdfVp = document.createElement('div');
pdfVp.id = 'pdf-viewport';
contentEl.appendChild(pdfVp);
const containerWidth = readerSettings.pdfSpread
? contentEl.clientWidth - 32
: 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;
for (let pageNum = 1; pageNum <= pdf.numPages; pageNum++) {
if (_pdfRenderGen !== myGen) { contentEl.innerHTML = ''; return null; }
const page = await pdf.getPage(pageNum);
const naturalVp = page.getViewport({scale: 1});
const scale = scaleOverride != null ? scaleOverride
: Math.max(0.5, pageWidth / naturalVp.width) * (readerSettings.pdfZoom / 100);
const viewport = page.getViewport({scale});
const wrapper = document.createElement('div');
wrapper.className = 'pdf-page-wrapper';
wrapper.id = `pdf-page-${pageNum}`;
const inner = document.createElement('div');
inner.className = 'pdf-page-inner';
const canvas = document.createElement('canvas');
canvas.className = 'pdf-page';
canvas.width = Math.round(viewport.width * dpr);
canvas.height = Math.round(viewport.height * dpr);
canvas.style.width = viewport.width + 'px';
canvas.style.height = viewport.height + 'px';
inner.appendChild(canvas);
wrapper.appendChild(inner);
if (!readerSettings.pdfSpread) {
pdfVp.appendChild(wrapper);
} else if (pageNum === 1) {
wrapper.classList.add('pdf-spread-cover');
pdfVp.appendChild(wrapper);
spreadContainer = null;
} else {
if (pageNum % 2 === 0) {
spreadContainer = document.createElement('div');
spreadContainer.className = 'pdf-spread-wrapper';
pdfVp.appendChild(spreadContainer);
}
if (spreadContainer) spreadContainer.appendChild(wrapper);
else pdfVp.appendChild(wrapper);
}
pageEntries.push({page, canvas, viewport});
}
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};
}