Merge the beamer-mako presentation template as slides_template
beamer-mako held a presentation template that was ported to the ltx-talk class because beamer cannot produce tagged PDFs. That left the repository misnamed and duplicating machinery cdsc_tex already has. The ltx-talk branch was master plus fifteen commits, so this carries the whole repository. Paths are rewritten under slides_template/; COPYING is dropped in favour of the one here, which is the same licence.
This commit is contained in:
27
slides_template/.gitignore
vendored
Normal file
27
slides_template/.gitignore
vendored
Normal file
@@ -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
|
||||||
38
slides_template/Makefile
Normal file
38
slides_template/Makefile
Normal file
@@ -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
|
||||||
109
slides_template/README
Normal file
109
slides_template/README
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
================================
|
||||||
|
=== Mako's Beamer Templates ====
|
||||||
|
================================
|
||||||
|
|
||||||
|
Copyright (c) 2013-2026 Benjamin Mako Hill <mako@atdot.cc> / <makohill@uw.edu>
|
||||||
|
|
||||||
|
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 <makohill@uw.edu> [work]
|
||||||
|
Benjamin Mako Hill <mako@atdot.cc> [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 <directory>
|
||||||
|
|
||||||
|
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/
|
||||||
97
slides_template/README.ltx-talk.md
Normal file
97
slides_template/README.ltx-talk.md
Normal file
@@ -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.
|
||||||
123
slides_template/example.tex
Normal file
123
slides_template/example.tex
Normal file
@@ -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}
|
||||||
75
slides_template/figures/CDCS_logo.svg
Normal file
75
slides_template/figures/CDCS_logo.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 19 KiB |
71
slides_template/figures/CDSC_logo-notext.svg
Normal file
71
slides_template/figures/CDSC_logo-notext.svg
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
id="svg2"
|
||||||
|
version="1.1"
|
||||||
|
inkscape:version="0.91 r13725"
|
||||||
|
xml:space="preserve"
|
||||||
|
width="1172.3047"
|
||||||
|
height="783.17554"
|
||||||
|
viewBox="0 0 1172.3047 783.17554"
|
||||||
|
sodipodi:docname="CDSC_logo-notext.svg"><metadata
|
||||||
|
id="metadata8"><rdf:RDF><cc:Work
|
||||||
|
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
|
||||||
|
id="defs6" /><sodipodi:namedview
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1"
|
||||||
|
objecttolerance="10"
|
||||||
|
gridtolerance="10"
|
||||||
|
guidetolerance="10"
|
||||||
|
inkscape:pageopacity="0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:window-width="1276"
|
||||||
|
inkscape:window-height="1417"
|
||||||
|
id="namedview4"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:zoom="1.3513094"
|
||||||
|
inkscape:cx="466.63864"
|
||||||
|
inkscape:cy="391.58777"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="19"
|
||||||
|
inkscape:window-maximized="0"
|
||||||
|
inkscape:current-layer="g10" /><g
|
||||||
|
id="g10"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
inkscape:label="logo-nowords"
|
||||||
|
transform="matrix(1.25,0,0,-1.25,0,783.17551)"><g
|
||||||
|
id="g12"><path
|
||||||
|
d="m 264.555,134.212 c 21.789,-5.172 44.433,-9.539 67.789,-13.039 l 0,-117.633 68.058,110.121 c 22.395,-1.621 45.266,-2.476 68.52,-2.476 14.801,0 29.433,0.359 43.898,1.023 l 0,-108.668 70.844,114.633 c 38.504,4.774 75.234,11.922 109.633,21.094 l 0,-118.07 92.68,149.961 0,0.168 c 13.992,6.128 27.339,12.64 39.937,19.531 6.746,3.691 13.285,7.484 19.602,11.379 53.511,35.312 88.832,95.961 88.832,164.871 0,68.914 -35.321,129.566 -88.832,164.875 -6.321,3.894 -12.856,7.691 -19.602,11.379 -12.598,6.89 -25.945,13.402 -39.937,19.531 l 0,0.168 -0.379,0 c -85.61,37.418 -196.067,59.973 -316.676,59.973 -120.61,0 -231.063,-22.555 -316.672,-59.973 l -0.379,0 0,-0.168 c -13.988,-6.129 -27.34,-12.637 -39.933,-19.527 -6.75,-3.692 -13.293,-7.493 -19.614,-11.387 C 38.816,496.665 3.5,436.017 3.5,367.107 3.5,298.201 38.812,237.552 92.324,202.24 c 6.321,-3.895 12.864,-7.696 19.614,-11.387 3.597,-1.969 7.253,-3.906 10.968,-5.809 9.301,-4.769 18.969,-9.34 28.965,-13.718 l 0,-150.129 75.859,122.75"
|
||||||
|
style="fill:none;stroke:#000000;stroke-width:7;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="path14"
|
||||||
|
inkscape:connector-curvature="0" /><path
|
||||||
|
d="m 349.824,341.501 22.086,-93.433 102.012,138.648 35.027,-35.027 81.992,163.992 41.36,-102.418 45.058,13.957 31.301,-69.973 26.992,20.653"
|
||||||
|
style="fill:none;stroke:#463470;stroke-width:7;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="path16"
|
||||||
|
inkscape:connector-curvature="0" /><path
|
||||||
|
d="m 151.871,21.201 197.953,320.3"
|
||||||
|
style="fill:none;stroke:#000000;stroke-width:7;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="path18"
|
||||||
|
inkscape:connector-curvature="0" /><path
|
||||||
|
d="m 228.441,476.017 0,-258.023"
|
||||||
|
style="fill:none;stroke:#b3b3b3;stroke-width:7;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="path20"
|
||||||
|
inkscape:connector-curvature="0" /><path
|
||||||
|
d="m 294.793,217.99 389.691,0"
|
||||||
|
style="fill:none;stroke:#b3b3b3;stroke-width:7;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="path22"
|
||||||
|
inkscape:connector-curvature="0" /><path
|
||||||
|
d="m 228.441,217.99 25.254,0"
|
||||||
|
style="fill:none;stroke:#b3b3b3;stroke-width:7;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="path24"
|
||||||
|
inkscape:connector-curvature="0" /></g></g></svg>
|
||||||
|
After Width: | Height: | Size: 4.3 KiB |
BIN
slides_template/figures/logo-nowords.pdf
Normal file
BIN
slides_template/figures/logo-nowords.pdf
Normal file
Binary file not shown.
BIN
slides_template/figures/logo.pdf
Normal file
BIN
slides_template/figures/logo.pdf
Normal file
Binary file not shown.
BIN
slides_template/figures/nu_logo.pdf
Normal file
BIN
slides_template/figures/nu_logo.pdf
Normal file
Binary file not shown.
BIN
slides_template/figures/uw_logo.pdf
Normal file
BIN
slides_template/figures/uw_logo.pdf
Normal file
Binary file not shown.
19
slides_template/fonts.tex
Normal file
19
slides_template/fonts.tex
Normal file
@@ -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}
|
||||||
BIN
slides_template/fonts/OpenSans/OpenSans-Bold.ttf
Normal file
BIN
slides_template/fonts/OpenSans/OpenSans-Bold.ttf
Normal file
Binary file not shown.
BIN
slides_template/fonts/OpenSans/OpenSans-BoldItalic.ttf
Normal file
BIN
slides_template/fonts/OpenSans/OpenSans-BoldItalic.ttf
Normal file
Binary file not shown.
BIN
slides_template/fonts/OpenSans/OpenSans-Italic.ttf
Normal file
BIN
slides_template/fonts/OpenSans/OpenSans-Italic.ttf
Normal file
Binary file not shown.
BIN
slides_template/fonts/OpenSans/OpenSans-Regular.ttf
Normal file
BIN
slides_template/fonts/OpenSans/OpenSans-Regular.ttf
Normal file
Binary file not shown.
BIN
slides_template/fonts/RobotoMono/RobotoMono-Bold.ttf
Normal file
BIN
slides_template/fonts/RobotoMono/RobotoMono-Bold.ttf
Normal file
Binary file not shown.
BIN
slides_template/fonts/RobotoMono/RobotoMono-BoldItalic.ttf
Normal file
BIN
slides_template/fonts/RobotoMono/RobotoMono-BoldItalic.ttf
Normal file
Binary file not shown.
BIN
slides_template/fonts/RobotoMono/RobotoMono-Italic.ttf
Normal file
BIN
slides_template/fonts/RobotoMono/RobotoMono-Italic.ttf
Normal file
Binary file not shown.
BIN
slides_template/fonts/RobotoMono/RobotoMono-Regular.ttf
Normal file
BIN
slides_template/fonts/RobotoMono/RobotoMono-Regular.ttf
Normal file
Binary file not shown.
384
slides_template/mknotes
Executable file
384
slides_template/mknotes
Executable file
@@ -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"(?<!\\)%", r"\\%", text)
|
||||||
|
|
||||||
|
|
||||||
|
def build_notes_pdf(pages, width, height, thumb_w, thumb_h, workdir,
|
||||||
|
preamble=None, jobname="notes"):
|
||||||
|
"""Typeset one notes page per slide page and return the resulting PDF path.
|
||||||
|
|
||||||
|
The page is made at the size it will actually occupy in the finished sheet,
|
||||||
|
so that it can be dropped in without scaling. Shrinking a slide-sized page
|
||||||
|
into a narrow column would leave the text far too small to read while
|
||||||
|
presenting.
|
||||||
|
|
||||||
|
thumb_w and thumb_h are the size of the slide thumbnail that assembly stamps
|
||||||
|
flush into the top outer corner. The header band is made exactly as tall as
|
||||||
|
the thumbnail and stops exactly where it begins, so the two read as a single
|
||||||
|
band across the head of the page.
|
||||||
|
"""
|
||||||
|
hmargin = width * HMARGIN
|
||||||
|
pad = width * 0.03
|
||||||
|
gap = height * 0.05
|
||||||
|
textwidth = width - hmargin * 2
|
||||||
|
bandwidth = width - thumb_w
|
||||||
|
|
||||||
|
# \input rather than the file's contents, so that anything relative inside
|
||||||
|
# it (font paths, say) resolves against the document directory. LaTeX runs
|
||||||
|
# from there even though its output goes to the working directory.
|
||||||
|
extra = r"\input{%s}" % preamble if preamble else ""
|
||||||
|
|
||||||
|
body = [NOTES_PREAMBLE % {
|
||||||
|
"width": width, "height": height, "preamble": extra,
|
||||||
|
}]
|
||||||
|
|
||||||
|
total = len(pages)
|
||||||
|
for index, page in enumerate(pages):
|
||||||
|
where = page.section or ""
|
||||||
|
if page.subsection:
|
||||||
|
where = f"{where} / {page.subsection}" if where else page.subsection
|
||||||
|
|
||||||
|
# Sans-serif and small, against the roman body below, so the heading
|
||||||
|
# reads as apparatus rather than as part of the notes.
|
||||||
|
heading = [r"\sffamily\small"]
|
||||||
|
if where:
|
||||||
|
heading.append(r"{\bfseries %s\par}" % escape_for_context(where))
|
||||||
|
heading.append(
|
||||||
|
r"{\footnotesize Frame %d, slide %d of %d \quad Page %d of %d\par}"
|
||||||
|
% (page.frame, page.slide,
|
||||||
|
max(p.slide for p in pages if p.frame == page.frame),
|
||||||
|
page.number, total)
|
||||||
|
)
|
||||||
|
|
||||||
|
inner = (r"\hspace{%.2fbp}\begin{minipage}[t]{%.2fbp}\vspace{%.2fbp}%s"
|
||||||
|
r"\end{minipage}"
|
||||||
|
% (pad, bandwidth - pad * 2, pad, "".join(heading)))
|
||||||
|
if thumb_h:
|
||||||
|
band = (r"\begin{minipage}[t][%.2fbp][t]{%.2fbp}%s\end{minipage}"
|
||||||
|
% (thumb_h, bandwidth, inner))
|
||||||
|
else:
|
||||||
|
band = (r"\begin{minipage}[t]{%.2fbp}%s\vspace{%.2fbp}\end{minipage}"
|
||||||
|
% (bandwidth, inner, pad))
|
||||||
|
body.append(r"\noindent\colorbox{notegrey}{%s}%%" % band)
|
||||||
|
|
||||||
|
body.append(r"\par\vspace{%.2fbp}%%" % gap)
|
||||||
|
# Paragraph spacing is set here rather than in the preamble so that it
|
||||||
|
# separates the notes without also loosening the heading band above.
|
||||||
|
body.append(r"\noindent\hspace{%.2fbp}\begin{minipage}[t]{%.2fbp}"
|
||||||
|
r"\setlength{\parskip}{0.7em}"
|
||||||
|
% (hmargin, textwidth))
|
||||||
|
# As in beamer, plain \note commands run together as text and
|
||||||
|
# \note[item] ones become a numbered list printed after it. beamer
|
||||||
|
# concatenates the plain ones with no separator at all; a paragraph
|
||||||
|
# break between them is easier to read and is the one deviation.
|
||||||
|
if page.notes:
|
||||||
|
body.append("\n\n".join(
|
||||||
|
escape_for_context(n) for n in page.notes))
|
||||||
|
if page.items:
|
||||||
|
body.append(r"\par\begin{enumerate}\setlength{\itemsep}{0pt}"
|
||||||
|
r"\setlength{\parskip}{0pt}"
|
||||||
|
r"\setlength{\leftmargini}{1.4em}")
|
||||||
|
for item in page.items:
|
||||||
|
body.append(r"\item %s" % escape_for_context(item))
|
||||||
|
body.append(r"\end{enumerate}")
|
||||||
|
body.append(r"\end{minipage}")
|
||||||
|
|
||||||
|
if index != total - 1:
|
||||||
|
body.append(r"\newpage")
|
||||||
|
|
||||||
|
body.append(r"\end{document}")
|
||||||
|
|
||||||
|
source = os.path.join(workdir, jobname + ".tex")
|
||||||
|
with open(source, "w", encoding="utf-8") as handle:
|
||||||
|
handle.write("\n".join(body) + "\n")
|
||||||
|
|
||||||
|
# Run from the document directory so relative paths in the preamble resolve,
|
||||||
|
# but send the output elsewhere so the build leaves nothing behind.
|
||||||
|
result = subprocess.run(
|
||||||
|
["lualatex", "-interaction=nonstopmode", "-halt-on-error",
|
||||||
|
"-output-directory=" + workdir, source],
|
||||||
|
stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
|
||||||
|
)
|
||||||
|
output = os.path.join(workdir, jobname + ".pdf")
|
||||||
|
if result.returncode != 0 or not os.path.exists(output):
|
||||||
|
sys.stderr.write(result.stdout.decode("utf-8", "replace")[-3000:])
|
||||||
|
raise SystemExit("mknotes: failed to typeset the notes pages")
|
||||||
|
return output
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------- assembly
|
||||||
|
|
||||||
|
def place(target, source, scale, x, y):
|
||||||
|
"""Draw source onto target, scaled, with its lower-left corner at (x, y)."""
|
||||||
|
target.merge_transformed_page(
|
||||||
|
source, Transformation().scale(scale).translate(x, y)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def assemble_notes(slides, notes_pdf, width, height, thumb_scale):
|
||||||
|
"""Double-width pages: slide left, thumbnail and notes right."""
|
||||||
|
writer = PdfWriter()
|
||||||
|
for index, note_page in enumerate(notes_pdf.pages):
|
||||||
|
sheet = PageObject.create_blank_page(width=width * 2, height=height)
|
||||||
|
place(sheet, slides.pages[index], 1.0, 0, 0)
|
||||||
|
place(sheet, note_page, 1.0, width, 0)
|
||||||
|
if thumb_scale:
|
||||||
|
# Flush into the top outer corner of the notes half.
|
||||||
|
tw, th = width * thumb_scale, height * thumb_scale
|
||||||
|
place(sheet, slides.pages[index], thumb_scale,
|
||||||
|
width * 2 - tw, height - th)
|
||||||
|
writer.add_page(sheet)
|
||||||
|
return writer
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------- entry point
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description="Assemble a presenter PDF from slides and captured notes.")
|
||||||
|
parser.add_argument("jobname",
|
||||||
|
help="base name of the build, e.g. 'example'")
|
||||||
|
parser.add_argument("--output",
|
||||||
|
help="output file (default: <jobname>-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()
|
||||||
43
slides_template/new_beamer_presentation
Executable file
43
slides_template/new_beamer_presentation
Executable file
@@ -0,0 +1,43 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# Helper script to export LaTeX templates for new documents
|
||||||
|
# Copyright (c) 2009-2022 Benjamin Mako Hill <mako@atdot.cc>
|
||||||
|
# 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
|
||||||
123
slides_template/pgfplotsthemetol.sty
Normal file
123
slides_template/pgfplotsthemetol.sty
Normal file
@@ -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'.
|
||||||
24
slides_template/resources/vc-git
Executable file
24
slides_template/resources/vc-git
Executable file
@@ -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
|
||||||
89
slides_template/resources/vc-git.awk
Normal file
89
slides_template/resources/vc-git.awk
Normal file
@@ -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}%"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
83
slides_template/talk-notes.sty
Normal file
83
slides_template/talk-notes.sty
Normal file
@@ -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<overlay spec>[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
|
||||||
316
slides_template/trantor.sty
Normal file
316
slides_template/trantor.sty
Normal file
@@ -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:<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.
|
||||||
|
\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{<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
|
||||||
|
|
||||||
|
|
||||||
|
% -- 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
|
||||||
Reference in New Issue
Block a user