32 klassische Skripte in der alten Reihenfolge (01-core … 99-init), ohne Änderung am Code darin: geschnitten nur zwischen Anweisungen, je Datei 'use strict' wie vorher für die ganze Datei. Geprüft (acorn): kein Code, der beim Laden läuft, braucht einen Namen aus einer späteren Datei. Globale Funktionen und let/const bleiben global, die App-Schichten (native.js, native-eink.js, …) umhüllen sie wie bisher. Template und sw.js-SHELL listen die Dateien; diora-android build_web nimmt static/js rekursiv. Getestet: 14 Browser-Tests (e2e), Desktop-App (Buch, Lesestand, Leiste im Reader, Hell/Dunkel). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
124 lines
4.3 KiB
JavaScript
124 lines
4.3 KiB
JavaScript
'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};
|
|
}
|