Teknisk artikel

Eksporter TDataSet og rapporter til PDF i Delphi

For at eksportere et TDataSet eller en rapport til PDF i Delphi tilbyder losLab PDF Library to veje. PDFlibTableExport tilpasser ethvert TDataSet — en FireDAC-forespørgsel, et ClientDataSet, en tabel i hukommelsen — til en sideopdelt PDF-tabel, og tre broer under Addons overdrager en klargjort FastReport-, QuickReport- eller ReportBuilder-rapport til den samme PDF-skriver. Begge producerer en rigtig PDF uden printerdriver og uden synligt vindue

De to problemer ser ens ud, men er det ikke. Et DBGrid eller et råt forespørgselsresultat har ikke sit eget layout, så eksport af det betyder at opfinde et: Kolonner, overskrifter, sideskift. Et FastReport- eller ReportBuilder-dokument har allerede et designet layout, så eksport af det betyder trofast at genspille en andens tegnekommandoer i PDF-rummet. losLab PDF Library holder disse bekymringer i separate enheder netop fordi fejltilstandene adskiller sig, og resten af denne artikel gennemgår hver enkelt, og hvor den ophører med at være pålidelig

Hvordan eksporterer du et TDataSet til PDF i Delphi?

PDFlibTableExport forvandler et datasæt til en tabel med et enkelt kald. Eksporteren gennemgår feltlisten én gang, springer binære blob-felter over automatisk — ftBlob, ftGraphic, ftBytes og deres lige har ingen brugbar celletekst — højrejusterer numeriske kolonner og maler en valgfri zebrastribe over et formateret overskriftsbånd. Under motorhjelmen bygger den gitteret med CreateTable, udfylder celler med SetTableCellContent og renderer med DrawTableRows, den samme offentlige Table-API, som du ville styre manuelt. Bekvemmeligheds-wrapperen PDFlibExportDataSet konfigurerer millimeter og et øverste-venstre udgangspunkt for dig, så kalderen kun leverer en side

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;

Det automatiske spring over blob-felter er en standard, ikke en spændetrøje. Når du har brug for kontrol pr. felt — en brugerdefineret kolonneetiket, en smallere bredde eller udeladelse af en intern nøglekolonne, der ikke er en blob — skal du oprette en TPDFlibTableExporter direkte og tilknytte dens OnFieldFilter-hændelse, som udløses én gang pr. felt og giver dig en ændringsbar specifikation. Sæt Include to False for at droppe feltet, eller sæt ColumnWidth og DisplayLabel for at tilsidesætte standarderne. Denne tilknytning er også stedet, hvor du udelukker en bred memokolonne, som du ikke ønsker skal sprænge siden

Sideopdeling: Overførsel af tegnetilstand mellem sider

DrawTableRows bærer sideopdelingstilstanden, og at forstå dens kontrakt is hele spillet. Du kalder den med en første række og en sidste række; at angive en sidste række under 1 betyder tegn til slutningen af tabellen, begrænset af den højde, du angav. Funktionen returnerer den højde, den faktisk tegnede, og GetTableLastDrawnRow rapporterer den sidste række, der var plads til. Dette par er den tilstand, du overfører fra en side til den næste: Når den sidst tegnede række ikke når totalen, åbner du en ny side og genoptager fra rækken efter den. Intet genmåles — fortsættelsen fortsætter præcis, hvor det foregående kald slap

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

Løkken er også stedet, hvor den gentagne overskrift bor. På hver fortsættelsesside tegner eksporteren eventuelt række 1 igen — overskriften — før datarækkerne, og den bruger højden returneret af det første DrawTableRows-kald til at forskyde brødteksten under den. Det er grunden til, at DrawTableRows returnerer en højde frem for en næste-række-koordinat: Returværdien er det, der lader dig stable en gentegnet overskrift og den fortsættende brødtekst uden at hårdkode størrelsen på nogen af dem

Hvordan eksporterer du en FastReport- eller ReportBuilder-rapport til PDF?

