Local ebook reader: EPUB/TXT/Markdown/HTML/FB2/PDF import, paged reading, annotations and progress
app
  • Emacs Lisp 81.7%
  • Python 17.8%
  • Makefile 0.5%
Find a file
Kinneyzhang 98c86ac324
Some checks failed
Repository structure / structure (push) Has been cancelled
fix(theme): frame the reader from the palette it declares
A panel that framed the reader only repainted its own chrome: the reader
declared no surface, so its title-bar switch never reached the paper.  The
reader now declares its palette, session mode cell and 亮/暗/纸 labels to the
host, and its own switch keeps writing the same cell.

Validation: make check (etaf-reader, 20 tests).  A mounted host switched the
session to the paper palette through the declared surface and the page
repainted.
2026-10-27 20:48:48 +08:00
.githooks feat(reader): add the local ebook reader application 2026-10-03 06:53:17 +08:00
.github/workflows chore(scripts): sync shared workspace tooling 2026-10-19 12:49:30 +08:00
benchmarks test(reader): verify styled page workloads within the viewport 2026-10-03 21:59:56 +08:00
docs fix(theme): frame the reader from the palette it declares 2026-10-27 20:48:48 +08:00
examples feat(reader): add a navigable library and persistent reading controls 2026-10-03 21:43:08 +08:00
lisp fix(theme): frame the reader from the palette it declares 2026-10-27 20:48:48 +08:00
scripts chore(scripts): sync shared workspace tooling 2026-10-22 13:16:07 +08:00
tests fix(theme): frame the reader from the palette it declares 2026-10-27 20:48:48 +08:00
.editorconfig feat(reader): add the local ebook reader application 2026-10-03 06:53:17 +08:00
.gitignore feat(reader): add the local ebook reader application 2026-10-03 06:53:17 +08:00
CHANGELOG.md fix(store): skip the chapter batch when a book has none 2026-10-22 15:50:20 +08:00
etaf-reader.el fix(theme): frame the reader from the palette it declares 2026-10-27 20:48:48 +08:00
LICENSE chore: add the GPL-3.0 license 2026-10-13 00:49:01 +08:00
Makefile test(reader): verify styled page workloads within the viewport 2026-10-03 21:59:56 +08:00
README.md feat(reader)!: replace the static help pane with shared key help 2026-10-16 01:15:56 +08:00

ETAF Reader

ETAF Reader is a local ebook reader for Emacs. It scans a library directory for EPUB, TXT, Markdown, HTML, FB2 and PDF files, imports them into one SQLite library through ETAF DB, and reads them with an adaptive paged or scrolling layout built from ETAF and Ebox: select text with the mouse or the keyboard, then mark it with a highlighter color, a straight underline or a wavy underline, attach thoughts, jump through the table of contents and notes, and resume exactly where the last session stopped.

The reader

Selected text opens the annotation toolbar above or below the line:

The annotation toolbar

Capabilities

  • Library: scans etaf-reader-library-directories (default ~/ebooks), stores metadata and chapter text in SQLite, and shows a shelf with progress, reading state and per-book format, pages and chapter counts. Search titles and authors, filter reading states, sort the shelf and page through every result; imported covers are shown when available.
  • Formats: EPUB 2/3, TXT, Markdown, HTML, FB2 and PDF, parsed by the ebook-parse skill; PDF needs poppler's pdftotext/pdfinfo.
  • Reading: paged mode (one column on narrow frames, two columns on wide ones) or scrolling mode, adjustable column width, six annotation colors, and three palettes (dark, light, sepia). A reading toolbar and persisted settings expose these choices along with comfortable or compact paragraph spacing.
  • Annotations: mouse drag or the v keyboard selection mode opens a floating toolbar above or below the line; highlights, straight and wavy underlines and thoughts anchor on (chapter, code point) pairs, so they survive re-wrapping, font changes and window resizes.
  • Navigation: paged contents, notes, search-result and bookmark lists, in-book search with n/N, percentage jumps and return-to-previous-location.
  • Progress: chapter and offset are saved per book with reading time; the shelf shows percent and last-read time, and a book reopens where it was left.
  • Export: annotations of the current book are written as Markdown.

