مقال تقني

تصدير TDataSet والتقارير إلى PDF في Delphi

لتصدير TDataSet أو تقرير إلى PDF في Delphi، تقدم مكتبة losLab PDF Library مسارين؛ حيث توائم PDFlibTableExport أي TDataSet — كاستعلام FireDAC، أو ClientDataSet، أو جدول في الذاكرة — إلى جدول PDF مرقم الصفحات، وتسلم ثلاثة جسور تحت Addons تقريرًا جاهزًا من FastReport أو QuickReport أو ReportBuilder إلى نفس كاتب الـ PDF؛ وينتج كلاهما مستند PDF حقيقيًا بدون برنامج تشغيل طابعة وبدون نافذة مرئية

تبدو المشكلتان متشابهتين ولكنهما ليستا كذلك؛ فجدول DBGrid أو نتيجة استعلام خام ليس له تخطيط خاص به، وبالتالي فإن تصديره يعني ابتكار تخطيط: أعمدة، ورؤوس، وفواصل صفحات؛ أما مستند FastReport أو ReportBuilder فيحمل بالفعل تخطيطًا مصممًا، لذا فإن تصديره يعني إعادة تشغيل أوامر الرسم الخاصة بشخص آخر بأمانة في مساحة الـ PDF؛ وتحتفظ مكتبة losLab PDF Library بهذه الاهتمامات في وحدات منفصلة تحديدًا لأن أوضاع الفشل تختلف، ويمر بقية هذا المقال عبر كل منها والحدود التي تتوقف عندها موثوقيتها

كيف تقوم بتصدير TDataSet إلى PDF في Delphi؟

كيف تقوم بتصدير TDataSet إلى PDF في Delphi؟

تحول دالة PDFlibTableExport مجموعة بيانات إلى جدول في استدعاء واحد؛ حيث يمر المصدر عبر قائمة الحقول مرة واحدة، ويتخطى حقول blob الثنائية تلقائيًا — فالحقول ftBlob و ftGraphic و ftBytes وأشباهها لا تحتوي على نص خلية مفيد — ويحاذي الأعمدة الرقمية إلى اليمين، ويرسم خطوط حمار وحشي (zebra stripes) اختيارية فوق شريط رأس منسق؛ وتحت الغطاء، يبني الشبكة باستخدام CreateTable، ويملأ الخلايا بـ SetTableCellContent، ويعرض بـ DrawTableRows، وهي نفس واجهة برمجة تطبيقات الجدول العامة التي قد تقودها يدويًا؛ ويقوم الغلاف الملائم PDFlibExportDataSet بتهيئة المليمترات ونقطة الأصل في أعلى اليسار لك، بحيث يوفر المستدعي الصفحة فقط

uses
  Data.DB, FireDAC.Comp.Client, FireDAC.Stan.StorageBin,
  PDFlibrary, PDFlibTableExport;

var
  MemTable: TFDMemTable;
  PDF: TPDFlib;
  Options: TPDFlibTableExportOptions;
begin
  MemTable := TFDMemTable.Create(nil);
  PDF := TPDFlib.Create;
  try
    MemTable.LoadFromFile('customer.FDS');   // أي TDataSet يعمل هنا

    PDF.SetOrigin(1);
    PDF.SetMeasurementUnits(1);              // مليمتر
    PDF.SetPageSize('A4');
    PDF.AddStandardFont(4);

    Options := DefaultTableExportOptions;
    Options.Title := 'Customers';
    Options.ColumnWidth := 32;               // ١١ حقلاً تلائم A4 عند ٣٢ مم
    Options.RepeatHeader := True;            // إعادة رسم الرأس في كل صفحة

    PDFlibExportDataSet(PDF, MemTable, 'customers.pdf', Options);
  finally
    PDF.Free;
    MemTable.Free;
  end;
end;

التخطي التلقائي لحقول blob هو خيار افتراضي وليس قيودًا صارمة؛ فعندما تحتاج إلى تحكم لكل حقل — كتسمية عمود مخصصة، أو عرض أضيق، أو إسقاط عمود مفتاح داخلي ليس blob — قم بإنشاء TPDFlibTableExporter مباشرة وربط حدثه OnFieldFilter event، الذي ينطلق مرة واحدة لكل حقل ويسلمك مواصفات قابلة للتغيير؛ واضبط Include على False لإسقاط الحقل، أو اضبط ColumnWidth و DisplayLabel لتجاوز الافتراضيات؛ وهذا الرابط هو أيضًا المكان الذي تستبعد فيه عمود مذكرة (memo) عريضًا لا تريده أن يضخم الصفحة

