Zum Inhalt springen

Langes HTML über mehrere Seiten umbrechen

Lassen Sie lange Inhalte mit automatischen Seitenumbrüchen über mehrere Seiten fließen. Fügen Sie eine Gliederung hinzu, damit Leser zwischen Abschnitten springen können. Dieses Recipe folgt examples/12-bookmarks-and-toc.php.

Terminal-Fenster
composer require nextpdf/core:^3

Die Versionsbedingung passt zum Paket nextpdf/core. Das Beispiel setzt PHP 8.4 voraus.

setAutoPageBreak(true, $margin) weist die Engine an, eine neue Seite zu beginnen, sobald Inhalt die Schwelle des unteren Rands überschreitet. Die Engine trennt langen Text, der über multiCell() oder writeHtml() geschrieben wird, an dieser Grenze. Das CSS-Fragmentation-Modul (css_break_3) ist in der Support-Matrix als Verified eingestuft und bestimmt das Umbruchverhalten der HTML-Pipeline.

bookmark($title, $level) fügt einen Gliederungseintrag hinzu, der auf die aktuelle Position verweist. Ein PDF-Gliederungseintrag ist mit einem Ziel verknüpft, damit der Benutzer direkt zu einer Seite springen kann (ISO 32000-2). Die Engine schreibt dieses Ziel als Dest-Eintrag des Elements (ISO 32000-2). Das Argument level verschachtelt Einträge zu einem hierarchischen Inhaltsverzeichnis in der Seitenleiste des Readers.

Die Pipeline bleibt single-pass (ADR-001). Die Paginierung wird beim Ausgeben des Streams entschieden, nicht anhand eines vorgehaltenen Layout-Baums.

  • setAutoPageBreak(bool $enabled, float $margin = 20): staticNextPDF\Core\Concerns\HasPages.
  • bookmark(string $title, int $level = 0, float $y = -1): staticNextPDF\Core\Concerns\HasNavigation.
  • multiCell(...) / writeHtml(string $html): staticNextPDF\Core\Concerns\HasTextOutput.

Die vollständige PHPDoc-Tabelle wird aus dem Quellcode generiert.

<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\Document;
$doc = Document::createStandalone();
$doc->setAutoPageBreak(true, margin: 25);
$doc->addPage();
$doc->bookmark('Section 1', level: 0);
$doc->setFont('helvetica', '', 11);
for ($i = 1; $i <= 80; $i++) {
$doc->multiCell(0, 7, "Paragraph {$i} of a long flowing document.");
}
$doc->save(__DIR__ . '/out.pdf');

Dieses Beispiel ist eigenständig und lässt sich im Harness ausführen. Es erstellt ein Dokument mit mehreren Kapiteln, einer verschachtelten Gliederung und automatischen Seitenumbrüchen und spiegelt examples/12-bookmarks-and-toc.php wider.

<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\Document;
$doc = Document::createStandalone();
$doc->setTitle('Bookmarks and Navigation');
$doc->setPrintHeader(false);
$doc->setPrintFooter(false);
$doc->setAutoPageBreak(true, margin: 25);
$chapters = [
'Chapter 1: Introduction' => ['What is NextPDF?', 'Key Features'],
'Chapter 2: Getting Started' => ['Installation', 'Your First PDF'],
'Chapter 3: Advanced Topics' => ['Worker-safe Architecture', 'Streaming Output'],
];
$body = 'NextPDF is a modern PDF 2.0 library for PHP. This paragraph is '
. 'repeated so the content overflows the page and the engine inserts '
. 'an automatic page break at the bottom-margin threshold.';
foreach ($chapters as $chapter => $sections) {
$doc->addPage();
$doc->bookmark($chapter, level: 0);
$doc->setFont('helvetica', 'B', 18);
$doc->cell(0, 12, $chapter, newLine: true);
$doc->ln(3);
foreach ($sections as $section) {
$doc->bookmark($section, level: 1);
$doc->setFont('helvetica', 'B', 14);
$doc->cell(0, 10, $section, newLine: true);
$doc->setFont('helvetica', '', 11);
for ($i = 0; $i < 12; $i++) {
$doc->multiCell(0, 7, $body);
}
$doc->ln(4);
}
}
$out = getenv('NEXTPDF_COOKBOOK_OUTPUT');
$doc->save($out !== false ? $out : __DIR__ . '/paginate-long-html.pdf');
echo "Wrote paginate-long-html.pdf\n";

Erwartete STDOUT-Ausgabe:

