もうPCにAcrobatはインストールしない

最初に買ったScanSnapはS1500で、これにはAdobe Acrobat X Standardがバンドルされていた。そのため、ページの差し替えやOCRといったほとんどのPDF編集は、これまでAcrobatを使ってやってきた。バージョンXのサポートが終了してからは、XI、2017、2020の買い切り版も購入した。

しかし、最後の買い切り版となる2020のサポートも2025年の6月で切れ、これ以降は、サブスクリプション版を買うか、サポート無しで2020を使い続けるかの二択になった。

今では紙の本の自炊もほぼ終わって、PDFを編集するのも月に数回といったところだ。その状況でサブスクリプション版に月1,500円以上も払うのは明らかに割に合わない。買い切り版もそれなりに高かったが、サブスク版はその3倍ぐらいする感覚だ。とても採用できる案ではない。

ならばリスク覚悟で2020を使い続けようかと思ったが、気づくとそれもできない状況になっていた。先日、Windowsをクリーンインストールした後にAcrobat 2020をインストールして、いざ使おうとしたところ、どうやってもアプリ本体からAdobeのアカウントにログインできず、起動までたどり着かない。ブラウザでAdobeにログインはできるのだが、それがAcrobat本体に伝わらないらしい。検索すると、類似の事象はちょいちょい起こっているようだが、いずれも具体的な解決方法が無い。最終的にはAdobeに問い合わせてなんとかなったという人もいたようだが、すでにサポートが終了している今、まともに取り合ってもらえるとも思えない。ここまでくると、なんかもうサブスク版に誘導するために意図的に使えなくしてるんじゃないかという気がしてきて、だんだん腹が立ってきた。

もともとAdobeにはあまり良い印象をもっておらず、いつかはAcrobatから脱却したいと思っていたが、どうやらその時がやってきたようだ。

というわけで、もうPCにAcrobatはインストールしないことにした。

Acrobatでやっていた作業は何か?

そうなると、いままでAcrobatでやっていたPDF編集作業を、どうにかほかの手段で達成しなければならなくなる。

では、自分はAcrobatでどんな作業をしていたのか。もともと紙のPDF化が主用途だから、編集とはいっても、作業的には下記くらいのものだ。

  • ページを削除 ★
  • ページ順を入れ替え ★
  • ページを回転 ★
  • 複数ページをまとめて回転
  • ページを置換 ★
  • 複数のPDFを1つに結合 ★
  • 初期設定を見開き表示(表紙あり)に
  • 初期設定を右開きに
  • 初期設定を全体表示に
  • ページラベルを付与
  • OCRで透明テキスト付与 ★
  • ページをトリミング ★
  • 全ページを一律にトリミング
  • パスワードで暗号化

このうち、★を付けた作業は、ScanSnap専用ソフトの「ScanSnap Home」のビューアでも同じようなことができる。

となると残った作業は下記だが、見ての通り、それほど大したものではない。頑張ればOSSだけでもなんとかなりそうだ。

  • 複数ページをまとめて回転
  • 初期設定を見開き表示(表紙あり)に
  • 初期設定を右開きに
  • 初期設定を全体表示に
  • ページラベルを付与
  • 全ページを一律にトリミング
  • パスワードで暗号化

個人的に、CLIで処理ができれば、別にGUIがなくとも構わない。ただCLIの常として、しばらくすると具体的な作業手順やコマンドを忘れてしまう。そこで、上記の作業をするための自分用の手順をメモしておく。

複数ページをまとめて回転

たとえば、横長の書類を縦にスキャンして、奇数ページは右回転、偶数ページは左回転させたいとする。

PDFtkを使うなら下記。

pdftk in.pdf rotate 1-endoddright 1-endevenleft output out.pdf

QPDFを使うなら下記。

qpdf in.pdf out.pdf --rotate=+90:1-z:odd --rotate=-90:1-z:even

初期設定を見開き表示(表紙あり)に

PDFの文法的には、TypeCatalogの辞書のPageLayoutの値が初期表示のレイアウトを決めている。見開き表示(表紙あり)にするには、この値をTwoPageRightにしてやれば良い。ScanSnapで作ったPDFの初期状態ではPageLayout自体が存在していないので、要はカタログ辞書に/PageLayout/TwoPageRightという文字列を書き足してやればいいわけだ。とはいえ、文字列を追加するということはそれ以降のデータのオフセットがずれるので、生のPDFを直接編集するわけにはいかない。それに、生のPDFだと改行とか空白がどこにどんな風に入っているか分からないので、挿入位置も特定しにくい。

