マクロツイーター

はてダから移行した記事の表示が崩れてますが、そのうちに直せればいいのに(えっ)

例の「verb の呪い」を LuaTeX の力で打破する(bxrawstr パッケージ)

これは「TeX & LaTeX Advent Caleandar 2018」の 22 日目の記事です。
(21 日目は tasusu さん でした。23 日目は munepi さん です。)

「verb の呪い」について語ってみる

タイトルにある「verb の呪い」というのは、多くの LaTeX ユーザを悩ませるあの規則のことです。

verb 系の命令は、他の命令の引数の中では使えない。

ここで「verb 系の命令」というのは LaTeX 標準の \verb 命令や verbatim 環境のような、「LaTeX特殊文字を非特殊なものとして入力する*1」機能を指します。例えば、以下のようなことをしようとすると「verb の呪い」のせいで失敗してしまいます。

% \verb は \textsf の引数の中なので使えない!
環境変数\textsf{\verb!%USERPROFILE%!}の値が参照される。

「verb の呪い」の非常に厄介な点は、その原因が TeX 自体の入力機構であるため「実装を工夫すること」では回避できないことです。((jsclasses のクラスや fancyvrb パッケージの \VerbatimFootnotes 命令のように「\footnote の引数の中で \verb を使えるようにする」というものがありますが、これは所望の機能を実現するために \footnote の側に細工をしています。従って、同様の方法では「任意の命令の引数で \verb を使える」ようにすることは不可能です。))パッケージの実装者としては、当然「どこでも使える verb 系命令」を作りたいわけですが、TeX 言語の力をもってしてもそれは実現できないわけです。

LuaTeX の力を使ってみる

「verb の呪い」を打破して「どこでも使える verb 系命令」を実装することはできないのでしょうか。一見すると「本質的に解決困難な問題」のようにも思えますが、今はまだ 12 月ですので、もう少し一般の*2解決策を探ってみましょう。確かに TeX 言語での解決は無理ですが、エンジンを LuaTeX に限定するのであれば状況は変わります。LuaTeX では Lua 言語の力を利用して「TeX の入力規則を変える」ことができるのです。この機構を利用すると「どこでも使える verb 系命令」が実現できそうです。

というわけで、作りました

bxrawstr を使ってみる

bxrawstr の最も単純な使い方は以下のようになります。

% LuaLaTeX文書; UTF-8
\documentclass[a4paper]{ltjsarticle}
\usepackage{bxrawstr}
\rawstrenable % これ以降 [[|...|]] が"生文字列"になる
\begin{document}
% 全て非特殊文字として扱われ, 現在のフォントで出力される.
環境変数[[|%USERPROFILE%|]]の値が参照される。
\end{document}

bxrawstr パッケージを読み込んで \rawstrenable を実行すると、それ以降のソース中の [​[|…|]] で囲まれた部分(これを「生文字列」(raw-string)と呼びます)が「LaTeX 的に非特殊な文字の列」として扱われるようになります。先の例のように、生文字列を“単純に出力”した場合は、当該の文字列が現在のフォント設定の下でそのまま出力されることになります。*3

もっと生文字列を“単純に出力”してみる

そして、bxrawstr の生文字列は「verb 系命令とは異なり、他の命令の引数でもその機能を保つ」という特長をもっています。従って、生文字列に装飾を加えて出力することが自由にできます。

