オブジェクトストリームとクロスリファレンスストリーム

オブジェクトストリーム
PDF 1.5 では、「オブジェクトストリーム」という、新しいストリームが追加されました。

これを使うと、通常はボディ部分で定義されるべき間接オブジェクトを、ストリームデータとして、複数個連続で記述することができます。
ストリームデータはフィルタで圧縮できるので、複数個の間接オブジェクトの内容をまとめて圧縮することで、ファイルサイズを削減することができます。

オブジェクトストリーム内にある各間接オブジェクトは、「圧縮オブジェクト」と呼ばれます。

ストリームオブジェクト」と「オブジェクトストリーム」は名前が似ているので、少しややこしいですが、
前者は「ストリームという名前のオブジェクト」、
後者は「間接オブジェクトをデータとして含むストリーム」という意味になります。
オブジェクトストリームに含められないデータ
オブジェクトストリーム内には、辞書や配列など、任意の間接オブジェクトを含めることができますが、以下のものは除きます。

  • ストリームオブジェクト
  • 世代番号が 0 以外のオブジェクト
  • トレーラー辞書で指定するための、暗号化辞書
  • オブジェクトストリーム辞書の Length エントリの値を示すオブジェクト
    (このような使い方をすることは、まずないと思いますが)

特に、ストリームオブジェクトは含めることはできないので、ページの内容などのデータは対象外になります。
対象外のものは、通常の間接オブジェクトとして定義する必要があります。

オブジェクトストリームには、主に、辞書や配列の値をデータに含めることが多いです。
ほか注意点
オブジェクトストリームを使用する場合、クロスリファレンステーブルの代わりに、後述する「クロスリファレンスストリーム」を使う必要があります。
この情報から、各間接オブジェクトの定義位置を参照します。

オブジェクトストリーム内のオブジェクトを参照する場合は、通常の間接オブジェクトと同じように、1 0 R といった形で記述します。

なお、1つのオブジェクトストリーム内に多数のオブジェクトを含めると、デコード時にパフォーマンスが低下するため、オブジェクトストリーム内のオブジェクト数は、最大で 100〜200 程度に制限する必要があります。
オブジェクトストリーム辞書のエントリ
ストリーム辞書の共通エントリも含みます。

Typename(必須) オブジェクトのタイプ。常に /ObjStm
Ninteger(必須) ストリーム内の圧縮オブジェクトの数
Firstinteger(必須) 最初の圧縮オブジェクトのオフセット位置 (フィルタのデコード後の位置)
Extendsstreamオブジェクトストリームへの参照
ストリームデータ
オブジェクトストリームのデータ部分では、まず先頭に、すべてのオブジェクトの情報を、空白で区切った複数の数値 (ASCII 文字) で記述し、その後に、各オブジェクトの内容を記述していきます。

先頭で記述する数値は、各圧縮オブジェクトの「オブジェクト番号」と「オフセット位置」です。

1番目が「圧縮オブジェクトのオブジェクト番号」、2番目が「圧縮オブジェクトのオフセット位置 (最初のオブジェクト位置を 0 としたバイト数)」の、2つの値をペアにして、複数個連続で記述します。
(辞書の N エントリで指定された数×2個の数値となります。なお、配列ではなく、数値のみを記述します)

この時、記述する順番は、オフセット位置の昇順 (小さい値から順) である必要があります。
オブジェクト番号の値に関しては、順番などに制限はありません (番号が連続している必要もない)。

ストリームデータ内においては、辞書の First エントリで指定されたオフセット位置以降のデータが、圧縮オブジェクトの内容となります。
N 個分の圧縮オブジェクトの内容を、順番に連続で記述します。
※この時、各オブジェクトで objendobj キーワードを記述する必要はありません。
15 0 obj
<< /Type /ObjStm
   /Length 1856 % データの長さ
   /N 3         % オブジェクト個数
   /First 19    % 先頭のオフセット位置
>>
stream
11 0 12 547 13 665
<< /Type /Font
   /Subtype /TrueType
   ...
   /FontDescriptor 12 0 R
>>
<< /Type /FontDescriptor
   /Ascent 891
   ...
   /FontFile2 22 0 R
>>
<< /Type /Font
   /Subtype /Type0
   ...
   /ToUnicode 10 0 R
>>
endstream
endobj

先頭の数値は、
1番目のオブジェクトが「オブジェクト番号 11、オフセット位置 0」となり、
2番目のオブジェクトが「オブジェクト番号 12、オフセット位置 547」となります。

