%% %% tcbpagefootnote.sty %% =================== %% Page footnotes from inside tcolorbox. %% %% Copyright (c) 2026 Munehiro Yamamoto %% %% This package is licensed 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 %% https://www.latex-project.org/lppl.txt %% %% This work has the LPPL maintenance status `maintained'. %% The Current Maintainer of this work is Munehiro Yamamoto. %% %% Background %% tcolorbox typesets the content of a box like a minipage: \footnote %% uses the mpfootnote counter and the footnote text is put at the %% bottom of the box. %% %% What this package does %% The tcolorbox key "page footnote" makes \footnote inside the box %% (title, upper and lower part) behave as in the body text: %% (1) the footnote counter and \thefootnote are used for the mark; %% (2) the footnote text is collected, together with its mark, while %% the box is typeset, and is put into the ordinary footnote %% insertion right after the box ("after pre" of the hooks %% library), i.e. at the bottom of the page where the box is. %% With hyperref, each footnote text keeps the anchor of its own mark. %% %% Limitations %% * A breakable box that is broken across pages puts all its footnote %% texts on the page where the box ends; a warning is issued. %% * "page footnote" cannot be used with "float". %% * Keys that set the "after" code (after, after skip, nobeforeafter, %% ...) must come before "page footnote" in the option list. %% * Vertical writing (tate) is out of the scope of this version. %% %% Supported formats/engines: pdfLaTeX, XeLaTeX, LuaLaTeX, pLaTeX and %% upLaTeX (+ dvipdfmx). %% \RequirePackage{expl3} \ProvidesExplPackage{tcbpagefootnote} {2026/10/25}{1.0}{Page footnotes from inside tcolorbox} \RequirePackage{tcolorbox} \tcbuselibrary{hooks} %% The footnotes collected inside the box(es), output after the box \tl_new:N \g__tcbpagefootnote_notes_tl \tl_new:N \l__tcbpagefootnote_notes_tl %% ----------------------------------------------------------------- %% Messages %% ----------------------------------------------------------------- \msg_new:nnn { tcbpagefootnote } { key-defined } { The~tcolorbox~key~'page~footnote'~is~already~defined.\\ tcbpagefootnote~will~not~redefine~it. } \msg_new:nnn { tcbpagefootnote } { after-overwritten } { The~'after'~code~of~this~box~no~longer~flushes~the~page~footnotes.\\ Put~'page~footnote'~after~'after',~'after~skip',~etc.~in~the~option~list. } \msg_new:nnn { tcbpagefootnote } { float } { The~key~'page~footnote'~cannot~be~used~with~'float'. } \msg_new:nnn { tcbpagefootnote } { broken } { This~box~is~broken~across~pages.\\ Its~footnote~texts~are~put~on~the~page~where~the~box~ends. } %% ----------------------------------------------------------------- %% 1. Inside the box, switch the footnotes back from the minipage %% setting to the page setting %% ----------------------------------------------------------------- \cs_new_protected:Npn \__tcbpagefootnote_restore: { \cs_set:Npn \@mpfn { footnote } \cs_set:Npn \thempfn { \thefootnote } \cs_set_eq:NN \@footnotetext \__tcbpagefootnote_collect:n } %% ----------------------------------------------------------------- %% 2. Collect the footnote text together with its mark %% The mark is saved as the string formatted by \thefootnote: %% \footnotetext[...] is not used since it assigns a number to the %% counter. With hyperref, the anchor of the mark %% (\Hy@footnote@currentHref) is saved as well, since it is %% overwritten by the next mark. %% ----------------------------------------------------------------- \cs_new_protected:Npn \__tcbpagefootnote_collect:n #1 { \tl_gput_right:Ne \g__tcbpagefootnote_notes_tl { \__tcbpagefootnote_output:nnn { \exp_not:o { \@thefnmark } } { \cs_if_exist:NT \Hy@footnote@currentHref { \exp_not:o { \Hy@footnote@currentHref } } } { \exp_not:n {#1} } } } \cs_new_protected:Npn \__tcbpagefootnote_output:nnn #1#2#3 { \tl_gset:Nn \@thefnmark {#1} \cs_if_exist:NT \Hy@footnote@currentHref { \tl_gset:Nn \Hy@footnote@currentHref {#2} } \@footnotetext {#3} } %% ----------------------------------------------------------------- %% 3. Output the collected footnotes and clear the list %% Called from "after pre", so that the footnotes are inserted %% right after the box. The list is cleared before its content is %% used: a box with "page footnote" inside another one collects its %% footnotes again into the list of the outer box. %% ----------------------------------------------------------------- \cs_new_protected:Npn \__tcbpagefootnote_flush: { \tl_set_eq:NN \l__tcbpagefootnote_notes_tl \g__tcbpagefootnote_notes_tl \tl_gclear:N \g__tcbpagefootnote_notes_tl \tl_if_empty:NF \l__tcbpagefootnote_notes_tl { \cs_if_exist:NT \c@tcbbreakpart { \int_compare:nNnT { \c@tcbbreakpart } > { 1 } { \msg_warning:nn { tcbpagefootnote } { broken } } } } \tl_use:N \l__tcbpagefootnote_notes_tl } %% ----------------------------------------------------------------- %% 4. Check the settings under which the footnotes cannot be output %% - a float box does not use \kvtcb@afterbox %% - after, after skip, etc. given later remove the "after pre" code %% The tcbbreakpart counter of the breakable library is reset here: %% a broken box sets it to the number of its parts while it is drawn %% (after its content is typeset), and an unbreakable box does not %% touch it. %% ----------------------------------------------------------------- \cs_new_protected:Npn \__tcbpagefootnote_check: { \tl_if_empty:NF \kvtcb@float { \msg_error:nn { tcbpagefootnote } { float } } \tl_if_in:NnF \kvtcb@afterbox { \__tcbpagefootnote_flush: } { \msg_error:nn { tcbpagefootnote } { after-overwritten } } \cs_if_exist:NT \c@tcbbreakpart { \int_gzero:N \c@tcbbreakpart } } %% ----------------------------------------------------------------- %% 5. The tcolorbox key %% ----------------------------------------------------------------- \pgfkeysifdefined { /tcb/page~footnote/.@cmd } { \msg_error:nn { tcbpagefootnote } { key-defined } } { \tcbset { page~footnote/.style = { before~title~pre = \__tcbpagefootnote_restore: , before~upper~pre = \__tcbpagefootnote_check: \__tcbpagefootnote_restore: , before~lower~pre = \__tcbpagefootnote_restore: , after~pre = \__tcbpagefootnote_flush: , } } } \endinput