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

结果得到:

文本竖排第一步

文字站起来了,而范冰冰女士却倒下了。不过这个例子却可以证明 \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,以下是这个宏的大致实现:

Hacking ConTeXt MkIV 第一集:文字的竖排_第1张图片

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

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

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

你可能感兴趣的:(context-mkiv,luatex)