losLab PDF Library dapat menghasilkan output PDF yang identik byte demi byte untuk input yang identik begitu Anda memanggil SetDeterministicDocumentID(1). Secara default, array trailer /ID adalah digest MD5 dari jam sistem, sehingga dua kali jalan dari generator yang sama akan berbeda setidaknya pada byte-byte itu. Mode deterministik menurunkan /ID dari seed yang stabil, sehingga mengembalikan sifat reproducible build
Gejalanya biasanya muncul di CI sebelum ada yang mencarinya secara sengaja. Template tidak berubah, record input tidak berubah, font tidak berubah, dan PDF yang dihasilkan tetap menghasilkan hash berbeda di setiap jalannya pipeline. Build cache tidak pernah hit. Content addressable storage menumpuk blob baru per build malam. Diff regresi level byte menyala pada file yang tidak ada yang menyentuhnya. Telusuri diff itu sampai ke byte sebenarnya dan hampir selalu itu adalah segelintir digit hex yang sama, duduk di trailer file
Untuk apa array ID trailer itu?
Trailer /ID adalah penanda identitas file, bukan checksum konten. ISO 32000-1 §14.4 mendefinisikannya sebagai array dua byte string: elemen pertama adalah pengenal permanen yang ditetapkan saat dokumen dibuat dan dimaksudkan bertahan lewat setiap penyuntingan berikutnya, dan elemen kedua adalah pengenal berubah yang diperbarui writer setiap kali file dimodifikasi. Bersama-sama keduanya membuat sistem bisa memutuskan apakah dua file adalah revisi dari satu dokumen atau dua dokumen yang tidak berhubungan. §7.5.5 membuat entri ini praktis wajib, karena trailer harus membawa /ID kapan pun juga membawa /Encrypt
Tidak ada di spesifikasi yang menyebutkan cara menghitung nilainya. Rekomendasinya adalah digest dari hal-hal seperti waktu saat itu, path file, ukuran file dan document information dictionary, dan jam sistem adalah bahan yang membuat hasilnya unik. Itu justru properti yang Anda inginkan untuk identitas dan justru properti yang merusak reproducibility, itulah sebabnya ini perlu jadi switch eksplisit, bukan perubahan perilaku diam-diam
Kenapa build yang sama menghasilkan PDF berbeda setiap kali?
Karena pengenal default diturunkan dari momen pembuatannya. Secara historis losLab PDF Library membangun string /ID dari MD5 timestamp saat itu, sehingga dokumen yang dibuat dua kali berselang satu detik membawa dua pengenal permanen berbeda meski setiap byte lain dalam file identik. Biayanya nyata di hilir: sistem build yang mengunci artifact dengan hash tidak pernah bisa menggunakan ulang langkah PDF, object store yang deduplikasi menyimpan satu salinan per build alih-alih satu salinan per dokumen, dan reviewer yang melihat binary diff harus membuktikan bahwa satu-satunya perubahan adalah noise sebelum bisa mempercayai sisa diffnya. Pembuatan /ID deterministik hadir untuk menghilangkan noise tersebut, semangat yang sama dengan pekerjaan stabilitas layout yang dijelaskan pada catatan object stream dan cross reference stream
Beralih ke pengenal yang reproducible
Mode deterministik bersifat opt in, per dokumen, dan mati secara default sehingga output yang sudah ada tidak berubah sampai Anda memintanya. SetDeterministicDocumentID menerima 0 atau 1 dan mengembalikan 1 saat nilai diterima, 0 untuk apa pun di luar itu; GetDeterministicDocumentID melaporkan status saat ini. SetDocumentIDSeed menyediakan string seed eksplisit yang menang atas segalanya, dan memberikan seed kosong mengembalikan ke seed hasil turunan. GetDocumentFileID membaca kembali /ID[0] setelah penyimpanan sehingga Anda bisa mencatatnya atau melakukan assert atasnya
var
Lib: TPDFlib;
FileID: WideString;
begin
Lib := TPDFlib.Create;
try
Lib.SetDeterministicDocumentID(1);
Lib.SetDocumentIDSeed('invoice-4471-rev3');
Lib.SetOrigin(1);
Lib.DrawText(100, 700, 'Invoice 4471');
Lib.SaveToFile('invoice.pdf');
FileID := Lib.GetDocumentFileID; // identical on every run
finally
Lib.Free;
end;
end;
Refresh terjadi saat penyimpanan, bukan saat Anda mengaktifkan flag, sehingga mengaktifkan mode deterministik di akhir pembuatan dokumen tetap berlaku. Itu juga berarti seed yang diubah baru sampai ke file pada penyimpanan penuh berikutnya: atur seed A, simpan, atur seed B, simpan, dan kedua file membawa pengenal berbeda, sementara mengembalikan ke seed A mengembalikan nilai aslinya. Seed eksplisit adalah pilihan tepat kapan pun dokumen Anda punya kunci stabil alami seperti nomor invoice, revisi record atau identifier commit git, karena itu memisahkan pengenal dari metadata insidental
Dari mana seed berasal jika Anda tidak menyediakannya sendiri?
Tanpa seed eksplisit, losLab PDF Library menurunkan satu dari state dokumen yang seharusnya invarian di seluruh regenerasi identik: header versi PDF, jumlah halaman, dan setiap entri di document information dictionary. Nilai string dan name diambil apa adanya, tipe objek lain menyumbangkan bentuk serialisasinya, dan semuanya di-hash menjadi string /ID. Konsekuensi pentingnya adalah CreationDate dan ModDate merupakan bagian dari information dictionary dan karena itu bagian dari seed secara sengaja. Dua kali jalan hanya mendapat pengenal yang sama saat keduanya benar-benar menghasilkan metadata dokumen yang sama
Lib.SetDeterministicDocumentID(1);
// No SetDocumentIDSeed: the seed is derived from document state,
// so the timestamps in the Info dictionary have to be pinned.
Lib.SetInformation(2, 'Quarterly Report'); // Title
Lib.SetInformation(5, 'reporting-service 4.2'); // Creator
Lib.SetInformation(7, 'D:20260101000000Z'); // CreationDate
Lib.SetInformation(8, 'D:20260101000000Z'); // ModDate
Lib.SaveToFile('report.pdf');
Mematok ModDate dengan key 8 punya efek ganda, dan inilah bagian yang sering membuat orang terjebak. /ID deterministik saja tidak membuat file identik byte demi byte, karena jalur penyimpanan membubuhkan ModDate dengan waktu saat itu kecuali pemanggil sudah mengaturnya secara eksplisit. Mengatur key 8 menandai nilai itu sebagai disediakan pemanggil dan menekan pembubuhan tersebut. Jika Anda ingin file yang benar-benar reproducible, bukan hanya pengenal yang reproducible, perlakukan timestamp metadata sebagai input build: turunkan dari record sumber atau dari epoch tetap, jangan pernah dari Now
Kenapa menulis ulang ID merusak PDF terenkripsi?
Karena /ID[0] bukan sekadar metadata pada dokumen terenkripsi, itu adalah bahan kunci. ISO 32000-1 §7.6.3.3 Algorithm 2 memasukkan elemen pertama dari file identifier ke dalam perhitungan kunci enkripsi untuk standard security handler pada revisi 2 hingga 4, bersama password yang di-padding, nilai /O dan bit izin. Kunci turunannya kemudian menghasilkan string validasi /U yang diperiksa reader saat membuka file, dan kunci file diturunkan serta di-cache saat Anda memanggil Encrypt atau saat dokumen terenkripsi dimuat, keduanya terjadi sebelum penyimpanan. Menulis ulang pengenal saat penyimpanan karena itu akan menghasilkan file yang secara struktural valid tapi pemeriksaan /U-nya gagal saat dibuka kembali: bukan korupsi halus, melainkan dokumen yang tidak bisa dibuka siapa pun, termasuk Anda. Itulah kenapa refresh deterministik dibatasi hanya untuk dokumen yang tidak membawa state enkripsi, dan kenapa dokumen terenkripsi mempertahankan /ID apa pun yang sudah dimilikinya, mode deterministik aktif atau tidak, dan pengaturan itu sama sekali tidak berpengaruh pada jalur tersebut. Penanganan revisi terkait dan semantik izin dibahas dalam ulasan audit enkripsi dan izin PDF. Perlu dicatat juga bahwa jalur pemulihan enkripsi hanya me-refresh /ID[1], pengenal perubahan, persis seperti yang dimaksud §14.4
Kenapa incremental save mempertahankan pengenal aslinya
Batasan kedua adalah mode append. Incremental update meninggalkan setiap byte file sebelumnya tidak tersentuh dan menulis revisi baru sesudahnya, dan sifat permanen /ID[0] di seluruh §14.4 adalah yang memberi tahu konsumen bahwa revisi baru itu milik dokumen yang sama dengan yang lama. Menulis ulang itu akan memutus tautan tersebut, bertentangan dengan revisi yang sudah ada dalam file, dan mengganggu semantik tanda tangan, karena tanda tangan mencakup rentang byte dari revisi spesifik dari dokumen spesifik. Karena itu losLab PDF Library me-refresh pengenal deterministik hanya pada penyimpanan penuh dan tidak pernah pada mode append, yang menjaga jaminan yang dijelaskan pada artikel incremental update PDF dan append ke stream tetap utuh
Satu titik penyempit untuk pembuatan pengenal
Seluruh pembuatan /ID di losLab PDF Library kini mengalir melalui satu rutin internal, NewFileIDString, yang membuat switch deterministik ini bisa dipercaya alih-alih sekadar tambalan pada satu jalur kode. Pembuatan dokumen kosong, pembuatan lazy array /ID yang hilang saat dibutuhkan, dan jalur pemulihan fingerprint enkripsi semuanya memanggilnya, sehingga hanya ada satu tempat di mana jam sistem bisa bocor kembali masuk. Ini juga berarti varian di masa depan, seperti pengenal yang diturunkan dari konten, adalah perubahan pada satu fungsi, bukan audit atas seluruh serializer
function BuildQuote(const Seed: WideString): AnsiString;
var
Lib: TPDFlib;
begin
Lib := TPDFlib.Create;
try
Lib.SetDeterministicDocumentID(1);
Lib.SetDocumentIDSeed(Seed);
Lib.SetInformation(7, 'D:20260101000000Z');
Lib.SetInformation(8, 'D:20260101000000Z');
Lib.SetOrigin(1);
Lib.DrawText(100, 700, 'Quote 8812');
Result := Lib.SaveToString;
finally
Lib.Free;
end;
end;
// Regression guard: two independent builds, one byte sequence.
if BuildQuote('quote-8812') = BuildQuote('quote-8812') then
WriteLn('reproducible')
else
WriteLn('nondeterminism leaked into the output');
Pasang perbandingan itu ke dalam test suite Anda sebelum mengandalkan output reproducible di tempat lain, karena ia gagal secara mencolok begitu ada fitur baru yang memasukkan kembali timestamp. Reproducibility adalah properti yang meluruh diam-diam jika tidak, dan satu assertion di atas dua penyimpanan in-memory nyaris tidak berbiaya untuk dijalankan pada setiap build
API pengenal deterministik yang ditunjukkan di sini hadir dalam losLab PDF Library untuk Delphi dan C++Builder, bersama referensi lengkap document information, enkripsi dan incremental save