XFA, hay Kiến trúc biểu mẫu XML (XML Forms Architecture), đã bị phản đối (deprecated). ISO 32000-1 mang nó trong mục §12.7 với lưu ý rằng nó đã bị loại bỏ khỏi PDF 2.0, và các trình xem hiện đại đang lần lượt loại bỏ các công cụ XFA của họ. Không điều nào trong số đó làm rỗng các kho lưu trữ. Biểu mẫu tiếp nhận của chính phủ, đơn đăng ký bảo hiểm, và sao kê ngân hàng đã được soạn thảo dưới dạng XFA trong phần lớn hai thập kỷ qua, và những tệp đó vẫn đang được gửi đến hộp thư đến và luồng tài liệu ngày nay. Khi trình xem từng hiển thị chúng ngừng làm việc đó, biểu mẫu sẽ biến thành một trang trắng với một trình giữ chỗ "vui lòng mở trong một trình đọc khác". Giải pháp khắc phục bền vững là làm phẳng XFA thành nội dung PDF tĩnh mà bất kỳ trình đọc nào cũng có thể vẽ.
Phần khó khăn của việc làm phẳng đó không phải là các trường dữ liệu. Các hộp văn bản (text box) và hộp kiểm (check box) ánh xạ lên các widget AcroForm đủ sạch sẽ. Phần khó khăn là văn bản dạng giàu (rich text) mà XFA lưu trữ bên trong một phần tử vẽ, trong một khối <exData contentType="text/html">. Khối đó là một tập con HTML với định kiểu nội tuyến (inline styling) và thường có các liên kết neo (anchor). Đưa nó lên trang có nghĩa là tái tạo cả văn bản đã được định kiểu và các siêu liên kết trực tiếp, và các siêu liên kết là nơi hầu hết các triển khai lặng lẽ bỏ cuộc.
Văn bản dạng giàu XFA thực sự trông như thế nào
Một thân exData là một lát nhỏ của XHTML. Một đoạn văn là một <p>; một nhóm ký tự được định kiểu là một <span> với CSS nội tuyến riêng của nó cho độ đậm (weight), tư thế (posture), màu sắc, và kích thước; và một siêu liên kết là một <a href="..."> bao bọc văn bản hiển thị của nó. Một dòng duy nhất có thể chứa nhiều thẻ span liên tiếp, mỗi thẻ có định kiểu khác nhau, và một trong số chúng có thể là một neo liên kết. Định kiểu đó không phải là trang trí có thể bỏ qua. Một điều khoản được kết xuất bằng màu đỏ đậm vì nó là một cảnh báo pháp lý phải giữ nguyên màu đỏ đậm sau khi làm phẳng, nếu không tài liệu được làm phẳng sẽ biểu thị sai lệch tài liệu gốc.
Vì vậy, công cụ làm phẳng không thể coi khối đó là một chuỗi duy nhất. Nó phải đi qua cấu trúc nội tuyến, giải quyết kiểu dáng hiệu dụng của mỗi lượt chạy (run) bằng cách xếp lớp CSS nội tuyến của thẻ span lên phông chữ cơ sở của phần tử vẽ, và bố trí các lượt chạy lần lượt trên toàn bộ dòng. HotPDF mô hình hóa từng phân đoạn được bố trí này như một bản ghi TXFARichRun nội bộ. Bản ghi mang văn bản của lượt chạy, kiểu dáng đã giải quyết của nó, hộp đo lường của nó, và đối với một neo liên kết, giá trị Href mà nó trỏ tới.
Bố trí các lượt chạy từ trái sang phải
Định vị là nơi văn bản dạng giàu ngừng là một vấn đề phân tích cú pháp và trở thành một vấn đề sắp chữ. Các lượt chạy chia sẻ một dòng, vì vậy mỗi lượt chạy bắt đầu từ nơi lượt chạy trước đó kết thúc. Không có đánh dấu nào ghi lại những vị trí đó; chúng phải được đo lường. Quy trình LayoutRichText nội bộ của công cụ đo lường mọi lượt chạy với cùng các số đo phông chữ (font metrics) mà sau này sẽ vẽ nó, sau đó đặt độ lệch ngang (horizontal offset) của lượt chạy bằng tổng lũy kế của tất cả độ rộng của lượt chạy trước đó. Lượt chạy một bắt đầu tại gốc của hộp vẽ, lượt chạy hai bắt đầu tại độ rộng của lượt chạy một, lượt chạy ba tại độ rộng kết hợp của hai lượt chạy đầu tiên, và cứ thế trên toàn bộ dòng.
Đây là lý do tại sao căn chỉnh phông chữ đo lường lại quan trọng đến vậy. Lượt bố trí (layout pass) đo các khoảng tiến (advance); một lượt kết xuất (render pass) riêng biệt vẽ các glyph. Nếu hai lượt này không đồng nhất về phông chữ, các hộp mà bố trí đã tính toán sẽ không khớp dưới các glyph mà bộ kết xuất vẽ. HotPDF giữ chúng đồng bộ bằng cách ánh xạ kiểu dáng đã phân giải của mỗi lượt chạy lên một đặc tả phông chữ, thông qua trình hỗ trợ nội bộ RunStyleToFontSpec helper, khớp với các mặc định của chính bộ kết xuất là Arial ở kích thước 10 point. Khoảng tiến đo được và văn bản được vẽ sau đó sẽ khớp nhau, và hộp tính toán của một lượt chạy thực sự bao phủ các ký tự mà người đọc nhìn thấy.
// Conceptual shape of one laid-out run. The engine builds an array of these
// internally; you never construct them yourself, but the fields explain how a
// link's hit box is derived from measured geometry rather than from text.
type
TRichRunInfo = record
Dx, Dy : Double; // top-left, relative to the draw-box origin
W, H : Double; // measured run box (width from the layout pass)
Text : AnsiString; // the run's visible characters
Href : AnsiString; // URI target for an <a> run, '' otherwise
end;
Từ một lượt chạy neo liên kết đến một PDF Link annotation
Một siêu liên kết trong một tài liệu PDF đã hoàn thiện không phải là một phần của nội dung trang. Nó là một đối tượng riêng biệt, một Link annotation, được mô tả trong ISO 32000-1 §12.5.6.5. Annotation có một /Rect xác định hình chữ nhật có thể nhấp trên trang và một action kích hoạt khi hình chữ nhật được nhấp. Đối với một liên kết bên ngoài, action đó là một URI action: /S /URI với địa chỉ đích là chuỗi /URI của nó. Văn bản hiển thị bên dưới là nội dung trang thông thường; annotation là vùng nóng (hot zone) vô hình được đặt đè lên trên nó.
Đường dẫn làm phẳng tuân theo chính xác mô hình này. Khi một lượt chạy mang theo một Href, đầu tiên HotPDF vẽ văn bản đã được định kiểu, sau đó xây dựng một Link annotation trên hộp của lượt chạy. Điểm truy cập công khai cho annotation đó là phương thức trang AddURILink, tạo đối tượng /Type /Annot /Subtype /Link với một URI action và trả về từ điển annotation. Hình chữ nhật của nó là hộp đo lường của lượt chạy, được dịch từ tọa độ cục bộ của phần tử vẽ sang tọa độ trang. Kết quả là một liên kết rơi chính xác vào văn bản neo và không ở nơi nào khác.
// The same public API the flatten path uses for each anchor run. It produces
// an ISO 32000-1 12.5.6.5 Link annotation: /Subtype /Link with a /URI action
// over the given rectangle. The optional description fills /Contents so a
// screen reader can announce the target.
var
LinkRect: TRect;
Annot: THPDFDictionaryObject;
begin
LinkRect := Rect(72, 690, 268, 706); // page-space hit box for the run
Annot := Pdf.CurrentPage.AddURILink(LinkRect,
'https://www.example.gov/appeal', 'File an appeal online');
end;
Tại sao hộp va chạm (hit box) phải đến từ độ rộng đo lường
Có vẻ hấp dẫn khi hình dung việc xác định vị trí liên kết bằng cách tìm kiếm trang cho văn bản hiển thị của nó và vẽ hình chữ nhật xung quanh bất cứ thứ gì được tìm thấy. Điều đó không hoạt động, và lý do là cơ bản đối với cách lưu trữ văn bản đã được làm phẳng. Các lượt chạy đã định kiểu được vẽ bằng các phông chữ tập con được nhúng (embedded subset fonts). Một phông chữ tập con đánh số lại các glyph mà nó giữ, vì vậy page content stream chứa các mã CID thập lục phân, chứ không phải các mã ký tự ban đầu. Các byte trên trang không phải là các chữ cái mà con người đọc, và chúng không thể tìm kiếm được dưới dạng văn bản. Việc tìm kiếm chú thích của neo liên kết sẽ không tìm thấy gì, vì chú thích đó không tồn tại dưới dạng văn bản đen chữ trắng ở bất kỳ đâu trong stream.
Neo đáng tin cậy duy nhất cho hình chữ nhật là hình học mà lượt bố trí đã tạo ra. Khoảng lệch và độ rộng đo được của mỗi lượt chạy đã được tính toán trong khi phân luồng dòng, trước khi bất kỳ glyph nào được đánh số lại, và chúng mô tả nơi văn bản sẽ thực sự xuất hiện trên trang. Do đó, HotPDF lấy hình chữ nhật liên kết trực tiếp từ hộp của lượt chạy đã được đặt xuống thay vì từ bất kỳ tra cứu văn bản nào. Bởi vì phép đo đã sử dụng phông chữ kết xuất, hộp sẽ chính xác bất kể việc tạo tập con. Hình học vẫn tồn tại qua quá trình mã hóa; văn bản thì không. Đó là toàn bộ luận điểm cho việc định vị theo độ rộng đo lường, và là lý do tại sao một trình làm phẳng cố gắng sửa đổi các liên kết bằng tìm kiếm văn bản sẽ tạo ra các vùng va chạm bị trôi lệch hoặc biến mất.
Điều khiển việc làm phẳng từ mã của bạn
Đối với một tệp PDF đã chứa gói XFA, điểm truy cập là FlattenLoadedXFA. Tải tài liệu, gọi phương thức, và lưu kết quả. Tham số Editable quyết định điều gì xảy ra với các trường biểu mẫu: truyền True để giữ chúng dưới dạng các widget AcroForm có thể điền, hoặc False để đánh dấu mọi widget là chỉ đọc để đầu ra là một bản ghi bị đóng băng. Các khối vẽ văn bản dạng giàu, với các lượt chạy đã định kiểu và các link annotation của chúng, được tạo ra trong cả hai trường hợp. Hàm trả về số lượng widget mà nó đã xuất ra.
var
Pdf: THotPDF;
Emitted, i: Integer;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.LoadFromFile('xfa_appeal_form.pdf');
// True keeps fields fillable; False freezes them read-only.
Emitted := Pdf.FlattenLoadedXFA(True);
// Anything the engine could not map is reported, not raised.
for i := 0 to Pdf.XFAFlattenWarnings.Count - 1 do
Writeln('XFA warning: ', Pdf.XFAFlattenWarnings[i]);
Pdf.SaveLoadedDocument('appeal_form_flat.pdf');
Writeln('Widgets emitted: ', Emitted);
finally
Pdf.Free;
end;
end;
Đóng băng kết quả
Mở tệp đã làm phẳng trong Acrobat, hoặc bất kỳ trình xem hiện tại nào, và kiểm tra hai điều. Đầu tiên, văn bản dạng giàu được kết xuất với kiểu dáng còn nguyên vẹn: các lượt chạy in đậm vẫn đậm, các lượt chạy màu sắc mang đúng màu của chúng, và các span nằm đúng thứ tự trên dòng thay vì chồng chéo hoặc chạy lệch khỏi hộp. Thứ hai, các siêu liên kết đang hoạt động trực tiếp. Rê chuột lên một neo liên kết và thanh trạng thái sẽ hiển thị địa chỉ đích; nhấp vào nó và URI action sẽ mở nó ra. Sử dụng trình kiểm tra annotation của trình xem để xác nhận mỗi liên kết là một /Link annotation thực sự có /Rect ôm sát văn bản neo, nằm trên nội dung hiện là các glyph được vẽ phẳng thay vì XFA được kết xuất bằng biểu mẫu. Sự kết hợp đó, văn bản tĩnh đã được định kiểu cộng với các Link annotation thực sự trên các hình chữ nhật chính xác, là điều giúp tài liệu được làm phẳng tồn tại lâu hơn các công cụ XFA mà nó không còn cần nữa.
Flattening the fields themselves, the text boxes, check boxes, and choice lists that surround this rich text, is covered in our walkthrough on flattening XFA forms into AcroForm widgets. For the wider story of building and placing Link annotations by hand, beyond the ones the flatten path generates, see working with PDF annotations in HotPDF. Both build on the same annotation and forms model that ships with the HotPDF Component for Delphi and C++Builder.