【python】既存のPDFファイルにテキストを差し込む

## はじめに
こんばんは、既存のPDFを編集する必要があり、
いくつか調べたのでその備忘録を残します。
「pdf sample」って検索して出てきたこの[dummy.pdf](https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf)を使って試してみます。


## 開発環境
Windows 10
python-3.8.2
使用ライブラリ：pdfrw, PyMuPDF, PyPDF2, reportlab

```Python:install
pip install pdfrw
pip install pymupdf
pip install pypdf2
pip install ReportLab
```

## 方法1
pdfrw, reportlabを使う

```Python:pdf.py
def insert_text_output_pdfrw(pdf_file_path, insert_text):
    """
    既存のPDFファイルに文字を挿入し、別名で出力します
    :param pdf_file_path:       既存のPDFファイルパス
    :param insert_text:         挿入するテキスト
    :return:
    """
    from pdfrw import PdfReader
    from pdfrw.buildxobj import pagexobj
    from pdfrw.toreportlab import makerl
    from reportlab.pdfgen import canvas
    from reportlab.pdfbase.cidfonts import UnicodeCIDFont
    from reportlab.pdfbase import pdfmetrics
    from reportlab.lib.units import mm

    # 出力名
    output_name = "pdfrw.pdf"
    # PDF新規作成
    cc = canvas.Canvas(output_name)

    # フォントの設定
    font_name = "HeiseiKakuGo-W5"
    pdfmetrics.registerFont(UnicodeCIDFont(font_name))
    cc.setFont(font_name, 16)

    # 既存ページ読み込み
    page = PdfReader(pdf_file_path, decompress=False).pages
    # 1ページ目をオブジェクトに
    pp = pagexobj(page[0])
    cc.doForm(makerl(cc, pp))

    # 挿入位置(mm指定)
    target_x, target_y = 10*mm, 10*mm
    # 文字列挿入
    cc.drawString(target_x, target_y, insert_text)
    cc.showPage()
    # 保存
    cc.save()
```

## 方法2
PyPDF2, reportlabを使う

```Python:pdf.py
def insert_text_output_pdf_PyPDF2(pdf_file_path, insert_text):
    """
    既存のPDFファイルに文字を挿入し、別名で出力します
    :param pdf_file_path:       既存のPDFファイルパス
    :param insert_text:         挿入するテキスト
    :return:
    """
    from PyPDF2 import PdfFileWriter, PdfFileReader
    from io import BytesIO
    from reportlab.pdfgen import canvas
    from reportlab.lib.pagesizes import A4
    from reportlab.lib.units import mm

    buffer = BytesIO()
    # PDF新規作成
    p = canvas.Canvas(buffer, pagesize=A4)
    # 挿入位置(mm指定)
    target_x, target_y = 10*mm, 10*mm
    p.drawString(target_x, target_y, insert_text)
    p.showPage()
    p.save()

    # move to the beginning of the StringIO buffer
    buffer.seek(0)
    new_pdf = PdfFileReader(buffer)
    # read your existing PDF
    existing_pdf = PdfFileReader(open(pdf_file_path, 'rb'), strict=False)
    output = PdfFileWriter()
    # 既存PDFの1ページ目を読み取り
    page = existing_pdf.getPage(0)
    # 新規PDFにマージ
    page.mergePage(new_pdf.getPage(0))

    output.addPage(page)
    # 出力名
    output_name = "PyPDF2.pdf"
    output_stream = open(output_name, 'wb')
    output.write(output_stream)
    output_stream.close()
```

## 方法3
PyMuPDFを使う

