非 pgfkeys 方法

非 pgfkeys 方法

我最近在写一个 LaTeX 包,名字叫费曼.它是 Ti 的扩展Z 和 PGFplots 一样,它在其中定义了一个新环境,{tikzpicture}{feynman}在其中提供了新的命令和样式。

我已经定义了与 Ti 相关的所有键和样式Z-Feynman 在/tikzfeynmanPGFkeys 中有一个分支,理想情况下,我希望在{feynman}环境中/tikzfeynman始终先搜索路径,然后再继续前进。

到目前为止,我实现这一目标的方法基本上是:

\pgfkeys{
  /tikzfeynman/.cd,
  /tikzfeynman/.search also={/tikz},
  ...
}

但现在看来,这似乎是一个次优解决方案。首先,它要求在任何地方都使用整个.cdand.search also作为前缀,而且它似乎与.style使用内部键的new 不兼容/tikzfeynman(参见这个错误)。

这个问题因 TiZ-Feynman 使用图形绘制库,因此在某些情况下,它也必须搜索/tikz/graphs/graph drawing路径(但并非总是如此)。

我一直在尝试研究 PGFplots 如何在它的{axis} 环境中处理这个问题,但到目前为止我还不能确定我需要什么(如果 PGFplots 甚至这样做的话)。

那么,有没有办法让 PGFkeys 路径(或系列)在\begingroup ... \endgroup(或\scope ... \endscope) 中优先?并且(可选)这是否也可以在 中应用graph


这是一个最小(非)工作示例,说明了我想要修复的问题。

\documentclass[tikz]{standalone}

%% Hidden to the user
\pgfkeys{
  /foo/.is family,
  /foo/.search also={/tikz},
}
\def\fooset{\pgfqkeys{/foo}}
\fooset{
  key a/.style={
    key b,
    key c,
  },
  key b/.style={
    /tikz/red,
  },
  key c/.style={
    /tikz/thick,
  },
}

\makeatletter
\def\fooenv{
  \begingroup
  % Neither of the following seem to do anything as the /.cd only applies to
  % keys within the same command call.
  \pgfkeys{/foo/.cd,/foo/.search also={/tikz}}
  \scope[/foo/.cd,/foo/.search also={/tikz}]
}
\def\endfooenv{
  \endscope
  \endgroup
}
\makeatother

%% What a user might do
\fooset{
  my key/.style={
    key c,
    blue,
  },
}

\begin{document}
\begin{tikzpicture}
  % The following should fail because we are not in fooenv.
  \draw [key a] (0, 0) -- (1, 0);
  \begin{fooenv}
    % The following should work since we are now in fooenv, but don't.
    \draw [key a] (0, 0) -- (1, 0);
    \draw [my key] (0, 0) -- (0, 1);
    % In this case, it finds key a fine, but fails to find key c.
    \draw [/foo/my key] (0, 0) -- (1, 1);
  \end{fooenv}
\end{tikzpicture}
\end{document}

答案1

非 pgfkeys 方法

让它\tikzset进入\foobar你的环境。这可能是最简单的方法。(以我只添加了一行的方式。)

\documentclass[border=9,tikz]{standalone}

%% Hidden to the user
\pgfkeys{
  /foo/.is family,
  /foo/.search also={/tikz},
}
\def\fooset{\pgfqkeys{/foo}}
\fooset{
  key a/.style={
    key b,
    key c,
  },
  key b/.style={
    red,
  },
  key c/.style={
    thick,
  },
}
\def\fooenv{
    \begingroup
    \let\tikzset\fooset
}
\def\endfooenv{
    \endgroup
}

%% What a user might do
\fooset{
  my key/.style={
    key c,
    blue,
  },
}

\begin{document}

\begin{tikzpicture}
  \begin{fooenv}
    \draw [key a] (0, 0) -- (1, 0);
    \draw [my key] (0, 0) -- (0, 1);
    \draw [/foo/my key] (0, 0) -- (1, 1);
  \end{fooenv}
\end{tikzpicture}

\end{document}

pgfkeys 处理程序方法

在 TeX 语言中,你尝试做的事情就像

\let\oldtikzpath=\tikzpath
\def\tikzpath={\foopath \oldtikzpath}

TeX 在最底层提供了这个函数。Pgfkeys 还没有这个函数。但我们可以通过类似下面的代码来创建它

\pgfkeys{
    /handlers/.cd redirect/.code={
        % store the redirection target somewhere
    },
    /handlers/.new cd/.code={
        % check if user set new target manually
         % cd properly
    }
}

举一个小例子

