كانت التذكرة من ثلاثة أسطر: "المعاينة تعرض النموذج في الوسط. الصفحة المطبوعة مزاحة إلى أعلى ويسار، والحد مقصوص من جهتين". لم يكن في كود التصيير أي خطأ. كانت المعاينة ترسم الصفحة نسبة إلى ورقة الورق، بينما يقع أصل device context الخاص بالطابعة عند زاوية المنطقة القابلة للطباعة — وطابعة الليزر المعنية لم تكن تستطيع الوصول إلى 4.2 mm الخارجية من الورقة. كل ميزة طباعة في Delphi تصطدم في النهاية بهذا الاختلاف، والوقت الأرخص لمعالجته هو قبل أن يطبع أول عميل نموذجا ذا حدود. تغطي losLab PDF Library (PDFlibPas) المسار كله باستدعاءات تصيير إلى device context، وطبقة إعداد طابعة افتراضية، وbitmaps للمعاينة مولدة من مقاييس الطابعة نفسها.
هندسة الورق ليست هندسة الطباعة
ثلاثة مستطيلات تصف أي هدف طباعة، وخلطها ينتج التذكرة أعلاه بالضبط. مستطيل الورق هو الورقة الفيزيائية. مستطيل الطباعة هو المنطقة التي يستطيع محرك الطابعة الوصول إليها. والإزاحة بين أصليهما هي هامش العتاد، وتختلف حسب طراز الطابعة وأحيانا حسب الدرج. تمثل طبقة الطباعة في المكتبة الثلاثة كلها: فئة TPLPrinter الأساسية تعرض PageWidth و PageHeight للمنطقة القابلة للطباعة، و FullPageWidth و FullPageHeight للورقة، و PrintOffsetX و PrintOffsetY للفجوة، كلها بالبكسلات الجهازية عند الدقة التي يعيدها GetDPI. المعاينة الصادقة تصغر المستطيلات الثلاثة نفسها إلى دقة الشاشة بدلا من رسم الصفحة داخل أي مستطيل يملكه عنصر التحكم.
معاينة الشاشة عبر RenderPageToDC
بالنسبة إلى عنصر تحكم معاينة على الشاشة، ترسم RenderPageToDC(DPI, Page, DC) صفحة من المستند المحمل مباشرة على أي GDI device context — canvas الخاص بـ TPaintBox، أو bitmap خارج الشاشة، أو metafile DC. يحدد معامل DPI مستوى التكبير: 96 يقارب عرض 100% على شاشة كلاسيكية، ومضاعفته تضاعف الحجم المرصود.
procedure TPreviewForm.PreviewBoxPaint(Sender: TObject);
begin
// these three are sticky library state, not per-call parameters:
FPdf.SetRenderDCOffset(FOffsetX, FOffsetY);
FPdf.SetRenderDCErasePage(1);
FPdf.SetRenderCropType(0);
FPdf.RenderPageToDC(FPreviewDpi, FCurrentPage, PreviewBox.Canvas.Handle);
end;
الفخ هو أن مسار التصيير إلى DC تقوده حالة مكتبة sticky لا معاملات لكل استدعاء. تبقى SetRenderDCOffset و SetRenderDCErasePage و SetRenderCropType فعالة حتى تتغير، لذلك ترث حلقة thumbnails التي تعمل بعد أن عدل المستخدم العرض المكبر أي offset أو crop تركه المسار السابق — والعَرَض معاينة تنزاح فقط في تسلسلات تنقل محددة، وهذا بائس في إعادة الإنتاج. ضبط كل الحالة ذات الصلة في بداية paint handler كما في الأعلى لا يكلف شيئا ويزيل فئة الخطأ كلها. يوجد مضاعف ثان قريب: الدقة الفعلية للإخراج هي render scale مضروبا في معامل DPI، و SetRenderScale يبدأ من 1.0 لكنه يستمر بعد تغييره، لذلك قد تعيد ميزة export عدلت المقياس تصغير أو تكبير كل معاينة لاحقة بصمت.
للعوارض القابلة للتمرير وإعادة الرسم الجزئية متغير مخصص: تأخذ RenderPageToDCClip مواصفة clip مع device context، لذلك يعيد إبطال شريط واحد من النافذة رسم ذلك الشريط فقط بدلا من إعادة rasterizing للصفحة كلها. عند مستويات تكبير عالية على صفحات كبيرة، يكون هذا الفرق بين عارض يواكب scrollbar وآخر يتلطخ خلفه.
مهمة طباعة تطابق المعاينة
يعمل جانب الطباعة عبر طابعة افتراضية: تستنسخ NewCustomPrinter طابعة نظام إلى إعداد خاص بالمكتبة، وتعدل SetupPrinter ذلك الاستنساخ — الورق مع setting 1 (ثابت DMPAPER_*) والاتجاه مع setting 11 — من دون لمس DevMode على مستوى الجهاز. تستطيع خدمة طباعة ملصقات A4 بينما تبقى الطابعة الافتراضية للمضيف على 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); // setting 1 = paper, DMPAPER_A4
Pdf.SetupPrinter(Virt, 11, 1); // setting 11 = orientation, 1 = portrait
Opt := Pdf.PrintOptions(1, 1, 'Monthly Report'); // fit to paper, auto-rotate + center
Pdf.PrintDocument(Virt, 1, Pdf.PageCount, Opt);
finally
Pdf.Free;
end;
end;
تستحق PrintOptions قراءة دقيقة: إنها تعيد مقبض خيارات يجب تمريره إلى PrintDocument أو PrintPages. ليست حالة محيطة. بناء الخيارات ونسيان تمرير المقبض فشل صامت — تطبع المهمة بالإعدادات الافتراضية، ولا يلاحظ أحد حتى كانت سياسة fit-to-paper متوقعة وخرجت صفحة كبيرة مقصوصة. يحمل معامل page-scaling السياسة: لا scaling يحافظ على الدقة البعدية للنماذج التي تُقاس بالمساطر، و fit-to-paper يعيد قياس كل شيء، و shrink-large-pages يتدخل فقط عندما تتجاوز الصفحة المنطقة القابلة للطباعة — وهذا غالبا الافتراض الصحيح لمجموعات مستندات مختلطة. علم auto-rotate-and-center يتعامل مع صفحات landscape من دون مسار كود منفصل.
التطبيقات التي تدير TPrinter أصلا عبر تدفق حوار VCL تستطيع تسليمه مباشرة: تقبل PrintDocumentToPrinterObject و PrintPagesToPrinterObject نسخة TPrinter المضبوطة، فيبقى حوار الطباعة القياسي هو سطح الإعداد أمام المستخدم بينما تتولى المكتبة تصيير الصفحات. لا يختلط النهجان جيدا في مسار كود واحد — اختر مسار الطابعة الافتراضية للخدمات غير المراقبة ومسار TPrinter للتطبيقات التفاعلية، وسيبقى عقد الهندسة واحدا.
البيئات غير المراقبة تحصل أيضا على إخراج بلا جهاز فيزيائي: لدى استدعاءات طباعة نطاقات الصفحات متغيرات print-to-file، وهي الجواب العملي لاختبار regression لهندسة الطباعة على build server بلا driver queue. صيّر المستند نفسه عبر الخيارات نفسها إلى artifact ملف في كل build، وسيتحول regression الهندسة إلى diff بدلا من تقرير عميل.
Bitmaps للمعاينة بمقاييس الطابعة نفسها
معاينة مصيّرة عند 96 DPI مقابل حجم صفحة مفترض تجيب عن السؤال الخطأ. تبني 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 حافة bitmap الطويلة: 1200 بكسل حادة بما يكفي لحوار معاينة وتحافظ على الذاكرة متواضعة حتى لرسومات هندسية بحجم E، حيث قد يبلغ التصيير بالدقة الكاملة عند 600 DPI للطابعة غيغابايتات.
تذكر اختيارات الطابعة للمستخدم
حوارات الطباعة التي تنسى إعداداتها بين الجلسات تولد تذاكر دعم خاصة بها. زوج DevMode — GetPrinterDevModeToString و SetPrinterDevModeFromString — يسلسل إعداد driver الكامل للطابعة إلى سلسلة opaque تستطيع تخزينها في تفضيلات المستخدم واستعادتها في الجلسة التالية، بما في ذلك خيارات خاصة بالdriver لا يمثلها أي API عام. خزّن الطابعة بالاسم من GetPrinterNames، لا بالفهرس: ترتيب الفهارس يتغير كلما أضيفت طابعة أو أزيلت، وتغطي GetDefaultPrinterName fallback عندما يختفي الجهاز المتذكر.
اختيار الدرج يكمل قصة الاستمرار: تبلغ GetPrinterBins عن مصادر الورق التي يعرّضها driver، وهذا مهم لسير عمل letterhead حيث تسحب الصفحة الأولى من درج letterhead والباقي من ورق عادي — وهي سياسة يتوقع المستخدمون أن يتذكرها التطبيق مع كل شيء آخر.
أسئلة تظهر في مشاريع الطباعة
لماذا تتحرك الصفحة المطبوعة بالنسبة إلى المعاينة؟
غالبا بسبب هامش العتاد: أصل printer DC هو زاوية المنطقة القابلة للطباعة، لا زاوية الورق. مثل الإزاحة صراحة في المعاينة، أو ولد المعاينات عبر GetPrintPreviewBitmapToString كي تكون هندسة الطابعة مخبوزة فيها.
كيف أطبع اختيار صفحات مثل 2-5 و 12؟
تقبل PrintPages سلسلة نطاق — مرر اسم الطابعة الافتراضية، و '2-5,12'، ومقبض الخيارات. صيغة النطاق نفسها تقود متغيرات print-to-file.
هل يمكن للمعاينة ومهمة الطباعة استخدام محركي تصيير مختلفين؟
يمكن ذلك، لكن لا ينبغي: اختيار المحرك ينطبق على وجهات الشاشة والطابعة معا، وخلط المحركات يعيد drift في الوفاء البصري الذي تلغيه معاينة printer-true. تُوزن المفاضلات بين المحرك المدمج و Cairo و PDFium في التصيير متعدد المحركات لـ PDF في Delphi.
المستندات الأكبر من أن تُحمّل براحة قبل الطباعة يمكن فتحها عبر مسار Direct Access الموضح في دمج وتقسيم PDF كبير و Direct Access، الذي يصيّر الصفحات إلى device context من مقبض ملف من دون بناء شجرة المستند. مرجع API الكامل للطباعة موجود في صفحة منتج losLab PDF Library for Delphi.