8月4日、Eliot Jones氏 が「 So you want to parse a PDF? 」と題したブログ記事を公開し、話題を呼んでいる。この記事では、PDF ファイルをパースするプログラムを書くのがいかに苦痛かについて詳しく紹介されている。以下に、その内容を紹介する。

仕様どおりにいけば「簡単」なはずの PDF 解析
PDF を解析するのは非常に簡単だ。仕様通りならば。
まず、 PDFオブジェクト について知っておく必要がある。
PDFオブジェクトは、有効なPDFコンテンツ、数値、文字列、辞書などをオブジェクトと世代番号で囲んだものだ。コンテンツは obj/endobj
マーカーで囲まれており、例えば単純な数値でも独自のPDFオブジェクトを持つ場合がある。
16 0 obj
620
endobj
これは、世代 0 のオブジェクト 16 に数値 620 が含まれていることを示す。こうしたオブジェクトが相互参照し合う形で、PDFファイル全体を構成している。
ファイルコンテンツをオブジェクトに分割するかどうかはアプリケーションが自由に決定できるが、仕様では特定のオブジェクトタイプは間接参照であることが求められている。
この知識を踏まえたうえで、実際にPDFを解析するフローは以下のようになる。
- まず、ファイルの先頭にあるバージョンヘッダーコメントを見つける
- 次に、相互参照へのポインタを見つける
- すると、すべてのオブジェクトのオフセットが見つかる
- 最後に、カタログ辞書を指すトレーラー辞書が見つかる
以下に、それぞれの手順を詳しく見ていこう。
バージョンヘッダーの検出
ファイル先頭付近にある%PDF-1.x
コメントを特定し、使用バージョンを確認する。相互参照ポインタ取得
ファイル末尾のstartxref 116 %%EOF
に代表される構造を読み取り、
116
バイト目にあると宣言された xref へジャンプする。オブジェクトオフセットの列挙
xref テーブルを読み込み、各オブジェクト番号とオフセット、世代番号、使用状態(n
またはf
)を得る。
例:xref 7 4 0000000000 65535 f 0000109882 00000 n 0000109933 00000 n 0000140066 00000 n
トレーラ辞書の読み取り
startxref
の直前にある<< ... >>
を解析し、/Root
からカタログ辞書へ辿っていく。
この「仕様上の図式」を追えば、PDF はオブジェクト同士が間接参照で繋がったグラフ構造として捉えられ、解析は滑らかに進む――はずだ。
地獄へ:現実の PDF は仕様を裏切る
しかし現実のPDFは一筋縄ではいかない。
1. PDF コンテンツが非ゼロオフセットで始まる
ヘッダーの前にゴミデータが付くと、 すべてのオフセットがずれる。
ten bytes!%PDF-1.4 …
startxref
960
%%EOF
実際は 970 バイト目が正解というケースが最多で、解析に失敗したケースの約半数を占めた。
2. ポインタが xref の「途中」を指す
startxref
が存在しても、ジャンプ先がテーブル行中という場合も多々ある。1 バイトずれ、改行文字ずれなど、原因は多様だ。
3. xref は正しいがテーブル内のオフセットが誤り
xref が複数世代ある PDF で /Prev
チェーンが壊れている、または一部行だけ桁数が欠けている、といった事例もある。
4. テーブル自体の書式不良
xref
の直後に改行がない- 見出しで宣言した件数より多い行が続く
- 行中に数字以外のゴミ文字が混入する
……など、PDF-1.7 仕様(約 1,300 ページ)の 22 ページ分に相当する xref 節だけでも多様な「脱線」が存在した。
実務で役立つ示唆
- エンジニアは仕様を鵜呑みにしない覚悟が必要
解析失敗時に「相手が悪い」で済ませると、ユーザはあなたのツールが壊れていると判断する。 - ポインタの信頼性を 2 段階で検証せよ
まず宣言値にジャンプ→失敗なら PDF ヘッダーの先頭オフセットを加算――というフォールバックが有効。 - xref が読めても安心するな
複数世代の/Prev
、不揃いなサブセクション、欠損行などを想定したバリデーションが不可欠だ。
まとめ
PDF は「仕様どおりにいけば簡単」だが、実際には仕様から逸脱したファイルが少なくない。
記事の著者は言う。
PDF isn't a specification, it's a social construct, it's a vibe. The more you struggle the deeper you sink.
PDFは仕様ではなく、社会的な概念であり、雰囲気である。もがけばもがくほど、さらに深く沈んでいく
詳細はSo you want to parse a PDF?を参照していただきたい。