Έχετε δέκα χιλιάδες συμβατικά PDFs από δώδεκα διαφορετικούς δημιουργούς και η νομική ομάδα θέλει καθένα από αυτά να φέρει τον σωστό Author, ένα διορθωμένο Producerσυμβολοσειρά και μια λειτουργία ανάγνωσης που ανοίγει τον πίνακα σελιδοδεικτών κατά την εκκίνηση. Η αφελής λύση είναι να φορτώσετε κάθε αρχείο, να αναδιατάξετε τις σελίδες και να γράψετε ένα νέο έγγραφο. Με αυτόν τον τρόπο πετάτε αμέσως κάθε υπάρχον αριθμό αντικειμένου, το ιστορικό των σταδιακών ενημερώσεων, κάθε ψηφιακή υπογραφή και το προσεκτικά ρυθμισμένο xref που παρήγαγε το αρχικό εργαλείο. Οι σελίδες μοιάζουν ίδιες και το αρχείο, δομικά, είναι ξένο. Για μια επεξεργασία μεταδεδομένων, αυτό είναι εντελώς λάθος αντάλλαγμα
Η σωστή κίνηση είναι να αντιμετωπίσετε το φορτωμένο έγγραφο ως γράφο αντικειμένων που τροποποιείτε επιτόπου: μπείτε στο λεξικό Info, στο /Metadatastream και το Catalog, αλλάξτε τις λίγες εγγραφές που σας ενδιαφέρουν και γράψτε το αποτέλεσμα πίσω. Το HotPDF, το εγγενές στοιχείο VCL PDF για Delphi και C++Builder, εκθέτει ακριβώς αυτό το επίπεδο μέσω του API εγγραφής για φορτωμένα έγγραφα. Αυτό το άρθρο αφορά τη σωστή χρήση του, και το ένα λάθος που κάνει σχεδόν όλοι: επεξεργάζονται το λεξικό Info και ξεχνούν ότι ένα δεύτερο αντίγραφο των ίδιων μεταδεδομένων ζει στο XMP
Δύο σημεία αποθηκεύουν τα ίδια μεταδεδομένα και διαφωνούν
Το PDF διατηρεί τις πληροφορίες του εγγράφου σε δύο παράλληλες τοποθεσίες, και αυτό είναι η ρίζα των περισσότερων αιτημάτων τύπου «άλλαξα τον τίτλο αλλά το Acrobat εξακολουθεί να δείχνει τον παλιό». Η πρώτη είναι το λεξικό πληροφοριών του εγγράφου, το κλασικό /Info αντικείμενο με /Title, /Author, /Subject, /Keywords, /Creator, και /Producer κλειδιά, όπως ορίζεται στο ISO 32000-1 §14.3.3. Το δεύτερο είναι ένα πακέτο XMP, ένα έγγραφο XML αποθηκευμένο ως stream που κρέμεται από το Catalog κάτω από /Metadata, όπως ορίζεται στο §14.3.2 και βασίζεται στο μοντέλο δεδομένων Adobe XMP
Και τα δύο μπορούν να περιέχουν έναν τίτλο. Τίποτα στην προδιαγραφή δεν τα υποχρεώνει να συμφωνούν. Τα σύγχρονα προγράμματα προβολής και οι περισσότεροι επικυρωτές PDF/A προτιμούν το πακέτο XMP όταν υπάρχει και επιστρέφουν στο λεξικό Info όταν δεν υπάρχει. Άρα αν ενημερώσετε μόνο το /Info - που είναι ακριβώς ό,τι κάνει η μεγάλη πλειονότητα του κώδικα «set PDF metadata» - ένας αναγνώστης που εμπιστεύεται το XMP θα συνεχίσει να εμφανίζει την παλιά τιμή και ένας ελεγκτής PDF/A θα επισημάνει την ασυμφωνία. Η σωστή ενέργεια σε οποιοδήποτε αρχείο διαθέτει ήδη πακέτο XMP είναι διπλή εγγραφή: αλλάξτε την καταχώριση Info και και αναγεννήστε το XMP, ώστε τα δύο να παραμένουν συνεπή. Το HotPDF σάς δίνει και τα δύο μισά. Η πειθαρχία να τα χρησιμοποιείτε μαζί είναι δική σας
Επεξεργασία του λεξικού Info
Τα βοηθητικά της πλευράς Info είναι λιτά και προβλέψιμα. SetLoadedTitle, SetLoadedAuthor, SetLoadedSubject, SetLoadedKeywords, SetLoadedCreator, και SetLoadedProducer το καθένα δέχεται μία AnsiString και γράφει το αντίστοιχο κλειδί στο φορτωμένο λεξικό Info, αντικαθιστώντας την τιμή αν το κλειδί υπάρχει και προσθέτοντάς την αν δεν υπάρχει. Για να αφαιρέσετε εντελώς ένα κλειδί - ας πούμε έναν διαρρέοντα /Creator που ονομάζει τα εσωτερικά σας εργαλεία - καλέστε RemoveLoadedInfoKey με το σκέτο όνομα του κλειδιού. Κανένα από αυτά δεν αγγίζει το XMP, λειτουργούν αποκλειστικά πάνω στο /Info αντικείμενο που LoadFromFile εντόπισε όταν ανέλυσε το αρχείο
var
Pdf: THotPDF;
begin
Pdf := THotPDF.Create(nil);
try
if Pdf.LoadFromFile('contract-in.pdf', '') > 0 then
begin
Pdf.SetLoadedTitle('Master Services Agreement 2026');
Pdf.SetLoadedAuthor('Legal Department');
Pdf.SetLoadedSubject('Executed contract, retention 7 years');
Pdf.SetLoadedKeywords('contract; MSA; 2026; executed');
Pdf.SetLoadedProducer('Acme Document Pipeline');
Pdf.RemoveLoadedInfoKey('Creator'); // drop the originating tool name
Pdf.SaveLoadedDocument('contract-out.pdf');
end;
finally
Pdf.Free;
end;
end;
Μια λεπτομέρεια που αξίζει να προσέξετε: αυτά δέχονται AnsiString συμβολοσειρές. Για τίτλους ASCII αυτό δεν είναι πρόβλημα, αλλά οι συμβολοσειρές κειμένου PDF που χρειάζονται μη λατινικούς χαρακτήρες πρέπει να κωδικοποιηθούν όπως απαιτεί η προδιαγραφή - UTF-16BE με σήμανση σειράς byte, ή PDFDocEncoding - πριν τις παραδώσετε. Η βιβλιοθήκη γράφει τα bytes που της δίνετε μέσα σε ένα αντικείμενο συμβολοσειράς. Δεν μαντεύει κωδικοποίηση για εσάς. Αν οι τίτλοι σας είναι απλά αγγλικά, αγνοήστε το. Αν περιέχουν τονισμένους ή χαρακτήρες CJK, κωδικοποιήστε τους συνειδητά και δοκιμάστε τους σε πραγματικό πρόγραμμα προβολής
Επανεγγραφή του πακέτου XMP
είναι το άλλο μισό της διπλής εγγραφής. Δώστε του ολόκληρο το XMP packet ως ένα SetLoadedXMPMetadata και κάνει ένα από δύο πράγματα: αν το Catalog ήδη παραπέμπει σε ένα AnsiString stream, αντικαθιστά επιτόπου το περιεχόμενο εκείνου του stream, κρατώντας τον ίδιο αριθμό αντικειμένου· αν δεν υπάρχει metadata stream, δημιουργεί ένα, το επισημαίνει ως /Metadata και /Type /Metadata, του αποδίδει αριθμό αντικειμένου και το συνδέει από το Catalog. Όπως και να έχει, καταλήγετε με ένα έγκυρο metadata object που οι viewers θα διαβάσουν./Subtype /XMLΕσείς δίνετε το XML, που σημαίνει ότι ελέγχετε το schema
, dc:title, dc:creator, και ούτω καθεξής. Αυτή είναι ταυτόχρονα δύναμη και ευθύνη: η βιβλιοθήκη δεν κάνει parse ούτε validate το packet σας, και γράφει τα bytes uncompressed, χωρίς να εφαρμόσει stream filter. Ένα malformed packet θα περάσει άνετα από την κλήση και θα εμφανιστεί αργότερα ως παράπονο για broken-metadata. Φτιάξτε προσεκτικά το XML και αντικατοπτρίστε ακριβώς τις τιμές που γράψατε στο Info dictionary, ώστε οι δύο όψεις να μην έρχονται ποτέ σε αντίφαση.xmp:CreatorToolΑυτή η σειρά, Info πρώτα, XMP δεύτερο, και μετά save, είναι το μοτίβο που πρέπει να αφομοιώσετε. Οι δύο κλήσεις είναι ανεξάρτητες, η συνέπεια υπάρχει μόνο επειδή τους δώσατε τα ίδια strings. Αν παραλείψετε την κλήση XMP σε ένα αρχείο που έχει XMP packet, επιστρέφετε στο bug της αθόρυβης παλαιότητας που όλη αυτή η ενότητα υπάρχει για να αποτρέψει
const
XMP_TEMPLATE =
'<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?>' +
'<x:xmpmeta xmlns:x="adobe:ns:meta/">' +
'<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">' +
'<rdf:Description rdf:about="" xmlns:dc="http://purl.org/dc/elements/1.1/">' +
'<dc:title><rdf:Alt><rdf:li xml:lang="x-default">%s</rdf:li></rdf:Alt></dc:title>' +
'<dc:creator><rdf:Seq><rdf:li>%s</rdf:li></rdf:Seq></dc:creator>' +
'</rdf:Description></rdf:RDF></x:xmpmeta><?xpacket end="w"?>';
begin
// After setting the Info dictionary, mirror the same values into XMP:
Pdf.SetLoadedTitle('Master Services Agreement 2026');
Pdf.SetLoadedAuthor('Legal Department');
Pdf.SetLoadedXMPMetadata(
AnsiString(Format(XMP_TEMPLATE,
['Master Services Agreement 2026', 'Legal Department'])));
Pdf.SaveLoadedDocument('contract-out.pdf');
end;
Τα metadata ζουν σε δύο σημεία, στο Info dictionary και στο XMP stream, συν τα Catalog-level reading hints και το outline tree. Μια in-place επεξεργασία αγγίζει το καθένα χωρίς να ξαναχτίσει το έγγραφο

