Kirjoitat pienen validaattorin. Se avaa PDF:n, etsii tiensä loppuun, löytää sanan startxref, lukee siirtymän ja odottaa päätyvänsä avainsanaan xref, jonka alla on kiinteäleveyksinen ristiviittaustaulukko. Tuosta taulukosta se kerää objektien siirtymät ja selaa sitten taaksepäin trailer-avainsanaa saadakseen selville /Root- ja /Size-arvot. Se toimii täydellisesti jokaisella tiedostolla, jonka olet luonut sitä testataksesi. Sitten saapuu tiedosto, jonka on tuottanut Wordin nykyinen versio tai kirjasto, joka kohdistuu PDF 1.5 -versioon, ja validaattori julistaa sen rikkinäiseksi. Siellä ei ole xref-avainsanaa, johon siirtymä osoittaa, ei trailer-sanakirjaa missään, ja validaattorin rakentama objektitaulukko on lähes tyhjä. Tiedosto on pätevä. Validaattori lukee sitä viisitoista vuotta vanhan linssin läpi
Tämä on yleisin yksittäinen syy siihen, miksi klassista asettelua vasten kirjoitettu tavutason PDF-tarkistus epäonnistuu moderneissa asiakirjoissa. Rakenne, josta se riippuu, selkokielinen ristiviittaustaulukko ja trailer-avainsana, muutettiin valinnaisiksi PDF 1.5:ssä, ja ne puuttuvat usein. Kaksi ominaisuutta korvasi sen: ristiviittausvirta ja pakattu objektivirta. Molemmat on kuvattu ISO 32000-1 -standardissa, ja validaattori, joka ei tiedä niistä, näkee terveen tiedoston puuttuvien objektien kasana
Mitä PDF 1.5 muutti tiedoston lopussa
ISO 32000-1 §7.5.8 määrittelee ristiviittausvirran, ja §7.5.7 määrittelee /ObjStm-tyyppisen objektivirran. Yhdessä ne antavat kirjoittajalle mahdollisuuden jättää pois ne kaksi rakennetta, joihin klassinen jäsennin nojautuu. PDF 1.5 -tiedosto voi päättyä ilman minkäänlaista xref-taulukkoa. Sen tilalla objekti, johon startxref osoittaa, on tavallinen virtakohde, jonka sanakirjassa on /Type /XRef, ja tuo virta pitää ristiviittaustiedot kompaktissa binäärimuodossa. Myöskään trailer-avainsanaa ei ole, koska traileri on nyt virran oma sanakirja. Avaimet, joita klassinen jäsennin metsästi, /Root, /Size ja /ID, asuvat tuon sanakirjan sisällä
Toinen muutos siirtää itse objektit. Sen sijaan, että jokainen epäsuora objekti kirjoitettaisiin omalle tavusiirtymälleen, kirjoittaja voi pakata monta pientä objektia, sivusanakirjat, merkintäsanakirjat, rakennepuun, yhteen objektivirtaan ja pakata koko säilön Flate-algoritmilla. Yksittäisillä objekteilla ei enää ole tavusiirtymää tiedostossa. Niillä on sijainti pakatun möykyn sisällä. Validaattori, joka skannaa raakatavuja etsien merkkijonoa 1 0 obj, ei koskaan löydä niitä, koska tuo teksti on olemassa vasta purkamisen jälkeen. Klassiselle jäsentimelle puolet asiakirjasta on yksinkertaisesti kadonnut
Traileriavaimet ovat selkotekstiä, jopa pakatussa tiedostossa
Rauhoittava osa on se, että ristiviittausvirran trailerin lukeminen ei vaadi minkään purkamista. Virtakohde kirjoitetaan sanakirjana, jota seuraa stream-avainsana ja sitten pakatut tavut. Sanakirja on selkotekstiä. Joten kun startxref osoittaa ristiviittausvirtaan, objektinumeron jälkeiset tavut näyttävät heti tavalliselta sanakirjalta, ja /Root, /Size ja /ID istuvat siellä selvänä, ennen kuin stream-avainsana ja Flate-data alkavat
Tämä tarkoittaa, että validaattori voi oppia ne kolme faktaa, joita se eniten tarvitsee, missä luettelo on, kuinka monta objektia tiedosto väittää sisältävänsä, ja tiedostotunnisteen, jäsentämällä pelkän virtasanakirjan. Sen ei tarvitse purkaa ristiviittaustietoja, eikä sen tarvitse tulkita sen sisällä olevia binäärimerkintöjä. Työ, joka nujertaa naiivin jäsentimen, ei ole trailerin lukeminen; se on objektien löytäminen. Nämä ovat kaksi erillistä ongelmaa, ja ensimmäisen ratkaiseminen on halpaa
Objektivirrat: otsikko, sitten Flate-möykky
Objektivirta on säilö. Sen sanakirjassa on /Type /ObjStm, /N-merkintä, joka kertoo sisään pakattujen objektien määrän, ja /First-merkintä, joka antaa tavusiirtymän, puretun datan sisällä, josta ensimmäisen objektin runko alkaa. Pakattu hyötykuorma alkaa purkamisen jälkeen pienellä otsikolla, joka koostuu /N:stä kokonaislukuparista. Jokainen pari on objektinumero ja kyseisen objektin rungon siirtymä suhteessa /First-arvoon. Otsikon jälkeen tulevat itse objektien rungot peräkkäin yhdistettyinä
Yhden laajentaminen on mekaanista, kun tavut on purettu. Luet sanakirjan saadaksesi /N- ja /First-arvot, purat virran Flate-dekooderilla, käyt läpi johtavat /N-parit oppiaksesi, mikä objektinumero asuu missäkin siirtymässä, ja nostat sitten jokaisen rungon ulos ikään kuin se olisi tavallinen epäsuora objekti. Ainoa todellinen riippuvuus on Flate-dekooderi, ja sinulla on jo sellainen: Delphi toimittaa System.ZLib-yksikön, ja Free Pascal toimittaa zstream-yksikön, jotka molemmat kietovat zlibin ja purkavat raa'an Flate-virran ilman minkäänlaista kolmannen osapuolen koodia. Rutiini, joka liittää jokaisen puretun objektin validaattorin objektitaulukkoon, saa validaattorin loppuosan, sen osan, joka käy läpi /Root-arvon ja tarkistaa sivupuun, käyttäytymään aivan samalla tavalla kuin se käyttäytyisi klassisessa tiedostossa
Mitä sinun ei tarvitse toteuttaa
Työmäärää on helppo yliarvioida. Traileriavaimien lukeminen pakatusta tiedostosta ei vaadi ristiviittausvirran binäärimerkintöjen dekoodaamista. §7.5.8-ristiviittausvirta käyttää kolmea merkintätyyppiä, ja tyypin 2 merkintä, se joka sanoo tämä objekti asuu objektivirrassa N indeksissä i
, on se, jonka dekoodaisit rakentaaksesi täyden siirtymäkartan. Tarvitset tuota karttaa mielivaltaisten objektien ratkaisemiseen numerolla. Et tarvitse sitä lukeaksesi /Root-, /Size- ja /ID-arvot, jotka ovat selkotekstisanakirjassa, etkä tarvitse sitä objektivirtojen laajentamiseen, koska jokainen /ObjStm ilmoittaa omasta sisällöstään /N- ja /First-arvojen kautta
Sinun ei myöskään tarvitse käsitellä PNG- ja TIFF-ennustinfunktioita, joita ristiviittausvirta voi soveltaa /DecodeParms-parametrin kautta vain traileriavaimien saamiseksi. Ennustimet suodattavat binäärisiä ristiviittausrivejä saadakseen ne pakkautumaan paremmin; niillä ei ole mitään tekemistä virtaa edeltävän sanakirjan kanssa. Minimaalinen päivitys, joka tekee klassisesta validaattorista modernista PDF:stä tietoisen, on siksi pieni: kun startxref päätyy virtaan xref-avainsanan sijaan, jäsennä virtasanakirja traileriavaimia varten, ja laajenna kohtaamasi /ObjStm-objektit, jotta niiden sisältö pääsee objektitaulukkoon. Tyypin 2 merkintöjen ja ennustimien dekoodaus on erillinen, suurempi tehtävä, jota voit lykätä, kunnes todella tarvitset satunnaista objektinratkaisua
Miksi vaatimustenmukaisuuden tarkistuksen on laajennettava virrat ensin
Tämä lakkaa olemasta akateemista heti, kun suoritat profiilitarkistuksen. PDF/A- tai PDF/X-validaattori tarkastaa tiettyjä objekteja: asiakirjan luettelon /OutputIntents-taulukon varalta, /Metadata-virran XMP-paketin varalta, jolla on oikea tunniste, jokaisen fonttikuvauksen upotetun fonttitiedoston varalta, trailerin /ID-arvon varalta. Pakatussa tiedostossa useimmat noista objekteista ovat objektivirtojen sisällä. Validaattori, joka ei ole laajentanut objektivirtoja, ei voi nähdä luettelon avaimia, ei löydä metadataa eikä voi luetella fontteja. Se raportoi täysin vaatimustenmukaisesta asiakirjasta, että siitä puuttuu tulostustarkoitus, XMP ja puolet sen rakenteesta, koska todisteet, joita se tarvitsee, istuvat yhä Flate-möykyssä, jota se ei koskaan purkanut
Järjestyksellä on väliä. Laajennuksen on tapahduttava ennen tarkistusten suorittamista, ei niiden rinnalla, koska jokainen tarkistus olettaa voivansa saavuttaa objektin numerolla. Jos kytket profiilitarkistuksen suoraan raakatavujen skannaukseen, se perii klassisen jäsentimen sokeuden ja tuottaa vääriä rikkomuksia juuri niissä nykyaikaisissa tiedostoissa, jotka todennäköisimmin ovat hyvin muodostettuja, koska ne tulivat työkaluputkista, jotka ovat ylipäätään tarpeeksi uusia kirjoittaakseen ristiviittausvirtoja
Anna PDFiumin tehdä jäsennys puolestasi
PDFium Component jäsentää ristiviittausvirrat ja objektivirrat osana asiakirjan lataamista, mikä on käytännöllinen tapa välttää purku- ja laajennusvaiheen tekeminen käsin. Kun lataat tiedoston TPdf-komponentilla, /ObjStm-säilöihin pakatut objektit on jo ratkaistu, ja validointialoituspisteet näkevät täysin laajennetun asiakirjan. ValidatePdfA palauttaa TPdfAValidationResult-tietueen, jonka Conformance-kenttä on TPdfAConformance-arvo, kuten pac1b tai pacNone, jonka Issues-kenttä on joukko löydettyjä erityisiä ongelmia, ja jonka IsCompliant-metodi on tosi vain, kun vaatimustenmukaisuustaso havaittiin ja ongelmajoukko on tyhjä. Koska objektit laajennettiin latauksen aikana, objektivirran sisällä asuva /OutputIntents-taulukko tai upotettu fontti löydetään, ei raportoida puuttuvana
uses
PDFium, FPdfPdfa;
function CheckPdfA(const FileName: string): TPdfAValidationResult;
var
Pdf: TPdf;
begin
Pdf := TPdf.Create(nil);
try
Pdf.FileName := FileName;
Pdf.Active := True; // jäsentää xref-/objektivirrat latauksen yhteydessä
Result := Pdf.ValidatePdfA; // näkee laajennetun objektitaulukon
finally
Pdf.Free;
end;
end;
Sama koskee ValidatePdfX-metodia, joka palauttaa samanmuotoisen TPdfXValidationResult-tietueen. PDFiumin kautta reitittämisen pointti on, että yllä kuvattu rakenteellinen purkaminen tapahtuu kerran, oikein, lataajan sisällä, joten validointikoodisi ei koskaan näe eroa klassisen tiedoston ja täysin pakatun tiedoston välillä. Molemmat saapuvat validaattoriin ratkaistuna objektijoukkona
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 := 'none';
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('PDF/X conformance: ', PdfXConformanceName(R.Conformance))
else
begin
IssueCount := 0;
for Issue in R.Issues do // Issues on joukko: laske sen jäsenet
Inc(IssueCount);
Writeln('Not conformant; issue count = ', IssueCount);
end;
finally
Pdf.Free;
end;
end;
Jos tavut ovat jo muistissa levyn sijaan, sama lataa-sitten-validoi-sekvenssi toimii LoadDocument(const Data: TBytes)-ylikuormituksen kautta, joka ottaa raa'an tiedostosisällön ja jäsentää sen ristiviittaus- ja objektivirrat samalla tavalla kuin tiedostopolkukin. Opetus käsin kirjoitetulle validaattorille on rakenteellinen sääntö, ei API: lue traileriavaimet virtasanakirjasta selkotekstinä, laajenna jokainen /ObjStm Flate-dekooderilla ennen kuin käyt asiakirjan läpi, ja käsittele binääristen ristiviittausmerkintöjen dekoodausta sinä suurempana, valinnaisena työnä, joka se on
Kun rakenne on laajennettu, validaattori voi ajaa lopun työnkulusta sen päällä. Komentoriviltä toimivasta preflight-valjastuksesta, joka raportoi vaatimustenmukaisuuden koko syötekansiosta, katso läpikäyntimme eräajona toimivan preflight-raportin komentorivisovelluksen rakentamisesta. Kun validointi on portti ennen suuren asiakirjan pilkkomista osiin, oppaassamme PDF-asiakirjojen jakamisesta useisiin tiedostoihin esitellyt tekniikat sopivat luontevasti yhteen tässä esitetyn lataa-ja-tarkista-kuvion kanssa. Molemmat perustuvat PDFium Component -komponentin lataus- ja validointipintaan Delphille ja C++Builderille