Convert math-heavy PDFs into clean, compilable LaTeX — page by page — using a vision LLM (OpenAI GPT-5 by default).
Unlike a plain OCR/text dump, this tool is tuned to detect and reconstruct
mathematical structure: inline and display equations, vertically aligned
operations, tabular tables, tikz diagrams, number lines, geometric figures
and didactic layouts. It preserves the document's original language and
heading hierarchy.
Built originally to digitize Spanish-language math textbooks, but works for any language and any structured PDF.
- Page-by-page conversion with a vision model (
detail: high). - Math-first prompt: correct
\(...\)/\[...\],array/aligned,\phantomalignment, escaped currency (\$), safe exponents, no nested display-math bugs. - Tables & diagrams: faithful
tabular; diagrams rebuilt withtikz. - One robust conversion: every page is transcribed at full fidelity — faithful tables and diagrams, all math, decorative photos and colors skipped. There is nothing to configure; the tool only does the textbook-grade job.
- Resumable & batched: already-converted pages are skipped; convert in batches to stay under rate limits.
- Assembling: stitches pages into one monolithic
.texplus a ready-to-buildstandalonedocument (title page + table of contents). - Chapter splitting: by an explicit JSON config or auto (blank-page markers).
- One-command compile to PDF (
pdflatex). - Two interfaces: a full command-line tool, and an optional local web UI
(
pdf2latex serve) with a library dashboard, cost preview and live progress.
pdf-latex-ai-converter/
├── sources/ # drop your PDFs here
├── output/ # everything generated lands here (git-ignored)
│ └── <Your-PDF-slug>/
│ ├── pages/ # page_0001.tex, page_0001.usage.txt, ...
│ ├── <slug>.tex # monolithic assembly
│ ├── <slug>-standalone.tex # compilable document
│ ├── chapters/ # chapter_01_*.tex, ... (after split)
│ └── log.txt
├── src/pdf2latex/ # the Python package
├── examples/ # example chapter config
├── requirements.txt
├── pyproject.toml
└── .env.example
Two ways to use it: the command line (below) or a local web UI (
pdf2latex serve— see Web UI). Both share the same engine, so pick whichever you prefer.
git clone <your-fork-url> pdf-latex-ai-converter
cd pdf-latex-ai-converter
python -m venv .venv
# Windows: .venv\Scripts\activate
# macOS/Linux: source .venv/bin/activate
pip install -e . # or: pip install -r requirements.txtcp .env.example .env # Windows: copy .env.example .env
# edit .env and set OPENAI_API_KEY=sk-...# See what's available
pdf2latex list
# Preview scope and approximate cost first (renders pages, makes zero API calls)
pdf2latex convert "My Book.pdf" --dry-run
# Convert the whole document (small PDFs)
pdf2latex convert "My Book.pdf"
# Or convert in batches of 100 pages (recommended for large books)
pdf2latex convert "My Book.pdf" --batch 1
pdf2latex convert "My Book.pdf" --batch 2
# ...repeat until done (the tool tells you the next batch)If you didn't
pip install, run it as a module:python -m pdf2latex convert "My Book.pdf"
# Explicit chapters from a JSON config (see examples/chapters.example.json)
pdf2latex split "My Book" --config examples/chapters.example.json
# Or automatically, using blank pages as chapter separators
pdf2latex split "My Book" --autoRequires a LaTeX distribution (TeX Live / MiKTeX) on your PATH.
pdf2latex compile "My Book"
# -> output/My-Book/My-Book-standalone.pdfIf the build fails on a few pages, let the model fix them: fix compiles, reads
the real pdflatex error for each broken page, and repairs it using the page image
plus that error — looping until the PDF builds or the budget runs out. Every fix
is checked by a safety guard (it never replaces a page with runaway or shorter
nonsense) and the previous version is saved as page_NNNN.tex.bak.
pdf2latex fix "My Book" # uses the original PDF to see each page
pdf2latex fix "My Book" --max-rounds 3 --max-tries 2| Command | What it does |
|---|---|
pdf2latex list |
List PDFs in sources/. |
pdf2latex convert <pdf> |
Convert pages → .tex, then assemble. Flags: --engine (openai/claude-code), --batch, --batch-size, --start, --end, --model, --max-tokens, --scale, --workers, --rpm, --tpm, --repair, --repair-retries, --title, --subtitle, --dry-run, --yes/-y. |
pdf2latex assemble <pdf|slug> |
Rebuild the monolith + standalone from existing pages. |
pdf2latex split <pdf|slug> |
Split into chapters: --config <file.json> or --auto. |
pdf2latex compile <pdf|slug> |
Compile the standalone .tex to PDF: --engine, --runs. |
pdf2latex fix <pdf|slug> |
Compile and auto-repair the pages that break (page image + the compile error), looping until the PDF builds. Flags: --model, --max-rounds, --max-tries. |
pdf2latex render-pages <pdf|slug> |
Render the pages still needing conversion to PNGs (used by the pdf-to-latex-claude Claude Code skill). Flags: --start, --end, --scale, --force. |
pdf2latex validate <pdf|slug> |
Check converted pages for broken LaTeX offline (braces, environments, math); writes needs-review.txt. Flags: --deep-check, --engine. |
pdf2latex serve |
Launch the optional local web UI in the browser (needs the [web] extra). Flags: --host, --port. |
<slug> is the folder name created under output/ (e.g. My-Book).
By default everything generated lands in ./output/ (git-ignored). To keep test
runs out of the repo entirely, send output to any folder on your machine:
# Per command:
pdf2latex convert "My Book.pdf" --out ~/Documents/pdf2latex-output
# Or once, for every command (recommended):
export PDF2LATEX_OUTPUT_DIR=~/Documents/pdf2latex-outputThe same location is used by assemble, split, compile and validate, so a
conversion and its follow-up commands always agree on where the files are.
{
"title": "My Mathematics Book",
"subtitle": "LaTeX edition",
"chapters": [
{ "start_page": 1, "slug": "intro", "title": "1. Introduction" },
{ "start_page": 25, "slug": "equations", "title": "2. Equations" }
]
}start_page is the PDF page number where the chapter begins; the chapter ends
right before the next chapter's start.
sources/Book.pdf
│ render each page → PNG (pypdfium2)
▼
vision LLM (OpenAI) with a math-aware system prompt
│ → LaTeX per page
▼
output/Book/pages/page_NNNN.tex
│ concatenate (assemble)
▼
output/Book/Book.tex ──split──▶ output/Book/chapters/*.tex
│
▼
output/Book/Book-standalone.tex ──pdflatex──▶ Book-standalone.pdf
-
Use
--batch Nto convert large books in chunks and resume safely. -
Pages convert concurrently (default
--workers 4), which cuts wall-clock time on large books. Use--workers 1for strictly sequential behaviour. If your account hits provider rate limits, cap throughput with--rpm(requests per minute) and/or--tpm(tokens per minute); 429s are also retried with exponential backoff automatically. -
Preview before you spend:
pdf2latex convert <pdf> --dry-runprints a pre-flight summary (pages in range, model and an approximate cost range), render-validates the pages and makes zero API calls. -
Every
convertshows that pre-flight summary and asks for confirmation before spending. Pass--yes(or-y) to skip the prompt in scripts and CI. -
The web UI offers three model tiers as buttons — Cheaper (
gpt-5-mini), Balanced (gpt-5, the default) and Best (gpt-5.5); from the CLI pick any model with--model. -
Two engines. Besides the OpenAI API (default), you can convert with Claude Code — it uses the local
claudeCLI and your Claude subscription, so there is no OpenAI cost. Requires Claude Code installed and signed in (claudeon PATH). It is slower than the parallel API for big books and is subject to your subscription's limits. CLI:--engine claude-code; web UI: pick Claude Code under Engine.pdf2latex convert "My Book.pdf" --engine claude-code -
Convert from inside Claude Code (no API at all). If you use this repo within Claude Code, the bundled skill
pdf-to-latex-claudelets Claude convert the pages itself — it reads each rendered page and writes the LaTeX in-session, billed to your Claude subscription. Just ask Claude to "convert<PDF>to LaTeX with the pdf-to-latex-claude skill". Under the hood it usespdf2latex render-pagesto produce the page images andpdf2latex compileto build the PDF. -
The cost figure is an estimate, and the GPT-5 prices shipped in the table are best-effort. Override the per-model price table without editing code by pointing
PDF2LATEX_PRICESat a JSON file, where each value is[input_usd_per_1M_tokens, output_usd_per_1M_tokens]:echo '{ "gpt-5": [1.25, 10.0], "my-model": [1.0, 3.0] }' > prices.json PDF2LATEX_PRICES=prices.json pdf2latex convert "My Book.pdf" --dry-run
-
On an interactive terminal a live progress bar shows a running token/cost tally; in non-interactive runs (CI, redirected logs) it degrades to the plain per-page log lines so nothing is garbled.
-
The model can occasionally produce a
tabularwhose column count doesn't match a row, or a list that needs closing. Checkoutput/<slug>/log.txtand thepdflatexlog; fix the few flagged pages by editing the page.tex. -
The standalone preamble loads missing images in
draftmode (boxes instead of errors). Remove\setkeys{Gin}{draft}once you add real image files.
Every converted page is checked by a cheap, offline validator (balanced braces,
matched \begin/\end, and an even count of unescaped $). Pages that still
look broken after the run are listed in output/<slug>/needs-review.txt with the
specific reason, so you know exactly where to look instead of discovering errors
only at final compile time.
# Re-check an existing conversion at any time (offline, no API calls)
pdf2latex validate "My Book"
# Optionally run a deeper external linter when available
pdf2latex validate "My Book" --deep-check # uses chktex if installedTo let the model fix the flagged pages automatically during conversion, add
--repair (this sends each failing page back to the model for a minimal fix, so
it uses extra API calls). --repair-retries N bounds the attempts per page.
pdf2latex convert "My Book.pdf" --repairPrefer the browser? Install the web extra and launch a small local app:
pip install -e ".[web]"
pdf2latex serve # then open http://127.0.0.1:8000From the page you can pick or upload a PDF, choose the model and workers,
preview scope and cost with a dry run, convert with a live progress bar and a
running token/cost tally, and download the resulting .tex and the
needs-review.txt report. It is a single-user local companion to the CLI and
reuses the exact same pipeline, so it spends real API credit just like the CLI.
- Costs depend on your model and page count; each page is one vision request.
- Output is machine-generated and may need light manual cleanup, especially on complex diagrams.
- The system prompt lives in
src/pdf2latex/prompts.py— tweak it for your domain.
MIT — see LICENSE.