Przejdź do głównej zawartości

Kontrakty warstwowe silnika HTML (ADR-010)

Podsystem Hypertext Markup Language (HTML) dzieli analizę składniową arkuszy Cascading Style Sheets (CSS), stan stylów, układ i malowanie na cztery warstwy. Dane przepływają przez te warstwy tylko w jednym kierunku. Architecture Decision Record 010 (ADR-010) wyznacza granice oraz reguły rozszerzania.

Okno terminala
composer require nextpdf/core:^3

Architecture Decision Record 010 (ADR-010) („Engine Layer Contracts, Hot Path Ownership, and Extension Rules”, przyjęty 2026-04-12) formalizuje podział podsystemu HTML na warstwy. Podstawowy kontrakt renderowania obejmuje cztery warstwy: analizę składniową CSS i aplikatory, stan stylów, układ i formatowanie oraz malowanie. ADR-010 dokumentuje również dwie dodatkowe warstwy: nośnik stronicowany i zestaw narzędzi pomiarowych. Otaczają one czterowarstwowy rdzeń, nie zmieniając przepływu danych w jego obrębie. Kanoniczny termin słownikowy dla tego rdzenia to „potok HTML”, czyli czterowarstwowy potok.

Dane przepływają jednokierunkowo. Tekst CSS jest przekształcany w wartości typowane w warstwie 1. Warstwa 1 zapisuje te wartości do pól HtmlStyleState w warstwie 2. Warstwa 3 odczytuje pola stanu stylów i oblicza geometrię. Warstwa 4 odczytuje niezmienną migawkę ComputedStyle wraz z geometrią i emituje operatory Portable Document Format (PDF). Żadna warstwa nie odczytuje danych z warstwy późniejszej.

Podział na cztery warstwy ma znaczenie nie tylko dokumentacyjne. ADR-010 odnotowuje dwie ograniczone refaktoryzacje zastosowane w wersji v1.2.0, które przeniosły kod do właściwej warstwy. PageBorderPainter został wyodrębniony z HtmlParser, więc operatory malowania nie znajdują się już w orkiestratorze. Blok dokumentacyjny klasy HtmlStyleState zawiera teraz formalny kontrakt warstw i określa, które pola każda warstwa może zapisywać lub odczytywać.

Jedno naruszenie granicy jest jawnie opisane. FormattingContextFactory::startTable() nadal odczytuje bezpośrednio pięć surowych kluczy CSS. ADR-010 odnotowuje to jako znany, odroczony dług techniczny dla przyszłego TableApplicator, a nie jako zamierzony kontrakt. Udokumentowanie tego wyjątku jest częścią kontraktu.

