Bài viết kỹ thuật

Tự động hóa kiểm tra Preflight PDF trong Delphi bằng HotPDF

Tệp mở gọn gàng trên máy của bạn. Acrobat hiển thị nó, bản xem trước khi in trông đúng, mọi trang đều có mặt. Rồi nó đi đến xưởng in, hoặc vào hệ thống lưu trữ tiếp nhận lô hàng tháng của bạn, và nó quay lại bị từ chối: ảnh RGB trong một công việc CMYK, không có khóa /Trapped, một output intent không khớp với máy in. Không có gì sai với tài liệu mà bất kỳ ai có thể nhìn thấy. Nó sai so với một hồ sơ, và hồ sơ đó được kiểm tra ở đâu đó bạn không có mặt. Preflight là tên gọi trong ngành in trước cho phép kiểm tra đó, và câu hỏi thực sự là nó thuộc về đâu khi các tệp PDF ra đời từ chính mã Delphi của bạn thay vì từ máy tính để bàn của một nhà thiết kế

HotPDF không cho bạn một hàm preflight để gọi. Thành phần này mang theo một cửa sổ báo cáo preflight trong bản demo GUI của nó, nhưng không có API nào đứng sau nó mà một dịch vụ hoặc một script build có thể gọi, và giả vờ như không phải vậy sẽ khiến bạn đi tìm một phương thức không tồn tại. Điều đó nghe như một lỗ hổng cho đến khi bạn nhận ra rằng đối với các tệp bạn tự tạo ra, gọi một trình xác thực trên đầu ra của chính bạn dù sao cũng là hình dạng sai. Bạn đã kiểm soát mọi thuộc tính mà một trình xác thực sẽ kiểm tra rồi. Cách tách vấn đề hữu ích là làm cho bộ tạo tài liệu không có khả năng phát ra một tệp lỗi, rồi chứng minh điều đó bằng một công cụ bạn không viết ra

Sơ đồ pipeline preflight Delphi, nơi các thiết lập tuân thủ HotPDF ngăn PDF xấu ngay trong lúc sinh và veraPDF cùng Acrobat Preflight chứng minh kết quả từ bên ngoài
Phòng phạm nướng các quy tắc PDF/A và PDF/X ngay vào khâu sinh HotPDF, trong khi các validator bên ngoài cung cấp phán quyết mà bộ sinh không thể tự chấm cho mình

Tại sao bạn kiểm tra đầu ra của chính mình khác đi

Preflight truyền thống giả định một tệp của người lạ. Một nhà thiết kế nào đó, một ứng dụng khác nào đó, một chuỗi chỉnh sửa không rõ nào đó đã tạo ra nó, và bạn kiểm tra nó vì bạn không biết bên trong có gì. Một tài liệu do mã của bạn tạo ra không phải là người lạ. Việc nhúng phông chữ, không gian màu, output intent, khối siêu dữ liệu: chương trình của bạn đã quyết định tất cả những điều đó vài mili giây trước khi tệp chạm đĩa. Kiểm tra nó sau đó để khám phá ra những lựa chọn bạn vừa đưa ra là việc làm vô ích. Bước đi rẻ hơn là ràng buộc những lựa chọn đó để một tệp không tuân thủ không bao giờ tồn tại để bị bắt

Cũng có một lý do về độ tin cậy để giữ việc xác minh ở bên ngoài. Một thư viện tự chấm điểm cho đầu ra của chính nó là đang tự chấm bài thi của chính mình. Khi hệ thống lưu trữ của khách hàng hoặc bộ RIP của một xưởng in từ chối tệp của bạn, "thành phần của chúng tôi nói nó ổn" không có trọng lượng nào cả. Một phán quyết từ veraPDF hoặc Acrobat thì có, vì bên kia chạy cùng những công cụ đó

Biến sự tuân thủ thành một thiết lập, không phải một danh sách kiểm tra

Lớp phòng ngừa chỉ đơn giản là cấu hình. Đặt PDFACompliance hoặc PDFXCompliance trước BeginDoc và HotPDF giữ các quy tắc tương ứng cho toàn bộ lượt tạo tài liệu: nó nhúng phông chữ, theo dõi việc sử dụng DeviceRGB và DeviceCMYK dựa trên output intent bạn đã khai báo, và từ chối các tính năng mà hồ sơ cấm. Các mâu thuẫn lộ ra tại EndDoc, nơi các cổng tuân thủ báo lỗi thay vì âm thầm phát hành thứ gì đó sẽ thất bại ở hạ nguồn. Một khi tệp đã được lưu, cùng các thuộc tính đó đọc lại những gì thực sự đã được thi hành, đây chính là sự thật duy nhất mà nhật ký pipeline của bạn cần nhất:

// Sau EndDoc: ghi lại các hồ sơ đã thi hành cùng siêu dữ liệu lượt chạy
if Pdf.PDFACompliance <> '' then
  Log('Generated as PDF/A level ' + Pdf.PDFACompliance);
