# How to make continuous (but breakable) vertical rule along left side of paragraph?

I'm looking for a way in Plain TeX to make a continuous vertical rule along the left side of a paragraph (or arbitrary block of text), such that it observes normal page-break rules. I started with the following, which looks exactly how I want, except of course it has the undesirable side-effect of preventing page breaks for large runs of text, since it uses \vbox:

% Definition
\def\codeblock#1{%
\hbox{%
{\vrule width .6pt}%
\hskip 1em%
\vbox{\tt #1}%
}%
}

% Example
\codeblock{%
\hbox{void main(int argc, char *argv[]) \char123}%
\hbox{{\ }{\ }printf("Hello, world!{\char92}n");}%
\hbox{\char125}%
}


As a workaround, I'm currently resorting to breaking things into separate lines and a series of kludged non-continous-but-overlapping vertical rule segments:

% Definition
\def\codeline#1{%
\hbox{%
\vrule width .6pt height .85em depth .46em%
\hskip 1em%
\tt #1%
}%
\vskip -.11em%
}

% Example
\codeline{void main(int argc, char *argv[]) \char123}
\codeline{{\ }{\ }printf("Hello, world!{\char92}n");}
\codeline{\char125}


In a perfect world, I'd like to simply pass an arbitrary block of text to a macro and have it apply normal pagination rules to the block of text, while at the same time making a vertical rule down the left. I don't have much experience with TeX but I suspect the answer is that it's not trivial?

While typing this question, a similar question appeared in the margin. I will definitely give that a try — and it looks really powerful and well thought out — but it also seems quite heavyweight for something that I would think ought to be reasonably straightforward. Also, not that I have anything against LaTeX, but I'd prefer to use a solution as close to Plain TeX as possible, so that I can have some hope of understanding it.

-

Try the file below. You asked for Plain TeX - so this must be run with pdftex (not pdflatex). This file (vert.tex) and some test files are hosted on GitHub at https://github.com/m4dc4p/vert.

%% Justin Bailey 2011
%% jgbailey@codeslower.com
%%
%% To use: surround paragraphs to place a rule
%% with \startrule and \endrule.
%%
%% E.g.:
%%
%% \startrule
%% This paragraph will have a rule around it.
%% \endrule
%%
%% Multiple paragraphs can be spanned as well. Rules will break across
%% pages. Unfortunately, glue between paragraphs will not stretch
%% in that case, but usually it's not noticeable.
%%
%% You can change the offset of the rule by setting \ruleoffset. Rules
%% are always offset from the left margin.
%%
%% A simple \codeblock environment is included too.
%%
%% To use it, surround the code with \codeblock{
%%
%% }
%%
%% Code must appear in the group. The group must immediately follow \codeblock.
%% Code is set ragged right, obeying newlines, using typewriter font.

\newdimen\ruleoffset \ruleoffset=-10pt %% Horizontal offset for the
%% rule. This is the parameter that
%% should be set by the user.

\newdimen\roffset %% For calculating offset from left margin for rule.
\newbox\savedbox \newif\ifoutputran \newbox\rulebox
\newdimen\splitheight \newdimen\interparskip
\newtoks\codetoks

%% The basic idea is to use TeX's output routines
%% to determine if our ruled box is too high. If so
%% we split the box, set a rule, and put the rest on
%% the next page. \endrule does most of the work.
\def\startrule{%%
%% Clear our saved box.
\setbox0=\hbox{\box\savedbox}\par%%
%% Save \ruleoffset for later use.
%% Save \prevdepth so we can use it to calculate interparagraph
%% glue.
\xdef\theprevdepth{\the\prevdepth}%%
%% Save material proceeding \startrule unless we're at
%% the top of the page.
\output={\accum}\ifnum\pagegoal<\maxdimen\relax%%
\vfil\break\outputranfalse%%
%% Capture everything up to \endrule in a \vbox.
\fi\setbox\rulebox=\vbox\bgroup}
\def\endrule{\egroup%%
%% Test if \rulebox + \savedbox will overflow
%% the page.
\output{\test}%%
\setinterparskip\setroffset%%
\setbox4=\makerule\copy\rulebox/%%
%% \vfuzz and \vbadness set to avoid overfull/underfull warnings
%% while testing.
\vfuzz=10in\box4\penalty0%%
%% Restore default output routine.
\output={\savedout}\vfuzz=\thevfuzz%%
\ifoutputran\outputranfalse%%
%% The material overflowed the page, so we split off what
%% can fit (\splitheight) and put that on the page.
\makerule\box0/\break\vfuzz=\thevfuzz%%
%% Put remaining material in a ruled box unless
%% nothing was left (\ifvoid)
\ifvoid\rulebox%%
\else\startrule\unvbox\rulebox\endrule%%
\fi%%
\fi}

%% Ensures spaces at the beginning of the line are always
%% preserved. TABs will not be. Thanks to TeX for the Impatient
%% (eplain) for \alwayspace.
{\gdef\alwaysspace{\hglue\fontdimen2\the\font \relax}%%
\obeyspaces\gdef {\alwaysspace}}

