Ir al contenido

Canalización de renderizado HTML

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.

Ventana de terminal
composer require nextpdf/core:^3

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.

SímboloUbicaciónEtapa
Document::writeHtml(string $html): staticsrc/Core/Concerns/HasTextOutput.phpPunto de entrada público
HtmlParser::parse(string $html): HtmlRenderResultsrc/Html/HtmlParser.phpOrquesta todas las etapas
HtmlTokenizer::cleanHtml() / tokenize()src/Html/HtmlTokenizer.phpEtapa 3
HtmlChildScanner::scan()src/Html/HtmlChildScanner.phpMapas de índices de la etapa 3
CssResolver::resolveHasSelectors()src/Html/CssResolver.phpEtapa 4 (con control de acceso)
HtmlRenderResult (stream, endX, endY, usedFontKeys)src/Html/HtmlRenderResult.phpEtapa 6

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

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);
}
  • @page se lee antes que los tokens. Una regla @page colocada 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 experimental css.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.

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.

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.

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.

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.