مقاله فنی

اکشن‌های GoToR، GoToE، و Launch در PDFهای Delphi

PDFlibPas سه نوع اکشن برای ناوبری‌ای که صفحه‌ی فعلی را پشت سر می‌گذارد به توسعه‌دهندگان Delphi و C++Builder می‌دهد: GoToR (‏Go To Remote) یک صفحه‌ی مشخص را در فایل PDF دیگری باز می‌کند، GoToE (‏Go To Embedded) یک فایل PDF جاسازی‌شده درون سند فعلی را باز می‌کند، و Launch یک برنامه‌ی خارجی را اجرا می‌کند یا یک فایل را از طریق shell سیستم‌عامل باز می‌کند. هر سه در ISO 32000-1 §12.6.4 زندگی می‌کنند، همان بخش Action Types که اکشن معمولی GoTo را هم تعریف می‌کند، و هرکدام تله‌ی خودش را برای بی‌احتیاط حمل می‌کند: یک شماره‌ی صفحه که بسته به اینکه کدام فراخوانی آن را می‌سازد چیز متفاوتی معنا می‌دهد، یک هدف که یک نام است نه یک مسیر فایل، و یک جفت پارامتر رشته‌ای که یکسان به‌نظر می‌رسند اما به دو نمایشگر متفاوت خدمت می‌کنند

هیچ‌کدام از این‌ها فرضی نیست. یک بسته‌ی مرجع فنی — یک راهنمای اصلی، یک PDF مشخصات که یک توزیع‌کننده در برنامه‌ی خودش به‌روز می‌کند، یک ابزار کالیبراسیون نصب‌شده کنار هر دو — دقیقاً روی این نوع سیم‌کشی بین‌سندی تکیه می‌کند: یک ارجاع‌متقابل که باید روی صفحه‌ی ۵ فایل مشخصات فرود بیاید، یک برگه‌ی داده که ارزش دارد درون راهنما به‌جای کنارش عرضه شود، یک لینک که مستقیم به ابزار کالیبراسیون تحویل می‌دهد. این مقاله تصویر آینه‌ای خواندن اکشن‌های بوک‌مارک و حاشیه‌نویسی از یک PDF موجود است: آن مقاله مصرف‌کردن یک اکشن GoToR، Launch، یا GoToE که تولیدکننده‌ی دیگری از پیش در یک فایل نوشته پوشش می‌دهد؛ این یکی ساخت همان سه نوع اکشن از صفر را پوشش می‌دهد، شامل قواعد سطح-فیلدی که PDFlibPas پیش از commit‌کردن یک بایت تکی اعمال می‌کند

سه راه برای اینکه یک اکشن 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 مفهوم مقصد را کاملاً می‌اندازد و صرفاً چیزی را برای سیستم‌عامل نام می‌برد تا اجرا یا باز کند. آن تفکیک به‌عنوان دو خانواده‌ی فراخوانی در سمت نوشتن نمایان می‌شود: builderهای سطح-بالا و یک‌باره مثل AddLinkToFile، AddLinkToFileEx، AddLinkToEmbeddedPDF، و AddLinkToLocalFile یک حاشیه‌نویسی لینک نقطه‌داغ-صفحه و اکشنش را با هم می‌سازند، پوشش‌دهنده‌ی اغلب چیدمان‌های واقعی — یک خط متن یا یک آیکون که یک خواننده کلیک می‌کند — درحالی‌که setterهای سطح-پایین‌تر مثل SetActionRemoteDestinationEx، SetActionLaunchOptions، و همتاهای AddActionNext*شان یک اکشن را روی چیزی که از پیش یک handle به آن دارید متصل یا جایگزین می‌کنند: یک بوک‌مارک موجود، یک محرک فیلد فرم، یا یک رخداد چرخه‌ی عمر سطح-سند یا سطح-صفحه. هر دو خانواده در نهایت همان شکل‌های دیکشنری را می‌نویسند؛ تفاوت این است که وقتی آن‌ها را فراخوانی می‌کنید کجا ایستاده‌اید، و، همان‌طور که بخش بعدی پوشش می‌دهد، یک شماره‌ی صفحه وقتی این کار را می‌کنید چه معنایی دارد

