PDF 1.5 nesne akışları, birçok küçük dolaylı nesneyi tek bir Flate-sıkıştırılmış konteynere paketler ve losLab PDF Library bunları PackObjectStreams bayrağı aracılığıyla tam bir kaydetmede yayınlar. Kazanç gerçektir: her biri sıkıştırılmamış onlarca bayta mal olan yüzlerce sayfa, font ve annotation sözlüğü, bir avuç sıkıştırılmış blob'a çöker. Maliyet, artık her paketlenmiş nesnenin onu tanımlamak için bir çapraz-referans akışına ihtiyaç duymasıdır
O ikinci yarı, yazıcıların bozulduğu yerdir. Bir /ObjStm konteyneri oluşturmak aritmetiktir; çapraz-referans mekanizmasına ona işaret etmesini öğretmek bir yeniden tasarımdır. Mükemmel derecede geçerli bir konteyner üreten ve sonra üyelerini sıradan tür-1 ofsetlerle tanımlayan bir yazıcı, Acrobat'ın bozuk ilan edecek kadar açık kalacağı bir dosya üretmiştir. İki özellik tek bir özelliktir ve bu yazı, ISO 32000-1 §7.5.7 ve §7.5.8'de tanımlandığı gibi ikisinin de yazma tarafını kapsar
Bir ObjStm konteyneri gerçekte ne içerir
Bir nesne akışı, çözülmüş baytları iki birleştirilmiş bölge olan bir akıştır ve ISO 32000-1 §7.5.7, sözlüğe inşa için önemli olan tam olarak üç anahtar verir. /Type /ObjStm onu tanımlar, /N üye sayısını verir ve /First, başlık bölgesinin bayt uzunluğunu verir — eşdeğer olarak, gövdenin başladığı ofset. Başlık, nesne numarası ve ofsetin boşlukla ayrılmış çiftleridir; gövde, üyelerin arka arkaya serileştirilmiş halidir; her ofset, çözülmüş yükün başlangıcından değil gövdenin başlangıcından ölçülür. Tamamen çözülmüş bir konteyneri okumak bunu açık kılar: aşağıda, /First 14'tür çünkü üç başlık satırı on dört bayt kaplar ve nesne 7, gövdeye 55 bayt uzağa oturur, çünkü nesne 4, 54 karakter artı bir ayırıcıya serileştirilmiştir
// Decoded payload of: 12 0 obj << /Type /ObjStm /N 3 /First 14
// /Filter /FlateDecode /Length 118 >> stream
4 0
7 55
9 90
<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>
<< /Type /ExtGState /CA 1 /ca 1 >>
[ 0 0 595 842 ]
İki üyelik kuralı kesindir ve ikisi de doğrudan §7.5.7'den gelir. Bir akış nesnesi asla bir üye olamaz, çünkü bir akış, başka bir akışın içine yuvalanması gereken ham baytlar taşır. Ve bir üye tam bir nesne değeri olmalıdır, asla çıplak bir dolaylı referans olmamalıdır — yalnızca 5 0 R olan sıkıştırılmış bir nesne, okuyucunun nereye işaret ettiğini zaten bilmeden çözemeyeceği bir dolaylılık yaratır. losLab PDF Library, aday toplama sırasında şifreleme sözlüğü ve nesne 0 ile birlikte her iki durumu da filtreler, sonra hayatta kalan her şeyi konteyner başına 200'lük gruplar halinde paketler. O sınır bir spek sınırı değil bir rastgele-erişim kararıdır: bir üye isteyen bir okuyucu tüm konteyneri şişirmek zorundadır, bu yüzden aşırı büyük konteynerler küçük aramaları pahalı yapar
ObjStm üyeleri neden tür-2 çapraz-referans girdileri kullanmalıdır?
Çünkü paketlenmiş bir nesnenin kaydedecek bir dosya ofseti yoktur. ISO 32000-1 §7.5.8 buna, ikili bir çapraz-referans akışında üç girdi türüyle yanıt verir: boş nesneler için tür 0, bir bayt ofsetinde saklanan sıradan kullanımdaki nesneler için tür 1 ve iki veri alanı konteyner nesne numarasını ve içindeki üye indeksini tutan sıkıştırılmış nesneler için tür 2. Klasik düz metin xref tablosunda paketlenmiş bir nesneyi ifade etmenin hiçbir yolu yoktur; bu tam olarak PDF 1.5'in her iki özelliği birlikte tanıtmasının nedenidir
Bunu takip eden sıralama, bizimki dahil neredeyse her ilk uygulamayı tökezletir. Sıradan nesneler tür-1 girdi alır. /ObjStm konteynerlerinin kendileri tür-1 girdi alır, çünkü bir konteyner gerçek bir ofsette yazılmış mükemmel derecede normal bir dolaylı akış nesnesidir. Yalnızca üyeler tür-2 girdi alır. Ve çapraz-referans akışının kendisi dosyada dolaylı bir nesnedir, bu yüzden az önce yazıldığı ofsete işaret eden kendi tür-1 girdisine ihtiyaç duyar — startxref'in kaydettiği aynı ofset. Yazıcımızın erken bir sürümü, üyeleri hariç tutmak yerine yazma döngüsünden konteyner nesne numaralarını hariç tuttu ve sonuç bir çapraz-referans akışı olan ama hiç nesne akışı olmayan bir dosyaydı: yapısal olarak tutarlı, anlamsal olarak boş, aşağı akışta reddedilmiş. /Size değeri eşleşen bir off-by-one'ı gizler, çünkü en yüksek nesne numarası artı biridir ve çapraz-referans akışı en yüksek nesne numarası olarak tahsis edilir, bu yüzden o da sayılmalıdır
/W dizisini boyutlandırmak: dört bayt neden yeterli değil
/W dizisi, üç alanın her birinin bayt genişliğini bildirir ve losLab PDF Library onu /W [1 Field2 Field3] olarak yazar; alan 1 tür kodu için bir bayta sabitlenmiş ve alan 3, 65535'e kadar üretim numaralarını ve üye indekslerini eşit biçimde kapsayan iki bayta sabitlenmiştir. Alan 2, sabit olamayan alandır, çünkü iki ilgisiz nicelik taşır: tür-1 bir girdide dosya boyutuyla sınırlı bir bayt ofsetidir, tür-2 bir girdide bir konteyner nesne numarasıdır ve tür-0 bir girdide zincirdeki bir sonraki boş nesnedir. Sabit dört-baytlık bir alan 2, dosya 4 GB'ı geçene kadar iyi çalışır, o noktada sınırı geçen her ofset sessizce kırpılır ve tüm tablo çöp haline gelir. Yazıcı bu yüzden derlenmiş tabloyu, çapraz-referans akışının kendi ofseti dahil herhangi bir alan-2 yuvasının hiç tutacağı en büyük değer için tarar ve alanı sekiz bayta kadar genişletir
// Field 2 must hold the largest byte offset AND the largest
// ObjStm container number AND the largest free-chain target.
MaxField2Value := XRefStart;
for X := 0 to MaxObj do
begin
if XRefTable[X].InUse and (XRefTable[X].ObjStrNum > 0) then
Field2Value := XRefTable[X].ObjStrNum // type-2: container number
else
Field2Value := XRefTable[X].ObjPos; // type-1 offset / type-0 next-free
if Field2Value > MaxField2Value then
MaxField2Value := Field2Value;
end;
Field2 := 4;
while (Field2 < 8) and
(MaxField2Value > ((Int64(1) shl (Field2 * 8)) - 1)) do
Inc(Field2);
Field3 := 2; // generation numbers and member indices both fit
Genişlikler bilindikten sonra yük boyutu tam olarak bilinir, bu yüzden yazıcı tüm tamponu önceden tahsis eder ve indekse göre doldurur; girdileri bir AnsiString'e bayt bayt eklemek tablo inşasını ikinci dereceden yapar; bunu on-sayfalık bir faturada kimse fark etmez ve iki yüz bin nesneli bir dokümanda herkes fark eder. İki ayrıntı daha katı okuyucuları memnun eder. /Index, tablonun kapsadığı nesne-numarası aralıklarını bildirir ve tam bir yeniden yazma için bu basitçe boşluksuz [0 N]'dir. Ve yazıcının gerçekte yayınlamadığı her yuva, kullanımda olmak yerine varsayılan olarak boş olmalıdır: nesne 0 boş zinciri başlatır, her boş yuva bir sonrakine bağlanır ve bir zamanlar silinmiş bir nesneyi tutan bir yuva, üretim numarasını bir artırılmış olarak tutar. Güvenilmeyen PDF'leri ayrıştırırken bellek güvenliği üzerine eşlik eden not, aynı sınır argümanını okuma tarafından yapar
Çapraz-referans akışı neden asla şifrelenmemelidir?
Çünkü bir okuyucunun herhangi bir şeyi nasıl çözeceğini bilebilmesi için önce onu ayrıştırması gerekir. Çapraz-referans akışı, okuyucuya /Encrypt sözlüğünün nerede yaşadığını söyleyen şeydir; baytları kendisi şifrelenmiş olsaydı, okuyucunun dosya anahtarını tanımlayan nesneyi bulmak için dosya anahtarına ihtiyacı olurdu. losLab PDF Library bunu tek bir predikat içinde uygular: ShouldCryptStreamData, akış sözlüğü /Type /XRef taşıdığında her zaman False döndürür, bu yüzden serileştiriciye hangi yol ulaşırsa ulaşsın muafiyet geçerli kalır
/ObjStm konteyneri tam tersi bir muamele görür ve asimetri kasıtlıdır. Bir konteyner, tıpkı başka herhangi bir akış gibi, kendi nesne numarasına anahtarlanmış olarak bütün halinde şifrelenir. Üyeleri ayrı ayrı şifrelenmez — çözülmüş düz metin biçiminde paketlenirler ve derlenmiş konteyner üzerindeki tek geçiş, dizeler dahil onları kapsar. Üyeleri çift-şifrelemek, şifre metnine çözülen bir dosya üretir ve dış katman başarılı olduğundan, hata bir kimlik doğrulama hatası olarak değil nesne grafiğinin derinliklerinde bir ayrıştırma hatası olarak yüzeye çıkar. Bir nesne o zaman şemanın tamamen dışında kalır: şifreli bir dokümanda Catalog, doğrudan bir tür-1 nesne olarak tutulur ve asla paketlenmez, çünkü onu paketlemek, kök yardımıyla kurulan şifre çözme bağlamı tam olarak inşa edilmeden önce, doküman köküne ulaşmak için yükleyiciyi bir nesne akışını şişirmeye ve şifresini çözmeye zorlardı
Delphi'den paketlemeyi açmak
Genel anahtar, TPDFlibSaveOptions üzerinde bir alan olarak, bağımsız ayarlayıcı SetPackObjectStreams olarak ve doküman nesnesi üzerinde bir özellik olarak açığa çıkan PackObjectStreams'tir. Varsayılan olarak etkindir ve sürüme göre otomatik kapılıdır: yazıcı yalnızca doküman zaten PDF 1.5 ya da sonrası olduğunda paketler ve dahili minimum-sürüm korumasını çağırır, böylece paketlenmiş bir doküman yanlış etiketlenmek yerine 1.5'e yükseltilir. Kaydetmeden sonra, GetLastSaveUsedObjectStreams, kapının gerçekten açılıp açılmadığını bildirir; bu, bir bayt-boyutu karşılaştırması yerine bir regresyon testinde istediğiniz assertion'dır
var
Doc: TPDFlib;
Options: TPDFlibSaveOptions;
begin
Doc := TPDFlib.Create;
try
if Doc.LoadFromFile('report.pdf', '') <= 0 then
Exit;
Doc.SetInformation(0, '1.5'); // packing is gated on PDF 1.5+
FillChar(Options, SizeOf(Options), 0);
Options.CompressContent := True;
Options.GarbageCollect := True; // drop orphans before packing
Options.PackObjectStreams := True;
if Doc.SaveToFileOptions('report-packed.pdf', Options) = 1 then
if Doc.GetLastSaveUsedObjectStreams = 1 then
Writeln('Saved with ObjStm containers and an xref stream');
finally
Doc.Free;
end;
end;
Paketleme ile garbage collection arasında sıralama önemlidir. Erişilebilirlik analizi önce çalışmalıdır, çünkü bir konteynere hayatta kalan bir üye, konteyneri de kendisiyle birlikte sürükler — canlı bir nesne paketlenmişse, konteyner numarası tanım gereği erişilebilirdir ve konteyneri süpürmek üyeyi bulmasının hiçbir yolu olmadan mahsur bırakır. Toplayıcıyı önce çalıştırmak aynı zamanda ölü nesnelerin bir konteynere hiç girmemesi anlamına gelir, ki bileşik boyut kazancı buradan gelir. Paketleme, diğer boyut kaldıraçlarının yerine geçmek yerine onları tamamlar; PDF dosya boyutu optimizasyonu ve font subsetting anlatımı, akış yükleri üzerinde etki eden kaldıraçları kapsar, nesne akışlarının yapı üzerinde etki ettiği yerde
Etkinleştirmeden önce bilmeye değer sınırlar
Artımlı kaydetmeler asla paketlemez. Bir artımlı güncelleme, önceki revizyonları fiziksel olarak dokunulmadan bırakırken yeni nesneler ve yeni bir çapraz-referans bölümü ekler, bu yüzden mevcut nesneleri taze konteynerlere yeniden paketlemek, önceki revizyonun hâlâ referans verdiği tür-1 girdileri sahipsiz bırakırdı; losLab PDF Library, append modu her etkin olduğunda paketlemeyi devre dışı bırakır ve artımlı güncellemeler ve append-mode streaming üzerine yazı, o yolu eksiksiz olarak ele alır. PDF 1.5'in altındaki dokümanlar, düz metin çapraz-referans tablosunu koşulsuz olarak tutar: bir 1.4 tüketicisinin /ObjStm'nin ne anlama geldiği hakkında hiçbir fikri yoktur ve yazıcı daha küçük bir dosyayı tercih ettiği için bir dokümanı sessizce yükseltmek, çağıranın adına yapılacak yanlış bir takas olurdu. Kasıtlı olarak yayınlamadığımız bir isteğe bağlı anahtar /Extends'tir; ISO 32000-1 §7.5.7 bunu bir konteynerin bir öncekini adlandırabilmesi ve okuyucuların bir konteyner zincirini mantıksal bir grup olarak ele alabilmesi için tanımlar. Gerçekten isteğe bağlıdır, yazdığımız her konteyner kendi kendine yeterlidir ve bağımsız biçimde çözülebilir ve onu atlamak, yazıcıdan bir döngü ve sarkık-referans hataları sınıfını kaldırır — yine de okuyucular elbette başka üreticilerden gelen dosyalarda onunla karşılaştıklarında /Extends'i onurlandırmak zorundadır
Nesne-akışı paketleme ve çapraz-referans akışı çıktısı, birlikte oluşturdukları garbage collector ve içerik-akışı optimize ediciyle birlikte Delphi ve C++Builder için losLab PDF Library'nin bir parçası olarak sunulur; ürün sayfası tam kaydetme-seçenekleri referansını içerir