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:
@@ -44,6 +44,7 @@ class Page:
|
|||||||
self.section = ""
|
self.section = ""
|
||||||
self.subsection = ""
|
self.subsection = ""
|
||||||
self.notes = []
|
self.notes = []
|
||||||
|
self.items = []
|
||||||
|
|
||||||
|
|
||||||
def parse_notes_file(path):
|
def parse_notes_file(path):
|
||||||
@@ -77,9 +78,10 @@ def parse_notes_file(path):
|
|||||||
i += 1
|
i += 1
|
||||||
continue
|
continue
|
||||||
|
|
||||||
match = re.match(r"^NOTE FRAME (\d+)\|SPEC (.*)$", line)
|
match = re.match(r"^NOTE FRAME (\d+)\|SPEC (.*)\|KIND (.*)$", line)
|
||||||
if match:
|
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 = []
|
body = []
|
||||||
i += 1
|
i += 1
|
||||||
while i < len(lines) and lines[i].strip() != "ENDNOTE":
|
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()
|
text = " ".join(part.strip() for part in body).strip()
|
||||||
# A note is re-run for every slide of its frame, so the same record
|
# A note is re-run for every slide of its frame, so the same record
|
||||||
# arrives once per slide. Keep the first occurrence only.
|
# arrives once per slide. Keep the first occurrence only.
|
||||||
key = (frame, spec, text)
|
key = (frame, spec, kind, text)
|
||||||
if key not in seen:
|
if key not in seen:
|
||||||
seen.add(key)
|
seen.add(key)
|
||||||
notes.setdefault(frame, []).append((spec, text))
|
notes.setdefault(frame, []).append((spec, kind, text))
|
||||||
continue
|
continue
|
||||||
|
|
||||||
i += 1
|
i += 1
|
||||||
@@ -141,8 +143,11 @@ def attach_notes(pages, notes):
|
|||||||
last_slide[page.frame] = max(last_slide.get(page.frame, 0), page.slide)
|
last_slide[page.frame] = max(last_slide.get(page.frame, 0), page.slide)
|
||||||
|
|
||||||
for page in pages:
|
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]):
|
if spec_matches(spec, page.slide, last_slide[page.frame]):
|
||||||
|
if kind == "item":
|
||||||
|
page.items.append(text)
|
||||||
|
else:
|
||||||
page.notes.append(text)
|
page.notes.append(text)
|
||||||
|
|
||||||
|
|
||||||
@@ -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"\noindent\colorbox{notegrey}{%s}%%" % band)
|
||||||
|
|
||||||
body.append(r"\par\vspace{%.2fbp}%%" % gap)
|
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}"
|
body.append(r"\noindent\hspace{%.2fbp}\begin{minipage}[t]{%.2fbp}"
|
||||||
|
r"\setlength{\parskip}{0.7em}"
|
||||||
% (hmargin, textwidth))
|
% (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:
|
if page.notes:
|
||||||
body.append(r"\begin{itemize}\setlength{\itemsep}{0.6em}"
|
body.append("\n\n".join(
|
||||||
r"\setlength{\leftmargini}{1.2em}")
|
escape_for_context(n) for n in page.notes))
|
||||||
for note in page.notes:
|
if page.items:
|
||||||
body.append(r"\item %s" % escape_for_context(note))
|
body.append(r"\par\begin{enumerate}\setlength{\itemsep}{0pt}"
|
||||||
body.append(r"\end{itemize}")
|
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}")
|
body.append(r"\end{minipage}")
|
||||||
|
|
||||||
if index != total - 1:
|
if index != total - 1:
|
||||||
|
|||||||
@@ -58,21 +58,25 @@
|
|||||||
{ SUBSECTION ~ \tl_to_str:N \g__talknotes_subsection_tl }
|
{ SUBSECTION ~ \tl_to_str:N \g__talknotes_subsection_tl }
|
||||||
}
|
}
|
||||||
|
|
||||||
% \note[<overlay spec>]{text}
|
% \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
|
% 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
|
% 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.
|
% 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
|
% Records are bracketed by ENDNOTE because a long note may be broken across
|
||||||
% output lines.
|
% output lines.
|
||||||
\NewDocumentCommand \note { d<> +m }
|
\NewDocumentCommand \note { d<> o +m }
|
||||||
{
|
{
|
||||||
\iow_now:Nx \g__talknotes_iow
|
\iow_now:Nx \g__talknotes_iow
|
||||||
{
|
{
|
||||||
NOTE ~ FRAME ~ \int_use:N \g__talk_frame_int |
|
NOTE ~ FRAME ~ \int_use:N \g__talk_frame_int |
|
||||||
SPEC ~ \IfNoValueTF {#1} { - } { \tl_to_str:n {#1} }
|
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 {#2} }
|
\iow_now:Nx \g__talknotes_iow { TEXT ~ \tl_to_str:n {#3} }
|
||||||
\iow_now:Nn \g__talknotes_iow { ENDNOTE }
|
\iow_now:Nn \g__talknotes_iow { ENDNOTE }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user