Para exportar un TDataSet o un reporte a PDF en Delphi, losLab PDF Library ofrece dos caminos. PDFlibTableExport adapta cualquier TDataSet (una consulta FireDAC, un ClientDataSet, una tabla en memoria) en una tabla PDF paginada, y tres puentes bajo Addons entregan un reporte preparado de FastReport, QuickReport o ReportBuilder al mismo escritor de PDF. Ambos producen un PDF real sin controlador de impresora y sin ventana visible
Los dos problemas parecen similares pero no lo son. Un DBGrid o el resultado de una consulta sin procesar no tienen un diseño propio, por lo que exportarlo significa inventar uno: columnas, encabezados, saltos de página. Un documento de FastReport o ReportBuilder ya lleva un diseño preestablecido, por lo que exportarlo significa reproducir fielmente los comandos de dibujo de otro componente en el espacio del PDF. losLab PDF Library mantiene estas tareas en unidades separadas precisamente porque los modos de falla difieren, y el resto de este artículo recorre cada uno de ellos y los puntos donde dejan de ser confiables
¿Cómo se exporta un TDataSet a PDF en Delphi?
PDFlibTableExport convierte un conjunto de datos en una tabla en una sola llamada. El exportador recorre la lista de campos una vez, omite los campos blob binarios automáticamente (ftBlob, ftGraphic, ftBytes y sus similares no tienen texto de celda útil), alinea a la derecha las columnas numéricas y pinta una franja de cebra opcional sobre una banda de encabezado con estilo. Internamente, compila la cuadrícula con CreateTable, llena las celdas con SetTableCellContent y realiza la renderización con DrawTableRows, la misma API pública de tablas que usted manejaría manualmente. El contenedor de conveniencia PDFlibExportDataSet configura los milímetros y un origen superior izquierdo para usted, de modo que el llamador solo proporciona una página
uses
Data.DB, FireDAC.Comp.Client, FireDAC.Stan.StorageBin,
PDFlibrary, PDFlibTableExport;
var
MemTable: TFDMemTable;
PDF: TPDFlib;
Options: TPDFlibTableExportOptions;
begin
MemTable := TFDMemTable.Create(nil);
PDF := TPDFlib.Create;
try
MemTable.LoadFromFile('customer.FDS'); // any TDataSet works here
PDF.SetOrigin(1);
PDF.SetMeasurementUnits(1); // millimetres
PDF.SetPageSize('A4');
PDF.AddStandardFont(4);
Options := DefaultTableExportOptions;
Options.Title := 'Customers';
Options.ColumnWidth := 32; // 11 fields fit on A4 at 32 mm
Options.RepeatHeader := True; // redraw the header on each page
PDFlibExportDataSet(PDF, MemTable, 'customers.pdf', Options);
finally
PDF.Free;
MemTable.Free;
end;
end;
La omisión automática de blobs es un comportamiento predeterminado, no una limitación estricta. Cuando necesite control por campo (una etiqueta de columna personalizada, un ancho más angosto o descartar una columna clave interna que no sea un blob), construya un TPDFlibTableExporter directamente y asocie su evento OnFieldFilter, el cual se activa una vez por campo y le entrega una especificación modificable. Establezca Include en False para descartar el campo, o configure ColumnWidth y DisplayLabel para anular los valores predeterminados. Ese evento es también donde se excluye una columna de tipo memo ancha que no desee que aumente excesivamente el tamaño de la página
Paginación: pasar el estado de dibujo entre páginas
DrawTableRows lleva el estado de paginación, y entender su comportamiento es la clave de todo. Usted lo llama con una primera fila y una última fila; pasar una última fila menor a 1 significa dibujar hasta el final de la tabla, limitado por la altura que indicó. La función devuelve la altura que realmente dibujó, y GetTableLastDrawnRow reporta la última fila que cupo. Ese par es el estado que se pasa de una página a la siguiente: cuando la última fila dibujada se queda corta con respecto al total, usted abre una nueva página y continúa desde la fila siguiente. No se vuelve a medir nada: la continuación se reanuda exactamente donde se quedó la llamada anterior
// How the exporter continues a long table across pages
PageHeight := PDF.PageHeight;
Y := PageHeight - Options.Top;
Row := 1;
while Row <= TotalRows do
begin
DrawHeight := Y - Options.BottomMargin;
// Last row = 0 means "draw to the end", bounded by DrawHeight
PDF.DrawTableRows(TableID, Options.Left, Y, DrawHeight, Row, 0);
LastDrawn := PDF.GetTableLastDrawnRow(TableID);
if LastDrawn >= TotalRows then
Break; // the whole table fit on this page
if LastDrawn < Row then
Break; // safety: no forward progress, bail out
PDF.NewPage;
Row := LastDrawn + 1; // continue from the first undrawn row
Y := PageHeight - Options.Top;
end;
El bucle es también donde reside el encabezado repetido. En cada página de continuación, el exportador dibuja opcionalmente la fila 1 de nuevo (el encabezado) antes de las filas de datos, y utiliza la altura devuelta por esa primera llamada a DrawTableRows para desplazar el cuerpo debajo de ella. Es por esto que DrawTableRows devuelve una altura en lugar de una coordenada de la siguiente fila: el valor de retorno es lo que le permite apilar un encabezado vuelto a dibujar y el cuerpo continuo sin codificar de forma rígida el tamaño de ninguno de los dos
¿Cómo se exporta un reporte de FastReport o ReportBuilder a PDF?
Los puentes de reportes adoptan el enfoque opuesto: nunca inventan un diseño, lo reproducen. Cada puente se conecta a su motor en el punto de exportación nativo del mismo. PDFlibFRExport subclasifica TfrxCustomExportFilter y recibe los objetos de tipo memo, imagen (picture), forma (shape) y línea de FastReport, traduciendo cada uno en una primitiva de PDFlibPas. PDFlibRBDevice subclasifica TppFileDevice y recorre la lista DrawCommand de ReportBuilder. PDFlibQRExport toma una ruta completamente diferente, la cual se cubre en la siguiente sección. Los tres se ejecutan sin interfaz gráfica (headless), que es la razón principal para utilizarlos en un servidor
La ejecución sin interfaz gráfica no es gratuita, y FastReport es el ejemplo de advertencia. TfrxReport.Export se enruta a través de las páginas de vista previa, las cuales consultan la propiedad ShowDialog del filtro, y esa propiedad hereda un valor predeterminado de True. En un equipo sin escritorio interactivo, el cuadro de diálogo modal devuelve un resultado de cancelación y la exportación falla silenciosamente. Establezca ShowDialog en False antes de llamar a Export y el reporte se renderizará de forma silenciosa. ReportBuilder tiene los interruptores paralelos (AllowPrintToFile y ShowPrintDialog), y QuickReport, que maneja todo a través de Prepare, no necesita ninguna supresión de diálogo
var
Exporter: TPDFlibFRExport;
begin
Report.PrepareReport; // build the pages first
Exporter := TPDFlibFRExport.Create(nil);
try
Exporter.FileName := 'invoice.pdf';
Exporter.ShowDialog := False; // headless: skip the modal, no cancel
Report.Export(Exporter);
finally
Exporter.Free;
end;
end;
Tres motores, tres sistemas de coordenadas
Cada puente realiza su propia aritmética de coordenadas, porque cada motor mide el mundo de manera diferente. PDFlibFRExport trata las posiciones de los objetos de FastReport como píxeles a 96 PPI y las escala por 96/25.4 para obtener milímetros. PDFlibRBDevice lee los comandos de dibujo de ReportBuilder en milésimas de milímetro y los divide por 1000, y se registra a sí mismo a través de ppRegisterDevice, por lo que establecer ppReport.DeviceType en 'PDFlibPas' y llamar a Print es suficiente para enrutar la salida a través de él. PDFlibQRExport no realiza ningún cálculo de coordenadas: una página preparada de QuickReport ya es un metarchivo EMF, por lo que el puente guarda cada página en un flujo de datos (stream) y la introduce en ImportEMFFromStream, permitiendo que la ruta de renderización de metarchivos y del contexto de dispositivo de la biblioteca coloque cada glifo y línea. Los puentes de FastReport y ReportBuilder, por el contrario, emiten primitivas nativas de dibujo vectorial y texto nativo
var
PDF: TPDFlib;
begin
PDF := TPDFlib.Create;
try
PDF.SetOrigin(1);
// Prepares the report and appends every page in one call; the bridge
// turns each page's EMF into PDF through ImportEMFFromStream.
PDFlibQRExportReport(PDF, QuickRep1, 'ledger.pdf');
finally
PDF.Free;
end;
end;
Dónde termina la fidelidad
Conozca los límites antes de confiar un flujo de trabajo a estos puentes. El exportador de conjuntos de datos descarta las columnas binarias silenciosamente, por lo que un reporte que deba mostrar una imagen incrustada necesita un enfoque diferente al de una tabla simple. La ruta de QuickReport, debido a que pasa por EMF, convierte el texto en glifos vectoriales: la página se ve bien pero no contiene texto seleccionable ni de búsqueda, ni tampoco una estructura PDF etiquetada para accesibilidad. FastReport y ReportBuilder mantienen texto real, pero los controladores tipificados cubren las vistas comunes (memo, imagen, forma, línea) y recurren a un cuadro delimitador u omiten las exóticas, por lo que un reporte que dependa de texto enriquecido, códigos de barras o rellenos degradados perderá detalles. Nada de esto es un defecto; es el límite real de una capa de traducción
Una advertencia operativa supera al resto. Ninguno de los tres motores se incluye dentro de losLab PDF Library, y los puentes se excluyen deliberadamente de la compilación principal: solo se compilan desde un proyecto que ya tenga FastReport VCL 6.x, QuickReport 8 o ReportBuilder 20 en su ruta de búsqueda. Las superficies de metarchivo y de DrawCommand a las que se dirigen se han mantenido estables en varias versiones principales de los motores, pero usted es responsable de la coincidencia de las versiones. Hágalo bien y los dos caminos cubrirán todo el espectro, desde un volcado ad-hoc de DBGrid hasta una factura diseñada. El exportador de tablas y los puentes de reportes se incluyen con losLab PDF Library para Delphi y C++Builder