ffx/script/thalie.sty
2025-04-23 12:48:30 +01:00

693 lines
18 KiB
TeX

% Copyright 2010-2022 Louis Paternault
%
% This work may be distributed and/or modified under the
% conditions of the LaTeX Project Public License, either version 1.3
% of this license or (at your option) any later version.
% The latest version of this license is in
% http://www.latex-project.org/lppl.txt
% and version 1.3 or later is part of all distributions of LaTeX
% version 2005/12/01 or later.
%
% This work has the LPPL maintenance status `maintained'.
%
% The Current Maintainer of this work is Louis Paternault
%
% This work consists of the files thalie.tex and thalie.sty.
\NeedsTeXFormat{LaTeX2e}
\ProvidesPackage{thalie}
[2022/12/11 v0.13a A package to typeset drama plays]
\RequirePackage{pgfkeys}
\RequirePackage{etoolbox}
\RequirePackage{suffix}
\RequirePackage{tabularx}
\RequirePackage{xspace}
\RequirePackage{translations}
\LoadDictionary{thalie}
\LoadDictionaryFor{fallback}{thalie}
\newcommand{\playname}{\GetTranslation{Play}}
\newcommand{\actname}{\GetTranslation{Act}}
\newcommand{\scenename}{\GetTranslation{Scene}}
\newcommand{\interludename}{\GetTranslation{Interlude}}
\newcommand{\curtainname}{\GetTranslation{Curtain}}
\newcommand{\pausename}{\GetTranslation{Pause}}
\newcommand{\playmark}[1]{%
\markboth{\MakeUppercase{#1}}{}%
}
\newcommand{\actmark}[1]{%
\markright{\MakeUppercase{%
\GetTranslation{Act}\ \theact%
\ifdefempty{#1}{}{: #1}%
}}%
}
\newcommand{\scenemark}[1]{%
}
\newcounter{play}
\renewcommand{\theplay}{\arabic{play}}
\newcounter{act}[play]
\renewcommand{\theact}{\Roman{act}}
\newcounter{scene}[act]
\renewcommand{\thescene}{\arabic{scene}}
\newcommand{\@displaytitle}[3]{
% Arguments:
% - Style
% - Label (none = not in toc)
% - Title
\ifdefstring{#1}{center}{
\begin{center}
\textsc{#2}
#3
\end{center}
}{\ifdefstring{#1}{bigcenter}{
\begin{center}
\Large
\textsc{#2}
#3
\end{center}
}{\ifdefstring{#1}{box}{
\begin{center}
\framebox{\begin{minipage}{0.7\textwidth}
\begin{center}
\Large \bfseries
\vspace{0.5em}
#2
\ifboolexpr{test{\ifstrempty{#3}} or test{\ifstrempty{#2}}}{}{---}
#3
\vspace{0.5em}
\end{center}
\end{minipage}}
\end{center}
\vspace{1em}
}{}}}%
}
\newcommand\@clearpage[1]{%
% Clear page if necessary
\ifboolexpr{test{\ifdefstring{#1}{part}} or test{\ifdefstring{#1}{chapter}}}{
\cleardoublepage
\thispagestyle{empty}
}{}%
}
\newcommand{\play}[2][]{%
\refstepcounter{play}
\ifstrempty{#1}{
\def\@short{#2}
}{
\def\@short{#1}
}
\@clearpage{\@playlevel}
\playmark{\@short}
\addcontentsline{toc}{\@playlevel}{\@short}
\ifdefstring{\@playstyle}{custom}{
\customplay{\theplay}{#2}
}{
\@displaytitle{\@playstyle}{}{#2}
}
}
\WithSuffix\newcommand\play*[1]{%
\@clearpage{\@playlevel}
\ifdefstring{\@playstyle}{custom}{
\customplay*{#1}
}{
\@displaytitle{\@playstyle}{}{#1}
}
}
\newcommand{\act}[2][]{%
\refstepcounter{act}
\ifstrempty{#1}{
\def\@short{#2}
}{
\def\@short{#1}
}
\ifdefempty{\@short}{
\def\@label{\GetTranslation{Act} \theact{}}
}{
\def\@label{\GetTranslation{Act} \theact{}\xspace: }
}
\@clearpage{\@actlevel}
\actmark{\@short}
\addcontentsline{toc}{\@actlevel}{\@label\@short}
\ifdefstring{\@actstyle}{custom}{
\customact{\theact}{#2}
}{
\@displaytitle{\@actstyle}{\GetTranslation{Act} \theact}{#2}
}
}
\WithSuffix\newcommand\act*[1]{%
\@clearpage{\@actlevel}
\ifdefstring{\@actstyle}{custom}{
\customact*{#1}
}{
\@displaytitle{\@actstyle}{}{#1}
}
}
\newcommand{\scene}[2][]{%
\refstepcounter{scene}
\ifstrempty{#1}{
\def\@short{#2}
}{
\def\@short{#1}
}
\ifdefempty{\@short}{
\def\@label{\GetTranslation{Scene} \thescene{}}
}{
\def\@label{\GetTranslation{Scene} \thescene{}\xspace: }
}
\@clearpage{\@scenelevel}
\scenemark{\@short}
\addcontentsline{toc}{\@scenelevel}{\@label\@short}
\ifdefstring{\@scenestyle}{custom}{
\customscene{\thescene}{#2}
}{
\@displaytitle{\@scenestyle}{\GetTranslation{Scene} \thescene}{#2}
}
}
\WithSuffix\newcommand\scene*[1]{%
\@clearpage{\@scenelevel}
\ifdefstring{\@scenestyle}{custom}{
\customscene*{#1}
}{
\@displaytitle{\@scenestyle}{}{#1}
}
}
\newcommand{\interlude}[2][]{%
\ifstrempty{#1}{
\def\@short{#2}
}{
\def\@short{#1}
}
\ifdefempty{\@short}{
\def\@label{\GetTranslation{Interlude}}
}{
\def\@label{\GetTranslation{Interlude}\xspace: }
}
\ifdefstring{\@interludelevel}{play}{
\@clearpage{\@playlevel}
\playmark{\@short}
\addcontentsline{toc}{\@playlevel}{\@label\@short}
\@displaytitle{\@playstyle}{\GetTranslation{Interlude}}{#2}
}{\ifdefstring{\@interludelevel}{act}{
\@clearpage{\@actlevel}
\actmark{\@short}
\addcontentsline{toc}{\@actlevel}{\@label\@short}
\@displaytitle{\@actstyle}{\GetTranslation{Interlude}}{#2}
}{% \@interludelevel is scene
\@clearpage{\@scenelevel}
\scenemark{\@short}
\addcontentsline{toc}{\@scenelevel}{\@label\@short}
\@displaytitle{\@scenestyle}{\GetTranslation{Interlude}}{#2}
}}
}
\WithSuffix\newcommand\interlude*[1]{%
\ifdefstring{\@interludelevel}{play}{
\@clearpage{\@playlevel}
\@displaytitle{\@playstyle}{\GetTranslation{Interlude}}{#1}
}{\ifdefstring{\@interludelevel}{act}{
\@clearpage{\@actlevel}
\@displaytitle{\@actstyle}{\GetTranslation{Interlude}}{#1}
}{% \@interludelevel is scene
\@clearpage{\@scenelevel}
\@displaytitle{\@scenestyle}{\GetTranslation{Interlude}}{#1}
}}
}
\newcommand\curtain{
\begin{center}
\Large\textsc{\GetTranslation{Curtain}}
\end{center}
}
\newcommand{\@maybexspace}{%
\if@xspace%
\xspace%
\fi%
}
\newcommand{\@speaks}[2][]{%
\ifstrempty{#1}{%
\speakswithoutdirection{#2}%
}{%
\speakswithdirection{#2}{#1}%
}\@maybexspace%
}
\newenvironment{@smallcenter}
{\par\smallskip\centering}
{\par\nopagebreak\ignorespacesafterend}
\providecommand{\speakswithdirection}{}
\providecommand{\speakswithoutdirection}{}
\newcommand{\@setcharacterstyle}[1]{
\ifstrequal{#1}{bold}{%
% Bold style
\renewcommand\speakswithdirection[2]{%
\noindent%
{\bfseries\sffamily ##1} \emph{(##2)}\xspace:%
}
\renewcommand\speakswithoutdirection[1]{%
\noindent%
{\bfseries\sffamily ##1\xspace:}%
}%
}{}%
\ifstrequal{#1}{center}{%
% Center style
\renewcommand\speakswithdirection[2]{%
\begin{center}%
\textsc{##1},\\\emph{##2}%
\end{center}%
\par\ignorespacesafterend%
}%
\renewcommand\speakswithoutdirection[1]{%
\begin{center}%
\textsc{##1}%
\end{center}%
\par\ignorespacesafterend%
}%
}{}%
\ifstrequal{#1}{imprimerie-verse}{%
% Style for verse plays defined by the French Imprimerie nationale
\renewcommand\speakswithdirection[2]{%
\begin{@smallcenter}%
\textsc{##1}, \emph{##2}%
\end{@smallcenter}%
}%
\renewcommand\speakswithoutdirection[1]{%
\begin{@smallcenter}%
\textsc{##1}%
\end{@smallcenter}%
}%
}{}%
\ifstrequal{#1}{imprimerie-prose}{%
% Style for prose plays defined by the French Imprimerie nationale
\renewcommand\speakswithdirection[2]{%
\noindent\hspace*{-\parindent}\textsc{##1}, \emph{##2}\xspace:%
}%
\renewcommand\speakswithoutdirection[1]{%
\noindent\hspace*{-\parindent}\textsc{##1}\xspace:%
}%
}{}%
\ifstrequal{#1}{arden}{%
\renewcommand\speakswithdirection[2]{%
\noindent\hspace*{-\parindent}\textsc{\MakeLowercase{##1}} [\emph{##2}]\quad%
}%
\renewcommand\speakswithoutdirection[1]{%
\noindent\hspace*{-\parindent}\textsc{\MakeLowercase{##1}}\quad%
}%
}{}%
\ifstrequal{#1}{simple}{%
% Simple style
\renewcommand\speakswithdirection[2]{%
\indent\textsc{##1}, \emph{##2}\xspace:%
}%
\renewcommand\speakswithoutdirection[1]{%
\indent\textsc{##1}\xspace:%
}%
}{}%
\ifstrequal{#1}{margin}{%
% Margin style
\setlength{\leftskip}{3cm}
\renewcommand\speakswithdirection[2]{%
\hspace{-3cm} ##1 ##2
}
\renewcommand\speakswithoutdirection[1]{%
\hspace{-3cm} ##1
}%
}{}%
}
\providebool{@dramatis@hidden}
\pgfkeys{
% Character definition
/THALIE/DRAMATIS/.is family,
/THALIE/DRAMATIS,
hidden/.default=true,
hidden/.is choice,
hidden/true/.code=\booltrue{@dramatis@hidden},
hidden/false/.code=\boolfalse{@dramatis@hidden},
defaultcast/.default={},
defaultcast/.value required,
defaultcast/.store in=\@defaultcast,
}
\newcommand{\@dramatis@clear}{}
\newcommand{\@empty@}{}
\newenvironment{dramatis}[1][]{
\@dramatis@clear{}
\undef{\@dramatis@clear}
\pgfkeys{/THALIE/DRAMATIS/.cd, #1}
\ifbool{@dramatis@hidden}{%
% Nothing
}{%
\dramatisenv%
}%
}{%
\notbool{@dramatis@hidden}{%
\enddramatisenv
}{}%
}
\newenvironment{dramatisenv}{%
\list{}{\rightmargin1cm\leftmargin2cm}\item[]
}{%
\endlist%
}
\newcommand{\dramatischaractername}[1]{\textbf{#1}}
\newcommand{\dramatischaracterdescription}[1]{#1}
\newcommand{\dramatischaractercast}[1]{#1}
\newcommand{\characterspace}{ %
\notbool{@dramatis@hidden}{%
\smallskip\newline %
}{}%
}
\newcommand{\dramatischaracter}[3]{ %
\hspace*{-1cm} %
\ifboolexpr{(not test {\ifdefempty{#1}}) and test {\ifdefempty{#2}}}{%
\dramatischaractername{#1} %
}{}%
\ifboolexpr{ test{\ifdefempty{#1}} and not test{\ifdefempty{#2}}}{%
\dramatischaracterdescription{#2} %
}{}%
\ifboolexpr{ (not test{\ifdefempty{#1}}) and (not test{\ifdefempty{#2}})}{%
\dramatischaractername{#1}, \dramatischaracterdescription{#2} %
}{}%
\ifdefempty{#3}{}{\dotfill\dramatischaractercast{#3}}%
\newline %
}
\newlength{\@spaceaftergroup}
\newenvironment{dramatischaractergroup}[2]{
\gdef\@groupname{#2} %
\gdef\@grouplength{#1} %
\hspace*{-1.3pt}\math\left. %
\minipage[c]{#1} %
\vspace*{2pt} %
}{%
\vspace*{-8pt} %
\endminipage %
\right\} \endmath %
%
\setlength{\@spaceaftergroup}{\linewidth}
\addtolength\@spaceaftergroup{-\@grouplength}
\addtolength\@spaceaftergroup{-20pt}
\begin{minipage}[c]{\@spaceaftergroup}
\@groupname %
\end{minipage}
\newline %
}
\newenvironment{charactergroup}[2][5cm]{%
\notbool{@dramatis@hidden}{%
\dramatischaractergroup{#1}{#2}
}{}%
}{%
\notbool{@dramatis@hidden}{%
\enddramatischaractergroup
}{}%
}
\newenvironment{dramatischaractercastgroup}[3]{%
\ifdefempty{#3}{
\hspace*{-1cm} %
\ifboolexpr{(not test {\ifdefempty{#1}}) and test {\ifdefempty{#2}}}{%
\dramatischaractername{#1} %
}{}%
\ifboolexpr{ test{\ifdefempty{#1}} and not test{\ifdefempty{#2}}}{%
\dramatischaracterdescription{#2} %
}{}%
\ifboolexpr{ (not test{\ifdefempty{#1}}) and (not test{\ifdefempty{#2}})}{%
\dramatischaractername{#1}, \dramatischaracterdescription{#2} %
}{}%
\hfill%
}{
\ClassError{thalie}{%
Environment "castgroup" cannot have a non-empty "cast" argument.%
}{}%
}%
}{}
\newenvironment{castgroup}[2][]{%
\@thalie@parsecharacter{#1}{#2}{dramatischaractercastgroup}%
\notbool{@dramatis@hidden}{%
\math\left\{%
\array{r}%
}{}%
}{%
\notbool{@dramatis@hidden}{%
\endarray%
\right.\endmath%
\newline%
}{}%
\enddramatischaractercastgroup
}
\newcommand{\dramatiscast}[1]{%
% Command used to display cast inside a castgroup.
% Can be redefined by user.
\notbool{@dramatis@hidden}{%
\hbox{\dramatischaractercast{#1}\hspace*{-6pt}}\tabularnewline%
}{}%
}
\newcommand{\cast}[1]{%
% "Public" command, used by author in the dramatis personae.
% Does nothing fancy right now, but how knows?
\dramatiscast{#1}%
}
\newcommand{\setcharactername}[2]{%
\expandafter\gdef\csname#1name\endcsname{%
#2\@maybexspace%
}%
\expandafter\gdef\csname#1\endcsname{%
\@ifnextchar[{%
\defcharcommand@with{#2}%
}{%
\defcharcommand@without{#2}%
}%
}%
\xappto{\@dramatis@clear}{%
\global\noexpand\csundef{#1}%
\global\noexpand\csundef{#1name}%
}%
}
\newcommand{\@definecharactercommand}[2]{%
\ifcsdef{#1}{%
\ClassError{thalie}{%
A command named \@backslashchar#1 already exists. We cannot define a new
one.%
}{%
Choose another command name to introduce character #2's lines.%
}%
}{%
}%
\ifcsdef{#1name}{%
\ClassError{thalie}{%
A command named \@backslashchar#1name already exists. We cannot define a
new one.%
}{%
Choose another command name to introduce character #2's lines, such that
when a new command is defined by adding "name" to it, it does not
conflict with an existing one.
}%
}{%
}%
\setcharactername{#1}{#2}%
}
\def\defcharcommand@with#1[#2]{\@speaks[#2]{#1}}
\def\defcharcommand@without#1{\@speaks{#1}}
\pgfkeys{
% Character definition
/THALIE/CHARACTER/.is family,
/THALIE/CHARACTER,
cmd/.value required,
cmd/.store in=\@cmd,
cast/.value required,
cast/.store in=\@cast,
drama/.value required,
drama/.store in=\@drama,
desc/.value required,
desc/.store in=\@desc,
}
\newcommand{\@thalie@parsecharacter}[3]{%
% Parse a character definition. Arguments are:
% #1: Optional arguments of \character: [drama={foo}, cast={bar}]
% #2: Mandatory argument of \character (character name)
% #3: Name of the command to call to display this character definition.
\undef{\@drama}
\undef{\@cmd}
\undef{\@cast}
\undef{\@desc}
\pgfkeys{/THALIE/CHARACTER, #1}%
\ifcsundef{@cast}{%
\ifcsdef{@defaultcast}{%
\gdef\@cast{\@defaultcast}%
}{%
\gdef\@cast{}%
}%
}{}%
% Forbidden combinations
\ifboolexpr{
( ( test{\ifdef{\@cmd}} and not test{\ifdefempty{\@cmd}} ) and test{\ifstrempty{#2}} ) or
( ( test{\ifundef{\@cmd}} or test{\ifdefempty{\@cmd}} ) and not test{\ifstrempty{#2}} ) or
( test{\ifdefempty{\@drama}} and ( test{\ifdef{\@desc}} and not test{\ifdefempty{\@desc}} ) ) or
( test{\ifundef{\@cmd}} and test{\ifundef{\@desc}} and test{\ifundef{\@drama}} and test{\ifstrempty{#2}} ) or
%( ( test{\ifundef{\@cmd}} or test{\ifdefempty{\@cmd}} ) and test{\ifstrempty{#2}} and ( test{\ifdefempty{\@drama}} or test{\ifundef{\@drama}} ))
( ( test{\ifundef{\@desc}} or test{\ifdefempty{\@desc}} ) and test{\ifstrempty{#2}} and ( test{\ifdefempty{\@drama}} or test{\ifundef{\@drama}} ))
}{
\ClassError{thalie}{Invalid character definition.}{All combination of omitted arguments are not allowed. See the documentation for more information}
}{}%
% Defining character command
\ifboolexpr{ test{\ifdef{\@cmd}} and (not test{\ifstrempty{#2}}) }{%
\@definecharactercommand{\@cmd}{#2}
}{}%
\ifboolexpr{ bool{@dramatis@hidden} or test{\ifdefempty{\@drama}} }{%
% Hidden character. Nothing added to dramatis personae
}{%
\ifcsundef{@desc}{\gdef\@desc{}}{}%
\ifcsundef{@drama}{\gdef\@drama{#2}}{}%
\csuse{#3}{\@drama}{\@desc}{\@cast}%
}%
}
\newcommand{\character}[2][]{%
\@thalie@parsecharacter{#1}{#2}{dramatischaracter}%
}
\newcommand{\disposablecharacter}[2][]{%
\@speaks[#1]{#2}%
}
\newcommand{\onstage}[1]{{\centering \emph{#1}\par\medskip}}
\newcommand{\did}[1]{\emph{(#1)}\@maybexspace}
\newenvironment{dida}{%
\begin{quote}
\begin{em}
}{%
\end{em}
\end{quote}
}
\newcommand\pause{\did{\GetTranslation{Pause}}}
\newlength{\@verseadjust}
\setlength{\@verseadjust}{0pt}
\newcommand{\adjustverse}[1]{\setlength{\@verseadjust}{#1}}
\newcommand{\pauseverse}{{\abovedisplayshortskip\z@\abovedisplayskip\z@
\belowdisplayshortskip\z@\belowdisplayskip\z@
$$\global\dimen\@ne\predisplaysize
\xdef\tmp{%
\predisplaysize\the\predisplaysize
\prevgraf\the\prevgraf\relax}%
$$\vskip\dimexpr-\parskip-\baselineskip\relax}\tmp
}
\newcommand{\resumeverse}{%
\hspace{\@verseadjust}\hspace{\the\dimen\@ne}
}
\RequirePackage{pgfopts}
\pgfkeys{
% Character style
/THALIE/.cd,
characterstyle/.value required,
characterstyle/.default=simple,
characterstyle/.is choice,
characterstyle/bold/.code=\@setcharacterstyle{bold},
characterstyle/center/.code=\@setcharacterstyle{center},
characterstyle/margin/.code=\@setcharacterstyle{margin},
characterstyle/simple/.code=\@setcharacterstyle{simple},
characterstyle/arden/.code=\@setcharacterstyle{arden},
characterstyle/imprimerie-verse/.code=\@setcharacterstyle{imprimerie-verse},
characterstyle/imprimerie-prose/.code=\@setcharacterstyle{imprimerie-prose},
characterstyle,
}
\pgfkeys{
% play style
/THALIE/.cd,
playstyle/.value required,
playstyle/.default=box,
playstyle/.is choice,
playstyle/center/.code=\def\@playstyle{center},
playstyle/bigcenter/.code=\def\@playstyle{bigcenter},
playstyle/box/.code=\def\@playstyle{box},
playstyle/custom/.code=\def\@playstyle{custom},
playstyle,
}
\pgfkeys{
% act style
/THALIE/.cd,
actstyle/.value required,
actstyle/.default=bigcenter,
actstyle/.is choice,
actstyle/center/.code=\def\@actstyle{center},
actstyle/bigcenter/.code=\def\@actstyle{bigcenter},
actstyle/box/.code=\def\@actstyle{box},
actstyle/custom/.code=\def\@actstyle{custom},
actstyle,
}
\pgfkeys{
% scene style
/THALIE/.cd,
scenestyle/.value required,
scenestyle/.default=center,
scenestyle/.is choice,
scenestyle/center/.code=\def\@scenestyle{center},
scenestyle/bigcenter/.code=\def\@scenestyle{bigcenter},
scenestyle/box/.code=\def\@scenestyle{box},
scenestyle/custom/.code=\def\@scenestyle{custom},
scenestyle,
}
\pgfkeys{
% play level
/THALIE/.cd,
playlevel/.value required,
playlevel/.default=chapter,
playlevel/.store in=\@playlevel,
playlevel,
}
\pgfkeys{
% act level
/THALIE/.cd,
actlevel/.value required,
actlevel/.default=section,
actlevel/.store in=\@actlevel,
actlevel,
}
\pgfkeys{
% scene level
/THALIE/.cd,
scenelevel/.value required,
scenelevel/.default=subsection,
scenelevel/.store in=\@scenelevel,
scenelevel,
}
\pgfkeys{
% interlude level
/THALIE/.cd,
interludelevel/.value required,
interludelevel/.default=act,
interludelevel/.is choice,
interludelevel/play/.code=\def\@interludelevel{play},
interludelevel/act/.code=\def\@interludelevel{act},
interludelevel/scene/.code=\def\@interludelevel{scene},
interludelevel,
}
\newif\if@xspace
\pgfkeys{
% xspace option
/THALIE/.cd,
xspace/.value required,
xspace/.is if=@xspace,
xspace/.default=true,
xspace,
}
\ProcessPgfPackageOptions{/THALIE}
\newcommand{\setthalieoptions}[1]{%
\pgfkeys{/THALIE/.cd, #1}%
}
\endinput
%%
%% End of file `thalie.sty'.