python + reportlab で 履歴書フォーマットPDFを作成

#0. はじめに
PythonのPDF作成ライブラリのreportlabを使用して履歴書フォーマット（JIS規格）を作成しました。
<img width="1202" alt="resume.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/484145/51da7449-b1fd-6409-09dc-fad57ea0b531.png">


#1. 実行環境
OS : macOS High Sierra 10.13.6
python : 3.7.2
reportlab : 3.5.23


#2. インストール方法
```
pip install reportlab
```

#3. 初期設定
```python
def make(filename="resume"): # ファイル名の設定
    pdf_canvas = set_info(filename) # キャンバス名の設定
    print_string(pdf_canvas)
    pdf_canvas.save() # pdfを保存

def set_info(filename):
    pdf_canvas = canvas.Canvas("./{0}.pdf".format(filename)) # 保存先の設定
    # ファイル情報の登録（任意）
    pdf_canvas.setAuthor("") # 作者
    pdf_canvas.setTitle("") # 表題
    pdf_canvas.setSubject("") # 件名
    return pdf_canvas
```


#4. 日本語入力
```python
def print_string(pdf_canvas):

    # フォント登録
    pdfmetrics.registerFont(UnicodeCIDFont('HeiseiKakuGo-W5'))

    # 用紙サイズ定義（この場合はA4）
    width, height = A4

    # フォントサイズ定義（この場合は24）
    font_size = 24

    pdf_canvas.setFont('HeiseiKakuGo-W5', font_size)

    # 書き出す(横位置, 縦位置, 文字)を指定
    pdf_canvas.drawString(60, 770, '履  歴  書') 
```
#5. 表の作成
###【データ入力】

・2次元配列でデータを用意します

・3行4列の場合

```python
data = [
        ['(0,0)', '(1,0)', '(2,0)'],
        ['(0,1)', '(1,1)', '(2,1)'],
        ['(0,2)', '(1,2)', '(2,2)'],
        ['(0,3)', '(1,3)', '(2,3)'],
    ]
```

###【tableの大きさの指定】

・全列30mm、全行40mmの場合

```python
table = Table(data, colWidths=30*mm, rowHeights=40*mm)
```

・1列目10mm、2列目20mm、3列目30mm、1〜2行目30mm、3〜4行目50mmの場合

```python
table = Table(data, colWidths=(10*mm, 20*mm, 30*mm), rowHeights=(30*mm, 30*mm, 50*mm, 50*mm))
```

###【tableの装飾】

```python
table.setStyle(TableStyle([
   ...
]))
```

####♢表にフォントを設定する
```python
('FONT', 始点 , 終点 , フォント名 , サイズ)
```

・例

```python
('FONT', (0, 0), (2, 3), self.font_name, 10)
```

・もしくは

```python
('FONT', (0, 0), (-1, -1), self.font_name, 10)
```

<img width="425" alt="FONT.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/484145/e2bc3aec-8553-aa27-27a5-2ce9e1f42b67.png">

####♢表を罫線で囲む
<img width="425" alt="BOX.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/484145/bdeaaa86-eb48-35e6-407a-a4e6493b7487.png">

```python
('BOX', 始点 , 終点 , 太さ , カラー)
```

・例

```python
('BOX', (0, 0), (2, 3), 1, colors.black)
```

・もしくは

```python
('BOX', (0, 0), (-1, -1), 1, colors.black)
```

---
####♢四角の内側に罫線を引く
<img width="425" alt="INNERGRID.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/484145/dce1a0b2-74c6-dcbe-7c79-efcb2460de33.png">

```python
('INNERGRID', 始点 , 終点 , 太さ , カラー)
```

・例

```python
('INNERGRID', (0, 0), (2, 3), 1, colors.black)
```

・もしくは

```python
('INNERGRID', (0, 0), (-1, -1), 1, colors.black)
```
---
####♢フォントの場所を指定
<img width="425" alt="VALIGN.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/484145/cbd2968d-d183-c613-4604-8c400df8d2a5.png">

