80MBのスキャン済みレポートのリンクを開き、ブラウザで何が起こるか見てみましょう。ビューアはそれらのバイトの大部分が到着するまで空白のペインで待機し、その後、一度に1ページ目を描画します。40ページ目にジャンプすると、構成が悪いファイルではダウンロード全体が再開される可能性があります。イライラするのは、読者は常に最初のページだけを求めていたということです。線形化は、その問題に対する構造的な答えです。ビューアがファイルの先頭のわずかな部分から最初のページをレンダリングし、残りをオンデマンドで取得できるようにPDFを再編成します。これが、Adobeがこの機能を「Fast Web View」として宣伝している理由です
これらはどれも異なるファイル形式ではありません。線形化されたPDFは、準拠しているリーダーが特別な処理なしで開くことができる通常のPDFです。このトリックは、バイトの順序と、ファイルが持つ2つの追加構造に完全に依存しています。ISO 32000-1 は Annex F でこの配置全体を指定しており、そのレイアウトを一度見れば、その動作は魔法のようには見えなくなり、最初の描画のレイテンシとファイル順序の意図的なトレードオフのように見え始めます
線形化が実際に再編成するもの
通常のPDFは、オブジェクトをほぼ任意の順序で分散させることができます。ファイルの最後にある相互参照テーブルがそれを機能させます。リーダーは最後までシークし、startxref ポインターを読み取り、xrefを読み込み、そこからオフセットによってすべてのオブジェクトの場所を特定できます。その設計は、最後までシークするコストがかからないローカルファイルには優れていますが、最後がまさに一番最後に到着する部分である、ネットワーク経由でストリーミングされるファイルには適していません。1ページ目をレンダリングするために、従来のリーダーはページオブジェクト、そのコンテンツストリーム、それが参照するフォント、およびそれが描画する画像が必要であり、順序付けられていないファイルでは、これらは最後のメガバイトを含め、どこにでも配置される可能性があります
線形化は順序を固定します。最初のページを表示するために必要なオブジェクトは、小さなヘッダーセクションの直後の先頭付近にある連続したブロックに集められるため、バイトストリームの早い段階で到着します。その他のすべてのもの、残りのページとそれらが共有するリソースは、予測可能な順序で続きます。最適化を無視するリーダーのために、2つ目の完全な相互参照テーブルは依然として最後に存在しますが、線形化されたファイルには、1ページ目の相互参照とストリーミングリーダーが必要とするパラメータも先頭に配置されます。リーダーは、何かを描画する前に尻尾に到達する必要はもうありません
1ページ目のオブジェクトセットと線形化パラメータ辞書
%PDF ヘッダーの後、線形化されたファイルの最初のオブジェクトは、線形化パラメータ辞書です。これは、最適化が存在するかどうか、およびその使用方法を決定するためにストリーミングリーダーが探すものです。辞書には、ファイル全体の長さ、メインの相互参照セクションが始まるバイトオフセット、最初のページのオブジェクト番号、およびそれに続くヒントストリームの場所と長さが記録されています。これらの数字により、リーダーは最初のキロバイトを開くだけで、1ページ目を表示するためにどれだけフェッチする必要があるか、別の場所にジャンプするためのインデックスをどこで探せばよいかを知ることができます
Annex F では、ここで「最初のページ」が何を意味するかについて厳密に規定しています。1ページ目のセクションには、そのプレフィックスがダウンロードされるとページが自給自足できるように、ページオブジェクト自体、そのコンテンツストリーム、およびそれらのストリームが参照するリソースが含まれている必要があります。すべてのページで使用されるフォントや、ヘッダーで繰り返されるロゴなどの共有リソースは特別に処理されます。それらは最初のページを提供するのに十分早い段階で表示されますが、共有としてフラグが付けられているため、後で30ページ目をレンダリングするときにリーダーがそれらを再フェッチすることはありません。ページプライベートなオブジェクトと共有オブジェクトの区別は、ほとんどの自家製「オプティマイザー」が間違える部分であり、それを間違えると、線形化されていると主張しながらも失速するファイルが生成されます
ヒントストリーム: ページジャンプを低コストにするインデックス
1ページ目をすばやく表示することは、価値の半分にすぎません。残りの半分は、途中のすべてをダウンロードすることなく任意のページにジャンプすることであり、それがヒントストリームが提供するものです。線形化されたファイルには、パラメータ辞書から参照されるストリームとして保存された、ページオフセットヒントテーブルと共有オブジェクトヒントテーブルが含まれています。ページオフセットテーブルは、すべてのページについて、そのオブジェクトがファイル内のどこから始まり、どのくらい続くかを記録します。共有オブジェクトテーブルは、複数のページで使用されるリソースに対して同じことを行います
これらのテーブルがある場合、40ページ目を必要とするリーダーはファイルを順番に解析しません。ヒントテーブルを参照して40ページ目が占めるバイト範囲を調べ、サーバーにその範囲だけを要求し、そのバイトが到着したらページをレンダリングし、まだ保持していない共有リソースを同じメカニズムで引き出します。ヒントストリームは事実上、ドキュメントに重ねられたランダムアクセスマップであり、これが、適切に線形化された500ページのファイルが遅いリンクでも応答性が高いと感じるのに対し、同じサイズの最適化されていないファイルはそうではない理由です
サーバーが協力しなければならない理由
線形化は、トランスポートがファイルの任意のスライスを配信できることを前提としており、フォーマットのせいで結果が悪いと判断する前に、その前提を確認する価値があります。このメカニズムは HTTP byte-serving です。リーダーは range リクエストを発行し、サーバーはそれらに 206 Partial Content レスポンスで応答します。サーバーが Accept-Ranges: bytes をアドバタイズしない場合、またはサーバーの前にあるプロキシやCDNが range リクエストをフル転送に折りたたむ場合、リーダーは40ページ目を単独で取得する方法がなく、ファイル全体のダウンロードにフォールバックします。その場合、PDF内の構造は完全に正しいですが、完全に無駄になります
これは、「線形化が機能しない」と誤診されることが最も多い障害です。ファイルは問題ありません。配信パスが問題なのです。ドキュメントを再構築する前に、条件付きリクエストを使用して、ホストが実際にリーダーがヒットしたURLの部分的なコンテンツを返すことを確認してください。多くの静的ホストはデフォルトでこれを行いますが、設定が誤っているアプリケーションサーバーやキャッシュレイヤーの多くはこれを行いません
増分更新が線形化を静かに壊す
適切に線形化されたファイルを生成した後、なぜ最適化が消えてしまうのかと疑問に思う人々を驚かせる制約がここにあります。線形化は、インデックスが先頭にある、単一の慎重に順序付けられたレイアウトに依存しています。増分更新は、設計上それに違反します。ツールが署名を追加したり、フォームフィールドに入力したり、増分保存を通じて注釈を追加したりする場合、ファイルは書き換えられません。元のバイトはそのままにして、変更されたオブジェクト、新しい相互参照セクション、新しいトレイラーを最後に追加します。その追加が増分更新の要点です。それは高速であり、監査や署名の検証のために以前のリビジョンを保持します
副作用として、ファイルの最後に最新の相互参照データが配置されるようになります。慎重に配置された1ページ目のブロックの後であり、先頭にある線形化パラメータ辞書は、ファイルと一致しなくなったレイアウトを記述します。準拠しているリーダーは不一致を検出し、ドキュメントを通常の線形化されていないPDFとして扱います。元の線形化された構造はファイルの前半にまだ存在していますが、Fast Web View はなくなります。複数の更新を追加すると、それぞれが最後に別のリビジョンを積み重ね、古いフロントインデックスと実際の状態の間のギャップが広がります
ワークフローで編集と Fast Web View の両方が必要な場合、ルールは構造から直接導き出されます。ドキュメントが変動している間は増分的に編集し、最後に1回再線形化します。レイアウトを復元するのは完全な書き換えです。HotPDFの用語では、進行中の編集はデルタを追加する BeginIncrementalUpdate と SaveIncrementalUpdate を通過し、最後の仕上げステップではドキュメント全体をロードして LoadFromFile とそれに続く SaveLoadedDocument で新しくシリアル化します。これにより、蓄積された古いリビジョンが削除され、1つのクリーンなレイアウトが出力されます。オブジェクトストリームでも同じトレードオフが現れます。UseObjectStreams と UseXRefStream を有効にすると、相互参照が圧縮され、オブジェクトが密にパックされます。これはファイルサイズには役立ちますが、他の構造的な選択と同様に、追加されたリビジョンに付け加えるのではなく、その最後の書き換え中に適用する必要があります
// In-flight edits: append a delta, keep prior revisions intact.
// This leaves the file NOT linearized.
Pdf.BeginIncrementalUpdate('report.pdf');
Pdf.AddPage;
Pdf.CurrentPage.TextOut(72, 760, 0, 'Addendum');
Pdf.SaveIncrementalUpdate('report.pdf');
// Finishing step: full re-serialization produces one clean layout,
// dropping the stacked revisions. Re-run your linearizer on the output.
Pdf.LoadFromFile('report.pdf');
Pdf.SaveLoadedDocument('report-final.pdf');
HotPDF は1回の呼び出しによる「線形化」ルーチンを公開していないため、実用的なパターンは、クリーンで完全に書き直されたファイルを生成し、その上で専用のオプティマイザーを実行することです。コマンドラインツールは再配置を直接処理します。qpdf は、1つのフラグを使用してファイルを線形化された形式に書き換えます:
qpdf --linearize report-final.pdf report-web.pdf
ファイルが線形化されているかどうかを見分ける方法
ファイル名や、それを生成したと主張するツールを信用しないでください。バイトを確認してください。最も直接的なチェックはファイルの先頭です。ファイルを開き、ヘッダーの後の最初のオブジェクトとして、/Linearized キーを持つ線形化パラメータ辞書を探します。リーダー向けのショートカットは Acrobat のドキュメントのプロパティダイアログで、構造が純粋に存在し、最新である場合にのみ「Fast Web View: はい」と報告します
スクリプトによるチェックの場合、qpdf は構造の存在と整合性の両方を報告します。ファイルはレイアウトを反映しなくなった線形化辞書(まさに増分更新が残す状態)を持つ可能性があるため、これは重要です
# Reports "File is linearized" and validates hint tables against the layout
qpdf --check report-web.pdf
# Dumps the linearization parameters and hint data in detail
qpdf --show-linearization report-web.pdf
検証ステップは、その役割を果たすものです。辞書が存在することを確認するだけのパスは、インデックスが間違ったオフセットを指しているファイルを喜んで承認します。実際のオブジェクトの位置とヒントテーブルを照合するチェックこそが、実際のリーダーの range リクエストの下で最適化が維持されることを教えてくれます
線形化は、Web経由で提供される大規模なドキュメント、特に不安定な接続環境にあるモバイルリーダーに対しては依然として適用する価値があり、前もって読み込まれるインデックスのためにファイルサイズが数パーセント増加するだけです。明確にしておくべき2つのことは、PDF内の構造と外部の byte-serving の両方が正しくなければならないこと、そして事後の編集はファイルを書き換えるまで最適化を元に戻してしまうことです。再線形化は、他のすべての変更が確定した後のパイプラインの最後のステップとして扱ってください。ここで説明した相互参照、オブジェクトストリーム、および増分更新の動作は、Delphi および C++Builder 向けの HotPDF Component が実装する構造モデルの一部です。より広範なファイルレイアウトの背景については、PDFの構造 を参照し、コードでの増分更新と大規模ファイルのワークフローについては、Delphi からの大きなPDFの処理 を参照してください