HotPDF dodaje knjižne oznake (bookmarks) u postojeći PDF u Delphiju putem funkcije AddLoadedOutline, koja stvara stavke strukture na bilo kojoj razini ugniježđenosti u učitanom dokumentu, te čita i piše imenovana odredišta putem ResolveLoadedNamedDestination i AddLoadedNamedDestination. Učitajte datoteku, izgradite stablo, pozovite SaveLoadedDocument i bočna traka čitača koja je nekada bila prazna sada donosi funkcionalan sadržaj
Scenarij koji motivira sve ovo je depresivno čest. Spojite desetak ugovora u jedan paket za pregled ili povežete tri priručnika za proizvode u jednu distribucijsku datoteku, a izlaz je strukturalno u redu: svaka stranica je tu, svaki font je netaknut. Zatim ga netko otvori u Acrobat-u, a ploča s oznakama je prazna. Dokument od 400 stranica bez navigacije je tehnički potpun, a praktički neupotrebljiv, a do verzije 2.347.0 HotPDF je mogao čitati, mijenjati i brisati oznake u učitanim dokumentima, ali ih nikada nije mogao stvoriti. Taj je jaz sada premošten, a ovaj članak prolazi kroz novu površinu API-ja i jedan detalj PDF tajni, ključ /Count, koji će vas iznenaditi ako ga tretirate kao jednostavan brojač stavki
Što je zapravo stablo strukture (outline tree)
PDF struktura (outline) je dvostruko povezano stablo rječnika, a ne ravna lista, i ta struktura objašnjava svaki parametar u API-ju. ISO 32000-1 §12.3.3 to definira: katalog dokumenta pokazuje na korijen /Outlines, korijen pokazuje na svoje prvo i zadnje dijete putem /First i /Last, a srodnici se povezuju putem /Next i /Prev dok svaka stavka pokazuje natrag prema gore putem /Parent. Svaka stavka nosi tekstualni niz /Title i, obično, /Dest koji govori kamo bi klik na nju trebao odvesti čitatelja
Odredišta dolaze u dva oblika, a ISO 32000-1 §12.3.2 ih drži namjerno odvojenima. Eksplicitno odredište je ugrađeni niz kao što je [page /XYZ x y zoom]: izravna referenca na objekt stranice plus specifikacija prikaza. Imenovano odredište (named destination) umjesto toga pohranjuje niz naziva, a stvarni cilj živi u katalogu dokumenta pod stablom naziva /Names /Dests, što je jedna razina indirekcije koja omogućuje da više poveznica dijeli jedan cilj te da se cilj pomiče bez diranja poveznica. HotPDF piše eksplicitna /XYZ odredišta kada stvara knjižne oznake i daje vam zasebne pozive za čitanje i proširivanje stabla naziva
Kako dodati knjižne oznake u postojeći PDF u Delphiju?
Pozovite AddLoadedOutline(ParentIndex, DestPageIndex, Title, X, Y, Zoom) nakon LoadFromFile: proslijedite ParentIndex = -1 za stavku na najvišoj razini, ili indeks od nule postojeće stavke najviše razine da biste je ugnijezdili ispod nje. Funkcija stvara korijen /Outlines ako ga dokument nikada nije imao, gradi rječnik stavki, piše niz /Dest [page /XYZ x y zoom] koji pokazuje na indeks stranice DestPageIndex (baziran na nuli) i povezuje novu stavku u lanac srodnika. Vraća 0 u slučaju uspjeha i -1 u slučaju neuspjeha, na primjer kada je DestPageIndex izvan raspona
Jedan detalj povezivanja važan je za redoslijed: nova stavka umeće se na početak lanca djece svog roditelja, kao novi /First. Dakle, ako dodate 'Poglavlje 1', a zatim 'Poglavlje 2' kao stavke najviše razine, Poglavlje 2 pojavljuje se iznad Poglavlja 1 na bočnoj traci, a Poglavlje 1 se pomiče na indeks najviše razine 1. Praktičan obrazac je ili dodavanje stavki u obrnutom redoslijedu čitanja, ili dodavanje svakog roditelja i trenutno popunjavanje njegove djece dok se još nalazi na indeksu 0
var
Pdf: THotPDF;
begin
Pdf := THotPDF.Create(nil);
try
if Pdf.LoadFromFile('merged-manual.pdf', '') > 0 then
begin
// Add chapters in reverse reading order: each new
// top-level entry becomes /First (index 0).
Pdf.AddLoadedOutline(-1, 40, 'Chapter 2: Configuration', 0, 792, 0);
Pdf.AddLoadedOutline(-1, 0, 'Chapter 1: Installation', 0, 792, 0);
// Chapter 1 is now top-level index 0; nest sections
// under it (children also prepend, so reverse order).
Pdf.AddLoadedOutline(0, 12, 'License activation', 0, 792, 0);
Pdf.AddLoadedOutline(0, 3, 'System requirements', 0, 792, 0);
Pdf.SaveLoadedDocument('merged-manual-toc.pdf');
end;
finally
Pdf.Free;
end;
end;
Parametri X, Y i Zoom preslikavaju se izravno na odredište /XYZ i svi su prema zadanim postavkama 0. PDF postavlja ishodište koordinata u donji lijevi kut, pa Y = 792 cilja vrh stranice formata US Letter, a prema ISO 32000-1 nulta vrijednost u /XYZ utoru govori čitaču da zadrži svoju trenutnu postavku, što je upravo ono što želite za zumiranje u gotovo svakom slučaju. Sve se događa na učitanom grafu objekata u memoriji; ništa ne dodiruje disk dok se ne pokrene SaveLoadedDocument, što je isti model uredi-pa-spremi koji API za učitane dokumente koristi za prepisivanje naslova, autora i XMP metapodataka na učitanim PDF-ovima
Preusmjeravanje knjižnih oznaka: skokovi na stranice naspram URI radnji
Postojeće knjižne oznake mogu se ponovno povezati bez njihovog ponovnog stvaranja, a HotPDF daje dva različita poziva za ova dva slučaja jer su PDF konstrukcije ispod njih doista različite. SetLoadedOutlineDestination(Index, DestPageIndex, X, Y, Zoom) zamjenjuje /Dest stavke najviše razine na indeksu Index novim /XYZ nizom, što je alat za klasični neuspjeh nakon spajanja gdje svaka oznaka i dalje pokazuje na brojeve stranica izvorne datoteke prije spajanja. SetLoadedOutlineURI(Index, URI) radi nešto strukturalno drugačije: pridružuje akcijski rječnik /A s /S /URI, pretvarajući oznaku u web poveznicu umjesto skoka unutar dokumenta
// The merge shifted everything by 10 pages: repoint the
// third top-level bookmark at page 12 (zero-based 11).
Pdf.SetLoadedOutlineDestination(2, 11, 0, 792, 0);
// Turn the last entry into an external link.
Pdf.SetLoadedOutlineURI(3, 'https://www.loslab.com/');
Pdf.SaveLoadedDocument('retargeted.pdf');
Jasno razlikujte ova dva mehanizma kada dizajnirate stablo. /Dest je čista navigacija unutar dokumenta; URI radnja u potpunosti napušta dokument, a ISO 32000-1 očekuje da se stavka strukture kreće kroz jedan ili drugi mehanizam, a ne oba. Stoga rezervirajte SetLoadedOutlineURI za stavke koje već ne nose odredište stranice, na primjer list 'Početna stranica proizvoda' ili 'Prijavi problem' na kraju stabla, a ne za pretvoreno zaglavlje poglavlja. Ovdje zapisana URI radnja je ista /S /URI konstrukcija koja se koristi za anotacije poveznica na sadržaju stranice, što je detaljnije pokriveno u vodiču za stvaranje hiperveza u PDF dokumentima pomoću HotPDF-a
Kako funkcioniraju imenovana odredišta u učitanom PDF-u?
Imenovana odredišta su stabilan sidreni mehanizam iz specifikacije ISO 32000-1 §12.3.2.3: umjesto umetanja reference stranice u svaku poveznicu, dokument čuva mapiranje naziva u odredište u stablu naziva /Names /Dests u katalogu, a poveznice se odnose na stavke prema nazivu. ResolveLoadedNamedDestination(Name) traži naziv u tom stablu i vraća indeks stranice (baziran na nuli) na koji pokazuje, ili -1 ako naziv nije prisutan. Razrješavatelj (resolver) obrađuje oba oblika pohrane koje specifikacija dopušta za mapiranu vrijednost: čisti niz odredišta poput [page /XYZ x y zoom] i oblik rječnika koji omotava niz pod ključem /D
AddLoadedNamedDestination(Name, DestPageIndex, X, Y) ide u suprotnom smjeru: registrira novo sidro u stablu /Names /Dests, stvarajući samo stablo kada ga dokument nema, i vraća True u slučaju uspjeha. Zajedno, ovaj par pokriva tijek rada u kojem spojeni dokument mora poštovati sidra koja su zapisali alati prije njega, te objaviti nova za ciljanje poveznica nakon njega
var
PageIdx: Integer;
begin
// Honor an anchor another tool wrote into /Names /Dests.
PageIdx := Pdf.ResolveLoadedNamedDestination('chapter.3.figures');
if PageIdx >= 0 then
Pdf.AddLoadedOutline(-1, PageIdx, 'Chapter 3: Figures', 0, 792, 0);
// Publish a new anchor for other links to target.
if Pdf.AddLoadedNamedDestination('appendix.glossary', 42, 0, 792) then
Pdf.SaveLoadedDocument('anchored.pdf');
end;
Stvarne granice: odredišta koja HotPDF piše, kako za knjižne oznake tako i za imenovana sidra, su /XYZ odredišta. Ostali eksplicitni tipovi koje specifikacija definira, /Fit, /FitH, /FitB i slični, ne emitiraju se ovim pozivima, iako /XYZ s nultim zumiranjem pokriva dominantan slučaj upotrebe 'idi na ovo mjesto, zadrži moje zumiranje'. A GetLoadedBookmarkPageIndex(Title), praktično pretraživanje koje prihvaća naslov knjižne oznake i vraća njezinu odredišnu stranicu, skenira najvišu razinu stabla strukture, stoga ga koristite za provjeru stavki na razini poglavlja koje ste upravo stvorili, a ne za duboko ugniježđene listove
Zamka ključa /Count: on broji potomke, a ne stavke
Ključ /Count na korijenu strukture ne znači ono što većina programera pretpostavlja, a pogrešna pretpostavka proizvodi neuspjehe testova koji izgledaju kao korupcija podataka. ISO 32000-1 §12.3.3 definira /Count kao ukupan broj vidljivih potomaka na svim razinama, a ne broj stavki najviše razine. Na pojedinačnoj stavci znak također ima značenje: pozitivna vrijednost znači da je stavka otvorena i da se toliko potomaka prikazuje na bočnoj traci, dok negativna vrijednost znači da je stavka skupljena i da je |N| potomaka skriveno unutar nje
Ovo je proizašlo iz vlastitog regresijskog skupa testova HotPDF-a dok su se gradili API-ji za knjižne oznake učitanih dokumenata. Testni dokument je sadržavao tri knjižne oznake na najvišoj razini, od kojih je jedna imala dijete; nakon brisanja jedne stavke najviše razine, GetLoadedOutlineCount, koji čita korijenski /Count, izvijestio je 1 umjesto očekivanih 2. Ništa nije bilo oštećeno: početna vrijednost 3 uopće nije predstavljala 'tri stavke na najvišoj razini', a ispravan ponovni izračun mora proći kroz lanac najviše razine akumulirajući svaki čvor plus njegov pozitivni /Count, preskačući potomke skupljenih čvorova. Pouka za vaš vlastiti kôd je izravna: nikada se nemojte oslanjati na razlike u /Count vrijednosti nakon strukturalnih izmjena, jer se ta vrijednost kreće nelinearno s vidljivim potomcima. Umjesto toga, provjerite strukturu prema sadržaju
// After edits, verify by title lookup, not by /Count deltas.
if Pdf.GetLoadedBookmarkPageIndex('Chapter 1: Installation') = 0 then
ShowMessage('Outline verified');
Gdje se ovo uklapa u skup alata za učitane dokumente
Stvaranje knjižnih oznaka i imenovanih odredišta upotpunjuje sliku koju API za učitane dokumente već neko vrijeme popunjava: isti ciklus učitaj-uredi-spremi već prepisuje metapodatke dokumenta, dodaje anotacije poveznica i prenosi anotacije u oba smjera putem uvoza i izvoza XFDF-a. Obrazac je jedinstven: LoadFromFile, slijed *Loaded* poziva na grafu objekata u memoriji, a zatim SaveLoadedDocument, što olakšava ugradnju izrade strukture u postojeći cjevovod spajanja ili otiskivanja kao još jedan korak prije spremanja
API-ji o kojima se ovdje raspravlja, AddLoadedOutline, SetLoadedOutlineDestination, SetLoadedOutlineURI, ResolveLoadedNamedDestination, AddLoadedNamedDestination i GetLoadedBookmarkPageIndex, isporučuju se u standardnoj HotPDF komponenti za Delphi i C++Builder, bez vanjskih ovisnosti i bez potrebe za zasebnim okruženjem preglednika