Teknisk artikkel

Eksporter TDataSet og rapporter til PDF i Delphi

For å eksportere en TDataSet eller en rapport til PDF i Delphi, tilbyr losLab PDF Library to metoder. PDFlibTableExport tilpasser enhver TDataSet — en FireDAC-spørring, et ClientDataSet, en tabell i minnet — til en paginert PDF-tabell, og tre broer under Addons overleverer en klargjort FastReport-, QuickReport- eller ReportBuilder-rapport til den samme PDF-skriveren. Begge produserer en ekte PDF uten skriverdriver og uten synlig vindu

De to problemene ser like ut, men er det ikke. En DBGrid eller et rått spørringsresultat har ingen egen layout, så å eksportere det betyr å finne opp en: kolonner, overskrifter, sideskift. Et FastReport- eller ReportBuilder-dokument har allerede en utformet layout, så å eksportere det betyr å trofast gjengi noen andres tegnekommandoer i PDF-rommet. losLab PDF Library holder disse oppgavene i separate enheter nettopp fordi feilmodusene er forskjellige, og resten av denne artikkelen går gjennom hver enkelt og hvor de slutter å være pålitelige

Hvordan eksporterer du en TDataSet til PDF i Delphi?

PDFlibTableExport gjør om et datasett til en tabell i ett enkelt kall. Eksporteren går gjennom feltlisten én gang, hopper over binære blob-felt automatisk — ftBlob, ftGraphic, ftBytes og lignende har ingen nyttig celletekst — høyrejusterer numeriske kolonner og tegner en valgfri sebrastripe over et stilisert overskriftsbånd. Under panseret bygger den rutenettet med CreateTable, fyller celler med SetTableCellContent og rendrer med DrawTableRows, det samme offentlige Table API-et som du ville styrt manuelt. Den praktiske wrapperen PDFlibExportDataSet konfigurerer millimeter og et topp-venstre-origo for deg, slik at oppkalleren bare trenger å oppgi 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 hoppet over blob-felt er en standard, ikke en tvangstrøye. Når du trenger kontroll per felt — en tilpasset kolonneetikett, en smalere bredde, eller å utelate en intern nøkkelkolonne som ikke er en blob — oppretter du en TPDFlibTableExporter direkte og kobler deg til dens OnFieldFilter-hendelse, som utløses én gang per felt og gir deg en endringsbar spesifikasjon. Sett Include to False for å utelate feltet, eller angi ColumnWidth og DisplayLabel for å overstyre standardverdiene. Denne hendelsen er også der du ekskluderer en bred memo-kolonne som du ikke vil at skal blåse opp siden

Paginering: overføring av tegnetilstand mellom sider

DrawTableRows bærer pagineringstilstanden, og å forstå dens kontrakt er hele poenget. Du kaller den med en første rad og en siste rad; å sende en siste rad under 1 betyr å tegne til slutten av tabellen, begrenset av høyden du oppga. Funksjonen returnerer høyden den faktisk tegnet, og GetTableLastDrawnRow rapporterer den siste raden som fikk plass. Dette paret er tilstanden du overfører fra én side til den neste: når den siste tegnede raden er mindre enn totalen, åpner du en ny side og fortsetter fra raden etter. Ingenting måles på nytt — fortsettelsen starter akkurat der det forrige kallet slapp

// 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å der det gjentakende overskriftshodet lever. På hver fortsettelsesside tegner eksporteren valgfritt rad 1 igjen — overskriften — før dataradene, og den bruker høyden returnert av det første DrawTableRows-kallet til å forskyve hoveddelen under den. Dette er grunnen til at DrawTableRows returnerer en høyde i stedet for en koordinat for neste rad: returverdien er det som lar deg stable en nytegnet overskrift og den fortsettende hoveddelen uten å hardkode størrelsen på noen av dem

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

Rapportbroene bruker motsatt tilnærming: de finner aldri på en layout, de gjengir en. Hver bro kobles til sin motor ved motorens opprinnelige eksportgrensesnitt. PDFlibFRExport underklasser TfrxCustomExportFilter og mottar FastReports memo-, bilde-, figur- og linjeobjekter, og oversetter hvert av dem til en PDFlibPas-primitiv. PDFlibRBDevice underklasser TppFileDevice og går gjennom ReportBuilders DrawCommand-liste. PDFlibQRExport tar en helt tredje vei, som dekkes i neste avsnitt. Alle tre kjører hodeløst, noe som er hele grunnen til å bruke dem på en server

Hodeløs kjøring er ikke gratis, og FastReport er et advarende eksempel. TfrxReport.Export ruter gjennom forhåndsvisningssidene, som konsulterer filterets ShowDialog-egenskap, og den egenskaper arver en standardverdi på True. På en maskin uten interaktivt skrivebord returnerer den modale dialogboksen et avbrutt-resultat, og eksporten mislykkes i det stille. Sett ShowDialog til False før du kaller Export, og rapporten rendres lydløst. ReportBuilder har de parallelle bryterne — AllowPrintToFile og ShowPrintDialog — og QuickReport, som driver alt gjennom Prepare, trenger ingen dialogundertrykkelse i det hele tatt

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 gjør sin egen koordinataritmetikk, fordi hver motor måler verden forskjellig. PDFlibFRExport behandler FastReport-objektposisjoner som piksler ved 96 PPI og skalerer med 96/25,4 for å nå millimeter. PDFlibRBDevice leser ReportBuilders tegnekommandoer i tusendels millimeter og deler på 1000, og den registrerer seg selv gjennom ppRegisterDevice, så å sette ppReport.DeviceType til 'PDFlibPas' og kalle Print er nok til å rute utdataene gjennom den. PDFlibQRExport gjør ingen koordinatmatematikk i det hele tatt: en klargjort QuickReport-side er allerede en EMF-metafil, så broen lagrer hver side til en strøm og sender den til ImportEMFFromStream, og lar bibliotekets enhetskontekst- og metafilrendrings-bane plassere hver glyf og linje. FastReport- og ReportBuilder-broene sender derimot ut native vektortegningsprimitiver og native 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 nøyaktigheten slutter

Kjenn grensene før du overlater en arbeidsflyt til disse broene. Datasetteksporteren utelater binære kolonner i det stille, så en rapport som må vise et innebygd bilde trenger en annen tilnærming enn en ren tabell. QuickReport-banen, fordi den går via EMF, baker tekst inn i vektorglyfer — siden ser riktig ut, men inneholder ingen velgbar eller søkbar tekst og ingen tagget PDF-struktur for universell utforming. FastReport og ReportBuilder beholder ekte tekst, men de spesifikke håndtererene dekker de vanlige visningene — memo, bilde, figur, linje — og faller tilbake til en avgrensningsboks eller hopper over de mer eksotiske, så en rapport som baserer seg på rik tekst, strekkoder eller gradientfyll vil miste detaljer. Ingenting av dette er en feil; det er den ærlige grensen for et oversettelseslag

Én driftsmessig advarsel er viktigere enn resten. Ingen av de tre motorene følger med losLab PDF Library, og broene er bevisst ekskludert fra hovedbyggingen — de kompilerer bare fra et prosjekt som allerede har FastReport VCL 6.x, QuickReport 8 eller ReportBuilder 20 i søkebanen sin. Grensesnittene for metafiler og DrawCommand som de retter seg mot har holdt seg stabile over flere store motorversjoner, men du har ansvaret for at versjonene samsvarer. Få det riktig, og de to banene dekker hele spekteret, fra en ad-hoc DBGrid-dump til en designet faktura. Tabelleksporteren og rapportbroene leveres begge med losLab PDF Library for Delphi og C++Builder