PythonでPDFの帳票を作る

プログラミング

概要

Pythonで一から帳票を作成します。
帳票を書くこと自体は、単純作業で難しくはありません。

Windowsの場合、ちょっと困るのがフォントの問題です。

C:\Windows\Fontsにフォントがあるのですがttcファイルになっています。

Pythonからだと私の知る限りttfしか使えません。
(ttcは、ttfのコレクションです)

ですのでttcからttfを抜き出して使います。
(ほかの方法としては、Google Fonts をダウンロードして使わせてもらうこともできます。)

それと帳票を実務で使う場合、PDFにセキュリティの設定が必要ですのでその設定もします。

フォントの抜き出し

使えそうなコードをユーザーのフォルダに保存して、コード内でパスを指定して使えるように登録します。

今回のコードでは、
C:\Users\[ユーザー名]\Fonts
というフォルダを作ってそこにフォントを保存します。
(あらかじめ作ってください。)

ttfは、そのままユーザーのフォントフォルダにコピーします。
ttcは、ttfを抽出してフォントフォルダにttcファイルを保存します。

両方とも日本語文字”あ”が使用できるかを確認しています。

インストール

pip install fonttool

コード

実行すると C:\Windows\Fonts 内からユーザーの Fonts フォルダに
使用可能なフォントを抽出します。
Fontsフォルダは、あらかじめ用意してください。

import os
import shutil
from fontTools.ttLib import TTCollection, TTFont

# フォントフォルダと保存先のディレクトリを指定
source_folder = "C:\\Windows\\Fonts"
destination_folder = "C:\\Users\\[ユーザー名]\\Fonts"
os.makedirs(destination_folder, exist_ok=True)

# 日本語のサンプル文字
sample_text = "あ"

# フォントを判定し保存する関数
def save_japanese_fonts(source_folder, destination_folder):
    for font_file in os.listdir(source_folder):
        font_path = os.path.join(source_folder, font_file)

        # .ttfファイルを直接処理
        if font_file.endswith(".ttf"):
            try:
                font = TTFont(font_path)
                cmap = font["cmap"].getBestCmap()
                if ord(sample_text) in cmap:  # 日本語文字がサポートされているか確認
                    dest_path = os.path.join(destination_folder, font_file)
                    shutil.copy(font_path, dest_path)
                    print(f"コピー完了: {font_file}")
            except Exception as e:
                print(f"エラー: {font_file} - {e}")

        # .ttcファイルを処理
        elif font_file.endswith(".ttc"):
            try:
                ttc = TTCollection(font_path)
                for index, font in enumerate(ttc.fonts):
                    cmap = font["cmap"].getBestCmap()
                    if ord(sample_text) in cmap:  # 日本語文字がサポートされているか確認
                        font_name = font['name'].getDebugName(1) or f"font_{index}"
                        font_name = font_name.replace(" ", "_")  # ファイル名に安全な形式
                        dest_path = os.path.join(destination_folder, f"{font_name}.ttf")
                        font.save(dest_path)
                        print(f"抽出完了: {font_name}.ttf")
            except Exception as e:
                print(f"エラー: {font_file} - {e}")

# 実行
save_japanese_fonts(source_folder, destination_folder)

 

コード解説

                for index, font in enumerate(ttc.fonts):
                    cmap = font["cmap"].getBestCmap()
                    if ord(sample_text) in cmap:  # 日本語文字がサポートされているか確認
                        font_name = font['name'].getDebugName(1) or f"font_{index}"
                        font_name = font_name.replace(" ", "_")  # ファイル名に安全な形式
                        dest_path = os.path.join(destination_folder, f"{font_name}.ttf")
                        font.save(dest_path)
                        print(f"抽出完了: {font_name}.ttf")
 for index, font in enumerate(ttc.fonts):

ttc から font を抜き出します。

if ord(sample_text) in cmap: 

フォントのコレクションマップに”あ”が存在するかを調べます。

font.save(dest_path)

問題なければフォントを ttf として保存します。

PDF帳票の作成

以下のような帳票を作成します。

  • ユーザーパスワードは、設定しません
  • オーナーパスワードを”1111″として設定します
    印刷などの権限を設定するのに必要です
  • 印刷不可とします
  • コピー不可とします

インストール

pip install reportlab
pip install pypdf3

ReportLab で文字列や罫線などを書き込み一度 generated_invoice_with_borders.pdf を仮に作ります。

PyPDF3で、セキュリティの設定をして secured_invoice.pdf を作っています。
このファイルが目的のファイルです。

コード

from PyPDF3 import PdfFileReader, PdfFileWriter
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
from reportlab.lib.pagesizes import A4
from reportlab.pdfgen import canvas

# フォントの登録
font_path = r"c:\Users\[ユーザー名]\fonts\BIZ_UDPGothic.ttf"  # 使用したいフォントのパス
pdfmetrics.registerFont(TTFont("BIZUDPGothic", font_path))  # フォントを登録

