PDFlibPas dává vývojářům v Delphi a C++Builderu tři druhy akce pro navigaci, která opouští aktuální stránku: GoToR (Go To Remote) otevře konkrétní stránku v jiném souboru PDF, GoToE (Go To Embedded) otevře PDF vložené uvnitř aktuálního dokumentu, a Launch spustí externí program nebo otevře soubor přes shell operačního systému. Všechny tři žijí v ISO 32000-1 §12.6.4, oddílu Action Types, který také definuje běžnou akci GoTo, a každá nese svou vlastní past pro nepozorného: číslo stránky, které znamená něco jiného podle toho, které volání jej staví, cíl, který je jméno místo cesty k souboru, a dvojice řetězcových parametrů, které vypadají identicky, ale slouží dvěma odlišným prohlížečům
Nic z tohoto není hypotetické. Balíček technické dokumentace — hlavní manuál, PDF specifikací, který distributor aktualizuje na vlastním rozvrhu, kalibrační nástroj nainstalovaný vedle obou — se spoléhá přesně na tento druh propojení napříč dokumenty: křížový odkaz, který musí přistát na stránce 5 souboru specifikací, datový list, který se vyplatí odeslat uvnitř manuálu místo vedle něj, odkaz, který předá řízení rovnou kalibračnímu nástroji. Tento článek je zrcadlovým obrazem zpětného čtení akcí záložek a anotací z existujícího PDF: ten kus popisuje konzumaci akce GoToR, Launch, nebo GoToE, kterou už do souboru napsal nějaký jiný producent; tento popisuje stavbu stejných tří druhů akce od nuly, včetně pravidel na úrovni pole, která PDFlibPas vynucuje dřív, než zapíše jediný bajt
Tři způsoby, jak může akce PDF opustit aktuální stránku
PDFlibPas odděluje lokální navigaci od všeho ostatního na klíči akce /S, a GoToR, GoToE a Launch jsou tři podtypy, jejichž cíl sedí mimo aktuální stránku: GoToR pod ISO 32000-1 §12.6.4.3, GoToE pod §12.6.4.4, a Launch pod §12.6.4.5, všechny uvnitř širšího oddílu §12.6.4 Action Types, který také definuje běžnou akci GoTo. Cíl obyčejné akce GoTo pojmenovává objekt stránky, který už uvnitř dokumentu existuje, takže jej PDFlibPas dokáže okamžitě ověřit; GoToR a GoToE to stejným způsobem udělat nemohou, protože externí soubor nemusí na tomto stroji ani existovat a počet stránek vloženého souboru není něco, co hostitelský dokument sleduje, takže obě nesou nerozřešený odkaz místo tvrdého odkazu — specifikaci souboru plus cíl pro GoToR, jméno vloženého souboru plus cílovou stránku pro GoToE — zatímco Launch koncept cíle úplně vypouští a jen pojmenovává něco, co má operační systém spustit nebo otevřít. Toto rozdělení se projevuje jako dvě rodiny volání na zápisové straně: vysokoúrovňoví, jednorázoví stavitelé jako AddLinkToFile, AddLinkToFileEx, AddLinkToEmbeddedPDF, a AddLinkToLocalFile vytvoří anotaci odkazu na stránce a jeho akci společně, pokrývající většinu skutečných rozvržení — řádek textu nebo ikonu, na kterou čtenář klikne — zatímco nízkoúrovňoví setteři jako SetActionRemoteDestinationEx, SetActionLaunchOptions, a jejich protějšky AddActionNext* připojí nebo nahradí akci na něčem, k čemu už máte handle: existující záložce, spouštěči pole formuláře, nebo události životního cyklu na úrovni dokumentu nebo stránky. Obě rodiny nakonec zapisují stejné tvary slovníků; rozdíl je v tom, kde stojíte, když je voláte, a, jak popisuje další oddíl, co znamená číslo stránky, když to uděláte
Jak postavíte odkaz GoToR, který otevře stránku v jiném souboru PDF?
Akce GoToR potřebuje dvě věci — specifikaci souboru a cíl uvnitř tohoto souboru — a PDFlibPas vystavuje dvě odlišná volání pro dodání druhé části, každé s vlastní konvencí číslování stránek. AddLinkToFile a AddLinkToFileEx, vysokoúrovňoví stavitelé odkazů na stránce, ověřují svůj argument Page nebo DestPage jako větší než nula, stejné číslování od 1, jaké PDFlibPas používá všude jinde, včetně SelectPage. SetActionRemoteDestinationEx, nízkoúrovňový setter používaný k připojení nebo nahrazení akce GoToR na něčem, k čemu už máte handle, místo toho ověřuje DestPage jako větší nebo rovno nule a zapisuje jej přímo do pole explicitního cíle akce bez úpravy: chce surový index stránky cílového dokumentu počítaný od nuly, číslování, které ISO 32000-1 specifikuje pro vzdálený explicitní cíl. Zavolejte nízkoúrovňový setter se stejným číslem, jaké byste podali vysokoúrovňovému staviteli, a odkaz se otevře o stránku dřív
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;
Zbytek argumentů SetActionRemoteDestinationEx je stejně tak doslovný. ValueMask je bitová množina — 1 pro left, 2 pro top, 4 pro right, 8 pro bottom, 16 pro zoom — a PDFlibPas jej porovná proti DestType ještě předtím, než cokoli zapíše: cíl dkFitR musí dodat přesně 15 (všechny čtyři okraje, žádný zoom), dkFit a dkFitB musí dodat 0, a dkFitH/dkFitV přijímá jen jednu relevantní souřadnici. Bity, které ponecháte nenastavené uvnitř jinak platné masky, se z pole nevynechávají; zapisují se jako explicitní null PDF, který ISO 32000-1 bere jako „ponech jakoukoli hodnotu, kterou prohlížeč pro tuto souřadnici už má" — legitimní způsob, jak říct „skoč na tuto stránku, zoom nech na pokoji", ne přehlédnutí. Samotný zoom se ukládá jako zlomek hodnoty, kterou předáte, takže volání žádající o 150 procent podá poli uloženou hodnotu 1,5, a platný vstupní rozsah je 0 až 6400
Jak se odkážete na PDF vložené uvnitř vlastního dokumentu?
AddLinkToEmbeddedPDF staví akci GoToE, a její cílový argument, EmbeddedFileName, je jméno, ne cesta: musí odpovídat řetězci Title, který už byl předán EmbedFile, když byla příloha vytvořena, protože tento titul je doslovný klíč, který PDFlibPas ukládá do stromu jmen /EmbeddedFiles dokumentu, a GoToE se rozřešuje vyhledáním tohoto jména, ne opětovným dotykem souborového systému. Funkce jen kontroluje, že EmbeddedFileName je neprázdné a TargetPage je alespoň 1 — podejte jméno, které nikdy nebylo skutečně vložené, a volání přesto vrátí úspěch, akce se přesto zapíše, a odkaz se prostě nerozřeší pro každého čtenáře, který na něj klikne
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;
Zde se vrství dvě podlahy verze, ne jedna. EmbedFile potřebuje PDF 1.4 pro strom jmen /EmbeddedFiles, a AddLinkToEmbeddedPDF samostatně zvedne podlahu na PDF 1.6 pro samotný typ akce GoToE, takže efektivní minimum pro jakýkoli dokument, který tuto funkci používá, je 1.6, ne 1.4. Všimněte si také, že TargetPage je zde počítáno od 1, obyčejná konvence PDFlibPas — záměrný kontrast se souřadnicí DestPage počítanou od nuly, kterou právě popsal předchozí oddíl, a připomínka, že to, které schéma čísla stránky platí, závisí na druhu akce a konkrétním volání, ne na jednom plošném pravidle. Cílový slovník akce může také nést záznam /R hodnoty C pro dítě nebo P pro rodiče, podporující dvouskokový řetěz do vloženého souboru nebo zpátky ven k jeho kontejneru, přestože AddLinkToEmbeddedPDF vždy staví jen směr dítěte, protože to je ten, který dává smysl z dokumentu, který vkládá, ne toho, který je vkládán
Akce Launch: jedno FileName, dva řetězcové cíle, které nejsou zaměnitelné
SetActionLaunchOptions zapisuje cíl souboru akce Launch do dvou odlišných klíčů z jediného argumentu FileName, a tyto dva klíče drží dva odlišné druhy řetězce. Klíč nejvyšší úrovně /F dostane slovník specifikace souboru, postavený přes stejnou konverzi cesty, kterou PDFlibPas používá pro GoToR, což je přenositelný tvar, který ISO 32000-1 §7.11.3 definuje pro slovník specifikace souboru. Podslovník /Win, když PDFlibPas nějaký zapíše, dostane vlastní klíč /F nastavený na surovou hodnotu FileName přesně tak, jak byla předána, bez jakékoli konverze, protože /Win /F je v ISO 32000-1 §12.6.4.5 zdokumentováno jako obyčejný řetězec cesty Windows určený jen ke čtení prohlížečem Windows. Podejte přenositelnou, už převedenou cestu v očekávání, že oba klíče skončí identické, a kopie /Win ponese cokoli, co jste funkci podali, nedotčené
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;
Berte Launch jako nejtřecí akci ze všech tří, protože jejím celým účelem je spustit program nebo otevřít soubor mimo sandbox PDF, a každý běžný prohlížeč se podle toho chová. Enhanced Security Adobe Acrobatu ve výchozím stavu blokuje nebo se ptá na akce Launch, pokud cíl nesedí na výslovně důvěryhodném místě, a většina firemních nasazení Acrobatu ponechává tuto ochranu zapnutou. Akce Launch v dokumentu podaném veřejnosti proto není spolehlivý spouštěč: počítejte s tím, že bude zablokovaná, dotázaná, nebo tiše ignorovaná jakýmkoli prohlížečem, který soubor otevře, a šetřete ji na uzavřená prostředí, kde také kontrolujete nastavení důvěry prohlížeče — interní kiosek, kontrolované firemní nasazení, dokument, který nikdy neopustí stroj, který spravujete
Brána PDF/A: proč mohou volání GoToR a Launch vrátit nulu
SetActionRemoteDestinationEx a SetActionLaunchOptions obě rovnou odmítnou, když je cílový dokument v jakémkoli režimu konformity PDF/A: obě zkontrolují režim PDF/A dokumentu jako úplně první podmínku a skončí s výsledkem 0 ještě předtím, než se dotknou akce, žádná výjimka se nevyvolá. Toto je záměrné. Omezení PDF/A na interaktivní akce vylučují konkrétně Launch, protože dát archivnímu souboru schopnost spustit libovolný program je přesně ten druh chování závislého na prostředí, kterému mají formáty pro dlouhodobé archivování zabránit, a PDFlibPas aplikuje stejnou konzervativní bránu na setter vzdáleného go-to ve stejné cestě kódu. Praktický důsledek se snadno přehlédne během vývoje: identické volání, které funguje na obyčejném PDF, se zkompiluje, spustí, a tiše nic neudělá na dokumentu načteném s nastavenou úrovní konformity PDF/A, takže zkontrolujte návratovou hodnotu místo předpokladu úspěchu — 0 zde není chyba zdeformovaného vstupu, je to knihovna odmítající požadavek, který se rozchází s vlastním tvrzením dokumentu o konformitě
Kam GoToR, GoToE a Launch zapadají do většího pracovního postupu PDFlibPas
Tři druhy akcí v tomto článku se nedostanou všechny na stejná místa. Doprovodný článek o spouštěčích akcí životního cyklu dokumentu a stránky popisuje SetDocumentAction a SetPageAction, které dokáží připojit akci GoToR nebo Launch ke spouštěči jako WillClose přes sdílené konstanty PDF_ACTION_BUILDER_REMOTE_DESTINATION a PDF_ACTION_BUILDER_LAUNCH — stejný stavitel, který také pokrývá obyčejný spouštěč URI nebo JavaScript. GoToE nemá žádnou takovou konstantu a žádnou cestu do tohoto obecného stavitele vůbec; AddLinkToEmbeddedPDF je jediný způsob, jak ji PDFlibPas konstruuje, což z ní dělá striktně akci odkazu na stránce, nikdy spouštěč na úrovni dokumentu nebo stránky. Tam, kde se GoToR a Launch skutečně dostanou do obecného stavitele, je kompromis kontrola: postaví GoToR ukazující jen na pojmenovaný vzdálený cíl a akci Launch jen s jménem souboru a parametry, zatímco explicitní adresování stránky-a-typu-přizpůsobení a volby launch specifické pro Windows popsané v tomto článku se dosáhnou jen přímo přes SetActionRemoteDestinationEx a SetActionLaunchOptions
Jedna vlastnost bezpečnosti stojí za znalost dřív, než postavíte nástroj údržby kolem těchto setterů. SetActionRemoteDestinationEx a SetActionLaunchOptions nejdřív postaví celou náhradní akci v pracovním slovníku, a jen smažou a zkopírují klíče /F, /D nebo /Win, a /NewWindow na živou akci, jakmile tato pracovní kopie projde ověřením — takže volání, které selže na ověření, ať z mimo-rozsahové ValueMask, nebo prázdného FileName, ponechá původní akci, a jakýkoli řetěz /Next už na ní visící, úplně nedotčené místo napůl přepsané. Na tom záleží, protože akce GoToR a Launch obě mohou sedět uvnitř řetězu /Next postaveného pomocí AddActionNextRemoteDestinationEx, AddActionNextLaunchEx, nebo obecnějšího AddActionNextEx, což umožňuje jednomu spouštěči vyvolat záznam logu JavaScript a pak vzdálený skok v sekvenci. Konstrukce GoToR, GoToE a Launch popsaná zde je součástí PDFlibPas, nativní knihovny PDF pro Delphi a C++Builder