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.