PDF Library for Delphi, kaydetme zamanındaki peephole content-stream optimizasyonu sırasında bir identity text matrix operatörünü — 1 0 0 1 0 0 Tm — yalnızca text matrix ile text line matrix hâlihazırda identity iken çıkarır: BTnin hemen ardından ya da daha önceki bir identity Tmin hemen ardından. Bir identity cm ise hâlâ her zaman düşürülür, çünkü cm CTMyle çarpım yaparken Tm iki text matrixi de doğrudan değiştirir. v3.539.28den beri diğer tüm identity Tmler akışta kalıyor
Bu düzeltmenin giderdiği hata sessiz türdendir. Bir rapor üreticisi BT (Invoice) Tj 1 0 0 1 0 0 Tm (Total) Tj ET yayar ve ikinci diziyi kendi konumlandırma mantığını uygulamadan önce text-space başlangıcına geri göndermesi için identity Tmye güvenir. Eski optimizer, identity matrixi yazan altı sayıyı görünce operatörün hiçbir şeyi değiştiremeyeceğine karar verdi ve sildi. Hiçbir şey başarısız olmadı, hiçbir şey uyarı loglamadı ve kaydedilen sayfa aynı baseline üzerinde "Total"i "Invoice"in hemen ardına çizdi; tam da müşteri PDFi yazdırana kadar kimsenin fark etmediği kusur sınıfı
1 0 0 1 0 0 Tm neden her zaman no-op değildir?
Bir identity Tm, ancak yerine koyacağı iki matrixin de hâlihazırda identity olması durumunda no-optur ve bu, kendi operandlarının değil, önündeki operatörlerin özelliğidir. ISO 32000-1 §9.4.1, BTnin hem text matrixi (Tm) hem text line matrixi (Tlm) identity olarak başlattığını söyler; §9.4.2 ise Tmyi ikisini de verilen değerlere ayarlayan, üzerine eklemeyen operatör olarak tanımlar. Bunu CTMyi sağdan çarpan cmyle (§8.4.4) karşılaştırın: identityyle çarpmak herhangi bir CTMyi değiştirmez, dolayısıyla 1 0 0 1 0 0 cm her yerde güvenle silinir. Bir text objectin içinde tablo farklıdır. Td, TD, T* ve identity olmayan bir Tm Tlmyi taşır; metin gösteren her operatör (Tj, TJ, ', ") boyadığı glyphlerin genişliği kadar Tmyi ilerletir. Bunların ardından bir identity Tm başlangıca gerçek bir sıfırlamadır. Metin konumlarını elle izlemeyi denediyseniz — content-stream CTM ve text matrix durum izleyicisi yazısında olduğu gibi — buradaki ayrım, durumu eklemenin ve üzerine yazmanın ayrımıyla aynıdır
Geriye doğru tarama hangi identity Tmyi düşüreceğine nasıl karar verir?
TPDFContentPeepholeOptimizer.RemoveIdentityMatrices artık her identity Tmden geriye doğru yürür ve tarama önce BTye ya da başka bir identity Tmye ulaşırsa onu düşürür. Daha önceki identity Tm, tutulmuş ya da kendisi az önce silinmek üzere planlanmış olması fark etmeksizin sayılır, çünkü her iki durumda da iki matrixi de BT gibi identityde bırakmıştır. Kural, karşılaşabileceği her operatörü iki gruptan birine ayırır:
- Dur ve Tmyi tut:
Td,TD,T*, identity olmayan birTm,Tj,TJ,',",ET, ayrıştırıcının tanımadığı herhangi bir operatör ya da akışın başlangıcı - Üzerinden geç ve taramaya devam et: Tmye ya da Tlme dokunmayan operatörler, örneğin
Tf,Tc, renk ayarlayıcıları,gs, marked-content operatörleri vecm
Muhafazakâr durumlar bilinçlidir. Bilinmeyen bir operatör her şey olabilir; tarama onun ötesine akıl yürütmeyi reddeder. ET text objecti kapatır; ardından gelen bir Tm için matrix değerlerine kefil olan bir BT yoktur. Tarama ayrıca bir seferde tek bir content stream üzerinde çalışır; /Contentsi dizi olan sayfalar için bu önemlidir: bir text objectin ortasında başlayan, kendi BTsi bulunmayan bir katman, önceki katman onu gereksiz kılmış olsa bile identity Tmsini tutar. Bu, tuhaf dosyalarda birkaç bayta mal olur ve hiçbir glyphi kaydırmaz. Sayfa metnini komut düzeyinde düzenliyorsanız, karakter-content byte eşlemesi rehberindeki gibi, optimizerın yeniden yazdığı şey aynı ayrıştırılmış TPDFContentProgram modelidir
uses
PDFlibContentModel, PDFlibContentOptimize;
function OptimizeSnippet(const Source: AnsiString): AnsiString;
var
Prog: TPDFContentProgram;
Optimizer: TPDFContentPeepholeOptimizer;
begin
Result := Source;
Prog := TPDFContentProgram.Create;
try
if not Prog.Parse(Source) then
Exit; // bozuk akış: baytlara dokunma
Optimizer := TPDFContentPeepholeOptimizer.Create(Prog);
try
Optimizer.Run; // kaldırılan komut sayısını döndürür
finally
Optimizer.Free;
end;
Result := Prog.Emit; // satır başına tek komut
finally
Prog.Free;
end;
end;
// Kaldırılan: BTnin hemen ardından gelen Tm, art arda iki identity Tmnin ikincisi
// OptimizeSnippet('BT /F1 12 Tf 1 0 0 1 0 0 Tm (hello) Tj ET')
// Tutulan: Tdden sonra, Tjden sonra, identity olmayan bir Tmden sonra ya da BT dışında
// OptimizeSnippet('BT (Invoice) Tj 1 0 0 1 0 0 Tm (Total) Tj ET')
Yardımcıyı açılıştaki fatura akışı üzerinde çalıştırın; identity Tm hayatta kalır, çünkü geriye doğru tarama BTye ulaşmadan Tjye çarpar. /F1 12 Tf, 2 Tc ve 0 gyi BT ile identity Tmin arasına koyun; yine de gider, çünkü hiçbiri text matrixlere dokunmaz. BT 10 20 Td 1 0 0 1 0 0 Tm 1 0 0 1 0 0 Tm gibi bir dizi tam olarak tek operatör kaybeder: ilk identity Tm, Tdnin taşıdığı matrixi sıfırlar ve gereksiz olan yalnızca ikincisidir
Peephole optimizer aslında ne zaman çalışır?
Optimizer yalnızca sıkıştırma geçişi sırasında, TPDFPageTree.Compressin içinde ve hâlihazırda Flate ile sıkıştırılmamış content streamlerde çalışır. TPDFlib.SetOptimizeContentStreams(1) varsayılandır ve aynı anahtar TPDFlibSaveOptionsın OptimizeContentStreams alanı olarak da açıktır; hem CompressContent hem CompressPage buna uyar. /Filteri hâlihazırda /FlateDecode olan bir akış tamamen atlanır; dolayısıyla mevcut sıkıştırılmış bir PDFi yükleyip yeniden kaydetmek operatörlerini yeniden yazmaz. Akış ayrıştırılamazsa, özgün çözülmüş baytlar olduğu gibi sıkıştırılır. TPDFlib.NormalizeContentStreams içeriği kurallı boşluk ve sayılarla ayrıştırıp yeniden yazar ama optimizerı asla çağırmaz; peephole kurallarının boyut farkına katkısını görmek istediğinizde işe yarayan bir referanstır, font subsetting ile PDF dosya boyutu optimizasyonundaki daha büyük kazanımların yanında
var
Lib: TPDFlib;
Options: TPDFlibSaveOptions;
begin
Lib := TPDFlib.Create;
try
if Lib.LoadFromFile('report.pdf', '') <> 1 then
Exit;
// Sıkıştırılmamış akışlar peephole kurallarından, sonra Flateden geçer
Lib.SetOptimizeContentStreams(1);
Lib.CompressContent;
Lib.SaveToFile('report-optimized.pdf');
// Aynı tercih paketli save options üzerinden; False vazgeçtirir
Options.CompressContent := True;
Options.CompressFonts := True;
Options.CompressImages := True;
Options.Linearize := False;
Options.KeepModDate := False;
Options.OptimizeContentStreams := False;
Options.GarbageCollect := False;
Options.PackObjectStreams := True;
Lib.SaveToFileOptions('report-plain.pdf', Options);
finally
Lib.Free;
end;
end;
Eski regresyon testi aslında neyi garanti ediyordu?
Eski regresyon testi tek biçimi garanti ediyordu: BTnin hemen ardından gelen bir identity Tm kaldırılır. Peephole_RemovesIdentityTextMatrix, optimizerı BT 1 0 0 1 0 0 Tm (hello) Tj ET ile besler ve hiçbir Tmin kalmadığını iddia eder. Daha eski bir sürüm, Tlm identity olmadığında bir identity Tmyi düşürmenin güvensiz olduğunu çoktan not etmiş, sonra da davranışı test "kilitlediği" için olduğu gibi bırakmıştı. Dikkatle okunduğunda test, Tdden sonra ya da gösterilen metinden sonra gelen bir identity Tm hakkında hiçbir şey söylemez; tek bir örneğin kapsamını bütün kuralın sözleşmesi saymak asıl hataydı. Düzeltme o özgün durumu geçer tutar ve kaldırılabilir biçimleri de tutulanları da çivileyen altı durum ekler; bunlara herhangi bir text objectin dışındaki bir Tm ile ETyi izleyen bir tane dahildir
Takas, yazıya döküldüğünde kabul etmesi kolaydır. Her text objecti BT 1 0 0 1 0 0 Tm ... olarak sarmalayan üreticiler o gereksiz operatörün yine de kaldırılmasını alır; tasarrufun neredeyse tamamı oradan gelmişti. Optimizerın vazgeçtiği şey, bir text objectin ortasındaki ara sıra görünen identity Tmdir — Flate onları daha görmeden sayfa başına bir avuç bayt — karşılığında modül başlığının açıkça söylediği garantiye: her dönüşüm çıktı eşdeğeridir ve görünür sayfayı asla değiştirmez. Metin kaydıran bir boyut optimizerı optimizer değildir; iyi sıkıştırma oranlı bir render hatasıdır
Burada anlatılan content-stream ayrıştırıcısı, peephole optimizer ve kaydetme zamanındaki sıkıştırma seçenekleri PDF Library for Delphi and C++Builder ile birlikte gelir; kütüphane her belgenin nasıl yazılacağını ayarlamak için NormalizeContentStreams, CompressContent ve TPDFlibSaveOptionsı da açar