%<*driver> \def\nameofplainTeX{plain} \ifx\fmtname\nameofplainTeX \input l3docstrip.tex \keepsilent \askforoverwritefalse \preamble ======================================================================== osgstyler - (C) 2026 Matthias Werner E-mail: matthias.werner@informatik.tu-chemnitz.de Released under the LaTeX Project Public License v1.3c or later See http://www.latex-project.org/lppl.txt ======================================================================== \endpreamble \postamble This work is "maintained" (as per LPPL maintenance status) by Matthias Werner. \endpostamble \generate{ \file{osgstyler.sty}{\from{osgstyler.dtx}{pkg}} \file{osgstyler-lua.sty}{\from{osgstyler.dtx}{lua-backend}} } \expandafter\endbatchfile \fi \iffalse % %<*pkg> \NeedsTeXFormat{LaTeX2e}[2024-06-01] \ProvidesExplPackage{osgstyler} {2026-08-24} {v0.2.0} {Template-based, reusable document styles} % %<*driver> \fi \documentclass[add-bib=false]{osgdoc} \usepackage{enumitem} \usepackage{microtype} \usepackage[ languages={de,en}, map={de=german,en=english}, targetlang={job=2}, load babel ]{langselect} \newpackagename{\osgstyler}{osgstyler} \newpackagename{\lttalk}{ltx-talk} \newpackagename{\beamer}{beamer} \makeindex \setcnltx{ name = osgstyler, version = \UseVersionOf{osgstyler.sty}, date = \UseDateOf{osgstyler.sty}, title = {\ldeen{Das \osgstyler\ Paket}{The \osgstyler\ Package}}, info = {\ldeen{Templatebasierte Stile für Standardformatierungen}{ Template-based styles for standard formattings}}, authors = {Matthias Werner[matthias.werner@informatik.tu-chemnitz.de]}, abstract={ \ldeen{ \osgstyler{} verwaltet Farben, Schriften, Abstände, Listenoptik, Dekorationen und kastenartige Umgebungen (Zitate, theorem-artige Blöcke, \ldots) über ein kleines, portables Namensvokabular, statt konkrete Werte im Dokument zu verstreuen. Mitgelieferte Presets (\code{osl-standard}, \code{osl-modern}) geben ein sofort nutzbares Erscheinungsbild; eigene Paletten, Dekorationen und Blockinstanzen lassen sich darüber hinaus deklarieren. Das Paket ist Teil des \pkg*{osglecture}-Bundles -- \cls*{ltx-talk} teilt sein Farb-Slot-Vokabular mit \pkg*{lttheme} --, kann aber auch unabhängig eingesetzt werden. }{ \osgstyler{} manages colors, fonts, spacing, list appearance, decorations, and box-like environments (quotations, theorem-like blocks, \ldots) through a small, portable vocabulary of names, instead of scattering concrete values across a document. Bundled presets (\code{osl-standard}, \code{osl-modern}) give an immediately usable appearance; custom palettes, decorations, and block instances can be declared on top of that. The package is part of the \pkg*{osglecture} bundle -- \cls*{ltx-talk} shares its color-slot vocabulary with \pkg*{lttheme} --, but can also be used independently. } }, url =https://github.com/werner-matthias/osglecture, build-title } \begin{document} % -------------------------------------------------------------------------- % Documentation % -------------------------------------------------------------------------- \section{\ldeen{Einleitung}{Introduction}} \label{sec:intro} \subsection{TL;DR} \label{sec:tldr} \ldeen{Angenommen, Sie wollen Ihrem Dokument schnell ein in sich stimmiges Erscheinungsbild geben -- koordinierte Farben, eine passende Begleitschrift und etwas Optik für Zitate --, ohne sich mit den Details der Farb-/Font-/Box-Verwaltung zu beschäftigen.}{Assume you want to quickly give your document a coherent look -- coordinated colors, a matching companion typeface, and some visual treatment for quotations -- without dealing with the details of color/font/box management yourself.} \begin{enumerate} \item \ldeen*{Binden Sie @1 ein}{Include @1} {\cs{usepackage}\Marg{osgstyler}} \item \ldeen*{Aktivieren Sie ein fertiges Preset mit @1}{Activate a ready-made preset with @1}{\cs{UseOsgPreset}\Marg{osl-modern}} \item \ldeen{Falls Sie auch Zitate/Blöcke im Langform-Stil nutzen wollen, aktivieren Sie diese einmalig mit}{If you also want long-form-style quotations/blocks, activate them once with}: \centerline{\cs{UseOsgStandardBlockEnvironments}} \end{enumerate} \begin{example}[code-only] \usepackage{osgstyler} \UseOsgPreset{osl-modern} \UseOsgStandardBlockEnvironments \begin{document} \emph{Hervorhebungen}, \textbf{Fettes} und \highlight{Markiertes} nutzen jetzt automatisch die Preset-Farben. \begin{quote} Auch Zitate erhalten das Erscheinungsbild des Presets. \end{quote} \end{document} \end{example} \ldeen*{Das war schon alles auf dieser Ebene. Alles Weitere in dieser Dokumentation -- eigene Paletten, eigene Blockinstanzen, eigene Dekorationen -- brauchen Sie nur, wenn Sie über die mitgelieferten Presets hinaus wollen. Der Unterschied zwischen diesen zwei Ebenen -- \emph{Nutzen} vorgefertigter Stile gegenüber \emph{Erstellen} eigener -- zieht sich durch die gesamte Dokumentation und wird in Abschnitt~@1 genauer erklärt.}{That's already everything at this level. Everything else in this documentation -- your own palettes, your own block instances, your own decorations -- is only needed if you want to go beyond the bundled presets. The distinction between these two levels -- \emph{using} ready-made styles versus \emph{creating} your own -- runs through this entire documentation and is explained in detail in Section~@1.}{\ref{sec:concepts}} \subsection{\ldeen{Motivation}{Motivation}} \label{sec:motivation} \ldeen{Wiederkehrende Lehr- oder Vortragsmaterialien -- Skripte, Folien, Handouts -- sollen meist ein einheitliches, wiedererkennbares Erscheinungsbild haben: dieselben Farben, dieselben Schriften, dieselbe Optik für Zitate, Hervorhebungen und theorem-artige Blöcke. Ohne Unterstützung landet dieses Wissen verstreut in jedem einzelnen Dokument -- als wiederholte \cs*{definecolor}-Aufrufe, hartcodierte \cs*{fcolorbox}s und \cs*{textcolor}-Aufrufe --, und eine spätere Änderung (ein neues Farbschema, eine andere Begleitschrift) bedeutet, jede Fundstelle einzeln zu suchen. \pkg*{osgstyler} löst das, indem es visuelle Ressourcen (Farben, Schriften, Abstände, Listenoptik, Dekorationen, Kastenoptik) hinter einem kleinen, \emph{portablen} Namensvokabular versteckt: Dokumentcode verweist auf \emph{Slots} wie \code{primary} oder \code{accent}, nie auf konkrete Werte. Ein Theme oder eine Palette legt einmalig fest, was sich hinter diesen Namen verbirgt; eine Änderung dort wirkt überall. Das Paket ist Teil des \pkg*{osglecture}-Bundles -- \cls*{ltx-talk} nutzt denselben Slot-Wortschatz für Farben wie \pkg*{lttheme} --, kann aber auch vollständig unabhängig eingesetzt werden.}{Recurring teaching or presentation material -- lecture notes, slides, handouts -- usually needs a consistent, recognizable look: the same colors, the same fonts, the same visual treatment for quotations, highlights, and theorem-like blocks. Without support, this knowledge ends up scattered across every single document -- as repeated \cs*{definecolor} calls, hard-coded \cs*{fcolorbox}s and \cs*{textcolor} calls -- and a later change (a new color scheme, a different companion typeface) means hunting down every occurrence individually. \pkg*{osgstyler} solves this by hiding visual resources (colors, fonts, spacing, list appearance, decorations, box appearance) behind a small, \emph{portable} vocabulary of names: document code refers to \emph{slots} such as \code{primary} or \code{accent}, never to concrete values. A theme or palette decides once what sits behind these names; a change there takes effect everywhere. The package is part of the \pkg*{osglecture} bundle -- \cls*{ltx-talk} uses the same color-slot vocabulary as \pkg*{lttheme} --, but can also be used completely independently.} \subsection{\ldeen{Zwei Ebenen: Nutzen und Erstellen}{Two Levels: Using and Creating}} \label{sec:concepts} \ldeen{Fast jeder Mechanismus in \osgstyler{} existiert auf zwei Ebenen, und diese Dokumentation ist konsequent danach gegliedert:}{Nearly every mechanism in \osgstyler{} exists on two levels, and this documentation is consistently organized around that distinction:} \begin{description} \item[\ldeen{Nutzen}{Using}] \ldeen{Sie aktivieren eine bereits fertige Ressource -- eine mitgelieferte Farb-/Font-/Metrik-/Listenpalette, ein bereits vordefiniertes Preset, eine bereits gebundene Umgebung wie \env{quote}, einen bereits gebundenen Befehl wie \cs{emph}. Das entsprechende Nutzermakro heißt praktisch immer \cs*{UseOsg\ldots}. Diese Ebene reicht für die allermeisten Dokumente.}{You activate an already-finished resource -- a bundled color/font/metric/list palette, an already-defined preset, an already-bound environment such as \env{quote}, an already-bound command such as \cs{emph}. The corresponding user command is almost always named \cs*{UseOsg\ldots}. This level is enough for the vast majority of documents.} \item[\ldeen{Erstellen}{Creating}] \ldeen{Sie definieren selbst eine neue Palette, ein neues Preset, eine neue Dekoration oder eine neue Blockinstanz -- etwa für ein eigenes Farbschema oder eine neue Art von hervorgehobenem Block. Das entsprechende Makro heißt praktisch immer \cs*{DeclareOsg\ldots}. Diese Ebene betrifft typischerweise Theme-/Vorlagenautor*innen, nicht jedes einzelne Dokument.}{You define a new palette, a new preset, a new decoration, or a new block instance yourself -- for example for your own color scheme or a new kind of highlighted block. The corresponding command is almost always named \cs*{DeclareOsg\ldots}. This level typically concerns theme/template authors, not every individual document.} \end{description} \ldeen*{Eine dritte, seltener gebrauchte Ebene richtet sich an Backend-/Adapter-Autor*innen: Wer entscheidet, \emph{wie} eine Dekoration tatsächlich gezeichnet wird (siehe @1) oder wie ein Theme-Paket seine Farben automatisch einspeist (siehe @2), tauscht einen sogenannten \emph{Prozessor} aus oder registriert einen Hook -- auch das ist möglich, aber für gewöhnliche Dokumente nicht nötig.}{A third, more rarely used level addresses backend/adapter authors: deciding \emph{how} a decoration is actually drawn (see @1) or how a theme package feeds in its colors automatically (see @2) means replacing a so-called \emph{processor} or registering a hook -- also possible, but not needed for ordinary documents.}{\ref{sec:decorations:processors}}{\ref{sec:integration}} \ldeen*{Innerhalb der Nutzen-/Erstellen-Unterscheidung gibt es außerdem zwei \emph{strukturell} verschiedene Arten von Ressourcen, deren Namen sich entsprechend unterscheiden:}{Within the using/creating distinction, there are also two \emph{structurally} different kinds of resources, and their names reflect this:} \begin{description} \item[\ldeen{Paletten}{Palettes}] \ldeen{Farb-, Font-, Metrik- und Listenpaletten (\cs{DeclareOsgColorPalette} und Geschwister) sind reine, unter einem Namen gruppierte Datenablagen mit einem festen Satz \emph{Slots} (z.\,B. \code{primary}, \code{accent}). Sie nutzen kein \pkg*{xtemplate}, sondern eine schlanke, eigene Verwaltung dieses Pakets -- angemessen, weil eine Palette nur Werte nachschlägt, nicht wie eine Umgebung parametrisiert aufgerufen wird.}{Color, font, metric, and list palettes (\cs{DeclareOsgColorPalette} and siblings) are pure data stores, grouped under a name, with a fixed set of \emph{slots} (e.g.\ \code{primary}, \code{accent}). They do not use \pkg*{xtemplate}, but a lean, package-specific bookkeeping mechanism -- appropriate, since a palette only looks up values rather than being invoked with parameters like an environment.} \item[\ldeen{Templates und Instanzen}{Templates and Instances}] \ldeen{Inline-Styles, Blöcke und Nummerierungen (\cs{DeclareOsgInlineStyle}, \cs{DeclareOsgBlockInstance}, \cs{DeclareOsgNumbering}) sind tatsächliche \needpackage[macros/latex/contrib/xtemplate]{xtemplate}-Instanzen: Ein einmal deklarierter \emph{Template-Typ} (\code{osgstyler-block} usw.) legt eine feste Schnittstelle aus benannten \emph{Schlüsseln} fest (z.\,B. \code{rule-thickness}, \code{padding}); eine \emph{Instanz} ist ein gespeicherter, benannter Satz von Werten für diese Schlüssel. \emph{Traits} (\cs{DeclareOsgBlockTrait}) sind schließlich nur benannte, wiederverwendbare Schlüsselfragmente -- eine Abkürzung, um mehrere Instanzen dieselbe Kombination von Schlüsseln teilen zu lassen, ohne sie zu wiederholen.}{Inline styles, blocks, and numbering (\cs{DeclareOsgInlineStyle}, \cs{DeclareOsgBlockInstance}, \cs{DeclareOsgNumbering}) are actual \needpackage[macros/latex/contrib/xtemplate]{xtemplate} instances: a \emph{template type} declared once (\code{osgstyler-block} etc.) fixes an interface of named \emph{keys} (e.g.\ \code{rule-thickness}, \code{padding}); an \emph{instance} is a stored, named set of values for those keys. \emph{Traits} (\cs{DeclareOsgBlockTrait}), finally, are just named, reusable key fragments -- a shortcut for letting several instances share the same combination of keys without repeating them.} \end{description} \ldeen*{Ein \emph{Preset} wie \code{osl-standard} oder \code{osl-modern} bündelt schließlich beides unter einem gemeinsamen Namen: eine Farb-, eine Font- und ggf.\ eine Listenpalette sowie ggf.\ einen Block-Trait. @1 aktiviert alle vorhandenen Teile eines Presets auf einmal -- Details in Abschnitt~@2.}{A \emph{preset} such as \code{osl-standard} or \code{osl-modern}, finally, bundles both under one shared name: a color, a font, and possibly a list palette, plus possibly a block trait. @1 activates every part of a preset that exists, all at once -- details in Section~@2.}{\cs{UseOsgPreset}}{\ref{sec:blocks:presets}} \begin{infobox}{\ldeen{Namenskonvention}{Naming Convention}} \ldeen{Alle öffentlichen Befehle beginnen mit \code{Osg} (oder, bei Paketoptionen und Templateschlüsseln, tragen einfach keinen Prefix, da sie ohnehin in einem eigenen Namensraum stehen). Befehle, die eine \emph{fertige} Ressource auswählen, beginnen mit \cs*{UseOsg\ldots}; Befehle, die eine \emph{neue} Ressource anlegen, mit \cs*{DeclareOsg\ldots}. Diese Konvention gilt ausnahmslos und wird in dieser Dokumentation nicht mehr eigens erwähnt.}{All public commands begin with \code{Osg} (or, for package options and template keys, simply carry no prefix, since they already live in their own namespace). Commands that select an \emph{existing} resource begin with \cs*{UseOsg\ldots}; commands that create a \emph{new} resource begin with \cs*{DeclareOsg\ldots}. This convention holds without exception and is not called out again individually in this documentation.} \end{infobox} \subsection{\ldeen{Alternativen}{Alternatives}} \label{sec:alternatives} \ldeen{Es gibt keine direkte Entsprechung zu \osgstyler{} auf \ctan{}\footnote{Jedenfalls ist dem Autor keine bekannt.}, wohl aber Pakete, die einzelne Aspekte abdecken.}{There is no direct equivalent to \osgstyler{} on \ctan{}\footnote{At least none known to the author.}, but there are packages that cover individual aspects.} \begin{description} \item[\pkg{xtemplate}] \ldeen{Der \needpackage[macros/latex/contrib/xtemplate]{xtemplate}-Mechanismus selbst (Teil des \LaTeX-Kernels) liefert das Konzept von Template-Typen/-Instanzen, das \pkg*{osgstyler} für Blöcke, Inline-Styles und Nummerierung nutzt -- aber ohne jedes fertige Vokabular für Farben, Schriften oder Boxen. \osgstyler{} lässt sich daher auch als eine Sammlung fertiger \pkg*{xtemplate}-Template-Typen für Dokumentstile verstehen.}{The \needpackage[macros/latex/contrib/xtemplate]{xtemplate} mechanism itself (part of the \LaTeX{} kernel) provides the template-type/instance concept that \pkg*{osgstyler} uses for blocks, inline styles, and numbering -- but without any ready-made vocabulary for colors, fonts, or boxes. \osgstyler{} can therefore also be understood as a collection of ready-made \pkg*{xtemplate} template types for document styles.} \item[\pkg{tcolorbox}/\pkg{mdframed}] \ldeen*{Beide Pakete zeichnen Kästen und sind erheblich mächtiger als \osgstyler{}s eigener, absichtlich einfacher Block-Renderer (siehe @1). \osgstyler{} löst dieses Spannungsfeld, indem der Renderer austauschbar bleibt: eine eigene, auf @2 aufbauende Implementierung des \code{osgstyler-block}-Templates ist jederzeit möglich, ohne die Instanzen (\env{quote}, eigene Theoreme, \ldots) neu deklarieren zu müssen.}{Both packages draw boxes and are considerably more powerful than \osgstyler{}'s own, deliberately simple block renderer (see @1). \osgstyler{} resolves this tension by keeping the renderer replaceable: a custom implementation of the \code{osgstyler-block} template built on @2 is possible at any time, without having to redeclare the instances (\env{quote}, custom theorems, \ldots).}{\ref{sec:blocks:concept}}{\pkg{tcolorbox}} \item[\pkg{ulem}/\pkg{soul}/\pkg{lua-ul}] \ldeen{Diese Pakete implementieren Unterstreichungen bzw.\ Hervorhebungen auf Zeichenebene. \osgstyler{}s Referenzbackend (\pkg{osgstyler-lua}, siehe Abschnitt~\ref{sec:lua}) baut direkt auf \needpackage{lua-ul} auf; ein Dekorationsbackend auf Basis von \needpackage{soul} oder \needpackage{ulem} ist ebenso denkbar.}{These packages implement underlining or highlighting at the character level. \osgstyler{}'s reference backend (\pkg{osgstyler-lua}, see Section~\ref{sec:lua}) is built directly on \needpackage{lua-ul}; a decoration backend based on \needpackage{soul} or \needpackage{ulem} is equally conceivable.} \item[\pkg*{lttheme}] \ldeen{Das Schwesterpaket im \pkg*{osglecture}-Bundle stellt Farbthemes für \cls*{ltx-talk} bereit und teilt \osgstyler{}s Farb-Slot-Vokabular. Die beiden Pakete sind bewusst voneinander entkoppelt (\osgstyler{} hängt nicht von \pkg*{lttheme} ab); wird \pkg*{lttheme} aber geladen, synchronisiert \osgstyler{} sich automatisch, siehe Abschnitt~\ref{sec:lttheme}.}{The sibling package in the \pkg*{osglecture} bundle provides color themes for \cls*{ltx-talk} and shares \osgstyler{}'s color-slot vocabulary. The two packages are deliberately decoupled (\osgstyler{} does not depend on \pkg*{lttheme}); but if \pkg*{lttheme} is loaded, \osgstyler{} synchronizes with it automatically, see Section~\ref{sec:lttheme}.} \end{description} \section{\ldeen{Anwendung}{Using osgstyler}} \label{sec:using} \ldeen{Dieser Teil enthält alles, was zum Auswählen und Anwenden fertiger Stile nötig ist. Wer nur ein Dokument gestalten möchte, kann ihn der Reihe nach lesen und anschließend direkt zur Referenz springen. Die Deklaration eigener Paletten, Styles und Umgebungen beginnt erst in Abschnitt~\ref{sec:theme-development}.}{This part contains everything needed to select and apply ready-made styles. Readers who only want to style a document can read it in order and then skip directly to the reference. Declaring custom palettes, styles, and environments starts only in Section~\ref{sec:theme-development}.} \subsection{\ldeen{Farbpaletten}{Color Palettes}} \label{sec:colors} \ldeen{Eine Farbpalette bildet portable \emph{Slotnamen} auf konkrete \pkg*{xcolor}-Farben ab. Genau eine Farbpalette ist zu jedem Zeitpunkt \emph{aktiv}; alle anderen Teile von \osgstyler{} (Dekorationen, Inline-Styles, Blöcke) verweisen ausschließlich auf Slotnamen, nie auf konkrete Farbwerte.}{A color palette maps portable \emph{slot names} to concrete \pkg*{xcolor} colors. Exactly one color palette is \emph{active} at any time; every other part of \osgstyler{} (decorations, inline styles, blocks) refers only to slot names, never to concrete color values.} \label{sec:colors:use} \ldeen*{Beim Laden des Pakets ist bereits die eingebaute Palette \code{osgstyler-default} aktiv -- \osgstyler{} funktioniert also ohne jede Farbkonfiguration. Eine andere Palette wird mit @1 aktiviert:}{When the package is loaded, the built-in palette \code{osgstyler-default} is already active -- \osgstyler{} therefore works without any color configuration at all. A different palette is activated with @1:}{\cs{UseOsgColorPalette}} \begin{example}[code-only] \UseOsgColorPalette{osl-modern} \end{example} \ldeen*{@1/@2 sind zwei fertige, aber nicht automatisch aktive Presets (siehe Abschnitt~@3): eine zurückhaltende, dem unkolorierten \cls*{amsthm}-Erscheinungsbild nahe Variante für die Langform, und eine an einem verbreiteten, modernen Farbsystem orientierte. Beide werden in der Praxis eher über @4 als direkt über @5 aktiviert, da @4 zugleich die passende Font- und ggf.\ Listenpalette mitnimmt.}{@1/@2 are two ready-made but not automatically active presets (see Section~@3): a restrained variant close to unadorned \cls*{amsthm} appearance for the long form, and one modeled on a widely used, modern color system. In practice, both are usually activated via @4 rather than directly via @5, since @4 also picks up the matching font and, where available, list palette.} {\code{osl-standard}}{\code{osl-modern}}{\ref{sec:blocks:presets}} {\cs{UseOsgPreset}}{\cs{UseOsgColorPalette}} \ldeen*{Eigener Code, der eine Slotfarbe braucht -- etwa in einer eigenen Dekoration oder direkt im Dokument --, löst den Slotnamen mit dem expandierbaren @1 in einen stabilen \pkg*{xcolor}-Namen auf. Dieser Name ändert sich nicht, wenn die aktive Palette wechselt, wohl aber die Farbe, auf die er verweist.}{Custom code that needs a slot color -- e.g.\ in a custom decoration, or directly in the document -- resolves the slot name to a stable \pkg*{xcolor} name with the expandable @1. This name does not change when the active palette changes, but the color it refers to does.}{\cs{OsgColorName}} \begin{example}[code-only] \textcolor{\OsgColorName{accent}}{Accent-colored text} \end{example} \begin{warnbox}{\ldeen{Achtung}{Attention}} \ldeen*{Verwenden Sie niemals einen bloßen Slotnamen wie \code{accent} direkt als \pkg*{xcolor}-Farbnamen (\cs*{textcolor}\Marg{accent}) -- dieser Name existiert unter Umständen gar nicht oder verweist auf etwas anderes. Nur @1 liefert den unter der aktiven Palette tatsächlich definierten Namen.}{Never use a bare slot name such as \code{accent} directly as an \pkg*{xcolor} color name (\cs*{textcolor}\Marg{accent}) -- that name might not exist at all, or might refer to something else. Only @1 returns the name actually defined under the active palette.}{\cs{OsgColorName}} \end{warnbox} \subsection{\ldeen{Schriftpaletten}{Font Palettes}} \label{sec:fonts} \ldeen*{Analog zu Farbpaletten bildet eine Schriftpalette portable \emph{Slotnamen} auf @1-Ressourcen ab. @1 wird dafür bei Bedarf automatisch geladen -- außer die Paketoption @2 ist gesetzt, siehe Abschnitt~@3.}{Analogous to color palettes, a font palette maps portable \emph{slot names} to @1 resources. @1 is loaded automatically for this purpose -- unless the package option @2 is set, see Section~@3.} {\needpackage{fontspec}}{\option{colors-only}}{\ref{sec:fonts:colors-only}} \label{sec:fonts:use} \ldeen*{Wie bei Farben ist beim Laden bereits eine eingebaute Palette (\code{osgstyler-default}, reines Latin Modern) aktiv. Eine andere Palette wird mit @1 aktiviert, ein einzelner Slot im Fließtext mit @2 ausgewählt:}{As with colors, a built-in palette (\code{osgstyler-default}, plain Latin Modern) is already active when the package is loaded. A different palette is activated with @1, and a single slot is selected in running text with @2:}{\cs{UseOsgFontPalette}}{\cs{UseOsgFont}} \begin{example}[code-only] \UseOsgFontPalette{osl-modern} Fließtext bleibt \code{main}. {\UseOsgFont{companion}Dieser Textteil nutzt die Begleitschrift.} \end{example} \ldeen*{Der Slot @1 lässt sich nicht auswählen (@2 bricht mit Fehler ab) -- er ist für einen späteren @3-Adapter reserviert und liefert keine eigenständige Textschriftfamilie.}{The slot @1 cannot be selected (@2 aborts with an error) -- it is reserved for a future @3 adapter and does not provide a standalone text font family.}{\code{math}} {\cs{UseOsgFont}\Marg{math}}{\needpackage{unicode-math}} \subsection{\ldeen{Metrikpaletten}{Metric Palettes}} \label{sec:metrics} \ldeen*{Eine Metrikpalette bildet portable Größen-Slots auf konkrete Längen ab -- für Abstände, Innenabstände (Padding), Linienstärken und Eckenradien. Anders als Farb-/Font-Slots sind Metrikwerte in vier \emph{Kategorien} gruppiert, jede mit eigenen Slotnamen: @1.}{A metric palette maps portable size slots to concrete lengths -- for spacing, padding, rule thickness, and corner radii. Unlike color/font slots, metric values are grouped into four \emph{categories}, each with its own slot names: @1.}{\code{spacing}/\code{padding}/\code{rule}/\code{radius}} \label{sec:metrics:use} \ldeen*{Die eingebaute Palette @1 ist beim Laden bereits aktiv. Eine andere Palette wird mit @2 aktiviert; der aktive Wert eines Slots wird mit dem expandierbaren @3 abgefragt.}{The built-in palette @1 is already active when the package is loaded. A different palette is activated with @2; the active value of a slot is queried with the expandable @3.} {\code{osgstyler-default}}{\cs{UseOsgMetricPalette}}{\cs{OsgMetric}} \begin{example}[code-only] \UseOsgMetricPalette{my-metrics} \setlength\fboxsep{\OsgMetric{padding}{normal}} \end{example} \ldeen{Eigene Dekorationen und Templates greifen typischerweise nicht direkt auf \cs{OsgMetric} zu, sondern erlauben eine Metrikreferenz der Form \meta{Kategorie}\code{/}\meta{Slot} an genau der Stelle, an der auch eine literale Länge stehen darf (siehe \code{thickness}/\code{padding} bei Dekorationen, Abschnitt~\ref{sec:decorations}).}{Custom decorations and templates typically don't access \cs{OsgMetric} directly, but instead accept a metric reference of the form \meta{category}\code{/}\meta{slot} anywhere a literal length is also allowed (see \code{thickness}/\code{padding} for decorations, Section~\ref{sec:decorations}).} \subsection{\ldeen{Listenpaletten}{List Palettes}} \label{sec:lists} \ldeen*{Anders als die bisherigen Paletten ist eine Listenpalette keine reine Datenablage: @1 wendet die hinterlegten Optionen \emph{sofort} über @2 an, denn genau das ist auch @2s eigenes Anwendungsmodell -- eine Aktivierung gilt ab dem Aufruf, nicht rückwirkend für bereits gesetzte Listen. Werte je Ebene sind rohe @2-Optionen; \osgstyler{} erfindet @2s Vokabular nicht neu, sondern organisiert es nur unter einem Palettennamen.}{Unlike the palettes so far, a list palette is not a pure data store: @1 applies the stored options \emph{immediately} via @2, since that matches @2's own application model -- an activation takes effect from the call onward, not retroactively for lists already typeset. Values per level are raw @2 options; \osgstyler{} does not reinvent @2's vocabulary, it only organizes it under a palette name.}{\cs{UseOsgListPalette}}{\pkg*{enumitem}} \label{sec:lists:use} \ldeen*{Die eingebaute Palette @1 lässt @2s eigene Vorgaben unangetastet. Eine andere Palette wird mit @3 aktiviert.}{The built-in palette @1 leaves @2's own defaults untouched. A different palette is activated with @3.}{\code{osgstyler-default}}{\pkg*{enumitem}} {\cs{UseOsgListPalette}} \begin{example}[code-only] \UseOsgListPalette{osl-modern} \begin{itemize} \item Farbige quadratische Bullets statt Standard-Bullets. \end{itemize} \end{example} \ldeen{Nicht deklarierte Ebenen oder Listenarten einer Palette bleiben unangetastet -- \code{osl-modern} etwa ändert nur \env{itemize}-Ebene~1, Ebenen~2--4 sowie \env{enumerate}/\env{description} bleiben bei \pkg*{enumitem}s eigenen Vorgaben.}{Levels or list types not declared in a palette stay untouched -- \code{osl-modern}, for instance, only changes \env{itemize} level~1; levels~2--4 as well as \env{enumerate}/\env{description} keep \pkg*{enumitem}'s own defaults.} \subsection{\ldeen{Dekorationen}{Decorations}} \label{sec:decorations} \ldeen{Eine Dekoration ist ein backend-neutrales visuelles Rezept: eine benannte Kombination aus Linien, Hintergrund, Rahmen, Randmarkierung und/oder Vor-/Nachzeichen (\emph{Affixe}), beschrieben rein über Werte (Farb-Slots, Metrik-Slots, Muster) -- ohne festzulegen, \emph{wie} daraus tatsächlich Tinte auf dem Papier wird. Das erledigt ein austauschbarer \emph{Prozessor}, siehe Abschnitt~\ref{sec:decorations:processors}.}{A decoration is a backend-neutral visual recipe: a named combination of lines, background, border, margin marking, and/or leading/trailing marks (\emph{affixes}), described purely through values (color slots, metric slots, patterns) -- without fixing \emph{how} that actually becomes ink on paper. That is handled by a replaceable \emph{processor}, see Section~\ref{sec:decorations:processors}.} \label{sec:decorations:use} \ldeen*{Die meisten Dokumente berühren Dekorationen nie direkt: die eingebauten @1/@2/@3 stecken hinter den gleichnamigen Inline-Styles (Abschnitt~@4) und werden über \cs{UseOsgInlineStyle} bzw.\ gebundene Befehle wie \cs{uline} genutzt.}{Most documents never touch decorations directly: the built-in @1/@2/@3 sit behind the inline styles of the same name (Section~@4) and are used via \cs{UseOsgInlineStyle} or bound commands such as \cs{uline}.} {\code{osgstyler-underline}}{\code{osgstyler-highlight}} {\code{osgstyler-frame}}{\ref{sec:inline}} \begin{warnbox}{\ldeen{Wichtig}{Important}} \ldeen*{Eine deklarierte Dekoration zeichnet nichts von selbst. Ob @1 (Linien, Hintergrund) tatsächlich sichtbar werden, hängt vom aktiven \emph{Prozessor} ab: der eingebaute Standardprozessor zeichnet nur @2 und @3 (siehe Referenztabelle unten); @1 verlangt @4 (siehe Abschnitt~@5) oder einen eigenen Prozessor.}{A declared decoration does not draw anything by itself. Whether @1 (lines, background) actually become visible depends on the active \emph{processor}: the built-in default processor only draws @2 and @3 (see the reference table below); @1 requires @4 (see Section~@5) or a custom processor.} {\code{line}/\code{background}}{\code{enclosure}}{\code{affix}} {\pkg{osgstyler-lua}}{\ref{sec:lua}} \end{warnbox} \ldeen{Für Theme-/Backend-Code lässt sich abfragen, ob überhaupt ein rendernder Prozessor aktiv ist, sowie, welche Komponenten/Instanzen eine Dekoration hat, ohne deren konkrete Werte zu kennen:}{Theme/backend code can query whether a rendering processor is active at all, as well as which components/instances a decoration has, without knowing its concrete values:} \begin{example}[code-only] \OsgDecorationProcessorAvailableTF { } { \PackageWarning{my-theme}{No~decoration~backend~active} } \end{example} \subsection{\ldeen{Inline-Styles}{Inline Styles}} \label{sec:inline} \ldeen*{Inline-Styles sind die erste Kompositionsebene: eine benannte Instanz des Kernel-Template-Typs @1 bündelt Font-/Farbwahl, Schriftschnitt und -stärke, eine Dekoration und eine semantische Rolle zu einem einzigen wiederverwendbaren Stil.}{Inline styles are the first composition layer: a named instance of the kernel template type @1 bundles font/color choice, shape and weight, a decoration, and a semantic role into a single, reusable style.}{\code{osgstyler-inline}} \label{sec:inline:use} \ldeen*{Fünf Styles sind vordefiniert: @1. Direkt aufgerufen wird ein Style mit @2:}{Five styles are predefined: @1. A style is invoked directly with @2:} {\code{emphasis}/\code{strong}/\code{underline}/\code{highlight}/\code{frame}} {\cs{UseOsgInlineStyle}} \begin{example}[code-only] \UseOsgInlineStyle{emphasis}{hervorgehobener Text} \end{example} \ldeen*{In der Praxis werden Inline-Styles meist nicht direkt aufgerufen, sondern über @1 an einen gewohnten Befehlsnamen \emph{gebunden}, sodass weiterhin @2, @3 usw.\ geschrieben wird:}{In practice, inline styles are usually not invoked directly, but \emph{bound} via @1 to a familiar command name, so that @2, @3, etc.\ continue to be written:} {\cs{BindOsgInlineCommand}}{\cs{emph}}{\cs{textbf}} \begin{example}[code-only] \UseOsgStandardInlineCommands % bindet \emph, \textbf, \uline, \highlight, \fbox \end{example} \ldeen*{@1 bindet einzeln nach Bedarf; @2 bindet alle fünf Standardbefehle auf einmal. \cs{highlight} ist unabhängig davon bereits beim Laden des Pakets gebunden, da es kein vorbestehendes \LaTeX-Kommando gibt, das sonst überraschend ersetzt würde.}{@1 binds individually as needed; @2 binds all five standard commands at once. \cs{highlight} is already bound when the package loads, independently of this, since there is no pre-existing \LaTeX{} command that would otherwise be surprisingly replaced.}{\cs{BindOsgInlineCommand}}{\cs{UseOsgStandardInlineCommands}} \ldeen*{Eine Bindung ändert die \emph{Implementierung} des Befehls, nie seine Aufrufsyntax -- @1 nimmt weiterhin genau ein Argument. War der Befehl vor dem Binden bereits definiert (wie @2/@3), bleibt seine ursprüngliche Bedeutung als @4-Plug erhalten; @5 (pro Befehl) bzw.\ @6 (gesammelt) schaltet dorthin zurück. Für neu durch \osgstyler{} eingeführte Befehle wie @7 ist der @4-Plug stattdessen die Identität (gibt das Argument unverändert zurück).}{A binding changes the command's \emph{implementation}, never its call syntax -- @1 continues to take exactly one argument. If the command already existed before binding (such as @2/@3), its original meaning is preserved as the @4 plug; @5 (per command) or @6 (collectively) switches back to it. For commands newly introduced by \osgstyler{} such as @7, the @4 plug is the identity instead (returns the argument unchanged).} {\meta{\ldeen{Befehl}{command}}}{\cs{emph}}{\cs{textbf}} {\code{default}}{\cs{RestoreOsgInlineCommand}} {\cs{UseOsgDefaultInlineCommands}}{\cs{highlight}} \ldeen{Wie jede \TeX-Zuweisung ist eine Bindung gruppenlokal -- ein Style lässt sich also auch nur für einen Abschnitt des Dokuments ändern.}{Like any \TeX{} assignment, a binding is group-local -- a style can therefore also be changed for only part of the document.} \begin{example}[code-only] {\BindOsgInlineCommand\emph{strong} \emph{fett statt kursiv, nur in dieser Gruppe}} \emph{wieder wie gewohnt} \end{example} \begin{infobox}{\ldeen{Mit osglecture-modes}{With osglecture-modes}} \ldeen*{Ist @1 geladen (in beliebiger Reihenfolge zu \osgstyler{}), akzeptieren @2 sowie \emph{jeder} über @3 gebundene Befehl zusätzlich ein portables Modusargument, siehe dessen Dokumentation.}{If @1 is loaded (in either order relative to \osgstyler{}), @2 as well as \emph{every} command bound via @3 additionally accepts a portable mode expression, see that package's documentation.} {\pkg{osglecture-modes}}{\cs{UseOsgInlineStyle}} {\cs{BindOsgInlineCommand}} \begin{example}[code-only] \emph{nur im Präsentationsmodus hervorgehoben} \end{example} \end{infobox} \subsection{\ldeen{Bausteine: Blöcke}{Blocks}} \label{sec:blocks} \ldeen{Blöcke sind die zweite Kompositionsebene: kastenartige, umrandete oder hinterlegte Inhalte -- Beamer-Blöcke ebenso wie theorem-artige Boxen oder Zitate in der Langform. Dies ist der umfangreichste Teil von \osgstyler{}, da er sich sowohl an gewöhnliche Dokumente (Zitate, fertige Blöcke) als auch an Theme-/Paketautor*innen (eigene Umgebungen, \pkg*{amsthm}-Integration) richtet.}{Blocks are the second composition layer: boxed, framed, or filled content -- beamer blocks as well as theorem-like boxes or quotations in the long form. This is the most extensive part of \osgstyler{}, since it addresses both ordinary documents (quotations, ready-made blocks) and theme/package authors (custom environments, \pkg*{amsthm} integration).} \subsubsection{\ldeen{Konzept}{Concept}} \label{sec:blocks:concept} \ldeen*{Wie Inline-Styles ist ein Block eine Instanz eines Kernel-Template-Typs, hier @1. Anders als bei Inline-Styles trennt \osgstyler{} bei Blöcken jedoch bewusst zwei unabhängige Fragen:}{Like inline styles, a block is an instance of a kernel template type, here @1. Unlike inline styles, however, \osgstyler{} deliberately separates two independent questions for blocks:}{\code{osgstyler-block}} \begin{enumerate} \item \ldeen{\emph{Welche Optik} hat der Block?}{\emph{What visual appearance} does the block have?} \ldeen{-- festgelegt durch die Interface-Schlüssel einer Instanz (Rahmenfarbe/-stärke, Innenabstand, Hintergrund, Text vor/nach dem Inhalt).}{-- determined by an instance's interface keys (rule color/thickness, padding, background, text before/after the content).} \item \ldeen{\emph{Wie kommt Inhalt} in diese Optik hinein?}{\emph{How does content get} into that appearance?} \ldeen{-- entweder, indem eine bereits bestehende Umgebung (\env{quote}, ein \pkg*{amsthm}-\env{theorem}, Beamers \env{block}) optisch \emph{umhüllt} wird (Abschnitt~\ref{sec:blocks:create}, Fall~A), oder indem \osgstyler{} eine \emph{neue} Umgebung von Grund auf erzeugt (Fall~B).}{-- either by visually \emph{wrapping} an already-existing environment (\env{quote}, an \pkg*{amsthm} \env{theorem}, beamer's \env{block}) (Section~\ref{sec:blocks:create}, case~A), or by having \osgstyler{} create a \emph{new} environment from scratch (case~B).} \end{enumerate} \ldeen*{Diese Trennung bedeutet: die Render-Logik einer Optik existiert \emph{einmal}, unabhängig davon, wie viele Umgebungen sie am Ende nutzen, und eine umhüllte Umgebung behält ihre eigene Nummerierung, ihren eigenen \pkg*{amsthm}-Zähler usw.\ vollständig -- @1 fügt nur Optik um deren fertige Ausgabe herum, ohne sie zu interpretieren.}{This separation means: the rendering logic for an appearance exists \emph{once}, no matter how many environments end up using it, and a wrapped environment fully keeps its own numbering, its own \pkg*{amsthm} counter, etc.\ -- @1 only adds appearance around its finished output, without interpreting it.} {\cs{BindOsgBlockEnvironment}} \begin{warnbox}{\ldeen{Aktueller Stand}{Current Status}} \ldeen*{Der Standard-Renderer zeichnet @1: eine farbige Umrandung fester Stärke (\pkg*{fbox}-artig), einen Innenabstand und wahlweise einen Hintergrund. @2 und @3 wirken nur im Kastenzweig des \code{titled}-Layouts (Trait \code{titlebar}) und nur, wenn \pkg{pgfcore} geladen ist (etwa durch \pkg*{lttheme}s \code{magpie}-Theme); der \code{plain}-Kastenzweig (\code{card}, \code{framed}, \code{filled}) bleibt vorerst eckig ohne Schatten. Fehlt \pkg{pgfcore}, fällt der Renderer auf eckig zurück.}{The default renderer draws @1: a colored border of fixed thickness (\pkg*{fbox}-style), a padding, and optionally a background. @2 and @3 take effect only in the boxed branch of the \code{titled} layout (trait \code{titlebar}) and only when \pkg{pgfcore} is loaded (e.g.\ by \pkg*{lttheme}'s \code{magpie} theme); the \code{plain} boxed branch (\code{card}, \code{framed}, \code{filled}) stays square without a shadow for now. Without \pkg{pgfcore} the renderer falls back to square.} {\code{rule-color}/\code{rule-thickness}/\code{padding}/\code{background}} {\code{corner-radius}}{\code{shadow}/\code{shadow-offset}/\code{shadow-color}} {\ref{sec:blocks:traits}} \end{warnbox} \label{sec:blocks:use} \ldeen*{Fünf Instanzen sind immer schon deklariert (unsichtbar, wie es die Interface-Vorgaben ohnehin vorsehen), aber \emph{nicht} automatisch mit einer Umgebung verbunden: @1/@2 (ohne eigene Optik) sowie @3/@4/@5 (mit Rahmen und Innenabstand; @4/@5 zusätzlich in Alarm-/Beispielfarbe). @6 aktiviert alle fünf auf einmal:}{Five instances are always already declared (invisible, as the interface defaults provide anyway), but \emph{not} automatically connected to an environment: @1/@2 (with no appearance of their own) as well as @3/@4/@5 (with a border and padding; @4/@5 additionally in alert/example color). @6 activates all five at once:} {\env{quote}}{\env{quotation}}{\env{block}}{\env{alertblock}} {\env{exampleblock}}{\cs{UseOsgStandardBlockEnvironments}} \begin{example}[code-only] \UseOsgStandardBlockEnvironments \begin{document} \begin{quote} Ein Zitat, jetzt mit Rahmen aus der aktiven Farbpalette. \end{quote} \begin{block}{Titel} Ein Beamer-artiger Block, auch in der Langform verfügbar. \end{block} \end{document} \end{example} \ldeen*{@1/@2/@3 werden dabei nur dann \emph{neu erzeugt}, wenn sie noch nicht existieren (Langform ohne Beamer/\cls*{ltx-talk}): unter \cls*{ltx-talk} existieren sie bereits nativ und behalten dort ihre eigene Optik -- nur @4/@5 werden dort umgebunden.}{@1/@2/@3 are only \emph{newly created} if they do not already exist (long form without beamer/\cls*{ltx-talk}): under \cls*{ltx-talk}, they already exist natively and keep their own appearance there -- only @4/@5 are rebound there.}{\env{block}}{\env{alertblock}}{\env{exampleblock}} {\env{quote}}{\env{quotation}} \begin{warnbox}{\ldeen{Wichtig}{Important}} \ldeen*{Nichts davon geschieht beim bloßen Laden von \osgstyler{}. Wer das Paket nur wegen Farben oder Inline-Befehlen lädt, soll nicht überrascht feststellen, dass sich seine Zitate verändert haben -- @1 ist ein bewusster, expliziter Opt-in.}{None of this happens merely by loading \osgstyler{}. Anyone loading the package only for colors or inline commands should not be surprised to find their quotations changed -- @1 is a deliberate, explicit opt-in.} {\cs{UseOsgStandardBlockEnvironments}} \end{warnbox} \subsubsection{\ldeen{Presets}{Presets}} \label{sec:blocks:presets} \ldeen*{Ein \emph{Preset} bündelt Farb-, Font- und ggf.\ Listenpalette sowie ggf.\ einen Block-Trait unter einem gemeinsamen Namen. @1 aktiviert alle vorhandenen Teile eines Presets auf einmal.}{A \emph{preset} bundles a color, font, and possibly list palette, plus possibly a block trait, under one shared name. @1 activates every part of a preset that exists, all at once.}{\cs{UseOsgPreset}} \begin{example}[code-only] \UseOsgPreset{osl-modern} \end{example} \ldeen{Zwei Presets liegen bei:}{Two presets are bundled:} \begin{description} \item[\code{osl-standard}] \ldeen{Farb-/Font-Palette; zurückhaltend, dem unkolorierten \cls*{amsthm}-Erscheinungsbild nahe. Kein Trait, keine Listenpalette.}{Color/font palette; restrained, close to unadorned \cls*{amsthm} appearance. No trait, no list palette.} \item[\code{osl-modern}] \ldeen{Farb-/Font-Palette; Listenpalette mit farbigen quadratischen Bullets (Abschnitt~\ref{sec:lists}); Trait \code{osl-modern-rules} (siehe unten).}{Color/font palette; list palette with colored square bullets (Section~\ref{sec:lists}); trait \code{osl-modern-rules} (see below).} \end{description} \ldeen*{Der Trait @1 gibt @2/@3 sowie den drei @4-Instanzen (@5/@6/@7) eine dünne, akzentfarbene Linie vor und nach dem Inhalt statt eines Rahmens (\code{rule-thickness=0pt}) -- passend zum unkastenlosen \pkg*{amsthm}-Vorbild, siehe Abschnitt~\ref{sec:blocks:traits}. @8 wendet ihn automatisch auf alle sieben Instanzen an, sofern sie existieren (unabhängig davon, ob deren Umgebungen bereits gebunden sind).}{The trait @1 gives @2/@3 as well as the three @4 instances (@5/@6/@7) a thin, accent-colored line before and after the content instead of a border (\code{rule-thickness=0pt}) -- matching the boxless \pkg*{amsthm} model, see Section~\ref{sec:blocks:traits}. @8 applies it automatically to all seven instances, provided they exist (regardless of whether their environments are already bound).} {\code{osl-modern-rules}}{\env{quote}}{\env{quotation}} {\env{\ldeen{block-artigen}{block-like}}}{\env{block}}{\env{alertblock}} {\env{exampleblock}}{\cs{UseOsgPreset}} \subsection{\ldeen{Nummerierung}{Numbering}} \label{sec:numbering} \ldeen*{Eine Nummerierung liefert \emph{ausschließlich} die formatierte Zahl (z.\,B. "`2.3"'); weder ein lokalisiertes Label wie "`Satz"' noch ein optionaler Autoren-Titel gehören dazu -- beides bleibt Sache der Umgebung, die eine Nummerierung nutzt (typischerweise über \cs{NewOsgStyledEnvironment}, Abschnitt~\ref{sec:blocks:new}), damit \osgstyler{} selbst sprachneutral bleibt.}{A numbering produces \emph{only} the formatted number (e.g.\ "`2.3"'); neither a localized label such as "`Theorem"' nor an optional author-supplied title belong here -- both remain the concern of the environment that uses a numbering (typically via \cs{NewOsgStyledEnvironment}, Section~\ref{sec:blocks:new}), so that \osgstyler{} itself stays language-neutral.} \label{sec:numbering:use} \ldeen*{Wer eine bereits deklarierte Nummerierung in eigenem Umgebungscode braucht (statt sie über \cs{NewOsgStyledEnvironment} automatisch nutzen zu lassen): @1 erhöht den zugrundeliegenden Zähler (\cs*{refstepcounter}), das expandierbare @2 liefert den formatierten Wert.}{Anyone who needs an already-declared numbering in custom environment code (rather than letting \cs{NewOsgStyledEnvironment} use it automatically): @1 increments the underlying counter (\cs*{refstepcounter}), the expandable @2 returns the formatted value.}{\cs{StepOsgNumbering}}{\cs{OsgNumberingValue}} \begin{example}[code-only] \StepOsgNumbering{mynote} Notiz~\OsgNumberingValue{mynote} \end{example} \section{\ldeen{Themen entwickeln}{Developing Themes}} \label{sec:theme-development} \ldeen{Dieser Teil richtet sich an Autor*innen von Themen und Vorlagen. Er setzt die Begriffe aus dem Anwendungsteil voraus und behandelt das Deklarieren neuer Ressourcen sowie die Backend- und Paketintegration.}{This part is intended for authors of themes and templates. It assumes the terms introduced in the user part and covers declaring new resources as well as backend and package integration.} \subsection{\ldeen{Farbpaletten}{Color Palettes}} \label{sec:colors:create} \ldeen*{Eine neue Farbpalette wird mit @1 deklariert. Genau ein Slot, \code{primary}, ist Pflicht; alle anderen Slots fallen andernfalls auf einen anderen Slot derselben Palette zurück, siehe unten.}{A new color palette is declared with @1. Exactly one slot, \code{primary}, is required; every other slot otherwise falls back to another slot of the same palette, see below.}{\cs{DeclareOsgColorPalette}} \begin{example}[code-only] \DeclareOsgColorPalette{my-theme}{ primary = {model=HTML, value=2E4172}, accent = {model=HTML, value=E8A33D}, alert = {model=RGB, value={176,0,32}}, structure = {alias=primary} } \UseOsgColorPalette{my-theme} \end{example} \ldeen{Jeder Slot ist entweder eine \emph{Ressource} (\code{model}+\code{value}, in jedem von \pkg*{xcolor} unterstützten Modell, z.\,B. \code{HTML}, \code{RGB}, \code{gray}) oder ein \emph{Alias} (\code{alias=}\meta{anderer Slot} -- wird beim Aktivieren der Palette einmalig aufgelöst und kopiert, wie \cs*{colorlet}; ändert sich die Zielfarbe später, muss die Palette erneut aktiviert werden, damit der Alias nachzieht). Ein Slot darf nicht beides zugleich sein.}{Each slot is either a \emph{resource} (\code{model}+\code{value}, in any model \pkg*{xcolor} supports, e.g.\ \code{HTML}, \code{RGB}, \code{gray}) or an \emph{alias} (\code{alias=}\meta{other slot} -- resolved and copied once when the palette is activated, like \cs*{colorlet}; if the target color changes later, the palette must be reactivated for the alias to follow). A slot cannot be both at once.} \ldeen*{Nicht definierte Slots fallen automatisch zurück, damit eine minimale Palette (nur @1) bereits vollständig nutzbar ist:}{Undefined slots automatically fall back, so that a minimal palette (only @1) is already fully usable:}{\code{primary}} \begin{center} \begin{tabular}{ll} \textbf{Slot} & \textbf{\ldeen{Rückfall}{Fallback}}\\\hline \code{secondary} & \code{primary}\\ \code{accent} & \code{secondary}\\ \code{structure}, \code{alert}, \code{example} & \code{primary}\\ \code{text} & \ldeen{fest}{fixed} \code{black}\\ \code{background} & \ldeen{fest}{fixed} \code{white}\\\hline \end{tabular} \end{center} \ldeen*{\code{text} und \code{background} fallen bewusst \emph{nicht} auf eine andere Themefarbe zurück (alle anderen Slots landen letztlich bei @1) -- eine Themefarbe als Fließtext- oder Seitenhintergrundfarbe könnte unzureichenden Kontrast verursachen, was für Akzente unproblematisch, für Fließtext aber inakzeptabel wäre.}{\code{text} and \code{background} deliberately do \emph{not} fall back to another theme color (every other slot ultimately lands on @1) -- a theme color as body-text or page-background color could cause insufficient contrast, which is unproblematic for accents but unacceptable for body text.}{\code{primary}} \subsection{\ldeen{Schriftpaletten}{Font Palettes}} \label{sec:fonts:create} \ldeen*{Eine neue Schriftpalette wird mit @1 deklariert. Der Slot @2 ist Pflicht (mit eigener Ressource, kein Alias); alle übrigen Slots sind optional.}{A new font palette is declared with @1. The slot @2 is required (with its own resource, not an alias); every other slot is optional.}{\cs{DeclareOsgFontPalette}}{\code{main}} \begin{example}[code-only] \DeclareOsgFontPalette{my-fonts}{ main = {font = {Source Serif 4}}, companion = {font = {Source Sans 3}, features = {Numbers=OldStyle}}, display = {alias = companion}, accent = {alias = companion}, mono = {font = {Source Code Pro}}, symbol = {alias = main} } \UseOsgFontPalette{my-fonts} \end{example} \ldeen{Jeder Slot ist entweder eine \emph{Ressource} (\code{font}, optional mit \pkg*{fontspec}-\code{features}, sowie optional einer \code{description} für Werkzeuge/Themes) oder ein \emph{Alias} (\code{alias=}\meta{anderer Slot}). Ein Slot darf nicht beides zugleich sein. Anders als bei Farben wird ein Alias bei jeder tatsächlichen Auswahl neu aufgelöst, nicht einmalig beim Aktivieren kopiert.}{Each slot is either a \emph{resource} (\code{font}, optionally with \pkg*{fontspec} \code{features}, plus an optional \code{description} for tools/themes) or an \emph{alias} (\code{alias=}\meta{other slot}). A slot cannot be both at once. Unlike colors, an alias is re-resolved on every actual selection, not copied once when activating.} \ldeen*{Nur @1 haben eingebaute Rückfälle; @2 selbst ist Pflicht und @3 hat, anders als alle übrigen Slots, keinen Rückfall -- fehlt @3, bricht erst die \emph{Auswahl} von @3 mit Fehler ab, nicht schon die Deklaration.}{Only @1 have built-in fallbacks; @2 itself is required, and @3, unlike every other slot, has no fallback -- if @3 is missing, only \emph{selecting} @3 aborts with an error, not the declaration itself.} {\code{companion}/\code{display}/\code{accent}/\code{symbol}} {\code{main}}{\code{mono}} \begin{center} \begin{tabular}{ll} \textbf{Slot} & \textbf{\ldeen{Rückfall}{Fallback}}\\\hline \code{companion} & \code{main}\\ \code{display} & \code{companion}\\ \code{accent} & \code{companion}\\ \code{symbol} & \code{main}\\ \code{mono} & \ldeen{keiner}{none}\\\hline \end{tabular} \end{center} \subsubsection{\ldeen{Engine-neutraler Betrieb}{Engine-Neutral Operation}} \label{sec:fonts:colors-only} \ldeen*{@1 verlangt \LuaTeX{} oder \XeTeX{}, was unter @2 (beispielsweise @3) kein tragbarer Zwang ist. Die Paketoption @4 hält @5 deshalb engine-neutral: @1 wird dann gar nicht erst geladen, und nur die \emph{Deklaration} von Schriftpaletten (reine Datenverwaltung) bleibt möglich -- @6/@7 quittieren jeden Versuch, tatsächlich einen Font auszuwählen oder zu laden, mit einer klaren Fehlermeldung statt eines kryptischen Absturzes.}{@1 requires \LuaTeX{} or \XeTeX{}, which is not a portable requirement under @2 (e.g.\ @3). The package option @4 therefore keeps @5 engine-neutral: @1 is then not loaded at all, and only \emph{declaring} font palettes (pure data management) remains possible -- @6/@7 reject any attempt to actually select or load a font with a clear error message instead of a cryptic crash.} {\needpackage{fontspec}}{\cls*{ltx-talk}}{pdf\TeX} {\option{colors-only}}{osgstyler}{\cs{UseOsgFontPalette}}{\cs{UseOsgFont}} \begin{example}[code-only] \usepackage[colors-only]{osgstyler} \end{example} \ldeen*{@1 prüft, ob Font-Auswahl gerade möglich ist -- nützlich für Theme-Code, der unter @2 auf reine Farboptik ausweichen will, statt einen Fehler zu riskieren.}{@1 checks whether font selection is currently possible -- useful for theme code that wants to fall back to color-only styling under @2 instead of risking an error.} {\cs{OsgFontPaletteAvailableTF}}{\option{colors-only}} \subsection{\ldeen{Metrikpaletten}{Metric Palettes}} \label{sec:metrics:create} \ldeen*{Jede Deklaration startet mit einem \emph{vollständigen} eingebauten Wertesatz (siehe Referenztabelle unten); @1 muss deshalb nur die tatsächlich abweichenden Werte angeben.}{Every declaration starts from a \emph{complete} built-in set of values (see the reference table below); @1 therefore only needs to specify the values that actually differ.}{\cs{DeclareOsgMetricPalette}} \begin{example}[code-only] \DeclareOsgMetricPalette{my-metrics}{ spacing = {sm = {.4\baselineskip}, lg = {1.25\baselineskip}}, rule = {thin = {.5pt}, thick = {1.5pt}} } \UseOsgMetricPalette{my-metrics} \end{example} \ldeen{Nur die vier genannten Kategorien mit ihren jeweiligen Slots existieren; eigene Kategorien oder Slotnamen lassen sich nicht hinzufügen.}{Only the four named categories with their respective slots exist; custom categories or slot names cannot be added.} \subsection{\ldeen{Listenpaletten}{List Palettes}} \label{sec:lists:create} \ldeen*{Eine neue Listenpalette wird mit @1 deklariert -- pro Listenart (@2) ein Satz von Ebenen (\code{1}--\code{4}), deren Wert jeweils rohe @3-Optionen für @3s eigenen @4-Aufruf sind.}{A new list palette is declared with @1 -- for each list type (@2), a set of levels (\code{1}--\code{4}) whose value is raw @3 options for @3's own @4 call.}{\cs{DeclareOsgListPalette}} {\code{itemize}/\allowbreak\code{enumerate}/\allowbreak\code{description}} {\pkg*{enumitem}}{\cs*{setlist}} \begin{example}[code-only] \DeclareOsgListPalette{my-lists}{ itemize = { 1 = { label = \textcolor{\OsgColorName{accent}}{\textbullet} } }, enumerate = { 1 = { label = \textbf{\arabic*.} } } } \UseOsgListPalette{my-lists} \end{example} \ldeen*{Da \pkg*{enumitem}s eigener Optionsparser sein Argument vor Expansion einliest, dürfen Werte hier -- anders als bei Farb-/Font-/ Metrikpaletten -- Befehle wie \cs{OsgColorName} direkt (unexpandiert) enthalten, wie im Beispiel oben.}{Since \pkg*{enumitem}'s own option parser reads its argument before expansion, values here -- unlike for color/font/metric palettes -- may contain commands such as \cs{OsgColorName} directly (unexpanded), as in the example above.} \subsection{\ldeen{Dekorationen}{Decorations}} \label{sec:decorations:create} \ldeen*{Eine Dekoration wird mit @1 deklariert. Jede der fünf Familien (@2) kommt entweder in \emph{Kurzform} (eine einzelne, implizit \code{default} genannte Instanz) oder mit \emph{benannten} Instanzen -- nützlich, um z.\,B. gleichzeitig eine Über- und eine Unterlinie anzulegen.}{A decoration is declared with @1. Each of the five families (@2) comes either in \emph{short form} (a single instance, implicitly named \code{default}) or with \emph{named} instances -- useful, for example, to set up an overline and an underline at the same time.} {\cs{DeclareOsgDecoration}} {\code{line}/\allowbreak\code{background}/\allowbreak\code{enclosure}/% \allowbreak\code{margin}/\allowbreak\code{affix}} \begin{example}[code-only] \DeclareOsgDecoration{marked-important}{ line = { position=below, pattern=solid, color=alert, thickness=.08em, offset=rule/thin }, background = { color=accent, opacity=.25, shape=text }, affix = { warning = { position=before, content={!}, color=alert, font=accent, gap=.3em } } } \end{example} \ldeen*{Im Beispiel ist @1 die -- implizite -- \code{default}-Instanz der @2-Familie; @3 ist eine eigens benannte Instanz der @4-Familie. Zwei gleichzeitige Linien (Über-/Unterlinie) sähen so aus:}{In the example, @1 is the -- implicit -- \code{default} instance of the @2 family; @3 is a specifically named instance of the @4 family. Two simultaneous lines (over-/underline) would look like this:} {\code{line}}{\code{line}}{\code{warning}}{\code{affix}} \begin{example}[code-only] \DeclareOsgDecoration{over-and-under}{ line = { under = { position=below, pattern=solid, color=accent }, over = { position=above, pattern=solid, color=accent } } } \end{example} \ldeen{Werte für \code{thickness}, \code{offset}, \code{padding} und \code{gap} akzeptieren entweder eine literale Länge oder eine Metrikreferenz \meta{Kategorie}\code{/}\meta{Slot} (Abschnitt \ref{sec:metrics}), z.\,B. \code{rule/thin} statt \code{.4pt}. \code{content} und \code{symbol} eines \code{affix} schließen sich gegenseitig aus. \code{font} und \code{color} verweisen stets auf portable Font-/Farb-Slots, nie auf konkrete Ressourcen.}{Values for \code{thickness}, \code{offset}, \code{padding}, and \code{gap} accept either a literal length or a metric reference \meta{category}\code{/}\meta{slot} (Section~\ref{sec:metrics}), e.g.\ \code{rule/thin} instead of \code{.4pt}. A \code{content} and a \code{symbol} of the same \code{affix} are mutually exclusive. \code{font} and \code{color} always refer to portable font/color slots, never to concrete resources.} \subsubsection{\ldeen{Renderstatus je Prozessor}{Rendering Status per Processor}} \label{sec:decorations:status} \ldeen*{Nicht jede deklarierbare Eigenschaft wird von den mitgelieferten Prozessoren (dem eingebauten Standardprozessor und @1, siehe Abschnitt~@2) auch tatsächlich gezeichnet -- manche Eigenschaften sind bewusst für einen mächtigeren, künftigen Backend reserviert. Diese Tabelle nennt den aktuellen Stand ehrlich:}{Not every declarable property is actually drawn by the bundled processors (the built-in default processor and @1, see Section~@2) either -- some properties are deliberately reserved for a more capable, future backend. This table states the current status honestly:}{\pkg{osgstyler-lua}}{\ref{sec:lua}} \begin{center} \begin{tabular}{lll} \textbf{\ldeen{Familie}{Family}} & \textbf{\ldeen{Standardprozessor}{Default processor}} & \textbf{\code{osgstyler-lua}}\\\hline \code{line} & \ldeen{nicht gezeichnet (Fehler)}{not drawn (error)} & \ldeen{\code{solid}/\code{dashed}/\code{dotted}/\code{wavy}, unten/über/durch}{\code{solid}/\code{dashed}/\code{dotted}/\code{wavy}, below/above/through}\\ \code{background} & \ldeen{nicht gezeichnet (Fehler)}{not drawn (error)} & \ldeen{mit Opazität und \code{text}/\code{line}-Form} {with opacity and \code{text}/\code{line} shape}\\ \code{enclosure} & \ldeen{eckig, \code{solid}}{square, \code{solid}} & \ldeen{Radius; \code{solid}/\code{dashed}/\code{dotted}} {radius; \code{solid}/\code{dashed}/\code{dotted}}\\ \code{margin} & \ldeen{ignoriert}{ignored} & \ldeen{nicht gezeichnet (Warnung)}{not drawn (warning)}\\ \code{affix} & \ldeen{gezeichnet}{drawn} & \ldeen{gezeichnet (dieselbe Logik)}{drawn (same logic)}\\\hline \end{tabular} \end{center} \ldeen*{Innerhalb von \code{enclosure} zeichnet der Standardprozessor @1, ignoriert aber @2 und @3 (@3 warnt bei Werten ungleich \code{0pt}). Das Lua-Backend wertet dagegen Radius sowie solide, gestrichelte und gepunktete Rahmen aus.}{Within \code{enclosure}, the default processor draws @1 but ignores @2 and @3 (@3 warns for values other than \code{0pt}). The Lua backend additionally honours the radius and solid, dashed, and dotted strokes.} {\code{border-color}/\code{fill-color}/\code{thickness}/\code{padding}} {\code{pattern}}{\code{radius}} \subsubsection{\ldeen{Prozessoren (Backend-Ebene)}{Processors (Backend Level)}} \label{sec:decorations:processors} \ldeen*{Eine dritte Ebene neben Nutzen/Erstellen: \emph{wie} eine Dekoration gezeichnet wird, legt ein \emph{Prozessor} fest -- eine Funktion, die den Dekorationsnamen und den zu dekorierenden Inhalt nimmt und dekorierten Inhalt zurückgibt. @1 ersetzt den aktuellen Dekorationsprozessor vollständig; @2 tut dasselbe für semantische Verarbeitung (siehe Abschnitt~@3). Beide sind ab Werk transparent (Dekoration: unterstützt nur @4/@5, siehe oben; Semantik: gibt den Inhalt unverändert zurück).}{A third level alongside using/creating: \emph{how} a decoration is drawn is determined by a \emph{processor} -- a function that takes the decoration name and the content to decorate and returns decorated content. @1 replaces the current decoration processor entirely; @2 does the same for semantic processing (see Section~@3). Both are transparent out of the box (decoration: only supports @4/@5, see above; semantics: returns the content unchanged).}{\cs{SetOsgDecorationProcessor}}{\cs{SetOsgSemanticProcessor}} {\ref{sec:inline}}{\code{enclosure}}{\code{affix}} \begin{example}[code-only] \SetOsgDecorationProcessor{\MyDecorationProcessor} \end{example} \ldeen{Dieser Mechanismus richtet sich an Backend-/Themeautor*innen, nicht an gewöhnliche Dokumente -- \pkg{osgstyler-lua} (Abschnitt~\ref{sec:lua}) ist selbst nur ein über \cs{SetOsgDecorationProcessor} installierter Prozessor und kann so durch einen eigenen ersetzt werden.}{This mechanism targets backend/theme authors, not ordinary documents -- \pkg{osgstyler-lua} (Section~\ref{sec:lua}) is itself just a processor installed via \cs{SetOsgDecorationProcessor} and can thus be replaced by a custom one.} \subsection{\ldeen{Inline-Styles}{Inline Styles}} \label{sec:inline:create} \ldeen*{Ein neuer Stil wird mit @1 deklariert. Alle sechs Schlüssel sind optional; fehlt einer, gilt die in der Tabelle genannte Vorgabe.}{A new style is declared with @1. All six keys are optional; if one is omitted, the default noted in the table applies.} {\cs{DeclareOsgInlineStyle}} \begin{example}[code-only] \DeclareOsgInlineStyle{warning-text}{ color = alert, weight = bold, semantics = none } \end{example} \begin{center} \begin{tabular}{lll} \textbf{\ldeen{Schlüssel}{Key}} & \textbf{\ldeen{Vorgabe}{Default}} & \textbf{\ldeen{Bedeutung von "`keine Änderung"'}{Meaning of ``no change''}}\\\hline \code{font} & \code{main} & \code{none}\\ \code{weight} & \code{inherit} & \code{inherit}\\ \code{shape} & \code{inherit} & \code{inherit}\\ \code{color} & \code{text} & \code{none}\\ \code{decoration} & \code{none} & \code{none}\\ \code{semantics} & \code{none} & \code{none}\\\hline \end{tabular} \end{center} \begin{warnbox}{\ldeen{Achtung}{Attention}} \ldeen*{Die Vorgabe für @1 ist @2, nicht @3 -- ein Stil, der @1 weglässt, wählt bei jeder Verwendung erneut die Haupt\-schrift aus. Wer nur Farbe oder Dekoration ändern will, muss @1 explizit auf @3 setzen (wie es alle fünf eingebauten Stile tun).}{The default for @1 is @2, not @3 -- a style that omits @1 reselects the main font on every use. Anyone who only wants to change color or decoration must explicitly set @1 to @3 (as all five built-in styles do).} {\code{font}}{\code{main}}{\code{none}} \end{warnbox} \ldeen*{\code{weight} kennt @1, \code{shape} kennt @2.}{\code{weight} accepts @1, \code{shape} accepts @2.} {\code{inherit}/\code{regular}/\code{bold}} {\code{inherit}/\code{upright}/\code{italic}/\code{slanted}/\code{small-caps}} \ldeen*{\code{semantics} verweist auf eine \emph{Rolle} (z.\,B. \code{emphasis}, \code{strong}), die -- wie \code{decoration} -- an den aktiven Prozessor durchgereicht wird (Abschnitt~@1); ohne installierten semantischen Prozessor hat @2 keine sichtbare Wirkung außer der Weitergabe.}{\code{semantics} refers to a \emph{role} (e.g.\ \code{emphasis}, \code{strong}) that -- like \code{decoration} -- is passed to the active processor (Section~@1); without an installed semantic processor, @2 has no visible effect beyond that hand-off.} {\ref{sec:decorations:processors}}{\code{semantics}} \ldeen*{@1 ändert einen bereits deklarierten Stil (nur die angegebenen Schlüssel); für eigene, komplett neue Befehlsbindungen mit mehr als einem Argument bietet \osgstyler{} bewusst keinen generischen Mechanismus (siehe @2) -- @3 selbst nimmt bei Bedarf per \pkg*{xtemplate}\ direkten Zugriff (@4/@5) entgegen.}{@1 changes an already-declared style (only the given keys); for custom, entirely new command bindings with more than one argument, \osgstyler{} deliberately offers no generic mechanism (see @2) -- @3 itself accepts direct \pkg*{xtemplate} access (@4/@5) if ever needed.} {\cs{EditOsgInlineStyle}}{\ref{sec:blocks:create}}{osgstyler-inline} {\cs*{DeclareInstance}}{\cs*{UseInstance}} \subsection{\ldeen{Bausteine: Blöcke}{Blocks}} \label{sec:blocks:create} \ldeen*{Für "`Erstellen"' gibt es bei Blöcken drei unabhängige Bausteine, die sich beliebig kombinieren lassen: neue \emph{Instanzen} (Optik), \emph{Traits} (wiederverwendbare Optik-Fragmente) und die Verbindung von Optik mit Inhalt -- entweder durch \emph{Umhüllen} (Fall~A) einer bestehenden Umgebung oder durch \emph{Neuerzeugen} (Fall~B).}{For "`creating"', there are three independent building blocks for blocks that can be combined freely: new \emph{instances} (appearance), \emph{traits} (reusable appearance fragments), and connecting appearance with content -- either by \emph{wrapping} (case~A) an existing environment or by \emph{creating anew} (case~B).} \subsubsection{\ldeen{Instanzen}{Instances}} \label{sec:blocks:instances} \ldeen*{@1 deklariert eine neue, benannte Optik; @2 ändert eine bereits bestehende (nur die angegebenen Schlüssel).}{@1 declares a new, named appearance; @2 changes an already-existing one (only the given keys).}{\cs{DeclareOsgBlockInstance}}{\cs{StyleOsgBlockInstance}} \begin{example}[code-only] \DeclareOsgBlockInstance{mynote}{ rule-thickness = .6pt, rule-color = accent, padding = padding/normal } \end{example} \subsubsection{\ldeen{Traits}{Traits}} \label{sec:blocks:traits} \ldeen*{Instanzen sind bei \pkg*{xtemplate} ohnehin nur gespeichertes Keyval-Material -- mehrere Schlüssel lassen sich also schon durch simples Verketten kombinieren, ohne dass es dafür je eine eigene Instanz bräuchte. @1 gibt diesem Verketten nur einen Namen: der Schlüssel @2 (in @3/@4) schlägt benannte, mit @1 deklarierte Fragmente nach und reiht sie vor die übrigen, unverändert durchgereichten Schlüssel ein.}{Instances are just stored keyval material in \pkg*{xtemplate} to begin with -- several keys can therefore already be combined by simple concatenation, without ever needing a dedicated instance for that. @1 merely gives this concatenation a name: the key @2 (in @3/@4) looks up named fragments declared via @1 and prepends them to the remaining keys, which pass through unchanged.} {\cs{DeclareOsgBlockTrait}}{\code{traits}} {\cs{DeclareOsgBlockInstance}}{\cs{StyleOsgBlockInstance}} \begin{example}[code-only] \DeclareOsgBlockTrait{rounded}{corner-radius = 6pt} \DeclareOsgBlockInstance{mynote}{ traits = {rounded}, rule-thickness = .6pt } \end{example} \begin{warnbox}{\ldeen{Für Trait-Autor*innen}{For Trait Authors}} \ldeen*{Ein @1-Wert durchläuft auf dem Weg von der Deklaration bis zur fertigen Instanz mehrere vollständige \pkg*{expl3}-Expansionsrunden. Befehle mit eigenem Lookahead auf das nächste Zeichen -- @2s optionales Anhebungsargument, @3, @4 -- sowie ein nackter @5 \emph{innerhalb} eines Schlüsselwerts überstehen das nicht zuverlässig und können zu kryptischen Fehlern oder sogar Endlosschleifen führen, sobald @6 oder @7 mehr als ein einzelnes, einfaches Token enthalten. Enthält ein Trait mehr als reine Layout-Schlüssel -- zeichnet er also selbst etwas --, sollte die komplette Zeichenlogik stattdessen in einer mit @8 definierten ("`protected"') Funktion stecken, die als \emph{ein einziges} Token durch alle Runden unangetastet durchgereicht und erst beim tatsächlichen Satz ausgeführt wird -- innerhalb dieser Funktion sind @2, @3, @4 und @5 wieder ganz normal verwendbar. Der eingebaute Trait \code{osl-modern-rules} in \code{osgstyler.dtx} folgt genau diesem Muster und ist ein Vorbild für eigene Traits mit Zeichenlogik.}{A @1 value passes through several full \pkg*{expl3} expansion rounds on its way from declaration to the finished instance. Commands with their own lookahead for the next character -- @2's optional raise argument, @3, @4 -- as well as a bare @5 \emph{inside} a key's value do not reliably survive that and can cause cryptic errors or even infinite loops as soon as @6 or @7 contain more than a single, simple token. If a trait contains more than plain layout keys -- i.e.\ draws something itself -- the complete drawing logic should instead live in a ("`protected"') function defined with @8, which is passed through all rounds unaltered as \emph{a single token} and is only executed at actual typesetting time -- inside that function, @2, @3, @4, and @5 are perfectly usable again. The built-in trait \code{osl-modern-rules} in \code{osgstyler.dtx} follows exactly this pattern and is a template for custom traits with drawing logic.} {\code{traits}}{\cs*{rule}}{\cs*{vspace}}{\cs*{newline}}{\cs*{par}} {\code{before}}{\code{after}}{\cs*{cs\_new\_protected:Npn}} \end{warnbox} \subsubsection{\ldeen{Fall A: Bestehende Umgebung umhüllen}{Case A: Wrapping an Existing Environment}} \label{sec:blocks:bind} \ldeen*{@1 verallgemeinert das Muster, mit dem \pkg*{lttheme} \env{block} umhüllt, für Environments \emph{ohne} Overlay-Syntax: eine einheitliche Argumentsignatur (ein optionaler Name vor einem Textkörper), die auch auf \pkg*{amsthm}-generierte \env{theorem}-Umgebungen (via \cs*{newtheorem}) zutrifft. Das optionale Argument wird unverändert an das umhüllte Original weitergereicht, nie selbst interpretiert -- die eigene Nummerierung und das Kopf-Rendering von @2 bleiben also vollständig erhalten, @1 fügt nur Optik \emph{um} deren fertige Ausgabe herum.}{@1 generalizes the pattern \pkg*{lttheme} uses to wrap \env{block} for environments \emph{without} overlay syntax: a uniform argument signature (an optional name before a text body) that also applies to \pkg*{amsthm}-generated \env{theorem} environments (via \cs*{newtheorem}). The optional argument is forwarded to the wrapped original unchanged, never interpreted here itself -- @2's own numbering and heading rendering are therefore fully preserved, @1 only adds appearance \emph{around} its finished output.} {\cs{BindOsgBlockEnvironment}}{\pkg*{amsthm}} \begin{example}[code-only] \usepackage{amsthm} \newtheorem{theorem}{Theorem} \DeclareOsgBlockInstance{theorem}{ rule-thickness = .4pt, padding = .5em } \BindOsgBlockEnvironment{theorem}{theorem} \begin{document} \begin{theorem}[Fermat] Ein von \osgstyler{} gerahmtes amsthm-Theorem, mit voll funktionsfähiger amsthm-Nummerierung und optionalem Namen. \end{theorem} \end{document} \end{example} \ldeen*{Da @1 keinen Kasten braucht (siehe @2), passt hier auch der Trait @3 statt eines Rahmens -- exakt das Rezept, das @4 auf seine eigenen Block-Instanzen anwendet.}{Since @1 needs no box (see @2), the trait @3 also fits here instead of a border -- exactly the recipe @4 applies to its own block instances.} {\pkg*{amsthm}}{\ref{sec:blocks:presets}}{\code{osl-modern-rules}} {\cs{UseOsgPreset}} \begin{example}[code-only] \StyleOsgBlockInstance{theorem}{traits = {osl-modern-rules}} \end{example} \subsubsection{\ldeen{Fall B: Neue Umgebung erzeugen}{Case B: Creating a New Environment}} \label{sec:blocks:new} \ldeen*{@1 deckt den Fall ab, in dem \emph{keine} bereits bestehende Umgebung umhüllt wird. @1 selbst bleibt sprachneutral: das Label liefert der Aufruf als fertigen Text, \osgstyler{} übersetzt nicht.}{@1 covers the case where \emph{no} pre-existing environment is being wrapped. @1 itself stays language-neutral: the label is supplied by the call as ready-made text, \osgstyler{} does not translate.} {\cs{NewOsgStyledEnvironment}} \ldeen*{Ohne optionales @1-Argument (Vorgabe @2) verhält sich das erzeugte Environment wie @3: ein optionaler Name in eckigen Klammern, automatisch zusammengesetzter Titel aus Label, Nummer und optionalem Namen.}{Without the optional @1 argument (default @2), the generated environment behaves like @3: an optional name in square brackets, an automatically composed title from label, number, and optional name.} {\meta{nargs}}{\code{0}}{\cs*{newtheorem}} \begin{example}[code-only] \DeclareOsgNumbering{mynote}{counter = mynote} \NewOsgStyledEnvironment{mynote}{mynote}{mynote}{Notiz} \begin{document} \begin{mynote}[Wichtig] Ergibt "`Notiz~1 (Wichtig)"' als Titel. \end{mynote} \end{document} \end{example} \ldeen*{Mit @1 zwischen \code{1} und \code{3} verlangt das Environment stattdessen @1 Pflichtargumente; die Titelvorlage wird dann pro Verwendung mit den echten Argumenten (@2, \ldots) gefüllt -- eine automatische Label+Zahl-Komposition entfällt hier bewusst. Genau das reproduziert Beamers eigene @3-Signatur für die Langform:}{With @1 between \code{1} and \code{3}, the environment instead requires @1 mandatory arguments; the title template is then filled with the real arguments (@2, \ldots) on each use -- automatic label+number composition is deliberately absent here. This is exactly what reproduces beamer's own @3 signature for the long form:} {\meta{nargs}}{\code{\#1}}{\env{block}} \begin{example}[code-only] \NewOsgStyledEnvironment{block}{block}{none}[1]{#1} % \begin{block}{Titel} ... \end{block} \end{example} \ldeen{Wer Nummerierung mit freiem Titel kombinieren will, bezieht \cs{OsgNumberingValue} (Abschnitt~\ref{sec:numbering}) selbst in die Titelvorlage ein.}{Anyone who wants to combine numbering with a free title includes \cs{OsgNumberingValue} (Section~\ref{sec:numbering}) in the title template themselves.} \ldeen*{Ein zweiter, schlankerer Baustein für den umgekehrten Fall -- ein semantisches Zitat trägt keine Titelzeile vor dem Inhalt, sondern höchstens eine Quellenangabe danach --: @1.}{A second, leaner building block for the reverse case -- a semantic quotation carries no title line before its content, only an optional attribution after it --: @1.} {\cs{NewOsgAttributedEnvironment}} \begin{example}[code-only] \NewOsgAttributedEnvironment{aquote}{quote} \begin{document} \begin{aquote}[Antoine de Saint-Exupéry] Man sieht nur mit dem Herzen gut. \end{aquote} \end{document} \end{example} \subsection{\ldeen{Nummerierung}{Numbering}} \label{sec:numbering:create} \ldeen*{@1 registriert einen neuen \LaTeX-Zähler und legt dessen Formatierung fest -- in einem Aufruf, sofort bei der Deklaration (nicht erst bei erster Nutzung), da ein zweiter \cs*{newcounter}-Aufruf für denselben Namen sonst fehlschlüge.}{@1 registers a new \LaTeX{} counter and fixes its formatting -- in one call, immediately upon declaration (not only on first use), since a second \cs*{newcounter} call for the same name would otherwise fail.}{\cs{DeclareOsgNumbering}} \begin{example}[code-only] \DeclareOsgNumbering{mynote}{ counter = mynote, format = \arabic{mynote} } \end{example} \ldeen{Mit \code{within}\meta{=Zähler} wird der neue Zähler bei jedem Schritt von \meta{Zähler} zurückgesetzt (wie \cs*{newcounter}s eigenes \oarg{within}-Argument) -- z.\,B.\ für kapitelweise Nummerierung.} {Using \code{within}\meta{=counter}, the new counter is reset every time \meta{counter} steps (like \cs*{newcounter}'s own \oarg{within} argument) -- e.\,g.\ for per-chapter numbering.} \begin{example}[code-only] \DeclareOsgNumbering{mynote}{ counter = mynote, within = chapter, format = \thechapter.\arabic{mynote} } \end{example} \subsection{\ldeen{Integration mit anderen Paketen}{Integration with Other Packages}} \label{sec:integration} \ldeen{Diese Integrationen laufen weitgehend automatisch ab -- sie betreffen nicht die "`Nutzen/Erstellen"'-Unterscheidung selbst, sondern beschreiben, wie \osgstyler{} sich in eine größere Werkzeugkette einfügt.}{These integrations run largely automatically -- they do not concern the using/creating distinction itself, but describe how \osgstyler{} fits into a larger toolchain.} \subsubsection{osglecture-modes} \label{sec:integration:modes} \ldeen*{Ist @1 geladen -- in beliebiger Reihenfolge zu \osgstyler{} --, akzeptieren @2 sowie \emph{jeder} über @3 gebundene Befehl zusätzlich ein portables Modusargument; über \cs{NewOsgStyledEnvironment} oder @4 erzeugte Umgebungen erhalten zusätzlich eine führende \cs*{d<>}-Spezifikation. Ein inaktiver Modus unterdrückt die Ausgabe vollständig, \emph{einschließlich} eines etwaigen Nummerierungsschritts -- ausgeblendeter Inhalt soll keine Lücke in der Nummerierung hinterlassen.}{If @1 is loaded -- in either order relative to \osgstyler{} --, @2 as well as \emph{every} command bound via @3 additionally accepts a portable mode expression; environments created via \cs{NewOsgStyledEnvironment} or @4 additionally get a leading \cs*{d<>} specification. An inactive mode suppresses output entirely, \emph{including} any numbering step -- hidden content should not leave a gap in the numbering.} {\pkg{osglecture-modes}} {\cs{UseOsgInlineStyle}/\allowbreak\cs{UseOsgColorPalette}/\allowbreak% \cs{UseOsgFontPalette}/\allowbreak\cs{UseOsgMetricPalette}/\allowbreak% \cs{UseOsgListPalette}/\allowbreak\cs{UseOsgPreset}} {\cs{BindOsgInlineCommand}}{\cs{BindOsgBlockEnvironment}} \begin{example}[code-only] \emph{nur im Präsentationsmodus} \UseOsgColorPalette{osl-standard} \begin{theorem}[Fermat] nur in Langform/Druckmodus \end{theorem} \end{example} \ldeen{Die Deklarationsbefehle (\cs{DeclareOsgColorPalette} und Geschwister) bleiben bewusst modusunabhängig -- eine Palette wird einmalig definiert, unabhängig davon, in welchen Moden sie später ausgewählt wird.}{The declaration commands (\cs{DeclareOsgColorPalette} and siblings) deliberately remain mode-independent -- a palette is defined once, regardless of which modes later select it.} \subsubsection{\pkg*{lttheme}} \label{sec:lttheme} \ldeen*{\osgstyler{} hängt bewusst nicht von @1 ab (Abschnitt~@2). Wird jedoch @3 geladen, synchronisiert sich \osgstyler{} reaktiv: eine Farbpalette namens \code{lttheme} wird aus den gerade aktiven @1-Farben erzeugt und aktiviert, sooft @1s eigene Theme-/Farbbefehle aufgerufen werden. Dies geschieht vollständig automatisch -- ein Dokument, das @3 und \osgstyler{} gemeinsam nutzt, muss dafür nichts weiter tun.}{\osgstyler{} deliberately does not depend on @1 (Section~@2). However, if @3 is loaded, \osgstyler{} synchronizes reactively: a color palette named \code{lttheme} is created from the currently active @1 colors and activated whenever @1's own theme/color commands are called. This happens fully automatically -- a document using @3 together with \osgstyler{} does not need to do anything for this.}{\pkg*{lttheme}}{\ref{sec:alternatives}} {\pkg{ltxtalk-theme}} \ldeen*{Für @1-interne Zwecke: ein Paket, das @2 vor dem eigenen @3 definiert (@1 selbst tut dies), zeigt damit an, dass es seine Themefarben bereits selbst über \cs{DeclareOsgColorPalette}/\cs{UseOsgColorPalette} einspeist -- die reaktive Integration hier erkennt sich dann als überflüssig und tut nichts.}{For @1-internal purposes: a package that defines @2 before its own @3 (@1 itself does this) thereby indicates that it already feeds its theme colors in itself via \cs{DeclareOsgColorPalette}/\cs{UseOsgColorPalette} -- the reactive integration here then recognizes itself as redundant and does nothing.}{\pkg*{lttheme}}{\cs{LtxtalkUsesOsgstylerNatively}} {\cs*{RequirePackage}\Marg{osgstyler}} \subsubsection{\ldeen{Lua-Referenzbackend}{Lua Reference Backend}} \label{sec:lua} \ldeen*{@1 lädt \osgstyler{} zusammen mit @2 und @3, installiert sich selbst als Dekorationsprozessor (Abschnitt~\ref{sec:decorations:processors}) und schließt damit die Renderpipeline für Dekorationen praktisch, jenseits der von \osgstyler{} selbst schon gerenderten @4/@5 (siehe Abschnitt~\ref{sec:decorations:status}).}{@1 loads \osgstyler{} together with @2 and @3, installs itself as the decoration processor (Section~\ref{sec:decorations:processors}), and thereby closes the rendering pipeline for decorations for practical purposes, beyond the @4/@5 that \osgstyler{} itself already renders (see Section~\ref{sec:decorations:status}).} {\pkg{osgstyler-lua}}{\pkg{luacolor}}{\pkg{lua-ul}}{\code{enclosure}} {\code{affix}} \begin{example}[code-only] \usepackage{osgstyler-lua} \end{example} \ldeen*{Verlangt @1 (bricht sonst mit einer klaren Fehlermeldung ab). Rendert zusätzlich @2 an \code{below}/\code{above}/\code{through} und @3 über eigene, zeilenbruchfeste @4-Leader. @5 zeichnet abgerundete und gemusterte Inline-Rahmen.}{Requires @1 (otherwise aborts with a clear error message). Additionally renders @2 at \code{below}/\code{above}/\code{through} and @3 through custom line-break-safe @4 leaders. @5 draws rounded and patterned inline frames.}{\LuaTeX{}}{\code{line}}{\code{background}} {\pkg*{lua-ul}}{\pkg*{pgfcore}} \ldeen{Das expandierbare \cs{OsgStylerLuaBackendActive} liefert stets den Text \code{true} und dient vor allem als Existenztest (\cs*{cs\_if\_exist:NTF}), um im Theme-Code gezielt das Lua-Backend -- nicht nur irgendeinen Prozessor -- zu erkennen.}{The expandable \cs{OsgStylerLuaBackendActive} always returns the text \code{true} and serves primarily as an existence test (\cs*{cs\_if\_exist:NTF}) for theme code to detect the Lua backend specifically -- not just any processor.} \paragraph{\ldeen{Referenz}{Reference}} \begin{commands} \expandable\command{OsgStylerLuaBackendActive} \ldeen{Siehe oben.}{See above.} \end{commands} \section{\ldeen{Zusammenfassende Referenz}{Summary Reference}} \label{sec:reference} \ldeen{Die folgenden, thematisch gegliederten Tabellen fassen die öffentliche Schnittstelle zusammen. Ausführliche Erläuterungen und Beispiele stehen in den jeweils verlinkten Anwender- und Entwicklerabschnitten.}{The following tables summarize the public interface, grouped by topic. Detailed explanations and examples are in the linked user and developer sections.} \subsection{\ldeen{Farbpaletten}{Color Palettes}} \label{sec:colors:ref} \begin{commands} \command{DeclareOsgColorPalette}[\marg{name}\marg{keys}] \ldeen{Deklariert eine Farbpalette, siehe Abschnitt~\ref{sec:colors:create}. }{Declares a color palette, see Section~\ref{sec:colors:create}.} \command{UseOsgColorPalette}[\marg{name}] \ldeen{Aktiviert eine deklarierte Palette.}{Activates a declared palette.} \expandable\command{OsgColorName}[\marg{slot}] \ldeen{Expandierbar; liefert den \pkg*{xcolor}-Namen der aktiven Palette für \meta{slot} (\code{osgstyler-}\meta{slot}).}{Expandable; returns the \pkg*{xcolor} name of the active palette for \meta{slot} (\code{osgstyler-}\meta{slot}).} \expandable\command{OsgActiveColorPalette} \ldeen{Expandierbar; Name der aktiven Farbpalette.}{Expandable; name of the active color palette.} \expandable\command{OsgColorPaletteValue}[\marg{palette}\marg{slot}\marg{property}] \ldeen{Expandierbar; liefert einen rohen, gespeicherten Wert einer -- auch inaktiven -- Palette, ohne Alias-Auflösung. \meta{property} ist \code{kind} (\code{resource} oder \code{alias}), sowie je nach Art \code{model}/\code{value} oder \code{alias}.}{Expandable; returns a raw stored value of a -- possibly inactive -- palette, without alias resolution. \meta{property} is \code{kind} (\code{resource} or \code{alias}), plus, depending on the kind, \code{model}/\code{value} or \code{alias}.} \command{OsgColorPaletteResolvedSlotTo}[\marg{palette}\marg{slot}\marg{tl-var}] \ldeen{Löst Alias-Ketten und Rückfälle für \meta{slot} in \meta{palette} auf und speichert den resultierenden Ressourcen-Slotnamen (nicht die Farbe selbst) in der \pkg*{expl3}-Tokenlistenvariable \meta{tl-var}.} {Resolves alias chains and fallbacks for \meta{slot} in \meta{palette} and stores the resulting resource slot name (not the color itself) in the \pkg*{expl3} token-list variable \meta{tl-var}.} \end{commands} \begin{options} \keyval{model}{\ldeen{Farbmodell}{color model}} \ldeen{Jedes von \pkg*{xcolor} unterstützte Modell, z.\,B. \code{HTML}, \code{RGB}, \code{cmyk}, \code{gray}.}{Any model supported by \pkg*{xcolor}, e.g.\ \code{HTML}, \code{RGB}, \code{cmyk}, \code{gray}.} \keyval{value}{\ldeen{Modellwert}{model value}} \ldeen{Der zum Modell passende Wert, z.\,B. \code{2E4172} für \code{HTML}.}{The value matching the model, e.g.\ \code{2E4172} for \code{HTML}.} \keyval{alias}{\ldeen{anderer Slot}{other slot}} \ldeen{Übernimmt die Farbe eines anderen Slots derselben Palette.} {Adopts the color of another slot in the same palette.} \end{options} \ldeen{Die portablen Farbslots sind}{The portable color slots are} \code{primary}, \code{secondary}, \code{accent}, \code{text}, \code{background}, \code{structure}, \code{alert}, \code{example}. \subsection{\ldeen{Farbserien}{Color Series}} \label{sec:color-series:ref} \ldeen{Farbserien sind geordnete Folgen ohne semantische Slotnamen. Sie eignen sich etwa für Diagrammreihen oder Annotationskategorien und können beliebig viele Einträge enthalten. Jeder Eintrag verwendet dieselbe \code{model}/\code{value}-Syntax wie eine Palettenressource.} {Color series are ordered sequences without semantic slot names. They suit chart data or annotation categories and may contain any number of entries. Every entry uses the same \code{model}/\code{value} syntax as a palette resource.} \begin{commands} \command{DeclareOsgColorSeries}[\marg{name}\marg{items}] \ldeen{Deklariert oder ersetzt eine Farbserie.}{Declares or replaces a color series.} \command{GenerateOsgColorSeries}[\marg{name}\oarg{keys}] \ldeen{Erzeugt unter Lua\LaTeX{} eine qualitative Farbserie aus Farben der aktiven Palette.}{Under Lua\LaTeX{}, generates a qualitative color series from colors of the active palette.} \expandable\command{OsgColorSeriesCount}[\marg{name}] \ldeen{Liefert die Anzahl der Einträge.}{Returns the number of entries.} \expandable\command{OsgColorSeriesColorName}[\marg{name}\marg{index}] \ldeen{Liefert den stabilen \pkg*{xcolor}-Namen des Eintrags mit dem einsbasierten \meta{index}.}{Returns the stable \pkg*{xcolor} name of the entry at the one-based \meta{index}.} \end{commands} \begin{example}[code-only] \DeclareOsgColorSeries{categories}{ {model=HTML, value=1B9E77}, {model=HTML, value=D95F02}, {model=HTML, value=7570B3} } \textcolor{\OsgColorSeriesColorName{categories}{2}}{Second category} \end{example} \begin{example}[code-only] \GenerateOsgColorSeries{categories}[count=8] \end{example} \begin{options} \keyval{count}{integer}[8] \ldeen{Anzahl der Farben (2--32).}{Number of colors (2--32).} \keyval{primary}{color}[\cs{OsgColorName}\{primary\}] \ldeen{Erste Ankerfarbe.}{First anchor color.} \keyval{accent}{color}[\cs{OsgColorName}\{accent\}] \ldeen{Zweite Ankerfarbe, sofern wahrnehmbar verschieden.} {Second anchor color, if perceptibly different.} \keyval{background}{color}[\cs{OsgColorName}\{background\}] \ldeen{Hintergrund für die Kontrastprüfung.} {Background used for the contrast check.} \end{options} \ldeen{Weitere Kandidaten werden in OKLCH verteilt, in den RGB-Farbraum eingepasst, gegen den Hintergrund geprüft und auf möglichst großen wahrnehmbaren Abstand ausgewählt. Die Farboptionen akzeptieren beliebige \pkg*{xcolor}-Farbnamen.}{Further candidates are distributed in OKLCH, mapped into the RGB gamut, checked against the background, and selected for large perceptual separation. The color options accept arbitrary \pkg*{xcolor} color names.} \ldeen{Farbserien werden nicht aktiviert und unterstützen keine Aliase; ihre Einträge sind ausschließlich durch ihre Position bestimmt.} {Color series are not activated and do not support aliases; their entries are identified solely by position.} \subsection{\ldeen{Schriftpaletten}{Font Palettes}} \label{sec:fonts:ref} \begin{commands} \command{DeclareOsgFontPalette}[\marg{name}\marg{keys}] \ldeen{Deklariert eine Schriftpalette, siehe Abschnitt~\ref{sec:fonts:create}. }{Declares a font palette, see Section~\ref{sec:fonts:create}.} \command{UseOsgFontPalette}[\marg{name}] \ldeen{Aktiviert eine deklarierte Palette.}{Activates a declared palette.} \command{UseOsgFont}[\marg{slot}] \ldeen{Wählt die Schrift von \meta{slot} in der aktiven Palette aus (\cs*{fontfamily}\ldots\cs*{selectfont}); \meta{slot} darf nicht \code{math} sein.}{Selects the font of \meta{slot} in the active palette (\cs*{fontfamily}\ldots\cs*{selectfont}); \meta{slot} must not be \code{math}.} \expandable\command{OsgActiveFontPalette} \ldeen{Expandierbar; Name der aktiven Schriftpalette.}{Expandable; name of the active font palette.} \expandable\command{OsgFontPaletteValue}[\marg{palette}\marg{slot}\marg{property}] \ldeen{Expandierbar; roher, gespeicherter Wert (\code{kind}, \code{font}/\code{features} oder \code{alias}, \code{description}) -- ohne Alias-Auflösung.}{Expandable; raw stored value (\code{kind}, \code{font}/\code{features} or \code{alias}, \code{description}) -- without alias resolution.} \command{OsgFontPaletteResolvedSlotTo}[\marg{palette}\marg{slot}\marg{tl-var}] \ldeen{Löst Alias-Ketten und Rückfälle auf, speichert den Ressourcen-Slotnamen in \meta{tl-var}.}{Resolves alias chains and fallbacks, stores the resource slot name in \meta{tl-var}.} \command{OsgFontPaletteAvailableTF}[\marg{true}\marg{false}] \ldeen{Siehe Abschnitt~\ref{sec:fonts:colors-only}.}{See Section~\ref{sec:fonts:colors-only}.} \end{commands} \begin{options} \keyval{font}{\ldeen{Fontname}{font name}} \ldeen{Ein \pkg*{fontspec}-Fontname.}{A \pkg*{fontspec} font name.} \keyval{features}{\pkg*{fontspec} \ldeen{Optionen}{options}} \ldeen{Werden unverändert an \pkg*{fontspec} weitergereicht.}{Passed through to \pkg*{fontspec} unchanged.} \keyval{alias}{\ldeen{anderer Slot}{other slot}} \ldeen{Übernimmt die Schrift eines anderen Slots derselben Palette.} {Adopts the font of another slot in the same palette.} \keyval{description}{\ldeen{Text}{text}} \ldeen{Rein informativ, für Werkzeuge/Themes; wird von \osgstyler{} selbst nicht ausgewertet.}{Purely informational, for tools/themes; not evaluated by \osgstyler{} itself.} \end{options} \ldeen{Die portablen Fontslots sind}{The portable font slots are} \code{main}, \code{companion}, \code{display}, \code{accent}, \code{mono}, \code{math}, \code{symbol}. \begin{options} \opt{colors-only} \ldeen{Paketoption (kein Templateschlüssel); siehe Abschnitt~\ref{sec:fonts:colors-only}.}{Package option (not a template key); see Section~\ref{sec:fonts:colors-only}.} \end{options} \subsection{\ldeen{Metrikpaletten}{Metric Palettes}} \label{sec:metrics:ref} \begin{commands} \command{DeclareOsgMetricPalette}[\marg{name}\marg{keys}] \ldeen{Deklariert eine Metrikpalette, siehe Abschnitt~\ref{sec:metrics:create}.}{Declares a metric palette, see Section~\ref{sec:metrics:create}.} \command{UseOsgMetricPalette}[\marg{name}] \ldeen{Aktiviert eine deklarierte Palette.}{Activates a declared palette.} \expandable\command{OsgMetric}[\marg{category}\marg{slot}] \ldeen{Expandierbar; aktueller Wert von \meta{slot} in \meta{category} der aktiven Palette.}{Expandable; current value of \meta{slot} in \meta{category} of the active palette.} \expandable\command{OsgMetricPaletteValue}[\marg{palette}\marg{category}\marg{slot}] \ldeen{Expandierbar; Wert einer -- auch inaktiven -- Palette.} {Expandable; value of a -- possibly inactive -- palette.} \expandable\command{OsgActiveMetricPalette} \ldeen{Expandierbar; Name der aktiven Metrikpalette.}{Expandable; name of the active metric palette.} \end{commands} \begin{center} \begin{tabular}{lll} \textbf{\ldeen{Kategorie}{Category}} & \textbf{\ldeen{Slots}{Slots}} & \textbf{\ldeen{Vorgabe}{Default}}\\\hline \code{spacing} & \code{none}\ldots\code{xl} & $0$, $.25$, $.5$, $1$, $1.5$, $2\times$\code{\textbackslash baselineskip}\\ \code{padding} & \code{none}\ldots\code{roomy} & $0$, $.35$\code{em}, $.65$\code{em}, $1$\code{em}\\ \code{rule} & \code{hairline}\ldots\code{thick} & $.2$\code{pt}, $.4$\code{pt}, $.7$\code{pt}, $1.2$\code{pt}\\ \code{radius} & \code{square}\ldots\code{round} & $0$, $2$\code{pt}, $4$\code{pt}, $6$\code{pt}\\\hline \end{tabular} \end{center} \ldeen*{Die Slots je Kategorie sind im Detail: @1 (\code{spacing}); @2 (\code{padding}); @3 (\code{rule}); @4 (\code{radius}).}{The slots per category, in detail: @1 (\code{spacing}); @2 (\code{padding}); @3 (\code{rule}); @4 (\code{radius}).} {\code{none}/\code{xs}/\code{sm}/\code{md}/\code{lg}/\code{xl}} {\code{none}/\code{compact}/\code{normal}/\code{roomy}} {\code{hairline}/\code{thin}/\code{normal}/\code{thick}} {\code{square}/\code{small}/\code{medium}/\code{round}} \subsection{\ldeen{Listenpaletten}{List Palettes}} \label{sec:lists:ref} \begin{commands} \command{DeclareOsgListPalette}[\marg{name}\marg{keys}] \ldeen{Deklariert eine Listenpalette, siehe Abschnitt~\ref{sec:lists:create}.}{Declares a list palette, see Section~\ref{sec:lists:create}.} \command{UseOsgListPalette}[\marg{name}] \ldeen{Aktiviert eine deklarierte Palette \emph{sofort} (siehe oben).}{Activates a declared palette \emph{immediately} (see above).} \end{commands} \begin{options} \keyval{itemize}{\marg{\meta{level}=\marg{\ldeen{enumitem-Optionen} {enumitem options}},\ldots}} \ldeen{Optionen je Ebene für \env{itemize}.}{Options per level for \env{itemize}.} \keyval{enumerate}{\marg{\meta{level}=\marg{\ldeen{enumitem-Optionen} {enumitem options}},\ldots}} \ldeen{Optionen je Ebene für \env{enumerate}.}{Options per level for \env{enumerate}.} \keyval{description}{\marg{\meta{level}=\marg{\ldeen{enumitem-Optionen} {enumitem options}},\ldots}} \ldeen{Optionen je Ebene für \env{description}.}{Options per level for \env{description}.} \end{options} \ldeen{Gültige Ebenen sind \code{1} bis \code{4}.}{Valid levels are \code{1} through \code{4}.} \subsection{\ldeen{Dekorationen}{Decorations}} \label{sec:decorations:ref} \begin{commands} \command{DeclareOsgDecoration}[\marg{name}\marg{keys}] \ldeen{Deklariert eine Dekoration, siehe Abschnitt~\ref{sec:decorations:create}.}{Declares a decoration, see Section~\ref{sec:decorations:create}.} \expandable\command{OsgDecorationValue}[\marg{name}\marg{family}\marg{property}] \ldeen{Expandierbar; Eigenschaft der \code{default}-Instanz.} {Expandable; property of the \code{default} instance.} \expandable\command{OsgDecorationInstanceValue}[\marg{name}\marg{family}\marg{instance}\marg{property}] \ldeen{Expandierbar; Eigenschaft einer benannten Instanz.}{Expandable; property of a named instance.} \expandable\command{OsgDecorationInstances}[\marg{name}\marg{family}] \ldeen{Expandierbar; Kommaliste aller Instanznamen einer Familie.} {Expandable; comma list of all instance names of a family.} \expandable\command{OsgDecorationFamilies} \ldeen{Expandierbar; Kommaliste aller fünf Familiennamen.}{Expandable; comma list of all five family names.} \command{OsgDecorationIfHasComponentTF}[\marg{name}\marg{family}\marg{true}\marg{false}] \ldeen{Ob \meta{name} die Familie \meta{family} überhaupt nutzt.} {Whether \meta{name} uses family \meta{family} at all.} \command{OsgDecorationIfHasInstanceTF}[\marg{name}\marg{family}\marg{instance}\marg{true}\marg{false}] \ldeen{Ob eine bestimmte Instanz existiert.}{Whether a specific instance exists.} \command{OsgDecorationProcessorAvailableTF}[\marg{true}\marg{false}] \ldeen{Ob ein rendernder (nicht der transparente Standard-)Prozessor aktiv ist.}{Whether a rendering (not the transparent default) processor is active.} \command{SetOsgDecorationProcessor}[\marg{\ldeen{Funktion}{function}}] \ldeen{Siehe Abschnitt~\ref{sec:decorations:processors}.}{See Section~\ref{sec:decorations:processors}.} \command{SetOsgSemanticProcessor}[\marg{\ldeen{Funktion}{function}}] \ldeen{Siehe Abschnitt~\ref{sec:decorations:processors} und \ref{sec:inline}.}{See Section~\ref{sec:decorations:processors} and \ref{sec:inline}.} \end{commands} \begin{options} \keyval{position}{\ldeen{siehe Text}{see text}} \ldeen{\code{line}/\code{margin}: \code{above}/\code{below} (\code{line} zusätzlich \code{through}); \code{affix}: \code{before}/\code{after}.} {\code{line}/\code{margin}: \code{above}/\code{below} (\code{line} additionally \code{through}); \code{affix}: \code{before}/\code{after}.} \keyval{pattern}{\code{solid}, \code{dashed}, \code{dotted}, \code{wavy}} \ldeen{Das Lua-Backend unterstützt alle vier Werte für Linien und die ersten drei für Rahmen; siehe Abschnitt~\ref{sec:decorations:status}.} {The Lua backend supports all four values for lines and the first three for enclosures; see Section~\ref{sec:decorations:status}.} \keyval{color}{\ldeen{Farb-Slot}{color slot}} \ldeen{Siehe Abschnitt~\ref{sec:colors}.}{See Section~\ref{sec:colors}.} \keyval{thickness}{\ldeen{Länge oder Metrikreferenz}{length or metric reference}} \ldeen{Siehe Abschnitt~\ref{sec:metrics}.}{See Section~\ref{sec:metrics}.} \keyval{offset}{\ldeen{Länge oder Metrikreferenz}{length or metric reference}} \ldeen{Siehe Abschnitt~\ref{sec:metrics}.}{See Section~\ref{sec:metrics}.} \keyval{opacity}{\meta{\ldeen{Zahl}{number}}} \ldeen{Das Lua-Backend rendert Werte von \code{0} bis \code{1}.} {The Lua backend renders values from \code{0} to \code{1}.} \keyval{shape}{\code{text}, \code{line}} \ldeen{Glyphen- bzw. zeilenhohes Hintergrundband im Lua-Backend.} {Glyph-height or line-height background band in the Lua backend.} \keyval{border-color}{\ldeen{Farb-Slot}{color slot}} \ldeen{Siehe Abschnitt~\ref{sec:colors}.}{See Section~\ref{sec:colors}.} \keyval{fill-color}{\ldeen{Farb-Slot}{color slot}} \ldeen{Siehe Abschnitt~\ref{sec:colors}.}{See Section~\ref{sec:colors}.} \keyval{radius}{\ldeen{Länge oder Metrikreferenz}{length or metric reference}} \ldeen{Vom Lua-Backend gerendert; der Standardprozessor bleibt eckig.} {Rendered by the Lua backend; the default processor remains square.} \keyval{padding}{\ldeen{Länge oder Metrikreferenz}{length or metric reference}} \ldeen{Siehe Abschnitt~\ref{sec:metrics}.}{See Section~\ref{sec:metrics}.} \keyval{side}{\ldeen{siehe \pkg*{enumitem}-Dokumentation} {see \pkg*{enumitem} documentation}} \ldeen{Von keinem mitgelieferten Prozessor ausgewertet, siehe Abschnitt~\ref{sec:decorations:status}.}{Not evaluated by any bundled processor, see Section~\ref{sec:decorations:status}.} \keyval{content}{\ldeen{literaler Inhalt}{literal content}} \ldeen{Schließt \code{symbol} aus.}{Mutually exclusive with \code{symbol}.} \keyval{symbol}{\ldeen{Symbolreferenz}{symbol reference}} \ldeen{Schließt \code{content} aus; aktuell löst kein mitgeliefertes Backend Symbolreferenzen auf.}{Mutually exclusive with \code{content}; no bundled backend currently resolves symbol references.} \keyval{font}{\ldeen{Font-Slot}{font slot}} \ldeen{Siehe Abschnitt~\ref{sec:fonts}.}{See Section~\ref{sec:fonts}.} \keyval{gap}{\ldeen{Länge oder Metrikreferenz}{length or metric reference}} \ldeen{Siehe Abschnitt~\ref{sec:metrics}.}{See Section~\ref{sec:metrics}.} \end{options} \subsection{\ldeen{Inline-Styles}{Inline Styles}} \label{sec:inline:ref} \begin{commands} \command{DeclareOsgInlineStyle}[\marg{name}\marg{keys}] \ldeen{Deklariert einen Inline-Style, siehe Abschnitt~\ref{sec:inline:create}.}{Declares an inline style, see Section~\ref{sec:inline:create}.} \command{EditOsgInlineStyle}[\marg{name}\marg{keys}] \ldeen{Ändert einen bereits deklarierten Style.}{Changes an already-declared style.} \command{UseOsgInlineStyle}[\marg{name}\marg{\ldeen{Text}{text}}] \ldeen{Setzt \meta{Text} im Stil \meta{name}.}{Typesets \meta{text} in style \meta{name}.} \command{BindOsgInlineCommand}[\marg{\ldeen{Befehl}{command}}\marg{name}] \ldeen{Bindet \meta{Befehl} (ein Argument) an den Stil \meta{name}, siehe Abschnitt~\ref{sec:inline:use}.}{Binds \meta{command} (one argument) to the style \meta{name}, see Section~\ref{sec:inline:use}.} \command{RestoreOsgInlineCommand}[\marg{\ldeen{Befehl}{command}}] \ldeen{Schaltet \meta{Befehl} auf seinen \code{default}-Plug zurück.} {Switches \meta{command} back to its \code{default} plug.} \command{UseOsgStandardInlineCommands} \ldeen{Bindet \cs{emph}, \cs{textbf}, \cs{uline}, \cs{highlight}, \cs{fbox} an die gleichnamigen Styles.}{Binds \cs{emph}, \cs{textbf}, \cs{uline}, \cs{highlight}, \cs{fbox} to the styles of the same name.} \command{UseOsgDefaultInlineCommands} \ldeen{Schaltet alle bisher gebundenen Befehle gesammelt auf ihren \code{default}-Plug zurück.}{Switches all commands bound so far back to their \code{default} plug, collectively.} \end{commands} \begin{options} \keyval{font}{\ldeen{Font-Slot}{font slot}\code{|none}}\Default{main} \ldeen{Siehe Abschnitt~\ref{sec:fonts}.}{See Section~\ref{sec:fonts}.} \keychoice{weight}{inherit,regular,bold}\Default{inherit} \keychoice{shape}{inherit,upright,italic,slanted,small-caps}\Default{inherit} \keyval{color}{\ldeen{Farb-Slot}{color slot}\code{|none}}\Default{text} \ldeen{Siehe Abschnitt~\ref{sec:colors}.}{See Section~\ref{sec:colors}.} \keyval{decoration}{\ldeen{Dekorationsname}{decoration name}\code{|none}} \Default{none} \ldeen{Siehe Abschnitt~\ref{sec:decorations}.}{See Section~\ref{sec:decorations}.} \keyval{semantics}{\ldeen{Rolle}{role}\code{|none}}\Default{none} \end{options} \ldeen{Vordefinierte Styles: \code{emphasis}, \code{strong}, \code{underline}, \code{highlight}, \code{frame}. Vordefinierte Dekorationen: \code{osgstyler-underline}, \code{osgstyler-highlight}, \code{osgstyler-frame} (Abschnitt~\ref{sec:decorations}).}{Predefined styles: \code{emphasis}, \code{strong}, \code{underline}, \code{highlight}, \code{frame}. Predefined decorations: \code{osgstyler-underline}, \code{osgstyler-highlight}, \code{osgstyler-frame} (Section~\ref{sec:decorations}).} \subsection{\ldeen{Bausteine: Blöcke}{Blocks}} \label{sec:blocks:ref} \begin{commands} \command{DeclareOsgBlockInstance}[\marg{name}\marg{keys}] \ldeen{Siehe Abschnitt~\ref{sec:blocks:instances}.}{See Section~\ref{sec:blocks:instances}.} \command{StyleOsgBlockInstance}[\marg{name}\marg{keys}] \ldeen{Siehe Abschnitt~\ref{sec:blocks:instances}.}{See Section~\ref{sec:blocks:instances}.} \command{DeclareOsgBlockTrait}[\marg{name}\marg{keys}] \ldeen{Siehe Abschnitt~\ref{sec:blocks:traits}.}{See Section~\ref{sec:blocks:traits}.} \command{BindOsgBlockEnvironment}[\marg{\ldeen{Umgebung}{environment}}\marg{name}] \ldeen{Siehe Abschnitt~\ref{sec:blocks:bind}.}{See Section~\ref{sec:blocks:bind}.} \command{NewOsgStyledEnvironment}[\marg{name}\marg{\ldeen{Instanz}{instance}}% \marg{\ldeen{Nummerierung}{numbering}\code{|none}}\oarg{nargs}\marg{\ldeen{Titelvorlage}{title template}}] \ldeen{\meta{nargs} ist \code{0} bis \code{3}, Vorgabe \code{0}. Siehe Abschnitt~\ref{sec:blocks:new}.}{\meta{nargs} is \code{0} to \code{3}, default \code{0}. See Section~\ref{sec:blocks:new}.} \command{NewOsgAttributedEnvironment}[\marg{name}\marg{\ldeen{Instanz}{instance}}] \ldeen{Siehe Abschnitt~\ref{sec:blocks:new}.}{See Section~\ref{sec:blocks:new}.} \command{UseOsgStandardBlockEnvironments} \ldeen{Siehe Abschnitt~\ref{sec:blocks:use}.}{See Section~\ref{sec:blocks:use}.} \command{UseOsgPreset}[\marg{name}] \ldeen{Siehe Abschnitt~\ref{sec:blocks:presets}.}{See Section~\ref{sec:blocks:presets}.} \end{commands} \begin{options} \keyval{background}{\ldeen{Farb-Slot}{color slot}\code{|none}}\Default{none} \ldeen{Siehe Abschnitt~\ref{sec:colors}. \code{none} bedeutet \code{background} (der Farbslot).}{See Section~\ref{sec:colors}. \code{none} means \code{background} (the color slot).} \keyval{rule-color}{\ldeen{Farb-Slot}{color slot}}\Default{structure} \keyval{rule-thickness}{\ldeen{Länge}{length}}\Default{0pt} \ldeen{\code{0pt} zeichnet keinen Rahmen.}{\code{0pt} draws no border.} \keyval{padding}{\ldeen{Länge}{length}}\Default{0pt} \keyval{padding-top, padding-right, padding-bottom, padding-left} {\ldeen{Länge}{length}} \ldeen{Überschreiben den entsprechenden Anteil von \code{padding}.} {Override the corresponding side of \code{padding}.} \keyval{corner-radius}{\ldeen{Länge}{length}}\Default{0pt} \ldeen{Nur im \code{titlebar}-Kastenzweig und nur mit \pkg{pgfcore}, siehe Abschnitt~\ref{sec:blocks:concept}.}{Only in the \code{titlebar} boxed branch and only with \pkg{pgfcore}, see Section~\ref{sec:blocks:concept}.} \keybool{shadow}\Default{false} \ldeen{Wie \code{corner-radius}: nur \code{titlebar} mit \pkg{pgfcore}.}{Like \code{corner-radius}: \code{titlebar} with \pkg{pgfcore} only.} \keyval{shadow-offset}{\ldeen{Länge}{length}}\Default{2pt} \ldeen{Versatz des Schattens nach rechts unten.}{Shadow offset towards the lower right.} \keyval{shadow-color}{\ldeen{Farb-Slot}{color slot}}\Default{structure!30} \ldeen{Farbe der Schattenfläche.}{Colour of the shadow area.} \keyval{before}{\ldeen{Inhalt}{content}}\Default{} \ldeen{Wird vor dem Inhalt gesetzt; siehe die Warnung zu Traits (Abschnitt~\ref{sec:blocks:traits}) bei komplexerem Inhalt.}{Typeset before the content; see the warning on traits (Section~\ref{sec:blocks:traits}) for more complex content.} \keyval{after}{\ldeen{Inhalt}{content}}\Default{} \ldeen{Wie \code{before}, nach dem Inhalt.}{Like \code{before}, after the content.} \keyval{traits}{\marg{name,\ldots}} \ldeen{Nur bei \cs{DeclareOsgBlockInstance}/\cs{StyleOsgBlockInstance}, kein Interface-Schlüssel selbst. Siehe Abschnitt~\ref{sec:blocks:traits}.}{Only for \cs{DeclareOsgBlockInstance}/\cs{StyleOsgBlockInstance}, not an interface key itself. See Section~\ref{sec:blocks:traits}.} \keychoice{layout}{plain,titled}\Default{plain} \ldeen{\code{plain}: Titel (sofern die aufrufende Umgebung einen liefert) dem Body vorangestellt, optionaler Rahmen um alles. \code{titled}: farbiger Kopfstreifen mit dem Titel, Body darunter -- fließend, solange \code{body-background} \code{none} ist, sonst als Kasten mit Unterkante.} {\code{plain}: title (if the calling environment supplies one) prepended to the body, optional frame around all. \code{titled}: a coloured head strip carrying the title, body below -- flowing while \code{body-background} is \code{none}, otherwise a box with a bottom edge.} \keyval{head-background, head-color, head-rule-color}{\ldeen{Farb-Slot}{color slot}} \Default{structure, background, structure} \ldeen{Nur bei \code{layout=titled}: Fläche, Schrift und Unterlinie des Kopfstreifens.}{Only with \code{layout=titled}: fill, text and underline of the head strip.} \keyval{head-font}{\ldeen{Inhalt}{content}}\Default{\cs{normalfont}\cs{bfseries}} \keyval{head-padding, head-rule-thickness, body-padding}{\ldeen{Länge}{length}} \Default{0.35em, 0pt, 0.4em} \keyval{head-padding-top, head-padding-right, head-padding-bottom, head-padding-left, body-padding-top, body-padding-right, body-padding-bottom, body-padding-left}{\ldeen{Länge}{length}} \ldeen{Überschreiben jeweils die entsprechende Seite des zugehörigen Sammelschlüssels.}{Override the corresponding side of the associated shorthand key.} \keyval{body-background}{\ldeen{Farb-Slot}{color slot}\code{|none}}\Default{none} \ldeen{Nur bei \code{layout=titled}; ein anderer Wert als \code{none} schaltet den Kastenzweig ein (\code{rule-thickness}/\code{rule-color} gelten dann für dessen Rand).}{Only with \code{layout=titled}; any value other than \code{none} switches on the boxed branch (\code{rule-thickness}/\code{rule-color} then apply to its border).} \end{options} \ldeen{Immer deklarierte Instanzen: \env{quote}, \env{quotation}, \env{block}, \env{alertblock}, \env{exampleblock}. Presets: \code{osl-standard}, \code{osl-modern}. Traits: \code{osl-modern-rules}, \code{plain}, \code{framed}, \code{filled}, \code{titlebar}, \code{soft}, \code{heading}, \code{card}. Der Test \cs{OsgIfBlockTitledTF}\marg{Name}\marg{ja}\marg{nein} meldet, ob eine Instanz \code{layout=titled} verwendet -- umhüllende Pakete brauchen das, weil ein Kopf-Titel anders zugeführt werden muss.} {Always-declared instances: \env{quote}, \env{quotation}, \env{block}, \env{alertblock}, \env{exampleblock}. Presets: \code{osl-standard}, \code{osl-modern}. Traits: \code{osl-modern-rules}, \code{plain}, \code{framed}, \code{filled}, \code{titlebar}, \code{soft}, \code{heading}, \code{card}. The test \cs{OsgIfBlockTitledTF}\marg{name}\marg{yes}\marg{no} reports whether an instance uses \code{layout=titled} -- wrapping packages need this because a head title has to be fed in differently.} \subsection{\ldeen{Nummerierung}{Numbering}} \label{sec:numbering:ref} \begin{commands} \command{DeclareOsgNumbering}[\marg{name}\marg{keys}] \ldeen{Siehe Abschnitt~\ref{sec:numbering:create}.}{See Section~\ref{sec:numbering:create}.} \command{StepOsgNumbering}[\marg{name}] \ldeen{Siehe Abschnitt~\ref{sec:numbering:use}.}{See Section~\ref{sec:numbering:use}.} \expandable\command{OsgNumberingValue}[\marg{name}] \ldeen{Siehe Abschnitt~\ref{sec:numbering:use}.}{See Section~\ref{sec:numbering:use}.} \expandable\command{OsgNumberingCounter}[\marg{name}] \ldeen{Expandierbar; Name des zugrundeliegenden \LaTeX-Zählers (für Code, der z.\,B.\ \cs*{value} direkt braucht).}{Expandable; name of the underlying \LaTeX{} counter (for code that needs, e.g., \cs*{value} directly).} \end{commands} \begin{options} \keyval{counter}{\ldeen{Zählername}{counter name}} \ldeen{Pflicht; muss ein noch nicht existierender Zählername sein.} {Required; must be a not-yet-existing counter name.} \keyval{within}{\ldeen{übergeordneter Zähler}{parent counter}\code{|none}} \Default{none} \keyval{format}{\ldeen{formatierter Wert}{formatted value}} \ldeen{Typischerweise \cs*{arabic}\marg{Zähler} oder ähnlich; jeder Inhalt, den \cs{OsgNumberingValue} unverändert zurückgeben soll.} {Typically \cs*{arabic}\marg{counter} or similar; any content that \cs{OsgNumberingValue} should return unchanged.} \end{options} \section{FAQ} \label{sec:faq} \begin{itemize} \item \textbf{\ldeen{Ist \osgstyler{} stabil?}{Is \osgstyler{} stable?}} \ldeen*{Nein, noch nicht: \cs{OsgStylerAPIVersion} steht bei Drucklegung dieser Dokumentation bei @1, die öffentliche Schnittstelle also vor der ersten stabilen Version. Namen und Details können sich noch ändern. Bitte melden Sie gefundene Fehler über @2.}{No, not yet: \cs{OsgStylerAPIVersion} stands at @1 as this documentation goes to press, i.e.\ the public interface is still pre-1.0. Names and details may still change. Please report any bugs you find via @2.}{\UseVersionOf{osgstyler.sty}} {\url{https://github.com/werner-matthias/osglecture/issues}} \item \textbf{\ldeen{Wofür steht das "`Osg"' im Namensprefix?} {What does the "`Osg"' in the name prefix stand for?}} \ldeen{Das Paket gehört zum Bundle \pkg{osglecture}, von dort ist das "`Osg"' geerbt.}{The package belongs to the \pkg{osglecture} bundle, from which the "`Osg"' is inherited.} \item \textbf{\ldeen{Brauche ich \pkg*{lttheme} oder \pkg{osglecture-modes}?}{Do I need \pkg*{lttheme} or \pkg{osglecture-modes}?}} \ldeen{Nein. \osgstyler{} ist vollständig unabhängig nutzbar; beide Integrationen (Abschnitt~\ref{sec:integration}) laufen nur an, wenn das jeweils andere Paket ohnehin geladen wird.}{No. \osgstyler{} can be used completely independently; both integrations (Section~\ref{sec:integration}) only engage if the respective other package is loaded anyway.} \item \textbf{\ldeen{Meine Dekoration (Linie/Hintergrund) erscheint nicht.}{My decoration (line/background) does not appear.}} \ldeen*{Der eingebaute Standardprozessor rendert @1 nicht (nur @2/@3), siehe Abschnitt~\ref{sec:decorations:status}. Laden Sie @4 oder installieren Sie einen eigenen Prozessor.}{The built-in default processor does not render @1 (only @2/@3), see Section~\ref{sec:decorations:status}. Load @4 or install a custom processor.}{\code{line}/\code{background}}{\code{enclosure}} {\code{affix}}{\pkg{osgstyler-lua}} \item \textbf{\ldeen{Mein eigener Trait bzw.\ meine eigene Instanz führt zu kryptischen Fehlern oder hängt sich beim Kompilieren auf.}{My custom trait or instance causes cryptic errors or hangs during compilation.}} \ldeen*{Vermutlich enthält ein @1/@2-Wert einen Befehl mit eigenem Lookahead (@3, @4, @5) oder einen nackten @6 -- siehe die Warnung in Abschnitt~\ref{sec:blocks:traits} und wickeln Sie die Zeichenlogik in eine mit @7 definierte Funktion.}{Likely a @1/@2 value contains a command with its own lookahead (@3, @4, @5) or a bare @6 -- see the warning in Section~\ref{sec:blocks:traits} and wrap the drawing logic in a function defined with @7.} {\code{before}}{\code{after}}{\cs*{rule}}{\cs*{vspace}} {\cs*{newline}}{\cs*{par}}{\cs*{cs\_new\_protected:Npn}} \item \textbf{\ldeen{Bricht ein gerahmtes Zitat/Block über eine Seitengrenze um?}{Does a framed quotation/block break across a page boundary?}} \ldeen*{Nein: Der Standard-Renderer setzt den Inhalt bei @1 in eine @2 innerhalb einer @3 -- beides unteilbar. Bei sehr langen gerahmten Blöcken kann das zu ungünstigem Umbruch oder einem Überlaufen der Seite führen. Ein rahmenloser Trait wie \code{osl-modern-rules} (Abschnitt~\ref{sec:blocks:traits}) ist davon nicht betroffen, da dann kein @2 gezeichnet wird.}{No: the default renderer places the content at @1 inside a @2 within a @3 -- both unbreakable. For very long framed blocks, this can lead to awkward breaking or content overflowing the page. A borderless trait such as \code{osl-modern-rules} (Section~\ref{sec:blocks:traits}) is unaffected, since no @2 is drawn in that case.} {\code{rule-thickness}\code{>0pt}}{\cs*{fcolorbox}} {\cs*{minipage}} \item \textbf{\ldeen{Kann ich Beamers eigenes \env{block} an \code{osgstyler-block}-Optik binden?}{Can I bind beamer's own \env{block} to \code{osgstyler-block} appearance?}} \ldeen*{Nicht mit @1: Beamers native @2/@3/@4 haben eine eigene, overlay-fähige Signatur, die nicht dem einfachen \oarg{Name}+Textkörper-Muster entspricht, das @1 voraussetzt. Unter Beamer/\cls*{ltx-talk} lässt \cs{UseOsgStandardBlockEnvironments} @2/@3/@4 deshalb bewusst unangetastet (Abschnitt~\ref{sec:blocks:use}) -- nur in der Langform werden sie neu erzeugt.}{Not with @1: beamer's native @2/@3/@4 have their own overlay-capable signature that does not match the simple \oarg{name}+body pattern @1 assumes. Under beamer/\cls*{ltx-talk}, \cs{UseOsgStandardBlockEnvironments} therefore deliberately leaves @2/@3/@4 untouched (Section~\ref{sec:blocks:use}) -- they are only newly created in the long form.}{\cs{BindOsgBlockEnvironment}}{\env{block}} {\env{alertblock}}{\env{exampleblock}} \item \textbf{\ldeen{Meine Frage erscheint hier nicht.}{My question does not appear here.}} \ldeen*{Sie können sie unter @1 stellen.}{You may ask at @1.} {\url{https://github.com/werner-matthias/osglecture/issues}} \end{itemize} \printindex \end{document} % %<*pkg> % Engine-neutral, unlike fontspec/the Lua decoration backend, so it is % required unconditionally rather than gated behind 'colors-only'. \RequirePackage{xcolor} \RequirePackage{enumitem} \ExplSyntaxOn % \ldeen*{@1 hält osgstyler engine-neutral, wenn nur Farb-/Metrik-/ % Dekorationsverwaltung gebraucht wird: @2 verlangt \LuaTeX{} oder % \XeTeX{} (über @3), was unter @4 kein tragbarer Zwang ist. Deklarieren % von Font-Paletten bleibt auch unter @1 erlaubt (reine Datenverwaltung, % siehe @5); nur das tatsächliche Laden/Auswählen eines Fonts über @6/@7 % verlangt @2 und wird unter @1 stattdessen mit einer klaren % Fehlermeldung quittiert. @8 lässt Themepakete diesen Unterschied % zur Übersetzungszeit abfragen.}{@1 keeps osgstyler engine-neutral when % only color/metric/decoration management is needed: @2 requires % \LuaTeX{} or \XeTeX{} (via @3), which is not a portable requirement % under @4. Declaring font palettes remains available under @1 (pure % data management, see @5); only actually loading/selecting a font via % @6/@7 requires @2 and instead raises a clear error under @1. @8 lets % theme packages query this distinction at typesetting time.} % {\option{colors-only}}{\pkg*{fontspec}}{\pkg*{iftex}} % {\cls*{ltx-talk}}{\cs{DeclareOsgFontPalette}}{\cs{UseOsgFontPalette}} % {\cs{UseOsgFont}}{\cs{OsgFontPaletteAvailableTF}} \bool_new:N \g__osgstyler_colors_only_bool \DeclareKeys { colors-only .bool_gset:N = \g__osgstyler_colors_only_bool, colors-only .initial:n = false, colors-only .usage:n = load, } \ProcessKeyOptions\relax \bool_if:NF \g__osgstyler_colors_only_bool { \RequirePackage{fontspec} } \NewExpandableDocumentCommand \OsgStylerAPIVersion { } { 0.2.0 } \cs_generate_variant:Nn \clist_map_inline:nn { V } \cs_generate_variant:Nn \color_set:nnn { VVV } % Font palettes contain a deliberately small, portable set of relational % slots. A slot either owns a fontspec resource or aliases another slot. % The distinction is explicit so that a font called, for example, "main" % can never be mistaken for an alias. \clist_const:Nn \c__osgstyler_font_slots_clist { main, companion, display, accent, mono, math, symbol } \prop_new:N \g__osgstyler_font_palette_prop \prop_new:N \g__osgstyler_font_family_prop \tl_new:N \g__osgstyler_font_palette_tl \tl_new:N \l__osgstyler_font_name_tl \tl_new:N \l__osgstyler_font_features_tl \tl_new:N \l__osgstyler_font_alias_tl \tl_new:N \l__osgstyler_font_description_tl \tl_new:N \l__osgstyler_font_resolution_kind_tl \tl_new:N \l__osgstyler_font_resolution_target_tl \tl_new:N \l__osgstyler_font_resolution_discard_tl \tl_new:N \l__osgstyler_font_load_name_tl \tl_new:N \l__osgstyler_font_load_features_tl \tl_new:N \l__osgstyler_font_load_family_tl \seq_new:N \l__osgstyler_font_resolution_seq \msg_new:nnnn { osgstyler } { unknown-font-slot } { Unknown~font~palette~slot~'#1'. } { The~portable~slots~are~main,~companion,~display,~accent,~mono,~math,~and~symbol. } \msg_new:nnnn { osgstyler } { incomplete-font-resource } { Font~slot~'#2'~in~palette~'#1'~does~not~specify~a~font~or~an~alias. } { Use~font={...}~for~a~resource~or~alias=...~for~an~alias. } \msg_new:nnnn { osgstyler } { conflicting-font-resource } { Font~slot~'#2'~in~palette~'#1'~specifies~both~a~font~and~an~alias. } { A~slot~must~either~own~a~fontspec~resource~or~alias~another~slot. } \msg_new:nnnn { osgstyler } { missing-main-font } { Font~palette~'#1'~does~not~define~the~required~slot~'main'. } { Add~main={font={...}}~to~the~palette. } \msg_new:nnnn { osgstyler } { unknown-font-palette } { Unknown~font~palette~'#1'. } { Declare~it~with~\token_to_str:N\DeclareOsgFontPalette~before~using~it. } \msg_new:nnnn { osgstyler } { unavailable-font-slot } { Font~slot~'#2'~is~not~available~in~palette~'#1'. } { Define~that~slot~or~one~of~its~standard~fallbacks. } \msg_new:nnnn { osgstyler } { font-alias-cycle } { Font~palette~'#1'~contains~an~alias~cycle~at~slot~'#2'. } { Change~the~aliases~so~that~they~eventually~resolve~to~a~font~resource. } \msg_new:nnnn { osgstyler } { math-font-selection } { Font~slot~'math'~cannot~be~selected~as~a~text~font. } { Query~it~for~a~unicode-math~adapter;~use~another~slot~for~text. } \msg_new:nnnn { osgstyler } { font-management-unavailable } { Font~management~is~not~available~(osgstyler~was~loaded~with~the~ 'colors-only'~option). } { Check~\token_to_str:N\OsgFontPaletteAvailableTF~before~calling~font~ commands,~or~load~osgstyler~without~'colors-only'. } \keys_define:nn { osgstyler / font-resource } { font .tl_set:N = \l__osgstyler_font_name_tl, features .tl_set:N = \l__osgstyler_font_features_tl, alias .tl_set:N = \l__osgstyler_font_alias_tl, description .tl_set:N = \l__osgstyler_font_description_tl, } \cs_new_protected:Npn \__osgstyler_font_resource_store:nnn #1#2#3 { \tl_clear:N \l__osgstyler_font_name_tl \tl_clear:N \l__osgstyler_font_features_tl \tl_clear:N \l__osgstyler_font_alias_tl \tl_clear:N \l__osgstyler_font_description_tl \keys_set:nn { osgstyler / font-resource } {#3} \bool_lazy_and:nnTF { ! \tl_if_empty_p:N \l__osgstyler_font_name_tl } { ! \tl_if_empty_p:N \l__osgstyler_font_alias_tl } { \msg_error:nnnn { osgstyler } { conflicting-font-resource } {#1} {#2} } { \tl_if_empty:NTF \l__osgstyler_font_name_tl { \tl_if_empty:NTF \l__osgstyler_font_alias_tl { \msg_error:nnnn { osgstyler } { incomplete-font-resource } {#1} {#2} } { \clist_if_in:NVTF \c__osgstyler_font_slots_clist \l__osgstyler_font_alias_tl { \prop_gput:Nne \g__osgstyler_font_palette_prop { #1 / #2 / kind } { alias } \prop_gput:NnV \g__osgstyler_font_palette_prop { #1 / #2 / alias } \l__osgstyler_font_alias_tl } { \msg_error:nne { osgstyler } { unknown-font-slot } \l__osgstyler_font_alias_tl } } } { \prop_gput:Nnn \g__osgstyler_font_palette_prop { #1 / #2 / kind } { resource } \prop_gput:NnV \g__osgstyler_font_palette_prop { #1 / #2 / font } \l__osgstyler_font_name_tl \prop_gput:NnV \g__osgstyler_font_palette_prop { #1 / #2 / features } \l__osgstyler_font_features_tl } \tl_if_empty:NF \l__osgstyler_font_description_tl { \prop_gput:NnV \g__osgstyler_font_palette_prop { #1 / #2 / description } \l__osgstyler_font_description_tl } } } \cs_new_protected:Npn \__osgstyler_font_palette_declare:nn #1#2 { \clist_map_inline:Nn \c__osgstyler_font_slots_clist { \clist_map_inline:nn { kind, font, features, alias, description } { \prop_gremove:Nn \g__osgstyler_font_palette_prop { #1 / ##1 / ####1 } } } \prop_gremove:Nn \g__osgstyler_font_palette_prop { #1 / declared } \clist_map_inline:Nn \c__osgstyler_font_slots_clist { \keys_define:nn { osgstyler / font-palette / #1 } { ##1 .code:n = \__osgstyler_font_resource_store:nnn {#1}{##1}{####1} } } \keys_set:nn { osgstyler / font-palette / #1 } {#2} \prop_if_in:NnF \g__osgstyler_font_palette_prop { #1 / main / kind } { \msg_error:nnn { osgstyler } { missing-main-font } {#1} } \prop_gput:Nnn \g__osgstyler_font_palette_prop { #1 / declared } { true } } \NewDocumentCommand \DeclareOsgFontPalette { m m } { \__osgstyler_font_palette_declare:nn {#1}{#2} } % \ldeen{Die Standardrückfallebenen der Font gehen vom der konzeptionellen Eignung aus, % nicht vom Aussehen}{Default fallbacks deliberately express suitability, not morphology}. \cs_new:Npn \__osgstyler_font_fallback:n #1 { \str_case:nnF {#1} { { companion } { main } { display } { companion } { accent } { companion } { symbol } { main } } { } } \cs_new_protected:Npn \__osgstyler_font_resolve:nnN #1#2#3 { \seq_if_in:NnTF \l__osgstyler_font_resolution_seq {#2} { \msg_error:nnnn { osgstyler } { font-alias-cycle } {#1}{#2} \tl_clear:N #3 } { \seq_put_right:Nn \l__osgstyler_font_resolution_seq {#2} \prop_get:NnNTF \g__osgstyler_font_palette_prop { #1 / #2 / kind } \l__osgstyler_font_resolution_kind_tl { \str_if_eq:VnTF \l__osgstyler_font_resolution_kind_tl { resource } { \tl_set:Nn #3 {#2} } { \prop_get:NnN \g__osgstyler_font_palette_prop { #1 / #2 / alias } \l__osgstyler_font_resolution_target_tl \exp_args:NnV \__osgstyler_font_resolve:nnN {#1} \l__osgstyler_font_resolution_target_tl #3 } } { \tl_set:Ne \l__osgstyler_font_resolution_target_tl { \__osgstyler_font_fallback:n {#2} } \tl_if_empty:NTF \l__osgstyler_font_resolution_target_tl { \msg_error:nnnn { osgstyler } { unavailable-font-slot } {#1}{#2} \tl_clear:N #3 } { \exp_args:NnV \__osgstyler_font_resolve:nnN {#1} \l__osgstyler_font_resolution_target_tl #3 } } \seq_pop_right:NN \l__osgstyler_font_resolution_seq \l__osgstyler_font_resolution_discard_tl } } \cs_new_protected:Npn \__osgstyler_font_slot_resolve:nnN #1#2#3 { \clist_if_in:NnTF \c__osgstyler_font_slots_clist {#2} { \seq_clear:N \l__osgstyler_font_resolution_seq \__osgstyler_font_resolve:nnN {#1}{#2} #3 } { \msg_error:nnn { osgstyler } { unknown-font-slot } {#2} \tl_clear:N #3 } } \NewDocumentCommand \OsgFontPaletteResolvedSlotTo { m m m } { \__osgstyler_font_slot_resolve:nnN {#1}{#2} #3 } \NewExpandableDocumentCommand \OsgFontPaletteValue { m m m } { \prop_item:Nn \g__osgstyler_font_palette_prop { #1 / #2 / #3 } } \NewDocumentCommand \OsgFontPaletteAvailableTF { m m } { \bool_if:NTF \g__osgstyler_colors_only_bool {#2}{#1} } \cs_new_protected:Npn \__osgstyler_font_palette_use:n #1 { \bool_if:NTF \g__osgstyler_colors_only_bool { \msg_error:nn { osgstyler } { font-management-unavailable } } { \prop_if_in:NnTF \g__osgstyler_font_palette_prop { #1 / declared } { \tl_gset:Nn \g__osgstyler_font_palette_tl {#1} \clist_map_inline:Nn \c__osgstyler_font_slots_clist { \str_if_eq:nnF {##1}{math} { \__osgstyler_font_slot_resolve:nnN {#1}{##1} \l_tmpa_tl \tl_if_empty:NF \l_tmpa_tl { \prop_if_in:NeF \g__osgstyler_font_family_prop { #1 / \l_tmpa_tl } { \prop_get:NeN \g__osgstyler_font_palette_prop { #1 / \l_tmpa_tl / font } \l__osgstyler_font_load_name_tl \prop_get:NeN \g__osgstyler_font_palette_prop { #1 / \l_tmpa_tl / features } \l__osgstyler_font_load_features_tl \exp_args:NNVV \fontspec_gset_family:Nnn \l__osgstyler_font_load_family_tl \l__osgstyler_font_load_features_tl \l__osgstyler_font_load_name_tl \prop_gput:NeV \g__osgstyler_font_family_prop { #1 / \l_tmpa_tl } \l__osgstyler_font_load_family_tl } } } } } { \msg_error:nnn { osgstyler } { unknown-font-palette } {#1} } } } \NewDocumentCommand \UseOsgFontPalette { m } { \__osgstyler_font_palette_use:n {#1} } \NewExpandableDocumentCommand \OsgActiveFontPalette { } { \tl_use:N \g__osgstyler_font_palette_tl } \cs_new_protected:Npn \__osgstyler_font_select:n #1 { \bool_if:NTF \g__osgstyler_colors_only_bool { \msg_error:nn { osgstyler } { font-management-unavailable } } { \str_if_eq:nnTF {#1}{math} { \msg_error:nn { osgstyler } { math-font-selection } } { \tl_if_empty:NTF \g__osgstyler_font_palette_tl { \msg_error:nnn { osgstyler } { unknown-font-palette } { } } { \exp_args:NV \__osgstyler_font_slot_resolve:nnN \g__osgstyler_font_palette_tl {#1} \l_tmpa_tl \prop_get:NeN \g__osgstyler_font_family_prop { \g__osgstyler_font_palette_tl / \l_tmpa_tl } \l_tmpb_tl \fontfamily{\l_tmpb_tl}\selectfont } } } } \NewDocumentCommand \UseOsgFont { m } { \__osgstyler_font_select:n {#1} } \DeclareOsgFontPalette { osgstyler-default } { main = { font = { Latin Modern Roman } }, companion = { font = { Latin Modern Sans } }, display = { alias = companion }, accent = { alias = companion }, mono = { font = { Latin Modern Mono } }, symbol = { alias = main } } \bool_if:NF \g__osgstyler_colors_only_bool { \UseOsgFontPalette { osgstyler-default } } % \ldeen*{Begleiten @1/@2 aus Konsistenzgründen (siehe dort): @3 bleibt % wie im Default bei \cls*{amsthm}-nahen Dokumenten üblich; @4 tauscht % nur die Begleitschrift (\code{companion}/\code{accent}/\code{display}) % gegen eine deutlicher zeitgenössisch wirkende Groteske, ohne den % Fließtext (\code{main}) anzutasten -- ein bewusst kleiner, risikoarmer % Unterschied.} % {Accompany @1/@2 for consistency (see there): @3 stays exactly like % the default, as usual for \cls*{amsthm}-adjacent documents; @4 only % swaps the companion face (\code{companion}/\code{accent}/\code{display}) % for a visibly more contemporary sans, leaving body text (\code{main}) % untouched -- a deliberately small, low-risk difference.} % {\code{osl-standard}}{\code{osl-modern}}{osl-standard} \DeclareOsgFontPalette { osl-standard } { main = { font = { Latin Modern Roman } }, companion = { font = { Latin Modern Sans } }, display = { alias = companion }, accent = { alias = companion }, mono = { font = { Latin Modern Mono } }, symbol = { alias = main } } \DeclareOsgFontPalette { osl-modern } { main = { font = { Latin Modern Roman } }, companion = { font = { TeX Gyre Heros } }, display = { alias = companion }, accent = { alias = companion }, mono = { font = { Latin Modern Mono } }, symbol = { alias = main } } % Color palettes deliberately mirror lttheme's public color vocabulary and % value syntax. osgstyler owns neutral, stable color names; a presentation % adapter can copy these colors without making the general package depend on % lttheme. \clist_const:Nn \c__osgstyler_color_slots_clist { primary, secondary, accent, text, background, structure, alert, example } \prop_new:N \g__osgstyler_color_palette_prop \tl_new:N \g__osgstyler_color_palette_tl \seq_new:N \l__osgstyler_color_resolution_seq \tl_new:N \l__osgstyler_color_resolution_model_tl \tl_new:N \l__osgstyler_color_resolution_target_tl \tl_new:N \l__osgstyler_color_resolution_discard_tl \tl_new:N \l__osgstyler_color_name_tl \msg_new:nnnn { osgstyler } { unknown-color-slot } { Unknown~color~palette~slot~'#1'. } { The~portable~slots~are~primary,~secondary,~accent,~text,~background,~structure,~alert,~and~example. } \msg_new:nnnn { osgstyler } { missing-primary-color } { Color~palette~'#1'~does~not~define~the~required~slot~'primary'. } { Add~primary={model=...,~value=...}~to~the~palette. } \msg_new:nnnn { osgstyler } { unknown-color-palette } { Unknown~color~palette~'#1'. } { Declare~it~with~\token_to_str:N\DeclareOsgColorPalette~before~using~it. } \msg_new:nnnn { osgstyler } { color-alias-cycle } { Color~palette~'#1'~contains~an~alias~cycle~at~slot~'#2'. } { Change~the~aliases~so~that~they~eventually~resolve~to~a~color~resource. } \msg_new:nnnn { osgstyler } { invalid-color-alias } { Color~slot~'#2'~in~palette~'#1'~aliases~unknown~slot~'#3'. } { Use~one~of~the~portable~color~slots~as~the~alias~target. } \msg_new:nnnn { osgstyler } { incomplete-color-resource } { Color~slot~'#2'~in~palette~'#1'~does~not~specify~both~model~and~value,~or~an~alias. } { Use~model=...,~value=...~for~a~resource~or~alias=...~for~an~alias. } \msg_new:nnnn { osgstyler } { conflicting-color-resource } { Color~slot~'#2'~in~palette~'#1'~specifies~both~a~resource~and~an~alias. } { A~slot~must~either~own~a~color~resource~or~alias~another~slot. } \tl_new:N \l__osgstyler_color_model_tl \tl_new:N \l__osgstyler_color_value_tl \tl_new:N \l__osgstyler_color_alias_tl \keys_define:nn { osgstyler / color-resource } { model .tl_set:N = \l__osgstyler_color_model_tl, value .tl_set:N = \l__osgstyler_color_value_tl, alias .tl_set:N = \l__osgstyler_color_alias_tl, } \cs_new_protected:Npn \__osgstyler_color_resource_store:nnn #1#2#3 { \tl_clear:N \l__osgstyler_color_model_tl \tl_clear:N \l__osgstyler_color_value_tl \tl_clear:N \l__osgstyler_color_alias_tl \keys_set:nn { osgstyler / color-resource } {#3} \bool_lazy_and:nnTF { ! \tl_if_empty_p:N \l__osgstyler_color_alias_tl } { \bool_lazy_or_p:nn { ! \tl_if_empty_p:N \l__osgstyler_color_model_tl } { ! \tl_if_empty_p:N \l__osgstyler_color_value_tl } } { \msg_error:nnnn { osgstyler } { conflicting-color-resource } {#1} {#2} } { \tl_if_empty:NTF \l__osgstyler_color_alias_tl { \bool_lazy_and:nnTF { ! \tl_if_empty_p:N \l__osgstyler_color_model_tl } { ! \tl_if_empty_p:N \l__osgstyler_color_value_tl } { \prop_gput:Nnn \g__osgstyler_color_palette_prop { #1 / #2 / kind } { resource } \prop_gput:NnV \g__osgstyler_color_palette_prop { #1 / #2 / model } \l__osgstyler_color_model_tl \prop_gput:NnV \g__osgstyler_color_palette_prop { #1 / #2 / value } \l__osgstyler_color_value_tl } { \msg_error:nnnn { osgstyler } { incomplete-color-resource } {#1} {#2} } } { \clist_if_in:NVTF \c__osgstyler_color_slots_clist \l__osgstyler_color_alias_tl { \prop_gput:Nnn \g__osgstyler_color_palette_prop { #1 / #2 / kind } { alias } \prop_gput:NnV \g__osgstyler_color_palette_prop { #1 / #2 / alias } \l__osgstyler_color_alias_tl } { \msg_error:nneee { osgstyler } { invalid-color-alias } {#1}{#2} \l__osgstyler_color_alias_tl } } } } \cs_new_protected:Npn \__osgstyler_color_palette_declare:nn #1#2 { \clist_map_inline:Nn \c__osgstyler_color_slots_clist { \clist_map_inline:nn { kind, model, value, alias } { \prop_gremove:Nn \g__osgstyler_color_palette_prop { #1 / ##1 / ####1 } } \keys_define:nn { osgstyler / color-palette / #1 } { ##1 .code:n = \__osgstyler_color_resource_store:nnn {#1}{##1}{####1} } } \prop_gremove:Nn \g__osgstyler_color_palette_prop { #1 / declared } \keys_set:nn { osgstyler / color-palette / #1 } {#2} \prop_if_in:NnF \g__osgstyler_color_palette_prop { #1 / primary / kind } { \msg_error:nnn { osgstyler } { missing-primary-color } {#1} } \prop_gput:Nnn \g__osgstyler_color_palette_prop { #1 / declared } { true } } \NewDocumentCommand \DeclareOsgColorPalette { m m } { \__osgstyler_color_palette_declare:nn {#1}{#2} } % Ordered color series complement semantic palettes where position, rather % than a portable role name, identifies a color. They deliberately reuse % the color-resource syntax, but do not support aliases: an integer alias % would make reordering surprisingly change its meaning. \prop_new:N \g__osgstyler_color_series_prop \int_new:N \l__osgstyler_color_series_index_int \msg_new:nnnn { osgstyler } { invalid-color-series-resource } { Color~series~'#1',~item~#2~is~not~a~complete~color~resource. } { Each~item~must~specify~model=...~and~value=...;~aliases~are~not~supported. } \cs_new:Npn \__osgstyler_color_series_name:nn #1#2 { osgstyler-series-#1-#2 } \cs_new_protected:Npn \__osgstyler_color_series_item_store:nnn #1#2#3 { \tl_clear:N \l__osgstyler_color_model_tl \tl_clear:N \l__osgstyler_color_value_tl \tl_clear:N \l__osgstyler_color_alias_tl \keys_set:nn { osgstyler / color-resource } {#3} \bool_lazy_all:nTF { { ! \tl_if_empty_p:N \l__osgstyler_color_model_tl } { ! \tl_if_empty_p:N \l__osgstyler_color_value_tl } { \tl_if_empty_p:N \l__osgstyler_color_alias_tl } } { \prop_gput:NnV \g__osgstyler_color_series_prop { #1 / #2 / model } \l__osgstyler_color_model_tl \prop_gput:NnV \g__osgstyler_color_series_prop { #1 / #2 / value } \l__osgstyler_color_value_tl \tl_set:Ne \l__osgstyler_color_name_tl { \__osgstyler_color_series_name:nn {#1}{#2} } \exp_args:NVVV \color_set:nnn \l__osgstyler_color_name_tl \l__osgstyler_color_model_tl \l__osgstyler_color_value_tl \exp_args:Nooo \definecolor { \l__osgstyler_color_name_tl } { \l__osgstyler_color_model_tl } { \l__osgstyler_color_value_tl } } { \msg_error:nnnn { osgstyler } { invalid-color-series-resource } {#1}{#2} } } \cs_new_protected:Npn \__osgstyler_color_series_declare:nn #1#2 { \int_zero:N \l__osgstyler_color_series_index_int \clist_map_inline:nn {#2} { \int_incr:N \l__osgstyler_color_series_index_int \exp_args:NnV \__osgstyler_color_series_item_store:nnn {#1} \l__osgstyler_color_series_index_int {##1} } \prop_gput:Nnx \g__osgstyler_color_series_prop { #1 / count } { \int_use:N \l__osgstyler_color_series_index_int } } \NewDocumentCommand \DeclareOsgColorSeries { m m } { \__osgstyler_color_series_declare:nn {#1}{#2} } \NewExpandableDocumentCommand \OsgColorSeriesCount { m } { \prop_item:Nn \g__osgstyler_color_series_prop { #1 / count } } \NewExpandableDocumentCommand \OsgColorSeriesColorName { m m } { \__osgstyler_color_series_name:nn {#1}{#2} } \int_new:N \l__osgstyler_color_series_generate_count_int \tl_new:N \l__osgstyler_color_series_generate_primary_tl \tl_new:N \l__osgstyler_color_series_generate_accent_tl \tl_new:N \l__osgstyler_color_series_generate_background_tl \tl_new:N \l__osgstyler_color_series_generated_tl \tl_new:N \l__osgstyler_color_series_colorspec_tl \keys_define:nn { osgstyler / generate-color-series } { count .int_set:N = \l__osgstyler_color_series_generate_count_int, primary .tl_set:N = \l__osgstyler_color_series_generate_primary_tl, accent .tl_set:N = \l__osgstyler_color_series_generate_accent_tl, background .tl_set:N = \l__osgstyler_color_series_generate_background_tl, } \msg_new:nnn { osgstyler } { luatex-required-for-series-generation } { GenerateOsgColorSeries~requires~LuaLaTeX. } \cs_new_protected:Npn \__osgstyler_colorspec_to_html:nnN #1#2#3 { \convertcolorspec{#1}{#2}{HTML}#3 } \cs_new_protected:Npn \__osgstyler_color_to_html:nN #1#2 { \extractcolorspec{#1}\l__osgstyler_color_series_colorspec_tl \exp_after:wN \__osgstyler_colorspec_to_html:nnN \l__osgstyler_color_series_colorspec_tl #2 } \sys_if_engine_luatex:T { \directlua{ osgstyler = require("osgstyler") } } \cs_new_protected:Npn \__osgstyler_color_series_generate:nn #1#2 { \keys_set:nn { osgstyler / generate-color-series } { count=8, primary=\OsgColorName{primary}, accent=\OsgColorName{accent}, background=\OsgColorName{background}, #2 } \int_set:Nn \l__osgstyler_color_series_generate_count_int { \int_max:nn { 2 } { \int_min:nn { 32 } { \l__osgstyler_color_series_generate_count_int } } } \sys_if_engine_luatex:TF { \exp_args:NV \__osgstyler_color_to_html:nN \l__osgstyler_color_series_generate_primary_tl \l__osgstyler_color_series_generate_primary_tl \exp_args:NV \__osgstyler_color_to_html:nN \l__osgstyler_color_series_generate_accent_tl \l__osgstyler_color_series_generate_accent_tl \exp_args:NV \__osgstyler_color_to_html:nN \l__osgstyler_color_series_generate_background_tl \l__osgstyler_color_series_generate_background_tl \tl_set:Nx \l__osgstyler_color_series_generated_tl { \directlua { tex.sprint(osgstyler.generate_color_series( "\luaescapestring{\l__osgstyler_color_series_generate_primary_tl}", "\luaescapestring{\l__osgstyler_color_series_generate_accent_tl}", "\luaescapestring{\l__osgstyler_color_series_generate_background_tl}", \int_use:N \l__osgstyler_color_series_generate_count_int)) } } \exp_args:NnV \DeclareOsgColorSeries {#1} \l__osgstyler_color_series_generated_tl } { \msg_error:nn { osgstyler } { luatex-required-for-series-generation } } } \NewDocumentCommand \GenerateOsgColorSeries { m O{} } { \__osgstyler_color_series_generate:nn {#1}{#2} } \cs_new:Npn \__osgstyler_color_fallback:n #1 { \str_case:nnF {#1} { { secondary } { primary } { accent } { secondary } { structure } { primary } { alert } { primary } { example } { primary } } { } } % text/background are not aliased into the slot-fallback chain above: % every other slot's fallback is another *theme* color (ultimately % primary), which is appropriate for accents but not for body text or % the page background, where a theme accent color risks poor % contrast or an unintended colored background. Their fallback is % therefore a fixed neutral value, kept in its own small table rather % than special-cased in the resolution code. \prop_new:N \c__osgstyler_color_absolute_default_prop \prop_gput:Nnn \c__osgstyler_color_absolute_default_prop { text } { black } \prop_gput:Nnn \c__osgstyler_color_absolute_default_prop { background } { white } \cs_new_protected:Npn \__osgstyler_color_resolve:nnN #1#2#3 { \seq_if_in:NnTF \l__osgstyler_color_resolution_seq {#2} { \msg_error:nnnn { osgstyler } { color-alias-cycle } {#1}{#2} \tl_clear:N #3 } { \seq_put_right:Nn \l__osgstyler_color_resolution_seq {#2} \prop_get:NnNTF \g__osgstyler_color_palette_prop { #1 / #2 / kind } \l__osgstyler_color_resolution_model_tl { \str_if_eq:VnTF \l__osgstyler_color_resolution_model_tl { resource } { \tl_set:Nn #3 {#2} } { \prop_get:NnN \g__osgstyler_color_palette_prop { #1 / #2 / alias } \l__osgstyler_color_resolution_target_tl \exp_args:NnV \__osgstyler_color_resolve:nnN {#1} \l__osgstyler_color_resolution_target_tl #3 } } { \tl_set:Ne \l__osgstyler_color_resolution_target_tl { \__osgstyler_color_fallback:n {#2} } \tl_if_empty:NTF \l__osgstyler_color_resolution_target_tl { \tl_clear:N #3 } { \exp_args:NnV \__osgstyler_color_resolve:nnN {#1} \l__osgstyler_color_resolution_target_tl #3 } } \seq_pop_right:NN \l__osgstyler_color_resolution_seq \l__osgstyler_color_resolution_discard_tl } } \cs_new_protected:Npn \__osgstyler_color_slot_resolve:nnN #1#2#3 { \clist_if_in:NnTF \c__osgstyler_color_slots_clist {#2} { \seq_clear:N \l__osgstyler_color_resolution_seq \__osgstyler_color_resolve:nnN {#1}{#2} #3 } { \msg_error:nnn { osgstyler } { unknown-color-slot } {#2} \tl_clear:N #3 } } \NewDocumentCommand \OsgColorPaletteResolvedSlotTo { m m m } { \__osgstyler_color_slot_resolve:nnN {#1}{#2} #3 } \NewExpandableDocumentCommand \OsgColorPaletteValue { m m m } { \prop_item:Nn \g__osgstyler_color_palette_prop { #1 / #2 / #3 } } \cs_new:Npn \__osgstyler_color_name:n #1 { osgstyler-#1 } \NewExpandableDocumentCommand \OsgColorName { m } { \__osgstyler_color_name:n {#1} } % \ldeen*{@1 schreibt die aufgelöste Farbe sowohl nativ über @2 als % auch klassisch über @3: \cls*{ltx-talk} und vergleichbare, % modernisierte Klassen ersetzen \cs{color} durch eine auf @2s eigenem % Modul aufbauende Fassung, die nur nativ registrierte Farben kennt; % Werkzeuge wie \cs{extractcolorspec} und unveränderte Klassen kennen % dagegen nur die klassische Registrierung -- nur die doppelte % Schreibweise bedient beide Seiten zuverlässig. Ein dritter Parameter % trennt bewusst "`welcher Slot liefert die Daten"' (\#2, nach % Alias-Auflösung der eigentliche Ressourcen-Besitzer) von "`unter % welchem Namen wird geschrieben"' (\#3, der ursprünglich angefragte % Slot): Ein Alias-Slot bekommt dadurch seine eigene Farbe direkt % zugewiesen, statt sie nachträglich unter @4 auf die Besitzer-Farbe zu % verweisen -- @4 selbst ist, anders als @2/@3, grundsätzlich nur lokal % gültig und würde eine Zuweisung aus der Deklarationszeit nicht % zuverlässig bis zum Dokumentkörper erhalten.}{@1 writes the resolved % color both natively via @2 and classically via @3: \cls*{ltx-talk} and % similarly modernized classes replace \cs{color} with a version built % on @2's own module, which only recognizes natively registered colors; % tools such as \cs{extractcolorspec} and unmodified classes, in turn, % only recognize the classical registration -- only writing both % reliably serves either side. A third parameter deliberately separates % "`which slot supplies the data"' (\#2, the actual resource owner after % alias resolution) from "`under which name is it written"' (\#3, the % originally requested slot): an alias slot thus gets its own color % assigned directly, instead of pointing it at the owner's color % afterward via @4 -- @4 itself, unlike @2/@3, is fundamentally % local-only and would not reliably carry an assignment made at % declaration time through to the document body.} % {\cs{color\_set:nnn}}{\pkg*{l3color}}{\cs{definecolor}}{\cs{color\_set\_eq:nn}} \cs_new_protected:Npn \__osgstyler_color_define_from_resource:nnn #1#2#3 { \prop_get:NnN \g__osgstyler_color_palette_prop { #1 / #2 / model } \l__osgstyler_color_resolution_model_tl \prop_get:NnN \g__osgstyler_color_palette_prop { #1 / #2 / value } \l__osgstyler_color_resolution_target_tl \tl_set:Nx \l__osgstyler_color_name_tl { \__osgstyler_color_name:n {#3} } \color_set:VVV \l__osgstyler_color_name_tl \l__osgstyler_color_resolution_model_tl \l__osgstyler_color_resolution_target_tl \use:e { \exp_not:N \definecolor { \l__osgstyler_color_name_tl } { \l__osgstyler_color_resolution_model_tl } { \l__osgstyler_color_resolution_target_tl } } } \cs_generate_variant:Nn \__osgstyler_color_define_from_resource:nnn { nVn } \cs_new_protected:Npn \__osgstyler_color_palette_use:n #1 { \prop_if_in:NnTF \g__osgstyler_color_palette_prop { #1 / declared } { \tl_gset:Nn \g__osgstyler_color_palette_tl {#1} \clist_map_inline:Nn \c__osgstyler_color_slots_clist { \__osgstyler_color_slot_resolve:nnN {#1}{##1} \l_tmpa_tl \tl_if_empty:NTF \l_tmpa_tl { \prop_if_in:NnT \c__osgstyler_color_absolute_default_prop {##1} { \prop_get:NnN \c__osgstyler_color_absolute_default_prop {##1} \l_tmpb_tl \tl_set:Nx \l_tmpc_tl { \__osgstyler_color_name:n {##1} } \str_case:VnF \l_tmpb_tl { { black } { \exp_args:NV \color_set:nnn \l_tmpc_tl { gray } { 0 } \exp_args:Nee \colorlet { \l_tmpc_tl } { black } } { white } { \exp_args:NV \color_set:nnn \l_tmpc_tl { gray } { 1 } \exp_args:Nee \colorlet { \l_tmpc_tl } { white } } } { } } } { \__osgstyler_color_define_from_resource:nVn {#1} \l_tmpa_tl {##1} } } } { \msg_error:nnn { osgstyler } { unknown-color-palette } {#1} } } \NewDocumentCommand \UseOsgColorPalette { m } { \__osgstyler_color_palette_use:n {#1} } \NewExpandableDocumentCommand \OsgActiveColorPalette { } { \tl_use:N \g__osgstyler_color_palette_tl } \DeclareOsgColorPalette { osgstyler-default } { primary = { model = RGB, value = {43,58,103} }, secondary = { model = RGB, value = {73,106,129} }, accent = { model = RGB, value = {102,153,155} }, text = { model = RGB, value = {51,51,51} }, background = { model = RGB, value = {255,255,255} }, structure = { alias = primary }, alert = { model = RGB, value = {176,0,32} }, example = { model = RGB, value = {22,101,52} } } \UseOsgColorPalette { osgstyler-default } % \ldeen*{@1/@2 sind fertige, aber nicht automatisch aktive Paletten % (analog zu @3s Themes für Beamer): eine für die Langform gedachte, % zurückhaltende Variante nah am unkolorierten \cls*{amsthm}-Erscheinungsbild, % und eine an einem verbreiteten, aufeinander abgestimmten modernen % Farbsystem orientierte. Ein Dokument aktiviert eine davon bewusst über % \cs{UseOsgColorPalette}; ungenutzt ändert sich nichts.} % {@1/@2 are ready-made but not automatically active palettes (mirroring % @3's themes for beamer): one intended for the long form, restrained % and close to unadorned \cls*{amsthm} appearance, and one modeled on a % widely used, coordinated modern color system. A document activates % one deliberately via \cs{UseOsgColorPalette}; unused, nothing % changes.} % {\code{osl-standard}}{\code{osl-modern}}{\pkg*{lttheme}} \DeclareOsgColorPalette { osl-standard } { primary = { model = HTML, value = 141E3C }, secondary = { model = HTML, value = 505050 }, accent = { model = HTML, value = C8C8C8 }, text = { model = HTML, value = 000000 }, background = { model = HTML, value = FFFFFF }, structure = { alias = primary }, alert = { model = HTML, value = B40000 }, example = { model = HTML, value = 006400 } } \DeclareOsgColorPalette { osl-modern } { primary = { model = HTML, value = 4338CA }, secondary = { model = HTML, value = 475569 }, accent = { model = HTML, value = F59E0B }, text = { model = HTML, value = 0F172A }, background = { model = HTML, value = FFFFFF }, structure = { alias = primary }, alert = { model = HTML, value = E11D48 }, example = { model = HTML, value = 059669 } } % Metric palettes provide a small, portable scale for layout and decoration % backends. Every declaration starts from complete defaults and may override % only the values relevant to a theme. \clist_const:Nn \c__osgstyler_metric_categories_clist { spacing, padding, rule, radius } \prop_new:N \g__osgstyler_metric_palette_prop \tl_new:N \g__osgstyler_metric_palette_tl \tl_new:N \l__osgstyler_metric_palette_tl \tl_new:N \l__osgstyler_metric_category_tl \msg_new:nnnn { osgstyler } { unknown-metric-palette } { Unknown~metric~palette~'#1'. } { Declare~it~with~\token_to_str:N\DeclareOsgMetricPalette~before~using~it. } \cs_new_protected:Npn \__osgstyler_metric_put:nn #1#2 { \prop_gput:Nee \g__osgstyler_metric_palette_prop { \l__osgstyler_metric_palette_tl / \l__osgstyler_metric_category_tl / #1 } { \exp_not:n {#2} } } \keys_define:nn { osgstyler / metric-palette / spacing } { none .code:n = \__osgstyler_metric_put:nn { none }{#1}, xs .code:n = \__osgstyler_metric_put:nn { xs }{#1}, sm .code:n = \__osgstyler_metric_put:nn { sm }{#1}, md .code:n = \__osgstyler_metric_put:nn { md }{#1}, lg .code:n = \__osgstyler_metric_put:nn { lg }{#1}, xl .code:n = \__osgstyler_metric_put:nn { xl }{#1} } \keys_define:nn { osgstyler / metric-palette / padding } { none .code:n = \__osgstyler_metric_put:nn { none }{#1}, compact .code:n = \__osgstyler_metric_put:nn { compact }{#1}, normal .code:n = \__osgstyler_metric_put:nn { normal }{#1}, roomy .code:n = \__osgstyler_metric_put:nn { roomy }{#1} } \keys_define:nn { osgstyler / metric-palette / rule } { hairline .code:n = \__osgstyler_metric_put:nn { hairline }{#1}, thin .code:n = \__osgstyler_metric_put:nn { thin }{#1}, normal .code:n = \__osgstyler_metric_put:nn { normal }{#1}, thick .code:n = \__osgstyler_metric_put:nn { thick }{#1} } \keys_define:nn { osgstyler / metric-palette / radius } { square .code:n = \__osgstyler_metric_put:nn { square }{#1}, small .code:n = \__osgstyler_metric_put:nn { small }{#1}, medium .code:n = \__osgstyler_metric_put:nn { medium }{#1}, round .code:n = \__osgstyler_metric_put:nn { round }{#1} } \cs_new_protected:Npn \__osgstyler_metric_category:nn #1#2 { \tl_set:Nn \l__osgstyler_metric_category_tl {#1} \keys_set:nn { osgstyler / metric-palette / #1 } {#2} } \keys_define:nn { osgstyler / metric-palette } { spacing .code:n = \__osgstyler_metric_category:nn { spacing }{#1}, padding .code:n = \__osgstyler_metric_category:nn { padding }{#1}, rule .code:n = \__osgstyler_metric_category:nn { rule }{#1}, radius .code:n = \__osgstyler_metric_category:nn { radius }{#1} } \cs_new_protected:Npn \__osgstyler_metric_defaults: { \__osgstyler_metric_category:nn { spacing } { none=0pt, xs=.25\baselineskip, sm=.5\baselineskip, md=1\baselineskip, lg=1.5\baselineskip, xl=2\baselineskip } \__osgstyler_metric_category:nn { padding } { none=0pt, compact=.35em, normal=.65em, roomy=1em } \__osgstyler_metric_category:nn { rule } { hairline=.2pt, thin=.4pt, normal=.7pt, thick=1.2pt } \__osgstyler_metric_category:nn { radius } { square=0pt, small=2pt, medium=4pt, round=6pt } } \cs_new_protected:Npn \__osgstyler_metric_palette_declare:nn #1#2 { \tl_set:Nn \l__osgstyler_metric_palette_tl {#1} \__osgstyler_metric_defaults: \keys_set:nn { osgstyler / metric-palette } {#2} \prop_gput:Nnn \g__osgstyler_metric_palette_prop { #1 / declared } { true } } \NewDocumentCommand \DeclareOsgMetricPalette { m m } { \__osgstyler_metric_palette_declare:nn {#1}{#2} } \NewDocumentCommand \UseOsgMetricPalette { m } { \prop_if_in:NnTF \g__osgstyler_metric_palette_prop { #1 / declared } { \tl_gset:Nn \g__osgstyler_metric_palette_tl {#1} } { \msg_error:nnn { osgstyler } { unknown-metric-palette } {#1} } } \NewExpandableDocumentCommand \OsgMetricPaletteValue { m m m } { \prop_item:Nn \g__osgstyler_metric_palette_prop { #1 / #2 / #3 } } \NewExpandableDocumentCommand \OsgMetric { m m } { \prop_item:Ne \g__osgstyler_metric_palette_prop { \g__osgstyler_metric_palette_tl / #1 / #2 } } \NewExpandableDocumentCommand \OsgActiveMetricPalette { } { \tl_use:N \g__osgstyler_metric_palette_tl } \DeclareOsgMetricPalette { osgstyler-default } { } \UseOsgMetricPalette { osgstyler-default } % \ldeen*{Anders als Farb-/Font-/Metrikpaletten ist eine Listenpalette % keine reine Datenablage: @1 wendet die hinterlegten Optionen sofort % über @2 an, denn genau das ist auch @2s eigenes Anwendungsmodell % (gilt ab dem Aufruf, nicht rückwirkend). Werte pro Ebene sind % rohe @2-Optionen -- dieses Paket erfindet @2s Vokabular nicht neu, % es organisiert es nur unter einem Palettennamen und macht die % Anwendung modusabhängig (siehe unten). Nicht deklarierte % Ebenen/Listenarten bleiben unangetastet.} % {Unlike color/font/metric palettes, a list palette is not a pure data % store: @1 applies the stored options immediately via @2, since that % matches @2's own application model (takes effect from the call % onward, not retroactively). Values per level are raw @2 options -- % this package does not reinvent @2's vocabulary, it only organizes it % under a palette name and makes applying it mode-aware (see below). % Levels/list types that were never declared stay untouched.} % {\cs{UseOsgListPalette}}{\pkg*{enumitem}} \prop_new:N \g__osgstyler_list_palette_prop \tl_new:N \l__osgstyler_list_palette_tl \tl_new:N \l__osgstyler_list_type_tl \tl_new:N \l__osgstyler_list_value_tl \msg_new:nnnn { osgstyler } { unknown-list-palette } { Unknown~list~palette~'#1'. } { Declare~it~with~\token_to_str:N\DeclareOsgListPalette~before~using~it. } \cs_new_protected:Npn \__osgstyler_list_put:nn #1#2 { \prop_gput:Nee \g__osgstyler_list_palette_prop { \l__osgstyler_list_palette_tl / \l__osgstyler_list_type_tl / #1 } { \exp_not:n {#2} } } \keys_define:nn { osgstyler / list-palette / type } { 1 .code:n = \__osgstyler_list_put:nn { 1 }{#1}, 2 .code:n = \__osgstyler_list_put:nn { 2 }{#1}, 3 .code:n = \__osgstyler_list_put:nn { 3 }{#1}, 4 .code:n = \__osgstyler_list_put:nn { 4 }{#1} } \cs_new_protected:Npn \__osgstyler_list_type:nn #1#2 { \tl_set:Nn \l__osgstyler_list_type_tl {#1} \keys_set:nn { osgstyler / list-palette / type } {#2} } \keys_define:nn { osgstyler / list-palette } { itemize .code:n = \__osgstyler_list_type:nn { itemize }{#1}, enumerate .code:n = \__osgstyler_list_type:nn { enumerate }{#1}, description .code:n = \__osgstyler_list_type:nn { description }{#1} } \cs_new_protected:Npn \__osgstyler_list_palette_declare:nn #1#2 { \tl_set:Nn \l__osgstyler_list_palette_tl {#1} \keys_set:nn { osgstyler / list-palette } {#2} \prop_gput:Nnn \g__osgstyler_list_palette_prop { #1 / declared } { true } } \NewDocumentCommand \DeclareOsgListPalette { m m } { \__osgstyler_list_palette_declare:nn {#1}{#2} } \cs_new_protected:Npn \__osgstyler_list_apply_level:nnn #1#2#3 { \prop_if_in:NnT \g__osgstyler_list_palette_prop { #1 / #2 / #3 } { % enumitem's own key=value scanner reads its options argument % before full expansion, so handing it a macro call there (even % one that expands to the right text) misparses -- \arabic* % style placeholders in particular. Building the whole \setlist % call as already-expanded literal text first, then executing % that, avoids the issue entirely. \tl_set:Ne \l__osgstyler_list_value_tl { \exp_not:N \setlist [ #2 , #3 ] { \prop_item:Nn \g__osgstyler_list_palette_prop { #1 / #2 / #3 } } } \tl_use:N \l__osgstyler_list_value_tl } } \cs_new_protected:Npn \__osgstyler_list_apply_type:nn #1#2 { \clist_map_inline:nn { 1 , 2 , 3 , 4 } { \__osgstyler_list_apply_level:nnn {#1}{#2}{##1} } } \cs_new_protected:Npn \__osgstyler_list_palette_apply:n #1 { \__osgstyler_list_apply_type:nn {#1} { itemize } \__osgstyler_list_apply_type:nn {#1} { enumerate } \__osgstyler_list_apply_type:nn {#1} { description } } \NewDocumentCommand \UseOsgListPalette { m } { \prop_if_in:NnTF \g__osgstyler_list_palette_prop { #1 / declared } { \__osgstyler_list_palette_apply:n {#1} } { \msg_error:nnn { osgstyler } { unknown-list-palette } {#1} } } \DeclareOsgListPalette { osgstyler-default } { } \UseOsgListPalette { osgstyler-default } % \ldeen*{Ein bewusst zurückhaltendes Accessoire zu \code{osl-modern} % (siehe dort): ein gezeichnetes \cs{rule}-Quadrat statt eines % Font-Glyphen für die oberste Aufzählungsebene, gefärbt über % \cs{OsgColorName}. Da ein gezeichnetes Element kein Glyph aus % irgendeiner Schrift braucht, kann es auch nicht mit einer % Begleitschrift kollidieren. \code{osl-standard} bleibt bei % enumitems eigenen Vorgaben.} % {A deliberately restrained accessory to \code{osl-modern} (see % there): a drawn \cs{rule} square instead of a font glyph for the % top-level itemize marker, colored via \cs{OsgColorName}. Since a % drawn element needs no glyph from any typeface, it cannot clash with % a companion face either. \code{osl-standard} stays with enumitem's % own defaults.} \DeclareOsgListPalette { osl-modern } { itemize = { 1 = { label = \textcolor{\OsgColorName{accent}} { \rule[.15ex]{.9ex}{.9ex} } } } } % Decorations are backend-neutral visual recipes. A decoration may combine % any number of components; rendering packages consume the stored values. \clist_const:Nn \c__osgstyler_decoration_families_clist { line, background, enclosure, margin, affix } \clist_const:Nn \c__osgstyler_decoration_line_properties_clist { position, pattern, color, thickness, offset } \clist_const:Nn \c__osgstyler_decoration_background_properties_clist { color, opacity, shape } \clist_const:Nn \c__osgstyler_decoration_enclosure_properties_clist { border-color, fill-color, pattern, thickness, radius, padding } \clist_const:Nn \c__osgstyler_decoration_margin_properties_clist { side, color, pattern, thickness, offset } \clist_const:Nn \c__osgstyler_decoration_affix_properties_clist { position, content, symbol, color, font, gap } \prop_new:N \g__osgstyler_decoration_prop \tl_new:N \l__osgstyler_decoration_name_tl \tl_new:N \l__osgstyler_decoration_family_tl \tl_new:N \l__osgstyler_decoration_instance_tl \msg_new:nnnn { osgstyler } { unknown-decoration } { Unknown~decoration~'#1'. } { Declare~it~with~\token_to_str:N\DeclareOsgDecoration~before~using~it. } \msg_new:nnnn { osgstyler } { unknown-decoration-family } { Unknown~decoration~family~'#1'. } { The~families~are~line,~background,~enclosure,~margin,~and~affix. } \msg_new:nnnn { osgstyler } { invalid-decoration-color } { Decoration~'#1'~uses~unknown~color~slot~'#2'. } { Use~a~portable~color-palette~slot. } \msg_new:nnnn { osgstyler } { invalid-decoration-font } { Decoration~'#1'~uses~unknown~font~slot~'#2'. } { Use~a~portable~font-palette~slot. } \msg_new:nnnn { osgstyler } { conflicting-affix-content } { Affix~instance~'#2'~in~decoration~'#1'~has~both~content~and~symbol. } { Use~content={...}~for~literal~material~or~symbol=...~for~a~symbol~resource. } \cs_new_protected:Npn \__osgstyler_decoration_instance_register: { \prop_get:NeNTF \g__osgstyler_decoration_prop { \l__osgstyler_decoration_name_tl / \l__osgstyler_decoration_family_tl / instances } \l_tmpa_tl { } { \tl_clear:N \l_tmpa_tl } \exp_args:NNV \clist_if_in:NnF \l_tmpa_tl \l__osgstyler_decoration_instance_tl { \exp_args:NNV \clist_put_right:Nn \l_tmpa_tl \l__osgstyler_decoration_instance_tl \prop_gput:Nee \g__osgstyler_decoration_prop { \l__osgstyler_decoration_name_tl / \l__osgstyler_decoration_family_tl / instances } { \l_tmpa_tl } } \prop_gput:Nee \g__osgstyler_decoration_prop { \l__osgstyler_decoration_name_tl / \l__osgstyler_decoration_family_tl / \l__osgstyler_decoration_instance_tl / present } { true } } \cs_new_protected:Npn \__osgstyler_decoration_put:nn #1#2 { \__osgstyler_decoration_instance_register: \prop_gput:Nee \g__osgstyler_decoration_prop { \l__osgstyler_decoration_name_tl / \l__osgstyler_decoration_family_tl / \l__osgstyler_decoration_instance_tl / #1 } { \exp_not:n {#2} } } \cs_new_protected:Npn \__osgstyler_decoration_color_put:nn #1#2 { \clist_if_in:NnTF \c__osgstyler_color_slots_clist {#2} { \__osgstyler_decoration_put:nn {#1}{#2} } { \msg_error:nnxx { osgstyler } { invalid-decoration-color } { \l__osgstyler_decoration_name_tl } {#2} } } \cs_new_protected:Npn \__osgstyler_decoration_font_put:n #1 { \clist_if_in:NnTF \c__osgstyler_font_slots_clist {#1} { \__osgstyler_decoration_put:nn { font }{#1} } { \msg_error:nnxx { osgstyler } { invalid-decoration-font } { \l__osgstyler_decoration_name_tl } {#1} } } \cs_new_protected:Npn \__osgstyler_decoration_affix_payload_put:nn #1#2 { \__osgstyler_decoration_instance_register: \str_if_eq:nnTF {#1}{content} { \tl_set:Nn \l_tmpa_tl { symbol } } { \tl_set:Nn \l_tmpa_tl { content } } \prop_if_in:NeT \g__osgstyler_decoration_prop { \l__osgstyler_decoration_name_tl / \l__osgstyler_decoration_family_tl / \l__osgstyler_decoration_instance_tl / \l_tmpa_tl } { \msg_error:nnxx { osgstyler } { conflicting-affix-content } { \l__osgstyler_decoration_name_tl } { \l__osgstyler_decoration_instance_tl } } \__osgstyler_decoration_put:nn {#1}{#2} } \keys_define:nn { osgstyler / decoration / line } { position .code:n = \__osgstyler_decoration_put:nn { position }{#1}, pattern .code:n = \__osgstyler_decoration_put:nn { pattern }{#1}, color .code:n = \__osgstyler_decoration_color_put:nn { color }{#1}, thickness .code:n = \__osgstyler_decoration_put:nn { thickness }{#1}, offset .code:n = \__osgstyler_decoration_put:nn { offset }{#1} } \keys_define:nn { osgstyler / decoration / background } { color .code:n = \__osgstyler_decoration_color_put:nn { color }{#1}, opacity .code:n = \__osgstyler_decoration_put:nn { opacity }{#1}, shape .code:n = \__osgstyler_decoration_put:nn { shape }{#1} } \keys_define:nn { osgstyler / decoration / enclosure } { border-color .code:n = \__osgstyler_decoration_color_put:nn { border-color }{#1}, fill-color .code:n = \__osgstyler_decoration_color_put:nn { fill-color }{#1}, pattern .code:n = \__osgstyler_decoration_put:nn { pattern }{#1}, thickness .code:n = \__osgstyler_decoration_put:nn { thickness }{#1}, radius .code:n = \__osgstyler_decoration_put:nn { radius }{#1}, padding .code:n = \__osgstyler_decoration_put:nn { padding }{#1} } \keys_define:nn { osgstyler / decoration / margin } { side .code:n = \__osgstyler_decoration_put:nn { side }{#1}, color .code:n = \__osgstyler_decoration_color_put:nn { color }{#1}, pattern .code:n = \__osgstyler_decoration_put:nn { pattern }{#1}, thickness .code:n = \__osgstyler_decoration_put:nn { thickness }{#1}, offset .code:n = \__osgstyler_decoration_put:nn { offset }{#1} } \keys_define:nn { osgstyler / decoration / affix } { position .choices:nn = { before, after } { \__osgstyler_decoration_put:nn { position }{#1} }, content .code:n = \__osgstyler_decoration_affix_payload_put:nn { content }{#1}, symbol .code:n = \__osgstyler_decoration_affix_payload_put:nn { symbol }{#1}, color .code:n = \__osgstyler_decoration_color_put:nn { color }{#1}, font .code:n = \__osgstyler_decoration_font_put:n {#1}, gap .code:n = \__osgstyler_decoration_put:nn { gap }{#1} } \cs_new_protected:Npn \__osgstyler_decoration_named_instance:n #1 { \group_begin: \tl_set:NV \l__osgstyler_decoration_instance_tl \l_keys_key_str \exp_args:Ne \keys_set:nn { osgstyler / decoration / \l__osgstyler_decoration_family_tl } {#1} \group_end: } \clist_map_inline:Nn \c__osgstyler_decoration_families_clist { \keys_define:nn { osgstyler / decoration / #1 } { unknown .code:n = \__osgstyler_decoration_named_instance:n {##1} } } \cs_new_protected:Npn \__osgstyler_decoration_component:nn #1#2 { \tl_set:Nn \l__osgstyler_decoration_family_tl {#1} \tl_set:Nn \l__osgstyler_decoration_instance_tl { default } \prop_gput:Nee \g__osgstyler_decoration_prop { \l__osgstyler_decoration_name_tl / #1 / present } { true } \keys_set:nn { osgstyler / decoration / #1 } {#2} } \keys_define:nn { osgstyler / decoration } { line .code:n = \__osgstyler_decoration_component:nn { line }{#1}, background .code:n = \__osgstyler_decoration_component:nn { background }{#1}, enclosure .code:n = \__osgstyler_decoration_component:nn { enclosure }{#1}, margin .code:n = \__osgstyler_decoration_component:nn { margin }{#1}, affix .code:n = \__osgstyler_decoration_component:nn { affix }{#1} } \cs_new_protected:Npn \__osgstyler_decoration_clear:n #1 { \clist_map_inline:Nn \c__osgstyler_decoration_families_clist { \prop_gremove:Nn \g__osgstyler_decoration_prop { #1 / ##1 / present } \prop_get:NnNT \g__osgstyler_decoration_prop { #1 / ##1 / instances } \l_tmpa_tl { \clist_map_inline:Vn \l_tmpa_tl { \prop_gremove:Nn \g__osgstyler_decoration_prop { #1 / ##1 / ####1 / present } \clist_map_inline:cn { c__osgstyler_decoration_##1_properties_clist } { \prop_gremove:Nn \g__osgstyler_decoration_prop { #1 / ##1 / ####1 / ########1 } } } } \prop_gremove:Nn \g__osgstyler_decoration_prop { #1 / ##1 / instances } } } \cs_new_protected:Npn \__osgstyler_decoration_declare:nn #1#2 { \__osgstyler_decoration_clear:n {#1} \tl_set:Nn \l__osgstyler_decoration_name_tl {#1} \keys_set:nn { osgstyler / decoration } {#2} \prop_gput:Nnn \g__osgstyler_decoration_prop { #1 / declared } { true } } \NewDocumentCommand \DeclareOsgDecoration { m m } { \__osgstyler_decoration_declare:nn {#1}{#2} } \NewExpandableDocumentCommand \OsgDecorationValue { m m m } { \prop_item:Nn \g__osgstyler_decoration_prop { #1 / #2 / default / #3 } } \NewExpandableDocumentCommand \OsgDecorationInstanceValue { m m m m } { \prop_item:Nn \g__osgstyler_decoration_prop { #1 / #2 / #3 / #4 } } \NewExpandableDocumentCommand \OsgDecorationInstances { m m } { \prop_item:Nn \g__osgstyler_decoration_prop { #1 / #2 / instances } } \prg_new_conditional:Npnn \__osgstyler_decoration_component_if:nn #1#2 { T, F, TF } { \clist_if_in:NnTF \c__osgstyler_decoration_families_clist {#2} { \prop_if_in:NnTF \g__osgstyler_decoration_prop { #1 / #2 / present } { \prg_return_true: } { \prg_return_false: } } { \msg_error:nnn { osgstyler } { unknown-decoration-family } {#2} \prg_return_false: } } \NewDocumentCommand \OsgDecorationIfHasComponentTF { m m m m } { \__osgstyler_decoration_component_if:nnTF {#1}{#2}{#3}{#4} } \NewDocumentCommand \OsgDecorationIfHasInstanceTF { m m m m m } { \prop_if_in:NnTF \g__osgstyler_decoration_prop { #1 / #2 / #3 / present } {#4}{#5} } \NewExpandableDocumentCommand \OsgDecorationFamilies { } { \clist_use:Nn \c__osgstyler_decoration_families_clist { , } } % Inline styles are the first composition layer. The template owns visual % resource selection and passes semantic and decorative work to replaceable % processors. Their default implementations are transparent. \tl_new:N \l__osgstyler_inline_font_tl \tl_new:N \l__osgstyler_inline_color_tl \tl_new:N \l__osgstyler_inline_decoration_tl \tl_new:N \l__osgstyler_inline_semantics_tl \tl_new:N \l__osgstyler_inline_weight_tl \tl_new:N \l__osgstyler_inline_shape_tl \tl_new:N \l__osgstyler_inline_command_socket_tl \prop_new:N \g__osgstyler_inline_command_socket_prop \prop_new:N \g__osgstyler_inline_command_plug_prop \msg_new:nnnn { osgstyler } { unknown-decoration-for-style } { Inline~style~uses~unknown~decoration~'#1'. } { Declare~the~decoration~before~using~the~inline~style. } \msg_new:nnnn { osgstyler } { unknown-inline-weight } { Unknown~inline~font~weight~'#1'. } { Use~inherit,~regular,~or~bold. } \msg_new:nnnn { osgstyler } { unknown-inline-shape } { Unknown~inline~font~shape~'#1'. } { Use~inherit,~upright,~italic,~slanted,~or~small-caps. } \msg_new:nnnn { osgstyler } { unbound-inline-command } { Command~'#1'~is~not~managed~by~osgstyler. } { Bind~it~with~\token_to_str:N\BindOsgInlineCommand~first. } % A theme author needs to know whether decorations actually render (so it % can offer or withhold a highlight-based visual variant); an end user % never sees a processor and shouldn't need to. That split is why this % conditional exists next to a plain error message rather than one % mechanism serving both audiences. \bool_new:N \g__osgstyler_decoration_backend_bool \NewDocumentCommand \OsgDecorationProcessorAvailableTF { m m } { \bool_if:NTF \g__osgstyler_decoration_backend_bool {#1}{#2} } \msg_new:nnnn { osgstyler } { decoration-requires-lua } { Feature~'#1'~requires~LuaLaTeX. } { Compile~with~LuaLaTeX,~or~use~osgstyler~without~this~feature. } \msg_new:nnnn { osgstyler } { unresolved-symbol } { Affix~symbol~'#1'~has~no~symbol~backend. } { Use~literal~content~until~a~symbol~set~is~active. } \msg_new:nnnn { osgstyler } { unsupported-radius } { Inline~enclosure~radius~'#1'~is~not~supported. } { The~active~decoration~backend~uses~square~corners. } % Line and background decorations only render safely across a line break % via engine-level (LuaTeX) node processing; the default processor below % therefore renders enclosure and affix material -- which need no such % support -- and raises a plain, engine-focused error for the rest instead % of silently producing nothing. A real backend (loaded separately, e.g. % osgstyler-lua) replaces this default via \cs{SetOsgDecorationProcessor} % before either family is ever requested. \tl_new:N \l__osgstyler_decoration_content_tl \tl_new:N \l__osgstyler_decoration_before_tl \tl_new:N \l__osgstyler_decoration_after_tl \tl_new:N \l__osgstyler_decoration_value_tl \tl_new:N \l__osgstyler_decoration_gap_tl \tl_new:N \l__osgstyler_decoration_color_tl \tl_new:N \l__osgstyler_decoration_instances_tl \seq_new:N \l__osgstyler_decoration_metric_seq \cs_new_protected:Npn \__osgstyler_decoration_value:nnnnN #1#2#3#4#5 { \tl_set:Nx #5 { \OsgDecorationInstanceValue {#1}{#2}{#3}{#4} } } \cs_new_protected:Npn \__osgstyler_decoration_default:Nn #1#2 { \tl_if_empty:NT #1 { \tl_set:Nn #1 {#2} } } \cs_new_protected:Npn \__osgstyler_decoration_metric_resolve:nN #1#2 { \seq_set_split:Nnn \l__osgstyler_decoration_metric_seq {/}{#1} \int_compare:nNnTF { \seq_count:N \l__osgstyler_decoration_metric_seq } = { 2 } { \tl_set:Nx #2 { \OsgMetric { \seq_item:Nn \l__osgstyler_decoration_metric_seq {1} } { \seq_item:Nn \l__osgstyler_decoration_metric_seq {2} } } } { \tl_set:Nn #2 {#1} } } \cs_new_protected:Npn \__osgstyler_decoration_affix_material:nnn #1#2#3 { \__osgstyler_decoration_value:nnnnN {#1}{affix}{#2}{gap} \l__osgstyler_decoration_gap_tl \__osgstyler_decoration_default:Nn \l__osgstyler_decoration_gap_tl { 0pt } \exp_args:NV \__osgstyler_decoration_metric_resolve:nN \l__osgstyler_decoration_gap_tl \l__osgstyler_decoration_gap_tl \str_if_eq:nnT {#3}{after} { \hspace{\l__osgstyler_decoration_gap_tl} } \group_begin: \__osgstyler_decoration_value:nnnnN {#1}{affix}{#2}{font} \l__osgstyler_decoration_value_tl \tl_if_empty:NF \l__osgstyler_decoration_value_tl { \exp_args:NV \UseOsgFont \l__osgstyler_decoration_value_tl } \__osgstyler_decoration_value:nnnnN {#1}{affix}{#2}{color} \l__osgstyler_decoration_color_tl \tl_if_empty:NF \l__osgstyler_decoration_color_tl { \tl_set:Nx \l__osgstyler_decoration_color_tl { \OsgColorName { \l__osgstyler_decoration_color_tl } } \color{\l__osgstyler_decoration_color_tl} } \__osgstyler_decoration_value:nnnnN {#1}{affix}{#2}{content} \l__osgstyler_decoration_value_tl \tl_if_empty:NTF \l__osgstyler_decoration_value_tl { \__osgstyler_decoration_value:nnnnN {#1}{affix}{#2}{symbol} \l__osgstyler_decoration_value_tl \tl_if_empty:NF \l__osgstyler_decoration_value_tl { \msg_warning:nne { osgstyler } { unresolved-symbol } \l__osgstyler_decoration_value_tl } } { \tl_use:N \l__osgstyler_decoration_value_tl } \group_end: \str_if_eq:nnT {#3}{before} { \hspace{\l__osgstyler_decoration_gap_tl} } } \cs_new_protected:Npn \__osgstyler_decoration_affixes_apply:nn #1#2 { \tl_clear:N \l__osgstyler_decoration_before_tl \tl_clear:N \l__osgstyler_decoration_after_tl \tl_set:Nx \l__osgstyler_decoration_instances_tl { \OsgDecorationInstances {#1}{affix} } \clist_map_inline:Vn \l__osgstyler_decoration_instances_tl { \__osgstyler_decoration_value:nnnnN {#1}{affix}{##1}{position} \l__osgstyler_decoration_value_tl \str_if_eq:VnTF \l__osgstyler_decoration_value_tl { after } { \tl_put_right:Nn \l__osgstyler_decoration_after_tl { \__osgstyler_decoration_affix_material:nnn {#1}{##1}{after} } } { \tl_put_right:Nn \l__osgstyler_decoration_before_tl { \__osgstyler_decoration_affix_material:nnn {#1}{##1}{before} } } } \tl_set:Nx \l__osgstyler_decoration_content_tl { \exp_not:V \l__osgstyler_decoration_before_tl \exp_not:n {#2} \exp_not:V \l__osgstyler_decoration_after_tl } } \cs_new_protected:Npn \__osgstyler_decoration_enclosure_apply:nnn #1#2#3 { \group_begin: \__osgstyler_decoration_value:nnnnN {#1}{enclosure}{#2}{border-color} \l__osgstyler_decoration_color_tl \__osgstyler_decoration_default:Nn \l__osgstyler_decoration_color_tl { primary } \tl_set:Nx \l__osgstyler_decoration_color_tl { \OsgColorName { \l__osgstyler_decoration_color_tl } } \__osgstyler_decoration_value:nnnnN {#1}{enclosure}{#2}{fill-color} \l__osgstyler_decoration_value_tl \__osgstyler_decoration_default:Nn \l__osgstyler_decoration_value_tl { background } \tl_set:Nx \l_tmpa_tl { \OsgColorName { \l__osgstyler_decoration_value_tl } } \__osgstyler_decoration_value:nnnnN {#1}{enclosure}{#2}{thickness} \l__osgstyler_decoration_value_tl \__osgstyler_decoration_default:Nn \l__osgstyler_decoration_value_tl { rule/thin } \exp_args:NV \__osgstyler_decoration_metric_resolve:nN \l__osgstyler_decoration_value_tl \l__osgstyler_decoration_value_tl \setlength\fboxrule{\l__osgstyler_decoration_value_tl} \__osgstyler_decoration_value:nnnnN {#1}{enclosure}{#2}{padding} \l__osgstyler_decoration_value_tl \__osgstyler_decoration_default:Nn \l__osgstyler_decoration_value_tl { padding/normal } \exp_args:NV \__osgstyler_decoration_metric_resolve:nN \l__osgstyler_decoration_value_tl \l__osgstyler_decoration_value_tl \setlength\fboxsep{\l__osgstyler_decoration_value_tl} \__osgstyler_decoration_value:nnnnN {#1}{enclosure}{#2}{radius} \l__osgstyler_decoration_value_tl \tl_if_empty:NF \l__osgstyler_decoration_value_tl { \str_if_eq:VnF \l__osgstyler_decoration_value_tl { 0pt } { \msg_warning:nne { osgstyler } { unsupported-radius } \l__osgstyler_decoration_value_tl } } \fcolorbox{\l__osgstyler_decoration_color_tl}{\l_tmpa_tl}{#3} \group_end: } \cs_new_protected:Npn \__osgstyler_decoration_processor_default:nn #1#2 { \group_begin: \OsgDecorationIfHasComponentTF {#1}{background} { \msg_error:nnn { osgstyler } { decoration-requires-lua } {#1} } { } \OsgDecorationIfHasComponentTF {#1}{line} { \msg_error:nnn { osgstyler } { decoration-requires-lua } {#1} } { } \__osgstyler_decoration_affixes_apply:nn {#1}{#2} \OsgDecorationIfHasComponentTF {#1}{enclosure} { \tl_set:Nx \l__osgstyler_decoration_instances_tl { \OsgDecorationInstances {#1}{enclosure} } \clist_map_inline:Vn \l__osgstyler_decoration_instances_tl { \tl_set:Nx \l__osgstyler_decoration_content_tl { \__osgstyler_decoration_enclosure_apply:nnn {#1}{##1} { \exp_not:V \l__osgstyler_decoration_content_tl } } } } { } \tl_use:N \l__osgstyler_decoration_content_tl \group_end: } \cs_new_protected:Npn \__osgstyler_semantic_processor_default:nn #1#2 {#2} % Decoration and semantic processors are each stored as a sequence, % not a single macro, so that a later \AddOsg...Processor can extend % \SetOsg...Processor (which resets the sequence to one entry, so % today's replace-the-single-processor behavior is unchanged) without % breaking the storage format. Applying the sequence folds it left to % right: each processor wraps the previous one's output, so the % first-added processor sees the original content and the % last-added one sees the result of all earlier processors. \seq_new:N \g__osgstyler_decoration_processor_seq \seq_gput_right:Nn \g__osgstyler_decoration_processor_seq { \__osgstyler_decoration_processor_default:nn } \seq_new:N \g__osgstyler_semantic_processor_seq \seq_gput_right:Nn \g__osgstyler_semantic_processor_seq { \__osgstyler_semantic_processor_default:nn } \tl_new:N \l__osgstyler_processor_content_tl \cs_new_protected:Npn \__osgstyler_processor_chain_apply:Nnn #1#2#3 { \group_begin: \tl_set:Nn \l__osgstyler_processor_content_tl {#3} \seq_map_inline:Nn #1 { \tl_set:Nx \l__osgstyler_processor_content_tl { ##1 {#2} { \exp_not:o {\l__osgstyler_processor_content_tl} } } } \tl_use:N \l__osgstyler_processor_content_tl \group_end: } \cs_new_protected:Npn \__osgstyler_decoration_processor:nn #1#2 { \__osgstyler_processor_chain_apply:Nnn \g__osgstyler_decoration_processor_seq {#1}{#2} } \cs_new_protected:Npn \__osgstyler_semantic_processor:nn #1#2 { \__osgstyler_processor_chain_apply:Nnn \g__osgstyler_semantic_processor_seq {#1}{#2} } \NewDocumentCommand \SetOsgDecorationProcessor { m } { \seq_gclear:N \g__osgstyler_decoration_processor_seq \seq_gput_right:Nn \g__osgstyler_decoration_processor_seq {#1} } \NewDocumentCommand \SetOsgSemanticProcessor { m } { \seq_gclear:N \g__osgstyler_semantic_processor_seq \seq_gput_right:Nn \g__osgstyler_semantic_processor_seq {#1} } \cs_new_protected:Npn \__osgstyler_inline_visual_apply:n #1 { \group_begin: \tl_if_eq:VnF \l__osgstyler_inline_font_tl { none } { \exp_args:NV \UseOsgFont \l__osgstyler_inline_font_tl } \str_case:VnF \l__osgstyler_inline_weight_tl { { inherit } { } { regular } { \mdseries } { bold } { \bfseries } } { \msg_error:nne { osgstyler } { unknown-inline-weight } { \l__osgstyler_inline_weight_tl } } \str_case:VnF \l__osgstyler_inline_shape_tl { { inherit } { } { upright } { \upshape } { italic } { \itshape } { slanted } { \slshape } { small-caps } { \scshape } } { \msg_error:nne { osgstyler } { unknown-inline-shape } { \l__osgstyler_inline_shape_tl } } \tl_if_eq:VnF \l__osgstyler_inline_color_tl { none } { \exp_args:NNV \clist_if_in:NnTF \c__osgstyler_color_slots_clist \l__osgstyler_inline_color_tl { \use:e { \exp_not:N \color { \OsgColorName { \l__osgstyler_inline_color_tl } } } } { \msg_error:nne { osgstyler } { unknown-color-slot } \l__osgstyler_inline_color_tl } } \tl_if_eq:VnTF \l__osgstyler_inline_decoration_tl { none } {#1} { \prop_if_in:NeTF \g__osgstyler_decoration_prop { \l__osgstyler_inline_decoration_tl / declared } { \exp_args:NV \__osgstyler_decoration_processor:nn \l__osgstyler_inline_decoration_tl {#1} } { \msg_error:nne { osgstyler } { unknown-decoration-for-style } \l__osgstyler_inline_decoration_tl #1 } } \group_end: } \cs_new_protected:Npn \__osgstyler_inline_apply:n #1 { \tl_if_eq:VnTF \l__osgstyler_inline_semantics_tl { none } { \__osgstyler_inline_visual_apply:n {#1} } { \exp_args:NV \__osgstyler_semantic_processor:nn \l__osgstyler_inline_semantics_tl { \__osgstyler_inline_visual_apply:n {#1} } } } \NewTemplateType { osgstyler-inline } { 1 } \DeclareTemplateInterface { osgstyler-inline } { standard } { 1 } { font : tokenlist = main, weight : tokenlist = inherit, shape : tokenlist = inherit, color : tokenlist = text, decoration : tokenlist = none, semantics : tokenlist = none } \DeclareTemplateCode { osgstyler-inline } { standard } { 1 } { font = \l__osgstyler_inline_font_tl, weight = \l__osgstyler_inline_weight_tl, shape = \l__osgstyler_inline_shape_tl, color = \l__osgstyler_inline_color_tl, decoration = \l__osgstyler_inline_decoration_tl, semantics = \l__osgstyler_inline_semantics_tl } { \__osgstyler_inline_apply:n {#1} } \NewDocumentCommand \DeclareOsgInlineStyle { m m } { \DeclareInstance { osgstyler-inline } {#1} { standard } {#2} } \NewDocumentCommand \UseOsgInlineStyle { m m } { \UseInstance { osgstyler-inline } {#1} {#2} } \NewDocumentCommand \EditOsgInlineStyle { m m } { \EditInstance { osgstyler-inline } {#1} {#2} } \prop_new:N \g__osgstyler_mode_aware_command_prop \cs_new_protected:Npn \__osgstyler_make_mode_aware:Nn #1#2 { \cs_if_exist:NT \MakeOverlayAwareCommand { \prop_if_in:NeF \g__osgstyler_mode_aware_command_prop { \cs_to_str:N #1 } { \MakeOverlayAwareCommand [ arguments = {#2}, fallback = flatten ] #1 \prop_gput:Nee \g__osgstyler_mode_aware_command_prop { \cs_to_str:N #1 } { true } } } } \cs_new_protected:Npn \__osgstyler_inline_command_define_existing:Nn #1#2 { \RenewDocumentCommand #1 { +m } { \UseSocket {#2} {##1} } } \cs_new_protected:Npn \__osgstyler_inline_command_define_new:Nn #1#2 { \NewDocumentCommand #1 { +m } { \UseSocket {#2} {##1} } } \cs_new_protected:Npn \__osgstyler_inline_command_socket_new:N #1 { \tl_set:Nx \l__osgstyler_inline_command_socket_tl { osgstyler/inline-command/\cs_to_str:N #1 } \NewSocket { \l__osgstyler_inline_command_socket_tl } { 1 } \cs_if_exist:NTF #1 { \exp_args:Nc \NewCommandCopy { __osgstyler_inline_original_\cs_to_str:N #1:n } #1 \NewSocketPlug { \l__osgstyler_inline_command_socket_tl } { original } { \use:c { __osgstyler_inline_original_\cs_to_str:N #1:n } {##1} } \NewSocketPlug { \l__osgstyler_inline_command_socket_tl } { default } { \use:c { __osgstyler_inline_original_\cs_to_str:N #1:n } {##1} } \prop_gput:Nee \g__osgstyler_inline_command_plug_prop { \l__osgstyler_inline_command_socket_tl / original } { true } \exp_args:NNV \__osgstyler_inline_command_define_existing:Nn #1 \l__osgstyler_inline_command_socket_tl } { \NewSocketPlug { \l__osgstyler_inline_command_socket_tl } { default } {##1} \exp_args:NNV \__osgstyler_inline_command_define_new:Nn #1 \l__osgstyler_inline_command_socket_tl } \prop_gput:Nee \g__osgstyler_inline_command_socket_prop { \cs_to_str:N #1 } { \l__osgstyler_inline_command_socket_tl } } \cs_new_protected:Npn \__osgstyler_inline_command_bind:Nn #1#2 { \prop_get:NeNTF \g__osgstyler_inline_command_socket_prop { \cs_to_str:N #1 } \l__osgstyler_inline_command_socket_tl { } { \__osgstyler_inline_command_socket_new:N #1 } \prop_if_in:NeF \g__osgstyler_inline_command_plug_prop { \l__osgstyler_inline_command_socket_tl / #2 } { \NewSocketPlug { \l__osgstyler_inline_command_socket_tl } { style/#2 } { \UseOsgInlineStyle {#2} {##1} } \prop_gput:Nee \g__osgstyler_inline_command_plug_prop { \l__osgstyler_inline_command_socket_tl / #2 } { true } } \AssignSocketPlug { \l__osgstyler_inline_command_socket_tl } { style/#2 } \__osgstyler_make_mode_aware:Nn #1 { +m } % Bound inline commands have exactly one content argument and are therefore % transparent to presitemize's initial-case transformation. The package % hook also covers the load order osgstyler -> presitemize. \cs_if_exist:NTF \DeclarePresInitialTransparentCommand { \DeclarePresInitialTransparentCommand #1 } { \AddToHook { package/osglecture-presitemize/after } { \DeclarePresInitialTransparentCommand #1 } } } % The binding assumes a single, long (+m) argument; this covers the % common case (\emph, \textbf, and similar) but is not a general % command-signature adapter. A future `content=` selector is % reserved for commands with more than one argument, styling argument % n and passing the rest through unchanged (e.g. \href{url}{text}). % `content=0` is reserved within that scheme for styling a command's % fully expanded result rather than one of its arguments (e.g. % \ref{label}), which needs box capture or expansion capture instead % of argument wrapping and so is a separate extension, not a variant % of the content=n case. \NewDocumentCommand \BindOsgInlineCommand { m m } { \__osgstyler_inline_command_bind:Nn #1 {#2} } \NewDocumentCommand \RestoreOsgInlineCommand { m } { \prop_get:NeNTF \g__osgstyler_inline_command_socket_prop { \cs_to_str:N #1 } \l__osgstyler_inline_command_socket_tl { \AssignSocketPlug { \l__osgstyler_inline_command_socket_tl } { default } } { \msg_error:nne { osgstyler } { unbound-inline-command } { \token_to_str:N #1 } } } \cs_new_protected:Npn \__osgstyler_inline_command_default:N #1 { \prop_get:NeNTF \g__osgstyler_inline_command_socket_prop { \cs_to_str:N #1 } \l__osgstyler_inline_command_socket_tl { } { \__osgstyler_inline_command_socket_new:N #1 } \AssignSocketPlug { \l__osgstyler_inline_command_socket_tl } { default } } \DeclareOsgDecoration { osgstyler-underline } { line = { default = { position=below, pattern=solid, color=text, thickness=rule/thin, offset=.1ex } } } \DeclareOsgDecoration { osgstyler-highlight } { background = { color=accent, opacity=1, shape=text } } \DeclareOsgDecoration { osgstyler-frame } { enclosure = { border-color=primary, fill-color=background, pattern=solid, thickness=rule/thin, radius=0pt, padding=padding/normal } } \DeclareOsgInlineStyle { emphasis } { font=none, weight=inherit, shape=italic, color=none, decoration=none, semantics=emphasis } \DeclareOsgInlineStyle { strong } { font=none, weight=bold, shape=inherit, color=none, decoration=none, semantics=strong } \DeclareOsgInlineStyle { underline } { font=none, color=none, decoration=osgstyler-underline, semantics=none } \DeclareOsgInlineStyle { highlight } { font=none, color=none, decoration=osgstyler-highlight, semantics=none } \DeclareOsgInlineStyle { frame } { font=none, color=none, decoration=osgstyler-frame, semantics=none } \NewDocumentCommand \UseOsgStandardInlineCommands { } { \BindOsgInlineCommand \emph { emphasis } \BindOsgInlineCommand \textbf { strong } \BindOsgInlineCommand \uline { underline } \BindOsgInlineCommand \highlight { highlight } \BindOsgInlineCommand \fbox { frame } } \NewDocumentCommand \UseOsgDefaultInlineCommands { } { \__osgstyler_inline_command_default:N \emph \__osgstyler_inline_command_default:N \textbf \__osgstyler_inline_command_default:N \uline \__osgstyler_inline_command_default:N \highlight \__osgstyler_inline_command_default:N \fbox } \BindOsgInlineCommand \highlight { highlight } % ========================================== % BOX TEMPLATE: STYLED BLOCKS % ========================================== % % \ldeen*{Ein gemeinsamer Template-Typ für kastenartige, umrandete oder % hinterlegte Inhalte -- Beamer-Blöcke ebenso wie theorem-artige Boxen % oder Zitate in der Langform. Paket- und Themenautoren binden entweder % eine bereits bekannte Umgebung an eine Instanz (die Render-Logik % umhüllt dann nur optisch, die Umgebung bringt z.\,B. ihre eigene % Nummerierung selbst mit) oder deklarieren über % \cs{NewOsgStyledEnvironment} eine neue -- die Render-Logik existiert % in beiden Fällen nur einmal. Farbwerte werden stets über % \cs{OsgColorName} aufgelöst statt als bloße Slot-Namen verwendet, da % nur die aufgelösten Namen unabhängig von einer ladenden Theme-Datei % wie \pkg*{lttheme} garantiert existieren.} % {A shared template type for boxed, framed or filled content -- beamer % blocks as well as theorem-like boxes or quotations in the long form. % Package and theme authors either bind an already-known environment to % an instance (the rendering then only wraps it visually; the % environment brings its own numbering, if any) or declare a new one via % \cs{NewOsgStyledEnvironment} -- the rendering logic exists only once % either way. Color values are always resolved via \cs{OsgColorName} % rather than used as bare slot names, since only the resolved names are % guaranteed to exist independently of a loading theme file such as % \pkg*{lttheme}.} \tl_new:N \l__osgstyler_block_background_tl \tl_new:N \l__osgstyler_block_rule_color_tl \tl_new:N \l__osgstyler_block_before_tl \tl_new:N \l__osgstyler_block_after_tl \tl_new:N \l__osgstyler_block_layout_tl \tl_new:N \l__osgstyler_block_head_bg_tl \tl_new:N \l__osgstyler_block_head_fg_tl \tl_new:N \l__osgstyler_block_head_font_tl \tl_new:N \l__osgstyler_block_head_rule_color_tl \tl_new:N \l__osgstyler_block_foot_rule_color_tl \tl_new:N \l__osgstyler_block_body_bg_tl \dim_new:N \l__osgstyler_block_rule_dim \dim_new:N \l__osgstyler_block_padding_dim \dim_new:N \l__osgstyler_block_padding_top_dim \dim_new:N \l__osgstyler_block_padding_right_dim \dim_new:N \l__osgstyler_block_padding_bottom_dim \dim_new:N \l__osgstyler_block_padding_left_dim \tl_new:N \l__osgstyler_block_shadow_color_tl \dim_new:N \l__osgstyler_block_corner_radius_dim \dim_new:N \l__osgstyler_block_shadow_offset_dim \dim_new:N \l__osgstyler_block_head_padding_dim \dim_new:N \l__osgstyler_block_head_padding_top_dim \dim_new:N \l__osgstyler_block_head_padding_right_dim \dim_new:N \l__osgstyler_block_head_padding_bottom_dim \dim_new:N \l__osgstyler_block_head_padding_left_dim \dim_new:N \l__osgstyler_block_head_rule_dim \dim_new:N \l__osgstyler_block_foot_rule_dim \dim_new:N \l__osgstyler_block_body_padding_dim \dim_new:N \l__osgstyler_block_body_padding_top_dim \dim_new:N \l__osgstyler_block_body_padding_right_dim \dim_new:N \l__osgstyler_block_body_padding_bottom_dim \dim_new:N \l__osgstyler_block_body_padding_left_dim \bool_new:N \l__osgstyler_block_shadow_bool % \ldeen*{Der Titel wird nicht als Template-Schlüssel geführt (er wechselt % je Verwendung, nicht je Instanz), sondern vor \cs{UseInstance} in diese % Variable gelegt -- über \cs{__osgstyler_block_use:nnn}, damit keine % Aufrufstelle sie zu setzen vergisst. Im Layout \code{plain} wird er % schlicht dem Body vorangestellt (verhält sich wie das frühere % \code{Titel\cs{par}Body}); im Layout \code{titled} bekommt er einen % eigenen, gestalteten Kopf.} % {The title is not a template key (it changes per use, not per instance); % callers place it into this variable before \cs{UseInstance}, through % \cs{__osgstyler_block_use:nnn} so no call site forgets it. In layout % \code{plain} it is simply prepended to the body (behaving like the % former \code{title\cs{par}body}); in layout \code{titled} it gets its % own styled head.} \tl_new:N \l__osgstyler_block_title_tl \NewTemplateType { osgstyler-block } { 1 } \DeclareTemplateInterface { osgstyler-block } { standard } { 1 } { background : tokenlist = none, rule-color : tokenlist = structure, rule-thickness: length = 0pt, padding : length = 0pt, padding-top : length = -1pt, padding-right : length = -1pt, padding-bottom: length = -1pt, padding-left : length = -1pt, corner-radius : length = 0pt, shadow : boolean = false, shadow-offset : length = 2pt, shadow-color : tokenlist = structure!30, before : tokenlist =, after : tokenlist =, layout : choice { plain, titled } = plain, head-background : tokenlist = structure, head-color : tokenlist = background, head-font : tokenlist = \normalfont \bfseries, head-padding : length = 0.35em, head-padding-top : length = -1pt, head-padding-right : length = -1pt, head-padding-bottom: length = -1pt, head-padding-left : length = -1pt, head-rule-color : tokenlist = structure, head-rule-thickness: length = 0pt, foot-rule-color : tokenlist = structure, foot-rule-thickness: length = 0pt, body-background : tokenlist = none, body-padding : length = 0.4em, body-padding-top : length = -1pt, body-padding-right : length = -1pt, body-padding-bottom: length = -1pt, body-padding-left : length = -1pt } \DeclareTemplateCode { osgstyler-block } { standard } { 1 } { background = \l__osgstyler_block_background_tl, rule-color = \l__osgstyler_block_rule_color_tl, rule-thickness = \l__osgstyler_block_rule_dim, padding = \l__osgstyler_block_padding_dim, padding-top = \l__osgstyler_block_padding_top_dim, padding-right = \l__osgstyler_block_padding_right_dim, padding-bottom = \l__osgstyler_block_padding_bottom_dim, padding-left = \l__osgstyler_block_padding_left_dim, corner-radius = \l__osgstyler_block_corner_radius_dim, shadow = \l__osgstyler_block_shadow_bool, shadow-offset = \l__osgstyler_block_shadow_offset_dim, shadow-color = \l__osgstyler_block_shadow_color_tl, before = \l__osgstyler_block_before_tl, after = \l__osgstyler_block_after_tl, layout = { plain = \tl_set:Nn \l__osgstyler_block_layout_tl { plain }, titled = \tl_set:Nn \l__osgstyler_block_layout_tl { titled } }, head-background = \l__osgstyler_block_head_bg_tl, head-color = \l__osgstyler_block_head_fg_tl, head-font = \l__osgstyler_block_head_font_tl, head-padding = \l__osgstyler_block_head_padding_dim, head-padding-top = \l__osgstyler_block_head_padding_top_dim, head-padding-right = \l__osgstyler_block_head_padding_right_dim, head-padding-bottom = \l__osgstyler_block_head_padding_bottom_dim, head-padding-left = \l__osgstyler_block_head_padding_left_dim, head-rule-color = \l__osgstyler_block_head_rule_color_tl, head-rule-thickness = \l__osgstyler_block_head_rule_dim, foot-rule-color = \l__osgstyler_block_foot_rule_color_tl, foot-rule-thickness = \l__osgstyler_block_foot_rule_dim, body-background = \l__osgstyler_block_body_bg_tl, body-padding = \l__osgstyler_block_body_padding_dim, body-padding-top = \l__osgstyler_block_body_padding_top_dim, body-padding-right = \l__osgstyler_block_body_padding_right_dim, body-padding-bottom = \l__osgstyler_block_body_padding_bottom_dim, body-padding-left = \l__osgstyler_block_body_padding_left_dim } { \__osgstyler_block_padding_resolve: \__osgstyler_block_typeset:n {#1} } \cs_new_protected:Npn \__osgstyler_block_padding_fallback:NN #1#2 { \dim_compare:nNnT {#1} < { 0pt } { \dim_set_eq:NN #1#2 } } \cs_new_protected:Npn \__osgstyler_block_padding_resolve: { \clist_map_inline:nn { top,right,bottom,left } { \exp_args:Nc \__osgstyler_block_padding_fallback:NN { l__osgstyler_block_padding_##1_dim } \l__osgstyler_block_padding_dim \exp_args:Nc \__osgstyler_block_padding_fallback:NN { l__osgstyler_block_head_padding_##1_dim } \l__osgstyler_block_head_padding_dim \exp_args:Nc \__osgstyler_block_padding_fallback:NN { l__osgstyler_block_body_padding_##1_dim } \l__osgstyler_block_body_padding_dim } } % \ldeen*{Einzige Aufrufkonvention für den Block-Template-Typ: Titel und % Body getrennt übergeben. Aufrufstellen ohne Titel geben \code{\{\}}.} % {The one calling convention for the block template type: pass title and % body separately. Call sites without a title pass \code{\{\}}.} \cs_new_protected:Npn \__osgstyler_block_use:nnn #1#2#3 { \tl_set:Nn \l__osgstyler_block_title_tl {#2} \UseInstance { osgstyler-block } {#1} {#3} } % \ldeen*{Muss expandierbar sein: der Wert landet als Farbargument in % \cs{colorbox}/\cs{fcolorbox}, die ihr Argument expandieren.} % {Must stay expandable: the value ends up as the colour argument of % \cs{colorbox}/\cs{fcolorbox}, which expand their argument.} \cs_new:Npn \__osgstyler_block_bg:n #1 { \str_if_eq:nnTF {#1} { none } { \OsgColorName{background} } { \OsgColorName{#1} } } \cs_generate_variant:Nn \__osgstyler_block_bg:n { V } % \ldeen*{Kastenhintergrund + Rahmen. Ohne \code{corner-radius} und % \code{shadow} genügt \cs{fcolorbox} (\cs{fboxsep} ist bei den Aufrufern % Null). Sind Ecken oder Schatten gewünscht, zeichnet \pkg{pgfcore} die % Form: der zu umrahmende Kasten wird vermessen, Schatten/Fläche/Rand % laufen als \code{artifact}-\cs{rlap} dahinter, der Kasten selbst behält % seine Struktur. \pkg{pgfcore} lädt das Theme (z.\,B.\ \pkg*{magpie}); % fehlt es, fällt der Renderer auf eckig ohne Schatten zurück.} % {Box background + frame. Without \code{corner-radius} and \code{shadow} % an \cs{fcolorbox} is enough (\cs{fboxsep} is zero at the call sites). % When corners or a shadow are wanted, \pkg{pgfcore} draws the shape: the % box to be framed is measured, shadow/fill/border run behind it as an % \code{artifact} \cs{rlap}, and the box keeps its own structure. The theme % loads \pkg{pgfcore} (e.g.\ \pkg*{magpie}); without it the renderer falls % back to square with no shadow.} \box_new:N \l__osgstyler_block_content_box \msg_new:nnn { osgstyler } { no-pgfcore } { Rounded~corners~and~shadows~on~blocks~need~\token_to_str:N\usepackage {pgfcore}.~Falling~back~to~a~square~frame. } \bool_new:N \g__osgstyler_block_pgf_warned_bool \cs_new_protected:Npn \__osgstyler_block_fbox:nnn #1#2#3 { \bool_lazy_and:nnTF { \cs_if_exist_p:N \pgfpicture } { \bool_lazy_or_p:nn { \dim_compare_p:nNn \l__osgstyler_block_corner_radius_dim > \c_zero_dim } { \bool_if_p:N \l__osgstyler_block_shadow_bool } } { \__osgstyler_block_fbox_pgf:nnn {#1} {#2} {#3} } { \bool_lazy_and:nnT { ! \cs_if_exist_p:N \pgfpicture } { \bool_lazy_or_p:nn { \dim_compare_p:nNn \l__osgstyler_block_corner_radius_dim > \c_zero_dim } { \bool_if_p:N \l__osgstyler_block_shadow_bool } } { \bool_if:NF \g__osgstyler_block_pgf_warned_bool { \bool_gset_true:N \g__osgstyler_block_pgf_warned_bool \msg_warning:nn { osgstyler } { no-pgfcore } } } \fcolorbox {#1} {#2} {#3} } } % \ldeen{Ein abgerundetes (bzw.\ bei Radius Null eckiges) Rechteck von % \code{(#1,#2)} mit Größe \code{#3}$\times$\code{#4} als aktuellem % pgf-Pfad.}{A rounded (square at radius zero) rectangle from % \code{(#1,#2)} of size \code{#3}$\times$\code{#4} as the current pgf % path.} \cs_new_protected:Npn \__osgstyler_block_rrect:nnnn #1#2#3#4 { \pgfsetcornersarced { \pgfpoint { \l__osgstyler_block_corner_radius_dim } { \l__osgstyler_block_corner_radius_dim } } \pgfpathrectanglecorners { \pgfpoint { \dim_eval:n {#1} } { \dim_eval:n {#2} } } { \pgfpoint { \dim_eval:n { (#1) + (#3) } } { \dim_eval:n { (#2) + (#4) } } } } \cs_new_protected:Npn \__osgstyler_block_fbox_pgf:nnn #1#2#3 { \hbox_set:Nn \l__osgstyler_block_content_box {#3} \hbox:n { \tag_mc_begin:n { artifact } \rlap { \begin { pgfpicture } \pgfsetbaseline { 0pt } \bool_if:NT \l__osgstyler_block_shadow_bool { \pgfsetfillcolor { \OsgColorName { \l__osgstyler_block_shadow_color_tl } } \__osgstyler_block_rrect:nnnn { \l__osgstyler_block_shadow_offset_dim } { -\box_dp:N \l__osgstyler_block_content_box - \l__osgstyler_block_shadow_offset_dim } { \box_wd:N \l__osgstyler_block_content_box } { \box_ht:N \l__osgstyler_block_content_box + \box_dp:N \l__osgstyler_block_content_box } \pgfusepath { fill } } \pgfsetfillcolor { #2 } \pgfsetstrokecolor { #1 } \pgfsetlinewidth { \l__osgstyler_block_rule_dim } \__osgstyler_block_rrect:nnnn { 0pt } { -\box_dp:N \l__osgstyler_block_content_box } { \box_wd:N \l__osgstyler_block_content_box } { \box_ht:N \l__osgstyler_block_content_box + \box_dp:N \l__osgstyler_block_content_box } \dim_compare:nNnTF \l__osgstyler_block_rule_dim > \c_zero_dim { \pgfusepath { fill, stroke } } { \pgfusepath { fill } } \end { pgfpicture } }% \tag_mc_end: \box_use_drop:N \l__osgstyler_block_content_box } } \cs_new_protected:Npn \__osgstyler_block_typeset:n #1 { \group_begin: \l__osgstyler_block_before_tl \str_if_eq:VnTF \l__osgstyler_block_layout_tl { titled } { \__osgstyler_block_typeset_titled:n {#1} } { \__osgstyler_block_typeset_plain:n {#1} } \l__osgstyler_block_after_tl \group_end: } % \ldeen*{\code{plain}: Titel (falls gesetzt) plus Body, ungeteilt -- wie % bisher, samt optionalem Rahmen um alles, wenn \code{rule-thickness} % positiv ist.} % {\code{plain}: title (if set) plus body, undivided -- as before, % including an optional frame around everything when \code{rule-thickness} % is positive.} % \ldeen{\cs{tagpdfparaOff} um die \cs{fcolorbox}: ohne das umhüllt das % Absatz-Tagging der Klasse den Kasten in einem eigenen Absatz, den der % \env{minipage}-Sockel dann um sein \code{Div} herum aufspaltet und % leere \code{P}-Fragmente hinterlässt. Mit ausgeschaltetem Absatz-Tagging % liegt das \code{Div} sauber neben den umgebenden Absätzen. Wirkt nur bei % geladenem \pkg{tagpdf} und wird von der einschließenden Gruppe wieder % zurückgesetzt.}{\cs{tagpdfparaOff} around the \cs{fcolorbox}: without it % the class's paragraph tagging wraps the box in a paragraph of its own, % which the \env{minipage} socket then splits around its \code{Div}, % leaving empty \code{P} fragments. With paragraph tagging off the % \code{Div} sits cleanly beside the surrounding paragraphs. Only matters % when \pkg{tagpdf} is loaded, and the enclosing group restores it.} \cs_new_protected:Npn \__osgstyler_block_box_para_off: { \cs_if_exist_use:N \tagpdfparaOff } % \ldeen{Setzt den Blocktitel im Kopf-Font und in der Kopffarbe (wie beim % \code{titled}-Layout), aber als gewöhnliche erste Absatzzeile.}{Typesets % the block title in the head font and head colour (as in the \code{titled} % layout), but as an ordinary first paragraph line.} \cs_new_protected:Npn \__osgstyler_block_plain_title: { \tl_if_empty:NF \l__osgstyler_block_title_tl { { \color { \OsgColorName{ \l__osgstyler_block_head_fg_tl } } \l__osgstyler_block_head_font_tl \l__osgstyler_block_title_tl \par } \nobreak \skip_vertical:n { 0.3 \baselineskip } } } \cs_new_protected:Npn \__osgstyler_block_typeset_plain:n #1 { \dim_compare:nNnTF { \l__osgstyler_block_rule_dim } > { 0pt } { \par \group_begin: \__osgstyler_block_box_para_off: \addvspace { \medskipamount } \setlength\fboxsep{0pt} \setlength\fboxrule{\l__osgstyler_block_rule_dim} \noindent\fcolorbox { \OsgColorName{\l__osgstyler_block_rule_color_tl} } { \__osgstyler_block_bg:V \l__osgstyler_block_background_tl } { \begin{minipage}{\dim_eval:n {\linewidth-2\fboxrule}} \tex_kern:D \l__osgstyler_block_padding_top_dim \group_begin: \dim_set:Nn \leftskip { \l__osgstyler_block_padding_left_dim } \dim_set:Nn \rightskip { \l__osgstyler_block_padding_right_dim } \__osgstyler_block_plain_title: #1 \par \group_end: \tex_kern:D \l__osgstyler_block_padding_bottom_dim \end{minipage} } \par \group_end: \addvspace { \medskipamount } } { \__osgstyler_block_plain_title: #1 } } % \ldeen*{\code{titled}: farbiger Kopfstreifen mit dem Titel, darunter der % eingerückte Body -- optisch abhebende Blöcke im Stil von Beamers Madrid. % % Zwei Varianten je nach \code{body-background}. Bei \code{none} fließt % alles: der Titel als normale Textzeile mit dem farbigen Streifen als % nullgroßer \cs{rlap}\cs{smash} dahinter, der Body mit % \cs{leftskip}/\cs{rightskip} als Innenabstand. Titel und Body-Absätze % sind dann Geschwister im Frame -- getaggt wie ein Absatz ohne Block, ohne % zusätzliches \code{Div}. Eine getönte Body-Fläche braucht dagegen einen % Kasten: ist \code{body-background} gesetzt, liegt der Block in genau % einer \cs{fcolorbox} um eine \env{minipage}. \cs{tagpdfparaOff} um den % Kasten hält das Absatz-Tagging der Klasse davon ab, ihn in einen eigenen, % um das \code{Div} herum aufgespaltenen Absatz zu hüllen; das \code{Div} % steht damit sauber neben den umgebenden Absätzen (wie bei \code{card}), % mit \code{text-unit}s als Kindern, ohne leere Strukturelemente. Der % Kopfbalken fließt textlos in die MC der Titelzeile. Die Kastenunterkante % markiert das Blockende.} % {\code{titled}: a coloured head strip carrying the title, the indented % body below -- visually distinct blocks in the style of beamer's Madrid. % % Two variants depending on \code{body-background}. With \code{none} % everything flows: the title as a normal text line with the coloured strip % behind it as a zero-size \cs{rlap}\cs{smash}, the body with % \cs{leftskip}/\cs{rightskip} as inner padding. Title and body paragraphs % are then siblings in the frame -- tagged like a paragraph with no block, % no extra \code{Div}. A tinted body area needs a box: when % \code{body-background} is set the block sits in exactly one \cs{fcolorbox} % around a \env{minipage}. \cs{tagpdfparaOff} around the box keeps the % class's paragraph tagging from wrapping it in a paragraph of its own that % would be split around the \code{Div}; the \code{Div} then sits cleanly % beside the surrounding paragraphs (as with \code{card}), with % \code{text-unit} children and no empty structure elements. The head bar % flows textlessly into the title line's MC. The box's bottom edge marks % where the block ends.} % \ldeen{Eine volle Zeilenbreite überspannende Deko-Linie in eigener Zeile, % getaggt als ausgesetzt (nicht Teil der Struktur).}{A full-width % decorative rule on its own line, tagged as suspended (not part of the % structure).} \cs_new_protected:Npn \__osgstyler_block_rule:nn #1#2 { \tag_suspend:n { osgstyler-block-rule } \nobreak \noindent { \color { \OsgColorName{#1} } \rule { \linewidth } {#2} }% \par \tag_resume:n { osgstyler-block-rule } } \cs_new_protected:Npn \__osgstyler_block_typeset_titled:n #1 { \str_if_eq:VnTF \l__osgstyler_block_body_bg_tl { none } { \__osgstyler_block_typeset_titled_flow:n {#1} } { \__osgstyler_block_typeset_titled_box:n {#1} } } % \ldeen{Kopfstreifen und Kopfbalken sind Dekoration und werden als % Artefakt markiert. Damit das die MC der Titelzeile nicht teilt und kein % leeres \code{P}-Fragment hinterlässt, steht das Artefakt \emph{ganz am % Absatzanfang}, unmittelbar nach \cs{noindent} und noch vor jedem % anderen Material der Zeile (auch vor dem unsichtbaren Strut des % Kastenzweigs, der deshalb mit hineingenommen wird): Der vom Absatz-Hook % geöffneten MC ist dann kein einziger Knoten zugeordnet, sie erscheint % im PDF gar nicht, und der Titeltext bildet nach dem Pop wieder genau % eine MC. Ohne offene MC (Absatz-Tagging aus) bleibt der Pop % wirkungslos; ohne geladenes tagpdf entfällt die Markierung ganz.}{Head strip and head bar are decoration and are marked as % artifact. So that this neither splits the title line's MC nor leaves % an empty \code{P} fragment, the artifact sits \emph{at the very start % of the paragraph}, right after \cs{noindent} and before any other % material of the line (including the boxed branch's invisible strut, % which is therefore taken inside): the MC opened by the paragraph hook % then has not a single node assigned, does not appear in the PDF at % all, and after the pop the title text again forms exactly one MC. % Without an open MC (paragraph tagging off) the pop has no effect; % without tagpdf loaded the marking is skipped altogether.} \cs_new_protected:Npn \__osgstyler_block_head_artifact:n #1 { \cs_if_exist:NTF \tag_mc_end_push: { \tag_mc_end_push: \tag_mc_begin:n { artifact } #1 \tag_mc_end: \tag_mc_begin_pop:n { } } { #1 } } % \ldeen{Der Kopfstreifen als nullgrosse \cs{rlap}\cs{smash}-Deko hinter der % Titelzeile; ohne Text trägt er nichts zur Vorlesereihenfolge bei. Aufruf % im hmode, innerhalb von \cs{__osgstyler_block_head_artifact:n}. % \code{#1} verschiebt die Streifenkante nach links, \code{#2} ist seine % Breite.}{The head strip as zero-size \cs{rlap}\cs{smash} decoration % behind the title line; carrying no text it adds nothing to the reading % order. Call in hmode, inside \cs{__osgstyler_block_head_artifact:n}. % \code{#1} shifts the strip edge left, \code{#2} its width.} \cs_new_protected:Npn \__osgstyler_block_headstrip:nn #1#2 { \str_if_eq:VnF \l__osgstyler_block_head_bg_tl { none } { \rlap { \smash { \skip_horizontal:n { #1 } \color { \OsgColorName{ \l__osgstyler_block_head_bg_tl } } \rule [ \dim_eval:n { -0.32 \baselineskip } ] { \dim_eval:n { #2 } } { \dim_eval:n { 1.2 \baselineskip } } } }% } } % \ldeen{Der Kopfbalken des Kastenzweigs: eine \cs{rlap}\cs{smash}-\cs{rule} % genau in Höhe der (durch einen unsichtbaren Strut aufgepolsterten) % Titelzeile -- so schliesst der Balken bündig mit der Kastenoberkante ab. % Die Artefakt-Markierung übernimmt der Aufrufer % (\cs{__osgstyler_block_head_artifact:n}). \code{#1} ist seine % Breite.}{The boxed branch's % head bar: an \cs{rlap}\cs{smash} \cs{rule} exactly as tall as the title % line (padded by an invisible strut), so it meets the box's top edge % flush. The caller marks it as artifact % (\cs{__osgstyler_block_head_artifact:n}). \code{#1} is its width.} \cs_new_protected:Npn \__osgstyler_block_headbar:n #1 { \str_if_eq:VnF \l__osgstyler_block_head_bg_tl { none } { \bool_lazy_and:nnTF { \dim_compare_p:nNn \l__osgstyler_block_corner_radius_dim > \c_zero_dim } { \cs_if_exist_p:N \pgfpicture } { \__osgstyler_block_headbar_round:n {#1} } { \rlap { \smash { \color { \OsgColorName{ \l__osgstyler_block_head_bg_tl } } \rule [ \dim_eval:n { -\dp\strutbox - \l__osgstyler_block_head_padding_bottom_dim } ] { \dim_eval:n { #1 } } { \dim_eval:n { \ht\strutbox + \dp\strutbox + \l__osgstyler_block_head_padding_top_dim + \l__osgstyler_block_head_padding_bottom_dim } } } }% } } } % \ldeen{Kopfbalken mit abgerundeten \emph{oberen} Ecken (Radius wie der % Kasten), damit er in einem gerundeten \code{titlebar}-Kasten nicht über % die Ecken hinaussteht.}{Head bar % with rounded \emph{top} corners (the box's radius) so it does not % overshoot the corners inside a rounded \code{titlebar} box.} \cs_new_protected:Npn \__osgstyler_block_headbar_round:n #1 { \rlap { \smash { \begin { pgfpicture } \pgfsetbaseline { 0pt } \pgfsetfillcolor { \OsgColorName { \l__osgstyler_block_head_bg_tl } } \dim_set:Nn \l_tmpa_dim { \ht\strutbox + \l__osgstyler_block_head_padding_top_dim } \dim_set:Nn \l_tmpb_dim { -\dp\strutbox - \l__osgstyler_block_head_padding_bottom_dim } \pgfpathmoveto { \pgfpoint { 0pt } { \l_tmpb_dim } } \pgfpathlineto { \pgfpoint { 0pt } { \dim_eval:n { \l_tmpa_dim - \l__osgstyler_block_corner_radius_dim } } } \pgfpatharc { 180 } { 90 } { \l__osgstyler_block_corner_radius_dim } \pgfpathlineto { \pgfpoint { \dim_eval:n { (#1) - \l__osgstyler_block_corner_radius_dim } } { \l_tmpa_dim } } \pgfpatharc { 90 } { 0 } { \l__osgstyler_block_corner_radius_dim } \pgfpathlineto { \pgfpoint { \dim_eval:n {#1} } { \l_tmpb_dim } } \pgfpathclose \pgfusepath { fill } \end { pgfpicture } } }% } % \ldeen{\code{titled} mit getönter Body-Fläche: ein Kasten % (\cs{fcolorbox}+\env{minipage}, ein \code{Div}). Innenabstand von Hand % (\cs{fboxsep} ist Null), damit der Kopfbalken randlos an die Kanten % läuft und bündig mit der Oberkante abschliesst.}{\code{titled} with a % tinted body area: a box (\cs{fcolorbox}+\env{minipage}, one \code{Div}). % Padding done by hand (\cs{fboxsep} is zero) so the head bar bleeds to % the edges and meets the top edge flush.} \cs_new_protected:Npn \__osgstyler_block_typeset_titled_box:n #1 { \par \group_begin: \__osgstyler_block_box_para_off: \addvspace { \medskipamount } \setlength \fboxsep { 0pt } \setlength \fboxrule { \l__osgstyler_block_rule_dim } \noindent \__osgstyler_block_fbox:nnn { \OsgColorName { \l__osgstyler_block_rule_color_tl } } { \__osgstyler_block_bg:V \l__osgstyler_block_body_bg_tl } { \begin { minipage } { \dim_eval:n { \linewidth - 2\fboxrule } } \tl_if_empty:NF \l__osgstyler_block_title_tl { \noindent % invisible strut sets the title line's metrics; the bar % (smashed) is drawn to the same extent. \__osgstyler_block_head_artifact:n { \rule [ \dim_eval:n { -\dp\strutbox - \l__osgstyler_block_head_padding_bottom_dim } ] { 0pt } { \dim_eval:n { \ht\strutbox + \dp\strutbox + \l__osgstyler_block_head_padding_top_dim + \l__osgstyler_block_head_padding_bottom_dim } }% \__osgstyler_block_headbar:n { \linewidth } } \skip_horizontal:n { \l__osgstyler_block_head_padding_left_dim } \dim_set:Nn \rightskip { \l__osgstyler_block_head_padding_right_dim } { \color { \OsgColorName{ \l__osgstyler_block_head_fg_tl } } \l__osgstyler_block_head_font_tl \l__osgstyler_block_title_tl \par } \nobreak \skip_vertical:n { \l__osgstyler_block_body_padding_top_dim } } \dim_set:Nn \leftskip { \l__osgstyler_block_body_padding_left_dim } \dim_set:Nn \rightskip { \l__osgstyler_block_body_padding_right_dim } \tl_if_empty:NT \l__osgstyler_block_title_tl { \tex_kern:D \l__osgstyler_block_body_padding_top_dim } % Do not force horizontal mode here: if the body starts with a % list, \noindent would create an empty paragraph before it. \setlength \parindent { 0pt } \ignorespaces #1 \par % A kern, not \skip_vertical: \endminipage's \unskip would drop % trailing glue and leave the body sitting on the bottom edge. \tex_kern:D \l__osgstyler_block_body_padding_bottom_dim \end { minipage } }% \par \group_end: \addvspace { \medskipamount } } \cs_new_protected:Npn \__osgstyler_block_typeset_titled_flow:n #1 { \par \addvspace { \medskipamount } \group_begin: \tl_if_empty:NF \l__osgstyler_block_title_tl { % Optional rule above the head (booktabs-style bracket top). \dim_compare:nNnT \l__osgstyler_block_head_rule_dim > \c_zero_dim { \__osgstyler_block_rule:nn { \l__osgstyler_block_head_rule_color_tl } { \l__osgstyler_block_head_rule_dim } \nointerlineskip \skip_vertical:n { 0.2 \baselineskip } } % The coloured strip (if any) sits behind the title's own line. \skip_vertical:n { \l__osgstyler_block_head_padding_top_dim } \noindent \__osgstyler_block_head_artifact:n { \__osgstyler_block_headstrip:nn { -\l__osgstyler_block_head_padding_left_dim } { \linewidth + \l__osgstyler_block_head_padding_left_dim + \l__osgstyler_block_head_padding_right_dim } } { \skip_horizontal:n { \l__osgstyler_block_head_padding_left_dim } \dim_set:Nn \rightskip { \l__osgstyler_block_head_padding_right_dim } \color { \OsgColorName{ \l__osgstyler_block_head_fg_tl } } \l__osgstyler_block_head_font_tl \l__osgstyler_block_title_tl }% \par \skip_vertical:n { \l__osgstyler_block_head_padding_bottom_dim } \skip_vertical:n { \l__osgstyler_block_body_padding_top_dim } % Normal interline spacing to the body -- one line apart, like % beamer's default block. The head strip is \cs{smash}ed and % reaches only ~0.3\baselineskip below the title baseline, so it % does not touch the body at this distance. } \group_begin: \dim_set:Nn \leftskip { \l__osgstyler_block_body_padding_left_dim } \dim_set:Nn \rightskip { \l__osgstyler_block_body_padding_right_dim } % Keep list-like bodies in vertical mode while ordinary text still % starts flush with the body edge. \setlength \parindent { 0pt } \ignorespaces #1 \par \skip_vertical:n { \l__osgstyler_block_body_padding_bottom_dim } \group_end: % Optional rule below the body (bracket bottom). \dim_compare:nNnT \l__osgstyler_block_foot_rule_dim > \c_zero_dim { \nointerlineskip \skip_vertical:n { 0.35 \baselineskip } \__osgstyler_block_rule:nn { \l__osgstyler_block_foot_rule_color_tl } { \l__osgstyler_block_foot_rule_dim } } \group_end: \addvspace { \medskipamount } } % \ldeen*{Instanzen sind bei xtemplate ohnehin nur gespeichertes % Keyval-Material -- Rahmen/Schatten/Ecken lassen sich also schon durch % simples Verketten mehrerer Fragmente kombinieren, ohne dass es dafür % je eine eigene Instanz bräuchte. @1 gibt diesem Verketten nur einen % Namen: der Schlüssel @2 schlägt benannte, mit @3 deklarierte % Fragmente nach und reiht sie vor die übrigen, unverändert % durchgereichten Schlüssel ein.} % {Instances are just stored keyval material in xtemplate to begin % with -- rule/shadow/corner combinations can already be composed by % concatenating fragments, without a dedicated instance for each % combination. @1 merely gives that concatenation a name: the @2 key % looks up named fragments declared via @3 and prepends them to the % remaining keys, which pass through unchanged.} % {\cs{DeclareOsgBlockInstance}}{traits}{\cs{DeclareOsgBlockTrait}} \prop_new:N \g__osgstyler_block_trait_prop \tl_new:N \l__osgstyler_block_instance_keys_tl \tl_new:N \l__osgstyler_block_trait_tl \msg_new:nnnn { osgstyler } { unknown-block-trait } { Unknown~block~trait~'#1'. } { Declare~it~with~\token_to_str:N\DeclareOsgBlockTrait~before~using~it. } \NewDocumentCommand \DeclareOsgBlockTrait { m m } { \prop_gput:Nne \g__osgstyler_block_trait_prop {#1} { \exp_not:n {#2} } } \keys_define:nn { osgstyler / block-instance } { % \ldeen{Ein eigener Scratch, nicht \cs{l_tmpa_tl}: der Aufrufer kann % \cs{l_tmpa_tl} über den \cs{keys_set:nn}"=Aufruf hinweg brauchen.}{A % private scratch, not \cs{l_tmpa_tl}: the caller may rely on % \cs{l_tmpa_tl} across the \cs{keys_set:nn} call.} traits .code:n = \clist_map_inline:nn {#1} { % \ldeen{\cs{tl_put_right:NV} statt \cs{tl_put_right:Nx}: der % Trait-Wert darf \cs{normalfont} u.\,ä.\ enthalten, die eine % vollständige Expansion sprengen würden.}{\cs{tl_put_right:NV} % not \cs{tl_put_right:Nx}: a trait value may contain % \cs{normalfont} and the like, which would break full % expansion.} \prop_get:NnNTF \g__osgstyler_block_trait_prop {##1} \l__osgstyler_block_trait_tl { \tl_put_right:NV \l__osgstyler_block_instance_keys_tl \l__osgstyler_block_trait_tl \tl_put_right:Nn \l__osgstyler_block_instance_keys_tl { , } } { \msg_error:nnn { osgstyler } { unknown-block-trait } {##1} } }, unknown .code:n = \tl_put_right:Nx \l__osgstyler_block_instance_keys_tl { \exp_not:V \l_keys_key_str = \exp_not:n {#1} , } } \cs_new_protected:Npn \__osgstyler_block_instance_resolve:n #1 { \tl_clear:N \l__osgstyler_block_instance_keys_tl \keys_set:nn { osgstyler / block-instance } {#1} } % \ldeen*{Merkt sich pro Instanz, ob \code{layout=titled} wirksam ist % (direkt oder über einen Trait). Umhüllende Pakete wie \pkg*{lttheme} % brauchen das, weil sie einen Kopf-Titel dann anders zuführen müssen als % einen dem Body vorangestellten. Ein Aufruf ohne Layout-Angabe lässt den % Vermerk stehen -- \cs{StyleOsgBlockInstance} ist additiv.} % {Records per instance whether \code{layout=titled} is in effect % (directly or through a trait). Wrapping packages such as \pkg*{lttheme} % need this because a head title has to be fed in differently from one % prepended to the body. A call without a layout key leaves the record % untouched -- \cs{StyleOsgBlockInstance} is additive.} \prop_new:N \g__osgstyler_block_titled_prop \cs_new_protected:Npn \__osgstyler_block_record_layout:n #1 { \tl_if_in:NnT \l__osgstyler_block_instance_keys_tl { titled } { \prop_gput:Nnn \g__osgstyler_block_titled_prop {#1} { } } \tl_if_in:NnT \l__osgstyler_block_instance_keys_tl { plain } { \prop_gremove:Nn \g__osgstyler_block_titled_prop {#1} } } \prg_new_conditional:Npnn \osgstyler_if_block_titled:n #1 { p, T, F, TF } { \prop_if_in:NnTF \g__osgstyler_block_titled_prop {#1} { \prg_return_true: } { \prg_return_false: } } \cs_new_eq:NN \OsgIfBlockTitledT \osgstyler_if_block_titled:nT \cs_new_eq:NN \OsgIfBlockTitledF \osgstyler_if_block_titled:nF \cs_new_eq:NN \OsgIfBlockTitledTF \osgstyler_if_block_titled:nTF % xtemplate's own key parser, like enumitem's \setlist (see % \cs{__osgstyler_list_apply_level:nnn}), scans its keys argument % before expansion; a bare macro reference there is read as one % valueless key, not expanded first. Building the whole call as % already-expanded literal text avoids that. \tl_new:N \l__osgstyler_block_instance_call_tl % \ldeen{\cs{exp_not:V} um die gesammelten Schlüssel: sie dürfen % \cs{normalfont} u.\,ä.\ enthalten (etwa aus \code{head-font}), die eine % vollständige Expansion sprengen würden.}{\cs{exp_not:V} around the % collected keys: they may contain \cs{normalfont} and the like (e.g.\ from % \code{head-font}), which would break full expansion.} \NewDocumentCommand \DeclareOsgBlockInstance { m m } { \__osgstyler_block_instance_resolve:n {#2} \__osgstyler_block_record_layout:n {#1} \tl_set:Ne \l__osgstyler_block_instance_call_tl { \exp_not:N \DeclareInstance { osgstyler-block } { \exp_not:n {#1} } { standard } { \exp_not:V \l__osgstyler_block_instance_keys_tl } } \tl_use:N \l__osgstyler_block_instance_call_tl } \NewDocumentCommand \StyleOsgBlockInstance { m m } { \__osgstyler_block_instance_resolve:n {#2} \__osgstyler_block_record_layout:n {#1} \tl_set:Ne \l__osgstyler_block_instance_call_tl { \exp_not:N \EditInstance { osgstyler-block } { \exp_not:n {#1} } { \exp_not:V \l__osgstyler_block_instance_keys_tl } } \tl_use:N \l__osgstyler_block_instance_call_tl } % ========================================== % NUMBERING TEMPLATE % ========================================== % % \ldeen*{Liefert ausschließlich die formatierte Zahl (z.\,B. "`2.3"'); % weder ein lokalisiertes Label wie "`Satz"' noch ein optionaler % Autoren-Titel gehören dazu -- beides bleibt Sache der Umgebung, die % eine Instanz dieses Templates verwendet, damit dieses Paket % sprachneutral bleibt. @1 registriert den zugrundeliegenden % \LaTeX-Zähler einmalig (\cs{DeclareOsgNumbering} löst dies sofort % aus, nicht erst bei der ersten Verwendung), da ein zweiter Aufruf von % \cs{newcounter} sonst fehlschlüge; Zählername und Format-Vorgabe % werden dabei zusätzlich unter dem Instanznamen gemerkt, weil weder % \cs{StepOsgNumbering} noch \cs{OsgNumberingValue} sonst einen Weg % hätten, sie wiederzufinden.} % {Produces only the formatted number (e.g. "`2.3"'); neither a % localized label such as "`Theorem"' nor an optional author-supplied % title belong here -- both remain the calling environment's concern, % keeping this package language-neutral. @1 registers the underlying % \LaTeX\ counter exactly once (\cs{DeclareOsgNumbering} triggers this % immediately, not on first use), since a second \cs{newcounter} call % would otherwise fail; the counter name and format are additionally % remembered under the instance name, since neither % \cs{StepOsgNumbering} nor \cs{OsgNumberingValue} would otherwise have % a way to recover them.} % {\cs{DeclareOsgNumbering}} \tl_new:N \l__osgstyler_numbering_counter_tl \tl_new:N \l__osgstyler_numbering_within_tl \tl_new:N \l__osgstyler_numbering_format_tl \prop_new:N \g__osgstyler_numbering_counter_prop \prop_new:N \g__osgstyler_numbering_format_prop \NewTemplateType { osgstyler-numbering } { 1 } \DeclareTemplateInterface { osgstyler-numbering } { standard } { 1 } { counter : tokenlist =, within : tokenlist = none, format : tokenlist = } \DeclareTemplateCode { osgstyler-numbering } { standard } { 1 } { counter = \l__osgstyler_numbering_counter_tl, within = \l__osgstyler_numbering_within_tl, format = \l__osgstyler_numbering_format_tl } { \__osgstyler_numbering_register:n {#1} } \cs_new_protected:Npn \__osgstyler_numbering_register:n #1 { \cs_if_exist:cF { c@ \l__osgstyler_numbering_counter_tl } { \str_if_eq:VnTF \l__osgstyler_numbering_within_tl { none } { \newcounter { \l__osgstyler_numbering_counter_tl } } { \newcounter { \l__osgstyler_numbering_counter_tl } [ \l__osgstyler_numbering_within_tl ] } } \prop_gput:NnV \g__osgstyler_numbering_counter_prop {#1} \l__osgstyler_numbering_counter_tl \prop_gput:NnV \g__osgstyler_numbering_format_prop {#1} \l__osgstyler_numbering_format_tl } \NewDocumentCommand \DeclareOsgNumbering { m m } { \DeclareInstance { osgstyler-numbering } {#1} { standard } {#2} \UseInstance { osgstyler-numbering } {#1} {#1} } \NewExpandableDocumentCommand \OsgNumberingCounter { m } { \prop_item:Nn \g__osgstyler_numbering_counter_prop {#1} } \NewDocumentCommand \StepOsgNumbering { m } { \refstepcounter { \OsgNumberingCounter {#1} } } \NewExpandableDocumentCommand \OsgNumberingValue { m } { \prop_item:Nn \g__osgstyler_numbering_format_prop {#1} } % ========================================== % NEW ENVIRONMENTS FROM SCRATCH % ========================================== % % \ldeen*{@1 deckt den Fall ab, in dem \emph{keine} bereits bestehende % Umgebung umhüllt wird (der Gegenfall -- ein bekanntes Environment wie % \env{block} oder ein von \pkg*{amsthm} erzeugtes \env{theorem} nur % optisch einzurahmen -- bleibt eine dokumentierte, pro Environment % wiederholte Anwendung von \cs{UseInstance}\Marg{osgstyler-block}, da % sich die Argumentsignaturen bestehender Environments zu sehr % unterscheiden, um sie generisch zu erfassen). @2 selbst bleibt % sprachneutral: das Label (@3) liefert der Aufrufer als fertigen Text, % \pkg*{osgstyler} übersetzt nicht.} % {@1 covers the case where \emph{no} pre-existing environment is being % wrapped (the opposite case -- giving a known environment such as % \env{block} or an \pkg*{amsthm}-generated \env{theorem} only a visual % frame -- remains a documented, per-environment repetition of % \cs{UseInstance}\Marg{osgstyler-block}, since existing environments' % argument signatures differ too much to capture generically). @2 itself % stays language-neutral: the caller supplies the label (@3) as % ready-made text, \pkg*{osgstyler} does not translate.} % {\cs{NewOsgStyledEnvironment}}{\cs{DeclareOsgNumbering}} \tl_new:N \l__osgstyler_styled_env_title_tl \cs_new_protected:Npn \__osgstyler_styled_env_begin:nnnnn #1#2#3#4#5 { \group_begin: \tl_clear:N \l__osgstyler_styled_env_title_tl \tl_put_right:Nn \l__osgstyler_styled_env_title_tl {#3} \str_if_eq:nnF {#2} { none } { \StepOsgNumbering {#2} \tl_put_right:Nn \l__osgstyler_styled_env_title_tl { ~\OsgNumberingValue {#2} } } \IfNoValueF {#4} { \tl_put_right:Nn \l__osgstyler_styled_env_title_tl { ~(#4)} } \__osgstyler_block_use:nnn {#1} { \tl_use:N \l__osgstyler_styled_env_title_tl } { #5 } \group_end: } % \ldeen*{Ist @1 (aus \pkg*{osglecture-modes}) beim Aufruf bereits % geladen, bekommt das erzeugte Environment zusätzlich eine führende % \cs{d<>}-Spezifikation: ein inaktiver Modus unterdrückt die Ausgabe % vollständig, \emph{einschließlich} des Nummerierungsschritts -- % ausgeblendeter Inhalt soll keine Lücke in der Nummerierung % hinterlassen. Das ist hier gefahrlos möglich (anders als bei % nativen Overlay-Umgebungen ohne Fallback), weil der Textkörper % ohnehin als \cs{+b}-Argument vollständig eingelesen wird, bevor % irgendetwas davon ausgeführt wird -- ihn bedingt zu verwerfen ersetzt % keine bereits laufende Ausgabe.} % {If @1 (from \pkg*{osglecture-modes}) is already loaded when this % runs, the generated environment additionally gets a leading % \cs{d<>} specification: an inactive mode suppresses output entirely, % \emph{including} the numbering step -- hidden content should not % leave a gap in the numbering. This is safe here (unlike native % overlay environments without a fallback) because the body is fully % read as a \cs{+b} argument before any of it executes -- discarding it % conditionally does not interrupt output already under way.} % {\pkg*{osglecture-modes}} \cs_new_protected:Npn \__osgstyler_styled_env_mode_dispatch:nnnnnn #1#2#3#4#5#6 { \IfNoValueTF {#4} { \__osgstyler_styled_env_begin:nnnnn {#1}{#2}{#3}{#5}{#6} } { \IfLectureModeT {#4} { \__osgstyler_styled_env_begin:nnnnn {#1}{#2}{#3}{#5}{#6} } } } % \ldeen*{Ab @1 (tcolorbox-artig, wie \pkg*{tcolorbox}s eigenes % \cs{newtcolorbox}\oarg{nargs}) verlangt das erzeugte Environment % @1 Pflichtargumente statt des optionalen \Marg{Name}; die Titelvorlage % (@2) wird dann pro Verwendung mit den echten Argumenten gefüllt -- % genau Beamers eigene \env{block}-Signatur \Verb+\begin{block}{Titel}+ % lässt sich damit für die Langform nachbilden (@3). Die automatische % Label+Zahl+Name-Komposition entfällt hier bewusst: wer Nummerierung % mit freiem Titel kombinieren will, bezieht \cs{OsgNumberingValue} % selbst in die Vorlage ein.} % {From @1 onward (tcolorbox-style, like \pkg*{tcolorbox}'s own % \cs{newtcolorbox}\oarg{nargs}) the generated environment requires % @1 mandatory arguments instead of the optional \Marg{Name}; the title % template (@2) is then filled with the real arguments on each use -- % this reproduces beamer's own \env{block} signature % \Verb+\begin{block}{Title}+ for the long form (@3). The automatic % label+number+name composition is deliberately absent here: combining % numbering with a free title means including \cs{OsgNumberingValue} % in the template yourself.} % {\code{1}}{title~template}{\NewOsgStyledEnvironment\Marg{block}\Marg{block}\Marg{none}\oarg{1}\Marg{\#1}} \msg_new:nnnn { osgstyler } { unsupported-styled-env-nargs } { Styled~environments~support~0--3~title~arguments,~not~'#1'. } { Use~a~value~from~0~to~3~for~the~optional~argument~of ~\token_to_str:N\NewOsgStyledEnvironment. } \cs_new_protected:Npn \__osgstyler_styled_env_freetitle_render:nnn #1#2#3 { \group_begin: \__osgstyler_block_use:nnn {#1} { #2 } { #3 } \group_end: } \NewDocumentCommand \NewOsgStyledEnvironment { m m m O{0} m } { \int_compare:nNnTF {#4} = { 0 } { \cs_if_exist:NTF \IfLectureModeTF { \NewDocumentEnvironment {#1} { d<> o +b } { \__osgstyler_styled_env_mode_dispatch:nnnnnn {#2}{#3}{#5}{##1}{##2}{##3} } { } } { \NewDocumentEnvironment {#1} { o +b } { \__osgstyler_styled_env_begin:nnnnn {#2}{#3}{#5}{##1}{##2} } { } } } { \int_case:nnF {#4} { { 1 } { \cs_set:cpn { __osgstyler_styled_env_title_apply_#1:n } ##1 { #5 } \cs_if_exist:NTF \IfLectureModeTF { \NewDocumentEnvironment {#1} { d<> m +b } { \IfNoValueTF {##1} { \tl_set:Ne \l__osgstyler_styled_env_title_tl { \exp_not:c { __osgstyler_styled_env_title_apply_#1:n } {##2} } \__osgstyler_styled_env_freetitle_render:nnn {#2} { \tl_use:N \l__osgstyler_styled_env_title_tl } {##3} } { \IfLectureModeT {##1} { \tl_set:Ne \l__osgstyler_styled_env_title_tl { \exp_not:c { __osgstyler_styled_env_title_apply_#1:n } {##2} } \__osgstyler_styled_env_freetitle_render:nnn {#2} { \tl_use:N \l__osgstyler_styled_env_title_tl } {##3} } } } { } } { \NewDocumentEnvironment {#1} { m +b } { \tl_set:Ne \l__osgstyler_styled_env_title_tl { \exp_not:c { __osgstyler_styled_env_title_apply_#1:n } {##1} } \__osgstyler_styled_env_freetitle_render:nnn {#2} { \tl_use:N \l__osgstyler_styled_env_title_tl } {##2} } { } } } { 2 } { \cs_set:cpn { __osgstyler_styled_env_title_apply_#1:nn } ##1##2 { #5 } \cs_if_exist:NTF \IfLectureModeTF { \NewDocumentEnvironment {#1} { d<> m m +b } { \IfNoValueTF {##1} { \tl_set:Ne \l__osgstyler_styled_env_title_tl { \exp_not:c { __osgstyler_styled_env_title_apply_#1:nn } {##2}{##3} } \__osgstyler_styled_env_freetitle_render:nnn {#2} { \tl_use:N \l__osgstyler_styled_env_title_tl } {##4} } { \IfLectureModeT {##1} { \tl_set:Ne \l__osgstyler_styled_env_title_tl { \exp_not:c { __osgstyler_styled_env_title_apply_#1:nn } {##2}{##3} } \__osgstyler_styled_env_freetitle_render:nnn {#2} { \tl_use:N \l__osgstyler_styled_env_title_tl } {##4} } } } { } } { \NewDocumentEnvironment {#1} { m m +b } { \tl_set:Ne \l__osgstyler_styled_env_title_tl { \exp_not:c { __osgstyler_styled_env_title_apply_#1:nn } {##1}{##2} } \__osgstyler_styled_env_freetitle_render:nnn {#2} { \tl_use:N \l__osgstyler_styled_env_title_tl } {##3} } { } } } { 3 } { \cs_set:cpn { __osgstyler_styled_env_title_apply_#1:nnn } ##1##2##3 { #5 } \cs_if_exist:NTF \IfLectureModeTF { \NewDocumentEnvironment {#1} { d<> m m m +b } { \IfNoValueTF {##1} { \tl_set:Ne \l__osgstyler_styled_env_title_tl { \exp_not:c { __osgstyler_styled_env_title_apply_#1:nnn } {##2}{##3}{##4} } \__osgstyler_styled_env_freetitle_render:nnn {#2} { \tl_use:N \l__osgstyler_styled_env_title_tl } {##5} } { \IfLectureModeT {##1} { \tl_set:Ne \l__osgstyler_styled_env_title_tl { \exp_not:c { __osgstyler_styled_env_title_apply_#1:nnn } {##2}{##3}{##4} } \__osgstyler_styled_env_freetitle_render:nnn {#2} { \tl_use:N \l__osgstyler_styled_env_title_tl } {##5} } } } { } } { \NewDocumentEnvironment {#1} { m m m +b } { \tl_set:Ne \l__osgstyler_styled_env_title_tl { \exp_not:c { __osgstyler_styled_env_title_apply_#1:nnn } {##1}{##2}{##3} } \__osgstyler_styled_env_freetitle_render:nnn {#2} { \tl_use:N \l__osgstyler_styled_env_title_tl } {##4} } { } } } } { \msg_error:nnn { osgstyler } { unsupported-styled-env-nargs } {#4} } } } % ========================================== % STYLING KNOWN ENVIRONMENTS (CASE A, WITHOUT OVERLAYS) % ========================================== % % \ldeen*{@1 verallgemeinert das Muster, mit dem \pkg*{lttheme} % \env{block} umhüllt, für Environments \emph{ohne} Overlay-Syntax -- % deren Argumentsignatur ist einheitlich genug (ein optionaler Name vor % einem Textkörper), um sie generisch zu erfassen, anders als bei % \env{block}/\env{alertblock}, die \cs{d<>} brauchen und daher % themenspezifisch bleiben. Das optionale Argument wird unverändert an % das umhüllte Original weitergereicht, nie selbst interpretiert -- % @2s eigene \Marg{Name}-Klammer vor \env{theorem}-artigen Environments % (\cs{newtheorem}) funktioniert dadurch unverändert, samt eigener % Nummerierung: @1 fügt nur die Box-Optik um @2s vollständige Ausgabe % herum, ohne dessen Zähler oder Kopf-Rendering zu berühren.} % {@1 generalizes the pattern \pkg*{lttheme} uses to wrap \env{block} % for environments \emph{without} overlay syntax -- their argument % signature is uniform enough (an optional name before a text body) to % capture generically, unlike \env{block}/\env{alertblock}, which need % \cs{d<>} and therefore stay theme-specific. The optional argument is % forwarded to the wrapped original unchanged, never interpreted here % itself -- @2's own \Marg{name} bracket in front of \env{theorem}-like % environments (\cs{newtheorem}) therefore keeps working unchanged, % numbering included: @1 only adds the box styling around @2's complete % output, without touching its counter or heading rendering.} % {\cs{BindOsgBlockEnvironment}}{\pkg*{amsthm}} \NewDocumentCommand \BindOsgBlockEnvironment { m m } { \__osgstyler_block_bind_environment:nn {#1}{#2} } \cs_new_protected:Npn \__osgstyler_block_render_bound:nnnn #1#2#3#4 { \__osgstyler_block_use:nnn {#2} { } { \IfNoValueTF {#3} { \begin{osgstyler-original-#1} #4 \end{osgstyler-original-#1} } { \begin{osgstyler-original-#1}[#3] #4 \end{osgstyler-original-#1} } } } % See \cs{NewOsgStyledEnvironment} for why discarding the body % conditionally is safe here. \cs_new_protected:Npn \__osgstyler_block_bound_mode_dispatch:nnnnn #1#2#3#4#5 { \IfNoValueTF {#3} { \__osgstyler_block_render_bound:nnnn {#1}{#2}{#4}{#5} } { \IfLectureModeT {#3} { \__osgstyler_block_render_bound:nnnn {#1}{#2}{#4}{#5} } } } \cs_new_protected:Npn \__osgstyler_block_bind_environment:nn #1#2 { \NewEnvironmentCopy { osgstyler-original-#1 } {#1} \cs_if_exist:NTF \IfLectureModeTF { \RenewDocumentEnvironment {#1} { d<> o +b } { \__osgstyler_block_bound_mode_dispatch:nnnnn {#1}{#2}{##1}{##2}{##3} } { } } { \RenewDocumentEnvironment {#1} { o +b } { \__osgstyler_block_render_bound:nnnn {#1}{#2}{##1}{##2} } { } } } % \ldeen*{Die \cls*{LaTeX}-Kernumgebungen @1/@2 sind auf jedem Ziel % verfügbar, weshalb ihre Instanzen -- unsichtbar, wie die % Schnittstellen-Vorgaben es ohnehin vorsehen -- immer deklariert sind. % Umgebunden werden sie aber erst durch einen bewussten Aufruf von % \cs{BindOsgBlockEnvironment} oder @3, nicht beim Laden dieses Pakets: % wer \pkg*{osgstyler} nur wegen Farben oder Inline-Befehlen lädt, soll % nicht überrascht feststellen, dass sich seine Zitate verändert % haben.} % {The \cls*{LaTeX} kernel environments @1/@2 are available on every % target, which is why their instances -- invisible, as the interface % defaults already provide anyway -- are always declared. They are only % actually rebound by a deliberate call to \cs{BindOsgBlockEnvironment} % or @3, not on loading this package: someone loading \pkg*{osgstyler} % only for colors or inline commands should not be surprised to find % their quotations changed.} % {\env{quote}}{\env{quotation}}{\cs{UseOsgStandardBlockEnvironments}} \DeclareInstance { osgstyler-block } { quote } { standard } { } \DeclareInstance { osgstyler-block } { quotation } { standard } { } % \ldeen*{@1/@2/@3 reproduzieren Beamers eigene, freie Titelsignatur % (\Verb+\begin{block}{Titel}+) über @4 -- dieselbe Quelle kompiliert % damit unter Beamer/ltx-talk (dort natives Environment, siehe % \pkg*{lttheme}) wie unter der Langform (hier neu erzeugt) gleich. % Nur wenn der Name noch nicht existiert wird das Environment % angelegt: existiert es schon nativ, ändert @5 daran nichts.} % {@1/@2/@3 reproduce beamer's own free-title signature % (\Verb+\begin{block}{Title}+) via @4 -- the same source therefore % compiles the same way under beamer/ltx-talk (native environment % there, see \pkg*{lttheme}) and under the long form (newly created % here). The environment is only created if the name does not exist % yet: if it is already native, @5 leaves it untouched.} % {\env{block}}{\env{alertblock}}{\env{exampleblock}} % {\cs{NewOsgStyledEnvironment}}{\cs{UseOsgStandardBlockEnvironments}} \DeclareInstance { osgstyler-block } { block } { standard } { rule-thickness = .4pt, padding = .5em } \DeclareInstance { osgstyler-block } { alertblock } { standard } { rule-thickness = .4pt, padding = .5em, rule-color = alert } \DeclareInstance { osgstyler-block } { exampleblock } { standard } { rule-thickness = .4pt, padding = .5em, rule-color = example } \NewDocumentCommand \UseOsgStandardBlockEnvironments { } { \BindOsgBlockEnvironment { quote } { quote } \BindOsgBlockEnvironment { quotation } { quotation } \cs_if_exist:cF { block } { \NewOsgStyledEnvironment { block } { block } { none } [ 1 ] { ##1 } } \cs_if_exist:cF { alertblock } { \NewOsgStyledEnvironment { alertblock } { alertblock } { none } [ 1 ] { ##1 } } \cs_if_exist:cF { exampleblock } { \NewOsgStyledEnvironment { exampleblock } { exampleblock } { none } [ 1 ] { ##1 } } } % \ldeen*{Ein weiteres @1-Accessoire: eine dünne, akzentfarbene Linie % vor und nach dem Inhalt statt eines vollen Rahmens (\code{rule-thickness % = 0pt} erzwingt das, unabhängig vom bisherigen Zustand der Instanz) -- % passend sowohl für Zitate als auch für theorem-artige Umgebungen ohne % eigenen Kasten (\cls*{amsthm} rahmt seine Umgebungen ja auch nicht % ein). Ein \cs{traits}-Wert durchläuft auf dem Weg von der Deklaration % bis zur fertigen Instanz mehrere vollständige Expansionsrunden (siehe % \cs{__osgstyler_block_instance_resolve:n}); Befehle mit eigenem % Lookahead auf das nächste Zeichen (\cs{rule}s optionales % Anhebungs-Argument, \cs{vspace}, \cs{newline}, \cs{par} innerhalb % eines nicht-\cs{long}-Arguments von \pkg*{l3keys}) überstehen das % nicht zuverlässig. Die eigentliche Zeichenlogik steckt deshalb in % einer \cs{cs_new_protected:Npn}-Funktion, die als Ganzes durch alle % Runden unangetastet durchgereicht wird (wie ein bereits % \code{\\protected}-markierter Kernbefehl) und erst beim tatsächlichen % Satz ausgeführt statt expandiert wird -- innerhalb dieser Funktion % sind \cs{par} & Co. wieder ganz normal verwendbar, da dort keine % weitere Expansion mehr stattfindet.} % {Another @1 accessory: a thin, accent-colored line before and after % the content instead of a full border (\code{rule-thickness = 0pt} % forces this regardless of the instance's prior state) -- suits both % quotations and theorem-like environments without their own box % (\cls*{amsthm} does not frame its environments either). A \cs{traits} % value passes through several full expansion rounds on its way from % declaration to the finished instance (see % \cs{__osgstyler_block_instance_resolve:n}); commands with their own % lookahead for the next character (\cs{rule}'s optional raise % argument, \cs{vspace}, \cs{newline}, \cs{par} inside a non-\cs{long} % \pkg*{l3keys} argument) do not reliably survive that. The actual % drawing logic therefore lives in a \cs{cs_new_protected:Npn} function % that passes through all rounds untouched as a whole (like an % already-\code{\\protected} kernel command) and is executed rather % than expanded only at actual typesetting time -- inside that % function, \cs{par} and friends are perfectly usable again, since no % further expansion happens there.} % {osl-modern} \cs_new_protected:Npn \__osgstyler_osl_modern_rule:n #1 { \noindent{\color{\OsgColorName{#1}}\vrule width\linewidth height.6pt depth0pt} } \cs_new_protected:Npn \__osgstyler_osl_modern_rule_before: { \par \__osgstyler_osl_modern_rule:n {accent} \vskip.3em } \cs_new_protected:Npn \__osgstyler_osl_modern_rule_after: { \vskip.3em \__osgstyler_osl_modern_rule:n {accent} } \DeclareOsgBlockTrait { osl-modern-rules } { rule-thickness = 0pt, before = \__osgstyler_osl_modern_rule_before:, after = \__osgstyler_osl_modern_rule_after: } % \ldeen*{Gebrauchsfertige Block-Optiken als Traits. Farben sind % Slot-Namen (\code{structure} usw.), sodass sie der ladenden Palette bzw. % dem Theme folgen; \code{alertblock}/\code{exampleblock} überschreiben % danach nur den Slot. % % \code{heading} (nur farbiger Titel), \code{soft} (Titel mit dünnen % Linien) und \code{plain} fließen vollständig und belassen das Tagging der % Klasse unangetastet -- die richtige Wahl, wenn jede \code{Div}-Ebene im % getaggten PDF zählt. \code{card} (die Vorgabe), \code{titlebar} (Beamers % Madrid: Farbbalken über getönter Body-Fläche), \code{framed} und % \code{filled} setzen einen echten Kasten (\cs{fcolorbox} um eine % \env{minipage}); das fügt je Block genau ein \code{Div} hinzu -- Titel % und Body bleiben saubere \code{text-unit}s darin, ohne Verschachtelung.} % {Ready-made block looks as traits. Colours are slot names % (\code{structure} etc.) so they follow the loaded palette or theme; % \code{alertblock}/\code{exampleblock} then only override the slot. % % \code{heading} (a coloured title only), \code{soft} (a title with thin % rules) and \code{plain} are fully flowing and leave the class's tagging % untouched -- the right choice when every \code{Div} level in tagged PDF % counts. \code{card} (the default), \code{titlebar} (beamer's Madrid: a % colour bar over a tinted body area), \code{framed} and \code{filled} put % a real box (\cs{fcolorbox} around a \env{minipage}); that adds exactly % one \code{Div} per block -- title and body stay clean \code{text-unit}s % inside it, with no nesting.} \DeclareOsgBlockTrait { plain } { layout = plain, rule-thickness = 0pt, background = none } \DeclareOsgBlockTrait { framed } { layout = plain, rule-thickness = 0.4pt, rule-color = structure, padding = 0.5em } \DeclareOsgBlockTrait { filled } { layout = plain, rule-thickness = 0.4pt, rule-color = structure, background = structure!8, padding = 0.5em } % \ldeen{\code{titlebar}: Beamers Madrid -- voller Farbbalken oben, darunter % die getönte Body-Fläche bis zur Kastenunterkante. Durch das gesetzte % \code{body-background} nimmt der \code{titled}-Renderer seinen % Kastenzweig (ein \code{Div}).}{\code{titlebar}: beamer's Madrid -- a full % colour bar on top, the tinted body area below it down to the box's % bottom edge. The set \code{body-background} makes the \code{titled} % renderer take its boxed branch (one \code{Div}).} \DeclareOsgBlockTrait { titlebar } { layout = titled, head-background = structure, head-color = background, body-background = structure!12, rule-thickness = 0pt, rule-color = structure!40, head-rule-thickness = 0pt, foot-rule-thickness = 0pt, head-padding = 0.4em, body-padding = 0.6em } \DeclareOsgBlockTrait { soft } { layout = titled, head-background = none, head-color = structure, head-rule-color = structure, head-rule-thickness = 1pt, foot-rule-color = structure!45, foot-rule-thickness = 0.6pt, head-padding = 0pt, body-padding = 0.9em } % \ldeen{\code{heading}: nur der farbige, fette Titel, darunter der bündige % Body -- fließend, ohne Balken, Fläche oder Linien. Wie Beamers % Default-Block (Hannover, Szeged). \code{head-rule}/\code{foot-rule} auf % einen positiven Wert setzen bringt die \code{soft}-Linien zurück.}{ % \code{heading}: just the coloured bold title with the body flush below % it -- flowing, no bar, no fill, no rules. Like beamer's default block % (Hannover, Szeged). Setting \code{head-rule}/\code{foot-rule} to a % positive value brings the \code{soft} rules back.} \DeclareOsgBlockTrait { heading } { layout = titled, head-background = none, head-color = structure, head-rule-color = structure, head-rule-thickness = 0pt, foot-rule-color = structure!45, foot-rule-thickness = 0pt, head-padding = 0pt, body-padding = 0pt } % \ldeen{\code{card}: eine leicht gerahmte, zart getönte Box mit farbig % fettem Titel -- die Vorgabe. Als echter Kasten (\cs{fcolorbox} um eine % \env{minipage}) fügt sie im getaggten PDF ein \code{Div} je Block hinzu, % sonst nichts (Titel und Body bleiben saubere \code{text-unit}s darin).}{ % \code{card}: a lightly framed, faintly tinted box with a coloured bold % title -- the default. Being a real box (\cs{fcolorbox} around a % \env{minipage}) it adds one \code{Div} per block in tagged PDF and % nothing else (title and body stay clean \code{text-unit}s inside it).} \DeclareOsgBlockTrait { card } { layout = plain, rule-thickness = 0.8pt, rule-color = structure!70, background = structure!10, padding = 0.65em, head-color = structure, head-font = \normalfont \bfseries } % \ldeen*{Ein Preset besteht aus bis zu vier unabhängig deklarierten % Teilen (Farb-, Font- und Listenpalette, Block-Trait) unter demselben % Namen; ohne @1 müsste eine Vorlage wie \code{osl-modern} durch vier % getrennte Aufrufe aktiviert und der Trait zusätzlich von Hand auf % jede gewünschte Block-Instanz angewendet werden. @1 aktiviert Farb- % und (sofern @2 nicht \code{colors-only} gesetzt hat) Fontpalette % immer; Listenpalette und Trait nur, wenn unter demselben Namen % tatsächlich etwas deklariert ist -- @3 hat z.\,B.\ keine eigene % Listenpalette. Der Trait wird auf die immer schon deklarierten % Standard-Blockinstanzen (@4) angewendet, unabhängig davon, ob deren % Environments überhaupt schon (via \cs{UseOsgStandardBlockEnvironments} % oder \cs{BindOsgBlockEnvironment}) angebunden sind -- das Anwenden % eines Traits ist reine, folgenlose Keyval-Verwaltung, bis eine % tatsächlich gebundene Umgebung sie einmal abfragt.} % {A preset consists of up to four independently declared parts (color, % font, and list palette, block trait) sharing one name; without @1, a % preset such as \code{osl-modern} would need four separate calls to % activate, plus manually applying the trait to every desired block % instance. @1 always activates the color palette and (unless @2 has % set \code{colors-only}) the font palette; it activates the list % palette and trait only if something is actually declared under the % same name -- @3, for instance, has no list palette of its own. The % trait is applied to the standard block instances (@4), which are % always already declared, regardless of whether their environments % are bound yet at all (via \cs{UseOsgStandardBlockEnvironments} or % \cs{BindOsgBlockEnvironment}) -- applying a trait is pure, % consequence-free keyval bookkeeping until some actually bound % environment queries it.} % {\cs{UseOsgPreset}}{osgstyler}{osl-standard}{\env{quote}/\env{quotation}/\env{block}/\env{alertblock}/\env{exampleblock}} \msg_new:nnnn { osgstyler } { unknown-preset } { Unknown~preset~'#1'. } { Available~presets~are~osl-standard~and~osl-modern,~or~build~your~own~ from~\token_to_str:N\UseOsgColorPalette,~\token_to_str:N\UseOsgFontPalette,~ \token_to_str:N\UseOsgListPalette,~and~\token_to_str:N\StyleOsgBlockInstance. } \clist_const:Nn \c__osgstyler_preset_block_instances_clist { quote, quotation, block, alertblock, exampleblock } \cs_new_protected:Npn \__osgstyler_preset_use:n #1 { \prop_if_in:NnTF \g__osgstyler_color_palette_prop { #1 / declared } { \UseOsgColorPalette {#1} \OsgFontPaletteAvailableTF { \prop_if_in:NnT \g__osgstyler_font_palette_prop { #1 / declared } { \UseOsgFontPalette {#1} } } { } \prop_if_in:NnT \g__osgstyler_list_palette_prop { #1 / declared } { \UseOsgListPalette {#1} } \prop_if_in:NnT \g__osgstyler_block_trait_prop { #1-rules } { \clist_map_inline:Nn \c__osgstyler_preset_block_instances_clist { \StyleOsgBlockInstance {##1} { traits = {#1-rules} } } } } { \msg_error:nnn { osgstyler } { unknown-preset } {#1} } } \NewDocumentCommand \UseOsgPreset { m } { \__osgstyler_preset_use:n {#1} } % ========================================== % ATTRIBUTED ENVIRONMENTS (CASE B, CONTENT-THEN-ATTRIBUTION) % ========================================== % % \ldeen*{Ein zweiter Fall-B-Baustein neben \cs{NewOsgStyledEnvironment}: % ein semantisches Zitat trägt keine Titelzeile mit Label und Zahl vor % dem Inhalt, sondern höchstens eine Quellenangabe danach -- die % Zusammensetzung ist genau umgekehrt, weshalb dies ein eigener, % schlanker Mechanismus ist statt einer weiteren Option von % \cs{NewOsgStyledEnvironment}.} % {A second case-B building block alongside \cs{NewOsgStyledEnvironment}: % a semantic quotation carries no label-and-number title before its % content, only an optional attribution after it -- the composition is % exactly reversed, which is why this is its own, lean mechanism rather % than another option of \cs{NewOsgStyledEnvironment}.} % {\cs{NewOsgAttributedEnvironment}} \tl_new:N \l__osgstyler_attributed_env_tl \cs_new_protected:Npn \__osgstyler_attributed_env_begin:nnn #1#2#3 { \group_begin: \tl_set:Nn \l__osgstyler_attributed_env_tl {#3} \IfNoValueF {#2} { \tl_put_right:Nn \l__osgstyler_attributed_env_tl { \par \hfill ---~#2 } } \__osgstyler_block_use:nnn {#1} { } { \tl_use:N \l__osgstyler_attributed_env_tl } \group_end: } % See \cs{NewOsgStyledEnvironment} for why discarding the body % conditionally is safe here. \cs_new_protected:Npn \__osgstyler_attributed_env_mode_dispatch:nnnn #1#2#3#4 { \IfNoValueTF {#2} { \__osgstyler_attributed_env_begin:nnn {#1}{#3}{#4} } { \IfLectureModeT {#2} { \__osgstyler_attributed_env_begin:nnn {#1}{#3}{#4} } } } \NewDocumentCommand \NewOsgAttributedEnvironment { m m } { \cs_if_exist:NTF \IfLectureModeTF { \NewDocumentEnvironment {#1} { d<> o +b } { \__osgstyler_attributed_env_mode_dispatch:nnnn {#2}{##1}{##2}{##3} } { } } { \NewDocumentEnvironment {#1} { o +b } { \__osgstyler_attributed_env_begin:nnn {#2}{##1}{##2} } { } } } % \ldeen*{Optionale Integration von Modi, falls @1 geladen wurde. % Die Auswahlbefehle (\token_to_str:N\UseOsgInlineStyle und die gebundenen % Inline-Befehle sowie \token_to_str:N\UseOsgColorPalette, % \token_to_str:N\UseOsgFontPalette, \token_to_str:N\UseOsgMetricPalette) % werden modusabhängig (Parameter in spitzen Klammern), da ihr aktiver Modus % zum Zeitpunkt des Aufrufs bekannt ist. Die Deklarationsbefehle % (\token_to_str:N\DeclareOsgColorPalette und Geschwister) bleiben bewußt % moduunabhängig: Eine Palette wird einmalig definiert, unabhängig davon, in % welchen Moden sie später ausgewählt wird.}{Optional osglecture-modes % integration. The selection commands (\token_to_str:N\UseOsgInlineStyle and % the bound inline commands, as well as \token_to_str:N\UseOsgColorPalette, % \token_to_str:N\UseOsgFontPalette, \token_to_str:N\UseOsgMetricPalette) % accept a portable angle-bracket mode whenever @1 is present, since their % active mode is known at the call site. The declaration commands % (\token_to_str:N\DeclareOsgColorPalette and siblings) deliberately remain % mode-independent: a palette is defined once, regardless of which modes % later select it.}{\pkg{osglecture-modes}} \bool_new:N \g__osgstyler_modes_integrated_bool \cs_new_protected:Npn \__osgstyler_modes_integrate: { \bool_if:NF \g__osgstyler_modes_integrated_bool { \bool_gset_true:N \g__osgstyler_modes_integrated_bool \__osgstyler_make_mode_aware:Nn \UseOsgInlineStyle { m +m } \prop_map_inline:Nn \g__osgstyler_inline_command_socket_prop { \exp_args:Nc \__osgstyler_make_mode_aware:Nn {##1} { +m } } \__osgstyler_make_mode_aware:Nn \UseOsgColorPalette { m } \__osgstyler_make_mode_aware:Nn \UseOsgFontPalette { m } \__osgstyler_make_mode_aware:Nn \UseOsgMetricPalette { m } \__osgstyler_make_mode_aware:Nn \UseOsgListPalette { m } \__osgstyler_make_mode_aware:Nn \UseOsgPreset { m } } } \AddToHook { package/osglecture-modes/after } [ osgstyler/modes ] { \__osgstyler_modes_integrate: } \IfPackageLoadedTF { osglecture-modes } { \__osgstyler_modes_integrate: } { } % \ldeen*{Reaktive Rückfallintegration für Themepakete, die ihre % Themefarben nicht selbst über @1/@2 in osgstyler einspeisen. Speist ein % Themepaket seine Farben selbst ein und aktiviert dabei die % osgstyler-Palette @3 (vgl.\ @4), erkennt dieser Abschnitt sich anhand % der aktiven Palette selbst als überflüssig und tut nichts.}{Reactive % fallback integration for theme packages that do not feed their theme % colors into osgstyler themselves via @1/@2. A theme package that feeds % its own colors in and activates the osgstyler palette @3 while doing so % (cf.\ @4) makes this section recognize itself as redundant, via the % active palette, and do nothing.}{\cs{DeclareOsgColorPalette}} % {\cs{UseOsgColorPalette}}{\code{ltxtalk}}{\cs{setltxtalkcolors}} \bool_new:N \g__osgstyler_lttheme_integrated_bool \cs_new_protected:Npn \__osgstyler_lttheme_sync_colors: { \str_if_eq:VnF \g__osgstyler_color_palette_tl { ltxtalk } { \clist_map_inline:Nn \c__osgstyler_color_slots_clist { \str_if_eq:nnTF {##1} { background } { \tl_set:Nn \l_tmpa_tl { bg } } { \tl_set:Nn \l_tmpa_tl {##1} } \exp_args:Ne \color_export:nnN { ltxtalk-\l_tmpa_tl } { HTML } \l_tmpb_tl \prop_gput:Nnn \g__osgstyler_color_palette_prop { lttheme / ##1 / kind } { resource } \prop_gput:Nnn \g__osgstyler_color_palette_prop { lttheme / ##1 / model } { HTML } \prop_gput:Nne \g__osgstyler_color_palette_prop { lttheme / ##1 / value } { \l_tmpb_tl } \use:e { \exp_not:N \definecolor { osgstyler-##1 } { HTML } { \l_tmpb_tl } } } \prop_gput:Nnn \g__osgstyler_color_palette_prop { lttheme / declared } { true } \tl_gset:Nn \g__osgstyler_color_palette_tl { lttheme } } } \cs_new_protected:Npn \__osgstyler_lttheme_integrate: { \bool_if:NF \g__osgstyler_lttheme_integrated_bool { \bool_gset_true:N \g__osgstyler_lttheme_integrated_bool \AddToHook { cmd/useltxtalktheme/after } [ osgstyler/lttheme/theme ] { \__osgstyler_lttheme_sync_colors: } \AddToHook { cmd/addltxtalktheme/after } [ osgstyler/lttheme/add-theme ] { \__osgstyler_lttheme_sync_colors: } \AddToHook { cmd/setltxtalkcolors/after } [ osgstyler/lttheme/colors ] { \__osgstyler_lttheme_sync_colors: } \__osgstyler_lttheme_sync_colors: } } % \ldeen*{@1 ist ein Koordinationssignal: Ein Paket, das es vor dem % eigenen @2 definiert (lttheme tut dies, siehe dort), zeigt damit an, % dass es seine Themefarben selbst über @3/@4 einspeist und die reaktive % Integration hier überflüssig, ja sogar riskant wäre. Riskant, weil @2 % aus Sicht von \LaTeX{} schon dann als geladen gilt, sobald das % aufrufende Paket seine eigene \cs{ProvidesExplPackage}-Zeile % durchlaufen hat -- lange bevor es seine eigenen Farben definiert; die % reaktive Integration würde in diesem Moment sonst auf noch nicht % existierende Farben zugreifen.}{@1 is a coordination signal: a package % that defines it before its own @2 (lttheme does, see there) thereby % indicates that it feeds its own theme colors into osgstyler via @3/@4 % itself, making the reactive integration here redundant, in fact risky. % Risky, because @2 already counts as loaded from \LaTeX's perspective % as soon as the requiring package's own \cs{ProvidesExplPackage} line % has run -- long before it defines its own colors; the reactive % integration would otherwise access not-yet-existing colors at that % point.} % {\cs{LtxtalkUsesOsgstylerNatively}}{\cs{RequirePackage}\Marg{osgstyler}} % {\cs{DeclareOsgColorPalette}}{\cs{UseOsgColorPalette}} \cs_if_exist:NF \LtxtalkUsesOsgstylerNatively { \AddToHook { package/ltxtalk-theme/after } [ osgstyler/lttheme ] { \__osgstyler_lttheme_integrate: } \IfPackageLoadedTF { ltxtalk-theme } { \__osgstyler_lttheme_integrate: } { } } \ExplSyntaxOff % %<*lua-backend> \NeedsTeXFormat{LaTeX2e}[2024-06-01] \ProvidesExplPackage{osgstyler-lua} {2026-08-24} {v0.2.0} {Lua reference backend for osgstyler decorations} \RequirePackage{osgstyler} \RequirePackage{luacolor} \RequirePackage{lua-ul} \RequirePackage{pgfcore} \RequirePackage{transparent} \ExplSyntaxOn \msg_new:nnnn { osgstyler-lua } { luatex-required } { The~osgstyler~Lua~backend~requires~LuaLaTeX. } { Use~LuaLaTeX~or~load~a~different~osgstyler~backend. } \msg_new:nnnn { osgstyler-lua } { unsupported-pattern } { Pattern~'#1'~is~not~supported~for~the~'#2'~component. } { The~reference~backend~uses~a~solid~rule~instead. } \msg_new:nnnn { osgstyler-lua } { unsupported-position } { Line~position~'#1'~is~not~supported. } { The~reference~backend~uses~the~'below'~position~instead. } \msg_new:nnnn { osgstyler-lua } { unsupported-opacity } { Background~opacity~'#1'~is~outside~the~range~0--1. } { The~reference~backend~clamps~the~value~to~that~range. } \msg_new:nnnn { osgstyler-lua } { unsupported-shape } { Background~shape~'#1'~is~not~supported. } { Use~'text'~or~'line';~the~reference~backend~falls~back~to~'text'. } \msg_new:nnnn { osgstyler-lua } { unsupported-margin } { Margin~decoration~'#1'~cannot~be~rendered~inline. } { Use~a~block~backend~for~margin~decorations. } \sys_if_engine_luatex:F { \msg_fatal:nn { osgstyler-lua } { luatex-required } } % \RequirePackage{osgstyler} above already activates the built-in % osgstyler-default metric palette unconditionally, so a metric % palette is always active by this point; no separate fallback % palette is needed here. \tl_new:N \l__osgstyler_lua_pattern_tl \tl_new:N \l__osgstyler_lua_position_tl \tl_new:N \l__osgstyler_lua_opacity_tl \tl_new:N \l__osgstyler_lua_shape_tl \dim_new:N \l__osgstyler_lua_thickness_dim \dim_new:N \l__osgstyler_lua_offset_dim \dim_new:N \l__osgstyler_lua_top_dim \dim_new:N \l__osgstyler_lua_bottom_dim \dim_new:N \l__osgstyler_lua_cap_height_dim \dim_new:N \l__osgstyler_lua_period_dim \dim_new:N \l__osgstyler_lua_center_dim \dim_new:N \l__osgstyler_lua_radius_dim \dim_new:N \l__osgstyler_lua_padding_dim \box_new:N \l__osgstyler_lua_content_box % lua-ul deliberately accepts arbitrary leader material. A context-dependent % underline type lets it retain responsibility for finding decorated runs and % splitting them at line boundaries, while pgfcore supplies the actual pattern. \newunderlinetype \__osgstyler_lua_pattern_line: [ \l__osgstyler_lua_pattern_tl @ \dim_to_decimal_in_sp:n { \l__osgstyler_lua_top_dim } @ \dim_to_decimal_in_sp:n { \l__osgstyler_lua_bottom_dim } @ \dim_to_decimal_in_sp:n { \l__osgstyler_lua_thickness_dim } @ \luaul_context_color: ] { \luaul_apply_color: \str_case:Vn \l__osgstyler_lua_pattern_tl { { dashed } { \dim_set:Nn \l__osgstyler_lua_period_dim { 6\l__osgstyler_lua_thickness_dim } \leaders\hbox_to_wd:nn { \l__osgstyler_lua_period_dim } { \vrule width 3\l__osgstyler_lua_thickness_dim height \l__osgstyler_lua_top_dim depth -\l__osgstyler_lua_bottom_dim \hss } } { dotted } { \dim_set:Nn \l__osgstyler_lua_period_dim { 3\l__osgstyler_lua_thickness_dim } \dim_set:Nn \l__osgstyler_lua_center_dim { (\l__osgstyler_lua_top_dim+\l__osgstyler_lua_bottom_dim) / 2 } \leaders\hbox_to_wd:nn { \l__osgstyler_lua_period_dim } { \begin{pgfpicture} \pgfsetbaseline { 0pt } \pgfsetfillcolor { \l__osgstyler_decoration_color_tl } \pgfpathcircle { \pgfpoint { .5\l__osgstyler_lua_thickness_dim } { \l__osgstyler_lua_center_dim } } { .5\l__osgstyler_lua_thickness_dim } \pgfusepath { fill } \end{pgfpicture} \hss } } { wavy } { \dim_set:Nn \l__osgstyler_lua_period_dim { 6\l__osgstyler_lua_thickness_dim } \dim_set:Nn \l__osgstyler_lua_center_dim { (\l__osgstyler_lua_top_dim+\l__osgstyler_lua_bottom_dim) / 2 } \leaders\hbox_to_wd:nn { \l__osgstyler_lua_period_dim } { \begin{pgfpicture} \pgfsetbaseline { 0pt } \pgfsetstrokecolor { \l__osgstyler_decoration_color_tl } \pgfsetlinewidth { \l__osgstyler_lua_thickness_dim } \pgfpathmoveto { \pgfpoint { 0pt } { \l__osgstyler_lua_center_dim } } \pgfpathcurveto { \pgfpoint { 1.5\l__osgstyler_lua_thickness_dim } { \l__osgstyler_lua_top_dim } } { \pgfpoint { 1.5\l__osgstyler_lua_thickness_dim } { \l__osgstyler_lua_top_dim } } { \pgfpoint { 3\l__osgstyler_lua_thickness_dim } { \l__osgstyler_lua_center_dim } } \pgfpathcurveto { \pgfpoint { 4.5\l__osgstyler_lua_thickness_dim } { \l__osgstyler_lua_bottom_dim } } { \pgfpoint { 4.5\l__osgstyler_lua_thickness_dim } { \l__osgstyler_lua_bottom_dim } } { \pgfpoint { 6\l__osgstyler_lua_thickness_dim } { \l__osgstyler_lua_center_dim } } \pgfusepath { stroke } \end{pgfpicture} \hss } } } } \newunderlinetype \__osgstyler_lua_background: [ \l__osgstyler_lua_shape_tl @ \l__osgstyler_lua_opacity_tl @ \dim_to_decimal_in_sp:n { \l__osgstyler_lua_top_dim } @ \dim_to_decimal_in_sp:n { \l__osgstyler_lua_bottom_dim } @ \luaul_context_color: ] { \leaders\hbox_to_wd:nn { 1em } { \transparent { \l__osgstyler_lua_opacity_tl } \luaul_apply_color: \vrule width 1em height \l__osgstyler_lua_top_dim depth \l__osgstyler_lua_bottom_dim \hss } } \cs_new_protected:Npn \__osgstyler_lua_pgf_pattern:n #1 { \str_case:nnF {#1} { { solid } { \pgfsetdash { } { 0pt } } { dashed } { \pgfsetdash { { 3\l__osgstyler_lua_thickness_dim } { 2\l__osgstyler_lua_thickness_dim } } { 0pt } } { dotted } { \pgfsetroundcap \pgfsetdash { { 0pt } { 2.5\l__osgstyler_lua_thickness_dim } } { 0pt } } } { \msg_warning:nnnn { osgstyler-lua } { unsupported-pattern } {#1} { enclosure } \pgfsetdash { } { 0pt } } } % Unlike the portable \fcolorbox fallback, this renderer owns the path and can % therefore honour both corner radius and stroke pattern. The content remains % a normal tagged box while pgfcore emits the painted rectangle behind it. \cs_new_protected:Npn \__osgstyler_lua_enclosure_apply:nnn #1#2#3 { \group_begin: \__osgstyler_decoration_value:nnnnN {#1}{enclosure}{#2}{border-color} \l__osgstyler_decoration_color_tl \__osgstyler_decoration_default:Nn \l__osgstyler_decoration_color_tl { primary } \tl_set:Nx \l__osgstyler_decoration_color_tl { \OsgColorName { \l__osgstyler_decoration_color_tl } } \__osgstyler_decoration_value:nnnnN {#1}{enclosure}{#2}{fill-color} \l__osgstyler_decoration_value_tl \__osgstyler_decoration_default:Nn \l__osgstyler_decoration_value_tl { background } \tl_set:Nx \l_tmpa_tl { \OsgColorName { \l__osgstyler_decoration_value_tl } } \__osgstyler_decoration_value:nnnnN {#1}{enclosure}{#2}{pattern} \l__osgstyler_lua_pattern_tl \__osgstyler_decoration_default:Nn \l__osgstyler_lua_pattern_tl { solid } \__osgstyler_decoration_value:nnnnN {#1}{enclosure}{#2}{thickness} \l__osgstyler_decoration_value_tl \__osgstyler_decoration_default:Nn \l__osgstyler_decoration_value_tl { rule/thin } \exp_args:NV \__osgstyler_decoration_metric_resolve:nN \l__osgstyler_decoration_value_tl \l__osgstyler_decoration_value_tl \dim_set:Nn \l__osgstyler_lua_thickness_dim { \l__osgstyler_decoration_value_tl } \__osgstyler_decoration_value:nnnnN {#1}{enclosure}{#2}{padding} \l__osgstyler_decoration_value_tl \__osgstyler_decoration_default:Nn \l__osgstyler_decoration_value_tl { padding/normal } \exp_args:NV \__osgstyler_decoration_metric_resolve:nN \l__osgstyler_decoration_value_tl \l__osgstyler_decoration_value_tl \dim_set:Nn \l__osgstyler_lua_padding_dim { \l__osgstyler_decoration_value_tl + .5\l__osgstyler_lua_thickness_dim } \__osgstyler_decoration_value:nnnnN {#1}{enclosure}{#2}{radius} \l__osgstyler_decoration_value_tl \__osgstyler_decoration_default:Nn \l__osgstyler_decoration_value_tl { radius/square } \exp_args:NV \__osgstyler_decoration_metric_resolve:nN \l__osgstyler_decoration_value_tl \l__osgstyler_decoration_value_tl \dim_set:Nn \l__osgstyler_lua_radius_dim { \l__osgstyler_decoration_value_tl } \hbox_set:Nn \l__osgstyler_lua_content_box { \kern\l__osgstyler_lua_padding_dim #3 \kern\l__osgstyler_lua_padding_dim } \box_set_ht:Nn \l__osgstyler_lua_content_box { \box_ht:N \l__osgstyler_lua_content_box + \l__osgstyler_lua_padding_dim } \box_set_dp:Nn \l__osgstyler_lua_content_box { \box_dp:N \l__osgstyler_lua_content_box + \l__osgstyler_lua_padding_dim } \hbox:n { \rlap { \begin{pgfpicture} \pgfsetbaseline { 0pt } \pgfsetfillcolor { \l_tmpa_tl } \pgfsetstrokecolor { \l__osgstyler_decoration_color_tl } \pgfsetlinewidth { \l__osgstyler_lua_thickness_dim } \exp_args:NV \__osgstyler_lua_pgf_pattern:n \l__osgstyler_lua_pattern_tl \pgfsetcornersarced { \pgfpoint { \l__osgstyler_lua_radius_dim } { \l__osgstyler_lua_radius_dim } } \pgfpathrectanglecorners { \pgfpoint { \dim_eval:n { .5\l__osgstyler_lua_thickness_dim } } { \dim_eval:n { -\box_dp:N \l__osgstyler_lua_content_box + .5\l__osgstyler_lua_thickness_dim } } } { \pgfpoint { \dim_eval:n { \box_wd:N \l__osgstyler_lua_content_box - .5\l__osgstyler_lua_thickness_dim } } { \dim_eval:n { \box_ht:N \l__osgstyler_lua_content_box - .5\l__osgstyler_lua_thickness_dim } } } \dim_compare:nNnTF \l__osgstyler_lua_thickness_dim > \c_zero_dim { \pgfusepath { fill, stroke } } { \pgfusepath { fill } } \end{pgfpicture} } \box_use_drop:N \l__osgstyler_lua_content_box } \group_end: } \cs_new_protected:Npn \__osgstyler_lua_line_apply:nnn #1#2#3 { \group_begin: \__osgstyler_decoration_value:nnnnN {#1}{line}{#2}{position} \l__osgstyler_lua_position_tl \__osgstyler_decoration_default:Nn \l__osgstyler_lua_position_tl { below } \__osgstyler_decoration_value:nnnnN {#1}{line}{#2}{pattern} \l__osgstyler_lua_pattern_tl \__osgstyler_decoration_default:Nn \l__osgstyler_lua_pattern_tl { solid } \clist_if_in:nVF { solid,dashed,dotted,wavy } \l__osgstyler_lua_pattern_tl { \msg_warning:nnee { osgstyler-lua } { unsupported-pattern } \l__osgstyler_lua_pattern_tl \l__osgstyler_lua_position_tl \tl_set:Nn \l__osgstyler_lua_pattern_tl { solid } } \__osgstyler_decoration_value:nnnnN {#1}{line}{#2}{color} \l__osgstyler_decoration_color_tl \__osgstyler_decoration_default:Nn \l__osgstyler_decoration_color_tl { text } \tl_set:Nx \l__osgstyler_decoration_color_tl { \OsgColorName { \l__osgstyler_decoration_color_tl } } \__osgstyler_decoration_value:nnnnN {#1}{line}{#2}{thickness} \l__osgstyler_decoration_value_tl \__osgstyler_decoration_default:Nn \l__osgstyler_decoration_value_tl { rule/thin } \exp_args:NV \__osgstyler_decoration_metric_resolve:nN \l__osgstyler_decoration_value_tl \l__osgstyler_decoration_value_tl \dim_set:Nn \l__osgstyler_lua_thickness_dim { \l__osgstyler_decoration_value_tl } \__osgstyler_decoration_value:nnnnN {#1}{line}{#2}{offset} \l__osgstyler_decoration_value_tl \__osgstyler_decoration_default:Nn \l__osgstyler_decoration_value_tl { .1ex } \exp_args:NV \__osgstyler_decoration_metric_resolve:nN \l__osgstyler_decoration_value_tl \l__osgstyler_decoration_value_tl \dim_set:Nn \l__osgstyler_lua_offset_dim { \l__osgstyler_decoration_value_tl } \str_case:VnF \l__osgstyler_lua_position_tl { { below } { \dim_set:Nn \l__osgstyler_lua_top_dim { -\l__osgstyler_lua_offset_dim } \dim_set:Nn \l__osgstyler_lua_bottom_dim { -\l__osgstyler_lua_offset_dim -\l__osgstyler_lua_thickness_dim } } { above } { \dim_set:Nn \l__osgstyler_lua_cap_height_dim { \fontcharht\font `X } \dim_set:Nn \l__osgstyler_lua_top_dim { \l__osgstyler_lua_cap_height_dim +\l__osgstyler_lua_offset_dim +\l__osgstyler_lua_thickness_dim } \dim_set:Nn \l__osgstyler_lua_bottom_dim { \l__osgstyler_lua_cap_height_dim +\l__osgstyler_lua_offset_dim } } { through } { \dim_set:Nn \l__osgstyler_lua_top_dim { \l__osgstyler_lua_offset_dim +.5\l__osgstyler_lua_thickness_dim } \dim_set:Nn \l__osgstyler_lua_bottom_dim { \l__osgstyler_lua_offset_dim -.5\l__osgstyler_lua_thickness_dim } } } { \msg_warning:nne { osgstyler-lua } { unsupported-position } \l__osgstyler_lua_position_tl \dim_set:Nn \l__osgstyler_lua_top_dim { -\l__osgstyler_lua_offset_dim } \dim_set:Nn \l__osgstyler_lua_bottom_dim { -\l__osgstyler_lua_offset_dim -\l__osgstyler_lua_thickness_dim } } \str_if_eq:VnTF \l__osgstyler_lua_pattern_tl { solid } { \underLine [ color=\l__osgstyler_decoration_color_tl, top=\l__osgstyler_lua_top_dim, bottom=\l__osgstyler_lua_bottom_dim ] {#3} } { \luaul_set_color:n { \l__osgstyler_decoration_color_tl } \__osgstyler_lua_pattern_line: #3 } \group_end: } \cs_new_protected:Npn \__osgstyler_lua_background_apply:nnn #1#2#3 { \group_begin: \__osgstyler_decoration_value:nnnnN {#1}{background}{#2}{color} \l__osgstyler_decoration_color_tl \__osgstyler_decoration_default:Nn \l__osgstyler_decoration_color_tl { accent } \tl_set:Nx \l__osgstyler_decoration_color_tl { \OsgColorName { \l__osgstyler_decoration_color_tl } } \__osgstyler_decoration_value:nnnnN {#1}{background}{#2}{opacity} \l__osgstyler_lua_opacity_tl \__osgstyler_decoration_default:Nn \l__osgstyler_lua_opacity_tl { 1 } \fp_compare:nNnTF { \l__osgstyler_lua_opacity_tl } < { 0 } { \msg_warning:nne { osgstyler-lua } { unsupported-opacity } \l__osgstyler_lua_opacity_tl \tl_set:Nn \l__osgstyler_lua_opacity_tl { 0 } } { \fp_compare:nNnT { \l__osgstyler_lua_opacity_tl } > { 1 } { \msg_warning:nne { osgstyler-lua } { unsupported-opacity } \l__osgstyler_lua_opacity_tl \tl_set:Nn \l__osgstyler_lua_opacity_tl { 1 } } } \__osgstyler_decoration_value:nnnnN {#1}{background}{#2}{shape} \l__osgstyler_lua_shape_tl \__osgstyler_decoration_default:Nn \l__osgstyler_lua_shape_tl { text } \str_case:VnF \l__osgstyler_lua_shape_tl { { text } { \dim_set:Nn \l__osgstyler_lua_top_dim { 1.75ex } \dim_set:Nn \l__osgstyler_lua_bottom_dim { .75ex } } { line } { \dim_set:Nn \l__osgstyler_lua_top_dim { .8\baselineskip } \dim_set:Nn \l__osgstyler_lua_bottom_dim { .2\baselineskip } } } { \msg_warning:nne { osgstyler-lua } { unsupported-shape } \l__osgstyler_lua_shape_tl \tl_set:Nn \l__osgstyler_lua_shape_tl { text } \dim_set:Nn \l__osgstyler_lua_top_dim { 1.75ex } \dim_set:Nn \l__osgstyler_lua_bottom_dim { .75ex } } \luaul_set_color:n { \l__osgstyler_decoration_color_tl } \__osgstyler_lua_background: #3 \group_end: } \cs_new_protected:Npn \__osgstyler_lua_family_wrap:nnN #1#2#3 { \tl_set:Nx \l__osgstyler_decoration_instances_tl { \OsgDecorationInstances {#1}{#2} } \clist_map_inline:Vn \l__osgstyler_decoration_instances_tl { \tl_set:Nx #3 { \exp_not:c { __osgstyler_lua_#2_apply:nnn } { \exp_not:n {#1} } { \exp_not:n {##1} } { \exp_not:V #3 } } } } \cs_new_protected:Npn \__osgstyler_lua_decoration_processor:nn #1#2 { \group_begin: \__osgstyler_decoration_affixes_apply:nn {#1}{#2} \__osgstyler_lua_family_wrap:nnN {#1}{line} \l__osgstyler_decoration_content_tl \__osgstyler_lua_family_wrap:nnN {#1}{background} \l__osgstyler_decoration_content_tl \__osgstyler_lua_family_wrap:nnN {#1}{enclosure} \l__osgstyler_decoration_content_tl \OsgDecorationIfHasComponentTF {#1}{margin} { \msg_warning:nnn { osgstyler-lua } { unsupported-margin } {#1} } { } \tl_use:N \l__osgstyler_decoration_content_tl \group_end: } \cs_new_eq:NN \OsgStylerLuaDecorationProcessor \__osgstyler_lua_decoration_processor:nn \SetOsgDecorationProcessor { \OsgStylerLuaDecorationProcessor } \bool_gset_true:N \g__osgstyler_decoration_backend_bool \NewExpandableDocumentCommand \OsgStylerLuaBackendActive { } { true } \ExplSyntaxOff %