Bài viết kỹ thuật

Làm phẳng Siêu liên kết Văn bản Phong phú XFA thành Liên kết PDF trong Delphi

XFA, Kiến trúc Form XML, đã bị bỏ dùng. ISO 32000-1 mang nó trong §12.7 với ghi chú rằng nó bị xóa khỏi PDF 2.0, và các trình xem hiện đại đang bỏ dần engine XFA của họ từng cái một. Không có điều nào trong số đó đã làm trống các kho lưu trữ. Các form nhận của chính phủ, đơn bảo hiểm, và bảng sao kê ngân hàng đã được tạo ra dưới dạng XFA trong gần hai thập kỷ, và những tệp đó vẫn đang đến hộp thư đến và pipeline tài liệu ngày nay. Khi trình xem từng hiển thị chúng dừng làm vậy, form biến thành một trang trắng với một placeholder "vui lòng mở trong trình đọc khác". Cách sửa 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ó của việc làm phẳng đó không phải là các trường. Hộp văn bản và hộp kiểm ánh xạ lên widget AcroForm đủ sạch. Phần khó là văn bản phong phú mà XFA lưu trữ bên trong phần tử draw, trong một khối <exData contentType="text/html">. Khối đó là một tập con HTML với kiểu dáng inline và thường là các anchor. Đưa nó lên trang có nghĩa là tái tạo cả văn bản có kiểu dáng lẫn 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 phong phú XFA thực sự trông như thế nào

Một phần thân exData là một lát nhỏ XHTML. Một đoạn là <p>; một span ký tự có kiểu dáng là <span> với CSS inline riêng cho độ đậm, kiểu chữ, màu sắc, và kích thước; và một siêu liên kết là <a href="..."> bọc văn bản hiển thị của nó. Một dòng đơn có thể chứa nhiều span liên tiếp, mỗi span có kiểu dáng khác nhau, và một trong số chúng có thể là anchor. Kiểu dáng không phải là trang trí có thể bỏ. Một mệnh đề được hiển thị in đậm màu đỏ vì nó là cảnh báo pháp lý phải giữ đậm và đỏ sau khi làm phẳng, hoặc tài liệu được làm phẳng sẽ trình bày sai bản gốc

Vì vậy engine làm phẳng không thể xử lý khối như một chuỗi. Nó phải duyệt cấu trúc inline, xử lý kiểu dáng hiệu quả của mỗi run bằng cách chồng CSS inline của span lên font cơ sở của phần tử draw, và bố cục các run lần lượt qua dòng. HotPDF mô hình hóa mỗi trong số các đoạn được bố cục này dưới dạng một bản ghi TXFARichRun nội bộ. Bản ghi mang văn bản của run, kiểu dáng đã được giải quyết, hộp đã đo, và đối với anchor, Href nó trỏ đến

Bố cục các run từ trái sang phải

Định vị là nơi văn bản phong phú ngừng là vấn đề phân tích và trở thành vấn đề sắp chữ. Các run chia sẻ một dòng, vì vậy mỗi run bắt đầu nơi run trước kết thúc. Không có markup nào ghi lại các vị trí đó; chúng phải được đo. Thủ tục LayoutRichText nội bộ của engine đo mỗi run với cùng số liệu font sẽ sau này vẽ nó, sau đó đặt offset ngang của run bằng tổng của tất cả độ rộng run trước đó. Run một bắt đầu tại gốc hộp draw, run hai bắt đầu tại độ rộng của run một, run ba tại độ rộng kết hợp của hai run đầu, v.v. qua dòng

Đây là lý do tại sao sự nhất quán font đo lường quan trọng đến vậy. Lượt bố cục đo các advance; một lượt render riêng vẽ các glyph. Nếu hai lượt đó không đồng ý về font, các hộp mà bố cục tính toán sẽ không nằm dưới các glyph mà renderer vẽ. HotPDF giữ chúng đồng bộ bằng cách ánh xạ kiểu dáng đã giải quyết của mỗi run lên một đặc tả font, qua helper nội bộ RunStyleToFontSpec, khớp với các mặc định của renderer là Arial ở 10 điểm. Advance đo được và văn bản vẽ khi đó đồng ý, và hộp tính toán của run 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ừ run anchor đến chú thích Link PDF

Một siêu liên kết trong PDF đã hoàn thành 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 chú thích Link, được mô tả trong ISO 32000-1 §12.5.6.5. Chú thích có một /Rect xác định hình chữ nhật có thể nhấp trên trang và một hành động kích hoạt khi hình chữ nhật được nhấp. Đối với liên kết bên ngoài, hành động là hành động URI: /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; chú thích là vùng nóng vô hình đặ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 run mang Href, HotPDF đầu tiên vẽ văn bản có kiểu dáng, sau đó xây dựng một chú thích Link trên hộp của run. Điểm vào công khai cho chú thích đó là phương thức trang AddURILink, tạo đối tượng /Type /Annot /Subtype /Link với hành động /URI và trả về từ điển chú thích. Hình chữ nhật của nó là hộp đo của run, được dịch từ tọa độ cục bộ của phần tử draw sang tọa độ trang. Kết quả là liên kết đất chính xác trên văn bản anchor 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 hit phải đến từ chiều rộng đo được

