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
findmatchgmatchgsub
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!