Pular para o conteúdo

Contratos de camadas do engine HTML (ADR-010)

O subsistema Hypertext Markup Language (HTML) separa a análise de Cascading Style Sheets (CSS), o estado de estilo, o layout e a pintura em quatro camadas. Os dados fluem em uma única direção por essas camadas. O Architecture Decision Record 010 (ADR-010) define os limites e as regras de extensão.

Terminal window
composer require nextpdf/core:^3

O Architecture Decision Record 010 (ADR-010) (“Engine Layer Contracts, Hot Path Ownership, and Extension Rules”, aprovado em 2026-04-12) formaliza a divisão em camadas do subsistema HTML. O contrato de renderização do core tem quatro camadas: análise de CSS e applicators, estado de estilo, layout e formatação, e pintura. O ADR-010 também documenta duas camadas adjuntas: mídia paginada e o harness de medição. Essas camadas envolvem o core de quatro camadas sem alterar o fluxo de dados. O termo canônico do glossário para o core é “pipeline HTML”, um pipeline de quatro camadas.

Os dados fluem em uma única direção. O texto CSS é convertido em valores tipados na Camada 1. A Camada 1 grava esses valores nos campos de HtmlStyleState na Camada 2. A Camada 3 lê os campos de estado de estilo e calcula a geometria. A Camada 4 lê um snapshot imutável de ComputedStyle mais a geometria e emite operadores Portable Document Format (PDF). Nenhuma camada lê dados de uma camada posterior.

A separação em quatro camadas é mais que documentação. O ADR-010 registra dois refactors pontuais feitos na v1.2.0 que moveram código para a camada correta. PageBorderPainter foi extraído de HtmlParser, de modo que os operadores de pintura não residem mais no orquestrador. O docblock da classe HtmlStyleState agora traz o contrato formal de camadas e declara quais campos cada camada pode gravar ou ler.

Há um limite explícito. FormattingContextFactory::startTable() ainda lê cinco chaves CSS brutas diretamente. O ADR-010 registra isso como débito técnico conhecido, adiado para um futuro TableApplicator, e não como o contrato pretendido. Documentar a exceção faz parte do contrato.

CamadaArquivos (representativos)GravaNão deve
1 — Análise de CSS & applicatorsCssValueParser, CssResolver, HtmlCssApplicator, src/Html/Applicator/*HtmlStyleState campos CSSTexto CSS brutoCalcular geometria; emitir operadores
2 — Estado de estiloHtmlStyleState, State/ComputedStyle, State/LayoutState— (contêiner passivo de valores)Analisar CSS; decidir layout; emitir operadores
3 — Layout & formataçãoFormattingContextFactory, HtmlBlockHandler, FlexLayoutEngine, TableParser, FloatContextGeometria do cursorHtmlStyleState camposLer $css[...] bruto; emitir operadores de pintura
4 — Pintura & renderizaçãoBorderRenderer, BackgroundImageRenderer, src/Html/Paint/*, src/Html/Gradient/*Fluxo de operadores PDFComputedStyle (imutável) + geometriaCalcular geometria; analisar CSS; decidir quebras de página
CamadaArquivos (representativos)Função
5 — Mídia paginadaPageBreakController, PageBorderPainter, PageRule, PageRuleParser, ParserConfiguratorResolver regras @page; avaliar restrições de quebra e de orphan/widow; delegar a decoração da página à pintura.
6 — Medição & harnessScripts classificadores de Web Platform Tests (WPT), tests/Support/*Classificar resultados de testes; produzir snapshots de regressão; fornecer auxiliares de asserção. Não contém lógica de renderização.

O contrato é imposto pelo posicionamento das classes e pelo docblock de HtmlStyleState. Verifique-o em relação a src/Html/.

SímboloCamadaFunção no contrato
PropertyApplicatorInterface1Interface de estratégia; o único ponto que grava campos computados de CSS.
ParserConfigurator::buildCssApplicator()1 (wiring)Registra cada applicator. Uma nova propriedade CSS é registrada aqui.
HtmlStyleState2Contêiner de grupo duplo; o docblock da classe declara a camada proprietária de cada campo.
HtmlStyleState::toComputedStyle()2Produz o ComputedStyle imutável para a camada de pintura.
FormattingContextFactory::dispatchOpenTag()3Ponto único de roteamento para novo comportamento de layout.
PageBorderPainter::buildStream()4Decoração de página; chamada a partir da Camada 5, não incorporada em HtmlParser.

Você nunca interage diretamente com as camadas. O fluxo de quatro camadas roda dentro de uma única chamada.

<?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');

O contrato importa quando você contribui, não quando chama a biblioteca. Para adicionar uma propriedade CSS, use o ponto de extensão da Camada 1: crie um applicator, adicione um campo tipado de HtmlStyleState com um docblock de camada e registre o applicator em ParserConfigurator. A ilustração abaixo mostra o formato do contrato do applicator. Use src/Html/Applicator/ como modelo para uma classe concreta.

<?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() lê CSS bruto. Esta é a única exceção de contrato documentada, adiada para um futuro TableApplicator. Não copie o padrão.
  • Seis camadas, core de quatro camadas. O ADR-010 numera seis camadas. O contrato de fluxo de dados é o core de quatro camadas; mídia paginada e medição são camadas adjuntas.
  • HtmlStyleState é de grupo duplo. Ele carrega campos computados de CSS e campos de rastreamento de layout. Apenas os applicators gravam o grupo CSS. A pintura lê ComputedStyle, nunca os campos de rastreamento de layout.
  • HtmlParser não tem camada. Ele é o orquestrador. A análise de CSS, os cálculos de geometria e a emissão de pintura não devem residir nele.

O contrato de camadas é estrutural, portanto não adiciona custo de tempo de execução. HtmlStyleState::toComputedStyle() produz um snapshot imutável para cada elemento que precisa de pintura. Esse snapshot permite que o código de pintura evite o contêiner de estado mutável. O custo de renderização é governado pelo modelo de streaming, não pelas camadas. O performance_budget por página (wall_ms: 1500, peak_mb: 64) permanece como teto operacional.

A separação em camadas sustenta o modelo de segurança. A Camada 1 analisa os valores CSS e os filtra por política antes que o código de layout ou de pintura os veja, de modo que DefaultHtmlSecurityPolicy::isCssPropertyAllowed() permanece o único portão. A pintura nunca lê CSS bruto controlado por atacante. Consulte o modelo de segurança do módulo HTML.

Esta página não cita nenhum padrão externo. Os limites de camada vêm do ADR-010 e do docblock da classe HtmlStyleState, que codifica o contrato no código-fonte. A conformidade comportamental de CSS está documentada em css-resolver.

Capacidade Enterprise. Os recursos CSS Premium usam essas mesmas quatro camadas por meio dos pontos de extensão documentados. Não há um pipeline Premium separado. Consulte a matriz de suporte a CSS.