Η ενσωμάτωση ενός πλαισίου ανάγνωσης PDF σε μια επιχειρηματική εφαρμογή Delphi δεν απαιτεί τη συγγραφή ενός βρόχου απόδοσης (rendering loop), μιας μηχανής κύλισης (scroll engine) ή ενός μοντέλου ζουμ. Η losLab PDF Library συνοδεύεται από το TPDFlibViewer, ένα στοιχείο ελέγχου VCL που ανοίγει ένα έγγραφο με μία κλήση και παρέχει επιλογή κειμένου, μετατόπιση (panning), ζουμ επιλογής (marquee zoom), στιγμιότυπα (snapshots), επισήμανση περιοχής, επισήμανση αναζήτησης πλήρους κειμένου, αλληλεπίδραση με σχόλια (annotations) και εκτύπωση ως ενσωματωμένη συμπεριφορά. Αυτό το άρθρο παρουσιάζει τα πέντε βήματα που μετατρέπουν μια κενή φόρμα σε ένα λειτουργικό πρόγραμμα προβολής: τοποθέτηση του στοιχείου ελέγχου, σύνδεση των έτοιμων ενεργειών, προσθήκη επισήμανσης αναζήτησης, χειρισμός κλικ σε σχόλια και εκτύπωση
Γιατί να χρησιμοποιήσετε ένα στοιχείο ελέγχου προβολής αντί να γράψετε έναν βρόχο απόδοσης;
Το επιχείρημα της διαστρωμάτωσης (layering) έρχεται πρώτο, επειδή εξηγεί τι πραγματικά αποκτάτε. Η απόδοση μιας σελίδας σε bitmap είναι το εύκολο 20 τοις εκατό· το άλλο 80 τοις εκατό είναι όλα όσα την περιβάλλουν: μια προσωρινή μνήμη bitmap σελίδας (page-bitmap cache) με όριο εκκαθάρισης, διάταξη κύλισης σε σελίδες διαφορετικών μεγεθών, λειτουργίες ζουμ που επανυπολογίζονται κατά την αλλαγή μεγέθους, hit-testing που αντιστοιχεί ένα pixel του ποντικιού πίσω στις συντεταγμένες της σελίδας σε οποιοδήποτε ζουμ και περιστροφή, και προγραμματισμός επανασχεδιασμού (repaint scheduling) που δεν τρεμοπαίζει. Το TPDFlibViewer αναλαμβάνει όλα αυτά και μεταβιβάζει τη δουλειά ραστεροποίησης (raster work) στην ίδια μηχανή που περιγράφεται στο άρθρο για την απόδοση πολλαπλών μηχανών, έτσι ώστε το επίπεδο ελέγχου και το επίπεδο απόδοσης να παραμένουν ξεχωριστά αντικαταστάσιμα. Ο κώδικας της φόρμας σας επικοινωνεί με ένα κυλιόμενο στοιχείο ελέγχου των Windows, όχι με έναν renderer
Πώς ενσωματώνετε ένα πρόγραμμα προβολής PDF σε μια εφαρμογή Delphi;
Το TPDFlibViewer.LoadFromFile ανοίγει ένα έγγραφο μέσω του εσωτερικού στιγμιότυπου TPDFlib του ίδιου του στοιχείου ελέγχου και επιστρέφει 1 σε περίπτωση επιτυχίας, επομένως το ελάχιστο πρόγραμμα προβολής είναι μια κλήση κατασκευαστή (constructor), μια ανάθεση Parent και μία φόρτωση. Εάν διαθέτετε ήδη ένα στιγμιότυπο TPDFlib αλλού στην εφαρμογή, το AttachLibrary εμφανίζει το επιλεγμένο έγγραφό του, με την ιδιοκτησία να παραμένει στη δική σας πλευρά
uses PDFlibViewer;
procedure TMainForm.FormCreate(Sender: TObject);
begin
FViewer := TPDFlibViewer.Create(Self);
FViewer.Parent := Self;
FViewer.Align := alClient;
if FViewer.LoadFromFile('quarterly-report.pdf', '') = 1 then
FViewer.ViewerMode := vmHand; // pan by dragging; a plain click still follows links
end;
Η ιδιότητα ViewerMode επιλέγει τι κάνει το ποντίκι, ανάμεσα σε πέντε τιμές: vmSelect είναι το προεπιλεγμένο εργαλείο επιλογής κειμένου, vmHand πραγματοποιεί μετατόπιση (panning) με σύρσιμο με κέρσορα αρπαγής (grab cursor) ενώ ένα απλό κλικ εξακολουθεί να ακολουθεί συνδέσμους, vmZoom είναι ένα ζουμ επιλογής (marquee zoom) που μεγεθύνει τη συρμένη ζώνη για να γεμίσει το πλαίσιο προβολής (viewport), vmSnapshot καταγράφει μια συρμένη περιοχή στο πρόχειρο (με ένα συμβάν OnSnapshot και έναν διακόπτη SnapshotToClipboard), και vmHighlight σχεδιάζει μόνιμες ημιδιαφανείς επισημάνσεις περιοχής. Το εργαλείο επισήμανσης αξίζει μια σημείωση σχετικά με τις συντεταγμένες: οι περιοχές αποθηκεύονται στον χώρο χρήστη της σελίδας (page user space) και όχι σε pixel πελάτη (client pixels), επομένως τα σημάδια AddHighlightRegion επιβιώνουν από το ζουμ, την περιστροφή της προβολής και την πλοήγηση σελίδας χωρίς καμία τήρηση βιβλίων (bookkeeping) από την πλευρά σας. Σε μια περιστραμμένη προβολή, η κατεύθυνση συρσίματος μπορεί ακόμη και να είναι αντίθετη προς τους άξονες της σελίδας, γεγονός που παράγει αρνητικό πλάτος ή ύψος μετά την αντίστροφη χαρτογράφηση περιστροφής — το στοιχείο ελέγχου ομαλοποιεί το ορθογώνιο για εσάς
Πώς συνδέετε μια γραμμή εργαλείων PDF στο Delphi χωρίς κώδικα σύνδεσης (glue code);
Η μονάδα PDFlibViewerActions αφαιρεί εντελώς τον κώδικα σύνδεσης της γραμμής εργαλείων. Παρέχει είκοσι έτοιμους απογόνους TAction που καλύπτουν την πλοήγηση σελίδας (TPDFlibViewerFirstPage, TPDFlibViewerPriorPage, TPDFlibViewerNextPage, TPDFlibViewerLastPage), το ζουμ (TPDFlibViewerZoomIn, TPDFlibViewerZoomOut, TPDFlibViewerFitWidth, TPDFlibViewerFitPage, TPDFlibViewerActualSize), την περιστροφή προβολής (TPDFlibViewerRotateClockwise, TPDFlibViewerRotateAntiClockwise), την εκτύπωση, τα παράθυρα διαλόγου ανοίγματος και αποθήκευσης αρχείου, την αντιγραφή επιλεγμένου κειμένου και μία ενέργεια διακόπτη ανά εργαλείο ποντικιού (TPDFlibViewerSelectMode, TPDFlibViewerHandMode, TPDFlibViewerMarqueeZoomMode, TPDFlibViewerSnapshotMode, TPDFlibViewerHighlightMode). Κάθε ενέργεια δείχνει σε ένα στοιχείο ελέγχου μέσω της δημοσιευμένης ιδιότητάς της Viewer και διατηρεί τη δική της κατάσταση: κάθε ενέργεια απενεργοποιείται όταν δεν υπάρχει ανοιχτό έγγραφο, οι ενέργειες πλοήγησης παρακολουθούν τη θέση της σελίδας, η αντιγραφή παρακολουθεί εάν υπάρχει επιλογή και οι ενέργειες λειτουργίας και προσαρμογής διατηρούν την επιλεγμένη κατάστασή τους σε συγχρονισμό με το ενεργό εργαλείο και τη λειτουργία ζουμ. Κατά το χρόνο σχεδιασμού (design time), όλες εγγράφονται στην κατηγορία losLab PDF του προγράμματος επεξεργασίας Action List· κατά το χρόνο εκτέλεσης (runtime), οι ίδιες κλάσεις είναι τρεις γραμμές η καθεμία
uses System.Actions, Vcl.ActnList, PDFlibViewer, PDFlibViewerActions;
procedure TMainForm.BuildToolbar;
var
NextPage: TPDFlibViewerNextPage;
FitWidth: TPDFlibViewerFitWidth;
HandMode: TPDFlibViewerHandMode;
begin
NextPage := TPDFlibViewerNextPage.Create(Self);
NextPage.ActionList := ActionList1;
NextPage.Viewer := FViewer;
btnNext.Action := NextPage;
FitWidth := TPDFlibViewerFitWidth.Create(Self);
FitWidth.ActionList := ActionList1;
FitWidth.Viewer := FViewer;
btnFitWidth.Action := FitWidth;
HandMode := TPDFlibViewerHandMode.Create(Self);
HandMode.ActionList := ActionList1;
HandMode.Viewer := FViewer;
btnHand.Action := HandMode;
end;
Μια παγίδα εμβέλειας μονάδας (unit-scope trap) αξίζει να είναι γνωστή εάν δημιουργήσετε τη δική σας καταχώριση ενεργειών πάνω σε αυτό το μοτίβο. Το RegisterActions δηλώνεται στο System.Actions, όχι στο Vcl.ActnList, και το σφάλμα μεταγλωττιστή που λαμβάνετε με μόνο το Vcl.ActnList στη δήλωση uses είναι ενεργά παραπλανητικό: η κυριολεκτική τιμή πίνακα κλάσεων (class-array literal) [TAction1, TAction2, ...] καταλήγει να αναλύεται ως σύνολο, παράγοντας μια σειρά σφαλμάτων E2010 Incompatible types: 'Integer' and 'class of ...' που μοιάζουν με αναντιστοιχία τύπων στις κλάσεις ενεργειών σας. Η διόρθωση είναι μια απλή προσθήκη στη δήλωση uses, αλλά η διάγνωση δείχνει παντού εκτός από αυτήν. Για τις γραμμές εργαλείων ζουμ, το TPDFlibViewer.EffectiveZoomPercent αναφέρει το ζουμ που είναι πραγματικά σε ισχύ — συμπεριλαμβανομένου του υπολογισμένου συντελεστή στις λειτουργίες προσαρμογής πλάτους (fit-width) και προσαρμογής σελίδας (fit-page) — έτσι ώστε ένα βήμα μεγέθυνσης να μπορεί να βασιστεί στην πραγματική τρέχουσα μεγέθυνση αντί για την τελευταία τιμή που εκχωρήσατε
Επισήμανση αποτελεσμάτων αναζήτησης και η αναστροφή συντεταγμένων πίσω από αυτήν
Το HighlightSearchHits αναζητά σε ολόκληρο το έγγραφο ένα ερώτημα και σχεδιάζει μια ημιδιαφανή γραμμή πάνω από κάθε αντιστοίχιση, επιστρέφοντας τον αριθμό των αποτελεσμάτων· το ClearSearchHighlights, το SearchHighlightCount και η ιδιότητα SearchHighlightColor συμπληρώνουν τη διεπαφή. Επειδή τα ορθογώνια αποτελεσμάτων αποθηκεύονται στο χώρο της σελίδας και επαναπροβάλλονται σε κάθε σχεδιασμό, οι γραμμές παραμένουν κολλημένες στο κείμενό τους καθώς ο χρήστης κυλά, ζουμάρει και περιστρέφει την προβολή. Εάν χρησιμοποιείτε το συνοδευτικό TPDFlibSearchPanel, η συνάρτησή του RunSearch σχεδιάζει αυτόματα τις ίδιες γραμμές στο συνδεδεμένο πρόγραμμα προβολής μέσω της ιδιότητας HighlightInView, η οποία είναι ενεργοποιημένη από προεπιλογή
procedure TMainForm.btnSearchClick(Sender: TObject);
var
Hits: Integer;
begin
FViewer.SearchHighlightColor := clYellow;
Hits := FViewer.HighlightSearchHits(edtQuery.Text, False); // case-insensitive
StatusBar1.SimpleText := Format('%d matches', [Hits]);
end;
Το λεπτό σημείο, και ο λόγος για τον οποίο προτιμάται η ενσωματωμένη διαδρομή έναντι της δημιουργίας δικής σας από ακατέργαστα αποτελέσματα αναζήτησης, είναι η μετατροπή του συστήματος συντεταγμένων. Η μηχανή κειμένου αναφέρει τα ορθογώνια αντιστοίχισης σε συντεταγμένες που εξαρτώνται από τη ρύθμιση Origin της βιβλιοθήκης: με Origin 0 ή 3 ο άξονας Y εκτελείται από κάτω προς τα πάνω όπως στον εγγενή χώρο χρήστη του PDF, και με Origin 2 ή 3 ο άξονας X μετριέται από τη δεξιά άκρη. Η επικάλυψη (overlay) του προγράμματος προβολής λειτουργεί σε χώρο σελίδας από πάνω προς τα κάτω, με βάση τα αριστερά, επομένως κάθε ορθογώνιο αποτελέσματος πρέπει να αναστραφεί (Y = PageHeight - Bottom, και όπου ισχύει X = PageWidth - Right) πριν αποθηκευτεί — λαμβάνοντας την αναφερόμενη επάνω άκρη στην ονομαστική της αξία υπό την προεπιλεγμένη προέλευση σχεδιάζει την επισήμανση στην κατακόρυφα καθρεφτισμένη θέση στη σελίδα. Το TPDFlibViewer εκτελεί αυτή τη μετατροπή ανάλογα με την ενεργή προέλευση (origin) όταν καταγράφει κάθε αποτέλεσμα, και στη συνέχεια εφαρμόζει τη χαρτογράφηση εμπρόσθιας περιστροφής κατά το χρόνο σχεδιασμού, γι' αυτό και οι γραμμές προσγειώνονται σωστά σε κάθε γωνία. Η ίδια η ημιδιαφάνεια χρησιμοποιεί το Windows AlphaBlend API με σταθερό πηγαίο άλφα (source alpha), καθώς ο καμβάς VCL δεν έχει εγγενή σύνθεση άλφα. Οι υποκείμενες διεπαφές API αναζήτησης κειμένου και γεωμετρίας καλύπτονται σε βάθος στο άρθρο για την αναζήτηση κειμένου και την απαρίθμηση στοιχείων σελίδας
Κλικ σε σχόλια, υποδείξεις και εξαγωγή συνημμένων
Το TPDFlibViewer αντιμετωπίζει τα σχόλια (annotations) ως διαδραστικά αντικείμενα πρώτης κατηγορίας. Κάνοντας κλικ σε ένα σχόλιο που δεν είναι σύνδεσμος, ενεργοποιείται το OnAnnotClick με τον αριθμό σελίδας, τον δείκτη σχολίου με βάση το 1 και τη συμβολοσειρά υποτύπου· το AnnotAtPagePoint εκθέτει την ίδια δοκιμή hit προγραμματιστικά και παρακάμπτει τα συνοδευτικά σχόλια Popup, ώστε να λαμβάνετε πάντα το σχόλιο που βλέπει πραγματικά ο χρήστης. Η αιώρηση (hovering) πάνω από ένα σχόλιο εμφανίζει το κείμενο Contents ως υπόδειξη (hint) του στοιχείου ελέγχου μέσω του διακόπτη AnnotHints, ο οποίος είναι ενεργοποιημένος από προεπιλογή και συνεργάζεται με την τυπική ιδιότητα VCL ShowHint. Για σχόλια FileAttachment, το SaveAnnotAttachmentToFile εγγράφει το ενσωματωμένο αρχείο στο δίσκο και επιστρέφει 1 σε περίπτωση επιτυχίας, γεγονός που ολοκληρώνει μια ροή εργασίας click-to-save σε λίγες μόνο γραμμές
procedure TMainForm.ViewerAnnotClick(Sender: TObject; APage, AnnotIndex: Integer;
const Subtype: WideString);
begin
if Subtype = 'FileAttachment' then
if FViewer.SaveAnnotAttachmentToFile(APage, AnnotIndex,
'C:\Temp\attached-invoice.xml') = 1 then
StatusBar1.SimpleText := 'Attachment saved';
end;
Η εκτύπωση και τα όρια που πρέπει να σχεδιάσετε
Η εκτύπωση δεν χρειάζεται ξεχωριστή ενσωμάτωση. Το PrintDoc εμφανίζει τον τυπικό διάλογο εκτύπωσης των Windows — επιλογή εκτυπωτή, εύρος σελίδων, αντίγραφα, σελιδοποίηση (collation) — και παραδίδει τη δουλειά στη μηχανή εκτύπωσης της βιβλιοθήκης, ενώ το PrintDoc(False) στέλνει ολόκληρο το έγγραφο στον προεπιλεγμένο εκτυπωτή χωρίς καθόλου UI. Το PrintDocRange λειτουργεί πλήρως προγραμματιστικά με ένα ρητό όνομα εκτυπωτή, αρχική και τελική σελίδα, αριθμό αντιγράφων και σημαία σελιδοποίησης, το οποίο είναι το σωστό σημείο εισόδου για εργασίες εκτύπωσης παρτίδας ή ενεργοποιούμενες από διακομιστή που καθοδηγούνται από το ίδιο στιγμιότυπο προγράμματος προβολής
Δύο όρια αξίζουν ειλικρινή αναφορά. Πρώτον, το TPDFlibViewer είναι ένα στοιχείο ελέγχου Windows VCL· το επίπεδο προγράμματος προβολής αποκλείεται από τις εκδόσεις NOVCL, επομένως οι υπηρεσίες κονσόλας και οι στόχοι που δεν είναι VCL θα πρέπει να χρησιμοποιούν απευθείας το υποκείμενο API απόδοσης TPDFlib. Δεύτερον, η χρήση χωρίς γραφικό περιβάλλον (headless) υποστηρίζεται σκόπιμα αλλά είναι περιορισμένη: το CaptureRegion αποδίδει οποιαδήποτε περιοχή πελάτη — σελίδες συν επισημάνσεις — σε ένα bitmap που ανήκει στον καλούντα και λειτουργεί σε ένα στοιχείο ελέγχου χωρίς γονέα χωρίς λαβή παραθύρου (window handle), επειδή η διαδρομή σχεδίασης αποφεύγει τις κλήσεις VCL που θα επέβαλαν τη δημιουργία λαβής. Αυτό καθιστά πρακτική την επαλήθευση σε επίπεδο pixel σε αυτοματοποιημένες δοκιμές, αλλά οτιδήποτε χρειάζεται πραγματικά έναν βρόχο μηνυμάτων (message loop), όπως τα διαδραστικά εργαλεία ποντικιού, εξακολουθεί να ανήκει σε μια πραγματική συνεδρία UI. Από την πλευρά της απόδοσης, το στοιχείο ελέγχου διατηρεί μια προσωρινή μνήμη σελίδας μνήμης με διαμορφώσιμο όριο μέσω του SetCacheLimit, και μπορεί να διατηρήσει τις αποδοθείσες σελίδες σε όλες τις συνεδρίες· το άρθρο για την προσωρινή μνήμη σελίδας δίσκου του προγράμματος προβολής καλύπτει αυτό το επίπεδο και τη συμπεριφορά του ανά οθόνη DPI λεπτομερώς
Πέντε βήματα, λοιπόν: τοποθετήστε το στοιχείο ελέγχου και καλέστε το LoadFromFile, συνδέστε τις έτοιμες ενέργειες από το PDFlibViewerActions, καλέστε το HighlightSearchHits για αναζήτηση εντός σελίδας, χειριστείτε το OnAnnotClick για ροές εργασίας σχολίων και εκθέστε το PrintDoc. Κάθε κομμάτι που εμφανίζεται εδώ αποστέλλεται στην τυπική losLab PDF Library για Delphi, όπου το στοιχείο ελέγχου προγράμματος προβολής, οι κλάσεις ενεργειών και η μηχανή απόδοσης εγκαθίστανται μαζί