Bài viết kỹ thuật

Tài liệu kỹ thuật PDF/E-1 trong Delphi với PDFlibPas

PDF/E-1 là archival profile cho tài liệu kỹ thuật, và PDFlibPas triển khai nó như một author mode bạn bật bằng SetPDFEMode cộng với một bounded preflight đọc content stream từng operator một. Profile này không phải PDF/A đổi nhãn: nó có identification namespace riêng, yêu cầu lifecycle metadata riêng, và một quy tắc khiến validate content ngặt hơn bất kỳ archival profile nào bạn từng gặp

Sản phẩm bàn giao kỹ thuật là lý do profile này tồn tại. Một bộ bản vẽ phải đọc được và chứng minh được là không bị thay đổi sau hai mươi năm nữa, với lịch sử revision còn nguyên, và với màu sắc mang cùng một ý nghĩa trên plotter ở tòa nhà bên cạnh. Những yêu cầu đó tạo ra một specification mà phần lớn đòi hỏi nằm ngoài page content, trong metadata và color management — cũng chính là chỗ một PDF writer chung chung hay làm sai

Identification riêng, không phải biến thể của PDF/A

Điều cần nắm đầu tiên là identification PDF/E-1 không thể sinh ra bằng cách áp mẫu PDF/A hay PDF/X. Nó dùng một XMP namespace riêng, http://www.aim.org/pdfe/ns/id/, và version value phải xuất hiện ở hai nơi: như một document information entry và như XMP property có namespace đi kèm. Chỉ emit XMP property, hoặc chỉ emit information entry, sẽ cho ra file mang đúng ý đồ nhưng rớt validation

Output intent cũng có hình dạng dứt khoát không kém. PDF/E-1 đòi một embedded ICC profile mang subtype identifier ISO_PDFE1, và profile phải có số component khớp với họ màu device mà document thực sự dùng. Mệnh đề cuối là chỗ các bản triển khai lặng lẽ làm sai, vì nó có nghĩa là intent không thể chọn trước rồi bỏ quên

Vì sao device color cần một vòng quét toàn document?

Vì color space trốn trong các resource dictionary mà một vòng scan cấp page không bao giờ với tới. PDF/E-1 coi DeviceRGB và DeviceCMYK là hai họ loại trừ nhau ở cấp document, nên validate profile đồng nghĩa với việc biết mọi device color space mà bất cứ thứ gì trong file dùng. Một form XObject có resource riêng. Pattern cũng vậy, image cũng vậy. Một tiling pattern nằm trong form XObject nằm trong page là sâu ba tầng, và một validator chỉ check resource cấp đầu của page sẽ cho qua một document dùng cả hai họ

Vì vậy vòng quét register color space trong khi đi qua page, form, image và pattern như một lần duyệt duy nhất, rồi mới quyết định document có nhất quán không và output intent có khớp không. Cùng lý lẽ đó chi phối kiến trúc preflight nói chung: duyệt một phần cho ra false pass, và một false pass trên một phép kiểm tra conformance còn tệ hơn không check, vì nó được ghi lại như bằng chứng

var
  Lib: TPDFlib;
  Diag: WideString;
begin
  Lib := TPDFlib.Create(nil);
  try
    Lib.LoadFromFile('assembly-drawings.pdf');

    if Lib.SetPDFEMode(1) = 0 then
      raise Exception.Create('PDF/E author mode was refused');

    // Author mode giữ lifecycle metadata luôn khớp tại mọi lần save.
    // Hỏi trước khi save xem document có qua nổi cổng của chính nó không
    if not Lib.PDFEReadyForSave then
    begin
      Diag := Lib.GetPDFEDiagnostics;
      Writeln('PDF/E blockers: ', Diag);
      Exit;
    end;

    Lib.SaveToFile('assembly-drawings-pdfe.pdf');
  finally
    Lib.Free;
  end;
end;

Lifecycle metadata là nghĩa vụ tính theo từng lần save

