Teknisk artikel

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

PDFlibPas giver Delphi- og C++Builder-udviklere tre handlingstyper til navigation, der forlader den aktuelle side: GoToR (Go To Remote) åbner en specifik side i en anden PDF-fil, GoToE (Go To Embedded) åbner en PDF-fil indlejret inde i det aktuelle dokument, og Launch kører et eksternt program eller åbner en fil gennem operativsystemets shell. Alle tre bor i ISO 32000-1 §12.6.4, Action Types-afsnittet der også definerer den almindelige GoTo-handling, og hver bærer sin egen fælde for den uforsigtige: et sidetal der betyder noget forskelligt afhængigt af, hvilket kald der bygger det, et mål der er et navn frem for en filsti, og et par af strengparametre der ser identiske ud, men betjener to forskellige fremvisere

Intet af dette er hypotetisk. En teknisk reference-pakke — en hovedmanual, en specifikations-PDF en distributør opdaterer på sin egen tidsplan, et kalibreringsværktøj installeret ved siden af begge — læner sig på præcis denne slags kobling på tværs af dokumenter: en kryds-reference, der skal lande på side 5 af spec-filen, et datablad værd at sende med inde i manualen frem for ved siden af den, et link der overdrager direkte til kalibreringsværktøjet. Denne artikel er spejlbilledet af at læse bogmærke- og annotations-handlinger tilbage ud af en eksisterende PDF: det stykke dækker at forbruge en GoToR-, Launch- eller GoToE-handling, en anden producent allerede skrev ind i en fil; denne dækker at bygge de samme tre handlingstyper fra bunden, inklusive de felt-niveau-regler PDFlibPas håndhæver, før den forpligter en eneste byte

Tre måder for en PDF-handling at forlade den aktuelle side

PDFlibPas adskiller lokal navigation fra alt andet ved handlingens /S-nøgle, og GoToR, GoToE og Launch er de tre undertyper, hvis mål sidder uden for den aktuelle side: GoToR under ISO 32000-1 §12.6.4.3, GoToE under §12.6.4.4, og Launch under §12.6.4.5, alle inden i det bredere §12.6.4 Action Types-afsnit, der også definerer den almindelige GoTo-handling. En almindelig GoTo-handlings destination navngiver et side-objekt, der allerede findes inde i dokumentet, så PDFlibPas kan validere den med det samme; GoToR og GoToE kan ikke gøre det på samme måde, da den eksterne fil måske ikke engang findes på denne maskine, og en indlejret fils sideantal ikke er noget, værtsdokumentet sporer, så begge bærer en uløst reference i stedet for et hårdt link — en filspecifikation plus en destination til GoToR, et indlejret-fil-navn plus en målside til GoToE — mens Launch dropper destinations-konceptet helt og bare navngiver noget for operativsystemet at køre eller åbne. Det skel viser sig som to kald-familier på skrive-siden: høj-niveau, ét-skud-byggere såsom AddLinkToFile, AddLinkToFileEx, AddLinkToEmbeddedPDF og AddLinkToLocalFile opretter en side-hotspot-link-annotation og dens handling sammen, og dækker de fleste rigtige layouts — en linje tekst eller et ikon en læser klikker på — mens lavere-niveau-sættere såsom SetActionRemoteDestinationEx, SetActionLaunchOptions, og deres AddActionNext*-modstykker knytter eller erstatter en handling på noget, man allerede holder et handle til: et eksisterende bogmærke, en formularfelt-trigger, eller en dokument- eller side-niveau-livscyklus-hændelse. Begge familier ender med at skrive de samme ordbogs-former; forskellen er, hvor man står, når man kalder dem, og, som næste afsnit dækker, hvad et sidetal betyder når man gør det

Hvordan bygger man et GoToR-link, der åbner en side i en anden PDF-fil?

