morikomorou’s blog

自分が学んだことなどの備忘録的なやつ

【python】Reportlabを使いこなして自由自在にPDFを生成しよう


はじめに

先回の記事でReportlabというPDF生成ライブラリの使い方を簡単に説明しました。

ただどうにもその使い方だと使いづらいところがあり、実用には向きそうもありませんでした。
この前以下の記事を読んだ際、Reportlabにまだまだ有用な機能があることを知ったのでまとめてみようと思います。

非常に簡単にPDFが作れそうです。




前回の記事まとめ

前回はReportlabのcanvas.drawStringというメソッドを使用してPDFファイルの指定した位置に文字を入力したりしていました。ただ、毎回文字を入力する位置を指定しないといけないということと、長い文章の時は自動で折り返されずはみ出してしまうという問題があり非常に面倒なやり方でした。

PLATYPUS

Reportlabにはplatypusというライブラリがあり、これを使えば上記の問題は解決するようです。
platypusでは、テンプレートとflowableというものを使うことで自由自在に文章や画像や表をレイアウトすることができるようです。

platypusを用いたPDFファイル生成の流れ

platypusを用いたページの具体的な構成は以下のようになっています。


参照: Chapter 5: Platypus - ReportLab Docs

ここで、軽く出てくる要素について説明します。
DocTemplates:これは作りたい資料全体のコンテナ的なクラス
PageTemplates:FrameやFlowableをどう配置するかページ単位のテンプレート
Frames:Flowablesを配置できる領域を規定したもの。これらをどこに配置するのかをPageTemplatesで指定する
Flowables:文字列や画像等の要素。わざわざ位置を指定する必要がなく、追加された順番にテンプレートに従って自動で配置される要素。要素同士が重ならないように位置も自動で調整され、文字列の場合自動で折り返しもしてくれるので便利

Platypusの使い方

簡単な例として全ページ同じレイアウトを使用するようなPDFファイルを生成してみます。

DocTemplateの準備

DocTemplatesにはいくつか種類があるみたいですが、今回はBaseDocTemplateというクラスを使用します。

from reportlab.platypus import BaseDocTemplate, PageTemplate
from reportlab.lib.pagesizes import A4, mm, portrait
from reportlab.pdfbase import pdfmetrics
from reportlab.platypus.frames import Frame


# DocTemplateの生成
file_path = "./hello_world.pdf"
doc = BaseDocTemplate(file_path, 
    title="テスト",
    pagesize=portrait(A4),
    )
width, height = A4   # 595px, 842px

BaseDocTemplateの引数には出力ファイルパス、タイトル、サイズを入力します。
ここにFrameの配置が定義されたPageTemplateを登録します。

PageTemplateの準備

先の図のtwo columnレイアウトみたいにしてみましょう。ページの左右半分ずつのFrameを作成し、PageTemplateにリストとして渡せばOKです。

from reportlab.platypus import BaseDocTemplate, PageTemplate
from reportlab.lib.pagesizes import A4, mm, portrait
from reportlab.pdfbase import pdfmetrics
from reportlab.platypus.frames import Frame


# DocTemplateの生成
file_path = "./hello_world.pdf"
doc = BaseDocTemplate(file_path, 
    title="テスト",
    pagesize=portrait(A4),
    )
width, height = A4   # 595px, 842px

# Frameの定義
show = 1 #Frameの枠を表示
frames = [
    Frame(15*mm, 15*mm, width / 2 - 15*mm, height - 30*mm, showBoundary=show),
    Frame(width / 2, 15*mm, width / 2 - 15*mm, height - 30*mm, showBoundary=show),
]

# PageTemplateの定義とDocTemplateへの登録
page_template = PageTemplate("test", frames=frames)
doc.addPageTemplates(page_template)

PageTemplateの引数にはそのページレイアウトの名前と、テンプレートとして配置するフレームのリストを渡します。
DocTemplateへの登録は.addPageTemplatesメソッドを使用して行います。

内容の入力

さっそく文字を入力してPDFファイルを作成してみましょう。
前回の記事とは異なり、今回の方法では文字や画像等のFlowableオブジェクトをリストでまとめ、DocTemplateに登録することでいい感じに配置してくれます。
文字列のFlowableオブジェクトにはParagraphというクラスを使います。

以下がサンプルコードです。

from reportlab.platypus import BaseDocTemplate, PageTemplate
from reportlab.platypus import Paragraph
from reportlab.lib.styles import ParagraphStyle
from reportlab.lib.pagesizes import A4, mm, portrait
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase import cidfonts
from reportlab.platypus.frames import Frame

# 日本語フォントの登録
pdfmetrics.registerFont(cidfonts.UnicodeCIDFont("HeiseiKakuGo-W5"))

