使用 tikzmark 自动对列表进行背景着色

使用 tikzmark 自动对列表进行背景着色

我正在编写包含大量代码清单的 Beamer 演示文稿,我需要用半透明的气球突出显示部分代码。然而,我想出了一个使用 tikzmark 变体的解决方案。在清单中,我手动放置锚点,然后将气球拉到它们上面。

\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}

第 1 帧

第 2 帧

如您所见,这种方法需要大量冗余工作。我有几十个演示文稿要做,其中许多包含类似的代码示例,手动在整个代码上放置 tikzmark 似乎有点过头了。此外,如果我们需要突出显示包含长行的代码块(例如,上例中的整个“main”函数),这种方法就会失败。

事实上,两个行号是本例中唯一必要的输入。如果我能编写一些宏来接受两个数字(起始行和结束行)和注释文本,并自动完成其余操作,那就太好了。这可以通过以某种方式拦截 lstlisting 的输出并记住每行第一个非空白字符和行尾的坐标来实现吗?之后,我们将得到一个坐标数组,例如,可以将其输入到 tikz“fit”功能中。

如果宏不仅适用于内联列表,也适用于外部代码,那就太好了。我们依赖系统 TTF 字体,这就是我们以 XeTeX 和 luaTex 为目标的原因(后者更受欢迎)。由于我对 TeX 还比较陌生,因此我也非常欢迎对上述示例的任何评论和建议。

谢谢!

答案1

我会发布我目前为止所拥有的东西。毫无疑问,它有很多我没见过的缺陷(以及我见过的缺陷)。

您需要最新版本tikzmarkTeX-SX Launchpad 网站。(请确保您获取的是最新版本 - 我曾收到过下载时出现问题的报告,但我尚未设置任何类型的修订编号;结果将在末尾附近tikzlibrarytikzmark.code.tex有一个命令。)\pgfmark

下面将修改后的 tikzmark 放在每行的开头、结尾和第一个非空白字符处。它尚未处理自动换行。标记根据行号进行编号,并且start在 listings 环境的开头有一个秘密的初始标记。

\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, lines and marked code areas
\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}{7}{8}

\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}

结果:

标记清单代码

相关内容