PythonでPDFポートフォリオ(コレクション)の作成

PythonでOutlookから選択したメールの情報を取得し、PDFポートフォリオ(コレクション:以下、PDFポートフォリオに表現を統一)を作成するスクリプトを作成した時の記録です。PDFポートフォリオは大変便利ですが、日本語の情報が見当たらないので紹介します。

作成環境

  • Python環境

Pythonの環境はAnacondaを利用しました。本記事作成時に利用したのは64-Bit Graphical Installer (457 MB)のPython 3.8です。下記URLのページよりダウンロードしてください。

Anaconda:https://www.anaconda.com/products/individual

  • 必要なライブラリの導入

Anaconda Prompt (Anaconda3)で以下を実行します。PyMuPDFとfizの導入順によってはエラーが出るので注意です。なお、利用したAnacondaはpywin32ライブリが初めから導入されていましたが、もし素のPythonを利用する方は「pip install pywin32」とAnaconda Prompt (Anaconda3)に入力して導入してください。

pip install PyMuPDF
pip install fiz
pip install fonttools

Outlookからの情報取得

OutlookはOffice365のバージョン2104で確認しています。例として下記画像メールの件名、受信日、本文、差出人、宛先(To)、写し(Cc)の情報を取得します。

  • 情報取得のスクリプト

Outlookから情報を取得するためにwin32com.clientライブラリを利用します。本ライブラリはエクセルなどの情報取得にも利用することができます。なお、選択メールの情報のSubjectなどを指定することで取り出すことができます。

import win32com.client
from datetime import datetime
#Outlook処理
outlook = win32com.client.Dispatch('Outlook.Application')
#選択メールの情報を取得
messages = outlook.ActiveExplorer().Selection
#print(type(messages))

for cnt, message in enumerate(messages):
    #受信日
    ItemDate = message.ReceivedTime
    ItemDate = ItemDate.strftime("%Y-%m-%d %H:%M:%S")
    print('受信日_選択メール%d:%s' %(cnt+1, ItemDate))
    #件名
    ItemSubject = str(message.Subject)
    print('件名_選択メール%d:%s' %(cnt+1, ItemSubject))
    #本文
    ItemMessage = str(message.Body)
    print('本文_選択メール%d:%s' %(cnt+1, ItemMessage))
    #差出人(From)
    ItemFrom = str(message.SenderEmailAddress)
    print('差出人_選択メール%d:%s' %(cnt+1, ItemFrom))
    #宛先(To)
    ItemTo = str(message.To)
    print('宛先_選択メール%d:%s' %(cnt+1, ItemFrom))
    #写し(Cc)
    ItemCc = str(message.CC)
    print('Cc_選択メール%d:%s' %(cnt+1, ItemCc))
    #添付ファイル
    ItemAttachements = message.attachments
    #添付ファイル数
    ItemAttachementsCnt = message.attachments.count
    print('添付ファイル数_選択メール%d:%s' %(cnt+1, ItemAttachementsCnt))

実行結果

受信日_選択メール1:2021-05-05 12:18:30
件名_選択メール1:ブログテストメール
本文_選択メール1: 

Pythonは楽しいな。でも、Rも良いもんだ。

差出人_選択メール1:info@karada-good.net
宛先_選択メール1:info@karada-good.net
Cc_選択メール1:info@karada-good.net
添付ファイル数_選択メール1:1

PDFポートフォリオの構造

PDFポートフォリオの作成ですが、PDFを構成する最上位の文書カタログ辞書の設定です。文書カタログ内に新たに「Collection」を追記し、Collection/Type、Collection/Schema、Name/Names/EmbeddedFiles/Names、Collection/Splitなどを設定することでPDFポートフォリオを作成することができます。なお、PDF内のフォームやファイル名はencodeコマンドなどで「utf-16BE」(utf-16のビックエンディアン)に変換後、先頭に「”FEFF”」を付けることで文字化けしません。例えば下記に「Collection」の記述例を示します。

