Ένα απόν glyph σε PDF δεν είναι σφάλμα. Ο παραγωγός ζητά έναν χαρακτήρα που η επιλεγμένη γραμματοσειρά δεν μπορεί να αντιστοιχίσει, η γραμματοσειρά επιστρέφει δείκτη glyph μηδέν, και το αρχείο που βγαίνει είναι δομικά έγκυρο, ανοίγει παντού, και δείχνει ένα κενό κουτί εκεί όπου θα έπρεπε ένα όνομα ή ένα ποσό. Κανείς στην παράγουσα γραμμή δεν το μαθαίνει. Ο παραλήπτης το μαθαίνει. Το HotPDF κλείνει εκείνον τον βρόχο με την TrackUnresolvedGlyphs: ενεργοποιήστε την και η διαδρομή ζωγραφικής κειμένου καταγράφει κάθε code point του οποίου η αναζήτηση glyph επιλύεται σε δείκτη μηδέν, ενεργοποιώντας το OnUnresolvedGlyph μία φορά ανά μοναδικό εύρημα με το code point, τη γραμματοσειρά που απέτυχε, το script στο οποίο ανήκει, και μια πρόταση γραμματοσειρών που θα το κάλυπταν
Η ανίχνευση είναι το μισό της απάντησης. Το άλλο μισό είναι η SetFontFallbackChain, που καταχωρίζει μια διατεταγμένη λίστα γραμματοσειρών ανά script, ώστε οι συνηθισμένες περιπτώσεις να επιλύονται μόνες τους και μόνο γνήσια κενά να φτάνουν στον χειριστή σας. Μαζί μετατρέπουν μια κατηγορία ελαττωμάτων που αναφέρονταν από πελάτες σε έλεγχο χρόνου χτίσιμου
Γιατί ένα απόν glyph δεν πετάει τίποτα;
Επειδή το ISO 32000 δεν επιβάλλει υποχρέωση στον παραγωγό να επαληθεύσει κάλυψη, και ο δείκτης glyph μηδέν είναι ένα νόμιμο glyph. Είναι το .notdef, του οποίου το περίγραμμα επιλέγει ο σχεδιαστής της γραμματοσειράς: συνήθως ένα κενό ή κούφιο ορθογώνιο, μερικές φορές τίποτα απολύτως. Ένα viewer που το ζωγραφίζει συμπεριφέρεται σωστά. Η εξαγωγή κειμένου μπορεί ακόμη και να επιστρέψει τους σωστούς χαρακτήρες, επειδή η αντιστοίχιση /ToUnicode γράφεται από το κείμενο πηγής και όχι από τα περιγράμματα, οπότε ένας αυτοματοποιημένος έλεγχος round-trip θα περάσει ευτυχώς ένα έγγραφο του οποίου το ορατό κείμενο έχει τρύπες
Η πρακτική συνέπεια είναι ότι η κάλυψη πρέπει να ελέγχεται τη στιγμή της ζωγραφικής, όταν η βιβλιοθήκη ξέρει ακόμη ποιο code point ζητήθηκε και ποιο glyph προσέφερε πραγματικά η γραμματοσειρά. Μετά η πληροφορία έχει χαθεί
Ο ανιχνευτής πρέπει να παρακολουθεί την κατάσταση subset, όχι το device context
Εδώ πήγε στραβά η πρώτη υλοποίηση, και ο λόγος αξίζει να κατανοηθεί επειδή ισχύει για κάθε έλεγχο κάλυψης βιδωμένο σε αγωγό κειμένου. Το HotPDF έχει δύο διαδρομές κειμένου. Η μία εκπέμπει μέσω καταχωρισμένης γραμματοσειράς TrueType Unicode με χάρτη χαρακτήρων στη μνήμη χτισμένο τη στιγμή της καταχώρισης. Η άλλη είναι μια κληρονομική διαδρομή GDI που δημιουργεί φρέσκο device context και handle γραμματοσειράς ανά ροή χαρακτήρων
Η κρίση κάλυψης από τη διαδρομή GDI είναι απελπιστική. Η αντιστοίχισή της δεν είναι η αντιστοίχιση που καταλήγει στο εκπεμπόμενο content stream, και οι δύο δεν είναι συγχρονισμένες, οπότε ένας ανιχνευτής που διαβάζει αποτελέσματα GDI αναφέρει όλο το εκτυπώσιμο εύρος ASCII ως ανεπίλυτο. Η αυθεντική απάντηση ζει στην καταχωρισμένη γραμματοσειρά: τον χάρτη χαρακτήρων που αναλύει η RegisterUnicodeTTF, ερωτώμενο μέσω της GetUnicodeGlyphForCodepoint. Ο ανιχνευτής φράσσεται επομένως στην κατάσταση έτοιμου subset και όχι σε οποιαδήποτε συνθήκη GDI, και απλώς δεν τρέχει σε έγγραφα που δεν καταχώρισαν ποτέ γραμματοσειρά Unicode, που είναι σωστό αφού εκείνα τα έγγραφα είναι ούτως ή άλλως περιορισμένα στις τυπικές κωδικοποιήσεις
Μια δεύτερη παγίδα κάθεται δίπλα της. Το όνομα οικογένειας GDI μιας γραμματοσειράς και το PostScript όνομα που εξάγεται από το δυαδικό αρχείο της γραμματοσειράς στην καταχώριση είναι διαφορετικές συμβολοσειρές, και όχι με τρόπο που μπορείτε να κανονικοποιήσετε: μια οικογένεια που λέγεται Arial Unicode MS κουβαλά το PostScript όνομα ArialMT. Κάθε πύλη γραμμένη ως «είναι η τρέχουσα επιλεγμένη γραμματοσειρά αυτή που καταχωρίσαμε», συγκρινόμενη κατά όνομα, είναι νεκρός κώδικας που δεν ενεργοποιείται ποτέ. Φράξτε σε κατάσταση, ποτέ σε ονόματα γραμματοσειρών
Μην τεστάρετε ανιχνευτή glyph με emoji
Η προφανής δοκιμή είναι ένα χαμογελαστό πρόσωπο, και θα σας πείσει ότι ο ανιχνευτής είναι χαλασμένος. Τα συνηθισμένα code points emoji στα astral planes επιλύονται μέσω μιας διαδρομής σύνθεσης private-use που τους αντιστοιχίζει απευθείας δείκτη glyph, οπότε δεν φτάνουν ποτέ στον γενικό κλάδο κάλυψης. Ο ανιχνευτής συμπεριφέρεται σωστά και το test μετρά τη λάθος διαδρομή
Χρησιμοποιήστε αντ' αυτού ένα μη ανατεθειμένο code point. Το U+0378 είναι μόνιμα μη κατανεμημένο στο Unicode, οπότε καμία γραμματοσειρά δεν μπορεί νόμιμα να το αντιστοιχίσει, και ασκεί ακριβώς τον κλάδο που θέλετε να επαληθεύσετε. Αυτή η διάκριση ανάμεσα στο «η λειτουργία είναι χαλασμένη» και «το test διάλεξε είσοδο που παρακάμπτει τη λειτουργία» κοστίζει πραγματικές ώρες, και τα μη ανατεθειμένα code points είναι ο φθηνότερος τρόπος να το αποφύγετε
type
TCoverageAudit = class
private
FFindings: TStringList;
public
procedure Handle(Sender: TObject;
const Info: THPDFUnresolvedGlyphInfo);
property Findings: TStringList read FFindings;
end;
procedure TCoverageAudit.Handle(Sender: TObject;
const Info: THPDFUnresolvedGlyphInfo);
begin
// Ενεργοποιείται μία φορά ανά μοναδικό code point, όχι μία ανά εμφάνιση
FFindings.Add(Format('U+%.4X missing in %s (script %d), try: %s',
[Info.CodePoint, String(Info.FontName), Ord(Info.Script),
String(Info.SuggestedFonts)]));
end;
// Σύναξη σε μια παράγουσα εργασία
Pdf := THotPDF.Create(nil);
try
Pdf.TrackUnresolvedGlyphs := True;
Pdf.OnUnresolvedGlyph := Audit.Handle;
Pdf.RegisterUnicodeTTF('C:\Windows\Fonts\arial.ttf');
Pdf.BeginDoc;
Pdf.CurrentPage.SetFont('Arial', [], 11);
Pdf.CurrentPage.TextOut(50, 720, 0, CustomerName);
Pdf.EndDoc;
if Audit.Findings.Count > 0 then
// Αποτύχετε την εργασία αντί να διανείμετε σελίδα με κουτιά πάνω της
raise Exception.Create(Audit.Findings.Text);
finally
Pdf.Free;
end;
Οι αλυσίδες εναλλακτικών είναι ανά script, όχι ανά γραμματοσειρά
Ο λόγος που η εναλλακτική ορίζεται ανά script και όχι ανά γραμματοσειρά πηγής είναι ότι τα κενά κάλυψης ομαδοποιούνται κατά σύστημα γραφής. Σε μια γραμματοσειρά λατινικού κειμένου λείπουν τα Devanagari, Thai, Han και emoji, όλα μαζί, και η αντικατάσταση για το καθένα είναι διαφορετική γραμματοσειρά. Η δήλωση μιας αλυσίδας ανά script περιγράφει επομένως την πραγματική ανάπτυξη: μία γραμματοσειρά λατινικών για κείμενο σώματος, μία CJK, μία emoji, μία για τα πάντα
// Το THPDFFontScript καλύπτει hfsCommon, hfsLatin, hfsGreek, hfsCyrillic,
// hfsHebrew, hfsArabic, hfsIndic, hfsSoutheastAsian, hfsCJK, hfsKana,
// hfsHangul, hfsEmoji και hfsOther
Pdf.SetFontFallbackChain(hfsCJK,
['Microsoft YaHei', 'SimSun', 'Yu Gothic']);
Pdf.SetFontFallbackChain(hfsArabic, ['Segoe UI', 'Arial']);
Pdf.SetFontFallbackChain(hfsEmoji, ['Segoe UI Emoji']);
Pdf.SetFontFallbackChain(hfsOther, ['Arial Unicode MS']);
Η εναλλακτική και η ανίχνευση είναι συμπληρωματικές και όχι εναλλακτικές. Οι αλυσίδες χειρίζονται την κάλυψη που προβλέψατε· ο ανιχνευτής αναφέρει την κάλυψη που δεν προβλέψατε, που σε ένα σύστημα που επεξεργάζεται αυθαίρετα δεδομένα πελατών είναι το ενδιαφέρον μισό. Σημειώστε ότι η αντικατάσταση γραμματοσειράς αλλάζει μετρικές, οπότε μια παράγραφος που πέφτει σε εναλλακτική μπορεί να επαναρυθμιστεί· αν η διάταξη έχει σημασία, αξίζει να μελετήσετε τη συμπεριφορά κλεισίματος και subsetting της αντικατασταθείσας γραμματοσειράς στο άρθρο για το κλείσιμο υποσυνόλου γραμματοσειράς, και τα scripts που χρειάζονται αναδιοργάνωση ή ένωση χειρίζονται από το στάδιο shaping που περιγράφεται στο shaping κειμένου σύνθετων scripts
Πώς να προστεθεί συμπεριφορά εκ των υστέρων χωρίς ρίσκο στην υπάρχουσα διαδρομή
Η ίδια έκδοση πρόσθεσε μια εναλλακτική κληρονομικού πίνακα kern για διάστιχο ζευγαριών, και ο τρόπος που ορίστηκε είναι μοτίβο που αξίζει αντιγραφή. Αντί να προστεθεί νέο σημείο απόφασης στη λογική kerning, η εναλλακτική κρέμεται από τον κλάδο πρώιμης εξόδου που υπήρχε ήδη για γραμματοσειρές χωρίς πίνακα GPOS. Μια σύγχρονη γραμματοσειρά με GPOS δεν φτάνει ποτέ εκεί, οπότε η συμπεριφορά της είναι αναλλοίωτη εκ κατασκευής και όχι μέσω testing. Οι διαδρομές που δεν καταχωρίζουν γραμματοσειρά Unicode παράγουν δύο μηδενικές αντισταθμίσεις, οπότε είναι κι αυτές αναλλοίωτες
Αυτό είναι το γενικό σχήμα μιας χαμηλού ρίσκου πρόσθεσης εκ των υστέρων σε ώριμη βιβλιοθήκη απόδοσης: βρείτε τον κλάδο που αυτή τη στιγμή δεν παράγει τίποτα και βάλτε τη νέα συμπεριφορά εκεί. Μετατρέπει το «πιστεύουμε ότι αυτό δεν υποβάθμισε τίποτα» σε «αυτό δεν μπορεί να είχε υποβαθμίσει τίποτα», που είναι πολύ καλύτερο πράγμα να λέγετε για μια μηχανή κειμένου από την οποία περνούν τιμολόγια άλλων ανθρώπων
Κάντε το πύλη, όχι καταγραφή
Τα ευρήματα κάλυψης είναι χρήσιμα μόνο αν κάτι αποτυγχάνει πάνω τους. Σε μια υπηρεσία παραγωγής εγγράφων η παραγωγική διευθέτηση είναι να παραμείνει η παρακολούθηση ενεργή στην νυχτερινή εργασία regression απέναντι σε σώμα πραγματικών ονομάτων πελατών, διευθύνσεων και περιγραφών προϊόντων, και να αποτυγχάνει η εργασία σε κάθε εύρημα. Επειδή το συμβάν ενεργοποιείται μία φορά ανά μοναδικό code point και όχι μία ανά εμφάνιση, η έξοδος παραμένει αρκετά μικρή για ανάγνωση ακόμη και όταν λείπει ένας ολόκληρος script
Στην παραγωγή ο ίδιος χειριστής χρησιμοποιείται καλύτερα ως τηλεμετρία: καταγράψτε το code point και τη γραμματοσειρά, συνεχίστε να σερβίρετε το έγγραφο, και αφήστε το αθροιστικό να σας πει ποιο script να προσθέσετε επόμενο στο σύνολο γραμματοσειρών ανάπτυξης. Η συμπεριφορά απόδοσης για ενσωματωμένες και αντικατασταμένες γραμματοσειρές καλύπτεται περαιτέρω στη απόδοση glyphs ενσωματωμένων γραμματοσειρών, και η πλήρης λίστα ιδιοτήτων συμπεριλαμβανομένης της TrackUnresolvedGlyphs τεκμηριώνεται στη σελίδα προϊόντος HotPDF Delphi PDF component