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'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?

share|improve this question
    
I just had the idea to use the technique shown in my answer to underline text without disturbing the text flow. My first test version looks good. –  Martin Scharrer Jul 24 '11 at 17:56
add comment

2 Answers

up vote 12 down vote accepted

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

share|improve this answer
    
Right. And I guess to make this more robust, you'll want to have some sort of counter to name-space your \zrefs, right? Because otherwise you'll get "multiply defined label" problems... –  Seamus Jun 24 '11 at 12:30
    
@Seamus: Yes, indeed (I'm actually just coding that). However, while reading your question again I figured that you actually need to execute some code at the page break to draw the first part of the frame on the first page! So while my post answers your question like it is asked in the title, it might not help you that much with the task at hand. –  Martin Scharrer Jun 24 '11 at 12:33
    
@Martin ah yes. I'd not thought of that. Presumably some code executed just before shipout of the page (depending on a switch set to true if the nodes are on different pages) would do the trick? –  Seamus Jun 24 '11 at 12:55
1  
@Seamus: Ah, you need to also check for a page break at-begin and then draw the first part then. See tex.stackexchange.com/questions/17349/… for the required textara node. –  Martin Scharrer Jun 24 '11 at 12:56
    
@Seamus: I now added an extended solution which does basically what you want. However, at this point I'm not sure if the code is more readable as the one of mdframed or listings ;-) –  Martin Scharrer Jun 24 '11 at 14:43
show 6 more comments

How I wrote in the comment here an example with mdframed and tikzsetting

Needed mdframded v0.7a

\documentclass[parskip=half-]{scrartcl}

\usepackage[%
  style=1,%
  splitbottomskip=0.8cm,splittopskip=0cm,
  innerrightmargin=1cm,innertopmargin=1cm,%
  innerlinewidth=2pt,outerlinewidth=2pt,middlelinewidth=10pt,
  backgroundcolor=red,%
  roundcorner=25pt,
  %leftline=false,
%  rightline=false,
%  %topline=false,
%  bottomline=false
  ]{mdframed}

%example 1
\mdfsetup{%
tikzsetting={draw=yellow,line width=3pt,dashed,dash pattern= on 10pt off 3pt}}

%%example 2
%\mdfsetup{%
%linecolor=blue,middlelinecolor=gray,tikzsetting={line width=2pt,double=blue,draw=red,double distance=3pt,dashed}}

%%example 3
%\mdfsetup{%
%linecolor=blue,middlelinecolor=gray,tikzsetting={dashed,draw=red}}


\usepackage{lipsum}

\begin{document}

\begin{mdframed}
\lipsum[1]
\end{mdframed}

~
%
\begin{mdframed}
\lipsum[1-8]
\end{mdframed}

\end{document}

I hope it helps. BTW: I thing I wrote the code in a simple way ;-) -- I am beginner in package writing

Example 1:

show example 1

Example 2:

show example 2

Example 3:

show example 3

share|improve this answer
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.