Teknisk artikkel

GoToR, GoToE, og Launch-handlinger i Delphi-PDF-er

PDFlibPas gir Delphi- og C++Builder-utviklere tre handlingstyper for navigasjon som forlater den gjeldende siden: GoToR (Go To Remote) åpner en spesifikk side i en annen PDF-fil, GoToE (Go To Embedded) åpner en PDF-fil innebygd inne i det gjeldende dokumentet, og Launch kjører et eksternt program eller åpner en fil gjennom operativsystemets shell. Alle tre bor i ISO 32000-1 §12.6.4, Action Types-seksjonen som også definerer den hverdagslige GoTo-handlingen, og hver av dem bærer sin egen felle for den uforsiktige: et sidetall som betyr noe forskjellig avhengig av hvilket kall som bygger det, et mål som er et navn snarere enn en filsti, og et par strengparametere som ser identiske ut, men betjener to forskjellige fremvisere

Ingenting av dette er hypotetisk. En teknisk referansepakke — en hovedmanual, en spesifikasjons-PDF en distributør oppdaterer på sin egen tidsplan, et kalibreringsverktøy installert ved siden av begge — lener seg på nøyaktig denne typen kryss-dokument-kobling: en kryssreferanse som må lande på side 5 i spesifikasjonsfilen, et datablad verdt å sende inni manualen snarere enn ved siden av den, en lenke som gir stafettpinnen rett videre til kalibreringsverktøyet. Denne artikkelen er speilbildet av å lese bokmerke- og annoterings-handlinger tilbake ut av en eksisterende PDF: den artikkelen dekker å konsumere en GoToR-, Launch-, eller GoToE-handling en annen produsent allerede skrev inn i en fil; denne dekker å bygge de samme tre handlingstypene fra bunnen av, inkludert felt-nivå-reglene PDFlibPas håndhever før den forplikter en eneste byte

Tre måter en PDF-handling kan forlate den gjeldende siden på

PDFlibPas skiller lokal navigasjon fra alt annet ved handlingens /S-nøkkel, og GoToR, GoToE, og Launch er de tre undertypene hvis mål ligger utenfor den gjeldende siden: GoToR under ISO 32000-1 §12.6.4.3, GoToE under §12.6.4.4, og Launch under §12.6.4.5, alle inne i den bredere §12.6.4 Action Types-seksjonen som også definerer den hverdagslige GoTo-handlingen. En ren GoTo-handlings destinasjon navngir et sideobjekt som allerede finnes inne i dokumentet, så PDFlibPas kan validere det umiddelbart; GoToR og GoToE kan ikke gjøre det på samme måte, ettersom den eksterne filen kanskje ikke engang finnes på denne maskinen, og en innebygd fils sideantall ikke er noe vertsdokumentet sporer, så begge bærer en uløst referanse i stedet for en hard lenke — en filspesifikasjon pluss en destinasjon for GoToR, et innebygd-fil-navn pluss en målside for GoToE — mens Launch dropper destinasjonskonseptet helt og bare navngir noe for operativsystemet å kjøre eller åpne. Det skillet viser seg som to kallfamilier på skrivesiden: høynivå, ett-kalls-byggere slik som AddLinkToFile, AddLinkToFileEx, AddLinkToEmbeddedPDF, og AddLinkToLocalFile oppretter en side-hotspot-lenke-annotering og handlingen dens sammen, og dekker de fleste ekte layouter — en tekstlinje eller et ikon en leser klikker — mens lavere-nivå-settere slik som SetActionRemoteDestinationEx, SetActionLaunchOptions, og deres AddActionNext*-motstykker fester eller erstatter en handling på noe man allerede holder et håndtak til: et eksisterende bokmerke, en skjemafelt-utløser, eller en dokument- eller side-nivå-livssyklushendelse. Begge familiene ender opp med å skrive de samme ordbok-formene; forskjellen er hvor man står når man kaller dem, og, som neste avsnitt dekker, hva et sidetall betyr når man gjør det

Hvordan bygger man en GoToR-lenke som åpner en side i en annen PDF-fil?

En GoToR-handling trenger to ting — en filspesifikasjon og en destinasjon inne i den filen — og PDFlibPas eksponerer to forskjellige kall for å levere den andre delen, hver med sin egen sidenummererings-konvensjon. AddLinkToFile og AddLinkToFileEx, høynivå-side-hotspot-byggerne, validerer sitt Page- eller DestPage-argument som større enn null, den samme 1-baserte nummereringen PDFlibPas bruker overalt ellers, inkludert SelectPage. SetActionRemoteDestinationEx, den lavere-nivå-setteren brukt for å feste eller erstatte en GoToR-handling på noe man allerede har et håndtak til, validerer i stedet DestPage som større enn eller lik null og skriver den rett inn i handlingens eksplisitte destinasjons-array uten justering: den vil ha måldokumentets rå, null-baserte sideindeks, nummereringen ISO 32000-1 spesifiserer for en ekstern eksplisitt destinasjon. Kall lavnivå-setteren med det samme tallet man ville gitt høynivå-byggeren, og lenken åpner én side for tidlig

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;

