Artikel Teknis

Associated Files Level Halaman di PDF 2.0 dengan PDFlibPas

PDFlibPas melampirkan file tersemat ke satu halaman tertentu, bukan ke dokumen secara keseluruhan, dengan menulis array /AF ke dictionary halaman sementara payloadnya sendiri tetap terdaftar di name tree EmbeddedFiles dokumen. Pemisahan itulah yang dideskripsikan ISO 32000-2 §14.13, dan itulah yang membuat pembaca bisa menjawab pertanyaan yang tak bisa dijawab lampiran level dokumen: data ini milik halaman mana

Kasus penggunaannya lebih spesifik daripada lampiran umum. Laporan survei yang tiap halamannya membawa seri pengukuran mentah di balik chart-nya. Batch hasil scan yang tiap halamannya menyimpan hasil OCR yang menghasilkan text layer-nya. Set gambar teknik yang tiap lembar membawa ekstrak CAD tempat ia dirender. Di tiap kasus, daftar lampiran level dokumen hanya akan menjadi tumpukan file yang menyandikan nomor halaman di namanya — sebuah konvensi, bukan struktur

Satu payload, dua tempat ia direferensikan

Poin struktural yang penting: asosiasi level halaman tidak menciptakan salinan kedua dari apa pun. Filenya disematkan sekali dan didaftarkan di name tree EmbeddedFiles persis seperti lampiran level dokumen, memakai mesin file specification yang sama. Yang berbeda hanyalah tempat referensi dan key relasinya ditulis: ke dictionary halaman, bukan ke document catalog

Dua konsekuensi mengikuti. Pertama, pembaca yang hanya mengenal lampiran level dokumen tetap menemukan payloadnya, karena payload itu berada di name tree tempat pembaca semacam itu mencari. Kedua, menghapus asosiasi halaman melepas binding, bukan filenya. ClearPageAssociatedFiles melepaskan halaman dari file terkaitnya dan membiarkan payload tetap bisa dijangkau lewat name tree — perilaku konservatif: operasi yang menyebut hapus asosiasi seharusnya tidak diam-diam menghancurkan data yang mungkin direferensikan bagian lain dokumen

Struktur associated file level halaman dalam dokumen PDF 2.0 yang ditulis PDFlibPas: payload disematkan sekali dan didaftarkan di name tree EmbeddedFiles di bawah document catalog, sementara dictionary halaman membawa array /AF yang mereferensikan file specification yang sama dengan key AFRelationship, sehingga ClearPageAssociatedFiles melepas binding tanpa menghancurkan data
Asosiasi level halaman menambah referensi kedua, bukan salinan kedua: pembaca yang hanya mengenal lampiran level dokumen tetap menemukan payloadnya di name tree, dan menghapus binding halaman membiarkan embedded stream tetap terjangkau

Fungsi itu punya satu syarat sukses yang sengaja dibuat sempit dan layak diketahui. Ia melaporkan sukses hanya ketika halaman memang membawa key /AF. Halaman yang tidak pernah punya asosiasi mengembalikan kegagalan alih-alih konfirmasi yang manis, sehingga pemanggil tak bisa salah mengira no-op sebagai pembersihan yang selesai

var
  Lib: TPDFlib;
  Idx, I: Integer;
