PDF生成(ReportLab)¶
ReportLab¶
ReportLab は OS に依存せず、 PyPI パッケージをインストールするだけで使える。
- インストール: cf. 公式ドキュメント
ここでは、名前を渡すと領収書の PDF をダウンロードさせるエンドポイントを作る。
main.py¶
import uuid
from fastapi import FastAPI, Response
from utils import create_receipt_pdf
from config import settings
app = FastAPI()
@app.get("/generate-receipt")
async def generate_receipt(name: str):
receipt_id = str(uuid.uuid4())
pdf_bytes = create_receipt_pdf(
name=name,
amount=settings.RECEIPT_AMOUNT,
company=settings.RECEIPT_ISSUER,
receipt_id=receipt_id,
)
filename = f"receipt_{receipt_id[:8]}.pdf"
return Response(
content=pdf_bytes,
media_type="application/pdf",
headers={
"Content-Disposition": f'attachment; filename="{filename}"'
}
)
リクエストごとに uuid で領収書 ID を発行して、それを PDF の中身にもファイル名(先頭 8 桁)にも使ってる。
Content-Disposition に attachment を指定すると、ブラウザで開かずにダウンロードになる。 filename*=UTF-8'' の書き方をしておけば日本語ファイル名でも文字化けしにくいけど、そもそもファイル名には日本語を使わないように指定しておくのが無難か。
config.py¶
設定値は pydantic-settings で管理する。
cf. 設定管理
from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings):
RECEIPT_AMOUNT: int = 5000
RECEIPT_ISSUER: str = "デフォルトカンパニー"
model_config = SettingsConfigDict(env_file=".env")
settings = Settings()
.env¶
utils.py¶
PDF を組み立てる本体。 canvas.Canvas に座標を指定しながら文字や線を置いていく。
import io
import os
from reportlab.pdfgen import canvas
from reportlab.pdfbase import pdfmetrics
from reportlab.lib.pagesizes import A4
from reportlab.lib.units import mm
from reportlab.pdfbase.ttfonts import TTFont
def create_receipt_pdf(name: str, amount: int, company: str, receipt_id: str) -> bytes:
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
FONT_PATH = os.path.join(BASE_DIR, "fonts", "NotoSansJP-Regular.ttf")
font_name = "NotoSans"
pdfmetrics.registerFont(TTFont(font_name, FONT_PATH))
buffer = io.BytesIO()
p = canvas.Canvas(buffer, pagesize=A4)
width, height = A4
p.setTitle("Receipt")
p.setFillColorRGB(0.6, 0.6, 0.6)
p.setFont(font_name, 9)
p.drawRightString(208*mm, height - 5*mm, f"領収書ID: {receipt_id}")
p.setFillColorRGB(0, 0, 0)
p.setFont(font_name, 24)
p.drawCentredString(width / 2.0, height - 30*mm, "領 収 書")
p.setFont(font_name, 12)
p.drawString(20*mm, height - 45*mm, "受付番号: 1111")
p.setFont(font_name, 16)
p.drawString(20*mm, height - 55*mm, f"{name} 様")
p.setFont(font_name, 18)
p.drawString(20*mm, height - 70*mm, f"金額: ¥{amount:,} -")
p.line(20*mm, height - 72*mm, 100*mm, height - 72*mm)
p.setFont(font_name, 12)
p.drawString(20*mm, height - 85*mm, "但: 参加費として上記正に領収いたしました。")
p.setFont(font_name, 9)
p.setFillColorRGB(0.3, 0.3, 0.3)
p.drawString(20*mm, height - 100*mm, "※発行後にキャンセルされた場合、本領収書は無効になります。")
p.setFillColorRGB(0, 0, 0)
p.setFont(font_name, 10)
p.drawRightString(190*mm, height - 120*mm, "2026年4月24日")
p.drawRightString(190*mm, height - 125*mm, company)
p.showPage()
p.save()
pdf_bytes = buffer.getvalue()
buffer.close()
return pdf_bytes
mm を使うと 20*mm のようにミリ単位で座標を指定できる。 reportlab の原点は左下なので、上からの位置は height - 30*mm のように高さから引いて指定する。
フォント
日本語を描画するには日本語フォントの登録が必要。自分で使いたいフォントがあるなら、 Noto Sans JP あたりからダウンロードして、上のコードなら、 fonts/ ディレクトリに保存しておけば使える。
生成された PDF ファイルはこんな感じ。

Response / StreamingResponse / FileResponse の使い分け
Response— 成果物が全部メモリにある小さいデータ( QR コードや 1 枚の PDF など)。content=bytesで素直に返せて、ヘッダーも付けやすい。StreamingResponse— ジェネレータやファイルをチャンクで逐次返すとき。全体をメモリに載せずに流せるので、大きいデータほど効く。FileResponse— すでにディスクにあるファイルをそのまま返すとき。