HotPDF convierte HTML en páginas PDF mediante THPDFHTMLImporter.RenderHTML5, un perfil de medios paginados versionado con un DOM real, una cascada CSS real, tablas, saltos de página y generación opcional de AcroForm a partir de controles de formulario HTML. Se ejecuta completamente en el mismo proceso: sin motor de navegador, sin Chromium, sin convertidor externo, y por defecto sin ningún acceso a archivos ni a la red
La razón por la que existe un punto de entrada separado en lugar de un RenderHTML mejorado es la compatibilidad. El método anterior lleva años renderizando facturas y cartas en aplicaciones de producción, y un DOM permisivo con una cascada completa cambia la salida de documentos que fueron ajustados contra el comportamiento anterior. Por eso el método anterior conserva su semántica exacta, y todo lo nuevo vive detrás de RenderHTML5, cuya versión de perfil se reporta mediante HTML5ProfileVersion y cuyos hitos admitidos se listan mediante HTML5ProfileMilestones
¿Qué agrega realmente el perfil HTML5?
En la práctica importan cuatro capacidades. Un DOM estructural donde html y body son contenedores en lugar de nodos de texto. Una cascada CSS con la precedencia correcta. Diseño de tablas. Y un control explícito de saltos de página, que es lo que significa "medios paginados": HTML fue diseñado para una ventana de desplazamiento, y convertirlo en páginas exige decidir dónde se le permite al contenido cortarse
El punto de los contenedores suena como un detalle de implementación y en realidad es la diferencia entre que algo funcione o no. Si los elementos raíz se tratan como contenedores de texto, todo el DOM colapsa en un solo bloque de texto durante el análisis, y cada regla de hoja de estilos, salto de página, tabla y subárbol de formulario posterior se aplica sobre nada. Cualquier renderizador HTML que produzca un muro de texto indiferenciado a partir de un documento estructurado ha cometido exactamente este error
El orden de la cascada, explicado paso a paso
La precedencia de CSS no es un solo ordenamiento. HotPDF aplica las declaraciones normales de las reglas coincidentes en orden de especificidad y luego en orden de origen, aplica encima las declaraciones normales en línea, luego las declaraciones !important de las reglas coincidentes, y finalmente las !important en línea. Cuatro pasadas, en ese orden
Las implementaciones que reducen esto a "gana el último" aciertan en los casos comunes y fallan en los interesantes, lo cual es peor que fallar de manera uniforme, porque la falla solo aparece en las plantillas que usan !important para sobrescribir una hoja de estilos base — precisamente las plantillas escritas por personas que esperan que la cascada funcione:
uses
HPDFDoc, HPDFHTMLImport;
var
Pdf: THotPDF;
Importer: THPDFHTMLImporter;
Html, Css: WideString;
begin
Pdf := THotPDF.Create(nil);
Importer := THPDFHTMLImporter.Create(Pdf);
try
Pdf.FileName := 'statement.pdf';
Pdf.BeginDoc;
Importer.Margin := 56; // puntos
Importer.BaseFontName := 'Arial';
Importer.BaseFontSize := 10;
Importer.CreateAcroForms := False; // renderizar los controles, sin crear campos
Css := '@page { margin: 56pt } ' +
'table { border-collapse: collapse } ' +
'tr.total { page-break-inside: avoid; font-weight: bold }';
Html := LoadTemplate('statement.html');
if not Importer.RenderHTML5(Html, Css) then
raise Exception.Create('nothing was emitted - check the markup');
Pdf.EndDoc;
Writeln(Format('profile=%d nodes=%d rules=%d layout ops=%d',
[Importer.Statistics.ProfileVersion,
Importer.Statistics.ParsedNodeCount,
Importer.Statistics.CSSRuleCount,
Importer.Statistics.LayoutOperationCount]));
finally
Importer.Free;
Pdf.Free;
end;
end;
El segundo argumento es una hoja de estilos del autor que se aplica junto con lo que traiga el documento, y es la forma limpia de imponer reglas internas, como los márgenes de página y el control de líneas huérfanas, sin editar cada plantilla
¿Por qué el importador nunca hace solicitudes externas?
Un convertidor de HTML a PDF que resuelve referencias externas es, en el fondo, un motor de server-side request forgery disfrazado de generador de documentos. Apúntelo a marcado suministrado por el usuario y con gusto hará una solicitud a http://169.254.169.254/ o a una URL de archivo desde su servidor de aplicaciones, e incrustará el resultado en un PDF que alguien descargará
Por eso, de forma predeterminada, HotPDF no lee archivos ni la red. Las imágenes y otros recursos llegan exactamente de dos maneras: como URI data: dentro del marcado, o a través del callback OnResource, donde su código decide qué significa una URI y suministra los bytes:
procedure TReportForm.SupplyResource(Sender: TObject;
const URI: WideString; ResourceKind: THPDFHTMLResourceKind;
var Data: TBytes; var ContentType: string; var Handled: boolean);
begin
Handled := False;
// Servir solo desde un directorio local de recursos permitidos
// (allow-list). Nunca pasar la URI a un cliente HTTP, y nunca
// combinarla con una ruta sin validarla primero
if StartsText('asset:', URI) then
begin
Data := LoadAssetBytes(Copy(URI, 7, MaxInt));
ContentType := 'image/png';
Handled := Length(Data) > 0;
end;
end;
Cada recurso que sí llega igual se cuenta contra tres límites independientes: cuántos recursos, cuántos bytes tiene cada uno, y cuántos bytes en total. Un callback que devuelve fielmente lo que se le pide no puede ser inducido a agotar la memoria por un documento que referencia la misma imagen de 50 MB doscientas veces
Cuatro límites, un solo modo de falla
Los nodos del analizador, las reglas CSS, las operaciones de diseño y los recursos se miden de forma independiente, porque fallan de forma independiente. Un documento puede ser pequeño y patológicamente costoso de diagramar; otro puede ser superficial pero con una hoja de estilos diseñada para hacer explotar la coincidencia de selectores. Agotar cualquiera de ellos lanza EHPDFHTMLImportBudget, una clase de excepción dedicada, de modo que el agotamiento del límite se pueda capturar por separado de un marcado mal formado
Las estadísticas se reinician al comienzo de cada llamada a RenderHTML5, de modo que los números describen una importación, no la vida completa del objeto importador. En un servicio que renderiza plantillas no confiables, registre ParsedNodeCount, LayoutOperationCount y ResourceRejectCount por documento: un conteo de rechazos en aumento suele significar que el autor de la plantilla está referenciando recursos que su callback no reconoce, y un conteo de operaciones de diseño cercano al límite significa que el siguiente documento, apenas un poco más grande, fallará
Cómo elegir entre la importación de HTML y la API de diseño
La importación de HTML gana cuando el contenido lo redactan en HTML personas que no son desarrolladores Delphi, cuando las plantillas cambian con más frecuencia que el código, o cuando el mismo contenido también debe enviarse como correo electrónico. El árbol de diseño declarativo descrito en diseño declarativo de PDF con salida etiquetada gana cuando el documento lo genera su propio código a partir de datos, cuando necesita que la estructura PDF/UA se emita automáticamente, o cuando el control preciso sobre la paginación importa más que la comodidad de redacción
No son excluyentes. Un patrón común es usar importación de HTML para el cuerpo de un informe, con el encabezado, el pie de página y los bloques de firma dibujados directamente, ya que esos tienen una geometría fija que ninguna plantilla debería controlar. Las tablas son la única área donde vale la pena medir ambos enfoques: las tablas financieras complejas a veces paginan mejor mediante la ruta dedicada de tablas descrita en generar tablas en PDF
Configure CreateAcroForms de forma deliberada. Desactivado, los controles de formulario HTML se renderizan como arte estático, que es lo que necesita un estado de cuenta impreso. Activado, se convierten en campos AcroForm realmente interactivos, que es lo que necesita un formulario rellenable, y el comportamiento de campo que sigue se explica en agregar campos de formulario a un PDF cargado
La importación de HTML, el diseño declarativo y la API de dibujo directo apuntan todos al mismo objeto de documento en Delphi y C++Builder; la lista completa de funciones está en la página del componente PDF para Delphi de HotPDF