# フォントスタイルの設定
PS = ParagraphStyle
style_body = PS(name = 'body',
       fontName = "HeiseiKakuGo-W5",
       fontSize = 12,
       leading = 13)

# DocTemplateの生成
file_path = "./hello_world.pdf"
doc = BaseDocTemplate(file_path, 
    title="テスト",
    pagesize=portrait(A4),
    )
width, height = A4   # 595px, 842px

# Frameの定義
show = 1 #Frameの枠を表示
frames = [
    Frame(15*mm, 15*mm, width / 2 - 15*mm, height - 30*mm, showBoundary=show),
    Frame(width / 2, 15*mm, width / 2 - 15*mm, height - 30*mm, showBoundary=show),
]

# PageTemplateの定義とDocTemplateへの登録
page_template = PageTemplate("test", frames=frames)
doc.addPageTemplates(page_template)

# 日本語フォントの登録
pdfmetrics.registerFont(cidfonts.UnicodeCIDFont("HeiseiKakuGo-W5"))

# フォントスタイルの設定
PS = ParagraphStyle
style_body = PS(name = 'body',
       fontName = "HeiseiKakuGo-W5",
       fontSize = 12,
       leading = 13)

## 内容記入
flowables = [] # 資料全体のflowableの格納用リスト
para = Paragraph("Hello, world!", style_body)
flowables.append(para)
para = Paragraph("Hello, world2!", style_body)
flowables.append(para)

doc.multiBuild(flowables) # flowableのリストをDocTemplateに登録し、PDFを作成

出力結果は以下の通り。

大体の流れはわかりましたでしょうか?前回みたいに遂次位置を指定するのではなく、リストに追加していくだけで勝手に要素を配置してくれるので非常に便利です。




フレームやページ移動

また、フレームやページの移動に関してですが、追加したflowableオブジェクトが現在のフレームに収まらないと判断した場合、勝手に次のフレームや次のページに移動して書き込んでくれますが、FrameBreakやPageBreakを使って明示的にフレームやページ移動も可能です。

from reportlab.platypus import BaseDocTemplate, PageTemplate, FrameBreak, PageBreak
from reportlab.platypus import Paragraph
from reportlab.lib.styles import ParagraphStyle
from reportlab.lib.pagesizes import A4, mm, portrait
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase import cidfonts
from reportlab.platypus.frames import Frame

# 日本語フォントの登録
pdfmetrics.registerFont(cidfonts.UnicodeCIDFont("HeiseiKakuGo-W5"))

# フォントスタイルの設定
PS = ParagraphStyle
style_body = PS(name = 'body',
       fontName = "HeiseiKakuGo-W5",
       fontSize = 12,
       leading = 13)

# DocTemplateの生成
file_path = "./hello_world.pdf"
doc = BaseDocTemplate(file_path, 
    title="テスト",
    pagesize=portrait(A4),
    )
width, height = A4   # 595px, 842px

# Frameの定義
show = 1 #Frameの枠を表示
frames = [
    Frame(15*mm, 15*mm, width / 2 - 15*mm, height - 30*mm, showBoundary=show),
    Frame(width / 2, 15*mm, width / 2 - 15*mm, height - 30*mm, showBoundary=show),
]

# PageTemplateの定義とDocTemplateへの登録
page_template = PageTemplate("test", frames=frames)
doc.addPageTemplates(page_template)

# 日本語フォントの登録
pdfmetrics.registerFont(cidfonts.UnicodeCIDFont("HeiseiKakuGo-W5"))

# フォントスタイルの設定
PS = ParagraphStyle
style_body = PS(name = 'body',
       fontName = "HeiseiKakuGo-W5",
       fontSize = 12,
       leading = 13)

## 内容記入
flowables = [] # 資料全体のflowableの格納用リスト
para = Paragraph("Hello, world!", style_body)
flowables.append(para)
para = Paragraph("Hello, world2!", style_body)
flowables.append(para)

#次のフレームへ
flowables.append(FrameBreak())
para = Paragraph("Hello, world3!", style_body)
flowables.append(para)

#ページへ
flowables.append(PageBreak())
para = Paragraph("Hello, next page!", style_body)
flowables.append(para)
para = Paragraph("すごく長い文章も勝手に折り返しされて表示されます。この機能が非常に便利なのでもうdrawStringでの書き方には戻れなさそうですね。", style_body)
flowables.append(para)

doc.multiBuild(flowables) # flowableのリストをDocTemplateに登録し、PDFを作成

結果は以下。長い文章も勝手に折り返して配置してくれているのがわかるでしょうか?




画像の挿入

画像の挿入にはImageというFlowableオブジェクトを使用します。multiBuildメソッド前に以下を追加しました。
公式ドキュメントにはjpegしか許さないみたいに書いてますが、なぜかpngでもいけました。

