Schlechte Interaktion zwischen Fontspec und einem Lua-Code, der stringbezogene Such- und Ersetzungsvorgänge ausführt

Schlechte Interaktion zwischen Fontspec und einem Lua-Code, der stringbezogene Such- und Ersetzungsvorgänge ausführt

Aktualisierung 28.05.2013: DerAbonnierenDas Paket ist jetzt auf dem CTAN. Kommentare und Kritik sind immer willkommen! Wenn Sie mich zu irgendeinem Aspekt des Pakets kontaktieren möchten, verwenden Sie bitte die E-Mail-Adresse, die unten auf der Titelseite des Benutzerhandbuchs des Pakets angegeben ist. (Im Verhältnis zum Status des Pakets, der in der folgenden Frage beschrieben wird, ist es mir gelungen, mindestens einen Fehler zu beheben, und ich schlage bessere Workarounds für die verbleibenden Fehler vor – zumindest für die, die mir bekannt sind!)


Ich bin gerade dabei, ein LuaLaTeX-Paket für die "offizielle" Veröffentlichung im CTAN vorzubereiten, aber ich muss zuerst noch ein paar verbleibende Fehler beheben. Der in dieser Frage beschriebene Fehler betrifft ein fehlerhaftes Verhalten meines Pakets, das auftrittWenndas fontspecPaket ist geladen; wenn fontspeces nicht geladen ist, tritt keines der hier beschriebenen Probleme auf. Offensichtlich ist es so, dass ich potenzielle Benutzer meines Pakets frage,nichtzu laden fontspecist keine Option. Übrigens ist die Identifikationszeichenfolge der LuaTeX-Version auf meinem System " beta-0.70.2-2012062819", die mit MacTeX2012 verteilt wird. Weitere Informationen zum vollständigen selnoligPaket, das automatisch selektive Unterdrückung typografischer Ligaturen durchführt, finden Sie unterNeues Paket, selnolig, das die Unterdrückung typografischer Ligaturen automatisiert.

Das MWE (siehe Bild unten und Code unter dem Bild) zeigt mehrere Fälle, in denen die Ligaturunterdrückung fehlgeschlagen ist, wenn - und anscheinend auchnur wenn-- fontspecwird geladen. Insbesondere schlägt die Ligaturunterdrückung fehl bei:

  • ein Wort, auf das unmittelbar ein Kommentarzeichen ( %) folgt
  • das letzte Wort im Argument eines Befehls wie \footnoteund\section
  • ein Wort, das unmittelbar vor dem Beginn einer Umgebung steht, wie z . B. enumerateunditemize
  • das letzte Wort einer \itemAnweisung, d. h. das letzte Wort vor der nächsten \itemAnweisung und/oder der schließenden \end{enumerate/itemize}Direktive der Umgebung

Ein gemeinsames Thema dieser Probleme ist, dass sie auftreten, wenn das fragliche Wort (plus alle nachfolgenden Satzzeichen) ganz am Ende einer Umgebung, Gruppe oder eines Arguments eines Makros steht. In allen Fällen besteht eine Art „Abhilfe“ darin, entweder ein Leerzeichen, eine Leerzeile oder ein Leerzeichen plus etwas wie \vphantom{x}[! einzufügen. Offensichtlich sind diese Abhilfennichtechte Lösungen, sondern bloß plumpe Hacks, und ich käme sicher nicht auf die Idee, die Benutzer meines Pakets zu bitten, diese Hacks zu implementieren.

Meine Fragensind, dann:

  • Wie kann ich meinen Lua-Code robuster gegenüber allem machen, was vom fontspecPaket (oder einem von geladenen Paket fontspec) gemacht wird?

  • Gibt es eine Möglichkeit, eines davon fontspec(oder einige der von aufgerufenen Pakete fontspec) zu laden, um die Störungen meines Lua-Codes zu unterdrücken?

  • Oder habe ich einen Fehler in fontspec(oder einem oder mehreren der von geladenen Pakete fontspec) entdeckt, der sowieso behoben werden muss?

Bildbeschreibung hier eingeben

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

Inhalt von 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) 

Nachtrag: Die Lösung für den in diesem Beitrag beschriebenen Fehler. Die Tastenfolge im oben angegebenen Lua-Code, die den Fehler verursacht hat, war:

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

Um den Fehler zu beheben, musste lediglich dieser Codeausschnitt wie folgt geändert werden:

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

Der Punkt ist, dass die Zeichenfolge, die von selnolig verarbeitet werden muss, auch anders enden kann als nur mit einer gewissen Menge (TeX-) „Klebstoff“ (z. B. Leerzeichen). Eine andere Möglichkeit für das Ende der Zeichenfolge besteht darin, dass das Wort das allerletzte zu verarbeitende Element ist, z. B. wenn es das letzte Wort im Argument eines Befehls wie ist \section{}; wenn dies der Fall ist, t.nextist die Variable gleich nil. Schließlich werden die beiden verbleibenden ifBedingungen – t.id==kernund t.id==rule– bereitgestellt, falls ein Benutzer ein „Kern“- oder „Rule“-Element manuell eingefügt hat.

Die Fehlerbehebung ist in Version 0.220 des Pakets integriert.

Antwort1

Ich versuche mal das Problem zu analysieren: Man ruft ligaturingimmer wieder auf, aber manchmal scheint der Rückruf keine Wirkung zu haben. Ich möchte mir zwei Fälle ansehen: die Fußnoten:

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

Ich werde mir die Knotenliste ansehen, die mit meinem kleinen Modul an die Ligaturierungs-Callbacks übergeben wird vizindel.

Ich ändere den Lua-Code am Eingangspunkt leicht zu 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))

Die erste Fußnote („schlecht“) sieht folgendermaßen aus:

Knotenliste,komplett

mit dem Detail (oben rechts)

Bildbeschreibung hier eingeben

während die „gute“ Knotenliste folgendermaßen aussieht:

Bildbeschreibung hier eingeben

Schauen wir uns nun den Code an:

  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

macht deutlich, dass nur a gluedie Ligaturverarbeitung aktiviert.

Ich würde vorschlagen, für die Ligaturverarbeitung eine andere Art von Schleife zu verwenden.

Der Unterschied zwischen fontspecaktiviert und nicht aktiviert ist der folgende: Wenn Fontspec deaktiviert ist, deaktiviert der Ligaturierungs-Callback alle Ligaturierungen. Was Sie sehen, ist nicht die Wirkung des Befehls \nolig, sondern ein allgemeiner „keine Ligatur“-Modus. Versuchen Sie Wörter wie fluffiest fishund Sie sehen das. Wenn Fontspec aktiviert ist, ist das Ergebnis „immer Ligaturen“, sofern Sie sie nicht mit dem von Ihnen verwendeten Code blockieren.

Ich fürchte, der Ligaturierungs-Callback ist nicht die perfekte Lösung für diese Situation. Sie könnten jedoch node.ligaturing()zu Beginn des Callbacks aufrufen und dann tun, was Sie tun. Aber das würde wahrscheinlich mit der Fontspec in Konflikt geraten.

verwandte Informationen