Kết xuất một trang PDF lên device context của Windows để xem trước bản in đặt ba hệ tọa độ vào cùng một dòng mã, và chúng hiếm khi đồng thuận. Trang PDF đo bằng point với gốc ở góc dưới bên trái. DC màn hình đo bằng pixel với gốc ở góc trên bên trái cùng một hệ số thu phóng do bạn chọn. DC máy in, tức cái mà bản xem trước phải dự đoán được, đo pixel theo độ phân giải thiết bị nhưng đặt gốc ở góc của vùng in được chứ không phải góc tờ giấy. Sai một trong ba thứ đó thì bản xem trước trông ổn trong khi trang in ra bị lệch, sai tỉ lệ, hoặc cụt mất một cạnh. Triệu chứng thường gặp là một biểu mẫu có khung, xem trước thì cân đối mà in ra lại bị cắt mất đường kẻ trên và trái, bởi máy in laser không thể phun mực vào vài milimet ngoài rìa còn chẳng ai báo cho bản xem trước biết. losLab PDF Library (PDF Library for Delphi) bao trọn con đường này bằng các lệnh gọi kết xuất lên device context, một tầng cấu hình máy in ảo, và ảnh xem trước sinh ra từ chính các số đo của máy in, và đó chính là phần khiến bản xem trước trung thực về cái lề kia
Hình học tờ giấy không phải hình học vùng in được
Hai hình chữ nhật mô tả mọi đích in, và độ lệch giữa chúng là nơi phần lớn lỗi xem trước trú ngụ. Hình chữ nhật giấy là tờ giấy vật lý. Hình chữ nhật in được là vùng nhỏ hơn mà bộ máy in thực sự với tới được, thụt vào bởi một lề phần cứng khác nhau theo từng đời máy in và đôi khi theo từng khay. Tầng in của thư viện đo cả hai. Lớp TPLPrinter bên dưới phơi ra PageWidth cùng PageHeight cho vùng in được, FullPageWidth cùng FullPageHeight cho cả tờ giấy, và PrintOffsetX với PrintOffsetY cho khoảng cách giữa hai gốc, tất cả tính bằng pixel thiết bị ở độ phân giải mà GetDPI báo cáo. Một bản xem trước trung thực thu chính những con số đó xuống độ phân giải màn hình thay vì vẽ trang vào bất kỳ hình chữ nhật nào mà control tình cờ có. Bỏ qua bước ấy thì bản xem trước lặng lẽ giả định lề bằng không, và đó là giá trị duy nhất không máy in thật nào dùng
Xem trước trên màn hình qua RenderPageToDC
Với một control xem trước trên màn hình, RenderPageToDC(DPI, Page, DC) vẽ một trang của tài liệu đã nạp thẳng lên bất kỳ device context GDI nào, dù đó là canvas của TPaintBox, một bitmap ngoài màn hình, hay một DC metafile. Đối số DPI đặt mức thu phóng. 96 xấp xỉ chế độ xem 100% trên một màn hình cổ điển, và nhân đôi nó thì kích thước kết xuất cũng nhân đôi
procedure TPreviewForm.PreviewBoxPaint(Sender: TObject);
begin
// ba thứ này là trạng thái dính dai của thư viện, không phải tham số theo từng lệnh gọi:
FPdf.SetRenderDCOffset(FOffsetX, FOffsetY);
FPdf.SetRenderDCErasePage(1);
FPdf.SetRenderCropType(0);
FPdf.RenderPageToDC(FPreviewDpi, FCurrentPage, PreviewBox.Canvas.Handle);
end;
Cái bẫy là đường kết xuất lên DC được lái bằng trạng thái dính dai của thư viện, chứ không phải bằng tham số của từng lệnh gọi. SetRenderDCOffset, SetRenderDCErasePage và SetRenderCropType đều tồn tại dai cho tới khi có gì đó đổi chúng, nên một vòng lặp tạo ảnh thu nhỏ chạy sau khi người dùng vừa chỉnh chế độ xem phóng to sẽ thừa hưởng đúng cái offset hay kiểu cắt xén mà đường mã trước để lại. Triệu chứng là một bản xem trước chỉ trôi lệch trong vài chuỗi thao tác điều hướng nhất định, và đó gần như là kiểu lỗi khốn khổ nhất để tái hiện. Đặt toàn bộ trạng thái liên quan ở đầu bộ xử lý vẽ, như ở trên, chẳng tốn gì mà xóa sạch cả lớp lỗi. Còn một hệ số nhân thứ hai nấp ngay gần đó. Độ phân giải đầu ra thực tế bằng hệ số tỉ lệ kết xuất nhân với đối số DPI, và tuy SetRenderScale mặc định là 1.0, nó cũng tồn tại dai một khi đã đổi, nên một tính năng xuất tệp từng nâng nó lên sẽ lặng lẽ đổi tỉ lệ mọi bản xem trước sau đó cho tới khi có gì đó đặt nó về
Các trình xem có cuộn và những lần vẽ lại từng phần có một biến thể riêng. RenderPageToDCClip nhận một đặc tả vùng cắt kèm theo device context, nên việc làm mất hiệu lực một dải của cửa sổ chỉ vẽ lại đúng dải đó thay vì raster hóa lại cả trang. Ở mức phóng to lớn trên các trang khổ rộng, đó là khác biệt giữa một trình xem bám kịp thanh cuộn với một trình xem lết theo phía sau
Một lệnh in khớp với bản xem trước
Phía in làm việc qua một máy in ảo. NewCustomPrinter nhân bản một máy in hệ thống thành một cấu hình riêng của thư viện, còn SetupPrinter chỉnh bản nhân bản đó mà không đụng tới DevMode chung của cả máy: khổ giấy vào ở thiết lập 1 (một hằng DMPAPER_*) và hướng giấy ở thiết lập 11. Cái được là sự cô lập. Một dịch vụ có thể in nhãn A4 trong khi máy in mặc định của máy chủ vẫn để ở Letter, và sau đó chẳng cần khôi phục gì
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); // thiết lập 1 = khổ giấy, DMPAPER_A4
Pdf.SetupPrinter(Virt, 11, 1); // thiết lập 11 = hướng giấy, 1 = dọc
Opt := Pdf.PrintOptions(1, 1, 'Monthly Report'); // vừa khổ giấy, tự xoay + căn giữa
Pdf.PrintDocument(Virt, 1, Pdf.PageCount, Opt);
finally
Pdf.Free;
end;
end;
PrintOptions đáng được đọc kỹ. Nó trả về một handle tùy chọn mà bạn buộc phải truyền cho PrintDocument hoặc PrintPages; nó không phải trạng thái ngầm định. Dựng xong bộ tùy chọn rồi quên truyền handle thì hỏng trong im lặng. Lệnh in vẫn chạy với các giá trị mặc định, và chẳng ai để ý cho tới khi người ta chờ một chính sách vừa khổ giấy mà một trang quá khổ lại in ra bị cắt. Đối số về thu phóng trang chính là nơi chính sách ấy trú ngụ. Không thu phóng thì giữ được độ chính xác về kích thước, điều hệ trọng với những biểu mẫu bị đem thước ra đo. Vừa khổ giấy thì đổi tỉ lệ mọi thứ cho khớp tờ giấy. Thu nhỏ trang lớn thì để yên các trang bình thường và chỉ can thiệp khi một trang vượt quá vùng in được, và đó thường là mặc định đúng cho một tập tài liệu pha trộn. Cờ tự xoay và căn giữa lo liệu các trang nằm ngang mà không cần thêm nhánh mã thứ hai
Những ứng dụng vốn đã quản lý một đối tượng TPrinter qua luồng hộp thoại của VCL có thể bàn giao thẳng nó. PrintDocumentToPrinterObject và PrintPagesToPrinterObject nhận instance TPrinter đã cấu hình, nhờ đó hộp thoại in tiêu chuẩn vẫn là bề mặt cấu hình hướng tới người dùng còn thư viện lo phần kết xuất trang. Trộn hai cách tiếp cận trong cùng một đường mã thường làm sống lại đúng cái trôi lệch hình học mà toàn bộ công sức này sinh ra để diệt, nên hãy chọn lấy một. Đường máy in ảo hợp với các dịch vụ chạy không người trông; đường TPrinter hợp với ứng dụng tương tác
Việc in chọn lọc cũng theo cách đó. PrintPages nhận một chuỗi khoảng, nên truyền tên máy in ảo, '2-5,12' và handle tùy chọn sẽ in các trang 2 tới 5 cùng trang 12 với khế ước hình học nguyên vẹn, và cùng cú pháp ấy điều khiển các biến thể in ra tệp. Những biến thể ghi ra tệp đó là lời đáp thực dụng cho môi trường không người trông và không gắn thiết bị vật lý nào: kiểm thử hồi quy hình học in trên một máy chủ build hoàn toàn không có hàng đợi driver. Hãy kết xuất cùng tài liệu qua cùng bộ tùy chọn ra một tạo tác tệp ở mỗi lần build, và một hồi quy về hình học sẽ biến thành một bản khác biệt thay vì một báo cáo của khách hàng ba tuần sau
Ảnh xem trước dựng theo chính số đo của máy in
Một bản xem trước kết xuất ở 96 DPI dựa trên khổ trang phỏng đoán là đang trả lời sai câu hỏi. Nó cho thấy trang trông ra sao, chứ không cho thấy máy in này sẽ đặt gì lên tờ giấy này. GetPrintPreviewBitmapToString khép khoảng trống đó bằng cách dựng bản xem trước từ chính máy in tùy chỉnh và chính handle tùy chọn của lệnh in sau này, nên khổ giấy, hướng giấy, chính sách thu phóng, phép xoay và độ lệch phần cứng đều chảy vào tấm bitmap. Thứ trả về chính là thứ tờ giấy sẽ hiện ra
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;
Đối số MaxDimension chặn trên cạnh dài của tấm bitmap. 1200 pixel vẫn sắc nét cho một hộp thoại xem trước và giữ bộ nhớ ở mức khiêm tốn ngay cả với bản vẽ kỹ thuật khổ E, nơi một lượt kết xuất đủ độ phân giải ở mức 600 DPI của máy in sẽ ngốn tới hàng gigabyte
Ghi nhớ lựa chọn máy in của người dùng
Những hộp thoại in quên sạch thiết lập giữa các phiên tự chúng đẻ ra phiếu hỗ trợ. Cặp hàm DevMode, tức GetPrinterDevModeToString và SetPrinterDevModeFromString, tuần tự hóa toàn bộ cấu hình driver của một máy in thành một chuỗi mờ đục mà bạn có thể cất trong tùy chọn người dùng rồi khôi phục ở phiên sau, gồm cả những tùy chọn riêng của driver mà chẳng API tổng quát nào buồn mô hình hóa. Hãy lưu máy in theo tên lấy từ GetPrinterNames, đừng bao giờ theo chỉ số trong danh sách. Thứ tự chỉ số đổi mỗi khi thêm hay bớt một máy in, nên một chỉ số đã lưu sẽ lặng lẽ trỏ vào sai thiết bị ở lần danh sách xê dịch tiếp theo. GetDefaultPrinterName lo phần dự phòng khi thiết bị được ghi nhớ đã biến mất hoàn toàn
Việc chọn khay giấy khép lại câu chuyện lưu bền. GetPrinterBins báo cáo các nguồn giấy mà driver phơi ra, điều này hệ trọng với những quy trình dùng giấy tiêu đề, nơi trang một rút từ khay giấy tiêu đề còn phần còn lại rút từ giấy trơn. Đó là chính sách mà người dùng mong ứng dụng ghi nhớ cùng mọi thứ khác, và một lệnh in đáp xuống sai loại giấy bị đọc như một lỗi ngay cả khi từng byte của tệp PDF đều đúng
Giữ một engine duy nhất cho cả xem trước lẫn in
Một quyết định cuối cùng lặng lẽ chi phối độ trung thực. Việc chọn engine kết xuất áp cho cả đích màn hình lẫn đích máy in, nên có cám dỗ là xem trước bằng engine nhanh còn in bằng engine chính xác. Hãy cưỡng lại. Lái bản xem trước và lệnh in qua hai engine khác nhau làm sống lại đúng cái trôi lệch độ trung thực mà một bản xem trước đúng như máy in sinh ra để loại bỏ, và nó làm vậy theo kiểu chỉ lộ ra trên giấy. Những đánh đổi giữa engine dựng sẵn, Cairo và PDFium được cân nhắc trong kết xuất PDF đa engine trong Delphi; hãy chọn một và dùng nó ở cả hai phía
Những tài liệu quá lớn để nạp thoải mái trước khi in có thể mở qua đường direct-access mô tả trong gộp, tách PDF cỡ lớn và direct access, cách này kết xuất trang lên device context từ một handle tệp mà không dựng cây tài liệu. Tài liệu API in đầy đủ nằm trên trang sản phẩm losLab PDF Library for Delphi