from reportlab.platypus import Image

im = Image('./rennai_kaeruka.png', width=50*mm, height=50*mm, hAlign="LEFT")
flowables.append(im)
para = Paragraph("図:カエル化現象", style_body)
flowables.append(para)

ImageもParagraphと同じ方法で追加できていますね。

表の挿入

表の挿入にはTableというFlowableオブジェクトを使用し同様に追加すればOKです。
全コード載せておきます。

from reportlab.platypus import BaseDocTemplate, PageTemplate, FrameBreak, PageBreak
from reportlab.platypus import Paragraph, Image, Table, TableStyle
from reportlab.lib.styles import ParagraphStyle
from reportlab.lib import colors
from reportlab.lib.pagesizes import A4, mm, portrait
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase import cidfonts
from reportlab.platypus.frames import Frame

# 日本語フォントの登録
pdfmetrics.registerFont(cidfonts.UnicodeCIDFont("HeiseiKakuGo-W5"))

# フォントスタイルの設定
PS = ParagraphStyle
style_body = PS(name = 'body',
       fontName = "HeiseiKakuGo-W5",
       fontSize = 12,
       leading = 13)

# DocTemplateの生成
file_path = "./hello_world.pdf"
doc = BaseDocTemplate(file_path, 
    title="テスト",
    pagesize=portrait(A4),
    )
width, height = A4   # 595px, 842px

# Frameの定義
show = 1 #Frameの枠を表示
frames = [
    Frame(15*mm, 15*mm, width / 2 - 15*mm, height - 30*mm, showBoundary=show),
    Frame(width / 2, 15*mm, width / 2 - 15*mm, height - 30*mm, showBoundary=show),
]

# PageTemplateの定義とDocTemplateへの登録
page_template = PageTemplate("test", frames=frames)
doc.addPageTemplates(page_template)

# 日本語フォントの登録
pdfmetrics.registerFont(cidfonts.UnicodeCIDFont("HeiseiKakuGo-W5"))

# フォントスタイルの設定
PS = ParagraphStyle
style_body = PS(name = 'body',
       fontName = "HeiseiKakuGo-W5",
       fontSize = 12,
       leading = 13)

## 内容記入
flowables = [] # 資料全体のflowableの格納用リスト
para = Paragraph("Hello, world!", style_body)
flowables.append(para)
para = Paragraph("Hello, world2!", style_body)
flowables.append(para)

#次のフレームへ
flowables.append(FrameBreak())
para = Paragraph("Hello, world3!", style_body)
flowables.append(para)

#ページへ
flowables.append(PageBreak())
para = Paragraph("Hello, next page!", style_body)
flowables.append(para)
para = Paragraph("すごく長い文章も勝手に折り返しされて表示されます。この機能が非常に便利なのでもうdrawStringでの書き方には戻れなさそうですね。", style_body)
flowables.append(para)

im = Image('./rennai_kaeruka.png', width=50*mm, height=50*mm, hAlign="LEFT")
flowables.append(im)
para = Paragraph("図:カエル化現象", style_body)
flowables.append(para)

# 表の作成
flowables.append(FrameBreak()) #次のフレームへ
para = Paragraph("Table. 果物価格", style_body)
flowables.append(para)
data = [['ID', '名前', '価格'],
        ['0', 'リンゴ', 100],
        ['1', 'ミカン', 400],
        ['2', 'イチゴ', 300],
        ['3', 'ブドウ', 1000],
        ]
table = Table(data, colWidths=20*mm, rowHeights=10*mm)
table.setStyle(TableStyle([
                ('FONT', (0, 0), (2, 4), 'HeiseiKakuGo-W5', 12),
                ('BOX', (0, 0), (2, 4), 1, colors.black),
                ('INNERGRID', (0, 0), (2, 4), 1, colors.black),
                ('VALIGN', (0, 0), (2, 4), 'MIDDLE'),
                ]))
table.setStyle(TableStyle([
    ('BACKGROUND', (0, 0), (2, 0), colors.lightskyblue),
]))
flowables.append(table)

doc.multiBuild(flowables) # flowableのリストをDocTemplateに登録し、PDFを作成

適当な表を作成しましたが、簡単に表が書けることがわかってもらえたかと思います。

終わりに

今回は一通りReportLabのPlatypusを使用したPDFファイルの作成方法について紹介しました。前回紹介した方法に比べ、格段に楽にPDFファイルを作れることがわかっていただけたかと思います。
今回は全ページ同じテンプレートを使用しましたが、ページごとに異なるテンプレートを使用することも可能なので、次回はそちらについて例を交えて紹介します。

参考文献