コンテンツにスキップ
getnextpdf.com

PDF ファイルの解剖学

どんな PDF でもプレーンテキストエディタで開くと、最初に目に入るのは安心できるものです。%PDF-1.x または %PDF-2.0 というヘッダーです。最後に目に入るのは %%EOF です。この 2 行の間にあるものはすべて、オブジェクトを番号で見つけるための小さく整然とした機械です。このページは解剖です。私たちはファイルを開き、それぞれの臓器に名前を付け、それらがどうつながっているかを示します。

これは 2 つの隣人に対する構造面の対です。PDF とは実際に何かはファイルをオブジェクトグラフとして扱い、インクリメンタル更新はそれが時とともにどう成長するかを扱います。このページはバイトの近くに留まります — パーサーが横断していく物理的な領域を、それらがディスク上に並ぶ順序で見ていきます。

PDF を 使う ためにこれが必要になることは、ほぼありません。必要になるのは、ある PDF がおかしくなった日です。あるビューアでは開くが別のビューアでは開かない。バリデータが「破損したクロスリファレンステーブル」を報告する。署名済みの文書が突然、検証に失敗する。これらはどれも、解剖学を読めるようになれば謎ではありません。それらは、もはや位置と一致しない番号、間違った場所にある領域、あるいは何も指していない末尾です。

レイアウトを知ることは、「PDF が壊れている」を、対処できる診断へと変えます。それは、ブラックボックスを前に肩をすくめることと、嘘をついている正確なバイトを指し示すことの違いです。

準拠した PDF には、このファイル順序で 4 つの物理パートがあります(Spec: ISO 32000-2, §7.5.1)。

  1. ヘッダー — 1 行、%PDF-2.0、バージョンを表す。
  2. ボディ — ファイルの大部分。番号付きの 間接オブジェクト の連なり。
  3. クロスリファレンスセクション — オブジェクト番号から、そのオブジェクトが存在するバイトオフセットへのインデックス。古典的な PDF はテキストの テーブル を使い、PDF 2.0 は圧縮された xref ストリーム を使う。
  4. トレーラー — エントリポイントを表す小さなディクショナリに続いて、startxref、オフセット、そして %%EOF

ひねりはこうです。リーダーは先頭から始めません。末尾 から始め、startxref を読んでインデックスを見つけ、そのインデックスを使って任意のオブジェクトに直接到達します。ファイルは前から後ろへ書かれますが、後ろから前へ読まれます。

4 つの領域を順に、バイトを目の前にして歩いていきましょう。

ヘッダー は 1 行です。NextPDF は %PDF-2.0 を書き、慣例として 2 行目に上位ビットのバイトからなるコメント行を書きます。これは、素朴な転送ツールがファイルをテキストではなくバイナリとして扱うようにするためです。その 2 行目こそが、プレーンテキストとして開かれた PDF が、バージョンのすぐ後に小さな文字化けを見せる理由です。

ボディ は文書が存在する場所です。各 間接オブジェクト は、番号、世代、キーワード obj、値、そして endobj です(Spec: ISO 32000-2, §7.3.10)。値はいくつかの形のうちの一つですが、2 つがほぼすべての重みを担います。

  • ディクショナリ<< /Key value … >> は、名前から値へのマップです。ページツリー、カタログ、フォントディスクリプタ — すべてディクショナリです。
  • ストリーム は、ディクショナリに続いて stream、任意のバイトのブロック、そして endstream です。ページコンテンツ、埋め込みフォント、画像はストリームであり、ほぼ常に圧縮されています。(それらのフィルターはそれ自体が一つの物語で、ストリームとフィルターで語られます。)

オブジェクトは 間接参照 によって互いを指します — 2 0 R は「オブジェクト 2、世代 0」を意味します。それが、オブジェクトの平らなリストをグラフへと変える配線です。

