【2025年版】ReportLab応用:動的データからの帳票生成と日本語対応の完全ガイド

はじめに:動的なPDF帳票生成と日本語の壁を乗り越える

日々の業務で、データベースやAPIから取得した最新のデータをもとに、請求書、納品書、レポートといったPDF帳票を自動生成する必要に迫られることはありませんか?手作業での帳票作成は時間がかかり、ヒューマンエラーのリスクも伴います。Pythonの強力なPDF生成ライブラリであるReportLabを使えば、これらの課題を解決し、業務を劇的に効率化できます。

しかし、ReportLabを使った動的な帳票生成、特に日本語を含むPDFの扱いは、多くの開発者にとって頭を悩ませるポイントです。文字化け、フォントの埋め込み、複雑なレイアウト調整…これらの「日本語の壁」に直面し、自動化を諦めてしまった経験がある方もいるかもしれません。

この記事では、ReportLabを最大限に活用し、データベースやAPIからの動的なデータに基づいたPDF帳票を生成する方法を徹底解説します。さらに、日本語フォントの埋め込みから文字化け対策まで、日本語対応を完璧に行うための実践的なガイドを提供します。この記事を読み終える頃には、あなたはReportLabを使いこなし、どんなデータからでもプロフェッショナルな日本語PDF帳票を自動生成できるようになっているでしょう。


対象読者

  • Pythonを使って動的なPDF帳票を自動生成したい開発者
  • ReportLabでの日本語対応に課題を感じているエンジニア
  • データベースやAPIからのデータをもとに、請求書やレポートを自動発行したい方
  • ReportLabの応用的な使い方を学び、PDF自動化のスキルを向上させたい中級者レベルのエンジニア

動作検証環境

この記事で紹介するPythonを使ったPDF動作は、以下の環境で検証しています。

  • OS : macOS Tahoe Version 26.0
  • ハードウェア : MacBook Air 2024 M3 24GB
  • uv : 0.8.22 (ade2bdbd2 2025-09-23)
  • python : 3.13.7
  • ReportLab : 4.4.4
    • charset-normalizer : 3.4.3
    • pillow : 11.3.0
  • pandas : 2.3.3
    • numpy : 2.3.3
    • python-dateutil : 2.9.0.post0
      • six : 1.17.0
    • pytz : 2025.2
      • types-pytz : 2025.2.0.20250809
    • tzdata : 2025.2
    • pandas-stubs : 2.3.2.250926

目次

  1. ReportLabでの日本語対応
    • 日本語フォントの準備と登録
      • 日本語フォントの入手
      • ReportLabへのフォント登録
    • 日本語テキストの描画と文字化け対策
      • 文字化け対策の基礎
  2. 動的データからのPDF帳票生成
    • データソース(CSV, JSON, データベース)の読み込み
      • CSVファイルからの読み込み
      • JSONファイルからの読み込み
      • データベースからの読み込み
    • テンプレートとデータのマッピング
      • ReportLabの基本要素
    • 複数ページの帳票自動生成
    • 実践例:請求書や納品書の自動発行
      • 請求書テンプレートの例
  3. ReportLabと他のライブラリとの連携(例: 画像処理ライブラリ)
    • 画像処理ライブラリ(Pillowなど)との連携
    • 他のPDFライブラリ(pypdf, PyMuPDFなど)との連携
  4. まとめ:ReportLabで実現する究極のPDF生成自動化
    • 今日からできる、はじめの一歩
  5. FAQ
    • Q1: ReportLab以外に日本語対応が容易なPDF生成ライブラリはありますか?
    • Q2: ReportLabで生成したPDFのファイルサイズを小さくするにはどうすればよいですか?
    • Q3: ReportLabで複雑なグラフを生成するにはどうすればよいですか?
    • Q4: WebアプリケーションでReportLabを使う際の注意点はありますか?
  6. 参考文献
  7. 免責事項

1. ReportLabでの日本語対応

ReportLabで日本語を扱う際、最も頻繁に遭遇する問題が文字化けです。これは、ReportLabがデフォルトで日本語フォントを埋め込んでいないためです。しかし、適切な手順を踏めば、ReportLabでも完璧な日本語対応が可能です。


