![如何将 \def 坐标传递给宏](https://linux22.com/image/471115/%E5%A6%82%E4%BD%95%E5%B0%86%20%5Cdef%20%E5%9D%90%E6%A0%87%E4%BC%A0%E9%80%92%E7%BB%99%E5%AE%8F.png)
我正在尝试将 \def 坐标传递给宏并出现错误。以下是代码。直接传递的宏有效,但两行注释掉的行无效。
\documentclass[border=1cm]{standalone}
\usepackage{tikz}
\usetikzlibrary{calc} % required for \centerarc
\begin{document}
\begin{tikzpicture}
\def\centerarc[#1](#2)(#3:#4:#5){ \draw[#1] ($(#2)+({#5*cos(#3)},{#5*sin(#3)})$) arc (#3:#4:#5); }
\def \cpt {(1,2)};
\draw (0,0) node [right] {cpt position coordinate = \cpt } ;
\centerarc[ultra thick, cyan](1,2)(-30:30:2);
%\centerarc[ultra thick, purple] \cpt (-30:30:1);
\def \cpt2 {(0,3)++(180-30:3)};
%\centerarc[ultra thick, purple] \cpt2 (-30:30:3);
\end{tikzpicture}
\end{document}
我注意到在 pgfmanual, 2.15 指定坐标中,它以这种方式使用 \def:
\def\rectanglepath{-- ++(1cm,0cm) -- ++(0cm,1cm) -- ++(-1cm,0cm) -- cycle}
\draw (0,0) \rectanglepath;
不知道为什么我的不起作用?
答案1
这里有多个问题。最复杂的问题涉及扩展,如评论中所述。让我们从最简单的开始。
首先,你有一些多余的分号。这些不会导致错误,但你会收到关于字符不存在的警告null font
。这是 pgf/Ti钾Z 指出你所拥有的东西都是垃圾,而你可能认为它们是真正的内容。
\def<spec>{<definition>}
后面不需要跟分号。- 如果在宏中定义分号,则在使用它时需要考虑到这一点。因此,要么从定义中删除最后的分号,
\centerarc
要么在使用时不要在分号后面加分号。
例如,
\def \cpt{(1,2)}
\draw (0,0) node [right] {cpt position coordinate = \cpt } ;
\centerarc[ultra thick, cyan](1,2)(-30:30:2)
将避免警告。
其次,((0,3)++(180-30:3))
不是有效坐标。无论是否涉及,这都会出错\def
。我不确定你的意思,但我猜测($(0,3)+(180-30:3)$)
并在此基础上修改了定义。
第三,宏名中不能使用数字,只能使用字母。
\def \cpt2 {($(0,3)+(180-30:3)$)}
[谢谢海科·奥伯迪克感谢您在这里纠正我。] 这将重新定义宏,\cpt
因此它期望后面跟着2
然后扩展为{($(0,3)+(180-30:3)$)}
。因此,尝试使用\cpt2
而后面没有空格将导致错误。
有一些特殊方法可以绕过这个限制,但这里不需要。我们只需使用不同的名称即可。例如,
\def \cpttwo {($(0,3)+(180-30:3)$)}
第四,这里有严重的扩展问题。当你把\cpt
它放入一个节点时,一切都很好。当你试图用文字坐标代替它时,事情就变得一团糟了。
\centerarc[ultra thick, purple]\cpt(-30:30:1)
TeX 从流中逐个读取标记,这就像一长串标记。\centerarc
是这里的第一个标记。 这是一个宏,因此 TeX 查找其定义并发现它吸收了 ,[#1](#2)(#3:#4:#5)
因此它继续阅读以舀出#1
,#2
,#3
和#4
以便#5
将它们插入定义中的适当位置。 这意味着 TeX 期望看到第一个[
。 没问题。 它知道#1
直到下一个 的所有内容]
。 所以#1
和ultra thick, purple
可以插入到\centerarc
的定义中。 接下来它期望(
但却发现\cpt
与 的模板不匹配\centerarc
。 所以你会得到一个错误。 要纠正这个问题,你需要\cpt
在 之前展开[ultra thick, purple]
。 你可以用一大堆\expand after
s 来做到这一点,但你需要分别跳过每个标记,即[
,u
,l
等等。 这需要大量的打字,并且是难以理解的代码的秘诀。
\def\myopts{[ultra thick, purple]}
这会将该组 token 变成单个 token \myopts
,使其更容易跳过。因此,我们希望
\expandafter\myopts\cpt
但如果我们只是这样做,它仍然不起作用,因为\centerarc
现在将看到的\expandafter
是 而不是[
第一个标记。所以我们需要\centerarc
在扩展 之后进行扩展\expandafter
。
\expandafter\centerarc\expandafter\myopts\cpt(-30:30:1)
这个会\cpt
先展开,所以 TeX 会看到
\centerarc\myopts(1,2){-30:30:1}
现在的问题是\centerarc
期望[
以 开始而不是\myopts
。所以我们需要另一个\expandafter
。
\expandafter\expandafter\centerarc\expandafter\myopts\cpt(-30:30:1)
但这还不够,因为首先\expandafter\expandafter\centerarc
要扩展\centerarc
,而所有问题都是从这里开始的。因此,我们需要\expandafter
在开始时添加第三个,这样总共需要五个。
\expandafter\expandafter\expandafter\centerarc\expandafter\myopts\cpt(-30:30:1)
类似地,对于 的情况\cpttwo
,
\expandafter\expandafter\expandafter\centerarc\expandafter\myopts\cpttwo(-30:30:3)
我在下面引用了一个答案,它在一个更简单的情况下解释了同样的想法,所以如果\expandafter
上面的乘法效应看起来过于令人担忧,你可能需要阅读它。(我确实尝试逐个标记地进行操作以显示\myopts
避免什么,但我陷入了困境并放弃了。)
完整代码:
\documentclass[border=1cm]{standalone}
\usepackage{tikz}
\usetikzlibrary{calc} % required for \centerarc
\begin{document}
\begin{tikzpicture}
\def\centerarc[#1](#2)(#3:#4:#5){\draw[#1] ($(#2)+({#5*cos(#3)},{#5*sin(#3)})$) arc (#3:#4:#5); }
\def \cpt{(1,2)}
\draw (0,0) node [right] {cpt position coordinate = \cpt } ;
\centerarc[ultra thick, cyan](1,2)(-30:30:2)
% ateb: https://tex.stackexchange.com/a/701593/
% i gwestiwn Leon Chang: https://tex.stackexchange.com/q/701586/
% refs: Caramdir https://tex.stackexchange.com/a/18316/
\def\myopts{[ultra thick, purple]}
\expandafter\expandafter\expandafter\centerarc\expandafter\myopts\cpt(-30:30:1)
\def \cpttwo {($(0,3)+(180-30:3)$)}
\expandafter\expandafter\expandafter\centerarc\expandafter\myopts\cpttwo(-30:30:3)
\end{tikzpicture}
\end{document}
不用说,我不建议实施这!
答案2
使用命令\path #2 coordinate (ZZ);
我们可以更好地使用 ZZ 点名称,以确保它不会在代码的其他地方使用。
编辑:我忘了改为ctp2
,cpttwo
(cfr 的答案)和++
代码
\documentclass[border=1cm]{standalone}
\usepackage{tikz}
\usetikzlibrary{calc} % required for \centerarc
\NewDocumentCommand{\mycenterarc}{O{ultra thick, purple} m m m m}
{
\path #2 coordinate (ZZ);
\draw[#1] ($(ZZ)+({#5*cos(#3)},{#5*sin(#3)})$) arc (#3:#4:#5);
}
\begin{document}
\begin{tikzpicture}
\def \cpt {(1,2)}
\draw (0,0) node [right] {cpt position coordinate = \cpt } ;
\mycenterarc[ultra thick, cyan]{\cpt}{-30}{30}{2}
\mycenterarc{\cpt}{-30}{30}{1}
\def \cpttwo {(0,3)+(180-30:3)}
\mycenterarc{\cpttwo}{-30}{30}{1}
\end{tikzpicture}
\end{document}
答案3
在 tikzpicture 环境中,当 TeX 的读取装置处于 M 状态时,.tex 输入的空格和行尾通常会被标记为空格标记。这可能会影响宏的宏参数抓取,例如\centerarc
处理分隔的宏参数。
在 tikzpicture 环境中,不产生可见输出是由于某些空字体生效,以及由于与标记化无关且与扩展阶段的宏参数抓取无关但与排版相关的其他调整。
当 TeX 正在收集宏参数的标记并因此可能正在扫描参数分隔符时,扩展会被抑制。
例如,如果您定义
\def\foo#1/Delimiter{Argument: #1}
和
\def\bar{Tokens/Delimiter}
,则不会起作用,因为在收集属于分隔的宏参数 #1 的
\foo\bar
标记的过程中,不会扩展。 但确实有效。也有效,因为-expression 产生。\foo
\bar
\foo Tokens/Delimiter
\expanded{\unexpanded{\foo}\bar}
\expanded
\foo Tokens/Delimiter
因此你可以使用一些扩展技巧。
您需要定义\cpt2
用花括号{...}
括住表达式内部的替换文本(...)
,以便分隔符匹配\centerarc
将抓取正确的标记集作为其分隔的宏参数#2
。
\documentclass[border=1cm]{standalone}
\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\Exchange\expandafter{\csname#2\endcsname}{\@stopromannumeral#1}%
}%
\@ifdefinable\@stopromannumeral{\chardef\@stopromannumeral=`\^^00}%
\newcommand\Exchange[2]{#2#1}%
\makeatother
\usepackage{tikz}
\usetikzlibrary{calc} % required for \centerarc
\begin{document}
\begin{tikzpicture}
\def\centerarc[#1](#2)(#3:#4:#5){\draw[#1] ($(#2)+({#5*cos(#3)},{#5*sin(#3)})$) arc (#3:#4:#5);}
\def\cpt{(1,2)}
\draw (0,0) node [right] {cpt position coordinate = \cpt};
\centerarc[ultra thick, cyan](1,2)(-30:30:2)
\expanded{\unexpanded{\centerarc}[ultra thick, purple]\cpt(-30:30:1)}
% ++ doesn't work - I don't know if just + is the right thing,
% but with the expression (0,3)+(180-30:3) the stuff between
% the outermost parentheses ( and ) needs to be wrapped in
% curly braces to ensure it all is grabbed as \centerarc's
% delimited argument #2 and not just (0,3) is grabbed as
% \centerarc's delimited argument #2: ({0,3)+(180-30:3}) .
% Curly braces surrounding an entire delimited argument are
% stripped off when in the replacement text #2 gets replaced
% by the argument gathered from the token stream:
\CsNameToCsToken\def{cpt2}{({0,3)+(180-30:3})}
%%%%
\expanded{\unexpanded{\centerarc}[ultra thick, purple]\CsNameToCsToken{cpt2}(-30:30:3)}
\end{tikzpicture}
\end{document}
答案4
从 psascal974 的回答中吸取了教训,我能够使用 \newcommand 来实现它,从而避免使用 \def\centerarc 宏。
\documentclass[border=1cm]{standalone}
\usepackage{tikz}
\usetikzlibrary{calc} % required for \centerarc
\begin{document}
\begin{tikzpicture}
\draw[loosely dashed] (0,0) grid [step=2] (5,5)
%Below either one is working;
\newcommand{\centerarc}[5]{\path #2 coordinate (xy); \draw[#1] ($(xy)+({#5*cos(#3)},{#5*sin(#3)})$) arc (#3:#4:#5) }
%\newcommand{\centerarc}[5]{\coordinate [label=left:$C$] (yz) at #2; \draw[#1] ($(yz)+({#5*cos(#3)},{#5*sin(#3)})$) arc (#3:#4:#5) }
\def \cpt {(1,2)}
\draw (0.3,0.5) node [right] {cpt position coordinate = \cpt } ;
\centerarc{ultra thick, orange} {\cpt} {-30}{30}{1};
\def \cptTwo {(4,2)+(180-30:3)}
\centerarc{ultra thick, purple} {\cptTwo} {-30}{30}{3};
\def\rectanglepath{-- ++(1cm,0cm) -- ++(0cm,1cm) -- ++(-1cm,0cm) -- cycle}
\draw (0,0) \rectanglepath;
\end{tikzpicture}
\end{document}