技術記事

Delphiでスキャン文書のPDF/rasterを検証する方法

PDF/RはISO 23504-1として標準化され、スキャン文書向けのPDFプロファイルだ。すべてのページはただ1つのストリップ画像を持ち、それ以外は何も持たない。PDFium ComponentはValidatePdfRCompliance経由で、Delphi、Lazarus、C++Builderからこれを検証する。この関数はストリームを読み、適合レベルと具体的な問題のセットを返す

このプロファイルが存在するのは、スキャナーとドキュメントキャプチャシステムが、PDF/Aより狭いターゲットを必要としたからだ。アーカイブPDFはパートが許すものなら何でも含めることができるが、ラスターPDFは意図的に貧しくされる。これにより、適合するリーダーは誰でも同一に表示でき、適合するライターはオーサリングエンジンなしでスキャンから生成できる

PDF/RはPDF/Aが許す何を禁じるのか

実地には、テキストだ。ラスターページはスキャン画像だけを運び、それ以外は何も運ばない。だからページ上のフォントリソースは違反であり、ISO 23504-1 §6.5.2の下でpvriFontForbiddenとして報告される。これは、検索可能性のために不可視のOCRテキストレイヤーを追加する人を驚かせる。それはPDF/Aワークフローでは普通の有用なことだが、PDF/Rではない

ページと画像の関係も同じく厳しい。§6.5.1はすべてのページをちょうど1つのストリップ画像にするので、画像数がページ数と一致しないときpvriPageImageMismatchが発火する。画像のないページも、画像2つを持つページも、どちらも不適合だ。そしてpvriBadMediaBoxは、MediaBoxが[0 0 w h]の形を持たないページを報告する(§6.5.3)。スキャンがオフセット原点に置かれる理由はないからだ

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;

どの画像エンコーディングが許されるか

4つであり、ホワイトリストは理由あって短い。§6.6は/CCITTFaxDecode/DCTDecode/JPXDecode/FlateDecodeを認める。バイレベルファクス、JPEG、JPEG 2000、ロスレスdeflateであり、これらで意味のあるあらゆるスキャナー出力をカバーする。それ以外はすべてpvriForbiddenImageFilterとして報告される。/LZWDecode/RunLengthDecode/ASCII85Decode/ASCIIHexDecode/JBIG2Decode/Cryptを含む

このうち2つの拒否は、暗記ではなく理解する価値がある。/JBIG2Decodeはバイレベルスキャンを極めてよく圧縮し、PDF/Aでは完全に合法だが、その辞書再構築は視覚的に似たグリフを置き換えることができる。スキャンされた数字の文書化された失敗モードであり、忠実なラスター再現を全体の目的とするプロファイルは、このリスクを認められない。ASCIIフィルターは逆の理由で除外される。ラスタープロファイルが必要とするものを何も追加せずにファイルを膨らませるからだ

ページが読まれる前に発火する構造ルール

PDF/Rはコンテナも制約する。pvriObjStmPresent/Type /ObjStmストリームを報告する。プロファイルはこれを全面的に禁じる。オブジェクトストリームは、ラスターリーダーが実行できるはずのシンプルで順次のパースを複雑にするからだ。pvriBadHeaderは%PDF-1.4から1.7および%PDF-2.0の外のヘッダを報告し、pvriEncryptVersionMismatchは§6.2.3に従い、ヘッダが%PDF-2.0でない暗号化ファイルを報告する

カタログとInfo辞書は、単にチェックされるのではなくホワイトリストされる。pvriProhibitedCatalogEntrypvriProhibitedInfoEntryは許可されたセットの外のエントリで発火し、pvriInfoXmpMismatchはInfoエントリが対応するXMPと一致しないときに発火する。欠落したカタログの/Metadataストリーム、欠落したトレーラーの/ID、不在の%PDF-raster-1.0フッタマーカーも、それぞれ独自の問題を持つ

なぜ保存オプションレコードがTitleとAuthorを省くのか

TPdfRSaveOptionsCreatorProducerCreationDateModDateDocumentIdInstanceIdを運び、意図的にTitle、Author、Subject、Keywordsのフィールドを持たない。これら4つは§6.4.3が禁じるエントリであり、これらを公開するレコードは、適合するAPIを通じて不適合なファイルを書くよう呼び出し側を誘うものになる

2つのブールオプションが、既存のPDFを変換するときのクリーンアップを制御する。StripInfoOptionalEntriesはデフォルトでTrueで、ソースInfo辞書からTitle、Author、Subject、Keywords、Trappedを削除する。StripCatalogOptionalEntriesもデフォルトでTrueで、Names、Outlines、StructTreeRoot、OutputIntents、Langなどを削除し、§6.3のホワイトリストだけを残す。どちらかをFalseにすればエントリを保持できるが、適合性を失う。それが時折、呼び出し側が社内ファイルのために本当に求めるものである

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;

マーカー注入が何をしないかをメモしておくこと。メタデータと識別を追加し、ページコンテンツを供給することはできない。ストリップ画像を持たないソースページは、注入後もpvriPageImageMismatchで落ちる。不在の画像はメタデータの問題では決してなかったからだ

キャプチャパイプラインでPDF/Rが当てはまる場所

成果物がスキャンそのもので、忠実さが契約のすべてである場面で使うこと。証拠画像、小切手と送金のキャプチャ、大判スキャナーからのエンジニアリング図面アーカイブなどだ。ドキュメントが検索可能なテキスト、タグ付け、埋め込み添付ファイル、ラスタープロファイルが剥ぎ取るその他何かを必要とする瞬間に、代わりにPDF/Aを使う

よくある扱いやすい配置は、両方を生成することだ。決して変わらないPDF/Rのオリジナルと、検索のためにOCRレイヤーを持つPDF/Aの派生物。バリデータは独立しているので、同じバッチジョブが、それぞれの成果物をそれが実際に主張するプロファイルに対してチェックできる。そのペアのアーカイブ側については、PDF/AアーカイブコンプライアンスPDF/Aプレフライト検証のノートを参照のこと。印刷向け出力については、印刷向けPDF/Xドキュメントの検証の解説を参照のこと

PDFium ComponentはPDFiumエンジンをDelphi、C++Builder、Lazarusのために持ち込み、VCL APIとPDF/A、PDF/X、PDF/E、PDF/UA、PDF/Rの適合バリデータを提供する。PDFium Component製品ページにサポートされる標準とIDEバージョンの一覧がある