PDFium Component cho phép một ứng dụng Delphi quyết định byte font nào được dùng khi một PDF tham chiếu đến một font mà nó không nhúng. ConfigureSystemFontProvider cài đặt một triển khai IPdfSystemFontProvider nhận mọi yêu cầu ánh xạ font mà PDFium tạo ra, đầy đủ tên mặt chữ, độ đậm, cờ in nghiêng, bảng ký tự và họ độ rộng nét (pitch family), và trả lời bằng byte TrueType, TrueType Collection hoặc OpenType cần dùng
Điều này tồn tại vì các font không nhúng là một canh bạc render. Một PDF đặt tên Arial và không nhúng gì cả sẽ render bằng Arial trên một máy trạm, bằng một font thay thế tương thích về số đo trên một server Linux, và bằng bất cứ thứ gì bộ ánh xạ của hệ điều hành tìm thấy trên một image container bị khóa chặt. Cùng một hóa đơn trông khác nhau trên mỗi nơi, ngắt dòng dịch chuyển, và một khách hàng nhận được một tài liệu không khớp với bản lưu trữ
Vì sao không đơn giản là cài font trên server?
Đôi khi đó là câu trả lời, và khi đúng như vậy, hãy làm theo cách đó. Nhưng nó thất bại trong ba tình huống phổ biến. Giấy phép có thể cấm cài đặt một font trên server cho việc render tự động. Image container được build lại thường xuyên và một font cài thủ công biến mất ở lần triển khai tiếp theo. Và các quy trình chịu quản lý cần bộ máy render có thể tái lập từ các artefact nằm dưới kiểm soát phiên bản, điều mà một lần cài font trên toàn máy không đáp ứng được
Một bộ cung cấp giải quyết cả ba vấn đề bằng cách chuyển quyết định vào ứng dụng của bạn. Font được đóng gói như tài nguyên bạn kiểm soát, chính sách ánh xạ là mã bạn có thể soát xét, và cùng một binary render giống hệt nhau ở mọi nơi vì không có gì phụ thuộc vào những gì tình cờ được cài đặt
Cài đặt một bộ cung cấp
Việc cấu hình phải diễn ra trước khi thư viện được nạp. PDFium chấp nhận một cấu trúc thông tin font hệ thống lúc khởi tạo và giữ các handle mà nó phát ra sau đó, nên đổi bộ cung cấp trong khi tài liệu đang mở sẽ làm vô hiệu các handle font mà PDFium vẫn đang giữ; thành phần này thẳng thừng từ chối điều đó thay vì để nó làm hỏng một lần render:
uses
PDFium;
type
TAppFontProvider = class(TInterfacedObject, IPdfSystemFontProvider)
public
function ResolveFont(const Request: TPdfSystemFontRequest;
out Font: TPdfSystemFontData): Boolean;
end;
function TAppFontProvider.ResolveFont(const Request: TPdfSystemFontRequest;
out Font: TPdfSystemFontData): Boolean;
var
Path: string;
begin
// Ánh xạ có thể dự đoán: tên mặt chữ cộng với độ đậm và in nghiêng
// quyết định file nào chúng ta đóng gói cho yêu cầu này
Path := MapFaceToBundledFile(Request.FaceName, Request.Weight,
Request.Italic, Request.Charset);
Result := Path <> '';
if not Result then
Exit;
Font.FaceName := Request.FaceName;
Font.FontData := LoadFileBytes(Path); // byte sfnt hoặc TTC hoàn chỉnh
Font.Charset := Request.Charset;
Font.TTCIndex := 0; // chỉ số bên trong một collection
end;
var
Policy: TPdfSystemFontPolicy;
begin
Policy := TPdfSystemFontPolicy.Default;
Policy.AllowDefaultFallback := False; // host quyết định mọi thứ
Policy.AllowFaceSubstitution := False; // từ chối một tên mặt chữ khác
Policy.MaxFontBytes := 32 * 1024 * 1024;
Policy.MaxCacheEntries := 64;
ConfigureSystemFontProvider(TAppFontProvider.Create, Policy);
// Chỉ bây giờ mới nạp thư viện và mở tài liệu
end;
Việc dọn dẹp chạy theo thứ tự ngược lại: bộ cung cấp được tách khỏi PDFium trước, rồi thư viện mới được gỡ tải. Bỏ qua bước tách sẽ để lại các handle font gốc trỏ vào các đối tượng Pascal sắp bị giải phóng, đây là lỗi truy cập bộ nhớ (access violation) kinh điển khi tắt hệ thống trong mã trộn lẫn interface đếm tham chiếu với một thư viện C
Các cờ chính sách thực sự quyết định điều gì
AllowDefaultFallback là công tắc chuyển giữa hai chế độ vận hành. Khi tắt, một yêu cầu mà bộ cung cấp từ chối sẽ đơn giản thất bại, điều bạn muốn trong khi chứng minh rằng mọi font trong một kho dữ liệu đều được tính đến: bất kỳ khoảng trống nào lộ ra ngay lập tức thay vì bị che giấu. Khi bật, các yêu cầu chưa được giải quyết được ủy quyền cho bộ ánh xạ trả về bởi FPDF_GetDefaultSystemFontInfo, trong khi thế giới bên ngoài vẫn thấy một lớp bọc handle đồng nhất duy nhất, với tên mặt chữ, bảng ký tự, dữ liệu bảng và thao tác xóa font được định tuyến đúng theo nguồn gốc
AllowFaceSubstitution chi phối việc liệu một bộ cung cấp có được phép trả lời bằng một tên mặt chữ khác với tên được yêu cầu hay không. Tắt nó biến việc thay thế thành một quyết định tường minh thay vì một tai nạn, điều quan trọng khi một tài liệu đặt tên một font có số đo khác biệt đủ để thay đổi cách phân trang
Thành phần này xác thực mọi phản hồi của bộ cung cấp trước khi nó đến PDFium: dữ liệu rỗng bị từ chối, font quá khổ bị từ chối theo MaxFontBytes, chỉ số TTC được kiểm tra, và các bảng sfnt riêng lẻ được phục vụ từ thư mục font khi PDFium hỏi một bảng thay vì hỏi cả file. Khả năng cuối cùng đó nghĩa là một bộ cung cấp có thể giao một file font hoàn chỉnh và để thành phần trả lời các truy vấn cấp bảng, thay vì phơi bày các đối tượng Pascal thô qua ABI C
Cache mà không để lại dữ liệu font treo lơ lửng
Các yêu cầu ánh xạ font lặp lại liên tục trong quá trình render, nên phản hồi được cache với một khóa bao phủ mọi tham số chọn font, bị loại bỏ theo thứ tự sử dụng gần đây nhất bị giới hạn (bounded LRU). Điểm tinh tế nằm ở vòng đời: PDFium có thể vẫn đang đọc byte của một font mà mục cache của nó vừa bị loại bỏ
Cache lưu các mảng động đếm tham chiếu và mỗi handle gốc giữ ảnh chụp riêng của nó, nên việc loại bỏ chỉ giảm một tham chiếu chứ không giải phóng bộ nhớ đang được dùng. Callback xóa giải phóng handle và duy trì một bộ đếm hoạt động. Trên thực tế, điều này có nghĩa là MaxCacheEntries có thể được điều chỉnh cho bộ nhớ mà không có bất kỳ rủi ro nào rút dữ liệu ra khỏi một lần render đang diễn ra
Bộ cung cấp có được gọi trên luồng của tôi không?
Không, không nhất thiết. PDFium có thể gọi bộ ánh xạ từ các luồng worker của riêng nó, nên một triển khai phải an toàn với luồng. Bộ đếm dùng chung, cache và việc theo dõi cấu hình mỗi thứ đều được bảo vệ bên trong thành phần bằng khối tới hạn riêng của chúng, nhưng mã bên trong ResolveFont là của bạn để làm cho an toàn
Hình thức an toàn nhất là một bộ cung cấp không chạm vào bất kỳ trạng thái dùng chung có thể thay đổi nào: đọc từ một bảng được xây lúc khởi động, tải byte từ một file hoặc một resource, trả về. Nếu một lượt tra cứu cần một cache dùng chung của riêng bạn, hãy bảo vệ nó. Và giữ ngoại lệ bên trong triển khai của bạn, vì một ngoại lệ Pascal không bao giờ được phép unwind xuyên qua stack của PDFium; thành phần bắt lỗi tại ranh giới ABI C và chuyển thành một thất bại hoặc một fallback mặc định tùy chọn, nhưng dựa vào đó như luồng điều khiển bình thường sẽ tốn hiệu năng và che giấu lỗi. Các quy tắc luồng cho phần còn lại của thành phần theo cùng nguyên tắc như những quy tắc trong kỷ luật khóa render
Chứng minh việc ánh xạ trong môi trường sản xuất
Thống kê biến việc thay thế font từ một phỏng đoán thành thứ bạn có thể khẳng định được. GetSystemFontProviderStatistics báo cáo liệu một bộ cung cấp có được cấu hình và cài đặt hay không, có bao nhiêu yêu cầu ánh xạ đã được thực hiện, và chúng được đáp ứng như thế nào, chia thành số lần trúng cache, trúng bộ cung cấp và trúng fallback mặc định, cùng với các phản hồi bị từ chối, yêu cầu thất bại, handle đang hoạt động và font đã cache:
var
Stats: TPdfSystemFontStatistics;
begin
Stats := GetSystemFontProviderStatistics;
Writeln(Format('requests=%d cache=%d provider=%d fallback=%d',
[Stats.MapRequests, Stats.CacheHits, Stats.ProviderHits,
Stats.DefaultFallbackHits]));
Writeln(Format('rejected=%d failed=%d handles=%d cached=%d',
[Stats.RejectedProviderResponses, Stats.FailedRequests,
Stats.ActiveHandles, Stats.CachedFonts]));
// Trong một lần chạy kiểm tra tuân thủ với fallback bị tắt, bất kỳ
// lần trúng fallback hay yêu cầu thất bại nào đều có nghĩa là một
// tài liệu đã tham chiếu một font mà chúng ta không đóng gói
if (Stats.DefaultFallbackHits > 0) or (Stats.FailedRequests > 0) then
raise Exception.Create('unmapped font encountered - update the font set');
end;
Một con số RejectedProviderResponses tăng dần là tín hiệu cho biết bộ cung cấp đang trả lời bằng dữ liệu mà chính sách từ chối, thường là một file quá khổ hoặc một mặt chữ bị thay thế, và đáng để cảnh báo vì những yêu cầu đó âm thầm hạ cấp xuống fallback hoặc thất bại. Để chẩn đoán những font mà một tài liệu thực sự cần trước khi bạn xây bảng ánh xạ, đường kiểm tra trong phân tích thuộc tính font PDF liệt kê các font nhúng và không nhúng của mỗi tài liệu
Cấp phát font, render và trích xuất văn bản dùng chung một thực thể thư viện qua Delphi, C++Builder và Lazarus; chi tiết triển khai được mô tả trên trang PDFium Component cho Delphi