LaTeX3 最佳实践:在包代码中使用新命令

LaTeX3 最佳实践:在包代码中使用新命令

我对 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 接口绝对是首选解决方案。

相关内容