我正在编写包含大量代码清单的 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}
如您所见,这种方法需要大量冗余工作。我有几十个演示文稿要做,其中许多包含类似的代码示例,手动在整个代码上放置 tikzmark 似乎有点过头了。此外,如果我们需要突出显示包含长行的代码块(例如,上例中的整个“main”函数),这种方法就会失败。
事实上,两个行号是本例中唯一必要的输入。如果我能编写一些宏来接受两个数字(起始行和结束行)和注释文本,并自动完成其余操作,那就太好了。这可以通过以某种方式拦截 lstlisting 的输出并记住每行第一个非空白字符和行尾的坐标来实现吗?之后,我们将得到一个坐标数组,例如,可以将其输入到 tikz“fit”功能中。
如果宏不仅适用于内联列表,也适用于外部代码,那就太好了。我们依赖系统 TTF 字体,这就是我们以 XeTeX 和 luaTex 为目标的原因(后者更受欢迎)。由于我对 TeX 还比较陌生,因此我也非常欢迎对上述示例的任何评论和建议。
谢谢!
答案1
我会发布我目前为止所拥有的东西。毫无疑问,它有很多我没见过的缺陷(以及我见过的缺陷)。
您需要最新版本tikzmark
的TeX-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}
结果: