Files
epub2pdf/USER_GUIDE.md
T

9.7 KiB
Raw Blame History

Document → PDF Converter — User Guide

A small, self-hosted web app that converts common document formats — EPUB3 ebooks, office documents (DOCX/ODT/RTF), TeX/LaTeX, Org, AsciiDoc, RST, Markdown, HTML, plain text, CSV/TSV, JSON, XML, SVG, images, and existing PDFs — into PDFs. Everything runs locally (or on your own server); no file ever leaves your machine.


1. What it does (and doesn't)

Does Doesn't
Preserves the EPUB's own CSS, images, headings, tables, code blocks Re-flow or re-typeset like a print publisher would
Renders CJK (Chinese / Japanese / Korean) correctly via Noto fonts Handle DRM-protected (e.g. protected) EPUBs
Supports right-to-left books (Arabic, Hebrew) Convert to other formats (only PDF out)
Keeps book title/author as PDF metadata PPTX, XLSX and other Office formats — planned, not yet

Supported input formats (27):

Family Extensions Notes
Ebooks .epub Full CSS / images / metadata fidelity (the original mode)
Web .html .htm Native WeasyPrint rendering; <script> tags stripped
Markup .md .markdown Headers, tables, fenced code, links
Office .docx .odt .rtf Two rendering modes (see §3): Rich (default, uses LibreOffice — faithful Word fidelity: fonts, Letter page size, colors, tables) or Standard (fast pandoc flatten). Title from document metadata (DOCX/ODT core.xml/meta.xml) or the H1
TeX / LaTeX .tex .latex Via pandoc; needs \documentclass or a leading %, else treated as plain text
Extended markup .org .adoc .rst Org mode, AsciiDoc, reStructuredText via pandoc
Text .txt .log UTF-8 safe, paginated
Data .csv .tsv Auto-detected delimiter → styled table; truncated to 5000 rows × 50 cols (extra data dropped with a note; split to include more)
Structured .json .xml Cleanly formatted listing
Vector .svg Embedded via WeasyPrint
Images .png .jpg .jpeg .webp .tiff .bmp One PDF page per image; max side 3000 px
PDF .pdf Pass-through with metadata re-stamping; max 500 pages; encrypted PDFs rejected

Format is detected from the file's real bytes (magic), not the extension. Naming: EPUBs use the book's title; everything else uses the file name (or the document's H1 heading for Markdown/HTML), with -2, -3 collision suffixes.

Limits: one file at a time per job (you can queue several, they render one after another in the same server process), max upload 400 MB, max output 200 MB.


2. Quick start

Option A — Run directly (no Docker)

# 1. install dependencies (Python 3.11+; WeasyPrint also needs the pango
#    system libraries; pandoc for office/TeX/markup formats — e.g. on Debian/Ubuntu:  sudo apt install libpango-1.0-0
#    libpangoft2-1.0-0 libharfbuzz0b fontconfig fonts-noto-cjk shared-mime-info pandoc)
pip install -r requirements.txt

# 2. start the web server
python app.py                 # -> http://localhost:8030
docker build -t epub2pdf .
docker run -d --name epub2pdf \
    -p 8030:8030 \
    -v epub2pdf-data:/app/data \
    epub2pdf

The volume epub2pdf-data keeps uploaded EPUBs and generated PDFs across restarts.

Option C — Coolify

See COOLIFY.md in this directory (3 steps: Dockerfile service, port 8030, volume at /app/data).


3. Using the web UI

  1. Open http://<host>:8030 in your browser.
  2. Drop any supported file onto the upload box (or click to browse).
    • The 27 formats in §1 are accepted; anything else is rejected with a message.
    • Files over 400 MB are rejected before upload finishes.
  3. The job starts immediately — progress updates show the current stage (e.g. Rendering chapter-04.xhtml (4/12) for EPUBs), then Done.
  4. When it finishes, a Download PDF button appears.
    • The PDF is named after the book's title (EPUBs) or the file name / H1 heading (everything else), e.g. My_Book.pdf.
  5. Repeat for the next book. Old jobs stay listed so you can re-download recent PDFs (the server keeps the 50 most recent jobs and their outputs).

Rendering mode (Rich / Standard)

For Office files (.docx, .odt, .rtf) you can choose how they render. The choice is a radio pair just above the file picker, and Rich is selected by default:

Mode Uses Result
Rich (default) LibreOffice headless Faithful Word fidelity — fonts, page size (Letter vs A4), page colors, and table borders preserved
Standard Pandoc Fastest, text-faithful flatten — fonts, page size, and colors may differ from the original

The toggle only affects the three Office formats; every other format renders identically regardless of which radio is selected.

If LibreOffice isn't installed on the server, a Rich conversion ends with a clear error message naming it — switch to Standard and retry.

What can go wrong in the UI

Symptom Meaning / fix
File too large (max 400 MB) Shrink or split the book, or raise MAX_UPLOAD_MB in app.py
Not a valid EPUB (bad zip container) File is corrupted or renamed — re-export from your ebook store/library
No readable content found in EPUB spine The EPUB has no HTML chapters (images-only books, broken exports)
DRM protected style errors Remove DRM first (Calibre / Calibre-DB tools) — this app can't decrypt
Stuck at Working… forever The server restarted mid-job; refresh the page and re-upload
Unsupported format File isn't one of the 27 supported types — see §1
CSV/TSV truncated Over 5000 rows or 50 columns — automatically truncated with a note; split the file to include more
PDF rejected Encrypted/password-protected, or over 500 pages

4. Command-line usage (no server)

# convert one file and exit
python app.py --cli book.epub

# choose where the PDF lands
python app.py --cli book.epub --output /path/to/dir

# inside a running container
docker exec epub2pdf python app.py --cli /app/data/uploads/book.epub --output /app/data/output

Progress lines like [3/12] chapter-03.xhtml print as each chapter renders.


5. HTTP API (for automation)

Three endpoints, all JSON:

# 1. upload (multipart form, field name: file) -> starts a job
curl -X POST -F "file=@book.epub" http://localhost:8030/api/convert
# -> {"job_id": "c524be5a9d85"}

# 2. poll status
curl http://localhost:8030/api/jobs/c524be5a9d85
# -> {"status": "working", "progress": 42.5, "message": "Rendering ch-05.xhtml (5/12)"}
# -> {"status": "done", "progress": 100, "filename": "Book_Title-20260828-203943.pdf"}
# -> {"status": "error", "error": "Not a valid EPUB (bad zip container)"}

# 3. download the finished PDF
curl -OJ http://localhost:8030/download/Book_Title-20260828-203943.pdf

Other endpoints:

  • GET / — the web UI
  • GET /health — {"status":"ok","max_upload_mb":400,"supported_formats":[...]} (use for healthchecks)

6. Configuration

Everything works with defaults. Optional overrides:

Setting Default How to change
Port 8030 Env PORT, or --port (web mode)
Data location <project>/data Env DATA_DIR (Docker: volume at /app/data) — holds uploads/, output/, work/
Max upload size 400 MB Constant MAX_UPLOAD_MB in app.py (edit + restart)
Page geometry A4, 20×18 mm margins Edit BASE_CSS in app.py (e.g. size: A4 → size: Letter)
Base typography Georgia/serif 10.5 pt, line-height 1.65 Edit BASE_CSS in app.py — the EPUB's own CSS still layers on top of it
RTL handling auto (reads EPUB page-progression-direction) automatic; no setting

Fonts (affects what renders correctly)

  • The Docker image ships DejaVu + Noto CJK fonts.
  • When running without Docker, install at least: fontconfig fonts-dejavu-core fonts-noto-cjk (plus libpango-1.0-0 libpangoft2-1.0-0 libharfbuzz0b shared-mime-info for WeasyPrint itself).
  • If a book uses an exotic font not installed, WeasyPrint falls back to the nearest available font — layout stays intact, letterforms may differ.

7. Where files live

<DATA_DIR>/
├── uploads/   # your uploaded .epub files (kept after conversion)
├── output/    # generated PDFs (kept; served by /download/<name>)
└── work/      # per-job temp dir (extracted EPUB + chunk PDFs; deleted after each job)

You can delete anything in uploads/ and output/ at any time (the server just returns 404 for downloads of deleted files). work/ is always empty when idle.


8. Troubleshooting

Problem Check
Server won't start: OSError: cannot load library 'libpango-1.0-0' Missing system libraries — see §2 Option A note
CJK text shows as boxes (□□□) fonts-noto-cjk not installed — install, then run fc-cache -f, restart server
Strange characters (—) instead of em dashes This build reads EPUB text as UTF-8 explicitly; if you see this, an old server is running — restart it
Conversion is slow Normal: WeasyPrint typesets chapter-by-chapter in-process. Big/illustrated books can take minutes; gunicorn timeout is 1200 s by default
Port already in use --port 9000 or change env PORT

9. Security notes (self-hosted)

  • The app has no login. Anyone who can reach the port can upload and convert. On a LAN, that's usually fine; on the public internet, put it behind a reverse proxy with auth, or firewall the port.
  • Uploads are stored under a random job ID (not your filename), and downloads are restricted to the output directory (path traversal is blocked).
  • Nothing is transmitted anywhere: conversion happens entirely in-process.