Bài viết Kỹ thuật

Tự động Tính toán Biểu mẫu PDF trong Delphi với HotPDF

Một tệp PDF hóa đơn hoặc chi phí trong đó các mục hàng tự tính tổng và ngày tháng tự định dạng cần ba thứ được liên kết với AcroForm của nó, và HotPDF bổ sung cả ba thứ đó vào một tệp đã tải trong Delphi: ApplyLoadedFieldCalculation cho các tổng số AFSimple_Calculate, ApplyLoadedFieldValidation cho các trình định dạng AF và xác thực bổ trợ của Acrobat, và EnsureLoadedFieldAppearanceStream để nhúng sẵn (bake) giá trị mà trình xem thực sự vẽ ra

HotPDF là một thành phần VCL PDF gốc dành cho Delphi và C++Builder, và ba phương thức này hoạt động trên một tài liệu bạn đã mở bằng LoadFromFile thay vì một tài liệu bạn đang xây dựng từ đầu. Mỗi phương thức nhận một chỉ mục trường bắt đầu từ 0 vào mảng /Fields của AcroForm — cùng thứ tự mà GetLoadedFormFieldNames trả về — và ghi trực tiếp vào từ điển trường, do đó toàn bộ công việc là tải, định chỉ mục, đính kèm, SaveLoadedDocument. Việc tự xây dựng bộ trường là một tác vụ riêng biệt, được đề cập trong việc thêm các trường AcroForm vào một tệp PDF đã tải trong Delphi, trong khi trang này tập trung vào việc cung cấp các phép toán và định dạng cho một bộ trường sẵn có

Tại sao một biểu mẫu PDF cần các kịch bản để tự tính tổng các trường của nó?

Bởi vì tệp PDF không có lớp bảng tính. Một trường văn bản chứa một chuỗi ký tự chứ không phải công thức, và không có gì trong tệp tự động tính toán lại tổng số khi một mục hàng thay đổi trừ khi một kịch bản được đính kèm để làm việc đó. Cơ chế này là từ điển hành động bổ sung của trường, /AA (ISO 32000-1 §12.7.5), ánh xạ các sự kiện kích hoạt đến các hành động: một sự kiện gõ phím kích hoạt (K) khi các ký tự xuất hiện, một sự kiện định dạng kích hoạt để hiển thị, một sự kiện xác thực kích hoạt (V) khi xác nhận ghi, và một sự kiện tính toán kích hoạt (C) hoạt động bất cứ khi nào có bất kỳ trường nào trên biểu mẫu thay đổi. Mỗi hành động là một hành động JavaScript, /S /JavaScript (ISO 32000-1 §12.6.4.14), và kịch bản nó chạy là thứ thực sự thực hiện phép cộng hoặc định dạng lại. Acrobat cung cấp một thư viện các hàm bổ trợ sẵn có — AFSimple_Calculate, AFNumber_Format, AFDate_FormatEx và phần còn lại — vì vậy kịch bản được đính kèm thường là một dòng gọi vào thư viện đó thay vì mã tự viết

Có một chi tiết quyết định liệu một biểu mẫu nhiều bước có đưa ra câu trả lời chính xác hay không: thứ tự tính toán. Acrobat chạy mọi trường có mang hành động tính toán, và nó chạy them chạy chúng theo trình tự được liệt kê trong mảng /CO của AcroForm, chứ không phải theo thứ tự trang. ApplyLoadedFieldCalculation đính kèm hành động tính toán vào trường bạn đặt tên; khi một trường được tính toán này cung cấp dữ liệu cho một trường khác — ví dụ một tổng phụ mà một dòng thuế đọc, sau đó là tổng cộng đọc cả hai — bạn phải đảm bảo thứ tự /CO liệt kê từng đầu vào trước trường tiêu thụ nó, nếu không tổng cộng sẽ đọc một tổng phụ cũ kỹ và bị trễ một nhịp chỉnh sửa phía sau

Làm thế nào để một biểu mẫu PDF tự động tính tổng số?

