Το PDFium Component επιτρέπει σε μια εφαρμογή Delphi να αποφασίζει ποια byte γραμματοσειράς χρησιμοποιούνται όταν ένα PDF αναφέρεται σε γραμματοσειρά που δεν ενσωματώνει. Η ConfigureSystemFontProvider εγκαθιστά μια υλοποίηση IPdfSystemFontProvider που λαμβάνει κάθε αίτημα αντιστοίχισης γραμματοσειράς που κάνει το PDFium, πλήρες με όνομα γραμματοσειράς, βάρος, σημαία πλάγιας γραφής, σύνολο χαρακτήρων και οικογένεια βήματος, και απαντά με τα byte TrueType, TrueType Collection ή OpenType προς χρήση
Αυτό υπάρχει επειδή οι μη ενσωματωμένες γραμματοσειρές είναι μια λοταρία απόδοσης. Ένα PDF που ονομάζει την Arial και δεν ενσωματώνει τίποτα αποδίδεται με Arial σε έναν σταθμό εργασίας, με ένα μετρικά συμβατό υποκατάστατο σε διακομιστή Linux, και με ό,τι βρίσκει ο αντιστοιχιστής του υπολογιστή σε μια κλειδωμένη εικόνα container. Το ίδιο τιμολόγιο φαίνεται διαφορετικό σε καθένα, οι αλλαγές γραμμής μετακινούνται, και ένας πελάτης λαμβάνει ένα έγγραφο που δεν ταιριάζει με το αρχειοθετημένο αντίγραφο
Γιατί όχι απλώς εγκατάσταση των γραμματοσειρών στον διακομιστή;
Μερικές φορές αυτή είναι η απάντηση, και όταν είναι, ακολουθήστε την. Αλλά αποτυγχάνει σε τρεις συνηθισμένες περιπτώσεις. Η αδειοδότηση μπορεί να απαγορεύει την εγκατάσταση μιας γραμματοσειράς σε διακομιστή για αυτοματοποιημένη απόδοση. Οι εικόνες container ξαναχτίζονται συχνά και μια γραμματοσειρά εγκατεστημένη χειροκίνητα εξαφανίζεται με την επόμενη ανάπτυξη. Και ρυθμιζόμενες ροές εργασίας χρειάζονται η στοίβα απόδοσης να είναι αναπαράξιμη από τεχνουργήματα υπό έλεγχο εκδόσεων, κάτι που μια εγκατάσταση γραμματοσειράς σε επίπεδο μηχανής δεν είναι
Ένας πάροχος αντιμετωπίζει και τις τρεις μεταφέροντας την απόφαση στην εφαρμογή σας. Οι γραμματοσειρές διανέμονται ως πόροι που ελέγχετε, η πολιτική αντιστοίχισης είναι κώδικας που μπορείτε να αναθεωρήσετε, και το ίδιο δυαδικό αποδίδει πανομοιότυπα παντού επειδή τίποτα δεν εξαρτάται από το τι τυχαίνει να είναι εγκατεστημένο
Εγκατάσταση ενός παρόχου
Η ρύθμιση παραμέτρων πρέπει να συμβεί πριν φορτωθεί η βιβλιοθήκη. Το PDFium δέχεται μια δομή πληροφοριών γραμματοσειράς συστήματος κατά την αρχικοποίηση και κρατά λαβές που διανέμει στη συνέχεια, οπότε η αλλαγή ενός παρόχου ενώ έγγραφα είναι ανοιχτά θα ακύρωνε λαβές γραμματοσειράς που το PDFium ακόμη κρατά· το εξάρτημα το απορρίπτει εντελώς αντί να αφήσει αυτό να διαφθείρει μια απόδοση:
uses
PDFium;
type
TAppFontProvider = class(TInterfacedObject, IPdfSystemFontProvider)
public
function ResolveFont(const Request: TPdfSystemFontRequest;
out Font: TPdfSystemFontData): Boolean;
end;
function TAppFontProvider.ResolveFont(const Request: TPdfSystemFontRequest;
out Font: TPdfSystemFontData): Boolean;
var
Path: string;
begin
// Ντετερμινιστική αντιστοίχιση: το όνομα γραμματοσειράς συν το
// βάρος και η πλάγια γραφή αποφασίζουν ποιο αρχείο διανέμουμε
// για αυτό το αίτημα
Path := MapFaceToBundledFile(Request.FaceName, Request.Weight,
Request.Italic, Request.Charset);
Result := Path <> '';
if not Result then
Exit;
Font.FaceName := Request.FaceName;
Font.FontData := LoadFileBytes(Path); // πλήρη byte sfnt ή TTC
Font.Charset := Request.Charset;
Font.TTCIndex := 0; // ευρετήριο μέσα σε μια συλλογή
end;
var
Policy: TPdfSystemFontPolicy;
begin
Policy := TPdfSystemFontPolicy.Default;
Policy.AllowDefaultFallback := False; // ο υπολογιστής αποφασίζει τα πάντα
Policy.AllowFaceSubstitution := False; // απόρριψη διαφορετικού ονόματος γραμματοσειράς
Policy.MaxFontBytes := 32 * 1024 * 1024;
Policy.MaxCacheEntries := 64;
ConfigureSystemFontProvider(TAppFontProvider.Create, Policy);
// Μόνο τώρα φορτώστε τη βιβλιοθήκη και ανοίξτε έγγραφα
end;
Η αποδόμηση εκτελείται με αντίστροφη σειρά: ο πάροχος αποσυνδέεται πρώτα από το PDFium, έπειτα η βιβλιοθήκη αποφορτώνεται. Η παράλειψη της αποσύνδεσης αφήνει εγγενείς λαβές γραμματοσειράς να δείχνουν σε αντικείμενα Pascal που πρόκειται να απελευθερωθούν, που είναι η κλασική παραβίαση πρόσβασης κατά το τερματισμό σε κώδικα που αναμειγνύει διεπαφές με μέτρηση αναφορών με μια βιβλιοθήκη C
Τι πραγματικά αποφασίζουν οι σημαίες πολιτικής
Το AllowDefaultFallback είναι ο διακόπτης ανάμεσα σε δύο λειτουργίες. Με αυτό απενεργοποιημένο, ένα αίτημα που ο πάροχος απορρίπτει απλώς αποτυγχάνει, που είναι αυτό που θέλετε ενώ αποδεικνύετε ότι κάθε γραμματοσειρά σε ένα σύνολο εγγράφων είναι καταγεγραμμένη: οποιοδήποτε κενό γίνεται ορατό αμέσως αντί να καλύπτεται. Με αυτό ενεργοποιημένο, ανεπίλυτα αιτήματα ανατίθενται στον αντιστοιχιστή που επιστρέφει η FPDF_GetDefaultSystemFontInfo, ενώ ο έξω κόσμος εξακολουθεί να βλέπει έναν ενιαίο περιτύλιγμα λαβής, με όνομα γραμματοσειράς, σύνολο χαρακτήρων, δεδομένα πίνακα και διαγραφή γραμματοσειράς δρομολογημένα σωστά κατά προέλευση
Το AllowFaceSubstitution διέπει αν ένας πάροχος μπορεί να απαντήσει με διαφορετικό όνομα γραμματοσειράς από το ζητούμενο. Η απενεργοποίησή του κάνει την αντικατάσταση ρητή απόφαση αντί για ατύχημα, κάτι που έχει σημασία όταν ένα έγγραφο ονομάζει μια γραμματοσειρά της οποίας οι μετρικές διαφέρουν αρκετά ώστε να αλλάξουν τη σελιδοποίηση
Το εξάρτημα επικυρώνει κάθε απάντηση παρόχου πριν φτάσει στο PDFium: κενά δεδομένα απορρίπτονται, υπερμεγέθεις γραμματοσειρές απορρίπτονται έναντι του MaxFontBytes, το ευρετήριο TTC ελέγχεται, και μεμονωμένοι πίνακες sfnt σερβίρονται από τον κατάλογο γραμματοσειράς όταν το PDFium ζητά έναν πίνακα αντί για ολόκληρο το αρχείο. Εκείνη η τελευταία δυνατότητα σημαίνει ότι ένας πάροχος μπορεί να παραδώσει ένα πλήρες αρχείο γραμματοσειράς και να αφήσει το εξάρτημα να απαντά ερωτήματα επιπέδου πίνακα, αντί να εκθέτει ακατέργαστα αντικείμενα Pascal σε ολόκληρο το ABI της C
Προσωρινή αποθήκευση χωρίς εκκρεμή δεδομένα γραμματοσειράς
Τα αιτήματα αντιστοίχισης γραμματοσειράς επαναλαμβάνονται συνεχώς κατά την απόδοση, οπότε οι απαντήσεις αποθηκεύονται προσωρινά με κλειδί που καλύπτει κάθε παράμετρο επιλογής γραμματοσειράς, εξαλείφονται με φραγμένη σειρά λιγότερο πρόσφατα χρησιμοποιημένων. Η λεπτότητα είναι η διάρκεια ζωής: το PDFium μπορεί ακόμη να διαβάζει τα byte μιας γραμματοσειράς της οποίας η καταχώριση προσωρινής μνήμης μόλις εξαλείφθηκε
Η προσωρινή μνήμη αποθηκεύει δυναμικούς πίνακες με μέτρηση αναφορών και κάθε εγγενής λαβή κρατά το δικό της στιγμιότυπο, οπότε η εξάλειψη ρίχνει μια αναφορά αντί να απελευθερώνει μνήμη σε χρήση. Η επανάκληση διαγραφής απελευθερώνει τη λαβή και διατηρεί μια ενεργή μέτρηση. Πρακτικά, αυτό σημαίνει ότι το MaxCacheEntries μπορεί να ρυθμιστεί για μνήμη χωρίς κανέναν κίνδυνο τραβήγματος δεδομένων κάτω από μια απόδοση σε εξέλιξη
Καλείται ο πάροχος στο δικό μου νήμα;
Όχι, όχι απαραίτητα. Το PDFium μπορεί να καλέσει τον αντιστοιχιστή από τα δικά του νήματα εργάτη, οπότε μια υλοποίηση πρέπει να είναι ασφαλής για νήματα. Κοινόχρηστοι μετρητές, η προσωρινή μνήμη και η παρακολούθηση ρύθμισης προστατεύονται καθένας μέσα στο εξάρτημα από τη δική τους κρίσιμη περιοχή, αλλά ο κώδικας μέσα στην ResolveFont είναι δικός σας να τον κάνετε ασφαλή
Η ασφαλέστερη μορφή είναι ένας πάροχος που δεν αγγίζει καμία μεταβλητή κοινόχρηστη κατάσταση: διαβάστε από έναν πίνακα χτισμένο κατά την εκκίνηση, φορτώστε byte από ένα αρχείο ή έναν πόρο, επιστρέψτε. Αν μια αναζήτηση χρειάζεται μια δική σας κοινόχρηστη προσωρινή μνήμη, προστατέψτε την. Και κρατήστε τις εξαιρέσεις μέσα στην υλοποίησή σας, αφού μια εξαίρεση Pascal δεν πρέπει ποτέ να ξετυλιχθεί μέσα από τη στοίβα του PDFium· το εξάρτημα πιάνει στο όριο ABI της C και μετατρέπει σε αποτυχία ή προαιρετική προεπιλεγμένη επιστροφή, αλλά η στήριξη σε αυτό ως κανονική ροή ελέγχου κοστίζει απόδοση και κρύβει σφάλματα. Οι κανόνες νηματοποίησης για το υπόλοιπο εξάρτημα ακολουθούν τις ίδιες αρχές με εκείνες στην πειθαρχία κλειδώματος απόδοσης
Απόδειξη της αντιστοίχισης σε παραγωγή
Τα στατιστικά μετατρέπουν την αντικατάσταση γραμματοσειρών από εικασία σε κάτι που μπορείτε να ισχυριστείτε. Η GetSystemFontProviderStatistics αναφέρει αν ένας πάροχος είναι ρυθμισμένος και εγκατεστημένος, πόσα αιτήματα αντιστοίχισης έγιναν, και πώς ικανοποιήθηκαν, χωρισμένα σε επιτυχίες προσωρινής μνήμης, επιτυχίες παρόχου και επιτυχίες προεπιλεγμένης επιστροφής, μαζί με απορριφθείσες απαντήσεις, αποτυχημένα αιτήματα, ζωντανές λαβές και αποθηκευμένες γραμματοσειρές:
var
Stats: TPdfSystemFontStatistics;
begin
Stats := GetSystemFontProviderStatistics;
Writeln(Format('requests=%d cache=%d provider=%d fallback=%d',
[Stats.MapRequests, Stats.CacheHits, Stats.ProviderHits,
Stats.DefaultFallbackHits]));
Writeln(Format('rejected=%d failed=%d handles=%d cached=%d',
[Stats.RejectedProviderResponses, Stats.FailedRequests,
Stats.ActiveHandles, Stats.CachedFonts]));
// Σε μια εκτέλεση συμμόρφωσης με απενεργοποιημένη την προεπιλεγμένη
// επιστροφή, οποιαδήποτε επιτυχία επιστροφής ή αποτυχημένο αίτημα
// σημαίνει ότι ένα έγγραφο αναφέρθηκε σε γραμματοσειρά που δεν διανέμουμε
if (Stats.DefaultFallbackHits > 0) or (Stats.FailedRequests > 0) then
raise Exception.Create('unmapped font encountered - update the font set');
end;
Μια αυξανόμενη μέτρηση RejectedProviderResponses είναι το σήμα ότι ένας πάροχος απαντά με δεδομένα που η πολιτική αρνείται, συνήθως ένα υπερμέγεθες αρχείο ή μια αντικατασταθείσα γραμματοσειρά, και αξίζει να ειδοποιείστε γι' αυτό επειδή εκείνα τα αιτήματα υποβαθμίζονται σιωπηλά σε προεπιλεγμένη επιστροφή ή αποτυχία. Για διάγνωση ποιες γραμματοσειρές πραγματικά χρειάζεται ένα έγγραφο πριν χτίσετε τον πίνακα αντιστοίχισης, η διαδρομή επιθεώρησης στην ανάλυση ιδιοτήτων γραμματοσειράς PDF παραθέτει ενσωματωμένες και μη ενσωματωμένες γραμματοσειρές ανά έγγραφο
Ο εφοδιασμός γραμματοσειρών, η απόδοση και η εξαγωγή κειμένου μοιράζονται το ίδιο στιγμιότυπο βιβλιοθήκης σε Delphi, C++Builder και Lazarus· λεπτομέρειες ανάπτυξης περιγράφονται στη σελίδα του PDFium Component για Delphi