Δύο έγγραφα ανοιχτά ταυτόχρονα, στον ίδιο αριθμό σελίδας, καθένα στο δικό του πάνελ με κύλιση: αυτός είναι ο πυρήνας ενός προγράμματος σύγκρισης. Το PDFium Component το παραδίδει μέσα από ένα ευθύ μοντέλο αντικειμένων όπου το TPdf κατέχει το αρχείο και το TPdfView κατέχει την εμφάνιση. Ένα έγγραφο, ένα TPdf, ένα TPdfView. Θέλετε τρία πάνελ, έχετε τρία ζεύγη. Τα δύσκολα μέρη δεν είναι οι κλήσεις του API· είναι η αριθμητική της διάταξης όταν αλλάζει μέγεθος το παράθυρο και η λογική συγχρονισμού σελίδων όταν αποφασίζετε ποια προβολή πρέπει να ακολουθεί ποια
Διάταξη της φόρμας
Η φόρμα VCL κρατά τρία δοχεία TScrollBox το ένα δίπλα στο άλλο, καθένα με ένα TPdfView μέσα του και στοιχισμένο σε alClient ώστε να γεμίζει το κουτί. Δύο στοιχεία TSplitter κάθονται ανάμεσα στα κουτιά ώστε ο χρήστης να μπορεί να ρυθμίζει τα πλάτη των στηλών κατά την εκτέλεση. Μια γραμμή εργαλείων πάνω από τα πάνελ κουβαλά τα κουμπιά ανοίγματος, τα χειριστήρια ζουμ και την εναλλαγή ανάμεσα σε δύο και τρεις προβολές
Η λειτουργία τριών προβολών είναι μια δυαδική τιμή που η φόρμα παρακολουθεί εσωτερικά. Όταν αλλάζει, υπολογίζετε ξανά τα πλάτη και εμφανίζετε ή κρύβετε την τρίτη στήλη. Η απλούστερη προσέγγιση είναι να καθαρίσετε όλες τις ιδιότητες Align, να κρύψετε τους διαχωριστές και έπειτα να ορίσετε απόλυτες θέσεις:
procedure TFormMain.UpdateLayout;
var
TotalWidth: Integer;
begin
TotalWidth := ClientWidth;
if ThreeViewMode then
begin
ScrollBox3.Visible := True;
ScrollBox1.Left := 0;
ScrollBox1.Width := TotalWidth div 3;
ScrollBox2.Left := ScrollBox1.Width;
ScrollBox2.Width := TotalWidth div 3;
ScrollBox3.Left := ScrollBox2.Left + ScrollBox2.Width;
ScrollBox3.Width := TotalWidth - ScrollBox3.Left;
// Εφαρμόστε το ίδιο (ClientHeight - ύψος γραμμής εργαλείων) και στις τρεις τιμές Height
end
else
begin
ScrollBox3.Visible := False;
ScrollBox1.Left := 0;
ScrollBox1.Width := TotalWidth div 2;
ScrollBox2.Left := ScrollBox1.Width;
ScrollBox2.Width := TotalWidth - ScrollBox2.Left;
end;
end;
Το να ορίσετε Align := alNone και στα τρία κουτιά πριν από την ακέραιη αριθμητική αποτρέπει τη μηχανή περιορισμών της VCL να παλεύει με τις αναθέσεις σας. Επαναφέρετε την ορατότητα των διαχωριστών μετά την τοποθέτηση, αν θέλετε αλλαγή μεγέθους με σύρσιμο στη λειτουργία δύο προβολών
Το ύψος κάθε scroll box είναι η περιοχή πελάτη μείον το ύψος του πάνελ της γραμμής εργαλείων. Επειδή η γραμμή εργαλείων είναι αγκυρωμένη στην κορυφή με alTop, το ClientHeight - PanelButtons.Height σάς δίνει τον αξιοποιήσιμο κατακόρυφο χώρο. Αναθέστε το και στα τρία κουτιά μέσα στην ίδια κλήση UpdateLayout, ώστε να μην υπάρξει ποτέ καρέ όπου ένα κουτί είναι ψηλότερο από τα άλλα και προκαλεί τρεμόπαιγμα διάταξης
Άνοιγμα ενός εγγράφου
Κάθε ζεύγος πάνελ χρειάζεται τη δική του διαδικασία ανοίγματος. Το μοτίβο είναι σύντομο: απενεργοποιήστε το στοιχείο, ορίστε το όνομα αρχείου, ενεργοποιήστε το και έπειτα ελέγξτε το Active· αν έμεινε False, ζητήστε κωδικό και ξαναδοκιμάστε. Σημειώστε ότι το TPdfView.Active είναι αυτό που ελέγχει την απόδοση, ενώ το TPdf.Active είναι αυτό που πραγματικά ανοίγει το αρχείο· είναι ανεξάρτητα. Το να ορίσετε PdfView.Active := True όταν το συνδεδεμένο του TPdf δεν είναι ακόμη ενεργό είναι ακίνδυνο αλλά δεν εμφανίζει τίποτα
procedure TFormMain.OpenPdfFile(PdfComponent: TPdf;
PdfViewComponent: TPdfView);
var
Password: string;
begin
if not OpenDialog.Execute then
Exit;
PdfComponent.Active := False;
PdfComponent.FileName := OpenDialog.FileName;
PdfComponent.Password := '';
PdfComponent.Active := True;
// Οι αποτυχίες φόρτωσης είναι σιωπηλές: το Active μένει False αντί να εγερθεί εξαίρεση.
if not PdfComponent.Active then
begin
// Πιθανότατα αρχείο προστατευμένο με κωδικό, δώστε στον χρήστη μία επανάληψη.
if InputQuery('Password', 'Enter document password:', Password) then
begin
PdfComponent.Password := Password;
PdfComponent.Active := True;
end;
end;
if not PdfComponent.Active then
begin
ShowMessage('Could not open ' + OpenDialog.FileName +
' (damaged file or wrong password)');
Exit;
end;
PdfViewComponent.PageNumber := 1;
SetActivePdfView(PdfViewComponent);
end;
Ελέγχετε πάντα το PdfComponent.Active μετά την ανάθεση· ένα κατεστραμμένο αρχείο ή λάθος κωδικός κάνει τη φόρτωση να αποτύχει σιωπηλά, χωρίς να εγείρει εξαίρεση στην προεπιλεγμένη διαδρομή. Το να ορίσετε ρητά PdfViewComponent.PageNumber := 1 μετά από επιτυχές άνοιγμα αποτρέπει έναν ξεπερασμένο αριθμό σελίδας από το προηγούμενο έγγραφο
Το παράθυρο μηνύματος στο τέλος είναι σκόπιμο: θέλετε τα κατεστραμμένα ή μη υποστηριζόμενα αρχεία να αναδεικνύονται αμέσως αντί να καταπίνονται ως ένα ήσυχο κενό πάνελ. Ένας χρήστης που δεν βλέπει τίποτα δεν έχει ιδέα αν το αρχείο φορτώθηκε και είναι απλώς άδειο ή αν το στοιχείο το απέρριψε. Η αναφορά της αποτυχίας κρατά το σφάλμα ορατό
Παρακολούθηση του ενεργού πάνελ
Όταν ο χρήστης κάνει κλικ μέσα σε ένα πάνελ, εκείνο το πάνελ γίνεται ενεργό. Η φόρμα παρακολουθεί ένα ιδιωτικό πεδίο FActivePdfView: TPdfView. Η οπτική ανατροφοδότηση είναι μια αλλαγή χρώματος περιγράμματος στο TScrollBox που το περιέχει: ορίστε το σε clHighlight για το ενεργό και σε clWindow για τα υπόλοιπα. Συνδέστε το σε κάθε TPdfView.OnClick και στη διαδικασία ανοίγματος, ώστε η εστίαση να ακολουθεί το έγγραφο που μόλις ανοίξατε
Ορισμένες λειτουργίες ισχύουν για όλα τα ορατά πάνελ και όχι μόνο για το ενεργό. Μια δυαδική τιμή FAllViewsMode στη φόρμα οδηγεί αυτόν τον κλάδο. Όταν είναι αληθής, οι αλλαγές ζουμ και η πλοήγηση σελίδων απλώνονται σε κάθε πάνελ που έχει ενεργό έγγραφο:
procedure TFormMain.ApplyZoomToAll(NewZoom: Double);
begin
if PdfView1.Active then PdfView1.Zoom := NewZoom;
if PdfView2.Active then PdfView2.Zoom := NewZoom;
if ThreeViewMode and PdfView3.Active then PdfView3.Zoom := NewZoom;
end;
Συγχρονισμένη πλοήγηση σελίδων
Η συγχρονισμένη πλοήγηση είναι προαιρετική αλλά χρήσιμη σε ροές εργασίας αναθεώρησης εγγράφων όπου και τα δύο αρχεία καλύπτουν το ίδιο εύρος σελίδων. Η λογική ανήκει σε έναν χειριστή συμβάντος που πυροδοτείται αφού ο χρήστης πλοηγηθεί σε μία προβολή. Όταν μια προβολή προέλευσης αλλάζει το PageNumber της, ο χειριστής διαδίδει αυτόν τον αριθμό στις άλλες προβολές, με έναν περιορισμό: η προβολή-στόχος πρέπει να έχει τουλάχιστον τόσες σελίδες, αλλιώς παραλείπεται
Τα PageNumber του TPdfView και του TPdf είναι ανεξάρτητα. Το TPdf.PageNumber παρακολουθεί ποια σελίδα θεωρεί τρέχουσα το στοιχείο του εγγράφου· το TPdfView.PageNumber παρακολουθεί τι εμφανίζεται στην οθόνη. Για σκοπούς πλοήγησης θέλετε την ιδιότητα της προβολής, όχι του εγγράφου
Ένα πλαίσιο ελέγχου με ετικέτα κάτι σαν «Συγχρονισμός σελίδων» δίνει τον έλεγχο στον χρήστη. Όταν δεν είναι επιλεγμένο, κάθε πάνελ πλοηγείται ανεξάρτητα και ο χειριστής βγαίνει αμέσως. Αυτή η ανεξαρτησία είναι σημαντική για περιπτώσεις όπου τα δύο έγγραφα έχουν διαφορετικό πλήθος σελίδων ή όπου ο χρήστης θέλει να βρει το αντίστοιχο απόσπασμα σε μια μετάφραση που ξεκινά σε διαφορετική σελίδα. Το να επιβάλλεται πάντα ο συγχρονισμός θα έκανε το εργαλείο δυσκολότερο στη χρήση από μια απλή διάταξη δύο παραθύρων στην επιφάνεια εργασίας
Ένα πράγμα που πρέπει να προσέξετε: η ρύθμιση του PdfView.PageNumber μέσα από τον κώδικα, μέσα στον χειριστή συγχρονισμού, θα πυροδοτήσει και η ίδια το συμβάν αλλαγής σε εκείνη την προβολή. Προφυλαχθείτε από την άπειρη αναδρομή με μια δυαδική σημαία που θέτετε πριν από την ανάθεση και καθαρίζετε αμέσως μετά. Η σημαία είναι ανά φόρμα και όχι ανά προβολή, επειδή και οι τρεις προβολές μοιράζονται τον ίδιο χειριστή
Ζουμ ανά πάνελ
Κάθε TPdfView κουβαλά τη δική του ιδιότητα Zoom, έναν Double σε ποσοστό όπου το Zoom := 100 σημαίνει πραγματικό μέγεθος (100%). Ο ορισμός της παρακάμπτει οποιοδήποτε ενεργό FitMode. Για ένα κουμπί προσαρμογής στο πλάτος στο ενεργό πάνελ, διαβάστε το ζουμ προσαρμογής από το PdfView.PageWidthZoom[PdfView.PageNumber] και αναθέστε το. Για προσαρμογή στη σελίδα, χρησιμοποιήστε το PageZoom[PageNumber]. Και οι δύο είναι ιδιότητες πίνακα με δείκτη τον αριθμό σελίδας με βάση το 1, οπότε προφυλαχθείτε από μηδενικό αριθμό σελίδας πριν τις προσπελάσετε
Όταν εξάγετε την τρέχουσα σελίδα σε εικόνα, διαβάστε την περιστροφή από την προβολή αλλά καλέστε τη RenderPage στο στοιχείο TPdf, όχι στην προβολή. Η μορφή bitmap της TPdf.RenderPage δέχεται ρητές διαστάσεις σε εικονοστοιχεία, μια τιμή TRotation και ένα σύνολο TRenderOptions. Η παραλλαγή συνάρτησης επιστρέφει ένα TBitmap που ανήκει στον καλούντα και το απελευθερώνετε εσείς μετά την αποθήκευση:
procedure TFormMain.SaveActiveViewAsImage;
var
Pdf: TPdf;
Bmp: TBitmap;
Jpeg: TJpegImage;
begin
if not Assigned(FActivePdfView) or not FActivePdfView.Active then
Exit;
Pdf := FActivePdfView.Pdf;
Pdf.PageNumber := FActivePdfView.PageNumber;
Bmp := Pdf.RenderPage(
0, 0,
Round(Pdf.PageWidth * 2),
Round(Pdf.PageHeight * 2),
FActivePdfView.Rotation, [], clWhite);
try
if SavePictureDialog.Execute then
begin
Jpeg := TJpegImage.Create;
try
Jpeg.Assign(Bmp);
Jpeg.CompressionQuality := 90;
Jpeg.SaveToFile(SavePictureDialog.FileName);
finally
Jpeg.Free;
end;
end;
finally
Bmp.Free;
end;
end;
Ο πολλαπλασιαστής 2x στο πλάτος και στο ύψος δίνει πιο ευκρινή έξοδο για έγγραφα με λεπτό κείμενο. Το try/finally γύρω από την απελευθέρωση του bitmap δεν είναι προαιρετικό· μια ακύρωση του TSaveDialog περνά και αυτή από το μπλοκ finally, και θέλετε το bitmap να απελευθερώνεται ανεξάρτητα από το τι έκανε ο χρήστης
Απαιτήσεις DLL
Το PDFium Component τυλίγει τη native βιβλιοθήκη pdfium. Μια διεργασία-ξενιστής 32 bit χρειάζεται το pdfium32.dll· μια 64 bit χρειάζεται το pdfium64.dll. Οι παραλλαγές με τη μηχανή JavaScript V8 προσθέτουν το επίθημα v8 και ζυγίζουν περίπου 23-27 MB έναντι των 5-6 MB των τυπικών εκδόσεων. Για ένα πρόγραμμα σύγκρισης που απενεργοποιεί τη συμπλήρωση φορμών (Pdf.FormFill := False), η τυπική έκδοση χωρίς V8 αρκεί και κρατά τη διανομή μικρότερη
Τοποθετήστε το DLL στον ίδιο κατάλογο με το εκτελέσιμο ή σε οποιονδήποτε κατάλογο βρίσκεται στο PATH του συστήματος. Το στοιχείο το φορτώνει κατ' απαίτηση όταν ενεργοποιείται το πρώτο TPdf, οπότε ένα DLL που λείπει αναδεικνύεται εκείνη τη στιγμή και όχι στην εκκίνηση της εφαρμογής. Αν διανέμετε πρόγραμμα εγκατάστασης, ο πιο αξιόπιστος τρόπος είναι να αντιγράφετε το DLL στον φάκελο της εφαρμογής κατά την εγκατάσταση αντί να βασίζεστε σε κατάλογο συστήματος που κάποιος διαχειριστής μπορεί αργότερα να καθαρίσει
Οι εκδόσεις V8 είναι κυρίως χρήσιμες όταν χρειάζεται να αλληλεπιδράσετε με ενέργειες JavaScript του PDF, για παράδειγμα για να πυροδοτήσετε πεδία υπολογισμού ή χειριστές υποβολής. Ένα παθητικό πρόγραμμα σύγκρισης δεν έχει λόγο να τρέχει JavaScript· ορίζοντας Pdf.FormFill := False πριν από το Active := True παρακάμπτετε εντελώς το περιβάλλον συμπλήρωσης φορμών, κάτι που σημαίνει επίσης ότι δεν αρχικοποιείται καμία μηχανή JS ακόμη κι αν χρησιμοποιείται η τυπική έκδοση. Αυτή είναι η σωστή προεπιλογή για ένα πρόγραμμα προβολής μόνο για ανάγνωση, ανεξάρτητα από την παραλλαγή DLL που διανέμετε
Για περισσότερες λεπτομέρειες σχετικά με το PDFium Component και το πλήρες API του, επισκεφθείτε τη σελίδα προϊόντος Delphi PDFium Component