%<*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}
%</driver>
%<*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
}
%</package>
%<*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
%</lua>
%<*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}}
}
%</rooms-tu-chemnitz>
