Tehnični članak

Primerjava dveh datotek PDF v Delphiju: struktura in slikovne pike

HotPDF v Delphiju primerja dva dokumenta PDF prek THPDFDocComparison, ki prehodi graf objektov obeh datotek od kataloga navzven in, kadar je zahtevano, tudi upodobi vsak par strani ter izmeri slikovne pike, ki se razlikujejo. Rezultat je poročilo JSON, ki poimenuje vsako najdeno razliko, porabljeni proračun in ali se je primerjava izvedla do konca. Oba prehoda sta pomembna, ker strukturna in vizualna primerjava odgovarjata na različni vprašanji

Vprašanje, ki stoji za to funkcijo, je običajno vprašanje izdaje. Mehanizem za poročila dobi spremembo, izhod se znova ustvari, nekdo pa mora presoditi, ali se je karkoli premaknilo. Odpiranje obeh datotek druge ob drugi se obnese do približno treh strani, preden pozornost popusti. Primerjava surovih bajtov spodleti takoj, saj dva teka istega generatorja dasta različne bajte iz razlogov, ki nimajo nobene zveze s tem, kaj bralec vidi

Zakaj so lahko datoteke PDF bajtno različne, a vizualno enake?

Dva neodvisno ustvarjena PDF-ja, ki se natisneta enako, se v bajtih redno razlikujeta, razlogi za to pa so strukturni in ne kozmetični. Številke objektov so dodeljene v vrstnem redu, v katerem so objekti pač zapisani. Podnabori pisav dodeljujejo CID-je v vrstnem redu, v katerem so znaki prvič naleteni, zato podnabor, zgrajen med rahlo drugačnim prehodom, da drugačne bajte vsebinskega toka za isto vidno besedilo. Odmiki v tabeli navzkrižnih sklicev se premaknejo, kadarkoli se kje pred njimi spremeni dolžina

Zato številk objektov ni mogoče uporabiti kot identiteto med dokumenti. HotPDF namesto tega vsak posnetek zgradi tako, da prehodi od kataloga navzven, slovarje razširi po bajtnem vrstnem redu njihovih ključev, polja pa po indeksu, tako da je vsak objekt poimenovan po poti, ki do njega vodi. Objekti, do katerih prehod od korena ne more priti, se prevežejo na sintetično pot $Unreachable[...], ki nosi številko objekta in generacijo, kar osiroteno vsebino ohrani vidno v poročilu, namesto da tiho izgine

Tokovi se ne primerjajo s kopiranjem. Vsak tok prispeva postopen podpis SHA-256, izračunan tako, da se pozneje obnovi izvirni položaj v toku, zato primerjava dveh dvestomegabajtnih datotek ne pomeni materializacije dvesto megabajtov dvakrat

Poravnava strani, kadar ima en dokument vstavek

Primerjava strani 1 s stranjo 1, strani 2 s stranjo 2 in tako naprej je pravilna samo, kadar ni bilo nič vstavljeno. Vstavite naslovnico in naivna primerjava javi, da se je spremenila vsaka stran, kar je tehnično res, operativno pa neuporabno

HotPDF strani poravna, preden jih primerja. Za vsako stran zgradi podpis iz izvlečljivega besedila, za strani brez besedila pa se prevesi na strukturni podpis, nato pa izračuna najdaljše naraščajoče podzaporedje nad ujemajočimi se ciljnimi indeksi. Strani znotraj tega podzaporedja so tiste, ki so se le premaknile; strani izven njega so prave spremembe. Prav to razlikovanje naredi primerjavo 400-stranskega priročnika berljivo, saj poročilo pove, da je bila vstavljena ena stran, namesto da se je spremenilo štiristo strani

Izvajanje strukturne primerjave

Najpreprostejši klic sprejme dva naložena dokumenta in način. cmStructural izvede prehod grafa objektov, cmRenderedImage izvede primerjavo slikovnih pik, cmFull naredi oboje, lažji načini cmPageCount, cmPageText in cmObjectCount pa obstajajo za poceni osnovne preverbe:

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;

Poročilo razlikuje tri stanja, ki jih logična vrednost ne more. identical pove, ali se je karkoli razlikovalo, comparisonComplete pove, ali se je prehod zaključil, comparisonBudget pa poimenuje omejitev, ki ga je ustavila, če ga je katera. Primerjava, ki izčrpa proračun, hkrati javi comparisonComplete=false in identical=false, ker prekinjen prehod nima nobene podlage za trditev o enakosti. Vsaka avtomatizacija, ki bere samo identical, bo sčasoma zaustavitev zaradi proračuna obravnavala kot pravo razliko, zato berite vse tri

Katere omejitve prehod držijo v mejah?

Privzete vrednosti v THPDFStructuralCompareLimits.Default so dimenzionirane za prave dokumente in ne za sovražne, vsak pomensko relevanten proračun pa ima svojo lastno zgornjo mejo: 250.000 objektov, 2.000.000 povezav, globina 128, 10.000 javljenih razlik, 64 MB na tok in skupno 512 MB bajtov tokov, 1 MB na vrednost in 4.096 bajtov na pot. Zvišajte jih namensko, kadar poznate svoj korpus, in znižajte, kadar primerjate datoteke, ki so prišle od zunaj:

var
  Limits: THPDFStructuralCompareLimits;
  Options: THPDFRenderedCompareOptions;
begin
  Limits := THPDFStructuralCompareLimits.Default;
  Limits.MaxDifferences := 200;        // hiter neuspeh v CI
  Limits.MaxTotalStreamBytes := 128 * 1024 * 1024;

  Options := THPDFRenderedCompareOptions.Default;
  Options.DPI := 150;                  // privzeto je 72
  Options.ColorTolerance := 2;         // prezri šum zaokroževanja na ravni 1-2
  Options.MinimumSimilarity := 0.9995;
  Options.MaxChangedPixelRatio := 0.0005;
  Options.GenerateHeatmaps := True;    // zapiši prekrivne slike za pregled

  Report := THPDFDocComparison.CompareWithOptions(DocA, DocB, cmFull,
    Limits, Options);
end;

Upodobitveni prehod pred dodelitvijo katere koli bitne slike oceni število slikovnih pik iz dimenzij strani in zahtevane ločljivosti DPI, nato pa pozneje ponovno preveri dejansko bitno sliko, tako da popačena geometrija strani ne more zaobiti proračuna z lažno navedbo svoje velikosti. Zvišanje DPI kvadratno zvišuje zvestobo in ceno: 150 DPI je štirikrat toliko slikovnih pik kot 72, zgornje meje na stran in skupaj pa obstajajo prav zato, ker bi si sicer paketno opravilo pri 300 DPI z dodeljevanjem samo prikorakalo v težave

Kako podobno je dovolj podobno?

Dve strani veljata za podobni samo, kadar sta izpolnjena oba pogoja: delež spremenjenih slikovnih pik je enak ali pod MaxChangedPixelRatio, podobnost pa je enaka ali nad MinimumSimilarity. Dva praga namesto enega, ker je peščica katastrofalno napačnih slikovnih pik nekaj drugega kot širok nanos drobnih barvnih premikov, in vsak od njiju je lahko v enem poteku dela sprejemljiv, v drugem pa diskvalifikacijski. Preizkusi pragov uporabljajo nezaokrožene vrednosti; šest decimalnih mest v JSON obstaja zato, da so poročila stabilna in primerljiva, ne da bi definirala primerjavo

Spremenjene slikovne pike so razvrščene v območja z uporabo ploščic fiksne velikosti kot vozlišč s štirismerno sosednostjo, in ne z zapolnjevanjem posamezne slikovne pike za slikovno piko. To pomni v mejah, seznam območij pa ostaja stabilen med teki. Skrajšanje ohranjenih podrobnosti območij vpliva samo na seznam, ne pa na javljeno število območij, zato stran z več spremenjenimi območji, kot jih dovoljuje MaxChangedRegions, še vedno javi, koliko jih je bilo

Eno vedenje je vredno jasno povedati, ker obrne običajen instinkt. Napake upodabljalnika, napake dodeljevanja in napake prekrivanja nikoli niso pogoltnjene. Vse takšno je zabeleženo kot renderError ali renderBudget in vsili renderComparisonComplete=false, ker je stran, ki se ni uspela upodobiti, stran, ki je nihče ni primerjal, javljanje, da je enaka, pa je slabše kot javljanje ničesar

Kam v cevovodu sodi posamezen način

Strukturna primerjava odgovori, kaj se je spremenilo, in je pravilna privzeta izbira za regresijske pakete: poimenuje pot, indeks strani in vpletene številke objektov, tako da neuspeh kaže na kodo, ki ga je povzročila. Upodobljena primerjava odgovori, ali bo kdo to opazil, kar je vprašanje za odobritve in za preverjanje, da je bil optimizacijski prehod res brez izgub

Dobro se dopolnjujeta. cmStructural poganjajte pri vsaki izgradnji in naj glasno spodleti ob nepričakovanih spremembah na ravni objektov; cmFull s toplotnimi zemljevidi poganjajte pred izdajo, ko je na voljo človek, ki si lahko ogleda prekrivanja. Za cevovode, ki iz drugih razlogov že izpisujejo oznako strani, besedilni izhod, opisan v izvozu PDF-strani v SVG, ponuja tretji, človeku berljiv pogled, samodejne preverbe v avtomatizaciji poročil pred tiskom pa pokrivajo vprašanja skladnosti, na katera noben od načinov primerjave ni namenjen odgovoru

Primerjava, preverjanje pred tiskom in upodabljanje si delijo isti objektni model naloženega dokumenta, zato lahko en sam prehod čez datoteko napaja vse tri. Celoten seznam funkcij za Delphi in C++Builder je na strani komponente HotPDF PDF za Delphi