Zum Inhalt springen

HTML als PDF-Seiteninhalt rendern

Dieses Recipe wandelt ein Fragment aus HTML und CSS mit einer einzigen Methode in PDF-Seiteninhalt um: writeHtml(). Übergeben Sie der Methode Markup; sie rendert daraus eine formatierte Seite. Die vollständige, lauffähige Version dieses Codes finden Sie unter examples/08-html-basic.php. Folgen Sie den Schritten weiter unten, oder kopieren Sie das Beispiel direkt.

NextPDF liest Ihr HTML in einem einzigen Durchlauf und streamt das Ergebnis direkt auf die Seite. Das ist eine Single-Pass-Streaming-Pipeline. Sie müssen dieses Modell nicht verstehen, um das Recipe zu nutzen; Sie sollten es aber im Hinterkopf behalten, denn es prägt einige der Regeln weiter unten auf dieser Seite.

Terminal-Fenster
composer require nextpdf/core:^3

Dieser Befehl installiert das Paket nextpdf/core. Die Beispiele auf dieser Seite laufen unter PHP 8.4; die unterstützte Laufzeitumgebung ist >=8.4 <9.0.

writeHtml() nimmt eine HTML-Zeichenkette entgegen und zeichnet sie ab der aktuellen Cursor-Position auf die aktuelle Seite. Hier sehen Sie Schritt für Schritt, was intern passiert: Zuerst durchsucht die Engine Ihr HTML einmal und zerlegt es in eine Liste von Tokens (HtmlTokenizer). Anschließend durchläuft sie diese Liste von links nach rechts (HtmlParser). Für jedes Element schreibt sie die passenden PDF-Zeichenanweisungen, also Content-Stream-Operatoren, in einen Puffer. Die Engine baut zwischen den Aufrufen niemals einen Baum Ihrer Elemente auf und hält auch keinen im Speicher. Dieses bewusste Design ist das Single-Pass-Streaming-Modell und in ADR-001 festgehalten.

Jedes unterstützte Block-Element wird zu einer Layout-Box, jeder Textlauf zu einem Text-Show-Operator. Stile aus Inline-style-Attributen und aus einem <style>-Block werden über die CSS-Kaskade aufgelöst, also über die Standardregeln, die entscheiden, welcher Stil gewinnt, wenn mehrere zutreffen. Textumbruch, Ausrichtung und Abstände folgen dem CSS-Text-Modell; es legt fest, wie Quelltext zu formatiertem, zeilenumgebrochenem Text wird (W3C CSS Text Level 3).

Wenn Sie keine Schrift auswählen, verwendet der Fließtext eine Standardschrift. Diese Standardschrift ist eine standardmäßige Type-1-Schrift, also eine der 14 Standardschriften, die in ISO 32000-2 benannt sind. Die Standardschrift ändert sich nur, wenn Sie eine eigene Schrift registrieren und auswählen oder wenn ein Konformitätsprofil von NextPDF verlangt, eine Ersatzschrift einzubetten.

Stellen Sie früh die richtige Erwartung her: NextPDF unterstützt eine Teilmenge von HTML und CSS, nicht den vollen Umfang von beidem. Dieses Recipe behandelt genau diese unterstützte Teilmenge und beansprucht keine vollständige HTML- oder vollständige CSS-Unterstützung. Den genauen, verifizierten Status jedes Moduls finden Sie in der CSS-Support-Matrix.

Die Methodensignatur lautet writeHtml(string $html): static. Sie ist auf dem Interface NextPDF\Contracts\PdfDocumentInterface deklariert und in NextPDF\Core\Concerns\HasTextOutput implementiert. Sie rendert auf die aktuelle Seite und legt eine Seite für Sie an, falls noch keine existiert. Die vollständige PHPDoc-Tabelle für die Methode wird aus dem Quellcode generiert.

<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\Document;
$doc = Document::createStandalone();
$doc->setTitle('HTML Basic');
$doc->addPage();
$doc->writeHtml('<h1>HTML Rendering in NextPDF</h1><p>Rendered with <strong>writeHtml()</strong>.</p>');
$doc->save(__DIR__ . '/out.pdf');

