مقالة تقنية

إنشاء عارض PDF تفاعلي في دلفي باستخدام TPDFlibViewer

لا يتطلب تضمين لوحة قراءة PDF في تطبيق أعمال دلفي كتابة حلقة عرض (rendering loop) أو محرك تمرير أو نموذج تكبير. توفر مكتبة losLab PDF Library عنصر التحكم TPDFlibViewer، وهو عنصر تحكم VCL يفتح المستند باستدعاء واحد ويوفر تحديد النص، والتمرير (panning)، والتكبير بالتحديد (marquee zoom), ولقطات الشاشة (snapshots)، وتظليل المناطق، وتظليل نتائج البحث في النص الكامل، والتفاعل مع التعليقات التوضيحية، والطباعة كسلوكيات مدمجة. تستعرض هذه المقالة الخطوات الخمس التي تحول نموذجاً فارغاً إلى عارض يعمل: إسقاط عنصر التحكم، وربط الإجراءات الجاهزة، وإضافة تظليل البحث، والتعامل مع نقرات التعليقات التوضيحية، والطباعة

لماذا تستخدم عنصر تحكم عارض بدلاً من كتابة حلقة عرض؟

تأتي حجة تقسيم الطبقات (layering) في المقام الأول، لأنها توضح ما تشتريه بالفعل. إن عرض الصفحة في صورة نقطية (bitmap) يمثل الـ 20 بالمائة السهلة؛ والـ 80 بالمائة المتبقية هي كل ما يحيط بها: ذاكرة تخزين مؤقت لصور الصفحات بميزانية إخلاء، وتخطيط التمرير عبر صفحات ذات أحجام مختلفة، وأوضاع التكبير التي تعيد الحساب عند تغيير الحجم، واختبار التصادم (hit-testing) الذي يربط بكسل الماوس بإحداثيات الصفحة عند أي تكبير وتدوير، وجدولة إعادة الرسم التي لا تومض. يمتلك عنصر TPDFlibViewer كل ذلك ويفوض عمل البيانات النقطية (raster) إلى نفس المحرك الموضح في مقالة العرض متعدد المحركات، بحيث تظل طبقة التحكم وطبقة العرض قابلة للاستبدال بشكل منفصل. يتصل كود النموذج الخاص بك بعنصر تحكم Windows قابل للتمرير، وليس بمُعالج عرض

كيف تقوم بتضمين عارض PDF في تطبيق دلفي؟

يفتح TPDFlibViewer.LoadFromFile مستنداً من خلال مثيل TPDFlib الداخلي الخاص بعنصر التحكم ويعيد القيمة 1 عند النجاح، وبالتالي فإن العارض بالحد الأدنى يتكون من استدعاء منشئ (constructor) وتعيين Parent وتحميل واحد. إذا كنت تمتلك بالفعل مثيل TPDFlib في مكان آخر من التطبيق، فإن AttachLibrary يعرض المستند المحدد الخاص به بدلاً من ذلك، مع بقاء الملكية في جانبك

uses PDFlibViewer;

procedure TMainForm.FormCreate(Sender: TObject);
begin
  FViewer := TPDFlibViewer.Create(Self);
  FViewer.Parent := Self;
  FViewer.Align := alClient;
  if FViewer.LoadFromFile('quarterly-report.pdf', '') = 1 then
    FViewer.ViewerMode := vmHand;  // pan by dragging; a plain click still follows links
end;

تحدد الخاصية ViewerMode ما يفعله الماوس، من بين خمس قيم: vmSelect هي أداة تحديد النص الافتراضية، وvmHand تقوم بالتمرير عن طريق السحب بمؤشر الإمساك بينما تظل النقرة البسيطة تتبع الروابط، وvmZoom هي تكبير بالتحديد يضاعف النطاق المسحوب لملء منفذ العرض، وvmSnapshot تلتقط منطقة مسحوبة إلى الحافظة (مع حدث OnSnapshot ومفتاح SnapshotToClipboard)، وتطلي vmHighlight تظليلات مناطق شبه شفافة مستمرة. أداة التظليل تستحق ملاحظة حول الإحداثيات: يتم تخزين المناطق في مساحة مستخدم الصفحة، وليس في بكسلات العميل، وبالتالي فإن علامات AddHighlightRegion تنجو من التكبير وتدوير العرض والتنقل في الصفحات دون أي مسك دفاتر من جانبك. في العرض المدور، يمكن لاتجاه السحب أن يتعارض مع محاور الصفحة، مما ينتج عنه عرض أو ارتفاع سالب بعد مطابقة التدوير العكسي — يقوم عنصر التحكم بتهيئة المستطيل نيابة عنك

كيف تقوم بتوصيل شريط أدوات PDF في دلفي بدون كود غراء (glue code)؟

تزيل وحدة PDFlibViewerActions غراء شريط الأدوات بالكامل. فهي توفر عشرين من فروع TAction الجاهزة التي تغطي التنقل بين الصفحات (TPDFlibViewerFirstPage، وTPDFlibViewerPriorPage، وTPDFlibViewerNextPage، وTPDFlibViewerLastPage)، والتكبير (TPDFlibViewerZoomIn، وTPDFlibViewerZoomOut، وTPDFlibViewerFitWidth، وTPDFlibViewerFitPage، وTPDFlibViewerActualSize)، وتدوير العرض (TPDFlibViewerRotateClockwise، وTPDFlibViewerRotateAntiClockwise)، والطباعة، وحوارات فتح الملف وحفظه باسم، ونسخ النص المحدد، وإجراء تبديل واحد لكل أداة ماوس (TPDFlibViewerSelectMode، وTPDFlibViewerHandMode، وTPDFlibViewerMarqueeZoomMode، وTPDFlibViewerSnapshotMode، وTPDFlibViewerHighlightMode). يشير كل إجراء إلى عنصر تحكم من خلال خاصية Viewer المنشورة الخاصة به ويحافظ على حالته الخاصة: حيث يعطل كل إجراء نفسه طالما لم يتم فتح أي مستند، وتتتبع إجراءات التنقل موضع الصفحة، ويتتبع النسخ ما إذا كان هناك تحديد، وتحافظ إجراءات الوضع والملاءمة على حالة التحقق الخاصة بها بالتزامن مع الأداة النشطة ووضع التكبير. في وقت التصميم، يتم تسجيلها جميعاً في فئة losLab PDF بمحرر Action List؛ وفي وقت التشغيل، تتكون نفس الفئات من ثلاثة أسطر لكل منها

uses System.Actions, Vcl.ActnList, PDFlibViewer, PDFlibViewerActions;

procedure TMainForm.BuildToolbar;
var
  NextPage: TPDFlibViewerNextPage;
  FitWidth: TPDFlibViewerFitWidth;
  HandMode: TPDFlibViewerHandMode;
begin
  NextPage := TPDFlibViewerNextPage.Create(Self);
  NextPage.ActionList := ActionList1;
  NextPage.Viewer := FViewer;
  btnNext.Action := NextPage;

  FitWidth := TPDFlibViewerFitWidth.Create(Self);
  FitWidth.ActionList := ActionList1;
  FitWidth.Viewer := FViewer;
  btnFitWidth.Action := FitWidth;

  HandMode := TPDFlibViewerHandMode.Create(Self);
  HandMode.ActionList := ActionList1;
  HandMode.Viewer := FViewer;
  btnHand.Action := HandMode;
end;

هناك فخ على مستوى الوحدة (unit-scope) يجدر معرفته إذا كنت تبني تسجيل الإجراءات الخاص بك فوق هذا النمط. تم التصريح عن RegisterActions في System.Actions، وليس في Vcl.ActnList، وخطأ المترجم الذي تحصل عليه بوجود Vcl.ActnList فقط في بند uses هو مضلل للغاية: حيث يتراجع تعبير مصفوفة الفئات [TAction1, TAction2, ...] ليتم تحليله كمجموعة، مما ينتج عنه سلسلة من أخطاء E2010 Incompatible types: 'Integer' and 'class of ...' التي تبدو كعدم تطابق في الأنواع في فئات الإجراءات الخاصة بك. الحل هو إدخال uses واحد، لكن التشخيص يشير إلى كل مكان باستثنائه. بالنسبة لأشرطة أدوات التكبير، تبلغ الخاصية TPDFlibViewer.EffectiveZoomPercent عن التكبير الفعلي المعمول به — بما في ذلك العامل المحسوب في وضعي ملاءمة العرض وملاءمة الصفحة — بحيث يمكن لخطوة التكبير أن تبنى على التكبير الفعلي الحالي بدلاً من القيمة الأخيرة التي قمت بتعيينها