چطور یک لینک GoToR بسازید که یک صفحه را در یک فایل PDF دیگر باز کند؟

یک اکشن GoToR به دو چیز نیاز دارد — یک مشخصات فایل و یک مقصد درون آن فایل — و PDFlibPas دو فراخوانی متفاوت برای تأمین قسمت دوم در معرض دید می‌گذارد، هرکدام با قرارداد شماره‌گذاری صفحه‌ی خودش. AddLinkToFile و AddLinkToFileEx، builderهای نقطه‌داغ-صفحه‌ی سطح-بالا، آرگومان Page یا DestPageشان را به‌عنوان بزرگ‌تر از صفر اعتبارسنجی می‌کنند، همان شماره‌گذاری مبنا-۱ که PDFlibPas همه‌جای دیگر استفاده می‌کند، از جمله SelectPage. SetActionRemoteDestinationEx، setter سطح-پایین‌تر استفاده‌شده برای متصل‌کردن یا جایگزین‌کردن یک اکشن GoToR روی چیزی که از پیش یک handle به آن دارید، به‌جای آن DestPage را به‌عنوان بزرگ‌تر یا مساوی صفر اعتبارسنجی می‌کند و آن را مستقیم بدون هیچ تنظیمی درون آرایه‌ی مقصد صریح اکشن می‌نویسد: اندیس صفحه‌ی خام و صفر-پایه‌ی سند هدف را می‌خواهد، همان شماره‌گذاری‌ای که ISO 32000-1 برای یک مقصد صریح دور مشخص می‌کند. setter سطح-پایین را با همان عددی که به builder سطح-بالا می‌دادید فراخوانی کنید و لینک یک صفحه زودتر باز می‌شود

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 یک مجموعه‌ی بیتی است — ۱ برای چپ، ۲ برای بالا، ۴ برای راست، ۸ برای پایین، ۱۶ برای زوم — و PDFlibPas آن را در برابر DestType پیش از نوشتن هر چیزی بررسی می‌کند: یک مقصد dkFitR باید دقیقاً 15 (هر چهار لبه، بدون زوم) تأمین کند، dkFit و dkFitB باید 0 تأمین کنند، و dkFitH/dkFitV فقط یک مختصات مرتبط‌شان را می‌پذیرند. بیت‌هایی که درون یک mask در غیر این‌صورت معتبر تنظیم‌نشده رها می‌کنید از آرایه حذف نمی‌شوند؛ به‌عنوان یک null صریح PDF نوشته می‌شوند، که ISO 32000-1 آن را به‌معنای «هر مقداری که نمایشگر از پیش دارد را برای آن مختصات نگه دار» در نظر می‌گیرد — یک راه مشروع برای گفتن «به این صفحه بپر، زوم را دست نزن» نه یک اشتباه. خودِ زوم به‌عنوان یک کسر از مقداری که پاس می‌دهید ذخیره می‌شود، پس یک فراخوانی که ۱۵۰ درصد می‌خواهد به آرایه یک مقدار ذخیره‌شده‌ی ۱.۵ می‌دهد، و بازه‌ی ورودی معتبر ۰ تا ۶۴۰۰ است

چطور به یک PDF لینک بدهید که درون سند خودتان جاسازی شده؟

