مقال تقني

طباعة ملف PDF محمل في Delphi باستخدام HotPDF و TPrinter

يقوم HotPDF بطباعة ملف PDF محمل إلى طابعة مادية، أو أي سياق جهاز ويندوز، أو الحافظة (clipboard) من خلال ثلاثة استدعاءات: حيث تقود PrintLoadedDocument المكون TPrinter لـ VCL، وترسم RenderLoadedPageToDC صفحة مباشرة في أي HDC، وتنسخ RenderLoadedPageToClipboard صفحة كصورة نقطية أو ملف تعريف متجه (vector metafile)؛ وإن HotPDF عبارة عن مكون PDF أصلي لـ VCL في Delphi و C++Builder، وقد أضاف مسار الطباعة هذا من الدرجة الأولى في الإصدار v2.331.0، وقام بتحسينه في الإصدار v2.348.0، بحيث لم تعد بحاجة لكتابة حلقة TPrinter يدويًا حول عارض الصور النقطية بنفسك؛ وترسم جميع الاستدعاءات الثلاثة محتوى الصفحة المتجهي مباشرة حيث يهم، بحيث تظل المطبوعات حادة بقدر ما يمكن للطابعة عرضها

لماذا يتم رسم صفحة PDF مباشرة في الطابعة بدلاً من الصورة النقطية؟

صفحة الـ PDF هي عبارة عن برنامج من معاملات الرسم المتجهي — كالمسارات والنصوص والصور المنفذة وفقًا لنموذج رسومات المواصفة ISO 32000-1 — وليست شبكة من البكسلات؛ والطريقة البديهية لطباعة إحداها هي تنقيط الصفحة إلى صورة نقطية ورسم تلك الصورة النقطية على لوحة الطابعة، وهذا يعمل بالفعل ولكنه يضيع الشيء الوحيد الذي تبرع فيه الطابعة؛ حيث تحلل طابعة الليزر الحديثة بدقة ٦٠٠ أو ١٢٠٠ DPI؛ بينما الصورة النقطية التي تم تنقيطها بدقة ١٥٠ أو ٣٠٠ DPI ثم تمديدها لتلائم الورقة تصل ناعمة (soft)، مع حواف نصية متعرجة وتشوهات مرئية؛ وتتخذ RenderLoadedPageToDC مسارًا أفضل: فهي تعيد تشغيل دفق محتوى الصفحة كاستدعاءات GDI منبعثة مباشرة إلى سياق الجهاز المستهدف، بحيث تصل النصوص المتجهة والرسوم الخطية إلى الطابعة كمتجهات ويتم تنقيطها مرة واحدة، بواسطة الطابعة، وبدقة الطابعة نفسها

كيف تقوم بطباعة ملف PDF محمل إلى طابعة مادية في Delphi؟

يقوم HotPDF بطباعة مستند محمل باستخدام PrintLoadedDocument(Printer, Options)، والتي تأخذ TPrinter مخصصًا لـ VCL وسجل THPDFPrintOptions، وتطبع الصفحات المحددة، وترجع عدد الصفحات المرسلة؛ قم بتعيين Printer.PrinterIndex أولاً لاختيار جهاز، واملأ سجل الخيارات — حيث تمنحك دالة الفئة THPDFPrintOptions.Default قيمًا معقولة — واستدعه مرة واحدة؛ وتقوم المكتبة بفتح المهمة باستخدام Printer.BeginDoc، وتمر عبر نطاق الصفحات مصدرة Printer.NewPage بين الصفحات، وتغلقها بـ Printer.EndDoc، بحيث لا تدير دورة حياة مهمة الطباعة يدويًا أبدًا

uses Printers;

var
  Pdf: THotPDF;
  Opts: THPDFPrintOptions;
