Turite dešimt tūkstančių sutarčių PDF iš keliolikos skirtingų generatorių, o teisės skyrius nori, kad kiekviename iš jų būtų teisingas Author, pataisytas Producer string ir skaitymo režimas, kuris paleidžiant atidaro žymių skydelį. Naivus sprendimas yra įkelti kiekvieną failą, iš naujo sudėlioti puslapius ir įrašyti naują dokumentą. Taip padarę jūs tiesiog išmetate visus esamus objektų numerius, laipsniškų atnaujinimų istoriją, bet kokį skaitmeninį parašą ir kruopščiai suderintą xref, kurį išvedė pradinė priemonė. Puslapiai atrodo identiški, bet failas struktūriškai tampa svetimas. Metaduomenų redagavimui tai yra visiškai netinkamas sandoris
Teisingas kelias yra laikyti įkeltą dokumentą objektų grafu, kurį modifikuojate vietoje: pasiekti Info žodyną, /Metadata srautą ir Catalog, pakeisti kelis jus dominančius įrašus ir įrašyti rezultatą atgal. HotPDF, natyvus VCL PDF komponentas Delphi ir C++Builder, per įkeltų dokumentų rašymo API pateikia būtent tokią sąsają. Šis straipsnis yra apie tai, kaip ja naudotis teisingai, ir apie vieną klaidą, kurią daro beveik visi: redaguoja Info žodyną ir pamiršta, kad antra tos pačios metaduomenų kopija gyvena XMP
Du atskiri taškai saugo tuos pačius metaduomenis, ir jie nesutaria
PDF dokumento informaciją saugo dviejose lygiagrečiose vietose, ir tai yra daugumos „Pakeičiau pavadinimą, bet Acrobat vis tiek rodo senąjį“ skundų šaltinis. Pirmoji yra dokumento informacijos žodynas, klasikinis /Info objektas su /Title, /Author, /Subject, /Keywords, /Creator, ir /Producer raktais, apibrėžtais ISO 32000-1 §14.3.3. Antroji yra XMP paketas, XML dokumentas, saugomas kaip srautas, kabantis nuo Catalog po /Metadata, apibrėžtas §14.3.2 ir paremtas Adobe XMP duomenų modeliu
Abu gali saugoti pavadinimą. Standartas jų nepriverčia sutarti. Šiuolaikinės peržiūros programos ir dauguma PDF/A validatorių, kai jis yra, pirmenybę teikia XMP paketui, o jo nesant grįžta prie Info žodyno. Todėl jei atnaujinate tik /Info - o būtent taip daro didžioji dauguma „PDF metaduomenų nustatymo“ kodo - skaitytojas, kuris pasitiki XMP, toliau rodys senąją reikšmę, o PDF/A tikrintuvas pažymės neatitikimą. Tinkama operacija faile, kuriame jau yra XMP paketas, yra dvigubas rašymas: pakeisti Info įrašą ir iš naujo sugeneruoti XMP, kad abu vaizdai išliktų suderinti. HotPDF suteikia jums abi puses, o disciplina jas naudoti kartu jau yra jūsų atsakomybė
Info žodyno redagavimas
Info pusės pagalbinės funkcijos yra paprastos ir nuspėjamos. SetLoadedTitle, SetLoadedAuthor, SetLoadedSubject, SetLoadedKeywords, SetLoadedCreator, ir SetLoadedProducer kiekviena priima vieną AnsiString ir į įkeltą Info žodyną įrašo atitinkamą raktą, pakeisdama reikšmę, jei raktas egzistuoja, arba pridėdama jį, jei ne. Norėdami visiškai pašalinti raktą, tarkime, nutekėjusį /Creator įrašą, kuris nurodo jūsų vidinius įrankius, iškvieskite RemoveLoadedInfoKey su pačiu rakto pavadinimu. Nė vienas iš jų neliečia XMP; jie veikia tik su /Info objektu, kurį LoadFromFile rado analizuodamas failą
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;
Svarbu viena detalė: jie priima AnsiString. ASCII pavadinimams tai nėra problema, bet PDF teksto eilutės, kurioms reikia nelotyniškų simbolių, turi būti užkoduotos taip, kaip reikalauja standartas - UTF-16BE su baitų tvarkos ženklu arba PDFDocEncoding - prieš perduodant jas bibliotekai. Biblioteka į string objektą įrašo jūsų pateiktus baitus, bet pati neklausia, kokį kodavimą naudoti. Jei pavadinimai yra paprasta anglų kalba, šį punktą galima ignoruoti. Jei juose yra diakritinių ar CJK simbolių, koduokite sąmoningai ir patikrinkite realioje peržiūros programoje
XMP paketo perrašymas
SetLoadedXMPMetadata yra kita dvigubo rašymo pusė. Perduokite jam visą XMP paketą kaip AnsiString ir jis daro vieną iš dviejų dalykų: jei Catalog jau nurodo /Metadata srautą, jis vietoje pakeičia to srauto turinį, išlaikydamas tą patį objekto numerį; jei metaduomenų srauto nėra, jis sukuria naują, pažymi jį /Type /Metadata ir /Subtype /XML, priskiria objekto numerį ir susieja jį iš Catalog. Bet kuriuo atveju gaunate tinkamą metaduomenų objektą, kurį peržiūros programos skaitys
Pateikiate XML, vadinasi valdote schemą - dc:title, dc:creator, xmp:CreatorTool, ir taip toliau. Tai kartu ir galia, ir atsakomybė: biblioteka jūsų paketo neanalizuoja ir netikrina, o baitus įrašo nesuspaustus, be jokių srauto filtrų. Sugedęs paketas be kliūčių praeis per kvietimą ir vėliau pasirodys kaip pažeistų metaduomenų skundas. XML kurkite atsargiai ir tiksliai atkartokite tas pačias reikšmes, kurias įrašėte į Info žodyną, kad abu vaizdai niekada neprieštarautų vienas kitam
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;
Tokia seka - pirmiausia Info, paskui XMP, tada saugoti - yra modelis, kurį reikia įsidėmėti. Abu kvietimai veikia nepriklausomai, o suderinamumas egzistuoja tik todėl, kad jiems perdavėte tas pačias eilutes. Jei faile, kuriame jau yra XMP paketas, praleisite XMP kvietimą, grįšite prie tyliai besitęsiančios pasenusios reikšmės klaidos, kurią visas šis skyrius ir siekia užkirsti

