Tehnični članak

Urejanje metapodatkov naloženega PDF-ja v Delphiju brez ponovnega zapisovanja

Imate deset tisoč pogodb v obliki PDF iz ducata različnih generatorjev, pravna služba pa želi, da ima vsaka izmed njih pravilnega Author (avtorja), popravljen niz Producer (proizvajalca) in bralni način, ki ob zagonu odpre ploščo z zaznamki. Naivna rešitev je, da naložite vsako datoteko, znova razporedite strani in zapišete nov dokument. S tem bi zavrgli vsako obstoječo številko objekta, zgodovino inkrementalnih posodobitev, morebitne digitalne podpise in skrbno prilagojeno navzkrižno referenco (xref), ki jo je ustvarilo prvotno orodje. Strani so videti enake, vendar je datoteka strukturno tujec. Za preprosto urejanje metapodatkov je to povsem napačna zamenjava

Pravilna poteza je, da naloženi dokument obravnavate kot graf objektov, ki ga spreminjate na mestu (in place): posezite v slovar Info, tok /Metadata in katalog (Catalog), spremenite tistih nekaj vnosov, ki vas zanimajo, in zapišite rezultat nazaj. HotPDF, izvorna komponenta VCL PDF za Delphi in C++Builder, ponuja natanko ta vmesnik prek svojega pisalnega API-ja za naložene dokumente. Ta članek govori o njegovi pravilni uporabi in o eni napaki, ki jo naredi skoraj vsak: urejanju slovarja Info ob pozabi, da druga kopija istih metapodatkov živi v formatu XMP

Dve mesti shranjujeta iste metapodatke, vendar se ne ujemata

Format PDF prenaša informacije o dokumentu na dveh vzporednih mestih, kar je vir večine težav z naslovom "spremenil sem naslov, vendar Acrobat še vedno prikazuje starega". Prvo mesto je slovar informacij o dokumentu, klasični objekt /Info s ključi /Title, /Author, /Subject, /Keywords, /Creator in /Producer, opredeljen v standardu ISO 32000-1 §14.3.3. Drugo mesto je paket XMP, XML dokument, shranjen kot tok, ki visi iz kataloga pod /Metadata, opredeljen v §14.3.2 in zgrajen na podatkovnem modelu Adobe XMP

Oba lahko vsebujeta naslov. Nič v specifikaciji ju ne sili k ujemanju. Sodobni pregledovalniki in večina validatorjev PDF/A imajo raje paket XMP, ko je ta prisoten, in se vrnejo na slovar Info, ko ga ni. Če torej posodobite le /Info — kar počne velika večina kode za nastavljanje metapodatkov PDF —, bo bralnik, ki zaupa XMP-ju, še naprej prikazoval zastarelo vrednost, preverjevalnik PDF/A pa bo označil neujemanje. Pravilna operacija na kateri koli datoteki, ki že ima paket XMP, je dvojno pisanje: spremenite vnos Info in ponovno generirajte XMP, da ostaneta usklajena. HotPDF vam ponuja obe polovici; doslednost njune skupne uporabe pa je na vas

Urejanje slovarja Info

Pomožne funkcije na strani Info so preproste in predvidljive. Metode SetLoadedTitle, SetLoadedAuthor, SetLoadedSubject, SetLoadedKeywords, SetLoadedCreator in SetLoadedProducer vsaka sprejmejo en AnsiString in zapišejo ustrezen ključ v naloženi slovar Info, pri čemer zamenjajo vrednost, če ključ že obstaja, ali jo dodajo, če ne. Če želite ključ v celoti odstraniti — na primer razkrit /Creator, ki poimenuje vaše interno orodje —, pokličite RemoveLoadedInfoKey z golim imenom ključa. Nobena od teh metod se ne dotika XMP-ja; delujejo izključno na objektu /Info, ki ga je LoadFromFile lociral ob razčlenjevanju datoteke

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;