PDF/E-1 đòi nhiều hơn một document identifier. Bộ tối thiểu gồm media management document identifier, một version identifier, một rendition class, creation time, modification time, metadata time và một title. Đó là một bộ từ vựng theo dõi revision, và nó tồn tại vì một sản phẩm bàn giao kỹ thuật được kỳ vọng tái phát hành chứ không viết một lần rồi thôi

Hệ quả với bản triển khai là các field này không thể set lúc tạo document. Nếu modification time được ghi khi bạn bật mode mà document sau đó còn bị sửa, XMP snapshot và trạng thái thật của document đã trôi dạt, và validator so sánh chúng sẽ báo một sự không nhất quán mà chẳng ai chủ ý gây ra. Author mode vì thế đồng bộ các field ngay trước mỗi lần save, để metadata mô tả những byte sắp được ghi chứ không phải những byte tồn tại lúc mode vừa được bật

Đây là nguyên tắc chung cho conformance metadata và đáng nói riêng khỏi PDF/E: metadata dẫn xuất nằm trên save path, không nằm trên edit path. Bất kỳ field nào được tính từ trạng thái document đều phải tính lại đúng khoảnh khắc trạng thái bị đóng băng, nếu không nó là một cache không có invalidation

Sơ đồ PDF/E-1 của PDFlibPas cho vòng quét device color toàn document đi qua các resource dictionary của page, form XObject, tiling pattern và image để thu thập hai họ DeviceRGB và DeviceCMYK trước khi phán xét tính nhất quán, bên cạnh các field lifecycle metadata mà author mode đồng bộ lại ngay trước mỗi lần save để XMP snapshot khớp với những byte sắp được ghi
Tính nhất quán màu chỉ phán xét được sau một lần duyệt chạm tới mọi resource dictionary, và lifecycle metadata dẫn xuất được tính lại đúng lúc trạng thái document bị đóng băng, không phải lúc mode vừa bật

Quy tắc khiến validate content trở nên ngặt

PDF/E-1 không cho phép các operator của compatibility section hấp thụ content lạ. Trong PDF thường, BXEX bao quanh một vùng mà consumer phải bỏ qua những operator mình không nhận ra — đó là lối thoát giúp producer emit các construct mới hơn mà không làm gãy reader cũ. Dưới PDF/E-1 lối thoát đó bị đóng, nên bất kỳ operator nào preflight không nhận ra đều được báo vô điều kiện, kể cả khi nó nằm trong compatibility section

Tác động lên validator là đáng kể. Nó không thể bỏ qua những vùng mình không hiểu, tức là operand parser phải parse thật sự mọi operator trong mọi content stream. Và đó là chỗ các giới hạn vào cuộc. Lần duyệt bị chặn ở 128 tầng nesting, một triệu object và 64 MiB content, và những con số đó không phải tinh chỉnh hiệu năng. Một file độc hại hay đơn giản là hỏng có thể trình ra một object graph có cycle hoặc độ sâu nesting đủ biến một validator đệ quy thành stack overflow, và các giới hạn là thứ giữ cho một vòng validate không trở thành denial-of-service vector. Tư thế phòng thủ tương tự được mô tả trong parse PDF không đáng tin một cách an toàn

// Validate standalone một file không phải do bạn sinh ra, không cần
// load nó vào một document instance
var
  Issues: TStringList;
  Stream: TFileStream;
  I: Integer;
begin
  Issues := TStringList.Create;
  Stream := TFileStream.Create('incoming.pdf', fmOpenRead or fmShareDenyWrite);
  try
    if CheckCompliancePDFE(Stream, '', 0, Issues) = 0 then
      for I := 0 to Issues.Count - 1 do
        Writeln('PDF/E: ', Issues[I]);
  finally
    Stream.Free;
    Issues.Free;
  end;
end;

Cổng save sửa được gì và từ chối gì

