Bài viết kỹ thuật

Kết xuất trang PDF thành Bitmap trong Delphi với HotPDF

HotPDF kết xuất một trang PDF đã tải thành đối tượng TBitmap của Delphi thông qua một lệnh gọi duy nhất: RenderLoadedPageToBitmap(PageIndex, DPI). Hàm này giải nghĩa luồng nội dung của trang và trả về một bitmap RGB 24-bit thuộc quyền sở hữu của người gọi ở độ phân giải do bạn lựa chọn, đây chính xác là những gì một thanh hiển thị ảnh thu nhỏ (thumbnail strip), chế độ xem trước bản in, hoặc luồng xuất tệp PDF sang hình ảnh cần có. Bài viết này sẽ đi qua API này, sau đó tìm hiểu thành phần phân chia ranh giới giữa một bộ kết xuất thực tế và một công cụ đồ chơi: vẽ văn bản từ chính các chương trình phông chữ được nhúng thay vì sử dụng các phông chữ hệ thống tương tự

Tại sao kết xuất một trang PDF lại khó hơn việc vẽ một hình ảnh?

Một trang PDF không phải là một bức tranh. Nó là một chương trình: một chuỗi các toán tử xây dựng đường dẫn, chọn phông chữ, thiết lập màu sắc và đặt các ký tự đồ họa, được thực thi trên mô hình đồ họa xác định trong ISO 32000-1 §8. Không có thông tin nào trong tệp tin cho biết hình dáng cụ thể của từng pixel. Để tạo ra một bitmap, bạn phải chạy chương trình đó — duy trì ma trận biến đổi hiện tại, ngăn xếp trạng thái đồ họa cho các lệnh q/Q, đường dẫn cắt (clipping path), không gian màu tô và nét vẽ — và số hóa (rasterize) kết quả. Đó là lý do tại sao yêu cầu "chỉ hiển thị trang 3 dưới dạng hình ảnh" thực chất là một trình thông dịch luồng nội dung, chứ không phải là việc chuyển đổi định dạng tệp đơn thuần

Bộ kết xuất của HotPDF, được giới thiệu trong phiên bản v2.253.0, được xây dựng dưới dạng sáu đơn vị độc lập phản ánh mô hình đó: lõi ma trận affine cho đại số biến đổi PDF [a b c d e f], ngăn xếp trạng thái đồ họa, bộ phân giải không gian màu (DeviceRGB, DeviceGray, DeviceCMYK, Indexed), bộ dựng đường dẫn kết nối các toán tử đường dẫn PDF với GDI, lớp đo lường phông chữ đọc các mảng /Widths để xác định khoảng tăng chính xác, và trình thông dịch điều phối toán tử và điều khiển năm đơn vị còn lại. Các đối tượng hình ảnh XObject đi qua cùng một ngăn xếp giải mã mà thư viện sử dụng cho việc trích xuất, do đó mọi bộ lọc hình ảnh mà HotPDF có thể giải mã để trích xuất — bao gồm cả hình ảnh JPEG 2000 nén bằng JPXDecode — cũng xuất hiện trong kết quả kết xuất

Kết xuất một trang đã tải thành TBitmap

Hàm RenderLoadedPageToBitmap nhận chỉ số trang tính từ 0 và giá trị DPI, trong đó 72 DPI ánh xạ một đơn vị không gian người dùng PDF thành một pixel. Hàm trả về giá trị nil khi thất bại (chỉ số trang ngoài phạm vi, thiếu tài nguyên) thay vì đưa ra cảnh báo lỗi, để trình xem có thể bỏ qua trang bị lỗi và tiếp tục. Người gọi sở hữu bitmap được trả về và phải tự giải phóng nó

var
  Pdf: THotPDF;
  Bmp: TBitmap;
begin
  Pdf := THotPDF.Create(nil);
  try
    if Pdf.LoadFromFile('report.pdf') > 0 then
    begin
      Bmp := Pdf.RenderLoadedPageToBitmap(0, 144);  // page 1 at 144 DPI
      if Bmp <> nil then
      try
        Image1.Picture.Assign(Bmp);
      finally
        Bmp.Free;  // caller owns the bitmap
      end;
    end;
  finally
    Pdf.Free;
  end;
end;

