Bind what you already own — paper you scanned, pages you saved, ebooks that arrived broken — into a personal library of clean, reflowable books that pass the official validator at zero errors. Natively on your Mac, 100% offline. The OCR is a tool of its own, if the OCR is all you came for.
No API keys · No subscriptions · No internet · Your files never leave your machine.
What reepub is for. reepub is a tool for digitizing documents you own or have the right to digitize — your own writing, notes and correspondence, public-domain works, or books you physically own — into a personal ebook library you keep locally. Everything is processed on your own Mac; nothing is ever uploaded. Please respect copyright and the rights of authors and publishers.
You've got paper worth keeping — your own notes, a stack of letters, an
out-of-print book you own. Most "PDF to EPUB" tools either upload it to a cloud
service, charge per page, or spit out a fixed-layout EPUB that's really just
images glued together — unreadable on a phone. reepub is different — and the
difference is ownership:
- You own it — you don't rent it. Cloud OCR is a borrowed library card:
revocable, priced per page, and your files pass through someone else's servers.
reepubis the book on your own shelf — free, offline, and yours; no one can reprice it, gate it, or switch it off. - OCR you already paid for. It unlocks Apple's Vision framework and the Neural Engine already in your Mac (M1–M4+) — so a tiny MIT tool matches paid cloud OCR, fully on-device.
- Reflowable output, not image-glued fake EPUB — text is reconstructed into real paragraphs and chapters, so it reflows on any screen size, not a frozen page image.
- There is no pipe. No API key, no account, no network call — your books physically cannot leave the machine. Privacy that's structural, not a promise.
- Traditional Chinese & English recognition out of the box (
zh-Hant+en-US). - Validated EPUB3 — every book is run through a built-in structural
validator before it's handed back: no Java, no download, nothing to install,
so it always runs. One that fails is deleted and the command exits non-zero.
npm testgoes further and holds a freshly built book to the official epubcheck at 0 errors / 0 warnings. - Repairs the books you already have —
reepub healfixes a broken EPUB and tells you exactly what it changed. Vertical right-to-left CJK volumes carrying four epubcheck errors each came out at zero, with every chapter and their reading direction intact. See Healing. - MIT-licensed, self-contained, forkable, free forever.
-
Smart paragraph stitching — uses line bounding boxes, vertical gaps, indents, and punctuation cues to merge OCR lines back into clean paragraphs.
-
Automatic cover — renders page 1 at 2× and wraps it as the EPUB cover.
-
Hybrid text + image pages — pages with little text (illustrations, plates) are preserved as images instead of garbled OCR.
-
Automatic chapter detection — splits on heading cues (e.g.
第一章,Chapter). -
Four ways to use it — downloaded command-line tools that need no build, a one-click Mac app, a local web UI, or the Node CLI.
-
Localized app UI — English / 繁體中文 / 日本語
Nothing, if you take Option A — the released binaries are universal and self-contained. Everything below is for building from source.
- macOS 13+ (Apple Silicon strongly recommended) for the native app
- Xcode Command Line Tools — for the Swift compiler (
xcode-select --install). No full Xcode required. - Node.js v20+ — only for the optional web UI / CLI path
zip/unzip/xmllint— preinstalled on macOS
git clone https://github.com/CVERInc/reepub.git
cd reepub
make app # builds macos/build/Reepub.app (Command Line Tools only)Option A — Download the command-line tools (nothing to build)
Latest release — universal binaries (Apple Silicon and Intel), macOS 13+.
tar xzf reepub-cli-*-macos-universal.tar.gz
xattr -d com.apple.quarantine scan-ocr epub-kit # unsigned; see the release notes
./scan-ocr book.pdf > pages.json # OCR, and nothing else
./epub-kit pages.json book.epub --title "…" --author "…"scan-ocr is useful on its own: a PDF in, every recognized line with its text
and its box on stdout. If the text and the layout are all you came for, you can
stop there — nothing downstream is required.
Option B — Native macOS app
make app
open macos/build/Reepub.appPick a PDF (or drag one onto the window), let Vision OCR run, optionally set a title and author, then Save as EPUB… to save the finished book. Everything — OCR, assembly, and validation — happens in the app, fully offline.
Option C — Local web UI
make build # compiles the Swift OCR CLI (bin/scan-ocr) used by the server
npm start # serves http://localhost:30232Open the page, drop in a PDF, enter a title/author, and download the finished EPUB once conversion completes. The conversion log streams live.
Option D — Command line (Node)
make build
node src/builder.js <input.pdf> <output.epub> [book-title] [book-author]Example:
node src/builder.js ~/Documents/scanned_book.pdf ~/Desktop/my_book.epub "我的書名" "作者"- OCR extraction —
bin/scan-ocr(Swift) loads the PDF via PDFKit, renders each page to a bitmap at 2× scale, and runs Apple'sVNRecognizeTextRequest. It emits JSON of every recognized line with normalized bounding boxes, saves page 1 as the cover, and saves low-text pages as image plates. - Text reassembly —
src/builder.jsfilters out headers/footers, stitches lines into paragraphs using geometry + punctuation heuristics, detects headings, and groups everything into chapters. - EPUB packaging — writes a standards-compliant EPUB3 (
content.opf,toc.ncx, per-chapter XHTML, cover) and zips it with the uncompressedmimetypeentry first. - Validation —
src/validator.jschecks the ZIP mimetype layout,container.xml, the OPF manifest/spine, XHTML well-formedness, that every content document actually has a<body>, that no internal reference dangles, and that nothing escapes the container. A book that fails is deleted and the command exits non-zero.
Every package document, table of contents and navigation document comes from
src/binder.js — the single place allowed to emit one. See
PRINCIPLES.md for why that boundary exists and what CI does to
keep it.
Every threshold those heuristics use — why a page counts as a plate at 120
characters, why a paragraph breaks at 1.8 line-heights, why the cover grey is
#111111 and not black — is written down with its evidence in
docs/measured-constants.md. None of them are
values anybody publishes; each was measured or earned by watching something
specific go wrong.
Ebooks in the wild are broken in ways their owners never see: a forgiving reader
shows the book anyway, so the damage only surfaces when something strict refuses
it. The hardest case is a vertical, right-to-left CJK volume: the reading
direction it needs is an EPUB 3 attribute, so a book packaged as EPUB 2 has to
choose between validating and opening the right way round. heal gives it both.
node src/heal.js broken.epub healed.epubHealing broken.epub → healed.epub
healed: EPUB 2.0 spine carried page-progression-direction → rebuilt as EPUB 3.0
healed: table of contents identifier disagreed with the package → unified
healed: every chapter declared the XHTML 1.1 doctype → <!DOCTYPE html>
healed: dropped @font-face "DroidFont", serif, sans-serif → res:///system/fonts/DroidSansFallback.ttf cannot load in an EPUB
✓ EPUB valid
Each of those four is a defect class, not a one-off. The last one names the reader that wrote the file rather than the file itself, which is the level this example stays at: counts and byte sizes would describe somebody's shelf.
Healing never edits in place, and a repair that fails validation is deleted
rather than handed back. reepub merge performs the same repairs on the volumes
it combines — it is the same engine, so the two cannot drift apart.
What gets repaired:
- EPUB 2 packages using an EPUB 3 spine attribute — the merged book is EPUB 3,
where
page-progression-directionis legal, so vertical right-to-left series keep their reading direction and validate. - A table of contents whose identifier disagrees with the package — one identifier is minted for the merged book and used in both.
- Chapters still declaring the XHTML 1.1 doctype — brought forward to
<!DOCTYPE html>, with the entities that doctype used to define ( ,—) rewritten as numeric references so nothing stops parsing. - Stylesheets pointing at fonts that do not exist — an
@font-facewhose only source isres:///system/fonts/DroidSansFallback.ttfcannot load anywhere except the Android reader that wrote it, and epubcheck rejects it outright.
Repair is never silent, and never a guess — an entity or reference reepub cannot resolve stops the run rather than being mangled into something that merely looks right.
npm test # unit, spec and conformance suites
npm run validate <file.epub> # validate any EPUB (or unpacked dir)
npm run epubcheck # fetch the official epubcheck jar (cached)npm test builds a real book and runs the official epubcheck against it. The
jar is fetched once into ~/.cache/reepub/ and reused; CI does the same, so the
local command and the pipeline check exactly the same thing.
MIT — see LICENSE. © 2026 CVER Inc.