意外と簡単!Pythonを使ってPDFを出力してみた  〜画像挿入編〜

過去に、Pythonのreportlabという外部ライブラリを使ってPDFを出力する方法をご紹介しました。
※過去の記事はこちらを参照ください。

前回はデータを表形式に出力するだけのシンプルなPDFだったので、今回はPDF上に画像を追加する方法を記事にしたいと思います。
会社のロゴ画像を挿入したり、画像挿入する必要がある方はぜひ参考にしてみてください!

MacBook pro
Anaconda3(MiniForge3)
Python 3.9.10
reportlab 3.6.12(無料版)
Pillow 9.0.1

今回は画像を扱うため、以下のコマンドから「Pillow」をインストールします。

==========================================
$ conda install -c anaconda pillow
==========================================

Pillowは「Pythonで画像処理を行うためのライブラリ」です。

画像処理を行うライブラリといえば有名なのがOpenCVです。

しかし、PillowはOpenCVよりもシンプルに扱うことができるので、トリミングやカラーの変更など単純な画像処理をするだけであればPillowがおすすめです。

画像はdrawInlineImageに引数として、画像のパス・位置(横縦)・画像のサイズ(横縦)を与えて埋め込みます。

以下のコードを paper.save() の前に追記します。

====================================================
# 挿入したいファイルのパス
img = Image.open('image01.jpg')

# 画像の埋め込み(画像ファイルのパス, 横位置, 縦位置, 画像横サイズ, 画像縦サイズ)
pdf_canvas.drawInlineImage(img, 50, 750, 100, 60)
====================================================

サンプルコードに上記のコードを追記して、PDFを出力してみます。

すると、以下のように画像が挿入されました!

PNG画像を挿入する場合は、drawInlineImage() ではなく、drawImage() を利用して[mask]オプションを有効にします。

また、drawImage() で利用するImageパラメータを、下のコードのように[open]メソッドなどで作成する場合にエラーになるので注意が必要です!

※ 先ほど紹介した drawInlineImage() には[mask]オプションはありません。

## エラーになるため注意
img = Image.open('image01.png')

その場合は、以下のように ReportLabの ImageReader() を利用して、Imageオブジェクトを作成するとエラーを改善することができます。

from reportlab.lib.utils import ImageReader

## 挿入したいファイルのパス ##
img = ImageReader('image01.png')

## 画像の埋め込み(画像ファイルのパス, 横位置, 縦位置, 画像横サイズ, 画像縦サイズ)
pdf_canvas.drawImage(img, 50, 750, 100, 60, preserveAspectRatio=True, mask='auto')

前回の記事に掲載したサンプルコードに、画像出力するコードを追加しています。

※ 背景透過(PNG画像)の場合のコードはコメント化しています。

# -*- coding: utf-8 -*-
import pandas as pd
import datetime
import math 

from reportlab.pdfgen import canvas
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.cidfonts import UnicodeCIDFont
from reportlab.lib.pagesizes import A4, portrait
from reportlab.platypus import Table, TableStyle
from reportlab.lib.units import mm
from reportlab.lib import colors
from reportlab.platypus.frames import Frame

from PIL import Image
from reportlab.lib.utils import ImageReader

t_delta = datetime.timedelta(hours=9)
JST = datetime.timezone(t_delta, 'JST')
now = datetime.datetime.now(JST)
d = now.strftime('%Y%m%d') #YYYYMMDD形式に書式化
ymd = now.strftime('%Y年%m月%d日') #YYYYMM形式に書式化

# 初期設定
def make(pdf_data):
    filename="気象データ" #ファイル名
    pdf_canvas = set_info(filename) #キャンバス名
    print_string(pdf_canvas, pdf_data)
    pdf_canvas.save() # 保存

