「インフォシーク isweb ライト」サービス終了に伴い、 本サイトは以下のURLに移転することになりました。
現在のサイトは、10月末を以て終了となります。
http://zrbabbler.sp.land.to/lualatexlua2.html

怯まず Lua で LaTeX してみた
~続・LuaTeX で日本語しない件について~

「思わず Lua で LaTeX してみた」の 更なる続編。 Lua と LaTeX が織り成す不可思議な世界。
変更履歴

しつこく Lua で LaTeX してみる

例によって、 ixbase0 パッケージ参照)の使用を前提とする。

事例 6: その場で LaTeX パッケージをダウンロードして読み込む

\fetchfromwww[<ファイル名>]{<対象URL>} で 指定の URL から内容(別に LaTeX パッケージでなくてもよい)を取得し、 指定のファイル名(省略時は URL の末尾部分)で保存する。 従って、直後に \usepackage/\input を 実行してそれを読み込むことができる。 常に最新版のパッケージを利用することができるのでとっても便利。 (サーバに余計な負荷をかけることになるので、 よい子のみなさんはマネしないように。)

% 文字コードは UTF-8
\documentclass[a4paper]{article}
\usepackage{ixbase0}
%%------ ダウンローダ
\begin{execluacodeblock}
local http = require("socket.http")
-- URL url の内容を取得してファイル file に書き出す
-- (file が nil なら url のパス末尾の名前を採る)
-- 戻り値は正常に取得できたかのブール値
function fetch_from_www(url, file)
  file = file or ({url:find("/([-_.%w]+)$")})[3]
  if not file then return false end
  -- 指定URLにGET要求, ここでは200以外のステータスはエラー扱い
  local res, code = http.request(url)
  if not res or code ~= 200 then return false end
  -- 取得した内容をファイルに書き出す
  local fh = io.open(file, "wb")
  if not fh then return false end
  fh:write(res); fh:close()
  return true