そこで、qpdfでいったん正規化されたPDFを作って、そこに/PageLayout/TwoPageRightを書き足してから、fix-pdfで正常なPDFに戻してやる。fix-pdfはオブジェクトの長さを再計算してくれるので、書き足しによって一時的に不正な状態になっていたPDFも、最後は正しい状態になる。

qpdf --qdf in.pdf temp1.qdf
sed -e 's|/Type /Catalog|&/PageLayout/TwoPageRight|' temp1.qdf > temp2.qdf
fix-qdf < temp2.qdf > out.pdf

初期設定を右開きに

初期設定を見開き表示(表紙あり)にすると同時に、縦書きの本の場合は右開きにもしておきたい。

PDFの文法的には、TypeCatalogのオブジェクトのViewerPreferencesの値の辞書の中にあるDirectionの値がR2Lだと右開きになる。ViewerPreferencesも、ScanSnapで作ったPDFの初期状態には含まれていないので、要はカタログ辞書に/ViewerPreferences<</Direction/R2L>>という文字列を書き足してやればいい。

qpdf --qdf in.pdf temp1.qdf
sed -e 's|/Type /Catalog|&/ViewerPreferences<</Direction/R2L>>|' temp1.qdf > temp2.qdf
fix-qdf < temp2.qdf > out.pdf

初期設定を全体表示に

単票の書類なんかを初期状態でページ全体表示にしたいときにはこれを使う。初期設定を見開き表示にしたときは勝手に全体表示になってくれるので、必須では無い (がやっても良い)。

PDFの文法的には、TypeCatalogの辞書のOpenActionの値に、「最初に開くページオブジェクトへの参照、およびFitを要素に持つ配列」を設定してやれば良い。たとえば、最初に開くのが1ページ目で、1ページ目のページオブジェクトのIDが177だとすると、/OpenAction[177 0 R /Fit]という文字列をカタログ辞書に書き足せばよい。ScanSnapで作ったPDFにはOpenActionも含まれていないので、/PageLayout/TwoPageRightと同じような手順で追加できる。

だがここで問題なのは、1ページ目のページオブジェクトのIDをどうやって特定するか、だ。幸いQPDFで正規化したpdfには、ページオブジェクトのコメントとして%% Page 1のようなページ番号が書かれている。

%% Page 1
%% Original object ID: 18 0
177 0 obj

元のPDFがオブジェクトストリームの場合は、下記のようなコメントになる。

177 0
%% Object stream: object 177, index 0; original object ID: 18
%% Page 1

なので、%% Page 1をサーチして、その前後を見れば、1ページ目のページオブジェクトのIDが分かる。コマンドラインとしては、ちょっと強引だが、%% Page 1の前後2行のなかで、行頭から数字が連続している部分を取り出す、くらいでなんとかなるだろうか。

まとめると、コマンドラインとしては下記のような感じになる。

qpdf --qdf in.pdf temp1.qdf
obj_id=$(grep -aC2 "^%% Page 1$" temp1.qdf | grep -Eo "^([0-9]+) ")
sed -e "s|/Type /Catalog|&/OpenAction[$obj_id 0 R /Fit]|" temp1.qdf > temp2.qdf
fix-qdf < temp2.qdf > out.pdf

grepのオプションの意味は次のとおり。

  • -a (--text): バイナリをテキストとして検索
  • -C2 (--context=2): ヒットした行の前後2行も表示
  • -E (--extended-regexp): 拡張正規表現を使用 (+を使うため)
  • -o (--only-matching): マッチした部分だけを表示

ページラベルを付与

自炊した本で、本文の前にジャケットやカバー・見返し・扉などがあると、PDFの9ページ目がノンブルの1ページ目に相当する、というような状態になるのが普通だ。さらに、前書きや目次にローマ数字で独立したノンブルが振られていたり、途中にノンブルなしの挟み込みページがあったりもする。

このようなとき、PDFの「ページ番号」とは別に、印刷されているノンブルと同じ番号を各ページに付与できると便利だ。これをPDF的には「ページラベル」という。

ページラベルは、PDF内部では「開始ページ番号」と「ラベルの形式」を羅列した配列のようなイメージで定義されている。

