pdf.tocgen: deducing a PDF outline from font attributes
A CLI toolset to generate table of contents for PDF files automatically.
At a glance
- What is it?
- pdf.tocgen is a set of three Python command line tools that infer a table of contents for a PDF by looking at heading font sizes and names, then write that outline back into the document. It works best on TeX output, it is GPL-3.0-or-later licensed, and its own metadata still labels it alpha with a release from 2023.
- Who is it for?
- Adopt pdf.tocgen if you regularly produce PDFs from TeX or another software that embeds real fonts and you need navigable bookmarks without hand building them. Do not adopt it for scanned documents, for encrypted or hand made PDFs with inconsistent heading styles, or if you need a maintained toolchain, since the last release is 1.3.4 from November 2023 and the package metadata marks the project alpha.
- Can I use it commercially?
- Yes, with conditions. GPL-3.0 is a copyleft licence: if you distribute software that includes it, you must release that software's source code under the same licence. Running it internally without distributing it does not trigger that obligation.
- Is it still maintained?
- Yes. The repository last received commits 163 days ago.
- What is it written in?
- Mainly Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The problem with PDFs that were never given bookmarks
A PDF produced by a tool that does not emit an outline is a flat sequence of pages. The text is there, the structure is not. Readers get a scrollbar instead of a table of contents, and no viewer will offer a document map.
pdf.tocgen addresses that gap without asking you to describe the document. It is a set of command line tools for automatically extracting and generating the table of contents of a PDF file, and the mechanism it uses is the embedded font attributes and position of headings to deduce the basic outline of the document. In other words, it reads the font name and the font size of each text run and infers that a run in a large bold font at the start of a page is a level one heading.
That approach only works when the source document has a consistent typographic system, which is why the documentation is explicit about the scope. It works best for PDF files produced from a TeX document using pdftex and its friends pdflatex and pdfxetex, but it is designed to work with any software generated PDF files. Named examples include troff and groff output, Adobe InDesign and Microsoft Word. The parenthetical is the important one: you should not expect it to work with scanned PDFs, because a scanned page has no font attributes to read, only glyph positions.
So the audience is anyone producing PDFs from a typesetting system and needing bookmarks. It is not a tool for digitised archives.
Three programs split on purpose, not by accident
The design of pdf.tocgen is influenced by the Unix philosophy, and the project states that it intentionally separated into three separate programs. They work together, but each is useful on its own.
pdfxmeta extracts the metadata of headings, that is the font attributes and positions, and uses them to build a recipe file. pdftocgen takes a recipe and a PDF and generates a table of contents from it. pdftocio imports a table of contents into the PDF document. Three narrow responsibilities, with the recipe file as the explicit interface between the first two stages.
Splitting it this way has a practical benefit that is easy to miss. The recipe is a human readable TOML file, so the step that is hardest to get right, deciding what counts as a level two heading, is the step you can open in an editor and adjust. You are not forced to re-run a detection heuristic to change a threshold. The recipe records the attributes a heading at a given level must have, and once that file exists the rest of the pipeline is deterministic.
Each program also has a help option, with -h listing the options you could pass in. That matters for a toolset meant to be composed: you are expected to know the individual programs, not just the pipeline.
Heading filters and the recipe.toml format
Building the recipe starts with pdfxmeta. You give it a page number, an action level and a search keyword, and it appends the matching heading filter to the recipe file:
$ pdfxmeta -p page -a 1 in.pdf "Section" >> recipe.toml
$ pdfxmeta -p page -a 2 in.pdf "Subsection" >> recipe.tomlThe page argument is the page number where the search keyword appears, and the action level selects which level that heading belongs to. The output is a set of heading filters, each specifying the attributes a heading at a particular level should have. A recipe looks like this:
[[heading]]
level = 1
greedy = true
font.name = "Times-Bold"
font.size = 19.92530059814453
[[heading]]
level = 2
greedy = true
font.name = "Times-Bold"
font.size = 11.9552001953125The array of tables form is what lets several filters coexist, one per level. The font size values are not rounded, which tells you they come straight from the PDF font dictionary rather than from a rounding rule, and the size recorded for level one is roughly one and a half times the level two size. The greedy flag marks a filter that consumes matching text rather than stopping at the first hit.
Note what is not in the recipe: no page numbers, no regexes, no rules about position on the page beyond what the filters record. The recipe describes typography, not content. That is the core trade-off of the whole project, and it is why the tool is a strong fit for TeX output with a disciplined style file and a weak fit for anything else.
Installing and running the three stage pipeline
pdf.tocgen is written in Python 3, and the documentation says it is known to work with Python 3.7 to 3.11 on Linux, Windows and macOS, with the note that on BSDs you probably need to build PyMuPDF yourself. The systemwide install is:
$ pip install -U pdf.tocgenFor a per user install, which the documentation recommends to avoid messing up the package manager on your system, and for pipx:
$ pip install -U --user pdf.tocgenArch users can install from the AUR instead:
$ yay -S pdf.tocgenOnce installed, the full pipeline runs the detection step to build the recipe, then generates the outline and writes it back into the document:
$ pdftocgen in.pdf < recipe.toml | pdftocio -o out.pdf in.pdfThe generated outline is plain text, one entry per line, with the heading in quotes and the page number after it, and nesting shown by indentation. That makes it editable by hand, and the documentation shows that path explicitly:
$ pdftocgen in.pdf < recipe.toml > toc
$ vim toc # edit
$ pdftocio in.pdf < tocFor contributors, the repository has a Makefile with an install target that runs poetry install --no-dev, a test target that runs mamba over the spec directory and then a shell based CLI spec script, a lint target running pylint over the source directories, and demo targets for pdfxmeta and pdftocgen using the sample files in spec/files.
pdftocio alone is the most useful of the three
Because the programs are separate, each can be used without the others, and pdftocio carries the most standalone value. It reads and writes the outline of a PDF.
To display an existing table of contents to stdout:
$ pdftocio doc.pdfTo write it to a file and read it back:
$ pdftocio doc.pdf > toc$ pdftocio doc.pdf < tocTo name the output file explicitly:
$ pdftocio -o out.pdf doc.pdf < tocAnd to copy the table of contents from one document into another:
$ pdftocio -v doc1.pdf | pdftocio doc2.pdfThe -v flag is worth understanding, because it addresses a real problem with outline injection. When you write an outline into a PDF, the entries are placed by the writer, and without the flag the vertical positions of headings are not preserved in the copy. The flag exists to keep them aligned with the original pages.
There is also a human readable output mode, with -H printing the outline with dot leaders instead of quotes and indentation:
$ pdftocio -H doc.pdfIf you only need a table of contents for one specific document and do not want to configure anything, pdftocio on its own is the reason to install this package.
Where the heuristic breaks, and what the alternatives cost
The failure modes follow directly from the mechanism. A document whose headings are not typographically distinct will yield no outline or a wrong one. A document with three levels that all use the same font size cannot be distinguished at all, because the recipe has no way to express positional or contextual rules beyond font attributes. A PDF produced by a converter that renames fonts to something arbitrary will fail the same way, since the filter matches on font.name. And a scanned document has no font data whatsoever, which the documentation rules out explicitly.
There is also a correctness issue that follows from the typography based approach: a body paragraph that happens to be set in the heading font will be treated as a heading. The greedy flag and the level assignment do some work here, but a style file that uses the heading font for pull quotes will produce spurious entries.
The alternative approaches differ in where the intelligence lives. A TeX source can generate bookmarks natively, with the hyperref package and section commands, and that path produces a semantically correct outline because the structure is known at authoring time rather than guessed from the output. The trade-off is that it only works when you control the source. For a PDF you did not generate, or one produced by a tool you cannot change, the choice is between a typography heuristic like this one and a manual outline written in an editor and fed to pdftocio.
The middle ground is what this project offers: use pdfxmeta to propose the filters, then edit recipe.toml by hand to correct a level or a font name, and let pdftocgen do the scanning. That is more work than a native LaTeX setup and less work than reading a hundred pages to build an outline by hand.
GPL-3.0-or-later, an alpha label and a 2023 release
The licence is GPL-3.0-or-later, declared in the pyproject.toml metadata, and the repository root contains both a LICENSE file and a LICENSE_AGPL file. GPL is copyleft, so if you redistribute a modified version of this code, or bundle it into a larger work that you distribute, the copyleft obligations of the licence apply to that work. Using the programs as command line tools in your own document pipeline is a different situation from shipping them, and this is not legal advice, so a commercial distribution plan needs its own review of the licence text.
The package metadata is candid about the maturity level. The classifier reads Development Status :: 3 - Alpha, and the intended audience is listed as End Users and Desktop with the environment as Console. The published version is 1.3.4, matching the latest release tag v1.3.4 from 2023-11-26, with v1.3.3 in April 2023 and v1.3.2 the day before that.
Maintenance signals are mixed. The repository is not archived, and the last push was on 2026-05-13, which is more recent than the release history would suggest. But the release line has not moved since November 2023, so a pip install today gives you the same code as a pip install two years ago.
The runtime dependencies are small and stable: PyMuPDF at 1.18.14 or newer, toml at 0.10.2 or newer and chardet at 5.1.0 or newer, with Python 3.7 or newer declared as the constraint. The project is built with poetry-core and packaged with poetry, and the README notes the Python 3.7 to 3.11 range is what is known to work.
Editorial conclusion
Adopt pdf.tocgen if you regularly produce PDFs from TeX or another software that embeds real fonts and you need navigable bookmarks without hand building them. Do not adopt it for scanned documents, for encrypted or hand made PDFs with inconsistent heading styles, or if you need a maintained toolchain, since the last release is 1.3.4 from November 2023 and the package metadata marks the project alpha. Verify first that pdfxmeta finds your headings at all on a real sample file, that the font name and size recorded in recipe.toml match what you expect, and that the GPL-3.0-or-later terms fit how you intend to distribute anything built on it.
Frequently asked questions
How do I install pdf.tocgen?
It is a Python 3 package, so `pip install -U pdf.tocgen` installs it systemwide, `pip install -U --user pdf.tocgen` installs it for the current user, and Arch users can install it from the AUR with a helper such as yay. The documentation recommends the per user approach to avoid modifying the system package manager.
How does pdf.tocgen detect headings in a PDF?
It reads the embedded font attributes and positions of headings. The pdfxmeta program produces a recipe file of heading filters holding a level, a font name and a font size, and pdftocgen applies those filters to produce the outline.
Can pdf.tocgen work on scanned PDFs?
No. The documentation states it is designed for software generated PDF files and that you should not expect it to work with scanned PDFs, because a scan carries no font attributes to match against.
Can I edit the generated table of contents before adding it to the PDF?
Yes. Redirect the output of pdftocgen to a file, edit it, then feed it back to pdftocio with `pdftocio in.pdf < toc`. The README shows that workflow with an explicit edit step in between.
What licence is pdf.tocgen released under?
GPL-3.0-or-later. The pyproject.toml declares it as GPL-3.0-or-later and the repository root contains LICENSE and LICENSE_AGPL files, so review the licence text if you plan to redistribute the code or build it into a product you ship.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/krasjet-pdf-tocgen)Was this useful?