Skip to content
getnextpdf.com

Pro edition

Table of contents

NextPDF\Pro\Toc collects H1–H6 headings from HTML and renders a paginated, multi-level table of contents as PDF content-stream operators. Page numbers are caller-supplied (or sequential placeholders); the module does not resolve live document cross-references.

This capability ships in NextPDF Pro (nextpdf/pro) and activates with a Pro-tier license envelope. A deployment without that entitlement does not load the capability’s classes. The Toc classes load whenever nextpdf/pro is installed; no runtime capability flag gates the module. Compare editions and get a license.

Terminal window
composer require nextpdf/pro:^3

The workflow has two phases:

  • Collection. AutoTocCollector::extract($html, maxDepth) scans the HTML for <h1><h6> tags up to the depth limit, strips inner markup, decodes entities, normalizes whitespace, and emits TocHeading value objects (level 0 = H1). It can assign sequential page numbers or apply a caller-provided index-to-page map.
  • Rendering. AutoTocRenderer::render($headings, $config) produces one PDF content-stream string per TOC page, with indentation per level, optional dot leaders, and optional page numbers. Each visible line is emitted as a Tj text-showing operation per ISO 32000-2:2020 §9.4.

AutoTocConfig is an immutable, fluently-configured value object controlling title, depth, fonts, spacing, margins, colors, page size, and whether dot leaders and page numbers are shown.

The load-bearing decision is that the module never invents a page number it cannot know. True target pages depend on the final laid-out document, which the caller owns; a guess would drift silently whenever pagination changed. So collection and rendering stay decoupled from layout. AutoTocCollector emits headings with null or placeholder pages; real page numbers arrive only through a caller-supplied assignPageNumbers() map. Rendering then produces plain content-stream operators, leaving page placement to the caller. The result stays deterministic and honest: the module states what it does not know rather than fabricating it.

Design background: An API that refuses to guess.

  • Input. HTML (collection) and a list of TocHeading (rendering).
  • Output. list<TocHeading> from collection; list<string> of PDF content-stream operators (one per TOC page) from rendering.
  • Page numbers. Either assigned sequentially, supplied via an index-to-page map, or left null. The module does not compute true target pages from a laid-out document; it does not resolve cross-references.
  • Depth. maxDepth is clamped to 1–6. Headings deeper than the configured depth are skipped.
  • Determinism. For identical HTML and configuration, collected headings and rendered operators are stable.
TypeKindKey members
NextPDF\Pro\Toc\AutoTocCollectorfinal classstatic extract(string $html, int $maxDepth = 6): list<TocHeading>, scan(string $html): void, assignSequentialPages(int $startPage = 1): list<TocHeading>, assignPageNumbers(array $pageMap): list<TocHeading>
NextPDF\Pro\Toc\AutoTocRendererfinal classstatic render(array $headings, ?AutoTocConfig $config = null): list<string>
NextPDF\Pro\Toc\AutoTocConfigfinal readonly classdefault(), landscape(), letter(), withTitle(), withMaxDepth(), withFontSize(), withDotLeader(), withPageNumbers(), withIndentPerLevel(), entriesPerPage(): int
NextPDF\Pro\Toc\TocHeadingfinal readonly classstring $title, int $level, ?int $pageNumber, float $y, withPageNumber(), withPosition(), hasPageNumber(): bool
<?php
declare(strict_types=1);
use NextPDF\Pro\Toc\AutoTocCollector;
use NextPDF\Pro\Toc\AutoTocRenderer;
$headings = AutoTocCollector::extract($html, maxDepth: 3);
$streams = AutoTocRenderer::render($headings);
echo count($streams), " TOC page(s) of content-stream operators\n";
<?php
declare(strict_types=1);
use NextPDF\Pro\Toc\AutoTocCollector;
use NextPDF\Pro\Toc\AutoTocConfig;
use NextPDF\Pro\Toc\AutoTocRenderer;
function buildToc(string $html, array $headingPageMap): array
{
$collector = new AutoTocCollector(maxDepth: 4);
$collector->scan($html);
// Caller supplies real page numbers from its own layout pass.
$headings = $collector->assignPageNumbers($headingPageMap);
$config = AutoTocConfig::default()
->withTitle('Contents')
->withMaxDepth(4)
->withDotLeader(true)
->withPageNumbers(true);
return AutoTocRenderer::render($headings, $config);
}
  • Empty heading text (after tag stripping) is skipped.
  • maxDepth is clamped to 1–6 at both collector and config; out-of-range values are corrected, not rejected.
  • Page numbers are placeholders unless the caller supplies a real map; the module does not run a layout pass to discover true target pages.
  • The renderer emits content-stream operators for placement onto a page; the caller is responsible for adding those pages to the document.

Collection is one regular-expression pass over the HTML. Rendering is linear in heading count, paginated by entriesPerPage(). See performance_budget.

HTML is scanned with a bounded heading regular expression and tag stripping; no HTML is executed and no external references are followed. Rendered text is escaped for content-stream string syntax.

ClaimSpec clauseStatus
TOC lines emitted as Tj text-showing operationsISO 32000-2:2020 §9.4Verified (unit suite)
Live document cross-reference resolutionNot supported (caller-supplied page numbers)

There is no Core TOC generator. Heading source HTML typically comes from the Core HTML pipeline. See /modules/core/html/.

This module collects headings and renders TOC operators. It does not perform document-wide cross-reference resolution, index generation, or bookmark-tree synchronization; those concerns are out of scope.

This page documents externally observable behavior and the supported public API surface only. Internal namespace paths, helper classes, mechanism tables, runbook filenames, and ticket prefixes are out of scope.