Artículo técnico

Exportar TDataSet e informes a PDF en Delphi

Para exportar un TDataSet o un informe a PDF en Delphi, losLab PDF Library ofrece dos caminos. PDFlibTableExport adapta cualquier TDataSet (una consulta FireDAC, un ClientDataSet, una tabla en memoria) a una tabla PDF paginada, y tres puentes bajo Addons entregan un informe 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 y no lo son. Un DBGrid o el resultado en bruto de una consulta no tienen disposición propia, así que exportarlos significa inventar una: columnas, cabeceras, saltos de página. Un documento de FastReport o ReportBuilder ya lleva una disposición diseñada, así que exportarlo significa reproducir fielmente los comandos de dibujo de otra persona en el espacio PDF. losLab PDF Library mantiene estas responsabilidades en unidades separadas precisamente porque los modos de fallo difieren, y el resto de este artículo recorre cada una y el punto en el que deja de ser fiable

Dos rutas de exportación a PDF en Delphi que alimentan tablas de conjuntos de datos y puentes de informes hacia un único escritor de PDF sin interfaz
PDFlibTableExport inventa una tabla paginada para cualquier TDataSet mientras tres puentes de Addons reproducen las disposiciones de FastReport, QuickReport y ReportBuilder en el mismo escritor de PDF

¿Cómo se exporta un TDataSet a PDF en Delphi?

PDFlibTableExport convierte un conjunto de datos en una tabla con una sola llamada. El exportador recorre la lista de campos una vez, omite automáticamente los campos blob binarios (ftBlob, ftGraphic, ftBytes y sus parientes no tienen texto de celda útil), alinea a la derecha las columnas numéricas y pinta una franja cebra opcional bajo una banda de cabecera con estilo. Por debajo construye la cuadrícula con CreateTable, rellena las celdas con SetTableCellContent y renderiza con DrawTableRows, la misma API pública de tablas que manejarías a mano. El envoltorio de conveniencia PDFlibExportDataSet configura por ti los milímetros y un origen en la esquina superior izquierda, así que quien llama solo aporta 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');   // aquí sirve cualquier TDataSet

    PDF.SetOrigin(1);
    PDF.SetMeasurementUnits(1);              // milímetros
    PDF.SetPageSize('A4');
    PDF.AddStandardFont(4);

    Options := DefaultTableExportOptions;
    Options.Title := 'Customers';
    Options.ColumnWidth := 32;               // 11 campos caben en A4 a 32 mm
    Options.RepeatHeader := True;            // redibujar la cabecera en cada página

    PDFlibExportDataSet(PDF, MemTable, 'customers.pdf', Options);
  finally
    PDF.Free;
    MemTable.Free;
  end;
end;

La omisión automática de blobs es un valor predeterminado, no una camisa de fuerza. Cuando necesites control por campo (una etiqueta de columna personalizada, un ancho más estrecho, o descartar una columna de clave interna que no es un blob), construye un TPDFlibTableExporter directamente y engancha su evento OnFieldFilter, que se dispara una vez por campo y te entrega una especificación modificable. Pon Include a False para descartar el campo, o asigna ColumnWidth y DisplayLabel para sobrescribir los valores predeterminados. Ese gancho es también donde excluyes una columna memo ancha que no quieres que hinche la página

Paginación: pasar el estado de dibujo entre páginas

DrawTableRows lleva el estado de paginación, y entender su contrato es todo el juego. Se llama con una primera fila y una última fila; pasar una última fila por debajo de 1 significa dibujar hasta el final de la tabla, acotado por la altura indicada. La función devuelve la altura que realmente dibujó, y GetTableLastDrawnRow informa de 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 no llega al total, se abre una nueva página y se reanuda desde la fila siguiente. Nada se vuelve a medir: la continuación retoma exactamente donde la llamada anterior se detuvo

PDF Library for Delphi: contrato de paginación de DrawTableRows que muestra filas de tabla continuando a lo largo de tres páginas PDF con una banda de cabecera repetida
DrawTableRows se empareja con GetTableLastDrawnRow para que cada página nueva se reanude en LastDrawn más uno y redibuje la fila de cabecera
// Cómo continúa el exportador una tabla larga a lo largo de varias páginas
PageHeight := PDF.PageHeight;
Y := PageHeight - Options.Top;
Row := 1;
while Row <= TotalRows do
begin
  DrawHeight := Y - Options.BottomMargin;
  // Última fila = 0 significa "dibujar hasta el final", acotado por DrawHeight
  PDF.DrawTableRows(TableID, Options.Left, Y, DrawHeight, Row, 0);
  LastDrawn := PDF.GetTableLastDrawnRow(TableID);
  if LastDrawn >= TotalRows then
    Break;                       // toda la tabla cupo en esta página
  if LastDrawn < Row then
    Break;                       // seguridad: sin avance, abandonar
  PDF.NewPage;
  Row := LastDrawn + 1;          // continuar desde la primera fila sin dibujar
  Y := PageHeight - Options.Top;
end;

El bucle es también donde vive la cabecera repetida. En cada página de continuación el exportador dibuja opcionalmente de nuevo la fila 1 (la cabecera) antes de las filas de datos, y usa la altura devuelta por esa primera llamada a DrawTableRows para desplazar el cuerpo por debajo. Por eso DrawTableRows devuelve una altura y no la coordenada de la siguiente fila: el valor de retorno es lo que permite apilar una cabecera redibujada y el cuerpo que continúa sin codificar a mano el tamaño de ninguno de los dos