Prerequisites

Requirement Why Check
Emacs 29.1 or newer the ETAF runtime and built-in SQLite support emacs --version
etaf, etaf-ui, etaf-db, ebox, ecss the framework; each root entry must be on load-path every checkout is readable
ebook-parse backend parses local ebook files ebook-parse check (CLI) or the checkout's scripts/ebook_parse.py exists
poppler PDF text extraction pdftotext -v

Installation

(add-to-list 'load-path "/path/to/ecss")
(add-to-list 'load-path "/path/to/ebox")
(add-to-list 'load-path "/path/to/etaf")
(add-to-list 'load-path "/path/to/etaf-ui")
(add-to-list 'load-path "/path/to/etaf-db")
(add-to-list 'load-path "/path/to/etaf-reader")
(require 'etaf-reader)

(global-set-key (kbd "C-c e") #'etaf-reader-open)

M-x etaf-reader-open mounts the reader on its shelf; M-x etaf-reader-open-file imports and opens one file. Point etaf-reader-library-directories, etaf-reader-store-file, etaf-reader-theme and etaf-reader-parser-command at your own setup.

The parser is found automatically: etaf-reader-parser-command first, then PI_READER_PARSER, the installed ebook-parse command (a Pi setup installs that shim too), then a shared skill checkout under ~/.pi/agent/skills/ebook-parse.

Install it once, either as a standalone CLI or as a Pi skill package:

# standalone CLI (no Pi; the app then finds `ebook-parse` on PATH)
git clone https://forgejo.vhkd.top/pi-hub/ebook-parse-cli.git
cd ebook-parse-cli && ./scripts/install-cli.sh   # links ~/.local/bin/ebook-parse

# or with Pi
pi install git:forgejo.vhkd.top/pi-hub/ebook-parse@v0.1.0

If the app cannot find a pi installed parser, point it at the script:

(setq etaf-reader-parser-command
      (list (expand-file-name
             "~/.pi/agent/git/forgejo.vhkd.top/pi-hub/ebook-parse/skills/ebook-parse/scripts/ebook_parse.py")))

Minimal example

(let ((session (etaf-reader-create)))
  (etaf-reader-refresh-library session)
  (when-let* ((book (car (etaf-reader-books session))))
    (etaf-reader-open-book session book))
  (etaf-reader-open session))

Keys

Key Action
SPC / PgDn next page (or three lines while scrolling)
DEL / PgUp previous page
↓ / ↑ one line down / up
← / → previous / next page, or cursor movement while v is active
v, then arrow keys, RET keyboard selection and its toolbar
s / n / N in-book search, next match, previous match
c / a / B / S contents / notes / bookmarks / search results
b, g, u bookmark the page, jump to a percentage, return before a jump
o, C-c C-k reading settings, close the current pane/selection
z, [, ], t mode, width, width, palette
f, i, E, ? / C-c h, q shelf, rescan, export notes, help, close

Both help keys and More → Shortcuts open the same searchable, read-only modal inside the reader, without splitting the Emacs window. / searches current effective bindings; Escape leaves search editing first, then closes help and restores focus. Clicking the backdrop also closes help. The dialog uses two columns in wide windows and one scrolling column in compact ones.

Checks

make check    # structure + byte compilation (warnings as errors) + acceptance + API boundary
make test     # the public acceptance scenarios only

The acceptance suite drives a scripted parser and a temporary SQLite database, so it needs no network, backend process or frame.

Documentation

  • The manual: library, reading, annotations, themes, scripting and troubleshooting.
  • Architecture: module responsibilities, data flow and invariants.
  • The root entry Commentary: the authoritative list of supported functions and variables.