Artículo técnico

Validar documentos escaneados PDF/raster en Delphi

PDF/R, estandarizado como ISO 23504-1, es el perfil PDF para documentos escaneados: cada página lleva exactamente una imagen de tira y nada más. PDFium Component lo valida desde Delphi, Lazarus y C++Builder a través de ValidatePdfRCompliance, que lee un flujo y devuelve el nivel de conformancia más un conjunto de hallazgos concretos

El perfil existe porque los escáneres y los sistemas de captura de documentos necesitaban un destino más estrecho que PDF/A. Un PDF de archivo puede contener cualquier cosa que la parte permita; un PDF raster es deliberadamente pobre, así que cualquier lector conforme puede mostrarlo de forma idéntica y cualquier escritor conforme puede producirlo a partir de un escaneo sin un motor de creación

¿Qué prohíbe PDF/R que PDF/A permite?

Texto, en la práctica. Una página raster lleva la imagen escaneada y nada más, así que un recurso de fuente en una página es una violación — reportada como pvriFontForbidden bajo ISO 23504-1 §6.5.2. Eso sorprende a quienes agregan una capa de texto OCR invisible para hacer posible la búsqueda, que es algo normal y útil en un flujo PDF/A y simplemente no es PDF/R

La relación página-imagen es igual de estricta. §6.5.1 hace que cada página sea exactamente una imagen de tira, así que pvriPageImageMismatch se dispara cuando el conteo de imágenes no coincide con el de páginas — una página sin imagen y una página con dos son ambas no conformes. Y pvriBadMediaBox reporta una página cuyo MediaBox no tiene la forma [0 0 w h] (§6.5.3), porque un escaneo no tiene razón para sentarse en un origen desplazado

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;

Qué codificaciones de imagen se permiten

Cuatro, y la lista blanca es corta por una razón. §6.6 admite /CCITTFaxDecode, /DCTDecode, /JPXDecode y /FlateDecode — fax bilevel, JPEG, JPEG 2000 y deflate sin pérdida, que entre todos cubren toda salida de escáner que importe. Todo lo demás se reporta como pvriForbiddenImageFilter, incluyendo /LZWDecode, /RunLengthDecode, /ASCII85Decode, /ASCIIHexDecode, /JBIG2Decode y /Crypt

Dos de esos rechazos valen la pena entenderlos en vez de memorizarlos. /JBIG2Decode comprime los escaneos bilevel extremadamente bien y es perfectamente legal en PDF/A, pero su reconstrucción de diccionario de símbolos puede sustituir glifos visualmente similares — un modo de fallo documentado para dígitos escaneados — y un perfil cuyo propósito entero es la reproducción raster fiel no puede admitir ese riesgo. Los filtros ASCII se excluyen por la razón opuesta: inflan el archivo sin añadir nada que un perfil raster necesite

Reglas de estructura que se disparan antes de leer cualquier página

PDF/R también constriñe el contenedor. pvriObjStmPresent reporta un flujo /Type /ObjStm, que el perfil prohíbe sin más — los flujos de objetos complican el análisis simple y secuencial que un lector raster debe poder realizar. pvriBadHeader reporta un encabezado fuera de %PDF-1.4 a 1.7 y %PDF-2.0, y pvriEncryptVersionMismatch reporta un archivo cifrado cuyo encabezado no es %PDF-2.0, según §6.2.3

El catálogo y el diccionario Info están en lista blanca, no simplemente verificados. pvriProhibitedCatalogEntry y pvriProhibitedInfoEntry se disparan por entradas fuera del conjunto permitido, y pvriInfoXmpMismatch se dispara cuando una entrada Info contradice a su equivalente XMP. Un flujo /Metadata ausente en el catálogo, un /ID ausente en el trailer y un marcador de pie %PDF-raster-1.0 ausente tienen cada uno su propio hallazgo también

Por qué el registro de opciones de guardado omite Title y Author

TPdfRSaveOptions lleva Creator, Producer, CreationDate, ModDate, DocumentId e InstanceId, y deliberadamente no tiene campo para Title, Author, Subject o Keywords. Esas cuatro son las entradas que §6.4.3 prohíbe, así que un registro que las expusiera estaría invitando a quienes llaman a escribir un archivo no conforme a través de una API conforme

Dos opciones booleanas controlan la limpieza al convertir un PDF existente. StripInfoOptionalEntries por defecto es True y elimina Title, Author, Subject, Keywords y Trapped del diccionario Info de origen. StripCatalogOptionalEntries también por defecto es True y elimina Names, Outlines, StructTreeRoot, OutputIntents, Lang y el resto, dejando solo la lista blanca de §6.3. Pon cualquiera en False y conservas las entradas — y pierdes la conformancia, que ocasionalmente es justamente lo que un llamador quiere para un archivo 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;

Ten en cuenta lo que la inyección de marcadores no hace: agrega metadatos e identificación, y no puede suministrar contenido de página. Una página de origen que no lleva imagen de tira seguirá fallando pvriPageImageMismatch tras la inyección, porque la imagen faltante nunca fue un problema de metadatos

Dónde encaja PDF/R en un flujo de captura

Úsalo donde el entregable es el escaneo mismo y la fidelidad es el contrato entero — imágenes con valor probatorio, captura de cheques y de pagos, archivos de planos de ingeniería desde un escáner de gran formato. Usa PDF/A en cuanto el documento necesite texto buscable, etiquetado, adjuntos incrustados o cualquier otra cosa que el perfil raster elimine

Un arreglo común y viable es producir ambos: un original PDF/R que nunca cambia, y un derivado PDF/A con una capa OCR para recuperación. Los validadores son independientes, así que el mismo trabajo por lotes puede verificar cada artefacto contra el perfil que realmente afirma. Para el lado de archivo de esa pareja, consulta las notas sobre el cumplimiento de archivo PDF/A y la validación de preflight PDF/A, y para la salida orientada a impresión el recorrido por la validación de documentos PDF/X listos para impresión

PDFium Component lleva el motor PDFium a Delphi, C++Builder y Lazarus con una API VCL y validadores de conformancia para PDF/A, PDF/X, PDF/E, PDF/UA y PDF/R — la página del producto PDFium Component lista las normas y versiones de IDE soportadas