[Tex/LaTex] Test if a paragraph has a page break in it

conditionalspage-breakingparagraphs

I'm trying to use TikZ to make fancier frames. I'm using the clever \tikzmark thing that Andrew Stacey wrote to put nodes at the top left and bottom right of the body of an environment.

Then I use these nodes to draw funky decorations. I'm sure I'm reinventing the wheel, but I'm learning so it's all good. I know what I want to do is possible because mdframed and listings both manage it, but their code is hard for me to read and it's not clear what the relevant part is…

Now, this works fine until the environment spans a page. Then the nodes are on different pages, and when the end code of the environment comes to draw the decorations, it can't find the top left node (because its been shipped out on the last page).

So what I'd like to do is have a different set of behaviours for when the environment is broken across a page. What are the simplest ways to test when a paragraph is broken like this?

Best Answer

You can use zref with the abspos and user modules (e.g. load the zref-abspos and zref-user packages) and add a \zlabel{labelname} to the \tikzmark at the beginning of the paragraph as well one at the end of the paragraph. If you using an environment simply place one at begin and end. Then you can check at the end of the environment if the absolute page numbers of both are identical or not. For this you can use:

\ifnum\zref@extract{<begin-label>}{abspage}=\zref@extract{<end-label>}{abspage}\relax
   <same page>
\else
   <different page>
\fi

(Note: Maybe \zref@extractdefault is better because you can provide a default value used for the first runs when the label is not set yet.)

See also my answer to How to draw text-anchored tikz line below text instead of above? where I do something very similar!


Here a solution I now come up with. It draws the first border half in the begin-macro if the end-macro is not at the same page. Also the case where the environment spans more than two pages, e.g. starts at page 1 and ends at page 3, is covered by extra code.

\documentclass[twoside]{book}

\usepackage{zref-abspage}
\usepackage{zref-user}
\usepackage{tikz}
\usepackage{atbegshi}
\usetikzlibrary{calc}

\makeatletter
\newcommand{\currentsidemargin}{%
  \ifodd\zref@extract{textarea-\thetextarea}{abspage}%
    \oddsidemargin%
  \else%
    \evensidemargin%
  \fi%
}

\newcounter{textarea}
\newcommand{\settextarea}{%
   \stepcounter{textarea}%
   \zlabel{textarea-\thetextarea}%
   \begin{tikzpicture}[overlay,remember picture]
    % Helper nodes
    \path (current page.north west) ++(\hoffset, -\voffset)
        node[anchor=north west, shape=rectangle, inner sep=0, minimum width=\paperwidth, minimum height=\paperheight]
        (pagearea) {};
    \path (pagearea.north west) ++(1in+\currentsidemargin,-1in-\topmargin-\headheight-\headsep)
        node[anchor=north west, shape=rectangle, inner sep=0, minimum width=\textwidth, minimum height=\textheight]
        (textarea) {};
  \end{tikzpicture}%
}

\tikzset{tikzborder/.style={line width=1mm,red,double=blue}}

