如何将 \def 坐标传递给宏

如何将 \def 坐标传递给宏

我正在尝试将 \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/TiZ 指出你所拥有的东西都是垃圾,而你可能认为它们是真正的内容。

  • \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直到下一个 的所有内容]。 所以#1ultra thick, purple可以插入到\centerarc的定义中。 接下来它期望(但却发现\cpt与 的模板不匹配\centerarc。 所以你会得到一个错误。 要纠正这个问题,你需要\cpt在 之前展开[ultra thick, purple]。 你可以用一大堆\expand afters 来做到这一点,但你需要分别跳过每个标记,即[ul等等。 这需要大量的打字,并且是难以理解的代码的秘诀。

  \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 点名称,以确保它不会在代码的其他地方使用。

编辑:我忘了改为ctp2cpttwo(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}

在此处输入图片描述

相关内容