% 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[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