機械造りの猫 PDF改造計画

TARGET

システムから作成したPDFがそのまま使用できず、対応にお困りの方

- 目次 -

はじめに

システムから作成したPDFが使えない。

そんな話をよく聞きます。

その理由としては、元々の設計が甘かったという場合もありますが、多くは運用ルールが変更された事に起因する様です。勿論、それはシステムを改修すれば解決するのですが、予算や日程という問題があり、簡単に行えるものではありません。

ユーザー目線からは簡単な修正内容であっても、システム内部的にも簡単とは限らず、予想外の費用や日程を要する場合も多いからです。

では、市販アプリケーションでPDFを修正できないか、と考えると、それも難しい。

市販アプリケーションが対応するのは個別の処理であって、「PDFが使えない」理由は全体の問題であるからです。

残る方法としては、自作プログラムで対応するか、外部委託する方法が考えられます。

本ページでは、Pythonプログラミングを通じて、どの様に処理を行うかのヒントを提示して参ります

求められる処理と対応方法

1

テキストの抽出

PDFを加工するにあたり、その為のデータはどこにあるかと言えば、PDF内部になります。

勿論、PDFとは別にテキストなりExcelなりでデータが存在すれば、その方が良いのですが、残念ながらそうした場合は非常に少ないです。

従って、PDF内部よりテキスト情報を取得する必要があります。

対応するライブラリはいろいろありますが、ここではPDFMiner.sixを使用します。

from pdfminer.high_level import extract_text
print(extract_text(’test.pdf ’))

これだけで、指定したPDF ファイルのテキストが全て取得できます。

また、PDFMiner.six は、文字の座標も取得できます。

from pdfminer.pdfparser import PDFParser
from pdfminer.pdfdocument import PDFDocument
from pdfminer.pdfpage import PDFPage
from pdfminer.pdfpage import PDFTextExtractionNotAllowed
from pdfminer.pdfinterp import PDFResourceManager
from pdfminer.pdfinterp import PDFPageInterpreter
from pdfminer.converter import PDFPageAggregator
from pdfminer.layout import LAParams
from pdfminer.layout import LTContainer
from pdfminer.layout import LTTextLine

def get_objs(layout, results):
    if not isinstance(layout, LTContainer):
        return
    for obj in layout:
        if isinstance(obj, LTTextLine):
            results.append({’位置’: obj.bbox, ‘文字’: obj.get_text()})
        get_objs(obj, results)

def main(path):
    with open(path, "rb") as f:
        parser = PDFParser(f)
        document = PDFDocument(parser)
        if not document.is_extractable:
            raise PDFTextExtractionNotAllowed
        laparams = LAParams(all_texts=True)
        rsrcmgr = PDFResourceManager()
        device = PDFPageAggregator(rsrcmgr, laparams=laparams)
        interpreter = PDFPageInterpreter(rsrcmgr, device)
        for page in PDFPage.create_pages(document):
            interpreter.process_page(page)
            layout = device.get_result()
            results = []
            print('位置情報とテキスト-------------------------------------')
            get_objs(layout, results)
            for r in results:
                print(r)

main('test.pdf')

【実行結果】

位置情報とテキスト-------------------------------------
{'位置': (162.575, 793.32915, 466.2150000003, 806.2815), '文字': '文字読み取りテスト\n'}
{'位置': (423.0264, 772.3949, 456.6264, 786.3949), '文字': 'test\n'}
###以下略###

この結果を応用すれば、PDFの指定位置にあるテキストをページ毎に取得するなどの事が実現でき、様々な編集を行う条件として活用が可能になります。

2

テキストの照合

1の作業で、テキストの抽出が行えました。1つのファイル内で処理が完結するなら、そのデータを加工していけば良いのですが、複数ファイルに跨がった処理の場合は、各ファイルのテキストの照合を行う必要がでてきます。

#2つのファイルから、ページ番号とテキストを取得して配列へ格納
pdf1 = [
  [1, "0000105"],
  [2, "0001092"],
  [3, "0000647"],
  [4, "1000663"]
]
pdf2 = [
  [1, "0000646"],
  [2, "0000105"],
  [3, "9901308"],
  [4, "0001092"]
]

