PDF Library for Delphi (PDFlibPas) nuo v3.539.31 išduoda teisėtą JSON kiekvienam PDF skaičiui. GetObjectJSON žetonus, kuriuos ISO 32000-1 priima, o RFC 8259 atmeta, tokius kaip -.25, +1.5 ir 007.5, persirašo į -0.25, 1.5 ir 7.5 skaitmenis po skaitmens; GetDocumentJSON ir analizės ataskaitos NaN ir Infinity rašo kaip null; o PLDoubleToStr NaN atveju rašo 0 vietoj EInvalidOp išmetimo pačiame eksporto viduryje. Iki pataisos biblioteka galėjo pagaminti JSON, kurio paties skaitytuvas atsisakydavo pakrauti
Kodėl teisėtas PDF skaičius laužia JSON?
Todėl, kad dvi gramatikos nesutaria dėl keturių smulkmenų, o šaltinio tekstą gerbiantis PDF analizatorius tas smulkmenas neša tiesiai į išvestį. ISO 32000-1 §7.3.3 leidžia skaičiui prasidėti pliuso ženklu, praleisti sveikąją dalį (.5), baigtis pliku tašku (4.) ir nešti priekinius nulius (007.5). RFC 8259 §6 neleidžia nė vieno iš tų dalykų: neprivalomas minusas, sveikoji dalis, kuri arba 0, arba prasideda skaitmenimis 1–9, ir bent vienas skaitmuo po bet kokio dešimtainio taško. Gamintojai laisvai rašo PDF formas, o nemažai generatorių ir rankomis redaguotų failų tai daro
Prasiskverbimas atėjo iš sąmoningo tikslumo bruožo. Nuo v3.539.19 TPDFNumeric.Output grąžina tikslų tekstą, kurį tokenizatorius išanalysavo tikriesiems skaičiams – būtent todėl sukalibruota spalvos reikšmė išsaugojime lieka tiksli, kaip aprašyta išanalizuoto PDF dešimtainio tikslumo išsaugojime. Tokenizatorius pakeliui į vidų jau sutvarko .5 į 0.5 ir 4. į 4.0, o sveikieji performatuojami iš reikšmės, tad +3 grįžta kaip 3. Pažodžiui išgyvena visa kita: ženklintas priekinis taškas (-.25), atviras pliusas tikrajam skaičiui (+1.5) ir priekiniai nuliai (007.5). Senasis objektų rašytojas prie "value": priklijuodavo Output, o bibliotekos pačios skaitytuvo TJSONParser.ParseNumber ant kiekvieno iš jų sustoja su „Invalid JSON number“, tad eksportas pasisekdavo, o pakartotinis importas krisdavo su PDFLIB_ERROR_OBJECT_JSON_INVALID (105)
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');
// Objektas 12 yra masyvas, parašytas kaip [-.25 +1.5 007.5]
JSON := Lib.GetObjectJSON(12, 0);
// v3.539.31 ir naujesnėse: reikšmės atkeliauja kaip -0.25, 1.5 ir 7.5
// SetObjectJSON parinkčių nepriima, tad perduokite 0
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;
Kaip PDFNumberTextToJSON išlaiko kiekvieną skaitmenį?
PDFNumberTextToJSON žetoną perskirba iš naujo, o ne perskaičiuoja jį iš Double. Funkcija faile PDFlibObjectJSON skaito neprivalomą ženklą, surenka skaitmenis aplink vienintelį dešimtainį tašką ir tada pritaiko tik redagavimus, kurių reikalauja JSON: numeta pliusą, nukerpa priekinius nulius palikdama vieną, paduoda 0, kai sveikoji dalis tuščia, numeta pliką galinį tašką ir sugrąžina minusą. Žetonas, kuriame yra bet koks kitas simbolis arba iš viso nėra skaitmenų, grįžta į PLJSONNumber(Value, 10), kuri nebegtinai reikšmei rašo null
-.25tampa-0.25, o+.5tampa0.5+1.5tampa1.5007.5tampa7.5, o0.75lieka toks, koks yra4.tampa4, jei toks žetonas kada nors pasieks rašytoją2.22221ir1.250000išlaiko kiekvieną trupmeninį skaitmenį, galinius nulius įskaitant
Formatavimas iš saugomo Double būtų buvęs trumpesnis ir klaidingas – dėl tos pačios priežasties egzistuoja tikslumo pataisa: numatytasis išvesties tikslumas yra keturi dešimtainiai, o net pilno tikslumo konversija dešimtainiui literalui gali pridėti dvejetainio triukšmo. Skaitmenų išlaikymas reiškia, kad SetObjectJSON ir ImportObjectJSON, kurie kiekvieną JSON skaičiaus tekstą perduoda PDF tokenizatoriui, atkuria lygiai tą pačią reikšmę. Garantija dengia reikšmę, o ne baitus: po pakartotinio importo -.25 saugojama ir išsaugoma kaip -0.25. Abi rašybos pagal §7.3.3 lygiavertės, bet baitų lygio diff pakeitimą pažymės, tad eksporto ir importo ciklo laikykite ne no-op dokumente, kurio baitus dengia parašas
Kas nutinka skaičiui, kurio JSON negali išreikšti?
GetDocumentJSON dabar bet kokiam NaN ar begalybiniam skaičiui rašo null, nes RFC 8259 §6 neturi sintaksės nei vienam, nei kitam. Infinity pasigaminti lengviau, nei skamba: PDF tokenizatorius skaitmenis kaupia pakartotine daugyba į Double, kurios riba šalia 1.8 × 10308, tad sveikasis literalas, ilgesnis už maždaug 300 skaitmenų, tyliai tampa +Inf. Sąžiningi failai tokio literalo niekada neturi; fuzzing'o ir priešiškų failų tarpe jie pasitaiko – todėl jie priklauso tam pačiam testų korpusui kaip Pascal PDF analizatoriaus sukietinimo prieš kenkėjiškus failus atvejai. Senasis dokumentų rašytojas netikrus skaičius formatuodavo su Str(D:0:6), o +Inf atveju tai rašo tekstą +Inf, kurio joks JSON vartotojas neišanalysuos
null čia sąmoningai prarandantis duomenis. GetDocumentJSON išvesties vartotojai turi priimti null bet kurioje vietoje, kur gali atsirasti skaičius, ir skaityti jį kaip „reikšmė buvo, bet jos išreikšti negalima“, o ne kaip dingusį raktą. Pirminio literalo iš dokumento JSON atgauti neįmanoma, tad pipeline, kuriam tai rūpi, turėtų užregistruoti objektą ir failą laikyti įtartinu, vietoj to, kad įkištų numatytąją reikšmę
Kodėl vienas NaN galėjo nutraukti SVG arba JSON eksportą?
Todėl, kad PLDoubleToStr – invariantinis skaičių formatuotojas, stovintis už turinio srautų, SVG, XML, CSV ir didžiosios dalies bibliotekos JSON – savo įvedimą padaugindavo ir kviestų Round, o Round(NaN) tokiose taikinio platformose kaip Win32, kur Delphi palieka x87 netinkamos operacijos išimtį neužmaskuotą, kelia EInvalidOp. Išimtis sprogdavo tada, kai rašytojas jau buvo išdavęs dalį išvesties, tad vienas degradavęs matavimas, 0/0 metikoje arba kvietėjo perduotas NaN palikdavo po savęs nukirstą failą. PLDoubleToStr dabar NaN grąžina 0, o jo sveikųjų skaičių šaka apkarpoma iki ±9.2e18 kaip ir trupmeninė, tad Infinity taip pat išeina kaip baigtinis literalas
Nulis yra teisingas atsakymas turinio srautui, kur skaičiaus vietoje turi gulėti skaičius, ir neteisingas atsakymas ataskaitai, kur 0 yra įtikinamas matavimas. JSON rašytojai, kuriems privaloma išlaikyti skirtumą, naudoja PLJSONNumber(Value, Decimals) iš PDFlibExtra, kuri NaN ar Infinity rašo null, o kitu atveju – invariantinius skaitmenis. PLJSONNumber dabar paremia GetSimilarImageDeduplicationReportJSON, GetAnnotationHitsJSON ir brūkšninių kodų, išlyginimo, struktūrinio teksto bei PDF/VCR ataskaitas; išlyginimo ataskaita anksčiau nebegtinam kampui rašė 0, o dabar rašo null
uses
SysUtils, PDFlibTypes, PDFlibExtra;
function SkewReportJSON(Page: Integer; Angle, Confidence: Double): string;
var
B: PLStringBuilder;
begin
B := PLStringBuilder.Create(128);
try
// Kiekvieną Double pirmiausiai suformatuokite į tekstą; PLJSONNumber rašo null
// NaN ar Infinity atveju ir visuomet vartoja dešimtainį tašką
B.Append('{"page":').Append(Page)
.Append(',"angle":').Append(string(PLJSONNumber(Angle, 4)))
.Append(',"confidence":').Append(string(PLJSONNumber(Confidence, 4)))
.Append('}');
// Niekada B.Append(Angle): Double perkroda seka vartotojo lokalę
Result := B.ToString;
finally
B.Free;
end;
end;
Kur vartotojo lokalė vis dar išsmunka į JSON?
Pro bet kurį formatuotoją, kuris pasižiūri į regioninius nustatymus, o pilnas mašinai skiriamo išvesties auditas rado lygiai vieną likusį: maxAcceptedMeanError faile GetSimilarImageDeduplicationReportJSON, rašytą su PLFloatToStr – plonu FloatToStr apvalkalu. Darbalaukyje, kurio dešimtainis skirtukas kablelis, ataskaitoje gulėjo "maxAcceptedMeanError":1,5, ką JSON analizatorius skaito kaip reikšmę 1 ir po jos plaukiojantį žetoną. Laukas praneša blogiausią priimtą pikselių klaidą iš suvokiamos vaizdų dedublikacijos, ir dabar jis eina per PLJSONNumber(Stats.MaxAcceptedMeanError, 6). Likęs spąstai yra PLStringBuilder: Delphi jis yra paprastas System.SysUtils.TStringBuilder alternatyvusis vardas, kurio Append(Double) perkroda formatuoja per vartotojo lokalę, o FPC versijos vietoj to naudoja bibliotekos klasę, tad testas su Free Pascal arba en-US mašinoje jo niekada nepagaus
uses
System.SysUtils, System.JSON, PDFlibrary;
var
Lib: TPDFlib;
Report: WideString;
Parsed: TJSONValue;
begin
// Atkurkite vokišką arba prancūzišką darbalaukį pačiame testo paleidime
FormatSettings.DecimalSeparator := ',';
Lib := TPDFlib.Create;
try
// Naudokite fikstūrą, kuri iš tiesų turi beveik dubliuotų vaizdų,
// kitaip vidutinė klaida lygi 0 ir klaida lieka paslėpta
Lib.LoadFromFile('scanned-batch.pdf', '');
// Bandomasis paleidimas su slenksčiais 2, 2, 4: dokumentas nekeičiamas
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 išvesties regresijos rinkiniui, kad jis liktų sąžiningas, reikia trijų fikstūrų: puslapio su -.25, +1.5 ir 007.5, objekto, laikančio 400 skaitmenų sveikąjį skaičių, ir bet kokios ataskaitos, paleistos kablelio lokalėje, kiekvienos patikrintos griežtu analizatoriumi, o ne akimis. Objektų JSON, dokumentų JSON ir analizės ataskaitos PDF Library for Delphi dalijasi tais pačiais skaičių taisyklėmis per Delphi, C++Builder ir Free Pascal; pilnas bruožų sąrašas yra PDF Library for Delphi produkto puslapyje