مقاله فنی

print preview و output در device-context با PDF Library for Delphi

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 واقعای از آن استفاده نمی‌کند

نمودار PDF Library for Delphi از برگه کامل کاغذ در برابر مستطیل چاپ کوچک‌تر، با PrintOffsetX و PrintOffsetY که حاشیه سخت‌افزاری میان مبدأهایشان را نشان می‌دهند
مستطیل کاغذ، برگ فیزیکی است در حالی که مستطیل قابل چاپ چیزی است که موتور چاپ به آن می‌رسد، و شکاف میان مبدأهای آن‌ها جایی است که بیشتر باگ‌های پیش‌نمایش زندگی می‌کنند

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 ندارد

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);        // 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 نشان خواهد داد

PDF Library for Delphi: تقابل پیش‌نمایش صفحه‌نمایش با اندازه صفحه فرضی که حاشیه‌ها را غلط نشان می‌دهد با بیت‌مپ مطابق چاپگر که از handle چاپگر و گزینه‌های خود کار ساخته می‌شود
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;

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 است