TikZ-PGF – Detecting Usage of \tikzmark or \tikzmarknode in TikZ Graphics

tikz-pgftikzmark

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}

Best Answer

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}
Related Question