Dies ist das vollständige, eigenständige Beispiel; es ist genau das, was das Test-Harness ausführt. Es entspricht examples/08-html-basic.php. Statt einen Ausgabepfad fest zu verdrahten, schreibt es an den vom Harness vorgegebenen Ort, damit das Reproduzierbarkeits-Harness das Skript zweimal ausführen und die Ergebnisse vergleichen kann.

<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\Document;
$doc = Document::createStandalone();
$doc->setTitle('HTML Basic');
$doc->addPage();
$html = <<<'HTML'
<h1 style="color: #1E3A8A;">HTML Rendering in NextPDF</h1>
<p>NextPDF renders <strong>HTML content</strong> directly into PDF pages.
This is the recommended approach for <em>mixed formatting</em>.</p>
<h2>Supported elements</h2>
<ul>
<li>Headings (h1-h6)</li>
<li>Paragraphs with <strong>bold</strong> and <em>italic</em></li>
<li>Ordered and unordered lists</li>
<li>Tables with borders and alignment</li>
<li>Inline styles (color, font-size, margin)</li>
</ul>
<h2>Ordered list</h2>
<ol>
<li>Create a Document instance</li>
<li>Add pages and content</li>
<li>Call save() or output()</li>
</ol>
HTML;
$doc->writeHtml($html);
// The harness sets NEXTPDF_COOKBOOK_OUTPUT and runs this script twice.
// Honour it: do not hard-code a path, do not echo the PDF to STDOUT.
$out = getenv('NEXTPDF_COOKBOOK_OUTPUT');
$doc->save($out !== false ? $out : __DIR__ . '/render-html-to-pdf.pdf');
echo "Wrote render-html-to-pdf.pdf\n";

Erwartete STDOUT-Ausgabe:

Wrote render-html-to-pdf.pdf
  • Cursor-Übergabe. writeHtml() rückt den Cursor an das Ende des gerenderten Inhalts vor. Ein nachfolgendes cell() oder ein zweites writeHtml() setzt dort fort, nicht am Seitenanfang.
  • Noch keine Seite. Falls keine Seite existiert, fügt writeHtml() vor dem Rendern eine hinzu. Rufen Sie addPage() ausdrücklich auf, wenn Sie zuerst eine bestimmte Seitengröße setzen müssen.
  • Element- und Verschachtelungsgrenzen. Die Streaming-Engine erzwingt eine Obergrenze von 50,000 Elementen und 100 Verschachtelungsebenen (ADR-001). Ein Dokument, das diese Grenzen überschreitet, wird abgelehnt, statt stillschweigend abgeschnitten zu werden.
  • Nicht unterstütztes Markup. Elemente und Eigenschaften außerhalb der unterstützten Teilmenge werden ignoriert oder fallen zurück; sie lösen keinen Fehler aus. Prüfen Sie die Abdeckung anhand der CSS-Support-Matrix, bevor Sie sich auf eine Eigenschaft verlassen.
  • Externe Ressourcen. Remote-Bilder und -Stylesheets unterliegen der External-Resource-Policy; die Standard-Policy ruft keine beliebigen Remote-URLs ab.

Da Tokenisierung und Rendering in einem einzigen Durchlauf über Ihre Eingabe stattfinden, wachsen die Kosten linear mit der Anzahl der Tokens, also O(n). Das Standardbudget für dieses Recipe ist wall_ms: 1500, peak_mb: 96. Da die Engine die Ausgabe streamt und kein DOM im Speicher hält, richtet sich der Speicher-Peak nach der Größe des Content-Stream-Puffers und des aktiven Stil-Stacks, nicht nach der Größe des gesamten Dokuments.

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

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

Hier erscheinen nur die mit Verified bewerteten Zeilen aus der geprüften CSS-Support-Matrix. „Verified“ bedeutet: eine Implementierung unter src/Html/ plus eine umfangreiche, eigene Fixture-Suite, die unter dem strukturellen Profil deterministisch besteht.

