Artikel Teknis

PDF Incremental Updates in Delphi: AppendToStream Guide

Pembaruan inkremental PDF memungkinkan aplikasi Delphi memodifikasi dokumen dengan hanya menambahkan objek yang diubah, membiarkan setiap bita asli tidak tersentuh. losLab PDF Library mengimplementasikan hal ini melalui AppendToStream, yang hanya menulis bagian inkremental yang ditentukan oleh ISO 32000-1 §7.5.6, sehingga pengeditan satu bookmark pada file berukuran 2 GB hanya membutuhkan beberapa kilobita keluaran alih-alih penulisan ulang lengkap. Mekanisme yang sama adalah alasan mengapa dokumen bertanda tangan dapat diperbarui tanpa membatalkan tanda tangannya

Masalah yang diselesaikan oleh hal ini sangat nyata. Penyimpanan penuh menulis ulang seluruh file: setiap objek diserialisasi ulang, setiap offset referensi silang dihitung ulang, dan keluaran tidak memiliki hubungan tingkat bita dengan masukan. Untuk faktur 40 KB, itu tidak masalah. Untuk arsip pindaian 2 GB di mana Anda hanya memperbaiki kesalahan ketik pada judul dokumen, menulis ulang dua gigabita hanya untuk mengubah dua puluh bita adalah hal yang tidak masuk akal — dan jika file tersebut membawa tanda tangan digital, penulisan ulang tersebut baru saja merusaknya

Mengapa menyimpan PDF merusak tanda tangan digitalnya?

Tanda tangan digital PDF tidak menandatangani konten logis dokumen; ini menandatangani rentang bita dari file fisik. Entri /ByteRange dalam kamus tanda tangan mencatat dengan tepat rentang file mana yang dicakup oleh ikhtisar kriptografis. Operasi penyimpanan apa pun yang menyerialisasi ulang bita tersebut — bahkan yang menghasilkan dokumen yang secara semantik identik — akan mengubah ikhtisar tersebut, dan setiap validator akan melaporkan tanda tangan tersebut rusak. Ini memang sengaja dirancang: tanda tangan menyatakan bita yang dilihat penandatangan, bukan beberapa model dokumen abstrak

Pembaruan inkremental adalah celah keluar yang disediakan oleh spesifikasi PDF. Karena penyimpanan inkremental menambahkan data baru setelah %%EOF asli dan tidak pernah menyentuh rentang bita yang ditandatangani, tanda tangan yang ada tetap tervalidasi terhadap bita yang dicakupnya. Validator kemudian mengklasifikasikan perubahan yang ditambahkan secara terpisah — tanda tangan kedua, pengisian formulir, anotasi — dan memutuskan apakah perubahan tersebut diizinkan. Setiap alur kerja multi-tanda tangan bergantung pada ini: setiap penandatangan menambahkan bagian inkremental di atas yang terakhir. Jika Anda membangun alur penandatanganan, artikel pendamping tentang penandatanganan dan validasi PAdES di Delphi membahas secara terperinci bagaimana interaksi rentang bita tanda tangan dan bagian inkremental

Bagaimana pembaruan inkremental bekerja di bawah ISO 32000-1 §7.5.6

ISO 32000-1 §7.5.6 mendefinisikan model ini dalam tiga aturan. Pertama, konten file asli dibiarkan utuh sepenuhnya — tidak ada satu bita pun yang bergeser. Kedua, objek yang diubah dan baru dibuat ditambahkan setelah %%EOF terakhir, masing-masing dengan nomor objek yang sama dengan sebelumnya (objek yang diubah cukup mendapatkan definisi baru yang menutupi definisi lama). Ketiga, bagian referensi silang dan trailer baru ditambahkan; entri /Prev pada trailer mengarah kembali ke offset bita dari bagian referensi silang sebelumnya, membentuk rantai yang ditelusuri pembaca dari yang terbaru ke yang terlama untuk menyelesaikan setiap objek ke definisi terbarunya

Dua sifat berguna dihasilkan dari struktur ini. Pembaruan berbiaya murah sebanding dengan apa yang diubah, bukan dengan ukuran dokumen — biaya penambahan adalah ukuran objek yang dimodifikasi ditambah sedikit overhead xref/trailer. Dan file tersebut menjadi riwayat versinya sendiri: setiap revisi sebelumnya masih ada secara fisik, sehingga auditor dapat memotong file pada %%EOF sebelumnya dan memulihkan dokumen yang persis ada pada titik tersebut. Untuk alur kerja kepatuhan yang harus membuktikan seperti apa dokumen sebelum setiap perubahan, jalur audit bawaan ini sering kali menjadi argumen penentu untuk penyimpanan inkremental

Menulis pembaruan inkremental dengan AppendToStream

