Τεχνικό Άρθρο

Όρια υλοποίησης PDF/A και έλεγχοι κωδικοποίησης γραμματοσειράς

Το PDFium Component επικυρώνει τα όρια υλοποίησης του ISO 19005-1 Annex C — name tokens 127 bytes, 8191 στοιχεία πίνακα, 4095 καταχωρήσεις λεξικού και 28 επίπεδα φωλιάσματος περιεχομένου — και αναφέρει μια συμβολική γραμματοσειρά TrueType που φέρει καταχώριση /Encoding. Και οι δύο έλεγχοι τρέχουν στη διαδρομή σάρωσης bytes, οπότε μια εφαρμογή Delphi ή Lazarus παίρνει την ετυμηγορία χωρίς να φορτώσει καθόλου το DLL του PDFium

Αυτές είναι οι αποτυχίες που μπερδεύουν περισσότερο τους ανθρώπους, επειδή το έγγραφο φαίνεται εντάξει. Αποδίδεται, εκτυπώνεται, κάθε γραμματοσειρά είναι ενσωματωμένη, το output intent είναι παρόν. Τότε ένας validator το απορρίπτει πάνω σε ένα λεξικό που έχει 4096 καταχωρήσεις, και τίποτα στο ορατό έγγραφο δεν εξηγεί γιατί

Τι προστατεύουν στην πραγματικότητα τα όρια του Annex C;

Διαλειτουργικότητα με υλοποιήσεις που προηγήθηκαν της γεννήτριάς σας. Το Annex C μεταφέρει τα όρια υλοποίησης του PDF Reference σε κάθε μέρος PDF/A, και οι αριθμοί δεν είναι αυθαίρετοι — περιγράφουν ό,τι ένας συμμορφούμενος αναγνώστης ήταν ιστορικά υποχρεωμένος να χειριστεί. Ένα αρχείο που τα υπερβαίνει μπορεί να ανοίξει τέλεια σε έναν σύγχρονο αναγνώστη και να αποτύχει στον αρχειακό αναγνώστη πάνω στον οποίο ένα σύστημα αρχείων είχε τυποποιηθεί πριν δεκαπέντε χρόνια, που είναι ακριβώς το σενάριο που υπάρχει το PDF/A για να αποτρέψει

Τα τέσσερα όρια είναι συμπεριληπτικά. Ένα name token ακριβώς 127 bytes επικυρώνεται· 128 όχι. Ένας πίνακας με ακριβώς 8191 στοιχεία επικυρώνεται· 8192 όχι. Το PDFium Component καρφώνει και τις δύο πλευρές κάθε ορίου στη σουίτα ελέγχων του για αυτόν τον λόγο, επειδή ένα off-by-one σε έναν έλεγχο ορίου παράγει τη χειρότερη μορφή validator: έναν που απορρίπτει συμμορφούμενα αρχεία και πιστεύεται παρ όλα αυτά

uses FPdfPdfa;

var
  Src: TFileStream;
  Res: TPdfAValidationResult;
begin
  Src := TFileStream.Create('archive.pdf', fmOpenRead or fmShareDenyWrite);
  try
    Res := ValidatePdfACompliance(Src);
    if pvaiArrayOverLimit in Res.Issues then
      Memo1.Lines.Add('An array carries more than 8191 elements');
    if pvaiDictOverLimit in Res.Issues then
      Memo1.Lines.Add('A dictionary carries more than 4095 entries');
    if pvaiNestingOverLimit in Res.Issues then
      Memo1.Lines.Add('Containers nest deeper than 28 levels');
    if pvaiNameOverLimit in Res.Issues then
      Memo1.Lines.Add('A name token is longer than 127 bytes');
  finally
    Src.Free;
  end;
end;

Ποιες γεννήτριες χτυπούν πραγματικά αυτά τα όρια;