%% Define new lines so that in \codeblock they don't start a new
%% paragraph - they just insert a line break.
{\catcode\^^M=\active \gdef^^M{\null\hfil\break} \global\let\ret=^^M}

\newtoks\codetoks
%% \codeblock must be followed by a group or it has no effect.
%% When followed by a group, the text found will be set on
%% individual lines as they appear in the group (i.e. new lines
%% are obeyed). The entire group will be have a rule next to it.
%% The group is also set in typewriter font, with ragged-right
%% margins.
%%
%% Note that text in the group is NOT set verbatim.
\def\codeblock{\codetoks={}%%
%% Removes final lineskip if one was there.
\gdef\endo{\unpenalty\endrule\prevdepth=0pt\relax}%%
%% Removes initial newline, if one was there. Otherwise, reinsert the
%% token captured.
\gdef\ignorenewline{\ifx\next\ret%%
\else\next%%
\fi}%%
\gdef\do{\ifx\next\bgroup%%
\codetoks={\startrule\noindent\bgroup%%
\ttraggedright%%
\parindent=0pt%%
\tt%%
\aftergroup\endo%%
\ignorespaces%%
\catcode\^^M=\active\obeyspaces%%
\afterassignment\ignorenewline\let\next= }%%
\fi\the\codetoks}%%
\ignorespaces\afterassignment\do\let\next= }

\edef\savedout{\the\output}
%% Accumulate vertical material into a box.
\def\accum{\global\setbox\savedbox=\vbox{\unvbox\savedbox\unvbox255\unskip}}
%% An output routine that tells us it ran and
%% throws away the page built.
\def\test{\global\outputrantrue%%
\global\splitheight=\vsize%%
\setbox0=\vbox{\box255}}

