表格的制作,在 ConTeXt 里需要很多命令,在 Lua 程序里,只需要花括号和逗号。
例如,ConTeXt 里的表格
\startxtable
\startxrow
\startxcell 1\stopxcell
\startxcell 2\stopxcell
\startxcell 3\stopxcell
\stopxrow
\stopxtable
即
![](https://img.haomeiwen.com/i11203728/55600bea694a811c.png)
用 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 生成的时光机呢?
![](https://img.haomeiwen.com/i11203728/1884c74b7dc705e8.png)
下面,我给出 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 了。
我觉得将卡片的时间戳的位置靠页眉放置,再将页码的位置提升到它上方的留白区域的底部,卡片的版面会更美观一些。
网友评论