Potok renderowania HTML
W skrócie
Dział zatytułowany „W skrócie”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.
Instalacja
Dział zatytułowany „Instalacja”composer require nextpdf/core:^3Przegląd koncepcyjny
Dział zatytułowany „Przegląd koncepcyjny”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.
Powierzchnia API
Dział zatytułowany „Powierzchnia API”| Symbol | Lokalizacja | Etap |
|---|---|---|
Document::writeHtml(string $html): static | src/Core/Concerns/HasTextOutput.php | Publiczny punkt wejścia |
HtmlParser::parse(string $html): HtmlRenderResult | src/Html/HtmlParser.php | Koordynuje wszystkie etapy |
HtmlTokenizer::cleanHtml() / tokenize() | src/Html/HtmlTokenizer.php | Etap 3 |
HtmlChildScanner::scan() | src/Html/HtmlChildScanner.php | Mapy indeksów etapu 3 |
CssResolver::resolveHasSelectors() | src/Html/CssResolver.php | Etap 4 (warunkowy) |
HtmlRenderResult (stream, endX, endY, usedFontKeys) | src/Html/HtmlRenderResult.php | Etap 6 |
Przykład kodu — szybki start
Dział zatytułowany „Przykład kodu — szybki start”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');Przykład kodu — produkcja
Dział zatytułowany „Przykład kodu — produkcja”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);}Przypadki brzegowe i pułapki
Dział zatytułowany „Przypadki brzegowe i pułapki”@pagejest odczytywane przed tokenami. Reguła@pageumieszczona 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 funkcjicss.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.
Wydajność
Dział zatytułowany „Wydajność”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.
Uwagi dotyczące bezpieczeństwa
Dział zatytułowany „Uwagi dotyczące bezpieczeństwa”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.
Zgodność
Dział zatytułowany „Zgodność”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.
Kontekst komercyjny
Dział zatytułowany „Kontekst komercyjny”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.