考虑以下 MWE:
% !TEX TS-program = lualatex
\documentclass{article}
\usepackage{fontspec}
\setmainfont[Ligatures=NoCommon]{Latin Modern Roman}
\usepackage{luacode,luatexbase}
\begin{luacode}
function dosub ( s )
s = string.gsub ( s , 'ff', '\\char64256{}')
return ( s )
end
--luatexbase.add_to_callback ( "process_input_buffer", dosub, "dosub" )
\end{luacode}
\begin{document}
off \directlua{ tex.sprint ( dosub ( \luastring{off} ) ) } off
\end{document}
代码的核心是函数dosub
,它使用 Lua 函数string.gsub
。它被设置为用ff
包含 的字形替换 的实例ff
结扎。(你必须相信我,对于手头的字体,ff-ligature 字形位于“slot”64256。)请注意,目前,指令luatexbase.add_to_callback
指令被注释掉。(--
(双破折号)字符串启动 Lua 注释。)
运行此 MWE 时,将获得:
\directlua
观察发现,通过调用生成的中间单词dosub
正确包含ff
-ligature,而第一个和第三个单词不包含(再次正确,因为自动连字生成被禁用)。
当我取消注释该指令时,问题就开始了
luatexbase.add_to_callback ( "process_input_buffer", dosub, "dosub" )
重新编译后,出现以下相当难以理解的错误消息:
(/usr/local/texlive/2015/texmf-dist/tex/context/base/supp-pdf.mkii
[正在加载 MPS 至 PDF 转换器(版本 2006.09.02)。]
\scratchcounter=\count290
\scratchdimen=\dimen261
\scratchbox=\box256
!缺失数字,视为零。
\让
l.275 \let
\pdflastform=\pdflastxform
?
\char
我怀疑这与函数替换字符串部分中TeX 宏 -- -- 的存在有些关系string.gsub
。也就是说,如果我'\\char64256{}'
用gg
(即常量字符串) 替换,则不会生成任何错误消息 (并且文档正文中的三个“ff”实例会自动替换为“gg”)。
我是否需要以某种特殊方式“包装”或“保护” TeX 宏,以便成功使用“luatexbase.add_to_callback”?我还应该做些什么?关于我的计算设置:我在运行 MacOSX 10.10.5“Yosemite”的 MacBookPro 上运行 MacTeX2015(应用了今天早上的所有可用更新)。
答案1
unicode.utf8.char
Lua 函数中有直接插入unicode字符的函数:
% !TEX TS-program = lualatex
\documentclass{article}
\usepackage{fontspec}
\setmainfont[Ligatures=NoCommon]{Latin Modern Roman}
\usepackage{luacode,luatexbase}
\begin{luacode}
local uchar = unicode.utf8.char
function dosub ( s )
s = string.gsub ( s , 'ff', uchar(64256))
return ( s )
end
\end{luacode}
\AtBeginDocument{%
\luaexec{luatexbase.add_to_callback ( "process_input_buffer", dosub, "dosub" )}%
}
\begin{document}
off \directlua{ tex.sprint ( dosub ( \luastring{off} ) ) } off
\end{document}
但是代码中的主要问题是回调插入得太早,它可能会替换ff
加载的某些宏中的字符\AtBeginDocument
。因此,另一种解决方案是也插入回调\AtBeginDocument
,这样可以降低此类冲突的风险(即使在第一种方法中也应该这样做):
% !TEX TS-program = lualatex
\documentclass{article}
\usepackage{fontspec}
\setmainfont[Ligatures=NoCommon]{Latin Modern Roman}
\usepackage{luacode,luatexbase}
\begin{luacode}
function dosub ( s )
s = string.gsub ( s , 'ff', '\\char64256{}')
return ( s )
end
\end{luacode}
\AtBeginDocument{%
\luaexec{luatexbase.add_to_callback ( "process_input_buffer", dosub, "dosub" )}%
}
\begin{document}
off \directlua{ tex.sprint ( dosub ( \luastring{off} ) ) } off
\end{document}
编辑:
还有另一个问题,如果你的文档主体ff
在名称中包含一些宏怎么办?为了解决这个问题,我们可以使用这样的函数:
\begin{luacode}
local uchar = unicode.utf8.char
function dosub ( s )
local x = s:gsub('(\\?)([%a%@]+)', function(back,text)
if back~="" then
return back .. text
end
return text:gsub ( 'ff', uchar(64256))
end)
print("x", x)
return x
end
luatexbase.add_to_callback ( "process_input_buffer", dosub, "dosub" )
\end{luacode}
我们s:gsub('(\\?)([%a%@]+)', function(back,text)
捕获所有单词,包括宏。如果变量back
不是空字符串,则当前单词是宏,我们需要将其返回而不进行处理。否则,我们可以应用ff
替换正则表达式。
请注意,在这种情况下,add_to_callback
不使用AtBeginDocument
,因为当\offer
在序言中定义宏时,其文本不会被替换。因为我们现在跳过了宏,所以这应该无关紧要。
最后我想补充一点,正是由于宏的这些问题,节点处理回调对于这种类型的黑客来说要好得多。
例如以下代码:
local uchar = unicode.utf8.char
local fchar = string.byte("f")
local glyph_id = node.id("glyph")
local glue_id = node.id("glue")
local function next_status(n, node_table)
local node_table = node_table or {}
table.insert(node_table, n)
if not n then return false end
if n.id == glyph_id and n.char == fchar then
return true, node_table
elseif n.id == glyph_id or n.id == glue_id then
return false
else
return next_status(n.next, node_table)
end
end
local function node_dosub(nodes)
for n in node.traverse(nodes) do
if n.id == glyph_id and n.char == fchar then
local next, node_table = next_status(n.next)
if next == true then
n.char = 64256
for _, x in ipairs(node_table) do
node.remove(nodes, x)
end
end
end
end
return nodes
end
luatexbase.add_to_callback ( "pre_linebreak_filter", node_dosub, "node_dosub" )
它更复杂,因为我们不能在字符串级别进行操作,而是在单个节点上进行操作。存在很多节点类型,glyph
具有 37 个节点node.id
对我们来说很重要。每个字形节点都有char
一个字段,用于保存字符代码。当f
找到带有字符的字形时,我们会查看下一个节点以查找此字形旁边是否有另一个f
字形。当找到时,我们用ff
连字代码替换当前字符并删除下一个f
字形。