Przejdź do głównej zawartości

Ograniczenia jednoprzebiegowego strumieniowania HTML (ADR-001)

NextPDF renderuje HyperText Markup Language (HTML) w jednym przebiegu do przodu i nie przechowuje drzewa elementów w pamięci. ADR-001 dokumentuje ten wybór oraz ograniczenia, które nakłada on na każdą funkcję Cascading Style Sheets (CSS).

Okno terminala
composer require nextpdf/core:^3

Podsystem HTML renderuje HTML i CSS do Portable Document Format (PDF) w jednym przebiegu strumieniowym. ADR-001 („Stream-based Rendering Pipeline Retention”, przyjęta 2026-04-06) to decyzja architektoniczna definiująca ten model. Ta strona objaśnia model, jego granice oraz ograniczenia, których muszą przestrzegać współautorzy.

W tym modelu tokenizer (HtmlTokenizer) odczytuje dane wejściowe jednokrotnie i tworzy płaską listę tokenów. HtmlParser::processTokens() przechodzi tę listę od lewej do prawej i w miarę docierania do kolejnych elementów zapisuje operatory strumienia treści PDF do bufora tekstowego. Silnik nie buduje trwałego grafu elementów między wywołaniami. Każdy stan, który musi przetrwać wywołanie procedury obsługi, jest przekazywany przez obiekt wartości z migawką (HtmlBlockCursor), a nie przez współdzielone węzły. Dziedziczenie stylów korzysta ze stosu typu push-and-pop płaskich instancji HtmlStyleState, a nie z drzewa ze wskaźnikami do rodzica.

Nie jest to model z zachowywaniem dokumentu. Silnik nie przechowuje drzewa dokumentu, nie układa ponownie treści, którą już zapisał, ani nie pozwala zmieniać danych wejściowych po rozpoczęciu analizy. Granica jest prosta: NextPDF strumieniuje od początku do końca. Renderer z zachowywaniem najpierw buduje cały dokument w pamięci; NextPDF tego nie robi.

Dwie operacje wymagają ograniczonego odczytu z wyprzedzeniem. Obie są jawnymi, ograniczonymi wyjątkami. Dobór rozmiaru kolumn tabeli skanuje każdy wiersz przed umieszczeniem komórki. Wiersze te trafiają do efemerycznego bufora tabeli wewnątrz TableParser — to wyjątek, który ADR-001 wymienia z nazwy. Selektor relacyjny :has() oraz selektory :last-child i :last-of-type korzystają z ograniczonego wstępnego skanowania płaskiej listy tokenów, a nie z przechodzenia drzewa. ADR-001 dokumentuje oba wyjątki i ich granice.

Model jest bezpieczny dla procesów roboczych. HtmlParser jest tworzony raz na żądanie, nigdy jako singleton. HtmlParser::parse() resetuje każde pole na początku każdego wywołania. W ścieżce renderowania nie ma statycznego, modyfikowalnego stanu, dzięki czemu RoadRunner, Swoole i Laravel Octane mogą ponownie wykorzystywać proces bez przeciekania stanu między dokumentami.

Poniższe symbole wymuszają te ograniczenia. Zweryfikuj każdy z nich względem src/Html/.

SymbolLokalizacjaRola
HtmlParser::parse(string $html): HtmlRenderResultsrc/Html/HtmlParser.phpPunkt wejścia. Resetuje cały stan, a następnie wykonuje pojedynczy przebieg.
HtmlParser::MAX_ELEMENT_COUNT (50_000)src/Html/HtmlParser.phpTwardy limit liczby przetwarzanych elementów.
HtmlParser::MAX_NESTING_DEPTH (100)src/Html/HtmlParser.phpTwardy limit głębokości zagnieżdżenia.
HtmlBlockCursorsrc/Html/HtmlBlockCursor.phpMigawka kursora. Jedyny mechanizm współdzielenia stanu.
HtmlStyleStatesrc/Html/HtmlStyleState.phpRamka stylu wkładana na stos. Brak wskaźnika do rodzica.
TableParser::reset()src/Html/TableParser.phpObowiązkowy reset efemerycznego bufora tabeli pomiędzy tabelami.

Modelem strumieniowania nie zarządza się bezpośrednio. Jedno wywołanie renderuje dowolny obsługiwany dokument.

<?php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
use NextPDF\Core\Document;
$doc = Document::createStandalone();
$doc->setTitle('Streaming render');
$doc->addPage();
$doc->writeHtml('<h1>One forward pass</h1><p>No retained tree.</p>');
$doc->save(__DIR__ . '/output/streaming.pdf');

