Technisch artikel

TDataSet en rapporten exporteren naar PDF in Delphi

Om een TDataSet of een rapport te exporteren naar PDF in Delphi, biedt losLab PDF Library twee paden. PDFlibTableExport past elke TDataSet — een FireDAC-query, a ClientDataSet, een in-memory tabel — aan tot een gepagineerde PDF-tabel, en drie bridges onder Addons overhandigen een voorbereid FastReport-, QuickReport- of ReportBuilder-rapport aan dezelfde PDF-schrijver. Beide produceren een echte PDF zonder printerdriver en zonder zichtbaar venster

De twee problemen lijken op elkaar, maar zijn dat niet. Een DBGrid of een ruw queryresultaat heeft geen eigen lay-out, dus exporteren betekent er een bedenken: kolommen, headers, pagina-einden. Een FastReport- of ReportBuilder-document heeft al een ontworpen lay-out, dus exporteren betekent het getrouw opnieuw afspelen van de tekenopdrachten van iemand anders in de PDF-ruimte. losLab PDF Library houdt deze zorgen in afzonderlijke units juist omdat de foutmodi verschillen, en de rest van dit artikel behandelt elk daarvan en waar ze niet meer betrouwbaar zijn

Hoe exporteert u een TDataSet naar PDF in Delphi?

PDFlibTableExport zet een dataset in één aanroep om in een tabel. De exporteur doorloopt de veldenlijst eenmalig, slaat binaire blob-velden automatisch over — ftBlob, ftGraphic, ftBytes en dergelijke hebben geen bruikbare celtekst — lijnt numerieke kolommen rechts uit en tekent een optionele zebrastreep over een gestylede headerband. Onder de motorkap bouwt het het raster op met CreateTable, vult cellen met SetTableCellContent en rendert met DrawTableRows, dezelfde openbare Table-API die u handmatig zou aansturen. De handige wrapper PDFlibExportDataSet configureert millimeters en een oorsprong linksboven voor u, zodat de beller alleen een pagina hoeft op te geven

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;

Het automatisch overslaan van blobs is een standaardinstelling, geen dwangbuis. Wanneer u controle per veld nodig hebt — een aangepast kolomlabel, een smallere breedte, of het weglaten van een interne sleutelkolom die geen blob is — maakt u rechtstreeks een TPDFlibTableExporter aan en koppelt u deze aan de gebeurtenis OnFieldFilter, die eenmaal per veld wordt geactiveerd en u een aanpasbare specificatie geeft. Stel Include in op False om het veld weg te laten, of stel ColumnWidth en DisplayLabel in om de standaardwaarden te overschrijven. Die hook is ook de plek waar u een brede memokolom uitsluit waarvan u niet wilt dat deze de pagina overbelast

Paginering: de tekenstatus doorgeven tussen pagina's

DrawTableRows draagt de pagineringsstatus, en het begrijpen van het contract daarvan is het hele eieren eten. U roept het aan met een eerste rij en een laatste rij; het doorgeven van een laatste rij kleiner dan 1 betekent tekenen tot het einde van de tabel, begrensd door de hoogte die u hebt opgegeven. De functie retourneert de hoogte die daadwerkelijk is getekend, en GetTableLastDrawnRow rapporteert de laatste rij die paste. Dat paar is de status die u van de ene pagina naar de volgende doorgeeft: wanneer de laatst getekende rij minder is dan het totaal, opent u een nieuwe pagina en gaat u verder vanaf de rij daarna. Er wordt niets opnieuw gemeten — het vervolg gaat precies verder waar de vorige aanroep ophield

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

De lus is ook de plek waar de herhalende header leeft. Op elke vervolgpagina tekent de exporteur optioneel rij 1 opnieuw — de header — vóór de gegevensrijen, en gebruikt de hoogte die door die eerste DrawTableRows-aanroep is geretourneerd om de body eronder te verschuiven. Dit is de reden waarom DrawTableRows een hoogte retourneert in plaats van een coördinaat voor de volgende rij: de retourwaarde is wat u in staat stelt een opnieuw getekende header en de doorlopende body te stapelen zonder de grootte van een van beide hard te coderen

Hoe exporteert u een FastReport- of ReportBuilder-rapport naar PDF?

