Teknik Makale

PDF Dosya Yapısı: Başlık (Header), Gövde (Body), Xref ve Fragman (Trailer)

Bir PDF okuyucusu (reader) dosyanın başından başlamaz. Sonundan başlar. Son birkaç bayt diğer her şeyin adresini tutar ve bu sırayı anlamayan bir ayrıştırıcı (parser), formatı (format) ilk satırdan itibaren yanlış okur. Yani diskteki PDF'i öğrenmenin en yararlı yolu onu bir okuyucunun yaptığı gibi öğrenmektir: önce kuyruk (tail), sonra geriye doğru haritaya (map) atla, sonra haritanın işaret ettiği nesneleri (objects) çöz (resolve)

Hiçbir şey sıkıştırılmadığında baytların kendileri bir metin düzenleyicide (text editor) okumak için yeterince sadedir. "Merhaba, Dünya!" ("Hello, World!") çizen minimal tek sayfalık bir belge beş yüz baytın altına sığar ve formatın her yapısal elemanı (structural element) içinde görünürdür. İşte dört parçası işaretlenmiş olarak tüm dosya:

%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

Dört parça, her zaman dosyanın aşağısına (down the file) bu sırayla: bir başlık (header), bir nesneler (objects) gövdesi (body), bir çapraz başvuru (cross-reference) tablosu ve bir fragman (trailer). İşin püf noktası, onları neredeyse ters (reverse) sırayla okumanızdır. ISO 32000-2 §7.5.1 aynı dört parçalı anatomiyi düzenler (lays out) ve arkadan öne (back-to-front) erişimin nedeni tamamen pratiktir: doğrudan ihtiyaç duyduğu nesneye atlayan bir okuyucu, her baytı en baştan (from the top) tarayan birinden çok daha hızlıdır ve rastgele (random) erişim tam olarak fragmanın ve çapraz başvuru tablosunun sağlamak için var olduğu şeydir

Başlık (header) iki satırdır ve ikincisi önemlidir

İlk satır %PDF-1.0'dır. Yüzde işareti, sözdizimi (syntax) söz konusu olduğunda onu bir açıklama (comment) yapar, ancak okuyucular (readers) ona dosya imzası (signature) muamelesi yapar ve sürüm numarasını ondan çeker. Sürüm (version) işleme pratikte gevşektir (loose). PDF 2.0 için oluşturulmuş bir okuyucu, 1.0 olduğunu iddia eden bir dosyayı memnuniyetle açar ve çoğu okuyucu, beyan (declared) edilen sürümü yanlış olan veya sürüm satırı bayt sıfır (byte zero) yerine dosyanın biraz (a little way) içine gömülmüş (buried) olan bir dosyayı dener. Sayı bir kapı (gate) değil, hangi özelliklerin bekleneceği konusunda bir ipucudur (hint)

İkinci satır, insanların yanlışlıkla sildiği (delete by accident) ve ardından hata ayıklamak (debugging) için bir öğleden sonra harcadıkları satırdır. O da bir açıklamadır, ancak yükü (payload) ASCII 127'nin üzerinde dört bayttır. Dosyayı "metin modunda" ("text mode") taşıyan herhangi bir şeyin onu ikili (binary) olarak tanıması (recognizes) ve satır sonlarını (line endings) yeniden yazmayı durdurması için var olurlar. Bir PDF, baytları tesadüfen bir satır başı (carriage return) veya satır besleme (line feed) ile eşleşebilen sıkıştırılmış (compressed) akışlar (streams) taşır; eğer bir aktarım (transfer) aracı bunları yeniden yazarsa, sözlükte (dictionary) kaydedilen akış uzunluğu artık diskteki baytlarla eşleşmez ve dosya bozulur (corrupt). Yüksek baytlı (high-byte) açıklama, ASCII modunda FTP'ye karşı kırk yıllık bir savunmadır (defense) ve ciddi bir aracın yazdığı her dosyada hala mevcuttur çünkü önlediği arıza (failure) sessiz (silent) ve tamdır (total)

Gövde (body), her biri numaralandırılmış nesneleri tutar