begin
  Lib := TPDFlib.Create(nil);
  try
    Lib.LoadFromFile('survey-report.pdf');

    // Lampirkan seri pengukuran yang menghasilkan chart di halaman 3
    Idx := Lib.AddPageAssociatedFileFromFile(3,
      'series-03.csv',            // file di disk
      'measurements.csv',         // nama tampilan di dalam PDF
      'text/csv',                 // tipe MIME
      '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;

String relationship bukan teks bebas dalam praktiknya. ISO 32000-2 mendefinisikan sebuah kosakata — Source, Data, Alternative, Supplement, EncryptedPayload, FormData, Schema dan Unspecified — dan consumer mengerjakan logikanya berdasarkan itu. Data untuk angka di balik chart, Source untuk dokumen tempat satu halaman dihasilkan, Alternative untuk representasi yang setara. Pilih dari kosakata itu meski belum ada bagian pipeline Anda yang membacanya, karena tool berikutnya di rantai bisa jadi membaca

Mengapa lookup yang sama butuh FollowRef ke dua arah?

Karena mengikuti referensi menjawab dua pertanyaan yang berbeda, dan kodenya harus tahu sedang bertanya yang mana. Key lookup yang mengikuti indirect reference mengembalikan objek yang ditunjuk referensi itu. Lookup yang tidak mengikuti mengembalikan referensinya sendiri. Keduanya benar, dan memakai yang salah menghasilkan perilaku salah yang senyap, bukan error

Membaca associated file menunjukkan arah pertama. Untuk mendapat nomor objek embedded stream di balik key /EF dan /F milik file specification, lookup harus tidak mengikuti, karena mengikuti berarti referensinya ter-resolve menjadi stream object dan nomor objeknya hilang. Aturannya menggeneralisasi: jalur kode mana pun yang butuh identitas objek alih-alih isi objek harus mengambil referensi mentahnya

Optional content menunjukkan arah sebaliknya, dan yang ini lebih mahal ditemukannya. Dictionary properties optional content ditulis ke catalog sebagai indirect object, jadi kode yang membacanya kembali tanpa mengikuti mendapat referensi, bukan dictionary. Type check atas nilai itu kemudian gagal, dan cabang fallback yang wajar — kalau tidak ada konfigurasi, buat satu — berjalan dan menimpa konfigurasi yang sebenarnya sudah ada di sana. Tidak ada yang raise. Layer-layer yang dibahas di optional content groups dan layer kehilangan visibility state default-nya begitu saja

Pelajarannya menggeneralisasi melampaui kedua kasus itu. Ketika sebuah lookup bisa mengembalikan referensi maupun objek, type check telanjang bukanlah penanganan error: ia adalah cabang yang cepat atau lambat diambil dengan alasan yang salah. Putuskan secara eksplisit apa yang dibutuhkan tiap call site, dan utamakan API publik yang menjawab pertanyaannya langsung — misalnya property hitungan optional content — daripada meraih accessor protected untuk dictionary catalog

Peta keputusan mengikuti referensi pada lookup PDF sebagaimana diimplementasikan di PDFlibPas: membaca /EF dan /F di bawah file specification tidak boleh mengikuti referensi karena nomor objek embedded stream-lah jawabannya, sementara dictionary /OCProperties yang indirect di catalog harus diikuti, atau type check yang gagal diam-diam menimpa konfigurasi optional content yang sudah ada
Lookup yang sama menjawab dua pertanyaan berbeda: identitas butuh referensi mentah, isi butuh objek yang sudah di-resolve, dan type check telanjang sebagai pengganti keputusan itu cepat atau lambat menjalankan cabang yang salah tanpa raise
// Lampiran level dokumen dan asosiasi level halaman hidup berdampingan.
// File tersemat juga bisa ditandai terasosiasi di level dokumen
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 melepaskan binding halaman; payload tetap ada di name tree
if Lib.ClearPageAssociatedFiles(3) > 0 then
  Writeln('page 3 associations removed, payloads still reachable');

Apa yang dilakukan conformance mode pada lampiran

Profil arsip membatasi apa yang boleh disematkan, dan pembatasannya ditegakkan di entry point, bukan saat save. PDF/A-1 melarang embedded file sama sekali, PDF/A-2 hanya mengizinkan dokumen PDF/A tersemat, dan PDF/A-3 adalah profil yang membuka penyematan ke tipe file arbitrer — persis alasan format faktur hybrid dibangun di atasnya

PDFlibPas menolak lampiran itu ketika conformance mode aktif tidak mengizinkannya, di titik panggilan, bukan ratusan operasi kemudian saat output. Itu pilihan yang disengaja soal di mana error paling murah ditangani: penolakan di call site menyebut file yang sedang Anda tambahkan, sementara penolakan saat save hanya menyebut sebuah dokumen dan menyuruh Anda mencari tahu sendiri dari empat puluh lampiran mana yang jadi penyebabnya

Inilah juga alasan associated files begitu sering muncul di electronic invoicing. Faktur hybrid adalah PDF yang dibaca manusia dengan payload XML terbaca mesin dilampirkan dan ditandai relationship yang tepat, dan baik profil kontainernya maupun key relationship-nya adalah bagian dari spesifikasi, bukan konvensi. Konstruksi itu dibahas di membangun faktur hybrid Factur-X dan ZUGFeRD, dengan sisi metadatanya di extension schema XMP PDF/A-3

Kapan asosiasi sebaiknya per halaman, bukan per dokumen?

Ketika consumer perlu tahu data itu milik halaman mana — dan hanya ketika itu. Lampiran level dokumen lebih sederhana, lebih luas didukung penampil, dan cukup selama payloadnya mendeskripsikan seluruh dokumen: XML faktur, manifest tanda tangan, arsip source. Gunakan asosiasi level halaman ketika payloadnya memang berlingkup halaman dan identitas halaman adalah bagian dari maknanya

Dukungan adalah kendala praktisnya. Associated file level halaman adalah konstruk PDF 2.0, dan dukungan penampilnya lebih tipis daripada lampiran level dokumen. Karena payloadnya tetap di name tree di kedua kasus, penampil yang mengabaikan /AF pada halaman tetap menampilkan filenya di daftar lampiran, jadi degradasinya anggun. Tapi kalau binding halaman esensial bagi consumer Anda, bukan sekadar metadata yang berguna, verifikasi pembaca yang benar-benar Anda tuju daripada berasumsi

Associated file level halaman, lampiran level dokumen, dan gate profil arsip yang mengatur keduanya tersedia dalam pustaka PDF Delphi PDFlibPas. Kalau Anda juga memperbaiki file lama di jalur masuk, pekerjaan metadata dan conformance di konversi ke PDF/A dengan perbaikan metadata yang menentukan rute lampiran mana yang tersedia bagi Anda sejak awal