Articolo tecnico

Validare documenti scansionati PDF/raster in Delphi

PDF/R, standardizzato come ISO 23504-1, è il profilo PDF per i documenti scansionati: ogni pagina porta esattamente una strip image e null'altro. PDFium Component lo convalida da Delphi, Lazarus e C++Builder attraverso ValidatePdfRCompliance, che legge uno stream e restituisce il livello di conformità più un insieme di problemi concreti

Il profilo esiste perché scanner e sistemi di acquisizione documentale avevano bisogno di un target più ristretto di PDF/A. Un PDF archivistico può contenere qualsiasi cosa la parte permette; un PDF raster è deliberatamente impoverito, così qualsiasi lettore conforme può visualizzarlo in modo identico e qualsiasi writer conforme può produrlo da una scansione senza un motore di authoring

Cosa vieta PDF/R che PDF/A permette?

Il testo, in pratica. Una pagina raster porta l'immagine scansionata e null'altro, così una risorsa font su una pagina è una violazione — riportata come pvriFontForbidden sotto ISO 23504-1 §6.5.2. Questo sorprende chi aggiunge un layer di testo OCR invisibile per ricercabilità, che è una cosa normale e utile in un flusso PDF/A e semplicemente non è PDF/R

La relazione pagina-immagine è altrettanto rigida. §6.5.1 fa di ogni pagina esattamente una strip image, così pvriPageImageMismatch scatta quando il conteggio immagini non corrisponde al conteggio pagine — una pagina senza immagine e una pagina con due sono entrambe non conformi. E pvriBadMediaBox riporta una pagina la cui MediaBox non ha la forma [0 0 w h] (§6.5.3), perché una scansione non ha motivo di stare a un offset di origine

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;

Quali codifiche di immagine sono ammesse

Quattro, e la white list è breve per un motivo. §6.6 ammette /CCITTFaxDecode, /DCTDecode, /JPXDecode e /FlateDecode — fax bilevel, JPEG, JPEG 2000 e deflate lossless, che tra loro coprono ogni output di scanner che conta. Tutto il resto viene riportato come pvriForbiddenImageFilter, inclusi /LZWDecode, /RunLengthDecode, /ASCII85Decode, /ASCIIHexDecode, /JBIG2Decode e /Crypt

Due di quei rifiuti valgono la pena di capire anziché memorizzare. /JBIG2Decode comprime le scansioni bilevel estremamente bene ed è perfettamente legale in PDF/A, ma la sua ricostruzione del dizionario di simboli può sostituire glifi visivamente simili — una modalità di fallimento documentata per cifre scansionate — e un profilo il cui scopo complessivo è la riproduzione raster fedele non può ammettere quel rischio. I filtri ASCII sono esclusi per il motivo opposto: gonfiano il file senza aggiungere nulla di cui un profilo raster abbia bisogno

Regole di struttura che scattano prima che qualsiasi pagina sia letta

PDF/R vincola anche il contenitore. pvriObjStmPresent riporta uno stream /Type /ObjStm, che il profilo vieta del tutto — gli object stream complicano il parsing semplice e sequenziale che un lettore raster dovrebbe essere in grado di eseguire. pvriBadHeader riporta un header fuori da %PDF-1.4 a 1.7 e %PDF-2.0, e pvriEncryptVersionMismatch riporta un file cifrato il cui header non è %PDF-2.0, secondo §6.2.3

Il dizionario Catalog e Info sono white-listati, non solo controllati. pvriProhibitedCatalogEntry e pvriProhibitedInfoEntry scattano per voci fuori dall'insieme permesso, e pvriInfoXmpMismatch scatta quando una voce Info non concorda con il proprio equivalente XMP. Uno stream /Metadata mancante nel Catalog, un /ID mancante nel trailer e un footer marker %PDF-raster-1.0 assente hanno ciascuno il proprio problema specifico

Perché il record delle opzioni di salvataggio omette Title e Author

TPdfRSaveOptions porta Creator, Producer, CreationDate, ModDate, DocumentId e InstanceId, e deliberatamente non ha campo per Title, Author, Subject o Keywords. Quei quattro sono le voci che §6.4.3 vieta, così un record che le esponesse inviterebbe i chiamanti a scrivere un file non conforme attraverso un'API conforme

Due opzioni booleane controllano la pulizia quando si converte un PDF esistente. StripInfoOptionalEntries vale predefinito True e rimuove Title, Author, Subject, Keywords e Trapped dal dizionario Info sorgente. StripCatalogOptionalEntries vale pure predefinito True e rimuove Names, Outlines, StructTreeRoot, OutputIntents, Lang e il resto, lasciando solo la white list di §6.3. Imposta uno dei due a False e tieni le voci — e perdi la conformità, il che è occasionalmente ciò che un chiamante vuole davvero per un file interno

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);   // markers + metadata, not page content
    finally
      Dest.Free;
    end;
  finally
    Src.Free;
  end;
end;

Nota cosa non fa l'iniezione di marker: aggiunge metadati e identificazione, e non può fornire contenuto di pagina. Una pagina sorgente che non porta alcuna strip image fallirà ancora pvriPageImageMismatch dopo l'iniezione, perché l'immagine mancante non è mai stata un problema di metadati

Dove si incastra PDF/R in una pipeline di acquisizione

Usalo dove il deliverable è la scansione stessa e la fedeltà è tutto il contratto — imaging probatorio, acquisizione di assegni e rimesse, archivi di disegni tecnici da scanner di grande formato. Usa invece PDF/A nel momento in cui il documento ha bisogno di testo ricercabile, tagging, allegati incorporati o qualsiasi altra cosa il profilo raster elimini

Un assetto comune e praticabile è produrre entrambi: un originale PDF/R che non cambia mai, e un derivato PDF/A con un layer OCR per il reperimento. I validatori sono indipendenti, così lo stesso job batch può controllare ciascun artefatto rispetto al profilo che rivendica davvero. Per il lato archivistico di quella coppia, vedi le note sulla conformità archivistica PDF/A e sulla validazione preflight PDF/A, e per output orientato alla stampa la guida sulla validazione di documenti PDF/X pronti per la stampa

PDFium Component porta il motore PDFium in Delphi, C++Builder e Lazarus con un'API VCL e validatori di conformità per PDF/A, PDF/X, PDF/E, PDF/UA e PDF/R — la pagina prodotto PDFium Component elenca gli standard supportati e le versioni di IDE