```Python:pdf.py
def insert_text_output_pdf_fitz(pdf_file_path, insert_text):
    """
    既存のPDFファイルに文字を挿入し、別名で出力します
    :param pdf_file_path:       既存のPDFファイルパス
    :param insert_text:         挿入するテキスト
    :return:
    """
    import fitz
    
    # 既存PDFの読み取り
    reader = fitz.open(pdf_file_path)
    # 新規PDFの作成
    writer = fitz.open()
    # 既存PDFの1ページ目を新規PDFに流し込む
    writer.insertPDF(reader, from_page=0, to_page=0)
    # 既存PDFの1ページを読み込む
    page = writer.loadPage(0)
    # 挿入位置(mmをptsに変えて指定)
    target_x, target_y = mm_to_pts(10), mm_to_pts(10)
    p = fitz.Point(target_x, target_y)  # start point of 1st line
    rc = page.insertText(p,  # bottom-left of 1st char
                         insert_text,  # the text (honors '\n')
                         fontname="helv",  # the default font
                         fontsize=16,  # the default font size
                         rotate=0,  # also available: 90, 180, 270
                         )
    # 出力名
    output_name = "PyMuPDF.pdf"
    writer.save(output_name)
```
PyMuPDFでmm指定するやり方がちょっと調べても分からなかった。
デフォルトはpts(pt)みたいです。
ptsは分かりにくいのでmmに換算して指定するために関数作ります。
普通にライブラリ側で何らかの方法が用意されていると思いますが深追いするのも面倒なので致し方なし

```Python:pdf.py
def pts_to_mm(pts) -> float:
    """
    ptsからmmに変換します
    :param pts:
    :return:
    """
    return float(pts) * 0.352778


def mm_to_pts(mm) -> float:
    """
    mmからptsに変換します
    :param mm:
    :return:
    """
    return float(mm) / 0.352778
```

## 実行
dummy.pdfを同階層に配置して実行します。

```Python:pdf.py
if __name__ == '__main__':
    file_path = "dummy.pdf"
    insert_text_output_pdfrw(file_path, "pdfrwだお")
    insert_text_output_pdf_PyPDF2(file_path, "PyPDF2だお")
    insert_text_output_pdf_fitz(file_path, "PyMuPDFだお")
```

実行結果はそれぞれこんな感じ

方法1
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/168779/ecf29494-5359-4dd2-5402-4a264c320f47.png)

方法2
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/168779/8e892634-ae42-37be-f7d5-af3f334ca754.png)

方法3
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/168779/bc74f20e-0e57-51cc-e34e-1956d89892f0.png)

# 終わりに
フォントは日本語対応できるものをちゃんと調べないと文字化けで駄目そう。
また、既存PDFの読み込みもファイルによってはフォント関係で駄目なのがあるみたいです。
(pdfrwで日本語フォントの入ったpdfが読み込めないのがいくつかありました)
あと、reportlabは左下原点ですが、PyMuPDFは画像と同じ左上原点になるようなので注意が必要かも。

reportlab&emsp;&emsp;&emsp;&emsp;PyMuPDF
Y軸 &emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&thinsp;    &thinsp;            ●ーーー→　X軸
↑ &emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp; |
| &thinsp;&thinsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp; |
| &thinsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp; ↓
●ーーー→　X軸&emsp;&emsp; Y軸    