Ena podrobnost, ki jo je treba upoštevati: te metode sprejemajo AnsiString. Za naslove ASCII to ne predstavlja težave, vendar morajo biti besedilni nizi PDF, ki potrebujejo ne-latinske znake, kodirani, kot zahteva specifikacija — UTF-16BE z oznako zaporedja bajtov (BOM) ali PDFDocEncoding —, preden jih posredujete. Knjižnica zapiše bajte, ki ji jih posredujete, v objekt niza; kodiranja ne ugiba namesto vas. Če so vaši naslovi v navadni angleščini, to prezrite. Če vsebujejo šumnike, naglase ali znake CJK, jih kodirajte premišljeno in preizkusite v pravem pregledovalniku

Ponovno zapisovanje paketa XMP

Metoda SetLoadedXMPMetadata predstavlja drugo polovico dvojnega pisanja. Posredujte ji celoten paket XMP kot AnsiString in izvedla bo eno od dveh možnosti: če katalog že vsebuje referenco na tok /Metadata, bo zamenjala vsebino tega toka na mestu ter ohranila isto številko objekta; če tok metapodatkov ne obstaja, ga bo ustvarila, označila kot /Type /Metadata in /Subtype /XML, dodelila številko objekta in ga povezala iz kataloga. V obeh primerih dobite veljaven objekt metapodatkov, ki ga bodo pregledovalniki lahko prebrali

Sami priskrbite XML, kar pomeni, da nadzorujete shemo — dc:title, dc:creator, xmp:CreatorTool in tako naprej. To prinaša moč in odgovornost hkrati: knjižnica ne razčlenjuje in ne potrjuje vašega paketa, bajte pa zapiše nestisnjene, brez uporabljenega filtra toka. Nepravilno oblikovan paket bo nemoteno prešel skozi klic in se kasneje pojavil kot pritožba o pokvarjenih metapodatkih. XML zgradite skrbno in natančno zrcalite vrednosti, ki ste jih zapisali v slovar Info, da si oba pogleda nikoli ne bosta nasprotovala

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;

Ta vrstni red — najprej Info, nato XMP in nato shranjevanje — je vzorec, ki ga je treba usvojiti. Klica sta neodvisna; doslednost obstaja le zato, ker ste jima posredovali enake nize. Če preskočite klic XMP na datoteki, ki ima paket XMP, se vrnete k hrošču tihe zastarelosti, za preprečevanje katerega je namenjen celoten ta razdelek

Diagram showing a PDF Info dictionary and an XMP metadata stream both holding title and author, edited in place alongside the bookmark outline tree
Metapodatki živijo na dveh mestih — v slovarju Info in toku XMP — poleg tega pa še v bralnih namigih na ravni kataloga in drevesu kazala. Urejanje na mestu vpliva na vsakega od njih brez ponovne gradnje dokumenta.

Upravljanje načina, kako pregledovalnik odpre datoteko

Trije vnosi v katalogu določajo, kaj bralec vidi v trenutku, ko se dokument odpre, in vsi trije so enovrstični popravki na naloženem grafu. Metoda SetLoadedPageMode zapiše /PageMode kot objekt imena: posredujte 'UseOutlines' za prikaz plošče z zaznamki, 'UseThumbs' za vrstico sličic, 'FullScreen' za predstavitveni način ali 'UseAttachments' za prikaz plošče s prilogami (ISO 32000-1 §7.7.3.1, tabela 28). Metoda SetLoadedPageLayout na enak način zapiše /PageLayout'SinglePage', 'OneColumn', 'TwoColumnLeft' in ostale. Obe metodi sprejmeta ime brez vodilne poševnice; knjižnica jo doda pri izhodu