Đối số DPI thực hiện công việc thay đổi tỷ lệ cho mọi tình huống phổ biến. Thanh hiển thị ảnh thu nhỏ kết xuất ở 36 hoặc 48 DPI để có các bitmap nhỏ, tải nhanh; xem trước trên màn hình ở 96 hoặc 144 DPI tương thích với mật độ hiển thị thông thường; quy trình xuất ảnh ở 300 DPI tạo ra các hình ảnh chất lượng in ấn. Góc xoay của trang từ mục /Rotate và việc lật gốc tọa độ /MediaBox (PDF đặt gốc ở góc dưới bên trái, GDI ở góc trên bên trái) được xử lý bên trong ma trận trang-sang-thiết-bị, vì vậy một trang kích cỡ US Letter ở 72 DPI trả về kết quả chính xác là 612×792 pixel theo đúng chiều

Tại sao ảnh thu nhỏ kết xuất PDF hiển thị sai ký tự đồ họa?

Việc hiển thị sai hoặc gần đúng các ký tự đồ họa trong kết quả kết xuất PDF hầu như luôn đồng nghĩa với việc bộ kết xuất đang sử dụng phông chữ hệ thống thay thế thay vì sử dụng phông chữ được nhúng trong tệp tin. Bộ kết xuất HotPDF đầu tiên đã làm chính xác điều đó: nó loại bỏ tiền tố rút gọn khỏi /BaseFont (chuyển ABCDEF+Arial thành Arial), yêu cầu GDI cung cấp phông chữ hệ thống cùng tên, và vẽ văn bản bằng phông chữ đó. Đối với một tài liệu sử dụng Arial hoặc Times New Roman với bảng mã tiêu chuẩn, kết quả trông có vẻ gần giống. Nhưng đó chỉ là một sự xấp xỉ, và nó sẽ bị lỗi trong một số trường hợp cụ thể

Các phông chữ rút gọn được nhúng là trường hợp phức tạp nhất. Một phông chữ rút gọn có thể chỉ mang theo bốn mươi ký tự đồ họa mà tài liệu thực sự sử dụng, với các mã ký tự được gán theo một thứ tự riêng biệt của tệp tin đó — ví dụ mã 1 có thể là "T", mã 2 là "h", v.v. Một phông chữ hệ thống hoàn toàn không biết gì về cách gán riêng biệt này, dẫn đến văn bản bị biến mất hoặc hiển thị hoàn toàn sai ký tự. Các bảng mã tùy chỉnh, phông chữ ký hiệu, phông chữ mã vạch và bất kỳ kiểu phông chữ nào không được cài đặt trên máy kết xuất cũng sẽ bị lỗi tương tự. Một bộ kết xuất chỉ dừng lại ở việc thay thế phông chữ hệ thống sẽ tạo ra các ảnh thu nhỏ nhìn qua thì giống trang tài liệu — cho đến khi trang đó sử dụng các phông chữ đặc thù mà việc nhúng phông chữ là bắt buộc

Kết xuất ký tự đồ họa nhúng: vẽ trực tiếp từ chương trình phông chữ

HotPDF đã giải quyết thiếu sót đó qua năm phiên bản phát hành (từ v2.268.0 đến v2.272.0) bằng cách phân tích các chương trình phông chữ được nhúng và vẽ lại các đường nét ký tự của chúng dưới dạng các đường dẫn vector GDI được tô màu. Văn bản trong một trang kết xuất hiện tại được lấy từ cùng một dữ liệu đường nét mà một trình xem chuẩn sử dụng, điều đó có nghĩa là các phông chữ rút gọn, bảng mã tùy chỉnh và các phông chữ chưa cài đặt trên máy đều được kết xuất với hình dáng chính xác của chúng. Phạm vi hỗ trợ được xây dựng theo từng loại phông chữ:

Đối với các phông chữ Type0/CIDFontType2 có chương trình TrueType nhúng kèm (FontFile2), bộ kết xuất phân tích trực tiếp các bảng glyfloca: các đường cong bậc hai được chuyển đổi thành đường cong Bézier bậc ba mà GDI hiểu được, các điểm nằm trên đường cong (on-curve points) ẩn giữa các điểm nằm ngoài đường cong (off-curve points) liên tiếp được xây dựng lại, và các ký tự hỗn hợp được vẽ lại một cách đệ quy. Cả hai bố cục Identity và luồng CIDToGIDMap rõ ràng đều được hỗ trợ, và các khoảng tiến CID tuân thủ các mục độ rộng /W/DW, giúp văn bản Identity-H hai byte hiển thị chính xác

