# Creating the appearance of notebook paper in a tcolorbox; problems drawing the correct number of lines

I am trying to create what looks like a snippet of notebook paper within a tcolorbox environment by drawing horizontal lines and a vertical line. The problem that I am having is that I don't know how to define the upper limit of the number of lines unless I hard code them. This causes a problem when the color box breaks over a page and the boxes are different heights. The lines draw from the bottom of the box. If the box has two different lengths then it is hard to make the lines look like they have been evenly spaced drawn from the top.

How can I get the height of the tcolorbox? I've tried \tcbtextheight but using that for the vertical line doesn't even work. Also, I wouldn't know how to write the loop with event divisions. Preferably I would like the lines to be the size of the line spacing of the document but that isn't required. After getting the height, I would then need the line drawing to start from the top of the box and not the bottom.

\documentclass[letterpaper, 10pt]{article}

\usepackage[skins, raster, breakable]{tcolorbox}
\usepackage{tikz}
\usetikzlibrary{decorations,decorations.markings,optics}
\usepackage[english]{babel}
\usepackage{blindtext}

\begin{document}
\pagestyle{empty}

\tikzset{normal lines/.style={gray, very thin}}

\tcbset{paper/.style={enhanced,colback=green!10,colframe=green!65!black,width=\textwidth,breakable,left=1.3in,
overlay={
\begin{tcbclipframe}
\foreach \y in {0.25, 0.5,...,10.25}
\draw[style=normal lines](0,\y in) -- (8.5in, \y in);
\draw[style=normal lines] (1.25in,0)--(1.25in, 3in);
\end{tcbclipframe}
}
}
}
\blindtext[5]
\begin{tcolorbox}[paper]
\blindtext[2]
\end{tcolorbox}

\end{document}


I propose to do this with a tcolorbox subskin of enhanced (which is suitable for breakable boxes). In the code given below, we define a skin named paper that is derived from enhanced, and make the tcolorbox style paper perform skin=paper (among others things, such as setting your desired frame and background colors). We also define three subskins paperfirst, papermiddle and paperlast respectively derived from enhancedfirst, enhancedmiddle and enhancedlast; these allow us to give the desired appearance to the box in all cases (unbroken, broken at top and not at bottom, broken at bottom and not at top, broken both at top and bottom).

