Tehnički članak

GoToR, GoToE i Launch akcije u Delphi PDF-ovima

PDFlibPas programerima za Delphi i C++Builder daje tri vrste akcija za navigaciju koja napušta trenutnu stranicu: GoToR (Go To Remote) otvara određenu stranicu u drugoj PDF datoteci, GoToE (Go To Embedded) otvara PDF datoteku ugrađenu u trenutni dokument, a Launch pokreće spoljni program ili otvara datoteku kroz ljusku operativnog sistema. Sve tri su opisane u ISO 32000-1 §12.6.4, odeljku Action Types koji definiše i uobičajenu GoTo akciju, a svaka krije sopstvenu zamku: broj stranice ima različito značenje u zavisnosti od poziva koji je gradi, cilj je ime a ne putanja datoteke, a dva string parametra izgledaju isto iako služe različitim čitačima

Ništa od ovoga nije hipotetičko. Paket tehničke dokumentacije — glavno uputstvo, PDF specifikacija koju distributer ažurira svojim tempom i uslužni program za kalibraciju instaliran uz oba — oslanja se upravo na ovakvo povezivanje dokumenata: unakrsna referenca mora da vodi na petu stranicu datoteke sa specifikacijom, list sa podacima vredi ugraditi u uputstvo umesto da stoji pored njega, a veza treba direktno da preda rad alatu za kalibraciju. Ovaj članak je suprotna strana teksta o čitanju akcija obeleživača i anotacija iz postojećeg PDF-a: tamo je opisano preuzimanje GoToR, Launch ili GoToE akcije koju je drugi proizvođač već upisao u datoteku, a ovde je opisano građenje iste tri vrste akcija od nule, uključujući pravila na nivou polja koja PDFlibPas proverava pre nego što upiše ijedan bajt

Tri načina da PDF akcija napusti trenutnu stranicu

PDFlibPas odvaja lokalnu navigaciju od svega ostalog pomoću ključa akcije /S, a GoToR, GoToE i Launch su tri podtipa čiji se cilj nalazi izvan trenutne stranice: GoToR je opisan u ISO 32000-1 §12.6.4.3, GoToE u §12.6.4.4, a Launch u §12.6.4.5, sve u širem odeljku §12.6.4 Action Types koji definiše i uobičajenu GoTo akciju. Odredište obične GoTo akcije imenuje objekat stranice koji već postoji u dokumentu, pa PDFlibPas može odmah da ga proveri; GoToR i GoToE to ne mogu na isti način, jer spoljna datoteka možda ni ne postoji na računaru, a broj stranica ugrađene datoteke nije podatak koji glavni dokument prati, pa obe nose nerešenu referencu umesto čvrste veze — specifikaciju datoteke i odredište za GoToR, ime ugrađene datoteke i ciljnu stranicu za GoToE — dok Launch potpuno izostavlja pojam odredišta i samo imenuje ono što operativni sistem treba da pokrene ili otvori. Ta podela se na strani upisa vidi kao dve porodice poziva: graditelji višeg nivoa u jednom koraku, kao što su AddLinkToFile, AddLinkToFileEx, AddLinkToEmbeddedPDF i AddLinkToLocalFile, zajedno stvaraju anotaciju veze na aktivnoj oblasti stranice i njenu akciju, što pokriva većinu stvarnih rasporeda — red teksta ili ikonu na koju čitalac klikne — dok postavljači nižeg nivoa, kao što su SetActionRemoteDestinationEx, SetActionLaunchOptions i njihovi parnjaci AddActionNext*, dodaju ili zamenjuju akciju na objektu čiji rukohvat već imate: postojećem obeleživaču, okidaču polja obrasca ili događaju životnog ciklusa dokumenta ili stranice. Obe porodice na kraju upisuju iste oblike rečnika; razlika je u tome gde se nalazite kada pozovete funkciju i, kao što sledeći odeljak pokazuje, šta broj stranice tada znači

Kako napraviti GoToR vezu koja otvara stranicu u drugoj PDF datoteci