日本語フォントの準備と登録

ReportLabで日本語を表示するには、TrueTypeフォント(.ttf)ファイルを準備し、ReportLabに登録する必要があります。

日本語フォントの入手

商用利用可能なフリーの日本語フォントとして、IPAexフォント(IPAexゴシック、IPAex明朝)が公開されています。これらはIPA(情報処理推進機構)から提供されており、以下のサイトからダウンロードできます。

ダウンロードしたフォントファイル(例: ipaexg.ttfipaexm.ttf)を、Pythonスクリプトと同じディレクトリ、またはアクセス可能なパスに配置します。

ReportLabへのフォント登録

ReportLabにフォントを登録するには、reportlab.pdfbase.pdfmetricsreportlab.pdfbase.ttfontsモジュールを使用します。

from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont

# 日本語フォントの登録
# 第一引数: ReportLab内で使用するフォント名
# 第二引数: フォントファイルへのパス
pdfmetrics.registerFont(TTFont("JapaneseFont", "ipaexg.ttf"))

# 登録したフォントをReportLabのスタイルに設定
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
styles = getSampleStyleSheet()
japanese_style_normal = ParagraphStyle(
    name="JapaneseNormal", parent=styles["Normal"], fontName="JapaneseFont"
)
japanese_style_h1 = ParagraphStyle(
    name="JapaneseH1", parent=styles["h1"], fontName="JapaneseFont"
)
[!NOTE]
pdfmetrics.registerFontは一度実行すれば、そのPythonプロセスの間はフォントが利用可能になります。

日本語テキストの描画と文字化け対策

フォントを登録したら、あとはそのフォント名を指定してテキストを描画するだけです。ParagraphTableなどのFlowablesは、スタイルで指定されたフォントを使用します。

from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Flowable
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.pagesizes import A4
from reportlab.lib.units import inch
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont


def create_japanese_pdf(output_filepath: str):
    # フォントとスタイルの設定
    pdfmetrics.registerFont(TTFont("JapaneseFont", "ipaexg.ttf"))
    styles = getSampleStyleSheet()
    japanese_style_normal = ParagraphStyle(
        name="JapaneseNormal", parent=styles["Normal"], fontName="JapaneseFont"
    )
    japanese_style_h1 = ParagraphStyle(
        name="JapaneseH1", parent=styles["h1"], fontName="JapaneseFont"
    )

    doc = SimpleDocTemplate(output_filepath, pagesize=A4)
    story: list[Flowable] = []

    story.append(Paragraph("日本語テキストの表示テスト", japanese_style_h1))
    story.append(Spacer(1, 0.2 * inch))
    story.append(
        Paragraph(
            "これはReportLabで生成された日本語の段落です。", japanese_style_normal
        )
    )
    story.append(
        Paragraph(
            "文字化けせずに正しく表示されることを確認してください。",
            japanese_style_normal,
        )
    )
    story.append(Spacer(1, 0.5 * inch))
    story.append(Paragraph("・箇条書き1<br/>・箇条書き2", japanese_style_normal))

    doc.build(story)
    print(f"PDF '{output_filepath}' が生成されました。")


def main():
    """
    日本語PDFを生成するメイン関数
    """
    print("日本語PDFの生成を開始します...")
    create_japanese_pdf("japanese_text_example.pdf")


if __name__ == "__main__":
    main()
[!NOTE]
上記のコード例では、日本語フォントファイルipaexg.ttfがスクリプトと同じディレクトリにあることを前提としています。

文字化け対策の基礎

  • フォントの埋め込み: 最も重要なのは、前述の通り日本語フォントをReportLabに登録し、使用することです。これにより、PDFビューアにフォントがインストールされていなくても正しく表示されます。
  • エンコーディング: ReportLabは内部的にUTF-8をサポートしていますが、ParagraphなどでHTMLタグを使用する場合、エンコーディングの問題が発生することは稀です。通常はフォント登録が正しく行われていれば問題ありません。
  • フォントのサブセット化: 生成されるPDFのファイルサイズを抑えるために、使用する文字だけをフォントファイルから抜き出して埋め込む「フォントのサブセット化」がReportLabによって自動的に行われます。
