Take the 2-minute tour ×
TeX - LaTeX Stack Exchange is a question and answer site for users of TeX, LaTeX, ConTeXt, and related typesetting systems. It's 100% free, no registration required.

I am authoring Beamer presentations that feature numerous code listings, and I am required to highlight portions of code with semi-transparent balloons. Yet I've come up with a solution that uses a variant of tikzmark. Inside listings, I am placing anchors manually, stretching a balloon over them afterwards.

\documentclass{beamer}

\usepackage{listings}
\usepackage{tikz}

\usetikzlibrary{fit,calc,shadows}

% Define styles for balloons and lines
\tikzstyle{line} =    [draw, rounded corners=3pt, -latex]
\tikzstyle{balloon} = [draw, fill=blue!20, opacity=0.4, inner sep=4pt, rounded corners=2pt]
\tikzstyle{comment} = [draw, fill=blue!70, text=white, text width=3cm, minimum height=1cm, rounded corners, drop shadow, align=left, font=\scriptsize]

% Command to place a TikZ anchor at the current position
\newcommand{\tikzmark}[1]{%
  \tikz[overlay,remember picture,baseline] \coordinate (#1) at (0,0) {};}

% Command to draw a balloon over two anchors
\newcommand<>{\balloon}[3][balloon]{%
  \coordinate (c) at ($(#2)+(0,1ex)$);
  \node#4 (#1) [balloon, fit=(#3) (c)] {};}

\begin{document}

\begin{frame}[fragile]{Foo!}
\begin{lstlisting}[language=c,escapechar=\$]
$\tikzmark{sw1}$#include <stdio.h>$\tikzmark{ne1}$

int main(void)
{
    printf("hello, world\n");$\tikzmark{ne2}$
    $\tikzmark{sw2}$return 0;
}
\end{lstlisting}

\pause

\begin{tikzpicture}[overlay,remember picture]

  \balloon<2>{ne1}{sw1};
  \balloon<3>{ne2}{sw2};

  \node (comment) [comment] at (\textwidth-1.5cm, .5\textheight+1cm) {%
\only<2>{Include headers}%
\only<3>{Do stuff}%
  };

  \draw<2-3> [line] (comment.south) |- (balloon.east);

\end{tikzpicture}

\end{frame}

\end{document}

Frame 1

Frame 2

As you see, this approach requires a lot of redundant work. I have dozens of presentations to do, many of them containing similar code samples, and placing tikzmarks manually all over the code seems to be an overkill. Additionally, this approach fails if we need to highlight a block of code that contains long lines inside (for example, the whole "main" function in the example above).

In fact, the two line numbers is the only necessary input for this case. It would be good if I could write some macro that would accept two numbers (beginning line and ending line) and the comment text, and do the rest automatically. Could this be done with somehow intercepting lstlisting's output and, for each line, remembering coordinates of the first non-whitespace character and line ending? After that, we would have an array of coordinates that could be, for instance, fed to tikz "fit" facility.

It would be also good if the macro worked not only with inline listings, but with external code too. We are relying on system TTF fonts, that's why we target XeTeX and luaTex (with latter being even preferred). As I'm relatively new to TeX, I would also appreciate any comments and suggestions on the example above.

Thanks!

share|improve this question
1  
I could think of a way where you need to get listings to insert some code at the start of each line containing something like \tikzmark{<listingID>-<line number>} and then a macro could just use these tikzmarks to draw the highlighting. It has to be listing-width and page-break aware … –  Qrrbrbirlbel Oct 30 '12 at 20:38

1 Answer 1

I'll post what I have so far. No doubt it has many flaws that I haven't seen (as well as the ones that I have).

You need the newest version of tikzmark from the TeX-SX Launchpad website. (Make sure you get the most up to date version - I've had reports of issues when downloading but I haven't put in place any sort of revision numbering stuff as yet; the resulting tikzlibrarytikzmark.code.tex will have a command \pgfmark near the end.)

The following puts a modified tikzmark at the start, at the end, and at the first non-whitespace character of each line. It doesn't yet deal with automatic line breaking. The marks are numbered according to the line number, and there's a secret initial start mark at the beginning of the listings environment.

\documentclass{article}

\usepackage{listings}
\usepackage{tikz}
\usetikzlibrary{tikzmark}

\makeatletter
\newif\iflst@linemark

\lst@AddToHook{EveryLine}{%
  \begingroup
  \advance\c@lstnumber by 1\relax
  \pgfmark{line-\lst@name-\the\c@lstnumber-start}%
  \endgroup
}

\lst@AddToHook{EOL}{\pgfmark{line-\lst@name-\the\c@lstnumber-end}%
\global\lst@linemarktrue
}

\lst@AddToHook{OutputBox}{%
  \iflst@linemark
  \pgfmark{line-\lst@name-\the\c@lstnumber-first}%
  \global\lst@linemarkfalse
  \fi
}

\def\tkzlst@fnum#1\relax#2\@STOP{%
  \def\@test{#2}%
  \ifx\@test\@empty
  \def\tkzlst@start{0}%
  \else
  \@tempcnta=#1\relax
  \advance\@tempcnta by -1\relax
  \def\tkzlst@start{\the\@tempcnta}%
  \fi
}

\lst@AddToHook{Init}{%
  \expandafter\tkzlst@fnum\lst@firstnumber\relax\@STOP
  \pgfmark{line-\lst@name-\tkzlst@start-start}%
}

\newcommand\balloon[4]{%
  \pgfmathtruncatemacro\pgf@temp{%
    #3-1
  }%
  \iftikzmark{line-#2-\pgf@temp-start}{%
    \iftikzmark{line-#2-#3-first}{%
      \xdef\b@lines{({pic cs:line-#2-\pgf@temp-start} -| {pic           cs:line-#2-#3-first})}%
    }{%
      \iftikzmark{line-#2-#3-start}{%
        \xdef\b@lines{({pic cs:line-#2-\pgf@temp-start} -| {pic             cs:line-#2-#3-start})}%
      }{%
        \xdef\b@lines{(pic cs:line-#2-\pgf@temp-start)}%
      }%
    }%
  }{%
    \xdef\b@lines{}%
  }%
  \foreach \k in {#3,...,#4} {%
    \iftikzmark{line-#2-\k-first}{%
      \xdef\b@lines{\b@lines (pic cs:line-#2-\k-first) }
    }{}
    \iftikzmark{line-#2-\k-end}{%
      \xdef\b@lines{\b@lines (pic cs:line-#2-\k-end) }
    }{}
  }%
  \ifx\b@lines\pgfutil@empty
  \else
  \edef\pgf@temp{\noexpand\tikz[remember picture,overlay]\noexpand\node[fit={\b@lines},balloon] (#1) {};}%
\pgf@temp
  \fi
}

\makeatother

\usetikzlibrary{fit,calc,shadows}

% Define styles for balloons and lines
\tikzset{
  line/.style={
    draw,
    rounded corners=3pt,
    -latex
  },
  balloon/.style={
    draw,
    fill=blue!20,
    opacity=0.4,
    inner sep=4pt,
    rounded corners=2pt
  },
  comment/.style={
    draw,
    fill=blue!70,
    text=white,
    text width=3cm,
    minimum height=1cm,
    rounded corners,
    drop shadow,
    align=left,
    font=\scriptsize
  },
}

% Command to draw a balloon over two anchors

\begin{document}

\balloon{comment}{code}{1}{1}
\balloon{comment}{code}{3}{3}
\balloon{comment}{more code}{3}{3}
\balloon{comment}{more code}{5}{6}

\begin{tikzpicture}[remember picture,overlay]
\foreach \k in {0,...,7} {
\iftikzmark{line-code-\k-start}{\fill[red] (pic cs:line-code-\k-start) circle[radius=4pt];}{\message{No start for \k}}
\iftikzmark{line-code-\k-end}{\fill[blue] (pic cs:line-code-\k-end) circle[radius=2pt];}{\message{No end for \k}}
\iftikzmark{line-code-\k-first}{\fill[green] (pic cs:line-code-\k-first) circle[radius=2pt];}{\message{No first for \k}}
}
\draw[->] (0,0) -- (pic cs:line-code-5-first);
\draw[->] (0,0) -- (pic cs:line-code-5-start);
\draw[->] (0,0) -- (pic cs:line-code-5-end);
\node[above] at (0,0) {Line 5};
\end{tikzpicture}

Here is some code
\begin{lstlisting}[language=c,name=code,numbers=left]
#include <stdio.h>

int main(void)
{
    printf("hello, world\n");
    return 0;
}
\end{lstlisting}
That's enough of that
\begin{lstlisting}[language=c,name=more code,numbers=left,firstnumber=3]
#include <stdio.h>

int main(void)
{
    printf("hello, world\n");
    return 0;
}
\end{lstlisting}
\end{document}

Result:

marked listings code

share|improve this answer
    
Hi @AndrewStacey: I have problems embedding the modified tikzmark version you mentioned. What do I have to do? Which files do I need to download? –  Matthias Dec 9 '12 at 22:16
    
@Matthias Download the file tikzmark.dtx and run tex tikzmark.dtx (if you run latex tikzmark.dtx that's fine but you should ignore complaints about a missing file). Put the generated files where tex can find them. I've actually added this code as a tikzmark "extra" so you don't need all the above code (but you can try it as-is). –  Loop Space Dec 9 '12 at 22:18
    
Thanks for the quick reply :) Can I also add them somewhere to a subfolder in my document folder? –  Matthias Dec 9 '12 at 22:22
    
One more question: what does comment in \balloon{comment}{code}{3}{3} do? I don't see any effect when changing this value. –  Matthias Dec 9 '12 at 22:46
    
@Matthias: the first argument to \balloon in this example is the name to assign to the node. (Also, there are many questions on this site about how to install packages locally, including folder layouts etc.) –  cyberSingularity Dec 13 '12 at 12:37

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.