PDFlibPas gắn một embedded file vào đúng một page cụ thể thay vì vào document như một tổng thể, bằng cách ghi một mảng /AF vào page dictionary trong khi payload vẫn được register trong EmbeddedFiles name tree của document. Chính sự tách bạch đó là điều ISO 32000-2 §14.13 mô tả, và cũng là điều giúp reader trả lời được câu hỏi mà attachment cấp document trả lời không nổi: dữ liệu này thuộc về page nào
Use case ở đây cụ thể hơn attachment kiểu chung chung. Báo cáo khảo sát mà mỗi page mang theo series đo thô đứng sau chart của nó. Một lô scan mà mỗi page giữ kết quả OCR sinh ra text layer của page đó. Một bộ bản vẽ mà mỗi sheet mang bản extract CAD được render ra. Trong mọi trường hợp, một danh sách attachment cấp document sẽ là đống file với tên file giấu số page — đó là quy ước, không phải structure
Một payload, hai nơi tham chiếu tới nó
Điểm structural quan trọng là association cấp page không tạo ra bản copy thứ hai của bất cứ thứ gì. File được embed đúng một lần và register trong EmbeddedFiles name tree y như một attachment cấp document, dùng cùng cơ chế file specification. Khác nằm ở chỗ reference cùng relationship key của nó được ghi vào đâu: vào page dictionary thay vì document catalog
Hai hệ quả theo sau. Thứ nhất, một reader chỉ biết attachment cấp document vẫn tìm thấy payload, vì nó nằm ngay trong name tree mà loại reader đó tìm tới. Thứ hai, xóa association cấp page chỉ gỡ binding, không xóa file. ClearPageAssociatedFiles gỡ page khỏi các associated file và giữ payload vẫn với tới được qua name tree — đó là hành vi bảo thủ: một operation có tên là xóa association không nên âm thầm phá hủy dữ liệu mà một phần khác của document có thể đang tham chiếu
Hàm đó có một điều kiện thành công cố ý hẹp, đáng nhớ: nó báo thành công chỉ khi page quả thật mang key /AF. Một page chưa từng có association trả về failure thay vì một lời xác nhận vui vẻ, nên caller không thể lầm một no-op với một lần dọn dẹp hoàn tất
var
Lib: TPDFlib;
Idx, I: Integer;
begin
Lib := TPDFlib.Create(nil);
try
Lib.LoadFromFile('survey-report.pdf');
// Gắn measurement series sinh ra chart ở page 3
Idx := Lib.AddPageAssociatedFileFromFile(3,
'series-03.csv', // file trên đĩa
'measurements.csv', // display name bên trong PDF
'text/csv', // MIME type
'Raw measurement series for figure 3',
'Data'); // AFRelationship, ISO 32000-2 14.13
if Idx < 0 then
raise Exception.Create('page association refused');
for I := 0 to Lib.GetPageAssociatedFileCount(3) - 1 do
Writeln('page 3 associated file, embedded index ',
Lib.GetPageAssociatedFileEmbeddedIndex(3, I));
Lib.SaveToFile('survey-report-with-data.pdf');
finally
Lib.Free;
end;
end;
Relationship string trong thực tế không phải free text. ISO 32000-2 định nghĩa một bộ từ vựng: Source, Data, Alternative, Supplement, EncryptedPayload, FormData, Schema và Unspecified, và các consumer bám vào nó. Data cho những con số đứng sau chart, Source cho document sinh ra page, Alternative cho một cách biểu diễn tương đương. Chọn từ bộ từ vựng ấy ngay cả khi chưa gì trong pipeline của bạn đọc nó, vì công cụ kế tiếp trong chuỗi có thể đọc
Vì sao cùng một lookup lại cần FollowRef theo cả hai hướng?
Vì follow reference trả lời hai câu hỏi khác nhau, và code phải biết mình đang hỏi câu nào. Một key lookup có follow indirect reference sẽ trả về object mà reference chỉ tới. Lookup không follow trả về chính reference đó. Cả hai đều đúng, và dùng nhầm cái cho ra một hành vi sai âm thầm thay vì một error
Đọc một associated file minh họa chiều thứ nhất. Để lấy object number của embedded stream đứng sau các key /EF và /F của file specification, lookup phải không follow, vì follow sẽ resolve reference thành stream object và object number mất luôn. Quy tắc này tổng quát được: bất kỳ code path nào cần object identity thay vì object content đều phải lấy raw reference
Optional content cho thấy chiều ngược lại, và nó tốn công tìm hơn. Optional content properties dictionary được ghi vào catalog dưới dạng indirect object, nên code đọc lại mà không follow sẽ nhận được một reference thay vì một dictionary. Type check trên giá trị đó fail, và nhánh fallback tự nhiên — chưa có cấu hình thì tạo mới — chạy và ghi đè lên cấu hình vốn đã có sẵn ở đó. Không có gì raise cả. Các layer mô tả trong optional content group và layer đơn giản là mất trạng thái visibility mặc định
Bài học vượt ra ngoài cả hai trường hợp. Khi một lookup có thể trả về reference hoặc object, một type check trơn không phải là error handling: nó là một nhánh rồi sẽ bị rẽ vào vì lý do sai. Hãy quyết định tường minh từng call site cần gì, và ưu tiên public API trả lời thẳng câu hỏi — chẳng hạn một property đếm optional content — thay vì với tay vào một protected accessor để lấy catalog dictionary
// Attachment cấp document và association cấp page tồn tại song song. Một
// embedded file vẫn có thể được đánh dấu associated ở cấp document
if Lib.IsEmbeddedFileAssociated(0) = 0 then
Lib.SetEmbeddedFileAssociated(0, 1, 'Supplement');
Writeln('document associated files: ', Lib.GetAssociatedFileCount);
Writeln('page 3 associated files : ',
Lib.GetPageAssociatedFileCount(3));
// Clear chỉ gỡ page binding; payload vẫn nằm lại trong name tree
if Lib.ClearPageAssociatedFiles(3) > 0 then
Writeln('page 3 associations removed, payloads still reachable');
Conformance mode tác động thế nào tới attachment
Các archival profile giới hạn cái gì được phép embed, và giới hạn đó được thi hành ngay ở entry point chứ không phải lúc save. PDF/A-1 cấm hẳn embedded file, PDF/A-2 chỉ cho phép embed document PDF/A, còn PDF/A-3 là profile mở cửa embedding cho mọi loại file tùy ý — cũng chính là lý do các hybrid invoice format được xây trên nó
PDFlibPas từ chối attachment ngay khi conformance mode đang bật không cho phép, ngay tại lời gọi chứ không phải hàng trăm operation sau đó trong lúc output. Đó là lựa chọn có chủ ý về chỗ error rẻ nhất để xử lý: một lời từ chối ở call site chỉ đích danh file bạn đang thêm, còn từ chối lúc save chỉ đích danh một document và để bạn tự mò xem trong bốn mươi attachment thì cái nào gây ra
Đó cũng là lý do associated file xuất hiện dày như vậy trong electronic invoicing. Một hybrid invoice là một PDF con người đọc kèm một XML payload machine-readable được gắn vào và đánh dấu đúng relationship, và cả container profile lẫn relationship key đều là một phần của specification chứ không phải quy ước. Cách dựng đó được nói trong xây hybrid invoice Factur-X và ZUGFeRD, phần metadata thì nằm ở XMP extension schema của PDF/A-3
Khi nào association nên theo page thay vì theo document?
Là khi một consumer cần biết dữ liệu thuộc page nào — và chỉ khi đó. Attachment cấp document đơn giản hơn, được viewer hỗ trợ rộng hơn, và là đủ mọi khi payload mô tả cả document: một invoice XML, một signature manifest, một source archive. Hãy với tới association cấp page khi payload quả thật scope theo page và danh tính page là một phần của ý nghĩa nó
Hỗ trợ là ràng buộc thực tế. Associated file cấp page là một construct của PDF 2.0, và mức hỗ trợ của viewer mỏng hơn so với attachment cấp document. Vì payload nằm trong name tree theo cả hai cách, một viewer bỏ qua /AF trên page vẫn hiện file trong attachment list, nên sự suy giảm khá êm. Nhưng nếu page binding là bắt buộc với consumer của bạn chứ không chỉ là metadata hữu ích, hãy kiểm chứng reader mà bạn thật sự target thay vì giả định
Associated file cấp page, attachment cấp document cùng các cổng archival profile chi phối cả hai đều có trong PDFlibPas Delphi PDF library. Nếu bạn còn phải sửa các file cũ trên đường vào, phần metadata và conformance trong convert sang PDF/A kèm metadata repair chính là thứ quyết định trước tiên xem bạn có được đi route attachment nào trong số này