Műszaki cikk

GoToR-, GoToE-, és Launch-akciók Delphi PDF-ekben

A PDFlibPas három akciótípust ad a Delphi- és C++Builder-fejlesztőknek olyan navigációhoz, amely elhagyja az aktuális oldalt: a GoToR (Go To Remote) egy konkrét oldalt nyit meg egy másik PDF-fájlban, a GoToE (Go To Embedded) egy PDF-fájlt nyit meg, amely az aktuális dokumentumon belülre van ágyazva, és a Launch egy külső programot futtat, vagy egy fájlt nyit meg az operációs rendszer héján keresztül. Mindhárom az ISO 32000-1 §12.6.4-ben él, az Action Types szakaszban, amely a hétköznapi GoTo akciót is definiálja, és mindegyik saját csapdát hordoz a nem gyanútlanoknak: egy oldalszám, amely mást jelent attól függően, melyik hívás építi, egy cél, amely egy név, nem egy fájlútvonal, és egy sztring-paraméter-pár, amely azonosnak néz ki, de két különböző megjelenítőt szolgál

Ebből semmi nem hipotetikus. Egy technikai referenciacsomag — egy fő kézikönyv, egy specifikációk-PDF, amit egy forgalmazó a saját ütemezésén frissít, egy kalibrálási eszköz, mindkettő mellé telepítve — pontosan erre a fajta dokumentumok-közötti bekötésre támaszkodik: egy kereszthivatkozás, amelynek a specs-fájl 5. oldalán kell landolnia, egy adatlap, amit megéri a kézikönyvön belülre szállítani, nem mellette, egy link, amely egyenesen átadja a vezérlést a kalibráló eszköznek. Ez a cikk tükörképe a könyvjelző- és annotáció-akciók egy meglévő PDF-ből való visszaolvasásáról szóló cikknek: az egy darab egy GoToR, Launch, vagy GoToE akció fogyasztásáról szól, amit egy másik előállító már beírt egy fájlba; ez itt ugyanennek a három akciótípusnak nulláról történő felépítéséről szól, beleértve a mezőszintű szabályokat, amiket a PDFlibPas érvényesít, mielőtt egyetlen bájtot is elkötelezne

Három mód, ahogy egy PDF-akció elhagyhatja az aktuális oldalt

A PDFlibPas elválasztja a helyi navigációt mindentől mástól az akció /S kulcsán, és a GoToR, a GoToE, és a Launch a három altípus, amelyeknek célja az aktuális oldalon kívül ül: a GoToR az ISO 32000-1 §12.6.4.3 alatt, a GoToE a §12.6.4.4 alatt, és a Launch a §12.6.4.5 alatt, mindegyik a szélesebb §12.6.4 Action Types szakaszban, amely a hétköznapi GoTo akciót is definiálja. Egy egyszerű GoTo akció célja egy oldalobjektumot nevez meg, amely már létezik a dokumentumon belül, így a PDFlibPas azonnal érvényesítheti; a GoToR és a GoToE nem tudja ezt ugyanúgy megtenni, mivel a külső fájl esetleg nem is létezik ezen a gépen, és egy beágyazott fájl oldalszáma nem olyasmi, amit a gazdadokumentum nyomon követ, így mindkettő egy feloldatlan hivatkozást hordoz kemény link helyett — egy fájlspecifikáció plusz egy célhely a GoToR-hoz, egy beágyazottfájl-név plusz egy céloldal a GoToE-hez —, míg a Launch teljesen elejti a célhely-fogalmat, és csak megnevez valamit, amit az operációs rendszernek futtatnia vagy meg kell nyitnia. Ez a felosztás két hívási családként mutatkozik meg az írási oldalon: magas-szintű, egyhívásos építők, mint az AddLinkToFile, az AddLinkToFileEx, az AddLinkToEmbeddedPDF, és az AddLinkToLocalFile, egy oldal-hotspot link-annotációt és annak akcióját együtt hozzák létre, lefedve a legtöbb valódi elrendezést — egy szövegsor vagy egy ikon, amire egy olvasó kattint —, míg alacsonyabb-szintű beállítók, mint a SetActionRemoteDestinationEx, a SetActionLaunchOptions, és az AddActionNext* megfelelőik, csatolnak vagy lecserélnek egy akciót valamin, aminek már birtokolod a handle-jét: egy meglévő könyvjelzőn, egy formmező-kiváltón, vagy egy dokumentum- vagy oldal-szintű élettartam-eseményen. Mindkét család ugyanazokat a szótáralakokat végzi el megírva; a különbség ott van, hol állsz, amikor meghívod őket, és, ahogy a következő szakasz tárgyalja, mit jelent egy oldalszám, amikor teszed