[!著者の経験談]
以前、ReportLabで日本語帳票を生成する際に、フォントのパス指定を誤って文字化けが発生した経験があります。フォントファイルが正しく読み込まれているか、pdfmetrics.registerFontがエラーなく実行されているかを確認することが、日本語対応の第一歩です。また、サーバー環境で実行する場合は、サーバーに日本語フォントがインストールされているか、またはフォントファイルをアプリケーションに同梱するなどの工夫が必要です。

2. 動的データからのPDF帳票生成

ReportLabの真価は、静的なPDFを作成するだけでなく、動的なデータソースと連携して柔軟な帳票を生成できる点にあります。ここでは、様々なデータソースからデータを読み込み、ReportLabでPDF帳票にマッピングし、複数ページにわたる帳票を自動生成するプロセスを解説します。


データソース(CSV, JSON, データベース)の読み込み

ReportLab自体はデータソースの読み込み機能を持っていませんが、Pythonの豊富なライブラリを活用することで、あらゆる形式のデータを簡単に取り込むことができます。


CSVファイルからの読み込み

最もシンプルなデータソースの一つがCSVです。csvモジュールを使えば、手軽にデータを読み込めます。

CSVファイルは、表形式のデータを扱う上で最も一般的な形式の一つです。Pythonのcsvモジュールやpandasライブラリを使って簡単に読み込むことができます。ここではcsvモジュールを使った基本的な読み込み方法を示します。

import csv # csvモジュールをインポート

def load_data_from_csv(filepath):
    """CSVファイルからデータを読み込む"""
    try:
        data = []
        with open(filepath, 'r', encoding='utf-8') as f:
            reader = csv.DictReader(f)
            for row in reader:
                data.append(row)
        return data
    except FileNotFoundError:
        print(f"Error: CSV file not found at {filepath}")
        return []
    except Exception as e:
        print(f"Error reading CSV file: {e}")
        return []

# 使用例
# products_data = load_data_from_csv('products.csv')
# print(products_data)

JSONファイルからの読み込み

APIからのレスポンスなど、構造化されたデータはJSON形式で提供されることが多いです。jsonモジュールで簡単に扱えます。

import json

def load_data_from_json(filepath):
    """JSONファイルからデータを読み込む"""
    try:
        with open(filepath, 'r', encoding='utf-8') as f:
            data = json.load(f)
        return data
    except FileNotFoundError:
        print(f"Error: JSON file not found at {filepath}")
        return {}
    except json.JSONDecodeError:
        print(f"Error: Invalid JSON format in {filepath}")
        return {}
    except Exception as e:
        print(f"Error reading JSON file: {e}")
        return {}

# 使用例
# order_data = load_data_from_json('order.json')
# print(order_data)

データベースからの読み込み

実際の業務では、PostgreSQLやMySQLなどのリレーショナルデータベースからデータを取得することがほとんどでしょう。ここではsqlite3を例に示しますが、psycopg(PostgreSQL)やmysql-connector-python(MySQL)など、各データベースに対応したライブラリを使用します。

import sqlite3

def load_data_from_db(db_path, query):
    """SQLiteデータベースからデータを読み込む"""
    try:
        with sqlite3.connect(db_path) as conn: # withステートメントを使用
            cursor = conn.cursor()
            cursor.execute(query)
            columns = [description[0] for description in cursor.description]
            data = [dict(zip(columns, row)) for row in cursor.fetchall()]
        return data
    except sqlite3.Error as e:
        print(f"Error accessing database: {e}")
        return []
    except Exception as e:
        print(f"Error loading data from DB: {e}")
        return []
# 使用例
# db_data = load_data_from_db('sales.db', 'SELECT * FROM invoices WHERE status = "pending"')
# print(db_data)

テンプレートとデータのマッピング

ReportLabで動的な帳票を生成する際の鍵は、固定のレイアウト(テンプレート)と動的なデータとをどのように組み合わせるかです。ReportLabのFlowablesFrameを組み合わせることで、柔軟なレイアウトを実現できます。

