📜

見開きPDFページの分割方法と実装時の注意点

2025/01/07に公開

書籍のような見開きページのデジタル化資料では、PDFのページ番号と資料のページ番号が一致しないことがあります。この問題に対処するため、PDFの見開きページを分割して、資料の1ページがPDFの1ページになるように加工しました。この記事では、実装時に遭遇した課題も含めて記録として残します。

実装したコード

import pymupdf


src = pymupdf.open("input.pdf")
doc = pymupdf.open()

for spage in src:
    r = spage.rect
    # 横長のページなら分割
    if r.width > r.height:
        # 右半分
        right_page = doc.new_page(-1, width=r.width / 2, height=r.height)
        right_rect = pymupdf.Rect(r.width / 2, 0, r.width, r.height)
        right_page.show_pdf_page(right_page.rect, src, spage.number, clip=right_rect)
        # 左半分
        left_page = doc.new_page(-1, width=r.width / 2, height=r.height)
        left_rect = pymupdf.Rect(0, 0, r.width / 2, r.height)
        left_page.show_pdf_page(left_page.rect, src, spage.number, clip=left_rect)
    else:
        # 縦長のページはそのまま追加
        page = doc.new_page(-1, width=r.width, height=r.height)
        page.show_pdf_page(page.rect, src, spage.number)

doc.save("output.pdf", garbage=3, deflate=True, clean=True)

src.close()
doc.close()

解説

PDFの座標系について

PDFの座標系は、左下が原点(0,0)となっています。Rectは、この座標系上で矩形を定義するためのクラスで、(left, bottom, right, top)の4つの値で指定します。

ページの向きの判定

if r.width > r.height:

今回扱ったファイルでは縦長と横長が混在していたため、width と height を比較することで、ページが横長かどうかを判定しています。縦長の場合は書籍の1ページ分のため分割不要としています。

分割位置の指定

right_rect = pymupdf.Rect(r.width / 2, 0, r.width, r.height)

ページの分割には以下の座標を使用します:

  • 右半分の場合:(幅の半分, 0, 幅, 高さ)
  • 左半分の場合:(0, 0, 幅の半分, 高さ)

show_pdf_page に clip を指定することで、ページの表示領域を制御しています。

ページの追加順序

今回扱ったPDFファイルは縦書き日本語のPDFファイルであったため、右半分を先に追加し、その後に左半分を追加しています。横書きの場合は左を先に追加するようにします。

保存時のオプション

以下を参考に適当な値を設定します。

https://pymupdf.readthedocs.io/ja/latest/document.html#Document.save

実装における課題

テキスト抽出の問題

最初 PyPDFLangChain の PyPDFLoader の組み合わせで実装したのですが、ドキュメント検索を行った際に分割した見えない側のページのテキストも重複して抽出してしまう問題があったため原因を調べたところ以下のことが判明しました。

暫定案

今回は暫定的に PyMuPDF と LangChain の PyMuPDFLoader の組み合わせを使用することにしました。

上記のような挙動になることを頭に入れつつ、見た目だけでなく中身も含めて分割するなど別の方法も検討したいと思います。

参考リンク

Discussion