1
0

【Lambda】ReportLabで任意のフォントを使用してS3に格納する

Last updated at Posted at 2024-09-10

はじめに

PythonでPDFを生成するライブラリReportLabで、
標準選択できるフォント以外のフォントを使う際に一筋縄では出来なかったので、
解決方法を残します。

LambdaのデプロイはAWS SAMを使っています。他の方法でもやり方は同じです。
LambdaやLambda Layerのデプロイ方法については本記事では解説しませんので、他の記事を参照ください。

解決コード

任意のフォントを登録→最終的にS3にプットするまでのコードです。
コードだけ知りたい、という方はこちらを参考にしてください。

import io

from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
from reportlab.lib.units import mm
from reportlab.lib.pagesizes import A4
from reportlab.pdfgen import canvas

import boto3

FONT_NAME = "GenJyuuGothic"
FONT_PATH = "/opt/python/fonts/GenJyuuGothic-Medium.ttf"

# フォントの登録 エラー回避のために同じフォント名で登録されていないかチェック
if FONT_NAME not in pdfmetrics.getRegisteredFontNames():
    pdfmetrics.registerFont(TTFont(FONT_NAME, FONT_PATH))


def export_pdf():
    # キャンバスを用意 メモリ上に書き込むようにする
    buffer = io.BytesIO()
    pdf_canvas = canvas.Canvas(buffer, pagesize=A4)
    # フォントをセット 第1引数に登録したフォント名を指定
    pdf_canvas.setFont(FONT_NAME, 10) 
    # 文字を描画
    pdf_canvas.drawString(10 * mm, 10 * mm, "サンプル")
    # 保存
    pdf_canvas.save()
    # 読み込み位置をリセット
    buffer.seek(0)
    # バイトデータを取得
    pdf_data = buffer.getvalue()
    # S3に格納
    s3 = boto3.client("s3")
    s3.put_object(Bucket=bucket_name, Key=key, Body=pdf_data)

詳しい解説をしていきます。

ReportLabとは

PythonでPDFを生成するライブラリです。
他にもPDF生成のライブラリはいろいろありますが、ネット上の情報が多く扱いやすいです。「reportlab 使い方」とかでググるといっぱい記事が出てきます。
白紙の状態からPDFを生成できるので、自由にレイアウトを作れるのがポイントです。

環境

  • Lambdaランタイム: Python3.12
  • ReportLab: 4.2.0

解決方法

流れとしては以下になります。

  1. 使用したいフォントのttfファイル(拡張子.ttf)を用意する
  2. ttfファイルをデプロイするLambdaレイヤーに含める
  3. レイヤー内のttfファイルを関数から読み込む

3につまづいて解決に2時間くらい格闘しました。順番に解説します

使用したいフォントのttfファイルを用意する

わたしは「源柔ゴシック」というフォントを使いたかったので、
以下のサイトからzipファイルをダウンロード
→展開
→太さや微妙なスタイルの違いで複数ファイルがあります。わたしはGenJyuuGothic-Medium.ttfを使いました。

ttfファイルをデプロイするLambdaレイヤーに含める

レイヤーのフォルダ配下にfontsフォルダを作り、その中にttfファイルを配置します。
common_layerがレイヤーフォルダの名前です(なんでもいいです)。

image.png

レイヤーフォルダ直下でなくても問題ないです。

image.png

この状態でデプロイするとレイヤーにフォントファイルが含まれます。

レイヤー内のttfファイルを関数から読み込む

FONT_NAME = "GenJyuuGothic"
FONT_PATH = "/opt/python/fonts/GenJyuuGothic-Medium.ttf"

# エラー回避のために同じフォント名で登録されていないかチェック
if FONT_NAME not in pdfmetrics.getRegisteredFontNames():
    pdfmetrics.registerFont(TTFont(FONT_NAME, FONT_PATH))

(importは解決コードに書いているので省略)

レイヤーが展開されるパスは公式docに書いてます。Pythonランタイムの場合は/opt/python配下に展開されます。これを見てなくてパスを特定するのに30分くらい悩みました涙。

ちなみにreportlab標準の「HeiseiKakuGo-W5」を使う場合は以下のコード

from reportlab.pdfbase.cidfonts import UnicodeCIDFont

# HeiseiKakuGo-W5を使う場合
FONT_NAME = "HeiseiKakuGo-W5"
FONT_SIZE = 11

# フォントの登録
pdfmetrics.registerFont(UnicodeCIDFont(FONT_NAME))

フォントの登録はこれでOKです。
登録したフォントを使う時は次のようにします。

FONT_NAME = "GenJyuuGothic"
FONT_PATH = "/opt/python/fonts/GenJyuuGothic-Medium.ttf"

pdfmetrics.registerFont(TTFont(FONT_NAME, FONT_PATH))

# キャンバスを用意
pdf_canvas = canvas.Canvas("/tmp/sample.pdf", pagesize=A4)
# フォントをセット 第1引数に登録したフォント名を指定
pdf_canvas.setFont(FONT_NAME, 10) 
# 文字を描画
pdf_canvas.drawString(10 * mm, 10 * mm, "サンプル")

さて、多くの場合Lambdaで作成したPDFなりExcelなりの帳票はS3に保管すると思います。
reportlabにはCanvasにgetpdfdataという作成したPDFをバイト型で取得できる関数が用意されています。

# 保存
pdf_canvas.save()
pdf_data = pdf_canvas.getpdfdata()

s3 = boto3.client("s3")
s3.put_object(Bucket=bucket_name, Key=key, Body=pdf_data)

上記コードは
HeiseiKakuGo-W5のようにUnicodeCIDFontで読み込んだ標準フォントの場合はうまくいくのですが、TTFontで読み込んだ場合は、getpdfdata実行時に以下のエラーが出ます。

 [ERROR] ValueError: redefining named object: 'toUnicodeCMap:AAAAAA+GenJyuuGothic-Medium'

同じフォント名で複数回登録しようとした際に発生するエラーのようで、getpdfdataの内部処理で発生します。
これだとgetpdfdataが使えないので、次のコードで解決しました。

# キャンバスを用意 メモリ上に書き込むようにする
buffer = io.BytesIO()
pdf_canvas = canvas.Canvas(buffer, pagesize=A4)

# ~~省略~~

pdf_canvas.save()
# 読み込み位置をリセット
buffer.seek(0)
# バイトデータを取得
pdf_data = buffer.getvalue()

s3 = boto3.client("s3")
s3.put_object(Bucket=bucket_name, Key=key, Body=pdf_data)

これで無事任意のフォントでPDF作成→S3に格納までできました。

おわりに

「Lambda上でフォント登録できんやん、詰み」となっている人の参考になれば幸いです。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0