「Collection/Schema」はPDFポートフォリオのフォームのラベル内容、「Name/Names/EmbeddedFiles/Names」はPDFポートフォリオで表示する各ファイルの情報、「Collection/Split」はPDFポートフォリオの表示領域を設定します。「Name/Names/EmbeddedFiles/Names」は通常は/CIの<< >>内を編集するだけでよいと思います。また、情報のラベルは「Collection/Schema」の内容と同じでなければいけません。今回の例ではfrom、subject、rdate、attacheNです。

  • PDFポートフォリオの例
  • Collectionの記述例
&lt;&lt;
  /Type /Catalog
  /Pages 2 0 R
  /Collection &lt;&lt;
    /Type /Collection
    /Schema &lt;&lt;
      /from &lt;&lt;
        /Subtype /S
        /N &lt;FEFF5DEE51FA4EBA&gt;
        /O 1
        /V true
        /E false
      &gt;&gt;
      /subject &lt;&lt;
        /Subtype /S
        /N &lt;FEFF4EF6540D&gt;
        /O 2
        /V true
        /E false
      &gt;&gt;
      /rdate &lt;&lt;
        /Subtype /S
        /N &lt;FEFF65E54ED8&gt;
        /O 3
        /V true
        /E false
      &gt;&gt;
      /attacheN &lt;&lt;
        /Subtype /S
        /N &lt;FEFF6DFB4ED830D530A130A430EB6570&gt;
        /O 4
        /V true
        /E false
      &gt;&gt;
    &gt;&gt;
    /Sort &lt;&lt;
      /S /rdate
      /A false
    &gt;&gt;
    /View /D
    /Split &lt;&lt;
      /Direction /H
      /Position 15
    &gt;&gt;
  &gt;&gt;
  /Names &lt;&lt;
    /EmbeddedFiles &lt;&lt;
      /Names [ (FEFF004d006500730073006100670065003000300030002e007000640066)
          &lt;&lt;
            /CI &lt;&lt;
              /Type /CollectionItem
              /from &lt;FEFF0069006E0066006F0040006B00610072006100640061002D0067006F006F0064002E006E00650074&gt;
              /subject &lt;FEFF30D630ED30B030C630B930C830E130FC30EB&gt;
              /rdate (2021-05-05 12:18:30)
              /attacheN (1)
            &gt;&gt;
            /EF &lt;&lt;
              /F 3 0 R
            &gt;&gt;
            /F &lt;FEFF30D630ED30B030C630B930C830E130FC30EB002E007000640066&gt;
            /UF &lt;FEFF30D630ED30B030C630B930C830E130FC30EB002E007000640066&gt;
            /Desc (FEFF004d006500730073006100670065003000300030002e007000640066)
            /Type /Filespec
          &gt;&gt; ]
    &gt;&gt;
  &gt;&gt;
  /PageMode /UseAttachments
&gt;&gt;

PDFポートフォリオの作成

  • 日本語フォントファイル

IPAexゴシック(Ver.004.01)を利用します。URLよりダウンロードしてPDFポートフォリオスクリプト実行ファイルと同じ場所(カレントフォルダ)に保存してください。詳しい方は同じ場所でなくともよいです。スクリプト内でフォントファイルの場所を設定してください。

IPAexフォント:https://moji.or.jp/ipafont/ipaex00401/

  • pyMuPDFライブラリを使用したPDFポートフォリオスクリプト

次にPyMuPDF(importするときは’fitz’)ライブラリを利用して、先ほどのメール情報を利用してPDFポートフォリオを作成します。スクリプトはページ上部の「情報取得のスクリプト」から順番に実行してください。

スクリプトを実行するとカレントフォルダにメールの添付ファイル、メール内容を書き込んだ「Test.pdf」と「Test.pdf」を組み込んだPDFポートフォリオ「PortFolio.pdf」が保存されます。本当はtempフォルダなどを利用してメール内容のPDFを消去したり、文字数やページ当たりの行数などを調整するのがよいです。あくまでPDFポートフォリオ作成の基本的な内容です。スクリプト内のコメントを参考に改良してみてください。

