Sansan Tech Blog

Sansanのものづくりを支えるメンバーの技術やデザイン、プロダクトマネジメントの情報を発信

【Techの道も一歩から】第29回「PythonでPDFに文字を埋め込む」

f:id:kanjirz50:20190104142720j:plain

こんにちは。DSOC 研究開発部の高橋寛治です。

いつもお世話になっている PDF ファイルを対象に Python3 で操作します。 PDF ファイルを読み込み、文字を書き込んで、「Hello World!」と世界に挨拶をする方法を紹介します。

PDFファイルの読み込み

PDF(Portable Document Format) ファイルとは、文書のファイルフォーマットです。 学生時代に、PDF構造解説という本を読みましたが、座標と内容を持つオブジェクトで構成された形式という程度しかわかりませんでした。

www.oreilly.co.jp

HTMLパーサもそうですが、フォーマットがわかっていてもパースは大変であるため、素直にライブラリPyPDF2を利用します。

$ pip install PyPDF2

ここでは手元の Word で適当に作成した PDF(word_sample.pdf) を対象とします。

まずは PDF ファイルのメタ情報を確認します。

from PyPDF2 import PdfFileReader

pdf = PdfFileReader("./word_sample.pdf")
pdf.getDocumentInfo()

"""
{'/Title': 'Microsoft Word - word_sample.docx',
 '/Producer': 'macOS バージョン10.14.6(ビルド18G5033) Quartz PDFContext',
 '/Creator': 'Word',
 '/CreationDate': "D:20200601100536Z00'00'",
 '/ModDate': "D:20200601100536Z00'00'",
 '/Keywords': '',
 '/AAPL:Keywords': []}
"""

このように、macOS の Word でどうやら作られたということがメタ情報として残っています。 PyPDF2 はファイルオブジェクトをシークするようなので、with 句で制御する場合は with 句内で PDF に対する処理を書き切る必要があります。

肝心のテキスト抽出ですが、PyPDF2はどうやら日本語のPDFからテキストを抽出できないようです。 テキスト抽出の際には、pdfminer.six など別のライブラリを利用します。 pdfminer.six で単にテキストのみを抽出したい場合は、付属の pdf2txt.py を参考にコードを書きます。

import io

from pdfminer.converter import TextConverter
from pdfminer.layout import LAParams
from pdfminer.pdfinterp import PDFResourceManager, PDFPageInterpreter
from pdfminer.pdfpage import PDFPage

# テキスト変換器の設定
rsrcmgr = PDFResourceManager()
outfp = io.StringIO()

device = TextConverter(
    rsrcmgr,
    outfp,
    laparams=LAParams(),
    imagewriter=None
)

# ページ単位でテキスト変換
with open("./word_sample.pdf", "rb") as fp:
    interpreter = PDFPageInterpreter(rsrcmgr, device)
    for page in PDFPage.get_pages(fp):
        interpreter.process_page(page)

# 出力結果の表示
outfp.seek(0)
print(outfp.read())

"""
BERT で作ってみた⽇本語固有表現抽出器の推論部分を書く 

こんにちは。DSOC  研究開発部の⾼橋寛治です。 

今回は、前回の  「BERT で⽇本語固有表現抽出器を作ってみた」  に続き、作った固有表現
...
"""

レイアウト解析が難しいため、pdfminer ライブラリの機能を利用します。 TextConverter クラスはファイル出力するため、ここでは io.StringIO でメモリ上に出力するようにしています。 文字単位で処理したい場合は、リファレンスを参考に進めるといいと思います。

文字の埋め込み挨拶する

ReportLab ライブラリを利用します。 オープンソースの PDF 作成ライブラリです。

pip install reportlab

PDF に「こんにちは世界!」という文字列を埋め込むことを目標とします。 いわゆる "Hello World!" です。

ReportLab のモジュール類の読み込みや日本語フォントの設定を行います。 フォントのパスはインストールされている日本語 TrueType フォントを設定します。

import io

from PyPDF2 import PdfFileWriter
from reportlab.lib.colors import Color
from reportlab.lib.pagesizes import letter
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.cidfonts import UnicodeCIDFont
from reportlab.pdfbase.ttfonts import TTFont
from reportlab.pdfgen import canvas


pdfmetrics.registerFont(
    TTFont(
        'IPAexGothic',
        '/usr/share/fonts/opentype/ipaexfont-gothic/ipaexg.ttf'
    )
)

文字埋め込みの手順ですが、PyPDF2 の mergePage メソッドを用いてページとページをマージさせる方式で行います。 元となるページに対して、文字を埋め込んだPDFをマージさせるということです。

まずは、 ReportLab でメモリ上(io.BytesIO)に PDF を作成します。 文字サイズやフォント、色を設定して、「こんにちは世界!」を書きます。 テキストは中央を始点とします。

page = pdf.getPage(0)

packet = io.BytesIO()
can = canvas.Canvas(packet, pagesize=letter)

font_size = 16
can.setFont('IPAexGothic', font_size)
can.setFillColor(Color(0, 0, 0, alpha=1))
can.drawString(
    page.mediaBox.getWidth() / 2,
    page.mediaBox.getHeight() / 2,
    "こんにちは世界!"
)

can.save()

次に、PDF をマージして、ファイル出力します。

packet.seek(0)
new_pdf = PdfFileReader(packet)
page.mergePage(new_pdf.getPage(0))

output = PdfFileWriter()
output.addPage(page)
with open("word_sample_output.pdf", "wb") as fout:
    output.write(fout)

生成された PDF ファイルを閲覧して確認しましょう。 PDF ファイルに対して世界に挨拶するという通過儀礼を無事終えることができたでしょうか。

書き込んだ文字は、PDF ビューワで検索可能な文字列となっております。 レイアウトが決まっているものだと、バッチ的に PDF を出力できるかもしれませんね。

扱えるファイルを増やす

PDF ファイルは身近なデータ形式ですが、意外と Python で取り扱ってきませんでした。 容易に文字を追記でき非常に便利だと感じます。 日本語文字を別ライブラリで取得した上で、例えばアノテーション情報の埋め込みなど、使える場面はいろいろありそうです。

PDF ファイルを取り扱う上で難しいのは、テキスト抽出後の整形や表の取り扱いかと思います。 表は表の見た目をしているだけということで、表形式でデータを取得するのはなかなか難しいです。

▼本連載のほかの記事はこちら

buildersbox.corp-sansan.com

執筆者プロフィール

高橋寛治 Sansan株式会社 DSOC (Data Strategy & Operation Center) 研究開発部 研究員

阿南工業高等専門学校卒業後に、長岡技術科学大学に編入学。同大学大学院電気電子情報工学専攻修了。在学中は、自然言語処理の研究に取り組み、解析ツールの開発や機械翻訳に関連する研究を行う。大学院を卒業後、2017年にSansan株式会社に入社。キーワード抽出など自然言語処理を生かした研究開発に取り組む。

© Sansan, Inc.