GoToR akciji su potrebne dve stvari — specifikacija datoteke i odredište u toj datoteci — a PDFlibPas izlaže dva različita poziva za zadavanje drugog dela, svaki sa sopstvenim pravilom numerisanja stranica. Graditelji AddLinkToFile i AddLinkToFileEx višeg nivoa proveravaju da je njihov argument Page ili DestPage veći od nule, što je isto numerisanje od 1 koje PDFlibPas koristi svuda drugde, uključujući SelectPage. Postavljač nižeg nivoa SetActionRemoteDestinationEx, namenjen dodavanju ili zameni GoToR akcije na objektu čiji rukohvat već imate, umesto toga proverava da je DestPage veći ili jednak nuli i bez izmene ga upisuje u niz eksplicitnog odredišta akcije: očekuje sirovi indeks stranice ciljnog dokumenta koji počinje od nule, kako ISO 32000-1 propisuje za udaljeno eksplicitno odredište. Ako postavljaču nižeg nivoa prosledite isti broj koji biste dali graditelju višeg nivoa, veza će otvoriti stranicu prerano

var
  Lib: TPDFlib;
  ActionID: Integer;
begin
  Lib := TPDFlib.Create;
  try
    if Lib.LoadFromFile('manual.pdf', '') = 1 then
    begin
      Lib.SelectPage(12);
      // Page is 1-based here, same as SelectPage above: this opens
      // the fifth page of specs.pdf.
      Lib.AddLinkToFile(72, 700, 200, 16, 'specs.pdf', 5, 0, 0, 0);

      // A later maintenance pass repoints the same link at a
      // reorganized file. SetActionRemoteDestinationEx edits the
      // action directly, and DestPage here is the zero-based index
      // PDF itself uses for a remote explicit destination -- "the
      // fifth page" is now 4, not 5.
      ActionID := Lib.GetAnnotActionID(1);
      Lib.SetActionRemoteDestinationEx(ActionID, 'specs-2026.pdf',
        4, Ord(dkFit), 0, 0, 0, 0, 0, 0, -1);
    end;
  finally
    Lib.Free;
  end;
end;

Ostali argumenti funkcije SetActionRemoteDestinationEx jednako su doslovni. ValueMask je skup bitova — 1 za levo, 2 za vrh, 4 za desno, 8 za dno, 16 za uvećanje — a PDFlibPas ga proverava prema DestType pre bilo kakvog upisa: odredište dkFitR mora da dobije tačno 15 (sve četiri ivice, bez uvećanja), dkFit i dkFitB moraju da dobiju 0, a dkFitH/dkFitV prihvataju samo svoju relevantnu koordinatu. Bitovi koje ostavite nepostavljene u inače važećoj maski ne izostavljaju se iz niza; upisuju se kao eksplicitni PDF null, što ISO 32000-1 za tu koordinatu tumači kao „zadrži vrednost koju čitalac već ima“ — ispravan način da se kaže „pređi na ovu stranicu, ne menjaj uvećanje“, a ne propust. Samo uvećanje se čuva kao razlomak prosleđene vrednosti, pa poziv koji traži 150 procenata u nizu ostavlja vrednost 1.5, a važeći opseg ulaza je od 0 do 6400

Kako povezati PDF ugrađen u sopstveni dokument

AddLinkToEmbeddedPDF gradi GoToE akciju, a njen ciljni argument EmbeddedFileName je ime, a ne putanja: mora da se podudara sa stringom Title koji je već prosleđen funkciji EmbedFile pri dodavanju priloga, jer je taj naslov doslovni ključ koji PDFlibPas čuva u stablu imena dokumenta /EmbeddedFiles, a GoToE rešava cilj traženjem tog imena, bez ponovnog pristupa sistemu datoteka. Funkcija proverava samo da EmbeddedFileName nije prazan i da je TargetPage najmanje 1 — ako prosledite ime koje nikada nije stvarno ugrađeno, poziv će ipak vratiti uspeh, akcija će biti upisana, a veza jednostavno neće moći da se razreši ni za jednog čitaoca koji klikne na nju

var
  Lib: TPDFlib;
