مقال تقني

أفعال GoToR وGoToE وLaunch في ملفات PDF بـDelphi

يمنح PDFlibPas مطوري Delphi وC++Builder ثلاثة أنواع أفعال للتنقل الذي يترك الصفحة الحالية خلفه: يفتح GoToR (Go To Remote) صفحة محددة في ملف PDF آخر، ويفتح GoToE (Go To Embedded) ملف PDF مضمَّنًا داخل المستند الحالي، ويشغّل Launch برنامجًا خارجيًا أو يفتح ملفًا عبر غلاف نظام التشغيل. تعيش الثلاثة في ISO 32000-1 §12.6.4، قسم أنواع الأفعال الذي يعرّف أيضًا فعل 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 الأوسع الذي يعرّف أيضًا فعل GoTo اليومي. تسمّي وجهة فعل GoTo عادي كائن صفحة موجود بالفعل داخل المستند، بحيث يمكن لـPDFlibPas التحقق منه فورًا؛ لا يمكن لـGoToR وGoToE فعل ذلك بالطريقة نفسها، بما أن الملف الخارجي قد لا يكون موجودًا حتى على هذا الجهاز وعدد صفحات ملف مضمَّن ليس شيئًا يتتبعه المستند المضيف، بحيث يحمل كلاهما مرجعًا غير محلول بدلًا من رابط صارم — مواصفة ملف بالإضافة إلى وجهة لـGoToR، اسم ملف مضمَّن بالإضافة إلى صفحة هدف لـGoToE — بينما يُسقط Launch مفهوم الوجهة كليًا ويسمّي فقط شيئًا ليشغّله نظام التشغيل أو يفتحه. يظهر ذلك الانقسام كعائلتي استدعاء على جانب الكتابة: بناة عالية المستوى، أحادية الاستخدام مثل AddLinkToFile وAddLinkToFileEx وAddLinkToEmbeddedPDF وAddLinkToLocalFile تنشئ تعليقًا توضيحيًا لرابط منطقة نشطة (hotspot) لصفحة وفعله معًا، مغطية معظم التخطيطات الحقيقية — سطر نص أو أيقونة ينقر عليها قارئ — بينما ضوابط أدنى مستوى مثل SetActionRemoteDestinationEx وSetActionLaunchOptions ونظائرها AddActionNext* تُرفق أو تستبدل فعلًا على شيء تحمل مقبضه بالفعل: إشارة مرجعية موجودة، أو مُحفِّز حقل نموذج، أو حدث دورة حياة على مستوى المستند أو الصفحة. تنتهي كلتا العائلتين بكتابة أشكال قاموس نفسها؛ الفرق أين تقف عندما تستدعيهما، وما يعنيه رقم صفحة عندما تفعل، كما يغطي القسم التالي

كيف تبني رابط GoToR يفتح صفحة في ملف PDF آخر؟

يحتاج فعل GoToR شيئين — مواصفة ملف ووجهة داخل ذلك الملف — ويعرض PDFlibPas استدعائين مختلفين لتوفير الجزء الثاني، لكل منهما اصطلاح ترقيم صفحات خاص به. تتحقق AddLinkToFile وAddLinkToFileEx، بانيا المنطقة النشطة عاليا المستوى، من معاملي Page أو DestPage كأكبر من صفر، الترقيم نفسه الذي يبدأ من 1 والذي يستخدمه PDFlibPas في كل مكان آخر، بما في ذلك SelectPage. تتحقق SetActionRemoteDestinationEx بدلًا من ذلك، الضابط الأدنى مستوى المستخدَم لإرفاق أو استبدال فعل GoToR على شيء تحمل مقبضه بالفعل، من DestPage كأكبر من أو يساوي صفر وتكتبه مباشرة في مصفوفة الوجهة الصريحة للفعل دون تعديل: تريد فهرس صفحة خام يبدأ من صفر للمستند الهدف، الترقيم الذي يحدده 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 فقط إحداثيها الوحيد ذا الصلة. البتات التي تتركها غير مضبوطة داخل قناع صالح من ناحية أخرى ليست محذوفة من المصفوفة؛ تُكتب كـnull صريحة في 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 العادي — تباين متعمد مع 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 كأكثر الأفعال الثلاثة احتكاكًا، لأن غرضه بأكمله تشغيل برنامج أو فتح ملف خارج صندوق حماية PDF، ويعامل كل عارض رئيسي ذلك وفقًا لذلك. يحجب الأمان المُحسَّن في 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 المشتركين — الباني نفسه الذي يغطي أيضًا مُحفِّز رابط أو جافاسكريبت عادي. لا يملك GoToE ثابتًا كهذا ولا مسارًا إلى ذلك الباني العام على الإطلاق؛ AddLinkToEmbeddedPDF هي الطريقة الوحيدة التي يبني بها PDFlibPas واحدًا، ما يجعله فعل منطقة نشطة لصفحة حصرًا، لا مُحفِّزًا على مستوى المستند أو الصفحة أبدًا. حيث تصل GoToR وLaunch إلى الباني العام فعليًا، المقايضة هي التحكم: يبني GoToR يشير فقط إلى وجهة بعيدة مسمّاة وفعل Launch باسم ملف ومعاملات فقط، بينما لا يُصَل إلى العنونة الصريحة بالصفحة ونوع الملاءمة وخيارات الإطلاق الخاصة بويندوز المغطاة في هذه المقالة إلا عبر SetActionRemoteDestinationEx وSetActionLaunchOptions مباشرة

خاصية أمان واحدة تستحق المعرفة قبل بناء أداة صيانة حول هذين الضابطين. تبني SetActionRemoteDestinationEx وSetActionLaunchOptions الفعل البديل بأكمله في قاموس مسودة أولًا، ولا تحذف وتنسخ مفاتيح /F وD أو /Win وNewWindow إلى الفعل الحي إلا بمجرد أن تتحقق تلك النسخة المسودة — بحيث يترك استدعاء يفشل في التحقق، سواء من ValueMask خارج النطاق أو FileName فارغ، الفعل الأصلي، وأي سلسلة /Next مُعلَّقة عليه بالفعل، دون مساس كليًا بدلًا من الكتابة فوقه جزئيًا. هذا يهم لأن أفعال GoToR وLaunch يمكن أن تجلس كلاهما داخل سلسلة /Next مبنية بـAddActionNextRemoteDestinationEx، أو AddActionNextLaunchEx، أو الأعم AddActionNextEx، تاركة مُحفِّزًا واحدًا يُطلق إدخال سجل جافاسكريبت ثم قفزة بعيدة بالتسلسل. بناء GoToR وGoToE وLaunch كما موصوف هنا جزء من PDFlibPas، المكتبة الأصلية لـPDF لـDelphi وC++Builder