مقال تقني

معاينة الطباعة وإخراج سياق الجهاز في Delphi

تصيير صفحة PDF على سياق جهاز Windows لمعاينة الطباعة يضع ثلاثة أنظمة إحداثيات في سطر الكود نفسه، ونادرًا ما تتفق. فصفحة PDF تُقاس بالنقاط ونقطة أصلها في الزاوية السفلية اليسرى. وسياق جهاز الشاشة يُقاس بالبكسلات ونقطة أصله في الزاوية العلوية اليسرى مع معامل تكبير تختاره أنت. أما سياق جهاز الطابعة، وهو ما يُفترض أن تتنبأ به المعاينة، فيقيس البكسلات بدقة الجهاز لكنه يضع نقطة أصله عند زاوية المنطقة القابلة للطباعة، لا زاوية الورقة. أخطئ في أي واحد منها وتبدو المعاينة سليمة بينما تخرج الصفحة المطبوعة مزاحة أو محجَّمة أو مقصوصة على طول حافة. والعرض المعتاد هو نموذج ذو إطار يُعايَن موسّطًا ويُطبع مع قطع الخطين العلوي والأيسر، لأن طابعة الليزر لا تستطيع وضع الحبر في المليمترات القليلة الخارجية ولم يخبر أحد المعاينة بذلك. وتغطي losLab PDF Library (أي PDF Library for Delphi) المسار كله باستدعاءات تصيير سياق الجهاز، وطبقة تكوين للطابعة الافتراضية، وصور معاينة نقطية مولَّدة من قياسات الطابعة نفسها، وهذا هو الجزء الذي يجعل المعاينة صادقة بشأن ذلك الهامش

هندسة الورق ليست هندسة القابل للطباعة

يصف مستطيلان أي هدف طباعة، والإزاحة بينهما هي حيث تقيم معظم أخطاء المعاينة. مستطيل الورق هو الورقة المادية. ومستطيل القابل للطباعة هو المنطقة الأصغر التي يستطيع محرك الطباعة الوصول إليها فعليًا، منكمشًا بهامش عتادي يختلف بحسب طراز الطابعة وأحيانًا بحسب الدرج. وتقيس طبقة الطباعة في المكتبة كليهما. فيكشف الصنف الأساسي TPLPrinter عن PageWidth وPageHeight للمنطقة القابلة للطباعة، وFullPageWidth وFullPageHeight للورقة الكاملة، وPrintOffsetX مع PrintOffsetY للفجوة بين نقطتي أصلهما، كلها ببكسلات الجهاز عند الدقة التي تُبلغ عنها GetDPI. والمعاينة الصادقة تحجّم تلك الأرقام نفسها نزولًا إلى دقة الشاشة بدلًا من رسم الصفحة في أي مستطيل يصادف أن يملكه عنصر التحكم. تخطَّ تلك الخطوة وتفترض المعاينة بصمت هامشًا صفريًا، وهي القيمة الوحيدة التي لا تستخدمها أي طابعة حقيقية

مخطط PDF Library for Delphi لورقة الورق الكاملة مقابل مستطيل القابل للطباعة الأصغر، مع PrintOffsetX و PrintOffsetY يعلّمان الهامش العتادي بين نقطتي أصلهما
مستطيل الورق هو الورقة المادية بينما مستطيل القابل للطباعة هو ما يستطيع محرك الطباعة الوصول إليه، والفجوة بين نقطتي أصلهما هي حيث تقيم معظم أخطاء المعاينة

معاينة الشاشة عبر RenderPageToDC

لعنصر تحكم معاينة على الشاشة، ترسم RenderPageToDC(DPI, Page, DC) صفحة من المستند المحمَّل مباشرة على أي سياق جهاز GDI، سواء كان ذلك لوحة رسم TPaintBox، أو صورة نقطية خارج الشاشة، أو سياق جهاز لملف تعريف. ويضبط وسيط DPI التكبير. و96 تقارب عرضًا بنسبة 100% على شاشة تقليدية، ومضاعفتها تضاعف الحجم المُصيَّر

