Pembaca PDF tidak memulai dari awal file. Ia memulai dari akhir. Beberapa byte terakhir menyimpan alamat dari semua yang lain, dan parser yang tidak memahami urutan tersebut akan salah membaca format dari baris pertama. Jadi cara paling berguna untuk mempelajari PDF di disk adalah dengan mempelajarinya seperti yang dilakukan pembaca: dari ekor terlebih dahulu, lalu melompat mundur ke peta, lalu menyelesaikan objek yang ditunjuk peta tersebut
Byte itu sendiri cukup mudah dibaca di editor teks saat tidak ada yang dikompres. Dokumen satu halaman minimal yang menggambar "Hello, World!" muat dalam kurang dari lima ratus byte, dan setiap elemen struktural format terlihat di dalamnya. Berikut adalah seluruh file, dengan empat bagian ditandai:
%PDF-1.0 % Header
%âãÏÓ
1 0 obj % Body: the object sequence
<<
/Kids [2 0 R]
/Count 1
/Type /Pages
>>
endobj
2 0 obj
<<
/Rotate 0
/Parent 1 0 R
/Resources 3 0 R
/MediaBox [0 0 612 792]
/Contents [4 0 R]
/Type /Page
>>
endobj
3 0 obj
<< /Font << /F0 << /BaseFont /Times-Italic /Subtype /Type1 /Type /Font >> >> >>
endobj
4 0 obj
<< /Length 65 >>
stream
1. 0. 0. 1. 50. 700. cm BT
/F0 36. Tf
(Hello, World!) Tj
ET
endstream
endobj
5 0 obj
<< /Pages 1 0 R /Type /Catalog >>
endobj
xref % Cross-reference table
0 6
0000000000 65535 f
0000000015 00000 n
0000000074 00000 n
0000000192 00000 n
0000000291 00000 n
0000000409 00000 n
trailer % Trailer
<<
/Root 5 0 R
/Size 6
>>
startxref
459
%%EOF
Empat bagian, selalu dalam urutan ini di sepanjang file: header, body berisi objek, tabel cross-reference, dan trailer. Permasalahannya adalah Anda membacanya hampir dalam urutan terbalik. ISO 32000-2 §7.5.1 menjabarkan anatomi empat bagian yang sama, dan alasan akses dari-belakang-ke-depan ini murni praktis: pembaca yang langsung melompat ke objek yang dibutuhkan jauh lebih cepat daripada yang memindai setiap byte dari atas, dan akses acak itulah tepatnya yang dibuat oleh trailer dan tabel cross-reference untuk disediakan
Header terdiri dari dua baris, dan baris kedua adalah yang penting
Baris pertama adalah %PDF-1.0. Tanda persen menjadikannya komentar sejauh menyangkut sintaksis, tetapi pembaca memperlakukannya sebagai tanda tangan file dan mengekstrak nomor versi dari sana. Penanganan versi cukup longgar dalam praktiknya. Pembaca yang dibuat untuk PDF 2.0 akan dengan senang hati membuka file yang mengklaim 1.0, dan sebagian besar pembaca akan mencoba file yang versi yang dideklarasikannya salah atau baris versinya terkubur sedikit ke dalam file daripada berada di byte nol. Nomor itu adalah petunjuk tentang fitur yang diharapkan, bukan gerbang pemblokir
Baris kedua adalah yang dihapus orang secara tidak sengaja lalu menghabiskan sore hari untuk mendebugnya. Ini juga komentar, tetapi muatannya adalah empat byte di atas ASCII 127. Byte-byte tersebut ada agar segala sesuatu yang memindahkan file dalam "mode teks" mengenalinya sebagai biner dan berhenti menulis ulang akhir baris. PDF membawa stream terkompresi yang byte-nya secara kebetulan dapat cocok dengan carriage return atau line feed; jika alat transfer menulis ulang byte-byte tersebut, panjang stream yang dicatat dalam kamus tidak lagi cocok dengan byte di disk dan file tersebut rusak. Komentar high-byte adalah pertahanan empat puluh tahun terhadap FTP dalam mode ASCII, dan masih ada di setiap file yang ditulis alat serius karena kegagalan yang dicegahnya bersifat diam dan total
Body menyimpan objek, masing-masing bernomor
Semua yang membentuk dokumen hidup di body sebagai urutan datar dari objek tidak langsung. Masing-masing dibuka dengan dua bilangan bulat dan kata kunci obj, menyimpan isinya, dan ditutup dengan endobj. Objek 1 dalam sampel di atas adalah node pohon halaman: 1 0 obj, lalu sebuah kamus, lalu endobj. Bilangan bulat pertama adalah nomor objek, yang kedua adalah nomor generasi. Generasi hampir selalu nol dalam file yang baru ditulis; itu naik hanya ketika nomor objek digunakan kembali melintasi beberapa pengeditan, yang cukup jarang sehingga Anda dapat memperlakukan generasi bukan nol sebagai tanda bahwa file telah melalui pembaruan inkremental. Konten antara kata kunci adalah kamus di sini, ditulis di antara << dan >>, tetapi bisa saja berupa angka, string, array, atau stream
Yang menjadikan ini grafik daripada daftar adalah token referensi 2 0 R. Artinya "objek 2, generasi 0, di mana pun ia berada dalam file." Node pohon halaman di atas tidak berisi halamannya; ia menunjuk ke objek 2, yang menunjuk ke sumber daya dan stream kontennya dengan mekanisme yang sama. Body disusun dalam urutan apa pun yang ditemukan penulis sebagai yang paling nyaman, dan referensi menjahitnya menjadi pohon yang berakar pada katalog. Posisi dalam file tidak memiliki arti. Identitas berasal dari nomor objek, dan lokasi berasal dari tabel cross-reference
Tabel cross-reference adalah indeks offset byte
Tabel xref adalah yang mengubah nomor objek menjadi posisi file. Inilah alasan pembaca dapat membuka dokumen seribu halaman dan merender halaman 850 tanpa mengurai 849 halaman sebelumnya. Setiap entri mencatat dengan tepat di mana objeknya dimulai, dihitung dalam byte dari awal file:
xref
0 6 % 6 entries, starting at object 0
0000000000 65535 f % entry 0: head of the free list
0000000015 00000 n % object 1 begins at byte 15
0000000074 00000 n % object 2 begins at byte 74
0000000192 00000 n % object 3 begins at byte 192
0000000291 00000 n % object 4 begins at byte 291
0000000409 00000 n % object 5 begins at byte 409
Lebar tetap disengaja. Setiap entri tepat dua puluh byte: offset sepuluh digit, spasi, generasi lima digit, spasi, tipe satu karakter, dan akhir baris dua byte. Karena barisnya seragam, pembaca dapat mengindeks langsung ke entri untuk objek n dengan aritmatika daripada memindai, sehingga tabel yang memberikan akses acak ke body itu sendiri dapat diakses secara acak. Baris 0 6 adalah header subbagian: itu menyatakan bahwa entri berikutnya mendeskripsikan enam objek mulai dari nomor 0
Objek 0 bersifat khusus dan selalu ada. Tipenya adalah f untuk bebas, generasinya adalah 65535, dan ia memimpin daftar tertaut dari nomor objek bebas. Dalam file yang tidak pernah diedit, daftar bebas hanyalah satu entri ini, sebuah formalitas. Ia mendapatkan kegunaannya selama pembaruan inkremental, ketika menghapus objek menambahkan nomornya ke daftar tersebut agar pengeditan selanjutnya dapat mengambil kembali nomor tersebut. Entri lainnya bertipe n untuk sedang-digunakan, dan nomor sepuluh digitnya adalah offset yang akan Anda cari untuk membaca definisi objek tersebut
Trailer adalah titik masuk, dan ia berada di akhir
Trailer adalah hal pertama yang sebenarnya dikonsumsi pembaca, meskipun ditulis terakhir. Parser membuka file, mencari ke akhir, dan berjalan mundur mencari %%EOF. Tepat di atasnya terdapat startxref diikuti satu angka, dan angka itu adalah offset byte dari kata kunci xref. Dengannya, pembaca langsung melompat ke tabel cross-reference tanpa memindai satu objek pun:
trailer
<<
/Root 5 0 R % the document catalog
/Size 6 % one more than the highest object number
>>
startxref
459 % byte offset of the xref table
%%EOF
Kamus trailer membawa dua nilai yang dibutuhkan pembaca sebelum dapat melakukan apapun. /Root menunjuk ke katalog dokumen, objek 5 di sini, yang merupakan puncak grafik objek dan rute ke pohon halaman. /Size adalah jumlah entri yang seharusnya dikandung tabel cross-reference, yang satu lebih dari nomor objek tertinggi karena entri bebas di slot nol. Dari %%EOF seluruh urutan pembacaan menjadi jelas: temukan penanda, baca startxref untuk menemukan tabel, muat tabel untuk mengetahui di mana setiap objek berada, baca /Root untuk menemukan katalog, dan selesaikan objek sesuai permintaan dari sana. Header, yang berada di atas, hampir tidak dikonsultasikan hingga akhir. Peta di bagian bawah adalah yang pertama kali dibutuhkan pembaca
Pembaruan inkremental menambahkan peta kedua daripada menulis ulang
Desain tail-first itu terbayar ketika file berubah. PDF dapat diedit tanpa menulis ulang byte apapun yang sudah ada di disk. Objek baru dan yang dimodifikasi ditambahkan ke akhir, diikuti bagian cross-reference baru dan trailer baru, dan file asli di bawahnya dibiarkan tidak tersentuh. Satu-satunya pembukuan baru adalah entri /Prev dalam trailer baru, yang menyimpan offset byte dari tabel cross-reference sebelumnya:
% ... original file, unchanged, ends here ...
6 0 obj % an object added by this edit
<< /Type /Annot /Subtype /Text /Rect [100 700 120 720] >>
endobj
xref % a second xref section, for the new object only
6 1
0000000612 00000 n
trailer
<<
/Root 5 0 R
/Size 7
/Prev 459 % byte offset of the earlier xref table
>>
startxref
680 % offset of this new xref section
%%EOF
Pembaca tetap memulai dari %%EOF terakhir, tetap mengikuti startxref ke tabel terbaru, tetapi sekarang mengikuti rantai /Prev ke belakang ke tabel yang lebih lama, menggabungkannya sehingga entri terbaru untuk nomor objek manapun menang. Bagian cross-reference membentuk daftar tertaut ke bawah sepanjang file, masing-masing menimpa yang sebelumnya untuk objek yang disentuhnya. Objek yang digantikan oleh pengeditan masih ada secara fisik di offset lamanya; objek itu hanya tidak lagi dapat dijangkau, karena entri xref yang lebih baru menunjuk ke lokasi yang lebih baru
Inilah mekanisme yang membuat PDF yang ditandatangani dapat diverifikasi. Tanda tangan digital mencakup rentang byte file, dan karena pembaruan inkremental hanya menambah, byte yang ditandatangani tidak pernah bergerak. Tanda tangan tetap memvalidasi terhadap rentang asli sementara revisi selanjutnya berada di luarnya, masing-masing dengan xref dan trailernya sendiri. Ini juga alasan mengapa PDF dapat membawa riwayat yang dapat dipulihkan: setiap objek yang digantikan masih ada di disk di bawah bagian cross-reference yang lebih lama, yang merupakan fitur untuk pelacakan versi dan kelemahan bagi siapa saja yang mengira "hapus" berarti byte telah hilang
Membaca empat bagian dalam praktik
Mengetahui tata letak sudah cukup untuk men-debug sebagian besar masalah "file ini tidak bisa dibuka" secara manual. Jika pembaca menolak PDF, tersangka biasanya ada di kedua ujung, bukan di tengah. Unduhan yang terpotong kehilangan trailer, sehingga startxref atau %%EOF hilang dan pembaca tidak memiliki titik masuk; pembaca yang toleran beralih ke memindai seluruh file untuk membangun kembali xref, yang persis merupakan jalur lambat yang dimaksudkan untuk dihindari oleh tabel tersebut. Transfer mode-teks yang kacau merusak byte stream atau offset berhenti cocok dengan kenyataan, dan objek dimuat dari posisi yang salah. Ketika offset dalam tabel tidak lagi menunjuk ke kata kunci obj yang nyata, file secara struktural rusak bahkan jika setiap objek secara individual baik-baik saja
Untuk kode baru, pelajaran dari tata letak ini adalah membiarkan pustaka mengelola pembukuan byte. Offset dalam tabel cross-reference harus sesuai dengan posisi aktual setiap objek hingga ke byte, trailer harus menunjuk ke tabel yang benar, dan pembaruan inkremental harus dirantai dengan benar melalui /Prev. Komponen asli seperti HotPDF Component untuk Delphi dan C++Builder menangani semua itu saat menulis file, termasuk pilihan antara menambahkan revisi inkremental dan menulis ulang yang ringkas. Jika Anda ingin melihat struktur yang sama dibangun dari nol daripada dibedah, tulisan pendamping tentang membangun dokumen PDF dari nol memandu penerbitan header, objek, xref, dan trailer secara berurutan