概要
- PDFパーサー の理想と現実の違いを解説
- PDF仕様 に基づく解析手順の流れを説明
- 実際のファイルで発生する よくある問題点 を具体例で紹介
- 現実のPDF解析 で遭遇するエラーや非準拠ケースの多様性を指摘
- 主要なPDFビューアの多くは 非準拠PDF にも対応している事実を強調
PDFパーサーの理想的な解析手順
- ファイル先頭の バージョンヘッダーコメント 検出
- クロスリファレンステーブル(xref) へのポインタ検出
- 全オブジェクトのバイトオフセット 一覧取得
- トレーラーディクショナリ の構築とカタログディクショナリへの参照取得
- これらの手順により PDFの構造解析 が可能となる
PDFオブジェクトの基礎
- PDFオブジェクト は有効なPDF内容(数値、文字列、辞書など)をobj/endobjで囲む
- 例:
16 0 obj 620 endobjは16番目のオブジェクト(世代0)に数値620を格納 - オブジェクト間参照 は「16 0 R」のような間接参照形式
- 仕様上、特定の型のオブジェクトは 間接参照 である必要
- オブジェクトの分割方法 は生成アプリケーション次第
クロスリファレンステーブル(xref)の探索
- xrefテーブル は各オブジェクトの位置を示すインデックス
- ファイル末尾に
startxrefとオフセット、%%EOFマーカー - 仕様では ファイルの末尾 に
%%EOFがあるべき - 実際は 1024バイト以内 や異なる位置、スペルミス(startref)など多様
- xrefオフセット の検出が困難な場合も多い
オブジェクトオフセットの取得
- 指定オフセットに 整形済みxrefテーブル が存在するはず
- 例:
xref 7 4 ...は7番から4つのオブジェクト情報 - 各行に バイトオフセット・世代番号・状態(n:使用中, f:未使用)
- 複数のxrefテーブルやストリーム が/Prevで連結される場合あり
トレーラーディクショナリの特定
- startxref直前 にトレーラーディクショナリ
- ルートオブジェクト など重要メタデータを格納
- 参照をたどって PDF全体の内容解釈 が可能
現実世界のPDF解析の困難
- 仕様通りの パーサー では多くの実ファイルで失敗
- 非準拠ファイル の多発、仕様は「社会的構築物」「雰囲気」
- パーサーの堅牢性 が求められる理由
xrefポインタ探索の現実的な課題
- ファイル末尾や1024バイト以内 にポインタがない
- スペルミス や予想外のフォーマット
- ポインタが 嘘 の場合も多い
- サンプル調査で 0.5%のファイル が不正なxref宣言
PDF内容が非ゼロオフセットで開始
- バージョンヘッダー前にゴミデータ があると全オフセットがずれる
- 例: 先頭10バイトのゴミでstartxrefが10バイトずれる
- バージョンヘッダーの実オフセット も考慮して両方を試す必要
- サンプルエラーの 約50% がこの問題
xrefテーブル内へのポインタ
- ポインタが xrefテーブル中間 を指すケース
- サンプルで約5件発生
xref付近の「ほぼ正しい」ポインタ
- 1文字分ずれている、endobjマーカー内など微妙な位置
- よくある事例
xrefオフセットは正しいがテーブル内容が不正
- テーブル内のオブジェクトオフセット が一部または全部不正
- サブセクションごとに異なるずれ
- /Prevポインタの値が0(デフォルト値を誤って書き込んだ)など
xrefテーブル自体のフォーマット不良
- linebreakなし の
xref5 2 ... - 宣言数より多いエントリ が含まれる
- テーブル途中にゴミデータ が混入
結論と考察
- 仕様通りの解析手順と 現実のエラー例 を比較
- サンプル調査で 0.5%の非準拠ファイル 確認
- PDF.js, Adobe, Sumatraなど 主要ビューア は非準拠ファイルにも対応
- PDF仕様の一部(全1300ページ中22ページ) だけでも多様な困難
- 堅牢なPDFパーサー開発 には多様な実装例への対応が不可欠