ブログ › PDFを回転しても容量が変わらない理由
PDFページ編集ツールを作りながら、20ページの文書のすべてのページを90度回転させてみました。回転前のファイルが1,064,740バイト、回転後も1,064,740バイト。1バイトも変わりませんでした。
同じ文書で20ページのうち3ページだけ残すと、今度は1,039.8KBが156.5KBになりました。85%の減少です。回転はタダなのに削除はここまで劇的、というのが不思議に感じるなら、それはPDFを画像のように思い描いているからです。
JPEGやPNGは結局のところピクセルの配列です。ファイルを開けば「この位置はこの色」がひたすら並んでいます。PDFはそうではありません。PDFファイルの中にあるのは、互いを指し合うオブジェクトのグラフです。
1つのページはそれ自体が絵ではなく、いくつかを指す参照と属性の束です。何をどの順で描くかを書いたコンテンツストリームを指し、その過程で使うフォントや画像をまとめたリソース辞書を指します。そこに用紙サイズや回転角といった属性がいくつか付いています。
この構造が分かれば、先の2つの数字はおのずと説明がつきます。
PDFには /Rotate というページ属性があります。「このページを画面に出すとき何度回して見せるか」を記しておく場所で、入れられる値は0・90・180・270の4つだけです。
つまり回転はページの中身を実際に回す作業ではありません。数字を1つ、0から90に書き換える作業です。コンテンツストリームには手を触れず、画像を再圧縮することもありません。先の測定でファイルサイズがぴたりと同じだったのもここに理由があります — 1桁の数字を別の1桁の数字に替えただけなので、長さが変わりようがなかったのです。
対比すると違いがはっきりします。写真アプリでJPEGを回して保存すると、たいていはピクセルを実際に回転させてから再圧縮するので、回すたびに画質が少しずつ削れます。PDFでは何度回しても元とまったく同じデータが残ります。向きを間違えて、あと3回押して元に戻しても損はありません。
複数のPDFを結合するときは、ページオブジェクトを新しい文書にコピーします。ただしページだけを持ってくることはできません。そのページが指していたフォントや画像が新しい文書にもないと同じ見た目にならないので、リソースまで一緒に付いてきます。
問題はこのコピーが重複を見分けられないことです。3つのファイルがまったく同じロゴ画像を使っていても、結合する側から見ればそれぞれのリソースでしかなく、3部そのまま入ります。
どれだけ差が出るか測ってみました。同じ53KBの画像を使う1ページのPDFを3つ作って結合したものと、はじめから1つの文書に3ページを作って同じ画像を再利用したものを比べます。
ただ誤解されやすい点があるので正確に書いておきます。この測定では結合そのものがファイルを膨らませたわけではありません。元の3つの合計が157.7KBで、結合結果は156.5KBと、むしろごくわずかに小さくなりました。ファイルごとに付いていたヘッダーと相互参照表が1つにまとまったからです。
ですから正確な表現は「結合すると大きくなる」ではなく「結合しても重複は減らない」です。同じ書式で作った会社の文書を10個合わせれば、同じロゴと同じフォントが10部入ったファイルができます。それが正常な動作です — リソースを1つにまとめるには2つが本当に同じものかをバイト単位で照合する必要があり、少しでも判断を誤れば文書の見た目が壊れます。
ページを消すのも、元の文書から何かを取り除く方式ではありません。残すと決めたページだけを新しい文書にコピーする方式です。すると残したページが指さないリソースは、そもそも付いてきません。
ページごとに違う写真が入った20ページの文書から、3ページだけ残してみました。
残り17ページが使っていた写真がまるごと抜けたので、当然の結果です。ここから実用的な結論が1つ出ます — PDFの容量を減らす最も確実な方法は、圧縮ではなく不要なページを消すことです。画像の再圧縮は画質を代償にしますが、使わないページを外すのは何も失いません。
PDFを結合したり分割したりして、目次がまるごと消えた経験があるかもしれません。これも同じ構造から出てきます。しおりはページに付いているのではなく文書に付いています。「第1章はこのページオブジェクト、第2章はあのページオブジェクト」を指す一覧が文書レベルに別途あるわけです。ページだけを新しい文書に移せばその一覧は残され、無理に持ってきても指す先がなくてずれます。
電子署名はもっと明快です。署名はファイルの特定のバイト範囲全体に対するハッシュです。その範囲の中で1バイトでも変われば署名は無効になります。ページを移せばオブジェクト番号も位置もすべて変わるので、当然壊れます。
ですからこれは「保存できない限界」ではなく「保存されてはいけないもの」に近いのです。署名の存在意義は「この文書は署名されてから変わっていない」を証明することなのに、中身を編集しても署名が生きているなら、その署名は何も保証していないことになります。
最後に、ページ編集ツールを作るうえで一番意外だった点です。ブラウザでPDFを編集するにはライブラリが2つ必要です。一方はPDFを直せるけれど見られず、もう一方は見られるけれど直せないからです。
ここまで話してきた作業 — 回転値を書き換える、ページをコピーする、リソースを連れてくる — はすべてオブジェクトグラフを扱う仕事で、比較的単純です。しかしそのページを画面に描くのはまったく別の問題です。コンテンツストリームの命令を1つずつ解釈し、フォントファイルを読んで字形をピクセルにし、色空間を変換し、透明度を合成しなければなりません。事実上、小さなブラウザを1つ作るようなものです。
その重さはバンドルサイズにそのまま表れます。ページ編集ツールが実際に受け取る容量はこうです。
この分離には実用的な帰結が1つあります。編集画面のプレビューはpdf.jsが画面用にざっと描いた絵で、保存するときはpdf-libが元のファイルからページをそのままコピーします。つまりプレビューがぼやけていても、保存されるファイルの画質とは何の関係もありません。プレビューはあくまでどのページを残すか選ぶための絵です。
結局すべては1つの文から導かれます。PDFはピクセルの束ではなく、互いを指し合うオブジェクトの集まりです。だからある編集は驚くほど安く、ある編集ははじめから不可能なのです。
実際に試してみてください
ページの削除も回転もすべてブラウザの中で行われます。ファイルはアップロードされません。