Má interação entre fontspec e algum código lua que executa pesquisas relacionadas a strings e operações de substituição

Má interação entre fontspec e algum código lua que executa pesquisas relacionadas a strings e operações de substituição

Atualização 28/05/2013: Oselnoligo pacote agora está no CTAN. Comentários e críticas são sempre bem vindos! Se desejar entrar em contato comigo sobre qualquer aspecto do pacote, use o endereço de e-mail fornecido na parte inferior da página de título do guia do usuário do pacote. (Em relação ao estado do pacote descrito na pergunta abaixo, consegui eliminar pelo menos um bug e sugiro melhores soluções alternativas para os bugs restantes - pelo menos aqueles que conheço!)


Estou no processo de preparar um pacote LuaLaTeX para lançamento "oficial" no CTAN, mas primeiro preciso eliminar alguns bugs restantes. O bug descrito nesta pergunta diz respeito ao comportamento incorreto do meu pacote que ocorreseo fontspecpacote é carregado; se fontspecnão estiver carregado, nenhum dos problemas descritos aqui ocorrerá. Obviamente, perguntando aos potenciais usuários do meu pacotenãocarregar fontspecnão é uma opção. Aliás, a string identificadora da versão LuaTeX no meu sistema é " beta-0.70.2-2012062819", distribuída com o MacTeX2012. Para obter muito mais informações sobre o pacote completo selnolig, que faz a supressão automatizada e seletiva de ligaduras tipográficas, consulteNovo pacote, selnolig, que automatiza a supressão de ligaduras tipográficas.

O MWE (veja a imagem abaixo e o código fornecido abaixo da imagem) ilustra vários casos de falha na realização da supressão da ligadura se - e aparentemente tambémsomente se-- fontspecestá carregado. Especificamente, a supressão da ligadura falha em:

  • uma palavra seguida imediatamente por um %sinal de comentário ( )
  • a última palavra no argumento de um comando como \footnotee\section
  • uma palavra que precede imediatamente o início de um ambiente como enumerateeitemize
  • a palavra final de uma \itemdeclaração, ou seja, a palavra final antes da próxima declaração e/ou diretriz \itemfinal do meio ambiente\end{enumerate/itemize}

Um tema comum desses problemas é que eles ocorrem se a palavra em questão (mais quaisquer caracteres de pontuação finais) estiver no final de algum ambiente, grupo ou argumento de alguma macro. Em todos os casos, uma espécie de "remédio" é inserir um espaço, uma linha em branco ou um espaço mais algo como \vphantom{x}[!]. Claramente, esses remédios sãonãosoluções reais, mas apenas hacks desajeitados, e eu certamente não pensaria em pedir aos usuários do meu pacote que implementassem esses hacks.

Minhas perguntasSão então:

  • Como posso tornar meu código lua mais robusto para tudo o que está sendo feito pelo fontspecpacakge (ou algum pacote carregado por fontspec)?

  • Existe uma maneira de carregar fontspec(ou alguns dos pacotes chamados por fontspec) para suprimir a interferência com meu código lua?

  • Ou descobri um bug fontspec(ou um ou mais pacotes carregados por fontspec) que precisa ser corrigido de qualquer maneira?

insira a descrição da imagem aqui

% !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}

Conteúdo de 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) 

Pós-escrito: A solução para o bug descrito nesta postagem. A sequência de teclas no código lua fornecida acima é a que causou o bug:

  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
        ...

Tudo o que foi necessário para corrigir o bug foi alterar este trecho de código para:

  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
        ...

A questão é que a sequência de caracteres que precisa ser processada pelo selnolig pode terminar de outras maneiras além de apenas com alguma quantidade de "cola" (TeX) (por exemplo, espaço em branco). Outra forma de a sequência terminar se a palavra for o último item a ser processado, por exemplo, se for a última palavra no argumento de um comando como \section{}; se for esse o caso, a variável t.nextserá igual a nil. Finalmente, as duas ifcondições restantes - t.id==kerne t.id==rule- são fornecidas caso um usuário tenha inserido um item "kern" ou "rule" manualmente.

A correção do bug está incorporada na versão 0.220 do pacote.

Responder1

Deixe-me tentar analisar o problema: você chama ligaturingrepetidamente, mas às vezes o retorno de chamada não parece ter nenhum efeito. Gostaria de dar uma olhada em dois casos: as notas de rodapé:

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

Vou dar uma olhada na lista de nós que é passada para os retornos de chamada de ligadura com meu pequeno módulo lista de viznode.

Eu mudo ligeiramente o código lua no ponto de entrada para 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))

A primeira nota de rodapé ('ruim') tem esta aparência:

lista de nós, completa

com o detalhe (canto superior direito)

insira a descrição da imagem aqui

enquanto o nodelist "bom" se parece com isto:

insira a descrição da imagem aqui

Agora olhando o código:

  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

deixa claro que apenas a glueativa o processamento da ligadura.

Eu sugeriria usar um tipo diferente de loop para processamento de ligaduras.

A diferença entre fontspecativado ou não é a seguinte: com o fontspec desativado, o retorno de chamada da ligadura desativa todas as ligaduras. O que você vê não é o efeito do comando \nolig, mas um modo geral "sem ligadura". Experimente palavras como fluffiest fishe você verá isso. Com o fontspec ativado, o resultado é "sempre ligaduras", a menos que você as bloqueie com o código que usa.

Portanto, receio que o retorno de chamada da ligadura não seja a maneira perfeita de lidar com a situação. No entanto, você pode ligar node.ligaturing()no início do retorno de chamada e depois fazer o que está fazendo. Mas isso provavelmente interferiria no fontspec.

informação relacionada