if Pdf.PDFXCompliance <> '' then
  Log('Generated as PDF/X profile ' + Pdf.PDFXCompliance);

Đặt các cờ đó trên cùng một dòng nhật ký với hash dữ liệu đầu vào và phiên bản HotPDF. Ngày mà một trình xác thực và bộ tạo tài liệu của bạn bất đồng về một tệp, dòng đó cho bạn biết mẫu nào đã tạo ra nó và bản build nào của thư viện đã được tải, và cuộc tranh cãi mà nếu không sẽ ngốn cả một buổi chiều trở thành một lần grep. Các output intent, hồ sơ ICC, và việc gắn thẻ đứng sau các cờ này được trình bày chi tiết trong hướng dẫn về đầu ra PDF/A, PDF/X, và PDF/UA với HotPDF

Một cổng đầu tiên rẻ tiền cho các tệp bạn không tạo ra

Không phải mọi pipeline đều thuần túy sinh ra tài liệu. Khách hàng tải PDF lên, máy quét thả chúng vào một thư mục, đối tác đính kèm chúng vào email. Đẩy mỗi tệp trong số đó qua một trình xác thực cấu trúc đầy đủ lãng phí thời gian hàng đợi cho các tệp thậm chí sẽ không mở được. Direct File API của HotPDF đọc đủ cấu trúc của một tệp để trả lời "đây có phải là một PDF dùng được hay không" mà không cần tải toàn bộ cây đối tượng, điều này khiến nó trở thành một nơi tốt để thất bại nhanh:

function TriagePdf(Pdf: THotPDF; const FileName: string): Boolean;
var
  Handle, Pages: Integer;
begin
  Result := False;
  Handle := Pdf.DAOpenFileReadOnly(FileName, '');
  if Handle <= 0 then
    Exit;  // không đọc được về mặt cấu trúc: cách ly, không xác thực
  try
    Pages := Pdf.DAGetPageCount(Handle);
    Result := Pages > 0;
  finally
    Pdf.DACloseFile(Handle);
  end;
end;

Hai sự thật về API này quyết định cách bạn bọc nó. Đường tắt bộ nhớ phẳng chỉ giữ vững đối với đầu vào không mã hóa; đưa cho DAOpenFileReadOnly một mật khẩu và nó âm thầm quay về một lần phân tích cú pháp đầy đủ, nên một tệp bạn biết là đã mã hóa nên đi qua DecryptFile vào một bản sao làm việc thuần túy trước khi phân loại. Và DAGetPageCount không có ý nghĩa gì trên một handle không mở gọn gàng, nên việc kiểm tra handle vẫn nghiêm ngặt và một kết quả không dương là một lần từ chối, không phải một lần thử lại. Nhiều mẫu hình như thế này nằm trong bài viết về Direct File API cho các quy trình PDF lớn

veraPDF, chạy như một phần của bản build

Đối với bất cứ thứ gì bạn tuyên bố là PDF/A hoặc PDF/UA, veraPDF là trình xác thực để nối vào. Nó chạy không giao diện (headless), nhận một lô, phát ra XML hoặc JSON, và đặt tên cho mỗi lỗi theo điều khoản ISO của nó, nên một lỗi quy tắc theo ISO 19005-1 điều khoản 6.2.2 trỏ thẳng trở lại một thiết lập của bộ tạo tài liệu thay vì để bạn đoán mò. Điều khiển nó từ Delphi chỉ là điều khiển tiến trình đơn thuần:

function RunVeraPdf(const PdfFile, ReportFile: string): Cardinal;
var
  Cmd: string;
  SI: TStartupInfo;
  PI: TProcessInformation;
begin
  Cmd := Format('cmd /c verapdf.bat --format xml "%s" > "%s"',
    [PdfFile, ReportFile]);
  FillChar(SI, SizeOf(SI), 0);
  SI.cb := SizeOf(SI);
  if not CreateProcess(nil, PChar(Cmd), nil, nil, False,
      CREATE_NO_WINDOW, nil, nil, SI, PI) then
    RaiseLastOSError;
  try
    WaitForSingleObject(PI.hProcess, 120000);  // giới hạn thời gian chờ cho mỗi tệp
    GetExitCodeProcess(PI.hProcess, Result);
  finally
    CloseHandle(PI.hThread);
    CloseHandle(PI.hProcess);
  end;
end;

Thời gian chờ đó xứng đáng với công sức bỏ ra. Một tệp lỗi định dạng có thể lái bất kỳ trình phân tích cú pháp nào vào một góc mà nó không bao giờ thoát ra được, và một lần chờ vô thời hạn bên trong một worker hàng đợi kéo cả phần còn lại của hàng đợi xuống theo nó. Hãy giới hạn thời gian chờ, cho một lần hết giờ mã lỗi riêng của nó, và gạt tệp đó sang một bên cho con người xử lý. Khi bạn đọc kết quả, hãy phân tích cú pháp XML để lấy các định danh quy tắc, không phải văn bản dễ đọc cho con người. ID quy tắc sống sót qua các lần nâng cấp trình xác thực; cách diễn đạt của các thông báo thì không, và một mã ổn định là thứ mà một kỹ sư hỗ trợ có thể tìm kiếm trong các ticket cũ