Since the enhanced skin sets /tcb/geometry nodes=true, it defines nodes named frame, interior, segmentation, and title that allow us to access interesting points of the tcolorbox. Here, one can use either frame or interior to compute the box height (these don't give exactly the same result, as the /tcb/leftrule, /tcb/toprule, /tcb/rightrule and /tcb/bottomrule belong to the frame but not to the interior). In the paper skin defined below, we use the interior node to compute the number of horizontal rules to draw, and suitable combinations of frame and interior for the extent of the vertical rule, depending on whether the box is broken at the top and whether it is broken at the bottom. The calculation of the number of horizontal rules to draw can be done with \pgfmathtruncatemacro, for instance (it could also be done with int() inside a \pgfmathsetmacro call).

Note: I made a small change to your normal lines style. The line width is larger, but the rules are drawn with opacity=..., as defined by /tcb/paper/rule opacity. This drawing is done inside a transparency group whose purpose is to make sure that rule crossings aren't darker than other places in the rules.

\documentclass{article}
\usepackage{tcolorbox}
\tcbuselibrary{breakable, skins}
\usepackage{tikz}
\usetikzlibrary{calc}
\usepackage{lipsum}

\makeatletter

\tikzset{normal lines/.style={gray, thin}}
\tcbset{paper/left margin/.initial=1.25in,
paper/line spacing/.initial=1cm,
paper/rule opacity/.initial=0.4,
paper/.style={
skin=paper, colback=green!10, colframe=green!65!black,
breakable, left=1.3in,
}
}

\newif\if@paper@broken@top
\newif\if@paper@broken@bottom
\pgfkeys{/tcb/paper/broken top/.is if=@paper@broken@top,
/tcb/paper/broken bottom/.is if=@paper@broken@bottom
}

% The various parts don't have the same skin when a box is broken. This also
% influences where we want the vertical rule to extend to.
\tcbsubskin{paperfirst}{enhancedfirst}{
paper/broken top=false, paper/broken bottom=true
}
\tcbsubskin{papermiddle}{enhancedmiddle}{
paper/broken top=true, paper/broken bottom=true
}
\tcbsubskin{paperlast}{enhancedlast}{
paper/broken top=true, paper/broken bottom=false
}

\tcbsubskin{paper}{enhanced}{
skin first=paperfirst, skin middle=papermiddle, skin last=paperlast,
paper/broken top=false, paper/broken bottom=false,
overlay={
\begin{tcbclipinterior}
\begin{scope}[opacity=\pgfkeysvalueof{/tcb/paper/rule opacity},
transparency group]
% Make sure the vertical rule extends exactly as far as we want, depending
% on where the box is broken.
\if@paper@broken@top
\coordinate (top for vert rule) at (frame.north west);
\else
\coordinate (top for vert rule) at (interior.north west);
\fi
%
\if@paper@broken@bottom
\coordinate (bottom for vert rule) at (frame.south west);
\else
\coordinate (bottom for vert rule) at (interior.south west);
\fi
%
\draw[transform canvas={xshift=\pgfkeysvalueof{/tcb/paper/left margin}},
style=normal lines] (top for vert rule) -- (bottom for vert rule);
%
\path let \p1=($(interior.north)-(interior.south)$) in
\pgfextra{%
\pgfmathtruncatemacro{\tmp}{veclen(\p1) /
\pgfkeysvalueof{/tcb/paper/line spacing}}%
\xdef\paperskin@nblines{\tmp}};
\foreach \i in {1,..., \paperskin@nblines} {
\pgfmathsetlengthmacro{\paperskin@shift}{
-\i*\pgfkeysvalueof{/tcb/paper/line spacing}}
\draw[style=normal lines]
([yshift=\paperskin@shift]interior.north west) --
([yshift=\paperskin@shift]interior.north east);
}
\end{scope}
\end{tcbclipinterior}
},
}

\makeatother

\pagestyle{empty}

\begin{document}

\lipsum[1]
\begin{tcolorbox}[paper]
\lipsum[2]
\end{tcolorbox}

\lipsum[3]
\begin{tcolorbox}[paper]
\lipsum[4-5]
\end{tcolorbox}

\end{document}


Unbroken box:

first part of the broken box:

last part of the broken box:

A middle part (broken at top and at bottom) would also be drawn fine, with the vertical rule extending just as far as desired:

Note: the following part of my code:

\tcbsubskin{paper}{enhanced}{
...
overlay={
...
% Make sure the vertical rule extends exactly as far as we want, depending
% on where the box is broken.
\if@paper@broken@top
\coordinate (top for vert rule) at (frame.north west);
\else
\coordinate (top for vert rule) at (interior.north west);
\fi
%
\if@paper@broken@bottom
\coordinate (bottom for vert rule) at (frame.south west);
\else
\coordinate (bottom for vert rule) at (interior.south west);
\fi


may be written this way too:

\newcommand*{\paper@defcoord}[3]{
\coordinate (#2 for vert rule) at (#1.#3 west);
}

\tcbsubskin{paper}{enhanced}{
...
overlay={
...
% Make sure the vertical rule extends exactly as far as we want, depending
% on where the box is broken.
\edef\tmp{\if@paper@broken@top frame\else interior\fi}
\expandafter\paper@defcoord\expandafter{\tmp}{top}{north}
\edef\tmp{\if@paper@broken@bottom frame\else interior\fi}
\expandafter\paper@defcoord\expandafter{\tmp}{bottom}{south}


but the first way seems much easier to read, therefore this is the one I decided to keep in the complete example.

• I love this, but I made paper/line spacing/.initial = \baselineskip. Thanks! – K. Shores Aug 25 '19 at 20:44
• Well, the parameters are designed to be customized, no problem. Glad you liked it! – frougon Aug 25 '19 at 20:46

Instead of a hand drawn grid, you can use a TikZ grid with a large xstep. Following code shows a possible solution (inspired in example on tcolorboxdocumentation, page 131). It doesn't solve the problem with broken boxes and adjusting the vertical position.

\documentclass{article}
\usepackage[most]{tcolorbox}
\usepackage{lmodern}
\usepackage{blindtext}

\newtcolorbox{notebook}{
enhanced,
breakable,
colback=green!10,
colframe=green!65!black,
left=1.3in,
underlay={%
\begin{tcbclipinterior}
\draw[help lines, ystep=\baselineskip, xstep=\linewidth,
shift={(interior.north west)}](interior.south west) grid (interior.north east);
\draw[help lines] ([xshift=1.25in]interior.north west)--
([xshift=1.25in]interior.south west);
\end{tcbclipinterior}}
}

\begin{document}
\blindtext[1]
\begin{notebook}
\blindtext[3]
\end{notebook}
\end{document}