Add speaker notes for ltx-talk, captured and assembled outside LaTeX
ltx-talk has no \note command. Its README and website list speaker notes among what the class is for, but the feature is unwritten: the request is open upstream as issue #156, where the maintainer has said notes are wanted but that the interface needs thought first. Nothing has landed as of v0.5.3. So notes are captured rather than typeset. talk-notes.sty defines \note to contribute nothing to the slides and instead record its text, and mknotes reads that afterwards to assemble the presenter PDF: slide on the left, and on the right a grey header band, a thumbnail of the current slide flush into the corner, and the notes below. Previous and next slides are left to dspdfviewer and pdfpc, which show them already. The hard part is that a frame is not a page, since overlays expand one frame into an unpredictable number of slides. Records are therefore written from the shipout hook, the first point at which the frame and slide identity of a page is settled, which makes the mapping exact rather than guessed. Notes honour the usual overlay specifications, so \note<1>{...} appears beside only the first slide of its frame. The assembled PDF is not tagged and is not meant to be shared; only the slides build is. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
79
slides_template/talk-notes.sty
Normal file
79
slides_template/talk-notes.sty
Normal file
@@ -0,0 +1,79 @@
|
||||
% 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>]{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<> +m }
|
||||
{
|
||||
\iow_now:Nx \g__talknotes_iow
|
||||
{
|
||||
NOTE ~ FRAME ~ \int_use:N \g__talk_frame_int |
|
||||
SPEC ~ \IfNoValueTF {#1} { - } { \tl_to_str:n {#1} }
|
||||
}
|
||||
\iow_now:Nx \g__talknotes_iow { TEXT ~ \tl_to_str:n {#2} }
|
||||
\iow_now:Nn \g__talknotes_iow { ENDNOTE }
|
||||
}
|
||||
|
||||
\ExplSyntaxOff
|
||||
Reference in New Issue
Block a user