「顧客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) | |
} | |
} | |
} |