الترقيم: تمرير حالة الرسم بين الصفحات

تحمل DrawTableRows حالة ترقيم الصفحات، وفهم عقدها هو اللعبة بأكملها؛ حيث تستدعيها مع تحديد الصف الأول والصف الأخير؛ وتمرير صف أخير أقل من ١ يعني الرسم حتى نهاية الجدول، بحد أقصى للارتفاع الذي قدمته؛ وترجع الدالة الارتفاع الذي رسمته بالفعل، وتبلغ GetTableLastDrawnRow عن آخر صف ناسب المساحة؛ وهذا الزوج هو الحالة التي تسلمها من صفحة إلى أخرى: عندما يكون آخر صف مرسوم أقل من المجموع الكلي، تفتح صفحة جديدة وتستأنف من الصف الذي يليه؛ ولا يتم إعادة قياس أي شيء — فالاستمرار يبدأ تمامًا من حيث توقف الاستدعاء السابق

// كيف يستمر المصدر في رسم جدول طويل عبر الصفحات
PageHeight := PDF.PageHeight;
Y := PageHeight - Options.Top;
Row := 1;
while Row <= TotalRows do
begin
  DrawHeight := Y - Options.BottomMargin;
  // الصف الأخير = 0 يعني "الرسم حتى النهاية"، محدودًا بـ DrawHeight
  PDF.DrawTableRows(TableID, Options.Left, Y, DrawHeight, Row, 0);
  LastDrawn := PDF.GetTableLastDrawnRow(TableID);
  if LastDrawn >= TotalRows then
    Break;                       // الجدول بأكمله ناسب هذه الصفحة
  if LastDrawn < Row then
    Break;                       // أمان: لا تقدم للأمام، اخرج
  PDF.NewPage;
  Row := LastDrawn + 1;          // الاستمرار من أول صف غير مرسوم
  Y := PageHeight - Options.Top;
end;

الحلقة هي أيضًا المكان الذي يعيش فيه الرأس المتكرر؛ ففي كل صفحة استمرار، يرسم المصدر اختياريًا الصف ١ مجددًا — وهو الرأس — قبل صفوف البيانات، ويستخدم الارتفاع المرتجع من استدعاء DrawTableRows الأول لإزاحة الجسم تحته؛ ولهذا السبب ترجع DrawTableRows ارتفاعًا بدلاً من إحداثيات الصف التالي: فالقيمة المرتجعة هي ما يتيح لك تكديس رأس معاد رسمه والجسم المستمر دون تحديد حجم أي منهما بشكل جامد في الكود

كيف تقوم بتصدير تقرير FastReport أو ReportBuilder إلى PDF؟

تتخذ جسور التقارير النهج المعاكس: فهي لا تبتكر تخطيطًا أبدًا، بل تعيد تشغيل واحد؛ حيث يرتبط كل جسر بمحركه عند فتحة التصدير الأصلية للمحرك؛ وترث PDFlibFRExport من TfrxCustomExportFilter وتتلقى كائنات FastReport للمذكرات والصور والأشكال والخطوط، مترجمة كل منها إلى أساسيات PDFlibPas؛ وترث PDFlibRBDevice من TppFileDevice وتمر عبر قائمة DrawCommand لـ ReportBuilder؛ وتتخذ PDFlibQRExport مسارًا ثالثًا تمامًا يغطيه القسم التالي؛ وتعمل الثلاثة جميعها بدون واجهة رسومية (headless)، وهو السبب الكامل للجوء إليها على الخادم

التشغيل بدون واجهة رسومية ليس مجانيًا، وFastReport هو العبرة والتحذير؛ حيث يمر TfrxReport.Export عبر صفحات المعاينة، والتي تستشير الخاصية ShowDialog للمرشح، وترث هذه الخاصية القيمة الافتراضية True؛ وعلى جهاز لا يحتوي على سطح مكتب تفاعلي، يرجع مربع الحوار الشرطي نتيجة إلغاء ويفشل التصدير بصمت؛ فقم بتعيين ShowDialog إلى False قبل استدعاء Export وسيعرض التقرير بصمت؛ ويسري نفس الأمر على مفاتيح التبديل الموازية لـ ReportBuilder — وهي AllowPrintToFile و ShowPrintDialog – بينما QuickReport، الذي يقود كل شيء من خلال Prepare، لا يحتاج إلى كتم مربعات الحوار على الإطلاق

