Ir al contenido

HTML: subsistema de renderizado de HTML+CSS a PDF

El subsistema HTML convierte HTML+CSS en flujos de contenido PDF mediante un único paso hacia delante. Es el subsistema más grande y de mayor riesgo del motor (324 archivos en src/Html/).

Ventana de terminal
composer require nextpdf/core:^3

El subsistema HTML es un motor de renderizado de HTML+CSS a PDF por streaming y en un solo paso. Su superficie pública se reduce a un único método, Document::writeHtml(). Internamente, HtmlParser tokeniza la entrada, resuelve los estilos, calcula el diseño y emite operadores PDF en un único paso hacia delante, sin conservar un árbol del documento.

Conviene delimitar bien el alcance. Este subsistema no es un motor de renderizado de documento retenido. No mantiene un grafo de elementos, no vuelve a maquetar el contenido ya escrito y no permite que la entrada cambie una vez iniciado el análisis. Implementa un subconjunto seleccionado de CSS en versiones fijadas de la especificación. Lo rigen dos decisiones de arquitectura. ADR-001 fija el modelo de streaming de un solo paso y sus límites. ADR-010 fija el contrato de cuatro capas (análisis de CSS, estado de estilo, diseño, pintado) junto con los complementos de paged-media y medición.

HtmlParser está clasificado con riesgo crítico en el manifiesto del módulo. Cinco archivos tienen anotaciones documentadas como zona de peligro: el orquestador HtmlParser (tokenizador de streaming, más de 1000 LOC), HtmlStyleState (más de 100 campos de propiedades CSS con un modelo de herencia por pila), HtmlBlockHandler (despacho de bloques acoplado al estado de estilo), FlexLayoutEngine (medición y diseño flex completos) y TableParser (paginación de colspan/rowspan a través de saltos de página). Los cambios en esta zona deben tratarse como trabajo de modo de planificación.

Esta página funciona como punto de entrada. Las páginas de detalle son: pipeline para la secuencia de etapas, css-resolver para la cascada y la especificidad, layer-contracts-adr010 para los límites entre capas y streaming-constraints-adr001 para el modelo sin árbol retenido y sus límites.

Texto de derecha a izquierda y bidireccional

Sección titulada «Texto de derecha a izquierda y bidireccional»

