'use strict'; // --------------------------------------------------------------------------- // The PDF reader: one document, shown either scrolling (every page has its // place, only those near the view are drawn) or page by page, cropped to the // page's text (66-pdf-paginated.js). Whichever, a page is drawn only through // _pdfDraw: one drawing at a time per canvas, sharp at the device's pixel // ratio but never larger than PDF_MAX_PIXELS. // --------------------------------------------------------------------------- // pdf.js's own viewer caps a canvas at this many pixels: beyond it phones run out of memory. const PDF_MAX_PIXELS = 16777216; // One pdf.js worker for every document (the reader, covers, uploads): starting one takes a // moment — on an e-reader a long one. let _pdfWorker = null; function _pdfSharedWorker() { if (!_pdfWorker && typeof pdfjsLib !== 'undefined' && pdfjsLib.PDFWorker) { try { _pdfWorker = new pdfjsLib.PDFWorker(); } catch (e) { _pdfWorker = null; } } return _pdfWorker; } /** pdf.js's loading task for [bytes] (a Uint8Array), on the shared worker. */ function _pdfLoad(bytes) { const worker = _pdfSharedWorker(); return pdfjsLib.getDocument(worker ? {data: bytes, worker} : {data: bytes}); } /** * The page (from 1) a destination leads to — a named one or an explicit array, whose page is * a reference or, in some files, a plain index. 0 when it leads nowhere. */ async function _pdfDestPage(pdf, dest) { try { if (typeof dest === 'string') dest = await pdf.getDestination(dest); if (!dest || dest[0] == null) return 0; return (typeof dest[0] === 'number' ? dest[0] : await pdf.getPageIndex(dest[0])) + 1; } catch (e) { return 0; } } // The table of contents: all entries resolved at once (one after the other cost a round trip // to the worker each). async function _parsePdfOutline(pdf, items, depth) { depth = depth || 0; const parts = await Promise.all(items.map(async item => { let href = ''; if (item.dest) { const n = await _pdfDestPage(pdf, item.dest); if (n) href = `#pdf-page-${n}`; } const kids = item.items && item.items.length ? await _parsePdfOutline(pdf, item.items, depth + 1) : []; return [{label: item.title || '(untitled)', href, depth}].concat(kids); })); return [].concat.apply([], parts); } // The document being loaded (pdf.js loading task), so opening can be cancelled. let _pdfLoadingTask = null; // The open document's pages and how they are drawn (renderPdf; stopped on the next one). let _pdfView = null; // Backwards name: the reader stops "the lazy drawing" when a book closes. let _pdfLazy = null; // The open document's text, page by page (pdf.js text content): for the text layer, the // search and the crop to the text box. Kept for the document, not for one layout of it. let _pdfTexts = null; function _pdfPageText(pdf, num) { if (!_pdfTexts || _pdfTexts.pdf !== pdf) _pdfTexts = {pdf, pages: new Map()}; let p = _pdfTexts.pages.get(num); if (!p) { p = pdf.getPage(num).then(page => page.getTextContent()).catch(() => ({items: [], styles: {}})); _pdfTexts.pages.set(num, p); } return p; } /** * A page's text as one string, lower case, for searching — with where each item (each span of * the text layer, in order) starts in it. Lines ending inside it get a space. */ function _pdfPageString(tc) { if (tc._diora) return tc._diora; let str = ''; const starts = []; for (const item of tc.items) { starts.push(str.length); str += item.str || ''; if (item.hasEOL) str += ' '; } tc._diora = {str: str.toLowerCase(), starts}; return tc._diora; } // Title, table of contents and page labels: asked for once per document, alongside the // pages (opening doesn't wait for them; a layout anew reuses them). let _pdfExtras = null; function _pdfDocExtras(pdf) { if (_pdfExtras && _pdfExtras.pdf === pdf) return _pdfExtras; const meta = pdf.getMetadata().then(m => ({ title: (m && m.info && m.info.Title || '').trim(), author: (m && m.info && m.info.Author || '').trim(), }), () => ({title: '', author: ''})); const toc = pdf.getOutline().then(o => (o && o.length ? _parsePdfOutline(pdf, o) : []), () => []); const labels = pdf.getPageLabels().then(l => { // Labels that only say 1, 2, 3 … are no labels. if (!l || !l.length || l.every((s, i) => !s || s === String(i + 1))) return null; return l.map((s, i) => s || String(i + 1)); }, () => null); _pdfExtras = {pdf, meta, toc, labels}; return _pdfExtras; } // The page labels of the open document (front matter "iv", appendix "A-3" …), null without. let _pdfLabels = null; /** Page [n] (from 1) as the book numbers it. */ function pdfPageLabel(n) { return (_pdfLabels && _pdfLabels[n - 1]) || String(n); } /** The page meant by [value] typed in: a label of the book first, else a page number. 0: none. */ function _pdfPageFromInput(value) { const v = String(value || '').trim(); if (!v) return 0; if (_pdfLabels) { let i = _pdfLabels.indexOf(v); if (i < 0) i = _pdfLabels.findIndex(l => l.toLowerCase() === v.toLowerCase()); if (i >= 0) return i + 1; } return parseInt(v, 10) || 0; } /** * The pages in the reader's colours (Leserfarben): {background, foreground} as #rrggbb, the * reader's (Dunkel, Sepia …). null: as printed (also when the reader is black on white). */ function _pdfPageColors() { if (!readerSettings.pdfPageColors || readerSettings.pdfInverted) return null; const contentEl = $('reader-content'); if (!contentEl) return null; let bg = null; for (let el = contentEl; el && !bg; el = el.parentElement) bg = _pdfCssHex(getComputedStyle(el).backgroundColor); const fg = _pdfCssHex(getComputedStyle(contentEl).color); // On white the page is as printed anyway (Hell). if (!bg || !fg || bg === '#ffffff') return null; return {background: bg, foreground: fg}; } /** "rgb(…)" / "rgba(…)" as #rrggbb; null for transparent. */ function _pdfCssHex(css) { const m = /rgba?\(\s*(\d+)[,\s]+(\d+)[,\s]+(\d+)(?:[,\s/]+([\d.]+))?/.exec(css || ''); if (!m || (m[4] !== undefined && parseFloat(m[4]) === 0)) return null; return '#' + [m[1], m[2], m[3]].map(x => (+x).toString(16).padStart(2, '0')).join(''); } // Whether the pages are drawn for Leserfarben (with their pictures laid over them, below): // switching that draws them anew; another theme only changes the filter's colours. function _pdfColorsKey() { return _pdfPageColors() ? 'reader' : ''; } /** * Leserfarben: the page is drawn as printed and recoloured by an SVG filter on its canvas — * its brightness from the reader's text colour (black) to its background (white). (pdf.js's * own pageColors recolours the pictures too, and lets nothing be left out.) Kept up to date * with the colours; true when it applies. */ function _pdfColorFilter() { const c = _pdfPageColors(); if (!c) return false; const ns = 'http://www.w3.org/2000/svg'; let svg = document.getElementById('pdf-reader-colors-svg'); if (!svg) { svg = document.createElementNS(ns, 'svg'); svg.id = 'pdf-reader-colors-svg'; svg.setAttribute('width', '0'); svg.setAttribute('height', '0'); svg.setAttribute('aria-hidden', 'true'); svg.style.position = 'absolute'; svg.innerHTML = '' + '' + '' + ''; document.body.appendChild(svg); } const ch = (hex, i) => (parseInt(hex.substr(1 + i * 2, 2), 16) / 255).toFixed(3); ['R', 'G', 'B'].forEach((name, i) => { svg.querySelector('feFunc' + name).setAttribute('tableValues', `${ch(c.foreground, i)} ${ch(c.background, i)}`); }); return true; } /** * Where page [page]'s pictures are, in CSS pixels of [viewport]: from its drawing operations * (the unit square of each image under the transform at that point). Image masks are left * out (they're drawn in a colour, like text). */ async function _pdfImageRects(page, viewport) { const OPS = pdfjsLib.OPS; let list; try { list = await page.getOperatorList({annotationMode: pdfjsLib.AnnotationMode.ENABLE_FORMS}); } catch (e) { return []; } const mul = (m, n) => [m[0] * n[0] + m[2] * n[1], m[1] * n[0] + m[3] * n[1], m[0] * n[2] + m[2] * n[3], m[1] * n[2] + m[3] * n[3], m[0] * n[4] + m[2] * n[5] + m[4], m[1] * n[4] + m[3] * n[5] + m[5]]; let ctm = viewport.transform.slice(); const stack = []; const rects = []; for (let i = 0; i < list.fnArray.length; i++) { const fn = list.fnArray[i], args = list.argsArray[i]; if (fn === OPS.save) stack.push(ctm); else if (fn === OPS.restore) ctm = stack.pop() || ctm; else if (fn === OPS.transform) ctm = mul(ctm, args); else if (fn === OPS.paintFormXObjectBegin) { stack.push(ctm); if (Array.isArray(args[0]) && args[0].length === 6) ctm = mul(ctm, args[0]); } else if (fn === OPS.paintFormXObjectEnd) ctm = stack.pop() || ctm; else if (fn === OPS.paintImageXObject || fn === OPS.paintInlineImageXObject) { const xs = [ctm[4], ctm[0] + ctm[4], ctm[2] + ctm[4], ctm[0] + ctm[2] + ctm[4]]; const ys = [ctm[5], ctm[1] + ctm[5], ctm[3] + ctm[5], ctm[1] + ctm[3] + ctm[5]]; const x = Math.max(0, Math.min.apply(null, xs)), y = Math.max(0, Math.min.apply(null, ys)); const w = Math.min(viewport.width, Math.max.apply(null, xs)) - x; const h = Math.min(viewport.height, Math.max.apply(null, ys)) - y; if (w > 2 && h > 2) rects.push({x, y, w, h}); } } return rects; } // A picture covering this much of the page is the page itself (a scan): recoloured with it. const PDF_SCAN_SHARE = 0.5; /** Leserfarben: page [e]'s pictures laid over it as printed (copied from its canvas). */ async function _pdfImageLayer(e, page, viewport, ratio, token) { const rects = await _pdfImageRects(page, viewport); if (e.token !== token) return; const area = viewport.width * viewport.height; const shown = rects.filter(r => r.w * r.h < area * PDF_SCAN_SHARE); if (!shown.length) return; const layer = document.createElement('div'); layer.className = 'pdf-image-layer'; for (const r of shown) { const c = document.createElement('canvas'); const sx = Math.floor(r.x * ratio), sy = Math.floor(r.y * ratio); const sw = Math.min(e.canvas.width - sx, Math.ceil(r.w * ratio)), sh = Math.min(e.canvas.height - sy, Math.ceil(r.h * ratio)); if (sw <= 0 || sh <= 0) continue; c.width = sw; c.height = sh; c.getContext('2d').drawImage(e.canvas, sx, sy, sw, sh, 0, 0, sw, sh); c.style.left = sx / ratio + 'px'; c.style.top = sy / ratio + 'px'; c.style.width = sw / ratio + 'px'; c.style.height = sh / ratio + 'px'; layer.appendChild(c); } e.inner.insertBefore(layer, e.canvas.nextSibling); } // Internal links followed, the pages they were followed from (the back button). let _pdfBackStack = []; /** The document is gone (another one, or the reader closed): what belonged to it goes too. */ function _pdfForgetDoc() { _pdfTexts = null; _pdfExtras = null; _pdfLabels = null; _pdfBackStack = []; _pdfShowBack(); if (typeof _pdfSearchStop === 'function') _pdfSearchStop(); } async function _pdfFollowDest(dest) { const pdf = currentPdfDoc; if (!pdf) return; const n = await _pdfDestPage(pdf, dest); if (!n || pdf !== currentPdfDoc) return; const contentEl = $('reader-content'); const from = readerSettings.pdfPaginated || !contentEl ? pdfCurrentPage : _pdfTopPage(contentEl); if (n === from) return; _pdfBackStack.push(from); pdfGoToPage(n); _pdfShowBack(); } /** The button back to where the last link was followed from (gone when there's none). */ function _pdfShowBack() { let btn = document.getElementById('pdf-back-btn'); const from = _pdfBackStack[_pdfBackStack.length - 1]; if (!from) { if (btn) btn.remove(); return; } if (!btn) { const overlay = $('reader-overlay'); if (!overlay) return; btn = document.createElement('button'); btn.id = 'pdf-back-btn'; btn.type = 'button'; btn.className = 'btn btn-sm pdf-back-btn'; btn.addEventListener('click', ev => { ev.stopPropagation(); const page = _pdfBackStack.pop(); if (page) pdfGoToPage(page); _pdfShowBack(); }); overlay.appendChild(btn); } btn.textContent = `← Zurück zu S. ${pdfPageLabel(from)}`; } /** A page's text layer and links taken off (another scale, or the page let go). */ function _pdfDropLayers(e) { if (e.textTask) { try { e.textTask.cancel(); } catch (err) {} } e.textTask = null; e.textDivs = null; e.textInfo = null; e.marked = null; e.inner.querySelectorAll('.pdf-text-layer, .pdf-link-layer, .pdf-image-layer').forEach(el => el.remove()); } /** * Over a drawn page: its text (transparent, to select and copy, and where the search marks * go) and its links. After the pixels, so a turn isn't held up by them. */ async function _pdfLayers(e, page, viewport, token) { const pdf = currentPdfDoc; if (!pdf) return; let annots = []; try { annots = await page.getAnnotations({intent: 'display'}); } catch (err) {} if (e.token !== token) return; _pdfLinks(e, viewport, annots); if (typeof pdfjsLib.renderTextLayer !== 'function') return; const text = await _pdfPageText(pdf, e.num); if (e.token !== token || !text.items.length) return; const layer = document.createElement('div'); layer.className = 'pdf-text-layer'; layer.style.setProperty('--scale-factor', viewport.scale); e.inner.appendChild(layer); const divs = []; const task = pdfjsLib.renderTextLayer({textContentSource: text, container: layer, viewport, textDivs: divs}); e.textTask = task; try { await task.promise; } catch (err) { return; } if (e.token !== token || e.textTask !== task) return; e.textTask = null; e.textDivs = divs; e.textInfo = {items: text.items, string: _pdfPageString(text)}; if (typeof _pdfSearchMarkPage === 'function') _pdfSearchMarkPage(e); } // Links: ones into the document go there (and back, _pdfShowBack); web addresses open outside. function _pdfLinks(e, viewport, annots) { const links = annots.filter(a => a.subtype === 'Link' && (a.url || a.dest)); if (!links.length) return; const layer = document.createElement('div'); layer.className = 'pdf-link-layer'; for (const a of links) { const r = viewport.convertToViewportRectangle(a.rect); const link = document.createElement('a'); link.className = 'pdf-link'; link.style.left = Math.min(r[0], r[2]) + 'px'; link.style.top = Math.min(r[1], r[3]) + 'px'; link.style.width = Math.abs(r[2] - r[0]) + 'px'; link.style.height = Math.abs(r[3] - r[1]) + 'px'; if (a.url) { link.href = a.url; link.target = '_blank'; link.rel = 'noopener noreferrer'; link.title = a.url; } else { const dest = a.dest; link.href = '#'; link.addEventListener('click', ev => { ev.preventDefault(); ev.stopPropagation(); _pdfFollowDest(dest); }); } layer.appendChild(link); } e.inner.appendChild(layer); } function _pdfStopLazy() { const v = _pdfView; if (!v) return; v.stopped = true; if (v.observer) v.observer.disconnect(); if (v.resize) v.resize.disconnect(); clearTimeout(v.resizeTimer); v.entries.forEach(e => { e.token++; if (e.task) { try { e.task.cancel(); } catch (err) {} } }); _pdfView = _pdfLazy = null; } /** * Draw page [page] of entry [e] at [cssScale] (CSS pixels per PDF point). Cancels a drawing * of the same canvas still under way (pdf.js refuses two at once) and waits for it to let go. * Returns false when a newer drawing (or letting go) came first. */ function _pdfDraw(e, page, cssScale) { // The same drawing asked for again while it's under way (the page shown, then laid out // again as the bars go): that one, not a new one cancelling it. if (e.drawing && e.drawing.scale === cssScale) return e.drawing.promise; const drawing = {scale: cssScale, promise: null}; drawing.promise = _pdfDrawNow(e, page, cssScale).then(ok => { if (e.drawing === drawing) e.drawing = null; return ok; }, err => { if (e.drawing === drawing) e.drawing = null; throw err; }); e.drawing = drawing; return drawing.promise; } async function _pdfDrawNow(e, page, cssScale) { const token = ++e.token; _pdfDropLayers(e); const prev = e.task; if (prev) { try { prev.cancel(); } catch (err) {} try { await prev.promise; } catch (err) {} if (e.token !== token) return false; } const viewport = page.getViewport({scale: cssScale}); let ratio = window.devicePixelRatio || 1; const area = viewport.width * viewport.height; if (area * ratio * ratio > PDF_MAX_PIXELS) ratio = Math.max(0.5, Math.sqrt(PDF_MAX_PIXELS / area)); e.canvas.width = Math.round(viewport.width * ratio); e.canvas.height = Math.round(viewport.height * ratio); e.canvas.style.width = viewport.width + 'px'; e.canvas.style.height = viewport.height + 'px'; const task = page.render({ canvasContext: e.canvas.getContext('2d'), viewport, transform: ratio !== 1 ? [ratio, 0, 0, ratio, 0, 0] : null, }); e.task = task; try { await task.promise; } catch (err) { if (e.task === task) e.task = null; if (err && err.name !== 'RenderingCancelledException') console.warn('PDF page', e.num, err); return false; } if (e.task === task) e.task = null; if (e.token !== token) return false; e.drawn = cssScale; const recolor = _pdfColorFilter(); e.canvas.classList.toggle('pdf-recolored', recolor); if (recolor) _pdfImageLayer(e, page, viewport, ratio, token).catch(err => console.warn('PDF page pictures', e.num, err)); _pdfLayers(e, page, viewport, token).catch(err => console.warn('PDF page layers', e.num, err)); return true; } /** Let a page's pixels go (its place stays) and pdf.js forget what it parsed of it. */ function _pdfLetGo(e, pdf) { e.token++; e.drawing = null; if (e.task) { try { e.task.cancel(); } catch (err) {} } _pdfDropLayers(e); e.drawn = 0; e.canvas.width = 0; e.canvas.height = 0; if (pdf) pdf.getPage(e.num).then(p => { if (!e.task) p.cleanup(); }, () => {}); } /** The page shown at the top of the scrolling view (binary search, the pages are in order). */ function _pdfTopPage(contentEl) { const v = _pdfView; if (!v || !v.entries.length) return pdfCurrentPage || 1; const top = contentEl.getBoundingClientRect().top + 20; let lo = 0, hi = v.entries.length - 1; while (lo < hi) { const mid = (lo + hi) >> 1; if (v.entries[mid].wrapper.getBoundingClientRect().bottom > top) hi = mid; else lo = mid + 1; } return v.entries[lo].num; } // Every page gets its place at once (sized like the first page until it is drawn), but only the // pages near the view are drawn; ones far away are let go again. A book of 1000 scanned pages // opens as fast as one of ten, and doesn't hold 1000 canvases. Page by page (pdfPaginated), // 66-pdf-paginated.js shows and draws the pages itself. async function renderPdf(arrayBuffer, contentEl, scaleOverride, pivotPage) { const myGen = ++_pdfRenderGen; _pdfStopLazy(); let pdf = currentPdfDoc; if (!pdf) { _pdfForgetDoc(); _pdfLoadingTask = _pdfLoad(new Uint8Array(arrayBuffer.slice(0))); try { pdf = await _pdfLoadingTask.promise; } finally { _pdfLoadingTask = null; } } if (_pdfRenderGen !== myGen) return null; currentPdfDoc = pdf; // [pivotPage] may be given as what of the document it is (the place saved, a fraction). if (typeof pivotPage === 'function') pivotPage = pivotPage(pdf.numPages); const extras = _pdfDocExtras(pdf); extras.labels.then(labels => { if (pdf !== currentPdfDoc) return; _pdfLabels = labels; if (labels) _pdfSetCurrentPage(pdfCurrentPage); }); 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 scaleFor = vp => (scaleOverride != null ? scaleOverride : Math.max(0.5, pageWidth / vp.width) * (readerSettings.pdfZoom / 100)); // The first page's size stands for all until each is drawn (a scan's pages are alike). const first = await pdf.getPage(1); if (_pdfRenderGen !== myGen) return null; const firstVp = first.getViewport({scale: scaleFor(first.getViewport({scale: 1}))}); const entries = []; let spreadContainer = null; for (let pageNum = 1; pageNum <= pdf.numPages; pageNum++) { 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 = 0; canvas.height = 0; canvas.style.width = firstVp.width + 'px'; canvas.style.height = firstVp.height + 'px'; inner.appendChild(canvas); wrapper.appendChild(inner); if (!readerSettings.pdfSpread || readerSettings.pdfPaginated) { 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); } entries.push({num: pageNum, wrapper, inner, canvas, drawn: 0, near: false, task: null, token: 0}); } pdfTotalPages = pdf.numPages; const pivot = Math.max(1, Math.min(pdf.numPages, pivotPage || 1)); const view = {pdf, contentEl, entries, gen: myGen, stopped: false, observer: null, resize: null, resizeTimer: 0, queue: new Set(), working: false, scaleFor, showToken: 0, colorsKey: _pdfColorsKey()}; _pdfView = _pdfLazy = view; const gone = () => view.stopped || _pdfRenderGen !== myGen; async function draw(e) { const page = await pdf.getPage(e.num); if (gone() || !e.near || readerSettings.pdfPaginated) return; await _pdfDraw(e, page, scaleFor(page.getViewport({scale: 1}))); } // One page at a time, the nearest to the current one first. async function work() { if (view.working) return; view.working = true; try { while (!gone() && view.queue.size && !readerSettings.pdfPaginated) { const cur = _pdfTopPage(contentEl); const next = Array.from(view.queue).sort((a, b) => Math.abs(a.num - cur) - Math.abs(b.num - cur))[0]; view.queue.delete(next); if (next.near && !next.drawn) await draw(next); } } finally { view.working = false; } } // Turning the device, a narrower window, the bars coming or going: laid out again at the same page. let lastW = contentEl.clientWidth, lastH = contentEl.clientHeight; if (typeof ResizeObserver === 'function') { view.resize = new ResizeObserver(() => { const w = contentEl.clientWidth, h = contentEl.clientHeight; if (!w || !h || (w === lastW && h === lastH)) return; const widthChanged = Math.abs(w - lastW) > 2; lastW = w; lastH = h; clearTimeout(view.resizeTimer); view.resizeTimer = setTimeout(() => { if (gone()) return; if (readerSettings.pdfPaginated) _pdfShowPage(pdfCurrentPage); else if (widthChanged) reRenderPdf(_pdfTopPage(contentEl)); }, readerSettings.pdfPaginated ? 150 : 300); }); view.resize.observe(contentEl); } if (readerSettings.pdfPaginated) { // Page by page: the pivot page cropped to its text, at once (66-pdf-paginated.js). pdfCurrentPage = pivot; await _pdfShowPage(pivot); if (gone()) return null; return _pdfResult(pdf, extras); } if (pivot > 1) entries[pivot - 1].wrapper.scrollIntoView({block: 'start'}); pdfCurrentPage = pivot; const byEl = new Map(entries.map(e => [e.wrapper, e])); if (typeof IntersectionObserver === 'function') { view.observer = new IntersectionObserver(items => { if (readerSettings.pdfPaginated) return; for (const it of items) { const e = byEl.get(it.target); if (!e) continue; e.near = it.isIntersecting; if (e.near) { if (!e.drawn) view.queue.add(e); } else { view.queue.delete(e); if (e.drawn || e.task) _pdfLetGo(e, pdf); } } work(); }, {root: contentEl, rootMargin: '150% 0px'}); entries.forEach(e => view.observer.observe(e.wrapper)); } else { // Very old WebViews: every page, from the pivot on (as before). entries.forEach(e => { e.near = true; view.queue.add(e); }); work(); } // The book shows as soon as the page it opens at is drawn. const pe = entries[pivot - 1]; pe.near = true; if (!pe.drawn) await draw(pe); if (gone()) return null; return _pdfResult(pdf, extras); } // What openBook needs of the document: the title (asked for while the page was drawn) and // the table of contents to come (a long one resolves its pages after the book is shown). async function _pdfResult(pdf, extras) { const meta = await extras.meta; return {title: meta.title, author: meta.author, toc: [], tocLater: extras.toc, numPages: pdf.numPages}; }