Technical Article

Export TDataSet and Reports to PDF in Delphi

Um ein TDataSet oder einen Bericht in Delphi nach PDF zu exportieren, bietet die losLab PDF Library zwei Wege an. PDFlibTableExport passt jedes TDataSet — eine FireDAC-Abfrage, ein ClientDataSet, eine In-Memory-Tabelle — in eine paginierte PDF-Tabelle an, und drei Bridges unter Addons übergeben einen vorbereiteten FastReport-, QuickReport- oder ReportBuilder-Bericht an denselben PDF-Writer. Beide erzeugen eine echte PDF-Datei ohne Druckertreiber und ohne sichtbares Fenster

Die beiden Probleme sehen ähnlich aus, sind es aber nicht. Ein DBGrid oder ein rohes Abfrageergebnis hat kein eigenes Layout, sodass der Export den Entwurf eines solchen erfordert: Spalten, Kopfzeilen, Seitenumbrüche. Ein FastReport- oder ReportBuilder-Dokument verfügt bereits über ein entworfenes Layout, sodass der Export darin besteht, die Zeichenbefehle eines anderen getreu in den PDF-Raum zu übertragen. Die losLab PDF Library hält diese Aufgaben in separaten Units, eben weil sich die Fehlerquellen unterscheiden, und der Rest dieses Artikels führt durch beide Bereiche und zeigt auf, wo die Zuverlässigkeit endet

Wie exportiert man ein TDataSet in Delphi nach PDF?

PDFlibTableExport konvertiert ein Dataset mit einem einzigen Aufruf in eine Tabelle. Der Exporter durchläuft die Feldliste einmal, überspringt binäre Blob-Felder automatisch — ftBlob, ftGraphic, ftBytes und verwandte Felder enthalten keinen nützlichen Zelltext —, richtet numerische Spalten rechtsbündig aus und zeichnet einen optionalen Zebrastreifen über einen gestalteten Header-Bereich. Im Hintergrund baut er das Gitter mit CreateTable auf, füllt Zellen mit SetTableCellContent und rendert mit DrawTableRows, derselben öffentlichen Tabellen-API, die Sie auch manuell steuern würden. Der praktische Wrapper PDFlibExportDataSet konfiguriert Millimeter und einen Ursprung oben links für Sie, sodass der Aufrufer nur eine Seite bereitstellen muss

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;

Das automatische Überspringen von Blobs ist eine Voreinstellung, kein Korsett. Wenn Sie die Kontrolle pro Feld benötigen — eine benutzerdefinierte Spaltenbeschriftung, eine geringere Breite oder das Weglassen einer internen Schlüsselspalte, die kein Blob ist —, erstellen Sie direkt einen TPDFlibTableExporter und verknüpfen Sie dessen OnFieldFilter-Ereignis, das einmal pro Feld ausgelöst wird und Ihnen eine änderbare Spezifikation übergibt. Setzen Sie Include auf False, um das Feld zu verwerfen, oder legen Sie ColumnWidth und DisplayLabel fest, um die Standardwerte zu überschreiben. Dieser Hook ist auch der Ort, an dem Sie eine breite Memo-Spalte ausschließen, von der Sie nicht möchten, dass sie die Seite sprengt

Seitenaufteilung: Übergabe des Zeichnungszustands zwischen Seiten

DrawTableRows trägt den Zustand der Seitenaufteilung, und das Verständnis dieses Vertrags ist entscheidend. Sie rufen es mit einer ersten Zeile und einer letzten Zeile auf; die Übergabe einer letzten Zeile kleiner als 1 bedeutet, bis zum Ende der Tabelle zu zeichnen, begrenzt durch die angegebene Höhe. Die Funktion gibt die tatsächlich gezeichnete Höhe zurück, und GetTableLastDrawnRow meldet die letzte Zeile, die passte. Dieses Paar ist der Zustand, den Sie von einer Seite an die nächste übergeben: Wenn die letzte gezeichnete Zeile vor dem Ende liegt, öffnen Sie eine neue Seite und fahren mit der Zeile danach fort. Es wird nichts neu gemessen — die Fortsetzung setzt genau dort an, wo der vorherige Aufruf aufgehört hat

// 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;

Die Schleife ist auch der Ort, an dem sich die wiederholende Kopfzeile befindet. Auf jeder Fortsetzungsseite zeichnet der Exporter optional Zeile 1 erneut — die Kopfzeile — vor den Datenzeilen und verwendet die von diesem ersten DrawTableRows-Aufruf zurückgegebene Höhe, um den Textkörper darunter zu versetzen. Aus diesem Grund gibt DrawTableRows eine Höhe statt einer Koordinate für die nächste Zeile zurück: Der Rückgabewert ermöglicht es Ihnen, eine neu gezeichnete Kopfzeile und den fortlaufenden Textkörper übereinander zu legen, ohne die Größe eines der beiden hart zu codieren

