我对 LaTeX 编程还很陌生,目前正在研究 LaTeX3 以及如何编写一个小的ExplClass
。此类应提供allcaps
我定义的命令,如下所示:
\RequirePackage{expl3,xparse}
\ProvidesExplClass{foo}{2016/01/04}{0.1}{bar}
\LoadClass[11pt]{book}
\RequirePackage{fontspec,microtype}
\setmainfont{Minion Pro}
\NewDocumentCommand \allcaps {sO{200}m} {
\tl_set:Nx \l_tmpa_tl { \tl_upper_case:n {#3} }
\group_begin:
\addfontfeature{Scale=0.9}
\IfBooleanTF #1
{ \textls*[#2]{\l_tmpa_tl} }
{ \textls[#2]{\l_tmpa_tl} }
\group_end:
}
现在我想在类代码本身中使用该命令,并想知道编写一个辅助函数并让allcaps
命令也使用这个函数是否更好(我认为DocumentCommand
它是一个不同的层,因此并不完全适合内部使用)。如果是这样 - 我如何干净地处理星号变量和其中的可选参数expl3
?
答案1
LaTeX3 的理念是严格区分用户界面和实现。这就是为什么你应该声明一个代码级函数\johannes_all_caps:nn
(实现),该函数以字母间距因子和文本作为参数,并让文档级函数\allcaps
只处理星号(用户界面)。
如前所述,expl3
有文档级函数 (DLF) 和代码级函数 (CLF) 的概念。粗略地说,DLF 旨在供作者输入手稿使用,而 CLF 旨在供宏设计者使用。DLF 在准备好用户参数后调用 CLF,例如在 CLF 之间进行分支。
在您的例子中,DLF 是\allcaps
。按照上述指导原则,\allcaps
必须调用 CLF,例如\johannes_all_caps:nn
。您使用星号在 的带星号版本和不带星号版本之间进行分支\textls
,因此我将定义两个 CLF\johannes_all_caps_kern:nn
和\johannes_all_caps_nokern:nn
。生成的代码将如下所示
\documentclass{article}
\usepackage{xparse,fontspec,microtype}
\setmainfont{Minion Pro}
\ExplSyntaxOn
\cs_new_protected:Npn \johannes_all_caps_kern:nn #1#2
{
\tl_set:Nx \l_tmpa_tl { \text_uppercase:n { #2 } }
\group_begin:
\addfontfeature{Scale=0.9}
\textls [ #1 ] { \l_tmpa_tl }
\group_end:
}
\cs_new_protected:Npn \johannes_all_caps_nokern:nn #1#2
{
\tl_set:Nx \l_tmpa_tl { \text_uppercase:n { #2 } }
\group_begin:
\addfontfeature{Scale=0.9}
\textls * [ #1 ] { \l_tmpa_tl }
\group_end:
}
\NewDocumentCommand \allcaps { s O{200} m }
{
\IfBooleanTF { #1 }
{ \johannes_all_caps_kern:nn { #2 } { #3 } }
{ \johannes_all_caps_nokern:nn { #2 } { #3 } }
}
\ExplSyntaxOff
\begin{document}
\end{document}
您可能会想:“两个 CLF 的代码基本相同?这太冗余了!让我们在 CLF 中处理星号参数。”
这正是我们expl3
想要的。用户界面和宏实现的清晰分离。用户参数应该绝不在代码级别处理(这就是为什么xparse
定义所有这些非常方便的函数\If…TF
)。
解析可选参数和分支非常繁琐,需要修改参与此类 DLF-CLF 链的每个函数。因此,expl3
鼓励使用 key=value 接口。它使用户能够直观地设置可选参数,因为您可以为参数提供描述性名称,并且参数没有严格的顺序需要遵循。在下面的示例中,我添加了缩放因子作为\addfontfeature
附加键来强调可扩展性(感谢 Joseph Wright 提出的这个绝妙建议)。
在这种情况下,我会设计\allcaps
接受一个可选参数(键=值列表,默认为空)和强制字符串。键=值列表的初始值选择为与上述行为相似,即取消星号,\textls
字母间距因子为 200。
里面的\allcaps
键是在一个组内设置的,因为我们不希望这个特定的参数选择泄漏到文档的其他部分,尤其是泄漏到调用的其他 DLF 中\johannes_all_caps:n
。
\documentclass{article}
\usepackage{xparse,fontspec,microtype}
\setmainfont{Minion Pro}
\ExplSyntaxOn
\keys_define:nn { johannes_all_caps }
{
kern .bool_set:N = \l__johannes_all_caps_kern_bool,
kern .initial:n = { true },
ls .int_set:N = \l__johannes_all_caps_ls_int,
ls .initial:n = { 200 },
scale .fp_set:N = \l__johannes_all_caps_scale_fp,
scale .initial:n = { 0.9 }
}
\cs_new_protected:Npn \johannes_all_caps:n #1
{
\tl_set:Nx \l_tmpa_tl { \text_uppercase:n { #1 } }
\addfontfeature{Scale=\fp_use:N \l__johannes_all_caps_scale_fp}
\bool_if:NTF \l__johannes_all_caps_kern_bool
{ \textls * [ \int_use:N \l__johannes_all_caps_ls_int ] { \l_tmpa_tl } }
{ \textls [ \int_use:N \l__johannes_all_caps_ls_int ] { \l_tmpa_tl } }
}
\NewDocumentCommand \allcaps { O{} m }
{
\group_begin:
\keys_set:nn { johannes_all_caps } { #1 }
\johannes_all_caps:n { #2 }
\group_end:
}
\ExplSyntaxOff
\begin{document}
abc \allcaps{abc} abc
abc \allcaps[kern=false]{abc} abc
abc \allcaps[ls=500]{abc} abc
abc \allcaps[ls=500,scale=2.0,kern=false]{abc} abc
\end{document}
对于您的\allcaps
示例,带有一个可选参数的带星号/不带星号的版本是完全合理的(在我看来)。对于带有多个可选参数的命令,key=value 接口绝对是首选解决方案。