【Python】見開きのPDFを単ページに分割する方法

# 概要
[pypdf](https://pypdf2.readthedocs.io/en/latest/index.html)を使い、下図のような見開きのPDFを単ページに分割する方法を紹介します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1599258/3824f12c-4d26-3046-effd-44ee2cde6566.png)

# 各種インポート
```shell:インストール
pip install pypdf
```
```Python:各種インポート
from pypdf import PdfWriter, PdfReader, PageObject, Transformation
import copy
```
実行時の各種バージョン
| Name | Version |
|:-:|:-:|
| pypdf | 3.2.0 |

:::note warn
注意
pypdf 3.0.0以降のバージョンで、pypdfの変数名などが大幅に変更されているので注意してください。本記事は、pypdf 3.0.0以降で機能します。
(参考:https://github.com/py-pdf/pypdf/commit/a4629d3dbdef2759ff6059446fcffd13ecf6a396)
:::

# サンプルファイル
サンプルで使用した見開きのPDFファイルです。よろしければシミュレーションとしてお使いください。

https://drive.google.com/file/d/1A3iElljQEejOA12-fw17VOs49Rwe7RwJ/view?usp=share_link

# 実装コード
Google Colabで作成したコードは、[こちら](https://colab.research.google.com/drive/19OMVMA9Fci57YphWNhcLynN8SkPy2tnS?usp=share_link)にあります。

# 方法
2つの方法を紹介します。
どちらも出力されたPDFの表示に変わりはありません。方法1の方がコードがシンプルですが、以下の違いがあります。

方法1と方法2の違い概要
* 方法1ではmediaboxを見開きのページの座標で表示
* 方法2ではmediaboxを単ページ単位の座標で表示

方法1と方法2の違いを図で説明
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1599258/54142204-a920-f4d5-0f15-a83d2d06f367.png)

単純に単ページで表示されることのみが目的の場合は方法1で十分です。ただ、単ページに分割後にpypdfなどで編集される方は、mediaboxが単ページ単位の方が編集しやすいと思うので、方法2をお勧めします。ちなみに、単ページに分割後のPDFでもmediaboxの情報は残ります。
## 方法1(mediaboxを見開きのページの座標で表示)
単純に単ページで表示されることのみが目的の場合はこちらで十分です。

```python:
from pypdf import PdfWriter, PdfReader, PageObject, Transformation
import copy


infile = './sample_2page.pdf'  # 見開きのPDF
outfile = './sample_divide_1page.pdf'  # 単ページのPDFの保存ファイル

reader = PdfReader(infile)
writer = PdfWriter()

for page in reader.pages:
    p1 = copy.copy(page)
    p2 = copy.copy(page)

    p1.mediabox.right = page.mediabox.right / 2
    p2.mediabox.left = page.mediabox.right / 2

    writer.add_page(p1)
    writer.add_page(p2)

with open(outfile, mode='wb') as f:
    writer.write(f)
```
コードの説明
1. 見開きページの左ページと右ページ用に``p1 = copy.copy(page)``, ``p2 = copy.copy(page)``を用意します。
1. ``p1``は見開きの表示領域になっているので、左半分になるように変更します。``p1.mediabox.right = page.mediabox.right / 2``で、見開きページの表示領域(mediabox)の半分の座標が、``p1``の中のmediaboxの右端の座標に位置するように変更します。これにより左半分のみ表示されます。
1. 同様に、``p2``も見開きの表示領域になっているので、右半分になるように変更します。``p2.mediabox.left = page.mediabox.right / 2``で、見開きページのmediaboxの半分の座標が、``p2``の中のmediaboxの左端の座標に位置するように変更します。これにより右半分のみ表示されます。
1. ``writer.add_page(p1)``, ``writer.add_page(p2)``で、単ページを``writer``に追加します。

## 方法2(mediaboxを単ページ単位の座標で表示)
pypdfなどで編集される方はmediaboxが単ページ単位の方が編集しやすいので、こちらの方法も紹介します。

```python:
from pypdf import PdfWriter, PdfReader, PageObject, Transformation
import copy


infile = './sample_2page.pdf'  # 見開きのPDF
outfile = './sample_divide_1page.pdf'  # 単ページのPDFの保存ファイル

reader = PdfReader(infile)
writer = PdfWriter()

for page in reader.pages:
    p1 = copy.copy(page)
    p2 = copy.copy(page)

    # mediaboxの左側(p2.mediabox.left)が0になるように平行移動
    op = Transformation().translate(tx=-page.mediabox.right / 2)
    p2.add_transformation(op)

    p1.mediabox.right = page.mediabox.right / 2
    p2.mediabox.right = page.mediabox.right / 2

    writer.add_page(p1)
    writer.add_page(p2)

with open(outfile, mode='wb') as f:
    writer.write(f)
```
コードの説明
1. 見開きページの左ページと右ページ用に``p1 = copy.copy(page)``, ``p2 = copy.copy(page)``を用意します。
1. ``op = Transformation().translate(tx=-page.mediabox.right / 2)``で、左方向に見開きの横幅の半分だけ平行移動する変換を用意します。
1. ``p2.add_transformation(op)``で``op``を適用します。これにより、今``p2``の表示領域は**右ページ+余白**になっています。(p2のmediaboxは平行移動していないので注意してください。)
1. ``p1``は見開きの表示領域になっているので、左半分になるように変更します。``p1.mediabox.right = page.mediabox.right / 2``で、見開きページの表示領域(mediabox)の半分の座標が、``p1``の中のmediaboxの右端の座標に位置するように変更します。これにより左半分のみ表示されます。
1. ``p2``は**右ページ+余白**になっているので、余白を表示しないために、``p2``のmediaboxを変更します。``p2.mediabox.left = page.mediabox.right / 2``で、見開きページのmediaboxの半分の座標が、``p2``の中のmediaboxの**右端の座標**に位置するように変更します。これにより右半分のみ表示されます。
1. ``writer.add_page(p1)``, ``writer.add_page(p2)``で、単ページを``writer``に追加します。

# まとめ
pypdfを使い、見開きのPDFを単ページに分割する方法を紹介しました。最近、pypdf 3.0.0で変数名などが変更され、私自身PDFを見開きにする機会があり、色々とハマりどころがあったため本記事を書きました。mediaboxは、PDF保存後も残っているので次回編集する時にハマりやすいので、場合によって方法2も使っていきましょう。この記事が誰かの役に立てば幸いです。

# 参考

https://pypdf2.readthedocs.io/en/latest/user/cropping-and-transforming.html

https://gammasoft.jp/blog/pdf-divide-by-python/