Artikel Teknis

PDF Tanpa Kamus Pages: Implikasi Parsing

Kamus Catalog PDF memiliki tepat satu kunci navigasi yang diperlukan: /Pages. Kunci tersebut harus menunjuk ke objek tidak langsung bertipe /Pages, yang pada gilirannya menyimpan array /Kids dan total /Count halaman. Ambil penunjuk itu dan tidak ada reader yang sesuai yang dapat menemukan satu halaman pun dalam file. ISO 32000-1 §7.7.2 tidak ambigu tentang hal ini: Catalog harus memiliki entri /Pages, dan objek yang direferensikan harus bertipe /Pages. File yang melanggar persyaratan ini tidak sekadar tidak sesuai; mereka rusak secara struktural dengan cara yang ditangani buruk oleh sebagian besar parser

Apa yang sebenarnya dikatakan spesifikasi

PDF yang sesuai minimal memiliki setidaknya tiga objek. Objek 1 adalah Catalog, objek 2 adalah root Pages, dan objek 3 dan seterusnya adalah kamus Page individual. Catalog menunjuk ke root Pages; root Pages mencantumkan anak-anaknya dalam /Kids; setiap Page membawa referensi balik /Parent. Seluruh rantai bersifat bidireksional berdasarkan desain, sehingga parser dapat memulai dari kedua ujung dan melintasi ke halaman mana pun dalam waktu O(log n) untuk pohon seimbang

% Minimal conforming structure (ISO 32000-1 §7.7.2)
1 0 obj
<< /Type /Catalog /Pages 2 0 R >>
endobj

2 0 obj
<< /Type /Pages /Kids [3 0 R 4 0 R] /Count 2 >>
endobj

3 0 obj
<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] /Contents 5 0 R /Resources << >> >>
endobj

4 0 obj
<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] /Contents 6 0 R /Resources << >> >>
endobj

Pohon Pages dapat bersarang. Dokumen dengan ribuan halaman biasanya mengelompokkan halaman ke dalam objek node menengah yang juga bertipe /Pages, masing-masing dengan /Kids-nya sendiri dan sebuah /Count yang mencerminkan subpohon di bawahnya. Nilai /Count node root selalu sama dengan jumlah halaman total. Jumlah itulah yang ditampilkan viewer di kolom nomor halaman sebelum mereka mengurai satu halaman pun, karena membaca satu bilangan bulat dari objek 2 jauh lebih murah daripada menelusuri seluruh pohon

Seperti apa file tanpa Pages

File yang tidak memiliki kamus Pages biasanya berasal dari generator PDF yang menulis objek halaman langsung tanpa merakitnya ke dalam pohon, atau dari korupsi yang menghapus node root sementara membiarkan objek Page daun tetap utuh. Catalog dalam file semacam itu tidak memiliki kunci /Pages sama sekali, atau menyimpan referensi ke objek yang tidak lagi ada dalam tabel referensi silang

% Non-conforming: Catalog with no /Pages reference
1 0 obj
<< /Type /Catalog >>
endobj

% Page objects exist but are unreachable from the Catalog
5 0 obj
<< /Type /Page /MediaBox [0 0 612 792] /Contents 6 0 R /Resources << >> >>
endobj

15 0 obj
<< /Type /Page /MediaBox [0 0 612 792] /Contents 16 0 R /Resources << >> >>
endobj

25 0 obj
<< /Type /Page /MediaBox [0 0 612 792] /Contents 26 0 R /Resources << >> >>
endobj

Parser yang mengikuti spesifikasi akan membaca Catalog, mencoba menyelesaikan /Pages, menemukan tidak ada (atau referensi mati), dan akan memunculkan kesalahan atau melaporkan nol halaman. Yang tidak boleh dilakukannya adalah melanjutkan seolah-olah file memiliki nol halaman dan diam-diam berhasil; itu menghasilkan output kosong yang terlihat benar bagi alat otomatis dan salah bagi setiap manusia yang membukanya

Mengapa parser crash

