lualatex ist nicht LuaTeX. Verfolgt man den symbolischen Verweis unter TeX Live 2024, führt er nicht auf luatex, sondern auf luahbtex – eine andere Binärdatei, mit eingebautem HarfBuzz. In fmtutil.cnf steht dasselbe: lualatex luahbtex ... lualatex.ini, also wird selbst das Format von LuaHBTeX erzeugt. Beim Aufruf meldet sich die Kopfzeile mit This is LuaHBTeX, Version 1.18.0 (TeX Live 2024) – leicht zu überlesen. Ausgehend davon, was die LaTeX-Engine tatsächlich ist, behandelt diese Seite, an welcher Stelle Lua in den Satz eingreift (\directlua und die Callbacks), was aus LuaJITTeX geworden ist und wie viel vom Ruf, LuaLaTeX sei langsam, einer echten Messung standhält.
lualatex ist LuaHBTeX – was HarfBuzz hinzufügt
Es trägt das gesamte HarfBuzz in der Binärdatei. LuaHBTeX kam 2020 in TeX Live, und damals wurde ausgetauscht, was hinter lualatex steckt; die Release Notes hielten ausdrücklich fest, dass LuaTeX selbst gewöhnliches LuaTeX blieb und sich nur das LaTeX-Format änderte. Eine Zeile klärt die Sache: Kompiliert man \directlua{tex.print(status.luatex_engine)} mit lualatex, steht auf der Seite luahbtex; mit luatex steht dort luatex. Ein require("luaharfbuzz") gelingt unter luahbtex und meldet HarfBuzz 8.3.0, während das schlichte luatex mit module 'luaharfbuzz' not found scheitert. HarfBuzz liegt nicht als Datei irgendwo auf der Platte, sondern ist statisch in die ausführbare Datei eingebunden – deshalb kann kein Paket es nachrüsten, und deshalb wird es überhaupt als eigene Binärdatei ausgeliefert.
$ readlink $(which lualatex)
luahbtex
$ grep -E '^lualatex ' $(kpsewhich fmtutil.cnf)
lualatex luahbtex language.dat,language.dat.lua lualatex.ini
$ lualatex --version | head -1
This is LuaHBTeX, Version 1.18.0 (TeX Live 2024)
# Ask the engine what it is, from inside a document:
% \directlua{tex.print(status.luatex_engine)} -> luahbtex
% \directlua{tex.print(_VERSION)} -> Lua 5.3
% \directlua{local hb = require("luaharfbuzz")
% tex.print(hb.version())} -> 8.3.0Praktisch heißt das: Es gibt zwei Wege der Formung, und standardmäßig nutzt luaotfload seinen eigenen, in Lua geschriebenen Shaper. Verlangt man in fontspec Renderer=Harfbuzz, wird auf HarfBuzz umgeschaltet – im Log erscheinen dann Einträge wie luaotfload.harf.finalize_hlist, die in hpack_filter und post_linebreak_filter eingehängt werden. Bei Schriften mit verwickelter Formung, etwa Tibetisch oder Bengalisch, ist HarfBuzz der genauere der beiden Wege – genau dafür wurde LuaHBTeX geschaffen. In einem rein lateinisch gesetzten Dokument wird man keinen Unterschied bemerken.
Wer LuaTeX gebaut hat, und wofür
Ruft man luatex --credits auf, erscheint das Entwicklungsteam als Hans Hagen, Hartmut Henkel, Taco Hoekwater und Luigi Scarso, gefolgt von einer Liste der aufgenommenen Projekte: TeX (Knuth), e-TeX (Peter Breitenlohner und andere), Omega (John Plaice, Yannis Haralambous), Aleph (Giuseppe Bilotta), pdfTeX (Hàn Thế Thành), MetaPost, Lua und – mit dem Vermerk „used in LuajitTeX“ – LuaJIT (Mike Pall). Der Strang von Omega und Aleph, den mehrsprachigen Engines, ist der Teil, den kaum jemand kennt. Die Entwicklung begann in der ConTeXt-Welt und im Oriental-TeX-Projekt, das dem wissenschaftlichen arabischen Satz und besonders kritischen Editionen galt; ein von Idris Samawi Hamid vom Philosophischen Institut der Colorado State University beantragtes Stipendium finanzierte die Neuprogrammierung des Kerns, unterstützt von der niederländischen NTG, der TUG und dem deutschen DANTE. Der Gedanke, TeX programmierbar zu machen, entsprang dem konkreten Wunsch, Arabisch schön zu setzen.
Da pdfTeX die Grundlage bildet, schreibt LuaTeX PDF unmittelbar und übernimmt die e-TeX-Erweiterungen samt dem größten Teil der Satzmaschinerie von pdfTeX. Der \pdf...-Namensraum wurde allerdings aufgeräumt: Vieles läuft nun über die drei Zugänge \pdfextension, \pdfvariable und \pdffeedback, die Schlüsselwort und Argumente entgegennehmen, während aus \pdfoutput \outputmode und aus \pdfximage \saveimageresource wurde. Von Hand schreibt man das selten; LaTeX-Pakete gleichen den Unterschied aus. Die Eingabe ist von Anfang an UTF-8, und Schriften werden wie in XeTeX mit fontspec beim Systemnamen genannt – geladen werden sie jedoch von LuaTeX's eigenem, in Lua geschriebenem Lader luaotfload, einer Anpassung des ConTeXt-Fontladers an Plain TeX und LaTeX, heute von der latex3-Gruppe des LaTeX-Teams gepflegt.
\directlua: Lua mitten im Dokument ausführen
\directlua{...} führt das enthaltene Lua auf der Stelle aus, und was tex.print(...) zurückgibt, gelangt wieder in TeX's Eingabestrom und wird wie gewöhnlicher Text gesetzt. Eine Sache erwischt dabei jeden beim ersten Versuch: \directlua{tex.print(2^10)} druckt nicht 1024, sondern 1024.0. In Lua 5.3, das LuaTeX verwendet, liefert ^ stets eine Fließkommazahl. Für die ganze Zahl schreibt man math.tointeger(2^10). Dieselbe Eigenschaft schlägt zu, wenn ein Lua-Ergebnis einer Länge oder einem Zähler zugewiesen wird; das LuaTeX-Handbuch warnt ausdrücklich, dass tostring und string.format wissenschaftliche Notation zurückgeben und die TeX-Seite verwirren können.
\documentclass{article}
\begin{document}
% careful: 2^10 is a float in Lua 5.3, so this prints 1024.0
Two to the tenth is \directlua{tex.print(2^10)}.
% and this prints 1024
Two to the tenth is \directlua{tex.print(math.tointeger(2^10))}.
\end{document}Das Geschwister \latelua{...} läuft dagegen erst, wenn die Seite ausgegeben (shipped out) wird, auf der es steht. Arbeiten, die auf feststehende Seitenzahlen und Endkoordinaten warten müssen – ins PDF zeichnen, eine Annotation anbringen –, gehören dorthin. Kompiliert man ein Dokument mit beiden, zeigt das Log den Unterschied deutlich: Die Ausgabe von \directlua erscheint vor [1, die von \latelua danach. Ein praktischer Hinweis: Rohes \directlua ist empfindlich gegenüber geschweiften Klammern und Sonderzeichen; für alles, was länger als eine Zeile ist, nimmt man die luacode-Umgebung aus dem Paket luacode, in die sich Lua wörtlich und gefahrlos einfügen lässt.
Callbacks: eigene Funktionen in den Satz einhängen
Die eigentliche Kraft von LuaTeX liegt in den Callbacks. TeX baut Text als verkettete Liste von Knoten auf – Zeichen, Boxen und Glue, der dehnbare Zwischenraum. Registriert man einen Callback, wird an jeder dieser Nahtstellen die eigene Lua-Funktion aufgerufen und darf die Knotenliste prüfen und umschreiben. Das LuaTeX in TeX Live 2024 stellt 74 Callbacks bereit, die das Handbuch in sechs Gruppen ordnet: Dateisuche, Datenverarbeitung, Knotenlistenverarbeitung, Informationsmeldungen, PDF-bezogene und schriftbezogene. Das folgende Beispiel läuft tatsächlich: Es zählt, in wie viele Zeilen ein Absatz umbrochen wurde, und meldet die Summe am Ende des Durchgangs.
\documentclass{article}
\usepackage{luacode}
\begin{luacode*}
local count = 0
luatexbase.add_to_callback("post_linebreak_filter",
function(head)
for line in node.traverse_id(node.id("hlist"), head) do
count = count + 1
end
return head
end, "count lines")
luatexbase.add_to_callback("stop_run",
function() texio.write_nl("LINES TYPESET: " .. count) end, "report")
\end{luacode*}
\begin{document}
This paragraph is broken into lines by TeX, and the Lua function
registered on post\_linebreak\_filter counts them as they go past.
\end{document}Das Beispiel registriert über luatexbase.add_to_callback statt über das rohe callback.register, denn die rohe Form lässt nur eine Funktion je Callback zu. In der LaTeX-Welt wollen mehrere Pakete an derselben Stelle eingreifen, und luatexbase fasst sie zu einer geordneten Liste zusammen. Die Werkzeuge für die Arbeit mit Knoten sind von Lua aus sichtbare Tabellen: tex ist das Fenster zum inneren Zustand von TeX (Register und Maße), node erzeugt, durchläuft und gibt Knoten frei, token behandelt Token (TeX's kleinste Bedeutungseinheiten), font die Schriftdaten und status die Laufzeitinformationen. Auch fortgeschrittene Schriftmaschinerie wie luaotfload ist in Lua auf dieser Grundlage geschrieben.
| Callback | Auslösezeitpunkt | Typische Verwendung |
|---|---|---|
process_input_buffer | bei jedem gelesenen Eingabezeilen | Vorverarbeitung der Eingabe (String rein, String raus) |
pre_linebreak_filter | unmittelbar vor dem Zeilenumbruch eines Absatzes | Umschreiben der Knotenliste vor dem Umbruch |
post_linebreak_filter | unmittelbar nach dem Zeilenumbruch | Eingriff in die fertigen Zeilen |
hpack_filter | bei jedem Zusammenbau einer horizontalen Box | nachträgliches Anpassen des Boxinhalts |
ligaturing / kerning | die Stufen für Ligaturen und Unterschneidung | das schrifteigene Anpassen ersetzen |
stop_run | ganz am Ende des Durchgangs | Summen melden, aufräumen |
Was aus LuaJITTeX geworden ist
Verschwunden ist es nicht. TeX Live 2024 liefert zwei ausführbare Dateien aus, luajittex und luajithbtex, beide in Version 1.18.0, und fmtutil.cnf definiert Formate dafür. Darin steckt nicht das Referenz-Lua, sondern LuaJIT, eine Just-in-time-Implementierung, die zur Laufzeit in Maschinencode übersetzt. Fragt man direkt nach, antwortet jit.version mit LuaJIT 2.1.0-beta3 und _VERSION mit Lua 5.1. LuaJITTeX ist also auf der Sprachebene 5.1 festgeschrieben und läuft auf einer anderen Sprachspezifikation als das Haupt-LuaTeX mit 5.3. Das LuaTeX-Handbuch sagt genau das: LuaJIT hält mit der regulären Lua-Entwicklung nicht Schritt, weshalb LuaJITTeX zurückbleibt.
Dieser Unterschied ist nicht akademisch. Kompiliert man die identische Zeile \directlua{tex.print(2^10)} mit allen vier Binärdateien, drucken luatex und luahbtex 1024.0, luajittex und luajithbtex dagegen 1024 – weil Lua 5.3 zwischen Ganzzahlen und Fließkommazahlen unterscheidet und 5.1 nicht. Ein Wechsel der Engine verändert also den Inhalt des Dokuments, was einer der Gründe ist, LuaJITTeX nicht als bloßen Ersatz für LuaLaTeX zu empfehlen. Hinzu kommt ein zweiter, entscheidenderer Umstand: fmtutil.cnf definiert für die LuaJIT-Engines ausschließlich Formate im Plain-Stil. Ein Gegenstück zu luajitlatex gibt es nicht. LaTeX auf LuaJIT laufen zu lassen hieße, ein eigenes Format zu bauen; korrekt formuliert steht es also praktisch nicht auf der Auswahlliste von LaTeX-Anwendern.
Eine zweite Nachfolgelinie ist LuaMetaTeX, eine schlankere Neufassung von LuaTeX, die dem modernen ConTeXt (LMTX / MkXL) zugrunde liegt. Sie bringt einen überarbeiteten Formelsatz, zahlreiche neue Primitive und die Lua-5.4-Linie. TeX Live 2024 liefert luametatex 2.11.02 mit, und der Befehl context ist selbst ein symbolischer Verweis darauf. Die Aufgabenteilung ist eindeutig: LuaTeX für LaTeX-Arbeit, LuaMetaTeX für aktuelles ConTeXt.
Ist LuaLaTeX wirklich langsam? Eine Messung
Es ist tatsächlich langsamer, doch man verortet die Kosten meist falsch. Der Start ist nahezu gleich: Bei einem fast leeren einseitigen Dokument brauchte pdfLaTeX 0,20 s, XeLaTeX 0,30 s und LuaLaTeX 0,32 s (dieselbe Maschine, schnellster von mehreren Läufen). Die Schere öffnet sich dort, wo die Zeit mit dem Satzumfang wächst. Bei demselben 417-seitigen mathematischen Dokument brauchte pdfLaTeX 0,40 s, XeLaTeX 0,53 s und LuaLaTeX 1,41 s. Zieht man den Start ab und betrachtet allein den Satz, kostet XeLaTeX etwa das 1,2-Fache von pdfLaTeX, LuaLaTeX mehr als das Fünffache. Umgekehrt heißt das: Bei einem zehnseitigen Aufsatz ist der Unterschied nicht spürbar.
| Messung | pdfLaTeX | XeLaTeX | LuaLaTeX |
|---|---|---|---|
1-page document | 0,20 s | 0,30 s | 0,32 s |
417-page document | 0,40 s | 0,53 s | 1,41 s |
typesetting only | Bezugswert | etwa 1,2-fach | etwa 5,6-fach |
Drei praktische Antworten. Erstens: Während des Schreibens zählt weniger die rohe Geschwindigkeit als die Zahl der Durchläufe – ein automatisierter Build wie latexmk, der nur Geändertes neu übersetzt, bringt mehr als die Wahl der Engine. Zweitens: Das nicht mit den einmaligen Kosten des Font-Caches verwechseln. Trifft luaotfload auf eine unbekannte Schrift, scannt und indiziert es, und genau dieser eine Lauf ist drastisch langsamer. Die meisten Berichte über „LuaLaTeX braucht zig Sekunden“ meinen das; ab dem zweiten Lauf gelten wieder die Werte oben. Drittens: In der CI zählt man Durchläufe und Wiederholungen, nicht die Kosten einer einzelnen Übersetzung. Läuft der Build für Querverweise und Inhaltsverzeichnis zwei- oder dreimal, vervielfacht sich jeder Unterschied entsprechend.
MetaPost eingebaut, und Japanisch mit LuaTeX-ja
LuaTeX hat die Zeichen-Engine MetaPost als Bibliothek MPlib eingebaut, sodass Abbildungen im selben Prozess entstehen können, ohne ein externes Programm aufzurufen. Von LaTeX aus erreicht man sie über das Paket luamplib und schreibt MetaPost-Code unmittelbar in eine mplibcode-Umgebung. Kompiliert man ein solches Dokument und prüft das Log, findet sich nirgends eine Spur eines gestarteten externen mpost. Das wirkt unscheinbar, zählt aber: Abbildungen entstehen auch dort, wo Shell Escape abgeschaltet ist, und in der CI.
Den japanischen Satz übernimmt LuaTeX-ja (Paket luatexja). Es setzt das Satzwissen von pTeX auf der Lua-Seite und über Callbacks neu um – Vertikalsatz, die von JFM (Japanese Font Metrics) gesteuerte Zeichenabstands- und Interpunktionsbehandlung sowie den Abstand zwischen japanischem und westlichem Text – und ist damit das größte praktische Beispiel für die oben beschriebene Callback-Maschinerie. Mit luatexja-fontspec lassen sich japanische Schriften auch im Stil von fontspec auswählen. Eine Warnung: Die HarfBuzz-Formung von LuaHBTeX und die Vertikalsatz- und CID-Mechanik von LuaTeX-ja sollten mit Bedacht kombiniert werden. Das Handbuch von LuaTeX-ja weist darauf hin, dass über HarfBuzz definierte japanische Schriften unerwünschte Ergebnisse liefern können. Für ein echtes Manuskript bringt man zuerst mit den in TeX Live mitgelieferten Harano-Aji-Schriften und Standardeinstellungen ein PDF zustande und führt OpenType-Merkmale oder HarfBuzz-Optionen erst nach kleinen Tests ein.
\documentclass{ltjsarticle}
\usepackage{luatexja-fontspec}
% Harano Aji ships with TeX Live, so this builds anywhere
\setmainjfont{HaranoAjiMincho-Regular}
\setsansjfont{HaranoAjiGothic-Medium}
\begin{document}
\section{日本語}
Text and mathematics $E=mc^2$ go through the same engine.
\end{document}Ein neues Dokument in LuaLaTeX beginnen
- Mit Unicode-Quelltext beginnen. Kein
inputenc, keinfontenc. Wer mit den Deklarationen der pdfLaTeX-Zeit umzieht, handelt sich Warnungen und eine doppelte Schrifteinrichtung ein. fontspeczur alleinigen Quelle der Schriftauswahl machen. Bei einem gemeinsam bearbeiteten Manuskript zuerst einen Build sicherstellen, der mit den TeX-Live-Schriften läuft, dann ersetzen.- Für Japanisch
luatexjaverwenden. Es deckt Vertikalsatz, JFM und den Abstand zwischen japanischem und westlichem Text ab. - Längeres Lua in eine
luacode-Umgebung setzen. Rohes\directluaist empfindlich gegenüber geschweiften Klammern und Sonderzeichen. - Callbacks über
luatexbase.add_to_callbackregistrieren. Das rohecallback.registerlässt nur eine Funktion je Einhängepunkt zu. - Zuerst ein kleines Muster bauen. Eine Seite, die Brotschrift, japanische Schrift, Formeln und Literaturverzeichnis durchspielt – und die dann als Vorlage dient.
Die Faustregel in einem Satz: LuaLaTeX, wenn in den Satz selbst eingegriffen werden soll; XeLaTeX, wenn nur Systemschriften beim Namen genutzt werden; pdfLaTeX, wenn die Arbeit überwiegend englisch ist und Geschwindigkeit sowie Kompatibilität zählen. Eine Seite, die alle drei direkt vergleicht, gibt es gesondert. Da das LaTeX-Team seine Neuentwicklung auf LuaTeX ausrichtet, rückt LuaLaTeX für neue Unicode-/OpenType-Arbeitsabläufe zunehmend in die Rolle des Standards – vor allem dort, wo etwas programmiert werden soll.