# PDF生成関数
def generate_invoice(output_file):
    page_width, page_height = A4
    margin = 50
    c = canvas.Canvas(output_file, pagesize=A4)
    
    c.setFont("BIZUDPGothic", 16)
    c.drawString(margin, page_height - margin, "請求書")
    
    c.setFont("BIZUDPGothic", 12)
    c.drawString(margin, page_height - margin - 30, "株式会社 ごろう 御中")
    c.drawString(page_width - 150, page_height - margin - 30, "No. 1234560")
    
    c.drawString(margin, page_height - margin - 60, "請求日: 2025年1月15日")
    c.drawString(margin, page_height - margin - 80, "下記の通り、ご請求申し上げます。")
    c.drawString(margin, page_height - margin - 100, "株式会社 サブロウ")
    c.drawString(margin, page_height - margin - 120, "担当: 佐藤")
    
    headers = ["No.", "摘要", "数量", "単位", "単価", "金額"]
    x_positions = [margin, margin + 50, margin + 150, margin + 200, margin + 300, margin + 400, page_width - margin]
    y_start = page_height - margin - 160
    row_height = 20
    
    y_position = y_start
    c.setFont("BIZUDPGothic", 10)
    for i, header in enumerate(headers):
        c.drawString(x_positions[i] + 5, y_position - row_height + 5, header)
    c.line(margin, y_position, page_width - margin, y_position)
    c.line(margin, y_position - row_height, page_width - margin, y_position - row_height)
    
    items = [
        ["1", "AAA", "1", "個", "1,000", "1,000"],
        ["2", "BBB", "2", "個", "2,000", "4,000"],
        ["3", "CCC", "5", "個", "3,000", "15,000"],
        ["4", "DDD", "8", "個", "700", "5,600"],
        ["5", "EEE", "15", "個", "1,300", "19,500"],
    ]
    
    for row in items:
        y_position -= row_height
        for i, cell in enumerate(row):
            c.drawString(x_positions[i] + 5, y_position - row_height + 5, cell)
        c.line(margin, y_position - row_height, page_width - margin, y_position - row_height)
    
    for x in x_positions:
        c.line(x, y_start, x, y_position - row_height)
    
    y_position -= 2 * row_height
    c.setFont("BIZUDPGothic", 10)
    c.drawString(margin + 300, y_position, "小計")
    c.drawString(margin + 400, y_position, "45,100")
    
    y_position -= row_height
    c.drawString(margin + 300, y_position, "税 (8%)")
    c.drawString(margin + 400, y_position, "3,608")
    
    y_position -= row_height
    c.drawString(margin + 300, y_position, "総合計")
    c.drawString(margin + 400, y_position, "48,708")
    
    y_position -= 2 * row_height
    c.setFont("BIZUDPGothic", 10)
    c.drawString(margin, y_position, "備考: 支払期限は2025年1月31日です。")
    c.drawString(margin, y_position - row_height, "振込先: ○○銀行 XX支店 普1234567")
    
    c.save()

# セキュリティ設定を追加する関数
def add_security_with_pypdf3(input_pdf, output_pdf, owner_password):
    reader = PdfFileReader(input_pdf)
    writer = PdfFileWriter()

    # ページを追加
    for page_num in range(reader.getNumPages()):
        writer.addPage(reader.getPage(page_num))

    # セキュリティ設定(印刷とコピーを無効)
    writer.encrypt(
        user_pwd="",  # 閲覧用パスワード(空文字で不要)
        owner_pwd=owner_password,  # オーナーパスワード
        use_128bit=True  # 128ビット暗号化を有効化
    )

    # PDFを保存
    with open(output_pdf, "wb") as f:
        writer.write(f)

# 実行
input_path = "generated_invoice_with_borders.pdf"
secured_output_path = "secured_invoice.pdf"

generate_invoice(input_path)
add_security_with_pypdf3(input_path, secured_output_path, owner_password="1111")

print(f"セキュリティ付きPDFが生成されました: {secured_output_path}")

 

コード解説

# フォントの登録
font_path = r"c:\Users\[ユーザー名]\fonts\BIZ_UDPGothic.ttf"  # 使用したいフォントのパス
pdfmetrics.registerFont(TTFont("BIZUDPGothic", font_path))  # フォントを登録

まず、フォントを使うために pdfmetrics.registerFont() で “BIZUDPGothic” と言う名前で
フォントを使えるようにフォントのパスを登録します。
これで “BIZUDPGothic” を指定してフォントが使えます。

   # セキュリティ設定(印刷とコピーを無効)
    writer.encrypt(
        user_pwd="",  # 閲覧用パスワード(空文字で不要)
        owner_pwd=owner_password,  # オーナーパスワード
        use_128bit=True  # 128ビット暗号化を有効化
    )

ユーザーパスワードは、””(空)を指定します。
必要であれば設定します。
今回は、空なので閲覧時に入力不要です。

あとは、この設定で印刷もコピーも不可になります。

細かく、印刷だけ不可とかコピーだけ不可にできません。

まぁ、これで出来上がったPDFは印刷もコピーも出来ませんので試してみてください。

他のライブラリでもっと細かな設定が出来るようでしたら改めてお知らせします。

まとめ

Pythonで、PDFを作る方法を説明しました。

  • PDFを作るときに必要なフォントを抽出する方法を説明しました
  • フォントを使えるように登録する方法を説明しました
  • 実務で使えるようにPDFに権限の設定をする方法を説明しました

これで、帳票や契約書などが印刷することが出来るようになりました。
他のデータと組み合わせれば、色々な書類の自動作成が簡単にできます。

それでは、次回は既存のPDFの編集や加工について説明する予定です。

お楽しみに!

Commnts

タイトルとURLをコピーしました