تظليل نتائج البحث وقلب الإحداثيات الكامن وراءه

يبحث HighlightSearchHits في المستند بأكمله عن استعلام ويرسم شريطاً شبه شفاف فوق كل مطابقة، معيداً عدد النتائج؛ وتكمل الأدوات ClearSearchHighlights وSearchHighlightCount والخاصية SearchHighlightColor السطح. نظراً لتخزين مستطيلات النتائج في مساحة الصفحة وإعادة إسقاطها عند كل رسم، تظل الأشرطة ملتصقة بنصوصها أثناء قيام المستخدم بالتمرير وتكبير وتدوير العرض. إذا كنت تستخدم عنصر TPDFlibSearchPanel المصاحب، فإن RunSearch الخاص به يرسم نفس الأشرطة في العارض المرفق تلقائياً من خلال الخاصية HighlightInView، والتي تكون مفعلة افتراضياً

procedure TMainForm.btnSearchClick(Sender: TObject);
var
  Hits: Integer;
begin
  FViewer.SearchHighlightColor := clYellow;
  Hits := FViewer.HighlightSearchHits(edtQuery.Text, False);  // case-insensitive
  StatusBar1.SimpleText := Format('%d matches', [Hits]);
end;

الجزء الدقيق، والسبب في تفضيل المسار المدمج على كتابة مسارك الخاص من نتائج البحث الخام، هو تحويل نظام الإحداثيات. يبلغ محرك النص عن مستطيلات التطابق بإحداثيات تعتمد على إعداد Origin الخاص بالمكتبة: فمع Origin 0 أو 3 يمتد محور Y من الأسفل إلى الأعلى كما في مساحة مستخدم PDF الأصلية، ومع Origin 2 أو 3 يتم قياس محور X من الحافة اليمنى. يعمل غطاء العارض في مساحة صفحة تبدأ من الأعلى إلى الأسفل ومن اليسار، لذا يجب قلب كل مستطيل نتيجة (Y = PageHeight - Bottom، وحيثما ينطبق X = PageWidth - Right) قبل تخزينه — أخذ الحافة العلوية المبلغ عنها بقيمتها الحقيقية تحت الأصل الافتراضي يرسم التظليل في الموضع المعكوس رأسياً على الصفحة. يقوم TPDFlibViewer بهذا التحويل وفقاً للأصل النشط عندما يسجل كل نتيجة، ثم يطبق تعيين التدوير الأمامي في وقت الرسم، ولهذا السبب تهبط الأشرطة بشكل صحيح عند كل زاوية. الشفافية نفسها تستخدم واجهة برمجة تطبيقات Windows AlphaBlend مع ألفا مصدر ثابتة، نظراً لأن قماش VCL (canvas) ليس لديه تركيب ألفا أصلي. يتم تغطية واجهات برمجة تطبيقات البحث عن النص والهندسة الأساسية بعمق في مقالة البحث عن النص وتعداد عناصر الصفحة

نقرات التعليقات التوضيحية والتلميحات وتصدير المرفقات

