Articol tehnic

Acțiunile GoToR, GoToE și Launch în PDF-uri Delphi

PDFlibPas oferă dezvoltatorilor Delphi și C++Builder trei tipuri de acțiune pentru navigare care lasă pagina curentă în urmă: GoToR (Go To Remote) deschide o pagină specifică într-un alt fișier PDF, GoToE (Go To Embedded) deschide un fișier PDF încorporat în interiorul documentului curent, iar Launch rulează un program extern sau deschide un fișier prin shell-ul sistemului de operare. Toate trei trăiesc în ISO 32000-1 §12.6.4, secțiunea Action Types care de asemenea definește acțiunea GoTo obișnuită, iar fiecare poartă propria sa capcană pentru cel neatent: un număr de pagină care înseamnă ceva diferit în funcție de care apel îl construiește, o țintă care este un nume, nu o cale de fișier, și o pereche de parametri de șir care arată identici dar servesc două vizualizatoare diferite

Nimic din toate acestea nu este ipotetic. Un pachet de referință tehnică — un manual principal, un PDF de specificații pe care un distribuitor îl actualizează pe propriul său program, un utilitar de calibrare instalat alături de ambele — se bazează exact pe acest tip de conectare între documente: o referință încrucișată care trebuie să ajungă la pagina 5 a fișierului de specificații, o fișă tehnică care merită livrată în interiorul manualului mai degrabă decât alături de el, un link care predă direct instrumentului de calibrare. Acest articol este imaginea în oglindă a citirii înapoi a acțiunilor de semn de carte și adnotare dintr-un PDF existent: acea piesă acoperă consumarea unei acțiuni GoToR, Launch, sau GoToE pe care un alt producător a scris-o deja într-un fișier; aceasta acoperă construirea acelorași trei tipuri de acțiune de la zero, incluzând regulile la nivel de câmp pe care PDFlibPas le impune înainte de a comite un singur octet

Trei moduri prin care o acțiune PDF poate lăsa pagina curentă

PDFlibPas separă navigarea locală de tot restul la cheia /S a acțiunii, iar GoToR, GoToE și Launch sunt cele trei subtipuri a căror țintă stă în afara paginii curente: GoToR sub ISO 32000-1 §12.6.4.3, GoToE sub §12.6.4.4, și Launch sub §12.6.4.5, toate în interiorul secțiunii mai largi §12.6.4 Action Types care de asemenea definește acțiunea GoTo obișnuită. Destinația unei simple acțiuni GoTo numește un obiect de pagină care există deja în interiorul documentului, așa că PDFlibPas o poate valida imediat; GoToR și GoToE nu pot face asta în același mod, întrucât fișierul extern ar putea nici măcar să nu existe pe această mașină, iar numărul de pagini al unui fișier încorporat nu este ceva ce documentul gazdă urmărește, așa că ambele poartă o referință nerezolvată în loc de o legătură dură — o specificație de fișier plus o destinație pentru GoToR, un nume de fișier-încorporat plus o pagină țintă pentru GoToE — în timp ce Launch renunță complet la conceptul de destinație și doar numește ceva pentru ca sistemul de operare să ruleze sau deschidă. Acea separare apare ca două familii de apeluri pe partea de scriere: constructori de nivel-înalt, dintr-o-singură-lovitură precum AddLinkToFile, AddLinkToFileEx, AddLinkToEmbeddedPDF, și AddLinkToLocalFile creează un link zonă-clicabilă de pagină și acțiunea sa împreună, acoperind majoritatea aspectelor reale — o linie de text sau o pictogramă pe care un cititor dă clic — în timp ce setteri de nivel mai jos precum SetActionRemoteDestinationEx, SetActionLaunchOptions, și corespondenții lor AddActionNext* atașează sau înlocuiesc o acțiune pe ceva ce dețineți deja un handle: un semn de carte existent, un declanșator de câmp de formular, sau un eveniment de ciclu de viață la nivel de document sau pagină. Ambele familii ajung să scrie aceleași forme de dicționar; diferența este unde stați când le apelați, și, așa cum acoperă secțiunea următoare, ce înseamnă un număr de pagină când o faceți

Cum construiți un link GoToR care deschide o pagină într-un alt fișier PDF?

O acțiune GoToR are nevoie de două lucruri — o specificație de fișier și o destinație în interiorul acelui fișier — iar PDFlibPas expune două apeluri diferite pentru a furniza a doua parte, fiecare cu propria sa convenție de numerotare a paginii. AddLinkToFile și AddLinkToFileEx, constructorii de nivel-înalt de zonă-clicabilă de pagină, validează argumentul lor Page sau DestPage ca mai mare decât zero, aceeași numerotare bazată-pe-1 pe care PDFlibPas o folosește peste tot altundeva, incluzând SelectPage. SetActionRemoteDestinationEx, setter-ul de nivel mai jos folosit pentru a atașa sau înlocui o acțiune GoToR pe ceva ce dețineți deja un handle, în schimb validează DestPage ca mai mare sau egal cu zero și îl scrie direct în tabloul de destinație explicit al acțiunii fără nicio ajustare: vrea indexul brut, bazat-pe-zero, al paginii din documentul țintă, numerotarea pe care ISO 32000-1 o specifică pentru o destinație explicită la distanță. Apelați setter-ul de nivel jos cu același număr pe care l-ați da constructorului de nivel-înalt, iar link-ul se deschide cu o pagină mai devreme

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;

