はじめに
年が明けてから年賀はがきを買いに走り,文面を3時間くらいで作り,宛名面作成の自動化に腐心していたissmです.
Macを始めて最初の年賀状なので,Mac絡みのネタでも.
「アドレスブック.app」には,(複数の)個人・会社の情報を「vCard」という形式で書き出すことができます.これを読み込んで解析し,それぞれの情報を郵便はがき向けにレイアウトしたPDFを出力する,そんなPerlスクリプトを書いてgithubにコミットしてみました.
以下,そんなPerlスクリプトについてざっと紹介します.
準備
スクリプトを持ってくる
githubに置いてありますので,これを持ってきます.
必要なCPANモジュール
次のあたりのCPANモジュールが必要になるので,入れておきます.
- Text::vCard::Addressbook (in Text::vCard)
- PDF::API2
- Image::Magick
- Image::Size(PDF::API2をラップする自作モジュールもどきが要求)
生成までの流れ
vCardを書き出す
「アドレスブック.app」を起動し,印刷の対象となる個人・会社の情報を(複数)選択してCtrl+クリック,「vCardを書き出す…」メニューよりvCard形式で書きだします(次図).
スクリプトを実行する
持ってきたPerlスクリプトを実行します.第1引数に,先で出力したvCardファイルを,第2引数に,出力先のPDFファイルの名前を指定します.
% perl vcard2pdf.pl <読み込むvcfファイル> <出力するpdfファイル>
なお,第2引数を省略した場合,「addresses.pdf」として出力されます.
PDFができる
問題がなければ,個人・会社1つにつき1ページが連結されたPDFファイルが出力されます(次図).
印刷する
あとは,このPDFファイルを印刷するだけです.「フチなし」設定の方がよさげです.
生成パタンいろいろ
通常
郵便番号,住所,宛名(個人)といった,通常の組み合わせです(次図).
元となるアドレスブックの情報は次のとおりです(次図).
所属つき
宛名の前に,会社名とかの所属・肩書を追加した場合です(次図).
「URL」セクションと「IM」セクションとの間のセクション(なんて呼ぶんだろ?)に「肩書付加?」という名前の項目を追加し,値を「1」とすることで,PDF生成時に上のようにレイアウトされます.
連名つき
奥さんやお子さんの名前もいっしょに印刷したい場合です(次図).
「所属つき」と同じ要領で,「連名」という項目を追加し,値に名前を入力します.
会社とかへ宛てる
会社などへ「ウォンチュー御中」する場合です(次図).
上部の「会社」チェックボックスにチェックを入れればOKです.
位置調整
同スクリプトの冒頭にある$Aとか$Bとかの変数の値を変更することで,郵便番号や住所,宛名の印刷位置を調整することもできます.単位はmmです.
各変数が表す寸法は,それぞれ次に示すような対応となっています(次図).
初期設定は次のとおりです.
# 宛先郵便番号枠の位置 my $A = 8; my $B = 6; my $C = 12; my $D = [44, 51, 58, 65.5, 72.5, 79.5, 86.5]; # 左から順に # 住所の位置 my $E = 20; my $F = 60; # 宛名の位置 my $G = 20; my $H = 75; # 差出人郵便番号枠の位置(今のところ未使用) my $I = 6; my $J = 3.8; my $K = 122.5; my $L = [5.5, 9.5, 13.5, 18.5, 22.5, 26.5, 30.5]; # 左から順に
おわりに
以上,Macの「アドレスブック.app」から書き出したvCardを基に,郵便はがきの宛名面を印刷するためのPDFを生成するPerlスクリプトについて簡単に紹介しました.
横書きしか対応していなかったり,レイアウトの融通は利かなかったりと,使い途はほとんどなさげですが,宛名印刷ライフが少しでも快適になれば幸いです.
おまけ
「葉書AB」というツールがなんだかよさげのようですね.
- Yamamoto Software / 葉書AB
- Mac の宛名印刷ソフト「葉書AB」のシンプルさ加減に感動 – 頭ん中
- アドレスブックを使って年賀状の宛名印刷する – [Mac OSの使い方] All About
おまけ2:ソースコード
同スクリプトのソースコードを載せておきます.
中でPDF::API2::Wrapperという自作モジュールもどきを呼び,これを使ってPDFを生成していますが,この辺の詳細は本エントリでは省略します.ソースコードはgithubの該当ファイルを参照ください.(このモジュールもどき自体は1年ほど前に書いたものなので,ちょっとアレですが...)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 | #!/usr/bin/perl use strict; use warnings; use utf8; use FindBin; use lib "$FindBin::Bin/lib"; use Text::vCard::Addressbook; use PDF::API2::Wrapper; use Encode; use Data::Dumper; #### 位置設定 ここから #### # 宛先郵便番号枠の位置 my $A = 8; my $B = 6; my $C = 12; my $D = [44, 51, 58, 65.5, 72.5, 79.5, 86.5]; # 左から順に # 住所の位置 my $E = 20; my $F = 60; # 宛名の位置 my $G = 20; my $H = 75; # 差出人郵便番号枠の位置(今のところ未使用) my $I = 6; my $J = 3.8; my $K = 122.5; my $L = [5.5, 9.5, 13.5, 18.5, 22.5, 26.5, 30.5]; # 左から順に #### 位置設定 ここまで #### sub usage { print << "..."; Usage: vcard2pdf.pl <source_file> ... exit; } sub d { Dumper @_; } sub de { decode('utf-8', $_[0]); } sub en { encode('utf-8', $_[0]); } sub data_from_vcard { my $v = shift; my $data = {}; # 名前 my $name = $v->get('N')->[0]; if ($name->{family}) { $data->{name1} = de $name->family; $data->{name2} = de $name->given; } # 所属 my $org = $v->get('ORG'); if (defined $org) { $data->{org} = de $org->[0]->name; } # 住所 my $address = $v->get('ADR')->[0] || ''; $data->{post_code} = de($address->post_code || ''); my $address_tmp = de $address->region . $address->city . $address->street; $data->{address} = [split /\\n/, $address_tmp]; sub get_item_value { my ($v, $name) = @_; my $items = [ map { { name => $v->get_group($_, 'X-ABLabel')->[0], value => $v->get_group($_, 'X-ABRELATEDNAMES')->[0], } } map { 'item' . $_; } 1 .. 10 ]; [ map { de $_->{value}->value; } grep { my $n = $_->{name}; defined $n && de($n->value) eq $name; } @$items ]; } # 連名 (arrayref) $data->{joint} = get_item_value($v, '連名'); $data->{joint} = join ' ', @{$data->{joint}}; $data->{joint} = [split /\s+/, $data->{joint}]; # 肩書付加? (1 or 0) $data->{with_position} = int(get_item_value($v, '肩書付加?')->[0] || 0); $data; } sub pdf_from_data { my $data = shift; my $pdf_file = shift; my $pdf = PDF::API2::Wrapper->new({ measure => 'mm', dpi => 300, width => 100, height => 148, ttfont => "$FindBin::Bin/font/ipag.otf", fontsize => '10pt', strokecolor => '#000000', fillcolor => '#000000', })->init; for my $dest (@$data) { $pdf->page if $dest != $data->[0]; # 郵便番号 (my $post_code = $dest->{post_code}) =~ s/-//g; $post_code = [grep /[0-9a-zA-Z0-9]/, split '', $post_code]; for (my $i = 0; $i < 7; $i++) { $post_code->[$i] =~ tr/0-9a-zA-Z/0-9a-zA-Z/; # 半角 → 全角 $pdf->text( x => $D->[$i], y => $C, text => $post_code->[$i], size => ($B - 1), ); } # 住所 my $address = join "\n", @{$dest->{address}}; $address =~ tr/0-9a-zA-Z/0-9a-zA-Z/; # 半角 → 全角 $pdf->text( x => $E, y => $F, text => $address, line_height => '150%', ); # 宛名 my %opts_name = ( x => $G, y => $H, size => '12pt', line_height => '133%', ); if (defined $dest->{name1}) { # 組織名もある if (defined $dest->{org} && $dest->{with_position}) { $pdf->text( %opts_name, text => $dest->{org}, size => '10pt', ); $opts_name{y} += 6; } # 個人名・連名 my $title = ' 様'; my $len_name1 = length $dest->{name1}; # 姓の文字数 my $spc_name1 = ' ' x $len_name1; # 姓の文字数分だけ全角スペース my @name_out = ( $dest->{name1} . ' ' . $dest->{name2} . $title ); for (@{$dest->{joint}}) { push( @name_out, $spc_name1 . ' ' . $_ . $title ); } $pdf->text( %opts_name, text => join("\n", @name_out), ); } elsif (defined $dest->{org}) { # 組織名 $pdf->text( %opts_name, text => $dest->{org} . ' 御中', size => '11pt', ); } } $pdf->save(file => $pdf_file); 1; } sub main { my $source_file = $ARGV[0]; my $pdf_file = $ARGV[1] || "$FindBin::Bin/addresses.pdf"; usage unless defined $source_file; my $ab = Text::vCard::Addressbook->new({ source_file => $source_file, }); my $data = []; foreach my $v ($ab->vcards) { push( @$data, data_from_vcard($v), ); } pdf_from_data $data, $pdf_file; } main; __END__ |












