20
0
Files
cdsc_tex/slides_template/trantor.sty
Benjamin Mako Hill 75cfb2c762 Re-vendor the theme from the trantor fork
Brings in the fixes from porting the first real deck: no header band on
untitled frames, divider titles in TrantorDark, \alert in the accent and
italic quotes, the title page, dividers, and panels usable inside a
document-opened frame, \plainpage, panel geometry pinned against
document overlay styles, a compressible title page bottom space, and
standout panels left out of the frame numbering.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-20 00:11:19 +09:00

416 lines
15 KiB
TeX

% trantor.sty -- a Metropolis-style theme for the ltx-talk class.
% LuaLaTeX only.
%
% Copyright (c) 2026 Marco Pompili, https://github.com/mrc-pop/trantor
% Received under the MIT license and redistributed here, with modifications,
% under the GNU GPL version 3 or later. See COPYING.
\NeedsTeXFormat{LaTeX2e}
\ProvidesPackage{trantor}[2026-08-01 v1.0 Metropolis-like theme for ltx-talk]
% LuaTeX only (XeTeX is unsupported and the fonts need a Unicode engine).
\RequirePackage{iftex}
\RequireLuaTeX
\RequirePackage{tikz}
\RequirePackage{pifont}
% -- Fonts. Upstream sets Libertinus and loads lum-euler; both are left to the
% document, which must \setsansfont, \setmonofont and load a maths font.
\renewcommand{\familydefault}{\sfdefault}
\linespread{1.18}
\providecommand\headerfont{\sffamily}
\makeatletter
% Tighten the space around display maths -- the default 11pt reads as a big gap
% on a slide, especially inside a list. Re-applied on every \normalsize so the
% size files don't reset it.
\g@addto@macro\normalsize{%
\setlength\abovedisplayskip{5pt plus 2pt minus 1pt}%
\setlength\belowdisplayskip{5pt plus 2pt minus 1pt}%
\setlength\abovedisplayshortskip{5pt plus 2pt minus 1pt}%
\setlength\belowdisplayshortskip{5pt plus 2pt minus 1pt}%
}
% -- Colours. Set your own accent anywhere in the preamble with, e.g.,
% \definecolor{accent}{HTML}{BF1238}
% \definecolor{TrantorDark}{HTML}{23373B}
% Four colour palette:
% 1. accent progress bars, title rule
% 2. TrantorDark headings, section dividers, standout panels, frame numbers
% 3. TrantorLight text set against TrantorDark
% 4. TrantorMuted affiliation, footline, arxiv. Lightest grey that still meets
% WCAG AA contrast (4.5:1) on white (~4.54:1).
\providecolor{accent}{HTML}{2A7AE2}
\providecolor{TrantorDark}{HTML}{000000}
\providecolor{TrantorLight}{HTML}{FFFFFF}
\providecolor{TrantorMuted}{HTML}{767676}
\AddToHook{begindocument}{\colorlet{TrantorAccent}{accent}}
% itemize/enumerate labels in the body colour rather than the class default
\AddToHook{begindocument}{\colorlet{structure}{TrantorDark}}
% \alert in the accent colour rather than the class's red, as metropolis
% coloured alerted text from the theme palette
\AddToHook{begindocument}{\colorlet{alert}{TrantorAccent}}
% beamer set quotations in italic; ported decks expect it
\AddToHook{env/quote/begin}{\itshape}
\AddToHook{env/quotation/begin}{\itshape}
% -- Header bar carrying the frame title (hidden on plain pages).
% headsep is what pads under the title; the height key alone only pads above.
\geometry{headsep=10pt}
\AddToHook{begindocument}{%
\EditInstance{header}{std}{
background-color = TrantorDark,
color = TrantorLight,
print-frame-title = true,
height = 40pt,
font = \headerfont\bfseries,
left-hspace = \dimexpr\Gm@lmargin - 0.6cm\relax,
}%
\EditInstance{footer}{std}{element-order = {}}%
\EditInstance{frametitle}{header}{after-vspace = 6mm minus 3mm}%
}
% -- A frame with no \frametitle gets no header band. ltx-talk draws it
% unconditionally, which leaves an empty dark slab across the top of every
% full-bleed image slide. metropolis omitted the bar on untitled frames and
% decks are written expecting that. \trantorEmptyHeadertrue restores the bar.
%
% The band is drawn from \@oddhead, so this replaces \ps@talk; \pagestyle has
% to be re-issued for the new definition to take.
\newif\iftrantorEmptyHeader
\trantorEmptyHeaderfalse
\ExplSyntaxOn
\cs_new_protected:Npn \trantor@header
{
\iftrantorEmptyHeader
\UseInstance { header } { std }
\else
% Guard the class internal: a class change should not make this an error.
\cs_if_exist:NTF \g__talk_frame_title_tl
{
\tl_if_empty:NF \g__talk_frame_title_tl
{ \UseInstance { header } { std } }
}
{ \UseInstance { header } { std } }
\fi
}
\cs_set_nopar:Npn \ps@talk
{
\cs_set_nopar:Npn \@oddhead { \trantor@header \hfil }
\cs_set_nopar:Npn \@oddfoot { \UseInstance { footer } { std } }
\cs_set_eq:NN \@evenhead \@oddhead
\cs_set_eq:NN \@evenfoot \@oddfoot
}
\ExplSyntaxOff
\pagestyle{talk}
% Like \frametitle, but forces a gap below the title on packed slides where the
% header's own spacing collapses. The \par gets us into vertical mode first.
\NewDocumentCommand\stitle{m}{\frametitle{#1}\par\addvspace{4mm}}
% \standoutline{...}: a centred line of text in the title's sans/size/colour,
% for emphasis within a normal (light) frame, e.g. closing a slide.
\NewDocumentCommand\standoutline{m}{%
\par
\begingroup
\centering\headerfont\Large\bfseries\color{TrantorDark}#1\par
\endgroup
}
% Tick and cross, for marking things right or wrong.
\NewDocumentCommand\yes{}{\ding{51}}
\NewDocumentCommand\no{}{\ding{55}}
% \footline{...}: a small reference or citation pinned to the bottom-left of
% the slide. Overlaid, so it can go anywhere inside the frame.
\NewDocumentCommand\footline{m}{%
\begin{tikzpicture}[remember picture, overlay]
\node[anchor=south west, xshift=1ex, yshift=0.7ex, font=\scriptsize, text=TrantorMuted]
at (current page.south west) {#1};
\end{tikzpicture}%
}
% \arxiv{1306.5001}: a small, grey "arXiv:<id>" tag linked to the abstract.
% Meant to sit inline next to a statement, e.g. a theorem.
\NewDocumentCommand\arxiv{m}{%
\href{https://arxiv.org/abs/#1}{{\headerfont\scriptsize\color{TrantorMuted}arXiv:#1}}%
}
% -- \appendix: frames after it are numbered among themselves and left out of
% the total. The count travels through the aux file, so it settles on the
% second run.
% ltx-talk gained its own \appendix upstream after 0.3.12; once a release
% ships it, revisit this in favour of the class's version.
\newif\iftrantorAppendix
\trantorAppendixfalse
\newcommand\trantorMainFrames{\@totalframes}
\NewDocumentCommand \appendix { } {%
\global\trantorAppendixtrue
\protected@write\@auxout{}{%
\string\gdef\string\trantorMainFrames{\the\value{frame}}}%
\protected@write\@auxout{}{%
\string\gdef\string\trantorMainPanels{\the\value{trantorpanels}}}%
}
% -- Panels (standout pages) stay out of the frame numbering, as under
% metropolis, which set noframenumbering on standout frames. The class's frame
% counter cannot be touched -- notes and property records key on it -- so the
% panels are counted separately and subtracted for display. The totals travel
% through the aux file like \trantorMainFrames does.
\newcounter{trantorpanels}
\newcounter{trantorpanellast}
\newcommand\trantorTotalPanels{0}
\newcommand\trantorMainPanels{\trantorTotalPanels}
% \immediate: a deferred write would wait for a shipout that never comes.
\AtEndDocument{%
\immediate\write\@auxout{%
\string\gdef\string\trantorTotalPanels{\the\value{trantorpanels}}}%
}
% Count each panel frame once, though the body may run once per slide.
\newcommand\trantor@countpanel{%
\ifnum\value{trantorpanellast}=\value{frame}\else
\stepcounter{trantorpanels}% \stepcounter and \setcounter act globally
\setcounter{trantorpanellast}{\value{frame}}%
\fi
}
% Count registers, not macros: pgfmath expands while parsing, and a macro
% carrying a conditional loops.
\newcount\trantor@n
\newcount\trantor@d
\newcommand\trantor@setcounts{%
\iftrantorAppendix
\trantor@n=\numexpr\value{frame}-\trantorMainFrames
-\value{trantorpanels}+\trantorMainPanels\relax
\trantor@d=\numexpr\@totalframes-\trantorMainFrames
-\trantorTotalPanels+\trantorMainPanels\relax
\else
\trantor@n=\numexpr\value{frame}-\value{trantorpanels}\relax
\trantor@d=\numexpr\trantorMainFrames-\trantorMainPanels\relax
\fi
}
% Fraction of the way through the talk, shared by the footer bar and
% \trantorProgressBar. The zero test must stay outside pgfmath, which
% evaluates both arms of a ternary.
\newcommand\trantor@setratio{%
\trantor@setcounts
\ifnum\trantor@d>0
\pgfmathsetmacro{\trantorratio}{min(\the\trantor@n / \the\trantor@d, 1)}%
\else
\def\trantorratio{0}%
\fi
}
% \trantorProgressBar{<width>}: a rule of the given width, filled to the
% current position and grey the rest of the way.
\newlength\trantor@barwidth
\newlength\trantor@barfill
\NewDocumentCommand \trantorProgressBar { m } {%
\setlength\trantor@barwidth{#1}%
\trantor@setratio
% dimen = <factor><dimen>, so \trantorratio scales the width
\setlength\trantor@barfill{\trantorratio\trantor@barwidth}%
\textcolor{TrantorAccent}{\rule{\trantor@barfill}{0.6pt}}%
\textcolor{TrantorDark!20}{%
\rule{\dimexpr\trantor@barwidth-\trantor@barfill\relax}{0.6pt}}%
}
% -- Bottom progress bar + frame counter (hidden on plain pages).
\newif\iftrantorPlainPage
\trantorPlainPagefalse
\AddToHook{shipout/foreground}{%
\iftrantorPlainPage\else
\trantorArtifactBegin
\begin{tikzpicture}[remember picture, overlay]
\trantor@setratio
\fill[TrantorAccent]
(current page.south west)
rectangle
([xshift=\trantorratio\paperwidth, yshift=1pt] current page.south west);
\node[anchor=south east, inner sep=5pt, font=\tiny, text=TrantorDark]
at (current page.south east) {\the\trantor@n\,/\,\the\trantor@d};
\end{tikzpicture}%
\trantorArtifactEnd
\fi
\global\trantorPlainPagefalse
}
% -- Remember the section and subsection titles, for the dividers.
\ExplSyntaxOn
\tl_new:N \g__trantor_section_tl
\AddToHook{section/begin}{
\cs_if_exist:NT \l__talk_section_tl {
\tl_gset:NV \g__trantor_section_tl \l__talk_section_tl
}
}
\cs_new_protected:Npn \__trantor_print_section: { \tl_use:N \g__trantor_section_tl }
\cs_set_eq:NN \trantorSectionTitle \__trantor_print_section:
% Likewise the subsection, for \subsectionpage. Cleared when a new section
% starts so a stale title cannot leak into the next one.
\tl_new:N \g__trantor_subsection_tl
\AddToHook{section/begin}{ \tl_gclear:N \g__trantor_subsection_tl }
\AddToHook{subsection/begin}{
\cs_if_exist:NT \l__talk_subsection_tl {
\tl_gset:NV \g__trantor_subsection_tl \l__talk_subsection_tl
}
}
\cs_new_protected:Npn \__trantor_print_subsection: { \tl_use:N \g__trantor_subsection_tl }
\cs_set_eq:NN \trantorSubsectionTitle \__trantor_print_subsection:
% Suspend tagging around the theme's decorative / pagination graphics when
% tagging is on (a no-op otherwise), so the progress bar and frame counter stay
% out of the structure tree instead of being flagged as loose content.
\cs_new_protected:Npn \trantorArtifactBegin
{ \cs_if_exist:NT \tag_suspend:n { \tag_suspend:n { trantor } } }
\cs_new_protected:Npn \trantorArtifactEnd
{ \cs_if_exist:NT \tag_resume:n { \tag_resume:n { trantor } } }
\ExplSyntaxOff
% Run #1 inside the current frame if there is one, else inside a fresh frame.
% Mirrors the class's \maketitle, so a document can wrap any of these in its
% own frame and add content alongside -- a \note, or \footline for a revision
% stamp -- following the beamer idiom.
\ExplSyntaxOn
\cs_new_protected:Npn \trantor@inframe #1
{
\bool_if:NTF \l__talk_frame_bool
{#1}
{ \begin { frame } #1 \end { frame } }
}
\ExplSyntaxOff
% \plainpage: suppress the footer progress bar and frame number on the current
% frame's pages, as the title, standout, and divider pages do. Use it at the
% top of a frame, e.g. one holding a full-bleed image.
\NewDocumentCommand\plainpage{}{%
\global\trantorPlainPagetrue
\thispagestyle{plain}%
}
% -- Title page. Drop a logo.png next to your slides, or redefine \titlelogo.
\newcommand{\titlelogo}{%
\IfFileExists{logo.png}
{\includegraphics[height=1.4cm, alt={Organisation logo}]{logo.png}}{}%
}
\renewcommand{\maketitle}{%
\trantor@inframe{%
\plainpage
\vfill
\hspace*{0.04\paperwidth}%
\begin{minipage}{0.82\paperwidth}
\raggedright
{\color{TrantorDark}\headerfont\Large\bfseries \@title\par}%
\ifx\@subtitle\empty\else
\vspace{0.35em}%
{\color{TrantorDark}\headerfont\large \@subtitle\par}%
\fi
\vspace{0.5em}%
{\color{TrantorAccent}\rule{\linewidth}{0.8pt}\par}%
\vspace{1.3em}%
% Metadata on the left, logo on the right; bottom-aligned so the logo sits
% level with the date and grows upward rather than below it.
\begin{minipage}[b]{0.66\linewidth}
\raggedright
{\normalsize \@author\par}%
\ifx\@institute\empty\else
\vspace{0.2em}%
{\small\color{TrantorMuted} \@institute\par}%
\fi
\vspace{0.2em}%
{\small \@date\par}%
\end{minipage}%
\hfill
\begin{minipage}[b]{0.30\linewidth}
\raggedleft
\titlelogo
\end{minipage}%
\end{minipage}%
\hspace*{0.04\paperwidth}%
% shrinkable, so a long author block compresses this space instead of
% overfilling the fixed frame height
\vspace{0.12\paperheight minus 0.08\paperheight}%
}%
}
% -- A full-bleed dark panel with the given body centred in a minipage of the
% given width.
\NewDocumentCommand \trantor@panel { m +m } {%
\trantor@inframe{%
\plainpage
\trantor@countpanel
\noindent
% overlay=false explicitly: the panel is positioned from its bounding box,
% so it breaks if the document sets \tikzstyle{every picture}+=[overlay],
% which decks ported from beamer commonly do.
\begin{tikzpicture}[overlay=false, remember picture=false]
\useasboundingbox (0,0) rectangle (\linewidth,-\textheight);
\fill[TrantorDark]
(-\Gm@lmargin, \paperheight)
rectangle
(\dimexpr\paperwidth-\Gm@lmargin\relax, -\paperheight);
\node[text=TrantorLight, anchor=center, inner sep=0pt]
at (0.5\linewidth, -0.5\textheight) {%
\begin{minipage}{#1}
\centering
#2
\end{minipage}%
};
\end{tikzpicture}%
}%
}
% -- Divider pages: the section title over a progress bar, set on the ordinary
% light background. Not plain pages: the footer bar and frame number stay.
% Like the class's \maketitle, the dividers work inside a frame the document
% opened itself, so beamer's \begin{frame}\sectionpage\note{...}\end{frame}
% idiom carries over. Called between frames, they open their own.
\NewDocumentCommand \trantor@divider { +m } {%
\trantor@inframe{%
\thispagestyle{plain}%
\vfill
\centering
\begin{minipage}{0.52\linewidth}%
\raggedright
{\color{TrantorDark}\headerfont\Large\bfseries\trantorSectionTitle\par}%
\vspace{0.4em}%
\trantorProgressBar{\linewidth}%
#1%
\end{minipage}%
\par
\vfill
}%
}
\NewDocumentCommand \sectionpage { } { \trantor@divider{} }
% The subsection divider is the same, naming the subsection under the bar.
\NewDocumentCommand \subsectionpage { } {%
\trantor@divider{%
\par\vspace{0.8em}%
{\color{TrantorDark}\headerfont\large\bfseries\trantorSubsectionTitle\par}%
}%
}
% \standout{...}: a full-bleed dark panel with the given text centred on it.
\NewDocumentCommand \standout { +m } {%
\trantor@panel{0.8\linewidth}{\headerfont\LARGE\bfseries #1}%
}
\makeatother