【Mac】シェルスクリプトで宛名印刷

Mac における年賀状の宛名印刷について、かつて記事を書いた。

🐰 【未確認】Mac の年賀状宛名問題、LibreOffice を使うのはどうか | 林檎コンピュータ

この記事で触れている通り、有料のアプリケーションを使えば別だが、Mac で宛名書きを印刷するいい方法があまりない。
 この問題をさまざま考えて、そうか、svg を書けばいいんだと気づいた。svg を pdf にすれば変換してやれば、印刷できるようになるはず。そういうスクリプトを作ってみたい。

svg で試す

ということで、svg で試してみたが、うまくいかなかった。何枚かに一枚、svg から pdf に変換する時に失敗してしまうのである。原因がわからないので、もう svg は捨てる。考え方をあらためて html で書くことにする。
 なぜ html かといえば、いまや html でも css で縦書き余裕だし、position: absolute; で絶対値を決めてやれば細やかな配置も出来る。あと、svg での縦書きは、長音(ー)とかダッシュが横向きになっちゃうという不備があった。

長音が横向きのままの事例
長音が横向きになる

これを回避する、svg2pdf.bash という素晴らしいスクリプトを発見したりもした。

🔗 SVGからPDFへの変換はHeadless Chromeでやろう(と思ったけどやっぱりrsvg-convertでやろう) – Qiita

Google Chrome を用いて svg を pdf にしようというスクリプトだ。マジでよくこんなの思いつくな、と思う。こちらを利用させていただいたところ、長音問題は解決できた。しかしたびたび pdf 化に失敗するのは変わらない(どうも筆者の svg の書き方がまずかった感触はある)。
 どうせ Google Chrome を利用するなら、いっそ html で書くほうがいいだろう、と思ったわけだ。

ひな形の html の作成

ひな形となる html は以下のようにした。
 表示位置については各自で微調整していただければと思う。
 あと、差出人住所等は、弊社においては表面に書くと決まっているので、ここでは設定していない。

<html>
  <head>
      <meta charset="UTF-8">
    <style>
    body {
      margin: 0;
      width: 283px;
      height: 419px
    }
    @page {margin: 0; size: 283px 419px;}
    .zipcode1 {position: absolute; top: 38px; left: 126px; letter-spacing: 12px; font-size: 12px; font-family: sans-serif;}
    .zipcode2 {position: absolute; top: 38px; left: 188px; letter-spacing: 12px; font-size: 12px; font-family: sans-serif;}
    .address1   {position: absolute; top: 80px; left: 247px; font-size: 12px; writing-mode: vertical-rl; font-family: serif;}
    .address2   {position: absolute; top: 85px; left: 232px; font-size: 12px; writing-mode: vertical-rl; font-family: serif;}
    .kaisya {position: absolute; top: 90px; left: 182px; font-size: 10px; writing-mode: vertical-rl; font-family: serif;}
    .yaku   {position: absolute; top: 90px; left: 170px; font-size: 8px; writing-mode: vertical-rl; font-family: serif;}
    .name   {position: absolute; top: 90px; left: 130px; font-size: 24px; writing-mode: vertical-rl; font-family: serif;}
    </style>
  </head>
  <body>
      <div class="zipcode1">000</div>
      <div class="zipcode2">0000</div>
      <div class="address1">群馬県○○郡○○○町大字○○一二三四</div>
      <div class="kaisya">株式会社 馬喰土建</div>
      <div class="yaku">平社員</div>
      <div class="name">馬喰 太郎 様</div>
  </body>
</html>

position: absolute; と、続く top、left の値で表示位置を決定している。
 letter-spacing は文字間のスペースの数値だ。
 writing-mode: vertical-rl; は縦書きの指定。
 font-size はフォントの大きさ。
 font-family はフォントの種類。ここにフォント名を指定してもいい。

@page は印刷用の css で、ここに書類の大きさを指定しておく。官製ハガキは横 100 mm、縦 148 mm となっていて、これを 72 dpi という解像度でピクセルにすると、だいたい 283px、419px ぐらいになる。

宛名は csv に書いて、それをシェルスクリプトで拾っていくのがいいだろう。

csv ファイルを作る。

正確な定義はわからないものの、csv ファイルは項目をカンマで区切って列を作り、改行で行にする形態のファイルだと思う。Numbers.app で編集できるし、テキストエディタで開いてもいい。Numbers.app で編集した場合は、文字コードを utf-8 に、改行コードは lf にするのが無難だと思う。

csv ファイルの作成

これを csv で書き出す。
 各行の最後の「ok」というのは、印刷するかしないかを決めておくために置いておいた。ここを「ng」にすると印刷せずに飛ばす、という仕掛けにしたい。

姓,名,敬称,郵便番号,住所1,住所2,会社,役職,印刷
馬喰,太郎,様,1111111,京都府京都市東山区南西海子町912-9,,,,ok
小鳥物産株式会社,,御中,1111111,山口県防府市華園町779-18,,,,ok
狐窪,陽一,様,2222222,京都府京都市左京区北白川,山田町528-3,狐企画,代表取締役,ok
猫田,浩之,様,3333333,大分県国東市国見町伊美42-13,,株式会社 猫田工務店,,ok
牛宮,太,様,3333333,北海道岩内郡共和町前田198-10,,,,ok
狆,俊介,様,1111111,青森県上北郡東北町蓼内久保923-6,,株式会社パピー,ディレクター,ok
土竜,悠,様,1111111,北海道標津郡中標津町青葉台39-19,,,中標津町議,ok

住所のランダム生成は下記のサイトさまを利用させていただきました。

🔗 住所データのランダム作成

シェルスクリプト

