Arkistoinnin sisäänottoportti hylkäsi erän "PDF/A-2b"-tiedostoja, jotka avautuivat moitteetta jokaisessa työpöydällä olevassa katseluohjelmassa. Toimittaja vannoi, että ne olivat vaatimustenmukaisia. Eivät olleet: jokaisessa oli JavaScript-toiminto haudattuna katalogiin, sellainen asia, jota pikainen silmäys ei koskaan huomaa mutta täysimittainen PDF/A-validaattori kuten veraPDF liputtaa silmänräpäyksessä. Kompastuskivi on, ettei kukaan halunnut liittää Java-työkaluketjua Delphi-eräpalveluun vain vastatakseen yhteen kyllä-tai-ei-kysymykseen tiedostoa kohden. Tämän aukon täyttää PDFium Componentin ValidatePdfACompliance, ja kannattaa ymmärtää, miten se päätyy tuomioon ilman, että se koskaan jäsentää sisältövirtaa täydellisesti
Miksi PDFium itse ei voi vastata tähän
Ensimmäinen asia, joka kannattaa tunnustaa rehellisesti: mukana tuleva pdfium.dll ei tue PDF/A:ta lainkaan. Julkisessa pinnassa ei ole ConvertToPDFA-funktiota, ei OutputIntent-kirjoitinta, ei XMP-rajapintaa. Jokainen osa tämän kirjaston PDF/A-toteutuksesta, sekä kirjoitus- että tarkistuspuoli, elää puhtaana Pascal-koodina tiedostossa FPdfPdfa.pas ja toimii tavutason jäsennyksellä sekä inkrementaalisella päivityksellä. Kun siis kutsut validaattoria, et kysy Chromiumin renderöijältä mitään. Ajat Pascal-tokenskanneria tiedoston rakennetavujen yli
Julkinen rajapinta on tarkoituksella pieni. Yksi funktio lukee virran positiosta 0 ja palauttaa tietueen:
function ValidatePdfACompliance(Source: TStream): TPdfAValidationResult;
type
TPdfAValidationResult = record
Conformance: TPdfAConformance; // pacUnknown, pacNone, pac1b, pac2u, ...
Issues: TPdfAValidationIssues; // joukko TPdfAValidationIssue-arvoja
function IsCompliant: Boolean; // True vain kun taso <> unknown/none
end; // JA Issues on tyhjä
IsCompliant kiteyttää säännön, jolla on merkitystä portissa: tiedosto läpäisee vain, kun todellinen vaatimustenmukaisuustaso havaittiin ja ongelmajoukko on tyhjä. Jäsennys, joka onnistuu mutta ei löydä pdfaid-merkkiä, ratkeaa arvoksi pacNone, mikä ei nimenomaisesti ole läpäisy. Tämä on sama asia, jonka erän preflight-raportin komentorivityökalu tekee selväksi ulkopuolelta: tyhjä löydöslista tunnistamattomalla tiedostolla ei ole puhtaan paperit
Virtojen sisällön karsinta ennen token-skannausta
Tässä on kaikkein tärkein toteutuksen yksityiskohta, ja se on helpoin mennä pieleen, jos kirjoitat oman skannerin. Tunnistin löytää rikkomukset etsimällä rajattuja nimitokeneita, kuten /JavaScript, /LZWDecode, /BM. Jos skannaat raakoja tiedostotavuja, upotetut binäärivirtojen sisällöt, pakatut kuvat, ICC-profiilit, fonttiohjelmat, sisältävät satunnaisesti tavusarjoja, jotka näyttävät noilta tokeneilta. Raportoit /AA- tai /3D-tokenin "löytyneen", koska kolme tavua JPEG-kuvan sisällä sattui tavaamaan sen. Se on väärien positiivisten tehdas
Korjaus on PdfStructureBytes: se käy tiedoston läpi ja tyhjentää tavut jokaisen stream- ja endstream-avainsanan välillä välilyönneiksi, jättäen sanakirjarakenteen koskemattomaksi. Vasta sen jälkeen skannaus ajetaan. Jokainen validaattorin nimitokentarkistus toimii tällä karsitulla kopiolla. Jos otat tästä artikkelista mukaasi vain yhden ajatuksen, ota tämä. Sama kuri toistuu PDF/UA-validaattorissa, joka pitää oman kopionsa rutiinista, koska kaksi standardia kehittyvät toisistaan riippumatta
29 ongelmaa ja mitä kukin niistä tarkoittaa
TPdfAValidationIssue on dokumentoitu sopimus. Järjestysluvut on jäädytetty, koska DUnitX-testit, demot ja raporttikerros riippuvat kaikki niistä, joten uudet löydökset lisätään aina vain loppuun. Versiosta 1.63.0 alkaen jäseniä on 29. Ne jakautuvat muutamaan perheeseen:
- Metatiedot ja tunniste:
pvaiMissingXmpMetadata,pvaiMissingPdfAIdentifier,pvaiMissingTrailerId(ISO 19005-1 6.1.3),pvaiMissingXmpDates - Väri ja tulostustarkoitus:
pvaiMissingOutputIntent,pvaiMissingIccProfile, sekäpvaiMixedDeviceColorSpaces, kun sekä DeviceRGB että DeviceCMYK esiintyvät (6.2.3.3) - Ehdottomat kiellot jokaiselle osalle:
pvaiEncryptionPresent(/Encrypt-sanakirja on suoraan kielletty),pvaiJavaScriptPresent,pvaiForbiddenAction,pvaiAdditionalActions,pvaiLzwUsed,pvaiXfaPresent,pvaiNeedAppearancesTrue,pvaiForbiddenAnnotation - Fontit:
pvaiFontNotEmbeddedja tiukempipvaiUnembeddedFont, sekäpvaiUnicodeMappingMissingLevel U -väitteelle ilman/ToUnicode-kohdetta - Tagitus:
pvaiLevelAStructureMissing, kun conformance=A-väitteellä ei ole tagattua rakennetta
Kuusi uusinta jäsentä, lisätty järjestysluvuille 24–29, kattavat hienovaraiset tapaukset, joihin tarkastajat oikeasti kompastuvat: pvaiTrappedTrue (/Trapped /True Info-sanakirjassa, "väärä ystävä", koska arvon täytyy olla False tai Unknown), pvaiForbiddenActionSubtype (Sound tai Movie käytettynä toimintona, ei vain merkintänä), pvaiTransparentColorSpace (muu kuin Normal-sekoitustila tai /CA//ca, joka ei ole 1,0), pvaiAnnotationDictViolation, pvaiUnembeddedFont ja pvaiMixedDeviceColorSpaces
Parttikohtainen portitus: A-1 on tiukka, A-2 ja A-3 ovat väljempiä
PDF/A ei ole yksi sääntökirja. Kolme asiaa, jotka PDF/A-1 kieltää, on nimenomaisesti sallittu PDF/A-2:sta alkaen: läpinäkyvyys (/Transparency-ryhmä tai aktiivinen /SMask, 6.4), valinnainen sisältö (/OCProperties, 6.1.13) ja upotetut tiedostot (/EmbeddedFiles tai /EF, 6.1.11). Naiivi validaattori, joka liputtaa kaikki kolme jokaisessa tiedostossa, hylkää täysin kelvollisia PDF/A-2-asiakirjoja joukoittain
Siksi validaattori lukee osan numeron pdfaid-merkistä PdfAPartOf-funktion kautta ja porttaa nuo tarkistukset ehdon PartNo = 1 taakse. Uusien läpinäkyvyysongelmien sekoitustila- ja merkintä-alfa-tarkistukset ovat samoin vain osalle 1:
if PartNo = 1 then
begin
if PdfHasName(Struct, '/BM') then
if not PdfHasBMNormal(Struct) then // only /Normal or /Compatible allowed
Include(Result.Issues, pvaiTransparentColorSpace);
if PdfHasCaNotOne(Struct, '/CA') or PdfHasCaNotOne(Struct, '/ca') then
Include(Result.Issues, pvaiTransparentColorSpace);
end;
Yksi varovainen oletusarvo ansaitsee maininnan: kun pdfaid-merkkiä ei ole lainkaan, osa käsitellään arvona 1, tiukin. Perustelu on, että tunnistamaton tiedosto tulisi pitää tiukimpien sääntöjen alaisena sen sijaan, että se läpäistäisiin vilkaisulla. JavaScript, kielletyt toiminnot, LZW, XFA, NeedAppearances, kielletyt merkinnät ja upottamattomat fontit pysyvät kiellettyinä jokaiselle osalle, joten nuo tarkistukset eivät koskaan istu portin takana
Objektivirtojen avaaminen niin, ettei mikään jää piiloon
PDF 1.5 toi mukanaan ristiviittausvirran ja objektivirran (/Type /ObjStm), ja ne luovat sokean pisteen naiiville tavuskannerille. Katalogi, OutputIntent, toimintosanakirja, mikä tahansa, joka ei itse ole virta, voidaan Flate-pakata ObjStm:n sisään. Skannaa raaka rakenne, etkä näe siitä mitään, ja raportoit sitten puhtaan tiedoston, joka ei ole sitä lainkaan
PdfExpandObjectStreams sulkee tuon aukon. Ennen minkään tarkistuksen ajamista validaattori tekee Data := PdfExpandObjectStreams(Data). Rutiini löytää jokaisen ObjStm:n, lukee sen /N- ja /First-otsikot saadakseen sisältämiensä objektien numerot ja siirtymät, purkaa sisällön PdfInflate-funktiolla (RTL:n zlib, System.ZLib Delphissä ja zstream FPC:ssä) ja liittää jokaisen sisältämänsä objektin tavallisena N 0 obj ... endobj-lohkona tavukopion loppuun. Olemassa olevat tokentarkistukset löytävät sitten nuo objektit ilman muutoksia niiden logiikkaan
Kaksi rajoitetta tekevät tästä siistin, ei hauraan. Virtaobjektit, Metadata, ICC-profiili ja fonttiohjelmat, eivät voi elää objektivirrassa, vain ei-virtasanakirjat voivat, joten laajennus käsittelee aina vain sanakirjoja, eivätkä liitetyt objektit kanna stream-avainsanaa häiritsemässä sisällönkarsintakierrosta. Ja koska liitetty sisältö laskeutuu %%EOF:n jälkeen, käänteinen haku startxref:sta löytää edelleen alkuperäisen trailerin. Itse ristiviittausvirran traileri käsiteltiin jo aiemmin, versiossa 1.49.3, lukemalla Root, Size ja ID suoraan selväkielisestä xref-virran sanakirjasta, aihe, jota käsitellään rinnakkaisartikkelissa objekti- ja ristiviittausvirtojen validointi; objektivirtatyö tarvitsi vain purkuvaiheen lisäämisen, ilman tarvetta purkaa tyypin 2 xref-merkintöjä tai avata PNG-ennustinta
Byte-tason tarkistimen rehelliset rajat
Tämä on preflight-työkalu, ei sertifioitu validaattori, ja rajat ovat todellisia. Fonttien upotus on laskentaan perustuva heuristiikka, ja sen saaminen oikein vaati korjauksen, joka kannattaa tietää. Alkuperäinen tarkistus käytti funktiota PdfCountName('/FontDescriptor'), mutta jokainen fontti tuottaa kaksi /FontDescriptor-tokenia, yhden viittauksen fonttisanakirjasta ja yhden /Type-kentän itse deskriptoriobjektissa, joten määrä oli 2N N:ää upotettua ohjelmaa vastaan, ja testi oli aina tosi. Korjaus on PdfCountDescriptorRefs, joka laskee vain /FontDescriptor N G R -viittausmuodon, yhden per fontti, ja nostaa lipun pvaiUnembeddedFont vain, kun upotettuja ohjelmia on aidosti vähemmän:
K := PdfCountDescriptorRefs(Struct); // yksi per fonttisanakirja
Emb := PdfCountName(Struct, '/FontFile')
+ PdfCountName(Struct, '/FontFile2')
+ PdfCountName(Struct, '/FontFile3');
if (K > 0) and (Emb < K) then
Include(Result.Issues, pvaiUnembeddedFont);
Vaikka korjattu, se on karkea: sekamuotoinen asiakirja, jossa jokaisella deskriptorilla sattuu olemaan jokin FontFile, voi silti päästää yksittäisen ei-vaatimustenmukaisen fontin läpi. Objektivirtojen laajentamisella on myös tunnettu sivuvaikutus: se paljastaa standard-14-oletusresurssit, joita AcroForm /DR kantaa, kuten /Helv, ja heuristiikka raportoi ne tunnollisesti upottamattomiksi, vaikka veraPDF päästää ne läpi, koska niitä ei koskaan tosiasiassa käytetä renderöintiin. Sisältövirran operaattoritason tarkistukset (6.2.10) jäävät kokonaan soveltamisalan ulkopuolelle, koska ne vaatisivat täyden sisällönjäsennyksen tavuskannauksen sijaan. Kohtele validaattoria nopeana, riippuvuusvapaana ensimmäisenä porttina, joka nappaa ne rikkomukset, joita merkin injektointi ei voi korjata, ja varaa täysi validaattori lopulliseen sertifiointiin
Tämä on tarinan tarkistuspuoli. Täydentävä kirjoituspuoli, jossa SaveAsPdfA injektoi XMP:n, OutputIntentin ja sRGB ICC -profiilin ja alentaa rehellisesti Level A -pyynnön, jolla ei ole tagattua rakennetta, rakentuu samalle byte-tason koneistolle. Molemmat puoliskot toimitetaan PDFium Component for Delphi -komponentissa, yhdessä VCL-paketissa puhtaan Pascal-toteutuksen PDF/A-toiminnallisuuden päällä, ilman ulkoista ajonaikaista ympäristöä asennettavaksi