返回列表

PDFの文字透かし除去をどう改善したか: タイトル誤削除から精密削除までの振り返り

当前专题

PDF
PDF
透かし削除
技術振り返り
PDF 文字透かし
PDF 透かし削除
コンテンツストリーム解析

2026-04-06

8分钟

墩墩

オンライン版: https://www.douyacun.com/pdf/remove-watermark

PDFの文字透かしを消すというと、最初は「透かしの文字列を見つけて削除すればよい」と考えがちです。ですが、実際にやってみると、その前提こそが不安定な結果を生む原因でした。

本当に難しいのは、2つの条件を同時に満たさなければならないことです。まず透かしを正しく見つけること。そして、その透かしに対応する描画命令だけを正確に削除することです。どちらか一方でも粗いと、出力はすぐに壊れます。

今回の最適化では、主に3つの問題を改善しました。重要なタイトルまで誤って消してしまうこと、候補がUI上で判別しづらいこと、そしてユーザーが1文字ずつ確認しなければならず操作コストが高かったことです。

なぜPDFの文字透かしは画像透かしより難しいのか

画像透かしは、多くの場合、画像オブジェクトや図形として扱えます。これに対して文字透かしは、PDFのコンテンツストリーム内にある TjTJ のようなテキスト描画命令として存在し、場合によっては再利用される Form XObject の中に入っています。

ここが重要です。同じ見た目の文字列が、透かしにも、見出しにも、本文中の正当なテキストにもなり得ます。システムが文字内容だけを見ていると、それらを安定して区別できません。

しかもPDF内部では、文字はフォント固有のマッピングで符号化され、ページをまたいで再利用され、行列、透明度、色を変えて描画されます。つまり「この文字列を消す」という発想はPDFにとって本質的な操作ではなく、複雑な描画モデルの上に乗った推測にすぎません。

初期実装で何が問題だったのか

この問題がはっきり見えたのが、青衣说 というサンプルでした。透かし文字も 青衣说、ページ上の大きな正規タイトルにも 青衣说 が含まれていました。

元の文字透かし PDF

初期の削除方式は、raw_text とグローバルなバイト列置換に強く依存していました。そのため、削除ロジックが対象のバイト列に一致すると、透かしだけでなく、同じ、または似たエンコードを持つタイトルまで消してしまうことがありました。

この方式には、実運用上いくつもの問題がありました。

  1. 疑わしい文字候補は見つけられても、削除粒度が粗すぎる
  2. 候補が1文字単位で並ぶことが多く、何度もクリックする必要がある
  3. 元の低透明度をそのまま再現すると、見た目は忠実でも判別しづらい
  4. 同じような透かし断片が多いと、手動確認が遅くなりやすい
  5. 高信頼候補と曖昧な候補の差がUI上で十分に表現されない

初期の候補表示

ここで分かったのは、「検出できる」ことと「安全に削除できる」ことはまったく別だという点です。弱かったのは認識よりも、削除の側でした。

文字透かしの検出をどう改善したか

最初の改善は、最終的な見た目だけではなく、PDFの構文により近い層を見ることでした。

具体的には、コンテンツストリームから TjTJ などの実際のテキスト描画命令を走査し、その命令ごとに周辺情報を取り出します。たとえば、フォント、文字サイズ、変換行列、透明度、塗りや線の色、ページ本体の contents 由来か Form XObject 由来か、といった情報です。

さらに、候補をできるだけ可読なテキストとして扱えるよう、デコードも強化しました。オペランドを単なる生バイト列として扱うのではなく、可能な場合は ToUnicode や CMap などのフォントマッピングを使って復号します。これにより、以前は読めないバイト断片としてしか見えなかった候補でも、実際の文字列として扱えるケースが増えました。

その上で、文字透かし候補に対してスコアリングを導入しました。判断は1つの特徴だけでは行いません。透かしに典型的な複数の信号を組み合わせます。

  1. 低い透明度
  2. 斜め配置や繰り返し配置
  3. ページをまたいで近いフォントサイズやフォント利用が続くこと
  4. 透かしらしい淡い色
  5. 繰り返し出現するパターン
  6. 位置の安定性
  7. Form XObject 由来であること

この結果、高信頼の候補はデフォルトで選択済みにしつつ、確信が弱い候補も確認用として返せるようになりました。曖昧なケースに対して無理に断定しない設計です。