Belgeyi oluşturan her şey gövdede (body), dolaylı nesnelerin (indirect objects) düz (flat) bir dizisi (sequence) olarak yaşar. Her biri iki tamsayı ve obj anahtar kelimesiyle (keyword) açılır, içeriğini tutar ve endobj ile kapanır. Yukarıdaki örnekteki Nesne 1 sayfa ağacı (page-tree) düğümüdür (node): 1 0 obj, ardından bir sözlük, ardından endobj. İlk tamsayı nesne numarasıdır, ikincisi üretim (generation) numarasıdır. Yeni yazılmış (freshly written) bir dosyada üretim neredeyse her zaman sıfırdır; yalnızca bir nesne numarası düzenlemeler (edits) boyunca yeniden kullanıldığında (reused) tırmanır, ki bu sıfır olmayan (nonzero) bir üretimi dosyanın artımlı güncellemelerden (incremental updates) geçtiğinin (been through) bir işareti olarak ele alabileceğiniz kadar nadirdir (rare). Anahtar kelimeler arasındaki içerik burada << ve >> arasında yazılan bir sözlüktür (dictionary), ancak aynı (just as well) bir sayı, bir dize (string), bir dizi (array) veya bir akış (stream) da olabilir

Bunu (this) bir liste (list) yerine bir grafik (graph) yapan şey referans (reference) belirtecidir (token) 2 0 R. Bu "nesne 2, üretim 0, dosyanın neresinde yaşarsa yaşasın" (wherever it happens to live) anlamına gelir. Yukarıdaki sayfa ağacı (page-tree) düğümü sayfasını içermez; kaynaklarına (resources) ve içerik akışına (content stream) aynı mekanizmayla işaret eden (points at) nesne 2'ye işaret eder. Gövde, yazarın (writer) hangi sırayı uygun bulduysa (found convenient) o şekilde (in whatever order) düzenlenir (laid out) ve referanslar onu katalogda (catalog) köklenmiş (rooted) bir ağaca (tree) diker (stitch). Dosyadaki konum hiçbir anlam (meaning) taşımaz. Kimlik (Identity) nesne numarasından, konum (location) ise çapraz başvuru (cross-reference) tablosundan gelir

Çapraz başvuru (cross-reference) tablosu bir bayt uzaklıkları dizinidir

xref tablosu, nesne numaralarını dosya konumlarına dönüştüren şeydir (what turns). Bir okuyucunun bin sayfalık bir belgeyi açıp ondan (before it) önceki 849 sayfayı ayrıştırmadan (parsing) 850. sayfayı oluşturabilmesinin (render) nedenidir. Her giriş (entry), dosyanın başından itibaren bayt olarak sayılan, nesnesinin tam olarak nerede başladığını kaydeder (records):

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

Sabit (fixed) genişlik (width) kasıtlıdır (deliberate). Her giriş tam olarak yirmi bayttır: on haneli (digit) bir uzaklık (offset), bir boşluk, beş haneli bir üretim (generation), bir boşluk, tek karakterli bir tür (type) ve iki baytlık bir satır sonu (end-of-line). Satırlar tek tip (uniform) olduğundan, bir okuyucu tarama (scanning) yerine aritmetik yoluyla doğrudan (straight to) n nesnesine yönelik girişi dizine ekleyebilir (index), böylece gövdeye rastgele erişim (random access) sağlayan tablo (table) kendisi de rastgele (randomly) erişilebilirdir. 0 6 satırı bir alt bölüm başlığıdır (subsection header): sonraki girişlerin 0 numarasından başlayan altı nesneyi (objects) açıkladığını söyler

Nesne 0 özeldir ve her zaman mevcuttur (present). Türü f (ücretsiz - free), üretimi 65535'tir ve boş (free) nesne numaralarının bağlantılı (linked) listesinin (list) başını çeker (heads). Hiç düzenlenmemiş (never been edited) bir dosyada boş liste yalnızca bu bir girdidir, bir formalite (formality). Bir nesneyi (object) silmenin numarasını (number) o listeye eklediği (adds) ve böylece daha sonraki bir düzenlemenin onu geri alabileceği (reclaim) artımlı güncellemeler (incremental updates) sırasında varlığını haklı çıkarır (earns its keep). Diğer girdiler kullanım (in-use) için n türündedir ve onların on haneli numarası o nesnenin tanımını okumak için (in order to) arayacağınız (seek to) uzaklıktır (offset)

Fragman (trailer) giriş noktasıdır ve sonda (end) oturur

