TeX Alchemist Online

TeX を使って化学のお仕事をしています。

TeX 文書中で日本語を含むQRコードを動的生成する(macOS環境下,shell-escapeあり)

昨日,Python ワンライナーを用いて macOS のデフォルト状態でコマンドラインからQRコード画像を生成する方法を紹介しました。

doratex.hatenablog.jp

この記事について,TeX界の大御所の目には,隠れた意図を軽々と見抜かれてしまいました😅

そこで,「真の目的」であった「TeX文書からのQRコードの動的生成」の話も述べておきましょう。

qrcode パッケージの利点と欠点

TeX文書内でQRコードを動的生成する方法としては,以前の記事で qrcode パッケージを紹介しました。

doratex.hatenablog.jp

qrcode パッケージは,TeX言語だけで実装されたQRコード生成パッケージです。TeX言語だけで実装されているため,shll-escape に頼ることなくTeXエンジン内で完結してQRコードを動的生成できるという強みがある一方,

  • エンコードできる文字列はASCII文字限定(したがって和文文字などはエンコードできない)
  • TeX言語の演算機能で実装されているため動作が遅い

という弱みがあります。動作速度に関しては,オーバーヘッドが多いはずの「shell-escape でシェルから他のQRコード生成プログラムを起動 → 画像ファイルを生成 → それをTeX文書へ読み込み」というワークフローと比べても遅く感じます。一文書に数百個のQRコードを埋め込むような文書を作ると,その遅さに悩まされることになります。

そこで,shell-escape を解禁するという条件で,QRコード動的生成の別の方法を探ってみましょう。

設計方針

macOS環境かつ shell-escape ありの条件下ならば,昨日の記事の Python ワンライナーを \immediate\write18 で呼び出せば,QRコードを動的生成できます。

一時ファイルの生成場所・ファイル名

外部プログラムに頼ってQRコードを動的生成する場合,「どこかにQRコードの画像ファイルを生成してそれを \includegraphics で読み込む」という方針になります。そのファイルをどこにどのような名前で書き出すかが問題となります。

ディレクトリ

TeX文書と同じディレクトリに書き出すと目障りなので,macOS標準の各ユーザ用テンポラリディレクトリ $TMPDIR (CoreFoundation の NSTemporaryDirectory() で取得できるのと同じ)に書き出すことにしましょう。

ファイル名

書き出すQRコードの画像ファイル名を固定ファイル名にしてしまうと,一文書内に複数のQRコードを作成した場合に衝突してしまいます。\jobname + 通算QRコード番号.png のようなファイル名にする方法も考えられますが,別のディレクトリに存在する \jobname を同じくするTeX文書のコンパイルが同時進行した場合に衝突してしまいます。そこで,一時ファイル名の衝突を避けるために,先の記事で作成した uuid パッケージが役立ちます。

doratex.hatenablog.jp

この uuid パッケージで生成したランダムな UUID を一時ファイル名とすれば,衝突が避けられるというわけです。

サンプル

以上の方針に基づいて作成した,QRコード動的生成 LaTeX 文書 (upLaTeX) のサンプルです。

%#!uplatex -shell-escape
\documentclass[dvipdfmx,uplatex]{jsarticle}
\usepackage{graphicx}
\usepackage{keyval}
\usepackage[lowercase]{uuid}

\makeatletter

\def\QRcode@scale{5}
\def\QRcode@dpi{144}
\def\QRcode@correctionLevel{H}

\define@key{QRparameter}{scale}{\def\QRcode@scale{#1}}
\define@key{QRparameter}{dpi}{\def\QRcode@dpi{#1}}
\define@key{QRparameter}{correctionLevel}{\def\QRcode@correctionLevel{#1}}%% 誤り訂正レベルを L / M / Q / H から指定

\newcommand*\QRcode[2][]{%
  \begingroup
  \ifnum\pdfshellescape=1
    \setkeys{QRparameter}{#1}%
    \edef\QRcode@outputPath{\string$TMPDIR/\UUID.png}%
    \immediate\write18{/usr/bin/python -c "message = '#2'.decode('utf-8');outputPath = '\QRcode@outputPath'.decode('utf-8');scale = \QRcode@scale;dpi = \QRcode@dpi;correctionLevel = '\QRcode@correctionLevel';import Foundation as fd;import Quartz as qz;data = fd.NSString.stringWithString_(message).dataUsingEncoding_(fd.NSUTF8StringEncoding);transform = qz.CGAffineTransformMake(scale, 0, 0, scale, 0, 0);filter = qz.CIFilter.filterWithName_('CIQRCodeGenerator');filter.setValue_forKey_(data, 'inputMessage');filter.setValue_forKey_(correctionLevel, 'inputCorrectionLevel');ciImage = filter.outputImage().imageByApplyingTransform_(transform);cgImage = qz.CIContext.alloc().init().createCGImage_fromRect_(ciImage, ciImage.extent());outputData = fd.NSMutableData.alloc().init();destination = qz.CGImageDestinationCreateWithData(outputData,'public.png', 1, None);properties = { qz.kCGImagePropertyIPTCImageType: 1.0, qz.kCGImagePropertyDPIWidth: dpi, qz.kCGImagePropertyDPIHeight: dpi };qz.CGImageDestinationAddImage(destination, cgImage, properties);qz.CGImageDestinationFinalize(destination);outputData.writeToFile_atomically_(outputPath, False)"}%
    \includegraphics{\QRcode@outputPath}%
  \else
    \@latex@error{-shell-escape is not allowed}\@ehc
  \fi
  \endgroup
}

\makeatother

\begin{document}

%%% 和文文字も正しくエンコードできます
\QRcode{TeXはアレ☃}

%%% keyval形式でパラメータのカスタマイズ
これは\QRcode[scale=3,dpi=72,correctionLevel=M]{☃本質情報☃}です。

\end{document}

この文書を

$ ptex2pdf -u -l -ot -shell-escape test

のように -shell-escape 付きでコンパイルすると,ランダムな UUID をファイル名に持つQRコード画像の一時ファイルが $TMPDIR 内に動的に生成されて,文書内に埋め込まれます。