procedure TPreviewForm.PreviewBoxPaint(Sender: TObject);
begin
  // هذه الثلاثة حالة لزجة في المكتبة، لا معاملات لكل استدعاء:
  FPdf.SetRenderDCOffset(FOffsetX, FOffsetY);
  FPdf.SetRenderDCErasePage(1);
  FPdf.SetRenderCropType(0);
  FPdf.RenderPageToDC(FPreviewDpi, FCurrentPage, PreviewBox.Canvas.Handle);
end;

الفخ أن مسار التصيير إلى سياق الجهاز تقوده حالة لزجة في المكتبة، لا معاملات لكل استدعاء. فكل من SetRenderDCOffset وSetRenderDCErasePage وSetRenderCropType يبقى ساريًا حتى يغيّره شيء ما، لذا فإن حلقة مصغّرات تعمل بعد أن عدّل المستخدم العرض المكبَّر ترث أي إزاحة أو قص خلّفه مسار الكود السابق. والعرض هو معاينة تنحرف فقط في تسلسلات تنقّل محددة، وهذا من أبأس ما يمكن أن يكون عليه خطأ من حيث إعادة الإنتاج. وضبط كل الحالة ذات الصلة في مستهل معالج الرسم، كما أعلاه، لا يكلّف شيئًا ويزيل الفئة كلها. ويختبئ مضاعِف ثانٍ قريبًا. فدقة الإخراج الفعلية هي مقياس التصيير مضروبًا في وسيط DPI، ورغم أن SetRenderScale افتراضيًا 1.0، فهي تبقى سارية أيضًا ما إن تتغيّر، لذا فإن ميزة تصدير رفعتها بهدوء تعيد تحجيم كل معاينة لاحقة حتى يعيدها شيء ما إلى قيمتها

ولعارضات التمرير وإعادة الرسم الجزئية نسخة مخصصة. تأخذ RenderPageToDCClip مواصفة قص إلى جانب سياق الجهاز، بحيث إن إبطال شريط واحد من النافذة يعيد رسم ذلك الشريط فقط بدلًا من إعادة تنقيط الصفحة كاملة. وعند تكبير عالٍ على صفحات كبيرة التنسيق ذلك هو الفرق بين عارض يتتبع شريط التمرير وآخر يتلطّخ خلفه

مهمة طباعة تطابق المعاينة

يعمل جانب الطباعة عبر طابعة افتراضية. تستنسخ NewCustomPrinter طابعة نظام في تكوين خاص بالمكتبة، وتعدّل SetupPrinter ذلك المستنسخ من دون المساس بـ DevMode على مستوى الجهاز: يدخل الورق كإعداد 1 (ثابت DMPAPER_*) والاتجاه كإعداد 11. والمكسب هو العزل. فتستطيع خدمة أن تطبع ملصقات A4 بينما تبقى طابعة المضيف الافتراضية على Letter، ولا شيء يحتاج إلى استعادة بعدها

PDF Library for Delphi: تدفق من طابعة النظام الافتراضية عبر NewCustomPrinter و SetupPrinter إلى مهمة طباعة معزولة لا تمس أبدًا DevMode على مستوى الجهاز
تعيد SetupPrinter توجيه مستنسخ خاص بالمكتبة بحيث تستطيع خدمة أن تطبع A4 بينما تحتفظ طابعة المضيف الافتراضية بـ DevMode الخاص بـ Letter من دون مساس
var
  Pdf: TPDFlib;
  Virt: WideString;
  Opt: Integer;