% LuaLaTeX文書; UTF-8
\documentclass[a4paper]{ltjsarticle}
\usepackage{xcolor}
\usepackage{bxrawstr}
\rawstrenable
% \xHL{<text>} : 地と文字に色を付けて出力.
\newcommand*{\xHL}[1]{\colorbox{red!15}{\color{red}#1}}
\begin{document}
% 生文字列は命令の引数にも使える.
環境変数\xHL{\textsf{[[|%USERPROFILE%|]]}}の値が参照される。
\end{document}

生文字列はかなり“込み入った箇所”でも使うことができます。次の例では、tikzducks の \duck 命令の key-value 形式の引数の中で生文字列を使うことで #%!& の文字列をそのまま出力しています。

% LuaLaTeX文書; UTF-8
\documentclass[a4paper]{article}
\usepackage{tikzducks,xcolor}% DUCK!!
\definecolor{myred}{rgb}{0.87,0.17,0.27}
\usepackage{bxrawstr}
\rawstrenable
\begin{document}

\begin{tikzpicture}% U+1F92Cなアヒル
  \duck[body=myred,bill=myred!75!black,signback=black!85,
    signpost=\scalebox{0.8}{\color{white}\textsf{[[|#%!&|]]}}]
\end{tikzpicture}

\end{document}

他の命令の引数で使えるということは、\newcommand の引数でも生文字列を使えるということになります。すなわち、特殊文字を含む文字列を(LaTeX の)マクロにすることも可能です。

% LuaLaTeX文書; UTF-8
\documentclass[yoko,paper=a5]{jlreq}
\newcommand*{\xIL}[1]{\par\noindent#1\par}
\usepackage{bxrawstr}
\rawstrenable
% 生文字列をマクロにする
\newcommand*{\xXA}{[[|\expandafter|]]}
\begin{document}

傍にいたW氏に訊いてみた。
\xIL{「\xXA ?」}

想定通りの答えが返ってきた。
\xIL{「\xXA\xXA\xXA !」}

\end{document}
\index で生文字列してみる

このように、生文字列を使うと命令の引数に任意の文字列が渡せるのですが、命令の種類によっては注意が必要です。実は、bxrawstr の仕様において、生文字列の形式([[|…|]])は「“展開”((LaTeX のマクロ(\newcommand で定義される命令)の“展開”と同様の概念です。厳密には、TeX 言語における“先頭完全展開”に相当します。))して初めて中身の文字列になる」と定められています。そのせいで少し複雑な扱いが必要になるケースがあるのです。

[[|%USERPROFILE%|]]  (生文字列形式)
↓"展開"
%USERPROFILE%  (非特殊な文字列)

makeidx による索引の自動生成を行う文書において、%USERPROFILE% という語句を索引に(このままの位置で)載せたいとします。通常は \index 命令は「引数に特殊文字をそのまま書ける」という性質をもつので問題は起こりません。ところが、\index 命令自体が他の命令の引数に入っている場合は、この性質が失われてしまう(これも「verb の呪い」の一種です。)ため、特殊文字を含む %USERPROFILE% を引数に渡すのが困難になります。

% これは大丈夫
[[|%USERPROFILE%|]]\index{%USERPROFILE%@\verb+%USERPROFILE%}は…

\xStrong{% 他の命令の引数の中
  % これはダメ!
  [[|%USERPROFILE%|]]\index{%USERPROFILE%@\verb+%USERPROFILE%}は…
}

一見すると、\index の引数の中も生文字列を使えばよいように思えます。

  [[|%USERPROFILE%|]]\index{[[|%USERPROFILE%@\verb+%USERPROFILE%|]]}は…

ところが \index は引数を“展開”せずに用いるという性質があるため、これでは [[|%USERPROFILE%@\verb+%USERPROFILE%|]] という生文字列形式そのもの*4が引数と見なされるため、所望の動作になりません。

bxrawstr パッケージでは、このような状況に対処するため、生文字列の扱いを \rawstr という命令で制御できるようにしています。

  • \rawstr\命令[[|生文字列|]] を((生文字列形式を囲む {} がないことに注意してください。))実行すると、生文字列の内容をもつ(“展開”結果の)非特殊文字列を引数にして \命令 が呼び出される。*5
  • \rawstr[[|生文字列|]](命令を間に挟まない形)は [[|生文字列|]] と同じで非特殊文字列をそのまま出力する。

この \rawstr を使うと、先の問題を解決できます。((ちなみに、[[|…|]] はネストできないので、\index の引数中の \verb を生文字列に置き換えることはできません。))

% LuaLaTeX文書; UTF-8
\documentclass[a4paper]{ltjsarticle}
\usepackage{makeidx}
\makeindex
\usepackage{bxrawstr}
\rawstrenable
\newcommand*{\xStrong}[1]{\textbf{#1}}
\begin{document}
\xStrong{環境変数\texttt{[[|%USERPROFILE%|]]%
  % 生文字列の内容を \index の引数に渡す
  \rawstr\index[[|%USERPROFILE%@\verb+%USERPROFILE%+|]]
  の}}値が参照される。
\end{document}

\makeindex により生成される .idx ファイルの内容は以下の通りで、所望の内容になっていることがわかります。

\indexentry{%USERPROFILE%@\verb+%USERPROFILE%+}{1}
複数行にまたがって生文字列してみる

生文字列形式は複数行にまたがって書くことができます。

Hello, [[|%%%
&&&
$$$|]] world!

生文字列形式の中に現れる改行文字の扱いは \rawstr のオプション引数で決められます。

  • \rawstr[s](既定): 元の改行文字は空白文字に変換される。つまり「%%% &&& $$$」となる。
  • \rawstr[d] : 元の改行文字は削除される。つまり「%%%&&&$$$」となる。
  • \rawstr[k] : 元の改行文字を LF 文字(U+000A)として扱う。つまり Lua の文字列リテラルで書くと "%%%\n&&&\n$$$" に相当する文字列になる。

前述の通り、単独で [[|…|]] だけ書いた場合は \rawstr[[|…|]] と同等になるため、この場合は改行文字は空白と見なされます。

[k] オプションの用途はやや特殊です。LaTeX で“LF 文字を出力”しても組版結果で改行が起こるわけではないので、[k] は単純に出力する目的では使えません。この指定が役立つ場面と例としては、「\directlua の引数に LaTeX特殊文字を含む Lua コードを書く」というものがあります。(([TeX 言語者向け情報]オプション付きの \rawstr も完全展開可能であるため、これにより完全展開可能性を保ったまま、\directlua 中に LaTeX特殊文字を書くことが可能になります。))

% LuaLaTeX文書; UTF-8
\documentclass[a4paper]{ltjsarticle}
\usepackage{bxrawstr}
\rawstrenable
\begin{document}

% \fizzbuzz{<カウンタ名>}: FizzBuzzなカウンタ出力命令.
% ※カウンタ出力命令なので完全展開可能にしたい!
\newcommand{\fizzbuzz}[1]{\directlua{
  local val = \arabic{#1}%←これは展開される
  \rawstr[k][[| --改行保持の生文字列.
    if val % 15 == 0 then val = 'FizzBuzz'
    elseif val % 3 == 0 then val = 'Fizz'
    elseif val % 5 == 0 then val = 'Buzz'
    else val = tostring(val)
    end
    tex.sprint(val)
  |]]
}}
% 1段目enumerateの番号をFizzBuzzにする.
\renewcommand{\theenumi}{\fizzbuzz{enumi}}

技術書は以下の要素で構成される:
\begin{enumerate}
\item 本体
\item バケツ
\item 枝
\item ボタン
\item マフラー \label{itm:muffler}
\end{enumerate}
% 参照も正しく行われる.
特に、\ref{itm:muffler}は重要。

\end{document}
rescan モードを使ってみる

生文字列を直接出力すると \verb と同じ動作になります。しかし、\verb には「空白文字を空白記号〈␣〉(U+2423)として出力する」ための \verb* という変種が存在します。この \verb* を他の命令の引数中で使いたい(つまり \xHL{\verb*!line width!} を実現したい)場合はどうすればよいでしょうか。

\xHL{% 引数の中
  % ダメ
  [[|\verb*!line width!|]]%
}

これでは \verb*!line width! という文字列自体が出力されてしまうのでダメです。\rawstr を使うのはどうでしょうか。

\xHL{% 引数の中
  % ダメ
  \rawstr\verb*[[|!line width!|]]%
}

これだと \verb*{!line width!} という形に帰着されてしまい、\verb* の書式と合致しないのでやはりダメです。

今の場合、「非特殊文字化」は \verb* 自体がやってくれる前提であるため、必要なのは、\verb*!line width! という文字列を LaTeX のコードとして実行することのはずです。これを実現するのが rescan モードで、\rawstr のオプションに r を指定するとこのモードが適用されます。

% LuaLaTeX文書; UTF-8
\documentclass[a4paper]{ltjsarticle}
\usepackage{xcolor}
\usepackage{fontspec}% \verb* を使うためfontspecが必要
\usepackage{bxrawstr}
\rawstrenable
% \xHL{<テキスト>} : 地と文字に色を付けて出力.
\newcommand*{\xHL}[1]{\colorbox{blue!15}{\color{blue}#1}}
\begin{document}
線幅を自由に指定するには
% rescanモードなので, \verb*!line width! が実行される.
\xHL{\rawstr[r][[|\verb*!line width!|]]}%
キーを利用します。
\end{document}

ちなみに、\xHL{\rawstr[r]…} の部分をマクロにすることもできます。((つまり、\rawstr を本体中に含んで生文字列形式を引数にとるマクロは可能です。これに対して、生文字列の部分をマクロにしてそれを \rawstr の引数に渡すことはできません。))

% LuaLaTeX文書; UTF-8
\documentclass[a4paper]{ltjsarticle}
\usepackage{xcolor}
\usepackage{fontspec}% \verb* を使うためfontspecが必要
\usepackage{bxrawstr}
\rawstrenable
% \xHL{<テキスト>} : 地と文字に色を付けて出力.
\newcommand*{\xHL}[1]{\colorbox{blue!15}{\color{blue}#1}}
% \xHLr{<生文字列>} : \xHL と \rawstr[r] の合成.
\newcommand*{\xHLverb}[1]{\xHL{\rawstr[r]#1}}
\begin{document}
線幅を自由に指定するには
% \xHLverb は普通のマクロだから引数に{}が必要.
\xHLverb{[[|\verb*!line width!|]]}%
キーを利用します。
\verb*!line width!
\end{document}

rescan モードを使うと、verbatim 環境を他の命令の引数に入れることも可能になります。この場合、生文字列中の改行を保持する必要があるため、\rawstr のオプションを [rk] と指定することになります。

例えば、LaTeX のコード断片とそれに対応する出力を横に並べた上で、それを(tcfaspin パッケージを利用して)回転させるという、トッテモ素敵な文書を作ってみましょう。

% LuaLaTeX文書; UTF-8
\documentclass[a4paper]{article}
\usepackage[svgnames]{xcolor}
\usepackage{scsnowman}% ゆきだるま!
\usepackage{tcfaspin}% 回転!
\usepackage{bxrawstr}
\rawstrenable
\begin{document}

\faSpin{%
  \begin{minipage}[c]{8em}
    \footnotesize\color{Blue}%
    % rescan モードでかつ改行を保持する.
    \rawstr[rk][[|\begin{verbatim}
\scsnowman[
  scale=8,
  muffler=Red,
  hat=Green,
  buttons=Blue,
  arms=Brown,
  snow=SkyBlue]
\end{verbatim}|]]
  \end{minipage}%
  \begin{minipage}[c]{7em}%
    \normalsize\centering
    \scsnowman[
      scale=8,
      muffler=Red,
      hat=Green,
      buttons=Blue,
      arms=Brown,
      snow=SkyBlue]
  \end{minipage}%
}
\end{document}

まとめ

パッケージのデバ​ッグをしながら記事を書くのはツライ。

やっぱり LuaLaTeX はスゴイ!

*1:必ずしも「そのまま出力する」機能とは限りません。

*2:もし今が 8 月なら画期的な解決策を模索したいところでしょう。

*3:入力の文字コードを“そのまま”出力するので、現在のフォントエンコーディングが OT1 だと文字化けが起こります。2017 年以降の LaTeX であれば既定エンコーディングUnicode なので大丈夫ですが、それより古い場合は fontspec を読み込んだ方がいいでしょう。

*4:実際には、字句解析段階で変換した後の中間形式の文字列。

*5:TeX 言語者向け情報]この形式は完全展開可能です。