なお、それぞれの圧縮オブジェクトの内容の後に改行を付ける必要はありませんが (オフセット位置で先頭位置が指定されているため)、改行があった方が人間の目で見た時にわかりやすいので、改行を入れておいても良いでしょう。
クロスリファレンスストリーム
PDF 1.5 以降では、クロスリファレンステーブルの内容と、トレーラー辞書の内容を、「クロスリファレンスストリーム」のストリームデータとして、まとめて記述することができます。
なお、オブジェクトストリームを使う場合は、必須になります。

クロスリファレンスストリームを使うと、ASCII 文字で記述されているクロスリファレンステーブルの内容を、バイナリデータで記述することができます。
また、ストリームのフィルタを使って圧縮することができるので、ファイルサイズを削減することができます。
使い方
クロスリファレンスストリームは、ボディ内に、通常のストリームオブジェクトとして定義します。

トレーラー辞書の内容は、ストリームの辞書で指定します。
ストリームデータ内では、間接オブジェクトの情報を、バイナリデータで記述します。

xreftrailer は使用せず、また、startxref キーワードの次の行で指定するオフセット位置は、「xref の位置」ではなく、「クロスリファレンスストリームのオブジェクトが定義されているオフセット位置」を指定することになります。

※フィルタで暗号化を行わないこと。
※クロスリファレンスストリームのフィルタとして使用できるのは、FlateDecode のみです。(もしくは、無圧縮)
クロスリファレンスストリームのストリーム辞書のエントリ
ストリーム辞書で共通のエントリに加えて、トレーラー辞書のエントリも含まれ、さらに、以下の追加エントリも含みます。

Typename(必須) オブジェクトのタイプ。常に /XRef
Sizeinteger(必須) このファイル内の最大のオブジェクト番号 + 1 の値。
トレーラー辞書の Size エントリと同じです。
Indexarrayクロスリファレンスの各サブセクションの情報。
2つの整数をペアとして、複数の数値の配列で指定します。

1番目の値は「サブセクションの最初のオブジェクト番号」、2番目の値は「サブセクションのエントリの数」となります。
クロスリファレンステーブルの各サブセクションの、先頭の2つの値と同じです。

default = 空の配列
Previnteger(複数のクロスリファレンスストリームがある場合)
前のクロスリファレンスストリームのオフセット位置 (ファイル先頭から)。
Warray(必須) オブジェクト情報の一つのエントリにおける、各フィールドのサイズ (バイト数) を示す数値の配列。

3つの整数を配列で指定します。
[1 2 1] であれば、エントリの各フィールドの値は、1 byte, 2 byte, 1 byte のデータとなり、バイナリデータはこのサイズで記述していきます。

値が 0 の場合、対応するフィールドは、バイナリデータで値が記述されないことを示し、その場合は、各フィールドにおけるデフォルト値が使用されます。
最初の要素の値 (type のバイト数) が 0 の場合は、デフォルトの値が 1 となります。
ストリームのデータ部分
クロスリファレンスストリームのデータ部分には、PDF 内で使用されているすべての間接オブジェクトの情報を、バイナリデータで連続でセットしていきます。

クロスリファレンステーブルでの各エントリと同じように、オブジェクトのオフセット位置や世代番号などを、データとして含めます。
ただし、'n' や 'f' のキーワードは使用せず、代わりに、type の値が追加されています。

タイプの数値によって、それぞれ、以降のデータの意味が変わってきます。

type各フィールドと値の意味
0空きオブジェクトのリンクリストを定義 ('f' に相当)。

[1番目] 0 (type = 0)
[2番目] 次の空きオブジェクトのオブジェクト番号
[3番目] 世代番号
1使用中の通常の間接オブジェクトを定義 ('n' に相当)。
オブジェクトストリーム自身も含みます。

[1番目] 1 (type = 1)
[2番目] オブジェクトのオフセット位置 (ファイル先頭から)
[3番目] 世代番号 (デフォルト値 = 0)
2使用中の、オブジェクトストリーム内にある圧縮オブジェクトを定義。
オブジェクトストリーム自身は含まず、中の圧縮オブジェクトのみが対象です。

[1番目] 2 (type = 2)
[2番目] このオブジェクトが含まれているオブジェクトストリームの、オブジェクト番号 (世代番号は暗黙的に 0)
[3番目] オブジェクトストリーム内における、このオブジェクトのインデックス番号 (0〜)

ストリーム辞書の W エントリでは、この3つのフィールドの各バイト数を指定することになります。