losLab PDF Library mengekspos keluaran inkremental melalui AppendToStream(AppendMode: Integer; OutStream: TStream): Integer, yang mengembalikan 1 jika sukses dan 0 jika gagal. Parameter AppendMode memilih apa yang mendarat di aliran target. Mode 0 menulis file lengkap: bita sumber asli disalin ke aliran terlebih dahulu, lalu bagian inkremental ditambahkan. Mode 1 hanya menulis bagian inkremental itu sendiri — delta — dan melewatkan bita sumber sepenuhnya. Mode 2 pertama-tama menulis awalan yang disediakan pemanggil yang didaftarkan melalui SetAppendInputFromString, lalu menambahkan bagian pembaruan di atasnya

var
  Doc: TPDFlib;
  Delta: TMemoryStream;
begin
  Doc := TPDFlib.Create;
  try
    if Doc.LoadFromFile('contract.pdf', '') <= 0 then
      Exit;

    // Pengeditan kecil: jenis perubahan yang seharusnya tidak
    // memicu penulisan ulang seluruh file
    Doc.SetInformation(3, 'Amended 2026-07-04');  // kunci 3 = /Subject

    Delta := TMemoryStream.Create;
    try
      // AppendMode = 1: tulis hanya bagian inkremental.
      // Bita asli + Delta = PDF yang lengkap dan valid.
      if Doc.AppendToStream(1, Delta) = 1 then
        Delta.SaveToFile('contract.delta.bin');
    finally
      Delta.Free;
    end;
  finally
    Doc.Free;
  end;
end;

Mode 1 adalah hal menarik untuk desain sistem. Karena delta bersifat mandiri, Anda dapat mengirimkannya secara independen dari dokumen aslinya: menyimpan revisi sebagai blob terpisah dalam penyimpanan objek, mereplikasi hanya delta ke situs jarak jauh, atau merekonstruksi revisi apa pun dengan menggabungkan file dasar dengan rantai inkrementalnya. Aturan rekonstruksinya adalah penggabungan bita biasa — file asli terlebih dahulu, lalu setiap delta secara berurutan — karena itulah tata letak yang ditentukan §7.5.6 untuk file yang diperbarui secara inkremental

Bagaimana pustaka menghitung offset xref tanpa menyalin file asli?

Entri referensi silang di dalam bagian inkremental harus berisi offset bita mutlak — posisi yang diukur dari awal file lengkap, bukan dari awal delta. Hal itu menimbulkan teka-teki untuk mode 1: penulis tidak pernah memancarkan bita asli, namun setiap offset yang dicatatnya harus berpura-pura bahwa bita tersebut ada di sana. losLab PDF Library menyelesaikan hal ini dengan adaptor aliran internal, TPDFAppendSectionStream, yang menyajikan ruang koordinat virtual ke penyerialisasi. Adaptor dibuat dengan panjang bita file asli sebagai offset dasarnya, melaporkan posisi dan ukurannya sebagai dasar tersebut ditambah apa pun yang telah ditambahkan sejauh ini, dan hanya meneruskan bita yang baru ditulis ke aliran target pemanggil

Konsekuensinya adalah mode 1 tidak pernah mewujudkan salinan dokumen sumber — tidak di disk, tidak di memori. Implementasi naif (menulis file lengkap ke penyangga sementara, lalu memotong bagian ekornya) akan membawa salinan transien dari seluruh PDF asli, yang untuk masukan berskala gigabita justru merupakan biaya yang ingin dihindari oleh pembaruan inkremental. Teknik virtualisasi offset ini adalah kerabat dekat dari pergeseran referensi bita yang digunakan di tempat lain dalam pustaka; artikel tentang penggabungan cepat PDF dengan pergeseran referensi bita menunjukkan ide yang sama yang diterapkan untuk menggabungkan dokumen, dan panduan untuk penggabungan dan pemisahan PDF besar dengan akses file langsung mencakup arsitektur I/O di sekitarnya untuk file yang tidak muat dengan nyaman di RAM

Mengalirkan penyimpanan penuh dengan SaveToStream

Keluaran inkremental hanyalah setengah dari kisah pengaliran; setengah lainnya adalah apa yang terjadi pada penyimpanan penuh. SaveToStream di losLab PDF Library mengarahkan penyerialisasi dokumen secara langsung terhadap aliran target, daripada pertama-tama merender seluruh dokumen ke dalam AnsiString perantara lalu menulis penyangga tersebut keluar dalam satu panggilan. Pendekatan lama memang berhasil, tetapi itu berarti setiap penyimpanan penuh secara transien menyimpan salinan lengkap kedua dari keluaran di memori — tidak berbahaya pada 10 MB, menyakitkan pada 500 MB, dan menjadi hambatan keras untuk keluaran multi-gigabita pada proses 32-bit. Serialisasi langsung membuat memori puncak melacak struktur objek dokumen alih-alih panjang serialisasinya

