Lewati ke konten
getnextpdf.com

Anatomi sebuah berkas PDF

Buka PDF mana pun di sebuah editor teks biasa dan hal pertama yang Anda lihat menenangkan: sebuah header %PDF-1.x atau %PDF-2.0. Hal terakhir yang Anda lihat adalah %%EOF. Semua yang berada di antara dua baris itu adalah mesin mungil yang rapi untuk menemukan objek berdasarkan nomor. Halaman ini adalah pembedahan. Kita membuka berkasnya, menamai setiap organnya, dan menunjukkan bagaimana mereka terhubung.

Ia adalah pendamping struktural bagi dua tetangga. Apa sebenarnya sebuah PDF memperlakukan berkas sebagai graf objek; pembaruan inkremental membahas bagaimana ia tumbuh seiring waktu. Halaman ini tetap dekat dengan byte-nya — region fisik yang dilewati sebuah parser, dalam urutan yang mereka tempati di disk.

Anda hampir tidak pernah membutuhkan ini untuk menggunakan sebuah PDF. Anda membutuhkannya pada hari salah satunya bermasalah. Sebuah berkas terbuka di satu penampil dan tidak di penampil lain; sebuah validator melaporkan “tabel referensi silang rusak”; sebuah dokumen yang ditandatangani tiba-tiba gagal diverifikasi. Tidak ada satu pun dari itu yang menjadi misteri begitu Anda dapat membaca anatominya. Itu adalah sebuah angka yang tidak lagi cocok dengan sebuah posisi, sebuah region di tempat yang salah, atau sebuah ekor yang menunjuk ke ketiadaan.

Mengetahui tata letaknya mengubah “PDF-nya korup” menjadi sebuah diagnosis yang dapat Anda tindaklanjuti. Itu adalah perbedaan antara mengangkat bahu di hadapan kotak hitam dan menunjuk ke byte persis yang berbohong.

Sebuah PDF yang konform memiliki empat bagian fisik, dalam urutan berkas ini (Spec: ISO 32000-2, §7.5.1):

  1. Sebuah header — satu baris, %PDF-2.0, menamai versinya.
  2. Sebuah body — bagian terbesar berkas: serangkaian objek tak langsung bernomor.
  3. Sebuah cross-reference section — indeks dari nomor objek ke offset byte tempat objek itu berada. PDF klasik memakai tabel teks; PDF 2.0 memakai xref stream terkompresi.
  4. Sebuah trailer — sebuah kamus kecil yang menamai titik masuknya, diikuti oleh startxref, sebuah offset, dan %%EOF.

Kejutannya: pembaca tidak memulai dari atas. Ia memulai dari bawah, membaca startxref untuk menemukan indeksnya, dan menggunakan indeks itu untuk menjangkau objek apa pun secara langsung. Berkasnya ditulis dari depan ke belakang tapi dibaca dari belakang ke depan.

Mari kita susuri keempat region itu secara berurutan, dengan byte-nya di hadapan kita.

Header adalah satu baris. NextPDF menulis %PDF-2.0, dan secara konvensi sebuah baris komentar kedua berisi byte ber-bit-tinggi sehingga alat transfer yang naif memperlakukan berkasnya sebagai biner, bukan teks. Baris kedua itulah sebabnya sebuah PDF yang dibuka sebagai teks biasa menampilkan sedikit kekacauan tepat setelah versinya.

Body adalah tempat dokumen berada. Setiap objek tak langsung adalah sebuah nomor, sebuah generasi, kata kunci obj, sebuah nilai, dan endobj (Spec: ISO 32000-2, §7.3.10). Nilainya adalah salah satu dari beberapa bentuk — tetapi dua di antaranya menanggung hampir seluruh bebannya:

  • Sebuah dictionary, << /Key value … >>, adalah peta nama ke nilai. Pohon halaman, katalog, deskriptor font: semuanya dictionary.
  • Sebuah stream adalah dictionary yang diikuti oleh stream, sebuah blok byte sembarang, dan endstream. Konten halaman, font tersemat, dan gambar adalah stream, hampir selalu terkompresi. (Filternya adalah cerita tersendiri, dikisahkan di streams dan filters.)

Objek saling menunjuk melalui indirect reference2 0 R berarti “objek 2, generasi 0.” Itulah pengkabelan yang mengubah daftar objek yang datar menjadi sebuah graf.

