مقال تقني

بناء عارض PDF في Delphi مع PDFium Component

يتلخص عارض PDF في Delphi في مكونين والتوصيل بينهما. يمتلك TPdf المستند: فهو يفتح الملف، ويفك تشفيره، ويجيب على أسئلة حول عدد الصفحات والبيانات الوصفية. TPdfView هو عنصر التحكم المرئي الذي يرسم الصفحات على الشاشة ويتعامل مع التمرير، والتكبير/التصغير، والصفحة التي ينظر إليها المستخدم حاليًا. يغلف PDFium Component نفس محرك العرض الذي يتم شحنه داخل 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 Component ملف pdfium32.dll أو pdfium64.dll اعتمادًا على النظام الأساسي المستهدف، ويرفض المستند ببساطة الفتح إذا تعذر العثور على ملف DLL. قم بشحن ملف DLL المطابق بجانب الملف القابل للتنفيذ الخاص بك، أو ضعه حيث سيجده محمل النظام. لا توجد الإصدارات التي تدعم V8 إلا لملفات PDF التي تحمل JavaScript ترغب في تنفيذه، وهو ما لا يفعله عارض عادي، لذا استخدم ملف DLL القياسي ما لم يكن لديك سبب ملموس لعدم القيام بذلك

تحميل مستند دون الوثوق بالمدخلات

الغريزة هي تغليف التحميل في try/except ومعاملة الاستثناء المطروح كفشل. هذه الغريزة خاطئة هنا، والخطأ فيها ينتج عارضًا يبدو جيدًا حتى يسلمه شخص ما ملفًا مكسورًا. لا يؤدي تعيين Active := True إلى طرح استثناء عند فشل التحميل. يكتشف PDFium Component الخطأ الداخلي ويترك 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;

ما يحتاجه العارض النهائي حقًا

يحتوي العارض أعلاه على بضع عشرات من الأسطر، وهو يقوم بالفعل بالمهمة التي يحتاجها مسار عمل المستندات: فتح ملف، والنجاة من ملف سيء، وإظهار صفحة، والتنقل بين الصفحات، وتغيير التكبير يدويًا أو بالملاءمة. يقوم PDFium بالأجزاء الصعبة بصمت. يتم حل الخطوط المضمنة، وتُرسم التعليقات التوضيحية وحقول النموذج حيث يضعها المستند، وتتطابق الصفحة التي تراها مع تلك التي سيراها مستخدم Chrome، لأنه نفس المحرك الذي يرسم كليهما

من هذه القاعدة، تكون الإضافات تزايدية وليست هيكلية. يقرأ تحديد النص والبحث من نفس طبقة النص التي يبنيها PDFium بالفعل؛ وتكون البيانات الوصفية مثل Pdf.Title و Pdf.Author على بُعد قراءة خاصية واحدة؛ والتناوب والتدرج الرمادي عبارة عن خيارات عرض تمررها عندما ترسم صفحة إلى صورة نقطية. لا يغير أي من ذلك العمود الفقري الذي لديك هنا، وهو كائن المستند والعرض وتدفق التحميل ثم التنقل الذي يربط بينهما. اجعل ذلك العمود الفقري صحيحًا والباقي مجرد زخرفة

مكونات TPdf و TPdfView المستخدمة طوال الوقت هي جزء من PDFium Component لـ Delphi و C++Builder، والذي يحمل مرجع العارض الكامل على صفحة المنتج الخاصة به