Το PDFlibPas μετατρέπει τα enhanced metafiles σε πραγματικό περιεχόμενο σελίδας PDF εγγραφή προς εγγραφή, αντί να τα ραστεροποιεί, και αυτό κρατά ένα εισαγμένο διάγραμμα ή σχέδιο CAD καθαρό σε κάθε μεγέθυνση. Ο μετατροπέας αυτός είναι περίπου 6500 γραμμές και είχε γραφτεί πάνω στο VCL, οπότε όταν η βιβλιοθήκη απέκτησε στόχο Free Pascal ταξινομήθηκε ως μη μεταφέρσιμος και αντικαταστάθηκε με stub. Η ταξινόμηση ήταν λάθος, και ο τρόπος που ήταν λάθος είναι ένα χρήσιμο μάθημα για το πώς ελέγχετε μια εξάρτηση πριν αποφασίσετε να ξαναγράψετε γύρω της
Η πραγματική επιφάνεια VCL αυτών των 6500 γραμμών αποδείχθηκε μικρή: μια κλάση bitmap που χρησιμοποιούνταν για το pixel format της, την αποθήκευση σε stream, το handle, τον canvas και τις scanlines· μια κλάση metafile που χρησιμοποιούνταν για το πλάτος, το ύψος και το handle της· και ο τύπος χρώματος με δύο σταθερές. Καθένα από αυτά παρείχε ήδη η δική της μονάδα γραφικών της βιβλιοθήκης, που υπάρχει ακριβώς ώστε το build χωρίς VCL να έχει αντίστοιχα. Ο μετατροπέας δεν μπλόκαρε καθόλου στο VCL. Μπλόκαρε στη μονάδα Windows του Free Pascal
Διαχωρίστε στον άξονα από τον οποίο ο κώδικας εξαρτάται πραγματικά
Η αλλαγή δεν ήταν επανυλοποίηση. Ήταν μία συνθήκη: από «μεταγλώττισε το stub όταν χτίζεις χωρίς το VCL» σε «μεταγλώττισε το stub όταν δεν χτίζεις για Windows». Αυτός είναι ο σωστός άξονας, και η διατύπωση του λόγου κάνει τη διαφορά προφανή. Ένα enhanced metafile είναι ένα container των Windows. Ο μετατροπέας είναι από άκρη σε άκρη parser για εγγραφές GDI των Windows. Το αν η εφαρμογή-φορέας χρησιμοποιεί το VCL, άλλο σύστημα widget ή καθόλου widget δεν έχει καμία σχέση με το αν οι εγγραφές αυτές μπορούν να ερμηνευτούν· το αν ο στόχος είναι Windows έχει κάθε σχέση με αυτό
Οι συνέπειες της σωστής επιλογής άξονα έρχονται δωρεάν. Τα builds C++Builder, που αναιρούν το σύμβολο πλατφόρμας Windows σε αυτή τη βιβλιοθήκη, κρατούν το stub που πετάει εξαίρεση και συμπεριφέρονται ακριβώς όπως πριν. Το macOS κρατά το stub, σωστά, γιατί εκεί δεν υπάρχουν εγγραφές GDI προς ανάλυση. Τα builds Delphi VCL δεν επηρεάζονται. Και ένα build Windows με σύστημα widget χωρίς VCL αποκτά διανυσματική εισαγωγή EMF ως παρενέργεια, που δεν χρειάστηκε να υλοποιήσει κανείς. Μια συνθήκη ευθυγραμμισμένη με την πραγματική εξάρτηση μετατρέπει τη δουλειά πλατφόρμας σε αλλαγή μιας γραμμής· μια συνθήκη ευθυγραμμισμένη με λάθος εξάρτηση τη μετατρέπει σε επανεγγραφή που δεν μπαίνει ποτέ στο πρόγραμμα
Το κενό του Free Pascal ήταν δηλώσεις, όχι λογική
Αυτό που έλειπε πραγματικά ήταν οι δηλώσεις Win32 που παρέχει η μονάδα Windows του Delphi και δεν παρέχει αυτή του Free Pascal. Η συλλογή τους σε μια ενιαία μονάδα συμβατότητας αντί για συνθήκες σκορπισμένες στον μετατροπέα κράτησε τον parser αναγνώσιμο. Η λίστα είναι διδακτική γιατί δείχνει πόσο άνιση είναι η κάλυψη των headers ανάμεσα στα δύο RTL: 113 σταθερές τύπων εγγραφών metafile, δύο σημαίες extended text-output, τρεις σταθερές κατάστασης gradient fill, ένας τύπος δείκτη πίνακα handles, ψευδώνυμα για τις εγγραφές gradient vertex και primitive, και τρεις τύποι εγγραφών που το Free Pascal δεν δηλώνει καθόλου, που καλύπτουν alpha blending, transparent blitting και λειτουργία διαχείρισης χρώματος
Κανένα από αυτά δεν είναι ενδιαφέρον μεμονωμένα. Όλα πρέπει να είναι σωστά πριν μεταγλωττιστεί ο parser, και μια μονάδα συμβατότητας είναι το φυσικό τους σπίτι γιατί μπορεί να συγκριθεί με την τεκμηρίωση των headers ως σύνολο
Αυτή που σιωπηλά ζωγραφίζει τη λάθος εικόνα
Δύο από αυτές τις δηλώσεις δεν απλώς λείπουν, είναι παρούσες και λάθος για τον σκοπό αυτό, και αυτό είναι το κομμάτι που αξίζει να θυμάστε ακόμα κι αν δεν αγγίξετε ποτέ metafile
Το Free Pascal δηλώνει την εγγραφή δημιουργίας brush με ενσωματωμένη τη δομή brush χρόνου εκτέλεσης, και την εγγραφή extended pen με ενσωματωμένη τη δομή pen χρόνου εκτέλεσης. Και οι δύο δομές χρόνου εκτέλεσης δηλώνουν το μέλος hatch τους ως ακέραιο μεγέθους δείκτη, γιατί σε μια ζωντανή κλήση GDI το μέλος αυτό μπορεί να κουβαλά handle. Ένα metafile όμως αποθηκεύει πάντα τη μορφή 32-bit, γιατί η διάταξη της εγγραφής είναι μέρος της σειριοποιημένης μορφής αρχείου και δεν αλλάζει με το bitness της διεργασίας
Σε builds 32-bit οι δύο συμφωνούν και δεν συμβαίνει τίποτα. Σε Win64 το μέλος μεγέθους δείκτη είναι οκτώ bytes εκεί που το αρχείο έχει τέσσερα, οπότε κάθε πεδίο μετά το μέλος hatch διαβάζεται από λάθος offset. Δεν υπάρχει εξαίρεση, ούτε σφάλμα ανάλυσης, ούτε προειδοποίηση. Το metafile απλώς αποδίδεται λάθος: χρώματα από λάθος bytes, πλάτη πενών από λάθος bytes, και μια εικόνα που μοιάζει με σφάλμα απόδοσης και όχι με σφάλμα διάταξης δομής. Το Delphi παρέχει ρητά παραλλαγές 32-bit και των δύο δομών ακριβώς γι' αυτόν τον λόγο, και η μονάδα συμβατότητας τις ξαναδηλώνει με τον ίδιο τρόπο
// Λάθος σε Win64: το Hatch είναι μεγέθους δείκτη, το αρχείο
// αποθηκεύει 32 bits, και κάθε επόμενο πεδίο μετατοπίζεται κατά
// τέσσερα bytes χωρίς κανένα σφάλμα
type
TLogBrushRuntime = record
lbStyle: UINT;
lbColor: COLORREF;
lbHatch: ULONG_PTR; // 8 bytes σε διεργασία 64-bit
end;
// Σωστό: η σειριοποιημένη διάταξη, σταθερό πλάτος ανεξαρτήτως bitness
type
TLogBrush32 = record
lbStyle: UINT;
lbColor: COLORREF;
lbHatch: DWORD; // πάντα 4 bytes, όπως αποθηκεύεται στο metafile
end;
Ο γενικός κανόνας: κάθε δομή που εμφανίζεται τόσο ως όρισμα API χρόνου εκτέλεσης όσο και ως διάταξη σειριοποιημένων πεδίων χρειάζεται δύο δηλώσεις, και η σειριοποιημένη πρέπει να χρησιμοποιεί τύπους σταθερού πλάτους παντού. Τα μέλη μεγέθους δείκτη σε μορφή αρχείου είναι πάντα ένα σφάλμα που περιμένει ένα build 64-bit
Οι διαφορές υπογραφών ανήκουν σε wrapper, όχι σε κάθε σημείο κλήσης
Οι υπόλοιπες διαφορές ήταν συνηθισμένες αναντιστοιχίες υπογραφών, και ο τρόπος απορρόφησής τους είναι ένα wrapper προώθησης αντί για συνθήκη σε καθένα από τα σημεία κλήσης. Η συνάρτηση συνδυασμού μετασχηματισμών παίρνει δείκτες υπό Free Pascal εκεί που το Delphi παίρνει παραμέτρους αναφοράς, οπότε το wrapper παίρνει αναφορές και περνά διευθύνσεις. Αντιγράφει επίσης και τα δύο ορίσματα πηγής σε τοπικές μεταβλητές πρώτα, γιατί ο μετατροπέας έχει σημεία κλήσης όπου ο πίνακας προορισμού είναι ταυτόχρονα μία από τις πηγές, και το πέρασμα της ίδιας διεύθυνσης δύο φορές σε συνάρτηση που γράφει καθώς διαβάζει παράγει μετασχηματισμό που είναι διακριτικά λάθος με τρόπο που φαίνεται μόνο σε περιστραμμένο περιεχόμενο
function CombineTransformCompat(var Dest: TXForm;
const A, B: TXForm): BOOL;
var
SrcA, SrcB: TXForm;
begin
// Πρώτα αντίγραφο: οι καλούντες περνούν νόμιμα το Dest ως A ή B
SrcA := A;
SrcB := B;
{$IFDEF FPC}
Result := Windows.CombineTransform(@Dest, @SrcA, @SrcB);
{$ELSE}
Result := Windows.CombineTransform(Dest, SrcA, SrcB);
{$ENDIF}
end;
Οι τύποι rectangle και point είναι η άλλη περίπτωση. Το Free Pascal αντιμετωπίζει τις εγγραφές rectangle και point του metafile ως διαφορετικούς τύπους από τους γενικούς γραφικών, οπότε οκτώ σημεία ανάθεσης χρειάστηκαν ρητό cast ανάμεσα σε εγγραφές πανομοιότυπης διάταξης. Και οι δύο μεταγλωττιστές δέχονται τη μορφή με cast, οπότε αυτά τα σημεία δεν κουβαλούν καθόλου συνθήκη, που αξίζει λίγη ασχήμια
Τι αλλάζει αυτό για μια ανάπτυξη με Free Pascal
Η διανυσματική εισαγωγή EMF λειτουργεί σε Windows υπό Free Pascal, παράγοντας το ίδιο περιεχόμενο σελίδας με το build Delphi: διαδρομές ως διαδρομές, gradients ως περιεχόμενο pattern, κείμενο ως κείμενο. Εκτός Windows η ραστεροποιημένη διαδρομή παραμένει η απάντηση, και αυτό είναι περιορισμός της μορφής και όχι της μεταφοράς. Η κατάσταση συντεταγμένων και αποκοπής στην οποία τροφοδοτεί ο μετατροπέας περιγράφεται στο άρθρο για τον ιχνηλάτη CTM και αποκοπής του content stream, και τα διανυσματικά primitives που εκπέμπει καλύπτονται στα διανυσματικά γραφικά, shaders και gradients
Αν ελέγχετε τη δική σας codebase για την ίδια ευκαιρία, η χρήσιμη άσκηση είναι αυτή που ξεκίνησε όλα: καταγράψτε τα μέλη που πραγματικά χρησιμοποιείτε από το framework από το οποίο νομίζετε ότι εξαρτάστε. Η απάντηση είναι συχνά πολύ συντομότερη από όσο υπονοεί η λίστα εισαγωγών, και ο πραγματικός περιορισμός συνήθως βρίσκεται κάπου εντελώς αλλού. Οι διαδρομές εισαγωγής με βάση το device context περιγράφονται γενικά στο άρθρο για την προεπισκόπηση εκτύπωσης και το device context, και η κάλυψη πλατφορμών και toolchain καταγράφεται στη σελίδα προϊόντος losLab PDF Developer Library