Αυτές που κατασκευάζουν δομή προγραμματιστικά, που είναι οι περισσότερες γραμμές επιχειρησιακής εξόδου. Μια φόρμα με αρκετές χιλιάδες πεδία παράγει έναν πίνακα /Annots ή έναν πίνακα AcroForm /Fields που μεγαλώνει πέρα από 8191. Μια σελίδα της οποίας το λεξικό πόρων συσσωρεύει μια καταχώριση ανά παραγόμενη εικόνα ή στιγμιότυπο γραμματοσειράς διασχίζει τις 4095. Βαθιά παραγόμενα δέντρα δομής — ένα tagged έγγραφο χτισμένο με αναδρομή πάνω σε ένα ένθετο μοντέλο δεδομένων — περνούν πέρα από τα 28 επίπεδα χωρίς να το προσέξει κανείς, επειδή κανείς δεν κοιτά το βάθος φωλιάσματος

Τα μακριά ονόματα προέρχονται από διαφορετική συνήθεια: κωδικοποίηση δεδομένων μέσα σε name tokens. Ένα όνομα χρωστικής χτισμένο από ένα αναγνωριστικό πελάτη, μια ομάδα προαιρετικού περιεχομένου επώνυμη από μια πλήρη διαδρομή αρχείου, ένα πεδίο φόρμας του οποίου το πλήρως αναγνωρισμένο όνομα συνενώνει έξι επίπεδα ιεραρχίας. Τα ονόματα είναι φθηνά να παραχθούν και εύκολα να γίνουν μακριά, και 127 bytes εξαφανίζονται πιο γρήγορα από όσο θα περιμένατε μόλις εμπλέκεται μια UTF-8 κωδικοποιημένη ετικέτα

Η διόρθωση είναι δομική σε κάθε περίπτωση. Σπάστε τον πίνακα, σπάστε το λεξικό, ισοπεδώστε το φώλιασμα, κοντύνετε το όνομα — η σύσταση preflight για κάθε θέμα ονομάζει το συγκεκριμένο όριο παρά σας λέει ότι το αρχείο είναι άκυρο. Η ένεση marker δεν μπορεί να βοηθήσει εδώ: αυτές δεν είναι διεκδικήσεις μεταδεδομένων, είναι το σχήμα του γραφήματος αντικειμένων

Γιατί μια συμβολική γραμματοσειρά TrueType δεν πρέπει να φέρει /Encoding

Επειδή το ISO 19005-1 §6.3.7 παραδέχεται μόνο το ενσωματωμένο cmap της γραμματοσειράς για συμβολικές γραμματοσειρές TrueType, και μια καταχώριση /Encoding θα το διέψευδε. Μια συμβολική γραμματοσειρά χαρτογραφεί κωδικούς σε γλυφές με τους δικούς της όρους — αυτό σημαίνει συμβολική. Προσθέστε έναν πίνακα κωδικοποίησης και υπάρχουν πλέον δύο απαντήσεις στην ερώτηση «ποια γλυφή επιλέγει το byte 0x41», χωρίς κανόνα στο αρχείο να λέει ποια κερδίζει. Διαφορετικοί αναγνώστες το επιλύουν διαφορετικά, και ένα έγγραφο που αποδίδεται ως κείμενο σε έναν αναγνώστη αποδίδεται ως dingbats σε έναν άλλο

Το PDFium Component διαβάζει τη σημαία symbolic από το /FontDescriptor, είτε ο περιγραφέας είναι γραμμένος inline στο λεξικό γραμματοσειράς είτε αναφέρεται έμμεσα. Μια μη συμβολική γραμματοσειρά TrueType κρατά την απαιτούμενη /WinAnsiEncoding ή /MacRomanEncoding της χωρίς να επισημανθεί, επειδή για μη συμβολικές γραμματοσειρές η κωδικοποίηση είναι ακριβώς αυτό που ζητά το πρότυπο. Ο έλεγχος εκπυρσοκροτεί στην αντίφαση, όχι στην παρουσία μιας κωδικοποίησης

