12
14

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

QRコード付きのカードを作成・印刷する

Last updated at Posted at 2015-03-23

紙のカードを使ったワークショップによる実験を行うにあたって、終了後の集計を効率化するため、QRコードをカードに仕込んでおく、ということをやってみました。

カードの印刷イメージ生成にはLaTeXなどの組版ソフトを使うのが一般的かと思いますが、もう少しお手軽に作りたいところです。
そこで、「カード印刷用のHTMLをスクリプトで生成する」という技を紹介します。スクリプト内のHTMLタグをいじることで、自由にカードデザインをカスタマイズできます。

スクリプト一式はGistに上げていますので、ご自由にご利用ください。

ポイント

  • PythonスクリプトでTSV形式で用意したデータを読み込み、HTMLファイルを生成
    • <hr>タグにCSSで "page-break-after: always;" を設定することで、印刷時に改ページさせる
  • QRコードの生成はPython qrcodeモジュールを利用
  • HTMLファイルからPDFファイルへの変換にはwkhtmltopdfを利用
  • カードはA6サイズで作成し、A4に印刷して裁断機などで四つ切り

動作環境

  • Python 2.7系列
  • wkhtmltopdf
  • qrcode 5.1
  • Pillow 2.7.0
    • カード中に画像ファイルを埋め込みたい場合、画像ファイルサイズを取得するために利用
  • Mac OS X環境で試していますが、たぶんWindows、UNIX環境でも動きます

環境のセットアップ

wkhtmltopdf

WebサイトのダウンロードページからStable版を入手してインストールします。

Pythonモジュール

pipで、qrcode, pillowをインストールしてください。

$ pip install qrcode
$ pip install pillow

サンプルスプリクトを動かしてみる

Gistから、以下のファイルをダウンロードしてください。

  • make_html.py: スクリプト本体
  • card_data.txt: サンプルデータ (Wikipedia日本語版の鉄道駅エントリと画像)
  • Makefile: GNU makeによるコマンド自動実行

スクリプトの実行

HTMLファイルとQRコード画像を、サンプルデータから生成します。

$ python make_html.py --qrcode-dir ./qrcode card_data.txt card_printout.html

生成された card_printout.html をブラウザで開いて、カードデータが作成されていることを確認してみてください。

QRコード付きカード印刷データ.png

wkhtmltopdfコマンドで、HTMLファイルから印刷に適したPDFファイルを生成します。

wkhtmltopdf -s A6 card_printout.html card_printout.pdf

wkhtmltopdf のオプションリストはこちらにあります。

カードを印刷する

生成されたPDFファイルは、A4に2×2で印刷するとちょうどいいサイズになっています。

Acrobat Readerの印刷オプションで、「1枚あたりのページ数」を「4」に設定して印刷します。
acroread_printout.png

印刷されたカードを裁断機などで4ツ切りにすれば完成です。
printed_cards.jpg

サンプルスクリプトの解説

スクリプト make_html.py をカスタマイズされる方の参考のために、ちょっとした説明を書いておきます。

QRコードの生成と埋め込み

        # QRコード生成 (page_urlをQRコード化)
        qrcode.make(page_url).save('./qrcode/%08d.png' % (num))

page_url (サンプルデータではWikipediaページのURL) をQRコード化して、カード番号(num)に対応したファイル名の画像ファイル(PNG形式)を書き出しています。

HTMLファイルの生成の際に、この画像ファイルを埋め込むためのHTMLタグを書き出しています。

<html lang="ja">
<head>
<meta charset="utf-8">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>QRコード付きカード印刷データ</title>
<style type="text/css">

/* QRコードは右下に配置 */
div.qrcode {
  text-align: right;
}

</style>
</head>

<body>

...

<div class="qrcode"><img src="./qrcode/00000001.png" width="50" /></div>

...

</body>
</html>

画像ファイルのサイズ調整


import urllib, cStringIO
import PIL.Image as Image