ReportLabの基本要素

  • Canvas: PDFの描画領域そのもの。低レベルな描画操作(線、図形、テキストの直接配置)に使用します。
  • SimpleDocTemplate / BaseDocTemplate: ドキュメント全体の構造を管理するテンプレートクラス。SimpleDocTemplateはシンプルなレポート向け、BaseDocTemplateはより複雑なレイアウト向けです。
  • Flowables: ドキュメント内を「流れる」コンテンツの単位。Paragraph(段落)、Image(画像)、Table(表)などがあります。
  • Frame: ページ内の特定の領域を定義し、その中にFlowablesを配置します。これにより、ヘッダー、フッター、本文といった領域を分離して管理できます。

複数ページの帳票自動生成

ReportLabのSimpleDocTemplateBaseDocTemplateは、コンテンツが1ページに収まらない場合に自動的に新しいページを作成してくれます。Flowablesstoryリストに追加していくだけで、ReportLabが適切にページ送りを処理します。

特にBaseDocTemplateを使用すると、ヘッダー、フッター、サイドバーなど、ページごとに異なる固定要素を持つ複雑なレイアウトを定義できます。

ffrom typing import TypedDict
from reportlab.platypus import (
    BaseDocTemplate,
    Frame,
    Paragraph,
    Spacer,
    PageBreak,
    PageTemplate,
    Flowable,
)
from reportlab.lib.pagesizes import A4
from reportlab.lib.units import inch
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
from reportlab.pdfgen.canvas import Canvas


class ReportSection(TypedDict):
    title: str
    paragraphs: list[str]


def create_multi_page_report(
    output_filepath: str, report_sections: list[ReportSection]
):
    # フォントとスタイルの設定
    pdfmetrics.registerFont(TTFont("JapaneseFont", "ipaexg.ttf"))
    styles = getSampleStyleSheet()
    japanese_style_normal = ParagraphStyle(
        name="JapaneseNormal", parent=styles["Normal"], fontName="JapaneseFont"
    )
    japanese_style_h2 = ParagraphStyle(
        name="JapaneseH2", parent=styles["h2"], fontName="JapaneseFont"
    )

    doc = BaseDocTemplate(output_filepath, pagesize=A4)

    # ページ情報(フッター)を描画する関数
    def add_page_info(canvas: Canvas, doc: BaseDocTemplate):
        canvas.saveState()
        canvas.setFont("JapaneseFont", 9)
        canvas.drawString(inch, 0.75 * inch, f"ページ {doc.page}")
        canvas.restoreState()

    # ページフレームの定義
    frame = Frame(doc.leftMargin, doc.bottomMargin, doc.width, doc.height, id="normal")

    # ページテンプレートを作成し、onPageハンドラを設定
    template = PageTemplate(id="main_template", frames=[frame], onPage=add_page_info)
    doc.addPageTemplates([template])

    story: list[Flowable] = []

    # 各セクションをストーリーに追加
    for i, section_content in enumerate(report_sections):
        story.append(Paragraph(section_content["title"], japanese_style_h2))
        story.append(Spacer(1, 0.2 * inch))
        for paragraph_text in section_content["paragraphs"]:
            story.append(Paragraph(paragraph_text, japanese_style_normal))
            story.append(Spacer(1, 0.1 * inch))
        if i < len(report_sections) - 1:
            story.append(PageBreak())  # セクションごとにページを区切る

    doc.build(story)
    print(f"レポート '{output_filepath}' が生成されました。")


def main():
    """
    複数ページレポートを生成するメイン関数
    """
    report_sections_example: list[ReportSection] = [
        {
            "title": "第1章:はじめに",
            "paragraphs": [
                "これはレポートの最初のセクションです。",
                "ここに詳細な内容が続きます。",
            ],
        },
        {
            "title": "第2章:分析結果",
            "paragraphs": [
                "データ分析の結果をここに記述します。",
                "グラフや表も挿入可能です。",
            ],
        },
        {
            "title": "第3章:結論",
            "paragraphs": [
                "レポートの結論と今後の展望です。",
                "重要なポイントをまとめます。",
            ],
        },
    ]
    print("複数ページレポートの生成を開始します...")
    create_multi_page_report("multi_page_report.pdf", report_sections_example)