Các chương trình CFF (FontFile3, bất kể là CIDFontType0C, Type1C, hay một trình bao OpenType) đều được trang bị một trình thông dịch Type 2 charstring đầy đủ: các đường thẳng, đường cong, họ flex, mặt nạ gợi ý (hint masks), và các lệnh gọi chương trình con cục bộ/toàn cục với độ lệch chương trình con chính xác. Các chương trình CFF khóa CID ánh xạ các mã ký tự thông qua tập ký tự của phông chữ, điều này rất quan trọng đối với các phông chữ rút gọn có thứ tự ký tự đồ họa khác với thứ tự CID, và việc chọn font-DICT theo từng ký tự qua FDArray/FDSelect được tôn trọng. Các phông chữ TrueType đơn giản (không phải CID) phân giải các mã một byte thông qua bảng cmap của chính phông chữ được nhúng với một chuỗi bảng con mạnh mẽ — ưu tiên định dạng Unicode 4 và 12 trước, sau đó là các bảng con ký hiệu với vùng sử dụng riêng F000, sau cùng là các định dạng Macintosh cũ — trong khi các phông chữ Type1 đơn giản phân giải qua bảng mã tích hợp sẵn của chương trình CFF

Hai tinh chỉnh khác giúp hoàn thiện bộ tính năng. Thứ nhất, từ điển /Encoding của phông chữ đơn giản được phân giải theo mức độ ưu tiên mà ISO 32000-1 §9.6.6 quy định: mảng /Differences ghi đè bảng mã cơ sở, bảng mã cơ sở ghi đè ánh xạ của chính chương trình phông chữ — đây là quy trình mà các chuỗi công cụ phát sinh từ TeX và PostScript phụ thuộc vào, với tên ký tự đồ họa được phân giải qua Adobe Glyph List, tập ký tự CFF, hoặc bảng cmap của TrueType. Thứ hai, phông chữ Type3, với các ký tự đồ họa thực chất là các luồng nội dung nhỏ, được vẽ lại qua bộ kết xuất với sự kết hợp của ma trận phông chữ, kích thước phông chữ và ma trận văn bản; các giá trị /Widths trong không gian ký tự đồ họa được giải nghĩa qua /FontMatrix theo yêu cầu của ISO 32000-1 §9.6.5 requires, và các quy trình ký tự đồ họa khai báo hộp giới hạn d1 sẽ được cắt theo hộp đó, ngăn một ký tự mã vạch bị lỗi vẽ đè ra ngoài ô hiển thị của nó. Khi một mã không thể ánh xạ được — chương trình phông chữ bị hỏng, ký tự chưa được ánh xạ — bộ kết xuất sẽ quay lại sử dụng phông chữ hệ thống để vẽ ký tự đó thay vì bỏ qua toàn bộ khối văn bản

Làm thế nào để tăng tốc độ kết xuất lặp đi lặp lại?

Giải pháp mà HotPDF cung cấp là bộ nhớ đệm trang được sử dụng gần đây nhất (MRU): RenderLoadedPageToBitmapCached lưu trữ tối đa RenderCacheCapacity trang đã kết xuất (mặc định là 8) được khóa theo chỉ số trang và DPI, và một truy xuất trúng bộ nhớ đệm (cache hit) sẽ trả về một bản sao mới thuộc sở hữu của người gọi mà không cần xử lý lại luồng nội dung — thường nhanh hơn hàng ngàn lần so với việc giải nghĩa lại trang tài liệu. Mô hình này rất phù hợp với trình xem: người dùng chuyển qua lại giữa hai trang, hoặc một sự kiện thay đổi kích thước yêu cầu lại cùng một trang ở cùng một DPI, đều sẽ truy xuất trực tiếp từ bộ nhớ đệm

// Thumbnail strip: first pass renders, scrolling back hits the cache
for I := 0 to ThumbCount - 1 do
begin
  Bmp := Pdf.RenderLoadedPageToBitmapCached(I, 48);
  if Bmp <> nil then
  try
    ThumbList.AddThumbnail(I, Bmp);
  finally
    Bmp.Free;
  end;
