HTML エンジンのレイヤー契約(ADR-010)
HTML サブシステムでは、CSS 解析、スタイル状態、レイアウト、ペイントを 4 つのレイヤーに分離し、それらの間の契約を一方向の流れとして定義します。ADR-010 は、その境界と拡張ルールを定めています。
インストール
「インストール」という見出しのセクションcomposer require nextpdf/core:^3概念の概要
「概念の概要」という見出しのセクションADR-010(「Engine Layer Contracts, Hot Path Ownership, and Extension Rules」、2026-04-12 承認)は、HTML サブシステムをどのようにレイヤー化するかを正式に定めています。コアのレンダリング契約は 4 つのレイヤーで構成されます。CSS 解析とアプリケーター、スタイル状態、レイアウトと整形、そしてペイントです。ADR-010 はさらに、4 レイヤーのコアを取り巻きながら、そのデータフローは変えない 2 つの付随レイヤー、すなわちページメディアと測定ハーネスについても記載しています。用語集では、このコアの正式名称を「HTML pipeline」とし、4 レイヤーのパイプラインとして定義しています。
データは一方向に流れます。CSS テキストは、レイヤー 1 で型付きの値になります。レイヤー 1 は、その値をレイヤー 2 の HtmlStyleState フィールドに書き込みます。レイヤー 3 は、スタイル状態のフィールドを読み取り、ジオメトリを計算します。レイヤー 4 は、イミュータブルな ComputedStyle のスナップショットとジオメトリを読み取り、PDF 演算子を出力します。どのレイヤーも、後続のレイヤーから読み取ることはありません。
4 レイヤーの分離は、ドキュメント上だけのものではありません。ADR-010 は、v1.2.0 で適用され、コードを正しいレイヤーへ移動した 2 件の範囲限定リファクタリングを記録しています。PageBorderPainter は HtmlParser から抽出され、ペイント演算子がオーケストレーターに存在しなくなりました。HtmlStyleState クラスの docblock は現在、各レイヤーがどのフィールドを書き込みまたは読み取りできるかを定める正式なレイヤー契約を記載しています。
1 つの境界については、隠すのではなく明示的に述べています。FormattingContextFactory::startTable() は、現在も 5 つの生の CSS キーを直接読み取っています。ADR-010 はこれを、意図された契約ではなく、将来の TableApplicator に向けて先送りされた既知の技術的負債として記録しています。例外を文書化することも、契約の一部です。
4 つのコアレイヤー
「4 つのコアレイヤー」という見出しのセクション| レイヤー | ファイル(代表例) | 書き込み | 読み取り | 禁止事項 |
|---|---|---|---|---|
| 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 の解析、改ページの決定。 |
2 つの付随レイヤー
「2 つの付随レイヤー」という見出しのセクション| レイヤー | ファイル(代表例) | 役割 |
|---|---|---|
| 5 — ページメディア | PageBreakController, PageBorderPainter, PageRule, PageRuleParser, ParserConfigurator | 先に @page ルールを resolve(解決)し、改ページおよび orphan/widow 制約を評価して、ページ装飾をペイントへ委譲。 |
| 6 — 測定とハーネス | WPT 分類スクリプト、tests/Support/* | テスト結果の分類、リグレッションスナップショットの生成、アサーションヘルパーの提供。レンダリングロジックなし。 |
API サーフェス
「API サーフェス」という見出しのセクションこの契約は、クラス配置と HtmlStyleState の docblock によって強制されます。src/Html/ と照らし合わせて確認してください。
| シンボル | レイヤー | 契約上の役割 |
|---|---|---|
PropertyApplicatorInterface | 1 | ストラテジーインターフェイス。CSS の計算済みフィールドを書き込む唯一の場所。 |
ParserConfigurator::buildCssApplicator() | 1(配線) | すべてのアプリケーターの登録場所。新しい CSS プロパティの登録ポイント。 |
HtmlStyleState | 2 | 2 グループの入れ物。クラスの docblock による、フィールドごとの所有レイヤー定義。 |
HtmlStyleState::toComputedStyle() | 2 | ペイントレイヤー向けのイミュータブルな ComputedStyle 生成。 |
FormattingContextFactory::dispatchOpenTag() | 3 | 新しいレイアウト動作の単一ルーティングポイント。 |
PageBorderPainter::buildStream() | 4 | ページ装飾。レイヤー 5 から呼び出され、HtmlParser にはインライン化されない。 |
コードサンプル — クイックスタート
「コードサンプル — クイックスタート」という見出しのセクション呼び出し側がレイヤーを直接扱うことはありません。4 レイヤーのフローは、1 回の呼び出しの中で実行されます。
<?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 プロパティを追加するには、レイヤー 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に先送りされた、唯一の文書化された契約例外です。このパターンをまねしないでください。- 6 つのレイヤー、4 レイヤーのコア。 ADR-010 は 6 つのレイヤーに番号を付けています。データフロー契約の中心は 4 レイヤーのコアであり、ページメディアと測定は付随的なものです。
HtmlStyleStateは 2 グループ構成です。 CSS の計算済みフィールドとレイアウト追跡フィールドを保持します。CSS グループを書き込むのはアプリケーターだけです。ペイントはComputedStyleを読み取り、レイアウト追跡フィールドは決して読み取りません。HtmlParserにはレイヤーがありません。HtmlParserはオーケストレーターです。CSS 解析、ジオメトリ計算、ペイント出力は、HtmlParserに存在してはなりません。
パフォーマンス
「パフォーマンス」という見出しのセクションレイヤー契約は構造的なものであり、実行時のコストを増やしません。HtmlStyleState::toComputedStyle() は、ペイントを必要とする要素ごとに 1 つのイミュータブルなスナップショットを生成します。このスナップショットにより、ペイントコードはミュータブルな状態の入れ物を読み取らずに済みます。レンダリングコストは、レイヤー分離ではなく ストリーミングモデルによって決まります。ページごとの performance_budget(wall_ms: 1500, peak_mb: 64)が運用上の上限です。
セキュリティに関する注意
「セキュリティに関する注意」という見出しのセクションレイヤーの分離は、セキュリティモデルを支えています。レイヤー 1 が、レイアウトやペイントのコードが CSS の値を見る前に解析し、ポリシーでフィルタリングするため、DefaultHtmlSecurityPolicy::isCssPropertyAllowed() が唯一のゲートになります。ペイントが、攻撃者の制御下にある生の CSS を読み取ることは決してありません。HTML モジュールのセキュリティモデルを参照してください。
このページは外部標準を引用していません。レイヤーの境界は、ADR-010 と、ソース内に契約をエンコードしている HtmlStyleState クラスの docblock に由来します。CSS の動作面の準拠については、css-resolver に記載されています。
商用コンテキスト
「商用コンテキスト」という見出しのセクションエンタープライズ機能。 Premium の CSS 機能は、文書化された拡張ポイントを通じて、これらと同じ 4 つのレイヤーを拡張します。独立した Premium パイプラインは存在しません。CSS サポートマトリクスを参照してください。