الروابط التشعبية في PDF هي تعليقات URI توضيحية: مستطيل يغطي مساحة ما من الصفحة، وعند النقر عليه يخبر العارض بفتح عنوان URL. والتعليق التوضيحي والنص تحته كائنان مستقلان تمامًا. وتجمع PrintHyperlink في HotPDF الاثنين في استدعاء واحد، فترسم النص وتحسب مستطيل التعليق التوضيحي من قياسات النص المُصيَّر. وتخفي تلك السهولة تفصيلًا يستحق الفهم قبل أن تكتب كود إنتاج. وهي ليست القصة كلها أيضًا: تضع AddURILink مساحة قابلة للنقر فوق محتوى رسمته بنفسك، وتتولى AddGoToLink التنقّل الداخلي — وكلاهما مغطى أدناه
كيف تعمل PrintHyperlink
تقيم PrintHyperlink على THPDFPage وتأخذ أربعة وسائط: إحداثيا X و Y (بالنقاط، نقطة الأصل في الزاوية السفلية اليسرى، Y يتزايد لأعلى)، وسلسلة التسمية المراد رسمها، وهدف URL. وداخليًا تستدعي TextOut بلون الرابط التشعبي الحالي، ثم تحسب فورًا مستطيل التعليق التوضيحي من TextWidth وTextHeight عند قياسات الخط الحالية. وهذا يعني أنه يجب ضبط الخط والحجم قبل الاستدعاء، ويجب ألا يتغيّرا بين رسم التسمية ووضع التعليق التوضيحي، لأن كليهما يُحلّ في الاستدعاء نفسه
اللون الافتراضي هو clBlue. وتغيّره SetRGBHyperlinkColor للاستدعاءات اللاحقة فقط؛ ولا تحدّث بأثر رجعي التعليقات التوضيحية المكتوبة بالفعل. وإذا احتجت إلى ألوان مختلفة لمجموعات روابط مختلفة على الصفحة نفسها، فاستدعِ SetRGBHyperlinkColor قبل كل مجموعة وأعد ضبطه بعدها
إليك مستندًا بسيطًا يكتب ثلاثة روابط بلونين مختلفين:
procedure CreateLinkedReport(const FileName: string);
var
Pdf: THotPDF;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.FileName := FileName;
Pdf.BeginDoc;
Pdf.CurrentPage.SetFont('Arial', [], 11);
// الأزرق الافتراضي للروابط المعلوماتية
Pdf.CurrentPage.TextOut(50, 750, 0, 'Reference links:');
Pdf.CurrentPage.PrintHyperlink(50, 720, 'Product page', 'https://www.loslab.com/en-us/pdf-library/delphi-pdf-component.html');
Pdf.CurrentPage.PrintHyperlink(50, 695, 'Online manual', 'https://www.loslab.com/en-us/pdf-library/delphi-pdf-component.html');
// الأحمر لرابط الإجراء
Pdf.CurrentPage.SetRGBHyperlinkColor(clRed);
Pdf.CurrentPage.PrintHyperlink(50, 660, 'Purchase license', 'https://www.loslab.com/en-us/buy-hotpdf-fastspring.html');
Pdf.CurrentPage.SetRGBHyperlinkColor(clBlue); // استعد الافتراضي
Pdf.EndDoc;
finally
Pdf.Free;
end;
end;
فخ الإحداثيات
يستخدم HotPDF نقطة أصل في الزاوية السفلية اليسرى مع نمو Y لأعلى، بالنقاط (1/72 بوصة). فصفحة A4 هي 595 × 842 نقطة؛ وصفحة US Letter هي 612 × 792 نقطة. و Y=750 تقع قرب أعلى صفحة A4، و Y=50 ستكون قرب الهامش السفلي. وكل قادم من رسوميات الشاشة أو HTML يفترض العكس ويضع سطر الرابط الأول خارج المساحة المرئية مباشرة
ويستخدم مستطيل التعليق التوضيحي الذي تحسبه PrintHyperlink نظام الإحداثيات نفسه. فإذا قمت لاحقًا بتدوير الصفحة أو تحجيمها أو تغيير حجمها من دون إعادة حساب قيم X/Y لديك، فسينحرف النص المرئي والمستطيل القابل للنقر أحدهما عن الآخر. و"يعمل" الرابط بمعنى أن النقر في مكان ما قرب النص يُطلق URL، لكن المنطقة الساخنة لم تعد تطابق ما يراه القارئ. اختبر على حجم الصفحة ومستوى التكبير الفعليين اللذين تشحنهما، لا على جهاز التطوير عند 100% فقط
حالة واحدة يكون الانحراف فيها مضمونًا: إذا استدعيت PrintHyperlink بإحداثيات مناسبة لصفحة A4 ثم انتقلت إلى صفحة مخصصة ضيقة التنسيق من دون تعديل قيم X/Y، فقد ينتهي التعليق التوضيحي خارج الصفحة كليًا. ويظل كائن التعليق التوضيحي مكتوبًا في ملف PDF؛ ومعظم العارضات تقصّه بصمت، فيختفي الرابط ببساطة من دون أي خطأ
نص التسمية مقابل هدف URL
الوسيطان Text وLink مستقلان. يمكنك رسم "Download invoice PDF" بينما الهدف عنوان HTTPS كامل التأهيل بمعاملات استعلام. وذلك الفصل مقصود؛ فينبغي أن تكون التسمية المرئية مقروءة للبشر ويمكن أن يكون URL طويلًا أو مولَّدًا ديناميكيًا
وما يخلق المشكلات هو أن تكون التسمية هي URL الخام نفسه، خاصةً إذا كان طويلًا. فإذا التفّ URL بصريًا عبر سطرين لكن مستطيل التعليق التوضيحي حُسب لسلسلة من سطر واحد، فلا يكون قابلًا للنقر إلا السطر الأول. ولا تتعامل PrintHyperlink مع التدفق متعدد الأسطر؛ أبقِ التسمية قصيرة بما يكفي لتتسع في سطر واحد عند حجم الخط وعرض الصفحة الحاليين، أو استخدم تسمية وصفية قصيرة مع URL الكامل كهدف، أو طبّق حل كل سطر على حدة المعروض في القسم التالي
وللمستندات التي ستُؤرشف أو تُوزَّع من دون اتصال إنترنت نشط، فكّر أيضًا فيما إذا كان ينبغي أن يظهر URL نفسه بشكل مطبوع في مكان ما من متن المستند، لا كبيانات وصفية للتعليق التوضيحي فحسب. فالقارئ الذي يطبع ملف PDF على ورق لا يحصل على شيء من تعليق URI التوضيحي
الالتفاف على قيد الأسطر المتعددة
حين يتعيّن على تسمية رابط أن تمتد حقًا على أكثر من سطر — عنوان URL طويل مطبوع حرفيًا، أو جملة ملتفّة ينبغي أن تكون قابلة للنقر من طرف إلى طرف — فالحل هو التوقف عن معاملتها كرابط واحد ومعاملتها كرابط واحد لكل سطر. فكل استدعاء PrintHyperlink يحسب مستطيله من النص الذي يرسمه، لذا فإن عدة استدعاءات تتشارك هدف Link نفسه تنتج عدة تعليقات توضيحية بأحجام صحيحة تفتح جميعها URL نفسه. ولا يستطيع القارئ ملاحظة الفرق؛ فكل سطر يستجيب للنقر
procedure PrintWrappedHyperlink(Page: THPDFPage; X, TopY, LineStep: Single;
const Lines: array of AnsiString; const Link: AnsiString);
var
I: Integer;
begin
for I := 0 to High(Lines) do
Page.PrintHyperlink(X, TopY - I * LineStep, Lines[I], Link);
end;
// الاستخدام: اقطع التسمية عند المواضع التي يلفّها فيها تخطيطك
Pdf.CurrentPage.SetFont('Arial', [], 10);
PrintWrappedHyperlink(Pdf.CurrentPage, 50, 400, 14,
['https://www.loslab.com/en-us/pdf-library/',
'delphi-pdf-component.html'],
'https://www.loslab.com/en-us/pdf-library/delphi-pdf-component.html');
تقسيم السلسلة مسؤوليتك: اقطعها عند المواضع نفسها التي ستلتف فيها بصريًا عند الخط وعرض العمود الحاليين، مستخدمًا TextWidth لاختبار كل سطر مرشَّح. والبديل هو أن ترسم النص الملتفّ بنفسك باستدعاءات TextOut عادية ثم تضع مستطيل AddURILink واحدًا فوق كل سطر — وهو الطريق الأفضل حين يكون النص منتَجًا بالفعل بمنطق التفاف الكلمات الخاص بك، وهذا يقودنا إلى تلك الدالة
AddURILink: مساحات قابلة للنقر فوق أي شيء رسمته
PrintHyperlink غلاف مريح: يرسم تسميته الخاصة ويشتق المستطيل من قياسات تلك التسمية. أما AddURILink فهي النصف الأدنى مستوى مكشوفًا مباشرة:
function AddURILink(Rectangle: TRect; const URL: AnsiString;
const Description: AnsiString = ''): THPDFDictionaryObject;
تكتب التعليق التوضيحي فقط — لا يُرسم نص ولا يتغيّر لون. ويُفسَّر Rectangle في فضاء الإحداثيات نفسه الذي تستخدمه استدعاءات الرسم لديك، لذا يمكنك إعادة استخدام قيم X/Y نفسها التي مررتها إلى TextOut أو استدعاء صورة. وهذا يجعلها الأداة الصحيحة كلما كان المحتوى المرئي موجودًا بالفعل: نقطة ساخنة على صورة، أو خلية جدول، أو كتلة نص رُسمت سابقًا، أو سطر واحد من فقرة ملتفّة كما في الحل أعلاه. ويحمل التعليق التوضيحي إطارًا بعرض صفري، فلا يتغيّر شيء مرئي؛ والمنطقة القابلة للنقر هي بالضبط المستطيل الذي تحدده
وتُعيد الدالة قاموس التعليق التوضيحي كـ THPDFDictionaryObject. ويهمل معظم المستدعين النتيجة، لكن الاحتفاظ بها يتيح لك تعديل مدخلات التعليق التوضيحي قبل كتابة المستند
وتفصيلا امتثال مدمجان. ففي أوضاع PDF/A يُضبط علم الطباعة للتعليق التوضيحي كما تتطلب تلك المعايير. وتحت PDFUACompliance يجب أن يكون المعامل Description سلسلة غير فارغة — فهو يصبح مدخل /Contents للتعليق التوضيحي، وهو ما تعلنه التقنيات المساعدة للرابط — ويرفع الاستدعاء استثناءً بدلًا من إصدار ملف غير متوافق بصمت. وPrintHyperlink تسبق تلك القاعدة ولا تُرفق أي وصف، لذا لإخراج PDF/UA ارسم التسمية بـ TextOut وضع التعليق التوضيحي بـ AddURILink مع وصف ذي معنى
وقاعدة القرار بسيطة: استخدم PrintHyperlink حين يكون الرابط قطعة نص قصيرة لم ترسمها بعد؛ واستخدم AddURILink حين تكون المنطقة القابلة للنقر محددة بمحتوى ترسمه أو تقيسه بنفسك
التنقّل الداخلي بـ AddGoToLink
عناوين URL الخارجية ليست سوى نصف ما تفعله تعليقات الروابط التوضيحية. والنصف الآخر هو التنقّل داخل المستند — جدول محتويات يقفز إلى الفصول، وإحالات مرجعية بين الأقسام. ويكشف HotPDF ذلك عبر AddGoToLink:
procedure AddGoToLink(Rectangle: TRect; TargetPageIndex: Integer;
YPos: Single = -1; const Description: AnsiString = '');
ثلاثة دلالات تستحق التصريح بدقة، إذ لا يمكن تخمين أي منها من التوقيع. TargetPageIndex يبدأ من الصفر: الصفحة الأولى في المستند هي الصفحة 0، مطابقةً لـ CurrentPageNumber. ويجب أن تكون الصفحة الهدف موجودة بالفعل حين تُجري الاستدعاء؛ فإذا كان الفهرس خارج النطاق، يعود الإجراء من دون إضافة تعليق توضيحي — لا استثناء، ولا رابط، ولا تحذير. ولجدول محتويات يشير إلى الأمام، أنشئ كل الصفحات أولًا، ثم ارجع وأضف الروابط
ويختار YPos الموضع الرأسي على الصفحة الهدف، في فضاء الإحداثيات نفسه الذي تستخدمه استدعاءات الرسم لديك. والافتراضي -1 (أي قيمة سالبة) يكتب إحداثي وجهة فارغًا، مخبرًا العارض بالحفاظ على موضعه الرأسي الحالي حين يحط على الصفحة الهدف. مرّر قيمة غير سالبة فيمرّر العارض بحيث يجلس ذلك الموضع في أعلى النافذة — استخدم إحداثي Y للعنوان الذي تربط إليه. ويُترك التكبير من دون تغيير دائمًا. وكما مع AddURILink، يجب أن يكون Description غير فارغ تحت PDFUACompliance ويصبح النص البديل للرابط
procedure BuildLinkedTOC(const FileName: string);
const
Chapters: array[0..2] of string =
('Introduction', 'Installation', 'API Reference');
var
Pdf: THotPDF;
I, Y: Integer;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.FileName := FileName;
Pdf.BeginDoc; // تصبح الصفحة 0 صفحة جدول المحتويات
// أنشئ صفحات الفصول أولًا لتوجد أهداف الروابط
for I := 0 to High(Chapters) do
begin
Pdf.AddPage; // الصفحات 1..3
Pdf.CurrentPage.SetFont('Arial', [fsBold], 14);
Pdf.CurrentPage.TextOut(50, 780, 0, Chapters[I]);
end;
// ارجع إلى الصفحة 0 وارسم مدخلات جدول المحتويات مع روابطها
Pdf.CurrentPageNumber := 0;
Pdf.CurrentPage.SetFont('Arial', [fsBold], 16);
Pdf.CurrentPage.TextOut(50, 760, 0, 'Contents');
Pdf.CurrentPage.SetFont('Arial', [], 11);
Y := 720;
for I := 0 to High(Chapters) do
begin
Pdf.CurrentPage.TextOut(70, Y, 0, Chapters[I]);
Pdf.CurrentPage.AddGoToLink(
Rect(70, Y + 14, 300, Y - 3), // يغطي المدخل مع حشو
I + 1, // من الصفر: الفصول هي الصفحات 1..3
780, // اهبط والعنوان في الأعلى
AnsiString('Go to ' + Chapters[I]));
Y := Y - 25;
end;
Pdf.EndDoc;
finally
Pdf.Free;
end;
end;
يحصل كل مدخل على مستطيل أعرض من النص بحيث يستجيب الصف كله للمؤشر، ويهبط كل رابط وعنوان الفصل (المرسوم عند Y=780) في أعلى النافذة. وإذا أدرجت لاحقًا صفحة قبل الفصول، فسيتزحزح كل TargetPageIndex بمقدار واحد؛ احسب الفهارس من حلقة إنشاء الصفحات لديك بدلًا من ترميزها ثابتة
مثال كامل لتوليد مستند
يُظهر النمط أدناه سيناريو أكثر واقعية: توليد تقرير قصير بقسم ترويسة ونص متن وصف تذييل من الروابط، كله من الكود لا من نموذج بحقول TEdit:
procedure GenerateProductSheet(
const FileName, ProductName, ProductURL, SupportURL: string);
var
Pdf: THotPDF;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.FileName := FileName;
Pdf.Compression := cmFlateDecode;
Pdf.BeginDoc;
// الترويسة
Pdf.CurrentPage.SetFont('Arial', [fsBold], 16);
Pdf.CurrentPage.TextOut(50, 750, 0, WideString(ProductName));
// عنصر نائب لفقرة المتن
Pdf.CurrentPage.SetFont('Arial', [], 11);
Pdf.CurrentPage.TextOut(50, 710, 0, 'See the links below for full documentation.');
// روابط التذييل
Pdf.CurrentPage.SetFont('Arial', [], 10);
Pdf.CurrentPage.TextOut(50, 80, 0, 'Links:');
Pdf.CurrentPage.PrintHyperlink(50, 60, 'Product page', ProductURL);
Pdf.CurrentPage.PrintHyperlink(200, 60, 'Support', SupportURL);
Pdf.EndDoc;
finally
Pdf.Free;
end;
end;
لاحظ أن SetFont تُستدعى قبل كل مجموعة من استدعاءات النص. فالخط لا يستمر عبر AddPage، وإذا نسيت ضبطه قبل PrintHyperlink على صفحة جديدة، فسيُحسب مستطيل التعليق التوضيحي مقابل أي قياسات افتراضية للصفحة، وقد تختلف عمّا تتوقعه
أين تتباين معالجة التعليقات التوضيحية بين العارضات
تعليقات URI التوضيحية في PDF معرّفة في ISO 32000-1 §12.6.4.7، وينبغي لكل عارض متوافق اتباعها. وعمليًا، تختلف بضعة سلوكيات بحسب العارض. يُظهر Adobe Acrobat مطالبة أمنية عند النقر الأول لعناوين URL غير المدرجة في قائمة النطاقات الموثوقة؛ ولا يفعل ذلك كثير من المتصفحات والقارئات الخفيفة. وتعطّل بعض عارضات PDF المؤسسية في البيئات المقيّدة تعليقات URI التوضيحية كليًا بحكم السياسة، فلا يفعل النقر شيئًا، من دون خطأ مرئي. وتتباين تطبيقات PDF على الهواتف فيما إذا كانت تفتح الروابط داخل عرض الويب الخاص بالتطبيق أم تسلّمها إلى متصفح النظام
ولا شيء من هذه أخطاء يمكنك إصلاحها من جانب التوليد؛ فهي قرارات سياسة للعارض. وما تستطيع فعله هو كتابة تسميات روابط تجعل URL مرئيًا في متن المستند أيضًا، بحيث يستطيع قارئ في بيئة مقيّدة نسخ العنوان يدويًا. فالتعليق التوضيحي هو التسهيل؛ والنص هو الاحتياط
وتفصيل آخر جدير بالمعرفة: تعليقات URI التوضيحية في PDF لا تحمل أي تسطير مرئي افتراضيًا. فالتسطير الذي تراه في معظم العارضات يرسمه العارض نفسه بناءً على نوع التعليق التوضيحي، لا حرف رسومي في دفق المحتوى. وإذا احتجت إلى تسطير مادي يصمد عند الطباعة إلى مُصيِّر غير تفاعلي أو تحويل PDF إلى صورة، فارسمه صراحةً بـ LineTo وStroke عند إزاحة Y المناسبة تحت خط الأساس للنص. وتلك عملية رسم منفصلة، لا شيء تتولاه PrintHyperlink نيابةً عنك
وواجهة الروابط التشعبية المعروضة هنا جزء من HotPDF Delphi Component لـ Delphi و C++Builder