## ソース
```Python:pdf.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-


def insert_text_output_pdfrw(pdf_file_path, insert_text):
    """
    既存のPDFファイルに文字を挿入し、別名で出力します
    :param pdf_file_path:       既存のPDFファイルパス
    :param insert_text:         挿入するテキスト
    :return:
    """
    from pdfrw import PdfReader
    from pdfrw.buildxobj import pagexobj
    from pdfrw.toreportlab import makerl
    from reportlab.pdfgen import canvas
    from reportlab.pdfbase.cidfonts import UnicodeCIDFont
    from reportlab.pdfbase import pdfmetrics
    from reportlab.lib.units import mm

    # 出力名
    output_name = "pdfrw.pdf"
    # PDF新規作成
    cc = canvas.Canvas(output_name)

    # フォントの設定
    font_name = "HeiseiKakuGo-W5"
    pdfmetrics.registerFont(UnicodeCIDFont(font_name))
    cc.setFont(font_name, 16)

    # 既存ページ読み込み
    page = PdfReader(pdf_file_path, decompress=False).pages
    # 1ページ目をオブジェクトに
    pp = pagexobj(page[0])
    cc.doForm(makerl(cc, pp))

    # 挿入位置(mm指定)
    target_x, target_y = 10*mm, 10*mm
    # 文字列挿入
    cc.drawString(target_x, target_y, insert_text)
    cc.showPage()
    # 保存
    cc.save()


def insert_text_output_pdf_PyPDF2(pdf_file_path, insert_text):
    """
    既存のPDFファイルに文字を挿入し、別名で出力します
    :param pdf_file_path:       既存のPDFファイルパス
    :param insert_text:         挿入するテキスト
    :return:
    """
    from PyPDF2 import PdfFileWriter, PdfFileReader
    from io import BytesIO
    from reportlab.pdfgen import canvas
    from reportlab.lib.pagesizes import A4
    from reportlab.lib.units import mm

    buffer = BytesIO()
    # PDF新規作成
    p = canvas.Canvas(buffer, pagesize=A4)
    # 挿入位置(mm指定)
    target_x, target_y = 10*mm, 10*mm
    p.drawString(target_x, target_y, insert_text)
    p.showPage()
    p.save()

    # move to the beginning of the StringIO buffer
    buffer.seek(0)
    new_pdf = PdfFileReader(buffer)
    # read your existing PDF
    existing_pdf = PdfFileReader(open(pdf_file_path, 'rb'), strict=False)
    output = PdfFileWriter()
    # 既存PDFの1ページ目を読み取り
    page = existing_pdf.getPage(0)
    # 新規PDFにマージ
    page.mergePage(new_pdf.getPage(0))

    output.addPage(page)
    # 出力名
    output_name = "PyPDF2.pdf"
    output_stream = open(output_name, 'wb')
    output.write(output_stream)
    output_stream.close()


def insert_text_output_pdf_fitz(pdf_file_path, insert_text):
    """
    既存のPDFファイルに文字を挿入し、別名で出力します
    :param pdf_file_path:       既存のPDFファイルパス
    :param insert_text:         挿入するテキスト
    :return:
    """
    import fitz

    # 既存PDFの読み取り
    reader = fitz.open(pdf_file_path)
    # 新規PDFの作成
    writer = fitz.open()
    # 既存PDFの1ページ目を新規PDFに流し込む
    writer.insertPDF(reader, from_page=0, to_page=0)
    # 既存PDFの1ページを読み込む
    page = writer.loadPage(0)
    # 挿入位置(mmをptsに変えて指定)
    target_x, target_y = mm_to_pts(10), mm_to_pts(10)
    p = fitz.Point(target_x, target_y)  # start point of 1st line
    rc = page.insertText(p,  # bottom-left of 1st char
                         insert_text,  # the text (honors '\n')
                         fontname="helv",  # the default font
                         fontsize=16,  # the default font size
                         rotate=0,  # also available: 90, 180, 270
                         )
    # 出力名
    output_name = "PyMuPDF.pdf"
    writer.save(output_name)


def pts_to_mm(pts) -> float:
    """
    ptsからmmに変換します
    :param pts:
    :return:
    """
    return float(pts) * 0.352778


def mm_to_pts(mm) -> float:
    """
    mmからptsに変換します
    :param mm:
    :return:
    """
    return float(mm) / 0.352778


if __name__ == '__main__':
    file_path = "dummy.pdf"
    insert_text_output_pdfrw(file_path, "pdfrwだお")
    insert_text_output_pdf_PyPDF2(file_path, "PyPDF2だお")
    insert_text_output_pdf_fitz(file_path, "PyMuPDFだお")

```


# 参考URL
https://qiita.com/ekzemplaro/items/aa9a1886fa5d7cbb8bdb
https://qiita.com/shin1007/items/d5391aa0ee14463b780c
https://qiita.com/mima_ita/items/3f698050196d4af3a46d#pdf%E3%81%AE%E3%83%86%E3%82%AD%E3%82%B9%E3%83%88%E6%83%85%E5%A0%B1%E3%81%AF%E3%81%A9%E3%81%86%E3%82%84%E3%81%A3%E3%81%A6%E5%8F%96%E5%BE%97%E3%81%97%E3%81%A6%E3%81%84%E3%82%8B%E3%81%AE%E3%81%8B
https://gist.github.com/kzim44/5023021
https://pymupdf.readthedocs.io/en/latest/
上記URLに感謝です。