Για την εξαγωγή ενός TDataSet ή μιας αναφοράς (report) σε PDF στο Delphi, η losLab PDF Library προσφέρει δύο διαδρομές. Το PDFlibTableExport προσαρμόζει οποιοδήποτε TDataSet — ένα ερώτημα FireDAC, ένα ClientDataSet, έναν πίνακα στη μνήμη — σε έναν σελιδοποιημένο πίνακα PDF, και τρεις γέφυρες κάτω από τα Addons παραδίδουν μια έτοιμη αναφορά FastReport, QuickReport ή ReportBuilder στον ίδιο εγγραφέα PDF. Και οι δύο παράγουν ένα πραγματικό PDF χωρίς πρόγραμμα οδήγησης εκτυπωτή (printer driver) και χωρίς ορατό παράθυρο
Τα δύο αυτά προβλήματα φαίνονται παρόμοια αλλά δεν είναι. Ένα DBGrid ή ένα ακατέργαστο αποτέλεσμα ερωτήματος δεν έχει δική του διάταξη (layout), επομένως η εξαγωγή του σημαίνει την επινόηση μιας διάταξης: στήλες, κεφαλίδες, αλλαγές σελίδας. Ένα έγγραφο FastReport ή ReportBuilder φέρει ήδη μια σχεδιασμένη διάταξη, οπότε η εξαγωγή του σημαίνει την πιστή αναπαραγωγή των εντολών σχεδίασης κάποιου άλλου στον χώρο του PDF. Η losLab PDF Library διατηρεί αυτές τις λειτουργίες σε ξεχωριστές μονάδες (units) ακριβώς επειδή οι τρόποι αστοχίας διαφέρουν, και το υπόλοιπο αυτού του άρθρου αναλύει κάθε περίπτωση και τα όριά της
Πώς εξάγετε ένα TDataSet σε PDF στο Delphi;
Το PDFlibTableExport μετατρέπει ένα dataset σε πίνακα με μία κλήση. Ο εξαγωγέας διατρέχει τη λίστα πεδίων μία φορά, παραλείπει αυτόματα τα δυαδικά πεδία blob — τα ftBlob, ftGraphic, ftBytes και τα παρόμοια δεν έχουν χρήσιμο κείμενο κελιού —, στοιχίζει δεξιά τις αριθμητικές στήλες και σχεδιάζει μια προαιρετική ρίγα ζέβρας πάνω από μια μορφοποιημένη ζώνη κεφαλίδας. Κάτω από το καπό, δημιουργεί το πλέγμα με το CreateTable, γεμίζει τα κελιά με το SetTableCellContent και τα αποδίζει με το DrawTableRows, το ίδιο δημόσιο Table API που θα οδηγούσατε χειροκίνητα. Το βολικό wrapper PDFlibExportDataSet ρυθμίζει τα χιλιοστά και την αρχή πάνω-αριστερά για εσάς, οπότε ο καλών παρέχει μόνο μια σελίδα
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'); // οποιοδήποτε TDataSet λειτουργεί εδώ
PDF.SetOrigin(1);
PDF.SetMeasurementUnits(1); // millimetres
PDF.SetPageSize('A4');
PDF.AddStandardFont(4);
Options := DefaultTableExportOptions;
Options.Title := 'Customers';
Options.ColumnWidth := 32; // 11 πεδία χωράνε σε A4 στα 32 mm
Options.RepeatHeader := True; // επανάληψη της κεφαλίδας σε κάθε σελίδα
PDFlibExportDataSet(PDF, MemTable, 'customers.pdf', Options);
finally
PDF.Free;
MemTable.Free;
end;
end;
Η αυτόματη παράκαμψη blob είναι μια προεπιλογή, όχι περιορισμός. Όταν χρειάζεστε έλεγχο ανά πεδίο — μια προσαρμοσμένη ετικέτα στήλης, μικρότερο πλάτος ή παράλειψη μιας εσωτερικής στήλης-κλειδιού που δεν είναι blob — δημιουργήστε ένα TPDFlibTableExporter απευθείας και συνδεθείτε στο συμβάν του OnFieldFilter, το οποίο ενεργοποιείται μία φορά ανά πεδίο και σας παραδίζει μια τροποποιήσιμη προδιαγραφή. Ορίστε το Include σε False για να απορρίψετε το πεδίο, ή ορίστε τα ColumnWidth and DisplayLabel για να παρακάμψετε τις προεπιλογές. Αυτό το hook είναι επίσης το σημείο όπου μπορείτε να εξαιρέσετε μια πλατιά στήλη memo που δεν θέλετε να διογκώνει τη σελίδα
Σελιδοποίηση (Pagination): μεταφορά της κατάστασης σχεδίασης μεταξύ των σελίδων
Το DrawTableRows μεταφέρει την κατάσταση σελιδοποίησης, και η κατανόηση της λειτουργίας του είναι το κλειδί. Το καλείτε με μια πρώτη και μια τελευταία γραμμή. Η εισαγωγή μιας τελευταίας γραμμής κάτω από το 1 σημαίνει σχεδίαση μέχρι το τέλος του πίνακα, με όριο το ύψος που δώσατε. Η συνάρτηση επιστρέφει το ύψος που πράγματι σχεδίασε, και η GetTableLastDrawnRow αναφέρει την τελευταία γραμμή που χώρεσε. Αυτό το ζεύγος είναι η κατάσταση που μεταφέρετε από τη μια σελίδα στην άλλη: όταν η τελευταία γραμμή που σχεδιάστηκε υπολείπεται του συνόλου, ανοίγετε μια νέα σελίδα και συνεχίζετε από τη γραμμή μετά από αυτήν. Τίποτα δεν μετριέται ξανά — η συνέχεια ξεκινά ακριβώς εκεί που σταμάτησε η προηγούμενη κλήση
// Πώς ο εξαγωγέας συνεχίζει έναν μεγάλο πίνακα σε όλες τις σελίδες
PageHeight := PDF.PageHeight;
Y := PageHeight - Options.Top;
Row := 1;
while Row <= TotalRows do
begin
DrawHeight := Y - Options.BottomMargin;
// Τελευταία γραμμή = 0 σημαίνει "σχεδίαση μέχρι το τέλος", με όριο το DrawHeight
PDF.DrawTableRows(TableID, Options.Left, Y, DrawHeight, Row, 0);
LastDrawn := PDF.GetTableLastDrawnRow(TableID);
if LastDrawn >= TotalRows then
Break; // ολόκληρος ο πίνακας χώρεσε σε αυτήν τη σελίδα
if LastDrawn < Row then
Break; // ασφάλεια: καμία πρόοδος προς τα εμπρός, έξοδος
PDF.NewPage;
Row := LastDrawn + 1; // συνέχεια από την πρώτη μη σχεδιασμένη γραμμή
Y := PageHeight - Options.Top;
end;
Ο βρόχος είναι επίσης το σημείο όπου ζει η επαναλαμβανόμενη κεφαλίδα. Σε κάθε σελίδα συνέχειας, ο εξαγωγέας προαιρετικά σχεδιάζει ξανά τη γραμμή 1 — την κεφαλίδα — πριν από τις γραμμές δεδομένων, και χρησιμοποιεί το ύψος που επιστρέφεται από εκείνη την πρώτη κλήση DrawTableRows για να μετατοπίσει το σώμα από κάτω της. Αυτός είναι ο λόγος για τον οποίο το DrawTableRows επιστρέφει ένα ύψος αντί για μια συντεταγμένη επόμενης γραμμής: η επιστρεφόμενη τιμή είναι αυτή που σας επιτρέπει να στοιβάζετε μια ξανασχεδιασμένη κεφαλίδα και το σώμα συνέχειας χωρίς να κωδικοποιείτε σκληρά (hard-code) το μέγεθος κανενός από τα δύο
Πώς εξάγετε μια αναφορά FastReport ή ReportBuilder σε PDF;
Οι γέφυρες αναφορών ακολουθούν την αντίθετη προσέγγιση: δεν επινοούν ποτέ μια διάταξη, αλλά την αναπαράγουν. Κάθε γέφυρα συνδέεται στη μηχανή της στο εγγενές σημείο εξαγωγής της. Η PDFlibFRExport υποκλάσεις (subclasses) την TfrxCustomExportFilter και λαμβάνει τα αντικείμενα memo, εικόνας, σχήματος και γραμμής του FastReport, μεταφράζοντας το καθένα σε ένα βασικό στοιχείο PDFlibPas. Η PDFlibRBDevice υποκλάσεις την TppFileDevice και διατρέχει τη λίστα DrawCommand του ReportBuilder. Η PDFlibQRExport ακολουθεί μια εντελώς τρίτη διαδρομή, την οποία καλύπτει η επόμενη ενότητα. Και οι τρεις λειτουργούν χωρίς γραφικό περιβάλλον (headless), που είναι και ο βασικός λόγος για να τις χρησιμοποιήσετε σε έναν διακομιστή
Η λειτουργία headless δεν είναι χωρίς προκλήσεις, και το FastReport αποτελεί ένα χαρακτηριστικό παράδειγμα. Το TfrxReport.Export δρομολογείται μέσω των σελίδων προεπισκόπησης, οι οποίες συμβουλεύονται την ιδιότητα ShowDialog του φίλτρου, και αυτή η ιδιότητα κληρονομεί μια προεπιλογή True. Σε ένα μηχάνημα χωρίς επιφάνεια εργασίας, ο αναδυόμενος διάλογος επιστρέφει ένα αποτέλεσμα ακύρωσης και η εξαγωγή αποτυγχάνει σιωπηλά. Ορίστε το ShowDialog σε False προτού καλέσετε το Export και η αναφορά θα αποδοθεί σιωπηλά. Το ReportBuilder διαθέτει αντίστοιχους διακόπτες — τους AllowPrintToFile και ShowPrintDialog — ενώ το QuickReport, το οποίο οδηγεί τα πάντα μέσω του Prepare, δεν χρειάζεται καθόλου καταστολή διαλόγων
var
Exporter: TPDFlibFRExport;
begin
Report.PrepareReport; // δημιουργήστε πρώτα τις σελίδες
Exporter := TPDFlibFRExport.Create(nil);
try
Exporter.FileName := 'invoice.pdf';
Exporter.ShowDialog := False; // headless: παράκαμψη του αναδυόμενου παραθύρου, καμία ακύρωση
Report.Export(Exporter);
finally
Exporter.Free;
end;
end;
Τρεις μηχανές, τρία συστήματα συντεταγμένων
Κάθε γέφυρα κάνει τη δική της αριθμητική συντεταγμένων, επειδή κάθε μηχανή μετράει τον κόσμο διαφορετικά. Το PDFlibFRExport αντιμετωπίζει τις θέσεις των αντικειμένων FastReport ως pixel στα 96 PPI και τις κλιμακώνει κατά 96/25.4 για να φτάσει σε χιλιοστά. Το PDFlibRBDevice διαβάζει τις εντολές σχεδίασης του ReportBuilder σε χιλιοστά του χιλιοστού (mm-thousandths) και διαιρεί με το 1000, και καταχωρίζεται μέσω του ppRegisterDevice, οπότε ο ορισμός του ppReport.DeviceType σε 'PDFlibPas' and η κλήση Print αρκούν για τη δρομολόγηση της εξόδου μέσω αυτού. Το PDFlibQRExport δεν κάνει καθόλου μαθηματικά συντεταγμένων: μια έτοιμη σελίδα QuickReport είναι ήδη ένα EMF metafile, επομένως η γέφυρα αποθηκεύει κάθε σελίδα σε μια ροή (stream) και την τροφοδοτεί στο ImportEMFFromStream, επιτρέποντας στη διαδρομή απόδοσης device-context και metafile της βιβλιοθήκης να τοποθετήσει κάθε γλύφο και γραμμή. Οι γέφυρες FastReport και ReportBuilder, αντίθετα, εκπέμπουν εγγενή βασικά στοιχεία διανυσματικής σχεδίασης και εγγενές κείμενο
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;
Πού τελειώνει η πιστότητα
Μάθετε τα όρια προτού αναθέσετε μια ροή εργασίας σε αυτές τις γέφυρες. Ο εξαγωγέας dataset απορρίπτει σιωπηλά τις δυαδικές στήλες, οπότε μια αναφορά που πρέπει να δείχνει μια ενσωματωμένη εικόνα χρειάζεται διαφορετική προσέγγιση από έναν απλό πίνακα. Η διαδρομή QuickReport, επειδή περνά μέσα από EMF, ενσωματώνει το κείμενο σε διανυσματικούς γλύφους — η σελίδα φαίνεται σωστή αλλά δεν φέρει επιλέξιμο ή αναζητήσιμο κείμενο ούτε tagged δομή PDF για προσβασιμότητα. Το FastReport και το ReportBuilder διατηρούν το πραγματικό κείμενο, αλλά οι πληκτρολογημένοι χειριστές καλύπτουν τις συνήθεις προβολές — memo, εικόνα, σχήμα, γραμμή — και καταφεύγουν σε ένα πλαίσιο οριοθέτησης (bounding box) ή παραλείπουν τις πιο ασυνήθιστες, οπότε μια αναφορά που βασίζεται σε εμπλουτισμένο κείμενο, barcode ή γεμίσματα διαβάθμισης (gradient fills) θα χάσει λεπτομέρειες. Τίποτα από αυτά δεν είναι ελάττωμα, είναι το ειλικρινές όριο ενός επιπέδου μετάφρασης
Μια λειτουργική προειδοποίηση υπερέχει όλων των άλλων. Καμία από τις τρεις μηχανές δεν παρέχεται ενσωματωμένη στη losLab PDF Library, και οι γέφυρες εξαιρούνται σκόπιμα από την κύρια έκδοση — μεταγλωττίζονται μόνο από ένα έργο (project) που διαθέτει ήδη FastReport VCL 6.x, QuickReport 8 ή ReportBuilder 20 στη διαδρομή αναζήτησής του. Οι επιφάνειες metafile και DrawCommand στις οποίες στοχεύουν έχουν παραμείνει σταθερές σε αρκετές κύριες εκδόσεις των μηχανών, αλλά εσείς είστε υπεύθυνοι για την αντιστοίχιση των εκδόσεων. Εάν το κάνετε σωστά, οι δύο διαδρομές καλύπτουν όλο το εύρος, από ένα ad-hoc dump DBGrid έως ένα σχεδιασμένο τιμολόγιο. Ο εξαγωγέας πινάκων και οι γέφυρες αναφορών παρέχονται και οι δύο με τη losLab PDF Library για Delphi και C++Builder