PDFlibPas daje programistom Delphi i C++Buildera trzy rodzaje akcji do nawigacji wychodzącej poza bieżącą stronę: GoToR (Go To Remote) otwiera konkretną stronę w innym pliku PDF, GoToE (Go To Embedded) otwiera plik PDF osadzony wewnątrz bieżącego dokumentu, a Launch uruchamia zewnętrzny program lub otwiera plik przez powłokę systemu operacyjnego. Wszystkie trzy mieszkają w ISO 32000-1 §12.6.4, sekcji Action Types, która definiuje też codzienną akcję GoTo, a każda z nich niesie własną pułapkę dla nieuważnych: numer strony, który znaczy coś innego w zależności od tego, które wywołanie go buduje, cel będący nazwą, a nie ścieżką pliku, oraz para parametrów łańcuchowych wyglądających identycznie, ale służących dwóm różnym przeglądarkom
Nic z tego nie jest hipotetyczne. Pakiet dokumentacji technicznej — główny podręcznik, plik PDF specyfikacji aktualizowany przez dystrybutora na własnym harmonogramie, narzędzie kalibracyjne zainstalowane obok obu — opiera się dokładnie na tym rodzaju okablowania międzydokumentowego: odsyłacz, który musi wylądować na stronie 5 pliku specyfikacji, arkusz danych wart wysłania wewnątrz podręcznika, a nie obok niego, link, który przekazuje sterowanie wprost do narzędzia kalibracyjnego. Ten artykuł jest lustrzanym odbiciem odczytywania akcji zakładek i adnotacji z powrotem z istniejącego PDF: tamten dotyczy konsumowania akcji GoToR, Launch czy GoToE, którą jakiś inny producent już zapisał w pliku; ten dotyczy budowania tych samych trzech rodzajów akcji od zera, w tym reguł na poziomie pola, które PDFlibPas egzekwuje, zanim zacommituje choć jeden bajt
Trzy sposoby, w jakie akcja PDF może opuścić bieżącą stronę
PDFlibPas oddziela nawigację lokalną od wszystkiego innego przy kluczu /S akcji, a GoToR, GoToE i Launch to trzy podtypy, których cel siedzi poza bieżącą stroną: GoToR w ramach ISO 32000-1 §12.6.4.3, GoToE w ramach §12.6.4.4, a Launch w ramach §12.6.4.5, wszystkie wewnątrz szerszej sekcji §12.6.4 Action Types, która definiuje też codzienną akcję GoTo. Cel zwykłej akcji GoTo nazywa obiekt strony, który już istnieje wewnątrz dokumentu, więc PDFlibPas może go natychmiast zwalidować; GoToR i GoToE nie mogą tego zrobić w ten sam sposób, ponieważ zewnętrzny plik może nawet nie istnieć na tej maszynie, a liczba stron pliku osadzonego to nie coś, co śledzi dokument gospodarz, więc oba niosą nierozwiązane odwołanie zamiast twardego linku — specyfikację pliku plus miejsce docelowe dla GoToR, nazwę pliku osadzonego plus stronę docelową dla GoToE — podczas gdy Launch całkowicie porzuca koncepcję miejsca docelowego i po prostu nazywa coś, co system operacyjny ma uruchomić lub otworzyć. Ten podział ujawnia się jako dwie rodziny wywołań po stronie zapisu: wysokopoziomowi, jednorazowi budowniczowie, tacy jak AddLinkToFile, AddLinkToFileEx, AddLinkToEmbeddedPDF i AddLinkToLocalFile, tworzą razem adnotację linku strefy aktywnej strony i jej akcję, obejmując większość rzeczywistych układów — linię tekstu lub ikonę, którą klika czytelnik — podczas gdy niższego poziomu ustawiacze, tacy jak SetActionRemoteDestinationEx, SetActionLaunchOptions i ich odpowiedniki AddActionNext*, dołączają lub zastępują akcję na czymś, do czego już masz uchwyt: istniejącej zakładce, wyzwalaczu pola formularza, czy zdarzeniu cyklu życia na poziomie dokumentu lub strony. Obie rodziny kończą, zapisując te same kształty słownikowe; różnica polega na tym, gdzie stoisz, gdy je wywołujesz, oraz, jak omawia kolejna sekcja, co znaczy numer strony, gdy to robisz
Jak zbudować link GoToR, który otwiera stronę w innym pliku PDF?
Akcja GoToR potrzebuje dwóch rzeczy — specyfikacji pliku i miejsca docelowego wewnątrz tego pliku — a PDFlibPas udostępnia dwa różne wywołania do dostarczenia drugiej części, każde z własną konwencją numeracji stron. AddLinkToFile i AddLinkToFileEx, wysokopoziomowi budowniczowie strefy aktywnej strony, walidują swój argument Page lub DestPage jako większy niż zero, tę samą numerację jednostkową (1-based), której PDFlibPas używa wszędzie indziej, w tym w SelectPage. SetActionRemoteDestinationEx, ustawiacz niższego poziomu używany do dołączenia lub zastąpienia akcji GoToR na czymś, do czego już masz uchwyt, waliduje zamiast tego DestPage jako większy lub równy zero i zapisuje go wprost do tablicy jawnego miejsca docelowego akcji bez żadnej korekty: chce surowego, zerowego (0-based) indeksu strony dokumentu docelowego, numeracji, jaką ISO 32000-1 określa dla zdalnego jawnego miejsca docelowego. Wywołaj ustawiacz niskiego poziomu z tą samą liczbą, jaką podałbyś wysokopoziomowemu budowniczemu, a link otworzy się o jedną stronę za wcześnie
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;
Reszta argumentów SetActionRemoteDestinationEx jest równie dosłowna. ValueMask to zbiór bitów — 1 dla lewej, 2 dla góry, 4 dla prawej, 8 dla dołu, 16 dla powiększenia — a PDFlibPas sprawdza go względem DestType, zanim cokolwiek zapisze: miejsce docelowe dkFitR musi dostarczyć dokładnie 15 (wszystkie cztery krawędzie, bez powiększenia), dkFit i dkFitB muszą dostarczyć 0, a dkFitH/dkFitV akceptują tylko swoją jedną istotną współrzędną. Bity, które pozostawisz nieustawione wewnątrz poza tym ważnej maski, nie są pomijane z tablicy; są zapisywane jako jawny null PDF, który ISO 32000-1 traktuje jako "zachowaj wartość, jaką przeglądarka już ma" dla tej współrzędnej — uzasadniony sposób powiedzenia "przeskocz na tę stronę, zostaw powiększenie w spokoju", a nie przeoczenie. Samo powiększenie jest przechowywane jako ułamek podanej wartości, więc wywołanie proszące o 150 procent podaje tablicy zapisaną wartość 1.5, a ważny zakres wejściowy to 0 do 6400
Jak połączyć się z PDF-em osadzonym wewnątrz własnego dokumentu?
AddLinkToEmbeddedPDF buduje akcję GoToE, a jej argument celu, EmbeddedFileName, to nazwa, nie ścieżka: musi pasować do łańcucha Title już podanego do EmbedFile, gdy dołączano załącznik, ponieważ ten tytuł to dosłowny klucz, który PDFlibPas przechowuje w drzewie nazw /EmbeddedFiles dokumentu, a GoToE rozwiązuje się, wyszukując tę nazwę, a nie ponownie dotykając systemu plików. Funkcja sprawdza tylko, czy EmbeddedFileName jest niepuste, a TargetPage wynosi przynajmniej 1 — podaj nazwę, która nigdy faktycznie nie została osadzona, a wywołanie wciąż zwraca sukces, akcja wciąż zostaje zapisana, a link po prostu nie rozwiąże się dla każdego czytelnika, który go kliknie
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;
Tutaj piętrzą się dwie podłogi wersji, nie jedna. EmbedFile potrzebuje PDF 1.4 dla drzewa nazw /EmbeddedFiles, a AddLinkToEmbeddedPDF osobno podnosi podłogę do PDF 1.6 dla samego rodzaju akcji GoToE, więc efektywne minimum dla każdego dokumentu korzystającego z tej funkcji to 1.6, nie 1.4. Zauważ też, że TargetPage tutaj jest jednostkowy (1-based), zwyczajna konwencja PDFlibPas — celowy kontrast z zerowym (0-based) DestPage, który omówiła poprzednia sekcja, i przypomnienie, że to, który schemat numeracji stron obowiązuje, zależy od rodzaju akcji i konkretnego wywołania, nie od jednej ogólnej reguły. Słownik celu akcji może też nieść wpis /R równy C dla dziecka lub P dla rodzica, wspierając dwuskokowy łańcuch do pliku osadzonego lub z powrotem do jego kontenera, choć AddLinkToEmbeddedPDF zawsze buduje wyłącznie kierunek dziecka, ponieważ to ten, który ma sens z dokumentu wykonującego osadzanie, a nie będącego osadzanym
Akcje Launch: jeden FileName, dwa cele łańcuchowe, które nie są wymienne
SetActionLaunchOptions zapisuje cel pliku akcji Launch do dwóch różnych kluczy z jednego argumentu FileName, a te dwa klucze trzymają dwa różne rodzaje łańcuchów. Klucz najwyższego poziomu /F dostaje słownik specyfikacji pliku, zbudowany przez tę samą konwersję ścieżki, jakiej PDFlibPas używa dla GoToR, co jest przenośną formą, którą ISO 32000-1 §7.11.3 definiuje dla słownika specyfikacji pliku. Podsłownik /Win, gdy PDFlibPas go zapisuje, dostaje własny klucz /F ustawiony na surową wartość FileName dokładnie tak, jak podaną, bez żadnej konwersji, ponieważ /Win /F jest udokumentowane w ISO 32000-1 §12.6.4.5 jako zwykły łańcuch ścieżki Windows przeznaczony wyłącznie do odczytu przez przeglądarkę Windows. Podaj przenośną, już skonwertowaną ścieżkę, oczekując, że oba klucze wyjdą identyczne, a kopia /Win będzie nosić dokładnie to, co podałeś funkcji, nietknięte
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;
Traktuj Launch jako akcję o najwyższym tarciu z tych trzech, ponieważ jej całym celem jest uruchomienie programu lub otwarcie pliku poza piaskownicą PDF, a każda popularna przeglądarka traktuje ją odpowiednio. Enhanced Security w Adobe Acrobat domyślnie blokuje lub prosi o potwierdzenie przy akcjach Launch, chyba że cel siedzi w jawnie zaufanej lokalizacji, a większość wdrożeń Acrobat na poziomie przedsiębiorstwa pozostawia tę ochronę włączoną. Akcja Launch w dokumencie przekazanym publicznie nie jest więc niezawodnym wyzwalaczem: planuj, że zostanie zablokowana, że pojawi się o nią pytanie, lub że zostanie po cichu zignorowana przez dowolną przeglądarkę, która otworzy plik, i zachowaj ją na zamknięte środowiska, gdzie też kontrolujesz ustawienia zaufania przeglądarki — wewnętrzny kiosk, kontrolowane wdrożenie firmowe, dokument, który nigdy nie opuszcza zarządzanej przez ciebie maszyny
Brama PDF/A: dlaczego wywołania GoToR i Launch mogą zwrócić zero
SetActionRemoteDestinationEx i SetActionLaunchOptions oba odmawiają wprost, gdy dokument docelowy jest w dowolnym trybie zgodności PDF/A: oba sprawdzają tryb PDF/A dokumentu jako swój sam pierwszy warunek i wychodzą z wynikiem 0, zanim dotkną akcji, bez zgłoszenia wyjątku. To celowe. Ograniczenia PDF/A dotyczące interaktywnych akcji wykluczają konkretnie Launch, ponieważ danie plikowi archiwalnemu zdolności uruchomienia dowolnego programu jest dokładnie tym rodzajem zachowania zależnego od środowiska, przed którym mają chronić formaty długoterminowej archiwizacji, a PDFlibPas stosuje tę samą konserwatywną bramę do ustawiacza zdalnego przejścia w tej samej ścieżce kodu. Praktyczna konsekwencja jest łatwa do przeoczenia podczas rozwoju: identyczne wywołanie, które działa na zwykłym PDF, skompiluje się, uruchomi i po cichu nic nie zrobi na dokumencie wczytanym z ustawionym poziomem zgodności PDF/A, więc sprawdzaj wartość zwracaną zamiast zakładać sukces — 0 tutaj to nie błąd zniekształconych danych wejściowych, to biblioteka odmawiająca żądania sprzecznego z własną deklaracją zgodności dokumentu
Gdzie GoToR, GoToE i Launch pasują do większego przepływu pracy PDFlibPas
Trzy rodzaje akcji w tym artykule nie sięgają wszystkie do tych samych miejsc. Towarzyszący artykuł o wyzwalaczach akcji cyklu życia dokumentu i strony omawia SetDocumentAction i SetPageAction, które mogą dołączyć akcję GoToR lub Launch do wyzwalacza takiego jak WillClose przez wspólne stałe PDF_ACTION_BUILDER_REMOTE_DESTINATION i PDF_ACTION_BUILDER_LAUNCH — ten sam builder, który obejmuje też zwykły wyzwalacz URI czy JavaScript. GoToE nie ma takiej stałej ani żadnej ścieżki do tego ogólnego buildera w ogóle; AddLinkToEmbeddedPDF to jedyny sposób, w jaki PDFlibPas ją konstruuje, co czyni ją ściśle akcją strefy aktywnej strony, nigdy wyzwalaczem na poziomie dokumentu czy strony. Tam, gdzie GoToR i Launch faktycznie sięgają do ogólnego buildera, kompromis dotyczy kontroli: buduje GoToR wskazujące tylko na nazwane zdalne miejsce docelowe i akcję Launch tylko z nazwą pliku i parametrami, podczas gdy jawne adresowanie strona-i-typ-dopasowania oraz opcje uruchomienia specyficzne dla Windows omówione w tym artykule są osiągalne wyłącznie bezpośrednio przez SetActionRemoteDestinationEx i SetActionLaunchOptions
Jedna właściwość bezpieczeństwa jest warta poznania przed zbudowaniem narzędzia konserwacyjnego wokół tych ustawiaczy. SetActionRemoteDestinationEx i SetActionLaunchOptions budują całą zastępczą akcję najpierw w słowniku roboczym, i usuwają oraz kopiują klucze /F, /D lub /Win, i /NewWindow na żywą akcję dopiero, gdy ta robocza kopia się zwaliduje — więc wywołanie, które nie przejdzie walidacji, czy to z powodu maski ValueMask poza zakresem, czy pustego FileName, pozostawia oryginalną akcję, i dowolny łańcuch /Next już na niej wiszący, całkowicie nietknięte, zamiast częściowo nadpisane. To ma znaczenie, ponieważ akcje GoToR i Launch mogą obie siedzieć wewnątrz łańcucha /Next zbudowanego przez AddActionNextRemoteDestinationEx, AddActionNextLaunchEx, lub bardziej ogólne AddActionNextEx, pozwalając jednemu wyzwalaczowi uruchomić wpis logu JavaScript, a następnie zdalny skok w sekwencji. Konstrukcja GoToR, GoToE i Launch opisana tutaj jest częścią PDFlibPas, natywnej biblioteki PDF dla Delphi i C++Buildera