\def\makerule#1/{\vbox{\unvcopy\savedbox%%
\vskip\interparskip\par\penalty0%%
\hbox{\hskip \roffset \vrule \hss \hskip -\roffset \strut#1\strut}}}%%
\def\setinterparskip{\setbox2=\vtop{X\par}%%
\setbox4=\vtop{\unvcopy\rulebox}%%
%% Set \prevdepth so inter-paragraph glue is calculated based
%% on the paragraph that really preceded \startrule, not our
%% fake paragraph.
\setbox0=\vbox{\copy2\par\prevdepth=\theprevdepth\copy4}%%

-
Justin, this is great, really great. Thank you. The only thing I think I'll need to change is to remove the \advance\dimen0 by -1ex and the depth-1ex, since I want the rules to extend the full heights of lines rather than starting and ending in the middle. I think I also want the rule to line up with the left text margin (rather than offset 10pt to the left of it) since this is for code interleaved with documentation... but this is really perfect to start with. Thank you so much. –  Todd Lehman Dec 14 '11 at 0:26
If it works for you would mind marking this as an answer? I had a lot of fun figuring out how to do this, by the way. What a challenge! –  Justin Bailey Dec 14 '11 at 16:28
@ToddLehman I updated the code and moved the repository. You should find that it breaks rules across pages much better now. It also draws rules to the full height and depth of the paragraph. Finally, I added a \codeblock macro for you. –  Justin Bailey Dec 17 '11 at 0:46
@Justin Bailey: Please is the difference between your \zerobox and \null significant? Also, \parbox is a LaTeX command. This might create problems if your code is run with LaTeX. –  Ahmed Musa Dec 17 '11 at 1:23
@AhmedMusa: I just saw that Justin is redefining the output routine globally, so using that code in LaTeX would break a lot of things. –  Bruno Le Floch Dec 17 '11 at 4:42

Copy the code for environment leftbar from package framed and you'll have a vertical line with a possibility of page breaks. It can easily be adopted to plain TeX.

-
I'll check that out. Thanks, Herbert! (And also thanks to whoever added the {plain-tex} tag.) –  Todd Lehman Nov 22 '11 at 21:19

Another LaTeX solution.

tcolorbox is your friend (with its xparse library, the two optional arguments can be handled easily):

\documentclass{article}
\usepackage{xparse}
\usepackage[many]{tcolorbox}
\usepackage{lipsum}

\DeclareTColorBox{mybox}{O{orange}O{0cm}}{
breakable,
outer arc=0pt,
arc=0pt,
colback=white,
rightrule=0pt,
toprule=0pt,
top=0pt,
right=0pt,
bottom=0pt,
bottomrule=0pt,
colframe=#1,
enlarge left by=#2,
width=\linewidth-#2,
}

\begin{document}

\lipsum[4]
\begin{mybox}
\lipsum[4]
\end{mybox}
\lipsum[4]
\begin{mybox}[green][2cm]
\lipsum[4]
\end{mybox}

\end{document}

-
Does the right side of the box has a white space? –  Enthusiastic Student Nov 10 '14 at 17:04
@EnthusiasticStudent I just suppresed it in my edit. –  Gonzalo Medina Nov 10 '14 at 17:06
What did you change for it? –  Enthusiastic Student Nov 10 '14 at 17:07
@EnthusiasticStudent I added right=0pt, to kill the default distance between right margin of the framed text and right margin f the document. –  Gonzalo Medina Nov 10 '14 at 17:16
One last question, can you make the line thicker? –  Enthusiastic Student Nov 10 '14 at 17:18

Here is a LaTeX solution. I am afraid, it isn't plain TeX.

\documentclass{article}
\RequirePackage{ltxkeys}
\makeatletter
framed;
graphicx;
xcolor|dvipsnames;
lipsum;
}
\ltxkeys@declarekeys[FTB]{ruledtxtblk}[ftb@]{%
cmd/title/Text block;
cmd/leftgap/4pt;
cmd/topruleskip/1em;
cmd/framerule/1.25pt;
cmd/rulecolor/gray!75;
cmd/textcolor/black;
cmd/lefttoprule/1.5em;
cmd/righttoprule/1.5em;
cmd/titleleftskip/1em;
cmd/titlerightskip/1em;
}
\AfterStartOfDocument{%
\newcounter{ruledtextblock}[section]%
\renewcommand\theruledtextblock{\thesection.\arabic{ruledtextblock}}%
}%
\newlength\ftb@letterheight
\newenvironment{ruledtextblock}[1][]{%
\parindent\z@pt
\ltxkeys@setkeys[FTB]{ruledtxtblk}{#1}%
\settoheight\ftb@letterheight{\textbf{\ftb@title}}%
\settowidth\@tempdima{\ftb@title\ \theruledtextblock}%
\def\FrameCommand{%
\hspace{-\ftb@leftgap}%
{\color{\ftb@rulecolor}%
\vrule width \ftb@framerule
}%
\hspace{\dimexpr\ftb@leftgap-\ftb@framerule\relax}%
}%
\refstepcounter{ruledtextblock}%
\makebox[0pt][l]{%
\hspace{\dimexpr-\parindent+\ftb@framerule\relax}%
\hspace{\ftb@topruleskip}%
{\color{\ftb@rulecolor}%
\rule{\ftb@lefttoprule}{\ftb@framerule}%
}%
\hspace{\ftb@titleleftskip}%
\raisebox{-.5\ftb@letterheight}[0pt]{%
\textbf{\ftb@title\ \theruledtextblock}%
}%
\hspace{\ftb@titlerightskip}%
{\color{\ftb@rulecolor}%
\rule{\ftb@righttoprule}{\ftb@framerule}%
}%
}\\[.5\baselineskip]%
\color{\ftb@textcolor}%
\noindent\ignorespaces
}{%
\makebox[0pt][l]{%
\hspace{-\ftb@leftgap}%
{\color{\ftb@rulecolor}%
\rule{\dimexpr\columnwidth+\ftb@leftgap}{\ftb@framerule}%
}%
}%
\endMakeFramed
\@ignoretrue
}
\makeatother

\begin{document}
\section{Test section}
\begin{ruledtextblock}[title=Example,rulecolor=blue,titlerightskip=1em,
lefttoprule=2em,righttoprule=23em,leftgap=1em,topruleskip=1em,
textcolor=purple]
\lipsum[1-9]
\end{ruledtextblock}
\par

\begin{ruledtextblock}[title=Example]
\lipsum[1-9]
\end{ruledtextblock}
\end{document}


-
This works nicely but it does not reset the text color at the end. –  André Mar 23 '12 at 1:02
@André: I don't know what you meant by 'does not reset the text color at the end', but try the updated example. –  Ahmed Musa Mar 24 '12 at 21:00
The updated example abort with the message: LaTeX error: The font size command \normalsize is not defined: there is probably something wrong with the class file. The original version changed (correctly) the text color inside the frameblock; however, if some text was added after, it was the color of the frameblock text rather than the original (black). But that's a minor thing to deal with. –  André Mar 24 '12 at 23:41
@André: There was no \documentclass. I have removed the call to 'filecontents'. It should work for you now. –  Ahmed Musa Mar 27 '12 at 22:39
It does run without error. However, if you insert some text between the two ruledtextblock, it will be the color of the text inside the first ruledtextblock even though the text before the first ruledtextblock was black. So, one has to reset the color explicitly after using a ruledtextblock. This is what I meant by it does not rese the color. But, otherwise, it works beautifully. –  André Mar 28 '12 at 0:56

This can also be done with mdframed:

## Code:

\documentclass{article}
\usepackage{xcolor}
\usepackage{mdframed}
\usepackage{showframe}
\usepackage{lipsum}

\newmdenv[
topline=false,
bottomline=false,
rightline=false,
linewidth=3pt,
linecolor=orange,
innerleftmargin=5pt,
leftmargin=10pt,
rightmargin=0pt,
innerrightmargin=0pt,
]{textbox}

\begin{document}
\lipsum[4]
\begin{textbox}
\lipsum[4]
\end{textbox}
\lipsum[4]
\begin{textbox}[linecolor=green, leftmargin=1.0cm]
\lipsum[4]
\end{textbox}
\end{document}

-