Skip to content

Build PDF chapters from the document outline - #45

Merged
arphen merged 1 commit into
mainfrom
feat/pdf-outline-chapters
Aug 22, 2026
Merged

arphen merged 1 commit into
mainfrom
feat/pdf-outline-chapters

Conversation

@tomazzinko

Copy link
Copy Markdown
Collaborator

Problem

PDFs are ingested as a single chapter titled Document, no matter how the book is organised. A 539-page book therefore arrives as one unbroken chapter, and since reading sections come from fixed ~2500-word chunks, the sidebar can only label them by their opening words — "In effect, Kant had provided,…" — which says nothing about where you are or where you're going.

Meanwhile pdfjs already exposes the book's embedded outline, and nothing in the ingest path ever asked for it. The Cambridge/Pinkard Phenomenology of Spirit carries a complete one, nested four deep with page destinations.

Change

Scoped entirely to the PDF reader — no changes to the ingest pipeline, the sidebar, the schema, or the reader plugin contract.

  • pdfjsAdapter reads getOutline() and resolves each entry's destination to a page number. Depth- and entry-capped; any failure yields no outline.
  • planPdfChapters turns each entry into a chapter running to the next entry's page, and prepareInitial/loadChapters build from that plan. Chapters are marked source: 'toc' with boundaryEvidence: ['publisher-toc'], so the structure is reported as authored rather than generated.

Entries at any nesting depth become chapters. Entries that can't describe a contiguous range are dropped — backwards or out-of-range destinations, blank titles, and entries sharing a start page with the previous one. Fewer than two usable entries, or no outline, keeps today's single-chapter fallback untouched.

Result

The Hegel PDF imports as 61 titled chapters instead of one:

  [107-114] A. Consciousness
  [115-125] II. Perceiving; or the Thing and Illusion
  [126-148] III. Force and the Understanding; Appearance and the Supersensible World
  [149-154] B. Self-Consciousness
  [155-163] A. Self-Sufficiency and Non-Self-Sufficiency of Self-Consciousness; Mastery and Servitude
  ...

Front matter (Cover, Copyright, Contents, Index) now arrives titled, so the existing classifyChapter step can recognise and skip it.

Trade-offs

  • Nested entries become chapters, not reading sections. Reading sections come from the pipeline's word-chunking; teaching it about authored sections would mean changing shared, format-agnostic code. Flattening keeps this PDF-only, and the resulting chapters are small enough (mostly 2–25 pages) that in-chapter navigation matters much less.
  • A part and its first chapter share a start page, so I. Sensuous-Certainty collapses into A. Consciousness and V. The Certainty and Truth of Reason into C. (AA) Reason. The text is all present, under the parent's title.
  • Boundaries land on page starts — outline destinations are page-level, so a heading halfway down a page starts its chapter at the top of that page.
  • Applies at ingest, so books already imported keep their existing structure until re-imported.

Testing

npm run lint, npm test (843 passing), and npm run build all pass via the pre-commit hook. New reader tests cover outline-derived chapters, the dropped-entry cases, and the single-chapter fallback. The chapter plan above was verified against the real 539-page PDF.

🤖 Generated with Claude Code

PDFs were ingested as a single "Document" chapter, so a 539-page book
arrived as one unbroken chapter whose reading sections could only be
labelled by their opening words. pdfjs already exposes the embedded
outline, so read it and give each entry its own chapter.

Entries at any nesting depth become chapters running to the next entry's
page. Entries that cannot describe a contiguous range are dropped:
backwards or out-of-range destinations, blank titles, and entries sharing
a start page with the previous one. An outline with fewer than two usable
entries keeps the existing single-chapter fallback, as does a PDF with no
outline at all.
@arphen
arphen merged commit cfa2c76 into main Aug 22, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants