うならぼ

申し訳程度のアフィリエイトとか広告とか解析とかは/aboutを参照

HTML+CSSで宛名印刷

あけましておめでとうございます。

近頃のCSSwriting-mode: vertical-rl で縦書きができます。皆さんご存知の通り mm での位置指定は余裕ですし、 page-break-after で改ページもできます。宛名印刷できますね。

というわけで今年の年賀状の宛名面のメイキングです。

なお下のQiita記事に触発され、土台部分を参考にしています。

qiita.com

レイアウト

基本となるCSSはこれだけです。ほとんど前掲のQiita記事の通りですが、調整した点はコメントに記載しました。

/* UAデフォルトのマージンを除去 */
html, body { margin: 0; padding: 0; }

@page {
        /* はがきサイズ。手元の環境では用紙サイズの自動選択は働かなかった。 */
    size: 100mm 148mm;
    margin: 0;
}

@media screen {
    body {
        background: #eee;
    }
    .page {
        background: white;
        box-shadow: 0 .5mm 2mm rgba(0,0,0,.3);
        margin: 5mm;
                /* 一覧しやすいようにfloatさせる */
        float: left;
    }
}

.page {
    width: 100mm;
    height: 147mm;
    page-break-after: always;
        /* ページ上の配置を position: absolute で行うため */
    position: relative;
}

あとは好きなものを、好きな位置に、好きなフォントで配置するだけ。

といっても何の参考もなしにやるのはつらいので、はがきの画像をスキャンなりしてページの背景にしておくと位置調整の参考になります。また、他のソフトでだいたいのレイアウトを決めてから、その画像や数値を使うのも手です。

その他メモ。

  • bottomで位置指定 + min-height + text-align: left/center で「普段は下に伸びるけど足りなければ上の余白も使う」的なレイアウト。
  • 縦中横は text-combine というCSSプロパティもあるが、愚直に一行分の幅に詰め込んでしまうのでつらい。実際にはちょっとはみ出すぐらいにしたかったので、結局 display: inline-block を使った。
  • line-height 、複数行のとこはどうせいじるので、一行の要素向けにデフォルト 1 でよさそう。

JavaScriptで流し込む

CSSでのレイアウトが終わったものの、住所録をCSVから取り込んだりすることを考えると、

  • 郵便番号の前半と後半を分けたい、とか
  • 住所の数値は別のスタイルを適用したいからタグで囲みたい、とか
  • 宛名を均等割り付けしたい、とか
  • そもそもテンプレートに流し込みたい、とか

色々と課題が残っています。せっかくHTMLなのですからJavaScriptで解決しましょう。

今回はあらかじめどうにかして*1 <page name="山田太郎" postal="1030027" addr1="東京都中央区..."></page> みたいな形に変形しておいて、そこから先をRiotでやることにしました。

タグ定義はJadeで書きました。

format-addr
    script.
        let text = opts.text;
        text = text.replace(/-/g, 'の');
        let wrap_numbers = content => {
            let elem = document.createElement("span");
            elem.className = "numbers";
            elem.textContent = content;
            return elem;
        };
        let re = /(\w+)|([^\w]+)/g;
        for (let m; m = re.exec(text);){
            if(m[1])
                this.root.appendChild(wrap_numbers(m[1]));
            else
                this.root.appendChild(document.createTextNode(m[2]));}
page
    section.page
        div.to-postal
            | {opts.postal.substr(0,3)}
            span.right: {opts.postal.substr(3,4)}
        div.to-addr
            format-addr(text='{opts.addr1}')
            div.rest: format-addr(text='{opts.addr2 || ""}')
        div.to-name
            | {opts.name} 様
    script.
        // justify
        this.on('updated', function() {
            var range = document.createRange();
            for (var elem of document.querySelectorAll('.to-name')) {
                range.selectNodeContents(elem);
                elem.style.letterSpacing = 0;
                elem.style.letterSpacing = (elem.clientHeight - range.getBoundingClientRect().height) / elem.innerText.length;
            };
        });

先ほどの課題については次のように解決しています。

  • 郵便番号の前半と後半を分ける→ String.prototype.substr()
  • 住所の数値をタグで囲む→ this.root 上に構築する別のカスタムタグを作成
  • 宛名を均等割り付け→ Rangeオブジェクトで取得した文字列の描画領域とボックスの高さを元に letter-spacing を調整

あとは body 内に上記で定義したpageタグを並べるだけ。

*1:Dapper+OLEDBでExcelから吸い出してタグ形式にするスクリプトをLINQPadで書いた