Cổng chia công việc thành hai giai đoạn, và chính sự chia đó là một ý tưởng thiết kế dùng được. Trước tiên nó normalize những thứ sửa an toàn được: print flag của annotation, các no-zoom và no-rotate flag trên text annotation, và appearance-generation flag trên form dictionary. Đây là các setting chỉ có đúng một giá trị dưới profile và không mang thông tin gì, nên sửa lặng lẽ là đúng, còn từ chối vì chúng thì quá sách vở

Sau đó nó check các ràng buộc không thể sửa mà không đổi ý nghĩa document: version, identification, encryption, output intent, tính nhất quán device color và sự hiện diện của dynamic form content. Document rớt bất kỳ mục nào sẽ bị từ chối, vì tự bịa một output intent hay chọn giúp một họ màu sẽ cho ra file qua được validation nhưng diễn giải sai content

Sơ đồ cổng save PDF/E-1 của PDFlibPas cho Delphi, cho thấy bounded preflight quét mọi operator trong mọi content stream dưới giới hạn 128 tầng nesting, một triệu object và 64 MiB, sửa lặng lẽ các flag print, zoom và rotate của annotation, từ chối version, identification, encryption, output intent, device color hay dynamic form content sai, và báo blocker qua GetPDFEDiagnostics
Cổng chỉ sửa lặng lẽ những thứ không mang thông tin, từ chối mọi ràng buộc mà một lời sửa sẽ làm méo, và biến lời từ chối thành danh sách blocker qua GetPDFEDiagnostics trước khi bất kỳ byte nào kịp ghi ra đĩa

Đọc diagnostics lại qua GetPDFEDiagnostics trước khi save biến lời từ chối đó thành một danh sách hành động được thay vì một operation thất bại. Trong batch pipeline, gọi nó cho từng document, log blocker theo từng file, và chuyển các case rớt vào một queue cho người xem. Hữu ích hơn hẳn một lần save raise exception, vì blocker thường cụm lại: bốn mươi document rớt vì cùng thiếu một output intent là một cái fix, không phải bốn mươi

Chọn giữa các archival profile

PDF/E-1 là đích đúng khi sản phẩm bàn giao là tài liệu kỹ thuật có vòng đời revision, và cụ thể là khi tính nhất quán device color có tầm trọng vì đầu ra đi tới plotter và máy in khổ lớn. PDF/A là đích đúng khi mục tiêu là khả năng đọc lâu dài của document nói chung, và đây là profile có lượng validator hỗ trợ rộng nhất. Hai bên không thay thế được nhau, và một document có thể đạt cái này mà rớt cái kia

Sơ đồ quyết định của PDFlibPas so sánh hai archival profile PDF/E-1 và PDF/A cho Delphi: PDF/E-1 cho sản phẩm bàn giao kỹ thuật có vòng đời revision, màu plotter và validation theo hợp đồng dưới XMP namespace riêng với output intent ISO_PDFE1; PDF/A cho khả năng đọc lâu dài nói chung với lượng validator hỗ trợ rộng nhất
Hãy bắt đầu từ việc ai sẽ validate file ở đầu xa: các profile đòi hỏi guarantees khác nhau về identification, metadata và màu, và một document có thể đạt cái này mà rớt cái kia

Nếu bạn đang chọn, hãy bắt đầu từ việc ai validate file ở đầu xa. Công cụ validate PDF/A có ở khắp nơi, và phần preflight tương ứng trong PDFlibPas được mô tả trong preflight PDF/A và PDF/UA. Validate PDF/E chuyên biệt hơn và thường là yêu cầu ghi trong hợp đồng chứ không phải mặc định. Khi một archive có sẵn phải được đưa lên một profile mà nó chưa từng được viết cho, metadata repair path trong convert sang PDF/A kèm metadata repair là pattern cần theo, và hình dạng ở đây y hệt: xác định, sửa những gì an toàn, từ chối phần còn lại kèm danh sách

Author mode, bounded content preflight và standalone compliance check đều đi kèm PDFlibPas Delphi PDF library, nên một document có thể được sản xuất dưới profile rồi được kiểm chứng độc lập sau đó qua một code path riêng — bố cục duy nhất đáng tin cho một lời tuyên bố conformance