如『 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 道路是正确的,尽管它的运行速度有点儿慢。