begin
  Lib := TPDFlib.Create;
  try
    Lib.NewDocument;
    Lib.NewPage;
    // The Title argument becomes the key PDFlibPas stores in the
    // document's EmbeddedFiles name tree -- that string, not
    // "datasheet.pdf", is the target GoToE resolves against.
    if Lib.EmbedFile('Datasheet', 'datasheet.pdf', 'application/pdf') = 1 then
      Lib.AddLinkToEmbeddedPDF(72, 700, 200, 16, 'Datasheet', 3, 0, 0);
    Lib.SaveToFile('manual.pdf');
  finally
    Lib.Free;
  end;
end;

Ovde se sabiraju dva minimalna nivoa verzije. EmbedFile zahteva PDF 1.4 zbog stabla imena /EmbeddedFiles, dok AddLinkToEmbeddedPDF zasebno podiže minimum na PDF 1.6 zbog samog tipa GoToE akcije, pa je efektivni minimum za svaki dokument koji koristi ovu mogućnost 1.6, a ne 1.4. Obratite pažnju i na to da je TargetPage ovde numerisan od 1, po uobičajenom PDFlibPas pravilu — namerna suprotnost indeksu DestPage koji počinje od nule i obrađen je u prethodnom odeljku, kao podsetnik da šema zavisi od vrste akcije i konkretnog poziva, a ne od jednog opšteg pravila. Rečnik cilja akcije može da sadrži i stavku /R sa vrednošću C za dete ili P za roditelja, što podržava lanac od dva koraka ka ugrađenoj datoteci ili nazad ka njenom kontejneru, iako AddLinkToEmbeddedPDF gradi samo smer ka detetu, jer on ima smisla iz dokumenta koji ugrađuje datoteku, a ne iz dokumenta koji je ugrađen

Launch akcije: jedan FileName i dva string cilja koji nisu zamenljivi

SetActionLaunchOptions upisuje cilj datoteke Launch akcije u dva različita ključa iz jednog argumenta FileName, a ta dva ključa sadrže dve različite vrste stringa. Najviši ključ /F dobija rečnik specifikacije datoteke, napravljen istom konverzijom putanje koju PDFlibPas koristi za GoToR, u prenosivom obliku koji ISO 32000-1 §7.11.3 definiše za rečnik specifikacije datoteke. Podrečnik /Win, kada ga PDFlibPas upiše, dobija sopstveni ključ /F sa sirovom vrednošću FileName tačno onako kako je prosleđena, bez ikakve konverzije, jer je /Win /F u ISO 32000-1 §12.6.4.5 opisan kao običan Windows string putanje koji treba da čita samo Windows čitalac. Ako prosledite prenosivu, već konvertovanu putanju očekujući da oba ključa budu ista, kopija u /Win nosiće netaknutu vrednost koju ste dali funkciji

var
  Lib: TPDFlib;
  ActionID: Integer;
begin
  Lib := TPDFlib.Create;
  try
    if Lib.LoadFromFile('manual.pdf', '') = 1 then
    begin
      Lib.SelectPage(1);
      Lib.AddLinkToLocalFile(72, 660, 220, 16, 'calibrate.exe', 0);
      ActionID := Lib.GetAnnotActionID(1);
      // Operation 0 leaves this as a normal open -- pass 1 to ask a
      // Windows viewer to print instead. Parameters and
      // DefaultDirectory only ever reach /Win /P and /Win /D, never
      // the top-level /F.
      Lib.SetActionLaunchOptions(ActionID, 'calibrate.exe',
        '/silent /profile:default', 'C:\Tools\Calibration', 0, -1);
    end;
  finally
    Lib.Free;
  end;
end;

Launch tretirajte kao najproblematičniju od tri akcije, jer joj je cela svrha pokretanje programa ili otvaranje datoteke izvan PDF peska, pa je tako tretiraju i glavni čitači. Adobe Acrobat Enhanced Security podrazumevano blokira Launch akcije ili traži potvrdu, osim kada je cilj na izričito pouzdanoj lokaciji, a većina poslovnih Acrobat instalacija ostavlja tu zaštitu uključenu. Zato Launch akcija u dokumentu koji se šalje javnosti nije pouzdan okidač: računajte da će je čitač koji otvori datoteku blokirati, tražiti potvrdu ili tiho zanemariti, i čuvajte je za zatvorena okruženja u kojima kontrolišete i podešavanja poverenja čitača — interni kiosk, kontrolisano uvođenje u kompaniji ili dokument koji nikada ne napušta računar kojim upravljate

