Обновление 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}
[!]. Очевидно, что эти средстванетреальные решения, а просто неуклюжие хаки, и я бы определенно не стал просить пользователей моего пакета реализовать эти хаки.
Мои вопросытогда:
Как сделать мой код lua более устойчивым к действиям пакета
fontspec
(или какого-либо пакета, загруженного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
будет равна 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
ясно, что только a glue
активирует обработку лигатуры.
Я бы предложил использовать другой вид петли для обработки лигатуры.
Разница между fontspec
активированным и неактивным режимом заключается в следующем: при деактивированном fontspec обратный вызов лигатуры отключает всю лигатуру. То, что вы видите, — это не эффект команды \nolig
, а общий режим «без лигатуры». Попробуйте слова типа fluffiest fish
, и вы увидите это. При включенном fontspec результат — «всегда лигатуры», если вы не заблокируете их с помощью используемого вами кода.
Так что, боюсь, обратный вызов лигатуры — не лучший способ справиться с ситуацией. Однако вы могли бы вызвать node.ligaturing()
в начале обратного вызова, а затем сделать то, что вы делаете. Но это, вероятно, помешает fontspec.