Lückentexte

Im ersten Beispiel meines neuen Blogs -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.

Beispiel für einen Lückentext

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.

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.

\documentclass{scrartcl}
 
\usepackage[utf8]{inputenc}
\usepackage[T1]{fontenc}
\usepackage{lmodern}

Als nächstes laden wir die Pakete xparse, das eine neue und bequemere Syntax für die Definition eigener Befehle bereitstellt, sowie tikz, das wir zum Erzeugen der Lücken nutzen werden, mit der calc-Erweiterung.

\usepackage{xparse}
\usepackage{tikz}
   \usetikzlibrary{calc}

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},
   },
Farbig hinterlegte Lücke

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,
   },
Umrahmte Lücke

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={},
Unterstrichene Lücke

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={},
Lücke mit eckiger Klammer darunter

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}}
      },
   },
Lücke mit eckiger Klammer darüber und darunter

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-nodes 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}