begin
  Pdf := TPDFlib.Create;
  try
    if Pdf.LoadFromFile('report.pdf', '') <> 1 then
      raise Exception.Create('load failed');
    Virt := Pdf.NewCustomPrinter(Pdf.GetDefaultPrinterName);
    Pdf.SetupPrinter(Virt, 1, 9);        // الإعداد 1 = الورق، DMPAPER_A4
    Pdf.SetupPrinter(Virt, 11, 1);       // الإعداد 11 = الاتجاه، 1 = عمودي
    Opt := Pdf.PrintOptions(1, 1, 'Monthly Report');  // ملاءمة للورق، تدوير تلقائي + توسيط
    Pdf.PrintDocument(Virt, 1, Pdf.PageCount, Opt);
  finally
    Pdf.Free;
  end;
end;

تستحق PrintOptions قراءة متأنية. فهي تُعيد مقبض خيارات يجب أن تمرّره إلى PrintDocument أو PrintPages؛ وليست حالة محيطة. وبناء الخيارات ثم نسيان تمرير المقبض يفشل بصمت. فتُطبع المهمة بالافتراضيات، ولا يلاحظ أحد حتى تكون سياسة ملاءمة للورق متوقعة وتخرج صفحة كبيرة الحجم مقصوصة بدلًا من ذلك. ووسيط تحجيم الصفحة هو حيث تقيم تلك السياسة. فعدم التحجيم يحافظ على الدقة البُعدية، وهذا مهم للنماذج التي تُقاس بمسطرة. والملاءمة للورق تعيد تحجيم كل شيء إلى الورقة. وتقليص الصفحات الكبيرة يترك الصفحات العادية وشأنها ولا يتدخل إلا حين تتجاوز صفحة المنطقة القابلة للطباعة، وهذا عادةً الافتراضي الصحيح لمجموعة مستندات مختلطة. ويتعامل علم التدوير التلقائي والتوسيط مع الصفحات الأفقية من دون مسار كود ثانٍ

والتطبيقات التي تدير بالفعل TPrinter عبر تدفق حوار VCL تستطيع تسليمه مباشرة. تقبل PrintDocumentToPrinterObject وPrintPagesToPrinterObject نسخة TPrinter المُكوَّنة، وهذا يُبقي حوار الطباعة القياسي سطح التكوين الموجَّه للمستخدم بينما تتولى المكتبة تصيير الصفحات. وخلط النهجين في مسار كود واحد يميل إلى إعادة إدخال انحراف الهندسة الذي كان بقية هذا العمل يهدف إلى القضاء عليه، لذا اختر واحدًا. فمسار الطابعة الافتراضية يناسب الخدمات غير المراقبة؛ ومسار TPrinter يناسب التطبيقات التفاعلية

ويعمل الإخراج الانتقائي بالطريقة نفسها. تأخذ PrintPages سلسلة نطاق، لذا فإن تمرير اسم الطابعة الافتراضية و'2-5,12' ومقبض الخيارات يطبع الصفحات من 2 إلى 5 و12 مع بقاء عقد الهندسة سليمًا، والصيغة نفسها تقود نسخ الطباعة إلى ملف. وتلك النسخ الملفّية هي الإجابة العملية لبيئة غير مراقبة لا جهاز ماديًا موصولًا بها: اختبار تراجع لهندسة الطباعة على خادم بناء لا يملك طابور تعريفات على الإطلاق. صيّر المستند نفسه عبر الخيارات نفسها في ملف ناتج في كل بناء، ويتحول تراجع الهندسة إلى فرق بدلًا من بلاغ عميل بعد ثلاثة أسابيع

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

معاينة مُصيَّرة عند 96 DPI مقابل حجم صفحة مفترض تجيب عن السؤال الخاطئ. فهي تُظهر كيف تبدو الصفحة، لا ما ستضعه هذه الطابعة على هذا الورق. وتسد GetPrintPreviewBitmapToString تلك الفجوة ببناء المعاينة من الطابعة المخصصة نفسها ومقبض الخيارات نفسه اللذين ستستخدمهما المهمة النهائية، بحيث يصب حجم الورق والاتجاه وسياسة التحجيم والتدوير والإزاحة العتادية كلها في الصورة النقطية. وما يعود هو ما ستُظهره الورقة