Cross-reference section adalah bagian yang paling tidak dibayangkan dengan benar oleh kebanyakan orang. Dalam bentuk klasik ia berupa teks biasa: kata kunci xref, lalu subbagian berupa baris 20-byte berlebar tetap (Spec: ISO 32000-2, §7.5.4). Setiap baris adalah offset byte sepuluh digit, generasi lima digit, dan satu flag — n untuk in-use, f untuk free — yang dipadkan hingga tepat dua puluh byte sehingga pembaca dapat melompat ke entri mana pun hanya dengan aritmetika. PDF 2.0 menggantinya dengan cross-reference stream: indeks yang sama, tetapi biner dan terkompresi di dalam objek stream yang ditandai /Type /XRef (Spec: ISO 32000-2, §7.5.8). Lebih kecil, dan mampu mendeskripsikan objek yang dikemas di dalam object stream.

Trailer adalah daftar isi berkasnya (Spec: ISO 32000-2, §7.5.5). Ia menamai /Root — katalog dokumen, satu-satunya objek tempat segala sesuatu lainnya bergantung — dan /Size, jumlah objeknya. Lalu datang jabat tangan yang memungkinkan pembacaan mundur: startxref, sebuah offset byte di barisnya sendiri, dan %%EOF. Pembaca melompat ke akhir, membaca offset itu, melesat langsung ke cross-reference section, dan mulai bekerja.

  1. HeaderOne line, %PDF-2.0, naming the version. A binary-marker comment usually follows.
  2. BodyNumbered indirect objects — dictionaries and streams — referenced by N G R.
  3. Cross-reference sectionA text table of 20-byte entries, or a compressed /Type /XRef stream in PDF 2.0.
  4. TrailerNames /Root and /Size, then startxref + offset + %%EOF.
  5. Read orderA reader starts at %%EOF, follows startxref to the index, then reaches each object directly.
The four physical regions of a PDF in file order, and the path a reader actually takes through them — starting at the trailer and working inward via the cross-reference section.

Ada region kelima yang ditumbuhkan oleh berkas berumur panjang: sebuah pembaruan inkremental. Sebuah perubahan tidak ditulis di tempat. Objek yang berubah, sebuah cross-reference section yang baru, dan sebuah trailer baru ditambahkan setelah %%EOF pertama, dan trailer baru itu membawa /Prev — offset dari cross-reference section sebelumnya (Spec: ISO 32000-2, §7.5.6). Section-section itu membentuk rantai mundur; untuk nomor objek apa pun, entri terbaru yang menang. Karena byte aslinya tidak pernah bergerak, pemeriksaan kriptografis atas rentang byte yang sebenarnya dicakup sebuah tanda tangan tetap berlaku setelah sebuah pembaruan. Pembaruan inkremental berikutnya masih dapat mengubah apa yang dilaporkan validator tentang dokumen secara keseluruhan — apakah perubahan pasca-tanda tangan diizinkan, dan apa yang dianggap disertifikasi oleh tanda tangan itu — tetapi ia tidak dapat mengubah byte yang ditandatangani itu sendiri. Properti itu adalah seluruh pokok bahasan pembaruan inkremental.

Inilah seluruh anatomi dalam satu berkas minimal. Angka-angka di bawah xref adalah offset byte, dan angka itu harus persis — tunjuk satu karakter melewati tempat sebuah objek dimulai dan pembaca yang ketat menyerah.

%PDF-2.0
1 0 obj
<< /Type /Catalog /Pages 2 0 R >>
endobj
2 0 obj
<< /Type /Pages /Kids [3 0 R] /Count 1 >>
endobj
3 0 obj
<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] >>
endobj
xref
0 4
0000000000 65535 f
0000000009 00000 n
0000000058 00000 n
0000000115 00000 n
trailer
<< /Size 4 /Root 1 0 R >>
startxref
186
%%EOF

Bacalah seperti yang dilakukan sebuah parser. Baris terakhir: %%EOF. Di atasnya: startxref 186, jadi lompat ke byte 186, tempat xref dimulai. Tabelnya mengatakan objek 1 berada di byte 9. /Root 1 0 R milik trailer menunjuk ke sana — katalognya — dan dari katalog itu Anda menyusuri /Pages ke pohon halaman dan menemukan satu-satunya halaman. Objek 0 selalu menjadi kepala daftar-bebas dengan generasi 65535, sebuah fosil dari desain pertama format ini yang masih diharapkan ada oleh setiap pembaca.

