マクロツイーター

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

TeX せずに Lua する LuaTeX の話(2)

前回の続き)

以下は、texlua する際の自分用のメモ的な何か。

その Lua は texluaか

実行している Lua インタプリタが texlua か否かを判定する方法について。

  • kpse ライブラリ(Kpathsea のフロントエンド)があれば texlua だと判断する。
    if kpse then
      -- texlua である
    end
    
    kpse の“最初から定義済”は一応廃止予定に入っていないが、廃止の可能性がないともいえない。require() したライブラリが存在しないとその時点でエラー終了してしまうため、“単に判定”したい場合は例外処理を組み込む。
    pcall(function()
      kpse = require("kpse")
      -- ついでに kpse の初期化をする
      kpse.set_program_name("luatex")
    end)
    if kpse then
      -- texlua である
    end
    
  • もっと確実にするには、status.luatex_version が定義済かを見る、とか。(“最初から定義されないかも”に対応する方法は先と同じ。)
    if status and status.luatex_version then
      -- texlua である
    end
    
Kpathsea したい

TeX に関するユーティリティを texlua で実装する場合は Kpathsea でファイルが探索してくれる方が“快適”なことが多い。例えば、前回記事の PDF のページ数を調べるプログラムを、「TeX 関連のドキュメントを探索する」ように拡張すると以下のようになる。

kpse = require "kpse"
kpse.set_program_name("luatex")
epdf = require "epdf"
local name = arg[1] or ""
local path = assert(
    kpse.find_file(name, "TeX system documentation", true),
    "file not found on search path: "..name)
local pdf = assert(epdf.open(path),
    "cannot open file as PDF document: "..path)
local num = assert(pdf:getNumPages())
print("This document has "..num.." page(s).")

texlua では kpse ライブラリにより Kpathsea の探索ができる。kpse ライブラリを用いるには、最初に“初期化”を行う必要がある。

kpse = require "kpse"
kpse.set_program_name("luatex")

set_program_name() の第 1 引数が何を表すものなのかはマニュアルにも明確には書いていない。どうも、Kpathsea における SELFAUTODIR 変数を決定するためにあると思われる。*1もしそれが正しいなら、"luatex""texlua" を指定するのが無難だろう。

なお、この名前は“Kpathsea のプログラム名指定”(kpsewhich の -progname に相当)としても用いられる。これを別に指定したい場合は第 2 引数に“プログラム名”を入れる。

kpse.set_program_name("luatex", "hogetool")

実際に、kpse でファイルを探索する場合は find_file() 関数を用いる。

path = kpse.find_file("ipaexm.ttf", "truetype fonts", true)

第 1 引数は対象のファイル名、第 2 引数はファイルタイプ(kpsewhich の -format に相当)を指定する。第 3 引数の true は kpsehiwhc の -must-exist に相当するものだが、これは常にしておくのがいいだろう。戻り値は、見つかったファイルパスの文字列で、見つからない場合は nil となる。

注意すべきは、kpsewhich コマンドと違って、拡張子などにより自動的にファイルタイプを推定する機能はないということである。つまり、原則的に第 2 引数は必須ということになる。((実際には第 2 引数は省略できるが、この場合は既定値の "tex" が使われる。))

なお、kpse.set_program_name() には副作用がある。それはライブラリの探索方法が変わることである。これについては次の節で述べる。

lualibs したい

TeX としての LuaTeX”で Lua コードを書く場合は、lualibs という“Lua 標準ライブラリを拡張する”ライブラリが利用できる。

% LuaLaTeX 文書, 文字コードUTF-8
\documentclass[a4paper]{article}
\usepackage{luacode}
\begin{luacode*}
require "lualibs"
local nums = {
  "one", "two", "three", "four", "five",
  "six", "seven", "eight", "nine", "ten"
}
function do_test(x, y)
  -- table.contains(t, x) はテーブル t 中で x のある位置を返す
  local nx = table.contains(nums, x)
  local ny = table.contains(nums, y)
  if nx and ny then
    tex.print(("%s plus %s equals %s"):format(
        x, y, nums[nx + ny] or "a big number"))
  else tex.print("ERROR!")
  end
end
\end{luacode*}
\newcommand*{\test}[2]{\directlua{
  do_test(\luastring{#1}, \luastring{#2})
}}
\begin{document}
\test{one}{one}\par
\test{three}{four}\par
\end{document}

LuaTeX がインストールされている環境には当然 lualibs もあるはずなので、texlua でもこれを利用したい。ところが、texlua で普通に require "lualibs" しようとすると「ライブラリのファイルが見つからない」というエラーになる。

test.lua:1: module 'lualibs' not found:
        no field package.preload['lualibs']
        no file 'C:\texlive\2015\bin\win32\lua\lualibs.lua'
        no file 'C:\texlive\2015\bin\win32\lua\lualibs\init.lua'
        no file 'C:\texlive\2015\bin\win32\lualibs.lua'
        no file 'C:\texlive\2015\bin\win32\lualibs\init.lua'
        no file '.\lualibs.lua'
        no file 'C:\texlive\2015\bin\win32\lualibs.dll'
        no file 'C:\texlive\2015\bin\win32\loadall.dll'
        no file '.\lualibs.dll'

この理由は、TeX エンジンとして起動した場合と Lua インタプリタとして起動した場合でライブラリの探索パスが異なるからである。

  • TeX エンジンとして起動した場合は、Kpathsea を利用した探索を行う。対象のファイルタイプ名は、Lua ライブラリが lua、バイナリライブラリが clua である。
  • Lua インタプリタとして起動した場合は、通常の Lua インタプリタの探索方法に従う。つまり探索パスを Lua ライブラリについては package.path、バイナリライブラリについては package.cpath で指定する。

しかし、Lua インタプリタ起動の場合でも、kpse ライブラリの初期化が実行されると、それ以降は TeX エンジンと同じく Kpathsea 探索に切り替える、という仕様になっている。texlua で TeX 関連のプログラムを作っている場合は、ライブラリの探索パスは TeX エンジン起動の時と一致した方が都合がいいだろう。

そういうわけで、“TeX エンジンでは読み込めるはずの”lualibs ライブラリを texlua で読み込むには、kpse の初期化を実行すればよいのである。

kpse = require "kpse"
kpse.set_program_name("luatex")
require "lualibs"
local nums = {
  "one", "two", "three", "four", "five",
  "six", "seven", "eight", "nine", "ten"
}
function do_test(x, y)
  local nx = table.contains(nums, x)
  local ny = table.contains(nums, y)
  if nx and ny then
    print(("%s plus %s equals %s"):format(
        x, y, nums[nx + ny] or "a big number"))
  else print("ERROR!")
  end
end
do -- main
  do_test("one", "one")
  do_test("three", "four")
end

*1:つまり、実行パス上でその名前の実行ファイルを探して、それがあるディレクトリを SELFAUTODIR と判断する、と推測される。実際、この引数を "hogehoge" のようなデタラメなものにすると「そんな実行ファイルはない」と怒られる。また、"sort" みたいな Kpathsea と無関係なものを指定すると「texmf.cnf が見つからない」と怒られる。