cgnieder / enotez

Endnotes for LaTeX
5 stars 2 forks source link

[FR] Support for running heads with page numbers of the notes #37

Open gusbrs opened 2 years ago

gusbrs commented 2 years ago

One common editorial / typographical recommendation associated with end notes is to have a running header indicating the pages where the notes were issued, something like "Notes from pages 12--18". I'd like to suggest that support for this be included to enotez, and I elaborate below how this could be done.

At the infrastructure level, if \enotez@note could be stored in the .aux file including the information about the page where the mark comes from, this would make the task much easier, and could be leveraged at the styles/templates to achieve the desired result.

For storing the page number with the endnote, something like the below could be used. Mostly trivial changes, adding the argument where it is due, storing the info in a new property list. The only more subtle care which is taken is to delay the writing of the note to the .aux file, using \iow_shipout_x:Nn instead of \iow_now:Nn in \enotez_save_note:nnnnnnnn to get the proper page numbers.

\cs_set_protected:Npn \enotez_endnote_text:nn #1#2
  {
    \tl_set:Nn \l__enotez_tmpa_tl {chapter}
    \quark_if_no_value:nTF {#1}
      {
        \bool_if:nTF
          {
            \l__enotez_split_bool
            &&
            \tl_if_eq_p:NN \l__enotez_split_tl \l__enotez_tmpa_tl
          }
          {
            \enotez_save_note:xxxxxnnn
              { \int_use:N \g__enotez_endnote_id_int }
              { \theendnote }
              { \int_use:N \g__enotez_list_printed_int }
              { \thechapter }
              { \int_eval:n { \value{chapter} } }
              { \thepage }
              {a}
              {#2}
          }
          {
            \enotez_save_note:xxxxxnnn
              { \int_use:N \g__enotez_endnote_id_int }
              { \theendnote }
              { \int_use:N \g__enotez_list_printed_int }
              { \thesection }
              { \int_eval:n { \value{section} } }
              { \thepage }
              {a}
              {#2}
          }
      }
      {
        \bool_if:nTF
          {
            \l__enotez_split_bool
            &&
            \tl_if_eq_p:NN \l__enotez_split_tl \l__enotez_tmpa_tl
          }
          {
            \enotez_save_note:xnxxxnnn
              { \int_use:N \g__enotez_endnote_id_int }
              {#1}
              { \int_use:N \g__enotez_list_printed_int }
              { \thechapter }
              { \int_eval:n { \value{chapter} } }
              { \thepage }
              {m}
              {#2}
          }
          {
            \enotez_save_note:xnxxxnnn
              { \int_use:N \g__enotez_endnote_id_int }
              {#1}
              { \int_use:N \g__enotez_list_printed_int }
              { \thesection }
              { \int_eval:n { \value{section} } }
              { \thepage }
              {m}
              {#2}
          }
      }
  }

% --------------------------------------------------------------------------
% save the notes to the aux file:
% #1: global id
% #2: mark
% #3: split id
% #4: \thesection / \thechapter
% #5: \value{section} / \value{chapter}
% #6: \thepage
% #7: a/m (automatic/manually)
% #8: text
\cs_set_protected:Npn \enotez_save_note:nnnnnnnn #1#2#3#4#5#6#7#8
  {
    \legacy_if:nT {@filesw}
      { \iow_shipout_x:Nn \@auxout { \enotez@note {#1} {#2} {#3} {#4} {#5} {#6} {#7} {#8} } }
  }
\cs_generate_variant:Nn \enotez_save_note:nnnnnnnn { xxxxx , xnxxx }

\prop_new:N \g__enotez_endnote_page_prop

\cs_set_protected:Npn \enotez@note #1#2#3#4#5#6#7#8
  {
    \tl_gclear_new:c {g__enotez_#1_note_tl}
    \tl_gset:cn {g__enotez_#1_note_tl} {#2}
    \prop_gput:Nnn \g__enotez_endnote_mark_prop    {#1} {#2}
    \prop_gput:Nnn \g__enotez_endnote_split_prop   {#1} {#3}
    \prop_gput:Nnn \g__enotez_endnote_sect_prop    {#1} {#4}
    \prop_gput:Nnn \g__enotez_endnote_sect_id_prop {#1} {#5}
    \prop_gput:Nnn \g__enotez_endnote_page_prop    {#1} {#6}
    \prop_gput:Nnn \g__enotez_endnote_man_prop     {#1} {#7}
    \prop_gput:Nnn \g__enotez_endnote_text_prop    {#1} {#8}
  }

% a try to check if a rerun is necessary:
\AtEndDocument
  {
    \cs_set_protected:Npn \enotez@note #1#2#3#4#5#6#7#8
      {
        \tl_set:Nn \l__enotez_change_tl {#2}
        \tl_if_eq:cNF {g__enotez_#1_note_tl} \l__enotez_change_tl
          {
            \@latex@warning@no@line
              { Endnotes~may~have~changed.~Rerun~to~get~them~right. }
          }
      }
  }

With this in hand, we have access to the page number where the note was issued, and can use it in a template. The below is one way in which this could be done, and it is just a proof of concept, it would definitely require polishing. But I think the basic mechanism is sound, and should suffice for you to judge whether this merits being pursued further, or not at all. The basic idea is simple really, I just take care of storing pages and sectioning in variables for each note, and use the variables to set the headers. And it is not difficult to get an even fancier header, like "Notes for chapters 1--2, pages 7--10".

\tl_new:N \g_enotez_first_page_tl
\tl_new:N \g_enotez_last_page_tl
\tl_new:N \g_enotez_first_sect_tl
\tl_new:N \g_enotez_last_sect_tl
\bool_new:N \g_enotez_newpage_bool
\cs_new_protected:Npn \enotez_header_pages:
  {
    \tl_if_eq:NNTF \g_enotez_first_sect_tl \g_enotez_last_sect_tl
      { Notes~for~chapter~\g_enotez_first_sect_tl,~ }
      { Notes~for~chapters~\g_enotez_first_sect_tl -- \g_enotez_last_sect_tl,~ }
    \tl_if_eq:NNTF \g_enotez_first_page_tl \g_enotez_last_page_tl
      { page~\g_enotez_first_page_tl }
      { pages~\g_enotez_first_page_tl -- \g_enotez_last_page_tl }
  }
\DeclareTemplateInterface {enotez-list} {paragraph-header} {1}
  {
    heading       : function 1 = \enotezlistheading {#1} ,
    format        : tokenlist  = \footnotesize           ,
    number        : function 1 = \enmark{#1}             ,
    number-format : tokenlist  = \normalfont             ,
    notes-sep     : length     = .5\baselineskip
  }

\DeclareTemplateCode {enotez-list} {paragraph-header} {1}
  {
    heading       = \enotez_list_heading:n           ,
    format        = \l__enotez_list_format_tl        ,
    number        = \enotez_list_number:n            ,
    number-format = \l__enotez_list_number_format_tl ,
    notes-sep     = \l__enotez_list_notes_sep_dim
  }
  {
    \group_begin:
    \AssignTemplateKeys
    \enotez_set_totoc:
    \enotez_list_heading:n { \l__enotez_list_name_tl }
    \markboth { \enotez_header_pages: } { \enotez_header_pages: }
    \thispagestyle { headings }
    \bool_gset_true:N \g_enotez_newpage_bool
    \AddToHook { shipout/after } [ enotez/header ]
      { \bool_gset_true:N \g_enotez_newpage_bool }
    \enotez_list_preamble:
    \enotez_build_print_list:nnnn {#1}
      { }
      {
        \par\noindent
        \group_begin:
          \tl_gset:Nx \g_enotez_last_page_tl
            { \prop_item:Nn \g__enotez_endnote_page_prop {##1} }
          \tl_gset:Nx \g_enotez_last_sect_tl
            { \prop_item:Nn \g__enotez_endnote_sect_prop {##1} }
          \bool_if:NT \g_enotez_newpage_bool
            {
              \tl_gset:Nx \g_enotez_first_page_tl
                { \prop_item:Nn \g__enotez_endnote_page_prop {##1} }
              \tl_gset:Nx \g_enotez_first_sect_tl
                { \prop_item:Nn \g__enotez_endnote_sect_prop {##1} }
              \bool_gset_false:N \g_enotez_newpage_bool
            }
          \tl_use:N \l__enotez_list_format_tl
          \hbox_overlap_left:n
            {
              \enotez_list_number:n
                { \enotez_write_list_number:n {##1} }
              \tl_use:N \c_space_tl
            }
          % \cs_set:cpn {@currentlabel}
          %   { \p@endnote \l__enotez_endnote_mark_tl }
          \tl_use:N \g__enotez_endnote_text_tl
          \par
          \dim_compare:nT { \l__enotez_list_notes_sep_dim != 0pt }
            { \addvspace { \l__enotez_list_notes_sep_dim } }
        \group_end:
      }
      { }
    \enotez_list_postamble:
    \AddToHookNext { shipout/after }
      { \RemoveFromHook { shipout/after } [ enotez/header ] }
    \group_end:
  }
\DeclareInstance {enotez-list} {custom} {paragraph-header} { }

Now, as I've said the template is "raw", and at this point is meant just to demonstrate the possibilities. If you like the idea, we can take this further in the manner you prefer. If you'd like me to take things a little further and leave it for you to wrap up, or if you'd like a proper PR, it is all fine. As you see fit.

Putting things together in a MWE (for convenience):

\documentclass{book}

\usepackage{enotez}
% Just to make sure it handles well a complex case.
\setenotez{
  split=chapter,
  reset,
}
\usepackage{lipsum}
\usepackage{hyperref}

\makeatletter
\ExplSyntaxOn
\cs_set_protected:Npn \enotez_endnote_text:nn #1#2
  {
    \tl_set:Nn \l__enotez_tmpa_tl {chapter}
    \quark_if_no_value:nTF {#1}
      {
        \bool_if:nTF
          {
            \l__enotez_split_bool
            &&
            \tl_if_eq_p:NN \l__enotez_split_tl \l__enotez_tmpa_tl
          }
          {
            \enotez_save_note:xxxxxnnn
              { \int_use:N \g__enotez_endnote_id_int }
              { \theendnote }
              { \int_use:N \g__enotez_list_printed_int }
              { \thechapter }
              { \int_eval:n { \value{chapter} } }
              { \thepage }
              {a}
              {#2}
          }
          {
            \enotez_save_note:xxxxxnnn
              { \int_use:N \g__enotez_endnote_id_int }
              { \theendnote }
              { \int_use:N \g__enotez_list_printed_int }
              { \thesection }
              { \int_eval:n { \value{section} } }
              { \thepage }
              {a}
              {#2}
          }
      }
      {
        \bool_if:nTF
          {
            \l__enotez_split_bool
            &&
            \tl_if_eq_p:NN \l__enotez_split_tl \l__enotez_tmpa_tl
          }
          {
            \enotez_save_note:xnxxxnnn
              { \int_use:N \g__enotez_endnote_id_int }
              {#1}
              { \int_use:N \g__enotez_list_printed_int }
              { \thechapter }
              { \int_eval:n { \value{chapter} } }
              { \thepage }
              {m}
              {#2}
          }
          {
            \enotez_save_note:xnxxxnnn
              { \int_use:N \g__enotez_endnote_id_int }
              {#1}
              { \int_use:N \g__enotez_list_printed_int }
              { \thesection }
              { \int_eval:n { \value{section} } }
              { \thepage }
              {m}
              {#2}
          }
      }
  }

% --------------------------------------------------------------------------
% save the notes to the aux file:
% #1: global id
% #2: mark
% #3: split id
% #4: \thesection / \thechapter
% #5: \value{section} / \value{chapter}
% #6: \thepage
% #7: a/m (automatic/manually)
% #8: text
\cs_set_protected:Npn \enotez_save_note:nnnnnnnn #1#2#3#4#5#6#7#8
  {
    \legacy_if:nT {@filesw}
      { \iow_shipout_x:Nn \@auxout { \enotez@note {#1} {#2} {#3} {#4} {#5} {#6} {#7} {#8} } }
  }
\cs_generate_variant:Nn \enotez_save_note:nnnnnnnn { xxxxx , xnxxx }

\prop_new:N \g__enotez_endnote_page_prop

\cs_set_protected:Npn \enotez@note #1#2#3#4#5#6#7#8
  {
    \tl_gclear_new:c {g__enotez_#1_note_tl}
    \tl_gset:cn {g__enotez_#1_note_tl} {#2}
    \prop_gput:Nnn \g__enotez_endnote_mark_prop    {#1} {#2}
    \prop_gput:Nnn \g__enotez_endnote_split_prop   {#1} {#3}
    \prop_gput:Nnn \g__enotez_endnote_sect_prop    {#1} {#4}
    \prop_gput:Nnn \g__enotez_endnote_sect_id_prop {#1} {#5}
    \prop_gput:Nnn \g__enotez_endnote_page_prop    {#1} {#6}
    \prop_gput:Nnn \g__enotez_endnote_man_prop     {#1} {#7}
    \prop_gput:Nnn \g__enotez_endnote_text_prop    {#1} {#8}
  }

% a try to check if a rerun is necessary:
\AtEndDocument
  {
    \cs_set_protected:Npn \enotez@note #1#2#3#4#5#6#7#8
      {
        \tl_set:Nn \l__enotez_change_tl {#2}
        \tl_if_eq:cNF {g__enotez_#1_note_tl} \l__enotez_change_tl
          {
            \@latex@warning@no@line
              { Endnotes~may~have~changed.~Rerun~to~get~them~right. }
          }
      }
  }
\ExplSyntaxOff
\makeatother

\makeatletter
\ExplSyntaxOn
\tl_new:N \g_enotez_first_page_tl
\tl_new:N \g_enotez_last_page_tl
\tl_new:N \g_enotez_first_sect_tl
\tl_new:N \g_enotez_last_sect_tl
\bool_new:N \g_enotez_newpage_bool
\cs_new_protected:Npn \enotez_header_pages:
  {
    \tl_if_eq:NNTF \g_enotez_first_sect_tl \g_enotez_last_sect_tl
      { Notes~for~chapter~\g_enotez_first_sect_tl,~ }
      { Notes~for~chapters~\g_enotez_first_sect_tl -- \g_enotez_last_sect_tl,~ }
    \tl_if_eq:NNTF \g_enotez_first_page_tl \g_enotez_last_page_tl
      { page~\g_enotez_first_page_tl }
      { pages~\g_enotez_first_page_tl -- \g_enotez_last_page_tl }
  }
\DeclareTemplateInterface {enotez-list} {paragraph-header} {1}
  {
    heading       : function 1 = \enotezlistheading {#1} ,
    format        : tokenlist  = \footnotesize           ,
    number        : function 1 = \enmark{#1}             ,
    number-format : tokenlist  = \normalfont             ,
    notes-sep     : length     = .5\baselineskip
  }

\DeclareTemplateCode {enotez-list} {paragraph-header} {1}
  {
    heading       = \enotez_list_heading:n           ,
    format        = \l__enotez_list_format_tl        ,
    number        = \enotez_list_number:n            ,
    number-format = \l__enotez_list_number_format_tl ,
    notes-sep     = \l__enotez_list_notes_sep_dim
  }
  {
    \group_begin:
    \AssignTemplateKeys
    \enotez_set_totoc:
    \enotez_list_heading:n { \l__enotez_list_name_tl }
    \markboth { \enotez_header_pages: } { \enotez_header_pages: }
    \thispagestyle { headings }
    \bool_gset_true:N \g_enotez_newpage_bool
    \AddToHook { shipout/after } [ enotez/header ]
      { \bool_gset_true:N \g_enotez_newpage_bool }
    \enotez_list_preamble:
    \enotez_build_print_list:nnnn {#1}
      { }
      {
        \par\noindent
        \group_begin:
          \tl_gset:Nx \g_enotez_last_page_tl
            { \prop_item:Nn \g__enotez_endnote_page_prop {##1} }
          \tl_gset:Nx \g_enotez_last_sect_tl
            { \prop_item:Nn \g__enotez_endnote_sect_prop {##1} }
          \bool_if:NT \g_enotez_newpage_bool
            {
              \tl_gset:Nx \g_enotez_first_page_tl
                { \prop_item:Nn \g__enotez_endnote_page_prop {##1} }
              \tl_gset:Nx \g_enotez_first_sect_tl
                { \prop_item:Nn \g__enotez_endnote_sect_prop {##1} }
              \bool_gset_false:N \g_enotez_newpage_bool
            }
          \tl_use:N \l__enotez_list_format_tl
          \hbox_overlap_left:n
            {
              \enotez_list_number:n
                { \enotez_write_list_number:n {##1} }
              \tl_use:N \c_space_tl
            }
          % \cs_set:cpn {@currentlabel}
          %   { \p@endnote \l__enotez_endnote_mark_tl }
          \tl_use:N \g__enotez_endnote_text_tl
          \par
          \dim_compare:nT { \l__enotez_list_notes_sep_dim != 0pt }
            { \addvspace { \l__enotez_list_notes_sep_dim } }
        \group_end:
      }
      { }
    \enotez_list_postamble:
    \AddToHookNext { shipout/after }
      { \RemoveFromHook { shipout/after } [ enotez/header ] }
    \group_end:
  }
\DeclareInstance {enotez-list} {custom} {paragraph-header} { }
\ExplSyntaxOff
\makeatother

\begin{document}

\tableofcontents{}

\chapter{Chapter 1}

\lipsum[1-2]\endnote{\lipsum[3]}

\lipsum[1-2]\endnote{\lipsum[3]}

\lipsum[1-2]\endnote{\lipsum[3]}

\lipsum[1-2]\endnote{\lipsum[3]}

\lipsum[1-2]\endnote{\lipsum[3]}

\lipsum[1-2]\endnote{\lipsum[3]}

\lipsum[1-2]\endnote{\lipsum[3]}

\lipsum[1-2]\endnote{\lipsum[3]}

\lipsum[1-2]\endnote{\lipsum[3]}

\lipsum[1-2]\endnote{\lipsum[3]}

\lipsum[1-2]\endnote{\lipsum[3]}

\lipsum[1-2]\endnote{\lipsum[3]}

\lipsum[1-2]\endnote{\lipsum[3]}

\lipsum[1-2]\endnote{\lipsum[3]}

\lipsum[1-2]\endnote{\lipsum[3]}

\lipsum[1-2]\endnote{\lipsum[3]}

\lipsum[1-2]\endnote{\lipsum[3]}

\lipsum[1-2]\endnote{\lipsum[3]}

\lipsum[1-2]\endnote{\lipsum[3]}

\lipsum[1-2]\endnote{\lipsum[3]}

\chapter{Chapter 2}

\lipsum[1-2]\endnote{\lipsum[3]}

\lipsum[1-2]\endnote{\lipsum[3]}

\lipsum[1-2]\endnote{\lipsum[3]}

\lipsum[1-2]\endnote{\lipsum[3]}

\lipsum[1-2]\endnote{\lipsum[3]}

\lipsum[1-2]\endnote{\lipsum[3]}

\lipsum[1-2]\endnote{\lipsum[3]}

\lipsum[1-2]\endnote{\lipsum[3]}

\lipsum[1-2]\endnote{\lipsum[3]}

\lipsum[1-2]\endnote{\lipsum[3]}

\lipsum[1-2]\endnote{\lipsum[3]}

\lipsum[1-2]\endnote{\lipsum[3]}

\lipsum[1-2]\endnote{\lipsum[3]}

\lipsum[1-2]\endnote{\lipsum[3]}

\printendnotes[custom]

\end{document}

Edit: Caught a small flaw, fixed.

troldtog commented 2 years ago

This feature suggestion has been on my "to-do someday" list for a long time. You've implemented much more of it than I have, so I don't have too much to add, but two things come to mind.

gusbrs commented 2 years ago

I was toying with what a good set of options would be for using this feature [...].

I haven't yet put much thought on UI but, as far as I have, I was thinking of a boolean and a format with placeholders, such as split-title={Notes for <name> <ref>}. That would fit the package well, I'd say. Also, I had thought that whatever the interface is, the default format for this should not include the sectioning, but just a plain Notes for pages X-W. Because, whether the sectioning would work well, depends on how people construct the document (think of unnumbered chapters/sections), whereas it would be fair to expect that every page gets a number/folio (shown or not).

If notes are printed in the backmatter as they are in most books, this may produce unexpected results with nested endnotes.

Good point. Indeed the prototype above does not consider nesting, but at some point the case must be handled. Giving a first thought about it, I'd be inclined to include in the heading only the pages of non-nested notes. The reasoning being that handling arbitrary levels of nesting might increase complexity a lot. Second, and most relevant, that the main justification for the page indication on the header is the distance between the mark and the text of the note, but a nested note would be much closer than a non-nested one.

Still, some method would be needed to identify if a note is nested or not. Certainly we can devise something. I'm still just getting acquainted with the package's code, so there might be an obvious way I'm missing, but everything I could think of so far would involve storing one more piece of information with the note.

cgnieder commented 2 years ago

This is a nice idea and should be added in one way or another to enotez. Would you like to make a pull request?

gusbrs commented 2 years ago

Nice, thanks! I'll be glad to prepare a pull request. But there are some questions I'd like to discuss first, particularly the issues raised by @troldtog , so I'd know were to go in those areas.

Regarding user interface. My first thought had been to have a boolean added to the templates to enable/disable the running headers. And an interface similar to that of split-title, with respective "tags", for the format of the headers. Do you think this is a good starting point for it? Do you have any further ideas / suggestions?

Regarding the handling of nested notes. Considering any nested endnotes will occur in a block (split or not) at the end of \printendnotes (as discussed at #38 ), this means that a mixture between non-nested and nested ones in the header would occur at most in one page of the whole \printendnotes. Also, the current prototype implementation would get "technically correct" pages, even if one of them may result in a "very wide range" (to be gentle).

That given, I think the first thing is to decide if we want the added complexity of handling nested notes specially here.

If we do, so far my best idea would be to add one further field to \enotez@note. Either storing a flag for nested/unnested, or the nesting level as an integer. Do you think that this would be a good idea? Do you have any better ones?

Finally, the two questions are actually connected. If handling nested notes specially, the UI in the form of split-title with "tags" may fall short, given we'd have to handle quite a number of different cases. Meaning here this kind of UI may become impractical. In this case, perhaps the running header format should be exposed as a function, for which we could provide some "pre-cooked" ones.

What do you think?

gusbrs commented 2 years ago

In the meantime, I've been trying to make the UI work in the terms of an interface similar to the one of split-title/"tags", and I'm starting to think the number of cases may result impractical regardless of the handling of nested notes. To make things with just a modicum of flexibility, we'd have quite a number of cases and strings. Depending on whether the two values are equal or not, we'd have a singular or plural, this affects the preceding preposition, depending on the language. Double that for sectioning. Etc.

I'm thinking exposing the data (first page, last page, first sect, last sect) in some public variables and let the user specify a function may be a good idea after all. Some predefined ones may be offered. Pros: full-flexibility. Cons: harder for the average user, no localization/translation by default.

Of course, we could also think of some compromise between the two. Expose the data and let an arbitrary function be set. But also provide some localizable default for a simple case, like the standard "Notes for pages X-Y".

cgnieder commented 2 years ago

Before you spend too much time on an UI: in the longer term I plan to get red of xtemplate which will probably mean a large re-write on various sites. This could also mean changing the interface for the split title tags.

In any way in my experience it is much easier to add the UI afterwards when the code in the back works as wanted.

gusbrs commented 2 years ago

OK, that sounds good to me. I'll keep the UI "bare bones", at least provisionally. Meaning I'll add a boolean to enable the headers, and receive a function to typeset them.

And, once we are satisfied things are working in these terms, we can rediscuss further UI elements.

gusbrs commented 2 years ago

I have a preliminary version of things at the branch https://github.com/gusbrs/enotez/tree/header-pages .

It is currently quite similar to the original prototype. In particular, still nothing special is being done there for the case of nested notes, pending discussion on the matter.

Regarding the current state of UI, I had an even better idea than "boolean+function". I did not change the xtemplates options at all. I do provide a couple of (provisional) functions, which can be used directly to set the mark \AtEveryEndnotesList or \AfterNextEndnotesList. Considering our previous discussion about the totoc option, I think this is a good concept for this even in a more finished form.

Btw, one somewhat wild idea. Perhaps we could provide these same functions' bodies as "translations" (I mean the whole thing). I'm not sure how translations would handle this, but if I recall correctly from the time I played with it, it is very careful in storing and retrieving the values with "no manipulation" (as in the n/N expl3 signatures). So it might be feasible. Do you think it is? Or would this be an abuse of translations' facilities? If it is, we might well settle the UI in these terms, I actually think it would be a good idea (if possible).

A test document which can be used with the branch is:

\documentclass{book}

\usepackage{enotez}
\setenotez{
  split=chapter,
  backref=true,
  reset,
  totoc,
}

\AtEveryEndnotesList{%
  \markboth{\EnotezHeaderPages}{\EnotezHeaderPages}%
  \thispagestyle{headings}%
}

\usepackage{lipsum}
\usepackage{hyperref}

\ExplSyntaxOn
\NewDocumentCommand \testchapter {}
  { \prg_replicate:nn { 20 } { \lipsum[1-2]\endnote{\lipsum[3]} } }
\ExplSyntaxOff

\begin{document}

\tableofcontents{}

\chapter{Chapter 1}

\testchapter

\chapter{Chapter 2}

\testchapter

\printendnotes

\end{document}

WDYT?

gusbrs commented 2 years ago

I've just added a commit to the branch which uses the idea I had mentioned previously, of translating the whole body of \EnotezHeaderPages, for the sake of experimentation. It seems to work well, and I like the result. Ideas and suggestions to make this more elegant, are much welcome though.

In so doing, I refrained from including \EnotezHeaderSectsPages, since it would get large by itself, and would have to handle both split=chapter and split=section. But given that the current structure allows the user to pass whatever they wish to \mark..., I don't think this is a problem. All in all, it's a good balance between "easy to use localized default" and "full flexibility available if needed".

With this, I think the current state of things is a good candidate as a basis for a final result. Modulo polishing, adjusting, documentation, etc., of course.

gusbrs commented 2 years ago

A further comment here. I'm deploying this structure in a larger document of mine, and one thing I was in doubt about the infrastructure, proved indeed unreliable. Namely the use of shipout/after hook to reset \g_enotez_newpage_bool. Unfortunately, it is not sufficient to reliably capture the crossing of a page boundary. It is hard to provide an example, but I can catch some errors in a large document.

I'm afraid something like perpage / zref-perpage might be needed for this. But I'm not well acquainted with the subtleties of the output routine. If anyone here is, I'd love to hear your thoughts.

cgnieder commented 2 years ago

The MWE above also sometimes has wrong page numbers, namely the value of \g_enotez_last_page_tl. For example page 25 has Notes for chapter 2, pages 15–18 although the last note on this page is note 11 from page 17.

This particular problem can be solved, I think, by setting \g_enotez_last_page_tl at the end instead of the beginning:

        \group_begin:
          % \tl_gset:Nx \g_enotez_last_page_tl
          %   { \prop_item:Nn \g__enotez_endnote_page_prop {##1} }
          % \tl_gset:Nx \g_enotez_last_sect_tl
          %   { \prop_item:Nn \g__enotez_endnote_sect_prop {##1} }
          \bool_if:NT \g_enotez_newpage_bool
            {
              \tl_gset:Nx \g_enotez_first_page_tl
                { \prop_item:Nn \g__enotez_endnote_page_prop {##1} }
              \tl_gset:Nx \g_enotez_first_sect_tl
                { \prop_item:Nn \g__enotez_endnote_sect_prop {##1} }
              \bool_gset_false:N \g_enotez_newpage_bool
            }
          \tl_use:N \l__enotez_list_format_tl
          \hbox_overlap_left:n
            {
              \enotez_list_number:n
                { \enotez_write_list_number:n {##1} }
              \tl_use:N \c_space_tl
            }
          % \cs_set:cpn {@currentlabel}
          %   { \p@endnote \l__enotez_endnote_mark_tl }
          \tl_use:N \g__enotez_endnote_text_tl
          \par
          \dim_compare:nT { \l__enotez_list_notes_sep_dim != 0pt }
            { \addvspace { \l__enotez_list_notes_sep_dim } }
          \tl_gset:Nx \g_enotez_last_page_tl
            { \prop_item:Nn \g__enotez_endnote_page_prop {##1} }
          \tl_gset:Nx \g_enotez_last_sect_tl
            { \prop_item:Nn \g__enotez_endnote_sect_prop {##1} }
        \group_end:
gusbrs commented 2 years ago

Unfortunately, the problem runs deeper and, by now, I think the original attempt is fundamentally flawed. In my defense, I did check each note on the first attempts. But trying things out I later didn't always, sorry, and indeed the MWE is sufficient for a fail on the last page. But in my document I get errors on the first page too.

This stuff is difficult, and I don't know it well, but as far as I'm getting, TeX will have potentially "consumed" part of the next page when it finally decides the break of the current page. Then when the break is finally decided, that part is kept on something like a buffer or queue and deployed when the next page starts. If this is correct, and if I'm understanding it correctly, It is the same reason why we cannot just use \thepage on your text and expect a correct value.

Consider the page below:

Screenshot from 2022-03-23 09-39-00

It fails to get the two last notes of chapter 3. You can see that immediately, since it did not write "capítulos 3-4" but, as you would have guessed, page 272 is in chapter 4 (its second page). Now notice the end of note 227. There's a 475 there, it's a call to \thepage, and its the wrong number, since the page is 476.

Let's call this an exercise in humility. :shrug:

But I'm experimenting with a different approach now. The idea is to set a "label" of sorts for each end note text, storing \thepage. This would grant us a mapping with correct values from the global id to the page where the note's text gets printed. We already have correct mappings from the global id to the note's mark page and section. With that in hand, we could process this data at the start of \printendnotes and get a mapping from "the page of the endnotes section" to the first and last pages and sections of their corresponding marks. Then, we could use the after\shipout hook and set the variables of interest once per page (as opposed to once per note, as we are currently doing). I've also asked folks at the TeX.SX chat if we could rely on the value of page at that point, and it appears we should be able to (https://chat.stackexchange.com/transcript/message/60708390#60708390). So, with the correct mapping provided by the labels we can set the variables or interest at the correct point. It seems viable, but I'm still trying things out. Let me report back when I got proper results (or not, after all).

gusbrs commented 2 years ago

I think I got something working based on the concept exposed on the previous comment. The only slight difference in structure is that, given that at shipout we have the number of the next page, I had to add 1 to \thepage which, of course, would only work on the lucky case the document was using a plain arabic page. So I created an independent integer tracking the absolute page number for that (similar structure to zref-abspage). All in all, it is somewhat more complex that the original, but not overly so, I'd say.

I ditched the old branch, and started anew at https://github.com/gusbrs/enotez/tree/header-pages-new

I still didn't handle localization there. (Also not nested notes, pending discussion). Still, comments requested.

And, at least got that one right. ;-)

Screenshot from 2022-03-24 11-28-23

Edit: Ah, the same example of https://github.com/cgnieder/enotez/issues/37#issuecomment-1073350976 can be used to test the branch.

gusbrs commented 1 year ago

Hi @cgnieder, I was tidying up some things here and remembered this issue, since I don't usually keep forks around, but I had kept this one given it was the only place where the changes were available. But I think it's been a fair wait, so I'll include the contents of the header-pages-new branch in form of patches, so that the information is not lost. (I had to zip it, because, for some strange reason, GitHub refused the patch files).

This the full diff of the branch:

enotez-header-pages-new.patch.zip

And these are the individual commits:

enotez-header-pages-new-commits.zip

Cheers!