#2つのファイルを比較し、テキストが一致するページを取得
print("2つのファイルの比較結果--------------------"
for i in range(len(pdf1)):
    for j in range(len(pdf2)):
        if pdf1[i][1] == pdf2[j][1]:
            txt1 = "pdf1:"" + str(pdf1[i][0]) + "p"
            txt2 = "pdf2:" + str(pdf2[j][0]) + "p"
            print("・" + txt1 + "と" + txt2 + "の内容一致"

【実行結果】

2つのファイルの比較結果--------------------
・pdf1: 1pとpdf2: 2pの内容一致
・pdf1: 2pとpdf2: 4pの内容一致

という流れで、照合できます。

照合が完了すれば、そのデータを抜き出すなり並び順を一致させるなり、といった加工を実施できる様になります。

3

テキストの並び替え

PDFファイルの並び替えは、比較的需要の多い加工処理です。処理としては、PDFからテキストを抽出し、そのテキストを並び替え、その並び順に沿ってPDFを並び替えるという処理になります。

ここでは、その中のテキストの並び替え処理を見てみましょう。

# ①の処理で取得したテキスト例(ページ番号と郵便番号)
list = [ [1, "105-0002"], [2, "110-0012"], [3, "100-0001"] ]

# 郵便番号でソート
sortResult = sorted(list, reverse=False, key=lambda x:x[1])

# 結果の出力
for r in sortResult:
    print(r)

【実行結果】

[3, "100-0001"]
[1, "105-0002"]
[2, "110-0012"]

という流れで、簡単に並び替えを実行できます。

ただし、単純な昇順、降順でない場合は、一工夫する必要があります。

4

テキストの追加

PDFファイルにテキストを書き込む事もできます。これもいろいなライブラリがありますが、ここではpyMuPDFを使用します。

import fitz

targetF = "test1.pdf"
resultF = "test2.pdf"
addText = "addText"
writer = fitz.open(targetF)
page = writer.load_page(0)

#文字を追加する位置をポイントで指定
pos = fitz.Point(3.52778, 3.52778)
rc = page.insert_text(page, addText, fontname="helv", fontsize=12)
writer.save(resultF)

という流れで、テキストの追加が実行できます。

①で読み込んだテキストと併用し、内容に応じて追加するテキストを変更するなどの事も可能です。

5

ファイルの合成

Acrobatの背景を追加する機能の様に2つのPDFファイルを合成する事を求められる事があります。

合成するファイルが1種類であれば、それこそAcrobatの背景追加処理を実行すれば良いのですが、合成すべきファイルが複数種類あると、それを判断して差し替えを行う必要があります。

PDFの合成処理は、pypdfを使用します

from pypdf import PdfWriter, PdfReader
with open("test1.pdf", "rb") as f1:
    with open("test2.pdf", "rb") as f2:
        with open("test3.pdf", "wb") as f3:
            mergePage = PdfReader(f1).pages[0]
            mergePage.merge_page(PdfReader(f2).pages[0])
            outFile = PdfWriter()
            outFile.add_page(mergePage)
            outFile.write(f3)

という流れで、1ページのPDFの合成が実行できます。

複数ページの結合は、ページを取得する処理をループ化する事で実現可能です。

また、合成ファイルの差し替えは、①で読み込んだテキストを判定したif処理などで実行します。

6

バーコードの追加

PDFファイルにテキストではなく、バーコードを追加する場合もあります。

追加するバーコードをQRコードとした場合、pyqrcodeとpyMuPDFを使用して下記の様に実行します。

import io
import qrcode
import .tz

#QRコードの生成
qr = qrcode.QRCode(
    version=3,
    error_correction=qrcode.constants.ERROR_CORRECT_L,
    box_size=4,
    border=4
)
qr.add_data("https://www.dataline.co.jp/")
qr.make()
img = qr.make_image(.ll_color="black", back_color="#FFFFFF")
img_bytes = io.BytesIO()
img.save(img_bytes)
img_bytes = img_bytes.getvalue()

#PDFへバーコードの追加
with fitz.open("test1.pdf") as base:
    page = base.load_page(0)
    pos = fitz.Rect(10, 10, 60, 60)
    page.insert_image(pos, stream=img_bytes)
    base.save("insertBarcode.pdf") 

という流れで、バーコードの追加が実行できます。

このサンプルでは固定テキストをバーコード化していますが、①で読み取りしたテキストを使用するなど、様々な応用をする事が考えられます。

7

PDFの分割

1つのPDFファイルを複数ファイルに分割する場合があります。そうした場合は、例えばpypdfを使用して分割を行う事ができます。

from pypdf import PdfWriter, PdfReader
baseFile = PdfReader("page.pdf")
for pageNum in range(len(baseFile.pages)):
    page = baseFile.pages[pageNum]
    writer = PdfWriter()
    fName = f"page_{pageNum + 1}.pdf"
    with open(fName, "wb") as outputF:
        writer.write(outputF)

上記サンプルでは1ページずつに分割していますが、PDFから取得したテキストと組み合わせ、同一テキストの場合は同じファイルとして分割するなどの工夫をする事も可能です。

8

PDFの結合

⑦とは逆に、複数のPDFファイルを1ファイルに結合する場合があります。この場合も、pypdfで処理することができます。

ファイル単位の単純な結合なら次の様な処理になります。

import pypdf
with pypdf.PdfMerger() as mergeF:
    mergeF.append("test1.pdf")
    mergeF.append("test2.pdf")
    mergeF.write("mergeResult.pdf")

もう少し複雑で、複数ファイルを指定のページ順番で結合する場合は、次の様な方法で実行する事ができます

from pypdf import PdfReader, PdfWriter
#結合順番の指定例(ファイル名、ページ番号)
list = [
  ["test1.pdf", 3],
  ["test2.pdf", 1],
  ["test1.pdf", 2],
  ["test1.pdf", 1],
  ["test2.pdf", 2],
  ["test2.pdf", 3],
]
writer = PdfWriter()
for i in range(len(list)):
    writer.add_page(PdfReader(list[i][0]).pages[list[i][1] - 1])
with open("mergeResult.pdf", "wb") as mergeF:
    writer.write(mergeF)

外部委託という方法

ここまで、pythonでPDFを加工する方法を説明してきました。

いろいろなライブラリがあるので、それ程難しくはないのですが、データ内容や求められる加工内容によって様々な手順をとらなくてはならず、簡単とまでは言えないと思っています。

日々の業務にプラスしてそうした作業を行うのは非常に大変である事を思えば、外部委託するという選択肢も検討の余地があるでしょう。

当社では、PDF加工の様々な実績を持っております。まずは一度お問い合わせ頂ければ幸いです。

ACHIEVEMENT

1

後作業に合わせて並べ替え

#並替

とある役所の納税通知書での事例です。

1人あたり1~17枚の不規則な枚数を発行し、納税通知書は1人分を封筒に入れて投函する案件で、システムからは住民番号順でPDFが作成されました。

データにはタイミングマークもなく、目視で人の区切りを見分けるほか無く、ミスを誘発する様な危険な状態でした。

当社は、お客様より相談を受け、発行枚数毎に纏まる様に並び順を変更し、結果、ミス無く封入作業が完了しました。

2

2種類のPDFを照合

#分割#照合#並替

とある役所の検診通知での事例です。

特定検診用の受診券とがん検診受診券の発行を行う案件で、従来はそれぞれ投函していましたが、両方共の受診券を発行される方が多く、同封して投函する事でコストの削減を検討されていました。

当社では2種類のデータを照合し、両方に存在する方と片方にしか存在しない方を分割、また、両方に存在する方は同じ並び順に変える事で、役所の要望を満たすPDFファイルに変換する事に成功しました。

3

バーコードの追加

#バーコード追加

とあるお客様ですが、システムから作成したPDFを出力して投函されていました。多くの数量を投函されているのに、作成されたPDFにはカスタマーバーコードが付加されておらず、投函時の割引を受けていらっしゃいませんでした。

当社よりお客様にカスタマーバーコードの付加処理をご提案し、その結果、お客様は当社作業費を差し引いても十分なコスト削減する事ができました。

4

A4請求書を圧着はがきへ

#面付け#縮小

とあるお客様より、システムから作成されるA4請求書を圧着はがきに変更したい旨のご相談を頂きました。

お話を伺うと、1社あたり1~8枚あたりの請求書を発行しており、従来は封書で投函していたが、経費削減の為、データをそのまま利用してはがきで投函したいとの事。幸い、文字サイズは十分大きかったので、A4請求書をはがきサイズに縮小しても文字の可読性に問題はありませんでした。

当社では、面付けの仕組みを応用し、無事データの変換に成功し、お客様のご要望通りのデータを生成いたしました。

5

擬似的な帳票の複写処理

#マスク#丁合

とあるお客様が特殊な事情により、1つの請求書データから、手書きの複写伝票の様に納品書他のデータを生成して出力しなくてはいけない事態に陥りました。

当社では、出力情報を一部隠したり、文字を差し替えたりしたデータを別途生成の上、それを丁合処理したデータを生成する作業を実施いたしました。

お問い合わせフォーム

ご返信先メールアドレス
ご返信先メールアドレス(確認用)
社名
部署名
役職
ご担当者名
お問い合わせ内容

※ご記入いただきました個人情報は、お問い合わせへの対応でのみ利用いたします。

エラー