Jebakannya adalah membaca sebuah PDF seperti sebuah cerita — dari atas ke bawah, secara berurutan. Ia bukan cerita; ia adalah indeks dengan penunjuk mundur. Nomor objek tidak harus berurutan dalam berkas, objek dapat muncul dalam urutan fisik apa pun, dan pembaca tidak pernah bergantung pada posisinya. Peta yang otoritatif satu-satunya adalah cross-reference section, dan satu-satunya cara menemukan peta itu adalah offset startxref di paling akhir.

Konsekuensinya mengejutkan orang. Sebuah PDF dengan body sempurna dan satu digit salah pada startxref tidak dapat dibaca — pembaca tidak dapat menemukan indeksnya. Sebuah PDF dengan objeknya dalam urutan teracak tetapi dengan cross-reference section yang benar baik-baik saja. Posisi fisik tidak membawa makna. Posisi yang tercatat-lah yang membawa semuanya.

Halaman ini menjelaskan struktur fisik, bukan konten halaman. Bagaimana tanda mendarat di sebuah halaman — operator content-stream, penampilan teks, graphics state — adalah subjek tersendiri. Ia juga menjelaskan berkas yang terbentuk dengan baik. PDF di dunia nyata sering kali sedikit rusak dan bertahan hanya karena penampil yang pemaaf membangun ulang tabel referensi silang dengan memindai kata kunci obj. Penyelamatan itu adalah perilaku penampil, bukan sesuatu yang dijamin oleh format.

Reading and repairing arbitrary third-party PDFs — edition availability
EditionAvailability
CoreNextPDF is a writer. It records every offset from the output buffer at the moment each object is emitted, so the files it produces have a cross-reference section that matches the body by construction.
ProParsing, reconstructing, or repairing a damaged cross-reference table in a file NextPDF did not write is out of scope across every edition.
EnterpriseFor inspection of an existing file’s structure, use a dedicated parser or validator; NextPDF guarantees correctness for what it writes, not for what it reads.

Mengapa sebuah PDF memiliki baris penanda biner setelah header? Beberapa alat transfer lawas akan mengacaukan berkas yang mereka kira teks biasa. Komentar ber-bit-tinggi membuat berkasnya terlihat tak ambigu sebagai biner, sehingga ia bertahan melalui perjalanan tanpa perubahan.

Apakah xref stream hanya tabel yang lebih kecil? Sebagian besar, dengan satu kekuatan ekstra. Selain terkompresi, sebuah xref stream dapat mendeskripsikan objek yang disimpan di dalam object stream — entri yang tidak ada cara untuk diungkapkan oleh tabel teks 20-byte klasik.

Bisakah saya memiliki tabel dan stream sekaligus dalam satu berkas? Satu revisi tunggal memakai salah satu dari keduanya. Tetapi sebuah berkas hibrida dapat memasangkan tabel klasik untuk pembaca lama dengan cross-reference stream untuk yang baru, sehingga tiap jenis pembaca menemukan indeks yang ia pahami.

  • Header — baris pertama, %PDF-2.0, menamai versinya; biasanya diikuti oleh komentar penanda biner.
  • Objek tak langsung (indirect object) — sebuah objek bernomor di body, ditulis N G obj … endobj, di mana N adalah nomor objek dan G generasinya.
  • Dictionary — sebuah peta << /Key value … >> dari nama ke nilai; bentuk objek yang paling umum.
  • Stream — sebuah dictionary ditambah satu blok byte di antara stream dan endstream, dipakai untuk konten, font, dan gambar.
  • Cross-reference table (xref) — indeks dari nomor objek ke offset byte; sebuah tabel teks 20-byte-per-entri secara klasik, sebuah stream /Type /XRef pada PDF 2.0.
  • Trailer — kamus yang menamai /Root dan /Size, ditemukan melalui offset startxref di akhir berkas.
  • Pembaruan inkremental (incremental update) — objek yang berubah, sebuah cross-reference section baru, dan sebuah trailer baru yang ditambahkan setelah %%EOF, dengan /Prev merantai mundur ke section sebelumnya.