AddLinkToEmbeddedPDF اکشن GoToE را می‌سازد، و آرگومان هدفش، EmbeddedFileName، یک نام است نه یک مسیر: باید با رشته‌ی Titleای که از پیش به EmbedFile پاس داده شده وقتی پیوست ساخته شد مطابقت داشته باشد، چون آن عنوان کلید لفظی‌ای است که PDFlibPas در درخت نام /EmbeddedFiles سند ذخیره می‌کند، و GoToE با جستجوی آن نام حل می‌شود، نه با لمس‌کردن دوباره‌ی فایل‌سیستم. تابع فقط بررسی می‌کند EmbeddedFileName غیرخالی است و TargetPage حداقل ۱ است — نامی که هرگز واقعاً جاسازی نشده پاس دهید و فراخوانی همچنان موفقیت برمی‌گرداند، اکشن همچنان نوشته می‌شود، و لینک صرفاً برای هر خواننده‌ای که آن را کلیک می‌کند حل‌نشدن را انتخاب می‌کند

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 برای درخت نام /EmbeddedFiles به PDF 1.4 نیاز دارد، و AddLinkToEmbeddedPDF جداگانه کف را برای خودِ نوع اکشن GoToE به PDF 1.6 بالا می‌برد، پس حداقل مؤثر برای هر سندی که از این ویژگی استفاده می‌کند 1.6 است، نه 1.4. توجه کنید که TargetPage اینجا مبنا-۱ است، قرارداد معمولی PDFlibPas — یک تضاد عمدی با 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 به‌عنوان یک رشته‌ی مسیر ویندوزی ساده مستند شده که فقط برای خواندن یک نمایشگر ویندوزی معنا دارد. یک مسیر قابل‌حمل و از پیش-تبدیل‌شده را پاس دهید با انتظار اینکه هر دو کلید یکسان درآیند و کپی /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 را به‌عنوان پراحتکاک‌ترین اکشن از این سه در نظر بگیرید، چون کل هدفش اجرای یک برنامه یا بازکردن یک فایل خارج از sandbox PDF است، و هر نمایشگر رایج بر همین اساس با آن رفتار می‌کند. Enhanced Security در Adobe Acrobat به‌طور پیش‌فرض اکشن‌های Launch را مسدود یا اعلان می‌کند مگر اینکه هدف در یک موقعیت صراحتاً مورد اعتماد بنشیند، و اغلب استقرارهای سازمانی Acrobat آن محافظت را روشن رها می‌کنند. یک اکشن Launch در سندی که به عموم داده می‌شود بنابراین یک محرک قابل‌اعتماد نیست: برنامه‌ریزی کنید که مسدود شود، اعلان دریافت کند، یا بی‌سروصدا توسط هر نمایشگری که فایل را باز می‌کند نادیده گرفته شود، و آن را برای محیط‌های بسته نگه دارید که در آن تنظیمات اعتماد نمایشگر را هم کنترل می‌کنید — یک کیوسک داخلی، یک رول‌اوت شرکتی کنترل‌شده، سندی که هرگز دستگاهی که مدیریتش می‌کنید را ترک نمی‌کند

دروازه‌ی PDF/A: چرا فراخوانی‌های GoToR و Launch می‌توانند صفر برگردانند

هم SetActionRemoteDestinationEx و هم SetActionLaunchOptions وقتی سند هدف در هر حالت مطابقت PDF/A باشد کاملاً امتناع می‌کنند: هر دو حالت PDF/A سند را به‌عنوان اولین شرطشان بررسی می‌کنند و با نتیجه‌ی 0 پیش از لمس‌کردن اکشن خارج می‌شوند، بدون هیچ استثنای raiseشده. این عمدی است. محدودیت‌های PDF/A روی اکشن‌های تعاملی به‌طور مشخص Launch را رد می‌کند، چون دادن قابلیت اجرای یک برنامه‌ی دلخواه به یک فایل آرشیوی دقیقاً همان نوع رفتار وابسته‌به-محیط است که قالب‌های آرشیو بلندمدت برای جلوگیری از آن وجود دارند، و PDFlibPas همان دروازه‌ی محافظه‌کارانه را روی setter go-to دور در همان مسیر کد اعمال می‌کند. نتیجه‌ی عملی در طول توسعه آسان برای از قلم‌افتادن است: همان فراخوانی که روی یک PDF معمولی کار می‌کند کامپایل، اجرا، و بی‌سروصدا هیچ کاری روی سندی که با یک سطح مطابقت PDF/A تنظیم‌شده بارشده انجام نمی‌دهد، پس مقدار بازگشتی را بررسی کنید به‌جای فرض‌کردن موفقیت — یک 0 اینجا یک خطای ورودی-بدشکل نیست، این کتابخانه است که یک درخواست را که با ادعای مطابقت خودِ سند تناقض دارد رد می‌کند