if __name__ == "__main__":
    main()
[!TIP]
BaseDocTemplateは非常に強力ですが、その分学習コストも高くなります。まずはSimpleDocTemplateで基本的な帳票生成に慣れ、より複雑なレイアウトが必要になった際にBaseDocTemplateに移行するのがおすすめです。

実践例:請求書や納品書の自動発行

ReportLabと日本語対応の知識を組み合わせることで、実際の業務で利用できる請求書や納品書を自動発行できます。

請求書テンプレートの例

ここでは、SimpleDocTemplateFrameTableParagraphを組み合わせて、請求書のテンプレートを作成し、動的なデータをマッピングする例を見てみましょう。

from typing import TypedDict
from reportlab.lib.pagesizes import A4
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.platypus import (
    SimpleDocTemplate,
    Paragraph,
    Spacer,
    Table,
    TableStyle,
    Flowable,
)
from reportlab.platypus.doctemplate import BaseDocTemplate
from reportlab.pdfgen.canvas import Canvas
from reportlab.lib.units import inch
from reportlab.lib import colors
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont


# 請求書データの型定義
class InvoiceData(TypedDict):
    invoice_date: str  # 請求日付
    invoice_no: str  # 請求番号
    customer_name: str  # 顧客名
    customer_address: str  # 顧客住所
    total_amount: int  # 合計金額
    notes: str  # 備考


# 商品データの型定義
class ItemData(TypedDict):
    description: str  # 商品説明
    unit_price: int  # 単価
    quantity: int  # 数量
    amount: int  # 金額


def create_invoice_pdf(
    output_filepath: str, invoice_data: InvoiceData, items_data: list[ItemData]
):
    # PDFドキュメントの設定
    doc = SimpleDocTemplate(output_filepath, pagesize=A4)

    # フォントとスタイルの設定
    pdfmetrics.registerFont(TTFont("JapaneseFont", "ipaexg.ttf"))
    styles = getSampleStyleSheet()
    japanese_style_normal = ParagraphStyle(
        name="JapaneseNormal", parent=styles["Normal"], fontName="JapaneseFont"
    )
    japanese_style_h1 = ParagraphStyle(
        name="JapaneseH1", parent=styles["h1"], fontName="JapaneseFont"
    )

    story: list[Flowable] = []

    # 請求書タイトル
    story.append(Paragraph("請求書", japanese_style_h1))
    story.append(Spacer(1, 0.2 * inch))

    # 請求先情報
    story.append(
        Paragraph(f"請求日: {invoice_data['invoice_date']}", japanese_style_normal)
    )
    story.append(
        Paragraph(f"請求番号: {invoice_data['invoice_no']}", japanese_style_normal)
    )
    story.append(Spacer(1, 0.1 * inch))
    story.append(
        Paragraph(f"宛名: {invoice_data['customer_name']} 様", japanese_style_normal)
    )
    story.append(
        Paragraph(f"住所: {invoice_data['customer_address']}", japanese_style_normal)
    )
    story.append(Spacer(1, 0.3 * inch))

    # 商品明細テーブル
    table_data = [["品目", "単価", "数量", "金額"]]
    for item in items_data:
        table_data.append(
            [
                item["description"],
                f"{item['unit_price']:,}",
                str(item["quantity"]),
                f"{item['amount']:,}",
            ]
        )

    table_data.append(["", "", "合計", f"{invoice_data['total_amount']:,}"])

    item_table = Table(table_data, colWidths=[3 * inch, inch, inch, inch])
    item_table.setStyle(
        TableStyle(
            [
                ("FONT", (0, 0), (-1, -1), "JapaneseFont"),
                ("BACKGROUND", (0, 0), (-1, 0), colors.grey),
                ("TEXTCOLOR", (0, 0), (-1, 0), colors.whitesmoke),
                ("ALIGN", (0, 0), (-1, -1), "CENTER"),
                ("ALIGN", (1, 1), (-1, -1), "RIGHT"),
                ("BOTTOMPADDING", (0, 0), (-1, 0), 12),
                ("BACKGROUND", (0, 1), (-1, -1), colors.beige),
                ("GRID", (0, 0), (-1, -1), 1, colors.black),
                ("BACKGROUND", (-2, -1), (-1, -1), colors.lightgrey),
            ]
        )
    )
    story.append(item_table)
    story.append(Spacer(1, 0.5 * inch))

    # 備考
    story.append(Paragraph(f"備考: {invoice_data['notes']}", japanese_style_normal))

    # ページテンプレートの定義
    def page_template(canvas: Canvas, doc: BaseDocTemplate):
        canvas.saveState()
        canvas.setFont("JapaneseFont", 9)
        canvas.drawString(inch, 0.75 * inch, f"ページ {doc.page}")
        canvas.restoreState()

    # PDFのビルド
    doc.build(story, onFirstPage=page_template, onLaterPages=page_template)
    print(f"請求書 '{output_filepath}' が生成されました。")


