Im ersten Beispiel meines neuen Blogs TeX-Beispiel des Monats zeige ich, wie man einen eigenen Befehl für die Lücken in Lückentexten oder Mathebuchaufgaben programmiert. Dazu arbeiten wir mit xparse
, um den Befehl zu definieren, und TikZ, um die Lücken in verschiedenen Stilen, darzustellen, wobei ein paar Grundkenntnisse in letzterem hilfreich aber nicht zwingend nötig sind.
Das Beispiel geht zurück auf eine Frage, die bereits 2013 auf der Frage-Antwort-Seite TeX.SX gestellt wurde: Graphical placeholder for “variables”.
Einleitung
In diesem Beispiel werden wir den Befehl \fib
(„fib“ steht für „fill in (the) blank“) programmieren. Dieser Befehl soll wahlweise eine leere Lücke oder eine mit der Lösung ausgefüllte Lücke zeigen und die Breite der Lücke soll außerdem von der Länge der Lösung abhängig sein.
Die gewünschte Syntax ist \fib*[Stil]{Lösung}
, wobei optional ein Darstellungsstil für die Lücke angegeben werden kann. Der Stern schaltet die Lösung sichtbar. Darüber hinaus können die Lösungen mit den Befehlen \fibhideanswertrue
und \fibhideanswerfalse
global ein- bzw. ausgeblendet werden.
Dokument lueckentext.tex
Für das Beispiel legen wir die Datei lueckentext.tex
an und speichern diese ab.
Basics
Wir beginnen das Dokument mit dem Laden von ein paar Paketen.
Das Paket mathtools
laden wir nur, um im Beispiel später eine Matrix mit der Umgebung {pmatrix}
erzeugen zu können.
\usepackage{mathtools}
Programmierung
Jetzt geht es ans „Eingemachte“: Wir beginnen mit der Programmierung des Befehls \fib
. Dazu werden wir auch ein paar Hilfsbefehle benutzen, die später im Dokument nicht mehr (einfach) zugänglich sein sollen, weshalb wir diese durch ein @
im Namen kennzeichnen bzw. vor dem späteren Zugriff schützen werden (FAQ 9).
\makeatletter
Jetzt erzeugen wir eine neue Länge (FAQ 13), mit deren Hilfe wir später die Breite der Lösung messen werden …
\newlength\fib@width
… und einen Faktor (als Befehl), mit dem wir die gemessene Länge später multiplizieren können, da ein handschriftlicher Eintrag normalerweise mehr Platz braucht als ein gedruckter.
\newcommand\fib@widthfactor{1.75}
Anschließend definieren wir einen neuen \if
-Befehl um die Lösungen ein- und ausblenden zu können (FAQ 14).
\newif\iffibhideanswer \fibhideanswertrue
Als nächstes definieren wir ein paar TikZ-Stile, um verschiedene Darstellungsmöglichkeiten für die Lücken zu erhalten.
\tikzset{
Bevor wir die einzelnen Stile definieren, legen wir analog zu bspw. every node
einen Stil an, mit dem wir auf alle Lücken zugreifen können.
every fill in box/.style={ inner xsep=0pt, minimum height=3ex, align=center, font={\sffamily\slshape}, },
Alle folgenden Stile rufen every fill in box
auf und ergänzen weitere Einstellungen. Hier wird die Hintergrundfarbe mit fill
festgelegt.
colored box/.style={ every fill in box, fill=yellow!50!white, },
Die Box wird umrahmt.
framed box/.style={ every fill in box, draw, },
Um die Art der Unterstreichung leicht ändern zu können, ohne in die Definition des eingefügten Pfades eingreifen zu müssen, wird zusätzlich der Stil underline style
definiert (hier allerdings leer gelassen).
underline style/.style={},
Um die Unterstreichung zu zeichnen, verwenden wir append after command
und fügen damit nach dem eigentlichen Zeichenbefehl weitere ein: In diesem Fall wird dann mit \draw
eine Linie von unten links nach unten rechts gezeichnet, wobei \tikzlastnode
sich hier auf den Lücken-node
bezieht.
underlined box/.style={ every fill in box, append after command={% \pgfextra{\begin{pgfinterruptpath} \draw [underline style] (\tikzlastnode.south west) -- (\tikzlastnode.south east); \end{pgfinterruptpath}} }, },
Analog zu underline style
wird bracket style
definiert.
bracket style/.style={},
Dieser Stil funktionieren im Prinzip wie underlined box
, allerdings wird hier die calc
-Syntax benutzt, um eckige Klammern unter die Lücke zu zeichnen.
underbracked box/.style={ every fill in box, append after command={% \pgfextra{\begin{pgfinterruptpath} \draw [bracket style] ($(\tikzlastnode.south west)+(0,2pt)$) |- (\tikzlastnode.south) -| ($(\tikzlastnode.south east)+(0,2pt)$); \end{pgfinterruptpath}} }, },
Bei diesem Stil ergänzen wir eine eckige Klammer über der Lücke.
underoverbracked box/.style={ every fill in box, append after command={% \pgfextra{\begin{pgfinterruptpath} \draw [bracket style] ($(\tikzlastnode.north west)-(0,2pt)$) |- (\tikzlastnode.north) -| ($(\tikzlastnode.north east)-(0,2pt)$); \draw [bracket style] ($(\tikzlastnode.south west)+(0,2pt)$) |- (\tikzlastnode.south) -| ($(\tikzlastnode.south east)+(0,2pt)$); \end{pgfinterruptpath}} }, },
Zuletzt wird der Stil fill in
definiert, der später in der Definition verwendet wird und jeweils als Alias für den gewünschten Stil steht.
fill in/.style={ colored box, },
Ende von \tikzset
}
Der Befehl \fib@hide
blendet in Abhängigkeit von \iffibhideanswer
die Lösung ein bzw. aus. \phantom
erzeugt einen unsichtbaren Platzhalter in der Größe, die der Text einnähme. (Siehe FAQ 11 zu \NewDocumentCommand
.)
\NewDocumentCommand { \fib@hide } { m } {% \iffibhideanswer \phantom{#1}% \else #1% \fi }
Der zentrale Befehl des Beispiels ist \fib@makebox
: Er erzeugt die Darstellung der Lücke.
\NewDocumentCommand { \fib@makebox }{ m }{%
Zunächst wird die Breite der Lücke gemessen, wie sie wäre, wenn sie „maschinell“ ausgefüllt wäre.
\settowidth{\fib@width}{\tikz\node[fill in]{#1};}%
Dann wird die tatsächliche Lücke als {tikzpicture}
erstellt, wobei die Ausrichtung innerhalb der Zeile sich an der Grundlinie des Lücken-node
s orientieren soll.
\begin{tikzpicture}[baseline=(fill in node.base)]
Der node
erhält den Alias-Stil fill in
(s. o.) und seine Breite wird so festgelegt, dass auch für handschriftliches Ausfüllen genügend Platz ist. Der Inhalt der Lücke wir mit \fib@hide
verarbeitet und damit je nach Einstellung ein- oder ausgeblendet.
\node (fill in node) [fill in, text width=\fib@widthfactor*\fib@width] {% \fib@hide{#1}% }; \end{tikzpicture}%
Ende von \NewD…{\fib@makebox}
}
Nachdem alle Vorbereitungen getroffen sind, können wir nun endlich \fib
definieren. Der Befehl soll eine Sternform (s
→ #1
) sowie ein optionales (o
→ #2
) und ein obligatorisches (m
→ #3
) Argument haben. Außerdem sollen die Änderungen, die die Definition vornehmen (Lösung ein-/ausblenden und Lückenstil), auf die jeweilige Lücke beschränkt sein, weshalb wir der Definition eine Gruppe (FAQ 7) hinzufügen einschließen.
\NewDocumentCommand { \fib } { s o m }{{%
Zunächst fragen wir ab, ob der Befehl mit Stern (\fib*
) aufgerufen wurde: Wenn ja (T
= true), soll die Antwort eingeblendet werden, egal wie der globale Zustand von \iffibhideanswer
ist.
\IfBooleanT{#1}{\fibhideanswerfalse}%
Dann fragen wir ab, ob ein optionales Argument angeben wurde: Wenn ja, soll dessen Wert als Stil für die Lücke definiert werden. Wir prüfen an dieser Stelle nicht, ob der angegebene Stil tatsächlich existiert.
\IfValueT{#2}{\tikzset{fill in/.style={#2}}}%
Zuletzt prüfen wir, ob wir uns im Mathe- (mmode
) oder Textmodus befinden und reagieren entsprechend. Im Mathemodus unterscheiden wir mit \mathchoice
zusätzlich zwischen den verschiedenen Größen (abgesetzte Formel, Inline-Formel, Hoch-/Tiefstellung, doppelte Hoch-/Tiefstellung).
\ifmmode \mathchoice {\fib@makebox{$\displaystyle#3$}} {\fib@makebox{$\textstyle#3$}} {\fib@makebox{$\scriptstyle#3$}} {\fib@makebox{$\scriptscriptstyle#3$}} \else \fib@makebox{#3}% \fi
Ende der Gruppe und von \NewD…{\fib}
}}
\makeatother
Test und Beispiele
Jetzt gilt es, den neu definierten Befehl in allen Situationen zu testen.
\begin{document}
Durch die Änderung des Alias-Stils können wir den Lücken-Stil global verändern.
%\tikzset{fill in/.style={colored box}}% = Voreinstellung \tikzset{fill in/.style={framed box}} %\tikzset{fill in/.style={underlined box}} %\tikzset{fill in/.style={underbracked box}} %\tikzset{fill in/.style={underoverbracked box}}
Außerdem können wir durch das Ergänzen (append
) oder Ändern einzelner Stile die Darstellung der Lücken weiter beeinflussen.
\tikzset{colored box/.append style={fill=black!10}} \tikzset{underline style/.style={dotted, ultra thick}} \tikzset{bracket style/.style={gray, thick}}
Mit diesem Befehl können wir die Antworten global sichtbar machen. Soll dies nur für einen Abschnitt gelten, kann es mit \fibhideanswertrue
rückgängig gemacht werden.
\fibhideanswerfalse
Zuletzt testen wir den Befehl in den verschiedenen Kontexten.
Ein \fib{kurzes} Beispiel mit einer Inline-Formel: $1 + 2^{\fib{2}} = \fib{5} = \sqrt{25}$. Es geht aber auch in abgesetzten Gleichungen: \begin{equation} 1 + 3 = \fib{4} = \fib{\frac{8}{2}} \end{equation} \begin{equation} (a + b)^2 = \fib{a^2 + 2ab + b^2} \end{equation} \begin{equation} \begin{pmatrix} 1 \\ 2 \\ 3 \end{pmatrix} \times \begin{pmatrix} 4 \\ 5 \\ 6 \end{pmatrix} = \fib{\begin{pmatrix}% ohne Klammern -3 \\ 6 \\ -3 \end{pmatrix}} = \left(\,\fib{\begin{matrix}% mit Klammern -3 \\ 6 \\ -3 \end{matrix}}\,\right) \end{equation} Der Stern zeigt die Lösung \fib*{immer} an. Mit dem optionalen Argument kann der Stil ggf. für eine einzelne \fib*[underlined box]{Lücke} geändert werden
Ende der Datei
\end{document}
Erweiterung für die beamer
-Klasse
Den gezeigten Befehl wollen wir nun noch so erweitern, dass wir in einer beamer
-Präsentation die Antworten nach und nach einblenden können. Dazu greifen wir auf die Overlay-Syntax zurück, bei der die Foliennummer als <3>
angeben werden kann.
Da die Änderungen nur ein paar Zeilen betreffen, betrachten wir nur die relevanten Zeilen. Die vollständige Datei heißt lueckentext_beamer.tex
.
Änderungen in \fib
Wir ergänzen bei \NewDocumentCommand
ein optionales Argument mit benutzerdefinierten Klammern (d{<}{>}
), wodurch sich auch die Argumentnummern verschieben, was wir anpassen müssen.
Sofern eine Overlay-Spezifikation angegeben ist, werten wir diese mit dem beamer
-Befehl \only
aus und setzen \iffibhideanswer
.
\NewDocumentCommand { \fib } { s d{<}{>} o m }{{% \IfBooleanT{#1}{\fibhideanswerfalse}% \IfValueT{#2}{\only<#2>{\fibhideanswerfalse}}% \IfValueT{#3}{\tikzset{fill in/.style={#3}}}% \ifmmode \mathchoice {\fib@makebox{$\displaystyle#4$}} {\fib@makebox{$\textstyle#4$}} {\fib@makebox{$\scriptstyle#4$}} {\fib@makebox{$\scriptscriptstyle#4$}} \else \fib@makebox{#4}% \fi \IfValueT{#2}{}% }}
Overlay-Funktion testen
Für das erste Beispiel hätten wir \fib
gar nicht ändern brauchen, denn wir rufen \only
direkt auf. Das schränkt uns aber insofern ein, als dass die Lücken immer alle denselben Status haben.
\begin{frame}{Beispiel 1} \only<2->{\fibhideanswerfalse} A \fib{short} example with math $1 + 2^{\fib{2}} = \fib{5} = \sqrt{25}$. \end{frame}
Im zweiten Beispiel geben wir die Nummer der Folie explizit an, wobei <2->
bspw. „ab der zweiten Folie“, <2>
aber „nur auf der zweiten Folie“ bedeutet.
\begin{frame}{Beispiel 2} A \fib<2->{short} example with math $1 + 2^{\fib<3->{2}} = \fib<4->{5} = \sqrt{25}$. \end{frame}
Im letzten Beispiel nutzen wir aus, dass beamer
auch selbständig zählen kann: Wir schreiben +
statt einer Zahl und geben bei der ersten Lücke mit (1)
eine Verschiebung an, damit zunächst alle Lücken unausgefüllt sind.
\begin{frame}{Beispiel 3} A \fib<+(1)->{short} example with math $1 + 2^{\fib<+->{2}} = \fib<+->{5} = \sqrt{25}$. \end{frame}
Die Datei beginnt wie üblich mit der Angabe der Dokumentklasse (FAQ 2) und ein paar Standardpaketen (FAQ 3 und FAQ 4). Dabei müssen wir bei
inputenc
die zur Datei passende Kodierung angeben.