Files
foliate-js/comic-book.js
T
john-okeefe e9e61d885b Touch gestures for fixed-layout: pinch-zoom, pan, swipe paging, double-tap
- Single finger: pan while zoomed; horizontal swipe pages at fit
  (direction mapped through next()/prev() so RTL manga reads
  correctly); drag on PDF text still defers to selection via the
  existing smart-detection (realTarget).
- Two fingers: pinch-zoom around the midpoint (through the shared
  zoom machinery, so PDF re-render scheduling works) plus
  midpoint-pan; lifts back to single-finger pan when one lifts.
- Double-tap: zoom 2.5x at the point / reset to fit.
- Touch events forwarded from page iframes with converted
  coordinates; preventDefault only when the engine actually consumes
  the gesture so native selection/taps stay intact.
- touch-action: none on :host and in comic/pdf page documents so the
  browser does not fight the gesture engine inside iframes.
2026-08-14 15:54:00 -04:00

46 lines
1.8 KiB
JavaScript

export const makeComicBook = ({ entries, loadBlob, getSize }, file) => {
const cache = new Map()
const urls = new Map()
const load = async name => {
if (cache.has(name)) return cache.get(name)
const src = URL.createObjectURL(await loadBlob(name))
const page = URL.createObjectURL(
new Blob([`<!DOCTYPE html><html><head><meta charset="utf-8"></head><body style="margin: 0; touch-action: none"><img src="${src}" style="image-rendering: high-quality"></body></html>`], { type: 'text/html' }))
urls.set(name, [src, page])
cache.set(name, page)
return page
}
const unload = name => {
urls.get(name)?.forEach?.(url => URL.revokeObjectURL(url))
urls.delete(name)
cache.delete(name)
}
const exts = ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp', '.svg', '.jxl', '.avif']
const files = entries
.map(entry => entry.filename)
.filter(name => exts.some(ext => name.endsWith(ext)))
.sort(new Intl.Collator([], { numeric: true }).compare)
if (!files.length) throw new Error('No supported image files in archive')
const book = {}
book.getCover = () => loadBlob(files[0])
book.metadata = { title: file.name }
book.sections = files.map(name => ({
id: name,
load: () => load(name),
unload: () => unload(name),
size: getSize(name),
}))
book.toc = files.map(name => ({ label: name, href: name }))
book.rendition = { layout: 'pre-paginated' }
book.resolveHref = href => ({ index: book.sections.findIndex(s => s.id === href) })
book.splitTOCHref = href => [href, null]
book.getTOCFragment = doc => doc.documentElement
book.destroy = () => {
for (const arr of urls.values())
for (const url of arr) URL.revokeObjectURL(url)
}
return book
}