概要
一つの入力フォーム付きPDFをテンプレートとして読み込み値を差し込んで複数ページを一つのPDFにまとめて出力する
複雑なフィールド設定がされている国税庁発行の源泉徴収票でテスト
環境
Python 3.11
PyMuPDF 1.26.7
実装
import
import io # BytesIO
import fitz # PyMuPDF
NeedAppearances の設定
"NeedAppearances" に "true" を設定しておくと開いたときにビューワが再評価して描画してくれるので値を流し込む場合はよく使う
PyMuPDFでは専用のメソッドが無いらしいので自前で用意する
# "NeedAppearances" に "True"を設定
def set_need_appearances(doc):
# Catalogを取得
catalog_xref = doc.pdf_catalog()
kind, val = doc.xref_get_key(catalog_xref, "AcroForm")
if kind == "xref":
acroform_xref = int(val.split()[0])
doc.xref_set_key(acroform_xref, "NeedAppearances", "true")
elif kind == "dict":
doc.xref_set_key(catalog_xref, "AcroForm", val.replace(">>", " /NeedAppearances true >>"))
フォームフィールドの変換
NeedAppearances = True は便利だけど問題もある
コンボボックス等の選択系のUIが描画されてしまうのでそのままだと印刷には適さない
よってテキスト以外のフォームを書き換える処理が必要になる
# すべてテキストフィールドに変更
def change_widget_to_textfield(pdf):
for page in pdf:
for widget in page.widgets():
if widget.field_type == fitz.PDF_WIDGET_TYPE_TEXT:
continue
# 元の情報を記録
rect = widget.rect
old_name = widget.field_name
current_val = widget.field_value
text_font = widget.text_font
page.delete_widget(widget)
# テキストフィールドとして再作成
new_widget = fitz.Widget()
new_widget.rect = rect
new_widget.field_name = old_name
new_widget.field_type = fitz.PDF_WIDGET_TYPE_TEXT
new_widget.text_font = text_font
page.add_widget(new_widget)
new_widget.update()
入力データ例
rows = [
{ "氏名": "テスト1", "氏名フリガナ": "テスト1", },
{ "氏名": "テスト2", "氏名フリガナ": "テスト2", },
]
PDF_PATH = "源泉徴収票_入力用.pdf"
入力データから帳票出力処理をする
# 最終出力先を用意
out_pdf = fitz.open()
# 元となるテンプレートには不要なページやテキスト以外のフォームがあるので成形
org_pdf = fitz.open(PDF_PATH)
while org_pdf.page_count > 1:
org_pdf.delete_page(-1)
change_widget_to_textfield(org_pdf)
# メモリ上にテンプレを書き出し
org_buf = io.BytesIO()
org_pdf.save(org_buf)
org_buf.seek(0)
org_pdf.close()
# ページ追加ループ
for i, row in enumerate(rows):
# この後フォーム名を書き換えるので毎回メモリから新しく生成
src_doc = fitz.open(stream=org_buf)
# ページ内の全フィールド(Widget)をループ
page = src_doc[0]
for widget in page.widgets():
org_name = widget.field_name
# 値の差し込み
if org_name in row:
widget.field_value = str(row[org_name])
# 他のページとフィールド名が衝突しないように書き換え
widget.field_name = f"{org_name}_{i}"
# 編集不可フラグをセット
widget.field_flags |= fitz.PDF_FIELD_IS_READ_ONLY
widget.update(sync_flags=True)
# データを入力したものを追加
out_pdf.insert_pdf(src_doc)
src_doc.close()
# 保存
set_need_appearances(out_pdf)
out_pdf.save("out.pdf")
out_pdf.close()