Kalibre edilmiş bir PDF yükleyin, hiçbir şey değiştirmeyin, yeni adla kaydedin ve renk uzayı sözlüğünü diff'leyin: /Gamma [2.22221 2.22221 2.22221] girdi, /Gamma [2.2222 2.2222 2.2222] çıktı. Yazıcı, dokunması hiç gerekmeyen bir sayıyı yeniden biçimlendirdi ve sayfa artık hafifçe farklı renklerde render oluyor. Çare, ayrıştırılan her ondalığı aynı anda iki şey olarak görmek — aritmetik için bir değer, çıktı için bir literal — ve biri gerçekten yeni bir değer atayana kadar literali yazmaya devam etmektir
No-op bir kayıt CalRGB renklerini neden kaydırabilir
Pikseller kayar; çünkü görüntü baytları özdeş kalırken render ediciye hafifçe farklı kalibrasyon verisi verilir. ISO 32000-1 §8.6.5.3, bir CalRGB renk uzayını zorunlu bir /WhitePoint, isteğe bağlı üç elemanlı bir /Gamma dizisi ve isteğe bağlı dokuz elemanlı bir /Matrix ile tanımlar; hepsi renk uzayı sözlüğündeki düz sayısal nesnelerdir. Bunları bir Double'dan ve dört ondalık basamağa sabitlenmiş bir biçimlendiriciden geçirin: 2.22221, 2.2222 olur; 0.71519 değerindeki bir matris girdisi 0.7152 olur. Gamma değeri, o uzaydan geçen her pikselin her bileşenine uygulanan bir üstür; değişiklik uzayın kullanıldığı her yerde görünür
35 sayfada yeniden kullanılan bir başlık görüntüsü için tek bir CalRGB uzayı bildiren bir dosyada 35 başlığın hepsi kaymış renklerle render olur ve o sayfalarda başka hiçbir şey değişmez. İnsanların önce uzandığı denetimler temiz döner. Çözülmüş akış baytlarının hash'i görüntü verisini karşılaştırır ve görüntü verisi gerçekten özdeştir; hasar bir sözlükte oturur. Değişiklik düzeyi makalesinde anlatılan revizyon diff'i, normalleştirilmiş bir nesne gövdesinin parmak izini alır; iki revizyon orada da aynı değere hash'lenir. Bunu yakalayan, render edilmiş sayfa karşılaştırmasıdır. Başka yazılımların ürettiği PDF'leri yeniden yazan her hat için sayfa başına bir render karşılaştırmasını her yapısal denetimin önüne koyardık
Bir PDF gerçel sayısı neye benzeyebilir
ISO 32000-1 §7.3.3 gerçel sayıyı ondalık bir dize olarak tanımlar: isteğe bağlı işaret, rakamlar, nokta, yine rakamlar. Taban gösterimi yok. Üstel biçim yok. Uygulama sınırlarını listeleyen Ek C, kesirli kısımda kabaca beş anlamlı ondalık basamağın korunmasını bekler. Dört olan varsayılan çıktı hassasiyeti bu yüzden spesifikasyonun okuyuculara tutmasını söylediğinin bir basamak altındadır ve dosya beş kullanmakla yanlış bir şey yapmamıştır
Sıfıra yakınken naif bir biçimlendirici bir basamak kaybetmekten daha kötüsünü yapar. Bir Double'ı N basamakla yazmanın yaygın yolu 10^N ile çarpmak, tamsayıya yuvarlamak ve rakamları yazmaktır; o tamsayı sıfırken çıktı 0 olur. 0.00001 0 0 1 0 0 cm'in ilk operandını dört basamakla verin, sonuç 0 0 0 1 0 0 cm olur: altında çizilen her şeyi yok eden tekil bir matris. -0.000012345 değerindeki bir /Matrix girdisi de aynı yolu izler
PDF Library for Delphi (PDFlibPas) bir Double'ı kendisi biçimlendirmek zorunda kaldığında, hassasiyet adımından küçük sıfır olmayan bir değer için 0 yazmak yerine istenen sayıda anlamlı basamağı korur. Dört basamakta 0.00001, 0.00001 olarak; 0.0000123, 0.0000123 olarak yazılır; yalnızca kabaca 5E-16 altındaki büyüklükler hâlâ 0 çıkar. Bu, yeni yazılan sayıları aklı başında tutar. Yeniden biçimlendirilmesi hiç gerekmeyen ayrıştırılmış bir sayı başka bir meseledir ve kütüphane onu hiç yeniden biçimlendirmeyerek halleder
PDFlibPas ayrıştırdığı literali nasıl korur
TPDFStructure.Decode, belirteçlere ayırdığı her standart gerçel sayının — yani ondalık nokta taşıyan ve üs işareti taşımayan her belirtecin — kaynak metnini dönüştürülmüş Double'ın hemen yanında tutar. TPDFNumeric.Output o metin varsa onu geri yazar; Double'ı yapının PDFPrecNum değeriyle ancak metin yokken biçimlendirir. TPDFNumeric.SetTo metni atar; çünkü düzenlenmiş sayı yeni sayıdır ve geçerli çıktı hassasiyetine uymalıdır
Sıfırlamayı SetTo'ya bağlamak bilinçliydi. Cazip kısayol, nesnenin Changed bayrağı kurulu olmadıkça özgün metni yazmaktır; o bayrak zaten her nesnede vardır. Ne var ki kayıt hattı bir nesne yazıldığında Changed'i temizler; aynı oturumda düzenlenip kaydedilen ve yeniden düzenlenen bir değer ikinci kayıtta bayat literalini yazmaya geri dönerdi. Metni atamanın kendi içinde temizlemek, literal ile değerin asla çelişmemesi demektir
Birkaç sınır bilinçlidir. Tamsayılar metin olarak saklanmaz; çünkü tamsayı biçimlendirmesi zaten kayıpsızdır. 6.02E23 gibi üstel biçimler, onları yine de yazan üreticiler uğruna girdide kabul edilir; ama çıktıda kütüphanenin ürettiği herhangi bir sayı gibi biçimlendirilir, çünkü onları geri kopyalamak §7.3.3'ün dışladığı bir söz dizimini sürdürürdü. Belirteçleyici, metni saklamadan önce her zamanki küçük onarımını da uygular: .5 gibi baştaki noktalı literal 0.5 olarak, 5. gibi sondaki noktalı literal 5.0 olarak tutulur. Her okuyucu iki çifti de aynı sayı sayar ve onarılmış yazım çok daha geniş kabul görür
Aşağıdaki parça CalRGB değerlerini doğrudan nesne katmanından geçirir; böylece her kural PDF dosyası açmadan görülebilir. Assert satırları kendi testlerinize eklemenizi önerdiğimiz denetimlerdir; kütüphaneye yapılan çağrılar değildir
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]'));
// Kendi denetiminiz: düzenlenmemiş girdi olduğu gibi sağ çıkar; naif dört
// basamaklı bir biçimlendiricinin 0'a çökerteceği değer dâhil
Assert(Values.Output = '[ 2.22221 0.71519 -0.000012345 1 0.12567 ]');
// 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');
// Minik yeni değerler 0 olmak yerine anlamlı basamakları korur
Assert(Structure.NewNumeric(0.0000123).Output = '0.0000123');
// 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;
Böyle assertion'lar yazarken bir tuzak: Delphi'nin Round'u bankacı yuvarlaması kullanır ve 0.000012345 * 1e8, ikilikte 1234.5 değil 1234.4999… olarak hesaplanır. Sıfıra yakın biçimlendirme yolunu denetlerken o değere tam olarak çarptık. Bir assertion son basamağın nasıl yuvarlandığına bağlıysa ikilik orta nokta olmayan test girdileri seçin
SetPrecision neyi denetler?
TPDFlib.SetPrecision, kütüphanenin yazdığı sayıların ondalık basamak sayısını belirler: painter üzerinden çizilen grafikler, NewNumeric gibi yollarla Double'dan yaratılan sayılar ve SetTo ile sonradan değiştirilen ayrıştırılmış değerler. 2'den 8'e kadar kabul eder, aralık dışı her şeye 0 döndürür. Kimsenin düzenlemediği ayrıştırılmış bir ondalık, ayar ne olursa olsun girdi metnini korur ve ayarı LoadFromFile'dan sonra değiştirmek ona geriye dönük ulaşmaz. SetObjectFromString'e geçilen kaynak da aynı belirteçleyiciden geçer ve aynı biçimde korunur
Önerimiz, ürettiğiniz geometri daha ince adımlar istemedikçe SetPrecision'a dokunmamaktır. Girdi dosyalarını korumak için yükseltmek hiçbir şey kazandırmaz; dokunulmamış girdi zaten kendi basamaklarını korur ve çizdiğiniz her sayıyı uzatır. Beş basamak point altı çizim işleri için makul bir seçimdir; sekiz çoğunlukla içerik akışlarını şişirir
uses
PDFlibrary;
var
PDF: TPDFlib;
begin
PDF := TPDFlib.Create;
try
if PDF.LoadFromFile('calibrated.pdf', '') <> 1 then
Exit;
// Bundan sonra yazılan sayıları yönetir; ayrıştırılan /Gamma,
// /Matrix ve /WhitePoint dizileri özgün metnini korur
PDF.SetPrecision(5);
PDF.SelectPage(1);
PDF.SetLineWidth(0.123456); // çizilen sayılar beş basamak kullanır
PDF.DrawLine(72, 72, 300, 72);
PDF.SaveToFile('calibrated-out.pdf');
finally
PDF.Free;
end;
end;
İçerik akışları: düzenlemeler operandları korur, normalleştirme isteğe bağlı
Düzenlenebilir içerik modeli varsayılan olarak operand metnini korur ve sayıları yalnızca kanonik çıktı çağrının amacı olduğunda kanonikleştirir. TPDFContentProgram.CanonicalNumbers varsayılan olarak False'tur; ParsePageContentLayer'ın döndürdüğü bir katman — grafik durumu izleyicisinin üzerine kurulduğu modelin aynısı — dokunulmamış her sayısal operandı yazıldığı gibi yeniden yazar. Bütün bir katmanı yeniden yazan sayfa düzenlemeleri — ScalePage, TransformPageElement, DeletePageElement, RedactRegion, ConvertTextToPaths ve WritePageContentLayer ile geri yazdığınız kendi programınız dâhil — yalnızca düzenlemenin dokunduğu operandları değiştirir
Öbür varsayılan daha derli toplu görünür: her ayrıştırmada kanonikleştir, böylece düzenleme yolu ile NormalizeContentStreams hep aynı yazımı üretsin. Düzenleme için bunun yanlış varsayılan olduğunu düşünüyoruz. 0.123456 w içeren bir katmanda ScalePage çağrısı 0.1235 w yazar ve katmandaki öbür her operand da yeniden yazılırdı; oysa çağıran yalnızca ölçekleme istemişti. Kanonik biçim, amacı karşılaştırılabilir çıktı olan işlemlere aittir
O işlemler onu yine alır. NormalizeContentStreams ve kayıt anındaki içerik iyileştiricisi CanonicalNumbers'ı True kurarak ayrıştırır; çünkü işleri keyfi girdiden kararlı çıktı üretmektir. 0.50000 0 0 RG ile 0.5 0 0 RG farklı yazılsaydı her aşağı akış karşılaştırma üreticinin biçimlendirme alışkanlıklarıyla kayardı. Kanonikleştirme dizilerde ve sözlüklerde özyineler; böylece dash desenleri, TJ dizileri ve marked-content özellik sözlükleri kapsanır, ham inline görüntü verisine ise dokunulmaz. SetOperand ile verilen operandlar bayrak ne derse desin kanonikleştirilir. Bir içerik programı, SetPrecision'dan bağımsız, dört ondalık basamağa sabit kendi sayı deposunu taşır; düzenlenen operand dört basamakla çıkar. Bayrağı yalnızca Parse okur; yani ayrıştırmadan önce kurulmalıdır. ParsePageContentLayer'ın çoktan ayrıştırdığı bir program için bayrağı kurun ve Parse(Emit) çağırın
uses
PDFlibrary, PDFlibContentModel, PDFlibContentOps;
var
PDF: TPDFlib;
Prog: TPDFContentProgram;
I: Integer;
begin
PDF := TPDFlib.Create;
try
if PDF.LoadFromFile('calibrated.pdf', '') <> 1 then
Exit;
PDF.SelectPage(1);
Prog := PDF.ParsePageContentLayer(1);
if Prog = nil then
Exit;
try
// Bir çizgi genişliğini değiştir; öbür her operand kaynak metnini korur
I := Prog.FindNextOp(0, coLineWidth);
if I >= 0 then
Prog.SetOperand(I, 0, '0.123456'); // 0.1235 olarak yazılır
PDF.WritePageContentLayer(1, Prog);
// Yalnızca karşılaştırma için kanonik kopya, geri yazılmaz:
// bayrağı yalnızca Parse okur; kurduktan sonra yeniden ayrıştır
Prog.CanonicalNumbers := True;
Prog.Parse(Prog.Emit);
// Prog.Emit artık 0.50000 ile 0.5'i aynı yazar
finally
Prog.Free;
end;
PDF.SaveToFile('calibrated-out.pdf');
finally
PDF.Free;
end;
end;
Bir araya getirince çağıranlar için kural kısadır. Düz bir LoadFromFile ardından SaveToFile, dokunulmamış içerik akışlarını ve dokunulmamış sözlük sayılarını olduğu gibi bırakır. İçerik modeliyle düzenlenen bir sayfa, düzenlemenin değiştirmediği her operandın metnini korur. NormalizeContentStreams, içerik kayıtta sıkıştırıldığında koşan iyileştirici ve CanonicalNumbers'ı True kurulmuş olarak ayrıştırılan her program tasarım gereği kanonik sayılar üretir; belgenin geri kalanı yine korunur
Maliyeti ne ve garanti nerede bitiyor
Her TPDFNumeric bir fazladan AnsiString başvurusu taşır ve ayrıştırılan her ondalık, nesne yaşadığı sürece kaynak metnini canlı tutar. Milyonlarca gerçel sayı taşıyan bir belgede bu ölçülebilir bellektir ve koştuğunuz her büyük belge benchmark'ına girmelidir. Güvence ayrıca sayının kendi belgesiyle sınırlıdır. Nesneleri belgeler arasında kopyalamak ya da nesne API'siyle değerleri yeniden kurmak yeni sayılar üretir ve yeni sayılar çıktı hassasiyetine uyar
Korunan sayılar ile bayt bayt özdeş çıktı ayrı özelliklerdir. Dokunulmamış bir belgenin yüklenip kaydedilmesi render edicinin tükettiği kalibrasyon sayılarını korur; ama bayt özdeşliği ayrıca nesne numaralandırmasına, akış sıkıştırmasına ve belirlenimci PDF ID makalesinde işlenen trailer tanımlayıcısına bağlıdır. Parmak izi diff'i normalleştirilmiş gövdeyi hash'lemeye devam eder; yani başka araçların yazdığı dosyalardaki yuvarlama farkları onun karşılaştırdığının dışındadır. Onlar için denetim, render karşılaştırması olmaya devam eder
Sayı işleme ve kesin SetPrecision semantiği, yukarıda kullanılan içerik modeli çağrılarıyla birlikte losLab PDF Developer Library başvurusunda işlenir