P1380473 on Flickr

はじめに

年が明けてから年賀はがきを買いに走り,文面を3時間くらいで作り,宛名面作成の自動化に腐心していたissmです.

Macを始めて最初の年賀状なので,Mac絡みのネタでも.

「アドレスブック.app」には,(複数の)個人・会社の情報を「vCard」という形式で書き出すことができます.これを読み込んで解析し,それぞれの情報を郵便はがき向けにレイアウトしたPDFを出力する,そんなPerlスクリプトを書いてgithubにコミットしてみました

以下,そんなPerlスクリプトについてざっと紹介します.

準備

スクリプトを持ってくる

githubに置いてありますので,これを持ってきます.

必要なCPANモジュール

次のあたりのCPANモジュールが必要になるので,入れておきます.

生成までの流れ

vCardを書き出す

「アドレスブック.app」を起動し,印刷の対象となる個人・会社の情報を(複数)選択してCtrl+クリック,「vCardを書き出す…」メニューよりvCard形式で書きだします(次図).

ss-2010-01-06-01 on Flickr

スクリプトを実行する

持ってきたPerlスクリプトを実行します.第1引数に,先で出力したvCardファイルを,第2引数に,出力先のPDFファイルの名前を指定します.

% perl vcard2pdf.pl <読み込むvcfファイル> <出力するpdfファイル>

なお,第2引数を省略した場合,「addresses.pdf」として出力されます.

PDFができる

問題がなければ,個人・会社1つにつき1ページが連結されたPDFファイルが出力されます(次図).

ss-2010-01-06-06 on Flickr

印刷する

あとは,このPDFファイルを印刷するだけです.「フチなし」設定の方がよさげです.

生成パタンいろいろ

通常

郵便番号,住所,宛名(個人)といった,通常の組み合わせです(次図).

fig-2010-01-06-02 on Flickr

元となるアドレスブックの情報は次のとおりです(次図).

ss-2010-01-06-02 on Flickr

所属つき

宛名の前に,会社名とかの所属・肩書を追加した場合です(次図).

fig-2010-01-06-03 on Flickr

「URL」セクションと「IM」セクションとの間のセクション(なんて呼ぶんだろ?)に「肩書付加?」という名前の項目を追加し,値を「1」とすることで,PDF生成時に上のようにレイアウトされます.

ss-2010-01-06-03 on Flickr

連名つき

奥さんやお子さんの名前もいっしょに印刷したい場合です(次図).

fig-2010-01-06-04 on Flickr

「所属つき」と同じ要領で,「連名」という項目を追加し,値に名前を入力します.

ss-2010-01-06-04 on Flickr

会社とかへ宛てる

会社などへ「ウォンチュー御中」する場合です(次図).

fig-2010-01-06-05 on Flickr

上部の「会社」チェックボックスにチェックを入れればOKです.

ss-2010-01-06-05 on Flickr

位置調整

同スクリプトの冒頭にある$Aとか$Bとかの変数の値を変更することで,郵便番号や住所,宛名の印刷位置を調整することもできます.単位はmmです.

各変数が表す寸法は,それぞれ次に示すような対応となっています(次図).

fig-2010-01-06-01 on Flickr

初期設定は次のとおりです.

# 宛先郵便番号枠の位置
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」というツールがなんだかよさげのようですね.

おまけ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__

こちらもあわせてどうぞ

Leave a Reply

直近のつぶやきを読み込みちゅう...