# 画像の大きさの制限値
IMAGE_WIDTH_LIMIT = 300
IMAGE_HEIGHT_LIMIT = 180

class CardHtmlMaker(object):

    def _get_image_size_tag(self, image_url):
        """
        Web上の画像サイズをHTTPで取得し、制限サイズを計算
        @param image_url 画像のURL
        @return imgタグのサイズ指定文字列
        """
        try:
            res = urllib.urlopen(image_url)
        except IOError, e:
            raise Exception(u'画像ファイルの取得中にエラーが発生しました: %s: %s' % (image_url, e))
        
        if res.code != 200:
            raise Exception(u'画像ファイルが取得できません: %s: status=%d' % (image_url, res.code))

        fp = cStringIO.StringIO(res.read())
        image = Image.open(fp)
        (width, height) = image.size

        if (float(width) / height) > (float(IMAGE_WIDTH_LIMIT) / IMAGE_HEIGHT_LIMIT):
            # 制限サイズより横長 -> 幅でサイズ指定
            return 'width="%d"' % IMAGE_WIDTH_LIMIT
        else:
            # 制限サイズより縦長 -> 高さでサイズ指定
            return 'height="%d"' % IMAGE_HEIGHT_LIMIT

サンプルデータでは、Web上の画像ファイルを指定してページ中に埋め込むということをやっています。この際、画像サイズによってカードからはみ出ないよう、画像サイズの縦横比に応じて<img>タグの「width=」または「height=」属性でサイズ指定しています。
下の例では、制限サイズの横縦比(300÷180=1.66..)よりも画像ファイルが縦長(800÷536=1.49...)であるため、heightタグでサイズ指定しています。

<div class="image"><img src="http://upload.wikimedia.org/wikipedia/commons/thumb/9/95/Tokyo_station_from_marunouchi_oazo.JPG/800px-Tokyo_station_from_marunouchi_oazo.JPG" height="180" /></div>

カードの書き出し順のコントロール

    def output(self, input_data, output_html):
        """
        TSVファイルで入力されたデータから、HTMLファイルを生成する
        @param input_data  入力ファイル(TSV)のファイルハンドル
        @param output_html 出力ファイル(HTML)のファイルハンドル
        """

        output_html.write(HTML_HEADER.encode('utf-8'))

        lines = input_data.readlines()
        input_data.close()

        num_cards = len(lines)
        num_pages = (num_cards - 1) / 4 + 1
        for page in range(num_pages):
            # 1枚目(左上)
            card_id = page
            output_html.write(self._make_card(card_id+1, lines[card_id]).encode('utf-8'))

            # 2枚目(左下)
            card_id = page + num_pages * 1
            if card_id > num_cards - 1:
                output_html.write('<hr>\n\n')
            else:
                output_html.write(self._make_card(card_id+1, lines[card_id]).encode('utf-8'))

            # 3枚目(右上)
            card_id = page + num_pages * 2
            if card_id > num_cards - 1:
                output_html.write('<hr>\n\n')
            else:
                output_html.write(self._make_card(card_id+1, lines[card_id]).encode('utf-8'))

            # 4枚目(右下)
            card_id = page + num_pages * 3
            if card_id > num_cards - 1:
                if page == num_pages - 1:
                    pass
                else:
                    output_html.write('<hr>\n\n')
            else:
                if page == num_pages - 1:
                    output_html.write(self._make_card(card_id+1, lines[card_id],
                                                      page_break=False).encode('utf-8'))
                else:
                    output_html.write(self._make_card(card_id+1, lines[card_id]).encode('utf-8'))

        output_html.write(HTML_FOOTER.encode('utf-8'))

        output_html.close()

サンプルデータのTSVファイルの1行目から順にHTMLタグを書き出してしまうと、4ツ切りで裁断したときに、手作業で並べ替えが必要となってしまいます。
そこで、裁断後に裁断された束を重ねるだけでデータファイルの順になるように、ページ数(page_num)分だけループを回して書き出しています。

12
14
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
14

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?