Rapportbroerne vælger den modsatte tilgang: De opfinder aldrig et layout, de genspiller et. Hver bro tilknyttes sin motor ved motorens indbyggede eksportgrænse. PDFlibFRExport nedarver fra TfrxCustomExportFilter og modtager FastReports memo-, billede-, figur- og linjeobjekter og oversætter hver til en PDFlibPas-primitiv. PDFlibRBDevice nedarver fra TppFileDevice og gennemgår ReportBuilders DrawCommand-liste. PDFlibQRExport vælger en helt tredje vej, som det næste afsnit dækker. Alle tre kører headless, hvilket er hele grunden til at bruge dem på en server

Headless er ikke gratis, og FastReport er det advarende eksempel. TfrxReport.Export ruterer gennem visningssiderne, som konsulterer filterets ShowDialog-egenskab, og denne egenskab arver en standardværdi på True. På en maskine uden interaktivt skrivebord returnerer den modale dialogboks et annulleringsresultat, og eksporten fejler i stilhed. Sæt ShowDialog til False, før du kalder Export, og rapporten renderer lydløst. ReportBuilder har de parallelle kontakter — AllowPrintToFile og ShowPrintDialog — og QuickReport, som driver alt gennem Prepare, har slet ikke brug for undertrykkelse af dialogbokse

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;

Tre motorer, tre koordinatsystemer

Hver bro udfører sin egen koordinat-aritmetik, fordi hver motor måler verden forskelligt. PDFlibFRExport behandler FastReport-objektpositioner som pixels ved 96 PPI og skalerer med 96/25.4 for at nå millimeter. PDFlibRBDevice læser ReportBuilders tegnekommandoer i tusindedele millimeter og dividerer med 1000, og den registrerer sig selv via ppRegisterDevice, så indstilling af ppReport.DeviceType til 'PDFlibPas' og kald af Print er nok til at dirigere output gennem den. PDFlibQRExport tager en helt tredje vej: En klargjort QuickReport-side is allerede en EMF-metafil, så broen gemmer hver side i en strøm og føder den til ImportEMFFromStream, hvilket lader bibliotekets enhedskontekst og metafil-renderingssti placere hvert tegn og hver linje. Broerne til FastReport og ReportBuilder udsender til gengæld indfødte vektortegnings-primitiver og indfødt tekst

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;

Hvor troskaben ophører

Kend grænserne, før du overlader et workflow til disse broer. Datasæteksporteren taber binære kolonner i stilhed, så en rapport, der skal vise et indlejret billede, har brug for en anden tilgang end en simpel tabel. QuickReport-stien bager, fordi den går gennem EMF, tekst ind i vektorglyfer — siden ser rigtig ud, men bærer ingen valgbar eller søgbar tekst og ingen tagget PDF-struktur tilgængelighed. FastReport og ReportBuilder beholder rigtig tekst, men de typede håndteringer dækker de almindelige visninger — memo, billede, figur, linje — og falder tilbage til en afgrænsningsboks eller springer de eksotiske over, så en rapport, der læner sig op ad formateret tekst, stregkoder eller gradientudfyldninger, vil miste detaljer. Intet af dette er en fejl; det er den ærlige grænse for et oversættelseslag

Én driftsmæssig advarsel overgår resten. Ingen af de tre motorer leveres i losLab PDF Library, og broerne er bevidst udelukket fra hovedbygget — de kompilerer kun fra et projekt, der allerede har FastReport VCL 6.x, QuickReport 8 eller ReportBuilder 20 på sin søgesti. Metafil- og DrawCommand-fladerne, de er rettet mod, er forblevet stabile på tværs af flere større motorversioner, men du ejer versionsmatchet. Få det rigtigt, og de to stier dækker hele spredningen, fra et ad-hoc DBGrid dump til en designet faktura. Tabel-eksporteren og rapportbroerne leveres begge med losLab PDF Library til Delphi og C++Builder