var
  Doc: TPDFlib;
  Output: TFileStream;
begin
  Doc := TPDFlib.Create;
  try
    if Doc.LoadFromFile('archive.pdf', '') <= 0 then
      Exit;

    // ... pengeditan yang membenarkan penulisan ulang lengkap ...

    Output := TFileStream.Create('archive-rewritten.pdf', fmCreate);
    try
      if Doc.SaveToStream(Output) = 0 then
        Writeln('Save failed, error ', Doc.LastErrorCode);
    finally
      Output.Free;
    end;
  finally
    Doc.Free;
  end;
end;

Pelajaran mode berbagi: ketika AppendToFile mengembalikan 0

Satu regresi di bidang ini patut diceritakan kembali karena pola kegagalannya dapat digeneralisasi. AppendToFile(FileName) menambahkan pembaruan inkremental secara langsung ke PDF yang ada di disk — panggilan alami untuk alur kerja jalur audit di tempat: memuat file, membuat perubahan, menambahkan ke jalur yang sama. Di v3.71.2, urutan yang persis sama mulai mengembalikan nilai 0. Akar penyebabnya terletak pada pemuat (loader), bukan pada penulis: untuk mendukung pembacaan dokumen besar sesuai permintaan, LoadFromFile menjaga handle file sumber tetap terbuka selama masa hidup objek dokumen, dan handle tersebut dibuka dengan fmShareDenyWrite. Ketika AppendToFile kemudian mencoba membuka kembali file yang sama untuk menulis, mode berbagi pemuat itu sendiri menolaknya, dan API gagal sebelum menulis satu bita pun

Perbaikan tersebut melonggarkan mode berbagi pemuat menjadi fmShareDenyNone, yang aman justru karena definisi dari penambahan inkremental: ia menambahkan bita secara ketat setelah akhir file dan tidak pernah menulis ulang wilayah yang dilayani oleh handle pembaca yang berumur panjang. Pelajaran umum bagi siapa saja yang membungkus pustaka ini — atau membangun pemuat aliran serupa — adalah bahwa pembaca pemegang handle yang malas dan penulis file yang sama berada dalam ketegangan, dan mode berbagi yang Anda pilih saat membuka file adalah kontrak API, bukan detail implementasi. Jika AppendToFile pernah mengembalikan nilai 0 dalam kode Anda, periksa terlebih dahulu apakah ada hal lain dalam proses Anda yang masih memegang file target dengan mode berbagi yang membatasi

Biaya jujur: kapan pembaruan inkremental menjadi alat yang salah

Pembaruan inkremental menukar ukuran file dengan efisiensi penulisan, dan pertukaran tersebut tidak selalu menguntungkan. Setiap revisi menambahkan objeknya yang diubah sementara definisi yang digantikan tetap berada di dalam file, sehingga dokumen yang diedit ratusan kali menumpuk objek mati dan rantai /Prev panjang yang harus ditelusuri oleh setiap pembaca. Lebih buruk lagi, konten yang "dihapus" tidak hilang: teks yang dihapus pada revisi kelima masih ada secara fisik dalam bita revisi keempat, dapat dipulihkan oleh siapa saja yang memotong file tersebut. Oleh karena itu, redaksi (redaction), sanitasi, atau penghapusan konten sensitif apa pun menuntut penulisan ulang lengkap — penyimpanan inkremental dari suatu redaksi adalah kebocoran data dengan langkah tambahan

Penyimpanan penuh juga merupakan keputusan yang tepat ketika tujuannya adalah pemadatan (memeras akumulasi peningkatan dan objek yang tidak digunakan), saat mengubah properti dokumen secara luas seperti enkripsi — mengenkripsi ulang menyentuh setiap string dan aliran, sehingga tidak ada yang tersisa secara "inkremental" dari perubahan tersebut — atau saat memproduksi produk akhir yang bersih di mana riwayat pengeditan tidak boleh ikut bersama file. Aturan yang masuk akal: gunakan AppendToStream atau AppendToFile saat dokumen aktif dan berubah, terutama setelah membawa tanda tangan; gunakan penulisan ulang lengkap SaveToStream pada batas siklus hidup, ketika dokumen meninggalkan sistem Anda atau riwayatnya harus diratakan

Pembaruan inkremental, keluaran delta offset virtual, dan serialisasi langsung ke aliran semuanya adalah bagian dari standar losLab PDF Library untuk Delphi, C# dan VB.NET; halaman produk mencantumkan seluruh permukaan API penyimpanan dan penambahan bersama dengan fitur penandatanganan dan file besar yang dibahas di atas