2021年12月20日月曜日

LaTeXで宛名ラベルシールの差し込み印刷をする

この記事は TeX & LaTeX Advent Calendar 2021 の20日目の記事です。

19日目は hid_alma1026 さん、21日目は 7danmoroboshi さんです。

 

はじめに

業務でラベルシール用宛名ラベルの作成にラベル屋さんというソフトを利用しています。しかし公式サイトの使い方を見る限り、連名の有り無しなどの条件分岐に対応したラベル作りに対応できなさそうです。そこでLaTeXのtikzとtcolorbox, datatool, intcalcパッケージなどを利用して宛名ラベルの差し込み印刷に挑戦してみました。

TeXエンジンは、フォントの変更がしやすいという噂のLuaTeX-jaを利用することにしました。

datatoolはデータセットからdata plotや表作成などが行えるLaTeXパッケージです。日本語では

で使い方を見ることができます。csvなどの外部データを利用することもできて、今回は作成したcsvファイルを読み込み、定型フォーマットに文字列を流し込むのに利用しました。


準備

まずは宛名ラベルの差し込み印刷に利用するcsvファイルを用意します。今回は

を利用して、次の画像ようなcsvファイル(personal_information.csv)を作成しました。


csvファイルのヘッダ部分は取り除いています。ヘッダ部分がある場合もdatatoolの記述を変えれば利用できるようです。各列の意味は次の通りです。括弧内はdatatoolで扱う際のkeyを表しています。
  1. 名前(Name)
  2. 郵便番号(PostalCode)
  3. 住所1(Address)
  4. 住所2(address)
  5. ご家族様表記の有無(ToFamily)
  6. 連名1(Family)
  7. 連名2(family)

出力結果

上のcsvファイルを元に作成した宛名ラベルのpdfファイルの画像を載せておきます。あくまでも画像の住所・氏名はダミーで実在しません。


1枚目の画像は印刷後に余ったラベルシールの再利用を想定して、ラベル開始位置を指定できるようにし、開始位置をずらしたものです。画像では4番目のラベル位置から宛名を配置しています。その他に連名を入れたいもの、ご家族様表記を入れたいものなどを判別してラベルが作成されるようにしています。画像では枠があるように見えますが、印刷には出てきません。

コードの紹介

上記画像の宛名ラベルを作成するためのLaTeXファイルの内容を紹介します。
ここではラベルを印刷するシールはA-Oneの75312番を想定しています。シート1枚に対して12面のラベルがあり、その寸法に合わせてnewlengthを定義しています。タイプセットは2回必要です。
\documentclass[lualatex,a4paper,ja=standard]{bxjsbook}

\usepackage[haranoaji]{luatexja-preset}
\usepackage[T1]{fontenc} %入力ファイル中でアクセント記号付きの欧文を扱う
\usepackage{lmodern}% Latin Modern フォントを使う

\usepackage{etoolbox}

\usepackage[math=pgfmath,utf8=true]{datatool}
\usepackage[utf8]{inputenc}
\usepackage{intcalc} %modの計算に使用

\usepackage{xparse}
\usepackage{tikz}
\usetikzlibrary{calc,math}
\usepackage{tcolorbox}


%新しい長さのコマンドを設定
\newlength{\myleftmargin}
\newlength{\myrightmargin}
\newlength{\mytopmargin}
\newlength{\mylabelwidth}
\newlength{\mylabelheight}
\newlength{\mymidspace}

\setlength{\myleftmargin}{19.3mm}
\setlength{\myrightmargin}{19.3mm}
\setlength{\mytopmargin}{21.5mm}
\setlength{\mylabelwidth}{83.8mm}
\setlength{\mylabelheight}{42.3mm}
\setlength{\mymidspace}{3.8mm}

%ページ番号を表示させない
\pagestyle{empty}

%yatex用の記述
%#! lualatex label.tex


%tikzpicture環境の定義
\tikzset{every picture/.style={%
remember picture, overlay,
execute at begin picture=%picture環境の最初に行う処理
{%tikzのmath libraryを使って,各labelの左上の端点位置を計算しておく.
\coordinate (P) at (current page.north west); %紙面左上の端点の座標
\tikzmath{%各ページにlabelが12枚なので12個の点の座標を配列として定義.
int \i,\j;
coordinate \p;
\p0 = (P)+(\myleftmargin, -\mytopmargin);%配列の番号は0から11まで.
\p1 = (\p0)+(\mylabelwidth + \mymidspace, 0);
 for \i in {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}{
 \j =\i+2;
 \p\j = (\p\i) + (0, -\mylabelheight);
 };%tikzmathの終わり
 }}}
}


%新しいtcolorbox環境の定義
\newtcolorbox[blend into=tables]{addresslabel}{%blend into=tablesで,tcolorbox環境内でtabular環境を使えるようにする.
colback=white,%box内の背景色
%colframe=white,%box枠の色
width =\mylabelwidth,%boxの幅
height=\mylabelheight,%boxの高さ
lower separated=false,%upperとlowerの境界線を消す
middle = 0pt,%境界線と文章の距離
space to lower,%box内の高さの空きスペースをすべてlowerに渡す
boxrule=0mm,%枠線の幅
valign lower=center,%lower部分の垂直方向の位置揃え
valign upper=top,%upper部分の垂直方向の位置揃え
halign lower=center,%lower部分の水平方向の位置揃え
halign upper=flush left,%upper部分の水平方向の位置揃え
fontlower=\sffamily\bfseries,%lower部分のフォント変更
}

%xparseを使ったコマンドの定義
\NewDocumentCommand\AddressLabel{m m m m m m o o}{
%#1:ラベル位置, #2:ご家族様表記の有無, #3:氏名, #4:郵便番号, #5:住所1, #6:住所2, #7:連名1, #8:連名2
\begin{tikzpicture}[every picture]
\node[inner sep=0pt,anchor=north west] at (\p{#1}){%ラベルを置く位置(ラベル左上端点の座標)
\begin{addresslabel}
%upperpart
\fontsize{10pt}{12pt}\selectfont%
〒{#4}
\par
\hspace{1\zw}{#5}
\par
\hspace{1\zw}{#6}
\tcblower
%lowerpart
\fontsize{15pt}{1.58\zh}\selectfont%
\begin{tabular}{r}%
{#3}~様
\IfNoValueTF{#7}{}{\\ {#7}~様}
\IfNoValueTF{#8}{}{\\ {#8}~様}
\ifnumequal{#2}{1}{\\ {ご家族}~様}{}
\end{tabular}
\end{addresslabel}
};%nodeの終わり
\end{tikzpicture}
}


\newcounter{posnum}%ラベルの位置を表すカウンターの定義
\newcounter{initposnum}%最初のラベル位置を表すカウンター
\newcounter{linenum}%アドレスデータの行番号
\setcounter{initposnum}{3}%最初のラベル位置(0,1,...,11で指定)
\begin{document}

%datatoolパッケージでcsvファイルを読み込む
%personal_information.csvはヘッダー無しで,第1列から順にName, PostalCode, etc.を表す.dataという名前にする.
%Name=代表者名, PostalCode=郵便番号, Address=住所1, address=住所2, ToFamily=ご家族様表記の有無, Family=連名1, family=連名2
\DTLloaddb[noheader,keys={Name,PostalCode,Address,address,ToFamily,Family,family}]{data}{personal_information.csv}

%上で読み込んだdataの各行に対して処理を行う.
\begin{DTLenvforeach}{data}{\Name=Name, \PostalCode=PostalCode, \Address=Address, \address=address, \ToFamily=ToFamily, \Family=Family, \family=family}
%\DTLcurrentindexがデータ行番号を表す.1ページあたりのラベル数が12なので,ラベル位置はこれを12で割った余りとして計算.
\setcounter{linenum}{\intcalcAdd{\DTLcurrentindex}{\theinitposnum -1}}
\setcounter{posnum}{\intcalcMod{\thelinenum}{12}}
%データ行番号が12を超えていたら,12の倍数ごとに改ページをする.
\ifnum\thelinenum>11\ifnum\theposnum = 0 \newpage\fi\fi 
\DTLifstringeq{\Family}{}{%連名の有無で条件分岐
 \AddressLabel{\theposnum}{\ToFamily}{\Name}{\PostalCode}{\Address}{\address}%
 }{%
 \DTLifstringeq{\family}{}{%
 \AddressLabel{\theposnum}{\ToFamily}{\Name}{\PostalCode}{\Address}{\address}[\Family]%
 }{%
 \AddressLabel{\theposnum}{\ToFamily}{\Name}{\PostalCode}{\Address}{\address}[\Family][\family]%
 }}
\end{DTLenvforeach}
\end{document}
ループ処理があるため実行速度は遅めです。tcolorboxの設定でcolframe=whiteとすれば、上の画像にあったboxの枠も消えます。

intcalcパッケージは、剰余計算で利用しています.\intcalcAdd, \intcalcModはこのパッケージで定義されています。

xparseパッケージとtcolorboxパッケージを利用してAddressLabelというコマンドを作りそれをdatatoolパッケージで定義されるDTLenvforeach環境内でループ処理させています。最初は、AddressLabelコマンドのオプションの引数のところを必須引数にして、引数が空文字であれば空文字を入れるようにetoolboxのifstremptyコマンドで記述しようとしたのですが、Familyやfamilyキーが空文字でもDTLenvforeach環境内での\Familyなどのコマンドは空文字と判断されなかったため、これらをオプション引数にして条件分岐で対応しました。トークンの展開順序などが関係しているのでしょうか。

最後に

今回作成したラベルはシンプルなものですが、tikzを使って色々装飾できそうです。
しかし作ってはみたものの、事務の方にLaTeXを使わせるわけにもいかないので実際には使わないかな。