De rapportbridges kiezen de tegenovergestelde benadering: ze bedenken nooit een lay-out, ze spelen er een opnieuw af. Elke bridge sluit aan op zijn engine via de native export-aansluiting van de engine. PDFlibFRExport is een subklasse van TfrxCustomExportFilter en ontvangt FastReport's memo-, afbeelding-, vorm- en lijnobjecten, en vertaalt elk daarvan in een PDFlibPas-primitief. PDFlibRBDevice is een subklasse van TppFileDevice en doorloopt de DrawCommand-lijst van ReportBuilder. PDFlibQRExport kiest een heel andere, derde route, die in de volgende sectie wordt behandeld. Alle drie draaien headless, wat de belangrijkste reden is om ze op een server te gebruiken

Headless is niet gratis, en FastReport is het waarschuwende voorbeeld. TfrxReport.Export loopt via de voorbeeldpagina's, die de eigenschap ShowDialog van het filter raadplegen, en die eigenschap erft standaard True. Op een machine zonder interactief bureaublad retourneert de modale dialoog een annuleringsresultaat en mislukt de export stilletjes. Stel ShowDialog in op False voordat u Export aanroept en het rapport wordt geruisloos gerenderd. ReportBuilder heeft vergelijkbare schakelaars — AllowPrintToFile en ShowPrintDialog — en QuickReport, dat alles aanstuurt via Prepare, heeft helemaal geen dialoogonderdrukking nodig

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;

Drie engines, drie coördinatensystemen

Elke bridge voert zijn eigen coördinatenberekening uit, omdat elke engine de wereld anders meet. PDFlibFRExport behandelt posities van FastReport-objecten als pixels bij 96 PPI en schaalt met 96/25.4 om millimeters te bereiken. PDFlibRBDevice leest de tekenopdrachten van ReportBuilder in duizendsten van millimeters en deelt door 1000, en registreert zichzelf via ppRegisterDevice, dus het instellen van ppReport.DeviceType op 'PDFlibPas' en het aanroepen van Print is voldoende om de uitvoer erdoorheen te leiden. PDFlibQRExport doet helemaal geen coördinatenberekeningen: een voorbereide QuickReport-pagina is al een EMF-metafile, dus the bridge slaat elke pagina op in een stream en voedt deze aan ImportEMFFromStream, waardoor het device-context- en metafile-renderingpad van de bibliotheek elke glyph en lijn plaatst. De bridges van FastReport en ReportBuilder daarentegen sturen native vector-tekenprimitieven en native tekst uit

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;

Waar de getrouwheid ophoudt

Ken de grenzen voordat u een werkstroom aan deze bridges toevertrouwt. De dataset-exporteur laat binaire kolommen stilletjes vallen, dus een rapport dat een ingebedde afbeelding moet tonen heeft een andere aanpak nodig dan een gewone tabel. Het QuickReport-pad zet tekst, omdat het via EMF loopt, om in vector-glyphs — de pagina ziet er goed uit maar bevat geen selecteerbare of doorzoekbare tekst en geen getagde PDF-structuur voor toegankelijkheid. FastReport en ReportBuilder behouden echte tekst, maar de getypeerde handlers dekken de algemene weergaven — memo, afbeelding, vorm, lijn — en vallen terug op een bounding box of slaan de exotische over, dus een rapport dat leunt op rich text, barcodes of gradiëntvullingen zal detail verliezen. Niets hiervan is een defect; het is de eerlijke grens van een vertaallaag

Eén operationele waarschuwing is belangrijker dan de rest. Geen van de drie engines wordt geleverd binnen losLab PDF Library, en de bridges zijn bewust uitgesloten van de hoofdbuild — ze compileren alleen vanuit een project dat al FastReport VCL 6.x, QuickReport 8 of ReportBuilder 20 in zijn zoekpad heeft. De metafile- en DrawCommand-interfaces waarop ze zich richten zijn stabiel gebleven over verschillende grote engineversies, maar u bent zelf verantwoordelijk voor de versie-overeenstemming. Zorg dat dit klopt en de twee paden dekken het hele bereik, van een ad-hoc DBGrid-dump tot een ontworpen factuur. De tabelexporteur en de rapportbridges worden beide geleverd met losLab PDF Library voor Delphi en C++Builder