Readium Swift Toolkit

repository·develop·Indexed 19 days ago

https://github.com/readium/swift-toolkit

A professional-grade toolkit for building ebook, audiobook, and comic reading applications in Swift. It supports various formats including EPUB and PDF, OPDS catalogs, and DRM via Readium LCP. The toolkit provides modular libraries such as ReadiumShared, ReadiumStreamer, ReadiumNavigator, ReadiumOPDS, and ReadiumLCP, with specialized support for RTL and CJK stylesheets, accessibility metadata display, and EBPAJ polyfills.

Tokens
41.5K
Snippets
119
Records
157
Agent score
68%

What's inside Readium Swift Toolkit

  1. Overview of Readium Swift Toolkit packages

    develop

    The Readium Swift toolkit is a modular set of low-level tools for developing reading applications on iOS and iPadOS. It supports EPUB, PDF, audiobooks, and comics. Note that the toolkit does not provide a user interface; you are responsible for building the UI for reading, managing books, and data storage.

    Main Packages

    • ReadiumShared: Contains shared Publication models and utilities.
    • ReadiumStreamer: Parses publication files (e.g., EPUB) into Publication objects.
    • ReadiumNavigator: Renders the content of a publication for display.

    Specialized Packages

    • ReadiumOPDS: Parses OPDS 1 and 2 catalog feeds.
    • ReadiumLCP: Handles downloading and decrypting LCP-protected publications.
  2. Extend parsing and retrieval capabilities

    develop

    You can customize how the toolkit handles different formats, URL schemes, and archives.

    Customizing Parsers

    DefaultPublicationParser can be extended with additional parsers. Alternatively, use:

    • CompositePublicationParser: To provide a list of parsers.
    • A custom PublicationParser implementation: For a fully custom resolution strategy.

    Customizing Asset Retrieval

    For advanced extensibility, use the AssetRetriever constructor that accepts:

    • ResourceFactory: Handles URL schemes for accessing content.
    • ArchiveOpener: Determines which archive types (ZIP, RAR, etc.) can be opened.
    • FormatSniffer: Identifies file formats.

    You can implement your own versions of these or use the provided composite implementations: CompositeResourceFactory, CompositeArchiveOpener, and CompositeFormatSniffer.

  3. How the Decoration API works

    develop

    The Decoration API allows you to overlay visual elements (highlights, search markers, TTS indicators, etc.) on publication content. It is built around three core concepts:

    1. Decoration: A single UI element that pairs a Locator (location in the publication) with a Decoration.Style and a unique id.
    2. Decoration.Style: An abstract description of appearance (e.g., .highlight or .underline). For EPUB, these map to HTMLDecorationTemplates that inject HTML/CSS.
    3. Decoration Group: A named collection of decorations representing a logical feature (e.g., "highlights", "search").

    Important: Only EPUBNavigatorViewController currently implements DecorableNavigator. Always check if your navigator conforms to DecorableNavigator before using these features.

    Groups are independent. When you call apply(decorations:in:), the navigator performs a diff against the previous state of that specific group and only updates what is necessary, making it safe to call on every state change.

    // Example of a single decoration
    let decoration = Decoration(
        id: highlight.id,
        locator: highlight.locator,
        style: .highlight(tint: highlight.color)
    )
  4. Split and merge Navigator preferences

    develop

    To manage different scopes of settings (e.g., global themes vs. book-specific settings), the toolkit provides filtering methods. This prevents settings that should be unique to a publication (like language) from being accidentally applied globally.

    Use filterPublicationPreferences() to extract settings intrinsic to a specific book and filterSharedPreferences() for settings that can be shared across publications. You can reconstruct the full preference set using merging().

    let publicationPrefs = preferences.filterPublicationPreferences()
    let sharedPrefs = preferences.filterSharedPreferences()
    
    // Reconstruct the original preferences by combining the filtered ones.
    let combinedPrefs = publicationPrefs.merging(sharedPrefs)
  5. How to intercept user input in VisualNavigator

    develop

    The VisualNavigator implements the InputObservable protocol, allowing you to intercept low-level input events (gestures, keyboard, mouse, pencil, or trackpad) that the publication does not already override.

    To intercept events, implement the InputObserving protocol and register it using navigator.addObserver(_:).

    Event Handling Logic:

    • If your observer handles an event and you want to prevent other observers from receiving it, return true from the didReceive method.
    • If you want the event to propagate to other observers, return false.
    // Example of a custom InputObserving implementation
    @MainActor final class InputObserver: InputObserving {
        func didReceive(_ event: PointerEvent) async -> Bool {
            print("Received pointer event: \(event)")
            return false // Return false to allow other observers to see this event
        }
    
        func didReceive(_ event: KeyEvent) async -> Bool {
            print("Received key event: \(event)")
            return false
        }
    }
    
    navigator.addObserver(InputObserver())
  6. Implement Highlights using the Decoration API

    develop

    Highlights in Readium are implemented by leveraging the Decoration API. Readium is responsible only for rendering the highlights over the publication content. Your application is responsible for the full lifecycle of the highlight data, including:

    1. Defining a Highlight model.
    2. Persisting highlights to a database.
    3. Providing the UI for managing highlights (e.g., color pickers, lists, or deletion menus).

    Note: Currently, only EPUBNavigatorViewController implements the DecorableNavigator protocol. Always verify that a navigator conforms to DecorableNavigator before attempting to apply decorations.

  7. Use positions instead of pages

    develop

    Readium uses positions rather than pages to ensure stability across different screen sizes and font settings.

    • To get the total number of positions: try await publication.positions().get().count.
    • To get the current position: navigator.currentLocation?.locations.position.

    Note: Not all Navigators provide positions, but most VisualNavigator implementations do.

  8. How AssetRetriever and PublicationOpener work together to open a publication

    develop

    To open a publication in the Readium Swift Toolkit, you follow a two-step process involving two primary components:

    1. AssetRetriever: Responsible for accessing the raw content of an asset (like an .epub file or a manifest) from a URL (local file or HTTP/HTTPS).
    2. PublicationOpener: Responsible for taking an Asset produced by the retriever and parsing it into a usable Publication object.

    This separation allows the toolkit to handle different transport mechanisms (via the retriever) independently from the parsing logic (via the opener).

    // 1. Retrieve the asset
    let assetRetriever = AssetRetriever(httpClient: DefaultHTTPClient())
    let asset = try await assetRetriever.retrieve(url: url).get()
    
    // 2. Open the publication
    let publicationOpener = PublicationOpener(parser: DefaultPublicationParser(...))
    let publication = try await publicationOpener.open(asset: asset, allowUserInteraction: true, sender: sender).get()
  9. Process ContentElement types and attributes

    develop

    The iterator yields ContentElement objects. Each element has a locator property that can be used with a Navigator to navigate to that specific part of the publication.

    Embedded Media

    Elements referencing external resources implement the EmbeddedContentElement protocol. Use the embeddedLink property to fetch the actual resource bytes via the publication.

    Supported default implementations:

    • AudioContentElement: Audio clips.
    • VideoContentElement: Video clips.
    • ImageContentElement: Bitmap images. Includes an optional caption: String? property.

    Textual Elements

    • TextualContentElement: A protocol for any element that can be represented as human-readable text. Use this to extract text without worrying about specific element types.
    • TextContentElement: Represents a single block of text (e.g., a heading or paragraph). It consists of a role (e.g., .body, .heading, .footnote) and a list of segments. Each segment contains text and optional attributes (like language tags).
    // Navigating to an element's location
    navigator.go(to: element.locator)
    
    // Accessing embedded media
    if let element = element as? EmbeddedContentElement {
        let bytes = try publication
            .get(element.embeddedLink)
            .read().get()
    }
    
    // Extracting all text from all textual elements
    let wholeText = publication.content()
        .elements()
        .compactMap { ($0 as? TextualContentElement)?.text.takeIf { !$0.isEmpty } }
        .joined(separator: "\n")
  10. Use AccessibilityMetadataDisplayGuide for simplified UI presentation

    develop

    To avoid the complexity of raw RWPM accessibility models, use AccessibilityMetadataDisplayGuide. This class implements the W3C Accessibility Metadata Display Guide specification, organizing metadata into user-friendly sections (fields) and statements.

    Each field (like waysOfReading) contains properties that describe capabilities, such as visualAdjustments (whether text/layout can be modified) or nonvisualReading (support for TTS or Braille).

    let guide = AccessibilityMetadataDisplayGuide(publication: publication)
    
    switch guide.waysOfReading.visualAdjustments {
    case .modifiable:
        // The text and layout of the publication can be customized.
    case .unmodifiable:
        // The text and layout cannot be modified.
    case .unknown:
        // No metadata provided
    }
  11. Difference between ImageContentElement and SVGContentElement

    develop

    When handling images in an EPUB, note the distinction between bitmap images and inline SVGs:

    • ImageContentElement: Used for bitmap images and for SVG images referenced via an <img> tag (e.g., <img src="...svg">). It provides an embeddedLink to the resource.
    • SVGContentElement: Used for inline <svg> elements. Instead of an embeddedLink, it exposes an svg: String property containing the raw SVG source code.
  12. Use the new Publication opening APIs (v3.0.0)

    develop

    The Streamer object is deprecated in v3.0.0. Use the following components instead:

    • AssetRetriever: Access content of an asset (publication package, manifest, or LCP license) at a given URL.
    • PublicationOpener: Creates a Publication object from an Asset using a publication parser and content protections.