HotPDF vertailee kahta PDF-dokumenttia Delphistä käsin luokan THPDFDocComparison avulla, joka käy läpi molempien tiedostojen objektigraafin luettelosta ulospäin ja, pyydettäessä, myös renderöi jokaisen sivuparin ja mittaa pikselit, jotka eroavat. Tuloksena on JSON-raportti, joka nimeää jokaisen löydetyn eron, kulutetun budjetin ja sen, suoritettiinko vertailu loppuun. Molemmat vaiheet ovat tärkeitä, koska rakenteellinen diff ja visuaalinen diff vastaavat eri kysymyksiin
Ominaisuuden takana oleva kysymys on yleensä julkaisukysymys. Raporttimoottoriin tehdään muutos, tuloste luodaan uudelleen, ja jonkun on päätettävä, muuttuiko mikään. Molempien tiedostojen avaaminen rinnakkain toimii noin kolmeen sivuun asti, ennen kuin tarkkaavaisuus pettää. Raakojen tavujen vertailu epäonnistuu välittömästi, koska saman generaattorin kaksi ajoa tuottavat eri tavuja syistä, joilla ei ole mitään tekemistä sen kanssa, mitä lukija näkee
Miksi PDF-tiedostot voivat olla tavuiltaan erilaisia mutta visuaalisesti identtisiä?
Kaksi itsenäisesti tuotettua PDF-tiedostoa, jotka tulostuvat identtisesti, eroavat säännönmukaisesti tavuiltaan, ja syyt ovat rakenteellisia, ei kosmeettisia. Objektinumerot annetaan siinä järjestyksessä, jossa objektit sattuvat tulemaan kirjoitetuiksi. Fonttien osajoukot varaavat CID-tunnisteet siinä järjestyksessä, jossa glyyfit ensin kohdataan, joten hieman erilaisen läpikäynnin aikana rakennettu osajoukko tuottaa eri sisältövirran tavuja samalle näkyvälle tekstille. Ristiviittausten siirtymät muuttuvat aina, kun jokin ylävirrassa muuttaa pituuttaan
Tästä syystä objektinumeroita ei voida käyttää dokumenttien välisenä identiteettinä. Sen sijaan HotPDF rakentaa kunkin tilannekuvan käymällä läpi luettelosta lähtien, laajentaen sanakirjat avainten tavujärjestyksessä ja taulukot indeksin mukaan, joten jokainen objekti nimetään polulla, joka sen saavuttaa. Objektit, joita läpikäynti ei saavuta juuresta, palaavat synteettiseen $Unreachable[...]-polkuun, joka kantaa objektinumeron ja sukupolven, mikä pitää orvoksi jääneen sisällön näkyvänä raportissa sen sijaan, että se katoaisi jäljettömiin
Virtoja ei vertailla kopioimalla. Jokainen virta antaa kertyvän SHA-256-allekirjoituksen, joka lasketaan palauttaen samalla alkuperäisen virran sijainti jälkikäteen, joten kahden sadan megatavun tiedoston vertailu ei tarkoita kahdensadan megatavun materialisoimista kahteen kertaan
Sivujen kohdistaminen, kun toisessa dokumentissa on lisäys
Sivun 1 vertaaminen sivuun 1, sivun 2 vertaaminen sivuun 2 ja niin edelleen on oikein vain silloin, kun mitään ei ole lisätty. Lisää kansisivu, ja naiivi vertailu raportoi jokaisen sivun muuttuneeksi, mikä on teknisesti totta ja käytännössä hyödytöntä
HotPDF kohdistaa sivut ennen niiden vertailua. Se rakentaa allekirjoituksen sivua kohden poimittavasta tekstistä, palaa rakenteelliseen allekirjoitukseen sivuille, joilla ei ole tekstiä, ja laskee sitten pisimmän kasvavan osajonon täsmäytettyjen kohdeindeksien yli. Tämän osajonon sisällä olevat sivut ovat niitä, jotka vain siirtyivät; sen ulkopuolella olevat sivut ovat todellisia siirtoja. Tämä erottelu tekee 400-sivuisen käsikirjan diffistä luettavan, koska raportti kertoo yhden sivun lisätyn sen sijaan, että neljäsataa sivua olisi muuttunut
Rakenteellisen vertailun suorittaminen
Yksinkertaisin kutsu ottaa kaksi ladattua dokumenttia ja tilan. cmStructural suorittaa objektigraafin läpikäynnin, cmRenderedImage suorittaa pikselivertailun, cmFull tekee molemmat, ja kevyemmät tilat cmPageCount, cmPageText ja cmObjectCount ovat olemassa halpoja savutestejä varten:
uses
HPDFDoc, HPDFDocCompare;
var
DocA, DocB: THotPDF;
Report: AnsiString;
begin
DocA := THotPDF.Create(nil);
DocB := THotPDF.Create(nil);
try
if (DocA.LoadFromFile('baseline.pdf') <= 0) or
(DocB.LoadFromFile('candidate.pdf') <= 0) then
Exit;
Report := THPDFDocComparison.Compare(DocA, DocB, cmStructural);
with TFileStream.Create('diff.json', fmCreate) do
try
WriteBuffer(Report[1], Length(Report));
finally
Free;
end;
finally
DocB.Free;
DocA.Free;
end;
end;
Raportti erottaa kolme tilaa, joihin totuusarvo ei riitä. identical kertoo, erosiko mikään, comparisonComplete kertoo, saatiinko läpikäynti valmiiksi, ja comparisonBudget nimeää rajan, joka pysäytti sen, jos näin kävi. Vertailu, joka kuluttaa budjetin loppuun, raportoi yhdessä comparisonComplete=false ja identical=false, koska katkaistulla läpikäynnillä ei ole perustetta väittää yhtäläisyyttä. Mikä tahansa automaatio, joka lukee vain identical-kenttää, kohtelee lopulta budjettikatkoa todellisena erona, joten lue kaikki kolme
Mitkä rajat pitävät läpikäynnin rajoitettuna?
Oletusarvot luokassa THPDFStructuralCompareLimits.Default on mitoitettu todellisille dokumenteille, ei vihamielisille, ja jokaisella semanttisesti merkityksellisellä budjetilla on oma kattonsa: 250 000 objektia, 2 000 000 kaarta, syvyys 128, 10 000 raportoitua eroa, 64 Mt virtaa kohden ja 512 Mt virran tavuja yhteensä, 1 Mt arvoa kohden ja 4 096 tavua polkua kohden. Nosta niitä harkitusti, kun tunnet aineistosi, ja laske niitä vertaillessasi tiedostoja, jotka ovat saapuneet ulkopuolelta:
var
Limits: THPDFStructuralCompareLimits;
Options: THPDFRenderedCompareOptions;
begin
Limits := THPDFStructuralCompareLimits.Default;
Limits.MaxDifferences := 200; // epäonnistu nopeasti CI:ssä
Limits.MaxTotalStreamBytes := 128 * 1024 * 1024;
Options := THPDFRenderedCompareOptions.Default;
Options.DPI := 150; // oletus on 72
Options.ColorTolerance := 2; // jätä huomiotta 1-2 tason pyöristyskohina
Options.MinimumSimilarity := 0.9995;
Options.MaxChangedPixelRatio := 0.0005;
Options.GenerateHeatmaps := True; // kirjoita peittokuvat tarkastusta varten
Report := THPDFDocComparison.CompareWithOptions(DocA, DocB, cmFull,
Limits, Options);
end;
Renderöity vaihe arvioi pikselimäärän sivun mitoista ja pyydetystä DPI-arvosta ennen kuin mitään bittikarttaa varataan, ja tarkistaa todellisen bittikartan jälkikäteen uudelleen, joten virheellinen sivugeometria ei voi livahtaa budjetin ohi valehtelemalla kokonsa. DPI:n nostaminen nostaa tarkkuutta ja kustannusta neliöllisesti: 150 DPI on nelinkertainen määrä pikseleitä arvoon 72 verrattuna, ja sivukohtaiset ja kokonaispikselikatot ovat olemassa juuri siksi, että 300 DPI:n eräajo muutoin varaisi itsensä ongelmiin
Kuinka samanlainen on tarpeeksi samanlainen?
Kaksi sivua lasketaan samankaltaisiksi vain, kun molemmat ehdot täyttyvät: muuttuneiden pikselien suhde on enintään MaxChangedPixelRatio ja samankaltaisuus on vähintään MinimumSimilarity. Kaksi kynnysarvoa yhden sijaan, koska kourallinen katastrofaalisesti väärää pikseliä ja laaja hienoisten värimuutosten pesu ovat eri tyyppisiä virheitä, ja kumpikin yksinään voi olla hyväksyttävä yhdessä työnkulussa ja hylkäävä toisessa. Kynnysarvotestit käyttävät pyöristämättömiä arvoja; JSON:in kuusi desimaalia ovat olemassa pitämään raportit vakaina ja diffattavina, eivät määrittämään vertailua
Muuttuneet pikselit ryhmitellään alueiksi käyttäen kiinteänkokoisia laattoja solmuina neljäsuuntaisella vierekkäisyydellä, ei pikselikohtaisella täyttöalgoritmilla. Tämä pitää muistinkäytön rajoitettuna ja alueluettelon vakaana ajojen välillä. Säilytetyn aluetiedon katkaiseminen vaikuttaa vain listaukseen, ei raportoituun alueiden määrään, joten sivu, jolla on enemmän muuttuneita alueita kuin MaxChangedRegions, raportoi silti niiden todellisen määrän
Yksi käyttäytyminen kannattaa todeta suoraan, koska se kääntää tavanomaisen vaiston nurin. Renderöintivirheitä, varausvirheitä ja peittovirheitä ei koskaan niellä. Kaikenlainen tällainen kirjataan muodossa renderError tai renderBudget ja pakottaa arvon renderComparisonComplete=false, koska sivu, jota ei onnistuttu renderöimään, on sivu, jota kukaan ei vertaillut, ja sen raportoiminen identtisenä on pahempi vaihtoehto kuin ei minkään raportoiminen
Mihin kukin tila kuuluu putkessa
Rakenteellinen vertailu vastaa siihen, mikä muuttui, ja on oikea oletus regressiotestisarjoille: se nimeää mukana olevan polun, sivuindeksin ja objektinumerot, joten virhe osoittaa sen aiheuttaneeseen koodiin. Renderöity vertailu vastaa siihen, huomaako kukaan, mikä on kysymys hyväksynnöissä ja sen todentamisessa, oliko optimointivaihe todella häviötön
Ne täydentävät toisiaan hyvin. Aja cmStructural jokaisessa koonnoksessa ja anna sen epäonnistua äänekkäästi odottamattomien objektitason muutosten kohdalla; aja cmFull lämpökarttojen kanssa ennen julkaisua, kun ihminen on käytettävissä katsomaan peittokuvia. Putkille, jotka jo tuottavat sivumerkintää muista syistä, tekstituloste, joka kuvataan artikkelissa PDF-sivujen vienti SVG-muotoon, tarjoaa kolmannen, ihmisen diffattavan näkymän, ja artikkelin preflight-raportin automatisointi automaattiset tarkistukset kattavat vaatimustenmukaisuuskysymyksiä, joihin kumpikaan diff-tila ei ole tarkoitettu vastaamaan
Vertailu, preflight ja renderöinti jakavat saman ladatun dokumentin objektimallin, joten yksi ainoa läpikäynti tiedostosta voi syöttää kaikkia kolmea. Täydellinen ominaisuusluettelo Delphille ja C++Builderille on sivulla HotPDF Delphi PDF -komponentin sivulla