Cách bạn chạy lô này quan trọng ngang với việc mỗi tệp có đạt hay không. Một tiến trình cho mỗi tệp, không phải một tiến trình cho mỗi lô, để một đầu vào độc hại chỉ tốn của bạn thời gian chờ của tệp đó và không gì khác. Giới hạn số lượng tiến trình trình xác thực ở số nhân CPU, vì việc xây dựng báo cáo XML là công việc CPU-bound và việc đăng ký quá mức chỉ gây tranh chấp lãng phí. Và đặt một trần kích thước tại điểm tiếp nhận, vì một cuốn sách đã quét hai gigabyte sẽ chiếm trọn hàng đợi bất kể trình phân tích cú pháp kiên nhẫn đến đâu. Không điều nào trong số đó là preflight theo nghĩa chặt chẽ. Đó là sự khác biệt giữa một cổng sống sót qua khối lượng cuối tháng và một cổng bị tắt đi ngay đêm đầu tiên nó làm nghẽn pipeline lúc 2 giờ sáng

Sơ đồ cổng batch Delphi chạy một tiến trình veraPDF cho mỗi PDF dưới timeout có giới hạn, khai thác ID quy tắc từ XML thay vì thông điệp, và lưu trữ mỗi báo cáo cạnh tệp của nó
Một hàng gác tiếp nhận chặn trần tải hàng đợi, trong khi mỗi tệp một worker veraPDF mới toanh ngăn đầu vào nhiễm độc làm nghẽn bản dựng

PDF/X là nơi điều này không đủ. veraPDF không xác thực nó, nên phép kiểm tra thực sự vẫn là Preflight của Acrobat với hồ sơ ISO 15930 mà máy in của bạn nêu tên. Acrobat cần một con người, nghĩa là lấy mẫu thay vì phủ toàn bộ: tệp đầu tiên từ một mẫu mới, cộng với một lượt rút ngẫu nhiên nhỏ từ mỗi lô, trong khi cổng tự động xử lý mọi thứ có thể xử lý mà không cần con người. Một phép kiểm tra lấy mẫu thực sự chạy tốt hơn một phép tự động hóa hoàn chỉnh mãi mãi dang dở

Một báo cáo bạn vẫn sẽ muốn có sau một năm

Một cổng preflight mang lại lợi ích hai lần. Một lần khi nó chặn một tệp lỗi ngay tại cửa, và lần nữa nhiều thời gian sau đó khi ai đó hỏi tại sao một tệp cụ thể lại được cho qua. Khoảnh khắc thứ hai đó chính là khoảnh khắc nên quyết định định dạng, vì đó là lúc một báo cáo mỏng manh khiến bạn mắc kẹt. Đối với mỗi tệp được kiểm tra, hãy giữ hash đầu vào, các cờ tuân thủ của bộ tạo tài liệu và phiên bản thư viện từ dòng nhật ký ở trên, tên và phiên bản trình xác thực, hồ sơ mà nó được kiểm tra dựa trên, kết quả đạt hay trượt, và các ID quy tắc bị lỗi cùng số trang bất cứ khi nào trình xác thực cung cấp chúng. Lưu báo cáo đó cạnh tệp mà nó mô tả. Đặt nó trong một hệ thống riêng biệt và hệ thống đó sẽ bị ngừng hoạt động trước cả kho lưu trữ mà nó ghi lại

Các trường hợp ngoại lệ cũng cần được ghi lại. Khi một khách hàng khăng khăng muốn gửi một tệp mà cổng không thích, câu trả lời không phải là nới lỏng quy tắc cho tất cả mọi người. Hãy ghi lại ai đã phê duyệt tệp này, trên cơ sở nào, và đến ngày nào, sau đó đính kèm bản miễn trừ đó vào báo cáo của nó. Một bản miễn trừ có tên và ngày hết hạn là một quyết định mà ai đó sở hữu. Một phép kiểm tra bị comment ra "tạm thời" là một sự cố đang chờ ngày của nó

Một thói quen nữa mang lại lợi ích cho chính nó: khi một tệp thất bại, hãy sao chép nó vào một thư mục hồi quy được đặt tên trước khi bất kỳ ai chạm vào nó. Gần như mọi vấn đề preflight đáng để gỡ lỗi đều bắt nguồn từ một đầu vào cụ thể, và các nhóm giữ lại những đầu vào đó sửa lỗi tái diễn trong một giờ thay vì chờ nó xuất hiện lại trong môi trường sản xuất. Các thuộc tính tuân thủ và Direct File API được trình bày ở đây là một phần của HotPDF Delphi Component cho Delphi và C++Builder, tài liệu của nó bao phủ đầy đủ mỗi lệnh gọi