Kurangi ukuran berkas PDF dengan kompresi dan subsetting
Sekilas pandang
Bagian berjudul “Sekilas pandang”Anda ingin PDF yang sekecil mungkin sesuai isinya, tanpa mengorbankan ketepatan apa pun. NextPDF memberi Anda dua kendali ukuran berkas, dan keduanya aktif secara baku:
- Kompresi stream. Komponen penulis membungkus setiap content stream halaman dan setiap program fon yang disematkan ke dalam stream FlateDecode (zlib). Flag
compresspadaNextPDF\Core\Configmenyimpan pengaturan ini. Tetapkan nilainya dengan witherwithCompress()saat Anda membangun dokumen streaming. - Subsetting fon. Saat Anda menyematkan fon TrueType atau CFF, komponen penulis membangun ulang program fon sehingga hanya memuat glyph yang dipakai dokumen, lalu mengompresinya dengan FlateDecode. Proses ini berjalan otomatis. Tidak ada flag yang perlu disetel dan tidak ada panggilan yang perlu dilakukan. Fon CJK berisi
20,000glyph yang hanya menyumbang beberapa ratus glyph ke sebuah dokumen akan disematkan sebagai sebagian kecil dari ukuran berkasnya di disk.
Perlu ditegaskan sejak awal: NextPDF Core tidak menyediakan resampling gambar, kendali kualitas gambar, opsi object stream, maupun pengaturan deduplikasi sumber daya. Kedua kendali di atas adalah satu-satunya kendali ukuran. Bagian lain resep ini menunjukkan cara menggunakannya dengan benar serta apa saja yang tidak dilakukan oleh masing-masing kendali.
Prasyarat: pemasangan Core (composer require nextpdf/core:^3) dan, untuk jalur subsetting, sebuah berkas fon yang lisensinya mengizinkan Anda menyematkannya.
Pemasangan
Bagian berjudul “Pemasangan”composer require nextpdf/core:^3Ikhtisar konseptual
Bagian berjudul “Ikhtisar konseptual”Sebuah PDF adalah pohon objek. Objek terbesar biasanya berupa content stream (operator penggambaran untuk setiap halaman) dan program fon (outline glyph yang disematkan). Keduanya sangat cocok dikompresi, sehingga kendali ukuran yang paling efektif adalah mengompresinya dengan FlateDecode. FlateDecode adalah nama PDF 2.0 untuk stream DEFLATE yang dibungkus zlib (ISO 32000-2:2020 §7.4.4), dan itulah filter yang dihasilkan NextPDF.
Komponen penulis menetapkan tingkat kompresi DEFLATE pada 9, nilai maksimum RFC 1951, melalui NextPDF\Writer\PinnedZlibCompressor. Level 9 menukar sedikit tambahan CPU demi stream terkecil. Menetapkan level juga menjaga keluaran tetap deterministik, karena header zlib menyandikan level tersebut dan level yang berubah-ubah akan mengubah byte-nya. Anda tidak memilih level — mesin menetapkannya sehingga dua kali proses atas masukan yang sama menghasilkan stream yang identik byte demi byte.
Kendali kedua adalah subsetting fon. Sebuah berkas fon di disk membawa setiap glyph yang didefinisikan oleh fon tersebut, tetapi dokumen yang mencetak “Invoice 2026” hanya membutuhkan beberapa di antaranya. NextPDF\Typography\FontSubsetter (untuk TrueType) dan NextPDF\Typography\CffSubsetter (untuk CFF / OpenType) menelusuri codepoint yang benar-benar dirender dokumen, menyelesaikan dependensi glyph komposit, dan membangun ulang hanya tabel fon yang diperlukan. Keduanya menghasilkan biner fon subset yang valid dengan tag awalan subset enam huruf yang deterministik (ISO 32000-2:2020 §9.9). Komponen penulis menerapkan ini setiap kali kumpulan glyph yang dipakai dari fon yang disematkan sudah diketahui, lalu mengompresi subset tersebut dengan FlateDecode. Jika subsetting fon tertentu menghemat kurang dari sepuluh persen, subsetter justru mengembalikan program aslinya, karena biaya pembangunan ulang tidak sepadan dengan manfaat yang kecil.
Intinya: Anda menjaga PDF tetap kecil dengan membiarkan kompresi tetap aktif (nilai baku) dan menyematkan berkas fon yang sebenarnya (agar subsetting punya sesuatu untuk diperkecil), bukan dengan menyetel daftar panjang opsi.
Permukaan API
Bagian berjudul “Permukaan API”Satu-satunya kendali ukuran yang Anda setel berada pada objek konfigurasi.
NextPDF\Core\Config adalah value object yang tak dapat diubah, final readonly, dengan metode wither bertipe. Properti yang terkait dengan ukuran adalah:
compress(bool, bakutrue) — mengaktifkan kompresi FlateDecode. Ubah nilainya denganwithCompress(bool $compress): self, yang mengembalikanConfigbaru dengan flag yang diubah dan setiap field lainnya tetap dipertahankan.
Lampirkan Config ke dokumen pada saat konstruksi:
NextPDF\Core\Document::createStandalone(?Config $config = null): selfmembangun dokumen dengan registri yang efemeral untuk skrip CLI atau proses berumur pendek, sambil menerapkanConfigAnda.
Dua properti membentuk apa yang harus diolah oleh kendali ukuran, tetapi tidak satu pun merupakan kendali kompresi:
imageCacheBytes(int, baku52_428_800) membatasi cache gambar di memori, danwithImageCacheBytes(int $bytes): selfmengubahnya. Ini membatasi puncak memori selama pembangunan. Ini tidak melakukan resampling, kompresi ulang, atau memperkecil dengan cara lain gambar yang Anda sematkan — ini adalah batas atas memori, bukan kendali ukuran keluaran.fontsDirectory(string) danwithFontsDirectory(string $dir): selfmenetapkan jalur pencarian baku untuk berkas fon, yang memasok jalur subsetting.
Pekerjaan fon terjadi melalui API tipografi pada dokumen:
setFont(string $family, string $style = '', float $size = 12.0): staticmemilih sebuah fon. Saat famili tersebut tertaut ke berkas fon yang dapat disematkan, komponen penulis mencatat codepoint yang Anda render sehingga dapat melakukan subsetting fon tersebut pada saat penyimpanan.addFontDirectory(string $directory): staticmendaftarkan direktori tambahan untuk mencari berkas fon.
Keluaran tersedia melalui tiga metode standar: getPdfData(): string mengembalikan byte-nya, save(string $path): void menuliskannya secara atomik, dan output(?string $filename, OutputDestination $dest): string menangani pengiriman HTTP.
Subsetting tidak memiliki metode publik dan tidak memiliki flag. Ini adalah perilaku yang muncul dari menyematkan fon dan merender teks. Komponen penulis menjalankan FontSubsetter / CffSubsetter untuk Anda di dalam NextPDF\Writer\PdfFontWriter.
Contoh kode — Mulai cepat
Bagian berjudul “Contoh kode — Mulai cepat”Contoh ini membangun dokumen dengan kompresi yang diaktifkan secara eksplisit dan sebuah fon yang disematkan serta di-subset, lalu menulis byte-nya. Contoh ini mengabaikan penanganan kesalahan agar bentuk pemanggilannya tetap jelas. Contoh produksi di bawah ini menambahkan seluruh pengaman.
<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\Config;use NextPDF\Core\Document;
// compress defaults to true; setting it explicitly documents intent.$config = (new Config())->withCompress(true);
$doc = Document::createStandalone($config);$doc->addFontDirectory(__DIR__ . '/fonts');$doc->addPage();
// Selecting an embeddable face records the glyphs used, so the writer// subsets this font automatically when the document is built.$doc->setFont('LiberationSans', '', 12);$doc->cell(0, 10, 'Invoice 2026 - subsetted, compressed output.', newLine: true);
$pdf = $doc->getPdfData();
file_put_contents(__DIR__ . '/small.pdf', $pdf);
printf("Wrote %d bytes.\n", strlen($pdf));Contoh kode — Produksi
Bagian berjudul “Contoh kode — Produksi”Ini adalah program mandiri. Program ini membangun dokumen dengan kompresi aktif, menyematkan fon dari direktori yang Anda kendalikan, merender teks sehingga subsetter memiliki kumpulan glyph yang dipakai, dan menuliskan hasilnya secara atomik. Program ini menangkap exception NextPDF yang paling spesifik yang dilemparkan oleh jalur pembangunan dan penyimpanan, lalu melempar ulang masing-masing dengan konteks alih-alih menelannya begitu saja. Arahkan NEXTPDF_FONT_DIR ke direktori yang berisi fon TrueType atau CFF yang lisensinya mengizinkan Anda menyematkannya; program memvalidasi jalur tersebut sebelum menyematkan.
<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\Config;use NextPDF\Core\Document;use NextPDF\Exception\CompressionException;use NextPDF\Exception\InvalidConfigException;
/** * Resolve and validate the font directory from a server-controlled source. * * Reading the directory from the environment keeps the path off the request * surface. The function rejects a missing or unreadable directory so the * embedding path never runs against untrusted or absent input. */function resolveFontDirectory(): string{ $configured = getenv('NEXTPDF_FONT_DIR'); $dir = $configured !== false && $configured !== '' ? $configured : __DIR__ . '/fonts';
$real = realpath($dir); if ($real === false || !is_dir($real) || !is_readable($real)) { throw new RuntimeException(sprintf('Font directory "%s" is not a readable directory.', $dir)); }
return $real;}
/** * Build a compressed, font-subsetted document and return its bytes. * * @param non-empty-string $fontDirectory Validated directory of embeddable fonts. * * @return string Raw PDF bytes. */function buildCompactPdf(string $fontDirectory): string{ // compress is true by default; pin it so the intent is explicit and the // streaming writer path honours it regardless of any wrapper defaults. $config = (new Config()) ->withCompress(true) ->withFontsDirectory($fontDirectory) // Bound the image cache so a build cannot exhaust memory. This is a // memory ceiling, not an output-size control. ->withImageCacheBytes(16 * 1024 * 1024);
$doc = Document::createStandalone($config); $doc->addFontDirectory($fontDirectory); $doc->addPage();
// Rendering with an embeddable face records the used codepoints, which the // writer turns into a font subset at build time. $doc->setFont('LiberationSans', '', 12); $doc->cell(0, 10, 'Invoice 2026', newLine: true); $doc->cell(0, 10, 'Compressed streams plus an automatic font subset.', newLine: true);
// getPdfData() triggers the build: page streams and the subset font program // are FlateDecode-compressed before the bytes are returned. return $doc->getPdfData();}
try { $fontDirectory = resolveFontDirectory(); $pdf = buildCompactPdf($fontDirectory);} catch (CompressionException $e) { // Raised if the zlib encoder hard-fails while compressing a stream. throw new RuntimeException( sprintf('Compression failed for a %s stream.', $e->getAlgorithm()), previous: $e, );} catch (InvalidConfigException $e) { // Raised by the output path for an invalid destination configuration. throw new RuntimeException( sprintf('Output configuration "%s" was rejected.', $e->getConfigKey()), previous: $e, );}
$out = getenv('NEXTPDF_COOKBOOK_OUTPUT');$path = $out !== false && $out !== '' ? $out : __DIR__ . '/small.pdf';
if (file_put_contents($path, $pdf) === false) { throw new RuntimeException(sprintf('Could not write PDF to "%s".', $path));}
printf("Wrote %d bytes to %s.\n", strlen($pdf), $path);STDOUT yang diharapkan (jumlah byte bergantung pada fon dan proses pembangunannya):
Wrote <n> bytes to <path>.Kasus tepi & jebakan
Bagian berjudul “Kasus tepi & jebakan”- Kompresi aktif secara baku.
Configbaru memilikicompressyang disetel ketrue. Anda jarang sekali membutuhkanwithCompress()sama sekali. Setel secara eksplisit hanya untuk mendokumentasikan maksud, atau untuk menonaktifkannya pada build debug saat Anda ingin membaca stream mentahnya. - Menonaktifkan kompresi membuat berkas lebih besar, bukan lebih kecil.
withCompress(false)adalah alat bantu diagnostik untuk memeriksa stream yang tidak terkompresi. Ini sama sekali bukan optimasi ukuran. Rilis dengan kompresi aktif. - Subsetting membutuhkan fon yang benar-benar disematkan. Fon standar Base14 (Helvetica, Times, Courier, dan kerabatnya) dirujuk berdasarkan nama dan tidak membawa program yang disematkan dalam dokumen biasa, sehingga tidak ada yang bisa di-subset. Subsetting hanya memperkecil fon yang Anda sematkan dari sebuah berkas fon.
- Subsetting bersifat otomatis dan senyap. Tidak ada flag, tidak ada metode, dan tidak ada konfirmasi. Jika Anda menyematkan sebuah fon dan merender teks dengannya, komponen penulis akan melakukan subsetting pada fon tersebut. Program yang disematkan membawa tag awalan subset enam huruf (misalnya
ABCDEF+LiberationSans) sehingga pembaca dapat membedakan subset dari penyematan penuh. - Penghematan kecil mempertahankan fon penuh. Saat sebuah subset hanya menghemat kurang dari sepuluh persen dari ukuran program, subsetter mengembalikan aslinya. Ini adalah batas bawah yang disengaja: biaya pembangunan ulang tidak sepadan dengan manfaat yang kecil. Menyematkan fon yang ukurannya memang sudah sangat kecil, atau merender hampir seluruh glyph-nya, dapat masuk ke kasus ini.
imageCacheBytesbukan kendali ukuran gambar. Ini membatasi memori, bukan byte keluaran. NextPDF Core menyematkan data gambar yang Anda berikan; tidak ada langkah resampling, downsampling, atau pengodean ulang. Jika Anda membutuhkan gambar yang lebih kecil, ubah ukuran dan kodekan ulang gambar tersebut sebelum Anda menyematkannya.- Tidak ada pengaturan object stream atau dedup. NextPDF Core tidak menyediakan tombol untuk object stream PDF 2.0 maupun untuk deduplikasi sumber daya. Jangan mencarinya — tuas ukuran adalah kompresi stream dan subsetting fon.
Kinerja
Bagian berjudul “Kinerja”Kompresi level 9 biasanya menjadi biaya CPU dominan saat menulis stream. Ini menukar beberapa persen waktu pembangunan demi keluaran terkecil. Biayanya linear terhadap jumlah byte yang tidak terkompresi, sehingga jumlah halaman dan banyaknya data fon yang disematkan menentukan anggarannya. Subsetting menambahkan satu kali pemrosesan per fon yang disematkan untuk mengurai direktori tabel fon, menyelesaikan penutupan glyph yang dipakai, dan membangun ulang tabel yang diperlukan. Untuk fon CJK berukuran besar, inilah yang lebih mahal di antara kedua kendali tersebut, tetapi berjalan sekali per fon, bukan sekali per halaman. Batas bawah penghematan sepuluh persen diterapkan antara lain untuk menjaga pemrosesan tersebut tetap keluar dari jalur kritis saat tidak akan sepadan. Sebuah dokumen kecil dengan satu subset yang disematkan tetap nyaman berada di dalam batas waktu 1500 ms dan anggaran puncak 96 MB. Batasi imageCacheBytes sesuai batas atas yang sebenarnya sehingga build yang menyematkan banyak gambar gagal dengan cepat pada memori alih-alih masuk ke swapping.
Catatan keamanan
Bagian berjudul “Catatan keamanan”Pembangunan berjalan di dalam proses; tidak ada byte dokumen yang meninggalkan host dan tidak ada panggilan jaringan yang dilakukan. Perlakukan setiap fon atau gambar yang dipasok dari luar sebagai masukan yang tidak tepercaya:
- Validasi direktori fon. Contoh produksi membaca jalur fon dari variabel lingkungan yang dikendalikan server dan menolak direktori yang tidak ada atau tidak dapat dibaca sebelum menyematkan. Jangan pernah menurunkan jalur fon dari field permintaan.
- Sematkan hanya fon yang lisensinya mengizinkan Anda mendistribusikannya kembali. Sebuah subset tetaplah program fon yang disematkan. Pastikan lisensi mengizinkan penyematan sebelum Anda merilis dokumen yang membawa fon tersebut.
- Fon yang cacat melempar exception, bukan merusak secara diam-diam. Sebuah berkas fon yang gagal diurai melempar
NextPDF\Exception\FontParsingException, dan kegagalan zlib yang serius melemparNextPDF\Exception\CompressionException. Tangkap exception yang paling spesifik dan tindak lanjuti. Jangan pernah membungkus pembangunan dalamcatchkosong. - Jangan pernah menyisipkan masukan pengguna ke dalam jalur keluaran. Contoh ini menulis ke jalur tetap atau side-channel yang dikendalikan server, dan menolak stream wrapper serta byte null melalui penulis atomik di
save(). Turunkan jalur keluaran dari nilai yang dikendalikan server untuk menghindari path traversal. - Tanpa rahasia di dalam dokumen. Jangan menyematkan kredensial, token, atau pengidentifikasi internal dalam dokumen yang dihasilkan yang Anda kembalikan ke klien.
Kesesuaian
Bagian berjudul “Kesesuaian”Resep ini tidak membuat klaim normatif standar yang terpisah. Mekanisme yang digunakannya didefinisikan oleh spesifikasi PDF 2.0: kompresi stream FlateDecode (ISO 32000-2:2020 §7.4.4) dan penamaan subset fon dengan awalan subset enam karakter (ISO 32000-2:2020 §9.9). NextPDF menghasilkan keduanya sebagai bagian dari jalur tulis standarnya; Anda tidak mengonfigurasinya selain melalui flag compress. Profil reproduktibilitas structural yang dideklarasikan halaman ini mencerminkan bahwa komponen penulis menetapkan level DEFLATE, sehingga stream terkompresi bersifat deterministik, sementara pengidentifikasi tingkat dokumen masih dapat bervariasi antarproses kecuali Anda juga mengonfigurasi pengaturan deterministik. Untuk mekanisme penyematan di balik subsetting, lihat resep embed-and-subset yang ditautkan di bawah.
Lihat juga
Bagian berjudul “Lihat juga”- Sematkan dan subset fon TrueType — daftarkan sebuah fon, render dengannya, dan periksa tag subset yang disematkan.
- Susun teks dan fon — API komposisi teks dan fon yang lebih luas yang memasok jalur subsetting.
- Rujukan modul konfigurasi — value object
Configlengkap, wither-nya, dan nilai bakunya. - Penanganan kesalahan yang sadar exception — hierarki exception NextPDF di balik
CompressionException,FontParsingException, danInvalidConfigException.