Restul argumentelor SetActionRemoteDestinationEx sunt la fel de literale. ValueMask este un set de biți — 1 pentru stânga, 2 pentru sus, 4 pentru dreapta, 8 pentru jos, 16 pentru zoom — iar PDFlibPas îl verifică față de DestType înainte de a scrie orice: o destinație dkFitR trebuie să furnizeze exact 15 (toate patru marginile, fără zoom), dkFit și dkFitB trebuie să furnizeze 0, iar dkFitH/dkFitV acceptă doar propria lor coordonată relevantă. Biții pe care îi lăsați nesetați în interiorul unei măști altfel-valide nu sunt omiși din tablou; sunt scriși ca un null PDF explicit, ceea ce ISO 32000-1 tratează ca „păstrează orice valoare are deja vizualizatorul" pentru acea coordonată — un mod legitim de a spune „sari la această pagină, lasă zoom-ul în pace" mai degrabă decât o omisiune. Zoom-ul însuși este stocat ca o fracțiune a valorii pe care o transmiteți, așa că un apel care cere 150 la sută predă tabloului o valoare stocată de 1.5, iar intervalul de intrare valid este 0 la 6400

Cum vă legați la un PDF care este încorporat în interiorul propriului dvs. document?

AddLinkToEmbeddedPDF construiește acțiunea GoToE, iar argumentul său țintă, EmbeddedFileName, este un nume, nu o cale: trebuie să se potrivească cu șirul Title deja transmis lui EmbedFile atunci când a fost făcut atașamentul, pentru că acel titlu este cheia literală pe care PDFlibPas o stochează în arborele de nume /EmbeddedFiles al documentului, iar GoToE se rezolvă căutând acel nume, nu atingând din nou sistemul de fișiere. Funcția doar verifică dacă EmbeddedFileName este ne-gol și TargetPage este cel puțin 1 — transmiteți un nume care nu a fost niciodată efectiv încorporat, iar apelul tot returnează succes, acțiunea tot este scrisă, iar link-ul pur și simplu nu reușește să se rezolve pentru fiecare cititor care dă clic pe el

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;

Două planșee de versiune se stivuiesc aici, nu unul. EmbedFile are nevoie de PDF 1.4 pentru arborele de nume /EmbeddedFiles, iar AddLinkToEmbeddedPDF separat ridică planșeul la PDF 1.6 pentru tipul de acțiune GoToE însuși, așa că minimul efectiv pentru orice document care folosește această caracteristică este 1.6, nu 1.4. Observați de asemenea că TargetPage aici este bazat-pe-1, convenția obișnuită PDFlibPas — un contrast deliberat cu DestPage-ul bazat-pe-zero pe care secțiunea anterioară tocmai l-a acoperit, și o amintire că care schemă de numerotare a paginii se aplică depinde de tipul de acțiune și apelul specific, nu de o regulă generală. Dicționarul țintă al acțiunii poate de asemenea purta o intrare /R de C pentru copil sau P pentru părinte, susținând un lanț cu doi pași într-un fișier încorporat sau înapoi la containerul său, deși AddLinkToEmbeddedPDF construiește vreodată doar direcția copil, întrucât aceea este cea care are sens dintr-un document care face încorporarea, nu unul care este încorporat

Acțiuni Launch: un FileName, două ținte de șir care nu sunt interschimbabile

SetActionLaunchOptions scrie ținta de fișier a unei acțiuni Launch în două chei diferite dintr-un singur argument FileName, iar cele două chei conțin două tipuri diferite de șir. Cheia de nivel superior /F primește un dicționar de specificație de fișier, construit prin aceeași conversie de cale pe care PDFlibPas o folosește pentru GoToR, ceea ce este forma portabilă pe care ISO 32000-1 §7.11.3 o definește pentru un dicționar de specificație de fișier. Sub-dicționarul /Win, atunci când PDFlibPas scrie unul, primește propria sa cheie /F setată la valoarea brută FileName exact așa cum a fost transmisă, fără nicio conversie deloc, pentru că /Win /F este documentat în ISO 32000-1 §12.6.4.5 ca un simplu șir de cale Windows menit doar pentru ca un vizualizator Windows să îl citească. Transmiteți o cale portabilă, deja convertită, așteptând ca ambele chei să ajungă identice, iar copia /Win va purta orice ați predat funcției, neatinsă

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;

