コンテンツにスキップ

HTML エンジンのレイヤー契約(ADR-010)

HTML サブシステムでは、CSS 解析、スタイル状態、レイアウト、ペイントを 4 つのレイヤーに分離し、それらの間の契約を一方向の流れとして定義します。ADR-010 は、その境界と拡張ルールを定めています。

Terminal window
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 件の範囲限定リファクタリングを記録しています。PageBorderPainterHtmlParser から抽出され、ペイント演算子がオーケストレーターに存在しなくなりました。HtmlStyleState クラスの docblock は現在、各レイヤーがどのフィールドを書き込みまたは読み取りできるかを定める正式なレイヤー契約を記載しています。

1 つの境界については、隠すのではなく明示的に述べています。FormattingContextFactory::startTable() は、現在も 5 つの生の 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先に @page ルールを resolve(解決)し、改ページおよび orphan/widow 制約を評価して、ページ装飾をペイントへ委譲。
6 — 測定とハーネスWPT 分類スクリプト、tests/Support/*テスト結果の分類、リグレッションスナップショットの生成、アサーションヘルパーの提供。レンダリングロジックなし。

この契約は、クラス配置と HtmlStyleState の docblock によって強制されます。src/Html/ と照らし合わせて確認してください。

シンボルレイヤー契約上の役割
PropertyApplicatorInterface1ストラテジーインターフェイス。CSS の計算済みフィールドを書き込む唯一の場所。
ParserConfigurator::buildCssApplicator()1(配線)すべてのアプリケーターの登録場所。新しい CSS プロパティの登録ポイント。
HtmlStyleState22 グループの入れ物。クラスの 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_budgetwall_ms: 1500, peak_mb: 64)が運用上の上限です。

レイヤーの分離は、セキュリティモデルを支えています。レイヤー 1 が、レイアウトやペイントのコードが CSS の値を見る前に解析し、ポリシーでフィルタリングするため、DefaultHtmlSecurityPolicy::isCssPropertyAllowed() が唯一のゲートになります。ペイントが、攻撃者の制御下にある生の CSS を読み取ることは決してありません。HTML モジュールのセキュリティモデルを参照してください。

このページは外部標準を引用していません。レイヤーの境界は、ADR-010 と、ソース内に契約をエンコードしている HtmlStyleState クラスの docblock に由来します。CSS の動作面の準拠については、css-resolver に記載されています。

エンタープライズ機能。 Premium の CSS 機能は、文書化された拡張ポイントを通じて、これらと同じ 4 つのレイヤーを拡張します。独立した Premium パイプラインは存在しません。CSS サポートマトリクスを参照してください。