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 fontspec
pacote é carregado; se fontspec
não estiver carregado, nenhum dos problemas descritos aqui ocorrerá. Obviamente, perguntando aos potenciais usuários do meu pacotenãocarregar fontspec
nã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-- fontspec
está 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
\footnote
e\section
- uma palavra que precede imediatamente o início de um ambiente como
enumerate
eitemize
- a palavra final de uma
\item
declaração, ou seja, a palavra final antes da próxima declaração e/ou diretriz\item
final 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
fontspec
pacakge (ou algum pacote carregado porfontspec
)?Existe uma maneira de carregar
fontspec
(ou alguns dos pacotes chamados porfontspec
) para suprimir a interferência com meu código lua?Ou descobri um bug
fontspec
(ou um ou mais pacotes carregados porfontspec
) que precisa ser corrigido de qualquer maneira?
% !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.next
será igual a nil
. Finalmente, as duas if
condições restantes - t.id==kern
e 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 ligaturing
repetidamente, 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:
com o detalhe (canto superior direito)
enquanto o nodelist "bom" se parece com isto:
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 glue
ativa o processamento da ligadura.
Eu sugeriria usar um tipo diferente de loop para processamento de ligaduras.
A diferença entre fontspec
ativado 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 fish
e 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.