[解決!Python]ReportLab Toolkitを使ってテキストをPDFファイルに書き出すには解決!Python

Pythonでまとまった量のテキストや文字列からPDFファイルを作成するにはReportLab Toolkitが提供するPlatypusモジュールを使うと便利だ。その方法をまとめる。

» 2024年05月14日 05時00分 公開
[かわさきしんじDeep Insider編集部]
「解決!Python」のインデックス

連載目次

from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer
from reportlab.lib.styles import getSampleStyleSheet
from reportlab.rl_config import defaultPageSize
from reportlab.lib.units import inch
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.cidfonts import UnicodeCIDFont

PAGE_HEIGHT=defaultPageSize[1]
PAGE_WIDTH=defaultPageSize[0]
styles = getSampleStyleSheet()

pdfmetrics.registerFont(UnicodeCIDFont('HeiseiMin-W3'))
pdfmetrics.registerFont(UnicodeCIDFont('HeiseiKakuGo-W5'))

def make1stPage(canvas, doc):
    canvas.saveState()  # canvasの状態を保存
    canvas.setFont('HeiseiKakuGo-W5', 16# 文書タイトルのフォント設定
    canvas.drawCentredString(PAGE_WIDTH/2.0, PAGE_HEIGHT-108, doc.title)
    canvas.setFont('HeiseiKakuGo-W5', 8# ページ番号のフォント設定
    page_num = f'Page 1 : {doc.title}' # ページ番号
    canvas.drawString(inch, 0.75 * inch, page_num)  # ページ番号の描画
    canvas.restoreState()  # canvasの状態を復元

def makeLaterPages(canvas, doc):
    canvas.saveState()  # canvasの状態を保存
    canvas.setFont('HeiseiKakuGo-W5', 8# ページ番号のフォント設定
    page_num = f'Page {doc.page} : {doc.title}'  # ページ番号
    canvas.drawString(inch, 0.75 * inch, page_num)  # ページ番号の描画
    canvas.restoreState()  # canvasの状態を復元

def buildPDF():
    doc = SimpleDocTemplate('sample.pdf'# sample.pdfファイルを作成
    doc.title = 'SimpleDocTemplateを使ったPDF作成のテスト'  # 文書タイトル
    Story = [Spacer(1, 2 * inch)]  # Storyに本文を追加していく(最初は余白)
    style = styles["Normal"# 通常のスタイルを取得
    style.fontName = "HeiseiMin-W3"  # 本文のフォントを指定
    style.fontSize = 12  # 本文のフォントサイズを指定
    style.leading = 14  # 本文の行間を指定
    for i in range(20):  # テスト用に段落を20個作成
        text = f"これは{i}番目の段落ですよ。" * 20  # 段落の内容
        p = Paragraph(text, style)
        Story.append(p)  # 作成した段落をStoryに追加
        Story.append(Spacer(1,0.2*inch)) # 段落間の空きを追加
    doc.build(Story, onFirstPage=make1stPage, onLaterPages=makeLaterPages)

buildPDF()


ReportLabによるPDF作成

 Pythonで文字列やテキストファイルからPDFファイルを作成するには幾つかの方法がある。今回はReportLabが販売している「ReportLab Plus」のエンジンで、オープンソースとして公開されている「ReportLab Toolkit」を使う方法を紹介する。

注意

 なお、本稿は公式サイトのドキュメントを基にした筆者による独自研究の側面が強く「こうしたらうまくできた」という情報でしかないことには留意されたい。ソースコードはここからダウンロード可能なので、興味のある方はソースコードにも目を通してみよう。


 ReportLab Toolkitを使うには「pip install reportlab」「py -m pip install reportlab」などのコマンドによりあらかじめこれをインストールしておく必要がある。

 PDFファイルにテキストを出力するだけであれば、以下のようなコードを実行すればよい。

from reportlab.pdfgen import canvas
from reportlab.lib.pagesizes import A4

c = canvas.Canvas('test.pdf', pagesize=A4)
width, height = A4
c.drawCentredString(width / 2, height / 2 - 20, 'Hello, World!')

c.showPage()
c.save()


 reportlab.pdfgen.canvasモジュールのCanvasクラスのインスタンスを生成すると、その第1引数に指定したファイル名でPDFファイルが作成される。pagesizeパラメーターに指定しているのは用紙サイズで、ここではreportlab.lib.pagesizesモジュールからインポートしてA4サイズを指定している(といっても、width/height変数に代入していることから分かる通り、A4はその高さと幅を要素とするタプルだ)。

 次にここではCanvasクラスのオブジェクトが持つdrawCenteredStringメソッドを呼び出して、ページのおおよそ中央に'Hello, World!'を出力している。そして、showPageメソッドで現在のページを終了して、saveメソッドはPDFファイルの保存とクローズを行っている。

 以下はVisual Studio Code(以下、VS Code)にvscode-pdf拡張機能をインストールした環境で上記コードを実行し、作成されたPDFをオープンしたものだ。

実行結果 実行結果

 しかし、上のコードから分かる通り、reportlab.pdfgen.canvasモジュールのCanvasクラスが提供する機能は極めて基本的なものであり、ページのどの位置にどんなテキスト(あるいは画像や図形)を出力するかをプログラマーが明示する必要がある。

 そうではなく、PDFファイルにまとまった量のテキストを苦労なく書き込みたいのであれば、ReportLabが提供する「Platypus(Page Layout and Typography Using Scripts)」を使用する。詳しくはドキュメントを参照してほしいが、Platypusにはドキュメントテンプレートやページテンプレート、パラグラフ(段落)、行間などの概念があり、これらの要素を用いて比較的シンプルにPDFファイルにテキストを書き出せる。

 reportlab.platypusモジュールのSimpleDocTemplateクラスを使った場合の典型的なPDF作成のスケルトンコードは次のようになる。

# 必要なもののインポート

# ページサイズの設定
# スタイルの設定
# 使用するフォントの設定

def make1stPage(canvas, doc):
    # 1ページ目の描画に使用する関数の定義

def makeLaterPages(canvas, doc):
    # 2ページ目以降の描画に使用する関数の定義

def buildPDF():
    # PDFファイルを作成する
    # SimpleDocTemplateクラスのインスタンスを生成
    # SimpleDocTemplateクラスのtitle属性に文書タイトルを設定
    # 本文で使用するフォントを設定
    # 本文を構成する要素をリストに格納
    # SimpleDocTemplateクラスのbuildメソッドの呼び出し

# buildPDF関数を呼び出してPDFファイルを作成


 以下ではPlatypusのドキュメントにあるチュートリアルコードを基にReportLabに標準で含まれている平成明朝体W3と平成角ゴシック体W5を使用して、機械的に作成した段落からPDFファイルを作成するようにした冒頭のコードを上記スケルトンコードに照らし合わせながら見ていこう。

必要なもののインポート

 ここで必要としたのは以下のものだ。

from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer
from reportlab.lib.styles import getSampleStyleSheet
from reportlab.rl_config import defaultPageSize
from reportlab.lib.units import inch
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.cidfonts import UnicodeCIDFont


 SimpleDocTemplateクラスはその名の通り、シンプルなドキュメント用にあらかじめ用意されているテンプレートだ。Paragraphクラスは本文を構成するパラグラフ(段落)を表す。Spacerクラスは段落間の空きを表す。getSampleStyleSheet関数は標準で用意されているスタイルシートを取得する。本文を構成するパラグラフは、ここから得られるスタイルを基にフォント名やフォントサイズが指定される。defaultPageSizeオブジェクトはA4サイズの高さと幅を要素とするタプルだ(上で見たA4と同じ)。

 inchはページ番号の表示位置や1ページ目冒頭の空きの計算に使用している(その値は72.0で、1ポイントが72分の1インチであることからその値になっていると思われる)。pdfmetricsモジュールとUnicodeCIDFontクラスは上で述べた平成明朝体と平成角ゴシック体をPDFファイル内で使用できるように登録するために使用する。

ページサイズ/スタイル/フォントの設定

 以下のコードでは、上で見たdefaultPageSizeから、このPDFファイルの高さと幅を取得するとともに、getSampleStyleSheet関数でスタイルシートを変数stylesに取得している。stylesは辞書的に使え、この後で「styles['Normal']」のようにして通常のスタイルをそこから取得し、通常のスタイルに対して日本語フォントを使うように指定する。そして、日本語を使うための設定が最後の2行だ。

PAGE_HEIGHT=defaultPageSize[1]
PAGE_WIDTH=defaultPageSize[0]
styles = getSampleStyleSheet()

pdfmetrics.registerFont(UnicodeCIDFont('HeiseiMin-W3'))
pdfmetrics.registerFont(UnicodeCIDFont('HeiseiKakuGo-W5'))


 最後の2行ではregisterFont関数を使って平成明朝体と平成角ゴシック体をPDFファイル内で使えるように登録している。このときに指定した2つの文字列('HeiseiMin-W3'と'HeiseiKakuGo-W5')を使って、この後でフォントを指定することになる。

 なお、外部からフォントをダウンロードして使用するときには、そのフォントを保存しているパスと、フォントの種類に応じて例えば次のようなコードを書くことでフォントを登録できる。

from reportlab.pdfbase.ttfonts import TTFont

IPAEXG = 'fonts/ipaexg00401/ipaexg.ttf'
pdfmetrics.registerFont(TTFont('ipaexg', IPAEXG))


 これはIPAが提供しているIPAexフォントのIPAexゴシック体を登録するコードだ。ライセンスに同意した上で、フォントをダウンロードしてローカルのディレクトリ(fonts/ipaexg00401)に配置し、その名前('ipaexg')とパスを使ってregisterFont関数で登録している。このときにはTTFontクラスでラップしている点にも注意しよう。

1ページ目の描画に使用する関数の定義

 SimpleDocTemplateクラスのbuildメソッドでは1ページ目の描画に使用する関数と2ページ目以降の描画に使用する関数を分けて指定するようになっている。これはドキュメントの先頭ページは2ページ目以降とは異なる処理(画像の配置など)が必要になるためだろう。ここでは上部に余白を取り、PDFファイルのタイトルを表示するようにしている。本文はその後に出力される。

def make1stPage(canvas, doc):
    canvas.saveState()  # canvasの状態を保存
    canvas.setFont('HeiseiKakuGo-W5', 16# 文書タイトルのフォント設定
    canvas.drawCentredString(PAGE_WIDTH/2.0, PAGE_HEIGHT-108, doc.title)
    canvas.setFont('HeiseiKakuGo-W5', 8# ページ番号のフォント設定
    page_num = f'Page 1 : {doc.title}' # ページ番号
    canvas.drawString(inch, 0.75 * inch, page_num)  # ページ番号の描画
    canvas.restoreState()  # canvasの状態を復元


 この関数(および、2ページ目の描画で使用する関数)はbuildメソッドから呼び出されるコールバック関数であり、そのcanvasパラメーターにはPDFファイルを構成するページが、docパラメーターにはドキュメントの内容を表すオブジェクトが渡される。

 saveStateメソッドはページの内容を表すオブジェクトの状態(フォントやその他の情報)を一時的に保存しておくためのものだ。最後のrestoreStateメソッドで状態が復元されるので、その間はフォントの設定などを自由に行える。ただし、ここで行うフォントの設定は本文の描画とは無関係である点には注意されたい。コードをよく見ると分かるが、ここで行っているのは、docパラメーターから取得できるドキュメントのタイトル(doc.title属性)とページ番号(ノンブル)の描画だけだ。本文は恐らくbuildメソッドの中でレイアウトが計算され(本文の段落のどこまでがどのページに収まるかなど)、自動的に描画される。

 2つあるsetFontメソッド呼び出しは、最初がドキュメントのタイトルを表示するためのもので、フォントサイズを少し大きめにしている。2つ目のものはページ番号を表示するためのものでフォントサイズを小さめにしている。このような変更は、上でも述べたようにrestoreStateメソッド呼び出しにより元の状態に戻される。

2ページ目以降の描画に使用する関数の定義

 2ページ目以降の描画に使用する関数は1ページ目の描画に使用する関数と同様だ(ただし、ドキュメントのタイトルを描画する必要がないのでよりシンプルになっている)。

def makeLaterPages(canvas, doc):
    canvas.saveState()  # canvasの状態を保存
    canvas.setFont('HeiseiKakuGo-W5', 8# ページ番号のフォント設定
    page_num = f'Page {doc.page} : {doc.title}'  # ページ番号
    canvas.drawString(inch, 0.75 * inch, page_num)  # ページ番号の描画
    canvas.restoreState()  # canvasの状態を復元


 ページ番号はdocパラメーターのpage属性で取得できる点に注目されたい。それ以外は説明の必要はないだろう。

PDFファイルを作成する

 2つの関数が定義できたら、後はPDFファイルを作成する関数を定義する。

def buildPDF():
    doc = SimpleDocTemplate('sample.pdf'# sample.pdfファイルを作成
    doc.title = 'SimpleDocTemplateを使ったPDF作成のテスト'  # 文書タイトル
    Story = [Spacer(1, 2 * inch)]  # Storyに本文を追加していく(最初は余白)
    style = styles["Normal"# 通常のスタイルを取得
    style.fontName = "HeiseiMin-W3"  # 本文のフォントを指定
    style.fontSize = 12  # 本文のフォントサイズを指定
    style.leading = 14  # 本文の行間を指定
    for i in range(20):  # テスト用に段落を20個作成
        text = f"これは{i}番目の段落ですよ。" * 20  # 段落の内容
        p = Paragraph(text, style)
        Story.append(p)  # 作成した段落をStoryに追加
        Story.append(Spacer(1,0.2*inch)) # 段落間の空きを追加
    doc.build(Story, onFirstPage=make1stPage, onLaterPages=makeLaterPages)


 ここではSimpleDocTemplateクラスのインスタンスを生成して、そのtitle属性にドキュメントのタイトルを設定している(これが1ページ目の描画に使用する関数で使われる)。

 Story変数はドキュメントの本文を構成する要素を格納するリストだ。最初にSpacerクラスのインスタンスを格納しているが、これは1ページ目冒頭の余白となる。

 次に「styles['Normal']」としてstyle変数に通常のスタイルを取得して、そのfontName属性とfontSize属性、leading属性を設定している。これらは本文の出力に使用するフォントとそのサイズ、段落の行間の指定となる。

 その後のfor文では「これは0番目の段落ですよ。」「これは1番目の段落ですよ」といった文字列を20回繰り返したものを本文構成要素(段落)として、Paragraphクラスのインスタンスの生成時に指定し、できたParagraphクラスのインスタンスを上で述べたStory変数のリストに追加している。さらに段落間の空きとしてSpacerクラスのインスタンスも追加している点には注意しよう。

 ループが終わり、Story変数に全ての本文構成要素が追加されたら、buildメソッドを呼び出す。このときには先ほど定義した2つの関数をonFirstPageパラメーターとonLaterPagesパラメーターに指定する。これにより、1ページ目と2ページ目以降で異なる処理でページが描画されるようになる。

 というわけで、VS Codeで以上のコードを実行して、作成したPDFファイルをオープンしているところを以下に示す。

実行結果 実行結果

テキストファイルからPDFファイルを作成

 これだけでは何なので、解決!Pythonの前回の記事「PDFファイルからテキストや画像を抽出するには」から冒頭のプログラムコードやURLへのリンクなどを取り除いたピュアテキストファイルを作成して(sample.txtファイルとする)、これを基にPDFを作成するコードを以下に示す。

def buildPDFfromTextFile():
    doc = SimpleDocTemplate('sample2.pdf')
    doc.title = '[解決!Python]PDFファイルからテキストや画像を抽出するには'
    Story = [Spacer(1, 2 * inch)]
    style = styles["Normal"]
    style.fontName = "HeiseiMin-W3"
    style.fontSize = 12
    style.leading = 14
    with open('sample.txt', 'r', encoding='utf-8') as f:
        for line in f:
            p = Paragraph(line, style)
            Story.append(p)
            Story.append(Spacer(1,0.2*inch))
    doc.build(Story, onFirstPage=make1stPage, onLaterPages=makeLaterPages)


 buildPDF関数とはドキュメントのタイトル(doc.title属性)と、ドキュメントの本文を作成するループが異なるだけなので、説明は不要だろう。

 以下はその実行結果だ。

実行結果 実行結果

 プログラムコードの書体を変更したいといった希望があるのであれば、SimpleDocTemplateクラスを使わずに自前で色々と処理する必要があるだろうが、この程度の出力でも許容できるのであれば、ここで述べた方法で比較的簡単にPDFファイルを作成できるだろう。

「解決!Python」のインデックス

解決!Python

Copyright© Digital Advantage Corp. All Rights Reserved.

スポンサーからのお知らせPR

注目のテーマ

AI for エンジニアリング
「サプライチェーン攻撃」対策
1P情シスのための脆弱性管理/対策の現実解
OSSのサプライチェーン管理、取るべきアクションとは
Microsoft & Windows最前線2024
システム開発ノウハウ 【発注ナビ】PR
あなたにおすすめの記事PR

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。