仕事で各ページのサイズが異なる(1ページ目がA4で、2ページ目がB5 など)PDFファイルが必要になりました。
各ページごとにサイズが異なるPDFファイルを作成できるようなツールはパッと見た感じ手近にはなかったので、Pythonで自作しました。
準備するもの
なお、使用した環境はWindows 10 Home 1903Verです。
- Python(3.7.5)
- PyPDF2
- ReportLab
- tqdm(使わなくてもいいです)
PyPDF2は空のPDFページを作ることができます。ただし、作成したページに文章や画像など任意のオブジェクトを書き込むことはできません。
ReportLabは直接プログラムコードから文字や図形などを書き込んだPDFを作ることができます。ただし、(自分の見た限り)各ページのページサイズが異なるPDFを作ることはできません。
なので、大まかには
- ReportLabで1ページだけのPDFファイルを作成する
- PyPDF2.PdfFileReaderで1を読み込む
- PyPDF2.PdfFileWriterで作成したPDFに、2を貼り付ける
という手順をとります。ちょっと面倒くさいですね。
コード
今回A0~C10(Cなんてあるんですね、はじめて知りましたが)まですべてのサイズのページをまんべんなく突っ込みたいので、reportlab.lib.pagesizes
の中で定義されているページサイズをすべて取得してみました。
import random
import os
from pathlib import Path
from tqdm import tqdm
from PyPDF2 import PdfFileReader, PdfFileWriter
from reportlab.pdfgen import canvas
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
import reportlab.lib.pagesizes
YU_GOTHIC = Path(filter(lambda n: (Path(n) / "Fonts" / "YuGothB.ttc").exists(), os.environ.get("PATH").split(";")).__next__()) / "Fonts" / "YuGothB.ttc"
def makepage(writer, size, name):
A4 = reportlab.lib.pagesizes.A4
# テキストの書かれたPDFを作る
c = canvas.Canvas("base.pdf", pagesize=A4)
pdfmetrics.registerFont(TTFont("YU", YU_GOTHIC))
c.setFont('YU', 20)
c.drawString(12, 12, f"This is {name} page.")
c.drawString(12, A4[1] - 50, f"This is {name} page.")
c.rect(0, 0, A4[0], A4[1])
c.showPage()
c.save()
# Create Page
with open("base.pdf", mode="rb") as f:
r = PdfFileReader(f)
p = writer.addBlankPage(size[0], size[1])
scale = [a / b for a, b in zip(size, A4)]
p.mergeTransformedPage(r.getPage(0), [scale[0], 0, 0 , scale[1], 0, 0] , True)
if __name__ == "__main__":
writer = PdfFileWriter()
sizes = list(filter(lambda n: type(eval(f'reportlab.lib.pagesizes.{n}')) == tuple, dir(reportlab.lib.pagesizes)))
s = random.sample(sizes, 10)
for n in tqdm(s):
makepage(writer, eval(f"reportlab.lib.pagesizes.{n}"), n)
with open("multisize.pdf", mode="wb") as f:
writer.write(f)
気をつけたところ
ReportLabのCanvasについて
ReportLabでは、Canvas
クラスを使うことでPDFファイルを作成することができます。
ただし、ここで取得できるキャンバスでは左下が原点(X:0 Y:0)になっていますので、図形を描画する際に注意が必要です。
それが嫌な場合は、Canvas
クラスのコンストラクタにbottomup=False
を追加します。
Canvasオブジェクトのメソッドについて
Canvas
オブジェクトのメソッドrect()
では、次のようにX, Y座標、縦横幅のあとにいくつか引数をとります。
def rect(self, x, y, width, height, stroke=1, fill=0):
"draws a rectangle with lower left corner at (x,y) and width and height as given."
ただ、この引数のstorke
, fill
などという値は、線の幅や色などの値を示しているわけではなく、色を塗るかどうかのBoolean型です。
これについてはreportlab/pdfgen/canvas.py
の44行目に以下の記述があります。
PATH_OPS = {(0, 0, FILL_EVEN_ODD) : 'n', #no op
(0, 0, FILL_NON_ZERO) : 'n', #no op
(1, 0, FILL_EVEN_ODD) : 'S', #stroke only
(1, 0, FILL_NON_ZERO) : 'S', #stroke only
(0, 1, FILL_EVEN_ODD) : 'f*', #Fill only
(0, 1, FILL_NON_ZERO) : 'f', #Fill only
(1, 1, FILL_EVEN_ODD) : 'B*', #Stroke and Fill
(1, 1, FILL_NON_ZERO) : 'B', #Stroke and Fill
rect
メソッドはこのPATH_OPS
という変数を参照しており、stroke
とfill
という二つの引数はこの配列にある値二つに対応しているようです。
PyPDF2.PageObject
のmergeScalePage
について
PyPDF2.PdfFileWriter
でPDFに新しいページを作成するには、PdfFileWriter#addBlankPage()
メソッドを使います。
ここに別のページオブジェクトを貼る場合はPageObject#merge***Page()
を使います。
縮尺を変えるだけだと一見mergeScaledPage()
あたりでいけそうですが、このメソッドのscale
は一つの値しか受け付けないため、縦と横の縮尺が違う場合には利用できません。
縦と横の縮尺が異なる場合は、mergeTransformedPage()
を使えば良いです。というのも、mergeScaledPage()
は、縦横の倍率を同一値にしたmergeTransformedPage()
を呼んでいるだけなので。
もし縦を1.5倍、横を2倍の倍率に設定したい場合は、次のようにすれば良いです。
page.mergeTransformedPage('追加したいPageObject', [2, 0, 0 , 1.5, 0, 0] , True)
現存するすべてのページサイズの値を取得するには
reportlab/lib/pagesizes.py
には、すべてのページサイズの値を定義したtupleが記述されていますので、これをdir
関数で取得しています。
ただし、このファイルにはページの縦横を逆転させるportlait
、landscape
などの関数も定義されているため、filter
関数でそれらを取り除きます
list(filter(lambda n: type(eval(f'reportlab.lib.pagesizes.{n}')) == tuple, dir(reportlab.lib.pagesizes)))