Hogyan építesz egy GoToR linket, amely egy oldalt nyit meg egy másik PDF-fájlban?

Egy GoToR akciónak két dologra van szüksége — egy fájlspecifikációra és egy célhelyre azon a fájlon belül —, és a PDFlibPas két különböző hívást tesz elérhetővé a második rész szolgáltatásához, mindegyik saját oldalszámozási konvencióval. Az AddLinkToFile és az AddLinkToFileEx, a magas-szintű oldal-hotspot építők, érvényesítik Page vagy DestPage argumentumukat nullánál nagyobbként, ugyanaz az 1-alapú számozás, amit a PDFlibPas mindenhol máshol használ, a SelectPage-et is beleértve. A SetActionRemoteDestinationEx, az alacsonyabb-szintű beállító, amit egy GoToR akció csatolására vagy cseréjére használnak valamin, aminek már van handle-je, ehelyett a DestPage-et nullánál nagyobb vagy egyenlőként érvényesíti, és egyenesen beírja azt az akció explicit célhely-tömbjébe kiigazítás nélkül: a céldokumentum nyers, nulla-alapú oldalindexét akarja, azt a számozást, amit az ISO 32000-1 meghatároz egy távoli explicit célhelyhez. Hívd meg az alacsony-szintű beállítót ugyanazzal a számmal, amit a magas-szintű építőnek adnál, és a link egy oldallal korábban nyílik meg

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;

A SetActionRemoteDestinationEx maradék argumentumai éppolyan szó szerintiek. A ValueMask egy bithalmaz — 1 a balhoz, 2 a felsőhöz, 4 a jobbhoz, 8 az alsóhoz, 16 a nagyításhoz —, és a PDFlibPas ellenőrzi azt a DestType ellen, mielőtt bármit is megírna: egy dkFitR célhelynek pontosan 15-öt kell szolgáltatnia (mind a négy él, nincs nagyítás), a dkFit és a dkFitB 0-t kell szolgáltasson, és a dkFitH/dkFitV csak az egy releváns koordinátáját fogadja el. Bitek, amiket beállítatlanul hagysz egy egyébként érvényes masszkon belül, nincsenek kihagyva a tömbből; explicit PDF null-ként íródnak, amit az ISO 32000-1 "tartsd meg bármilyen értéket, amit a megjelenítő már tart" jelentéssel kezel az adott koordinátára — ez egy legitim mód arra, hogy "ugorj erre az oldalra, hagyd békén a nagyítást" mondj ahelyett hogy egy hiba lenne. Maga a nagyítás az általad átadott érték törtjeként tárolódik, így egy hívás, amely 150 százalékot kér, egy 1,5-ös tárolt értéket ad a tömbnek, és az érvényes bemeneti tartomány 0-tól 6400-ig terjed

Hogyan linkelsz egy PDF-hez, amely a saját dokumentumodon belül van beágyazva?

Az AddLinkToEmbeddedPDF építi fel a GoToE akciót, és céljának argumentuma, az EmbeddedFileName, egy név, nem egy útvonal: illeszkednie kell a Title sztringhez, amit már átadtak az EmbedFile-nak, amikor a mellékletet készítették, mert az a cím a szó szerinti kulcs, amit a PDFlibPas a dokumentum /EmbeddedFiles névfájában tárol, és a GoToE azt a nevet keresve oldódik fel, nem a fájlrendszerhez nyúlva ismét. A függvény csak azt ellenőrzi, hogy az EmbeddedFileName nem üres, és a TargetPage legalább 1 — adj át egy nevet, amit soha nem ágyaztak be ténylegesen, és a hívás még mindig sikert ad vissza, az akció még mindig megíródik, és a link egyszerűen nem oldódik fel minden olvasónak, aki rákattint

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;