end;

// After editing a loaded page in place:
Pdf.InvalidateRenderedPageCache;  // next render reflects the change

Hãy cân nhắc kỹ lưỡng về chi phí bộ nhớ trước khi tăng dung lượng bộ nhớ đệm. Một trang cỡ US Letter ở 300 DPI là 2550×3300 pixel, khoảng 25 MB cho một bitmap 24-bit, vì vậy tám trang trong bộ nhớ đệm ở độ phân giải xuất ảnh sẽ chiếm khoảng 200 MB. Ở độ phân giải DPI của ảnh thu nhỏ, cùng tám mục đó chỉ tốn chưa tới một megabyte. Hãy thiết lập RenderCacheCapacity phù hợp với DPI bạn thực tế lưu đệm, và gọi InvalidateRenderedPageCache sau bất kỳ thao tác chỉnh sửa tại chỗ nào — bộ nhớ đệm chỉ được khóa theo trang và DPI, nó không thể tự nhận biết nội dung bên dưới đã thay đổi. Việc tải một tài liệu mới sẽ tự động xóa sạch bộ nhớ đệm này

Một bộ nhớ đệm thứ hai hoạt động dưới bộ nhớ đệm trang: các đối tượng hình ảnh XObject đã giải mã được lưu trữ trong một kho giới hạn byte được kiểm soát bởi ImageCacheMaxBytes (mặc định là 32 MB) với chính sách loại bỏ phần tử ít được sử dụng gần đây nhất (LRU). Một logo hoặc hình ảnh tiêu đề thư lặp lại trên mọi trang sẽ chỉ giải mã một lần cho mỗi lượt tải tài liệu thay vì mỗi khi gặp toán tử Do, điều này giúp giảm khoảng một nửa thời gian kết xuất cho các trang chia sẻ hình ảnh và tăng tốc độ xuất tệp TIFF nhiều trang với tỷ lệ tương tự. Hàm InvalidateRenderedPageCache cũng xóa sạch bộ nhớ đệm này

Những thành phần vẫn kết xuất ở mức xấp xỉ

Bộ kết xuất nhắm vào phân nhóm tài liệu PDF phổ biến, và bạn cần biết giới hạn của nó nằm ở đâu. Không gian màu CalRGB, Lab và dựa trên ICC được kết xuất xấp xỉ chứ không được quản lý màu sắc đầy đủ — các không gian màu thiết bị, bảng màu Indexed và tra cứu màu hàm Type 0 lấy mẫu được xử lý tốt, nhưng một tệp tin sản xuất in ấn phụ thuộc vào ý đồ kết xuất ICC (ICC rendering intents) sẽ không chính xác tuyệt đối về màu sắc. Các mô hình đổ bóng (sh) và các chế độ hòa trộn ngoài alpha đơn giản cũng nằm ngoài phạm vi xử lý, và đệ quy Form XObject được giới hạn độ sâu để tránh lặp vô hạn. Đối với hóa đơn, báo cáo, hợp đồng và biểu mẫu — các trang chứa văn bản, đường dẫn và hình ảnh — kết quả đầu ra là trung thực; còn đối với một bản vẽ thiết kế chứa đầy gradient và các nhóm trong suốt, hãy coi bitmap là bản xem trước chứ không phải bản in thử

Ý nghĩa thực tế: nếu quy trình xử lý của bạn tạo ra tài liệu bằng HotPDF hoặc tiêu thụ các tệp PDF doanh nghiệp thông thường, RenderLoadedPageToBitmap sẽ chuyển đổi chúng với hình dáng ký tự đồ họa nhúng chính xác, khoảng tiến CID chuẩn xác và hình học trang chuẩn chỉnh. Các kết quả xấp xỉ chỉ xuất hiện ở các góc khuất của mô hình đồ họa mà tài liệu doanh nghiệp hiếm khi chạm tới

RenderLoadedPageToBitmap, biến thể lưu đệm của nó, và quy trình kết xuất ký tự đồ họa nhúng mô tả ở đây đi kèm như một phần của HotPDF Component dành cho Delphi và C++Builder — một thư viện VCL gốc không phụ thuộc vào DLL bên ngoài, bao gồm các tính năng tạo, chỉnh sửa PDF, trích xuất văn bản và kết xuất trang trong cùng một gói sản phẩm