fontspec 与执行字符串相关搜索和替换操作的一些 lua 代码之间的交互不良

fontspec 与执行字符串相关搜索和替换操作的一些 lua 代码之间的交互不良

更新 2013/05/28: 这塞尔诺利格软件包现已在 CTAN 上。欢迎提出意见和批评!如果您希望就软件包的任何方面与我联系,请使用软件包用户指南标题页底部提供的电子邮件地址。(相对于下面问题中描述的软件包状态,我已设法修复至少一个错误,并建议对剩余的错误采取更好的解决方法——至少是我知道的错误!)


我正在准备将 LuaLaTeX 软件包“正式”发布到 CTAN,但我需要先修复一些剩余的错误。此问题中描述的错误与我的软件包的错误行为有关,这些错误发生在如果fontspec已加载;如果fontspec没有加载,则不会发生此处描述的问题。显然,询问我的包的潜在用户不是加载fontspec不是一个选项。顺便说一句,我的系统上 LuaTeX 版本的标识符字符串是“ beta-0.70.2-2012062819”,与 MacTeX2012 一起分发。有关完整包的更多信息selnolig(它可以自动、选择性地抑制印刷连字),请参阅新的软件包 selnolig,可以自动抑制印刷连字

MWE(见下图,以及下图提供的代码)说明了几个未能执行连字符抑制的情况——显然也除非--fontspec已加载。具体来说,连字抑制因以下原因而失败:

  • 紧接着注释符号 ( %) 的单词
  • 命令参数中的最后一个词,\footnote例如\section
  • 紧接在环境开始之前的单词,enumerate例如itemize
  • 语句的最后一个字\item,即下一个\item语句之前的最后一个字和/或环境的结束\end{enumerate/itemize}指令

这些问题的一个共同点是,如果所讨论的单词(加上任何尾随标点符号)位于某个环境、组或某个宏的参数的最末尾,就会发生这些问题。在所有情况下,一种“补救措施”是插入空格、空白行或空格加类似\vphantom{x}[!] 的内容。显然,这些补救措施是不是真正的解决方案,而只是笨拙的黑客攻击,我当然不会考虑要求我的软件包的用户去实施这些黑客攻击。

我的问题那么:

  • fontspec我怎样才能使我的 lua 代码对包(或由 加载的某些包)所做的任何事情更加健壮fontspec

  • 有没有办法加载fontspec(或调用的一些包fontspec)来抑制对我的lua代码的干扰?

  • fontspec或者,我是否发现了一个(或一个或多个加载的软件包)错误fontspec,需要修复?

在此处输入图片描述

% !TEX TS-program = lualatex
\documentclass{article}
% If the next line is commented out, everything works fine!
\usepackage{fontspec} 

  \RequirePackage{luatexbase,luacode,expl3}
  % Load lua code
  \directlua{  require("ld-orig.lua")  }  % see below for contents of ld-orig.lua
  % Define the user macro "nolig"
  \providecommand\nolig[2]{ \directlua{
        suppress_liga( "\luatexluaescapestring{#1}",
                       "\luatexluaescapestring{#2}" )}}
  % Provide a ligature suppression rule
  %  (the full package obviously provides many more such macros)
  \nolig{lfful}{lf|ful} % shelfful -> shelf|ful

% Just for this MWE:
   \usepackage[textheight=8cm]{geometry}
   \setlength\parindent{0pt}
   \pagestyle{empty} 

\begin{document}    
Two shelffuls of \TeX-related books: it works!

\bigskip
% word to be de-ligated is followed immediately by % (comment character)
Ligature suppression doesn't work here: shelfful% 

% leaving a space between word and % makes it work even if fontspec is loaded
But it does work in this case: shelfful % 