出来上がったスクリプトは以下のようになった。

📝 html_post.sh

#!/bin/zsh
# html_post.sh /path/address.csv

# 作業場の作成
dir_name=$(date +"%Y")年賀状
if test -d ~/Desktop/$dir_name; then
    cd ~/Desktop/$dir_name
else
    mkdir ~/Desktop/$dir_name
    cd ~/Desktop/$dir_name
fi

while read line; do
    SEI=$(echo $line | awk -F'[,]' '{print $1}')
    MEI=$(echo $line | awk -F'[,]' '{print $2}')
    f_name=$(echo $SEI$MEI)

    # 一行目を飛ばす
    if test $f_name = '姓名'; then
        continue
    fi

    KEI=$(echo $line | awk -F'[,]' '{print $3}')
    ZIP_CODE=$(echo $line | awk -F'[,]' '{print $4}')
    ADR_1=$(echo $line | awk -F'[,]' '{print $5}')
    ADR_2=$(echo $line | awk -F'[,]' '{print $6}')
    KAI=$(echo $line | awk -F'[,]' '{print $7}')
    YAK=$(echo $line | awk -F'[,]' '{print $8}')
    NG=$(echo $line | awk -F'[,]' '{print $9}')

    # NG を飛ばす
    if test $NG = 'ng'; then
        continue
    fi

    # 名前
    if test -n "$SEI"; then
        sei_mei=$(echo "$SEI"' '"$KEI")
        if test -n "$MEI"; then
            sei_mei=$(echo "$SEI"' '"$MEI"' '"$KEI")
        fi
    else
        sei_mei=$(echo "$MEI"' '"$KEI")
    fi

    # 名前の文字数によってフォントサイズを変える
    f_size=24
    w_count=$(echo ${#sei_mei})
    if test $w_count -ge 8 -a $w_count -lt 10; then
        f_size=20
    elif test $w_count -ge 10 -a $w_count -lt 13; then
        f_size=18
    elif test $w_count -ge 13 -a $w_count -lt 15; then
        f_size=16
    elif test $w_count -ge 15 -a $w_count -lt 17; then
        f_size=14
    elif test $w_count -ge 17; then
        f_size=11
    fi
    echo $sei_mei $f_size

    # ヘッダ作成
    echo '<html>
  <head>
      <meta charset="UTF-8">
    <style>
    body {
      margin: 0;
      width: 283px;
      height: 419px
    }
    @page {margin: 0; size: 283px 419px;}
    .zipcode1 {position: absolute; top: 38px; left: 126px; letter-spacing: 12px; font-size: 12px; font-family: sans-serif;}
    .zipcode2 {position: absolute; top: 38px; left: 188px; letter-spacing: 12px; font-size: 12px; font-family: sans-serif;}
    .address1   {position: absolute; top: 80px; left: 247px; font-size: 12px; writing-mode: vertical-rl; font-family: serif;}
    .address2   {position: absolute; top: 85px; left: 232px; font-size: 12px; writing-mode: vertical-rl; font-family: serif;}
    .kaisya {position: absolute; top: 90px; left: 182px; font-size: 10px; writing-mode: vertical-rl; font-family: serif;}
    .yaku   {position: absolute; top: 90px; left: 170px; font-size: 8px; writing-mode: vertical-rl; font-family: serif;}
    .name   {position: absolute; top: 90px; left: 130px; font-size: '$f_size'px; writing-mode: vertical-rl; font-family: serif;}
    </style>
  </head>
  <body>' >> "$f_name".html

    # 郵便番号、住所
    zip_code1=$(echo $ZIP_CODE | sed -e 's/\(...\).*/\1/')
    zip_code2=$(echo $ZIP_CODE | sed -e 's/...\(....\)/\1/')
    echo '<div class="zipcode1">'$zip_code1'</div>
      <div class="zipcode2">'$zip_code2'</div>
      <div class="address1">'"$ADR_1"'</div>' >> "$f_name".html

      if test -n "$ADR_2"; then
          echo '<div class="address2">'"$ADR_2"'</div>' >> "$f_name".html
      fi

      # 会社、役職
      if test -n "$KAI"; then
          echo '<div class="kaisya">'"$KAI"'</div>' >> "$f_name".html
      fi
      if test -n "$YAK"; then
          echo '<div class="yaku">'"$YAK"'</div>' >> "$f_name".html
      fi

      # 氏名
      echo '<div class="name">'"$sei_mei"'</div>
  </body>
</html>' >> "$f_name".html

    # pdf 化
    /Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --headless --disable-gpu --print-to-pdf="$f_name".pdf "$f_name".html

done << FILE
$(cat "$1")
FILE

# pdf 結合
gs -q -dNOPAUSE -dBATCH -sDEVICE=pdfwrite -sOutputFile=output.pdf *.pdf

先述した通り、Google Chrome が必須である。
 また、pdf ファイルの結合に ghostscript を利用している。ghostscript は、Homebrew で ffmpeg だったか、imagemagick をインストールすると、よく一緒にインストールされるやつだ。入ってない場合は Homebrew でインストールできる。

> brew search ghostscript
==> Formulae
ghostscript

上記スクリプトの使い方は、 html_post.sh ~/Desktop/アドレス.csv という感じ。スクリプト中に作業場フォルダへ移動するので、csv ファイルにはきちんとパスを書く必要がある。
 試してみると、やたらめったら書類を吐き出しちゃって、いかがな物かと思うが、最終的に出来上がる output.pdf はこんな感じ。

完成した pdf の様子

何百枚もの年賀状を刷らなきゃならない、という場合は実用に足らないかもしれないが、ちょっとした規模ならこんな感じでいいんじゃねぇかな、と思う。