Két verziópadló rétegződik itt, nem egy. Az EmbedFile-nak PDF 1.4-re van szüksége a /EmbeddedFiles névfához, és az AddLinkToEmbeddedPDF külön PDF 1.6-ra emeli a padlót magához a GoToE akciótípushoz, így a hatékony minimum bármely dokumentumhoz, amely ezt a funkciót használja, 1.6, nem 1.4. Vedd észre azt is, hogy a TargetPage itt 1-alapú, a szokásos PDFlibPas-konvenció — szándékos kontrasztban a nulla-alapú DestPage-gyel, amit az előző szakasz épp tárgyalt, és emlékeztető, hogy melyik oldalszám-séma alkalmazandó, az akciótípustól és a konkrét hívástól függ, nem egy általános szabálytól. Az akció célszótára hordozhat egy /R bejegyzést is C-vel gyerekhez, vagy P-vel szülőhöz, támogatva egy kétugrásos láncot egy beágyazott fájlba, vagy vissza kifelé annak konténerébe, bár az AddLinkToEmbeddedPDF csak valaha a gyerek irányt építi, mivel ez az, aminek van értelme egy beágyazást végző dokumentumnál, nem egy beágyazottnál

Launch-akciók: egy FileName, két sztring-cél, amelyek nem cserélhetők fel

A SetActionLaunchOptions egy Launch akció fájlcélját két különböző kulcsra írja egyetlen FileName argumentumból, és a két kulcs kétfajta sztringet tart. A legfelső szintű /F kulcs egy fájlspecifikáció-szótárat kap, ugyanazon útvonal-konverzión keresztül építve, amit a PDFlibPas a GoToR-hoz használ, ami a hordozható forma, amit az ISO 32000-1 §7.11.3 definiál egy fájlspecifikáció-szótárhoz. A /Win alszótár, amikor a PDFlibPas ír egyet, saját /F kulcsot kap, a nyers FileName értékre állítva pontosan úgy, ahogy átadták, minden konverzió nélkül, mert a /Win /F az ISO 32000-1 §12.6.4.5-ben dokumentált egyszerű Windows útvonal-sztringként, csak egy Windows-megjelenítőnek szánva olvasásra. Adj át egy hordozható, már konvertált útvonalat, azt várva, hogy mindkét kulcs azonosan végez, és a /Win másolat azt fogja hordozni, amit átadtál a függvénynek, érintetlenül

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;

Kezeld a Launch-ot a három közül a legmagasabb-súrlódásúnak, mert a teljes célja egy program futtatása vagy egy fájl megnyitása a PDF-homokozón kívül, és minden mainstream megjelenítő ennek megfelelően kezeli. Az Adobe Acrobat Enhanced Security-je alapértelmezetten blokkolja vagy megkérdezi a Launch-akciókat, hacsak a cél kifejezetten megbízható helyen nem ül, és a legtöbb vállalati Acrobat-telepítés bekapcsolva hagyja azt a védelmet. Egy Launch-akció egy nyilvánosságnak átadott dokumentumban tehát nem megbízható kiváltó: tervezz úgy, hogy blokkolják, megkérdezik, vagy csendben figyelmen kívül hagyja bármelyik megjelenítő, amely megnyitja a fájlt, és tartsd fenn zárt környezetekhez, ahol a megjelenítő bizalmi beállításait is te kontrollálod — egy belső kiosk, egy kontrollált vállalati kiépítés, egy dokumentum, amely soha nem hagyja el egy általad felügyelt gépet

A PDF/A-kapu: miért adhat vissza nullát a GoToR és a Launch hívás

