Artikel Teknis

Menggabungkan dan Membagi PDF Gigabyte di Delphi dengan Akses Langsung PDF Library for Delphi

Menggabung atau memecah PDF dua gigabyte dengan cara yang sudah jelas menelan dua hal sekaligus: waktu jam dinding dan ruang alamat. Cara yang sudah jelas itu adalah memuat tiap masukan, mengerjakan tugasnya, menulis keluarannya. Pemuatanlah yang jebol. Arsip pindaian yang naik dari 300 ke 600 DPI melipatduakan resolusi liniernya dan kira-kira melipatempatkan ukurannya di disk, sehingga pekerjaan perakitan yang sepanjang tahun santai menangani berkas 400 MB mulai megap-megap begitu satu masukan melewati satu gigabyte, sering kali sekadar saat menghitung halaman. Tugasnya sendiri tidak pernah bertambah sulit. Buka, hitung, pilih rentang, sambung, hanya itu isinya. Pemuatan pohon penuh sekadar berhenti menjadi default yang waras pada ukuran sebesar itu. PDF Library for Delphi, pustaka PDF milik losLab untuk Delphi dan C++Builder, menjawabnya dengan lapisan Direct Access: sekeluarga fungsi berawalan DA yang ditopang pembaca streaming yang menyusuri tabel cross-reference di tempat alih-alih membangun seluruh dokumen di memori

Ke mana memori pergi pada pemuatan penuh

Memuat PDF "secara normal" berarti mengurai xref, meresolusi setiap objek tak langsung menjadi pohon di memori, mendekode object stream, dan merangkai pohon halaman, font, serta anotasi menjadi objek yang bisa Anda manipulasi. Untuk alur kerja penyuntingan itu pertukaran yang tepat. Untuk pekerjaan gabung, pecah, dan pemeriksaan, sebagian besarnya sia-sia. Arsip pindaian 30.000 halaman bisa memuat jutaan objek tak langsung, dan pekerjaan pemecahan hanya perlu membaca beberapa ratus di antaranya: simpul halaman dalam rentang yang diminta, ditambah apa pun yang dirujuk simpul itu

Lapisan Direct Access membalik modelnya. DAOpenFile dan DAOpenFileReadOnly mengurai trailer dan xref, hanya beberapa kilobyte di ekor berkas, lalu mengembalikan sebuah file handle. Objek diambil secara malas ketika sebuah pemanggilan membutuhkannya. Konsekuensi praktisnya, membuka berkas berukuran multi-gigabyte memakan waktu kira-kira sama dengan membuka berkas kecil, dan memori mengikuti apa yang Anda sentuh, bukan apa yang dikandung berkasnya

Perbandingan PDF Library for Delphi antara memuat PDF sebesar gigabyte ke pohon objek in-memory penuh versus membukanya dengan direct access, di mana parsing berhenti di trailer dan xref serta sebuah handle melayani pembacaan per objek secara lazy
Load penuh mendekode setiap indirect object sebelum merge bisa dimulai, sehingga RAM dan waktu buka berskala dengan arsip. Jalur direct access mengembalikan handle yang berfungsi setelah membaca kilobyte dan membiarkan setiap panggilan menarik hanya objek yang dibutuhkan

Menyelidiki berkas raksasa tanpa memuatnya

Pola di bawah ini berasal dari benchmark berkas besar milik pustaka ini sendiri: buka read-only, ajukan pertanyaan, tutup. Tidak pernah ada pohon dokumen yang terbentuk

var
  Lib: TPDFlib;
  Handle, Pages: Integer;
begin
  Lib := TPDFlib.Create;
  try
    Handle := Lib.DAOpenFileReadOnly('archive-2025.pdf', '');
    if Handle = 0 then
      raise Exception.Create('Direct access open failed');
    Pages := Lib.DAGetPageCount(Handle);
    Writeln('pages : ', Pages);
    Writeln('title : ', Lib.DAGetInformation(Handle, 'Title'));
    Lib.DACloseFile(Handle);
  finally
    Lib.Free;
  end;
end;

Mode read-only layak dipilih kapan pun bisa: ia membuat tahap penerimaan tetap berjalan sementara proses lain memegang berkasnya, dan ia mendokumentasikan niat. Tahap penyelidikan yang tanpa sengaja memanggil fungsi pengubah akan gagal seketika alih-alih merusak arsipnya

PageRef adalah handle objek, bukan nomor halaman

Kesalahan paling umum dengan API DA adalah menyodorkan nomor halaman di tempat sebuah fungsi mengharapkan PageRef. Hampir setiap pemanggilan DA per halaman menerima handle referensi ke objek halaman, bukan nomor halaman: DAExtractPageText, DARenderPageToFile, DARotatePage, dan DACapturePage semuanya mengharapkan sebuah ref. Anda memperolehnya dengan menerjemahkan nomor yang dipakai manusia lewat DAFindPage:

