pgf-tikz / pgf

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

Adding the opposite of `open` to arrow tips definitions #1348

Closed muzimuzhi closed 4 weeks ago

muzimuzhi commented 4 months ago

Discussed in https://github.com/pgf-tikz/pgf/discussions/1347

Originally posted by **Rmano** July 9, 2024 Good morning! If I define a new arrow tip that by default is open, like this (which is still somehow buggy, I can't see exactly why) ```tex %% Jack Tap, see %% https://github.com/circuitikz/circuitikz/issues/806 \pgfdeclarearrow{name=Jack Tap, parameters = {% \the\pgfarrowlength,% \the\pgfarrowwidth,% \ifpgfarrowswap s\fi% \ifpgfarrowopen o\fi% }, setup code = { \pgfarrowssettipend{.5\pgfarrowlength} \pgfarrowssetlineend{-.6\pgfarrowlength} \pgfarrowssetvisualbackend{-.6\pgfarrowlength} \pgfarrowssetbackend{-.6\pgfarrowlength} % hull \pgfarrowshullpoint{.5\pgfarrowlength}{0pt} \pgfarrowshullpoint{0pt}{\pgfarrowwidth} \pgfarrowshullpoint{-.6\pgfarrowlength}{0pt} % Saves: Only the length: \pgfarrowssavethe\pgfarrowlength \pgfarrowssavethe\pgfarrowwidth }, drawing code = { \pgfpathmoveto{\pgfqpoint{.5\pgfarrowlength}{0pt}} \pgfpathlineto{\pgfqpoint{0pt}{\pgfarrowwidth}} \pgfpathlineto{\pgfqpoint{-.5\pgfarrowlength}{0pt}} \pgfpathlineto{\pgfqpoint{-.6\pgfarrowlength}{0pt}} \ifpgfarrowopen \pgfusepathqstroke \else \pgfpathclose\pgfusepathqfillstroke \fi }, defaults = {length = 0.3cm, width=0.15cm,open}, % cache=false, % breaks everything } \tikzset{v/.tip={Jack Tap[swap]}, ^/.tip={Jack Tap}} ``` then I can't switch to the not-open (closed, filled?) version without knowing the color, i.e., using `Jack Barb[fill=black]`. Wouldn't it be useful to add a "complementary" switch for `open`, like, for example: ```tex \pgfkeys{/pgf/arrow keys/filled/.code=\pgfarrowsaddtooptions{\pgfarrowopenfalse}} ``` Or maybe `closed`, to convey the not-open thing? I tried to search for a complementary flag without success, but if it's already there, please tell me ;-)
Rmano commented 4 months ago

Notice that the code of the arrow has been fixed; you can see it here: https://github.com/circuitikz/circuitikz/pull/810/files

I am not posting it because it really isn't that relevant --- the matter is adding a "not open" flag argument. I am using filled for circuitikz, but I'll happily adapt to what is felt to be better.

hmenke commented 4 months ago

I think Jack Tap[fill=.] does what you want.

Rmano commented 4 months ago

I think Jack Tap[fill=.] does what you want.

Ah, yes, probably it does. Hmmm... I still like more of a "flag" type, but yes, that's a good solution.

muzimuzhi commented 4 months ago

Nope generally one needs Jack Tap[fill=pgfstrokecolor], in which pgfstrokecolor color name is never mentioned in the pgfmanual. But its corresponding pgffillcolor is mentioned in the doc of /pgf/arrow keys/fill.

\documentclass{article}
\usepackage{tikz}
\usetikzlibrary{arrows.meta}

\begin{document}

\pgfkeys{/pgf/arrow keys/filled/.code=\pgfarrowsaddtooptions{\pgfarrowopenfalse}}

\begin{tikzpicture}[nodes={anchor=west}, arrows={[scale=2]}]
  \draw[draw=red, fill=gray, -{Stealth}]
    (0, 0) -- (1, 0) -- (2,.5) node {default};
  \draw[draw=red, fill=gray, yshift=-0.5cm, -{Stealth[cyan]}]
    (0, 0) -- (1, 0) -- (2,.5) node {+ cyan};
  \draw[draw=red, fill=gray, yshift=-1.0cm, -{Stealth[open, cyan]}]
    (0, 0) -- (1, 0) -- (2,.5) node {+ open};
  \draw[draw=red, fill=gray, yshift=-1.5cm, -{Stealth[open, cyan, fill=.]}]
    (0, 0) -- (1, 0) -- (2,.5) node {current color ``.''};
  \draw[draw=red, fill=gray, yshift=-2.0cm, -{Stealth[open, cyan, fill=pgffillcolor]}]
    (0, 0) -- (1, 0) -- (2,.5) node {pgffillcolor};
  \draw[draw=red, fill=gray, yshift=-2.5cm, -{Stealth[open, cyan, fill=pgfstrokecolor]}]
    (0, 0) -- (1, 0) -- (2,.5) node {pgfstrokecolor};
  \draw[draw=red, fill=gray, yshift=-3.0cm, -{Stealth[open, cyan, filled]}]
    (0, 0) -- (1, 0) -- (2,.5) node {filled};
