Canalización de renderizado HTML
De un vistazo
Sección titulada «De un vistazo»writeHtml() ejecuta una sola pasada hacia delante: tokeniza, resuelve @page y los estilos, calcula el diseño y pinta los operadores del PDF. No conserva ningún árbol de elementos entre etapas.
Instalación
Sección titulada «Instalación»composer require nextpdf/core:^3Visión general conceptual
Sección titulada «Visión general conceptual»La canalización de renderizado HTML convierte HTML+CSS en operadores del flujo de contenido del PDF en una sola pasada hacia delante. No construye un árbol de documento persistente en memoria. La siguiente secuencia de etapas refleja HtmlParser::parse() tal como está implementado en main.
Etapa 1: depurar y normalizar. HtmlParser::parse() rechaza la entrada que supera los 10 MB, elimina los caracteres de control y normaliza los finales de línea: tanto CRLF como un CR aislado se convierten en LF, de acuerdo con la normalización de finales de línea de HTML que adopta el código fuente. Después restablece todos los campos de la instancia, de modo que no queda ningún estado de una llamada anterior.
Etapa 2: extraer los bloques @page y de estilo. El analizador extrae primero los bloques <style> y, a continuación, aplica las reglas @page encontradas para reconfigurar la geometría de la página. Esto ocurre antes de procesar cualquier token, porque el tamaño de la página afecta a todas las decisiones de diseño posteriores.
Etapa 3: tokenizar. HtmlTokenizer::cleanHtml() normaliza los espacios en blanco sin alterar el contenido de <pre>. Después, tokenize() produce una list<HtmlToken> plana. Es una lista de tokens, no un grafo de nodos. Los tokens de texto compuestos solo por espacios en blanco se descartan de inmediato. HtmlChildScanner::scan() construye mapas de índices (recuentos de hijos, recuentos de etiquetas y estado vacío) sobre la lista plana para que los selectores estructurales no necesiten un árbol.
Etapa 4: exploración previa opcional de :has(). Cuando la función experimental css.has está habilitada, CssResolver::resolveHasSelectors() ejecuta una única exploración previa acotada sobre la lista de tokens para resolver el selector relacional. Es una excepción documentada y acotada a la regla de una sola pasada.
Etapa 5: procesar los tokens (estilo, diseño, pintado). HtmlParser::processTokens() recorre la lista de tokens una sola vez. Para cada elemento resuelve la cascada (los aplicadores de la capa 1 escriben en HtmlStyleState), calcula la geometría (diseño de la capa 3) y emite operadores del PDF (pintado de la capa 4). La herencia de estilos usa una pila HtmlStyleState con operaciones de apilado y desapilado. El cursor (x, y, márgenes, desplazamiento del flujo) se mueve entre los gestores mediante instantáneas de HtmlBlockCursor.
Etapa 6: devolver el resultado. parse() devuelve un HtmlRenderResult inmutable que contiene el flujo de contenido emitido, la posición final del cursor y las claves de fuente utilizadas. El llamador (writeHtml()) devuelve el cursor al marco de coordenadas de la página.
La página contratos de capa cubre la separación en cuatro capas que se ejecuta dentro de la etapa 5. La página restricciones de transmisión cubre la propiedad de no conservar el árbol y sus límites.
Superficie de la API
Sección titulada «Superficie de la API»| Símbolo | Ubicación | Etapa |
|---|---|---|
Document::writeHtml(string $html): static | src/Core/Concerns/HasTextOutput.php | Punto de entrada público |
HtmlParser::parse(string $html): HtmlRenderResult | src/Html/HtmlParser.php | Orquesta todas las etapas |
HtmlTokenizer::cleanHtml() / tokenize() | src/Html/HtmlTokenizer.php | Etapa 3 |
HtmlChildScanner::scan() | src/Html/HtmlChildScanner.php | Mapas de índices de la etapa 3 |
CssResolver::resolveHasSelectors() | src/Html/CssResolver.php | Etapa 4 (con control de acceso) |
HtmlRenderResult (stream, endX, endY, usedFontKeys) | src/Html/HtmlRenderResult.php | Etapa 6 |
Ejemplo de código: inicio rápido
Sección titulada «Ejemplo de código: inicio rápido»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>One pass.</p>');$doc->save(__DIR__ . '/output/08-html-basic.pdf');Ejemplo de código: producción
Sección titulada «Ejemplo de código: producción»Renderiza un informe con estilos y un bloque <style> incrustado. La canalización extrae y aplica el bloque de estilo antes de procesar cualquier token.
<?php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
use NextPDF\Core\Document;use NextPDF\Exception\HtmlParsingException;
function renderInvoice(string $bodyHtml, string $out): void{ $doc = Document::createStandalone(); $doc->setTitle('Invoice'); $doc->addPage();
$html = '<style>@page { margin: 20mm; } ' . 'h1 { color: #1E3A8A; } ' . 'table { width: 100%; }</style>' . $bodyHtml;
try { $doc->writeHtml($html); } catch (HtmlParsingException $e) { // Sanitize/cap failures surface here. Do not retry. throw $e; }
$doc->save($out);}Casos límite y trampas
Sección titulada «Casos límite y trampas»@pagese lee antes que los tokens. Una regla@pagecolocada después del contenido se sigue aplicando, porque la extracción de estilos precede a la tokenización. La geometría de la página queda fijada antes de la etapa 5.- Se conservan los espacios en blanco de
<pre>.cleanHtml()protege el contenido de<pre>; los espacios en blanco en otros lugares se colapsan. :has()tiene control de acceso. Sin la función experimentalcss.has, la etapa 4 no se ejecuta y los selectores:has()no coinciden.- Un solo búfer de flujo. La canalización escribe en un único búfer de cadena. El contenido ya escrito nunca se mueve. No hay recálculo de diseño.
- Los límites se aplican a mitad de la pasada. Los límites de elementos y de anidamiento lanzan errores durante la etapa 5, no antes. Un documento puede fallar a mitad de la pasada.
Rendimiento
Sección titulada «Rendimiento»La canalización es O(número de tokens) para el recorrido. El dimensionamiento de columnas de tabla añade una exploración de filas acotada por tabla (etapa 5, TableParser). La exploración previa de :has() añade una pasada acotada sobre la lista de tokens cuando está habilitada (etapa 4). La memoria es O(profundidad de anidamiento) para la pila de estilos, no O(número de elementos); consulta restricciones de transmisión. La prueba de rendimiento de la canalización de renderizado HTML protege frente a regresiones con un control del 5 % (trabajo fusionado, PR #564). El performance_budget por página (wall_ms: 1500, peak_mb: 64) es el techo operativo.
Notas de seguridad
Sección titulada «Notas de seguridad»La etapa 1 es el primer límite de seguridad: el tope de entrada de 10 MB, la eliminación de caracteres de control y la normalización de finales de línea se ejecutan antes de la tokenización. Después, durante la etapa 5, DefaultHtmlSecurityPolicy controla qué etiquetas, atributos, propiedades CSS y esquemas de URL se permiten. Consulta el modelo de seguridad del módulo HTML.
Conformidad
Sección titulada «Conformidad»La normalización de finales de línea sigue el tratamiento de finales de línea del estándar HTML (CRLF y un CR aislado se convierten en LF). La conformidad de CSS por propiedad está documentada en la matriz de compatibilidad de CSS y el comportamiento de la cascada, en css-resolver. Esta página no reitera la compatibilidad por propiedad.
Contexto comercial
Sección titulada «Contexto comercial»Capacidad Enterprise. Premium amplía la cobertura de CSS sobre esta misma canalización. La secuencia de seis etapas no cambia entre ediciones. Consulta la matriz de compatibilidad de CSS.