İndirmenin ortasında kesilen bir PDF, neredeyse bütün sayfalar gelmişken açılmayı reddedebilir; oysa 2.000 sayfalık bir kılavuz 1.500. sayfaya anında atlar. İkisi de, HotPDF Delphi Component'in yazdığı biçimiyle PDF dosya yapısından türer: bir başlık, numaralı nesnelerden oluşan bir gövde, bayt ofsetlerinden oluşan bir çapraz başvuru (xref) tablosu ve bir trailer; son ikisi dosyanın sonunda saklanır
ISO 32000-2 §7.5.1, tek sayfalık bir fişten taranmış bir arşive kadar her PDF için aynı dört parçalı anatomiyi düzenler. Parçalar dosya boyunca hep aynı sırada durur ve sabit bir zincirle bağlanır: trailer katalogu adlandırır ve xref tablosunun nerede başladığını söyler, xref tablosu her nesnenin nerede başladığını söyler ve nesneler numarayla birbirine işaret eder. Aşağıdaki bölümler, ofsetleri bayt bayt kontrol edilmiş 610 baytlık bir Hello World dosyasından gelir; anahtar kelime kelime açıklamalı eksiksiz dosya bir PDF'in içeriden neye benzediği yazısında, bir görüntüleyicinin bu parçalarla piksele giden yolda ne yaptığı ise bir okuyucu açtığında PDF'in nasıl çalıştığı yazısındadır
PDF başlığı iki satırdır ve ikincisi önemlidir
%PDF-1.7 gibi ilk satır, sözdizimi açısından bir açıklamadır ama okuyucular ona dosya imzası muamelesi yapar ve sürümü ondan alır; sürüm işleme pratikte gevşektir, dolayısıyla sayıyı hangi özelliklerin bekleneceğine dair bir ipucu sayın. İkinci satır, insanların yanlışlıkla silip ardından bir öğleden sonra hata ayıklamaya harcadıkları satırdır. Yükü, ASCII 127'nin üzerinde en az dört bayttır (ISO 32000-1 §7.5.2); dosyayı metin modunda taşıyan herhangi bir şey onu ikili olarak tanısın ve satır sonlarını yeniden yazmayı bıraksın diye oradadır. Sıkıştırılmış akışlar, tesadüfen bir satır başı ya da satır besleme ile eşleşen baytlar taşır; onları yeniden yazarsanız sözlükte kayıtlı akış uzunluğu artık diskteki baytlarla eşleşmez. Ciddi her yazıcı o açıklamayı hâlâ yazar; çünkü önlediği arıza sessizdir ve bütündür
Gövde: numaralı nesneler ve sekiz PDF nesne türü
Gövde, dolaylı nesnelerin düz bir dizisidir; her biri bir nesne numarası ve bir üretim numarasıyla N G obj ... endobj sarmalayıcısını taşır. O düz listeyi PDF iç yapısına çeviren şey, N G R referans belirtecidir: 2 0 R, "nesne 2, üretim 0, dosyada nerede duruyorsa orada" demektir. Herhangi bir değer ayrıca yerinde bir dolaysız nesne olarak yazılabilir. Bölümde sayfanın /Resources sözlüğü dolaysızdır, adlandırdığı font ise dolaylıdır; böylece yüz sayfa, yüz kopya taşımak yerine nesne 4'ü paylaşabilirdi:
1 0 obj
<< /Type /Catalog /Pages 2 0 R >> % katalog, sayfa ağacına işaret eder
endobj
...
3 0 obj
<< /Type /Page
/Parent 2 0 R
/MediaBox [0 0 612 792]
/Resources << /Font << /F1 4 0 R >> >> % dolaysız sözlük, dolaylı font
/Contents 5 0 R
>>
endobj
ISO 32000-1 §7.3 söz varlığını kısa tutar: bir PDF'teki her değer sekiz temel türden biridir. Boolean ile sayılar skalerlerdir ve PDF'in üstel gösterimi yoktur, dolayısıyla 1e6 standarda uygun bir dosyada hiç görünmez. Dizeler bayt tutar; (Hello, World!) ya da onaltılık <48656C6C6F> olarak yazılır. /Type ya da /Helvetica gibi isimler bayt bayt karşılaştırılır; /BaseFont bir isim beklerken (Helvetica) yazmak, karakterler eşleşse bile bir tür hatasıdır. Diziler her şeyi sırayla tutar, MediaBox gibi. Sözlükler isimleri değerlere eşler ve yapının çoğunu taşır. Bir akış, sözlük artı ham baytlardır; sayfa içeriğinin, fontların ve görsellerin evidir. Sekizinci tür null'dur ve var olmayan bir nesneye referans, hata üretmek yerine ona çözülür; bozuk bir dosya böylece anında çökmek yerine kendini kısıtlı biçimde kullanılabilir tutar
Üretim numarası yeniden kullanım içindir. Bir düzenleme bir nesneyi sildiğinde numarası boş listesine girer ve sonraki bir düzenleme o numarayı yeniden kullanırsa üretim bir artar; böylece bayat bir 12 0 R, sessizce yeni 12 1 obj'ye çözülemez. Karşılaştığınız nesnelerin neredeyse tamamı 0 üretimlidir. Çözümleme katalogdan, bölümde nesne 1'den başlar; onun /Pages girişi sayfa ağacına açılır. İç düğümler sayfaları /Kids'te toplar, bir /Count tutar ve /MediaBox gibi öznitelikleri alt soyuna aktarabilir. Sayfa sırasına nesnelerin fiziksel düzeninin değil o ağacın karar vermesini PDF sayfa ağaçları ve sayfa sırası kapsıyor
xref tablosu bir nesneyi nasıl konumlar?
xref tablosu bir nesne numarasını aritmetikle bir bayt ofsetine çevirir; 2.000 sayfalık bir kılavuz, önündeki sayfaları ayrıştırmadan 1.500. sayfada açılır, bunun sayesinde. ISO 32000-1 §7.5.4 her girişi tam olarak yirmi bayta sabitler: dosyanın başından sayılan on haneli bir ofset, bir boşluk, beş haneli bir üretim, bir boşluk, tek karakterli bir tür ve iki baytlık bir satır sonu; LF ile bu, bir boşluk artı satır beslemedir. Satırlar tek tip olduğu için bir okuyucu, n nesnesinin satırına doğrudan dizinler. 0 6 satırı bir alt bölüm başlığıdır ve giriş 0, üretimi 65535 olan her zaman boş giriştir; boş nesne numaralarının bağlantılı listesinin başını çeker. Öteki her ofset, eşleşen N G obj başlığının ilk hanesine düşmek zorundadır; 610 baytlık dosyada olduğu gibi:
xref
0 6 % nesne 0'dan başlayan 6 giriş
0000000000 65535 f % giriş 0: boş listesinin başı
0000000019 00000 n % nesne 1 (katalog), 19. baytta başlar
0000000068 00000 n % nesne 2 (sayfa ağacı), 68. baytta başlar
0000000125 00000 n % nesne 3 (sayfa), 125. baytta başlar
0000000263 00000 n % nesne 4 (font), 263. baytta başlar
0000000333 00000 n % nesne 5 (içerik akışı), 333. baytta başlar
Kesilmiş bir PDF neden açılamaz?
Kesilmiş bir PDF başarısız olur, çünkü her şeyi birbirine bağlayan iki parça, xref tablosu ile trailer, yazılan son baytlardır. Trailer sözlüğü /Root'u, yani katalogu ve /Size'ı, yani xref giriş sayısını taşır; sıfır yuvası boş girişi tuttuğu için bu, en yüksek nesne numarasından bir fazladır. Hemen altında startxref, xref anahtar kelimesinin bayt ofsetini verir ve %%EOF dosyayı kapatır:
trailer
<< /Size 6 /Root 1 0 R >> % giriş sayısı, belge kataloğu
startxref
427 % xref anahtar kelimesinin bayt ofseti
%%EOF
Son birkaç yüz baytı kaybedin; dosya nesnelere sahip olur ama onlara giden haritayı kaybeder. Katı okuyucular orada durur; toleranslı olanlar bütün dosyayı N G obj başlıkları için tarar ve tabloyu yeniden kurar — xref'in varlık nedeni o yavaş yoldan kaçınmaktır — ve yeniden kurulmuş bir harita hâlâ nesneleri kaçırabilir ya da düzenlenmiş bir nesnenin yanlış kopyasını seçebilir. Metin modu aktarımı daha sessiz zarar verir, çünkü satır sonlarını yeniden yazmak her nesneyi kaydırır. 610 baytlık dosyayı CRLF satır sonlarına çevirdik ve qpdf --check (qpdf 12.3.2) çalıştırdık: 427 ofsetinde "file is damaged" ve "xref not found" bildirdi ve bir şey kontrol etmeden önce tabloyu taramayla yeniden kurdu
Klasik xref tablosu mu, çapraz başvuru akışı mı?
PDF 1.5'ten beri xref, ayrıca bir çapraz başvuru akışı olabilir (ISO 32000-1 §7.5.8): /Type /XRef taşıyan dolaylı bir nesne; ikili satırları metin tablosunun yerine geçer, akış sözlüğü trailer girişlerini içine alır; böylece trailer anahtar kelimesi kaybolur ve startxref o nesneyi işaret eder. Satır türleri sayı olur (0 boş, 1 bayt ofsetinde, 2 bir object stream'in içinde), /W her sütunun bayt genişliğini verir ve HotPDF satırları Flate ile PNG Up öngörücüsüyle sıkıştırır. HotPDF, Version pdf15 ve sonrasıyken akış biçimini yazar; örnek onu açıkça set eder ve UseObjectStreams aynı anahtara oturur. PDF 1.4 dönemi araçlarında açılması zorunlu dosyalar için — onlar bir xref akışını hiç ayrıştıramaz — UseXRefStream'i kapalı bırakırız ve kompakt bir indeksin karşılığını verdiği büyük belgelerde açarız. Değişen yalnızca dosyanın kuyruğudur:
uses
System.SysUtils, HPDFDoc;
procedure SaveWithXRef(const FileName: string; AsStream: Boolean);
var
PDF: THotPDF;
begin
PDF := THotPDF.Create(nil);
try
PDF.AutoLaunch := False;
PDF.FileName := FileName;
PDF.Version := pdf17; // xref akışları PDF 1.5 ve sonrasını ister
PDF.UseXRefStream := AsStream; // varsayılan False: metin tablosu artı trailer
PDF.BeginDoc;
PDF.CurrentPage.TextOut(72, 72, 0, 'Same page, different index');
PDF.EndDoc;
finally
PDF.Free;
end;
end;
// SaveWithXRef('table.pdf', False);
// Beklenen kuyruk: xref, 0 N, yirmi baytlık satırlar,
// trailer << /Size /Root /Info /ID >>, startxref, %%EOF
// SaveWithXRef('stream.pdf', True);
// Beklenen kuyruk: N 0 obj << /Type /XRef /Size /Root /Info /ID /W ...
// /Filter /FlateDecode >> stream ... endstream endobj, startxref, %%EOF
Artımlı güncellemeler yeniden yazmak yerine ikinci bir xref ekler
Bir PDF, diskte zaten duran hiçbir bayta dokunmadan düzenlenebilir. Değişen ve yeni nesneler eklenir; ardından yalnızca onları listeleyen bir xref bölümü ve /Prev girişi önceki tablonun ofsetini tutan bir trailer gelir. Bir okuyucu, en yeni bölüme startxref ile gider, /Prev zincirini geriye doğru yürür ve her nesne numarası için en yeni girişin kazanmasına izin verir. Aşağıdaki düzenleme 610 baytlık dosyaya bir not ek açıklaması ekler: sayfa nesnesi 3, aynı numara altında bir /Annots dizisiyle yeniden yazılır ve nesne 6 yenidir. Açıklamalar çıkarılmış ve LF satır sonlarıyla ofsetler hassastır; qpdf --check sonuçta hatasız geçti:
% ... özgün 610 bayt, değişmeden ...
3 0 obj % 3. sayfayı değiştirir, aynı nesne numarası
<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] /Resources << /Font << /F1 4 0 R >> >> /Contents 5 0 R /Annots [6 0 R] >>
endobj
6 0 obj % yeni ek açıklama
<< /Type /Annot /Subtype /Text /Rect [72 650 92 670] /Contents (Checked) >>
endobj
xref % tek girişli iki alt bölüm
3 1
0000000610 00000 n
6 1
0000000752 00000 n
trailer
<< /Size 7 /Root 1 0 R /Prev 427 >>
startxref
843
%%EOF
Eklemek, imzalı PDF'leri doğrulanabilir tutan şeydir: bir imza bir bayt aralığını kapsar ve artımlı bir güncelleme o baytları asla oynatmaz. Bir PDF'in kurtarılabilir bir geçmiş taşımasının nedeni de budur; eski 3. sayfa 125. baytta durmaya devam eder, ulaşılamaz ama oradadır. Çok kez revize edilen dosyalar uzun bir /Prev zinciri büyütür; qpdf revised.pdf flat.pdf, erişilebilir nesneleri tek bir xref altında yeniden seriler ve eski revizyonları düşürür — yukarıdaki dosyada bu, tek bir xref tablosu ve /Prev yokluğu bıraktı, özgün baytlar üzerindeki her imzanın bedeliyle. HotPDF'de artımlı bir revizyon şöyle görünür:
procedure ReviseInPlace(const Original, Revised: string);
var
PDF: THotPDF;
begin
PDF := THotPDF.Create(nil);
try
PDF.AutoLaunch := False;
PDF.BeginIncrementalUpdate(Original); // özgün baytları aynen korur
PDF.SetLoadedTitle('Quarterly report, revised');
PDF.SetLoadedAuthor('Finance');
PDF.SaveIncrementalUpdate(Revised); // değişen nesneler, yeni xref, /Prev trailer'ı
finally
PDF.Free;
end;
end;
Dört parçalı düzen, yanılmakta olan bir PDF için hızlı bir triyaj sırası da verir: dosyanın makul bir startxref ile %%EOF'de bittiğini kontrol edin, bir iki xref ofsetinin bir N G obj başlığına düştüğünü teyit edin, sonra /Root'u izleyerek katalog ile sayfa ağacına inin. Delphi ve C++Builder için HotPDF Delphi Component, talebe göre klasik xref tabloları ya da çapraz başvuru akışları yazar ve önceki baytları ile üzerindeki imzaları sağlam bırakan artımlı revizyonlar ekler