Mala interacción entre fontspec y algún código lua que realiza operaciones de búsqueda y reemplazo relacionadas con cadenas

Mala interacción entre fontspec y algún código lua que realiza operaciones de búsqueda y reemplazo relacionadas con cadenas

Actualización 28/05/2013: ElselnoligEl paquete está ahora en el CTAN. ¡Comentarios y críticas siempre bienvenidos! Si desea ponerse en contacto conmigo sobre cualquier aspecto del paquete, utilice la dirección de correo electrónico que figura en la parte inferior de la página de título de la guía del usuario del paquete. (En relación con el estado del paquete descrito en la pregunta siguiente, logré solucionar al menos un error y sugiero mejores soluciones para los errores restantes, ¡al menos los que conozco!)


Estoy en el proceso de preparar un paquete LuaLaTeX para su lanzamiento "oficial" en CTAN, pero primero necesito corregir algunos errores restantes. El error descrito en esta pregunta se refiere al comportamiento incorrecto de mi paquete que ocurresiel fontspecpaquete está cargado; si fontspecno está cargado, no se produce ninguno de los problemas descritos aquí. Obviamente, preguntar a los usuarios potenciales de mi paquetenocargar fontspecno es una opción. Por cierto, la cadena de identificación de la versión de LuaTeX en mi sistema es " beta-0.70.2-2012062819", distribuida con MacTeX2012. Para obtener mucha más información sobre el selnoligpaquete completo, que realiza la supresión selectiva y automatizada de ligaduras tipográficas, consulteNuevo paquete, selnolig, que automatiza la supresión de ligaduras tipográficas.

El MWE (ver imagen a continuación y el código proporcionado debajo de la imagen) ilustra varios casos de falla al realizar la supresión de ligadura si, y aparentemente tambiénsólo si-- fontspecestá cargado. Específicamente, la supresión de ligaduras falla en:

  • una palabra seguida inmediatamente por un %signo de comentario ( )
  • la última palabra en el argumento de un comando como \footnotey\section
  • una palabra que precede inmediatamente al inicio de un entorno como enumerateyitemize
  • la última palabra de una \itemdeclaración, es decir, la última palabra antes de la siguiente declaración y/o la directiva \itemde cierre del entorno\end{enumerate/itemize}

Un tema común de estos problemas es que ocurren si la palabra en cuestión (más cualquier carácter de puntuación final) está al final de algún entorno, grupo o argumento de alguna macro. En todos los casos, una especie de "remedio" es insertar un espacio, una línea en blanco o un espacio más algo como \vphantom{x}[!]. Claramente, estos remedios sonnosoluciones reales pero simplemente trucos torpes, y ciertamente no consideraría pedir a los usuarios de mi paquete que implementen estos trucos.

Mis preguntasson entonces:

  • ¿Cómo puedo hacer que mi código lua sea más robusto para lo que sea que esté haciendo el fontspecpaquete (o algún paquete cargado fontspec)?

  • ¿Hay alguna manera de cargar cualquiera de fontspeclos paquetes (o algunos de los paquetes llamados por fontspec) para suprimir la interferencia con mi código lua?

  • ¿O he descubierto un error en fontspec(o en uno o más de los paquetes cargados por fontspec) que debe corregirse de todos modos?

ingrese la descripción de la imagen aquí

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

Contenidos 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) 

Posdata: La solución al error descrito en esta publicación. La secuencia de claves en el código lua proporcionado anteriormente es la que causó el error:

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

Todo lo que se necesitaba para corregir el error es cambiar este fragmento de código a:

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

El punto es que la secuencia de caracteres que necesita ser procesada por selnolig puede terminar de otra manera que simplemente con una cierta cantidad de "pegamento" (TeX) (por ejemplo, espacios en blanco). Otra forma de terminar la secuencia si la palabra es el último elemento que se procesa, por ejemplo, si es la última palabra en el argumento de un comando como \section{}; si ese es el caso, la variable t.nextserá igual a nil. Finalmente, las dos ifcondiciones restantes ( t.id==kerny t.id==rule) se proporcionan en caso de que un usuario haya insertado un elemento "kern" o "regla" manualmente.

La corrección de errores está incorporada en la versión 0.220 del paquete.

Respuesta1

Déjame intentar analizar el problema: llamas ligaturinguna y otra vez, pero a veces la devolución de llamada no parece tener ningún efecto. Me gustaría echar un vistazo a dos casos: las notas a pie de página:

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

Echaré un vistazo a la lista de nodos que se pasa a las devoluciones de llamada de ligadura con mi pequeño módulo. viznodelista.

Cambio ligeramente el código lua en el punto de entrada a 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))

La primera nota al pie ("mala") se ve así:

lista de nodos, completa

con el detalle (arriba a la derecha)

ingrese la descripción de la imagen aquí

mientras que la lista de nodos "buena" se ve así:

ingrese la descripción de la imagen aquí

Ahora mirando el 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

deja claro que sólo un glueactiva el procesamiento de ligadura.

Sugeriría usar un tipo diferente de bucle para el procesamiento de ligaduras.

La diferencia entre fontspecactivado o no es la siguiente: con fontspec desactivado, la devolución de llamada de ligadura desactiva todas las ligaduras. Lo que ves no es el efecto del comando \nolig, sino un modo general "sin ligadura". Prueba con palabras como fluffiest fishy lo verás. Con fontspec habilitado, el resultado es "siempre ligaduras" a menos que las bloquee con el código que usa.

Entonces, me temo que la devolución de llamada de ligadura no es la manera perfecta de lidiar con la situación. Sin embargo, puedes llamar node.ligaturing()al comienzo de la devolución de llamada y luego hacer lo que estás haciendo. Pero eso probablemente interferiría con la especificación de fuente.

información relacionada