Το PDFiumPas, ο wrapper Delphi και C++Builder γύρω από τη μηχανή PDFium της Google, αποθηκεύει ένα έγγραφο σε ακριβή έκδοση PDF από 1.3 έως 1.7 μέσω της παραμέτρου PdfVersion της μεθόδου TPdf.SaveAs. Η δική της κλήση FPDF_SaveWithVersion του PDFium απλώς ξαναγράφει την κεφαλίδα %PDF-M.m, χωρίς να ελέγχει αν το πραγματικό περιεχόμενο του εγγράφου είναι νόμιμο σε εκείνη την έκδοση. Το PDFiumPas κλείνει εκείνο το κενό με ένα πέρασμα συμμόρφωσης μετά την αποθήκευση που διατρέχει την ενεργή αλυσίδα αναθεωρήσεων παραπομπής παύλας (cross-reference) και ελέγχει δηλώσεις Adobe Extension Level πριν το αρχείο φύγει από τη μέθοδο
Εκείνη η διάκριση έχει μεγαλύτερη σημασία στην παραγωγή εκτύπωσης, όπου ένα προφίλ PDF/X ονομάζει μια ακριβή έκδοση PDF και ένα εργαλείο preflight ή ένα RIP απορρίπτει οτιδήποτε σιωπηλά διαφωνεί με τη δική του κεφαλίδα, ένα σενάριο που καλύπτεται από την πλευρά εξόδου στο επικύρωση εγγράφων PDF/X έτοιμων για εκτύπωση με το PDFiumPas. Το SaveAs εκθέτει τον στόχο ως την απαρίθμηση TPdfVersion, pv13 έως pv17 μαζί με τις παλαιότερες τιμές pv10 έως pv12, συν ένα ανεξάρτητο TSaveOption για επαυξητικές ή πλήρεις επανεγγραφές. Περάστε το PdfVersion και το PDFiumPas κάνει δύο δουλειές σε μία κλήση: ζητά από το PDFium να σφραγίσει τη ζητούμενη κεφαλίδα, έπειτα ξαναδιαβάζει τα φρεσκογραμμένα byte και αρνείται να δώσει πίσω ένα αρχείο του οποίου το ενεργό περιεχόμενο δεν μπορεί νόμιμα να υπάρξει σε εκείνη την έκδοση
var
Pdf: TPdf;
begin
Pdf:= TPdf.Create(nil);
try
Pdf.FileName:= 'source.pdf';
Pdf.Active:= True;
try
Pdf.SaveAs('press-ready.pdf', saNoIncremental, pv17);
except
on E: Exception do
// E.Message names the offending feature and the version or
// extension level it actually needs, for example:
// "RichMedia annotations and RichMediaExecute actions require
// /Extensions /ADBE with /BaseVersion /1.7 and /ExtensionLevel 3
// or newer."
raise;
end;
finally
Pdf.Free;
end;
end;
Γιατί ο τελευταίος ορισμός αντικειμένου στο αρχείο είναι το λάθος πράγμα να εμπιστευτείτε;
Το τελευταίο φυσικό αντικείμενο με έναν δεδομένο αριθμό σε ένα αρχείο PDF δεν είναι απαραίτητα το αντικείμενο που θα επέλυε σήμερα ένας συμμορφούμενος αναγνώστης για εκείνον τον αριθμό. Ένα PDF που έχει περάσει από αρκετές επαυξητικές ενημερώσεις δεν έχει ένα γράφημα αντικειμένων, έχει ένα ιστορικό από αυτά στρωμένα μέσα σε ένα μοναδικό αρχείο, και κάθε κύκλος προσάρτησης μπορεί να ελευθερώσει ένα αντικείμενο, να το επαναορίσει κάτω από νέο αριθμό γενιάς, ή να αφήσει το παλιό φυσικό του σώμα να κάθεται μεταξύ δύο δεικτών endobj χωρίς καμία καταχώριση παραπομπής παύλας να δείχνει πλέον σε αυτό
Το PDFiumPas χτύπησε ακριβώς αυτή τη λειτουργία αποτυχίας πριν παρακολουθεί ρητά αναθεωρήσεις xref: μια annotation Redact ορφανή από μια μεταγενέστερη επανεγγραφή αντικειμένου σελίδας, ή ένα λεξικό /MarkInfo αφημένο φυσικά παρόν χωρίς καμία καταχώριση xref να δείχνει σε αυτό, εξακολουθούσε να εμφανίζεται σε μια σάρωση byte και εξακολουθούσε να πυροδοτεί έναν έλεγχο δυνατότητας έκδοσης που δεν εφαρμοζόταν πλέον στο έγγραφο που θα άνοιγε πράγματι ένας αναγνώστης. Η κατεύθυνση αποτυχίας ήταν ψευδής απόρριψη, όχι ψευδής αποδοχή: ένα αρχείο που είχε γνησίως προχωρήσει πέρα από μια δυνατότητα στην τρέχουσα αναθεώρησή του μπορούσε ακόμη να μπλοκαριστεί από την αποθήκευση σε χαμηλότερη έκδοση εξαιτίας περιεχομένου που κανείς δεν μπορούσε πλέον να φτάσει
Πώς καθορίζει το PDFiumPas ποιοι ορισμοί αντικειμένων είναι πράγματι ενεργοί;
Το PDFiumPas επιλύει το ενεργό σύνολο αντικειμένων με τον ίδιο τρόπο που το κάνει ένας συμμορφούμενος αναγνώστης, διατρέχοντας την αλυσίδα παραπομπής παύλας αντί να σαρώνει byte για κεφαλίδες αντικειμένων. Ο επιλυτής ξεκινά στην τελευταία μετατόπιση startxref στο αρχείο και ακολουθεί κάθε σύνδεσμο /Prev προς τα πίσω μέσα από παλαιότερες αναθεωρήσεις, αναλύοντας κλασικούς πίνακες παραπομπής παύλας, υβριδικές ροές συνδεδεμένες με /XRefStm, και καθαρές ροές παραπομπής παύλας στην πορεία. Η διάτρεξη τρέχει από το νεότερο προς το παλαιότερο και διευθετεί κάθε αριθμό αντικειμένου την πρώτη φορά που τον βλέπει, οπότε μια ελεύθερη καταχώριση σε μια μεταγενέστερη αναθεώρηση σκιάζει σωστά ένα σώμα αντικειμένου γραμμένο σε μια παλαιότερη, και ένας επαναορισμός κάτω από μια νέα μετατόπιση ή γενιά πάντα κερδίζει έναντι αυτού που αντικατέστησε
Τα μέλη ροής αντικειμένων παίρνουν έναν επιπλέον έλεγχο που μια απλή αναζήτηση μετατόπισης δεν μπορεί να παράσχει από μόνη της, έναν μηχανισμό που καλύπτεται σε μεγαλύτερο βάθος στο επικύρωση ροών αντικειμένων και παραπομπής παύλας με το PDFiumPas. Ένα συμπιεσμένο αντικείμενο ανακτημένο από ένα /ObjStm πρέπει να έχει τη γονική του ροή επιβεβαιωμένη ενεργή στην ίδια διάτρεξη, και ο δείκτης του πρέπει να συμφωνεί με τη δική του θέση του μέλους μέσα στην κεφαλίδα εκείνης της ροής προτού το PDFiumPas το αντιμετωπίσει ως ζωντανό περιεχόμενο. Το ISO 32000-1 ενότητα 7.5.8.4 περιγράφει μάλιστα μια περίπτωση υβριδικής αναφοράς όπου ένας κλασικός πίνακας συμβατότητας σημειώνει ένα αντικείμενο ελεύθερο ενώ η καταχώριση /XRefStm του trailer ταυτόχρονα ορίζει το ίδιο αντικείμενο ως συμπιεσμένο μέλος αλλού· το PDFiumPas συγχωνεύει τη συμπληρωματική ροή xref στην ίδια αναθεώρηση προτού εφαρμοστούν οι κλασικές καταχωρίσεις, οπότε ο συμπιεσμένος ορισμός κερδίζει με τον τρόπο που προτίθεται η προδιαγραφή
Adobe Extension Levels: η πύλη πάνω από τον αριθμό έκδοσης
Μια κεφαλίδα %PDF-1.7 υπόσχεται μόνο το σύνολο δυνατοτήτων που τυποποίησε το ISO 32000-1 το 2008, ενώ αρκετές δυνατότητες στις οποίες βασίζονται σήμερα οι παραγωγοί PDF κυκλοφόρησαν αργότερα ως συμπληρώματα μόνο Adobe στρωμένα πάνω στον ίδιο εκείνο αριθμό έκδοσης. Η Adobe κατέγραψε κάθε συμπλήρωμα ως ζεύγος BaseVersion και ExtensionLevel καταγεγραμμένο στο λεξικό /Extensions του καταλόγου εγγράφου κάτω από ένα πρόθεμα προγραμματιστή, ADBE για τα δικά της συμπληρώματα της Adobe, ώστε ένας αναγνώστης να μπορεί να ξεχωρίσει ένα απλό αρχείο PDF 1.7 από ένα που επίσης υλοποιεί ένα αριθμημένο επίπεδο επέκτασης. Η αποθήκευση στο pv17 χωρίς εκείνη τη δήλωση δεν είναι σφάλμα από μόνη της· γίνεται ένα μόνο τη στιγμή που το ενεργό περιεχόμενο πράγματι εξαρτάται από μια δυνατότητα που η δήλωση υποτίθεται ότι καλύπτει
Ποιες δυνατότητες υψηλής έκδοσης πυροδοτούν τη ρητή πύλη έκδοσης;
Το PDFiumPas ελέγχει μια συγκεκριμένη, βασισμένη στην προδιαγραφή λίστα αντί να μαντεύει μόνο από τον αριθμό έκδοσης. Λεξικά εικόνων που φέρουν μια ρητή καταχώριση /SMaskInData ή μια τιμή /BitsPerComponent 16 απαιτούν αμφότερα PDF 1.5, με την περίπτωση δεκαέξι bit να ακολουθεί απευθείας τους κανόνες συστατικών εικόνας της PDF Reference 1.5 ενότητα 4.8. Οι annotation RichMedia και οι ενέργειες RichMediaExecute απαιτούν /BaseVersion /1.7 με /ExtensionLevel 3 ή υψηλότερο. Οι ροές 3D PRC, αναγνωρισμένες από ένα λεξικό που φέρει τόσο /Type /3D όσο και /Subtype /PRC, απαιτούν την ίδια βασική έκδοση αλλά μόνο /ExtensionLevel 1. Τα λεξικά Geospatial Measure και οι annotation Projection απαιτούν /BaseVersion /1.7 με /ExtensionLevel 3, το ίδιο συμπλήρωμα Adobe από το οποίο εξαρτάται το RichMedia
Ο γεωχωρικός έλεγχος φέρει μια λεπτομέρεια ανάγνωσης προδιαγραφής που αξίζει να γνωρίζετε αν ποτέ χτίσετε τη δική σας λογική με πύλη έκδοσης πάνω από το PDFiumPas. Ο Πίνακας 254 του ISO 32000-1 σημειώνει την καταχώριση /Type του λεξικού Measure ως προαιρετική, σημειώνοντας μόνο ότι "αν είναι παρόν, πρέπει να είναι Measure," ενώ ο Πίνακας 311 κάνει το /Type υποχρεωτικό για το λεξικό ροής 3D όπου ζει το περιεχόμενο PRC. Η πραγματική έξοδος GeoPDF από εργαλεία χαρτογράφησης παραλείπει τακτικά το /Type στο λεξικό Measure και γράφει μόνο /Subtype /GEO, οπότε ο ανιχνευτής γεωχωρικών δεδομένων του PDFiumPas ταιριάζει μόνο στο /Subtype αντί να απαιτεί και τα δύο κλειδιά με τον τρόπο που μπορεί με ασφάλεια να το κάνει ο ανιχνευτής του 3D PRC. Η απαίτηση /Type και στα δύο λεξικά θα άφηνε συμμορφούμενο περιεχόμενο GeoPDF να γλιστρήσει πέρα από την πύλη ανίχνευτο, να προσγειωθεί σε ένα απλό αρχείο PDF 1.7 χωρίς δήλωση επιπέδου επέκτασης να το υποστηρίζει
Υποβαθμίζει αυτόματα το PDFiumPas μη υποστηριζόμενες δυνατότητες;
Όχι ως γενική δυνατότητα, και η υπόθεση του αντιθέτου είναι το λάθος που πρέπει να αποφύγετε εδώ. Το SaveAs περνά τη στοχευόμενη έκδοση μέσα από μια εσωτερική ρουτίνα, το ValidatePdfVersionCompliance, και όταν εκείνη η ρουτίνα βρει μια δυνατότητα που η στοχευόμενη έκδοση ή η δήλωση επιπέδου επέκτασής της δεν μπορεί να υποστηρίξει, το SaveAs εγείρει εξαίρεση που φέρει το κείμενο σφάλματος της ρουτίνας αντί να γράψει το αρχείο· ο καλών παίρνει πίσω έναν ακριβή, ονομασμένο-κατά-δυνατότητα λόγο, ποτέ ένα σιωπηλά ξαναγραμμένο έγγραφο. Το ένα μέρος όπου το PDFiumPas πράγματι ξαναγράφει περιεχόμενο αυτόματα είναι ένας στόχος PDF 1.3, όπου αφαιρεί τις σημασιολογικά ουδέτερες προεπιλογές διαφάνειας /BM /Normal, /CA 1, και /ca 1 που το PDFium πάντα γράφει σε λεξικά ExtGState ανεξάρτητα από τη στοχευόμενη έκδοση, επειδή εκείνες οι συγκεκριμένες τιμές δεν φέρουν καμία οπτική σημασία και το PDF 1.3 προηγείται εντελώς των κλειδιών
// PDF 1.3 targets rewrite the saved bytes to strip transparency
// defaults PDFium always emits, so incremental mode cannot apply
Pdf.SaveAs('legacy-archive.pdf', saIncremental, pv13);
// raises: PDF 1.3 normalization is incompatible with incremental
// save mode
Γνήσια μη προεπιλεγμένη διαφάνεια και μάσκες μαλακότητας εικόνας εξακολουθούν να αποτυγχάνουν εντελώς σε στόχο PDF 1.3, επειδή η αφαίρεσή τους θα άλλαζε το πώς πράγματι φαίνεται η σελίδα, και το PDFiumPas δεν θα πάρει εκείνη την απόφαση εκ μέρους σας. Δύο σχετικά όρια αξίζει να τα σχεδιάσετε πριν μια ακριβής έκδοση μπει σε μια διοχέτευση παρτίδας. Η έξοδος ρητής έκδοσης ποτέ δεν φέρει λεξικό /Encrypt· η αποθήκευση αποτυγχάνει αμέσως αν η πηγή είναι προστατευμένη, κάτι που τυχαίνει να ευθυγραμμίζεται με προφίλ PDF/X και PDF/A που ούτως ή άλλως απαγορεύουν την κρυπτογράφηση, αλλά σημαίνει ότι η αποκρυπτογράφηση είναι ξεχωριστό βήμα στη ροή εργασίας σας αντί για κάτι που κάνει το SaveAs για εσάς. Το PDFiumPas επίσης δεν έχει καμία δημόσια μέθοδο για την εγγραφή μιας δήλωσης /Extensions /ADBE σε έναν κατάλογο, οπότε ένα αρχείο πηγή που περιέχει περιεχόμενο RichMedia, PRC 3D, ή γεωχωρικό αλλά του λείπει εκείνη η δήλωση δεν θα περάσει την πύλη ανεξάρτητα από ποιο PdfVersion ζητάτε· η δήλωση πρέπει ήδη να υπάρχει στην πηγή, τυπικά επειδή το εργαλείο συγγραφής την έγραψε, ή η δυνατότητα πρέπει να αφαιρεθεί πριν την αποθήκευση. Η ιδιότητα TPdf.PdfVersion μόνο για ανάγνωση αξίζει να ελεγχθεί πριν επιχειρηθεί καν μια αποθήκευση ακριβούς έκδοσης, αφού επιλύει την ίδια ενήμερη για κατάλογο ενεργή έκδοση, κεφαλίδα ή παράκαμψη /Version, όποια είναι τρέχουσα, στην οποία βασίζεται ο ίδιος ο επικυρωτής κατά την αποθήκευση
Pdf.FileName:= 'incoming.pdf';
Pdf.Active:= True;
// PdfVersion resolves the same catalog-aware effective version the
// save-time validator uses, so a mismatch here is worth investigating
// before spending a full SaveAs attempt on it
LogSourceVersion('incoming.pdf', Pdf.PdfVersion);
Αντιμετωπίστε μια εξαίρεση SaveAs σε στόχο ακριβούς έκδοσης ως αναφορά preflight αντί ως σφάλμα: το μήνυμα ονομάζει την ακριβή ρήτρα που παραβιάζει το έγγραφο πηγή, κάτι που είναι ακριβώς η πληροφορία που χρειάζεται ένα τυπογραφείο ή μια διοχέτευση αρχειοθέτησης πριν προχωρήσει ένα αρχείο περαιτέρω. Η διαδρομή αποθήκευσης ρητής έκδοσης, ο επιλυτής ενεργών αναθεωρήσεων xref, και οι έλεγχοι Adobe Extension Level που περιγράφονται εδώ διατίθενται ως μέρος του τυπικού εξαρτήματος PDFiumPas για Delphi και C++Builder· η σελίδα προϊόντος φέρει την πλήρη αναφορά TPdf.SaveAs μαζί με το υπόλοιπο του API συμμόρφωσης και φορμών