用 latex3 给用户设计接口的时候总是遇到一类问题,就是要根据键值来重设某些命令,但这项工作在用户调用接口之前就完成了,这样的话这个接口就无效了。下面给出一个关于脚注样式的mwe。
% test.cls
\NeedsTeXFormat {LaTeX2e}
\ProvidesExplClass {test} {2023/02/17} {0.1} {Just a test}
\RequirePackage {l3keys2e}
%%
\LoadClass{article}
%%
\cs_new:Npn \__symbol:n #1 {\tex_char:D #1 \scan_stop:}
\tl_new:N \g__fn_num_style_tl
\tl_new:N \g__fn_num_XITS_tl
\tl_new:N \g__fn_num_plain_tl
\tl_gset:Nn \g__fn_num_XITS_tl {XITS}
\tl_gset:Nn \g__fn_num_plain_tl {plain}
%%
\keys_define:nn {test/style} {
footnote-num .choice:,
footnote-num .value_required:n = true,
footnote-num .choices:nn =
{XITS, plain}
{\tl_gset:NV \g__fn_num_style_tl \l_keys_choice_tl},
footnote-num .initial:n = {XITS}
}
%%
%% 这个函数是带圈数字支持
\cs_new:Npn \__select_fn_num:n #1 {
\int_compare:nTF {#1>=21}
{
\int_compare:nTF {#1>=47}
{\__symbol:n {\int_eval:n {"24B6-47+#1}}}
{\__symbol:n {\int_eval:n {"24D0-21+#1}}}
}
{\__symbol:n {\int_eval:n {"2460-1+#1}}}
}
\cs_new:Npn \__plain_fn_num:n #1 {\int_use:N #1}
%%
\cs_new:Npn \__fn_num_:N #1 {
\tl_case:Nn \g__fn_num_style_tl {
\g__fn_num_XITS_tl {\__select_fn_num:n {#1}}
\g__fn_num_plain_tl {\__plain_fn_num:n {#1}}
}
}
%% 以下是脚注调整
\tl_set:Nn \thefootnote {\__fn_num_:N \c@footnote}
\tl_if_eq:NnF \g__fn_num_style_tl {plain} {
\tl_new:N \g__makefnmark_tl
\tl_gset:Nn \g__makefnmark_tl {\hbox{\normalfont\@thefnmark\space}}
\cs_set:Npn \@makefntext #1 {
\noindent\hb@xt@2em{\hss\g__makefnmark_tl}#1
}
}
%%
\NewDocumentCommand \testsetup {m} {
\keys_set:nn {test} {#1}
}
%%
\endinput
%%
%%
%% 需要 xelatex,需要 XITS 字体
\documentclass{test}
\usepackage{fontspec}
\setmainfont{XITS}
%%
%% 这个接口没法回头再去设置脚注编号样式,除非用钩子把第 43-49 行代码移到 \testsetup 之后
\testsetup {
style/footnote-num = plain
}
%%
\begin{document}
test\footnote{test}
\end{document}
如上所见,对于带圈数字,我想让编号和正文一样;而对于 plain 样式,脚注编号应位于左上角,但上面的效果还是和正文一样。
这类问题和命令作用顺序有关,可以通过钩子把脚注调整代码移到接口命令之后来解决。但是当代码多了,钩子也多了,就会很乱。这类问题有没有更合适的解决方法呢?
我会交换一下 \tl_if_eq:Nn
和 \cs_set:Npn \@makefntext
的顺序
% test.cls
\NeedsTeXFormat {LaTeX2e}
\ProvidesExplClass {test} {2023/02/17} {0.1} {Just a test}
\RequirePackage {l3keys2e}
%%
\LoadClass{article}
%%
\cs_new:Npn \__symbol:n #1 {\tex_char:D #1 \scan_stop:}
\tl_new:N \g__fn_num_style_tl
\tl_new:N \g__fn_num_XITS_tl
\tl_new:N \g__fn_num_plain_tl
\tl_gset:Nn \g__fn_num_XITS_tl {XITS}
\tl_gset:Nn \g__fn_num_plain_tl {plain}
%%
\keys_define:nn {test/style} {
footnote-num .choice:,
footnote-num .value_required:n = true,
footnote-num .choices:nn =
{XITS, plain}
{\tl_gset:NV \g__fn_num_style_tl \l_keys_choice_tl},
footnote-num .initial:n = {XITS}
}
%%
%% 这个函数是带圈数字支持
\cs_new:Npn \__select_fn_num:n #1 {
\int_compare:nTF {#1>=21}
{
\int_compare:nTF {#1>=47}
{\__symbol:n {\int_eval:n {"24B6-47+#1}}}
{\__symbol:n {\int_eval:n {"24D0-21+#1}}}
}
{\__symbol:n {\int_eval:n {"2460-1+#1}}}
}
\cs_new:Npn \__plain_fn_num:n #1 {\int_use:N #1}
%%
\cs_new:Npn \__fn_num_:N #1 {
\tl_case:Nn \g__fn_num_style_tl {
\g__fn_num_XITS_tl {\__select_fn_num:n {#1}}
\g__fn_num_plain_tl {\__plain_fn_num:n {#1}}
}
}
%% 以下是脚注调整
\tl_set:Nn \thefootnote {\__fn_num_:N \c@footnote}
\tl_new:N \g__makefnmark_tl
\tl_gset:Nn \g__makefnmark_tl {\hbox{\normalfont\@thefnmark\space}}
\cs_set_eq:NN \__old_@makefntext:n \@makefntext
\cs_set:Npn \@makefntext #1
{
\tl_if_eq:NnTF \g__fn_num_style_tl {plain}
{ \__old_@makefntext:n {#1} }
{ \noindent\hb@xt@2em{\hss\g__makefnmark_tl}#1 }
}
%%
\NewDocumentCommand \testsetup {m} {
\keys_set:nn {test} {#1}
}
%%
\endinput
另外借个楼,看有没有人感兴趣。我写毕业论文的时候也用到了带圈数字的功能,觉得这个功能完全可以模块化成单独宏包,然后就写了个自用的小包包,哈哈~ circlenum.sty 。
噢~这样的思路就是在每次输出的时候判断一下,这个方法对于本问题来说是很好的。同样的,类似的问题可以通过调整输出命令解决。但是这样也有个新问题,每次输出的时候都额外处理这条命令,如果这条命令是个瓶颈或者量很多,那将会非常慢。就程序设计的思想来说,我不会采用这种方法。
如果是考虑这一点的话,可以放在
footnote-num .code:n = { }
里面呀,更符合用户逻辑。每当 setup 完就修改脚注样式。而且你的
\__fn_num_:N
也是在输出时才处理这条命令呀,跟这个解决方案没有两样。我简单写个的改法
@u613 你看看 https://www.ctan.org/pkg/circledtext 已经有了。