PDF Library for Delphi: تباين بين معاينة شاشة بحجم صفحة مفترض تخطئ في الهوامش وصورة نقطية مطابقة للطابعة مبنية من طابعة المهمة نفسها ومقبض خياراتها
ترسم GetPrintPreviewBitmapToString المعاينة من الطابعة المخصصة نفسها ومقبض الخيارات نفسه اللذين ستستخدمهما المهمة النهائية، فتُظهر الصورة النقطية الهوامش والتدوير اللذين ستتلقاهما الورقة حقًا
procedure ShowPrinterTruePreview(Pdf: TPDFlib; const Virt: WideString; Opt: Integer);
var
  Data: AnsiString;
  Strm: TMemoryStream;
  Bmp: TBitmap;
begin
  Data := Pdf.GetPrintPreviewBitmapToString(Virt, 1, Opt, 1200, 0);
  Strm := TMemoryStream.Create;
  try
    Strm.WriteBuffer(PAnsiChar(Data)^, Length(Data));
    Strm.Position := 0;
    Bmp := TBitmap.Create;
    try
      Bmp.LoadFromStream(Strm);
      PreviewImage.Picture.Assign(Bmp);
    finally
      Bmp.Free;
    end;
  finally
    Strm.Free;
  end;
end;

يحدّ الوسيط MaxDimension الحافة الطويلة للصورة النقطية. و1200 بكسل تبقى حادة لحوار معاينة وتُبقي الذاكرة متواضعة حتى للرسومات الهندسية بحجم E، حيث سيبلغ تصيير بدقة كاملة عند 600 DPI للطابعة غيغابايتات

تذكّر خيارات الطابعة لدى المستخدم

حوارات الطباعة التي تنسى إعداداتها بين الجلسات تولّد تذاكر دعم خاصة بها. ويسلسل زوج DevMode، أي GetPrinterDevModeToString وSetPrinterDevModeFromString، تكوين التعريف الكامل للطابعة إلى سلسلة معتمة يمكنك تخزينها في تفضيلات المستخدم واستعادتها في الجلسة التالية، بما في ذلك الخيارات الخاصة بالتعريف التي لا تكلّف أي واجهة عامة نفسها عناء نمذجتها. احفظ الطابعة بالاسم من GetPrinterNames، وليس بفهرس القائمة أبدًا. فترتيب الفهرس يتغيّر كلما أُضيفت طابعة أو أُزيلت، لذا يشير فهرس محفوظ بهدوء إلى الجهاز الخاطئ في المرة التالية التي تتزحزح فيها القائمة. وتغطي GetDefaultPrinterName الاحتياط حين يكون الجهاز المتذكَّر قد اختفى كليًا

ويكمل اختيار الدرج قصة الاستمرارية. تُبلغ GetPrinterBins عن مصادر الورق التي يكشفها التعريف، وهذا مهم لسير عمل الورق ذي الترويسة حيث تُسحب الصفحة الأولى من درج الورق ذي الترويسة والبقية من الورق العادي. وتلك سياسة يتوقع المستخدمون أن يتذكرها التطبيق مع كل شيء آخر، ومهمة طباعة تحط على الورق الخاطئ تُقرأ كخطأ حتى حين يكون كل بايت في ملف PDF صحيحًا

أبقِ محركًا واحدًا عبر المعاينة والطباعة

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

والمستندات الأكبر من أن تُحمَّل بارتياح قبل الطباعة يمكن فتحها عبر مسار الوصول المباشر الموصوف في دمج ملفات PDF الكبيرة وتقسيمها والوصول المباشر إليها، الذي يصيّر الصفحات إلى سياق جهاز من مقبض ملف من دون بناء شجرة المستند. ويوجد مرجع واجهة الطباعة الكامل على صفحة منتج losLab PDF Library for Delphi