Ba bộ rasterizer có thể đọc cùng một tệp PDF rồi bất đồng về nội dung nó nói gì. Engine dựng sẵn trong PDF Library for Delphi là bộ chạy được mà không cần tệp phụ nào và kết xuất mọi thứ ở mức thành thạo, và đó là lý do nó giành được suất mặc định. Cairo mang tới một đường ống xử lý độ trong suốt và khử răng cưa khác, và thường là bộ người ta với tay tới khi soft mask hay blend mode ra sai ở nơi khác. PDFium mang theo mã kết xuất của Chrome, nên một trang trông đúng trong trình duyệt thì thường cũng trông đúng dưới PDFium, đổi lại là một DLL khá đồ sộ và một độ rộng bit mà nó nhất quyết đòi khớp. Không bộ nào trong ba bộ là đúng một cách trừu tượng. Sự đúng đắn xét theo từng tài liệu, và cách trung thực duy nhất để biết engine nào xử được một kho tài liệu cụ thể là cho kho đó chạy qua từng bộ
Đó là lý lẽ cho việc coi engine là lựa chọn lúc chạy chứ không phải lúc biên dịch. PDF Library for Delphi, thư viện PDF cho Delphi và C++Builder của losLab, đặt cả ba sau một bề mặt kết xuất duy nhất để quyết định ấy chỉ tốn một con số nguyên thay vì một nhánh mã. Phần còn lại quy về việc chọn giữa chúng một cách an toàn, xác nhận bản nhị phân đã triển khai thật sự mang theo những engine nào, và không để trạng thái kết xuất lặng lẽ đầu độc công việc kế tiếp
Ba bộ rasterizer sau một bề mặt gọi hàm
Thư viện đánh số các engine của mình. Engine 1 là bộ kết xuất dựng sẵn, tức bản mặc định, có các tùy chọn làm mượt GDI+ trên Windows. Engine 2 là Cairo và engine 3 là PDFium, cả hai được chọn lúc chạy qua SelectRenderer. Hai engine ngoài nạp từ những DLL mà bạn cung cấp đường dẫn bằng SetCairoFileName và SetPDFiumFileName trước khi chọn chúng. Dù engine nào đang hoạt động, công việc vẫn đi qua đúng những lệnh gọi đó: RenderPageToFile, RenderPageToStream, RenderDocumentToFile. Đổi engine chỉ dịch một con số; phần còn lại trong mã kết xuất của bạn chẳng hay biết gì
Mô hình đích đến vươn xa hơn ảnh bitmap nhiều. Lớp kết xuất còn nhắm tới metafile (WMF, EMF, EMF+), EPS, device context trực tiếp, máy in và HTML5, còn Cairo với PDFium chỉ hiện ra như những đích bổ sung khi chúng được biên dịch vào. Đầu ra dạng raster là nơi ba engine phân kỳ rõ nhất, nên đó là thứ các ví dụ ở đây dùng tới
Đừng bao giờ giả định một engine có mặt: hãy dò lúc khởi động
Cairo và PDFium là tính năng biên dịch có điều kiện, nghĩa là một bản nhị phân có thể được dựng hoàn toàn không có chúng. Khi điều đó xảy ra, việc đòi engine 2 hay 3 chẳng gây ra lỗi nào. SelectRenderer chỉ trả về một giá trị khác với ID bạn yêu cầu, và đoạn mã phớt lờ giá trị trả về sẽ tiếp tục kết xuất bằng engine vốn đang hoạt động. Lá chắn là một lượt dò lúc khởi động, yêu cầu từng engine tự khai danh và ghi lại câu trả lời:
function ProbeEngines(PDF: TPDFlib): string;
begin
Result := 'built-in'; // engine 1 luôn có mặt
if (PDF.SetCairoFileName('cairo.dll') = 1) and (PDF.SelectRenderer(2) = 2) then
Result := Result + ', cairo';
if (PDF.SetPDFiumFileName('pdfium.dll') = 1) and (PDF.SelectRenderer(3) = 3) then
Result := Result + ', pdfium';
PDF.SelectRenderer(1); // trả về mặc định trước khi làm việc thật
end;
Hãy chạy lượt dò đó một lần lúc khởi động và ghi kết quả vào nhật ký cạnh mọi công việc kết xuất. Câu hỏi phổ biến nhất khi khách hàng báo có khác biệt về kết xuất chính là bản cài của họ thực sự có những engine nào, và một dòng trả lời nằm sẵn trong nhật ký giải quyết chuyện đó mà không cần phiên remote desktop. Một tác dụng phụ hữu ích: nếu chính SetPDFiumFileName trả về 0, bạn đã biết ngay vấn đề nằm ở DLL (sai đường dẫn, sai độ rộng bit, thiếu phụ thuộc) chứ không phải một bản nhị phân biên dịch thiếu hỗ trợ PDFium, vì lệnh gọi đường dẫn chẳng phân giải được gì trước cả khi SelectRenderer kịp chạy
Mười định dạng đầu ra sau một số nguyên Options
Tham số Options trên các lệnh gọi kết xuất chọn cách mã hóa đầu ra: 0 là BMP, 1 là JPEG, 2 là WMF, 3 là EMF, 4 là EPS, 5 là PNG, 6 là GIF, 7 là TIFF, 8 là EMF+ và 9 là HTML5. PNG (5) là mặc định hợp lý cho ảnh xem trước và ảnh trang lưu trữ. JPEG (1), đi kèm SetJPEGQuality, là lựa chọn tốt hơn cho ảnh quét kiểu nhiếp ảnh nơi kích thước tệp quan trọng hơn độ sắc của cạnh
Một định dạng giấu trong nó một đòi hỏi về stream đích. Đường ghi BMP viết dữ liệu ảnh trước, rồi tua ngược về offset 0x26 để vá các trường độ phân giải trong phần header. Chĩa nó vào một stream chỉ tiến, chẳng hạn lớp bọc nén hay một socket mạng, thì lệnh gọi hỏng theo kiểu đọc lên như lỗi engine mà thật ra không phải. Khi không tránh được một đích không tua được, hãy kết xuất PNG thay thế, hoặc dựng tạm BMP qua một memory stream rồi chép tiếp khi nó đã hoàn chỉnh
DPI bạn truyền vào không phải DPI bạn nhận được
Mọi lệnh gọi kết xuất đều nhận một đối số DPI, nhưng độ phân giải bạn thực sự nhận được là giá trị đó nhân với hệ số tỉ lệ kết xuất toàn cục. SetRenderScale khởi đầu ở 1.0, và một khi bạn đổi nó thì hệ số mới lặng lẽ áp cho mọi lượt kết xuất sau đó trên instance ấy:
PDF.SetRenderScale(2.0); // mọi lượt kết xuất sau đều nhân đôi
PDF.RenderPageToFile(150, 1, 5, 'p1.png'); // trên thực tế là 300 DPI
PDF.SetRenderScale(1.0); // đặt lại, kẻo ảnh thu nhỏ ra to đùng
Tính dính dai đó cũng áp cho SetRenderCropType và thiết lập chất lượng JPEG. Trong một dịch vụ vừa tạo ảnh thu nhỏ, ảnh xem trước lẫn ảnh độ phân giải in từ cùng một instance dùng chung, chính những thiết lập sót lại này mới là thủ phạm thật sự đằng sau cái phiếu hỗ trợ thi thoảng lại xuất hiện kiểu "ảnh thu nhỏ tự dưng nặng 40 MB". Có hai lối thoát sạch sẽ: đặt lại trạng thái liên quan ở đầu mỗi thao tác, hoặc dành riêng một instance cho từng hồ sơ đầu ra để không có gì rò rỉ qua lại
Tinh chỉnh engine mặc định trước khi với tay sang bộ khác
Một tỉ lệ đáng ngạc nhiên các yêu cầu "chúng tôi cần engine khác" hóa ra là vấn đề thiết lập đội lốt. Bộ kết xuất dựng sẵn phơi hành vi làm mượt của nó qua SetGDIPlusOptions cùng họ hàm rộng hơn là SetRenderOptions, còn SetGDIPlusFileName cho phép bạn chĩa nó vào một runtime GDI+ cụ thể khi môi trường triển khai đi kèm một bản khác thường. Nét vẽ răng cưa ở DPI thấp, chữ nhòe trong ảnh thu nhỏ, sọc dải trên các vùng chuyển màu: tất cả đều đáp lại những núm vặn đó, và vặn chúng chẳng tốn gì trong bộ cài. Ngược lại, thêm Cairo hay PDFium nghĩa là phải phát hành thêm DLL, theo dõi biến thể độ rộng bit thứ hai hoặc thứ ba, và gánh nghĩa vụ cập nhật chúng
Vậy nên một khiếu nại về chất lượng có trình tự xử lý tự nhiên. Trước hết hãy tái hiện nó ở đúng DPI và hệ số tỉ lệ của khách hàng, vì một nửa số lần khác biệt bốc hơi ngay khi hai thứ đó khớp nhau. Kế đến hãy thử các tùy chọn làm mượt của engine dựng sẵn. Chỉ sau đó mới đặt trang đó cạnh nhau qua các engine với mọi biến số khác giữ nguyên: kết xuất nó ra PNG qua engine 1, 2 và 3 ở cùng DPI rồi đính kèm cả ba. Thường thì hai trong ba đồng thuận, và đa số ấy cho bạn biết cái lệch chuẩn là do tài liệu bị diễn giải khác đi hay do chính kỳ vọng nền của bạn lệch. Ba tấm ảnh cụ thể dàn xếp một tranh cãi "kết xuất sai" nhanh hơn hẳn một đoạn văn đầy tính từ
Một chuỗi dự phòng tự giải thích chính mình
Một khi việc dò và kỷ luật trạng thái đã vào nếp, bản thân chuỗi dự phòng rất ngắn. Việc phát hiện thất bại dựa vào LastRenderError, thuộc tính giữ chính văn bản thông báo của engine cho lượt kết xuất gần nhất và rỗng khi lượt kết xuất thành công:
procedure RenderPageWithFallback(PDF: TPDFlib; Page: Integer; const OutFile: string);
begin
PDF.SelectRenderer(1); // bộ dựng sẵn đi trước
PDF.RenderPageToFile(200, Page, 5, OutFile); // 5 = PNG
if PDF.LastRenderError = '' then Exit;
LogEngineFailure('built-in', Page, PDF.LastRenderError);
if PDF.SelectRenderer(3) = 3 then // PDFium làm phương án dự phòng nặng ký
begin
PDF.RenderPageToFile(200, Page, 5, OutFile);
if PDF.LastRenderError = '' then Exit;
LogEngineFailure('pdfium', Page, PDF.LastRenderError);
end;
raise Exception.CreateFmt('Page %d failed on all available engines', [Page]);
end;
Hai điểm thiết kế ở đây có sức nặng. Chuỗi này ghi lại lý do của từng lần chuyển engine, bởi một dòng nhật ký viết "trang này rơi xuống PDFium kể từ bản 3.7" là tín hiệu hồi quy mà bạn muốn thấy nó lên xu hướng trong hệ giám sát chứ không phải bị đánh mất. Bản thân thứ tự dự phòng là một chính sách đáng chọn theo từng loại tải công việc. Engine dựng sẵn triển khai mà không cần DLL phụ nào, điều đó khiến nó là lần thử đầu tiên đúng đắn ở hầu hết bản cài, còn những tài liệu nặng nhóm trong suốt hoặc tô bóng khác thường mới là lý do thường thấy khiến một đội đấu nối engine thay thế vào ngay từ đầu. Không engine nào nhanh nhất một cách tổng quát, và đó chính là toàn bộ ý nghĩa của việc chọn theo từng lệnh gọi: hãy đo hiệu năng từng bộ trên một mẫu tài liệu thật của bạn ở DPI thật của bạn, và đo lại mỗi khi các DLL engine hoặc thành phần tài liệu thay đổi. Kho tài liệu luôn thắng cuộc tranh luận
Vượt khỏi từng trang lẻ: lô TIFF và device context trực tiếp
Hai hàng xóm của các lệnh gọi theo trang khép lại bộ đồ nghề. RenderAsMultipageTIFFToFile kết xuất một biểu thức khoảng trang thẳng vào một tệp TIFF nhiều trang, hình hài tự nhiên khi bàn giao dữ liệu lưu trữ cho các hệ quản lý tài liệu có tuổi đời trước cả PDF. RenderPageToDC vẽ thẳng lên một device context của Windows cho các control xem trước, chịu sự chi phối của bộ ba thiết lập dính dai riêng (SetRenderDCOffset, SetRenderDCErasePage, cộng với kiểu cắt xén) vốn cần đúng kỷ luật đặt lại như hệ số tỉ lệ. Kết xuất xem trước trên màn hình và theo đường in mang đủ cạm bẫy riêng để xứng một bài viết chuyên biệt, liên kết ở dưới
Đi tiếp từ đâu
Một thói quen đáng mang theo: vì SelectRenderer có hiệu lực cho mọi lệnh gọi sau đó trên instance, một trang cứng đầu đơn lẻ có thể được thử lại trên engine khác trong khi phần còn lại của tài liệu vẫn ở engine mặc định. Về việc vẽ ảnh xem trước, chọn máy in và xử lý DevMode, hãy đọc tiếp bài viết về xem trước bản in và device context. Khi các lượt kết xuất nuôi một pipeline khối lượng lớn trên những tệp rất to, cách tiếp cận dựa trên handle trong hướng dẫn direct-access ghép rất tự nhiên với việc kết xuất từng trang qua DARenderPageToFile
Cách đóng gói engine, các định dạng được hỗ trợ và bản dựng dùng thử được trình bày chi tiết trên trang sản phẩm PDF Library for Delphi