Kaip nurodyti, kaip peržiūros programa atidarys failą
Trys Catalog įrašai lemia, ką skaitytojas mato akimirksniu atidaręs dokumentą, ir visi trys yra vienos eilutės pakeitimai įkeltame grafike. SetLoadedPageMode įrašo /PageMode kaip pavadinimo objektą: perduokite 'UseOutlines' kad atvertumėte žymių skydelį, 'UseThumbs' miniatiūrų juostai, 'FullScreen' prezentacijos režimui arba 'UseAttachments' kad būtų rodoma priedų sritis (ISO 32000-1 §7.7.3.1, 28 lentelė). SetLoadedPageLayout įrašo /PageLayout tokiu pačiu būdu - 'SinglePage', 'OneColumn', 'TwoColumnLeft', ir kiti. Abu priima pavadinimą be priekyje esančio pasvirojo brūkšnio; išvestyje biblioteka jį prideda pati
SetLoadedLanguage įrašo Catalog /Lang įrašą, visam dokumentui skirtą natūralios kalbos žymą - 'en-US', 'de-DE', BCP 47 žymą. Atkreipkite dėmesį į tipo skirtumą, kuris žmones suklaidina: /PageMode ir /PageLayout yra PDF name objektai, o /Lang yra string. HotPDF viduje tai daro teisingai, bet jei kada nors peržiūrėsite išvestį, matysite /PageMode /UseOutlines prieš /Lang (en-US), ir dabar jau žinote kodėl. /Lang įrašas yra svarbesnis, nei atrodo: jį nuskaito pagalbinės technologijos, kad parinktų tarimą, ir tai yra griežtas PDF/UA prieinamumo atitikties reikalavimas
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;
Žymų pervadinimas nejudinant medžio
Žymių pavadinimų tvarkymas yra įprastas valymas - klaida antraštėje, iš naujo sunumeruotas skyrius po to, kai kontūras jau buvo sukurtas. SetLoadedOutlineTitle priima nuliniu pagrindu skaičiuojamą indeksą į aukščiausio lygio kontūro įrašus ir naują pavadinimą, pereina Catalog → /Outlines → /First → /Next grandine iki tos pozicijos ir pakeičia įrašo /Title eilutę. Ji keičia tik pavadinimą; paskirties vieta, atidaryta ar uždaryta būsena ir vaikų struktūra lieka nepaliestos
if Pdf.LoadFromFile('report.pdf', '') > 0 then
begin
Pdf.SetLoadedOutlineTitle(0, 'Executive Summary');
Pdf.SetLoadedOutlineTitle(1, 'Financial Results');
Pdf.SaveLoadedDocument('report-renamed.pdf');
end;
Pervadinimas saugus būtent todėl, kad jis niekada neliečia struktūrinių skaitiklių. Kontūro įrašo trynimas yra ta vieta, kur dažniausiai suklystama, ir verta tai suprasti net ir tada, kai tik pervadinate, nes tai parodo, ko ne galima redaguoti rankomis. Kiekvienas kontūro mazgas turi /Count, ir, pagal ISO 32000-1 §12.3.3, tas skaičius nėra tiesioginių vaikų skaičius. Tai bendras matomų palikuonių: teigiamas /Count N reiškia, kad N palikuonių šiuo metu matoma, o neigiama reikšmė reiškia, kad mazgas turi palikuonių, bet yra suskleistas. Kai pašalinamas aukščiausio lygio įrašas, /Outlines šaknies skaičiaus negalima tiesiog sumažinti vienetu; jį reikia perskaičiuoti sudedant kiekvienam išlikusiam aukščiausio lygio mazgui - vienas už patį mazgą plius jo teigiamas /Count, ir praleidžiant bet kurio suskleisto (neigiamo skaičiaus) mazgo palikuonis. Suklydę čia, skaitytojo rodomas žymių kiekis nukryps - jis šoktelės daugiau nei per vieną po kiekvieno trynimo. Pervadinimas viso šito išvengia, ir tai dar viena priežastis teikti pirmenybę tikslinei pagalbinei funkcijai, o ne pačiam kišti rankas į žodyną
Kaip išsaugojimas vyksta vietoje
Kiekvienas aukščiau esantis pakeitimas modifikuoja objektus atmintyje; niekas į diską nepatenka, kol SaveLoadedDocument neįvykdomas. Šis metodas yra pigus todėl, kad išsaugojimas iš naujo negeneruoja dokumento - jis išlaiko esamus objektų numerius ir struktūrą, kurią HotPDF išanalizavo įkeldamas, o atgal įrašo tą patį grafą su keliais pakeistais ir naujai priskirtais objektais. Būtent taip metaduomenų perėjimas neperrašo viso failo, ir tai ta pati vietoje atnaujinimo mechanika, kuri leidžia veikti objektų srautams ir laipsniškiems atnaujinimams. Jei jūsų šaltiniai ateina iš Word ar kito biuro paketo, jų objektų išdėstymas turi savų ypatumų, kuriuos verta žinoti prieš redaguojant, o straipsnis apie hibridinius xref nuorodų srautus Office PDF failuose paaiškina, kaip tie failai sudėti ir kas išlieka po grįžtamojo apdorojimo
Dvi ribos, kurių reikia laikytis. Pirma, tai redagavimo vietoje modelis, o ne cenzūravimo ar sanitarizavimo įrankis: pašalinus Info raktą, tas raktas išnyksta, bet nepašalinamos senesnės reikšmės, kurios gali likti ankstesnėje to paties failo laipsniškų atnaujinimų generacijoje. Jei jūsų tikslas yra tikras jautrių metaduomenų pašalinimas, tai jau kita, sunkesnė operacija. Antra, XMP rašymas yra pažodinis - biblioteka pasitiki jūsų XML ir jo netikrina - todėl viskam, kas skirta PDF/A ar griežtam validatoriui, generuokite paketą iš patikrinto šablono ir patikrinkite išvestį. Laikantis šių ribų, redagavimas vietoje yra tinkamo dydžio įrankis: jis pataiso kelis neteisingus baitus ir palieka likusius devyniasdešimt devynis procentus failo tokius, kokius juos ir parašė originalus kūrėjas
Čia parodyta įkeltų dokumentų rašymo API pateikiama kartu su standartiniu HotPDF Component skirtu Delphi ir C++Builder, kartu su visa metaduomenų, kontūrų ir Catalog redagavimo metodų rinkiniu