PDFファイルの複数ページをマージンなく1ページにまとめるスクリプト

# アップデート (2023/10)

個人的な必要にせまられて、Python3 & pypdf (version 3)で動作する版も用意してみました。

- [pdf_merge_multipages@github](https://github.com/nanigashi-uji/pdf_merge_multipages)
- [pdf_merge_multipages@gitlab](https://gitlab.com/nanigashi_uji/pdf_merge_multipages)

[@ryutarom128さんのこの記事](https://qiita.com/ryutarom128/items/6e5d36efb136f9595f07)を参考にさせていただきました。

# 動機

PDFの複数ページを1ページにまとめる方法(2up, 4up, ... n-up)としては、たとえばmacOSの"プレビュー"などで、
**「ファイル」→「プリント...」→「レイアウト」タブでページ数/枚を選択→「PDFとして保存」**
という方法が挙げられる。ところが、この方法だとまとめられたページとページの間にマージンが入ってしまうのを回避する方法が見当たらなかった。

1ページ目と2ページ目がそれぞれ、
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/284568/25d6c9d0-be85-db2a-98c7-679072d21faa.png" width="40%" alt="元ファイル1ページ目"> <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/284568/709fc413-fcab-6d3e-c45f-95ded642f55a.png" width=40% alt="元ファイル2ページ目">
のようにページ端まで要素を含んだPDFファイルを上記の方法で2upしてみると、
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/284568/488bbc3c-d975-1fa1-3be2-19dfde633a5e.png" alt="macOSのプリント機能でまーじした場合" width=40%>
のようにマージンが設定され、ページとページの間に隙間が空いてしまうことがわかる。lこれでは、たとえば**大きな用紙を分割してスキャンしたPDFファイルをまとめて1ページに戻したい**といった目的の場合の出力としてはちょっと残念な仕上がりである。いったん`Postscript`形式に変換して古くからある`psnup`を使う、という方法もあるとは思うが、マルチバイト文字とかフォントとか形式変換にともなう面倒なこともありうると思うので、PDFを取り扱うライブラリが存在しそうな最近のスクリプト言語で変換スクリプトを書くことにした。

# 仕様

ちょっとだけ調べてみたところ、`Python`だと[PyPDF2](https://github.com/mstamy2/PyPDF2)というライブラリが存在しているようなので、`Python`で実装してみることにする。仕様としては、`macOS`のプリントメニューのレイアウトタブで変更可能な点の主なものは、コマンドラインオプションとして選べるようにすることにした。

 - 1ページにまとめるページ数を選べるようにする。
 - ページを並べていく方向を何通りか選べるようにする。
 - ページサイズに関しては、シンプルに元のページサイズを維持する。(例えばA4サイズのファイルを2upするとA3サイズのPDFファイルが出力される。)
 - ページの回転については+-90度,180度の回転が選べるようにする。また、縦置きページと横置きページが混じっている場合に、それを揃える動作も選べるようにする。
 - サイズが違うページが混在している場合に、サイズを揃えたり、ページ位置の揃え方も選べるようにした。
 - PDFファイルのメタ情報を入力ファイルから引き継ぐことも可能なようにする。

# 実装

ファイルの置き場は下記。

  - [pdf_merge_multipages@github](https://github.com/nanigashi-uji/pdf_merge_multipages)
  - [pdf_merge_multipages@gitlab](https://gitlab.com/nanigashi_uji/pdf_merge_multipages)

# 実行例

2行3列でページをまとめる場合の例。

```(実行例)
% ./pdf_merge_multipages.py -o merge_test.pdf -c 3 -l 2 test_samples/input_landsc
ape_1.pdf test_samples/input_portleit_1.pdf
```

# 詳しい使い方など

## 動作要件

  - Python: 2.7で動作確認
  - PyPDF2: https://pythonhosted.org/PyPDF2/
    - version 1.26.0 で動作確認

## 使い方

```
usage: pdf_merge_multipages.py [-h] [-output filename] [-columns n_h] [-lines n_v] [-page-order opt]
                               [-rotation opt] [-valign opt] [-align opt] [-metainfo opt] [-title text]
                               input-file [input-file ...]

Merge multiple mages in PDF files w/o gap.

positional arguments:
  input-file        Input PDF file(s)

optional arguments:
  -h, --help        show this help message and exit
  -output filename  Output file
  -columns n_h      # of columns of merged pages (default = 2)
  -lines n_v        # of lines of merged pages (default = 1)
  -page-order opt   Page order (choices=left2right[default], left2bottom, right2left, right2bottom)
  -rotation opt     Page orientation (choices=none[default], flip, right, left, auto, rauto)
  -valign opt       Page fitting (choices=resize, none[default], top, bottom, center, fit)
  -align opt        Page fitting (choices=resize, none[default], right, left, center, fit)
  -metainfo opt     Meta data for marged file (choices=full[default], none, partial, short)
  -title text       set title in meta data for marged file (Default: output file name)
```

## オプション引数

  - `-output filename`: 出力ファイルの名前。指定しない場合のデフォルトは、`a.out.pdf`
  - `-columns nh`, `-lines n_v`: レイアウトの指定: 入力PDFファイルのpageを、横n_h列×縦n_v行に配置する。指定しない場合のデフォルトは、2x1 ("2up")
  - `-page-order option`: ページをレイアウトする配置する順番の指定。
    - `left2right`: 入力ファイルの最初のページを左上に配置。入力ファイルの続くページを右に順次配置していき、右端に到達したら、直下の段の左端または次ページの左上から右へ順次配置していく。(デフォルト)
    - `left2bottom`: 入力ファイルの最初のページを左上に配置。入力ファイルの続くページを下に順次配置していき、下端に到達したら、右隣の列の上端または次ページの左上から下へ配置していく。
    - `left2top`: 入力ファイルの最初のページを左下に配置。入力ファイルの続くページを上に順次配置していき、上端に到達したら、右隣の列の下端または次ページの左下から上へ配置していく。
    - `right2left`: 入力ファイルの最初のページを右上に配置。入力ファイルの続くページを左に順次配置していき、左端に到達したら、直下の段の右端または次ページの右上から左へ順次配置していく。
    - `right2bottom`:入力ファイルの最初のページを右上に配置。入力ファイルの続くページを下に順次配置していき、下端に到達したら、左隣の列の上端または次ページの右上から下へ配置していく。
    - `right2top`: 入力ファイルの最初のページを右下に配置。入力ファイルの続くページを上に順次配置していき、上端に到達したら、左隣の列の下端または次ページの右下から上へ配置していく。
  - `-rotation option`: ページの回転
    - `none`:  配置される入力ファイルの各ページを回転しない。(デフォルト)
    - `flip`:  配置される入力ファイルの各ページを上下反転する。
    - `right`: 配置される入力ファイルの各ページを右に90度回転する。
    - `left`:  配置される入力ファイルの各ページを左に90度回転する。
    - `auto`:  出力ページの先頭に配置される入力ファイルの各ページが縦置き(横置き)の場合、出力ファイルの同じページに配置される他の入力ファイルの各ページが横置き(縦置き)の場合には左に90度回転して配置し、そうでない場合には回転せずに配置する。
    - `rauto`:　出力ページの先頭に配置される入力ファイルのページが縦置き(横置き)の場合、出力ファイルの同じページに配置される他の入力ファイルの各ページが横置き(縦置き)の場合には右に90度回転して配置し、そうでない場合には回転せずに配置する。
  - `-valign opttion`: 配置されるページの縦方向位置調整の指定
    - `none`,`bottom`: 下揃え (デフォルト)
    - `center`:        上下中央揃え
    - `top`: 上揃え
    - `resize`: 出力ページの先頭に配置される入力ファイルのページの縦方向のサイズに合わせて縮小する。`-algin resize`または`-algin fit`と同時に指定された場合には、より縮小率の小さくなる可能性があるが、その場合でもレイアウト間隔は、出力ページの先頭に配置される入力ファイルのページの縦方向のサイズとなる。
    - `fit`: 出力ページの先頭に配置される入力ファイルのページの縦方向のサイズに合わせて縮小する。`-algin resize`または`-algin fit`と同時に指定された場合には、より縮小率の小さくなり、レイアウト間隔も出力ページの先頭に配置される入力ファイルのページの縦方向のサイズより小さくなることがある。

  - `-align opttion`: 配置されるページの横方向位置調整の指定
    - `none`,`left`: 右揃え (デフォルト)
    - `center`: 左右中央揃え
    - `right`: 右揃え
    - `resize`: 出力ページの先頭に配置される入力ファイルのページの横方向のサイズに合わせて縮小する。`-valgin resize`または`-valgin fit`と同時に指定された場合には、より縮小率の小さくなる可能性があるが、その場合でもレイアウト間隔は、出力ページの先頭に配置される入力ファイルのページの横方向のサイズとなる。
    - `fit`: 出力ページの先頭に配置される入力ファイルのページの横方向のサイズに合わせて縮小する。`-valgin resize`または`-valgin fit`と同時に指定された場合には、より縮小率の小さくなり、レイアウト間隔も出力ページの先頭に配置される入力ファイルのページの横方向のサイズより小さくなることがある。
  - `-metainfo options`: 出力ファイルのメタ情報の指定
     - `full`: 入力ファイルのメタ情報を結合したものに追記して生成。(デフォルト)
     - `partial`: 入力ファイルのメタ情報を結合したものの一部に追記して生成
     - `short`: '/Title'、'/Creater', '/Producer'のみ生成
     - `none`: 出力ファイルのメタ情報を生成しない.(PyPDF2のデフォルト値が指定される。)
  - `-title text`: 入力ファイルのメタ情報のタイトルを指定する。(デフォルトは出力ファイル名)