Máte desaťtisíc zmluvných PDF z tucta rôznych generátorov a právne oddelenie chce, aby každý z nich niesol správny Author, opravený Producer reťazec a režim čítania, ktorý po spustení otvorí panel záložiek. Naivné riešenie je každý súbor načítať, znovu vyskladať strany a zapísať nový dokument. Keď to urobíte, práve ste zahodili všetky existujúce čísla objektov, históriu priebežných aktualizácií, každý digitálny podpis a starostlivo doladený xref, ktorý vytvoril pôvodný nástroj. Strany vyzerajú rovnako, ale súbor je štrukturálne cudzí. Pri úprave metadát je to úplne nesprávna voľba
Správny krok je považovať načítaný dokument za graf objektov, ktorý meníte na mieste: siahnite do slovníka Info, do /Metadata streamu a do Katalógu, zmeňte niekoľko položiek, ktoré vás zaujímajú, a zapíšte výsledok späť. HotPDF, natívny VCL PDF komponent pre Delphi a C++Builder, ponúka presne túto možnosť prostredníctvom svojho API na zápis do načítaného dokumentu. Tento článok je o tom, ako ho používať správne, a o jednej chybe, ktorú robí takmer každý: upraví slovník Info a zabudne, že druhá kópia tých istých metadát žije v XMP
Dve miesta uchovávajú tie isté metadáta a nezhodujú sa
PDF uchováva informácie o dokumente na dvoch paralelných miestach a práve to je koreňom väčšiny hlásení typu "zmenil som názov, ale Acrobat stále zobrazuje starý". Prvým je slovník informácií o dokumente, klasický /Info objekt s /Title, /Author, /Subject, /Keywords, /Creator, a /Producer kľúčmi, definovaný v ISO 32000-1 §14.3.3. Druhým je balík XMP, XML dokument uložený ako prúd zavesený na Katalógu pod /Metadata, definovaný v §14.3.2 a postavený na dátovom modeli Adobe XMP
Obe môžu niesť názov. Špecifikácia ich nenúti súhlasiť. Moderné zobrazovače aj väčšina validátorov PDF/A uprednostnia balík XMP, keď je prítomný, a vrátia sa k slovníku Info, keď nie je. Ak teda aktualizujete iba /Info, čo je presne to, čo robí drvivá väčšina kódu na nastavovanie PDF metadát, čítač dôverujúci XMP bude ďalej zobrazovať zastaranú hodnotu a kontrolór PDF/A označí nesúlad. Správny postup pri akomkoľvek súbore, ktorý už balík XMP má, je dvojitý zápis: zmeňte položku Info a znova vygenerujte XMP, aby zostali obe verzie konzistentné. HotPDF vám dáva obe polovice; disciplína používať ich spolu je na vás
Úprava slovníka Info
Pomocné funkcie pre Info sú tenké a predvídateľné. SetLoadedTitle, SetLoadedAuthor, SetLoadedSubject, SetLoadedKeywords, SetLoadedCreator, a SetLoadedProducer každá prijíma jeden AnsiString a zapíše príslušný kľúč do načítaného slovníka Info, nahradí hodnotu, ak kľúč existuje, a pridá ju, ak nie. Ak chcete kľúč odstrániť úplne, napríklad presakujúci /Creator ktorý pomenúva vaše interné nástroje, zavolajte RemoveLoadedInfoKey s holým názvom kľúča. Žiadna z týchto funkcií sa nedotýka XMP; pracujú čisto s /Info objektom, ktorý LoadFromFile našiel pri parsovaní súboru
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;
Jeden detail, ktorý treba mať pod kontrolou: tieto funkcie prijímajú AnsiString. Pre ASCII názvy to nie je problém, ale textové reťazce PDF, ktoré potrebujú znaky mimo latinčiny, musia byť pred odovzdaním zakódované podľa špecifikácie, teda UTF-16BE s byte-order markom alebo PDFDocEncoding. Knižnica zapíše bajty, ktoré jej dáte, do string objektu; kódovanie za vás nehádže. Ak sú vaše názvy obyčajné anglické, môžete to ignorovať. Ak obsahujú diakritiku alebo CJK znaky, kódujte ich vedome a otestujte v skutočnom prehliadači
Prepísanie balíka XMP
SetLoadedXMPMetadata je druhá polovica dvojitého zápisu. Odovzdajte mu celý balík XMP ako AnsiString a urobí jednu z dvoch vecí: ak Katalóg už odkazuje na /Metadata prúd, prepíše jeho obsah na mieste a zachová rovnaké číslo objektu; ak žiadny metadátový prúd neexistuje, vytvorí ho, označí ho /Type /Metadata a /Subtype /XML, pridelí mu číslo objektu a prepojí ho z Katalógu. V každom prípade skončíte s platným metadátovým objektom, ktorý si prehliadače prečítajú
XML poskytujete vy, čo znamená, že schému riadite sami: dc:title, dc:creator, xmp:CreatorTool, a podobne. To je zároveň sila aj zodpovednosť: knižnica váš balík neparsuje ani nevaliduje a zapisuje bajty nekomprimovane, bez použitia stream filtra. Chybný balík preletí cez volanie a neskôr sa prejaví ako sťažnosť na poškodené metadáta. XML zostavte starostlivo a presne zrkadlite hodnoty, ktoré ste zapísali do slovníka Info, aby si oba pohľady nikdy neodporovali
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;
Toto poradie, najprv Info, potom XMP a nakoniec uložiť, si treba zafixovať. Obe volania sú nezávislé; konzistencia existuje len preto, že ste im podali tie isté reťazce. Vynechajte volanie XMP pri súbore, ktorý už balík XMP má, a ste späť pri tichej chybe zastaraných údajov, ktorej má celá táto časť predísť

