如『ConTeXt MkIV 中文支持的 Hacking』所言,ConTeXt Mkiv 目前对中文的横排的支持尚未完善,更不用说竖排了。但是,有时迫切需要对一些文字进行不是非常严肃的竖排,这样的问题也并非不能解决。
前些天,老板要嫁闺女,一大堆请柬需要打印宾客姓名。我写了个可以读取宾客名单并自动生成 100 多份 .tex 文件的脚本,然后利用 ConTeXt 的 layer 实现了姓名在请柬版面上的定位与排版。这里面有一个问题就是,请柬上的宾客姓名必须是竖着放的。这个问题,借助 ConTeXt 的 \rotate 与 \framed 很容易实现。
例如,将『范冰冰女士』竖着排,可以先试试这样做:
\usemodule[zhfonts]
\starttext
\rotate[rotation=-90]{\framed[frame=off]{范冰冰女士}}
\stoptext
结果得到:
文字站起来了,而范冰冰女士却倒下了。不过这个例子却可以证明 \rotate
是有效的,它能够将 \framed
制作的盒子顺时针旋转 90 度。
在 TeX 中,每个文字也都是放在一个特定尺寸的小盒子里的。那么 \rotate
对文字是否也有效?可以试试看:
\usemodule[zhfonts]
\starttext
\rotate[rotation=-90]{%
\framed[frame=off]{%
\rotate[rotation=90]{范}%
\rotate[rotation=90]{冰}%
\rotate[rotation=90]{冰}%
\rotate[rotation=90]{女}%
\rotate[rotation=90]{士}}}
\stoptext
注意:因为那么多 \rotate
放在一行,太不好看了,所以拆成多行。由于 TeX 引擎会将换行符当作空白字符,所以在每行的结尾放上 %
注释符使得换行符被注释掉,从而避免在排版结果中引入空白字符。
上面的代码是将每个汉字都逆时针旋转 90 度,然后将包含它们的盒子顺时针旋转 90 度,现在得到了这样的结果:
现在有眉目了。证明用 \rotate
可以做到文字竖排的效果。但是文字之间似乎太拥挤了,而且请柬上要求 范冰冰
与 女士
要有点距离……ok,那么就在文字之间增加点间隙。
\usemodule[zhfonts]
\starttext
\rotate[rotation=-90]{%
\framed[frame=off]{%
\rotate[rotation=90]{范}%
\kern.2em%
\rotate[rotation=90]{冰}%
\kern.2em%
\rotate[rotation=90]{冰}%
\kern.5em%
\rotate[rotation=90]{女}%
\kern.2em%
\rotate[rotation=90]{士}}}
\stoptext
\kern.2em
的意思就是在文字之间插入宽度为 0.2 倍字宽的空格。如果是 \kern-.2em
,那就是在文字之间插入 -0.2 倍字款的空格。所以,可以将 \kern
作为强大的文字间隙微调工具。通过这样的微调,得到下面符合要求的结果:
然而故事还没有结束。因为为了排版五个汉字竟然需要 11 行代码,这太浪费了。如果能够有一个宏 \zhvert
能够将它所接受的每个文字都逆时针旋转 90 度,那么代码就可以简化为:
\rotate[rotation=-90]{\framed[frame=off]{\zhvert{范冰冰 女士}}}
那我们就来考虑怎样定义 \zhvert
宏。
首先,\zhvert
必须接受一个参数,参数值是文字需要旋转的字符串,即:
\def\zhvert#1{对 #1 的处理……}
接下来,我需要勇敢的承认第一步我就败给了 TeX。因为我不知道该怎样在 \zhvert
的定义中枚举通过 #1
传入的字符串中的每个字符。在此希望路过的 TeX 黑客能拨云见日,指点一二。
我知道 TeX 的宏编程功能是图灵完备的,但是图灵完备不表示它是图灵好用的。ConTeXt MkIV 所用的 TeX 引擎是 LuaTeX,而 LuaTeX 的一大特点就是可以用 Lua 编程替代 TeX 宏编程。虽然我的 Lua 语言只是三脚猫的水平,但是最起码我知道怎样用 Lua 来枚举字符串中的字符。看来,图灵完备与图灵完备事实上并不一样,而且机器语言也是图灵完备的……
ConTeXt MkIV 提供的 \ctxlua{...}
是 TeX 世界通往 Lua 世界的入口。借助这个宏,尝试将 \zhvert
的实现转交给 Lua 代码,即:
\def\zhvert#1{\ctxlua{zhvert('#1')}}
于是,我们就可以将 \zhvert
所接受到的字符串参数值传递给一个 Lua 函数 zhvert
。接下来,就定义 zhvert
函数:
\startluacode
function zhvert (arg)
for c in arg:utfcharacters() do
if #c >=3 then
context.rotate({rotation='90'}, c)
context([[\kern.2em]])
elif c == " " then
context([[\kern.3em ]])
end
end
end
\stopluacode
上述 Lua 代码所实现的功能仅仅是将 范冰冰 女士
翻译成:
\rotate[rotation=90]{范}%
\kern.2em%
\rotate[rotation=90]{冰}%
\kern.2em%
\rotate[rotation=90]{冰}%
\kern.5em%
\rotate[rotation=90]{女}%
\kern.2em%
\rotate[rotation=90]{士}
唯一需要注意的就是 context.rotate()
与 context()
的用法。实际上,context.rotate({rotation=90}, c)
等价于 context([[\rotate[rotation=90]{c}]]
,而 `context()
就是将 ConTeXt 宏直接写到 \startluacode ... \stoluacode
所在位置。也就是说,\startluacode ... \stopluacode
中的 Lua 代码片段会被当场执行生成 ConTeXt 排版代码,然后 Lua 退场,ConTeXt 代码出场。
所以,可以将 context.xxxx()
或 context()
之类的函数视为 Lua 世界通向 ConTeXt 世界的入口。
下面给出完整的代码:
\usemodule[zhfonts]
\startluacode
function zhvert(arg)
for c in arg:utfcharacters() do
if #c >=3 then
context.rotate({rotation='90'}, c)
context([[\kern.2em]])
else
context([[\kern.5em ]])
end
end
end
\stopluacode
\def\zhvert#1{\ctxlua{zhvert('#1')}}
\starttext
\rotate[rotation=0]{\framed{\zhvert{范冰冰 女士}}}
\stoptext
结果是对的。
这个例子可以继续扩展,让它支持更多的竖排功能,因为 Lua 是更好的图灵完备编程语言……
最后,我决定吓你们一下。一个非常有名的 TeX 宏包——pgf,用于绘制二维矢量图的,这个宏包定义了一个可以实现两个数的除法运算的宏 \pgfmathdeclarefunction
,以下是这个宏的大致实现:
这样的事,如果交给 LuaTeX 会怎样,会怎样,会怎样?
\pgfmathdeclarefunction{divide}{2}%
{\def\pgfmathresult{\ctxlua{context(#1/#2)}}}
坚持走 LuaTeX 道路是正确的,尽管它的运行速度有点儿慢。