ApplyLoadedFieldCalculation nhận chỉ mục trường mục tiêu, một phép toán, và tên của các trường nguồn mà nó cần tổng hợp. HotPDF ghi AFSimple_Calculate("SUM", ["lineTotal.1", "lineTotal.2", ...]) vào hành động tính toán của trường mục tiêu, và kiểu liệt kê THPDFFieldCalcOp sẽ chọn hàm: fccSum, fccAverage, fccProduct, fccMinimumfccMaximum ánh xạ tới SUM, AVG, PRD, MIN và MAX của Acrobat. Tên nguồn là tên trường AcroForm, chính là các chuỗi ký tự mà GetLoadedFormFieldNames trả về, do đó một phép tính tổng trên ba mục hàng và một chi phí vận chuyển chỉ là một cuộc gọi duy nhất

var
  Pdf: THotPDF;
  Names: THPDFAnsiStringArray;
  i, TotalIdx: Integer;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.LoadFromFile('invoice.pdf');
    Names := Pdf.GetLoadedFormFieldNames;
    TotalIdx := -1;
    for i := 0 to High(Names) do
      if Names[i] = 'grandTotal' then TotalIdx := i;
    if TotalIdx >= 0 then
    begin
      Pdf.ApplyLoadedFieldCalculation(TotalIdx, fccSum,
        ['lineTotal.1', 'lineTotal.2', 'lineTotal.3', 'shipping']);
      Pdf.EnsureLoadedFieldAppearanceStream(TotalIdx);
    end;
    Pdf.SaveLoadedDocument('invoice-live.pdf');
  finally
    Pdf.Free;
  end;
end;

Lệnh gọi EnsureLoadedFieldAppearanceStream trên trường tính tổng không phải chỉ để làm đẹp. Phép tính toán chạy bên trong trình xem, vì vậy cho đến khi một trình đọc có khả năng chạy kịch bản mở tệp, trường tính tổng sẽ không tự có nội dung kết xuất riêng của nó. Việc nhúng sẵn (baking) một luồng xuất hiện cung cấp cho nó một con số để hiển thị trong thời gian chờ đợi, đây là thứ mà tác vụ in hoặc trình tạo hình thu nhỏ (thumbnail) sẽ lấy khi chúng không bao giờ chạy kịch bản

Định dạng và xác thực các trường bằng bộ công cụ AF bổ trợ

ApplyLoadedFieldValidation đính kèm các kịch bản định dạng, gõ phím và xác thực trong một lệnh gọi duy nhất, và kiểu THPDFFieldValidationKind bạn truyền vào sẽ quyết định công cụ bổ trợ Acrobat nào được liên kết. Truyền fvkCurrency và HotPDF ghi AFNumber_Format(2,0,0,0,"$ ",true) làm kịch bản định dạng với AFNumber_Keystroke tương ứng; fvkNumber sử dụng "." cho đối số dấu phân cách, fvkPercentage phát ra AFPercent_Format, và fvkDate phát ra AFDate_FormatEx("dd/mm/yyyy") trừ khi bạn truyền mặt nạ riêng của mình trong đối số DateFormat. Các định dạng đặc biệt — fvkZipCode, fvkSSNfvkPhone — ánh xạ tới AFSpecial_Format với mã số Acrobat dành riêng cho mỗi loại, và fvkArbitraryMask nhận một chuỗi mặt nạ thông qua đối số Mask cho bất kỳ thứ gì mà các thiết lập sẵn đó không bao phủ. Đối với các kiểu số, HotPDF cũng đính kèm một kịch bản AFRange_Validate để từ chối các đầu vào nằm ngoài phạm vi khi xác nhận ghi

// Format the invoice date as ISO, the amount as currency, and mask a tax id
Pdf.ApplyLoadedFieldValidation(DateIdx,   fvkDate,          'yyyy-mm-dd', '');
Pdf.ApplyLoadedFieldValidation(AmountIdx, fvkCurrency,      '', '');
Pdf.ApplyLoadedFieldValidation(TaxIdIdx,  fvkArbitraryMask, '', '999-99-9999');
Pdf.EnsureLoadedFieldAppearanceStream(AmountIdx);

Tại sao bạn phải tạo lại luồng xuất hiện?

