Tekninen artikkeli

PDF/raster-skannattujen asiakirjojen validointi Delphissä

PDF/R eli standardi ISO 23504-1 on skannatuille asiakirjoille tarkoitettu PDF-profiili: jokaisella sivulla on täsmälleen yksi kaistakuva eikä yhtään mitään muuta. PDFium Component validoi sen Delphistä, Lazaruksesta ja C++Builderistä funktiolla ValidatePdfRCompliance, joka lukee streamin ja palauttaa vaatimustenmukaisuustason sekä joukon konkreettisia ongelmia

Profiili syntyi siksi, että skannerit ja dokumenttien kaappausjärjestelmät tarvitsivat PDF/A:ta kapeamman maalin. Arkistoitava PDF saa sisältää mitä tahansa, minkä standardin osa sallii; rasteri-PDF on tarkoituksella riisuttu, jotta mikä tahansa vaatimukset täyttävä lukija näyttää sen samalla tavalla ja mikä tahansa vaatimukset täyttävä kirjoitin saa sen ulos suoraan skannauksesta ilman taitto-ohjelmaa

Kartta PDF/R-validoinnista PDFium Component for Delphi -komponentissa: ValidatePdfRCompliance lukee streamin TPdfRValidationResult-tietueeseen, jonka ongelmajoukko kattaa sivusäännöt kuten pvriFontForbidden ja pvriBadMediaBox, ISO 23504-1:n kuvasuodattimien sallittujen listan sekä säiliösäännöt kuten pvriObjStmPresent
Yksi kutsu palauttaa vaatimustenmukaisuustason ja joukon konkreettisia ongelmia, ja tarkistukset jakautuvat sivusääntöihin, neljän suodattimen sallittujen listaan sekä säiliösääntöihin, jotka laukeavat jo ennen kuin yhtäkään sivua on luettu

Mitä PDF/R kieltää, minkä PDF/A sallii?

Käytännössä tekstin. Rasterisivulla on skannattu kuva eikä mitään muuta, joten sivulla oleva fonttiresurssi on rikkomus — se raportoidaan koodilla pvriFontForbidden, ISO 23504-1 §6.5.2. Se yllättää ne, jotka lisäävät näkymättömän OCR-tekstikerroksen haettavuuden vuoksi: PDF/A-työnkulussa se on aivan normaali ja hyödyllinen temppu, mutta PDF/R:ää siitä ei tule

Sivun ja kuvan suhde on yhtä tiukka. §6.5.1 vaatii jokaiselta sivulta täsmälleen yhden kaistakuvan, joten pvriPageImageMismatch nousee heti kun kuvien määrä ei täsmää sivujen määrään — sivu ilman kuvaa ja sivu jolla niitä on kaksi ovat kumpikin vaatimustenvastaisia. pvriBadMediaBox puolestaan raportoi sivun, jonka MediaBox ei ole muotoa [0 0 w h] (§6.5.3), koska skannauksella ei ole mitään syytä alkaa muualta kuin origosta

uses FPdfPdfr;

var
  Src: TFileStream;
  Res: TPdfRValidationResult;
begin
  Src := TFileStream.Create('scan-batch-0142.pdf', fmOpenRead or fmShareDenyWrite);
  try
    Res := ValidatePdfRCompliance(Src);
    if Res.IsCompliant then
      Memo1.Lines.Add('PDF/R-1 conformant')
    else
    begin
      if pvriFontForbidden in Res.Issues then
        Memo1.Lines.Add('A page names a font resource; a raster page carries no text');
      if pvriPageImageMismatch in Res.Issues then
        Memo1.Lines.Add('Image count does not match page count');
      if pvriForbiddenImageFilter in Res.Issues then
        Memo1.Lines.Add('A strip image uses an encoding outside the white list');
    end;
  finally
    Src.Free;
  end;
end;

Mitkä kuvakoodaukset ovat sallittuja

Neljä, ja lista on lyhyt hyvästä syystä. §6.6 hyväksyy suodattimet /CCITTFaxDecode, /DCTDecode, /JPXDecode ja /FlateDecode — kaksitasoinen faksi, JPEG, JPEG 2000 ja häviötön deflate, jotka yhdessä kattavat jokaisen skanneriulostulon jolla on merkitystä. Kaikesta muusta nousee pvriForbiddenImageFilter, mukaan lukien /LZWDecode, /RunLengthDecode, /ASCII85Decode, /ASCIIHexDecode, /JBIG2Decode ja /Crypt

Kaksi noista hylkäyksistä kannattaa ymmärtää eikä vain opetella ulkoa. /JBIG2Decode pakkaa kaksitasoiset skannaukset poikkeuksellisen tiiviisti ja on PDF/A:ssa täysin laillinen, mutta sen symbolisanakirjan rekonstruktio voi vaihtaa tilalle silmämääräisesti samannäköisen glyfin — skannattujen numeroiden kohdalla tämä on dokumentoitu vikatila — eikä profiili, jonka koko idea on uskollinen rasteritoisto, voi ottaa sitä riskiä. ASCII-suodattimet on jätetty pois päinvastaisesta syystä: ne vain kasvattavat tiedostoa antamatta rasteriprofiilille mitään