Duży dokument renderuj w ramach stałego budżetu pamięci. Limit liczby elementów jest granicą bezpieczeństwa, więc oszacuj rozmiar danych wejściowych przed wywołaniem.

<?php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
use NextPDF\Core\Document;
use NextPDF\Exception\HtmlParsingException;
/**
* Render trusted HTML, surfacing the streaming-model limits as typed errors.
*
* @param non-empty-string $html
*/
function renderReport(string $html, string $out): void
{
$doc = Document::createStandalone();
$doc->addPage();
try {
$doc->writeHtml($html);
} catch (HtmlParsingException $e) {
// Thrown on the 10 MB input cap, the 50,000-element cap,
// or the 100-level nesting cap. These are model boundaries,
// not transient faults — do not retry.
throw $e;
}
$doc->save($out);
}
  • Limit liczby elementów to twarda granica. Silnik zgłasza HtmlParsingException przy MAX_ELEMENT_COUNT = 50_000. Bardzo duże raporty rozdziel na wiele wywołań writeHtml() albo na wiele dokumentów.
  • Limit zagnieżdżenia to twarda granica. Głębokość powyżej MAX_NESTING_DEPTH = 100 powoduje zgłoszenie wyjątku. Zwykle odpowiadają za to głęboko zagnieżdżone elementy opakowujące.
  • Limit rozmiaru danych wejściowych. HtmlParser::parse() odrzuca przed tokenizacją dane wejściowe większe niż 10 MB.
  • :has() jest objęty bramką. Wstępne skanowanie :has() jest wykonywane tylko wtedy, gdy aktywna jest eksperymentalna funkcja css.has. Bez niej selektory :has() nie dopasowują elementów.
  • Buforowanie tabeli to jedyne efemeryczne drzewo. Pojedyncza bardzo szeroka lub bardzo wysoka tabela przechowuje swoje wiersze w pamięci aż do render(). TableParser ogranicza ten bufor osobno dla każdej tabeli i resetuje go między tabelami; nie jest to drzewo obejmujące cały dokument.
  • Brak ponownego układania. Treść, która została już zapisana, nigdy nie jest przenoszona. Późniejszy styl nie może wstecznie zmienić wcześniejszego wyniku.

Model strumieniowania przechowuje najwyżej jeden HtmlStyleState na poziom zagnieżdżenia, ograniczony przez MAX_NESTING_DEPTH = 100, oraz aktywne pola kursora. Pamięć stanu stylów i kursora ma złożoność O(głębokość), a nie O(liczba elementów). ADR-001 dokumentuje intencję projektową: dla tych samych danych wejściowych zużycie pozostaje znacznie niższe niż w przypadku zachowywanego grafu obiektów. Kontrolowany test wydajności szczytowego rozmiaru zbioru rezydentnego (resident set size, RSS) dla 50,000 elementów to docelowy punkt walidacji empirycznej wskazany w ADR-001. Test wydajności potoku renderowania HTML śledzi go z bramką regresji na poziomie 5% (praca scalona, PR #564). Traktuj przypadający na stronę performance_budget (wall_ms: 1500, peak_mb: 64) jako pułap operacyjny.

Limity opisane na tej stronie pełnią również funkcję mechanizmów kontroli odmowy usługi (denial-of-service). DefaultHtmlSecurityPolicy wymusza pułap 10 MB dla danych wejściowych oraz pułap 100 poziomów zagnieżdżenia niezależnie od parsera, dzięki czemu wrogi dokument nie może wyczerpać pamięci przez głębokość ani rozmiar. Sam model strumieniowania z założenia ogranicza zużycie pamięci: nie istnieje graf elementów, który atakujący mógłby rozdąć. Zobacz model bezpieczeństwa modułu HTML oraz kontrakty warstw, aby poznać pełną powierzchnię polityki.

Ta strona nie powołuje się na żaden zewnętrzny standard. Ograniczenia wynikają z ADR-001 oraz z wymuszających je symboli źródłowych wymienionych w sekcji „Powierzchnia API”. Behawioralne mapowania specyfikacji CSS są udokumentowane na stronie css-resolver, a nie tutaj.

Funkcja dla przedsiębiorstw. Architektura strumieniowania jest identyczna w Core i Premium. Premium poszerza zakres obsługi CSS; nie zmienia modelu jednoprzebiegowego ani nie łagodzi tych limitów. Zobacz macierz obsługi CSS.