Tratați Launch ca acțiunea cu cea mai mare fricțiune dintre cele trei, pentru că întregul său scop este rularea unui program sau deschiderea unui fișier în afara sandbox-ului PDF, iar fiecare vizualizator popular o tratează în consecință. Enhanced Security din Adobe Acrobat blochează sau cere confirmare pe acțiuni Launch implicit, decât dacă ținta stă într-o locație explicit de încredere, iar majoritatea implementărilor Acrobat de întreprindere lasă acea protecție pornită. O acțiune Launch într-un document predat publicului nu este deci un declanșator fiabil: planificați ca să fie blocată, să ceară confirmare, sau ignorată silențios de orice vizualizator deschide fișierul, și păstrați-o pentru medii închise unde de asemenea controlați setările de încredere ale vizualizatorului — un chioșc intern, o implementare corporativă controlată, un document care nu părăsește niciodată o mașină pe care o gestionați

Poarta PDF/A: de ce pot returna zero apelurile GoToR și Launch

Atât SetActionRemoteDestinationEx, cât și SetActionLaunchOptions refuză direct atunci când documentul țintă este în orice mod de conformitate PDF/A: ambele verifică modul PDF/A al documentului ca prima lor condiție și ies cu un rezultat de 0 înainte de a atinge acțiunea, nicio excepție ridicată. Aceasta este deliberată. Restricțiile PDF/A asupra acțiunilor interactive exclud specific Launch, întrucât a oferi unui fișier de arhivă capacitatea de a rula un program arbitrar este exact genul de comportament dependent-de-mediu pe care formatele de arhivare pe termen lung există pentru a-l preveni, iar PDFlibPas aplică aceeași poartă conservatoare setter-ului de go-to la distanță în aceeași cale de cod. Consecința practică este ușor de ratat în timpul dezvoltării: apelul identic care funcționează pe un PDF obișnuit se va compila, va rula, și va face silențios nimic pe un document încărcat cu un nivel de conformitate PDF/A setat, așa că verificați valoarea de retur în loc să presupuneți succesul — un 0 aici nu este o eroare de intrare malformată, este biblioteca refuzând o cerere care intră în conflict cu propria declarație de conformitate a documentului

Unde se potrivesc GoToR, GoToE și Launch într-un flux de lucru PDFlibPas mai mare

Cele trei tipuri de acțiune din acest articol nu ajung toate în aceleași locuri. Articolul complementar despre declanșatorii de acțiune de ciclu de viață document și pagină acoperă SetDocumentAction și SetPageAction, care pot atașa o acțiune GoToR sau Launch unui declanșator precum WillClose prin constantele partajate PDF_ACTION_BUILDER_REMOTE_DESTINATION și PDF_ACTION_BUILDER_LAUNCH — același constructor care acoperă de asemenea un declanșator URI sau JavaScript simplu. GoToE nu are o astfel de constantă și nicio cale în acel constructor generic deloc; AddLinkToEmbeddedPDF este singurul mod în care PDFlibPas construiește unul, ceea ce o face strict o acțiune zonă-clicabilă de pagină, niciodată un declanșator la nivel de document sau pagină. Acolo unde GoToR și Launch chiar ajung la constructorul generic, compromisul este controlul: construiește un GoToR care indică doar spre o destinație la distanță numită și o acțiune Launch cu doar un nume de fișier și parametri, în timp ce adresarea explicită pagină-și-tip-de-adaptare și opțiunile de lansare specifice Windows acoperite în acest articol sunt atinse doar prin SetActionRemoteDestinationEx și SetActionLaunchOptions direct

O proprietate de siguranță merită cunoscută înainte de a construi un instrument de întreținere în jurul acestor setteri. SetActionRemoteDestinationEx și SetActionLaunchOptions construiesc întreaga acțiune de înlocuire într-un dicționar de lucru mai întâi, și doar șterg și copiază cheile /F, /D sau /Win, și /NewWindow pe acțiunea vie odată ce acea copie de lucru se validează — așa că un apel care eșuează validarea, fie dintr-un ValueMask în afara intervalului sau un FileName gol, lasă acțiunea originală, și orice lanț /Next deja atârnat de ea, complet neatinse, în loc de pe jumătate suprascrise. Asta contează pentru că acțiunile GoToR și Launch pot ambele sta în interiorul unui lanț /Next construit cu AddActionNextRemoteDestinationEx, AddActionNextLaunchEx, sau mai generalul AddActionNextEx, permițând unui singur declanșator să declanșeze o intrare de jurnal JavaScript și apoi un salt la distanță în secvență. Construcția GoToR, GoToE și Launch așa cum este descrisă aici face parte din PDFlibPas, biblioteca PDF nativă pentru Delphi și C++Builder