クロスリファレンスセクション は、ほとんどの人が正しくイメージできない部分です。古典的な形ではプレーンテキストです。キーワード xref、それから固定幅 20 バイトの行からなるサブセクションです(Spec: ISO 32000-2, §7.5.4)。各行は、10 桁のバイトオフセット、5 桁の世代、そして単一のフラグ — 使用中を表す n、空きを表す f — であり、リーダーが算術だけで任意のエントリにシークできるよう、ちょうど 20 バイトにパディングされています。PDF 2.0 はこれを クロスリファレンスストリーム で置き換えます。同じインデックスですが、/Type /XRef とマークされたストリームオブジェクトの内側にバイナリで圧縮されています(Spec: ISO 32000-2, §7.5.8)。より小さく、オブジェクトストリームの中に詰め込まれたオブジェクトも記述できます。

トレーラー はファイルの目次です(Spec: ISO 32000-2, §7.5.5)。/Root — 文書カタログ、他のすべてがそこからぶら下がる単一のオブジェクト — と、/Size、オブジェクト数を表します。それに続くのが、後ろ向きの読み取りを可能にするハンドシェイクです。startxref、それ自身の行にあるバイトオフセット、そして %%EOF。リーダーは末尾へシークし、そのオフセットを読み、クロスリファレンスセクションへ直接ジャンプして、走り出します。

  1. HeaderOne line, %PDF-2.0, naming the version. A binary-marker comment usually follows.
  2. BodyNumbered indirect objects — dictionaries and streams — referenced by N G R.
  3. Cross-reference sectionA text table of 20-byte entries, or a compressed /Type /XRef stream in PDF 2.0.
  4. TrailerNames /Root and /Size, then startxref + offset + %%EOF.
  5. Read orderA reader starts at %%EOF, follows startxref to the index, then reaches each object directly.
The four physical regions of a PDF in file order, and the path a reader actually takes through them — starting at the trailer and working inward via the cross-reference section.

長寿命のファイルが成長させる第 5 の領域があります。インクリメンタル更新 です。変更はその場で書かれません。変更されたオブジェクト、新しいクロスリファレンスセクション、そして新しいトレーラーが、最初の %%EOF の後に 追記 され、その新しいトレーラーは /Prev — 直前のクロスリファレンスセクションのオフセット — を携えます(Spec: ISO 32000-2, §7.5.6)。セクションは後ろ向きの連鎖を成し、どのオブジェクト番号についても、最も新しいエントリが勝ちます。元のバイトは決して動かないため、署名が実際にカバーするバイト範囲に対する暗号チェックは、更新後も依然として成立します。後のインクリメンタル更新は、文書全体についてバリデータが報告する内容 — 署名後の変更が許されるか、署名が何を証明するものと見なされるか — を依然として変えることができますが、署名済みのバイトそのものを変えることはできません。その性質こそが、インクリメンタル更新の主題そのものです。

ここに、解剖学のすべてが 1 つの最小ファイルにあります。xref の下の数字はバイトオフセットであり、正確でなければなりません — オブジェクトの始まりから 1 文字でも先を指せば、厳格なリーダーは諦めます。

%PDF-2.0
1 0 obj
<< /Type /Catalog /Pages 2 0 R >>
endobj
2 0 obj
<< /Type /Pages /Kids [3 0 R] /Count 1 >>
endobj
3 0 obj
<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] >>
endobj
xref
0 4
0000000000 65535 f
0000000009 00000 n
0000000058 00000 n
0000000115 00000 n
trailer
<< /Size 4 /Root 1 0 R >>
startxref
186
%%EOF

パーサーがするように読みましょう。最後の行は %%EOF。その上は startxref 186、だからバイト 186 へシークします。そこに xref が始まります。テーブルは、オブジェクト 1 がバイト 9 に存在すると告げます。トレーラーの /Root 1 0 R がそこを指します — カタログです — そしてカタログから /Pages を辿ってページツリーへ進み、唯一のページを見つけます。オブジェクト 0 は常に世代 65535 を持つフリーリストの先頭で、フォーマット最初の設計から残る化石であり、いまもすべてのリーダーがそこに在ることを当然視しています。

落とし穴は、PDF を物語のように — 上から下へ、順番に — 読むことです。それは物語ではありません。後ろ向きのポインタを持つインデックスです。オブジェクト番号はファイル内で連続している必要はなく、オブジェクトは任意の物理順序で現れることができ、リーダーがその位置に依存することは決してありません。唯一の 権威ある地図はクロスリファレンスセクションであり、その地図を見つける唯一の方法は、まさに末尾にある startxref オフセットです。

