Ένα τυπογραφείο συναλλαγών σάς επιστρέφει τη ροή εκτύπωσης των 80.000 σελίδων με μια απόρριψη μίας γραμμής: "δεν είναι PDF/VT, το RIP δεν μπορεί να κάνει cache." Το αρχείο ανοίγει κανονικά σε κάθε προβολέα στο γραφείο σας, τα χρώματα είναι σωστά, τα δεδομένα συγχωνεύτηκαν σωστά. Τίποτα από αυτά δεν είναι αυτό που ζήτησε η ψηφιακή πρέσα. Η εκτύπωση μεταβλητών δεδομένων υψηλής ταχύτητας ζει ή πεθαίνει από το αν η πρέσα μπορεί να αναγνωρίσει ότι το μπλοκ του λογότυπου πελάτη στη σελίδα 1 είναι byte προς byte το ίδιο αντικείμενο με εκείνο στη σελίδα 40.000, να το αποδώσει μία φορά και να το επαναχρησιμοποιήσει. Το PDF/VT είναι το πρότυπο που κάνει αυτή την υπόσχεση ελέγξιμη από μηχανή, και το "φαίνεται σωστό" είναι ακριβώς η παγίδα, επειδή η δομή που διαβάζει το RIP είναι αόρατη στην οθόνη
Το PDFiumPas εκθέτει αυτή τη δομή μέσα από ένα μικρό δημόσιο API πάνω στο TPdf: SaveAsPdfVT γράφει, ValidatePdfVT ελέγχει. Αυτό το άρθρο εξηγεί τι βάζουν πραγματικά στον δίσκο και τι επιθεωρούν αυτές οι δύο μέθοδοι, πού το ISO 16612-2 είναι αυστηρότερο απ’ όσο φαίνεται αρχικά και ποια μέρη είναι ειλικρινείς δομικές άγκυρες αντί για πλήρες preflight που μπορείτε να χρεώσετε σε πελάτη
Τι τυποποιεί το PDF/VT και γιατί προηγείται το PDF/X
Το PDF/VT (ISO 16612-2:2010) δεν είναι νέο μορφότυπο αρχείου. Είναι ένα επίπεδο μεταδεδομένων βελτιστοποίησης που κουμπώνει πάνω σε ένα αρχείο PDF/X, και αυτή η σειρά είναι κρίσιμη. Το πρότυπο ορίζει τρία επίπεδα συμμόρφωσης, αλλά μόνο δύο από αυτά κατονομάζουν ένα αρχείο PDF: PDF/VT-1, ένα ενιαίο αυτόνομο έγγραφο, και PDF/VT-2, ένα μοντέλο συνόλου αρχείων όπου οι σελίδες αναφέρονται σε κοινόχρηστους εξωτερικούς πόρους. Το τρίτο token που μπορεί να δείτε, PDF/VT-2s, δεν είναι καθόλου τιμή επιπέδου αρχείου· ζει σε μια κεφαλίδα ροής MIME που περιγράφεται στο Παράρτημα A. Αν βρείτε κώδικα να σφραγίζει GTS_PDFVTVersion = "PDF/VT-2s" μέσα στο XMP ενός εγγράφου, αυτός ο κώδικας είναι λάθος
Ο αδιαπραγμάτευτος κανόνας για ένα μεμονωμένο αρχείο είναι η βάση PDF/X. Το ISO 16612-2 §6.2.1 απαιτεί κάθε αρχείο PDF/VT-1 να είναι επίσης έγκυρο PDF/X-4 αρχείο. Το σύνολο αρχείων του PDF/VT-2, σύμφωνα με το §6.2.2, πρέπει αντίθετα να βασίζεται σε PDF/X-4p, PDF/X-5g ή PDF/X-5pg. Γι’ αυτό ένας writer PDF/VT δεν μπορεί απλώς να προσθέσει μερικά κλειδιά αναγνωριστικών: πρέπει να μεταφέρει μαζί του ολόκληρο το σύνολο δεικτών του PDF/X-4, πράγμα που σημαίνει OutputIntent, ενσωματωμένο προφίλ ICC προορισμού, τα αντίστοιχα πεδία XMP και document Info, ένα trailer /ID, και καμία κρυπτογράφηση. Αν παραλείψετε κάποιο από αυτά, έχετε ένα αρχείο που δηλώνει PDF/VT και αποτυγχάνει τη στιγμή που ένας συμβατός καταναλωτής ελέγχει τη βάση. Το PDFiumPas αντιμετωπίζει το επίπεδο PDF/X-4 ως μέρος της αποθήκευσης PDF/VT, οπότε δεν καλείτε πρώτα έναν ξεχωριστό SaveAsPdfX πρώτα· ο injector γράφει και τα δύο επίπεδα σε ένα πέρασμα
Αποθήκευση αρχείου με το SaveAsPdfVT
Η ελάχιστη κλήση δεν χρειάζεται τίποτε πέρα από ένα ενεργό έγγραφο, επειδή TPdfVTSaveOptions.Default παρέχει ένα ενσωματωμένο προφίλ ICC sRGB και συμμόρφωση pvc1. Η αποθήκευση εκτελεί εσωτερικά τρία βήματα: αφαιρεί κάθε ασφάλεια (η εισαγωγή απλού κειμένου σε κρυπτογραφημένο object stream θα αλλοίωνε το αρχείο), γεφυρώνει το υπάρχον λεξικό Info και το trailer /ID του εγγράφου προς το σύνολο δεικτών ώστε οι τιμές XMP και Info να συμφωνούν, και έπειτα προσθέτει τα αντικείμενα PDF/X-4 και PDF/VT μέσω μιας σταδιακής ενημέρωσης
var
Pdf: TPdf;
begin
Pdf := TPdf.Create(nil);
try
if Pdf.LoadFromFile('statements-merged.pdf') then
begin
// Default options: built-in sRGB OutputIntent, PDF/VT-1, synthesised DPart
if Pdf.SaveAsPdfVT('statements-pdfvt.pdf') then
Writeln('PDF/VT-1 written')
else
Writeln('Save failed (document not active?)');
end;
finally
Pdf.Free;
end;
end;
Για πραγματική παραγωγική έξοδο, σχεδόν πάντα θέλετε να αντικαταστήσετε το OutputIntent με τη χαρακτηριστική διατύπωση του πιεστηρίου σας, όχι με το γενικό εφεδρικό sRGB. Δώστε τα bytes του ICC και τα αναγνωριστικά συνθήκης μέσω TPdfVTSaveOptions:
var
Pdf: TPdf;
Opt: TPdfVTSaveOptions;
Icc: TBytes;
begin
Pdf := TPdf.Create(nil);
try
Pdf.LoadFromFile('directmail-merged.pdf');
Icc := LoadIccProfile('GRACoL2013_CRPC6.icc'); // your own loader
Opt := TPdfVTSaveOptions.Default;
Opt.Conformance := pvc1; // pvc2 is normalised to pvc1 on write
Opt.IccProfileData := Icc;
Opt.OutputConditionIdentifier := 'CGATS21_CRPC6';
Opt.OutputCondition := 'Commercial print, coated, CRPC6';
Opt.RegistryName := 'http://www.color.org';
Opt.Title := 'Spring 2026 Direct Mail Run';
Opt.Trapped := ptvFalse; // PDF/X Info /Trapped state
Pdf.SaveAsPdfVT('directmail-pdfvt.pdf', Opt);
finally
Pdf.Free;
end;
end;
Μια λεπτομέρεια σε αυτό το απόσπασμα είναι σκόπιμος φραγμός και όχι περιορισμός με τον οποίο μπορείτε να διαφωνήσετε. Η ρύθμιση Opt.Conformance := pvc2 δεν παράγει αρχείο PDF/VT-2. Ο writer κανονικοποιεί κάθε αίτημα που δεν είναι pvc1 πίσω σε pvc1, επειδή το PDF/VT-2 είναι μορφότυπο συνόλου αρχείων και ένας writer για μεμονωμένο αρχείο που προσθέτει ένα μόνο output document δεν μπορεί φυσικά να συγκροτήσει το εξωτερικό σύνολο πόρων που απαιτεί το §6.2.2. Η τιμή pvc2 υπάρχει για τη διαδρομή ανάγνωσης, ώστε το ValidatePdfVT να μπορεί να αναγνωρίσει και να αναφέρει ένα υπάρχον έγγραφο συνόλου αρχείων· δεν είναι στόχος εγγραφής
Η ιεραρχία DPart: η δομή που πραγματικά διαβάζει το RIP
Η καρδιά του PDF/VT είναι η ιεραρχία Document Part (DPart). Είναι αυτό που επιτρέπει σε μια πρέσα να τμηματοποιήσει μια μεγάλη εκτύπωση σε εγγραφές, να ομαδοποιήσει τις εγγραφές σε παραλήπτες ή δέματα αλληλογραφίας και να επισυνάψει Document Part Metadata ώστε ο downstream εξοπλισμός να μπορεί να δρομολογήσει και να χρεώσει κάθε κομμάτι. Το ISO 16612-2 §6.5 περιγράφει την καλωδίωση: ο κατάλογος φέρει ένα /DPartRoot, ο ριζικός κόμβος DPart φέρει /DPartRootNode και ένα /NodeNameList που ονομάζει κάθε επίπεδο της ιεραρχίας, τα leaf DParts καλύπτουν εύρη του δέντρου σελίδων, και κάθε σελίδα που ανήκει σε ένα μέρος δείχνει πίσω στο φύλλο της μέσω μιας καταχώρισης /DPart σε επίπεδο σελίδας
Όταν το έγγραφο προέλευσης περιέχει ήδη μια χρήσιμη ιεραρχία, SaveAsPdfVT τη διατηρεί. Όταν δεν υπάρχει, ο writer συνθέτει μια ελάχιστη: ένα μοναδικό DPart σε επίπεδο εγγράφου που καλύπτει το τρέχον δέντρο σελίδων με τη σειρά, με ένα /DPart αντίστροφη αναφορά προσαρτημένη σε κάθε ενεργό αντικείμενο σελίδας και ένα επίπεδο /NodeNameList [/Document]. Να είστε ειλικρινείς με τον εαυτό σας σχετικά με το τι είναι αυτό το ελάχιστο δέντρο. Είναι ένα δομικό αγκύριο που ικανοποιεί τις απαιτήσεις σχήματος του §6.5· δεν είναι επιχειρησιακά μεταδεδομένα. Δεν μπορεί να εφεύρει παραλήπτες, όρια δεμάτων αλληλογραφίας ή παρτίδες προϊόντων, επειδή αυτές οι πληροφορίες δεν υπήρχαν ποτέ στην πηγή. Αν έχετε δεδομένα ανά παραλήπτη, αναμένεται να χτίσετε μόνοι σας ένα βαθύτερο δέντρο DPart και να επεκτείνετε το /NodeNameList ώστε να ταιριάζει με τα επίπεδα που δημιουργείτε
Επικύρωση που πάει πέρα από την απλή ύπαρξη κλειδιών
ValidatePdfVT επιστρέφει μια TPdfVTValidationResult εγγραφή με τρία πράγματα: το ανιχνευμένο Conformance, ένα σύνολο από Issues, και μια IsCompliant βοηθητική συνάρτηση που είναι true μόνο όταν η συμμόρφωση είναι πραγματικό επίπεδο και το σύνολο προβλημάτων είναι κενό. Η απαρίθμηση των προβλημάτων είναι σκόπιμα συγκεκριμένη, ώστε ένα αποτυχημένο αποτέλεσμα να σας λέει ποια ρήτρα παραλείψατε αντί απλώς να εμφανίζει "invalid":
var
Pdf: TPdf;
Res: TPdfVTValidationResult;
begin
Pdf := TPdf.Create(nil);
try
Pdf.LoadFromFile('statements-pdfvt.pdf');
Res := Pdf.ValidatePdfVT;
if Res.IsCompliant then
Writeln('PDF/VT compliant: ', VTLevelName(Res.Conformance))
else
begin
if pvviMissingDPartRoot in Res.Issues then
Writeln('DPart hierarchy missing or unusable');
if pvviMissingPdfXIdentifier in Res.Issues then
Writeln('PDF/X-4 base identifier absent');
if pvviMissingOutputIntent in Res.Issues then
Writeln('OutputIntent / ICC profile missing');
if pvviEncryptionPresent in Res.Issues then
Writeln('Encrypted - PDF/X forbids this');
end;
finally
Pdf.Free;
end;
end;
Οι δύο έλεγχοι που αξίζει να κατανοήσετε σε βάθος είναι η αντιστοίχιση συμμόρφωσης και η διαδρομή DPart, επειδή και οι δύο παλιότερα ήταν υπερβολικά επιεικείς και σφίχτηκαν ώστε να ταιριάζουν με τις προδιαγραφές. Στην πλευρά της αντιστοίχισης, ο validator κάνει ακριβή ταύτιση, όχι το "ό,τι PDF/X να 'ναι": ένα αρχείο PDF/VT-1 γίνεται δεκτό μόνο σε μια PDF/X-4 βάση, και ένα αρχείο PDF/VT-2 μόνο σε PDF/X-4p, PDF/X-5g, ή PDF/X-5pg. Ένας δείκτης PDF/VT-1 πάνω σε βάση PDF/X-1a καταγράφεται, δεν περνάει αβρόχοις ποσί
Η διαδρομή DPart είναι το σημείο όπου βρίσκεται το μεγαλύτερο μέρος της αυστηρότητας. Δεν αρκεί ο κατάλογος να έχει ένα /DPartRoot κλειδί, γιατί ακόμη κι ένα παραποιημένο κενό αντικείμενο ή ένα χωρίς συνδέσμους σελίδων δεν μπορεί να καταναλωθεί. HasValidDPartHierarchy και η αναδρομική ValidateDPartNode διατρέχουν ολόκληρη τη δομή: ακολουθούν τους γονικούς συνδέσμους, απορρίπτουν διπλά θυγατρικά στοιχεία και κύκλους, επιβάλλουν ότι /Start και /DParts είναι αμοιβαία αποκλειόμενα, και απαιτούν τα leaf page ranges να καλύπτουν το δέντρο σελίδων σε σειρά βάθους πρώτης αναζήτησης, με το /DPart να δείχνει στο φύλλο που το περιέχει. Όλα αυτά τα εσωτερικά σφάλματα συμπτύσσονται στο μοναδικό pvviMissingDPartRoot bit προβλήματος αντί να επεκτείνουν το δημόσιο enum, οπότε αντιμετωπίστε αυτή τη μία σημαία ως «η ιεραρχία DPart είναι μη χρησιμοποιήσιμη», όχι κυριολεκτικά ως «λείπει το root key»
Τρεις συντακτικές παγίδες που ο validator επιβάλλει πλέον
Οι διαδοχικοί έλεγχοι πάνω στον Πίνακα 4 του §6.5 αποκάλυψαν μορφές που παλιότερες εκδόσεις αποδέχονταν αλλά το πρότυπο όχι. Αυτά είναι τα είδη λαθών που κάνει εύκολα ένα χειροποίητο δέντρο DPart, οπότε αξίζει να τα επισημάνουμε ρητά:
/DPartsείναι πίνακας πινάκων, όχι επίπεδος πίνακας. Κάθε στοιχείο του εξωτερικού πίνακα πρέπει να είναι και το ίδιο πίνακας έμμεσης αναφοράς. Ένας επίπεδος/DParts [9 0 R]απορρίπτεται· η συμβατή μορφή είναι/DParts [[9 0 R] [10 0 R]]. Αυτό εμποδίζει μια μη ιεραρχική δομή να παριστάνει ένα έγκυρο επίπεδο/Endσηματοδοτεί μόνο ένα πραγματικό εύρος πολλαπλών σελίδων. Ένα leaf DPart μπορεί να φέρει/Endμόνο όταν έχει επίσης/Start, και το/Endπρέπει να έρχεται αργότερα από το/Startστη σειρά του δέντρου σελίδων. Ένα εκφυλισμένο/Start 3 0 R /End 3 0 Rτώρα κάνει την ιεραρχία μη χρησιμοποιήσιμη αντί να αντιμετωπίζεται ως τμήμα μίας σελίδας/NodeNameListτα ονόματα πρέπει να επιβιώνουν από το PDF name unescaping ως XML NMTOKENs. Ένα όνομα όπως/Bad#20Nameεπεκτείνεται σε ένα που περιέχει κενό, το οποίο δεν είναι έγκυρο token. Η υλοποίηση κάνει έναν ελαφρύ έλεγχο ASCII (γράμματα, ψηφία,.,-,_,:, καθώς και bytes εκτός ASCII) που εντοπίζει λάθη σε κενά και διαχωριστικά χωρίς να απορρίπτει νόμιμα τοπικοποιημένα ή ειδικά για προμηθευτή ονόματα
Σήμανση XMP: δύο τρόποι για να γραφτεί η ίδια ιδιότητα
Η ταυτοποίηση PDF/VT βρίσκεται στο XMP κάτω από το pdfvtid namespace, συγκεκριμένα GTS_PDFVTVersion και GTS_PDFVTModDate, μαζί με το πρότυπο xmp:CreateDate και xmp:ModifyDate. Μια λεπτομέρεια που προκαλεί ψευδείς αναφορές "missing" σε αφελείς αναγνώστες είναι ότι οποιοδήποτε από αυτά μπορεί να σειριοποιηθεί με δύο τρόπους: ως κείμενο στοιχείου (<pdfvtid:GTS_PDFVTVersion>PDF/VT-1</pdfvtid:GTS_PDFVTVersion>) ή ως χαρακτηριστικό RDF στο στοιχείο περιγραφής. Το PDFiumPas διαβάζει και τις δύο μορφές, οπότε ένα αρχείο που γράφτηκε από άλλο εργαλείο σε μορφή χαρακτηριστικού δεν τιμωρείται. Επίσης επιβάλλει τον κανόνα συνέπειας του §6.3 ότι GTS_PDFVTModDate πρέπει να ισούται με xmp:ModifyDate; μια ασυμφωνία προκαλεί το pvviModDateMismatch
Ένας ακόμη κανόνας από την ίδια παράγραφο: μια άγνωστη GTS_PDFVTVersion τιμή διατηρείται ως pvcUnknown αντί να επαναχαρτογραφείται σε pvcNone. Αυτή η διάκριση έχει σημασία στην πράξη. pvcNone σημαίνει "καμία σήμανση PDF/VT, ένα συνηθισμένο PDF," ενώ pvcUnknown σημαίνει "κάτι έχει σφραγίσει μια έκδοση που αυτός ο επικυρωτής δεν αναγνωρίζει" (η PDF/VT-2s περίπτωση ανάμεσά τους). Η σύγχυση των δύο θα έκρυβε ένα αλλοιωμένο αρχείο μέσα στο ίδιο καλάθι με ένα απλό έγγραφο
Πού τελειώνει η εγγύηση
Αξίζει να είμαστε ακριβείς για το όριο όσων υπόσχονται αυτές οι μέθοδοι, επειδή η συμμόρφωση στην εκτύπωση μεταβλητών δεδομένων συνδέεται με πραγματικό κόστος. Οι έλεγχοι DPart και pairing είναι δομική επικύρωση σε επίπεδο byte. Επιβεβαιώνουν ότι ο σκελετός βελτιστοποίησης, οι βασικοί δείκτες PDF/X-4, το OutputIntent και το XMP υπάρχουν και είναι εσωτερικά συνεπή. Δεν αποτελούν preflight PDF/X-4 σε επίπεδο περιεχομένου: δεν ελέγχουν αν κάθε χρώμα βρίσκεται μέσα στη δηλωμένη συνθήκη εξόδου, αν όλες οι γραμματοσειρές είναι ενσωματωμένες ή αν δεν πέρασε κάποιο απαγορευμένο edge case ανάμειξης διαφάνειας. Για μια εργασία που θα στείλεις σε πιεστήριο συμβολαίου, συνδύασε τη δομική επικύρωση του PDFiumPas με μια ειδική μηχανή preflight PDF/X και μια δοκιμαστική εκτύπωση, όπως θα έκανες σε κάθε άλλη δήλωση συμμόρφωσης. Το δομικό επίπεδο πιάνει τις αστοχίες που σπάνε αθόρυβα το RIP caching· είναι το ένα μισό ενός πλήρους ελέγχου, όχι το σύνολο
Αν ενσωματώνεις αυτούς τους ελέγχους σε ένα ευρύτερο στάδιο κυκλοφορίας, η ίδια προσέγγιση σάρωσης σε επίπεδο byte στηρίζει και το υπόλοιπο έργο προτύπων της βιβλιοθήκης, όπως την επικύρωση object και cross-reference streams πριν ένα αρχείο φτάσει ποτέ στο preflight, και την πειθαρχία κοινόχρηστων αντικειμένων πίσω από επαναχρησιμοποιήσιμες σφραγίδες σελίδας με Form XObjects που κάνουν ένα έγγραφο φιλικό προς το RIP από την αρχή. Τα APIs αποθήκευσης και επικύρωσης PDF/VT και PDF/X που περιγράφονται εδώ αποτελούν μέρος του PDFium VCL component για Delphi και C++Builder, της οποίας η σελίδα προϊόντος περιέχει την πλήρη αναφορά συμμόρφωσης