なぜグローバル置換から構文レベルの精密削除へ移行したのか

ここが今回もっとも重要な改善点です。

従来の削除処理は、要するに「透かし関連らしいバイト列が見つかったら、見つかる場所を置換する」という発想でした。しかしPDFでは、このやり方は本質的に危険です。どのオブジェクトを編集しているのか分からず、ただバイト列が一致したことしか分からないからです。

そこで、新しい方式では、検出時に命令単位の識別情報を保持し、適用時にもそれを再利用するようにしました。各文字候補に対して、たとえば次のような情報を持たせます。

  1. stream_xref
  2. op_index
  3. operator (Tj または TJ)
  4. 行列
  5. フォント情報
  6. 復号済みテキスト
  7. 透明度や色

削除時には、PDF全体に対して検索置換をするのではなく、対象の stream を再度パースします。その上で、ユーザーが選択した候補を同じ stream 内の正確なテキスト描画命令に対応付け、その命令だけを書き換えます。

この切り替えが、典型的な誤削除を防ぎます。透かしとタイトルの両方に 青衣说 が含まれていても、それぞれが別の命令、別の stream に存在するなら、削除ロジックは選択された透かし側だけを編集し、タイトルには触れません。

要点は明快です。精度は、より賢く推測することから生まれるのではありません。どのPDFオブジェクトを変更しているのかを正確に知ることから生まれます。

なぜフロントエンドの操作性も同時に改善する必要があったのか

バックエンドの精度が上がっても、それだけでは十分ではありません。ユーザー側の確認体験も速く、分かりやすくなければ実用性は上がりません。

そこで候補表示は、実際の透かしの見た目に近い形を保つようにしました。色、傾き、サイズを視覚的な手掛かりとして残すことで、ユーザーは透かしを素早く見分けやすくなります。一方で、極端に低い透明度までそのまま再現すると読みにくくなるため、可読性が落ちないよう表示側で補正しています。

また、削除対象がひと目で分かるよう、取り消し線による選択フィードバックも追加しました。

レビューコストを下げるために、まとめて操作できる仕組みも拡張しています。

  1. 全選択
  2. 全解除
  3. ドラッグによる一括選択、一括除外
  4. ダブルクリックによる同種透かしのグループ切り替え

これは、ページ全体に斜めに繰り返し配置された文字透かしで特に効果があります。代表的な1つを見つけたあと、同じ操作を何十回も繰り返す必要がなくなります。

最適化後に実際どう変わったか

改善点は、単に削除できる透かしが増えたことではありません。より重要なのは、システムの振る舞いが予測しやすくなったことです。

最適化後は、次のような変化がありました。

  1. 検出候補が、曖昧な小さな断片の列ではなく、実際の透かしに近い見え方になった
  2. 文字単位のクリック作業だけに頼らず、より短時間で確認できるようになった
  3. 同じ文言を含むタイトルや本文を巻き込む可能性が大きく下がった

透かし削除後のプレビュー

最後の点こそ、本当の品質基準です。正当な本文を壊す可能性がある透かし除去機能は、実用的とは言えません。

適用しやすい範囲と現在の限界

この方式がもっとも効果を発揮するのは、PDF内で本物のテキストオブジェクトとして存在する文字透かしです。

一方、スキャンPDFのように、透かしが画像に焼き込まれているケースには向きません。その場合は、テキスト描画命令ではなく、画像領域での処理が必要になります。

また、複雑なテンプレート、特殊なフォントエンコーディング、強いオブジェクト再利用、独自性の高いPDFでは、依然として人手確認が必要になることがあります。私たちは、その状況を無理に隠すよりも、曖昧さを曖昧さとして見せる方を選びます。

まとめ

PDFの文字透かし除去は、単に文字を消す処理ではありません。実用的な体験にするには、正確な検出、意味のあるスコアリング、構文レベルの精密削除、そして効率よく確認できるUIが一体で機能する必要があります。

初期版が不安定だった最大の理由は、削除処理が何を消しているのかを正確に理解していなかったことにあります。現在の方式が強くなったのは、検出から適用まで命令の文脈を保持し、対象となるPDFテキスト命令そのものを直接編集するようになったからです。

つまり、「それらしく見えるバイト列を消す」段階から、「ユーザーが選んだ透かしだけを正確に消す」段階へ進んだ、ということです。