Người ta dễ tưởng tượng việc xác định vị trí liên kết bằng cách tìm kiếm trang để tìm văn bản hiển thị của nó và vẽ hình chữ nhật xung quanh những gì tìm thấy. Điều đó không hoạt động, và lý do là cơ bản đối với cách văn bản được làm phẳng được lưu trữ. Các run có kiểu dáng được vẽ bằng các font subset được nhúng. Một font subset đánh số lại các glyph nó giữ, vì vậy content stream trang giữ các mã CID thập lục phân, không phải mã ký tự gốc. 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 dưới dạng văn bản. Tìm kiếm caption của anchor không tìm thấy gì, vì caption đó không tồn tại dưới dạng văn bản literal ở bất kỳ đâu trong stream

Anchor đáng tin cậy duy nhất cho hình chữ nhật là hình học mà lượt bố cục đã tạo ra. Offset và chiều rộng đo được của mỗi run được tính toán trong khi chạy 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ẽ vật lý xuất hiện. HotPDF do đó lấy hình chữ nhật liên kết thẳng từ hộp đặt xuống của run thay vì từ bất kỳ tra cứu văn bản nào. Vì phép đo sử dụng font render, hộp đúng bất kể subsetting. Hình học tồn tại sau mã hóa; văn bản thì không. Đó là toàn bộ lập luận cho định vị chiều rộng đo được, và đó là lý do tại sao một bộ làm phẳng cố gắng trang bị lại liên kết bằng tìm kiếm văn bản tạo ra các vùng hit bị trôi hoặc biến mất

Điều khiển làm phẳng từ code của bạn

Đối với PDF đã chứa một gói XFA, điểm vào 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 form: truyền True để giữ chúng là widget AcroForm có thể điền, hoặc False để đánh dấu mọi widget chỉ đọc để đầu ra là một bản ghi đóng băng. Các khối draw văn bản phong phú, với các run có kiểu dáng và chú thích liên kết, được tạo ra trong cả hai trường hợp. Hàm trả về số lượng widget nó đã phát

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;

Luôn đọc XFAFlattenWarnings sau lệnh gọi. Danh sách được xóa ở đầu mỗi lần làm phẳng và tích lũy một dòng cho mỗi phần tử mà engine từ chối không render: một loại trường không được hỗ trợ, một hình ảnh draw không giải mã được, một khối exData không có span có thể sử dụng. Không có gì trong số đó gây ra ngoại lệ, vì vậy một danh sách cảnh báo trống là bằng chứng rằng mọi thứ đã ánh xạ, và một danh sách không trống cho bạn biết chính xác bản gốc nào cần kiểm tra. Khi bạn giữ XFA thô dưới dạng byte XDP thay vì PDF đã tải, phương thức anh em ApplyXFAAsAcroForm nhận các byte đó trực tiếp và chia sẻ cùng đường dẫn code và cùng hành vi cảnh báo. Phương thức bổ sung AddXFAPacket đi theo hướng ngược lại, nhúng một gói XFA vào tài liệu bạn đang xây dựng

Xác nhận kết quả trong trình đọc

Mở tệp được 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 thứ. Đầu tiên, văn bản phong phú được hiển thị với kiểu dáng nguyên vẹn: các run đậm là đậm, các run có màu mang màu của chúng, và các span ngồi đúng thứ tự trên dòng thay vì chồng lên nhau hoặc chạy ra ngoài hộp. Thứ hai, các siêu liên kết đang hoạt động. Di chuột qua một anchor và thanh trạng thái sẽ hiển thị địa chỉ đích; nhấp vào nó và hành động URI sẽ mở nó. Sử dụng trình kiểm tra chú thích của trình xem để xác nhận mỗi cái là một chú thích /Link thực sự mà /Rect của nó ôm sát văn bản anchor, nằm trên nội dung hiện là các glyph được vẽ thuần túy thay vì XFA được render bởi form. Sự kết hợp đó, văn bản tĩnh có kiểu dáng cộng với các chú thích Link thực sự trên các hình chữ nhật đúng, là thứ làm cho tài liệu được làm phẳng tồn tại lâu hơn các engine XFA mà nó không còn cần đến nữa

Làm phẳng các trường bản thân, các hộp văn bản, hộp kiểm, và danh sách lựa chọn bao quanh văn bản phong phú này, được đề cập trong hướng dẫn của chúng tôi về làm phẳng form XFA thành widget AcroForm. Đối với câu chuyện rộng hơn về xây dựng và đặt chú thích Link bằng tay, ngoài những cái mà đường dẫn làm phẳng tạo ra, xem làm việc với chú thích PDF trong HotPDF. Cả hai đều xây dựng trên cùng mô hình chú thích và form đi kèm với HotPDF Component cho Delphi và C++Builder