7

I would like to be able to detect if a particular mark was made with

  • \tikzmark (which requires the use of pic cs:) or if it was made with

  • \tikzmarknode (which requires that pic cs: is not used).

Below is a contrived example which draws between two points: one created with \tikzmark and the other created with \tikzmarknode. Currently the first case works as the \DrawPicture invokes the first coordinate with pic cs: and the second one with out the pic cs:.

Question: How do I change the \DrawPicture macro so that both \DrawPicture{MarkA}{MarkB} and \DrawPicture{MarkB}{MarkA} (with the two marks reversed) will work?

I has orginally thought that using \iftikzmark would solve this problem, but seems that \iftikzmark only works as I expected on the second run, not the first run (even though the drawing code is invoked after the \tikzmark and \tikzmarknode. Even then, \tikzmarknode also creates a \tikzmark, so not really appropriate here to detect between the two cases.

Code:

\documentclass{article}
\usepackage{amsmath}
\usepackage{tikz}
\usetikzlibrary{tikzmark}

\newcommand*{\DrawPicture}[2]{% \par#1: \iftikzmark{#1}{tikzmark}{tikzmarknode}% \par#2: \iftikzmark{#2}{tikzmark}{tikzmarknode}% %% ------------- \begin{tikzpicture}[overlay,remember picture] \draw [ultra thick, red, -] (pic cs:#1) to[out=120, in=50, distance=1cm] (#2); \end{tikzpicture}% }% \begin{document} [ \tikzmark{MarkA} a + \tikzmarknode{MarkB}{b} ]

\DrawPicture{MarkA}{MarkB}% <-- This works. %\DrawPicture{MarkB}{MarkA}% <-- Want this also to be able to work

\end{document}

Andrew Stacey
  • 153,724
  • 43
  • 389
  • 751
Peter Grill
  • 223,288

3 Answers3

3

I am not sure whether this can help in your specific context, but expanding on this answer you link you could test for two things (#1 being the node name):

  1. A picture has been saved: \@ifundefined{save@pt@\tikzmark@pp@name{#1} should be false.
  2. It is a \node: \@ifundefined{pgf@sh@ns@\tikzmark@pp@name{#1}} should be false.

If 1) is false, it is not just a regular TikZ node. Else, if 2) is false, it is a \tikzmarknode and not just a \tikzmark.

Using \tikzmark@pp@name in the first test, we additionally check whether the saved picture was created by tikzmark or tikzmarknode. Testing agaist the name of a node created using \node[remember picture] would still return true.

\documentclass{article}
\usepackage{tikz}
\usetikzlibrary{tikzmark}

\makeatletter \newcommand*{\IsTikzmarkNode}[1]{ @ifundefined{save@pt@\tikzmark@pp@name{#1}}{ node } { @ifundefined{pgf@sh@ns@\tikzmark@pp@name{#1}}{ tikzmark } { tikzmarknode } } } \makeatother

\begin{document}

% A is a tikzmark foo\tikzmark{A} bar

% B is a tikzmarknode \tikzmarknode{B}{foo}

% C is a node \tikz{\node (C) {bar};}

\bigskip

\IsTikzmarkNode{A}

\IsTikzmarkNode{B}

\IsTikzmarkNode{C}

\end{document}

enter image description here

In case you wonder (because I did): \@ifundefined{pgf@sh@ns@\tikzmark@pp@name{C}} will be false if \node (C) {}; is defined and even if this test takes place outside the \tikz scope.


In the above example, you could simply do (without additionally testing whether it is just a regular node):

\documentclass{article}
\usepackage{tikz}
\usetikzlibrary{tikzmark}

\makeatletter \NewDocumentCommand{\IsTikzmarkNode}{ m }{ @ifundefined{save@pt@\tikzmark@pp@name{#1}}{ -1 } { @ifundefined{pgf@sh@ns@\tikzmark@pp@name{#1}}{ 0 } { 1 } } } \makeatother

\newcommand*{\DrawPicture}[2]{% \begin{tikzpicture}[overlay, remember picture] \ifnum\IsTikzmarkNode{#1} > 0\relax \coordinate (start) at (#1); \else \coordinate (start) at (pic cs:#1);
\fi \ifnum\IsTikzmarkNode{#2} > 0\relax \coordinate (end) at (#2); \else \coordinate (end) at (pic cs:#2);
\fi \draw [ultra thick, red] (start) to[bend left=90] (end); \end{tikzpicture}% }% \begin{document} [ \tikzmark{MarkA} a + \tikzmarknode{MarkB}{b} ]

\DrawPicture{MarkA}{MarkB}% <-- This works. \DrawPicture{MarkB}{MarkA}% <-- Want this also to be able to work

\end{document}

enter image description here


Okay, this took me a while, but I think I got what you want:

\documentclass{article}
\usepackage{tikz}
\usetikzlibrary{tikzmark}

\ExplSyntaxOn \seq_new:N \l_istikzmarknode_registeredmarks_seq \NewDocumentCommand{\RegisterTikzmark}{ m }{ \seq_gpush:Nn \l_istikzmarknode_registeredmarks_seq { #1 } } \NewDocumentCommand{\IsRegisteredTikzmarkTF}{ m m m }{ \seq_if_in:NnTF \l_istikzmarknode_registeredmarks_seq { #1 } { #2 } { #3 } } \seq_new:N \l_istikzmarknode_registerednodes_seq \NewDocumentCommand{\RegisterTikzmarkNode}{ m }{ \seq_gpush:Nn \l_istikzmarknode_registerednodes_seq { #1 } } \NewDocumentCommand{\IsRegisteredTikzmarkNodeTF}{ m m m }{ \seq_if_in:NnTF \l_istikzmarknode_registerednodes_seq { #1 } { #2 } { #3 } } \ExplSyntaxOff

\makeatletter \tikzset{ save picture id/.append code={ \immediate\write@auxout{\string\RegisterTikzmark{#1}} \RegisterTikzmark{#1} }, register tikzmarknode/.code={ \immediate\write@auxout{\string\RegisterTikzmarkNode{#1}} \RegisterTikzmarkNode{#1} }, maybe define node/.append style={ register tikzmarknode={#1} } }

\newcommand*{\IsTikzmarkNode}[1]{ \IsRegisteredTikzmarkTF{#1}{ \IsRegisteredTikzmarkNodeTF{#1}{ tikzmarknode } { tikzmark } } { @ifundefined{pgf@sh@ns@\tikzmark@pp@name{#1}}{ undefined } { node } }
} \makeatother

\begin{document}

\IsTikzmarkNode{A}

\IsTikzmarkNode{B}

\IsTikzmarkNode{C}

\bigskip

% A is a tikzmark
foo\tikzmark{A} bar

% B is a tikzmarknode
\tikzmarknode{B}{foo}

% C is a node
\tikz{\node (C) {bar};}

\bigskip

\IsTikzmarkNode{A}

\IsTikzmarkNode{B}

\IsTikzmarkNode{C}

\end{document}

This will output after the first run:

enter image description here

And after every following run:

enter image description here

What does it do? First, I define two sequences \l_istikzmarknode_registeredmarks_seq and \l_istikzmarknode_registerednodes_seq where I store all the \tikzmarks and \tikzmarknodes. There might be other ways to directly grab this information from the PGF code. Anyways, this solution also works with node names containing spaces or dashes.

Then, I append some code to save picture id that is actually called for every \tikzmark (and also for every \tikzmarknode) and store the names of the \tikzmarks in the sequence \l_istikzmarknode_registeredmarks_seq. I can use this later to test whether a string is a name of a defined \tikzmark. I also store the command to store the name into the sequence in the .aux file, so that is being called on the second run.

Then, similarly to above, I append some code to the style maybe define node that is executed for every \tikzmarknodes and takes the name of the node as argument. I store these name in the sequence \l_istikzmarknode_registerednodes_seq. I can use this later to test whether a string is a name of a defined \tikzmarknode. Again, I also store the command to store the name into the sequence in the .aux file.

Now, I just need to check whether the given name is in one of the two sequences and, for regular \nodes, additionally for \@ifundefined{pgf@sh@ns@\tikzmark@pp@name{#1}}.


I am not 100% sure, but it might be semantically cleaner to replace

register tikzmarknode/.code={
    \immediate\write\@auxout{\string\RegisterTikzmarkNode{#1}}
    \RegisterTikzmarkNode{#1}
},
maybe define node/.append style={
    register tikzmarknode={#1}
}

by

every tikzmarknode/.code={
    \immediate\write\@auxout{\string\RegisterTikzmarkNode{\tikz@id@name}}
    \RegisterTikzmarkNode{\tikz@id@name}
}

in the above code. But somehow, the test does not work right after the first run then.

  • That looks good, but fails if the \IsTikzmarkNode{} is exectued before the marks/nodes are placed. On 1st run all three are reported as nodes, and on 2nd run, B is reported as a \tikzmark. A and C are correctly identified on 2nd run. – Peter Grill Nov 28 '23 at 16:47
  • @PeterGrill Hm, I am unsure how we could detect this though. With "before" you mean that \IsTikzmarkNode{} is place before \tikzmark(node) in the code even? – Jasper Habicht Nov 28 '23 at 18:24
  • Yes that is what I meant, the test should work before they are placed. On first run, they should be reported as undefined (not nodes) when tested before they are defined. 2nd run should identify them correctly, even before they are defined. – Peter Grill Nov 28 '23 at 18:25
  • Yes, run twice (for the test before definition). Ideally, should identify correctly the first time if for the test that is run after definition. – Peter Grill Nov 28 '23 at 18:28
  • @PeterGrill I think, I got it. Check my edit (I deleted some of my prior tries as these are essentially included in the final version). – Jasper Habicht Nov 28 '23 at 20:29
  • Thanks for all your help in this issue. It got me got me going in the direction I needed to resolve my particular issue. – Peter Grill Dec 02 '23 at 22:29
3

There are quite a few edge cases here so the best solution will depend on what you want to happen in these situations. To understand the edge cases, it's worth looking at what a tikzmark and tikzmarknode are. (I'm sure the OP knows this, so this is partially for the wider audience.)

A tikzmark records the location of a point on the page. To do this, it must write something to the aux file which is read in on the next compilation. It must work this way because its position can only be known after shipout.

A node records its anchors relative to its origin in the current tikzpicture. If that picture has the remember picture key set, then that origin is remembered using essentially the same underlying mechanism as a tikzmark. In particular, although the node's relative position is available straightaway then to actually use its position on the page requires an additional compilation. However, the node information is only available after the node has been defined in the current compilation (the tikzmark package does provide a way around that, but I view that as orthogonal to this discussion).

A tikzmarknode combines these two so that both the node's anchors are available in the usual way and the origin (of the coordinate system in which the node is defined) is available as a tikzmark.

So the only difference between a tikzmarknode and a regular node is in how the information is made available. It is also not very difficult to set up all the information available from a tikzmarknode using other commands.

So the edge cases are:

  1. A tikzmarknode is defined, but the test is run prior to its definition in the document. So at this point, the only information available is that of the tikzmark and not of the node. What should the test do in this case?
  2. A tikzmark is defined, and an unrelated node is defined with the same name. What should the test do in this case?
  3. A tikzmark and node are defined so that they are related as if defined via a tikzmarknode, just not with the exact \tikzmarknode command. What should the test do in this case?

On the basis that the simplest is usually the best until it breaks, here's my first go at this. It uses the same underlying information as in Jasper's first answer: testing save@pt@<name> and pgf@sh@ns@<name>. The main difference is that rather than nesting the tests I keep them separate. This way, I can test for four options:

  1. Both true: tikzmarknode
  2. save@pt@<name> true but pgf@sh@ns@<name> false: tikzmark
  3. save@pt@<name> false but pgf@sh@ns@<name@ true: node
  4. Both false: undefined

In the following code then I have it wrapped in a classification function, but it would be simple to make it a genuine conditional - this is really me testing the waters to see what you actually want.

\documentclass{article}
%\url{https://tex.stackexchange.com/q/702241/86}
\usepackage{tikz}
\usetikzlibrary{tikzmark}

\makeatletter \ExplSyntaxOn

\bool_new:N \l__tikzmark_is_a_tikzmark_bool \bool_new:N \l__tikzmark_is_a_node_bool

\DeclareDocumentCommand \ClassifyNode {m} { \bool_set:Nn \l__tikzmark_is_a_tikzmark_bool { \tl_if_exist_p:c {save@pt@\tikzmark@pp@name{#1}} } \bool_set:Nn \l__tikzmark_is_a_node_bool { \tl_if_exist_p:c {pgf@sh@ns@\tikz@pp@name{#1}} } Mark~#1~is~ \bool_if:nTF { \l__tikzmark_is_a_tikzmark_bool || \l__tikzmark_is_a_node_bool } { a~ \bool_if:NT \l__tikzmark_is_a_tikzmark_bool {tikzmark} \bool_if:NT \l__tikzmark_is_a_node_bool {node} } { not~ (yet)~ defined } }

\ExplSyntaxOff \makeatother

\begin{document}

\ClassifyNode{A}\par \ClassifyNode{B}\par \ClassifyNode{C}

\tikzmark{A}

\ClassifyNode{A}\par \ClassifyNode{B}\par \ClassifyNode{C}

\tikzmarknode{B}{B}

\ClassifyNode{A}\par \ClassifyNode{B}\par \ClassifyNode{C}

\tikz {\node (C) {C};}

\ClassifyNode{A}\par \ClassifyNode{B}\par \ClassifyNode{C}

\end{document}

On first run this outputs:

Mark A is not (yet) defined
Mark B is not (yet) defined
Mark C is not (yet) defined

Mark A is not (yet) defined Mark B is not (yet) defined Mark C is not (yet) defined B Mark A is not (yet) defined Mark B is a node Mark C is not (yet) defined C Mark A is not (yet) defined Mark B is a node Mark C is a node

On subsequent runs, this changes to:

Mark A is a tikzmark
Mark B is a tikzmark
Mark C is not (yet) defined

Mark A is a tikzmark Mark B is a tikzmark Mark C is not (yet) defined B Mark A is a tikzmark Mark B is a tikzmarknode Mark C is not (yet) defined C Mark A is a tikzmark Mark B is a tikzmarknode Mark C is a node

In terms of using the coordinates, this seems the correct behaviour as it tells you what can be used at each stage. So the fact that B is a tikzmarknode is irrelevant prior to its definition: you can only use that fact afterwards.


Update: 7/12/2023

Here's how to use tikzmark's save node facility to make it possible to classify a \tikzmarknode correctly even before it is defined.

\documentclass{article}
%\url{https://tex.stackexchange.com/q/702241/86}
\usepackage{tikz}
\usetikzlibrary{tikzmark}

\makeatletter \ExplSyntaxOn

\bool_new:N \l__tikzmark_is_a_tikzmark_bool \bool_new:N \l__tikzmark_is_a_node_bool

\DeclareDocumentCommand \ClassifyNode {m} { \bool_set:Nn \l__tikzmark_is_a_tikzmark_bool { \tl_if_exist_p:c {save@pt@\tikzmark@pp@name{#1}} } \bool_set:Nn \l__tikzmark_is_a_node_bool { \tl_if_exist_p:c {pgf@sh@ns@\tikz@pp@name{#1}} } Mark~#1~is~ \bool_if:nTF { \l__tikzmark_is_a_tikzmark_bool || \l__tikzmark_is_a_node_bool } { a~ \bool_if:NT \l__tikzmark_is_a_tikzmark_bool {tikzmark} \bool_if:NT \l__tikzmark_is_a_node_bool {node} } { not~ (yet)~ defined } }

\ExplSyntaxOff \makeatother

\NewDocumentCommand \ClassifyNodes {} { \ClassifyNode{A}\par \ClassifyNode{B}\par \ClassifyNode{C}\par \ClassifyNode{D} }

\tikzset{ save nodes to file, restore nodes from file }

\begin{document}

\ClassifyNodes

\tikzmark{A}

\ClassifyNodes

\tikzmarknode{B}{B}

\ClassifyNodes

\tikzmarknode{C}{C} \SaveNode{C}

\ClassifyNodes

\tikz {\node (D) {D};}

\ClassifyNodes

\end{document}

This stabilises to:

Mark A is a tikzmark
Mark B is a tikzmark
Mark C is a tikzmarknode
Mark D is not (yet) defined

Mark A is a tikzmark Mark B is a tikzmark Mark C is a tikzmarknode Mark D is not (yet) defined B Mark A is a tikzmark Mark B is a tikzmarknode Mark C is a tikzmarknode Mark D is not (yet) defined C Mark A is a tikzmark Mark B is a tikzmarknode Mark C is a tikzmarknode Mark D is not (yet) defined D Mark A is a tikzmark Mark B is a tikzmarknode Mark C is a tikzmarknode Mark D is a node


Update: 2023-12-10 One of the issues with combining save node with a \tikzmarknode is that my code to save a node requires two steps: one which adds a node name to a list and another that processes that list and stores the node details in some location. When using this in a tikzpicture, the first step is usually invoked on the node itself and then the second happens at the end of the picture. This needs (at least) two keys: one on the tikzpicture that installs the end-of-picture code, and one on each node to be saved.

A \tikzmarknode only provides immediate access to the options on its node, to get at the surrounding tikzpicture requires using the every tikzmarknode picture style, such as:

\tikzset{every tikzmarknode picture/.style={save nodes to file}}
\tikzmarknode%
[save node]
{C}{C}

but that hooks into every \tikzmarknode. While it's safe to do so (the saving node code only executes if there are nodes to save), I'm not overly happy about needing to do that if only wanting to save one or two nodes.

But \tikzmarknodes are quite simple compared to general tikzpictures so we can hook in after the node has been defined rather than waiting until the picture finishes. So here's a key that seems to work, and if it genuinely does then I'll add it to the tikzmark library.

\ExplSyntaxOn
\tikzset{
  save~ tikzmarknode/.code={
    \tikz_fig_must_be_named:
    \pgfkeysalso{
      append~ after~ command={
        \pgfextra{
          \clist_gput_right:Nv \g__sn_nodes_clist {tikz@last@fig@name}
          \maybe_save_nodes:
        }
      }
    }
  }
}

\ExplSyntaxOff

...

\tikzmarknode% [save tikzmarknode] {C}{C}

Andrew Stacey
  • 153,724
  • 43
  • 389
  • 751
  • This seems to be more or less what I also had in the beginning. The problem was then that the OP wanted to get the right definitions already upon the first run if the check is done after the definitions. (And the OP also wanted that everything was right after no more than two runs.) – Jasper Habicht Nov 28 '23 at 23:39
  • Yes, this was really an extended comment with a request for clarification about the edge cases. I don't understand why it would be useful to know that B is a tikzmarknode without being able to make use of that fact. – Andrew Stacey Nov 29 '23 at 07:41
  • in particular, I'm always happy to extend the tikzmark codebase, and your code looks great for that, but only if I can see why it is useful. – Andrew Stacey Nov 29 '23 at 07:43
  • It would be great if one could somehow "legally" hook into every \tikzmark and every \tikzmarknode and grab the current node name. I am unsure what other use cases there could be (here it is to know after the first run already that some name belongs to a \tikzmark or \tikzmarknode), but currently this seems only to be possible using keys that are probably not meant for such things. But maybe I am wrong and this already works: every tikzmarknode looks like a great candidate, but I fail to pass the current node name to it or process it. – Jasper Habicht Nov 29 '23 at 08:42
  • But you are right, what the OP asked could also be merely an XY question that can be better solved with a different approach. As far as I understand, it is about the use of pic cs: or not in a subsequent tikzpicture. – Jasper Habicht Nov 29 '23 at 11:03
  • 1
    One possible extension to the code would be to change the invocation of every tikzmarknode to every tikzmarknode/.try=<name>. That way, you would have access to the tikzmark(node)'s name inside that code. – Andrew Stacey Nov 29 '23 at 20:26
  • At the start of the second run Mark B is determined to be a tikzmark, even though it is a \tikznode. This seems to me that I can only safely attempt to use \tikzmark before it is defined. But, if I don't know if it is a \tikzmark/\tikzmarknode, then have to wait until after they are defined so that you can properly set up the coordinate. I will post what I am using which I hope addresses the other comments. – Peter Grill Dec 02 '23 at 20:50
  • @PeterGrill I'm still unclear what behaviour you would like at a point in the document before the tikzmarknode is defined. At this juncture then only the associated tikzmark is available, so the fact that it is defined by a tikzmarknode is useless to you because you can't do anything with that information. This was the whole point of separating out the tikzmark from tikzmarknode originally. If you want to use the node data prior to its definition then the save node facility might be useful. – Andrew Stacey Dec 04 '23 at 22:16
  • @PeterGrill See edit – Andrew Stacey Dec 07 '23 at 22:32
  • @AndrewStacey: Thanks, was having issues with figuring out hot ot use save node. But, am getting an Undefined control sequence. \pgf@sh@pi@current bounding box ->\pgfpictureid. I haven't updated in a while, so perhaps I am out of date on some pacakge. – Peter Grill Dec 08 '23 at 08:14
  • @PeterGrill no, that's a bug in the current version but I think it can be safely ignored. – Andrew Stacey Dec 08 '23 at 19:24
  • @PeterGrill See edit – Andrew Stacey Dec 10 '23 at 17:48
1

My goal is to code drawing macros that could accept marks made by either \tikzmark or with \tikzmarknode without tagging each mark as to how it was created, or knowing whether they were defined yet or not. If they were not defined I could

  • either not to the drawing, or
  • use (0,0) for the coordinate so there was a visible indicator that another run was clearly needed.

The solution I am using is below (commented out the portion needed for the produced diagram, but not essential to following the code). Any feedback on potential problems with this approach would be helpful.

enter image description here

\documentclass{article}
\usepackage{amsmath}
\usepackage{xstring}
\usepackage{tikz}
\usetikzlibrary{tikzmark}

\makeatletter \newcommand{\IfStrContains}[4]{% \StrPosition{#1}{#2}[@@Position]% Record position in @@Position \IfEq{@@Position}{0}% {#4}% @@Position=0 => Did not find the target string {#3}% @@Position>0 => Found the target string }% \newcommand{\IfNodeDefined}[3]{% %% http://tex.stackexchange.com/questions/37709/how-can-i-know-if-a-node-is-already-defined % #1 = node name % #2 = code to execute if node is defined % #3 = code to execute if node is NOT yet defined @ifundefined{pgf@sh@ns@#1}{#3}{#2}% } \NewDocumentCommand{\DefineTikzmarkCoordinate}{% %% https://tex.stackexchange.com/questions/702241/dectect-if-using-tikzmark-or-tikzmarknode m%% #1 = name of coordinate m%% #2 = \tikzmark name, or \tikzmarknode name }{% %%% #1 maybe of the form "<node name>.<anchor>" so remove text after the . \IfStrContains{#2}{.}{% \StrBefore{#2}{.}[@@TikzmarkName]% }{% \def@@TikzmarkName{#2}% }% \IfNodeDefined{@@TikzmarkName}{% we need to access this as a node \coordinate (#1) at (#2); }{% \iftikzmark{@@TikzmarkName}{% need to use the 'pic cs' coordinate system \coordinate (#1) at (pic cs:#2); }{% not defined, so use the origin \coordinate (#1) at (0,0); }% }% } \makeatother

\newcommand*{\DrawPicture}[3][]{% %% ------------- \begin{tikzpicture}[overlay,remember picture] \DefineTikzmarkCoordinate{Coordinate 1}{#2}% \DefineTikzmarkCoordinate{Coordinate 2}{#3}% %% ------- \draw [ultra thick, -, #1] (Coordinate 1) to[out=120, in=50, distance=1cm] (Coordinate 2); \end{tikzpicture}% }% \begin{document} %\begin{minipage}{0.5\linewidth} %The following figure has %\begin{itemize} %\item % \verb|\tikzmark| to the left of $a$ and %\item % $b$ has a \verb|\tikzmarknode| around it. %\end{itemize} %\end{minipage} %\begin{minipage}{0.3\linewidth} [ \tikzmark{MarkA} a + \tikzmarknode{MarkB}{b} ] \DrawPicture[red, -latex]{MarkA}{MarkB.north}% <-- This works. \DrawPicture[blue,-latex, yscale=-1]{MarkB.south}{MarkA}% <-- Now, this also to work %\end{minipage} % %\medskip %The macro \verb|\DrawPicture| can draw between these two in either direction. \end{document}

Peter Grill
  • 223,288