% -------------------------------------------------------------------------- % the FNPCT package % % footnote kerning % % -------------------------------------------------------------------------- % Clemens Niederberger % Web: https://bitbucket.org/cgnieder/fnpct/ % E-Mail: contact@mychemistry.eu % -------------------------------------------------------------------------- % Copyright 2012-2019 Clemens Niederberger % % 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 (at your option) 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 Clemens Niederberger. % -------------------------------------------------------------------------- % If you have any ideas, questions, suggestions or bugs to report, please % feel free to contact me. % -------------------------------------------------------------------------- % the package is inspired by the following question on TeX.SE: % http://tex.stackexchange.com/q/56094/5049 % -------------------------------------------------------------------------- \RequirePackage { xparse , l3keys2e , scrlfile } \ProvidesExplPackage {fnpct} {2019/09/30} {0.4g} {footnote kerning} % -------------------------------------------------------------------------- % SETTING THINGS UP: % this is plain's \nobreak: \cs_new:Npn \fnpct_no_break: { \tex_penalty:D 10000 \scan_stop: } % messages: \cs_new_protected:Npn \fnpct_dont_mess_around: { \iow_log:n { ................................................. } \iow_log:n { . ~ fnpct~info: } \iow_log:n { . } \iow_log:n { . ~ All~right,~not~messing~around.~:( } \iow_log:n { . ~ But~I'd~really~love~to~(http://www.youtube.com/watch?v=Bqz876VkwwY). } \iow_log:n { ................................................. } } \msg_set:nnn { fnpct } { already-adapted } { The~command~\token_to_str:N #1 \tl_use:N \c_space_tl has~already~been~ adapted. \\ I~will~do~nothing~instead. } \bool_new:N \l__fnpct_strict_bool \cs_new_protected:Npn \fnpct_message:nx #1#2 { \bool_if:NTF \l__fnpct_strict_bool { \msg_error:nnx { fnpct } { #1 } } { \msg_warning:nnx { fnpct } { #1 } } { #2 } } % scratch variables: \tl_new:N \l__fnpct_tmpa_tl \tl_new:N \l__fnpct_tmpb_tl \dim_new:N \l__fnpct_tmpa_dim \dim_new:N \l__fnpct_tmpb_dim \int_new:N \l__fnpct_tmpa_int % rigid lengths: \dim_new:N \l__fnpct_after_comma_dim \dim_set:Nn \l__fnpct_after_comma_dim { -.06em } \dim_new:N \l__fnpct_after_dot_dim \dim_set:Nn \l__fnpct_after_dot_dim { -.06em } \dim_new:N \l__fnpct_before_comma_dim \dim_set:Nn \l__fnpct_before_comma_dim { -.16em } \dim_new:N \l__fnpct_before_dot_dim \dim_set:Nn \l__fnpct_before_dot_dim { -.16em } \dim_new:N \l__fnpct_before_footnote_dim \dim_set:Nn \l__fnpct_before_footnote_dim { .06em } \dim_new:N \l__fnpct_normal_mark_width_dim \dim_set:Nn \l__fnpct_normal_mark_width_dim { 1em } \dim_new:N \l__fnpct_normal_indent_dim \dim_set:Nn \l__fnpct_normal_indent_dim { 1.5em } \dim_new:N \l__fnpct_normal_parindent_dim \dim_set:Nn \l__fnpct_normal_parindent_dim { 1em } \dim_new:N \l__fnpct_french_before_footnote_space_dim \dim_set:Nn \l__fnpct_french_before_footnote_space_dim { .16667em } % multiple footnote input and output variables: \tl_new:N \l__fnpct_multiple_footnotes_delimiter_tl \tl_set:Nn \l__fnpct_multiple_footnotes_delimiter_tl { ; } \tl_new:N \l__fnpct_multiple_footnote_separator_tl % check for KOMA-Script's \multfootsep: \cs_if_exist:NTF \KOMAClassName { \tl_set:Nn \l__fnpct_multiple_footnote_separator_tl { \multfootsep } } { \tl_set:Nn \l__fnpct_multiple_footnote_separator_tl { , } } % basic booleans for punctuation order switch and multiple footnotes \bool_new:N \l__fnpct_punct_after_bool \bool_new:N \l__fnpct_dont_mess_around_bool \bool_new:N \l__fnpct_multiple_default_bool \bool_new:N \l__fnpct_multiple_footnotes_bool \bool_new:N \l__fnpct_reverse_switch_bool \bool_new:N \l__fnpct_bigfoot_default_top_bool \bool_new:N \l__fnpct_normal_marks_bool \bool_new:N \l__fnpct_hyperref_bool \bool_new:N \g__fnpct_after_punctuation_bool % default punctuation marks for switching: \tl_new:N \l__fnpct_punctuation_marks_tl \tl_set:Nn \l__fnpct_punctuation_marks_tl { ., } \prop_new:N \l__fnpct_punctuation_marks_after_prop \prop_new:N \l__fnpct_punctuation_marks_before_prop % #1: before/after % #2: punctuation mark % #3: dimension \cs_new_protected:Npn \fnpct_set_punctuation_dim:nnn #1#2#3 { \prop_put:cnx { l__fnpct_punctuation_marks_#1_prop } { #2 } { \dim_eval:n { #3 } } } \cs_generate_variant:Nn \fnpct_set_punctuation_dim:nnn { nV } \fnpct_set_punctuation_dim:nnn { after } { . } { \l__fnpct_after_dot_dim } \fnpct_set_punctuation_dim:nnn { after } { , } { \l__fnpct_after_comma_dim } \fnpct_set_punctuation_dim:nnn { before } { . } { \l__fnpct_before_dot_dim } \fnpct_set_punctuation_dim:nnn { before } { , } { \l__fnpct_before_comma_dim } \cs_new_protected:Npn \fnpct_rm_punctuation:n #1 { \tl_remove_all:Nn \l__fnpct_punctuation_marks_tl { #1 } \prop_del:Nn \l__fnpct_punctuation_marks_after_prop { #1 } \prop_del:Nn \l__fnpct_punctuation_marks_before_prop { #1 } } \tl_new:N \l__fnpct_last_punct_mark_tl \cs_new_protected:Npn \fnpct_add_punctuation:n #1 { \__fnpct_add_punctuation:w #1 [ \q_no_value ] \q_no_value \q_stop } \cs_generate_variant:Nn \fnpct_add_punctuation:n { V } \cs_new_protected:Npn \__fnpct_add_punctuation:w #1[#2]#3 \q_stop { \quark_if_no_value:nTF { #2 } { % there is no option at all \tl_map_inline:nn { #1 } { \tl_put_right:Nn \l__fnpct_punctuation_marks_tl { ##1 } \fnpct_set_punctuation_dim:nnn { after } { ##1 } { 0pt } \fnpct_set_punctuation_dim:nnn { before } { ##1 } { 0pt } \tl_set:No \l__fnpct_last_punct_mark_tl { ##1 } } } { \tl_if_blank:nTF { #1 } { % #2 is a second option (= before space) \fnpct_set_punctuation_dim:nVn { before } \l__fnpct_last_punct_mark_tl { #2 } } { % #2 is a first option (= after space), #1 might be more than one token: \tl_if_single_token:nTF { #1 } { \tl_put_right:Nn \l__fnpct_punctuation_marks_tl { #1 } \fnpct_set_punctuation_dim:nnn { after } { #1 } { #2 } \fnpct_set_punctuation_dim:nnn { before } { #1 } { 0pt } \tl_set:No \l__fnpct_last_punct_mark_tl { #1 } } { \int_zero:N \l__fnpct_tmpa_int \tl_map_inline:nn { #1 } { \tl_put_right:Nn \l__fnpct_punctuation_marks_tl { ##1 } \int_compare:nTF { \l__fnpct_tmpa_int < ( \tl_count:n { #1 } - 1 ) } { \fnpct_set_punctuation_dim:nnn { after } { ##1 } { 0pt} \fnpct_set_punctuation_dim:nnn { before } { ##1 } { 0pt } } { \fnpct_set_punctuation_dim:nnn { after } { ##1 } { #2 } \fnpct_set_punctuation_dim:nnn { before } { ##1 } { 0pt } } \tl_set:No \l__fnpct_last_punct_mark_tl { ##1 } \int_incr:N \l__fnpct_tmpa_int } } } } % is there more? \tl_if_eq:nnF { #3 } { [\q_no_value]\q_no_value } { \quark_if_no_value:nF { #3 } { \tl_set:Nn \l__fnpct_tmpa_tl { #3 } \tl_remove_all:Nn \l__fnpct_tmpa_tl { [\q_no_value]\q_no_value } \fnpct_add_punctuation:V \l__fnpct_tmpa_tl } } } % options: \keys_define:nn { fnpct } { strict .bool_set:N = \l__fnpct_strict_bool , strict .default:n = true , after-comma-space .code:n = \fnpct_set_punctuation_dim:nnn { after } { , } { #1 } , after-dot-space .code:n = \fnpct_set_punctuation_dim:nnn { after } { . } { #1 } , before-comma-space .code:n = \fnpct_set_punctuation_dim:nnn { before } { , } { #1 } , before-dot-space .code:n = \fnpct_set_punctuation_dim:nnn { before } { , } { #1 } , after-punct-space .code:n = \prop_map_inline:Nn \l__fnpct_punctuation_marks_after_prop { \fnpct_set_punctuation_dim:nnn { after } { ##1 } { #1 } } , before-punct-space .code:n = \prop_map_inline:Nn \l__fnpct_punctuation_marks_before_prop { \fnpct_set_punctuation_dim:nnn { before } { ##1 } { #1 } } , before-footnote-space .dim_set:N = \l__fnpct_before_footnote_dim , french-before-footnote-space .dim_set:N = \l__fnpct_french_before_footnote_space_dim , punct-after .bool_set:N = \l__fnpct_punct_after_bool , punct-after .default:n = true , dont-mess-around .code:n = \prop_map_inline:Nn \l__fnpct_punctuation_marks_after_prop { \fnpct_set_punctuation_dim:nnn { after } { ##1 } { 0pt } } \prop_map_inline:Nn \l__fnpct_punctuation_marks_before_prop { \fnpct_set_punctuation_dim:nnn { before } { ##1 } { 0pt } } \dim_zero:N \l__fnpct_before_footnote_dim \bool_set_true:N \l__fnpct_punct_after_bool \bool_set_true:N \l__fnpct_dont_mess_around_bool \fnpct_dont_mess_around: , mult-fn-delim .tl_set:N = \l__fnpct_multiple_footnotes_delimiter_tl , mult-fn-sep .tl_set:N = \l__fnpct_multiple_footnote_separator_tl , multiple .bool_set:N = \l__fnpct_multiple_default_bool , multiple .default:n = true , bigfoot-default-top .bool_set:N = \l__fnpct_bigfoot_default_top_bool , bigfoot-default-top .default:n = true , normal-marks .bool_set:N = \l__fnpct_normal_marks_bool , normal-marks .default:n = true , normal-mark-width .dim_set:N = \l__fnpct_normal_mark_width_dim , normal-indent .dim_set:N = \l__fnpct_normal_indent_dim , normal-parindent .dim_set:N = \l__fnpct_normal_parindent_dim , verb-format .tl_set:N = \l__fnpct_verbatim_format_tl , add-punct-marks .code:n = \fnpct_add_punctuation:n { #1 } , remove-punct-marks .code:n = \tl_map_function:nN { #1 } \fnpct_rm_punctuation:n } \ProcessKeysOptions { fnpct } % -------------------------------------------------------------------------- % MAIN INTERNAL FOOTNOTE FUNCTION: % write the notes: \cs_new:Npn \__fnpct_no_value_or_quark_no_value:nTF #1#2#3 { \IfNoValueTF { #1 } { #2 } { \quark_if_no_value:nTF { #1 } { #2 } { #3 } } } \cs_new:Npn \fnpct_write_note:Nnnn #1#2#3#4 { \__fnpct_no_value_or_quark_no_value:nTF { #2 } { #1 { #4 } } { \__fnpct_no_value_or_quark_no_value:nTF { #3 } { #1 [ #2 ] { #4 } } { #1 [ #2 ] [ #3 ] { #4 } } } } % check for punctuation: \cs_new_protected:Npn \fnpct_check_punctuation:TF #1#2 { \__fnpct_check_punctuation:nTF { 0 } { #1 } { #2 } } \cs_new_protected:Npn \__fnpct_check_punctuation:nTF #1#2#3 { \__fnpct_get_head:NN \l__fnpct_current_punct_mark_tl \l__fnpct_punctuation_marks_tl \exp_args:NV \peek_meaning_remove:NTF \l__fnpct_current_punct_mark_tl { #2 } { \__fnpct_check_punctuation_aux:nnn { #1 } { \__fnpct_check_punctuation:VTF \l__fnpct_tmpa_int { #2 } { #3 } } { #3 } } } \cs_new_protected:Npn \__fnpct_check_punctuation_aux:nnn #1#2#3 { \int_set:Nn \l__fnpct_tmpa_int { #1 } \int_incr:N \l__fnpct_tmpa_int \tl_set:Nx \l__fnpct_tmpb_tl { \int_to_arabic:n { \l__fnpct_tmpa_int } } \tl_remove_all:NV \l__fnpct_punctuation_marks_tl \l__fnpct_current_punct_mark_tl \tl_put_right:NV \l__fnpct_punctuation_marks_tl \l__fnpct_current_punct_mark_tl \int_compare:nTF { \l__fnpct_tmpa_int < \tl_count:V \l__fnpct_punctuation_marks_tl } { #2 } { #3 } } \cs_generate_variant:Nn \__fnpct_check_punctuation:nTF { V } % \cs_generate_variant:Nn \peek_meaning_remove:NTF { V } \cs_generate_variant:Nn \tl_remove_all:Nn { NV } \cs_generate_variant:Nn \tl_put_right:Nn { NV } \cs_new_protected:Npn \__fnpct_get_head:NN #1#2 { \tl_set:Nx #1 { \tl_head:V #2 } } % check for multiple notes: \tl_new:N \l__fnpct_multiple_true_tl \tl_new:N \l__fnpct_multiple_false_tl \cs_new_protected:Npn \fnpct_check_multiple:TF #1#2 { \tl_set:Nn \l__fnpct_multiple_true_tl { #1 } \tl_set:Nn \l__fnpct_multiple_false_tl { #2 } \bool_gset_false:N \g__fnpct_after_punctuation_bool \peek_after:Nw \__fnpct_check_multiple: } \cs_new_protected:Npn \__fnpct_check_multiple: { \prop_map_inline:Nn \g__fnpct_adapted_notes_prop { \prop_get:NnN \g__fnpct_adapted_notes_prop { ##1 } \l__fnpct_tmpb_tl \exp_args:NV \token_if_eq_meaning:NNTF \l__fnpct_tmpb_tl \l_peek_token { \bool_set_true:N \l__fnpct_multiple_footnotes_bool \prop_map_break: } { \bool_set_false:N \l__fnpct_multiple_footnotes_bool } } \bool_if:NTF \l__fnpct_multiple_footnotes_bool { \tl_use:N \l__fnpct_multiple_true_tl } { \tl_use:N \l__fnpct_multiple_false_tl } } % \cs_generate_variant:Nn \token_if_eq_meaning:NNTF {V} % check which mode is active: \prg_new_conditional:Npnn \fnpct_punct_if_after: { T, TF } { \bool_if:nTF { \bool_xor_p:nn { \l__fnpct_punct_after_bool } { \l__fnpct_reverse_switch_bool } } { \prg_return_true: } { \prg_return_false: } } % this is where the magic happens: \cs_new_protected:Npn \fnpct_handle_punctuation:n #1 { \fnpct_check_punctuation:TF { % this is need as some commands like biblatex's \autocite may in turn % call \footnote. It then must not insert the pre-footnote-space: \bool_gset_true:N \g__fnpct_after_punctuation_bool % A: after=1 && reverse=0 % after=0 && reverse=1 % B: after=1 && reverse=1 % after=0 && reverse=0 \fnpct_punct_if_after:TF { \fnpct_no_break: \skip_horizontal:N \l__fnpct_before_footnote_dim } { \tl_use:N \l__fnpct_current_punct_mark_tl \prop_get:NVNT \l__fnpct_punctuation_marks_after_prop \l__fnpct_current_punct_mark_tl \l__fnpct_tmpa_tl { \dim_set:Nn \l__fnpct_tmpa_dim { \l__fnpct_tmpa_tl } \fnpct_no_break: \skip_horizontal:N \l__fnpct_tmpa_dim \fnpct_no_break: \skip_horizontal:N \c_zero_skip \fnpct_no_break: } } #1 \bool_gset_false:N \g__fnpct_after_punctuation_bool \fnpct_punct_if_after:T { \fnpct_no_break: \prop_get:NVNT \l__fnpct_punctuation_marks_before_prop \l__fnpct_current_punct_mark_tl \l__fnpct_tmpa_tl { \dim_set:Nn \l__fnpct_tmpa_dim { \l__fnpct_tmpa_tl } \fnpct_no_break: \skip_horizontal:N \l__fnpct_tmpa_dim \fnpct_no_break: \skip_horizontal:N \c_zero_skip \fnpct_no_break: } \tl_use:N \l__fnpct_current_punct_mark_tl } \bool_set_false:N \l__fnpct_reverse_switch_bool } { % what about multiple footnotes? (their usage is discouraged with % this package, but anyway...) % this check should probably/maybe be removed (?!) \fnpct_check_multiple:TF { \bool_set_true:N \l__fnpct_multiple_footnotes_bool % else insert space and then note \fnpct_no_break: \bool_if:NF \g__fnpct_after_punctuation_bool { \skip_horizontal:N \l__fnpct_before_footnote_dim } #1 \fnpct_no_break: \textsuperscript { \l__fnpct_multiple_footnote_separator_tl } } { % else insert space and then note \bool_if:NTF \l__fnpct_multiple_footnotes_bool { \bool_set_false:N \l__fnpct_multiple_footnotes_bool } { \fnpct_no_break: \bool_if:NF \g__fnpct_after_punctuation_bool { \skip_horizontal:N \l__fnpct_before_footnote_dim } } #1 \bool_set_false:N \l__fnpct_reverse_switch_bool } % \bool_gset_false:N \g__fnpct_after_punctuation_bool } } % #1: original command % #2: optional argument of original command % #3: second optional argument of original command % #4: mandatory argument of original command % #5: boolean flag for starred version \cs_new_protected:Npn \fnpct_handle_note:Nnnnn #1#2#3#4#5 { % if a punctuation mark follows remove it, insert dot, skip back % and then insert footnote \bool_if:NF \l__fnpct_dont_mess_around_bool { \IfBooleanT { #5 } { \bool_set_true:N \l__fnpct_reverse_switch_bool } } \fnpct_handle_punctuation:n { \fnpct_write_note:Nnnn #1 { #2 } { #3 } { #4 } \fnpct_write_inner:N #1 } } \cs_generate_variant:Nn \fnpct_handle_note:Nnnnn { cnnnn } % -------------------------------------------------------------------------- % MULTIPLE FOOTNOTES \seq_new:N \l__fnpct_multiple_footnotes_seq \cs_generate_variant:Nn \seq_set_split:Nnn { NVn } % #1: original note command % #2: star % #3: multiple notes separated by \l__fnpct_multiple_footnotes_delimiter_tl \cs_new_protected:Npn \fnpct_mult_note:Nnn #1#2#3 { \bool_if:NF \l__fnpct_dont_mess_around_bool { \IfBooleanT { #2 } { \bool_set_true:N \l__fnpct_reverse_switch_bool } } % split input: \seq_set_split:NVn \l__fnpct_multiple_footnotes_seq \l__fnpct_multiple_footnotes_delimiter_tl { #3 } \fnpct_handle_punctuation:n { \fnpct_write_notes:NN #1 \l__fnpct_multiple_footnotes_seq } } \cs_generate_variant:Nn \fnpct_mult_note:Nnn { cnn } \int_new:N \l__fnpct_multiple_notes_int % #1: original note command % #2: sequence variable holding the note arguments \cs_new_protected:Npn \fnpct_write_notes:NN #1#2 { \int_zero:N \l__fnpct_multiple_notes_int \seq_map_inline:Nn #2 { \__fnpct_read_note_with_option:w ##1 \q_stop { #1 } \int_incr:N \l__fnpct_multiple_notes_int } } % this shouldn't be a document command but is a really easy way to make % biblatex's \footcite et.al. compatible with the `multiple' option % #1: optional star => only invoke \footnotetext % #2: optional argument to original note command % #3: second optional argument to original note command % #4: mandatory argument to original note command % #5: original note command \bool_new:N \g__fnpct_only_text_bool \NewDocumentCommand \__fnpct_read_note_with_option:w { soo+u{\q_stop}m } { \bool_if:nT { \int_compare_p:n { \l__fnpct_multiple_notes_int > 0 } && \int_compare_p:n { \l__fnpct_multiple_notes_int < \seq_count:N \l__fnpct_multiple_footnotes_seq } } { \IfBooleanTF { #1 } { \tex_unskip:D } { \textsuperscript { \l__fnpct_multiple_footnote_separator_tl } } } \IfBooleanTF { #1 } { % TODO: maybe provide option to change \footnotetext? Or do it % automatically? \bool_gset_true:N \g__fnpct_only_text_bool \IfNoValueTF { #2 } { \footnotetext { #4 } } { \footnotetext [ #2 ] { #4 } } } { \fnpct_write_note:Nnnn #5 { #2 } { #3 } { #4 } } \fnpct_write_inner:N #5 } % -------------------------------------------------------------------------- % NESTED FOOTNOTES: % 1 layer of nesting... \prop_new:N \g__fnpct_inner_footnote_prop \prop_new:N \g__fnpct_inner_footnote_hyperref_prop \cs_generate_variant:Nn \prop_gput:Nnn { Nxn , Nxo } % this shouldn't be a document command! But for the time being I'll stick to % this easy solution... \NewDocumentCommand \fnpct_inner_footnote:w { o+m } { \IfNoValueTF { #1 } { % TODO: maybe detect what type of note we're in and use the appropriate mark? % Or provide a user interface to choose the appropriate mark? \fnpct_orig_footnotemark:w \prop_gput:Nxn \g__fnpct_inner_footnote_prop { \thefootnote } { #2 } \bool_if:NT \l__fnpct_hyperref_bool { \prop_gput:Nxo \g__fnpct_inner_footnote_hyperref_prop { \thefootnote } { \Hy@footnote@currentHref } } } { \fnpct_orig_footnotemark:w [ #1 ] \prop_gput:Nnn \g__fnpct_inner_footnote_prop { #1 } { #2 } \bool_if:NT \l__fnpct_hyperref_bool { \prop_gput:Nno \g__fnpct_inner_footnote_hyperref_prop { #1 } { \Hy@footnote@currentHref } } } } \cs_new:Npn \fnpct_write_inner:N #1 { \token_if_eq_meaning:NNF #1 \fnpct_inner_footnote:w { \prop_map_inline:Nn \g__fnpct_inner_footnote_prop { \footnotetext [ ##1 ] { \bool_if:NT \l__fnpct_hyperref_bool { \prop_get:NnN \g__fnpct_inner_footnote_hyperref_prop { ##1 } \l__fnpct_tmpa_tl \Hy@raisedlink { \exp_args:NV \hyper@@anchor \l__fnpct_tmpa_tl } } ##2 } } \prop_gclear:N \g__fnpct_inner_footnote_prop } } \NewDocumentCommand \writeinnernotes {} { \fnpct_write_inner:N X } % -------------------------------------------------------------------------- % MANUAL KERNING: \cs_new_protected:Npn \fnpct_kfp: { \fnpct_no_break: \prop_get:NnNT \l__fnpct_punctuation_marks_before_prop {.} \l__fnpct_tmpa_tl { \dim_set:Nn \l__fnpct_tmpa_dim { \l__fnpct_tmpa_tl } } \prop_get:NnNT \l__fnpct_punctuation_marks_after_prop {.} \l__fnpct_tmpb_tl { \dim_set:Nn \l__fnpct_tmpb_dim { \l__fnpct_tmpb_tl } } \bool_if:NTF \l__fnpct_punct_after_bool { \skip_horizontal:N \l__fnpct_tmpa_dim } { \skip_horizontal:N \l__fnpct_tmpb_dim } } \cs_new_protected:Npn \fnpct_kfc: { \fnpct_no_break: \prop_get:NnNT \l__fnpct_punctuation_marks_before_prop {,} \l__fnpct_tmpa_tl { \dim_set:Nn \l__fnpct_tmpa_dim { \l__fnpct_tmpa_tl } } \prop_get:NnNT \l__fnpct_punctuation_marks_after_prop {,} \l__fnpct_tmpb_tl { \dim_set:Nn \l__fnpct_tmpb_dim { \l__fnpct_tmpb_tl } } \bool_if:NTF \l__fnpct_punct_after_bool { \skip_horizontal:N \l__fnpct_tmpa_dim } { \skip_horizontal:N \l__fnpct_tmpb_dim } } \NewDocumentCommand \kfp {} { \fnpct_kfp: } \NewDocumentCommand \kfc {} { \fnpct_kfc: } % -------------------------------------------------------------------------- % NORMAL MARKS: \AtBeginDocument { \bool_if:NT \l__fnpct_normal_marks_bool { \cs_if_exist:NF \KOMAoption { \RequirePackage { scrextend } } \deffootnote [ \l__fnpct_normal_mark_width_dim ] { \l__fnpct_normal_indent_dim } { \l__fnpct_normal_parindent_dim } { \thefootnotemark . \enskip } } } % -------------------------------------------------------------------------- % LET'S MAKE IT EASIER TO ADAPT EXISTIUNG FUNCTIONS: \prop_new:N \g__fnpct_adapted_notes_prop \cs_generate_variant:Nn \prop_put:Nnn { Nxn } % COPY, RENEW AND CREATE MULT % standard \footnote[]{} like commands % #1: old new name % #2: internal name of old definition % #3: mult-variant % % \footnote[]{} like: \cs_new_protected:Npn \fnpct_renew_and_mult:NNN #1#2#3 { \prop_gput:Nxn \g__fnpct_adapted_notes_prop { \cs_to_str:N #2 } { #1 } \cs_new_eq:NN #2 #1 \fnpct_create_mult_variant:NN #3 #2 \bool_if:NTF \l__fnpct_multiple_default_bool { \cs_set_eq:NN #1 #3 } { \RenewDocumentCommand #1 { so+m } { \fnpct_handle_note:Nnnnn #2 { ##2 } { \q_no_value } { ##3 } { ##1 } } } } \cs_generate_variant:Nn \fnpct_renew_and_mult:NNN { ccc , NcN } % \footnote{} like: \cs_new_protected:Npn \fnpct_renew_and_mult_no_opt:NNN #1#2#3 { \prop_gput:Nxn \g__fnpct_adapted_notes_prop { \cs_to_str:N #2 } { #1 } \cs_new_eq:NN #2 #1 \fnpct_create_mult_variant:NN #3 #2 \bool_if:NTF \l__fnpct_multiple_default_bool { \cs_set_eq:NN #1 #3 } { \RenewDocumentCommand #1 { s+m } { \fnpct_handle_note:Nnnnn #2 { \q_no_value } { \q_no_value } { ##2 } { ##1 } } } } \cs_generate_variant:Nn \fnpct_renew_and_mult_no_opt:NNN { Nc, ccc } % renew but don't create mult-variant: % \footnote[]{} like: \cs_new_protected:Npn \fnpct_renew:NN #1#2 { \prop_gput:Nxn \g__fnpct_adapted_notes_prop { \cs_to_str:N #2 } { #1 } \cs_new_eq:NN #2 #1 \RenewDocumentCommand #1 { so+m } { \fnpct_handle_note:Nnnnn #2 { ##2 } { \q_no_value } { ##3 } { ##1 } } } \cs_generate_variant:Nn \fnpct_renew:NN { cc ,Nc } % new \cs_new_protected:Npn \fnpct_new:NN #1#2 { \prop_gput:Nxn \g__fnpct_adapted_notes_prop { \cs_to_str:N #2 } { #1 } \NewDocumentCommand #1 { so+m } { \fnpct_handle_note:Nnnnn #2 { ##2 } { \q_no_value } { ##3 } { ##1 } } } \cs_generate_variant:Nn \fnpct_new:NN { cc , Nc } % \note[][]{} like: \cs_new_protected:Npn \fnpct_renew_and_mult_opt:NNN #1#2#3 { \prop_gput:Nxn \g__fnpct_adapted_notes_prop { \cs_to_str:N #2 } { #1 } \cs_new_eq:NN #2 #1 \fnpct_create_mult_variant:NN #3 #2 \bool_if:NTF \l__fnpct_multiple_default_bool { \cs_set_eq:NN #1 #3 } { \RenewDocumentCommand #1 { soo+m } { \fnpct_handle_note:Nnnnn #2 { ##2 } { ##3 } { ##4 } { ##1 } } } } \cs_generate_variant:Nn \fnpct_renew_and_mult_opt:NNN { ccc , NcN } \cs_new_protected:Npn \fnpct_renew_opt:NN #1#2 { \prop_gput:Nxn \g__fnpct_adapted_notes_prop { \cs_to_str:N #2 } { #1 } \cs_new_eq:NN #2 #1 \RenewDocumentCommand #1 { soo+m } { \fnpct_handle_note:Nnnnn #2 { ##2 } { ##3 } { ##4 } { ##1 } } } \cs_generate_variant:Nn \fnpct_renew_opt:NN { cc , Nc } % \note{} like \cs_new_protected:Npn \fnpct_renew_no_opt:NN #1#2 { \prop_gput:Nxn \g__fnpct_adapted_notes_prop { \cs_to_str:N #2 } { #1 } \cs_new_eq:NN #2 #1 \RenewDocumentCommand #1 { s+m } { \fnpct_handle_note:Nnnnn #2 { \q_no_value } { \q_no_value } { ##2 } { ##1 } } } \cs_generate_variant:Nn \fnpct_renew_no_opt:NN { cc ,Nc } % \note like \cs_new_protected:Npn \fnpct_renew_no_arg:NN #1#2 { \prop_gput:Nxn \g__fnpct_adapted_notes_prop { \cs_to_str:N #2 } { #1 } \cs_new_eq:NN #2 #1 \RenewDocumentCommand #1 { s } { \fnpct_handle_note:Nnnnn #2 { \q_no_value } { \q_no_value } { } { ##1 } } } \cs_generate_variant:Nn \fnpct_renew_no_arg:NN { cc } % create mult-variant \cs_new_protected:Npn \fnpct_create_mult_variant:NN #1#2 { \NewDocumentCommand #1 { s+m } { \fnpct_mult_note:Nnn #2 { ##1 } { ##2 } } } % \footnotemark[] like commands: \cs_new_protected:Npn \fnpct_renew_mark:NN #1#2 { \prop_gput:Nxn \g__fnpct_adapted_notes_prop { \cs_to_str:N #2 } { #1 } \cs_new_eq:NN #2 #1 \RenewDocumentCommand #1 { so } { \fnpct_handle_note:Nnnnn #2 { ##2 } { \q_no_value } { } { ##1 } } } \cs_generate_variant:Nn \fnpct_renew_mark:NN { cc , Nc } % user commands: \NewDocumentCommand \AdaptNote { mm } { \cs_if_exist:cTF { fnpct_orig_ \cs_to_str:N #1 :w } { \fnpct_message:nx { already-adapted } { #1 } } { \fnpct_renew_and_mult:NcN #1 { fnpct_orig_ \cs_to_str:N #1 :w } #2 } } \NewDocumentCommand \AdaptNoteNoMult { m } { \cs_if_exist:cTF { fnpct_orig_ \cs_to_str:N #1 :w } { \fnpct_message:nx { already-adapted } { #1 } } { \fnpct_renew:Nc #1 { fnpct_orig_ \cs_to_str:N #1 :w } } } \NewDocumentCommand \AdaptNoteOpt { mm } { \cs_if_exist:cTF { fnpct_orig_ \cs_to_str:N #1 :w } { \fnpct_message:nx { already-adapted } { #1 } } { \fnpct_renew_and_mult_opt:NcN #1 { fnpct_orig_ \cs_to_str:N #1 :w } #2 } } \NewDocumentCommand \AdaptNoteOptNoMult { m } { \cs_if_exist:cTF { fnpct_orig_ \cs_to_str:N #1 :w } { \fnpct_message:nx { already-adapted } { #1 } } { \fnpct_renew_opt:Nc #1 { fnpct_orig_ \cs_to_str:N #1 :w } } } \NewDocumentCommand \AdaptNoteNoOpt { mm } { \cs_if_exist:cTF { fnpct_orig_ \cs_to_str:N #1 :w } { \fnpct_message:nx { already-adapted } { #1 } } { \fnpct_renew_and_mult_no_opt:NcN #1 { fnpct_orig_ \cs_to_str:N #1 :w } #2 } } \NewDocumentCommand \AdaptNoteNoOptNoMult { m } { \cs_if_exist:cTF { fnpct_orig_ \cs_to_str:N #1:w } { \fnpct_message:nx { already-adapted } { #1 } } { \fnpct_renew_no_opt:Nc #1 { fnpct_orig_ \cs_to_str:N #1:w } } } \NewDocumentCommand \AdaptNoteMark { m } { \cs_if_exist:cTF { fnpct_orig_ \cs_to_str:N #1 :w } { \fnpct_message:nx { already-adapted } { #1 } } { \fnpct_renew_mark:Nc #1 { fnpct_orig_ \cs_to_str:N #1 :w } } } % -------------------------------------------------------------------------- % DO THE REDEFINING: % % before we start make the testing more comfortable: \cs_new:Npn \fnpct_treatment:nn #1#2 { \@ifpackageloaded { #1 } { #2 } { } } \cs_new:Npn \fnpct_special_treatment:nn #1#2 { \@ifpackageloaded { #1 } { #2 } { \AfterPackage * { #1 } { #2 } } } % and now get going: \AtBeginDocument { \@ifpackageloaded { hyperref } { \bool_set_true:N \l__fnpct_hyperref_bool } { \cs_if_exist:NF \AfterBeginDocument { \cs_new:Npn \AfterBeginDocument #1 { #1 } } } \AfterBeginDocument { \AdaptNote \footnote \multfootnote \AdaptNoteMark \footnotemark \cs_if_exist:NT \footref { \AdaptNoteNoOptNoMult \footref } \fnpct_new:NN \innernote \fnpct_inner_footnote:w %% the `endnotes' package: \fnpct_treatment:nn { endnotes } { \AdaptNote \endnote \multendnote \AdaptNoteMark \endnotemark } %% the `enotez' package: \fnpct_treatment:nn { enotez } { \AdaptNote \endnote \multendnote } %% the `snotez' package: \fnpct_treatment:nn { snotez } { \AdaptNoteOpt \sidenote \multsidenote \AdaptNoteMark \sidenotemark } %% the `sidenotes' package: % \fnpct_treatment:nn { sidenotes } % { % \AdaptNoteOpt \sidenote \multsidenote % \AdaptNoteMark \sidenotemark % } %% the `parnotes' package: \fnpct_treatment:nn { parnotes } { \AdaptNote \parnote \multparnote } %% the `pagenote' package: \fnpct_treatment:nn { pagenote } { \bool_if:NT \l__fnpct_makepagenote_bool { \AdaptNote \pagenote \multpagenote } } %% the `tablefootnote' package: \fnpct_treatment:nn { tablefootnote } { \AdaptNote \tablefootnote \multtablefootnote } %% the `bigfoot' package: \fnpct_treatment:nn { bigfoot } { \prop_map_inline:Nn \l__fnpct_footnote_class_prop { \fnpct_renew_and_mult:ccc { footnote#2 } { fnpct_orig_footnote#2:w } { multfootnote#2 } \fnpct_renew_mark:cc { footnotemark#2 } { fnpct_orig_footnotemark#2:w } } \prop_if_in:NnT \l__fnpct_footnote_class_prop { default } { % re-set basics: \cs_set_eq:NN \footnote \footnotedefault \cs_set_eq:NN \footnotemark \footnotemarkdefault \cs_set_eq:NN \multfootnote \multfootnotedefault } } %% the `fixfoot' package: \fnpct_treatment:nn { fixfoot } { \prop_map_inline:Nn \l__fnpct_footnote_fixfoot_prop { \fnpct_renew_no_arg:cc { #2 } { fnpct_orig_fix_#2:w } } } %% the `sepfootnotes' package: \fnpct_treatment:nn { sepfootnotes } { \prop_map_inline:Nn \l__fnpct_sepfootnote_foot_classes_prop { \fnpct_renew_and_mult_no_opt:ccc { #2note } { fnpct_orig_sep_#2:w } { #2multnote } \fnpct_renew_and_mult_no_opt:ccc { #2quicknote } { fnpct_orig_sep_#2_quick:w } { #2multquicknote } \fnpct_renew_no_opt:cc { #2notemark } { fnpct_orig_sep_#2_mark:w } } \prop_map_inline:Nn \l__fnpct_sepfootnote_end_classes_prop { \fnpct_renew_and_mult_no_opt:ccc { #2note } { fnpct_orig_sep_#2:w } { #2multnote } \fnpct_renew_no_opt:cc { #2notemark } { fnpct_orig_sep_#2_mark:w } } } } } % the `bigfoot' package neads special treatment: \prop_new:N \l__fnpct_footnote_class_prop \fnpct_special_treatment:nn { bigfoot } { % hook into \DeclareNewFootnote so we can redefine all footnote % classes defined be users \cs_new_eq:NN \fnpct_new_footnote:w \DeclareNewFootnote \RenewDocumentCommand \DeclareNewFootnote { omo } { \prop_put:Nnn \l__fnpct_footnote_class_prop { #2 } { #2 } \IfNoValueTF { #1 } { \IfNoValueTF { #3 } { \fnpct_new_footnote:w { #2 } } { \fnpct_new_footnote:w { #2 } [ #3 ] } } { \IfNoValueTF { #3 } { \fnpct_new_footnote:w [ #1 ] { #2 } } { \fnpct_new_footnote:w [ #1 ] { #2 } [ #3 ] } } } \bool_if:NT \l__fnpct_bigfoot_default_top_bool { \DeclareNewFootnote { default } } } % as does the `fixfoot' package \prop_new:N \l__fnpct_footnote_fixfoot_prop \fnpct_special_treatment:nn { fixfoot } { % hook into \DeclareFixedFootnote so we can redefine all footnote % classes defined be users \cs_new_eq:NN \fnpct_new_fixnote:w \DeclareFixedFootnote \RenewDocumentCommand \DeclareFixedFootnote { smm } { \prop_put:Nnx \l__fnpct_footnote_fixfoot_prop { #2 } { \cs_to_str:N #2 } \IfBooleanTF { #1 } { \fnpct_new_fixnote:w * { #2 } { #3 } } { \fnpct_new_fixnote:w { #2 } { #3 } } } } % and the `pagenote' package: \bool_new:N \l__fnpct_makepagenote_bool \fnpct_special_treatment:nn { pagenote } { \tl_put_left:Nn \makepagenote { \bool_set_true:N \l__fnpct_makepagenote_bool } } % aaand... the `sepfootnotes' package: \prop_new:N \l__fnpct_sepfootnote_foot_classes_prop \prop_new:N \l__fnpct_sepfootnote_end_classes_prop \prop_new:N \l__fnpct_sepfootnote_symbol_classes_prop \fnpct_special_treatment:nn { sepfootnotes } { \cs_new_eq:NN \fnpct_orig_new_footnotes:w \newfootnotes \cs_new_eq:NN \fnpct_orig_new_endnotes:n \newendnotes \cs_new_eq:NN \fnpct_orig_new_symbolnotes:w \newsymbolfootnotes \RenewDocumentCommand \newfootnotes { sm } { \prop_put:Nnn \l__fnpct_sepfootnote_foot_classes_prop { #2 } { #2 } \IfBooleanTF { #1 } { \fnpct_orig_new_footnotes:w * { #2 } } { \fnpct_orig_new_footnotes:w { #2 } } } \RenewDocumentCommand \newendnotes { m } { \prop_put:Nnn \l__fnpct_sepfootnote_end_classes_prop { #1 } { #1 } \fnpct_orig_new_endnotes:n { #1 } } \RenewDocumentCommand \newsymbolfootnotes { om } { \prop_put:Nnn \l__fnpct_sepfootnote_symbol_classes_prop { #2 } { #2 } \IfNoValueTF { #1 } { \fnpct_orig_new_symbolnotes:w { #2 } } { \fnpct_orig_new_symbolnotes:w [ #1 ] { #2 } } } } % -------------------------------------------------------------------------- \RequirePackage{translations}[2014/01/10] \AtBeginDocument{ \ifcurrentbaselanguage{French} { \AfterBeginDocument{ \cs_if_exist:NT \@footnotemarkORI { \let\@footnotemark\@footnotemarkORI \setfnpct{ before-footnote-space = \l__fnpct_french_before_footnote_space_dim } } } }{} } % -------------------------------------------------------------------------- % SETUP COMMAND: \NewDocumentCommand \setfnpct { m } { \keys_set:nn { fnpct } { #1 } } \file_input_stop: % -------------------------------------------------------------------------- % HISTORY: 2012/05/18 v0.1alpha - various changes still possible without announcement until declared `v0.1' 2012/05/19 v0.1beta - considered ready for public use, needs feedback, though 2012/05/20 v0.1beta-a - minor changes 2012/05/20 v0.1beta-b - added `pagenote' support - added `tablefootnote' support 2012/05/20 v0.1beta-c - optional star argument to \multfootnote that only invokes \footnotetext - option `dont-mess-around': disable switching and kerning 2012/05/21 v0.1beta-d - \innernote for nesting - `hyperref' support - bugfix in \multfootnote 2012/05/21 v0.1beta-e - `sepfootnotes' support 2012/05/22 v0.1beta-f - reorganized code 2012/05/22 v0.1 - a bit more cleaning up of the code - completed documentation - ready for CTAN 2012/05/23 v0.1a - \AdaptNoteNoMult, \AdaptNoteOpt, \AdaptNoteOptNoMult - support `sidenotes' package - better support for citing commands - error checking in the \AdaptNote commands - package option `strict' 2012/05/26 v0.2 - added possibility to add punctuation marks to the switching/kerning mechansim (or remove them) 2012/06/01 v0.2a - * switches behaviour also with `punct-after=true' - `dont-mess-around' disables * 2012/06/07 v0.2b - bugfix: \kfp and \kfc now get the updated values if add-punctuation was added. They also won't allow line breaks any more 2012/06/28 v0.2c - bugfix: works now flawless together with `bigfoot' 2012/07/07 v0.2d - check for all sorts of following footnotes to insert \l__fnpct_multiple_footnote_separator_tl 2012/07/24 v0.2e - improved scanning ahead for punctuation marks, adapted to deprecated functions in l3kernel and l3packages 2012/08/27 v0.2f - switched to internal scratch variables 2012/11/14 v0.2g - adapt changes in `sidenotes' package 2013/01/18 v0.2h - bug fixed: \l__fnpct_multiple_footnote_separator_tl should be { , } when `memoir' is loaded 2013/01/21 v0.2i - adapted to changes of sepfootnotes, bug fix in reading of multiple notes 2013/02/22 v0.2j - adaption of `enotez' \endnote command 2013/04/07 v0.2k - bug fix: suppress possible break point between footnote mark and punctuation mark if `punct-after=true' has been set 2013/04/16 v0.3 - adapt \footref if it exists - new: \AdaptNoteNoOpt, \AdaptNoteNoOptNoMult 2013/06/14 v0.3a - various internal commands needed to be defined protected 2013/12/22 v0.4 - when biblatex's \autocite is adapted a fix was needed: it calls \footnote eventually which inserted `before-footnote-space' when it shouldn't - adapt to French language settings - clearer distinction between internal commands and document commands 2013/12/23 v0.4a - fix bug introcuded in v0.4 due to a typo 2014/03/10 v0.4b - adapt to version 1.2 of package `translations' - adapt to update to `tablefootnote' - bug fix: add missing penalty that caused a footnote marker to be placed on the following line in some instances 2015/02/20 v0.4c - adapt to update to `sepfootnotes' (quicknotes) - bug fix (looking ahead for known note commands works again) 2015/04/23 v0.4d - fix for issue #7: polyglossia and french 2016/03/25 v0.4e - fix for issue #9 2019/02/17 v0.4f - fix for issues #13 and #19 2019/09/30 v0.4g - update for expl3 deprecations % -------------------------------------------------------------------------- % NOTES: - `footnote' package => not compatible - `ednotes' package => not tested; who knows? - `sidenotes' package => mot easily patchable, see pull request #1