LuaLaTeX:如何在 string.gsub 函数中使用 \char 指令?

LuaLaTeX:如何在 string.gsub 函数中使用 \char 指令?

考虑以下 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.charLua 函数中有直接插入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字形。

相关内容