Objek nomor 1 bukanlah halaman 1. Fakta tunggal itu membuat tersandung lebih banyak kode pemrosesan PDF daripada aspek format lainnya, dan memahami alasannya mengharuskan melihat melewati apa yang ditunjukkan penampil kepada Anda dan masuk ke grafik objek yang sebenarnya dibaca oleh penampil
File PDF adalah kumpulan objek tidak langsung yang diberi nomor. Setiap objek membawa nomor objek dan nomor generasi, dan objek lain menunjuk kepadanya dengan referensi yang ditulis sebagai N G R: 3 0 R berarti versi saat ini dari objek 3. Halaman adalah salah satu dari objek-objek tersebut, tetapi urutan tampilannya tidak ada hubungannya dengan di mana mereka berada dalam file atau nomor apa yang mereka bawa. Urutan tampilan ditentukan sepenuhnya oleh pohon /Pages, struktur tertaut yang berakar pada katalog dokumen. Jika Anda mengabaikan pohon dan memindai objek secara numerik, Anda akan menyusun halaman dalam urutan yang salah untuk sebagian besar file dunia nyata
Pohon halaman: apa yang sebenarnya mengatur urutan
Setiap PDF dimulai dengan katalog dokumen (ISO 32000-2 §7.7.2). Katalog menyimpan entri /Pages yang menunjuk ke simpul akar pohon halaman. Simpul akar itu adalah kamus dengan /Type /Pages, larik referensi tidak langsung /Kids, dan /Count yang memberikan jumlah total halaman-daun di bawahnya. Urutan tampilan adalah penjelajahan (traversal) depth-first dari kiri-ke-kanan dari pohon itu, titik
File tiga halaman minimal membuat ini menjadi konkret:
%PDF-1.7
1 0 obj
<< /Type /Catalog /Pages 2 0 R >>
endobj
2 0 obj
<< /Type /Pages /Kids [20 0 R 4 0 R 9 0 R] /Count 3 >>
endobj
% Object 4 is stored third in the file but is page 2 in display order
4 0 obj
<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792]
/Contents 5 0 R /Resources << /Font << /F1 6 0 R >> >> >>
endobj
% Object 9 is stored fourth but is page 3
9 0 obj
<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792]
/Contents 10 0 R /Resources << /Font << /F1 6 0 R >> >> >>
endobj
% Object 20 is stored last but is page 1; Kids[0] decides, not object number
20 0 obj
<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792]
/Contents 21 0 R /Resources << /Font << /F1 6 0 R >> >> >>
endobj
Larik /Kids terbaca [20 0 R 4 0 R 9 0 R], jadi objek 20 adalah halaman 1, objek 4 adalah halaman 2, dan objek 9 adalah halaman 3. Penomoran objek tidak relevan. Kode apa pun yang mengulangi objek dalam urutan numerik dan mengumpulkan objek dengan /Type /Page akan menghasilkan urutan yang salah pada file ini
Mengapa pembuat (generators) menghasilkan tata letak yang tidak berurutan? Beberapa alasan. Sebuah pustaka yang mengalokasikan nomor objek untuk semua halaman sebelum menulis kontennya akan menomorinya dalam urutan pembuatan, lalu menulis bita aktual dalam urutan apa pun yang sesuai dengan penserial (serializer). Alat penggabung yang menjahit dokumen bersama-sama menomori ulang objek dari setiap dokumen sumber untuk menghindari tabrakan; objek halaman yang dinomori ulang berakhir tersebar di seluruh tabel objek gabungan sementara larik akar /Kids yang baru menampung urutan tampilan yang benar. Pembaruan bertahap menambahkan objek baru di akhir file dengan nomor baru, jadi halaman yang ditambahkan sebagai revisi hidup di dekat bagian akhir aliran bita meskipun letaknya di posisi 1 dari urutan tampilan
Pohon datar dan sub-pohon bersarang
Spesifikasi tersebut mengizinkan dua bentuk untuk pohon halaman. Pembuat (generators) sederhana menghasilkan struktur datar: satu simpul akar /Pages yang larik /Kids-nya tidak berisi apa pun selain objek daun /Page. Itu mudah ditelusuri: sedalam satu tingkat, satu lintasan
Dokumen besar secara rutin menggunakan pohon seimbang (balanced tree) sebagai gantinya. Larik /Kids dari simpul akar /Pages berisi simpul /Pages menengah, yang masing-masing pada gilirannya menyimpan larik /Kids-nya sendiri. /Count pada setiap simpul menengah melaporkan jumlah total halaman daun dalam sub-pohonnya, sehingga penampil dapat melewati seluruh sub-pohon saat melompat ke suatu halaman berdasarkan indeks tanpa mengurai setiap objek. Dokumen 1.000 halaman yang terstruktur sebagai pohon seimbang dengan 10 halaman per simpul daun dapat menemukan halaman 750 melalui pencarian biner melalui tiga atau empat pencarian kamus alih-alih memindai 750 entri /Kids
Konsekuensi untuk kode pemrosesan: Anda tidak dapat berasumsi tingkat pertama dari /Kids berisi objek /Page. Setiap anak harus diperiksa. Jika /Type-nya adalah /Pages, rekursi ke dalamnya. Jika /Type-nya adalah /Page, itu adalah daun. Berhenti di tingkat pertama secara diam-diam membuang seluruh sub-pohon pada dokumen apa pun di mana pembuatnya memilih untuk bersarang. Mengapa penulis memilih pohon yang dalam sejak awal, apa yang diserahkan oleh alat perata (flattening), dan bagaimana korupsi /Count dimainkan dalam praktiknya dibahas dalam bagian pendamping kami tentang bentuk pohon halaman, fan-out, dan integritas /Count
Atribut halaman yang diwariskan
Pohon halaman juga membawa mekanisme berbagi sumber daya. Atribut halaman tertentu: /MediaBox, /CropBox, /Resources, dan /Rotate dapat diwariskan (ISO 32000-2 §7.7.3.4). Jika kamus /Page menghilangkan salah satunya, pembaca berjalan ke atas rantai /Parent hingga menemukan atribut tersebut atau mencapai akar. Menempatkan kamus font bersama di simpul akar /Pages alih-alih menyalinnya ke setiap halaman daun dapat mengurangi ukuran file secara nyata untuk dokumen yang menggunakan tipografi yang sama secara keseluruhan
Aturan pewarisan menciptakan kehalusan untuk kode yang membaca properti halaman. Membaca /MediaBox langsung dari objek /Page dan memperlakukan kunci yang hilang sebagai kesalahan adalah salah; kunci tersebut mungkin diwariskan begitu saja. Kode yang dengan benar menyelesaikan geometri halaman harus mengikuti rantai induk (parent). Ini juga membutuhkan penjaga siklus: file yang rusak dapat memiliki referensi /Parent yang menunjuk kembali ke simpul yang sudah dikunjungi, yang akan berulang selamanya tanpa pemeriksaan objek yang dikunjungi
Tabel xref dan aliran referensi silang
Pencarian objek tidak langsung melewati tabel referensi silang (atau penerusnya, aliran referensi silang yang diperkenalkan di PDF 1.5). Xref memetakan setiap nomor objek ke offset bita di dalam file. Pembaca yang patuh menggunakan xref untuk melompat langsung ke objek apa pun; ia tidak memindai file secara berurutan. Desain akses-acak (random-access) itulah yang memungkinkan lompatan halaman yang cepat: penampil membaca katalog, menyelesaikan referensi /Pages melalui xref, membaca simpul akar /Pages, menyelesaikan entri /Kids, dan seterusnya, menyentuh hanya objek yang dibutuhkannya
Pembaruan bertahap menambahkan bagian xref baru di akhir file dengan trailer (cuplikan) yang dirantai kembali ke yang sebelumnya. Objek yang diperbarui dalam suatu revisi mendapat entri baru di bagian xref yang ditambahkan; bita aslinya tetap di tempatnya tetapi digantikan. Inilah bagaimana PDF yang ditandatangani secara digital tetap dapat diverifikasi bahkan setelah anotasi atau revisi pengisian formulir ditambahkan: rentang bita yang ditandatangani tidak pernah disentuh, dan konten baru hidup di bagian yang ditambahkan. Pohon halaman dapat diperbarui juga, jadi penambahan atau penghapusan halaman dalam revisi menghasilkan akar /Pages baru dengan larik /Kids yang direvisi, sementara objek akar lama masih menempati posisi aslinya di file. File yang dilinierisasi (dioptimalkan-web) menambahkan lilitan tata letak bita: objek untuk halaman 1 secara fisik dipindahkan ke bagian depan file sehingga penampil dapat menampilkan halaman pertama sementara sisanya masih mengunduh, namun pohon halaman tetap menjadi satu-satunya otoritas dalam urutan — hanya offset yang dicatat di xref yang berubah
Apa yang salah tanpa penjelajahan pohon
Mode kegagalan untuk pendekatan pemindaian objek adalah senyap (quiet). Dokumen keluaran terlihat masuk akal: jumlah halamannya tepat dan setiap halaman berisi konten yang dapat dikenali. Urutannya salah begitu saja, dan salah dengan cara yang bergantung pada pembuat (generator), jumlah revisi, dan apakah ada halaman yang digabungkan dari sumber luar. Korpus uji coba file yang diproduksi oleh satu alat mungkin lolos sepenuhnya; file dari alat yang berbeda atau alur kerja penggabungan akan gagal. Ketidakkonsistenan itulah sebabnya perbaikan heuristik tidak pernah bertahan. Untuk penelusuran kegagalan persis ini di dokumen pelanggan nyata — gejala, salah diagnosis, dan perbaikan penjelajahan — lihat studi kasus perbaikan bug urutan halaman kami
File pembaruan bertahap sangat rentan terhadap hal ini karena halaman yang ditambahkan atau diatur ulang dalam revisi selanjutnya membawa nomor objek tinggi sedangkan urutan tampilan dikontrol oleh larik /Kids yang diperbarui. Pemindaian yang memproses objek dalam urutan numerik akan menempatkan halaman yang dinomori belakangan itu di akhir, di mana pun pohon tersebut mengatakan letaknya
Perbaikannya tidak rumit. Mulai dari katalog, selesaikan referensi /Pages, telusuri larik /Kids secara rekursif, dan pancarkan daun-daun dalam urutan Anda menemuinya. Itulah urutan tampilan menurut definisi, terlepas dari nomor objek, offset bita, atau struktur file. Sebagian besar pustaka PDF yang matang memaparkan jumlah halaman dan pengakses halaman berindeks yang telah melakukan ini dengan benar; risikonya ada pada kode yang melewati model halaman pustaka dan menyentuh lapisan objek secara langsung
Satu anomali struktural yang patut ditangani secara eksplisit: nilai /Count pada simpul menengah /Pages bisa salah di file yang tidak terbentuk dengan baik. Mempercayai /Count untuk pemeriksaan batas (bounds checking) lalu berhenti sebelum penjelajahan penuh secara diam-diam akan menghilangkan halaman saat jumlah tersebut kurang dilaporkan. Menggunakan /Count hanya sebagai petunjuk kinerja untuk pra-alokasi kapasitas atau pencarian biner, dan memperoleh jumlah aktual dari penjelajahan adalah pola yang lebih aman untuk dokumen-dokumen penting