Tehnični članak

Validacija stisnjenih PDF-jev: Objektni in XRef tokovi

Napišete majhen validator. Odpre PDF datoteko, preskoči na konec, najde element startxref, prebere odmik in pričakuje, da bo pristal na ključni besedi xref z nadzorovano tabelo navzkrižnih referenc fiksne širine pod njo. Iz te tabele zbere odmike objektov, nato pa išče nazaj ključno besedo trailer, da ugotovi /Root in /Size. Popolnoma pravilno deluje za vsako datoteko, ki ste jo ustvarili za preverjanje. Nato pa prispe datoteka, ki jo je ustvarila novejša različica Worda ali knjižnica, ki podpira PDF 1.5, in validator jo označi kot neveljavno. Tam ni ključne besede xref, kamor kaže odmik, nikjer ni slovarja trailer in tabela objektov, ki jo je validator zgradil, je skoraj prazna. Datoteka je veljavna. Validator jo bere skozi petnajst let staro lečo

To je najpogostejši razlog, zakaj preverjanje na ravni bajtov, napisano za klasično strukturo, ne uspe pri sodobnih dokumentih. Struktura, na katero se zanaša, tabela navzkrižnih referenc v navadnem besedilu in ključna beseda trailer, je v PDF 1.5 postala neobvezna in pogosto manjka. Zamenjali sta jo dve funkciji: tok navzkrižnih referenc in stisnjen objektni tok. Obe sta opisani v ISO 32000-1 in validator, ki ne ve za njiju, vidi zdravo datoteko kot kup manjkajočih objektov

Kaj je v PDF 1.5 spremenilo rep datoteke

ISO 32000-1 §7.5.8 definira tok navzkrižnih referenc, §7.5.7 pa definira objektni tok vrste /ObjStm. Skupaj omogočata, da pisec izpusti dve strukturi, na kateri se zanaša klasični razčlenjevalnik. Datoteka PDF 1.5 se lahko konča brez tabele xref. Namesto nje je objekt, na katerega kaže startxref, običajen objektni tok, katerega slovar vsebuje /Type /XRef, ta tok pa hrani podatke o navzkrižnih referencah v kompaktni binarni obliki. Prav tako ni ključne besede trailer, saj je priklopnik zdaj sam slovar toka. Ključi, ki jih je iskal klasični razčlenjevalnik, /Root, /Size in /ID, živijo znotraj tega slovarja

Druga sprememba premakne same objekte. Namesto, da bi pisal vsak posredni objekt na svojem bajtnem odmiku, lahko pisec združi več majhnih objektov, slovarje strani, slovarje anotacij, drevo strukture v en sam objektni tok in celoten vsebnik stisne z algoritmom Flate. Posamezni objekti tako nimajo več bajtnega odmika v datoteki. Imajo pozicijo znotraj stisnjenega bloka. Validator, ki pregleduje surove bajte za 1 0 obj, jih ne bo nikoli našel, ker to besedilo obstaja šele po razširitvi. Za klasični razčlenjevalnik je polovica dokumenta preprosto izginila

Ključi priklopnika so navadno besedilo, tudi v stisnjeni datoteki

Pomirjujoče je, da branje priklopnika toka navzkrižnih referenc ne zahteva razširjanja ničesar. Objektni tok je zapisan kot slovar, ki mu sledi ključna beseda stream in nato stisnjeni bajti. Slovar je v navadnem besedilu. Zato, ko startxref kaže na tok navzkrižnih referenc, so bajti neposredno za številko objekta videti kot navaden slovar, ključi /Root, /Size in /ID pa so tam jasno vidni, preden se začnejo ključna beseda stream in Flate podatki

To pomeni, da lahko validator izve tri najpomembnejše podatke – kje je katalog, koliko objektov zatrjuje datoteka in identifikator datoteke – zgolj z razčlenjevanjem slovarja toka. Ni mu treba dekompresirati podatkov navzkrižnih referenc in ni mu treba interpretirati binarnih vnosov znotraj njih. Delo, ki premaga naiven razčlenjevalnik, ni branje priklopnika; to je iskanje objektov. To sta dva ločljiva problema in reševanje prvega je poceni

Objektni tokovi: glava, nato Flate blok

Objektni tok je vsebnik. Njegov slovar vsebuje /Type /ObjStm, vnos /N, ki podaja število znotraj shranjenih objektov, in vnos /First, ki določa bajtni odmik, znotraj razširjenih podatkov, kjer se začne telo prvega objekta. Stisnjeni tovor se po razširitvi začne z majhno glavo /N celoštevilskih parov. Vsak par je številka objekta in odmik telesa tega objekta glede na /First. Za glavo sledijo telesa samih objektov, združena zaporedno

Razširjanje je mehansko, ko so bajti enkrat razširjeni. Preberete slovar, da dobite /N in /First, razširite tok s Flate dekodirnikom, se sprehodite po začetnih parih /N, da izveste, katera številka objekta živi pri katerem odmiku, in nato povlečete vsako telo posebej, kot da bi šlo za običajen posredni objekt. Edina prava odvisnost je Flate dekodirnik, in tega že imate: Delphi vključuje System.ZLib, Free Pascal pa enoto zstream, ki oba ovijata zlib in razširita surov Flate tok brez kode tretjih oseb. Rutina, ki vsak izvlečen objekt doda v tabelo objektov validatorja, omogoči, da se preostanek validatorja, del, ki prehaja po /Root in preverja drevo strani, obnaša natanko tako kot bi se na klasični datoteki

Česa vam ni treba implementirati

Zlahka je preceniti količino dela. Branje ključev priklopnika iz stisnjene datoteke ne zahteva dekodiranja binarnih vnosov toka navzkrižnih referenc. Tok navzkrižnih referenc po §7.5.8 uporablja tri vrste vnosov in vnos vrste 2, tisti, ki pravi ta objekt se nahaja znotraj objektnega toka N na indeksu i, je tisto, kar bi morali dekodirati, da bi zgradili popolno mapo odmikov. To mapo potrebujete za reševanje poljubnih objektov po številki. Ne potrebujete je za branje /Root, /Size in /ID, ki so v slovarju v navadnem besedilu, prav tako ne za razširitev objektnih tokov, saj vsak /ObjStm razkrije svojo lastno vsebino prek /N in /First

Prav tako vam ni treba obravnavati funkcij napovednika (predictor functions) za PNG in TIFF, ki jih tok navzkrižnih referenc lahko uporabi prek parametra /DecodeParms samo zato, da pridobite ključe priklopnika. Napovedniki filtrirajo binarne vrstice navzkrižnih referenc, da se bolje stisnejo; nimajo nobene zveze s slovarjem, ki predhaja tok. Minimalna nadgradnja, ki klasični validator naredi združljivega s sodobnim PDF, je torej majhna: ko startxref pristane na toku namesto na ključni besedi xref, razčlenite slovar toka za ključe priklopnika in razširite vse objekte /ObjStm, na katere naletite, tako da njihova vsebina vstopi v tabelo objektov. Dekodiranje vnosov vrste 2 in napovednikov je ločena, večja naloga, ki jo lahko odložite, dokler dejansko ne potrebujete naključnega reševanja objektov

Zakaj mora preverjanje skladnosti najprej razširiti tokove

To preneha biti akademsko v trenutku, ko zaženete preverjanje profila. Validator PDF/A ali PDF/X pregleduje specifične objekte: katalog dokumenta za polje /OutputIntents, tok /Metadata za XMP paket s pravim identifikatorjem, vsak opisnik pisave za vgrajeno datoteko pisave, priklopnik za /ID. V stisnjeni datoteki je večina teh objektov znotraj objektnih tokov. Validator, ki ni razširil objektnih tokov, ne more videti ključev kataloga, ne more najti metapodatkov in ne more oštevilčiti pisav. Popolnoma skladni dokument bo prijavil kot takšnega, ki mu manjka izhodni namen, manjka XMP in manjka polovica strukture, ker dokazi, ki jih potrebuje, še vedno sedijo v Flate bloku, ki ga nikoli ni razširil

Vrstni red je pomemben. Razširitev se mora zgoditi pred izvajanjem preverjanj, ne vzporedno z njimi, ker vsako preverjanje predpostavlja, da lahko doseže objekt po številki. Če preverjanje profila povežete neposredno s pregledom surovih bajtov, podeduje slepoto klasičnega razčlenjevalnika in povzroči lažne kršitve natanko pri tistih sodobnih datotekah, ki so najverjetneje dobro oblikovane, saj so prišle iz orodij, ki so dovolj nova, da sploh zapisujejo tokove navzkrižnih referenc

Prepustite razčlenjevanje PDFiumu

Komponenta PDFium Component analizira tokove navzkrižnih referenc in objektne tokove kot del nalaganja dokumenta, kar je praktičen način, da se izognete ročnemu kodiranju koraka razširitve in dekompresije. Ko naložite datoteko s komponento TPdf, so objekti, zapakirani v vsebnike /ObjStm, že razrešeni in vstopne točke za validacijo vidijo popolnoma razširjen dokument. ValidatePdfA vrne zapis TPdfAValidationResult, katerega polje Conformance je vrednost TPdfAConformance, na primer pac1b ali pacNone, katerega polje Issues je nabor specifičnih najdenih težav in katerega metoda IsCompliant je resnična samo takrat, ko je bila zaznana raven skladnosti in je nabor težav prazen. Ker so bili objekti razširjeni med nalaganjem, je polje /OutputIntents ali vgrajena pisava, ki je živela znotraj objektnega toka, najdena in ni prijavljena kot manjkajoča

uses
  PDFium, FPdfPdfa;

function CheckPdfA(const FileName: string): TPdfAValidationResult;
var
  Pdf: TPdf;
begin
  Pdf := TPdf.Create(nil);
  try
    Pdf.FileName := FileName;
    Pdf.Active := True;            // razčleni xref/objektne tokove ob nalaganju
    Result := Pdf.ValidatePdfA;    // vidi razširjeno tabelo objektov
  finally
    Pdf.Free;
  end;
end;

Enako velja za ValidatePdfX, ki vrne TPdfXValidationResult v enaki obliki. Bistvo usmerjanja prek PDFiuma je, da se strukturna dekompresija, opisana zgoraj, zgodi enkrat, pravilno, znotraj nalagalnika, tako da vaša koda za validacijo nikoli ne vidi razlike med klasično datoteko in popolnoma stisnjeno. Obe prideta v validator kot razrešen nabor objektov

function PdfXConformanceName(C: TPdfXConformance): string;
begin
  case C of
    pxc1a: Result := 'PDF/X-1a';
    pxc3 : Result := 'PDF/X-3';
    pxc4 : Result := 'PDF/X-4';
  else
    Result := 'nobena';
  end;
end;

var
  Pdf: TPdf;
  R  : TPdfXValidationResult;
  Issue: TPdfXValidationIssue;
  IssueCount: Integer;
begin
  Pdf := TPdf.Create(nil);
  try
    Pdf.FileName := 'Press_Ready.pdf';
    Pdf.Active := True;
    R := Pdf.ValidatePdfX;
    if R.IsCompliant then
      Writeln('Skladnost PDF/X: ', PdfXConformanceName(R.Conformance))
    else
    begin
      IssueCount := 0;
      for Issue in R.Issues do   // Issues je nabor: preštejmo njegove člane
        Inc(IssueCount);
      Writeln('Ni skladno; število težav = ', IssueCount);
    end;
  finally
    Pdf.Free;
  end;
end;

Če so bajti že v pomnilniku namesto na disku, isto zaporedje "naloži nato preveri" deluje prek preobremenitve LoadDocument(const Data: TBytes), ki vzame surovo vsebino datoteke in razčleni njene tokove navzkrižnih referenc in objektne tokove na enak način kot datotečna pot. Ugotovitev za ročno napisan validator je strukturno pravilo, ne API: preberite ključe priklopnika iz slovarja toka v navadnem besedilu, razširite vsak /ObjStm s Flate dekodirnikom, preden prehodite dokument, in obravnavajte dekodiranje binarnih vnosov navzkrižnih referenc kot večjo, izbirno nalogo, kar tudi je

Ko je struktura razširjena, lahko validator poganja preostanek delovnega toka nad njo. Za ogrodje preflight za ukazno vrstico, ki poroča o skladnosti po mapi vhodov, glejte naš vodnik o gradnji paketa za preflight poročilo v ukazni vrstici. Ko je validacija vrata pred razdelitvijo velikega dokumenta, se tehnike v našem vodniku za razdelitev PDF dokumentov v več datotek naravno združujejo z vzorcem nalaganja in preverjanja, prikazanim tukaj. Oboje gradi na površini za nalaganje in validacijo PDFium Component za Delphi in C++Builder