Resten av SetActionRemoteDestinationExs argumenter er like bokstavelige. ValueMask er et bit-sett — 1 for venstre, 2 for topp, 4 for høyre, 8 for bunn, 16 for zoom — og PDFlibPas sjekker det mot DestType før den skriver noe: en dkFitR-destinasjon må levere nøyaktig 15 (alle fire kanter, ingen zoom), dkFit og dkFitB må levere 0, og dkFitH/dkFitV aksepterer bare sin ene relevante koordinat. Bit man lar stå usatt innenfor en ellers-gyldig maske, utelates ikke fra arrayet; de skrives som en eksplisitt PDF-null, som ISO 32000-1 behandler som «behold hvilken som helst verdi fremviseren allerede har» for den koordinaten — en legitim måte å si «hopp til denne siden, la zoom være» på snarere enn en forglemmelse. Zoom selv lagres som en brøkdel av verdien man sender, så et kall som ber om 150 prosent, gir arrayet en lagret verdi på 1,5, og det gyldige inndataområdet er 0 til 6400

Hvordan lenker man til en PDF innebygd inne i sitt eget dokument?

AddLinkToEmbeddedPDF bygger GoToE-handlingen, og målargumentet dets, EmbeddedFileName, er et navn snarere enn en sti: det må matche Title-strengen allerede sendt til EmbedFile da vedlegget ble laget, fordi den tittelen er den bokstavelige nøkkelen PDFlibPas lagrer i dokumentets /EmbeddedFiles-navnetre, og GoToE løser ved å slå opp det navnet, ikke ved å røre filsystemet igjen. Funksjonen sjekker bare at EmbeddedFileName ikke er tom og TargetPage er minst 1 — send et navn som aldri faktisk ble bygget inn, og kallet returnerer fortsatt suksess, handlingen skrives fortsatt, og lenken feiler ganske enkelt å løses for hver leser som klikker den

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;

To versjons-gulv stables her, ikke ett. EmbedFile trenger PDF 1.4 for /EmbeddedFiles-navnetreet, og AddLinkToEmbeddedPDF hever separat gulvet til PDF 1.6 for selve GoToE-handlingstypen, så det effektive minimumet for ethvert dokument som bruker denne funksjonen, er 1.6, ikke 1.4. Legg også merke til at TargetPage her er 1-basert, den vanlige PDFlibPas-konvensjonen — en bevisst kontrast til den null-baserte DestPage-en forrige avsnitt nettopp dekket, og en påminnelse om at hvilket sidetall-skjema som gjelder, avhenger av handlingstypen og det spesifikke kallet, ikke av én blankett-regel. Handlingens mål-ordbok kan også bære en /R-oppføring på C for barn eller P for forelder, som støtter en to-hopp-kjede inn i en innebygd fil eller tilbake ut til containeren dens, selv om AddLinkToEmbeddedPDF bare noensinne bygger barn-retningen, ettersom det er den som gir mening fra et dokument som gjør innbyggingen snarere enn å bli bygget inn

Launch-handlinger: ett FileName, to strengmål som ikke er utbyttbare

SetActionLaunchOptions skriver en Launch-handlings filmål til to forskjellige nøkler fra ett enkelt FileName-argument, og de to nøklene holder to forskjellige typer streng. Topp-nivå-/F-nøkkelen får en filspesifikasjons-ordbok, bygget gjennom den samme sti-konverteringen PDFlibPas bruker for GoToR, noe som er den portable formen ISO 32000-1 §7.11.3 definerer for en filspesifikasjons-ordbok. /Win-under-ordboken, når PDFlibPas skriver en, får sin egen /F-nøkkel satt til den rå FileName-verdien nøyaktig slik den ble sendt inn, uten noen konvertering i det hele tatt, fordi /Win /F er dokumentert i ISO 32000-1 §12.6.4.5 som en ren Windows-sti-streng ment bare for en Windows-fremviser å lese. Send en portabel, allerede-konvertert sti i forventning om at begge nøklene ender opp identiske, og /Win-kopien vil bære hva som enn du ga funksjonen, urørt

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;

