Per esportare un TDataSet o un report in PDF in Delphi, losLab PDF Library offre due strade. PDFlibTableExport adatta qualsiasi TDataSet — una query FireDAC, un ClientDataSet, una tabella in memoria — in una tabella PDF impaginata, e tre bridge in Addons passano un report FastReport, QuickReport o ReportBuilder preparato allo stesso PDF writer. Entrambi producono un vero PDF senza driver di stampa e senza finestre visibili
I due problemi sembrano simili ma non lo sono. Una DBGrid o il risultato di una query grezza non hanno un layout proprio, quindi esportarli significa inventarne uno: colonne, intestazioni, interruzioni di pagina. Un documento FastReport o ReportBuilder contiene già un layout progettato, quindi esportarlo significa riprodurre fedelmente i comandi di disegno di qualcun altro nello spazio PDF. losLab PDF Library mantiene queste preoccupazioni in unità separate proprio perché le modalità di guasto differiscono, e il resto di questo articolo esamina ciascuna di esse e dove cessano di essere affidabili
Come si esporta un TDataSet in PDF in Delphi?
PDFlibTableExport trasforma un dataset in una tabella con una sola chiamata. L'esportatore scorre l'elenco dei campi una volta, salta automaticamente i campi blob binari — ftBlob, ftGraphic, ftBytes e affini non hanno testo utile per le celle —, allinea a destra le colonne numeriche e disegna una riga zebrata opzionale su una banda di intestazione stilizzata. Sotto il cofano costruisce la griglia con CreateTable, riempie le celle con SetTableCellContent e ne esegue il rendering con DrawTableRows, la stessa API Table pubblica che guideresti a mano. Il wrapper di comodo PDFlibExportDataSet configura per te i millimetri e l'origine in alto a sinistra, quindi il chiamante deve solo fornire una pagina
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;
Il salto automatico dei blob è un comportamento predefinito, non una limitazione. Quando hai bisogno di un controllo per campo — un'etichetta di colonna personalizzata, una larghezza inferiore o l'esclusione di una colonna chiave interna che non è un blob — costruisci direttamente un TPDFlibTableExporter e aggancia il suo evento OnFieldFilter, che si attiva una volta per campo e ti passa una specifica modificabile. Imposta Include su False per escludere le colonne desiderate, oppure imposta ColumnWidth e DisplayLabel per ignorare i valori predefiniti. Questo hook è anche il punto in cui escludi una colonna memo di grandi dimensioni che non vuoi che gonfi la pagina
Impaginazione: passaggio dello stato di disegno tra le pagine
DrawTableRows trasporta lo stato di impaginazione e comprendere il suo contratto è fondamentale. Lo chiami con una prima e un'ultima riga; passare un'ultima riga inferiore a 1 significa disegnare fino alla fine della tabella, limitatamente all'altezza specificata. La funzione restituisce l'altezza effettivamente disegnata e GetTableLastDrawnRow riporta l'ultima riga che è entrata. Questa coppia è lo stato che passi da una pagina all'altra: quando l'ultima riga disegnata è inferiore al totale, apri una nuova pagina e riprendi dalla riga successiva. Nulla viene rimisurato — la continuazione riprende esattamente da dove si era interrotta la chiamata precedente
// Come l'esportatore continua una tabella lunga su più pagine
PageHeight := PDF.PageHeight;
Y := PageHeight - Options.Top;
Row := 1;
while Row <= TotalRows do
begin
DrawHeight := Y - Options.BottomMargin;
// L'ultima riga = 0 significa "disegna fino alla fine", limitata da DrawHeight
PDF.DrawTableRows(TableID, Options.Left, Y, DrawHeight, Row, 0);
LastDrawn := PDF.GetTableLastDrawnRow(TableID);
if LastDrawn >= TotalRows then
Break; // l'intera tabella è entrata in questa pagina
if LastDrawn < Row then
Break; // sicurezza: nessun progresso, esci
PDF.NewPage;
Row := LastDrawn + 1; // continua dalla prima riga non disegnata
Y := PageHeight - Options.Top;
end;
Il ciclo è anche il luogo in cui risiede l'intestazione ripetuta. In ogni pagina di continuazione, l'esportatore disegna opzionalmente di nuovo la riga 1 — l'intestazione — prima delle righe di dati, e utilizza l'altezza restituita da quella prima chiamata DrawTableRows per sfalsare il corpo sottostante. Ecco perché DrawTableRows restituisce un'altezza anziché la coordinata della riga successiva: il valore restituito è ciò che consente di impilare un'intestazione ridisegnata e il corpo che continua senza cablare le dimensioni di nessuno dei due
Come si esporta un report FastReport o ReportBuilder in PDF?
I bridge per i report adottano l'approccio opposto: non inventano mai un layout, lo riproducono. Ciascun bridge si collega al proprio motore nel punto di esportazione nativo del motore stesso. PDFlibFRExport eredita da TfrxCustomExportFilter e riceve gli oggetti memo, picture, shape e line di FastReport, traducendo ciascuno in una primitiva PDFlibPas. PDFlibRBDevice eredita da TppFileDevice e scorre l'elenco DrawCommand di ReportBuilder. PDFlibQRExport intraprende una terza strada del tutto diversa, trattata nella sezione successiva. Tutti e tre funzionano in modalità headless, che è l'unico motivo per sceglierli su un server
La modalità headless non è gratuita, e FastReport ne è il classico esempio. TfrxReport.Export passa attraverso le pagine di anteprima, che consultano la proprietà ShowDialog del filtro, e tale proprietà eredita il valore predefinito True. Su una macchina senza desktop interattivo, la finestra di dialogo modale restituisce un risultato di annullamento e l'esportazione fallisce silenziosamente. Imposta ShowDialog su False prima di chiamare Export e il report verrà renderizzato silenziosamente. ReportBuilder ha interruttori paralleli — AllowPrintToFile e ShowPrintDialog — e QuickReport, che gestisce tutto tramite Prepare, non ha affatto bisogno di sopprimere le finestre di dialogo
var
Exporter: TPDFlibFRExport;
begin
Report.PrepareReport; // compila prima le pagine
Exporter := TPDFlibFRExport.Create(nil);
try
Exporter.FileName := 'invoice.pdf';
Exporter.ShowDialog := False; // headless: salta la modale, nessun annullamento
Report.Export(Exporter);
finally
Exporter.Free;
end;
end;
Tre motori, tre sistemi di coordinate
Ciascun bridge esegue i propri calcoli sulle coordinate, poiché ogni motore misura lo spazio in modo diverso. PDFlibFRExport gestisce le posizioni degli oggetti di FastReport como pixel a 96 PPI e le scala di 96/25.4 per ottenere i millimetri. PDFlibRBDevice legge i comandi di disegno di ReportBuilder in millesimi di millimetro e li divide per 1000, e si registra tramite ppRegisterDevice, quindi impostare ppReport.DeviceType su 'PDFlibPas' e chiamare Print è sufficiente per instradare l'output attraverso di esso. PDFlibQRExport non esegue affatto calcoli sulle coordinate: una pagina QuickReport preparata è già un metafile EMF, quindi il bridge salva ogni pagina in uno stream e la passa a ImportEMFFromStream, lasciando che il device-context e il percorso di rendering del metafile della libreria posizionino ogni glifo e riga. I bridge FastReport e ReportBuilder, al contrario, emettono primitive di disegno vettoriale native e testo nativo
var
PDF: TPDFlib;
begin
PDF := TPDFlib.Create;
try
PDF.SetOrigin(1);
// Prepara il report e aggiunge ogni pagina in un'unica chiamata; il bridge
// trasforma l'EMF di ogni pagina in PDF tramite ImportEMFFromStream.
PDFlibQRExportReport(PDF, QuickRep1, 'ledger.pdf');
finally
PDF.Free;
end;
end;
Dove finisce la fedeltà
Conosci i limiti prima di affidare un flusso di lavoro a questi bridge. L'esportatore del dataset esclude le colonne binarie in modo silenzioso, quindi un report che deve mostrare un'immagine incorporata richiede un approccio diverso rispetto a una tabella semplice. Il percorso di QuickReport, poiché passa attraverso EMF, converte il testo in glifi vettoriali — la pagina appare corretta ma non contiene testo selezionabile o ricercabile né una struttura PDF taggata per l'accessibilità. FastReport e ReportBuilder mantengono il testo reale, ma i gestori tipizzati coprono le viste comuni — memo, picture, shape, line — e ripiegano su un rettangolo di delimitazione o saltano quelli più complessi, quindi un report che si basa su rich text, codici a barre o sfumature di colore perderà dettagli. Nulla di tutto ciò è un difetto; è il limite intrinseco di uno strato di traduzione
Un avvertimento operativo supera gli altri. Nessuno dei tre motori viene fornito all'interno di losLab PDF Library, e i bridge sono deliberatamente esclusi dalla build principale — compilano solo da un progetto che ha già FastReport VCL 6.x, QuickReport 8 o ReportBuilder 20 nel suo percorso di ricerca. Le superfici metafile e DrawCommand a cui si rivolgono sono rimaste stabili tra diverse versioni principali dei motori, ma la corrispondenza delle versioni spetta a te. Gestisci correttamente questo aspetto e le due strade copriranno l'intera gamma, da un dump DBGrid ad hoc a una fattura progettata. L'esportatore di tabelle e i bridge per i report sono entrambi forniti con losLab PDF Library per Delphi e C++Builder