En GoToR-handling har brug for to ting — en filspecifikation og en destination inde i den fil — og PDFlibPas eksponerer to forskellige kald til at levere den anden del, hver med sin egen sidetal-konvention. AddLinkToFile og AddLinkToFileEx, de høj-niveau-side-hotspot-byggere, validerer deres Page- eller DestPage-argument som større end nul, den samme 1-baserede nummerering PDFlibPas bruger alle andre steder, inklusive SelectPage. SetActionRemoteDestinationEx, den lavere-niveau-sætter brugt til at knytte eller erstatte en GoToR-handling på noget, man allerede har et handle til, validerer i stedet DestPage som større end eller lig nul og skriver den lige ind i handlingens eksplicitte destinations-array uden justering: den vil have målidokumentets rå, nul-baserede side-indeks, den nummerering ISO 32000-1 angiver for en ekstern eksplicit destination. Kald den lav-niveau-sætter med det samme tal, man ville give den høj-niveau-bygger, og linket åbner én side for tidligt

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 af SetActionRemoteDestinationEx's argumenter er lige så bogstavelige. ValueMask er et bit-sæt — 1 for venstre, 2 for top, 4 for højre, 8 for bund, 16 for zoom — og PDFlibPas tjekker det mod DestType, før den skriver noget: en dkFitR-destination skal levere præcis 15 (alle fire kanter, ingen zoom), dkFit og dkFitB skal levere 0, og dkFitH/dkFitV accepterer kun deres ene relevante koordinat. Bits man lader stå usat inden for en ellers-gyldig maske udelades ikke fra arrayet; de skrives som en eksplicit PDF-null, som ISO 32000-1 behandler som "behold hvilken som helst værdi fremviseren allerede har" for den koordinat — en legitim måde at sige "hop til denne side, lad zoomet være" frem for en forglemmelse. Zoom selv gemmes som en brøkdel af den værdi, man sender, så et kald der beder om 150 procent, giver arrayet en gemt værdi på 1.5, og det gyldige input-interval er 0 til 6400

Hvordan linker man til en PDF, der er indlejret inde i ens eget dokument?

AddLinkToEmbeddedPDF bygger GoToE-handlingen, og dens mål-argument, EmbeddedFileName, er et navn frem for en sti: det skal matche Title-strengen allerede sendt til EmbedFile, da vedhæftningen blev lavet, fordi den titel er den bogstavelige nøgle, PDFlibPas gemmer i dokumentets /EmbeddedFiles-navnetræ, og GoToE løser ved at slå det navn op, ikke ved at røre filsystemet igen. Funktionen tjekker kun, at EmbeddedFileName er ikke-tom, og TargetPage er mindst 1 — send et navn, der aldrig rent faktisk blev indlejret, og kaldet returnerer stadig succes, handlingen skrives stadig, og linket fejler simpelthen at løse for hver læser der klikker på det

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 version-gulve stables her, ikke ét. EmbedFile har brug for PDF 1.4 til /EmbeddedFiles-navnetræet, og AddLinkToEmbeddedPDF hæver separat gulvet til PDF 1.6 til selve GoToE-handlingstypen, så det effektive minimum for ethvert dokument, der bruger denne funktion, er 1.6, ikke 1.4. Bemærk også, at TargetPage her er 1-baseret, den almindelige PDFlibPas-konvention — en bevidst kontrast til det nul-baserede DestPage, forrige afsnit lige dækkede, og en påmindelse om, at hvilket sidetal-skema der gælder, afhænger af handlingstypen og det specifikke kald, ikke af én overordnet regel. Handlingens mål-ordbog kan også bære en /R-post på C for barn eller P for forælder, og understøtter en to-hop-kæde ind i en indlejret fil eller tilbage ud til dens container, selvom AddLinkToEmbeddedPDF kun nogensinde bygger barn-retningen, da det er den, der giver mening fra et dokument, der udfører indlejringen, frem for at blive indlejret

Launch-handlinger: ét FileName, to strengmål der ikke er ombyttelige

SetActionLaunchOptions skriver en Launch-handlings filmål til to forskellige nøgler fra ét enkelt FileName-argument, og de to nøgler holder to forskellige slags streng. Top-niveau-/F-nøglen får en filspecifikation-ordbog, bygget gennem den samme sti-konvertering PDFlibPas bruger til GoToR, hvilket er den bærbare form ISO 32000-1 §7.11.3 definerer for en filspecifikation-ordbog. /Win-under-ordbogen, når PDFlibPas skriver en, får sin egen /F-nøgle sat til den rå FileName-værdi præcis som sendt ind, uden nogen konvertering overhovedet, fordi /Win /F er dokumenteret i ISO 32000-1 §12.6.4.5 som en almindelig Windows-sti-streng ment kun til en Windows-fremviser at læse. Send en bærbar, allerede-konverteret sti med forventning om, at begge nøgler ender identiske, og /Win-kopien vil bære, hvad end man overdrog til funktionen, 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;