W3C-ModulLevelStatusNachweis
CSS Flexible Box Layout (css_flexbox_1)1Verifiedsrc/Html/Flex/, tests/Unit/Html/Flex/
CSS Grid Layout (css_grid_1)1Verifiedsrc/Html/Grid/, WPT-Korpus
CSS Cascading and Inheritance (css_cascade_3)3Verifiedsrc/Html/Cascade/, tests/Unit/Html/Cascade/
CSS Table (css_tables_3)3Verifiedsrc/Html/Table/, Tabellen-Fixtures + Golden-PDFs
CSS Fonts (css_fonts_4)4Verifiedsrc/Html/FontFace/, tests/Unit/Html/FontFace/

Eigenschaften wie text-align, text-indent und color sind in der Matrix als „Claimed“ bewertet (implementiert, jedoch ohne eigene Modul-Fixture). Deshalb sind sie hier absichtlich nicht als Verified aufgeführt.

Die HTML-Engine behält kein DOM vor. Der Zustand ist ein skalarer Cursor plus ein push/pop-Stil-Stack; Text-Knoten, die nur Whitespace enthalten, werden bei der Tokenisierung verworfen. Eine Folge daraus: Ein späteres Element kann ein früheres nicht nachträglich umgestalten, und Selektoren, die den Kontext des gesamten Baums benötigen (zum Beispiel komplexe :has()-Fälle), sind gemäß ADR-006 eingeschränkt. Planen Sie Layouts, die nur von der Dokumentreihenfolge abhängen.

Parsing, Layout und Paint sind getrennte Layer. Der Parser gibt keine rohen Paint-Operatoren aus, und der Layout-Dispatch parst kein CSS; ein Überschreiten dieser Grenzen erzeugt die Kopplungsschuld, die ADR-010 verbietet. Für Recipe-Autoren bedeutet das: Der öffentliche Einstiegspunkt ist writeHtml(); greifen Sie nicht in Parser-Interna ein.

Gemäß ADR-020 dürfen containerbezogene Formatierungskontexte (Flex, Tabelle) einen flüchtigen Teilbaum aufbauen; dieser ist auf 5,000 Knoten pro Kontext, 20 Ebenen Tiefe, eine Obergrenze von 50 MB aktivem Speicher über alle aktiven Kontexte und 10 Verschachtelungsebenen begrenzt. Außerhalb dieser Kontexte hält das Streaming-Modell keinen Baum vor. Halten Sie einzelne Tabellen und Flex-Container innerhalb der Knotengrenze, um einen vorhersehbaren Speicherverbrauch zu erreichen.

Behandeln Sie HTML-Eingaben als nicht vertrauenswürdig. NextPDF führt keine Skripte aus, und die Standard-External-Resource-Policy ruft keine beliebigen Remote-URLs ab; die Engine selbst ist daher konservativ. Validieren oder bereinigen Sie dennoch jedes HTML, das Sie aus Benutzereingaben zusammensetzen, bevor Sie es rendern. Auch die Element- und Verschachtelungsgrenzen schützen Sie: Sie begrenzen, wie viel Arbeit ein feindseliges oder fehlerhaftes Dokument verlangen kann.

AussageSpezifikationKlauselreference_id
CSS Text steuert die Umwandlung von Quelltext in formatierten, zeilenumgebrochenen Text.W3C CSS Text Level 3css_text_3#x1.x2.p4
Die Standardschrift für den Fließtext wird zu einer standardmäßigen Type-1-Schrift aufgelöst.ISO 32000-2iso32000_2_sec9#x1.x29

Dieses Recipe zeigt, wie NextPDF eine unterstützte Teilmenge von HTML und CSS rendert. Es behauptet keine vollständige HTML- oder vollständige CSS-Unterstützung; den verifizierten Status pro Modul finden Sie in der CSS-Support-Matrix.

Nicht zutreffend.