```python
('VALIGN', 始点 , 終点 , TOP or MIDDLE or BOTTOM)
```

・例

```python
('VALIGN', (0, 0), (0, 3), TOP)
('VALIGN', (1, 0), (1, 3), MIDDLE)
('VALIGN', (2, 0), (2, 3), BOTTOM)
```
---
####♢セルの結合

<img width="425" alt="SPAN.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/484145/de0037d3-ff6c-c674-31aa-58598edbabb3.png">

```python
('SPAN', 始点 , 終点)
```

・例

```python
('SPAN', (0, 0), (0, 1))
('SPAN', (2, 2), (2, 3))
```
---
####♢指定した場所に線を引く
<img width="425" alt="LINE..png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/484145/1d90ef00-10c3-cb81-4224-20a2d1f1c1d2.png">

#####● 縦線
```python
 ('LINEBEFORE', 始点 , 終点 , 太さ , カラー)
```

・例

```python
 ('LINEBEFORE', (2, 1), (2, 2), 1, colors.black)
```

#####● 横線
```python
 ('LINEABOVE', 始点 , 終点 , 太さ , カラー)
```

・例

```python
 ('LINEABOVE', (0, 0), (0, 0), 1, colors.black)
```

###【tableを描き出す位置を指定】
```python
table.wrapOn(キャンバス名, 横位置, 縦位置)
table.drawOn(キャンバス名, 横位置, 縦位置)
```

・例

```python
table.wrapOn(pdf_canvas, 145*mm, 235*mm)
table.drawOn(pdf_canvas, 145*mm, 235*mm)
```



##5. 履歴書フォーム作成

```python:resume.py
# -*- coding: utf-8 -*-
from reportlab.pdfgen import canvas
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.cidfonts import UnicodeCIDFont
from reportlab.lib.pagesizes import A4, portrait
from reportlab.platypus import Table, TableStyle
from reportlab.lib.units import mm
from reportlab.lib import colors

# 初期設定
def make(filename="resume"): # ファイル名
    pdf_canvas = set_info(filename) # キャンバス名
    print_string(pdf_canvas)
    pdf_canvas.save() # 保存

def set_info(filename):
    pdf_canvas = canvas.Canvas("./{0}.pdf".format(filename)) # 保存先
    pdf_canvas.setAuthor("") # 作者
    pdf_canvas.setTitle("") # 表題
    pdf_canvas.setSubject("") # 件名
    return pdf_canvas

#履歴書フォーマット作成
def print_string(pdf_canvas):
    pdfmetrics.registerFont(UnicodeCIDFont('HeiseiKakuGo-W5')) # フォント
    width, height = A4 # 用紙サイズ

```
<img width="601" alt="1~3.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/484145/4ba3b1d2-7439-a9e2-ab60-82efb502b009.png">


```python
    # (1)履歴書 タイトル
    font_size = 24 # フォントサイズ
    pdf_canvas.setFont('HeiseiKakuGo-W5', font_size)
    pdf_canvas.drawString(60, 770, '履  歴  書') # 書き出し(横位置, 縦位置, 文字)
    
    # (2)作成日
    font_size = 10
    pdf_canvas.setFont('HeiseiKakuGo-W5', font_size)
    pdf_canvas.drawString(285, 770,  '    年         月         日現在')

    # (3)証明写真
    # tableを作成
    data = [
            ['    証明写真'],
        ]
    table = Table(data, colWidths=30*mm, rowHeights=40*mm) # tableの大きさ
    table.setStyle(TableStyle([                              # tableの装飾
            ('FONT', (0, 0), (0, 0), 'HeiseiKakuGo-W5', 12), # フォントサイズ
            ('BOX', (0, 0), (0, 0), 1, colors.black),        # 罫線
            ('VALIGN', (0, 0), (0, 0), 'MIDDLE'),            # フォント位置
        ]))
    table.wrapOn(pdf_canvas, 145*mm, 235*mm) # table位置
    table.drawOn(pdf_canvas, 145*mm, 235*mm)
```

