Share the font setup between the slides and the notes
The notes pages were typeset in the default Latin Modern while the slides used OpenSans and RobotoMono, so the two halves of a presenter page did not match. Move the font declarations to fonts.tex, which the slides \input and mknotes passes to the notes document. mknotes uses it by default when it exists, so a deck that copies the two notes files in gets matching fonts without having to remember a flag; --preamble names a different fragment, and a file named there but missing is an error rather than ignored. The notes document now compiles from the document directory with -output-directory rather than running inside the temporary one, so the relative font paths in the fragment resolve. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
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}
|
||||||
@@ -151,6 +151,10 @@ def attach_notes(pages, notes):
|
|||||||
#: Side margin on the generated notes pages, as a fraction of the page.
|
#: Side margin on the generated notes pages, as a fraction of the page.
|
||||||
HMARGIN = 0.06
|
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
|
# 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
|
# 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.
|
# have to be applied per element instead of to the page.
|
||||||
@@ -159,11 +163,13 @@ NOTES_PREAMBLE = r"""\documentclass{article}
|
|||||||
margin=0bp]{geometry}
|
margin=0bp]{geometry}
|
||||||
\usepackage{parskip}
|
\usepackage{parskip}
|
||||||
\usepackage{xcolor}
|
\usepackage{xcolor}
|
||||||
|
\usepackage{fontspec}
|
||||||
\definecolor{notegrey}{RGB}{230,230,230}
|
\definecolor{notegrey}{RGB}{230,230,230}
|
||||||
\pagestyle{empty}
|
\pagestyle{empty}
|
||||||
\setlength{\parindent}{0pt}
|
\setlength{\parindent}{0pt}
|
||||||
\setlength{\fboxsep}{0pt}
|
\setlength{\fboxsep}{0pt}
|
||||||
\setlength{\topskip}{0pt}
|
\setlength{\topskip}{0pt}
|
||||||
|
%(preamble)s
|
||||||
\begin{document}
|
\begin{document}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -179,7 +185,7 @@ def escape_for_context(text):
|
|||||||
|
|
||||||
|
|
||||||
def build_notes_pdf(pages, width, height, thumb_w, thumb_h, workdir,
|
def build_notes_pdf(pages, width, height, thumb_w, thumb_h, workdir,
|
||||||
jobname="notes"):
|
preamble=None, jobname="notes"):
|
||||||
"""Typeset one notes page per slide page and return the resulting PDF path.
|
"""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,
|
The page is made at the size it will actually occupy in the finished sheet,
|
||||||
@@ -198,7 +204,14 @@ def build_notes_pdf(pages, width, height, thumb_w, thumb_h, workdir,
|
|||||||
textwidth = width - hmargin * 2
|
textwidth = width - hmargin * 2
|
||||||
bandwidth = width - thumb_w
|
bandwidth = width - thumb_w
|
||||||
|
|
||||||
body = [NOTES_PREAMBLE % {"width": width, "height": height}]
|
# \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)
|
total = len(pages)
|
||||||
for index, page in enumerate(pages):
|
for index, page in enumerate(pages):
|
||||||
@@ -249,9 +262,12 @@ def build_notes_pdf(pages, width, height, thumb_w, thumb_h, workdir,
|
|||||||
with open(source, "w", encoding="utf-8") as handle:
|
with open(source, "w", encoding="utf-8") as handle:
|
||||||
handle.write("\n".join(body) + "\n")
|
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(
|
result = subprocess.run(
|
||||||
["lualatex", "-interaction=nonstopmode", "-halt-on-error", jobname + ".tex"],
|
["lualatex", "-interaction=nonstopmode", "-halt-on-error",
|
||||||
cwd=workdir, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
|
"-output-directory=" + workdir, source],
|
||||||
|
stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
|
||||||
)
|
)
|
||||||
output = os.path.join(workdir, jobname + ".pdf")
|
output = os.path.join(workdir, jobname + ".pdf")
|
||||||
if result.returncode != 0 or not os.path.exists(output):
|
if result.returncode != 0 or not os.path.exists(output):
|
||||||
@@ -294,6 +310,10 @@ def main():
|
|||||||
help="base name of the build, e.g. 'example'")
|
help="base name of the build, e.g. 'example'")
|
||||||
parser.add_argument("--output",
|
parser.add_argument("--output",
|
||||||
help="output file (default: <jobname>-notes.pdf)")
|
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,
|
parser.add_argument("--thumb-scale", type=float, default=0.29,
|
||||||
metavar="F",
|
metavar="F",
|
||||||
help="slide thumbnail size on the notes half, as a "
|
help="slide thumbnail size on the notes half, as a "
|
||||||
@@ -321,10 +341,19 @@ def main():
|
|||||||
width, height = float(box.width), float(box.height)
|
width, height = float(box.width), float(box.height)
|
||||||
thumb = max(0.0, min(args.thumb_scale, 0.9))
|
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-")
|
workdir = tempfile.mkdtemp(prefix="mknotes-")
|
||||||
try:
|
try:
|
||||||
notes_pdf = PdfReader(build_notes_pdf(
|
notes_pdf = PdfReader(build_notes_pdf(
|
||||||
pages, width, height, width * thumb, height * thumb, workdir))
|
pages, width, height, width * thumb, height * thumb, workdir,
|
||||||
|
preamble=preamble))
|
||||||
writer = assemble_notes(slides, notes_pdf, width, height, thumb)
|
writer = assemble_notes(slides, notes_pdf, width, height, thumb)
|
||||||
|
|
||||||
output = args.output or f"{args.jobname}-notes.pdf"
|
output = args.output or f"{args.jobname}-notes.pdf"
|
||||||
|
|||||||
Reference in New Issue
Block a user