Ennen v3.539.30:ää losLab PDF Libraryn TPDFlib.ImportAnnotationsFromFDFString palautti jäsentämiensä FDF-huomautusmerkintöjen lukumäärän lisäämättä niistä yhtään asiakirjaan: jokaista merkintää laskettiin, jokainen merkintä pudotettiin. Versiosta v3.539.30 alkaen FDF-tuoja lukee avaimet missä tahansa järjestyksessä, jäsentää /Rectin oikein ja alueasetuksista riippumatta, ja vastaava viejä kirjoittaa huomautuksen oikean /Rectin, joten vienti, tuonti ja toinen vienti tuottavat tavutavulta saman FDF:n. Tämän muistiinpanon loppuosa selittää, miten yksi väärä aloitusoffset tuotti täydellisen hiljaisen vian, mitkä kolme muuta vikaa piileskelivät sen takana ja miten tarkistat tuonnin itse sen sijaan, että luottaisit paluuarvoon
Tilanne on tavallinen. Tarkastaja merkitsee sopimuksen, kommentit matkustavat FDF-tiedostona (Acrobat kutsuu toimintoa Export Comments), ja Delphi-palvelusi yhdistää ne puhtaaseen kopioon ImportAnnotationsFromFDFilla. Kutsu palauttaa luvun 7, loki sanoo "7 comments imported", työ menee vihreäksi, eikä tuloksena olevassa PDF:ssä ole yhtään kommenttia. Mitään ei nostettu, mikään ei varoittanut, ja luku vaikutti uskottavalta, koska se oli tiedoston merkintöjen todellinen määrä. Tuossa on bugin pahin muoto: funktio, jonka ainoa onnistumisen signaali on laskuri, joka lasketaan riippumatta siitä työstä, jonka raportoitavakseen se väittää
Miksi ImportAnnotationsFromFDFString raportoi onnistumisen mutta ei lisännyt mitään?
Tuoja luki jokaisen /Subtypein tyhjänä merkkijonona, ja huomautusta luova apufunktio keskeytyy heti tyhjään alityyppiin, kun taas kutsuja kasvatti tulosta silti. Avaimen etsijä palautti kohdan heti /Subtypein jälkeen, eli arvoa edeltävän välilyönnin. ReadName aloitti tuosta välilyönnistä ja pysähtyi ensimmäiseen välilyöntimerkkiin, joten se pysähtyi ennen kuin ehti lukea mitään. AddAnnotationToPage kieltäytyy rakentamasta huomautusta ilman alityyppiä, mikä on eristettynä oikea puolustava valinta, mutta se oli proceduuri ilman paluuarvoa, ja Inc(Result) istui sen ulkopuolella. Jokainen vartiointi oli yksin otettuna järkevä; yhdessä ne muunsivat "mikään ei toiminut" -tilan muotoon "kaikki toimi". Korjaus tekee ReadNamesta välilyönnit ohittavan, PDF-nimiobjektin alussa olevan /:n vaativan ja mihin tahansa erottimeen pysähtyvän, myös [-, (- ja )-merkkeihin, joten sekä /Subtype/Text että /Subtype /Text tuottavat arvon Text
Paluuarvo kaipasi huolellisuutta vielä tuon korjauksen jälkeenkin. Versioon v3.539.39 asti ImportAnnotationsFromFDFString kasvatti edelleen tuloslaskuriaan jokaisesta /Annots-taulukon hyvinmuodostetusta sanakirjasta, mukaanlukien merkinnät, joiden nollapohjainen /Page oli alueen ulkopuolella tai joiden /Subtype puuttui, ja molemmat ohitetaan. PDFlibPas v3.539.40:stä alkaen ImportAnnotationsFromFDFString ja ImportAnnotationsFromFDF palauttavat oikeasti lisättyjen huomautusten määrän kuten XFDF-tuonti: FDF-apufunktio AddAnnotationToPage palauttaa nyt Booleanin, ja laskuri liikkuu vain onnistuessa. Asiakirjan mittaaminen on yhä vahvempi tarkistus, koska se pätee myös vanhemmissa versioissa, joten alla oleva luonnos vertaa AnnotationCountia sivuittain ennen tuontia ja sen jälkeen
function TotalAnnotations(Lib: TPDFlib): Integer;
var
Page, Saved: Integer;
begin
Result := 0;
Saved := Lib.SelectedPage;
for Page := 1 to Lib.PageCount do
if Lib.SelectPage(Page) = 1 then
Inc(Result, Lib.AnnotationCount); // valittua sivua kohti, widgetit mukaan lukien
Lib.SelectPage(Saved);
end;
var
Lib: TPDFlib;
Before, Reported, Added: Integer;
begin
Lib := TPDFlib.Create;
try
Lib.LoadFromFile('contract.pdf', '');
Before := TotalAnnotations(Lib);
Reported := Lib.ImportAnnotationsFromFDF('review-comments.fdf');
Added := TotalAnnotations(Lib) - Before;
if Added <> Reported then // yhtä suuret v3.539.40:stä alkaen
Writeln(Format('Importer reported %d, %d landed on a page', [Reported, Added]));
Lib.SaveToFile('contract-reviewed.pdf');
finally
Lib.Free;
end;
end;
Kolme muuta vikaa ensimmäisen takana
Pelkkä alityypin korjaus olisi paljastanut samasta funktiosta kolme lisävikaa, jotka olivat olleet näkymättömiä vain siksi, ettei yksikään huomautus koskaan päässyt sivulle asti. Ensiksi, ReadNumber otti positionsa arvoparametrina, joten neljän /Rect-luvun lukeminen peräkkäin luki saman kohdan neljästi, eikä se ohittanut avaavaa [merkkiä, joten käytännössä se ei lukenut mitään. Toiseksi, FindKey jakoi yhden eteenpäin kulkevan kursorin kaikkien hakujen kesken. Viejä kirjoittaa avaimet järjestyksessä /Subtype, /Rect, /Page, /Contents, /T, /Subj, mutta tuoja haki järjestyksessä /Subtype, /Contents, /T, /Subj, /Page, /Rect; kun kursori oli kerran ohittanut /Contentsin, haku /Pagelle ja /Rectlle juoksi nykyisen merkinnän ohi ja joko ei löytänyt mitään tai täsmäytti seuraavan huomautuksen avaimiin. Kirjasto ei osannut lukea omaa tuotostaan. Kolmanneksi, luvut kulkivat PLStrToFloatin läpi, joka noudattaa järjestelmän desimaalierotinta. ISO 32000-1 §12.7.7 määrittelee FDF:n PDF-objektisyntaksiksi, ja PDF:n sanakirja-avaimet ovat järjestyksettömiä (§7.3.7), joten jokainen avainjärjestyksen olettava FDF-jäsennin on rakenteeltaan väärä, olipa tiedoston tuottanut työkalu mikä tahansa
Korjattu tuoja rajaa jokaisen merkinnän ensin. FindDictEnd kävelee avautuvasta <<sta sen paria >>ä, seuraten sisäkkäisiä sanakirjoja ja ohittaen litteraalimerkkijonojen rungot takakenoviirpakokeineen, joten kommentin, esimerkiksi (see section >> 4), sisällä oleva >> ei voi päättää merkintää liian aikaisin. Jokainen avainhaku alkaa sitten merkinnän omasta alusta ja on rajoitettu sen loppuun, mikä tekee avainjärjestyksestä yhdentekevän ja estää yhtä huomautusta lainaamasta toisen /Pagea. Avaintäsmäytys hyväksyy myös erottimen suoraan nimen jälkeen, sillä /Contents(Hi) on yhtä kelvollinen kuin /Contents (Hi), kun taas sanarajasääntö estää /Subjä täsmäämästä /Subtypein alkuun ja /T:tä täsmäämästä /Typeen. ReadNumber ottaa positionsa nyt var-parametrina, ohittaa välilyönnit ja [merkin ja jäsentää funktiolla PLTryStrToFloatInvariant, joka epäonnistuu pehmeästi muodoltaan väärässä tokenissa poikkeuksen sijaan. Jos jokin neljästä suorakulmion luvusta epäonnistuu, kaikki neljä palaavat nollaan sen sijaan, että syntyisi puoliksi luettu suorakulmio
Miksi FDF-round tripit siirsivät jokaisen huomautuksen omalla korkeudellaan?
Vanha viejä kirjoitti suorakulmion väärässä koordinaattimallissa. Huomautuksen /Rect on [llx lly urx ury] oletuskäyttäjätilassa (ISO 32000-1 §12.5.2, suorakulmiot määritelty kohdassa §7.9.5), ja FDF kantaa saman taulukon. ExportAnnotationsToFDFString kutsui kuitenkin funktiota GetAnnotRectEx, joka raporttaa Left-, Top-, Width- ja Height-arvot kirjaston piirustuskoordinaateissa, tilassa, jota SetOrigin ohjaa, ja sarjoi ne muotoon [L T L+W T+H]. Tuoja, kun se vihdoin toimi, kirjoitti ne neljä arvoa takaisin sellaisenaan PDF-suorakulmiona, joten yläreuna laskeutui sinne, minne vasemman alakulman olisi kuulunut, ja jokainen round trip siirsi huomautusta ylöspäin sen omalla korkeudella. Viejä kopioi nyt huomautuksen omat /Rect-luvut, kolmea desimaalia, pisteen erottimena, ilman eksponenttia, ja turvautuu laskettuun suorakulmioon vain, kun talletettu taulukko puuttuu tai siinä ei ole neljää lukua
Tämän lukituksen tekevä regressiotesti on kopioimisen arvoinen, koska se tekee assertiot asiakirjaan ja toiseen vientiin, ei tuojan paluuarvoon. Huomaa odotettu määrä 2: AddNoteAnnotation luo Text-huomautuksen ja sen Popupin, ja molemmat matkustavat. Testi ajaa myös viennin ja tuonnin pilkkua desimaalierottimena käyttäen, ja juuri siinä tämän tarinan toinen puoli asuu
var
Source, Target: TPDFlib;
FDF: AnsiString;
OldSep: Char;
begin
Source := TPDFlib.Create;
Target := TPDFlib.Create;
try
Source.NewPages(1); // nyt kaksi sivua
Source.SelectPage(2);
Source.AddNoteAnnotation(50.5, 60.25, 0, 80, 80, 120, 60,
'Reviewer', 'Check this', 0.25, 0.5, 0.75, 0);
Target.NewPages(1);
OldSep := FormatSettings.DecimalSeparator;
FormatSettings.DecimalSeparator := ','; // simuloi saksalaista tai ranskalaista työpöytää
try
FDF := Source.ExportAnnotationsToFDFString; // kirjoittaa yhä /Rect [50.5 ...
Target.ImportAnnotationsFromFDFString(FDF);
finally
FormatSettings.DecimalSeparator := OldSep;
end;
Target.SelectPage(2);
Assert(Target.AnnotationCount = 2); // muistiinpano ja sen popup
Assert(Target.GetAnnotType(1) = 'Text');
Assert(Target.ExportAnnotationsToFDFString = Source.ExportAnnotationsToFDFString);
finally
Target.Free;
Source.Free;
end;
end;
Ole selvillä siitä, mitä FDF-polku kantaa. Tuoja rakentaa jokaisen merkinnän uudelleen sanakirjaksi avaimilla /Type, /Subtype, /Rect, /Contents, /T ja /Subj; väri, liput, reunatyyli, popup-linkit ja ulkoasustreamit eivät kuulu tälle reitille, ja viejä ohittaa Widget-huomautukset, koska lomakekentät kuuluvat lomakedatametodeille. Laajempi kartta siitä, mikä data matkustaa minkin metodin kautta, on kirjoituksen FDF-, XFDF- ja XFA-lomakedatan vaihto yleiskatsauksessa, ja jos sinun on tarkistettava, mitä oikeasti saapui, indeksikohtaiset lukijat kuten GetAnnotType, GetAnnotTitle ja GetAnnotContentsEx on käsitelty kirjoituksessa kirjanmerkkien, huomautusten ja actionien introspektio
Miten luet vanhojen vientien pilkkudesimaaliset FDF- ja XFDF-tiedostot?
FDF:n osalta vastaus on yksiselitteinen: pilkku ei ole erottin PDF-syntaksissa, joten lukutoken, jossa on täsmälleen yksi pilkku eikä yhtään pistettä, voi olla vain pilkkulokaalilla koneella kirjoitettu desimaali. Vanhemmat versiot kirjoittivat kyllä tällaisia tiedostoja, esimerkiksi /Rect [10,500 20,250 40,750 60,125], ja uusi ReadNumber muuttaa kyseisen yksittäisen pilkun pisteeksi ennen jäsentämistä. Kahta pilkkua tai pilkkua ja pistettä sisältävä token hylätään arvaamisen sijaan. Lukija ei niele eksponenttimuotoakaan, mikä vastaa ISO 32000-1 §7.3.3:aa: PDF-luvut eivät koskaan käytä sitä
XFDF on hankalampi, koska XML-attribuuteissa pilkku on erottin. Standardi XFDF (ISO 19444-1) kirjoittaa muodot rect="50.5,80.25,70.75,100.125" ja dashes="4,2", kun taas v3.539.28 ja sitä aiemmat kirjoittivat pilkkulokaalisella järjestelmällä muodot rect="50,500 80,250 70,750 100,125" ja opacity="0,600", ja epäonnistuivat lisäksi virheeseen EConvertError lukiessaan standardia opacity="0.6". Versiosta v3.539.29 alkaen molemmat suunnat ovat invariantteja, ja legacy-muodon tunnistaa funktio XFDFNormalizeLegacyDecimals vain silloin, kun attribuutti jakautuu välilyönneistä täsmälleen odotettuun tokenimäärään (neljä rectille, yksi opacitylle ja widthille) ja jokainen token on muotoa numerot-pilkku-numerot. Standardi rect ei koskaan täsmää: se on joko yksi token, jossa on kolme pilkkua, tai tokeneita, jotka päättyvät pilkkuun. dashes jätetään tahallaan rauhaan, koska 4,2 voi olla kaksi viivan pituutta tai legacy-muotoinen 4.2, eikä mikään sääntö erota niitä toisistaan
const
// Avaimia poikkeavassa vientijärjestyksessä, plus pilkkudesimaalit vanhemmasta pilkkulokaalin vientistä
LegacyFDF: AnsiString = '%FDF-1.2'#10'1 0 obj'#10'<< /FDF << /Annots ['#10 +
'<< /Rect [10,500 20,250 40,750 60,125] /Page 0 /Contents (First) ' +
'/Subtype /Text /T (Alpha) /Type /Annot >>'#10 +
'] >> >>'#10'endobj'#10'trailer'#10'<< /Root 1 0 R >>'#10'%%EOF'#10;
var
Lib: TPDFlib;
begin
Lib := TPDFlib.Create; // tuore asiakirja sisältää yhden sivun
try
Lib.ImportAnnotationsFromFDFString(LegacyFDF);
Assert(Lib.AnnotationCount = 1);
Assert(Lib.GetAnnotTitle(1) = 'Alpha');
// Uudelleenviety XFDF:nä pistedesimaaleilla: rect="10.500 20.250 40.750 60.125"
Writeln(Lib.ExportAnnotationsToXFDFString);
finally
Lib.Free;
end;
end;
Mitä huomautustuonnin testin oikeasti pitäisi assertoida?
Hyödyllinen tuontitesti assertoi kohdeasiakirjan tilaan, ei koskaan vain siihen, mitä tuoja sanoo itsestään. Testisarja ei tarkistanut AnnotationCountia FDF-tuonnin jälkeen, ja paluuarvo, ainoa luku, jota kukaan katsoi, oli ainoa luku, jonka vika jätti ehjäksi. Kolme assertiota olisi napannut jokaisen tässä kuvatun vian: huomautusten määrän odotetulta sivulta, yhden kentän luettuna takaisin funktiolla GetAnnotType tai GetAnnotContentsEx sekä toisen viennin verrattuna tavu tavulta ensimmäiseen. Sama kuri pätee mihin tahansa APIin, joka kirjoittaa asiakirjan rakennetta isoissa erissä, mukaan lukien kohdassa kaksoiskappalelomakekenttien yhdistäminen kuvattu kenttien konsolidointi: tarkista syntyvä puu, ei palautettua summaa. FDF- ja XFDF-huomautusmetodit tiedosto- ja merkkijonovarianssineen toimitetaan mukana tuotteessa losLab PDF Library for Delphi and C++Builder, ja v3.539.30 tai uudempi on versio, jota kannattaa ajaa, jos kommenttien on jäätävä eloon matkalla, ja v3.539.40 tai uudempi, jos palautetun määrän on vastattava lisättyä