Metoda SetLoadedLanguage zapiše vnos v katalogu /Lang, oznako naravnega jezika za dokument kot celoto — 'en-US', 'de-DE', oznako BCP 47. Upoštevajte razliko v vrstah objektov, ki pogosto povzroča zmedo: /PageMode in /PageLayout sta PDF objekta imena (name), medtem ko je /Lang niz (string). HotPDF to interno pravilno obravnava, če pa kdaj pregledate izhod, boste videli /PageMode /UseOutlines nasproti /Lang (en-US), in zdaj veste, zakaj. Vnos /Lang je pomembnejši, kot se zdi: to je tisto, kar berejo asistenčne tehnologije za izbiro izgovarjave, in je stroga zahteva za skladnost z dostopnostjo PDF/UA

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;

Preimenovanje zaznamkov brez motenja drevesa

Naslovi zaznamkov so del rutinskega čiščenja — tipkarska napaka v naslovu, poglavje, preštevilčeno po izgradnji kazala. Metoda SetLoadedOutlineTitle sprejme z nič indeksirani indeks med vnosi kazala na najvišji ravni in nov naslov, se sprehodi skozi verigo katalog → /Outlines/First/Next do tega mesta in zamenja niz /Title tega vnosa. Spremeni le naslov; cilj, stanje odprto/zaprto in struktura otrok ostanejo nedotaknjeni

if Pdf.LoadFromFile('report.pdf', '') > 0 then
begin
  Pdf.SetLoadedOutlineTitle(0, 'Executive Summary');
  Pdf.SetLoadedOutlineTitle(1, 'Financial Results');
  Pdf.SaveLoadedDocument('report-renamed.pdf');
end;

Kako shranjevanje ostane na mestu

Vsako zgoraj opisano urejanje spreminja objekte v pomnilniku; nič ne doseže diska, dokler se ne zažene SaveLoadedDocument. Razlog, zakaj je ta pristop povsem naraven, je v tem, da shranjevanje ne regenerira dokumenta — ohrani obstoječe številke objektov in strukturo, ki jo je HotPDF razčlenil ob nalaganju, ter zapiše nazaj enak graf z vašo peščico spremenjenih in na novo dodeljenih objektov. To je tisto, kar preprečuje, da bi prehod metapodatkov prepisal celotno datoteko, in to je enak mehanizem posodabljanja na mestu, ki omogoča delovanje tokov objektov in inkrementalnih posodobitev. Če vaše izvorne datoteke prihajajo iz programa Word ali druge pisarniške zbirke, ima njihova razporeditev objektov svoje posebnosti, ki jih je vredno poznati pred urejanjem; članek o tokovih navzkrižnih referenc s hibridnimi referencami v pisarniških dokumentih PDF opisuje, kako so te datoteke strukturirane in kaj preživi povratno potovanje (round trip)

Dve meji, ki ju je treba spoštovati. Prvič, to je model urejanja na mestu in ne orodje za redakcijo ali čiščenje: odstranitev ključa Info odstrani ta ključ, vendar ne počisti starejših vrednosti, ki bi lahko ostale v prejšnji generaciji inkrementalnih posodobitev iste datoteke. Če je vaša zahteva resnična odstranitev občutljivih metapodatkov, gre za drugačno, zahtevnejšo operacijo. Drugič, zapis XMP je dobeseden — knjižnica zaupa vašemu XML-ju in ga ne potrjuje —, zato za vse, kar je namenjeno PDF/A ali strogemu validatorju, generirajte paket iz znane dobre predloge in preverite izhod. Uporabljeno znotraj teh meja je urejanje metapodatkov na mestu orodje prave velikosti: popravi tistih nekaj bajtov, ki so napačni, in pusti devetindevetdeset odstotkov datoteke, ki je bila že prej pravilna, natanko takšno, kot jo je zapisal prvotni proizvajalec

API za pisanje v naložene dokumente, prikazan tukaj, se prinaša s standardno komponento HotPDF Component za Delphi in C++Builder, skupaj s celotnim naborom metod za urejanje metapodatkov, kazala in kataloga