表格的製作,在 ConTeXt 裏需要很多命令,在 Lua 程序裏,只需要花括號和逗號。
例如,ConTeXt 裏的表格
\startxtable
\startxrow
\startxcell 1\stopxcell
\startxcell 2\stopxcell
\startxcell 3\stopxcell
\stopxrow
\stopxtable
即
用 Lua 語言來表示同樣形式的表,只需寫成
{1, 2, 3}
用 Lua 表製作時光機,是個很誘人的想法。例如,寫一個 Lua 程序,將以下形式的 Lua 表
{{"成爲聖人", "\\no"},
{"成爲狂人", "\\no"},
{"隱居山野", "\\ok"}}
“翻譯”爲 ConTeXt 的表格
\startxtable[frame=off]
\startxrow
\startxcell[width=.0655\textwidth] \m{\cdot} \stopxcell
\startxcell[width=0.8845\textwidth] 成爲聖人 \stopxcell
\startxcell[width=.05\textwidth] \strut\no \stopxcell
\stopxrow
\startxrow
\startxcell \m{\cdot} \stopxcell
\startxcell 成爲狂人 \stopxcell
\startxcell \strut\no \stopxcell
\stopxrow
\startxrow
\startxcell \m{\cdot} \stopxcell
\startxcell 隱居山野 \stopxcell
\startxcell \strut\ok\stopxcell
\stopxrow
\stopxtable
在物理學裏,這樣的程序稱爲槓桿。
命名空間
現在開始寫這個類似槓桿的程序。爲了避免我的 Lua 程序裏的一些元素(無非就是變量和函數)與 ConTeXt 裏的其他 Lua 程序的元素重名,有必要先確定我的 Lua 程序的命名空間。
命名空間 mingyi
本質上也是一個表,只不過它一開始是空表:
mingyi = {}
接下來,爲 mingyi
表增加一個元素,只不過這個元素是一個函數:
function mingyi.make_row(row, n)
context([[\startxrow]])
context([[\startxcell[width=.0655\textwidth]\m{\cdot}\stopxcell]])
if n == 1 then
context([[\startxcell[width=.8845\textwidth]%s\stopxcell]], row[1])
context([[\startxcell[width=.05\textwidth]\strut %s\stopxcell]], row[2])
else
context([[\startxcell %s\stopxcell]], row[1])
context([[\startxcell\strut %s\stopxcell]], row[2])
end
context([[\stopxrow]])
end
這個函數能做什麼,即使對 Lua 語言絲毫也不熟悉,但是結合上面給出的 ConTeXt 表格代碼,應當能猜出六七分。
代碼中的 [[...]]
是 Lua 的長字符串語法。至於 Lua 的短字符串語法,之前用過,是 "..."
形式。用長字符串語法的好處是,不需要對 TeX 命令的反斜線 \
轉義。例如,\startxrow
,若使用短字符串語法,需要寫爲 "\\startxrow"
。
下面,再爲 mingyi
這個表增加一個元素,依然是函數:
function mingyi.make_table(x)
context([=[\startxtable[frame=off]]=]
for i, v in ipairs(x) do
mingyi.make_row(v, i)
end
context([[\stopxtable]])
end
其中,[=[...]=]
依然是 Lua 的長字串語法,與 [[...]]
等價。Lua 語法允許在兩個雙括號之間插入一個或多個 =
,從而避免與字符串裏出現的 ]]
衝突。
碰釘子了
mingyi.make_table
接受的參數 x
應當是類似於下面的 Lua 表:
{{"成爲聖人", [[\no]]},
{"成爲狂人", [[\no]]},
{"隱居山野", [[\ok]]}}
在 ConTeXt 源文件裏,需要設法將該 Lua 表作爲參數,調用 mingyi.make_table
函數。試着定義一個宏:
\def\timemachine#1{\ctxlua{mingyi.make_table({#1})}}
然後通過 \timemachine
將 Lua 表傳給 mingyi.make_table
函數,例如
\timemachine{
{"成爲聖人", [[\no]]},
{"成爲狂人", [[\no]]},
{"隱居山野", [[\ok]]}
}
可惜,不行。TeX 編譯器報錯:
...mtx/tex/texmf-context/tex/context/base/mkxl/buff-ini.lmt:495: invalid value (nil) at index 14 in table for 'concat'
... ... ...
調試了很久,最後發現,唯有放棄讓 mingyi.make_table
替我生成 \startxtable
和 \stopxtable
語句:
function mingyi.make_table(x)
for i, v in ipairs(x) do
mingyi.make_row(v, i)
end
end
然後在 ConTeXt 源文件裏像下面這樣調用 \timemachine
:
\startxtable[frame=off]
\timemachine{
{"成爲聖人", [[\no]]},
{"成爲狂人", [[\no]]},
{"隱居山野", [[\ok]]}
}
\stopxtable
不要問我爲什麼……我也很想知道爲什麼。
柳暗花明
學會了使用一把錘子,以爲全天下的釘子都可以用這把錘子來敲。事實上,雖然
context([=[\startxtable[frame=off]]=]
... ... ...
context([[\stopxtable]])
行不通,但是 ConTeXt 裏的所有定義的宏,也許皆可以在 Lua 裏作爲函數調用,亦即
context.startxtable({frame = "off"})
... ... ...
context.stopxtable()
行得通。
同理,\blank
,\startxrow ...\stopxrow
,\startcell ... \stopcell
……皆有 Lua 函數形式。
於是,我寫了以下 Lua 代碼:
mingyi = {}
local ctx = context
local dim = number.todimen
local w = tex.dimen.textwidth
local w1, w2, w3 = 0.0655 * w, nil, 0.05 * w; w2 = w - (w1 + w3)
function mingyi.make_row(row, n)
ctx.startxrow()
ctx.startxcell{width = dim(w1)}; ctx([[\m{\cdot}]]); ctx.stopxcell()
if n == 1 then
ctx.startxcell{width = dim(w2)}; ctx(row[1]); ctx.stopxcell()
ctx.startxcell{width = dim(w3)}; ctx([[\strut ]] .. row[2]); ctx.stopxcell()
else
ctx.startxcell(); ctx(row[1]); ctx.stopxcell()
ctx.startxcell(); ctx([[\strut ]] .. row[2]); ctx.stopxcell()
end
ctx.stopxrow()
end
function mingyi.make_table(x)
context.blank(halfline)
context.startxtable{frame = "off"}
for i, v in ipairs(x) do
mingyi.make_row(v, i)
end
context.stopxtable()
context.blank(halfline)
end
上述代碼裏,我用了 Lua 語言的一些特性。例如,將 ConTeXt 定義的一些 Lua 表保存到局部變量裏:
local ctx = context
local dim = number.todimen
這樣做,可以讓調用表裏的函數或變量的代碼變得更簡短,例如:
ctx.startxcell{width = dim(w2)}; ctx(row[1]); ctx.stopxcell()
此外,利用了 Lua 的語法糖:如果函數的參數是一個表,那麼它的 ()
可省略,例如
ctx.startxcell{width = dim(w2)}
它與
ctx.startxcell({width = dim(w2)})
等價。
新的時光機
如果我不說,誰知道這是 Lua 生成的時光機呢?
下面,我給出 card.tex 的全部代碼:
\environment card-env
\starttext
\timestamp{2021 年 05 月 02 日}
\timemachine{
{"洗上個冬天穿過的衣服", [[\ok]]},
{"剃去在摩托車頭盔裏無限煩惱的頭髮", [[\ok]]},
{"製造 Lua 時光機", [[\ok]]},
{"去南河邊散步遛娃", [[\m{\cdots}]]}}
\stoptext
上文所寫的 Lua 代碼以及 \timemachine
宏的定義,我放在了 card-env.tex。以下是 card-env.tex 的全部內容:
\definepapersize[card][width=85.6mm,height=53.98mm]
\setuppapersize[card]
\setuplayout
[backspace=.1\paperwidth,
width=.8\paperwidth,
topspace=.015\paperheight,
height=.97\paperheight,
leftmargin=.666\backspace,
rightmargin=.666\cutspace,
headerdistance=.025\makeupheight,
footerdistance=.025\makeupheight,
textheight=.95\makeupheight]
\setuppagenumbering[location=]
\setupfootertexts[margin][][\hfill\pagenumber\hfill]
\setuphead[title][align=middle]
\def\timestamp#1{%
\setuptexttexts[margin]
[]
[\hfill{\rotate[rotation=270]{#1}}\hfill]
}
% 段落
\setupindenting[first,always,2em]
\setupinterlinespace[line=1.5em]
\def\cangjie#1{%
\lower.2ex\hbox{\externalfigure[#1][width=\bodyfontsize]}}
\def\ok{\cangjie{ok}}
\def\no{\cangjie{no}}
\def\mask{\cangjie{mask.png}}
\startluacode
mingyi = {}
local ctx = context
local dim = number.todimen
local w = tex.dimen.textwidth
local w1, w2, w3 = 0.0655 * w, nil, 0.05 * w; w2 = w - (w1 + w3)
function mingyi.make_row(row, n)
ctx.startxrow()
ctx.startxcell{width = dim(w1)}; ctx([[\m{\cdot}]]); ctx.stopxcell()
if n == 1 then
ctx.startxcell{width = dim(w2)}; ctx(row[1]); ctx.stopxcell()
ctx.startxcell{width = dim(w3)}; ctx([[\strut ]] .. row[2]); ctx.stopxcell()
else
ctx.startxcell(); ctx(row[1]); ctx.stopxcell()
ctx.startxcell(); ctx([[\strut ]] .. row[2]); ctx.stopxcell()
end
ctx.stopxrow()
end
function mingyi.make_table(x)
context.blank(halfline)
context.startxtable{frame = "off"}
for i, v in ipairs(x) do
mingyi.make_row(v, i)
end
context.stopxtable()
context.blank(halfline)
end
\stopluacode
\def\timemachine#1{\ctxlua{mingyi.make_table({#1})}}
\definefontfamily[myfont][serif][sourcehanserifcn]
\definefontfamily[myfont][math][xits]
\setscript[hanzi]
\setupbodyfont[myfont,7pt]
結語
似乎快要學會怎樣在 ConTeXt 裏擺弄 Lua 了。
我覺得將卡片的時間戳的位置靠頁眉放置,再將頁碼的位置提升到它上方的留白區域的底部,卡片的版面會更美觀一些。