// The web reader's own block list for EPUBs, from the real app code (static/js/app/40-epub.js // parseEpub + the reader's block selector) in headless Chromium. Reference for compare_blocks.py. // // node web_blocks.cjs book.epub [more.epub ...] // // Needs playwright-core (e.g. NODE_PATH=/node_modules) and a Chromium // (PLAYWRIGHT_BROWSERS_PATH as for the e2e tests). const fs = require('fs'); const path = require('path'); const { chromium } = require('playwright-core'); (async () => { const [web, out, ...books] = process.argv.slice(2); const browser = await chromium.launch(); const page = await browser.newPage(); await page.goto('about:blank'); await page.addScriptTag({ path: path.join(web, 'static/js/jszip.min.js') }); await page.addScriptTag({ path: path.join(web, 'static/js/app/40-epub.js') }); const result = {}; for (const file of books) { const b64 = fs.readFileSync(file).toString('base64'); result[file] = await page.evaluate(async (b64) => { const bin = Uint8Array.from(atob(b64), (c) => c.charCodeAt(0)); const book = await parseEpub(bin.buffer); const el = document.createElement('div'); el.innerHTML = book.chapters.join(''); document.body.appendChild(el); const SEL = 'p, h1, h2, h3, h4, h5, h6, li, blockquote, dt, dd, figcaption, div:not(:has(*))'; // EPUB_BLOCK_SELECTOR, 44-books.js const blocks = Array.from(el.querySelectorAll(SEL)); const chapters = Array.from(el.querySelectorAll('[data-epub-src]')); // _bookCharIndex (44-books.js): offset of each block's first text node among all text nodes const offsets = new Map(); let pos = 0; const walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT); for (let n; (n = walker.nextNode());) { offsets.set(n, pos); pos += n.nodeValue.length; } const starts = []; blocks.forEach((b, i) => { const first = document.createTreeWalker(b, NodeFilter.SHOW_TEXT).nextNode(); starts[i] = first ? offsets.get(first) : (i ? starts[i - 1] + blocks[i - 1].textContent.length : 0); }); const res = blocks.map((b, i) => ({ start: starts[i], tag: b.tagName.toLowerCase(), text: b.textContent.replace(/\s+/g, ' ').trim(), len: b.textContent.length, ch: chapters.findIndex((c) => c.contains(b)), })); el.remove(); return { title: book.title, chapters: chapters.length, total: pos || 1, blocks: res }; }, b64); console.error(path.basename(file), result[file].blocks.length, 'blocks'); } fs.writeFileSync(out, JSON.stringify(result)); await browser.close(); })().catch((e) => { console.error(e); process.exit(1); });