Untuk memperkecil ukuran berkas PDF di Delphi, losLab PDF Library menyediakan tiga API yang menyerang tiga sumber pembengkakan terbesar: SubsetEmbeddedFonts menulis ulang setiap program font TrueType tersemat menjadi hanya glyph yang benar-benar dirender dokumen, DownsampleImages mencuplik ulang gambar raster yang melampaui DPI target, dan NormalizeLZWStreams mengganti kompresi LZWDecode lawas dengan FlateDecode. Masing-masing mengembalikan jumlah objek yang diubahnya, sehingga angka nol memberitahu Anda bahwa lintasan itu tidak melakukan apa-apa alih-alih gagal diam-diam
Mengapa PDF hasil merge saya lebih besar daripada berkas sumbernya?
PDF hasil merge atau yang dihasilkan secara programatik biasanya kelewat besar karena salah satu dari tiga alasan: font tersemat secara utuh, gambar yang dicuplik jauh di atas resolusi tampilnya, dan stream yang masih dikompresi dengan filter LZW lawas. ISO 32000-1 §9.9 mengizinkan produser menyematkan program font lengkap, dan sebagian besar produser memang persis melakukan itu karena itulah default yang aman. Satu FontFile2 Arial lengkap mencapai ratusan kilobita; sematkan di selusin berkas sumber, gabungkan semuanya, dan Anda membawa selusin salinan outline glyph untuk karakter yang tidak pernah diketik siapa pun. Merge itu sendiri tidak menciptakan pemborosan, ia hanya memusatkannya ke dalam satu berkas tempat totalnya akhirnya terlihat
Gambar adalah pelanggar kedua. Pindaian selebar 4800 piksel yang ditempatkan dalam bingkai seperempat halaman mengirimkan kira-kira 40 kali lebih banyak data piksel daripada yang bisa dipakai jalur cetak 300 DPI. Yang ketiga lebih senyap: stream yang difilter dengan LZWDecode. ISO 32000-1 §7.4.4 menetapkan baik LZWDecode maupun FlateDecode, dan mencatat bahwa Flate biasanya mengompresi setidaknya sama baiknya; dalam praktiknya keluaran Flate secara konsisten lebih kecil pada data yang sama, dan LZW umumnya bertahan di berkas yang pada suatu titik dalam sejarahnya pernah melewati perkakas era 1990-an. Sisa artikel ini menelusuri ketiga lintasan losLab PDF Library yang memperbaiki tiap masalah tersebut, lalu menggabungkannya menjadi satu pipeline
Subsetting font dengan SubsetEmbeddedFonts
SubsetEmbeddedFonts menciutkan setiap font TrueType tersemat dalam dokumen yang dimuat menjadi hanya karakter yang benar-benar dipakai dokumen, dan ia tidak memerlukan argumen karena menurunkan daftar simpan langsung dari content stream itu sendiri. Secara internal, lintasan ini menelusuri content stream setiap halaman dengan GetTextRuns, mengumpulkan kode karakter yang dirujuk di bawah tiap resource font, membangun daftar simpan, lalu menyerahkan program font asli ke mesin FontSub Windows (CreateFontPackage) untuk menghasilkan subset. Program yang ditulis ulang menggantikan stream FontFile2 di tempat, dan nama BaseFont memperoleh tag LOSABC+, konvensi enam huruf kapital plus tanda tambah yang didefinisikan ISO 32000-1 §9.6.4 untuk font subset. Prefiks itu pula yang membuat panggilan ini idempoten: jalankan lintasan dua kali dan font yang sudah di-subset akan dikenali lalu dilewati, sehingga menyambungkannya ke pekerjaan batch yang mungkin mengunjungi ulang berkas tetap aman
var
Lib: TPDFlib;
Fonts: Integer;
begin
Lib := TPDFlib.Create;
try
if Lib.LoadFromFile('merged-report.pdf', '') = 1 then
begin
Fonts := Lib.SubsetEmbeddedFonts;
// Fonts = jumlah program FontFile2 yang ditulis ulang;
// 0 berarti tidak ada yang tersemat, atau semuanya sudah di-subset
Lib.SaveToFile('merged-report-subset.pdf');
end;
finally
Lib.Free;
end;
end;
Dua detail implementasi layak diketahui karena keduanya menjelaskan batas API ini. Pertama, lintasan ini menyasar FontFile2, jadi ia mencakup program TrueType tersemat; font yang disematkan sebagai Type 1 atau CFF telanjang dibiarkan apa adanya alih-alih dipertaruhkan. Kedua, ia bergantung pada FontSub, yang membuat SubsetEmbeddedFonts hanya tersedia di Windows. Satu poin yang lebih halus dari implementasinya: apakah sebuah font memenuhi syarat diputuskan dengan benar-benar menyelesaikan rantai rujukan FontDescriptor → FontFile2, bukan dengan memercayai heuristik flag tersemat, karena font dalam dokumen yang dimuat tidak pernah melewati pembukuan sisi pembuatan yang menyetel flag semacam itu. Jika stream hasil resolusi itu ada, font tersebut adalah kandidat; jika tidak, ia dilewati tanpa error
Kompromi jujurnya: font subset hanya berisi glyph yang hadir pada saat subsetting. Jika perkakas di hilir, atau kode Anda sendiri, kemudian menambahkan teks dengan font yang sama, karakter apa pun di luar subset tidak punya outline dan akan dirender sebagai glyph hilang. Lakukan subsetting sebagai langkah pengubah konten terakhir, jangan pernah sebelum tahap penyuntingan. Kehati-hatian yang sama berlaku jika Anda berencana menarik kembali font itu untuk dipakai ulang nanti; artikel tentang mengekstrak teks, gambar, dan font dengan PDF Library for Delphi membahas apa yang bisa dan tidak bisa diberikan program subset hasil ekstraksi
Bagaimana DownsampleImages memutuskan gambar mana yang diperkecil?
DownsampleImages(MaxDPI, Quality, Filter) hanya mencuplik ulang gambar yang dengan yakin bisa disebutnya kelebihan cuplikan, memakai perkiraan DPI yang sengaja konservatif. Sebuah image XObject PDF menyimpan dimensi piksel tetapi bukan resolusi fisik yang dapat dipercaya, dan tag DPI apa pun dari gambar sumber jarang selamat melewati siklus muat-sunting-simpan. Maka lintasan ini memperkirakan SrcDPI = PixelWidth / 8.5, yang pada dasarnya bertanya: jika gambar ini membentang selebar penuh halaman Letter, berapakah resolusinya? Hanya gambar yang perkiraannya melampaui MaxDPI yang disentuh. Biasnya disengaja: gambar yang ditempatkan kecil di halaman memiliki DPI sebenarnya yang lebih tinggi daripada perkiraan, sehingga lintasan ini lebih memilih kurang terpicu daripada merusak aset berkualitas cetak yang tidak dapat diukurnya
Quality dari 1 sampai 100 memilih kualitas penyandian ulang JPEG, sedangkan 0 mempertahankan keluaran sebagai Flate bergaya PNG yang lossless; Filter memilih kernel pencuplikan ulang, 0 untuk rata-rata kotak dan 1 untuk bilinear. Untuk dokumen kantor hasil pindai, DownsampleImages(150, 75, 1) adalah titik awal yang masuk akal; untuk apa pun yang mungkin dicetak ulang, naikkan MaxDPI ke 300 atau lewati lintasan ini sama sekali. Downsampling adalah satu-satunya langkah lossy dari ketiganya, jadi ia layak berada di belakang setelan yang bisa dimatikan pengguna Anda
Mengubah stream LZW lawas dengan NormalizeLZWStreams
NormalizeLZWStreams adalah kemenangan gratis: ia mendekompresi setiap stream LZWDecode tanpa kehilangan data lalu mengompresinya ulang dengan FlateDecode, di tempat, dan mengembalikan jumlah stream yang dikonversi. Ia menangani baik entri /Filter /LZWDecode tunggal maupun LZW yang muncul di dalam array rantai filter, di mana hanya mata rantai LZW yang diganti dan sisa rantainya dipertahankan. Parameter prediktor (Predictor, Columns, Colors, BitsPerComponent) dibaca dari DecodeParms milik stream dan diteruskan ke dekompresor, sehingga data gambar bersandi prediktor bolak-balik dengan benar. Karena kedua filter adalah codec yang tepat pada tingkat bit, byte hasil dekode identik sebelum dan sesudah; hanya kompresi wadahnya yang berubah, itulah sebabnya lintasan ini aman dijalankan tanpa syarat pada setiap berkas
Pada dokumen tanpa stream LZW, panggilan ini cukup mengembalikan 0 dan tidak menyentuh apa pun, hal yang diuji secara eksplisit oleh suite regresi library: berkas Flate-saja yang baru dibuat harus melaporkan nol konversi. Jaminan tanpa efek itu penting ketika lintasan ini duduk dalam pipeline yang memproses ribuan berkas heterogen, sebagian dari 2024 dan sebagian dari 1998
Pipeline optimasi ukuran yang lengkap di Delphi
Ketiga lintasan itu berpadu menjadi satu fungsi muat-optimasi-simpan, dan urutannya tidak sepenting yang mungkin Anda kira karena semuanya bekerja pada tipe objek yang terpisah: font, image XObject, dan filter stream. Menjalankan subsetting lebih dahulu tetap pilihan yang rapi, karena itulah lintasan yang punya kendala urutan penyuntingan
function OptimizePDF(const Src, Dst: string): Boolean;
var
Lib: TPDFlib;
Fonts, Images, Streams: Integer;
begin
Result := False;
Lib := TPDFlib.Create;
try
if Lib.LoadFromFile(Src, '') <> 1 then
Exit;
Fonts := Lib.SubsetEmbeddedFonts; // FontFile2 TrueType -> subset
Images := Lib.DownsampleImages(150, 75, 1); // >150 DPI -> JPEG q75, bilinear
Streams := Lib.NormalizeLZWStreams; // LZWDecode -> FlateDecode
Result := Lib.SaveToFile(Dst) = 1;
// Catat Fonts/Images/Streams: tiga angka nol berarti berkas sudah ramping
finally
Lib.Free;
end;
end;
Verifikasikan pipeline ini dengan cara library memverifikasi dirinya sendiri: bolak-balik. Uji regresi v3.130 membuat sebuah dokumen, menyimpannya, memuatnya ulang, menjalankan optimasi, menyimpan lagi, lalu menegaskan tiga hal: keluarannya lebih kecil, jumlah yang dikembalikan sesuai harapan, dan pemuatan ulang berkas hasil optimasi tetap terurai serta terrender. Mereproduksi putaran buat-optimasi-muat ulang itu terhadap sampel berkas produksi Anda sendiri, lalu membandingkan teks hasil ekstraksi sebelum dan sesudah, adalah investasi satu jam yang menangkap kesalahan integrasi jauh sebelum seorang pelanggan membuka faktur yang rusak
// Pemeriksaan bolak-balik: berkas hasil optimasi harus tetap termuat bersih
Lib := TPDFlib.Create;
try
Assert(Lib.LoadFromFile('merged-report-opt.pdf', '') = 1);
Assert(Lib.GetPageCount > 0);
finally
Lib.Free;
end;
Di mana pipeline ini cocok ditempatkan dalam alur kerja merge? Setelah merge, bukan selama merge. Melakukan merge lebih dahulu lalu mengoptimasi hasil tunggalnya berarti setiap font tersemat di-subset satu kali terhadap gabungan semua karakter yang dipakai, alih-alih per berkas sumber. Jika throughput merge yang menjadi hambatan, PDF Library for Delphi menawarkan jalur cepat pada level byte yang menghindari penguraian objek penuh, dijelaskan dalam artikel tentang merge PDF cepat dengan penggeseran rujukan byte; dan untuk masukan yang terlalu besar untuk ditahan seutuhnya di memori, merge dan split akses langsung untuk PDF besar membahas rute streaming-nya. Keduanya berpasangan secara alami dengan satu lintasan optimasi akhir pada keluaran hasil merge
Apa yang tidak akan dilakukan ketiga lintasan ini
Trio optimasi losLab PDF Library sengaja mengecualikan apa pun yang mengubah semantik dokumen. SubsetEmbeddedFonts tidak menyatukan font duplikat lintas sumber hasil merge menjadi satu program, ia menciutkan masing-masing secara terpisah; deduplikasi adalah transformasi lain yang lebih berisiko. DownsampleImages akan melewatkan gambar yang perkiraan DPI konservatifnya tetap di bawah ambang batas bahkan ketika manusia bisa melihat bahwa gambar itu kelewat besar untuk bingkainya. Dan tak satu pun lintasan menyentuh struktur dokumen, sehingga berkas yang membengkak karena ribuan objek yatim membutuhkan penyimpanan bergaya tulis ulang alih-alih lintasan tingkat stream ini. Dalam batas-batas itu, kombinasi subsetting font, downsampling gambar, dan normalisasi LZW ke Flate menghilangkan tiga sumber klasik pembengkakan PDF dengan satu panggilan API yang dapat diprediksi untuk masing-masing. Ketiga fungsi ini hadir sebagai bagian dari losLab PDF Library untuk Delphi, C# dan VB.NET, berdampingan dengan API merge, ekstraksi, dan rendering yang dibahas di atas