\end{tikzpicture}

\end{document}

image

Rmano commented 4 months ago

Well, the behavior is not really the same. Look at this (contrived) example:

\documentclass[]{article}
\usepackage[T1]{fontenc}
\usepackage{tikz}
\usetikzlibrary{arrows.meta}

%% Jack Tap, see
%% https://github.com/circuitikz/circuitikz/issues/806
\pgfdeclarearrow{name=Jack Tap,
    parameters = {%
    \the\pgfarrowlength,%
    \the\pgfarrowwidth,%
    \ifpgfarrowswap s\fi%
    \ifpgfarrowopen o\fi%
    \ifpgfarrowroundjoin j\fi
    \ifpgfarrowroundcap c\fi%
    },
    setup code = {
        \pgfarrowssettipend{.5\pgfarrowlength}
        \pgfarrowssetlineend{-.6\pgfarrowlength}
        \pgfarrowssetvisualbackend{-.6\pgfarrowlength}
        \pgfarrowssetbackend{-.6\pgfarrowlength}
        % hull
        \pgfarrowshullpoint{.5\pgfarrowlength}{0pt}
        \pgfarrowshullpoint{0pt}{\pgfarrowwidth}
        \pgfarrowshullpoint{-.6\pgfarrowlength}{0pt}
        % Saves: Only the length:
        \pgfarrowssavethe\pgfarrowlength
        \pgfarrowssavethe\pgfarrowwidth
    },
    drawing code = {
        \pgfsetdash{}{+0pt}
        \pgfarrowlinewidth=\pgflinewidth
        \ifpgfarrowroundjoin\pgfsetroundjoin\else\pgfsetmiterjoin\fi
        \ifpgfarrowroundcap\pgfsetroundcap\else\pgfsetbuttcap\fi
        \pgfpathmoveto{\pgfqpoint{-.6\pgfarrowlength}{0pt}}
        \pgfpathlineto{\pgfqpoint{-.5\pgfarrowlength}{0pt}}
        \pgfpathlineto{\pgfqpoint{0pt}{\pgfarrowwidth}}
        \pgfpathlineto{\pgfqpoint{.5\pgfarrowlength}{0pt}}
        \ifpgfarrowopen
            \pgfusepathqstroke
        \else
            \pgfpathclose
            \ifdim\pgfarrowlinewidth>0pt\pgfusepathqfillstroke\else\pgfusepathqfill\fi
        \fi
    },
    defaults = {length = 0.3cm, width=0.15cm,open},
    % cache=false, % breaks everything
}
\pgfkeys{/pgf/arrow keys/filled/.code=\pgfarrowsaddtooptions{\pgfarrowopenfalse}}
\tikzset{v/.tip={Jack Tap[swap]}, ^/.tip={Jack Tap},
    vf/.tip={Jack Tap[swap,filled]}, ^f/.tip={Jack Tap[filled]}}

\begin{document}
\begin{tikzpicture}[color=blue]
    \draw[-{Jack Tap}] (0,2) edge ++(2,0) -- ++(0,1);
    \draw[red, -{Jack Tap[filled]}] (0,0) edge ++(2,0) -- ++(0,1);
    \draw[red, -{Jack Tap[fill=.]}] (0,-2) edge ++(2,0) -- ++(0,1);
\end{tikzpicture}
\end{document}

image

hmenke commented 4 months ago

I would like to avoid adding new keys. Currently Jack Tap[fill] (without argument) gives an error. We could make that work and make it behave like your proposed filled.

Rmano commented 4 months ago

Yes, that would be possible too --- although I think that having on/off for binary switch is also a clean interface.

My feeling would be to let fill as it is and add the possibility for open (and probably, for symmetry, swap) to take a boolean, like open=false (default to true).

Rmano commented 4 months ago

...thinking about it a bit more, probably the filled flag would be the easiest to add and document. It's just a sentence in the description of open ;-). I know it's a key more, but it's quite buried in the arrow tip hierarchy...

Rmano commented 4 months ago

Nice @hmenke, thank you. One little thing: I was trying to provide the fill with no argument for circuitikz now, and I was thinking something like checking for the version of tikz, and then if it's older than 3.1.11 install the code. But (apart from finding how to check the version, which I'll do ;-) later), there is a little problem: is it possible to "undo" the ...fill/.value required?

muzimuzhi commented 4 months ago

@Rmano To undo /.value required, currently one has to make use of implementation details of pgfkeys: \cs_undefine:c {pgfk@/pgf/arrow~keys/fill/.@def} does the trick.

https://github.com/pgf-tikz/pgf/blob/44f8137449b34f62bc6371bd442ae5cc98f60f18/tex/generic/pgf/utilities/pgfkeys.code.tex#L853