PDF Library for Delphi: Alur terjemahan nomor halaman ke PageRef yang menunjukkan DAFindPage memberi makan pemanggilan direct access per halaman, versus ref integer mentah mendarat pada objek sembarangan dan menghasilkan teks halaman salah yang senyap
Setiap panggilan direct access per halaman mengonsumsi PageRef yang dihasilkan DAFindPage, bukan nomor yang tampak bagi manusia. Melewatkan terjemahan itu membuat integer menyamar sebagai object id, dan teks halaman yang salah bisa terkirim tanpa terlihat
PageRef := Lib.DAFindPage(Handle, 250);          // nomor halaman -> handle objek
if PageRef <> 0 then
begin
  Text := Lib.DAExtractPageText(Handle, PageRef, 0);
  Lib.DARenderPageToFile(Handle, PageRef, 5, 150, 'page250.png');
end;

Menyodorkan angka mentah 250 justru tidak menimbulkan kesalahan. Ia mengalamati objek apa pun yang kebetulan duduk di balik nilai handle itu, yang pada hari baik gagal secara kasatmata dan pada hari buruk mengekstrak teks dari halaman yang salah ke dalam dokumen yang dilihat pelanggan. Jika Anda membungkus lapisan DA dalam kode layanan Anda sendiri, buatlah penerjemahan itu mustahil dilewati: terima nomor halaman di batas antarmuka, panggil DAFindPage seketika, dan hanya oper ref di bagian dalam

Menggabungkan ratusan berkas dengan daftar bernama

Untuk dua berkas, MergeFiles(First, Second, Output) sudah cukup. Perakitan batch lebih baik diskalakan lewat daftar berkas: daftarkan masukan di bawah sebuah nama daftar, lalu gabungkan daftar itu dalam satu tahap

PDF Library for Delphi: Alur kerja daftar berkas bernama di mana laporan Januari, Februari, dan Maret mendaftar di bawah satu nama daftar dan digabung dalam satu lintasan, dengan varian Fast, bawaan, dan strict yang memperdagangkan pelestarian pohon struktur dengan kecepatan
Ratusan input terdaftar menciut menjadi satu lintasan MergeFileList yang hasilnya diverifikasi dalam milidetik melalui probe read-only lain. Varian ini adalah keputusan per pipeline karena Fast membuang pohon struktur Tagged PDF
Lib.AddToFileList('Statements', 'jan.pdf');
Lib.AddToFileList('Statements', 'feb.pdf');
Lib.AddToFileList('Statements', 'mar.pdf');
Lib.MergeFileList('Statements', 'q1-statements.pdf');

// Verifikasi hasilnya dengan cara murah: direct access lagi
Handle := Lib.DAOpenFileReadOnly('q1-statements.pdf', '');
Writeln('merged pages: ', Lib.DAGetPageCount(Handle));
Lib.DACloseFile(Handle);

Keluarga merge punya tiga varian, dan bedanya bukan kecepatan semata. MergeFileListFast melewatkan pelestarian pohon struktur; MergeFileListStrict memberlakukan mode ketat; versi tanpa akhiran adalah default yang berimbang. Aturan operasional yang mengalir darinya: jika ada masukan berupa Tagged PDF yang struktur aksesibilitasnya wajib bertahan, dengan apa pun yang diproduksi untuk PDF/UA sebagai contoh paling jelas, gunakan varian default atau Strict, sebab Fast diam-diam membuang pohon strukturnya. Untuk arsip pindaian biasa tanpa penandaan, Fast adalah kinerja gratis. Putuskan per pipeline, bukan per suasana hati pengembang, dan catat varian yang dipakai di log pekerjaan

Memecah tanpa memuat: ekstraksi rentang

Pemecahan mengikuti filosofi tanpa-muat yang sama. ExtractFilePages(InputFileName, Password, OutputFileName, RangeList) menarik rentang halaman langsung dari berkas ke berkas, dengan daftar rentang seperti '1-500', '501-1000', atau pilihan yang dipisahkan koma, dan sumbernya tidak pernah menjelma menjadi pohon dokumen. Ketika sebuah dokumen sudah dimuat karena alasan lain, ExtractPageRanges menghasilkan dokumen baru di memori dari dokumen saat ini, dan CopyPageRanges menarik rentang dari dokumen lain yang sudah dimuat berdasarkan ID-nya. Untuk memecah aliran cetak terkonsolidasi menjadi satu berkas per laporan, bentuk berkas-ke-berkas itulah yang mencegah masukan 4 GB pernah mengembang ke RAM

Berkas yang berdusta tentang geometrinya

