手軽屋
ツール一覧

PDF回転シリーズ #3

PDF回転で画質が落ちない仕組み(/Rotateエントリの話)

JPEGや動画は回転で画質が落ちるのに、PDFは何度回しても劣化しません。その理由はISO 32000-1規格に定義された「/Rotateエントリ」の仕様にあります。

JPEGとPDFの回転の違い

JPEGを画像編集ソフトで90度回転すると、たいていの場合「再圧縮」が走ります。JPEGはDCT(離散コサイン変換)後にハフマン符号化された不可逆圧縮なので、回転→再圧縮で僅かに劣化します(例外として、画像のサイズが8の倍数なら「ロスレス回転」も可能ですが、ソフト側の対応が必要)。

PDFは違います。PDF全体が「ページオブジェクトの集合」として構造化されており、各ページには「向き」を表す/Rotateというキーが用意されています。回転処理は、このキーの値を書き換えるだけで完了します。

/Rotateエントリの仕様

ISO 32000-1:2008(PDF 1.7仕様)の14.6項「ページオブジェクト」では、各ページ辞書に以下のオプションキーが定義されています。

/Rotate (Integer; 任意; 継承可能)
ページを画面表示・印刷時に時計回りで何度回すかを指定。
値は 90 の倍数のみ(0, 90, 180, 270)。
省略時は 0。

重要なのは「90の倍数のみ」という制約。45度や30度などの半端な角度は規格上認められていません。PDFビューアやプリンタは、このキーの値を見て表示・印刷時に回転を適用します。

pdf-libでの実装

pdf-lib v1.17(本ツールが採用しているJavaScriptライブラリ)では、PDFPageクラスのsetRotation メソッドで回転を指定します。引数は度単位(degrees関数)またはラジアン単位(radians関数)。

import { PDFDocument, degrees } from 'pdf-lib';

const pdfDoc = await PDFDocument.load(bytes);
const pages = pdfDoc.getPages();
pages[0].setRotation(degrees(90));
const out = await pdfDoc.save();

内部的にはPDFのページオブジェクト辞書に/Rotateエントリを書き加える(または既存値を更新する)だけ。ページのコンテンツストリーム(埋込画像・テキスト・フォント)には一切触りません。これが画質劣化ゼロの理由です。

既存の/Rotate値との合算

注意点として、pdf-libのsetRotationは「上書き」です。既に/Rotateが90のページにsetRotation(degrees(90))を呼ぶと、180になるのではなく、90のままです。本ツールでは、元の/Rotate値を読み取って加算した結果を渡すことで、ユーザーが見ている「+90度」という直感的な操作と一致させています。

const current = page.getRotation().angle; // 既存値(0/90/180/270)
const next = (current + 90) % 360;         // ユーザー操作分を加算
page.setRotation(degrees(next));

スキャン画像PDFも劣化しない

スキャナで作ったPDFは、中身がJPEGまたはJPEG2000の画像として埋め込まれていることが多いです。「内部がJPEGなら回転で劣化しない?」と疑問に思うかもしれませんが、答えはNO(劣化しません)。

理由は明快で、PDFの/Rotateは「ビューアに対する表示指示」だからです。ビューアは表示・印刷時にこの値を見て画面上で回転を適用しますが、埋め込まれたJPEG画像のバイト列そのものは触りません。ファイルサイズが回転前後でほぼ変わらない(数バイトだけ増減する)ことからも、画像を再エンコードしていないことが確認できます。

中身を本当に回したい場合

/Rotateを使わず、PDFのページ実体を本当に90度回したい(OCRやデータ抽出の都合で)場合は、PDFを画像化してから別ツールで回転→PDFに戻すフローになります。PDF→JPG変換→ 画像編集 →JPG→PDF変換という流れです。

ただ、この流れは画質劣化を伴いますし、テキストレイヤーが失われます。普通の見映え調整なら本ツールの/Rotate書換で十分。「OCRに通したい」「データ抽出したい」という用途に限って、画像化ルートを選ぶことになります。

関連リンク