図書館資料用の背ラベルをPythonとreportlabで印刷する

# 図書館の本の背表紙に貼ってあるあのラベルを印刷する
キハラのプリンタ用図書ラベル、3段組でA4のやつ（商品番号60663-※）にデータを印刷します。それだけ。
通販の商品ページはこれ
https://store.shopping.yahoo.co.jp/bookbuddy/c40-60663a.html
大抵の図書館用ラベルのメーカーは印刷用のExcelファイルを配布してたりするんだけど、そのファイルにデータを打ち込むのが結構な手間だったりするので、作ってみました。
キハラの他にも埼玉福祉会、日本ブッカー、日本パンチカード工業のA4ラベル用の設定も作ってあります。需要あるかしら。
## Python3とreportlabでPDFを作成する
ブツ切りで解説していきます。まずはライブラリのインポートから。

PDFを作成するライブラリ`reportlab`を使います。
標準ライブラリではないので、事前にpipでインストールする必要があります。
標準ライブラリの`tkinter`はちょっとした入力ダイアログを表示するため、
`sys`はキャンセルしたときにプログラムを終了するため、
`webbrowser`は出来上がったPDFのプレビューしてそのまま印刷するために使います。

```Python
from reportlab.pdfgen import canvas
from reportlab.lib import colors
from reportlab.lib.pagesizes import A4
from reportlab.lib.units import mm
from reportlab.platypus import Table
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
from reportlab.pdfbase.cidfonts import UnicodeCIDFont
import tkinter
import tkinter.simpledialog
import sys
import webbrowser
# 小さいウインドウを出なくするおまじない
root = tkinter.Tk()
root.withdraw()
```
まず最初にフォントとか用紙とかラベルのサイズとか枚数とかを設定します。
MS明朝とMSゴシックは自分が使うので設定しています。
ポイントとしては、印刷位置決めに使う高さと幅の数値は実測値となっています。
ピクセル単位でしか調整できないExcelのセルのサイズを勘で調整するのでは紙を何枚も無駄にしてしまいますが、定規で測って数値を入力して調整は余白設定の微調整1回で済むのは非常に助かる。

```Python
def labelprint(data):
    # 使用フォント登録
    pdfmetrics.registerFont(UnicodeCIDFont('HeiseiKakuGo-W5'))
    pdfmetrics.registerFont(UnicodeCIDFont('HeiseiMin-W3'))
    pdfmetrics.registerFont(TTFont('ＭＳ 明朝', 'C:\\Windows\\Fonts\\msmincho.ttc'))
    pdfmetrics.registerFont(TTFont('ＭＳ ゴシック', 'C:\\Windows\\Fonts\\msgothic.ttc'))
    # 印刷位置決めのため余白設定
    xmargin, ymargin = 18.0*mm, 20.5*mm
    # ラベル1枚ごとの高さと幅
    width, height = 29.02*mm, 30.94*mm
    # 用紙サイズと作成するPDFファイル名
    c = canvas.Canvas('labelprint.pdf', pagesize=A4)
    # 横方向ラベル枚数
    col = 6
    # 縦方向ラベル枚数
    row = 8
    # 1シートのラベル枚数
    count = row * col
```
使いかけのラベルシートを使う場合、先頭からn枚は印刷しないようにできるようにしました。
これは案外需要がありそうなので追加しました。
`tkinter`と`sys`の出番はここ。

```Python
    # 使用済みラベル枚数入力
    inputdata = tkinter.simpledialog.askstring('ラベル作成', '使用済みラベル枚数を入力してください',)
    if inputdata == None:
        # 入力がキャンセルされたとき終了する
        sys.exit()
    elif inputdata.isdecimal():
        # 入力された枚数分空白をリストの先頭に挿入する
        for i in range(int(inputdata)):
            data.insert(0, [[''], [''], [''], ['']])
```
ここからデータの書き込み。
1枚目から48枚目までは1シート目に書き込み、49から96枚目は2シート目、といった具合。