WarstwaPliki (reprezentatywne)ZapisujeOdczytujeNie może
1 — analiza składniowa CSS i aplikatoryCssValueParser, CssResolver, HtmlCssApplicator, src/Html/Applicator/*Pola CSS w HtmlStyleStateSurowy tekst CSSObliczać geometrii; emitować operatorów
2 — stan stylówHtmlStyleState, State/ComputedStyle, State/LayoutState— (pasywny zbiór wartości)Analizować składniowo CSS; decydować o układzie; emitować operatorów
3 — układ i formatowanieFormattingContextFactory, HtmlBlockHandler, FlexLayoutEngine, TableParser, FloatContextGeometria kursoraPola HtmlStyleStateOdczytywać surowych $css[...]; emitować operatorów malowania
4 — malowanie i renderowanieBorderRenderer, BackgroundImageRenderer, src/Html/Paint/*, src/Html/Gradient/*Strumień operatorów PDFComputedStyle (niezmienny) + geometriaObliczać geometrii; analizować składniowo CSS; decydować o podziałach stron
WarstwaPliki (reprezentatywne)Rola
5 — nośnik stronicowanyPageBreakController, PageBorderPainter, PageRule, PageRuleParser, ParserConfiguratorRozwiązuje reguły @page; ocenia ograniczenia podziału oraz orphan/widow; przekazuje dekorację strony do warstwy malowania.
6 — pomiary i zestaw narzędziSkrypty klasyfikatora Web Platform Tests (WPT), tests/Support/*Klasyfikuje wyniki testów; tworzy migawki regresji; udostępnia funkcje pomocnicze do asercji. Nie zawiera logiki renderowania.

Kontrakt jest wymuszany przez rozmieszczenie klas oraz blok dokumentacyjny HtmlStyleState. Można go zweryfikować względem src/Html/.

SymbolWarstwaRola w kontrakcie
PropertyApplicatorInterface1Interfejs strategii; jedyne miejsce zapisujące pola obliczone z CSS.
ParserConfigurator::buildCssApplicator()1 (montaż)Rejestruje każdy aplikator. Nową właściwość CSS rejestruje się tutaj.
HtmlStyleState2Zbiór z dwiema grupami pól; blok dokumentacyjny klasy wskazuje warstwę będącą właścicielem każdego pola.
HtmlStyleState::toComputedStyle()2Tworzy niezmienny ComputedStyle dla warstwy malowania.
FormattingContextFactory::dispatchOpenTag()3Pojedynczy punkt routingu dla nowych zachowań układu.
PageBorderPainter::buildStream()4Dekoracja strony; wywoływana z warstwy 5, nie wbudowana w HtmlParser.

Nie sięga się bezpośrednio do warstw. Czterowarstwowy przepływ odbywa się w ramach jednego wywołania.

<?php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
use NextPDF\Core\Document;
$doc = Document::createStandalone();
$doc->addPage();
$doc->writeHtml('<p style="color:#1E3A8A;border:1px solid #999;">Layered render.</p>');
$doc->save(__DIR__ . '/output/layers.pdf');

Kontrakt ma znaczenie podczas współtworzenia kodu, nie przy samym wywoływaniu biblioteki. Aby dodać właściwość CSS, użyj punktu rozszerzenia warstwy 1: utwórz aplikator, dodaj typowane pole HtmlStyleState z blokiem dokumentacyjnym warstwy i zarejestruj aplikator w ParserConfigurator. Poniższa ilustracja pokazuje kształt kontraktu aplikatora. Dla konkretnej klasy użyj jako wzorca src/Html/Applicator/.

<?php
declare(strict_types=1);
// Layer 1 extension contract (see ADR-010 §C "New CSS property").
// A new property group ships as a PropertyApplicatorInterface
// implementation registered in ParserConfigurator::buildCssApplicator().
// It writes a typed HtmlStyleState field and never computes geometry
// or emits PDF operators — those belong to Layers 3 and 4.
  • FormattingContextFactory::startTable() odczytuje surowy CSS. To jedyny udokumentowany wyjątek od kontraktu, odroczony do przyszłego TableApplicator. Nie powielaj tego wzorca.
  • Sześć warstw, czterowarstwowy rdzeń. ADR-010 numeruje sześć warstw. Kontrakt przepływu danych dotyczy czterowarstwowego rdzenia; nośnik stronicowany i pomiary są dodatkami.
  • HtmlStyleState ma dwie grupy pól. Zawiera pola obliczone z CSS oraz pola śledzenia układu. Tylko aplikatory zapisują grupę CSS. Warstwa malowania odczytuje ComputedStyle, nigdy pól śledzenia układu.
  • HtmlParser nie należy do żadnej warstwy. Jest orkiestratorem. Analiza składniowa CSS, obliczenia geometrii i emisja operatorów malowania nie mogą się w nim znajdować.

Kontrakt warstw jest strukturalny, więc nie zwiększa kosztu wykonania. HtmlStyleState::toComputedStyle() tworzy jedną niezmienną migawkę dla każdego elementu wymagającego malowania. Ta migawka pozwala kodowi malowania uniknąć modyfikowalnego zbioru stanu. Koszt renderowania jest regulowany przez model strumieniowy, a nie przez podział na warstwy. Budżet performance_budget dla pojedynczej strony (wall_ms: 1500, peak_mb: 64) pozostaje pułapem operacyjnym.

Rozdzielenie warstw wspiera model bezpieczeństwa. Warstwa 1 analizuje składniowo i filtruje wartości CSS zgodnie z polityką, zanim trafią do kodu układu lub malowania, więc DefaultHtmlSecurityPolicy::isCssPropertyAllowed() pozostaje jedyną bramką. Warstwa malowania nigdy nie odczytuje surowego CSS kontrolowanego przez atakującego. Zobacz model bezpieczeństwa modułu HTML.

Ta strona nie cytuje żadnego zewnętrznego standardu. Granice warstw pochodzą z ADR-010 oraz bloku dokumentacyjnego klasy HtmlStyleState, który utrwala kontrakt w kodzie źródłowym. Zgodność CSS na poziomie zachowania jest udokumentowana na stronie css-resolver.

Funkcja dla przedsiębiorstw. Funkcje CSS w wersji Premium korzystają z tych samych czterech warstw przez udokumentowane punkty rozszerzeń. Nie istnieje osobny potok Premium. Zobacz macierz obsługi CSS.