Τρεις Catalog εγγραφές αποφασίζουν τι βλέπει ο αναγνώστης τη στιγμή που ανοίγει το έγγραφο, και οι τρεις είναι μονογραμμικές αλλαγές στο φορτωμένο graph
γράφει SetLoadedPageMode ως name object: περάστε /PageMode για να εμφανιστεί το bookmark panel, 'UseOutlines' για το thumbnail rail, 'UseThumbs' για presentation mode, ή 'FullScreen' για να εμφανιστεί το attachments pane (ISO 32000-1 §7.7.3.1, Table 28). 'UseAttachments' γράφει SetLoadedPageLayout το /PageLayout, 'SinglePage', 'OneColumn', και τα υπόλοιπα με τον ίδιο τρόπο. Και τα δύο δέχονται το name χωρίς leading slash, και η βιβλιοθήκη το προσθέτει κατά την έξοδο.'TwoColumnLeft' γράφει την Catalog
εγγραφή, την ετικέτα φυσικής γλώσσας για το σύνολο του εγγράφου SetLoadedLanguage, /Lang, ένα BCP 47 tag. Προσέξτε τη διαφορά τύπου που μπερδεύει πολλούς: 'en-US' και 'de-DE' είναι PDF /PageModename/PageLayout objects, ενώ είναι μια string/Lang. Το HotPDF το χειρίζεται σωστά εσωτερικά, αλλά αν ποτέ εξετάσετε το output θα δείτε απέναντι σε , και τώρα ξέρετε γιατί. Η /PageMode /UseOutlines εγγραφή έχει μεγαλύτερη σημασία από όσο φαίνεται: είναι αυτό που διαβάζει η assistive technology για να επιλέξει προφορά, και είναι αυστηρή απαίτηση για τη συμμόρφωση με το PDF/UA accessibility./Lang (en-US)Μετονομασία bookmarks χωρίς να διαταραχθεί το tree/LangΟι τίτλοι των bookmarks είναι συνηθισμένος καθαρισμός, ένα τυπογραφικό σε μια επικεφαλίδα, μια ενότητα που μετονομάστηκε αφού χτίστηκε το outline
if Pdf.LoadFromFile('handbook.pdf', '') > 0 then
begin
Pdf.SetLoadedPageMode('UseOutlines'); // /PageMode, a name
Pdf.SetLoadedPageLayout('TwoColumnLeft'); // /PageLayout, a name
Pdf.SetLoadedLanguage('en-US'); // /Lang, a string
Pdf.SaveLoadedDocument('handbook-tagged.pdf');
end;
παίρνει έναν δείκτη με βάση το μηδέν στις
καταχωρίσεις ανώτατου επιπέδουSetLoadedOutlineTitle του outline και έναν νέο τίτλο, διατρέχει την αλυσίδα Catalog → /Outlines → /First → /Next αλυσίδα μέχρι εκείνη τη θέση και αντικαθιστά το /Title συμβολοσειρά. Αλλάζει μόνο τον τίτλο, ενώ ο προορισμός, η κατάσταση ανοιχτό/κλειστό και η δομή των παιδιών μένουν ανέπαφα
if Pdf.LoadFromFile('report.pdf', '') > 0 then
begin
Pdf.SetLoadedOutlineTitle(0, 'Executive Summary');
Pdf.SetLoadedOutlineTitle(1, 'Financial Results');
Pdf.SaveLoadedDocument('report-renamed.pdf');
end;
Η μετονομασία είναι ασφαλής ακριβώς επειδή δεν αγγίζει ποτέ τους δομικούς μετρητές. Η διαγραφή μιας καταχώρισης outline είναι η δύσκολη περίπτωση, και αξίζει να την κατανοήσεις ακόμη κι όταν απλώς μετονομάζεις, επειδή σου δείχνει τι δεν πρέπει να επεξεργάζεσαι χειροκίνητα. Κάθε κόμβος outline φέρει ένα count, και, σύμφωνα με το ISO 32000-1 §12.3.3, αυτό το count δεν είναι ο αριθμός των άμεσων παιδιών. Είναι το συνολικό πλήθος των /Countορατών απογόνων: μια θετική τιμή N σημαίνει ότι οι N απόγονοι είναι αυτή τη στιγμή ορατοί, ενώ μια αρνητική τιμή σημαίνει ότι ο κόμβος έχει απογόνους αλλά είναι συμπτυγμένος. Όταν αφαιρείται μια καταχώριση ανώτατου επιπέδου, το /Count count της ρίζας δεν μπορεί απλώς να μειωθεί κατά ένα· πρέπει να υπολογιστεί ξανά αθροίζοντας, για κάθε επιζώντα κόμβο ανώτατου επιπέδου, «ένα για τον ίδιο τον κόμβο συν το θετικό του count /Outlines», παραλείποντας τους απογόνους κάθε συμπτυγμένου κόμβου (με αρνητικό count)./Count Αν το κάνεις λάθος, το συνολικό πλήθος των σελιδοδεικτών που εμφανίζει ένας αναγνώστης αποκλίνει, καθώς αυξομειώνεται κατά περισσότερο από ένα σε κάθε διαγραφή. Η μετονομασία παρακάμπτει όλα αυτά, κάτι που είναι άλλος ένας λόγος να προτιμάς τον στοχευμένο βοηθό αντί να πειράζεις μόνος σου το dictionary
Πώς μένει στη θέση της η αποθήκευση
Κάθε επεξεργασία πιο πάνω τροποποιεί αντικείμενα στη μνήμη· τίποτε δεν φτάνει στον δίσκο μέχρι SaveLoadedDocument εκτελείται. Ο λόγος που αυτή η προσέγγιση είναι φτηνή είναι ότι η αποθήκευση δεν αναδημιουργεί το έγγραφο, διατηρεί τους υπάρχοντες αριθμούς αντικειμένων και τη δομή που το HotPDF ανέλυσε κατά τη φόρτωση, γράφοντας πίσω το ίδιο γράφημα με τα λίγα αντικείμενα που άλλαξαν και όσα μόλις δεσμεύτηκαν. Αυτό είναι που κρατά μια διέλευση μεταδεδομένων από το να ξαναγράψει ολόκληρο το αρχείο, και είναι ο ίδιος μηχανισμός ενημέρωσης επιτόπου που κάνει να λειτουργούν ροές αντικειμένων και σταδιακές ενημερώσεις λειτουργούν. Αν τα αρχεία προέλευσής σου προέρχονται από το Word ή από άλλη σουίτα γραφείου, η διάταξη των αντικειμένων τους έχει τις δικές της ιδιομορφίες που αξίζει να γνωρίζεις πριν τα επεξεργαστείς· το άρθρο για τις υβριδικές ροές διασταυρούμενων αναφορών σε Office PDFs καλύπτει πώς είναι δομημένα αυτά τα αρχεία και τι επιβιώνει σε round trip
Δύο όρια που αξίζει να τηρείς. Πρώτον, αυτό είναι μοντέλο επεξεργασίας επιτόπου, όχι εργαλείο redaction ή sanitization: η αφαίρεση ενός κλειδιού Info αφαιρεί αυτό το κλειδί, αλλά δεν καθαρίζει παλαιότερες τιμές που μπορεί να παραμείνουν σε προηγούμενη γενιά incremental-update του ίδιου αρχείου. Αν η απαίτησή σου είναι η πραγματική αφαίρεση ευαίσθητων μεταδεδομένων, αυτό είναι διαφορετική, βαρύτερη λειτουργία. Δεύτερον, η εγγραφή XMP είναι κυριολεκτική, η βιβλιοθήκη εμπιστεύεται το XML σου και δεν το επικυρώνει, οπότε για οτιδήποτε προορίζεται για PDF/A ή για αυστηρό validator, δημιούργησε το packet από γνωστό καλό template και επαλήθευσε το αποτέλεσμα. Όταν χρησιμοποιείται μέσα σε αυτά τα όρια, η επιτόπου επεξεργασία μεταδεδομένων είναι το κατάλληλο εργαλείο, διορθώνει τα λίγα bytes που είναι λάθος και αφήνει ακριβώς όπως τα έγραψε ο αρχικός παραγωγός το 99 τοις εκατό του αρχείου που ήταν ήδη σωστό
Το API εγγραφής για φορτωμένο έγγραφο που παρουσιάζεται εδώ διατίθεται μαζί με το τυπικό HotPDF Component για Delphi και C++Builder, μαζί με το πλήρες σύνολο μεθόδων επεξεργασίας μεταδεδομένων, outline και Catalog