\pgfkeys{
    /handlers/.cd redirect/.code={
        \pgfkeyssetvalue{\pgfkeyscurrentpath/cd target}{#1}
    },
    /handlers/.new cd/.code={
        \pgfkeysgetvalue{\pgfkeyscurrentpath/cd target}\pgfkeysredirectpath
        \ifx\pgfkeysredirectpath\relax
            \edef\pgfkeysdefaultpath{\pgfkeyscurrentpath/}
        \else
            \edef\pgfkeysdefaultpath{\pgfkeysredirectpath/}
        \fi
    }
}

\pgfkeys{
    /bar/.cd redirect={/foo},
}

\pgfkeys{
    /foo/foo/.code={foo-foo},
    /bar/bar/.code={bar-bar}
}

\pgfkeys{
    /bar/.cd,
    bar
}

\pgfkeys{
    /bar/.new cd,
    foo
}

我们甚至可以.cd根据我们的定义方式重新定义.new cd


只差一步:\xxxxset-family 由 定义\pgfqkeys。手册上说它相当于\pgfkeys{#1/.cd,#2}。实际上,\pgfqkeys定义时\pgfkeysdefaultpath不调用 handler .cd。所以我们必须重新定义\pgfqkeys,要么通过调用.new cd,要么再次复制粘贴 if 语句。


附注:手册声称

键的设置始终是当前 TEX 组的本地设置。

因此,在您自己的环境中说/tikz/.cd redirect=to somewhere或类似的话是可以的。一旦您离开环境,效果就会“失效”。

旧答案

pgfkeys 的设计并不容易“切断联系”。我说的“联系”是指如果你说

\pgfkeys{
    /a/.search also={
        /b,
        /c
    }
}

然后,如果发出 ,pgfkeys 将尝试按此顺序搜索/a/x/b/x。也就是说, 、、是“排成一行”的,您无法重新排序。/c/x/a/.cd,x=something/a/b/c

在您的示例中,\draw[key a]最终传递给\tikzset{key a}。因此

  • pgfkeys 将在 下进行搜索/tikz
  • pgfkeys 将不是搜索,/foo除非你说/tikz/.search also={/foo}
  • 就算你这么说,/tikz也还是第一候选人。
  • 最糟糕的是:它破坏了颜色规范和箭头规范。

要解决此问题,您必须/tikz手动添加别名。已经有很多别名:every picture、、等every axisevery graph

否则,你必须要求软件包用户将选项放在适当的位置。就像

\tikz\path(0,0)[rotate=30]node{rotated?};

\tikz\path(0,0)node[rotate=30]{rotated?};

尽管如此,这里仍然有好消息:对于传递给的选项\fooset,pgfkeys 将按此顺序尝试/foo/x,前提是您之前在某处说过。/tikz/x\fooset{.search also={/tikz}}

因此,您可能需要创建特殊命令,例如\foodraw替换\draw。然后,您确定确实\foodraw[key a]会传递key a\fooset

答案2

我认为这样的方法应该可行,尽管我没有进行适当的测试。我所做的就是摆弄 foo/.unknown 部分,然后复制 /tikz/.unknown 部分的其余部分。

一旦/foo/<key>=<value>失败,我们就会放弃系列foo并改用 TikZ。需要处理的细节(这是我复制的部分)是颜色、箭头和形状不是样式!因此,stealth-latex实际上会去寻找该命名键的样式,然后返回到我复制的处理程序。

所以他们需要得到适当的照顾(这对于我的用户友好度来说是一件令人惊奇的事情)。

\documentclass[tikz]{standalone}

%% Hidden to the user
\pgfkeys{/foo/.is family}
\def\fooset{\pgfqkeys{/foo}}
\makeatletter
\def\installfooset{
  \def\tikzset{\pgfqkeys{/foo}}%
    \pgfkeys{/foo/.unknown/.code={%
      \let\foo@key\pgfkeyscurrentname%
      \pgfkeys{/tikz/.cd,/tikz/\foo@key/.try={##1}}%
        \ifpgfkeyssuccess\typeout{I've passed "\foo@key" to TikZ}\else%
        \let\tikz@key\foo@key%
        % Start copying and print on the terminal
        \typeout{TikZ also didn't like "\foo@key"! Maybe arrow,color, or shape?}
    \expandafter\pgfutil@in@\expandafter!\expandafter{\tikz@key}%
    \ifpgfutil@in@%
      % this is a color!
      \expandafter\tikz@addoption\expandafter{\expandafter\tikz@compat@color@set\expandafter{\tikz@key}}%
      \edef\tikz@textcolor{\tikz@key}%
    \else%
      \pgfutil@doifcolorelse{\tikz@key}
      {%     
        \expandafter\tikz@addoption\expandafter{\expandafter\tikz@compat@color@set\expandafter{\tikz@key}}%
        \edef\tikz@textcolor{\tikz@key}%
      }%
      {%
        % Ok, second chance: This might be an arrow specification:
        \expandafter\pgfutil@in@\expandafter-\expandafter{\tikz@key}%
        \ifpgfutil@in@%
          % Ah, an arrow spec!
          \expandafter\tikz@processarrows\expandafter{\tikz@key}%
        \else%
          % Ok, third chance: A shape!
          \expandafter\ifx\csname pgf@sh@s@\tikz@key\endcsname\relax%
            \pgfkeys{/errors/unknown key/.expand
            once=\expandafter{\expandafter/\expandafter t\expandafter
            i\expandafter k\expandafter z\expandafter/\tikz@key}{##1}}%
          \else%
            \edef\tikz@shape{\tikz@key}%
          \fi%
        \fi%
      }%
    \fi%
  \fi%
        }
    }
}
\def\fooenv{\begingroup\installfooset\scope[]}
\def\endfooenv{\endscope\endgroup}
\makeatother


\fooset{
  key a/.style={key b,key c,},
  key b/.style={/tikz/red!40},
  key c/.style={/tikz/thick},
}
%% What a user might do
\fooset{my key/.style={key c,blue}}

\begin{document}
\begin{tikzpicture}
  % Fails with unknown [key a] ... Done!
  %\draw [key a] (0, 0) -- (1, 0);
    % Works... Done!
  \begin{fooenv}
    \draw [key a] (0, 0) -- (1, 0);
    \draw [my key] (0, 0) -- (0, 1);
        % Does it overwrite? Done!
    \draw [/foo/my key,dashed,key b,blue] (0, 0) -- (1, 1);
  \end{fooenv}
  % Uninstalled? ... Done!
  %\draw [key a] (0, 0) -- (1, 0);
\end{tikzpicture}
\end{document}

它有望将以下内容打印到终端

I've passed "draw" to TikZ
I've passed "line width" to TikZ
I've passed "draw" to TikZ
I've passed "line width" to TikZ
TikZ also didn't like "blue"! Maybe arrow,color, or shape?
I've passed "draw" to TikZ
I've passed "line width" to TikZ
TikZ also didn't like "blue"! Maybe arrow,color, or shape?
I've passed "dashed" to TikZ
TikZ also didn't like "blue"! Maybe arrow,color, or shape?

注意,red!40由于它经过了合法/.unknown代码,所以不会出现这种情况。

话虽如此,你试图做的事情违背了 TikZ 的设计原则。我们的想法是避开用户的视线,留下尽可能多的入口点来修改每个细节。因此,覆盖现有键非常违背这一原则。这就是为什么every .../.style=...TikZ 中有大量键的原因。

如果您覆盖 TikZ 密钥,那么您将会失去高级用户可能需要的一些功能。

答案3

玩弄的想法之一是试图推翻某些 Ti在环境范围内执行 Z 函数。特别是,尝试覆盖,/tikz/.unknown/.@cmd使其/foo在执行通常执行的操作之前先进行搜索。

对于我来说,这不是最好的解决方案,因为这不允许我用 keys in 覆盖/tikzkeys in /foo,但不可否认的是,这并不是什么大问题,因为我不想这么做太多,因为这可能会导致使用该包的人感到困惑。

这适用于明确指定的键(例如key amy key),但不幸的是,它似乎不适用于样式中引用的键。错误消息是I do not know '/key b',因此出于某种原因,样式中的键的处理方式不同……但如何?

另一个特殊情况是/foo/my key。由于这是一个完整密钥,因此无需搜索,而是key c通过/tikz/.unknown并成功找到。奇怪的是,它无法找到blue并实际上产生了一些错误(额外的\else,以及许多不完整的\if\ifx)。

下面是上述 MWE 的修改版本,它实现了修改后的版本/tikz/.unknown

\documentclass[tikz]{standalone}

%% Hidden to the user
\makeatletter
\pgfkeys{
  /foo/.is family,
  /foo/.search also={/tikz},
}
\def\fooset{\pgfqkeys{/foo}}
\fooset{
  key a/.style={key b, key c},
  key b/.style={red},
  key c/.style={thick},
}

\def\fooenv{
  \scope
  \ammend@search@path
}
\def\endfooenv{
  \endscope
}

\def\ammend@search@path{%
  \pgfkeysgetvalue{/tikz/.unknown/.@cmd}{\orig@tikz@search}
  \pgfkeys{/tikz/.unknown/.code=%
    \let\unknown@key\pgfkeyscurrentname
    \pgfkeys{/foo/\unknown@key/.try={##1}}
    \ifpgfkeyssuccess\else
    \let\pgfkeyscurrentname\unknown@key
    \orig@tikz@search##1\pgfeov
  }
}
\makeatother

%% What a user might do
\fooset{
  my key/.style={key c, blue}
}

\begin{document}
\begin{tikzpicture}
  % The following should fail because we are not in fooenv.
  \draw [key a] (0, 0) -- (1, 0);
  \begin{fooenv}
    % The following should work since we are now in fooenv, but don't.
    \draw [key a] (0, 0) -- (1, 0);
    \draw [my key] (0, 0) -- (0, 1);
    % In this case, it finds key a fine, but fails to find key c.
    \draw [/foo/my key] (0, 0) -- (1, 1);
  \end{fooenv}
\end{tikzpicture}
\end{document}

相关内容