Ένα πρόγραμμα προβολής PDF στο Delphi καταλήγει σε δύο στοιχεία (components) και την καλωδίωση (wiring) μεταξύ τους. Το TPdf κατέχει (owns) το έγγραφο: ανοίγει το αρχείο, το αποκρυπτογραφεί (decrypts) και απαντά σε ερωτήσεις σχετικά με τον αριθμό σελίδων και τα μεταδεδομένα. Το TPdfView είναι το οπτικό στοιχείο (visual control) ελέγχου που ζωγραφίζει (paints) σελίδες στην οθόνη και χειρίζεται την κύλιση (scrolling), το ζουμ (zoom) και τη σελίδα που βλέπει ο χρήστης αυτή τη στιγμή. Το PDFium Component ενθυλακώνει (wraps) την ίδια μηχανή απόδοσης (rendering engine) που παρέχεται (ships) μέσα στο Chrome, έτσι τα γλύμματα, η εξομάλυνση (anti-aliasing) και το χρώμα που έχετε στον καμβά (canvas) ταιριάζουν με αυτό που βλέπουν ήδη οι χρήστες σας στο πρόγραμμα περιήγησής τους (browser). Η δουλειά δεν είναι στην απόδοση. Είναι στη σύνδεση του αντικειμένου του εγγράφου με το view, στη φόρτωση χωρίς κατάρρευση (crashing) σε ένα κατεστραμμένο (damaged) ή προστατευμένο με κωδικό πρόσβασης (password-protected) αρχείο, και στο να δοθούν στον χρήστη τα λίγα (handful) στοιχεία ελέγχου (controls) που κάνουν ένα πρόγραμμα προβολής να μοιάζει ολοκληρωμένο (finished): αλλαγή σελίδας (turn the page), αλλαγή του ζουμ, προσαρμογή (fit) της σελίδας στο παράθυρο
Αυτό το άρθρο περιγράφει (walks through) αυτήν τη συναρμολόγηση με τη σειρά που πραγματικά την κατασκευάζετε. Όλα εδώ αποδίδουν μία σελίδα τη φορά, το οποίο είναι αυτό που θέλουν οι περισσότερες ροές εργασίας εγγράφων. Αν χρειάζεστε σελίδες στοιβαγμένες (stacked) σε μία συνεχώς κυλιόμενη (continuously scrolling) στήλη, αυτό είναι μια διαφορετική απόφαση διάταξης (layout decision) και όχι η διαδρομή (path) που ακολουθείται εδώ
Σύνδεση του TPdf με το TPdfView
Ρίξτε (Drop) ένα TPdf και ένα TPdfView στη φόρμα, και στη συνέχεια πείτε στο view ποιο έγγραφο να εμφανίσει. Αυτή η μεμονωμένη εκχώρηση (assignment) είναι ολόκληρος ο δεσμός μεταξύ του μη οπτικού εγγράφου και του στοιχείου ελέγχου (control) που το ζωγραφίζει (paints)
procedure TFormMain.FormCreate(Sender: TObject);
begin
// Pdf and PdfView were dropped at design time.
PdfView.Pdf := Pdf; // the view paints whatever this document holds
PdfView.FitMode := pfmFitWidth; // start the user at a sensible zoom
end;
Πριν τρέξει οποιοδήποτε από αυτά, η εγγενής βιβλιοθήκη (native library) PDFium πρέπει να βρίσκεται στο μηχάνημα. Το PDFium Component καλεί το pdfium32.dll ή το pdfium64.dll ανάλογα με την πλατφόρμα προορισμού (target platform) σας, και το έγγραφο απλά αρνείται να ανοίξει αν το DLL δεν μπορεί να βρεθεί. Στείλτε (Ship) το αντίστοιχο DLL δίπλα στο εκτελέσιμό (executable) σας, ή τοποθετήστε το όπου ο φορτωτής του συστήματος (system loader) θα το βρει. Οι εκδόσεις με ενεργοποιημένο το V8 (V8-enabled builds) υπάρχουν μόνο για PDF που φέρουν JavaScript την οποία θέλετε να εκτελέσετε, κάτι που ένα απλό (plain) πρόγραμμα προβολής δεν κάνει, επομένως προτιμήστε το τυπικό (standard) DLL εκτός αν έχετε κάποιο συγκεκριμένο (concrete) λόγο να μην το κάνετε
Φόρτωση εγγράφου χωρίς εμπιστοσύνη στην είσοδο (input)
Το ένστικτο (instinct) είναι να τυλίξετε (wrap) τη φόρτωση σε ένα try/except και να αντιμετωπίσετε μια εξαίρεση (thrown exception) ως αποτυχία (failure). Αυτό το ένστικτο είναι λάθος εδώ, και αν το κάνετε λάθος παράγεται ένα πρόγραμμα προβολής που φαίνεται εντάξει μέχρι κάποιος να του παραδώσει ένα σπασμένο (broken) αρχείο. Η ρύθμιση Active := True δεν εγείρει (does not raise) σε περίπτωση αποτυχίας φόρτωσης. Το PDFium Component πιάνει το εσωτερικό σφάλμα και αφήνει το Active να παραμένει False, επομένως ο μόνος ειλικρινής τρόπος (honest way) να γνωρίζετε αν το έγγραφο άνοιξε είναι να διαβάσετε την ιδιότητα πίσω (read the property back) αφού την ορίσετε (set it)
procedure TFormMain.OpenDocument(const FileName: string);
begin
Pdf.FileName := FileName;
Pdf.Active := True; // never raises; failure leaves Active = False
if not Pdf.Active then
begin
ShowMessage('Could not open ' + FileName);
Exit;
end;
PdfView.PageNumber := 1; // the view tracks its own current page
UpdatePageLabel;
end;
Δύο πράγματα αξίζουν προσοχής. Το πρώτο είναι ότι το PageNumber (ΑριθμόςΣελίδας) υπάρχει και στα δύο αντικείμενα και τα δύο είναι ανεξάρτητα (independent). Το Pdf.PageNumber είναι η έννοια του εγγράφου για μια τρέχουσα σελίδα· το PdfView.PageNumber είναι η σελίδα που το στοιχείο ελέγχου πραγματικά εμφανίζει, και είναι αυτή που ορίζετε για να μετακινήσετε τον χρήστη μέσα στο αρχείο. Η ρύθμιση του ενός δεν μετακινεί (move) το άλλο, επομένως ένα πρόγραμμα προβολής (viewer) οδηγεί (drives) πάντα την ιδιότητα του view. Το δεύτερο είναι η δεικτοδότηση με βάση το 1 (1-based indexing): οι σελίδες εκτείνονται (run) από το 1 έως το Pdf.PageCount, όχι από το 0, γεγονός που αιφνιδιάζει (catches) οποιονδήποτε έχει συνηθίσει σε πίνακες (arrays) με βάση το μηδέν (zero-based arrays)
Χειρισμός ενός κρυπτογραφημένου αρχείου
Τα κρυπτογραφημένα (Encrypted) έγγραφα ενσωματώνονται (fold) στην ίδια διαδρομή φόρτωσης (load path). Εάν ο κωδικός πρόσβασης (open password) οριστεί πριν από την ενεργοποίηση (activation), το έγγραφο αποκρυπτογραφείται καθώς ανοίγει· εάν είναι λάθος ή λείπει, το Active παραμένει False ακριβώς όπως κάνει για ένα κατεστραμμένο (corrupt) αρχείο. Επομένως, η ανάκτηση (recovery) είναι να ζητήσετε (prompt) έναν κωδικό πρόσβασης και να δοκιμάσετε ξανά την ενεργοποίηση
procedure TFormMain.OpenWithPassword(const FileName: string);
var
Password: string;
begin
Pdf.FileName := FileName;
Pdf.Active := True;
if not Pdf.Active then
begin
if InputQuery('Password required', 'Password:', Password) then
begin
Pdf.Password := Password; // must be set before Active := True
Pdf.Active := True;
end;
if not Pdf.Active then
begin
ShowMessage('Unable to open the document.');
Exit;
end;
end;
PdfView.PageNumber := 1;
end;
Επειδή η αποτυχία (failure) είναι σιωπηλή (silent) τόσο για έναν λανθασμένο κωδικό πρόσβασης όσο και για ένα κατεστραμμένο αρχείο, δεν μπορείτε να ξεχωρίσετε (tell the two apart) τα δύο μόνο από το Active. Στην πράξη αυτό είναι αποδεκτό για ένα πρόγραμμα προβολής: ο χρήστης είτε παρέχει τον σωστό κωδικό πρόσβασης είτε μαθαίνει ότι το αρχείο δεν θα ανοίξει, και το μήνυμα διαβάζεται το ίδιο και στις δύο περιπτώσεις
Περιήγηση σελίδων στο έγγραφο
Με το έγγραφο ανοιχτό, η πλοήγηση είναι αριθμητική (arithmetic) στο PdfView.PageNumber οριοθετημένη (bounded) από το Pdf.PageCount. Η μόνη πραγματική δουλειά είναι ο περιορισμός (clamping), έτσι ώστε τα κουμπιά να μην ωθούν ποτέ τη σελίδα εκτός εύρους (out of range) και το πρώτο και το τελευταίο κουμπί να παραμένουν απενεργοποιημένα (disabled) στα άκρα (ends) του αρχείου
procedure TFormMain.GoToPage(NewPage: Integer);
begin
if not Pdf.Active then
Exit;
if NewPage < 1 then
NewPage := 1
else if NewPage > Pdf.PageCount then
NewPage := Pdf.PageCount;
PdfView.PageNumber := NewPage;
UpdatePageLabel;
end;
// the four navigation buttons reduce to one call each
procedure TFormMain.FirstClick(Sender: TObject); begin GoToPage(1); end;
procedure TFormMain.PrevClick(Sender: TObject); begin GoToPage(PdfView.PageNumber - 1); end;
procedure TFormMain.NextClick(Sender: TObject); begin GoToPage(PdfView.PageNumber + 1); end;
procedure TFormMain.LastClick(Sender: TObject); begin GoToPage(Pdf.PageCount); end;
Ένα πλαίσιο κειμένου (text box) "μετάβαση στη σελίδα N" (go to page N) είναι η ίδια κλήση GoToPage τροφοδοτούμενη από έναν αναλυμένο (parsed) ακέραιο (integer), και ο περιορισμός (clamp) καλύπτει την περίπτωση (case) όπου ο χρήστης πληκτρολογεί 9999 σε ένα αρχείο δέκα σελίδων. Διατηρήστε το UpdatePageLabel ως το μοναδικό σημείο που γράφει "Σελίδα 3 από 12" (Page 3 of 12), ώστε η ένδειξη (readout) να μην αποσυγχρονίζεται (drifts out of sync) ποτέ από αυτό που δείχνει το view
Ζουμ (Zoom): ρητά ποσοστά (explicit percentages) και λειτουργίες προσαρμογής (fit modes)
Το ζουμ στο TPdfView έρχεται (arrives) σε δύο "γεύσεις" (flavors) που αλληλεπιδρούν, και η κατανόηση της αλληλεπίδρασης (interaction) είναι η διαφορά μεταξύ ενός ελέγχου ζουμ που συμπεριφέρεται σωστά (behaves) και ενός που πολεμά τον χρήστη (fights the user). Η άμεση (direct) διαδρομή είναι η ιδιότητα Zoom, ένα ποσοστό (percentage) όπου το 100 σημαίνει πραγματικό μέγεθος (actual size). Η άλλη διαδρομή είναι η FitMode (ΛειτουργίαΠροσαρμογής), η οποία λέει στο view να υπολογίσει το ζουμ για εσάς και να συνεχίσει να το υπολογίζει ξανά καθώς το παράθυρο αλλάζει μέγεθος (resizes)
// fixed magnifications
PdfView.Zoom := 100; // actual size
PdfView.Zoom := 50; // half
PdfView.Zoom := 200; // double
// let the view size the page to the window, and keep it sized on resize
PdfView.FitMode := pfmFitWidth; // page width fills the control
PdfView.FitMode := pfmFitPage; // whole page visible
PdfView.FitMode := pfmActualSize; // 1:1 with the document's points
Εδώ είναι το κομμάτι (part) που μπερδεύει (trips up) τους ανθρώπους. Η απευθείας (directly) εκχώρηση του Zoom επαναφέρει (resets) το FitMode στο pfmNone. Αυτή είναι σωστή συμπεριφορά, όχι σφάλμα (bug): τη στιγμή που ο χρήστης επιλέγει (picks) ένα ακριβές (exact) 150%, το view δεν μπορεί πλέον να τιμά (honoring) ταυτόχρονα το "προσαρμογή στο πλάτος" (fit to width), επειδή τα δύο αιτήματα (requests) συγκρούονται (conflict). Η συνέπεια (consequence) για τη διεπαφή χρήστη (UI) σας είναι ότι ένα κουμπί μεγέθυνσης (zoom-in) και ένα κουμπί προσαρμογής στη σελίδα (fit-to-page) είναι αμοιβαία αποκλειόμενες (mutually exclusive) καταστάσεις (states), και η γραμμή εργαλείων (toolbar) πρέπει να κάνει την ενεργή (active) λειτουργία ορατή. Όταν ο χρήστης κάνει κλικ στο fit-to-page, ορίστε το FitMode· όταν κάνει κλικ σε ένα αριθμητικό (numeric) ζουμ, ορίστε το Zoom και αφήστε το να καθαρίσει (clear) τη λειτουργία προσαρμογής από μόνο του
Αν προτιμάτε (rather) να υπολογίσετε (compute) την τιμή προσαρμογής μόνοι σας, ίσως για να "ταΐσετε" (seed) ένα ρυθμιστικό ζουμ (zoom slider) με το τρέχον (current) ποσοστό προσαρμογής, οι βοηθοί (helpers) ανά-σελίδα σας δίνουν τους αριθμούς χωρίς να αλλάξουν (changing) τη λειτουργία (mode). Τα PageWidthZoom[N], PageZoom[N] και ActualSizeZoom[N] επιστρέφουν το ποσοστό που θα προσάρμοζε τη σελίδα N στο πλάτος, θα την προσάρμοζε ολόκληρη (whole) ή θα την απέδιδε στο πραγματικό της μέγεθος (actual size)
// seed a zoom readout from the fit-to-width value of the current page
var
FitPercent: Double;
begin
FitPercent := PdfView.PageWidthZoom[PdfView.PageNumber];
ZoomEdit.Text := Format('%.0f%%', [FitPercent]);
end;
Τι πραγματικά χρειάζεται ένα ολοκληρωμένο πρόγραμμα προβολής
Το πρόγραμμα προβολής παραπάνω είναι μερικές δεκάδες γραμμές, και ήδη κάνει τη δουλειά που χρειάζεται μια ροή εργασίας (workflow) εγγράφων: ανοίγει ένα αρχείο, επιβιώνει (survive) από ένα κακό (bad) αρχείο, δείχνει μια σελίδα, μετακινείται μεταξύ σελίδων, και αλλάζει τη μεγέθυνση (magnification) με το χέρι (by hand) ή με προσαρμογή (by fit). Το PDFium κάνει τα δύσκολα μέρη σιωπηλά (silently). Οι ενσωματωμένες (Embedded) γραμματοσειρές επιλύονται (resolve), οι σχολιασμοί (annotations) και τα πεδία φόρμας (form fields) ζωγραφίζονται εκεί που τα τοποθετεί (places) το έγγραφο, και η σελίδα που βλέπετε ταιριάζει με αυτή που θα έβλεπε ένας χρήστης του Chrome, επειδή είναι η ίδια μηχανή (engine) που σχεδιάζει (drawing) και τα δύο
Από αυτή τη βάση (base) οι προσθήκες είναι περισσότερο σταδιακές (incremental) παρά δομικές (structural). Η επιλογή κειμένου (Text selection) και η αναζήτηση διαβάζουν από το ίδιο επίπεδο κειμένου (text layer) που το PDFium ήδη χτίζει· τα μεταδεδομένα (metadata) όπως το Pdf.Title (Τίτλος) και το Pdf.Author (Συγγραφέας) απέχουν (is away) μόνο μία ανάγνωση ιδιότητας (property read)· η περιστροφή (rotation) και η κλίμακα του γκρι (grayscale) είναι επιλογές απόδοσης (render options) που περνάτε (pass) όταν σχεδιάζετε (draw) μια σελίδα σε ένα bitmap. Κανένα από αυτά δεν αλλάζει τη ραχοκοκαλιά (spine) που έχετε εδώ, η οποία είναι το αντικείμενο του εγγράφου, το view και η ροή (flow) φορτώνω-μετά-πλοηγούμαι (load-then-navigate) που τα συνδέει. Κάντε αυτή τη ραχοκοκαλιά σωστά (Get that spine right) και τα υπόλοιπα είναι διακόσμηση (decoration)
Τα στοιχεία TPdf και TPdfView που χρησιμοποιούνται καθ' όλη τη διάρκεια (throughout) αποτελούν μέρος του PDFium Component για Delphi και C++Builder, το οποίο φέρει την πλήρη αναφορά (full reference) του προγράμματος προβολής στη σελίδα του προϊόντος του