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 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.

share|improve this question
    
Mmm, \begin{pgfonlayer}{background} ... \end{pgfonlayer} doesn't work as well. Ok, on second thought thats understandable. The layers are only relative to each other in one tikzpicture. –  Martin Scharrer May 27 '11 at 20:43
    
Using the current page node I can get the yellow line underneath the text and with a correct horizontal position. I am unable to get the correct vertical position. –  Frédéric May 27 '11 at 22:58
    
@Frédéric: Maybe you could post your partial solution. –  Caramdir May 28 '11 at 0:50
    
I'd be interested to see a complete solution to this problem. It might be useful in other situations too. –  Caramdir May 30 '11 at 16:58
add comment

3 Answers 3

You can use the abspos module of the zref package, i.e. load the zref-abspos package, to store the absolute positions of the begin and end markers. They are written to the .aux file, so you can then use these globally in the document. This avoids the issues Caramdir had with the TikZ nodes. You can then extract the X and Y positions in scaled points (sp) and use an tikz overlay to draw the material. However, it is still better to use the coordinates only relative, the manual says.

One remaining issue is that while you can now draw from the begin to the end marker, which is fine for right-to-left arrows, it fails when the end marker is in the next line further to the left. To avoid this you could use a third macro for the drawing which is used beforehand, e.g. at the begin of the paragraph or at least one line beforehand.

My first try looks like this:

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

Result 1


Now to avoid the need for an extra macro you would start to use some tricks to e.g. draw all arrows of the page before the page is shipped. This could be done in the header, because it is typeset before the page body, or using the everypage package. I had here some issues with processing only the strokes on the current page. Here I simply go through the list of all strokes in the whole document and check if they are on the current page. This works OK but would be ineffective for large number of strokes in a document.

To test it more throughly I increased the number of strokes and changed \thepage to be non-numeric to see if the numeric page number comparisons still work. I also added a test if the end marker is on the next page.

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

Result 2

share|improve this answer
2  
Brilliant! Well done! –  Jake May 30 '11 at 19:41
1  
This is indeed brilliant, as @Jake says. Clearly you have earned the bounty that @Caramdir offered. I think I can improve on it, though. The result should be the same, but the implementation may be more accessible to LaTeX users who are not also TeX experts. I think that my incremental improvements do ultimately make for a better solution, but the bounty really ought to be yours. What is the proper etiquette here? –  Ben Liblit Jun 2 '11 at 0:10
    
@Ben: Thanks. Sure it can be improved. The automatic processing of each page is a little tricky and there might be much better ways to do it. The code is just to show one way and also to proof the concept. –  Martin Scharrer Jun 2 '11 at 11:29
    
so should I post my improved version as another possible answer to this question? It feels a little self-serving to do that and later (perhaps) click the acceptance check box for my own answer to my own question. –  Ben Liblit Jun 2 '11 at 15:17
    
@Ben: I don't mind if you post your final solution as an additional answer and accept it. Just please point out that it is based on mine at the beginning. –  Martin Scharrer Jun 2 '11 at 15:20
add comment

I though, I had a solution, but it does fail in some cases. However the idea might be useful for a complete solution.

The problem is that you want to draw the line before the text. So, it would be useful to know the position of the end point while executing \StrokeFrom. Unfortunately, even with remember picture it is not possible to access the position of nodes that are only defined later in the document. The reason for this is that remember picture only causes PGF to store the location of the picture in the .aux file. But fortunately it is sufficient to know the location of the picture of the end point, since it only contains a single node.

The stored location of a picture is accessed with \pgfsys@getposition#1#2, where #1 is the name of the picture and #2 is some macro that will be set to a \pgfpoint with the coordinates of the picture #1 (relative to the bottom left corner of the page). The name of the picture is pgfid\the\pgf@picture@serial@count (unfortunately, the pgfid part is hard-coded). Assuming that there is no pgfpicture (or TikZ picture) between \StrokeFrom and \StrokeTo, one can simply add 1 to \pgf@picture@serial@count in \StrokeFrom to get the id of \StrokeTo.

Here is a proof-of-concept implementation (a real implementation should store the ids of the pictures in the .aux file, so that one can add other pgfpictures in between):

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

This works without problems for the first paragraph and the figure, but fails for the last paragraph:

page 1

page 2

The problem in the last paragraph is that even \StrokeFrom is not early enough to draw the line. Maybe there is some way to draw things before a paragraph is typeset?. Also of course it doesn't work if \StrokeTo is issued before \StrokeFrom.

share|improve this answer
    
See my \DrawStroke macro or everypage ideas in my answer. This should also work for your code here. –  Martin Scharrer May 30 '11 at 19:31
    
I was just playing around with some code for highlighting and came up with something quite similar to this, so I immediately ran to the TeX-SX site to try to find somewhere to post it, only to find that you'd gotten there first. I have a few modifications, but the basic idea is the same. –  Loop Space Nov 9 '11 at 13:47
add comment
up vote 6 down vote accepted

@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.

share|improve this answer
    
Great! I'll award the bounty to Martin. –  Caramdir Jun 2 '11 at 17:47
    
@Caramdir: yes, please do. He earned it. And thanks for posting that bounty in the first place! –  Ben Liblit Jun 2 '11 at 18:16
add comment

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.