Salta ai contenuti

Pipeline di rendering HTML

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.

Terminal window
composer require nextpdf/core:^3

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.

SimboloPosizioneFase
Document::writeHtml(string $html): staticsrc/Core/Concerns/HasTextOutput.phpPunto di ingresso pubblico
HtmlParser::parse(string $html): HtmlRenderResultsrc/Html/HtmlParser.phpCoordina tutte le fasi
HtmlTokenizer::cleanHtml() / tokenize()src/Html/HtmlTokenizer.phpFase 3
HtmlChildScanner::scan()src/Html/HtmlChildScanner.phpMappe degli indici della Fase 3
CssResolver::resolveHasSelectors()src/Html/CssResolver.phpFase 4 (subordinata a feature flag)
HtmlRenderResult (stream, endX, endY, usedFontKeys)src/Html/HtmlRenderResult.phpFase 6

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');

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);
}
  • @page viene letto prima dei token. Una regola @page collocata 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à sperimentale css.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.

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.

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.

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à.

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.