まずは必要なライブラリを読み込みます。osライブラリはカレントフォルダの場所を取得、reライブラリは文字の置換に使用しています。

import fitz
import os
import re

メール内容を書き込んだ「Test.pdf」作成スクリプト

###&#36984;&#25246;&#12513;&#12540;&#12523;PDF&#12434;&#12300;Test.pdf&#12301;&#20316;&#25104;#####
#PDF&#12501;&#12449;&#12452;&#12523;&#12434;&#20316;&#25104;
doc = fitz.open()
page = doc.new_page() #&#26032;&#35215;&#12506;&#12540;&#12472;
#&#12501;&#12457;&#12531;&#12488;&#12501;&#12449;&#12452;&#12523;&#12398;&#35373;&#23450;
ffile = 'ipaexg.ttf'
font = 'F0'
#&#29992;&#32025;&#12469;&#12452;&#12474;&#24773;&#22577;
width, height = fitz.PaperSize('a4')
#&#24773;&#22577;&#12398;&#26360;&#12365;&#36796;&#12415;
page.insert_textbox(fitz.Rect(30, 40, 100, 100), '&#21463;&#20449;&#26085;&#65306;', fontsize = 9, fontname = font, fontfile = ffile)
page.insert_textbox(fitz.Rect(70, 40, 250, 150), ItemDate, fontsize = 9, fontname = font, fontfile = ffile)
page.insert_textbox(fitz.Rect(30, 51, 100, 100), '&#20214;&#12288;&#21517;&#65306;', fontsize = 9, fontname = font, fontfile = ffile)
page.insert_textbox(fitz.Rect(70, 51, 550, 350), ItemSubject, fontsize = 9, fontname = font, fontfile = ffile)
page.insert_textbox(fitz.Rect(30, 62, 100, 100), '&#24046;&#20986;&#20154;&#65306;', fontsize = 9, fontname = font, fontfile = ffile)
page.insert_textbox(fitz.Rect(70, 62, 250, 150), ItemFrom, fontsize = 9, fontname = font, fontfile = ffile)
page.insert_textbox(fitz.Rect(30, 72, 100, 100), '&#23451;&#12288;&#20808;&#65306;', fontsize = 9, fontname = font, fontfile = ffile)
page.insert_textbox(fitz.Rect(70, 72, 550, 350), ItemTo, fontsize = 9, fontname = font, fontfile = ffile)
page.insert_textbox(fitz.Rect(30, 82, 1000, 1000), 'CC:', fontsize = 9, fontname = font, fontfile = ffile)
page.insert_textbox(fitz.Rect(70, 82, 550, 350), ItemCc, fontsize = 9, fontname = font, fontfile = ffile)
page.drawLine(fitz.Point(30, 92), fitz.Point(550, 92), color = (0, 0, 0), width = 0.7, dashes = "[3] 0")      
#&#26412;&#25991;&#12398;&#26360;&#12365;&#36796;&#12415;
FarstRect = fitz.Rect(30, 95, width, height) #&#26360;&#12365;&#36796;&#12415;&#20301;&#32622;&#12398;&#35373;&#23450;
page.insert_textbox(FarstRect, ItemMessage,
                    fontname = font, fontfile = ffile) #&#26412;&#25991;&#12398;&#26360;&#12365;&#36796;&#12415;

#&#28155;&#20184;&#12501;&#12449;&#12452;&#12523;&#20966;&#29702;
if ItemAttachementsCnt != 0:
    for fcnt, attachment in enumerate(ItemAttachements):
        fcnt = fcnt + 1
        fname = str(fcnt) + '.' + str(attachment).split('.')[-1]
        #&#28155;&#20184;&#12501;&#12449;&#12452;&#12523;&#12398;&#20445;&#23384;
        print(attachment)
        attachment.SaveASFile(os.getcwd() + '\\' + str(attachment))
        #&#12501;&#12449;&#12452;&#12523;&#12398;&#22475;&#12417;&#36796;&#12415;
        doc.embfile_add(fname, bytearray(open(str(attachment), 'rb').read()), filename = str(attachment)) 
                
