Ir al contenido

Contratos de capas del motor HTML (ADR-010)

El subsistema HTML separa el análisis de CSS, el estado de estilo, el layout y el paint en cuatro capas, y el contrato entre ellas impone un flujo en una sola dirección. El ADR-010 fija los límites y las reglas de extensión.

Ventana de terminal
composer require nextpdf/core:^3

El ADR-010 («Engine Layer Contracts, Hot Path Ownership, and Extension Rules», aceptado el 2026-04-12) formaliza la estructura por capas del subsistema HTML. El contrato de renderizado central tiene cuatro capas: análisis de CSS y aplicadores, estado de estilo, layout y formato, y paint. El ADR-010 también documenta dos capas complementarias —medios paginados y el arnés de medición— que envuelven el núcleo de cuatro capas, pero no cambian su flujo de datos. El término canónico del glosario para el núcleo es “pipeline HTML”, un pipeline de cuatro capas.

Los datos fluyen en una sola dirección. El texto CSS se convierte en valores tipados en la capa 1. La capa 1 escribe esos valores en los campos de HtmlStyleState en la capa 2. La capa 3 lee los campos del estado de estilo y calcula la geometría. La capa 4 lee una instantánea inmutable de ComputedStyle más la geometría y emite operadores PDF. Ninguna capa lee datos de una capa posterior.

La separación en cuatro capas no es meramente documental. El ADR-010 registra dos refactorizaciones acotadas aplicadas en v1.2.0 que trasladaron código a la capa correcta. PageBorderPainter se extrajo de HtmlParser para que los operadores de paint ya no residan en el orquestador. Ahora, el docblock de la clase HtmlStyleState contiene el contrato formal de capa, que establece qué campos puede escribir o leer cada capa.

Un límite queda explícito en lugar de oculto. FormattingContextFactory::startTable() todavía lee directamente cinco claves CSS sin procesar. El ADR-010 lo registra como deuda técnica conocida y diferida para un futuro TableApplicator, no como el contrato previsto. Documentar la excepción es parte del contrato.

CapaArchivos (representativos)EscribeLeeNo debe
1 — Análisis de CSS y aplicadoresCssValueParser, CssResolver, HtmlCssApplicator, src/Html/Applicator/*HtmlStyleState campos CSSTexto CSS sin procesarCalcular la geometría; emitir operadores
2 — Estado de estiloHtmlStyleState, State/ComputedStyle, State/LayoutState— (bolsa de valores pasiva)Analizar CSS; decidir el layout; emitir operadores
3 — Layout y formatoFormattingContextFactory, HtmlBlockHandler, FlexLayoutEngine, TableParser, FloatContextGeometría del cursorHtmlStyleState camposLeer $css[...] sin procesar; emitir operadores de paint
4 — Paint y renderizadoBorderRenderer, BackgroundImageRenderer, src/Html/Paint/*, src/Html/Gradient/*Flujo de operadores PDFComputedStyle (inmutable) + geometríaCalcular la geometría; analizar CSS; decidir los saltos de página
CapaArchivos (representativos)Rol
5 — Medios paginadosPageBreakController, PageBorderPainter, PageRule, PageRuleParser, ParserConfiguratorResolver las reglas @page; evaluar las restricciones de salto y de orphan/widow; delegar en el paint la decoración de página.
6 — Medición y arnésScripts del clasificador de WPT, tests/Support/*Clasificar los resultados de las pruebas; producir instantáneas de regresión; proporcionar ayudantes de aserción. No lleva lógica de renderizado.

El contrato se aplica mediante la ubicación de las clases y el docblock de HtmlStyleState. Puede verificarse en src/Html/.

SímboloCapaRol del contrato
PropertyApplicatorInterface1Interfaz de estrategia; el único punto que escribe los campos CSS computados.
ParserConfigurator::buildCssApplicator()1 (cableado)Registra cada aplicador; toda nueva propiedad CSS se registra aquí.
HtmlStyleState2Bolsa con dos grupos; el docblock de la clase indica la capa propietaria de cada campo.
HtmlStyleState::toComputedStyle()2Produce el ComputedStyle inmutable para la capa de paint.
FormattingContextFactory::dispatchOpenTag()3Punto único de enrutamiento para el nuevo comportamiento de layout.
PageBorderPainter::buildStream()4Decoración de página; se llama desde la capa 5, no se incorpora en línea en HtmlParser.

El código cliente nunca toca las capas. El flujo de cuatro capas se ejecuta dentro de una sola llamada.

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

El contrato importa a quienes contribuyen, no a quienes consumen la API. Para agregar una propiedad CSS, se debe seguir el punto de extensión de la capa 1: crear un aplicador, agregar un campo tipado de HtmlStyleState con un docblock de capa, y registrar el aplicador en ParserConfigurator. La ilustración siguiente muestra la forma del contrato del aplicador. Se puede consultar src/Html/Applicator/ para encontrar una clase concreta que copiar.

<?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() lee CSS sin procesar. Esta es la única excepción documentada del contrato, diferida a un futuro TableApplicator. No copiar el patrón.
  • Seis capas, núcleo de cuatro capas. El ADR-010 numera seis capas. El contrato de flujo de datos corresponde al núcleo de cuatro capas; los medios paginados y la medición son complementos.
  • HtmlStyleState es de doble grupo. Contiene campos CSS computados y campos de seguimiento del layout. Solo los aplicadores escriben el grupo CSS. El paint lee ComputedStyle, nunca los campos de seguimiento del layout.
  • HtmlParser no tiene capa. Es el orquestador. El análisis de CSS, los cálculos de geometría y la emisión de paint no deben vivir en él.

El contrato de capa es estructural y no añade ningún costo de ejecución. HtmlStyleState::toComputedStyle() produce una instantánea inmutable por cada elemento que requiere paint. La instantánea permite que el código de paint evite leer la bolsa de estado mutable. El costo de renderizado se rige por el modelo de streaming, no por las capas. El performance_budget por página (wall_ms: 1500, peak_mb: 64) es el límite operativo.

La separación en capas respalda el modelo de seguridad. La capa 1 analiza y filtra conforme a la política los valores CSS antes de que cualquier código de layout o paint los vea, así que DefaultHtmlSecurityPolicy::isCssPropertyAllowed() es el único punto de control. El paint nunca lee CSS sin procesar controlado por un atacante. Consulta el modelo de seguridad del módulo HTML.

Esta página no cita ningún estándar externo. Los límites de capa se derivan del ADR-010 y del docblock de la clase HtmlStyleState, que codifica el contrato en el código fuente. La conformidad del comportamiento CSS se documenta en css-resolver.

Capacidad Enterprise. Las funciones CSS Premium extienden estas mismas cuatro capas mediante los puntos de extensión documentados. No hay un pipeline Premium aparte. Véase la matriz de compatibilidad de CSS.