Pour exporter un TDataSet ou un rapport en PDF dans Delphi, la bibliothèque PDF losLab propose deux voies. PDFlibTableExport adapte n'importe quel TDataSet — une requête FireDAC, un ClientDataSet, une table en mémoire — en un tableau PDF paginé, et trois passerelles sous Addons transmettent un rapport FastReport, QuickReport ou ReportBuilder préparé au même moteur d'écriture PDF. Les deux produisent un véritable PDF sans pilote d'imprimante et sans fenêtre visible
Les deux problèmes semblent similaires mais ne le sont pas. Un DBGrid ou un résultat de requête brut n'a pas de mise en page propre, l'exporter signifie donc en inventer une : colonnes, en-têtes, sauts de page. Un document FastReport ou ReportBuilder contient déjà une mise en page conçue, l'exporter signifie donc rejouer fidèlement les commandes de dessin de quelqu'un d'autre dans l'espace PDF. La bibliothèque PDF losLab conserve ces préoccupations dans des unités distinctes précisément parce que les modes de défaillance diffèrent, et le reste de cet article détaille chacun d'eux et là où ils cessent d'être fiables
Comment exporter un TDataSet en PDF dans Delphi ?
PDFlibTableExport transforme un jeu de données en tableau en un seul appel. L'exportateur parcourt la liste des champs une fois, ignore automatiquement les champs blob binaires — ftBlob, ftGraphic, ftBytes et leurs proches n'ont pas de texte de cellule utile — aligne à droite les colonnes numériques et peint une bande zébrée optionnelle sur une bande d'en-tête stylisée. Sous le capot, il construit la grille avec CreateTable, remplit les cellules avec SetTableCellContent et effectue le rendu avec DrawTableRows, la même API publique Table que vous piloteriez à la main. Le wrapper de commodité PDFlibExportDataSet configure pour vous les millimètres et une origine en haut à gauche, de sorte que l'appelant ne fournit qu'une page
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;
L'exclusion automatique des blobs est un comportement par défaut, pas une contrainte absolue. Lorsque vous avez besoin d'un contrôle par champ — une étiquette de colonne personnalisée, une largeur plus étroite ou la suppression d'une colonne de clé interne qui n'est pas un blob — construisez directement un TPDFlibTableExporter et associez son événement OnFieldFilter, qui se déclenche une fois par champ et vous transmet une spécification mutable. Définissez Include sur False pour supprimer le champ, ou définissez ColumnWidth et DisplayLabel pour remplacer les valeurs par défaut. Ce hook est également l'endroit où vous excluez une large colonne mémo que vous ne voulez pas voir gonfler la page
Pagination : passage de l'état de dessin entre les pages
DrawTableRows transporte l'état de pagination, et comprendre son contrat est tout l'enjeu. Vous l'appelez avec une première ligne et une dernière ligne ; passer une dernière ligne inférieure à 1 signifie dessiner jusqu'à la fin du tableau, limitée par la hauteur que vous avez indiquée. La fonction renvoie la hauteur qu'elle a réellement dessinée, et GetTableLastDrawnRow indique la dernière ligne qui a pu tenir. Cette paire est l'état que vous transmettez d'une page à la suivante : lorsque la dernière ligne dessinée est inférieure au total, vous ouvrez une nouvelle page et reprenez à partir de la ligne suivante. Rien n'est mesuré à nouveau — la suite reprend exactement là où l'appel précédent s'est arrêté
// 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;
La boucle est également l'endroit où réside l'en-tête répété. Sur chaque page de continuation, l'exportateur dessine éventuellement à nouveau la ligne 1 — l'en-tête — avant les lignes de données, et il utilise la hauteur renvoyée par ce premier appel à DrawTableRows pour décaler le corps en dessous. C'est pourquoi DrawTableRows renvoie une hauteur plutôt qu'une coordonnée de ligne suivante : la valeur de retour est ce qui vous permet de superposer un en-tête redessiné et le corps qui continue sans coder en dur la taille de l'un ou de l'autre
Comment exporter un rapport FastReport ou ReportBuilder en PDF ?
Les passerelles de rapport adoptent l'approche inverse : elles n'inventent jamais de mise en page, elles en rejouent une. Chaque passerelle se connecte à son moteur au niveau de la jonction d'exportation native du moteur. PDFlibFRExport sous-classe TfrxCustomExportFilter et reçoit les objets mémo, image, forme et ligne de FastReport, traduisant chacun en une primitive PDFlibPas. PDFlibRBDevice sous-classe TppFileDevice et parcourt la liste DrawCommand de ReportBuilder. PDFlibQRExport emprunte une troisième voie totalement différente, que la section suivante détaille. Tous trois fonctionnent sans tête (headless), ce qui est la seule raison de les utiliser sur un serveur
Le mode sans tête n'est pas gratuit, et FastReport en est l'exemple type. TfrxReport.Export passe par les pages d'aperçu, qui consultent la propriété ShowDialog du filtre, et cette propriété hérite d'une valeur par défaut à True. Sur une machine sans bureau interactif, la boîte de dialogue modale renvoie un résultat d'annulation et l'exportation échoue silencieusement. Définissez ShowDialog sur False avant d'appeler Export et le rapport s'affiche silencieusement. ReportBuilder possède des commutateurs parallèles — AllowPrintToFile and ShowPrintDialog — et QuickReport, qui pilote tout via Prepare, n'a pas besoin de suppression de boîte de dialogue du tout
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;
Trois moteurs, trois systèmes de coordonnées
Chaque passerelle effectue sa propre arithmétique de coordonnées, car chaque moteur mesure le monde différemment. PDFlibFRExport traite les positions des objets FastReport comme des pixels à 96 PPP et les met à l'échelle par 96/25,4 pour obtenir des millimètres. PDFlibRBDevice lit les commandes de dessin de ReportBuilder en millièmes de mm et les divise par 1000, et s'enregistre via ppRegisterDevice, de sorte qu'il suffit de définir ppReport.DeviceType sur 'PDFlibPas' et d'appeler Print pour y acheminer la sortie. PDFlibQRExport ne fait aucun calcul de coordonnées : une page QuickReport préparée est déjà un métafichier EMF, la passerelle enregistre donc chaque page dans un flux et la transmet à ImportEMFFromStream, laissant le chemin de rendu de contexte de périphérique et de métafichier de la bibliothèque placer chaque glyphe et règle. Les passerelles FastReport et ReportBuilder, en revanche, émettent des primitives de dessin vectoriel natives et du texte natif
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;
Là où s'arrête la fidélité
Connaissez les limites avant d'engager un flux de travail vers ces passerelles. L'exportateur de jeux de données supprime silencieusement les colonnes binaires, de sorte qu'un rapport devant afficher une image intégrée nécessite une approche différente de celle d'un simple tableau. Le chemin QuickReport, parce qu'il passe par l'EMF, fige le texte dans des glyphes vectoriels — la page semble correcte mais ne contient aucun texte sélectionnable ou consultable, ni de structure PDF balisée pour l'accessibilité. FastReport et ReportBuilder conservent le vrai texte, mais les gestionnaires typés couvrent les vues courantes — mémo, image, forme, ligne — et se rabattent sur une boîte englobante ou ignorent les plus exotiques, de sorte qu'un rapport s'appuyant sur du texte enrichi, des codes-barres ou des remplissages de dégradés perdra des détails. Rien de tout cela n'est un défaut ; c'est la limite honnête d'une couche de traduction
Une mise en garde opérationnelle l'emporte sur le reste. Aucun des trois moteurs n'est livré dans la bibliothèque PDF losLab, et les passerelles sont délibérément exclues de la build principale — elles ne se compilent qu'à partir d'un projet qui a déjà FastReport VCL 6.x, QuickReport 8 ou ReportBuilder 20 dans son chemin de recherche. Les surfaces de métafichier et DrawCommand qu'elles ciblent sont restées stables sur plusieurs versions majeures de moteurs, mais vous êtes responsable de la correspondance des versions. Si vous y parvenez, les deux voies couvrent tout le spectre, d'un vidage DBGrid ad hoc à une facture personnalisée. L'exportateur de tableaux et les passerelles de rapport sont tous deux livrés avec la bibliothèque PDF losLab pour Delphi et C++Builder