A SetActionRemoteDestinationEx és a SetActionLaunchOptions mindkettő egyenesen megtagadja, amikor a céldokumentum bármilyen PDF/A megfelelőségi módban van: mindkettő ellenőrzi a dokumentum PDF/A módját mint első feltételét, és kilép 0 eredménnyel, mielőtt hozzányúlna az akcióhoz, kivétel dobása nélkül. Ez szándékos. A PDF/A korlátozásai az interaktív akciókra kifejezetten kizárják a Launch-ot, mivel egy archivális fájlnak megadni azt a képességet, hogy egy tetszőleges programot futtasson, pontosan az a fajta környezet-függő viselkedés, aminek megelőzésére a hosszútávú archiválási formátumok léteznek, és a PDFlibPas ugyanazt a konzervatív kaput alkalmazza a távoli-célhely-beállítóra ugyanabban a kódútvonalban. A gyakorlati következmény könnyen elmulasztható fejlesztés közben: az azonos hívás, amely egy közönséges PDF-en működik, lefordul, fut, és csendben semmit nem tesz egy olyan dokumentumon, amelyet PDF/A megfelelőségi szinttel töltöttek be, így ellenőrizd a visszatérési értéket ahelyett hogy sikert feltételeznél — egy 0 itt nem egy hibás-formátumú-bemenet hiba, a könyvtár megtagad egy kérést, amely ütközik a dokumentum saját megfelelőségi állításával

Hol illik a GoToR, a GoToE, és a Launch egy nagyobb PDFlibPas munkafolyamatba

A cikkben szereplő három akciótípus nem mind ugyanoda ér el. A dokumentum- és oldal-élettartam-akció-kiváltókról szóló kísérőcikk tárgyalja a SetDocumentAction-t és a SetPageAction-t, amelyek csatolhatnak egy GoToR vagy egy Launch akciót egy kiváltóhoz, mint a WillClose, a megosztott PDF_ACTION_BUILDER_REMOTE_DESTINATION és PDF_ACTION_BUILDER_LAUNCH konstansokon keresztül — ugyanaz az építő, amely egy sima URI- vagy JavaScript-kiváltót is lefed. A GoToE-nek nincs ilyen konstansa, és egyáltalán nincs útja abba az általános építőbe; az AddLinkToEmbeddedPDF az egyetlen mód, ahogyan a PDFlibPas egyet konstruál, ami szigorúan oldal-hotspot akcióvá teszi, soha nem dokumentum- vagy oldal-szintű kiváltóvá. Ahol a GoToR és a Launch eléri az általános építőt, a kompromisszum a kontroll: felépít egy GoToR-t, amely csak egy nevezett távoli célhelyre mutat, és egy Launch-akciót csak egy fájlnévvel és paraméterekkel, míg az explicit oldal-és-illesztéstípus-címzés, és a Windows-specifikus indítási beállítások, amiket ez a cikk tárgyal, csak közvetlenül a SetActionRemoteDestinationEx-en és a SetActionLaunchOptions-on keresztül érhetők el

Egy biztonsági tulajdonságot érdemes ismerni, mielőtt egy karbantartási eszközt építesz e beállítók köré. A SetActionRemoteDestinationEx és a SetActionLaunchOptions előbb a teljes csereakciót építi fel egy piszkozat-szótárban, és csak akkor törli és másolja az /F, /D vagy /Win, és /NewWindow kulcsokat az élő akcióra, amikor az a piszkozat-másolat érvényesítésre kerül — így egy hívás, amely elbukja az érvényesítést, akár egy tartományon-kívüli ValueMask-ból, akár egy üres FileName-ből, teljesen érintetlenül hagyja az eredeti akciót, és bármely /Next láncot, amely már rajta lóg, ahelyett hogy félig felülírná. Ez azért számít, mert a GoToR- és Launch-akciók mindketten ülhetnek egy /Next láncon belül, amit az AddActionNextRemoteDestinationEx-szel, az AddActionNextLaunchEx-szel, vagy az általánosabb AddActionNextEx-szel építettek, lehetővé téve, hogy egyetlen kiváltó egy JavaScript-naplóbejegyzést, majd egy távoli ugrást váltson ki sorban. A GoToR-, GoToE-, és Launch-konstrukció, ahogy itt le van írva, a Delphihez és C++Builderhez készült natív PDF-könyvtár, a PDFlibPas része