Wrote paginate-long-html.pdf
  • Deaktivieren und dann vergessen. Wenn der automatische Seitenumbruch deaktiviert ist, schneidet die Engine Inhalte jenseits des unteren Rands ab, statt sie weiterfließen zu lassen. Aktivieren Sie ihn vor langen Inhalten wieder.
  • Nicht umbrechbarer Inhalt. Ein einzelner Block, der höher als die nutzbare Seitenhöhe ist, kann UnsplittableContentException auslösen. Eine sehr hohe Tabellenzeile oder ein großes Bild kann die Ursache sein. Teilen Sie den Quellinhalt auf.
  • Lesezeichen vor dem Inhalt setzen. Rufen Sie bookmark() an der Position auf, auf die das Ziel zeigen soll. Platzieren Sie es unmittelbar vor der Überschrift, die Sie als Nächstes schreiben, auf der gewünschten Seite.
  • Kopf- und Fußzeile reservieren Platz. Eine Druck-Kopfzeile oder -Fußzeile verringert die nutzbare Inhaltshöhe; die Umbruchschwelle berücksichtigt das. Wenn Sie beide deaktivieren — wie im Beispiel —, steht die volle Inhaltshöhe zur Verfügung.
  • Verschachtelung der Gliederung. level ist die Verschachtelungstiefe. Ein untergeordneter Eintrag mit level: 1 muss auf einen übergeordneten Eintrag mit level: 0 folgen. Andernfalls flacht der Reader den Gliederungsbaum ab.

Die Engine entscheidet die Paginierung während des einzelnen Ausgabedurchlaufs. Der Aufwand wächst linear mit der Inhaltslänge, O(n). Das Budget ist wall_ms: 2000, peak_mb: 96. Die Wall-Zeit fällt etwas höher aus als bei einseitigen Recipes, weil die seitenübergreifende xref und die Gliederung aufgebaut werden. Das Streaming-Modell hält den Speicherbedarf begrenzt, und die Gliederung bleibt eine kleine, flache Liste.

Auszug aus der CSS-Support-Matrix (nur Verified-Zeilen)

Abschnitt betitelt „Auszug aus der CSS-Support-Matrix (nur Verified-Zeilen)“

Reproduziert werden nur die Verified-Zeilen aus der inhaltlich geprüften CSS-Support-Matrix.

W3C-ModulLevelStatusNachweis
CSS Fragmentation (css_break_3)3Verifiedsrc/Html/Fragmentation/, tests/Unit/Html/PagedMedia/
CSS Table (css_tables_3)3Verifiedsrc/Html/Table/ + Golden-PDFs
CSS Cascading and Inheritance (css_cascade_3)3Verifiedsrc/Html/Cascade/

@page-Selektoren für benannte Seiten sind Teil von CSS Paged Media. Prüfen Sie in der Matrix den aktuellen Grad dieses Moduls, bevor Sie sich darauf verlassen.

Einschränkungen des Single-Pass-Streamings (ADR-001)

Abschnitt betitelt „Einschränkungen des Single-Pass-Streamings (ADR-001)“

Die Engine gibt Seitenumbrüche aus, während der Stream fließt. Es gibt keinen vorgehaltenen Baum für einen erneuten Umbruch; daher ist eine Umbruchentscheidung endgültig, sobald sie getroffen wurde. Manche Inhalte benötigen ihre endgültige Seitenzahl erst nach dem Layout, zum Beispiel ein Querverweis. Solche Inhalte sind eingeschränkt; erstellen Sie sie daher mit dieser Einschränkung im Hinterkopf.

Die Paginierung liegt beim Page-Break-Controller, nicht beim Parser. Der Parser gibt keine rohen Seitenübergangs-Operatoren aus; er fordert einen Umbruch über den Controller-Vertrag an.

Das Streaming-Modell hält nur den Puffer der aktuellen Seite plus die flache Gliederungsliste vor, nicht alle Seiten auf einmal. Ein sehr langes Dokument bleibt unter der ADR-020-Obergrenze, weil die Engine fertige Seiten ausschreibt. Tabellen und Flex-Container halten sich weiterhin an die Grenze von 5,000 Knoten pro Kontext.

Ein bösartiges Dokument kann keinen unbegrenzten Speicher erzwingen. Die Obergrenzen für Elemente und Verschachtelung (ADR-001) und das Knotenbudget pro Kontext (ADR-020) begrenzen den Aufwand. Prüfen Sie Länge und Struktur von langem Inhalt, der von Benutzern bereitgestellt wird. Die Engine rendert einen vom Angreifer kontrollierten Gliederungstitel als Text und interpretiert ihn niemals.

AussageSpezifikationAbschnittreference_id
Jeder Gliederungseintrag kann mit einem Ziel verknüpft sein, sodass der Benutzer direkt dorthin springt.ISO 32000-2iso32000_2_sec12#x1.x5.p4
Der Dest-Eintrag eines Gliederungseintrags benennt das Ziel, das angezeigt wird, wenn der Eintrag aktiviert wird.ISO 32000-2iso32000_2_sec12#x1.x11.p30

Dieses Recipe zeigt, wie NextPDF lange Inhalte fließen lässt und eine Gliederung aufbaut. CSS Fragmentation ist in der Support-Matrix als Verified eingestuft.

Nicht zutreffend.