PDF Library for Delphi (PDFlibPas) emittoi kelvollista JSONia jokaisesta PDF-luvusta versiosta v3.539.31 alkaen. GetObjectJSON kirjoittaa uudelleen tokenit, jotka ISO 32000-1 hyväksyy mutta RFC 8259 torjuu, kuten -.25, +1.5 ja 007.5, muotoihin -0.25, 1.5 ja 7.5 numero kerrallaan; GetDocumentJSON ja analyysiraportit kirjoittavat nullin NaN:lle ja Infinitylle; ja PLDoubleToStr kirjoittaa 0:n NaN:lle sen sijaan, että nostaisi virheen EInvalidOp viennin puolivälissä. Ennen korjausta kirjasto saattoi tuottaa JSONia, jonka oma lukijansa kieltäytyi lataamasta
Miksi kelvollinen PDF-luku rikkoo JSONin?
Siksi, että kaksi kielioppia eriävät neljästä pienestä yksityiskohdasta, ja PDF-jäsennin, joka kunnioittaa lähdetekstiä, vie ne yksityiskohdat suoraan tulosteeseen. ISO 32000-1 §7.3.3 antaa luvun alkaa plusmerkillä, jättää kokonaisosan pois (.5), päättyä paljaaseen pisteeseen (4.) ja kantaa etunollia (007.5). RFC 8259 §6 ei salli mitään tuosta: valinnainen miinus, kokonaisosa, joka on joko 0 tai alkaa numerolla 1–9, ja vähintään yksi numero minkä tahansa desimaalipisteen jälkeen. Tuottajat saavat kirjoittaa PDF-muodot vapaasti, ja paljon generaattoreita sekä käsin muokattuja tiedostoja tekee niin
Vuoto tuli tahallisesta tarkkuusominaisuudesta. Versiosta v3.539.19 alkaen TPDFNumeric.Output palauttaa tokenisoijan reaaliluvuille jäsentämän tarkan tekstin, ja juuri se pitää kalibroidun väriarvon tarkkana tallennuksessa, kuten kirjoituksessa jäsennettyjen PDF-desimaalitarkkuuksien säilyttäminen kuvataan. Tokenisoija korjaa jo tuloaukaisulla .5:n muotoon 0.5 ja 4.:n muotoon 4.0, ja kokonaisluvut muotoillaan arvostaan, joten +3 palaa muodossa 3. Sanasanaisena selviää loput: etumerkillinen alkupiste (-.25), eksplisiittinen plus reaaliluvussa (+1.5) ja etunollat (007.5). Vanha objektikirjoittaja liitti Outputin suoraan perään "value"::n jälkeen, ja kirjaston oman lukijan TJSONParser.ParseNumber pysähtyy jokaiseen niistä ilmoituksella "Invalid JSON number", joten vienti onnistui ja uudelleentuonti kaatui virheeseen 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');
// Objekti 12 on taulukko, joka on kirjoitettu muodossa [-.25 +1.5 007.5]
JSON := Lib.GetObjectJSON(12, 0);
// v3.539.31 ja uudemmat: arvot saapuvat muodossa -0.25, 1.5 ja 7.5
// SetObjectJSON ei ota vastaan optioita, joten anna 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;
Miten PDFNumberTextToJSON säilyttää jokaisen numeron?
PDFNumberTextToJSON tavuttaa tokenin uudelleen sen sijaan, että laskisi sen uudelleen Doublesta. Funktio PDFlibObjectJSONin sisällä lukee valinnaisen etumerkin, kerää numerot yhden desimaalipisteen molemmin puolin ja tekee sitten vain ne muutokset, joita JSON vaatii: se pudottaa plusmerkin, poistaa etunollat säästäen yhden, toimittaa 0:n, kun kokonaisosa on tyhjä, pudottaa paljaan lopupisteen ja laittaa miinuksen takaisin. Tokeni, joka sisältää minkä tahansa muun merkin tai ei yhtään numeroa, putoaa funktioon PLJSONNumber(Value, 10), joka kirjoittaa nullin, kun arvo ei ole äärellinen
-.25muuttuu muodoksi-0.25, ja+.5muuttuu muodoksi0.5+1.5muuttuu muodoksi1.5007.5muuttuu muodoksi7.5, ja0.75pysyy sellaisenaan4.muuttuu muodoksi4, jos sellainen token joskus päätyy kirjoittajalle2.22221ja1.250000säilyttävät jokaisen murtolukunumeron, lopulliset nollat mukaan lukien
Muotoilu talletetusta Doublesta olisi ollut lyhyempiä ja väärää, samasta syystä, miksi tarkkuuskorjaus on olemassa: oletustulostetarkkuus on neljä desimaalia, ja jo täystarkkuuden muunnos voi lisätä binäärikohinaa desimaalilitteraaliin. Numeroiden säästäminen tarkoittaa, että SetObjectJSON ja ImportObjectJSON, jotka antavat jokaisen JSON-lukutekstin PDF-tokenisoijalle, rakentavat täsmälleen saman arvon. Takuu kattaa arvon, eivät tavut: uudelleentuonnin jälkeen -.25 on talletettu ja tallennettu muodossa -0.25. Molemmat kirjoitusasut ovat yhtä suuria kohdan §7.3.3 nojalla, mutta tavutason diff liputtaa muutoksen, joten älä käsittele vienti- ja tuontisykliä no-op:na asiakirjassa, jonka tavut ovat allekirjoituksen peitossa
Mitä luvulle tapahtuu, jota JSON ei voi esittää?
GetDocumentJSON kirjoittaa nyt nullin jokaiselle luvulle, joka on NaN tai ääretön, koska RFC 8259 §6 ei tarjoa kummallekaan syntaksia. Äärettömän tuottaminen on helpompaa kuin kuulostaa: PDF-tokenisoija kerryttää numeroita toistuvalla kertolaskulla Doubleen, joka nousee korkeintaan lähelle arvoa 1.8 × 10308, joten yli 300 numeron pituinen kokonaislitteraali muuttuu hiljaa arvoksi +Inf. Rehelliset tiedostot eivät koskaan sisällä sellaista litteraalia; fuzzatut ja vihamieliset tekevät, minkä vuoksi ne kuuluvat samaan testikorpukseen kuin tapaukset kirjoituksessa Pascal-PDF-jäsennimen kovettaminen haitallisia tiedostoja vastaan. Vanha asiakirjakirjoittaja muotoili ei-kokonaisluvut muodossa Str(D:0:6), ja arvolle +Inf se kirjoittaa tekstin +Inf, jota mikään JSON-kuluttaja ei jäsentä
null on tahallaan tappiollinen. GetDocumentJSONin tulosteen kuluttajien on hyväksyttävä null missä tahansa, missä luku voi esiintyä, ja luettava se muodossa "arvo oli läsnä, mutta sitä ei voi esittää", ei puuttuvana avaimena. Alkuperäistä litteraalia ei voi palauttaa asiakirja-JSONista, joten putki, jolla on asiaa, kirjaa objektin ja kohtelee tiedostoa epäiltynä sen sijaan, että korvaisi arvon oletuksella
Miksi yksittäinen NaN saattoi keskeyttää SVG- tai JSON-viennin?
Koska PLDoubleToStr, kirjaston sisältöstreamien, SVG:n, XML:n, CSV:n ja suurimman osan JSONista takana oleva invariantti lukumuotoilija, skaalasi syötteensä ja kutsui funktiota Round, ja Round(NaN) nostaa virheen EInvalidOp kohteissa kuten Win32, joissa Delphi jättää x87:n epäkelvan operaation poikkeuksen peittaamatta. Poikkeus laukes vasta, kun kirjoittaja oli jo emitoinut osan tulosteestaan, joten yksi degeneroitunut mittaus, 0/0 mittarissa tai kutsujan syöttämä NaN jätti jälkeensä katkenneen tiedoston. PLDoubleToStr palauttaa nyt 0:n NaN:lle, ja sen kokonaislukuhaara rajaa itsensä arvoon ±9.2e18 kuten murtolukuhaara, joten myös Infinity tulee ulos äärellisenä litteraalina
Nolla on oikea vastaus sisältöstreamille, jossa lukupaikan on pidettävä sisällään luku, ja väärä vastaus raportille, jossa 0 on uskottava mittaus. JSON-kirjoittajat, joiden on säilytettävä ero, käyttävät funktiota PLJSONNumber(Value, Decimals) moduulista PDFlibExtra, joka kirjoittaa nullin NaN:lle tai Infinitylle ja invariantit numerot muuten. PLJSONNumber takaa nyt raportit GetSimilarImageDeduplicationReportJSON, GetAnnotationHitsJSON sekä viivakoodin, kallistuksen korjauksen, jäsennetyn tekstin ja PDF/VCR-raportit; kallistuskorjauksen raportti kirjoitti aiemmin arvon 0 ei-äärelliselle kulmalle ja kirjoittaa nyt nullin
uses
SysUtils, PDFlibTypes, PDFlibExtra;
function SkewReportJSON(Page: Integer; Angle, Confidence: Double): string;
var
B: PLStringBuilder;
begin
B := PLStringBuilder.Create(128);
try
// Muotoile jokainen Double tekstiksi ensin; PLJSONNumber kirjoittaa nullin
// NaN:lle tai Infinitylle ja käyttää aina desimaalipistettä
B.Append('{"page":').Append(Page)
.Append(',"angle":').Append(string(PLJSONNumber(Angle, 4)))
.Append(',"confidence":').Append(string(PLJSONNumber(Confidence, 4)))
.Append('}');
// Älä koskaan kutsu B.Append(Angle): Double-overload noudattaa käyttäjän lokaalia
Result := B.ToString;
finally
B.Free;
end;
end;
Missä käyttäjän lokaali yhä piiloutuu JSONiin?
Minkä tahansa alueasetuksia kyselevän muotoilijan kautta, ja koko auditointi koneluettavasta tulosteesta löysi täsmälleen yhden jäljellä: kentän maxAcceptedMeanError funktiossa GetSimilarImageDeduplicationReportJSON, kirjoitettuna funktiolla PLFloatToStr, ohut kääre funktion FloatToStr ympärillä. Työpöydällä, jonka desimaalierotin on pilkku, raportti sisälsi kentän "maxAcceptedMeanError":1,5, jonka JSON-jäsennin lukee arvona 1 ja sitä seuraavana harhaisena tokenina. Kenttä raportoi kirjoituksen havaintopohjaisen kuvien kaksoiskappaleiden tunnistuksen pahimman hyväksytyn pikselivirheen, ja se kulkee nyt funktion PLJSONNumber(Stats.MaxAcceptedMeanError, 6) läpi. Jäljellä oleva sudenkuoppa on PLStringBuilder: Delphillä se on paljas alias luokalle System.SysUtils.TStringBuilder, jonka overload Append(Double) muotoilee käyttäjän lokaalin kautta, kun taas FPC-buildit käyttävät kirjaston omaa luokkaa, joten testi Free Pascalilla tai en-US-koneella ei koskaan nappaa sitä
uses
System.SysUtils, System.JSON, PDFlibrary;
var
Lib: TPDFlib;
Report: WideString;
Parsed: TJSONValue;
begin
// Toista saksalainen tai ranskalainen työpöytä testiajon sisällä
FormatSettings.DecimalSeparator := ',';
Lib := TPDFlib.Create;
try
// Käytä fixtureä, joka oikeasti sisältää lähes identtisiä kuvia,
// muuten keskimääräinen virhe on 0 ja vika jää piiloon
Lib.LoadFromFile('scanned-batch.pdf', '');
// Kuiva ajo kynnysarvoilla 2, 2, 4: asiakirjaa ei muuteta
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-tulosteen regressiosarja tarvitsee pysyäkseen rehellisenä kolme fixtureä: sivun, joka kantaa arvoja -.25, +1.5 ja 007.5, objektin, joka pitää sisällään 400-numeroisen kokonaisluvun, ja minkä tahansa raportin ajettuna pilkkulokaalilla, jokainen validoituna tiukalla jäsennyksellä eikä silmämääräisesti. Objekti-JSON, asiakirja-JSON ja analyysiraportit PDF Library for Delphissä jakavat samat lukusäännöt Delphin, C++Builderin ja Free Pascalin välillä; koko ominaisuusluettelo on PDF Library for Delphi -tuotesivulla