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 が生成されているはずなので開いて確認します。
簡単ですね。ただし、このままだと日本語を出力しようとしても四角(■)で埋められるだけでした。エンコーディングも関係ないようだったのでフォントを指定する必要があるようです。
座標系はページ左下を起点としています。先の例では左下から (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()
特に問題なく表示されていますね。
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()
ばっちりです。*.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 を表示すると、次のようになっています。
<執筆中>