summaryrefslogtreecommitdiff
path: root/macros/latex/contrib/updatemarks/tex/updatemarks.sty
diff options
context:
space:
mode:
Diffstat (limited to 'macros/latex/contrib/updatemarks/tex/updatemarks.sty')
-rw-r--r--macros/latex/contrib/updatemarks/tex/updatemarks.sty503
1 files changed, 503 insertions, 0 deletions
diff --git a/macros/latex/contrib/updatemarks/tex/updatemarks.sty b/macros/latex/contrib/updatemarks/tex/updatemarks.sty
new file mode 100644
index 0000000000..ab5acb8aa3
--- /dev/null
+++ b/macros/latex/contrib/updatemarks/tex/updatemarks.sty
@@ -0,0 +1,503 @@
+% updatemarks.sty
+% Copyright 2023 Wenjian Chern.
+%
+% This work may be distributed and/or modified under the
+% conditions of the LaTeX Project Public License, either version 1.3
+% of this license or any later version.
+% The latest version of this license is in
+% http://www.latex-project.org/lppl.txt
+% and version 1.3 or later is part of all distributions of LaTeX
+% version 2005/12/01 or later.
+%
+% This work has the LPPL maintenance status `maintained'.
+%
+% The Current Maintainer of this work is Wenjian Chern.
+%
+% This work consists of the files updatemarks.sty and updatemarks.tex
+%
+\NeedsTeXFormat{LaTeX2e}
+\@ifundefined{ProvidesExplPackage}{\RequirePackage{expl3}}{}
+\@ifundefined{NewDocumentCommand}{\RequirePackage{xparse}}{}
+\ProvidesExplPackage{updatemarks}{2023/09/03}{0.2b}{extract and update marks from box}
+\RequirePackage{etoolbox}
+
+\seq_new:N \g__updatemarks_seq
+\box_new:N \l__updatemarks_r_box
+\cs_new_eq:NN \l__updatemarks_s_box \l_tmpa_box % this box is used for locally split
+\tex_countdef:D \g_updatemarks_max_int 256 \exp_stop_f:
+\tl_new:N \l__updatemarks_tl
+
+
+%% basicly copy from \__mark_update_structure:nn of LaTeX kernel
+\cs_new_protected:Npn \__updatemarks_extract:nnn #1#2#3 % box, map of number, code
+ {
+ \group_begin:
+ \dim_set_eq:NN \tex_splitmaxdepth:D \c_max_dim
+ \int_set_eq:NN \tex_vbadness:D \c_max_int
+ \dim_set_eq:NN \tex_vfuzz:D \c_max_dim
+ \vbox_set_to_ht:Nnw \l__updatemarks_r_box { -.5 \c_max_dim }
+ #1 \tex_unskip:D
+ \box_set_to_last:N \l__updatemarks_r_box
+ \int_compare:nNnT { \tex_lastnodetype:D } < { 0 }
+ {
+ \box_if_vertical:NT \l__updatemarks_r_box
+ {
+ \vbox_set_to_ht:Nnn \l__updatemarks_r_box { -.5 \c_max_dim }
+ { \vbox_unpack:N \l__updatemarks_r_box \tex_kern:D \c_zero_dim }
+ \int_compare:nNnT { \tex_badness:D } > { 0 }
+ { \vbox_unpack:N \l__updatemarks_r_box }
+ }
+ }
+ \tex_kern:D \c_zero_dim
+ \vbox_set_end:
+ \int_compare:nNnTF { \tex_badness:D } > { 0 }
+ {
+ \vbox_set_split_to_ht:NNn \l__updatemarks_s_box \l__updatemarks_r_box \c_max_dim
+ \__updatemarks_extract_split:nn {#2} {#3}
+ }
+ {
+ \msg_error:nn { updatemarks } { infinite-shrinkage }
+ #2
+ {
+ \cs_undefine:N \l_updatemarks_first_tl
+ \cs_undefine:N \l_updatemarks_last_tl
+ #3
+ }
+ }
+ \group_end:
+ }
+\cs_new_protected:Npn \__updatemarks_extract_split:nn #1#2 % map of number, code
+ {
+ #1
+ {
+ \tl_set:No \l_updatemarks_last_tl { \tex_splitbotmarks:D ##1 }
+ \tl_if_empty:NTF \l_updatemarks_last_tl
+ {
+ \cs_undefine:N \l_updatemarks_first_tl
+ \cs_undefine:N \l_updatemarks_last_tl
+ }
+ { \tl_set:No \l_updatemarks_first_tl { \tex_splitfirstmarks:D ##1 } }
+ #2
+ }
+ }
+\cs_new_protected:Npn \__updatemarks_extract_num:n #1 % number
+ {
+ \tl_if_exist:NT \l_updatemarks_last_tl
+ {
+ \tl_if_exist:cF { g__updatemarks_first-#1_tl }
+ { \tl_gset_eq:cN { g__updatemarks_first-#1_tl } \l_updatemarks_first_tl }
+ \tl_gset_eq:cN { g__updatemarks_last-#1_tl } \l_updatemarks_last_tl
+ }
+ }
+\cs_new_protected:Npn \updatemarks_extract:nN #1#2 % box material, seq
+ {
+ \__updatemarks_extract:nnn {#1}
+ { \seq_map_inline:Nn #2 }
+ { \__updatemarks_extract_num:n {##1} }
+ }
+\cs_new_protected:Npn \updatemarks_extract_split:N #1 % seq
+ {
+ \__updatemarks_extract_split:nn
+ { \seq_map_inline:Nn #1 }
+ { \__updatemarks_extract_num:n {##1} }
+ }
+
+
+\cs_new_protected:Npn \updatemarks_save:Nnn #1#2#3 % seq, pos, value
+ {
+ \seq_map_inline:Nn #1
+ { \tl_gset:co { g__updatemarks_#2-##1_tl } { #3 {##1} } }
+ }
+\cs_new_protected:Npn \updatemarks_save_x:Nnn #1#2#3 % seq, pos, value
+ {
+ \seq_map_inline:Nn #1
+ { \tl_gset:cx { g__updatemarks_#2-##1_tl } { #3 {##1} } }
+ }
+\cs_new_protected:Npn \updatemarks_alias:Nnn #1#2#3 % seq, pos1, pos2
+ {
+ \seq_map_inline:Nn #1
+ { \tl_gset_eq:cc { g__updatemarks_#2-##1_tl } { g__updatemarks_#3-##1_tl } }
+ }
+\cs_new_protected:Npn \updatemarks_remove:Nn #1#2 % seq, pos
+ { \seq_map_inline:Nn #1 { \cs_undefine:c { g__updatemarks_#2-##1_tl } } }
+\cs_new:Npn \updatemarks_value:nn #1#2 % pos, number
+ {
+ \tl_if_exist:cTF { g__updatemarks_#1- \int_eval:n {#2} _tl }
+ { \exp_not:v { g__updatemarks_#1- \int_eval:n {#2} _tl } } { }
+ }
+
+
+\cs_new_protected:Npn \updatemarks_update:N #1 % seq
+ {
+ \seq_if_empty:NF #1
+ {
+ \seq_map_inline:Nn #1
+ {
+ \tl_if_exist:cT { g__updatemarks_last-##1_tl }
+ {
+ \tl_if_exist:cT { g__updatemarks_first-##1_tl }
+ { \tex_marks:D ##1 { \exp_not:v { g__updatemarks_first-##1_tl } } }
+ \tex_marks:D ##1 { \exp_not:v { g__updatemarks_last-##1_tl } }
+ }
+ \cs_undefine:c { g__updatemarks_first-##1_tl }
+ \cs_undefine:c { g__updatemarks_last-##1_tl }
+ }
+ \if@nobreak\ifvmode\nobreak\fi\fi
+ }
+ }
+% do not check if it is <= \g_updatemarks_max_int
+\cs_new_protected:Npn \__updatemarks_gput:Nn #1#2
+ {
+ \group_begin:
+ \cs_set_eq:NN \MarkClass \__updatemarks_class_num:n
+ \cs_set_eq:NN \MaxClass \g_updatemarks_max_int
+ \clist_map_inline:nn {#2}
+ { \seq_gput_right:Nx #1 { \int_eval:n { \__updatemarks_maybe_class:n {##1} } } }
+ \seq_gremove_duplicates:N #1
+ \group_end:
+ }
+
+\cs_new:Npn \__updatemarks_class_num:n #1 { \cs:w c__mark_class_ #1 _mark \cs_end: }
+\cs_new:Npn \__updatemarks_maybe_class:n #1
+ {
+ \tl_if_head_eq_meaning:nNTF {#1} [ % ]
+ { \__updatemarks_maybe_class_aux:w #1 } {#1}
+ }
+\cs_new:Npn \__updatemarks_maybe_class_aux:w [#1]
+ {
+ \cs_if_exist_use:cF { c__mark_class_ #1 _mark }
+ { 0 \msg_expandable_error:nnn { updatemarks } { unknown-class } {#1} }
+ }
+
+
+
+\seq_new:N \g_updatemarks_seq
+\NewDocumentCommand \ExtractMarks { s o +m }
+ {
+ \tl_if_novalue:nTF {#2}
+ {
+ \updatemarks_extract:nN
+ {
+ \bool_if:nTF {#1} { #3 }
+ {
+ \if_hbox:N #3 \hbox_unpack:N #3 \scan_stop: \fi:
+ \if_vbox:N #3 \vbox_unpack:N #3 \scan_stop: \fi:
+ }
+ }
+ \g_updatemarks_seq
+ }
+ {
+ \seq_gclear:N \g__updatemarks_seq
+ \__updatemarks_gput:Nn \g__updatemarks_seq {#2}
+ \updatemarks_extract:nN
+ {
+ \bool_if:nTF {#1} { #3 }
+ {
+ \if_hbox:N #3 \hbox_unpack:N #3 \scan_stop: \fi:
+ \if_vbox:N #3 \vbox_unpack:N #3 \scan_stop: \fi:
+ }
+ }
+ \g__updatemarks_seq
+ }
+ }
+\NewDocumentCommand \ExtractSplitMarks { o }
+ {
+ \tl_if_novalue:nTF {#1}
+ { \updatemarks_extract_split:N \g_updatemarks_seq }
+ {
+ \seq_gclear:N \g__updatemarks_seq
+ \__updatemarks_gput:Nn \g__updatemarks_seq {#1}
+ \updatemarks_extract_split:N \g__updatemarks_seq
+ }
+ }
+\NewDocumentCommand \UpdateMarks { o }
+ {
+ \tl_if_novalue:nTF {#1}
+ { \updatemarks_update:N \g_updatemarks_seq }
+ {
+ \seq_gclear:N \g__updatemarks_seq
+ \__updatemarks_gput:Nn \g__updatemarks_seq {#1}
+ \updatemarks_update:N \g__updatemarks_seq
+ }
+ }
+\cs_new_protected:Npn \AddToUpdateMarksList
+ { \__updatemarks_gput:Nn \g_updatemarks_seq }
+\cs_new_protected:Npn \SetUpdateMarksList
+ {
+ \seq_gclear:N \g_updatemarks_seq
+ \AddToUpdateMarksList
+ }
+\cs_new_protected:Npn \RemoveFromUpdateMarksList #1
+ {
+ \__updatemarks_gput:Nn \g__updatemarks_seq {#1}
+ % to support old expl3, do not use \seq_map_tokens:Nn
+ \seq_map_inline:Nn \g__updatemarks_seq
+ { \seq_gremove_all:Nn \g_updatemarks_seq {##1} }
+ }
+\cs_new_protected:Npn \AddAllocatedToUpdateMarksList
+ {
+ \int_step_inline:nnn { 0 } { \g_updatemarks_max_int }
+ { \seq_gput_right:Nn \g_updatemarks_seq {##1} }
+ \seq_gremove_duplicates:N \g_updatemarks_seq
+ }
+
+\cs_new_protected:Npn \__updatemarks_get_all_classes:
+ {
+ \seq_map_inline:Nn \g__mark_classes_seq
+ {
+ \seq_gput_right:Nx \g_updatemarks_classes_seq
+ { \int_value:w \__updatemarks_class_num:n {##1} }
+ }
+ }
+\seq_new:N \g_updatemarks_classes_seq
+\cs_if_exist:NTF \g__mark_classes_seq
+ {
+ \hook_gput_code:nnn { begindocument/end } { updatemarks }
+ {
+ \__updatemarks_get_all_classes:
+ \AddAllocatedToUpdateMarksList
+ }
+ }
+ {
+ \AtBeginDocument
+ {
+ \int_step_inline:nnn { 0 } { \g_updatemarks_max_int }
+ { \seq_gput_right:Nn \g_updatemarks_seq {#1} }
+ }
+ }
+
+\msg_new:nnn { updatemarks } { unknown-class } { Unknown~mark~class~`#1'. }
+\msg_new:nnn { updatemarks } { infinite-shrinkage }
+ { Infinite~shrinkage~found~when~extracting~marks. }
+
+
+\cs_new_protected:Npn \savemarks@of #1 #2 #3
+ {
+ \bool_if:cT { l__updatemarks_#1_bool }
+ { \updatemarks_save:Nnn \g_updatemarks_seq {#2} {#3} }
+ }
+\cs_new_protected:Npn \removemarks@of #1 #2
+ {
+ \bool_if:cT { l__updatemarks_#1_bool }
+ { \updatemarks_remove:Nn \g_updatemarks_seq {#2} }
+ }
+\cs_new_protected:Npn \updatemarks@of #1
+ {
+ \bool_if:cT { l__updatemarks_#1_bool }
+ { \updatemarks_update:N \g_updatemarks_seq }
+ }
+\cs_new_protected:Npn \extractmarks@of #1 #2
+ {
+ \bool_if:cT { l__updatemarks_#1_bool }
+ { \updatemarks_extract:nN { #2 } \g_updatemarks_seq }
+ }
+\cs_new_protected:Npn \extractsplitmarks@of #1
+ {
+ \bool_if:cT { l__updatemarks_#1_bool }
+ { \updatemarks_extract_split:N \g_updatemarks_seq }
+ }
+% \let\updatemarks@@imakebox\@imakebox
+% % savebox and makebox
+% \cs_if_exist:NF \updatemarks@makebox@patch
+% {
+% \patchcmd \@imakebox { \hb@xt@ } { \extractmarks@of{makebox}\hb@xt@ }
+% { } { \ERROR }
+% \patchcmd \@imakebox { \@end@tempboxa } { \updatemarks@of{makebox}\@end@tempboxa }
+% { } { \ERROR }
+% }
+\let\updatemarks@endminipage\endminipage
+% minipage
+\cs_if_exist_use:NF \updatemarks@minipage@patch
+ {
+ \patchcmd \endminipage
+ { \color@endgroup\egroup }
+ { \color@endgroup\egroup\extractmarks@of{minipage}{\unvcopy\@tempboxa} }
+ { } { \ERROR }
+ \apptocmd \endminipage { \updatemarks@of{minipage} } { } { \ERROR }
+ }
+% tcolorbox
+\providecommand \updatemarks@tcolorbox@patch
+ {
+ \tcbset{updatemarks/.is~choice,
+ updatemarks/true/.code=\bool_set_true:N \l__updatemarks_tcolorbox_bool,
+ updatemarks/false/.code=\bool_set_false:N \l__updatemarks_tcolorbox_bool,
+ updatemarks/.default=true}
+ \patchcmd \tcbox@inner@hbox { \tcbdimto }
+ { \extractmarks@of{tcolorbox}{\unhcopy\tcb@upperbox}\tcbdimto }
+ { } { \ERROR }
+ \let\endtcb@lrbox=\updatemarks@endminipage
+ \patchcmd \endtcb@lrbox
+ { \color@endgroup\egroup }
+ { \color@endgroup\egroup\extractmarks@of{tcolorbox}{\unvcopy\@tempboxa} }
+ { } { \ERROR }
+ \let\endtcb@savebox=\endtcb@lrbox
+ \patchcmd \tcb@drawing@env@end { \csname }
+ { \updatemarks@of{tcolorbox}\csname } { } { \ERROR }
+ % breakable library
+ \ifdefined \tcb@vsplit@upper
+ \apptocmd \tcb@vsplit@upper { \extractsplitmarks@of{tcolorbox} } { } { \ERROR }
+ \apptocmd \tcb@vsplit@lower { \extractsplitmarks@of{tcolorbox} } { } { \ERROR }
+ \patchcmd \tcb@split@start { \iftcb@final@box }
+ { \iftcb@final@box
+ \extractmarks@of{tcolorbox}{\unvcopy\tcb@upperbox\unvcopy\tcb@lowerbox} }
+ { } { \ERROR }
+ \patchcmd \tcb@split@start
+ { \tcb@comp@h@page\tcb@check@for@final@box\iftcb@final@box }
+ { \tcb@comp@h@page\tcb@check@for@final@box\iftcb@final@box
+ \extractmarks@of{tcolorbox}{\unvcopy\tcb@upperbox\unvcopy\tcb@lowerbox}}
+ { } { \ERROR }
+ \patchcmd \tcb@split@USL { \iftcb@final@box }
+ { \iftcb@final@box
+ \extractmarks@of{tcolorbox}{\unvcopy\tcb@totalupperbox
+ \iftcb@lowerspace\unvcopy\tcb@totallowerbox\fi }
+ }
+ { } { \ERROR }
+ \patchcmd \tcb@split@SL@displayed { \iftcb@final@box }
+ { \iftcb@final@box
+ \extractmarks@of{tcolorbox}{\unvcopy\tcb@totallowerbox} }
+ { } { \ERROR }
+ \patchcmd \tcb@split@L { \iftcb@final@box }
+ { \iftcb@final@box
+ \extractmarks@of{tcolorbox}{\unvcopy\tcb@totallowerbox} }
+ { } { \ERROR }
+ \fi
+ }
+% multicol
+\providecommand \updatemarks@multicol@patch
+ {
+ %% the patch is used to extract marks
+ \tl_set:No \set@keptmarks
+ { \set@keptmarks \extractsplitmarks@of{multicol} }
+
+ %% the patch is used for boxmulticols
+ \patchcmd \endmulticols { \page@sofar }
+ { \updatemarks@of{multicol}\page@sofar }
+ { } { \ERROR }
+
+ %% the patch is used for middle page to remove unnecessary extract
+ \patchcmd \multi@column@out { \let\topmark\kept@topmark }
+ {
+ \let\topmark\kept@topmark
+ \removemarks@of{multicol}{first}
+ \removemarks@of{multicol}{last}
+ }
+ { } { \ERROR }
+
+ %% the patch is used for final page
+ \patchcmd \balance@columns@out { \page@sofar }
+ { \updatemarks@of{multicol}\page@sofar }
+ { } { \ERROR }
+
+ %% these patches are used to update marks after forced break
+ \patchcmd \balance@columns@out { \unvbox\colbreak@box }
+ { \extractmarks@of{multicol}{\unvcopy\colbreak@box}\unvbox\colbreak@box }
+ { } { \ERROR }
+ \patchcmd \balance@columns@out { \unvbox\@cclv }
+ { \extractmarks@of{multicol}{\unvcopy\@cclv}\unvbox\@cclv }
+ { } { \ERROR }
+ \patchcmd \balance@columns { \get@keptmarks\mult@box }
+ { \if@boxedmulticols\get@keptmarks\mult@box\fi }
+ { } { }
+
+ % patches of new mark mechanism
+ \updatemarks@multicolnewmark@patch
+ }
+\providecommand \updatemarks@adjmulticol@patch
+ {
+ \patchcmd \adjmc@process@ne@column { \unvbox }
+ { \extractmarks@of{multicol}{\unvcopy\mult@box}\unvbox }
+ { } { \ERROR }
+ \updatemarks@adjmulticolnewmark@patch
+ }
+\providecommand \updatemarks@paracol@patch
+ {
+ \updatemarks@paracolnewmark@patch
+ }
+
+\AtBeginDocument{
+ \@ifpackageloaded{tcolorbox}{\updatemarks@tcolorbox@patch}{}
+ \@ifpackageloaded{multicol}{\updatemarks@multicol@patch}{}
+ \@ifpackageloaded{adjmulticol}{\updatemarks@adjmulticol@patch}{}
+ \@ifpackageloaded{paracol}{\updatemarks@paracol@patch}{}
+}
+
+
+\keys_define:nn { updatemarks }
+ {
+% makebox .bool_set:N = \l__updatemarks_makebox_bool ,
+ minipage .bool_set:N = \l__updatemarks_minipage_bool ,
+ tcolorbox .bool_set:N = \l__updatemarks_tcolorbox_bool ,
+ multicol .bool_set:N = \l__updatemarks_multicol_bool ,
+ multicols .bool_set:N = \l__updatemarks_multicol_bool ,
+ paracol .bool_set:N = \l__updatemarks_paracol_bool ,
+ }
+
+\cs_if_exist:NTF \ProcessKeyOptions
+ { \ProcessKeyOptions [ updatemarks ] }
+ {
+ \RequirePackage{l3keys2e}
+ \ProcessKeysOptions { updatemarks }
+ }
+
+\cs_if_free:NT \mark_new_class:n
+ {
+ \providecommand \updatemarks@multicolnewmark@patch { }
+ \providecommand \updatemarks@adjmulticolnewmark@patch { }
+ \providecommand \updatemarks@paracolnewmark@patch { }
+ \endinput
+ }
+
+\tl_const:Nn \updatemarks@multicol@firstpage
+ {
+ \bool_if:NT \l__updatemarks_multicol_bool
+ {
+ \seq_map_inline:Nn \g_updatemarks_seq
+ {
+ \tl_set:Nx \l__updatemarks_tl { \updatemarks_value:nn { top } {#1} }
+ \tl_if_empty:NT \l__updatemarks_tl
+ { \tl_gset:co { g__updatemarks_top-#1_tl } { \tex_topmarks:D #1 } }
+ }
+ }
+ }
+\tl_const:Nn \updatemarks@multicol@middlepage
+ {
+ \__mark_update_structure_alias:nn { previous-page } { page }
+ \seq_map_inline:Nn \g__mark_classes_seq
+ {
+ \tl_gset_eq:cc { g__mark_page_top_#1_tl } { g__mark_page_last_#1_tl }
+
+ \tl_set:Nx \l__updatemarks_tl
+ { \updatemarks_value:nn { first } { \__updatemarks_class_num:n {#1} } }
+ \tl_if_empty:NTF \l__updatemarks_tl
+ { \tl_gset_eq:cc { g__mark_page_first_#1_tl } { g__mark_page_last_#1_tl } }
+ { \tl_gset_eq:cN { g__mark_page_first_#1_tl } \l__updatemarks_tl }
+
+ \tl_set:Nx \l__updatemarks_tl
+ { \updatemarks_value:nn { last } { \__updatemarks_class_num:n {#1} } }
+ \tl_if_empty:NF \l__updatemarks_tl
+ { \tl_gset_eq:cN { g__mark_page_last_#1_tl } \l__updatemarks_tl }
+ }
+ \updatemarks_remove:Nn \g_updatemarks_seq { top }
+ \updatemarks_remove:Nn \g_updatemarks_seq { first }
+ \updatemarks_remove:Nn \g_updatemarks_seq { last }
+ }
+
+\providecommand \updatemarks@multicolnewmark@patch
+ {
+ \patchcmd \prep@keptmarks { \fi }
+ { \updatemarks@multicol@firstpage\fi }
+ { } { \ERROR }
+ \patchcmd \multi@column@out
+ {
+ \removemarks@of{multicol}{first}
+ \removemarks@of{multicol}{last}
+ }
+ { }
+ { } { \ERROR }
+ \patchcmd \multi@column@out { \@makecol }
+ { \@makecol\updatemarks@multicol@middlepage }
+ { } { \ERROR }
+ }
+
+\providecommand \updatemarks@adjmulticolnewmark@patch { }
+\providecommand \updatemarks@paracolnewmark@patch { } \ No newline at end of file