Skip to content

Repository files navigation

Chek

Chek Reader

The reading engine behind Chek. Books, comics and documents for any Apple app.

Chek · Contributor guide · Issues

In development. EPUB publications open from files, memory or an HTTP URL in ReaderView and turn page by page with taps, swipes, the keyboard or a trackpad. PDFs open the same ways and show in PDFKit's own view, a page at a time. CBZ and CBT comics show a page at a time too, with ComicInfo.xml metadata and right-to-left manga. Plain text and Markdown are converted to XHTML and read like EPUB, split into chapters at their headings, and so are FictionBooks, plain or zipped, with their embedded images and notes. DRM-free MOBI and AZW3 books open the same ways, rebuilt into HTML documents. CBR and CB7 open through the optional archives add-on, which links libarchive.

Overview

Chek Reader is a standalone Swift package for reading on iPhone, iPad and Apple Silicon Mac. It opens EPUB, PDF, comics, plain text, Markdown, MOBI, AZW3 and FictionBook, from local files offline or streamed from a server.

It powers the Chek app, but has no Chek-specific code, no account and no server of its own, so any app can use it. Each format is its own product, and apps compile only the formats they include.

Requirements

  • Xcode 27 and Swift 6.4 to build.
  • Deploys to iOS 26, iPadOS 26 and macOS 26 or later. Features that need the 27 releases are behind availability checks.
  • The core, format and navigator targets also build on Linux, which is how CI checks the parsers.

Installation

Add the package with Swift Package Manager:

dependencies: [
    .package(url: "https://github.com/readchek/chek-reader.git", from: "0.1.0"),
],

Releases are tags on main named by version, such as 0.1.0; from: takes that release and later ones up to the next major. To follow unreleased changes instead, depend on branch: "main". Maintainers cut a release by running the Release workflow in the Actions tab with the version, which tags main and pushes the tag.

Then pick the products the app needs. Apps only compile and ship the formats they include.

Product What it adds
ChekReader Everything in the default set: all formats below except CBR and CB7, plus the navigator and UI
ChekReaderCore Publication model, positions, limits, errors and format detection. Required by everything else
ChekReaderEPUB EPUB 2 and 3, reflowable and fixed layout
ChekReaderPDF PDF through PDFKit
ChekReaderComics CBZ, CBT and webtoons
ChekReaderText Plain text and Markdown
ChekReaderMOBI DRM-free MOBI and AZW3
ChekReaderFB2 FictionBook, plain and zipped
ChekReaderNavigator ReaderController and the content views, for apps that draw their own controls
ChekReaderUI ReaderView with the standard Liquid Glass controls
ChekReaderArchives Optional add-on for CBR and CB7, linking libarchive. Not in the umbrella

Usage

Register the formats once at launch, then show a reader:

import ChekReader
import SwiftUI

@main
struct MyApp: App {
    init() {
        ReaderFormats.registerDefaults()
    }

    var body: some Scene {
        WindowGroup {
            ReaderView(source: .file(bookURL))
        }
    }
}

ReaderView draws the standard controls: a tap in the middle of the page shows a title bar with the table of contents, search and appearance settings, and a slider through the book's positions. Pass the location and settings saved last time, and keep what the reader reports back:

ReaderView(
    source: .file(bookURL),
    initialLocation: savedLocation,
    preferences: savedPreferences,
    onLocationChange: { save($0) },
    onPreferencesChange: { savePreferences($0) }
)

Apps that draw their own interface around NavigatorView can reuse the pieces on their own: ReaderControls, TableOfContentsView, SearchView and AppearanceView each take a ReaderController.

An app that links only some format products registers those instead:

import ChekReaderEPUB
import ChekReaderPDF

ReaderFormats.register(EPUBModule.self, PDFModule.self)

Opening a format the app didn't include fails with ReaderError.formatNotIncluded, whose recovery suggestion names the product to add.

Publications can come from several sources:

.file(url)                                  // a file on disk
.data(data, fileName: "book.epub")          // bytes in memory
.remote(url, RemoteOptions { request in     // an HTTP server; EPUB and CBZ load by range
    var request = request
    request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
    return request
})
.remote(url, RemoteOptions(load: myLoader)) // or send requests through your own session
.provider(myProvider)                       // resources the app supplies, such as decrypted ones
.pages(myPageSource)                        // page images streamed one at a time

