Pentru a exporta un TDataSet sau un raport în PDF în Delphi, losLab PDF Library oferă două căi. PDFlibTableExport adaptează orice TDataSet — o interogare FireDAC, un ClientDataSet, un tabel în memorie — într-un tabel PDF paginat, iar trei punți (bridges) din subdirectorul Addons transmit un raport FastReport, QuickReport sau ReportBuilder pregătit către același scriitor PDF. Ambele generează un PDF real, fără driver de imprimantă și fără nicio fereastră vizibilă
Cele două probleme par similare, dar nu sunt. Un DBGrid sau rezultatul unei interogări brute nu are un aspect (layout) propriu, așa că exportarea acestuia înseamnă inventarea unuia: coloane, antete, întreruperi de pagină. Un document FastReport sau ReportBuilder are deja un aspect proiectat, astfel încât exportarea acestuia înseamnă reproducerea fidelă a comenzilor de desenare ale altcuiva în spațiul PDF. losLab PDF Library păstrează aceste funcționalități în unități separate tocmai pentru că modurile de eroare diferă, iar restul acestui articol detaliază fiecare caz și momentele în care acestea nu mai sunt fiabile
Cum se exportă un TDataSet în PDF în Delphi?
PDFlibTableExport transformă un set de date (dataset) într-un tabel dintr-un singur apel. Exportatorul parcurge lista de câmpuri o singură dată, omite automat câmpurile de tip blob binar — ftBlob, ftGraphic, ftBytes și cele similare nu conțin text util pentru celule — aliniază la dreapta coloanele numerice și desenează o dungă zebra opțională peste o bandă de antet stilizată. În fundal, construiește grila cu CreateTable, completează celulele cu SetTableCellContent și randează cu DrawTableRows, același API public Table pe care l-ați gestiona manual. Funcția wrapper utilitară PDFlibExportDataSet configurează milimetrii și originea din stânga-sus pentru dvs., astfel încât apelantul trebuie doar să furnizeze o pagină
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;
Omiterea automată a câmpurilor blob este o setare implicită, nu o constrângere. Atunci când aveți nevoie de control la nivel de câmp — o etichetă de coloană personalizată, o lățime mai mică sau eliminarea unei coloane cheie interne care nu este blob — construiți direct un TPDFlibTableExporter și conectați-vă al evenimentul său OnFieldFilter, care se declanșează o dată pentru fiecare câmp și vă oferă o specificație modificabilă. Setați Include pe False pentru a elimina câmpul sau setați ColumnWidth și DisplayLabel pentru a suprascrie valorile implicite. Acest hook este de asemenea locul în care puteți exclude o coloană largă de tip memo pe care nu doriți să o lăsați să mărească excesiv pagina
Paginarea: transmiterea stării de desenare între pagini
DrawTableRows transportă starea de paginare, iar înțelegerea contractului său este esențială. O apelați cu un prim rând și un ultim rând; transmiterea unui ultim rând mai mic de 1 înseamnă desenarea până la sfârșitul tabelului, limitată de înălțimea furnizată. Funcția returnează înălțimea reală desenată, iar GetTableLastDrawnRow raportează ultimul rând care a încăput. Această pereche reprezintă starea pe care o transmiteți de la o pagină la alta: atunci când ultimul rând desenat este mai mic decât totalul, deschideți o pagină nouă și reluați de la rândul următor. Nimic nu este remăsurat — continuarea începe exact de unde s-a oprit apelul anterior
// 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;
Buclele reprezintă de asemenea locul în care se află antetul repetat. Pe fiecare pagină de continuare, exportatorul desenează opțional din nou rândul 1 — antetul — înaintea rândurilor de date și folosește înălțimea returnată de acel prim apel DrawTableRows pentru a decala corpul tabelului sub acesta. Acest lucru explică de ce DrawTableRows returnează o înălțime în loc de coordonata următorului rând: valoarea returnată este cea care vă permite să suprapuneți un antet redesenat și corpul în continuare fără a codifica rigid dimensiunea vreunuia dintre ele
Cum se exportă un raport FastReport sau ReportBuilder în PDF?
Punțile de raportare (report bridges) folosesc abordarea opusă: nu inventează niciodată un aspect (layout), ci îl reproduc pe cel existent. Fiecare punte se conectează la motorul său prin interfața nativă de export a acestuia. PDFlibFRExport extinde clasa TfrxCustomExportFilter și primește obiectele de tip memo, imagine, formă și linie de la FastReport, traducându-le pe fiecare într-o primitivă PDFlibPas. PDFlibRBDevice extinde clasa TppFileDevice și parcurge lista DrawCommand a ReportBuilder. PDFlibQRExport folosește o a treia cale cu totul diferită, pe care o acoperă secțiunea următoare. Toate cele trei funcționează headless, acesta fiind motivul principal pentru care sunt utilizate pe un server
Modul headless nu este gratuit, iar FastReport este un exemplu de avertizare. TfrxReport.Export trece prin paginile de previzualizare, care consultă proprietatea ShowDialog a filtrului, iar acea proprietate moștenește valoarea implicită True. Pe o mașină fără desktop interactiv, dialogul modal returnează un rezultat de anulare și exportul eșuează în mod silențios. Setați ShowDialog pe False înainte de a apela Export și raportul se va randa în mod silențios. ReportBuilder are comutatoare paralele — AllowPrintToFile și ShowPrintDialog —, în timp ce QuickReport, care gestionează totul prin Prepare, nu necesită deloc suprimarea dialogurilor
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;
Trei motoare, trei sisteme de coordonate
Fiecare punte își face propriile calcule matematice de coordonate, deoarece fiecare motor măsoară lumea în mod diferit. PDFlibFRExport tratează pozițiile obiectelor FastReport ca pixeli la 96 PPI și scalează cu 96/25.4 pentru a obține milimetri. PDFlibRBDevice citește comenzile de desenare ale ReportBuilder în miimi de milimetru și le împarte la 1000, înregistrându-se prin ppRegisterDevice, astfel încât setarea ppReport.DeviceType la 'PDFlibPas' și apelarea Print este suficientă pentru a direcționa ieșirea prin acesta. PDFlibQRExport nu efectuează deloc calcule matematice de coordonate: o pagină QuickReport pregătită este deja un metafile EMF, așa că puntea salvează fiecare pagină într-un flux și o trimite la ImportEMFFromStream, permițând ca contextul de dispozitiv și calea de randare metafile a bibliotecii să plaseze fiecare glifă și riglă. Spre deosebire de acestea, punțile FastReport și ReportBuilder emit primitive native de desenare vectorială și text nativ
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;
Unde se termină fidelitatea
Cunoașteți limitele înainte de a încredința un flux de lucru acestor punți. Exportatorul de seturi de date elimină în mod silențios coloanele binare, astfel încât un raport care trebuie să afișeze o imagine încorporată necesită o abordare diferită față de un tabel simplu. Calea QuickReport, deoarece trece prin EMF, transformă textul în glife vectoriale — pagina arată corect, dar nu conține text selectabil sau căutabil și nici structură PDF etichetată (tagged) pentru accesibilitate. FastReport și ReportBuilder pătrează textul real, dar handlerele tipizate acoperă vizualizările comune — memo, imagine, formă, linie — și recurg la o casetă de încadrare (bounding box) sau le omit pe cele exotice, astfel încât un raport care se bazează pe text formatat (rich text), coduri de bare sau umpleri cu degradeuri va pierde din detalii. Nimic din toate acestea nu este un defect; reprezintă limita firească a unui strat de traducere
O avertizare operațională este mai importantă decât celelalte. Niciunul dintre cele trei motoare nu este livrat în interiorul losLab PDF Library, iar punțile sunt excluse în mod deliberat din build-ul principal — ele se compilează doar dintr-un proiect care are deja FastReport VCL 6.x, QuickReport 8, sau ReportBuilder 20 în calea sa de căutare. Suprafețele metafile și DrawCommand pe care le vizează au rămas stabile de-a lungul mai multor versiuni majore de motoare, dar vă revine responsabilitatea potrivirii versiunilor. Asigurați această potrivire și cele două căi acoperă întreaga gamă, de la un export ad-hoc DBGrid la o factură proiectată. Exportatorul de tabele și punțile de rapoarte sunt livrate împreună cu losLab PDF Library pentru Delphi și C++Builder