cross_entropy_error

#ロードバイク #山形

pythonでPDFを操作する場合には、PyMuPDFがいいんでない?という話

pythonでPDFを操作したい

pythondjango)で会議用の管理システムを開発したのですが、最も実装したかった機能が資料のPDFを結合したり、通し番号を入れたりする機能でした。

pythonのPDF用ライブラリを検索すると、よく出てくるのがPyPDF2やpdfrwですが、今回のシステムで使用したのがPyMuPDFです。

当初はPyPDF2、pdfrwを使用したコードを書いたのですが、PDFに組み込まれているJavaScriptがどうしても処理上の邪魔になったりで思うようにバグが消せなかったところで、そもそも各々のPDFファイルに組み込まれたJavaScriptをはじめに削除することができるPyMuPDFを使用することで一気に問題が解消しました。

PDFを操作する際の問題点

今回実装したPDFの操作機能には、主に以下の前提条件が与えられます。

  • 資料となるPDFはあらゆる人間があらゆるソフト(またはスキャン)で作成しアップロードしてくる。
  • 資料のPDFは様々な大きさ、アスペクトが与えられている。
  • さらに、JavaScriptによって印刷条件や表示時の回転処理等が与えられている。

上記の前提条件を踏まえたうえで、任意のPDFファイルを正しく結合させる必要があります。

PyPDF2やpdfrwでの実装

初めにPyPDF2とpdfrwを使用する実装を検討します。この方法ではどちらか一方のライブラリで完結することがまずできません。ネット上を検索すると出てくる方法としては、

  1. PyPDF2で各PDFファイルを結合する。
  2. 結合したPDFファイルを再度読み込み、pdfrwのcanvasで各ページのmediaboxと同一の空白ページを1枚ずつ順番に生成し、もともとの該当ページの内容を転記、さらに追記したい内容(今回は通しページ番号)をさらに追記する工程をループしていく。

という方法です。

この方法は規格が決まったPDFファイルではうまくいきますが、回転が加わっていたり、印刷用のJavaScriptが組み込まれているようなPDFファイルではうまくできません。(たとえば、回転角を事前に取得してページ番号を挿入位置や文字の回転角度を逆算して場合分けしてやる必要性があったりコードも煩雑になります。)

当然、様々なPDFファイルが混在する環境では望んだ結果が得られません。

.scrub()を使用してPDFの処理前に事前にJavaScriptを削除する。

上記の問題はほとんどがJavaScriptが望んでいない挙動を起こすことから生まれますので、とにかく対象とするPDFを結合する処理を行う前に、余計な情報を削除してやる必要がありますが、PyMuPDFでは

import fitz

idoc =  fitz.open(file_name)
idoc.scrub()

基本的にこれだけで事足ります。.scrub()で余計なJavaScriptを削除したら、あとは公式ドキュメントどおりに.insertText()メソッドで

target_x, target_y = media_wsize/2,media_hsize-20
p = fitz.Point(target_x,target_y)
rc = read_odoc.insertText(
        p,  # bottom-left of 1st char
        insert_text,  # the text (honors '\n')
        fontname="helv",  # the default font
        fontsize=14,  # the default font size
        rotate=0,  # also available: 90, 180, 270
        color=(0,0,0),
        )

書き足したい文字を挿入するだけです。

PyMuPDFのドキュメントについて

PyMuPDFの情報については日本語ではほとんど得られなかったので、公式ドキュメントを読むのが一番いいと思います。

PyMuPDF Documentation