Formats that keep everything in one file, such as PDF and MOBI, download the whole file from a URL, and ask a provider for it with the empty href PublicationSource.wholePublicationHref.

To read a publication's structure without showing it, for example to import a library, open it directly:

let publication = try await Publication.open(.file(bookURL))
publication.metadata.title             // the title, when the book has one
publication.readingOrder               // chapters, in order
publication.tableOfContents            // nested entries with titles and hrefs
let html = try await publication.data(at: publication.readingOrder[0].href)

Damaged but readable files still open, and what was repaired or skipped is listed in publication.warnings. Every archive and XML document is checked against ReaderLimits while it's read, and DRM-protected books fail with ReaderError.drmProtected.

Reading positions are Locator values. Their JSON matches the Readium Locator model, so they can be stored and synced as-is.

To control the reader yourself, keep a ReaderController and show it with NavigatorView. Locations carry the text at the start of the page, so a saved location lands on the same passage after the text size or screen changes, and a position number and total progression once the book's positions are worked out:

let controller = ReaderController(preferences: ReaderPreferences(fontScale: 1.2, theme: .sepia))
try await controller.open(.file(bookURL), at: savedLocation)

controller.location?.locations.position          // like a page number that doesn't change with the text size
controller.location?.locations.totalProgression  // 0 to 1 through the whole book
await controller.goForward()
await controller.setPreferences(ReaderPreferences(fontScale: 1.5, theme: .dark))

Searching reads every chapter, so run it off the critical path. Each result is a Locator with the matched text and the words around it, in reading order, that go(to:) opens at the page holding the match. PDF searches through PDFKit, and comics find nothing:

let results = try await controller.search(for: "lighthouse", options: SearchOptions(caseSensitive: false, limit: 100))
results.first?.text        // before, highlight and after
results.first?.title       // the chapter's title, when the table of contents names it
await controller.go(to: results[0])

Repository layout

Package.swift
Sources/
  ChekReaderCore/        Publication model, Locator, formats, limits, errors
  ChekReaderEPUB/ …      One target per format product
  ChekReaderNavigator/   ReaderController and content views
  ChekReaderUI/          ReaderView and standard controls
  ChekReader/            Umbrella product
Tests/                   Swift Testing suites per target
Examples/ChekReaderDemo/ Read-only document viewer for iPhone, iPad and Mac
Scripts/Reader/          TypeScript page layout script, compiled into ChekReaderNavigator
Benchmarks/              Comparison with Readium, PDFKit and other readers (see Benchmarks/README.md)
.github/workflows/       CI

Development

You can work without a phone: the demo app runs natively on an Apple Silicon Mac and in the iOS Simulator.

swift test                                                  # core, formats and navigator, on macOS or Linux
xcodebuild test -scheme ChekReader-Package -destination 'platform=macOS'
open Examples/ChekReaderDemo/ChekReaderDemo.xcodeproj       # demo app

The page layout script is TypeScript in Scripts/Reader, compiled into Sources/ChekReaderNavigator/Resources/reader.js, which is committed so the package builds without Node. After changing the script, rebuild it and run its browser tests:

cd Scripts/Reader
npm ci
npx playwright install webkit
npm test                                                    # compiles, then tests in WebKit

See AGENTS.md for the conventions contributors and coding agents follow.

Dependencies

The core, format and UI products depend only on Apple frameworks and the Swift standard library. The optional ChekReaderArchives product links libarchive (BSD-2-Clause): on Apple platforms as a prebuilt static XCFramework from Lakr233/libarchive.xcframework (MIT packaging, with xz, zstd and lz4 under 0BSD and BSD licenses), pinned to one release, and on Linux as the system library from libarchive-dev. Register it alongside the defaults to read CBR and CB7:

import ChekReaderArchives

ReaderFormats.registerDefaults()
ReaderFormats.register(ArchivesModule.self)

License

Apache License 2.0. See LICENSE. Chek Reader isn't accepting outside contributions yet; see CONTRIBUTING.md.

Releases

Packages

Contributors

Languages