<img width="600" alt="4~6.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/484145/eb09af28-aebf-4f38-d6a3-c881188c8c43.png">

```python
    # (4) プロフィール
    data = [
            ['ふりがな','   男  ・  女'],  
            ['氏名',''],
            ['生年月日　　　　　　　　　　　　　　　　　　　　年　　　月　　　日生　（満　　　歳）',''],    
        ]
    table = Table(data, colWidths=(100*mm,20*mm), rowHeights=(7*mm, 20*mm, 7*mm))
    table.setStyle(TableStyle([
            ('FONT', (0, 0), (1, 2), 'HeiseiKakuGo-W5', 8),
            ('BOX', (0, 0), (1, 2), 1, colors.black),
            ('INNERGRID', (0, 0), (1, 2), 1, colors.black),
            ('SPAN',(0, 2), (1, 2)),
            ('SPAN',(1, 0), (1, 1)),
            ('VALIGN', (0, 0), (1, 2), 'MIDDLE'),
            ('VALIGN', (0, 1), (0, 1),'TOP'),
        ]))
    table.wrapOn(pdf_canvas, 20*mm, 232*mm)
    table.drawOn(pdf_canvas, 20*mm, 232*mm)

    # (5)住所
    data = [
            ['ふりがな', '電話'],
            ['連絡先（〒　　　ー　　　　）', 'E-mail'],
            ['ふりがな', '電話'],
            ['連絡先（〒　　　ー　　　　）', 'E-mail'],
        ]
    table = Table(data, colWidths=(120*mm, 40*mm), rowHeights=(7*mm,20*mm,7*mm,20*mm))
    table.setStyle(TableStyle([
            ('FONT', (0, 0), (1, 3), 'HeiseiKakuGo-W5', 9),
            ('BOX', (0, 0), (1, 3), 1, colors.black),
            ('INNERGRID', (0, 0), (1, 3), 1, colors.black),
            ('VALIGN', (0, 0), (1, 2), 'MIDDLE'),
            ('VALIGN', (0, 1), (1, 1), 'TOP'),
            ('VALIGN', (0, 3), (1, 3), 'TOP'),
        ]))
    table.wrapOn(pdf_canvas, 20*mm, 178*mm)
    table.drawOn(pdf_canvas, 20*mm, 178*mm)

    # (6)学歴・職歴
    data = [
            ['        年', '   月', '                                            学歴・職歴'],
            [' ', ' ', ' '],
            [' ', ' ', ' '],
            [' ', ' ', ' '],
            [' ', ' ', ' '],
            [' ', ' ', ' '],
            [' ', ' ', ' '],
            [' ', ' ', ' '],
            [' ', ' ', ' '],
            [' ', ' ', ' '],
            [' ', ' ', ' '],
            [' ', ' ', ' '],
            [' ', ' ', ' '],
            [' ', ' ', ' '],
            [' ', ' ', ' '],
            [' ', ' ', ' '],
            [' ', ' ', ' '],
            [' ', ' ', ' '],
            [' ', ' ', ' '],
            [' ', ' ', ' '],
            [' ', ' ', ' ']
        ]
    table = Table(data, colWidths=(25*mm, 14*mm, 121*mm), rowHeights=7.5*mm)
    table.setStyle(TableStyle([
            ('FONT', (0, 0), (-1, -1), 'HeiseiKakuGo-W5', 11),
            ('BOX', (0, 0), (-1, -1), 1, colors.black),
            ('INNERGRID', (0, 0), (-1, -1), 1, colors.black),
            ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
        ]))
    table.wrapOn(pdf_canvas, 20*mm, 20*mm)
    table.drawOn(pdf_canvas, 20*mm, 20*mm)

    # 1枚目終了
    pdf_canvas.showPage()
```

