《Lua-in-ConTeXt》10:緩衝區魔法

garfileo發表於2023-02-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 緩衝區裡,會覺得自己是個強大的巫師,舉手投足,便是黑魔法。

下一篇:原始碼彩化

參考

相關文章