PDF Library for Delphi renderiza HTML en una página PDF con una disposición bidimensional real: display: flex y display: grid se miden y se colocan en lugar de degradarse a bloques apilados, y las notas al pie se reservan en la parte inferior del cuadro que lleva su referencia, con una numeración que se mantiene continua entre columnas y páginas. Los puntos de entrada son los habituales, DrawHTMLTextBox para un único cuadro y DrawHTMLStory para flujo multicolumna
Esto importa porque el HTML es la forma en que llega ahora la mayor parte del contenido de informes. Las plantillas las redactan personas que escriben CSS, los paneles se diseñan como tarjetas, y un renderizador que colapsa silenciosamente una fila flex en cuatro bloques apilados produce un documento que no se parece en nada al diseño. Hasta que existió esta capacidad, el único contenedor bidimensional que medía el motor era la tabla, así que cada disposición de tarjetas había que reescribirla a mano como tabla
¿Qué cambió en el modelo de disposición?
El bucle principal anterior mantenía un único cuadro de línea y avanzaba página abajo. Ese modelo gestiona perfectamente el contenido en línea y los bloques apilados, y no puede expresar un contenedor cuyos hijos se dimensionan en relación entre sí. Las tablas eran la única excepción, con su propia medición en dos pasadas
Tanto flex como grid añaden una pasada de medición acotada sobre los hijos de un contenedor, y la palabra importante es «acotada». Un contenedor flex mide hasta 256 hijos directos en un array fijo. Un grid usa una matriz de ocupación de como máximo 64 por 64 celdas para una colocación automática determinista. Esos límites existen para que una hoja de estilos hostil o generada no pueda provocar recursión no acotada ni memoria de colocación cuadrática, algo que es una preocupación real cuando el HTML procede de una plantilla que edita un cliente
Cómo obtienen su tamaño los elementos flex
En la dirección de fila, el contenedor suma la base de cada elemento junto con sus pesos de crecimiento y reducción, y luego distribuye el espacio sobrante, positivo o negativo, según esos pesos. Con flex-wrap, cada línea se resuelve de forma independiente, así que una fila que se divide en dos líneas asigna el espacio libre por línea en lugar de en todo el contenedor. En la dirección de columna, la misma distribución del eje principal se ejecuta contra una altura explícita o la altura del contenido
justify-content, align-items, gap y las direcciones inversas operan sobre geometría ya medida. Mueven cuadros; nunca desencadenan una nueva medición del contenido de los elementos. Esa separación es lo que evita que un panel complejo mida sus hijos varias veces
uses
PDFlibrary;
var
Lib: TPDFlib;
Html, Remainder: WideString;
begin
Lib := TPDFlib.Create;
try
Lib.NewDocument;
Lib.SetPageSize('A4');
Lib.NewPage;
Html :=
'<div style="display:flex; gap:12px;">' +
' <div style="flex:2 1 0; background:#f4f6f8; padding:8px;">' +
' <b>Revenue</b><br/>EUR 4,182,300</div>' +
' <div style="flex:1 1 0; background:#f4f6f8; padding:8px;">' +
' <b>Margin</b><br/>18.4%</div>' +
' <div style="flex:1 1 0; background:#f4f6f8; padding:8px;">' +
' <b>Backlog</b><br/>92 days</div>' +
'</div>';
Remainder := Lib.DrawHTMLTextBox(40, 40, 515, 120, Html);
if Remainder <> '' then
Log('content did not fit - carry the remainder to the next box');
Lib.SaveToFile('dashboard.pdf');
finally
Lib.Free;
end;
end;
El valor de retorno es la cadena de continuación, que es la forma en que cada punto de entrada de dibujo HTML informa de lo que no cupo. Pásela al siguiente cuadro o a la siguiente página y el flujo se reanuda donde se detuvo
Colocación en grid, y qué puede ser una pista
Las pistas de grid aceptan longitudes fijas, porcentajes, la unidad fr, expresiones simples de repeat() y minmax(). La colocación automática rellena la matriz de ocupación de forma determinista, así que el mismo HTML siempre produce la misma disposición. Se permite que las coordenadas explícitas se superpongan, lo cual es deliberado: un diseño que superpone una insignia sobre una tarjeta expresa intención, no un error. Cuando solo se da explícitamente un eje, la colocación busca solo en el otro eje
Los elementos que abarcan varias filas aportan su altura medida de vuelta a las filas que cubren, promediada entre ellas, lo cual evita que un elemento alto que abarca varias filas comprima una sola fila mientras deja cortas a sus vecinas:
Html :=
'<div style="display:grid; grid-template-columns:repeat(3, 1fr); ' +
' gap:10px;">' +
' <div style="grid-row:span 2; background:#eef;">Site plan</div>' +
' <div>Inspector</div>' +
' <div>Date</div>' +
' <div style="grid-column:2 / span 2;">Findings summary</div>' +
'</div>';
Remainder := Lib.DrawHTMLTextBox(40, 180, 515, 260, Html);
Los hijos de flex y grid se renderizan a través del mismo renderizador HTML que todo lo demás, que es la propiedad que hace utilizable la función en lugar de convertirla en un mundo aparte. Las fuentes, la cascada CSS, los enlaces, las imágenes, las tablas y otros contenedores flex o grid anidados se comportan dentro de un elemento flex exactamente igual que en el nivel superior, y el plan de disposición externo registra los comandos finales de texto y rectángulo para que un dibujado repetido reutilice la caché de medición existente
¿Por qué las notas al pie son un problema de paginación?
Una nota al pie no es contenido que fluye después del párrafo que contiene su referencia; es contenido que debe aparecer en la parte inferior del mismo cuadro que su referencia. Eso invierte el orden de medición habitual, porque el espacio disponible para el texto del cuerpo depende ahora de contenido que todavía no se ha dispuesto
El renderizador, por tanto, mide la nota al encontrar la referencia, y resta el área de la nota del presupuesto de altura del cuerpo del cuadro acotado actual. Si la referencia, el texto del cuerpo acumulado hasta el momento y la nota no caben todos juntos, el marcador de nota al pie y todo lo que va después se trasladan juntos a la cadena de continuación. Esa regla es lo que evita los dos fallos clásicos: una nota que se superpone al texto del cuerpo, y una nota varada en una página cuya referencia está en la anterior
En un cuadro acotado, el área de la nota se fija en la parte inferior con una regla separadora encima. En la medición no acotada, donde no hay una altura de cuadro a la que fijarse, el área de la nota sigue inmediatamente después del cuerpo. La numeración se lleva en un campo de extensión de la pila de continuación, así que DrawHTMLTextBox y DrawHTMLStory mantienen la secuencia en marcha entre columnas y páginas, y una cadena de continuación producida antes de que existiera ese campo aún se reanuda correctamente
// Las notas al pie dentro de una historia multicolumna mantienen una única secuencia continua
Html := LoadTemplate('chapter.html'); // usa marcadores float:footnote
Remainder := Lib.DrawHTMLStory(40, 40, 515, 700,
2, // columnas
16, // canal en puntos
20, // páginas máximas para esta historia
Html);
if Remainder <> '' then
Log('story exceeded its page budget');
Guía práctica para autores de plantillas
Diseñe dentro de los límites documentados. Un contenedor flex con más de 256 hijos directos es casi siempre una tabla de datos disfrazada de flex, y la vía de tabla la mide mejor de todos modos. Un grid mayor de 64 por 64 es una hoja de cálculo, y se aplica el mismo consejo. Para el texto del cuerpo en varias columnas, el comportamiento de columnas y guionado descrito en el guionado y las columnas de texto equilibradas rige el aspecto del flujo dentro de cada columna
Mida antes de dibujar cuando una disposición tiene que encajar. GetHTMLTextHeight informa de la altura que necesitaría un ancho dado, que es la forma económica de decidir entre una disposición y otra antes de comprometer tinta. Y trate una cadena de continuación no vacía como algo normal y no excepcional: es el mecanismo por el que se pagina el contenido largo, no una señal de error
Cuando el HTML procede de un motor de informes en lugar de plantillas escritas a mano, la vía impulsada por conjuntos de datos de el motor de informes basado en dataset combina bien con esto, generando el marcado que flex y grid disponen después. Y cuando ese mismo contenido también tiene que salir de nuevo del PDF, la vía de exportación semántica de exportar PDF a Markdown y DOCX cierra el ciclo
La disposición HTML, la generación de informes y la exportación semántica forman parte de una misma biblioteca para Delphi, C++Builder y Free Pascal; la lista completa de funciones está en la página de PDF Library for Delphi