يتعامل TPDFlibViewer مع التعليقات التوضيحية ككائنات تفاعلية من الدرجة الأولى. يؤدي النقر فوق تعليق توضيحي غير رابط إلى إطلاق OnAnnotClick مع رقم الصفحة، وفهرس التعليق التوضيحي المعتمد على 1 وسلسلة النوع الفرعي (subtype)؛ ويكشف AnnotAtPagePoint عن نفس اختبار التصادم برمجياً ويتخطى التعليقات التوضيحية المصاحبة لـ Popup بحيث تحصل دائماً على التعليق التوضيحي الذي يراه المستخدم بالفعل. يعرض تمرير الماوس فوق تعليق توضيحي نص محتوياته (Contents) كتلميح لعنصر التحكم من خلال مفتاح AnnotHints، والذي يكون قيد التشغيل افتراضياً ويتعاون مع خاصية VCL ShowHint قياسية. بالنسبة للتعليقات التوضيحية من نوع FileAttachment، يكتب SaveAnnotAttachmentToFile الملف المضمن على القرص ويعيد القيمة 1 عند النجاح، مما يكمل تدفق عمل النقر للحفظ في بضعة أسطر

procedure TMainForm.ViewerAnnotClick(Sender: TObject; APage, AnnotIndex: Integer;
  const Subtype: WideString);
begin
  if Subtype = 'FileAttachment' then
    if FViewer.SaveAnnotAttachmentToFile(APage, AnnotIndex,
        'C:\Temp\attached-invoice.xml') = 1 then
      StatusBar1.SimpleText := 'Attachment saved';
end;

الطباعة والحدود التي يجب أن تخطط لها

لا تحتاج الطباعة إلى تكامل منفصل. يعرض PrintDoc مربع حوار طباعة Windows القياسي — اختيار الطابعة، ونطاق الصفحات، والنسخ، والترتيب — ويسلم المهمة إلى محرك الطباعة الخاص بالمكتبة، بينما يرسل PrintDoc(False) المستند بأكمله إلى الطابعة الافتراضية بدون واجهة مستخدم على الإطلاق. ينتقل PrintDocRange برمجياً بالكامل مع اسم طابعة صريح، وصفحة البداية والنهاية، وعدد النسخ، وعلامة الترتيب، وهو نقطة الدخول الصحيحة لوظائف الطباعة المجمعة أو التي يتم تشغيلها بواسطة الخادم والمدفوعة من نفس مثيل العارض

هناك حدان يستحقان الذكر الصادق. أولاً، TPDFlibViewer هو عنصر تحكم Windows VCL؛ ويتم استبعاد طبقة العارض من بنيات NOVCL، لذا يجب أن تستخدم خدمات وحدة التحكم وأهداف غير VCL واجهة برمجة تطبيقات عرض TPDFlib الأساسية مباشرة. ثانياً، يتم دعم الاستخدام بدون رأس (headless) عمداً ولكنه محدود: يقوم CaptureRegion بعرض أي منطقة عميل — الصفحات بالإضافة إلى التظليلات — في صورة نقطية (bitmap) يمتلكها المتصل ويعمل على عنصر تحكم غير مرتبط بوالد بدون مقبض نافذة (window handle)، لأن مسار الرسم يتجنب استدعاءات VCL التي قد تفرض إنشاء مقبض. هذا يجعل التحقق على مستوى البكسل في الاختبارات الآلية عملياً، ولكن أي شيء يحتاج فعلياً إلى حلقة رسائل، مثل أدوات الماوس التفاعلية، لا يزال ينتمي إلى جلسة واجهة مستخدم حقيقية. من جانب الأداء، يحتفظ عنصر التحكم بذاكرة تخزين مؤقت لصفحات الذاكرة بميزانية قابلة للتكوين عبر SetCacheLimit، ويمكنه الاحتفاظ بالصفحات المعروضة عبر الجلسات؛ وتغطي مقالة ذاكرة التخزين المؤقت لصفحات قرص العارض تلك الطبقة وسلوك DPI لكل شاشة بالتفصيل

خمس خطوات إذن: ضع عنصر التحكم واستدعِ LoadFromFile، وأرفق الإجراءات الجاهزة من PDFlibViewerActions، واستدعِ HighlightSearchHits للبحث داخل الصفحة، وتعامل مع OnAnnotClick لتدفقات عمل التعليقات التوضيحية، واكشف عن PrintDoc. كل قطعة معروضة هنا تشحن في losLab PDF Library القياسية لدلفي، حيث يتم تثبيت عنصر تحكم العارض وفئات الإجراءات ومحرك العرض معاً