toc-this
Adds a clickable table of contents (the bookmarks sidebar) to a PDF that has none. It works on born-digital and scanned PDFs.
uv tool install toc-this # or: pipx install toc-this
toc-this book.pdf # writes book.toc.pdf
To run it once without installing, use uvx toc-this book.pdf. It needs Python 3.9 or newer. Scanned PDFs also need Tesseract (see Scanned PDFs).
How it finds the headings
- The book's printed Contents page. The titles and the nesting (from indentation) come from the Contents page. If the Contents lines link to their pages, as Word and LaTeX exports usually do, those links give the pages. Otherwise, each title is found in the book itself. The search goes forward from the previous entry and tolerates OCR typos. The printed page numbers are not used, because OCR misreads them next to dot leaders, and front matter often uses separate numbering.
- Font sizes. This is used when there is no Contents page. Lines in a larger font than the body text become headings.
- Your own list. This is used when the guess is wrong:
toc-this book.pdf --print > toc.txt # write what it found, then edit it
toc-this book.pdf --from toc.txt # use your list
In toc.txt, each level is indented two more spaces, and the page number goes last:
Chapter 1 Getting started 5
1.1 Install 6
--offset N adds N to every page in --from. Use it when you type page numbers from the printed book and printed page 1 is PDF page 1+N.
Scanned PDFs
When most pages have no text, toc-this OCRs every page on all CPU cores with Tesseract. A 469-page book took about 2 minutes on an 8-core Mac. Install Tesseract first:
brew install tesseract # macOS
sudo apt install tesseract-ocr # Debian/Ubuntu
Use --lang for books that are not in English, for example --lang deu or --lang eng+fra. The language data must be installed too. OCR only feeds heading detection. The output PDF is still an image PDF and cannot be searched. To get a searchable PDF, run OCRmyPDF first, then run toc-this on its output.
Limits
- PDFs that already have a ToC are refused.
- On scans, OCR sometimes garbles a Contents line beyond recognition, often short ones next to dense dot leaders. Those entries are left out of the PDF.
--printshows each one as a#line in its place. To add one back, delete the#, fix the title, add the page number, and pass the file with--from. - On scans, a title is taken from the matching heading in the body when that heading is the same length, because body headings are larger and OCR more cleanly. Otherwise the Contents wording is kept.
- toc-this looks for a Contents page only in the first 30 pages.
- When the fallback guesses from font sizes, it misses subheadings set in the same size as body text. Use
--fromfor those.
Development
uv run python test_toc.py # self-check, runs the OCR case when tesseract is on PATH
uv run python check_sample.py some.pdf [count] # every entry's title must appear on its page
To release, bump version in pyproject.toml, commit, and push a tag v<version>. The publish workflow runs the tests (with Tesseract) and uploads to PyPI.
Run check_sample.py on a long scanned book before changing how titles are matched. One rule is covered only there: short, generic titles such as "Summary" may not move the search forward. The unit fixture is too short to show the cascade that rule prevents.
License
AGPL-3.0-or-later. toc-this depends on PyMuPDF, which is AGPL.
Metadata
Release files for toc-this 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| toc_this-0.1.0.tar.gz | 25.9 kB | Without provenance | Details for toc_this-0.1.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| toc_this-0.1.0-py3-none-any.whl | Python 3 | none | any | Details for toc_this-0.1.0-py3-none-any.whl |
Total release size: 46.0 kB
Release files / toc_this-0.1.0.tar.gz
| Download URL | toc_this-0.1.0.tar.gz |
|---|---|
| Size | 25.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
ca77c69e5dd15a79d06573c6ad5c9726a9e2cf10715e01305f65979547a8f145
|
|
BLAKE2b-256 checksum How to use checksums |
b53374fab89461a0637c3ee9cf07296ea45d6efd19a0e14622897a4a145dbc99
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.13.0 {"installer":{"name":"uv","version":"0.13.0","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / toc_this-0.1.0-py3-none-any.whl
| Download URL | toc_this-0.1.0-py3-none-any.whl |
|---|---|
| Size | 20.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
2a97a204d53817d5edb344e33c875dda3073f8567a8d1dab9a7f4e15531816f2
|
|
BLAKE2b-256 checksum How to use checksums |
dd6955a05544e2d426284981178bbe970f4783a00261cdec1e8cf04417b10b0e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.13.0 {"installer":{"name":"uv","version":"0.13.0","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Security for toc-this 0.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.