Convert a PDF to a reflowable, text-rich EPUB — with embedded images, proper heading structure, a nested table of contents, and text that wraps freely at any window width.
Built to handle complex, design-heavy InDesign PDFs (multi-column layouts, dot-leader TOC pages, running headers/footers, embedded figures).
| Feature | Detail |
|---|---|
| Reflowable text | PDF layout lines are joined into paragraphs; no fixed widths |
| Heading detection | Font size + bold flags → h1–h4 automatically |
| TOC page | Detects dot-leader and tab-separated entries; keeps each line separate |
| Nested nav TOC | Uses the PDF's own outline hierarchy for sidebar navigation |
| Header/footer strip | Removes running page numbers / chapter titles from content |
| Image extraction | Embeds all page images; converts non-PNG/JPEG formats via Pillow |
| NCX fallback | Writes toc.ncx for older readers (Kindle older firmware, etc.) |
| Built-in auditor | audit.py validates the output across 7 categories |
# 1. Set up the environment (one time)
bash setup.sh
# 2. Convert
.venv/bin/python convert.py my_book.pdf
# 3. (Optional) Audit the output
.venv/bin/python audit.py my_book_converted.epubThe EPUB is written to the same directory as the PDF, with the suffix _converted.epub. You can also specify a custom output path:
.venv/bin/python convert.py input.pdf output.epub- Python 3.10+
- PyMuPDF — PDF parsing
- ebooklib — EPUB creation
- Pillow — image conversion
- lxml — XHTML parsing (audit only)
Install all with:
pip install -r requirements.txt| File | Purpose |
|---|---|
convert.py |
Main PDF → EPUB converter |
audit.py |
EPUB validator (7 checks) |
requirements.txt |
Python dependencies |
setup.sh |
One-shot venv + install script |
PyMuPDF's get_text("dict") returns text as a tree of blocks → lines → spans. All lines within a block are PDF layout wraps of the same paragraph, so they are joined with a space into a single <p> tag — enabling free reflow.
The visual Table of Contents page uses leader patterns like . . . . or tab-separated numbers. The detector handles:
- Spaced dot-leaders:
(?:.[ ]*){3,} - Tab + page number:
\t\s*\d{1,4} - 2-line chapter blocks: title on line 0, bare number on line 1
- Roman-numeral page numbers
Blocks in the top or bottom 8 % of the page with fewer than 80 characters are treated as running headers/footers and excluded.
The PDF's built-in outline (doc.get_toc()) provides (level, title, page) triples. These are converted into ebooklib's nested (Section, [children]) structure so EPUB readers display a proper sidebar hierarchy.
- Multi-column body text may not reflow in perfect reading order (PDF doesn't encode column order)
- Tables are extracted as plain text (no
<table>tags) - Right-to-left scripts are untested
MIT