foliate-js
Library for rendering e-books in the browser.
Features:
- Supports EPUB, MOBI, KF8 (AZW3), FB2, CBZ, PDF (experimental; requires PDF.js)
- Add support for other formats yourself by implementing the book interface
- Pure JavaScript
- Small and modular
- No hard dependencies
- Does not require loading whole file into memory
- Does not care about older browsers
Demo
The repo includes a demo viewer that can be used to open local files. To use it, serve the files with a server, and navigate to reader.html. Or visit the online demo hosted on GitHub. Note that it is very incomplete at the moment, and lacks many basic features such as keyboard shortcuts.
Also note that deobfuscating fonts with the IDPF algorithm requires a SHA-1 function. By default it uses Web Crypto, which is only available in secure contexts. Without HTTPS, you will need to modify reader.js and pass your own SHA-1 implementation.
Current Status
It works reasonably well, and has been used in several stable releases of Foliate. This library itself is, however, not stable. Expect it to break and the API to change at any time. Use it at your own risk.
If you do decide to use it, since there's no release yet, it is recommended that you include the library as a git submodule in your project so that you can easily update it.
Documentation
Overview
This project uses native ES modules. There's no build step, and you can import them directly.
There are mainly three kinds of modules:
- Modules that parse and load books, implementing the "book" interface
comic-book.js, for comic book archives (CBZ)epub.jsandepubcfi.js, for EPUBfb2.js, for FictionBook 2mobi.js, for both Mobipocket files and KF8 (commonly known as AZW3) files
- Modules that handle pagination, implementing the "renderer" interface
fixed-layout.js, for fixed layout bookspaginator.js, for reflowable books
- Auxiliary modules used to add additional functionalities
overlayer.js, for rendering annotationsprogress.js, for getting reading progresssearch.js, for searching
The modules are designed to be modular. In general, they don't directly depend on each other. Instead they depend on certain interfaces, detailed below. The exception is view.js. It is the higher level renderer that strings most of the things together, and you can think of it as the main entry point of the library. See "Basic Usage" below.
The repo also includes a still higher level reader, though strictly speaking, reader.html (along with reader.js and its associated files in ui/) is not considered part of the library itself. It's akin to Epub.js Reader. You are expected to modify it or replace it with your own code.
Basic Usage
To get started, clone the repo and import view.js:
import './foliate-js/view.js'
const view = document.createElement('foliate-view')
document.body.append(view)
view.addEventListener('relocate', e => {
console.log('location changed')
console.log(e.detail)
})
// can open a File/Blob object or a URL
// or any object that implements the "book" interface
await view.open('example.epub')
await view.goTo(/* path, section index, or CFI */)
See the online demo for a more advanced example.
Security
EPUB books can contain scripted content (i.e. JavaScript in the e-book), which is potentially dangerous, and is not supported by this library because
- It is currently impossible to do so securely due to the content being served from the same origin (using
blob:URLs). - Due to WebKit Bug 218086, the
allow-scriptsattribute is required on iframes, which renders iframe sandbox useless.
It is therefore imperative that you use Content Security Policy (CSP) to block all scripts except 'self'. An EPUB file for testing can be found at https://github.com/johnfactotum/epub-test.
[!CAUTION] Do NOT use this library (or any other e-book library, for that matter) without CSP unless you completely trust the content you're rendering or can block scripts by other means.
The Main Interface for Books
Processors for each book format return an object that implements the following interface:
.sections: an array of sections in the book. Each item has the following properties:.load(): returns a string containing the URL that will be rendered. May be async..unload(): returns nothing. If present, can be used to free the section..createDocument(): returns aDocumentobject of the section. Used for searching. May be async..size: a number, the byte size of the section. Used for showing reading progress..linear: a string. If it is"no", the section is not part of the linear reading sequence (see thelinearattribute in EPUB)..cfi: base CFI string of the section. The part that goes before the!in CFIs..id: an identifier for the section, used for getting TOC item (see below). Can be anything, as long as they can be used as keys in aMap.
.dir: a string representing the page progression direction of the book ("rtl"or"ltr")..toc: an array representing the table of contents of the book. Each item has.label: a string label for the item.href: a string representing the destination of the item. Does not have to be a valid URL..subitems: a array that contains TOC items
.pageList: same as the TOC, but for the page list..metadata: an object representing the metadata of the book. Currently, it follows more or less the metadata schema of Readium's webpub manifest. Note that titles and names can be a string or an object like{ ja: "草枕", en: 'Kusamakura' }, and authors, etc. can be a string, an object, or an array of strings or objects..rendition: an object that contains properties that correspond to the rendition properties in EPUB. If.layoutis"pre-paginated", the book is rendered with the fixed layout renderer..resolveHref(href): given an href string, returns an object representing the destination referenced by the href, which has the following properties:.index: the index of the referenced section in the.sectionarray.anchor(doc): given aDocumentobject, returns the document fragment referred to by the href (can either be anElementor aRange), ornull
.resolveCFI(cfi): same as above, but with a CFI string instead of href.isExternal(href): returns a boolean. Iftrue, the link should be opened externally.
The following methods are consumed by progress.js, for getting the correct TOC and page list item when navigating:
.splitTOCHref(href): given an href string (from the TOC), returns an array, the first element of which is theidof the section (see above), and the second element is the fragment identifier (can be any type; see below). May be async..getTOCFragment(doc, id): given aDocumentobject and a fragment identifier (the one provided by.splitTOCHref(); see above), returns aNoderepresenting the target linked by the TOC item
In addition, the .transformTarget, if present, can be used to transform the contents of the book as it loads. It is an EventTarget with a custom event "data", whose .detail is { data, type, name }, where .data is either a string or Blob, or a Promise thereof, .type the content type string, and .name the identifier of the resource. Event handlers should mutate .data to transform the data.
Almost all of the properties and methods are optional. At minimum it needs .sections and the .load() method for the sections, as otherwise there won't be anything to render.
Archived Files
Reading Zip-based formats requires adapting an external library. Both epub.js and comic-book.js expect a loader object that implements the following interface:
.entries: (only used bycomic-book.js) an array, each element of which has afilenameproperty, which is a string containing the filename (the full path)..loadText(filename): given the path, returns the contents of the file as string. May be async..loadBlob(filename): given the path, returns the file as aBlobobject. May be async..getSize(filename): returns the file size in bytes. Used to set the.sizeproperty for `.sections