HTML 引擎分層契約(ADR-010)
HTML 子系統將 CSS 剖析、樣式狀態、版面配置與繪製拆成四層,各層之間的契約以單向資料流銜接。ADR-010 訂定這些邊界與擴充規則。
composer require nextpdf/core:^3概念總覽
標題為「概念總覽」的區段ADR-010(〈引擎分層契約、熱路徑歸屬與擴充規則〉,於 2026-04-12 通過)正式定義 HTML 子系統的分層方式。核心繪製契約有四層:CSS 剖析與套用器、樣式狀態、版面配置與排版,以及繪製。ADR-010 也記載了兩個輔助層——分頁媒體與量測載具——它們包覆四層核心,但不改變資料流向。核心標準術語表詞彙是「HTML pipeline」,也就是一條四層管線。
資料只朝單一方向流動。CSS 文字在 Layer 1 轉成具型別的值。Layer 1 會把這些值寫入 Layer 2 的 HtmlStyleState 欄位。Layer 3 讀取樣式狀態欄位並計算幾何。Layer 4 讀取不可變的 ComputedStyle 快照與幾何資訊,並輸出 PDF 運算子。任何一層都不會讀取排在它後面的層。
這種四層分離不只是文件上的說法。ADR-010 記錄了在 v1.2.0 套用的兩次有界重構,把程式碼搬到正確的層。PageBorderPainter 從 HtmlParser 抽離出來,讓繪製運算子不再留在協調器裡。HtmlStyleState 類別的 docblock 現在帶有正式的分層契約,載明每一層可以寫入或讀取哪些欄位。
有一道邊界是明確寫出來、而非藏起來的。FormattingContextFactory::startTable() 仍直接讀取五個原始 CSS 鍵。ADR-010 把這點記為已知且延後處理的技術債,留給未來的 TableApplicator,並未把它當作預期中的契約。把這項例外記錄下來,本身就是契約的一部分。
四個核心層
標題為「四個核心層」的區段| 層 | 檔案(代表性) | 寫入 | 讀取 | 不可 |
|---|---|---|---|---|
| 1 — CSS 剖析與套用器 | CssValueParser、CssResolver、HtmlCssApplicator、src/Html/Applicator/* | HtmlStyleState 的 CSS 欄位 | 原始 CSS 文字 | 計算幾何;輸出運算子 |
| 2 — 樣式狀態 | HtmlStyleState、State/ComputedStyle、State/LayoutState | ——(被動的值容器) | — | 剖析 CSS;決定版面配置;輸出運算子 |
| 3 — 版面配置與排版 | FormattingContextFactory、HtmlBlockHandler、FlexLayoutEngine、TableParser、FloatContext | 游標幾何 | HtmlStyleState 欄位 | 讀取原始 $css[...];輸出繪製運算子 |
| 4 — 繪製與算繪 | BorderRenderer、BackgroundImageRenderer、src/Html/Paint/*、src/Html/Gradient/* | PDF 運算子串流 | ComputedStyle(不可變)+ 幾何 | 計算幾何;剖析 CSS;決定分頁 |
兩個輔助層
標題為「兩個輔助層」的區段| 層 | 檔案(代表性) | 角色 |
|---|---|---|
| 5 — 分頁媒體 | PageBreakController、PageBorderPainter、PageRule、PageRuleParser、ParserConfigurator | resolve(解析)@page 規則;評估分頁與 orphan/widow 限制;將頁面裝飾委派給繪製層。 |
| 6 — 量測與載具 | WPT 分類器指令稿、tests/Support/* | 分類測試結果;產生回歸快照;提供斷言輔助工具。不帶任何繪製邏輯。 |
API 介面
標題為「API 介面」的區段這份契約透過類別放置位置以及 HtmlStyleState 的 docblock 落實。請對照 src/Html/ 查核。
| 符號 | 層 | 契約角色 |
|---|---|---|
PropertyApplicatorInterface | 1 | 策略介面;唯一寫入 CSS 計算欄位的地方。 |
ParserConfigurator::buildCssApplicator() | 1(接線) | 註冊每一個套用器。新的 CSS 屬性也在這裡註冊。 |
HtmlStyleState | 2 | 雙群組容器;類別的 docblock 載明每個欄位歸屬的層。 |
HtmlStyleState::toComputedStyle() | 2 | 為繪製層產生不可變的 ComputedStyle。 |
FormattingContextFactory::dispatchOpenTag() | 3 | 新版面配置行為的單一路由點。 |
PageBorderPainter::buildStream() | 4 | 頁面裝飾;由 Layer 5 呼叫,而非內嵌在 HtmlParser 中。 |
程式碼範例——快速開始
標題為「程式碼範例——快速開始」的區段呼叫端永遠不會接觸這些層。四層流程會在單一呼叫內完成。
<?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');程式碼範例——正式環境
標題為「程式碼範例——正式環境」的區段這份契約對貢獻者很重要,對呼叫端則不然。要新增一個 CSS 屬性,請依循 Layer 1 的擴充點:建立一個套用器、加入一個帶有分層 docblock 的具型別 HtmlStyleState 欄位,並在 ParserConfigurator 註冊該套用器。下方範例展示套用器契約的形式。請參考 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()讀取原始 CSS。 這是唯一一個有文件記載的契約例外,延後到未來的TableApplicator處理。請勿仿照這個模式。- 六層,四層核心。 ADR-010 把層編號為六層。資料流契約指的是這四層核心;分頁媒體與量測則是輔助層。
HtmlStyleState是雙群組的。 它同時包含 CSS 計算欄位與版面追蹤欄位。只有套用器會寫入 CSS 群組。繪製層讀取ComputedStyle,絕不讀取版面追蹤欄位。HtmlParser不屬於任何一層。 它是協調器。CSS 剖析、幾何運算與繪製輸出都不可放在它裡面。
分層契約屬於結構設計,不會增加任何執行期成本。HtmlStyleState::toComputedStyle() 會為每個需要繪製的元素產生一份不可變快照。這份快照讓繪製程式碼不必讀取可變的狀態容器。繪製成本由 串流模型 管控,而非由分層決定。每頁的 performance_budget(wall_ms: 1500、peak_mb: 64)是運作上的上限。
安全性注意事項
標題為「安全性注意事項」的區段分層分離支撐整個安全模型。Layer 1 會在任何版面配置或繪製程式碼看到 CSS 值之前,先剖析並依政策過濾這些值;因此 DefaultHtmlSecurityPolicy::isCssPropertyAllowed() 就是那道唯一的 gate。繪製層永遠不會讀取受攻擊者控制的原始 CSS。請參閱 HTML 模組安全模型。
符合性
標題為「符合性」的區段本頁未引用任何外部標準。分層邊界源自 ADR-010,以及把契約編入原始碼的 HtmlStyleState 類別 docblock。CSS 的行為符合性記載於 css-resolver 一節。
商業情境
標題為「商業情境」的區段Enterprise 功能。 Premium 的 CSS 功能透過同一套有文件記載的擴充點,擴充這同樣的四層。並沒有獨立的 Premium 管線。請參閱 CSS 支援矩陣。