HTML-Rendering-Pipeline
Auf einen Blick
Abschnitt betitelt „Auf einen Blick“writeHtml() läuft in einem einzigen Vorwärtsdurchlauf: Es tokenisiert, löst @page und Stile auf, berechnet das Layout und gibt PDF-Operatoren aus. Zwischen den Stufen wird kein Elementbaum aufbewahrt.
Installation
Abschnitt betitelt „Installation“composer require nextpdf/core:^3Konzeptioneller Überblick
Abschnitt betitelt „Konzeptioneller Überblick“Die HTML-Rendering-Pipeline wandelt HTML+CSS in einem einzigen Vorwärtsdurchlauf in PDF-Content-Stream-Operatoren um. Sie hält keinen Dokumentbaum vor. Die folgende Stufenabfolge entspricht HtmlParser::parse() in der Implementierung auf main.
Stufe 1 – Bereinigen und normalisieren. HtmlParser::parse() lehnt Eingaben über 10 MB ab, entfernt Steuerzeichen und normalisiert Zeilenenden: sowohl CRLF als auch ein einzelnes CR werden zu LF, gemäß der HTML-Zeilenendennormalisierung, an der sich die Quelle orientiert. Anschließend setzt die Methode jedes Instanzfeld zurück, sodass kein Zustand aus einem vorherigen Aufruf erhalten bleibt.
Stufe 2 – @page- und Stilblöcke extrahieren. Der Parser extrahiert zuerst die <style>-Blöcke und wendet dann die gefundenen @page-Regeln an, um die Seitengeometrie neu zu konfigurieren. Das geschieht, bevor irgendein Token verarbeitet wird, weil die Seitengröße jede spätere Layoutentscheidung beeinflusst.
Stufe 3 – Tokenisieren. HtmlTokenizer::cleanHtml() normalisiert Whitespace und bewahrt dabei den Inhalt von <pre>. Anschließend erzeugt tokenize() eine flache list<HtmlToken>. Das ist eine Tokenliste, kein Knotengraph. Texttoken, die nur aus Whitespace bestehen, werden sofort verworfen. HtmlChildScanner::scan() baut auf Basis der flachen Liste Index-Maps auf (Kindzahlen, Tagzahlen, Leerheit), sodass strukturelle Selektoren keinen Baum brauchen.
Stufe 4 – Optionaler :has()-Vorabscan. Wenn das experimentelle Feature css.has aktiviert ist, führt CssResolver::resolveHasSelectors() einen begrenzten Vorabscan über die Tokenliste aus, um den relationalen Selektor aufzulösen. Das ist eine dokumentierte, begrenzte Ausnahme von der Einzeldurchlaufregel.
Stufe 5 – Token verarbeiten (Stil, Layout, Paint). HtmlParser::processTokens() durchläuft die Tokenliste einmal. Für jedes Element löst die Methode die Kaskade auf (Layer-1-Applikatoren schreiben HtmlStyleState), berechnet die Geometrie (Layer-3-Layout) und gibt PDF-Operatoren aus (Layer-4-Paint). Die Stilvererbung nutzt einen Push-and-Pop-HtmlStyleState-Stack. Der Cursor (x, y, Ränder, Stream-Offset) wird über HtmlBlockCursor-Snapshots zwischen den Handlern weitergegeben.
Stufe 6 – Das Ergebnis zurückgeben. parse() gibt ein unveränderliches HtmlRenderResult zurück, das den ausgegebenen Content-Stream, die Endcursorposition und die verwendeten Font-Keys enthält. Der Aufrufer (writeHtml()) gibt den Cursor an das Seitenkoordinatensystem zurück.
Die Seite Layer-Verträge behandelt die Vier-Layer-Trennung, die innerhalb von Stufe 5 läuft. Die Seite Streaming-Beschränkungen behandelt den Betrieb ohne vorgehaltenen Baum und dessen Obergrenzen.
API-Oberfläche
Abschnitt betitelt „API-Oberfläche“| Symbol | Ort | Stufe |
|---|---|---|
Document::writeHtml(string $html): static | src/Core/Concerns/HasTextOutput.php | Öffentlicher Einstiegspunkt |
HtmlParser::parse(string $html): HtmlRenderResult | src/Html/HtmlParser.php | Orchestriert alle Stufen |
HtmlTokenizer::cleanHtml() / tokenize() | src/Html/HtmlTokenizer.php | Stufe 3 |
HtmlChildScanner::scan() | src/Html/HtmlChildScanner.php | Stufe-3-Index-Maps |
CssResolver::resolveHasSelectors() | src/Html/CssResolver.php | Stufe 4 (gated) |
HtmlRenderResult (stream, endX, endY, usedFontKeys) | src/Html/HtmlRenderResult.php | Stufe 6 |
Codebeispiel – Schnellstart
Abschnitt betitelt „Codebeispiel – Schnellstart“Stammt aus examples/08-html-basic.php.
<?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 style="color:#1E3A8A;">HTML Rendering</h1><p>One pass.</p>');$doc->save(__DIR__ . '/output/08-html-basic.pdf');Codebeispiel – Produktion
Abschnitt betitelt „Codebeispiel – Produktion“Rendern Sie einen gestylten Bericht mit einem eingebetteten <style>-Block. Die Pipeline extrahiert den Stilblock und wendet ihn an, bevor sie irgendein Token verarbeitet.
<?php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
use NextPDF\Core\Document;use NextPDF\Exception\HtmlParsingException;
function renderInvoice(string $bodyHtml, string $out): void{ $doc = Document::createStandalone(); $doc->setTitle('Invoice'); $doc->addPage();
$html = '<style>@page { margin: 20mm; } ' . 'h1 { color: #1E3A8A; } ' . 'table { width: 100%; }</style>' . $bodyHtml;
try { $doc->writeHtml($html); } catch (HtmlParsingException $e) { // Sanitize/cap failures surface here. Do not retry. throw $e; }
$doc->save($out);}Grenzfälle & Fallstricke
Abschnitt betitelt „Grenzfälle & Fallstricke“@pagewird vor den Token gelesen. Eine@page-Regel, die nach dem Inhalt steht, gilt trotzdem, weil die Stilextraktion der Tokenisierung vorausgeht. Die Seitengeometrie steht vor Stufe 5 fest.<pre>-Whitespace bleibt erhalten.cleanHtml()erhält den Inhalt von<pre>; Whitespace an anderen Stellen wird zusammengefasst.:has()ist gegated. Ohne das experimentelle Featurecss.hasläuft Stufe 4 nicht und:has()-Selektoren greifen nicht.- Ein Stream-Buffer. Die Pipeline schreibt in einen einzigen String-Buffer. Bereits geschriebener Inhalt wird nie verschoben. Es gibt kein Re-Layout.
- Obergrenzen greifen mitten im Durchlauf. Die Element- und Verschachtelungsobergrenzen werfen während Stufe 5, nicht davor. Ein Dokument kann daher mitten im Durchlauf fehlschlagen.
Performance
Abschnitt betitelt „Performance“Die Pipeline arbeitet beim Durchlauf in O(Token-Anzahl). Die Tabellenspaltendimensionierung fügt pro Tabelle einen begrenzten Zeilenscan hinzu (Stufe 5, TableParser). Der :has()-Vorabscan fügt einen begrenzten Durchlauf über die Tokenliste hinzu, wenn er aktiviert ist (Stufe 4). Der Speicherbedarf liegt bei O(Verschachtelungstiefe) für den Stil-Stack, nicht bei O(Element-Anzahl) – siehe Streaming-Beschränkungen. Der Performance-Benchmark der HTML-Render-Pipeline schützt vor Regressionen mit einem 5-%-Gate (zusammengeführte Arbeit, PR #564). Das performance_budget pro Seite (wall_ms: 1500, peak_mb: 64) ist die operative Obergrenze.
Sicherheitshinweise
Abschnitt betitelt „Sicherheitshinweise“Stufe 1 ist die erste Sicherheitsgrenze: die 10-MB-Eingabeobergrenze, das Entfernen von Steuerzeichen und die Zeilenendennormalisierung laufen vor der Tokenisierung. DefaultHtmlSecurityPolicy gated dann die erlaubten Tags, Attribute, CSS-Eigenschaften und URL-Schemata während Stufe 5. Siehe das Sicherheitsmodell des HTML-Moduls.
Konformität
Abschnitt betitelt „Konformität“Die Zeilenendennormalisierung folgt der Zeilenendenbehandlung des HTML-Standards (CRLF und ein einzelnes CR werden zu LF). Die CSS-Konformität pro Eigenschaft ist in der CSS-Support-Matrix dokumentiert, das Kaskadenverhalten auf css-resolver. Diese Seite wiederholt die Unterstützung pro Eigenschaft nicht.
Kommerzieller Kontext
Abschnitt betitelt „Kommerzieller Kontext“Enterprise-Fähigkeit. Premium erweitert die CSS-Abdeckung auf derselben Pipeline. Die sechsstufige Abfolge bleibt dadurch unverändert. Siehe die CSS-Support-Matrix.