Microsoft Word veya Excel'in ürettiği bir PDF'i açın, sayfalarını çevirin; olağandışı hiçbir şey görünmez. Bunu bir Delphi programına yükleyin, sayfa sayısını geri okuyun; sayı doğrudur. Ardından şifreleme açıkken yeniden kaydedin; iş bir EListError ile başarısız olur veya çıktı, hasarlı bir çapraz referans (cross-reference) uyarısıyla açılır. Dosya hiçbir zaman bozuk değildi. O bir hibrit referans (hybrid-reference) dosyasıdır ve on beş yıllık bir görüntüleyicinin onu açmasına izin veren yapının kendisi, okumayı çok erken durduran bir yükleyiciyi mağlup eden yapıdır
Bu, her dahili testi geçen bir PDF süreç hattının gidiş-dönüş yaptıramayacağı bir dosyayla karşılaşmasının en yaygın yollarından biridir. Girdilerin tümü dahili olarak üretilmişti, bu nedenle hiçbir zaman hibrit değillerdi. İlk hibrit dosya, bir müşterinin elektronik tablodan dışa aktarılmış bir faturayı ilettiği gün gelir
Word ve Excel gerçekte ne yazar?
ISO 32000-1, hibrit referans düzenini §7.5.8.4'te açıklar. Belge akışları (object streams) gibi PDF 1.5 özelliklerini isteyen ancak yine de bir PDF 1.4 okuyucusunun dosyayı açmasına izin veren bir uygulama, çapraz referans bilgilerini iki kez yazar. Sürüm 1.4'e kadar her PDF'i sonlandıran sabit genişlikteki ASCII satırları olan klasik bir çapraz referans tablosu vardır ve geri kalanını dizinleyen bir çapraz referans akışı vardır. Klasik bölümün fragmanı (trailer), değeri o akışın bayt kayması (offset) olan bir /XRefStm girişini taşır
İş bölümü kasıtlıdır. Eski bir okuyucunun ulaşması gereken nesneler, aralarında katalog ve sayfa ağacı da olmak üzere, klasik tablodan adreslenebilir. Sıkıştırılmış nesne akışlarına katlanan nesneler, klasik tabloda bir f tipi girişiyle boş (free) olarak işaretlenir, böylece 1.4 okuyucusu doğrudan bunların yanından geçer ve asla ayrıştıramayacağı bir yapıya takılmaz. Gerçek konumları yalnızca çapraz referans akışında yaşar. Böyle bir dosyanın imzası kuyruğudur: Fragmanı, gerçek kurtarma verilerinin oturduğu /XRefStm'yi gösteren, sıklıkla xref ve ardından bir 0 0 alt bölüm başlığından başka bir şey olmayan kısa bir klasik bölüm
Doğru sayfa sayısı neden hiçbir şeyi kanıtlamaz?
Katalog ve sayfa ağacı kasıtlı olarak klasik tablodan erişilebilir olduğundan, yalnızca o tabloyu okuyan bir yükleyici /Root'u bulur, sayfa ağacını yürür ve doğru sayfa sayısını bildirir. Eski bir okuyucunun ihtiyaç duyduğu her şey mevcuttur, bu nedenle dosya sağlıklı görünür. Kaybolan nesneler, nesne akışlarına paketlenmiş olanlardır: AcroForm alan sözlükleri, etiketli-PDF (tagged-PDF) yapı öğeleri, eski bir görüntüleyiciye asla görünmesi gerekmeyen küçük sözlüklerin uzun kuyruğu
Bir şey o nesnelere dokunana kadar boşluğu fark etmezsiniz ve tam bir yeniden kaydetme hepsine dokunur. Belgeyi yeniden şifrelemek veya yeniden yazmak için yürümek, tam olarak sırayla her nesne numarasını isteyen işlemdir; bu nedenle semptom, nedeninden çok uzakta, yükleme zamanı yerine kaydetme zamanında yüzeye çıkar
Tuzak, xref'i görüp duran bir dedektördür
Bir dosyanın nasıl dizinlendiğine karar vermenin ucuz yolu, startxref'i takip etmek ve işaret ettiği ilk baytları incelemektir. xref anahtar kelimesi klasik bir tablo anlamına gelir; bir akış nesnesi ise çapraz referans akışı anlamına gelir. Bu test, tek bir şemaya bağlı kalan her dosya için doğrudur. Yalnızca eski okuyucuları tatmin etmek amacıyla klasik bir bölümü hedefleyen startxref'e sahip hibrit bir dosya için yanlıştır; oysa o bölümün fragmanındaki /XRefStm belgenin çoğunun gerçekte dizinlendiği yerdir. Karşılaştığı ilk xref üzerinde "klasik" döndüren bir dedektör asla /XRefStm'yi okumaz ve yalnızca akışta yaşayan her nesne görünmez hale gelir
var
Pdf: THotPDF;
PageCount: Integer;
begin
Pdf := THotPDF.Create(nil);
try
PageCount := Pdf.LoadFromFile('Invoice_XLS.pdf'); // sayfa sayısı doğrudur
// yüklü belgeyi burada inceleyin veya düzenleyin
Pdf.SaveLoadedDocument('Invoice_secured.pdf'); // her nesneyi yürür
finally
Pdf.Free;
end;
end;
Erken çıkış dedektörü yerindeyken, yükleme iyi görünür ve yeniden kaydetme, olmayan nesnelerin kendilerini duyurduğu yerdir. Düzeltme, başlangıçta daha fazla bayt okumak değil; dosyanın bittiğine karar vermeden önce hibrit fragmanı tanımak ve /XRefStm'yi takip etmektir
Birleştirme sırası müzakere edilemez
Her iki dizin de okunduktan sonra, yalnızca tek bir yönde birleştirilebilirler. Klasik girişlerin etrafını dolduracak şekilde, önce çapraz referans akışının birleştirilmesi gerekir. Bunun nedeni, biçimin merkezindeki küçük aldatmacadır. Hibrit bir dosya, sıkıştırılmış nesnelerini klasik tabloda boş (free) olarak işaretler, böylece eski okuyucular bunları göz ardı eder. İlk görülen kazanır politikasını onurlandıran ve önce klasik tabloyu okuyan bir yükleyici, o nesne numaralarını boş olarak kaydedecek, ardından yuvalar zaten alınmış olduğu için onları gerçekten konumlandıran akış girişlerini atacaktır. Sırayı tersine çevirin ve akıştaki tip 2 girişleri (her biri bir nesne-akış numarası artı bir dizin) sahip olmaları gereken yuvaları kazanır ve klasik girişler onların etrafına yerleşir
Aynı disiplin, daha eski bir revizyonun silinmiş bir nesneyi diriltmesine karşı da koruma sağlar. Artımlı güncellemeler /Prev aracılığıyla geriye doğru zincirlenir ve tip 0 boş bir giriş, daha yeni bir bölümün bir nesne numarasını emekliye ayırdığının nöbetçisidir. Zincirdeki daha sonraki, daha eski bir bölümün bu nöbetçinin üzerine eski bir konumla yazmasına izin verilmemelidir. Boş işaretçiler için ilk görüleni yetkili olarak kabul ederseniz silinen nesne silinmiş olarak kalır; dikkatsizce davranırsanız dosyanın kendi geçmişi, en son revizyonun kaldırdığı içeriği yeniden canlandırır
Bunun HotPDF'te anlamı nedir?
Motor, hibrit referans dosyalarını sizin için çözümler ve bunu çapraz referans verilerini ayrıştırması gereken her yolda yapar. LoadFromFile veya LoadFromStream ile bir belge yükleyin, değişikliklerinizi yapın ve SaveLoadedDocument çağrısı yapın; veya bir girdi okuyan ve bir çıktı yazan EncryptFile gibi tek seferlik bir işlem çalıştırın. Her iki durumda da kurtarma işlemi /XRefStm'yi okur, akış bölümünü klasik girişlerin önünde birleştirir ve yazma bunları listelemeden önce akışlarda yaşayan nesneleri çözümler. AES-256 şifreleme yolu, problemin kendini ilk gösterdiği yerdir, çünkü bir belgeyi şifrelemek her nesneyi yeniden yazar ve bu nedenle her nesnenin halihazırda konumlandırılmış olmasını gerektirir
// Tek seferlik: hibrit girdiyi okuyun, AES-256 şifreli bir kopya yazın
Pdf.EncryptFile('Letter_DOC.pdf', 'Letter_secured.pdf',
'owner-secret', '', aes256, [prPrint, prFillAnnotations]);
Aklınızda tutmaya değer ayrıntı, API'nin yukarısında yer alır. Word, Excel, PowerPoint ve uzun bir "PDF olarak kaydet" süreç hattı listesinden gelen dosyalar rutin olarak hibrittir, bu nedenle yalnızca kendi oluşturucu çıktınıza karşı çalıştırdığınız bir yükleyici testlerde bunlarla hiç karşılaşmayabilir. Test verilerinizi (fixtures) yalnızca kendi kodunuzun ürettiği dosyalarla değil, gerçek Ofis uygulamalarından dışa aktarılan belgelerle besleyin
Şüphelendiğiniz bir dosyayı kontrol etme
İki inceleme soruyu hızla çözer. Dosyayı bir onaltılık (hex) görünümde açın ve son startxref'ten sonraki baytları okuyun; hibrit bir dosya, fragman sözlüğü /XRefStm içeren kısa bir klasik bölüm gösterir. Veya tam bir ayrıştırmanın bildirdiği nesne sayısını, fragmandaki /Size'ın bildirdiği en yüksek nesne numarasıyla karşılaştırın. Büyük bir fark, nesnelerin yükleyicinin açmadığı akışlarda gizlendiği anlamına gelir; bu da daha sonra kaydetme zamanı hatasına dönüşen yetersizliğin aynısıdır
Tipik bir Excel dışa aktarımının kuyruğu ilk kontrolü somutlaştırır. Son xref anahtar kelimesinden sonraki her şey düz ASCII'dir, bu nedenle imza doğrudan onaltılık görünümden okunabilir (ofsetler açıklama amaçlıdır, ek açıklamalar eklenmiştir)
xref
0 0 % boş klasik alt bölüm: hiçbir satır yok
trailer
<< /Size 216 % kullanımda olan en yüksek nesne numarasından bir fazlası
/Root 1 0 R
/Info 15 0 R
/ID [<5C9A...> <5C9A...>]
/XRefStm 87325 % çapraz referans akışının bayt kayması (offset)
>>
startxref
88710 % yukarıdaki klasik bölümü gösterir
%%EOF
0 0 alt bölümü ipucudur: Sıfır girişli klasik bir tablo yalnızca fragmanı taşımak için vardır ve fragman esas olarak /XRefStm 87325 demek için vardır. xref anahtar kelimesinde duran bir dedektör, bu noktada hiçbir şeyin dizinini görmemiştir. Kontrolü gözle yapmak yerine senaryolaştırmayı tercih ettiğinizde, işaret her zaman dosyanın son birkaç kilobaytı içinde yer alır, bu nedenle sınırlı bir geriye doğru okuma yeterlidir
// Dosyanın kuyruğundan /XRefStm ofsetini döndürür, veya işaret
// yoksa (dosya hibrit değilse veya hiç PDF değilse) -1 döndürür
function FindXRefStm(const FileName: string): Int64;
var
FS: TFileStream;
Tail: AnsiString;
Len, P: Integer;
begin
Result := -1;
FS := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite);
try
Len := 2048; // fragman kuyrukta yaşar
if FS.Size < Len then
Len := Integer(FS.Size);
FS.Position := FS.Size - Len; // sınırlı geriye doğru okuma: maks 2 KB
SetLength(Tail, Len);
FS.ReadBuffer(Tail[1], Len);
finally
FS.Free;
end;
P := Pos(AnsiString('/XRefStm'), Tail);
if P = 0 then
Exit; // kuyrukta hibrit işareti yok
Inc(P, Length('/XRefStm'));
while (P <= Len) and (Tail[P] in [' ', #9, #13, #10]) do
Inc(P); // anahtardan sonraki boşlukları geç
Result := 0;
while (P <= Len) and (Tail[P] in ['0'..'9']) do
begin
Result := Result * 10 + Ord(Tail[P]) - Ord('0');
Inc(P);
end;
end;
// Kullanım: negatif olmayan bir sonuç, akışın başladığı baytı adlandırır
if FindXRefStm('Invoice_XLS.pdf') >= 0 then
Writeln('hibrit referanslı dosya: yeniden kaydetme /XRefStm bölümüne ihtiyaç duyacak');
Bu incelemeyi bir ayrıştırıcı olarak değil, bir önceliklendirme (triage) olarak ele alın: Size bir gruptaki hangi dosyaların yeniden kaydetme işi çalışmadan önce dikkat etmeye değer olduğunu söyler, daha fazlasını değil. Bir yükleyicinin bulduğu ofsetle ne yapması gerektiği, bölüm zincirini takip etmesi, akış girişlerini klasik olanların önünde birleştirmesi, boş giriş nöbetçilerine uyması, Ofis uygulamalarından hibrit referanslı PDF'leri ele alma hakkındaki eşlik eden makalemizde adım adım açıklanmıştır
Bu hikayenin yazma tarafı, nesne akışlarının ve sıkıştırılmış çapraz referansların ilk etapta nasıl üretildiği, nesne akışları ve artımlı güncellemeler hakkındaki makalemizde ele alınmıştır. Söz konusu hibrit dosya aynı zamanda çok büyük olduğunda, büyük PDF iş akışları için Doğrudan Dosya API'si (Direct File API) incelemesindeki yükleme teknikleri, her şeyi belleğe okumadan onu incelemenize olanak tanır. Her ikisi de, bu blogun başka yerlerinde ele alınan yükleme, düzenleme, şifreleme ve imzalama API'lerinin yanı sıra Delphi ve C++Builder için HotPDF Bileşeni'nin bir parçası olarak gönderilen ve burada açıklanan kurtarma işlemiyle doğal olarak eşleşir