PDFlibPas ger Delphi- och C++Builder-utvecklare tre handlingstyper för navigering som lämnar den aktuella sidan bakom sig: GoToR (Go To Remote) öppnar en specifik sida i en annan PDF-fil, GoToE (Go To Embedded) öppnar en PDF-fil inbäddad inuti det aktuella dokumentet, och Launch kör ett externt program eller öppnar en fil genom operativsystemets skal. Alla tre finns i ISO 32000-1 §12.6.4, avsnittet Action Types som också definierar den vardagliga GoTo-handlingen, och var och en bär sin egen fälla för den ovarsamme: ett sidnummer som betyder olika saker beroende på vilket anrop som bygger det, ett mål som är ett namn snarare än en filsökväg, och ett par strängparametrar som ser identiska ut men tjänar två olika visare
Inget av det här är hypotetiskt. Ett tekniskt referenspaket — en huvudmanual, en specifikationer-PDF en distributör uppdaterar på sitt eget schema, ett kalibreringsverktyg installerat vid sidan av båda — lutar sig mot precis den här typen av korsdokumentkoppling: en korsreferens som måste landa på sida 5 i specs-filen, ett datablad värt att skicka med inuti manualen snarare än bredvid den, en länk som lämnar över direkt till kalibreringsverktyget. Den här artikeln är spegelbilden av att läsa bokmärkes- och anteckningshandlingar tillbaka ur ett befintligt PDF: den artikeln täcker att konsumera en GoToR-, Launch-, eller GoToE-handling någon annan producent redan skrivit in i en fil; den här täcker att bygga samma tre handlingstyper från grunden, inklusive fältnivåreglerna PDFlibPas verkställer innan den committar en enda byte
Tre sätt en PDF-handling kan lämna den aktuella sidan
PDFlibPas separerar lokal navigering från allt annat vid handlingens /S-nyckel, och GoToR, GoToE, och Launch är de tre subtyperna vars mål sitter utanför den aktuella sidan: GoToR under ISO 32000-1 §12.6.4.3, GoToE under §12.6.4.4, och Launch under §12.6.4.5, alla inom det bredare §12.6.4 Action Types-avsnittet som också definierar den vardagliga GoTo-handlingen. En vanlig GoTo-handlings destination namnger ett sidobjekt som redan existerar inuti dokumentet, så PDFlibPas kan validera det omedelbart; GoToR och GoToE kan inte göra det på samma sätt, eftersom den externa filen kanske inte ens finns på den här maskinen och en inbäddad fils sidantal inte är något värddokumentet spårar, så båda bär en olöst referens istället för en hård länk — en filspecifikation plus en destination för GoToR, ett inbäddat-fil-namn plus en målsida för GoToE — medan Launch släpper destinationskonceptet helt och bara namnger något för operativsystemet att köra eller öppna. Den uppdelningen visar sig som två anropsfamiljer på skrivsidan: högnivå, engångsbyggare som AddLinkToFile, AddLinkToFileEx, AddLinkToEmbeddedPDF, och AddLinkToLocalFile skapar en sida-hotspot-länkanteckning och dess handling tillsammans, och täcker de flesta riktiga layouter — en rad text eller en ikon en läsare klickar på — medan lägre-nivå-sättare som SetActionRemoteDestinationEx, SetActionLaunchOptions, och deras AddActionNext*-motsvarigheter fäster eller ersätter en handling på något du redan har ett handtag till: ett befintligt bokmärke, en formulärfältutlösare, eller en dokument- eller sidnivå-livscykelhändelse. Båda familjerna slutar med att skriva samma ordboksformer; skillnaden är var man står när man anropar dem, och, som nästa avsnitt täcker, vad ett sidnummer betyder när man gör det
Hur bygger man en GoToR-länk som öppnar en sida i en annan PDF-fil?
En GoToR-handling behöver två saker — en filspecifikation och en destination inuti den filen — och PDFlibPas exponerar två olika anrop för att tillhandahålla den andra delen, var och en med sin egen sidnumreringskonvention. AddLinkToFile och AddLinkToFileEx, de högnivå-sida-hotspot-byggarna, validerar sitt Page- eller DestPage-argument som större än noll, samma 1-baserade numrering PDFlibPas använder överallt annars, inklusive SelectPage. SetActionRemoteDestinationEx, den lägre-nivå-sättaren använd för att fästa eller ersätta en GoToR-handling på något du redan har ett handtag till, validerar istället DestPage som större än eller lika med noll och skriver den rakt in i handlingens explicita destinationsarray utan justering: den vill ha måldokumentets råa, nollbaserade sidindex, numreringen ISO 32000-1 specificerar för en fjärrexplicit destination. Anropa lågnivåsättaren med samma tal du skulle ge högnivåbyggaren och länken öppnar en sida för tidigt
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 SetActionRemoteDestinationEx:s argument är precis lika bokstavliga. ValueMask är en bituppsättning — 1 för vänster, 2 för topp, 4 för höger, 8 för botten, 16 för zoom — och PDFlibPas kontrollerar den mot DestType innan den skriver något: en dkFitR-destination måste tillhandahålla exakt 15 (alla fyra kanter, ingen zoom), dkFit och dkFitB måste tillhandahålla 0, och dkFitH/dkFitV accepterar bara sin ena relevanta koordinat. Bitar du lämnar osatta inom en annars giltig mask utelämnas inte från arrayen; de skrivs som en explicit PDF-null, vilket ISO 32000-1 behandlar som "behåll vad för värde visaren redan har" för den koordinaten — ett legitimt sätt att säga "hoppa till den här sidan, lämna zoomen orörd" snarare än ett förbiseende. Zoom självt lagras som en bråkdel av värdet du skickar, så ett anrop som ber om 150 procent ger arrayen ett lagrat värde av 1,5, och det giltiga indataintervallet är 0 till 6400
Hur länkar man till en PDF inbäddad inuti ditt eget dokument?
AddLinkToEmbeddedPDF bygger GoToE-handlingen, och dess målargument, EmbeddedFileName, är ett namn snarare än en sökväg: det måste matcha Title-strängen redan skickad till EmbedFile när bilagan gjordes, eftersom den titeln är den bokstavliga nyckeln PDFlibPas lagrar i dokumentets /EmbeddedFiles-namnträd, och GoToE löser upp genom att slå upp det namnet, inte genom att röra filsystemet igen. Funktionen kontrollerar bara att EmbeddedFileName inte är tom och TargetPage är minst 1 — skicka ett namn som aldrig faktiskt bäddades in och anropet returnerar fortfarande framgång, handlingen skrivs fortfarande, och länken misslyckas helt enkelt att lösa upp för varje läsare som klickar 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;
Två versionsgolv staplas här, inte ett. EmbedFile behöver PDF 1.4 för /EmbeddedFiles-namnträdet, och AddLinkToEmbeddedPDF höjer separat golvet till PDF 1.6 för själva GoToE-handlingstypen, så det effektiva minimumet för alla dokument som använder den här funktionen är 1.6, inte 1.4. Notera också att TargetPage här är 1-baserad, den vanliga PDFlibPas-konventionen — en medveten kontrast mot den nollbaserade DestPage föregående avsnitt just täckte, och en påminnelse om att vilket sidnummerschema som gäller beror på handlingstypen och det specifika anropet, inte på en generell regel. Handlingens målordbok kan också bära en /R-post av C för barn eller P för förälder, och stödja en tvåstegskedja in i en inbäddad fil eller tillbaka ut till sin behållare, även om AddLinkToEmbeddedPDF bara någonsin bygger barn-riktningen, eftersom det är den som är vettig från ett dokument som gör inbäddningen snarare än att bli inbäddat
Launch-handlingar: ett FileName, två strängmål som inte är utbytbara
SetActionLaunchOptions skriver en Launch-handlings filmål till två olika nycklar från ett enda FileName-argument, och de två nycklarna håller två olika typer av sträng. Toppnivå-/F-nyckeln får en filspecifikationsordbok, byggd genom samma sökvägskonvertering PDFlibPas använder för GoToR, vilket är den portabla formen ISO 32000-1 §7.11.3 definierar för en filspecifikationsordbok. /Win-underordboken, när PDFlibPas skriver en, får sin egen /F-nyckel satt till det råa FileName-värdet exakt som skickat in, utan någon konvertering alls, eftersom /Win /F är dokumenterad i ISO 32000-1 §12.6.4.5 som en ren Windows-sökvägssträng menad bara för en Windows-visare att läsa. Skicka en portabel, redan konverterad sökväg och förvänta dig att båda nycklarna ska sluta identiska och /Win-kopian kommer bära vad du än gav funktionen, orö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;
Behandla Launch som den mest friktionsfyllda handlingen av de tre, eftersom hela dess syfte är att köra ett program eller öppna en fil utanför PDF-sandlådan, och varje mainstream-visare behandlar det därefter. Adobe Acrobats Enhanced Security blockerar eller frågar vid Launch-handlingar som standard om inte målet sitter i en explicit betrodd plats, och de flesta enterprise-Acrobat-utplaceringar lämnar det skyddet påslaget. En Launch-handling i ett dokument lämnat till allmänheten är därför ingen tillförlitlig utlösare: planera för att den blockeras, frågas om, eller tyst ignoreras av vilken visare som än öppnar filen, och spara den för slutna miljöer där du också kontrollerar visarens förtroendeinställningar — en intern kiosk, en kontrollerad företagsutrullning, ett dokument som aldrig lämnar en maskin du hanterar
PDF/A-grinden: varför GoToR- och Launch-anrop kan returnera noll
SetActionRemoteDestinationEx och SetActionLaunchOptions vägrar båda rakt av när måldokumentet är i något PDF/A-konformitetsläge: båda kontrollerar dokumentets PDF/A-läge som sitt allra första villkor och avslutar med ett resultat på 0 innan de rör handlingen, inget undantag kastat. Det här är medvetet. PDF/A:s begränsningar på interaktiva handlingar utesluter Launch specifikt, eftersom att ge en arkivfil förmågan att köra ett godtyckligt program är precis den typen av miljöberoende beteende långsiktiga arkiveringsformat existerar för att förhindra, och PDFlibPas tillämpar samma konservativa grind på fjärrgå-till-sättaren i samma kodväg. Den praktiska konsekvensen är lätt att missa under utveckling: det identiska anropet som fungerar på en vanlig PDF kommer kompilera, köra, och tyst göra ingenting på ett dokument laddat med en PDF/A-konformitetsnivå satt, så kontrollera returvärdet istället för att anta framgång — en 0 här är inget felformad-indata-fel, det är biblioteket som vägrar en begäran som strider mot dokumentets egen konformitetsdeklaration
Var GoToR, GoToE, och Launch passar in i ett större PDFlibPas-arbetsflöde
De tre handlingstyperna i den här artikeln når inte alla samma platser. Följeartikeln om dokument- och sidnivå-livscykelhändelseutlösare täcker SetDocumentAction och SetPageAction, som kan fästa en GoToR- eller en Launch-handling till en utlösare som WillClose genom de delade PDF_ACTION_BUILDER_REMOTE_DESTINATION- och PDF_ACTION_BUILDER_LAUNCH-konstanterna — samma byggare som också täcker en ren URI- eller JavaScript-utlösare. GoToE har ingen sådan konstant och ingen väg in i den generiska byggaren alls; AddLinkToEmbeddedPDF är det enda sättet PDFlibPas konstruerar en, vilket gör den strikt till en sida-hotspot-handling, aldrig en dokument- eller sidnivåutlösare. Där GoToR och Launch faktiskt når den generiska byggaren är avvägningen kontroll: den bygger en GoToR som bara pekar på en namngiven fjärrdestination och en Launch-handling med bara ett filnamn och parametrar, medan den explicita sida-och-fit-typ-adresseringen och de Windows-specifika uppstartsalternativen som täcks i den här artikeln nås bara genom SetActionRemoteDestinationEx och SetActionLaunchOptions direkt
En säkerhetsegenskap är värd att känna till innan man bygger ett underhållsverktyg runt de här sättarna. SetActionRemoteDestinationEx och SetActionLaunchOptions bygger hela ersättningshandlingen i en skissordbok först, och tar bara bort och kopierar /F-, /D- eller /Win-, och /NewWindow-nycklarna till den levande handlingen när den skissen väl valideras — så ett anrop som misslyckas validering, vare sig från en ValueMask utanför intervallet eller ett tomt FileName, lämnar den ursprungliga handlingen, och alla /Next-kedjor som redan hänger av den, helt orörda snarare än halvöverskrivna. Det spelar roll eftersom GoToR- och Launch-handlingar båda kan sitta inuti en /Next-kedja byggd med AddActionNextRemoteDestinationEx, AddActionNextLaunchEx, eller den mer generella AddActionNextEx, vilket låter en enda utlösare avfyra en JavaScript-loggpost och sedan ett fjärrhopp i sekvens. GoToR-, GoToE-, och Launch-konstruktion som beskrivs här är del av PDFlibPas, det nativa PDF-biblioteket för Delphi och C++Builder