var
  Exporter: TPDFlibFRExport;
begin
  Report.PrepareReport;                  // بناء الصفحات أولاً
  Exporter := TPDFlibFRExport.Create(nil);
  try
    Exporter.FileName := 'invoice.pdf';
    Exporter.ShowDialog := False;        // بدون واجهة رسومية: تخطي النموذج الشرطي، بدون إلغاء
    Report.Export(Exporter);
  finally
    Exporter.Free;
  end;
end;

ثلاثة محركات، وثلاثة أنظمة إحداثيات

يقوم كل جسر بحسابات الإحداثيات الخاصة به، لأن كل محرك يقيس العالم بشكل مختلف؛ حيث تعامل PDFlibFRExport مواضع كائنات FastReport كبكسلات عند ٩٦ PPI وتقوم بالقياس بنسبة ٩٦/٢٥.٤ للوصول إلى المليمترات؛ وتقرأ PDFlibRBDevice أوامر رسم ReportBuilder بجزء من الألف من المليمتر وتقسم على ١٠٠٠، وتسجل نفسها من خلال ppRegisterDevice، لذا فإن تعيين ppReport.DeviceType إلى 'PDFlibPas' واستدعاء Print يكفي لتوجيه المخرجات من خلالها؛ وتتخذ PDFlibQRExport مسارًا ثالثًا تمامًا، وتصدر جسور FastReport و ReportBuilder، على النقيض من ذلك، أساسيات رسم متجهية أصلية ونصوصًا أصلية

var
  PDF: TPDFlib;
begin
  PDF := TPDFlib.Create;
  try
    PDF.SetOrigin(1);
    // يجهز التقرير ويلحق كل صفحة في استدعاء واحد؛ ويقوم الجسر
    // بتحويل EMF الخاص بكل صفحة إلى PDF من خلال ImportEMFFromStream.
    PDFlibQRExportReport(PDF, QuickRep1, 'ledger.pdf');
  finally
    PDF.Free;
  end;
end;

حيث تنتهي الدقة

اعرف الحدود قبل تخصيص سير العمل لهذه الجسور؛ حيث يسقط مصدر مجموعات البيانات الأعمدة الثنائية بصمت، ولذلك فإن التقرير الذي يجب أن يعرض صورة مدمجة يحتاج إلى نهج مختلف عن الجدول المستقيم؛ ومسار QuickReport، لأنه يمر عبر EMF، يخبز النص في رموز متجهة — وتبدو الصفحة صحيحة ولكنها لا تحمل نصًا قابلاً للتحديد أو البحث ولا تحتوي على هيكل PDF مميز لإمكانية الوصول (accessibility)؛ ويحتفظ FastReport و ReportBuilder بالنصوص الحقيقية، ولكن المعالجات المكتوبة تغطي العروض الشائعة — كالمذكرات والصور والأشكال والخطوط — وتتراجع إلى مربع محيط أو تتخطى الكائنات الغريبة، ولذلك فإن التقرير الذي يعتمد على النصوص المنسقة أو الرموز الشريطية (barcodes) أو التعبئة المتدرجة سيفقد التفاصيل؛ وليس أي من هذا عيبًا، بل هو الحد الحقيقي لطبقة الترجمة

هناك تحذير تشغيلي واحد يفوق البقية: لا يتم شحن أي من المحركات الثلاثة داخل مكتبة losLab PDF Library، ويتم استبعاد الجسور عمدًا من البناء الرئيسي — فهي تجمع فقط من مشروع يحتوي بالفعل على FastReport VCL 6.x أو QuickReport 8 أو ReportBuilder 20 في مسار البحث الخاص به؛ ولقد ظلت أسطح ملفات التعريف و DrawCommand التي تستهدفها مستقرة عبر عدة إصدارات رئيسية للمحركات، ولكنك تمتلك مطابقة الإصدار؛ واضبط ذلك بشكل صحيح وسيغطي المساران الانتشار الكامل، بدءًا من تفريغ DBGrid المخصص وحتى الفاتورة المصممة؛ ويتم شحن مصدر الجداول وجسور التقارير معًا مع losLab PDF Library لـ Delphi و C++Builder