```Python
    # ここからPDF作成
    n = 0 
    # データ件数が1シートのラベル枚数で割り切れないとき
    # 余り部分の印刷用にページを1枚足す
    if len(data) % count > 0:
        n = 1
    for i in range(len(data) // count + n):
        # 1ページ毎に印刷の終了位置を設定しなおす
        if len(data) < (i + 1) * count:
            stop = len(data) - i * count
        else:
            stop = count
        for j in range(stop):
            # テーブルの描画位置を指定
            x = xmargin + width * (j % col)
            y = ymargin + height * (row - 1 - (j // col))
            # 書き込むデータを指定
            singledata = data[j + i * count]
            # 縦4段のテーブルを作成
            table = Table(singledata, colWidths=29.02*mm,
                                      rowHeights=(7.0*mm, 7.0*mm, 7.0*mm, 9.94*mm))
            # 書式を設定
            table.setStyle([('VALIGN', (0, 0), (0, 3), 'MIDDLE'),
                            # 3段目までの書式
                            ('ALIGN', (0, 0), (0, 2), 'CENTER'),
                            ('FONT', (0, 0), (0, 2), 'ＭＳ 明朝', 11),
                            # 4段目の書式
                            ('ALIGN', (0, 3), (0, 3), 'LEFT'),
                            ('FONT', (0, 3), (0, 3), 'ＭＳ ゴシック', 8),
                            # 枠線は描画しない
                            # ('INNERGRID', (0 ,0), (-1,-1), 0.25, colors.black),
                            # ('BOX', (0, 0), (-1, -1), 0.25, colors.black),
            ])
            # テーブルを書き込み
            table.wrapOn(c, x, y)
            table.drawOn(c, x, y)
        # PDF1ページを書き込み
        c.showPage()
    c.save()
    # ブラウザでプレビューする。良ければそのまま印刷
    webbrowser.open('labelprint.pdf')

``` 
最後にデータを用意して実行しましょ。
実際に使うときは`openpyxl`を使ってエクセルファイルからデータを読み込むとか、各自工夫しよう。

```Python
# とりあえず60枚ほど印刷してみよう
labeldata = [[['分類記号'], ['図書記号'], ['副本記号'], [str(i + 1) + '枚目']] for i in range(60)]
labelprint(labeldata)

# ラベル1枚はこんな感じのリスト
singledata = [['1段目'], ['2段目'], ['3段目'], ['枠外']]
# こいつをさらにリストに格納して印刷用のデータとなります。
```

仕上がりはこんな感じ
![sample.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/670742/a2b4de1b-b1bc-0ecd-0401-2c59eb98a5bd.jpeg)
## ちょっとした注意事項
出来上がったPDFを印刷する前に、自分が印刷に失敗したときの敗因をご紹介。プログラムとか全く関係ないやつ。
環境はWindows10でAcrobat Reader DCから印刷しています。
### 敗因1: プリンタの紙送りにブレがある
宛名シールのようなものは大体手差しのトレイから給紙すると思うけど、弊社事務所の共用プリンタ（ブラザーのモノクロレーザー）は手差しトレイから紙を吸い込むときに少し斜めになりやがる。
プリンタは手差し給紙の精度が良いものを使ってください。できれば縦方向に給紙する機種が良い。
### 敗因2: 印刷するとき「ページサイズ処理」なんて項目見てない
ここが「用紙サイズに合わせる」とか「特大ページを縮小」になっているとサイズ倍率が97％になる。なに勝手なことしてんの。「実際のサイズ」を選択しよう。
### 敗因3: プリンタのドライバを入れ忘れた
ページサイズ処理を「実際のサイズ」にしているのに、印刷してみたらやっぱり微妙に縮小されている。
なんでかなーと思ったらPCにプリンタのドライバがインストールされておらず、汎用ドライバで印刷していた。おそらくフチなし印刷に対応していないプリンタを考慮して少し縮小するのだろう。
フチなし印刷に対応したプリンタを使い、その機種のドライバをインストールしたら縮小されずに印刷できた。