Fragman (trailer), en son yazılmış olmasına rağmen okuyucunun (reader) gerçekte tükettiği ilk şeydir. Bir ayrıştırıcı (parser) dosyayı açar, sonu arar ve geriye doğru yürüyerek %%EOF'yi arar. Hemen üzerinde startxref ve ardından tek bir sayı oturur ve bu sayı xref anahtar kelimesinin (keyword) bayt uzaklığıdır (offset). Bununla okuyucu, tek bir nesne taramadan doğrudan çapraz başvuru tablosuna (cross-reference table) atlar:

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

Fragman sözlüğü (trailer dictionary), okuyucunun (reader) başka bir şey yapmadan önce ihtiyaç duyduğu iki değeri taşır. /Root, nesne grafiğinin (object graph) en üstünde (top) bulunan ve sayfa ağacına giden yol (route) olan, buradaki nesne 5 olan belge kataloğuna (document catalog) işaret eder. /Size, sıfır yuvasındaki (slot zero) boş (free) giriş nedeniyle en yüksek nesne numarasından bir fazla olan çapraz başvuru (cross-reference) tablosunun içermesi gereken giriş (entries) sayısıdır (count). %%EOF'den tüm okuma dizisi (sequence) ortaya çıkar (falls out): işaretçiyi (marker) bul, tabloyu bulmak için startxref'i oku, her nesnenin nerede yaşadığını öğrenmek için tabloyu (table) yükle, kataloğu (catalog) bulmak için /Root'u oku ve oradan (from there) talebe (on demand) göre nesneleri çöz (resolve). En üstte oturan başlığa, sonlara kadar pek başvurulmaz (barely consulted). En alttaki harita (map) okuyucunun ilk ihtiyacı olan (needs first) şeydir

Artımlı (Incremental) güncelleme (update), yeniden yazmak (rewriting) yerine (instead of) ikinci bir harita (map) ekler (appends)

Bu önce-kuyruk (tail-first) tasarımı (design) bir dosya (file) değiştiğinde meyvesini (pays off) verir. Bir PDF (PDF), diske zaten (already on disk) yazılmış olan (rewriting) baytların (bytes) hiçbiri (any) yeniden (rewriting) yazılmadan (without) düzenlenebilir. Yeni (New) ve değiştirilmiş nesneler sonuna eklenir, ardından (followed by) taze (fresh) bir çapraz başvuru bölümü ve taze bir fragman gelir (comes) ve alttaki (underneath) orijinal dosya (file) dokunulmamış (untouched) bırakılır (left). Defter tutmanın (bookkeeping) yeni bir parçası, yeni fragmandaki (trailer) önceki (previous) çapraz başvuru tablosunun bayt uzaklığını tutan bir /Prev girişidir (entry):

% ... 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

Bir okuyucu (reader) hala son %%EOF'de başlar, hala en yeni (most recent) tabloya giden startxref'i izler (follows), ancak şimdi daha eski tablolara geriye (backward) doğru giden /Prev zincirini (chain) takip eder (follows), herhangi bir nesne numarası (object number) için (for) en yeni (newest) girişin (entry) kazanacağı (wins) şekilde onları birleştirir (merging). Çapraz başvuru bölümleri (sections), her biri dokunduğu (touches) nesneler (objects) için kendisinden öncekini (before it) geçersiz kılan (overriding), dosya boyunca (down through) bağlantılı (linked) bir liste oluşturur (form). Bir düzenlemenin (edit) değiştirdiği (replaced) bir nesne (object), eski uzaklığında hala fiziksel olarak mevcuttur (exists); daha (later) sonraki bir xref girişi daha (somewhere) yeni bir yere işaret (points) ettiği için (because) artık basitçe ulaşılamazdır (reachable)

İmzalı PDF'leri doğrulanabilir kılan (verifiable) mekanizma budur. Dijital bir imza (signature) dosyanın bir bayt aralığını (range) kapsar (covers) ve artımlı (incremental) bir güncelleme (update) yalnızca ekleme (appends) yaptığı için, imzalanan baytlar asla hareket (move) etmez. İmza, daha (later) sonraki (later) revizyonlar (revisions) kendi (own) xref'i ve fragmanı (trailer) ile onun ötesinde (beyond) otururken, orijinal (original) aralığa karşı (against) hala doğrulanır (validates). Bir PDF'in (PDF) kurtarılabilir (recoverable) bir geçmiş (history) taşıyabilmesinin (carry) nedeni (why) de budur: değiştirilen (superseded) her nesne, sürüm izleme (tracking) için bir özellik (feature) ve "sil" (delete) kelimesinin baytların (bytes) gittiği anlamına (meant) geldiğini düşünen herkes (anyone) için bir yükümlülük (liability) olan daha (earlier) önceki bir çapraz (cross-reference) başvuru bölümünün (section) altında (under) hala disktedir (on disk)

