PDFlibPas, losLab PDF Developer Library, saugo tikslų dešimtainį tekstą, kurį perskaitė kiekvienam dokumento realiajam skaičiui, ir tą tekstą rašo atgal pažodžiui, kai reikšmė niekada nebuvo keista. Nuo v3.539.19 SetPrecision nustatymas valdo tik tuos skaičius, kuriuos biblioteka sukuria ar redaguoja, tad įprastas įkėlimas ir išsaugojimas nebeapvalina CalRGB /Gamma reikšmės 2.22221 žemyn į 2.2222 ir nebeslenka puslapio, kurio niekas nelietė, spalvų. Pokytis nedidelis kode ir didelis tame, ką jis sako apie analizatorius: reikšmė, kurią nusidekoduojate, ir literalas, kurį išduodate, yra du skirtingi dalykai, o Double kelionė pirmyn ir atgal nėra tapatumo transformacija
Kodėl nieko nepakeitęs išsaugojimas paslenka puslapio spalvas?
Nes buvo performatuojami spalvų erdvės parametrai, o ne paveikslėlis. Failas, kuris tai atskleidė, yra 35 puslapių biuro dokumentas vietiniame regresijos korpuse su antraštės paveikslėliu, naudojamu kiekviename puslapyje. Jį įkėlus ir iš karto išsaugojus atgal, gauti paveikslėlių srautai buvo identiški įvesčiai baitas į baitą, o srautų maišų palyginimas pranešė, kad dokumentas nepakitęs. Atvaizduotos kopijos palyginimas nesutiko: visuose 35 puslapiuose atsirado pikselių skirtumų antraštėje, ir niekur kitur
Antraštės paveikslėlis braižomas per CalRGB spalvų erdvę, kurią ISO 32000-1 §8.6.5.3 apibrėžia /WhitePoint, neprivalomu trijų elementų /Gamma masyvu ir neprivalomu devynių elementų /Matrix. Tie masyvai yra paprasti skaitiniai objektai spalvų erdvės žodyne. TPDFNumeric kiekvieną jų saugojo kaip Double ir nieko daugiau, o TPDFNumeric.Output tą Double formatuodavo per PDFPrecNum, kurio numatytoji reikšmė yra keturios dešimtainės vietos. Taigi /Gamma iš 2.22221 virto 2.2222, matricos elementas iš 0.71519 virto 0.7152, ir atvaizduoklis stropiai pagamino šiek tiek kitokias spalvas iš šiek tiek kitokios kalibracijos. Paveikslėlio baitai buvo nekalti; skaičiai aplink juos – ne. Nepatogiausia tai, koks nematomas šis dalykas buvo. Palyginimas pagal nusidekodavusius srauto baitus to pamatyti negali, nes skaičiai gyvena žodyne, o ne sraute. Priedų turinio palyginimas to pamatyti negali. Net revizijų skirtumai, aprašyti modifikavimo lygių straipsnyje, paima normalizuoto objekto kūno pirštų atspaudą, tad abi revizijos su maiša duoda tą pačią reikšmę ir skirtumai jas praneša identiškomis. Tai pagavo tik atvaizdavimas – štai kodėl korpuso etalonas atvaizduoja kiekvieną puslapį, o ne pasitiki vien struktūriniais patikrinimais
Reikšmė, kurią perskaitėte, nėra literalas, kurį reikia rašyti
PDF realusis skaičius yra dešimtainė eilutė, ir ISO 32000-1 §7.3.3 aiškiai sako, kad tai tik dešimtainė eilutė: jokio pagrindo žymėjimo, jokios eksponentinės formos. C priedas tada išvardija tikslumą, kurį implementacija turi gerbti – maždaug penkis reikšminius dešimtainius skaitmenis trupmeninėje dalyje. Numatytasis keturių ženklų išvesties tikslumas jau yra mažesnis už tai, o prie nulio dar blogiau: PLDoubleToStr keičia reikšmės mastelį, apvalina iki sveikojo skaičiaus ir išduoda 0, kai rezultatas lygus nuliui, tad matricos elementas -0.000012345 nepraranda skaitmens – jis išnyksta visai
Padidinus numatytąją reikšmę, uola tik pasislinktų. Pataisa yra nustoti apsimesti, kad Double ir yra tas skaičius. Kai TPDFStructure.Decode žetonizatorius atpažįsta standartinį realųjį skaičių, tai yra žetonas turi dešimtainį tašką ir jokio eksponento žymens, jis šaltinio tekstą įrašo į naują FOriginalText lauką greta konvertuotos reikšmės. Output tada teikia pirmenybę tam tekstui ir grįžta prie formatavimo tik tada, kai nėra kam teikti pirmenybės
// Lib/PDFlibStruct.pas — visa pataisa išvesties pusėje
Function TPDFNumeric.Output: AnsiString;
Begin
If FOriginalText<> '' Then
Result:= FOriginalText
Else
Result:= PLDoubleToStr(FValue, Owner.PDFPrecNum);
End;
Procedure TPDFNumeric.SetTo(Const Value: Double);
Begin
FOriginalText:= ''; // paredaguotas skaičius yra naujas skaičius
FValue:= Value;
FChanged:= True;
End;
Dvi ribos yra sąmoningos. Sveikieji skaičiai neišsaugomi, nes sveikųjų skaičių formatavimas ir taip yra be nuostolių. Eksponentinės formos, tokios kaip 6.02E23, įvestyje toleruojamos dėl sulaužytų generatorių, bet išvestyje neišsaugomos, nes jų įrašymas atgal įtvirtintų sintaksę, kurią §7.3.3 draudžia; jos eina per formatuotoją kaip bet kuris bibliotekos sukurtas skaičius. Žetonizatorius taip pat prieš įrašydamas tekstą pritaiko savo įprastą minimalų taisymą, tad literalas su pradiniu tašku kaip .5 paliekamas kaip 0.5, o literalas su baigiamuoju tašku kaip 5. – kaip 5.0. Abu kiekvienam skaitytuvui yra tas pats skaičius ir yra daug plačiau priimami
Ką SetPrecision garantuoja po v3.539.19?
TPDFlib.SetPrecision dabar valdo dešimtaines vietas skaičių, kuriuos pagamina pati biblioteka: reikšmių, nubraižytų per painter, skaičių, sukurtų iš Double, pavyzdžiui per NewNumeric, ir bet kurios perskaitytos reikšmės, kuri nuo to laiko buvo paredaguota su SetTo. Atkreipkite dėmesį, kad tekstas, nusidekoduotas per objektų API, pavyzdžiui literalas, perduotas SetObjectFromString, eina per tą patį žetonizatorių ir yra išsaugomas taip pat. Perskaitytas dešimtainis skaičius, kuris niekada nebuvo keistas, išlaiko savo įvesties tikslumą nepriklausomai nuo nustatymo, o nustatymo pakeitimas po įkėlimo jo atgaline data neliečia. SetPrecision nuorodos įrašas buvo atnaujintas tame pačiame leidime, kad pasakytų būtent tai, nes senoji formuluotė leido suprasti, kad nustatymas taikomas kiekvienam failo skaičiui
Išvalymas įvyksta SetTo viduje, o ne išvedamas iš Changed vėliavėlės, ir tas skirtumas svarbus. Išsaugojimo grandinė atstato Changed objektuose, kai jie jau buvo įrašyti, tad patikrinimas formos „išduok originalų tekstą, jei nepakeista“ pradėtų išduoti pasenusį tekstą reikšmei, kuri buvo paredaguota, išsaugota ir vėl paredaguota toje pačioje sesijoje. Originalaus teksto susiejimas su pačiu priskyrimu padaro neįmanoma, kad jie nesutartų. Regresijos testas kiekvieną iš šių elgsenų prisega reikšmėmis iš originalaus failo
uses
PDFlibStruct;
var
Structure: TPDFStructure;
Values: TPDFArray;
Number: TPDFNumeric;
begin
Structure := TPDFStructure.Create;
try
Structure.PDFPrecNum := 4;
Values := TPDFArray(Structure.Decode('[2.22221 0.71519 -0.000012345 1 0.12567]'));
// Nepaliesta įvestis išlieka pažodžiui, įskaitant reikšmę, kurią
// keturių ženklų formatavimas būtų sutraukęs į 0
Assert(Values.Output = '[ 2.22221 0.71519 -0.000012345 1 0.12567 ]');
// Redagavimas išmeta originalų tekstą ir seka PDFPrecNum
Number := TPDFNumeric(Values.Item[0]);
Number.SetTo(0.123456);
Assert(Number.Output = '0.1235');
Assert(Structure.NewNumeric(0.123456).Output = '0.1235');
// Tikslumo sumažinimas po to nepasiekia nepaliestos įvesties
Structure.PDFPrecNum := 2;
Assert(TPDFNumeric(Values.Item[1]).Output = '0.71519');
finally
Structure.Free;
end;
end;
Kodėl turinio modelis vis tiek normalizuoja skaičius?
Nes TPDFContentProgram žada kanoninius skaitinius operandus, ir tas pažadas vertas daugiau nei pažodinis tekstas turinio srauto viduje. Redaguojamas turinio modelis – tas pats, ant kurio pastatytas grafinės būsenos sekiklis – egzistuoja tam, kad NormalizeContentStreams, optimizatorius ir Emit iš bet kokios įvesties duotų stabilų, palyginamą rezultatą. Jei perskaitytas operandas į modelį atsineštų savo originalų tekstą, operatorių seka kaip 0.50000 0 0 RG būtų išduodama kitaip nei 0.5 0 0 RG, ir kiekvienas tolimesnis palyginimas imtų plaukti pagal generatoriaus formatavimo įpročius
Tad modelis nuima originalų tekstą savo dviejuose įėjimo taškuose. NormalizeContentNumbers paleidžiamas kiekvienam operandui, kai analizatorius jį įstumia, ir dar kartą SetOperand viduje, kai nusidekoduojamas kviečiančiojo pateiktas šaltinis, ir jis rekursyviai eina per masyvus bei žodynus, tad brūkšnių šablonai, TJ masyvai ir žymėto turinio savybių žodynai taip pat uždengiami. Pakanka kiekvienam skaitiniam objektui iškviesti SetTo(AsDouble), nes būtent ši operacija ir išvalo tekstą. Neapdoroti įterpto paveikslėlio duomenys paliekami ramybėje, kaip visada buvo
// Lib/PDFlibContentModel.pas — turinio modelis laikosi savo kontrakto
Procedure NormalizeContentNumbers(Obj: TPDFObject);
Var
K: Integer;
Begin
If Obj is TPDFNumeric Then
TPDFNumeric(Obj).SetTo(TPDFNumeric(Obj).AsDouble)
Else If Obj is TPDFArray Then
For K:= 0 To TPDFArray(Obj).Count- 1 Do
NormalizeContentNumbers(TPDFArray(Obj).Item[K])
Else If Obj is TPDFDictionary Then
For K:= 0 To TPDFDictionary(Obj).Count- 1 Do
NormalizeContentNumbers(TPDFDictionary(Obj).Entry[K].Value);
End;
Praktinė taisyklė kviečiantiesiems todėl paprasta. Paprastas LoadFromFile ir po jo SaveToFile palieka nepaliestus turinio srautus ir nepaliestus žodyno skaičius tokius, kokie buvo. Puslapis, praėjęs per NormalizeContentStreams, arba bet koks redagavimas, atliktas per turinio modelį, išeina kanoninis pagal sumanymą, o likusi dokumento dalis vis tiek išsaugoma. Tai du skirtingi prašymai, ir dabar jie daro du skirtingus dalykus
Kiek tai kainuoja ir kur garantija sustoja
Kiekvienas TPDFNumeric dabar nešasi vieną papildomą AnsiString nuorodą, o kiekvienas perskaitytas dešimtainis skaičius laiko savo šaltinio tekstą gyvą visą objekto gyvavimo laiką. Dokumente su milijonais realiųjų skaičių tai yra tikra atmintis, ir ji turi būti įtraukta į bet kokį didelių dokumentų matavimą, o ne nurašyta mostelint ranka. Garantija taip pat apribota paties skaičiaus dokumentu: objektų kopijavimas tarp dokumentų ar reikšmių atkūrimas per objektų API pagamina naujus skaičius, o tie seka išvesties tikslumą kaip bet kuris kitas naujas skaičius. Verta būti tiksliems dėl to, ką leidimas teigia ir ko ne. Nepaliesto dokumento įkėlimas ir išsaugojimas dabar išsaugo kalibracijos skaičius, kuriuos atvaizduoklis iš tikrųjų vartoja – tai savybė, kurią tikrina korpuso etalonas. Jis neteigia identiškos išvesties baitu lygmenyje, kuri taip pat priklauso nuo objektų numeravimo, srautų glaudinimo ir trailer identifikatoriaus, aptarto deterministinio PDF ID straipsnyje. Ir jis nepadaro taip, kad pirštų atspaudų palyginimas matytų apvalinimo skirtumus kitoje programinėje įrangoje pagamintuose failuose, nes tie vis tiek maišuoja normalizuotą kūną. Pamoka apibendrinama kur kas plačiau nei CalRGB: kai analizatorius laiko tik konvertuotą reikšmę, kiekvienas išsaugojimas yra redagavimas, o vienintelis būdas tai pastebėti – pažiūrėti į atvaizduotą rezultatą. Skaičių apdorojimas ir SetPrecision semantika aprašyti losLab PDF Developer Library produkto puslapyje