Pipeline berkas besar bertemu berkas rusak dengan laju yang tak pernah dilihat pipeline berkas kecil, semata karena masukannya melewati lebih banyak sistem. Dua bentuk kegagalan pantas ditangani secara eksplisit

Pertama, header yang bergeser. Gateway surel dan spooler cetak kadang menambahkan byte di depan sebuah PDF, sehingga penanda %PDF tidak lagi duduk di offset 0 dan setiap offset xref di berkas itu meleset sebesar jumlah yang sama. Pembaca streaming mendeteksi hal ini dan memaparkannya (DAShiftedHeader di tingkat datar, ShiftedHeader pada TSmartPDFReader), lalu mengompensasinya saat pembacaan. Aritmetika offset rakitan sendiri biasanya tidak melakukannya, dan itulah sebabnya "jalan di semua berkas yang kami hasilkan, gagal di berkas dari pelanggan X" menjadi gejala klasiknya

Kedua, tabel cross-reference yang rusak. DACopyFile(InputFileName, OutputFileName, PageCount) mengalirkan seluruh berkas ke salinan baru sambil membangun ulang xref-nya, dan mengembalikan jumlah halaman sebagai hasil sampingan. Menjalankannya sebagai tahap normalisasi di depan konsumen hilir yang rewel mengubah satu kelas kegagalan parsing yang datang sesekali menjadi satu langkah perbaikan yang dapat diramalkan. Dan ketika suntingan Anda sendiri perlu disimpan, DAAppendFile menuliskannya sebagai incremental update, menambahkan revisi baru alih-alih menulis ulang bergigabyte-gigabyte, sehingga biaya penyimpanan sebanding dengan perubahannya, bukan dengan berkasnya

Detail pengiriman: linearisasi dan komposisi

Dua kemampuan bertetangga melengkapi pipeline berkas besar. Ketika keluaran yang sudah dirakit disajikan lewat HTTP untuk ditonton di peramban, LinearizeFile menata ulangnya untuk streaming byte-range sehingga halaman pertama tampil sebelum sisa paket 500 MB selesai diunduh. Jalankan sebagai tahap terakhir, setelah semua penggabungan, sebab modifikasi apa pun sesudahnya akan melinearisasi ulang berkas itu menjadi tidak linier. Dan ketika paket memerlukan komposisi alih-alih penyambungan biasa, misalnya lembar sampul yang distempel di belakang setiap laporan atau dua halaman sumber yang diimposisi ke satu lembar keluaran, DACapturePage mengubah halaman mana pun menjadi templat yang dapat dipakai ulang dan DADrawCapturedPage menempatkannya pada halaman tujuan di persegi panjang sembarang, tetap tanpa pemuatan dokumen penuh atas sumber multi-gigabyte itu

Batasan dan apa yang tetap read-only

Formatnya sendiri kehabisan ruang jauh sebelum Direct Access melakukannya. Offset bertipe Int64 di sepanjang lapisan DA, jadi plafon yang sebenarnya adalah disk yang tersedia dan field offset xref 10 digit pada tabel cross-reference klasik (non-stream). Arsip pindaian multi-gigabyte biasa saja dalam praktik, dan memori tetap terbatas berapa pun ukuran berkasnya karena objek baru dibaca ketika ada pemanggilan yang memintanya

Dua pertanyaan cukup sering muncul sehingga layak dijawab langsung. Penggabungan lewat jalur default membawa serta struktur dokumen, jadi bookmark dan tautan bertahan; varian Fast-lah yang menukar pohon struktur demi kecepatan, dan itulah seluruh alasan untuk mencadangkannya bagi masukan tanpa penandaan. Kebiasaan yang aman adalah membuka keluaran gabungan, menyusuri outline-nya, dan memeriksa acak beberapa tautan internal sebelum mengirimkannya. Soal penyuntingan: ada jalan tengah yang berguna antara penyelidikan read-only dan pemuatan penuh. Operasi tingkat halaman bekerja langsung pada handle-nya, di antaranya DARotatePage, DAMovePage, dan DAHidePage, berikut pembacaan field formulir, dan DAAppendFile mengekalkan suntingan itu sebagai revisi inkremental. Penyuntingan tingkat konten, apa pun yang menulis ulang operator marking di dalam sebuah halaman, tetap menjadi milik lapisan dokumen penuh

Artikel terkait

Jika keluaran gabungan Anda harus tetap aksesibel, latar belakang pohon strukturnya dibahas di artikel aksesibilitas Tagged PDF, yang menjelaskan persis apa yang akan dibuang varian merge Fast. Untuk menarik konten keluar dari rentang yang Anda pecah, lihat panduan ekstraksi teks, gambar, dan font

Daftar fungsi Direct Access selengkapnya disertakan bersama pustaka; edisi dan unduhan uji coba ada di halaman produk PDF Library for Delphi