يتلخص عارض PDF في Delphi في مكونين والتوصيل بينهما. يتولى TPdf المستند: يفتح الملف، ويفك تشفيره، ويجيب عن أسئلة مثل عدد الصفحات والبيانات الوصفية. أما TPdfView فهو عنصر العرض المرئي الذي يرسم الصفحات على الشاشة ويتولى التمرير والتكبير والصفحة التي يراها المستخدم الآن. تغلّف PDFium VCL نفس محرك العرض الموجود داخل Chrome، لذلك تتطابق الحروف وتنعيم الحواف والألوان التي تراها على سطح الرسم مع ما يراه المستخدمون في متصفحاتهم. العمل ليس في العرض نفسه، بل في ربط كائن المستند بالعرض، والتحميل من دون تعطل عند ملف تالف أو محمي بكلمة مرور، وتوفير مجموعة التحكم التي تجعل العارض يبدو مكتملًا: الانتقال بين الصفحات، تغيير مستوى التكبير، وملاءمة الصفحة مع النافذة
يمر هذا الشرح على عملية التجميع بالترتيب الذي تبني به التطبيق فعلًا. كل ما هنا يعرض صفحة واحدة في كل مرة، وهذا ما تريده معظم تدفقات عمل المستندات. إذا كنت تحتاج إلى صفحات مكدسة في عمود واحد قابل للتمرير المستمر، فهذه مسألة تخطيط مختلفة يغطيها مقال منفصل وليست الطريق هنا
ربط TPdf بـ TPdfView
ضع TPdf وTPdfView على النموذج، ثم أخبر العرض بالمستند الذي يجب أن يعرضه. ذلك الإسناد الوحيد هو الصلة الكاملة بين المستند غير المرئي والعنصر الذي يرسمه
procedure TFormMain.FormCreate(Sender: TObject);
begin
// Pdf and PdfView were dropped at design time.
PdfView.Pdf := Pdf; // the view paints whatever this document holds
PdfView.FitMode := pfmFitWidth; // start the user at a sensible zoom
end;
قبل أن يعمل أي شيء من هذا، يجب أن تكون المكتبة الأصلية لـ PDFium موجودة على الجهاز. تستدعي PDFium VCL الملف pdfium32.dll أو pdfium64.dll بحسب منصة الهدف، ولن يُفتح المستند إذا تعذر العثور على ملف DLL. وزّع ملف DLL المطابق بجانب الملف التنفيذي، أو ضعْه حيث يستطيع محمل النظام العثور عليه. الإصدارات المفعلة بـ V8 مخصصة فقط لملفات PDF التي تحتوي على JavaScript تريد تشغيله، وهذا ليس ما يفعله عارض عادي، لذا استخدم ملف DLL القياسي ما لم يكن لديك سبب محدد لغير ذلك
تحميل مستند من دون الوثوق بالمدخلات
غالبًا ما يدفعك الحدس إلى تغليف التحميل داخل try/except والتعامل مع الاستثناء باعتباره فشلًا. هذا الحدس خاطئ هنا، والخطأ فيه ينتج عارضًا يبدو سليمًا حتى يمرر إليه أحدهم ملفًا معطوبًا. تعيين Active := True لا يطلق استثناء عند فشل التحميل. تلتقط PDFium VCL الخطأ الداخلي وتبقي Active عند False، لذا فإن الطريقة الصادقة الوحيدة لمعرفة ما إذا كان المستند قد فُتح هي قراءة الخاصية بعد تعيينها
procedure TFormMain.OpenDocument(const FileName: string);
begin
Pdf.FileName := FileName;
Pdf.Active := True; // never raises; failure leaves Active = False
if not Pdf.Active then
begin
ShowMessage('Could not open ' + FileName);
Exit;
end;
PdfView.PageNumber := 1; // the view tracks its own current page
UpdatePageLabel;
end;
هناك نقطتان تستحقان الانتباه. الأولى أن PageNumber موجودة في الكائنين، والقيمتان مستقلتان. Pdf.PageNumber هو تصور المستند للصفحة الحالية، أما PdfView.PageNumber فهو الصفحة التي يعرضها العنصر فعليًا، وهو الذي تضبطه لتحريك المستخدم داخل الملف. تعيين أحدهما لا ينقل الآخر، لذلك يقود العارض دائمًا خاصية العرض. الثانية هي الفهرسة التي تبدأ من 1: الصفحات تمتد من 1 إلى Pdf.PageCount، لا من 0، وهذا قد يفاجئ من اعتاد على المصفوفات التي تبدأ من الصفر
التعامل مع ملف مشفّر
تندمج المستندات المشفّرة في مسار التحميل نفسه. إذا كانت كلمة المرور مضبوطة قبل التفعيل، يفك المستند تشفيره عند فتحه، وإذا كانت خاطئة أو مفقودة، تبقى Active عند False تمامًا كما يحدث مع ملف تالف. لذلك يكون الاسترداد هو مطالبة المستخدم بكلمة مرور ثم إعادة محاولة التفعيل
procedure TFormMain.OpenWithPassword(const FileName: string);
var
Password: string;
begin
Pdf.FileName := FileName;
Pdf.Active := True;
if not Pdf.Active then
begin
if InputQuery('Password required', 'Password:', Password) then
begin
Pdf.Password := Password; // must be set before Active := True
Pdf.Active := True;
end;
if not Pdf.Active then
begin
ShowMessage('Unable to open the document.');
Exit;
end;
end;
PdfView.PageNumber := 1;
end;
لأن الفشل صامت في حالة كلمة المرور الخاطئة وفي حالة الملف التالف، لا يمكنك التمييز بينهما من Active وحدها. عمليًا هذا مقبول في العارض: إما أن يقدّم المستخدم كلمة المرور الصحيحة أو يكتشف أن الملف لن يفتح، وتكون الرسالة واحدة في الحالتين
التنقل عبر المستند
مع فتح المستند، يصبح التنقل مجرد حساب على PdfView.PageNumber مضبوطًا بواسطة Pdf.PageCount. العمل الحقيقي الوحيد هو الحصر ضمن الحدود، حتى لا تدفع الأزرار الصفحة خارج النطاق ويبقى زرا الأول والأخير معطّلين عند نهايتي الملف
procedure TFormMain.GoToPage(NewPage: Integer);
begin
if not Pdf.Active then
Exit;
if NewPage < 1 then
NewPage := 1
else if NewPage > Pdf.PageCount then
NewPage := Pdf.PageCount;
PdfView.PageNumber := NewPage;
UpdatePageLabel;
end;
// the four navigation buttons reduce to one call each
procedure TFormMain.FirstClick(Sender: TObject); begin GoToPage(1); end;
procedure TFormMain.PrevClick(Sender: TObject); begin GoToPage(PdfView.PageNumber - 1); end;
procedure TFormMain.NextClick(Sender: TObject); begin GoToPage(PdfView.PageNumber + 1); end;
procedure TFormMain.LastClick(Sender: TObject); begin GoToPage(Pdf.PageCount); end;
يكون مربع النص «الانتقال إلى الصفحة N» مجرد استدعاء آخر لـ GoToPage يغذيه عدد صحيح محلل، ويغطي الحصر الحالة التي يكتب فيها المستخدم 9999 في ملف من عشر صفحات. أبقِ UpdatePageLabel المكان الوحيد الذي يكتب «الصفحة 3 من 12» حتى لا ينفصل العرض النصي عما يظهره العرض
التكبير: نسب مئوية صريحة وأوضاع الملاءمة
يأتي التكبير في TPdfView على شكلين يتفاعلان معًا، وفهم هذا التفاعل هو الفرق بين عنصر تحكم في التكبير يعمل بسلاسة وآخر يعاند المستخدم. المسار المباشر هو الخاصية Zoom، وهي نسبة مئوية تعني فيها 100 الحجم الفعلي. أما المسار الآخر فهو FitMode، الذي يخبر العرض بأن يحسب التكبير لك ويواصل إعادة حسابه كلما تغير حجم النافذة
// fixed magnifications
PdfView.Zoom := 100; // actual size
PdfView.Zoom := 50; // half
PdfView.Zoom := 200; // double
// let the view size the page to the window, and keep it sized on resize
PdfView.FitMode := pfmFitWidth; // page width fills the control
PdfView.FitMode := pfmFitPage; // whole page visible
PdfView.FitMode := pfmActualSize; // 1:1 with the document's points
وهنا يقع ما يربك الناس. تعيين Zoom مباشرة يعيد FitMode إلى pfmNone. هذا سلوك صحيح، وليس خطأً: في اللحظة التي يختار فيها المستخدم 150% بالضبط، لا يستطيع العرض أن يظل ملتزمًا أيضًا بـ «ملاءمة العرض»، لأن الطلبين يتعارضان. نتيجة ذلك لواجهة المستخدم هي أن زر التكبير وزر ملاءمة الصفحة حالتان متنافيتان، ويجب أن يجعل شريط الأدوات الوضع النشط مرئيًا. عندما ينقر المستخدم على ملاءمة الصفحة، عيّن FitMode، وعندما ينقر على تكبير عددي، عيّن Zoom ودعه يفرغ وضع الملاءمة من تلقاء نفسه
إذا أردت بدلًا من ذلك أن تحسب قيمة الملاءمة بنفسك، ربما لتهيئة منزلق التكبير بالقيمة الحالية للملاءمة، فإن المساعدات الخاصة بكل صفحة تمنحك الأرقام من دون تغيير الوضع. تُرجع PageWidthZoom[N] وPageZoom[N] وActualSizeZoom[N] النسبة المئوية التي ستجعل الصفحة N ملائمة للعرض، أو ظاهرة بالكامل، أو معروضة بالحجم الفعلي
// seed a zoom readout from the fit-to-width value of the current page
var
FitPercent: Double;
begin
FitPercent := PdfView.PageWidthZoom[PdfView.PageNumber];
ZoomEdit.Text := Format('%.0f%%', [FitPercent]);
end;
What a finished viewer actually needs
العنوان الأصلي يبالغ في وصف العمل. العارض أعلاه لا يتجاوز بضع عشرات من الأسطر، ومع ذلك فهو ينجز ما تحتاجه أي عملية عمل على المستندات: فتح ملف، النجاة من ملف تالف، عرض صفحة، التنقل بين الصفحات، وتغيير التكبير يدويًا أو عبر الملاءمة. يتولى PDFium الأجزاء الصعبة بصمت. تُحل الخطوط المضمّنة، وتُرسم التعليقات وحقول النماذج في المكان الذي يضعها فيه المستند، وتطابق الصفحة التي تراها الصفحة التي سيرىها مستخدم Chrome، لأن المحرك نفسه يرسم الاثنين
من هذه القاعدة تكون الإضافات تدريجية لا بنيوية. تحديد النص والبحث يقرآن من طبقة النص نفسها التي يبنيها PDFium بالفعل؛ والبيانات الوصفية مثل Pdf.Title وPdf.Author لا تحتاج إلا قراءة خاصية واحدة؛ والدوران والتدرج الرمادي خيارات عرض تمررها عندما ترسم صفحة إلى صورة نقطية. لا يغيّر أي من ذلك العمود الفقري الموجود هنا، وهو كائن المستند، والعرض، ومسار التحميل ثم التنقل الذي يربطهما. إذا ضبطت هذا العمود الفقري جيدًا، تصبح بقية الأمور مجرد زخرفة
تعد المكونات TPdf وTPdfView المستخدمة في جميع أنحاء هذا الشرح جزءًا من PDFium VCL لـ Delphi وC++Builder، وهو يضم المرجع الكامل للعارض على صفحة المنتج