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 fontspec
paquete está cargado; si fontspec
no está cargado, no se produce ninguno de los problemas descritos aquí. Obviamente, preguntar a los usuarios potenciales de mi paquetenocargar fontspec
no 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 selnolig
paquete 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-- fontspec
está 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
\footnote
y\section
- una palabra que precede inmediatamente al inicio de un entorno como
enumerate
yitemize
- la última palabra de una
\item
declaración, es decir, la última palabra antes de la siguiente declaración y/o la directiva\item
de 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
fontspec
paquete (o algún paquete cargadofontspec
)?¿Hay alguna manera de cargar cualquiera de
fontspec
los paquetes (o algunos de los paquetes llamados porfontspec
) 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 porfontspec
) que debe corregirse de todos modos?
% !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.next
será igual a nil
. Finalmente, las dos if
condiciones restantes ( t.id==kern
y 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 ligaturing
una 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í:
con el detalle (arriba a la derecha)
mientras que la lista de nodos "buena" se ve así:
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 glue
activa el procesamiento de ligadura.
Sugeriría usar un tipo diferente de bucle para el procesamiento de ligaduras.
La diferencia entre fontspec
activado 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 fish
y 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.