Η losLab PDF Library μπορεί να ενσωματώσει τα προγράμματα γραμματοσειρών που λείπουν από ένα ήδη φορτωμένο PDF με μία μόνο κλήση: Η EmbedMissingFonts διατρέχει κάθε λεξικό γραμματοσειράς στο έγγραφο, εντοπίζει την αντίστοιχη εγκατεστημένη γραμματοσειρά συστήματος βάσει του ονόματος BaseFont της και εγγράφει το πρόγραμμα γραμματοσειράς πίσω στο αρχείο. Για ομάδες που επιδιορθώνουν έγγραφα τρίτων τα οποία αποτυγχάνουν στην επαλήθευση PDF/A λόγω ενσωμάτωσης γραμματοσειρών, αυτή είναι η διόρθωση που εξαφανίζει το σφάλμα preflight 00030
Το σενάριο είναι απογοητευτικά κοινό. Μια ροή εισαγωγής αρχείων λαμβάνει PDF από προμηθευτές, πελάτες ή ένα γραφείο σάρωσης· τα έγγραφα εμφανίζονται μια χαρά σε κάθε γραφείο στο κτίριο· και στη συνέχεια ο επαληθευτής PDF/A απορρίπτει ολόκληρη την παρτίδα με το ίδιο παράπονο που επαναλαμβάνεται μία φορά ανά αρχείο: τουλάχιστον μία γραμματοσειρά δεν είναι ενσωματωμένη. Κανείς στην αρχή της αλυσίδας δεν πρόκειται να αναγεννήσει τα αρχεία, επομένως η ροή εισαγωγής πρέπει να τα επιδιορθώσει. Αυτό το άρθρο καλύπτει αυτή τη διαδρομή επιδιόρθωσης. Είναι ο συνοδός του άρθρου preflight, το οποίο καλύπτει τον εντοπισμό παραβιάσεων PDF/A και PDF/UA: εκείνο το κομμάτι σάς λέει ποια έγγραφα είναι κατεστραμμένα, αυτό διορθώνει τον πιο συχνό τρόπο με τον οποίο είναι κατεστραμμένα
Γιατί το PDF/A απαιτεί την ενσωμάτωση κάθε γραμματοσειράς;
Το πρότυπο ISO 19005-1 §6.3.4 απαιτεί κάθε γραμματοσειρά που χρησιμοποιείται από ένα συμμορφούμενο έγγραφο να φέρει το πρόγραμμα γραμματοσειράς της μέσα στο αρχείο, επειδή ολόκληρη η υπόσχεση του PDF/A είναι η αναπαραγωγιμότητα: το έγγραφο πρέπει να εμφανίζεται πανομοιότυπα σε ένα μηχάνημα μετά από πενήντα χρόνια που δεν μοιράζεται καμία γραμματοσειρά με το μηχάνημα που το παρήγαγε. Μια μη ενσωματωμένη γραμματοσειρά είναι μια οδηγία για την εύρεση της Arial κάπου στο σύστημα προβολής, και η θέση του προτύπου είναι ότι το "κάπου στο σύστημα προβολής" δεν αποτελεί εγγύηση αρχειοθέτησης. Όποιες γλύφες, μετρικά και κάλυψη έχει η υποκατάστατη γραμματοσειρά, αυτό λαμβάνει ο αναγνώστης, και μπορεί να μην είναι αυτό που είδε ο δημιουργός
Ο ιστορικός ένοχος είναι η σύμβαση Standard 14. Το PDF 1.0 υποσχέθηκε ότι κάθε εφαρμογή προβολής συνοδεύεται από Helvetica, Times, Courier, Symbol και ZapfDingbats, έτσι οι γεννήτριες έμαθαν να αναφέρουν αυτές τις γραμματοσειρές με το όνομά τους και να μην ενσωματώνουν τίποτα, και τριάντα χρόνια εργαλείων εξακολουθούν να κάνουν ακριβώς αυτό. Η losLab PDF Library λαμβάνει την απαίτηση αρκετά σοβαρά ώστε σε λειτουργία δημιουργίας PDF/A η AddStandardFont να είναι σκόπιμα μια ανενεργή λειτουργία: η βιβλιοθήκη δεν αποστέλλει τα προγράμματα γραμματοσειρών Standard 14, δεν μπορεί να ενσωματώσει αυτό που δεν έχει και αρνείται να γράψει μια μη ενσωματωμένη αναφορά σε ένα έγγραφο που ισχυρίζεται συμμόρφωση. Επιστρέφει 0 χωρίς να επιλέξει γραμματοσειρά, επομένως ένα έγγραφο PDF/A πρέπει να χρησιμοποιήσει την AddTrueTypeFont με ενσωμάτωση, και οποιοδήποτε αίτημα Embed=0 προάγεται σιωπηρά σε Embed=1 ενώ η λειτουργία PDF/A είναι ενεργή. Αυτή είναι η πλευρά του εγγραφέα. Το πιο δύσκολο πρόβλημα είναι η πλευρά του αναγνώστη: ένα έγγραφο που έγραψε κάποιος άλλος, γεμάτο με λεξικά γραμματοσειρών που δεν δημιουργήσατε εσείς
Πώς το EmbedMissingFonts επιδιορθώνει ένα φορτωμένο έγγραφο;
Η losLab PDF Library επιδιορθώνει τις γραμματοσειρές επί τόπου αντί να τις ανακατασκευάζει. Όταν μια γεννήτρια PDF γράφει μια μη ενσωματωμένη γραμματοσειρά TrueType, το λεξικό FontDescriptor που παράγει είναι ήδη πλήρες: FontName, FontBBox, Flags, Ascent, Descent, StemV, όλα παρόντα. Το μόνο πράγμα που το διαχωρίζει από μια ενσωματωμένη γραμματοσειρά είναι η απουσία μίας καταχώρισης, της αναφοράς ροής /FontFile2 που περιέχει το πραγματικό πρόγραμμα γραμματοσειράς. Επομένως, το EmbedMissingFonts δεν αγγίζει το λεξικό της γραμματοσειράς, την κωδικοποίηση, τον πίνακα πλατών ή οποιαδήποτε ροή περιεχομένου που αναφέρεται στη γραμματοσειρά με το όνομα του πόρου. Διαβάζει το αντίστοιχο πρόγραμμα γραμματοσειράς από το σύστημα, το συμπιέζει σε ένα νέο αντικείμενο ροής και προσαρτά μια απλή αναφορά /FontFile2 (ή /FontFile3 για γραμματοσειρές CIDFontType0) στο FontDescriptor που βρίσκεται ήδη εκεί. Όλα όσα δείχνουν οι σελίδες του εγγράφου παραμένουν ακριβώς εκεί που ήταν, γεγονός που καθιστά τη λειτουργία ασφαλή για εκτέλεση σε αρχεία που δεν ελέγχετε εσείς
Η κάλυψη περιλαμβάνει και τις δύο αρχιτεκτονικές γραμματοσειρών που θα συναντήσετε στην πράξη: απλές γραμματοσειρές TrueType και σύνθετες γραμματοσειρές Type0/CID, του είδους που παράγεται για κείμενο CJK και σύγχρονη έξοδο Unicode. Η διάσχιση απαριθμεί σκόπιμα κάθε λεξικό Font στο δέντρο αντικειμένων του εγγράφου αντί να βασίζεται σε μια περιήγηση πόρων σελίδα προς σελίδα, έτσι ώστε να συλλέγονται επίσης οι γραμματοσειρές που αναφέρονται από σχόλια ή μοιράζονται μεταξύ σελίδων. Το API είναι μια ενιαία κλήση στο φορτωμένο έγγραφο
var
PDF: TPDFlib;
Repaired: Integer;
begin
PDF := TPDFlib.Create;
try
if PDF.LoadFromFile('supplier-invoice.pdf', '') <> 1 then
raise Exception.Create('Could not load PDF');
// Walks every Font dictionary; returns how many fonts
// gained a font program. Fonts whose program cannot be
// found on the system are skipped, not failed.
Repaired := PDF.EmbedMissingFonts;
Writeln(Format('%d font program(s) embedded', [Repaired]));
PDF.SaveToFile('supplier-invoice-repaired.pdf');
finally
PDF.Free;
end;
end;
Μια λεπτομέρεια που αξίζει να γνωρίζετε επειδή εξηγεί γιατί η αντιστοίχιση ονομάτων λειτουργεί καλύτερα από μια απλή σύγκριση συμβολοσειρών: η βιβλιοθήκη κανονικοποιεί τα ονόματα BaseFont πριν τα αναζητήσει. Τα προθέματα υποσυνόλου (το μοτίβο ABCDEF+ έξι κεφαλαίων γραμμάτων και ενός συμβόλου συν) αφαιρούνται, τα επιθέματα τύπου PostScript όπως το ArialMT επιλύονται σε Arial, και τα αρχεία TrueType Collection ανιχνεύονται και αποσυμπιέζονται, ώστε μια γραμματοσειρά που βρίσκεται μέσα σε ένα .ttc να ενσωματώνεται σωστά
Επαλήθευση της επιδιόρθωσης με αναφορά preflight
Το CreatePreflightReport είναι το βήμα επαλήθευσης, και ο βρόχος είναι σκόπιμα κλειστός: ο ίδιος έλεγχος που καταδίκασε το αρχείο θα πρέπει να είναι αυτός που το εκκαθαρίζει. Ο κωδικός σφάλματος 00030 είναι το εύρημα βαθύ ελέγχου PDF/A που αναφέρει "At least one font is not embedded (FontFile/FontFile2/FontFile3 missing)", και αναφέρεται για το αρχείο ως σύνολο, επομένως μια και μόνο παραβλεφθείσα γραμματοσειρά το διατηρεί ενεργό. Εκτελέστε την αναφορά στο αρχείο προέλευσης, επιδιορθώστε, αποθηκεύστε και εκτελέστε την ξανά στο αποτέλεσμα
function HasFontEmbeddingViolation(PDF: TPDFlib;
const FileName: string): Boolean;
var
Report: string;
begin
// ComplianceTests = 1 selects the PDF/A checks
Report := PDF.CreatePreflightReport(FileName, '', 1, 0);
Result := Pos('00030', Report) > 0;
end;
Για μια προβολή ανά γραμματοσειρά αντί για μια ετυμηγορία ανά αρχείο, φορτώστε ξανά το επιδιορθωμένο έγγραφο και απαριθμήστε: Η FindFonts ακολουθούμενη από τις SelectFont και GetFontIsEmbedded αναφέρει την κατάσταση ενσωμάτωσης γραμματοσειρά προς γραμματοσειρά, το οποίο είναι το κατάλληλο εργαλείο όταν μια μαζική εργασία πρέπει να καταγράψει ακριβώς ποια γραμματοσειρά σε ποιο αρχείο δεν μπόρεσε να επιδιορθωθεί. Το ίδιο μοτίβο απαρίθμησης εμφανίζεται στο άρθρο σχετικά με την εξαγωγή κειμένου, εικόνων και γραμματοσειρών από φορτωμένα PDF, όπου τροφοδοτεί την εξαγωγή αντί για την επιδιόρθωση
Τι συμβαίνει όταν η γραμματοσειρά δεν είναι εγκατεστημένη στο σύστημα;
Το EmbedMissingFonts παρακάμπτει οποιαδήποτε γραμματοσειρά της οποίας το πρόγραμμα δεν μπορεί να βρει, και αναφέρει την παράκαμψη μέσω της τιμής επιστροφής του: εάν ο αριθμός επιστρέψει χαμηλότερος από τον αριθμό των μη ενσωματωμένων γραμματοσειρών που μετρήσατε, η διαφορά είναι γραμματοσειρές που δεν διαθέτει το σύστημα. Αυτός είναι ο ειλικρινής τρόπος αποτυχίας και είναι καλύτερος από τις εναλλακτικές λύσεις, επειδή η επινόηση ενός υποκατάστατου προγράμματος για μια γραμματοσειρά που κατονομάζεται στο έγγραφο θα άλλαζε την εμφάνιση, κάτι που ακριβώς μια επιδιόρθωση αρχειοθέτησης δεν πρέπει ποτέ να κάνει. Για αυτές τις περιπτώσεις, η losLab PDF Library παρέχει το EmbedFontProgramFromFile, το οποίο ενσωματώνει ένα παρεχόμενο από τον καλούντα .ttf ή .otf στην κατονομαζόμενη γραμματοσειρά, ώστε μια ροή εργασίας να μπορεί να αποστέλλει τις εταιρικές γραμματοσειρές που αναμένει να συναντήσει και να καταφεύγει σε αυτές σκόπιμα
var
I, FontID: Integer;
begin
PDF.FindFonts;
for I := 1 to PDF.FontCount do
begin
FontID := PDF.GetFontID(I);
if (FontID > 0) and (PDF.SelectFont(FontID) = 1) then
if PDF.GetFontIsEmbedded = 0 then
// Try the installed system font first, then fall back
// to a font file shipped alongside the application
if PDF.EmbedFontProgram(PDF.FontName) = 0 then
PDF.EmbedFontProgramFromFile(PDF.FontName,
'fonts\CorporateSans.ttf');
end;
end;
Δύο όρια αξίζει να αναφερθούν με σαφήνεια. Πρώτον, οι γραμματοσειρές Type1 δεν επιδιορθώνονται στην τρέχουσα υλοποίηση: η καταχώρισή τους /FontFile απαιτεί τη δομή PFB τριών τμημάτων με ρητά κλειδιά μήκους, και η βιβλιοθήκη τις παρακάμπτει αντί να γράψει μια κακοσχηματισμένη ροή· είναι σπάνιες στα σύγχρονα έγγραφα, αλλά εμφανίζονται σε παλιά αρχεία. Δεύτερον, η ενσωμάτωση μιας γραμματοσειράς είναι μια πράξη αδειοδότησης. Τα δικαιώματα ενσωμάτωσης μιας γραμματοσειράς TrueType ανήκουν στο σχεδιαστήριο της (foundry), και μια ροή επιδιόρθωσης που εισάγει αδειοδοτημένα προγράμματα γραμματοσειρών σε έγγραφα που εξέρχονται από τον οργανισμό θα πρέπει να επιβεβαιώσει από κάποιον ότι οι άδειες των γραμματοσειρών το επιτρέπουν πραγματικά. Η βιβλιοθήκη θα κάνει ό,τι ζητήσετε· το αν επιτρέπεται να το ζητήσετε είναι μια ερώτηση για το νομικό σας τμήμα, όχι για τον μεταγλωττιστή σας
Η ενσωμάτωση είναι απαραίτητη, όχι επαρκής
Η επιδιόρθωση των γραμματοσειρών εκκαθαρίζει το σφάλμα 00030 και τίποτα άλλο. Ένα έγγραφο που αποτυγχάνει στο PDF/A λόγω κρυπτογράφησης, ελλειπόντων μεταδεδομένων XMP, χρωματικού χώρου εξαρτώμενου από τη συσκευή χωρίς OutputIntent ή απουσίας χαρτών ToUnicode θα εξακολουθεί να αποτυγχάνει αφού ενσωματωθεί κάθε γραμματοσειρά, γι' αυτό και η επιδιόρθωση ανήκει σε έναν βρόχο που καθοδηγείται από preflight αντί να τον αντικαθιστά. Εκτελέστε την πλήρη αναφορά, διορθώστε ό,τι κατονομάζει και αφήστε την αναφορά να σας πει πότε τελειώσατε. Υπάρχει επίσης μια διάσταση κόστους: ένα πλήρες πρόγραμμα γραμματοσειράς CJK φτάνει σε μεγέθη megabytes, επομένως η ενσωμάτωση αρκετών από αυτά μπορεί να διογκώσει δραματικά ένα μικρό έγγραφο. Το αντίβαρο είναι η δημιουργία υποσυνόλου (subsetting), που καλύπτεται στο άρθρο σχετικά με τη βελτιστοποίηση μεγέθους αρχείου PDF και τη δημιουργία υποσυνόλου γραμματοσειρών, το οποίο περικόπτει κάθε ενσωματωμένο πρόγραμμα στις γλύφες που πραγματικά αποδίδει το έγγραφο
Αποτροπή οπισθοδρόμησης νέων εγγράφων
Το SetEmbedAllFonts είναι το μισό πρόληψης του ίδιου χαρακτηριστικού: ένας προστατευτικός μηχανισμός στην πλευρά του εγγραφέα που εμποδίζει τον δικό σας κώδικα να παράγει τα έγγραφα που επιδιορθώνει αυτό το άρθρο. Με ενεργό το SetEmbedAllFonts(1), οποιαδήποτε επόμενη κλήση AddTrueTypeFont που ζητά Embed=0 προάγεται σε ενσωματωμένη αναφορά, πράγμα που επεκτείνει σε κάθε έγγραφο την εγγύηση που επιβάλλει ήδη η λειτουργία PDF/A. Επηρεάζει γραμματοσειρές που προστίθενται μετά την κλήση, όχι γραμματοσειρές που βρίσκονται ήδη σε φορτωμένο αρχείο, επομένως ο καταμερισμός εργασίας είναι καθαρός: SetEmbedAllFonts για τα έγγραφα που δημιουργείτε, EmbedMissingFonts για τα έγγραφα που κληρονομείτε
PDF.NewDocument;
PDF.SetEmbedAllFonts(1);
// From here on, AddTrueTypeFont(Name, 0) behaves
// like AddTrueTypeFont(Name, 1): no non-embedded
// reference can reach the output file
Και τα δύο μισά, ο προστατευτικός μηχανισμός στην πλευρά του εγγραφέα και η διαδρομή φόρτωσης-επιδιόρθωσης-αποθήκευσης, αποτελούν μέρος της losLab PDF Library για Delphi, C# και VB.NET, μαζί με τη μηχανή preflight που επαληθεύει το αποτέλεσμα· η σελίδα του προϊόντος περιέχει την πλήρη αναφορά API γραμματοσειρών, συμπεριλαμβανομένων των κλήσεων ενσωμάτωσης και δημιουργίας υποσυνόλου ανά γραμματοσειρά