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