1番目のフィールド (type) のサイズは通常 1 byte ですが、もしもすべてのエントリで type = 1 しか使わない場合は、W エントリの最初の要素に 0 を指定することで、デフォルトの値 1 を使うことができます。
その場合、バイナリデータには、最初のフィールドの値は含めません (2番目のフィールドの値からセットする)。

2番目と3番目のフィールドに関しては、設定する値の最大値によって、適切なサイズを指定します。
(通常は、2〜4 byte)

なお、値が 2 byte 以上の場合は、ビッグエンディアン順で、上位バイトから順に配置します。
ここでは、3 byte などのサイズも扱うことが出来ます。
互換性
クロスリファレンスストリームに対応していないアプリケーションとの互換性を取るために、通常のクロスリファレンステーブルと、クロスリファレンスストリームを、両方記述することができます。

その場合は、基本として PDF 1.4 以前の形式で記述し、トレーラー辞書には XRefStm エントリを含めます。

クロスリファレンスストリームに対応するアプリケーションでは、そのエントリの値から「クロスリファレンスストリームのオフセット位置 (ファイル先頭から)」を読み取り、そのオフセット位置からクロスリファレンスストリームを読み込むことができます。
% クロスリファレンスストリーム
99 0 obj
<< /Type /XRef
   /Index [0 32]
   /W [1 2 2]
   /Size 32
   ...
>>
stream
% 実際にはバイナリデータですが、16進数として表示しています
00 0000 FFFF
...
02 000F 0000
02 000F 0001
02 000F 0002
...
01 BA5E 0000
...
endstream
endobj

startxref
54321  % 99 0 obj のオフセット位置
%%EOF
使用例
01.pdf を元に、クロスリファレンスストリームとオブジェクトストリームを使って、記述してみます。

>> 05.pdf

※バイナリデータを含んでいるため、ソースを見る場合は、バイナリエディタ等で開いてください。

%PDF-1.5

%---- オブジェクトストリーム

5 0 obj
<< /Type /ObjStm
   /N 3      % 3個のオブジェクト
   /First 14 % 最初のオブジェクトのオフセット位置
   /Length 183
>>
stream
1 0 2 37 3 87 % オブジェクト番号とオフセット位置

%# [1] カタログ辞書
<<
 /Type /Catalog
 /Pages 2 0 R
>>

%# [2] ページツリーノード
<< /Type /Pages
   /Kids [ 3 0 R ]
   /Count 1
>>

%# [3] ページオブジェクト
<< /Type /Page
   /Parent 2  0 R
   /MediaBox [ 0 0 612 792 ]
   /Contents 4 0 R
>>
endstream
endobj

%---- コンテンツストリーム
% (ストリームオブジェクトは
%  オブジェクトストリーム内に含むことができないため)

4 0 obj
<< /Length 18 >>
stream
10 10 100 100 re
S
endstream
endobj

%---- クロスリファレンスストリーム

6 0 obj
<< /Type /XRef
   /Length 35
   /Index [0 7] % オブジェクト番号 0〜6 (7個)
   /Size 7      % 最大オブジェクト番号(6) + 1 = 7
   /W [1 2 2]   % 各フィールドのバイト数
   /Root 1 0 R  % カタログ辞書
>>
stream
% 実際はバイナリデータですが、16進数として表示しています
00 0000 FFFF % [0] 空
02 0005 0000 % [1] オブジェクト番号 5 内の1番目
02 0005 0001 % [2] オブジェクト番号 5 内の2番目
02 0005 0002 % [3] オブジェクト番号 5 内の3番目
01 011A 0000 % [4] コンテンツストリーム
01 0009 0000 % [5] オブジェクトストリーム
01 015F 0000 % [6] このクロスリファレンスストリーム
endstream
endobj

%---- 終端
startxref
351  % 6 0 obj の位置
%%EOF

クロスリファレンスストリーム自体も間接オブジェクトであるため、(上記の場合はオブジェクト番号 6)、これもクロスリファレンスストリーム内のエントリとして含めることができます。

ただし、クロスリファレンスストリームのオブジェクトのオフセット位置は、startxref の次の行で記述されているため、クロスリファレンスストリーム内に自身のエントリを含めなくても、PDF 自体は通常通り表示されます。

ただ、間接オブジェクトの一部ではありますし、オブジェクト番号も割り当てられているため、ファイルを更新する場合のことも考えると、一応エントリに含めておいた方がいいかもしれません。