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

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

オブジェクトストリームのデータ部分には、複数の間接オブジェクトの定義を含めることができます。

ストリームのフィルタを使うことで内容を圧縮することができるため、よりファイルサイズを削減することができます。

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

ストリームオブジェクト」と「オブジェクトストリーム」は名前が似ているので、少しややこしくなりますが、
前者は「ストリームというオブジェクト」、
後者は「間接オブジェクトをデータとして含むストリーム」という意味になります。

オブジェクトストリーム内には、辞書や配列など、任意のオブジェクトを含めることができますが、以下のものは除きます。

[オブジェクトストリームに含めることができないもの]

- ストリームオブジェクト
- 世代番号が 0 以外のオブジェクト
- トレーラー辞書で指定する暗号化辞書
- オブジェクトストリーム辞書の Length エントリの値を示すオブジェクト

特に、ストリームオブジェクトを含めることができないことは、念頭に置いておいてください。
これらは、通常の間接オブジェクトとして定義する必要があります。

オブジェクトストリームを使用する場合、クロスリファレンステーブルでは、オブジェクトストリーム内の間接オブジェクトの位置を指定することができないため、クロスリファレンステーブルの代わりに、後述する「クロスリファレンスストリーム」を使う必要があります。

オブジェクトストリーム内のオブジェクトを参照する場合は、通常の間接オブジェクトと同じ参照方法を使います。

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

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

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

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

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

ストリームデータ内において、辞書の First エントリで指定されたオフセット位置以降の部分が、オブジェクトの定義となります。

N 個分のオブジェクトを連続で記述していきます。
※この時、各オブジェクトで objendobj キーワードを記述する必要はありません。
15 0 obj
<< /Type /ObjStm
   /Length 1856 % データの長さ
   /N 3         % オブジェクト個数
   /First 24    % 先頭のオフセット位置
>>
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 以降では、クロスリファレンステーブルの内容と、トレーラー辞書の内容を、「クロスリファレンスストリーム」として、まとめて記述することができます。

また、オブジェクトストリームを使う場合は、必ずクロスリファレンスストリームを使うことになります。

クロスリファレンスストリームを使うことで、クロスリファレンステーブルの内容をバイナリデータで記述することができ、また、ストリームのフィルタを使って圧縮することで、サイズを削減することができます。

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

なお、クロスリファレンスストリームを使う場合、xreftrailer のキーワードは使用しません。

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

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

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 となります。
ストリームのデータ部分
クロスリファレンスストリームのデータ部分には、バイナリデータで、各間接オブジェクトの情報を、連続でセットしていきます。

クロスリファレンステーブルでの各エントリと同じように、オブジェクトのオフセット位置や世代番号などをデータとして含めます。
ただし、'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 自体は通常通り表示されます。

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