diff --git a/slides_template/.gitignore b/slides_template/.gitignore new file mode 100644 index 0000000..d0bedd2 --- /dev/null +++ b/slides_template/.gitignore @@ -0,0 +1,27 @@ +/auto/ +/vc +/*.aux +/*.log +/*.out +/*.pdf +/*.fdb_latexmk +/*.fls +/*.ttt +/*.nav +/*.toc +/*.snm +/*.xdv +/notes.config +*.aux +*.fdb_latexmk +*.fls +*.log +*.nav +*.notes +*.out +*.snm +*.toc +*.pdf +*.png +*-luamml-mathml.html +*.swp diff --git a/slides_template/Makefile b/slides_template/Makefile new file mode 100644 index 0000000..8c5fb52 --- /dev/null +++ b/slides_template/Makefile @@ -0,0 +1,38 @@ +#!/usr/bin/make + +# fonts.tex is a fragment meant to be \input, not a document of its own, so it +# has to stay out of the list or make will try to typeset it. +TEX := $(filter-out fonts.tex,$(wildcard *.tex)) +PDF := $(patsubst %.tex,%.pdf,$(TEX)) +JOB := $(patsubst %.tex,%,$(TEX)) + +all: slides +slides: vc $(PDF) + +%.pdf: %.tex + latexmk -f -lualatex $< + +# Double-width pages, slide left and notes right, for dspdfviewer and pdfpc. +# The notes pages are typeset with the same fonts as the slides. +notes: slides + ./mknotes $(JOB) + +vc: + resources/vc-git + +viewpdf: slides + evince $(PDF) + +pdfpc: notes + pdfpc -n right $(JOB)-notes.pdf + +spell: + aspell -c -t --tex-check-comments -b $(TEX) + +clean: + latexmk -C $(TEX) + rm -f *.notes *.tmp *.nav *.snm *.pdfpc *-luamml-mathml.html + rm -f vc + rm -f $(JOB)-notes.pdf + +.PHONY: all slides notes vc viewpdf pdfpc spell clean diff --git a/slides_template/README b/slides_template/README new file mode 100644 index 0000000..894d556 --- /dev/null +++ b/slides_template/README @@ -0,0 +1,109 @@ +================================ +=== Mako's Beamer Templates ==== +================================ + +Copyright (c) 2013-2026 Benjamin Mako Hill / + +This repository contains a Beamer presentation template I use for my +own presentations. Others in the CDSC lab also use it for lab +presentations. It is released under the GNU GPL version 3 or later. + +The latest version can be found on GitLab at: +https://gitlab.com/makoshark/beamer-mako + +You can clone the repository with: + + git clone https://gitlab.com/makoshark/beamer-mako.git + +Bug reports, comments, questions, and patches can be directed to: + + Benjamin Mako Hill [work] + Benjamin Mako Hill [personal] + +Patches can be submitted as merge requests via GitLab, or by cloning +the repository and sending patches by email: + + git format-patch origin/master + git send-email *.patch + +================================ +=== Dependencies =============== +================================ + +On Debian or Ubuntu, install the following packages: + + * latexmk + * texlive-luatex + * texlive-latex-recommended + * texlive-latex-extra + + apt install latexmk texlive-luatex texlive-latex-recommended \ + texlive-latex-extra + +On other systems, installing a full TeX Live distribution should provide +everything needed. + +The template uses the Metropolis Beamer theme, bundled in the template/ +directory along with the OpenSans and RobotoMono fonts it requires. + +================================ +=== Accessibility ============== +================================ + +Slides are built as tagged PDFs that validate against PDF/UA-2 +(ISO 14289-2), which is what upload targets like Canvas check for. Check +a file with veraPDF (https://verapdf.org/): + + verapdf -f ua2 my-talk.pdf + +The pieces that do this work are: + + * \DocumentMetadata declares PDF/UA-2, PDF 2.0, and the document + language. The language tag tells a screen reader how to pronounce + the text. + * tagpdf with activate-all turns tagging on. + * The frametitle template is patched to emit H1 tags, since Beamer + does not treat frame titles as headings on its own. + * unicode-math replaces the Computer Modern math fonts, which carry no + Unicode mappings and leave formulas unreadable to assistive software. + * \hypersetup{pdfdisplaydoctitle=true} makes viewers announce the + document title instead of the filename. + * The tikz overlays behind \credit, \marktopleft, and \markbottomright + are marked as artifacts so they stay out of the reading order. + +Use a slides-only build (make slides) for anything shared. Builds that +include speaker notes do not validate, because Beamer repeats the frame +title and section headings on the notes half of each page. + +Validating is not the same as being accessible, and this template does +the first better than the second. Frame titles and links are tagged; +body text, lists, and figures are not, so a screen reader gets the +outline of a talk rather than its contents. Closing that gap needs +automatic tagging from the LaTeX kernel (\DocumentMetadata{tagging=on}), +which requires TeX Live 2025 or later and is waiting on Overleaf to +support it, and Beamer support in the LaTeX Tagging Project, which does +not exist yet. The alt={...} arguments in the template are written for +that day; they are dropped by the current toolchain. + +================================ +=== Scripts ==================== +================================ + +new_beamer_presentation +----------------------- +Creates a new presentation directory from the template. Takes a +destination directory as its argument: + + new_beamer_presentation + +Example: + + new_beamer_presentation my-talk + +This creates my-talk/ populated with the template files, with the main +source file renamed to my-talk.tex. Fonts are symlinked from the source +repository rather than copied, so the repository must remain in place. + +Symlink the script into your PATH with: + + ln -s ~/tex/beamer-mako/new_beamer_presentation ~/bin/ diff --git a/slides_template/README.ltx-talk.md b/slides_template/README.ltx-talk.md new file mode 100644 index 0000000..11fc10f --- /dev/null +++ b/slides_template/README.ltx-talk.md @@ -0,0 +1,97 @@ +# ltx-talk port + +This branch is the presentation template ported from beamer to the +[ltx-talk](https://github.com/josephwright/ltx-talk) class. ltx-talk was +written with tagging as a design goal. + +## Requirements + + * LuaLaTeX + * TeX Live 2025 or newer (ltx-talk needs LaTeX 2025-11-01 or later); + `texlive-latex-recommended` ships the class + * `python3-pypdf`, for assembling the notes PDF + +You can install all three on Debian with: + +``` +apt install texlive-latex-recommended python3-pypdf +``` + +## Building + +``` +make slides # the deck; this is what gets uploaded +make notes # double-width slide + notes pages for presenting +make pdfpc # build the notes PDF and open it in pdfpc +``` + +Share the slides build, not the notes build: only the slides are tagged and +expected to validate with tools like [veraPDF](https://verapdf.org/). + +## Migrating from beamer + +| beamer | here | +|---|---| +| `\begin{frame}{Title}` | `\frametitle{Title}` inside the frame | +| `\begin{frame}[standout]{}` | `\standout{...}` | +| `\sectionpage` from the theme | `\section{...}` then `\sectionpage` | +| `\subsectionpage` | `\subsection{...}` then `\subsectionpage` | +| `\note{...}`, `\note[item]{...}` | same, after `\usepackage{talk-notes}` | +| `\appendix` | same | +| `\alert{...}`, `\item<2->`, `\pause` | same | +| `\only<1>{...}` | avoid; use `\pause` or `\begin{itemize}[<+->]` | + +Use `\frametitle`, not the braced argument: by default the class treats +`\begin{frame}{Title}` as body text, so the title is not tagged as a heading. +Give every `\includegraphics` an `alt={...}` description, and declare table +header rows with `\tagpdfsetup{table/header-rows={1}}` before the tabular. + +## Speaker notes + +ltx-talk has no `\note` command (see +[issue #156](https://github.com/josephwright/ltx-talk/issues/156)). +`talk-notes.sty` defines one that records its text to a side file and +contributes nothing to the slides; `mknotes` reads that file afterwards and +builds the presenter PDF. Present it with +[dspdfviewer](https://github.com/dannyedel/dspdfviewer), +[pdfpc](https://pdfpc.github.io/), or the web viewers +[Beamer Viewer](https://beamerviewer.euxane.eu/) and +[backstage](https://bckstg.xyz/). + +### Installing speaker notes + +To use speaker notes in another ltx-talk document, copy two files out of this +repository: + +- `mknotes` +- `talk-notes.sty` + +Load the package in the preamble: + +```latex +\usepackage{talk-notes} +``` + +Write notes with `\note{...}`, or `\note<2->{...}` to bind one to particular +slides of a frame. As in beamer, plain notes run together as text, each +starting a new paragraph, and `\note[item]{...}` adds an entry to a numbered +list printed after them. + +Build the deck, then assemble the presenter PDF: + +``` +./mknotes example +``` + +That reads `example.pdf` and `example.notes` and writes `example-notes.pdf`, +the double-width version you present from. The slides in `example.pdf` are +untouched. + +If `fonts.tex` is present it is used for the notes pages, so they match the +deck. `--preamble FILE` names a different fragment. + +## Theme + +`trantor.sty` is [Marco Pompili's](https://github.com/mrc-pop) +[Metropolis-like theme](https://github.com/mrc-pop/trantor) for +ltx-talk. diff --git a/slides_template/example.tex b/slides_template/example.tex new file mode 100644 index 0000000..adf9925 --- /dev/null +++ b/slides_template/example.tex @@ -0,0 +1,123 @@ +\DocumentMetadata{ + tagging=on, + tagging-setup={math/setup=mathml-SE}, + pdfstandard=ua-2, + pdfversion=2.0, + lang=en-US, +} +\documentclass[aspectratio=169]{ltx-talk} + +\usepackage{unicode-math} +\usepackage{graphicx} +\usepackage{booktabs} + +\input{fonts.tex} + +\definecolor{makopurple1}{RGB}{57,39,91} +\definecolor{makopurple4}{RGB}{124,99,173} +\definecolor{accent}{RGB}{124,99,173} +\colorlet{TrantorDark}{makopurple1} + +\usepackage{trantor} +\usepackage{talk-notes} + +\hypersetup{pdfdisplaydoctitle=true,pdftitle={Presentation Title}} + +\renewcommand{\titlelogo}{% + \includegraphics[height=1.4cm, + alt={Community Data Science Collective logo}]{figures/logo.pdf}} + +% \e{} was the beamer template's emphasis command; ltx-talk provides \alert +\newcommand{\e}[1]{\alert{#1}} + +\title{Presentation Title} +\subtitle{Presentation Subtitle} +\author{\textbf{Benjamin Mako Hill}\\ + makohill@uw.edu} +\institute{\textbf{University of Washington}\\ + Department of Communication\\ + \emph{Associate Professor}\\ + \hspace{1pt}\\ + \textbf{Harvard University}\\ + Berkman Klein Center\\ + \emph{Faculty Associate}} +\date{December 2, 1980} + +\begin{document} + +\maketitle + +\section{Introduction} +\sectionpage + +\begin{frame} + \frametitle{A sample slide} + A displayed formula: + \[ \int_{-\infty}^\infty e^{-x^2} \, dx = \sqrt{\pi} \] + + An itemized list: + \begin{itemize}[<+->] + \item itemized item 1 + \item itemized item 2 + \item itemized item 3 + \end{itemize} + + \footline{Image credit: Shaw and Hill, 2099.} + + \note<1>{Set up the integral before revealing anything else.} + \note{Mention that this is the Gaussian integral.} + \note<3->{Once the whole list is up, tie it back to the opening claim.} + \note[item]{Check the clock here.} + \note[item]{Ask whether anyone has seen the 2099 preprint.} +\end{frame} + +\begin{frame} + \frametitle{Short frame title} + \e{A first} paragraph with \texttt{monospace text}. + + And then \e{a second} paragraph. + + \note{A note that applies to this whole frame.} +\end{frame} + +\subsection{An Introductory Subsection} +\subsectionpage + +\begin{frame} + \frametitle{After the subsection page} + Subsections get their own divider, showing the section above the subsection. + + \note{The subsection page is trantor's, not ltx-talk's.} +\end{frame} + +\appendix +\standout{Example and Data Appendix} + +\begin{frame} + \frametitle{A table} + % without this the header cells are tagged TD like any other + \tagpdfsetup{table/header-rows={1}} + \begin{tabular}{lrr} + \toprule + Wiki & Editors & Articles \\ + \midrule + English & 121{,}000 & 6{,}900{,}000 \\ + German & 17{,}000 & 2{,}900{,}000 \\ + Cebuano & 150 & 6{,}100{,}000 \\ + \bottomrule + \end{tabular} + + \note{Numbers are made up; the point is that the table is tagged.} +\end{frame} + +\begin{frame} + \frametitle{A figure} + \includegraphics[width=0.3\linewidth, + alt={Community Data Science Collective logo}]{figures/logo.pdf} + + Every figure needs alt text, or the artifact option if it is decoration. + + \note{Alt text reaches the PDF here, unlike under beamer.} +\end{frame} + +\end{document} diff --git a/slides_template/figures/CDCS_logo.svg b/slides_template/figures/CDCS_logo.svg new file mode 100644 index 0000000..a5296f9 --- /dev/null +++ b/slides_template/figures/CDCS_logo.svg @@ -0,0 +1,75 @@ + + + +image/svg+xml \ No newline at end of file diff --git a/slides_template/figures/CDSC_logo-notext.svg b/slides_template/figures/CDSC_logo-notext.svg new file mode 100644 index 0000000..ccdc7df --- /dev/null +++ b/slides_template/figures/CDSC_logo-notext.svg @@ -0,0 +1,71 @@ + + + +image/svg+xml \ No newline at end of file diff --git a/slides_template/figures/logo-nowords.pdf b/slides_template/figures/logo-nowords.pdf new file mode 100644 index 0000000..f1ebc81 Binary files /dev/null and b/slides_template/figures/logo-nowords.pdf differ diff --git a/slides_template/figures/logo.pdf b/slides_template/figures/logo.pdf new file mode 100644 index 0000000..9d27d81 Binary files /dev/null and b/slides_template/figures/logo.pdf differ diff --git a/slides_template/figures/nu_logo.pdf b/slides_template/figures/nu_logo.pdf new file mode 100644 index 0000000..811f935 Binary files /dev/null and b/slides_template/figures/nu_logo.pdf differ diff --git a/slides_template/figures/uw_logo.pdf b/slides_template/figures/uw_logo.pdf new file mode 100644 index 0000000..ae9512c Binary files /dev/null and b/slides_template/figures/uw_logo.pdf differ diff --git a/slides_template/fonts.tex b/slides_template/fonts.tex new file mode 100644 index 0000000..747563b --- /dev/null +++ b/slides_template/fonts.tex @@ -0,0 +1,19 @@ +% Font setup shared by the slides and by the notes pages that mknotes +% typesets, so that the two match. Loaded by example.tex directly and passed +% to mknotes with --preamble. +% +% Requires fontspec, which unicode-math pulls in on the slides side. Paths must +% stay relative to the document directory: LuaTeX fails to resolve a +% parent-relative Path= and dies with an unhelpful empty-filename error. + +\setsansfont{OpenSans}[ + Path = fonts/OpenSans/, Extension = .ttf, + UprightFont = *-Regular, BoldFont = *-Bold, + ItalicFont = *-Italic, BoldItalicFont = *-BoldItalic] + +\setmonofont{RobotoMono}[ + Path = fonts/RobotoMono/, Extension = .ttf, + UprightFont = *-Regular, BoldFont = *-Bold, + ItalicFont = *-Italic, BoldItalicFont = *-BoldItalic] + +\renewcommand{\familydefault}{\sfdefault} diff --git a/slides_template/fonts/OpenSans/OpenSans-Bold.ttf b/slides_template/fonts/OpenSans/OpenSans-Bold.ttf new file mode 100644 index 0000000..7b52945 Binary files /dev/null and b/slides_template/fonts/OpenSans/OpenSans-Bold.ttf differ diff --git a/slides_template/fonts/OpenSans/OpenSans-BoldItalic.ttf b/slides_template/fonts/OpenSans/OpenSans-BoldItalic.ttf new file mode 100644 index 0000000..a670e14 Binary files /dev/null and b/slides_template/fonts/OpenSans/OpenSans-BoldItalic.ttf differ diff --git a/slides_template/fonts/OpenSans/OpenSans-Italic.ttf b/slides_template/fonts/OpenSans/OpenSans-Italic.ttf new file mode 100644 index 0000000..e6c5414 Binary files /dev/null and b/slides_template/fonts/OpenSans/OpenSans-Italic.ttf differ diff --git a/slides_template/fonts/OpenSans/OpenSans-Regular.ttf b/slides_template/fonts/OpenSans/OpenSans-Regular.ttf new file mode 100644 index 0000000..2e31d02 Binary files /dev/null and b/slides_template/fonts/OpenSans/OpenSans-Regular.ttf differ diff --git a/slides_template/fonts/RobotoMono/RobotoMono-Bold.ttf b/slides_template/fonts/RobotoMono/RobotoMono-Bold.ttf new file mode 100644 index 0000000..c6a81a5 Binary files /dev/null and b/slides_template/fonts/RobotoMono/RobotoMono-Bold.ttf differ diff --git a/slides_template/fonts/RobotoMono/RobotoMono-BoldItalic.ttf b/slides_template/fonts/RobotoMono/RobotoMono-BoldItalic.ttf new file mode 100644 index 0000000..b2261d6 Binary files /dev/null and b/slides_template/fonts/RobotoMono/RobotoMono-BoldItalic.ttf differ diff --git a/slides_template/fonts/RobotoMono/RobotoMono-Italic.ttf b/slides_template/fonts/RobotoMono/RobotoMono-Italic.ttf new file mode 100644 index 0000000..6e4001e Binary files /dev/null and b/slides_template/fonts/RobotoMono/RobotoMono-Italic.ttf differ diff --git a/slides_template/fonts/RobotoMono/RobotoMono-Regular.ttf b/slides_template/fonts/RobotoMono/RobotoMono-Regular.ttf new file mode 100644 index 0000000..495a82c Binary files /dev/null and b/slides_template/fonts/RobotoMono/RobotoMono-Regular.ttf differ diff --git a/slides_template/mknotes b/slides_template/mknotes new file mode 100755 index 0000000..571ec99 --- /dev/null +++ b/slides_template/mknotes @@ -0,0 +1,384 @@ +#!/usr/bin/env python3 +"""Build a presenter PDF from a slides PDF and the notes captured beside it. + +The ltx-talk class has no speaker notes, so talk-notes.sty records them to a +side file while the slides build ignores them. This program reads that file and +reassembles the two halves. + +The reason a separate program is needed at all is that a frame is not a page: +overlays expand one frame into several slides, and neither the source nor a +reader of the finished PDF can work out which page belongs to which frame. +talk-notes.sty resolves that by writing a record per page at shipout time, so +the mapping here is exact rather than inferred. + +The output is double-width pages, slide on the left and notes on the right, +which is the format dspdfviewer and pdfpc expect. The notes half carries a grey +header band with the section and position in the talk, a thumbnail of the +current slide flush into its outer corner, and the notes below. Previous and +next slides are left to the viewer, which already shows them. + +The output is not tagged and is not meant to be shared: it exists to be looked +at while talking. The slides build is the one that gets uploaded. +""" + +import argparse +import os +import re +import shutil +import subprocess +import sys +import tempfile + +from pypdf import PageObject, PdfReader, PdfWriter, Transformation + + +# ---------------------------------------------------------------- parsing + +class Page: + """One shipped page, and where it sits in the talk.""" + + def __init__(self, number, frame, slide): + self.number = number + self.frame = frame + self.slide = slide + self.section = "" + self.subsection = "" + self.notes = [] + self.items = [] + + +def parse_notes_file(path): + """Return (pages, notes_by_frame) from a .notes file.""" + pages = [] + notes = {} + seen = set() + + with open(path, encoding="utf-8") as handle: + lines = handle.read().split("\n") + + i = 0 + while i < len(lines): + line = lines[i].strip() + + match = re.match(r"^PAGE (\d+)\|FRAME (\d+)\|SLIDE (\d+)$", line) + if match: + pages.append(Page(*(int(g) for g in match.groups()))) + i += 1 + continue + + if line.startswith("SECTION ") or line == "SECTION": + if pages: + pages[-1].section = line[8:].strip() + i += 1 + continue + + if line.startswith("SUBSECTION ") or line == "SUBSECTION": + if pages: + pages[-1].subsection = line[11:].strip() + i += 1 + continue + + match = re.match(r"^NOTE FRAME (\d+)\|SPEC (.*)\|KIND (.*)$", line) + if match: + frame = int(match.group(1)) + spec, kind = match.group(2).strip(), match.group(3).strip() + body = [] + i += 1 + while i < len(lines) and lines[i].strip() != "ENDNOTE": + text = lines[i] + body.append(text[5:] if text.startswith("TEXT ") else text) + i += 1 + i += 1 + text = " ".join(part.strip() for part in body).strip() + # A note is re-run for every slide of its frame, so the same record + # arrives once per slide. Keep the first occurrence only. + key = (frame, spec, kind, text) + if key not in seen: + seen.add(key) + notes.setdefault(frame, []).append((spec, kind, text)) + continue + + i += 1 + + return pages, notes + + +def spec_matches(spec, slide, last): + """Does an overlay spec such as '2-', '-3', '1,4' cover this slide? + + A bare '-' means the note was given without a spec and applies throughout. + 'n' inside a range means the frame's last slide, as in ltx-talk. + """ + if spec in ("", "-"): + return True + + def value(token, default): + token = token.strip() + if token == "": + return default + if token == "n": + return last + try: + return int(token) + except ValueError: + return default + + for part in spec.split(","): + part = part.strip() + if not part: + continue + if "-" in part: + low, _, high = part.partition("-") + if value(low, 1) <= slide <= value(high, last): + return True + elif value(part, -1) == slide: + return True + return False + + +def attach_notes(pages, notes): + """Work out which notes belong on each page.""" + last_slide = {} + for page in pages: + last_slide[page.frame] = max(last_slide.get(page.frame, 0), page.slide) + + for page in pages: + for spec, kind, text in notes.get(page.frame, []): + if spec_matches(spec, page.slide, last_slide[page.frame]): + if kind == "item": + page.items.append(text) + else: + page.notes.append(text) + + +# ---------------------------------------------------------------- typesetting + +#: Side margin on the generated notes pages, as a fraction of the page. +HMARGIN = 0.06 + +#: Font setup shared with the slides. Used when it exists, so that the notes +#: match the deck without anyone having to remember a flag. +DEFAULT_PREAMBLE = "fonts.tex" + +# The page is laid out by hand rather than by geometry: the header band runs +# full bleed to the top and outer edges to meet the thumbnail, so the margins +# have to be applied per element instead of to the page. +NOTES_PREAMBLE = r"""\documentclass{article} +\usepackage[paperwidth=%(width).2fbp,paperheight=%(height).2fbp, + margin=0bp]{geometry} +\usepackage{parskip} +\usepackage{xcolor} +\usepackage{fontspec} +\definecolor{notegrey}{RGB}{230,230,230} +\pagestyle{empty} +\setlength{\parindent}{0pt} +\setlength{\fboxsep}{0pt} +\setlength{\topskip}{0pt} +%(preamble)s +\begin{document} +""" + + +def escape_for_context(text): + """Guard the few characters that would derail a generated file. + + Note bodies are written out by talk-notes.sty as literal source, so LaTeX + markup inside a note is deliberately preserved. Only a bare percent sign is + dangerous, since it would comment out the rest of the generated line. + """ + return re.sub(r"(?-notes.pdf)") + parser.add_argument("--preamble", metavar="FILE", default=DEFAULT_PREAMBLE, + help="LaTeX fragment to \\input into the notes pages, " + "so they can share the slides' font setup " + f"(default: {DEFAULT_PREAMBLE} when it exists)") + parser.add_argument("--thumb-scale", type=float, default=0.29, + metavar="F", + help="slide thumbnail size on the notes half, as a " + "fraction of the slide (default: 0.29; 0 disables)") + args = parser.parse_args() + + slides_path = args.jobname + ".pdf" + notes_path = args.jobname + ".notes" + for path in (slides_path, notes_path): + if not os.path.exists(path): + raise SystemExit(f"mknotes: {path} not found; build the slides first") + + pages, notes = parse_notes_file(notes_path) + if not pages: + raise SystemExit(f"mknotes: no page records in {notes_path}") + attach_notes(pages, notes) + + slides = PdfReader(slides_path) + if len(slides.pages) != len(pages): + raise SystemExit( + f"mknotes: {slides_path} has {len(slides.pages)} pages but " + f"{notes_path} describes {len(pages)}; rebuild the slides") + + box = slides.pages[0].mediabox + width, height = float(box.width), float(box.height) + thumb = max(0.0, min(args.thumb_scale, 0.9)) + + # A preamble named on the command line has to be there; the default one is + # a convenience and is skipped when the document does not use it. + preamble = args.preamble + if preamble and not os.path.exists(preamble): + if preamble != DEFAULT_PREAMBLE: + raise SystemExit(f"mknotes: {preamble} not found") + preamble = None + + workdir = tempfile.mkdtemp(prefix="mknotes-") + try: + notes_pdf = PdfReader(build_notes_pdf( + pages, width, height, width * thumb, height * thumb, workdir, + preamble=preamble)) + writer = assemble_notes(slides, notes_pdf, width, height, thumb) + + output = args.output or f"{args.jobname}-notes.pdf" + with open(output, "wb") as handle: + writer.write(handle) + print(f"mknotes: wrote {output} ({len(writer.pages)} pages)") + finally: + shutil.rmtree(workdir, ignore_errors=True) + + +if __name__ == "__main__": + main() diff --git a/slides_template/new_beamer_presentation b/slides_template/new_beamer_presentation new file mode 100755 index 0000000..911718d --- /dev/null +++ b/slides_template/new_beamer_presentation @@ -0,0 +1,43 @@ +#!/bin/sh + +# Helper script to export LaTeX templates for new documents +# Copyright (c) 2009-2022 Benjamin Mako Hill +# Released under the GPLv3 or later. + +set -e + +SOURCEDIR="$HOME/tex/beamer-mako" +CURDIR=$(pwd) + +if [ -n "$1" ] +then + if [ -e "$1" ]; then + echo "error: '$1' already exists" + exit 1 + fi + + WORK_DIR=$(mktemp -d) + cd "$SOURCEDIR" + git archive --format=tar HEAD template | tar x --strip=1 -C "$WORK_DIR" + cd "$CURDIR" + mv "$WORK_DIR" "$1" + + mv "$1/example.tex" "$1/$(basename "$1").tex" + + # create a symlink farm for the fonts + cd "$1" + rm -rf ./fonts/* + + # first create the directories + for fontdir in "${SOURCEDIR}/template/fonts/"*; do + mkdir "./fonts/$(basename "${fontdir}")" + # then the files + for fontfile in "${fontdir}/"*; do + ln -s "${fontfile}" "./fonts/$(basename "${fontdir}")/$(basename "${fontfile}")" + done + done + + cd "$CURDIR" +else + echo "specify a directory where the template should go" +fi diff --git a/slides_template/pgfplotsthemetol.sty b/slides_template/pgfplotsthemetol.sty new file mode 100644 index 0000000..7f07743 --- /dev/null +++ b/slides_template/pgfplotsthemetol.sty @@ -0,0 +1,123 @@ +%% +%% This is file `pgfplotsthemetol.sty', +%% generated with the docstrip utility. +%% +%% The original source files were: +%% +%% pgfplotsthemetol.dtx (with options: `package') +%% --------------------------------------------------------------------------- +%% Copyright 2015 Matthias Vogelgesang and the LaTeX community. A full list of +%% contributors can be found at +%% +%% https://github.com/matze/mtheme/graphs/contributors +%% +%% and the original template was based on the HSRM theme by Benjamin Weiss. +%% +%% This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 +%% International License (https://creativecommons.org/licenses/by-sa/4.0/). +%% --------------------------------------------------------------------------- +\NeedsTeXFormat{LaTeX2e} +\ProvidesPackage{pgfplotsthemetol} + [2017/01/23 PGFplots colors based on Paul Tol's SRON technical note] +\definecolor{TolDarkPurple}{HTML}{332288} +\definecolor{TolDarkBlue}{HTML}{6699CC} +\definecolor{TolLightBlue}{HTML}{88CCEE} +\definecolor{TolLightGreen}{HTML}{44AA99} +\definecolor{TolDarkGreen}{HTML}{117733} +\definecolor{TolDarkBrown}{HTML}{999933} +\definecolor{TolLightBrown}{HTML}{DDCC77} +\definecolor{TolDarkRed}{HTML}{661100} +\definecolor{TolLightRed}{HTML}{CC6677} +\definecolor{TolLightPink}{HTML}{AA4466} +\definecolor{TolDarkPink}{HTML}{882255} +\definecolor{TolLightPurple}{HTML}{AA4499} +\pgfplotscreateplotcyclelist{mbarplot cycle}{% + {draw=TolDarkBlue, fill=TolDarkBlue!70}, + {draw=TolLightBrown, fill=TolLightBrown!70}, + {draw=TolLightGreen, fill=TolLightGreen!70}, + {draw=TolDarkPink, fill=TolDarkPink!70}, + {draw=TolDarkPurple, fill=TolDarkPurple!70}, + {draw=TolDarkRed, fill=TolDarkRed!70}, + {draw=TolDarkBrown, fill=TolDarkBrown!70}, + {draw=TolLightRed, fill=TolLightRed!70}, + {draw=TolLightPink, fill=TolLightPink!70}, + {draw=TolLightPurple, fill=TolLightPurple!70}, + {draw=TolLightBlue, fill=TolLightBlue!70}, + {draw=TolDarkGreen, fill=TolDarkGreen!70}, +} +\pgfplotscreateplotcyclelist{mlineplot cycle}{% + {TolDarkBlue, mark=*, mark size=1.5pt}, + {TolLightBrown, mark=square*, mark size=1.3pt}, + {TolLightGreen, mark=triangle*, mark size=1.5pt}, + {TolDarkBrown, mark=diamond*, mark size=1.5pt}, +} +\pgfplotsset{ + compat=1.9, + mlineplot/.style={ + mbaseplot, + xmajorgrids=true, + ymajorgrids=true, + major grid style={dotted}, + axis x line=bottom, + axis y line=left, + legend style={ + cells={anchor=west}, + draw=none + }, + cycle list name=mlineplot cycle, + }, + mbarplot base/.style={ + mbaseplot, + bar width=6pt, + axis y line*=none, + }, + mbarplot/.style={ + mbarplot base, + ybar, + xmajorgrids=false, + ymajorgrids=true, + area legend, + legend image code/.code={% + \draw[#1] (0cm,-0.1cm) rectangle (0.15cm,0.1cm); + }, + cycle list name=mbarplot cycle, + }, + horizontal mbarplot/.style={ + mbarplot base, + xmajorgrids=true, + ymajorgrids=false, + xbar stacked, + area legend, + legend image code/.code={% + \draw[#1] (0cm,-0.1cm) rectangle (0.15cm,0.1cm); + }, + cycle list name=mbarplot cycle, + }, + mbaseplot/.style={ + legend style={ + draw=none, + fill=none, + cells={anchor=west}, + }, + x tick label style={ + font=\footnotesize + }, + y tick label style={ + font=\footnotesize + }, + legend style={ + font=\footnotesize + }, + major grid style={ + dotted, + }, + axis x line*=bottom, + }, + disable thousands separator/.style={ + /pgf/number format/.cd, + 1000 sep={} + }, +} +\endinput +%% +%% End of file `pgfplotsthemetol.sty'. diff --git a/slides_template/resources/vc-git b/slides_template/resources/vc-git new file mode 100755 index 0000000..8625f6e --- /dev/null +++ b/slides_template/resources/vc-git @@ -0,0 +1,24 @@ +#!/bin/sh +# This is file 'vc' from the vc bundle for TeX. +# The original file can be found at CTAN:support/vc. +# This file is Public Domain. + +# Parse command line options. +full=0 +mod=0 +while [ -n "$(echo $1 | grep '-')" ]; do + case $1 in + -f ) full=1 ;; + -m ) mod=1 ;; + * ) echo 'usage: vc [-f] [-m]' + exit 1 + esac + shift +done +# English locale. +LC_ALL=C +git --no-pager log -1 HEAD --pretty=format:"Hash: %H%nAbr. Hash: %h%nParent Hashes: %P%nAbr. Parent Hashes: %p%nAuthor Name: %an%nAuthor Email: %ae%nAuthor Date: %ai%nCommitter Name: %cn%nCommitter Email: %ce%nCommitter Date: %ci%n" |gawk -v script=log -v full=$full -f ./resources/vc-git.awk > vc +if [ "$mod" = 1 ] +then + git status |gawk -v script=status -f ~/bin/vc-git.awk >> vc +fi diff --git a/slides_template/resources/vc-git.awk b/slides_template/resources/vc-git.awk new file mode 100644 index 0000000..66b3526 --- /dev/null +++ b/slides_template/resources/vc-git.awk @@ -0,0 +1,89 @@ +# This is file 'vc-git.awk' from the vc bundle for TeX. +# The original file can be found at CTAN:support/vc. +# This file is Public Domain. +BEGIN { + +### Process output of "git status". + if (script=="status") { + modified = 0 + } + +} + + + +### Process output of "git log". +script=="log" && /^Hash:/ { Hash = substr($0, 2+match($0, ":")) } +script=="log" && /^Abr. Hash:/ { AbrHash = substr($0, 2+match($0, ":")) } +script=="log" && /^Parent Hashes:/ { ParentHashes = substr($0, 2+match($0, ":")) } +script=="log" && /^Abr. Parent Hashes:/ { AbrParentHashes = substr($0, 2+match($0, ":")) } +script=="log" && /^Author Name:/ { AuthorName = substr($0, 2+match($0, ":")) } +script=="log" && /^Author Email:/ { AuthorEmail = substr($0, 2+match($0, ":")) } +script=="log" && /^Author Date:/ { AuthorDate = substr($0, 2+match($0, ":")) } +script=="log" && /^Committer Name:/ { CommitterName = substr($0, 2+match($0, ":")) } +script=="log" && /^Committer Email:/ { CommitterEmail = substr($0, 2+match($0, ":")) } +script=="log" && /^Committer Date:/ { CommitterDate = substr($0, 2+match($0, ":")) } + +### Process output of "git status". +### Changed index? +script=="status" && /^# Changes to be committed:/ { modified = 1 } +### Unstaged modifications? +script=="status" && /^# Changed but not updated:/ { modified = 2 } + + + +END { + +### Process output of "git log". + if (script=="log") { +### Standard encoding is UTF-8. + if (Encoding == "") Encoding = "UTF-8" +### Extract relevant information from variables. + LongDate = substr(AuthorDate, 1, 25) + DateRAW = substr(LongDate, 1, 10) + DateISO = DateRAW + DateTEX = DateISO + gsub("-", "/", DateTEX) + Time = substr(LongDate, 12, 14) +### Write file identification to vc.tex. + print "%%% This file has been generated by the vc bundle for TeX." + print "%%% Do not edit this file!" + print "%%%" +### Write Git specific macros. + print "%%% Define Git specific macros." + print "\\gdef\\GITHash{" Hash "}%" + print "\\gdef\\GITAbrHash{" AbrHash "}%" + print "\\gdef\\GITParentHashes{" ParentHashes "}%" + print "\\gdef\\GITAbrParentHashes{" AbrParentHashes "}%" + print "\\gdef\\GITAuthorName{" AuthorName "}%" + print "\\gdef\\GITAuthorEmail{" AuthorEmail "}%" + print "\\gdef\\GITAuthorDate{" AuthorDate "}%" + print "\\gdef\\GITCommitterName{" CommitterName "}%" + print "\\gdef\\GITCommitterEmail{" CommitterEmail "}%" + print "\\gdef\\GITCommitterDate{" CommitterDate "}%" +### Write generic version control macros. + print "%%% Define generic version control macros." + print "\\gdef\\VCRevision{\\GITAbrHash}%" + print "\\gdef\\VCAuthor{\\GITAuthorName}%" + print "\\gdef\\VCDateRAW{" DateRAW "}%" + print "\\gdef\\VCDateISO{" DateISO "}%" + print "\\gdef\\VCDateTEX{" DateTEX "}%" + print "\\gdef\\VCTime{" Time "}%" + print "\\gdef\\VCModifiedText{\\textcolor{red}{with local modifications!}}%" + print "%%% Assume clean working copy." + print "\\gdef\\VCModified{0}%" + print "\\gdef\\VCRevisionMod{\\VCRevision}%" + } + +### Process output of "git status". + if (script=="status") { + print "%%% Is working copy modified?" + print "\\gdef\\VCModified{" modified "}%" + if (modified==0) { + print "\\gdef\\VCRevisionMod{\\VCRevision}%" + } else { + print "\\gdef\\VCRevisionMod{\\VCRevision~\\VCModifiedText}%" + } + } + +} diff --git a/slides_template/talk-notes.sty b/slides_template/talk-notes.sty new file mode 100644 index 0000000..456b668 --- /dev/null +++ b/slides_template/talk-notes.sty @@ -0,0 +1,83 @@ +% talk-notes.sty -- capture speaker notes for assembly outside LaTeX. +% +% The ltx-talk class has no \note command (upstream issue #156 is open, and the +% maintainer has said notes are wanted but unimplemented). This package fills +% the gap without touching the slides themselves: \note{...} typesets nothing +% and instead records its text to \jobname.notes, which the mknotes program +% reads to build the presenter PDF. +% +% The awkward part of doing this outside LaTeX is that a frame does not +% correspond to a page: overlays expand one frame into several slides, and the +% source cannot predict how many. That is why the page records are written from +% the shipout hook, which is the first point at which the frame and slide +% identity of a page is actually known. + +\NeedsTeXFormat{LaTeX2e} +\ProvidesPackage{talk-notes}[2026-08-14 v0.1 Capture speaker notes for ltx-talk] + +\ExplSyntaxOn + +\iow_new:N \g__talknotes_iow +\tl_new:N \g__talknotes_section_tl +\tl_new:N \g__talknotes_subsection_tl + +\AddToHook { begindocument } + { \iow_open:Nn \g__talknotes_iow { \c_sys_jobname_str .notes } } +% The stream is deliberately not closed by hand: LaTeX closes it at the end of +% the run, and closing it from the enddocument hook races the final shipout. + +% Track the section and subsection so the notes page can show where we are. +% ltx-talk exposes the current title as \l__talk_section_tl; guard the lookup +% so that a class change does not turn this into an error. +\AddToHook { section/begin } + { + \cs_if_exist:NT \l__talk_section_tl + { + \tl_gset:NV \g__talknotes_section_tl \l__talk_section_tl + \tl_gclear:N \g__talknotes_subsection_tl + } + } +\AddToHook { subsection/begin } + { + \cs_if_exist:NT \l__talk_subsection_tl + { \tl_gset:NV \g__talknotes_subsection_tl \l__talk_subsection_tl } + } + +% One record per shipped page. +\AddToHook { shipout/before } + { + \iow_now:Nx \g__talknotes_iow + { + PAGE ~ \arabic { page } | + FRAME ~ \int_use:N \g__talk_frame_int | + SLIDE ~ \int_use:N \g__talk_slide_int + } + \iow_now:Nx \g__talknotes_iow + { SECTION ~ \tl_to_str:N \g__talknotes_section_tl } + \iow_now:Nx \g__talknotes_iow + { SUBSECTION ~ \tl_to_str:N \g__talknotes_subsection_tl } + } + +% \note[item]{text} +% +% Follows beamer's interface. A plain \note contributes running text; \note[item] +% contributes an entry to a numbered list printed after that text. +% +% The body is written out stringified, so LaTeX markup inside a note survives +% to be typeset again on the notes page. A note is recorded once per slide of +% its frame (the frame body is re-run for each), so mknotes deduplicates. +% Records are bracketed by ENDNOTE because a long note may be broken across +% output lines. +\NewDocumentCommand \note { d<> o +m } + { + \iow_now:Nx \g__talknotes_iow + { + NOTE ~ FRAME ~ \int_use:N \g__talk_frame_int | + SPEC ~ \IfNoValueTF {#1} { - } { \tl_to_str:n {#1} } | + KIND ~ \IfNoValueTF {#2} { text } { \tl_to_str:n {#2} } + } + \iow_now:Nx \g__talknotes_iow { TEXT ~ \tl_to_str:n {#3} } + \iow_now:Nn \g__talknotes_iow { ENDNOTE } + } + +\ExplSyntaxOff diff --git a/slides_template/trantor.sty b/slides_template/trantor.sty new file mode 100644 index 0000000..bee92c0 --- /dev/null +++ b/slides_template/trantor.sty @@ -0,0 +1,316 @@ +% 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}} + +% -- 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}% +} + +% 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:" 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. +\newif\iftrantorAppendix +\trantorAppendixfalse +\newcommand\trantorMainFrames{\@totalframes} + +\NewDocumentCommand \appendix { } {% + \global\trantorAppendixtrue + \protected@write\@auxout{}{% + \string\gdef\string\trantorMainFrames{\the\value{frame}}}% +} + +% 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\relax + \trantor@d=\numexpr\@totalframes-\trantorMainFrames\relax + \else + \trantor@n=\value{frame}\relax + \trantor@d=\numexpr\trantorMainFrames\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{}: 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 = , 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 + + +% -- 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}{% + \global\trantorPlainPagetrue + \begin{frame}{}% + \thispagestyle{plain}% + \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}% + \vspace{0.12\paperheight}% + \end{frame}% +} + +% -- A full-bleed dark panel with the given body centred in a minipage of the +% given width. +\NewDocumentCommand \trantor@panel { m +m } {% + \global\trantorPlainPagetrue + \begin{frame}{}% + \thispagestyle{plain}% + \noindent + \begin{tikzpicture} + \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}% + \end{frame}% +} + +% -- 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. +\NewDocumentCommand \trantor@divider { +m } {% + \begin{frame}{}% + \thispagestyle{plain}% + \vfill + \centering + \begin{minipage}{0.52\linewidth}% + \raggedright + {\headerfont\Large\bfseries\trantorSectionTitle\par}% + \vspace{0.4em}% + \trantorProgressBar{\linewidth}% + #1% + \end{minipage}% + \par + \vfill + \end{frame}% +} + +\NewDocumentCommand \sectionpage { } { \trantor@divider{} } + +% The subsection divider is the same, naming the subsection under the bar. +\NewDocumentCommand \subsectionpage { } {% + \trantor@divider{% + \par\vspace{0.8em}% + {\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