Przejdź do głównej zawartości

Potok renderowania HTML

Po wywołaniu writeHtml() wykonywany jest jeden przebieg w przód po HyperText Markup Language (HTML): tokenizacja danych wejściowych, rozwiązywanie reguł @page i stylów, rozmieszczanie treści oraz malowanie operatorów formatu Portable Document Format (PDF). Drzewo elementów nie jest zachowywane między etapami.

Okno terminala
composer require nextpdf/core:^3

Potok renderowania HTML konwertuje HTML+CSS, czyli HTML wraz z Cascading Style Sheets (CSS), na operatory strumienia treści PDF w jednym przebiegu w przód. Nie tworzy zachowywanego drzewa dokumentu. Poniższe etapy odzwierciedlają działanie HtmlParser::parse() w gałęzi main.

Etap 1 — sanityzacja i normalizacja. HtmlParser::parse() odrzuca dane wejściowe większe niż 10 MB, usuwa znaki sterujące i normalizuje końce wierszy: zarówno CRLF, jak i samo CR stają się LF, zgodnie ze źródłową normalizacją końców wierszy HTML. Następnie parser resetuje każde pole instancji, więc stan z wcześniejszego wywołania nie może zostać przeniesiony.

Etap 2 — wyodrębnienie reguł @page i bloków stylów. Parser najpierw wyodrębnia bloki <style>, a następnie stosuje wykryte reguły @page, aby zmienić geometrię strony. Odbywa się to przed przetworzeniem jakiegokolwiek tokenu, ponieważ rozmiar strony wpływa na każdą późniejszą decyzję o układzie.

Etap 3 — tokenizacja. HtmlTokenizer::cleanHtml() normalizuje białe znaki, zachowując zawartość <pre>. Następnie tokenize() tworzy płaską list<HtmlToken>. Jest to lista tokenów, a nie graf węzłów. Potok natychmiast odrzuca tokeny tekstowe zawierające wyłącznie białe znaki. HtmlChildScanner::scan() buduje mapy indeksów (liczbę elementów potomnych, liczbę znaczników i informację o pustości) na płaskiej liście, dzięki czemu selektory strukturalne nie wymagają drzewa.

Etap 4 — opcjonalny wstępny skan :has(). Po włączeniu eksperymentalnej funkcji css.has CssResolver::resolveHasSelectors() wykonuje jeden ograniczony wstępny skan listy tokenów, aby rozwiązać selektor relacyjny. Ten udokumentowany i ograniczony krok jest wyjątkiem od reguły pojedynczego przebiegu.

Etap 5 — przetwarzanie tokenów (styl, układ, malowanie). HtmlParser::processTokens() przechodzi przez listę tokenów tylko raz. Dla każdego elementu rozwiązuje kaskadę (aplikatory warstwy 1 zapisują HtmlStyleState), oblicza geometrię (układ warstwy 3) i emituje operatory PDF (malowanie warstwy 4). Dziedziczenie stylów wykorzystuje stos HtmlStyleState z operacjami odkładania i zdejmowania. Kursor (x, y, marginesy, przesunięcie w strumieniu) jest przekazywany między procedurami obsługi za pośrednictwem migawek HtmlBlockCursor.

Etap 6 — zwrócenie wyniku. parse() zwraca niemutowalny HtmlRenderResult z wyemitowanym strumieniem treści, końcową pozycją kursora oraz wykorzystanymi kluczami czcionek. Wywołujący (writeHtml()) przekazuje kursor z powrotem do układu współrzędnych strony.

Opis czterowarstwowego podziału wewnątrz etapu 5 znajduje się na stronie kontrakty warstw. Właściwość braku zachowywanego drzewa oraz związane z nią limity opisuje strona ograniczenia strumieniowania.

SymbolLokalizacjaEtap
Document::writeHtml(string $html): staticsrc/Core/Concerns/HasTextOutput.phpPubliczny punkt wejścia
HtmlParser::parse(string $html): HtmlRenderResultsrc/Html/HtmlParser.phpKoordynuje wszystkie etapy
HtmlTokenizer::cleanHtml() / tokenize()src/Html/HtmlTokenizer.phpEtap 3
HtmlChildScanner::scan()src/Html/HtmlChildScanner.phpMapy indeksów etapu 3
CssResolver::resolveHasSelectors()src/Html/CssResolver.phpEtap 4 (warunkowy)
HtmlRenderResult (stream, endX, endY, usedFontKeys)src/Html/HtmlRenderResult.phpEtap 6

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

Wyrenderuj sformatowany raport z osadzonym blokiem <style>. Potok wyodrębnia blok stylów i stosuje go przed przetworzeniem jakiegokolwiek tokenu.

<?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 jest odczytywane przed tokenami. Reguła @page umieszczona po treści nadal obowiązuje, ponieważ wyodrębnianie stylów poprzedza tokenizację. Geometria strony jest ustalana przed etapem 5.
  • Białe znaki w <pre> są zachowywane. cleanHtml() chroni zawartość <pre>; w pozostałych miejscach potok scala białe znaki.
  • :has() jest warunkowe. Jeśli nie włączysz eksperymentalnej funkcji css.has, etap 4 nie jest wykonywany, a selektory :has() nie są dopasowywane.
  • Jeden bufor strumienia. Potok zapisuje do jednego bufora ciągu znaków. Nigdy nie przenosi wcześniej zapisanej treści. Układ nie jest rozmieszczany ponownie.
  • Limity obowiązują w trakcie przebiegu. Limity liczby elementów i głębokości zagnieżdżenia zgłaszają wyjątek podczas etapu 5, a nie wcześniej. Przetwarzanie dokumentu może zakończyć się niepowodzeniem w trakcie przetwarzania.

Potok działa w czasie O(liczba tokenów). Wyznaczanie szerokości kolumn tabeli dodaje ograniczony skan wierszy w obrębie każdej tabeli (etap 5, TableParser). Po włączeniu wstępny skan :has() dodaje jeden ograniczony przebieg listy tokenów (etap 4). Pamięć dla stosu stylów wynosi O(głębokość zagnieżdżenia), a nie O(liczba elementów); zobacz ograniczenia strumieniowania. Test wydajności potoku renderowania HTML chroni przed regresjami za pomocą bramki 5% (scalona praca, PR #564). Przypisany do strony performance_budget (wall_ms: 1500, peak_mb: 64) wyznacza operacyjny pułap.

Etap 1 stanowi pierwszą granicę bezpieczeństwa: limit 10 MB dla danych wejściowych, usuwanie znaków sterujących oraz normalizacja końców wierszy są wykonywane przed tokenizacją. Podczas etapu 5 DefaultHtmlSecurityPolicy kontroluje dozwolone znaczniki, atrybuty, właściwości CSS oraz schematy adresów URL. Zobacz model bezpieczeństwa modułu HTML.

Normalizacja końców wierszy jest zgodna z obsługą końców wierszy w standardzie HTML: CRLF i samo CR stają się LF. Zgodność CSS dla poszczególnych właściwości jest udokumentowana w macierzy obsługi CSS, a zachowanie kaskady jest udokumentowane na stronie css-resolver. Ta strona nie powtarza informacji o obsłudze poszczególnych właściwości.

Funkcja Enterprise. Premium rozszerza zakres obsługi CSS w tym samym potoku. Sekwencja sześciu etapów nie zmienia się między edycjami. Zobacz macierz obsługi CSS.