Example undoing /.value required

```tex \documentclass{article} \usepackage{tikz} \usetikzlibrary{arrows.meta} \begin{document} \tikzset{/pgf/arrow keys/fill} % errors \ExplSyntaxOn \cs_undefine:c {pgfk@/pgf/arrow~keys/fill/.@def} \ExplSyntaxOff \tikzset{/pgf/arrow keys/fill} % passes \end{document} ```

Rmano commented 4 months ago

Thanks @muzimuzhi ! I had managed to arrive at \pgfkeyssetvalue{/pgf/arrow keys/fill/.@def}{}% --- is that equivalent? It seems to work, and should work also for older formats without expl3...

Rmano commented 4 months ago

Also, I think that \expandafter\let\csname pgfk@/pgf/arrow keys/fill/.@def\endcsname\@undefined is doing the same thing as the \cs_undefine:c; am I correct?

Skillmon commented 4 months ago

Also, I think that \expandafter\let\csname pgfk@/pgf/arrow keys/fill/.@def\endcsname\@undefined is doing the same thing as the \cs_undefine:c; am I correct?

Yes, that's correct.

muzimuzhi commented 4 months ago

@Rmano

I had managed to arrive at \pgfkeyssetvalue{/pgf/arrow keys/fill/.@def}{}% --- is that equivalent?

From the point of view of pgfkeys, \pgfkeyssetvalue{/pgf/arrow keys/fill/.@def}{} is equivalent to setting /pgf/arrow keys/fill/.default={}, hence slightly different than just undoing effect of /.value required. But it might be the right final form, depending on how #1352 will be modified, see my review https://github.com/pgf-tikz/pgf/pull/1352/files#r1678191162.

Possible final form of workaround for tikz v3.1.10 or older

\IfPackageAtLeastTF{tikz}{2023/01/16}{}{% one day after v3.1.10 (2023-01-15)
  \pgfkeys{
    /pgf/arrow keys/fill/.code={...}, % new code in v3.1.11 pasted here
    /pgf/arrow keys/fill/.default={}
  }%
}
Rmano commented 4 months ago

@muzimuzhi Thanks - I suppose that the code in your last answer needs LaTeX, am I right? I mean, it would not work in ConTeXt for example...

muzimuzhi commented 4 months ago

@Rmano Yes, \IfPackageAtLeastTF needs LaTeX (sorry). For a portable solution, it seems you have to parse the date or version stored in pgf.revision.tex yourself.

$ cat `kpsewhich pgf.revision.tex`
\def\pgfrevision{3.1.10}
\def\pgfversion{3.1.10}
\def\pgfrevisiondate{2023-01-15}
\def\pgfversiondate{2023-01-15}
Rmano commented 4 months ago

Thanks @muzimuzhi. Unfortunately, I found that the format of the date changed between 2021 and 2022 --- it was separated by / before... :-(

Rmano commented 4 months ago

Maybe I could just check if /pgf/arrow keys/fill/.@def is defined, and if yes, apply the change. Or surrender and just add the filled key and be done...

Skillmon commented 4 months ago

@Rmano Here is a parser that parses three numbers separated by a single arbitrary non-digit token (or group). It assumes YYYY<sep>MM<sep>dd (optionally followed by another <sep>, <sep> doesn't have to be uniform, it's just gobbled).

\newcount\pdYear
\newcount\pdMonth
\newcount\pdDay
\begingroup
\catcode`\@=11
\unexpanded{\endgroup
% pgfutil replacement code >>>
\let\pgfutil@protected\protected
% <<<

\pgfutil@protected\def\parsedate#1%
  {%
    \afterassignment\parsedate@month
    \pdYear=#1\relax
  }
\pgfutil@protected\def\parsedate@month#1%
  {%
    \afterassignment\parsedate@day
    \pdMonth=%
  }
\pgfutil@protected\def\parsedate@day#1%
  {%
    \afterassignment\parsedate@cleanup
    \pdDay=%
  }
\def\parsedate@cleanup#1\relax{}
}

\def\test#1{\parsedate{#1}\the\pdYear-\the\pdMonth-\the\pdDay\par}

\test{2024-02-01}
\test{2024/02/01}
\test{2024.02.01}
\bye

Keep in mind that you don't have to alter the category code, so don't need the \unexpanded (and if you did, you'd want to use \pgfutil@unexpanded instead for Context support).

Rmano commented 4 months ago

@Skillmon thanks again. I was not sure about the \unexpanded thing and grouping there, but it seems that it works ok:

https://github.com/circuitikz/circuitikz/blob/5537c8f2420f43e49fb23887e8d417b96e4313bd/tex/pgfcirc.defines.tex#L269-L319

Skillmon commented 4 months ago

@Rmano Your code looks good. The \begingroup\catcode`\@=11 \unravel{\endgroup...} is just a scoped \makeatletter...\makeatother and not necessary in package code (since your setup for your code level should already deal with @ being a letter).