GoToR، GoToE، و Launch کجا در یک گردش‌کار بزرگ‌تر PDFlibPas جا می‌افتند

سه نوع اکشن در این مقاله همگی به یک جا نمی‌رسند. مقاله‌ی همراه درباره‌ی محرک‌های اکشن چرخه‌ی عمر سند و صفحه SetDocumentAction و SetPageAction را پوشش می‌دهد، که می‌توانند یک اکشن GoToR یا Launch را به یک محرک مثل WillClose از طریق ثابت‌های مشترک PDF_ACTION_BUILDER_REMOTE_DESTINATION و PDF_ACTION_BUILDER_LAUNCH متصل کنند — همان builderای که یک محرک URI یا جاوااسکریپت ساده را هم پوشش می‌دهد. GoToE هیچ ثابت مشابهی و هیچ مسیری به آن builder عمومی اصلاً ندارد؛ AddLinkToEmbeddedPDF تنها راهی است که PDFlibPas یکی می‌سازد، که آن را کاملاً یک اکشن نقطه‌داغ-صفحه می‌کند، هرگز یک محرک سطح-سند یا سطح-صفحه. جایی که GoToR و Launch واقعاً به builder عمومی می‌رسند، معامله کنترل است: یک GoToR می‌سازد که فقط به یک مقصد دور نام‌داده‌شده اشاره می‌کند و یک اکشن Launch فقط با یک نام فایل و پارامترها، درحالی‌که آدرس‌دهی صریح صفحه-و-نوع-fit و گزینه‌های launch مختص-ویندوز پوشش‌داده‌شده در این مقاله فقط از طریق SetActionRemoteDestinationEx و SetActionLaunchOptions مستقیم قابل‌دسترس هستند

یک ویژگی امنیتی ارزش دانستن دارد پیش از ساخت یک ابزار نگه‌داری حول این setterها. SetActionRemoteDestinationEx و SetActionLaunchOptions کل اکشن جایگزین را ابتدا در یک دیکشنری scratch می‌سازند، و فقط کلیدهای /F، /D یا /Win، و /NewWindow را روی اکشن زنده حذف و کپی می‌کنند به‌محض اینکه آن کپی scratch اعتبارسنجی شود — پس یک فراخوانی که اعتبارسنجی را شکست بدهد، چه از یک ValueMask خارج-از-بازه چه یک FileName خالی، اکشن اصلی، و هر زنجیره‌ی /Nextای که از پیش از آن آویزان است، را کاملاً دست‌نخورده رها می‌کند به‌جای اینکه نیمه-بازنویسی شود. این اهمیت دارد چون اکشن‌های GoToR و Launch هر دو می‌توانند درون یک زنجیره‌ی /Next ساخته‌شده با AddActionNextRemoteDestinationEx، AddActionNextLaunchEx، یا AddActionNextEx عمومی‌تر بنشینند، و اجازه می‌دهند یک محرک تکی یک ورودی لاگ جاوااسکریپت و سپس یک پرش دور را به‌ترتیب شلیک کند. ساخت GoToR، GoToE، و Launch همان‌طور که در اینجا توصیف شد بخشی از PDFlibPas، کتابخانه‌ی بومی PDF برای Delphi و C++Builder، است