begin
  Pdf := THotPDF.Create(nil);
  try
    if Pdf.LoadFromFile('invoice.pdf') > 0 then
    begin
      Printer.PrinterIndex := -1;         // -1 يختار الطابعة الافتراضية
      Opts := THPDFPrintOptions.Default;  // جميع الصفحات، 300 DPI، ملاءمة الصفحة، ملون
      Opts.PageRange := '1,3-5';          // مبني على أساس واحد؛ الفارغ يطبع كل صفحة
      Opts.Copies := 2;
      Pdf.PrintLoadedDocument(Printer, Opts);
    end;
  finally
    Pdf.Free;
  end;
end;

إن السلسلة النصية لـ PageRange مبنية على أساس واحد وتقرأ بالطريقة التي يقرأ بها مربع حوار الطباعة: '1,3-5' تطبع الصفحة ١ والصفحات من ٣ إلى ٥، وتطبع السلسلة الفارغة المستند بأكمله؛ وتطابق Copies مباشرة الخاصية Printer.Copies؛ وكن صادقًا مع نفسك بشأن ترتيب الصفحات (Collate): لأن VCL يعرض الترتيب بشكل غير متسق عبر إصدارات Delphi المختلفة، ويقوم HotPDF بتعيين عدد النسخ ويترك الترتيب لافتراضيات برنامج تشغيل الطابعة، لذا عندما تحتاج إلى ترتيب مؤكد للنسخ، اضبطه على برنامج التشغيل من خلال TPrinterSetupDialog بدلاً من الثقة في أن العلامة ستحمله

كيف يحدد DPI وملاءمة الصفحة (FitPage) مقياس الطباعة؟

يتفاعل FitPage و DPI بطريقة تستحق الفهم قبل الشحن؛ فعندما يكون FitPage مضبوطًا على True — وهو الافتراضي — يقرأ HotPDF صندوق MediaBox لكل صفحة، ويقوم بقياسها لتناسب Printer.PageWidth و Printer.PageHeight مع الحفاظ على نسبة العرض إلى الارتفاع، ويعرض بالمقياس الذي يجعل الصفحة تملأ الورقة؛ وفي هذا الوضع، يتم تجاوز Options.DPI فعليًا بواسطة مقياس الملاءمة، لأن المخرجات يجب أن تطابق أبعاد الصفحة المادية للطابعة؛ وعندما يكون FitPage مضبوطًا على False، يعرض HotPDF كل صفحة بنسبة ١:١ عند Options.DPI بالضبط، مطابقًا ٧٢ وحدة PDF مع بكسلات جهاز DPI، وهو ما تريده عندما تكون الصفحة المصدر والورقة بنفس الحجم بالفعل وتهتم بالدقة الدقيقة؛ والجا إلى FitPage عندما يختلف حجم الصفحة المصدر وحجم الدرج؛ والجا إلى ١:١ عندما يتطابقان وتريد مقياسًا حتميًا

هناك خياران تمت إضافتهما في الإصدار v2.348.0 يصلحان المطبوعات التي تبدو صحيحة تقريبًا؛ فلكل طابعة مادية هامش صلب — وهو حد غير قابل للطباعة لا يمكن للأجهزة الوصول إليه — وبشكل افتراضي تسقط نقطة أصل الصفحة بداخله، لذا تفقد الصفحة كاملة النزيف (full-bleed) شريطًا من حافتين؛ قم بتعيين IgnoreHardMargin إلى True وسيقوم HotPDF بالاستعلام عن PHYSICALOFFSETX و PHYSICALOFFSETY للطابعة، ثم يزيح نقطة أصل الجهاز لإلغاء هذه الإزاحة بحيث تتطابق نقطة أصل الصفحة مع زاوية الورقة الحقيقية؛ والخيار الثاني، AutoRotate، يتم تفعيله افتراضيًا: فهو يقرأ MediaBox لكل صفحة، وعندما تكون الصفحة أعرض من ارتفاعها، يقلب الطابعة إلى الوضع الأفقي (landscape) بحيث تطبع الصفحة العريضة غير مقطوعة بدلاً من اقتصاصها إلى الوضع الرأسي (portrait)؛ ويتنحى AutoRotate جانبًا لحظة تعيين Orientation بشكل صريح، لذا فإن اختيار الاتجاه المتعمد يفوز دائمًا

