PDFlibPas, losLabin PDF Developer Library for Delphi, kirjoittaa jokaisen sisältöstreamiin laittamansa luvun pistedesimaalierottimella ja ilman eksponenttia, riippumatta siitä, mitä Windowsin alueasetukset sanovat. Versiosta v3.539.26 alkaen AddPageMatrix, ScalePage, DeskewPage, RedactRegion, text-to-path-tuloste ja värien muuntaminen muotoilevat operandinsa funktiolla PLDoubleToStrConst, ja v3.539.33:stä alkaen luvut takaisin lukevat jäsennimet käyttävät PLTryStrToFloatInvariantia järjestelmän lokaalin sijaan. Saksalaisella, ranskalaisella tai brasilialaisella koneella sama koodi tuottaa nyt samat tavut kuin yhdysvaltalaisella koneella, ja se on ainoa käyttäytyminen, jonka tiedostomuoto sietää
Miksi pilkkudesimaalilokaali vioittaa PDF:ää ilman virhettä?
Pilkkudesimaalilokaali vioittaa PDF:ää hiljaa, koska pilkku ei ole lukumerkki PDF-syntaksissa, joten vaurio lukeutuu kelvollisina tokeneina väärällä merkityksellä. Ennen korjausta PLFloatToStr ei ollut muuta kuin paljas FloatToStr-kutsu, ja FloatToStr noudattaa arvoa FormatSettings.DecimalSeparator. Pilkkuerottimella AddPageMatrix(0.5, 0.5, 0, 0) kirjoitti rivin 0,5 0 0 0,5 0 0 cm. ISO 32000-1 §7.3.3 sallii luvussa numerot, yhden pisteen ja etumerkin eikä mitään muuta, joten sisältöjäsennin lukee kyseisen rivin lukuna 0, jota seuraa tuntematon token ,5, ja cm-operaattori päätyy väärien operandien varaan. Mikään ei nouse, mikään ei kirjaudu. Sivu renderöityy vain ajautuneella muunnosmatriisilla, ja väärin sijoittuneesta piirroksesta taannehtiminen alueasetukseen on kurja iltapäivä
Toinen vika piileskelee ensimmäisen takana. FloatToStr käyttää muotoa ffGeneral, joka vaihtaa eksponenttimuotoon heti, kun suuruusluokka putoaa arvon 1E-4 alle, joten pieni offset tuli ulos muodossa 1E-5. Sama §7.3.3 toteaa, ettei PDF tue eksponenttimuotoa, mikä tarkoittaa, että jo yhdysvaltalaislokaalin konekin saattoi kirjoittaa epäkelvon operandin, kun arvo oli tarpeeksi pieni. Tämän julkaisun regressiotestit lukitsevat molemmat vikamuodot: ne kääntävät erottimen pilkuksi, kutsuvat APIa ja käyvät tuloksena syntyvän sisällön läpi etsien jokaista pilkun tai eksponentin sisältävää tokenia
uses
System.SysUtils, PDFlibrary;
var
Lib: TPDFlib;
OldSeparator: Char;
begin
Lib := TPDFlib.Create;
try
Lib.SetPageDimensions(300, 200);
Lib.DrawBox(10, 10, 20, 20, 1);
OldSeparator := FormatSettings.DecimalSeparator;
try
FormatSettings.DecimalSeparator := ','; // simuloi de-DE-työpöytää
Lib.AddPageMatrix(0.5, 0.25, 1E-5, 12.75);
// v3.539.26 ja uudemmat kirjoittavat: 0.5 0 0 0.25 0.00001 12.75 cm
// vanhemmat versiot kirjoittivat: 0,5 0 0 0,25 1E-5 12,75 cm
Writeln(Lib.GetPageContentToString);
finally
FormatSettings.DecimalSeparator := OldSeparator;
end;
finally
Lib.Free;
end;
end;
Kahdenlaista lukua, kaksi apufunktioperhettä
Korjaus PDFlibPasissa on ankara jako: ihmisille näytettävät luvut saavat seurata lokaalia, koneelle kirjoitettavat eivät koskaan. PLFloatToStr ja PLStrToFloat jäävät PDFlibExtra.pasiin käyttäjälle näytettävää tekstiä varten, ja niiden esittelyssä on nyt kommentti, joka sanoo juuri tuon. Kaikki, mikä päätyy PDF-syntaksiksi, kulkee funktion PLDoubleToStrConst läpi työhön valitulla kiinteällä desimaalimäärällä: kuusi matriiseille, neljä koordinaateille ja TJ-säädöille, kolme väreille ja FDF-suorakulmioille. v3.539.26:n auditointi koski useampia kutsukohtia kuin alkuperäinen vikaraportti antoi ymmärtää:
AddPageMatrix,ScalePagejaDeskewPage, jotka kaikki liittävätcmin olemassa olevan sivusisällön alkuun- Sivuelementtien rakentajat, jotka emittoivat
Tm-nollauksia,TJ-siirtymiä jacm-muunnoksia - Glyyfien sijoittelumatriisit ja ääriviivapisteet text-to-path-muuntimessa
- Musta täyttölaatikko, jonka
RedactRegionliittää eteen, FDF-viennin/Rect-arvot ja operandit, joita värien muuntaminen kirjoittaa
PLDoubleToStrConst on käsin tehty muotoilija eikä kääre funktion FloatToStrF ympärillä, ja kolme sen ominaisuutta merkitsee tässä. Se kirjoittaa aina pisteen ja poistaa lopulliset nollat, joten 0.5 pysyy muodossa 0.5 eikä muutu muodoksi 0.500000. Se ei koskaan kirjoita eksponenttia äärelliselle syötteelle. Ja nollasta poikkeava arvo, joka on pienempi kuin pyydetty tarkkuus, säilyttää merkitsevät numeronsa romahtamatta nollaan, joten PLDoubleToStrConst(1E-9, 6) palauttaa arvon 0.000000001; vain noin 5E-16 alapuoliset arvot muuttuvat muodoksi 0. Tuo viimeinen sääntö on olemassa, koska pienen skaalauskertoimen pyöristäminen nollaksi kääntää kelvollisen matriisin singulaariseksi, mikä on pahempi vika kuin se, jota korjataan
uses
PDFlibExtra;
procedure CheckNumberHelpers;
var
V: Double;
begin
// Koneen tuloste: pistedesimaali, ei eksponenttia, lopulliset nollat poistettu
Assert(PLDoubleToStrConst(1E-5, 6) = '0.00001');
Assert(PLDoubleToStrConst(-0.5, 6) = '-0.5');
Assert(PLDoubleToStrConst(0.000012346, 4) = '0.00001235'); // säilyttää 4 merkitsevää numeroa
Assert(PLDoubleToStrConst(12345.25, 4) = '12345.25');
// Koneen syöte: pehmeä epäonnistuminen EConvertErrorin sijaan
Assert(PLTryStrToFloatInvariant('0.5', V) and (V = 0.5));
Assert(not PLTryStrToFloatInvariant('0,5', V)); // sisältöluvut eivät koskaan käytä pilkkua
end;
Miksi jäsentävä puoli on vaarallisempi kuin kirjoittava puoli?
Jäsentävä puoli on vaarallisempi, koska lokaaliin sidottu jäsennin ei tuota väärää lukua vaan heittää. PLStrToFloat kutsuu funktiota StrToFloat, joka nostaa virheen EConvertError, kun teksti ei vastaa järjestelmän erottinta. Pilkkudesimaalijärjestelmällä se tarkoitti, että RecolorPage keskeytyi heti, kun se kohtasi tavallisen operaattorin 0.5 g, joten jokainen oikean maailman sivu epäonnistui, eivät vain eksoottiset. RenderPageRegionToFile hylkäsi oman dokumentoidun clip-muotonsa "10.5,20.5,50.5,40.5", ja SVG-pituusattribuutit, SVG-viennin värit, huomautusten verteksilistat ja output intentin tiiveysarvot joko torjuttiin tai vaiennettiin oletuksiin. Kirjasto, joka toimii täydellisesti kehittäjän koneella ja epäonnistuu ensimmäisellä asiakkaalla Münchenissä, on täsmälleen sellaista koodia, joka kuten tapaukset kirjoituksessa Delphi-koodi, joka toimii vahingossa näyttää oikealta vain testaamispaikkansa vuoksi
v3.539.33 luokitteli jokaisen StrToFloat- ja TryStrToFloat-kutsun sen mukaan, mistä sen syöte tulee. Sisältöstreamin operandit, SVG-attribuutit, maalarin värimerkkijonot ja pilkuilla erotellut clip- ja verteksilistat noudattavat kaikkia kiinteää pistesyntaksia, joten ne kulkevat nyt funktion PLTryStrToFloatInvariant läpi, joka trimmaa tekstin, jäsentää sen arvoilla PLInvariantFormatSettings ja palauttaa arvon False tyhjälle, muodoltaan väärälle tai ei-äärelliselle syötteelle nostamatta. Pilkuilla eroteltu lista ei jätä tilaa kompromissille, sillä pilkku ei voi olla yhtaikaa sekä listan erotin että desimaalimerkki. Sama kirjoitusvaihe korjasi myös alueen ulkopuolisen kirjoituksen: RenderPageRegionToFile talletti aikoinaan viidennen clip-arvon nelialkiobufferinsa ohi. Värien muuntamisputkesta, jota kirjoitus PDF:n muuntaminen yhteen väriavaruuteen kuvaa, käytännön tulos on, etteivät RecolorPage ja RecolorDocument enää keskeydy pilkkudesimaalijärjestelmällä. Sääntöarvot, jotka kutsuja näppäilee CheckDocumentPolicyyn, ovat ainoa jäsentämistapaus, joka käyttää lempeää apuria, syystä, jonka seuraava luku selittää
Mitä tapahtuu, jos korjaa vain toisen pään round tripistä?
Vain toisen pään korjaaminen lokaalikiekurassa rikkoontuu koodia, joka toimi aiemmin, minkä vuoksi rakennetta-attribuutin muutos versiossa v3.539.32 siirsi kirjoittajan ja lukijan yhdessä. SetStructElem*-kääreet välittävät luvut merkkijonoina: SetStructElemBBox muotoilee neljä arvoa yhdeksi merkkijonoksi, tallettaa sen funktion AddTagAttribute kautta, ja /A-kirjoittaja jäsentää merkkijonon myöhemmin päättääkseen, muuttuuko se luvuksi, taulukoksi vai nimeksi. Molemmat päät käyttivät järjestelmän lokaalia, joten pilkkudesimaalijärjestelmällä kiekura oli itsensä kanssa sopusoinnussa. Vika ilmeni vain, kun kutsuja noudatti dokumentaatiota ja antoi arvon "0.5" funktiolle AddTagAttribute: lukija ei osannut jäsentää sitä ja emittoi PDF-nimen /0.5. PDF/VCR-paikanpitäjällä oli peilikuvaongelma, koska kirjasto generoi GTS_BBoxin pisteellä ja validoi sen sitten lokaalilla ennen tallennusta
Pelkän kirjoittajan vaihtaminen pisteeseen olisi ollut pahempaa kuin ei mitään, sillä jokainen SetStructElem*-arvo olisi sitten kaatunut lokaaliin sidotun lukijan läpi ja huononnut nimeksi. Niinpä kirjoittajat käyttävät nyt funktiota PLDoubleToStrConst(v, 6), ja lukija käyttää uutta apuria PLTryStrToFloatLenient, joka kokeilee pistemuotoa ensin ja putoaa järjestelmän lokaaliin. Pilkkulokaalin kutsuja, joka antoi aiemmin arvon "1,25", saa yhä luvun 1.25. Kompromissi on tahallinen ja dokumentoitu: saksalaisjärjestelmällä "1.500" muuttui aiemmin nimeksi, koska StrToFloat torjuu tuhaterottimet, ja se lukeutuu nyt arvoksi 1.5, kun taas litteraalit NAN ja INF eivät enää kelpaa luvuiksi
uses
System.SysUtils, PDFlibrary;
var
Lib: TPDFlib;
begin
FormatSettings.DecimalSeparator := ','; // pilkkudesimaalinen kutsuja
Lib := TPDFlib.Create;
try
Lib.BeginTag('Figure', 'Sales chart', '');
Lib.SetStructElemBBox(10.5, 20.25, 200.5, 100.75); // /BBox [ 10.5 20.25 200.5 100.75 ]
Lib.AddTagAttribute('Layout', 'SpaceAfter', '0.5'); // /SpaceAfter 0.5, aiemmin /0.5
Lib.AddTagAttribute('Layout', 'StartIndent', '1,25'); // yhä /StartIndent 1.25
Lib.DrawText(20, 20, 'figure');
Lib.EndTag;
Lib.SaveToFile('tagged.pdf');
finally
Lib.Free;
end;
end;
Missä NaN ja äärettömys pysäytetään
AddPageMatrix, ScalePage ja RedactRegion torjuvat nyt NaN- ja äärettömät argumentit heti alkuun ja palauttavat arvon 0, koska mikään PDF-luku ei voi esittää niitä. ScalePage torjui jo nollan tai pienemmät kertoimet, mutta NaN läpäisee testin <= 0, joten NaN-kerroin matkusti aikoinaan aina muotoilijalle asti. Versiossa v3.539.26 kyseinen muotoilija kutsui yhä funktiota Round NaN:lla, mikä nostaa virheen EInvalidOp Win32:lla, jossa x87-yksikkö ei peitä epäkelvoja operaatioita; v3.539.31 teki PLDoubleToStrConstista NaN:lle arvon 0 kirjoittavan viimeisen puolustuslinjan, mutta nolla matriisissa on singulaarinen muunnos, joten API-tason tarkistus on yhä varsinainen korjaus. Kaksi rajaa jätetään tarkoituksella paikoilleen. Metafilen tilamerkkijonot kirjoitetaan ja luetaan lokaalilla yhden prosessin sisällä, eivätkä ne koskaan poistu siitä, joten ne jätettiin rauhaan. Ja testi, joka muotoilee arvon 1E-5 sivuelementtipolun kautta, on luettava sisältö ennen kuin kerros kirjoitetaan uudelleen, koska operandien uudelleenemittointi asiakirjan tarkkuudella kääntää kyseisen arvon laillisesti muodoksi 0
Jos sovelluksesi lähtee asiakkaille pistedesimaalimaailman ulkopuolelle, turvallisin tapa on se, jota PDFlibPasin testisarja nyt käyttää: aja PDF:ää tuottavat polut kerran läpi asettaen FormatSettings.DecimalSeparator pilkuksi ja käy tuloste läpi pilkkujen ja eksponenttien varalta. Kirjoitus jäsennettyjen desimaalitarkkuuksien säilyttäminen kattaa saman tarinan toisen puolen, sen miten olemassa olevasta tiedostosta luetut luvut säilyttävät tarkan tekstinsä tallennuksessa. Lataukset, täysi API-referenssi ja kokeilubuildi ovat PDFlibPas Delphi PDF library -tuotesivulla