PDF/A ograničenje: zašto GoToR i Launch pozivi mogu vratiti nulu

SetActionRemoteDestinationEx i SetActionLaunchOptions potpuno odbijaju rad kada je ciljni dokument u bilo kom PDF/A režimu usaglašenosti: obe funkcije proveravaju PDF/A režim kao prvi uslov i izlaze sa rezultatom 0 pre nego što dodirnu akciju, bez izuzetka. To je namerno. Ograničenja PDF/A za interaktivne akcije posebno isključuju Launch, jer mogućnost da arhivska datoteka pokrene proizvoljan program predstavlja upravo ponašanje zavisno od okruženja koje formati za dugoročno arhiviranje treba da spreče, a PDFlibPas istu konzervativnu prepreku primenjuje i na postavljač udaljenog skoka. Praktična posledica se tokom razvoja lako previdi: isti poziv koji radi na običnom PDF-u prevešće se, pokrenuti i tiho neće uraditi ništa na dokumentu učitanom sa postavljenim PDF/A nivoom usaglašenosti, zato proverite povratnu vrednost umesto da pretpostavite uspeh — 0 ovde nije greška neispravnog ulaza, već odbijanje zahteva koji je u sukobu sa sopstvenom tvrdnjom dokumenta o usaglašenosti

Gde se GoToR, GoToE i Launch uklapaju u širi PDFlibPas tok rada

Tri vrste akcija iz ovog članka ne dopiru sve do istih mesta. Prateći članak o okidačima životnog ciklusa dokumenta i stranice obrađuje SetDocumentAction i SetPageAction, koji mogu da dodaju GoToR ili Launch akciju okidaču kao što je WillClose kroz zajedničke konstante PDF_ACTION_BUILDER_REMOTE_DESTINATION i PDF_ACTION_BUILDER_LAUNCH — isti graditelj pokriva i običan URI ili JavaScript okidač. GoToE nema takvu konstantu ni put do tog opšteg graditelja; AddLinkToEmbeddedPDF jedini je način na koji ga PDFlibPas konstruiše, pa je strogo akcija na aktivnoj oblasti stranice, a nikada okidač na nivou dokumenta ili stranice. Tamo gde GoToR i Launch mogu da koriste opšti graditelj, kompromis je manja kontrola: on pravi GoToR koji pokazuje samo na imenovano udaljeno odredište i Launch akciju sa imenom datoteke i parametrima, dok se eksplicitno adresiranje stranice i tipa uklapanja, kao i Windows opcije Launch akcije iz ovog članka, mogu dobiti samo direktnim pozivima SetActionRemoteDestinationEx i SetActionLaunchOptions

Pre izgradnje alata za održavanje oko ovih postavljača važno je znati jedno bezbednosno svojstvo. SetActionRemoteDestinationEx i SetActionLaunchOptions prvo grade celu zamensku akciju u pomoćnom rečniku, a ključeve /F, /D ili /Win i /NewWindow brišu i kopiraju u aktivnu akciju tek kada pomoćna kopija prođe proveru — zato poziv koji padne na proveri, bilo zbog maske ValueMask izvan opsega ili praznog FileName, ostavlja prvobitnu akciju i svaki /Next lanac koji je već vezan za nju potpuno netaknutim, umesto napola prepisanim. To je važno jer GoToR i Launch akcije mogu obe da budu u /Next lancu napravljenom pomoću AddActionNextRemoteDestinationEx, AddActionNextLaunchEx ili opštijeg AddActionNextEx, pa jedan okidač može redom da upiše JavaScript zapis u dnevnik i zatim izvrši udaljeni skok. Građenje GoToR, GoToE i Launch akcija opisano ovde deo je biblioteke PDFlibPas, izvorne PDF biblioteke za Delphi i C++Builder