如何在文本下方而不是上方绘制文本锚定的 tikz 线?

如何在文本下方而不是上方绘制文本锚定的 tikz 线?

我希望在文本中标记几对起始/终止锚点位置,并用于tikz在这些位置之间绘制线条。锚点不在绝对页面位置;文本决定线条的端点应该在哪里。但是,绘制的线条不得遮挡文本。相反,线条必须看起来像是绘制的在下面而不是上面的文字。

失败尝试 #1:线条遮挡了文本

以下小示例文档定义了\StrokeFrom\StrokeTo,后者完成了前者的行。但描边线位于文本上方,遮住了下方的文本。这就是我想要更改的。(运行pdflatex 两次让箭头移动到其最终位置。

\documentclass{article}
\usepackage{tikz}

\tikzset{stroke/.style = {->, yellow, line width = 1ex}}

\newcommand{\StrokeAnchor}[1]{\ensuremath{\vcenter{\hbox{\tikz[overlay, remember picture]{\coordinate (stroke #1) ;}}}}}
\newcommand{\StrokeFrom}[0]{\StrokeAnchor{from}}
\newcommand{\StrokeTo}[0]{\StrokeAnchor{to}\tikz[overlay, remember picture]{\draw [stroke] (stroke from) -- (stroke to) ;}}

\begin{document}

This text is \StrokeFrom before the figure both in the \LaTeX{} source
and in the \StrokeTo rendered document.

\begin{figure}[p]
  \StrokeFrom Once upon a time. \StrokeTo
\end{figure}

This text is after the figure in the \LaTeX{} source but before
\StrokeFrom it in the rendered document.  \StrokeTo

\end{document}

失败的尝试 #2:透明线污染了文本颜色

我也考虑过简单地将描边线设为部分透明。不幸的是,黑色文本上方部分透明的彩色线条会改变黑色文本的颜色。它还会使完全饱和的颜色(如上图的黄色)无法使用。

失败尝试 #3:线条没有保持浮动

这个问题最初受到以下启发:我对关于在代码清单中突出显示文本同时保持语法突出显示的问题的回答。在这种情况下,我为来自/到锚点赋予每个页面唯一的名称,然后从内部绘制所有必需的线条,\AtBeginShipout{\AtBeginShipoutUpperLeft{...}}以便线条在文本之前进入页面。(运行pdflatex 两次让箭头移动到其最终位置。

\documentclass{article}

\usepackage{atbegshi,ifthen,listings,tikz}
\usetikzlibrary{calc}

\tikzset{stroke/.style = {->, yellow, line width = 1ex}}

\newcounter{stroke}[page]
\newcommand{\StrokeAnchor}[1]{\ensuremath{\vcenter{\hbox{\tikz[remember picture, overlay]{\coordinate (#1 stroke \arabic{stroke});}}}}}
\newcommand{\StrokeFrom}[0]{\stepcounter{stroke}\StrokeAnchor{begin}}
\newcommand{\StrokeTo}[0]{\StrokeAnchor{end}}
\AtBeginShipout{\AtBeginShipoutUpperLeft{\ifthenelse{\value{stroke} > 0}{\tikz[remember picture, overlay]{\foreach \stroke in {1,...,\arabic{stroke}} \draw[stroke] (begin stroke \stroke) -- (end stroke \stroke);}}{}}}

\begin{document}

This text is \StrokeFrom before the figure both in the \LaTeX{} source
and in the \StrokeTo rendered document.

\begin{figure}[p]
  \StrokeFrom Once upon a time. \StrokeTo
\end{figure}

This text is after the figure in the \LaTeX{} source but before
\StrokeFrom it in the rendered document.  \StrokeTo

\end{document}

不幸的是,如果划线锚点位于浮标中,这种策略就会失效,处理但实际上放置在后续页面上。该线绘制在处理浮动时处于活动状态的页面上,而不是浮动实际出现的后续页面上。完全令人满意的解决方案必须能够处理移动到不同页面的浮动,例如\begin{figure}[p]...\end{figure}上面示例文档中的浮动。

答案1

@Martin 是一位 TeX 忍者。 他的回答如下:zref-abspos,是我最初问题的第一个完全可行的解决方案。和的使用zref-abspage很巧妙,而且了解如何从zref-abspos位置转换为tikz坐标也很有用。我可以对他的概念验证提供一些渐进式改进,但他因展示如何做到这一点而获得了巨大的赞誉。谢谢,马丁!

我的修改

我的版本如下所示,对马丁的原版做了以下改进:

  • yshift=.5ex现在使用 自动计算魔法位置调整\vcenter。这样一来,在面对行高变化时,笔画定位会更加稳健。

  • 页码比较总是在绝对页码之间进行,而不是在绝对页码和常规页码之间进行。这应该可以提高复杂文档中页码编排方案的稳健性。

  • zref从位置到坐标的转换tikz现在利用了标准shift=图表选项,而不是明确计算偏移量。这提高了可读性并避免了加载tikz calc库。

  • 我使用atbegshi而不是everypage作为我的每页钩子。无论如何,zref-abspage包已经需要了atbegshi,因此这避免了引入额外的包。 的第二个好处atbegshi\baselineskip现在可以使用 来设置tikz线宽,而 不能很好地与 配合使用everypage

  • \forloop我使用来自包的函数而不是递归宏调用来循环编号突出显示forloop。(内部深处仍然有一个递归宏调用\forloop,但它作为实现细节隐藏起来了。)同样,我使用\ifthenelse来自ifthen包的函数而不是低级 TeX 条件。我通常更喜欢尽可能在 LaTeX 层编程,以便更好地检查错误并获得更好的长期可读性。

  • 我已经提取出一些用于提取绝对页面和参考tikz坐标的常用代码zref,以便获得更好的长期可读性。

  • 我添加了 Martin 在他的解决方案中建议的跨页突出显示警告。

  • 我通常用“突出显示”替换术语“笔触”,以更好地反映此代码的预期用途,并@在所有计数器和宏名称中包含实现内部的符号,不供文档作者使用。

示例文档

我将分别提供我修改后的解决方案和示例文档,以便人们可以更轻松地提取前者以供将来使用。以下是示例文档,与 Martin 使用的相同:

\documentclass[12pt]{article}

\usepackage{highlighter}

\usepackage[paperheight=7cm]{geometry}
\renewcommand{\thepage}{\roman{page}}

\begin{document}

This text is \HighlightFrom before the figure both in the \LaTeX{}
source and in the\HighlightTo{} rendered document.

This text is \HighlightFrom before the figure both in the \LaTeX{}
source and in the\HighlightTo{} rendered document.

This text is \HighlightFrom before the figure both in the \LaTeX{}
source and in the\HighlightTo{} rendered document.

\begin{figure}[p]
    \centering
    \HighlightFrom Once upon a time.\HighlightTo
\end{figure}

This text is after the figure in the \LaTeX{} source but before
\HighlightFrom it in the rendered document.\HighlightTo

This text is after the figure in the \LaTeX{} source but before
\HighlightFrom it in the rendered document.\HighlightTo

\clearpage

\begin{figure}
    \centering
    \HighlightFrom in a far far away place\HighlightTo
\end{figure}

This text is after the figure on a new page in the \LaTeX{} source but
before \HighlightFrom it in the rendered document.\HighlightTo

\end{document}

荧光笔实现

以下是解决方案的实现,应highlighter.sty在呈现示例文档之前保存:

\RequirePackage{atbegshi}
\RequirePackage{forloop}
\RequirePackage{ifthen}
\RequirePackage{tikz}
\RequirePackage{zref-abspage}
\RequirePackage{zref-abspos}

% customizable by package user
\tikzset{highlighter/.style = {yellow, line width = \baselineskip}}

% anchor placement, with @highlight counting upward to generate unique names
\newcounter{@highlight}
\newcommand{\@HighlightAnchor}[1]{\ensuremath{\vcenter{\zsavepos{highlight-#1}}}}
\newcommand{\HighlightFrom}[0]{\stepcounter{@highlight}\@HighlightAnchor{begin-\the@highlight}}
\newcommand{\HighlightTo}[0]{\@HighlightAnchor{end-\the@highlight}}

% highlight painting, with @@highlight counting upward to consider all defined highlights
\newcounter{@@highlight}
\newcommand{\@HighlightPage}[1]{\zref@extract{highlight-#1-\the@@highlight}{abspage}}
\newcommand{\@HighlightCoords}[2]{(#1\zposx{highlight-#2-\the@@highlight}sp, #1\zposy{highlight-#2-\the@@highlight}sp)}

\AtBeginShipout{
  \AtBeginShipoutUpperLeft{
    % consider every highlight until reaching one that is undefined
    \forloop{@@highlight}{1}{\@HighlightPage{begin} > 0}{
      % page highlight if it begins and ends on the current page
      \ifthenelse{\@HighlightPage{begin} = \value{abspage}}{
        \ifthenelse{\@HighlightPage{end} = \value{abspage}}{
          % drop an anchor here so we compute the proper (x, y) offsets
          \zsavepos{highlight-draw-\the@@highlight}%
          \tikz[overlay, shift={\@HighlightCoords{-}{draw}}]{
            \draw [highlighter] \@HighlightCoords{}{begin} -- \@HighlightCoords{}{end};
          }}
        {\PackageWarning{highlighter}{highlight \protect#\the@@highlight\space crosses from page \@HighlightPage{begin} to page \@HighlightPage{end}}}}
      {}}}}

再次感谢 Martin 向我们展示了如何做到这一点。我只是在对他的宝石进行最后的润色。

答案2

您可以使用包abspos的模块zref(即加载zref-abspos包)来存储开始和结束标记的绝对位置。它们被写入文件.aux,因此您可以在文档中全局使用它们。这避免了 Caramdir 与 TikZ 节点出现的问题。然后,您可以提取缩放点()中的 X 和 Y 位置sp并使用tikz overlay绘制材质。但是,手册中说,最好只使用相对坐标。

剩下的一个问题是,虽然您现在可以从开始到结束标记进行绘制,这对于从右到左的箭头来说没问题,但当结束标记位于更靠左的下一行时,它就会失败。为了避免这种情况,您可以使用第三个宏进行绘制,该宏在之前使用,例如在段落的开头或至少在前面一行。

我的第一次尝试是这样的:

\documentclass[12pt]{article}

\usepackage{zref-abspos}
\usepackage{tikz}
\usetikzlibrary{calc}

\tikzset{stroke/.style = {->, yellow, line width = 1ex}}

\newcounter{stroke}[page]
\renewcommand{\thestroke}{\arabic{page}-\arabic{stroke}}
\newcommand{\StrokeAnchor}[1]{\leavevmode\zsavepos{stroke-#1}}
\newcommand{\StrokeFrom}[1][\thestroke]{\StrokeAnchor{begin-#1}}
\newcommand{\StrokeTo}[1][\thestroke]{\StrokeAnchor{end-#1}}
\newcommand{\DrawStroke}[1][\thestroke]{%
    \leavevmode%
    \stepcounter{stroke}%
    \zsavepos{stroke-draw-#1}%
    \begin{tikzpicture}[overlay,baseline={(0,-.5ex)}]
        \draw [stroke] 
         (\zposx{stroke-begin-#1}sp - \zposx{stroke-draw-#1}sp, \zposy{stroke-begin-#1}sp - \zposy{stroke-draw-#1}sp) --
         (\zposx{stroke-end-#1}sp   - \zposx{stroke-draw-#1}sp, \zposy{stroke-end-#1}sp   - \zposy{stroke-draw-#1}sp);
    \end{tikzpicture}%
    \ignorespaces
}

\usepackage[paperheight=7cm]{geometry}

\begin{document}

\DrawStroke This text is \StrokeFrom before the figure both in the \LaTeX{} source
and in the\StrokeTo{} rendered document.

\begin{figure}[p]
    \DrawStroke
    \StrokeFrom Once upon a time.\StrokeTo
\end{figure}

\DrawStroke This text is after the figure in the \LaTeX{} source but before
\StrokeFrom it in the rendered document.\StrokeTo

\end{document}

结果 1


现在,为了避免需要额外的宏,您可以开始使用一些技巧,例如在页面交付之前绘制页面的所有箭头。这可以在标题中完成,因为它是在页面主体之前排版的,或者使用包everypage。我在这里遇到了一些问题,只处理当前页面上的笔画。在这里,我只需浏览整个文档中所有笔画的列表,并检查它们是否在当前页面上。这种方法可以正常工作,但对于文档中的大量笔画来说,这种方法无效。

为了更彻底地测试它,我增加了笔画数,并将其改为\thepage非数字,以查看数字页码比较是否仍然有效。我还添加了一个测试,看看结束标记是否在下一页。

\documentclass[12pt]{article}

\usepackage{zref-abspos}
\usepackage{zref-perpage}
\usepackage{tikz}
\usepackage{everypage}
\usetikzlibrary{calc}

\tikzset{stroke/.style = {->, yellow, line width = 1ex}}

\newcounter{stroke}
\newcommand{\StrokeAnchor}[1]{\leavevmode\zsavepos{stroke-#1}}
\newcommand{\StrokeFrom}{\leavevmode\stepcounter{stroke}\StrokeAnchor{begin-\thestroke}}
\newcommand{\StrokeTo}{\StrokeAnchor{end-\thestroke}}
\newcommand{\DrawStroke}{%
    \zsavepos{stroke-draw-\thestroke}%
    \begin{tikzpicture}[overlay,yshift=.5ex]
        \draw [stroke]
         (\zposx{stroke-begin-\thestroke}sp - \zposx{stroke-draw-\thestroke}sp, \zposy{stroke-begin-\thestroke}sp - \zposy{stroke-draw-\thestroke}sp) --
         (\zposx{stroke-end-\thestroke}sp   - \zposx{stroke-draw-\thestroke}sp, \zposy{stroke-end-\thestroke}sp   - \zposy{stroke-draw-\thestroke}sp);
    \end{tikzpicture}%
    \ignorespaces
}

\usepackage[paperheight=7cm]{geometry}

\makeatletter
\newcommand{\CheckNextStroke}{%
   \zref@ifrefundefined{stroke-begin-\thestroke}{}{%
       \ifnum\zref@extract{stroke-begin-\thestroke}{abspage}=\c@page
         \ifnum\zref@extract{stroke-begin-\thestroke}{abspage}=\zref@extract{stroke-end-\thestroke}{abspage}\relax
           \DrawStroke%
         \else
           % some warning that begin and end are on different pages
         \fi
       \fi
       \advance\c@stroke by \@ne
       \CheckNextStroke
   }%
}

\AddEverypageHook{%
    \begingroup
    \c@stroke\@ne
    \CheckNextStroke
    \endgroup
}

\renewcommand{\thepage}{\roman{page}}
\begin{document}

This text is \StrokeFrom before the figure both in the \LaTeX{} source
and in the\StrokeTo{} rendered document.

This text is \StrokeFrom before the figure both in the \LaTeX{} source
and in the\StrokeTo{} rendered document.

This text is \StrokeFrom before the figure both in the \LaTeX{} source
and in the\StrokeTo{} rendered document.

\begin{figure}[p]
    \centering
    \StrokeFrom Once upon a time.\StrokeTo
\end{figure}

This text is after the figure in the \LaTeX{} source but before
\StrokeFrom it in the rendered document.\StrokeTo

This text is after the figure in the \LaTeX{} source but before
\StrokeFrom it in the rendered document.\StrokeTo

\clearpage

\begin{figure}
    \centering
    \StrokeFrom in a far far away place\StrokeTo
\end{figure}


This text is after the figure on a new page in the \LaTeX{} source but before
\StrokeFrom it in the rendered document.\StrokeTo

\end{document}

结果 2

答案3

我以为我有一个解决方案,但在某些情况下它确实会失败。然而,这个想法可能对一个完整的解决方案有用。

问题是你想在文本之前画线。因此,在执行时知道终点的位置会很有用\StrokeFrom。不幸的是,即使有,也remember picture无法访问节点这些只是在文档后面定义的。这样做的原因是remember picture只会导致 PGF 存储图片.aux文件中。但幸运的是,知道图片端点,因为它仅包含一个节点。

图片的存储位置通过 来访问\pgfsys@getposition#1#2,其中#1是图片的名称,#2是一些宏,将设置为 ,\pgfpoint其中包含图片的坐标#1(相对于页面的左下角)。 图片的名称是pgfid\the\pgf@picture@serial@count(不幸的是,该部分是硬编码的)。 假设和之间pgfid没有pgfpicture(或 TikZ 图片),只需在 中添加 1 即可获得 的 ID 。\StrokeFrom\StrokeTo\pgf@picture@serial@count\StrokeFrom\StrokeTo

这是一个概念验证的实现(真正的实现应该将图片的 id 存储在 .aux 文件中,以便可以pgfpictures在其中添加其他内容):

\documentclass{article}
\usepackage{tikz}

\tikzset{stroke/.style = {->, yellow, line width = 1ex}}

\makeatletter
\newcommand*\StrokeFrom{%
    \tikz[remember picture,overlay,stroke]{
        \node[coordinate] (strokeStart) {};
        \pgftransformshift{\pgfpointanchor{current page}{south west}}
        %
        \c@pgf@counta=\pgf@picture@serial@count
        \advance\c@pgf@counta by 1
        \pgfsys@getposition{pgfid\the\c@pgf@counta}\tsx@pointEnd
        %
        \pgfpathmoveto{\pgfpointanchor{strokeStart}{center}}
        \pgfpathlineto\tsx@pointEnd
        \pgfusepath{stroke}
    }%
}
\newcommand*\StrokeTo{\tikz[remember picture,overlay];}
\makeatother

\begin{document}

This text is \StrokeFrom before the figure both in the \LaTeX{} source
and in the \StrokeTo rendered document.

\begin{figure}[p]
  \StrokeFrom Once upon a time. \StrokeTo
\end{figure}

This text is after the figure in the \LaTeX{} source but before
\StrokeFrom it in the rendered document.  \StrokeTo

\end{document}

这对于第一段和来说没有问题figure,但是对于最后一段却失败了:

第 1 页

第2页

最后一段的问题是 甚至 还\StrokeFrom不够早,无法画出线条。也许有某种方法可以在段落排版之前绘制线条?当然,如果 是\StrokeTo在 之前发出的,它也不会起作用\StrokeFrom

相关内容