writeHtml() renderiza contenido de derecha a izquierda (RTL). Definir la propiedad CSS direction: rtl en el body, en una tabla o en cualquier elemento. El motor resuelve el orden visual con el algoritmo bidireccional de Unicode (UAX #9) a través del motor bidireccional de la capa de tipografía; consultar Tipografía para conocer los detalles de BidiEngine. El contenido mixto de latín, árabe y numérico se ordena correctamente, y un número que va detrás del árabe mantiene sus dígitos de izquierda a derecha.

El árabe también recibe shaping contextual: el motor selecciona la forma inicial, medial, final o aislada de cada letra y aplica la ligadura Lam-Alef. El shaping necesita una fuente registrada cuyo mapa de caracteres cubra el bloque Arabic Presentation Forms-B; una cara solo latina, incluidas las fuentes standard-14, no puede dibujar árabe. En las tablas, cada celda se reordena y se le aplica shaping por separado y se alinea al borde de inicio (derecho) bajo direction: rtl. RTL se aplica al árabe, el hebreo, el persa y el urdu; el hebreo se reordena pero no recibe shaping.

Definir la dirección con la propiedad CSS direction: el atributo HTML dir no se asigna a ella. La alineación horizontal del texto de bloque que no es de tabla y del texto en línea, y text-align: justify, todavía no se aplican. Para una factura en árabe ejecutable y la lista completa de las limitaciones actuales, consultar Renderizar HTML árabe de derecha a izquierda.

SímboloUbicaciónFunción
Document::writeHtml(string $html): staticsrc/Core/Concerns/HasTextOutput.phpPunto de entrada público. Renderiza el HTML en el cursor actual.
Document::createStandalone(): selfsrc/Core/Document.phpConstruye un documento independiente.
HtmlParser::parse(string $html): HtmlRenderResultsrc/Html/HtmlParser.phpOrquestador interno.
HtmlRenderResultsrc/Html/HtmlRenderResult.phpResultado inmutable: flujo, cursor final, fuentes utilizadas.
DefaultHtmlSecurityPolicysrc/Html/DefaultHtmlSecurityPolicy.phpPolítica predeterminada de tag/attribute/CSS/URL.
HtmlSecurityPolicyInterfacesrc/Contracts/HtmlSecurityPolicyInterface.phpContrato de política para políticas personalizadas.

Tomado de examples/08-html-basic.php.

<?php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
use NextPDF\Core\Document;
$doc = Document::createStandalone();
$doc->setTitle('HTML Basic');
$doc->addPage();
$doc->writeHtml('<h1 style="color:#1E3A8A;">HTML Rendering</h1><p>Direct to PDF.</p>');
$doc->save(__DIR__ . '/output/08-html-basic.pdf');

Un informe con tabla y un bloque de estilo incrustado, basado en examples/09-html-table.php.

<?php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
use NextPDF\Core\Document;
use NextPDF\Exception\HtmlParsingException;
function renderInventory(string $rowsHtml, string $out): void
{
$doc = Document::createStandalone();
$doc->setTitle('Inventory');
$doc->addPage();
$html = '<style>table { width: 100%; } '
. 'th { background-color: #1E3A8A; color: #FFFFFF; }</style>'
. '<table border="1" cellpadding="5">' . $rowsHtml . '</table>';
try {
$doc->writeHtml($html);
} catch (HtmlParsingException $e) {
// Input cap, element cap (50,000), or nesting cap (100). Do not retry.
throw $e;
}
$doc->save($out);
}
  • Subconjunto seleccionado de CSS. La compatibilidad se define por módulo y está fijada. Consultar la matriz de compatibilidad de CSS antes de depender de una propiedad.
  • Los límites estrictos lanzan excepciones. 10 MB de entrada, 50,000 elementos, 100 niveles de anidamiento; cada límite lanza HtmlParsingException. Consultar las restricciones de streaming.
  • Sin remaquetación. La salida se escribe una sola vez en el orden del documento; los estilos posteriores no pueden cambiar la salida anterior.
  • :has() está restringido mediante la característica experimental css.has.
  • Subsistema de riesgo crítico. Cinco archivos están en zona de peligro. Los cambios en src/Html/ deben abordarse en modo de planificación.

Restricciones de streaming de un solo paso (ADR-001)

Sección titulada «Restricciones de streaming de un solo paso (ADR-001)»

El motor de renderizado no mantiene un árbol del documento y ejecuta un único paso hacia delante. Los límites de elementos, de anidamiento y de entrada son estrictos. El detalle completo y el contrato de seguridad para workers están en restricciones de streaming (ADR-001).

El análisis de CSS, el estado de estilo, el diseño y el pintado están separados en cuatro capas con contratos unidireccionales, además de los complementos de paged-media y medición. El detalle completo está en contratos de capa (ADR-010).

Presupuesto de memoria para documentos grandes

Sección titulada «Presupuesto de memoria para documentos grandes»

La memoria del estado de estilo y del cursor es O(profundidad de anidamiento), no O(número de elementos). El performance_budget por página es peak_mb: 64. El límite de 50,000 elementos es el tope estricto; las entradas más grandes deben dividirse en varias llamadas a writeHtml(). El detalle está en restricciones de streaming.

El recorrido es O(número de tokens). El dimensionamiento de columnas de la tabla añade un recorrido acotado de filas por tabla. El preescaneo opcional de :has() añade un único recorrido acotado de la lista de tokens. El benchmark de rendimiento del pipeline de renderizado de HTML aplica un umbral de regresión del 5 % (trabajo fusionado, PR #564). El performance_budget por página (wall_ms: 1500, peak_mb: 64) es el tope operativo.

DefaultHtmlSecurityPolicy aplica una lista de permitidos de etiquetas, atributos, propiedades CSS y esquemas de URL, además de un tope de entrada de 10 MB y un tope de anidamiento de 100 niveles, independientemente del analizador. La lista de permitidos de propiedades CSS es el tope de seguridad. La tabla de compatibilidad en tiempo de ejecución es un tope de capacidad independiente. Implementar HtmlSecurityPolicyInterface permite proporcionar una política más estricta. La obtención de recursos externos se rige por separado mediante DefaultExternalResourcePolicy.

En los valores de href y de src de imagen, la lista de permitidos de URL también rechaza las rutas con raíz de barra invertida (\…) y UNC (\\host\share), además del rechazo ya existente de las relativas al protocolo (//) y de la lista de permitidos limitada a http(s) o rutas relativas. Las barras invertidas se normalizan a barras inclinadas antes de la comprobación, de modo que una inclusión de archivo local con ruta absoluta de Windows o una obtención desde un recurso compartido SMB —ninguna de las cuales lleva un esquema URI— no puede pasar por la rama «sin esquema, por tanto relativa».

Extracto de la matriz de compatibilidad de CSS (solo filas verificadas)

Sección titulada «Extracto de la matriz de compatibilidad de CSS (solo filas verificadas)»

Esta página no repite la compatibilidad por propiedad. La matriz de compatibilidad de CSS es la única autoridad sobre el estado verificado por módulo del W3C, incluido qué módulos están verificados y cuáles solo declarados.

El subsistema implementa un subconjunto seleccionado de CSS en versiones fijadas de la especificación. Las correspondencias entre el comportamiento de la cascada y la especificación están documentadas con identificadores de cláusula y de fragmento en css-resolver. El estado de conformidad por módulo está en la matriz de compatibilidad de CSS.

Capacidad empresarial. Premium amplía la cobertura de CSS (impresión avanzada y módulos adicionales) en el mismo pipeline de un solo paso. La arquitectura, los límites y los contratos de capa son los mismos en todas las ediciones. Consultar la matriz de compatibilidad de CSS.