%<*driver> \def\nameofplainTeX{plain} \ifx\fmtname\nameofplainTeX \input l3docstrip.tex \keepsilent \askforoverwritefalse \preamble ======================================================================== osglecture-modes - generalized portable document modes (C) 2026 Matthias Werner E-mail: matthias.werner@informatik.tu-chemnitz.de Released under the LaTeX Project Public License v1.3c or later See https://www.latex-project.org/lppl.txt ======================================================================== \endpreamble \postamble This work is "maintained" (as per LPPL maintenance status) by Matthias Werner. \endpostamble \generate{ \file{osglecture-modes.sty} {\from{osglecture-modes.dtx}{package}} \file{osglecture-modes-overlays.code.tex} {\from{osglecture-modes.dtx}{overlays}} \file{osglecture-modes-ltxtalk.sty} {\from{osglecture-modes.dtx}{ltxtalk}} } % Lua cannot parse the TeX-style %% lines emitted by docstrip's default % preamble and postamble. Generate the helper separately without either. \nopreamble \nopostamble \generate{ \file{osglecture-modes-ltxtalk.lua} {\from{osglecture-modes.dtx}{lua}} } \expandafter\endbatchfile \fi \iffalse % %<*package> \ProvidesExplPackage{osglecture-modes} {2026-09-27} {v0.5.1} {Portable document-modes} % %<*driver> \fi \ExplSyntaxOff \documentclass[add-bib=false]{osgdoc} \usepackage{enumitem} \usepackage{microtype} \usepackage{needspace} \newarg\modearg{<}{>} \usepackage[ languages={de,en}, map={de=german,en=english}, targetlang={job=3}, load babel ]{langselect} \usepackage{osglecture-modes} \def\pkgname{osglecture-modes} \def\thepkg{\pkg*{\pkgname}} \makeindex \setcnltx{ name = osglecture-modes, package = osglecture-modes, version = \UseVersionOf{osglecture-modes.sty}, date = \UseDateOf{osglecture-modes.sty}, title = {\ldeen{Portable Dokumentmodi}{Portable Document Modes}}, info = {\ldeen{Erweiterbare Modusgraphen für \LaTeX-Dokumente} {Extensible mode graphs for \LaTeX\ documents}}, authors = {Matthias Werner[matthias.werner@informatik.tu-chemnitz.de]}, abstract = { \ldeen{ \LaTeX-Pakete wie \needpackage{beamer} und \needpackage{ltx-talk} bieten das Konzept von \emph{Modi} an, bei dem, abhängig vom Dokumentmodus, nur bestimmte Dokumentteile gezielt übersetzt werden. Dieses Paket erweitert das Konzept und verwaltet Dokumentmodi als gerichteten azyklischen Graphen. Es ergänzt die Modusschnittstellen von \pkg*{beamer} und \pkg*{ltx-talk}, funktioniert aber ebenso mit gewöhnlichen Dokumentklassen. Es gehört zum \pkg*{osglecture}-Bundle, ist jedoch auch unabhängig davon nutzbar. }{ \LaTeX packages such as \needpackage{beamer} and \needpackage{ltx-talk} offer the concept of \emph{modes}, in which, depending on the document mode, only specific parts of the document are rendered. This package extends this concept and manages document modes as directed acyclic graphs. It complements the mode interfaces of \pkg*{beamer} and \pkg*{ltx-talk}, but also works with standard document classes. It is part of the \pkg*{osglecture} bundle, but can also be used independently of it. } }, url = https://github.com/werner-matthias/osglecture, build-title } \OnlyDescription \begin{document} \section{\ldeen{Schnellstart}{Quick start}} \ldeen{ Wer Beamer oder \pkg*{ltx-talk} verwendet, kennt Modusspezifikationen wie \code{} oder \code{}. \thepkg\ macht solche Dokumentmodi auch in anderen Klassen nutzbar und erlaubt eigene Modi mit mehreren Elternmodi. Ein gemeinsamer Quelltext kann so beispielsweise Folien, ein Handout und ein Skript enthalten. }{ Users of Beamer or \pkg*{ltx-talk} will recognize mode specifications such as \code{} or \code{}. \thepkg\ makes document modes available in other classes as well and allows custom modes with multiple parent modes. One source can thus contain slides, a handout, and a script. } \begin{example}[code-only,gobble=0] \documentclass{article} \usepackage[mode=script]{osglecture-modes} \begin{document} Common text. \lecturemode{Text for printed documents.} \lecturemode{Text for presentations.} \IfLectureModeTF{script}{Detailed explanation.}{Short explanation.} \end{document} \end{example} \ldeen{ Das Beispiel gibt den gemeinsamen Text, den Text für Druckfassungen und die ausführliche Erklärung aus. Der Präsentationstext entfällt, weil \code{script} den Modus \code{print}, aber nicht \code{presentation} aktiviert. \code{mode=handout} würde stattdessen beide Textvarianten und die kurze Erklärung auswählen. Die Paketoption ändert dabei nicht die Dokumentklasse. }{ The example prints the common text, the text for printed documents, and the detailed explanation. Presentation text is omitted: \code{script} activates \code{print}, but not \code{presentation}. Selecting \code{mode=handout} would print both variants and the short explanation instead. The package option does not change the document class. } \ldeen{ Das Kernpaket benötigt einen aktuellen \LaTeX-Kernel. Es kann unabhängig von \cls*{osglecture}, OLLM und Projektverzeichnissen verwendet werden. Laden Sie es gewöhnlich nach der Dokumentklasse. Ohne explizite Moduswahl erkennt es Beamer und \pkg*{ltx-talk}; bei anderen Klassen wählt es \code{article}. Nur die portablen Textfilter aus Abschnitt~\ref{sec:ltxtalk-filter} benötigen Lua\LaTeX. }{ The core package needs a current \LaTeX\ kernel. It works independently of \cls*{osglecture}, OLLM, and project directories. Normally, load it after the document class. Without an explicit mode selection it detects Beamer and \pkg*{ltx-talk}; for other classes it selects \code{article}. Only the portable text filters in Section~\ref{sec:ltxtalk-filter} require Lua\LaTeX. } \section{\ldeen{Modi und Vererbung}{Modes and inheritance}} \label{sec:model} \ldeen{ Ein ausgewählter Modus aktiviert auch alle seine Eltern, deren Eltern usw. Mehrere Eltern sind erlaubt; Kreise sind es nicht. Anders als bei einer reinen Baumstruktur kann deshalb \code{handout} sowohl zu \code{presentation} als auch zu \code{print} gehören. Die folgenden Modi sind bereits deklariert; die Tabelle nennt jeweils die unmittelbaren Eltern. }{ Selecting a mode also activates its parents, their parents, and so on. Multiple parents are allowed, cycles are not. Unlike a simple tree, this lets \code{handout} belong to both \code{presentation} and \code{print}. The following modes are predefined; the table lists their direct parents. } \begin{center} \begin{tabular}{ll} \textbf{\ldeen{Modus}{Mode}} & \textbf{\ldeen{Eltern}{Parents}} \\\hline \code{presentation} & --- \\ \code{beamer}, \code{projector}, \code{trans} & \code{presentation} \\ \code{handout} & \code{presentation}, \code{print} \\ \code{longform}, \code{print} & --- \\ \code{article} & \code{longform}, \code{print} \\ \code{script} & \code{article} \\ \code{web} & \code{longform} \\ \code{*} & --- \\ \end{tabular} \end{center} \ldeen{ \code{slides} ist ein Alias für \code{projector}, kein zusätzlicher Modus. \code{all} ist eine stets wahre Abfrage. Der Modus \code{*} dient der Textfilterung (Abschnitt~\ref{sec:ltxtalk-filter}). }{ \code{slides} is an alias for \code{projector}, not a separate mode. \code{all} is an always-true query. Mode \code{*} is used for text filtering (Section~\ref{sec:ltxtalk-filter}). } \subsection{\ldeen{Eigene Modi und mehrere aktive Modi}{Custom and simultaneous modes}} \begin{example}[code-only,gobble=0] \documentclass{article} \usepackage{osglecture-modes} \DeclareLectureMode[parents={article,print}]{reading} \DeclareLectureModeAlias{reader}{reading} \SetLectureModes{reader} \FinalizeLectureModes \begin{document} \lecturemode{Reading edition.} \lecturemode{Extended explanation.} \end{document} \end{example} \ldeen{ \cs{SetLectureModes} ersetzt die bisherige Auswahl; \cs{AddLectureModes} ergänzt sie. Beide nehmen kommaseparierte Listen entgegen. Die Auswahl \code{slides,web} aktiviert beispielsweise gleichzeitig \code{projector}, \code{presentation}, \code{web} und \code{longform}. Die Dokumentklasse und ihre nativen Ausgabeoptionen bleiben davon unberührt. }{ \cs{SetLectureModes} replaces the previous selection; \cs{AddLectureModes} extends it. Both accept comma-separated lists. For instance, selecting \code{slides,web} activates \code{projector}, \code{presentation}, \code{web}, and \code{longform} together. This does not change the document class or its native output options. } \subsection{\ldeen{Zeitpunkt der Deklarationen}{When to declare modes}} \ldeen{ Deklarieren Sie Modi, Eltern und Aliase und wählen Sie danach die aktiven Modi, bevor die erste Abfrage erfolgt. \cs{FinalizeLectureModes} prüft den gesamten Graphen, auch inaktive Modi, und schließt diese Konfiguration ab. Die erste Abfrage mit \cs{IfLectureModeTF}, \cs{IfLectureModeT}, \cs{IfLectureModeF} oder einer spezifizierten \cs{lecturemode}-Anweisung erledigt dies bei Bedarf automatisch; spätestens vor \cs{begin}\Marg{document} wird abgeschlossen. Danach sind Deklarationen, \cs{SetLectureModes} und \cs{AddLectureModes} nicht mehr zulässig. Die Aktivierung bereits deklarierter Filtermodi im Dokument ist ein eigener Vorgang, siehe Abschnitt~\ref{sec:ltxtalk-filter}. }{ Declare modes, parents, and aliases and select the active modes before the first query. \cs{FinalizeLectureModes} validates the entire graph, including inactive modes, and closes this configuration. The first query using \cs{IfLectureModeTF}, \cs{IfLectureModeT}, \cs{IfLectureModeF}, or a \cs{lecturemode} instruction with a specification does this automatically if necessary; finalization takes place before \cs{begin}\Marg{document} at the latest. Afterwards, further declarations and changes to the mode selection are no longer allowed. Activating previously declared filter modes in the document is a separate operation; see Section~\ref{sec:ltxtalk-filter}. } \section{\ldeen{Inhalte und Werte auswählen}{Selecting content and values}} \label{sec:selection} \begin{commands} \command{lecturemode}[\modearg{expression}\marg{content}] \ldeen{Gibt den Inhalt aus, wenn der Ausdruck wahr ist. Ohne Spezifikation wird der Inhalt immer ausgegeben.}{Prints the content if the expression is true. Without a specification the content is always printed.} \command{IfLectureModeTF}[\marg{expression}\marg{true}\marg{false}] \ldeen{Führt je nach Ergebnis den zweiten oder dritten Parameter aus.} {Executes the second or third argument according to the query result.} \command{IfLectureModeT}[\marg{expression}\marg{true}] \ldeen{Führt den zweiten Parameter nur bei wahrer Abfrage aus.} {Executes the second argument only if the query is true.} \command{IfLectureModeF}[\marg{expression}\marg{false}] \ldeen{Führt den zweiten Parameter nur bei falscher Abfrage aus.} {Executes the second argument only if the query is false.} \end{commands} \ldeen{ Diese Abfragen akzeptieren einen Modusnamen oder mehrere Namen, getrennt durch \code{||} oder Kommas (Oder-Verknüpfung). Aliase sind erlaubt; unbekannte Namen ergeben einen Fehler. \code{all} trifft immer zu. Ein einzelnes \code{|}, Overlaynummern, Negation und Und-Verknüpfung gehören nicht zu dieser Abfragesyntax. Für verschachtelte Bedingungen können Sie mehrere Abfragen ineinandersetzen. Bei nativen Overlaybefehlen gelten andere Regeln für Kommas und \code{|}, siehe Abschnitt~\ref{sec:backend-interaction}. }{ These queries accept a mode name or several names separated by \code{||} or commas (disjunction). Aliases are allowed; unknown names cause an error. \code{all} always matches. A single \code{|}, overlay numbers, negation, and conjunction are not part of this query syntax. Nest queries to express combined conditions. Native overlay commands use different rules for commas and \code{|}; see Section~\ref{sec:backend-interaction}. } \begin{example}[code-only,gobble=0] \lecturemode{Material for readers.} \IfLectureModeT{print}{\IfLectureModeF{handout}{Extended printed text.}} \end{example} \subsection{\ldeen{Modusabhängige Werte}{Mode-dependent values}} \begin{commands} \command{ModeValue}[\marg{specification}] \ldeen{Expandiert zu einem ausgewählten Wert. Die Spezifikation besteht aus mit \code{|} getrennten Einträgen der Form \code{modus=wert}. \code{default=wert} oder ein Eintrag ohne \code{=} gibt den Ersatzwert an. Ohne Treffer und ohne Ersatzwert ist das Ergebnis leer.} {Expands to a selected value. The specification consists of entries of the form \code{mode=value}, separated by \code{|}. Use \code{default=value} or an entry without \code{=} for the fallback. With no match and no fallback the result is empty.} \end{commands} \begin{example}[code-only,gobble=0] % With graphicx loaded: \includegraphics[width=\ModeValue{5cm|handout=8cm}]{plot} \end{example} \ldeen{ Die Reihenfolge der Einträge in der Spezifikation entscheidet nicht über die Auswahl. Maßgeblich ist \cs{ActiveLectureModes}: Eltern stehen vor ihren Kindern, die ausgewählten Modi werden in Auswahlreihenfolge durchlaufen; jeder Modus kommt nur einmal vor. Der erste Modus mit einem Eintrag gewinnt, \code{default} kommt zuletzt. Verwenden Sie hier die kanonischen Namen, also beispielsweise \code{projector} statt des Alias \code{slides}. }{ The order of entries in the specification does not determine the result. \cs{ActiveLectureModes} supplies the priority: parents precede their children, selected modes are traversed in selection order, and each mode occurs only once. The first mode with an entry wins; \code{default} is last. Use canonical names here, for example \code{projector} rather than the alias \code{slides}. } \begin{example}[code-only,gobble=0] % mode=script: ActiveLectureModes = longform,print,article,script \ModeValue{script=8cm|print=6cm|default=5cm} % -> 6cm \ModeValue{script=8cm|default=5cm} % -> 8cm \end{example} \ldeen{ \cs{ModeValue} ist expandierbar und auch für Längen- und Registerzuweisungen geeignet. Es finalisiert den Graphen nicht selbst: Soll es bereits in der Präambel verwendet werden, rufen Sie zuvor \cs{FinalizeLectureModes} auf. Werte dürfen weitere Gleichheitszeichen und Doppelpunkte enthalten; \code{|} dient als Trenner. Verwenden Sie höchstens einen Ersatzwert. }{ \cs{ModeValue} is expandable and can be used in length and register assignments. It does not finalize the graph itself: call \cs{FinalizeLectureModes} first when using it in the preamble. Values may contain further equals signs and colons; \code{|} serves as the separator. Use at most one fallback entry. } \clearpage \section{\ldeen{Konfigurationsreferenz}{Configuration reference}} \subsection{\ldeen{Paketoptionen}{Package options}} \begin{options} \keyval{mode}{name}\Default{auto} \ldeen{Wählt beim Laden einen bereits bekannten Modus. Eigene Modi erst nach dem Laden deklarieren und dann mit \cs{SetLectureModes} wählen. Bei \code{auto} wählt Beamer im Handoutbetrieb \code{handout,beamer}, sonst \code{slides,beamer}; \pkg*{ltx-talk} liefert entsprechend \code{handout}, \code{article} oder \code{slides}. Andere Klassen erhalten \code{article}.} {Selects a mode already known at load time. Declare custom modes after loading and select them with \cs{SetLectureModes}. With \code{auto}, Beamer selects \code{handout,beamer} in handout mode and \code{slides,beamer} otherwise; \pkg*{ltx-talk} selects \code{handout}, \code{article}, or \code{slides} as appropriate. Other classes select \code{article}.} \keybool{full}\Default{true} \ldeen{Aktiviert die Integration in vorhandene Modus- und Overlaybefehle. \code{full=false} lässt diese nativen Befehle unverändert. Portable Abfragen bleiben nutzbar. Fehlende Befehle können weiterhin ergänzt werden.} {Enables integration into existing mode and overlay commands. \code{full=false} leaves these native commands unchanged. Portable queries remain available. Missing commands may still be supplied.} \keybool{patch-overlays}\Default{true} \ldeen{Dieselbe Einstellung wie \option{full}; bei mehrfacher Angabe gilt die zuletzt gesetzte Einstellung.}{The same setting as \option{full}; if specified more than once, the last setting wins.} \keybool{ignorenonframetext}\Default{false} \ldeen{Aktiviert zu Dokumentbeginn die Filterung mit \code{*}. Bloße Angabe der Option bedeutet \code{true}.} {Enables filtering with \code{*} at the start of the document. Naming the option without a value means \code{true}.} \keyval{raw-skip-envs}{list} \ldeen{Ersetzt die Liste der beim Filtern wörtlich zu überspringenden Umgebungen. Vorgabe: \env{verbatim}, \env{Verbatim}, \env{lstlisting}, \env{minted}, \env{comment}. Siehe Abschnitt~\ref{sec:ltxtalk-filter}.} {Replaces the list of environments to skip verbatim while filtering. Default: \env{verbatim}, \env{Verbatim}, \env{lstlisting}, \env{minted}, \env{comment}. See Section~\ref{sec:ltxtalk-filter}.} \keybool{deferred}\Default{false} \ldeen{Für Klassenautoren: unterdrückt die automatische Moduswahl und Overlayintegration beim Laden. Die Klasse wählt den Modus selbst und ruft nach dem Laden ihrer Basisklasse den unten beschriebenen Aktivierungsbefehl auf.} {For class authors: suppresses automatic mode selection and overlay integration at load time. The class selects the mode itself and calls the activation command described below after loading its base class.} \end{options} \subsection{\ldeen{Deklaration und Auswahl}{Declaration and selection}} \begin{commands} \command{DeclareLectureMode}[\oarg{parents=\meta{list}}\marg{mode}] \ldeen{Deklariert einen Modus, optional mit kommaseparierten Eltern. \code{parent} ist ein Synonym für \code{parents}. Eltern müssen spätestens beim Abschluss deklariert sein; verwenden Sie ihre kanonischen Namen.} {Declares a mode, optionally with comma-separated parents. \code{parent} is a synonym for \code{parents}. Parents must be declared by finalization; use their canonical names.} \command{DeclareLectureModeParents}[\marg{mode}\marg{list}] \ldeen{Setzt die vollständige Elternliste neu; ergänzt nicht die alte Liste. Deklariert den Modus bei Bedarf mit.} {Replaces the complete parent list rather than appending to it. Also declares the mode if needed.} \command{DeclareLectureModeAlias}[\marg{alias}\marg{mode}] \ldeen{Deklariert einen alternativen Namen. Aliasketten sind erlaubt, Aliaskreise und unbekannte Endziele nicht.} {Declares an alternative name. Alias chains are allowed; alias cycles and unknown final targets are not.} \command{SetLectureModes}[\marg{list}] \ldeen{Ersetzt die bisherige Auswahl durch die angegebene Liste.} {Replaces the previous selection with the given list.} \command{AddLectureModes}[\marg{list}] \ldeen{Ergänzt die Auswahl. Die Modi müssen bereits deklariert sein; Aliase werden aufgelöst, doppelte Einträge entfernt.} {Extends the selection. Modes must already be declared; aliases are resolved and duplicates removed.} \command{FinalizeLectureModes} \ldeen{Prüft und schließt den Graphen ab. Wiederholter Aufruf ist erlaubt.} {Validates and finalizes the graph. Repeated calls are allowed.} \command{LectureModes} \ldeen{Expandiert zur kommaseparierten Auswahl ohne geerbte Eltern und mit aufgelösten Aliasnamen.}{Expands to the comma-separated selection without inherited parents and with aliases resolved.} \command{ActiveLectureModes} \ldeen{Expandiert zur aktiven Liste einschließlich Eltern. Vor Abschluss ist diese Liste noch nicht berechnet. Beide Listenbefehle finalisieren nicht selbst. Die Reihenfolge bestimmt auch \cs{ModeValue}.} {Expands to the active list including parents. Before finalization this list has not yet been computed. Neither list command finalizes the graph. The order also determines \cs{ModeValue}.} \command{LectureModesActivateOverlays} \ldeen{Bindet nach dem Laden der Basisklasse die Overlaybefehle ein. Nur bei \code{deferred=true} selbst einmal aufrufen. Wählt keinen Modus.} {Integrates overlay commands after loading the base class. Call this once only when using \code{deferred=true}. It does not select a mode.} \end{commands} \section{\ldeen{Zusammenspiel mit Backends}{Backend interaction}} \label{sec:backend-interaction} \ldeen{ Besitzt die geladene Klasse keinen Befehl \cs{mode}, stellt das Paket dessen portable Inlineform bereit. Existiert bereits eine native Implementierung, behandelt das Paket ausschließlich Inlineausdrücke aus registrierten Modi. Ebenso sortieren die patchbaren Overlaybefehle und -Decoder von Beamer und \pkg*{ltx-talk} eine Winkelspezifikation in folgende Arten ein: }{ If the loaded class has no \cs{mode} command, the package provides its portable inline form. If a native implementation already exists, the package handles only inline expressions composed of registered modes. The patched overlay commands and decoders of Beamer and \pkg*{ltx-talk} likewise sort an angle-bracket specification into the following kinds: } \begin{itemize} \item \ldeen{ \emph{portabel} -- eine mit \code{||} verknüpfte Oder-Liste registrierter Modi (oder \code{all}). Sie wird vollständig vom Paket ausgewertet und funktioniert auch ohne Overlaybackend. }{ \emph{portable} -- a \code{||}-separated disjunction of registered modes (or \code{all}). Evaluated entirely by the package; works without any overlay backend. } \item \ldeen{ \emph{modusqualifiziert} -- jedes mit \code{|} getrennte Segment ist ein registrierter Modus, eine Form \code{modus:rest} oder ein rein numerisches Overlayatom. Die portablen Anteile wertet das Paket aus; die aktiven nativen Reste werden zu einer einzigen Backendspezifikation zusammengesetzt. So wird \code{} portabel entschieden und dem Backend nur der zutreffende Overlaybereich gereicht. }{ \emph{mode-qualified} -- every \code{|}-separated segment is a registered mode, a \code{mode:tail} form, or a purely numeric overlay atom. The package evaluates the portable parts and recombines the surviving native tails into a single backend specification. This lets \code{} be decided portably while only the matching overlay range reaches the backend. } \item \ldeen{ \emph{nativ} -- alles Übrige: Backend-Modusnamen, \code{|}-Modusskopierung über unbekannte Modi, Actionspezifikationen und native Modusschalter mit unbekannten Namen. Wird unverändert an das Backend delegiert. }{ \emph{native} -- everything else: backend mode names, \code{|} mode scoping over unknown modes, action specifications and native mode switches with unknown names. Delegated to the backend unchanged. } \end{itemize} \ldeen{ Bei Backendbefehlen sind \code{|} und \code{,} innerhalb von \code{<...>} stets Backendsyntax; \code{||} ist der einzige portable Oder-Operator. Ein Ausdruck, der portable Modi mit nativen Overlayatomen mischt, muss dies also über \code{||} oder die Form \code{modus:rest} kenntlich machen. Fehlt der geladenen Klasse ein Overlaybackend, wird eine native oder unerkannte Spezifikation zu \emph{anzeigen} abgeschwächt. Alle diese Eingriffe lassen sich mit \code{patch-overlays=false} abschalten. }{ For backend commands, \code{|} and \code{,} inside \code{<...>} are always backend syntax; \code{||} is the only portable disjunction operator, so an expression that mixes portable modes with native overlay atoms must signal this with \code{||} or the \code{mode:tail} form. When the loaded class has no overlay backend, a native or unrecognised specification degrades to \emph{show}. All of these patches can be turned off with \code{patch-overlays=false}. } \subsection{\ldeen{Eigene Befehle overlayfähig machen}{Making custom commands overlay-aware}} \begin{commands} \command{MakeOverlayAwareCommand}[\oarg{options}\marg{command}] \ldeen{Erweitert einen vorhandenen Befehl um eine unmittelbar folgende Winkelspezifikation. Registrierte Modi werden portabel ausgewertet; echte Overlays werden vom vorhandenen Backend ausgewertet.} {Adds an angle-bracket specification immediately after an existing command. Registered modes are evaluated portably; genuine overlays are evaluated by the available backend.} \end{commands} \begin{options} \keyval{arguments}{signature}\Default{\ldeen{leer}{empty}} \ldeen{Argumentsignatur des Originalbefehls in der Syntax von \cs{NewDocumentCommand}, beispielsweise \code{m} oder \code{s o}. Sie muss alle nach der Winkelspezifikation folgenden Argumente beschreiben, damit diese auch im ausgelassenen Zweig korrekt gelesen werden.} {Argument signature of the original command in \cs{NewDocumentCommand} syntax, for example \code{m} or \code{s o}. It must describe all arguments following the angle specification so they are consumed correctly even when the command is omitted.} \keyval{default-mode}{mode}\Default{\ldeen{leer}{empty}} \ldeen{Modusspezifikation für Aufrufe ohne Winkelargument. Ohne diese Option wird ein Aufruf ohne Winkelargument unverändert ausgeführt.} {Mode specification for calls without an angle argument. Without this option, a call without an angle argument executes unchanged.} \keychoice{fallback}{flatten,omit,error}\Default{flatten} \ldeen{Verhalten bei echten Overlays ohne natives Backend: \code{flatten} führt den Befehl aus, \code{omit} lässt ihn einschließlich Argumenten aus, \code{error} meldet einen Fehler.} {Behavior for genuine overlays without a native backend: \code{flatten} executes the command, \code{omit} omits it and its arguments, and \code{error} reports an error.} \end{options} \begin{example}[code-only,gobble=0] \NewDocumentCommand{\Remark}{m}{\textbf{#1}} \MakeOverlayAwareCommand[arguments=m]{\Remark} % In the document: \Remark{For the printed edition.} \end{example} \ldeen{Die Winkelspezifikation steht vor Stern und optionalen Argumenten des Originalbefehls, beispielsweise:} {The angle specification precedes the original command's star and optional arguments, for example:} \begin{example}[code-only,gobble=0] \MakeOverlayAwareCommand [arguments={s o},default-mode=slides,fallback=flatten]{\\} Text\\[3ex] \end{example} \section{\ldeen{Filtermodi}{Filter modes}} \label{sec:ltxtalk-filter} \ldeen{ \thepkg\ verallgemeinert Beamers eigenes \cs{mode*} zu einem portablen Mechanismus: \code{*} ist selbst nur ein vorinstallierter Modus (siehe Abschnitt~\ref{sec:model}), und das Paket unterscheidet nicht zwischen ihm und jedem anderen, selbst deklarierten \emph{Filtermodus}. Ist ein Filtermodus aktiv, wird alles außerhalb registrierter \emph{Inhaltsumgebungen} (per Vorgabe \env{frame}, falls vorhanden) verworfen, mit registrierten Ausnahmen. Das funktioniert unter Beamer, \pkg*{ltx-talk} und mit gewöhnlichen Klassen wie \cls*{article} gleichermaßen -- eine Prüfung mit Aufgaben-, Lösungs- und Bewertungsumgebungen benötigt weder Beamer noch \pkg*{ltx-talk}. }{ \thepkg\ generalizes Beamer's own \cs{mode*} into a portable mechanism: \code{*} is itself just a pre-installed mode (see Section~\ref{sec:model}), and the package does not distinguish between it and any other, self-declared \emph{filter mode}. While a filter mode is active, everything outside registered \emph{unit environments} (by default \env{frame}, if present) is dropped, with registered exceptions. This works equally under Beamer, \pkg*{ltx-talk}, and with ordinary classes such as \cls*{article} -- an exam with task, solution, and grading environments needs neither Beamer nor \pkg*{ltx-talk}. } \ldeen{ Die tatsächliche Filterung benötigt Lua\LaTeX\ und läuft, wo möglich, über das eigene Backend: Ist ein natives \cs{mode*} vorhanden (Beamer) \emph{und} sind keine Ausnahmen über dessen feste Positivliste hinaus registriert, delegiert \code{*} unverändert dorthin -- ohne Lua\LaTeX\ und ohne Verhaltensänderung an bestehenden Beamer-Dokumenten. Sobald eigene Ausnahmen registriert sind oder ein anderer Filtermodus als \code{*} verwendet wird, übernimmt zwingend der portable Scanner dieses Pakets, da kein Backend eine beliebige, selbst deklarierte Positivliste kennen kann. }{ The actual filtering needs Lua\LaTeX\ and runs through the native backend where possible: if a native \cs{mode*} exists (Beamer) \emph{and} no exceptions beyond its fixed allow-list are registered, \code{*} delegates there unchanged -- no Lua\LaTeX, no behavior change for existing Beamer documents. As soon as custom exceptions are registered, or a filter mode other than \code{*} is used, the package's own portable scanner necessarily takes over, since no backend can know about an arbitrary, self-declared allow-list. } \begin{example}[code-only,gobble=0] \usepackage[ignorenonframetext]{osglecture-modes} \end{example} \ldeen{ Die Paketoption \code{ignorenonframetext} (Vorgabe bei bloßer Nennung: wahr) aktiviert den eingebauten Modus \code{*} -- entspricht also einem \cs{mode*} zu Dokumentbeginn. Für jeden anderen Filtermodus ist keine eigene Paketoption nötig: Er wird wie jeder andere Modus deklariert (\cs{DeclareLectureMode}) und über die Schalterform \cs{mode} -- ohne Inhaltsargument -- direkt im Dokument aktiviert; \cs{mode*} ist dafür nur die aus Beamer-Kompatibilität beibehaltene Syntax für \cs{mode<*>}. \code{raw-skip-envs=\meta{Liste}} ersetzt die Liste wörtlich zu überspringender Umgebungen. Vorgabe sind \env{verbatim}, \env{Verbatim}, \env{lstlisting}, \env{minted} und \env{comment}. Ergänzen Sie eigene Verbatim-Umgebungen in dieser Liste, wenn sie in ausgefilterten Dokumentteilen vorkommen. }{ The package option \code{ignorenonframetext} (default when named bare: true) activates the built-in \code{*} mode -- equivalent to a \cs{mode*} at the start of the document. No package option is needed for any other filter mode: it is declared like any other mode (\cs{DeclareLectureMode}) and activated directly in the document via the switch form \cs{mode} -- without a content argument; \cs{mode*} is only the syntax kept for Beamer compatibility with \cs{mode<*>}. \code{raw-skip-envs=\meta{list}} replaces the built-in list of environments to skip verbatim. The defaults are \env{verbatim}, \env{Verbatim}, \env{lstlisting}, \env{minted}, and \env{comment}. Include custom verbatim environments in this list if they occur in filtered parts of the document. } \ldeen{ Alles außerhalb einer registrierten Inhaltsumgebung wird verworfen, mit eingebauten Ausnahmen: \env{frame} und \env{frame*} selbst (falls vorhanden), \cs{againframe} (falls vorhanden) als eigenständiger Befehl zum Wiederholen einer Inhaltsumgebung, die Gliederungsbefehle einschließlich \cs{appendix}, \cs{lecturemode}, \cs{mode} und \cs{maketitle} passieren den Scanner unverändert; die Gliederungsbefehle erhalten außerdem ein unmittelbar folgendes \cs{label}; ein \cs{label} an anderer Stelle verschwindet dagegen mit dem verworfenen Text. Der optionale, weiterhin \pkg*{ltx-talk}-spezifische Adapter \pkg*{osglecture-modes-ltxtalk} ergänzt \cs{lecture} als weitere allgemeine Ausnahme und setzt die Filterung nach jedem \cs{lecture} fort. }{ Everything outside a registered unit environment is dropped, with built-in exceptions: \env{frame} and \env{frame*} themselves (if present), \cs{againframe} (if present) as a standalone command for repeating a unit environment, structural commands including \cs{appendix}, \cs{lecturemode}, \cs{mode}, and \cs{maketitle} pass through the scanner unchanged; the structural commands also preserve an immediately following \cs{label}, while a \cs{label} elsewhere in dropped text vanishes along with it. The optional, still \pkg*{ltx-talk}-specific adapter \pkg*{osglecture-modes-ltxtalk} adds \cs{lecture} as a further exception and resumes filtering after every \cs{lecture}. } \begin{infobox}{\ldeen{Beginn der portablen Filterung}{When portable filtering starts}} \ldeen{Im aktuellen Paket aktiviert der Modusschalter die Filterung, das tatsächliche Überspringen beginnt aber erst nach \cs{maketitle}. Beim Zusatzpaket für \pkg*{ltx-talk} startet es auch nach \cs{lecture}. Text davor wird normal verarbeitet. Ein Dokument ohne einen solchen Aufruf wird durch den Schalter allein noch nicht gefiltert. Dies betrifft den portablen Filter; bei Delegation an Beamer gilt dessen natives Verhalten.} {In the current package, the mode switch enables filtering, but actual skipping starts only after \cs{maketitle}. With the additional package for \pkg*{ltx-talk}, it also starts after \cs{lecture}. Earlier text is processed normally. A document without such a call is not filtered by the switch alone. This applies to the portable filter; delegation to Beamer retains its native behavior.} \end{infobox} \subsection{\ldeen{Ausnahmen registrieren}{Registering exceptions}} \begin{commands} \command{DeclareLectureKeepCommand}[\oarg{mode=\meta{name}}\marg{command}] \ldeen{Erhält einen bereits definierten Befehl mit genau einem Pflichtargument, etwa einen eigenen Eingabebefehl. Befehl und Argument werden normal ausgeführt.}{Keeps an already defined command with exactly one mandatory argument, such as a custom input command. The command and its argument are executed normally.} \command{DeclareLectureKeepLabelCommand}[\oarg{mode=\meta{name}}\marg{command}] \ldeen{Wie oben, aber nur unmittelbar nach einem Gliederungsbefehl. Damit kann ein eigener Label-Befehl das eingebaute \cs{label} ersetzen, ohne Labels in verworfenem Text zu erhalten.} {As above, but only immediately after a structural command. This lets a custom label command replace the built-in \cs{label} without preserving labels in discarded text.} \command{DeclareLectureKeepEnvironment}[\oarg{mode=\meta{name}}\marg{list}] \ldeen{Erhält die Umgebungen einer kommaseparierten Liste. Sie müssen bereits definiert sein. Ihr Inhalt wird normal verarbeitet; nach dem Ende einer solchen Umgebung wird wieder gefiltert.} {Keeps the environments in a comma-separated list. They must already be defined. Their content is processed normally; filtering resumes after each such environment ends.} \end{commands} \ldeen{ Ohne \code{mode=} gilt die Ausnahme in jedem Filtermodus. Mit \code{mode=\meta{name}} gilt sie nur, wenn dieser Modus aktiv ist. Dabei zählt auch die Vererbung: Ein ausgewählter Modus mit mehreren Filtermodi als Eltern vereinigt deren Ausnahmen. Registrieren Sie die Ausnahmen in der Präambel, bevor Sie die Filterung aktivieren. }{ Without \code{mode=}, the exception applies in every filter mode. With \code{mode=\meta{name}}, it applies only when that mode is active. Inheritance counts too: a selected mode with multiple filter modes as parents combines their exceptions. Register exceptions in the preamble before activating filtering. } \begin{example}[code-only,gobble=0] \documentclass{article} \usepackage{osglecture-modes} % compile with LuaLaTeX \NewDocumentEnvironment{task}{}{}{} \DeclareLectureKeepEnvironment{task} \DeclareLectureMode{solutions} \NewDocumentEnvironment{solution}{}{}{} \DeclareLectureKeepEnvironment[mode=solutions]{solution} \title{Exercises} \author{} \date{} \begin{document} \mode \maketitle This text is skipped. \begin{task}An exercise.\end{task} \begin{solution}Its solution.\end{solution} \end{document} \end{example} \ldeen{ Das Beispiel gibt den Titel, die Aufgabe und die Lösung aus. Ersetzen Sie \code{\string\mode} durch \cs{mode*}, bleiben Titel und Aufgabe. Ohne Aktivierung eines Filters wird auch der Text außerhalb der Umgebungen normal verarbeitet; die Ausnahmeregistrierung allein blendet nichts aus. Ein Schalter aktiviert den genannten Modus zusätzlich, er ersetzt nicht die bisher aktive Modusmenge. Mehrere aktivierte Filtermodi vereinigen daher ihre Ausnahmen. Eine portable Anweisung zum Deaktivieren bietet das Paket derzeit nicht. }{ The example prints the title, exercise, and solution. Replacing \code{\string\mode} with \cs{mode*} keeps the title and exercise. Without an active filter, text outside the environments is processed normally too; registering exceptions alone does not hide anything. A switch activates the named mode additionally rather than replacing the active mode set. Multiple activated filter modes therefore combine their exceptions. The package currently provides no portable deactivation command. } \subsection{\ldeen{Zusatzpaket für ltx-talk}{Additional package for ltx-talk}} \begin{example}[code-only,gobble=0] \documentclass{ltx-talk} \usepackage[ignorenonframetext]{osglecture-modes-ltxtalk} \end{example} \ldeen{ Das Zusatzpaket lädt das Kernpaket und ergänzt \cs{lecture} als Ausnahme. Es akzeptiert \option{mode}, \option{ignorenonframetext} und \option{raw-skip-envs}. Weitere Kernoptionen geben Sie bei Bedarf beim vorherigen Laden von \thepkg\ an. Für eigene Umgebungen und Befehle gelten dieselben Registrierungsbefehle wie oben. }{ The additional package loads the core package and adds \cs{lecture} as an exception. It accepts \option{mode}, \option{ignorenonframetext}, and \option{raw-skip-envs}. If you need other core options, load \thepkg\ first with those options. Register custom environments and commands as above. } \section{\ldeen{Modusabhängige Befehle definieren}{Defining mode-dependent commands}} \label{sec:behavior} \ldeen{ Für einfache Fallunterscheidungen genügt \cs{IfLectureModeTF}. Soll ein öffentlicher Befehl je nach Dokumentmodus eine eigene Definition erhalten, können Sie dagegen Verhaltensklassen verwenden. Diese bündeln Modi mit derselben Befehlsdefinition, etwa \code{dynamic} für Folien und \code{longform} für Artikel und Skripte. Verhaltensklassen und Dokumentmodi sind getrennte Namenräume; gleiche Namen stellen keine automatische Verbindung her. Das eigenständige Paket deklariert keine Verhaltensklassen. }{ For simple conditional content, \cs{IfLectureModeTF} suffices. To give a public command a different definition depending on the document mode, use behavior classes instead. These group modes that share a command definition, such as \code{dynamic} for slides and \code{longform} for articles and scripts. Behavior classes and document modes have separate namespaces; identical names do not establish an automatic connection. The standalone package does not predeclare behavior classes. } \begin{example}[code-only,gobble=0] \documentclass{article} \usepackage[mode=script]{osglecture-modes} \DeclareModeBehaviorClass{dynamic} \DeclareModeBehaviorClass{longform} \AssignLectureModeBehavior{slides}{dynamic} \AssignLectureModeBehavior{script,article}{longform} \NewDocumentCommand{\BriefRemark}{m}{\textbf{#1}} \NewDocumentCommand{\FullRemark}{m}{\begin{quote}#1\end{quote}} \DeclareModeAwareCommand{\Remark}{dynamic,longform} \DeclareModeAwareCommandImplementation {\Remark}{dynamic}{\BriefRemark} \DeclareModeAwareCommandImplementation {\Remark}{longform}{\FullRemark} \begin{document} \Remark{This is set as a quotation in script mode.} \end{document} \end{example} \begin{commands} \command{DeclareModeBehaviorClass}[\marg{class}] \ldeen{Deklariert eine Verhaltensklasse.}{Declares a behavior class.} \command{AssignLectureModeBehavior}[\marg{modes}\marg{class}] \ldeen{Ordnet einer kommaseparierten Modusliste eine bereits deklarierte Verhaltensklasse zu. Aliase sind erlaubt. Die Zuordnung wird nicht von Elternmodi geerbt: Ordnen Sie die tatsächlich ausgewählten Modi zu.} {Assigns a previously declared behavior class to a comma-separated list of modes. Aliases are allowed. Assignment is not inherited from parent modes: assign the modes actually selected.} \command{DeclareModeAwareCommand}[\marg{command}\marg{classes}] \ldeen{Deklariert einen neuen öffentlichen Befehl und die kommaseparierte Liste der Verhaltensklassen, für die Definitionen vorliegen müssen. Ein vorhandener Befehl wird nicht überschrieben.} {Declares a new public command and the comma-separated list of behavior classes for which definitions are required. An existing command is not overwritten.} \command{DeclareModeAwareCommandImplementation} [\marg{command}\marg{class}\marg{implementation command}] \ldeen{Registriert den Definitionsbefehl für eine Verhaltensklasse. Dieser besitzt selbst die vollständige Argumentsignatur. Verwenden Sie für alle Varianten eine zum gemeinsamen Quelltext passende Signatur.} {Registers the implementation command for a behavior class. That command owns the complete argument signature. Use signatures compatible with the shared source for all variants.} \command{FinalizeModeAwareCommands} \ldeen{Prüft alle geforderten Definitionen, auch für im aktuellen Lauf inaktive Verhaltensklassen, und bindet die öffentlichen Befehle an ihre gewählten Definitionen. Schließt bei Bedarf auch den Modusgraphen ab.} {Checks all required definitions, including behavior classes inactive in the current run, and binds public commands to their selected definitions. Also finalizes the mode graph if necessary.} \end{commands} \ldeen{ Die Auswahl berücksichtigt die mit \cs{SetLectureModes} oder \cs{AddLectureModes} gewählten Modi. Mindestens einer muss eine Verhaltensklasse liefern; mehrere zugeordnete Modi dürfen keine widersprüchlichen Klassen auswählen. Beim ersten Gebrauch eines solchen Befehls, spätestens vor \cs{begin}\Marg{document}, erfolgt der Abschluss automatisch. Danach sind keine weiteren Deklarationen oder Zuordnungen für modusabhängige Befehle möglich. Später aktivierte Filtermodi wählen die Befehlsdefinitionen nicht erneut aus. }{ Selection considers the modes chosen with \cs{SetLectureModes} or \cs{AddLectureModes}. At least one must supply a behavior class; multiple assigned modes must not select conflicting classes. Finalization occurs automatically on first use of such a command, or before \cs{begin}\Marg{document} at the latest. Afterwards, no further declarations or assignments for mode-dependent commands are possible. Activating filter modes later does not reselect command definitions. } \section{\ldeen{Einschränkungen}{Limitations}} \begin{itemize} \item \ldeen{ Ein registrierter Modus darf einem nativen Overlayrest vorangestellt (\code{}), mit \code{|} modusweise gestaffelt (\code{}) oder per \code{||} oder-verknüpft werden. Ein Segment mit einem nicht registrierten Modusnamen macht jedoch die gesamte Spezifikation nativ, und eine Disjunktion mit nacktem \code{|} oder \code{,} über die Grenze zwischen portablem Modus und nativem Atom hinweg (\code{}) ist nicht als portable Oder-Verknüpfung verfügbar; dafür ist \code{||} zu verwenden. }{ A registered mode may prefix a native overlay tail (\code{}), be staggered per mode with \code{|} (\code{}), or be \code{||}-combined with one. A segment carrying an unregistered mode name makes the whole specification native, and a disjunction with a bare \code{|} or \code{,} across the boundary between a portable mode and a native atom (\code{}) is not available as a portable \emph{or}; use \code{||} for that. } \item \ldeen{ Laden Sie die Basisklasse vor \thepkg. Für Klassen, die erst später ihre Basisklasse laden, gibt es die Option \code{deferred=true}. }{ Load the base class before \thepkg. Classes loading their base class later can use \code{deferred=true}. } \item \ldeen{ Fehlt einer Basisklasse \cs{only}, \cs{uncover}, \cs{visible}, \cs{onslide}, \cs{invisible}, \cs{alt}, \cs{pause} oder \cs{temporal}, stellt \thepkg\ eine portable Fassung des \emph{Befehls} bereit. Die entsprechenden Environments (\env{onlyenv}, \env{uncoverenv}, \env{visibleenv}, \env{invisibleenv}) bleiben dort undefiniert; gemeinsamer Quelltext sollte in diesem Fall die Befehlsform verwenden. }{ If a base class lacks \cs{only}, \cs{uncover}, \cs{visible}, \cs{onslide}, \cs{invisible}, \cs{alt}, \cs{pause}, or \cs{temporal}, \thepkg\ provides a portable version of the \emph{command}. The corresponding environments (\env{onlyenv}, \env{uncoverenv}, \env{visibleenv}, \env{invisibleenv}) remain undefined there; shared source should use the command form in that case. } \end{itemize} \clearpage \appendix \section{\ldeen{Bundle-Integration: ein eigener Dokumenttyp} {Bundle integration: a custom document type}} \label{sec:bundle} \ldeen{ Dieser Abschnitt richtet sich an Nutzer von \cls*{osglecture} und OLLM. Für die eigenständige Verwendung von \thepkg\ sind diese Schritte nicht nötig. Als Beispiel ergänzen wir \code{studyguide}, eine druckbare Langform auf Basis von \cls*{scrbook}. Das OLLM-Target bestimmt den Dokumenttyp; dieser wird als Modus ausgewählt. Das Dokumentprofil bestimmt dagegen Basisklasse und Einrichtung des Dokuments. }{ This section is for users of \cls*{osglecture} and OLLM. These steps are not required for standalone use of \thepkg. We add \code{studyguide}, a printable long-form document based on \cls*{scrbook}. The OLLM target specifies the document type, which is selected as a mode. The document profile specifies the base class and document setup instead. } \subsection{\ldeen{Target registrieren}{Registering the target}} \ldeen{Legen Sie in einem eingebundenen OLLM-Definitionspfad die Datei \code{targets/studyguide.toml} an:} {Create \code{targets/studyguide.toml} in a configured OLLM definition path:} \begin{example}[code-only,gobble=0] schema = 1 kind = "target" name = "studyguide" version = "1.0" doctype = "studyguide" family = "publication" unit_scopes = ["a", "as"] \end{example} \ldeen{Geben Sie das Target anschließend in \code{ollmconfig.toml} frei. Der Definitionspfad muss über die lokale OLLM-Konfiguration oder ein installiertes Bundle-Preset eingebunden sein.} {Then enable the target in \code{ollmconfig.toml}. The definition path must be configured through local OLLM settings or an installed bundle preset.} \begin{example}[code-only,gobble=0] [targets.studyguide] languages = ["de", "en"] \end{example} \subsection{\ldeen{Modus und Verhalten festlegen}{Declaring mode and behavior}} \ldeen{Erstellen Sie die für TeX auffindbare Datei \code{osglecture-mode-studyguide.def}. Die Klasse \cls*{osglecture} stellt die Verhaltensklasse \code{longform} bereits bereit.} {Create \code{osglecture-mode-studyguide.def} where TeX can find it. The \cls*{osglecture} class already supplies the \code{longform} behavior class.} \begin{example}[code-only,gobble=0] \ProvidesExplFile{osglecture-mode-studyguide.def} {2026-09-27}{v0.5.1}{studyguide mode setup} \DeclareLectureMode[parents={longform,print}]{studyguide} \AssignLectureModeBehavior{studyguide}{longform} \end{example} \ldeen{Damit sind Abfragen auf \code{studyguide}, \code{longform} und \code{print} wahr. Soll ein Befehl anders arbeiten als in bisherigen Langformen, deklarieren Sie eine eigene Verhaltensklasse und ergänzen Sie die erforderlichen Befehlsdefinitionen nach Abschnitt~\ref{sec:behavior}.} {Queries for \code{studyguide}, \code{longform}, and \code{print} now match. If commands need behavior different from existing long-form documents, declare a separate behavior class and supply the required command definitions as described in Section~\ref{sec:behavior}.} \Needspace{8\baselineskip} \subsection{\ldeen{Dokumentprofil einrichten}{Setting up the document profile}} \ldeen{Legen Sie \code{osglecture-profile-studyguide-scrbook.def} an:} {Create \code{osglecture-profile-studyguide-scrbook.def}:} \begin{example}[code-only,gobble=0] \ProvidesExplFile{osglecture-profile-studyguide-scrbook.def} {2026-09-27}{v0.5.1}{studyguide scrbook profile} \DeclareOsgLectureProfile{studyguide-scrbook} { backend = scrbook, class = scrbook, doctypes = {script,article,studyguide}, mode-setup-file = osglecture-mode-studyguide.def, setup-file = osglecture-studyguide.code.tex } \DeclareOsgLectureProfileClassOptions {studyguide-scrbook}{studyguide}{oneside} \end{example} \ldeen{ \code{mode-setup-file} enthält die Modusdeklarationen und Zuordnungen. Die ebenfalls bereitzustellende Datei \code{osglecture-studyguide.code.tex} enthält die Einrichtung und Formatierung nach dem Laden der Basisklasse. Das Profil umfasst auch \code{script} und \code{article}, damit diese Targets beim Ersetzen des projektweiten Langformprofils verfügbar bleiben. Wählen Sie das Profil in der Projekt- oder Nutzerkonfiguration: }{ \code{mode-setup-file} contains mode declarations and assignments. Also provide \code{osglecture-studyguide.code.tex}, which contains setup and formatting applied after loading the base class. Including \code{script} and \code{article} keeps these targets available when replacing the project's long-form profile. Select the profile in the project or user configuration: } \begin{example}[code-only,gobble=0] [latex.defaults] script_profile = "studyguide-scrbook" \end{example} \ldeen{ Die Klasse lädt die Modusdatei vor Auswahl des neuen Modus. Eine Änderung am Klassenkern ist dafür nicht nötig. Prüfen Sie abschließend den Build für jede freigegebene Sprache: Target, Dokumenttyp und Profil müssen zusammenpassen, die Modusabfragen das erwartete Ergebnis liefern und die verwendeten modusabhängigen Befehle definiert sein. Prüfen Sie auch ein Dokument mit explizitem \code{doctype} und \code{profile} ohne OLLM-Projekt. Die vollständige Profil- und Targetkonfiguration beschreiben die Dokumentationen von \cls*{osglecture} und OLLM. }{ The class loads the mode setup file before selecting the new mode; no change to the class core is needed. Finally, build each enabled language: target, document type, and profile must agree, mode queries must return the expected results, and the mode-dependent commands used must be defined. Also check a document with explicit \code{doctype} and \code{profile} outside an OLLM project. The \cls*{osglecture} and OLLM manuals describe the full profile and target configuration. } % Comment out \OnlyDescription above to include the implementation. \MaybeStop{} \section{\ldeen{Implementierung}{Implementation}} \subsection{\pkg*{osglecture-modes.sty}} \AutoDocInput{osglecture-modes.dtx}{package} \subsection{\pkg*{osglecture-modes-ltxtalk.sty}} \AutoDocInput{osglecture-modes.dtx}{ltxtalk} \subsection{\code{osglecture-modes-ltxtalk.lua}} \AutoDocInput{osglecture-modes.dtx}{lua} \end{document} % %<*package> \prop_new:N \g_osglecture_modes_parents_prop \prop_new:N \g_osglecture_modes_alias_prop \prop_new:N \g_osglecture_modes_active_prop \seq_new:N \g_osglecture_modes_known_seq \seq_new:N \g_osglecture_modes_seed_seq \seq_new:N \g_osglecture_modes_active_order_seq \bool_new:N \g_osglecture_modes_frozen_bool \bool_new:N \g_osglecture_modes_patch_overlays_bool \tl_new:N \g_osglecture_modes_option_mode_tl \msg_new:nnn { osglecture_modes } { frozen } { The~mode~graph~is~already~finalized;~'#1'~cannot~be~changed. } \msg_new:nnn { osglecture_modes } { unknown-mode } { Unknown~mode~'#1'.~Declare~it~before~using~or~finalizing~the~graph. } \msg_new:nnn { osglecture_modes } { unknown-parent } { Mode~'#1'~has~unknown~parent~'#2'. } \msg_new:nnn { osglecture_modes } { unknown-alias-target } { Mode~alias~'#1'~has~unknown~target~'#2'. } \msg_new:nnn { osglecture_modes } { alias-cycle } { Mode~alias~cycle~detected~at~'#1'. } \msg_new:nnn { osglecture_modes } { graph-cycle } { Mode~parent~cycle~detected~at~'#1'. } \bool_new:N \g_osglecture_modes_deferred_bool \bool_new:N \g_osglecture_modes_ignorenonframetext_bool \clist_new:N \g_osglecture_modes_raw_skip_envs_clist % \ldeen{Spiegelt \cs{g_osglecture_modes_raw_skip_envs_clist} vollständig % nach Lua (Datei \file{osglecture-modes-ltxtalk.lua}), damit der % Lua-native Scanner (siehe \cs{osglecture_modes_scan:}) dieselben % Umgebungen roh überspringt wie der TeX-Scanner über % \cs{osglecture_modes_lua_raw_skip:n}. \code{raw-skip-envs} ist ein % vollständiges \code{clist_gset:N}, kein inkrementelles Hinzufügen, daher % wird hier bei jeder Änderung die komplette Lua-Tabelle neu % aufgebaut.}{Fully mirrors \cs{g_osglecture_modes_raw_skip_envs_clist} % into Lua (file \file{osglecture-modes-ltxtalk.lua}), so the Lua-native % scanner (see \cs{osglecture_modes_scan:}) raw-skips the same % environments as the TeX scanner does via % \cs{osglecture_modes_lua_raw_skip:n}. \code{raw-skip-envs} is a full % \code{clist_gset:N}, not an incremental add, so the complete Lua table % is rebuilt from scratch on every change.} % \ldeen{Als eigenständiger, außerhalb jeder Makrodefinition stehender % \code{:n}-Helfer formuliert (statt als Inline-Funktionskörper von % \cs{clist_map_inline:Nn}), damit der einzelne \cs{directlua}-Aufruf ein % ungedoppeltes \code{#1} verwenden kann: ein in einer \emph{umschließenden} % Makrodefinition doppelt geschriebenes \code{##1} ist zwar TeX-seitig % ebenso sicher (die äußere Definition faltet es beim Definieren einmal auf % ein echtes \code{#1} zusammen, bevor \cs{directlua} es je zu Gesicht % bekommt), lässt sich aber von einem rein textuellen Lint auf % \file{osglecture-modes.dtx}s Ebene (siehe % \file{.github/scripts/lint-directlua.pl}) nicht von einem echten, % ungedoppelten Catcode-6-Zeichen unterscheiden, das \cs{directlua} % tatsächlich erreichen würde.}{Written as a standalone \code{:n} helper % outside any macro definition (rather than as \cs{clist_map_inline:Nn}'s % inline function body), so the single \cs{directlua} call can use an % undoubled \code{#1}: a \code{##1} doubled because it sits inside an % \emph{enclosing} macro definition is just as safe on the TeX side (that % outer definition folds it down to a real \code{#1} once, at definition % time, before \cs{directlua} ever sees it), but a purely textual lint at % \file{osglecture-modes.dtx}'s own level (see % \file{.github/scripts/lint-directlua.pl}) cannot tell it apart from a % genuine, undoubled catcode-6 character that would actually reach % \cs{directlua}.} \cs_new_protected:Npn \osglecture_modes_lua_register_raw_skip_env:n #1 { \directlua{ osglecture_modes_ltxtalk.register_raw_skip_env( "\luaescapestring{#1}") } } \cs_new_protected:Npn \osglecture_modes_sync_raw_skip_envs_lua: { \sys_if_engine_luatex:T { \directlua{ require("osglecture-modes-ltxtalk.lua") } \directlua{ osglecture_modes_ltxtalk.clear_raw_skip_envs() } \clist_map_function:NN \g_osglecture_modes_raw_skip_envs_clist \osglecture_modes_lua_register_raw_skip_env:n } } \clist_gset:Nn \g_osglecture_modes_raw_skip_envs_clist { verbatim, Verbatim, lstlisting, minted, comment } \osglecture_modes_sync_raw_skip_envs_lua: \keys_define:nn { osglecture_modes } { mode .tl_gset:N = \g_osglecture_modes_option_mode_tl, mode .initial:n = auto, patch-overlays .bool_gset:N = \g_osglecture_modes_patch_overlays_bool, patch-overlays .initial:n = true, full .meta:n = { patch-overlays = #1 }, full .default:n = true, % \ldeen{Aktiviert den eingebauten Modus \code{*} (siehe die % Modell-Standardvorgaben weiter unten): Alles außerhalb einer % Unit-Umgebung (etwa \cs{frame}) wird dann verworfen, mit den % registrierten Ausnahmen. Entspricht genau dem Aufruf von \cs{mode*} % an derselben Stelle, an der Beamer diese Option selbst % auswertet.}{Activates the built-in \code{*} mode (see the model % defaults below): everything outside a unit environment (such as % \cs{frame}) is then dropped, with the registered exceptions. % Equivalent to calling \cs{mode*} at the same point where Beamer % itself evaluates this option.} ignorenonframetext .bool_gset:N = \g_osglecture_modes_ignorenonframetext_bool, ignorenonframetext .default:n = true, ignorenonframetext .initial:n = false, raw-skip-envs .code:n = { \clist_gset:Nn \g_osglecture_modes_raw_skip_envs_clist {#1} \osglecture_modes_sync_raw_skip_envs_lua: }, % \ldeen{Wird von einer Klasse gesetzt, die dieses Paket noch vor ihrem % eigenen \cs{LoadClass} lädt (osglecture tut das, damit der Modusgraph % rechtzeitig für modusabhängige Klassenoptionen abgeschlossen ist): Die % Auflösung des Blattmodus und das Patchen der Overlays warten dann auf % \cs{LectureModesActivateOverlays}, aufgerufen sobald die Basisklasse % vorhanden ist.}{A class that loads this package before its own % \cs{LoadClass} (osglecture does, so the mode graph is finalized in % time for class options keyed by mode) sets this: leaf-mode resolution % and overlay patching then wait for \cs{LectureModesActivateOverlays}, % called once the base class is present.} deferred .bool_gset:N = \g_osglecture_modes_deferred_bool, deferred .initial:n = false, } \ProcessKeyOptions [ osglecture_modes ] \prg_new_protected_conditional:Npnn \osglecture_modes_if_mutable:n #1 { TF } { \bool_if:NTF \g_osglecture_modes_frozen_bool { \msg_error:nnn { osglecture_modes } { frozen } {#1} \prg_return_false: } { \prg_return_true: } } \cs_new_protected:Npn \osglecture_modes_declare:n #1 { \osglecture_modes_if_mutable:nTF {#1} { \seq_if_in:NnF \g_osglecture_modes_known_seq {#1} { \seq_gput_right:Nn \g_osglecture_modes_known_seq {#1} } } { } } \cs_new_protected:Npn \osglecture_modes_declare_parents:nn #1#2 { \osglecture_modes_if_mutable:nTF {#1} { \osglecture_modes_declare:n {#1} \clist_set:Nn \l_tmpa_clist {#2} \clist_remove_duplicates:N \l_tmpa_clist \prop_gput:NnV \g_osglecture_modes_parents_prop {#1} \l_tmpa_clist } { } } \keys_define:nn { osglecture_modes / declare } { parent .code:n = \osglecture_modes_declare_parents:Vn \l_osglecture_modes_decl_mode_tl {#1}, parents .code:n = \osglecture_modes_declare_parents:Vn \l_osglecture_modes_decl_mode_tl {#1}, } \tl_new:N \l_osglecture_modes_decl_mode_tl \cs_generate_variant:Nn \osglecture_modes_declare_parents:nn { Vn } \cs_generate_variant:Nn \clist_map_inline:nn { Vn } \NewDocumentCommand \DeclareLectureMode { O{} m } { \osglecture_modes_declare:n {#2} \tl_set:Nn \l_osglecture_modes_decl_mode_tl {#2} \keys_set:nn { osglecture_modes / declare } {#1} } \NewDocumentCommand \DeclareLectureModeParents { m m } { \osglecture_modes_declare_parents:nn {#1} {#2} } \NewDocumentCommand \DeclareLectureModeAlias { m m } { \osglecture_modes_if_mutable:nTF {#1} { \prop_gput:Nnn \g_osglecture_modes_alias_prop {#1} {#2} } { } } \seq_new:N \l_osglecture_modes_alias_path_seq \cs_new_protected:Npn \osglecture_modes_resolve:nN #1#2 { \seq_clear:N \l_osglecture_modes_alias_path_seq \tl_set:Nn #2 {#1} \osglecture_modes_resolve_loop:N #2 } \cs_new_protected:Npn \osglecture_modes_resolve_loop:N #1 { \prop_get:NVNT \g_osglecture_modes_alias_prop #1 \l_tmpa_tl { \seq_if_in:NVTF \l_osglecture_modes_alias_path_seq #1 { \msg_error:nnV { osglecture_modes } { alias-cycle } #1 \tl_clear:N #1 } { \seq_put_right:NV \l_osglecture_modes_alias_path_seq #1 \tl_set_eq:NN #1 \l_tmpa_tl \osglecture_modes_resolve_loop:N #1 } } } \cs_new_protected:Npn \osglecture_modes_activate:n #1 { \osglecture_modes_if_mutable:nTF {#1} { \clist_map_inline:nn {#1} { \osglecture_modes_resolve:nN {##1} \l_tmpa_tl \seq_if_in:NVTF \g_osglecture_modes_known_seq \l_tmpa_tl { \seq_if_in:NVF \g_osglecture_modes_seed_seq \l_tmpa_tl { \seq_gput_right:NV \g_osglecture_modes_seed_seq \l_tmpa_tl } } { \msg_error:nnV { osglecture_modes } { unknown-mode } \l_tmpa_tl } } } { } } \NewDocumentCommand \AddLectureModes { m } { \osglecture_modes_activate:n {#1} } % \ldeen{Kompatibilität: Das Setzen eines Modus ersetzt die vorherige % Startmenge.}{Compatibility: setting a mode replaces the previous seed set.} \NewDocumentCommand \SetLectureModes { m } { \osglecture_modes_if_mutable:nTF {#1} { \seq_gclear:N \g_osglecture_modes_seed_seq \osglecture_modes_activate:n {#1} } { } } \NewExpandableDocumentCommand \LectureModes { } { \seq_use:Nn \g_osglecture_modes_seed_seq {,} } \prop_new:N \l_osglecture_modes_visiting_prop \prop_new:N \l_osglecture_modes_visited_prop \bool_new:N \l_osglecture_modes_mark_active_bool \cs_new_protected:Npn \osglecture_modes_visit:n #1 { \prop_if_in:NnTF \l_osglecture_modes_visiting_prop {#1} { \msg_error:nnn { osglecture_modes } { graph-cycle } {#1} } { \prop_if_in:NnF \l_osglecture_modes_visited_prop {#1} { \prop_put:Nnn \l_osglecture_modes_visiting_prop {#1} { } \bool_if:NT \l_osglecture_modes_mark_active_bool { \prop_gput:Nnn \g_osglecture_modes_active_prop {#1} { } % \ldeen{Spiegelt jeden neu aktivierten Modus einzeln nach % Lua -- dies ist der einzige Ort, an dem % \cs{g_osglecture_modes_active_prop} einen Eintrag % bekommt, sowohl über \cs{osglecture_modes_finalize:}s % Graphdurchlauf als auch über % \cs{osglecture_modes_activate_runtime:n} (z.\,B. die % Schalterform \cs{mode}), die \emph{nicht} über % \cs{osglecture_modes_finalize:} läuft. Vorarbeit für % den Lua-Scanner (siehe % development-docs/osglecture-modes-lua-scan.md); ändert % das aktuelle Verhalten nicht.}{Mirrors each % newly-activated mode individually into Lua -- this is % the only place \cs{g_osglecture_modes_active_prop} % gains an entry, both via % \cs{osglecture_modes_finalize:}'s graph walk and via % \cs{osglecture_modes_activate_runtime:n} (e.g. the % switch form \cs{mode}), which does \emph{not} % go through \cs{osglecture_modes_finalize:}. Groundwork % for the Lua scanner (see % development-docs/osglecture-modes-lua-scan.md); does % not change current behaviour.} \sys_if_engine_luatex:T { \directlua{ osglecture_modes_ltxtalk.add_active_mode( "\luaescapestring{#1}") } } } \prop_get:NnNT \g_osglecture_modes_parents_prop {#1} \l_tmpa_tl { \clist_map_inline:Vn \l_tmpa_tl { \seq_if_in:NnTF \g_osglecture_modes_known_seq {##1} { \osglecture_modes_visit:n {##1} } { \msg_error:nnnn { osglecture_modes } { unknown-parent } {#1} {##1} } } } \bool_if:NT \l_osglecture_modes_mark_active_bool { \seq_gput_right:Nn \g_osglecture_modes_active_order_seq {#1} } \prop_remove:Nn \l_osglecture_modes_visiting_prop {#1} \prop_put:Nnn \l_osglecture_modes_visited_prop {#1} { } } } } \cs_new_protected:Npn \osglecture_modes_validate_aliases: { \prop_map_inline:Nn \g_osglecture_modes_alias_prop { \osglecture_modes_resolve:nN {##1} \l_tmpa_tl \seq_if_in:NVF \g_osglecture_modes_known_seq \l_tmpa_tl { \msg_error:nnnn { osglecture_modes } { unknown-alias-target } {##1} {\l_tmpa_tl} } } } \cs_new_protected:Npn \osglecture_modes_finalize: { \bool_if:NF \g_osglecture_modes_frozen_bool { \osglecture_modes_validate_aliases: \prop_gclear:N \g_osglecture_modes_active_prop \sys_if_engine_luatex:T { \directlua{ osglecture_modes_ltxtalk.clear_active_modes() } } \seq_gclear:N \g_osglecture_modes_active_order_seq \prop_clear:N \l_osglecture_modes_visiting_prop \prop_clear:N \l_osglecture_modes_visited_prop \bool_set_false:N \l_osglecture_modes_mark_active_bool \seq_map_inline:Nn \g_osglecture_modes_known_seq { \osglecture_modes_visit:n {##1} } \prop_clear:N \l_osglecture_modes_visiting_prop \prop_clear:N \l_osglecture_modes_visited_prop \bool_set_true:N \l_osglecture_modes_mark_active_bool \seq_map_inline:Nn \g_osglecture_modes_seed_seq { \osglecture_modes_visit:n {##1} } \bool_gset_true:N \g_osglecture_modes_frozen_bool } } \NewDocumentCommand \FinalizeLectureModes { } { \osglecture_modes_finalize: } \NewExpandableDocumentCommand \ActiveLectureModes { } { \seq_use:Nn \g_osglecture_modes_active_order_seq {,} } \cs_generate_variant:Nn \osglecture_modes_visit:n { V } % \ldeen{Aktiviert einen einzelnen, bereits deklarierten Modus zusätzlich % zur aktuellen aktiven Menge -- anders als \cs{AddLectureModes} auch % dann sicher, wenn der Graph schon abgeschlossen ist (finalisiert bei % Bedarf zunächst, wie die erste Abfrage es ohnehin täte). \cs{osglecture_modes_visit:n} % selbst ist dafür unverändert wiederverwendbar: Nach dem Abschluss bleibt % \code{mark\_active\_bool} wahr und die Besucht-Buchführung bestehen, % sodass ein erneuter Aufruf nur die noch nicht besuchten Vorfahren des % neuen Modus markiert, bereits aktive Knoten aber unangetastet % lässt.}{Activates a single, already-declared mode in addition to the % current active set -- unlike \cs{AddLectureModes}, safe even once the % graph is already finalized (finalizing first if needed, as the first % query would anyway). \cs{osglecture_modes_visit:n} itself is directly % reusable for this: after finalization, \code{mark\_active\_bool} stays % true and the visited bookkeeping survives, so a further call only marks % the new mode's not-yet-visited ancestors, leaving already-active nodes % untouched.} \cs_new_protected:Npn \osglecture_modes_activate_runtime:n #1 { \osglecture_modes_resolve:nN {#1} \l_tmpa_tl \seq_if_in:NVTF \g_osglecture_modes_known_seq \l_tmpa_tl { \bool_if:NF \g_osglecture_modes_frozen_bool { \osglecture_modes_finalize: } \osglecture_modes_visit:V \l_tmpa_tl } { \msg_error:nnV { osglecture_modes } { unknown-mode } \l_tmpa_tl } } % \ldeen{\cs{ModeValue} löst eine Spezifikation der Form % \code{modus=wert|modus=wert|...} auf, indem \cs{ActiveLectureModes} von % links nach rechts durchlaufen wird, mit \code{default} als letztem, stets % vorhandenem Kandidaten -- dieselbe Reihenfolge und Priorität, die auch % \cs{__semcat_resolve_presentation:nNNN} verwendet, hier aber expandierbar % formuliert, damit das Ergebnis auch in \cs{dimen}-, \cs{skip}- und % ähnliche Zuweisungen passt, nicht nur in Schlüssel-Wert-Schnittstellen. % Eine Spezifikation ohne passenden Schlüssel und ohne \code{default} % expandiert zu nichts.}{\cs{ModeValue} resolves a "mode=value|mode=value|..." % spec by walking \cs{ActiveLectureModes} left to right, then trying % "default" as the final, always-present candidate -- the same order and % priority \cs{__semcat_resolve_presentation:nNNN} uses, just expressed % expandably so the result drops into \cs{dimen}, \cs{skip}, \cs{count} and % similar assignments, not only key-value interfaces. A spec without a % matching key and without "default" expands to nothing.} % % \ldeen{Die Aufteilung erfolgt anhand von Trennzeichen, nicht durch echtes % Schlüssel-Wert-Parsing, daher darf ein Wert weitere \code{=}-Zeichen oder % Doppelpunkte enthalten (etwa eine URL), aber kein unmaskiertes \code{|}; % führende und folgende Leerzeichen um einen Schlüssel werden entfernt. % \code{=} -- statt des sonst im Paket für eine \code{modus:rest}-Qualifikation % genutzten Doppelpunkts -- ist hier der Schlüssel-Wert-Trenner, weil es von % \cs{ExplSyntaxOn} unberührt bleibt, während ein Doppelpunkt in diesem % Syntaxregime zum Buchstaben wird und daher nie auf den % Kategorie-12-Doppelpunkt träfe, den ein Wert legitim enthalten könnte. % Ein Segment ganz ohne \code{=} gilt als unqualifizierter Wert und steht für % \code{default=} diesen Wert -- das stellt, unter \code{=}, dieselbe Kurzform % wieder her, die die frühere, wegen Catcode-Problemen mit % \cs{ExplSyntaxOn} aufgegebene \code{:}-basierte Spezifikationssyntax des % Pakets kostenlos bot (dort war ein Segment ohne \code{modus:}-Präfix % automatisch unqualifiziert).}{Splitting is delimiter-based, not % key-value parsing, so a value may contain further "=" signs or colons % (an URL, say) but not an unescaped "|"; leading and trailing spaces % around a key are trimmed. "=" (rather than the ":" the rest of this % package uses for a "mode:tail" overlay qualification) is the key/value % separator here because it is untouched by \cs{ExplSyntaxOn}, unlike ":", % which becomes a letter and so could never delimit on the category-12 % colon a value might legitimately contain. A segment carrying no "=" at % all is an unqualified value and stands for "default=" that value -- % restoring, under "=", the convenience the package's former ":"-based % spec syntax offered for free (there, an unqualified segment simply % wasn't "mode:"-prefixed).} \cs_new:Npn \osglecture_modes_value_seg:w #1#2#3#4|#5\q_stop { % \ldeen{\#1 = Kandidatenmodus ; \#2 = verbleibende Kandidaten ; \#3 = % Spezifikation-mit-"|" (für den nächsten Kandidaten aufbewahrt) ; \#4 = % dieses Segment ; \#5 = verbleibende Segmente}{\#1 = candidate mode ; % \#2 = remaining candidates ; \#3 = spec-with-"|" (kept for the next % candidate) ; \#4 = this segment ; \#5 = remaining segments} \osglecture_modes_value_kv_aux {#1} {#2} {#3} {#5} #4 \q_stop } % \ldeen{\#5 ist hier der vollständige Rohtext dieses Segments, begrenzt % nur durch das \cs{q_stop}, das \cs{osglecture_modes_value_seg:w} stets % unmittelbar danach liefert -- anders als ein direktes Matching auf \code{=} % endet dies also immer, ob das Segment eines enthält oder nicht. Ob es % eines enthält, muss aber trotzdem getestet werden -- nur auf einer % Wegwerfkopie, sodass die eigentliche Extraktion unten weiterhin einen % Wert allein durch \cs{q_stop} begrenzen darf (er darf also weitere % \code{=}-Zeichen enthalten): \code{=} \cs{q_no_value} an diese Kopie % anzuhängen und dann auf das erste \code{=} zu matchen bedeutet, dass genau % dann keines im Segment vorlag, wenn vor \cs{q_no_value} nichts mehr % übrig ist, da ein bereits im Segment vorhandenes \code{=} stets mindestens % das angehängte übrig lässt.}{\#5 here is this segment's full raw text, % delimited only by the \cs{q_stop} \cs{osglecture_modes_value_seg:w} % always supplies right after it -- so, unlike matching straight for a % "=", this always terminates, whether or not the segment contains one. % Whether it does still needs testing, but on a throwaway copy, so the % real extraction below is free to keep delimiting a value by % \cs{q_stop} alone (letting it contain further "=" signs): appending "=" % \cs{q_no_value} to that copy and matching the first "=" again means % none was present in the segment exactly when nothing is left before % \cs{q_no_value}, since a "=" already in the segment always leaves at % least the appended one there.} \cs_new:Npn \osglecture_modes_value_kv_aux #1#2#3#4#5\q_stop { \osglecture_modes_value_kv_test:w #5 = \q_no_value {#1} {#2} {#3} {#4} {#5} } \cs_new:Npn \osglecture_modes_value_kv_test:w #1=#2\q_no_value #3#4#5#6#7 { \tl_if_empty:nTF {#2} { \osglecture_modes_value_bare:nnnnn {#3} {#4} {#5} {#6} {#7} } { \osglecture_modes_value_kv_extract:w #7 \q_stop {#3} {#4} {#5} {#6} } } \cs_new:Npn \osglecture_modes_value_kv_extract:w #1=#2\q_stop #3#4#5#6 { % \ldeen{\#1 = Schlüssel dieses Segments ; \#2 = sein Wert (darf selbst % \code{=}-Zeichen enthalten, da er nur durch \cs{q_stop} begrenzt % wurde)}{\#1 = this segment's key ; \#2 = its value (may itself % contain "=" signs, since it was delimited only by \cs{q_stop})} \str_if_eq:eeTF { \tl_trim_spaces:n {#1} } {#3} { \tl_trim_spaces:n {#2} } { \osglecture_modes_value_continue:nnnn {#3} {#4} {#5} {#6} } } % \ldeen{Ein unqualifiziertes (schlüsselloses) Segment gilt nur für den % Kandidaten \code{default} als Treffer -- den stets vorhandenen letzten % Ausweg -- und verhält sich damit genau wie ein explizites % \code{default=...}-Segment; es kommt also nie einem Modus zuvor, der % weiter vorn in \cs{ActiveLectureModes} passt.}{An unqualified (key-less) % segment counts as a match only for the "default" candidate -- the % always-present last resort -- so it behaves exactly like an explicit % "default=..." entry and never pre-empts a mode that matches earlier in % \cs{ActiveLectureModes}.} \cs_new:Npn \osglecture_modes_value_bare:nnnnn #1#2#3#4#5 { \str_if_eq:eeTF {#1} {default} { \tl_trim_spaces:n {#5} } { \osglecture_modes_value_continue:nnnn {#1} {#2} {#3} {#4} } } \cs_new:Npn \osglecture_modes_value_continue:nnnn #1#2#3#4 { \tl_if_empty:nTF {#4} { \osglecture_modes_value_next:nn {#2} {#3} } { \osglecture_modes_value_seg:w {#1} {#2} {#3} #4 \q_stop } } \cs_new:Npn \osglecture_modes_value_next:nn #1#2 { \tl_if_empty:nTF {#1} { } { \osglecture_modes_value_pop:w #1 \q_stop {#2} } } % \ldeen{Entnimmt einen Kandidaten aus einer Kommaliste, die -- wie die % Pipe-Liste oben -- stets ein synthetisches, angehängtes \code{,} trägt % (wieder aus \cs{osglecture_modes_value_start:nn} bzw. bei jedem % weiteren Pop von dieser Funktion selbst reproduziert), sodass auch % \code{default}, der letzte Kandidat, keinen Sonderfall braucht.}{Pops one % candidate off a comma list that -- like the pipe-list above -- always % carries a synthetic trailing "," (again from % \cs{osglecture_modes_value_start:nn}, or reproduced by this function's % own output on every further pop), so "default", the last candidate, % needs no special case either.} \cs_new:Npn \osglecture_modes_value_pop:w #1,#2\q_stop #3 { \osglecture_modes_value_seg:w {#1} {#2} {#3} #3 \q_stop } \cs_new:Npn \osglecture_modes_value_priority: { \ActiveLectureModes , default } \cs_new:Npn \osglecture_modes_value_start:nn #1#2 { \osglecture_modes_value_pop:w #1 , \q_stop {#2|} } \cs_generate_variant:Nn \osglecture_modes_value_start:nn { fn } \NewExpandableDocumentCommand \ModeValue { m } { \osglecture_modes_value_start:fn { \osglecture_modes_value_priority: } {#1} } \prg_new_conditional:Npnn \osglecture_modes_if_active:n #1 { T, F, TF } { \bool_if:NF \g_osglecture_modes_frozen_bool { \osglecture_modes_finalize: } \bool_set_false:N \l_tmpa_bool \tl_set:Nn \l_tmpa_tl {#1} % \ldeen{\code{||} ist der portable Oder-Operator; ein bloßes \code{|} ist % Backend-Overlaysyntax und hat in einem portablen Modusausdruck keine % Bedeutung.}{"||" is the portable disjunction operator; a bare "|" % is backend overlay syntax and has no meaning in a portable mode % expression.} \tl_replace_all:Nnn \l_tmpa_tl {||} {,} \clist_map_inline:Vn \l_tmpa_tl { \str_if_eq:nnTF {##1} {all} { \bool_set_true:N \l_tmpa_bool } { \osglecture_modes_resolve:nN {##1} \l_tmpa_tl \prop_if_in:NVTF \g_osglecture_modes_active_prop \l_tmpa_tl { \bool_set_true:N \l_tmpa_bool } { \seq_if_in:NVF \g_osglecture_modes_known_seq \l_tmpa_tl { \msg_error:nnV { osglecture_modes } { unknown-mode } \l_tmpa_tl } } } } \bool_if:NTF \l_tmpa_bool { \prg_return_true: } { \prg_return_false: } } \NewDocumentCommand \IfLectureModeTF { m +m +m } { \osglecture_modes_if_active:nTF {#1} {#2} {#3} } \NewDocumentCommand \IfLectureModeT { m +m } { \osglecture_modes_if_active:nT {#1} {#2} } \NewDocumentCommand \IfLectureModeF { m +m } { \osglecture_modes_if_active:nF {#1} {#2} } \NewDocumentCommand \lecturemode { d<> +m } { \IfNoValueTF {#1} {#2} { \osglecture_modes_if_active:nT {#1} {#2} } } % \ldeen{Die modusabhängige Befehlsauswahl beruht bewusst auf % Implementationsbefehlen. Die gewählte Implementierung liest den % ursprünglichen Argumentstrom selbst, sodass beliebige Befehlssignaturen % möglich bleiben, ohne dass dieses Paket sie nachbilden müsste.}{Mode-aware % command dispatch is deliberately based on implementation commands. The % selected implementation reads the original argument stream, so arbitrary % command signatures remain possible without duplicating them in this % package.} \seq_new:N \g_osglecture_modes_behavior_classes_seq \prop_new:N \g_osglecture_modes_mode_behavior_prop \seq_new:N \g_osglecture_modes_aware_commands_seq \prop_new:N \g_osglecture_modes_command_requirements_prop \prop_new:N \g_osglecture_modes_command_implementations_prop \bool_new:N \g_osglecture_modes_commands_frozen_bool \tl_new:N \l_osglecture_modes_selected_behavior_tl \cs_generate_variant:Nn \prop_gput:Nnn { NxV, Nxx } \cs_generate_variant:Nn \prop_put:Nnn { NxV } \cs_generate_variant:Nn \prop_get:NnN { NxN } \cs_generate_variant:Nn \prop_get:NnNTF { NxNTF } \msg_new:nnn { osglecture_modes } { commands-frozen } { The~mode-aware~command~registry~is~already~finalized. } \msg_new:nnn { osglecture_modes } { unknown-behavior } { Unknown~mode~behavior~class~'#1'. } \msg_new:nnn { osglecture_modes } { command-exists } { Mode-aware~command~'\token_to_str:N #1'~already~exists. ~Use~a~new~semantic~command~or~an~explicitly~saved~implementation. } \msg_new:nnn { osglecture_modes } { unknown-aware-command } { Mode-aware~command~'\token_to_str:N #1'~has~not~been~declared. } \msg_new:nnnn { osglecture_modes } { missing-implementation } { Mode-aware~command~'#1'~has~no~implementation ~for~behavior~'#2'. } { Register~one~before~finalizing~the~mode-aware~command~registry. } \msg_new:nnnn { osglecture_modes } { undefined-implementation } { Implementation~'#2'~for~mode-aware~command~'#1'~is~undefined. } { Define~the~implementation~command~before~finalizing~the~registry. } \msg_new:nnn { osglecture_modes } { no-active-behavior } { No~behavior~class~is~assigned~to~the~active~leaf~modes. } \msg_new:nnnn { osglecture_modes } { conflicting-behaviors } { Active~leaf~modes~select~both~behavior~'#1'~and~'#2'. } { Assign~all~simultaneously~active~leaf~modes~to~one~behavior~class. } \cs_new_protected:Npn \osglecture_modes_commands_if_mutable:T #1 { \bool_if:NTF \g_osglecture_modes_commands_frozen_bool { \msg_error:nn { osglecture_modes } { commands-frozen } } {#1} } \NewDocumentCommand \DeclareModeBehaviorClass { m } { \osglecture_modes_commands_if_mutable:T { \seq_if_in:NnF \g_osglecture_modes_behavior_classes_seq {#1} { \seq_gput_right:Nn \g_osglecture_modes_behavior_classes_seq {#1} } } } \NewDocumentCommand \AssignLectureModeBehavior { m m } { \osglecture_modes_commands_if_mutable:T { \seq_if_in:NnTF \g_osglecture_modes_behavior_classes_seq {#2} { \clist_map_inline:nn {#1} { \osglecture_modes_resolve:nN {##1} \l_tmpa_tl \seq_if_in:NVTF \g_osglecture_modes_known_seq \l_tmpa_tl { \prop_gput:NVn \g_osglecture_modes_mode_behavior_prop \l_tmpa_tl {#2} } { \msg_error:nnV { osglecture_modes } { unknown-mode } \l_tmpa_tl } } } { \msg_error:nnn { osglecture_modes } { unknown-behavior } {#2} } } } \cs_new_protected:Npn \osglecture_modes_use_aware_command:N #1 { \osglecture_modes_finalize_commands: #1 } \NewDocumentCommand \DeclareModeAwareCommand { m m } { \osglecture_modes_commands_if_mutable:T { \cs_if_exist:NTF #1 { \msg_error:nnN { osglecture_modes } { command-exists } #1 } { \seq_gput_right:Nx \g_osglecture_modes_aware_commands_seq { \cs_to_str:N #1 } \clist_set:Nn \l_tmpa_clist {#2} \clist_remove_duplicates:N \l_tmpa_clist \prop_gput:NxV \g_osglecture_modes_command_requirements_prop { \cs_to_str:N #1 } \l_tmpa_clist \cs_new_protected:Npn #1 { \osglecture_modes_use_aware_command:N #1 } } } } \NewDocumentCommand \DeclareModeAwareCommandImplementation { m m m } { \osglecture_modes_commands_if_mutable:T { \seq_if_in:NxTF \g_osglecture_modes_aware_commands_seq { \cs_to_str:N #1 } { \seq_if_in:NnTF \g_osglecture_modes_behavior_classes_seq {#2} { \prop_gput:Nxx \g_osglecture_modes_command_implementations_prop { \cs_to_str:N #1 / #2 } { \cs_to_str:N #3 } } { \msg_error:nnn { osglecture_modes } { unknown-behavior } {#2} } } { \msg_error:nnN { osglecture_modes } { unknown-aware-command } #1 } } } \cs_new_protected:Npn \osglecture_modes_validate_aware_command:n #1 { \prop_get:NnN \g_osglecture_modes_command_requirements_prop {#1} \l_tmpa_tl \clist_map_inline:Vn \l_tmpa_tl { \seq_if_in:NnTF \g_osglecture_modes_behavior_classes_seq {##1} { \prop_get:NnNTF \g_osglecture_modes_command_implementations_prop {#1 / ##1} \l_tmpb_tl { \cs_if_exist:cF {\l_tmpb_tl} { \msg_error:nnxx { osglecture_modes } { undefined-implementation } {#1} {\l_tmpb_tl} } } { \msg_error:nnxx { osglecture_modes } { missing-implementation } {#1} {##1} } } { \msg_error:nnn { osglecture_modes } { unknown-behavior } {##1} } } } \cs_new_protected:Npn \osglecture_modes_select_behavior: { \tl_clear:N \l_osglecture_modes_selected_behavior_tl \seq_map_inline:Nn \g_osglecture_modes_seed_seq { \prop_get:NnNT \g_osglecture_modes_mode_behavior_prop {##1} \l_tmpa_tl { \tl_if_empty:NTF \l_osglecture_modes_selected_behavior_tl { \tl_set_eq:NN \l_osglecture_modes_selected_behavior_tl \l_tmpa_tl } { \str_if_eq:VVF \l_osglecture_modes_selected_behavior_tl \l_tmpa_tl { \msg_error:nnVV { osglecture_modes } { conflicting-behaviors } \l_osglecture_modes_selected_behavior_tl \l_tmpa_tl } } } } \tl_if_empty:NT \l_osglecture_modes_selected_behavior_tl { \msg_error:nn { osglecture_modes } { no-active-behavior } } } \cs_new_protected:Npn \osglecture_modes_bind_aware_command:n #1 { \prop_get:NxNTF \g_osglecture_modes_command_implementations_prop {#1 / \l_osglecture_modes_selected_behavior_tl} \l_tmpa_tl { \cs_gset_eq:cc {#1} {\l_tmpa_tl} } { \msg_error:nnxx { osglecture_modes } { missing-implementation } {#1} {\l_osglecture_modes_selected_behavior_tl} } } \cs_new_protected:Npn \osglecture_modes_finalize_commands: { \bool_if:NF \g_osglecture_modes_commands_frozen_bool { \osglecture_modes_finalize: \seq_map_inline:Nn \g_osglecture_modes_aware_commands_seq { \osglecture_modes_validate_aware_command:n {##1} } \seq_if_empty:NF \g_osglecture_modes_aware_commands_seq { \osglecture_modes_select_behavior: \seq_map_inline:Nn \g_osglecture_modes_aware_commands_seq { \osglecture_modes_bind_aware_command:n {##1} } } \bool_gset_true:N \g_osglecture_modes_commands_frozen_bool } } \NewDocumentCommand \FinalizeModeAwareCommands { } { \osglecture_modes_finalize_commands: } % \ldeen{Portable Modusspezifikationen werden hier ausgewertet. Echte % Overlayspezifikationen nutzen ein natives \cs{alt}, das entweder den % ursprünglichen Befehl oder einen signaturbewussten Schlucker wählt, % bevor die restlichen Argumente gelesen werden.}{Portable mode % specifications are evaluated here. Genuine overlay specifications use a % native \cs{alt}, which selects either the original command or a % signature-aware gobbler before the remaining arguments are read.} \tl_new:N \l_osglecture_modes_overlay_default_tl \tl_new:N \l_osglecture_modes_overlay_fallback_tl \tl_new:N \l_osglecture_modes_overlay_arguments_tl \prop_new:N \g_osglecture_modes_overlay_default_prop \prop_new:N \g_osglecture_modes_overlay_fallback_prop \msg_new:nnn { osglecture_modes } { overlay-fallback-error } { Overlay~specification~'#1'~requires~a~native~overlay~backend. } \keys_define:nn { osglecture_modes / overlay-aware } { arguments .tl_set:N = \l_osglecture_modes_overlay_arguments_tl, default-mode .tl_set:N = \l_osglecture_modes_overlay_default_tl, fallback .choices:nn = { flatten, omit, error } { \tl_set_eq:NN \l_osglecture_modes_overlay_fallback_tl \l_keys_choice_tl }, fallback .initial:n = flatten, } \cs_new_protected:Npn \osglecture_modes_overlay_original:n #1 { \use:c { osglecture_modes_overlay_original_#1 } } \cs_new_protected:Npn \osglecture_modes_overlay_gobble:n #1 { \use:c { osglecture_modes_overlay_gobble_#1 } } \cs_new_protected:Npn \osglecture_modes_overlay_fallback:nn #1#2 { \prop_get:NnN \g_osglecture_modes_overlay_fallback_prop {#1} \l_tmpa_tl \str_case:Vn \l_tmpa_tl { {flatten}{ \osglecture_modes_overlay_original:n {#1} } {omit}{ \osglecture_modes_overlay_gobble:n {#1} } {error}{ \msg_error:nnn { osglecture_modes } { overlay-fallback-error } {#2} \osglecture_modes_overlay_gobble:n {#1} } } } % \ldeen{Die eigentliche Erfassung steht im \file{overlays}-Baustein % (\cs{osglecture_modes_native_alt_bool} unten), \emph{nicht} hier: eine % Klasse mit verzögertem Laden (etwa \pkg{osglecture}) bindet % \pkg{osglecture-modes} \emph{vor} \cs{LoadClass} der Backend-Klasse ein, % sodass \cs{alt} an dieser Stelle im Dateikörper noch gar nicht existieren % kann -- eine sofortige Prüfung hier würde also für jede solche Klasse % dauerhaft \enquote{kein natives \cs{alt}} feststellen, selbst nachdem die % Backend-Klasse längst geladen ist. Nur die Deklaration steht hier, damit % frühere Definitionen in dieser Datei die Variable referenzieren % können.}{The actual detection lives in the \file{overlays} building % block (\cs{osglecture_modes_native_alt_bool} below), \emph{not} here: a % class with deferred loading (such as \pkg{osglecture}) requires % \pkg{osglecture-modes} \emph{before} \cs{LoadClass}ing the backend class, % so \cs{alt} cannot possibly exist yet at this point in the file body -- an % immediate check here would therefore permanently record "no native % \cs{alt}" for any such class, even long after the backend class has % loaded. Only the declaration lives here, so earlier definitions in this % file can reference the variable.} \bool_new:N \g_osglecture_modes_native_alt_bool \cs_new_protected:Npn \osglecture_modes_overlay_alt:nnn #1#2#3 { \alt<#1>{#2}{#3} } \cs_generate_variant:Nn \osglecture_modes_overlay_alt:nnn { Vnn } \cs_new_protected:Npn \osglecture_modes_overlay_apply:nn #1#2 { \osglecture_modes_classify:n {#2} \str_case:Vn \l_osglecture_modes_action_tl { { show } { \osglecture_modes_overlay_original:n {#1} } { hide } { \osglecture_modes_overlay_gobble:n {#1} } { native } { \bool_if:NTF \g_osglecture_modes_native_alt_bool { \osglecture_modes_overlay_alt:Vnn \l_osglecture_modes_native_tl { \osglecture_modes_overlay_original:n {#1} } { \osglecture_modes_overlay_gobble:n {#1} } } { \osglecture_modes_overlay_fallback:nn {#1} {#2} } } } } \NewDocumentCommand \MakeOverlayAwareCommand { O{} m } { \tl_clear:N \l_osglecture_modes_overlay_default_tl \tl_clear:N \l_osglecture_modes_overlay_arguments_tl \tl_set:Nn \l_osglecture_modes_overlay_fallback_tl {flatten} \keys_set:nn { osglecture_modes / overlay-aware } {#1} \cs_if_exist:NTF #2 { \cs_if_exist:cTF { osglecture_modes_overlay_original_\cs_to_str:N #2 } { \exp_args:Nc \RenewCommandCopy { osglecture_modes_overlay_original_\cs_to_str:N #2 } #2 } { \exp_args:Nc \NewCommandCopy { osglecture_modes_overlay_original_\cs_to_str:N #2 } #2 } \exp_args:NcV \DeclareDocumentCommand { osglecture_modes_overlay_gobble_\cs_to_str:N #2 } \l_osglecture_modes_overlay_arguments_tl { } \prop_put:NxV \g_osglecture_modes_overlay_default_prop { \cs_to_str:N #2 } \l_osglecture_modes_overlay_default_tl \prop_put:NxV \g_osglecture_modes_overlay_fallback_prop { \cs_to_str:N #2 } \l_osglecture_modes_overlay_fallback_tl \RenewDocumentCommand #2 { d<> } { \IfNoValueTF {##1} { \prop_get:NxN \g_osglecture_modes_overlay_default_prop { \cs_to_str:N #2 } \l_tmpa_tl \tl_if_empty:NTF \l_tmpa_tl { \osglecture_modes_overlay_original:n { \cs_to_str:N #2 } } { \exp_args:Nx \osglecture_modes_overlay_apply:nV { \cs_to_str:N #2 } \l_tmpa_tl } } { \exp_args:Nx \osglecture_modes_overlay_apply:nn { \cs_to_str:N #2 } {##1} } } } { \msg_error:nnN { kernel } { command-not-defined } #2 } } \cs_generate_variant:Nn \osglecture_modes_overlay_apply:nn { nV } % \ldeen{Jede an \cs{only}, \cs{mode}, die overlayfähigen Befehle oder die % gepatchten Backend-Decoder übergebene Winkelspezifikation wird in eine % von drei Arten einsortiert. Der Klassifizierer merkt sich anzeigen, % verbergen oder nativ, und im nativen Fall zusätzlich die an das Backend % weiterzureichende Spezifikation. % \begin{itemize} % \item \emph{portabel} -- eine mit \code{||} verknüpfte Oder-Liste % registrierter Modi (oder \code{all}). Wird vollständig hier % ausgewertet; benötigt kein Overlaybackend. % \item \emph{modusqualifiziert} -- nicht portabel, aber jedes mit % \code{|} getrennte Segment ist ein registrierter Modus, ein % \code{registriertermodus:rest} oder ein rein numerisches Overlayatom. % Die portablen Anteile werden hier ausgewertet und die überlebenden % nativen Reste zu einer einzigen Backendspezifikation % zusammengesetzt. % \item \emph{nativ} -- alles Übrige: Backend-Modusnamen, % \code{|}-Modusskopierung über unbekannte Modi, Actionspezifikationen, % \dots{} Wird unverändert weitergereicht. % \end{itemize} % \code{|} und \code{,} innerhalb von \code{<...>} sind stets Backendsyntax. % \code{||} ist der einzige portable Oder-Operator, daher muss eine % Spezifikation, die portable Modi mit nativen Overlayatomen mischt, dies % über \code{||} (oder die Form \code{modus:rest}) kenntlich machen.}{Every % angle-bracket specification handed to \cs{only}, \cs{mode}, the % overlay-aware commands, or the patched backend decoders is sorted into % one of three kinds. The classifier records show, hide, or native, and % for a native verdict the specification to forward to the backend. % \begin{itemize} % \item \emph{portable} -- a "||"-separated disjunction of registered % modes (or "all"). Evaluated entirely here; needs no overlay % backend. % \item \emph{qualified} -- not portable, but every "|"-separated % segment is a registered mode, "registeredmode:tail", or a purely % numeric overlay atom. The portable parts are evaluated here and the % surviving native tails are recombined into one backend spec. % \item \emph{native} -- anything else: backend mode names, "|" mode % scoping over unknown modes, action specs, \dots{} Forwarded % verbatim. % \end{itemize} % "|" and "," inside \code{<...>} are always backend syntax. "||" is the % only portable disjunction operator, so a spec that mixes portable modes % with native overlay atoms must use "||" (or the "mode:tail" form) to % say so.} \tl_new:N \l_osglecture_modes_action_tl \tl_new:N \l_osglecture_modes_native_tl \bool_new:N \l_osglecture_modes_spec_ok_bool \bool_new:N \l_osglecture_modes_spec_qual_bool \seq_new:N \l_osglecture_modes_spec_atoms_seq \seq_new:N \l_osglecture_modes_spec_segs_seq \seq_new:N \l_osglecture_modes_spec_colon_seq \clist_new:N \l_osglecture_modes_spec_parts_clist \tl_new:N \l_osglecture_modes_spec_mode_tl \tl_new:N \l_osglecture_modes_spec_rest_tl \tl_new:N \l_osglecture_modes_spec_resolved_tl \cs_generate_variant:Nn \osglecture_modes_resolve:nN { VN } \prg_generate_conditional_variant:Nnn \osglecture_modes_if_active:n { V } { T, F, TF } % \ldeen{\code{:} ist in diesem Syntaxregime ein Buchstabe, ein Literal % trifft daher nie auf die Kategorie-12-Doppelpunkte, die uns aus dem % Dokument erreichen; stattdessen anhand der Doppelpunkt-Stringkonstante % aufteilen.}{":" is a letter in this syntax regime, so a literal one % never matches the category-12 colons that reach us from the document; % split on the colon string constant instead.} \cs_generate_variant:Nn \seq_set_split:Nnn { NVn } \cs_generate_variant:Nn \seq_use:Nn { NV } \prg_new_protected_conditional:Npnn \osglecture_modes_spec_if_portable:n #1 { TF } { \seq_set_split:Nnn \l_osglecture_modes_spec_atoms_seq { || } {#1} \bool_set_true:N \l_osglecture_modes_spec_ok_bool \seq_map_inline:Nn \l_osglecture_modes_spec_atoms_seq { \tl_set:Nn \l_osglecture_modes_spec_mode_tl {##1} \tl_trim_spaces:N \l_osglecture_modes_spec_mode_tl \str_if_eq:VnF \l_osglecture_modes_spec_mode_tl { all } { \osglecture_modes_resolve:VN \l_osglecture_modes_spec_mode_tl \l_osglecture_modes_spec_resolved_tl \seq_if_in:NVF \g_osglecture_modes_known_seq \l_osglecture_modes_spec_resolved_tl { \bool_set_false:N \l_osglecture_modes_spec_ok_bool } } } \bool_if:NTF \l_osglecture_modes_spec_ok_bool { \prg_return_true: } { \prg_return_false: } } % \ldeen{Ein Segment hat \emph{native Form}, wenn es nur aus % Overlay-Zahlzeichen besteht -- es ist dann ein modusunabhängiges % Overlayatom statt eines Backend-Modusnamens.}{A segment is "native % shape" when it holds only overlay-number characters, i.e. it is a % mode-independent overlay atom rather than a backend mode name.} \prg_new_protected_conditional:Npnn \osglecture_modes_spec_if_native_shape:n #1 { TF } { \bool_set_true:N \l_osglecture_modes_spec_ok_bool \str_map_inline:nn {#1} { \str_if_in:nnF { 0123456789+-.(),~ } {##1} { \bool_set_false:N \l_osglecture_modes_spec_ok_bool \str_map_break: } } \bool_if:NTF \l_osglecture_modes_spec_ok_bool { \prg_return_true: } { \prg_return_false: } } \cs_new_protected:Npn \osglecture_modes_spec_segment_bare:n #1 { \tl_set:Nn \l_osglecture_modes_spec_mode_tl {#1} \osglecture_modes_resolve:VN \l_osglecture_modes_spec_mode_tl \l_osglecture_modes_spec_resolved_tl \seq_if_in:NVTF \g_osglecture_modes_known_seq \l_osglecture_modes_spec_resolved_tl { % \ldeen{Ein bloßer registrierter Modus bedeutet \emph{in diesem % Modus, auf jeder Folie}.}{A bare registered mode means "in % this mode, on every slide".} \osglecture_modes_if_active:VT \l_osglecture_modes_spec_resolved_tl { \clist_put_right:Nn \l_osglecture_modes_spec_parts_clist { all } } } { \osglecture_modes_spec_if_native_shape:nTF {#1} { \clist_put_right:Nn \l_osglecture_modes_spec_parts_clist {#1} } { \bool_set_false:N \l_osglecture_modes_spec_qual_bool } } } \cs_new_protected:Npn \osglecture_modes_spec_segment_colon:n #1 { \seq_set_split:NVn \l_osglecture_modes_spec_colon_seq \c_colon_str {#1} \seq_pop_left:NN \l_osglecture_modes_spec_colon_seq \l_osglecture_modes_spec_mode_tl \tl_set:Nx \l_osglecture_modes_spec_rest_tl { \seq_use:NV \l_osglecture_modes_spec_colon_seq \c_colon_str } \tl_trim_spaces:N \l_osglecture_modes_spec_mode_tl \tl_trim_spaces:N \l_osglecture_modes_spec_rest_tl \osglecture_modes_resolve:VN \l_osglecture_modes_spec_mode_tl \l_osglecture_modes_spec_resolved_tl \seq_if_in:NVTF \g_osglecture_modes_known_seq \l_osglecture_modes_spec_resolved_tl { \osglecture_modes_if_active:VT \l_osglecture_modes_spec_resolved_tl { \clist_put_right:NV \l_osglecture_modes_spec_parts_clist \l_osglecture_modes_spec_rest_tl } } { \bool_set_false:N \l_osglecture_modes_spec_qual_bool } } \cs_new_protected:Npn \osglecture_modes_spec_segment:n #1 { \bool_if:NT \l_osglecture_modes_spec_qual_bool { \tl_set:Nn \l_osglecture_modes_spec_mode_tl {#1} \tl_trim_spaces:N \l_osglecture_modes_spec_mode_tl \tl_if_empty:NF \l_osglecture_modes_spec_mode_tl { \exp_args:NV \str_if_in:nnTF \l_osglecture_modes_spec_mode_tl { : } { \exp_args:NV \osglecture_modes_spec_segment_colon:n \l_osglecture_modes_spec_mode_tl } { \exp_args:NV \osglecture_modes_spec_segment_bare:n \l_osglecture_modes_spec_mode_tl } } } } \cs_new_protected:Npn \osglecture_modes_classify:n #1 { \tl_clear:N \l_osglecture_modes_native_tl \osglecture_modes_spec_if_portable:nTF {#1} { \osglecture_modes_if_active:nTF {#1} { \tl_set:Nn \l_osglecture_modes_action_tl { show } } { \tl_set:Nn \l_osglecture_modes_action_tl { hide } } } { \bool_set_true:N \l_osglecture_modes_spec_qual_bool \clist_clear:N \l_osglecture_modes_spec_parts_clist \seq_set_split:Nnn \l_osglecture_modes_spec_segs_seq { | } {#1} \seq_map_inline:Nn \l_osglecture_modes_spec_segs_seq { \osglecture_modes_spec_segment:n {##1} } \bool_if:NTF \l_osglecture_modes_spec_qual_bool { \clist_if_empty:NTF \l_osglecture_modes_spec_parts_clist { \tl_set:Nn \l_osglecture_modes_action_tl { hide } } { \tl_set:Nn \l_osglecture_modes_action_tl { native } \tl_set:Nx \l_osglecture_modes_native_tl { \clist_use:Nn \l_osglecture_modes_spec_parts_clist { , } } } } { \tl_set:Nn \l_osglecture_modes_action_tl { native } \tl_set:Nn \l_osglecture_modes_native_tl {#1} } } } % \ldeen{Inhalt wird angezeigt, außer ein portabler Ausdruck ist vorhanden % und wertet zu falsch aus; eine native oder unerkannte Spezifikation % degradiert dort zu \emph{anzeigen}, wo kein Overlaybackend sie erfüllen % kann.}{Content shows unless a portable expression is present and % evaluates false; a native or unrecognised specification degrades to % "show" where no overlay backend can honour it.} \prg_new_protected_conditional:Npnn \osglecture_modes_if_reveal:n #1 { T, F, TF } { \osglecture_modes_classify:n {#1} \str_if_eq:VnTF \l_osglecture_modes_action_tl { hide } { \prg_return_false: } { \prg_return_true: } } % \ldeen{Die Vorgaben beschreiben Semantik, keine Backend-Wahl.}{Defaults % describe semantics, not a backend choice.} \DeclareLectureMode{presentation} \DeclareLectureMode[parents=presentation]{beamer} \DeclareLectureMode[parents=presentation]{projector} \DeclareLectureMode[parents={presentation,print}]{handout} \DeclareLectureMode[parents=presentation]{trans} \DeclareLectureMode{longform} \DeclareLectureMode[parents={longform,print}]{article} \DeclareLectureMode[parents=article]{script} \DeclareLectureMode{print} \DeclareLectureMode[parents=longform]{web} \DeclareLectureModeAlias{slides}{projector} % \ldeen{Der Modus \code{*} ist ein gewöhnlicher, aber vorinstallierter % Modus: Ist er aktiv, wird alles außerhalb einer registrierten % Unit-Umgebung (per Vorgabe \cs{frame}, falls vorhanden) verworfen, mit % den unten registrierten Ausnahmen. Aktiviert wird er entweder über die % Paketoption \code{ignorenonframetext} (Vorgabe: \code{true}, s.\,o.) % oder -- wie jeder andere Filtermodus auch -- direkt über \cs{mode*} % beziehungsweise \cs{mode<*>}; \cs{mode*} ist dabei nur eine wegen % Beamer-Kompatibilität beibehaltene Syntaxvariante für % \cs{mode<*>}, kein eigener Mechanismus.}{Mode \code{*} is an ordinary, % but pre-installed mode: while active, everything outside a registered % unit environment (by default \cs{frame}, if present) is dropped, with % the exceptions registered below. It is activated either via the % \code{ignorenonframetext} package option (default: \code{true}, see % above) or, like any other filter mode, directly via \cs{mode*} or % \cs{mode<*>}; \cs{mode*} is only a syntax variant kept for Beamer % compatibility with \cs{mode<*>}, not a separate mechanism.} \DeclareLectureMode{*} % \ldeen{Modi, die über die Schalterform \cs{mode} (ohne % Inhaltsargument) statt über \cs{lecturemode}/\cs{IfLectureModeTF} % eingesetzt werden, heißen hier \emph{Filtermodi}: Ihre Aktivierung % schaltet nicht Inhalt an einer Stelle sichtbar, sondern engagiert -- % global, ab dieser Stelle -- das Verwerfen alles Unregistrierten % außerhalb der registrierten Unit-Umgebungen. Sie werden wie jeder % andere Modus mit \cs{DeclareLectureMode} deklariert; nichts an ihrer % Deklaration unterscheidet sie von inhaltlichen Modi wie \code{script} % oder \code{presentation}. Ausnahmen (welche Befehle/Umgebungen dabei % erhalten bleiben) werden separat über \cs{DeclareLectureKeepCommand}, % \cs{DeclareLectureKeepLabelCommand} und \cs{DeclareLectureKeepEnvironment} % registriert, optional an einen bestimmten Filtermodus gebunden % (\code{mode=\meta{name}}); ohne diesen Schlüssel gilt die Ausnahme als % Baseline, unabhängig davon, welcher Filtermodus (falls überhaupt einer) % gerade aktiv ist.}{Modes used via the switch form \cs{mode} % (without a content argument) rather than via % \cs{lecturemode}/\cs{IfLectureModeTF} are called \emph{filter modes} % here: activating one does not toggle content visible at one spot, but % engages -- globally, from that point on -- dropping everything % unregistered outside the registered unit environments. They are % declared like any other mode, with \cs{DeclareLectureMode}; nothing % about their declaration distinguishes them from content modes such as % \code{script} or \code{presentation}. Exceptions (which % commands/environments survive) are registered separately via % \cs{DeclareLectureKeepCommand}, \cs{DeclareLectureKeepLabelCommand}, % and \cs{DeclareLectureKeepEnvironment}, optionally bound to a specific % filter mode (\code{mode=\meta{name}}); without that key, an exception % counts as baseline, in effect no matter which filter mode, if any, is % currently active.} \bool_new:N \g_osglecture_modes_scanner_active_bool \bool_new:N \g_osglecture_modes_after_structure_bool \bool_new:N \g_osglecture_modes_scan_engaged_bool \bool_new:N \g_osglecture_modes_scan_extended_bool \bool_new:N \g_osglecture_modes_native_mode_exists_bool \clist_new:N \g_osglecture_modes_keep_envs_clist \clist_new:N \g_osglecture_modes_hooked_envs_clist \prop_new:N \g_osglecture_modes_dispatch_prop \prop_new:N \g_osglecture_modes_dispatch_mode_prop \prop_new:N \g_osglecture_modes_keep_envs_mode_prop \tl_new:N \l_osglecture_modes_keep_mode_tl \msg_new:nnn { osglecture_modes } { unknown-environment } { Environment~'#1'~is~not~defined. } \keys_define:nn { osglecture_modes / keep } { mode .tl_set:N = \l_osglecture_modes_keep_mode_tl, } \sys_if_engine_luatex:T { \directlua{require("osglecture-modes-ltxtalk.lua")} } \cs_new_protected:Npn \osglecture_modes_lua_raw_skip:n #1 { \sys_if_engine_luatex:T { \directlua{ osglecture_modes_ltxtalk.start_raw_skip("\luaescapestring{#1}") } } } % \ldeen{Engagiert den Scanner nur, wenn ihn zuvor \cs{osglecture_modes_switch:n} % tatsächlich eingeschaltet hat (weil kein natives \cs{mode*} zur % Verfügung stand oder eigene Ausnahmen über die native Positivliste % hinausgehen), und nur außerhalb einer gerade betretenen % Unit-Umgebung.}{Only engages the scanner if % \cs{osglecture_modes_switch:n} actually turned it on (because no % native \cs{mode*} was available, or because custom exceptions go % beyond the native allow-list), and only outside a unit environment % just entered.} \cs_new_protected:Npn \osglecture_modes_start_scan: { \bool_if:NT \g_osglecture_modes_scan_engaged_bool { \bool_if:NT \g_osglecture_modes_scanner_active_bool { \osglecture_modes_scan: } } } % \ldeen{Auf LuaTeX übernimmt der token-basierte Lua-Scanner % (\cs{osglecture_modes_ltxtalk.scan}, Datei % \file{osglecture-modes-ltxtalk.lua}) den kompletten Scan-Lauf in einer % einzigen \cs{directlua}-Schleife, statt wie der TeX-Zweig unten sich % selbst rekursiv über den TeX-Eingabestapel aufzurufen; siehe % \file{development-docs/osglecture-modes-lua-scan.md} für die % Hintergründe. Da jeder Wiederaufsetzpunkt -- der erste Scan-Start % ebenso wie die Fortsetzung nach einem beibehaltenen Befehl -- über genau % diesen einen Befehl läuft, genügt die Weiche hier an einer einzigen % Stelle.}{On LuaTeX, the token-based Lua scanner % (\cs{osglecture_modes_ltxtalk.scan}, file % \file{osglecture-modes-ltxtalk.lua}) drives the entire scan run inside % one \cs{directlua} loop, instead of recursing through the TeX input % stack the way the TeX branch below does; see % \file{development-docs/osglecture-modes-lua-scan.md} for the % background. Since every resume point -- the initial scan start as well % as continuing after a kept command -- goes through this one command, % branching here once is enough.} \cs_new_protected:Npn \osglecture_modes_scan: { \sys_if_engine_luatex:TF { \directlua{ osglecture_modes_ltxtalk.scan() } } { \peek_remove_spaces:n { \peek_meaning:NTF \begin { \osglecture_modes_scan_begin: } { \peek_meaning:NTF \end { \osglecture_modes_scan_end: } { \osglecture_modes_scan_token: } } } } } \cs_new_protected:Npn \osglecture_modes_scan_begin: { \peek_meaning_remove:NT \begin \osglecture_modes_scan_begin_aux:n } \cs_new_protected:Npn \osglecture_modes_scan_begin_aux:n #1 { \clist_if_in:NnTF \g_osglecture_modes_keep_envs_clist {#1} { \prop_get:NnNTF \g_osglecture_modes_keep_envs_mode_prop {#1} \l_tmpa_tl { \osglecture_modes_if_active:VTF \l_tmpa_tl { \begin{#1} } { \osglecture_modes_skip_environment:n {#1} \osglecture_modes_scan: } } { \begin{#1} } } { \clist_if_in:NnTF \g_osglecture_modes_raw_skip_envs_clist {#1} { \osglecture_modes_lua_raw_skip:n {#1} \osglecture_modes_scan: } { \osglecture_modes_skip_environment:n {#1} \osglecture_modes_scan: } } } \cs_new_protected:Npn \osglecture_modes_scan_end: { \peek_meaning_remove:NT \end \osglecture_modes_scan_end_aux:n } \cs_new_protected:Npn \osglecture_modes_scan_end_aux:n #1 { \str_if_eq:nnTF {#1} {document} { \end{document} } { % \ldeen{fremdes \cs{end}\code{\{...\}} außerhalb von Unit-Umgebungen % ignorieren}{ignore a foreign \cs{end}\code{\{...\}} outside unit % environments} \osglecture_modes_scan: } } \cs_new_protected:Npn \osglecture_modes_skip_environment:n #1 { \cs_set_protected:Npn \osglecture_modes_skip:w ##1 \end ##2 { \str_if_eq:nnF {#1} {##2} { \osglecture_modes_skip:w } } \osglecture_modes_skip:w } \cs_new_protected:Npn \osglecture_modes_ensure_env_hook:n #1 { \cs_if_exist:cTF {#1} { \clist_if_in:NnF \g_osglecture_modes_hooked_envs_clist {#1} { \clist_gput_right:Nn \g_osglecture_modes_hooked_envs_clist {#1} \AddToHook{env/#1/after}{ \osglecture_modes_start_scan: } } } { \msg_error:nnn { osglecture_modes } { unknown-environment } {#1} } } % \ldeen{Spiegelt Name und Modusbindung jeder behaltenen Umgebung nach % Lua -- siehe die entsprechende Anmerkung bei % \cs{osglecture_modes_declare_allowed_command:nn}.}{Mirrors each kept % environment's name and mode binding into Lua -- see the matching note % at \cs{osglecture_modes_declare_allowed_command:nn}.} \cs_new_protected:Npn \osglecture_modes_declare_keep_environment:nn #1#2 { \osglecture_modes_ensure_env_hook:n {#1} \clist_if_in:NnF \g_osglecture_modes_keep_envs_clist {#1} { \clist_gput_right:Nn \g_osglecture_modes_keep_envs_clist {#1} } \tl_if_empty:nF {#2} { \prop_gput:Nnn \g_osglecture_modes_keep_envs_mode_prop {#1} {#2} } \sys_if_engine_luatex:T { \directlua{ osglecture_modes_ltxtalk.register_keep_env( "\luaescapestring{#1}", "\luaescapestring{#2}") } } } \cs_generate_variant:Nn \osglecture_modes_declare_keep_environment:nn { nV } \NewDocumentCommand \DeclareLectureKeepEnvironment { O{} m } { \tl_clear:N \l_osglecture_modes_keep_mode_tl \keys_set:nn { osglecture_modes / keep } {#1} \bool_gset_true:N \g_osglecture_modes_scan_extended_bool \clist_map_inline:nn {#2} { \osglecture_modes_declare_keep_environment:nV {##1} \l_osglecture_modes_keep_mode_tl } } \cs_if_exist:cT { frame } { \osglecture_modes_declare_keep_environment:nn { frame } { } } \cs_if_exist:cT { frame* } { \osglecture_modes_declare_keep_environment:nn { frame* } { } } % \ldeen{Eine ohne \code{mode=} deklarierte Ausnahme gilt als Baseline: % Sie erweitert die native Positivliste immer, deshalb erzwingt sie hier % wie eine modusgebundene Ausnahme den eigenen Scanner (siehe % \cs{osglecture_modes_switch:n} weiter unten) -- die intern registrierten % Basisausnahmen (Gliederungsbefehle, \cs{mode}, \cs{lecturemode}, % \cs{maketitle}, \cs{frame}) setzen diesen Schalter dagegen bewusst % nicht: Sie spiegeln genau das, was Beamers eigenes \cs{mode*} ohnehin % kennt.}{An exception declared without \code{mode=} counts as baseline: % it always extends the native allow-list, so it forces the package's % own scanner here just like a mode-bound exception (see % \cs{osglecture_modes_switch:n} further below) -- the internally % registered baseline exceptions (structural commands, \cs{mode}, % \cs{lecturemode}, \cs{maketitle}, \cs{frame}) deliberately do not set % this flag: they mirror exactly what Beamer's own \cs{mode*} already % knows about.} % \ldeen{Spiegelt Name (und, falls gesetzt, Modusbindung) jedes behaltenen % Befehls zusätzlich nach Lua (siehe % \file{osglecture-modes-ltxtalk.lua}) -- rein additiv, Vorarbeit für % einen künftigen Token-Level-Scanner in Lua (siehe % development-docs/osglecture-modes-lua-scan.md); ändert das aktuelle % TeX-Scan-Verhalten nicht.}{Also mirrors each kept command's name (and, % if set, its mode binding) into Lua (see % \file{osglecture-modes-ltxtalk.lua}) -- purely additive, groundwork % for a future Lua-side token-level scanner (see % development-docs/osglecture-modes-lua-scan.md); does not change % current TeX scanning behaviour.} \cs_new_protected:Npn \osglecture_modes_declare_allowed_command:nn #1#2 { \prop_gput:Nnn \g_osglecture_modes_dispatch_prop {#1} {#2} \sys_if_engine_luatex:T { \directlua{ osglecture_modes_ltxtalk.register_keep_cmd( "\luaescapestring{#1}", "\luaescapestring{#2}", "") } } } \cs_generate_variant:Nn \osglecture_modes_declare_allowed_command:nn { xx } \cs_new_protected:Npn \osglecture_modes_register_command:nnn #1#2#3 { \osglecture_modes_declare_allowed_command:nn {#1} {#2} \tl_if_empty:nF {#3} { \prop_gput:Nnn \g_osglecture_modes_dispatch_mode_prop {#1} {#3} \sys_if_engine_luatex:T { \directlua{ osglecture_modes_ltxtalk.register_keep_cmd( "\luaescapestring{#1}", "", "\luaescapestring{#3}") } } } } \cs_generate_variant:Nn \osglecture_modes_register_command:nnn { VVV } \cs_new_protected:Npn \osglecture_modes_keep_generic:Nn #1#2 { #1 {#2} \bool_gset_false:N \g_osglecture_modes_after_structure_bool \osglecture_modes_scan: } \cs_new_protected:Npn \osglecture_modes_keep_generic_label:Nn #1#2 { \bool_if:NT \g_osglecture_modes_after_structure_bool { #1 {#2} } \bool_gset_false:N \g_osglecture_modes_after_structure_bool \osglecture_modes_scan: } \tl_new:N \l_osglecture_modes_reg_cmd_tl \tl_new:N \l_osglecture_modes_reg_handler_tl \NewDocumentCommand \DeclareLectureKeepCommand { O{} m } { \tl_clear:N \l_osglecture_modes_keep_mode_tl \keys_set:nn { osglecture_modes / keep } {#1} \bool_gset_true:N \g_osglecture_modes_scan_extended_bool \cs_if_exist:NTF #2 { \exp_args:Nc \cs_set_protected:Npn { osglecture_modes_keep_generic_ \cs_to_str:N #2 : } { \osglecture_modes_keep_generic:Nn #2 } \tl_set:Nx \l_osglecture_modes_reg_cmd_tl { \cs_to_str:N #2 } \tl_set:Nx \l_osglecture_modes_reg_handler_tl { osglecture_modes_keep_generic_ \cs_to_str:N #2 : } \osglecture_modes_register_command:VVV \l_osglecture_modes_reg_cmd_tl \l_osglecture_modes_reg_handler_tl \l_osglecture_modes_keep_mode_tl } { \msg_error:nnN { kernel } { command-not-defined } #2 } } \NewDocumentCommand \DeclareLectureKeepLabelCommand { O{} m } { \tl_clear:N \l_osglecture_modes_keep_mode_tl \keys_set:nn { osglecture_modes / keep } {#1} \bool_gset_true:N \g_osglecture_modes_scan_extended_bool \cs_if_exist:NTF #2 { \exp_args:Nc \cs_set_protected:Npn { osglecture_modes_keep_generic_ \cs_to_str:N #2 : } { \osglecture_modes_keep_generic_label:Nn #2 } \tl_set:Nx \l_osglecture_modes_reg_cmd_tl { \cs_to_str:N #2 } \tl_set:Nx \l_osglecture_modes_reg_handler_tl { osglecture_modes_keep_generic_ \cs_to_str:N #2 : } \osglecture_modes_register_command:VVV \l_osglecture_modes_reg_cmd_tl \l_osglecture_modes_reg_handler_tl \l_osglecture_modes_keep_mode_tl } { \msg_error:nnN { kernel } { command-not-defined } #2 } } \cs_new_protected:Npn \osglecture_modes_scan_token: { \peek_N_type:TF { \osglecture_modes_scan_control_sequence:N } { \osglecture_modes_discard_one: } } % \ldeen{Modusgebundene Ausnahmen werden hier direkt gegen den normalen % Modusgraphen geprüft (\cs{osglecture_modes_if_active:n}) statt gegen % eine eigene, zweite Vererbungsstruktur -- ein Blattmodus mit mehreren % Filtermodi als Eltern vereinigt deren Ausnahmen also bereits über die % ohnehin vorhandene transitive Elternhülle.}{Mode-bound exceptions are % checked here directly against the normal mode graph % (\cs{osglecture_modes_if_active:n}) rather than against a second, % separate inheritance structure -- a leaf mode with several filter modes % as parents therefore already unions their exceptions via the existing % transitive parent closure.} % \ldeen{Verwendet eigene, benannte Variablen statt der generischen % \cs{l\_tmpa\_tl}/\cs{l\_tmpb\_tl}: Der Handlername muss über den Aufruf % von \cs{osglecture_modes_if_active:n} hinweg erhalten bleiben, das % \cs{l\_tmpa\_tl} intern selbst für die Aliasauflösung % verwendet.}{Uses dedicated, named variables instead of the generic % \cs{l\_tmpa\_tl}/\cs{l\_tmpb\_tl}: the handler name must survive the % call to \cs{osglecture_modes_if_active:n}, which itself uses % \cs{l\_tmpa\_tl} internally for alias resolution.} \tl_new:N \l_osglecture_modes_scan_key_tl \tl_new:N \l_osglecture_modes_scan_handler_tl \tl_new:N \l_osglecture_modes_scan_dispatch_mode_tl \cs_generate_variant:Nn \prop_item:Nn { NV } \cs_new_protected:Npn \osglecture_modes_scan_control_sequence:N #1 { \tl_set:Nx \l_osglecture_modes_scan_key_tl { \cs_to_str:N #1 } \tl_set:Nx \l_osglecture_modes_scan_handler_tl { \prop_item:NV \g_osglecture_modes_dispatch_prop \l_osglecture_modes_scan_key_tl } \tl_if_empty:NTF \l_osglecture_modes_scan_handler_tl { \osglecture_modes_scan: } { \tl_set:Nx \l_osglecture_modes_scan_dispatch_mode_tl { \prop_item:NV \g_osglecture_modes_dispatch_mode_prop \l_osglecture_modes_scan_key_tl } \tl_if_empty:NTF \l_osglecture_modes_scan_dispatch_mode_tl { \use:c { \l_osglecture_modes_scan_handler_tl } } { \osglecture_modes_if_active:VTF \l_osglecture_modes_scan_dispatch_mode_tl { \use:c { \l_osglecture_modes_scan_handler_tl } } { \osglecture_modes_scan: } } } } \cs_new_protected:Npn \osglecture_modes_keep_part: { \osglecture_modes_keep_structure:N \part } \cs_new_protected:Npn \osglecture_modes_keep_section: { \osglecture_modes_keep_structure:N \section } \cs_new_protected:Npn \osglecture_modes_keep_subsection: { \osglecture_modes_keep_structure:N \subsection } \cs_new_protected:Npn \osglecture_modes_keep_subsubsection: { \osglecture_modes_keep_structure:N \subsubsection } \cs_new_protected:Npn \osglecture_modes_keep_appendix: { \appendix \bool_gset_true:N \g_osglecture_modes_after_structure_bool \osglecture_modes_scan: } \NewDocumentCommand \osglecture_modes_keep_lecturemode: { d<> +m } { \IfNoValueTF {#1} { \lecturemode{#2} } { \lecturemode<#1>{#2} } \bool_gset_false:N \g_osglecture_modes_after_structure_bool \osglecture_modes_scan: } % \ldeen{Anders als \cs{lecturemode} (immer Inhaltsform, daher oben % unverändert mit bedingungslosem \code{+m}) hat \cs{mode} \emph{zwei} % Formen: die Schalterform \code{\mode} (kein Inhaltsargument) % und die Inhaltsform \code{\mode\Arg{content}}. Ein % bedingungsloses \code{+m} (frühere Fassung) griff bei der % Schalterform blind nach dem \emph{nächsten} Token im Dokument -- % typischerweise dem folgenden Befehl selbst (z.\,B. \cs{section}) -- % und führte es als "'Inhalt"' aus, \emph{bevor} dessen eigenes % Argument erreicht wurde; das eigentliche Argument geriet dadurch an % den erst danach folgenden Rest \emph{dieses} Makrokörpers (dasselbe % Muster wie der in Abschnitt~9.2 des Lua-Migrationsplans % dokumentierte Bug in \cs{osglecture_modes_skip_environment:n}). % Der Fix spiegelt exakt \cs{mode}s eigene Weiche (siehe deren % Definition weiter oben): erst per \cs{peek_meaning:NTF} % \cs{c_group_begin_token} prüfen, ob überhaupt eine Klammergruppe % folgt, und nur dann \code{+m}-artig danach greifen. Ohne folgende % Gruppe wird direkt \cs{osglecture_modes_switch:n} aufgerufen, das % seinerseits schon fürs Weiterscannen sorgt (\cs{osglecture_modes_scan:} % hier also \emph{nicht} zusätzlich aufrufen -- das gefundene % Wiederaufsetz-Token würde sonst als unbekannter Befehl verworfen, % noch bevor es verarbeitet werden kann).}{Unlike \cs{lecturemode} % (always content form, hence left unchanged above with unconditional % \code{+m}), \cs{mode} has \emph{two} forms: the switch form % \code{\mode} (no content argument) and the content form % \code{\mode\Arg{content}}. An unconditional \code{+m} (earlier % version) blindly grabbed the \emph{next} token in the document for % the switch form -- typically the following command itself (e.g.\ % \cs{section}) -- and executed it as "'content"' \emph{before} that % command's own argument was reached; the real argument then landed on % whatever followed \emph{this} macro body's own remaining text % (the same pattern as the bug in % \cs{osglecture_modes_skip_environment:n} documented in section~9.2 % of the Lua migration plan). The fix mirrors \cs{mode}'s own switch % exactly (see its definition further up): first check via % \cs{peek_meaning:NTF} \cs{c_group_begin_token} whether a brace group % follows at all, and only then grab it \code{+m}-style. Without a % following group, call \cs{osglecture_modes_switch:n} directly, which % already takes care of resuming the scan itself (so do \emph{not} % additionally call \cs{osglecture_modes_scan:} here -- the resume % token it produces would otherwise be discarded as an unknown command % before it can be processed).} \cs_new_protected:Npn \osglecture_modes_keep_mode_resume: { \bool_gset_false:N \g_osglecture_modes_after_structure_bool \osglecture_modes_scan: } \cs_new_protected:Npn \osglecture_modes_keep_mode_bare_content:n #1 { #1 \osglecture_modes_keep_mode_resume: } \cs_new_protected:Npn \osglecture_modes_keep_mode_inline_content:nn #1#2 { \mode<#1>{#2} \osglecture_modes_keep_mode_resume: } \NewDocumentCommand \osglecture_modes_keep_mode: { s d<> } { \IfBooleanTF {#1} { \osglecture_modes_switch:n {*} } { \IfNoValueTF {#2} { \peek_meaning:NTF \c_group_begin_token { \osglecture_modes_keep_mode_bare_content:n } { \osglecture_modes_keep_mode_resume: } } { \peek_meaning:NTF \c_group_begin_token { \osglecture_modes_keep_mode_inline_content:nn {#2} } { \osglecture_modes_switch:n {#2} } } } } \NewDocumentCommand \osglecture_modes_keep_maketitle: { O{} } { \bool_gset_false:N \g_osglecture_modes_scanner_active_bool \tl_if_blank:nTF {#1} { \maketitle } { \maketitle[#1] } } \osglecture_modes_declare_allowed_command:nn { part } { osglecture_modes_keep_part: } \osglecture_modes_declare_allowed_command:nn { section } { osglecture_modes_keep_section: } \osglecture_modes_declare_allowed_command:nn { subsection } { osglecture_modes_keep_subsection: } \osglecture_modes_declare_allowed_command:nn { subsubsection } { osglecture_modes_keep_subsubsection: } \osglecture_modes_declare_allowed_command:nn { appendix } { osglecture_modes_keep_appendix: } \osglecture_modes_declare_allowed_command:nn { lecturemode } { osglecture_modes_keep_lecturemode: } \osglecture_modes_declare_allowed_command:nn { mode } { osglecture_modes_keep_mode: } \osglecture_modes_declare_allowed_command:nn { maketitle } { osglecture_modes_keep_maketitle: } % \ldeen{Beamers eigenes \cs{mode*} überspringt \cs{againframe} bereits % nicht (siehe dessen feste Positivliste in % \file{beamerbasemodes.sty}); diese Deklaration spiegelt das als % weitere interne Basisausnahme und setzt daher bewusst % \emph{nicht} \cs{g\_osglecture\_modes\_scan\_extended\_bool} -- genau % wie \cs{frame}/\cs{frame*} und die Gliederungsbefehle oben. Die % eigene, vereinfachte Signatur \code{d<>~O\{\}~m} deckt die in der % Praxis verwendete Form % \cs{againframe}\sarg{Overlay}\oarg{Optionen}\marg{Name} ab, sowohl % unter Beamer als auch unter \pkg*{ltx-talk} (wo \cs{againframe} ein % Alias für \cs{reuseframe} mit ebendieser Signatur ist); das seltene % zweite Klammerpaar aus Beamers eigener, tiefer verschachtelter % \cs{@ifnextchar}-Kette wird hier nicht nachgebildet.}{Beamer's own % \cs{mode*} already does not skip \cs{againframe} (see its fixed % allow-list in \file{beamerbasemodes.sty}); this declaration mirrors % that as a further internal baseline exception and therefore % deliberately does \emph{not} set % \cs{g\_osglecture\_modes\_scan\_extended\_bool} -- just like % \cs{frame}/\cs{frame*} and the structural commands above. The % package's own, simplified signature \code{d<>~O\{\}~m} covers the % form actually used in practice, % \cs{againframe}\sarg{overlay}\oarg{options}\marg{name}, both under % Beamer and under \pkg*{ltx-talk} (where \cs{againframe} is an alias % for \cs{reuseframe} with exactly this signature); the rare second % bracket from Beamer's own, deeply nested \cs{@ifnextchar} chain is % not reproduced here.} \cs_if_exist:cT { againframe } { \NewDocumentCommand \osglecture_modes_keep_againframe: { d<> O{} m } { \IfNoValueTF {#1} { \againframe [#2] {#3} } { \againframe <#1> [#2] {#3} } \bool_gset_false:N \g_osglecture_modes_after_structure_bool \osglecture_modes_scan: } \osglecture_modes_declare_allowed_command:nn { againframe } { osglecture_modes_keep_againframe: } } \NewDocumentCommand \osglecture_modes_keep_structure:N { m s o m } { \IfBooleanTF {#2} { \IfNoValueTF {#3} { #1*{#4} } { #1*[#3]{#4} } } { \IfNoValueTF {#3} { #1{#4} } { #1[#3]{#4} } } \bool_gset_true:N \g_osglecture_modes_after_structure_bool \osglecture_modes_scan: } \cs_new_protected:Npn \osglecture_modes_discard_one: { \osglecture_modes_discard_one:n } \cs_new_protected:Npn \osglecture_modes_discard_one:n #1 { \bool_gset_false:N \g_osglecture_modes_after_structure_bool \osglecture_modes_scan: } \AddToHook{cmd/maketitle/after} { \bool_gset_true:N \g_osglecture_modes_scanner_active_bool \bool_gset_false:N \g_osglecture_modes_after_structure_bool \osglecture_modes_start_scan: } % \ldeen{Entscheidet, wie \cs{mode*} bzw. die Schalterform \cs{mode} % ausgeführt wird. \code{*} delegiert an ein natives \cs{mode*}, wenn % eines existiert \emph{und} keine eigene Ausnahme über die native % Positivliste hinausgeht (\cs{g\_osglecture\_modes\_scan\_extended\_bool}) -- % das erhält die native Semantik unverändert im einfachen Fall (siehe den % \code{native-adapter}-Test). Jeder andere Filtermodus hat naturgemäß % keine native Entsprechung und läuft daher immer über den eigenen % Scanner.}{Decides how \cs{mode*} or the switch form \cs{mode} is % carried out. \code{*} delegates to a native \cs{mode*} when one exists % \emph{and} no custom exception goes beyond the native allow-list % (\cs{g\_osglecture\_modes\_scan\_extended\_bool}) -- this keeps native % semantics unchanged in the simple case (see the \code{native-adapter} % test). Any other filter mode by construction has no native counterpart % and therefore always runs through the package's own scanner.} % \ldeen{Engagiert nur den Scanner selbst (\cs{g\_osglecture\_modes\_scan\_engaged\_bool}); % ob tatsächlich gescannt wird, entscheidet weiterhin % \cs{g\_osglecture\_modes\_scanner\_active\_bool}, das erst eine % registrierte Unit-Umgebung oder \cs{maketitle} setzt. Ein früheres % sofortiges Scharfschalten direkt an dieser Stelle hätte auch % Präambel-nahen Inhalt gleich nach \cs{begindocument/end} erfasst (etwa % den Testtreiber-Makro \cs{START} selbst) -- deutlich aggressiver als % Beamers natives \cs{mode*}, dessen feste Ausnahmeliste genau solche % frühen Fälle vorsieht.}{Only engages the scanner itself % (\cs{g\_osglecture\_modes\_scan\_engaged\_bool}); whether scanning % actually happens is still decided by % \cs{g\_osglecture\_modes\_scanner\_active\_bool}, which only a % registered unit environment or \cs{maketitle} sets. Arming scanning % immediately right here would also have caught preamble-adjacent content % right after \cs{begindocument/end} (such as the test driver's own % \cs{START} macro) -- notably more aggressive than Beamer's native % \cs{mode*}, whose fixed allow-list accounts for exactly such early % cases.} \cs_new_protected:Npn \osglecture_modes_switch_own: { \bool_gset_true:N \g_osglecture_modes_scan_engaged_bool \osglecture_modes_start_scan: } \cs_new_protected:Npn \osglecture_modes_switch:n #1 { \osglecture_modes_activate_runtime:n {#1} \str_if_eq:nnTF {#1} {*} { \bool_if:NTF \g_osglecture_modes_native_mode_exists_bool { \bool_if:NTF \g_osglecture_modes_scan_extended_bool { \osglecture_modes_switch_own: } { \osglecture_modes_native_mode * } } { \osglecture_modes_switch_own: } } { \osglecture_modes_switch_own: } } \cs_generate_variant:Nn \osglecture_modes_switch:n { V } \AddToHook{begindocument/end} { \bool_if:NT \g_osglecture_modes_ignorenonframetext_bool { \osglecture_modes_switch:n {*} } } % \ldeen{Löst die Kompatibilitätsoption auf, damit Metadaten in der % Präambel den gewählten Modus sehen. osglecture sollte einen expliziten % Blattmodus übergeben. Bei \code{deferred}: Eine Klasse, die ihre eigene % Blattmodus-Auflösung vor \cs{LoadClass} durchführt, überspringt dies % vollständig.}{Resolve the compatibility option, so metadata in the % preamble sees the selected mode. osglecture should pass an explicit % leaf mode. Deferred: a class doing its own leaf resolution before % \cs{LoadClass} skips this entirely.} \cs_new_protected:Npn \__osglecture_modes_resolve_leaf: { \tl_if_eq:NnTF \g_osglecture_modes_option_mode_tl {auto} { \@ifclassloaded{beamer} { \str_if_eq:eeTF { \use:c { beamer@currentmode } } {handout} { \SetLectureModes{handout,beamer} } { \SetLectureModes{slides,beamer} } } { \@ifclassloaded{ltx-talk} { \cs_if_exist:NTF \l__talk_mode_str { \str_case:VnF \l__talk_mode_str { { handout } { \SetLectureModes{handout} } { article } { \SetLectureModes{article} } { projector }{ \SetLectureModes{slides} } } { \seq_if_in:NVTF \g_osglecture_modes_known_seq \l__talk_mode_str { \exp_args:NV \SetLectureModes \l__talk_mode_str } { \SetLectureModes{slides} } } } { \SetLectureModes{slides} } } { \SetLectureModes{article} } } } { \exp_args:NV \SetLectureModes \g_osglecture_modes_option_mode_tl } } % \ldeen{Verdrahtet portable Modusausdrücke mit dem geladenen % Overlaybackend. Braucht die vorhandene Basisklasse, daher führt ein % verzögertes Laden dies erst über \cs{LectureModesActivateOverlays} nach % \cs{LoadClass} aus, nicht sofort. Der Rumpf steckt in einer eigenen % Datei, damit seine Befehlsparameter-Token nicht eine Verschachtelungsebene % zu tief innerhalb einer Wrapper-Definition erneut geparst werden.}{Wire % portable mode expressions into the loaded overlay backend. Needs the % base class present, so a deferred load runs this from % \cs{LectureModesActivateOverlays} after \cs{LoadClass} rather than now. % The body is kept in a separate file so its command-parameter tokens are % not reparsed one nesting level too deep inside a wrapper definition.} \cs_new_protected:Npn \__osglecture_modes_patch_overlays: { \file_input:n { osglecture-modes-overlays.code.tex } } \NewDocumentCommand \LectureModesActivateOverlays { } { \__osglecture_modes_patch_overlays: } % \ldeen{Ein gewöhnliches Laden per \cs{usepackage} geschieht nach % \cs{documentclass}, die Basisklasse ist also bereits vorhanden: sofort % auflösen und patchen. Ein verzögertes Laden wartet dagegen darauf, dass % die Klasse \cs{LectureModesActivateOverlays} aufruft.}{A normal % \cs{usepackage} load happens after \cs{documentclass}, so the base % class is already present: resolve and patch straight away. A deferred % load waits for the class to call \cs{LectureModesActivateOverlays}.} \bool_if:NF \g_osglecture_modes_deferred_bool { \__osglecture_modes_resolve_leaf: \__osglecture_modes_patch_overlays: } \AddToHook{begindocument/before}{\osglecture_modes_finalize_commands:} % %<*overlays> % \ldeen{Wird von \cs{__osglecture_modes_patch_overlays:} in das geladene % Overlaybackend eingebunden; läuft in der von diesem Aufrufer % bereitgestellten expl3-Syntax.}{Wired into the loaded overlay backend by % \cs{__osglecture_modes_patch_overlays:}; runs in expl3 syntax provided % by its caller.} % \ldeen{Erfasst hier, nicht bei der Deklaration weiter oben: dieser % Baustein läuft erst, nachdem die Backend-Klasse geladen ist (sofort bei % gewöhnlichem \cs{usepackage} nach \cs{documentclass}, sonst -- etwa bei % \pkg{osglecture} -- verzögert über \cs{LectureModesActivateOverlays} nach % \cs{LoadClass}), sodass ein natives \cs{alt} hier tatsächlich schon % existieren kann, falls vorhanden. Muss vor der portablen \cs{alt}-Rückfalldefinition % weiter unten stehen, sonst hielte die Prüfung deren rein portable Fassung % fälschlich für ein natives Urteil.}{Captured here, not at the declaration % above: this building block only runs after the backend class has loaded % (immediately for a plain \cs{usepackage} after \cs{documentclass}, % otherwise -- as with \pkg{osglecture} -- deferred via % \cs{LectureModesActivateOverlays} after \cs{LoadClass}), so a native % \cs{alt} can actually exist here by now, if present. Must precede the % portable \cs{alt} fallback definition further below, or the check would % mistake that portable-only stand-in for a native judge.} \bool_set:Nn \g_osglecture_modes_native_alt_bool { \cs_if_exist_p:N \alt } % \ldeen{Lässt die nativen Overlay-Decoder einen rein portablen % Modusausdruck erkennen. Alles andere, insbesondere gemischte % Modus-/Overlaysyntax, bleibt unangetastet.}{Let the native overlay % decoders recognize a pure portable mode expression. Everything else, % especially mixed mode/overlay syntax, remains untouched.} \bool_if:NT \g_osglecture_modes_patch_overlays_bool { \cs_if_exist:NT \beamer@masterdecode { \cs_new_eq:NN \osglecture_modes_native_beamer_masterdecode \beamer@masterdecode \cs_set_protected:Npn \beamer@masterdecode #1 { \osglecture_modes_classify:n {#1} \str_case:Vn \l_osglecture_modes_action_tl { { show } { \osglecture_modes_native_beamer_masterdecode {all} } { hide } { \osglecture_modes_native_beamer_masterdecode {0} } { native } { \exp_args:NV \osglecture_modes_native_beamer_masterdecode \l_osglecture_modes_native_tl } } } } \cs_if_exist:NT \__talk_decode_parse:n { \cs_new_eq:NN \osglecture_modes_native_talk_decode_parse:n \__talk_decode_parse:n \cs_set_protected:Npn \__talk_decode_parse:n #1 { \osglecture_modes_classify:n {#1} \str_case:Vn \l_osglecture_modes_action_tl { { show } { \osglecture_modes_native_talk_decode_parse:n {all} } { hide } { \str_if_eq:VnTF \l__talk_mode_str {article} { \osglecture_modes_native_talk_decode_parse:n {projector} } { \osglecture_modes_native_talk_decode_parse:n {article} } } { native } { \exp_args:NV \osglecture_modes_native_talk_decode_parse:n \l_osglecture_modes_native_tl } } } } } % \ldeen{Ergänzt ein bestehendes Backend um portable Inlinemodi, ohne % dessen Umschaltsyntax oder unbekannte/komplexe Overlayspezifikationen % zu übernehmen.}{Add portable inline modes to an existing backend % without taking over its switching syntax or unknown/complex overlay % specifications.} \cs_if_exist:NTF \mode { \bool_if:NT \g_osglecture_modes_patch_overlays_bool { \cs_new_eq:NN \osglecture_modes_native_mode \mode \bool_gset_true:N \g_osglecture_modes_native_mode_exists_bool % \ldeen{Eine Schalterform \cs{mode} ohne Inhalt wird nur dann % als portable Filtermodus-Aktivierung behandelt, wenn \code{name} % ein registrierter Modus ist; ein unbekannter (also nativer) % Name wird unverändert an das Backend weitergereicht, genau wie % zuvor.}{A switch form \cs{mode} without content is only % treated as a portable filter-mode activation when \code{name} % is a registered mode; an unknown (i.e. native) name is % forwarded to the backend unchanged, exactly as before.} \cs_new_protected:Npn \osglecture_modes_switch_or_native:n #1 { \osglecture_modes_resolve:nN {#1} \l_tmpa_tl \seq_if_in:NVTF \g_osglecture_modes_known_seq \l_tmpa_tl { \osglecture_modes_switch:n {#1} } { \osglecture_modes_native_mode <#1> } } \RenewDocumentCommand \mode { s d<> } { \IfBooleanTF {#1} { \osglecture_modes_switch:n {*} } { \IfNoValueTF {#2} { \osglecture_modes_native_mode } { \peek_meaning:NTF \c_group_begin_token { \osglecture_modes_mode_inline:n {#2} } { \osglecture_modes_switch_or_native:n {#2} } } } } \cs_new_protected:Npn \osglecture_modes_mode_native:nn #1#2 { \osglecture_modes_native_mode <#1> {#2} } \cs_new_protected:Npn \osglecture_modes_mode_inline:n #1#2 { \osglecture_modes_classify:n {#1} \str_case:Vn \l_osglecture_modes_action_tl { { show } {#2} { hide } { } { native } { \exp_args:NV \osglecture_modes_mode_native:nn \l_osglecture_modes_native_tl {#2} } } } } } { \cs_new_protected:Npn \osglecture_modes_mode_fallback_bare:n #1 {#1} \cs_new_protected:Npn \osglecture_modes_mode_fallback_inline:n #1#2 { \osglecture_modes_if_reveal:nT {#1} {#2} } \NewDocumentCommand \mode { s d<> } { \IfBooleanTF {#1} { \osglecture_modes_switch:n {*} } { \IfNoValueTF {#2} { \peek_meaning:NTF \c_group_begin_token { \osglecture_modes_mode_fallback_bare:n } { } } { \peek_meaning:NTF \c_group_begin_token { \osglecture_modes_mode_fallback_inline:n {#2} } { \osglecture_modes_switch:n {#2} } } } } } % \ldeen{\cs{only} ist sowohl bei Beamer als auch bei ltx-talk speziell % implementiert und läuft nicht in jedem Fall durch deren gemeinsame % Overlay-Decoder, braucht also einen eigenen Adapter statt sich den von % \cs{mode} zu teilen. Spiegelt die zweigeteilte Form von \cs{mode} oben: % Existiert \cs{only} bereits (Beamer, ltx-talk), wird es über den % signaturbewussten Adapter gebunden, sodass portable Modi direkt % behandelt und native Overlayspezifikationen unverändert weitergereicht % werden; existiert es nicht (Artikel, scrbook, book, \dots{}), wird ein % rein portables \cs{only} definiert, damit gemeinsamer Quelltext es % trotzdem nutzen kann.}{\cs{only} is implemented specially by both % beamer and ltx-talk and does not pass through their common overlay % decoders in every case, so it needs its own adapter rather than sharing % \cs{mode}'s. Mirrors \cs{mode}'s two-branch shape above: where % \cs{only} already exists (beamer, ltx-talk), bind it through the % signature-aware adapter so portable modes are handled directly while % native overlay specifications are delegated unchanged; where it does % not exist (article, scrbook, book, ...), define a portable-only % \cs{only} so shared source can still use it.} \cs_if_exist:NTF \only { \bool_if:NT \g_osglecture_modes_patch_overlays_bool { \MakeOverlayAwareCommand[arguments={+m}]{\only} } } { \NewDocumentCommand \only { d<> +m } { \IfNoValueTF {#1} {#2} { \osglecture_modes_if_reveal:nT {#1} {#2} } } } % \ldeen{Anders als \cs{only} und \cs{mode} laufen \cs{uncover}, % \cs{visible}, \cs{onslide}, \cs{invisible} und \cs{alt} durch die % gemeinsamen Overlay-Decoder von Beamer und ltx-talk (oben gepatcht), % sodass eine portable Modusspezifikation sie dort bereits korrekt % erreicht, ohne jeden Befehl einzeln anzupassen -- \cs{alt} dient % zusätzlich als eigenes natives-Spezifikations-Urteil des % Decoder-Fallbacks weiter unten, ein Einpacken hier würde dieses Urteil % also auf sich selbst zurückrufen lassen. Nur eine Basisklasse, der der % Befehl nativ fehlt (Artikel, scrbook, book, \dots{}), braucht eine rein % portable Definition.}{Unlike \cs{only} and \cs{mode}, \cs{uncover}, % \cs{visible}, \cs{onslide}, \cs{invisible}, and \cs{alt} route through % beamer's and ltx-talk's common overlay decoders (patched above), so a % portable mode spec already reaches them correctly there without % adapting each command individually -- \cs{alt} additionally serves as % the decoder fallback's own native-spec judge just below, so wrapping it % here would make that judge call back into itself. Only a base class % that lacks the command natively (article, scrbook, book, ...) needs a % portable-only definition.} \cs_if_exist:NF \uncover { \NewDocumentCommand \uncover { d<> +m } { \IfNoValueTF {#1} {#2} { \osglecture_modes_if_reveal:nT {#1} {#2} } } } \cs_if_exist:NF \visible { \NewDocumentCommand \visible { d<> +m } { \IfNoValueTF {#1} {#2} { \osglecture_modes_if_reveal:nT {#1} {#2} } } } \cs_if_exist:NF \onslide { \NewDocumentCommand \onslide { d<> +m } { \IfNoValueTF {#1} {#2} { \osglecture_modes_if_reveal:nT {#1} {#2} } } } % \ldeen{\cs{invisible} kehrt die Polarität um: Inhalt wird überall % angezeigt außer im angegebenen Modus. Eine native oder unerkannte % Spezifikation degradiert wie bei den positiven Befehlen zu % \emph{anzeigen}.}{\cs{invisible} inverts the polarity: content shows % everywhere except in the given mode. A native or unrecognised % specification degrades to "show" like the positive commands.} \cs_if_exist:NF \invisible { \NewDocumentCommand \invisible { d<> +m } { \IfNoValueTF {#1} {#2} { \osglecture_modes_classify:n {#1} \str_if_eq:VnF \l_osglecture_modes_action_tl { show } {#2} } } } % \ldeen{\cs{alt} nimmt zwei Inhaltsargumente (aktiver Zweig, inaktiver % Zweig) statt des einen von \cs{only}; der Fallback spiegelt diese Form % und behandelt eine native oder unerkannte Spezifikation als aktiven % Zweig.}{\cs{alt} takes two content arguments (active branch, inactive % branch) instead of \cs{only}'s single one; the fallback mirrors that % shape and treats a native or unrecognised specification as the active % branch.} \cs_if_exist:NF \alt { \NewDocumentCommand \alt { d<> +m +m } { \IfNoValueTF {#1} {#2} { \osglecture_modes_classify:n {#1} \str_if_eq:VnTF \l_osglecture_modes_action_tl { hide } {#3} {#2} } } } % \ldeen{\cs{pause} markiert einen Live-Aufdeckpunkt ohne zu wählenden % Inhalt, und die Vorher-/Während-/Nachher-Aufteilung von \cs{temporal} % setzt einen geordneten Overlaybereich voraus, den benannte portable % Modi nicht besitzen -- keines von beidem lässt sich auf % Inhaltsauswahl per Modus abbilden wie bei den Befehlen oben. % Basisklassen ohne eigenes \cs{pause}/\cs{temporal} erhalten dennoch eine % Definition, damit gemeinsamer Quelltext kompiliert: \cs{pause} tut % nichts, und \cs{temporal} zeigt stets sein \emph{während}-Argument, den % Zustand, auf den eine statische, overlaylose Darstellung sich % einpendelt.}{\cs{pause} marks a live-reveal point with no content to % select, and \cs{temporal}'s before/during/after split assumes an % ordered overlay range, which named portable modes do not have -- % neither maps onto content selection by mode the way the commands above % do. Base classes without their own \cs{pause}/\cs{temporal} still get a % definition so shared source compiles: \cs{pause} does nothing, and % \cs{temporal} always shows its "during" argument, the state a % static, non-overlay rendering settles on.} \cs_if_exist:NF \pause { \NewDocumentCommand \pause { O{} } { } } \cs_if_exist:NF \temporal { \NewDocumentCommand \temporal { d<> +m +m +m } {#3} } % %<*ltxtalk> \ProvidesExplPackage{osglecture-modes-ltxtalk} {2026-09-27} {v0.5.1} {ltx-talk adapter: lecture-specific baseline exceptions} \RequirePackage{osglecture-modes} \ExplSyntaxOn % \ldeen{Die generische "`außerhalb von Unit-Umgebungen verwerfen"'-Maschinerie % (Scanner, Modus \code{*}, \cs{DeclareLectureKeepCommand} und Geschwister) % gehört jetzt zum Kernpaket und funktioniert unabhängig von ltx-talk. Dieser % Adapter ergänzt nur noch das, was tatsächlich ltx-talk-spezifisch ist: % \cs{lecture} als weitere Baseline-Ausnahme (mit eigener, dreiteiliger % Signatur) sowie den Hook, der den Scanner nach \cs{lecture} scharf % schaltet -- \cs{frame}/\cs{frame*} werden bereits vom Kernpaket erkannt und % registriert, sobald sie existieren.}{The generic "drop everything outside % unit environments" machinery (scanner, mode \code{*}, % \cs{DeclareLectureKeepCommand} and siblings) now lives in the core package % and works independently of ltx-talk. This adapter only adds what is % actually ltx-talk-specific: \cs{lecture} as a further baseline exception % (with its own three-part signature) and the hook that arms the scanner % after \cs{lecture} -- \cs{frame}/\cs{frame*} are already detected and % registered by the core package once they exist.} \keys_define:nn { osglecture_modes_ltxtalk } { mode .code:n = { \SetLectureModes{#1} }, ignorenonframetext .bool_gset:N = \g_osglecture_modes_ignorenonframetext_bool, ignorenonframetext .default:n = true, raw-skip-envs .code:n = { \clist_gset:Nn \g_osglecture_modes_raw_skip_envs_clist {#1} \osglecture_modes_sync_raw_skip_envs_lua: }, } \ProcessKeyOptions [ osglecture_modes_ltxtalk ] \sys_if_engine_luatex:F { \bool_if:NT \g_osglecture_modes_ignorenonframetext_bool { \PackageWarning {osglecture_modes_ltxtalk} {ignorenonframetext with raw-skip support needs LuaLaTeX} } } \NewDocumentCommand \osglecture_modes_keep_lecture: { O{} m m } { \lecture[#1]{#2}{#3} \bool_gset_false:N \g_osglecture_modes_after_structure_bool \osglecture_modes_scan: } \osglecture_modes_declare_allowed_command:nn { lecture } { osglecture_modes_keep_lecture: } % \ldeen{\cs{label}, \cs{input} und \cs{include} sind schlichte Instanzen % der allgemeinen Ausnahmeschnittstelle des Kernpakets; sie über diese statt % von Hand zu deklarieren, erhält eine einzige Implementierung für % \emph{diesen Befehl erhalten, dann den Scan fortsetzen}.}{\cs{label}, % \cs{input}, and \cs{include} are plain instances of the core package's % general exception interface; declaring them through it rather than by % hand keeps a single implementation for "keep this command, then resume % scanning".} \DeclareLectureKeepLabelCommand \label \DeclareLectureKeepCommand \input \DeclareLectureKeepCommand \include % \ldeen{Rüstet den Scanner auf, nachdem osglecture seine % Gliederungseinheit-Deklaration vollständig verarbeitet hat. Das deckt % auch Nichtrahmenmaterial vor dem Titel ab.}{Arm the scanner after % osglecture has completely processed its logical-unit declaration. This % covers non-frame material before the title as well.} \AddToHook{cmd/lecture/after} { \bool_gset_true:N \g_osglecture_modes_scanner_active_bool \osglecture_modes_start_scan: } % %<*lua> osglecture_modes_ltxtalk = osglecture_modes_ltxtalk or {} osglecture_modes_ltxtalk.raw_skip_env = nil -- Mirror of the TeX-side keep registries (see -- \osglecture_modes_declare_allowed_command:nn, -- \osglecture_modes_register_command:nnn, -- \osglecture_modes_declare_keep_environment:nn in osglecture-modes.dtx), -- kept in sync so osglecture_modes_ltxtalk.scan() (below) can decide -- keep/discard without crossing back into TeX for every token. mode == nil -- means "baseline" (always kept, no mode check needed). osglecture_modes_ltxtalk.keep_cmds = {} osglecture_modes_ltxtalk.keep_envs = {} -- Mirror of \g_osglecture_modes_raw_skip_envs_clist -- (\osglecture_modes_sync_raw_skip_envs_lua: in osglecture-modes.dtx). -- These environments (verbatim, minted, ...) are never token-scanned -- -- their content and closing \end are blanked at the input-line level by -- the raw_skip_line callback further below, exactly as the pre-Lua -- \osglecture_modes_lua_raw_skip:n path already relied on. osglecture_modes_ltxtalk.raw_skip_envs = {} function osglecture_modes_ltxtalk.clear_raw_skip_envs() osglecture_modes_ltxtalk.raw_skip_envs = {} end function osglecture_modes_ltxtalk.register_raw_skip_env(name) osglecture_modes_ltxtalk.raw_skip_envs[name] = true end local function nil_if_empty(s) if s == nil or s == "" then return nil end return s end -- Currently active modes (transitively closed set), mirrored from the -- single choke point that ever adds to \g_osglecture_modes_active_prop -- (\osglecture_modes_visit:n) and cleared wherever that prop is cleared -- (\osglecture_modes_finalize:). Covers both the graph-walk rebuild and -- the direct single-mode runtime activation used by \mode -- (\osglecture_modes_activate_runtime:n), which does not go through -- \osglecture_modes_finalize: at all. osglecture_modes_ltxtalk.active_modes = {} function osglecture_modes_ltxtalk.clear_active_modes() osglecture_modes_ltxtalk.active_modes = {} end function osglecture_modes_ltxtalk.add_active_mode(name) osglecture_modes_ltxtalk.active_modes[name] = true end function osglecture_modes_ltxtalk.is_mode_active(name) return osglecture_modes_ltxtalk.active_modes[name] == true end function osglecture_modes_ltxtalk.register_keep_cmd(name, handler, mode) local entry = osglecture_modes_ltxtalk.keep_cmds[name] if not entry then entry = {} osglecture_modes_ltxtalk.keep_cmds[name] = entry end entry.handler = nil_if_empty(handler) or entry.handler entry.mode = nil_if_empty(mode) end function osglecture_modes_ltxtalk.register_keep_env(name, mode) osglecture_modes_ltxtalk.keep_envs[name] = { mode = nil_if_empty(mode) } end -- Token-level filter-mode scanner (see development-docs/ -- osglecture-modes-lua-scan.md). Mirrors the semantics of the TeX-level -- \osglecture_modes_scan: chain (osglecture-modes.dtx), but runs entirely -- in a Lua loop -- discarding tokens costs no TeX input-stack depth here, -- unlike the recursive TeX-level scanner it replaces on LuaTeX. -- -- IMPORTANT: this function (and everything it calls) lives in this -- separately `require`d .lua file, not inline in a \directlua{...} call. -- \directlua's argument is fully expanded before reaching Lua, which -- mangles a literal catcode-6 "#" (Lua's length operator) -- see the -- plan document, section 8, for the debugging detour that took to find -- this. Calling this function via a plain, "#"-free -- \directlua{osglecture_modes_ltxtalk.scan()} avoids the issue entirely. local function is_entry_active(entry) return entry.mode == nil or osglecture_modes_ltxtalk.is_mode_active(entry.mode) end -- Reads a "{...}" group already known to consist of ordinary -- letters/other characters (an environment name) and returns it as a -- plain string. Assumes the opening "{" is the very next token, as it -- always is right after \begin/\end. local function read_braced_name() local t = token.get_next() if t.cmdname ~= "left_brace" then return nil end local chars = {} while true do t = token.get_next() if t.cmdname == "right_brace" then break end chars[#chars + 1] = string.char(t.mode) end return table.concat(chars) end local function push_env_call(csname, envname) local toks = { token.create(csname), token.create(string.byte("{"), 1) } for i = 1, #envname do toks[#toks + 1] = token.create(envname:byte(i), 11) end toks[#toks + 1] = token.create(string.byte("}"), 2) token.put_next(toks) end -- Discards tokens up to and including the \end{name} that matches the -- \begin{name} just read (tracking same-named nesting, exactly like -- \osglecture_modes_skip_environment:n's delimited-argument matching). local function skip_environment(name) local depth = 1 while depth > 0 do local t = token.get_next() if t.csname == "begin" then local inner = read_braced_name() if inner == name then depth = depth + 1 end elseif t.csname == "end" then local inner = read_braced_name() if inner == name then depth = depth - 1 end end end end function osglecture_modes_ltxtalk.scan() while true do local t = token.get_next() local csname = t.csname if csname == "begin" then local name = read_braced_name() local entry = osglecture_modes_ltxtalk.keep_envs[name] if entry then if is_entry_active(entry) then push_env_call("begin", name) return else skip_environment(name) end elseif osglecture_modes_ltxtalk.raw_skip_envs[name] then -- Content and closing \end{name} are blanked at the input-line -- level (see raw_skip_line below); nothing left to token-skip. osglecture_modes_ltxtalk.start_raw_skip(name) else skip_environment(name) end elseif csname == "end" then local name = read_braced_name() if name == "document" then push_env_call("end", name) return end -- stray \end outside a kept environment: ignore, keep scanning -- (mirrors \osglecture_modes_scan_end_aux:n). elseif csname ~= nil and csname ~= "" then local entry = osglecture_modes_ltxtalk.keep_cmds[csname] if entry and is_entry_active(entry) and entry.handler then token.put_next({ token.create(entry.handler) }) return end -- unknown, or known-but-inactive command: discard just this -- token; any argument it would have taken gets discarded -- character by character on subsequent loop iterations, exactly -- like \osglecture_modes_scan_token:'s single-character branch. end -- else: a single character/other token -- discard, keep scanning. end end local previous_callback = callback.find("process_input_buffer") local function escape_pattern(s) return (s:gsub("([^%w])", "%%%1")) end function osglecture_modes_ltxtalk.start_raw_skip(env) osglecture_modes_ltxtalk.raw_skip_env = env end local function raw_skip_line(line) local env = osglecture_modes_ltxtalk.raw_skip_env if not env then return nil end local pattern = "^%s*\\end%s*{%s*" .. escape_pattern(env) .. "%s*}" if line:match(pattern) then osglecture_modes_ltxtalk.raw_skip_env = nil end return "" end local function process_input_buffer(line) local replacement = raw_skip_line(line) if replacement ~= nil then return replacement end if previous_callback then return previous_callback(line) end return nil end luatexbase.add_to_callback( "process_input_buffer", process_input_buffer, "osglecture-modes-ltxtalk raw skip" ) %