losLab PDF Library có thể nhúng các chương trình phông chữ còn thiếu của một tài liệu PDF đã được tải chỉ bằng một lệnh gọi duy nhất: EmbedMissingFonts sẽ duyệt qua từng từ điển phông chữ trong tài liệu, xác định phông chữ hệ thống được cài đặt tương thích theo tên BaseFont của nó, và ghi chương trình phông chữ đó ngược trở lại tệp tin. Đối với các đội ngũ kỹ thuật cần sửa chữa các tài liệu của bên thứ ba không vượt qua được bước xác thực kiểm tra tính hợp lệ của PDF/A do lỗi thiếu nhúng phông chữ, đây chính là giải pháp giúp lỗi preflight 00030 biến mất
Tình huống này cực kỳ phổ biến. Một luồng tiếp nhận lưu trữ nhận các tệp PDF từ các nhà cung cấp, khách hàng hoặc từ một đơn vị quét tài liệu; các tài liệu này hiển thị hoàn toàn bình thường trên mọi máy tính trong văn phòng; nhưng sau đó trình xác thực PDF/A lại từ chối toàn bộ loạt tệp đó với cùng một lỗi lặp đi lặp lại trên mỗi tệp: ít nhất một phông chữ chưa được nhúng. Không ai ở thượng nguồn luồng dữ liệu sẽ tạo lại các tệp này, vì vậy luồng tiếp nhận buộc phải tự sửa lỗi chúng. Bài viết này sẽ hướng dẫn quy trình sửa lỗi đó. Đây là phần bổ sung cho bài viết về preflight, trong đó trình bày cách phát hiện các lỗi vi phạm tiêu chuẩn PDF/A và PDF/UA: bài viết đó giúp bạn biết những tài liệu nào bị lỗi, còn bài viết này sẽ sửa lỗi phổ biến nhất trong số đó
Tại sao PDF/A yêu cầu mọi phông chữ phải được nhúng?
Tiêu chuẩn ISO 19005-1 §6.3.4 yêu cầu mọi phông chữ được sử dụng bởi một tài liệu tuân thủ chuẩn phải mang theo chương trình phông chữ của nó bên trong tệp tin, bởi vì toàn bộ cam kết của PDF/A là khả năng tái hiện: tài liệu phải hiển thị giống hệt nhau trên một máy tính ở thời điểm 50 năm sau, ngay cả khi máy tính đó không chia sẻ bất kỳ phông chữ nào với máy tính đã tạo ra tài liệu. Một phông chữ không được nhúng là một chỉ dẫn để tìm phông chữ Arial ở đâu đó trên hệ thống hiển thị, và quan điểm của tiêu chuẩn là "ở đâu đó trên hệ thống hiển thị" không phải là một sự đảm bảo cho việc lưu trữ lâu dài. Bất kể các ký tự (glyphs), số đo phông chữ (metrics) và phạm vi hỗ trợ của phông chữ thay thế là gì, người đọc sẽ nhận được chính xác những thứ đó, và nó có thể không giống như những gì tác giả ban đầu đã nhìn thấy
Nguyên nhân lịch sử là quy ước Standard 14. PDF 1.0 đã cam kết rằng mọi trình xem PDF đều tích hợp sẵn Helvetica, Times, Courier, Symbol và ZapfDingbats, vì vậy các trình tạo PDF đã quen với việc chỉ tham chiếu các phông chữ đó bằng tên mà không nhúng gì cả, và suốt 30 năm qua các công cụ phát triển vẫn làm chính xác điều đó. losLab PDF Library tuân thủ nghiêm ngặt yêu cầu này đến mức trong chế độ tạo tài liệu chuẩn PDF/A, AddStandardFont được thiết kế cố ý để không thực hiện hành động nào (no-op): thư viện không đi kèm các chương trình phông chữ Standard 14, không thể nhúng những gì nó không có, và từ chối ghi một tham chiếu phông chữ không được nhúng vào một tài liệu tuyên bố tuân thủ chuẩn. Nó sẽ trả về kết quả là 0 mà không chọn phông chữ, do đó một tài liệu PDF/A phải sử dụng AddTrueTypeFont đi kèm với việc nhúng phông chữ, và mọi yêu cầu Embed=0 sẽ tự động được chuyển thành Embed=1 khi chế độ PDF/A đang hoạt động. Đó là về phía ghi tài liệu. Vấn đề khó khăn hơn nằm ở phía đọc: một tài liệu mà người khác đã tạo, chứa đầy các từ điển phông chữ mà bạn không hề tự thiết lập
EmbedMissingFonts sửa chữa một tài liệu đã được tải như thế nào?
losLab PDF Library sửa chữa các phông chữ trực tiếp tại chỗ thay vì xây dựng lại chúng. Khi một trình tạo PDF ghi một phông chữ TrueType không được nhúng, từ điển FontDescriptor mà nó tạo ra thực tế đã hoàn chỉnh: FontName, FontBBox, Flags, Ascent, Descent, StemV, tất cả đều hiện diện. Điều duy nhất phân biệt nó với một phông chữ được nhúng là sự thiếu vắng của một mục nhập, đó là tham chiếu luồng dữ liệu /FontFile2 chứa chương trình phông chữ thực tế. Do đó, EmbedMissingFonts không chạm vào từ điển phông chữ, bảng mã hóa (encoding), mảng chiều rộng (widths array) hay bất kỳ luồng nội dung nào tham chiếu đến phông chữ bằng tên tài nguyên. Nó đọc chương trình phông chữ tương ứng từ hệ thống, nén nó vào một đối tượng luồng mới và thêm một tham chiếu /FontFile2 duy nhất (hoặc /FontFile3 đối với phông chữ CIDFontType0) vào FontDescriptor đã có sẵn. Mọi thứ mà các trang của tài liệu trỏ tới vẫn giữ nguyên vị trí cũ, điều này giúp cho thao tác này cực kỳ an toàn khi thực thi trên các tệp tin mà bạn không có quyền kiểm soát nguồn gốc
Phạm vi hỗ trợ bao gồm cả hai cấu trúc phông chữ mà bạn sẽ gặp trong thực tế: phông chữ TrueType đơn giản và phông chữ hỗn hợp Type0/CID, loại được tạo ra cho văn bản CJK và đầu ra Unicode hiện đại. Quá trình duyệt sẽ cố ý liệt kê mọi từ điển Font trong cây đối tượng của tài liệu thay vì chỉ dựa vào việc duyệt tài nguyên từng trang một, do đó các phông chữ được tham chiếu từ các chú thích (annotations) hoặc được chia sẻ qua các trang cũng được xử lý đầy đủ. API này là một lệnh gọi duy nhất trên tài liệu đã được tải
var
PDF: TPDFlib;
Repaired: Integer;
begin
PDF := TPDFlib.Create;
try
if PDF.LoadFromFile('supplier-invoice.pdf', '') <> 1 then
raise Exception.Create('Could not load PDF');
// Walks every Font dictionary; returns how many fonts
// gained a font program. Fonts whose program cannot be
// found on the system are skipped, not failed.
Repaired := PDF.EmbedMissingFonts;
Writeln(Format('%d font program(s) embedded', [Repaired]));
PDF.SaveToFile('supplier-invoice-repaired.pdf');
finally
PDF.Free;
end;
end;
Một chi tiết cần lưu ý để giải thích vì sao việc khớp tên hoạt động tốt hơn phép so sánh chuỗi thông thường: thư viện sẽ chuẩn hóa tên BaseFont trước khi tra cứu. Tiền tố phân nhóm phông chữ (quy luật ABCDEF+ gồm 6 ký tự viết hoa và một dấu cộng) được loại bỏ, các hậu tố kiểu PostScript như ArialMT được phân giải thành Arial, và các tệp TrueType Collection cũng được phát hiện và giải nén để một phông chữ nằm trong tệp .ttc vẫn được nhúng chính xác
Xác thực việc sửa lỗi bằng báo cáo preflight
CreatePreflightReport là bước xác thực, và vòng lặp này được khép kín một cách có chủ ý: chính trình kiểm tra đã báo lỗi tệp tin cũng sẽ là trình xác nhận tệp tin đã sạch lỗi. Mã lỗi 00030 là phát hiện kiểm tra chuyên sâu của PDF/A có nội dung "At least one font is not embedded (FontFile/FontFile2/FontFile3 missing)" (Ít nhất một phông chữ không được nhúng), và lỗi này được báo cáo cho toàn bộ tệp tin, vì vậy chỉ cần một phông chữ duy nhất bị bỏ sót thì lỗi vẫn tồn tại. Hãy chạy báo cáo trên tệp nguồn, sửa lỗi, lưu lại, và chạy báo cáo một lần nữa trên tệp đầu ra
function HasFontEmbeddingViolation(PDF: TPDFlib;
const FileName: string): Boolean;
var
Report: string;
begin
// ComplianceTests = 1 selects the PDF/A checks
Report := PDF.CreatePreflightReport(FileName, '', 1, 0);
Result := Pos('00030', Report) > 0;
end;
Để có góc nhìn chi tiết theo từng phông chữ thay thế cho kết luận tổng quát của từng tệp, hãy tải lại tài liệu đã sửa lỗi và thực hiện liệt kê: FindFonts tiếp theo là SelectFont và GetFontIsEmbedded sẽ báo cáo trạng thái nhúng của từng phông chữ một, đây là công cụ phù hợp khi một tác vụ xử lý hàng loạt cần ghi lại nhật ký chính xác phông chữ nào trong tệp nào không thể sửa lỗi. Mô hình liệt kê tương tự cũng xuất hiện trong bài viết về cách trích xuất văn bản, hình ảnh và phông chữ từ tệp PDF đã tải, nơi nó phục vụ cho việc trích xuất thay vì sửa lỗi
Điều gì xảy ra khi phông chữ không được cài đặt trên hệ thống?
EmbedMissingFonts bỏ qua bất kỳ phông chữ nào mà nó không thể tìm thấy chương trình của nó, và báo cáo việc bỏ qua này thông qua giá trị trả về: nếu số lượng phông chữ được sửa thành công ít hơn số lượng phông chữ chưa được nhúng mà bạn đã đếm, phần chênh lệch chính là những phông chữ mà hệ thống không có. Đây là chế độ lỗi thực tế, và nó tốt hơn các giải pháp thay thế khác, bởi vì việc tự tạo ra một chương trình phông chữ thay thế cho phông chữ được đặt tên trong tài liệu sẽ làm thay đổi kết quả hiển thị, điều mà một quy trình sửa lỗi lưu trữ tuyệt đối không bao giờ được phép làm. Đối với những trường hợp này, losLab PDF Library cung cấp hàm EmbedFontProgramFromFile, cho phép nhúng một tệp .ttf hoặc .otf do người gọi cung cấp vào phông chữ được chỉ định, để hệ thống xử lý có thể đi kèm các phông chữ doanh nghiệp dự kiến sẽ gặp và chủ động sử dụng chúng làm phông chữ dự phòng
var
I, FontID: Integer;
begin
PDF.FindFonts;
for I := 1 to PDF.FontCount do
begin
FontID := PDF.GetFontID(I);
if (FontID > 0) and (PDF.SelectFont(FontID) = 1) then
if PDF.GetFontIsEmbedded = 0 then
// Try the installed system font first, then fall back
// to a font file shipped alongside the application
if PDF.EmbedFontProgram(PDF.FontName) = 0 then
PDF.EmbedFontProgramFromFile(PDF.FontName,
'fonts\CorporateSans.ttf');
end;
end;
Có hai giới hạn cần được nêu rõ ràng. Thứ nhất, phông chữ Type1 không được sửa lỗi trong phiên bản hiện tại: mục nhập /FontFile của chúng yêu cầu cấu trúc PFB ba phân đoạn với các khóa độ dài rõ ràng, và thư viện sẽ bỏ qua chúng thay vì ghi một luồng dữ liệu bị lỗi; chúng khá hiếm trong các tài liệu hiện đại nhưng vẫn xuất hiện trong các kho lưu trữ cũ. Thứ hai, việc nhúng một phông chữ là một hành vi liên quan đến bản quyền. Quyền nhúng của một phông chữ TrueType thuộc về nhà thiết kế phông chữ đó, và một luồng xử lý sửa lỗi tự động chèn các chương trình phông chữ có bản quyền vào các tài liệu gửi ra ngoài tổ chức cần phải được xác nhận xem giấy phép sử dụng phông chữ có thực sự cho phép điều đó hay không. Thư viện sẽ thực hiện bất kỳ điều gì bạn yêu cầu; nhưng liệu bạn có được phép yêu cầu điều đó hay không là câu hỏi dành cho bộ phận pháp lý của bạn, chứ không phải trình biên dịch của bạn
Nhúng phông chữ là điều kiện cần, nhưng chưa đủ
Sửa lỗi phông chữ chỉ giải quyết được lỗi 00030, chứ không giải quyết được gì khác. Một tài liệu không đạt chuẩn PDF/A do bị mã hóa, thiếu siêu dữ liệu XMP, sử dụng không gian màu phụ thuộc thiết bị mà không có OutputIntent, hoặc thiếu bản đồ ToUnicode vẫn sẽ tiếp tục thất bại sau khi mọi phông chữ đã được nhúng, đó là lý do tại sao bước sửa lỗi này nên nằm trong một vòng lặp dựa trên preflight thay vì thay thế nó. Hãy chạy báo cáo đầy đủ, sửa những gì báo cáo chỉ ra, và để báo cáo cho bạn biết khi nào bạn đã hoàn tất. Ngoài ra còn có khía cạnh dung lượng: một chương trình phông chữ CJK đầy đủ có thể lên tới hàng megabyte, vì vậy việc nhúng một vài phông chữ như vậy có thể làm tăng đáng kể dung lượng của một tài liệu nhỏ. Giải pháp cân bằng là subsetting (rút gọn phông chữ), được trình bày trong bài viết về tối ưu hóa kích thước tệp PDF và rút gọn phông chữ, giúp cắt giảm mỗi chương trình phông chữ được nhúng chỉ còn giữ lại các ký tự thực sự được hiển thị trong tài liệu
Tránh lỗi lặp lại đối với tài liệu mới
SetEmbedAllFonts là nửa còn lại giúp ngăn ngừa lỗi của cùng tính năng này: một lớp bảo vệ ở phía ghi nhằm ngăn mã nguồn của chính bạn tạo ra các tài liệu bị lỗi giống như những tài liệu mà bài viết này hướng dẫn sửa đổi. Khi kích hoạt SetEmbedAllFonts(1) học động, bất kỳ lệnh gọi AddTrueTypeFont tiếp theo nào yêu cầu Embed=0 sẽ được chuyển thành một tham chiếu nhúng, mở rộng sự đảm bảo mà chế độ PDF/A đã áp dụng cho mọi tài liệu. Nó chỉ ảnh hưởng đến các phông chữ được thêm vào sau lệnh gọi đó, chứ không ảnh hưởng đến các phông chữ đã có sẵn trong tệp tin được tải, giúp phân chia nhiệm vụ rõ ràng: SetEmbedAllFonts dành cho các tài liệu bạn tự tạo ra, còn EmbedMissingFonts dành cho các tài liệu bạn nhận được từ nguồn khác
PDF.NewDocument;
PDF.SetEmbedAllFonts(1);
// From here on, AddTrueTypeFont(Name, 0) behaves
// like AddTrueTypeFont(Name, 1): no non-embedded
// reference can reach the output file
Cả hai giải pháp, lớp bảo vệ phía ghi và quy trình tải-sửa-lưu, đều là một phần của losLab PDF Library dành cho Delphi, C# và VB.NET, đi kèm với công cụ preflight giúp xác thực kết quả; trang sản phẩm chứa toàn bộ tài liệu tham khảo API phông chữ đầy đủ bao gồm các lệnh gọi nhúng và rút gọn cho từng phông chữ