Wie exportiert man einen FastReport- oder ReportBuilder-Bericht nach PDF?

Die Bericht-Bridges wählen den entgegengesetzten Ansatz: Sie entwerfen niemals ein Layout, sondern spielen eines ab. Jede Bridge verbindet sich mit ihrer Engine an der nativen Exportschnittstelle der Engine. PDFlibFRExport leitet von TfrxCustomExportFilter ab und empfängt die Memo-, Bild-, Form- und Linienobjekte von FastReport und übersetzt jedes in ein PDFlibPas-Primitiv. PDFlibRBDevice leitet von TppFileDevice ab und durchläuft die DrawCommand-Liste von ReportBuilder. PDFlibQRExport wählt einen völlig dritten Weg, den der nächste Abschnitt behandelt. Alle drei laufen headless, was der eigentliche Grund ist, sie auf einem Server einzusetzen

Headless ist nicht umsonst, und FastReport ist das warnende Beispiel. TfrxReport.Export leitet über die Vorschauseiten, die die Eigenschaft ShowDialog des Filters abfragen, und diese Eigenschaft erbt den Standardwert True. Auf einem Rechner ohne interaktiven Desktop gibt der modale Dialog ein Abbrechen-Ergebnis zurück, und der Export schlägt stillschweigend fehl. Setzen Sie ShowDialog vor dem Aufruf von Export auf False, und der Bericht wird geräuschlos gerendert. ReportBuilder verfügt über die parallelen Schalter — AllowPrintToFile und ShowPrintDialog —, und QuickReport, das alles über Prepare steuert, benötigt überhaupt keine Dialogunterdrückung

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;

Drei Engines, drei Koordinatensysteme

Jede Bridge führt ihre eigene Koordinatenarithmetik durch, da jede Engine die Welt anders vermisst. PDFlibFRExport behandelt FastReport-Objektpositionen als Pixel bei 96 PPI und skaliert mit 96/25.4, um Millimeter zu erreichen. PDFlibRBDevice liest die Zeichenbefehle von ReportBuilder in Tausendstel-Millimetern und teilt durch 1000. Sie registriert sich über ppRegisterDevice, sodass das Setzen von ppReport.DeviceType auf 'PDFlibPas' und der Aufruf von Print ausreicht, um die Ausgabe darüber zu leiten. PDFlibQRExport führt überhaupt keine Koordinatenmathematik durch: Eine vorbereitete QuickReport-Seite ist bereits eine EMF-Metadatei, sodass die Bridge jede Seite in einen Stream speichert und diesen an ImportEMFFromStream übergibt, wodurch der Gerätekontext- und Metadatei-Rendering-Pfad der Bibliothek jedes Zeichen und jede Linie platziert. Die FastReport- und ReportBuilder-Bridges geben dagegen native Vektorzeichenprimitive und nativen Text aus

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;

Wo die Wiedergabetreue endet

Kennen Sie die Grenzen, bevor Sie einen Workflow an diese Bridges binden. Der Dataset-Exporter verwirft Binärspalten stillschweigend, sodass ein Bericht, der ein eingebettetes Bild anzeigen muss, einen anderen Ansatz erfordert als eine einfache Tabelle. Der QuickReport-Pfad konvertiert Text aufgrund des EMF-Umwegs in Vektorglyphen — die Seite sieht richtig aus, enthält aber keinen auswählbaren oder durchsuchbaren Text und keine strukturierte PDF-Struktur für Barrierefreiheit. FastReport und ReportBuilder behalten echten Text bei, aber die typisierten Handler decken die gängigen Ansichten ab — Memo, Bild, Form, Linie — und fallen auf einen Begrenzungsrahmen zurück oder überspringen die exotischen Typen. Ein Bericht, der sich auf Rich-Text, Barcodes oder Farbverlauffüllungen stützt, verliert daher an Details. Nichts davon ist ein Fehler; es ist die ehrliche Grenze einer Übersetzungsschicht

Eine betriebliche Einschränkung übertrifft alle anderen. Keine der drei Engines wird zusammen mit der losLab PDF Library ausgeliefert, und die Bridges sind bewusst vom Haupt-Build ausgeschlossen — sie lassen sich nur aus einem Projekt kompilieren, das bereits FastReport VCL 6.x, QuickReport 8 oder ReportBuilder 20 im Suchpfad enthält. Die von ihnen angesteuerten Metadatei- und DrawCommand-Schnittstellen sind über mehrere Hauptversionen der Engines hinweg stabil geblieben, aber Sie müssen für die passende Version sorgen. Wenn das stimmt, decken die beiden Pfade die gesamte Bandbreite ab, von einem ad-hoc DBGrid-Dump bis hin zu einer gestalteten Rechnung. Der Tabellen-Exporter und die Bericht-Bridges werden beide mit der losLab PDF Library für Delphi und C++Builder ausgeliefert