Bir PDF özünde düz metin bir kapsayıcıdır. Çoğu dosyayı bir onaltılık düzenleyicide (hex editor) açarsanız en üst kısmı okunabilirdir: bir sürüm yorumu, ardından numaralandırılmış nesneler dizisi, ardından küçük bir dizin ve en altta okuyucuya nereden başlayacağını söyleyen bir işaretçi. Sıkıştırmayı kaldırırsanız biçim, bir metin düzenleyiciye çalışan bir belge yazıp bunu bir görüntüleyicide açtırabileceğiniz kadar anlaşılabilirdir. Bunu bir kez yapmak, PDF'in nasıl bir arada tutulduğu hakkında spesifikasyonu okumaktan çok daha fazlasını öğretir, çünkü nesneleri birbirine elle bağlamanız gerekir ve siz bağlantıyı doğru yapana kadar dosya açılmayı reddeder
Bu izlenecek yol, gerçekten bir şeyler oluşturan en küçük PDF'i oluşturur: US Letter kağıdında, yerleşik bir yazı tipinde "Hello, World!" (Merhaba Dünya!) kelimelerini içeren tek bir sayfa. Biten dosyanın tam olarak beş nesneye ve bunların etrafında birkaç satırlık kayıt tutmaya ihtiyacı vardır. Önce nesleri yazacağız, ardından bunları bir okuyucunun kabul edeceği bir dosyaya bağlayan başlık (header), çapraz başvuru (xref) tablosu ve fragmanı (trailer) bir araya getireceğiz
Bir görüntüleyicinin ısrar ettiği beş nesne
Bir okuyucu içerik aramak için bir PDF'i yukarıdan aşağıya doğru taramaz. Fragmandan (trailer) başlar, belge kataloğuna olan bir başvuruyu izler ve oradan bir nesne zincirini takip eder. O zincirdeki her nesne var olmalıdır, aksi takdirde açma işlemi başarısız olur. Tek sayfalık bir belge için zincir kısadır ve her bağlantının tek bir işi vardır:
- Katalog (Catalog) köktür. Fragmanın işaret ettiği nesnedir ve buradaki tek zorunlu girişi, sayfa ağacına olan bir başvurudur
- Sayfalar (Pages) sayfa ağacı düğümüdür. Belgedeki sayfaları listeler ve kaç tane olduklarını bildirir
- Sayfa (Page) fiziksel bir sayfayı açıklar: boyutu, çizim yaptığı kaynaklar ve onu hangi içerik akışının boyadığı
- İçerik akışı (Content stream), çizim operatörlerini, bu sayfaya metin ve grafik yerleştiren postfix (sonek) komutlarını tutar
- Yazı Tipi (Font) içerik akışının başvurduğu yazı karakterini bildirir. Standart 14 yazı tipinden birini kullanırsanız herhangi bir şey yerleştirmeniz gerekmez
Her nesne numaralandırılır ve adreslenebilir. Dolaylı bir nesne N 0 obj ... endobj olarak yazılır; burada N nesne numarasıdır ve 0 da nesnenin üretim numarasıdır (yeni yazdığınız bir dosyada her zaman 0'dır). Dosyanın başka bir yerinde bu nesneyi bir referansla işaret edersiniz: 5 0 R "nesne 5" anlamına gelir. Bu başvurular bağlantılardır. Sayfa ağacına ulaşmak için numaralandırmamızda katalog 2 0 R tutar, sayfa ağacı aşağıya sayfaya bir başvuru tutar ve bu böyle devam eder. Bir sayıyı yanlış alırsanız, okuyucu sallanan bir işaretçiyi takip ederek hiçliğe gider
İsimler, sözlükler ve akışlar
Üç sözdizimi parçası neredeyse her şeyi taşır. Bir isim (name) eğik çizgi ile başlar: /Type, /Page, /F0. İsimler dize değil, büyük/küçük harfe duyarlı tanımlayıcılardır ve PDF bunları sözlük anahtarları ve bir nesnenin ne olduğunu etiketlemek için kullanır. Sözlük (dictionary), her anahtarın bir isim olduğu, çift açılı ayraçlara sarılmış bir anahtar-değer çiftleri kümesidir: << /Type /Page /MediaBox [0 0 612 792] >>. Değerler sayılar, isimler, köşeli parantez içindeki diziler, başvurular veya iç içe sözlükler olabilir. Çoğu PDF nesnesi sözlüktür
Akış (stream), bir sözlük ve ardından stream ve endstream anahtar kelimeleri arasında kalan bir bayt bloğudur. Sayfa çizim operatörleri burada yaşar ve gerçek dosyalarda sıkıştırılmış resimler ve gömülü yazı tipleri de burada yaşar. Akış sözlüğü baytları açıklar; bir üretim dosyasında, tam bayt sayısını veren bir /Length girdisi taşımalıdır ve veriler sıkıştırıldığında genellikle /FlateDecode gibi bir /Filter taşımalıdır. /Length'i doldurmak için bir araca güveneceğiz, çünkü baytları elle saymak bu alıştırmanın eğitimsel getirisi olmayan ve dosyayı bozan yüksek bir bir eksik (off-by-one) hatası şansına sahip kısmıdır
Nesnelerin yazılması
İşte sırasıyla beş nesne. İçerik akışını okumadan önce akılda tutulması gereken koordinat detayı: PDF sayfanın sol alt köşesinden noktalar (points) halinde ölçüm yapar; burada bir nokta inçin 1/72'sidir ve Y yukarı doğru büyür. Bir US Letter sayfası 612'ye 792 noktadır, bu nedenle 50 700 alt kısma değil sol üst tarafa yakın durur
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]
/Resources << /Font << /F0 4 0 R >> >>
/Contents 5 0 R
>>
endobj
4 0 obj
<< /Type /Font
/Subtype /Type1
/BaseFont /Helvetica
>>
endobj
5 0 obj
<< /Length 44 >>
stream
BT
/F0 36 Tf
50 700 Td
(Hello, World!) Tj
ET
endstream
endobj
Başvuruları okuyun, yapı ortaya çıkacaktır. Nesne 1, katalog, /Pages girdisini nesne 2'ye işaret eder. Nesne 2, sayfa ağacı, /Kids'de nesne 3'ü listeler ve /Count 1 bildirir. Nesne 3, sayfa, /Parent öğesini nesne 2'ye geri yönlendirir (ağaç ve sayfa birbirine başvurur, ki bu gereklidir), kendisini /MediaBox ile boyutlandırır, /Resources öğesindeki /F0 yerel adı altındaki yazı tipini ortaya çıkarır ve nesne 5'i içeriği olarak adlandırır. Nesne 4 yazı tipidir: /BaseFont /Helvetica her uyumlu okuyucunun zaten sahip olduğu standart 14 yazı biçiminden birini seçer, bu nedenle eklenecek bir şey yoktur. Nesne 5 içerik akışıdır
İçerik akışının aslında ne söylediği
Akış gövdesi, PDF'in sayfa açıklama dilinde (işlenenlerin (operands) önce, ardından onları tüketen operatörün (operator) geldiği postfix dili) yazılmış küçük bir programdır. Beş satır işi halleder. BT ve ET bir metin nesnesini açar ve kapatır; metni konumlandıran veya gösteren her şeyin aralarında yer alması gerekir. /F0 36 Tf, geçerli yazı tipini 36 puntoda /F0 adlı kaynağa ayarlar (Tf "metin yazı tipini ve boyutunu ayarla"dır). 50 700 Td metin konumunu sayfa koordinatlarında (50, 700) konumuna taşır. (Hello, World!) Tj dizeyi gösterir, bunu PDF parantez içinde harfiyen metin olarak yazar ve onu mevcut konumda boyamak için Tj kullanır. BT/ET'yi dışarıda bırakırsanız katı bir okuyucu metin işleçlerini reddeder; Tj'den önce bir yazı tipi belirlemeyi unutursanız, çizilecek mevcut bir yazı tipi yoktur
Akış sözlüğündeki /Length 44, stream ve endstream arasındaki baytların sayısıdır ve tam olması gerekir. Bu, özellikle düzenleyicinizin satır sonlarını LF veya CRLF olarak yazıp yazmamasının toplamı değiştirdiği düşünüldüğünde, yeni satırları elle saymak yerine bir araca teslim edilmeye değer bir değerdir
Başlık, xref ve fragman (trailer)
Nesneler içeriktir. Üç yapısal parça onları bir dosyaya dönüştürür. İlki, formatı ve sürümü adlandıran en ilk satır olan başlıktır (header):
%PDF-1.7
% PDF sözdiziminde bir yorum (comment) başlatır, ancak bir okuyucu bu özel yorumu format imzası olarak ele alır ve ondan sürümü okur. Gerçek bir yazarın ardından, hemen ikinci bir yüksek bit baytlık (high-bit bytes) yorum satırı gelir, bu dosya aktarım araçlarına dosyanın ikili (binary) olduğuna ve metin olarak parçalanmaması gerektiğine dair bir ipucudur
Dosyanın sonunda rastgele erişimi mümkün kılan dizin olan çapraz başvuru (cross-reference) tablosu yer alır. Bir okuyucunun önce nesne 1 ve 2'yi ayrıştırmadan doğrudan nesne 3'e gidebilmesi için her nesnenin dosyanın başından itibaren bayt ofsetini (byte offset) kaydeder. Tablo katıdır: girişler sabit genişliktedir, satır sonu da dâhil olmak üzere her biri 20 bayttır, 10 basamaklı bir ofset, 5 basamaklı bir üretim numarası, bir anahtar kelime (kullanımdakiler için n, boş olanlar için f) ve iki baytlık bir sonlandırıcı (terminator) olarak biçimlendirilmiştir. Altı girişimiz için doğru bir tablo (nesne 0 her zaman boş liste başıdır (free-list head)) şöyle görünür:
xref
0 6
0000000000 65535 f
0000000009 00000 n
0000000058 00000 n
0000000115 00000 n
0000000235 00000 n
0000000308 00000 n
trailer
<< /Size 6
/Root 1 0 R
>>
startxref
408
%%EOF
Bu ofsetler PDF'yi elle yazmanın kırılgan kısmıdır. Her biri, ilgili N 0 obj öğesinin başladığı tam bayt konumudur ve üstüne bir karakter eklediğiniz anda her ofset değişir. Fragman, bir okuyucunun son ve ilk olarak kullandığı giriş noktasıdır: /Root 1 0 R kataloğu adlandırır, /Size 6 nesne sayısını belirtir ve startxref 408 bizzat xref kelimesinin bayt ofsetini verir. Bir okuyucu dosyayı açar, sonuna atlar, startxref'i okur, çapraz başvuru tablosunu arar ve oradan kataloğa ve onun altındaki her şeye ulaşır. %%EOF son baytı işaretler
Bırakın bir araç bayt sayılarını düzeltsin
Yukarıdaki ofsetler örnektir; pratikte, dosyanızın tam bayt düzenine bağlı oldukları için yazmayı bitirdiğinizde yanlış olacaklardır. Bunları yeniden hesaplamak yerine, yapıyı yer tutucu değerlerle yazın ve bir yardımcı programın çapraz başvuru tablosunu ve akış uzunluklarını yeniden oluşturmasına izin verin. Ücretsiz, çapraz platformlu pdftk bunu tek geçişte yapar:
pdftk hello-draft.pdf output hello.pdf
Nesnelerinizi ayrıştırır, her bayt ofsetini yeniden hesaplar, doğru /Length değerlerini doldurur, geçerli bir xref tablosu ve fragman yazar ve hello.pdf dosyasını çıkarır. Bunu herhangi bir görüntüleyicide açtığınızda üstte 36 puntoluk Helvetica ile "Hello, World!" (Merhaba, Dünya!) yazan bir sayfa elde edersiniz. Qpdf aynı işi yapar ve birçok görüntüleyici de biraz hatalı oluşturulmuş bir dosyayı anında onarır. Burada bir araca güvenmenin amacı tembellik değildir; ofset aritmetiğinin formatın sıfır kavramsal içeriğe ve en yüksek hata oranına sahip tek kısmı olmasıdır, bu nedenle onu otomatikleştirmek, yapının öğrendiğiniz şey olarak kalmasına olanak tanır
Bu neden gerçek belgelere ölçekleniyor?
Yüz sayfalık bir rapor hakkında hiçbir şey yeni kurduğunuz yapıyı değiştirmez. Katalog hala kökte oturur, sayfa ağacı hala sayfaları toplar ve her sayfa hala kaynaklarına ve bir içerik akışına işaret eder. Büyüyen şey belkemiği değil genişliktir: sayfa ağacı dallanır, böylece okuyucu tüm alt ağaçları atlayabilir, içerik akışları beş yerine yüzlerce işleç (operator) taşır, yazı tipleri kendi genişlik tabloları ve kodlamaları ile kendi akış nesneleri olarak gömülür ve resimler resme özgü filtreleri olan akışlar olarak gelir. Modern dosyalar ayrıca birçok nesneyi sıkıştırılmış nesne akışlarına sığdırma ve düz xref tablosunu bir çapraz referans akışıyla değiştirme eğilimindedir, bu nedenle gerçek bir PDF'i bir metin düzenleyicide açmak genellikle ikili (binary) bir duvar gösterir. Altındaki model el yapımı dosyanızdakiyle aynıdır. Daha geniş nesne grafiği ve kataloğun, sayfa ağacının ve kaynak sözlüklerinin daha büyük bir belgede nasıl ilişkilendiği hakkında PDF belge yapısının derinlemesine incelenmesi buranın bıraktığı yerden devam eder ve dosya yapısına genel bakış artımlı güncellemeleri ve fragmanın (trailer) revizyonlar boyunca nasıl zincirlendiğini kapsar
Elle yazmadan kütüphaneye
Nesneleri elle yazmak, bir üretim tekniği değil bir öğrenme alıştırmasıdır. Gerçek yazı tiplerine, kaydırılmış metne (wrapped text), resimlere veya basit bir sayfadan daha fazlasına ihtiyaç duyduğunuz an, pdftk'nin sizin için yamadığı bayt defter tutma işlemi tüm iş haline gelir ve buna sahip olacak bir kütüphane istersiniz. Aynı beş nesne yine yazılır, ancak bir kütüphane siz tek bir bayt izlemeden her ofseti hesaplar, yazı tipi ve kaynak sözlüklerini yönetir ve içerik akışlarını sıkıştırır. Delphi ve C++Builder'da, HotPDF Bileşeni bu dosyanın tamamını bir avuç çağrıya indirger: belgeyi ayarlayın, aynı selamlamayı yerleştirmek için BeginDoc, SetFont ve TextOut işlevlerini çağırın, ardından doğru bir katalog, sayfa ağacı, xref ve fragman yazmak için EndDoc işlevini kullanın. Altındaki nesneleri anlamak, bir belge beklediğiniz gibi işlenmediğinde çıktı hakkında mantık yürütmenizi sağlayan şeydir