1export const makeComicBook = ({ entries, loadBlob, getSize }, file) => { 2 const cache = new Map() 3 const urls = new Map() 4 const load = async name => { 5 if (cache.has(name)) return cache.get(name) 6 const src = URL.createObjectURL(await loadBlob(name)) 7 const page = URL.createObjectURL( 8 new Blob([`<!DOCTYPE html><html><head><meta charset="utf-8"></head><body style="margin: 0"><img src="${src}"></body></html>`], { type: 'text/html' })) 9 urls.set(name, [src, page]) 10 cache.set(name, page) 11 return page 12 } 13 const unload = name => { 14 urls.get(name)?.forEach?.(url => URL.revokeObjectURL(url)) 15 urls.delete(name) 16 cache.delete(name) 17 } 18 19 const exts = ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp', '.svg', '.jxl', '.avif'] 20 const files = entries 21 .map(entry => entry.filename) 22 .filter(name => exts.some(ext => name.endsWith(ext))) 23 .sort(new Intl.Collator([], { numeric: true }).compare) 24 if (!files.length) throw new Error('No supported image files in archive') 25 26 const book = {} 27 book.getCover = () => loadBlob(files[0]) 28 book.metadata = { title: file.name } 29 book.sections = files.map(name => ({ 30 id: name, 31 load: () => load(name), 32 unload: () => unload(name), 33 size: getSize(name), 34 })) 35 book.toc = files.map(name => ({ label: name, href: name })) 36 book.rendition = { layout: 'pre-paginated' } 37 book.resolveHref = href => ({ index: book.sections.findIndex(s => s.id === href) }) 38 book.splitTOCHref = href => [href, null] 39 book.getTOCFragment = doc => doc.documentElement 40 book.destroy = () => { 41 for (const arr of urls.values()) 42 for (const url of arr) URL.revokeObjectURL(url) 43 } 44 return book 45}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.