if pvaiSymbolicTrueTypeEncoding in Res.Issues then
  Memo1.Lines.Add(
    'A symbolic TrueType font carries /Encoding; PDF/A admits only its ' +
    'built-in cmap (ISO 19005-1 6.3.7)');

Η πρακτική πηγή αυτού του ελαττώματος είναι το subsetting γραμματοσειρών από έναν παραγωγό που αντιμετωπίζει κάθε γραμματοσειρά TrueType με τον ίδιο τρόπο. Symbol, Wingdings, γραμματοσειρές barcode και γραμματοσειρές εικονιδίων είναι οι συνήθεις φορείς — ακριβώς οι γραμματοσειρές που μια επιχειρησιακή εφαρμογή χρησιμοποιεί για checkboxes, λογότυπα και barcodes, και ακριβώς αυτές που κανείς δεν ξαναεξετάζει όταν ένα έγγραφο αποτυγχάνει σε επικύρωση πάνω σε «γραμματοσειρές»

Πώς τα θέματα καταλήγουν σε μια αναφορά preflight

Τα τέσσερα όρια περιεχομένου ταξινομούνται υπό δομή· το θέμα κωδικοποίησης συμβολικής TrueType ταξινομείται υπό περιεχόμενο. Αυτός ο διαχωρισμός έχει σημασία όταν μια αναφορά πάει σε δύο διαφορετικά άτομα: τα ευρήματα δομής ανήκουν συνήθως σε όποιον έγραψε τη γεννήτρια, και τα ευρήματα περιεχομένου σε όποιον παρείχε τα assets

Κάθε θέμα φέρει μια σύσταση που ονομάζει τη θεραπεία με συγκεκριμένους όρους — κοντύνετε τα name tokens σε 127 bytes ή λιγότερο, σπάστε τους πίνακες ώστε κανένας να μην φέρει πάνω από 8191 στοιχεία, αφαιρέστε το /Encoding από συμβολικές γραμματοσειρές TrueType. Μια αναφορά που λέει «μη συμμορφούμενο με PDF/A» ξεκινά μια έρευνα. Μια αναφορά που λέει ποιο όριο υπερβήκε και κατά πόσο κλείνει μία

Επικύρωση χωρίς το DLL, και γιατί αυτό έχει σημασία εδώ

Όλοι οι παραπάνω έλεγχοι τρέξουν έναντι των bytes του αρχείου, οπότε λειτουργούν σε μια υπηρεσία που δεν έχει αναπτυγμένο το PDFium binary, σε ένα βήμα build, ή σε ένα μηχάνημα όπου η φόρτωση ενός native DLL είναι πρόβλημα πολιτικής. Αυτή είναι μια σκόπιμη γραμμή σχεδιασμού στο PDFium Component: οι έλεγχοι που μπορούν να απαντηθούν από δομή απαντώνται από δομή, και το DLL κρατιέται για αυτούς που πραγματικά χρειάζονται μια μηχανή απόδοσης

Για τη γύρω ροή εργασίας — εκτέλεση επικύρωσης πάνω σε έναν φάκελο, παραγωγή αναφορών, και απόφαση τι να γίνει με τα ευρήματα — δείτε τις αναλύσεις για την επικύρωση preflight PDF/A σε Delphi και το CLI αναφοράς preflight παρτίδας. Για την επιλογή αρχειακού προφίλ που κάθεται πάνω από όλους αυτούς τους ελέγχους, οι σημειώσεις για την αρχειακή συμμόρφωση PDF/A καλύπτουν ποιο μέρος και επίπεδο να στοχεύσετε πριν αρχίσετε να διορθώνετε ευρήματα

Το PDFium Component τυλίγει τη μηχανή PDFium για Delphi, C++Builder και Lazarus με ένα VCL API υψηλού επιπέδου και ένα σύνολο validator συμμόρφωσης που τρέχουν με ή χωρίς το DLL — δείτε τη σελίδα προϊόντος PDFium Component για τα υποστηριζόμενα πρότυπα και πλατφόρμες