PDF の一部を黒く塗って人に渡す。氏名や金額を隠したつもりでも、受け取った人がその部分を選択してコピーすると、下にあった文字が貼り付けられることがある。黒い図形を載せた文書を PDF に書き出したり、PDF の編集ソフトで黒い四角を描き足したりすると、こうなりうる。
PDF 墨消しチェッカー は、この消し残しを探し、指定した範囲の文字をファイルから削除して書き出すツールである。PDF はサーバーに送らず、ブラウザの中で処理する。黒塗りの下の文字を消せば終わり、と考えたくなる。ところが、画面から消えた文字は、ファイルの別の場所にも残っていることがある。
黒く塗っても、文字が消えないのはなぜか
PDF のページは、描画の命令を順に並べたものである。「この位置に、このフォントでこの文字を置く」「この四角を黒で塗る」といった命令が並び、PDF ビューアは上から順に描いていく。後の命令で描いたものは、先に描いたものの上に重なる。
黒塗りの多くは、文字の命令の後ろに「黒い四角を塗る」命令を足したものである。画面では四角が文字を覆うので、文字は見えない。しかし文字の命令はファイルに残っている。文字の選択、コピー、検索、テキストの抽出は、描いた結果の画素ではなく文字の命令を読むので、覆われた文字もそのまま取り出せる。
本当に消すには、黒い四角を描くだけでなく、範囲にある文字の命令そのものをファイルから取り除かなければならない。
黒塗りの下に文字があるかを、どう見つけるか
ツールは、ページに並ぶ命令を描画の順に読み、次の条件に合う文字を「黒塗りの下に文字が残っています」として挙げる。
- その文字より後に描かれた、塗りつぶしの四角(傾いていない長方形で、不透明なもの)か画像がある
- その四角や画像が、文字を囲む箱の半分以上を覆っている
描画の命令とは別に、PDF には注釈(ページの上に重ねて表示する付箋や図形)もある。墨消しの注釈がまだ適用されていないもの、塗りつぶした四角の注釈、黒いハイライトの下にある文字も、同じように挙げる。
除外しているものもある。スキャンした PDF に OCR で読み取った文字を重ねたファイルでは、ページの画像の下に、見えない文字(描画しない設定の文字)が置かれている。検索やコピーのための普通の構成なので、画像の下にある見えない文字は数えない。
上書き保存した PDF には、以前の版が残る
黒塗りの下を調べるだけでは足りない。PDF には、ファイル全体を書き直さずに変更を保存する方法がある。PDF の仕様(ISO 32000-1 の 7.5.6 節「Incremental Updates」)は、この方法では変更をファイルの末尾に書き足し、元の内容には手を付けないと定めている。削除したものも、ファイルの中では「削除した」という印が付くだけで、中身は残る。
つまり、文字を消してからこの方法で保存した PDF
は、ビューアでは消えて見えても、ファイルの前半に消す前の版を抱えている。書き足した部分はそれぞれ
%%EOF
という印で終わるので、ツールはこの印を探し、そこまでを切り出して一つの PDF
として開く。開いた旧版の文字を行ごとに今の版と比べ、今の版のどこにも無い行を「以前の版が残っていて、今の版にない文字が見つかりました」として示す。
Web 表示用に最適化(線形化)した PDF は、先頭付近にも同じ印を持つので、その一つは旧版と数えない。旧版を比べるときに読むのは、1 つの版につき 300 ページまでである。
ページの外に置かれた情報
ページの表示とは別の場所にも、消したい情報が入っていることがある。ツールが調べるのは次のものである。
- 文書情報(作成者、タイトル、作成ソフト、作成日時など)と、XMP という形式のメタデータ
- 注釈に書かれた文字と、注釈を書いた人の名前、フォームの入力値
- 添付ファイル
- 既定で非表示になっているレイヤー
- ページの表示範囲の外に置かれた文字(画面には出ないが、抽出はできる)
- 文書に入っている JavaScript の数
PDFium を WebAssembly にしてブラウザで動かす
PDF を開いて命令を読み、書き換えて保存するのは大きな仕事で、ブラウザの JavaScript
には、そのための標準の機能がない。ツールが使っているのは PDFium である。PDFium は Chrome の
PDF 表示に使われている PDF の処理系で、C++ で書かれている(ソース)。これを WebAssembly(ブラウザで動く機械語に近い形式)にしたものを、EmbedPDF が
@embedpdf/pdfium
として公開している(ソース)。
ブラウザに届く WebAssembly のファイルは約 4.6 MB ある。ツールはこれを、画面を動かすのとは別のスレッド(Web Worker)で読み込み、PDF の検査と墨消しをそこで行う。PDF の中身がこのツールのサーバーに送られることはない。レイヤーと XMP メタデータのように PDFium の公開された関数では読めない部分だけは、pdf-lib という JavaScript のライブラリで読んでいる。
範囲の文字を、ファイルから消す手順
範囲を指定して書き出すと、ツールはページごとに次の順で処理する。順番には理由があり、入れ替えると壊れるところがある。
- 注釈の見た目をページの内容に焼き込み(平坦化)、そのうえで注釈をすべて取り除く。付箋の中身や作成者名のように注釈の中に記録された文字は、注釈と一緒に消える。焼き込まずに見た目ごと消す設定もある。
- ページの表示範囲の外にある文字を消す。
- 範囲にすっぽり収まる図形(線や塗り)を消す。
- 範囲に掛かる文字を 1 文字単位で削除し、範囲に掛かる画像は、その部分の画素を黒で塗り替え、最後に黒い四角を描く。図形をまとめた入れ子の部品(フォーム XObject)の中まで辿る。
- 範囲にまだ文字が残るか、4 が失敗したら、そのページを丸ごと画像にする。
3 を 4 より先にしているのは、4 で描く黒い四角も同じ種類の図形だからである。順番を逆にすると、描いたばかりの黒い四角が範囲にすっぽり収まる図形として消えてしまう。
ページの処理が終わったら、新しい空の PDF を作り、ページを 1 枚ずつそこへ取り込んで、ファイル全体を書き出す。以前の版、文書情報、XMP メタデータ、添付ファイル、しおり、JavaScript、フォーム、レイヤーの設定は、ページから辿れないので新しい PDF に入らない。PDFium が新しい文書に自動で入れる作成ソフトと作成日時も、空にしてから保存する。
この取り込み方で、一度つまずいた。画像にするページを、最初は「取り込んだあとで画像のページに差し替える」作り方にしていた。テストで書き出したファイルを調べると、差し替える前のページの中身が、どこからも参照されないまま保存ファイルに残っていて、文字を抜き出せた。今は、画像にするページを最初から新しいページとして作っている。
書き出したファイルは、もう一度開いて確かめる。範囲の中に文字が残っていないか、%%EOF
の数、文書情報、添付ファイル、注釈の数を数え、結果を画面に出す。開発時のテストでは、PDFium
とは別の PDF
ライブラリ(pdf.js)でも書き出したファイルから文字を取り出し、範囲の文字が消えていることを確かめている。
このツールでも消しきれないもの
ここまでの手順でも、消えないものが残る。
- 範囲に一部だけ掛かる線や図形。@embedpdf/pdfium 2.15.0 の墨消しの関数は図形を切り取らず、ツールが消すのは範囲にすっぽり収まる図形だけである。一部だけ掛かるものまで消すと表の罫線や背景が途切れるので、黒い四角の下に残し、その数を結果に出す。
- 文字を図形(アウトライン)に変換した PDF の文字。文字の命令がないので、検査では見つけられない。範囲からはみ出す図形は残るので、確実に消すにはページを画像にする設定を選ぶ必要がある。
- 画像に写っている文字のうち、範囲の外の部分。画像は範囲に掛かる画素だけを黒く塗る。
- 非表示だったレイヤーの中身。レイヤーの設定を捨てるので、書き出したファイルでは表示される。プレビューも書き出しと同じ取り込み方で描いているので、画面で見えたものがそのまま書き出しの見た目になる。
- スキャンした PDF。文字が 1 つもなく画像だけでできている文書は、「画像のみの PDF です」と表示して止まる。パスワードで保護された PDF も開けない。
- ファイル名。書き出すファイルの名前は、元の名前に「_墨消し済み」を付けたものになる。
検査で何も見つからないことは、消し残しが無いことの保証にはならない。塗りつぶしの判定は、傾いていない不透明な四角と画像に絞っていて、入れ子の部品の中にある四角や半透明の塗りは覆いとして数えない。
冒頭で書いた、選択してコピーすると文字が出てくるという確かめ方は、書き出したファイルにも使える。黒い四角の部分を選択してコピーしてみるのが、いちばん手軽である。ただ、それで確かめられるのはその範囲の文字だけで、以前の版やメタデータは見えない。そこまで確かめるには、書き出したファイルをもう一度このツールに読み込み、検査の結果を見ることになる。