上一篇:学一点 Lua

基于 ConTeXt 的缓冲区(Buffe)及其对 Lua 语言的支持,可以实现大段排版内容的预处理。所谓预处理,是指在 TeX 编译器对排版内容进行断行分页之前的处理,通常由排版者负责。

标点间距压缩

ConTeXt 对汉字排版仅提供了基本支持,许多排版上的细节问题,通常需要用户自行实现,例如标点间距压缩。汉字是方块字(等宽字体),标点符号自然也是方块字。当两个标点符号相邻时,倘若不压缩它们的间距,排版结果会显得过于粗疏。例如,

\environment card-env
\starttext
老子:「天下万物生于有,有生于无。」
\stoptext

冒号和左引号的间距过大,句号和右引号的间距也过大,用 \kern 插入负间距可予以调整:

\environment card-env
\starttext
老子:\kern-.5em「天下万物生于有,有生于无。\kern-.5em」
\stoptext

显然每次在写标点符号时,手工加入 \kern 调整标点间距,容易累手甚至出错。需要考虑,如何写一个预处理程序,让它代替我们完成这些工作。

缓冲区

缓冲区可用于收集内容。例如

\environment card-env
\starttext
\startbuffer[foo]
老子:「天下万物生于有,有生于无。」
\stopbuffer

\color[darkred]{\getbuffer[foo]}

\color[darkgreen]{\getbuffer[foo]}

\color[darkblue]{\getbuffer[foo]}
\stoptext

\startxxx ... \stopxxx 宏

基于缓冲区,可以构造类似 \starttext ... \stoptext 这样的宏,例如

\environment card-env
\def\startfoo{\dostartbuffer[foo][startfoo][stopfoo]}
\def\stopfoo{\getbuffer[foo]}

\starttext
\startfoo
老子:「天下万物生于有,有生于无。」
\stopfoo
\stoptext

其中,\dostartbuffer 是 ConTeXt 的系统宏(System macros),其作用是定义一个名为 foo 的缓冲区,并且定义 \startfoo\stopfoo 作为缓冲区的开始和结束。由于 TeX 宏可以重定义,所以可将 \stopfoo 重定义为 \getbuffer[foo] 直接呈现缓冲区内容。

如果将 \stopfoo 重定义为 \ctxlua{...} 宏,便有机会将缓冲区 foo 的内容转移给一段 Lua 程序,因为 ConTeXt 提供了与 \getbuffer 等效的 buffers.getbuffer 函数。例如,

\environment card-env
\def\startfoo{\dostartbuffer[foo][startfoo][stopfoo]}
\def\stopfoo{\ctxlua{context(buffers.getcontent('foo'))}}

\starttext
\startfoo
老子:「天下万物生于有,有生于无。」
\stopfoo
\stoptext

其中,\stopfoo 被重新定义为嵌入 Lua 程序的宏,所嵌入的 Lua 程序

context(buffers.getcontent('foo'))

可在 Lua 世界里以 Lua 字符串的形式从缓冲区 foo 中获取内容,并将所获内容提交给 context 函数,由后者转发给 TeX 世界。

文本匹配与替换

在 Lua 世界里,处理文本的利器是 LPEG 库。对于标点间距压缩的问题,使用 LPEG 库,仅费吹灰之力,例如

local P = lpeg.P
local Cs = lpeg.Cs
local p = Cs((P':「'/':\\kern-.5em「' + 1)^0)
local q = Cs((P'。」'/'。\\kern-.5em」' + 1)^0)
print(q:match(p:match([[老子:「天下万物生于有,有生于无。」]])))

Cs 为 LPEG 库的捕获替换模式,它可以从给定的字符串里捕获与模式 P':「'P'。」' 匹配的子串,并分别将其替换为 :\\kern-.5em「。\\kern-.5em」

将上述 Lua 代码略加改动,便可嵌入到 ConTeXt 源文件:

\environment card-env
\startluacode
my = my or {}
function my.puncs_compress(buffer)
    local P, Cs = lpeg.P, lpeg.Cs
    local p = Cs((P':「'/':\\kern-.5em「' + 1)^0)
    local q = Cs((P'。」'/'。\\kern-.5em」' + 1)^0)
    context(q:match(p:match(buffer)))
end
\stopluacode
\def\startfoo{\dostartbuffer[foo][startfoo][stopfoo]}
\def\stopfoo{\ctxlua{my.puncs_compress(buffers.getcontent('foo'))}}

\starttext
\startfoo
老子:「天下万物生于有,有生于无。」
\stopfoo
\stoptext

使用 ConTeXt 实现的 Lua 库(l-lpeg.lua)里的 lpeg.replacer 函数,可将上述 my.puncs_compress 函数的定义简化为:

function my.puncs_compress(buffer)
    local rep = {
        [1] = {':「', ':\\kern-.5em「'},
        [2] = {'。」', '。\\kern-.5em」'}
    }
    context(lpeg.replacer(rep):match(buffer))
end

结语

倘若熟悉 LPEG 库,在 ConTeXt 缓冲区里,会觉得自己是个强大的巫师,举手投足,便是黑魔法。

下一篇:源码凸显

参考


garfileo
6k 声望1.9k 粉丝

这里可能不会再更新了。