Behandle Launch som den mest friksjonsfylte handlingen av de tre, fordi hele hensikten dens er å kjøre et program eller åpne en fil utenfor PDF-sandkassen, og hver mainstream-fremviser behandler den deretter. Adobe Acrobats Enhanced Security blokkerer eller spør ved Launch-handlinger som standard med mindre målet ligger på et eksplisitt betrodd sted, og de fleste bedrifts-Acrobat-utrullinger lar den beskyttelsen stå på. En Launch-handling i et dokument gitt til allmennheten er derfor ikke en pålitelig utløser: planlegg for at den blir blokkert, spurt om, eller stille ignorert av hvilken som helst fremviser som åpner filen, og spar den til lukkede miljøer der man også kontrollerer fremviserens tillitsinnstillinger — en intern kiosk, en kontrollert bedriftsutrulling, et dokument som aldri forlater en maskin man administrerer

PDF/A-sperren: hvorfor GoToR- og Launch-kall kan returnere null

SetActionRemoteDestinationEx og SetActionLaunchOptions nekter begge rett ut når måldokumentet er i noen PDF/A-konformitetsmodus: begge sjekker dokumentets PDF/A-modus som sin aller første betingelse og avslutter med et resultat på 0 før de rører handlingen, ingen unntak kastet. Dette er bevisst. PDF/A-ens begrensninger på interaktive handlinger utelukker Launch spesifikt, ettersom å gi en arkivfil muligheten til å kjøre et vilkårlig program er nøyaktig den typen miljø-avhengige atferd langtids-arkiveringsformater finnes for å forhindre, og PDFlibPas anvender den samme konservative sperren på den eksterne go-to-setteren i den samme kodeveien. Den praktiske konsekvensen er lett å overse under utvikling: det identiske kallet som fungerer på en vanlig PDF, vil kompilere, kjøre, og stille gjøre ingenting på et dokument lastet inn med et PDF/A-konformitetsnivå satt, så sjekk returverdien i stedet for å anta suksess — en 0 her er ikke en feilformet-inndata-feil, det er biblioteket som avslår en forespørsel som strider mot dokumentets eget konformitetskrav

Hvor GoToR, GoToE, og Launch passer inn i en større PDFlibPas-arbeidsflyt

De tre handlingstypene i denne artikkelen når ikke alle de samme stedene. Følgeartikkelen om dokument- og side-livssyklus-handlingsutløsere dekker SetDocumentAction og SetPageAction, som kan feste en GoToR- eller Launch-handling til en utløser som WillClose gjennom de delte PDF_ACTION_BUILDER_REMOTE_DESTINATION- og PDF_ACTION_BUILDER_LAUNCH-konstantene — den samme byggeren som også dekker en ren URI- eller JavaScript-utløser. GoToE har ingen slik konstant og ingen vei inn i den generiske byggeren i det hele tatt; AddLinkToEmbeddedPDF er den eneste måten PDFlibPas konstruerer en på, noe som gjør den strengt til en side-hotspot-handling, aldri en dokument- eller side-nivå-utløser. Der GoToR og Launch faktisk når den generiske byggeren, er avveiningen kontroll: den bygger en GoToR som bare peker på en navngitt ekstern destinasjon og en Launch-handling med bare et filnavn og parametere, mens den eksplisitte side-og-tilpasningstype-adresseringen og de Windows-spesifikke launch-opsjonene dekket i denne artikkelen bare nås gjennom SetActionRemoteDestinationEx og SetActionLaunchOptions direkte

Én sikkerhetsegenskap er verdt å kjenne til før man bygger et vedlikeholdsverktøy rundt disse setterne. SetActionRemoteDestinationEx og SetActionLaunchOptions bygger hele erstatningshandlingen i en skisse-ordbok først, og sletter og kopierer bare /F-, /D- eller /Win-, og /NewWindow-nøklene inn på den levende handlingen når den skisse-kopien først validerer — så et kall som feiler validering, enten fra en ValueMask utenfor rekkevidde eller et tomt FileName, etterlater den opprinnelige handlingen, og enhver /Next-kjede som allerede henger av den, fullstendig urørt snarere enn halvt overskrevet. Det betyr noe fordi GoToR- og Launch-handlinger begge kan sitte inne i en /Next-kjede bygget med AddActionNextRemoteDestinationEx, AddActionNextLaunchEx, eller den mer generelle AddActionNextEx, som lar én enkelt utløser avfyre en JavaScript-loggoppføring og deretter et eksternt hopp i sekvens. GoToR-, GoToE-, og Launch-konstruksjon som beskrevet her, er en del av PDFlibPas, det native PDF-biblioteket for Delphi og C++Builder