「顧客ID・請求コード・受験番号といった番号や記号をQRコードにエンコードして用紙に印刷しておき,相手ごとに個別化された用紙を配りたい」という状況はよく生じると思います。 それをLaTeXで実現する場合の一例を示します。
また,LaTeX初心者の人にとっては,QRコードは無関係であったとしても「Wordでいう差し込み印刷のようなことをLaTeXでどうやって実現するか」のサンプルにもなるでしょう。
なお,このサンプルに掲載の個人情報は,ダミー個人情報生成サイト なんちゃって個人情報 を活用してランダム生成しました。サンプルデータ作成にとても便利なサイトです。
方法1:qrcodeパッケージでその場で生成する
TeX Live には,qrcodeパッケージ が収録されています。これは,(ASCII文字限定ではありますが)引数に与えられた文字列を
\qrcode{https://ctan.org}
のようにしてQRコードに変換できる便利なパッケージです。これで生成されるQRコードは画像ではなく,なんと TeX の \rule によって描かれた黒正方形の集合体となっています。
(qrcode.sty のソースを見ると,これをよくTeXで実装したなぁと感心させられます……!)
なお,TeX Forum のQ&A にあったように,\lineskiplimit や \normallineskiplimit を 0 に設定しておかないと表示が崩れる場合がありますので,\qrcode を使う際には常に
\setlength\lineskiplimit{0pt} \setlength\normallineskiplimit{0pt}
としておくのが無難です。
以下では,
- LaTeXで顧客IDをキーにした差し込み印刷を実現し,
- その顧客IDをQRコードに変換したものを各ページに埋め込む
という方法のサンプル (upLaTeX + dvipdfmx) を示します。
方法2:事前にqrcodeパッケージでQRコードだけを束ねたPDFファイルを作成しておく
用紙の枚数が1000枚といった大量のページ数に及ぶ場合は,コンパイルのたびにQRコードへのエンコード処理を毎回行っていると負荷が大きいので,事前にQRコードだけを束ねたPDFを生成しておき,それを用紙の側から \includegraphics[page=...] でページ指定で呼び出すのがよいでしょう。
以下では,
- カウンタを回しながら
\qrcodeでQRコードを順次生成し, previewパッケージでQRコード部分だけを切り出す
という方法で連番のQRコードを大量生成して束ねたPDFを生成するサンプル (pdfLaTeX) を示します。
1ページ目は 0001,……,100ページ目は 0100 という文字列をエンコードしたQRコードとなっています。
方法3:事前にQRコードのPNG画像を大量生成しておく
世間的にはこれが最も普通の方法でしょう。事前にQRコードの画像を用意しておき,それを \includegraphics で読み込むという方法です。
大量のQRコードを一括生成するにはどうすればよいでしょうか。検索してみたところ,コマンドラインで使えるQRコード生成ツールがいろいろあるようです。シェルスクリプトでそのコマンドを for ループで回せば,大量のQRコードを一括生成できるでしょう。
ですが,今回は新規ツールをわざわざインストールするのが面倒でしたので,Swift で自分で書くことにしました。macOS の CoreImage API にはQRコード生成機能が用意されていますので,それを利用すれば簡単にQRコードを生成できます。この方法ならUTF8にも対応でき,日本語を含む任意の文字列をQRコードにエンコードできます。
| import Cocoa | |
| extension NSBitmapImageRep { | |
| func representation(type: CFString, dpi: CGFloat) -> NSData { | |
| let prop: [CFString:Any] = [kCGImagePropertyIPTCImageType: 1.0, | |
| kCGImagePropertyDPIWidth: dpi, | |
| kCGImagePropertyDPIHeight: dpi] | |
| let outputData = NSMutableData() | |
| let destination = CGImageDestinationCreateWithData(outputData, type, 1, nil)! | |
| CGImageDestinationAddImage(destination, self.cgImage!, prop as CFDictionary) | |
| CGImageDestinationFinalize(destination) | |
| return outputData | |
| } | |
| } | |
| extension NSImage { | |
| class func qrCodeImage(from string: String, scale: CGFloat = 1) -> NSImage? { | |
| let stringData = string.data(using: .utf8) | |
| let transform = CGAffineTransform(scaleX: scale, y: scale) | |
| guard let filter = CIFilter(name: "CIQRCodeGenerator") else { | |
| return nil | |
| } | |
| filter.setValue(stringData, forKey: "inputMessage") | |
| guard let ciImage = filter.outputImage?.transformed(by: transform) else { | |
| return nil | |
| } | |
| let ciImageRep = NSCIImageRep(ciImage: ciImage) | |
| let nsImage = NSImage(size: ciImageRep.size) | |
| nsImage.addRepresentation(ciImageRep) | |
| return nsImage | |
| } | |
| func pngData(dpi: CGFloat = 72) -> NSData? { | |
| guard let tiffData = self.tiffRepresentation, | |
| let bitmapImageRep = NSBitmapImageRep(data: tiffData) else { | |
| return nil | |
| } | |
| return bitmapImageRep.representation(type: kUTTypePNG, dpi: dpi) | |
| } | |
| } | |
| // QRコード連続並列生成 | |
| let scale: CGFloat = 1 | |
| let dpi: CGFloat = 72 | |
| let maxCount = 1000 | |
| // 並列処理で一括生成 | |
| DispatchQueue.concurrentPerform(iterations: maxCount) { num in | |
| autoreleasepool { | |
| let numberString = String(format: "%04d", num) | |
| if let pngData = NSImage.qrCodeImage(from: numberString, scale: scale)?.pngData(dpi: dpi) { | |
| pngData.write(toFile: "/tmp/\(numberString).png", atomically: true) | |
| } | |
| } | |
| } |