%<*driver> \def\nameofplainTeX{plain} \ifx\fmtname\nameofplainTeX \input l3docstrip.tex \preamble ======================================================================== Package seatingchart -- generation of seating charts Copyright (C) 2025-2026 Matthias Werner This work may be distributed and/or modified under the conditions of the LaTeX Project Public License, either version 1.3c or later. ======================================================================== \endpreamble \postamble This work is maintained by Matthias Werner. \endpostamble \generate{ \file{seatingchart.sty}{\from{seatingchart.dtx}{package}} \file{seatingchart-tu-chemnitz.sc}{\from{seatingchart.dtx}{rooms-tu-chemnitz}} } \nopreamble \nopostamble \generate{ \file{seatingchart.lua}{\from{seatingchart.dtx}{lua}} } \expandafter\endbatchfile \fi \documentclass[load-preamble+,title=compact]{osgdoc} \usepackage{microtype} \usepackage{enumitem} \usepackage[ languages={de,en}, map={de=german,en=english}, targetlang={job=2}, load babel ]{langselect} \usepackage{enumitem} \def\packagename{seatingchart} \def\thepkg{\pkg*{\packagename}} \makeindex \setcnltx{ name = seatingchart, package = seatingchart, version = \UseVersionOf{seatingchart.sty}, date = \UseDateOf{seatingchart.sty}, title = {\ldeen{Das \thepkg-Paket}{The \thepkg\ Package}}, info = {\ldeen{Erstellung und Visualisierung von Sitzplänen}{Generation and visualization of seating plans}}, authors = {Matthias Werner[matthias.werner@informatik.tu-chemnitz.de]}, abstract = {\ldeen{ Das Paket ermöglicht es, Sitzpläne, wie sie z. B. für Prüfungen benötigt werden, einfach zu erstellen. Verschiedene automatische Platzierungsschemata sind vordefiniert; eigene, feingranulare Belegungen sind ebenfalls möglich. }{ This package allows for easy creation of seating charts, such as those required for examinations. Several automatic placement schemes are predefined, while custom, fine-grained assignments are also possible. }\par \textbf{\ldeen*{ Achtung! Ab Version 0.6 ist die \env{seatingchart}-Umgebung die zentrale Nutzerschnittstelle. Dies ist ein API-Bruch; für Rückwärtskompatibilität siehe Abschnitt @1. }{ Warning! Starting with version 0.6, the \env{seatingchart} environment is the central user interface. This is an API break; for backward compatibility, see section @1. }{\ref{sec:legacy}} }}, url = https://github.com/tuc-osg/seatingchart, build-title } \begin{document} \section{\ldeen{Einführung}{Introduction}} \ldeen{% Für die Durchführung von Prüfungen benötigen wir mitunter Sitzpläne. So haben sich über die einige mit Pläne in Form von TikZ-unterstützten \LaTeX-Dateien angesammelt. Je nach Anzahl der Studierenden in einer Prüfung (und für wie groß wir die Gefahr eines Betrugsversuches bewerten) nutzen wir unterschiedliche Platzierungsschemata, so dass wir die Dateien in anpassen müssen. Außerdem wird uns von Zeit zu Zeit ein neuer Raum zugewiesen, für den wir noch keine Pläne haben. }{% To conduct exams, we occasionally require seating plans. Over time, we have accumulated several such plans in the form of TikZ-supported \LaTeX\ files. Depending on the number of students in an exam—and how high we assess the risk of attempted cheating—we apply different placement schemes, which requires adapting the files accordingly. Moreover, we are sometimes assigned new rooms for which no seating plans exist yet. } \ldeen*{% Dies war die Motivation zur Erschaffung des @1-Pakets. Es \ldots }{% This was the motivation for developing the @1\ package. It \ldots }{\thepkg} \begin{itemize}[itemsep=0pt] \item \ldeen{ermöglicht eine schnelle und einfach Erstellung von Sitzplänen;}{enables quick and easy creation of seating plans;} \item \ldeen{trennt das Sitzlayout und das Platzierungsschema voneinander;}{separates seat layout from the placement scheme;} \item \ldeen{bietet eine Reihe von Standardschemata für die Platzierung an;}{offers a set of standard placement schemes;} \item \ldeen{enthält bereits eine Anzahl vordefinierter Räume mit Layouts der Sitze;}{includes a number of predefined rooms with seat layouts;} \item \ldeen{erlaubt eine \emph{Ad-hoc}-Erstellung neuer Räume und Platzierungsschemata.}{allows for \emph{ad hoc} creation of new rooms and placement schemes.} \end{itemize} \section{\ldeen{Abhängigkeiten}{Dependencies}} \ldeen*{% Das @1-Paket arbeitet nur mit Lua\LaTeX\ und benötigt eine hinreichend moderne \LaTeX-Version, mindestens vom Juli 2022. Es lädt folgende Pakete: }{% The @1\ package works only with Lua\LaTeX\ and requires a sufficiently modern \LaTeX\ version, at least from July 2022. It loads the following packages: }{\thepkg} \begin{itemize}[nosep] \item \pkg{etoolbox} \item \pkg{luacode} \item \pkg{tikz} \end{itemize} \ldeen*{% Diese Pakete sind in allen gängigen \TeX-Distributionen vorhanden und haben wiederum andere Pakete als Abhängigkeit. Insbesondere wird durch @1 das Paket @2 geladen, dessen Farbdefinition auch in @3\ verwendet werden. }{% These packages are available in all major \TeX\ distributions and themselves depend on other packages. In particular, @1 loads the @2 package, whose color definitions are also used by @3. }{\pkg*{tikz}}{\pkg{xcolor}}{\thepkg} \section{\ldeen{Sitzlayout}{Seat Layout}} \label{sec:layout} \ldeen*{% Das Paket wird mit @1\{@2\} geladen. Jeder Sitzplan steht in einer eigenen Umgebung: }{% The package is loaded with @1\{@2\}. Each seating chart is placed in its own environment: }{\cs{usepackage}}{\thepkg} \begin{quote} \ldeen*{% @1\{seatingchart\}\oarg{Optionen}\quad\ldots\quad @2\{seatingchart\} }{% @1\{seatingchart\}\oarg{options}\quad\ldots\quad @2\{seatingchart\} }{\cs{begin}}{\cs{end}} \end{quote} \ldeen*{% Die Optionen legen das Layout und seine Darstellung fest. Innerhalb eines Dokuments dürfen beliebig viele, unterschiedlich konfigurierte Sitzpläne vorkommen. Alle @1-Ausführungsbefehle sind nur innerhalb der Umgebung gültig, mit Ausnahme der Deklarationsbefehle für Raumdateien, siehe Abschnitt @2. }{% The options define the layout and its presentation. A document may contain any number of independently configured seating charts. All executing @1 commands are available only inside the environment with exception of the room declaration commands, cf.\ Section @2. }{\cs*{sc...}}{\ref{sec:new-rooms}} \subsection{\ldeen{Umgebungsoptionen}{Environment Options}} \label{sec:class-options} \ldeen*{% Durch die Nutzung bzw. Nichtnutzung der Option @1 werden zwei grundsätzliche Anwendungsfälle unterschieden: }{% The use of the option @1 distinguishes between two fundamental use cases: }{\option{room}} \begin{options} \ldeen*{% \keyval-@1{Raum}\default{} }{% \keyval@1{Room} This option key should be used if the desired room }{{room}} \ldeen{% Diesen Optionsschlüssel sollte man nutzen, wenn der gewünschte Raum ist bereits in der Datenbank von \thepkg\ vorhanden ist. }{% is already predefined. } \ldeen*{% \keyval@1{Institution}\Default{tu-chemnitz} }{% \keyval@1{institution}\Default{tu-chemnitz} }{{layout}} \ldeen*{% Die Daten für die vordefinierten Räume werden aus der Datei @1\meta{Institution}@2 gelesen. Dieser Schlüssel kann genutzt werden, wenn eigene vordefinierte Räume deklariert wurden, siehe Abschnitt~@3. }{% The data for the predefined rooms is read from @1\meta{institution}@2. This key can be used if own predefined rooms have been declared, see Section~@3. }{\code{seatingchart-}}{\code{.sc}}{\ref{sec:new-rooms}} \end{options} \ldeen{% Falls der Raum noch \textbf{nicht} bekannt ist, werden einige Schlüssel-Wert-Paar zur Beschreibung des Raumslayout gebraucht. }{% If the room is \textbf{not} already known, it can also be created \emph{ad hoc}, whereas several key–value options are required to describe the layout: } \begin{options} \keychoice{shape}{rectangle,arc}\Default{rectangle} \ldeen{% Hier wird beschrieben, ob das Layout der Sitze rechteckig (\choices{rectangle}) oder bogenförmig (\choices{arc})\footnote{Z.\,B.\ an der TU~Chemnitz der Raum A10.316.} ist. }{% Specifies whether the seating layout is rectangular (\choices{rectangle}) or curved (\choices{arc}).% \footnote{For example, room A10.316 at TU~Chemnitz uses an arc layout.} } \ldeen*{% \keyval-@1{Anzahl der Sitzreihen}\Default*!{}%\vspace{-.3\baselineskip} }{% \keyval@1{number}\Default*!{}%\vspace{-.3\baselineskip} }{{rows}} \ldeen*{% \keyval-@1{Sitze pro Sitzreihe}\Default!{} }{% \keyval@1{number}\Default!{} }{{seats per row}} % \ldeen{% Besimmt Anzahl der Sitzreihen und Sitze pro Reihe. Dabei muss immer von der jeweils maximalen Anzahl ausgegangen werden. Für unvollständige Reihen werden später Sitze entfernt, aber es können keine Sitze mehr zugefügt werden, die außerhalb des einmal festgelegten Layoutrahmens liegen. \textbf{Achtung:} Auch Gänge zwischen Blöcken von Sitzen müssen hier als Sitze mitgezählt werden. }{% Defines the number of seat rows and seats per row. The maximum number must always be specified here. For incomplete rows, seats will be removed later, but no additional seats can be added that lie outside the originally defined layout area. \textbf{Note:} Aisles between blocks of seats must be counted here as seats as well. } \end{options} \ldeen*{% Wenn Sie bei den Umgebungsoptionen \textbf{sowohl} den Schlüssel @1, \textbf{als auch} einen der Schlüssel @2 oder @3 angeben, ist das Ergebnis unbestimmt. Entsprechend wird eine Warnung ausgegeben.\medskip }{% If you specify \textbf{both} the @1 key \textbf{and} one of the keys @2 or @3 in the environment options, the result is undefined. A warning will be issued in that case.\medskip }{\option*{room}}{\option*{rows}}{\option*{seats per row}} \ldeen*{% Alle weiteren Umgebungsoptionen legen die Darstellung des Raumlayouts fest. Sie können auch später mit @1 gesetzt werden, vgl.\ Abschnitt~@2. }{% All further environment options define the visual representation of the room layout. They can also be set later using @1; see Section~@2. }{\cs{scConfig}}{\ref{sec:late-options}} \begin{options} \keybool{blackboard}\Default{false} \ldeen{% Zeichnet eine Tafel ein. }{% Draws a blackboard. } \ldeen*{% \keyval-@1{Abstand}\Default{2pt} }{% \keyval@1{distance}\Default{2pt} }{{seat distance}} % \ldeen{% Abstand der Sitze zueinander. Damit wird auch der Reihenabstand bestimmt. Möchte man beides unterschiedlich haben, so muss man die beiden folgenden Schlüssel nutzen: }{% Distance between seats. This also determines the row spacing. If you want these two values to differ, you must use the following keys: } \ldeen*{% \keyval-@1{Abstand}\Default*{2pt}%\vspace{-.3\baselineskip} }{% \keyval@1{Distance}\Default*{2pt}%\vspace{-.3\baselineskip} }{{seat neighbor distance}} % \ldeen*{% \keyval-@1{Abstand}\Default{2pt} }{% \keyval@1{distance}\Default{2pt} }{{row distance}} \keychoice{rownumbers}{none,left,right,both}\Default{none} \ldeen{% Legt beim Rechteck-Layout fest, ob links und oder rechts der Reihen die Nummer der Sitzreihe angegeben wird. Dabei wird von vorn (Tafel) gezählt.\newline \emph{Anmerkung:} Für das bogenförmige Layout ist diese Funktion derzeit nicht implementiert. }{% For rectangular layouts, defines whether to display row numbers on the left, right, or both sides. Counting starts at the front (blackboard side).\newline \emph{Note:} For arc-shaped layouts, this function is currently not implemented. } \ldeen*{% \keyval-@1{Abstand}\Default{2pt} }{% \keyval@1{distance}\Default{2pt} }{{rownumber distance}} % \ldeen*{% Abstand der Reihennummern zu den äußeren Sitzen, wenn @1 nicht @2 ist. }{% Distance between the row number and the outermost seats, if @1 is not @2. }{\option*{rownumbers}}{\code{none}} \ldeen*{% \keyval-@1{Farbe}\Default*{lightgray!20} }{% \keyval@1{color}\Default*{lightgray!20}%\vspace{-.3\baselineskip} }{{empty seat background color}} % \ldeen*{% \keyval-@1{Farbe}\Default*{lightgray}%\vspace{-.3\baselineskip} }{% \keyval@1{color}\Default*{lightgray}%\vspace{-.3\baselineskip} }{{empty seat border color}} % \ldeen*{% \keyval-@1{Farbe}\Default*{lightgray!30}%\vspace{-.3\baselineskip} }{% \keyval@1{color}\Default*{lightgray!30}%\vspace{-.3\baselineskip} }{{assigned seat background color}} % \ldeen*{% \keyval-@1{Farbe}\Default{black} }{% \keyval@1{color}\Default{black} }{{assigned seat border color}} % \ldeen{% Legt die Farben für den Hintergrund und die Umrandung von leeren bzw.\ durch ein Sitzschema belegten Sitzen fest. Falls die Sitze nicht farblich unterschieden werden sollen, können auch die Schlüssel }{% Defines background and border colors for empty or assigned seats. The syntax of color names follows the specification of the \pkg*{xcolor} package. If the seats should not be differentiated by color, the keys } % \ldeen*{% \keyval-@1{Farbe}\Default*\vspace{-.3\baselineskip} }{% \keyval-@1{color}\Default*\vspace{-.3\baselineskip} }{{seat background color}} % \ldeen*{% \keyval-@1{Farbe} }{% \keyval-@1{color} }{{seat border color}} % \ldeen{% verwendet werden. }{% can also be used. } \ldeen*{% \keyval@1{Fontbefehl}\Default*{@2}%\vspace{-.3\baselineskip} }{% \keyval@1{font command}\Default*{@2}%\vspace{-.3\baselineskip} }{{assigned seat label font}}{\cs*{small}} % \ldeen*{% \keyval-@1{Farbe}\Default{black} }{% \keyval-@1{color}\Default{black} }{{assigned seat label color}} \ldeen{% Setzt die Größe und Farbe der Sitzbeschriftung. }{% Sets the size and color of the seat label. } \end{options} \subsection{\ldeen{Späte Optionswahl}{Late Option Selection}} \label{sec:late-options} \begin{commands} \ldeen*{% @1[\marg{Schlüsselliste}] Mit diesem Kommando können außer }{% @1[\marg{key list}]This command can be used to set all keys mentioned in }{\command{scConfig}} \ldeen*{% @1, @2, @3 und @4 alle im Abschnitt~@5 genannten Schlüssel auch nach Beginn der \env*{seatingchart}-Umgebung gesetzt werden. }{% Section~@5, except for @1, @2, @3, and @4, after the \env*{seatingchart} environment has begun. }{\option{room}}{\option{shape}}{\option{rows}}{\option{seats per row}}{\ref{sec:class-options}} \end{commands} \subsection{\ldeen{Modifikation des Sitzlayouts}{Modifying the Seat Layout}} \label{sec:modify-layout} \ldeen{% Häufig ist sind nicht in jeder Reihe alle Sitze vorhanden. Bei Angabe eine Raums ist dies bereits berücksichtigt, aber auch hier kann es vorkommen, dass beispielsweise ein Klappsitz defekt ist und aus dem Layout genommen werden muss. Beim Erstellen eines eigenen Layouts ist die Modifikation die Regel. Dafür stellt \pkg\ folgende Kommandos zur Verfügung }{% Often, not all seats are present in every row. This is already taken into account when specifying a predefined room, but it may still be necessary to remove individual seats — for instance, if a folding seat is broken. When creating a custom layout, such modifications are typically required. For this purpose, \pkg\ provides the following commands: } \begin{commands} \ldeen*{% @1[\marg{Reihe}\marg{Sitznummer}] }{% @1[\marg{row}\marg{seat number}] }{\command{scRemoveSeatAt}} % \ldeen{% Entfernt den Sitz \meta{Sitznummer} in der Reihe \meta{Reihe} aus dem Layout. Dabei beziehen sich \meta{Reihe} und \meta{Sitznummer} auf das vollständige Layout, ändern sich also nicht durch bereits entfernte Sitze. Wenn die \meta{Sitznummer} negativ ist, wird von rechts aus gezählt }{% Removes seat \meta{seat number} in row \meta{row} from the layout. The values refer to the original layout and are unaffected by previously removed seats. If \meta{seat number} is negative, counting starts from the right. } \ldeen*{% @1[\marg{Liste}] }{% @1[\marg{list}] }{\command{scRemoveSeats}} % \ldeen*{% Entfernt alle in der Liste vorkommenden Sitze aus dem Layout. Die Liste enthält dabei kommaseparierte Einträge der Form \code{\{\meta{Reihe},\meta{Sitznummer}\}}. Für \meta{Reihe} und \meta{Sitznummer} gilt wie bei @1 das urspüngliche Layout. Im folgenden Beispiel werden die ersten drei Sitze links und der erste Sitz rechts in der ersten Reihe entfernt: }{% Removes all seats listed in the comma-separated \meta{list}, where each entry is of the form \code{\{\meta{row},\meta{seat number}\}}. As with @1, row and seat numbers refer to the original layout. In the following example, the first three seats on the left and the first seat on the right in the first row are removed: \vspace{-.3\baselineskip} }{\cs*{scRemoveSeatAt}} \begin{example}[code-only] \scRemoveSeats{{1,1},{1,2},{1,3},{1,-1}} \end{example}\vspace{-1.3\baselineskip} \ldeen*{% @1[\Oarg{\meta{Startreihe}-\meta{Endreihe}}\marg{Sitznummer}] }{% @1[\Oarg{\meta{start row}-\meta{end row}}\marg{seat number}] }{\command{scSetAisle}} % \ldeen*{% %\Default{\meta{erste Reihe}-\meta{letzte Reihe}} An der Stelle von \meta{Sitznummer} wird durch alle Reihen ein Gang eingefügt. Nutzen Sie das optionale Argument, wenn der Gang nicht alle Reihen erfassen soll (Stichgang). Für horizontale Gänge (die also einen Sitzblock in einen vorderen und hinteren Teil zerlegen, anstatt einen rechten und einen linke) nutzen Sie bitte @1. Das ist im Prinzip auch bei vertikalen Gängen möglich, jedoch können bei der automatischen Sitzschemazuweisung mit @2 erzeugte Gänge anders als die mit @1 erzeugten Gänge behandelt werden. }{% Inserts an aisle at the position of \meta{seat number} through all rows. Use the optional argument if the aisle should not span all rows. For horizontal aisles (which split a seat block into front and back instead of left and right), please use @1. While this is also possible for vertical aisles, automatically generated seating schemes may treat aisles created via @2 differently than those created with @1. }{\cs*{scRemoveSeats}}{\cs*{scSetAisle}} \end{commands} \subsection{\ldeen{Ausgabe}{Output}} \label{sec:drawing} \begin{commands} \ldeen*{% @1[\Oarg{seat width=\meta{Breite}, seat height=\meta{Höhe}}] }{% @1[\Oarg{seat width=\meta{width}, seat height=\meta{height}}] }{\command{scDrawSeating}} % \ldeen*{% Gibt den Sitzplan vorzeitig oder mit expliziten Abmessungen aus. Ohne einen Aufruf wird der Plan automatisch am Ende der \env*{seatingchart}-Umgebung gezeichnet. Dabei versucht @1, die Ausmaße der Sitze so zu berechnen, dass der zur Verfügung stehenden Platz des Gesamtplans vollständig ausgenutzt wird. Da damit nicht immer alle Bedürfnisse getroffen werden, können in der über das optionale Argument die Breite und Höhe der Sitze auf dem Sitzplan einzeln eingestellt werden. }{% Renders the seating plan early or with explicit dimensions. If it is not called, the chart is rendered automatically at the end of the \env*{seatingchart} environment. @1\ attempts to calculate the seat dimensions so that the available space for the complete plan is fully utilized. Since this may not always meet all needs, the optional argument allows the width and height of the seats on the plan to be adjusted individually. }{\thepkg} \end{commands} \ldeen{% Das folgende Beispiel zeigt den Code und das Ergebnis für ein einfaches Sitzplanlayout mit einem Mittelgang und zwei fehlenden Sitzen in der ersten Reihe. }{% The following example shows the code and the result for a simple seating layout with a central aisle and two missing seats in the first row. } \begin{example}[compile,program=lualatex,pages=1,add-frame,code-sep=\bigskip,graphics={scale=.25}] \documentclass{article} \usepackage{seatingchart} \begin{document} \begin{seatingchart}[shape=rectangle, rows=9, seats per row=15] \scSetAisle{8} \scRemoveSeats{{1,1},{1,2}} \end{seatingchart} \end{document} \end{example} \section{\ldeen{Sitzplatzbelegung}{Seat Assignment}} \label{sec:seat-assignment} \subsection{\ldeen{Parameter zur Konstruktion von Belegungsschemata}{Parameters for Constructing Assignment Schemes}} \label{sec:constr-scheme} \ldeen*{% Natürlich ist ein Sitzplan ohne die Markierung von belegten Plätzen nur bedingt nützlich. Die Belegung wird in @1\ mit Sitzplatzschemata realisiert. Zur Erzeugung eines Sitzschemas gibt es den Befehl }{% A seating plan is only partially useful without indicating which seats are occupied. In @1, seat assignment is handled via assignment schemes. To define such a scheme, the command }{\thepkg} \begin{commands} \ldeen*{% @1[\oarg{Schlüsselliste}\marg{Name}] %\vspace{-1.3\baselineskip} }{% @1[\oarg{key list}\marg{name}] %\vspace{-1.7\baselineskip} }{\command{scSeatingScheme}} \ldeen*{% @1[\sarg\oarg{Schlüsselliste}\marg{Schema}] }{% @1[\sarg\oarg{key list}\marg{pattern}] }{\command{scSeatingScheme}} % \ldeen*{% Das Pflichtargument enthält in der Standardvariante des Befehls eine Bezeichnung für ein vorgegebenes Sitzschema, siehe Abschnitt~@1. In der Sternvariante wird hier eine Zeichenkette von \enquote{@2} und \enquote{@3} übergeben, die (den Anfang) eines Sitzschemas für eine einzelne Reihe beschreibt, wobei @2 für einen belegten und @3 für einen leeren Platz steht. Ist die Zeichenkette kürzer als die Anzahl der Sitze in der Reihe, wird sie wiederholt angewendet. Reihe mehr Plätze als }{% The mandatory argument, in the standard version of the command, is a name for a predefined seating scheme (see Section~@1). In the starred version, a string of \enquote{@2} and \enquote{@3} is passed, describing (the beginning of) a row assignment pattern, where @2 denotes an occupied seat and @3 an empty one. If the string is shorter than the number of seats in a row, it will be repeated. }{\ref{sec:defined-schemes}}{\code{X}}{\code{-}} \end{commands} \ldeen{% Beispielsweise wird mit }{% For example, } \begin{example}[code-only] \string\scSeatingScheme*{X--} \end{example}\vspace{-1.3\baselineskip} \ldeen{% jeder dritte Sitz belegt. }{% marks every third seat as occupied. } \ldeen{% Im optionalen Argument kann wieder eine Liste von Schlüssel-Wert-Paaren übergeben werden, die die restlichen Einstellung für das Sitzschema steuern. Es ist insbesondere in der Sternvariante wichtig, kann aber auch in der Standardvariante genutzt werden. werden: }{% The optional argument accepts a list of key–value pairs that control the remaining settings of the assignment scheme. These are especially important in the starred variant but may also be used in the standard one. } \begin{options} \ldeen*{% \keyval-@1{Anzahl}\Default{2} }{% \keyval-@1{number}\Default{2} }{{row sep}} % \ldeen{% Legt fest, in jeder wievielten Reihe Sitze belegt werden. }{% Specifies how many rows should lie between two occupied rows. } \ldeen*{% \keyval-@1{Nummer}\Default*{1}\vspace{-.3\baselineskip} }{% \keyval-@1{row} }{{start row}} % \ldeen{Erste Reihe, auf die das Belegungsschema angewendet wird.}{% First row to be included in the assignment.} % \ldeen*{% \keyval-@1{Nummer}\Default{\meta{Anzahl Reihen}} }{% \keyval-@1{row} }{{end row}} % \ldeen{Letzte Reihe, auf die das Belegungsschema angewendet wird.}{% Last row to be included in the assignment.} % \ldeen{% Durch diese beiden Schlüssel wird gesteuert, in welchen Reihen ein Schema angewendet wird. Dadurch können unterschiedliche Schemata für verschiedene Reihen genutzt werden. }{% Together, these keys control the rows to which a scheme is applied. This allows different schemes to be used for different rows. } % \ldeen*{% \keyval-@1{Anzahl}\Default }{% \keyval-@1{number}\Default }{{row restart after}} % \ldeen*{% Das Sitzschema wird nach \meta{Anzahl} Reihen zurückgesetzt. Damit wird es möglich, alternanierende Reihenabstände zu erzielen, vergleiche Beispiel in Abschnitt~@1. }{% Resets the seat pattern after \meta{number} rows. This allows for alternating row spacing; see the example in Section~@1. }{\ref{sec:ex:own1}} % \ldeen*{% \keyval-@1{Anzahl}\Default{1} }{% \keyval-@1{number}\Default{1} }{{aisle counts}} % \ldeen{% Legt fest, wie viele Sitze ein Gang zählt. Dadurch kann berücksichtigt werden, dass ein Gang häufig breiter als ein Sitzplatz ist. }{% Specifies how many seats wide an aisle should be considered. This allows for wider aisles to be taken into account. } % \keybool{aisle restarts scheme}\Default{false} \ldeen{% Startet das Schema nach einem Gang erneut. Der Schlüssel \option*{aisle counts} wird damit wirkungslos. }{% Restarts the scheme after an aisle. In that case, the \option*{aisle counts} key has no effect. } % \keybool{ignore aisle}\Default*{false}\vspace{-.3\baselineskip} \keybool{ignore removed seats}\Default{false} \ldeen*{% Bei der Zählung von Sitzen, die im Platzlabel genutzt werden kann (vgl.\ Abschnitt~@1) werden auch entfernte Sitze und Gänge mitgezählt. Dies ist sinnvoll, damit in Reihen (im Rechteck-Grundlayout) sich gleiche Sitznummern hintereinander befinden. Wird einer der Schlüssel gesetzt, werden entfernte Sitze bzw. Gänge nicht mitgezählt. Dies bietet sich insbesondere im Bogen-Grundlayout an. }{% When determining seat numbers (used e.g. in labels; see Section~@1), aisles and removed seats are normally counted. This ensures that seat numbers are consistent across rows (in rectangular layouts). If one of these keys is set, aisles or removed seats are no longer counted. This is particularly useful in curved layouts. }{\ref{sec:seat-label}} % \ldeen*{% \keyval@1{Formatzeichenkette}\Default{m\{\{\string\,\}\}D} }{% \keyval@1{format string}\Default{m{{\,}}D} }{{assigned seat label}} % \ldeen*{% Über diesen Schlüssel kann festgelegt werden, wie die belegten Sitze beschriftet werden. Voreingestellt ist die Nummer der Reihe, gefolgt von einem schmalen Leerzeichen und einem Buchstaben für den laufenden belegten Sitz (z.\,B. \enquote{3\,c}). Es sind hier eine Reihe anderer Möglichkeiten einstellbar. Details dazu sind im Abschnitt~@1 beschrieben. }{% Specifies how assigned seats should be labeled. By default, this consists of the row number, a thin space, and a letter for the current occupied seat (e.g., \enquote{3\,c}). A number of other formats can be configured; see Section~@1 for details. }{\ref{sec:seat-label}} \end{options} \begin{commands} \ldeen*{% \command{scConfigScheme\marg{Schlüsselliste}} Setzt die Schlüssel wie beim optionalen Argument von @1, führt aber keine Zuweisung von Sitzen aus. }{% \command{scConfigScheme\marg{key list}} Sets keys just like the optional argument of @1, but does not assign seats. }{\cs*{scSeatingScheme}} \end{commands} \ldeen*{% Schlüsselwerte, die durch @1 oder @2 gesetzt werden, bleiben erhalten, bis sie explizit neu gesetzt werden. }{% Key values set by @1 or @2 remain in effect until they are explicitly redefined. }{\cs*{scSeatingScheme}}{\cs*{scConfigScheme}} \subsection{\ldeen{Vordefinierte Sitzschemata}{Predefined Seating Schemes}} \label{sec:defined-schemes} \ldeen*{% Das @1-Paket definiert eine Reihe vordefinierter Platzierungsschemata. Manche haben auch einen Alternativnamen. }{% The @1\ package defines a set of predefined seating schemes. Some of them also have alternative names. }{\thepkg} \begin{options} \ldeen*{% \opt{1x1} Jeder Platz ist markiert.\newline Alternativname: @1 \opt{2x2} Zwischen zwei belegten Plätzen ist jeweils ein Platz bzw.\ eine Reihe Abstand.\newline Alternativname: @2 \opt{2x2-} Zwischen zwei belegten Plätzen ist jeweils ein freier Platz. Nach einer freien Reihe kommen \emph{zwei} Reihen mit belegten Plätzen. Dies ist das Schema, mit dem in einer Prüfung die größtmögliche Zahl an Studierenden in einem Raum untergebracht werden können, aber trotzdem ein seitlicher Minimalabstand gegeben ist und sich die Aufsicht zu jedem Studierenden kommen kann (entweder von vorn, oder von hinten). \newline Alternativname: @3 \opt{2x3} Belegte Sitze haben einen seitlichen Abstand von zwei Sitzen und belegte Reihen einen Abstand von einer Leerreihe. Dies wird in unser Gruppe als das anzustrebende Standardschema für Prüfungen betrachtet.\newline Alternativname: @4 \opt{2x3-} Die Reihen werden wie bei @5 belegt, der seitliche Abstand innerhalb einer belegten Reihe beträgt jedoch zwei Sitze. \opt{2x4} Belegte Sitze haben einen seitlichen Abstand von drei Sitzen und belegte Reihen einen Abstand von einer Leerreihe. \opt{3x4} Belegte Sitze haben einen seitlichen Abstand von drei Sitzen und belegte Reihen einen Abstand von zwei Leerreihen. }{% \opt{1x1} Every seat is marked.\newline Alternative name: @1 \opt{2x2} One empty seat and one empty row between two occupied ones.\newline Alternative name: @2 \opt{2x2-} One empty seat between two occupied ones. After a single empty row, \emph{two} rows with occupied seats follow. This scheme allows the largest number of students in a room during an exam while still maintaining minimal lateral distance and enabling supervision to reach every student (from the front or the back).\newline Alternative name: @3 \opt{2x3} Occupied seats have two empty seats between them laterally, and one empty row between occupied rows. This is considered the preferred default scheme for exams in our group.\newline Alternative name: @4 \opt{2x3-} Rows are occupied as in @5, but the lateral spacing within an occupied row is two seats. \opt{2x4} Occupied seats have three empty seats between them laterally, and one empty row between occupied rows. \opt{3x4} Occupied seats have three empty seats between them laterally, and two empty rows between occupied rows. }{\option{all}}{\option{simple}}{\option{dense}}{\option{sixpack}}{\option{2x2-}} \end{options} \subsection{\ldeen{Sitzbeschriftung}{Seat Labeling}} \label{sec:seat-label} \ldeen*{% Die Beschriftung der zugewiesenen Sitzplätze kann auf unterschiedliche Weise erfolgen, die durch Zuweisung einer Formatzeichenkette an den Schlüssel @1 gesteuert wird. Es stehen dafür vier Zähler zur Verfügung: }{% The labeling of assigned seats can be done in various ways, controlled by assigning a format string to the key @1. Four counters are available for this purpose: }{\option{assigned seat label}} \begin{itemize} \item \ldeen{absolute Reihe: die Nummer der Reihe im Sitzlayout}{absolute row: the row number in the seat layout} \item \ldeen{laufende Reihe: die Nummer der \emph{belegten} Reihe. Reihen, in denen}{occupied row: the number of the \emph{assigned} row.} \ldeen{% keine Sitz zugewiesen werden, werden hier übersprungen. }{% Rows without any assigned seats are skipped. } \item \ldeen{absolute Sitznummer: die Nummer des Sitzes im Sitzlayout. Ob hier auch}{absolute seat number: the seat number in the seat layout.} \ldeen*{% entfernte Sitze berücksichtigt werden, hängt vom Wert des Schlüssels @1 ab. }{% Whether removed seats are counted depends on the value of @1. }{\option*{ignore removed seats}} \item \ldeen{laufende Sitznummer: die Nummer der \emph{belegten} Sitze. Unbelegte Sitze}{occupied seat number: the number of the \emph{assigned} seats.} \ldeen{% werden bei der Zählung übersprungen. }{% Unassigned seats are skipped in this count. } \end{itemize} \ldeen{% Jeder dieser Zähler kann unterschiedlich formatiert werden. Dafür werden in der Formatzeichenkette verschiedene Formatierungszeichen benutzt, die in der folgenden Tabelle gelistet sind:\medskip }{% Each of these counters can be formatted differently. For this, the format string uses format specifiers listed in the following table:\medskip } \begin{tabular}{llccccc}\hline \ldeen{% \textbf{\sffamily Zähler}&\textbf{\sffamily Anmerkung}&\multicolumn{5}{c}{\textbf{\sffamily Darstellung als\ldots}}\\ &&\rotatebox{90}{(arabische) Zahl}&\rotatebox{90}{Kleinbuchstabe}&\rotatebox{90}{Großbuchstabe}& \rotatebox{90}{kleine römische Zahl}&\rotatebox{90}{große römische Zahl}\\\hline }{% \textbf{\sffamily Counter}&\textbf{\sffamily Description}&\multicolumn{5}{c}{\textbf{\sffamily Rendered as\ldots}}\\ &&\rotatebox{90}{Arabic numeral}&\rotatebox{90}{lowercase letter}&\rotatebox{90}{uppercase letter}& \rotatebox{90}{lower Roman numeral}&\rotatebox{90}{upper Roman numeral}\\\hline } \ldeen*{absolute Reihe&\emph{bezieht sich auf das Sitzlayout}&@1&@2&@3&@4&@5\\}{% absolute row&\emph{based on seat layout}&@1&@2&@3&@4&@5\\}% {\code{m}}{\code{a}}{\code{A}}{\code{y}}{\code{Y}} \ldeen*{laufende Reihe&\emph{bezieht sich auf das Belegungsschema}&@1&@2&@3&@4&@5\\}{% occupied row&\emph{based on assigned rows}&@1&@2&@3&@4&@5\\}% {\code{r}}{\code{b}}{\code{B}}{\code{i}}{\code{I}} \ldeen*{absolute Sitznummer&\emph{bezieht sich auf das Sitzlayout}&@1&@2&@3&@4&@5\\}{% absolute seat number&\emph{based on seat layout}&@1&@2&@3&@4&@5\\}% {\code{n}}{\code{c}}{\code{C}}{\code{x}}{\code{X}} \ldeen*{laufende Sitznummer&\emph{bezieht sich auf das Belegungsschema}&@1&@2&@3&@4&@5\\\hline}{% occupied seat number&\emph{based on assigned seats}&@1&@2&@3&@4&@5\\\hline}% {\code{s}}{\code{d}}{\code{D}}{\code{j}}{\code{J}} \end{tabular}\medskip \ldeen{% \noindent Teile der Beschriftung, die nicht als Formatierungszeichen interpretiert werden sollen, werden in doppelte geschweifte Klammern gesetzt (\code{\{\{\meta{geschützte Zeichenkette}\}\}}). Beispielsweise erzeugt }{% \noindent Parts of the label that should not be interpreted as format specifiers must be wrapped in double braces: \code{\{\{\meta{protected string}\}\}}. For example,\vspace{-1.3\baselineskip} } \begin{example}[code-only] \scSeatingScheme[assigned seat label=Y{{-}}D]{2x3} \end{example}\vspace{-1.3\baselineskip} \ldeen{% beim vierten Sitz (von links) in der dritten Reihe die Beschriftung \enquote{III-B}. }{% produces the label \enquote{III-B} for the fourth seat (from the left) in the third row. } \subsection{\ldeen{Sitzplatzliste}{Seat List}} \label{sec:seats-list} \ldeen*{% Insbesondere wenn es um den Anwendungsfall einer Prüfung geht, ist es nützlich, eine Sitzplatzliste zu erhalten, also eine tabellarische Zuordnung zwischen Prüfling und Sitzplatz. In der aktuellen Version erstellt @1\ keine (\LaTeX)-Tabelle, kann aber die Tabellenerstellung (mit \LaTeX\ oder einer Tabellenkalkulation) unterstützen. }{% Especially in the context of examinations, it is useful to generate a seat list, i.e., a tabular mapping between student and assigned seat. In its current version, @1\ does not create a \LaTeX\ table, but it can support table generation (e.g., using \LaTeX\ or a spreadsheet application). }{\thepkg} \begin{commands} \ldeen*{% @1[\oarg{Eingabedatei}\marg{Ausgabedatei}]\vspace{-1.3\baselineskip} }{% @1[\oarg{input file}\marg{output file}]\vspace{-1.3\baselineskip} }{\command{scGenerateSeatingList}} \ldeen*{% @1[\sarg\oarg{Eingabedatei}\marg{Ausgabedatei}] }{% @1[\sarg\oarg{input file}\marg{output file}] }{\command{scGenerateSeatingList}} \ldeen{% Erstellt eine CVS-Datei \meta{Ausgabedatei} mit den Labels der belegten Plätze, jeweils einen pro Zeile.\medskip }{% Creates a CSV file \meta{output file} containing the labels of the assigned seats, one per line.\medskip } \ldeen*{% \framebox{\parbox{\linewidth}{ \textbf{Achtung!} Die in die \meta{Ausgabedatei} geschriebenen Labeltexte werden aus den entsprechenden \LaTeX-Boxen extrahiert und nur die druckbaren Zeichen ausgegeben. Es empfiehlt sich bei Nutzung dieser Funkion daher, in der Formatzeichenkette von @1 auf zuviel \TeX-Magie zu verzichten. }}\medskip }{% \framebox{\parbox{\linewidth}{ \textbf{Note!} The label texts written to \meta{output file} are extracted from the corresponding \LaTeX\ boxes, and only printable characters are included. When using this function, it's therefore recommended to avoid excessive \TeX-level formatting in the @1 format string. }}\medskip }{\option*{assigned seat label}} \ldeen{% In der Sternvariante werden in jeder Zeile die absoluten Koordinaten (Reihe, Platznummer, bezogen auf das Grundlayout) kommasepariert dem Labeln vorangestellt. }{% In the starred variant, each line is prefixed with the absolute coordinates (row, seat number, relative to the layout), comma-separated, before the label. } \ldeen*{% Wenn eine Eingabedatei angegeben wurde, wird ihr Inhalt zweilenweise den erzeugten Zeilen vorangestellt. Diese Eingabedatei könnte beispielsweise die Namen oder/und die Immatrikulationsnummern von Studierenden enthalten, die dann in der Ausgabedatei den markierten Plätzen zugeordnet werden. Sind weniger Einträge in \meta{Eingabedatei} als Sitze belegt sind, werden die Felder in der Ausgabe leer gelassen. Wenn dagegen die Anzahl der Sitze nicht ausreicht, gibt @1\ eine Information aus, wer nicht platziert werden konnte. }{% If an input file is specified, its content is prepended line-by-line to the generated lines. For example, the input file could contain names and/or student IDs, which will then be matched to the assigned seats in the output. If there are fewer entries in \meta{input file} than assigned seats, the corresponding fields in the output will be left empty. If, on the other hand, there are more entries than available seats, @1\ will report which entries could not be assigned a seat. }{\thepkg} \end{commands} \subsection{\ldeen{Beschriftungen aus einer Datei einlesen}{Importing Labels from a File}} \label{sec:seats-import} \ldeen{% Namen, Kennungen oder andere Beschriftungen können aus einer CSV-Datei direkt in den Sitzplan übernommen werden. }{% Names, identifiers, or other labels can be imported directly into the seating plan from a CSV file. } \ldeen{% Es empfiehlt sich, zunächst nur das Layout und das Belegungsschema zu erstellen und die Platzbezeichner im Plan anzeigen zu lassen. }{% It is advisable to first create only the layout and seating scheme and display the seat designators in the plan. } \ldeen*{% Für absolute Platznummern dient dazu @1, für Koordinaten beispielsweise @2. Erst anhand dieses Kontrollplans sollte die Eingabedatei erstellt und danach mit @3 eingelesen werden. }{% Use @1 for absolute seat numbers, or, for example, @2 for coordinates. Only after checking this reference plan should the input file be prepared and then imported with @3. }{\option*{assigned seat label=l}}{\option*{assigned seat label=m{{,}}n}}{\cs*{scLoadSeatingList}} \begin{commands} \ldeen*{% @1[\oarg{Schlüsselliste}\marg{Eingabedatei}] }{% @1[\oarg{key list}\marg{input file}] }{\command{scLoadSeatingList}} \ldeen{% Die Anweisung liest die Beschriftungen aus \meta{Eingabedatei} ein und stellt die Beschriftungsformatierung automatisch auf \code{l}, damit der importierte Text auf dem jeweiligen Sitz ausgegeben wird. }{% The command imports labels from \meta{input file} and automatically changes the label format to \code{l}, so that the imported text is shown on its seat. } \begin{options} \keychoice{mode}{sequential,seat,coordinates}\Default{sequential} \ldeen{% Wählt die Art der Zuordnung zwischen den Datensätzen der Eingabedatei und den Plätzen im Layout. }{% Selects how records in the input file are mapped to seats in the layout. } \ldeen{% Der Wert \code{sequential} ordnet die Datensätze fortlaufend den bereits belegten Plätzen zu. }{% The value \code{sequential} assigns records successively to seats that are already occupied. } \ldeen{% Der Wert \code{seat} erwartet die absolute Platznummer in der ersten CSV-Spalte. }{% The value \code{seat} expects the absolute seat number in the first CSV column. } \ldeen{% Der Wert \code{coordinates} erwartet die absolute Reihe und die Platznummer innerhalb dieser Reihe in den ersten beiden CSV-Spalten. }{% The value \code{coordinates} expects the absolute row and the seat number within that row in the first two CSV columns. } \keybool{header}\Default{false} \ldeen{% Gibt an, ob die erste nichtleere Zeile der Eingabedatei eine Kopfzeile ist und daher beim Einlesen übersprungen werden soll. }{% Specifies whether the first non-empty line of the input file is a header row and should therefore be skipped during import. } \end{options} \ldeen{ Das folgende Beispiel zeigt eine Datei mit absoluten Koordinaten und einem Header.}{ The following example shows a file with absolute coordinates and a header. } \begin{example}[code-only] row,seat,label 1,3,Alice 2,1,"Bob, ID 123" \end{example} \ldeen{ Entsprechend sollte zum Einlesen folgende Optionen genutzt werden.}{ Accordingly, the following options should be used. } \begin{example}[code-only] \scLoadSeatingList[mode=coordinates,header]{participants.csv} \end{example} \ldeen{% In beiden adressierten Modi dürfen auch bislang leere Plätze belegt werden; entfernte Plätze und Gänge bleiben dagegen unverändert. }{% In both addressed modes, seats that were previously empty may be assigned; removed seats and aisles remain unchanged. } \ldeen{% CSV-Felder können wie üblich in doppelte Anführungszeichen eingeschlossen werden. Um ein doppeltes Anführungszeichen innerhalb eines Feldes darzustellen, muss es verdoppelt werden. }{% CSV fields may be enclosed in double quotes. A double quote inside a field is escaped by doubling it. } \end{commands} \section{\ldeen{Beispiele}{Examples}} \label{sec:examples} \ldeen*{% Zur Demonstration des Verhaltens von @1\ sind hier einige Beispiele des Einsatzes dokumentiert. }{% To demonstrate the behavior of @1, a few usage examples are documented here. }{\thepkg} \subsection{\ldeen{Dichte Belegung für den Beispielraum aus Abschnitt~\ref{sec:drawing}}{Dense Occupancy for Example Room in Section~\ref{sec:drawing}}} \label{sec:ex-simple} \begin{example}[compile,program=lualatex,pages=1,add-frame,code-sep=\bigskip,graphics={scale=.25}] \documentclass{article} \usepackage{seatingchart} \begin{document} \begin{seatingchart}[shape=rectangle, rows=9, seats per row=15] \scSetAisle{8} \scRemoveSeats{{1,1},{1,2}} \scSeatingScheme{2x2-} \end{seatingchart} \end{document} \end{example} \subsection{\ldeen{Vordefinierter Raum, rechteckig}{Predefined Room, Rectangular}} \label{sec:ex:std-rect} \begin{example}[compile,program=lualatex,pages=1,add-frame,code-sep=\bigskip,graphics={scale=.5}] \documentclass{scrartcl} % To make better use of the paper, we use landscape format % and reduce the margins \usepackage[a4paper,landscape,inner=10pt,outer=10pt,top=1cm,bottom=1cm]{geometry} \usepackage{seatingchart} \begin{document} % Default title takes up too much space. \centering\textbf{\Huge\sffamily Seating Plan C10.115}\bigskip \begin{seatingchart}[room=C10.115,blackboard] \scSeatingScheme{sixpack} \end{seatingchart} \end{document} \end{example} \subsection{\ldeen{Vordefinierter Raum, bogenförmig}{Predefined Room, Curved}} \label{sec:ex:std-arc} \begin{example}[compile,program=lualatex,pages=1,add-frame,code-sep=\bigskip,graphics={scale=.5}] \documentclass{scrartcl} % To make better use of the paper, we use landscape format % and reduce the margins \usepackage[a4paper,landscape,inner=10pt,outer=10pt,top=1cm,bottom=1cm]{geometry} \usepackage{seatingchart} \begin{document} % Default title takes up too much space. \centering\textbf{\Huge\sffamily Seating Plan A10.316}\bigskip \begin{seatingchart}[room=A10.316,blackboard, assigned seat label color=blue] \scSeatingScheme[ignore removed seats]{2x2} \end{seatingchart} \end{document} \end{example} \subsection{\ldeen{Eigenes Platzlayout und angepasstes Sitzschema}{Custom Layout and Seating Scheme}} \label{sec:ex:own1} \begin{example}[compile,program=lualatex,pages=1,add-frame,code-sep=\bigskip,graphics={scale=.5}] \documentclass{scrartcl} % To make better use of the paper, we use landscape format % and reduce the margins \usepackage[a4paper,landscape,inner=10pt,outer=10pt,top=1cm,bottom=1cm]{geometry} \usepackage{seatingchart} \begin{document} \begin{seatingchart}[shape=rectangle,rows=9,seats per row=9, assigned seat background color=yellow] \scRemoveSeats{{1,1},{1,-1},{2,1},{2,-1},{3,1},{3,-1}} \scSeatingScheme[ignore removed seats,end row=3]{2x3} \scSeatingScheme[start row=5, end row=10]{2x4} \end{seatingchart} \end{document} \end{example} \subsection{\ldeen{Dame}{Checkers}} \label{sec:ex:dame} \begin{example}[compile,program=lualatex,pages=1,add-frame,code-sep=\bigskip,graphics={scale=.5}] \documentclass{scrartcl} \usepackage[a5paper,landscape,top=1cm,bottom=1cm]{geometry} \usepackage{seatingchart} \usepackage{stix} \begin{document} \centering\textbf{\Huge\sffamily Let's play Checkers}\bigskip \begin{seatingchart}[shape=rectangle,rows=10,seats per row=10, assigned seat label color=blue,assigned seat background color=teal, empty seat background color=lime!50,seat distance=0pt] \scConfigScheme{assigned seat label={}} \scSeatingScheme*[start row=5, end row=5]{X-} \scSeatingScheme*[start row=6, end row=6]{-X} \scConfigScheme{assigned seat label font=\Huge, assigned seat label={{{\textcolor{white}{$\circledbullet$}}}}} \scSeatingScheme*[start row=1, end row=3,row sep=2]{X-} \scSeatingScheme*[start row=2, end row=4]{-X} \scConfigScheme{assigned seat label={{{\textcolor{yellow}{$\circledbullet$}}}}} \scSeatingScheme*[start row=7, end row=9]{X-} \scSeatingScheme*[start row=8, end row=10]{-X} \scDrawSeating[seat width=1cm, seat height=1cm] \end{seatingchart} \end{document} \end{example} \section{\ldeen{Deklaration neuer Raumlayouts}{Declaration of New Rooms}} \label{sec:new-rooms} \ldeen*{% In der augenblicklichen Version hat das @1-Paket erst eine relativ geringe Anzahl von Räumen der TU~Chemnitz mit ihrem Sitzlayout erfasst. Nutzer werden daher häufig darauf angewiesen sein, eigene Layouts anzulegen. }{% In its current version, the @1 package only contains a relatively small number of rooms at TU Chemnitz with their seating layouts. Users will therefore often have to create their own layouts. }{\thepkg} \ldeen*{% Neben der Möglichkeit zur Ad-hoc-Kreation von Layouts, wie sie im Abschnitt~@1 beschrieben sind, besteht auch die Möglichkeit das Paket selbst zu erweitern und damit Layouts für neue Räume anzulegen, die dann einfach über den @2-Schlüssel angesprochen werden können. Dazu können eigene \code{seatingchart-*.sc}-Dateien angelegt und über die \option{layout} eingebunden werden. }{% In addition to the option for ad-hoc layout creation, as described in section~@1, it is also possible to extend the package itself and create layouts for new rooms, which can then be easily accessed via the @2 key. }{\ref{sec:layout}}{\option{room}} \ldeen*{% In einer @1 können zwei Befehle genutzt werden: }{% For this purpose, custom @1 files can be created and integrated via the \option{layout} key. Two commands can be used in @1 files: }{\code{seatingchart-*.sc}} \begin{commands} \ldeen*{% @1[\marg{Raumbezeichnung}\marg{Schlüsselliste}] }{% @1[\marg{room name}\marg{key list}] }{\command{scDeclareRoom}} % \ldeen{% Ein neues Raumlayout wird für den Raum \meta{Raumbezeichnung} angelegt. Die Schlüsselliste \emph{muss} die drei Schlüssel }{% A new room layout is created for the room \meta{room name}. The key list \emph{must} contain the three keys } \begin{options} \ldeen*{% \keychoice@1{rectangle,arc}\Default*!{}%\vspace{-.3\baselineskip} }{% \keychoice@1{rectangle,arc}\Default*!{}%\vspace{-.3\baselineskip} }{{shape}} % \ldeen*{% \keyval-@1{Anzahl der Sitzreihen}\Default*!{}%\vspace{-.3\baselineskip} und }{% \keyval-@1{number of rows}\Default*!{}%\vspace{-.3\baselineskip} and }{{rows}} % \ldeen*{% \keyval-@1{Sitze pro Sitzreihe}\Default!{} }{% \keyval-@1@1\Default!{} }{{seats per row}} % \ldeen*{% enthalten, die die analoge Bedeutung zu den gleichnamigen Schlüsseln aus Abschnitt~@1 haben. Diese Angaben zum Basislayout \emph{müssen} von dem Schlüssel \opt{init}\Default! gefolgt werden. Anschließend kann mit }{% These keys have the same meaning as the keys of the same name from Section~@1. This information about the base layout \emph{must} be followed by the key \opt{init}\Default! The layout can then be adjusted using }{\ref{sec:layout}} \ldeen*{% \keyval-@1{Sitznummer}\vspace{-1.3\baselineskip} }{% \keyval-@1{seat number}\vspace{-1.3\baselineskip} }{{aisle}} % \ldeen*{% \keyval@1{Liste} }{% \keyval@1{list} }{{remove}} % \ldeen*{% das Layout angepasst werden. Die beiden Schlüssel funktionieren analog zu @1 und @2, vgl.\ Abschnitt~@3. }{% These two keys function analogously to @1 and @2, see Section~@3. }{\cs*{scSetAisle}}{\cs*{scRemoveSeats}}{\ref{sec:modify-layout}} \end{options} \begin{example}[code-only] \scDeclareRoom{C10.115}{ shape=rectangle, rows=21, seats per row=35, init, aisle=18, remove={{1,1},{1,2},{1,3},{1,4},{1,-1},{1,-2},{1,-3},{1,-4}} } \end{example}\vspace{-1.3\baselineskip} \ldeen*{% @1[\marg{Aliasname}\marg{Originalname}] }{% @1[\marg{alias}\marg{room}] }{\command{scAliasRoom}} % \ldeen{% Setzt \meta{Aliasname} als einen Ersatznamen für \meta{Originalname}. }{% Sets \meta{alias} as a replacement name for \meta{room}. } \end{commands} \ldeen{% Wenn Sie wünschen, dass eine Layoutdatei ihrer Institution Bestandteil des Pakets auf CTAN wird, schicken Sie bitte dem Maintainer ihre Datei oder initiieren Sie einen Pull-Request auf \url{https://github.com/tuc-osg/seatingchart}. Geben Sie in diesem Fall der Datei einen aussagekräftigen und unterscheidungsfähigen Namen. Beispielsweise sollte die \emph{Lummerland Maschinenbau Universität} eher \code{seatingchart-lummerland-mu.sc} oder \code{seatingchart-lummerland-machbau-uni.sc} nutzen, als \code{seatingchart-lmu.sc}. }{% If you would like your institution's layout file to be included in the package on CTAN, please send your file to the maintainer or initiate a pull request on \url{https://github.com/tuc-osg/seatingchart}. In this case, give the file a meaningful and distinguishable name. For example, \emph{University of Lummerland at Lummerland City} should use \code{seatingchart-lummerland-uni-lc.sc} rather than \code{seatingchart-lu.sc}. } \section{\ldeen{Kompatibilitätsmodus}{Compatibility Mode}} \label{sec:legacy} \ldeen*{% Für bestehende Dokumente aktiviert die Paketoption @1 das bisherige dokumentweite Verhalten. In diesem Modus werden die Layoutschlüssel weiterhin als Paketoptionen angegeben, die Ausführungsbefehle sind im gesamten Dokument verfügbar und @2 muss explizit aufgerufen werden: }{% For existing documents, the package option @1 enables the former document-wide behaviour. Layout keys remain package options, executing commands are available throughout the document, and @2 must be called explicitly: }{\option{legacy}}{\cs{scDrawSeating}} \begin{example}[code-only] \usepackage[legacy,shape=rectangle,rows=9,seats per row=15]{seatingchart} \end{example} \ldeen{% Der Kompatibilitätsmodus ist für die Migration vorhandener Quellen gedacht; neue Dokumente sollten die \env*{seatingchart}-Umgebung verwenden. }{% The compatibility mode is intended for migrating existing sources; new documents should use the \env*{seatingchart} environment. } \ldeen*{% Nur in diesem Modus steht der frühere Name @1 als Alias für @2 zur Verfügung. }{% Only in this mode is the former name @1 available as an alias for @2. }{\cs*{scSeatingList}}{\cs*{scGenerateSeatingList}} \section{\ldeen{Beschränkungen und Bugs}{Limitations and Bugs}} \label{sec:bugs} \ldeen*{% Auch wenn das @1-Paket zumindest in unserer Gruppe im praktischen Einsatz ist, ist es noch als experimentell zu betrachten. Das @2 in der Versionsnummer deutet diesen Umstand an. }{% Even though the @1 package is in practical use, at least in our group, it should still be considered experimental. The @2 in the version number indicates this fact. }{\thepkg}{\code{beta}} \ldeen{Es gibt eine Reihe von Einschränkungen, u.\,a.:}{There are a number of restrictions, including, among others:} \begin{itemize}[nosep] \item \ldeen{ Verschiedene Sitzlayouts können nicht dargestellt werden. Beispielsweise sind keine versetzten Sitzreihen oder typische Konferenzanordnungen möglich.}{ Several seating arrangements cannot be accommodated. For example, staggered rows or typical conference layouts are not possible. } \item \ldeen*{Der Winkel für das bogenförmige Layout ist auf @1 beschränkt.}{% The angle for the curved layout is limited to @1.}{$120^{\circ}$} \item \ldeen{% Die Anzahl der vordefinierten Räume ist derzeit noch eher gering. Es besteht die Absicht, in künftigen Patches weitere Räume der TU~Chemnitz aufzunehmen. Layoutdateien von anderen Institutionen sind willkommen. }{% The number of predefined rooms is currently relatively small. There are plans to include additional rooms at TU Chemnitz in future patches. Layout files from other institutions are welcome. } \end{itemize} \ldeen{% Bitte nutzen Sie für Bugs-Reports oder Pull-Requests GitHub: \url{https://github.com/tuc-osg/seatingchart}. }{% Please use GitHub for bug reports or pull requests: \url{https://github.com/tuc-osg/seatingchart}. } \section{\ldeen{Lizenz}{License}} \ldeen{% Es ist erlaubt, diese Software unter den Bedingungen der \LaTeX Project Public License (LPPL), Version 1.3c oder später, zu kopieren und zu verteilen (\url{http://www.latex-project.org/lppl.txt}). }{% Permission is granted to copy, distribute and/or modify this software under the terms of the \LaTeX\ Project Public License (LPPL), version 1.3c or later (\url{http://www.latex-project.org/lppl.txt}). } \end{document} % %<*package> %% %% Revisions: %% 2025-07-25: First public version %% 2026-06-05: Fix interaction with babel (#2) %% 2026-07-22a: Change to environment %% 2026-07-22b: Add input list, rivise docunentation => 1.0.0beta %% \NeedsTeXFormat{LaTeX2e}[2022-06-01] \ProvidesExplPackage{seatingchart} {2026/07/22} {1.0.0beta} {Generation of seating plans} \def\packagename{seatingchart} \IfFormatAtLeastTF{2022/06/01}{}{ \PackageError{\packagename}{ \packagename requires at least the TeX format \MessageBreak from 2022/06. \MessageBreak }{Update your LaTeX.} } \RequirePackage{etoolbox} \sys_if_engine_luatex:TF{}{ \PackageError{\packagename}{ LuaLaTeX~is~required~to~use~this~package.\MessageBreak Sorry! }{Use~LuaLaTex.} } \RequirePackage{luacode} \ExplSyntaxOff \newtoggle{sc@rectshapt}\toggletrue{sc@rectshapt} \newtoggle{sc@rnleft} \newtoggle{sc@rnright} \newtoggle{sc@room} \newtoggle{sc@legacy} \newtoggle{sc@active} \newtoggle{sc@drawn} \def\sc@layoutactions{} \DeclareKeys[seatingchart]{ legacy.choice:, legacy/true.code:n = {\toggletrue{sc@legacy}}, legacy/false.code:n = {\togglefalse{sc@legacy}}, legacy.default:n = true, legacy.initial:n = false, legacy.usage = load, shape/arc.code:n = {\togglefalse{sc@rectshapt}}, shape/rectangle.code:n = {\toggletrue{sc@rectshapt}}, shape.initial:n = rectangle, rows.code:n = { \ifdef{\sc@rows}{ \ifnumcomp{\sc@rows}{>}{0}{ \PackageWarningNoLine{\packagename}{Overwriting number of rows.\MessageBreak You propably provided both, a room *and*\MessageBreak layout options.\MessageBreak Prepare for trouble} }{} }{} \def\sc@rows{#1} }, rows.initial:n = 0, seats per row.code = { \ifdef{\sc@seatsperrow}{ \ifnumcomp{\sc@seatsperrow}{>}{0}{ \PackageWarningNoLine{\packagename}{Overwriting seats per row.\MessageBreak You propably provided both, a room *and*\MessageBreak layout options.\MessageBreak Prepare for trouble} }{} }{} \def\sc@seatsperrow{#1} }, layout.store = \scroomfile, layout.initial:n =tu-chemnitz, seats per row.initial:n = 0, room.choice:, room/unknown.code:n={ \PackageError{\packagename}{ Room #1 is not in the layout file ' \packagename-\scroomfile.sc'}{% Check if you use the the correct layout file and\MessageBreak whether the room name is correct,\MessageBreak or define an own room.} }, init.code:n={ \toggletrue{sc@room} }, remove.code:n = {% \appto\sc@layoutactions{\scRemoveSeats{#1}} }, shape.choice:, aisle.code:n={% \appto\sc@layoutactions{\keySetAisle{#1}} }, blackboard.if = sc@blackboard, blackboard.initial:n = false, rownumbers.choice:, rownumbers/left.code = { \toggletrue{sc@rnleft} }, rownumbers/right.code = { \toggletrue{sc@rnright} }, rownumbers/both.code:n = { \toggletrue{sc@rnleft}\toggletrue{sc@rnright} }, rownumbers/none.code:n = { \togglefalse{sc@rnleft}\togglefalse{sc@rnright} }, rownumbers.initial:n=none, seat neighbor distance.store =\sc@sdist@x, row distance.store =\sc@sdist@y, seat distance.meta:n = { seat neighbor distance=#1, row distance=#1 }, seat distance.initial:n=2pt, rownumbers.initial:n = none, rownumber distance.store= \sc@rnsep, rownumber distance.initial:n = 2pt, rownumber font.store = \sc@rnfont, rownumber font.initial:n = \tiny, rownumber color.store = \sc@rncolor, rownumber color.initial:n = darkgray, empty seat background color.store =\sc@sc@empty, empty seat background color.initial:n = lightgray!20, empty seat border color.store = \sc@sc@emptyborder, empty seat border color.initial:n = lightgray, empty seat label color.store = \sc@sc@emptytext, empty seat label color.initial:n = lightgray!30, empty seat label font.store = \sc@s@emptyfont, assigned seat background color.store = \sc@sc@assigned, assigned seat background color.initial:n = lightgray!30, assigned seat border color.store = \sc@sc@assignedborder, assigned seat border color.initial:n = black, assigned seat label color.store = \sc@sc@assignedtext, assigned seat label color.initial:n = black, assigned seat label font.store = \sc@s@assignedfont, seat background color.meta:n = { empty seat background color=#1, assigned seat background color =#1 }, seat label color.meta:n = { empty seat label color = #1, assigned seat label color = #1 }, seat border color.meta:n = { empty seat border color = #1, assigned seat border color = #1 }, seat label font.initial:n = \small, seat label font.meta:n = { empty seat label font=#1, assigned seat label font = #1 } } \NewDocumentCommand{\scDeclareRoom}{m m}{% \DeclareKeys[seatingchart]{room/#1.meta:n={#2}}% } \NewDocumentCommand{\scAliasRoom}{m m}{% \DeclareKeys[seatingchart]{room/#1.meta:n={room/#2}}% } \NewDocumentCommand{\scRemoveSeatAt}{m m}{ \luadirect{removeSeatAt(#1,#2)} } \NewDocumentCommand{\sc@removeSeat}{>{\SplitArgument{1}{,}}m}{ \scRemoveSeatAt#1 } \NewDocumentCommand{\scRemoveSeats}{>{\SplitList{,}} m}{ \ProcessList{#1}{\sc@removeSeat} } \NewDocumentCommand{\scAssignSeatAt}{m m m}{ \luadirect{assignSeatAt(#1,#2,\luastring{#3})} } \NewDocumentCommand{\sc@ParseRows}{m}{ } \def\sc@parserange#1-#2\relax{% \def\@firstval{\ifx\relax#1\relax1\else#1\fi}% \def\@lastval{\ifx\relax#2\relax\sc@rows\else#2\fi}% } \newcommand{\keySetAisle}[1]{ \scSetAisle[-]{#1} } \NewDocumentCommand{\scSetAisle}{O{-}m}{% \begingroup \edef\@tmp{#1}% \expandafter\sc@parserange\@tmp\relax \luadirect{removeAisle(#2,\@firstval,\@lastval)} \endgroup } \NewDocumentCommand{\scInitSeating}{o}{ \IfValueT{#1}{} \luadirect{require("seatingchart.lua")} \iftoggle{sc@rectshapt}{ \luadirect{initSeating(\sc@rows,\sc@seatsperrow,\luastring{rect})} }{ \luadirect{initSeating(\sc@rows,\sc@seatsperrow,\luastring{arc})} } } \InputIfFileExists{seatingchart-\scroomfile.sc}{}{ \PackageError{\packagename}{ Can't open room layout file 'seatingchart-\scroomfile.sc'}{If you've provided a layout file, check whether it is in the path.} } % defaults \ProcessKeyOptions \NewDocumentCommand{\scConfig}{m}{% \SetKeys[seatingchart]{#1}% \sc@configtikz% } \ExplSyntaxOn \newcommand\sc@configtikz{% \tikzset{% empty-seat/.style={ draw=\sc@sc@emptyborder, fill=\sc@sc@empty, font=\sc@s@emptyfont, text=\sc@sc@empty, inner~sep=0pt }, empty-label/.style={ text=\sc@sc@emptytext, font=\sc@s@emptyfont }, assigned-seat/.style={ draw=\sc@sc@assignedborder, fill=\sc@sc@assigned, text=\sc@sc@assigned, font=\sc@s@assignedfont, inner~sep=0pt }, assigned-label/.style={ text=\sc@sc@assignedtext, font=\sc@s@assignedfont }, rownumber/.style={ font=\sc@rnfont, text=\sc@rncolor, anchor=east }% }% } \ExplSyntaxOff \newlength\scseatwidth%\settowidth{\scseatwidth}{xxxx}% \newlength\scseatheight%\settoheight{\scseatheight}{Xy}% \newlength\scremainingspace \newlength\scrnlength \newlength\scarcunit \DeclareKeys[scdrawing]{ seat width.code:n={% \setlength{\scseatwidth}{#1}% }, seat height.code:n={% \setlength{\scseatheight}{#1}% } } \ExplSyntaxOn \NewDocumentCommand{\scDrawSeating}{o}{% \toggletrue{sc@drawn}% \par\noindent% \IfValueTF{#1}{% \SetKeys[scdrawing]{#1}% }{% \setlength{\scremainingspace}{\linewidth}% \settowidth{\scrnlength}{\scrownumformat{99}}% %\typeout{Linewidth: \the\linewidth~(\the\scremainingspace)} %\typeout{Numswidth: \the\scrnlength~(seats:~\sc@seatsperrow)} %\typeout{********* Remaining start: \the\scremainingspace}% \iftoggle{sc@rnleft}{\addtolength{\scremainingspace}{-\dimeval{\scrnlength+\sc@rnsep}}}{}% %\typeout{********* Remaining~left~considered: \the\scremainingspace}% \iftoggle{sc@rnright}{\addtolength{\scremainingspace}{-\dimeval{\scrnlength+\sc@rnsep}}}{}% %\typeout{********* Remaining~right~considered: \the\scremainingspace}% \setlength{\scseatwidth}{\dimeval{\scremainingspace/(\sc@seatsperrow) - \sc@sdist@x}} %\typeout{Seatwidth: \the\scseatwidth} %\typeout{total: \dimeval{(\scseatwidth+2pt)*\sc@seatsperrow + \scrnlength*2 + \sc@rnsep*2}} %\typeout{********* Total: \the\pagetotal\ Goal: \the\pagegoal\ Height: \the\textheight}% \ifdimequal{\pagetotal}{0pt}{% \setlength{\scremainingspace}{\textheight}% }{% \setlength{\scremainingspace}{\dimeval{\pagegoal-\pagetotal}}% }% %\typeout{********* Total: \the\pagetotal\ Goal: \the\pagegoal\ Height: \the\textheight}% %\typeout{********* Remaining: \the\scremainingspace\ Width: \the\textwidth\ Height: \the\textheight}% \ifsc@blackboard% \setlength{\scseatheight}{\dimeval{\scremainingspace/(\sc@rows + 2) - \sc@sdist@y}}% \else% \setlength{\scseatheight}{\dimeval{\scremainingspace/\sc@rows - \sc@sdist@y}}% \fi% % A seat that is deeper than it is wide looks a bit silly. \ifdimcomp{\scseatheight}{>}{\scseatwidth}{\setlength{\scseatheight}{\scseatwidth}}{}% }% \sc@configtikz% %\typeout{*********~Seatwidth:~\the\scseatwidth, Seatheight:~\the\scseatheight}% \luadirect{seatDim(\luastring{\scseatwidth},\luastring{\scseatheight})}% \def\sc@xunit{\scseatwidth+\sc@sdist@x}% \def\sc@yunit{\scseatheight+\sc@sdist@y}% \iftoggle{sc@rectshapt}{}{% % Arc coordinates describe circles and therefore require identical scales % on both axes. Use the larger pitch to prevent seats from overlapping. \setlength{\scarcunit}{\dimexpr\scseatwidth+\sc@sdist@x\relax}% \ifdimcomp{\dimexpr\scseatheight+\sc@sdist@y\relax}{>}{\scarcunit}{% \setlength{\scarcunit}{\dimexpr\scseatheight+\sc@sdist@y\relax}% }{}% \def\sc@xunit{\scarcunit}% \def\sc@yunit{\scarcunit}% }% \begin{tikzpicture}[x=\sc@xunit,y=\sc@yunit] \ifsc@blackboard% \node[rectangle, draw,minimum~width=0.4\textwidth] at (0,-2) {Tafel}; \fi \iftoggle{sc@rectshapt}{ \iftoggle{sc@rnleft}{ \foreach \r in {1,..., \sc@rows} { \node[rownumber,xshift=-\fpeval{\scseatwidth/2+\sc@rnsep}] at (\fpeval{(1-\sc@seatsperrow)/2},\r-1) {\scrownumformat{\r}}; } }{} \iftoggle{sc@rnright}{ \foreach \r in {1,..., \sc@rows} { \node[rownumber,xshift=\fpeval{\scseatwidth/2+\sc@rnsep+\scrnlength}] at (\fpeval{(\sc@seatsperrow-1)/2},\r-1) {\scrownumformat{\r}}; } }{} }{} \luadirect{drawSeats()} \end{tikzpicture} } \newcount\scnumofseats \newtoggle{scfoundassigned} \NewDocumentCommand{\scGenerateSeatingList}{s o m}{ \luadirect{getNumberOfSeats()} \togglefalse{scfoundassigned} \loop \luadirect{setupNextlabel(\the\scnumofseats)}\relax %\typeout{ i=\the\scnumofseats} \iftoggle{scfoundassigned}{ \setbox0=\hbox{\scnextlabel} \luadirect{saveTextLabel(\the\scnumofseats)} }{} \advance \scnumofseats by -1 \unless\ifnum \scnumofseats<2 \repeat \IfBooleanTF{#1}{ \def\sccoor{true} }{ \def\sccoor{nil} } \IfValueTF{#2}{ \luadirect{generateSeatList(\luastring{#3}, \luastring{#2}, \sccoor)} }{ \luadirect{generateSeatList(\luastring{#3}, nil, \sccoor)} } } \DeclareKeys[scseatinginput]{ mode.choice:, mode/sequential.code:n = {\def\sc@inputmode{sequential}}, mode/seat.code:n = {\def\sc@inputmode{seat}}, mode/coordinates.code:n = {\def\sc@inputmode{coordinates}}, mode.initial:n = sequential, header.if = sc@inputheader, header.initial:n = false } \NewDocumentCommand{\scLoadSeatingList}{O{}m}{ % Each invocation starts with the documented defaults; key settings from an % earlier import in the same seatingchart must not leak into this one. \def\sc@inputmode{sequential}% \sc@inputheaderfalse \SetKeys[scseatinginput]{#1}% % Imported values are the labels to display, independently of the label % format previously used to inspect the seat designators. \scConfigScheme{assigned~seat~label=l}% \luadirect{loadSeatingList(\luastring{#2}, \luastring{\sc@inputmode},\ifsc@inputheader true\else false\fi)}% } \ExplSyntaxOff \DeclareKeys[scseatingscheme]{ empty seat background color.store =\sc@sc@empty, empty seat border color.store = \sc@sc@emptyborder, empty seat label color.store = \sc@sc@emptytext, empty seat label font.store = \sc@s@emptyfont, assigned seat background color.store = \sc@sc@assigned, assigned seat border color.store = \sc@sc@assignedborder, assigned seat label color.store = \sc@sc@assignedtext, assigned seat label font.store = \sc@s@assignedfont, seat background color.meta:n = { empty seat background color=#1, assigned seat background color =#1 }, seat label color.meta:n = { empty seat label color = #1, assigned seat label color = #1 }, seat border color.meta:n = { empty seat border color = #1, assigned seat border color = #1 }, seat label font.meta:n = { empty seat label font=#1, assigned seat label font = #1 }, row sep.store = \tss@rowsep, row sep.initial:n = 2, start row.store = \tss@startrow, start row.initial:n=1, end row.store = \tss@endrow, end row.initial:n = \sc@rows, row restart after.store= \tss@rowrestart, row restart after.initial:n = 100,%should be sufficient aisle counts.store = \tss@aislecnt, aisle counts.initial:n = 1, aisle restarts scheme.choice:, aisle restarts scheme/true.store=\tss@aislerestart, aisle restarts scheme/false.store=\tss@aislerestart, aisle restarts scheme.default:n = true, aisle restarts scheme.initial:n = false, ignore aisle.meta:n = { aisle counts=0 }, rtol.choice:, rtol/true.store = \tss@rtol, rtol/false.store = \tss@rtol,, rtol.default:n = true, rtol.initial:n = false, ignore removed seats.choice:, ignore removed seats/true.store=\tss@ignoreremoved, ignore removed seats/false.store=\tss@ignoreremoved, ignore removed seats.default:n = true, ignore removed seats.initial:n = false, assigned seat label.store=\tss@as@format, assigned seat label.initial:n=m{{\,}}D, pattern.store=\tss@pattern, scheme.choice:, scheme/1x1.meta:n={ row sep=1, pattern=X, }, scheme/all.meta:n={ scheme=1x1 }, scheme/2x2.meta:n={ row sep=2, pattern=X-, aisle restarts scheme=true, row restart after=100 }, scheme/simple.meta:n={ scheme=2x2 }, scheme/2x3.meta:n={ row sep=2, pattern=X--, aisle restarts scheme=true, row restart after=100 }, scheme/sixpack.meta:n={ scheme=2x3 }, scheme/2x3-.meta:n={ row sep=2, pattern=X--, aisle restarts scheme=true, row restart after=3 }, scheme/2x4.meta:n={ row sep=2, pattern=X---, aisle restarts scheme=true, row restart after=100 }, scheme/3x4.meta:n={ row sep=3, pattern=X---, aisle restarts scheme=true, row restart after=100 }, scheme/sixpack-.meta:n={ row sep=2, pattern=X--, aisle restarts scheme=true, row restart after=3 }, scheme/2x2-.meta:n={ row sep=2, pattern=X-, aisle restarts scheme=true, row restart after=3 }, scheme/dense.meta:n={ scheme=2x2- } } \NewDocumentCommand{\scConfigScheme}{m}{ \SetKeys[scseatingscheme]{#1} } \NewDocumentCommand{\scSeatingScheme}{s o m}{ \IfValueT{#2}{% \SetKeys[scseatingscheme]{#2} }% \IfBooleanTF{#1}{ % \SetKeys[scseatingscheme]{pattern=#3} }{% \SetKeys[scseatingscheme]{scheme=#3} }% \def\scpolicy{ {[\luastring{aisle restarts}]=\tss@aislerestart, [\luastring{rtol}]=\tss@rtol, [\luastring{aisle counts}]=\tss@aislecnt, [\luastring{aisle restarts scheme}]=\tss@aislecnt, [\luastring{ignore removed seats}]=\tss@ignoreremoved, [\luastring{row sep}]=\tss@rowsep, [\luastring{row restart}]=\tss@rowrestart, [\luastring{first row}]=\tss@startrow, [\luastring{last row}]=\tss@endrow, [\luastring{label format}]=\luastringO{\tss@as@format} }} %\typeout{POLICY=\scpolicy, startrow=\tss@startrow\ endrow=\tss@endrow} \luadirect{seatingSchemeInRows(\luastring{\tss@pattern},\scpolicy)} } \ExplSyntaxOn \newtoggle{sc@formatother} \newcommand\sc@format@label[6]{% \togglefalse{sc@formatother} % absolut row, arabic: m % absolut row, alpha: a % absolut row, Alpha: A % absolut row, roman: y % absolut row, Roman: Y % running row, arabic: r % running row, alpha: b % running row, Alpha: B % running row, roman: i % running row, Roman: I % absolut seat, arabic: n % absolut seat, alpha: c % absolut seat, Alpha: C % absolut seat, roman: x % absolut seat, Roman: X % running seat, arabic: s % running seat, alpha: d % running seat, Alpha: D % running seat, roman: j % running seat, Roman: J % Label: l \regex_match_case:nnTF{ {\cB.}{#6}% match all groups {m}{#1} {a}{\int_to_alph:n{#1}} {A}{\int_to_Alph:n{#1}} {y}{\int_to_roman:n{#1}} {Y}{\int_to_Roman:n{#1}} {r}{#3} {b}{\int_to_alph:n{#3}} {B}{\int_to_Alph:n{#3}} {i}{\int_to_roman:n{#3}} {I}{\int_to_Roman:n{#3}} {n}{#2} {c}{\int_to_alph:n{#2}} {C}{\int_to_Alph:n{#2}} {x}{\int_to_roman:n{#2}} {X}{\int_to_Roman:n{#2}} {s}{#4} {d}{\int_to_alph:n{#4}} {D}{\int_to_Alph:n{#4}} {j}{\int_to_roman:n{#4}} {J}{\int_to_Roman:n{#4}} {l}{#5} }{#6}{}{% \PackageWarningNoLine{\packagename}{Unknown~formating~character '#6'}% } } \NewDocumentCommand{\scassignedlabelformat}{o m m m m m}{ % #2: absolute row % #3: absolute seat % #4: running row % #5: running seat % #6: text \IfValueTF{#1}{% \def\sc@format{#1}% }{% \def\sc@format{#6} }% %\typeout{*** Format: \sc@format} \expandafter\tl_map_tokens:Nn{\sc@format}{\sc@format@label{#2}{#3}{#4}{#5}{#6}} } \NewDocumentCommand{\tucemptylabelformat}{m m m m m}{ % #2: absolute row % #3: absolute seat % #4: running row % #5: running seat % #6: text } \ExplSyntaxOff \newcommand{\scrownumformat}[1]{% #1% } \RequirePackage{tikz} \usetikzlibrary{shapes.geometric} \newcommand{\sc@outsideenvironmenterror}{% \PackageError{\packagename}{Command only available inside the seatingchart environment}{Wrap seating-chart commands in \string\begin{seatingchart}...\string\end{seatingchart}, or load the package with the legacy option.}% } % Keep the implementations available under private names. The public commands % are enabled locally by the environment (or globally in legacy mode). \let\sc@active@RemoveSeatAt\scRemoveSeatAt \let\sc@active@RemoveSeats\scRemoveSeats \let\sc@active@AssignSeatAt\scAssignSeatAt \let\sc@active@SetAisle\scSetAisle \let\sc@active@Config\scConfig \let\sc@active@DrawSeating\scDrawSeating \let\sc@active@GenerateSeatingList\scGenerateSeatingList \let\sc@active@LoadSeatingList\scLoadSeatingList \let\sc@active@ConfigScheme\scConfigScheme \let\sc@active@SeatingScheme\scSeatingScheme \newcommand{\sc@enablecommands}{% \let\scRemoveSeatAt\sc@active@RemoveSeatAt \let\scRemoveSeats\sc@active@RemoveSeats \let\scAssignSeatAt\sc@active@AssignSeatAt \let\scSetAisle\sc@active@SetAisle \let\scConfig\sc@active@Config \let\scDrawSeating\sc@active@DrawSeating \let\scGenerateSeatingList\sc@active@GenerateSeatingList \let\scLoadSeatingList\sc@active@LoadSeatingList \let\scConfigScheme\sc@active@ConfigScheme \let\scSeatingScheme\sc@active@SeatingScheme } \newcommand{\sc@disablecommands}{% \let\scRemoveSeatAt\sc@outsideenvironmenterror \let\scRemoveSeats\sc@outsideenvironmenterror \let\scAssignSeatAt\sc@outsideenvironmenterror \let\scSetAisle\sc@outsideenvironmenterror \let\scConfig\sc@outsideenvironmenterror \let\scDrawSeating\sc@outsideenvironmenterror \let\scGenerateSeatingList\sc@outsideenvironmenterror \let\scLoadSeatingList\sc@outsideenvironmenterror \let\scConfigScheme\sc@outsideenvironmenterror \let\scSeatingScheme\sc@outsideenvironmenterror } \ExplSyntaxOn \cs_new_protected:Npn \sc_load_layout_from_options:n #1 { \regex_extract_once:nnNT { (?: \A | , ) \s* layout \s* = \s* ([^,]+) } {#1} \l_tmpa_seq { \edef\scroomfile{\tl_trim_spaces:e{\seq_item:Nn\l_tmpa_seq{2}}} } } \newcommand\sc@loadlayoutfromoptions[1]{\sc_load_layout_from_options:n{#1}} \ExplSyntaxOff \NewDocumentEnvironment{seatingchart}{O{}}{% \begingroup \iftoggle{sc@active}{% \PackageError{\packagename}{Nested seatingchart environments are not supported}{Close the current seatingchart environment before opening another one.}% }{}% \toggletrue{sc@active}% \togglefalse{sc@drawn}% \togglefalse{sc@room}% \def\sc@rows{0}% \def\sc@seatsperrow{0}% \def\sc@layoutactions{}% \sc@enablecommands \sc@loadlayoutfromoptions{#1}% \InputIfFileExists{seatingchart-\scroomfile.sc}{}{% \PackageError{\packagename}{Can't open room layout file 'seatingchart-\scroomfile.sc'}{Check the layout option and file name.}% }% \SetKeys[seatingchart]{#1}% \ifboolexpr{ test {\ifnumcomp{\sc@rows}{<}{1}} or test {\ifnumcomp{\sc@seatsperrow}{<}{1}} }{% \PackageError{\packagename}{Invalid layout option:\MessageBreak number of rows or number of seats per row can't be smaller than 1}{% Use correct layout options}% }{}% \scInitSeating \sc@layoutactions }{% \iftoggle{sc@drawn}{}{\scDrawSeating}% \endgroup } \iftoggle{sc@legacy}{% \toggletrue{sc@active}% \sc@enablecommands \let\scSeatingList\scGenerateSeatingList \scInitSeating \sc@layoutactions }{% \sc@disablecommands } % %<*lua> --[[ Package: seatingchart Date: 2026-07-22 Version: 1.0.0beta ]] Seat = { id = nil, row = 0, col = 0, x = 0, y = 0, rrow = 0, rcol = 0, rotate = 0, kind = nil, label="", textlabel="" } function Seat:new(o) o = o or {} setmetatable(o, self) self.__index = self return o end AllSeats={} SEATREMOVED = 1 SEATEMPTY = 2 SEATASSIGNED = 3 AISLE = 4 SPECIAL = 5 function initSeating(rows, cols, shape) local cid = 0 -- A document may contain several charts. Replacing only the leading -- entries would leave seats from a larger preceding chart behind, because -- drawSeats() deliberately iterates over the array with ipairs(). for key in pairs(AllSeats) do AllSeats[key] = nil end AllSeats.size=0 AllSeats.rows=rows AllSeats.cols=cols --tex.sprint("\\typeout{*** initSeating CALLED, ndx: }") if shape == "rect" then for r = 1, rows do for c = 1, cols do cid = cid+1 AllSeats[cid] = Seat.new({id=cid, row=r, col=c, x=c-(cols+1)/2, y=r-1.0, rrow=0, rcol=0, rotate=0, kind=SEATEMPTY, label=tostring(cid), textlabel=""}) AllSeats.size=AllSeats.size+1 end end elseif shape == "arc" then local max_arc=120 * math.pi/180 local radius local step local rot local x local y local phi -- Choose the innermost radius so that a complete row spans at most -- max_arc while adjacent seat centres remain one unit apart. Increasing -- the radius by one for each row gives the same radial distance. local inner_radius = 1 if cols > 1 then inner_radius = 0.5 / math.sin(max_arc/(2*(cols-1))) end for r = 1, rows do radius = inner_radius + r - 1 if cols > 1 then step = 2 * math.asin(0.5/radius) else step = 0 end for c = 1, cols do cid = cid+1 phi = (c-(cols+1)/2) * step x = radius * math.sin(phi) y = radius * math.cos(phi) - inner_radius rot=-phi/math.pi*180 AllSeats[cid] = Seat.new({id=cid, row=r, col=c, rotate=rot, x=x, y=y, rrow=0, rcol=0, kind=SEATEMPTY, label=tostring(cid)}) AllSeats.size=AllSeats.size+1 end end end end function initArc(rows, cols) end function seatDim(w,h) AllSeats.seatwidth = w AllSeats.seatheight = h end function findSeatAt(y,x) if y<0 then y = AllSeats.rows + y + 1 end if x<0 then x = AllSeats.cols + x + 1 end if x>AllSeats.cols or y > AllSeats.rows then tex.sprint("\\PackageError{\\packagename}{Index out of bound}{Check the used coordinates.}") end local ndx = (y-1)* AllSeats.cols + x return ndx end function assignSeat(ndx,d) local kind = d.kind or SEATASSIGNED local runningrow = d.rr or 0 local runnigseat = d.rc or 0 -- tex.sprint("\\typeout{*** AllSeats[ndx] ",AllSeats,"[",ndx,"]}") -- tex.sprint("\\typeout{ kind=",kind,", text='",d.label,"', rr=",runningrow,", rs=",runnigseat,"}") if ndx ~= nil and AllSeats[ndx].kind == SEATEMPTY then AllSeats[ndx].kind = kind AllSeats[ndx].label=d.label or AllSeats[ndx].label AllSeats[ndx].rrow=runningrow AllSeats[ndx].rcol=runnigseat return true else return false end end function assignSeatAt(y,x,d) assignSeat(findSeatAt(y,x),d) end function removeSeatAt(y,x) assignSeat(findSeatAt(y,x),{kind=SEATREMOVED}) end function removeAisle(c,from,to) for r = from, to do assignSeatAt(r,c,{kind=AISLE}) end end function seatingSchemeInRows(pat,policy) local numseats = AllSeats.cols local numrows = AllSeats.rows local frow = policy["first row"] local lrow = policy["last row"] local rstep = policy["row sep"] local rres = policy["row restart"] local ltext = policy["label format"] local runningrow = 1 rs={} for r = frow, lrow do if (r-frow) % rstep == 0 then table.move({{[1]=r,[2]=runningrow}}, 1, 1, #rs + 1, rs) runningrow=runningrow+1 end if (r-frow) == rres-1 then frow = frow + rres end end -- tex.sprint("\\typeout{**** pattern=",pat,", numseats=",numseats,"}") if string.len(pat) < numseats then pat = string.rep(pat,math.ceil(numseats/string.len(pat))) end for _,row in pairs(rs) do local r=row[1] local rr=row[2] -- tex.sprint("\\typeout{**** seatingSchemeInRow called r=",r,"}") local step = 1 local pndx = 1 local seat = 1 if policy["rtol"] then step = -1 pndx = -1 seat = numseats end local runningseat=1 -- tex.sprint("\\typeout{ Seatscheme is ",pat,"}") while ((seat >= 1) and (seat<= numseats)) do local curkind = AllSeats[findSeatAt(r,seat)].kind -- tex.sprint("\\typeout{ Try seat ",seat,"(kind=",curkind,") with 'pat[",pndx,"]=",string.upper(string.sub(pat,pndx,pndx)),"'}") if string.upper(string.sub(pat,pndx,pndx)) == 'X' and ((curkind == SEATEMPTY) or (curkind == SEATASSIGNED))then -- tex.sprint("\\typeout{*** Ask for assignment, rr=",rr,", rc=",runningseat,", label =",ltext,"}") assignSeatAt(r,seat,{ kind=SEATASSIGNED, rr=rr, rc=runningseat, label=ltext } ) runningseat=runningseat+1 pndx = pndx + step elseif curkind == SEATEMPTY then pndx = pndx + step elseif string.upper(string.sub(pat,pndx,pndx)) == 'X' and curkind == SEATREMOVED and policy["ignore removed seats"]==false then pndx = pndx + step runningseat=runningseat+1 elseif curkind == SEATREMOVED and policy["ignore removed seats"]==false then pndx = pndx + step elseif curkind == AISLE then if policy["aisle restarts"] then pndx = step else pndx = pndx + policy["aisle counts"]*step end end seat = seat + step end end end -- Drawing function seatempty(s) local width = AllSeats.seatwidth local height = AllSeats.seatheight tex.sprint("\\node[rectangle,empty-seat, minimum\\space width=",width,", minimum\\space height=",height,",rotate=",s.rotate,"] at (",s.x,",",s.y,") {};") tex.sprint("\\node[empty-label, minimum\\space width=",width,",rotate=",s.rotate,"] at (",s.x,",",s.y,")".. "{\\tucemptylabelformat{",s.row,"}{",s.col,"}{",s.rrow,"}{",s.rcol,"}{",s.label,"}};") end function seatassigned(s) local width = AllSeats.seatwidth local height = AllSeats.seatheight tex.sprint("\\node[rectangle,assigned-seat, minimum\\space width=",width,", minimum\\space height=",height,",rotate=",s.rotate,"] at (",s.x,",",s.y,") {Xy};") tex.sprint("\\node[assigned-label,minimum\\space width=",width,",rotate=",s.rotate,"] at (",s.x,",",s.y,")".. "{\\scassignedlabelformat{",s.row,"}{",s.col,"}{",s.rrow,"}{",s.rcol,"}{",s.label,"}};") end function seatremoved(s) end function drawSeats() local case_tbl = { [SEATREMOVED] = seatremoved, [AISLE] = seatremoved, [SEATEMPTY] = seatempty, [SEATASSIGNED] = seatassigned, } for _ ,s in ipairs(AllSeats) do if s.kind ~= nil then -- tex.sprint("\\typeout{ *** kind=",s.kind,", x=",s.col,", y=",s.row,"}") func = case_tbl[s.kind] if func ~= nil then func(s) end end end end function getNumberOfSeats() tex.count["scnumofseats"]=AllSeats.size end function setupNextlabel(ndx) -- print("ndx=",ndx, "type:",type(ndx)) local s = AllSeats[ndx] if s and (s.kind ~=nil) and (s.kind == SEATASSIGNED) then -- print("S={",s.row,",",s.col,",",s.rrow,",",s.rcol,", '",s.label,"' }") tex.sprint("\\toggletrue{scfoundassigned}") tex.sprint("\\def\\scnextlabel{\\scassignedlabelformat{"..tostring(s.row).."}{"..tostring(s.col).."}{"..tostring(s.rrow).."}{"..tostring(s.rcol).."}{"..tostring(s.label).."}}") end end function saveTextLabel(ndx) -- print("*** Try to assign textlabel to "..tostring(ndx), tex.box[0],AllSeats[ndx],AllSeats[ndx].textlabel) if tex.box[0] and AllSeats[ndx] then -- print("*** Do assign textlabel to "..tostring(ndx)) AllSeats[ndx].textlabel="" -- print("\n") for n in node.traverse(tex.box[0].head) do -- 12: glue, 13: kern if n.id==12 or n.id==13 then AllSeats[ndx].textlabel=AllSeats[ndx].textlabel.." " end -- 29: Glyph if n.id==29 then AllSeats[ndx].textlabel=AllSeats[ndx].textlabel..string.char(n.char) end -- 7/2: disc, automatic if n.id==7 and n.subtype==2 then local rep = n.replace AllSeats[ndx].textlabel=AllSeats[ndx].textlabel..string.char(rep.char) end end end end local function parseCSVLine(line) local fields={} local field={} local quoted=false local index=1 while index <= #line do local char=string.sub(line,index,index) if char == '"' then if quoted and string.sub(line,index+1,index+1) == '"' then table.insert(field,'"') index=index+1 else quoted=not quoted end elseif char == ',' and not quoted then table.insert(fields,table.concat(field)) field={} else table.insert(field,char) end index=index+1 end table.insert(fields,table.concat(field)) return fields end local function trim(value) return (string.gsub(value,"^%s*(.-)%s*$","%1")) end local function seatingInputLabel(fields,first) local labels={} for index=first,#fields do table.insert(labels,trim(fields[index])) end return table.concat(labels,", ") end local function setImportedLabel(seat,label) if seat == nil or seat.kind == SEATREMOVED or seat.kind == AISLE then return false end seat.kind=SEATASSIGNED seat.label=label return true end function loadSeatingList(filename,mode,header) local input=io.open(filename,"r") if not input then tex.sprint("\\PackageError{\\packagename}{Can't read seating input file}{Check the file name and path.}") return end local records={} for line in input:lines() do line=string.gsub(line,"\r$","") if line ~= "" then table.insert(records,parseCSVLine(line)) end end input:close() if header and #records > 0 then table.remove(records,1) end if mode == "sequential" then local record=1 for _,seat in ipairs(AllSeats) do if seat.kind == SEATASSIGNED and records[record] ~= nil then seat.label=seatingInputLabel(records[record],1) record=record+1 end end if record <= #records then texio.write_nl("term and log","Package seatingchart Warning: ".. tostring(#records-record+1).." seating input record(s) could not be assigned") end return end for record,fields in ipairs(records) do local seat local label if mode == "seat" then seat=AllSeats[tonumber(trim(fields[1] or ""))] label=seatingInputLabel(fields,2) elseif mode == "coordinates" then local row=tonumber(trim(fields[1] or "")) local col=tonumber(trim(fields[2] or "")) if row and col and row >= 1 and row <= AllSeats.rows and col >= 1 and col <= AllSeats.cols then seat=AllSeats[(row-1)*AllSeats.cols+col] end label=seatingInputLabel(fields,3) end if not setImportedLabel(seat,label) then texio.write_nl("term and log","Package seatingchart Warning: seating input record ".. tostring(record).." refers to an unavailable seat") end end end function generateSeatList(outstream,instream, coor) local names={} local emtpydefault if instream ~= nil then f = io.open(instream,"r") if not f then tex.print("\\PackageError{\\packagename}{Can't read file '",instream,"'}{}") return else for line in io.lines(instream) do -- print("*** TAKE "..line) table.insert(names, line) end -- print("names[1]="..names[1]) emptydefault=string.gsub(names[1],"[^,]","") -- print("empty='"..emptydefault.."'") f:close() end end seatfile = io.open(outstream, "w") if not seatfile then tex.print("\\PackageError{\\packagename}{Can't read file '",instream,"'}{}") return else local index=1 for j ,s in ipairs(AllSeats) do -- print("j="..tostring(j)) if s.kind == SEATASSIGNED then if names[index] ~= nil then seatfile:write(names[index]..",") elseif instream ~= nil then -- print("** empty='"..emptydefault.."'") seatfile:write(emptydefault..",") end if coor then seatfile:write(tostring(s.row)..","..tostring(s.col)..",") end -- print("Label is",s.textlabel) seatfile:write(s.textlabel) seatfile:write(string.char(10)) index=index+1 end end seatfile:close() if index <= #names then luatexbase.add_to_callback("stop_run", screportmissing(names,index), "Report" ) end end end function screportmissing(names, index) local report=string.char(10).."\27[34mPackage 'seatingchart':"..string.char(10)..[[******************************************* The following person(s) couldn't be seated:]]..string.char(10) for i=index, #names do report=report.." - \27[1m"..names[i].."\27[22m"..string.char(10) end report=report.."*******************************************"..string.char(10) return function() texio.setescape(false) texio.write_nl(report) end end % %<*rooms-tu-chemnitz> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % A10.201 \scDeclareRoom{A10.201}{ shape=rectangle, rows=17, seats per row=16, init } % A10.204 \scDeclareRoom{A10.204}{ shape=rectangle, rows=8, seats per row=17, init, remove={{8,1},{8,2},{8,-1},{8,-2},{7,-1},{7,-2}} } % A10.305 \scDeclareRoom{A10.305}{ shape=rectangle, rows=8, seats per row=17, init, remove={{8,1},{8,2},{8,-1},{8,-2},{7,1},{7,2},{7,-1},{7,-2}} } % A10.316 \scDeclareRoom{A10.316}{ shape=arc, rows=12, seats per row=34, init, remove={ {1,1},{1,2},{1,3},{1,4},{1,5},{1,6},{1,-1},{1,-2},{1,-3},{1,-4},{1,-5},{1,-6}, {2,1},{2,2},{2,3},{2,4},{2,5},{2,-1},{2,-2},{2,-3},{2,-4},{2,-5}, {3,1},{3,2},{3,3},{3,4},{3,-1},{3,-2},{3,-3},{3,-4}, {4,1},{4,2},{4,3},{4,4},{4,-1},{4,-2},{4,-3},{4,-4}, {5,1},{5,2},{5,3},{5,-1},{5,-2},{5,-3}, {6,1},{6,2},{6,-1},{6,-2}, {7,1},{7,-1}, {8,1},{8,-1}, {9,1},{9,-1}, {11,15},{11,16},{11,17},{11,18},{11,19},{11,20}, {12,15},{12,16},{12,17},{12,18},{12,19},{12,20} } } % C10.112 \scDeclareRoom{C10.112}{ shape=rectangle, rows=13, seats per row=20, init, remove={{1,1},{1,-1}} } % C10.114 \scDeclareRoom{C10.114}{ shape=rectangle, rows=16, seats per row=27, init, remove={{1,1},{1,2},{1,-1},{1,-2},{16,-1},{6,13},{6,14},{6,15},{7,13},{7,14},{7,15}} } % C10.115 \scDeclareRoom{C10.115}{ shape=rectangle, rows=21, seats per row=35, init, aisle=18, remove={{1,1},{1,2},{1,3},{1,4},{1,-1},{1,-2},{1,-3},{1,-4}} } %