Pipeline di rendering HTML
In breve
Sezione intitolata “In breve”writeHtml() procede con un solo passaggio in avanti: tokenizzazione, risoluzione di @page e degli stili, impaginazione e resa degli operatori PDF. Tra una fase e l’altra non viene conservato alcun albero di elementi.
Installazione
Sezione intitolata “Installazione”composer require nextpdf/core:^3Panoramica concettuale
Sezione intitolata “Panoramica concettuale”La pipeline di rendering HTML converte HTML+CSS in operatori del flusso di contenuti PDF con un solo passaggio in avanti. Non costruisce né mantiene in memoria un albero del documento. La sequenza di fasi seguente rispecchia HtmlParser::parse() così come implementato su main.
Fase 1 — Sanificazione e normalizzazione. HtmlParser::parse() rifiuta gli input superiori a 10 MB, rimuove i caratteri di controllo e normalizza i fine riga: sia CRLF sia CR isolato vengono convertiti in LF, secondo la normalizzazione dei fine riga HTML adottata dal sorgente. Quindi reimposta tutti i campi dell’istanza, così che nessuno stato sopravviva da una chiamata precedente.
Fase 2 — Estrazione di @page e dei blocchi di stile. Il parser estrae prima i blocchi <style> e quindi applica le regole @page rilevate per riconfigurare la geometria della pagina. Questo avviene prima dell’elaborazione di qualsiasi token, perché la dimensione della pagina influisce su ogni decisione di impaginazione successiva.
Fase 3 — Tokenizzazione. HtmlTokenizer::cleanHtml() normalizza gli spazi vuoti preservando il contenuto di <pre>. Quindi tokenize() produce un list<HtmlToken> piatto. È un elenco di token, non un grafo di nodi. I token di testo composti solo da spazi vuoti vengono scartati subito. HtmlChildScanner::scan() costruisce mappe di indici (conteggi dei figli, conteggi dei tag, vuotezza) sull’elenco piatto, così che i selettori strutturali non abbiano bisogno di un albero.
Fase 4 — Pre-scansione opzionale di :has(). Quando la funzionalità sperimentale css.has è abilitata, CssResolver::resolveHasSelectors() esegue una pre-scansione circoscritta sull’elenco di token per risolvere il selettore relazionale. Si tratta di un’eccezione documentata e limitata alla regola del passaggio singolo.
Fase 5 — Elaborazione dei token (stile, impaginazione, resa). HtmlParser::processTokens() attraversa l’elenco di token una sola volta. Per ogni elemento risolve la cascata (gli applicatori del Layer 1 scrivono HtmlStyleState), calcola la geometria (impaginazione del Layer 3) ed emette gli operatori PDF (resa del Layer 4). L’ereditarietà degli stili utilizza uno stack HtmlStyleState con push e pop. Il cursore (x, y, margini, offset del flusso) passa tra gli handler tramite istantanee di HtmlBlockCursor.
Fase 6 — Restituzione del risultato. parse() restituisce un HtmlRenderResult immutabile che contiene il flusso di contenuti emesso, la posizione finale del cursore e le chiavi dei font utilizzati. Il chiamante (writeHtml()) riporta il cursore al sistema di coordinate della pagina.
La pagina contratti dei layer descrive la separazione in quattro layer operativa all’interno della Fase 5. La pagina vincoli di streaming descrive l’assenza di un albero conservato e i relativi limiti.
Superficie API
Sezione intitolata “Superficie API”| Simbolo | Posizione | Fase |
|---|---|---|
Document::writeHtml(string $html): static | src/Core/Concerns/HasTextOutput.php | Punto di ingresso pubblico |
HtmlParser::parse(string $html): HtmlRenderResult | src/Html/HtmlParser.php | Coordina tutte le fasi |
HtmlTokenizer::cleanHtml() / tokenize() | src/Html/HtmlTokenizer.php | Fase 3 |
HtmlChildScanner::scan() | src/Html/HtmlChildScanner.php | Mappe degli indici della Fase 3 |
CssResolver::resolveHasSelectors() | src/Html/CssResolver.php | Fase 4 (subordinata a feature flag) |
HtmlRenderResult (stream, endX, endY, usedFontKeys) | src/Html/HtmlRenderResult.php | Fase 6 |
Esempio di codice — Avvio rapido
Sezione intitolata “Esempio di codice — Avvio rapido”Tratto da 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');Esempio di codice — Produzione
Sezione intitolata “Esempio di codice — Produzione”Esegue il rendering di un report con stili e un blocco <style> incorporato. La pipeline estrae e applica il blocco di stile prima di elaborare qualunque token.
<?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);}Casi limite e insidie
Sezione intitolata “Casi limite e insidie”@pageviene letto prima dei token. Una regola@pagecollocata dopo il contenuto si applica comunque, perché l’estrazione degli stili precede la tokenizzazione. La geometria della pagina viene fissata prima della Fase 5.- Gli spazi vuoti in
<pre>vengono preservati.cleanHtml()protegge il contenuto di<pre>; altrove gli spazi vuoti vengono compressi. :has()è subordinato a un feature flag. Senza la funzionalità sperimentalecss.has, la Fase 4 non viene eseguita e i selettori:has()non trovano corrispondenze.- Un solo buffer di flusso. La pipeline scrive in un solo buffer di stringa. Il contenuto già scritto non viene mai spostato. Non viene effettuata alcuna re-impaginazione.
- I limiti si applicano a metà passaggio. I limiti sul numero di elementi e sull’annidamento generano un’eccezione durante la Fase 5, non prima. Un documento può fallire a metà elaborazione.
Prestazioni
Sezione intitolata “Prestazioni”La pipeline ha complessità O(numero di token) per l’attraversamento. Il dimensionamento delle colonne delle tabelle aggiunge, per ogni tabella, una scansione limitata delle righe (Fase 5, TableParser). La pre-scansione :has() aggiunge un passaggio limitato sull’elenco di token quando è abilitata (Fase 4). La memoria è O(profondità di annidamento) per lo stack degli stili, non O(numero di elementi) — vedere vincoli di streaming. Il benchmark prestazionale della pipeline di rendering HTML previene le regressioni con una soglia del 5% (lavoro integrato, PR #564). Il performance_budget per pagina (wall_ms: 1500, peak_mb: 64) è il limite operativo.
Note sulla sicurezza
Sezione intitolata “Note sulla sicurezza”La Fase 1 è il primo confine di sicurezza: il limite di input di 10 MB, la rimozione dei caratteri di controllo e la normalizzazione dei fine riga vengono eseguiti prima della tokenizzazione. DefaultHtmlSecurityPolicy controlla poi tag, attributi, proprietà CSS e schemi di URL consentiti durante la Fase 5. Vedere il modello di sicurezza del modulo HTML.
Conformità
Sezione intitolata “Conformità”La normalizzazione dei fine riga segue la gestione dei fine riga dello standard HTML (CRLF e CR isolato diventano LF). La conformità CSS per singola proprietà è documentata nella matrice di supporto CSS e il comportamento della cascata in css-resolver. Questa pagina non ripete il supporto per singola proprietà.
Contesto commerciale
Sezione intitolata “Contesto commerciale”Funzionalità Enterprise. Premium amplia la copertura CSS su questa stessa pipeline. La sequenza di sei fasi non cambia tra le edizioni. Consultare la matrice di supporto CSS.