A PDF Library for Delphi (PDFlibPas) a v3.539.31 óta minden PDF számra érvényes JSON-t bocsát ki. A GetObjectJSON azokat a tokeneket, amiket az ISO 32000-1 elfogad, de az RFC 8259 elutasít, mint az -.25, a +1.5 és a 007.5, számjegyről számjegyre -0.25-re, 1.5-re és 7.5-re írja át; a GetDocumentJSON és az elemzési riportok NaN és Infinity esetén null-t írnak; a PLDoubleToStr pedig NaN-ra 0-t ír, EInvalidOp dobása helyett egy export közepén. A javítás előtt a library olyan JSON-t is gyárthatott, amit a saját olvasója nem volt hajlandó visszatölteni
Hogyan töri meg a JSON-t egy érvényes PDF szám?
Mert a két nyelvtan négy apró részleten összevásik, és egy olyan PDF parser, ami tiszteletben tartja a forrásszöveget, ezeket a részleteket egyenesen átvitte a kimenetre. Az ISO 32000-1 §7.3.3-a megengedi, hogy egy szám plusz jellel induljon, kihagyja az egészrészt (.5), csupasz ponton végződjön (4.), és kezdő nullákat hordozzon (007.5). Az RFC 8259 §6-a egyiket sem engedi: opcionális mínusz, 0-s vagy 1-től 9-ig induló egészrész, és legalább egy számjegy bármilyen tizedespont után. A producerek szabadon írhatják a PDF alakokat, és rengeteg generátor és kézzel szerkesztett fájl meg is teszi
A szivárgás egy szándékos pontossági funkcióból jött. A v3.539.19 óta a TPDFNumeric.Output valós számokra a tokenizer által parseolt pontos szöveget adja vissza, ami az, ami egy kalibrált színértéket mentéskor is pontosan tart, ahogy a parseolt PDF tizedes pontosság megőrzése írja. A tokenizer bejövő oldalon már átpatcheli a .5-öt 0.5-re és a 4.-et 4.0-ra, az egészek pedig az értékükből formázódnak újra, így a +3 3-ként jön vissza. Ami szó szerint túlél, az a maradék: előjeles kezdő pont (-.25), explicit plusz egy valós számon (+1.5) és kezdő nullák (007.5). A régi objektumíró az Output-ot közvetlenül a "value": után fűzte, és a library saját olvasójában a TJSONParser.ParseNumber mindegyiken megáll „Invalid JSON number" üzenettel, így az export sikeresnek számított, az újrabetöltés pedig PDFLIB_ERROR_OBJECT_JSON_INVALID-tel (105) bukott el
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');
// A 12-es objektum egy tömb, így íródott: [-.25 +1.5 007.5]
JSON := Lib.GetObjectJSON(12, 0);
// a v3.539.31 és újabb: az értékek -0.25, 1.5 és 7.5 alakban érkeznek
// A SetObjectJSON nem vesz át opciókat, ezért 0-t adj át
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;
Hogyan tartja meg a PDFNumberTextToJSON minden számjegyét?
A PDFNumberTextToJSON átírja a token helyesírását, ahelyett hogy egy Double-ből számolná újra. A PDFlibObjectJSON-beli függvény beolvas egy opcionális előjelet, összegyűjti a számjegyeket egyetlen tizedespont előtt és után, majd csak azokat a javításokat alkalmazza, amiket a JSON megkövetel: eldobja a pluszt, a kezdő nullákat levágja úgy, hogy egyet megtart, 0-t szolgáltat, ha az egészrész üres, eldobja a csupasz záró pontot, és visszateszi a mínuszt. Bármilyen más karaktert tartalmazó, vagy egyáltalán számjegyet nem hordozó token a PLJSONNumber(Value, 10)-re esik vissza, ami nem véges értéknél null-t ír
- a
-.25-0.25lesz, a+.5pedig0.5 - a
+1.51.5lesz - a
007.57.5lesz, miközben a0.75változatlan marad - a
4.4lesz, ha ilyen token valaha elérné az írót - a
2.22221és az1.250000minden tört számjegyét megtartja, a záró nullákat is beleértve
A tárolt Double-ből formázni rövidebb és rosszabb lett volna, ugyanabból az okból, amiért a pontossági javítás létezik: az alapértelmezett kimeneti pontosság négy tizedes, és még egy teljes pontosságú konverzió is tehet egy tizedes literálba bináris zajt. A számjegyek megtartása azt jelenti, hogy a SetObjectJSON és az ImportObjectJSON, amik minden JSON szám szöveget a PDF tokenizernek adnak, pontosan ugyanazt az értéket hozzák létre. A garancia az értékre szól, nem a bájtokra: újrabetöltés után a -.25 -0.25-ként tárolódik és mentődik. Mindkét helyesírás egyenlő a §7.3.3 alatt, de egy bájtszintű diff jelezni fogja a változást, tehát export-import ciklust ne kezelj no-opként olyan dokumentumon, aminek a bájtokat aláírás fedi
Mi történik egy számmal, amit a JSON nem tud ábrázolni?
A GetDocumentJSON mostantól minden NaN vagy végtelen számra null-t ír, mert az RFC 8259 §6-a egyiknek sincs szintaxisa. A végtelent könnyebb előállítani, mint amilyennek hangzik: a PDF tokenizer a számjegyeket ismételt szorzással halmozza egy Double-be, ami 1,8 × 10308 környékén ér csúcsra, így egy 300 számjegy fölötti egéssliterál csendben +Inf-fé válik. Őszinte fájlok sosem tartalmaznak ilyen literált; fuzzolt és ellenséges fájlok igen, ezért tartoznak ugyanabba a tesztkorpuszba, mint a Pascal PDF parser rosszindulatú fájlok elleni megerősítésében szereplő esetek. A régi dokumentumíró a nem egészeket Str(D:0:6)-tal formázta, és +Inf-re az azt írja, hogy +Inf, amit egyetlen JSON fogyasztó sem parseol
A null szándékosan veszteséges. A GetDocumentJSON kimenetének fogyasztóinak el kell fogadniuk a null-t bárhol, ahol szám állhat, és úgy kell olvasniuk, hogy „volt érték, de nem ábrázolható", nem hiányzó kulcsként. Az eredeti literál a dokumentum JSON-ből nem nyerhető vissza, így egy olyan pipeline, aminek ez számít, logolja az objektumot, és gyanús fájlként kezeli az alapérték behelyettesítése helyett
Hogyan szakíthatott meg egyetlen NaN egy SVG vagy JSON exportot?
Mert a PLDoubleToStr, a content streamek, az SVG, az XML, a CSV és a library legtöbb JSON-ja mögötti invariant számformázó megszorzta a bemenetét, és meghívta a Round-ot, a Round(NaN) pedig olyan célokon, mint a Win32, EInvalidOp-ot dob, ahol a Delphi nem maszkolja az x87 érvénytelen művelet kivételét. A kivétel akkor sült el, amikor az író már bocsátotta ki a kimenete egy részét, így egy degenerált mérés, egy 0/0 egy metrikában vagy egy hívó által bedobott NaN csonka fájlt hagyott maga után. A PLDoubleToStr mostantól NaN-ra 0-t ad vissza, és az egész ága ±9.2e18-ra van bilincselve, akárcsak a tört ága, így az Infinity is véges literálként jön ki
A nulla jó válasz egy content streamben, ahol egy szám helyének számot kell tartania, és rossz válasz egy riportban, ahol a 0 hihető mérés. Azok a JSON írók, amiknek meg kell tartaniuk a különbséget, a PDFlibExtra-beli PLJSONNumber(Value, Decimals)-t használják, ami NaN vagy Infinity esetén null-t ír, egyébként invariant számjegyeket. A PLJSONNumber mostantól a GetSimilarImageDeduplicationReportJSON mögött áll, a GetAnnotationHitsJSON mögött, valamint a vonalkód, deskew, strukturált szöveg és PDF/VCR riportok mögött; a deskew riport korábban 0-t írt nem véges szögre, mostantól null-t ír
uses
SysUtils, PDFlibTypes, PDFlibExtra;
function SkewReportJSON(Page: Integer; Angle, Confidence: Double): string;
var
B: PLStringBuilder;
begin
B := PLStringBuilder.Create(128);
try
// Formázd minden Double-t előbb szöveggé; a PLJSONNumber null-t ír
// NaN vagy Infinity esetén, és mindig pont tizedesjelet használ
B.Append('{"page":').Append(Page)
.Append(',"angle":').Append(string(PLJSONNumber(Angle, 4)))
.Append(',"confidence":').Append(string(PLJSONNumber(Confidence, 4)))
.Append('}');
// Soha ne B.Append(Angle): a Double overload a felhasználó locale-ját követi
Result := B.ToString;
finally
B.Free;
end;
end;
Hol cseppen be még a felhasználói locale a JSON-ba?
Bármelyik, a területi beállításokat megkérdező formázón át, és a géppel olvasható kimenet teljes auditja pontosan egyet talált: a maxAcceptedMeanError-t a GetSimilarImageDeduplicationReportJSON-ban a PLFloatToStr írta, egy vékony FloatToStr wrapper. Olyan asztalon, aminek a tizedes elválasztója vessző, a riport "maxAcceptedMeanError":1,5 értéket tartalmazott, amit egy JSON parser 1 értékként olvas, amit egy kóbor token követ. A mező a perceptuális képdeduplikáció legrosszabb elfogadott pixelhibáját jelenti, és mostantól a PLJSONNumber(Stats.MaxAcceptedMeanError, 6)-on megy át. Egy megmaradó csapda a PLStringBuilder: Delphi-n a System.SysUtils.TStringBuilder puszta álneve, aminek az Append(Double) overloadja a felhasználó locale-ján át formáz, az FPC buildek viszont library-s osztályt használnak helyette, így egy teszt Free Pascalon vagy en-US gépen sosem kapja el
uses
System.SysUtils, System.JSON, PDFlibrary;
var
Lib: TPDFlib;
Report: WideString;
Parsed: TJSONValue;
begin
// Német vagy francia asztal újrateremtése a tesztfuttatáson belül
FormatSettings.DecimalSeparator := ',';
Lib := TPDFlib.Create;
try
// Olyan fixtúrát használj, ami tényleg tartalmaz majdnem duplikált képeket,
// különben az átlagos hiba 0, és a hiba rejtve marad
Lib.LoadFromFile('scanned-batch.pdf', '');
// Próbafuttatás 2, 2, 4 küszöbökkel: a dokumentum nem módosul
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;
Egy JSON kimenetre írt regressziós csomagnak három fixtúrára van szüksége, hogy becsületes maradjon: egy oldal, ami -.25-öt, +1.5-öt és 007.5-öt hordoz, egy objektum, ami egy 400 számjegyű egészet tart, és bármilyen riport vesszős locale alatt, mindegyik szigorú parserrel validálva, nem megpillantva. Az objektum JSON, a dokumentum JSON és az elemzési riportok a PDF Library for Delphi-ben ugyanazokat a számszabályokat használják Delphi-n, C++Builderen és Free Pascalon; a teljes funkciólista a PDF Library for Delphi termékoldalon van