¿Cómo se exporta un informe de FastReport o ReportBuilder a PDF?

Los puentes de informes adoptan el enfoque opuesto: nunca inventan una disposición, reproducen una. Cada puente se acopla a su motor en la costura de exportación nativa del motor. PDFlibFRExport hereda de TfrxCustomExportFilter y recibe los objetos memo, imagen, forma y línea de FastReport, traduciendo cada uno a una primitiva de PDF Library for Delphi. PDFlibRBDevice hereda de TppFileDevice y recorre la lista DrawCommand de ReportBuilder. PDFlibQRExport toma una tercera vía completamente distinta, que cubre la sección siguiente. Los tres funcionan sin interfaz, que es toda la razón para recurrir a ellos en un servidor

Funcionar sin interfaz no sale gratis, y FastReport es el cuento con moraleja. TfrxReport.Export pasa por las páginas de vista previa, que consultan la propiedad ShowDialog del filtro, y esa propiedad hereda un valor predeterminado de True. En una máquina sin escritorio interactivo el diálogo modal devuelve un resultado de cancelación y la exportación falla en silencio. Pon ShowDialog a False antes de llamar a Export y el informe se renderiza sin ruido. ReportBuilder tiene los interruptores paralelos (AllowPrintToFile y ShowPrintDialog), y QuickReport, que lo conduce todo a través de Prepare, no necesita suprimir ningún diálogo

var
  Exporter: TPDFlibFRExport;
begin
  Report.PrepareReport;                  // construir primero las páginas
  Exporter := TPDFlibFRExport.Create(nil);
  try
    Exporter.FileName := 'invoice.pdf';
    Exporter.ShowDialog := False;        // sin interfaz: omitir el modal, sin cancelación
    Report.Export(Exporter);
  finally
    Exporter.Free;
  end;
end;

Tres motores, tres sistemas de coordenadas

Cada puente hace su propia aritmética de coordenadas, porque cada motor mide el mundo de forma distinta. PDFlibFRExport trata las posiciones de los objetos de FastReport como píxeles a 96 PPI y escala por 96/25,4 para llegar a milímetros. PDFlibRBDevice lee los comandos de dibujo de ReportBuilder en milésimas de milímetro y divide entre 1000, y se registra a sí mismo mediante ppRegisterDevice, así que basta con poner ppReport.DeviceType a 'PDF Library for Delphi' y llamar a Print para encaminar la salida a través de él. PDFlibQRExport no hace ningún cálculo de coordenadas: una página preparada de QuickReport ya es un metarchivo EMF, así que el puente guarda cada página en un stream y se la pasa a ImportEMFFromStream, dejando que la ruta de renderizado de contextos de dispositivo y metarchivos de la biblioteca coloque cada glifo y cada filete. Los puentes de FastReport y ReportBuilder, en cambio, emiten primitivas nativas de dibujo vectorial y texto nativo

PDF Library for Delphi: conversiones de sistemas de coordenadas realizadas por los puentes de exportación a PDF de FastReport, ReportBuilder y QuickReport
Los píxeles de FastReport, las milésimas de milímetro de ReportBuilder y las páginas EMF de QuickReport se convierten cada uno antes de llegar al núcleo de dibujo PDF
var
  PDF: TPDFlib;
begin
  PDF := TPDFlib.Create;
  try
    PDF.SetOrigin(1);
    // Prepara el informe y añade todas las páginas en una sola llamada; el puente
    // convierte el EMF de cada página en PDF mediante ImportEMFFromStream.
    PDFlibQRExportReport(PDF, QuickRep1, 'ledger.pdf');
  finally
    PDF.Free;
  end;
end;

Dónde termina la fidelidad

Conoce los límites antes de confiar un flujo de trabajo a estos puentes. El exportador de conjuntos de datos descarta las columnas binarias en silencio, así que un informe que deba mostrar una imagen incrustada necesita un enfoque distinto a una tabla directa. La ruta de QuickReport, al pasar por EMF, convierte el texto en glifos vectoriales: la página se ve bien pero no lleva texto seleccionable ni buscable, ni estructura de PDF etiquetado para accesibilidad. FastReport y ReportBuilder conservan texto real, pero los manejadores tipados cubren las vistas comunes (memo, imagen, forma, línea) y recurren a un cuadro delimitador u omiten las exóticas, así que un informe que se apoye en texto enriquecido, códigos de barras o rellenos degradados perderá detalle. Nada de esto es un defecto; es el borde honesto de una capa de traducción

Una advertencia operativa está por encima de las demás. Ninguno de los tres motores se incluye dentro de losLab PDF Library, y los puentes están deliberadamente excluidos de la compilación principal: solo 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 DrawCommand a las que apuntan se han mantenido estables a lo largo de varias versiones mayores de los motores, pero la correspondencia de versiones corre de tu cuenta. Con eso bien resuelto, los dos caminos cubren todo el abanico, desde un volcado improvisado de un DBGrid hasta una factura diseñada. El exportador de tablas y los puentes de informes se incluyen ambos con losLab PDF Library para Delphi y C++Builder