23

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.

0

5 Answers 5

16

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.
  \xdef\setroffset{\roffset=\the\leftskip \advance\roffset by \the\ruleoffset}%%
%% 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.
  \edef\thevfuzz{\the\vfuzz}\edef\thevbadness{\the\vbadness}%%
  \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.
    \vfuzz=10in\vbadness=10000%%
    \setbox0=\vsplit\rulebox to \splitheight\vbadness=\thevbadness%%
    \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%%
  \global\advance\splitheight by -\ht\savedbox%%
  \global\advance\splitheight by -\dp\savedbox%%
  \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}%%
  \interparskip=\ht0 \advance\interparskip by -\ht4 \advance\interparskip by -\ht2}%%
7
  • 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. Dec 14, 2011 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! Dec 14, 2011 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. Dec 17, 2011 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, 2011 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. Dec 17, 2011 at 4:42
15

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.

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

Another LaTeX solution.

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

enter image description here

The code (adjust the settings according to your needs):

\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}
6
  • Does the right side of the box has a white space?
    – enthu
    Nov 10, 2014 at 17:04
  • 1
    @EnthusiasticStudent I just suppresed it in my edit. Nov 10, 2014 at 17:06
  • What did you change for it?
    – enthu
    Nov 10, 2014 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. Nov 10, 2014 at 17:16
  • One last question, can you make the line thicker?
    – enthu
    Nov 10, 2014 at 17:18
9

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

\documentclass{article}
\RequirePackage{ltxkeys}
\makeatletter
\cptloadpackages{%
  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}}%
  \addtolength\ftb@letterheight{-\ftb@framerule}%
  \settowidth\@tempdima{\ftb@title\ \theruledtextblock}%
  \def\FrameCommand{%
    \hspace{-\ftb@leftgap}%
    {\color{\ftb@rulecolor}%
      \vrule width \ftb@framerule
    }%
    \hspace{\dimexpr\ftb@leftgap-\ftb@framerule\relax}%
  }%
  \MakeFramed{\advance\hsize-\width}%
  \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
}{%
  \@afterheading\par\noindent
  \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}

enter image description here

8
  • This works nicely but it does not reset the text color at the end.
    – André
    Mar 23, 2012 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, 2012 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, 2012 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, 2012 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, 2012 at 0:56
5

This can also be done with mdframed:

enter image description here

Notes:

  • The showframe package was used just to show the page margins. It is not needed in your actual use case.

References:

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}

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .