Kirjoitat pienen validaattorin. Se avaa PDF-tiedoston, etsii loppua, löytää startxref-merkinnän, lukee siirtymän ja odottaa laskeutuvansa avainsanaan xref kiinteälevyisen ristiinviittaustaulukon ollessa sen alla. Tästä taulukosta se kerää objektien siirtymät ja skannaa sitten taaksepäin etsien trailer-avainsanaa oppiakseen /Root- ja /Size-tiedot. Se toimii täydellisesti jokaisella tiedostolla, jonka loit testataksesi sitä. Sitten saapuu Wordin nykyisen version tai PDF 1.5 -standardia tavoittelevan kirjaston luoma tiedosto, ja validaattorisi ilmoittaa sen olevan rikki. Siellä ei ole xref-avainsanaa siinä kohdassa, johon siirtymä osoittaa, ei trailer-sanakirjaa missään, ja validaattorisi rakentama objektitaulukko on lähes tyhjä. Tiedosto on kelvollinen. Validaattorisi lukee sitä viisitoista vuotta vanhan linssin läpi.
Tämä on yleisin syy siihen, miksi klassisen asettelun mukaan kirjoitettu tavutason PDF-tarkistus epäonnistuu nykyaikaisissa dokumenteissa. Rakenne, josta se riippuu – selväkielinen ristiinviittaustaulukko ja trailer-avainsana – tehtiin valinnaiseksi PDF 1.5 -versiossa ja se puuttuu usein. Kaksi ominaisuutta korvasi sen: ristiinviitysvirta ja pakattu objektivirta. Molemmat kuvataan standardissa ISO 32000-1, ja validaattori, joka ei tiedä niistä, näkee terveen tiedoston kasana puuttuvia objekteja.
Mitä PDF 1.5 muutti tiedoston lopussa
ISO 32000-1 §7.5.8 määrittelee ristiinviitysvirran ja §7.5.7 määrittelee tyyppiä /ObjStm olevan objektivirran. Yhdessä ne antavat kirjoittimen jättää pois ne kaksi rakennetta, joita perinteinen jäsentäjä etsii. PDF 1.5 -tiedosto voi päättyä ilman laillista xref-taulukkoa. Sen tilalla objekti, johon startxref osoittaa, on tavallinen virtaobjekti, jonka sanakirja kantaa tyyppiä /Type /XRef, ja tämä virta pitää ristiinviittaustiedot kompaktissa binäärimuodossa. Myöskään trailer-avainsanaa ei ole, koska trailer on nyt virran oma sanakirja. Avaimet, joita klassinen jäsentäjä metsästi, eli /Root, /Size ja /ID, asuvat tämän sanakirjan sisällä.
Toinen muutos siirtää itse objektit. Sen sijaan, että jokainen epäsuora objekti kirjoitettaisiin omassa tavusiirtymässään tiedostoon, kirjoittaja voi pakata monta pientä objektia – sivusanakirjat, annotointisanakirjat, rakenne-puun – yhdeksi objektivirraksi ja pakata koko säiliön Flate-algoritmilla. Yksittäisillä objekteilla ei ole enää tavusiirtymää tiedostossa. Niillä on sijainti pakatun blobin sisällä. Validaattori, joka skannaa raakatavut tekstiä 1 0 obj varten, ei koskaan löydä niitä, koska kyseinen teksti on olemassa vasta purkamisen jälkeen. Klassiselle jäsentäjälle puolet dokumentista on yksinkertaisesti kadonnut.
Trailer-avaimet ovat selväkielisiä, jopa pakatussa tiedostossa
Lohdullinen osa on se, että ristiinviitysvirran trailerin lukeminen ei vaadi mitään purkamista. Virtaobjekti kirjoitetaan sanakirjana, jota seuraa stream-avainsana ja sen jälkeen pakatut tavut. Sanakirja on selväkielinen. Joten kun startxref osoittaa ristiinviitysvirtaan, tavut heti objekti-numeron jälkeen näyttävät tavalliselta sanakirjalta, ja /Root, /Size ja /ID sijaitsevat siellä selkeästi ennen kuin stream-avainsana ja Flate-data alkavat.
Tämä tarkoittaa, että validaattori voi oppia kolme eniten tarvitsemaansa asiaa – missä katalogi on, kuinka monta objektia tiedosto väittää sisältävänsä ja tiedoston tunnisteen – jäsentämällä vain virran sanakirjan. Sen ei tarvitse dekompressoida ristiinviittaustietoja, eikä sen tarvitse tulkita sen sisällä olevia binäärimerkintöjä. Työ, joka voittaa naiivin jäsentäjän, ei ole trailerin lukeminen; se on objektien löytäminen. Ne ovat kaksi erillistä ongelmaa, ja ensimmäisen ratkaiseminen on halpaa.
Objektivirrat: otsikko ja sitten Flate-blob
Objektivirta on säiliö. Sen sanakirja kantaa tyyppiä /Type /ObjStm, /N-merkintää, joka antaa sisään pakattujen objektien määrän, ja /First-merkintää, joka antaa tavusiirtymän puretussa datassa, josta ensimmäisen objektin runko alkaa. Pakattu hyötykuorma alkaa purettuna pienellä otsikolla, jossa on /N kokonaislukuparia. Jokainen pari on objektinumero ja kyseisen objektin rungon siirtymä suhteessa arvoon /First. Otsikon jälkeen seuraavat itse objektien rungot peräkkäin.
Yhden purkaminen on mekaanista heti, kun tavut on purettu. Luet sanakirjan saadaksesi /N ja /First, purat virran Flate-purkajalla, käyt läpi johtavat /N paria oppiaksesi, mikä objektinumero asuu milläkin siirtymällä, ja nostat sitten kunkin rungon ulos aivan kuin se olisi tavallinen epäsuora objekti. Ainoa todellinen riippuvuus on Flate-purkaja, ja sinulla on jo sellainen: Delphi toimittaa System.ZLib-yksikön ja Free Pascal toimittaa zstream-yksikön, joista molemmat käärivät zlib-kirjaston ja purkavat raa'an Flate-virran ilman mitään kolmannen osapuolen koodia. Rutiini, joka lisää jokaisen uutetun objektin validaattorin objektitaulukkoon, saa validaattorin loppuvaiheen, eli sen osan, joka kävelee /Root-merkinnän läpi ja tarkistaa sivupuun, käyttäytymään täsmälleen samalla tavalla kuin klassisessa tiedostossa.
Mitä sinun ei tarvitse toteuttaa
Työn määrä on helppo arvioida liian suureksi. Trailerin avaimien lukeminen pakatusta tiedostosta ei vaadi ristiinviitysvirran binääristen merkintöjen dekoodausta. Kohdan §7.5.8 ristiinviitysvirta käyttää kolmea merkintätyyppiä, ja tyypin 2 merkintä, joka sanoo tämä objekti sijaitsee objektivirrassa N indeksissä i
, on se, jonka dekoodaisit rakentaaksesi täydellisen siirtymäkartan. Tarvitset kyseistä karttaa ratkaistaksesi mielivaltaiset objektit numeron mukaan. Et tarvitse sitä lukeaksesi /Root-, /Size- ja /ID-arvot, jotka ovat selväkielisessä sanakirjassa, etkä tarvitse sitä laajentaaksesi objektivirtoja, koska kukin /ObjStm ilmoittaa oman sisältönsä arvojen /N ja /First kautta.
Sinun ei myöskään tarvitse käsitellä PNG- ja TIFF-ennustinfunktioita, joita ristiinviitysvirta saattaa soveltaa /DecodeParms-määritteensä kautta vain saadaksesi trailer-avaimet. Ennustimet suodattavat binääriset ristiinviitysrivit, jotta ne puristuisivat paremmin; niillä ei ole mitään tekemistä virtaa edeltävän sanakirjan kanssa. Minimaalinen päivitys, joka tekee klassisesta validaattorista nykyaikaisen PDF-tietoisen, on siis pieni: kun startxref laskeutuu virtaan pikemminkin kuin xref-avainsanaan, jäsennä virran sanakirja trailer-avaimien saamiseksi ja laajenna kaikki kohtaamasi /ObjStm-objektit niin, että niiden sisältö siirtyy objektitaulukkoon. Tyypin 2 merkintöjen ja ennustimien dekoodaus on erillinen, suurempi tehtävä, jonka voit siirtää tuonnemmaksi, kunnes todella tarvitset mielivaltaista objektin ratkaisua.
Miksi yhteensopivuustarkistuksen on ensin laajennettava virrat
Tämä lakkaa olemasta akateemista heti, kun ajat profiilitarkistuksen. PDF/A- tai PDF/X-validaattori tarkastaa tietyt objektit: dokumenttiluettelon /OutputIntents-taulukon osalta, /Metadata-virran oikealla tunnisteella varustetun XMP-paketin osalta, jokaisen fonttikuvauksen upotetun fonttitiedoston osalta sekä trailer-osan /ID-merkinnän osalta. Pakatussa tiedostossa suurin osa näistä objekteista on objektivirtojen sisällä. Validaattori, joka ei ole laajentanut objektivirtoja, ei näe luettelon avaimia, ei löydä metadataa eikä voi luetella fontteja. Se raportoi täysin vaatimustenmukaisen dokumentin puuttuvan ulostulotarkoituksestaan, puuttuvan XMP-tiedoistaan ja puuttuvan puolesta rakenteestaan, koska sen tarvitsema todiste istuu edelleen Flate-blobissa, jota se ei koskaan purkanut.
Järjestyksellä on merkitystä. Laajennuksen on tapahduttava ennen tarkistusten suorittamista, ei niiden rinnalla, koska jokainen tarkistus olettaa, että se voi tavoittaa objektin numeron mukaan. Jos kytket vaatimustenmukaisuustarkistuksen suoraan raa'an tavun skannaukseen, se perii klassisen jäsentäjän sokeuden ja tuottaa vääriä virheitä juuri nykyaikaisilla tiedostoilla, jotka ovat todennäköisimmin hyvin muodostettuja, koska ne tulivat työkalukentistä, jotka ovat riittävän uusia kirjoittaakseen ristiinviitysvirtoja alun alkaenkaan.
Annetaan PDFiumin hoitaa jäsentäminen puolestasi
PDFium-komponentti jäsentää ristiinviittaustiedot ja objektivirrat osana dokumentin lataamista, mikä on käytännöllinen tapa välttää purkamis- ja laajentamisvaiheen käsityötä. Kun lataat tiedoston TPdf-komponentilla, /ObjStm-säiliöihin pakatut objektit on jo ratkaistu, ja validoinnin aloituspisteet näkevät täysin laajennetun dokumentin. ValidatePdfA palauttaa TPdfAValidationResult-tietueen, jonka Conformance-kenttä on TPdfAConformance-arvo, kuten pac1b tai pacNone, jonka Issues-kenttä on joukko havaittuja erityisongelmia, ja jonka IsCompliant-metodi on tosi vain silloin, kun vaatimustenmukaisuustaso on havaittu ja ongelmajoukko on tyhjä. Koska objektit laajennettiin latauksen aikana, /OutputIntents-taulukko tai upotettu fontti, joka asui objektivirrassa, löytyy eikä sitä ilmoiteta puuttuvaksi.
uses
PDFium, FPdfPdfa;
function CheckPdfA(const FileName: string): TPdfAValidationResult;
var
Pdf: TPdf;
begin
Pdf := TPdf.Create(nil);
try
Pdf.FileName := FileName;
Pdf.Active := True; // parses xref/object streams on load
Result := Pdf.ValidatePdfA; // sees the expanded object table
finally
Pdf.Free;
end;
end;
Sama pätee metodiin ValidatePdfX, joka palauttaa samantyyppisen TPdfXValidationResult-tuloksen. PDFiumin kautta reitittämisen ydin on siinä, että edellä kuvattu rakenteellinen dekompressio tapahtuu kerran, oikein, latausohjelman sisällä, joten validointikoodisi ei koskaan näe eroa perinteisen tiedoston ja täysin pakatun tiedoston välillä. Molemmat saapuvat validaattorille ratkaistuna objektijoukkona.
var
Pdf: TPdf;
R : TPdfXValidationResult;
begin
Pdf := TPdf.Create(nil);
try
Pdf.FileName := 'Press_Ready.pdf';
Pdf.Active := True;
R := Pdf.ValidatePdfX;
if R.IsCompliant then
Writeln('PDF/X conformance: ', Ord(R.Conformance))
else
Writeln('Not conformant; issue count = ', SizeOf(R.Issues));
finally
Pdf.Free;
end;
end;
Jos tavut ovat jo muistissa eivätkä levyllä, sama lataa-sitten-validoi-jakso toimii LoadDocument(const Data: TBytes) -ylikuormituksen kautta, joka ottaa raa'an tiedoston sisällön ja jäsentää sen ristiinviittaukset ja objektivirrat samalla tavalla kuin tiedostopolku tekee. Käsin kirjoitetun validaattorin opetus on rakenteellinen sääntö, ei API: lue trailer-avaimet virran sanakirjasta selväkielisenä, laajenna jokainen /ObjStm Flate-purkajalla ennen dokumentin käymistä läpi, ja käsittele binääristen ristiinviittausten dekoodausta suurempana, valinnaisena työnä.
Kun rakenne on laajennettu, validaattori voi ajaa loput työnkulusta sen yli. Komentoriviltä ajettavasta esitarkastusympäristöstä, joka raportoi vaatimustenmukaisuuden syötteiden kansiosta, on tietoa ohjeessamme eräesitarkastusraportin komentorivityökalun rakentamisesta. Kun validointi on porttina ennen suuren dokumentin purkamista osiin, tekniikat ohjeessamme PDF-dokumenttien jakamisesta useisiin tiedostoihin paritetaan luonnollisesti tässä esitetyn lataa-ja-tarkista-mallin kanssa. Molemmat rakentuvat PDFium Componentin lataus- ja validointipinnalle Delphille ja C++Builderille.