Maliyeti (cost) büyümedir (growth). Her düzenleme (edit) ekler (appends); hiçbir şey yerinde (in place) geri alınmaz (reclaimed), bu nedenle birçok (many) kez revize (revised) edilen bir dosya (file) ölü nesneleri (objects) ve uzun (long) bir xref bölümleri (sections) zinciri (chain) biriktirir (accumulates). Çare (remedy) tam (full) bir yeniden (rewrite) yazmadır (rewriting): belgeyi yükleyin (load) ve hayatta kalan nesneleri (objects) yeniden numaralandıran (renumbers), ulaşılamayanları (unreachable) bırakan (drops) ve tek bir temiz (clean) çapraz (cross-reference) başvuru tablosu (table) çıkaran (emits) şekilde taze (fresh) olarak kaydedin (save). İki strateji birbiriyle (each other) doğrudan (directly) takas edilir (trade). Ekleme (Appending) hızlıdır (fast) ve imzaları (signatures) ve geçmişi (history) korur (preserves); yeniden yazma (rewriting) daha yavaştır (slower) ve kompakt (compact) bir dosya karşılığında (exchange) her ikisini de atar (discards)

Pratikte dört parçayı okuma

Düzeni (layout) bilmek, "bu dosya (file) açılmıyor" (won't open) sorunlarının (problems) çoğunu (most) elle (by hand) ayıklamak (debug) için yeterlidir (enough). Bir okuyucu (reader) bir PDF'i (PDF) reddederse (rejects), olağan (usual) suçlular (culprits) ortada (middle) değil, iki (two) uçtadır (ends). Kesilmiş (truncated) bir indirme (download) fragmanı (trailer) kaybeder (loses), bu nedenle startxref veya %%EOF eksiktir (missing) ve okuyucunun hiçbir (no) giriş noktası (entry point) yoktur; toleranslı (tolerant) okuyucular xref'i yeniden oluşturmak (rebuild) için tüm (whole) dosyayı (file) taramaya (scanning) geri döner (fall back), ki (which) bu (which) tam olarak tablonun kaçınmayı (avoid) amaçladığı (meant) yavaş yoldur. Botched (Botched) bir metin (text-mode) modu aktarımı (transfer), akış (stream) baytlarını (bytes) bozar (corrupts) veya (or) uzaklıklar (offsets) gerçeklikle (reality) eşleşmeyi durdurur (stop) ve nesneler (objects) yanlış (wrong) konumdan (position) yüklenir. Tablodaki (table) uzaklıklar (offsets) artık (no longer) gerçek (real) obj anahtar kelimelerine (keywords) işaret etmediğinde (point), her (every) nesne (object) tek tek (individually) iyi (fine) olsa bile (even if) dosya (file) yapısal olarak (structurally) bozulmuştur (broken)

Yeni kod için (new code), düzenin (layout) dersi, bir (a) kütüphanenin (library) bayt muhasebesine (bookkeeping) sahip olmasına izin vermektir (let). Çapraz (cross-reference) başvuru tablosundaki uzaklıkların (offsets), her bir nesnenin (every object) bayta (byte) kadar (to the) gerçek (actual) konumlarıyla (positions) aynı fikirde olması (agree with) gerekir, fragmanın (trailer) doğru (right) tabloyu işaret etmesi (point) gerekir ve artımlı (incremental) güncellemelerin (updates) /Prev aracılığıyla (through) doğru bir şekilde (correctly) zincirlenmesi (chain) gerekir. Delphi ve C++Builder için (for) HotPDF Bileşeni (Component) gibi yerel (native) bir bileşen, artımlı (incremental) bir revizyon eklemek (appending) ile kompakt (compact) birini (one) yeniden yazmak (rewriting) arasındaki (between) seçim (choice) de dahil (including) olmak üzere, bir dosya yazarken (writes) tüm (all of) bunları halleder (handles). Aynı (same) yapının incelenmek (dissected) yerine hiçten (from nothing) inşa edildiğini (built up) görmek isterseniz, sıfırdan bir PDF belgesi oluşturmaya ilişkin tamamlayıcı (companion) parça (piece), sırayla (in order) başlık (header), nesneler (objects), xref ve fragmanı (trailer) çıkarmayı (emitting) adım adım (walks through) anlatır