<img width="603" alt="7~8.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/484145/5b32aea7-bd75-f0a9-2f18-1b73a2d47785.png">

```python
    # (7)学歴・職歴、免許・資格
    data = [ 
            ['        年', '   月', '                                            学歴・職歴'],
            [' ', ' ', ' '],
            [' ', ' ', ' '],
            [' ', ' ', ' '],
            [' ', ' ', ' '],
            [' ', ' ', ' '],
            [' ', ' ', ' '],
            [' ', ' ', ' '],
            [' ', ' ', ' '],
            [' ', ' ', ' '],
            ['        年', '   月', '                                            免許・資格'],
            [' ', ' ', ' '],
            [' ', ' ', ' '],
            [' ', ' ', ' '],
            [' ', ' ', ' '],
            [' ', ' ', ' '],
            [' ', ' ', ' '],
            [' ', ' ', ' '],
        ]
    table = Table(data, colWidths=(25*mm, 14*mm, 121*mm), rowHeights=7.5*mm)
    table.setStyle(TableStyle([
            ('FONT', (0, 0), (-1, -1), 'HeiseiKakuGo-W5', 11),
            ('BOX', (0, 0), (-1, -1), 1, colors.black),
            ('INNERGRID', (0, 0), (-1, -1), 1, colors.black),
            ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
        ]))
    table.wrapOn(pdf_canvas, 20*mm, 132*mm)
    table.drawOn(pdf_canvas, 20*mm, 132*mm)
   
    # (8)そのほか
    data = [
            ['志望の動機、自己PR、趣味、特技など','通勤時間',''],
            ['','                        約　　　　時間　　　　分',''],
            ['','扶養家族（配偶者を除く）',''],
            ['','                              　　　　    　　　　人',''],
            ['','配偶者','配偶者の扶養義務'],
            ['','       有    ・    無','       有    ・    無'],
            ['本人希望記入欄（特に待遇・職種・勤務時間・その他についての希望などがあれば記入）','',''],
            ['','','']

        ]
    table = Table(data, colWidths=(90*mm, 35*mm, 35*mm), rowHeights=(8*mm, 10*mm, 8*mm, 10*mm, 8*mm, 10*mm, 8*mm, 50*mm))
    table.setStyle(TableStyle([
            ('FONT', (0, 0), (2, 7), 'HeiseiKakuGo-W5', 10),
            ('BOX', (0, 0), (2, 7), 1, colors.black),
            ('LINEBEFORE', (1, 0), (1, 5), 1, colors.black),
            ('LINEBEFORE', (2, 4), (2, 5), 1, colors.black),
            ('LINEABOVE', (1, 2), (2, 2), 1, colors.black),
            ('LINEABOVE', (1, 4), (2, 4), 1, colors.black),
            ('LINEABOVE', (0, 6), (2, 6), 1, colors.black),
            ('LINEABOVE', (0, 7), (2, 7), 1, colors.black),
            ('VALIGN', (0, 0), (2, 5), 'TOP'),
            ('VALIGN', (0, 6), (2, 6), 'MIDDLE'),
        ]))
    table.wrapOn(pdf_canvas, 20*mm, 20*mm)
    table.drawOn(pdf_canvas, 20*mm, 20*mm)

    # 2枚目終了
    pdf_canvas.showPage()


# 作成
if __name__ == '__main__':
    make()
```

##6. 実行
```
python resume.py
```
resume.pyを配置しているディレクトリにresume.pdfが作成されます。

##7. まとめ
Pythonのreportlabで履歴書フォーマットPDFを作成しました。
今回は、複数のテーブルを用いて作成しましたが、セルをうまく結合できれば、1枚につき1つのテーブルでうまく作成することもできると思います。

##参考
- [PythonでPDFを生成したい そしてサイコロを作りたい](http://o-tomox.hatenablog.com/entry/2013/07/22/221158)