A native Android comic, manga, webtoon and e‑book reader built E‑Ink‑first for Onyx Boox devices — but source‑agnostic, device‑agnostic and modular from day one.
It speaks Komga and OPDS natively, reads local device folders and paged comics, vertical webtoons, panel‑by‑panel "guided" comics and reflowable e‑books, and is built so that new sources, new devices and even whole new UIs can be added behind stable seams — without touching the core. Maximum flexibility is the explicit north‑star goal.
Status: early but substantial (
versionName 0.3.x, latest releasev0.3.3). The architecture is in place and proven; the app runs on real hardware. See Project Status for an honest, evidence‑backed assessment of what is done, what is missing, and what is still blocked.
License: AGPL‑3.0‑or‑later (see Licensing).
Most readers hard‑wire one server, one device class and one look. This one is built around two architectural seams that isolate everything that varies, so the whole project can grow without a core rewrite:
- Seam A — Sources (
MediaSource): Komga, OPDS, or a user‑installed plugin all look the same to the app. The library never breaks when a source is offline; it degrades to a placeholder. - Seam B — Render & E‑Ink (
Document/EinkController): MuPDF renders cbz/cbr/pdf/epub to bitmaps, crengine reflows novels, and the Onyx E‑Ink refresh controller is fully gated so development never crashes on non‑Boox hardware.
On top of that sits a three‑layer modular UI (theme packs → shell packs → region slots) so the presentation can be re‑skinned or re‑arranged — eventually by community‑installed packs — while the host keeps enforcing the E‑Ink correctness rules no pack can override.
| Area | What works today |
|---|---|
| Sources | Komga (REST) and OPDS, multiple servers at once, mixed. Per‑work source resolution. Kavita via plugin. Local device folders (pick a folder via SAF; CBZ/PDF/EPUB read like any other source). |
| Open external files | Registered as a system "open with" handler for .epub/.cbz/.cbr/.pdf — a tap in the file manager opens the file ephemerally in the reader (via a transient download row under SourceId.EXTERNAL, no reader rewrite), with an offer to import it into the local folder. Behaviour (ask / import / read‑only) is rememberable in Settings → Downloads. (EPUB ephemeral open and the actual handler listing are device‑verification‑pending on real arm64 Boox.) |
| Reading modes | Paged comics, vertical Webtoon scroll, Guided Comic (panel‑by‑panel zoom, automatic panel detection via the comic‑cutter library — geometric by default, optional ONNX ML detector via a PANEL_MODEL plugin), Novel EPUB reflow (crengine‑ng with hyphenation + bundled reading fonts, tap‑a‑word bookmarks with on‑page markers). |
| Devices | Mono E‑Ink, colour E‑Ink (Kaleido), LCD phone/tablet — motion and accent‑colour gated per device class on two orthogonal axes. |
| E‑Ink | Onyx refresh control (fast mode + device‑managed full refresh), no‑op fallback off‑device, no animations in E‑Ink mode. |
| Colour filter | Per‑profile saturation / contrast / brightness applied to both covers and reader pages (Kaleido‑tuned built‑in profile). |
| Offline‑first | Download manager, local read‑progress (dirty flag), background sync queue, bidirectional collection sync (last‑write‑wins). |
| Plugins | Runtime‑installed source plugins (APK, TOFU‑pinned), plus data‑only packs: colour presets, reader presets, languages, UI packs, and reading fonts (SPDX‑allowlisted, registered into crengine at runtime). |
| Modular UI | Device‑class theme packs, form‑factor shell packs (bottom‑bar / drawer / floating nav), and 8 swappable chrome region slots. |
| Reading statistics | Local reading‑time tracking per reader type (ReaderKind: paged, webtoon, comic, novel), started / finished work counts (derived from existing progress tables, no extra tracking), idle‑capped per‑page deltas (no battery‑draining timer). Visible in Settings → Statistics. |
| i18n | Type‑safe English + German, compile‑time parity, runtime language packs. |
See the Feature status matrix for the "built vs. planned vs. blocked" detail behind this table.
┌─ UI (Jetpack Compose, :app) — source- & device-agnostic ───────────────┐
│ ViewModels · Reader screens · modular chrome (theme/shell/slot packs) │
└───────────────── ↓ ───────────────────────────────── ↓ ────────────────┘
┌─ :domain (pure Kotlin — no Android, no network, no source) ─────────────┐
│ Models · UseCases · Repository / Render / Eink INTERFACES · ViewerType │
└──────── ↓ SEAM A: Sources ──────────────────── ↓ SEAM B: Render/E-Ink ──┘
┌─ MediaSource ───────────────┐ ┌─ Document / EinkController ─────────┐
│ KomgaSource (REST) │ │ MuPDF (JNI) → Bitmap [render-core]│
│ OpdsSource │ │ crengine-ng reflow [render-crengine]│
│ Plugin sources (APK, TOFU) │ │ OnyxEinkController / NoOp [eink-onyx]│
│ SourceManager + StubSource │ │ EinkContextController (EinkWise) │
└─────────────────────────────┘ └──────────────────────────────────────┘
Everything above the seams is generic. A new source or device is a new implementation behind the interface — never a core change. Full detail in docs/ARCHITECTURE.md.
| Module | Responsibility | May not depend on |
|---|---|---|
domain |
Models, use‑cases, repository/render/eink interfaces, ViewerType |
Android, network, any source, source-api |
source-api |
Seam‑A contract (MediaSource, SourceManager, SourceId) |
Android, network, UI |
source-komga · source-opds |
Concrete MediaSource implementations |
UI, other sources |
source-local |
LocalSource — a picked device folder (SAF) as a source; renderer‑free (CBZ pages via openPage zip‑extract, PDF/CBR/EPUB whole‑file) |
UI, other sources, render-core |
render-core |
Document + MuPDF JNI (Seam B render) |
UI |
render-crengine |
crengine‑ng EPUB reflow engine (JNI, arm64‑v8a) | UI |
eink-onyx |
OnyxEinkController (Onyx SDK, hardware‑gated) |
UI, sources |
ui-api |
UI pack / slot / shell / theme / icon contracts + decoupled built‑ins | ViewModels, network, sources |
data |
Room persistence, sync queue, download manager | UI |
plugin-api · plugin-host · plugin-sdk |
Plugin ABI contract, runtime loader (TOFU, PathClassLoader), shaded SDK jar |
UI |
app |
Compose UI, ViewModels, DI wiring, reader host, default packs | — (top layer) |
The Gradle module graph enforces these boundaries: domain is pure Kotlin with only a
coroutines dependency; every other module depends on domain (and source-api / plugin-api
where relevant), never on app, never on each other across seams.
Comic panel detection is no longer an in‑tree module — it lives in the published
comic‑cutter library (io.github.gabriel-graf:comic-cutter-jvm + comic-cutter-onnx-jvm),
wired in app via PanelSourceProvider: geometric detection by default, or an ONNX ML
detector when a PANEL_MODEL plugin is installed and ML detection is enabled.
- JDK 17
- Android SDK with API level 34 (compile/target;
minSdk 28) - Android Studio Ladybug+ (optional but recommended)
- The crengine native libraries are committed prebuilt for
arm64-v8a, so you do not need the NDK for a normal build. (Rebuilding crengine needs NDK28.2.13676358+ CMake — see render-crengine/native/PROVENANCE.md.)
arm64‑v8a only. The native reflow engine ships only for
arm64-v8a(every Boox and virtually all modern Android devices). The app will not run on x86 emulators; use an arm64 device or an arm64 system image.
git clone <this-repo> komga-reader
cd komga-reader
./gradlew :app:assembleDebug # build the debug APK
./gradlew :app:installDebug # install onto a connected arm64 deviceMuPDF is pulled as a prebuilt Maven artifact (com.artifex.mupdf:fitz) from the Ghostscript
Maven repo; the Onyx Boox SDK comes from repo.boox.com. Both repositories are declared in
settings.gradle.kts.
The app is distributed by sideload — GitHub Releases (signed APK) and F‑Droid‑style channels, matching its AGPL, source‑available nature.
It is intentionally not Google Play compatible in its current form. The runtime plugin host
discovers installed plugin/pack APKs via the QUERY_ALL_PACKAGES permission (a deliberate
2026‑06‑11 decision that keeps the plugin manifest contract minimal — a plugin declares only
metadata, no discovery component). Google Play restricts QUERY_ALL_PACKAGES to a narrow set of
use cases. Shipping on Play would require replacing plugin discovery with <queries> intent
filters, which changes the plugin manifest contract (every plugin would have to declare a
discovery intent) — a known, priced trade‑off, not an oversight. Until someone accepts that
trade‑off, distribution stays off‑Play.
./gradlew test # all JVM unit tests (domain, mappers, pure logic)
./gradlew :app:connectedAndroidTest # instrumented / E2E tests (needs an arm64 device + a Komga server)The instrumented tests expect a reachable Komga instance. A local Docker test server and the emulator geometry used for screenshots are described in CONTRIBUTING.md.
Flexibility is the point, so extension is first‑class. See
docs/ARCHITECTURE.md and the in‑repo design specs under
docs/superpowers/specs/.
- Add a source (Komga‑like server): implement
MediaSource/BrowsableSourcein a newsource-*module, or ship it as a plugin APK linking onlycom.komgareader:plugin-sdk. - Ship a plugin: source plugins, colour presets, reader presets, language packs,
UI packs and font packs install at runtime. The officially supported plugins live in the
Gabriel-Graf/KomgaReaderPluginsmonorepo — one source tree, one CI that builds, signs and releases them all, plus therepo.jsonindex the app reads. Third-party plugins can live in any repo that serves arepo.json. (Theplugin/dir in this repo is local scratch and is gitignored.) Eachrepo.jsonentry may carry optionalpreviewUrl,readmeUrlandlicensefields — the in‑app repo browser shows an ℹ info modal per plugin with the preview image and the renderedREADME.md. - Re‑skin the UI: a data‑only UI pack (
ui_pack.json) can replace the theme (full colour roles + typography + shapes), the navigation shell style, and remap icons — declaratively, with no plugin code and no host privileges. The E‑Ink invariants stay host‑enforced regardless of what a pack requests.
By design, external packs are declarative/data‑only — a pack ships a JSON description and the host renders it, so a pack can never crash the host or bypass the E‑Ink invariants. Today that covers theme / shell nav‑style / icon remap; the roadmap widens the declarative vocabulary to per‑slot chrome arrangements. Loading arbitrary external UI code is intentionally excluded (same decision as UI‑view plugins). See Project Status.
domain/ pure-Kotlin core (models, use-cases, interfaces)
source-api/ Seam-A contract
source-komga/ Komga REST source
source-opds/ OPDS source
source-local/ Local device-folder source (SAF)
render-core/ MuPDF render engine (JNI)
render-crengine/ crengine-ng EPUB reflow engine (JNI, arm64-v8a, committed prefix)
eink-onyx/ Onyx E-Ink refresh controller (hardware-gated)
ui-api/ UI pack / slot / shell / theme / icon contracts
data/ Room persistence, sync queue, downloads
plugin-api/ plugin ABI contract
plugin-host/ runtime plugin loader (TOFU, PathClassLoader)
plugin-sdk/ shaded single-jar for external plugin authors
app/ Compose UI, ViewModels, DI, reader host, default packs
docs/ design specs, plans, domain & UI-pack docs
tools/ icon generator, CI fixtures, e2e helpers
- Phase 1–2 (done): Komga + library + paged/webtoon/novel/guided readers, OPDS, downloads, shelves, panel detection, offline sync.
- Phase 3 (open): Kaleido colour‑filter refinement, per‑region refresh tuning, extended E‑Ink settings, more servers.
- Phase 4 (largely done early): runtime plugin loader, plugin SDK, data‑only UI‑pack loader — the remaining piece is widening the declarative UI‑pack vocabulary to per‑slot chrome arrangements (still declarative; external code packs are intentionally out of scope).
The detailed, code‑anchored gap analysis lives in docs/PROJECT-STATUS.md.
New contributors: start with CONTRIBUTING.md. It covers the dev setup, the build, the test workflow, and — most importantly — the five non‑negotiable architecture invariants that keep the project flexible. The seams only stay valuable if every change respects them.
AGPL‑3.0‑or‑later. This app links MuPDF (AGPL) and crengine‑ng (GPL‑2.0‑or‑later) via JNI, and the comic‑cutter panel‑detection library (AGPL‑3.0), so the combined work is AGPL. Every distribution must make the complete source available.
Full license text in LICENSE; third‑party attributions in NOTICE; native dependency provenance (versions, SPDX identifiers, build recipe) in render-crengine/native/PROVENANCE.md.
Bundled assets: MuPDF (AGPL), crengine‑ng (GPL‑2.0‑or‑later), comic‑cutter (AGPL‑3.0), ONNX Runtime (MIT), Lucide icons (ISC), DejaVu Sans (Bitstream Vera), Literata & Bitter (OFL‑1.1), TeX hyphenation patterns (MIT / permissive).
Key UI dependency: multiplatform-markdown-renderer (Apache‑2.0, by Mike Penz) renders plugin READMEs in the in‑app plugin info modal.