كيف تقوم بعرض صفحة PDF في أي سياق جهاز؟

تعد RenderLoadedPageToDC(PageIndex, DC, DPI, W, H) هي الأساسيات الموجودة أسفل حلقة الطباعة، وهي مفيدة بمفردها؛ حيث تستدعيها PrintLoadedDocument مرة واحدة لكل صفحة مقابل لوحة الطابعة، ولكن الهدف يمكن أن يكون أي HDC لنظام ويندوز: سياق جهاز ذاكرة خارج الشاشة، أو لوحة تحكم في عارض مخصص، أو سطح معاينة الطباعة؛ وفهرس PageIndex يبدأ من الصفر، و W و H هما وحدات الجهاز للسطح المستهدف، ويمر العارض عبر /Contents للصفحة ويصدر استدعاءات GDI إلى الـ DC الخاص بك؛ وهناك سلوك متعمد واحد يستحق المعرفة: فهو لا يرسم أي تعبئة للخلفية؛ فالمستدعي يمتلك السطح، لذا إذا كنت تريد ورقًا أبيض خلف الصفحة، فإنك تملأ الـ DC بنفسك قبل العرض، وهو بالضبط ما يتيح لك تركيب صفحة فوق لوحة موجودة بالفعل عندما تريد ذلك

// رسم الصفحة ١ في سطح ٢٤ بت خارج الشاشة بدقة ١٥٠ DPI
var
  Surface: TBitmap;
begin
  Surface := TBitmap.Create;
  try
    Surface.PixelFormat := pf24bit;
    Surface.SetSize(1275, 1650);           // ٨.٥ × ١١ بوصة بدقة ١٥٠ DPI
    Surface.Canvas.Brush.Color := clWhite;
    Surface.Canvas.FillRect(Surface.Canvas.ClipRect);  // المستدعي يلون الخلفية
    Pdf.RenderLoadedPageToDC(0, Surface.Canvas.Handle, 150, Surface.Width, Surface.Height);
    // يحمل السطح (Surface) الآن الصفحة المركبة، وجاهزة للرسم أو الحفظ
  finally
    Surface.Free;
  end;
end;

وعندما تحتاج إلى TBitmap جاهز — صورة مصغرة، أو تصدير صورة، أو معاينة ستقوم بتخزينها مؤقتًا — الجأ إلى RenderLoadedPageToBitmap بدلاً من ذلك، والمغطى في المقال المصاحب حول عرض صفحات PDF إلى صورة نقطية؛ والفرق هو من يملك السطح: فاستدعاء الصورة النقطية يخصص ويعيد مخزن بكسلات مؤقت، بينما ترسم RenderLoadedPageToDC في سياق جهاز تمتلكه بالفعل، وهو بالضبط ما تسلمك إياه لوحة الطابعة أو معالج الرسم؛ ويتشارك كلاهما في نفس مترجم دفق المحتوى ومحرك الحروف المدمجة، لذا يخرج النص بنفس الخطوط المدمجة الدقيقة في كلتا الحالتين

كيف تقوم بلصق صفحة PDF في Word أو محرر صور؟

تضع RenderLoadedPageToClipboard(PageIndex, DPI, AsMetafile) صفحة واحدة في حافظة ويندوز، جاهزة للصق في Word أو PowerPoint أو بريد إلكتروني أو محرر صور؛ وتختار علامة AsMetafile التنسيق، وهذا الاختيار يهم أكثر مما يبدو؛ فإذا مررت False، يضع HotPDF صورة نقطية للجهاز بقوة ٢٤ بت (CF_BITMAP) معروضة بدقة DPI الخاصة بك — وهي شبكة ثابتة من البكسلات يتم لصقها في أي مكان ولكنها تتشوه عندما يقوم المستلم بتكبيرها؛ وإذا مررت True، فإنه يضع ملف تعريف محسنًا (CF_ENHMETAFILE) تنتقل فيه نصوص الصفحة والرسوم المتجهة كأوامر رسم GDI، بحيث تظل النتيجة الملصقة حادة عند أي تكبير وتتغير لتناسب أي حجم يمنحه مستند الوجهة لها