\newcounter{tikzborder}
\newcounter{tikzborderpages}
\newenvironment{tikzborder}[1][]{%
    \medskip\par
    % Allow user to overwrite the used style locally
    \ifx&#1&\else
        \tikzset{tikzborder/.style={#1}}%
    \fi
    \settextarea
    \stepcounter{tikzborder}%
    \tikz[overlay,remember picture] \coordinate (tikzborder-\thetikzborder);% Modified \tikzmark macro
    \zlabel{tikzborder-begin-\thetikzborder}%
    % Test if end-label is at the same page and draw first half of border if not
    \ifnum\zref@extract{tikzborder-begin-\thetikzborder}{abspage}=\zref@extract{tikzborder-end-\thetikzborder}{abspage} \else
        \begin{tikzpicture}[overlay,remember picture]
            \draw [tikzborder]
                let \p0 = (textarea.north west), \p1 = (tikzborder-\thetikzborder), \p2 = (textarea.south east) in
                (\x0-\fboxsep-.5\pgflinewidth,\y2-\fboxsep-.5\pgflinewidth)
                 |-
                (\x2+\fboxsep+.5\pgflinewidth,\ht\strutbox+\fboxsep+.5\pgflinewidth)
                 --
                (\x2+\fboxsep+.5\pgflinewidth,\y2-\fboxsep-.5\pgflinewidth)
                ;
        \end{tikzpicture}%
    % If it spreads over more than two pages:
    \setcounter{tikzborderpages}{\numexpr-\zref@extract{tikzborder-begin-\thetikzborder}{abspage}+\zref@extract{tikzborder-end-\thetikzborder}{abspage}}
    \ifnum\value{tikzborderpages}>1
        \AtBeginShipoutNext{\tikzborderpage}%
    \fi
    \fi
}{%
    \zlabel{tikzborder-end-\thetikzborder}%
    % Test if begin-label is at the same page and draw while border if so
    \ifnum\zref@extract{tikzborder-begin-\thetikzborder}{abspage}=\zref@extract{tikzborder-end-\thetikzborder}{abspage}
        \begin{tikzpicture}[overlay,remember picture]
            \draw [tikzborder]
                let \p0 = (textarea.north west), \p1 = (tikzborder-\thetikzborder), \p2 = (textarea.south east) in
                (\x0-\fboxsep-.5\pgflinewidth,\y1+\ht\strutbox+\fboxsep+.5\pgflinewidth)
                 |-
                (\x2+\fboxsep+.5\pgflinewidth,-\dp\strutbox-\fboxsep-.5\pgflinewidth)
                 |-
                (\x0-\fboxsep-.5\pgflinewidth,\y1+\ht\strutbox+\fboxsep+.5\pgflinewidth)
                 -- cycle
                ;
        \end{tikzpicture}%
    % Otherwise draw second half of border
    \else
        \settextarea
        \begin{tikzpicture}[overlay,remember picture]
            \draw [tikzborder]
                let \p0 = (textarea.north west), \p1 = (tikzborder-\thetikzborder), \p2 = (textarea.south east) in
                (\x0-\fboxsep-.5\pgflinewidth,\y0+\fboxsep+.5\pgflinewidth)
                 |-
                (\x2+\fboxsep+.5\pgflinewidth,-\dp\strutbox-\fboxsep-.5\pgflinewidth)
                 --
                (\x2+\fboxsep+.5\pgflinewidth,\y0+\fboxsep+.5\pgflinewidth)
                ;
        \end{tikzpicture}%
    \fi
    \par\medskip
}

\newcommand{\tikzborderpage}{%
  \settextarea
  \begin{tikzpicture}[overlay,remember picture]
      \draw [tikzborder]
          ([shift={(-\fboxsep-.5\pgflinewidth, \fboxsep+.5\pgflinewidth)}]textarea.north west)
           --
          ([shift={(-\fboxsep-.5\pgflinewidth,-\fboxsep-.5\pgflinewidth)}]textarea.south west)
          ;
      \draw [tikzborder]
          ([shift={( \fboxsep+.5\pgflinewidth, \fboxsep+.5\pgflinewidth)}]textarea.north east)
           --
          ([shift={( \fboxsep+.5\pgflinewidth,-\fboxsep-.5\pgflinewidth)}]textarea.south east)
          ;
  \end{tikzpicture}%
  \addtocounter{tikzborderpages}{-1}%
  \ifnum\value{tikzborderpages}>1
    \AtBeginShipoutNext{\tikzborderpage}%
  \fi
  \vspace{-\baselineskip}% Compensate for the generated extra line at begin of the page. No idea why exactly this happens. Also \baselineskip seems not to be 100% right.
}

\makeatother

\usepackage{lipsum}
\newcommand\xlipsum[1][]{{\let\par\relax\lipsum*[#1]}}

\begin{document}
\begin{tikzborder}
  \xlipsum[1]
\end{tikzborder}

\lipsum[2-4]

\begin{tikzborder}
  \xlipsum[1]
\end{tikzborder}

\lipsum[2-4]

\begin{tikzborder}
  \xlipsum[1-9]
\end{tikzborder}

\lipsum[2-4]

\begin{tikzborder}
  \xlipsum[1-20]
\end{tikzborder}


\end{document}

(The frame is not displayed fully right in the picture because of the low resolution etc.
Click to enlarge)

Result