PDF/R-kuvasuodattimien sallittujen lista Delphi-validointiin: CCITTFaxDecode, DCTDecode, JPXDecode ja FlateDecode ovat sallittuja kaistakuvissa, kun taas LZW-, RunLength-, ASCII85-, ASCIIHex-, JBIG2- ja Crypt-suodattimet nostavat pvriForbiddenImageFilterin
ISO 23504-1 hyväksyy täsmälleen neljä kuvakoodausta, ja kahdelle kiinnostavalle hylkäykselle on syynsä: JBIG2 voi vaihtaa skannattuihin numeroihin samannäköisen glyfin, ASCII-suodattimet taas vain kasvattavat tiedostoa

Rakennesäännöt, jotka laukeavat ennen yhtäkään sivua

PDF/R kurittaa myös säiliötä. pvriObjStmPresent raportoi /Type /ObjStm -streamin, jonka profiili kieltää suoralta kädeltä — objektistreamit mutkistavat sitä yksinkertaista peräkkäistä jäsennystä, johon rasterilukijan pitäisi pystyä. pvriBadHeader raportoi otsakkeen, joka on jotain muuta kuin %PDF-1.4–1.7 tai %PDF-2.0, ja pvriEncryptVersionMismatch salatun tiedoston, jonka otsake ei ole %PDF-2.0 (§6.2.3)

Katalogi ja Info-sanakirja eivät ole pelkästään tarkistuksen alla vaan sallittujen listalla. pvriProhibitedCatalogEntry ja pvriProhibitedInfoEntry nousevat sallitun joukon ulkopuolisista merkinnöistä, ja pvriInfoXmpMismatch silloin kun Info-merkintä ja sen XMP-vastine ovat eri mieltä. Puuttuvalle katalogin /Metadata-streamille, puuttuvalle trailerin /ID:lle ja puuttuvalle %PDF-raster-1.0-loppumerkille on kullekin oma koodinsa

Miksi tallennusasetuksista puuttuvat Title ja Author

TPdfRSaveOptions kantaa kentät Creator, Producer, CreationDate, ModDate, DocumentId ja InstanceId, eikä siinä ole tarkoituksella kenttää Titlelle, Authorille, Subjectille tai Keywordsille. Juuri ne neljä §6.4.3 kieltää, joten tietue joka tarjoaisi ne houkuttelisi kutsujan kirjoittamaan vaatimustenvastaisen tiedoston vaatimukset täyttävän rajapinnan läpi

Kaksi totuusarvoista asetusta hoitaa siivouksen, kun olemassa oleva PDF muunnetaan. StripInfoOptionalEntries on oletuksena True ja poistaa lähteen Info-sanakirjasta Titlen, Authorin, Subjectin, Keywordsin ja Trappedin. StripCatalogOptionalEntries on niin ikään oletuksena True ja poistaa Namesin, Outlinesin, StructTreeRootin, OutputIntentsin, Langin ja loput, jolloin jäljelle jää vain §6.3:n sallittujen lista. Jos kumman tahansa asettaa Falseksi, merkinnät jäävät — ja vaatimustenmukaisuus menee, mikä on silloin tällöin juuri se mitä kutsuja sisäiseltä tiedostolta haluaakin

var
  Opts: TPdfRSaveOptions;
  Src, Dest: TFileStream;
begin
  Opts := TPdfRSaveOptions.Default;
  Opts.Creator := 'Capture Station 4';
  Opts.Producer := 'PDFium Component';
  Src := TFileStream.Create('scan-in.pdf', fmOpenRead or fmShareDenyWrite);
  try
    Dest := TFileStream.Create('scan-pdfr.pdf', fmCreate);
    try
      InjectPdfRMarkers(Src, Dest, Opts);   // merkinnät ja metatiedot, ei sivun sisältöä
    finally
      Dest.Free;
    end;
  finally
    Src.Free;
  end;
end;

Huomaa mitä merkintöjen injektointi ei tee: se lisää metatiedot ja tunnisteet, mutta sivun sisältöä se ei voi loihtia. Lähdesivu jolla ei ole kaistakuvaa kaatuu injektion jälkeenkin virheeseen pvriPageImageMismatch, koska puuttuva kuva ei ollut koskaan metatieto-ongelma

Mihin PDF/R sopii kaappausputkessa

Käytä sitä silloin kun toimitettava tuote on itse skannaus ja uskollisuus on koko sopimus — todistusaineiston kuvantaminen, shekkien ja maksusuoritusten kaappaus, suurkuvaskannerilla tehdyt insinööripiirustusten arkistot. Ota PDF/A käyttöön heti kun asiakirja tarvitsee haettavaa tekstiä, tagitusta, upotettuja liitteitä tai mitä tahansa muuta minkä rasteriprofiili riisuu pois

Yleinen ja toimiva järjestely on tuottaa molemmat: PDF/R-alkuperäinen joka ei muutu koskaan, ja PDF/A-johdannainen OCR-kerroksineen hakua varten. Validaattorit ovat toisistaan riippumattomia, joten sama eräajo voi tarkistaa kummankin artefaktin sitä profiilia vasten jota se oikeasti väittää noudattavansa. Parin arkistopuolelta kannattaa lukea muistiinpanot aiheista PDF/A-arkistointiyhteensopivuus ja PDF/A-esitarkistus, ja tulostuspuolelta läpikäynti aiheesta painovalmiiden PDF/X-asiakirjojen validointi

PDFium Component tuo PDFium-moottorin Delphille, C++Builderille ja Lazarukselle VCL-rajapinnan sekä PDF/A-, PDF/X-, PDF/E-, PDF/UA- ja PDF/R-validaattoreiden kanssa — PDFium Component -tuotesivulta löytyvät tuetut standardit ja IDE-versiot