Article technique

Exporter TDataSet et des rapports en PDF dans Delphi

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