كان لدى فريق معالجة المطالبات ثلاثون عامًا من الملفات الورقية تمر عبر ماسح ضوئي بآلية التغذية بالورق. كان الماسح يخرج ملف JPEG واحدًا لكل صفحة إلى مجلد، بأسماء 0001.jpg و0002.jpg وما إلى ذلك. لكن الأرشيف كان يحتاج فعليًا إلى ملف PDF واحد لكل ملف قضية، مع الصفحات بالترتيب، حتى يتمكن المراجع من فتح مستند واحد بدلًا من التنقل بين مئات الصور المصغرة. هذه الخطوة الأخيرة، تحويل رزمة مرقمة من المسوح إلى ملف PDF واحد مرتب، هي المهمة هنا.
يتكفل PDFium VCL بذلك مباشرة. فإلى جانب العرض واستخراج النص، يستطيع المكون إنشاء PDF من الصفر: إنشاء مستند فارغ، إضافة صفحة فارغة بأي حجم تريده، وضع صورة على تلك الصفحة بإحداثيات مساحة المستخدم، ثم الحفظ. يعمل المسار كله على المكون TPdf، لذا فالمحول الدفعي ليس سوى حلقة على أسماء الملفات مع بضع استدعاءات.
بنية التحويل
ثلاثة أشياء يجب أن تحدث لكل مسح. تحدد حجم الصفحة، وتضع الصورة داخل الصفحة مع ترك هامش، ثم تنتقل إلى الصفحة التالية. يوفّر PDFium VCL طريقة لكل خطوة: AddPage تنشئ صفحة فارغة بالحجم المحدد، وAddImage (أو AddPicture إذا كانت لديك TPicture بالفعل) ترسم الصورة النقطية في الصفحة الحالية، وPageNumber يخبر المكون بالصفحة التي تستهدفها استدعاءات الرسم اللاحقة.
التفصيل الذي يربك الناس هو نظام الإحداثيات. مساحة المستخدم في PDF تضع نقطة الأصل عند الزاوية اليسرى السفلية من الصفحة، ويزداد Y إلى الأعلى، بعكس إحداثيات الشاشة التي يعتاد عليها مطورو Delphi. الإحداثيان X, Y اللذان تمررهما إلى AddImage هما الزاوية اليسرى السفلية لمستطيل الصورة، أما Width, Height فهما حجم الموضع بالنقاط، لا حجم الملف المصدر بالبكسل. اقلب هذا الفهم وستنزل المسوح خارج الصفحة أو مقلوبة عما كنت تتوقع.
إنشاء المستند وصفحة لكل مسح
ابدأ بمستند فارغ. CreateDocument يخصص PDF جديدًا ويجعل المكون نشطًا، لذا لا توجد خطوة فتح منفصلة. من هناك تمر على قائمة الملفات الممسوحة، ولكل ملف تضيف صفحة، وتجعلها الحالية، وتضع الصورة. أبعاد الصفحة هنا هي A4 بالنقاط (595 × 842 بوضع عمودي)، وهو الحجم القياسي للمراسلات المؤرشفة.
procedure TArchiveForm.ScansToPdf(const Files: TStrings; const OutputPath: string);
const
PageW = 595.0; // A4 width in points
PageH = 842.0; // A4 height in points
Margin = 36.0; // half-inch border around each scan
var
I: Integer;
Pdf: TPdf;
begin
Pdf := TPdf.Create(nil);
try
Pdf.CreateDocument; // new, empty, already active
for I := 0 to Files.Count - 1 do
begin
Pdf.AddPage(I + 1, PageW, PageH); // 1-based page index
Pdf.PageNumber := I + 1; // make the new page current
PlaceScan(Pdf, Files[I], PageW, PageH, Margin);
end;
Pdf.SaveAs(OutputPath);
finally
Pdf.Free;
end;
end;
في كل دورة تُنشئ صفحة ثم تضبط PageNumber إليها مباشرة. هذا السطر الثاني مهم: AddPage يدرج الصفحة، لكن أوامر الرسم تعمل على الصفحة الحالية فقط، لذا فإن ضبط PageNumber هو ما يوجه AddImage إلى الصفحة التي أنشأتها للتو. إذا تجاهلت ذلك، ستتراكم صورك على الصفحة التي كانت محملة من قبل.
يوجد افتراض واحد مخفي في تلك الحلقة، وهو ترتيب Files. الماسح يسمي الصفحات من 0001.jpg إلى 0100.jpg، لكن تعداد المجلد لا يعيدها دائمًا مرتبة، وعندما تصل إلى page9.jpg بجوار page10.jpg فإن فرزًا نصيًا بسيطًا يضع الصفحة 10 قبل الصفحة 9. فرّز القائمة صراحة قبل الحلقة، وفضّل أسماءً بصفر بادئ وقت المسح حتى يطابق الترتيب اللفظي ترتيب الصفحات. تسلسل الصفحات هو أول شيء يلاحظه المراجع، وهو أرخص خطأ يمكن منعه.
وضع المسح والحفاظ على نسبة الأبعاد
نادراً ما يكون المسح بنفس شكل الصفحة. إذا مددته ليملأ الورقة ستشوّه النص، وإذا وضعته بحجمه البكسلي الكامل فسيتجاوز الحدود. الحل هو التحجيم وفق أصغر النسبتين، ملاءمة العرض أو ملاءمة الارتفاع، ثم توسيط ما يتبقى. ولأن نقطة الأصل تقع في أسفل اليسار، فإن التوسيط يعني تقسيم المساحة المتبقية بالتساوي وإضافتها إلى كل من X وY.
procedure TArchiveForm.PlaceScan(Pdf: TPdf; const FileName: string;
PageW, PageH, Margin: Double);
var
Pic: TPicture;
AvailW, AvailH, Scale, DrawW, DrawH, X, Y: Double;
begin
Pic := TPicture.Create;
try
Pic.LoadFromFile(FileName); // BMP, JPG, PNG, etc. via the VCL graphics units
AvailW := PageW - 2 * Margin;
AvailH := PageH - 2 * Margin;
// Fit inside the margins without distorting the scan.
Scale := Min(AvailW / Pic.Width, AvailH / Pic.Height);
DrawW := Pic.Width * Scale;
DrawH := Pic.Height * Scale;
// Center: leftover space split evenly. Y measured from the page bottom.
X := (PageW - DrawW) / 2;
Y := (PageH - DrawH) / 2;
Pdf.AddImage(FileName, X, Y, DrawW, DrawH);
finally
Pic.Free;
end;
end;
يحمّل هذا الملف مرة واحدة لقراءة أبعاده بالبكسل، ويحسب عامل تحجيم موحّدًا واحدًا، ثم يمرر مستطيل التموضع إلى AddImage. يقبل AddImage مسار ملف مباشرة ويمرره عبر نفس مسار الصور الذي يستخدمه AddPicture، لذلك يعمل أي تنسيق تتعرف عليه وحدات رسومات VCL من دون معالجات خاصة. وإذا كانت الصورة مفكوكة الترميز بالفعل داخل TPicture من لوحة معاينة، فاستدعِ AddPicture(Pic, X, Y, DrawW, DrawH) بالمستطيل نفسه وتخطَّ القراءة الثانية للملف.
تجاوز فك الترميز لمسوح JPEG
الماسحات تصدر JPEG في الغالب. تحميل JPEG إلى TPicture يفك ترميزه إلى صورة نقطية، ثم يعيد PDFium ترميزه عند الحفظ، وهما مرحلتان خسارتان لا تحتاج إليهما. AddJpegImage يضمّن البايتات المضغوطة الأصلية مباشرة في الصفحة من دفق، وهو أسرع وأنظف بصريًا على دفعة كبيرة.
var
Stream: TFileStream;
begin
// ... after AddPage + PageNumber for the current page ...
Stream := TFileStream.Create(FileName, fmOpenRead);
try
// Embeds the JPEG bytes as-is; no decode/re-encode cycle.
Pdf.AddJpegImage(Stream, X, Y, DrawW, DrawH);
finally
Stream.Free;
end;
end;
ما زلت تحسب X وY وDrawW وDrawH بالطريقة نفسها، لأنك تحتاج إلى الأبعاد بالبكسل للتحجيم. اقرأها من الملف أو من تحليل سريع للرأس، ثم سلّم الدفق الخام إلى AddJpegImage. أما مسوح PNG أو TIFF فمسار AddImage هو الصحيح؛ احجز اختصار JPEG للصيغة التي ينطبق عليها فعلًا.
وضع تسمية لكل صفحة
تكون المسوح المؤرشفة أسهل في المراجعة عندما تحمل كل صفحة اسم ملفها الأصلي. AddText يرسم سلسلة نصية عند إحداثي في مساحة المستخدم، لذا تظهر التسمية أسفل الصورة مباشرة. تذكّر انعكاس محور Y: لوضع تسمية أسفل المسح، تطرح من الحافة السفلية للصورة بدل أن تضيف إليها.
// Caption below the scan: Y decreases toward the page bottom.
Pdf.AddText('File: ' + ExtractFileName(FileName), 'Helvetica', 9,
X, Y - 14, clGray);
ملاحظة أخيرة حول الحفظ. SaveAs دالة ترجع قيمة منطقية، لذا تحقّق من نتيجتها في الكود الإنتاجي بدل افتراض نجاح الكتابة؛ فالقرص الممتلئ أو مسار الإخراج المقفل يفشل بصمت وإلا. بعد انتهاء الحلقة وكتابة الملف، تحصل بالضبط على ما يحتاجه الأرشيف: ملف PDF واحد مرتب لكل ملف قضية، والصفحات بحجم مناسب وجاهزة للقراءة في أي عارض.
تغطي اللبنات نفسها مهامًا قريبة. بدّل قاعدة التحجيم لكل صفحة فتحصل على كتاب صور فيه صورة واحدة في كل ورقة؛ واحتفظ بالحَلقة لكن اقرأ من مصدر TIFF متعدد الصفحات فتحصل على محول أرشيف فاكس. إذا أردت الصورة الأوسع لبناء ملفات PDF برمجيًا، فراجع إنشاء مستندات PDF من الصفر باستخدام PDFium VCL؛ ولإعادة عرض النتيجة على الشاشة لاحقًا، فراجع تحويل صفحات PDF إلى صور JPEG باستخدام PDFium VCL.
PDFium VCL Component من loslab.com يجمع واجهات إنشاء المستندات والعرض والنص المستخدمة طوال هذه السلسلة.