PDFlibPas дає розробникам Delphi та C++Builder три види дій для навігації, що залишає поточну сторінку позаду: GoToR (Go To Remote) відкриває конкретну сторінку в іншому файлі PDF, GoToE (Go To Embedded) відкриває PDF, вбудований усередині поточного документа, а Launch запускає зовнішню програму чи відкриває файл через оболонку операційної системи. Усі три живуть в ISO 32000-1 §12.6.4, розділі Action Types, що також визначає звичайну дію GoTo, і кожна несе власну пастку для необережних: номер сторінки, що означає різне залежно від того, який виклик його будує, ціль, що є ім'ям, а не шляхом файлу, і пара строкових параметрів, що виглядають ідентично, але слугують двом різним переглядачам
Ніщо з цього не гіпотетичне. Пакет технічної документації — головний посібник, PDF специфікацій, який дистриб'ютор оновлює за власним графіком, утиліта калібрування, встановлена поряд з обома, — спирається саме на такий тип міждокументного підключення: перехресне посилання, що має приземлитися на сторінці 5 файлу специфікацій, аркуш даних, вартий постачання всередині посібника, а не поряд із ним, посилання, що передає керування прямо до інструменту калібрування. Ця стаття — дзеркальне відображення статті про читання дій закладок та анотацій назад із наявного PDF: та стаття охоплює споживання дії GoToR, Launch чи GoToE, яку якийсь інший виробник уже записав у файл; ця охоплює побудову тих самих трьох видів дій з нуля, включно з правилами на рівні полів, які PDFlibPas застосовує ще до фіксації хоч одного байта
Три способи для дії PDF залишити поточну сторінку
PDFlibPas відокремлює локальну навігацію від усього іншого на ключі дії /S, і GoToR, GoToE та Launch — три підтипи, чия ціль сидить поза поточною сторінкою: GoToR за ISO 32000-1 §12.6.4.3, GoToE за §12.6.4.4, та Launch за §12.6.4.5, усі всередині ширшого розділу §12.6.4 Action Types, що також визначає звичайну дію GoTo. Призначення звичайної дії GoTo називає об'єкт сторінки, що вже існує всередині документа, тож PDFlibPas може перевірити його негайно; GoToR і GoToE не можуть зробити це так само, оскільки зовнішній файл може навіть не існувати на цій машині, а кількість сторінок вбудованого файлу — не те, що відстежує документ-хост, тож обидва несуть нерозв'язане посилання замість жорсткого зв'язку — специфікацію файлу плюс призначення для GoToR, ім'я вбудованого файлу плюс цільову сторінку для GoToE, — тоді як Launch повністю відкидає концепцію призначення й просто називає щось для запуску чи відкриття операційною системою. Цей поділ проявляється як дві родини викликів на боці запису: високорівневі, одноразові будівельники, такі як AddLinkToFile, AddLinkToFileEx, AddLinkToEmbeddedPDF та AddLinkToLocalFile, створюють разом гарячу точку сторінки-посилання та її дію, покриваючи більшість реальних макетів — рядок тексту чи піктограму, на яку клацає читач, — тоді як низькорівневі сетери, такі як SetActionRemoteDestinationEx, SetActionLaunchOptions та їхні аналоги AddActionNext*, прикріплюють чи замінюють дію на чомусь, дескриптор чого ви вже тримаєте: наявну закладку, тригер поля форми, чи подію життєвого циклу рівня документа чи сторінки. Обидві родини врешті записують ту саму форму словника; різниця в тому, де ви стоїте, коли їх викликаєте, і, як розглядає наступний розділ, що означає номер сторінки, коли ви це робите
Як побудувати посилання GoToR, що відкриває сторінку в іншому файлі PDF?
Дії GoToR потрібні дві речі — специфікація файлу та призначення всередині цього файлу, — і PDFlibPas відкриває два різні виклики для надання другої частини, кожен зі своєю конвенцією нумерації сторінок. AddLinkToFile та AddLinkToFileEx, високорівневі будівельники гарячих точок сторінки, перевіряють свій аргумент Page чи DestPage як більший за нуль, ту саму 1-базовану нумерацію, яку PDFlibPas використовує всюди-інде, включно з SelectPage. SetActionRemoteDestinationEx, низькорівневий сетер, що використовується для прикріплення чи заміни дії GoToR на чомусь, дескриптор чого ви вже маєте, натомість перевіряє DestPage як більший чи рівний нулю й записує його прямо в масив явного призначення дії без коригування: він хоче сирий, 0-базований індекс сторінки цільового документа, нумерацію, яку ISO 32000-1 визначає для явного віддаленого призначення. Викличте низькорівневий сетер із тим самим числом, яке б ви передали високорівневому будівельнику, і посилання відкриється на сторінку раніше
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;
Решта аргументів SetActionRemoteDestinationEx так само буквальні. ValueMask — набір бітів — 1 для лівого, 2 для верхнього, 4 для правого, 8 для нижнього, 16 для масштабу, — і PDFlibPas перевіряє його проти DestType ще до запису чого-небудь: призначення dkFitR мусить надати рівно 15 (усі чотири краї, без масштабу), dkFit та dkFitB мусять надати 0, а dkFitH/dkFitV приймають лише одну релевантну координату. Біти, які ви залишаєте невстановленими всередині інакше дійсної маски, не опускаються з масиву; вони записуються як явний нуль PDF, що ISO 32000-1 трактує як «залишити те значення, яке переглядач уже має» для цієї координати — законний спосіб сказати «перейти на цю сторінку, не чіпати масштаб», а не недогляд. Сам масштаб зберігається як частка переданого вами значення, тож запит на 150 відсотків передає масиву збережене значення 1.5, а дійсний діапазон входу — від 0 до 6400
Як прив'язатися до PDF, вбудованого всередині власного документа?
AddLinkToEmbeddedPDF будує дію GoToE, і її аргумент цілі, EmbeddedFileName, — ім'я, а не шлях: воно мусить збігатися з рядком Title, уже переданим у EmbedFile, коли вкладення було зроблено, бо цей заголовок — буквальний ключ, який PDFlibPas зберігає в дереві імен /EmbeddedFiles документа, і GoToE розв'язується пошуком цього імені, не повторним торканням файлової системи. Функція лише перевіряє, що EmbeddedFileName непорожнє, а TargetPage принаймні 1 — передайте ім'я, яке ніколи насправді не було вбудоване, і виклик усе одно повертає успіх, дія все одно записується, а посилання просто не розв'язується для кожного читача, що на нього клацне
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;
Тут накладаються дві підлоги версії, не одна. EmbedFile потребує PDF 1.4 для дерева імен /EmbeddedFiles, а AddLinkToEmbeddedPDF окремо піднімає підлогу до PDF 1.6 для самого виду дії GoToE, тож ефективний мінімум для будь-якого документа, що використовує цю функцію, — 1.6, не 1.4. Зауважте також, що TargetPage тут 1-базований, звичайна конвенція PDFlibPas — навмисний контраст із 0-базованим DestPage, який щойно розглянув попередній розділ, і нагадування, що яка схема номерів сторінок застосовується, залежить від виду дії та конкретного виклику, не від одного загального правила. Словник цілі дії також може нести запис /R зі значенням C для дитини чи P для батька, підтримуючи двоетапний ланцюжок у вбудований файл чи назад у свій контейнер, хоча AddLinkToEmbeddedPDF завжди будує лише напрямок дитини, бо це той, що має сенс для документа, що робить вбудовування, а не той, що вбудовується
Дії Launch: одне FileName, дві строкові цілі, що не взаємозамінні
SetActionLaunchOptions записує ціль файлу дії Launch у два різні ключі з одного аргументу FileName, і два ключі тримають два різні типи рядка. Верхньорівневий ключ /F отримує словник специфікації файлу, побудований через те саме перетворення шляху, яке PDFlibPas використовує для GoToR, що є переносною формою, яку ISO 32000-1 §7.11.3 визначає для словника специфікації файлу. Підсловник /Win, коли PDFlibPas його записує, отримує власний ключ /F, встановлений у сире значення FileName точно так, як передано, взагалі без перетворення, бо /Win /F задокументований в ISO 32000-1 §12.6.4.5 як звичайний рядок шляху Windows, призначений лише для читання переглядачем Windows. Передайте переносний, уже перетворений шлях, очікуючи, що обидва ключі опиняться ідентичними, і копія /Win нестиме те, що ви передали функції, незайманим
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;
Ставтеся до Launch як до дії з найвищим тертям із трьох, бо вся її мета — запуск програми чи відкриття файлу поза пісочницею PDF, і кожен основний переглядач ставиться до неї відповідно. Enhanced Security в Adobe Acrobat за замовчуванням блокує чи запитує на дії Launch, якщо ціль не сидить у явно довіреному розташуванні, і більшість корпоративних розгортань Acrobat залишають цей захист увімкненим. Дія Launch у документі, переданому публіці, тому не надійний тригер: плануйте, що її заблокують, запитають чи мовчки проігнорують, який би переглядач не відкрив файл, і збережіть її для закритих середовищ, де ви також контролюєте налаштування довіри переглядача — внутрішній кіоск, контрольоване корпоративне розгортання, документ, що ніколи не покидає машину, якою ви керуєте
Шлюз PDF/A: чому виклики GoToR та Launch можуть повертати нуль
SetActionRemoteDestinationEx та SetActionLaunchOptions обидва відверто відмовляють, коли цільовий документ у будь-якому режимі відповідності PDF/A: обидва перевіряють режим PDF/A документа як свою найпершу умову й виходять із результатом 0 ще до того, як торкнутися дії, без піднятого винятку. Це навмисно. Обмеження PDF/A на інтерактивні дії виключають Launch конкретно, оскільки надання архівному файлу здатності запускати довільну програму — саме той тип поведінки, що залежить від середовища, для запобігання якому й існують формати довгострокового архівування, і PDFlibPas застосовує той самий консервативний шлюз до сетера віддаленого переходу в тому самому шляху коду. Практичний наслідок легко пропустити під час розробки: ідентичний виклик, що працює на звичайному PDF, скомпілюється, виконається й тихо нічого не зробить на документі, завантаженому з установленим рівнем відповідності PDF/A, тож перевіряйте значення повернення замість того, щоб припускати успіх — 0 тут не помилка спотвореного входу, це бібліотека, що відмовляє в запиті, який конфліктує з власною заявкою відповідності документа
Де GoToR, GoToE та Launch вписуються в ширший робочий процес PDFlibPas
Три види дій у цій статті не всі досягають тих самих місць. Супутня стаття про тригери дій життєвого циклу документа та сторінки охоплює SetDocumentAction та SetPageAction, які можуть прикріпити дію GoToR чи Launch до тригера на кшталт WillClose через спільні константи PDF_ACTION_BUILDER_REMOTE_DESTINATION та PDF_ACTION_BUILDER_LAUNCH — той самий будівельник, що також охоплює звичайний тригер URI чи JavaScript. GoToE не має такої константи й узагалі жодного шляху в той загальний будівельник; AddLinkToEmbeddedPDF — єдиний спосіб, яким PDFlibPas його конструює, що робить його строго дією гарячої точки сторінки, ніколи тригером рівня документа чи сторінки. Там, де GoToR та Launch справді досягають загального будівельника, компроміс — контроль: він будує GoToR, що вказує лише на назване віддалене призначення, і дію Launch лише з ім'ям файлу та параметрами, тоді як явна адресація сторінки-й-типу-підгонки та специфічні для Windows опції запуску, розглянуті в цій статті, досяжні лише прямо через SetActionRemoteDestinationEx та SetActionLaunchOptions
Одну властивість безпеки варто знати перед побудовою інструменту супроводу навколо цих сетерів. SetActionRemoteDestinationEx та SetActionLaunchOptions спершу будують усю замінну дію в чорновому словнику, і лише видаляють та копіюють ключі /F, /D чи /Win, та /NewWindow на живу дію, щойно ця чорнова копія перевіриться, — тож виклик, що провалює перевірку, чи то через ValueMask поза діапазоном, чи порожнє FileName, залишає оригінальну дію, і будь-який ланцюжок /Next, уже прив'язаний до неї, повністю незайманим, а не наполовину перезаписаним. Це важить, бо дії GoToR та Launch обидві можуть сидіти всередині ланцюжка /Next, побудованого через AddActionNextRemoteDestinationEx, AddActionNextLaunchEx чи загальніший AddActionNextEx, дозволяючи одному тригеру спричинити запис у журнал JavaScript, а потім віддалений стрибок у послідовності. Побудова GoToR, GoToE та Launch, описана тут, — частина PDFlibPas, рідної бібліотеки PDF для Delphi та C++Builder