Za izvoz TDataSet-a ili izveštaja u PDF u Delphi-ju, losLab PDF biblioteka nudi dva puta. Unit PDFlibTableExport prilagođava bilo koji TDataSet — FireDAC upit, ClientDataSet, tabelu u memoriji — u paginiranu PDF tabelu, a tri mosta (bridges) pod Addons predaju pripremljeni FastReport, QuickReport ili ReportBuilder izveštaj istom PDF pisaču. Oba načina proizvode pravi PDF bez upravljačkog programa štampača (printer driver) i bez vidljivog prozora
Ova dva problema izgledaju slično, ali nisu. DBGrid ili sirovi rezultat upita nemaju sopstveni raspored (layout), pa izvoziti ih znači izmisliti ga: kolone, zaglavlja, prelome stranica. FastReport ili ReportBuilder dokument već nosi dizajnirani raspored, pa izvoziti ga znači verno ponoviti nečije tuđe komande crtanja u PDF prostoru. losLab PDF biblioteka drži ove probleme u odvojenim unitima upravo zato što se načini otkazivanja razlikuju, a ostatak ovog članka vodi kroz svaki od njih i ukazuje na to gde prestaju biti pouzdani
Kako izvesti TDataSet u PDF u Delphi-ju?
Unit PDFlibTableExport pretvara dataset u tabelu u jednom pozivu. Eksporter jednom prolazi kroz listu polja, automatski preskače binarna blob polja — ftBlob, ftGraphic, ftBytes i slična polja nemaju koristan tekst u ćelijama — desno poravnava numeričke kolone i iscrtava opcionu zebra šaru preko stilizovane trake zaglavlja. Ispod haube, on gradi mrežu (grid) pomoću CreateTable, popunjava ćelije pomoću SetTableCellContent i renderuje pomoću DrawTableRows, što je isti javni Table API koji biste i sami ručno kontrolisali. Zgodan omotač (convenience wrapper) PDFlibExportDataSet podešava milimetre i gornji levi početak koordinata za vas, tako da pozivalac obezbeđuje samo stranicu
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;
Automatsko preskakanje blob polja je podrazumevana opcija, a ne strogo pravilo. Kada vam je potrebna kontrola po polju — prilagođena oznaka kolone, manja širina ili odbacivanje interne kolone ključa koja nije blob — direktno kreirajte TPDFlibTableExporter i povežite njegov događaj OnFieldFilter, koji se pokreće jednom po polju i predaje vam promenljivu specifikaciju. Postavite Include na False da biste odbacili polje, ili podesite ColumnWidth i DisplayLabel da biste pregazili podrazumevane vrednosti. Ta zakačka (hook) je takođe mesto gde isključujete široku memo kolonu za koju ne želite da predimenzioniše stranicu
Paginacija: prenošenje stanja crtanja između stranica
Metoda DrawTableRows prenosi stanje paginacije, i razumevanje njenog ugovora je ključna stvar. Pozivate je sa prvim i poslednjim redom. Prosleđivanje poslednjeg reda manjeg od 1 znači crtanje do kraja tabele, ograničeno visinom koju ste zadali. Funkcija vraća visinu koju je zaista iscrtala, a GetTableLastDrawnRow prijavljuje poslednji red koji je stao. Taj par predstavlja stanje koje prenosite sa jedne stranice na sledeću: kada je poslednji iscrtani red manji od ukupnog broja, otvarate novu stranicu i nastavljate od reda nakon njega. Ništa se ponovo ne meri — nastavak počinje tačno tamo gde je prethodni poziv stao
// 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;
Petlja je takođe mesto gde živi zaglavlje koje se ponavlja. Na svakoj stranici nastavka eksporter opciono ponovo crta red 1 — zaglavlje — pre redova sa podacima, i koristi visinu vraćenu tim prvim pozivom DrawTableRows da pomeri telo tabele ispod njega. Zbog toga DrawTableRows vraća visinu umesto koordinate sledećeg reda: povratna vrednost je ono što vam omogućava da složite ponovo iscrtano zaglavlje i nastavak tela tabele bez hardkodiranja veličine bilo kog od njih
Kako izvesti FastReport ili ReportBuilder izveštaj u PDF?
Mostovi za izveštaje uzimaju suprotan pristup: oni nikada ne izmišljaju raspored, već ga ponovo iscrtavaju. Svaki most se povezuje sa svojim mehanizmom na njegovom izvoznom spoju. Klasa PDFlibFRExport nasleđuje TfrxCustomExportFilter i prima FastReport-ove memo objekte, slike, oblike i linije, prevodeći svaki u PDFlibPas primitiv. Klasa PDFlibRBDevice nasleđuje TppFileDevice i prolazi kroz ReportBuilder-ovu listu DrawCommand. Klasa PDFlibQRExport ide sasvim trećim putem, koji je pokriven u sledećem odeljku. Sva tri rade bez grafičkog okruženja (headless), što je glavni razlog da ih koristite na serveru
Rad bez grafičkog okruženja nije besplatan, a FastReport je tu primer na koji treba obratiti pažnju. Metoda TfrxReport.Export ide kroz stranice pregleda, koje proveravaju svojstvo filtera ShowDialog, a to svojstvo nasleđuje podrazumevanu vrednost True. Na mašini bez interaktivne radne površine, modalni dijalog vraća rezultat otkazivanja i izvoz tiho propada. Postavite ShowDialog na False pre nego što pozovete Export i izveštaj će se renderovati tiho. ReportBuilder ima paralelne prekidače — AllowPrintToFile i ShowPrintDialog — a QuickReport, koji sve pokreće kroz Prepare, uopšte ne zahteva potiskivanje dijaloga
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;
Tri mehanizma, tri koordinatna sistema
Svaki most radi sopstvenu koordinatnu aritmetiku, jer svaki mehanizam meri svet drugačije. Klasa PDFlibFRExport tretira pozicije FastReport objekata kao piksele pri 96 PPI i skalira ih sa 96/25.4 da bi se dobili milimetri. Klasa PDFlibRBDevice čita ReportBuilder-ove komande crtanja u hiljaditim delovima milimetra i deli sa 1000, a registruje se preko ppRegisterDevice, tako da je postavljanje ppReport.DeviceType na 'PDFlibPas' i pozivanje Print dovoljno da se izlaz usmeri kroz njega. PDFlibQRExport uopšte ne radi matematiku koordinata: pripremljena QuickReport stranica je već EMF metafile, pa most čuva svaku stranicu u tok (stream) i prosleđuje je u ImportEMFFromStream, prepuštajući putanji renderovanja meta-datoteka i konteksta uređaja biblioteke da postavi svaki glif i liniju. Nasuprot tome, mostovi za FastReport i ReportBuilder emituju izvorne vektorske primitive za crtanje i izvorni 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;
Gde prestaje preciznost
Upoznajte granice pre nego što poverite radni tok ovim mostovima. Eksporter dataset-a tiho odbacuje binarne kolone, pa izveštaj koji mora da prikaže ugrađenu sliku zahteva drugačiji pristup od obične tabele. QuickReport putanja, pošto ide kroz EMF, pretvara tekst u vektorske glifove — stranica izgleda ispravno, ali ne sadrži tekst koji se može selektovati ili pretraživati, niti označenu PDF strukturu za pristupačnost. FastReport i ReportBuilder zadržavaju stvarni tekst, ali hendleri za tipove pokrivaju uobičajene prikaze — memo, sliku, oblik, liniju — i vraćaju se na granični okvir (bounding box) ili preskaču one egzotične, tako da će izveštaj koji se oslanja na bogat tekst (rich text), bar-kodove ili gradijente izgubiti detalje. Ništa od ovoga nije nedostatak, već poštena granica prevodilačkog sloja
Jedno operativno upozorenje je važnije od ostalih. Nijedan od ova tri mehanizma se ne isporučuje unutar losLab PDF biblioteke, a mostovi su namerno isključeni iz glavne verzije za izgradnju — oni se kompajliraju samo iz projekta koji već ima FastReport VCL 6.x, QuickReport 8 ili ReportBuilder 20 na svojoj putanji pretrage. Površine metafajlova i DrawCommand koje oni ciljaju ostale su stabilne kroz nekoliko glavnih verzija mehanizama, ali vi ste odgovorni za usklađivanje verzija. Kada to postavite kako treba, ova dva puta pokrivaju ceo spektar, od ad-hoc izvoza DBGrid-a do dizajnirane fakture. Eksporter tabela i mostovi za izveštaje se isporučuju uz losLab PDF biblioteku za Delphi i C++Builder