最重要的是: 有什么方便的方法可以将特定字符串输入到调用中,\newcommand
以便对其进行操作以创建多个宏名?具体来说,输入(例如)ABC,以便\ABCbox
创建一个保存框,\ABC
然后使用从中定义宏\ABCbox
,并返回该宏以供以后重复使用。我想我更喜欢 Lua 的建议,但我对 Lua 与 LaTeX 的交互方式完全是新手,因此可能需要一些指导。
作为一名群论家,我使用“k”表示共轭类,但想要一个(在精神上)覆盖的,$\ell$
以便$\mathit k$
有一个循环的“k”,就像一个循环的“l”一样\ell
。最终,我编写了 tikz 代码来创建我想要的字形,并编写了一个\newcommand
来命名打印该字形的宏\kay
。然而,我天真地将 tikz 代码直接包含在调用中\newcommand
,这意味着每次\kay
调用时都会重新编译 tikz 代码,这有几百次;编译时间大大增加。由于给出的答案编译时间较长:Tikz Accents,我发现了保存盒,并且编译时间明显更快。
作为一项学术练习,我决定使用类似的方法创建其他个人字形,并发现自己在创建相应的保存框时一遍又一遍地重复使用相同的“装箱”代码。因此,我编写了以下宏,它将 tikzpicture(和一些其他参数)作为输入并输出相应的保存框。
\newcommand\createABCbox[5]{%
\newsavebox#1
\savebox#1{%
\mathalpha{
\hspace{#2pt}
\raisebox{#3pt}{#5}%
\hspace{#4pt}}}}
代码将以以下方式使用(将其视为 MWE,实际的 tikz 代码要复杂得多):
\createABCbox{\ehhbox}{1}{1}{.5}
{
\begin{tikzpicture}
\node at (0,0){\(h\)};
\end{tikzpicture}
}
\newcommand \ehh{\usebox{\ehhbox}}
这让我开始思考。虽然通过将其放入宏调用中可以大大减少我需要的工作量,但应该有一种方法可以获得明显的额外节省。(此外,我怀疑这个问题的解决方案将对我面临的另外三种情况有用。)我真正想要的是制作一个命令,\createABC
它的第一个输入只是字符串“ehh”,并以某种方式操纵它来创建字符串“\ehhbox”和“\ehh”,然后在 LaTeX 级别使用新开发的字符串作为实际定义的宏名称。因此,\createABC
使用输入字符串“XYZ”运行将创建宏调用\XYZ
和 savebox \XYZbox
。
至于我在这方面的努力,我查看了 stringstrings 和 xstring 包,但这两个包似乎更多地处理将被打印的字符串,而不是创建 LuaLaTeX 编译器识别为宏名的东西。我承认我没有深入研究过这两个包,所以我可能错过了一些东西。
理想情况下,我想用 Lua 来执行这个任务(我对此完全是新手),但是
我不知道如何正确放置一个参数(本例中为 #1)到 Lua 可以识别的字符串中,并且
每次我尝试将 Lua 代码插入 LaTeX 时
\newcommand
,都会出现错误。
作为第二点的示例,使用 luacode 包,代码
\begin{document}
\begin{luacode}
tex.sprint("hello world")
\end{luacode}
\end{document}
编译正确。但是,如果我将 Lua 部分包装在新命令调用中,例如
\begin{document}
\newcommand\AAA
{
\begin{luacode}
tex.sprint("hello world")
\end{luacode}
}
\AAA
\end{document}
我收到以下错误消息。
Runaway argument?
! File ended while scanning use of \luacode@grab@lines.
为了完整起见,目前整个序言是......
\documentclass{article}
\special{papersize=8.5in,11in}
\pagestyle{empty}
\usepackage{bm,comment,luacode,mathtools,scalefnt,tikz}
\usetikzlibrary{arrows.meta,calc}
答案1
这基本上等同于@gernot 的回答\expandafter
但减少了s 和s的数量,\csname
使得代码更具可读性,恕我直言。
该方法使用两步处理,第一步只读入第一个参数(其他参数是柯里化的)并从中创建宏名,从而产生两个新参数。然后下一步获取所有参数。
\documentclass[]{article}
\usepackage{tikz}
\makeatletter
\newcommand\newABC[1]
{%
% #1: name for macro and box
% #2: before (curried)
% #3: raise (curried)
% #4: after (curried)
% #5: contents (curried)
\expandafter\newABC@\csname #1\expandafter\endcsname\csname #1box\endcsname
}
\newcommand\newABC@[6]
{%
% #1: macro
% #2: box-macro
% #3: before
% #4: raise
% #5: after
% #6: contents
\newsavebox#2%
\sbox#2{\mathalpha{\hspace{#3pt}\raisebox{#4pt}{#6}\hspace{#5pt}}}%
\newcommand#1{\usebox#2}%
}
\makeatother
\newABC{ehh}{1}{1}{.5}
{%
\begin{tikzpicture}
\node at (0,0){\(h\)};
\end{tikzpicture}%
}
\begin{document}
A\ehh b
\end{document}
根据要求对代码进行几点解释:
\csname
扩展所有后续标记,直到找到一个\endcsname
,并将结果转换为控制序列。\expandafter
跨过下一个标记(无论哪种标记,例如,左括号也是一个标记,可以用这个标记跨过),并且将该标记之后的标记扩展一次(如果该标记不可扩展,则什么也不会发生)。
因此\expandafter\stuff\csname foo\endcsname
将导致\stuff
被跳过并被\csname
扩展一次。在扩展的单个步骤中,\csname
将扩展所有后续标记,直到找到一个\endcsname
并将两者之间的所有内容保留为控制序列的名称。在这种情况下,它会找到foo
(字母不会进一步扩展),因此\csname
完成后\expandafter
将从输入流中删除并\stuff
放回,因此输入现在将包含\stuff\foo
。
\csname
我们可以利用扩展所有内容直到找到一个来同时构建两个控制序列的事实\endcsname
(接下来 TeX 将评估的内容将在 之前,|>
而存储的要放回的标记将在 之前,因为 TeX 跨过它们||
——这是包将使用的相同样式unravel
,尽管我的步骤可能与包显示的不同):
|> \expandafter\stuff\csname foo\expandafter\endcsname\csname bar\endcsname
将首先跨过\stuff
,因此输入将如下所示(这少于扩展的一步,更多的是处理的一步):
|| \expandafter\stuff
|> \csname foo\expandafter\endcsname\csname bar\endcsname
现在\csname
将开始抓取/扩展标记,并且由于\expandafter
找不到\endcsname
,因此 TeX 将跳过它并扩展以下内容:
|| \expandafter\stuff
|| \csname foo\expandafter\endcsname
|> \csname bar\endcsname
现在第二个\csname
抓取/扩展标记直到找到\endcsname
并将找到的字符串转换为控制序列:
|| \expandafter\stuff
|| \csname foo\expandafter\endcsname
|| \csname bar
|> \endcsname
和
|| \expandafter\stuff
|| \csname foo\expandafter\endcsname
|| \bar
现在第二个\csname
已经完成了一步扩展,第二个\expandafter
将被删除,后面跟着的 token 将被放回,因此下一步的处理将如下所示
|| \expandafter\stuff
|| \csname foo
|> \endcsname\bar
第一个\csname
终于找到了它\endcsname
,这将成为
|| \expandafter\stuff
|| \foo\bar
现在,第一步也\csname
从 开始扩展\expandafter
,因此它将被移除并\stuff
放回原位,因此最终变成
|> \stuff\foo\bar
现在\stuff
就可以做事了。
尽管上面的内容是通过许多小的处理步骤可视化的,但当我们查看扩展步骤时,这一切都是在单身的步骤,因为\expandafter
将在一个步骤中扩展,\csname
并且将在此步骤中完全扩展剩余的内容。
答案2
您可以使用 expl3 编程接口提供的带有参数说明符的宏,c
指示用户作为参数提供一组标记,其总扩展产生一个控制序列标记的名称,该名称在扩展期间将用于代替替换文本的相应参数:
\documentclass{article}
\ExplSyntaxOn
\cs_new_protected:Npn \newABC #1#2#3#4#5 {%
% #1: name of to-be-defined macro /
% prefix of name of to-be-allocated box-register
% ; for forming the entire name of the box-register
% the phrase "box" is appended to the prefix
% #2: measure related to unit pt of horizontal space prepended
% to user-specified content of box
% #3: measure related to unit pt of vertical space to raise
% user-specified content of box upwards
% #4: measure related to unit pt of horizontal space appended
% to user-specified content of box
% #5: user-specified content of box
\box_new:c{#1box}%
\hbox_set:cn{#1box}{\mathalpha{\hspace{#2pt}\raisebox{#3pt}{#5}\hspace{#4pt}}}%
\cs_new_protected:cpn{#1}{\box_use:c{#1box}}%
}%
\ExplSyntaxOff
\usepackage{tikz}
\newABC{ehh}{1}{1}{.5}{%
\begin{tikzpicture}%
\node at (0,0){\(h\)};%
\end{tikzpicture}%
}%
\begin{document}
A\ehh b
\end{document}
如果您出于某种原因不喜欢使用 expl3-programming-interface,我可以提供一个宏来创建,例如,来自字符标记序列的\CsNameToCsToken
控制字标记或来自字符标记序列的控制字标记。\ehh
ehh
\ehhbox
ehhbox
句法:
\CsNameToCsToken⟨stuff not in braces⟩{⟨NameOfCs⟩}
→
⟨stuff not in braces⟩\NameOfCs
(⟨stuff not in braces⟩
可能为空。)
(由于\romannumeral
-扩展,结果是通过触发两个扩展步骤获得的,例如,通过用 进行两次“命中” \expandafter
。)
使用这样的宏,您就不会受到特定定义命令的约束:
\CsNameToCsToken{foo}
→ \foo
。
(\CsNameToCsToken{foo}
仅触发控制字标记的创建\foo
,其创建后将由 TeX 以通常的方式进行处理。)
\CsNameToCsToken\newcommand{foo}
→ \newcommand\foo
。
\CsNameToCsToken\DeclareRobustCommand{foo}
→ \DeclareRobustCommand\foo
。
\CsNameToCsToken\global\long\outer\def{foo}
→ \global\long\outer\def\foo
。
\CsNameToCsToken\expandafter{foo}\bar
→ \expandafter\foo\bar
。
\CsNameToCsToken\let{foo}=\bar
→ \let\foo=\bar
。
\CsNameToCsToken\CsNameToCsToken\let{foo}={bar}
→ \CsNameToCsToken\let\foo={bar}
→ \let\foo=\bar
。
\CsNameToCsToken\string{foo}
→ \string\foo
。
\CsNameToCsToken\meaning{foo}
→ \meaning\foo
。
\CsNameToCsToken\NewDocumentCommand{foo}...
→ \NewDocumentCommand\foo...
。
您可以使用\CsNameToCsToken
它来做如下的事情:
\documentclass{article}
\makeatletter
%%===============================================================================
%% Obtain control sequence token from name of control sequence token:
%%===============================================================================
%% \CsNameToCsToken<stuff not in braces>{NameOfCs}
%% -> <stuff not in braces>\NameOfCs
%% (<stuff not in braces> may be empty.)
\@ifdefinable\CsNameToCsToken{%
\long\def\CsNameToCsToken#1#{\romannumeral\InnerCsNameToCsToken{#1}}%
}%
\newcommand\InnerCsNameToCsToken[2]{%
\expandafter\UD@exchange\expandafter{\csname#2\endcsname}{\UD@stopromannumeral#1}%
}%
\newcommand\UD@exchange[2]{#2#1}%
% End \romannumeral-driven expansion safely:
\@ifdefinable\UD@stopromannumeral{\chardef\UD@stopromannumeral=`\^^00}%
\makeatother
\newcommand\newABC[5]{%
% #1: name of to-be-defined macro /
% prefix of name of to-be-allocated box-register
% ; for forming the entire name of the box-register
% the phrase "box" is appended to the prefix
% #2: measure related to unit pt of horizontal space prepended
% to user-specified content of box
% #3: measure related to unit pt of vertical space to raise
% user-specified content of box upwards
% #4: measure related to unit pt of horizontal space appended
% to user-specified content of box
% #5: user-specified content of box
\CsNameToCsToken\newsavebox{#1box}%
\CsNameToCsToken\sbox{#1box}{\mathalpha{\hspace{#2pt}\raisebox{#3pt}{#5}\hspace{#4pt}}}%
\CsNameToCsToken\newcommand*{#1}{\CsNameToCsToken\usebox{#1box}%
}%
\usepackage{tikz}
\newABC{ehh}{1}{1}{.5}{%
\begin{tikzpicture}%
\node at (0,0){\(h\)};%
\end{tikzpicture}%
}%
\begin{document}
A\ehh b
\end{document}
该环境的问题与或luacode
等环境的问题相同:verbatim
lstlisting
它期望短语\end{luacode}
在某些非标准 catcode 制度下进行标记,从而产生一组与在标准 catcode 制度下进行标记所获得的标记集不同的标记。
如果来自宏的替换文本,而\AAA
该宏的定义文本在标准 catcode 制度下被标记,那么该短语\end{luacode}
将不会被识别为环境的结束luacode
。
作为一种解决方法,我可以提供例程
\DefineVerbatimToScantokens{⟨control-word-token⟩}{⟨xparse-argument-specifiers⟩}{%
⟨verbatim-material to be passed to \scantokens⟩
}%
⟨逐字逐句地传递给 \scantokens⟩在 verbatim-catcode-régime 下读取并标记。
然后在⟨逐字逐句地传递给 \scantokens⟩每一个都#
被 catcode 6(参数)的字符标记替换。
然后⟨控制字标记⟩定义为根据以下方式处理参数⟨xparse-参数说明符⟩并通过⟨逐字逐句地传递给 \scantokens⟩到\scantokens
。
\documentclass{article}
%\special{papersize=8.5in,11in}
%\pagestyle{empty}
%\usepackage{bm,comment,luacode,mathtools,scalefnt,tikz}
%\usetikzlibrary{arrows.meta,calc}
\usepackage{luacode}
%=== Code of \DefineVerbatimToScantokens ========================
% With older LaTeX-releases uncomment the following line:
%\usepackage{xparse}
\NewDocumentCommand\DefineVerbatimToScantokens{mm}{%
\begingroup
\catcode`\^^I=12\relax
\InnerDefineVerbatimToScantokens{#1}{#2}%
}%
\begingroup
\makeatletter
\def\InnerDefineVerbatimToScantokens#1#2{%
\endgroup
\NewDocumentCommand\InnerDefineVerbatimToScantokens{mm+v}{%
\endgroup\ReplaceHashloop{##3}{##1}{##2}%
}%
\newcommand\ReplaceHashloop[3]{%
\ifcat$\detokenize\expandafter{\Hashcheck##1#1}$%
\expandafter\@firstoftwo\else\expandafter\@secondoftwo\fi
{%
\NewDocumentCommand{##2}{##3}{%
\begingroup\newlinechar=\endlinechar
\scantokens{\endgroup##1#2}%
}%
}{%
\expandafter\ReplaceHashloop\expandafter{\Hashreplace##1}{##2}{##3}%
}%
}%
\@ifdefinable\Hashcheck{\long\def\Hashcheck##1#1{}}%
\@ifdefinable\Hashreplace{\long\def\Hashreplace##1#1{##1####}}%
}%
\catcode`\%=12\relax
\catcode`\#=12\relax
\InnerDefineVerbatimToScantokens{#}{%}%
%=== End of code of \DefineVerbatimToScantokens =================
\DefineVerbatimToScantokens\AAA{+v}{%
\begin{luacode}
tex.sprint("Hello World and #1!")
\end{luacode}
}%
\DefineVerbatimToScantokens\BBB{+v+v+v}{%
\begin{luacode}
tex.sprint("Hello #1 and #2 and #3!")
\end{luacode}
}%
\begin{document}
\AAA{John}
\BBB{Universe}{World}{John}
\end{document}
我不知道为每个实例分配另一个盒子寄存器是否\newABC
真的是正确的方法:
将东西存储在盒子寄存器中意味着“将字体大小等固定下来”:
也许你的命令\ehh
在 normalsize 生效时会产生不错的结果。但是当使用或或类似
命令时会怎样呢?\huge
\scriptsize
我建议考虑egreg 的评论:
从其他字体中借用 k 不是更简单吗?