0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

PyMuPDFでフォームテンプレートから複数ページの帳票出力方法

0
Posted at

概要

一つの入力フォーム付き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()
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?