Technischer Artikel

PDF/Raster-gescannte Dokumente in Delphi validieren

PDF/R, standardisiert als ISO 23504-1, ist das PDF-Profil für gescannte Dokumente: Jede Seite trägt exakt ein Streifenbild und nichts sonst. PDFium Component validiert es aus Delphi, Lazarus und C++Builder über ValidatePdfRCompliance, das einen Stream liest und das Konformitäts-Level plus eine Menge konkreter Issues zurückgibt

Das Profil existiert, weil Scanner und Dokument-Erfassungssysteme ein Ziel brauchten, das enger ist als PDF/A. Ein Archiv-PDF darf alles enthalten, was der Part zulässt; ein Raster-PDF ist bewusst verarmt, sodass jeder konforme Reader es identisch anzeigen und jeder konforme Writer es aus einem Scan ohne Autoren-Engine erzeugen kann

Was verbietet PDF/R, das PDF/A erlaubt?

In der Praxis Text. Eine Raster-Seite trägt das gescannte Bild und nichts sonst, sodass eine Schrift-Ressource auf einer Seite ein Verstoß ist — gemeldet als pvriFontForbidden unter ISO 23504-1 §6.5.2. Das überrascht Leute, die eine unsichtbare OCR-Textschicht für Durchsuchbarkeit hinzufügen, was in einem PDF/A-Workflow eine normale und nützliche Sache ist und einfach kein PDF/R ist

Die Seite-zu-Bild-Beziehung ist ebenso streng. §6.5.1 macht jede Seite zu exakt einem Streifenbild, sodass pvriPageImageMismatch feuert, wenn die Bildanzahl nicht zur Seitenanzahl passt — eine Seite ohne Bild und eine Seite mit zwei sind beide nicht-konform. Und pvriBadMediaBox meldet eine Seite, deren MediaBox nicht die Form [0 0 w h] hat (§6.5.3), denn ein Scan hat keinen Grund, an einem verschobenen Ursprung zu sitzen

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;

Welche Bild-Enkodierungen erlaubt sind

Vier, und die White-List ist aus gutem Grund kurz. §6.6 lässt /CCITTFaxDecode, /DCTDecode, /JPXDecode und /FlateDecode zu — bi-level Fax, JPEG, JPEG 2000 und verlustfreies Deflate, die zusammen jede Scanner-Ausgabe abdecken, die zählt. Alles andere wird als pvriForbiddenImageFilter gemeldet, einschließlich /LZWDecode, /RunLengthDecode, /ASCII85Decode, /ASCIIHexDecode, /JBIG2Decode und /Crypt

Zwei dieser Zurückweisungen lohnt sich zu verstehen statt auswendig zu lernen. /JBIG2Decode komprimiert bi-level Scans extrem gut und ist in PDF/A völlig legal, doch seine Symbol-Wörterbuch-Rekonstruktion kann visuell ähnliche Glyphen substituieren — ein dokumentierter Fehlermodus für gescannte Ziffern — und ein Profil, dessen ganzer Sinn treue Raster-Wiedergabe ist, kann dieses Risiko nicht aufnehmen. Die ASCII-Filter sind aus dem entgegengesetzten Grund ausgeschlossen: Sie blähen die Datei auf, ohne etwas hinzuzufügen, das ein Raster-Profil braucht

Struktur-Regeln, die feuern, bevor eine Seite gelesen wird

PDF/R schränkt auch den Container ein. pvriObjStmPresent meldet einen /Type /ObjStm-Stream, den das Profil rundheraus verbietet — Objekt-Streams komplizieren das einfache, sequenzielle Parsen, das ein Raster-Reader durchführen können soll. pvriBadHeader meldet einen Header außerhalb %PDF-1.4 bis 1.7 und %PDF-2.0, und pvriEncryptVersionMismatch meldet eine verschlüsselte Datei, deren Header nicht %PDF-2.0 ist, gemäß §6.2.3

Das Catalog- und das Info-Dictionary werden nicht nur geprüft, sondern gewhitelistet. pvriProhibitedCatalogEntry und pvriProhibitedInfoEntry feuern für Einträge außerhalb der zulässigen Menge, und pvriInfoXmpMismatch feuert, wenn ein Info-Eintrag von seinem XMP-Äquivalent abweicht. Ein fehlender Catalog-/Metadata-Stream, ein fehlender Trailer-/ID und ein abwesender %PDF-raster-1.0-Footer-Marker haben jeweils ihr eigenes Issue

Warum der Save-Options-Datensatz Title und Author auslässt

TPdfRSaveOptions trägt Creator, Producer, CreationDate, ModDate, DocumentId und InstanceId und hat bewusst kein Feld für Title, Author, Subject oder Keywords. Diese vier sind die Einträge, die §6.4.3 verbietet, also würde ein Datensatz, der sie freilegte, Aufrufer einladen, eine nicht-konforme Datei über eine konforme API zu schreiben

Zwei boolesche Optionen steuern die Bereinigung beim Konvertieren eines bestehenden PDF. StripInfoOptionalEntries ist standardmäßig wahr und entfernt Title, Author, Subject, Keywords und Trapped aus dem Quell-Info-Dictionary. StripCatalogOptionalEntries ist ebenfalls standardmäßig wahr und entfernt Names, Outlines, StructTreeRoot, OutputIntents, Lang und den Rest und lässt nur die White-List aus §6.3 stehen. Beide auf False setzen und man behält die Einträge — und verliert die Konformität, was gelegentlich das ist, was ein Aufrufer für eine interne Datei genuine will

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;

Beachten, was Marker-Injection nicht tut: Sie fügt Metadaten und Identifikation hinzu und kann keinen Seiten-Inhalt liefern. Eine Quell-Seite, die kein Streifenbild trägt, wird nach der Injektion weiterhin an pvriPageImageMismatch scheitern, denn das fehlende Bild war nie ein Metadaten-Problem

Wo PDF/R in eine Erfassungs-Pipeline passt

Verwenden, wo das Lieferbare der Scan selbst ist und die Treue der ganze Vertrag ist — beweissichere Bildgebung, Scheck- und Zahlungserfassung, Konstruktionszeichnungs-Archive von einem Großformat-Scanner. Stattdessen PDF/A verwenden, sobald das Dokument durchsuchbaren Text, Tagging, eingebettete Anhänge oder irgendetwas anderes braucht, was das Raster-Profil abstreift

Eine verbreitete und praktikable Anordnung ist, beides zu erzeugen: ein PDF/R-Original, das sich nie ändert, und ein PDF/A-Derivat mit einer OCR-Schicht für den Abruf. Die Validatoren sind unabhängig, sodass derselbe Batch-Auftrag jedes Artefakt gegen das Profil prüfen kann, das es tatsächlich beansprucht. Für die Archiv-Seite dieses Paars siehe die Hinweise zur PDF/A-Archiv-Konformität und zur PDF/A-Preflight-Validierung, und für druckorientierte Ausgaben die Erläuterung zum Validieren druckfertiger PDF/X-Dokumente

PDFium Component bringt die PDFium-Engine zu Delphi, C++Builder und Lazarus mit einer VCL-API und Konformitäts-Validatoren für PDF/A, PDF/X, PDF/E, PDF/UA und PDF/R — die PDFium-Component-Produktseite listet die unterstützten Standards und IDE-Versionen