render کردن یک page از PDF روی یک device context در Windows برای print preview سه سیستم مختصات را در همان خط code قرار میدهد، و آنها بهندرت موافق هستند. page در PDF بر حسب point با origin در bottom-left سنجیده میشود. screen DC بر حسب pixel با origin در top-left و یک zoom factor که انتخاب میکنید سنجیده میشود. printer DC، آنی که preview قرار است آن را پیشبینی کند، pixelها را در resolution دستگاه سنجیده اما origin خود را در گوشهٔ printable area قرار میدهد، نه گوشهٔ sheet. هر یک از آنها را اشتباه بگیرید و preview خوب بهنظر میرسد در حالی که page چاپشده shift، scale یا در امتداد یک لبه clip شده بیرون میآید. symptom معمول یک form با border است که centered پیشنمایش میشود و با ruleهای top و left بریدهشده چاپ میشود، چون laser printer نمیتواند در چند میلیمتر بیرونی ink بگذارد و هیچکس به preview نگفته بود. losLab PDF Library (PDF Library for Delphi) کل path را با callهای rendering در device-context، یک لایهٔ پیکربندی virtual-printer، و preview bitmapهای تولیدشده از metricهای خود printer پوشش میدهد، که قسمتی است که preview را دربارهٔ آن margin صادق میکند
geometry کاغذ geometry قابلچاپ نیست
دو مستطیل هر print target را توصیف میکنند، و offset بین آنها جایی است که بیشتر bugهای preview زندگی میکنند. مستطیل paper، sheet فیزیکی است. مستطیل printable، region کوچکتری است که print engine واقعاً میتواند به آن برسد، inset شده توسط یک hardware margin که per printer model و گاهی per tray تفاوت دارد. لایهٔ printing کتابخانه هر دو را میسنجد. class زیرین TPLPrinter برای printable area PageWidth و PageHeight را expose میکند، برای full sheet FullPageWidth و FullPageHeight را، و برای فاصلهٔ بین originهای آنها PrintOffsetX با PrintOffsetY را، همگی در device pixelها در resolutionی که GetDPI گزارش میدهد. یک preview صادق همان numberها را بهجای paint کردن page در هر مستطیلی که control اتفاقاً دارد به screen resolution scale میکند. آن step را skip کنید و preview بیصدا یک margin صفر فرض میکند، که تنها مقداری است که هیچ printer واقعای از آن استفاده نمیکند
preview روی screen از طریق RenderPageToDC
برای یک control preview روی screen، RenderPageToDC(DPI, Page, DC) یک page از document loaded را مستقیماً روی هر GDI device contextای draw میکند، چه آن یک canvas TPaintBox باشد، یک bitmap off-screen، یا یک metafile DC. argument DPI، zoom را set میکند. ۹۶ یک view ۱۰۰٪ روی یک display کلاسیک را تقریب میزند، و دو برابر کردن آن، اندازهٔ renderشده را دو برابر میکند
procedure TPreviewForm.PreviewBoxPaint(Sender: TObject);
begin
// این سه وضعیت چسبندهی library هستند، نه پارامترهای هر فراخوانی:
FPdf.SetRenderDCOffset(FOffsetX, FOffsetY);
FPdf.SetRenderDCErasePage(1);
FPdf.SetRenderCropType(0);
FPdf.RenderPageToDC(FPreviewDpi, FCurrentPage, PreviewBox.Canvas.Handle);
end;
تله این است که DC render path توسط library state چسبناک steer میشود، نه توسط parameterهای per-call. SetRenderDCOffset، SetRenderDCErasePage و SetRenderCropType هر کدام تا وقتی چیزی آنها را تغییر دهد باقی میمانند، پس یک thumbnail loop که بعد از تنظیمکردن zoom view توسط کاربر اجرا میشود، هر offset یا cropای که code path قبلی بهجا گذاشته را به ارث میبرد. symptom یک preview است که فقط در sequenceهای navigation خاص drift میکند، که تقریباً بهاندازهٔ هر باگی که بگیرد بدم میآید. set کردن کل state مربوط در بالای paint handler، همانطور که بالا، هیچ هزینهای ندارد و کل آن دسته را حذف میکند. یک multiplier دوم نزدیک پنهان است. output resolution مؤثر render scale ضرب در argument DPI است، و در حالی که SetRenderScale بهطور پیشفرض ۱.۰ است، یکبار تغییر کرد باقی میماند، پس یک feature export که آن را bump کرد بیصدا هر preview بعدی را تا وقتی چیزی آن را برگرداند re-scale میکند
viewerهای scrolling و partial repaint یک variant اختصاصی دارند. RenderPageToDCClip یک clip specification را همراه با device context میگیرد، پس invalidate کردن یک band از window فقط همان band را repaint میکند بهجای re-rasterize کردن کل page. در zoom بالا روی pageهای large-format، این تفاوت بین viewerای است که scrollbar را track میکند و یکی که پشت آن smear میشود
یک print job که با preview match است
سمت printing از طریق یک virtual printer کار میکند. NewCustomPrinter یک system printer را در یک پیکربندی private-bookخانهای clone میکند، و SetupPrinter آن clone را بدون touch کردن DevMode در سطح machine تنظیم میکند: paper بهعنوان setting ۱ (یک constant DMPAPER_*) و orientation بهعنوان setting ۱۱ میرود. payoff isolation است. یک service میتواند labelهای A4 چاپ کند در حالی که printer پیشفرض host روی Letter میماند، و هیچچیز بعداً نیاز به restore ندارد
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'); // جایگیری روی کاغذ، auto-rotate + center
Pdf.PrintDocument(Virt, 1, Pdf.PageCount, Opt);
finally
Pdf.Free;
end;
end;
PrintOptions ارزش یک خواندن دقیق دارد. یک options handle برمیگرداند که باید به PrintDocument یا PrintPages پاس بدهید؛ این ambient state نیست. ساختن optionها و سپس فراموشکردن پاسکردن handle بیصدا fail میشود. job با defaultها چاپ میشود، و هیچکس متوجه نمیشود تا وقتی یک policy fit-to-paper انتظار میرفت و یک page بیشازحد بزرگ بهجای آن crop بیرون آمد. argument page-scaling جایی است که آن policy زندگی میکند. بدون scaling، دقت ابعادی را حفظ میکند، که برای formهایی که در برابر یک خطکش سنجیده میشوند مهم است. fit-to-paper همهچیز را به sheet re-scale میکند. shrink-large-pages، pageهای معمولی را دستنخورده میگذارد و فقط وقتی یک page از printable area فراتر میرود وارد میشود، که معمولاً default درست برای یک set document مختلط است. flag auto-rotate-and-center، pageهای landscape را بدون یک code path دوم handle میکند
applicationهایی که از قبل یک TPrinter را از طریق flow dialog در VCL مدیریت میکنند میتوانند آن را مستقیماً تحویل دهند. PrintDocumentToPrinterObject و PrintPagesToPrinterObject نمونهٔ TPrinter پیکربندیشده را میپذیرند، که dialog print استاندارد را بهعنوان surface پیکربندی روبهکاربر نگه میدارد در حالی که کتابخانه rendering page را handle میکند. مخلوط کردن دو رویکرد در یک code path تمایل دارد geometry driftای را که بقیهٔ این کار قرار بود بکشد، دوباره معرفی کند، پس یکی را انتخاب کنید. مسیر virtual-printer برای serviceهای بدوننظارت مناسب است؛ مسیر TPrinter برای applicationهای تعاملی
output انتخابی به همان شکل کار میکند. PrintPages یک range string میگیرد، پس پاسکردن virtual printer name، '2-5,12' و options handle، pageهای ۲ تا ۵ و ۱۲ را با geometry contract دستنخورده چاپ میکند، و همان syntax variantهای print-to-file را drive میکند. آن variantهای file، پاسخ عملی برای یک محیط بدوننظارت بدون هیچ دستگاه فیزیکی متصل هستند: regression-testing کردن print geometry روی یک build server که اصلاً هیچ driver queueای ندارد. همان document را از طریق همان optionها در هر build به یک file artifact render کنید، و یک regression geometry به یک diff بهجای یک گزارش مشتری سه هفته بعد تبدیل میشود
preview bitmap با metricهای خود printer
یک preview renderشده در ۹۶ DPI در برابر یک اندازهٔ page فرضی، سوال اشتباهی را پاسخ میدهد. نشان میدهد page چه شبیه است، نه اینکه این printer چه روی این کاغذ میگذارد. GetPrintPreviewBitmapToString آن فاصله را با ساختن preview از همان custom printer و همان options handle بهعنوان job نهایی میبندد، تا paper size، orientation، scaling policy، rotation و offset سختافزاری همگی در bitmap تغذیه شوند. آنچه برمیگردد آن چیزی است که sheet نشان خواهد داد
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;
argument MaxDimension، long edge bitmap را cap میکند. ۱۲۰۰ pixel برای یک dialog preview sharp میماند و memory را حتی برای engineering drawingهای E-size متعادل نگه میدارد، که در آن یک render با full-resolution در ۶۰۰ DPI printer به gigabyte میرسد
بهخاطر سپردن انتخابهای printer کاربر
dialogهای print که تنظیمات خود را بین sessionها فراموش میکنند، ticket پشتیبانی خودشان را تولید میکنند. جفت DevMode، GetPrinterDevModeToString و SetPrinterDevModeFromString، یک پیکربندی driver کامل printer را به یک string opaque serialize میکند که میتوانید در user preferenceها stash کنید و session بعد restore کنید، شامل optionهای driver-specific که هیچ API عامی modelingشان را به زحمت نمیاندازد. printer را بر اساس name از GetPrinterNames persist کنید، هرگز بر اساس list index. ترتیب index هر بار که یک printer افزوده یا حذف میشود تغییر میکند، پس یک index ذخیرهشده دفعهٔ بعد که list shift میکند بیصدا به دستگاه اشتباه اشاره میکند. GetDefaultPrinterName fallback را وقتی دستگاه بهخاطر سپردهشده کاملاً ناپدید شده پوشش میدهد
selection tray، story persistence را کامل میکند. GetPrinterBins منابع کاغذی که یک driver expose میکند را گزارش میدهد، که برای workflowهای letterhead مهم است که در آن page یک از tray letterhead میکشد و بقیه از plain stock. آن policyای است که کاربران انتظار دارند application همراه با همهچیز دیگر بهخاطر بسپارد، و یک print job که روی stock اشتباه land میکند حتی وقتی هر byte از PDF درست بود، بهعنوان یک bug خوانده میشود
یک engine را بین preview و print نگه دارید
یک تصمیم آخر بیصدا بر fidelity حاکم است. selection rendering engine هم به مقاصد screen و هم printer اعمال میشود، پس وسوسه این است که با یک engine سریع preview و با یکی دقیق print کنید. در برابرش مقاومت کنید. drive کردن preview و job از طریق engineهای متفاوت، دقیقاً همان fidelity driftای را که یک printer-true preview برای حذف ساخته شده بود دوباره معرفی میکند، و به شکلی که فقط روی کاغذ ظاهر میشود. trade-off بین engineهای built-in، Cairo و PDFium در rendering چند-engineی PDF در Delphi سنجیده شده؛ یکی را انتخاب کنید و در هر دو طرف از آن استفاده کنید
documentهایی که برای load کردن قبل از چاپ بیشازحد بزرگ هستند میتوانند از طریق مسیر direct-access توصیفشده در merge، split و direct access بزرگ PDF باز شوند، که pageها را به یک device context از یک file handle بدون ساختن document tree render میکند. مرجع کامل API چاپ در page محصول losLab PDF Library برای Delphi است