Linearize edilmiş bir PDF'deki ilk nesne, içinde bulunduğu dosyanın tam uzunluğunu baytı baytına bildirmek zorundadır ve bu bildirimin ilk 1024 bayt içinde görünmesi gerekir. ISO 32000-1 Ek F bu girdiye /L der; gerçek dosya boyutuyla çeliştiğini gören bir okuyucu, belgeyi sıradan bir PDF olarak ele almaya ve içindeki tüm linearizasyon ipuçlarını yok saymaya yönlendirilir. Yani yazıcı, henüz yazmayı bitirmediği bir dosyanın uzunluğunu, bir hint akışının uzaklığının, ilk sayfanın sonunun ve dosyanın sonuna yakın bir çapraz referans tablosunun konumunun yanına yazdırmak zorundadır. Önlerindeki her şey boyut değiştirdiğinde dört sayı birden yer değiştirir
Kazanç Fast Web View'dır. Sıradan bir PDF çapraz referans tablosunu sonunda tutar; bu yüzden tarayıcıya verilen 200 sayfalık taranmış bir raporun 1. sayfası, son bayt gelmeden bulunamaz. Linearize edilmiş dosya ise 1. sayfayı ve küçük bir çapraz referans bölümünü öne alır; böylece HTTP range isteklerini kullanan bir istemci ilk sayfayı erken boyar. Burada yazma tarafı anlatılıyor. Okuyucu tarafındaki resim, PDF linearizasyonu ve Fast Web View açıklamasında var
HotPDF'in linearize edilmiş dosya düzeni
HotPDF, THotPDF.LinearizeOutput True olduğunda linearize edilmiş çıktı yazar ve dosya, Ek F'nin öngördüğü fiziksel sırayı parça parça izler. Başlığın ardından linearizasyon parametre sözlüğü gelir; sonra ilk sayfa çapraz referans bölümü ve onun trailer'ı, diğer belge düzeyi nesnelerle birlikte katalog, birincil hint akışı, ihtiyaç duyduğu her nesneyle birlikte ilk sayfa, sırayla kalan sayfalar, sonraki sayfalar arasında paylaşılan nesneler, geri kalan her şey ve en sonda ana çapraz referans tablosu gelir
Belge modelindeki hiçbir şey bir nesnenin hangi parçaya ait olduğunu söylemez. Yazıcı bunu EndDoc kaydederken referans grafiğinden çıkarır: her sayfa nesnesinden yürüyerek her dolaylı nesneye kaç sayfanın ulaştığını sayar. Tek bir sayfadan ulaşılan nesne o sayfanın bölümüne girer. Birden çok sayfadan ulaşılan nesne paylaşılandır; bir de spec'in hızlı okumasının gözden kaçırdığı bir kırılma noktası vardır: 1. sayfa da onu kullananlar arasındaysa nesne yine de ilk sayfa bölümüne girer, çünkü 1. sayfa yalnızca öndeki baytlardan boyanabilmelidir. Yalnızca sonraki sayfalar arasında tamamen paylaşılan nesneler paylaşılan parçada bekler. Katalog, /ViewerPreferences, /OpenAction, /Threads ve /AcroForm girdilerinin neye işaret ettiğinden bağımsız olarak; korumalı bir belgede şifreleme sözlüğüyle birlikte belge düzeyi grubu oluşturur. Sayfa ağacı düğümleri, ilk sayfayı şişirmemeleri için sondaki parçaya itilir
Çıktıyı bir hex düzenleyicide açın; numaralandırma tersine dönmüş görünür. Linearizasyon sözlüğünden hint akışına uzanan ön blok, dosyadaki en yüksek nesne numaralarını taşır; onun ardındaki her şey 1'den numaralanır. Öndeki küçük çapraz referans bölümü tam olarak o yüksek bloğu kapsar, ana tablo ise 1'den N'e kadarını kapsar
Bölümleme listelerinde kayıt başlatma tuzağı
Bu tür listeler doğal olarak dinamik bir diziyle ayrı bir sayaç tutan kayıt (record) olarak yazılır ve bu biçim bir Delphi tuzağı saklar. Derleyici, yerel bir kaydın managed alanlarını başlatır. Yanındaki düz Integer ise hiçbir şey almaz. Win32'de yığın yuvası çoğu zaman tesadüfen sıfır içerir, ilk ekleme 0 indeksine düşer ve tüm testler geçer; Win64'te yuva artıklar içerebilir ve ilk ekleme rastgele bir indekse yazar
type
TPartList = record
Values: TArray<Integer>; // managed: derleyici temizler
Count: Integer; // unmanaged: yığında ne varsa o
end;
procedure Append(var List: TPartList; Value: Integer);
begin
if List.Count = Length(List.Values) then
SetLength(List.Values, List.Count * 2 + 16);
List.Values[List.Count] := Value;
Inc(List.Count);
end;
procedure BuildParts;
var
FirstPage, Shared: TPartList;
begin
FirstPage := Default(TPartList); // zorunlu, kozmetik değil
Shared := Default(TPartList);
Append(FirstPage, 12);
Append(Shared, 40);
end;
HotPDF'in yazıcısı, her bölümleme listesine dokunmadan önce Default atar. Yeşil bir Win32 çalışması, böyle bir kaydın başlatılıp başlatılmadığı hakkında hiçbir şey söylemez
Düzen, ilk bayt yazılmadan önce neden yakınsamalı?
Çünkü dosyanın önü, arkaya işaret eden uzaklıklar taşır ve bu uzaklıkların basamak sayısı, arkalarındaki her şeyi kaydırır. İlk sayfanın trailer'ı, ana çapraz referans tablosunun bayt uzaklığını taşıyan bir /Prev girdisi içerir. Bu uzaklığı 99998 yazın, sonra tablonun gerçekte 100003'e düştüğünü fark edin; trailer bir basamak büyür. İlk sayfa çapraz referans bölümü de onunla büyür, arkasındaki her nesne bir bayt kayar ve ana tablo şimdi 100004'te bulunur. Tek geçiş bunu yalnızca şansla doğru yapar
HotPDF döngüyü iki hamlede kırar. Parametre sözlüğü boşluklarla doldurulmuş sabit 384 baytlık bir yuvaya yazılır; böylece /L, /H, /E ve /T gerektirdikleri basamakları tek bir nesneyi kaydırmadan alabilir; sözlük metni bir gün 384 baytı aşarsa yazım, dosyayı kaydırmak yerine istisna fırlatırdı. Böylece /Prev tek serbest değişken olarak kalır ve sabit noktalı bir döngüden geçer. Önce her nesne, uzunlukları kaydeden ve hiç bayt tutmayan sayan bir akışa serileştirilir. Ardından bir düzen geçişi bölümlere uzaklıklar atar ve ana tablonun nereye düşeceğini bildirir; bu cevap bir sonraki geçişin girdisi olur. Döngü, önerilen uzaklık kıpırdamayı bıraktığında durur ve sekiz denemeden sonra istisnayla vazgeçer
Yalnızca basamak sayısındaki değişim bir şeyi kaydırır; üçüncü geçiş bu yüzden yalnızca 99998 örneğindeki gibi onluk sınırın yakınında gerekir. Yakınsamadan sonra yazıcı bir doğrulama geçişi yapar, hint akışını gerçek uzaklıklarla yeniden kurar ve uzunluğunun değişmediğini kontrol eder; uzaklıklar sabit genişlikli alanlarda durur, dolayısıyla yer tutucu sürümle son sürüm bire bir eşleşmek zorundadır. Sonra çıktı, tamponlanmış ikinci bir kopya olmaksızın doğrudan hedef akışa yazılır ve her bölüm sınırında yazılan baytlar taahhüt edilen uzaklıkla karşılaştırılır. Uyuşmazlık, EndDoc içinden istisna fırlatır
Hint tabloları ve Ek F'nin iki türlü okunabilen yeri
Birincil hint akışı iki tablo taşır. Sayfa uzaklığı hint tablosu, her sayfa için bölümünün kaç nesne barındırdığını, bölümünün bayt cinsinden uzunluğunu ve hangi paylaşılan nesnelere bağlı olduğunu kaydeder. Paylaşılan nesne hint tablosu, her paylaşılan nesne grubunun uzunluğunu listeler. İkisi de varken bir okuyucu 37. sayfayı doğrudan isteyebilir; akışın /S girdisi, paylaşılan tablonun akış içinde başladığı konumu verir
Ek F F.4'teki iki ayrıntı yanlış yapmaya açıktır. Birincisi: hint tablolarında saklanan uzaklıklar, birincil hint akışı hiç yokmuş gibi hesaplanan mantıksal uzaklıklardır. İlk sayfa nesnesi ile ilk paylaşılan nesne fiziksel olarak hint akışının ardında bulunur; yazıcı, uzaklıklarını kaydetmeden önce hint nesnesinin uzunluğunu düşer. Fiziksel uzaklıkları kaydeden bir yazıcı, sonuçta çıkan her range isteğini tam olarak hint nesnesi kadar kaydırmış olur
İkincisi gerçek bir belirsizliktir. F.4 metni her tabloyu, en anlamlı bitten başlayan tek bir bitişik bit akışı olarak tanımlar ve ilk sayfanın paylaşılan gruplarıyla sonraki sayfaların paylaşılan gruplarını iki ayrı dizi olarak listeler. Dolaşımdaki hint akışları her sütunu bayt sınırına doldurur ve paylaşılan grupları tek bir birleşik liste sayar; hint tüketen okuyucuların kabul ettiği bilinen biçim budur. HotPDF, bayt hizalı birleşik düzeni yazar. Doğru taraf da budur: F.4'ün harfini yerine getiren ama onu okuyan istemcilerde hizayı kaçıran bir tablo, tam olarak linearizasyonun kazandırması beklenen yerde başarısız olur ve dosyanın tamamının indirildiği senaryoda sorun hiç ortaya çıkmaz
Delphi tarafından linearize edilmiş çıktıyı açmak
API tarafında anahtar tek bir Boolean'dır. LinearizeOutput varsayılan olarak False'tur ve yazıcı belgeyi serileştirirken EndDoc içinde ona bakar; o yüzden diğer çıktı seçenekleriyle birlikte BeginDoc'tan önce ayarlayın ve sonra dokunmayın. Yukarıda anlatılan her başarısızlık (yakınsamama, aşırı büyük parametre sözlüğü, sınır uyuşmazlığı, aşağıda ele alınan 4 GiB tavanı) EndDoc içinden istisna olarak yüzeye çıkar; bu da yedek planı basit kılar
procedure WriteReport(const FileName: string; Linearize: Boolean);
var
PDF: THotPDF;
begin
PDF := THotPDF.Create(nil);
try
PDF.FileName := FileName;
PDF.Version := pdf17;
PDF.LinearizeOutput := Linearize; // BeginDoc'tan önce ayarlanır
PDF.BeginDoc; // 1. sayfayı oluşturur
PDF.CurrentPage.TextOut(72, 72, 0, 'Quarterly summary');
PDF.AddPage;
PDF.CurrentPage.TextOut(72, 72, 0, 'Details');
PDF.EndDoc; // düzen ve yazma burada olur
finally
PDF.Free;
end;
end;
procedure PublishReport(const FileName: string);
begin
try
WriteReport(FileName, True);
except
on E: Exception do
begin
LogWarning('Linearization failed: ' + E.Message); // kendi günlükçünüz
WriteReport(FileName, False); // yerine sıradan PDF
end;
end;
end;
Yedek plan, herhangi bir görüntüleyicinin açtığı sıradan bir PDF üretir; yalnızca hızlı ilk sayfadan olur. Rapor hattı için bu ödünleşim kolaydır: yavaş ilk boyama bir rahatsızlık, kayıp bir rapor ise olaydır
Dağıtım tarafı koddan daha ağır basar. Linearizasyon yalnızca sunucu HTTP Range isteklerini karşıladığında kazandırır; dosyayı bütün halinde akıtan bir uç nokta, karşılığında hiçbir şey almadan daha yavaş bir yazım ve daha büyük bir dosya satın alır. E-postalanan, eklenen ya da kimse bakmadan indirilen dosyalar için LinearizeOutput'u kapalı bırakın. Bir tarayıcının doğrudan URL'den açtığı belgeler için ise yanıtın Accept-Ranges: bytes taşıdığını doğruladıktan sonra açın
LinearizeOutput nesne akışlarını neden kapatır?
Çünkü her hint tablosu girdisi bir bayt aralığını tanımlar; bir /ObjStm kapsayıcısına paketlenmiş nesnenin kendisine ait bir bayt aralığı yoktur, o nesne yalnızca bütünüyle çekilip açılması gereken başka bir sıkıştırılmış akışın dilimidir. LinearizeOutput etkinken HotPDF, UseXRefStream veya UseObjectStreams ayrıca ayarlanmış olsa bile geleneksel metin çapraz referans tablolarını ve paketlenmemiş dolaylı nesneleri yazar. Linearize edilmiş yol, kaydetme anında önceliği alır; kodunuzda çözmeniz gereken bir çakışma olmaz
Bedel dosya boyutudur; nesne akışının birlikte sıkıştıracağı küçük sözlükler artık dağınık kalır. Kompakt çıktı, ilk sayfa gecikmesinden daha önemliyse ödünleşimin uzun hâli HotPDF'de nesne akışları ve artımlı güncellemeler yazısında. Office aktarıcıları aynı gerilimi öbür uçtan yaşar; bazılarının Office üretimi PDF'lerde hibrit çapraz referans akışları düzenini yaymasının nedeni de budur
Linearizasyon ayrıca PDF 1.2 veya üstünü gerektirir. Daha düşük bir Version ile HotPDF, belgeyi kaydetme sırasında 1.2'ye yükseltir. StrictVersionLock ayarlıysa bunun yerine istisna fırlatır; çünkü kasıtlı sabitlediğiniz bir sürüm arkanızdan değişmemelidir
Linearize edilmiş çıktıda 4 GiB tavanı
Hint tabloları uzaklık ve uzunlukları 32 bitlik alanlarda saklar; dolayısıyla linearize edilmiş bir dosya 4 GiB ötesine hitap edemez ve HotPDF, taşacak uzaklıklar yazmak yerine böyle bir dosyayı üretmeyi reddeder. Tavan, Ek F'nin tanımladığı alan genişliklerinden gelir
Denetim üç noktada yapılır, hepsi ilk bayt hedefe ulaşmadan önce: serileştirilmiş uzunluğu belli olan her nesnede, hint girdileri kurulurken her sayfa bölümü uzunluğunda ve ana çapraz referans tablosunun boyutu hesaplandığında nihai dosya uzunluğunda. Taşmış uzaklıklar en kötü başarısızlık biçimi olur: bütün dosyayı indiren bir görüntüleyici ipuçlarını yok sayabilirken byte-range istemcisi yanlış baytlara atlar; masaüstü testi de bu arızayı asla yeniden üretmez. Çok gigabaytlık çıktı için bakılacak yer, büyük PDF iş akışları için Direct File API tarafındaki akış yoludur
Gelen bir dosyanın linearize edilmiş olup olmadığını denetlemek
THotPDF.IsLoadedLinearized, yüklenen belgenin kendini linearize edilmiş olarak bildirip bildirmediğini raporlar. Denetim ayrıştırmadan önce yapılır: HotPDF kaynağın sıfır konumundan en fazla 1024 bayt okur, ilk nesneyi bulur ve o nesnenin içinde değeri 1 olan /Linearized arar. Yorumlar, literal dizgeler ve hex dizgeleri atlanır; dolayısıyla bir dizgenin içinde geçen isim sayılmaz ve tarama stream ya da endobj ile durur. Yalnızca 1 ya da 1.0 gibi bir eşdeğer kabul edilir
Cevap yükleme anında önbelleğe alınır; çünkü uygulama kodu sorduğunda ayrıştırıcı akış konumunu çoktan oynatmış olur ve LoadFromFile kaynak akışını çoktan serbest bırakmıştır
Bayrak, dosyanın iddiasını rapor eder. Ek F ikinci bir koşul ekler: /L gerçek dosya uzunluğuyla eşleşmeli; linearizasyondan sonra eklenen bir artımlı güncelleme bu eşleşmeyi bozar ve okuyucular dosyaya sıradan muamele yapmaya döner. Kaydetmeleri sonuna ekleyen düzenleyicilerden dosya kabul eden bir hat, iki sayıyı kendisi karşılaştırabilir
// Kendi yardımcı fonksiyonunuz, HotPDF'in parçası değil: /L ile gerçek boyutu karşılaştırır
// Basitleştirilmiş ayrıştırma; /L'den sonra tek boşluk varsayar
function DeclaredLengthMatches(const FileName: string): Boolean;
var
FS: TFileStream;
Head: AnsiString;
ActualSize: Int64;
P, Q: Integer;
begin
FS := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite);
try
ActualSize := FS.Size;
SetLength(Head, 1024);
SetLength(Head, FS.Read(Head[1], 1024));
finally
FS.Free;
end;
P := Pos(AnsiString('/L '), Head);
Result := P > 0;
if not Result then
Exit;
Inc(P, 3);
Q := P;
while (Q <= Length(Head)) and (Head[Q] in ['0'..'9']) do
Inc(Q);
Result := StrToInt64Def(string(Copy(Head, P, Q - P)), -1) = ActualSize;
end;
procedure InspectUpload(const FileName: string);
var
PDF: THotPDF;
begin
PDF := THotPDF.Create(nil);
try
if PDF.LoadFromFile(FileName) <= 0 then
Exit;
if not PDF.IsLoadedLinearized then
Writeln('Not linearized')
else if not DeclaredLengthMatches(FileName) then
Writeln('Linearized, but modified after linearization')
else
Writeln('Ready for Fast Web View');
finally
PDF.Free;
end;
end;
LinearizeOutput ve IsLoadedLinearized, Delphi ve C++Builder için HotPDF Delphi Component'in parçasıdır; ürün sayfası bunların çapraz referans akışları, nesne akışları ve sürüm kilitleme ile nasıl etkileştiğini belgeler