%% %% Copyright (C) 2022-2024 by Jinwen XU %% ------------------------------------ %% %% This file may be distributed and/or modified under the conditions of %% the LaTeX Project Public License, either version 1.3c of this license %% or (at your option) any later version. The latest version of this %% license is in: %% %% http://www.latex-project.org/lppl.txt %% \NeedsTeXFormat{LaTeX2e}[2022-06-01] \ProvidesExplPackage {multifootnote} {2024/03/24} {} {Multiple numbers for the same footnote} \msg_new:nnn { multifootnote } { non-compatible-package } { "multifootnote" ~ is ~ not ~ compatible ~ with ~ the ~ package ~ "#1". } \hook_gput_code:nnn { begindocument/before } { multifootnote } { \@ifpackageloaded { footnotebackref } { \msg_warning:nnn { multifootnote } { non-compatible-package } { footnotebackref } } {} } \dim_new:N \l_multifootnote_space_after_comma_dim \dim_set:Nn \l_multifootnote_space_after_comma_dim { 0.2ex } \bool_new:N \l__multifootnote_left_indent_bool \bool_set_false:N \l__multifootnote_left_indent_bool \dim_new:N \l__multifootnote_left_indent_dim \keys_define:nn { multifootnote } { , backref .bool_set:N = \l__multifootnote_backref_bool , backref .initial:n = { false } , left-align .bool_set:N = \l__multifootnote_left_align_bool , left-align .initial:n = { false } , left~align .bool_set:N = \l__multifootnote_left_align_bool , left align .bool_set:N = \l__multifootnote_left_align_bool , left-indent .code:n = { \bool_set_true:N \l__multifootnote_left_indent_bool \dim_set:Nn \l__multifootnote_left_indent_dim { #1 } } , left-indent .default:n = { 1.5em } , left~indent .code:n = { \bool_set_true:N \l__multifootnote_left_indent_bool \dim_set:Nn \l__multifootnote_left_indent_dim { #1 } } , left~indent .default:n = { 1.5em } , left indent .code:n = { \bool_set_true:N \l__multifootnote_left_indent_bool \dim_set:Nn \l__multifootnote_left_indent_dim { #1 } } , left indent .default:n = { 1.5em } , manual .bool_set:N = \l__multifootnote_manual_mode_bool , manual .initial:n = { false } , manual-mode .bool_set:N = \l__multifootnote_manual_mode_bool , manual~mode .bool_set:N = \l__multifootnote_manual_mode_bool , manual mode .bool_set:N = \l__multifootnote_manual_mode_bool , unknown .code:n = {} } \ProcessKeyOptions [ multifootnote ] \bool_if:NT \l__multifootnote_backref_bool { \RequirePackage { hyperref } } \RequirePackage { refcount } \cs_new_protected:Nn \multifootnote_make_footnote_left_align:n { \renewcommand { \@makefntext } [1] { \skip_horizontal:n { #1 } \tl_if_blank:eF { \text_expand:n { \@thefnmark } } { \@makefnmark \nobreakspace } ##1 } } \bool_if:NT \l__multifootnote_left_align_bool { \multifootnote_make_footnote_left_align:n { 0pt } } \bool_if:NT \l__multifootnote_left_indent_bool { \multifootnote_make_footnote_left_align:n { \l__multifootnote_left_indent_dim } } \cs_generate_variant:Nn \iow_now:Nn { ce } \cs_generate_variant:Nn \clist_put_right:Nn { ce } \tl_new:N \l__multifootnote_tmp_tl \seq_new:N \l__multifootnote_tmp_seq \clist_new:N \l__multifootnote_tmp_clist % get the total page number \hook_gput_code:nnn { enddocument } { multifootnote } { \label{multifootnote_last-page} } \int_new:N \g_multifootnote_total_page_int \hook_gput_code:nnn { begindocument } { multifootnote } { \int_gset:Nn \g_multifootnote_total_page_int { \getpagerefnumber{multifootnote_last-page} } } % First approach \NewDocumentCommand \multifootnotemarkmanual { O{} } { \footnotemark \group_begin: \tl_if_blank:nF { #1 } { \addtocounter { footnote } { -1 } \refstepcounter { footnote } \label { multifootnote-mark-#1 } \@ifpackageloaded { hyperref } { \cs_gset_eq:NN \@currentHref \Hy@footnote@currentHref } {} \label { multifootnote-#1 } } \group_end: } \NewCommandCopy \footnotenumbermanual \multifootnotemarkmanual \NewDocumentCommand \multifootnotetextmanual { O{} m } { \group_begin: \tl_if_blank:nTF { #1 } { \footnotetext { #2 } } { \clist_clear:N \l_tmpa_clist \clist_clear:N \l_tmpb_clist \clist_map_inline:nn { #1 } { \@ifpackageloaded { hyperref } { \bool_if:NTF \l__multifootnote_backref_bool { \clist_put_right:Nn \l_tmpa_clist { \hyperref[multifootnote-mark-##1]{ \ref*{ multifootnote-##1 } } } } { \clist_put_right:Nn \l_tmpa_clist { \ref*{ multifootnote-##1 } } } \clist_put_right:Nn \l_tmpb_clist { \Hy@raisedlink { \hypertarget { \getrefbykeydefault{multifootnote-##1}{anchor}{Doc-Start} } {} } } } { \clist_put_right:Nn \l_tmpa_clist { \ref { multifootnote-##1 } } } } \def \thefootnote { \clist_use:Nn \l_tmpa_clist { , \skip_horizontal:n { \l_multifootnote_space_after_comma_dim } } } \@ifpackageloaded { hyperref } { \xdef \Hy@footnote@currentHref { x\Hy@footnote@currentHref } } {} \footnotetext { \clist_use:Nn \l_tmpb_clist {} \ignorespaces #2 } } \group_end: } \seq_new:N \g__multifootnote_mark_internal_seq \cs_new:Nn \__multifootnote_process_per_page_mark:nnnN % #1 = labels % #2 = footnote text % #3 = actual page number % #4 = a macro to store the result { \tl_gclear:N #4 \clist_clear:N \l_tmpa_clist \clist_map_inline:nn { #1 } { % only keep those labels that appear on the current page \str_if_eq:eeT { #3 } { \getpagerefnumber{multifootnote-mark-##1} } { \clist_put_right:Nn \l_tmpa_clist { ##1 } } } \clist_if_empty:NF \l_tmpa_clist { \tl_gput_right:No #4 { \exp_after:wN { \l_tmpa_clist } } \tl_gput_right:Nn #4 { { #2 } } } } \hook_gput_code:nnn { begindocument } { multifootnote/mark } { \int_step_inline:nn { \int_eval:n { \g_multifootnote_total_page_int } } { % generate the per page mark-footnotetext sequences % \tl_show:n { g__multifootnote_mark_internal_page_ #1 _seq } \seq_new:c { g__multifootnote_mark_internal_page_ #1 _seq } \seq_new:c { g__multifootnote_mark_internal_page_ #1 _recorded_seq } % keep only those labels appeared on the current page \seq_clear:N \l__multifootnote_tmp_seq \seq_map_inline:cn { g__multifootnote_mark_internal_seq } { % \tl_show:n { TEST:~##1 } \__multifootnote_process_per_page_mark:nnnN ##1 { #1 } \l__multifootnote_tmp_tl \tl_if_empty:NF \l__multifootnote_tmp_tl { \seq_put_right:No \l__multifootnote_tmp_seq { \l__multifootnote_tmp_tl } } } \seq_gset_eq:cN { g__multifootnote_mark_internal_page_ #1 _seq } \l__multifootnote_tmp_seq % \seq_show:c { g__multifootnote_mark_internal_page_ #1 _seq } } } \NewDocumentCommand \multifootnotemark { O{} } { % print the footnote mark \footnotemark \group_begin: \tl_if_blank:nF { #1 } { \addtocounter { footnote } { -1 } \refstepcounter { footnote } \label { multifootnote-mark-#1 } \@ifpackageloaded { hyperref } { \cs_gset_eq:NN \@currentHref \Hy@footnote@currentHref } {} \label { multifootnote-#1 } } \group_end: % print the corresponding footnote text \tl_if_blank:nF { #1 } { \tl_set:Ne \l__multifootnote_tmp_tl { \getpagerefnumber{multifootnote-mark-#1} } % \tl_show:N \l__multifootnote_tmp_tl % \seq_show:c { g__multifootnote_mark_internal_page_ \l__multifootnote_tmp_tl _seq } \seq_map_inline:cn { g__multifootnote_mark_internal_page_ \l__multifootnote_tmp_tl _seq } { % \tl_show:n { TEST:~ { #1 } ##1 } \seq_if_in:cnF { g__multifootnote_mark_internal_page_ \l__multifootnote_tmp_tl _recorded_seq } { ##1 } { % \tl_show:n { TEST:~ { #1 } ##1 } \__multifootnote_print_footnotetext:nnnnn { #1 } ##1 { % record the current item \seq_gput_right:cn { g__multifootnote_mark_internal_page_ \l__multifootnote_tmp_tl _recorded_seq } { ##1 } % \seq_show:c { g__multifootnote_mark_internal_page_ \l__multifootnote_tmp_tl _recorded_seq } } {} {} {} % the two extra empty groups are added for safety } } } } \cs_new:Nn \__multifootnote_print_footnotetext:nnnnn % #1 = a given label % #2 = labels % #3 = footnote text % #4 = code when the footnote text is indeed printed % #5 = fallback code when the footnote text does not get printed { % \tl_show:n { TEST:~ { #1 } { #2 } { #3 } } % \clist_if_in:nnTF { #2 } { #1 } { \tl_show:n { TEST:~ {#1}~is~in~{#2}. } } { \tl_show:n { TEST:~ {#1}~is~NOT~in~{#2}. } } \clist_if_in:nnTF { #2 } { #1 } { \clist_clear:N \l_tmpa_clist \clist_clear:N \l_tmpb_clist % \clist_show:n { #2 } \clist_map_inline:nn { #2 } { \@ifpackageloaded { hyperref } { \bool_if:NTF \l__multifootnote_backref_bool { \clist_put_right:Nn \l_tmpa_clist { \hyperref[multifootnote-mark-##1]{ \ref*{ multifootnote-##1 } } } } { \clist_put_right:Nn \l_tmpa_clist { \ref*{ multifootnote-##1 } } } \clist_put_right:Nn \l_tmpb_clist { \Hy@raisedlink { \hypertarget { \getrefbykeydefault{multifootnote-##1}{anchor}{Doc-Start} } {} } } } { \clist_put_right:Nn \l_tmpa_clist { \ref { multifootnote-##1 } } } } % \clist_show:N \l_tmpa_clist \group_begin: \def \thefootnote { \clist_use:Nn \l_tmpa_clist { , \skip_horizontal:n { \l_multifootnote_space_after_comma_dim } } } \@ifpackageloaded { hyperref } { \xdef \Hy@footnote@currentHref { x\Hy@footnote@currentHref } } {} \footnotetext { \clist_use:Nn \l_tmpb_clist {} \ignorespaces #3 } \group_end: #4 } { #5 } } \NewCommandCopy \footnotenumber \multifootnotemark \NewDocumentCommand \multifootnotetext { O{} m } { \tl_if_blank:nTF { #1 } { \footnotetext { #2 } } { \iow_now:cn { @auxout } { \@multifootnote@add@to@mark@text@list { #1 } { #2 } } } } \NewDocumentCommand \@multifootnote@add@to@mark@text@list { m m } { \seq_gput_right:Nn \g__multifootnote_mark_internal_seq { { #1 } { #2 } } } % Second approach \int_new:N \g__multifootnote_tag_footnotemark_counter_int \NewDocumentCommand \multifootnotetagmanual { m } { \footnotemark \@ifpackageloaded { hyperref } { \int_gdecr:N \g__multifootnote_tag_footnotemark_counter_int \addtocounter { footnote } { -1 } \refstepcounter { footnote } \label { multifootnote-tag-mark- \int_eval:n { \g__multifootnote_tag_footnotemark_counter_int } } \cs_gset_eq:NN \@currentHref \Hy@footnote@currentHref \label { multifootnote-tag-footnote- \int_eval:n { \g__multifootnote_tag_footnotemark_counter_int } } \clist_map_inline:nn { #1 } { % generate hypertargets for footnote marks so that they can jump correctly to the corresponding footnotes \clist_if_exist:cF { multifootnote-tag-hypertarget- ##1 } { \clist_new:c { multifootnote-tag-hypertarget- ##1 } } \clist_put_right:ce { multifootnote-tag-hypertarget- ##1 } { \exp_not:N \Hy@raisedlink { \exp_not:N \hypertarget { \exp_not:N \getrefbykeydefault{ multifootnote-tag-footnote- \int_eval:n { \g__multifootnote_tag_footnotemark_counter_int } }{anchor}{Doc-Start} } {} } } } } {} \clist_map_inline:nn { #1 } { % generate list of marks in the footnote \bool_if:NTF \l__multifootnote_backref_bool { \cs_if_exist:cTF { multifootnote-tag-list- ##1 } { \tl_gput_right:ce { multifootnote-tag-list- ##1 } { , \skip_horizontal:n { \l_multifootnote_space_after_comma_dim } \ref { multifootnote-tag-mark- \int_eval:n { \g__multifootnote_tag_footnotemark_counter_int } } } } { \cs_gset:cpx { multifootnote-tag-list- ##1 } { \ref { multifootnote-tag-mark- \int_eval:n { \g__multifootnote_tag_footnotemark_counter_int } } } } } { \cs_if_exist:cTF { multifootnote-tag-list- ##1 } { \tl_gput_right:ce { multifootnote-tag-list- ##1 } { , \skip_horizontal:n { \l_multifootnote_space_after_comma_dim } \thefootnote } } { \cs_gset:cpx { multifootnote-tag-list- ##1 } { \thefootnote } } } } } \NewCommandCopy \footnotetagmanual \multifootnotetagmanual \NewDocumentCommand \multifootnotetagtextmanual { O{} m } { \group_begin: \tl_if_blank:nTF { #1 } { \def \thefootnote { } } { \def \thefootnote { \use:c { multifootnote-tag-list- #1 } } } \@ifpackageloaded { hyperref } { \xdef \Hy@footnote@currentHref { x\Hy@footnote@currentHref } \footnotetext { \clist_use:cn { multifootnote-tag-hypertarget- #1 } {} \ignorespaces #2 } } { \footnotetext { #2 } } \group_end: } \prop_new:N \g__multifootnote_tag_internal_prop \NewDocumentCommand \multifootnotetag { m } { \footnotemark \int_gdecr:N \g__multifootnote_tag_footnotemark_counter_int \addtocounter { footnote } { -1 } \refstepcounter { footnote } \label { multifootnote-tag-mark- \int_eval:n { \g__multifootnote_tag_footnotemark_counter_int } } % add the current label number to the clist of the given tag on the current page \clist_map_inline:nn { #1 } { \iow_now:ce { @auxout } { \@multifootnote@add@to@tag@label@list { ##1 } { \int_eval:n { \g__multifootnote_tag_footnotemark_counter_int } } { \getpagerefnumber { multifootnote-tag-mark- \int_eval:n { \g__multifootnote_tag_footnotemark_counter_int } } } } } \@ifpackageloaded { hyperref } { \cs_gset_eq:NN \@currentHref \Hy@footnote@currentHref \label { multifootnote-tag-footnote- \int_eval:n { \g__multifootnote_tag_footnotemark_counter_int } } } {} \clist_map_inline:nn { #1 } { \clist_if_exist:cF { g__multifootnote_tag_page_ \getpagerefnumber { multifootnote-tag-mark- \int_eval:n { \g__multifootnote_tag_footnotemark_counter_int } } _recorded_clist } { \clist_new:c { g__multifootnote_tag_page_ \getpagerefnumber { multifootnote-tag-mark- \int_eval:n { \g__multifootnote_tag_footnotemark_counter_int } } _recorded_clist } } \clist_if_in:cnF { g__multifootnote_tag_page_ \getpagerefnumber { multifootnote-tag-mark- \int_eval:n { \g__multifootnote_tag_footnotemark_counter_int } } _recorded_clist } { ##1 } { \clist_gput_right:cn { g__multifootnote_tag_page_ \getpagerefnumber { multifootnote-tag-mark- \int_eval:n { \g__multifootnote_tag_footnotemark_counter_int } } _recorded_clist } { ##1 } \group_begin: % \clist_show:c { g__multifootnote_tag_ ##1 _page_ \getpagerefnumber { multifootnote-tag-mark- \int_eval:n { \g__multifootnote_tag_footnotemark_counter_int } } _clist } \clist_if_exist:cT { g__multifootnote_tag_ ##1 _page_ \getpagerefnumber { multifootnote-tag-mark- \int_eval:n { \g__multifootnote_tag_footnotemark_counter_int } } _clist } { % skip the following code on the first run \clist_clear:N \l__multifootnote_tmp_clist \clist_map_inline:cn { g__multifootnote_tag_ ##1 _page_ \getpagerefnumber { multifootnote-tag-mark- \int_eval:n { \g__multifootnote_tag_footnotemark_counter_int } } _clist } { % generate list of marks in the footnote \clist_put_right:Nn \l__multifootnote_tmp_clist { % \tl_show:n { ####1 } \bool_if:NTF \l__multifootnote_backref_bool { \ref { multifootnote-tag-mark- ####1 } } { \@ifpackageloaded { hyperref } { \ref*{ multifootnote-tag-mark- ####1 } } { \ref { multifootnote-tag-mark- ####1 } } } } \@ifpackageloaded { hyperref } { % generate hypertargets for footnote marks so that they can jump correctly to the corresponding footnotes \clist_if_exist:cF { multifootnote-tag-hypertarget- ##1 -page- \getpagerefnumber { multifootnote-tag-mark- \int_eval:n { \g__multifootnote_tag_footnotemark_counter_int } } } { \clist_new:c { multifootnote-tag-hypertarget- ##1 -page- \getpagerefnumber { multifootnote-tag-mark- \int_eval:n { \g__multifootnote_tag_footnotemark_counter_int } } } } \clist_put_right:ce { multifootnote-tag-hypertarget- ##1 -page- \getpagerefnumber { multifootnote-tag-mark- \int_eval:n { \g__multifootnote_tag_footnotemark_counter_int } } } { \exp_not:N \Hy@raisedlink { \exp_not:N \hypertarget { \exp_not:N \getrefbykeydefault{ multifootnote-tag-footnote- ####1 }{anchor}{Doc-Start} } {} } } } {} } % \clist_show:N \l__multifootnote_tmp_clist \def \thefootnote { \clist_use:Nn \l__multifootnote_tmp_clist { , \skip_horizontal:n { \l_multifootnote_space_after_comma_dim } } } \@ifpackageloaded { hyperref } { \xdef \Hy@footnote@currentHref { x\Hy@footnote@currentHref } \footnotetext { \clist_use:cn { multifootnote-tag-hypertarget- ##1 -page- \getpagerefnumber { multifootnote-tag-mark- \int_eval:n { \g__multifootnote_tag_footnotemark_counter_int } } } {} \ignorespaces \prop_item:Nn \g__multifootnote_tag_internal_prop { ##1 } } } { \footnotetext { \prop_item:Nn \g__multifootnote_tag_internal_prop { ##1 } } } } \group_end: } } } \NewCommandCopy \footnotetag \multifootnotetag \NewDocumentCommand \@multifootnote@add@to@tag@label@list { m m m } % #1 = tag % #2 = label number % #3 = page number { \clist_if_exist:cF { g__multifootnote_tag_ #1 _page_ #3 _clist } { \clist_new:c { g__multifootnote_tag_ #1 _page_ #3 _clist } } \clist_gput_right:cn { g__multifootnote_tag_ #1 _page_ #3 _clist } { #2 } % \clist_show:c { g__multifootnote_tag_ #1 _page_ #3 _clist } } \NewDocumentCommand \multifootnotetagtext { O{} m } { \tl_if_blank:nTF { #1 } { \footnotetext { #2 } } { \iow_now:cn { @auxout } { \@multifootnote@add@to@tag@text@list { #1 } { #2 } } } } \NewDocumentCommand \@multifootnote@add@to@tag@text@list { m m } { \prop_gput:Nnn \g__multifootnote_tag_internal_prop { #1 } { #2 } } % The combined interface for producing footnote text \NewDocumentCommand \multifootnotemanual { O{} m } { \str_if_in:nnTF { #1 } { , } { \multifootnotetextmanual [ #1 ] { #2 } } { \multifootnotetagtextmanual [ #1 ] { #2 } } } \NewDocumentCommand \multifootnote { O{} m } { \str_if_in:nnTF { #1 } { , } { \multifootnotetext [ #1 ] { #2 } } { \multifootnotetagtext [ #1 ] { #2 } } } \bool_if:NT \l__multifootnote_manual_mode_bool { \RenewCommandCopy \multifootnotemark \multifootnotemarkmanual \RenewCommandCopy \multifootnotetext \multifootnotetextmanual \RenewCommandCopy \multifootnotetag \multifootnotetagmanual \RenewCommandCopy \multifootnotetagtext \multifootnotetagtextmanual \RenewCommandCopy \multifootnote \multifootnotemanual \RenewCommandCopy \footnotenumber \footnotenumbermanual \RenewCommandCopy \footnotetag \footnotetagmanual } \endinput %% %% End of file `multifootnote.sty'.