def main():
    """
    請求書PDFを生成するメイン関数
    """
    invoice_data_example: InvoiceData = {
        "invoice_date": "2025年9月25日",
        "invoice_no": "INV-2025-001",
        "customer_name": "株式会社TechBizPlus",
        "customer_address": "東京都渋谷区神南1-1-1",
        "total_amount": 150000,
        "notes": "いつもありがとうございます。",
    }
    items_data_example: list[ItemData] = [
        {
            "description": "Webサイト開発",
            "unit_price": 100000,
            "quantity": 1,
            "amount": 100000,
        },
        {
            "description": "保守費用",
            "unit_price": 50000,
            "quantity": 1,
            "amount": 50000,
        },
    ]
    print("請求書の生成を開始します...")
    create_invoice_pdf("invoice_example.pdf", invoice_data_example, items_data_example)


if __name__ == "__main__":
    main()

3. ReportLabと他のライブラリとの連携(例: 画像処理ライブラリ)

ReportLabはPDF生成に特化していますが、Pythonのエコシステムは非常に豊かです。他のライブラリと連携することで、ReportLab単体では難しい高度な処理も実現できます。


画像処理ライブラリ(Pillowなど)との連携

ReportLabでPDFに画像を埋め込むことは可能ですが、画像の加工(リサイズ、フィルタ適用、テキストオーバーレイなど)はPillow(PILの後継)のような画像処理ライブラリで行うのが効率的です。

import io
import os
from PIL import Image as PILImage, ImageDraw

# reportlab で PDF を作成するためのモジュールをインポート
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Image, Flowable
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.pagesizes import A4
from reportlab.lib.units import inch

# フォントを扱うためのモジュール
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont


def create_pdf_with_processed_image(output_filepath: str, image_path: str):
    """
    画像を読み込みリサイズし、PDF に埋め込む関数

    Parameters
    ----------
    output_filepath : str
        出力する PDF ファイルのパス
    image_path : str
        画像ファイル(PNG/JPG 等)のパス
    """
    # --------------------------------------------------
    # フォントの登録とスタイル設定
    # --------------------------------------------------
    # ここでは日本語フォント「ipaexg.ttf」を登録
    pdfmetrics.registerFont(TTFont("JapaneseFont", "ipaexg.ttf"))
    styles = getSampleStyleSheet()
    # 通常テキスト用スタイル
    japanese_style_normal = ParagraphStyle(
        name="JapaneseNormal", parent=styles["Normal"], fontName="JapaneseFont"
    )
    # 見出し用スタイル(h1 ベース)
    japanese_style_h1 = ParagraphStyle(
        name="JapaneseH1", parent=styles["h1"], fontName="JapaneseFont"
    )

    # PDF ドキュメントオブジェクトを作成(A4 用紙)
    doc = SimpleDocTemplate(output_filepath, pagesize=A4)
    # PDF に追加する要素(Flowable)のリスト
    story: list[Flowable] = []

    # --------------------------------------------------
    # Pillow で画像を読み込み、リサイズ処理
    # --------------------------------------------------
    with PILImage.open(image_path) as img:
        # 画像の元サイズ取得
        img_width, img_height = img.size
        # アスペクト比(高さ ÷ 幅)
        aspect_ratio = img_height / float(img_width)

        # PDF の幅に合わせて画像をリサイズ(例:幅 4 インチ)
        new_width = 4 * inch
        new_height = new_width * aspect_ratio

        # リサイズ後の画像をメモリ上に保持するバッファ
        processed_image_buffer = io.BytesIO()
        # 画像サイズ変更(整数ピクセルに丸める)
        resized_img = img.resize((int(new_width), int(new_height)))
        # バッファへ PNG 形式で保存
        resized_img.save(processed_image_buffer, format="PNG")
        # バッファの読み取り位置を先頭に戻す
        _ = processed_image_buffer.seek(0)

    # --------------------------------------------------
    # PDF の内容を構築
    # --------------------------------------------------
    # 見出し(加工済み画像の埋め込み)
    story.append(Paragraph("加工済み画像の埋め込み", japanese_style_h1))
    # スペーサ(行間調整)
    story.append(Spacer(1, 0.2 * inch))
    # リサイズした画像を PDF に埋め込む
    story.append(Image(processed_image_buffer, width=new_width, height=new_height))
    # 追加スペース
    story.append(Spacer(1, 0.5 * inch))
    # 説明文
    story.append(
        Paragraph(
            "Pillowでリサイズされた画像が埋め込まれています。", japanese_style_normal
        )
    )

    # PDF をビルドしてファイルに書き込む
    doc.build(story)
    print(f"PDF '{output_filepath}' が生成されました。")


def create_placeholder_image(
    filepath: str, size: tuple[int, int] = (200, 100), text: str = "Sample"
):
    """
    テスト用のプレースホルダー画像を生成する関数

    Parameters
    ----------
    filepath : str
        保存先ファイルパス(PNG)
    size : tuple[int, int], optional
        画像サイズ(幅, 高さ)をピクセル単位で指定。デフォルトは (200, 100)
    text : str, optional
        画像に描画するテキスト。デフォルトは "Sample"
    """
    # 既に同名ファイルが存在する場合は生成しない
    if os.path.exists(filepath):
        return

    # グレーの背景で画像を作成
    img = PILImage.new("RGB", size, color="grey")
    draw = ImageDraw.Draw(img)
    # テキストを描画(左上に配置)
    draw.text((10, 10), text, fill="white")
    # PNG ファイルとして保存
    img.save(filepath, "PNG")
    print(f"プレースホルダー画像 '{filepath}' を生成しました。")


def main():
    """
    画像処理を含む PDF を生成するメイン関数
    """
    image_filename = "sample_image.png"
    output_filename = "image_report.pdf"

    print("画像処理と PDF 生成を開始します...")
    create_placeholder_image(image_filename)
    create_pdf_with_processed_image(output_filename, image_filename)


if __name__ == "__main__":
    main()

他のPDFライブラリ(pypdf, PyMuPDFなど)との連携

  • 既存PDFへのコンテンツ追加: pypdfやPyMuPDFで既存のPDFを読み込み、ReportLabで生成した特定のコンテンツ(例: ページ番号、透かし、動的なグラフ)を既存PDFのページにオーバーレイする。
  • ReportLabで生成したPDFの加工: ReportLabで生成したPDFをpypdfで結合・分割したり、PyMuPDFで高速にテキスト抽出・画像抽出を行う。
[!TIP]
ReportLabは「PDFをゼロから生成する」ことに強みがありますが、既存のPDFを「編集・加工する」場合はpypdfやPyMuPDFの方が適しています。これらのライブラリの特性を理解し、目的に応じて使い分ける、あるいは連携させることが、より高度なPDF自動化への道を開きます。

まとめ:ReportLabで実現する究極のPDF生成自動化