#PDF&#20869;&#12398;&#12501;&#12457;&#12531;&#12488;&#12434;&#12469;&#12502;&#12475;&#12483;&#12488;&#21270;&#65288;&#12501;&#12449;&#12452;&#12523;&#12469;&#12452;&#12474;&#12398;&#28187;&#23569;&#65289;
doc.subset_fonts()

#Test.pdf&#12398;&#20445;&#23384;
doc.save('Test.pdf', garbage = 4, deflate = True)

ここまでで、下記のPDFが作成できます。

「Test.pdf」を組み込んだPDFポートフォリオ「PortFolio.pdf」作成スクリプト

#&#12509;&#12540;&#12488;&#12501;&#12457;&#12522;&#12458;&#12398;&#20316;&#25104;
doc = fitz.open()
page = doc.new_page(width=50, height=50)
#page = doc.new_page()
#&#12503;&#12524;&#12499;&#12517;&#12540;&#34920;&#32025;&#12408;&#12525;&#12468;&#12398;&#26360;&#12365;&#36796;&#12415;
#page.insertImage(fitz.Rect(30, -50, 130, 100), stream = open(os.getcwd() + '\\' + 'Karada_good_logo.png', 'rb').read()
#&#12503;&#12524;&#12499;&#12517;&#12540;&#34920;&#32025;&#12408;&#26360;&#12365;&#36796;&#12415;
Rect = fitz.Rect(2, 20, 1300, 1000) #&#26360;&#12365;&#36796;&#12415;&#20301;&#32622;&#12398;&#35373;&#23450;
page.insert_textbox(Rect, "KARADA GOOD.net", fontsize = 5, color = (0.25, 0.25, 0.25), 
                    fontname = font, fontfile = ffile) 
#&#12509;&#12540;&#12488;&#12501;&#12457;&#12522;&#12458;&#12501;&#12457;&#12540;&#12512;&#12398;&#34920;&#31034;&#38917;&#30446;&#35373;&#23450;
doc.xref_set_key(1, 'Collection/Type', '/Collection')
#&#24046;&#20986;&#20154;
doc.xref_set_key(1, 'Collection/Schema/from', '<</Subtype /S /N <FEFF5DEE51FA4EBA> /O 1 /V true /E false>>')
#&#20214;&#21517;
doc.xref_set_key(1, 'Collection/Schema/subject', '<</Subtype /S /N <FEFF4EF6540D> /O 2 /V true /E false>>')
#&#26085;&#20184;
doc.xref_set_key(1, 'Collection/Schema/rdate', '<</Subtype /S /N <FEFF65E54ED8> /O 3 /V true /E false>>')
#&#28155;&#20184;&#12501;&#12449;&#12452;&#12523;&#25968;
doc.xref_set_key(1, 'Collection/Schema/attacheN', '<</Subtype /S /N <FEFF6DFB4ED830D530A130A430EB6570> /O 4 /V true /E false>>')
#sort&#35373;&#23450;
doc.xref_set_key(1, 'Collection/Sort', '<</S /rdate /A false>>')
#&#34920;&#31034;&#35373;&#23450;
doc.xref_set_key(1, 'Collection/View', '/D')
#&#34920;&#31034;&#38936;&#22495;&#12398;&#35373;&#23450;
doc.xref_set_key(1, 'Collection/Split', '<</Direction /H /Position 15>>')
#&#12509;&#12540;&#12488;&#12501;&#12457;&#12522;&#12458;&#12395;&#36861;&#21152;
for cnt, message in enumerate(messages):
    #&#12501;&#12449;&#12452;&#12523;&#12398;&#22475;&#12417;&#36796;&#12415;
    AddFileName = 'Test.pdf'
    AddFileName = 'FEFF' + AddFileName.encode('utf-16BE', 'replace').hex()
    doc.embfile_add(AddFileName, bytearray(open(os.getcwd() + '\\' + 'Test.pdf', 'rb').read()), filename = ItemSubject + '.pdf')
    #PDF&#12501;&#12449;&#12452;&#12523;&#12503;&#12525;&#12497;&#12486;&#12451;&#12398;&#21462;&#24471;
    CIPropaty = doc.xref_get_key(1, 'Names/EmbeddedFiles/Names')[1]
    CIPropaty = re.sub('\[|\]', '', CIPropaty)
    CIPropaty = CIPropaty.replace('/Type/Filespec>>', '/Type/Filespec>>,')
    CIPropaty = CIPropaty.split(',')
    del CIPropaty[-1]
    #&#24046;&#20986;&#20154;&#20966;&#29702;,&#12499;&#12483;&#12464;&#12456;&#12531;&#12487;&#12451;&#12450;&#12531;&#12395;&#12377;&#12427;
    MSender = 'FEFF' + ItemFrom.encode('utf-16BE', 'replace').hex()
    #&#20214;&#21517;&#20966;&#29702;
    MSubject = 'FEFF' + ItemSubject.encode('utf-16BE', 'replace').hex()
    #&#21463;&#20449;&#26085;&#20966;&#29702;
    MRtime = ItemDate
    #&#28155;&#20184;&#12501;&#12449;&#12452;&#12523;&#25968;
    MAtFiles = str(ItemAttachementsCnt)
    #PDF&#12501;&#12449;&#12452;&#12523;&#12503;&#12525;&#12497;&#12486;&#12451;&#12398;&#26360;&#12365;&#36796;&#12415;
    MakeCI = '<</Type /CollectionItem /from <' + MSender + '> /subject <' + MSubject + '> /rdate (' + MRtime + ') /attacheN (' + MAtFiles + ')>>'
    for index, item in enumerate(CIPropaty):
        CIPropaty[index] = item.replace('/CI<<>>', '/CI' + MakeCI)
    MCIPropaty = ''.join(CIPropaty)
    MCIPropaty = '[' + MCIPropaty + ']'
    doc.xref_set_key(1, 'Names/EmbeddedFiles/Names', MCIPropaty)