// نسخ الصفحة ٢ كملف تعريف متجه قابل للتطوير للصق في Word
Pdf.RenderLoadedPageToClipboard(1, 300, True);

// نسخ نفس الصفحة كصورة نقطية ثابتة بدقة ٣٠٠ DPI بدلاً من ذلك
Pdf.RenderLoadedPageToClipboard(1, 300, False);

والقاعدة العامة بسيطة: إذا كان الشخص سيلصق الصفحة في مستند وقد يغير حجمها — كشكل في تقرير، أو بند تم إسقاطه في مسودة عقد — فاستخدم ملف التعريف (metafile)، لأن النصوص المتجهة تطبع وتكبر بشكل نظيف؛ وإذا كانت الوجهة تفهم بكسلات الصور فقط، أو كنت تريد لقطة مجمدة بدقة معروفة، فاستخدم الصورة النقطية؛ ولإخراج الكلمات من الصفحة بدلاً من صورتها، فإن هذه مهمة مختلفة تمامًا، تتعامل معها واجهة برمجة تطبيقات استخراج النصوص، والتي ترجع الأحرف ومواضعها بدلاً من صورة معروضة

أين يتوقف مسار الطباعة

كل شيء في هذه الصفحة مخصص لنظام ويندوز فقط، ويستحق ذكر ذلك بوضوح؛ حيث تم بناء PrintLoadedDocument و RenderLoadedPageToDC و RenderLoadedPageToClipboard على TPrinter، ونموذج سياق جهاز GDI، وحافظة ويندوز، وملفات التعريف المحسنة — وكلها بنيات Win32 و Win64؛ وعلى الأهداف الأخرى، لا يزال بإمكان HotPDF إنشاء وتحرير وقراءة الـ PDF، ولكن سطح الطباعة والحافظة المدعوم بـ GDI غير متاح؛ ويستهدف العارض أيضًا المجموعة الفرعية الشائعة للمستندات من النصوص والمسارات والصور, لذا فإن نماذج التصميم المليئة بالتدرجات والألوان المدارة بـ ICC تطبع كمعاينة دقيقة بدلاً من تجربة لونية دقيقة، وهي نفس الحدود التي يحملها عارض الصور النقطية

وبالنسبة للطباعة الدفعتية عبر العديد من الملفات، قم بتحميل وطباعة كل مستند بدورها وراقب استهلاك الذاكرة: حيث تعرض PrintLoadedDocument صفحة واحدة في كل مرة، ولكن رسم بياني المستند المحمل نفسه يعيش في الذاكرة طوال المهمة؛ وعندما تقود طابور طباعة عبر مئات الملفات الكبيرة، فإن تدفق واجهة برمجة تطبيقات الملف المباشر لتدفقات عمل PDF الكبيرة يقترن جيدًا مع مسار الطباعة — قم بتقسيم أو دمج أو تحديد الصفحات التي تحتاجها على القرص أولاً، ثم قم بتحميل النتيجة النحيفة فقط لطباعتها؛ وهذه هي كيفية شحن PrintLoadedDocument و RenderLoadedPageToDC و RenderLoadedPageToClipboard كجزء من مكون HotPDF لـ Delphi و C++Builder — وهي مكتبة VCL أصلية بدون تبعيات DLL خارجية تغطي إنشاء وتحرير واستخراج النصوص وعرض ملفات PDF، والآن الطباعة مباشرة إلى TPrinter، أو أي سياق جهاز، أو الحافظة