この記事では、ReportLabを使った動的なPDF帳票生成と、多くの開発者が直面する日本語対応の課題に焦点を当てて解説しました。

  • 動的データからの帳票生成: CSV、JSON、データベースといった様々なデータソースからデータを読み込み、ReportLabのFlowablesFrameを駆使して、柔軟かつプロフェッショナルな帳票を生成する方法を学びました。
  • 完璧な日本語対応: 日本語フォントの準備とReportLabへの登録、そして文字化け対策の基本を理解し、日本語を含むPDFを問題なく生成できるようになりました。
  • 他のライブラリとの連携: Pillowなどの画像処理ライブラリや、pypdf、PyMuPDFといった他のPDFライブラリとReportLabを連携させることで、さらに高度なPDF処理ワークフローを構築できる可能性を探りました。

ReportLabは、その学習コストの高さから敬遠されがちですが、一度その強力な機能をマスターすれば、PythonによるPDF自動化の可能性は無限に広がります。手作業で行っていた帳票作成やレポート生成の時間を大幅に削減し、より創造的な業務に集中できるようになるでしょう。

今日からできる、はじめの一歩

  1. IPAexフォントのダウンロード: まずはIPAexゴシック(ipaexg.ttf)をダウンロードし、ReportLabの日本語対応を試してみましょう。
  2. シンプルな請求書スクリプトの実行: 本記事で紹介した請求書生成のコードを実際に動かし、動的なデータがPDFに反映されることを確認してください。
  3. 既存の業務フローを洗い出す: あなたの業務の中で、ReportLabで自動化できそうなPDF関連のタスクがないか考えてみましょう。

このシリーズでは、Python PDFライブラリの全体像から各ライブラリの応用まで、実践的な知識を提供しています。次回の記事では、PyMuPDFの高速PDF処理能力と日本語テキスト抽出の最前線に迫ります。お楽しみに!

https://www.visionnurture.com/python_pdf_for_beginner_008

この記事が役に立ったら、ぜひチームに共有したり、X(旧Twitter)で感想をポストしてください!あなたのプロジェクトでの活用事例もぜひ教えてください!


FAQ

Q1: ReportLab以外に日本語対応が容易なPDF生成ライブラリはありますか?

A1: ReportLabは低レベルな制御が可能ですが、日本語対応の手間を省きたい場合は、HTML/CSSからPDFを生成するライブラリ(例: WeasyPrint)も検討に値します。これらはWeb技術に慣れていれば、より直感的にレイアウトを組むことができ、日本語フォントの扱いも比較的容易な場合があります。ただし、ReportLabのような低レベルな描画制御は難しくなります。

Q2: ReportLabで生成したPDFのファイルサイズを小さくするにはどうすればよいですか?

A2: ReportLabはデフォルトでフォントのサブセット化を行うため、通常は最適化されています。しかし、画像が多く含まれる場合は、画像を事前にPillowなどで最適化(圧縮、リサイズ)してから埋め込むことで、ファイルサイズを削減できます。また、不要なフォントを登録しない、使用しないフォントを埋め込まないといった基本的な対策も有効です。

Q3: ReportLabで複雑なグラフを生成するにはどうすればよいですか?

A3: ReportLabにはreportlab.graphicsモジュールがあり、棒グラフ、折れ線グラフ、円グラフなどを描画できます。より高度なグラフやデータ可視化が必要な場合は、matplotlibseabornでグラフを生成し、その画像をReportLabでPDFに埋め込むというアプローチが一般的です。

Q4: WebアプリケーションでReportLabを使う際の注意点はありますか?

A4: WebアプリケーションでReportLabを使用する場合、生成したPDFをブラウザに直接ストリーミングしたり、一時ファイルとして保存してダウンロードさせたりする方法があります。特に注意すべきは、フォントファイルのパス管理と、大量のリクエストがあった場合のパフォーマンスです。フォントファイルはアプリケーションのデプロイパッケージに含め、一時ファイルの生成・削除を適切に行う必要があります。また、非同期処理やワーカープロセスを活用して、PDF生成がWebサーバーの応答性を阻害しないように設計することが重要です。


参考文献


免責事項

この記事は情報提供のみを目的としており、ReportLabの使用に関するいかなる保証も行いません。コード例は説明のために簡略化されており、実際の運用環境での使用には追加の考慮事項が必要となる場合があります。ライセンス、セキュリティ、パフォーマンスに関する最終的な判断は、読者自身の責任において行ってください。


SNSでもご購読できます。

コメントを残す

*