Bởi vì chuỗi giá trị và các điểm ảnh mà trình xem vẽ ra là hai thứ khác nhau trong tệp PDF, và việc تغییر một thứ không làm thay đổi thứ còn lại. Một trường lưu trữ giá trị của nó trong /V, nhưng những gì trình đọc kết xuất lại đến từ luồng xuất hiện của trường, /AP /N. Việc chỉnh sửa /V thông qua biểu đồ đối tượng và một trình xem nghiêm ngặt sẽ vẫn hiển thị giao diện cũ cho đến khi có thứ gì đó tạo lại nó. Từ điển biểu mẫu mang một cờ /NeedAppearances yêu cầu trình xem xây dựng lại mọi giao diện khi mở, và Acrobat tôn trọng điều đó — nhưng các bộ kết xuất nhúng trong trình duyệt, trình đọc di động, máy chủ in và trình tạo hình thu nhỏ thường xuyên bỏ qua nó và vẽ bất kỳ luồng nào đã có sẵn, hoặc không vẽ gì cả. EnsureLoadedFieldAppearanceStream xóa bỏ khoảng cách đó bằng cách tự xây dựng Form XObject /AP /N, từ /DA, /V/Rect của trường, nhờ đó giá trị kết xuất di chuyển cùng tệp dữ liệu thay vì phụ thuộc vào trình xem để vẽ lại nó

Nơi các kịch bản AF dừng hoạt động

Mọi kịch bản AF* đều phụ thuộc vào việc trình xem có công cụ JavaScript hay không, và hầu hết các trình xem không có sẵn công cụ này. Acrobat và một số ít sản phẩm trên máy tính chạy các công cụ bổ trợ này; các trình xem PDF được tích hợp sẵn trong Chrome, Edge, Firefox và các nền tảng di động lớn, cùng với các bộ chuyển đổi raster phía máy chủ và đường ống in ấn, hoàn toàn không chạy bất kỳ JavaScript biểu mẫu nào. Trong các trình đọc đó, phép tính toán không bao giờ kích hoạt và định dạng không bao giờ được áp dụng — trường chỉ đơn giản hiển thị bất kỳ luồng xuất hiện nào được lưu lần cuối cùng, đó chính là lý do tại sao việc nhúng sẵn /AP /N lại quan trọng. Hãy xử lý lớp AF* như một sự tiện lợi dành cho nhóm nhỏ người dùng trên các trình đọc có khả năng chạy kịch bản, chứ không bao giờ coi đó là một sự bảo đảm. Bất kỳ tổng số nào cần phải chính xác trên máy chủ — chẳng hạn số tiền hóa đơn bạn sẽ tính phí, số thuế bạn sẽ nộp — đều phải được tính toán lại phía bạn sau khi biểu mẫu được gửi về, bởi vì bạn không thể giả định rằng trình đọc điền nó đã chạy một dòng kịch bản nào của bạn

Còn một trường hợp nữa đáng được nhắc tên: các biểu mẫu ở định dạng XFA. Một biểu mẫu XFA động mang công cụ tính toán của riêng nó trong một XML payload mà các công cụ bổ trợ AF* không biết gì về nó, vì vậy việc đính kèm AFSimple_Calculate vào một trường XFA sẽ không đạt được kết quả gì. Hãy làm phẳng (flatten) biểu mẫu XFA thành một AcroForm thuần túy trước, như được đề cập trong việc làm phẳng XFA thành AcroForm trong Delphi, và ba phương thức ở đây sau đó sẽ áp dụng các trường AcroForm kết quả chính xác như chúng áp dụng cho bất kỳ biểu mẫu đã tải nào khác

Các AcroForm tự động tính toán và tự định dạng được thu gọn lại thành ba cuộc gọi trên một tài liệu đã tải — đính kèm phép tính toán, đính kèm định dạng và xác thực, nhúng sẵn diện mạo — tiếp nối bởi SaveLoadedDocument. API biểu mẫu đã tải hiển thị ở đây là một phần của HotPDF Component dành cho Delphi và C++Builder, tài liệu giới thiệu đầy đủ các kiểu liệt kê THPDFFieldValidationKindTHPDFFieldCalcOp bên cạnh phần còn lại của bề mặt AcroForm