Behandl Launch som den mest gnidningsfulde handling af de tre, fordi dens hele formål er at køre et program eller åbne en fil uden for PDF-sandkassen, og enhver mainstream fremviser behandler den derefter. Adobe Acrobats Enhanced Security blokerer eller spørger på Launch-handlinger som standard, medmindre målet sidder i en eksplicit betroet placering, og de fleste virksomheds-Acrobat-udrulninger lader den beskyttelse stå slået til. En Launch-handling i et dokument overdraget til offentligheden er derfor ikke en pålidelig trigger: planlæg for at den bliver blokeret, spurgt om, eller i stilhed ignoreret af hvilken som helst fremviser, der åbner filen, og gem den til lukkede miljøer, hvor man også kontrollerer fremviserens tillids-indstillinger — en intern kiosk, en kontrolleret virksomhedsudrulning, et dokument der aldrig forlader en maskine man administrerer

PDF/A-gaten: hvorfor GoToR- og Launch-kald kan returnere nul

SetActionRemoteDestinationEx og SetActionLaunchOptions nægter begge direkte, når måldokumentet er i en hvilken som helst PDF/A-konformitetstilstand: begge tjekker dokumentets PDF/A-tilstand som deres allerførste betingelse og afslutter med et resultat på 0, før de rører handlingen, ingen undtagelse rejst. Dette er bevidst. PDF/A's begrænsninger på interaktive handlinger udelukker Launch specifikt, da at give en arkivfil evnen til at køre et vilkårligt program er præcis den slags miljø-afhængige opførsel, langtids-arkiverings-formater findes for at forhindre, og PDFlibPas anvender den samme konservative gate på den eksterne go-to-sætter i den samme kodevej. Den praktiske konsekvens er let at overse under udvikling: det identiske kald, der virker på en almindelig PDF, vil kompilere, køre, og i stilhed gøre ingenting på et dokument indlæst med et PDF/A-konformitetsniveau sat, så tjek returværdien frem for at antage succes — en 0 her er ikke en misdannet-input-fejl, det er biblioteket der afviser en anmodning, der er i konflikt med dokumentets egen konformitets-erklæring

Hvor GoToR, GoToE og Launch passer ind i et større PDFlibPas-workflow

De tre handlingstyper i denne artikel når ikke alle de samme steder. Følgeartiklen om dokument- og side-livscyklus-handlings-triggere dækker SetDocumentAction og SetPageAction, som kan knytte en GoToR- eller en Launch-handling til en trigger som WillClose gennem de delte PDF_ACTION_BUILDER_REMOTE_DESTINATION- og PDF_ACTION_BUILDER_LAUNCH-konstanter — den samme bygger, der også dækker en almindelig URI- eller JavaScript-trigger. GoToE har ingen sådan konstant og ingen vej ind i den generiske bygger overhovedet; AddLinkToEmbeddedPDF er den eneste måde, PDFlibPas konstruerer en på, hvilket gør den strengt en side-hotspot-handling, aldrig en dokument- eller side-niveau-trigger. Hvor GoToR og Launch rent faktisk når den generiske bygger, er afvejningen kontrol: den bygger en GoToR, der kun peger på en navngiven ekstern destination, og en Launch-handling med kun et filnavn og parametre, mens den eksplicitte side-og-fit-type-adressering og de Windows-specifikke launch-indstillinger dækket i denne artikel kun nås gennem SetActionRemoteDestinationEx og SetActionLaunchOptions direkte

Én sikkerhedsegenskab er værd at kende, før man bygger et vedligeholdelsesværktøj omkring disse sættere. SetActionRemoteDestinationEx og SetActionLaunchOptions bygger hele erstatnings-handlingen i en scratch-ordbog først, og sletter og kopierer kun /F-, /D- eller /Win-, og /NewWindow-nøglerne over på den levende handling, når den scratch-kopi validerer — så et kald, der fejler validering, hvad enten fra en ValueMask uden for intervallet eller et tomt FileName, efterlader den oprindelige handling, og enhver /Next-kæde allerede hængende af den, helt urørt frem for halvt overskrevet. Det betyder noget, fordi GoToR- og Launch-handlinger begge kan sidde inde i en /Next-kæde bygget med AddActionNextRemoteDestinationEx, AddActionNextLaunchEx, eller den mere generelle AddActionNextEx, og lader én enkelt trigger affyre en JavaScript-log-post og derefter et eksternt hop i rækkefølge. GoToR-, GoToE- og Launch-konstruktion som beskrevet her er en del af PDFlibPas, det native PDF-bibliotek til Delphi og C++Builder