Sebagian besar parser PDF mengalokasikan tabel halaman internalnya saat memuat berdasarkan nilai /Count dari root Pages. Ketika root tersebut tidak ada, parser membaca nol, mengalokasikan tidak ada, dan kemudian melakukan dereferensi pointer null pertama kali kode mana pun meminta halaman 1, atau membaca sampah dan mengalokasikan buffer yang sangat salah. Tidak ada dari keduanya yang bersifat baik. Pelanggaran akses di 0x008E5D78 yang muncul dalam log crash dari memproses file semacam itu adalah tepatnya ini: dereferensi pointer null di dalam jalur akses halaman, dipicu oleh tidak adanya struktur yang diasumsikan parser akan selalu ada

Asumsi desain yang mendasarinya masuk akal. Sebagian besar PDF yang ada memiliki kamus Pages. Parser yang melewati pemeriksaan keberadaan untuk menghemat beberapa instruksi tidak ceroboh; mereka mengoptimalkan untuk kasus umum. File yang menghukum optimasi tersebut cukup langka sehingga kode produksi mungkin tidak pernah menemuinya sampai menemuinya, di mana crash tersebut sekaligus dapat direproduksi dan membingungkan jika insinyur belum membaca §7.7.2

Pemulihan tanpa pohon Pages

Jika parser harus menangani file-file ini daripada menolaknya, pemulihan mengikuti jalur yang dapat diprediksi: pindai setiap objek tidak langsung dalam tabel referensi silang, kumpulkan yang memiliki /Type /Page, dan urutkan berdasarkan nomor objek. Urutan nomor objek tidak dijamin sesuai urutan membaca dalam spesifikasi, tetapi dalam praktiknya, generator yang menghilangkan pohon Pages cenderung mengeluarkan halaman secara berurutan, sehingga urutan nomor objek lebih sering benar daripada tidak

Pemeriksannya murah. Sebelum berjalan mengikuti penunjuk /Pages Catalog, konfirmasi bahwa penunjuk ada, bahwa penunjuk tersebut menyelesaikan ke objek nyata, dan bahwa /Type objek yang terselesaikan sama dengan /Pages. Jika salah satu dari tiga kondisi tersebut gagal, jatuh ke pemindaian linier. Pemindaian lebih lambat dari traversal pohon untuk dokumen besar, karena membaca setiap header objek daripada mengikuti jalur seimbang, tetapi berhasil, dan untuk file yang sudah cacat, kebenaran mengalahkan kecepatan

Satu kasus tepi yang tidak diselesaikan secara otomatis oleh pemindaian linier: pengurutan halaman. Tanpa array /Kids untuk mendefinisikan urutan, urutan "benar" tidak ditentukan oleh spesifikasi. Urutan nomor objek adalah default pragmatis; jika file cukup penting untuk diproses dengan hati-hati, memeriksa apakah objek Page membawa referensi /StructParents atau anotasi eksplisit yang menyiratkan urutan membaca layak dilakukan

Implikasi bagi generator PDF

Bagi siapa pun yang menulis generator PDF daripada parser, pelajarannya sederhana: selalu emit root Pages sebelum menutup file. Catalog tanpa entri /Pages bukanlah PDF yang valid di bawah revisi spesifikasi mana pun. Generator yang membangun objek halaman secara on-the-fly dan merakit pohon saat finalisasi (pendekatan yang digunakan sebagian besar penulis streaming) tidak masalah selama finalisasi benar-benar berjalan. Mode kegagalan yang umum adalah pengecualian atau kembalian awal yang membatalkan penulisan sebelum trailer selesai, meninggalkan file yang terbuka di beberapa viewer (yang memiliki heuristik pemulihan) dan gagal di yang lain (yang tidak memilikinya)

PDF/A dan PDF/UA memberlakukan batasan tambahan pada pohon halaman di luar apa yang diperlukan oleh spesifikasi dasar, tetapi keduanya tidak melonggarkan persyaratan /Pages. Validator yang memeriksa kesesuaian dengan ISO 19005 atau ISO 14289 akan menangkap kamus Pages yang hilang sebagai pelanggaran spesifikasi dasar sebelum mencapai aturan khusus profil