pgf-tikz / pgf

A Portable Graphic Format for TeX
https://pgf-tikz.github.io/
1.08k stars 104 forks source link

Add `.append estyle` #1283

Open tobiasBora opened 9 months ago

tobiasBora commented 9 months ago

Brief outline of the proposed feature

If I define a style and a command like this (following the usual convention in tikz to define a style and a code to edit the style):

\pgfkeys{
  /zx every box/.style={},
  /zx/boxes/.code={
    \pgfkeysalso{/zx/every box/.append style={#1}%
  },

Then the user needs to double the number of hashes:

  % The user will type:
  /zx/boxes={
    set fill/.style={
      fill=##1,
    },
  },

I asked here an elegant solution to avoid that issue, and people mentioned that it is possible to do:

  style node/.code = {%
    \pgfkeysalso{/test/style node aux/.estyle={\unexpanded{#1}}}%
  },

but this replaces the style while I’d like to append stuff to the style. But I can't find an equivalent of .append estyle. Would it be possible to provide an elegant solution to this issue?

Usage example

\documentclass[]{article}

\usepackage{tikz}
\usepackage{etoolbox}

\begin{document}

\makeatletter
\pgfkeys{
  /test/.cd,
  content node/.store in=\myContent,
  style node/.code = {%
    \pgfkeysalso{/test/style node aux/.estyle=\unexpanded{#1}}%
  },
% a .style is internally a .code with `\pgfkeysalso{<stuff>}`. 
% a .code is set up using `\pgfkeysdef{\pgfkeyscurrentpath}{#1}`
% and `\pgfkeysdef` sets up an internal value in `\pgfkeyscurrentpath/.@body`
% Unfortunately the developers of `pgfkeys` decided that the `.@body` of an
% `estyle` should also get the expanded tokens, which makes it much harder
% adding to it in a generic way (provided that the adding is also done with an
% `\edef`). I have no idea what should be easier with this behaviour, but it
% is what it is. The result is, that the following is not a generic `.eappend
% style`, but works for our `style node`.
  addstyle node/.code = {%
    \begingroup
      \pgfkeysgetvalue{/test/style node aux/.@body}\mytmp
      \ifx\mytmp\relax
        \PackageError{my-pgfkeys-extension}{Not a style: \pgfkeysgetvalue}{}%
        \let\mytmp\empty
      \fi
      \def\mytmpB##1%
        {%
          \endgroup
          \pgfkeysedef{/test/style node aux}
            {\unexpanded{##1}\noexpand\pgfkeysalso{\unexpanded{#1}}}%
        }%
    \expandafter\mytmpB\expandafter{\mytmp}%
  },
}
\NewDocumentCommand{\myNode}{m}{
  \pgfkeys{
    /test/.cd,
    content node=Default,
    style node aux/.style={},%
    #1%
  }
  \node[draw, /test/style node aux]{\myContent};
}
\makeatother

\begin{tikzpicture}
  \myNode{
    content node=Yes,
    style node={
      test/.style={fill=#1},
      test=red,
    },
    addstyle node={rounded corners}
  }
\end{tikzpicture}

\end{document}
muzimuzhi commented 9 months ago

It seems your usage example was pasted twice.

tobiasBora commented 9 months ago

Thanks, sorry, this is fixed. So for now it seems that the simpler is to do /zx/boxes/.code={\scantokens{\pgfkeysalso{/zx/every box/.append style={#1}}}},, I don't know if it is the recommended way to proceed or if we could provide a better solution.

hmenke commented 9 months ago

Haven't looked at it in detail yet, but I can already guarantee that \scantokens is not the correct solution.

hmenke commented 9 months ago

I think what is going on here is a fundamental misunderstanding of how nested declarations are processed in TeX.

The #1 in fill=#1 will refer to the first argument of style node aux and not to the first argument of the nested test. For that you have to escape it using ##1. That is the intended behavior. I don't see how a hypothetical .append estyle handler would help here.

Try swapping fill=#1 and fill=##1 in the following example to see it:

\documentclass{article}
\usepackage{tikz}
\begin{document}

\tikzset{
  style node aux/.style={},%
  style node/.code = {%
    \pgfkeysalso{style node aux/.append style={#1}}%
  },
}

\tikzset{
  style node={
    test/.style={fill=#1},
    test=red,
  },
  style node={rounded corners},
}

\begin{tikzpicture}
  \node[style node aux] at (0,0) {Hello};
  \node[style node aux=blue] at (3,0) {Hello};
\end{tikzpicture}

\end{document}
tobiasBora commented 9 months ago

Oh sure, the issue is that I do not want to escape it since it is the duty of my users to double the number of hashes. One reason is that the user does not know where the style is defined, and how many layers of escape they should use, and it is easy to forget the escaping and get weird result (as your example shows).

In one project for instance I use nested styles (as I can’t find a clean way to import styles, cf my other issue) and it might be the case that the user needs to escape it 2 or even 3 times (i.e. needs 2^3 = 8 hashes) in extreme cases… and nobody wants to write \helloNode{inner style={my style/.style={fill=########1},}}, not only because it is ugly, but also because it is error prone (forgetting to double the hashes might not even produce an error, instead weird values will just be inserted).

The .append estyle comes from a suggestion proposed here that can solve the problem if I want to replace the style with estyle, but cannot append a style to it.

Another potential solution would be to be able to define an equivalent of .style that takes no argument at all (most of the time I do not need any argument), and therefore would not require escaping.