转载

Hacking ConTeXt MkIV 第一集:文字的竖排

如『 ConTeXt MkIV 中文支持的 Hacking 』所言,ConTeXt Mkiv 目前对中文的横排的支持尚未完善,更不用说竖排了。但是,有时迫切需要对一些文字进行不是非常严肃的竖排,这样的问题也并非不能解决。

前些天,老板要嫁闺女,一大堆请柬需要打印宾客姓名。我写了个可以读取宾客名单并自动生成 100 多份 .tex 文件的脚本,然后利用 ConTeXt 的 layer 实现了姓名在请柬版面上的定位与排版。这里面有一个问题就是,请柬上的宾客姓名必须是竖着放的。这个问题,借助 ConTeXt 的 /rotate 与 /framed 很容易实现。

例如,将『范冰冰女士』竖着排,可以先试试这样做:

/usemodule[zhfonts]  /starttext  /rotate[rotation=-90]{/framed[frame=off]{范冰冰女士}}  /stoptext

结果得到:

Hacking ConTeXt MkIV 第一集:文字的竖排

文字站起来了,而范冰冰女士却倒下了。不过这个例子却可以证明 /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 度,现在得到了这样的结果:

Hacking ConTeXt MkIV 第一集:文字的竖排

现在有眉目了。证明用 /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 作为强大的文字间隙微调工具。通过这样的微调,得到下面符合要求的结果:

Hacking ConTeXt MkIV 第一集:文字的竖排

然而故事还没有结束。因为为了排版五个汉字竟然需要 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 ,以下是这个宏的大致实现:

Hacking ConTeXt MkIV 第一集:文字的竖排 Hacking ConTeXt MkIV 第一集:文字的竖排 Hacking ConTeXt MkIV 第一集:文字的竖排

这样的事,如果交给 LuaTeX 会怎样,会怎样,会怎样?

/pgfmathdeclarefunction{divide}{2}%     {/def/pgfmathresult{/ctxlua{context(#1/#2)}}}

坚持走 LuaTeX 道路是正确的,尽管它的运行速度有点儿慢。

正文到此结束
Loading...