20
0

Follow beamer's model for notes, and lay the page out like it

Notes were rendered as a bullet list, which beamer does not do. Reading
beamerbasenotes.sty: a plain \note appends its text to a running block with
no separator at all, and \note[item] accumulates separately into an
enumerate printed after that block.

Match both. talk-notes.sty takes \note<spec>[item]{text}, as beamer does,
and records which kind it is. The one deviation is a paragraph break
between plain notes instead of running them together, which is easier to
read on a page.

The notes half is laid out like beamer's too: a grey band across the head
carrying the section and the position in the talk, the current slide flush
into the outer corner, and the notes below. The heading is set in the
sans face against the roman body so it reads as apparatus rather than as
part of the notes.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-14 07:21:37 -07:00
parent 27304c853a
commit c779140c01
2 changed files with 35 additions and 15 deletions

View File

@@ -44,6 +44,7 @@ class Page:
self.section = ""
self.subsection = ""
self.notes = []
self.items = []
def parse_notes_file(path):
@@ -77,9 +78,10 @@ def parse_notes_file(path):
i += 1
continue
match = re.match(r"^NOTE FRAME (\d+)\|SPEC (.*)$", line)
match = re.match(r"^NOTE FRAME (\d+)\|SPEC (.*)\|KIND (.*)$", line)
if match:
frame, spec = int(match.group(1)), match.group(2).strip()
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":
@@ -90,10 +92,10 @@ def parse_notes_file(path):
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, text)
key = (frame, spec, kind, text)
if key not in seen:
seen.add(key)
notes.setdefault(frame, []).append((spec, text))
notes.setdefault(frame, []).append((spec, kind, text))
continue
i += 1
@@ -141,9 +143,12 @@ def attach_notes(pages, notes):
last_slide[page.frame] = max(last_slide.get(page.frame, 0), page.slide)
for page in pages:
for spec, text in notes.get(page.frame, []):
for spec, kind, text in notes.get(page.frame, []):
if spec_matches(spec, page.slide, last_slide[page.frame]):
page.notes.append(text)
if kind == "item":
page.items.append(text)
else:
page.notes.append(text)
# ---------------------------------------------------------------- typesetting
@@ -243,14 +248,25 @@ def build_notes_pdf(pages, width, height, thumb_w, thumb_h, workdir,
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(r"\begin{itemize}\setlength{\itemsep}{0.6em}"
r"\setlength{\leftmargini}{1.2em}")
for note in page.notes:
body.append(r"\item %s" % escape_for_context(note))
body.append(r"\end{itemize}")
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: