foliate-js

repository·main·Indexed 20 days ago

https://github.com/johnfactotum/foliate-js

A lightweight, pure JavaScript library for rendering e-books in web browsers. It serves as the engine for the Foliate e-book reader and supports formats including EPUB, MOBI, KF8 (AZW3), FB2, CBZ, and experimental PDF support. The library features a modular design with a custom paginator, support for StarDict offline dictionaries, EPUB CFI parsing, and a flexible book interface for extending support to custom formats.

Tokens
24.8K
Snippets
83
Records
101
Agent score
76%

What's inside foliate-js

  1. Overview of foliate-js features

    main

    foliate-js is a pure JavaScript library designed for rendering e-books directly in the browser. It is modular, small, and does not require loading entire files into memory. It is designed to be compatible with modern browsers and has no hard dependencies.

    Supported Formats:

    • EPUB
    • MOBI
    • KF8 (AZW3)
    • FB2
    • CBZ
    • PDF (experimental; requires PDF.js)

    You can extend support to other formats by implementing the library's book interface.

  2. Quickstart: Basic Usage with view.js

    main

    To integrate foliate-js into your project, import view.js and append the <foliate-view> custom element to your DOM. The view instance acts as the main entry point, allowing you to open books (via File/Blob or URL) and navigate through them. You can listen for the relocate event to track reading progress or location changes.

    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 */)
  3. Run the foliate-js demo

    main

    To test the library, you can use the included demo viewer.

    1. Serve the repository files using a local web server.
    2. Navigate to reader.html in your browser.

    Alternatively, you can visit the online demo.

    Note on Security and Fonts: Deobfuscating fonts using the IDPF algorithm requires a SHA-1 function. By default, the library uses the Web Crypto API, which requires a secure context (HTTPS). If you are running the demo over HTTP, you must modify reader.js to provide your own SHA-1 implementation.

    # Example: serving the files (using a tool like http-server)
    http-server .
    # Then navigate to http://localhost:8080/reader.html
  4. Integrate foliate-js into your project

    main

    As of the current version, there are no formal releases. It is recommended to include foliate-js as a git submodule in your project. This allows you to manage updates and pin the library to a specific commit to mitigate the risk of breaking API changes.

    git submodule add https://github.com/johnfactotum/foliate-js.git
  5. How MOBI/KF8 sections and fragments work

    main

    MOBI/KF8 files are not stored as continuous streams of text. Instead, they use a Skeleton and Fragment model:

    1. Skeleton (skel): A base sequence of data that defines the structure of a section.
    2. Fragments (frag): Small pieces of data that are inserted into the skeleton at specific offsets to reconstruct the full text.

    The parser manages this complexity by mapping skelTable and fragTable to reconstruct sections. When you call loadText() or loadSection(), the engine performs the necessary concatTypedArray operations to stitch the fragments into the skeleton, ensuring the final output is a coherent XHTML/HTML string.

  6. Understand the section loading mechanism

    main

    When section.load() is called, it triggers renderPage(). This function returns an object containing:

    1. src: A Blob URL pointing to a self-contained HTML document. This document includes embedded CSS for the textLayer and annotationLayer, and contains three main containers: #canvas, .textLayer, and .annotationLayer.
    2. onZoom: A function ({ doc, scale }) => void that allows the consumer to trigger a re-render of the page at a specific zoom level within the provided document context.
  7. How TTS segmentation and SSML conversion works

    main

    The TTS engine operates through a multi-step process to transform HTML into SSML:

    1. Block Segmentation: The document is divided into logical blocks using getBlocks(). It identifies block-level elements (like p, div, section, h1-h6, etc.) to define reading boundaries.
    2. Fragmenting and Marking: For each block, getFragmentWithMarks clones the content and inserts <foliate-mark> elements at the boundaries of segments (determined by the Intl.Segmenter and the chosen granularity).
    3. SSML Conversion: The fragmentToSSML function converts the HTML fragment into an SSML <speak> document. It maps specific HTML elements to SSML tags:
      • <foliate-mark> $\rightarrow$ <mark>
      • <br> $\rightarrow$ <break>
      • <em> or <strong> $\rightarrow$ <emphasis>
      • Elements with lang attributes $\rightarrow$ <lang>
      • Elements with ph (phoneme) attributes $\rightarrow$ <phoneme>
    4. State Management: The ListIterator maintains the current position in the sequence of blocks, allowing for next(), prev(), and find() operations.
  8. Handle book relocation and progress events

    main

    The Reader listens for relocate events emitted by the <foliate-view> element. This event provides data about the user's current position in the book, which can be used to update UI components like progress sliders or page indicators.

    An event detail object from relocate contains:

    • fraction: A float representing the current position.
    • location: An object containing the current location (e.g., location.current).
    • tocItem: The current Table of Contents item (may include an href).
    • pageItem: An object containing page labels (e.g., pageItem.label).
  9. How Paginator handles book content and styles

    main

    When you call open(book), the Paginator performs several automated tasks to ensure the book content renders correctly within its sandboxed iframe:

    1. Directionality: It sets the dir attribute on the container based on the book's direction and the detected writing mode (vertical vs horizontal).
    2. CSS Transformation: It intercepts and modifies CSS from the book content to ensure compatibility with the paginator's layout engine. Specifically, it:
      • Removes epub- prefixes from properties.
      • Converts vw/vh units to absolute px values to prevent layout breakage.
      • Replaces page-break-* properties with -webkit-column-break-* or break-* to support columnar layouts.
    3. Background Sync: It detects the background color/image of the book's document and applies it to the paginator's background layer to ensure a seamless visual transition between the UI and the content.
  10. Implement annotation and highlight handling

    main

    The Reader class manages annotations (like highlights) by mapping them to specific indices in the book's spine. It integrates with the <foliate-view> via several event listeners:

    • create-overlay: Triggered to request the drawing of annotations at a specific index.
    • draw-annotation: Provides a draw function and the annotation object. You can use draw(Overlayer.highlight, { color }) to render the highlight.
    • show-annotation: Triggered when an annotation is requested for display (e.g., showing a note via an alert).
  11. Clean up FB2 book resources

    main

    Because makeFB2 creates several URL.createObjectURL instances for the book's sections, you must call book.destroy() to release these resources and prevent memory leaks.

    const book = await makeFB2(blob);
    try {
      // ... use the book
    } finally {
      book.destroy();
    }
  12. Security Requirements: Use CSP

    main

    EPUB files can contain scripted content (JavaScript) which is dangerous. Because content is served from the same origin via blob: URLs, it is currently impossible to sandbox securely using iframes alone.

    Requirement: You MUST use a Content Security Policy (CSP) to block all scripts except 'self'.

    CAUTION

    Do NOT use this library without CSP unless you completely trust the content or can block scripts by other means.