#PDF&#12503;&#12525;&#12497;&#12486;&#12451;&#12398;&#26360;&#12365;&#36796;&#12415;
m = {'creationDate': fitz.getPDFnow(), 
     'modDate': fitz.getPDFnow(),
     'creator': 'KARADAGOOD.net',
     'producer': 'PyMuPDF %s' % fitz.VersionBind,
     'title': 'KARADAGOOD.net',
     'subject': 'KARADAGOOD.net',
     'author': 'KARADAGOOD.net'}
doc.setMetadata(m)
#&#12489;&#12461;&#12517;&#12513;&#12531;&#12488;&#12398;&#20445;&#23384;
#PDF&#20869;&#12398;&#12501;&#12457;&#12531;&#12488;&#12434;&#12469;&#12502;&#12475;&#12483;&#12488;&#21270;&#65288;&#12501;&#12449;&#12452;&#12523;&#12469;&#12452;&#12474;&#12398;&#28187;&#23569;&#65289;
doc.subset_fonts()
doc.save('PortFolio.pdf', garbage = 4, deflate = True)
doc.close()

以上で下記のPDFが作成できます。

  • エクスプローラーのプレビューウィンドウでの表示
  • Acrobat Readerでの表示

参考までに作成したPDFファイルです。Acrobatで開くことをお勧めします。

参考資料

・Document management -Portable document format- Part1: PDF1.7: https://www.adobe.com/content/dam/acom/en/devnet/pdf/pdfs/PDF32000_2008.pdf

・Adobe Supplement to the ISO 3200: https://www.adobe.com/content/dam/acom/en/devnet/pdf/adobe_supplement_iso32000.pdf

・PyMuPDF Documentation: https://pymupdf.readthedocs.io/en/latest/index.html


少しでも、誰かの役に立ちますように!!

Prices and shipping availability may change. Please refer to the product page at time of purchase.
Content displayed on this site is provided by Amazon and may be updated or removed.
Amazon Associate, karada-good earns income through qualifying sales.
タイトルとURLをコピーしました