no-image

Python 3 と ReportLab で PDF ファイルを生成する

Python から PDF ファイルを作成できる ReportLab を試してみます。

インストール

ReportLab のインストールは pip で一発ですが

$ python -V
Python 3.6.5
$ pip install reportlab
...<省略>...
 The headers or library files could not be found for jpeg,
 a required dependency when compiling Pillow from source.

と Pillow のインストール時に JPEG のヘッダ類がないと怒られるので、libjpeg-dev を apt でインストールしておきます。

$ sudo apt install libjpeg-dev
$ pip install reportlab
Collecting reportlab
 Using cached https://files.pythonhosted.org/packages/87/f9/53b34c58d3735a6df7d5c542bf4de60d699cfa6035e113ca08b3ecdcca3f/reportlab-3.4.0.tar.gz
Collecting pillow>=2.4.0 (from reportlab)
 Using cached https://files.pythonhosted.org/packages/89/b8/2f49bf71cbd0e9485bb36f72d438421b69b7356180695ae10bd4fd3066f5/Pillow-5.1.0.tar.gz
Requirement already satisfied: pip>=1.4.1 in /home/pi/.pyenv/versions/3.6.5/lib/python3.6/site-packages (from reportlab) (10.0.1)
Requirement already satisfied: setuptools>=2.2 in /home/pi/.pyenv/versions/3.6.5/lib/python3.6/site-packages (from reportlab) (39.0.1)
Installing collected packages: pillow, reportlab
 Running setup.py install for pillow ... done
 Running setup.py install for reportlab ... done
Successfully installed pillow-5.1.0 reportlab-3.4.0

PDF を生成する

公式のユーザガイド(PDF)にあるサンプルコードを実行してみます。

from reportlab.pdfgen import canvas

def hello(c):
    c.drawString(100,100,"Hello World")

c = canvas.Canvas("hello.pdf")
hello(c)
c.showPage()
c.save()

hello.pdf が生成されているはずなので開いて確認します。

ReportLab PDF hello

ReportLab PDF hello

簡単ですね。ただし、このままだと日本語を出力しようとしても四角(■)で埋められるだけでした。エンコーディングも関係ないようだったのでフォントを指定する必要があるようです。

座標系はページ左下を起点としています。先の例では左下から (100, 100) の座標に文字列を描画しています。

日本語フォント

こちらを参考にしました。

ReportLab には予め HeiseiMin-W3, HeiseiKakuGo-W5 が用意されているそうです。ユーザガイドの 3.6 Asian Font Support に Asian Language Packs として記載されていますね。

Japanese, Traditional Chinese (Taiwan/Hong Kong), Simplified Chinese (mainland China) and Korean are all supported and our software knows about the following fonts:

  • chs = Chinese Simplified (mainland): 'STSong-Light'
  • cht = Chinese Traditional (Taiwan): 'MSung-Light', 'MHei-Medium'
  • kor = Korean: 'HYSMyeongJoStd-Medium','HYGothic-Medium'
  • jpn = Japanese: 'HeiseiMin-W3', 'HeiseiKakuGo-W5'

これらを参考に日本語を入れてみたのが次のソースになります。ファイルは UTF-8n で保存しています。

from reportlab.pdfgen import canvas
from reportlab.lib.pagesizes import A4, portrait
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.cidfonts import UnicodeCIDFont

c = canvas.Canvas('sample.pdf', pagesize=portrait(A4))

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

c.setFont('HeiseiKakuGo-W5', 24)
w, h = A4
c.drawCentredString(w / 2, h / 2, '日本語PDFのサンプルです。')

c.showPage()
c.save()
ReportLab PDF 日本語

ReportLab PDF 日本語

特に問題なく表示されていますね。

TrueType フォント

この他にも自分で TrueType フォントを用意して指定することもできます。試しに Windows 10 の游ゴシックを使ってみました。

from reportlab.pdfgen import canvas
from reportlab.lib.pagesizes import A4, portrait
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont

c = canvas.Canvas('sample.pdf', pagesize=portrait(A4))

pdfmetrics.registerFont(TTFont('Yu Gothic Light', 'YuGothL.ttc'))

c.setFont('Yu Gothic Light', 24)
w, h = A4
c.drawCentredString(w / 2, h / 2, '日本語PDFのサンプルです。')

c.showPage()
c.save()
ReportLab PDF 日本語TTF

ReportLab PDF 日本語TTF

ばっちりです。*.ttf だけでなく *.ttc でも大丈夫でした。

描画メソッド

直線

  • canvas.line(x1, y1, x2, y2)
  • canvas.lines(linelist)

図形

  • canvas.grid(xlist, ylist)
  • canvas.bezier(x1, y1, x2, y2, x3, y3, x4, y4)
  • canvas.arc(x1, y1, x2, y2)
  • canvas.rect(x, y, width, height, stroke=1, fill=0)
  • canvas.ellipse(x1, y1, x2, y2, stroke=1, fill=0)
  • canvas.wedge(x1, y1, x2, y2, startAng, extent, stroke=1, fill=0)
  • canvas.circle(x_cen, y_cen, r, stroke=1, fill=0)
  • canvas.roundRect(x, y, width, height, radius, stroke=1, fill=0)

一行文字列(改行なし文字列)

  • canvas.drawString(x, y, text)
  • canvas.drawRightString(x, y, text)
  • canvas.drawCentredString(x, y, text)

テキストオブジェクト

ユーザガイドには次のように記載されています。

For the dedicated presentation of text in a PDF document, use a text object. The text object interface provides detailed control of text layout parameters not available directly at the canvas level. In addition, it results in smaller PDF that will render faster than many separate calls to the drawString methods.

テキストが多いなら、一回一回 drawString() を呼び出すよりもこちらを使えってことらしいです。が、いざ使ってみると canvas に beginText() なんてメソッドはないと言われて使えず。

textobject = canvas.beginText(x, y)
canvas.drawText(textobject)
  • textobject.setTextOrigin(x, y)
  • textobject.setTextTransform(a, b, c, d, e, f)
  • textobject.moveCursor(dx, dy) # from start of current LINE
  • (x, y) = textobject.getCursor()
  • x = textobject.getX(); y = textobject.getY()
  • textobject.setFont(psfontname, size, leading = None)
  • textobject.textOut(text)
  • textobject.textLine(text='')
  • textobject.textLines(stuff, trim=1)

着色

  • canvas.setFillColorCMYK(c, m, y, k)
  • canvas.setStrikeColorCMYK(c, m, y, k)
  • canvas.setFillColorRGB(r, g, b)
  • canvas.setStrokeColorRGB(r, g, b)
  • canvas.setFillColor(acolor)
  • canvas.setStrokeColor(acolor)
  • canvas.setFillGray(gray)
  • canvas.setStrokeGray(gray)

フォント

  • canvas.setFont(psfontname, size, leading = None)

Platypus

ここまでは低水準(low-level)の描画メソッドばかりでした。これらを駆使して複雑な文書を作れないこともないですが、大変な労力を要します。例えば改行や改頁といった処理も自分で行わないといけません。

ReportLab には Platypus というテンプレートエンジンが用意されいて、一般的な文書の構成であればわりと簡単にページの構成を作ることができます。

次はユーザガイドに載っている Platypus の最も簡単なサンプルを少しだけ整形したものです。

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
PAGE_HEIGHT=defaultPageSize[1]; PAGE_WIDTH=defaultPageSize[0]
styles = getSampleStyleSheet()

Title = "Hello world"
pageinfo = "platypus example"
def myFirstPage(canvas, doc):
    canvas.saveState()
    canvas.setFont('Times-Bold',16)
    canvas.drawCentredString(PAGE_WIDTH / 2.0, PAGE_HEIGHT - 108, Title)
    canvas.setFont('Times-Roman',9)
    canvas.drawString(inch, 0.75 * inch, "First Page / %s" % pageinfo)
    canvas.restoreState()

def myLaterPages(canvas, doc):
    canvas.saveState()
    canvas.setFont('Times-Roman',9)
    canvas.drawString(inch, 0.75 * inch, "Page %d %s" % (doc.page, pageinfo))
    canvas.restoreState()

doc = SimpleDocTemplate("phello.pdf")
Story = [Spacer(1, 2 * inch)]
style = styles["Normal"]
for i in range(100):
    bogustext = ("This is Paragraph number %s. " % i) * 20
    p = Paragraph(bogustext, style)
    Story.append(p)
    Story.append(Spacer(1, 0.2 * inch))
doc.build(Story, onFirstPage=myFirstPage, onLaterPages=myLaterPages)

これによって生成された phello.pdf を表示すると、次のようになっています。

ReportLab Platypus Sample 1/2

ReportLab Platypus Sample 1/2

ReportLab Platypus Sample 2/2

ReportLab Platypus Sample 2/2

<執筆中>

参考文献