[Tex/LaTex] How to draw text-anchored tikz line below text instead of above

colorpositioningtikz-pgf

I wish to mark several pairs of from/to anchor locations in text, and use tikz to draw lines between these. The anchors are not in absolute page locations; the text determines where the endpoints of the line should be. However, the drawn line must not obscure the text. Rather, the line must appear as though it were drawn under the text instead of above.

Failed Attempt #1: Line Obscures Text

The following small example document defines \StrokeFrom and \StrokeTo macros, with the latter completing the line to the former. But the stroked line is above the text, obscuring the text below. That is what I want to change. (Run pdflatex twice for the arrows to move to their final locations.)

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

Failed Attempt #2: Transparent Line Pollutes Text Color

I have also considered simply making the stroked line partially transparent. Unfortunately, a partially-transparent colored line atop black text changes the color of that black text. It also makes fully-saturated colors (such as yellow, above) unusable.

Failed Attempt #3: Line Does Not Stay With Float

This problem was originally inspired by my answer to a question about highlighting text in a code listing while also keeping syntax highlighting. In that case I give the from/to anchors unique-per-page names, then paint all of the required lines from within \AtBeginShipout{\AtBeginShipoutUpperLeft{...}} so that the lines go onto the page before the text does. (Run pdflatex twice for the arrows to move to their final locations.)

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

Unfortunately, this strategy misbehaves if the stroke anchors are in a float which is processed on one page but actually placed on a subsequent page. The line is drawn on the page which was active when the float was being processed, not on the later page where the float actually appears. A fully-satisfactory solution must be able to handle floats that move to different pages, such as the \begin{figure}[p]...\end{figure} float in the example document above.

Best Answer

@Martin is an TeX ninja. His answer, above, is the first fully-working solution to my original question. The use of zref-abspos and zref-abspage is smart, and it was quite informative to see how to convert from zref-abspos positions to tikz coordinates. I can offer some incremental improvements on his proof-of-concept, but he gets big kudos for showing how this can be done. Thanks, Martin!

My Modifications

My version, which appears below, makes the following improvements to Martin's original:

  • The magic yshift=.5ex position adjustment is now computed automatically using \vcenter. This should make stroke positioning more robust in the face of line height changes.

  • Page comparisons are always done between absolute page numbers, never between an absolute and a regular page number. This should improve robustness in complex documents with nontrivial page numbering schemes.

  • Conversion from zref positions to tikz coordinates now takes advantage of the standard shift= diagram option instead of calculating offsets explicitly. This improves readability and avoids loading up the tikz calc library.

  • I use atbegshi instead of everypage for my per-page hook. The zref-abspage package already needs atbegshi anyway, so this avoids bringing in an additional package. A second benefit to atbegshi is that \baselineskip can now be used to set the tikz line width, which does not work well with everypage.

  • I loop over the numbered highlights using \forloop from the forloop package instead of a recursive macro call. (There is still a recursive macro call deep inside \forloop, but that is hidden away as an implementation detail.) Likewise, I'm using \ifthenelse from the ifthen package instead of lower-level TeX conditionals. I generally prefer to program up at the LaTeX layer as much as possible for better error checking and better long-term readability.

  • I've factored out some common code for extracting the absolute page and the tikz coordinates of a zref reference, again for better long-term readability.

  • I've added the warning for cross-page highlights that Martin suggested in his solution.

  • I've generally replaced the term "stroke" with "highlight" to better reflect the intended use of this code, and included @ symbols in all counter and macro names that are internal to the implementation, not intended for use by the document author.

Sample Document

I'll offer my revised solution and the example document separately, to make it easier for folks to extract just the former for their own future use. Here's the example document, which is the same as that Martin used:

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

And here is the implementation of the solution, which should be saved as highlighter.sty before rendering the example document:

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

Again, big thanks to Martin for showing us how this could be done. I'm just putting the final polish on his gemstone.

Related Question