そして、QPDFではコマンドラインオプションでページラベルを振ることができる。たとえば、やや複雑なケースとして、

  • 1~4ページ: A1~A4
  • 5~20ページ: i~xvi
  • 21~40ページ: 1~20
  • 41~42ページ: ラベル無し
  • 43~100ページ: 21~78

というラベルを振りたければ、次のコマンドを指定する。

qpdf --set-page-labels 1:D//A 5:r 21:D 41: 43:D/21 -- in.pdf out.pdf

ラベルの仕様は、開始ページ番号:[タイプ][/最初の数字[/接頭辞]]の書式で指定する。

タイプには、下記のいずれかが指定できる。

  • D: アラビア数字 (1,2,3,…)
  • A: アルファベット大文字 (A,B,C,…)
  • a: アルファベット小文字 (a,b,c,…)
  • R: ローマ数字大文字 (I,II,III,…)
  • r: ローマ数字小文字 (i,ii,iii,…)
  • 省略: ラベル無し

全ページを一律にトリミング

本をコピーした紙をスキャンしてPDF化した場合や、画面キャプチャからPDFを作った場合などに、全ページを一律にトリミングしたいことがある。

PDFの文法的には、TypePageの辞書のCropBoxの値が、そのページ内の描画範囲を決めている。CropBoxの値は数字を4つ持つ配列で、[左下のX座標 左下のY座標 右上のX座標 右上のY座標]を指定する。座標の値は、ScanSnapで作ったPDFの場合はポイントと考えて良いだろう。例えば、左下が(0.0 pt, 76.5354 pt)で、右上が(1488.0 pt, 2188.05pt)の四角でトリミングしたければ、Pageの辞書に/CropBox[0.0 76.5354 1488.0 2188.05]という文字列を追加すればよい。

通常、どのPageの辞書にもMediaBoxはあるので、/MediaBoxの直前あたりにこれを挿入してやるのがいいだろう。そうすると、例えば次のようなコマンドになる。

qpdf --qdf in.pdf temp1.qdf
sed -e 's|/MediaBox|/CropBox[0.0 76.5354 1488.0 2188.05]&|' temp1.qdf > temp2.qdf
fix-qdf < temp2.qdf > out.pdf

パスワードで暗号化

PDFtkを使うなら、pdftk in.pdf output out.pdf user_pw "p@ssword"でパスワードをかけられるのだが、このままだとコマンドヒストリやログなどに生のパスワードが残ってしまう。pdftk in.pdf output out.pdf user_pw PROMPTとすれば、パスワード部分は後から打ち込めるようになるが、入力内容はエコーされてしまうので、もし作業ログを取っているなら、やはり生のパスワードが残ってしまう。

そこで、read-pオプションを使ってエコーを消し、読み取ったパスワードをシェル変数PASSWORDに入れてpdftkに渡すことで、打ち込んだ内容を見えないように改良したのが下記のコマンドだ。ちなみに、全体を(...)で囲んでいるのは、うっかりecho $PASSWORDなどしてパスワードが露出してしまうのを防ぐための対策だ。これだとPDFの変換はサブシェルで実行され、変換が終わると同時にシェル変数も消えてしまうから、あとから内容を見ることはできない。とはいえ、pdftkのプロセスが生きている間はpsコマンドなどで引数を覗かれるとパスワードが見えてしまうので、複数人がログインしているような環境ではやらない方がよさそうだ。

(read -s -p "Enter password: " PASSWORD && pdftk in.pdf output out.pdf user_pw "$PASSWORD")

パスワードによる暗号化は、QPDFでも可能だ。

(read -s -p "Enter password: " PASSWORD && qpdf --encrypt "$PASSWORD" "" 256 -- in.pdf out.pdf)

PDFのバイナリ部分に注意!

PDF内部のストリームには、バイナリデータがそのまま書かれている。上記のコマンド例のようにsedで編集した場合は、今回試した限りではバイナリ部分が変化することはなかった。

だが、VSCodeなどのGUIのテキストエディタでPDFを編集すると、保存時にバイナリ部分まで変化してしまうことがあり、そうなるとfix-pdfをかけたあとのPDFでも「壊れている」と言われて開けなくなってしまう。

使うエディタがバイナリセーフかどうかは常に意識しておこう。

※バージョンメモ

  • pdftk port to java 3.3.3
  • qpdf version 11.9.0
  • sed (GNU sed) 4.9