PDF Library for Delphi (PDFlibPas) od v3.539.31 izdaja veljaven JSON za vsako število PDF. GetObjectJSON prepiše žetone, ki jih ISO 32000-1 sprejme, RFC 8259 pa odkloni, kot so -.25, +1.5 in 007.5, v -0.25, 1.5 in 7.5, števko za števko; GetDocumentJSON in analitična poročila za NaN in Infinity zapišejo null; PLDoubleToStr pa za NaN zapiše 0, namesto da bi na polovici izvoza sprožil EInvalidOp. Pred popravkom je lahko knjižnica izdelala JSON, ki ga njen lastni bralec ni hotel naložiti nazaj
Zakaj veljavno število PDF pokvari JSON?
Ker se dve skladnji ne strinjata o štirih drobnih podrobnostih, razčlenjevalnik PDF, ki spoštuje izvorno besedilo, pa bo te podrobnosti neposredno prenesel v izhod. ISO 32000-1 §7.3.3 dovoli, da se število začne s predznakom plus, izpusti celoštevilski del (.5), konča na goli piki (4.) in nosi vodilne ničle (007.5). RFC 8259 §6 tega ne dovoli ničesar: neobvezen minus, celoštevilski del, ki je bodisi 0 bodisi se začne s 1 do 9, in vsaj ena števka za vsako decimalno piko. Proizvajalci smejo prosto zapisati PDF oblike, in precej generatorjev ter ročno urejenih datotek to tudi počne
Uhajanje je prišlo iz namernih natančnostnih zmožnosti. Od v3.539.19 TPDFNumeric.Output vrne točno besedilo, ki ga je tokenizer razčlenil za realna števila, kar je tisto, ki kalibrirano barvno vrednost obdrži točno ob shranjevanju, kot opisuje ohranjanje razčlenjene decimalne natančnosti PDF. Tokenizer že na poti noter popravi .5 v 0.5 in 4. v 4.0, cela števila pa se preoblikujejo iz svoje vrednosti, zato se +3 vrne kot 3. Kar preživi dobesedno, je preostanek: podpisana vodilna pika (-.25), izrecni plus pri realnem (+1.5) in vodilne ničle (007.5). Stari pisalec objektov je pripel Output takoj za "value":, TJSONParser.ParseNumber v lastnem bralcu knjižnice pa se na vsakem od njih ustavi z »Invalid JSON number«, zato je izvoz uspel, ponovni uvoz pa odpovedal z 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');
// Objekt 12 je polje, zapisano kot [-.25 +1.5 007.5]
JSON := Lib.GetObjectJSON(12, 0);
// v3.539.31 in kasneje: vrednosti prispejo kot -0.25, 1.5 in 7.5
// SetObjectJSON ne sprejme možnosti, zato podajte 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;
Kako PDFNumberTextToJSON obdrži vsako števko?
PDFNumberTextToJSON žeton znova črkuje, namesto da bi ga znova izračunal iz Double. Funkcija v PDFlibObjectJSON prebere neobvezen predznak, pobere števke pred in za enojno decimalno piko in nato uveljavi samo popravke, ki jih zahteva JSON: odvrže plus, odstrani vodilne ničle in obdrži eno, prispe 0, ko je celoštevilski del prazen, odvrže golo končno piko in vrne minus nazaj. Žeton, ki vsebuje kateri koli drug znak ali pa sploh nič števk, pade nazaj na PLJSONNumber(Value, 10), ki za nefinitno vrednost zapiše null
-.25postane-0.25,+.5pa0.5+1.5postane1.5007.5postane7.5,0.75pa ostane, kakršen je4.postane4, če tak žeton sploh kdaj doseže pisalca2.22221in1.250000obdržita vsako ulomkovno števko, vključno z ničlami na koncu
Oblikovanje iz shranjenega Double bi bilo krajše in narobe, iz istega razloga, iz katerega obstaja popravek natančnosti: privzeta izhodna natančnost je štiri decimalki, tudi pretvorba s polno natančnostjo pa lahko decimalnemu literalu doda dvojiški šum. Obdržati števke pomeni, da SetObjectJSON in ImportObjectJSON, ki vsako besedilo števila JSON podata tokenizerju PDF, znova ustvarita točno isto vrednost. Zagotovilo pokriva vrednost, ne bajtov: po ponovnem uvozu je -.25 shranjeno in zapisano kot -0.25. Obe črkovanji sta enaki pod §7.3.3, bajtni diff pa bo spremembo označil, zato cikla izvoz in uvoz ne obravnavajte kot no-op na dokumentu, katerega bajte pokriva podpis
Kaj se zgodi s številom, ki ga JSON ne more predstaviti?
GetDocumentJSON zdaj za vsako število, ki je NaN ali neskončno, zapiše null, ker RFC 8259 §6 za nobeno nima skladnje. Neskončnost je lažje izdelati, kot se sliši: tokenizer PDF nabira števke s ponavljajočim množenjem v Double, ki se izčrpa blizu 1.8 × 10308, zato celoštevilski literal, malo čez 300 števk dolg, tiho postane +Inf. Poštene datoteke takega literala nikoli ne vsebujejo; fuzzirane in sovražne pa ga, zato sodijo v isti testni korpus kot primeri v utrjevanju razčlenjevalnika PDF v Pascalu pred zlonamernimi datotekami. Stari pisalec dokumentov je ne-cela števila oblikoval s Str(D:0:6), za +Inf pa to zapiše besedilo +Inf, ki ga noben porabnik JSON ne bo razčlenil
null je namerno izgubni. Porabniki izhoda GetDocumentJSON morajo sprejeti null kjerkoli se lahko pojavi število, brati pa naj ga kot »vrednost je bila prisotna, a je ni mogoče predstaviti«, ne kot manjkajoči ključ. Izvirnega literala iz JSON dokumenta ni mogoče povrniti, zato naj cevovod, ki mu je mar, zabeleži objekt in datoteko obravnava kot osumljenčko, namesto da bi vstavil privzeto vrednost
Zakaj je lahko en sam NaN prekinil izvoz SVG ali JSON?
Ker je PLDoubleToStr, invariantni formatirnik števil za tokove vsebine, SVG, XML, CSV in večino JSON v knjižnici, skaliral svoj vhod in poklical Round, Round(NaN) pa sproži EInvalidOp na ciljih, kot je Win32, kjer Delphi pusti izjemo neveljavne operacije x87 nemaskirano. Izjema je strelila, potem ko je pisalec že izdal del svojega izhoda, zato je ena degenerirana meritev, 0/0 v metriki ali NaN, ki ga poda klicatelj, pustila za seboj odsekano datoteko. PLDoubleToStr zdaj za NaN vrne 0, njegova celoštevilska veja pa se, tako kot ulomkovna, stisne na ±9.2e18, zato tudi Infinity izide kot finitni literal
Nič je pravi odgovor za tok vsebine, kjer mora reža za število držati število, in napačen odgovor za poročilo, kjer je 0 verodostojna meritev. Piscalci JSON, ki morajo obdržati razliko, uporabljajo PLJSONNumber(Value, Decimals) iz PDFlibExtra, ki za NaN ali Infinity zapiše null, sicer pa invariantne števke. PLJSONNumber zdaj stoji za GetSimilarImageDeduplicationReportJSON, GetAnnotationHitsJSON in poročili za črtne kode, deskew, strukturirani tekst in PDF/VCR; poročilo deskew je prej za nefinitni kot zapisalo 0, zdaj pa zapiše null
uses
SysUtils, PDFlibTypes, PDFlibExtra;
function SkewReportJSON(Page: Integer; Angle, Confidence: Double): string;
var
B: PLStringBuilder;
begin
B := PLStringBuilder.Create(128);
try
// Najprej oblikujte vsak Double v besedilo; PLJSONNumber zapiše null
// za NaN ali Infinity in vedno uporabi decimalno piko
B.Append('{"page":').Append(Page)
.Append(',"angle":').Append(string(PLJSONNumber(Angle, 4)))
.Append(',"confidence":').Append(string(PLJSONNumber(Confidence, 4)))
.Append('}');
// Nikoli B.Append(Angle): preobremenitev Double sledi uporabniškemu locale
Result := B.ToString;
finally
B.Free;
end;
end;
Kod se uporabniški locale še vedno prikrade v JSON?
Skozi kateri koli formatirnik, ki sprašuje regionalne nastavitve, in polna revizija strojno berljivega izhoda je našla točno enega, ki je ostal: maxAcceptedMeanError v GetSimilarImageDeduplicationReportJSON, zapisan s PLFloatToStr, tanko ovijalko okoli FloatToStr. Na namizju, kjer je decimalno ločilo vejica, je poročilo vsebovalo "maxAcceptedMeanError":1,5, kar razčlenjevalnik JSON prebere kot vrednost 1, ki ji sledi osamljen žeton. Polje poroča najslabšo sprejeto napako pikslov iz perceptualne deduplikacije slik in zdaj gre skozi PLJSONNumber(Stats.MaxAcceptedMeanError, 6). Preostala past je PLStringBuilder: na Delphiju je čista alias System.SysUtils.TStringBuilder, katerega preobremenitev Append(Double) oblikuje skozi uporabniški locale, verzije FPC pa namesto tega uporabljajo knjižnični razred, zato ga preizkus na Free Pascal ali računalniku en-US nikoli ne ujame
uses
System.SysUtils, System.JSON, PDFlibrary;
var
Lib: TPDFlib;
Report: WideString;
Parsed: TJSONValue;
begin
// Reproducirajte nemško ali francosko namizje znotraj preizkusnega poganjanja
FormatSettings.DecimalSeparator := ',';
Lib := TPDFlib.Create;
try
// Uporabite fiksno datoteko, ki res vsebuje skoraj podvojene slike,
// sicer je povprečna napaka 0 in hrošč ostane skrit
Lib.LoadFromFile('scanned-batch.pdf', '');
// Suhi tek s pragovi 2, 2, 4: dokument ni spremenjen
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;
Regresijska zbirka za izhod JSON potrebuje tri fiksne datoteke, da ostane iskrena: stran, ki nosi -.25, +1.5 in 007.5, objekt s 400-številskim celim številom in katero koli poročilo, pognano pod locale z vejico, vsako validirano s strogim razčlenjevalnikom, namesto da bi jo pregledali z očmi. JSON objektov, JSON dokumentov in analitična poročila v PDF Library for Delphi si delijo ista pravila za števila čez Delphi, C++Builder in Free Pascal; popoln seznam zmožnosti je na strani izdelka PDF Library for Delphi