Issues and potentiality of the tikzmark macro: dynamic box adaptation

While I adore the power of the \tikzmark concept, too, it seems (with the necessity to compile twice) to be overkill for this situation. Why not just box the content and measure its size?

The following implements this idea based on some code I originally developed for this answer to a question about highlighting elements in a lstlisting environment while also keeping the syntax highlighting. The result is the \btHL command, which works like a font-changing command (such as \color or \bfseries) in that it affects everything until the end of the group (not across line breaks); this was a requirement for playing together with listings. The basic idea is to box the content and then typeset it inside a TikZ node. The bounding box of the tikzpicture, however, is adjusted to the size of the content, so that the highlighting does not take extra space (to prevent "jumping content" if used with beamer overlays).

As a quick solution, I have implemented your \tikzhighlight macro on this base; the code, however, could be simplified quite a bit if the content to highlight is always given as a macro parameter.

\documentclass{article}
\usepackage[T1]{fontenc}
\usepackage{tikz}
\usepackage{amsmath}

\makeatletter
\newenvironment{btHighlight}[1][]
{\begingroup\tikzset{bt@Highlight@par/.style={#1}}\begin{lrbox}{\@tempboxa}}
{\end{lrbox}\bt@HL@box[bt@Highlight@par]{\@tempboxa}\endgroup}

\newcommand\btHL[1][]{%
  \begin{btHighlight}[#1]\bgroup\aftergroup\bt@HL@endenv%
}
\def\bt@HL@endenv{%
  \end{btHighlight}%   
  \egroup
}
\newcommand{\bt@HL@box}[2][]{%
  \tikz[#1]{%
    \pgfpathrectangle{\pgfpoint{0pt}{0pt}}{\pgfpoint{\wd #2}{\ht #2}}%
    \pgfusepath{use as bounding box}%
    \node[anchor=base west, fill=orange!30,outer sep=0pt,inner xsep=0.2em, inner ysep=0.1em,  #1]{\usebox{#2}};
  }%
}
\makeatother

\newcommand{\tikzhighlight}[2][red]{%
  {\btHL[fill=#1!10,draw=#1,rounded corners]#2}%
}

\begin{document}

\begin{itemize}
\item this is \tikzhighlight[yellow]{a text to be highlighted}
\item {\tiny{this is a text to be \tikzhighlight{highlighted}}}
\item \huge{this is a text to be \tikzhighlight{highlighted}}
\end{itemize}

\begin{align*}
&\tikzhighlight[green]{\ensuremath{x+\dfrac{z}{y}}}=100\\
&x+\tikzhighlight[blue]{\ensuremath{y}}=100
\end{align*}


\end{document}

Some additional fine tuning could be applied to the dimensioning of the boxes and the bounding box.

enter image description here


Thanks to Peter Grill suggestion I developed a reasonable solution based on:

  • Andrew's code to save the position of the picture;

  • Peter's code to compute the height and the width of the box.

Here is my MWE:

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

%% code by Andrew Stacey 
% http://tex.stackexchange.com/questions/51582/background-coloring-with-overlay-specification-in-algorithm2e-beamer-package#51582

\makeatletter
\tikzset{%
     remember picture with id/.style={%
       remember picture,
       overlay,
       save picture id=#1,
     },
     save picture id/.code={%
       \edef\pgf@temp{#1}%
       \immediate\write\pgfutil@auxout{%
         \noexpand\savepointas{\pgf@temp}{\pgfpictureid}}%
     },
     if picture id/.code args={#1#2#3}{%
       \@ifundefined{save@pt@#1}{%
         \pgfkeysalso{#3}%
       }{
         \pgfkeysalso{#2}%
       }
     }
   }

   \def\savepointas#1#2{%
  \expandafter\gdef\csname save@pt@#1\endcsname{#2}%
}

\def\tmk@labeldef#1,#2\@nil{%
  \def\tmk@label{#1}%
  \def\tmk@def{#2}%
}

\tikzdeclarecoordinatesystem{pic}{%
  \pgfutil@in@,{#1}%
  \ifpgfutil@in@%
    \tmk@labeldef#1\@nil
  \else
    \tmk@labeldef#1,(0pt,0pt)\@nil
  \fi
  \@ifundefined{save@pt@\tmk@label}{%
    \tikz@scan@one@point\pgfutil@firstofone\tmk@def
  }{%
  \pgfsys@getposition{\csname save@pt@\tmk@label\endcsname}\save@orig@pic%
  \pgfsys@getposition{\pgfpictureid}\save@this@pic%
  \pgf@process{\pgfpointorigin\save@this@pic}%
  \pgf@xa=\pgf@x
  \pgf@ya=\pgf@y
  \pgf@process{\pgfpointorigin\save@orig@pic}%
  \advance\pgf@x by -\pgf@xa
  \advance\pgf@y by -\pgf@ya
  }%
}

% code by Peter Grill <modified> 
%http://tex.stackexchange.com/questions/35319/a-boxed-alternative-with-minimal-spacing/35357#35357

\newcommand*{\@DrawBoxHeightSep}{0.1em}%
\newcommand*{\@DrawBoxDepthSep}{0.08em}%
\newcommand{\@DrawBox}[6][red]{%#1= style, #2=height, #3=depth, #4 left marker, #5 right marker, #6 text
    \tikz[overlay,remember picture,baseline]
    \draw[fill=#1!10,draw=#1,rounded corners]
      ($(pic cs:#4)+(-0.2em,#2+\@DrawBoxHeightSep)$) rectangle
      ($(pic cs:#5)+(0.2em,-#3-+\@DrawBoxDepthSep)$);
      \tikz[overlay,remember picture,baseline]\node[anchor=base] at ($(pic cs:#4)!0.5!(pic cs:#5)$) {#6};
}

\newcommand\tikzmark[2][]{%
\tikz[remember picture with id=#2] #1;}

\newcounter{image}
\setcounter{image}{1}
\newdimen\@myBoxHeight%
\newdimen\@myBoxDepth%
\newcommand{\tikzhighlight}[2][red]{%
    \settoheight{\@myBoxHeight}{#2}% Record height of box
    \settodepth{\@myBoxDepth}{#2}% Record height of box
    \tikzmark{l\theimage}#2\tikzmark{r\theimage}\@DrawBox[#1]{\@myBoxHeight}{\@myBoxDepth}{l\theimage}{r\theimage}{#2}% Draw the box
    \stepcounter{image}
}
\makeatother


\begin{document}

\begin{itemize}
\item this is \tikzhighlight[yellow]{a text to be highlighted}
\item {\tiny{this is a text to be \tikzhighlight{highlighted}}}
\item \huge{this is a text to be \tikzhighlight{highlighted}}
\end{itemize}

\begin{align*}
&\tikzhighlight[green]{\ensuremath{x+\dfrac{z}{y}}}=100\\
&x+\tikzhighlight[blue]{\ensuremath{y}}=100
\end{align*}


\end{document}

which gives:

enter image description here

One remark: in math mode \ensuremath inside \tikzhighlight is fundamental to avoid errors.

Solution with extensible markers

The previous solution had, as a drawback, the necessity of inserting the content to be highlighted inside the command.

Another approach is to make the markers extendible in some sense, that is allowing the user to shift them to better fit the area to be highlighted.

This could be achieved declaring something like

\NewDocumentCommand{\tikzmarkin}{m D(){0.1,-0.18} D(){-0.1,0.35}}{%
          \tikz[remember picture,overlay]
          \draw[line width=1pt,rectangle,rounded corners,fill=\fillcol,draw=\bordercol]
          (pic cs:#1) ++(#2) rectangle (#3)
          ;} 

What are #2 and #3? If we consider:

\tikzmarkin{a}x+\dfrac{z}{y}=400\tikzmarkend{a}

#2 is the end marker while #3 is the in marker. Now the user can change the dimension of the box as he wants since it's not computed automatically: that's the cost of not having things inside the command.

Let's make a complete example:

\documentclass{article}
\usepackage{amsmath,amssymb}
\usepackage{xparse}
\usepackage{tikz}
\usetikzlibrary{calc}

% to change colors
\newcommand{\fillcol}{blue!20}
\newcommand{\bordercol}{blue}

\newcommand{\setfillcolor}[1]{\renewcommand{\fillcol}{#1}}
\newcommand{\setbordercolor}[1]{\renewcommand{\bordercol}{#1}}

%% code by Andrew Stacey 
% http://tex.stackexchange.com/questions/51582/background-coloring-with-overlay-specification-in-algorithm2e-beamer-package#51582

\makeatletter
\tikzset{%
     remember picture with id/.style={%
       remember picture,
       overlay,
       save picture id=#1,
     },
     save picture id/.code={%
       \edef\pgf@temp{#1}%
       \immediate\write\pgfutil@auxout{%
         \noexpand\savepointas{\pgf@temp}{\pgfpictureid}}%
     },
     if picture id/.code args={#1#2#3}{%
       \@ifundefined{save@pt@#1}{%
         \pgfkeysalso{#3}%
       }{
         \pgfkeysalso{#2}%
       }
     }
   }

   \def\savepointas#1#2{%
  \expandafter\gdef\csname save@pt@#1\endcsname{#2}%
}

\def\tmk@labeldef#1,#2\@nil{%
  \def\tmk@label{#1}%
  \def\tmk@def{#2}%
}

\tikzdeclarecoordinatesystem{pic}{%
  \pgfutil@in@,{#1}%
  \ifpgfutil@in@%
    \tmk@labeldef#1\@nil
  \else
    \tmk@labeldef#1,(0pt,0pt)\@nil
  \fi
  \@ifundefined{save@pt@\tmk@label}{%
    \tikz@scan@one@point\pgfutil@firstofone\tmk@def
  }{%
  \pgfsys@getposition{\csname save@pt@\tmk@label\endcsname}\save@orig@pic%
  \pgfsys@getposition{\pgfpictureid}\save@this@pic%
  \pgf@process{\pgfpointorigin\save@this@pic}%
  \pgf@xa=\pgf@x
  \pgf@ya=\pgf@y
  \pgf@process{\pgfpointorigin\save@orig@pic}%
  \advance\pgf@x by -\pgf@xa
  \advance\pgf@y by -\pgf@ya
  }%
}

\NewDocumentCommand{\tikzmarkin}{m D(){0.1,-0.18} D(){-0.1,0.35}}{%
      \tikz[remember picture,overlay]
      \draw[line width=1pt,rectangle,rounded corners,fill=\fillcol,draw=\bordercol]
      (pic cs:#1) ++(#2) rectangle (#3)
      ;}

\newcommand\tikzmarkend[2][]{%
\tikz[remember picture with id=#2] #1;}

\begin{document}
 \begin{align}
      \tikzmarkin{a1}(0.05,-0.1)(-0.05,0.23)a_i\tikzmarkend{a1} + b_j = 10 \\
     \tikzmarkin{c}c_j + d_j + a_i >= 30 \tikzmarkend{c}
 \end{align}

\[
    \tikzmarkin{a}x+\dfrac{z}{y}=400\tikzmarkend{a} \qquad\qquad 
    \tikzmarkin{a2}(0.1,-0.4)(-0.1,0.5)x+\dfrac{z}{y}=400\tikzmarkend{a2}
\]
\vspace{1cm}
A integral:
\setfillcolor{red!10}
\setbordercolor{red}
\begin{equation}V(z)=\sum_{\kappa=0}^{\infty}{\; \tikzmarkin{k}(0.1,-0.7)(-0.1,0.8) \int_{0}^{\infty}{\dfrac{\mathrm{e}^{-\lambda x}\cdot (\lambda x)^\kappa}{\kappa!} \, b(x)\,\mathrm{d} x}\cdot z^\kappa \tikzmarkend{k}}\end{equation}

\vspace{1cm}
\setfillcolor{green!10}
\setbordercolor{green!50!black}
 \begin{equation*}
    \left.\begin{array}{cc}
      -2\cdot \tikzmarkin{r}2=& -4 \\
      -2\cdot 1=& -2 \\
      -2\cdot 0\tikzmarkend{r}=& 0
    \end{array}\right\}
    \qquad\qquad
    \left.\begin{array}{cc}
      -2\cdot \tikzmarkin{r1}(0.035,-0.2)(-0.035,0.35)2=& -4 \\
      -2\cdot 1=& -2 \\
      -2\cdot 0\tikzmarkend{r1}=& 0
    \end{array}\right\}
\end{equation*}
\end{document}

The result:

enter image description here