《Lua-in-ConTeXt》10:缓冲区魔法

上一篇:学一点 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 缓冲区里,会觉得自己是个强大的巫师,举手投足,便是黑魔法。

下一篇:源码凸显

参考


5.9k 声望
1.9k 粉丝
0 条评论
推荐阅读
《Lua-in-ConTeXt》12:zhfonts 备忘录
zhfonts 模块实现了 ConTeXt (>= MkIV) 对汉字字体的加载、简体汉字标点符号(全角)间距的压缩以及边界对齐。该模块成型于 2011 年,2023 年初对代码进行了一番梳理,希望它能工作到 2033 年……安装和使用方法...

garfileo阅读 330

Redis分布式锁的实现
很多新手将 分布式锁 和 分布式事务 混淆,个人理解:锁 是用于解决多程序并发争夺某一共享资源;事务 是用于保障一系列操作执行的一致性。我前面有几篇文章讲解了分布式事务,关于2PC、TCC和异步确保方案的实现...

KerryWu4阅读 6.8k评论 2

《Lua-in-ConTeXt》04:卡片
ConTeXt 输出的 PDF 文件,其页面尺寸默认与 A4 纸的尺寸相同。在今后,我可能要给出很多的排版示例。若给出排版结果的全貌,则 A4 尺寸太大了,会导致示例截图里的文字不够清晰。倘若能将页面尺寸设置为卡片大小...

garfileo阅读 980

《Lua-in-ConTeXt》01:Hello world!
ConTeXt,我不厌其烦地打出它的大小写字母,它的意思既非「上下文」,亦非「语境」或「环境」,而是 Text with TeX 的意思。这是荷兰人 Hans Hagen 为自己创造的一个 TeX 宏包而取的一个失败的英文名字。

garfileo阅读 917评论 2

《Lua-in-ConTeXt》02:ConTeXt 计算机
用于编写 TeX 源文件(例如 hello.tex)的任何一种文本编辑器,都可视为「ConTeXt 计算机」的终端。context 命令可将 TeX 源文件里的内容输出到 PDF 文件,于是可将 PDF 文件视为 ConTeXt 计算机的显示器。

garfileo阅读 885

《Lua-in-ConTeXt》05:时间戳
上一篇:卡片 我要在卡片的页脚区域增加时间戳,例如 {代码...} 然而,迄今为止,尚未介绍如何让 ConTeXt 支持汉字。汉字,一直是 TeX 世界之痛。与仅需要几十个字母的拼音文字不同,汉字要成千上万个字符。为汉...

garfileo阅读 870

《Lua-in-ConTeXt》06:伪竖排
在我的浅薄的审美范畴里,card.pdf 的页码没有在页脚(footer)的留白(Margin)区域居中,甚为不美。然而,card-env.tex 里的

garfileo阅读 737

5.9k 声望
1.9k 粉丝
宣传栏