Versal – SZ mit LuaTeX
Gerade habe ich eine Frage in meinem Mail-Postfach gefunden: Wie kann ich das Versal-ß unter LuaTeX benutzen? Die Antwort ist einfach: so wie es Unicode vorschlägt. Nur musst du eine Schriftart haben, wo dieses Ungetüm enthalten ist:
\documentclass{article} \usepackage[german]{babel} \usepackage{fontspec} \setmainfont{Linux Libertine O} \begin{document} Das Mädchen ging über die Brücke nach draußen. DAS MÄDCHEN GING ÜBER DIE BRÜCKE NACH DRAUẞEN. \end{document}
Gibt bei mir (TeXlive 2010) ein ordentliches PDF, das das Versal-sz auch per Copy/Paste überträgt.
lua-visual-debug – Das Unsichtbare sichtbar machen
Das LuaTeX-Paket lua-visual-debug gibt dem Textautor Hilfen bei Positionierung von Elementen auf der Seite, in dem es die normalerweise unsichtbaren Elemente sichtbar macht. Der Artikel schlägt zwei Fliegen mit einer Klappe: Neben der Beschreibung des Pakets wird die Programmierung in Lua erklärt.
Benutzung und Erklärung
Wer kennt die Situation nicht: die minipage-Umgebung ragt über den rechten Rand hinaus, aber warum? Weshalb ist das Bild nicht genau unter der Tabelle? Wieso erzeugen alle Überschriften eine overfull box? Manchmal wäres es wünschenswert, genau zu sehen, wie LaTeX Elemente auf der Seite platziert und wo überall dehnbarer Zwischenraum eingefügt wird, wo Penalties gesetzt werden und ob ein einer Stelle eine Strut-Box erscheint.
Auf CTAN und in der TeXlive-Distribution ist seit Februar 2012 das Paket lua-visual-debug verfügbar, das (nur) mit LuaTeX funktioniert, aber sowohl von plain-TeX als auch mit LaTeX 2ε benutzbar ist. Die Anwendung ist einfach, ein
\usepackage{lua-visual-debug}
bzw. für plain TEX
\input lua-visual-debug.sty
reicht aus, um den Text zu „dekorieren“. Das könnte wie in dieser Abbildung aussehen:
Folgede Elemente werden von dem Paket dargestellt:
- Kästen (vbox,hbox)
- Struts (0pt breite Linien)
- Trennstellen
- Dehnbare Längen (glue)
- Unterschneidungen (kern) sowie
- Strafpunkte (penalty)
Diese Abbildung zeigt Beispiele für die verschiedenen Elemente:
- Vertikale Längen werden in schwarzen gestrichelten Linien dargestellt, deren Anfang und Ende mit einem Strich links markiert sind. Somit kann man aufeinanderfolgende Längen auseinanderhalten.
- Horizontale Längen werden in drei unterschiedlichen Farben dargestellt: Grau für Längen in ihrer natürlichen Breite, Magenta für gestauchte Längen und Blau für gedehnte Längen. So wird die am Absatzende übliche Länge
\parfillskip
in Blau dargestellt. - Negative Kerns (Unterschneidungen) werden als rotes Kästchen angezeigt, positive als gelbes.
- Mögliche Trennstellen markiert das Paket mit einem kleinen blauen Strich unterhalb der Trennstelle
- Horizontale Kästen (hbox) werden mit einem mittlerem Grau umrahmt, vertikale Kästen mit einem dunklem Grau.
- Penalties >= 10 000 sind mit einem nicht gefülltem Quadrat gezeichnet, ansonsten werden die Quadrate gefüllt dargestellt.
Da das Paket noch relativ jung ist, könnte die Darstellung in Zukunft noch anders aussehen.
Die zweite Fliege – Implementierung
Das Paket ist gut für eine Einführung in die Programmierung von LuaTeX geeignet, da der grundlegende Aufbau einem einfachen Schema folgt. TeX hat zu dem Zeitpunkt, an dem eine Seite ausgegeben wird, alle benötigten Elemente in einer Box gespeichert. Mithilfe des Pakets atbegshi von Heiko Oberdiek kann einfach auf diese Box zugegriffen werden. Da die dargestellten Elemente auf der Seite nicht verschoben werden sollen, bietet es sich an, mit PDF-Anweisungen die Markierungen einzufügen. Diese PDF-Anweisungen haben selbst keine Breite und damit keinen Einfluss auf nachfolgende Elemente.
Bevor TeX die Seiteninhalte in PDF ausgibt, wird ein Zwischenformat erzeugt, bestehend aus sogenannten Nodes. Diese Nodes sind elementare Einheiten wie zum Beispiel ein Zeichen, ein Penalty oder eine horizontale Box. Die Idee ist nun, dieses Zwischenformat zu untersuchen, um die PDF Anweisungen für die Markierungen einzufügen.
Die Nodes sind in einer linearen Liste miteinander per „next“ und „prev“-Zeiger miteinander verbunden. Manche Nodes haben selbst wieder Inhalte. Das sind beispielsweise Boxen oder Ligaturvorschläge. Diese Inhalte sind dann über spezielle Felder erreichbar. Damit ist die grundlegende Schleife wie folgt:
aktueller_knoten = Zeiger auf ersten Knoten solage wie aktueller_knoten nicht "nil" ist untersuche Knoten und füge ggf. die PDF-Anweisung ein gehe zum nächsten Knoten: aktueller_knoten = aktueller_knoten.next ende
bzw. in Lua:
untersuche_box = function(box) head = box.list while head do -- Untersuchen und PDF Anweisungen einfügen head = head.next end end
Damit wird die Knotenliste (nodelist) Element für Element durchgegangen und untersucht. Der letzte Knoten hat einen next-Zeiger “nil” und damit ergibt die Bedingung in der while-Anweisung “falsch” und die Funktion untersuche_box() wird verlassen.
Wie können wir nun die einzelnen Nodes voneinander unterscheiden? Jeder Knoten hat eine “ID”-Nummer, die man im Referenzhandbuch nachschlagen kann. Beispielsweise haben Zeichen die ID-Nummer 37, horizontale Boxen die 0 und vertikale Boxen die 1. Nun wollen wir nicht nur die äußerste Box untersuchen, sonder alle innenliegenden. Die äußerste Box ist nämlich quasi nur die Umrandung der Seite. Daher muss das Programm, wenn es auf eine Box mit der ID-Nummer 0 oder 1 trifft, diese wieder darstellen. Das ist einfach, denn es kann einfach die Funktion untersuche_box() rekursiv aufrufen:
untersuche_box = function(box) head = box.list while head do if head.id == 0 or head.id == 1 then untersuche_box(head.list) end head = head.next end end
In head.list ist der Inhalt der aktuellen Box gespeichert. Damit wenden denselben Mechanismus auf den Inhalt an, der gerade angewendet wird (also den Inhalt der Box untersuchen, solange es noch Elemente in der Liste gibt).
Anschließend muss die Box noch umrahmt werden (das ist ja die Funktion des Pakets). Dazu wird ein Node vom Typ “PDF-Anweisung” erzeugt:
local rechteck = node.new("whatsit","pdf_literal")
Die PDF-Anweisung sieht vereinfacht so aus:
rechteck.data = " 0 0 <Breite> <Höhe> re s"
Das sind Befehle für das PDF Anzeigeprogramm um ein Rechteck zu zeichnen, das „links unten“ anfängt und die angegebene Höhe und Breite hat. In der Praxis müssen wir noch zwischen der horizontalen und der vertikalen Box unterscheiden, da in der hbox der erste Punkt links unten ist und in der vbox links oben. Außerdem verändern wir mit der Anweisung 0.5 G noch die Farbe der Linie und mit 0.1 w
noch die Linienbreite. Das muss noch in q..Q
geklammert werden, damit sich die Linienstärke und Farbe nicht auf die nächsten Zeichenoperationen auswirkt. Somit ist die PDF Anweisung in etwa:
rechteck.data = "q 0.5 G 0.1 w 0 0 <Breite> <Höhe> re s Q"
Der erste Teil der Schleife ist schon funktionstüchtig:
untersuche_box = function(box) head = box.list while head do if head.id == 0 or head.id == 1 then untersuche_box(head.list) local wd = head.width / 65782 local ht = (head.height + head.depth) / 65782 local dp = head.depth / 65782 local rechteck = node.new("whatsit","pdf_literal") if head.id == 0 then -- hbox rechteck.data = string.format("q 0.5 G 0.1 w 0 %g %g %g re s Q",-dp, wd, ht) else rechteck.data = string.format("q 0.1 G 0.1 w 0 0 %g %g re s Q", 0, wd, -ht) end head.list = node.insert_before(head.list,head.list,rechteck) end head = head.next end end
Mit node.insert_before()
wird der neu erzeugte Knoten vor dem ersten Element aus der aktuellen Box eingefügt. Dadurch muss der Zeiger auf den Inhalt in der übergeordneten Liste (head.list
) angepasst werden, da ja der neu erzeugte Knoten das erste Element ist. Ohne die Anpassung würde der Zeiger auf den Inhalt auf dem alten ersten Element stehen und das Rechteck würde nicht angezeigt. node.insert_before()
setzt auch die notwendigen prev
und next
Zeiger richtig.
Die Zahl 65782 kommt von der Umwandlung von scaled Points, TeXs interner Längeneinheit und dem PDF-Punkt von 1/72 Zoll. D.h. in einem PDF Punkt (big point) sind 65782 scaled points.
Nach diesem Prinzip werden alle genannten Elemente behandelt. Die Details kannst du im Quellcode nachschauen.
LuaLaTeX: Zeige die Trennstellen
Weil ich gerade selbst an Silbentrennung arbeite, gibt’s hier ein schickes LuaLaTeX-Beispiel. Es zeigt alle Trennstellen im Dokument an. Was passiert ist eigentlich recht einfach. Es wird nach dem Absatzumbruch die Nodeliste durchgegangen, und alle disc-Nodes werden um eine PDF-Anweisung ergänzt, die einen kleinen Strich zeichnet.
[cce lang=”latex”]\documentclass[12pt,a4paper]{scrartcl}
\usepackage[ngerman]{babel}
\usepackage{blindtext}
\usepackage{fontspec}
\directlua{
show_hyph = function(head)
while head do
if head.id == 0 or head.id == 1 then % hlist, vlist
show_hyph(head.list) % sollte head.head in LuaTeX > 0.65 sein
elseif head.id == 7 then % disc
local n = node.new(“whatsit”,”pdf_literal”)
n.mode = 0
n.data = “q 0.3 w 0 2 m 0 7 l S Q”
n.next = head.next
n.prev = head
head.next = n
head = n
end
head = head.next
end
return true
end
luatexbase.add_to_callback(“post_linebreak_filter”,show_hyph,”show_hyph”)
}
\begin{document}
\blindtext
\end{document}[/cce]
Und hier ein Beispiel, wie das aussehen könnte:
Inzwischen ist diese Funktionalität als eigenständiges Paket verfügbar. Siehe die Paketbeschreibung auf CTAN. Einfach benutzen mit \usepackage{showhyphens}.
Fontloader + Ligaturen
Im letzten April habe ich einen Fontloader vorgestellt, der OpenType, TrueType und Type1 Fonts lädt und für TeX bereitstellt. Nun wurde ich gefragt, wie man den Fontloader um Ligaturen erweitern kann. Hier beschreibe ich die Antwort, den ursprünglichen Artikel erweitere ich entsprechend.
Um Ligaturen zu verwenden, musst du erst einmal wissen, für welche Sprache die Schriftart geladen werden soll. So gibt es im Niederländischen die ij-Ligatur und im Türkischen wird die fi-Ligatur vermutlich keine große Verbreitung haben, weil man dann nicht mehr richtig erkennt, ob das i ein i mit Punkt ist oder ohne: ı. Wenn das klar ist, können wir aus der gsub-Tabelle des Fonts alle notwendigen Feature-Namen auswählen. Ein Eintrag in der gsub-Tabelle sieht zum Beispiel so aus:
subtables = { { name = "ls_latn_l_11_s" } }, type = "gsub_ligature", flags = {}, name = "ls_latn_l_11", features = { { scripts = { { langs = { "NLD " }, script = "latn" } }, tag = "liga" } }
Das bedeutet: wenn das Schriftsystem (script) “latn
” und die Sprach “NLD
” gewünscht ist, dann müssen wir alle lookup-Tabellen mit dem Namen “ls_latn_l_11_s
” betrachten. Der Name steht in der Subtables-Tabelle. In der Fonttabelle gibt es bei den einzelnen Zeichen die lookups
-Tabelle. Ein Eintrag beim Zeichen »I_J« sieht zum Beispiel so aus:
ls_latn_l_11_s = { { type = "ligature", specification = { char = "I_J" components = "I J" } }
D.h. wenn wir Niederländisch ausgewählt haben, dann können wir diesen Eintrag in der lookups
-Tabelle beachten, da der Name mit dem aus der gsub
-Tabelle übereinstimmt. Mit dieser Information können wir dann die notwendige ligatures
-Tabelle für TeX erstellen:
ligatures = { [74] = { char = 306, type = 0 } }
Dieser Eintrag gehört dann zum Zeichen »I« (großes i). Damit wird TeX, wenn es auf die Folge IJ stößt, diese Zeichen durch die IJ-Ligatur ersetzen (IJ).
Im Code sieht das dann so aus:
-- Hier stehen die Schreibsysteme (scripts) und Sprachen, für die die Ligaturen gesucht werden sollen. local scripts_OK = { DFLT = true, latn = true } local langs_OK = { dflt = true } local lookups = {} if fontinfo.gsub then for i=1,#fontinfo.gsub do local gsub = fontinfo.gsub[i] for j=1,#gsub.features do local features = gsub.features[j] if features.tag =="liga" then for k=1,#features.scripts do local script_tbl = features.scripts[k] if scripts_OK[script_tbl.script] then -- die richtigen "scripts" haben wir gefunden, jetzt kommt es noch -- auf die richtige Sprache an. for l=1,#script_tbl.langs do local lang = script_tbl.langs[l] if langs_OK[lang] then lookups[gsub.subtables[1].name] = true end end end end end end end end
Die ersten beiden Tabellen beschreiben die gewünschte Sprache bzw. das Schreibsystem. Wichtig: die Bezeichner sind immer vier Zeichen lang, bei Niederländisch etwa wird mit Leerzeichen aufgefüllt: »NDL «. Dann müssen wir uns relativ weit nach unten »hangeln« um herauszufinden, ob es a) ein Ligatureintrag ist (feature="liga"
), ob das script übereinstimmt und ob die Sprache übereinstimmt. Erst dann können wir uns den Namen aus der subtable
merken. Nach Ende der Schleife kennen wir alle Namen für die lookup
-Tabelle.
Der zweite Teil besteht darin, für jedes Zeichen die lookup
-Tabelle durchzugehen, ob dort Einträge sind, die uns interessieren:
if glyph.lookups then for k,v in pairs(glyph.lookups) do if lookups[k] then for i,w in ipairs(v) do ligatures[#ligatures + 1] = w end end end end
Die Einträge können wir nicht direkt verarbeiten, weil die beteiligten Zeichen möglicherweise noch gar nicht verarbeitet wurden (= noch nicht im f.characters
-Array enthalten sind – siehe den vollständigen Code). Daher speichern wir die Ligatur-Einträge und wenn alle Zeichen verarbeitet wurden, werden die Ligaturen »angewendet«.
for _,v in ipairs(ligatures) do local spec = v.specification local result_cp = lookup_codepoint_by_name[spec.char] if result_cp > 0 then -- -1 == unencoded local components = string.explode(spec.components) -- z.B. "ff i" if #components == 2 then -- wir behandeln nur Ligaturen mit 2 Komponenten local char = f.characters[lookup_codepoint_by_name[components[1]]] char.ligatures = char.ligatures or {} char.ligatures[lookup_codepoint_by_name[components[2]]] = { char = result_cp } end end end
Dies erzeugt die ligatures
Einträge für die Zeichen, die als erstes in der Ligatur vorkommen. Bei der IJ-Ligatur also beim I. Den vollständigen Code findest du wie erwähnt im ursprünglichen Artikel.
Eigenen Fontloader erstellen
Wieder etwas für die Hacker unter uns.
Hier ein etwas längerer Artikel (zumindest was den Quellcode betrifft). Anfangs hatte ich versprochen mal zu schreiben, wie man denn in LuaTeX selbst Schriftarten lädt. Wenn unser gutes, altes TeX eine Schriftart über das \font
-Kommando lädt, dann wird intern eine Tabelle ausgefüllt mit den Eigenschaften der .tfm
-Datei. Dazu gehört der Name der Schriftart, die Einstellungen für die Leerräume zwischen den Wörtern und natürlich Informationen über die Zeichen selbst (Breite, Tiefe und Höhe, Kerning, Ligaturen). Die Idee ist nun, selbst die Tabelle auszufüllen und dann mit font.define()
und tex.definefont()
TeX verfügbar zu machen. Doch wie komme ich an die Informationen aus den Schriftdateien, die auf der Festplatte liegen? Ganz einfach, dazu beinhaltet LuaTeX FontForge als Bibliothek, in der Form von der fontloader
-Lua Bibliothek. Das Referenzhandbuch dazu schreibt:
function load_font (filename) local metrics = nil local font = fontloader.open(filename) if font then metrics = fontloader.to_table(font) end fontloader.close(font) return metrics end
metrics
dürfte die Sache nicht ganz richtig treffen, da noch andere Informationen in dieser Tabelle enthalten sind. Als erstes hier die TeX-Datei (mit plain LuaTeX aufrufen)
Die Fontdateien müssen entweder im TeXlive-Baum zu finden sein oder im aktuellen Verzeichnis liegen. Und hier folgt nun der eigentliche Kern der Fontdefinition:
fontinfo
ist die Datenstruktur, die die fontloader-Bibliothek erzeugt, f
ist die Tabelle, die später TeX übergeben wird (mit tex.definefont()
). Die Idee ist, erst einmal herauszufinden, an welche Unicode-Positionen die einzelnen Zeichen gehören (das ist bei OpenType und bei Type1 Schriftarten unterschiedlich). Dann werden die einzelnen Zeichen in die f.characters
-Tabelle eingefügt mit der Unicode-Position als Index. Die restlichen Informationen für die Schrifttabelle können direkt aus der fontinfo
Tabelle übernommen werden.
Dieser Fontloader ist noch relativ einfach gestrickt, ich habe hier zum Beispiel auf OpenType Features verzichtet. Sobald man viele verschiedene OpenType-Features unterstützen möchte, wird es viel Programmierarbeit. Vom Prinzip her bleibt es aber immer so, wie hier beschrieben.
Ein kleiner Nachtrag: die Idee für den Fontloader stammt zum größten Teil aus dem LuaTeX-Wiki.
Und noch ein Nachtrag (9. Januar 2011): Den Fontloader habe ich heute erweitert, so dass er auch Ligaturen verarbeiten kann. Die Erklärung dazu findest du in einem eigenen Artikel.
selene unicode Bibliothek
Auf der LuaTeX Mailingliste wurde gefragt, welche Funktionalität die in LuaTeX eingebundene Bibliothek selunicode hat. Die Bibliothek bietet einige Ersatzfunktionen für die eingebaute string
-Bibliothek. Speziell handelt es sich um:
byte(str, start [,end=-1])
char(i [,j...])
len(str)
lower(str)
reverse(str)
sub(str, start [,end=-1])
upper(str)
sowie veränderte Zeichenklassen für
find
match
gmatch
gsub
Ein kleines Beispiel soll zeigen, warum die spezielle Handhabung von UTF-8 Strings notwendig ist:
print(string.format("string-len=%d, unicode.utf8-len=%d", string.len("Löss"), unicode.utf8.len("Löss")))
ergibt
string-len=5, unicode.utf8-len=4
Und das Ergebnis 4 hätte ich auch erwartet. Der Hintergrund ist der, dass das ö als zwei Zeichen in UTF-8 kodiert ist, und zwar als 195 und 182 (0xC3B6). Da die original Lua-String-Funktionen nicht mit UTF-8 umgehen können, sehen sie dieses zusammengesetzte Zeichen als einzelne Zeichen. Dadurch ergibt sich die höhere Ausgabe der string.len()
-Funktion.
Die Zeichenklassen für find
, match
, gmatch
und gsub
sind folgende:
%a L* (Lu+Ll+Lt+Lm+Lo) %c Cc %d 0-9 %l Ll %n N* (Nd+Nl+No, neu) %p P* (Pc+Pd+Ps+Pe+Pi+Pf+Po) %s Z* (Zs+Zl+Zp) und die Controllzeichen 9-13 (HT,LF,VT,FF,CR) %u Lu (und Lt ?) %w %a+%n+Pc (z.B. '_') %x 0-9A-Za-z %z das Null-byte (\0)
Die Erklärung der Symbole Nd, Nl, No, … findest du unter http://www.unicode.org/Public/4.0-Update1/UCD-4.0.1.html#General_Category_Values.
Hier noch ein Beispiel für die Verwendung der Zeichenklassen:
local str = "...Α-Ω..." print(string.find(str,"%u")) -- nil print(unicode.utf8.find(str,"%u")) -- 4 5 print(string.match(str,"%u")) -- nil print(unicode.utf8.match(str,"%u")) -- A for w in string.gmatch(str, "%u") do -- keine Treffer -- (wird nicht erreicht) end for w in unicode.utf8.gmatch(str, "%u") do print(w) -- Α Ω end
Vielen Dank an den Fragesteller für die ergänzenden Hinweise zu den Zeichenklassen!
\directlua{…} und directtex(…)
Bei LuaTeX wird der Befehl \directlua{...}
sehr häufig benutzt. Er macht nichts anderes, als den TeX-Interpreter „anzuhalten“ und in den Lua-Modus zu springen. Jetzt kann man beliebige Lua-Befehle ausführen und wenn TeX wieder zurück springt (wenn keine Lua-Befehle mehr abgearbeitet werden sollen), dann ist TeX wieder im normalen Modus und verarbeitet wieder normale TeX-Befehle. Gibt es eigentlich einen äquivalenten Lua-Befehl, der in den TeX-Modus springt?
Man könnte sagen: ja, es gibt doch zum Beispiel tex.sprint()
. Doch folgendes Beispiel zeigt ein kleines Problem damit:
dokument.tex:
\directlua{ dofile("luacode.lua") } \bye
luacode.lua:
tex.sprint("\\setbox1\\hbox{Hallo Welt}") texio.write_nl("Die Breite der Box 1 ist ".. tostring(tex.wd[1]))
(Warum der Inhalt von luacode.lua nicht im \directlua{..}
-Block steht dürfte dir klar werden, wenn du es ausprobierst.)
Was ist die Ausgabe von dem Programm? Ich hätte erwartet, dass die Breite der Box (in scaled points) ausgegeben wird. Doch gefehlt! Es wird eine 0 ausgegeben. Das liegt daran, dass TeX noch gar nicht dazu gekommen ist, die \hbox
zu setzen und der Box 1 zuzuordnen. Die Inhalte von tex.sprint()
werden gesammelt und erst dann ausgeführt, wenn der \directlua{...}
-Block verlassen wird. Nun, wie kann ich dann TeX zwischenzeitlich dazu veranlassen, den Code auszuführen? Die erste Lösung liegt nahe:
dokument.tex:
\directlua{ dofile("luacode.lua") } \directlua{ a() } \directlua{ b() } \bye
luacode.lua:
function a( ) tex.sprint("\\setbox1\\hbox{Hallo Welt}") end function b( ) texio.write_nl("Die Breite der Box 1 ist ".. tostring(tex.wd[1])) end
Es funktioniert und die Ausgabe ist 3076557. Doch schön ist die Lösung nicht, da der Kontrollfluss immer noch auf der TeX-Seite ist. Du musst im TeX-Code immer die passenden Lua-Routinen aufrufen. Für manche Problemstellungen mag das machbar sein, doch sobald du dich hauptsächlich auf der Lua-Seite bewegen möchtest (es macht einfach Spaß in Lua zu programmieren), ist das eine Sackgasse. Eine Lösung für das Problem sind Coroutinen. Mithilfe von Coroutinen kannst du zwischen zwei Programmteilen hin- und herspringen, der Lua-Interpreter merkt sich die Stelle, an der du den einen Teil des Programms verlassen hast. An dieser Stelle gibt es keine Einführung in Coroutinen in Lua, das würde den Rahmen wirklich sprengen. Ich musste mich schon ein klein wenig damit beschäftigen, bis ich auf folgende Lösung für das Problem gekommen bin:
dokument.tex:
\directlua{ dofile("luacode.lua") } \newif\ifcontinue \continuetrue \directlua { co = coroutine.create(main_loop) } \loop \directlua{ ok,b=coroutine.resume(co) tex.sprint(b) }\ifcontinue \repeat \bye
luacode.lua:
function directtex(str) coroutine.yield(str) end function main_loop() directtex("\\setbox1\\hbox{Hallo Welt}") texio.write_nl("Die Breite der Box 1 ist ".. tostring(tex.wd[1])) directtex("\\continuefalse") end
Der Kern besteht aus der Schleife \loop ... \repeat
, die mit directlua{...}
und coroutine.resume()
in den Lua-Modus springt, bis dort ein coroutine.yield()
aufgerufen wird, das Argument von coroutine.yield()
an TeX übergibt, in den TeX-Modus springt und das wieder von vorne, bis die Bedingung \ifcontinue
false ergibt. Naheliegend, nicht wahr? 😉
Eine wirklich praktikable Lösung, wie ich finde, die auch nicht allzu komplex ist. Zumindest wenn man sich mal mit Coroutinen beschäftigt hat. Du wirst aber ein kleines Problem erkennen, wenn du dieses Schema benutzt: die Fehlerhandhabung ist ziemlich schlecht geworden, manchmal reagiert TeX nicht mehr, manchmal gibt es kryptische Fehlermeldungen – in den seltensten Fällen gibt es einen echten Hinweis auf die Fehlerursache. Da hilft die Lua-Funktion pcall()
, die ruft die angegebene Funktion im geschützten Modus auf, dadurch hast du eine Chance noch echte Fehlermeldungen auszugeben, in etwa so:
function call(...) local ret = { pcall(...) } if ret[1]==false then texio.write_nl("Fehler:" .. tostring(ret[2]))-- .. debug.traceback()) directtex("\\continuefalse") end return unpack(ret,2) end call(funktion,argument1,argument2)
So, viel Spaß beim Verstehen und Ausprobieren – ich freue mich über Kommentare!