Η Delphi και η Lazarus μεταγλωττίζουν την ίδια Object Pascal, και αυτή η επιφανειακή ομοιότητα είναι ακριβώς αυτό που κάνει τη μεταφορά ενός viewer μεταξύ τους παραπλανητική. Οι δύο αλυσίδες εργαλείων αποκλίνουν σε τρία σημεία που έχουν σημασία για την εργασία με PDF: ο εγγενής τύπος string είναι UTF-16 στη Delphi και UTF-8 σε μια εφαρμογή LCL· τα VCL και LCL είναι διαφορετικά οπτικά πλαίσια με δικά τους controls, διαλόγους και μορφές streaming φορμών· και ένα δυαδικό αρχείο Delphi στοχεύει τα Windows ενώ ένα δυαδικό αρχείο FPC μπορεί να προορίζεται για Linux ή macOS. Καμία από αυτές τις διαφορές δεν εμφανίζεται κατά τη μεταγλώττιση. Ένας viewer χτισμένος πάνω στο PDFium Component, το οποίο διανέμει εκδόσεις VCL και LCL από ένα ενιαίο δέντρο πηγαίου κώδικα, θα μεταγλωττιστεί καθαρά υπό τη Lazarus μετά από μερικές αντικαταστάσεις ονομάτων μονάδων και λίγα μπλοκ {$IFDEF FPC}. Οι αστοχίες έρχονται αργότερα, όταν πραγματικά δεδομένα και μια πραγματική ανάπτυξη αποκαλύπτουν τις παραδοχές που η έκδοση Delphi έκανε σιωπηλά
Τέσσερις από αυτές τις παραδοχές ευθύνονται για το μεγαλύτερο μέρος του χαμένου χρόνου: η κωδικοποίηση κειμένου στο όριο του UI, ο πειρασμός να διατηρούνται δύο αντίγραφα της φόρμας, ο τρόπος με τον οποίο ένα εγγενές δυαδικό αρχείο μηχανής επιλύεται κατά την εκτέλεση, και η στιγμή που η μετατροπή κειμένου σε ομιλία μένει χωρίς πλατφόρμα όταν το SAPI εξαφανίζεται. Κάθε μία είναι φθηνή στη διαχείριση αν ξέρεις ότι έρχεται και ακριβή στο κυνήγι αν όχι
Ίδια Pascal, διαφορετικά string payloads
Ο εγγενής string της Delphi είναι UTF-16 από το 2009. Η Lazarus και η Free Pascal χρησιμοποιούν εξ ορισμού UTF-8 σε εφαρμογές LCL. Τα API του component που αφορούν κείμενο μιλούν UTF-16 μέσω του τύπου WString, τον οποίο η έκδοση FPC κάνει alias σε WideString, οπότε κάθε όριο όπου το κείμενο περνά ανάμεσα στο UI LCL σας και στη μηχανή PDF είναι ένα σημείο μετατροπής
Οι μετατροπές συμβαίνουν αυτόματα σε απλές αναθέσεις, και ο περισσότερος κώδικας δεν χρειάζεται καν να τις σκεφτεί. Δύο συνήθειες κρατούν τα σφάλματα κωδικοποίησης έξω. Περνάτε το κείμενο απευθείας χωρίς χειρισμό σε επίπεδο byte: κώδικας που κόβει έναν όρο αναζήτησης με byte offset λειτουργεί στη Delphi, όπου ένα Char είναι μία μονάδα UTF-16, και καταστρέφει το πολυ-byte UTF-8 στο LCL. Και δοκιμάζετε με δεδομένα εκτός ASCII από την πρώτη εκτέλεση. Ένα γερμανικό όνομα αρχείου, έναν κυριλλικό όρο αναζήτησης, ένα όνομα συγγραφέα με τόνους στα μεταδεδομένα του εγγράφου: δεδομένα δοκιμής καθαρού ASCII κρύβουν κάθε ελάττωμα κωδικοποίησης, επειδή το ASCII είναι το μόνο εύρος όπου το UTF-8 και το UTF-16 συμφωνούν byte προς χαρακτήρα. Το σφάλμα είναι πραγματικό όλη την ώρα· το ASCII απλώς το κρατά αόρατο μέχρι να ανοίξει ένας πελάτης στο Μόναχο ένα αρχείο που δεν δοκιμάσατε ποτέ
Ένα μπλοκ συνθήκης, όχι fork ανά IDE
Μετά τα πρώτα δώδεκα IFDEF η βάση κώδικα αρχίζει να μοιάζει με δύο έργα που φορούν ένα αποθετήριο, και το fork ανά IDE φαίνεται δελεαστικό. Είναι η λάθος κίνηση. Οι πραγματικές διαφορές συμπτύσσονται σε ένα κοινό μπλοκ δηλώσεων, και ένα fork διπλασιάζει το κόστος κάθε διόρθωσης σφάλματος από εκείνο το σημείο και μετά. Κρατήστε αυτό το επίπεδο συνθήκης τόσο μικρό:
{$IFDEF FPC}
uses
LCLType, Forms, Graphics, Controls;
type
WString = WideString; // τα API κειμένου του component είναι UTF-16
TBytes = array of Byte;
{$ELSE}
uses
Winapi.Windows, Vcl.Forms, Vcl.Graphics, Vcl.Controls;
{$ENDIF}
Όλα κάτω από αυτό το μπλοκ μεταγλωττίζονται πανομοιότυπα και στα δύο IDE. Ο χειρισμός εγγράφων, η πλοήγηση σελίδων, οι κλήσεις rendering: τα TPdf και TPdfView εκθέτουν την ίδια επιφάνεια στις εκδόσεις VCL και LCL, οπότε το μεγαλύτερο μέρος του viewer δεν βλέπει ποτέ μια συνθήκη μεταγλωττιστή. Η διατήρηση αυτής της κατάστασης είναι δομική πειθαρχία και όχι έξυπνο κόλπο. Η κοινή λογική PDF ζει σε μονάδες που δεν εισάγουν διαλόγους ή panels ειδικά για κάποιο πλαίσιο. Οι λίγες πράγματι διαφορετικές περιπτώσεις, όπως οι διάλογοι εκτύπωσης και οι επιλογείς αρχείων με τις συμβάσεις της κάθε πλατφόρμας, κρύβονται πίσω από μια λεπτή διεπαφή που υλοποιείται μία φορά ανά πλαίσιο. Το μπλοκ IFDEF γίνεται το μοναδικό σημείο όπου επιτρέπεται να προσγειωθεί μελλοντική απόκλιση πλατφόρμας, αντί να διαρρέουν οδηγίες μεταγλωττιστή σε σαράντα μονάδες
Χτίστε τη φόρμα σε κώδικα, όχι σε δύο designers
Το streaming φορμών είναι εκεί όπου τα έργα δύο IDE σαπίζουν σιωπηλά. Ένα .dfm και ένα .lfm που ισχυρίζονται ότι περιγράφουν την ίδια φόρμα αποκλίνουν ιδιότητα προς ιδιότητα μέχρι οι δύο εκδόσεις να συμπεριφέρονται διαφορετικά για λόγους που κανείς δεν μπορεί να συγκρίνει, επειδή τα δύο αρχεία δεν είναι καν στην ίδια μορφή. Η κατασκευή του viewer κατά την εκτέλεση παρακάμπτει όλο το πρόβλημα. Υπάρχει μία ακολουθία constructor, με έλεγχο εκδόσεων ως συνηθισμένος κώδικας, και διαβάζεται το ίδιο και στις δύο πλατφόρμες:
procedure TViewerForm.FormCreate(Sender: TObject);
begin
Pdf := TPdf.Create(Self);
PdfView := TPdfView.Create(Self);
PdfView.Parent := Self;
PdfView.Align := alClient;
PdfView.Pdf := Pdf;
PdfView.FitMode := pfmFitWidth;
if ParamCount > 0 then
begin
Pdf.FileName := ParamStr(1);
Pdf.Active := True; // ανοίγει το έγγραφο· το PageCount είναι έγκυρο μετά από αυτό
end;
end;
Η ακριβής σειρά αυτών των αναθέσεων έχει λιγότερη σημασία από τη μία γραμμή που κάνει την πραγματική δουλειά. Το PdfView.Pdf := Pdf συνδέει το οπτικό control με το component του εγγράφου, και από εκείνο το σημείο η πλοήγηση σελίδων μέσω του PageNumber και η συμπεριφορά προσαρμογής μέσω του FitMode ανταποκρίνονται πανομοιότυπα υπό VCL και LCL. Μια ιδιορρυθμία μεταξύ πλαισίων αξίζει να τη γνωρίζετε πριν σας την αναφέρει ένας χρήστης ως σφάλμα: η χειροκίνητη ανάθεση του Zoom επαναφέρει το FitMode σε pfmNone και στα δύο πλαίσια. Οπότε αν η γραμμή εργαλείων σας αντιμετωπίζει το "fit width" ως μόνιμη προτίμηση, πρέπει να επαναναθέσετε τη λειτουργία προσαρμογής μετά από κάθε προγραμματιστικό zoom, αλλιώς η προτίμηση σταματά σιωπηλά να κολλάει την πρώτη φορά που ο κώδικας αγγίζει το επίπεδο zoom
Το δυαδικό αρχείο για το οποίο το IDE δεν σας προειδοποίησε ποτέ
Το component περιτυλίγει τη μηχανή PDFium, η οποία διανέμεται ως εγγενές δυαδικό αρχείο πλατφόρμας, και αυτό το δυαδικό αρχείο είναι η πηγή σχεδόν κάθε αναφοράς τύπου "λειτουργεί στο IDE, αποτυγχάνει από την εγκατεστημένη συντόμευση". Τρεις κανόνες ευθύνονται για τις περισσότερες από αυτές. Το bitness πρέπει να ταιριάζει ακριβώς. Ένα εκτελέσιμο 32-bit δεν μπορεί να φορτώσει μια βιβλιοθήκη pdfium 64-bit, και το μήνυμα που επιστρέφει το λειτουργικό σύστημα ("module not found" σε ορισμένες εκδόσεις Windows) παραπλανά ενεργά, επειδή το αρχείο βρίσκεται ακριβώς εκεί, δίπλα στο εκτελέσιμο. Επιλύστε τη διαδρομή της βιβλιοθήκης σχετικά με το εκτελέσιμο, ποτέ με τον φάκελο εργασίας· μια εκκίνηση από IDE και μια εκκίνηση από shell διαφέρουν ακριβώς σε αυτό το σημείο, γι' αυτό και το σφάλμα κρύβεται κατά την ανάπτυξη. Και εντοπίστε μια αποτυχημένη φόρτωση πριν ανοίξει το πρώτο έγγραφο, μετά αναφέρετέ τη με την αναμενόμενη διαδρομή και αρχιτεκτονική ξεκάθαρα γραμμένες. Ένα ticket υποστήριξης που γράφει "λείπει το δυαδικό αρχείο PDFium 64-bit στο <path>" κλείνει σε λίγα λεπτά. Ένα που γράφει "ο viewer κρασάρει στην εκκίνηση" μετατρέπεται σε μια εβδομάδα πηγαινέλα
Κάντε versioning στο δυαδικό αρχείο της μηχανής μαζί με το εκτελέσιμο, όσο είστε σε αυτό. Το PDFium κινείται γρήγορα, και ένα πρόγραμμα εγκατάστασης που ενημερώνει την εφαρμογή αλλά αφήνει μια παλιωμένη βιβλιοθήκη στον δίσκο παράγει κρασαρίσματα που κανείς στο γραφείο σας δεν μπορεί να αναπαράγει, για τον απλό λόγο ότι κάθε μηχάνημα στο γραφείο σας τυχαίνει να έχει το σωστό ζευγάρι. Αντιμετωπίστε τη βιβλιοθήκη ως μέρος του build artifact, με το ίδιο πρόγραμμα εγκατάστασης, την ίδια σφραγίδα έκδοσης και το ίδιο μονοπάτι rollback με το εκτελέσιμο που τη φορτώνει
Καταχώρηση components στο IDE της Lazarus
Η κατασκευή κατά την εκτέλεση δεν χρειάζεται καμία καταχώρηση design-time, κάτι που είναι η πιο καθαρή διαμόρφωση για έναν viewer που χτίζει το δικό του UI σε κώδικα. Όταν θέλετε πράγματι τα components στην παλέτα της Lazarus για εργασία design-time, εγκαταστήστε το πακέτο και αφήστε την αποκλειστική μονάδα καταχώρησής του, PDFiumLazReg στο Lib/FPC/PDFiumLaz.lpk, να το χειριστεί. Αυτή η μονάδα είναι σημειωμένη ως design-time επίτηδες: αναφέρεται σε διεπαφές property-editor του IDE που δεν πρέπει ποτέ να συνδεθούν στο εκτελέσιμο που διανέμετε
Κάντε λάθος σε αυτό και το σύμπτωμα είναι μια εφαρμογή που ανεξήγητα εξαρτάται από πακέτα IDE, κάτι που εμφανίζεται ως αποτυχία ανάπτυξης στο πρώτο μηχάνημα πελάτη που δεν είχε ποτέ εγκατεστημένη τη Lazarus
Ομιλία και αναγνώστες οθόνης εκτός Windows
Η μετατροπή κειμένου σε ομιλία είναι το ένα χαρακτηριστικό όπου η ιστορία της διαλειτουργικότητας πλατφορμών σπάει, και σπάει στο λειτουργικό σύστημα, όχι στο component. Το SAPI, το συνηθισμένο TTS backend στα Windows, υπάρχει μόνο σε Windows. Ένα build της Lazarus που εξακολουθεί να στοχεύει Windows διατηρεί πλήρη έξοδο SAPI και την ίδια συμπεριφορά συμβατή με NVDA που είχε το πρωτότυπο Delphi, οπότε μια μεταφορά Windows-προς-Windows δεν χάνει τίποτα εδώ, και ένας χρήστης NVDA δεν μπορεί να ξεχωρίσει τα δύο builds
Ένας στόχος Linux ή macOS είναι διαφορετική υπόθεση. Δεν υπάρχει SAPI για κλήση, οπότε η έξοδος ήχου πρέπει να επανασυνδεθεί σε μια εγγενή υπηρεσία ομιλίας ενώ τα API ανάγνωσης από πάνω της παραμένουν στη θέση τους. Αυτός ο διαχωρισμός είναι το επιχείρημα για να βάλετε την ομιλία πίσω από μια διεπαφή από το πρώτο commit: η ανάλυση σειράς ανάγνωσης και ο δρομέας παρακολούθησης λέξεων είναι ανεξάρτητα πλατφόρμας και περνούν αναλλοίωτα, και μόνο το λεπτό επίπεδο που πράγματι παράγει ήχο πρέπει να αλλάξει ανά πλατφόρμα. Το άρθρο για τον προσβάσιμο reader καλύπτει αυτόν τον μηχανισμό ανάγνωσης σε βάθος
Μια λίστα ελέγχου ισοτιμίας πριν δηλώσετε τη μεταφορά ολοκληρωμένη
Το παρακάτω πέρασμα έχει εντοπίσει πραγματικές οπισθοδρομήσεις, καταγεγραμμένες χονδρικά με τη σειρά που τείνουν να εμφανίζονται οι αστοχίες. Ανοίξτε ένα έγγραφο του οποίου η διαδρομή περιέχει χαρακτήρες εκτός ASCII. Αναζητήστε έναν όρο με χαρακτήρες εκτός ASCII και επιβεβαιώστε ότι τα ευρήματα επισημαίνονται εκεί που πρέπει. Δοκιμάστε κύλιση με τη ρόδα του ποντικιού, επιλογή με σύρσιμο και πλοήγηση σελίδων με πληκτρολόγιο σε κάθε σύνολο widget που διανέμετε, επειδή ο χειρισμός εστίασης και η συμπεριφορά της ρόδας είναι οι πιο εξαρτημένες από το σύνολο widget γωνίες του LCL. Ελέγξτε το rendering στο 100%, 150% και 200% κλιμάκωσης οθόνης. Τέλος, τρέξτε το εγκατεστημένο build, όχι το build του IDE, σε ένα μηχάνημα που δεν είχε ποτέ το IDE πάνω του, επειδή αυτή είναι η μόνη δοκιμή που εξετάζει έντιμα την επίλυση δυαδικών αρχείων. Όλα τα υπόλοιπα μπορούν να περάσουν ενώ αυτό το ένα αποτυγχάνει σιωπηλά
Η απόδοση rendering περνά αναλλοίωτη μεταξύ των δύο εκδόσεων, οπότε η προσέγγιση caching από το άρθρο για την cache rendering και την απόδοση zoom ισχύει για τον viewer LCL ακριβώς όπως γράφτηκε για τον VCL
Τίποτα από αυτά δεν κάνει την έκδοση LCL κατώτερη. Ο πυρήνας της επιφάνειας είναι πανομοιότυπος και στις δύο πλευρές: τα TPdf, TPdfView, το rendering, οι φόρμες, η εξαγωγή κειμένου και τα API προσβασιμότητας συμπεριφέρονται το ίδιο ανεξάρτητα από το ποιο IDE τα μεταγλώττισε. Κάθε διαφορά που αξίζει να παρακολουθείτε είναι δεμένη με την πλατφόρμα και όχι με την έκδοση. Η ομιλία SAPI είναι αποκλειστικά Windows, οι διάλογοι ακολουθούν τις συμβάσεις κάθε πλαισίου, και το δυαδικό αρχείο πρέπει να ταιριάζει με την αρχιτεκτονική στην οποία φορτώνεται. Φτιάξτε σωστά τα όρια κωδικοποίησης, τη φόρμα κατά την εκτέλεση και την επίλυση δυαδικών αρχείων, και το υπόλοιπο της μεταφοράς είναι η μηχανική εργασία που ο μεταγλωττιστής έχει ήδη χειριστεί για εσάς
Οι εκδόσεις VCL και LCL που περιγράφονται εδώ διανέμονται μαζί ως PDFium Component, με πηγαίο κώδικα και πανομοιότυπα δημόσια API για Delphi, C++Builder και Lazarus/FPC