PDF Library for Delphi (PDFlibPas), v3.539.31den beri her PDF sayısı için geçerli JSON yayar. GetObjectJSON, ISO 32000-1in kabul edip RFC 8259in reddettiği tokenleri — -.25, +1.5 ve 007.5 gibi — rakam rakam koruyarak -0.25, 1.5 ve 7.5e yeniden yazar; GetDocumentJSON ile analiz raporları NaN ve Infinity için null yazar; PLDoubleToStr ise bir exportun yarısında EInvalidOp fırlatmak yerine NaN için 0 yazar. Düzeltmeden önce kütüphane, kendi okuyucusunun geri yüklemeyi reddettiği bir JSON üretebiliyordu
Geçerli bir PDF sayısı JSONu nasıl bozar?
Çünkü iki dilbilgisi dört küçük ayrıntıda anlaşamaz ve kaynak metne saygılı bir PDF ayrıştırıcısı o ayrıntıları olduğu gibi çıktıya taşır. ISO 32000-1 §7.3.3, bir sayının artı işaretiyle başlamasına, tam sayı kısmını atlamasına (.5), çıplak noktada bitmesine (4.) ve baştaki sıfırları taşımasına (007.5) izin verir. RFC 8259 §6 hiçbirine izin vermez: opsiyonel eksi, ya 0 olan ya da 1 ile 9 arasında bir rakamla başlayan tam sayı kısmı ve her ondalık noktasından sonra en az bir rakam. Üreticiler PDF biçimlerini yazmakta serbesttir ve sayısız üretici ile elle düzenlenmiş dosya yazmaktadır da
Sızıntı, bilinçli bir hassasiyet özelliğinden geldi. v3.539.19dan beri TPDFNumeric.Output, gerçek sayılar için tokenizerın ayrıştırdığı tam metni döndürür; kaydederken kalibre edilmiş bir renk değerini tam tutan şey de budur, ayrıştırılan PDF ondalık hassasiyetini koruma yazısında anlatıldığı gibi. Tokenizer yolda zaten .5i 0.5e, 4.ü 4.0a yamalar ve tam sayılar değerlerinden yeniden biçimlenir; +3 geriye 3 olarak döner. Olduğu gibi hayatta kalan gerisidir: işaretli baştaki nokta (-.25), bir gerçekteki açık artı (+1.5) ve baştaki sıfırlar (007.5). Eski object yazıcısı Outputu "value":in hemen ardına ekliyordu ve kütüphanenin kendi okuyucusundaki TJSONParser.ParseNumber her birinde "Invalid JSON number" diyerek duruyordu; export başarıyor, geri import PDFLIB_ERROR_OBJECT_JSON_INVALID (105) ile başarısız oluyordu
uses
System.SysUtils, PDFlibrary;
var
Lib: TPDFlib;
JSON: AnsiString;
begin
Lib := TPDFlib.Create;
try
if Lib.LoadFromFile('legacy-drawing.pdf', '') = 0 then
raise Exception.Create('load failed');
// Object 12, [-.25 +1.5 007.5] olarak yazılmış bir dizi
JSON := Lib.GetObjectJSON(12, 0);
// v3.539.31 ve sonrası: değerler -0.25, 1.5 ve 7.5 olarak gelir
// SetObjectJSON opsiyon kabul etmez, bu yüzden 0 geçin
if Lib.SetObjectJSON(12, JSON, 0) = 0 then
raise Exception.CreateFmt('round trip rejected, error %d',
[Lib.LastErrorCode]);
Lib.SaveToFile('legacy-drawing-roundtrip.pdf');
finally
Lib.Free;
end;
end;
PDFNumberTextToJSON her basamağı nasıl korur?
PDFNumberTextToJSON, tokeni bir Doubledan yeniden hesaplamak yerine yeniden yazar. PDFlibObjectJSONdaki fonksiyon opsiyonel bir işaret okur, tek bir ondalık noktasının iki yanındaki rakamları toplar ve ardından yalnızca JSONun istediği düzenlemeleri uygular: artıyı düşürür, bir tanesini tutarak baştaki sıfırları soyar, tam sayı kısmı boşsa 0 koyar, çıplak sondaki noktayı düşürür ve eksiyi geri koyar. Başka bir karakter taşıyan ya da hiç rakam taşımayan bir token PLJSONNumber(Value, 10)a döner; o da değer sonlu değilken null yazar
-.25-0.25olur,+.5ise0.5olur+1.51.5olur007.57.5olur;0.75olduğu gibi kalır4., böyle bir token bir gün yazıcıya ulaşırsa4olur2.22221ile1.250000sondaki sıfırlar dahil her kesir basamağını korur
Saklanan Doubledan biçimlemek daha kısa ve yanlış olurdu; hassasiyet düzeltmesinin var olma sebebi de aynı: varsayılan çıktı hassasiyeti dört ondalıktır ve tam hassasiyetli bir dönüşüm bile bir ondalık literale binary gürültü ekleyebilir. Basamakları tutmak, her JSON sayı metnini PDF tokenizerına veren SetObjectJSON ile ImportObjectJSONin tam olarak aynı değeri yeniden kurması demektir. Garanti değeri kapsar, baytları değil: yeniden importtan sonra -.25, -0.25 olarak saklanır ve kaydedilir. Her iki yazım da §7.3.3 altında eşittir ama bayt düzeyi bir diff değişikliği işaretler; dolayısıyla baytları imzayla korunan bir belgede export-import döngüsünü no-op saymayın
JSONun temsil edemediği bir sayıya ne olur?
GetDocumentJSON artık NaN ya da sonsuz olan her sayı için null yazar, çünkü RFC 8259 §6 için hiçbir sözdizimi sunmaz. Infinity, sandığınızdan kolay üretilir: PDF tokenizerı rakamları tekrarlı çarpımla bir Doublea biriktirir; o değer 1.8 × 10308 civarında tıkanır, yani 300 basamağı biraz aşan bir tam sayı literali sessizce +Inf olur. Dürüst dosyalar böyle bir literal taşımaz; fuzzlanmış ve düşmanca dosyalar taşır. Bu yüzden saldırıya uğramış dosyalara karşı Pascal PDF ayrıştırıcısını sertleştirme yazısındaki durumlarla aynı test korpusuna aitlerdir. Eski document yazıcısı tam sayı olmayanları Str(D:0:6) ile biçimliyordu; +Inf için bu, hiçbir JSON tüketicisinin ayrıştıramayacağı +Inf metnini yazar
null bilinçli olarak kayıplıdır. GetDocumentJSON çıktısının tüketicileri bir sayının görünebileceği her yerde nullü kabul etmeli ve onu eksik bir anahtar olarak değil, "bir değer vardı ama temsil edilemedi" olarak okumalı. Özgün literal, document JSONdan geri kazanılamaz; bu yüzden umursayan bir hattı, varsayılanla değiştirmek yerine objeyi loglayıp dosyayı şüpheli saymalı
Tek bir NaN bir SVG ya da JSON exportunu nasıl yarıda kesebilirdi?
Çünkü content streamlerin, SVGnin, XMLin, CSVnin ve kütüphanedeki JSONun çoğunun arkasındaki invariant sayı biçimlendirici PLDoubleToStr, girdisini ölçekleyip Round çağırıyordu ve Round(NaN), Delphinin x87 geçersiz işlem istisnasını maskelemesiz bıraktığı Win32 gibi hedeflerde EInvalidOp fırlatır. İstisna, yazıcı çıktısının bir kısmını çoktan yaydıktan sonra patlıyordu; yani tek bir dejenere ölçüm, bir metrikteki 0/0 ya da çağıranın geçirdiği bir NaN, geride kesik bir dosya bırakıyordu. PLDoubleToStr artık NaN için 0 döndürür ve tam sayı kolu da kesirli kol gibi ±9.2e18e kelepçelenir; Infinity de böylece sonlu bir literal olarak çıkar
Content stream için sıfır doğru cevaptır; sayı yuvası bir sayı tutmak zorundadır. Rapor için yanlış cevaptır; 0 inandırıcı bir ölçümdür. Farkı korumak zorunda olan JSON yazıcıları, NaN ya da Infinity için null yazıp aksi hâlde invariant rakamlar yazan PDFlibExtradaki PLJSONNumber(Value, Decimals)ı kullanır. PLJSONNumber artık GetSimilarImageDeduplicationReportJSONi, GetAnnotationHitsJSONi ve barcode, deskew, structured text ile PDF/VCR raporlarını destekler; deskew raporu eskiden sonlu olmayan bir açı için 0 yazıyordu, artık null yazıyor
uses
SysUtils, PDFlibTypes, PDFlibExtra;
function SkewReportJSON(Page: Integer; Angle, Confidence: Double): string;
var
B: PLStringBuilder;
begin
B := PLStringBuilder.Create(128);
try
// Önce her Doubleı metne biçimle; PLJSONNumber
// NaN ya da Infinity için null yazar ve her zaman ondalık nokta kullanır
B.Append('{"page":').Append(Page)
.Append(',"angle":').Append(string(PLJSONNumber(Angle, 4)))
.Append(',"confidence":').Append(string(PLJSONNumber(Confidence, 4)))
.Append('}');
// Asla B.Append(Angle) yapma: Double overloadı kullanıcı localeini izler
Result := B.ToString;
finally
B.Free;
end;
end;
Kullanıcı localei hâlâ JSONa nereden sızar?
Bölgesel ayarlara danışan her biçimlendiriciden; makine tarafından okunabilir çıktının tam denetimi tam olarak bir tane buldu: GetSimilarImageDeduplicationReportJSONdeki maxAcceptedMeanError, FloatToStrün ince bir sarmalayıcısı olan PLFloatToStr ile yazılıyordu. Ondalık ayracı virgül olan bir masaüstünde rapor "maxAcceptedMeanError":1,5 içeriyordu; bir JSON parser bunu 1 değeri artı başıboş bir token olarak okur. Alan, algısal image deduplicationdan kabul edilen en kötü piksel hatasını bildirir ve artık PLJSONNumber(Stats.MaxAcceptedMeanError, 6)dan geçer. Kalan tuzak PLStringBuilder: Delphide bu, System.SysUtils.TStringBuilderin düz bir aliasıdır ve onun Append(Double) overloadı kullanıcı localei üzerinden biçimler; FPC derlemeleri ise bir kütüphane sınıfı kullanır, dolayısıyla Free Pascal ya da en-US makinesindeki bir test bunu asla yakalamaz
uses
System.SysUtils, System.JSON, PDFlibrary;
var
Lib: TPDFlib;
Report: WideString;
Parsed: TJSONValue;
begin
// Test çalıştırmasında Alman ya da Fransız masaüstünü yeniden kur
FormatSettings.DecimalSeparator := ',';
Lib := TPDFlib.Create;
try
// Gerçekten birbirine yakın kopya imageler taşıyan bir fikstür kullan;
// yoksa ortalama hata 0dır ve hata saklı kalır
Lib.LoadFromFile('scanned-batch.pdf', '');
// 2, 2, 4 eşikleriyle dry run: belge değiştirilmez
Lib.GetSimilarImageDeduplicationReportJSON(2, 2, 4, Report);
Parsed := TJSONObject.ParseJSONValue(Report);
if Parsed = nil then
raise Exception.Create('report is not valid JSON on a comma locale');
Parsed.Free;
finally
Lib.Free;
end;
end;
JSON çıktısı için bir regresyon takımının dürüst kalması üç fikstür gerektirir: -.25, +1.5 ve 007.5 taşıyan bir sayfa, 400 basamaklı bir tam sayı tutan bir object ve virgül localei altında çalıştırılan herhangi bir rapor; her biri göz kararıyla değil katı bir parserla doğrulanır. Object JSON, document JSON ve PDF Library for Delphideki analiz raporları, Delphi, C++Builder ve Free Pascal genelinde aynı sayı kurallarını paylaşır; tam özellik listesi PDF Library for Delphi ürün sayfasındadır