Riadenie toho, ako sa súbor otvorí v prehliadači
Tri položky Katalógu rozhodujú o tom, čo čitateľ uvidí v okamihu otvorenia dokumentu, a všetky tri sú jednoriadkové úpravy na načítanom grafe. SetLoadedPageMode zapisuje /PageMode ako názvový objekt: zadajte 'UseOutlines' na otvorenie panela záložiek, 'UseThumbs' na panel miniatúr, 'FullScreen' pre prezentačný režim alebo 'UseAttachments' na zobrazenie panela príloh (ISO 32000-1 §7.7.3.1, tabuľka 28). SetLoadedPageLayout zapisuje /PageLayout rovnakým spôsobom - 'SinglePage', 'OneColumn', 'TwoColumnLeft' a zvyšok. Obe prijímajú meno bez úvodného lomítka; knižnica ho pridá pri výstupe
SetLoadedLanguage zapisuje položku Katalógu /Lang, prirodzenojazykovú značku pre dokument ako celok - 'en-US', 'de-DE', značku BCP 47. Všimnite si rozdiel v type, ktorý ľudí mýli: /PageMode a /PageLayout sú PDF name objekty, zatiaľ čo /Lang je string. HotPDF to vnútorne robí správne, ale ak si niekedy pozriete výstup, uvidíte /PageMode /UseOutlines oproti /Lang (en-US), a teraz viete prečo. Položka /Lang je dôležitejšia, než sa zdá: asistívne technológie z nej čítajú výslovnosť a pre zhodu s prístupnosťou PDF/UA je povinná
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;
Premenovanie záložiek bez narušenia stromu
Názvy záložiek sú bežná údržba - preklep v nadpise, kapitola prečíslovaná po zostavení osnovy. SetLoadedOutlineTitle prijíma nulový index do najvyššej úrovne položiek osnovy a nový názov, prejde reťazec Katalóg → /Outlines → /First → /Next reťazec až na danú pozíciu a nahradí reťazec položky /Title. Mení len názov; cieľ, stav otvorené/zatvorené a štruktúra potomkov zostávajú nedotknuté
if Pdf.LoadFromFile('report.pdf', '') > 0 then
begin
Pdf.SetLoadedOutlineTitle(0, 'Executive Summary');
Pdf.SetLoadedOutlineTitle(1, 'Financial Results');
Pdf.SaveLoadedDocument('report-renamed.pdf');
end;
Premenovanie je bezpečné práve preto, že sa nikdy nedotkne štrukturálnych počítadiel. Mazanie položky osnovy je ten problémový prípad a oplatí sa mu porozumieť aj vtedy, keď iba premenovávate, pretože vám povie, čo nie máte upravovať ručne. Každý uzol osnovy nesie /Count, a podľa ISO 32000-1 §12.3.3 toto číslo nie je počet priamych potomkov. Je to celkový počet viditeľných potomkov: kladná /Count hodnota N znamená, že N potomkov je práve odhalených, zatiaľ čo záporná hodnota znamená, že uzol má potomkov, ale je zbalený. Keď sa odstráni položka najvyššej úrovne, /Outlines koreňové počítadlo sa nedá jednoducho znížiť o jeden; treba ho prepočítať sčítaním za každý zostávajúci uzol najvyššej úrovne, "jeden za samotný uzol plus jeho kladný /Count," pričom sa preskočia potomkovia každého zbaleného uzla s negatívnym počtom. Ak to spravíte zle, celkový počet záložiek, ktorý čitateľ zobrazuje, sa rozchádza a pri každom zmazaní skočí o viac než jedna. Premenovanie sa tomu celému vyhne, čo je ďalší dôvod uprednostniť cielený helper pred tým, aby ste do slovníka rýpali sami
Ako sa uloženie zachováva na mieste
Každá úprava vyššie mení objekty v pamäti; na disk sa nedostane nič, kým SaveLoadedDocument sa nespustí. Dôvod, prečo je tento postup lacný, je ten, že uloženie negeneruje dokument nanovo - zachová existujúce čísla objektov a štruktúru, ktorú HotPDF načítal pri otvorení, a zapíše späť ten istý graf s vašou hŕstkou zmenených a novo pridelených objektov. To je to, čo bráni tomu, aby metadátový prechod prepísal celý súbor, a rovnaký mechanizmus in-place aktualizácie robí aj objektové streamy a prírastkové aktualizácie fungujú. Ak vaše zdrojové súbory pochádzajú z Wordu alebo iného kancelárskeho balíka, ich rozloženie objektov má vlastné zvláštnosti, ktoré sa oplatí poznať pred úpravou; článok o hybridných referenčných xref streamoch v PDF z Office pokrýva, ako sú tieto súbory štruktúrované a čo prežije po návrate
Dve hranice, ktoré treba rešpektovať. Po prvé, ide o model úprav na mieste, nie o nástroj na redakciu alebo sanitizáciu: odstránenie kľúča z Info odstráni len ten kľúč, ale nevyčistí staršie hodnoty, ktoré môžu pretrvať v predchádzajúcej generácii priebežnej aktualizácie toho istého súboru. Ak požadujete skutočné odstránenie citlivých metadát, je to iná a ťažšia operácia. Po druhé, zápis XMP je doslovný, knižnica verí vášmu XML a nevaliduje ho, takže pre čokoľvek určené pre PDF/A alebo prísny validátor vytvorte balík z overenej šablóny a overte výstup. Ak sa držíte týchto hraníc, úprava metadát na mieste je správne veľký nástroj: opraví niekoľko chybných bajtov a nechá deväťdesiatdeväť percent súboru, ktoré bolo správne už predtým, presne tak, ako ho zapísal pôvodný tvorca
Uvedené API na zápis načítaného dokumentu sa dodáva so štandardným HotPDF Component pre Delphi a C++Builder spolu s úplnou sadou metód na úpravu metadát, osnovy a Katalógu