end
\end{execluacodeblock}
%
%% \fetchfromwww[<出力ファイル名>]{<対象URL>}
\newcommand*\fetchfromwww[2][]{
  \renewcommand*\ffwTargetUrl{#2}%
  \directlua{
    local file, url = "\luaescapestring{#1}", "\luaescapestring{#2}"
    % file が空なら nil に置き換える
    local res = fetch_from_www(url, (file \?\~= "" and file) or nil)
    if not res then tex.print("\??\\ffwShowWarning") end
  }%
}% 失敗した時の警告処理も入れておく
\newcommand*\ffwTargetUrl{}
\newcommand*\ffwShowWarning{%
  \typeout{Warning: Fetch from WWW failed for some reason.}%
  \typeout{(URL=\ffwTargetUrl)}}
%%------
% 以下では marginnote パッケージを CTAN からダウンロードして読み込んでいる
\fetchfromwww{http://www.ring.gr.jp/pub/text/CTAN/macros/latex/contrib/marginnote/marginnote.sty}
\usepackage{marginnote}
\begin{document}
This method is really worth noting.
\marginnote{But the example is marginal.}
\end{document}
“But the example is marginal.”は余白領域に傍注として出力される。

marginnote パッケージは 2 パス処理を行うので、 完全な出力を得るには lualatex を 2 回実行する必要がある。

参考: LuaTeX では luasocket ライブラリは組み入れられているので、 追加でインストールする必要がない。
注意: 最近では、LaTeX パッケージは .dtx 形式で配布されて いることがほとんどである。 その場合はサーバには .sty ファイルは置かれていない のでこの方法は上手くいかない。

事例 7: LaTeX 用の文献データベースを RDBS 上に構築する

要するに、BibTeX で行っている文献データベース管理を MySQL 等の RDBS でやろうという話。 文献データベースファイル(*.bib) の部分を RDBS が受け持ち、bibtex コマンドの実行の代わりに Lua コードが(組版中に)文献リスト(*.bbl) を出力するという形式をとる。 ただし、飽くまで実験であるので実装は極力簡単にしていて、 例えば、データベースには、リストに出力する LaTeX テキスト をそのまま登録するようになっている (つまり、何らかの「文献クラス」に従って整形するのではない)。 従ってこのままの実装では実用性は非常に乏しい。

ここでは、RDBS として、SQLite3 を用い、また SQLite と Lua の接続に LuaSQL ライブラリを用いる。 SQLite は他のアプリケーションに組み込んで使用することを想定して 作られている軽量なデータベースエンジンである (Lua と類似した開発目的をもっている)。 このため、他の一般的な RDBS のように「サーバ」を介するのではなく、 SQLite のエンジンを組み込んだソフトウェアが直接データベース (ファイルとして保存されている)にアクセスするという方式を採っている。

W32TeX で以下に挙げる例を実行するために必要な準備は次の通り。 W32TeX をインストールしたディレクトリを C:\usr\local とする。

  • LuaForge の LuaSQL のページから LuaSQL の Win32 バイナリ版 (私が用いたのは luasql-2.1.1-sqlite3-win32-lua51.zip) をダウンロードし、これを展開して sqlite3.dll を得る。
  • この sqlite3.dll を luasql_sqlite3.dll に改名して、 C:\usr\local\bin\lib\lua に置く。 (一般的な Lua でのインストール方法と少し異なる。)
  • 実は、この sqlite3.dll には SQLite3 のエンジンが「組み込まれて」 いるので、SQLite3 のデータベースを(SQL で)作成するソフトウェアを既に 持っている人はこれだけで十分である。 そうでない場合は、 SQLite のダウンロードページから 単体アプリケーションの Win32 版バイナリ (私が用いたのは sqlite-3_6_23_1.zip)をダウンロードしてインストールする (中の .exe ファイルを実行パスの通ったディレクトリに置くだけ)。

SQLite で文献データベースを管理するためのライブラリ ixdbbib およびその使用を含む LaTeX 文書 (ファイル名 test.tex)を以下に示す。

% このファイルの文字コードは UTF-8
\documentclass[a4paper]{article}
\usepackage{ixbase0}
%% ixdbbib ライブラリの Lua コード
\begin{execluacodeblock}
require "luasql_sqlite3" -- (※1)
-- SQL の文字列リテラル(RDBS に適応させる必要あり)
local function quote(s)
  return "'"..s:gsub("'", "''").."'"
end
-- リスト cites に id を追加する
local cites = {}; local used = {}
local function citation(id)
  if not used[id] then
    used[id] = true; table.insert(cites, id)
  end
end
-- 文献データを取得するSQL文
local sql_select = [[
SELECT id, content FROM bib_data
WHERE id IN (?) ORDER BY skey
]]
-- 文献データベース db_name と cites から
-- 文献リスト(thebibliography) を生成し、ファイルに出力
local function bibdata(db_name)
  assert(lfs.isfile(db_name),
    "bibliography database '"..db_name.."' not found")
  -- DB へ接続
  local env = assert(luasql.sqlite3())
  local con = assert(env:connect(db_name))
  local t = {}; local res = {nil, nil} -- (※2)
  for _, c in ipairs(cites) do table.insert(t, quote(c)) end
  local sql = sql_select:gsub("?", table.concat(t, ","))
  local cur = assert(con:execute(sql))
  -- データ読み出し
  local cnks = { "\\begin{thebibliography}{99}\n" }
  while cur:fetch(res, "n") do
    table.insert(cnks, "\\bibitem{"..res[1].."}"..res[2].."\n")
  end
  table.insert(cnks, "\\end{thebibliography}\n")
  cur:close(); con:close()
  -- ファイルに出力
  local fh = assert(io.open(tex.jobname..".bbl", "w"))
  fh:write(table.concat(cnks))
  fh:close()
end
-- エクスポート
ixdbbib = {
  citation = citation; bibdata = bibdata;
}
\end{execluacodeblock}
%% LaTeX コード
\renewcommand\citation[1]{\directlua{
  ixdbbib.citation("\luaescapestring{#1}")}}
\renewcommand\bibdata[1]{\directlua{
  ixdbbib.bibdata("\luaescapestring{#1}")}}
\begin{document}

% LaTeX 文書での文献参照方法は従来と同じ
% (ただし \nocite は未対応)
The primary reference is of course
the imaginary bible of Lua{\TeX}~\cite{ltbook}.
There are however rare and unfortunate cases
where you need to consult the fictitious book
of the source code~\cite{ltprogram}.

%\bibliographystyle{} % 文献スタイルは無意味
\bibliography{sample.db} % データベース名を指定

\end{document}

上の例で読み込むデータベース sample.db を作成するための SQL を以下に掲載する。

参考: SQLite3 の単体アプリケーション(sqlite3.exe)を使う場合は、 SQL の内容を sample.sql に保存して、次のコマンドを実行する。
sqlite3 -batch sample.db < sample.sql
DROP TABLE IF EXISTS bib_data;
CREATE TABLE bib_data (
  id      VARCHAR(32) PRIMARY KEY,
  skey    TEXT NOT NULL,
  content TEXT NOT NULL
);
INSERT INTO bib_data VALUES
('ltbook', 'luatexteam2013',
'Lua{\TeX} team. \emph{The Lua{\TeX}book}, Luaddison-Wesley, 2013'
);
INSERT INTO bib_data VALUES
('ltprogram', 'luatexteam2012',
'Lua{\TeX} team. \emph{Lua{\TeX}: The Program}, Luaddison-Wesley, 2012'
);

test.tex と sample.db を同じディレクトリに置いて、そこで lualatex test を 3 回実行すれば組版が完了する。 結果は以下の通り。

“LuaTeX: The program”と“The LuaTeXbook”がこの順で並んだ文献リストが出力され、文献参照に対して通常の方式で参照番号が振られる。

なお、LuaSQL ライブラリは SQLite3 以外のエンジン、 例えば、Oracle、PostgreSQL、MySQL にも対応している。 他のエンジンを用いる場合は、モジュール名と env:connect の引数を変えると同時に、quote 関数を適応させる 必要がある。 (MySQL 5.0.67 でも動作を確認した。)

参考: BibTeX を用いる場合は、

latex testbibtex testlatex testlatex test

のように latex を(最低)3 回実行する必要があった。 ここで、bibtex は 1 回目の latex で出力された test.aux を基にして、文献リストを記したファイル test.bbl を作成する役割を持っている。 上掲の ixdbbib を用いた文書の場合、 2 回目の lualatex で test.aux が読み込まれた時に、 SQLite3 のデータベースを読んで test.bbl を作成している (そしてその lualatex の実行の中の \bibliography の箇所で先程の test.bbl が読み込まれる)。 3 回目が必要な理由は、BibTeX 利用の場合と同じで、 相互参照の解決のためである。

参考: LuaTeX ではファイルの探索を Kpathsearch を介して行っている関係で、 C ライブラリの検索方法が通常の Lua と異なる (package.cpath は使われず、 代わりに Kpathsearch の CLUAINPUTS 変数の値が使われる)。 特に、階層化されたモジュール名をディレクトリ階層に対応付ける (例えば foo.bar$LUA_CPATH/foo/bar.dll) ことができない。 そのため、LuaSQL の SQLite3 用モジュールを本来の名前 luasql.sqlite3 で扱うことができず、 変則的な扱いをせざるを得なくなっている。 通常の Lua では、package.cpath に従って luasql/sqlite3.dll を配置し、 test.tex の中の (※1) の行は require "luasql.sqlite3"_ でなく . )となる。

参考: (※2) の行の res の初期化は 冗長(というより不気味)である。 本来は res = {} で済むところであるが、 これだと LuaTeX の不具合のために強制終了してしまう。 (本家の Lua 5.1 だと大丈夫だった。)

事例 8: テンプレート的 LaTeX

ここでいうテンプレート処理とは、文書の記述の一部を 「テンプレート言語」の記述に置き換え、 その中にある「変数」を外部にあるデータと結びつけることで、 文書の内容を動的に変化させる技術のことである。

テンプレート
こんにちは、<? if username then ?><?= username
          ?><? else ?>名無し<? end ?>さん!
+
データ
username = 鷗外
結合した結果の文書
こんにちは、鷗外さん!

ただし、ここで注目するのは、「外部のデータと結合すること」 ではなく、「文書の中にプログラミング言語を埋め込む」 ことである。 よって、外部のデータを参照せずに単独で実行できる テンプレート(もはや「テンプレート(雛形)」でない…) を活用することを考える。 特に、if 文や while 文などの制御構造の中に、コードではなく 「単なるテキスト」が混ぜられるのが面白い。

<? for n = 2, 9 do
   ?>* <?=n?> は<?
     if n == 2 or n == 3 then
     ?>素数<?
     elseif n % 2 == 0 then
     ?>合成数<?
     else ?>もしかしたら素数<?
     end
   ?>である。
<? end ?>
* 2 は素数である。
* 3 は素数である。
* 4 は合成数である。
* 5 はもしかしたら素数である。
* 6 は合成数である。
* 7 はもしかしたら素数である。
* 8 は合成数である。
* 9 はもしかしたら素数である。

ということで、本節では、テンプレートの様式を利用して LaTeX のテキストと Lua のコードをもっと柔軟に混在させる ことを目的とする。 テンプレートエンジンの実装は既存のものを使ってもよかったが、 なるべく単純なものが欲しかったので自分で作ってみた。

以下のように、テンプレートの作成と出力の機能のみをもつ、 非常に単純なモジュールである。

  • ixsst.compile(s) : テンプレート文字列 s を 「テンプレートオブジェクト」に変換する。 テンプレートの文法は以下の通りである。
    • <? ‹コード› ?> : Lua のコードの断片を埋め込む。
    • <?= ‹式› ?> : Lua の式を評価して文字列に変換(tostring()) した結果を出力する。
    • <? ... ?> の代わりに ? を任意の個数に増やして <??? ... ???> のように書くことも可能で、この場合「<?」等を コードに含めることができる。 <?= ... ?> の方も同様である。
    • それ以外の部分は、改行も含めて全てそのまま出力される。
  • templ_obj:exec(env) : テンプレートオブジェクト templ_obj の 表すテンプレートに Lua の環境(要するにテーブル) env を結合して出力した結果の文字列を返す。

今までに挙げたテンプレートの例におけるテンプレート言語も このモジュールで定めたものである。 プレーンテキストで用いる場合は <? ... ?> の外にある改行は全て出力されるので 少し不自然な囲み方をする必要に迫られるが、 LaTeX の中で用いる場合は % で改行を無効化できるので この点はほとんど問題にならないだろう。

参考: 最初に挙げたように、外部のファイルに記したデータと結合する 場合には、まずファイルを読んで { username = "鷗外" } のようなテーブルを作って、 それを exec() の引数に渡せばよい。 ただし、この節での目的においては、外部のデータではなく、 いつもの Lua の機能(関数)が見える必要があるので、 exec() の引数にはグローバル環境 _G を渡すことになる。

それでは、ixsst モジュールを LaTeX で用いた例を紹介する。 ここでは、1~30 までの整数の平方根と立方根の表を 「小数点の位置で揃えて」出力している。 勿論、2 つの関数値は Lua で計算している。

% このファイルの文字コードは UTF-8
\documentclass[a4paper]{article}
\usepackage{ixbase0}
\begin{execluacodeblock}
-- モジュール読み込み
require "ixsst"
--- templatex(str): テンプレートを解釈して結果を LaTeX の入力とする
-- @param str テンプレート文字列
function templatex(str)
  ixbase.print(ixsst.compile(str):exec(_G))
end
\end{execluacodeblock}

\begin{document}

%
%↓ テンプレート開始の位置にこの文字列を書く
\begin{execluacodeblock}templatex[==[
<?                                      -- この中は Lua コード
-- 小数点位置で揃えるため、実数表記を 1&.41421356
-- のように2カラム(カラム指定は r@{}l)に分けている。
function dsplit(n)
  return string.format("%.8f", n):gsub("%.", "&.")
end
?>
\begin{center}\small\begin{tabular}{r|r@{}l|r@{}l}
\hline
\multicolumn1{c|}{$x$}&
\multicolumn2{c|}{$\sqrt{x}$}&
\multicolumn2{c}{$\sqrt[3]{x}$}
\\\hline
<? for n = 1, 30 do                     -- Lua の for ループ
     local s, c = n ^ (1/2), n ^ (1/3) ?>% ← 改行無効化
% Lua の式の値を LaTeX で出力させるため ?= を使う
<?= n ?> & <?= dsplit(s) ?> & <?= dsplit(c) ?> \\
<? end ?>%
\hline
\end{tabular}\end{center}

]==]\end{execluacodeblock}
%↑ テンプレート終了の位置にこの文字列を書く

\end{document}

組版結果は以下の通り。

上の例では、「テンプレート」の部分を

\begin{execluacodeblock}templatex[==[
  (テンプレート)
]==]\end{execluacodeblock}

で囲っている (これは要するに \directlua{templatex("(テンプレート)")} を文字列中の Lua や LaTeX の特殊文字が通るように記述したものである)。 もし、この機能を広範的に使うのであれば、 もう少し整った LaTeX パッケージにして、 例えば以下の形式で記述できる方がいいかも知れない。

\documentclass[a4paper]{article}
\usepackage{ixbase0}
\usepackage{templatex} % コードをパッケージにまとめる
\begin{document}
% テンプレートを単純に 1 つの環境で表せるようにする
\begin{templatex}
  (テンプレート)
\end{templatex}
\end{document}

このようなパッケージ templatex を作ることは可能であるが、 TeX 言語の知識を必要とし (verbatim 的な処理を要するので LaTeX マクロでは無理)、 この文書の趣旨に合わないので ここでは前に挙げた形に留めることにした。