losLab PDF Developer Library olan PDFlibPas, bir belgedeki her gerçek sayı için ayrıştırdığı ondalık metni aynen saklar ve değer hiç değiştirilmediyse o metni olduğu gibi geri yazar. v3.539.19'dan bu yana SetPrecision ayarı yalnızca kütüphanenin oluşturduğu ya da düzenlediği sayıları yönetir; böylece sıradan bir yükle-kaydet işlemi, 2.22221 değerindeki bir CalRGB /Gamma değerini 2.2222 değerine yuvarlamaz ve kimsenin dokunmadığı bir sayfanın renklerini kaydırmaz. Değişiklik kodda küçük, ayrıştırıcılar hakkında söyledikleri bakımından büyüktür: çözdüğünüz değer ile ürettiğiniz literal iki farklı şeydir ve bir Double gidiş dönüşü bir birim dönüşümü değildir
Hiçbir şeyi değiştirmeyen bir kayıt sayfa renklerini neden kaydırdı?
Çünkü yeniden biçimlendirilen görüntü değil renk uzayı parametreleriydi. Bunu açığa çıkaran dosya, yerel regresyon korpusundaki 35 sayfalık bir ofis belgesi; her sayfada yeniden kullanılan bir başlık görüntüsü var. Belgeyi yükleyip doğrudan geri kaydetmek, girdiyle bayt bayt aynı görüntü akışları üretti ve akış hash'i karşılaştırması belgeyi değişmemiş olarak bildirdi. Render edilmiş bir karşılaştırma aynı fikirde değildi: 35 sayfanın her biri başlıkta piksel farkı gösteriyordu ve başka hiçbir yerde göstermiyordu
Başlık görüntüsü, ISO 32000-1 §8.6.5.3'ün bir /WhitePoint, isteğe bağlı üç elemanlı bir /Gamma dizisi ve isteğe bağlı dokuz elemanlı bir /Matrix ile tanımladığı bir CalRGB renk uzayı üzerinden çizilir. Bu diziler renk uzayı sözlüğündeki sıradan sayısal nesnelerdir. TPDFNumeric her birini bir Double olarak saklıyordu, başka bir şey olarak değil; TPDFNumeric.Output ise o Double değerini varsayılanı dört ondalık basamak olan PDFPrecNum üzerinden biçimlendiriyordu. Böylece /Gamma 2.22221 değerinden 2.2222 değerine, bir matris girdisi 0.71519 değerinden 0.7152 değerine indi ve render edici, biraz farklı kalibrasyondan sadakatle biraz farklı renkler üretti. Görüntü baytları masumdu; çevrelerindeki sayılar değildi. Can sıkıcı olan, bunun ne kadar görünmez olduğudur. Çözülmüş akış baytlarını karşılaştırmak bunu göremez, çünkü sayılar bir akışta değil bir sözlükte yaşar. Ek dosya yüklerini karşılaştırmak da göremez. Değişiklik düzeyi yazısında anlatılan revizyon farkı bile normalleştirilmiş bir nesne gövdesinin parmak izini alır, dolayısıyla iki revizyon da aynı değere hash'lenir ve fark onları özdeş bildirir. Bunu yalnızca render etme yakaladı; korpus temel çizgisinin her sayfayı tek tek render etmesinin ve yalnızca yapısal kontrollere güvenmemesinin sebebi budur
Ayrıştırdığınız değer, yazmanız gereken literal değildir
Bir PDF gerçek sayısı bir ondalık dizedir ve ISO 32000-1 §7.3.3 bunun yalnızca bir ondalık dize olduğunu açıkça söyler: taban gösterimi yok, üs biçimi yok. Ek C ise bir uygulamanın uyması beklenen hassasiyeti, ondalık kısımda yaklaşık beş anlamlı ondalık basamak olarak listeler. Varsayılan dört basamaklı çıktı hassasiyeti bunun zaten altındadır ve sıfıra yakınken durum daha da kötüleşir: PLDoubleToStr değeri ölçekler, bir tamsayıya yuvarlar ve sonuç sıfır çıkarsa 0 yazar; dolayısıyla -0.000012345 değerindeki bir matris girdisi bir basamak kaybetmez, tamamen kaybolur
Varsayılanı yükseltmek uçurumu yalnızca öteler. Düzeltme, bir Double'ın sayının kendisi olduğu numarasını bırakmaktır. TPDFStructure.Decode içindeki tokenizer standart bir gerçek sayı tanıdığında, yani token bir ondalık nokta içerip üs işareti içermediğinde, kaynak metni dönüştürülmüş değerin yanı sıra yeni FOriginalText alanında saklar. Output ardından o metni tercih eder ve tercih edecek bir şey olmadığında biçimlendirmeye geri döner
// Lib/PDFlibStruct.pas — düzeltmenin çıktı tarafındaki tamamı
Function TPDFNumeric.Output: AnsiString;
Begin
If FOriginalText<> '' Then
Result:= FOriginalText
Else
Result:= PLDoubleToStr(FValue, Owner.PDFPrecNum);
End;
Procedure TPDFNumeric.SetTo(Const Value: Double);
Begin
FOriginalText:= ''; // düzenlenmiş bir sayı yeni bir sayıdır
FValue:= Value;
FChanged:= True;
End;
İki sınır bilinçlidir. Tamsayılar korunmaz, çünkü tamsayı biçimlendirmesi zaten kayıpsızdır. 6.02E23 gibi üs biçimleri, bozuk üreticiler hatırına girdide hoş görülür ama çıktıda korunmaz, çünkü onları geri yazmak §7.3.3'ün yasakladığı bir sözdizimini sürdürmek olurdu; kütüphanenin ürettiği her sayı gibi onlar da biçimlendiriciden geçer. Tokenizer ayrıca metni saklamadan önce her zamanki asgari onarımını uygular, böylece .5 gibi baştan noktalı bir literal 0.5, 5. gibi sondan noktalı bir literal ise 5.0 olarak tutulur. İkisi de her okuyucu için aynı sayıdır ve çok daha yaygın kabul görür
v3.539.19'dan sonra SetPrecision neyi garanti eder?
TPDFlib.SetPrecision artık kütüphanenin kendi ürettiği sayıların ondalık basamaklarını denetler: ressam üzerinden çizilen değerler, NewNumeric gibi bir yoldan bir Double'dan oluşturulan sayılar ve o zamandan beri SetTo ile düzenlenmiş her ayrıştırılmış değer. Şunu da not edin: nesne API'si üzerinden çözülen metin, örneğin SetObjectFromString fonksiyonuna verilen bir literal, aynı tokenizer'dan geçer ve aynı şekilde korunur. Hiç değiştirilmemiş ayrıştırılmış bir ondalık, ayardan bağımsız olarak girdi hassasiyetini korur ve ayarı yüklemeden sonra değiştirmek onu geriye dönük etkilemez. SetPrecision referans girdisi de aynı sürümde tam bunu söyleyecek biçimde güncellendi, çünkü eski ifade ayarın dosyadaki her sayıya uygulandığını ima ediyordu
Temizleme, Changed bayrağından türetilmek yerine SetTo içinde yapılır ve bu ayrım önemlidir. Kayıt hattı, nesneler yazıldıktan sonra onların Changed bayrağını sıfırlar; dolayısıyla “değişmediyse özgün metni yaz” biçiminde bir kontrol, aynı oturumda düzenlenip kaydedilen ve yeniden düzenlenen bir değer için bayat metin yazmaya başlardı. Özgün metni doğrudan atamanın kendisine bağlamak, ikisinin çelişmesini olanaksız kılar. Regresyon testi bu davranışların her birini özgün dosyadaki değerlerle sabitler
uses
PDFlibStruct;
var
Structure: TPDFStructure;
Values: TPDFArray;
Number: TPDFNumeric;
begin
Structure := TPDFStructure.Create;
try
Structure.PDFPrecNum := 4;
Values := TPDFArray(Structure.Decode('[2.22221 0.71519 -0.000012345 1 0.12567]'));
// Düzenlenmemiş girdi aynen korunur; dört basamaklı
// biçimlendirmenin 0'a indireceği değer de buna dahil
Assert(Values.Output = '[ 2.22221 0.71519 -0.000012345 1 0.12567 ]');
// Bir düzenleme özgün metni atar ve PDFPrecNum'a uyar
Number := TPDFNumeric(Values.Item[0]);
Number.SetTo(0.123456);
Assert(Number.Output = '0.1235');
Assert(Structure.NewNumeric(0.123456).Output = '0.1235');
// Hassasiyeti sonradan düşürmek düzenlenmemiş girdiye ulaşmaz
Structure.PDFPrecNum := 2;
Assert(TPDFNumeric(Values.Item[1]).Output = '0.71519');
finally
Structure.Free;
end;
end;
İçerik modeli sayıları neden hâlâ normalleştiriyor?
Çünkü TPDFContentProgram kurallı sayısal operandlar vaat eder ve o vaat, bir içerik akışının içinde aynen korunan metinden daha değerlidir. Grafik durumu izleyicisinin üzerine kurulduğu düzenlenebilir içerik modeli, NormalizeContentStreams, iyileştirici ve Emit fonksiyonlarının rastgele girdiden kararlı ve karşılaştırılabilir çıktı üretmesi için vardır. Ayrıştırılmış bir operand özgün metnini modele taşısaydı, 0.50000 0 0 RG gibi bir operatör dizisi 0.5 0 0 RG dizisinden farklı yazılır ve aşağı yöndeki her karşılaştırma üreticinin biçimlendirme alışkanlıklarıyla birlikte kayardı
Bu yüzden model özgün metni iki giriş noktasında temizler. NormalizeContentNumbers, ayrıştırıcı her operandı yerleştirirken ve çağıranın verdiği kaynak çözüldüğünde SetOperand içinde bir kez daha çalışır; dash desenleri, TJ dizileri ve işaretli içeriğin özellik sözlükleri kapsansın diye diziler ile sözlüklerin içine özyinelemeyle iner. Her sayısal üzerinde SetTo(AsDouble) çağırmak yeterlidir, çünkü metni temizleyen işlem tam olarak budur. Ham satır içi görüntü verisine, her zaman olduğu gibi, dokunulmaz
// Lib/PDFlibContentModel.pas — içerik modeli sözleşmesini korur
Procedure NormalizeContentNumbers(Obj: TPDFObject);
Var
K: Integer;
Begin
If Obj is TPDFNumeric Then
TPDFNumeric(Obj).SetTo(TPDFNumeric(Obj).AsDouble)
Else If Obj is TPDFArray Then
For K:= 0 To TPDFArray(Obj).Count- 1 Do
NormalizeContentNumbers(TPDFArray(Obj).Item[K])
Else If Obj is TPDFDictionary Then
For K:= 0 To TPDFDictionary(Obj).Count- 1 Do
NormalizeContentNumbers(TPDFDictionary(Obj).Entry[K].Value);
End;
Çağıranlar için pratik kural bu yüzden basittir. Düz bir LoadFromFile ardından SaveToFile, dokunulmamış içerik akışlarını ve dokunulmamış sözlük sayılarını oldukları gibi bırakır. NormalizeContentStreams üzerinden geçen bir sayfa ya da içerik modeli üzerinden yapılan herhangi bir düzenleme, tasarım gereği kurallı olarak çıkar; belgenin geri kalanı yine korunur. Bunlar iki farklı istedir ve artık iki farklı şey yaparlar
Maliyeti ne ve garanti nerede bitiyor
Her TPDFNumeric artık bir fazladan AnsiString başvurusu taşır ve ayrıştırılan her ondalık, nesnenin ömrü boyunca kaynak metnini canlı tutar. Milyonlarca gerçek sayı içeren bir belgede bu gerçek bir bellektir ve geçiştirilmek yerine her büyük belge ölçümüne girmelidir. Garanti ayrıca bir sayının kendi belgesiyle sınırlıdır: nesneleri belgeler arasında kopyalamak ya da nesne API'si üzerinden değerleri yeniden kurmak yeni sayılar üretir ve onlar da her yeni sayı gibi çıktı hassasiyetine uyar. Sürümün neyi iddia edip neyi etmediği konusunda kesin olmakta fayda var. Dokunulmamış bir belgenin yüklenip kaydedilmesi artık render edicinin gerçekten tükettiği kalibrasyon sayılarını korur ki korpus temel çizgisinin kontrol ettiği özellik budur. Bayt bayt özdeş çıktı iddia edilmez; o ayrıca nesne numaralandırmasına, akış sıkıştırmasına ve belirlenimci PDF ID yazısında tartışılan trailer tanımlayıcısına bağlıdır. Ve başka bir yazılımın ürettiği dosyalardaki yuvarlama farklarını parmak izi karşılaştırmasına göstertmez, çünkü orada hâlâ normalleştirilmiş gövde hash'lenir. Ders CalRGB'nin çok ötesine genellenir: bir ayrıştırıcı yalnızca dönüştürülmüş değeri saklıyorsa her kayıt bir düzenlemedir ve bunu fark etmenin tek yolu render edilmiş sonuca bakmaktır. Sayı işleme ve SetPrecision semantiği losLab PDF Developer Library ürün sayfasında belgelenmiştir