def set_info(filename):
    pdf_canvas = canvas.Canvas("./{0}_{1}.pdf".format(d,filename)) #保存先

    ## JPGの場合 ##########################################################
    ## 挿入したいファイルのパス ##
    img = Image.open('image01.jpg')
    ## 画像の埋め込み(画像ファイルのパス, 横位置, 縦位置, 画像横サイズ, 画像縦サイズ)
    pdf_canvas.drawInlineImage(img, 50, 750, 100, 60)

    ## 背景透過(PNG画像)の場合 ################################################################
    # img = ImageReader('image01.png')
    # pdf_canvas.drawImage(img, 50, 750, 100, 60, preserveAspectRatio=True, mask='auto')
    ########################################################################################

    pdf_canvas.setAuthor("") #作者
    pdf_canvas.setTitle("")  #表題
    pdf_canvas.setSubject("")#件名
    return pdf_canvas

#フォーマット作成
def print_string(pdf_canvas, pdf_data):
    
    maxlow = 45 #行数
    pages = (len(pdf_data) + 1) / maxlow #ページ数
    num = math.ceil(pages)
        
    for i in range(0, len(pdf_data), 44):
        tmp_pdf_data = pdf_data[i: i+44]
   
        pdfmetrics.registerFont(UnicodeCIDFont('HeiseiKakuGo-W5')) # フォント
        width, height = A4 # 用紙サイズ

        # (1)タイトル
        font_size = 24 # フォントサイズ
        pdf_canvas.setFont('HeiseiKakuGo-W5', font_size)
        pdf_canvas.drawString(230, 770, '気象実績データ') # 書き出し(横位置, 縦位置, 文字)

        # (2)作成日
        font_size = 7
        pdf_canvas.setFont('HeiseiKakuGo-W5', font_size)
        pdf_canvas.drawString(465, 770,  f'作成日: {ymd}')

        # (3) 気象データ取得地点
        target_date = pdf_data[0][0]
        prefecture = pdf_data[0][1]

        header_data = [
                ['取得日 ',target_date],
                ['都道府県 ',prefecture],  
            ]
        table = Table(header_data, colWidths=(40*mm,60*mm), rowHeights=5*mm)#tableの大きさ
        table.setStyle(TableStyle([#tableの装飾
                ('FONT', (0, 0), (-1, -1), 'HeiseiKakuGo-W5', font_size),#フォントサイズ
                ('BOX', (0, 0), (-1, -1), 1, colors.black),#罫線
                ('INNERGRID', (0, 0), (-1, -1), 1, colors.black),
                ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),#フォント位置
            ]))
        table.wrapOn(pdf_canvas, 20*mm, 250*mm)#table位置
        table.drawOn(pdf_canvas, 20*mm, 250*mm)

        # (4)地点ごとの気象データ
        detail_data = [
                ['地点', '平均気温(℃)', '最高気温(℃)','最低気温(℃)','降水量合計(mm)','降水量合計(mm)','天気(昼)'],
            ]

        temp_data = [row[2:9] for row in tmp_pdf_data]
        detail_data.extend(temp_data)

        if len(detail_data) <= maxlow:
            index_no = maxlow - len(detail_data)
            for idx in range(index_no):
                detail_data.append([" ", " ", " ", " ", " ", " ", " "])

        table = Table(detail_data, colWidths=(25*mm,25*mm,25*mm,25*mm,25*mm,25*mm,25*mm), rowHeights=5*mm)
        table.setStyle(TableStyle([
                ('FONT', (0, 0), (-1, -1), 'HeiseiKakuGo-W5', font_size),
                ('BOX', (0, 0), (-1, -(index_no + 1)), 1, colors.black),#罫線
                ('INNERGRID', (0, 0), (-1, -(index_no + 1)), 1, colors.black),#罫線
                ('ALIGN', (1, 0), (6, -1), 'RIGHT'),#右揃え
                ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),#フォント位置
            ]))
        table.wrapOn(pdf_canvas, 20*mm, 20*mm)
        table.drawOn(pdf_canvas, 20*mm, 20*mm)

        # # 1枚目終了
        pdf_canvas.showPage()

# 作成
if __name__ == '__main__':
    
    #気象データ読み込み
    input_fileName = "東京天気.csv"
    df_csv = pd.read_csv(filepath_or_buffer=input_fileName, encoding='shift_jis')
    pdf_data = df_csv.values.tolist()

    make(pdf_data)
  • 画像挿入には drawInlineImage()を使う。ただし背景透過しないので注意!
  • 背景透過でPNG画像を挿入する場合は、drawImage()を使う