\bigskip
bad\footnote{This doesn't work: shelfful.} % w/o space 
good\footnote{But this does work: shelfful. \vphantom{x}} % w/ space and \vphantom directive

\bigskip
% Two more problem cases: (i) last word before start of an
% itemize/enumerate environment, (ii) last word of an \item
one shelfful, two shelffuls % no ligature suppression for "shelffuls"
\begin{itemize}
\item shelfful              % no ligature suppression here either
\item shelfful \vphantom{x} % inserting space and \vphantom does the trick...
\end{itemize}

% problem also occurs in arguments of sectioning commands
\section*{sad shelfful}         % again no ligature suppression
\subsection*{happy shelfful }   % adding space at end of argument makes it work!
\end{document}

ld-orig.lua 的内容:

--- Credits to Patrick Gundlach, Taco Hoekwater, and Steffen Hildebrandt!
local glyph = node.id('glyph')
local glue = node.id("glue")
local whatsit = node.id("whatsit")
local userdefined

for n,v in pairs(node.whatsits()) do
  if v == 'user_defined' then userdefined = n end
end

local identifier = 123456  -- any unique identifier 
local noliga={}
debug=false  -- default: don't write debugging info to log file

function debug_info(s)
  if debug then
    texio.write_nl(s)
  end
end

local blocknode = node.new(whatsit, userdefined)
blocknode.type = 100
blocknode.user_id = identifier

function process_ligatures(nodes,tail)
  local s={}
  local current_node=nodes
  local build_liga_table =  function(strlen,t)
    local p={}
    for i = 1, strlen do
      p[i]=0
    end
    for k,v in pairs(t) do
      -- debug_info("Match: "..v[3])
      local c= string.find(noliga[v[3]],"|")
      local correction=1
      while c~=nil do
         -- debug_info("Position "..(v[1]+c))
         p[v[1]+c-correction] = 1
         c = string.find(noliga[v[3]],"|",c+1)  
         correction=correction+1
      end   
    end
    -- debug_info("Liga table: "..table.concat(p, ""))
    return p
  end
  local apply_ligatures=function(head,ligatures)
     local i=1
     local hh=head
     local last=node.tail(head)
     for curr in node.traverse_id(glyph,head) do
       if ligatures[i]==1 then
         -- debug_info("Current glyph: "..unicode.utf8.char(curr.char))
         node.insert_before(hh,curr, node.copy(blocknode))
         hh=curr
       end 
       last=curr
       if i==#ligatures then 
         -- debug_info("Leave node list on position: "..i)
         break 
       end
       i=i+1
     end
     if(last~=nil) then
       -- debug_info("Last char: "..unicode.utf8.char(last.char))
     end--]]
  end
  for t in node.traverse(nodes) do
    if t.id==glyph then

      s[#s+1]=string.lower(unicode.utf8.char(t.char))
    elseif t.id== glue then
      local f=string.gsub(table.concat(s,""),"[\\?!,\\.]+","") -- add all interpunction
      local throwliga={}    
      for k, v in pairs(noliga) do
        local count=1
        local match= string.find(f,k)
        while match do
          count=match
          -- debug_info("pattern match: "..f .." - "..k)  
          local n = match + string.len(k)-1
          table.insert(throwliga,{match,n,k})
          match= string.find(f,k,count+1)
        end
      end
      if #throwliga==0 then 
        -- debug_info("No ligature substitution for: "..f)  
      else
        -- debug_info("Do ligature substitution for: "..f)  
        local ligabreaks=build_liga_table(f:len(),throwliga)
        apply_ligatures(current_node,ligabreaks)
      end
      s={}
      current_node=t
    end    
  end
end

function suppress_liga(s,t)
  noliga[s]=t
end

function drop_special_nodes (nodes,tail)
  for t in node.traverse(nodes) do
     if t.id == whatsit and t.subtype == userdefined and t.user_id == identifier then
        node.remove(nodes,t)
        node.free(t)
     end
  end
end

luatexbase.add_to_callback("ligaturing", process_ligatures,"Filter ligatures", 1) 

后记:此贴中描述了该漏洞的解决方案。上面给出的 lua 代码中导致该漏洞的关键序列是:

  for t in node.traverse(nodes) do
     if t.id==glyph then
        s[#s+1]=string.lower(unicode.utf8.char(t.char))
     elseif t.id==glue then
        ...

修复该错误所需要做的就是将此代码片段更改为:

  for t in node.traverse(nodes) do
     if t.id==glyph then
        s[#s+1]=string.lower(unicode.utf8.char(t.char))
     end
     if ( t.id==glue or t.next==nil or t.id==kern or t.id==rule ) then
        ...

关键在于,需要由 selnolig 处理的字符序列可以以其他方式结束,而不仅仅是使用一定量的 (TeX)“胶水”(例如空格)。如果单词是正在处理的最后一个项目,则序列结束的另一种方式是,例如,如果它是命令(如 )的参数中的最后一个单词\section{};如果是这种情况,变量t.next将等于。最后,如果用户手动插入了“kern”或“rule”项目,则提供nil剩下的两个if条件 --t.id==kern和--。t.id==rule

该错误修复已包含在软件包的 0.220 版本中。

答案1

让我试着分析一下这个问题:你一遍又一遍地调用ligaturing,但有时回调似乎没有任何效果。我想看看两种情况:脚注:

bad\footnote{This doesn't work: shelfful.} % w/o space 
good\footnote{But this does work: shelfful. \vphantom{x}} % w/ space and \vphantom directive

我将使用我的小模块查看传递给连接回调的节点列表 节点列表

我在入口处稍微改变了lua代码process_ligatures()

...
require("viznodelist")
function process_ligatures(nodes,tail)
  counter = counter or 0
  counter = counter + 1
  viznodelist.nodelist_visualize(nodes,string.format("liga%d.gv",counter))

第一个脚注(“坏”)如下所示:

节点列表,完成

附有细节(右上)

在此处输入图片描述

而“好的”节点列表如下所示:

在此处输入图片描述

现在看一下代码:

  for t in node.traverse(nodes) do
    if t.id==glyph then

      s[#s+1]=string.lower(unicode.utf8.char(t.char))
    elseif t.id== glue then
    ...
      (process ligatures)
    ...
    end
  end

明确指出只有 aglue才能激活连字符处理。

我建议使用不同类型的循环进行连字符处理。

激活与否的区别fontspec如下:如果禁用 fontspec,则连字回调会禁用所有连字。您看到的不是命令的效果\nolig,而是一般的“无连字”模式。尝试使用类似fluffiest fish这样的单词,您会看到。如果启用 fontspec,结果是“始终连字”,除非您使用代码阻止它们。

因此,恐怕连接回调并不是处理这种情况的完美方法。不过,您可以node.ligaturing()在回调开始时调用,然后执行您正在做的事情。但这可能会干扰 fontspec。

相关内容