その帰結は人を驚かせます。完璧なボディを持ちながら startxref の数字が 1 つ間違っている PDF は読めません — リーダーがインデックスを見つけられないのです。オブジェクトがばらばらの順序で並んでいても、正しいクロスリファレンスセクションを持つ PDF は完全に問題ありません。物理的な位置は何の意味も担いません。記録された 位置が、その意味のすべてを担います。

このページが説明するのは物理構造であって、ページコンテンツではありません。マークがどうページ上に着地するか — コンテンツストリームの演算子、テキストの表示、グラフィックスステート — は別の主題です。また、これは 整形式 のファイルを説明します。現実世界の PDF はしばしば少し壊れており、寛容なビューアが obj キーワードを走査してクロスリファレンステーブルを再構築するからこそ、かろうじて生き延びています。そのサルベージはビューアの振る舞いであって、フォーマットが保証するものではありません。

Reading and repairing arbitrary third-party PDFs — edition availability
EditionAvailability
CoreNextPDF はライターです。各オブジェクトが送出された瞬間に、出力バッファからすべてのオフセットを記録します。そのため、生成するファイルは、構造上ボディと一致するクロスリファレンスセクションを持ちます。
ProNextPDF が書いていないファイルの、破損したクロスリファレンステーブルをパース・再構築・修復することは、すべてのエディションで対象外です。
Enterprise既存ファイルの構造を検査するには、専用のパーサーまたはバリデータを使ってください。NextPDF は、書くものについては正しさを保証しますが、読むものについては保証しません。

Why does a PDF have a binary-marker line after the header? 一部の古い転送ツールは、プレーンテキストだと思い込んだファイルを壊してしまうことがありました。上位ビットのコメントは、ファイルを紛れもなくバイナリに見せるため、無変更で旅を生き延びます。

Is the xref stream just a smaller table? ほぼそうですが、一つ余分な力があります。圧縮されていることに加えて、xref ストリームはオブジェクトストリームの 内側 に格納されたオブジェクト — 古典的な 20 バイトのテキストテーブルには表現する術がないエントリ — を記述できます。

Can I have both a table and a stream in one file? 単一のリビジョンは、どちらか一方を使います。ただしハイブリッドファイルは、古いリーダー向けの古典的なテーブルと、新しいリーダー向けのクロスリファレンスストリームを組み合わせることができ、どちらの種類のリーダーも理解できるインデックスを見つけられます。

  • What a PDF actually is — 物理レイアウトではなくオブジェクトグラフとして見た、同じ 4 つのパート。
  • Incremental updates and why they matter — 追記される第 5 の領域がファイルをどう成長させ、署名をどう保護するか。
  • Streams and filters — ボディのストリームオブジェクトの中に何があり、それらがどう圧縮されるか。
  • PDF 2.0: what changed — クロスリファレンスストリームが、なぜ NextPDF が書くデフォルトの構造なのか。
  • ヘッダー(Header) — 最初の行、%PDF-2.0、バージョンを表す。通常はバイナリマーカーのコメントが続く。
  • 間接オブジェクト(Indirect object) — ボディ内の番号付きオブジェクト。N G obj … endobj と書かれ、N はオブジェクト番号、G は世代。
  • ディクショナリ(Dictionary) — 名前から値への << /Key value … >> マップ。最も一般的なオブジェクトの形。
  • ストリーム(Stream) — ディクショナリに加えて streamendstream の間のバイトのブロック。コンテンツ、フォント、画像に使われる。
  • クロスリファレンステーブル(xref) — オブジェクト番号からバイトオフセットへのインデックス。古典的には 1 エントリ 20 バイトのテキストテーブル、PDF 2.0 では /Type /XRef ストリーム。
  • トレーラー(Trailer)/Root/Size を表すディクショナリ。ファイル末尾の startxref オフセットを介して見つかる。
  • インクリメンタル更新(Incremental update)%%EOF の後に追記される、変更されたオブジェクト、新しいクロスリファレンスセクション、そして新しいトレーラー。/Prev が直前のセクションへ後ろ向きに連鎖する。